mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 16:02:24 +00:00
stable staticinputeditor!
This commit is contained in:
@@ -44,24 +44,28 @@
|
||||
)
|
||||
|
||||
const outputs = initOutput($worldStore, id, {
|
||||
result: [] as Options
|
||||
result: isObjectOptionArray(items)
|
||||
? ([] as ObjectOption[])
|
||||
: isStringArray(items)
|
||||
? ([] as string[])
|
||||
: ([] as unknown[])
|
||||
})
|
||||
|
||||
let value: Options | undefined = isLabeledArray(outputs?.result.peak())
|
||||
let value: Options | undefined = isObjectOptionArray(outputs?.result.peak())
|
||||
? ([...new Set(outputs?.result.peak())] as ObjectOption[])
|
||||
: ([...new Set(outputs?.result.peak())] as string[])
|
||||
|
||||
$componentControl[id] = {
|
||||
setValue(nvalue: Options) {
|
||||
if (isLabeledArray(nvalue)) {
|
||||
if (isObjectOptionArray(nvalue)) {
|
||||
value = [...new Set(nvalue)] as ObjectOption[]
|
||||
outputs?.result.set([...(value ?? [])])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isObjectOption(item: Options[number]): item is ObjectOption {
|
||||
if (typeof item === 'string') {
|
||||
function isObjectOption(item: any): item is ObjectOption {
|
||||
if (!item || item !== Object(item)) {
|
||||
return false
|
||||
}
|
||||
if (item.label === undefined || item.label === null) {
|
||||
@@ -73,11 +77,17 @@
|
||||
return true
|
||||
}
|
||||
|
||||
function isLabeledArray(arr: Options): arr is ObjectOption[] {
|
||||
function isObjectOptionArray(arr: any | undefined): arr is ObjectOption[] {
|
||||
if (!arr || !Array.isArray(arr)) {
|
||||
return false
|
||||
}
|
||||
return arr.every((item) => isObjectOption(item))
|
||||
}
|
||||
|
||||
function isStringArray(arr: Options): arr is string[] {
|
||||
function isStringArray(arr: any | undefined): arr is string[] {
|
||||
if (!arr || !Array.isArray(arr)) {
|
||||
return false
|
||||
}
|
||||
return arr.every((item) => typeof item === 'string')
|
||||
}
|
||||
|
||||
@@ -121,7 +131,7 @@
|
||||
}
|
||||
|
||||
function handleItems() {
|
||||
if (isLabeledArray(resolvedConfig.items)) {
|
||||
if (isObjectOptionArray(resolvedConfig.items)) {
|
||||
handleLabeledItems()
|
||||
} else if (isStringArray(resolvedConfig.items)) {
|
||||
handleStringItems()
|
||||
@@ -138,9 +148,11 @@
|
||||
outputs?.result.set([])
|
||||
return
|
||||
}
|
||||
let rawNvalue = new Set(resolvedConfig.defaultItems?.filter((v) => typeof v === 'string'))
|
||||
if (isLabeledArray(items)) {
|
||||
nvalue = items?.filter((item) => rawNvalue.has(item.value)) as ObjectOption[]
|
||||
let rawNvalue = new Set(
|
||||
resolvedConfig.defaultItems?.filter((v) => typeof v === 'string' || typeof v === 'number')
|
||||
)
|
||||
if (isObjectOptionArray(items)) {
|
||||
nvalue = items?.filter((item) => rawNvalue.has(item.label)) as ObjectOption[]
|
||||
value = [...new Set(nvalue)]
|
||||
outputs?.result.set([...(value ?? [])])
|
||||
} else if (isStringArray(items)) {
|
||||
@@ -184,6 +196,23 @@
|
||||
}
|
||||
let w = 0
|
||||
let open: boolean = false
|
||||
|
||||
const multiSelectProps = {
|
||||
outerDivClass: resolvedConfig.allowOverflow ? '' : 'h-full',
|
||||
ulSelectedClass: resolvedConfig.allowOverflow ? '' : 'overflow-auto max-h-full',
|
||||
'--sms-border': 'none',
|
||||
'--sms-min-height': '32px',
|
||||
'--sms-focus-border': 'none',
|
||||
placeholder: resolvedConfig.placeholder,
|
||||
allowUserOptions: resolvedConfig.create,
|
||||
onOpen: () => {
|
||||
$selectedComponent = [id]
|
||||
open = true
|
||||
},
|
||||
onClose: () => {
|
||||
open = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['multiselectcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -222,46 +251,67 @@
|
||||
bind:clientWidth={w}
|
||||
>
|
||||
{#if !value || Array.isArray(value)}
|
||||
<MultiSelect
|
||||
bind:outerDiv
|
||||
outerDivClass={`${resolvedConfig.allowOverflow ? '' : 'h-full'}`}
|
||||
ulSelectedClass={`${resolvedConfig.allowOverflow ? '' : 'overflow-auto max-h-full'} `}
|
||||
--sms-border={'none'}
|
||||
--sms-min-height={'32px'}
|
||||
--sms-focus-border={'none'}
|
||||
bind:selected={value}
|
||||
options={items}
|
||||
placeholder={resolvedConfig.placeholder}
|
||||
allowUserOptions={resolvedConfig.create}
|
||||
on:change={(event) => {
|
||||
if (event?.detail?.type === 'removeAll') {
|
||||
outputs?.result.set([])
|
||||
} else {
|
||||
outputs?.result.set([...(value ?? [])])
|
||||
}
|
||||
}}
|
||||
on:open={() => {
|
||||
$selectedComponent = [id]
|
||||
open = true
|
||||
}}
|
||||
on:close={() => {
|
||||
open = false
|
||||
}}
|
||||
let:option
|
||||
>
|
||||
<!-- needed because portal doesn't work for mouseup event en mobile -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="w-full"
|
||||
on:mouseup|stopPropagation
|
||||
on:pointerdown|stopPropagation={(e) => {
|
||||
let newe = new MouseEvent('mouseup')
|
||||
e.target?.['parentElement']?.dispatchEvent(newe)
|
||||
{#if isObjectOptionArray(value) && isObjectOptionArray(items)}
|
||||
<MultiSelect
|
||||
bind:selected={value}
|
||||
options={items}
|
||||
on:change={(event) => {
|
||||
if (event?.detail?.type === 'removeAll') {
|
||||
outputs?.result.set([])
|
||||
} else {
|
||||
outputs?.result.set([...(value ?? [])])
|
||||
}
|
||||
}}
|
||||
{...multiSelectProps}
|
||||
bind:outerDiv
|
||||
on:open={multiSelectProps.onOpen}
|
||||
on:close={multiSelectProps.onClose}
|
||||
let:option
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
</MultiSelect>
|
||||
<!-- needed because portal doesn't work for mouseup event en mobile -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="w-full"
|
||||
on:mouseup|stopPropagation
|
||||
on:pointerdown|stopPropagation={(e) => {
|
||||
let newe = new MouseEvent('mouseup')
|
||||
e.target?.['parentElement']?.dispatchEvent(newe)
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
</MultiSelect>
|
||||
{:else if isStringArray(value) && isStringArray(items)}
|
||||
<MultiSelect
|
||||
bind:selected={value}
|
||||
options={items}
|
||||
on:change={(event) => {
|
||||
if (event?.detail?.type === 'removeAll') {
|
||||
outputs?.result.set([])
|
||||
} else {
|
||||
outputs?.result.set([...(value ?? [])])
|
||||
}
|
||||
}}
|
||||
{...multiSelectProps}
|
||||
bind:outerDiv
|
||||
on:open={multiSelectProps.onOpen}
|
||||
on:close={multiSelectProps.onClose}
|
||||
let:option
|
||||
>
|
||||
<!-- needed because portal doesn't work for mouseup event en mobile -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="w-full"
|
||||
on:mouseup|stopPropagation
|
||||
on:pointerdown|stopPropagation={(e) => {
|
||||
let newe = new MouseEvent('mouseup')
|
||||
e.target?.['parentElement']?.dispatchEvent(newe)
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
</MultiSelect>
|
||||
{/if}
|
||||
<Portal name="app-multiselect-v2">
|
||||
<div use:floatingContent class="z5000" hidden={!open}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
|
||||
@@ -2199,11 +2199,8 @@ This is a paragraph.
|
||||
items: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'labeledselect',
|
||||
value: [
|
||||
{ value: 'foo', label: 'Foo' },
|
||||
{ value: 'bar', label: 'Bar' }
|
||||
],
|
||||
subFieldType: 'simplestringselect',
|
||||
value: ['Foo', 'Bar'],
|
||||
hasLabeledMode: true
|
||||
} as StaticAppInput,
|
||||
defaultItems: {
|
||||
|
||||
+68
-26
@@ -2,15 +2,16 @@
|
||||
import { Button } from '$lib/components/common'
|
||||
import { GripVertical, Loader2, Plus, X } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { InputType, LabeledOption, StaticInput, StaticOptions } from '../../inputType'
|
||||
import type { InputType, StaticInput, StaticOptions } from '../../inputType'
|
||||
import SubTypeEditor from './SubTypeEditor.svelte'
|
||||
import { dragHandle, dragHandleZone } from '@windmill-labs/svelte-dnd-action'
|
||||
import { generateRandomString, pluralize } from '$lib/utils'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import QuickAddColumn from './QuickAddColumn.svelte'
|
||||
import RefreshDatabaseStudioTable from './RefreshDatabaseStudioTable.svelte'
|
||||
import type { ObjectOption } from '$lib/components/multiselect/types'
|
||||
|
||||
export let componentInput: StaticInput<any[]> & { loading?: boolean; isLabeled?: boolean }
|
||||
export let componentInput: StaticInput<any[]> & { loading?: boolean }
|
||||
export let subFieldType: InputType | undefined = undefined
|
||||
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
|
||||
export let id: string | undefined
|
||||
@@ -19,17 +20,6 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
const flipDurationMs = 200
|
||||
|
||||
console.log(componentInput.value)
|
||||
|
||||
// transform string[] to LabeledOption[], or the opposite way
|
||||
function handleLabeledChange() {
|
||||
if (labeled) {
|
||||
componentInput.value = componentInput.value?.map((option: LabeledOption) => option?.label)
|
||||
} else if (!labeled) {
|
||||
componentInput.value = componentInput.value?.map((label) => ({ label, value: label }))
|
||||
}
|
||||
}
|
||||
|
||||
function addElementByType() {
|
||||
if (!Array.isArray(componentInput.value)) {
|
||||
componentInput.value = []
|
||||
@@ -224,16 +214,62 @@
|
||||
|
||||
$: subFieldType === 'db-explorer' && clearTableOnComponentReset(componentInput?.value)
|
||||
|
||||
$: items != undefined && handleItemsChange()
|
||||
let raw: boolean = false
|
||||
let labeled: boolean = subFieldType === 'labeledselect'
|
||||
|
||||
function handleItemsChange() {
|
||||
componentInput.value = items.map((item) => item.value).filter((item) => item != undefined)
|
||||
const newComponentInput = { ...componentInput }
|
||||
newComponentInput.value = items.map((item) => item.value).filter((item) => item != undefined)
|
||||
|
||||
const newSubFieldType = labeled ? 'labeledselect' : 'simplestringselect'
|
||||
|
||||
dispatch('componentInputChange', { newComponentInput, newSubFieldType })
|
||||
}
|
||||
|
||||
let raw: boolean = false
|
||||
let labeled: boolean = false
|
||||
function isObjectOption(item: any): item is ObjectOption {
|
||||
if (!item || item !== Object(item)) {
|
||||
return false
|
||||
}
|
||||
if (item.label === undefined || item.label === null) {
|
||||
return false
|
||||
}
|
||||
if (typeof item.label !== 'string' && typeof item.label !== 'number') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
$: labeled
|
||||
function isObjectOptionArray(arr: any | undefined): arr is ObjectOption[] {
|
||||
if (!arr || !Array.isArray(arr)) {
|
||||
return false
|
||||
}
|
||||
return arr.every((item) => isObjectOption(item))
|
||||
}
|
||||
|
||||
function isStringArray(arr: any | undefined): arr is string[] {
|
||||
if (!arr || !Array.isArray(arr)) {
|
||||
return false
|
||||
}
|
||||
return arr.every((item) => typeof item === 'string')
|
||||
}
|
||||
|
||||
// transform string[] to ObjectOption[], or the opposite way
|
||||
function handleLabeledChange() {
|
||||
if (labeled && isStringArray(items.map((item) => item.value))) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const itemValue = items[i].value
|
||||
items[i].value = { label: itemValue, value: itemValue }
|
||||
}
|
||||
} else if (!labeled && isObjectOptionArray(items.map((item) => item.value))) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const itemValue = items[i].value
|
||||
items[i].value = itemValue.label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: labeled !== undefined && handleLabeledChange()
|
||||
$: items !== undefined && handleItemsChange()
|
||||
|
||||
// let mounted = false
|
||||
|
||||
@@ -257,16 +293,22 @@
|
||||
</script>
|
||||
|
||||
<div class="flex gap-2 flex-col mt-2 w-full">
|
||||
<button
|
||||
on:click={() =>
|
||||
console.log(
|
||||
'labeled =',
|
||||
labeled,
|
||||
'items =',
|
||||
items,
|
||||
'componentInput =',
|
||||
componentInput,
|
||||
'subFieldType =',
|
||||
subFieldType
|
||||
)}>console.log in ArrayStaticInputEditor</button
|
||||
>
|
||||
{#if Array.isArray(items) && componentInput.value}
|
||||
<!-- {#if componentInput.isLabeled}<Toggle
|
||||
size="xs"
|
||||
options={{ right: 'LabeledOption' }}
|
||||
bind:checked={isLabeled}
|
||||
/>
|
||||
{/if} -->
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<div class="text-xs text-tertiary font-semibold">{pluralize(items.length, 'item')}</div>
|
||||
|
||||
{#if subFieldType === 'ag-grid' || subFieldType === 'table-column'}
|
||||
<Toggle
|
||||
options={{
|
||||
@@ -304,7 +346,7 @@
|
||||
<div class="grow min-w-0">
|
||||
<SubTypeEditor
|
||||
{id}
|
||||
subFieldType={raw ? 'object' : !labeled ? 'simplestringselect' : subFieldType}
|
||||
subFieldType={raw ? 'object' : labeled ? 'labeledselect' : subFieldType}
|
||||
bind:componentInput
|
||||
bind:value={item.value}
|
||||
on:remove={() => deleteElementByType(index)}
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
$: fakeComponentInput && (value = fakeComponentInput.value)
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={() => console.log('subFieldType =', subFieldType, 'componentInput =', componentInput)}
|
||||
>console.log in SubtypeEditor</button
|
||||
>
|
||||
<StaticInputEditor
|
||||
{id}
|
||||
fieldType={subFieldType}
|
||||
|
||||
+16
@@ -35,6 +35,21 @@
|
||||
|
||||
const { onchange } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
// Workaround for MultiSelect: changing fieldType needs parent rehydration
|
||||
function handleComponentInputChange(event: {
|
||||
newComponentInput: StaticInput<any>
|
||||
newSubFieldType: InputType
|
||||
}) {
|
||||
if (!event) {
|
||||
console.error('fired a componentInputChange with no data?')
|
||||
return
|
||||
}
|
||||
const { newComponentInput, newSubFieldType } = event
|
||||
|
||||
componentInput = { ...componentInput, ...newComponentInput }
|
||||
subFieldType = newSubFieldType
|
||||
}
|
||||
|
||||
$: componentInput && onchange?.()
|
||||
</script>
|
||||
|
||||
@@ -168,6 +183,7 @@
|
||||
{subFieldType}
|
||||
bind:componentInput
|
||||
on:deleteArrayItem
|
||||
on:componentInputChange={(e) => handleComponentInputChange(e.detail)}
|
||||
{hasLabeledMode}
|
||||
/>
|
||||
{:else if fieldType === 'schema'}
|
||||
|
||||
@@ -209,7 +209,7 @@ export type AppInput =
|
||||
| (AppInputSpec<'array', string[], 'select'> & StaticOptions)
|
||||
| AppInputSpec<'array', object[], 'labeledresource'>
|
||||
| AppInputSpec<'array', object[], 'labeledselect'>
|
||||
| AppInputSpec<'array', object[], 'simplestringselect'>
|
||||
| AppInputSpec<'array', string[], 'simplestringselect'>
|
||||
| AppInputSpec<'labeledselect', object>
|
||||
| AppInputSpec<'labeledresource', object>
|
||||
| AppInputSpec<'array', object[], 'tab-select'>
|
||||
|
||||
Reference in New Issue
Block a user