@d34dman/flowdrop 0.0.25 → 0.0.27
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/README.md +52 -62
- package/dist/components/App.svelte +12 -2
- package/dist/components/ConfigForm.svelte +500 -9
- package/dist/components/ConfigForm.svelte.d.ts +2 -0
- package/dist/components/ConfigModal.svelte +4 -70
- package/dist/components/ConfigPanel.svelte +4 -9
- package/dist/components/EdgeRefresher.svelte +41 -0
- package/dist/components/EdgeRefresher.svelte.d.ts +9 -0
- package/dist/components/ReadOnlyDetails.svelte +3 -1
- package/dist/components/UniversalNode.svelte +6 -3
- package/dist/components/WorkflowEditor.svelte +30 -0
- package/dist/components/WorkflowEditor.svelte.d.ts +3 -1
- package/dist/components/form/FormCheckboxGroup.svelte +2 -9
- package/dist/components/form/FormField.svelte +1 -12
- package/dist/components/form/FormFieldWrapper.svelte +2 -10
- package/dist/components/form/FormFieldWrapper.svelte.d.ts +1 -1
- package/dist/components/form/FormMarkdownEditor.svelte +0 -2
- package/dist/components/form/FormNumberField.svelte +5 -6
- package/dist/components/form/FormRangeField.svelte +3 -13
- package/dist/components/form/FormSelect.svelte +4 -5
- package/dist/components/form/FormSelect.svelte.d.ts +1 -1
- package/dist/components/form/FormTextField.svelte +3 -4
- package/dist/components/form/FormTextarea.svelte +3 -4
- package/dist/components/form/FormToggle.svelte +2 -3
- package/dist/components/form/index.d.ts +14 -14
- package/dist/components/form/index.js +14 -14
- package/dist/components/form/types.d.ts +2 -2
- package/dist/components/form/types.js +1 -1
- package/dist/components/nodes/NotesNode.svelte +39 -45
- package/dist/components/nodes/NotesNode.svelte.d.ts +1 -1
- package/dist/components/nodes/SimpleNode.svelte +92 -142
- package/dist/components/nodes/SquareNode.svelte +75 -58
- package/dist/components/nodes/WorkflowNode.svelte +1 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/services/dynamicSchemaService.d.ts +108 -0
- package/dist/services/dynamicSchemaService.js +445 -0
- package/dist/styles/base.css +1 -1
- package/dist/types/index.d.ts +213 -0
- package/package.json +163 -155
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
- Dynamic form generation from JSON Schema using modular form components
|
|
9
9
|
- UI Extensions support for display settings (e.g., hide unconnected handles)
|
|
10
10
|
- Extensible architecture for complex schema types (array, object)
|
|
11
|
+
- Admin/Edit support for external configuration links and dynamic schema fetching
|
|
11
12
|
|
|
12
13
|
Accessibility features:
|
|
13
14
|
- Proper label associations with for/id attributes
|
|
@@ -18,9 +19,21 @@
|
|
|
18
19
|
|
|
19
20
|
<script lang="ts">
|
|
20
21
|
import Icon from '@iconify/svelte';
|
|
21
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
ConfigSchema,
|
|
24
|
+
WorkflowNode,
|
|
25
|
+
NodeUIExtensions,
|
|
26
|
+
ConfigEditOptions
|
|
27
|
+
} from '../types/index.js';
|
|
22
28
|
import { FormField, FormFieldWrapper, FormToggle } from './form/index.js';
|
|
23
29
|
import type { FieldSchema } from './form/index.js';
|
|
30
|
+
import {
|
|
31
|
+
getEffectiveConfigEditOptions,
|
|
32
|
+
fetchDynamicSchema,
|
|
33
|
+
resolveExternalEditUrl,
|
|
34
|
+
invalidateSchemaCache,
|
|
35
|
+
type DynamicSchemaResult
|
|
36
|
+
} from '../services/dynamicSchemaService.js';
|
|
24
37
|
|
|
25
38
|
interface Props {
|
|
26
39
|
/** Optional workflow node (if provided, schema and values are derived from it) */
|
|
@@ -31,20 +44,81 @@
|
|
|
31
44
|
values?: Record<string, unknown>;
|
|
32
45
|
/** Whether to show UI extension settings section */
|
|
33
46
|
showUIExtensions?: boolean;
|
|
47
|
+
/** Optional workflow ID for context in external links */
|
|
48
|
+
workflowId?: string;
|
|
34
49
|
/** Callback when form is saved (includes both config and extensions if enabled) */
|
|
35
50
|
onSave: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
|
|
36
51
|
/** Callback when form is cancelled */
|
|
37
52
|
onCancel: () => void;
|
|
38
53
|
}
|
|
39
54
|
|
|
40
|
-
let {
|
|
55
|
+
let {
|
|
56
|
+
node,
|
|
57
|
+
schema,
|
|
58
|
+
values,
|
|
59
|
+
showUIExtensions = true,
|
|
60
|
+
workflowId,
|
|
61
|
+
onSave,
|
|
62
|
+
onCancel
|
|
63
|
+
}: Props = $props();
|
|
41
64
|
|
|
42
65
|
/**
|
|
43
|
-
*
|
|
66
|
+
* State for dynamic schema loading
|
|
44
67
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
);
|
|
68
|
+
let dynamicSchemaLoading = $state(false);
|
|
69
|
+
let dynamicSchemaError = $state<string | null>(null);
|
|
70
|
+
let fetchedDynamicSchema = $state<ConfigSchema | null>(null);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the admin edit configuration for the node
|
|
74
|
+
*/
|
|
75
|
+
const configEditOptions = $derived.by<ConfigEditOptions | undefined>(() => {
|
|
76
|
+
if (!node) return undefined;
|
|
77
|
+
return getEffectiveConfigEditOptions(node);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Determine if we should show the external edit link
|
|
82
|
+
*/
|
|
83
|
+
const showExternalEditLink = $derived.by(() => {
|
|
84
|
+
if (!configEditOptions?.externalEditLink) return false;
|
|
85
|
+
// Show if no dynamic schema, or if both exist but preferDynamicSchema is false
|
|
86
|
+
if (!configEditOptions.dynamicSchema) return true;
|
|
87
|
+
return !configEditOptions.preferDynamicSchema;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Determine if we should use/fetch dynamic schema
|
|
92
|
+
*/
|
|
93
|
+
const useDynamicSchema = $derived.by(() => {
|
|
94
|
+
if (!configEditOptions?.dynamicSchema) return false;
|
|
95
|
+
// Use if no external link, or if both exist and preferDynamicSchema is true
|
|
96
|
+
if (!configEditOptions.externalEditLink) return true;
|
|
97
|
+
return configEditOptions.preferDynamicSchema === true;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get the configuration schema from node metadata, direct prop, or fetched dynamic schema
|
|
102
|
+
* Priority: fetchedDynamicSchema > direct schema prop > node metadata configSchema
|
|
103
|
+
*/
|
|
104
|
+
const configSchema = $derived.by<ConfigSchema | undefined>(() => {
|
|
105
|
+
// If we have a fetched dynamic schema, use it
|
|
106
|
+
if (fetchedDynamicSchema) {
|
|
107
|
+
return fetchedDynamicSchema;
|
|
108
|
+
}
|
|
109
|
+
// Otherwise use the direct prop or node metadata
|
|
110
|
+
return schema ?? (node?.data.metadata?.configSchema as ConfigSchema | undefined);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if the node has no static schema and needs dynamic loading
|
|
115
|
+
*/
|
|
116
|
+
const needsDynamicSchemaLoad = $derived.by(() => {
|
|
117
|
+
if (!node) return false;
|
|
118
|
+
const staticSchema = schema ?? node.data.metadata?.configSchema;
|
|
119
|
+
// Need to load if: no static schema AND dynamic schema is configured
|
|
120
|
+
return !staticSchema && useDynamicSchema && !fetchedDynamicSchema && !dynamicSchemaLoading;
|
|
121
|
+
});
|
|
48
122
|
|
|
49
123
|
/**
|
|
50
124
|
* Get the current configuration from node or direct prop
|
|
@@ -74,6 +148,85 @@
|
|
|
74
148
|
return { ...typeDefaults, ...instanceOverrides };
|
|
75
149
|
});
|
|
76
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Fetch dynamic schema when needed
|
|
153
|
+
*/
|
|
154
|
+
async function loadDynamicSchema(): Promise<void> {
|
|
155
|
+
if (!node || !configEditOptions?.dynamicSchema) return;
|
|
156
|
+
|
|
157
|
+
dynamicSchemaLoading = true;
|
|
158
|
+
dynamicSchemaError = null;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const result: DynamicSchemaResult = await fetchDynamicSchema(
|
|
162
|
+
configEditOptions.dynamicSchema,
|
|
163
|
+
node,
|
|
164
|
+
workflowId
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
if (result.success && result.schema) {
|
|
168
|
+
fetchedDynamicSchema = result.schema;
|
|
169
|
+
} else {
|
|
170
|
+
dynamicSchemaError =
|
|
171
|
+
result.error ?? configEditOptions.errorMessage ?? 'Failed to load configuration schema';
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
dynamicSchemaError =
|
|
175
|
+
err instanceof Error
|
|
176
|
+
? err.message
|
|
177
|
+
: (configEditOptions.errorMessage ?? 'Failed to load configuration schema');
|
|
178
|
+
} finally {
|
|
179
|
+
dynamicSchemaLoading = false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Refresh the dynamic schema (invalidate cache and reload)
|
|
185
|
+
*/
|
|
186
|
+
async function refreshDynamicSchema(): Promise<void> {
|
|
187
|
+
if (!node || !configEditOptions?.dynamicSchema) return;
|
|
188
|
+
|
|
189
|
+
// Invalidate the cache first
|
|
190
|
+
invalidateSchemaCache(node, configEditOptions.dynamicSchema);
|
|
191
|
+
fetchedDynamicSchema = null;
|
|
192
|
+
|
|
193
|
+
// Reload the schema
|
|
194
|
+
await loadDynamicSchema();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get the resolved external edit URL
|
|
199
|
+
*/
|
|
200
|
+
function getExternalEditUrl(): string {
|
|
201
|
+
if (!node || !configEditOptions?.externalEditLink) return '#';
|
|
202
|
+
return resolveExternalEditUrl(configEditOptions.externalEditLink, node, workflowId);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Handle opening external edit link
|
|
207
|
+
*/
|
|
208
|
+
function handleExternalEditClick(): void {
|
|
209
|
+
if (!node || !configEditOptions?.externalEditLink) return;
|
|
210
|
+
|
|
211
|
+
const url = getExternalEditUrl();
|
|
212
|
+
const openInNewTab = configEditOptions.externalEditLink.openInNewTab !== false;
|
|
213
|
+
|
|
214
|
+
if (openInNewTab) {
|
|
215
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
216
|
+
} else {
|
|
217
|
+
window.location.href = url;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Auto-load dynamic schema on mount if needed
|
|
223
|
+
*/
|
|
224
|
+
$effect(() => {
|
|
225
|
+
if (needsDynamicSchemaLoad) {
|
|
226
|
+
loadDynamicSchema();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
77
230
|
/**
|
|
78
231
|
* Initialize config values when node/schema changes
|
|
79
232
|
*/
|
|
@@ -131,7 +284,10 @@
|
|
|
131
284
|
if (inputEl.id && !inputEl.id.startsWith('ext-')) {
|
|
132
285
|
if (inputEl instanceof HTMLInputElement && inputEl.type === 'checkbox') {
|
|
133
286
|
updatedConfig[inputEl.id] = inputEl.checked;
|
|
134
|
-
} else if (
|
|
287
|
+
} else if (
|
|
288
|
+
inputEl instanceof HTMLInputElement &&
|
|
289
|
+
(inputEl.type === 'number' || inputEl.type === 'range')
|
|
290
|
+
) {
|
|
135
291
|
updatedConfig[inputEl.id] = inputEl.value ? Number(inputEl.value) : inputEl.value;
|
|
136
292
|
} else if (inputEl instanceof HTMLInputElement && inputEl.type === 'hidden') {
|
|
137
293
|
// Parse hidden field values that might be JSON
|
|
@@ -176,7 +332,74 @@
|
|
|
176
332
|
}
|
|
177
333
|
</script>
|
|
178
334
|
|
|
179
|
-
|
|
335
|
+
<!-- External Edit Link Section (shown when configured and preferred) -->
|
|
336
|
+
{#if showExternalEditLink && configEditOptions?.externalEditLink}
|
|
337
|
+
<div class="config-form__admin-edit">
|
|
338
|
+
<div class="config-form__admin-edit-header">
|
|
339
|
+
<Icon icon="heroicons:arrow-top-right-on-square" />
|
|
340
|
+
<span>External Configuration</span>
|
|
341
|
+
</div>
|
|
342
|
+
<div class="config-form__admin-edit-content">
|
|
343
|
+
<p class="config-form__admin-edit-description">
|
|
344
|
+
{configEditOptions.externalEditLink.description ??
|
|
345
|
+
'This node requires external configuration. Click the button below to open the configuration panel.'}
|
|
346
|
+
</p>
|
|
347
|
+
<button
|
|
348
|
+
type="button"
|
|
349
|
+
class="config-form__button config-form__button--external"
|
|
350
|
+
onclick={handleExternalEditClick}
|
|
351
|
+
>
|
|
352
|
+
<Icon
|
|
353
|
+
icon={configEditOptions.externalEditLink.icon ?? 'heroicons:arrow-top-right-on-square'}
|
|
354
|
+
/>
|
|
355
|
+
<span>{configEditOptions.externalEditLink.label ?? 'Configure Externally'}</span>
|
|
356
|
+
</button>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
{/if}
|
|
360
|
+
|
|
361
|
+
<!-- Dynamic Schema Loading State -->
|
|
362
|
+
{#if dynamicSchemaLoading}
|
|
363
|
+
<div class="config-form__loading">
|
|
364
|
+
<div class="config-form__loading-spinner"></div>
|
|
365
|
+
<p class="config-form__loading-text">
|
|
366
|
+
{configEditOptions?.loadingMessage ?? 'Loading configuration options...'}
|
|
367
|
+
</p>
|
|
368
|
+
</div>
|
|
369
|
+
{:else if dynamicSchemaError}
|
|
370
|
+
<div class="config-form__error">
|
|
371
|
+
<div class="config-form__error-header">
|
|
372
|
+
<Icon icon="heroicons:exclamation-triangle" />
|
|
373
|
+
<span>Configuration Error</span>
|
|
374
|
+
</div>
|
|
375
|
+
<div class="config-form__error-content">
|
|
376
|
+
<p class="config-form__error-message">{dynamicSchemaError}</p>
|
|
377
|
+
<div class="config-form__error-actions">
|
|
378
|
+
<button
|
|
379
|
+
type="button"
|
|
380
|
+
class="config-form__button config-form__button--secondary"
|
|
381
|
+
onclick={refreshDynamicSchema}
|
|
382
|
+
>
|
|
383
|
+
<Icon icon="heroicons:arrow-path" />
|
|
384
|
+
<span>Retry</span>
|
|
385
|
+
</button>
|
|
386
|
+
{#if configEditOptions?.externalEditLink}
|
|
387
|
+
<button
|
|
388
|
+
type="button"
|
|
389
|
+
class="config-form__button config-form__button--external"
|
|
390
|
+
onclick={handleExternalEditClick}
|
|
391
|
+
>
|
|
392
|
+
<Icon
|
|
393
|
+
icon={configEditOptions.externalEditLink.icon ??
|
|
394
|
+
'heroicons:arrow-top-right-on-square'}
|
|
395
|
+
/>
|
|
396
|
+
<span>{configEditOptions.externalEditLink.label ?? 'Use External Editor'}</span>
|
|
397
|
+
</button>
|
|
398
|
+
{/if}
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
{:else if configSchema}
|
|
180
403
|
<form
|
|
181
404
|
class="config-form"
|
|
182
405
|
onsubmit={(e) => {
|
|
@@ -184,6 +407,35 @@
|
|
|
184
407
|
handleSave();
|
|
185
408
|
}}
|
|
186
409
|
>
|
|
410
|
+
<!-- Dynamic Schema Refresh Button -->
|
|
411
|
+
{#if fetchedDynamicSchema && configEditOptions?.showRefreshButton !== false}
|
|
412
|
+
<div class="config-form__schema-actions">
|
|
413
|
+
<button
|
|
414
|
+
type="button"
|
|
415
|
+
class="config-form__schema-refresh"
|
|
416
|
+
onclick={refreshDynamicSchema}
|
|
417
|
+
title="Refresh configuration schema"
|
|
418
|
+
>
|
|
419
|
+
<Icon icon="heroicons:arrow-path" />
|
|
420
|
+
<span>Refresh Schema</span>
|
|
421
|
+
</button>
|
|
422
|
+
{#if configEditOptions?.externalEditLink}
|
|
423
|
+
<button
|
|
424
|
+
type="button"
|
|
425
|
+
class="config-form__schema-external"
|
|
426
|
+
onclick={handleExternalEditClick}
|
|
427
|
+
title={configEditOptions.externalEditLink.description ?? 'Open external editor'}
|
|
428
|
+
>
|
|
429
|
+
<Icon
|
|
430
|
+
icon={configEditOptions.externalEditLink.icon ??
|
|
431
|
+
'heroicons:arrow-top-right-on-square'}
|
|
432
|
+
/>
|
|
433
|
+
<span>{configEditOptions.externalEditLink.label ?? 'External Editor'}</span>
|
|
434
|
+
</button>
|
|
435
|
+
{/if}
|
|
436
|
+
</div>
|
|
437
|
+
{/if}
|
|
438
|
+
|
|
187
439
|
{#if configSchema.properties}
|
|
188
440
|
<div class="config-form__fields">
|
|
189
441
|
{#each Object.entries(configSchema.properties) as [key, field], index (key)}
|
|
@@ -256,12 +508,24 @@
|
|
|
256
508
|
</button>
|
|
257
509
|
</div>
|
|
258
510
|
</form>
|
|
259
|
-
{:else}
|
|
511
|
+
{:else if !dynamicSchemaLoading && !showExternalEditLink}
|
|
260
512
|
<div class="config-form__empty">
|
|
261
513
|
<div class="config-form__empty-icon">
|
|
262
514
|
<Icon icon="heroicons:cog-6-tooth" />
|
|
263
515
|
</div>
|
|
264
516
|
<p class="config-form__empty-text">No configuration options available for this node.</p>
|
|
517
|
+
{#if configEditOptions?.externalEditLink}
|
|
518
|
+
<button
|
|
519
|
+
type="button"
|
|
520
|
+
class="config-form__button config-form__button--external config-form__empty-button"
|
|
521
|
+
onclick={handleExternalEditClick}
|
|
522
|
+
>
|
|
523
|
+
<Icon
|
|
524
|
+
icon={configEditOptions.externalEditLink.icon ?? 'heroicons:arrow-top-right-on-square'}
|
|
525
|
+
/>
|
|
526
|
+
<span>{configEditOptions.externalEditLink.label ?? 'Configure Externally'}</span>
|
|
527
|
+
</button>
|
|
528
|
+
{/if}
|
|
265
529
|
</div>
|
|
266
530
|
{/if}
|
|
267
531
|
|
|
@@ -479,4 +743,231 @@
|
|
|
479
743
|
font-style: italic;
|
|
480
744
|
line-height: 1.5;
|
|
481
745
|
}
|
|
746
|
+
|
|
747
|
+
.config-form__empty-button {
|
|
748
|
+
margin-top: 1rem;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/* ============================================
|
|
752
|
+
ADMIN/EDIT SECTION - External Configuration
|
|
753
|
+
============================================ */
|
|
754
|
+
|
|
755
|
+
.config-form__admin-edit {
|
|
756
|
+
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
|
757
|
+
border: 1px solid var(--color-ref-blue-200, #bfdbfe);
|
|
758
|
+
border-radius: 0.625rem;
|
|
759
|
+
overflow: hidden;
|
|
760
|
+
margin-bottom: 1rem;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
.config-form__admin-edit-header {
|
|
764
|
+
display: flex;
|
|
765
|
+
align-items: center;
|
|
766
|
+
gap: 0.5rem;
|
|
767
|
+
padding: 0.75rem 1rem;
|
|
768
|
+
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
|
|
769
|
+
border-bottom: 1px solid var(--color-ref-blue-200, #bfdbfe);
|
|
770
|
+
font-size: 0.8125rem;
|
|
771
|
+
font-weight: 600;
|
|
772
|
+
color: var(--color-ref-blue-800, #1e40af);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.config-form__admin-edit-header :global(svg) {
|
|
776
|
+
width: 1rem;
|
|
777
|
+
height: 1rem;
|
|
778
|
+
color: var(--color-ref-blue-600, #2563eb);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
.config-form__admin-edit-content {
|
|
782
|
+
padding: 1rem;
|
|
783
|
+
display: flex;
|
|
784
|
+
flex-direction: column;
|
|
785
|
+
gap: 0.75rem;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.config-form__admin-edit-description {
|
|
789
|
+
margin: 0;
|
|
790
|
+
font-size: 0.8125rem;
|
|
791
|
+
color: var(--color-ref-blue-700, #1d4ed8);
|
|
792
|
+
line-height: 1.5;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/* ============================================
|
|
796
|
+
LOADING STATE
|
|
797
|
+
============================================ */
|
|
798
|
+
|
|
799
|
+
.config-form__loading {
|
|
800
|
+
display: flex;
|
|
801
|
+
flex-direction: column;
|
|
802
|
+
align-items: center;
|
|
803
|
+
justify-content: center;
|
|
804
|
+
padding: 3rem 1.5rem;
|
|
805
|
+
gap: 1rem;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
.config-form__loading-spinner {
|
|
809
|
+
width: 2.5rem;
|
|
810
|
+
height: 2.5rem;
|
|
811
|
+
border: 3px solid var(--color-ref-blue-100, #dbeafe);
|
|
812
|
+
border-top-color: var(--color-ref-blue-500, #3b82f6);
|
|
813
|
+
border-radius: 50%;
|
|
814
|
+
animation: config-form-spin 0.8s linear infinite;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
@keyframes config-form-spin {
|
|
818
|
+
to {
|
|
819
|
+
transform: rotate(360deg);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.config-form__loading-text {
|
|
824
|
+
margin: 0;
|
|
825
|
+
font-size: 0.875rem;
|
|
826
|
+
color: var(--color-ref-gray-600, #4b5563);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/* ============================================
|
|
830
|
+
ERROR STATE
|
|
831
|
+
============================================ */
|
|
832
|
+
|
|
833
|
+
.config-form__error {
|
|
834
|
+
background-color: var(--color-ref-red-50, #fef2f2);
|
|
835
|
+
border: 1px solid var(--color-ref-red-200, #fecaca);
|
|
836
|
+
border-radius: 0.5rem;
|
|
837
|
+
overflow: hidden;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.config-form__error-header {
|
|
841
|
+
display: flex;
|
|
842
|
+
align-items: center;
|
|
843
|
+
gap: 0.5rem;
|
|
844
|
+
padding: 0.75rem 1rem;
|
|
845
|
+
background-color: var(--color-ref-red-100, #fee2e2);
|
|
846
|
+
border-bottom: 1px solid var(--color-ref-red-200, #fecaca);
|
|
847
|
+
font-size: 0.8125rem;
|
|
848
|
+
font-weight: 600;
|
|
849
|
+
color: var(--color-ref-red-800, #991b1b);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.config-form__error-header :global(svg) {
|
|
853
|
+
width: 1rem;
|
|
854
|
+
height: 1rem;
|
|
855
|
+
color: var(--color-ref-red-600, #dc2626);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.config-form__error-content {
|
|
859
|
+
padding: 1rem;
|
|
860
|
+
display: flex;
|
|
861
|
+
flex-direction: column;
|
|
862
|
+
gap: 0.75rem;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
.config-form__error-message {
|
|
866
|
+
margin: 0;
|
|
867
|
+
font-size: 0.8125rem;
|
|
868
|
+
color: var(--color-ref-red-700, #b91c1c);
|
|
869
|
+
line-height: 1.5;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.config-form__error-actions {
|
|
873
|
+
display: flex;
|
|
874
|
+
gap: 0.5rem;
|
|
875
|
+
flex-wrap: wrap;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/* ============================================
|
|
879
|
+
SCHEMA ACTIONS (Refresh, External Editor)
|
|
880
|
+
============================================ */
|
|
881
|
+
|
|
882
|
+
.config-form__schema-actions {
|
|
883
|
+
display: flex;
|
|
884
|
+
gap: 0.5rem;
|
|
885
|
+
margin-bottom: 1rem;
|
|
886
|
+
padding-bottom: 0.75rem;
|
|
887
|
+
border-bottom: 1px solid var(--color-ref-gray-100, #f3f4f6);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.config-form__schema-refresh,
|
|
891
|
+
.config-form__schema-external {
|
|
892
|
+
display: inline-flex;
|
|
893
|
+
align-items: center;
|
|
894
|
+
gap: 0.375rem;
|
|
895
|
+
padding: 0.375rem 0.625rem;
|
|
896
|
+
font-size: 0.75rem;
|
|
897
|
+
font-weight: 500;
|
|
898
|
+
font-family: inherit;
|
|
899
|
+
border-radius: 0.375rem;
|
|
900
|
+
cursor: pointer;
|
|
901
|
+
transition: all 0.15s ease;
|
|
902
|
+
border: 1px solid transparent;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
.config-form__schema-refresh {
|
|
906
|
+
background-color: var(--color-ref-gray-50, #f9fafb);
|
|
907
|
+
border-color: var(--color-ref-gray-200, #e5e7eb);
|
|
908
|
+
color: var(--color-ref-gray-600, #4b5563);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.config-form__schema-refresh:hover {
|
|
912
|
+
background-color: var(--color-ref-gray-100, #f3f4f6);
|
|
913
|
+
border-color: var(--color-ref-gray-300, #d1d5db);
|
|
914
|
+
color: var(--color-ref-gray-700, #374151);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
.config-form__schema-refresh :global(svg),
|
|
918
|
+
.config-form__schema-external :global(svg) {
|
|
919
|
+
width: 0.875rem;
|
|
920
|
+
height: 0.875rem;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.config-form__schema-external {
|
|
924
|
+
background-color: var(--color-ref-blue-50, #eff6ff);
|
|
925
|
+
border-color: var(--color-ref-blue-200, #bfdbfe);
|
|
926
|
+
color: var(--color-ref-blue-700, #1d4ed8);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
.config-form__schema-external:hover {
|
|
930
|
+
background-color: var(--color-ref-blue-100, #dbeafe);
|
|
931
|
+
border-color: var(--color-ref-blue-300, #93c5fd);
|
|
932
|
+
color: var(--color-ref-blue-800, #1e40af);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/* ============================================
|
|
936
|
+
EXTERNAL BUTTON STYLE
|
|
937
|
+
============================================ */
|
|
938
|
+
|
|
939
|
+
.config-form__button--external {
|
|
940
|
+
background: linear-gradient(
|
|
941
|
+
135deg,
|
|
942
|
+
var(--color-ref-indigo-500, #6366f1) 0%,
|
|
943
|
+
var(--color-ref-blue-600, #2563eb) 100%
|
|
944
|
+
);
|
|
945
|
+
color: #ffffff;
|
|
946
|
+
box-shadow:
|
|
947
|
+
0 1px 3px rgba(99, 102, 241, 0.3),
|
|
948
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
.config-form__button--external:hover {
|
|
952
|
+
background: linear-gradient(
|
|
953
|
+
135deg,
|
|
954
|
+
var(--color-ref-indigo-600, #4f46e5) 0%,
|
|
955
|
+
var(--color-ref-blue-700, #1d4ed8) 100%
|
|
956
|
+
);
|
|
957
|
+
box-shadow:
|
|
958
|
+
0 4px 12px rgba(99, 102, 241, 0.35),
|
|
959
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
|
960
|
+
transform: translateY(-1px);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
.config-form__button--external:active {
|
|
964
|
+
transform: translateY(0);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
.config-form__button--external:focus-visible {
|
|
968
|
+
outline: none;
|
|
969
|
+
box-shadow:
|
|
970
|
+
0 0 0 3px rgba(99, 102, 241, 0.4),
|
|
971
|
+
0 4px 12px rgba(99, 102, 241, 0.35);
|
|
972
|
+
}
|
|
482
973
|
</style>
|
|
@@ -8,6 +8,8 @@ interface Props {
|
|
|
8
8
|
values?: Record<string, unknown>;
|
|
9
9
|
/** Whether to show UI extension settings section */
|
|
10
10
|
showUIExtensions?: boolean;
|
|
11
|
+
/** Optional workflow ID for context in external links */
|
|
12
|
+
workflowId?: string;
|
|
11
13
|
/** Callback when form is saved (includes both config and extensions if enabled) */
|
|
12
14
|
onSave: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
|
|
13
15
|
/** Callback when form is cancelled */
|
|
@@ -19,10 +19,6 @@
|
|
|
19
19
|
cancel: void;
|
|
20
20
|
}>();
|
|
21
21
|
|
|
22
|
-
function handleSave() {
|
|
23
|
-
dispatch('save', { values: localConfigValues });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
22
|
function handleCancel() {
|
|
27
23
|
dispatch('cancel');
|
|
28
24
|
}
|
|
@@ -79,29 +75,13 @@
|
|
|
79
75
|
<ConfigForm
|
|
80
76
|
schema={props.configSchema}
|
|
81
77
|
values={localConfigValues}
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
showUIExtensions={false}
|
|
79
|
+
onSave={(config) => {
|
|
80
|
+
dispatch('save', { values: config });
|
|
84
81
|
}}
|
|
82
|
+
onCancel={handleCancel}
|
|
85
83
|
/>
|
|
86
84
|
</div>
|
|
87
|
-
|
|
88
|
-
<!-- Modal Footer -->
|
|
89
|
-
<div class="config-modal__footer">
|
|
90
|
-
<button
|
|
91
|
-
type="button"
|
|
92
|
-
class="config-modal__btn config-modal__btn--secondary"
|
|
93
|
-
onclick={handleCancel}
|
|
94
|
-
>
|
|
95
|
-
Cancel
|
|
96
|
-
</button>
|
|
97
|
-
<button
|
|
98
|
-
type="button"
|
|
99
|
-
class="config-modal__btn config-modal__btn--primary"
|
|
100
|
-
onclick={handleSave}
|
|
101
|
-
>
|
|
102
|
-
Save Configuration
|
|
103
|
-
</button>
|
|
104
|
-
</div>
|
|
105
85
|
</div>
|
|
106
86
|
</div>
|
|
107
87
|
{/if}
|
|
@@ -179,48 +159,6 @@
|
|
|
179
159
|
min-height: 0;
|
|
180
160
|
}
|
|
181
161
|
|
|
182
|
-
.config-modal__footer {
|
|
183
|
-
display: flex;
|
|
184
|
-
align-items: center;
|
|
185
|
-
justify-content: flex-end;
|
|
186
|
-
gap: 0.75rem;
|
|
187
|
-
padding: 1rem 1.5rem 1.5rem 1.5rem;
|
|
188
|
-
border-top: 1px solid #e5e7eb;
|
|
189
|
-
background-color: #f9fafb;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.config-modal__btn {
|
|
193
|
-
padding: 0.5rem 1rem;
|
|
194
|
-
border-radius: 0.375rem;
|
|
195
|
-
font-size: 0.875rem;
|
|
196
|
-
font-weight: 500;
|
|
197
|
-
cursor: pointer;
|
|
198
|
-
transition: all 0.2s;
|
|
199
|
-
border: 1px solid transparent;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
.config-modal__btn--primary {
|
|
203
|
-
background-color: #3b82f6;
|
|
204
|
-
color: white;
|
|
205
|
-
border-color: #3b82f6;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.config-modal__btn--primary:hover {
|
|
209
|
-
background-color: #2563eb;
|
|
210
|
-
border-color: #2563eb;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.config-modal__btn--secondary {
|
|
214
|
-
background-color: white;
|
|
215
|
-
color: #374151;
|
|
216
|
-
border-color: #d1d5db;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.config-modal__btn--secondary:hover {
|
|
220
|
-
background-color: #f9fafb;
|
|
221
|
-
border-color: #9ca3af;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
162
|
/* Responsive adjustments */
|
|
225
163
|
@media (max-width: 1024px) {
|
|
226
164
|
.config-modal {
|
|
@@ -242,10 +180,6 @@
|
|
|
242
180
|
.config-modal__content {
|
|
243
181
|
padding: 1rem;
|
|
244
182
|
}
|
|
245
|
-
|
|
246
|
-
.config-modal__footer {
|
|
247
|
-
padding: 0.75rem 1rem 1rem 1rem;
|
|
248
|
-
}
|
|
249
183
|
}
|
|
250
184
|
|
|
251
185
|
@media (max-width: 640px) {
|