Files
windmill/frontend/src/lib/components/apps/editor/settingsPanel/GridCondition.svelte
T
2025-06-13 17:34:23 +02:00

202 lines
5.9 KiB
Svelte

<script lang="ts">
import type { AppInputSpec } from '../../inputType'
import Button from '$lib/components/common/button/Button.svelte'
import PanelSection from './common/PanelSection.svelte'
import { dragHandle, dragHandleZone } from '@windmill-labs/svelte-dnd-action'
import { GripVertical, Plus, 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'
export let conditions: RichConfiguration[] = []
export let component: AppComponent
let items = conditions.slice(0, -1).map((condition, index) => {
return { value: condition, id: generateRandomString(), originalIndex: index }
})
$: conditions = items
.map((item) => item.value)
.concat([
{
type: 'evalv2',
expr: 'true',
fieldType: 'boolean',
connections: []
}
])
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)
})
}
}
function deleteSubgrid(index: number) {
let subgrid = `${component.id}-${index}`
for (const item of $app!.subgrids![subgrid]) {
const components = deleteGridItem($app, item.data, subgrid)
for (const key in components) {
delete $runnableComponents[key]
}
}
$runnableComponents = $runnableComponents
for (let i = index; i < items.length; i++) {
$app!.subgrids![`${component.id}-${i}`] = $app!.subgrids![`${component.id}-${i + 1}`]
}
// Remove the corresponding item from the items array
const nitems = items.filter((item) => item.originalIndex !== index)
component.numberOfSubgrids = nitems.length + 1
// Update the originalIndex of the remaining items
nitems.forEach((item, i) => {
item.originalIndex = i
})
items = nitems
delete $app!.subgrids![`${component.id}-${items.length + 1}`] // $app = $app
}
function addCondition(): void {
const numberOfConditions = conditions.length
if (!$app.subgrids) {
$app.subgrids = {}
}
$app.subgrids[`${component.id}-${numberOfConditions}`] =
$app.subgrids[`${component.id}-${numberOfConditions - 1}`]
$app.subgrids[`${component.id}-${numberOfConditions - 1}`] = []
const newCondition: AppInputSpec<'boolean', boolean> = {
type: 'evalv2',
expr: 'false',
fieldType: 'boolean',
connections: []
}
items.splice(conditions.length - 1, 0, {
value: newCondition,
id: generateRandomString(),
originalIndex: items.length
})
component.numberOfSubgrids = items.length + 1
}
</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-tertiary">No Tabs</span>
{/if}
<div class="w-full flex flex-col mt-2">
<section
use:dragHandleZone={{
items: items,
flipDurationMs: 200,
dropTargetStyle: {}
}}
on:consider={handleConsider}
on:finalize={handleFinalize}
>
{#each items as item, index (item.id)}
{@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 bg-surface relative')}>
<InputsSpecEditor
key={`condition${index + 1}`}
bind:componentInput={item.value}
id={component.id}
userInputEnabled={false}
shouldCapitalize={true}
resourceOnly={false}
fieldType={condition?.['fieldType']}
subFieldType={condition?.['subFieldType']}
format={condition?.['format']}
selectOptions={condition?.['selectOptions']}
tooltip={condition?.['tooltip']}
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 -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div on:click={() => deleteSubgrid(index)}>
<X size={16} />
</div>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div use:dragHandle class="w-4 h-4 handle" aria-label="drag-handle">
<GripVertical size={16} />
</div>
</div>
</div>
{/each}
</section>
<div class="border rounded-md p-3 mb-2">
<div class="flex">
<span class="font-bold text-xs">Default</span>
</div>
<Alert title="Default" size="xs">
The default container is rendered if no other conditions evaluate to true.
</Alert>
</div>
<Button
size="xs"
color="light"
variant="border"
startIcon={{ icon: Plus }}
on:click={addCondition}
iconOnly
/>
</div>
</PanelSection>