Compare commits

...

66 Commits

Author SHA1 Message Date
Clement Zhang
30ac3d3679 use array methods only on arrays 2024-11-18 16:15:12 +01:00
Clement Zhang
8fcf5de0b8 multiselect findOptionsByValue: JSON.parse 2024-11-18 15:11:46 +01:00
Clement Zhang
927dbc7b6d rm unnecessary parse 2024-11-18 12:32:06 +01:00
Clement Zhang
489d0f7926 better looking on empty string option label 2024-11-18 12:27:47 +01:00
Clement Zhang
82c2ac564a multiselect: new item key order {label, value} 2024-11-18 12:12:51 +01:00
Clement Zhang
ce01d975b3 multiselect: handle duplicates (do not allow) 2024-11-18 12:09:42 +01:00
Clement Zhang
74250285fc object/array as default item 2024-11-18 11:10:15 +01:00
Clement Zhang
3c50c0ffa3 Merge branch 'main' into feat/appeditor-multiselect 2024-11-18 10:50:45 +01:00
Clement Zhang
e783f2487a Merge branch 'main' into feat/appeditor-multiselect 2024-11-15 18:14:46 +01:00
Clement Zhang
985846f312 parse 2024-11-15 17:58:04 +01:00
Clement Zhang
1a6f89df21 appselect: render (empty string), check for equal value 2024-11-15 17:00:25 +01:00
Clement Zhang
552a0f8ec8 Merge branch 'feat/appeditor-multiselect' of github.com:windmill-labs/windmill into feat/appeditor-multiselect 2024-11-15 16:15:04 +01:00
Clement Zhang
5abac8bc2b result is any[], rm stringify 2024-11-15 16:12:19 +01:00
Ruben Fiszel
200112cf1f Merge branch 'main' into feat/appeditor-multiselect 2024-11-15 15:51:58 +01:00
Clement Zhang
4fc09da15d Merge branch 'feat/appeditor-multiselect' of github.com:windmill-labs/windmill into feat/appeditor-multiselect 2024-11-15 10:13:22 +01:00
Clement Zhang
2df7c85e2d result as string[] 2024-11-15 10:09:14 +01:00
Clement Zhang
686dc177e8 multiselect: keep defaultItems (no breaking change) 2024-11-15 10:09:14 +01:00
Clement Zhang
88d75cc584 add multiselect novelties to <AppSelect />, harmonize, clean <AppSelect /> 2024-11-15 10:09:14 +01:00
Clement Zhang
1367296de0 cleaning, renaming 2024-11-15 10:09:14 +01:00
Clement Zhang
51d5987110 rm seamingly unused vars 2024-11-15 10:09:14 +01:00
Clement Zhang
2fc808c065 every app components: switching from static to eval preserves the user data input 2024-11-15 10:09:14 +01:00
Clement Zhang
940af69a6a eval=>static preserves labeled state, wording (w/ label) 2024-11-15 10:09:14 +01:00
Clement Zhang
59a9458ca8 eval preserves labeled objects 2024-11-15 10:09:14 +01:00
Clement Zhang
756b07b16c setValue param is (option.value)[] 2024-11-15 10:09:14 +01:00
Clement Zhang
e2ebad90ba only use ObjectOption in AppMultiSelectv2 2024-11-15 10:09:14 +01:00
Clement Zhang
c724982d33 dirty fix 2024-11-15 10:09:14 +01:00
Clement Zhang
70737c6642 cleaning, doc 2024-11-15 10:09:14 +01:00
Clement Zhang
b84c76ee9f rm local output 2024-11-15 10:09:14 +01:00
Clement Zhang
663a193c2a clarify label when empty string 2024-11-15 10:09:14 +01:00
Clement Zhang
70b4bff32c avoid ghost options on labeled toggle, tiny cleaning 2024-11-15 10:09:14 +01:00
Clement Zhang
42bc803445 stable staticinputeditor! 2024-11-15 10:09:14 +01:00
Clement Zhang
a1038c32b7 WIP: unstable type handler 2024-11-15 10:09:14 +01:00
Clement Zhang
8e605a7f0f use the same editor as <Select /> 2024-11-15 10:09:14 +01:00
Clement Zhang
d6dc3be311 addressing ellipsis bot PR comments 2024-11-15 10:09:14 +01:00
Clement Zhang
3207a98d1a fix default items 2024-11-15 10:09:14 +01:00
Clement Zhang
629e1ec36b change input type 2024-11-15 10:09:14 +01:00
Clement Zhang
349be01861 Merge remote-tracking branch 'upstream/main' into appeditor-multiselect-input-type 2024-11-14 10:35:35 +01:00
Clement Zhang
d04620e86e result as string[] 2024-11-08 13:41:30 +01:00
Clement Zhang
d4a556d0f6 multiselect: keep defaultItems (no breaking change) 2024-11-08 11:26:42 +01:00
Clement Zhang
f350e3679b Merge remote-tracking branch 'upstream/main' into appeditor-multiselect-input-type 2024-11-07 16:42:04 +01:00
Clement Zhang
e338c28713 add multiselect novelties to <AppSelect />, harmonize, clean <AppSelect /> 2024-11-07 16:39:32 +01:00
Clement Zhang
8549cb20b0 cleaning, renaming 2024-11-07 14:52:02 +01:00
Clement Zhang
4b519a5dc8 rm seamingly unused vars 2024-11-07 14:32:24 +01:00
Clement Zhang
6b60ccc7ad Merge remote-tracking branch 'upstream/main' into appeditor-multiselect-input-type 2024-11-07 14:18:27 +01:00
Clement Zhang
34249143e7 every app components: switching from static to eval preserves the user data input 2024-11-07 14:18:14 +01:00
Clement Zhang
14ea9d6b55 eval=>static preserves labeled state, wording (w/ label) 2024-11-07 13:15:32 +01:00
Clement Zhang
eaa6e36f9d eval preserves labeled objects 2024-11-07 12:19:50 +01:00
Clement Zhang
fa9e4ba328 setValue param is (option.value)[] 2024-11-07 11:47:37 +01:00
Clement Zhang
322ed7dfd0 only use ObjectOption in AppMultiSelectv2 2024-11-07 11:10:07 +01:00
Clement Zhang
b91f4372e9 Merge remote-tracking branch 'upstream/main' into appeditor-multiselect-input-type 2024-11-07 10:32:48 +01:00
Clement Zhang
67e06d54f0 dirty fix 2024-11-06 18:45:07 +01:00
Clement Zhang
b5f3188239 cleaning, doc 2024-11-06 18:12:04 +01:00
Clement Zhang
6258c501a2 rm local output 2024-11-06 17:50:59 +01:00
Clement Zhang
e94c1f9a9d clarify label when empty string 2024-11-06 17:49:48 +01:00
Clement Zhang
d256f41793 Merge remote-tracking branch 'upstream/main' into appeditor-multiselect-input-type 2024-11-06 17:40:13 +01:00
Clement Zhang
7d52eecad8 avoid ghost options on labeled toggle, tiny cleaning 2024-11-06 17:37:10 +01:00
Clement Zhang
d74e406a2d stable staticinputeditor! 2024-11-06 16:53:25 +01:00
Clement Zhang
d9bd24df95 Merge remote-tracking branch 'upstream/main' into appeditor-multiselect-input-type 2024-11-06 10:45:45 +01:00
Clement Zhang
390e46245d WIP: unstable type handler 2024-11-05 18:39:15 +01:00
Clement Zhang
e1ea5bd2e8 Merge remote-tracking branch 'upstream/main' into appeditor-multiselect-input-type 2024-11-05 10:29:22 +01:00
Clement Zhang
546da5b983 use the same editor as <Select /> 2024-11-04 19:00:23 +01:00
Clement Zhang
29c9ba874c Merge remote-tracking branch 'upstream/main' into appeditor-multiselect-input-type 2024-11-04 18:01:44 +01:00
Clement Zhang
32f8127e02 addressing ellipsis bot PR comments 2024-11-04 17:53:50 +01:00
Clement Zhang
f875e74064 Merge branch 'main' into appeditor-multiselect-input-type 2024-11-04 17:37:24 +01:00
Clement Zhang
0ae0321e4b fix default items 2024-11-04 17:35:21 +01:00
Clement Zhang
9aee6f78d0 change input type 2024-11-04 16:44:53 +01:00
10 changed files with 269 additions and 84 deletions

