feat(apps): add action on form/button/formbutton

This commit is contained in:
Ruben Fiszel
2023-03-21 18:22:20 +01:00
parent 95cbad49be
commit dfa9e333aa
18 changed files with 519 additions and 336 deletions
@@ -4,13 +4,13 @@
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import { loadIcon } from '../icon'
import { twMerge } from 'tailwind-merge'
import { initConfig, initOutput } from '../../editor/appUtils'
import { components, configurationKeys } from '../../editor/component'
import { components } from '../../editor/component'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
export let id: string
export let componentInput: AppInput | undefined
@@ -111,26 +111,27 @@
}
</script>
{#each configurationKeys['buttoncomponent'] as key (key)}
<InputValue
key={key + extraKey}
{#each Object.keys(components['buttoncomponent'].initialData.configuration) as key (key)}
<ResolveConfig
{id}
input={configuration[key]}
bind:value={resolvedConfig[key]}
{extraKey}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
/>
{/each}
<!-- gotoNewTab={resolvedConfig.onSuccess.selected == 'goto'} -->
<RunnableWrapper
bind:this={runnableWrapper}
{recomputeIds}
bind:runnableComponent
{componentInput}
doOnSuccess={resolvedConfig.onSuccess}
{id}
{extraQueryParams}
autoRefresh={false}
gotoUrl={resolvedConfig.goto}
gotoNewTab={resolvedConfig.gotoNewTab}
setTab={resolvedConfig.setTab}
{render}
{outputs}
{extraKey}
@@ -3,13 +3,13 @@
import { faUser } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import { Icon } from 'svelte-awesome'
import { initOutput } from '../../editor/appUtils'
import { initConfig, initOutput } from '../../editor/appUtils'
import { components } from '../../editor/component'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
@@ -31,12 +31,8 @@
loading: false
})
let labelValue: string = ''
let color: ButtonType.Color
let size: ButtonType.Size
let resolvedConfig = initConfig(components['formcomponent'].initialData.configuration)
let runnableComponent: RunnableComponent
let goto: string | undefined = undefined
let setTab: Array<{ id: string; index: number }> | undefined = undefined
let isLoading: boolean = false
@@ -58,11 +54,14 @@
$: css = concatCustomCss($app.css?.formcomponent, customCss)
</script>
<InputValue {id} input={configuration.goto} bind:value={goto} />
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<InputValue {id} input={configuration.color} bind:value={color} />
<InputValue {id} input={configuration.size} bind:value={size} />
<InputValue {id} input={configuration.setTab} bind:value={setTab} />
{#each Object.keys(components['formcomponent'].initialData.configuration) as key (key)}
<ResolveConfig
{id}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
/>
{/each}
<RunnableWrapper
{recomputeIds}
@@ -70,13 +69,12 @@
bind:runnableComponent
{componentInput}
{id}
gotoUrl={goto}
doOnSuccess={resolvedConfig.onSuccess}
{extraQueryParams}
autoRefresh={false}
forceSchemaDisplay={true}
runnableClass="!block"
runnableStyle={css?.container?.style}
{setTab}
{outputs}
>
<AlignWrapper {horizontalAlignment}>
@@ -110,10 +108,10 @@
on:click={() => {
runnableComponent?.runComponent()
}}
{size}
{color}
size={resolvedConfig.size}
color={resolvedConfig.color}
>
{labelValue}
{resolvedConfig.label}
</Button>
{/if}
</div>
@@ -13,7 +13,9 @@
import Portal from 'svelte-portal'
import Modal from '$lib/components/common/modal/Modal.svelte'
import { concatCustomCss } from '../../utils'
import { initOutput } from '../../editor/appUtils'
import { initConfig, initOutput } from '../../editor/appUtils'
import { components } from '../../editor/component'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
export let id: string
export let componentInput: AppInput | undefined
@@ -32,12 +34,8 @@
loading: false
})
let labelValue: string = ''
let color: ButtonType.Color
let size: ButtonType.Size
let resolvedConfig = initConfig(components['formbuttoncomponent'].initialData.configuration)
let runnableComponent: RunnableComponent
let disabled: boolean | undefined = undefined
let setTab: Array<{ id: string; index: number }> | undefined = undefined
let isLoading: boolean = false
let ownClick: boolean = false
@@ -71,21 +69,19 @@
$: css = concatCustomCss($app?.css?.formbuttoncomponent, customCss)
</script>
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<InputValue {id} input={configuration.color} bind:value={color} />
<InputValue {id} input={configuration.size} bind:value={size} />
<InputValue
{id}
input={configuration.disabled}
bind:value={disabled}
bind:error={errors.disabled}
/>
<InputValue {id} input={configuration.setTab} bind:value={setTab} />
{#each Object.keys(components['formbuttoncomponent'].initialData.configuration) as key (key)}
<ResolveConfig
{id}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
/>
{/each}
<Portal>
<Modal
{open}
title={labelValue}
title={resolvedConfig.label ?? ''}
class={css?.popup?.class}
style={css?.popup?.style}
on:canceled={() => {
@@ -106,7 +102,7 @@
forceSchemaDisplay={true}
runnableClass="!block"
{outputs}
{setTab}
doOnSuccess={resolvedConfig.onSuccess}
>
<div class="flex flex-col gap-2 px-4 w-full">
<div>
@@ -151,15 +147,15 @@
<div class="text-red-500 text-xs">{errorsMessage}</div>
{/if}
<Button
{disabled}
{size}
{color}
disabled={resolvedConfig.disabled ?? false}
size={resolvedConfig.size ?? 'md'}
color={resolvedConfig.color}
btnClasses={css?.button?.class ?? ''}
style={css?.button?.style ?? ''}
on:click={(e) => {
open = true
}}
>
{labelValue}
{resolvedConfig.label}
</Button>
</AlignWrapper>
@@ -2,14 +2,11 @@
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { initConfig, initOutput } from '../../editor/appUtils'
import { components, configurationKeys } from '../../editor/component'
import type { staticValues } from '../../editor/componentsPanel/componentStaticValues'
import { components, selectOptions } from '../../editor/component'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
import Loader from '../helpers/Loader.svelte'
type FitOption = (typeof staticValues)['objectFitOptions'][number]
import ResolveConfig from '../helpers/ResolveConfig.svelte'
export let id: string
export let configuration: RichConfigurations
@@ -17,7 +14,7 @@
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
const fit: Record<FitOption, string> = {
const fit: Record<string, string> = {
cover: 'object-cover',
contain: 'object-contain',
fill: 'object-fill'
@@ -31,8 +28,13 @@
$: css = concatCustomCss($app.css?.imagecomponent, customCss)
</script>
{#each configurationKeys['imagecomponent'] as key (key)}
<InputValue {key} {id} input={configuration[key]} bind:value={resolvedConfig[key]} />
{#each Object.keys(components['imagecomponent'].initialData.configuration) as key (key)}
<ResolveConfig
{id}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
/>
{/each}
{#if render}
@@ -0,0 +1,34 @@
<script lang="ts">
import { InputValue } from '.'
import type { RichConfiguration } from '../../types'
export let id: string
export let extraKey: string = ''
export let key: string
export let resolvedConfig: any | { type: 'oneOf'; configuration: any; selected: string }
export let configuration: RichConfiguration
$: configuration?.type == 'oneOf' && handleSelected(configuration.selected)
function handleSelected(selected: string) {
if (resolvedConfig?.selected != undefined && resolvedConfig?.selected != selected) {
resolvedConfig.selected = selected
}
}
</script>
{#if configuration?.type == 'oneOf' && resolvedConfig?.type == 'oneOf'}
{@const choice = resolvedConfig.selected}
{#each Object.keys(configuration.configuration?.[choice] ?? {}) as nestedKey (nestedKey)}
{#if resolvedConfig.configuration?.[choice] != undefined}
<InputValue
key={key + choice + nestedKey + extraKey}
{id}
input={configuration?.configuration?.[choice]?.[nestedKey]}
bind:value={resolvedConfig.configuration[choice][nestedKey]}
/>
{/if}
{/each}
{:else}
<InputValue key={key + extraKey} {id} input={configuration} bind:value={resolvedConfig} />
{/if}
@@ -7,6 +7,7 @@
import NonRunnableComponent from './NonRunnableComponent.svelte'
import RunnableComponent from './RunnableComponent.svelte'
import { goto } from '$app/navigation'
import { sendUserToast } from '$lib/utils'
export let componentInput: AppInput | undefined
export let id: string
@@ -19,9 +20,21 @@
export let forceSchemaDisplay: boolean = false
export let runnableClass = ''
export let runnableStyle = ''
export let gotoUrl: string | undefined = undefined
export let gotoNewTab: boolean | undefined = undefined
export let setTab: Array<{ id: string; index: number }> | undefined = undefined
export let doOnSuccess:
| {
selected: 'gotoUrl' | 'none' | 'setTab' | 'sendToast'
configuration: {
gotoUrl: { url: string | undefined; newTab: boolean | undefined }
setTab: {
setTab: { id: string; index: number }[] | undefined
}
sendToast: {
message: string | undefined
}
}
}
| undefined = undefined
export let render: boolean
export let recomputeIds: string[] = []
export let outputs: { result: Output<any>; loading: Output<boolean> }
@@ -46,19 +59,35 @@
}
export function onSuccess() {
if (Array.isArray(setTab)) {
setTab.forEach((tab) => {
const { id, index } = tab
$componentControl[id].setTab?.(index)
})
}
if (!doOnSuccess) return
if (gotoUrl && gotoUrl != '' && result?.error == undefined) {
if (gotoNewTab) {
window.open(gotoUrl, '_blank')
} else {
goto(gotoUrl)
if (doOnSuccess.selected == 'none') return
if (doOnSuccess.selected == 'setTab') {
if (Array.isArray(doOnSuccess.configuration.setTab.setTab)) {
doOnSuccess.configuration.setTab?.setTab?.forEach((tab) => {
if (tab) {
const { id, index } = tab
$componentControl[id].setTab?.(index)
}
})
}
} else if (
doOnSuccess.selected == 'gotoUrl' &&
doOnSuccess.configuration.gotoUrl.url &&
doOnSuccess.configuration.gotoUrl.url != ''
) {
if (doOnSuccess.configuration.gotoUrl.newTab) {
window.open(doOnSuccess.configuration.gotoUrl.url, '_blank')
} else {
goto(doOnSuccess.configuration.gotoUrl.url)
}
} else if (
doOnSuccess.selected == 'sendToast' &&
doOnSuccess.configuration.sendToast.message &&
doOnSuccess.configuration.sendToast.message != ''
) {
sendUserToast(doOnSuccess.configuration.sendToast.message)
}
if (recomputeIds) {
@@ -5,8 +5,7 @@ import type {
ConnectingInput,
EditorBreakpoint,
FocusedGrid,
GridItem,
StaticRichConfigurations
GridItem
} from '../types'
import {
ccomponents,
@@ -14,7 +13,7 @@ import {
getRecommendedDimensionsByComponent,
type AppComponent,
type BaseComponent,
type TypedComponent
type InitialAppComponent
} from './component'
import { gridColumns } from '../gridUtils'
import { allItems } from '../utils'
@@ -94,8 +93,6 @@ export function getAllRecomputeIdsForComponent(app: App, id: string) {
}
export function createNewGridItem(grid: GridItem[], id: string, data: AppComponent): GridItem {
console.log('INSERT X')
const newComponent = {
fixed: false,
x: 0,
@@ -135,24 +132,45 @@ export function getGridItems(app: App, focusedGrid: FocusedGrid | undefined): Gr
}
}
function cleanseValue(key: string, value: { type: 'eval' | 'static'; value?: any; expr?: string }) {
if (!value) {
return [key, undefined]
}
if (value.type === 'static') {
return [key, { type: value.type, value: value.value }]
} else {
return [key, { type: value.type, expr: value.expr }]
}
}
export function appComponentFromType<T extends keyof typeof components>(
type: T
): (id: string) => BaseAppComponent & BaseComponent<T> {
return (id: string) => {
const init = ccomponents[type].initialData
const init = JSON.parse(JSON.stringify(ccomponents[type].initialData)) as InitialAppComponent
return {
type,
//TODO remove tooltip and onlyStatic from there
configuration: Object.fromEntries(
Object.entries(init.configuration).map(([key, value]) => {
if (value.ctype === undefined) {
if (value.type === 'static') {
return [key, { type: value.type, value: value.value }]
} else if (value.type === 'eval') {
return [key, { type: value.type, expr: value.expr }]
}
if (value.type != 'oneOf') {
return cleanseValue(key, value)
} else {
return [
key,
{
type: value.type,
selected: value.selected,
configuration: Object.fromEntries(
Object.entries(value.configuration).map(([key, val]) => [
key,
Object.fromEntries(
Object.entries(val).map(([key, val]) => cleanseValue(key, val))
)
])
)
}
]
}
return [key, value]
})
),
componentInput: init.componentInput,
@@ -370,16 +388,61 @@ export function initOutput<I extends Record<string, any>>(
) as Outputtable<I>
}
export function initConfig<T extends Record<string, StaticAppInput | EvalAppInput>>(
export function initConfig<
T extends Record<
string,
| StaticAppInput
| EvalAppInput
| {
type: 'oneOf'
selected: string
configuration: Record<string, Record<string, StaticAppInput | EvalAppInput>>
}
>
>(
r: T
): {
[Property in keyof T]: T[Property] extends StaticAppInput ? T[Property]['value'] | undefined : any
[Property in keyof T]: T[Property] extends StaticAppInput
? T[Property]['value'] | undefined
: T[Property] extends { type: 'oneOf' }
? {
type: 'oneOf'
selected: keyof T[Property]['configuration']
configuration: {
[Choice in keyof T[Property]['configuration']]: {
[IT in keyof T[Property]['configuration'][Choice]]: T[Property]['configuration'][Choice][IT] extends StaticAppInput
? T[Property]['configuration'][Choice][IT]['value'] | undefined
: undefined
}
}
}
: undefined
} {
return Object.fromEntries(
Object.entries(r).map(([key, value]) =>
value.type == 'static' ? [key, undefined] : [key, undefined]
return JSON.parse(
JSON.stringify(
Object.fromEntries(
Object.entries(r).map(([key, value]) =>
value.type == 'static'
? [key, undefined]
: value.type == 'oneOf'
? [
key,
{
selected: value.selected,
type: 'oneOf',
configuration: Object.fromEntries(
Object.entries(value.configuration).map(([choice, config]) => [
choice,
initConfig(config)
])
)
}
]
: [key, undefined]
)
) as any
)
) as any
)
}
export function expandGriditem(
@@ -1,5 +1,7 @@
import type { IntRange } from '../../../../common'
import type { NARROW_GRID_COLUMNS, WIDE_GRID_COLUMNS } from '../../gridUtils'
import { BUTTON_COLORS } from '../../../common'
import { defaultAlignement } from '../componentsPanel/componentDefaultProps'
import {
BarChart4,
@@ -187,6 +189,80 @@ export interface InitialAppComponent extends Partial<Aligned> {
panes?: number[]
}
const buttonColorOptions = [...BUTTON_COLORS]
export const selectOptions = {
buttonColorOptions,
tabsKindOptions: ['tabs', 'sidebar', 'invisibleOnView'],
buttonSizeOptions: ['xs', 'sm', 'md', 'lg', 'xl'],
tableSearchOptions: ['By Component', 'By Runnable', 'Disabled'],
chartThemeOptions: ['theme1', 'theme2', 'theme3'],
textStyleOptions: ['Title', 'Subtitle', 'Body', 'Label', 'Caption'],
currencyOptions: ['USD', 'EUR', 'GBP', 'CAD', 'AUD', 'JPY', 'CNY', 'INR', 'BRL'],
localeOptions: [
'en-US',
'en-GB',
'en-IE',
'de-DE',
'fr-FR',
'br-FR',
'ja-JP',
'pt-TL',
'fr-CA',
'en-CA'
],
objectFitOptions: ['contain', 'cover', 'fill'],
splitPanelOptions: ['2', '3', '4']
}
const onSuccessClick = {
type: 'oneOf',
tooltip: 'Action to perform on success',
selected: 'none',
labels: {
none: 'Do nothing',
gotoUrl: 'Go to an url',
setTab: 'Set the tab of a tabs component',
sendToast: 'Display a toast notification'
},
configuration: {
none: {},
gotoUrl: {
url: {
tooltip: 'Go to the given url, absolute or relative',
fieldType: 'text',
type: 'static',
value: '',
placeholder: '/apps/get/foo'
},
newTab: {
tooltip: 'Open the url in a new tab',
fieldType: 'boolean',
type: 'static',
value: true
}
},
setTab: {
setTab: {
type: 'static',
value: [] as Array<{ id: string; index: number }>,
fieldType: 'array',
subFieldType: 'tab-select',
tooltip: 'Set the tabs id and index to go to on success'
}
},
sendToast: {
message: {
tooltip: 'The message of the toast to diplay',
fieldType: 'text',
type: 'static',
value: '',
placeholder: 'Hello there'
}
}
}
} as const
export const components = {
displaycomponent: {
name: 'Rich Result',
@@ -242,7 +318,7 @@ export const components = {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'textStyleOptions',
selectOptions: selectOptions.textStyleOptions,
value: 'Body'
},
copyButton: {
@@ -288,14 +364,14 @@ export const components = {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'buttonColorOptions',
selectOptions: selectOptions.buttonColorOptions,
value: 'blue'
},
size: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'buttonSizeOptions',
selectOptions: selectOptions.buttonSizeOptions,
value: 'xs'
},
fillContainer: {
@@ -309,18 +385,7 @@ export const components = {
type: 'eval',
expr: 'false'
},
goto: {
tooltip: 'Go to an url on success if not empty',
fieldType: 'text',
type: 'static',
value: ''
},
gotoNewTab: {
tooltip: 'Go to an url on a new tab',
fieldType: 'boolean',
type: 'static',
value: true
},
onSuccess: onSuccessClick,
beforeIcon: {
type: 'static',
value: undefined,
@@ -338,13 +403,6 @@ export const components = {
value: false,
fieldType: 'boolean',
onlyStatic: true
},
setTab: {
type: 'static',
value: [] as Array<{ id: string; index: number }>,
fieldType: 'array',
subFieldType: 'tab-select',
tooltip: 'Set the tabs id and index to go to on success'
}
}
}
@@ -367,13 +425,6 @@ export const components = {
},
recomputeIds: undefined,
configuration: {
setTab: {
type: 'static',
value: [] as Array<{ id: string; index: number }>,
fieldType: 'array',
subFieldType: 'tab-select',
tooltip: 'Set the tabs id and index to go to on success'
},
label: {
type: 'static',
value: 'Submit',
@@ -384,21 +435,16 @@ export const components = {
type: 'static',
onlyStatic: true,
value: 'dark',
optionValuesKey: 'buttonColorOptions'
selectOptions: selectOptions.buttonColorOptions
},
size: {
fieldType: 'select',
type: 'static',
value: 'xs',
onlyStatic: true,
optionValuesKey: 'buttonSizeOptions'
selectOptions: selectOptions.buttonSizeOptions
},
goto: {
tooltip: 'Go to an url on success if not empty',
fieldType: 'text',
type: 'static',
value: ''
}
onSuccess: onSuccessClick
}
}
},
@@ -431,21 +477,20 @@ export const components = {
type: 'static',
onlyStatic: true,
value: 'dark',
optionValuesKey: 'buttonColorOptions'
selectOptions: buttonColorOptions
},
size: {
fieldType: 'select',
type: 'static',
value: 'xs',
onlyStatic: true,
optionValuesKey: 'buttonSizeOptions'
selectOptions: selectOptions.buttonSizeOptions
},
setTab: {
type: 'static',
value: [] as Array<{ id: string; index: number }>,
fieldType: 'array',
subFieldType: 'tab-select',
tooltip: 'Set the tabs id and index to go to on success'
onSuccess: onSuccessClick,
disabled: {
fieldType: 'boolean',
type: 'eval',
expr: 'false'
}
}
}
@@ -463,7 +508,7 @@ export const components = {
type: 'static',
onlyStatic: true,
fieldType: 'select',
optionValuesKey: 'chartThemeOptions',
selectOptions: selectOptions.chartThemeOptions,
value: 'theme1'
},
doughnutStyle: {
@@ -493,7 +538,7 @@ export const components = {
type: 'static',
onlyStatic: true,
fieldType: 'select',
optionValuesKey: 'chartThemeOptions',
selectOptions: selectOptions.chartThemeOptions,
value: 'theme1'
},
line: {
@@ -732,7 +777,7 @@ Hello \${ctx.username}
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'tableSearchOptions',
selectOptions: selectOptions.tableSearchOptions,
value: 'Disabled'
}
},
@@ -1026,14 +1071,14 @@ Hello \${ctx.username}
value: 'USD',
fieldType: 'select',
onlyStatic: true,
optionValuesKey: 'currencyOptions'
selectOptions: selectOptions.currencyOptions
},
locale: {
type: 'static',
value: 'en-US',
fieldType: 'select',
onlyStatic: true,
optionValuesKey: 'localeOptions',
selectOptions: selectOptions.localeOptions,
tooltip: 'Currency format'
}
}
@@ -1217,7 +1262,7 @@ Hello \${ctx.username}
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'tabsKindOptions',
selectOptions: selectOptions.tabsKindOptions,
value: 'tabs'
}
},
@@ -1375,7 +1420,7 @@ Hello \${ctx.username}
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'objectFitOptions',
selectOptions: selectOptions.objectFitOptions,
value: 'contain'
},
altText: {
@@ -1415,14 +1460,14 @@ Hello \${ctx.username}
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'buttonColorOptions',
selectOptions: buttonColorOptions,
value: 'blue'
},
size: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'buttonSizeOptions',
selectOptions: selectOptions.buttonSizeOptions,
value: 'xs'
},
fillContainer: {
@@ -1555,30 +1600,6 @@ Hello \${ctx.username}
}
} as const
export const configurationKeys: Record<AppComponent['type'], string[]> = Object.fromEntries(
Object.entries(components)
.map(([k, v]) => [
k,
[
...new Set(
Object.entries(v.initialData.configuration).flatMap(([k, v]) => {
if (!v.ctype) {
return [k]
} else if (v.ctype == 'oneOf') {
return [
k,
...Object.values(v.configuration ?? {}).flatMap((v) => Object.keys(v ?? {}))
]
} else if (v.ctype == 'group') {
return Object.keys(v.configuration ?? {})
}
})
)
]
])
.filter(([k, v]) => v != undefined)
)
export const ccomponents: {
[Property in keyof typeof components]: AppComponentConfig<Property>
} = components
@@ -14,7 +14,6 @@
import { X } from 'lucide-svelte'
import { push } from '$lib/history'
import { flip } from 'svelte/animate'
import { Badge } from '$lib/components/common'
const { app, selectedComponent, focusedGrid, worldStore } =
getContext<AppViewerContext>('AppViewerContext')
@@ -1,27 +0,0 @@
import { BUTTON_COLORS } from '../../../common'
const buttonColorOptions = [...BUTTON_COLORS]
export const staticValues = {
buttonColorOptions,
tabsKindOptions: ['tabs', 'sidebar', 'invisibleOnView'],
buttonSizeOptions: ['xs', 'sm', 'md', 'lg', 'xl'],
tableSearchOptions: ['By Component', 'By Runnable', 'Disabled'],
chartThemeOptions: ['theme1', 'theme2', 'theme3'],
textStyleOptions: ['Title', 'Subtitle', 'Body', 'Label', 'Caption'],
currencyOptions: ['USD', 'EUR', 'GBP', 'CAD', 'AUD', 'JPY', 'CNY', 'INR', 'BRL'],
localeOptions: [
'en-US',
'en-GB',
'en-IE',
'de-DE',
'fr-FR',
'br-FR',
'ja-JP',
'pt-TL',
'fr-CA',
'en-CA'
],
objectFitOptions: ['contain', 'cover', 'fill'],
splitPanelOptions: ['2', '3', '4']
} as const
@@ -1,19 +1,19 @@
<script lang="ts">
import { Button } from '$lib/components/common'
import { faPlus, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { X } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import type { InputType, StaticAppInput, StaticInput } from '../../inputType'
import { staticValues } from '../componentsPanel/componentStaticValues'
import { fade } from 'svelte/transition'
import type { InputType, StaticAppInput, StaticInput, StaticOptions } from '../../inputType'
import SubTypeEditor from './SubTypeEditor.svelte'
export let componentInput: StaticInput<any[]>
export let subFieldType: InputType | undefined = undefined
export let optionValuesKey: keyof typeof staticValues | undefined = undefined
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
const dispatch = createEventDispatcher()
function addElementByType() {
console.log('ADD')
if (!Array.isArray(componentInput.value)) {
componentInput.value = []
}
@@ -38,14 +38,12 @@
subFieldType === 'datetime'
) {
value.push('')
} else if (subFieldType === 'select' && optionValuesKey) {
value.push(staticValues[optionValuesKey][0])
} else if (subFieldType === 'select' && selectOptions) {
value.push(selectOptions[0])
}
} else {
value.push('')
}
console.log(value, componentInput.value)
componentInput = componentInput
}
@@ -64,19 +62,17 @@
{#if Array.isArray(componentInput.value)}
{#each componentInput.value as value, index (index)}
<div class="flex flex-row gap-2 items-center relative">
<SubTypeEditor {subFieldType} bind:componentInput bind:value />
<div class="absolute top-1 right-1">
<Button
size="xs"
color="light"
variant="border"
on:click={() => deleteElementByType(index)}
iconOnly
btnClasses="!text-red-500"
startIcon={{ icon: faTrashAlt }}
/>
<div>
<SubTypeEditor {subFieldType} bind:componentInput bind:value />
</div>
<button
transition:fade|local={{ duration: 100 }}
class="z-10 rounded-full p-1 duration-200 hover:bg-gray-200"
aria-label="Remove item"
on:click|preventDefault|stopPropagation={() => deleteElementByType(index)}
>
<X size={14} />
</button>
</div>
{/each}
{/if}
@@ -14,7 +14,6 @@
import { getContext } from 'svelte'
import type { AppViewerContext, RichConfiguration } from '../../types'
import type { InputType } from '../../inputType'
import type { staticValues } from '../componentsPanel/componentStaticValues'
export let id: string
export let componentInput: RichConfiguration
@@ -28,112 +27,120 @@
export let fieldType: InputType
export let subFieldType: InputType | undefined
export let format: string | undefined
export let optionValuesKey: keyof typeof staticValues | undefined
export let selectOptions: string[] | undefined
export let placeholder: string | undefined
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
$: if (componentInput == undefined) {
//@ts-ignore
componentInput = {
type: 'static',
value: undefined
}
}
</script>
{#if componentInput?.ctype == undefined}
{#if !(resourceOnly && (fieldType !== 'object' || !format?.startsWith('resource-')))}
<div class="flex flex-col gap-1">
<div class="flex justify-between items-end gap-1">
<span class="text-sm font-semibold truncate">
{shouldCapitalize ? capitalize(addWhitespaceBeforeCapitals(key)) : key}
{#if tooltip}
<Tooltip>
{tooltip}
</Tooltip>
{/if}
</span>
{#if !(resourceOnly && (fieldType !== 'object' || !format?.startsWith('resource-')))}
<div class="flex flex-col gap-1">
<div class="flex justify-between items-end gap-1">
<span class="text-sm font-semibold truncate">
{shouldCapitalize ? capitalize(addWhitespaceBeforeCapitals(key)) : key}
{#if tooltip}
<Tooltip>
{tooltip}
</Tooltip>
{/if}
</span>
<div class="flex gap-x-2 gap-y-1 flex-wrap justify-end items-center">
<Badge color="blue">
{fieldType === 'array' && subFieldType
? `${capitalize(fieldTypeToTsType(subFieldType))}[]`
: capitalize(fieldTypeToTsType(fieldType))}
</Badge>
<div class="flex gap-x-2 gap-y-1 flex-wrap justify-end items-center">
<Badge color="blue">
{fieldType === 'array' && subFieldType
? `${capitalize(fieldTypeToTsType(subFieldType))}[]`
: capitalize(fieldTypeToTsType(fieldType))}
</Badge>
{#if !onlyStatic && componentInput?.type && componentInput.type != 'eval'}
<ToggleButtonGroup
bind:selected={componentInput.type}
on:selected={(e) => {
if (e.detail == 'connected' && !componentInput['connection']) {
$connectingInput = {
opened: true,
input: undefined,
hoveredComponent: undefined
}
{#if !onlyStatic && componentInput?.type && componentInput.type != 'eval'}
<ToggleButtonGroup
bind:selected={componentInput.type}
on:selected={(e) => {
if (e.detail == 'connected' && !componentInput['connection']) {
$connectingInput = {
opened: true,
input: undefined,
hoveredComponent: undefined
}
}}
>
}
}}
>
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="left"
value="static"
startIcon={{ icon: faPen }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Static</svelte:fragment>
</Popover>
{#if userInputEnabled && !format?.startsWith('resource-')}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="left"
value="static"
startIcon={{ icon: faPen }}
position="center"
value="user"
startIcon={{ icon: faUser }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Static</svelte:fragment>
<svelte:fragment slot="text">User Input</svelte:fragment>
</Popover>
{#if userInputEnabled && !format?.startsWith('resource-')}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="user"
startIcon={{ icon: faUser }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">User Input</svelte:fragment>
</Popover>
{/if}
{#if 'fileUpload' in componentInput}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="upload"
startIcon={{ icon: faUpload }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Upload</svelte:fragment>
</Popover>
{/if}
{/if}
{#if 'fileUpload' in componentInput}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="right"
value="connected"
startIcon={{ icon: faArrowRight }}
position="center"
value="upload"
startIcon={{ icon: faUpload }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Connect</svelte:fragment>
<svelte:fragment slot="text">Upload</svelte:fragment>
</Popover>
</ToggleButtonGroup>
{/if}
</div>
{/if}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="right"
value="connected"
startIcon={{ icon: faArrowRight }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Connect</svelte:fragment>
</Popover>
</ToggleButtonGroup>
{/if}
</div>
{#if componentInput?.type === 'connected'}
<ConnectedInputEditor bind:componentInput />
{:else if componentInput?.type === 'row'}
<RowInputEditor bind:componentInput />
{:else if componentInput?.type === 'static'}
<StaticInputEditor
{fieldType}
{subFieldType}
{optionValuesKey}
{format}
bind:componentInput
/>
{:else if componentInput?.type === 'eval'}
<EvalInputEditor {hasRows} {id} bind:componentInput />
{:else if componentInput?.type === 'upload'}
<UploadInputEditor bind:componentInput />
{:else if componentInput?.type === 'user'}
<span class="text-2xs italic text-gray-600">Field's value is set by the user</span>
{/if}
</div>
{/if}
{#if componentInput?.type === 'connected'}
<ConnectedInputEditor bind:componentInput />
{:else if componentInput?.type === 'row'}
<RowInputEditor bind:componentInput />
{:else if componentInput?.type === 'static'}
<StaticInputEditor
{fieldType}
{subFieldType}
{selectOptions}
{format}
{placeholder}
bind:componentInput
/>
{:else if componentInput?.type === 'eval'}
<EvalInputEditor {hasRows} {id} bind:componentInput />
{:else if componentInput?.type === 'upload'}
<UploadInputEditor bind:componentInput />
{:else if componentInput?.type === 'user'}
<span class="text-2xs italic text-gray-600">Field's value is set by the user</span>
{/if}
</div>
{/if}
@@ -2,6 +2,7 @@
import type { RichConfigurations } from '../../types'
import InputsSpecEditor from './InputsSpecEditor.svelte'
import OneOfInputSpecsEditor from './OneOfInputSpecsEditor.svelte'
export let id: string
export let inputSpecs: RichConfigurations
@@ -15,7 +16,19 @@
{#if inputSpecs}
<div class="w-full flex flex-col gap-4">
{#each Object.keys(inputSpecsConfiguration) as k}
{#if inputSpecs[k] && inputSpecs[k]?.ctype == undefined}
{#if inputSpecs[k] && inputSpecs[k]?.type == 'oneOf'}
<OneOfInputSpecsEditor
key={k}
bind:oneOf={inputSpecs[k]}
{id}
{shouldCapitalize}
{resourceOnly}
{rowColumns}
inputSpecsConfiguration={inputSpecsConfiguration?.[k]?.['configuration']}
labels={inputSpecsConfiguration?.[k]?.['labels']}
tooltip={inputSpecsConfiguration?.[k]?.['tooltip']}
/>
{:else}
{@const meta = inputSpecsConfiguration?.[k]}
<!-- {JSON.stringify(meta)} -->
<InputsSpecEditor
@@ -29,9 +42,10 @@
fieldType={meta?.['fieldType']}
subFieldType={meta?.['subFieldType']}
format={meta?.['format']}
optionValuesKey={meta?.['optionValuesKey']}
selectOptions={meta?.['selectOptions']}
tooltip={meta?.['tooltip']}
onlyStatic={meta?.['onlyStatic']}
placeholder={meta?.['placeholder']}
/>
{/if}
{/each}
@@ -0,0 +1,56 @@
<script lang="ts">
import Tooltip from '$lib/components/Tooltip.svelte'
import type { RichConfiguration } from '../../types'
import InputsSpecEditor from './InputsSpecEditor.svelte'
export let key: string
export let oneOf: { selected: string; configuration: RichConfiguration } | any
export let inputSpecsConfiguration: RichConfiguration | any
export let labels: Record<string, string> | undefined
export let shouldCapitalize: boolean
export let id: string
export let resourceOnly: boolean
export let rowColumns: boolean
export let tooltip: string | undefined
$: if (oneOf.configuration[oneOf.selected] == undefined) {
oneOf.configuration[oneOf.selected] = {}
}
</script>
<div class="p-2 border">
<h4 class="mb-2">{key}&nbsp;<Tooltip>{tooltip}</Tooltip></h4>
<select
class="w-full border border-gray-300 rounded-md p-2"
value={oneOf.selected}
on:change={(e) => {
oneOf = { ...oneOf, selected: e?.target?.['value'] }
}}
>
{#each Object.keys(inputSpecsConfiguration ?? {}) as choice}
<option value={choice}>{labels?.[choice] ?? choice}</option>
{/each}
</select>
<div class="mb-4" />
<div class="flex flex-col gap-4">
{#each Object.keys(inputSpecsConfiguration?.[oneOf.selected] ?? {}) as nestedKey}
{@const config = inputSpecsConfiguration?.[oneOf.selected]?.[nestedKey]}
{#if config && oneOf.configuration[oneOf.selected]}
<InputsSpecEditor
key={nestedKey}
bind:componentInput={oneOf.configuration[oneOf.selected][nestedKey]}
{id}
userInputEnabled={false}
{shouldCapitalize}
{resourceOnly}
hasRows={rowColumns}
fieldType={config?.['fieldType']}
subFieldType={config?.['subFieldType']}
format={config?.['format']}
selectOptions={config?.['selectOptions']}
placeholder={config?.['placeholder']}
/>
{/if}
{/each}
</div>
</div>
@@ -1,7 +1,6 @@
<script lang="ts">
import Toggle from '$lib/components/Toggle.svelte'
import { staticValues } from '../../componentsPanel/componentStaticValues'
import type { InputType, StaticInput } from '../../../inputType'
import type { InputType, StaticInput, StaticOptions } from '../../../inputType'
import ArrayStaticInputEditor from '../ArrayStaticInputEditor.svelte'
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
import JsonEditor from './JsonEditor.svelte'
@@ -16,7 +15,9 @@
export let componentInput: StaticInput<any> | undefined
export let fieldType: InputType | undefined = undefined
export let subFieldType: InputType | undefined = undefined
export let optionValuesKey: keyof typeof staticValues | undefined = undefined
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
export let placeholder: string | undefined = undefined
export let format: string | undefined = undefined
export let noVariablePicker: boolean = false
@@ -35,12 +36,18 @@
<input on:keydown|stopPropagation type="date" bind:value={componentInput.value} />
{:else if fieldType === 'boolean'}
<Toggle bind:checked={componentInput.value} />
{:else if fieldType === 'select' && optionValuesKey}
{:else if fieldType === 'select' && selectOptions}
<select on:keydown|stopPropagation on:keydown|stopPropagation bind:value={componentInput.value}>
{#each staticValues[optionValuesKey] || [] as option}
<option value={option}>
{option}
</option>
{#each selectOptions ?? [] as option}
{#if typeof option == 'string'}
<option value={option}>
{option}
</option>
{:else}
<option value={option.value}>
{option.label}
</option>
{/if}
{/each}
</select>
{:else if fieldType === 'icon-select'}
@@ -108,7 +115,7 @@
<input
on:keydown|stopPropagation
type="text"
placeholder="Static value"
placeholder={placeholder ?? 'Static value'}
bind:value={componentInput.value}
/>
{#if !noVariablePicker}
@@ -24,7 +24,6 @@
$: onLoad =
!onClick ||
(appComponent?.configuration?.triggerOnAppLoad != undefined &&
appComponent?.configuration?.triggerOnAppLoad?.ctype === undefined &&
appComponent.configuration.triggerOnAppLoad.type == 'static' &&
appComponent.configuration.triggerOnAppLoad.value)
</script>
+2 -15
View File
@@ -1,5 +1,4 @@
import type { ReadFileAs } from '../common/fileInput/model'
import type { staticValues } from './editor/componentsPanel/componentStaticValues'
import type { InlineScript } from './types'
export type InputType =
@@ -122,11 +121,8 @@ type InputConfiguration<T extends InputType, V extends InputType> = {
}
}
type StaticOptions = {
/**
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
*/
optionValuesKey: keyof typeof staticValues
export type StaticOptions = {
selectOptions: string[] | { value: string; label: string }[]
}
export type AppInput =
@@ -141,12 +137,6 @@ export type AppInput =
| AppInputSpec<'any', any>
| AppInputSpec<'object', Record<string | number, any>>
| AppInputSpec<'object', string>
| (AppInputSpec<'select', string> & {
/**
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
*/
optionValuesKey: keyof typeof staticValues
})
| (AppInputSpec<'select', string> & StaticOptions)
| AppInputSpec<'icon-select', string>
| AppInputSpec<'color', string>
@@ -158,9 +148,6 @@ export type AppInput =
| AppInputSpec<'array', string[], 'time'>
| AppInputSpec<'array', string[], 'datetime'>
| AppInputSpec<'array', object[], 'object'>
| (AppInputSpec<'array', string[], 'select'> & {
optionValuesKey: keyof typeof staticValues
})
| (AppInputSpec<'array', string[], 'select'> & StaticOptions)
| AppInputSpec<'array', object[], 'labeledresource'>
| AppInputSpec<'labeledresource', object>
+5 -4
View File
@@ -29,6 +29,7 @@ export type Aligned = {
export interface GeneralAppInput {
onlyStatic?: boolean
tooltip?: string
placeholder?: string
}
export type ComponentCssProperty = {
@@ -51,14 +52,14 @@ export type Configuration =
export type StaticConfiguration = GeneralAppInput & StaticAppInput
export type RichConfigurationT<T> =
| (T & { ctype?: undefined })
| (T & { type: AppInput['type'] })
| {
ctype: 'oneOf'
type: 'oneOf'
selected: string
tooltip?: string
labels?: Record<string, string>
configuration: Record<string, Record<string, T>>
}
| { ctype: 'group'; title: string; configuration: Record<string, T> }
export type RichConfiguration = RichConfigurationT<Configuration>
export type RichConfigurations = Record<string, RichConfiguration>