diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte index 01527d1faf..f705ed8c3b 100644 --- a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte +++ b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte @@ -8,7 +8,7 @@ import { classNames, defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils' import { deepEqual } from 'fast-equals' import { Bug } from 'lucide-svelte' - import { createEventDispatcher, getContext, onDestroy } from 'svelte' + import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte' import type { AppInputs, Runnable } from '../../inputType' import type { Output } from '../../rx' import type { AppViewerContext, CancelablePromise, InlineScript } from '../../types' @@ -60,38 +60,6 @@ let donePromise: (() => void) | undefined = undefined - const cancellableRun: (inlineScript?: InlineScript) => CancelablePromise = ( - inlineScript?: InlineScript - ) => { - let rejectCb: (err: Error) => void - let p: Partial> = new Promise((resolve, reject) => { - rejectCb = reject - donePromise = resolve - executeComponent(true, inlineScript).catch(reject) - }) - p.cancel = () => { - testJobLoader?.cancelJob() - loading = false - rejectCb(new Error('Canceled')) - } - - return p as CancelablePromise - } - - $runnableComponents[id] = { - autoRefresh: autoRefresh && recomputableByRefreshButton, - refreshOnStart, - cb: cancellableRun - } - - if (!$initialized.initializedComponents.includes(id)) { - $initialized.initializedComponents = [...$initialized.initializedComponents, id] - } - - onDestroy(() => { - $initialized.initializedComponents = $initialized.initializedComponents.filter((c) => c !== id) - }) - $runnableComponents = $runnableComponents let args: Record | undefined = undefined @@ -189,8 +157,6 @@ } async function executeComponent(noToast = false, inlineScriptOverride?: InlineScript) { - console.debug('execute', id) - if (runnable?.type === 'runnableByName' && runnable.inlineScript?.language === 'frontend') { loading = true try { @@ -234,6 +200,11 @@ return } + if (!testJobLoader) { + console.warn('No test job loader') + return + } + loading = true try { @@ -359,6 +330,40 @@ const event = e as unknown as PointerEvent !$connectingInput.opened && selectId(event, id, selectedComponent, $app) } + + onMount(() => { + const cancellableRun: (inlineScript?: InlineScript) => CancelablePromise = ( + inlineScript?: InlineScript + ) => { + let rejectCb: (err: Error) => void + let p: Partial> = new Promise((resolve, reject) => { + rejectCb = reject + donePromise = resolve + executeComponent(true, inlineScript).catch(reject) + }) + p.cancel = () => { + testJobLoader?.cancelJob() + loading = false + rejectCb(new Error('Canceled')) + } + + return p as CancelablePromise + } + + $runnableComponents[id] = { + autoRefresh: autoRefresh && recomputableByRefreshButton, + refreshOnStart, + cb: cancellableRun + } + + if (!$initialized.initializedComponents.includes(id)) { + $initialized.initializedComponents = [...$initialized.initializedComponents, id] + } + }) + + onDestroy(() => { + $initialized.initializedComponents = $initialized.initializedComponents.filter((c) => c !== id) + }) {#each Object.entries(fields ?? {}) as [key, v] (key)} diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte index 51e72bc58f..60a250ad51 100644 --- a/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte +++ b/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte @@ -11,6 +11,7 @@ import InitializeComponent from './InitializeComponent.svelte' export let componentInput: AppInput | undefined + export let id: string export let result: any = undefined export let initializing: boolean = true @@ -112,7 +113,11 @@ bind:result runnable={componentInput.runnable} transformer={componentInput.transformer} - {autoRefresh} + autoRefresh={autoRefresh !== undefined + ? autoRefresh + : componentInput?.type === 'runnable' + ? componentInput.autoRefresh + : false} bind:recomputeOnInputChanged={componentInput.recomputeOnInputChanged} {id} {extraQueryParams} diff --git a/frontend/src/lib/components/apps/editor/SettingsPanel.svelte b/frontend/src/lib/components/apps/editor/SettingsPanel.svelte index 8bab2ca684..8066543081 100644 --- a/frontend/src/lib/components/apps/editor/SettingsPanel.svelte +++ b/frontend/src/lib/components/apps/editor/SettingsPanel.svelte @@ -1,6 +1,4 @@ {#if componentSettings} @@ -82,34 +96,12 @@ /> {/key} {:else if hiddenInlineScript} -
- -
- - - You may want to disable this so that the background script is only triggered by changes to - other values or triggered by another computation on a button (See 'Recompute Others') - -
-
+ -
- {#if hiddenInlineScript.script.inlineScript} - - {:else} - No script defined - {/if} -
+
{#if Object.keys(hiddenInlineScript.script.fields).length > 0} {#key $stateId} @@ -122,6 +114,13 @@ {/key} {/if} + + {#if hiddenInlineScript.script.inlineScript?.language === 'frontend'} + +
Frontend cannot have inputs
+
+ {/if} +
{/if} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/SelectedRunnable.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/SelectedRunnable.svelte index 8df3fbf700..bdb0ddf842 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/SelectedRunnable.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/SelectedRunnable.svelte @@ -1,21 +1,21 @@ -
- - {#if appInput.runnable?.type === 'runnableByName'} - {appInput.runnable.name} - {:else if appInput.runnable?.type === 'runnableByPath'} - {appInput.runnable.path} - {/if} - -
- {#if appInput.runnable?.type === 'runnableByName' && appInput.runnable.inlineScript} - - {/if} - -
-
-
-
-
- - Transformer   - - A transformer is an optional frontend script that is executed right after the component's - script whose purpose is to do lightweight transformation in the browser. It takes the - previous computation's result as `result` - - -
- {#if !appInput.transformer} -
- -
- {:else} - - {/if} -
-
- -{#if !['buttoncomponent', 'formbuttoncomponent', 'formcomponent'].includes(appComponent.type)} -
- -
-{/if} -{#if appInput.runnable?.type === 'runnableByName'} -
- -
-{/if} -{#if appInput.runnable?.type === 'runnableByName' && !appInput.runnable.inlineScript} - - Please configure the language in the inline script panel - -{/if} + diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/BackgroundScriptSettings.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/BackgroundScriptSettings.svelte new file mode 100644 index 0000000000..46bdbebe51 --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/BackgroundScriptSettings.svelte @@ -0,0 +1,31 @@ + + +
+ + { + const autoRefresh = !script.autoRefresh + if ($runnableComponents?.[id]?.autoRefresh !== autoRefresh && autoRefresh !== undefined) { + $runnableComponents[id] = { + ...$runnableComponents[id], + autoRefresh + } + } + }} + /> + +
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/ComponentScriptSettings.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/ComponentScriptSettings.svelte new file mode 100644 index 0000000000..ee2c8c8836 --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/ComponentScriptSettings.svelte @@ -0,0 +1,76 @@ + + + + +
+ + + {#if hasScript} + + { + const autoRefresh = !( + appComponent.componentInput?.type === 'runnable' && + appComponent?.componentInput?.autoRefresh + ) + + if ( + appComponent.componentInput?.type === 'runnable' && + $runnableComponents?.[appComponent.id]?.autoRefresh !== autoRefresh && + !isTriggerable(appComponent.type) && + autoRefresh !== undefined + ) { + $runnableComponents[appComponent.id] = { + ...$runnableComponents[appComponent.id], + autoRefresh + } + } + }} + /> + + {:else} + +
Please configure the language in the inline script panel
+
+ {/if} +
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/BackgroundScriptTriggerBy.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/BackgroundScriptTriggerBy.svelte new file mode 100644 index 0000000000..08d6ff6179 --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/BackgroundScriptTriggerBy.svelte @@ -0,0 +1,124 @@ + + + + {#if isFrontend} +
+ +
+ {/if} + {#if hasNoTriggers} + + This script has no triggers. It will never run. + + {:else} + {#if triggerEvents.length > 0} +
Events
+
+ {#each triggerEvents as triggerEvent} + {triggerEvent} + {/each} +
+ {/if} + {#if changeEvents.length > 0 && (recomputeOnInputChanged || isFrontend)} +
Change on value
+
+ {#each changeEvents as changeEvent} + + {changeEvent} + {#if isFrontend} + + {/if} + + + {/each} +
+ {/if} + {/if} +
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ComponentScriptTriggerBy.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ComponentScriptTriggerBy.svelte new file mode 100644 index 0000000000..ba1b40510a --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ComponentScriptTriggerBy.svelte @@ -0,0 +1,48 @@ + + + + {#if hasNoTriggers} + + This script has no triggers. It will never run. + + {:else} + {#if triggerEvents.length > 0} +
Events
+
+ {#each triggerEvents as triggerEvent} + {triggerEvent} + {/each} +
+ {/if} + {#if changeEvents.length > 0 && appInput.recomputeOnInputChanged && !isTriggerable(appComponent.type)} +
Change on value
+
+ {#each changeEvents as changeEvent} + {changeEvent} + {/each} +
+ {/if} + {/if} +
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptRunConfiguration.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptRunConfiguration.svelte new file mode 100644 index 0000000000..1a8552d2ee --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptRunConfiguration.svelte @@ -0,0 +1,55 @@ + + +{#if canConfigureRecomputeOnInputChanged || canConfigureRunOnStart} + +
+ {#if autoRefresh !== undefined && canConfigureRunOnStart} +
+
+ Run on start and app refresh + + You may want to disable this so that the background script is only triggered by + changes to other values or triggered by another computation on a button (See + 'Recompute Others') + +
+ { + dispatch('updateAutoRefresh') + }} + /> +
+ {/if} + {#if recomputeOnInputChanged !== undefined && canConfigureRecomputeOnInputChanged} +
+
+ Recompute on any input changes +
+ +
+ {/if} +
+
+{:else} + +
Triggerable component runs only when an interaction happens.
+
+{/if} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptSettingHeader.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptSettingHeader.svelte new file mode 100644 index 0000000000..af052f7ca0 --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptSettingHeader.svelte @@ -0,0 +1,19 @@ + + +
+
+ + + {name} + +
+ + +
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptSettingsActions.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptSettingsActions.svelte new file mode 100644 index 0000000000..8bf6f4f1cd --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptSettingsActions.svelte @@ -0,0 +1,31 @@ + + +
+ {#each actions as action, index (index)} + + + {action.label} + + + + {/each} +
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptSettingsSection.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptSettingsSection.svelte new file mode 100644 index 0000000000..530edcea4c --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptSettingsSection.svelte @@ -0,0 +1,20 @@ + + +
+
+ {title} + + {#if tooltip} + + {tooltip} + + {/if} +
+ + +
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptTransformer.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptTransformer.svelte new file mode 100644 index 0000000000..4b208b8b1c --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptTransformer.svelte @@ -0,0 +1,41 @@ + + + +
+ +
+
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/utils.ts b/frontend/src/lib/components/apps/editor/settingsPanel/script/utils.ts new file mode 100644 index 0000000000..258163642b --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/utils.ts @@ -0,0 +1,63 @@ +import type { Runnable } from '$lib/components/apps/inputType' +import type { AppComponent } from '../../component' +import type { + StaticAppInput, + ConnectedAppInput, + RowAppInput, + UserAppInput +} from '$lib/components/apps/inputType' + +export function getDependencies( + fields: + | Record + | undefined +): string[] { + let dependencies: string[] = [] + + if (!fields) return dependencies + + Object.values(fields).forEach((field) => { + if (field.type === 'connected' && dependencies && field.connection) { + dependencies.push(`${field.connection.componentId} - ${field.connection.path}`) + } + }) + return dependencies +} + +export function isFrontend(runnable: Runnable): boolean { + return runnable?.type === 'runnableByName' && runnable.inlineScript?.language === 'frontend' +} + +export function isTriggerable(componentType: string): boolean { + return ['buttoncomponent', 'formbuttoncomponent', 'formcomponent'].includes(componentType) +} + +export function isTriggerOnAppLoad(appComponent: AppComponent): boolean { + return Boolean( + appComponent?.configuration?.triggerOnAppLoad != undefined && + appComponent.configuration.triggerOnAppLoad.type == 'static' && + appComponent.configuration.triggerOnAppLoad.value + ) +} + +export function getAllTriggerEvents( + appComponent: AppComponent, + autoRefresh: boolean | undefined +): string[] { + const events: string[] = [] + const triggerOnAppLoad = isTriggerOnAppLoad(appComponent) + const isTriggerableComponent = isTriggerable(appComponent.type) + + if (isTriggerableComponent) { + events.push('click') + + if (triggerOnAppLoad) { + events.push('start') + } + } else if (autoRefresh) { + events.push('start') + events.push('refresh') + } + + return events +} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/BackgroundScriptTriggerList.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/BackgroundScriptTriggerList.svelte deleted file mode 100644 index 1c52eeae2c..0000000000 --- a/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/BackgroundScriptTriggerList.svelte +++ /dev/null @@ -1,52 +0,0 @@ - - -{#if inlineScript.language !== 'frontend'} -
- -
-{/if} - - diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/ComponentTriggerList.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/ComponentTriggerList.svelte deleted file mode 100644 index f606384b69..0000000000 --- a/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/ComponentTriggerList.svelte +++ /dev/null @@ -1,44 +0,0 @@ - - - diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/TriggerBadgesList.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/TriggerBadgesList.svelte deleted file mode 100644 index 51966aae71..0000000000 --- a/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/TriggerBadgesList.svelte +++ /dev/null @@ -1,174 +0,0 @@ - - -
- {#if recomputedBadges.length === 0 && !onLoad && !onClick && inputDependencies?.length === 0 && !frontendDependencies} -

- This script has no triggers. It will never run. -

- {:else} -
Triggered by
- - {#if onLoad || onClick} -
-
Events
-
- {#if onLoad} - Start - Refresh - {/if} - {#if onClick} - Click - {/if} -
-
- {/if} - {#if inputDependencies.length > 0 && (recomputeOnInputChanged ?? true)} -
-
-
Change on values
-
-
- {#each inputDependencies as label} - - {label} - - {/each} -
-
- {/if} - - {#if recomputedBadges?.length > 0} -
-
Computation of
-
- {#each recomputedBadges as badge} - {badge} - {/each} -
-
- {/if} - {/if} - - {#if frontendDependencies && recomputeOnInputChanged} -
-
-
Change on values
- {#if inlineScript?.language === 'frontend' && !shouldHideAddDependencyButton} - - {/if} -
-
- {#each frontendDependencies as label, index} - - {label} - - - {/each} -
-
- {/if} -
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/triggerListUtils.ts b/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/triggerListUtils.ts deleted file mode 100644 index 8b64dfe49a..0000000000 --- a/frontend/src/lib/components/apps/editor/settingsPanel/triggerLists/triggerListUtils.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { - StaticAppInput, - ConnectedAppInput, - RowAppInput, - UserAppInput -} from '$lib/components/apps/inputType' - -export function getDependencies( - fields: - | Record - | undefined -): string[] { - let dependencies: string[] = [] - - if (!fields) return dependencies - - Object.values(fields).forEach((field) => { - if (field.type === 'connected' && dependencies && field.connection) { - dependencies.push(`${field.connection.componentId} - ${field.connection.path}`) - } - }) - return dependencies -} diff --git a/frontend/src/lib/components/apps/inputType.ts b/frontend/src/lib/components/apps/inputType.ts index 16675a86b3..bf2daa5886 100644 --- a/frontend/src/lib/components/apps/inputType.ts +++ b/frontend/src/lib/components/apps/inputType.ts @@ -92,6 +92,7 @@ export type ResultInput = { // kept for migration purposes doNotRecomputeOnInputChanged?: boolean recomputeOnInputChanged?: boolean + autoRefresh?: boolean } type AppInputSpec = ( diff --git a/frontend/src/lib/components/apps/types.ts b/frontend/src/lib/components/apps/types.ts index 030d58c5a7..4553d63121 100644 --- a/frontend/src/lib/components/apps/types.ts +++ b/frontend/src/lib/components/apps/types.ts @@ -103,6 +103,17 @@ export type InlineScript = { export type AppCssItemName = 'viewer' | 'grid' | AppComponent['type'] +export type HiddenInlineScript = { + name: string + inlineScript: InlineScript | undefined + fields: Record + autoRefresh?: boolean + //deprecated and to be removed after migration + doNotRecomputeOnInputChanged?: boolean + recomputeOnInputChanged?: boolean + noBackendValue?: any +} + export type App = { grid: GridItem[] fullscreen: boolean @@ -111,16 +122,7 @@ export type App = { name: string inlineScript: InlineScript }> - hiddenInlineScripts: Array<{ - name: string - inlineScript: InlineScript | undefined - fields: Record - autoRefresh?: boolean - //deprecated and to be removed after migration - doNotRecomputeOnInputChanged?: boolean - recomputeOnInputChanged?: boolean - noBackendValue?: any - }> + hiddenInlineScripts: Array css?: Partial>> subgrids?: Record } diff --git a/frontend/src/lib/components/common/button/ButtonDropdown.svelte b/frontend/src/lib/components/common/button/ButtonDropdown.svelte index 87ab0b3e39..1c21eeb57d 100644 --- a/frontend/src/lib/components/common/button/ButtonDropdown.svelte +++ b/frontend/src/lib/components/common/button/ButtonDropdown.svelte @@ -9,10 +9,10 @@