fix reactivity for static inputs

This commit is contained in:
Ruben Fiszel
2023-03-17 20:50:19 +01:00
parent 7178d0264d
commit a6db050bcc
8 changed files with 169 additions and 158 deletions
@@ -75,8 +75,6 @@
}}
/>
{initialValue}
{value}
<div class="flex flex-row gap-x-1 w-full">
<Select
value={valueSelect}
@@ -12,6 +12,7 @@
import { twMerge } from 'tailwind-merge'
import { goto } from '$app/navigation'
import { initOutput } from '../../editor/appUtils'
import { configurationKeys } from '../../editor/component'
export let id: string
export let componentInput: AppInput | undefined
@@ -35,37 +36,52 @@
$componentControl[id] = controls
}
let labelValue: string
let color: ButtonType.Color
let size: ButtonType.Size
let runnableComponent: RunnableComponent
let disabled: boolean | undefined = undefined
let fillContainer: boolean | undefined = undefined
let gotoUrl: string | undefined = undefined
let gotoNewTab: boolean | undefined = undefined
// let labelValue: string
// let color: ButtonType.Color
// let size: ButtonType.Size
// let disabled: boolean | undefined = undefined
// let fillContainer: boolean | undefined = undefined
// let gotoUrl: string | undefined = undefined
// let gotoNewTab: boolean | undefined = undefined
let isLoading: boolean = false
let ownClick: boolean = false
let triggerOnAppLoad = false
let beforeIcon: undefined | string = undefined
let afterIcon: undefined | string = undefined
// let beforeIcon: undefined | string = undefined
// let afterIcon: undefined | string = undefined
let resolvedConfig = {} as {
beforeIcon?: string
afterIcon?: string
label?: string
color?: ButtonType.Color
size?: ButtonType.Size
disabled?: boolean
fillContainer?: boolean
gotoUrl?: string
gotoNewTab?: boolean
triggerOnAppLoad?: boolean
}
$: initializing = resolvedConfig?.label == undefined
let beforeIconComponent: any
let afterIconComponent: any
$: beforeIcon && handleBeforeIcon()
$: afterIcon && handleAfterIcon()
$: resolvedConfig.beforeIcon && handleBeforeIcon()
$: resolvedConfig.afterIcon && handleAfterIcon()
async function handleBeforeIcon() {
if (beforeIcon) {
beforeIconComponent = await loadIcon(beforeIcon)
if (resolvedConfig.beforeIcon) {
beforeIconComponent = await loadIcon(resolvedConfig.beforeIcon)
}
}
async function handleAfterIcon() {
if (afterIcon) {
afterIconComponent = await loadIcon(afterIcon)
if (resolvedConfig.afterIcon) {
afterIconComponent = await loadIcon(resolvedConfig.afterIcon)
}
}
@@ -74,7 +90,7 @@
loading: false
})
$: triggerOnAppLoad && runnableComponent?.runComponent()
$: resolvedConfig.triggerOnAppLoad && runnableComponent?.runComponent()
$: if (outputs?.loading != undefined) {
outputs.loading.set(false, true)
@@ -107,11 +123,11 @@
ownClick = true
if (!runnableComponent) {
if (gotoUrl) {
if (gotoNewTab) {
window.open(gotoUrl, '_blank')
if (resolvedConfig.gotoUrl) {
if (resolvedConfig.gotoNewTab) {
window.open(resolvedConfig.gotoUrl, '_blank')
} else {
goto(gotoUrl)
goto(resolvedConfig.gotoUrl)
}
}
} else {
@@ -120,28 +136,9 @@
}
</script>
<InputValue
on:done={() => initializing && (initializing = false)}
{id}
input={configuration.label}
bind:value={labelValue}
/>
<InputValue {id} input={configuration.goto} bind:value={gotoUrl} />
<InputValue {id} input={configuration.color} bind:value={color} />
<InputValue {id} input={configuration.size} bind:value={size} />
<InputValue {id} input={configuration.beforeIcon} bind:value={beforeIcon} />
<InputValue {id} input={configuration.afterIcon} bind:value={afterIcon} />
<InputValue {id} input={configuration.triggerOnAppLoad} bind:value={triggerOnAppLoad} />
<InputValue
{id}
input={configuration.disabled}
extraContext={extraQueryParams}
bind:value={disabled}
bind:error={errors.disabled}
/>
<InputValue {id} input={configuration.fillContainer} bind:value={fillContainer} />
<InputValue {id} input={configuration.gotoNewTab} bind:value={gotoNewTab} />
{#each configurationKeys['buttoncomponent'] as key (key)}
<InputValue {key} {id} input={configuration[key]} bind:value={resolvedConfig[key]} />
{/each}
<RunnableWrapper
{recomputeIds}
@@ -150,8 +147,8 @@
{id}
{extraQueryParams}
autoRefresh={false}
goto={gotoUrl}
{gotoNewTab}
goto={resolvedConfig.gotoUrl}
gotoNewTab={resolvedConfig.gotoNewTab}
{render}
>
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
@@ -162,25 +159,25 @@
btnClasses={twMerge(
$app.css?.['buttoncomponent']?.['button']?.class,
customCss?.button.class,
fillContainer ? 'w-full h-full' : ''
resolvedConfig.fillContainer ? 'w-full h-full' : ''
)}
style={[$app.css?.['buttoncomponent']?.['button']?.style, customCss?.button.style].join(';')}
{disabled}
disabled={resolvedConfig.disabled}
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
on:click={handleClick}
{size}
{color}
size={resolvedConfig.size}
color={resolvedConfig.color}
{loading}
>
<span class="truncate inline-flex gap-2 items-center">
{#if beforeIcon && beforeIconComponent}
{#if resolvedConfig.beforeIcon && beforeIconComponent}
<svelte:component this={beforeIconComponent} size={14} />
{/if}
<div>{labelValue}</div>
{#if afterIcon && afterIconComponent}
<div>{resolvedConfig.label}</div>
{#if resolvedConfig.afterIcon && afterIconComponent}
<svelte:component this={afterIconComponent} size={14} />
{/if}
</span>
@@ -16,6 +16,7 @@
export let extraContext: Record<string, any> = {}
export let key: string = ''
$: console.log(value)
const { componentControl } = getContext<AppViewerContext>('AppViewerContext')
const dispatch = createEventDispatcher()
@@ -26,6 +27,7 @@
let lastInput = input ? JSON.parse(JSON.stringify(input)) : undefined
$: console.log(input)
$: if (input && !deepEqual(input, lastInput)) {
lastInput = JSON.parse(JSON.stringify(input))
// Needed because of file uploads
@@ -63,6 +65,7 @@
debounce(async () => (value = await evalExpr(lastInput)))
async function handleConnection() {
console.log('handleConnection', lastInput)
if (lastInput.type === 'connected') {
$worldStore?.connect<any>(lastInput, onValueChange, `${id}-${key}`)
} else if (lastInput.type === 'static' || lastInput.type == 'template') {
@@ -1586,3 +1586,27 @@ Hello \${ctx.username}
}
}
}
export const configurationKeys: Record<AppComponent['type'], string[]> = Object.fromEntries(
Object.entries(components)
.map(([k, v]) => [
k,
[
...new Set(
Object.entries(v.data.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)
)
@@ -6,31 +6,16 @@
import Tooltip from '$lib/components/Tooltip.svelte'
import Popover from '$lib/components/Popover.svelte'
import type {
ConnectedAppInput,
EvalAppInput,
RowAppInput,
StaticAppInput,
UploadAppInput,
UserAppInput
} from '../../inputType'
import ConnectedInputEditor from './inputEditor/ConnectedInputEditor.svelte'
import EvalInputEditor from './inputEditor/EvalInputEditor.svelte'
import RowInputEditor from './inputEditor/RowInputEditor.svelte'
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
import UploadInputEditor from './inputEditor/UploadInputEditor.svelte'
import { getContext } from 'svelte'
import type { AppViewerContext } from '../../types'
import type { AppViewerContext, RichConfiguration } from '../../types'
export let id: string
export let componentInput: (
| StaticAppInput
| ConnectedAppInput
| UserAppInput
| RowAppInput
| EvalAppInput
| UploadAppInput
) & { tooltip?: string; onlyStatic?: boolean }
export let componentInput: RichConfiguration
export let key: string
export let hasRows: boolean = false
export let userInputEnabled: boolean = false
@@ -40,99 +25,101 @@
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
</script>
{#if !(resourceOnly && (componentInput.fieldType !== 'object' || !componentInput.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 componentInput.tooltip}
<Tooltip>
{componentInput.tooltip}
</Tooltip>
{/if}
</span>
{#if componentInput.ctype == undefined}
{#if !(resourceOnly && (componentInput.fieldType !== 'object' || !componentInput.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 componentInput.tooltip}
<Tooltip>
{componentInput.tooltip}
</Tooltip>
{/if}
</span>
<div class="flex gap-x-2 gap-y-1 flex-wrap justify-end items-center">
<Badge color="blue">
{componentInput.fieldType === 'array' && componentInput.subFieldType
? `${capitalize(fieldTypeToTsType(componentInput.subFieldType))}[]`
: capitalize(fieldTypeToTsType(componentInput.fieldType))}
</Badge>
<div class="flex gap-x-2 gap-y-1 flex-wrap justify-end items-center">
<Badge color="blue">
{componentInput.fieldType === 'array' && componentInput.subFieldType
? `${capitalize(fieldTypeToTsType(componentInput.subFieldType))}[]`
: capitalize(fieldTypeToTsType(componentInput.fieldType))}
</Badge>
{#if !componentInput.onlyStatic && 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 !componentInput.onlyStatic && 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 && !componentInput.format?.startsWith('resource-')}
}}
>
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="user"
startIcon={{ icon: faUser }}
position="left"
value="static"
startIcon={{ icon: faPen }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">User Input</svelte:fragment>
<svelte:fragment slot="text">Static</svelte:fragment>
</Popover>
{/if}
{#if 'fileUpload' in componentInput}
{#if userInputEnabled && !componentInput.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}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
value="upload"
startIcon={{ icon: faUpload }}
position="right"
value="connected"
startIcon={{ icon: faArrowRight }}
size="xs"
iconOnly
/>
<svelte:fragment slot="text">Upload</svelte:fragment>
<svelte:fragment slot="text">Connect</svelte:fragment>
</Popover>
{/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}
</ToggleButtonGroup>
{/if}
</div>
</div>
</div>
{#if componentInput.type === 'connected'}
<ConnectedInputEditor bind:componentInput />
{:else if componentInput.type === 'row'}
<RowInputEditor bind:componentInput />
{:else if componentInput.type === 'static'}
<StaticInputEditor 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-6f00">Field's value is set by the user</span>
{/if}
</div>
{#if componentInput.type === 'connected'}
<ConnectedInputEditor bind:componentInput />
{:else if componentInput.type === 'row'}
<RowInputEditor bind:componentInput />
{:else if componentInput.type === 'static'}
<StaticInputEditor 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}
@@ -9,9 +9,7 @@
import InputsSpecEditor from './InputsSpecEditor.svelte'
export let id: string
export let inputSpecs:
| RichConfigurations
| Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
export let inputSpecs: RichConfigurations
export let userInputEnabled: boolean = false
export let shouldCapitalize: boolean = true
export let rowColumns = false
@@ -20,11 +18,11 @@
{#if inputSpecs}
<div class="w-full flex flex-col gap-4">
{#each Object.entries(inputSpecs) as [k, v] (k)}
{#if v.ctype == undefined}
{#each Object.keys(inputSpecs) as k}
{#if inputSpecs[k].ctype == undefined}
<InputsSpecEditor
key={k}
bind:componentInput={v}
bind:componentInput={inputSpecs[k]}
{id}
{userInputEnabled}
{shouldCapitalize}
+5 -1
View File
@@ -43,7 +43,11 @@ export type Configuration = GeneralAppInput &
export type RichConfiguration =
| (Configuration & { ctype?: undefined })
| { ctype: 'oneOf'; selected: string; configuration: Record<string, Configuration> }
| {
ctype: 'oneOf'
selected: string
configuration: Record<string, Record<string, Configuration>>
}
| { ctype: 'group'; title: string; configuration: Record<string, Configuration> }
export type RichConfigurations = Record<string, RichConfiguration>
@@ -122,8 +122,8 @@
ts = date.toISOString()
}
const newJobs = await fetchJobs(maxTs, minTs ?? ts)
if (newJobs && newJobs.length > 0) {
const oldJobs = jobs.map((x) => x.id)
if (newJobs && newJobs.length > 0 && jobs) {
const oldJobs = jobs?.map((x) => x.id)
jobs = newJobs.filter((x) => !oldJobs.includes(x.id)).concat(jobs)
newJobs
.filter((x) => oldJobs.includes(x.id))