@d34dman/flowdrop 0.0.9 → 0.0.11
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 +20 -385
- package/dist/components/ConfigForm.svelte +299 -448
- package/dist/components/ConfigForm.svelte.d.ts +6 -29
- package/dist/components/PipelineStatus.svelte +5 -0
- package/dist/components/WorkflowEditor.svelte +110 -48
- package/dist/utils/performanceUtils.d.ts +30 -0
- package/dist/utils/performanceUtils.js +109 -0
- package/package.json +2 -2
|
@@ -1,555 +1,406 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
ConfigForm Component
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Handles dynamic form rendering for node configuration
|
|
4
|
+
Uses reactive $state for proper Svelte 5 reactivity
|
|
5
5
|
-->
|
|
6
6
|
|
|
7
7
|
<script lang="ts">
|
|
8
|
-
import type { ConfigSchema,
|
|
9
|
-
import { createEventDispatcher } from 'svelte';
|
|
8
|
+
import type { ConfigSchema, WorkflowNode } from "../types/index.js"
|
|
10
9
|
|
|
11
10
|
interface Props {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
node: WorkflowNode
|
|
12
|
+
onSave: (config: Record<string, unknown>) => void
|
|
13
|
+
onCancel: () => void
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
let
|
|
18
|
-
let dispatch = createEventDispatcher<{
|
|
19
|
-
change: { values: ConfigValues };
|
|
20
|
-
validate: { isValid: boolean; errors: string[] };
|
|
21
|
-
}>();
|
|
22
|
-
|
|
23
|
-
// Local copy of values for editing
|
|
24
|
-
let localValues = $state<ConfigValues>({ ...props.values });
|
|
25
|
-
|
|
26
|
-
// Validation errors
|
|
27
|
-
let validationErrors = $state<Record<string, string>>({});
|
|
16
|
+
let { node, onSave, onCancel }: Props = $props()
|
|
28
17
|
|
|
29
18
|
/**
|
|
30
|
-
*
|
|
19
|
+
* Get the configuration schema from node metadata
|
|
31
20
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
property: ConfigProperty
|
|
36
|
-
): string | null {
|
|
37
|
-
// Check if schema exists
|
|
38
|
-
if (!props.schema) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Required field validation
|
|
43
|
-
if (
|
|
44
|
-
props.schema.required?.includes(fieldName) &&
|
|
45
|
-
(value === null || value === undefined || value === '')
|
|
46
|
-
) {
|
|
47
|
-
return `${property.title || fieldName} is required`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Type validation
|
|
51
|
-
if (value !== null && value !== undefined) {
|
|
52
|
-
switch (property.type) {
|
|
53
|
-
case 'string':
|
|
54
|
-
if (typeof value !== 'string') {
|
|
55
|
-
return `${property.title || fieldName} must be a string`;
|
|
56
|
-
}
|
|
57
|
-
if (property.minLength && value.length < property.minLength) {
|
|
58
|
-
return `${property.title || fieldName} must be at least ${property.minLength} characters`;
|
|
59
|
-
}
|
|
60
|
-
if (property.maxLength && value.length > property.maxLength) {
|
|
61
|
-
return `${property.title || fieldName} must be at most ${property.maxLength} characters`;
|
|
62
|
-
}
|
|
63
|
-
if (property.pattern && !new RegExp(property.pattern).test(value)) {
|
|
64
|
-
return `${property.title || fieldName} format is invalid`;
|
|
65
|
-
}
|
|
66
|
-
break;
|
|
67
|
-
|
|
68
|
-
case 'number':
|
|
69
|
-
if (typeof value !== 'number') {
|
|
70
|
-
return `${property.title || fieldName} must be a number`;
|
|
71
|
-
}
|
|
72
|
-
if (property.minimum !== undefined && value < property.minimum) {
|
|
73
|
-
return `${property.title || fieldName} must be at least ${property.minimum}`;
|
|
74
|
-
}
|
|
75
|
-
if (property.maximum !== undefined && value > property.maximum) {
|
|
76
|
-
return `${property.title || fieldName} must be at most ${property.maximum}`;
|
|
77
|
-
}
|
|
78
|
-
break;
|
|
79
|
-
|
|
80
|
-
case 'boolean':
|
|
81
|
-
if (typeof value !== 'boolean') {
|
|
82
|
-
return `${property.title || fieldName} must be a boolean`;
|
|
83
|
-
}
|
|
84
|
-
break;
|
|
85
|
-
|
|
86
|
-
case 'array':
|
|
87
|
-
if (!Array.isArray(value)) {
|
|
88
|
-
return `${property.title || fieldName} must be an array`;
|
|
89
|
-
}
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
21
|
+
const configSchema = $derived(
|
|
22
|
+
node.data.metadata?.configSchema as ConfigSchema | undefined
|
|
23
|
+
)
|
|
96
24
|
|
|
97
25
|
/**
|
|
98
|
-
*
|
|
26
|
+
* Get the current node configuration
|
|
99
27
|
*/
|
|
100
|
-
|
|
101
|
-
const errors: string[] = [];
|
|
102
|
-
const newValidationErrors: Record<string, string> = {};
|
|
103
|
-
|
|
104
|
-
// Check if schema and properties exist
|
|
105
|
-
if (!props.schema || !props.schema.properties || typeof props.schema.properties !== 'object') {
|
|
106
|
-
console.warn('ConfigForm: Invalid schema or properties:', {
|
|
107
|
-
schema: props.schema,
|
|
108
|
-
properties: props.schema?.properties
|
|
109
|
-
});
|
|
110
|
-
return { isValid: true, errors: [] };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
Object.entries(props.schema.properties).forEach(([fieldName, property]) => {
|
|
114
|
-
const value = localValues[fieldName];
|
|
115
|
-
const error = validateField(fieldName, value, property);
|
|
116
|
-
|
|
117
|
-
if (error) {
|
|
118
|
-
errors.push(error);
|
|
119
|
-
newValidationErrors[fieldName] = error;
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
validationErrors = newValidationErrors;
|
|
124
|
-
|
|
125
|
-
const isValid = errors.length === 0;
|
|
126
|
-
dispatch('validate', { isValid, errors });
|
|
127
|
-
|
|
128
|
-
return { isValid, errors };
|
|
129
|
-
}
|
|
28
|
+
const nodeConfig = $derived(node.data.config || {})
|
|
130
29
|
|
|
131
30
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
31
|
+
* Create reactive configuration values using $state
|
|
32
|
+
* This fixes the Svelte 5 reactivity warnings
|
|
134
33
|
*/
|
|
135
|
-
|
|
136
|
-
localValues[fieldName] = value;
|
|
137
|
-
|
|
138
|
-
// Validate the changed field
|
|
139
|
-
if (props.schema && props.schema.properties && props.schema.properties[fieldName]) {
|
|
140
|
-
const property = props.schema.properties[fieldName];
|
|
141
|
-
const error = validateField(fieldName, value, property);
|
|
142
|
-
|
|
143
|
-
if (error) {
|
|
144
|
-
validationErrors[fieldName] = error;
|
|
145
|
-
} else {
|
|
146
|
-
delete validationErrors[fieldName];
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Merge hidden field values from original props to ensure they're preserved
|
|
151
|
-
const hiddenFieldValues: ConfigValues = {};
|
|
152
|
-
if (props.schema?.properties) {
|
|
153
|
-
Object.entries(props.schema.properties).forEach(([key, property]) => {
|
|
154
|
-
if (property.format === 'hidden' && key in props.values) {
|
|
155
|
-
hiddenFieldValues[key] = props.values[key];
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Emit change event with hidden fields preserved
|
|
161
|
-
dispatch('change', { values: { ...localValues, ...hiddenFieldValues } });
|
|
162
|
-
|
|
163
|
-
// Validate entire form
|
|
164
|
-
validateForm();
|
|
165
|
-
}
|
|
34
|
+
let configValues = $state<Record<string, unknown>>({})
|
|
166
35
|
|
|
167
36
|
/**
|
|
168
|
-
*
|
|
37
|
+
* Initialize config values when node or schema changes
|
|
169
38
|
*/
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
|
|
39
|
+
$effect(() => {
|
|
40
|
+
if (configSchema?.properties) {
|
|
41
|
+
const mergedConfig: Record<string, unknown> = {}
|
|
42
|
+
Object.entries(configSchema.properties).forEach(([key, field]) => {
|
|
43
|
+
const fieldConfig = field as any
|
|
44
|
+
// Use existing value if available, otherwise use default
|
|
45
|
+
mergedConfig[key] =
|
|
46
|
+
nodeConfig[key] !== undefined ? nodeConfig[key] : fieldConfig.default
|
|
47
|
+
})
|
|
48
|
+
configValues = mergedConfig
|
|
173
49
|
}
|
|
50
|
+
})
|
|
174
51
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Handle form submission
|
|
54
|
+
*/
|
|
55
|
+
function handleSave(): void {
|
|
56
|
+
// Collect all form values including hidden fields
|
|
57
|
+
const form = document.querySelector(".flowdrop-config-sidebar__form")
|
|
58
|
+
const updatedConfig: Record<string, unknown> = { ...configValues }
|
|
59
|
+
|
|
60
|
+
if (form) {
|
|
61
|
+
const inputs = form.querySelectorAll("input, select, textarea")
|
|
62
|
+
inputs.forEach((input: any) => {
|
|
63
|
+
if (input.id) {
|
|
64
|
+
if (input.type === "checkbox") {
|
|
65
|
+
updatedConfig[input.id] = input.checked
|
|
66
|
+
} else if (input.type === "number") {
|
|
67
|
+
updatedConfig[input.id] = input.value ? Number(input.value) : input.value
|
|
68
|
+
} else if (input.type === "hidden") {
|
|
69
|
+
// Parse hidden field values that might be JSON
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(input.value)
|
|
72
|
+
updatedConfig[input.id] = parsed
|
|
73
|
+
} catch {
|
|
74
|
+
// If not JSON, use raw value
|
|
75
|
+
updatedConfig[input.id] = input.value
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
updatedConfig[input.id] = input.value
|
|
79
|
+
}
|
|
180
80
|
}
|
|
181
|
-
|
|
182
|
-
case 'number':
|
|
183
|
-
return 0;
|
|
184
|
-
case 'boolean':
|
|
185
|
-
return false;
|
|
186
|
-
case 'array':
|
|
187
|
-
return [];
|
|
188
|
-
case 'object':
|
|
189
|
-
return {};
|
|
190
|
-
default:
|
|
191
|
-
return null;
|
|
81
|
+
})
|
|
192
82
|
}
|
|
193
|
-
}
|
|
194
83
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
84
|
+
// Preserve hidden field values from original config if not collected from form
|
|
85
|
+
if (node.data.config && configSchema?.properties) {
|
|
86
|
+
Object.entries(configSchema.properties).forEach(([key, property]: [string, any]) => {
|
|
87
|
+
if (
|
|
88
|
+
property.format === "hidden" &&
|
|
89
|
+
!(key in updatedConfig) &&
|
|
90
|
+
key in node.data.config
|
|
91
|
+
) {
|
|
92
|
+
updatedConfig[key] = node.data.config[key]
|
|
204
93
|
}
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
// Validate on initialization
|
|
208
|
-
validateForm();
|
|
94
|
+
})
|
|
209
95
|
}
|
|
210
|
-
|
|
96
|
+
|
|
97
|
+
onSave(updatedConfig)
|
|
98
|
+
}
|
|
211
99
|
</script>
|
|
212
100
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
{#each Object.entries(
|
|
217
|
-
{
|
|
218
|
-
|
|
219
|
-
<
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
value={typeof (localValues[fieldName] ?? property.default) === 'object'
|
|
223
|
-
? JSON.stringify(localValues[fieldName] ?? property.default ?? {})
|
|
224
|
-
: (localValues[fieldName] ?? property.default ?? '')}
|
|
225
|
-
/>
|
|
226
|
-
{:else}
|
|
227
|
-
<div class="flowdrop-form-field">
|
|
228
|
-
<label class="flowdrop-form-label" for={fieldName}>
|
|
229
|
-
{property.title || fieldName}
|
|
230
|
-
{#if props.schema.required?.includes(fieldName)}
|
|
231
|
-
<span class="flowdrop-form-required">*</span>
|
|
232
|
-
{/if}
|
|
101
|
+
{#if configSchema}
|
|
102
|
+
<div class="flowdrop-config-sidebar__form">
|
|
103
|
+
{#if configSchema.properties}
|
|
104
|
+
{#each Object.entries(configSchema.properties) as [key, field] (key)}
|
|
105
|
+
{@const fieldConfig = field as any}
|
|
106
|
+
{#if fieldConfig.format !== "hidden"}
|
|
107
|
+
<div class="flowdrop-config-sidebar__field">
|
|
108
|
+
<label class="flowdrop-config-sidebar__field-label" for={key}>
|
|
109
|
+
{fieldConfig.title || fieldConfig.description || key}
|
|
233
110
|
</label>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (checked) {
|
|
254
|
-
if (!currentValues.includes(option)) {
|
|
255
|
-
handleFieldChange(fieldName, [...currentValues, option]);
|
|
256
|
-
}
|
|
257
|
-
} else {
|
|
258
|
-
handleFieldChange(
|
|
259
|
-
fieldName,
|
|
260
|
-
currentValues.filter((v) => v !== option)
|
|
261
|
-
);
|
|
111
|
+
{#if fieldConfig.enum && fieldConfig.multiple}
|
|
112
|
+
<!-- Checkboxes for enum with multiple selection -->
|
|
113
|
+
<div class="flowdrop-config-sidebar__checkbox-group">
|
|
114
|
+
{#each fieldConfig.enum as option (String(option))}
|
|
115
|
+
<label class="flowdrop-config-sidebar__checkbox-item">
|
|
116
|
+
<input
|
|
117
|
+
type="checkbox"
|
|
118
|
+
class="flowdrop-config-sidebar__checkbox"
|
|
119
|
+
value={String(option)}
|
|
120
|
+
checked={Array.isArray(configValues[key]) &&
|
|
121
|
+
configValues[key].includes(String(option))}
|
|
122
|
+
onchange={(e) => {
|
|
123
|
+
const checked = e.currentTarget.checked
|
|
124
|
+
const currentValues = Array.isArray(configValues[key])
|
|
125
|
+
? [...(configValues[key] as unknown[])]
|
|
126
|
+
: []
|
|
127
|
+
if (checked) {
|
|
128
|
+
if (!currentValues.includes(String(option))) {
|
|
129
|
+
configValues[key] = [...currentValues, String(option)]
|
|
262
130
|
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
{option}
|
|
283
|
-
</option>
|
|
284
|
-
{/each}
|
|
285
|
-
</select>
|
|
286
|
-
{:else if property.format === 'multiline' || (property.maxLength && property.maxLength > 100)}
|
|
287
|
-
<!-- Textarea for multiline or long text -->
|
|
288
|
-
<textarea
|
|
289
|
-
id={fieldName}
|
|
290
|
-
class="flowdrop-form-textarea {validationErrors[fieldName]
|
|
291
|
-
? 'flowdrop-form-textarea--error'
|
|
292
|
-
: ''}"
|
|
293
|
-
placeholder={property.description || ''}
|
|
294
|
-
rows="4"
|
|
295
|
-
disabled={props.disabled || false}
|
|
296
|
-
onchange={(e) =>
|
|
297
|
-
handleFieldChange(fieldName, (e.target as HTMLTextAreaElement).value)}
|
|
298
|
-
>{localValues[fieldName] || ''}</textarea
|
|
299
|
-
>
|
|
300
|
-
{:else}
|
|
301
|
-
<!-- Regular text input -->
|
|
302
|
-
<input
|
|
303
|
-
id={fieldName}
|
|
304
|
-
type="text"
|
|
305
|
-
class="flowdrop-form-input {validationErrors[fieldName]
|
|
306
|
-
? 'flowdrop-form-input--error'
|
|
307
|
-
: ''}"
|
|
308
|
-
value={localValues[fieldName] || ''}
|
|
309
|
-
placeholder={property.description || ''}
|
|
310
|
-
disabled={props.disabled || false}
|
|
311
|
-
onchange={(e) =>
|
|
312
|
-
handleFieldChange(fieldName, (e.target as HTMLInputElement).value)}
|
|
313
|
-
/>
|
|
314
|
-
{/if}
|
|
315
|
-
{:else if property.type === 'number'}
|
|
316
|
-
<!-- Number input as text field -->
|
|
317
|
-
<input
|
|
318
|
-
id={fieldName}
|
|
319
|
-
type="text"
|
|
320
|
-
class="flowdrop-form-input {validationErrors[fieldName]
|
|
321
|
-
? 'flowdrop-form-input--error'
|
|
322
|
-
: ''}"
|
|
323
|
-
value={localValues[fieldName] || ''}
|
|
324
|
-
placeholder="Enter a number"
|
|
325
|
-
disabled={props.disabled || false}
|
|
326
|
-
oninput={(e) => {
|
|
327
|
-
const value = (e.target as HTMLInputElement).value;
|
|
328
|
-
const numValue = value === '' ? 0 : parseFloat(value);
|
|
329
|
-
if (!isNaN(numValue)) {
|
|
330
|
-
handleFieldChange(fieldName, numValue);
|
|
331
|
-
}
|
|
332
|
-
}}
|
|
333
|
-
onblur={(e) => {
|
|
334
|
-
const value = (e.target as HTMLInputElement).value;
|
|
335
|
-
const numValue = value === '' ? 0 : parseFloat(value);
|
|
336
|
-
if (!isNaN(numValue)) {
|
|
337
|
-
handleFieldChange(fieldName, numValue);
|
|
338
|
-
}
|
|
339
|
-
}}
|
|
340
|
-
/>
|
|
341
|
-
{:else if property.type === 'boolean'}
|
|
342
|
-
<!-- Checkbox -->
|
|
343
|
-
<label class="flowdrop-form-checkbox">
|
|
344
|
-
<input
|
|
345
|
-
id={fieldName}
|
|
346
|
-
type="checkbox"
|
|
347
|
-
class="flowdrop-form-checkbox__input"
|
|
348
|
-
checked={Boolean(localValues[fieldName])}
|
|
349
|
-
disabled={props.disabled || false}
|
|
350
|
-
onchange={(e) =>
|
|
351
|
-
handleFieldChange(fieldName, (e.target as HTMLInputElement).checked)}
|
|
352
|
-
/>
|
|
353
|
-
<span class="flowdrop-form-checkbox__label">
|
|
354
|
-
{property.description || ''}
|
|
355
|
-
</span>
|
|
356
|
-
</label>
|
|
357
|
-
{:else if property.type === 'array'}
|
|
358
|
-
<!-- Array input (comma-separated) -->
|
|
359
|
-
<textarea
|
|
360
|
-
id={fieldName}
|
|
361
|
-
class="flowdrop-form-textarea {validationErrors[fieldName]
|
|
362
|
-
? 'flowdrop-form-textarea--error'
|
|
363
|
-
: ''}"
|
|
364
|
-
placeholder="Enter values separated by commas"
|
|
365
|
-
rows="3"
|
|
366
|
-
disabled={props.disabled || false}
|
|
367
|
-
onchange={(e) =>
|
|
368
|
-
handleFieldChange(
|
|
369
|
-
fieldName,
|
|
370
|
-
(e.target as HTMLTextAreaElement).value
|
|
371
|
-
.split(',')
|
|
372
|
-
.map((v) => v.trim())
|
|
373
|
-
.filter((v) => v)
|
|
374
|
-
)}
|
|
375
|
-
>{Array.isArray(localValues[fieldName])
|
|
376
|
-
? localValues[fieldName].join(', ')
|
|
377
|
-
: ''}</textarea
|
|
131
|
+
} else {
|
|
132
|
+
configValues[key] = currentValues.filter(
|
|
133
|
+
(v) => v !== String(option)
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
138
|
+
<span class="flowdrop-config-sidebar__checkbox-label">
|
|
139
|
+
{String(option)}
|
|
140
|
+
</span>
|
|
141
|
+
</label>
|
|
142
|
+
{/each}
|
|
143
|
+
</div>
|
|
144
|
+
{:else if fieldConfig.enum}
|
|
145
|
+
<!-- Select for enum with single selection -->
|
|
146
|
+
<select
|
|
147
|
+
id={key}
|
|
148
|
+
class="flowdrop-config-sidebar__select"
|
|
149
|
+
bind:value={configValues[key]}
|
|
378
150
|
>
|
|
379
|
-
|
|
380
|
-
|
|
151
|
+
{#each fieldConfig.enum as option (String(option))}
|
|
152
|
+
<option value={String(option)}>{String(option)}</option>
|
|
153
|
+
{/each}
|
|
154
|
+
</select>
|
|
155
|
+
{:else if fieldConfig.type === "string" && fieldConfig.format === "multiline"}
|
|
156
|
+
<!-- Textarea for multiline strings -->
|
|
381
157
|
<textarea
|
|
382
|
-
id={
|
|
383
|
-
class="flowdrop-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
placeholder="Enter JSON object"
|
|
158
|
+
id={key}
|
|
159
|
+
class="flowdrop-config-sidebar__textarea"
|
|
160
|
+
bind:value={configValues[key]}
|
|
161
|
+
placeholder={String(fieldConfig.placeholder || "")}
|
|
387
162
|
rows="4"
|
|
388
|
-
|
|
163
|
+
></textarea>
|
|
164
|
+
{:else if fieldConfig.type === "string"}
|
|
165
|
+
<input
|
|
166
|
+
id={key}
|
|
167
|
+
type="text"
|
|
168
|
+
class="flowdrop-config-sidebar__input"
|
|
169
|
+
bind:value={configValues[key]}
|
|
170
|
+
placeholder={String(fieldConfig.placeholder || "")}
|
|
171
|
+
/>
|
|
172
|
+
{:else if fieldConfig.type === "number"}
|
|
173
|
+
<input
|
|
174
|
+
id={key}
|
|
175
|
+
type="number"
|
|
176
|
+
class="flowdrop-config-sidebar__input"
|
|
177
|
+
bind:value={configValues[key]}
|
|
178
|
+
placeholder={String(fieldConfig.placeholder || "")}
|
|
179
|
+
/>
|
|
180
|
+
{:else if fieldConfig.type === "boolean"}
|
|
181
|
+
<input
|
|
182
|
+
id={key}
|
|
183
|
+
type="checkbox"
|
|
184
|
+
class="flowdrop-config-sidebar__checkbox"
|
|
185
|
+
checked={Boolean(configValues[key] || fieldConfig.default || false)}
|
|
389
186
|
onchange={(e) => {
|
|
390
|
-
|
|
391
|
-
handleFieldChange(
|
|
392
|
-
fieldName,
|
|
393
|
-
JSON.parse((e.target as HTMLTextAreaElement).value)
|
|
394
|
-
);
|
|
395
|
-
} catch {
|
|
396
|
-
// Handle JSON parse error
|
|
397
|
-
}
|
|
187
|
+
configValues[key] = e.currentTarget.checked
|
|
398
188
|
}}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
189
|
+
/>
|
|
190
|
+
{:else if fieldConfig.type === "select" || fieldConfig.options}
|
|
191
|
+
<select
|
|
192
|
+
id={key}
|
|
193
|
+
class="flowdrop-config-sidebar__select"
|
|
194
|
+
bind:value={configValues[key]}
|
|
402
195
|
>
|
|
196
|
+
{#if fieldConfig.options}
|
|
197
|
+
{#each fieldConfig.options as option (String(option.value))}
|
|
198
|
+
{@const optionConfig = option as any}
|
|
199
|
+
<option value={String(optionConfig.value)}
|
|
200
|
+
>{String(optionConfig.label)}</option
|
|
201
|
+
>
|
|
202
|
+
{/each}
|
|
203
|
+
{/if}
|
|
204
|
+
</select>
|
|
403
205
|
{:else}
|
|
404
|
-
<!--
|
|
206
|
+
<!-- Fallback for unknown field types -->
|
|
405
207
|
<input
|
|
406
|
-
id={
|
|
208
|
+
id={key}
|
|
407
209
|
type="text"
|
|
408
|
-
class="flowdrop-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
value={localValues[fieldName] || ''}
|
|
412
|
-
placeholder={property.description || ''}
|
|
413
|
-
disabled={props.disabled || false}
|
|
414
|
-
onchange={(e) => handleFieldChange(fieldName, (e.target as HTMLInputElement).value)}
|
|
210
|
+
class="flowdrop-config-sidebar__input"
|
|
211
|
+
bind:value={configValues[key]}
|
|
212
|
+
placeholder={String(fieldConfig.placeholder || "")}
|
|
415
213
|
/>
|
|
416
214
|
{/if}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
{#if property.description}
|
|
423
|
-
<div class="flowdrop-form-help">{property.description}</div>
|
|
215
|
+
{#if fieldConfig.description}
|
|
216
|
+
<p class="flowdrop-config-sidebar__field-description">
|
|
217
|
+
{String(fieldConfig.description)}
|
|
218
|
+
</p>
|
|
424
219
|
{/if}
|
|
425
220
|
</div>
|
|
426
221
|
{/if}
|
|
427
222
|
{/each}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
</
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
223
|
+
{:else}
|
|
224
|
+
<!-- If no properties, show the raw schema for debugging -->
|
|
225
|
+
<div class="flowdrop-config-sidebar__debug">
|
|
226
|
+
<p><strong>Debug - Config Schema:</strong></p>
|
|
227
|
+
<pre>{JSON.stringify(configSchema, null, 2)}</pre>
|
|
228
|
+
</div>
|
|
229
|
+
{/if}
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<!-- Footer -->
|
|
233
|
+
<div class="flowdrop-config-sidebar__footer">
|
|
234
|
+
<button
|
|
235
|
+
class="flowdrop-config-sidebar__button flowdrop-config-sidebar__button--secondary"
|
|
236
|
+
onclick={onCancel}
|
|
237
|
+
>
|
|
238
|
+
Cancel
|
|
239
|
+
</button>
|
|
240
|
+
<button
|
|
241
|
+
class="flowdrop-config-sidebar__button flowdrop-config-sidebar__button--primary"
|
|
242
|
+
onclick={handleSave}
|
|
243
|
+
>
|
|
244
|
+
Save Changes
|
|
245
|
+
</button>
|
|
246
|
+
</div>
|
|
247
|
+
{:else}
|
|
248
|
+
<p class="flowdrop-config-sidebar__no-config">
|
|
249
|
+
No configuration options available for this node.
|
|
250
|
+
</p>
|
|
251
|
+
{/if}
|
|
437
252
|
|
|
438
253
|
<style>
|
|
439
|
-
.flowdrop-config-
|
|
440
|
-
padding: 1rem;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
.flowdrop-form {
|
|
254
|
+
.flowdrop-config-sidebar__form {
|
|
444
255
|
display: flex;
|
|
445
256
|
flex-direction: column;
|
|
446
257
|
gap: 1rem;
|
|
447
258
|
}
|
|
448
259
|
|
|
449
|
-
.flowdrop-
|
|
260
|
+
.flowdrop-config-sidebar__field {
|
|
450
261
|
display: flex;
|
|
451
262
|
flex-direction: column;
|
|
452
263
|
gap: 0.5rem;
|
|
453
264
|
}
|
|
454
265
|
|
|
455
|
-
.flowdrop-
|
|
266
|
+
.flowdrop-config-sidebar__field-label {
|
|
456
267
|
font-size: 0.875rem;
|
|
457
268
|
font-weight: 500;
|
|
458
269
|
color: #374151;
|
|
459
|
-
display: flex;
|
|
460
|
-
align-items: center;
|
|
461
|
-
gap: 0.25rem;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
.flowdrop-form-required {
|
|
465
|
-
color: #dc2626;
|
|
466
|
-
font-weight: 700;
|
|
467
270
|
}
|
|
468
271
|
|
|
469
|
-
.flowdrop-
|
|
470
|
-
.flowdrop-
|
|
471
|
-
|
|
472
|
-
padding: 0.5rem 0.75rem;
|
|
272
|
+
.flowdrop-config-sidebar__input,
|
|
273
|
+
.flowdrop-config-sidebar__select {
|
|
274
|
+
padding: 0.5rem;
|
|
473
275
|
border: 1px solid #d1d5db;
|
|
474
276
|
border-radius: 0.375rem;
|
|
475
277
|
font-size: 0.875rem;
|
|
476
|
-
|
|
477
|
-
|
|
278
|
+
transition:
|
|
279
|
+
border-color 0.2s,
|
|
280
|
+
box-shadow 0.2s;
|
|
478
281
|
}
|
|
479
282
|
|
|
480
|
-
.flowdrop-
|
|
481
|
-
.flowdrop-
|
|
482
|
-
.flowdrop-form-select:focus {
|
|
283
|
+
.flowdrop-config-sidebar__input:focus,
|
|
284
|
+
.flowdrop-config-sidebar__select:focus {
|
|
483
285
|
outline: none;
|
|
484
286
|
border-color: #3b82f6;
|
|
485
287
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
486
288
|
}
|
|
487
289
|
|
|
488
|
-
.flowdrop-
|
|
489
|
-
.flowdrop-form-textarea--error,
|
|
490
|
-
.flowdrop-form-select--error {
|
|
491
|
-
border-color: #dc2626;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
.flowdrop-form-input--error:focus,
|
|
495
|
-
.flowdrop-form-textarea--error:focus,
|
|
496
|
-
.flowdrop-form-select--error:focus {
|
|
497
|
-
border-color: #dc2626;
|
|
498
|
-
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
.flowdrop-form-textarea {
|
|
502
|
-
resize: vertical;
|
|
503
|
-
min-height: 4rem;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
.flowdrop-form-checkbox-group {
|
|
290
|
+
.flowdrop-config-sidebar__checkbox-group {
|
|
507
291
|
display: flex;
|
|
508
292
|
flex-direction: column;
|
|
509
293
|
gap: 0.5rem;
|
|
510
294
|
}
|
|
511
295
|
|
|
512
|
-
.flowdrop-
|
|
296
|
+
.flowdrop-config-sidebar__checkbox-item {
|
|
513
297
|
display: flex;
|
|
514
298
|
align-items: center;
|
|
515
299
|
gap: 0.5rem;
|
|
516
300
|
cursor: pointer;
|
|
517
301
|
}
|
|
518
302
|
|
|
519
|
-
.flowdrop-
|
|
303
|
+
.flowdrop-config-sidebar__checkbox {
|
|
520
304
|
width: 1rem;
|
|
521
305
|
height: 1rem;
|
|
522
306
|
accent-color: #3b82f6;
|
|
307
|
+
cursor: pointer;
|
|
523
308
|
}
|
|
524
309
|
|
|
525
|
-
.flowdrop-
|
|
310
|
+
.flowdrop-config-sidebar__checkbox-label {
|
|
526
311
|
font-size: 0.875rem;
|
|
527
312
|
color: #374151;
|
|
313
|
+
cursor: pointer;
|
|
528
314
|
}
|
|
529
315
|
|
|
530
|
-
.flowdrop-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
316
|
+
.flowdrop-config-sidebar__textarea {
|
|
317
|
+
width: 100%;
|
|
318
|
+
padding: 0.5rem 0.75rem;
|
|
319
|
+
border: 1px solid #d1d5db;
|
|
320
|
+
border-radius: 0.375rem;
|
|
321
|
+
font-size: 0.875rem;
|
|
322
|
+
background-color: #ffffff;
|
|
323
|
+
transition: all 0.2s ease-in-out;
|
|
324
|
+
resize: vertical;
|
|
325
|
+
min-height: 4rem;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.flowdrop-config-sidebar__textarea:focus {
|
|
329
|
+
outline: none;
|
|
330
|
+
border-color: #3b82f6;
|
|
331
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
534
332
|
}
|
|
535
333
|
|
|
536
|
-
.flowdrop-
|
|
334
|
+
.flowdrop-config-sidebar__field-description {
|
|
335
|
+
margin: 0;
|
|
537
336
|
font-size: 0.75rem;
|
|
538
337
|
color: #6b7280;
|
|
539
|
-
|
|
338
|
+
line-height: 1.4;
|
|
540
339
|
}
|
|
541
340
|
|
|
542
|
-
.flowdrop-
|
|
341
|
+
.flowdrop-config-sidebar__no-config {
|
|
543
342
|
text-align: center;
|
|
544
|
-
padding: 2rem;
|
|
545
343
|
color: #6b7280;
|
|
344
|
+
font-style: italic;
|
|
345
|
+
padding: 2rem 1rem;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.flowdrop-config-sidebar__footer {
|
|
349
|
+
padding: 1rem;
|
|
350
|
+
border-top: 1px solid #e5e7eb;
|
|
351
|
+
background-color: #f9fafb;
|
|
352
|
+
display: flex;
|
|
353
|
+
gap: 0.75rem;
|
|
354
|
+
justify-content: flex-end;
|
|
355
|
+
height: 40px;
|
|
356
|
+
align-items: center;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.flowdrop-config-sidebar__button {
|
|
360
|
+
padding: 0.5rem 1rem;
|
|
361
|
+
border-radius: 0.375rem;
|
|
362
|
+
font-size: 0.875rem;
|
|
363
|
+
font-weight: 500;
|
|
364
|
+
cursor: pointer;
|
|
365
|
+
transition: all 0.2s;
|
|
366
|
+
border: 1px solid transparent;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.flowdrop-config-sidebar__button--secondary {
|
|
370
|
+
background-color: #ffffff;
|
|
371
|
+
border-color: #d1d5db;
|
|
372
|
+
color: #374151;
|
|
546
373
|
}
|
|
547
374
|
|
|
548
|
-
.flowdrop-
|
|
549
|
-
.flowdrop-form-textarea:disabled,
|
|
550
|
-
.flowdrop-form-select:disabled {
|
|
375
|
+
.flowdrop-config-sidebar__button--secondary:hover {
|
|
551
376
|
background-color: #f9fafb;
|
|
552
|
-
color: #9ca3af;
|
|
553
|
-
|
|
377
|
+
border-color: #9ca3af;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.flowdrop-config-sidebar__button--primary {
|
|
381
|
+
background-color: #3b82f6;
|
|
382
|
+
color: #ffffff;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.flowdrop-config-sidebar__button--primary:hover {
|
|
386
|
+
background-color: #2563eb;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.flowdrop-config-sidebar__debug {
|
|
390
|
+
background-color: #f3f4f6;
|
|
391
|
+
border: 1px solid #d1d5db;
|
|
392
|
+
border-radius: 0.375rem;
|
|
393
|
+
padding: 1rem;
|
|
394
|
+
margin: 1rem 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.flowdrop-config-sidebar__debug pre {
|
|
398
|
+
background-color: #ffffff;
|
|
399
|
+
border: 1px solid #e5e7eb;
|
|
400
|
+
border-radius: 0.25rem;
|
|
401
|
+
padding: 0.75rem;
|
|
402
|
+
font-size: 0.75rem;
|
|
403
|
+
overflow-x: auto;
|
|
404
|
+
margin: 0.5rem 0 0 0;
|
|
554
405
|
}
|
|
555
406
|
</style>
|