@d34dman/flowdrop 0.0.47 → 0.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/App.svelte +4 -0
- package/dist/components/App.svelte.d.ts +2 -0
- package/dist/components/ConfigForm.svelte +20 -4
- package/dist/components/ConfigForm.svelte.d.ts +3 -1
- package/dist/components/SchemaForm.svelte +4 -2
- package/dist/components/form/FormAutocomplete.svelte +7 -4
- package/dist/components/form/FormField.svelte +29 -2
- package/dist/components/form/FormField.svelte.d.ts +11 -0
- package/dist/components/form/FormFieldLight.svelte +0 -1
- package/dist/components/form/FormTemplateEditor.svelte +249 -41
- package/dist/components/form/FormTemplateEditor.svelte.d.ts +11 -7
- package/dist/components/form/index.d.ts +1 -1
- package/dist/components/form/index.js +1 -1
- package/dist/components/form/templateAutocomplete.d.ts +0 -9
- package/dist/components/form/templateAutocomplete.js +0 -56
- package/dist/components/form/types.d.ts +0 -6
- package/dist/components/nodes/TerminalNode.svelte +27 -15
- package/dist/services/apiVariableService.d.ts +116 -0
- package/dist/services/apiVariableService.js +338 -0
- package/dist/services/variableService.d.ts +50 -9
- package/dist/services/variableService.js +112 -17
- package/dist/svelte-app.d.ts +2 -0
- package/dist/svelte-app.js +2 -1
- package/dist/types/index.d.ts +137 -0
- package/dist/utils/handlePositioning.d.ts +31 -0
- package/dist/utils/handlePositioning.js +35 -0
- package/package.json +8 -4
|
@@ -71,6 +71,8 @@
|
|
|
71
71
|
variant?: 'primary' | 'secondary' | 'outline';
|
|
72
72
|
onclick?: (event: Event) => void;
|
|
73
73
|
}>;
|
|
74
|
+
/** Show settings gear icon in navbar */
|
|
75
|
+
showSettings?: boolean;
|
|
74
76
|
/** API base URL */
|
|
75
77
|
apiBaseUrl?: string;
|
|
76
78
|
/** Endpoint configuration */
|
|
@@ -96,6 +98,7 @@
|
|
|
96
98
|
pipelineId,
|
|
97
99
|
navbarTitle,
|
|
98
100
|
navbarActions = [],
|
|
101
|
+
showSettings = true,
|
|
99
102
|
apiBaseUrl,
|
|
100
103
|
endpointConfig: propEndpointConfig,
|
|
101
104
|
authProvider,
|
|
@@ -705,6 +708,7 @@
|
|
|
705
708
|
}
|
|
706
709
|
]}
|
|
707
710
|
showStatus={true}
|
|
711
|
+
{showSettings}
|
|
708
712
|
/>
|
|
709
713
|
{/snippet}
|
|
710
714
|
|
|
@@ -36,6 +36,8 @@ interface Props {
|
|
|
36
36
|
variant?: 'primary' | 'secondary' | 'outline';
|
|
37
37
|
onclick?: (event: Event) => void;
|
|
38
38
|
}>;
|
|
39
|
+
/** Show settings gear icon in navbar */
|
|
40
|
+
showSettings?: boolean;
|
|
39
41
|
/** API base URL */
|
|
40
42
|
apiBaseUrl?: string;
|
|
41
43
|
/** Endpoint configuration */
|
|
@@ -18,13 +18,15 @@
|
|
|
18
18
|
-->
|
|
19
19
|
|
|
20
20
|
<script lang="ts">
|
|
21
|
+
import { setContext } from 'svelte';
|
|
21
22
|
import Icon from '@iconify/svelte';
|
|
22
23
|
import type {
|
|
23
24
|
ConfigSchema,
|
|
24
25
|
WorkflowNode,
|
|
25
26
|
WorkflowEdge,
|
|
26
27
|
NodeUIExtensions,
|
|
27
|
-
ConfigEditOptions
|
|
28
|
+
ConfigEditOptions,
|
|
29
|
+
AuthProvider
|
|
28
30
|
} from '../types/index.js';
|
|
29
31
|
import { FormField, FormFieldWrapper, FormToggle } from './form/index.js';
|
|
30
32
|
import type { FieldSchema } from './form/index.js';
|
|
@@ -61,6 +63,8 @@
|
|
|
61
63
|
* When provided along with workflowNodes, enables autocomplete for template fields.
|
|
62
64
|
*/
|
|
63
65
|
workflowEdges?: WorkflowEdge[];
|
|
66
|
+
/** Auth provider for API requests (used for template variable API mode) */
|
|
67
|
+
authProvider?: AuthProvider;
|
|
64
68
|
/** Callback when any field value changes (fired on blur for immediate sync) */
|
|
65
69
|
onChange?: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
|
|
66
70
|
/** Callback when form is saved (includes both config and extensions if enabled) */
|
|
@@ -78,11 +82,17 @@
|
|
|
78
82
|
saveWorkflowWhenSavingConfig = false,
|
|
79
83
|
workflowNodes = [],
|
|
80
84
|
workflowEdges = [],
|
|
85
|
+
authProvider,
|
|
81
86
|
onChange,
|
|
82
87
|
onSave,
|
|
83
88
|
onCancel
|
|
84
89
|
}: Props = $props();
|
|
85
90
|
|
|
91
|
+
// Set context for child components (e.g., FormAutocomplete)
|
|
92
|
+
// Use getter functions to ensure child components always get the current prop value,
|
|
93
|
+
// even if the prop changes after initial mount
|
|
94
|
+
setContext<() => AuthProvider | undefined>('flowdrop:getAuthProvider', () => authProvider);
|
|
95
|
+
|
|
86
96
|
/**
|
|
87
97
|
* State for dynamic schema loading
|
|
88
98
|
*/
|
|
@@ -132,13 +142,14 @@
|
|
|
132
142
|
});
|
|
133
143
|
|
|
134
144
|
/**
|
|
135
|
-
* Check if the node
|
|
145
|
+
* Check if the node needs dynamic schema loading
|
|
146
|
+
* Loads when: no static schema OR preferDynamicSchema is true
|
|
136
147
|
*/
|
|
137
148
|
const needsDynamicSchemaLoad = $derived.by(() => {
|
|
138
149
|
if (!node) return false;
|
|
139
150
|
const staticSchema = schema ?? node.data.metadata?.configSchema;
|
|
140
|
-
// Need to load if: no static schema AND dynamic schema is configured
|
|
141
|
-
return !staticSchema && useDynamicSchema && !fetchedDynamicSchema && !dynamicSchemaLoading;
|
|
151
|
+
// Need to load if: (no static schema OR preferDynamicSchema is true) AND dynamic schema is configured
|
|
152
|
+
return (!staticSchema || configEditOptions?.preferDynamicSchema === true) && useDynamicSchema && !fetchedDynamicSchema && !dynamicSchemaLoading;
|
|
142
153
|
});
|
|
143
154
|
|
|
144
155
|
/**
|
|
@@ -537,6 +548,11 @@
|
|
|
537
548
|
value={configValues[key]}
|
|
538
549
|
{required}
|
|
539
550
|
animationIndex={index}
|
|
551
|
+
{node}
|
|
552
|
+
nodes={workflowNodes}
|
|
553
|
+
edges={workflowEdges}
|
|
554
|
+
{workflowId}
|
|
555
|
+
{authProvider}
|
|
540
556
|
onChange={(val) => handleFieldChange(key, val)}
|
|
541
557
|
/>
|
|
542
558
|
{/each}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConfigSchema, WorkflowNode, WorkflowEdge, NodeUIExtensions } from '../types/index.js';
|
|
1
|
+
import type { ConfigSchema, WorkflowNode, WorkflowEdge, NodeUIExtensions, AuthProvider } from '../types/index.js';
|
|
2
2
|
interface Props {
|
|
3
3
|
/** Optional workflow node (if provided, schema and values are derived from it) */
|
|
4
4
|
node?: WorkflowNode;
|
|
@@ -22,6 +22,8 @@ interface Props {
|
|
|
22
22
|
* When provided along with workflowNodes, enables autocomplete for template fields.
|
|
23
23
|
*/
|
|
24
24
|
workflowEdges?: WorkflowEdge[];
|
|
25
|
+
/** Auth provider for API requests (used for template variable API mode) */
|
|
26
|
+
authProvider?: AuthProvider;
|
|
25
27
|
/** Callback when any field value changes (fired on blur for immediate sync) */
|
|
26
28
|
onChange?: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
|
|
27
29
|
/** Callback when form is saved (includes both config and extensions if enabled) */
|
|
@@ -157,8 +157,10 @@
|
|
|
157
157
|
}: Props = $props();
|
|
158
158
|
|
|
159
159
|
// Set context for child components (e.g., FormAutocomplete)
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
// Use getter functions to ensure child components always get the current prop value,
|
|
161
|
+
// even if the prop changes after initial mount
|
|
162
|
+
setContext<() => AuthProvider | undefined>('flowdrop:getAuthProvider', () => authProvider);
|
|
163
|
+
setContext<() => string>('flowdrop:getBaseUrl', () => baseUrl);
|
|
162
164
|
|
|
163
165
|
/**
|
|
164
166
|
* Internal reactive state for form values
|
|
@@ -59,9 +59,10 @@
|
|
|
59
59
|
onChange
|
|
60
60
|
}: Props = $props();
|
|
61
61
|
|
|
62
|
-
// Get AuthProvider from context
|
|
63
|
-
|
|
64
|
-
const
|
|
62
|
+
// Get AuthProvider and baseUrl from context via getter functions
|
|
63
|
+
// This pattern ensures we always get the current value, even if props change after mount
|
|
64
|
+
const getAuthProvider = getContext<(() => AuthProvider | undefined) | undefined>('flowdrop:getAuthProvider');
|
|
65
|
+
const getBaseUrl = getContext<(() => string) | undefined>('flowdrop:getBaseUrl');
|
|
65
66
|
|
|
66
67
|
// Configuration with defaults
|
|
67
68
|
const queryParam = $derived(autocomplete.queryParam ?? 'q');
|
|
@@ -142,6 +143,7 @@
|
|
|
142
143
|
* @returns Full URL with query parameter
|
|
143
144
|
*/
|
|
144
145
|
function buildUrl(query: string): string {
|
|
146
|
+
const baseUrl = getBaseUrl?.() ?? '';
|
|
145
147
|
const url = autocomplete.url.startsWith('http')
|
|
146
148
|
? autocomplete.url
|
|
147
149
|
: `${baseUrl}${autocomplete.url}`;
|
|
@@ -194,7 +196,8 @@
|
|
|
194
196
|
'Content-Type': 'application/json'
|
|
195
197
|
};
|
|
196
198
|
|
|
197
|
-
// Add auth headers if provider is available
|
|
199
|
+
// Add auth headers if provider is available (call getter to get current value)
|
|
200
|
+
const authProvider = getAuthProvider?.();
|
|
198
201
|
if (authProvider) {
|
|
199
202
|
const authHeaders = await authProvider.getAuthHeaders();
|
|
200
203
|
Object.assign(headers, authHeaders);
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
import FormAutocomplete from './FormAutocomplete.svelte';
|
|
44
44
|
import type { FieldSchema } from './types.js';
|
|
45
45
|
import { getSchemaOptions } from './types.js';
|
|
46
|
+
import type { WorkflowNode, WorkflowEdge, AuthProvider } from '../../types/index.js';
|
|
46
47
|
|
|
47
48
|
interface Props {
|
|
48
49
|
/** Unique key/id for the field */
|
|
@@ -57,9 +58,31 @@
|
|
|
57
58
|
animationIndex?: number;
|
|
58
59
|
/** Callback when the field value changes */
|
|
59
60
|
onChange: (value: unknown) => void;
|
|
61
|
+
/** Current workflow node (optional, used for template variable API mode) */
|
|
62
|
+
node?: WorkflowNode;
|
|
63
|
+
/** All workflow nodes (optional, used for port-derived variables) */
|
|
64
|
+
nodes?: WorkflowNode[];
|
|
65
|
+
/** All workflow edges (optional, used for port-derived variables) */
|
|
66
|
+
edges?: WorkflowEdge[];
|
|
67
|
+
/** Workflow ID (optional, used for template variable API mode) */
|
|
68
|
+
workflowId?: string;
|
|
69
|
+
/** Auth provider (optional, used for API requests) */
|
|
70
|
+
authProvider?: AuthProvider;
|
|
60
71
|
}
|
|
61
72
|
|
|
62
|
-
let {
|
|
73
|
+
let {
|
|
74
|
+
fieldKey,
|
|
75
|
+
schema,
|
|
76
|
+
value,
|
|
77
|
+
required = false,
|
|
78
|
+
animationIndex = 0,
|
|
79
|
+
onChange,
|
|
80
|
+
node,
|
|
81
|
+
nodes,
|
|
82
|
+
edges,
|
|
83
|
+
workflowId,
|
|
84
|
+
authProvider
|
|
85
|
+
}: Props = $props();
|
|
63
86
|
|
|
64
87
|
/**
|
|
65
88
|
* When schema.readOnly is true, disable all inputs (no editing).
|
|
@@ -352,11 +375,15 @@
|
|
|
352
375
|
height={(schema.height as string | undefined) ?? '250px'}
|
|
353
376
|
darkTheme={(schema.darkTheme as boolean | undefined) ?? false}
|
|
354
377
|
variables={schema.variables}
|
|
355
|
-
variableHints={(schema.variableHints as string[] | undefined) ?? []}
|
|
356
378
|
placeholderExample={(schema.placeholderExample as string | undefined) ??
|
|
357
379
|
'Hello {{ name }}, your order #{{ order_id }} is ready!'}
|
|
358
380
|
ariaDescribedBy={descriptionId}
|
|
359
381
|
disabled={isReadOnly}
|
|
382
|
+
{node}
|
|
383
|
+
{nodes}
|
|
384
|
+
{edges}
|
|
385
|
+
{workflowId}
|
|
386
|
+
{authProvider}
|
|
360
387
|
onChange={(val) => onChange(val)}
|
|
361
388
|
/>
|
|
362
389
|
{:else if fieldType === 'autocomplete' && schema.autocomplete}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FieldSchema } from './types.js';
|
|
2
|
+
import type { WorkflowNode, WorkflowEdge, AuthProvider } from '../../types/index.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
/** Unique key/id for the field */
|
|
4
5
|
fieldKey: string;
|
|
@@ -12,6 +13,16 @@ interface Props {
|
|
|
12
13
|
animationIndex?: number;
|
|
13
14
|
/** Callback when the field value changes */
|
|
14
15
|
onChange: (value: unknown) => void;
|
|
16
|
+
/** Current workflow node (optional, used for template variable API mode) */
|
|
17
|
+
node?: WorkflowNode;
|
|
18
|
+
/** All workflow nodes (optional, used for port-derived variables) */
|
|
19
|
+
nodes?: WorkflowNode[];
|
|
20
|
+
/** All workflow edges (optional, used for port-derived variables) */
|
|
21
|
+
edges?: WorkflowEdge[];
|
|
22
|
+
/** Workflow ID (optional, used for template variable API mode) */
|
|
23
|
+
workflowId?: string;
|
|
24
|
+
/** Auth provider (optional, used for API requests) */
|
|
25
|
+
authProvider?: AuthProvider;
|
|
15
26
|
}
|
|
16
27
|
declare const FormField: import("svelte").Component<Props, {}, "">;
|
|
17
28
|
type FormField = ReturnType<typeof FormField>;
|
|
@@ -242,7 +242,6 @@
|
|
|
242
242
|
showStatusBar={schema.showStatusBar as boolean | undefined}
|
|
243
243
|
spellChecker={schema.spellChecker as boolean | undefined}
|
|
244
244
|
variables={schema.variables}
|
|
245
|
-
variableHints={schema.variableHints as string[] | undefined}
|
|
246
245
|
placeholderExample={schema.placeholderExample as string | undefined}
|
|
247
246
|
onChange={(val: unknown) => onChange(val)}
|
|
248
247
|
/>
|
|
@@ -33,15 +33,19 @@
|
|
|
33
33
|
type ViewUpdate,
|
|
34
34
|
MatchDecorator
|
|
35
35
|
} from '@codemirror/view';
|
|
36
|
-
import { EditorState } from '@codemirror/state';
|
|
36
|
+
import { EditorState, Compartment } from '@codemirror/state';
|
|
37
37
|
import { history, historyKeymap, defaultKeymap, indentWithTab } from '@codemirror/commands';
|
|
38
38
|
import { syntaxHighlighting, defaultHighlightStyle, indentOnInput } from '@codemirror/language';
|
|
39
39
|
import { oneDark } from '@codemirror/theme-one-dark';
|
|
40
|
-
import type {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
import type {
|
|
41
|
+
VariableSchema,
|
|
42
|
+
TemplateVariablesConfig,
|
|
43
|
+
WorkflowNode,
|
|
44
|
+
WorkflowEdge,
|
|
45
|
+
AuthProvider
|
|
46
|
+
} from '../../types/index.js';
|
|
47
|
+
import { createTemplateAutocomplete } from './templateAutocomplete.js';
|
|
48
|
+
import { getVariableSchema } from '../../services/variableService.js';
|
|
45
49
|
|
|
46
50
|
interface Props {
|
|
47
51
|
/** Field identifier */
|
|
@@ -67,12 +71,6 @@
|
|
|
67
71
|
* @deprecated Use `variables.schema` instead
|
|
68
72
|
*/
|
|
69
73
|
variableSchema?: VariableSchema;
|
|
70
|
-
/**
|
|
71
|
-
* Simple variable names for basic hints (backward compatible).
|
|
72
|
-
* Used when variableSchema is not provided.
|
|
73
|
-
* @deprecated Use `variables.schema` instead
|
|
74
|
-
*/
|
|
75
|
-
variableHints?: string[];
|
|
76
74
|
/** Placeholder variable example for the hint */
|
|
77
75
|
placeholderExample?: string;
|
|
78
76
|
/** Whether the field is disabled (read-only) */
|
|
@@ -81,6 +79,16 @@
|
|
|
81
79
|
ariaDescribedBy?: string;
|
|
82
80
|
/** Callback when value changes */
|
|
83
81
|
onChange: (value: string) => void;
|
|
82
|
+
/** Current workflow node (required for API mode) */
|
|
83
|
+
node?: WorkflowNode;
|
|
84
|
+
/** All workflow nodes (required for port-derived variables) */
|
|
85
|
+
nodes?: WorkflowNode[];
|
|
86
|
+
/** All workflow edges (required for port-derived variables) */
|
|
87
|
+
edges?: WorkflowEdge[];
|
|
88
|
+
/** Workflow ID (required for API mode) */
|
|
89
|
+
workflowId?: string;
|
|
90
|
+
/** Auth provider for API requests */
|
|
91
|
+
authProvider?: AuthProvider;
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
let {
|
|
@@ -92,26 +100,83 @@
|
|
|
92
100
|
height = '250px',
|
|
93
101
|
variables,
|
|
94
102
|
variableSchema,
|
|
95
|
-
variableHints = [],
|
|
96
103
|
placeholderExample = 'Hello {{ name }}, your order #{{ order_id }} is ready!',
|
|
97
104
|
disabled = false,
|
|
98
105
|
ariaDescribedBy,
|
|
99
|
-
onChange
|
|
106
|
+
onChange,
|
|
107
|
+
node,
|
|
108
|
+
nodes = [],
|
|
109
|
+
edges = [],
|
|
110
|
+
workflowId,
|
|
111
|
+
authProvider
|
|
100
112
|
}: Props = $props();
|
|
101
113
|
|
|
114
|
+
/** Loading state for API variable fetching */
|
|
115
|
+
let isLoadingVariables = $state(false);
|
|
116
|
+
|
|
117
|
+
/** Error state for API variable fetching */
|
|
118
|
+
let variableLoadError = $state<string | null>(null);
|
|
119
|
+
|
|
120
|
+
/** The effective variable schema (loaded synchronously or asynchronously) */
|
|
121
|
+
let effectiveVariableSchema = $state<VariableSchema | undefined>(undefined);
|
|
122
|
+
|
|
102
123
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
124
|
+
* Load variable schema on mount or when configuration changes.
|
|
125
|
+
* Handles both synchronous (schema-based) and asynchronous (API-based) loading.
|
|
105
126
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
127
|
+
async function loadVariableSchema() {
|
|
128
|
+
// Reset error state
|
|
129
|
+
variableLoadError = null;
|
|
130
|
+
|
|
131
|
+
// If we have deprecated variableSchema prop, use it directly
|
|
110
132
|
if (variableSchema && Object.keys(variableSchema.variables).length > 0) {
|
|
111
|
-
|
|
133
|
+
effectiveVariableSchema = variableSchema;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// If no variables config, clear schema
|
|
138
|
+
if (!variables) {
|
|
139
|
+
effectiveVariableSchema = undefined;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// If variables config has static schema only (no API), use it directly
|
|
144
|
+
if (variables.schema && !variables.api && Object.keys(variables.schema.variables).length > 0) {
|
|
145
|
+
effectiveVariableSchema = variables.schema;
|
|
146
|
+
return;
|
|
112
147
|
}
|
|
113
|
-
|
|
114
|
-
|
|
148
|
+
|
|
149
|
+
// If variables config requires node context (ports or API mode)
|
|
150
|
+
if ((variables.ports !== undefined || variables.api) && node && nodes && edges) {
|
|
151
|
+
try {
|
|
152
|
+
isLoadingVariables = true;
|
|
153
|
+
effectiveVariableSchema = await getVariableSchema(
|
|
154
|
+
node,
|
|
155
|
+
nodes,
|
|
156
|
+
edges,
|
|
157
|
+
variables,
|
|
158
|
+
workflowId,
|
|
159
|
+
authProvider
|
|
160
|
+
);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('Failed to load variable schema:', error);
|
|
163
|
+
variableLoadError = error instanceof Error ? error.message : 'Failed to load variables';
|
|
164
|
+
effectiveVariableSchema = undefined;
|
|
165
|
+
} finally {
|
|
166
|
+
isLoadingVariables = false;
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
// No schema available
|
|
170
|
+
effectiveVariableSchema = undefined;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Retry loading variables after an error
|
|
176
|
+
*/
|
|
177
|
+
function retryLoadVariables() {
|
|
178
|
+
loadVariableSchema();
|
|
179
|
+
}
|
|
115
180
|
|
|
116
181
|
/**
|
|
117
182
|
* Whether to show the variable hints section.
|
|
@@ -121,13 +186,12 @@
|
|
|
121
186
|
|
|
122
187
|
/**
|
|
123
188
|
* Derive the list of top-level variable names for the hints display.
|
|
124
|
-
* Prefers effectiveVariableSchema if available, falls back to variableHints.
|
|
125
189
|
*/
|
|
126
190
|
const displayVariables = $derived.by(() => {
|
|
127
191
|
if (effectiveVariableSchema) {
|
|
128
192
|
return Object.keys(effectiveVariableSchema.variables);
|
|
129
193
|
}
|
|
130
|
-
return
|
|
194
|
+
return [];
|
|
131
195
|
});
|
|
132
196
|
|
|
133
197
|
/** Reference to the container element */
|
|
@@ -139,6 +203,9 @@
|
|
|
139
203
|
/** Flag to prevent update loops */
|
|
140
204
|
let isInternalUpdate = false;
|
|
141
205
|
|
|
206
|
+
/** Compartment for dynamic autocomplete reconfiguration */
|
|
207
|
+
const autocompleteCompartment = new Compartment();
|
|
208
|
+
|
|
142
209
|
/**
|
|
143
210
|
* Create a MatchDecorator for {{ variable }} patterns
|
|
144
211
|
* This highlights the entire {{ variable }} expression
|
|
@@ -285,15 +352,12 @@
|
|
|
285
352
|
EditorState.tabSize.of(2)
|
|
286
353
|
];
|
|
287
354
|
|
|
288
|
-
// Add autocomplete
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
// Fallback to simple autocomplete with just variable names
|
|
295
|
-
extensions.push(createSimpleTemplateAutocomplete(variableHints));
|
|
296
|
-
}
|
|
355
|
+
// Add autocomplete compartment (can be reconfigured dynamically)
|
|
356
|
+
// When disabled or no schema, use empty array
|
|
357
|
+
if (!disabled && effectiveVariableSchema) {
|
|
358
|
+
extensions.push(autocompleteCompartment.of(createTemplateAutocomplete(effectiveVariableSchema)));
|
|
359
|
+
} else {
|
|
360
|
+
extensions.push(autocompleteCompartment.of([]));
|
|
297
361
|
}
|
|
298
362
|
|
|
299
363
|
if (darkTheme) {
|
|
@@ -341,19 +405,23 @@
|
|
|
341
405
|
}
|
|
342
406
|
|
|
343
407
|
/**
|
|
344
|
-
* Initialize CodeMirror editor on mount
|
|
408
|
+
* Initialize CodeMirror editor and load variables on mount
|
|
345
409
|
*/
|
|
346
410
|
onMount(() => {
|
|
347
411
|
if (!containerRef) {
|
|
348
412
|
return;
|
|
349
413
|
}
|
|
350
414
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
415
|
+
// Load variables first
|
|
416
|
+
loadVariableSchema().then(() => {
|
|
417
|
+
// Then create editor with loaded schema
|
|
418
|
+
editorView = new EditorView({
|
|
419
|
+
state: EditorState.create({
|
|
420
|
+
doc: value,
|
|
421
|
+
extensions: createExtensions()
|
|
422
|
+
}),
|
|
423
|
+
parent: containerRef
|
|
424
|
+
});
|
|
357
425
|
});
|
|
358
426
|
});
|
|
359
427
|
|
|
@@ -389,6 +457,28 @@
|
|
|
389
457
|
isInternalUpdate = false;
|
|
390
458
|
}
|
|
391
459
|
});
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Reconfigure editor when variable schema changes (e.g., after async loading)
|
|
463
|
+
*/
|
|
464
|
+
$effect(() => {
|
|
465
|
+
// Only track effectiveVariableSchema changes
|
|
466
|
+
const schema = effectiveVariableSchema;
|
|
467
|
+
|
|
468
|
+
if (!editorView) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// When effectiveVariableSchema changes, reconfigure the autocomplete compartment
|
|
473
|
+
// This happens after async API loading completes
|
|
474
|
+
const newAutocomplete = !disabled && schema
|
|
475
|
+
? createTemplateAutocomplete(schema)
|
|
476
|
+
: [];
|
|
477
|
+
|
|
478
|
+
editorView.dispatch({
|
|
479
|
+
effects: [autocompleteCompartment.reconfigure(newAutocomplete)]
|
|
480
|
+
});
|
|
481
|
+
});
|
|
392
482
|
</script>
|
|
393
483
|
|
|
394
484
|
<div class="form-template-editor">
|
|
@@ -412,6 +502,60 @@
|
|
|
412
502
|
aria-label="Template editor"
|
|
413
503
|
></div>
|
|
414
504
|
|
|
505
|
+
<!-- Loading banner (shown while fetching variables from API) -->
|
|
506
|
+
{#if isLoadingVariables}
|
|
507
|
+
<div class="form-template-editor__banner form-template-editor__banner--loading">
|
|
508
|
+
<svg
|
|
509
|
+
class="form-template-editor__banner-icon form-template-editor__banner-icon--spin"
|
|
510
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
511
|
+
fill="none"
|
|
512
|
+
viewBox="0 0 24 24"
|
|
513
|
+
>
|
|
514
|
+
<circle
|
|
515
|
+
class="opacity-25"
|
|
516
|
+
cx="12"
|
|
517
|
+
cy="12"
|
|
518
|
+
r="10"
|
|
519
|
+
stroke="currentColor"
|
|
520
|
+
stroke-width="4"
|
|
521
|
+
></circle>
|
|
522
|
+
<path
|
|
523
|
+
class="opacity-75"
|
|
524
|
+
fill="currentColor"
|
|
525
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
526
|
+
></path>
|
|
527
|
+
</svg>
|
|
528
|
+
<span>Loading variables...</span>
|
|
529
|
+
</div>
|
|
530
|
+
{/if}
|
|
531
|
+
|
|
532
|
+
<!-- Error banner (shown when API fetch fails) -->
|
|
533
|
+
{#if variableLoadError}
|
|
534
|
+
<div class="form-template-editor__banner form-template-editor__banner--error">
|
|
535
|
+
<svg
|
|
536
|
+
class="form-template-editor__banner-icon"
|
|
537
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
538
|
+
viewBox="0 0 20 20"
|
|
539
|
+
fill="currentColor"
|
|
540
|
+
>
|
|
541
|
+
<path
|
|
542
|
+
fill-rule="evenodd"
|
|
543
|
+
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z"
|
|
544
|
+
clip-rule="evenodd"
|
|
545
|
+
/>
|
|
546
|
+
</svg>
|
|
547
|
+
<span>{variableLoadError}</span>
|
|
548
|
+
<button
|
|
549
|
+
type="button"
|
|
550
|
+
class="form-template-editor__banner-btn"
|
|
551
|
+
onclick={retryLoadVariables}
|
|
552
|
+
title="Retry loading variables"
|
|
553
|
+
>
|
|
554
|
+
Retry
|
|
555
|
+
</button>
|
|
556
|
+
</div>
|
|
557
|
+
{/if}
|
|
558
|
+
|
|
415
559
|
<!-- Variable hints section (shown when variables are available and showHints is true) -->
|
|
416
560
|
{#if showHints && displayVariables.length > 0}
|
|
417
561
|
<div class="form-template-editor__hints">
|
|
@@ -613,4 +757,68 @@
|
|
|
613
757
|
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace;
|
|
614
758
|
font-size: 0.625rem;
|
|
615
759
|
}
|
|
760
|
+
|
|
761
|
+
/* Loading and error banners */
|
|
762
|
+
.form-template-editor__banner {
|
|
763
|
+
display: flex;
|
|
764
|
+
align-items: center;
|
|
765
|
+
gap: 0.5rem;
|
|
766
|
+
margin-top: 0.625rem;
|
|
767
|
+
padding: 0.625rem 0.75rem;
|
|
768
|
+
border-radius: var(--fd-radius-md);
|
|
769
|
+
font-size: 0.75rem;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
.form-template-editor__banner--loading {
|
|
773
|
+
background-color: rgba(59, 130, 246, 0.1);
|
|
774
|
+
border: 1px solid rgba(59, 130, 246, 0.3);
|
|
775
|
+
color: rgb(29, 78, 216);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.form-template-editor__banner--error {
|
|
779
|
+
background-color: rgba(239, 68, 68, 0.1);
|
|
780
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
781
|
+
color: rgb(185, 28, 28);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
.form-template-editor__banner-icon {
|
|
785
|
+
width: 1rem;
|
|
786
|
+
height: 1rem;
|
|
787
|
+
flex-shrink: 0;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
.form-template-editor__banner-icon--spin {
|
|
791
|
+
animation: spin 1s linear infinite;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
@keyframes spin {
|
|
795
|
+
from {
|
|
796
|
+
transform: rotate(0deg);
|
|
797
|
+
}
|
|
798
|
+
to {
|
|
799
|
+
transform: rotate(360deg);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.form-template-editor__banner-btn {
|
|
804
|
+
margin-left: auto;
|
|
805
|
+
padding: 0.25rem 0.625rem;
|
|
806
|
+
background-color: rgba(239, 68, 68, 0.15);
|
|
807
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
808
|
+
border-radius: var(--fd-radius-sm);
|
|
809
|
+
font-size: 0.6875rem;
|
|
810
|
+
font-weight: 500;
|
|
811
|
+
color: rgb(185, 28, 28);
|
|
812
|
+
cursor: pointer;
|
|
813
|
+
transition: all var(--fd-transition-fast);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.form-template-editor__banner-btn:hover {
|
|
817
|
+
background-color: rgba(239, 68, 68, 0.25);
|
|
818
|
+
border-color: rgba(239, 68, 68, 0.5);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.form-template-editor__banner-btn:active {
|
|
822
|
+
transform: scale(0.98);
|
|
823
|
+
}
|
|
616
824
|
</style>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { VariableSchema, TemplateVariablesConfig } from '../../types/index.js';
|
|
1
|
+
import type { VariableSchema, TemplateVariablesConfig, WorkflowNode, WorkflowEdge, AuthProvider } from '../../types/index.js';
|
|
2
2
|
interface Props {
|
|
3
3
|
/** Field identifier */
|
|
4
4
|
id: string;
|
|
@@ -23,12 +23,6 @@ interface Props {
|
|
|
23
23
|
* @deprecated Use `variables.schema` instead
|
|
24
24
|
*/
|
|
25
25
|
variableSchema?: VariableSchema;
|
|
26
|
-
/**
|
|
27
|
-
* Simple variable names for basic hints (backward compatible).
|
|
28
|
-
* Used when variableSchema is not provided.
|
|
29
|
-
* @deprecated Use `variables.schema` instead
|
|
30
|
-
*/
|
|
31
|
-
variableHints?: string[];
|
|
32
26
|
/** Placeholder variable example for the hint */
|
|
33
27
|
placeholderExample?: string;
|
|
34
28
|
/** Whether the field is disabled (read-only) */
|
|
@@ -37,6 +31,16 @@ interface Props {
|
|
|
37
31
|
ariaDescribedBy?: string;
|
|
38
32
|
/** Callback when value changes */
|
|
39
33
|
onChange: (value: string) => void;
|
|
34
|
+
/** Current workflow node (required for API mode) */
|
|
35
|
+
node?: WorkflowNode;
|
|
36
|
+
/** All workflow nodes (required for port-derived variables) */
|
|
37
|
+
nodes?: WorkflowNode[];
|
|
38
|
+
/** All workflow edges (required for port-derived variables) */
|
|
39
|
+
edges?: WorkflowEdge[];
|
|
40
|
+
/** Workflow ID (required for API mode) */
|
|
41
|
+
workflowId?: string;
|
|
42
|
+
/** Auth provider for API requests */
|
|
43
|
+
authProvider?: AuthProvider;
|
|
40
44
|
}
|
|
41
45
|
declare const FormTemplateEditor: import("svelte").Component<Props, {}, "">;
|
|
42
46
|
type FormTemplateEditor = ReturnType<typeof FormTemplateEditor>;
|
|
@@ -44,4 +44,4 @@ export { default as FormCodeEditor } from './FormCodeEditor.svelte';
|
|
|
44
44
|
export { default as FormMarkdownEditor } from './FormMarkdownEditor.svelte';
|
|
45
45
|
export { default as FormTemplateEditor } from './FormTemplateEditor.svelte';
|
|
46
46
|
export { default as FormAutocomplete } from './FormAutocomplete.svelte';
|
|
47
|
-
export { createTemplateAutocomplete
|
|
47
|
+
export { createTemplateAutocomplete } from './templateAutocomplete.js';
|