mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 00:02:23 +00:00
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:
@@ -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))),
|
||||
|
||||
@@ -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.
@@ -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
@@ -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,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' }
|
||||
|
||||
Generated
+12
-12
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
+52
-18
@@ -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
|
||||
}
|
||||
|
||||
@@ -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_) {
|
||||
|
||||
Reference in New Issue
Block a user