diff --git a/frontend/src/lib/components/apps/components/layout/AppConditionalWrapper.svelte b/frontend/src/lib/components/apps/components/layout/AppConditionalWrapper.svelte new file mode 100644 index 0000000000..5d8a8b71cd --- /dev/null +++ b/frontend/src/lib/components/apps/components/layout/AppConditionalWrapper.svelte @@ -0,0 +1,92 @@ + + +{#each conditions ?? [] as condition, index} + +{/each} + + + +
+ {#if $app.subgrids?.[`${id}-${selectedConditionIndex}`]} + { + if (!$connectingInput.opened) { + $selectedComponent = [id] + } + onFocus() + }} + /> + {/if} +
diff --git a/frontend/src/lib/components/apps/editor/ComponentHeader.svelte b/frontend/src/lib/components/apps/editor/ComponentHeader.svelte index e1ebcca52e..b2484ee7be 100644 --- a/frontend/src/lib/components/apps/editor/ComponentHeader.svelte +++ b/frontend/src/lib/components/apps/editor/ComponentHeader.svelte @@ -8,6 +8,8 @@ import type { AppComponent } from './component' import { twMerge } from 'tailwind-merge' import { getErrorFromLatestResult } from './appUtils' + import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte' + import { MenuItem } from '@rgossiaux/svelte-headlessui' export let component: AppComponent export let selected: boolean @@ -17,9 +19,11 @@ export let hasInlineEditor: boolean = false export let inlineEditorOpened: boolean = false + let isConditionalWrapperManuallySelected: boolean = false + const dispatch = createEventDispatcher() - const { errorByComponent, openDebugRun, jobs, connectingInput } = + const { errorByComponent, openDebugRun, jobs, connectingInput, componentControl } = getContext('AppViewerContext') $: error = getErrorFromLatestResult(component.id, $errorByComponent, $jobs) @@ -68,6 +72,58 @@ {/if} {/if} + {#if component.type === 'conditionalwrapper'} + + {/if} - - + {/if} diff --git a/frontend/src/lib/components/apps/editor/appUtils.ts b/frontend/src/lib/components/apps/editor/appUtils.ts index 0c10ae298b..aaf8ebedb8 100644 --- a/frontend/src/lib/components/apps/editor/appUtils.ts +++ b/frontend/src/lib/components/apps/editor/appUtils.ts @@ -248,6 +248,7 @@ export function appComponentFromType( componentInput: init.componentInput, panes: init.panes, tabs: init.tabs, + conditions: init.conditions, customCss: {}, recomputeIds: init.recomputeIds ? [] : undefined, actionButtons: init.actionButtons ? [] : undefined, diff --git a/frontend/src/lib/components/apps/editor/component/Component.svelte b/frontend/src/lib/components/apps/editor/component/Component.svelte index 26eb33f937..c94b594621 100644 --- a/frontend/src/lib/components/apps/editor/component/Component.svelte +++ b/frontend/src/lib/components/apps/editor/component/Component.svelte @@ -42,6 +42,7 @@ import AppModal from '../../components/layout/AppModal.svelte' import AppSchemaForm from '../../components/buttons/AppSchemaForm.svelte' import AppStepper from '../../components/layout/AppStepper.svelte' + import AppConditionalWrapper from '../../components/layout/AppConditionalWrapper.svelte' export let component: AppComponent export let selected: boolean @@ -402,6 +403,14 @@ componentInput={component.componentInput} {render} /> + {:else if component.type === 'conditionalwrapper' && component.conditions} + {:else if component.type === 'containercomponent'} = { type: T @@ -109,6 +112,10 @@ export type ModalComponent = BaseComponent<'modalcomponent'> export type StepperComponent = BaseComponent<'steppercomponent'> & { tabs: string[] } +export type ConditionalWrapperComponent = BaseComponent<'conditionalwrapper'> & { + conditions: RichConfiguration[] +} + export type Schemaformcomponent = BaseComponent<'schemaformcomponent'> export type TypedComponent = @@ -154,6 +161,7 @@ export type TypedComponent = | ModalComponent | StepperComponent | Schemaformcomponent + | ConditionalWrapperComponent export type AppComponent = BaseAppComponent & TypedComponent @@ -196,6 +204,7 @@ export interface InitialAppComponent extends Partial { actionButtons?: boolean tabs?: string[] panes?: number[] + conditions?: AppInputSpec<'boolean', boolean>[] } const buttonColorOptions = [...BUTTON_COLORS] @@ -445,7 +454,7 @@ export const components = { } }, formcomponent: { - name: 'Form', + name: 'Submit form', icon: FormInput, dims: '3:5-6:5' as AppComponentDimensions, customCss: { @@ -1744,7 +1753,7 @@ Hello \${ctx.username} } }, schemaformcomponent: { - name: 'Schema Form', + name: 'Form', icon: FileText, dims: '3:8-8:12' as AppComponentDimensions, customCss: { @@ -1782,6 +1791,32 @@ Hello \${ctx.username} } } } + }, + conditionalwrapper: { + name: 'Conditional tabs', + icon: Split, + dims: '2:8-6:8' as AppComponentDimensions, + + customCss: { + container: { class: '', style: '' } + }, + initialData: { + configuration: {}, + componentInput: undefined, + numberOfSubgrids: 2, + conditions: [ + { + type: 'eval', + expr: 'false', + fieldType: 'boolean' + }, + { + type: 'eval', + expr: 'true', + fieldType: 'boolean' + } + ] as AppInputSpec<'boolean', boolean>[] + } } } as const diff --git a/frontend/src/lib/components/apps/editor/component/sets.ts b/frontend/src/lib/components/apps/editor/component/sets.ts index 9c7fe511ef..8c7a91952d 100644 --- a/frontend/src/lib/components/apps/editor/component/sets.ts +++ b/frontend/src/lib/components/apps/editor/component/sets.ts @@ -1,9 +1,13 @@ import type { ComponentSet } from '../../types' +const tabs: ComponentSet = { + title: 'Tabs', + components: ['tabscomponent', 'conditionalwrapper'] +} as const + const layout: ComponentSet = { title: 'Layout', components: [ - 'tabscomponent', 'containercomponent', 'horizontaldividercomponent', 'verticaldividercomponent', @@ -23,6 +27,7 @@ const buttons: ComponentSet = { const inputs: ComponentSet = { title: 'Inputs', components: [ + 'schemaformcomponent', 'textinputcomponent', 'textareainputcomponent', 'passwordinputcomponent', @@ -36,8 +41,7 @@ const inputs: ComponentSet = { 'checkboxcomponent', 'selectcomponent', 'resourceselectcomponent', - 'multiselectcomponent', - 'schemaformcomponent' + 'multiselectcomponent' ] } as const @@ -49,17 +53,26 @@ const display: ComponentSet = { 'imagecomponent', 'mapcomponent', 'htmlcomponent', - 'tablecomponent', - 'aggridcomponent', - 'barchartcomponent', - 'piechartcomponent', - 'vegalitecomponent', - 'plotlycomponent', - 'scatterchartcomponent', - 'timeseriescomponent', 'pdfcomponent', 'displaycomponent' ] } as const -export const COMPONENT_SETS = [layout, buttons, inputs, display] as const +const tables: ComponentSet = { + title: 'Tables', + components: ['tablecomponent', 'aggridcomponent'] +} as const + +const charts: ComponentSet = { + title: 'Charts', + components: [ + 'barchartcomponent', + 'piechartcomponent', + 'vegalitecomponent', + 'plotlycomponent', + 'scatterchartcomponent', + 'timeseriescomponent' + ] +} as const + +export const COMPONENT_SETS = [tabs, layout, buttons, inputs, tables, display, charts] as const diff --git a/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts b/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts index 1b8ad79bba..bf23dfd16f 100644 --- a/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts +++ b/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts @@ -553,6 +553,9 @@ export const quickStyleProperties: Record< selectedStep: [typographyGrouping, sizeGrouping], container: containerDefaultProps }, + conditionalwrapper: { + container: containerDefaultProps + }, pdfcomponent: { container: containerDefaultProps }, diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte index 1c2574009c..e6d0dcdd43 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/ComponentPanel.svelte @@ -16,7 +16,7 @@ import AlignmentEditor from './AlignmentEditor.svelte' import RunnableInputEditor from './inputEditor/RunnableInputEditor.svelte' import TemplateEditor from '$lib/components/TemplateEditor.svelte' - import { COMPONENT_SETS, ccomponents } from '../component' + import { ccomponents } from '../component' import CssProperty from '../componentsPanel/CssProperty.svelte' import GridTab from './GridTab.svelte' import { clearErrorByComponentId, clearJobsByComponentId, deleteGridItem } from '../appUtils' @@ -27,6 +27,8 @@ import { secondaryMenu } from './secondaryMenu' import StylePanel from './StylePanel.svelte' import { Delete } from 'lucide-svelte' + import GridCondition from './GridCondition.svelte' + import { isTriggerable } from './script/utils' export let componentSettings: { item: GridItem; parent: string | undefined } | undefined = undefined @@ -127,7 +129,7 @@ $: componentSettings?.item?.data && ($app = $app) const hasInteraction = componentSettings?.item.data.type - ? COMPONENT_SETS[1].components.includes(componentSettings?.item.data.type) + ? isTriggerable(componentSettings?.item.data.type) : false @@ -244,6 +246,8 @@ {:else if componentSettings.item.data.type === 'steppercomponent'} + {:else if componentSettings.item.data.type === 'conditionalwrapper'} + {:else if componentSettings.item.data.type === 'verticalsplitpanescomponent' || componentSettings.item.data.type === 'horizontalsplitpanescomponent'} {:else if componentSettings.item.data.type === 'tablecomponent' && Array.isArray(componentSettings.item.data.actionButtons)} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/GridCondition.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/GridCondition.svelte new file mode 100644 index 0000000000..e0ac0cb180 --- /dev/null +++ b/frontend/src/lib/components/apps/editor/settingsPanel/GridCondition.svelte @@ -0,0 +1,214 @@ + + + + + Conditions are evaluated in order. The first condition that evaluates to true will render its + subgrid. + + {#if items.length == 0} + No Tabs + {/if} +
+
+ {#each items as item, index (item.id)} + {#if index < items.length - 1} + {@const condition = item.value} +
+
+ +
+ +
+ +
deleteSubgrid(index)}> + +
+ + +
+ +
+
+
+ {/if} + {/each} +
+
+
+ Default + +
+ + The default subgrid is rendered when no conditions evaluate to true. + +
+
+
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/TabSelectInput.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/TabSelectInput.svelte index 1393651c03..19a96d3446 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/TabSelectInput.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/TabSelectInput.svelte @@ -9,7 +9,8 @@ const { app } = getContext('AppViewerContext') const tabComponents = allItems($app.grid, $app.subgrids).filter( - (component) => component.data.type === 'tabscomponent' + (component) => + component.data.type === 'tabscomponent' || component.data.type === 'conditionalwrapper' ) diff --git a/frontend/src/lib/components/apps/inputType.ts b/frontend/src/lib/components/apps/inputType.ts index cba61e902c..bd7d23ee2f 100644 --- a/frontend/src/lib/components/apps/inputType.ts +++ b/frontend/src/lib/components/apps/inputType.ts @@ -97,7 +97,7 @@ export type ResultInput = { hideRefreshButton?: boolean } -type AppInputSpec = ( +export type AppInputSpec = ( | StaticInput | ConnectedInput | UserInput diff --git a/frontend/src/lib/components/common/button/ButtonDropdown.svelte b/frontend/src/lib/components/common/button/ButtonDropdown.svelte index 1c21eeb57d..016d01dc28 100644 --- a/frontend/src/lib/components/common/button/ButtonDropdown.svelte +++ b/frontend/src/lib/components/common/button/ButtonDropdown.svelte @@ -1,10 +1,13 @@ - - +