diff --git a/frontend/src/lib/components/apps/components/inputs/AppFileInput.svelte b/frontend/src/lib/components/apps/components/inputs/AppFileInput.svelte index da42594e1d..f30e7350fc 100644 --- a/frontend/src/lib/components/apps/components/inputs/AppFileInput.svelte +++ b/frontend/src/lib/components/apps/components/inputs/AppFileInput.svelte @@ -13,8 +13,9 @@ export let configuration: RichConfigurations export let customCss: ComponentCustomCSS<'fileinputcomponent'> | undefined = undefined export let render: boolean + export let onFileChange: string[] | undefined = undefined - const { app, worldStore, componentControl, mode } = + const { app, worldStore, componentControl, mode, runnableComponents } = getContext('AppViewerContext') let acceptedFileTypes: string[] | undefined = undefined @@ -36,6 +37,7 @@ }) } outputs?.result.set(files) + onFileChange?.forEach((id) => $runnableComponents?.[id]?.cb?.forEach((cb) => cb?.())) } let css = initCss($app.css?.fileinputcomponent, customCss) diff --git a/frontend/src/lib/components/apps/components/inputs/AppS3FileInput.svelte b/frontend/src/lib/components/apps/components/inputs/AppS3FileInput.svelte index 28090f133b..694be31664 100644 --- a/frontend/src/lib/components/apps/components/inputs/AppS3FileInput.svelte +++ b/frontend/src/lib/components/apps/components/inputs/AppS3FileInput.svelte @@ -16,6 +16,7 @@ export let customCss: ComponentCustomCSS<'s3fileinputcomponent'> | undefined = undefined export let render: boolean export let extraKey: string | undefined = undefined + export let onFileChange: string[] | undefined = undefined let resolvedConfig = initConfig( components['s3fileinputcomponent'].initialData.configuration, @@ -33,7 +34,8 @@ } let fileUploads: Writable = writable([]) - const { app, worldStore, componentControl } = getContext('AppViewerContext') + const { app, worldStore, componentControl, runnableComponents } = + getContext('AppViewerContext') $componentControl[id] = { clearFiles: () => { @@ -125,6 +127,7 @@ on:addition={(evt) => { const curr = outputs.result.peak() outputs.result.set(curr.concat(evt.detail)) + onFileChange?.forEach((id) => $runnableComponents?.[id]?.cb?.forEach((cb) => cb?.())) }} on:deletion={(evt) => { const curr = outputs.result.peak() diff --git a/frontend/src/lib/components/apps/components/layout/AppConditionalWrapper.svelte b/frontend/src/lib/components/apps/components/layout/AppConditionalWrapper.svelte index 4a2de1daf5..672c1a0458 100644 --- a/frontend/src/lib/components/apps/components/layout/AppConditionalWrapper.svelte +++ b/frontend/src/lib/components/apps/components/layout/AppConditionalWrapper.svelte @@ -14,9 +14,17 @@ export let customCss: ComponentCustomCSS<'conditionalwrapper'> | undefined = undefined export let render: boolean export let conditions: RichConfiguration[] + export let onTabChange: string[] | undefined = undefined - const { app, focusedGrid, selectedComponent, worldStore, connectingInput, componentControl } = - getContext('AppViewerContext') + const { + app, + focusedGrid, + selectedComponent, + worldStore, + connectingInput, + componentControl, + runnableComponents + } = getContext('AppViewerContext') const outputs = initOutput($worldStore, id, { conditions: [] as boolean[], @@ -52,6 +60,7 @@ selectedConditionIndex = index outputs.selectedTabIndex.set(index) + onTabChange?.forEach((id) => $runnableComponents?.[id]?.cb?.forEach((cb) => cb?.())) } $: resolvedConditions && handleResolvedConditions() diff --git a/frontend/src/lib/components/apps/components/layout/AppStepper.svelte b/frontend/src/lib/components/apps/components/layout/AppStepper.svelte index 38e4df3af7..9a15955ec6 100644 --- a/frontend/src/lib/components/apps/components/layout/AppStepper.svelte +++ b/frontend/src/lib/components/apps/components/layout/AppStepper.svelte @@ -22,6 +22,8 @@ export let recomputeIds: string[] | undefined = undefined export let extraQueryParams: Record = {} export let componentInput: AppInput | undefined + export let onNext: string[] | undefined = undefined + export let onPrevious: string[] | undefined = undefined const { app, @@ -30,7 +32,8 @@ selectedComponent, componentControl, connectingInput, - mode + mode, + runnableComponents } = getContext('AppViewerContext') let selected = tabs[0] @@ -206,6 +209,7 @@ on:click={(e) => { e.preventDefault() directionClicked = 'left' + onPrevious?.forEach((id) => $runnableComponents?.[id]?.cb?.forEach((cb) => cb?.())) runStep(selectedIndex - 1) }} > @@ -227,6 +231,7 @@ on:click={(e) => { e.preventDefault() directionClicked = 'right' + onNext?.forEach((id) => $runnableComponents?.[id]?.cb?.forEach((cb) => cb?.())) runStep(selectedIndex + 1) }} > diff --git a/frontend/src/lib/components/apps/components/layout/AppTabs.svelte b/frontend/src/lib/components/apps/components/layout/AppTabs.svelte index 14ce061a50..4d702c5eef 100644 --- a/frontend/src/lib/components/apps/components/layout/AppTabs.svelte +++ b/frontend/src/lib/components/apps/components/layout/AppTabs.svelte @@ -23,6 +23,7 @@ export let customCss: ComponentCustomCSS<'tabscomponent'> | undefined = undefined export let render: boolean export let disabledTabs: RichConfiguration[] + export let onTabChange: string[] | undefined = undefined let resolvedConfig = initConfig( components['tabscomponent'].initialData.configuration, @@ -36,7 +37,8 @@ selectedComponent, mode, componentControl, - connectingInput + connectingInput, + runnableComponents } = getContext('AppViewerContext') let selected: string = tabs[0] @@ -50,6 +52,8 @@ selectedIndex = tabs?.indexOf(selected) outputs?.selectedTabIndex.set(selectedIndex) + onTabChange?.forEach((id) => $runnableComponents?.[id]?.cb?.forEach((cb) => cb?.())) + if ($focusedGrid?.parentComponentId != id || $focusedGrid?.subGridIndex != selectedIndex) { $focusedGrid = { parentComponentId: id, diff --git a/frontend/src/lib/components/apps/editor/SettingsPanel.svelte b/frontend/src/lib/components/apps/editor/SettingsPanel.svelte index 174e3064ba..df16b59593 100644 --- a/frontend/src/lib/components/apps/editor/SettingsPanel.svelte +++ b/frontend/src/lib/components/apps/editor/SettingsPanel.svelte @@ -7,9 +7,10 @@ import ComponentPanel from './settingsPanel/ComponentPanel.svelte' import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte' import BackgroundScriptSettings from './settingsPanel/script/BackgroundScriptSettings.svelte' - import Recompute from './settingsPanel/Recompute.svelte' + import EventHandlerItem from './settingsPanel/EventHandlerItem.svelte' - const { selectedComponent, app, stateId } = getContext('AppViewerContext') + const { selectedComponent, app, stateId, runnableComponents } = + getContext('AppViewerContext') let firstComponent = $selectedComponent?.[0] @@ -153,7 +154,17 @@ {/if} - + + _id !== id)} + bind:value={hiddenInlineScript.script.recomputeIds} + /> +
{/key} {/if} diff --git a/frontend/src/lib/components/apps/editor/component/Component.svelte b/frontend/src/lib/components/apps/editor/component/Component.svelte index 75fa758f4c..662e3d7a16 100644 --- a/frontend/src/lib/components/apps/editor/component/Component.svelte +++ b/frontend/src/lib/components/apps/editor/component/Component.svelte @@ -606,6 +606,7 @@ id={component.id} tabs={component.tabs} disabledTabs={component.disabledTabs} + onTabChange={component.onTabChange} customCss={component.customCss} {componentContainerHeight} {render} @@ -617,6 +618,8 @@ customCss={component.customCss} {componentContainerHeight} componentInput={component.componentInput} + onNext={component.onNext} + onPrevious={component.onPrevious} {render} /> {:else if component.type === 'conditionalwrapper' && component.conditions} @@ -624,6 +627,7 @@ id={component.id} conditions={component.conditions} customCss={component.customCss} + onTabChange={component.onTabChange} {componentContainerHeight} {render} /> @@ -675,6 +679,7 @@ configuration={component.configuration} id={component.id} customCss={component.customCss} + onFileChange={component.onFileChange} {render} /> {:else if component.type === 's3fileinputcomponent'} @@ -682,6 +687,7 @@ configuration={component.configuration} id={component.id} customCss={component.customCss} + onFileChange={component.onFileChange} {render} /> {:else if component.type === 'imagecomponent'} diff --git a/frontend/src/lib/components/apps/editor/component/components.ts b/frontend/src/lib/components/apps/editor/component/components.ts index 1f7ec80d63..ee46dfef50 100644 --- a/frontend/src/lib/components/apps/editor/component/components.ts +++ b/frontend/src/lib/components/apps/editor/component/components.ts @@ -186,11 +186,15 @@ export type RadioComponent = BaseComponent<'radiocomponent'> export type IconComponent = BaseComponent<'iconcomponent'> export type HorizontalDividerComponent = BaseComponent<'horizontaldividercomponent'> export type VerticalDividerComponent = BaseComponent<'verticaldividercomponent'> -export type FileInputComponent = BaseComponent<'fileinputcomponent'> +export type FileInputComponent = BaseComponent<'fileinputcomponent'> & { + onFileChange?: string[] +} export type TabsComponent = BaseComponent<'tabscomponent'> & { tabs: string[] disabledTabs: RichConfiguration[] + onTabChange?: string[] } + export type ListComponent = BaseComponent<'listcomponent'> export type ContainerComponent = BaseComponent<'containercomponent'> & { groupFields: RichConfigurations @@ -214,9 +218,12 @@ export type ModalComponent = BaseComponent<'modalcomponent'> & { } export type StepperComponent = BaseComponent<'steppercomponent'> & { tabs: string[] + onNext?: string[] + onPrevious?: string[] } export type ConditionalWrapperComponent = BaseComponent<'conditionalwrapper'> & { conditions: RichConfiguration[] + onTabChange?: string[] } export type Schemaformcomponent = BaseComponent<'schemaformcomponent'> @@ -234,7 +241,9 @@ export type DBExplorerComponent = BaseComponent<'dbexplorercomponent'> & { actions: TableAction[] } -export type S3FileInputComponent = BaseComponent<'s3fileinputcomponent'> +export type S3FileInputComponent = BaseComponent<'s3fileinputcomponent'> & { + onFileChange?: string[] +} export type DecisionTreeNode = { id: string diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte index 93a6d19190..9af36b112c 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte @@ -9,7 +9,6 @@ import ConnectedInputEditor from './inputEditor/ConnectedInputEditor.svelte' import { classNames, getModifierKey, isMac } from '$lib/utils' import { buildExtraLib } from '../../utils' - import Recompute from './Recompute.svelte' import Tooltip from '$lib/components/Tooltip.svelte' import ComponentInputTypeEditor from './ComponentInputTypeEditor.svelte' import AlignmentEditor from './AlignmentEditor.svelte' @@ -40,6 +39,7 @@ import GridAgChartsLicenseKe from './GridAgChartsLicenseKe.svelte' import Toggle from '$lib/components/Toggle.svelte' import ContextVariables from './ContextVariables.svelte' + import EventHandlers from './EventHandlers.svelte' export let componentSettings: { item: GridItem; parent: string | undefined } | undefined = undefined @@ -402,46 +402,8 @@ {ccomponents[component.type].name} has no configuration
{/if} - {#if (`recomputeIds` in componentSettings.item.data && Array.isArray(componentSettings.item.data.recomputeIds)) || componentSettings.item.data.type === 'buttoncomponent' || componentSettings.item.data.type === 'formcomponent' || componentSettings.item.data.type === 'formbuttoncomponent' || componentSettings.item.data.type === 'checkboxcomponent'} - - {/if} - {#if (`onOpenRecomputeIds` in componentSettings.item.data && Array.isArray(componentSettings.item.data.onOpenRecomputeIds)) || componentSettings.item.data.type === 'modalcomponent' || componentSettings.item.data.type === 'drawercomponent'} - - {/if} - {#if (`onCloseRecomputeIds` in componentSettings.item.data && Array.isArray(componentSettings.item.data.onCloseRecomputeIds)) || componentSettings.item.data.type === 'modalcomponent' || componentSettings.item.data.type === 'drawercomponent'} - - {/if} - {#if componentSettings.item.data.type === 'checkboxcomponent'} - - {/if} - {#if componentSettings.item.data.type === 'resourceselectcomponent' || componentSettings.item.data.type === 'selectcomponent'} - - {/if} + +
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/EventHandlerItem.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/EventHandlerItem.svelte new file mode 100644 index 0000000000..d4012176a6 --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/EventHandlerItem.svelte @@ -0,0 +1,37 @@ + + +
+
+ {title} + {tooltip} +
+ +
+ {#if items.length === 0} +
+ No components to recompute. +
+ {:else} + + {/if} +
+
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/EventHandlers.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/EventHandlers.svelte new file mode 100644 index 0000000000..ce49fc7578 --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/EventHandlers.svelte @@ -0,0 +1,108 @@ + + +{#if componentsWithEventHandler.includes(item.data.type)} + + {#if (`onOpenRecomputeIds` in item.data && Array.isArray(item.data.onOpenRecomputeIds)) || item.data.type === 'modalcomponent' || item.data.type === 'drawercomponent'} + id !== ownId)} + bind:value={item.data.onOpenRecomputeIds} + /> + {/if} + + {#if (`onCloseRecomputeIds` in item.data && Array.isArray(item.data.onCloseRecomputeIds)) || item.data.type === 'modalcomponent' || item.data.type === 'drawercomponent'} + id !== ownId)} + bind:value={item.data.onCloseRecomputeIds} + /> + {/if} + {#if (`recomputeIds` in item.data && Array.isArray(item.data.recomputeIds)) || item.data.type === 'buttoncomponent' || item.data.type === 'formcomponent' || item.data.type === 'formbuttoncomponent' || item.data.type === 'checkboxcomponent'} + id !== ownId)} + bind:value={item.data.recomputeIds} + /> + {/if} + {#if item.data.type === 'checkboxcomponent'} + id !== ownId)} + bind:value={item.data.onToggle} + /> + {/if} + {#if item.data.type === 'resourceselectcomponent' || item.data.type === 'selectcomponent'} + id !== ownId)} + bind:value={item.data.onSelect} + /> + {/if} + {#if item.data.type === 'tabscomponent' || item.data.type === 'conditionalwrapper'} + id !== ownId)} + bind:value={item.data.onTabChange} + /> + {/if} + {#if item.data.type === 'fileinputcomponent' || item.data.type === 's3fileinputcomponent'} + id !== ownId)} + bind:value={item.data.onFileChange} + /> + {/if} + {#if item.data.type === 'steppercomponent'} + id !== ownId)} + bind:value={item.data.onNext} + /> + {/if} + {#if item.data.type === 'steppercomponent'} + id !== ownId)} + bind:value={item.data.onPrevious} + /> + {/if} + +{/if} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecsEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecsEditor.svelte index 46215836b8..4dad7ce22e 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecsEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecsEditor.svelte @@ -23,6 +23,11 @@ $: finalInputSpecsConfiguration = inputSpecsConfiguration ?? inputSpecs const dispatch = createEventDispatcher() + + const mapping = { + onSuccess: 'On success wizard', + onError: 'On error wizard' + } {#if inputSpecs} @@ -38,7 +43,7 @@ {:else if finalInputSpecsConfiguration[k]?.type == 'oneOf'} - import Badge from '$lib/components/common/badge/Badge.svelte' - import { getContext } from 'svelte' - import type { AppViewerContext } from '../../types' - import PanelSection from './common/PanelSection.svelte' - import Toggle from '$lib/components/Toggle.svelte' - - export let recomputeIds: string[] | undefined = undefined - export let ownId: string - export let title: string = 'Trigger runnables on success' - export let tooltip: string = - 'Select components to recompute after this runnable has successfully run' - export let documentationLink: string = - 'https://www.windmill.dev/docs/apps/app-runnable-panel#trigger-runnables-on-success' - - const { runnableComponents } = getContext('AppViewerContext') - - function onChange(checked: boolean, id: string) { - if (checked) { - recomputeIds = [...(recomputeIds ?? []), id] - } else { - recomputeIds = recomputeIds?.filter((x) => x !== id) - } - } - - - - {#if Object.keys($runnableComponents ?? {}).filter((id) => id !== ownId).length > 0} - - - - - - - - - {#each Object.keys($runnableComponents ?? {}).filter((id) => id !== ownId) as id} - - - - - {/each} - -
- Component - - Recompute -
- {id} - - onChange(e.detail, id)} - checked={recomputeIds?.includes(id)} - /> -
- {:else} -
No components to recompute. Create one and select it here.
- {/if} -
diff --git a/frontend/src/lib/components/multiselect/MultiSelect.svelte b/frontend/src/lib/components/multiselect/MultiSelect.svelte index c2ad7260cc..b0f1ad2429 100644 --- a/frontend/src/lib/components/multiselect/MultiSelect.svelte +++ b/frontend/src/lib/components/multiselect/MultiSelect.svelte @@ -78,6 +78,7 @@ export let ulSelectedClass: string = `` export let ulSelectedStyle: string | null = null export let value: Option | Option[] | null = null + export let disableRemoveAll: boolean = false const selected_to_value = (selected: Option[]) => { value = maxSelect === 1 ? selected[0] ?? null : selected @@ -507,7 +508,7 @@ class="remove" > - + {/if} @@ -552,7 +553,7 @@ {#if disabled} disable {:else if selected.length > 0} - {#if maxSelect !== 1 && selected.length > 1} + {#if maxSelect !== 1 && selected.length > 1 && !disableRemoveAll}