Files
windmill/frontend/src/lib/components/apps/editor/settingsPanel/GridCondition.svelte
2023-06-18 16:07:48 +02:00

214 lines
6.0 KiB
Svelte

<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'
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)
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}
noVariablePicker={true}
/>
</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">
<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: faPlus }}
on:click={addCondition}
iconOnly
/>
</div>
</PanelSection>