View File

@@ -16,10 +16,13 @@
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
import MultiSelect from '$lib/components/multiselect/MultiSelect.svelte'
import type { ObjectOption } from '../../../multiselect/types'
import { parseConfigOptions } from './utils'
import { deepEqual } from 'fast-equals'
export let id: string
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'multiselectcomponent'> | undefined = undefined
export let id: string
export let render: boolean
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
@@ -30,55 +33,33 @@
const { app, worldStore, selectedComponent, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
let items: string[]
const resolvedConfig = initConfig(
components['multiselectcomponent'].initialData.configuration,
components['multiselectcomponentv2'].initialData.configuration,
configuration
)
const outputs = initOutput($worldStore, id, {
result: [] as string[]
})
let value: string[] | undefined = [...new Set(outputs?.result.peak())] as string[]
$componentControl[id] = {
setValue(nvalue: string[]) {
value = [...new Set(nvalue)]
outputs?.result.set([...(value ?? [])])
}
}
$: resolvedConfig.items && handleItems()
function handleItems() {
if (Array.isArray(resolvedConfig.items)) {
items = resolvedConfig.items?.map((label) => {
return typeof label === 'string' ? label : `NOT_STRING`
})
}
}
$: resolvedConfig.defaultItems && handleDefaultItems()
function handleDefaultItems() {
if (Array.isArray(resolvedConfig.defaultItems)) {
const nvalue = resolvedConfig.defaultItems?.map((label) => {
return typeof label === 'string' ? label : `NOT_STRING`
})
value = [...new Set(nvalue)]
outputs?.result.set([...(value ?? [])])
}
}
const outputs = initOutput($worldStore, id, { result: [] as any[] })
let options: ObjectOption[] = []
let selectedOptions: ObjectOption[] = [...new Set(findOptionsByValue(outputs?.result.peak()))]
let css = initCss($app.css?.multiselectcomponent, customCss)
let outerDiv: HTMLDivElement | undefined = undefined
let portalRef: HTMLDivElement | undefined = undefined
let w = 0
let open: boolean = false
function setOuterDivStyle(outerDiv: HTMLDivElement, portalRef: HTMLDivElement, style: string) {
outerDiv.setAttribute('style', style)
// find ul in portalRef and set style
const ul = portalRef.querySelector('ul')
ul?.setAttribute('style', extractCustomProperties(style))
$: if (resolvedConfig.items) {
options = parseConfigOptions(resolvedConfig.items)
}
$componentControl[id] = {
setValue(newValues: any[]) {
selectOptionsByValue(newValues)
}
}
$: if (resolvedConfig.defaultItems) {
selectOptionsByValue(resolvedConfig.defaultItems)
}
$: outerDiv &&
@@ -86,8 +67,56 @@
css?.multiselect?.style &&
setOuterDivStyle(outerDiv, portalRef, css?.multiselect?.style)
let outerDiv: HTMLDivElement | undefined = undefined
let portalRef: HTMLDivElement | undefined = undefined
$: if (render && portalRef && outerDiv && options?.length > 0) {
tick().then(() => {
moveOptionsToPortal()
})
}
function findOptionsByValue(values: any[]) {
if (!Array.isArray(values)) {
return []
}
return options?.filter((item) => {
let itemValue = item.value
try {
if (typeof itemValue == 'string') {
itemValue = JSON.parse(itemValue)
}
} catch (_) {}
return values.some((value) => deepEqual(itemValue, value))
}) as ObjectOption[]
}
function selectOptionsByValue(values: any[]) {
if (!Array.isArray(values)) {
outputs?.result.set([])
}
selectedOptions = findOptionsByValue(values)
outputs?.result.set([...toValues(selectedOptions)])
}
function toValues(options: ObjectOption[] | undefined) {
if (!options || !Array.isArray(options)) {
return []
}
return options.map((o) => {
let val = o.value
try {
if (typeof val === 'string') val = JSON.parse(val)
} catch (_) {}
return val
})
}
function setOuterDivStyle(outerDiv: HTMLDivElement, portalRef: HTMLDivElement, style: string) {
outerDiv.setAttribute('style', style)
// find ul in portalRef and set style
const ul = portalRef.querySelector('ul')
ul?.setAttribute('style', extractCustomProperties(style))
}
function moveOptionsToPortal() {
// Find ul element with class 'options' within the outerDiv
@@ -98,14 +127,6 @@
portalRef?.appendChild(ul)
}
}
$: if (render && portalRef && outerDiv && items?.length > 0) {
tick().then(() => {
moveOptionsToPortal()
})
}
let w = 0
let open: boolean = false
</script>
{#each Object.keys(components['multiselectcomponent'].initialData.configuration) as key (key)}
@@ -128,7 +149,6 @@
{/each}
<InitializeComponent {id} />
<AlignWrapper {render} hFull {verticalAlignment}>
<div
class="w-full app-editor-input"
@@ -143,7 +163,7 @@
use:floatingRef
bind:clientWidth={w}
>
{#if !value || Array.isArray(value)}
{#if !selectedOptions || Array.isArray(selectedOptions)}
<MultiSelect
bind:outerDiv
outerDivClass={`${resolvedConfig.allowOverflow ? '' : 'h-full'}`}
@@ -151,15 +171,19 @@
--sms-border={'none'}
--sms-min-height={'32px'}
--sms-focus-border={'none'}
bind:selected={value}
options={items}
bind:selected={selectedOptions}
{options}
placeholder={resolvedConfig.placeholder}
allowUserOptions={resolvedConfig.create}
key={(option) => {
if (typeof option == 'string' || typeof option == 'number') return option
return option.value
}}
on:change={(event) => {
if (event?.detail?.type === 'removeAll') {
outputs?.result.set([])
} else {
outputs?.result.set([...(value ?? [])])
outputs?.result.set([...toValues(selectedOptions)])
}
}}
on:open={() => {
@@ -181,9 +205,10 @@
e.target?.['parentElement']?.dispatchEvent(newe)
}}
>
{option}
{@html option.label === '' ? `&nbsp;` : option.label}
</div>
</MultiSelect>
<Portal name="app-multiselect-v2">
<div use:floatingContent class="z5000" hidden={!open}>
<!-- svelte-ignore a11y-click-events-have-key-events -->
@@ -197,7 +222,7 @@
</div>
</Portal>
{:else}
Value {value} is not an array
Value {selectedOptions} is not an array
{/if}
</div>
</AlignWrapper>

View File

@@ -0,0 +1,48 @@
import {
type ObjectOption,
isObjectOptionArray,
isStringArray
} from '$lib/components/multiselect/types'
import type { StaticAppInput } from '../../inputType'
export function parseConfigOptions(resolvedConfigItems: StaticAppInput) {
if (!resolvedConfigItems) {
return []
}
if (isObjectOptionArray(resolvedConfigItems)) {
return parseLabeledItems(resolvedConfigItems)
} else if (isStringArray(resolvedConfigItems)) {
return parseStringItems(resolvedConfigItems)
}
return []
}
export function parseLabeledItems(resolvedConfigItems: ObjectOption[]) {
return resolvedConfigItems?.map((item: ObjectOption) => {
if (!item || typeof item !== 'object') {
console.error(
'When labeled, MultiSelect component items should be an array of { label: string, value: string }.'
)
return {
label: 'not object',
value: 'not object'
}
}
return {
label: item?.label ?? 'undefined',
value:
typeof item?.value === 'object' ? JSON.stringify(item.value) : item?.value ?? 'undefined'
}
})
}
export function parseStringItems(resolvedConfigItems: string[]): ObjectOption[] {
return resolvedConfigItems?.map((option: string) => {
if (option === null || option === undefined || typeof option !== 'string') {
console.error('When not labeled, MultiSelect component items should be an array of strings.')
return { label: 'not string', value: 'not string' }
}
if (option === '') {
return { label: 'empty string', value: 'empty string' }
}
return { label: option, value: option }
})
}

View File

@@ -2079,7 +2079,8 @@ This is a paragraph.
value: [
{ value: 'foo', label: 'Foo' },
{ value: 'bar', label: 'Bar' }
]
],
hasLabeledMode: true
} as StaticAppInput,
create: {
type: 'static',
@@ -2199,14 +2200,15 @@ This is a paragraph.
items: {
type: 'static',
fieldType: 'array',
subFieldType: 'text',
value: ['Foo', 'Bar']
subFieldType: 'simplestringselect',
value: ['Foo', 'Bar'],
hasLabeledMode: true
} as StaticAppInput,
defaultItems: {
type: 'static',
fieldType: 'array',
subFieldType: 'text',
value: []
subFieldType: 'simplestringselect',
value: undefined
} as StaticAppInput,
placeholder: {
type: 'static',

View File

@@ -9,15 +9,30 @@
import Toggle from '$lib/components/Toggle.svelte'
import QuickAddColumn from './QuickAddColumn.svelte'
import RefreshDatabaseStudioTable from './RefreshDatabaseStudioTable.svelte'
import { isObjectOptionArray, isStringArray } from '$lib/components/multiselect/types'
export let componentInput: StaticInput<any[]> & { loading?: boolean }
export let componentInput: StaticInput<any[]> & {
loading?: boolean
expr?: string
isLabeled?: boolean
}
export let subFieldType: InputType | undefined = undefined
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
export let id: string | undefined
export let hasLabeledMode: boolean = false
const dispatch = createEventDispatcher()
const flipDurationMs = 200
let items = getItems(componentInput)
let raw: boolean = false
let labeled: boolean = subFieldType === 'labeledselect'
$: subFieldType === 'db-explorer' && clearTableOnComponentReset(componentInput?.value)
$: labeled !== undefined && handleLabeledChange()
$: items !== undefined && handleItemsChange()
function addElementByType() {
if (!Array.isArray(componentInput.value)) {
componentInput.value = []
@@ -32,12 +47,13 @@
} else if (subFieldType === 'object') {
value.push({})
} else if (subFieldType === 'labeledresource' || subFieldType === 'labeledselect') {
value.push({ value: 'value', label: 'label' })
value.push({ label: 'label', value: 'value' })
} else if (subFieldType === 'tab-select') {
value.push({ id: '', index: 0 })
} else if (
subFieldType === 'text' ||
subFieldType === 'textarea' ||
subFieldType === 'simplestringselect' ||
// TODO: Add support for these types
subFieldType === 'date' ||
subFieldType === 'time' ||
@@ -193,8 +209,6 @@
componentInput.value = reorderedValues
}
let items = getItems(componentInput)
function getItems(componentInput: StaticInput<any[]> & { loading?: boolean }) {
return (Array.isArray(componentInput.value) ? componentInput.value : [])
.filter((x) => x != undefined)
@@ -209,15 +223,30 @@
}
}
$: subFieldType === 'db-explorer' && clearTableOnComponentReset(componentInput?.value)
$: items != undefined && handleItemsChange()
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)
newComponentInput.expr = JSON.stringify(newComponentInput.value)
newComponentInput.isLabeled = labeled
dispatch('componentInputChange', newComponentInput)
}
/** Transforms items string[] to ObjectOption] and vice versa. Mutating items will fire handleItemsChange. */
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
}
}
}
let raw: boolean = false
// let mounted = false
// $: if (componentInput.value && mounted) {
@@ -243,7 +272,6 @@
{#if Array.isArray(items) && componentInput.value}
<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={{
@@ -253,6 +281,16 @@
bind:checked={raw}
/>
{/if}
{#if hasLabeledMode}
<Toggle
options={{
right: 'w/ label'
}}
size="xs"
bind:checked={labeled}
on:change={handleLabeledChange}
/>
{/if}
</div>
<section
use:dragHandleZone={{
@@ -271,7 +309,7 @@
<div class="grow min-w-0">
<SubTypeEditor
{id}
subFieldType={raw ? 'object' : subFieldType}
subFieldType={raw ? 'object' : labeled ? 'labeledselect' : subFieldType}
bind:componentInput
bind:value={item.value}
on:remove={() => deleteElementByType(index)}

View File

@@ -43,6 +43,7 @@
export let documentationLink: string | undefined = undefined
export let markdownTooltip: string | undefined = undefined
export let securedContext = false
export let hasLabeledMode = false
const { connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
@@ -171,6 +172,7 @@
{selectOptions}
{format}
{placeholder}
{hasLabeledMode}
bind:componentInput
/>
</div>

View File

@@ -81,6 +81,7 @@
{recomputeOnInputChanged}
{showOnDemandOnlyToggle}
{securedContext}
hasLabeledMode={meta?.['hasLabeledMode']}
/>
{#if deletable}
<div class="flex flex-row-reverse -mt-4">

View File

@@ -30,6 +30,9 @@
editor?.setCode(code)
}
let fullscreen = false
let focus = false
$: extraLib =
componentInput?.expr && $worldStore
? buildExtraLib($worldStore?.outputsById ?? {}, acceptSelf ? '' : id, $state, false)
@@ -52,9 +55,6 @@
inferDeps(code, $worldStore.outputsById, componentInput, app)
}
}
let fullscreen = false
let focus = false
</script>
{#if componentInput?.type === 'evalv2'}

View File

@@ -3,7 +3,7 @@
import ArrayStaticInputEditor from '../ArrayStaticInputEditor.svelte'
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
import JsonEditor from './JsonEditor.svelte'
import { getContext } from 'svelte'
import { getContext, onMount } from 'svelte'
import type { AppViewerContext } from '$lib/components/apps/types'
import IconSelectInput from './IconSelectInput.svelte'
import ColorInput from './ColorInput.svelte'
@@ -24,17 +24,49 @@
import EditableSchemaDrawer from '$lib/components/schema/EditableSchemaDrawer.svelte'
import AppPicker from '$lib/components/wizards/AppPicker.svelte'
export let componentInput: StaticInput<any> | undefined
export let componentInput: (StaticInput<any> & { isLabeled?: boolean }) | undefined
export let fieldType: InputType | undefined = undefined
export let subFieldType: InputType | undefined = undefined
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
export let placeholder: string | undefined = undefined
export let format: string | undefined = undefined
export let id: string | undefined
export let hasLabeledMode: boolean = false
const { onchange } = getContext<AppViewerContext>('AppViewerContext')
$: componentInput && onchange?.()
onMount(() => {
/** preserve the `labeled` toggling when switching from eval to static */
if (componentInput !== undefined) {
handleComponentInputChange(componentInput)
}
})
/**
* Every components: switching from `static` to `eval` preserves the user data
*
* Multiselect: toggling `labeled` needs to mutate input value and input fieldType.
* */
function handleComponentInputChange(newComponentInput: StaticInput<any>) {
componentInput = { ...componentInput, ...newComponentInput }
if (componentInput?.isLabeled) {
if (fieldType === 'array' && subFieldType === 'simplestringselect') {
subFieldType = 'labeledselect'
}
if (fieldType === 'simplestringselect') {
fieldType = 'labeledselect'
}
} else if (componentInput?.isLabeled === false) {
if (fieldType === 'array' && subFieldType === 'labeledselect') {
subFieldType = 'simplestringselect'
}
if (fieldType === 'labeledselect') {
fieldType = 'simplestringselect'
}
}
}
</script>
{#key subFieldType}
@@ -133,7 +165,7 @@
{/if}
{:else if fieldType === 'color'}
<ColorInput bind:value={componentInput.value} />
{:else if fieldType === 'object' || fieldType == 'labeledselect'}
{:else if fieldType === 'object' || fieldType == 'labeledselect' || fieldType === 'simplestringselect'}
{#if format?.startsWith('resource-') && (componentInput.value == undefined || typeof componentInput.value == 'string')}
<ResourcePicker
initialValue={componentInput.value?.split('$res:')?.[1] || ''}
@@ -162,7 +194,14 @@
</div>
{/if}
{:else if fieldType === 'array'}
<ArrayStaticInputEditor {id} {subFieldType} bind:componentInput on:deleteArrayItem />
<ArrayStaticInputEditor
{id}
{subFieldType}
bind:componentInput
on:deleteArrayItem
on:componentInputChange={(e) => handleComponentInputChange(e.detail)}
{hasLabeledMode}
/>
{:else if fieldType === 'schema'}
<div class="w-full">
<EditableSchemaDrawer bind:schema={componentInput.value} />

View File

@@ -20,6 +20,7 @@ export type InputType =
| 'any'
| 'labeledresource'
| 'labeledselect'
| 'simplestringselect'
| 'tab-select'
| 'schema'
| 'ag-grid'
@@ -175,6 +176,7 @@ type InputConfiguration<T extends InputType, V extends InputType> = {
noStatic?: boolean
onDemandOnly?: boolean
hideRefreshButton?: boolean
isLabeled?: boolean
}
export type StaticOptions = {
@@ -207,6 +209,7 @@ export type AppInput =
| (AppInputSpec<'array', string[], 'select'> & StaticOptions)
| AppInputSpec<'array', object[], 'labeledresource'>
| AppInputSpec<'array', object[], 'labeledselect'>
| AppInputSpec<'array', string[], 'simplestringselect'>
| AppInputSpec<'labeledselect', object>
| AppInputSpec<'labeledresource', object>
| AppInputSpec<'array', object[], 'tab-select'>

View File

@@ -45,3 +45,30 @@ export type MultiSelectEvents = {
touchmove: TouchEvent
touchstart: TouchEvent
}
export 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
}
export function isObjectOptionArray(arr: any): arr is ObjectOption[] {
if (!arr || !Array.isArray(arr)) {
return false
}
return arr.every((item) => isObjectOption(item))
}
export function isStringArray(arr: any): arr is string[] {
if (!arr || !Array.isArray(arr)) {
return false
}
return arr.every((item) => typeof item === 'string')
}