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}
-
-
+
{/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 @@
-