stable staticinputeditor!

This commit is contained in:
Clement Zhang
2024-11-06 16:53:25 +01:00
parent a1038c32b7
commit 42bc803445
6 changed files with 190 additions and 81 deletions
@@ -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: {
@@ -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}
@@ -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'>