mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 16:02:19 +00:00
feat(frontend): conditional rendering (#1555)
* feat(frontend): conditional rendering * feat(frontend): done * feat(frontend): small fix * feat(frontend): done * feat(frontend): renaming * feat(frontend): restructure components * feat(frontend): revert setTab * feat(frontend): fix setTab * feat(frontend): fix setTab
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfiguration } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { InputValue } from '../helpers'
|
||||
|
||||
export let id: string
|
||||
export let componentContainerHeight: number
|
||||
export let customCss: ComponentCustomCSS<'containercomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
export let conditions: RichConfiguration[]
|
||||
|
||||
const { app, focusedGrid, selectedComponent, worldStore, connectingInput, componentControl } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const outputs = initOutput($worldStore, id, {
|
||||
conditions: [] as boolean[],
|
||||
selectedConditionIndex: 0
|
||||
})
|
||||
|
||||
function onFocus() {
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: selectedConditionIndex
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.containercomponent, customCss)
|
||||
|
||||
let resolvedConditions: boolean[] = []
|
||||
let selectedConditionIndex = 0
|
||||
|
||||
function handleResolvedConditions() {
|
||||
const slicedArray = resolvedConditions.slice(0, conditions.length)
|
||||
const firstTrueIndex = slicedArray.findIndex((c) => c)
|
||||
|
||||
outputs.conditions.set(slicedArray, true)
|
||||
|
||||
setSelectedIndex(firstTrueIndex)
|
||||
}
|
||||
|
||||
function setSelectedIndex(index: number) {
|
||||
console.trace('setSelectedIndex', index)
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: index
|
||||
}
|
||||
|
||||
selectedConditionIndex = index
|
||||
outputs.selectedConditionIndex.set(index)
|
||||
}
|
||||
|
||||
$: resolvedConditions && handleResolvedConditions()
|
||||
|
||||
$componentControl[id] = {
|
||||
setTab: (conditionIndex: number) => {
|
||||
if (conditionIndex === -1) {
|
||||
handleResolvedConditions()
|
||||
} else {
|
||||
setSelectedIndex(conditionIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each conditions ?? [] as condition, index}
|
||||
<InputValue {id} input={condition} bind:value={resolvedConditions[index]} />
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<div class="w-full h-full">
|
||||
{#if $app.subgrids?.[`${id}-${selectedConditionIndex}`]}
|
||||
<SubGridEditor
|
||||
visible={render}
|
||||
{id}
|
||||
class={css?.container?.class}
|
||||
style={css?.container?.style}
|
||||
subGridId={`${id}-${selectedConditionIndex}`}
|
||||
containerHeight={componentContainerHeight}
|
||||
on:focus={() => {
|
||||
if (!$connectingInput.opened) {
|
||||
$selectedComponent = [id]
|
||||
}
|
||||
onFocus()
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -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>('AppViewerContext')
|
||||
|
||||
$: error = getErrorFromLatestResult(component.id, $errorByComponent, $jobs)
|
||||
@@ -68,6 +72,58 @@
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
{#if component.type === 'conditionalwrapper'}
|
||||
<button
|
||||
title="Conditions"
|
||||
class={classNames(
|
||||
'text-2xs py-0.5 font-bold w-fit border cursor-pointer rounded-sm',
|
||||
isConditionalWrapperManuallySelected
|
||||
? 'bg-red-100 text-red-600 border-red-500 hover:bg-red-200 hover:text-red-800'
|
||||
: 'bg-indigo-100 text-indigo-600 border-indigo-500 hover:bg-indigo-200 hover:text-indigo-800'
|
||||
)}
|
||||
on:click={() => dispatch('triggerInlineEditor')}
|
||||
on:pointerdown|stopPropagation
|
||||
>
|
||||
<ButtonDropdown hasPadding={false}>
|
||||
<svelte:fragment slot="items">
|
||||
{#each component.conditions ?? [] as { }, index}
|
||||
<MenuItem
|
||||
on:click={() => {
|
||||
$componentControl?.[component.id]?.setTab?.(index)
|
||||
isConditionalWrapperManuallySelected = true
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'!text-gray-600 text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
|
||||
)}
|
||||
>
|
||||
{#if index === component.conditions.length - 1}
|
||||
{`Debug default condition`}
|
||||
{:else}
|
||||
{`Debug condition ${index + 1}`}
|
||||
{/if}
|
||||
</div>
|
||||
</MenuItem>
|
||||
{/each}
|
||||
<MenuItem
|
||||
on:click={() => {
|
||||
$componentControl?.[component.id]?.setTab?.(-1)
|
||||
isConditionalWrapperManuallySelected = false
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'!text-red-600 text-left px-4 py-2 gap-2 cursor-pointer hover:bg-gray-100 !text-xs font-semibold'
|
||||
)}
|
||||
>
|
||||
{`Reset debug mode`}
|
||||
</div>
|
||||
</MenuItem>
|
||||
</svelte:fragment>
|
||||
</ButtonDropdown>
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
title="Expand"
|
||||
class={classNames(
|
||||
@@ -94,17 +150,18 @@
|
||||
<Anchor aria-label="Lock position" size={14} />
|
||||
{/if}
|
||||
</button>
|
||||
<span
|
||||
<div
|
||||
draggable="false"
|
||||
title="Move"
|
||||
on:mousedown|stopPropagation|capture
|
||||
class={classNames(
|
||||
'px-1 text-2xs py-0.5 font-bold w-fit border cursor-move rounded-sm',
|
||||
'bg-indigo-100 text-indigo-600 border-indigo-500 hover:bg-indigo-200 hover:text-indigo-800'
|
||||
'bg-indigo-100 text-indigo-600 border-indigo-500 hover:bg-indigo-200 hover:text-indigo-800',
|
||||
'flex items-center justify-center'
|
||||
)}
|
||||
>
|
||||
<Move size={14} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -248,6 +248,7 @@ export function appComponentFromType<T extends keyof typeof components>(
|
||||
componentInput: init.componentInput,
|
||||
panes: init.panes,
|
||||
tabs: init.tabs,
|
||||
conditions: init.conditions,
|
||||
customCss: {},
|
||||
recomputeIds: init.recomputeIds ? [] : undefined,
|
||||
actionButtons: init.actionButtons ? [] : undefined,
|
||||
|
||||
@@ -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}
|
||||
<AppConditionalWrapper
|
||||
id={component.id}
|
||||
conditions={component.conditions}
|
||||
customCss={component.customCss}
|
||||
{componentContainerHeight}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'containercomponent'}
|
||||
<AppContainer
|
||||
id={component.id}
|
||||
|
||||
@@ -34,17 +34,20 @@ import {
|
||||
FlipHorizontal,
|
||||
FlipVertical,
|
||||
FileText,
|
||||
AtSignIcon
|
||||
AtSignIcon,
|
||||
Split
|
||||
} from 'lucide-svelte'
|
||||
import type {
|
||||
Aligned,
|
||||
BaseAppComponent,
|
||||
ComponentCustomCSS,
|
||||
GridItem,
|
||||
RichConfiguration,
|
||||
StaticRichConfigurations
|
||||
} from '../../types'
|
||||
import type { Size } from '../../svelte-grid/types'
|
||||
import type { ResultAppInput, StaticAppInput } from '../../inputType'
|
||||
|
||||
import type { AppInputSpec, ResultAppInput, StaticAppInput } from '../../inputType'
|
||||
|
||||
export type BaseComponent<T extends string> = {
|
||||
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<Aligned> {
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -553,6 +553,9 @@ export const quickStyleProperties: Record<
|
||||
selectedStep: [typographyGrouping, sizeGrouping],
|
||||
container: containerDefaultProps
|
||||
},
|
||||
conditionalwrapper: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
pdfcomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
|
||||
@@ -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
|
||||
</script>
|
||||
|
||||
@@ -244,6 +246,8 @@
|
||||
<GridTab bind:tabs={componentSettings.item.data.tabs} {component} />
|
||||
{:else if componentSettings.item.data.type === 'steppercomponent'}
|
||||
<GridTab bind:tabs={componentSettings.item.data.tabs} {component} word="Step" />
|
||||
{:else if componentSettings.item.data.type === 'conditionalwrapper'}
|
||||
<GridCondition bind:conditions={componentSettings.item.data.conditions} {component} />
|
||||
{:else if componentSettings.item.data.type === 'verticalsplitpanescomponent' || componentSettings.item.data.type === 'horizontalsplitpanescomponent'}
|
||||
<GridPane bind:panes={componentSettings.item.data.panes} {component} />
|
||||
{:else if componentSettings.item.data.type === 'tablecomponent' && Array.isArray(componentSettings.item.data.actionButtons)}
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
<script lang="ts">
|
||||
import type { AppInputSpec } from '../../inputType'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
import { dndzone } from 'svelte-dnd-action'
|
||||
import { GripVertical, X } from 'lucide-svelte'
|
||||
import InputsSpecEditor from './InputsSpecEditor.svelte'
|
||||
import { generateRandomString } from '$lib/utils'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppViewerContext, RichConfiguration } from '../../types'
|
||||
import { getContext, tick } from 'svelte'
|
||||
import { deleteGridItem } from '../appUtils'
|
||||
import type { AppComponent } from '../component'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
|
||||
export let conditions: RichConfiguration[] = []
|
||||
export let component: AppComponent
|
||||
let dragDisabled = true
|
||||
|
||||
let items = conditions.map((condition, index) => {
|
||||
return { value: condition, id: generateRandomString(), originalIndex: index }
|
||||
})
|
||||
|
||||
$: conditions = items.map((item) => item.value)
|
||||
|
||||
const { app, runnableComponents, componentControl } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
function handleConsider(e: CustomEvent): void {
|
||||
const { items: newItems } = e.detail
|
||||
items = newItems
|
||||
}
|
||||
|
||||
function handleFinalize(e: CustomEvent) {
|
||||
const { items: newItems } = e.detail
|
||||
|
||||
items = newItems
|
||||
|
||||
// if the originalIndex are not in order, we should swap the subgrids
|
||||
if (items.some((item, index) => item.originalIndex !== index)) {
|
||||
const newSubgrids = {}
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
newSubgrids[`${component.id}-${i}`] =
|
||||
$app!.subgrids![`${component.id}-${items[i].originalIndex}`] ?? []
|
||||
}
|
||||
|
||||
// update originalIndex
|
||||
items.forEach((item, i) => {
|
||||
item.originalIndex = i
|
||||
})
|
||||
|
||||
$app!.subgrids = {
|
||||
...$app!.subgrids,
|
||||
...newSubgrids
|
||||
}
|
||||
$app = $app
|
||||
|
||||
tick().then(() => {
|
||||
const targetIndex = items.findIndex((i) => i.id === e.detail.info.id)
|
||||
$componentControl[component.id]?.setTab?.(targetIndex)
|
||||
})
|
||||
}
|
||||
|
||||
dragDisabled = true
|
||||
}
|
||||
|
||||
function startDrag(event) {
|
||||
event.preventDefault()
|
||||
dragDisabled = false
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent): void {
|
||||
if ((event.key === 'Enter' || event.key === ' ') && dragDisabled) {
|
||||
dragDisabled = false
|
||||
}
|
||||
}
|
||||
|
||||
function deleteSubgrid(index: number) {
|
||||
let subgrid = `${component.id}-${index}`
|
||||
for (const item of $app!.subgrids![subgrid]) {
|
||||
const components = deleteGridItem($app, item.data, subgrid, false)
|
||||
for (const key in components) {
|
||||
delete $runnableComponents[key]
|
||||
}
|
||||
}
|
||||
$runnableComponents = $runnableComponents
|
||||
for (let i = index; i < items.length - 1; i++) {
|
||||
$app!.subgrids![`${component.id}-${i}`] = $app!.subgrids![`${component.id}-${i + 1}`]
|
||||
}
|
||||
|
||||
items.splice(index, 1)
|
||||
|
||||
items = [...items]
|
||||
|
||||
component.numberOfSubgrids = items.length
|
||||
}
|
||||
|
||||
function addCondition(): void {
|
||||
const numberOfConditions = items.length
|
||||
|
||||
if (!$app.subgrids) {
|
||||
$app.subgrids = {}
|
||||
}
|
||||
|
||||
const lastSubgrid = JSON.parse(
|
||||
JSON.stringify($app.subgrids[`${component.id}-${numberOfConditions - 1}`])
|
||||
)
|
||||
|
||||
$app.subgrids[`${component.id}-${numberOfConditions - 1}`] = []
|
||||
$app.subgrids[`${component.id}-${numberOfConditions}`] = lastSubgrid
|
||||
component.numberOfSubgrids = items.length
|
||||
|
||||
const newCondition: AppInputSpec<'boolean', boolean> = {
|
||||
type: 'eval',
|
||||
expr: 'false',
|
||||
fieldType: 'boolean'
|
||||
}
|
||||
|
||||
items.splice(conditions.length - 1, 0, {
|
||||
value: newCondition,
|
||||
id: generateRandomString(),
|
||||
originalIndex: items.length - 1
|
||||
})
|
||||
|
||||
component.numberOfSubgrids = items.length
|
||||
}
|
||||
</script>
|
||||
|
||||
<PanelSection title={'Conditions'}>
|
||||
<Alert title="Evaluation" size="xs">
|
||||
Conditions are evaluated in order. The first condition that evaluates to true will render its
|
||||
subgrid.
|
||||
</Alert>
|
||||
{#if items.length == 0}
|
||||
<span class="text-xs text-gray-500">No Tabs</span>
|
||||
{/if}
|
||||
<div class="w-full flex flex-col mt-2">
|
||||
<section
|
||||
use:dndzone={{
|
||||
items: items,
|
||||
flipDurationMs: 200,
|
||||
dropTargetStyle: {}
|
||||
}}
|
||||
on:consider={handleConsider}
|
||||
on:finalize={handleFinalize}
|
||||
>
|
||||
{#each items as item, index (item.id)}
|
||||
{#if index < items.length - 1}
|
||||
{@const condition = item.value}
|
||||
<div class="w-full flex flex-row gap-2 items-center relative">
|
||||
<div class={twMerge('grow border p-3 my-2 rounded-md border-gray-200 bg-white')}>
|
||||
<InputsSpecEditor
|
||||
key={`Condition ${index + 1}`}
|
||||
bind:componentInput={item.value}
|
||||
id={component.id}
|
||||
userInputEnabled={false}
|
||||
shouldCapitalize={true}
|
||||
resourceOnly={false}
|
||||
hasRows={false}
|
||||
fieldType={condition?.['fieldType']}
|
||||
subFieldType={condition?.['subFieldType']}
|
||||
format={condition?.['format']}
|
||||
selectOptions={condition?.['selectOptions']}
|
||||
tooltip={condition?.['tooltip']}
|
||||
onlyStatic={condition?.['onlyStatic']}
|
||||
fileUpload={condition?.['fileUpload']}
|
||||
placeholder={condition?.['placeholder']}
|
||||
customTitle={condition?.['customTitle']}
|
||||
displayType={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center gap-2">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div on:click={() => deleteSubgrid(index)}>
|
||||
<X size={16} />
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div
|
||||
tabindex={dragDisabled ? 0 : -1}
|
||||
class="w-4 h-4"
|
||||
on:mousedown={startDrag}
|
||||
on:touchstart={startDrag}
|
||||
on:keydown={handleKeyDown}
|
||||
>
|
||||
<GripVertical size={16} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</section>
|
||||
<div class="border rounded-md p-3 mb-2">
|
||||
<div class="flex justify-between">
|
||||
<span class="font-bold text-xs">Default</span>
|
||||
<Toggle checked={true} disabled size="xs" />
|
||||
</div>
|
||||
<Alert title="Default" size="xs">
|
||||
The default subgrid is rendered when no conditions evaluate to true.
|
||||
</Alert>
|
||||
</div>
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
startIcon={{ icon: faPlus }}
|
||||
on:click={addCondition}
|
||||
iconOnly
|
||||
/>
|
||||
</div>
|
||||
</PanelSection>
|
||||
+2
-1
@@ -9,7 +9,8 @@
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const tabComponents = allItems($app.grid, $app.subgrids).filter(
|
||||
(component) => component.data.type === 'tabscomponent'
|
||||
(component) =>
|
||||
component.data.type === 'tabscomponent' || component.data.type === 'conditionalwrapper'
|
||||
)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ export type ResultInput = {
|
||||
hideRefreshButton?: boolean
|
||||
}
|
||||
|
||||
type AppInputSpec<T extends InputType, U, V extends InputType = never> = (
|
||||
export type AppInputSpec<T extends InputType, U, V extends InputType = never> = (
|
||||
| StaticInput<U>
|
||||
| ConnectedInput
|
||||
| UserInput<U>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menu, Transition, MenuButton, MenuItems } from '@rgossiaux/svelte-headlessui'
|
||||
import { ChevronDown } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let hasPadding: boolean = true
|
||||
</script>
|
||||
|
||||
<Menu let:open as="div" class="relative hover:z-50 flex w-full h-full">
|
||||
<MenuButton class="h-full w-full px-2">
|
||||
<MenuButton class={twMerge('h-full w-full', hasPadding ? 'px-2' : '')}>
|
||||
<ChevronDown class="w-5 h-5" />
|
||||
</MenuButton>
|
||||
<Transition
|
||||
|
||||
Reference in New Issue
Block a user