commit
a5ac60f201
|
@ -2,9 +2,15 @@ export default function (url) {
|
|||
return url
|
||||
.split("/")
|
||||
.map(part => {
|
||||
// if parameter, then use as is
|
||||
if (part.startsWith(":")) return part
|
||||
return encodeURIComponent(part.replace(/ /g, "-"))
|
||||
part = decodeURIComponent(part)
|
||||
part = part.replace(/ /g, "-")
|
||||
|
||||
// If parameter, then use as is
|
||||
if (!part.startsWith(":")) {
|
||||
part = encodeURIComponent(part)
|
||||
}
|
||||
|
||||
return part
|
||||
})
|
||||
.join("/")
|
||||
.toLowerCase()
|
||||
|
|
|
@ -6,15 +6,10 @@
|
|||
export let overlayEnabled = true
|
||||
|
||||
let imageError = false
|
||||
let imageLoaded = false
|
||||
|
||||
const imageRenderError = () => {
|
||||
imageError = true
|
||||
}
|
||||
|
||||
const imageLoadSuccess = () => {
|
||||
imageLoaded = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="template-card" style="background-color:{backgroundColour};">
|
||||
|
@ -23,8 +18,7 @@
|
|||
alt={name}
|
||||
src={imageSrc}
|
||||
on:error={imageRenderError}
|
||||
on:load={imageLoadSuccess}
|
||||
class={`${imageLoaded ? "loaded" : ""}`}
|
||||
class:error={imageError}
|
||||
/>
|
||||
<div style={`display:${imageError ? "block" : "none"}`}>
|
||||
<svg
|
||||
|
@ -104,15 +98,14 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.template-card img.loaded {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.template-card img {
|
||||
display: none;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
border-radius: var(--border-radius-s) 0px var(--border-radius-s) 0px;
|
||||
}
|
||||
.template-card img.error {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
background: var(--spectrum-alias-background-color-tertiary);
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
import PathTree from "./PathTree.svelte"
|
||||
|
||||
let routes = {}
|
||||
$: paths = Object.keys(routes || {}).sort()
|
||||
let paths = []
|
||||
|
||||
$: {
|
||||
const allRoutes = $store.routes
|
||||
$: allRoutes = $store.routes
|
||||
$: selectedScreenId = $store.selectedScreenId
|
||||
$: updatePaths(allRoutes, $selectedAccessRole, selectedScreenId)
|
||||
|
||||
const updatePaths = (allRoutes, selectedRoleId, selectedScreenId) => {
|
||||
const sortedPaths = Object.keys(allRoutes || {}).sort()
|
||||
const selectedRoleId = $selectedAccessRole
|
||||
const selectedScreenId = $store.selectedScreenId
|
||||
|
||||
let found = false
|
||||
let firstValidScreenId
|
||||
|
@ -41,11 +42,15 @@
|
|||
})
|
||||
})
|
||||
})
|
||||
routes = filteredRoutes
|
||||
routes = { ...filteredRoutes }
|
||||
paths = Object.keys(routes || {}).sort()
|
||||
|
||||
// Select the correct role for the current screen ID
|
||||
if (!found && screenRoleId) {
|
||||
selectedAccessRole.set(screenRoleId)
|
||||
if (screenRoleId !== selectedRoleId) {
|
||||
updatePaths(allRoutes, screenRoleId, selectedScreenId)
|
||||
}
|
||||
}
|
||||
|
||||
// If the selected screen isn't in this filtered list, select the first one
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
.filter(a => a.definition.trigger?.stepId === "APP")
|
||||
.map(automation => {
|
||||
const schema = Object.entries(
|
||||
automation.definition.trigger.inputs.fields
|
||||
automation.definition.trigger.inputs.fields || {}
|
||||
).map(([name, type]) => ({ name, type }))
|
||||
|
||||
return {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import { roles } from "stores/backend"
|
||||
|
||||
export let value
|
||||
export let error
|
||||
</script>
|
||||
|
||||
<Select
|
||||
|
@ -11,4 +12,5 @@
|
|||
options={$roles}
|
||||
getOptionLabel={role => role.name}
|
||||
getOptionValue={role => role._id}
|
||||
{error}
|
||||
/>
|
||||
|
|
|
@ -8,14 +8,50 @@
|
|||
import { currentAsset, store } from "builderStore"
|
||||
import { FrontendTypes } from "constants"
|
||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||
import { allScreens, selectedAccessRole } from "builderStore"
|
||||
|
||||
export let componentInstance
|
||||
export let bindings
|
||||
|
||||
function setAssetProps(name, value, parser) {
|
||||
if (parser && typeof parser === "function") {
|
||||
let errors = {}
|
||||
|
||||
const routeTaken = url => {
|
||||
const roleId = get(selectedAccessRole) || "BASIC"
|
||||
return get(allScreens).some(
|
||||
screen =>
|
||||
screen.routing.route.toLowerCase() === url.toLowerCase() &&
|
||||
screen.routing.roleId === roleId
|
||||
)
|
||||
}
|
||||
|
||||
const roleTaken = roleId => {
|
||||
const url = get(currentAsset)?.routing.route
|
||||
return get(allScreens).some(
|
||||
screen =>
|
||||
screen.routing.route.toLowerCase() === url.toLowerCase() &&
|
||||
screen.routing.roleId === roleId
|
||||
)
|
||||
}
|
||||
|
||||
const setAssetProps = (name, value, parser, validate) => {
|
||||
if (parser) {
|
||||
value = parser(value)
|
||||
}
|
||||
if (validate) {
|
||||
const error = validate(value)
|
||||
errors = {
|
||||
...errors,
|
||||
[name]: error,
|
||||
}
|
||||
if (error) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
errors = {
|
||||
...errors,
|
||||
[name]: null,
|
||||
}
|
||||
}
|
||||
|
||||
const selectedAsset = get(currentAsset)
|
||||
store.update(state => {
|
||||
|
@ -38,7 +74,6 @@
|
|||
}
|
||||
|
||||
const screenSettings = [
|
||||
// { key: "description", label: "Description", control: Input },
|
||||
{
|
||||
key: "routing.route",
|
||||
label: "Route",
|
||||
|
@ -49,8 +84,26 @@
|
|||
}
|
||||
return sanitizeUrl(val)
|
||||
},
|
||||
validate: val => {
|
||||
const exisingValue = get(currentAsset)?.routing.route
|
||||
if (val !== exisingValue && routeTaken(val)) {
|
||||
return "That URL is already in use for this role"
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "routing.roleId",
|
||||
label: "Access",
|
||||
control: RoleSelect,
|
||||
validate: val => {
|
||||
const exisingValue = get(currentAsset)?.routing.roleId
|
||||
if (val !== exisingValue && roleTaken(val)) {
|
||||
return "That role is already in use for this URL"
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
{ key: "routing.roleId", label: "Access", control: RoleSelect },
|
||||
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
||||
]
|
||||
</script>
|
||||
|
@ -62,9 +115,11 @@
|
|||
control={def.control}
|
||||
label={def.label}
|
||||
key={def.key}
|
||||
error="asdasds"
|
||||
value={deepGet($currentAsset, def.key)}
|
||||
onChange={val => setAssetProps(def.key, val, def.parser)}
|
||||
onChange={val => setAssetProps(def.key, val, def.parser, def.validate)}
|
||||
{bindings}
|
||||
props={{ error: errors[def.key] }}
|
||||
/>
|
||||
{/each}
|
||||
</DetailSummary>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { onMount } from "svelte"
|
||||
import { templates } from "stores/portal"
|
||||
|
||||
let loaded = false
|
||||
let loaded = $templates?.length
|
||||
let template
|
||||
let creationModal = false
|
||||
let creatingApp = false
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
let unpublishModal
|
||||
let iconModal
|
||||
let creatingApp = false
|
||||
let loaded = false
|
||||
let loaded = $apps?.length || $templates?.length
|
||||
let searchTerm = ""
|
||||
let cloud = $admin.cloud
|
||||
let appName = ""
|
||||
|
@ -292,8 +292,8 @@
|
|||
<div class="title">
|
||||
<div class="welcome">
|
||||
<Layout noPadding gap="XS">
|
||||
<Heading size="M">{welcomeHeader}</Heading>
|
||||
<Body size="S">
|
||||
<Heading size="L">{welcomeHeader}</Heading>
|
||||
<Body size="M">
|
||||
{welcomeBody}
|
||||
</Body>
|
||||
</Layout>
|
||||
|
@ -301,7 +301,7 @@
|
|||
<div class="buttons">
|
||||
<Button
|
||||
dataCy="create-app-btn"
|
||||
size="L"
|
||||
size="M"
|
||||
icon="Add"
|
||||
cta
|
||||
on:click={initiateAppCreation}
|
||||
|
@ -311,7 +311,7 @@
|
|||
{#if $apps?.length > 0}
|
||||
<Button
|
||||
icon="Experience"
|
||||
size="L"
|
||||
size="M"
|
||||
quiet
|
||||
secondary
|
||||
on:click={$goto("/builder/portal/apps/templates")}
|
||||
|
@ -348,7 +348,7 @@
|
|||
{#if enrichedApps.length}
|
||||
<Layout noPadding gap="S">
|
||||
<div class="title">
|
||||
<Detail size="L">My apps</Detail>
|
||||
<Detail size="L">Apps</Detail>
|
||||
{#if enrichedApps.length > 1}
|
||||
<div class="app-actions">
|
||||
{#if cloud}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import { onMount } from "svelte"
|
||||
import { templates } from "stores/portal"
|
||||
|
||||
let loaded = false
|
||||
let loaded = $templates?.length
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
.spectrum-Card-footer {
|
||||
word-wrap: anywhere;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.horizontal .spectrum-Card-coverPhoto {
|
||||
|
|
|
@ -125,7 +125,11 @@
|
|||
{#if schemaLoaded}
|
||||
<Block>
|
||||
<div class="card-list" use:styleable={$component.styles}>
|
||||
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}>
|
||||
<BlockComponent
|
||||
type="form"
|
||||
bind:id={formId}
|
||||
props={{ dataSource, disableValidation: true }}
|
||||
>
|
||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||
<div class="header" class:mobile={$context.device.mobile}>
|
||||
<div class="title">
|
||||
|
|
|
@ -106,7 +106,11 @@
|
|||
{#if schemaLoaded}
|
||||
<Block>
|
||||
<div class={size} use:styleable={$component.styles}>
|
||||
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}>
|
||||
<BlockComponent
|
||||
type="form"
|
||||
bind:id={formId}
|
||||
props={{ dataSource, disableValidation: true }}
|
||||
>
|
||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||
<div class="header" class:mobile={$context.device.mobile}>
|
||||
<div class="title">
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
color: var(--spectrum-global-color-gray-700) !important;
|
||||
}
|
||||
div :global(.apexcharts-datalabel) {
|
||||
fill: var(--spectrum-global-color-gray-800);
|
||||
fill: white;
|
||||
}
|
||||
div :global(.apexcharts-tooltip) {
|
||||
background-color: var(--spectrum-global-color-gray-200) !important;
|
||||
|
@ -45,4 +45,12 @@
|
|||
background-color: var(--spectrum-global-color-gray-100) !important;
|
||||
border-color: var(--spectrum-global-color-gray-300) !important;
|
||||
}
|
||||
div :global(.apexcharts-theme-dark .apexcharts-tooltip-text) {
|
||||
color: white;
|
||||
}
|
||||
div
|
||||
:global(.apexcharts-theme-dark
|
||||
.apexcharts-tooltip-series-group.apexcharts-active) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
export let disabled = false
|
||||
export let actionType = "Create"
|
||||
|
||||
// Not exposed as a builder setting. Used internally to disable validation
|
||||
// for fields rendered in things like search blocks.
|
||||
export let disableValidation = false
|
||||
|
||||
const context = getContext("context")
|
||||
const { API, fetchDatasourceSchema } = getContext("sdk")
|
||||
|
||||
|
@ -102,6 +106,7 @@
|
|||
{schema}
|
||||
{table}
|
||||
{initialValues}
|
||||
{disableValidation}
|
||||
>
|
||||
<slot />
|
||||
</InnerForm>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
export let size
|
||||
export let schema
|
||||
export let table
|
||||
export let disableValidation = false
|
||||
|
||||
const component = getContext("component")
|
||||
const { styleable, Provider, ActionTypes } = getContext("sdk")
|
||||
|
@ -141,12 +142,14 @@
|
|||
|
||||
// Create validation function based on field schema
|
||||
const schemaConstraints = schema?.[field]?.constraints
|
||||
const validator = createValidatorFromConstraints(
|
||||
schemaConstraints,
|
||||
validationRules,
|
||||
field,
|
||||
table
|
||||
)
|
||||
const validator = disableValidation
|
||||
? null
|
||||
: createValidatorFromConstraints(
|
||||
schemaConstraints,
|
||||
validationRules,
|
||||
field,
|
||||
table
|
||||
)
|
||||
|
||||
// If we've already registered this field then keep some existing state
|
||||
let initialValue = Helpers.deepGet(initialValues, field) ?? defaultValue
|
||||
|
@ -164,7 +167,7 @@
|
|||
// If this field has already been registered and we previously had an
|
||||
// error set, then re-run the validator to see if we can unset it
|
||||
if (fieldState.error) {
|
||||
initialError = validator(initialValue)
|
||||
initialError = validator?.(initialValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,7 +257,7 @@
|
|||
}
|
||||
|
||||
// Update field state
|
||||
const error = validator ? validator(value) : null
|
||||
const error = validator?.(value)
|
||||
fieldInfo.update(state => {
|
||||
state.fieldState.value = value
|
||||
state.fieldState.error = error
|
||||
|
@ -288,12 +291,14 @@
|
|||
|
||||
// Create new validator
|
||||
const schemaConstraints = schema?.[field]?.constraints
|
||||
const validator = createValidatorFromConstraints(
|
||||
schemaConstraints,
|
||||
validationRules,
|
||||
field,
|
||||
table
|
||||
)
|
||||
const validator = disableValidation
|
||||
? null
|
||||
: createValidatorFromConstraints(
|
||||
schemaConstraints,
|
||||
validationRules,
|
||||
field,
|
||||
table
|
||||
)
|
||||
|
||||
// Update validator
|
||||
fieldInfo.update(state => {
|
||||
|
|
|
@ -329,13 +329,13 @@ export const enrichButtonActions = (actions, context) => {
|
|||
return actions
|
||||
}
|
||||
|
||||
// Button context is built up as actions are executed.
|
||||
// Inherit any previous button context which may have come from actions
|
||||
// before a confirmable action since this breaks the chain.
|
||||
let buttonContext = context.actions || []
|
||||
|
||||
const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]])
|
||||
return async eventContext => {
|
||||
// Button context is built up as actions are executed.
|
||||
// Inherit any previous button context which may have come from actions
|
||||
// before a confirmable action since this breaks the chain.
|
||||
let buttonContext = context.actions || []
|
||||
|
||||
for (let i = 0; i < handlers.length; i++) {
|
||||
try {
|
||||
// Skip any non-existent action definitions
|
||||
|
@ -346,6 +346,7 @@ export const enrichButtonActions = (actions, context) => {
|
|||
// Built total context for this action
|
||||
const totalContext = {
|
||||
...context,
|
||||
state: get(stateStore),
|
||||
actions: buttonContext,
|
||||
eventContext,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue