feat: windmill dyn multiselect (#6488)

* done

* fix

* better

* typo

* use old key

* chore: publish pkgs and update deps

---------

Co-authored-by: HugoCasa <hugo@casademont.ch>
This commit is contained in:
dieriba
2025-09-05 09:10:01 +02:00
committed by GitHub
parent 2027a9c2bd
commit 548c4da147
22 changed files with 241 additions and 153 deletions
@@ -235,6 +235,9 @@ fn parse_typ(id: &str) -> Typ {
x @ _ if x.starts_with("DynSelect_") => {
Typ::DynSelect(x.strip_prefix("DynSelect_").unwrap().to_string())
}
x @ _ if x.starts_with("DynMultiselect_") => {
Typ::DynMultiselect(x.strip_prefix("DynMultiselect_").unwrap().to_string())
}
_ => Typ::Resource(map_resource_name(id)),
}
}
@@ -810,6 +810,10 @@ fn tstype_to_typ(
Typ::DynSelect(symbol.strip_prefix("DynSelect_").unwrap().to_string()),
false,
),
symbol @ _ if symbol.starts_with("DynMultiselect_") => (
Typ::DynMultiselect(symbol.strip_prefix("DynMultiselect_").unwrap().to_string()),
false,
),
symbol @ _ => {
let symbol = to_snake_case(symbol);
resolve_ts_interface_and_type_alias(
@@ -846,7 +850,8 @@ fn parse_one_of_type(
if ["Resource", "Date", "Base64", "Email", "Sql"]
.iter()
.any(|s| *s == symbol)
|| symbol.starts_with("DynSelect_") =>
|| symbol.starts_with("DynSelect_")
|| symbol.starts_with("DynMultiselect_") =>
{
return None
}
@@ -617,6 +617,35 @@ mod tests {
);
}
#[test]
fn test_parse_dyn_multiselect() {
let code = r#"
export async function main(
selector: DynMultiselect_my_options
) {
return selector;
}
"#;
let sig = parse_deno_signature(code, false, false, None).unwrap();
assert_eq!(
sig,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![Arg {
name: "selector".to_string(),
otyp: None,
typ: Typ::DynMultiselect("my_options".to_string()),
default: None,
has_default: false,
oidx: None,
}],
no_main_func: Some(false),
has_preprocessor: Some(false),
}
);
}
#[test]
fn test_parse_invalid_typescript() {
let code = r#"
@@ -69,6 +69,7 @@ pub enum Typ {
Email,
Sql,
DynSelect(String),
DynMultiselect(String),
Object(ObjectType),
OneOf(Vec<OneOfVariant>),
#[default]
@@ -274,6 +274,7 @@ impl OpenAPISchema {
Typ::Email => Self::from_str("string"),
Typ::Sql => Self::from_str("string"),
Typ::DynSelect(_) => Self::from_str("string"),
Typ::DynMultiselect(_) => Self::from_str("string"),
Typ::List(typ) => OpenAPISchema {
r#type: Some(SchemaType::Single("array".to_string())),
items: Some(Box::new(Self::from_typ(typ))),
+1
View File
@@ -73,6 +73,7 @@ fn make_rules_for_arg_typ(typ: &Typ) -> Vec<SchemaValidationRule> {
}
Typ::Resource(_) => (),
Typ::DynSelect(_) => (),
Typ::DynMultiselect(_) => (),
Typ::Unknown => (),
}
Binary file not shown.
+1 -1
View File
@@ -254,7 +254,7 @@ const imports = {
const ret = Object.entries(arg0);
return ret;
},
__wbg_eval_5e26645562cd9430: function(arg0, arg1) {
__wbg_eval_d0dfcbbfaeff4b3c: function(arg0, arg1) {
const ret = eval(getStringFromWasm0(arg0, arg1));
return ret;
},
Binary file not shown.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "windmill-utils-internal",
"version": "1.0.0",
"version": "1.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-utils-internal",
"version": "1.0.0",
"version": "1.1.0",
"license": "Apache 2.0",
"devDependencies": {
"@types/node": "^24.2.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-utils-internal",
"version": "1.0.0",
"version": "1.1.0",
"description": "Internal utility functions for Windmill",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -60,6 +60,7 @@ export function argSigToJsonSchemaType(
| null
}
| { dynselect: string }
| { dynmultiselect: string }
| { str: string[] | null }
| { object: { name?: string; props?: { key: string; typ: any }[] } }
| {
@@ -150,7 +151,11 @@ export function argSigToJsonSchemaType(
} else if (typeof t !== 'string' && `dynselect` in t) {
newS.type = 'object'
newS.format = `dynselect-${t.dynselect}`
} else if (typeof t !== 'string' && `list` in t) {
} else if (typeof t !== 'string' && `dynmultiselect` in t) {
newS.type = 'object'
newS.format = `dynmultiselect-${t.dynmultiselect}`
}
else if (typeof t !== 'string' && `list` in t) {
newS.type = 'array'
if (t.list === 'int' || t.list === 'float') {
newS.items = { type: 'number' }
+12 -12
View File
@@ -80,14 +80,14 @@
"windmill-parser-wasm-java": "1.510.1",
"windmill-parser-wasm-nu": "1.510.1",
"windmill-parser-wasm-php": "1.510.1",
"windmill-parser-wasm-py": "1.510.1",
"windmill-parser-wasm-py": "1.538.0",
"windmill-parser-wasm-regex": "1.512.0",
"windmill-parser-wasm-ruby": "1.526.1",
"windmill-parser-wasm-rust": "1.510.1",
"windmill-parser-wasm-ts": "1.514.1",
"windmill-parser-wasm-ts": "1.538.0",
"windmill-parser-wasm-yaml": "1.510.1",
"windmill-sql-datatype-parser-wasm": "1.512.0",
"windmill-utils-internal": "^1.0.0",
"windmill-utils-internal": "^1.1.0",
"xterm": "^5.3.0",
"xterm-readline": "^1.1.2",
"y-monaco": "^0.1.4",
@@ -12900,9 +12900,9 @@
"integrity": "sha512-qM+yeaqPdMuAaPpqND31ZabpeHlPxxtmRWLs11cGHOCHU32FIaZ92/icNUoAxgBhdJjRhR4GXaeZG32nWit2cg=="
},
"node_modules/windmill-parser-wasm-py": {
"version": "1.510.1",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-py/-/windmill-parser-wasm-py-1.510.1.tgz",
"integrity": "sha512-0ES6W1l/j3NjvHgGX0l6wHvioMX3SsTWe4kQIkYP12ISpAIKfSvDpE2sFm6FQ+xoxekzGqYS4luPSmKX4aDYpQ=="
"version": "1.538.0",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-py/-/windmill-parser-wasm-py-1.538.0.tgz",
"integrity": "sha512-s+bdIgT/fA5em3zYUwF8D14uA/dZh7iu0krZYZQqZUO7txN37hwSCVfovbMkIwm4zPbsJ50mU8DRLt7UpAPZIw=="
},
"node_modules/windmill-parser-wasm-regex": {
"version": "1.512.0",
@@ -12920,9 +12920,9 @@
"integrity": "sha512-tqT+w5gvwiX9NZCzT7iafh7dWrWSs46t+LI+N8/1+QOpv95G9/NJ/m0DIfH9Wyfkvtx0ge6igLMFha2wlPKG+w=="
},
"node_modules/windmill-parser-wasm-ts": {
"version": "1.514.1",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-ts/-/windmill-parser-wasm-ts-1.514.1.tgz",
"integrity": "sha512-8GHDOlPqcEd/4qBVUMUP4SdU8CgvvQJw1JZg5YDMJd3dj6mhqyt7Znye9ZudmNqdhGGqsdUXxQDBc8dvcmEFXg=="
"version": "1.538.0",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-ts/-/windmill-parser-wasm-ts-1.538.0.tgz",
"integrity": "sha512-hHhMIVIPhmsHx0lsNCGMoIa7cDBFlVWhhd9j/5yOIq2sxwqg5sl5juQIIJGvuwg5umdsD0ChlSm2/uES78DLYg=="
},
"node_modules/windmill-parser-wasm-yaml": {
"version": "1.510.1",
@@ -12935,9 +12935,9 @@
"integrity": "sha512-uHNL8F72/Tf96xF3hOHnPDjkEyqXw7fNjcPJiUhth9sTQkcwUIoJMOdwm8/cs+j9kKVRJ4tgNYMHEBLylazp6g=="
},
"node_modules/windmill-utils-internal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/windmill-utils-internal/-/windmill-utils-internal-1.0.0.tgz",
"integrity": "sha512-S93XgzdM8WNmDt+MUqiBzyA/3GacZV3Wo9DxxYpAuDHvIP4s8c9zKBhNIZqB008i1opI4tTxB8SwhsYBhTaRuw==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/windmill-utils-internal/-/windmill-utils-internal-1.1.0.tgz",
"integrity": "sha512-Vzm+lNTYR+75hSefDKgjBafNN5OGhhjkdsSElOeS8L1QcgB21DEdLtBmF7mXUGe147deFu36raq/LQzOZ7jqoQ==",
"license": "Apache 2.0"
},
"node_modules/word-wrap": {
+3 -3
View File
@@ -147,14 +147,14 @@
"windmill-parser-wasm-java": "1.510.1",
"windmill-parser-wasm-nu": "1.510.1",
"windmill-parser-wasm-php": "1.510.1",
"windmill-parser-wasm-py": "1.510.1",
"windmill-parser-wasm-py": "1.538.0",
"windmill-parser-wasm-regex": "1.512.0",
"windmill-parser-wasm-ruby": "1.526.1",
"windmill-parser-wasm-rust": "1.510.1",
"windmill-parser-wasm-ts": "1.514.1",
"windmill-parser-wasm-ts": "1.538.0",
"windmill-parser-wasm-yaml": "1.510.1",
"windmill-sql-datatype-parser-wasm": "1.512.0",
"windmill-utils-internal": "^1.0.0",
"windmill-utils-internal": "^1.1.0",
"xterm": "^5.3.0",
"xterm-readline": "^1.1.2",
"y-monaco": "^0.1.4",
+6 -6
View File
@@ -9,7 +9,7 @@
emptySchema,
emptyString,
getSchemaFromProperties,
type DynamicSelect
type DynamicInput as DynamicInputTypes
} from '$lib/utils'
import { DollarSign, Plus, X, Check, Loader2, ExternalLink } from 'lucide-svelte'
import { createEventDispatcher, onDestroy, onMount, tick, untrack } from 'svelte'
@@ -36,7 +36,7 @@
import SchemaFormDnd from './schema/SchemaFormDND.svelte'
import SchemaForm from './SchemaForm.svelte'
import { deepEqual } from 'fast-equals'
import DynSelect from './DynSelect.svelte'
import DynamicInput from './DynamicInput.svelte'
import type { SchemaDiff } from '$lib/components/schema/schemaUtils.svelte'
import type { ComponentCustomCSS } from './apps/types'
import MultiSelect from './select/MultiSelect.svelte'
@@ -97,7 +97,7 @@
orderEditable?: boolean
shouldDispatchChanges?: boolean
noDefaultOnSelectFirst?: boolean
helperScript?: DynamicSelect.HelperScript
helperScript?: DynamicInputTypes.HelperScript
otherArgs?: Record<string, any>
lightHeader?: boolean
diffStatus?: SchemaDiff | undefined
@@ -917,13 +917,13 @@
/>
</div>
</div>
{:else if inputCat == 'dynselect'}
<DynSelect
{:else if inputCat == 'dynamic'}
<DynamicInput
name={label}
{otherArgs}
{helperScript}
bind:value
entrypoint={format?.substring('dynselect_'.length) ?? ''}
format={format ?? ''}
/>
{:else if inputCat == 'resource-object' && resourceTypes == undefined}
<span class="text-2xs text-tertiary">Loading resource types...</span>
@@ -18,21 +18,32 @@
import { usePromise } from '$lib/svelte5Utils.svelte'
import JobLoader, { type Callbacks } from './JobLoader.svelte'
import Select from './select/Select.svelte'
import MultiSelect from './select/MultiSelect.svelte'
import { safeSelectItems } from './select/utils.svelte'
import Tooltip from './Tooltip.svelte'
import { Loader2 } from 'lucide-svelte'
import { type DynamicSelect } from '$lib/utils'
import { type DynamicInput } from '$lib/utils'
import { deepEqual } from 'fast-equals'
import { untrack } from 'svelte'
interface Props {
value?: any
helperScript?: DynamicSelect.HelperScript
entrypoint: string
helperScript?: DynamicInput.HelperScript
format: string
otherArgs?: Record<string, any>
name: string
}
let { value = $bindable(), helperScript, entrypoint, otherArgs: otherArgs }: Props = $props()
let { value = $bindable(), helperScript, format, otherArgs: otherArgs }: Props = $props()
const [inputType, entrypoint] = format.includes('-') ? format.split('-', 2) : [format, '']
const isMultiple = inputType === 'dynmultiselect'
const isSelect = inputType === 'dynselect' || inputType === 'dynmultiselect'
if (isMultiple && value === undefined) {
value = []
}
let resultJobLoader: JobLoader | undefined = $state()
let _items = usePromise(getItemsFromOptions, { clearValueOnRefresh: false })
@@ -48,7 +59,7 @@
if (!result || !Array.isArray(result)) {
if (result?.error?.message && result?.error?.name) {
reject(
'Error in DynSelect function execution: ' +
`Error in ${inputType} function execution: ` +
result?.error?.name +
' - ' +
result?.error?.message
@@ -95,9 +106,17 @@
let neverLoaded = $state(true)
$effect(() => {
if (_items.value && value) {
if (!_items.value.find((x) => x.value == value)) {
value = undefined
if (_items.value && value !== undefined && isSelect) {
if (isMultiple && Array.isArray(value)) {
const availableValues = new Set(_items.value.map((x) => x.value))
const filteredValue = value.filter((v) => availableValues.has(v))
if (filteredValue.length !== value.length) {
value = filteredValue
}
} else if (!isMultiple && value !== undefined) {
if (!_items.value.find((x) => x.value == value)) {
value = undefined
}
}
}
})
@@ -128,15 +147,30 @@
<JobLoader onlyResult bind:this={resultJobLoader} />
<div class="w-full flex-col flex">
<Select
bind:value
bind:open
{items}
bind:filterText
loading={!open && _items.status === 'loading'}
clearable
noItemsMsg={_items.status === 'loading' ? 'Loading...' : 'No items found'}
/>
{#if inputType === 'dynmultiselect'}
<MultiSelect
bind:value
items={safeSelectItems(items || [])}
placeholder="Select items"
noItemsMsg={_items.status === 'loading' ? 'Loading...' : 'No items found'}
disabled={_items.status === 'loading'}
/>
{:else if inputType === 'dynselect'}
<Select
bind:value
bind:open
{items}
bind:filterText
loading={!open && _items.status === 'loading'}
clearable
noItemsMsg={_items.status === 'loading' ? 'Loading...' : 'No items found'}
/>
{:else}
<!-- Future dynamic input types can be added here -->
<div class="text-red-400 text-sm">
Unsupported dynamic input type: {inputType}
</div>
{/if}
{#if _items.error}
<div class="text-red-400 text-2xs">
error: <Tooltip>{_items.error}</Tooltip>
@@ -146,7 +180,7 @@
{:else}
<div class="flex flex-col gap-1 w-full">
<div class="text-xs text-tertiary"
>Dynamic Select is not available in this mode, write value directly</div
>Dynamic input ({inputType}) is not available in this mode, write value directly</div
>
{#await import('$lib/components/JsonEditor.svelte')}
<Loader2 class="animate-spin" />
@@ -21,7 +21,7 @@
import { sendUserToast } from '$lib/toast'
import Toggle from './Toggle.svelte'
import {
DynamicSelect,
DynamicInput,
emptyString,
generateRandomString,
readFieldsRecursively
@@ -71,9 +71,9 @@
customUi?: EditableSchemaFormUi | undefined
pannelExtraButtonWidth?: number
class?: string
dynSelectCode?: string | undefined
dynSelectLang?: ScriptLang | undefined
showDynSelectOpt?: boolean
dynCode?: string | undefined
dynLang?: ScriptLang | undefined
showDynOpt?: boolean
addPropertyInEditorTab?: boolean
openEditTab?: import('svelte').Snippet
addProperty?: import('svelte').Snippet
@@ -108,9 +108,9 @@
customUi = undefined,
pannelExtraButtonWidth = 0,
class: clazz = '',
dynSelectCode = $bindable(),
dynSelectLang = $bindable(),
showDynSelectOpt = false,
dynCode = $bindable(),
dynLang = $bindable(),
showDynOpt = false,
addPropertyInEditorTab = false,
openEditTab,
addProperty,
@@ -124,11 +124,11 @@
if (args == undefined) {
args = {}
}
if (dynSelectLang === undefined) {
dynSelectLang = schema?.['x-windmill-dyn-select-lang'] || 'bun'
if (dynLang === undefined) {
dynLang = schema?.['x-windmill-dyn-select-lang'] || 'bun'
}
if (dynSelectCode === undefined) {
dynSelectCode = schema?.['x-windmill-dyn-select-code'] || ''
if (dynCode === undefined) {
dynCode = schema?.['x-windmill-dyn-select-code'] || ''
}
})
@@ -140,10 +140,10 @@
})
$effect(() => {
if (schema && dynSelectCode !== undefined && dynSelectLang !== undefined) {
if (dynSelectCode && dynSelectCode.trim()) {
schema['x-windmill-dyn-select-code'] = dynSelectCode.trim()
schema['x-windmill-dyn-select-lang'] = dynSelectLang
if (schema && dynCode !== undefined && dynLang !== undefined) {
if (dynCode && dynCode.trim()) {
schema['x-windmill-dyn-select-code'] = dynCode.trim()
schema['x-windmill-dyn-select-lang'] = dynLang
} else {
delete schema['x-windmill-dyn-select-code']
delete schema['x-windmill-dyn-select-lang']
@@ -223,6 +223,7 @@
if (property.type !== 'object') return property.type
if (property.format === 'resource-s3_object') return 'S3'
if (property.format?.startsWith('dynselect-')) return 'dynselect'
if (property.format?.startsWith('dynmultiselect-')) return 'dynmultiselect'
if (property.oneOf && property.oneOf.length >= 2) return 'oneOf'
if (property.format?.startsWith('resource-')) return 'resource'
return 'object'
@@ -339,19 +340,21 @@
!!editTab ? openEditTabFn() : closeEditTab()
})
let dynSelectFunctions = $derived(
let dynamicFunctions = $derived(
Object.entries(schema?.properties ?? {})
.filter(([_, property]) => {
const props = property as any
return (
props.type === 'object' &&
(props.format?.startsWith('dynselect-') || props.format?.startsWith('dynselect_'))
)
return props.type === 'object' && DynamicInput.isDynInputFormat(props.format)
})
.map(([fieldName, _]) => fieldName.replace(/\s+/g, '_'))
)
let typeOptions = [
const DYNAMIC_OPTIONS = [
['DynSelect', 'dynselect'],
['DynMultiselect', 'dynmultiselect']
]
let typeOptions = $state([
['String', 'string'],
['Number', 'number'],
['Integer', 'integer'],
@@ -361,22 +364,17 @@
['Array', 'array'],
['Boolean', 'boolean'],
['S3', 'S3']
]
if (showDynSelectOpt) {
typeOptions.push(['DynSelect', 'dynselect'])
])
function initDynFn(lang: ScriptLang) {
const generateFn = DynamicInput.getGenerateTemplateFn(lang)
return dynamicFunctions.map((functionName) => generateFn(functionName)).join('')
}
function initDynSelectFn(lang: ScriptLang) {
const generateFn = DynamicSelect.getGenerateTemplateFn(lang)
return Object.entries(schema?.properties ?? {})
.map(([functionName]) => generateFn(functionName))
.join('')
}
function updateDynSelectCode(functionName: string, lang: ScriptLang = 'bun') {
const generateFn = DynamicSelect.getGenerateTemplateFn(lang)
function updateDynCode(functionName: string, lang: ScriptLang = 'bun') {
const generateFn = DynamicInput.getGenerateTemplateFn(lang)
const code = generateFn(functionName)
dynSelectCode = dynSelectCode ? dynSelectCode.concat(code) : code
dynCode = dynCode ? dynCode.concat(code) : code
}
let dndType = $state(generateRandomString())
@@ -445,8 +443,8 @@
}}
helperScript={{
type: 'inline',
code: dynSelectCode!,
lang: dynSelectLang!
code: dynCode!,
lang: dynLang!
}}
prettifyHeader={isAppInput}
disabled={!!previewSchema}
@@ -460,55 +458,53 @@
{@render runButton?.()}
{#if dynSelectFunctions.length > 0}
<div class="grow">
<Section
label="Dynamic select functions"
collapsable={true}
collapsed={false}
class="text-sm"
>
<div class="flex flex-col gap-2 h-full">
{#if dynSelectFunctions.length > 0}
<div class="bg-blue-50 dark:bg-blue-900/20 p-3 rounded-md">
<div class="text-sm font-medium text-blue-800 dark:text-blue-200 mb-2">
Expected Functions for Dynamic Select Fields:
</div>
<ul class="text-xs text-blue-700 dark:text-blue-300 space-y-1">
{#each dynSelectFunctions as functionName}
<li class="font-mono bg-blue-100 dark:bg-blue-800/30 px-2 py-1 rounded">
{functionName}()
</li>
{/each}
</ul>
{#if dynamicFunctions.length > 0}
<Section
label="Dynamic input functions"
collapsable={true}
collapsed={false}
class="text-sm"
>
<div class="flex flex-col gap-2 h-full">
{#if dynamicFunctions.length > 0}
<div class="bg-blue-50 dark:bg-blue-900/20 p-3 rounded-md">
<div class="text-sm font-medium text-blue-800 dark:text-blue-200 mb-2">
Expected Functions for Dynamic Input Fields:
</div>
{/if}
<ToggleButtonGroup
bind:selected={dynSelectLang}
on:selected={({ detail }) => {
dynSelectCode = initDynSelectFn(detail)
}}
>
{#snippet children({ item })}
<ToggleButton value="bun" label="Typescript (Bun)" {item} />
<ToggleButton value="python3" label="Python" {item} />
{/snippet}
</ToggleButtonGroup>
{#key dynSelectLang}
<div class="border w-full h-full">
<Editor
bind:this={dynamicSelectEditor}
class="flex flex-1 grow h-80 w-full"
scriptLang={dynSelectLang}
useWebsockets={false}
automaticLayout
bind:code={dynSelectCode}
/>
</div>
{/key}
</div>
</Section>
</div>
<ul class="text-xs text-blue-700 dark:text-blue-300 space-y-1">
{#each dynamicFunctions as functionName}
<li class="font-mono bg-blue-100 dark:bg-blue-800/30 px-2 py-1 rounded">
{functionName}()
</li>
{/each}
</ul>
</div>
{/if}
<ToggleButtonGroup
bind:selected={dynLang}
on:selected={({ detail }) => {
dynCode = initDynFn(detail)
}}
>
{#snippet children({ item })}
<ToggleButton value="bun" label="Typescript (Bun)" {item} />
<ToggleButton value="python3" label="Python" {item} />
{/snippet}
</ToggleButtonGroup>
{#key dynLang}
<div class="border w-full h-full">
<Editor
bind:this={dynamicSelectEditor}
class="flex flex-1 grow h-80 w-full"
scriptLang={dynLang}
useWebsockets={false}
automaticLayout
bind:code={dynCode}
/>
</div>
{/key}
</div>
</Section>
{/if}
</div>
</div>
@@ -687,6 +683,7 @@
const isS3 = v == 'S3'
const isOneOf = v == 'oneOf'
const isDynSelect = v == 'dynselect'
const isDynMultiselect = v == 'dynmultiselect'
const emptyProperty = {
contentEncoding: undefined,
enum_: undefined,
@@ -716,15 +713,17 @@
type: 'object',
format: 'resource-'
}
} else if (isDynSelect) {
} else if (isDynSelect || isDynMultiselect) {
const functionName = argName.replace(/\s+/g, '_')
schema.properties[argName] = {
...emptyProperty,
type: 'object',
format: 'dynselect-' + functionName
format:
(isDynSelect ? 'dynselect-' : 'dynmultiselect-') +
functionName
}
updateDynSelectCode(argName, dynSelectLang)
dynamicSelectEditor?.setCode(dynSelectCode || '')
updateDynCode(argName, dynLang)
dynamicSelectEditor?.setCode(dynCode || '')
} else if (isOneOf) {
schema.properties[argName] = {
...emptyProperty,
@@ -775,6 +774,11 @@
{#each typeOptions as x}
<ToggleButton value={x[1]} label={x[0]} {item} />
{/each}
{#if showDynOpt}
{#each DYNAMIC_OPTIONS as x}
<ToggleButton value={x[1]} label={x[0]} {item} />
{/each}
{/if}
{/snippet}
</ToggleButtonGroup>
</Label>
@@ -5,7 +5,7 @@
import type { Schema } from '$lib/common'
import { VariableService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { allTrue, computeShow, type DynamicSelect } from '$lib/utils'
import { allTrue, computeShow, type DynamicInput } from '$lib/utils'
import { Button } from './common'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
@@ -44,7 +44,7 @@
onlyMaskPassword?: boolean
dndConfig?: DndOptions | undefined
items?: { id: string; value: string }[] | undefined
helperScript?: DynamicSelect.HelperScript
helperScript?: DynamicInput.HelperScript
lightHeader?: boolean
diff?: Record<string, SchemaDiff>
nestedParent?: { label: string; nestedParent: any | undefined } | undefined
@@ -77,8 +77,8 @@
let editableSchemaForm: EditableSchemaForm | undefined = $state(undefined)
let savedPreviewArgs: Record<string, any> | undefined = $state(undefined)
let isValid = $state(true)
let dynSelectCode: string | undefined = $state(undefined)
let dynSelectLang: ScriptLang | undefined = $state(undefined)
let dynCode: string | undefined = $state(undefined)
let dynLang: ScriptLang | undefined = $state(undefined)
function updateEditPanelSize(size: number | undefined) {
if (!$flowInputEditorState) return
@@ -375,7 +375,7 @@
on:delete={(e) => {
addPropertyV2?.handleDeleteArgument([e.detail])
}}
showDynSelectOpt
showDynOpt
displayWebhookWarning
editTab={$flowInputEditorState?.selectedTab}
{previewSchema}
@@ -414,8 +414,8 @@
}
}}
bind:isValid
bind:dynSelectCode
bind:dynSelectLang
bind:dynCode
bind:dynLang
>
{#snippet openEditTab()}
<div class={twMerge('flex flex-row divide-x', ButtonType.ColorVariants.blue.divider)}>
@@ -16,6 +16,7 @@
import { Pen, Plus, Trash2 } from 'lucide-svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import ResourcePicker from '../ResourcePicker.svelte'
import { DynamicInput } from '$lib/utils'
interface Props {
format?: string | undefined
@@ -131,7 +132,7 @@
? 'resource'
: 'custom-object'
)
let isDynSelect = $derived(format?.startsWith('dynselect-') ?? false)
let isDyn = $derived(DynamicInput.isDynInputFormat(format))
let customObjectSelected: 'editor' | 'json-schema-resource' = $state(
format?.startsWith('jsonschema-') ? 'json-schema-resource' : 'editor'
@@ -274,9 +275,9 @@
{/if}
</Label>
<div class="py-2"></div>
{:else if type === 'object' && format?.startsWith('resource-') && format !== 'resource-s3_object' && !isDynSelect}
{:else if type === 'object' && format?.startsWith('resource-') && format !== 'resource-s3_object' && !isDyn}
<ResourceNarrowing bind:format />
{:else if type === 'object' && !format?.startsWith('resource-') && !isDynSelect}
{:else if type === 'object' && !format?.startsWith('resource-') && !isDyn}
<div class="py-2">
<Label label="Object properties">
<ToggleButtonGroup
@@ -349,7 +350,7 @@
</div>
{/if}
{#if !(type === 'object' && oneOf && oneOf.length >= 2) && !(type == 'object' && initialObjectSelected == 'custom-object') && !isDynSelect}
{#if !(type === 'object' && oneOf && oneOf.length >= 2) && !(type == 'object' && initialObjectSelected == 'custom-object') && !isDyn}
<Label label="Default">
<ArgInput
noDefaultOnSelectFirst
@@ -6,7 +6,7 @@
import type { Schema } from '$lib/common'
import { deepEqual } from 'fast-equals'
import type { SchemaDiff } from '$lib/components/schema/schemaUtils.svelte'
import { generateRandomString, type DynamicSelect } from '$lib/utils'
import { generateRandomString, type DynamicInput } from '$lib/utils'
interface Props {
schema: Schema
args?: Record<string, any>
@@ -22,7 +22,7 @@
nestedClasses?: string
isValid?: boolean
noVariablePicker?: boolean
helperScript?: DynamicSelect.HelperScript
helperScript?: DynamicInput.HelperScript
className?: string
dndType?: string
}
+11 -7
View File
@@ -591,10 +591,12 @@ export type InputCat =
| 'yaml'
| 'currency'
| 'oneOf'
| 'dynselect'
| 'dynamic'
| 'json-schema'
export namespace DynamicSelect {
export namespace DynamicInput {
const DYN_FORMAT_PREFIX = ['dynmultiselect-', 'dynselect-']
export type HelperScript =
| { type: 'inline'; path?: string; lang: Script['language']; code: string }
| { type: 'hash'; hash: string }
@@ -633,6 +635,11 @@ export function ${functionName}(filterText: string) {
export const getGenerateTemplateFn = (lang: ScriptLang) => {
return lang === 'bun' ? generateJsFnTemplate : generatePythonFnTemplate
}
export const isDynInputFormat = (format?: string) => {
if (!format) return false
return DYN_FORMAT_PREFIX.some((format_prefix) => format.startsWith(format_prefix))
}
}
export function setInputCat(
@@ -650,11 +657,8 @@ export function setInputCat(
return 'list'
} else if (type == 'object' && format?.startsWith('resource')) {
return 'resource-object'
} else if (
type == 'object' &&
(format?.startsWith('dynselect-') || format?.startsWith('dynselect_'))
) {
return 'dynselect'
} else if (type == 'object' && DynamicInput.isDynInputFormat(format)) {
return 'dynamic'
} else if (!type || type == 'object' || type == 'array') {
return 'object'
} else if (type == 'string' && enum_) {