@d34dman/flowdrop 0.0.21 → 0.0.23
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 +69 -260
- package/dist/components/ConfigForm.svelte +357 -267
- package/dist/components/ConfigForm.svelte.d.ts +12 -3
- package/dist/components/ConfigPanel.svelte +160 -0
- package/dist/components/ConfigPanel.svelte.d.ts +32 -0
- package/dist/components/ReadOnlyDetails.svelte +168 -0
- package/dist/components/ReadOnlyDetails.svelte.d.ts +25 -0
- package/dist/components/WorkflowEditor.svelte +1 -1
- package/dist/components/form/FormArray.svelte +1049 -0
- package/dist/components/form/FormArray.svelte.d.ts +22 -0
- package/dist/components/form/FormCheckboxGroup.svelte +152 -0
- package/dist/components/form/FormCheckboxGroup.svelte.d.ts +15 -0
- package/dist/components/form/FormField.svelte +279 -0
- package/dist/components/form/FormField.svelte.d.ts +18 -0
- package/dist/components/form/FormFieldWrapper.svelte +133 -0
- package/dist/components/form/FormFieldWrapper.svelte.d.ts +18 -0
- package/dist/components/form/FormNumberField.svelte +109 -0
- package/dist/components/form/FormNumberField.svelte.d.ts +23 -0
- package/dist/components/form/FormSelect.svelte +126 -0
- package/dist/components/form/FormSelect.svelte.d.ts +18 -0
- package/dist/components/form/FormTextField.svelte +88 -0
- package/dist/components/form/FormTextField.svelte.d.ts +17 -0
- package/dist/components/form/FormTextarea.svelte +94 -0
- package/dist/components/form/FormTextarea.svelte.d.ts +19 -0
- package/dist/components/form/FormToggle.svelte +123 -0
- package/dist/components/form/FormToggle.svelte.d.ts +17 -0
- package/dist/components/form/index.d.ts +41 -0
- package/dist/components/form/index.js +45 -0
- package/dist/components/form/types.d.ts +208 -0
- package/dist/components/form/types.js +29 -0
- package/dist/components/nodes/GatewayNode.svelte +84 -12
- package/dist/components/nodes/NotesNode.svelte +89 -307
- package/dist/components/nodes/NotesNode.svelte.d.ts +3 -22
- package/dist/components/nodes/SimpleNode.svelte +41 -5
- package/dist/components/nodes/SimpleNode.svelte.d.ts +2 -1
- package/dist/components/nodes/SquareNode.svelte +41 -5
- package/dist/components/nodes/SquareNode.svelte.d.ts +2 -1
- package/dist/components/nodes/WorkflowNode.svelte +88 -5
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -4
- package/dist/stores/workflowStore.d.ts +15 -0
- package/dist/stores/workflowStore.js +28 -0
- package/dist/types/index.d.ts +77 -0
- package/dist/types/index.js +16 -0
- package/package.json +3 -3
- package/dist/components/ConfigSidebar.svelte +0 -916
- package/dist/components/ConfigSidebar.svelte.d.ts +0 -51
- package/dist/config/demo.d.ts +0 -58
- package/dist/config/demo.js +0 -142
- package/dist/data/samples.d.ts +0 -51
- package/dist/data/samples.js +0 -3245
|
@@ -1,916 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Configuration Sidebar Component
|
|
3
|
-
Right-side sliding sidebar for node configuration
|
|
4
|
-
Styled with BEM syntax
|
|
5
|
-
-->
|
|
6
|
-
|
|
7
|
-
<script lang="ts">
|
|
8
|
-
import type { ConfigSchema, ConfigValues, ConfigProperty } from '../types/index.js';
|
|
9
|
-
import Icon from '@iconify/svelte';
|
|
10
|
-
import { createEventDispatcher } from 'svelte';
|
|
11
|
-
|
|
12
|
-
const dispatch = createEventDispatcher();
|
|
13
|
-
|
|
14
|
-
interface Props {
|
|
15
|
-
isOpen: boolean;
|
|
16
|
-
title: string;
|
|
17
|
-
configSchema?: ConfigSchema;
|
|
18
|
-
configValues: ConfigValues;
|
|
19
|
-
nodeDetails?: {
|
|
20
|
-
type: string;
|
|
21
|
-
category: string;
|
|
22
|
-
description: string;
|
|
23
|
-
version?: string;
|
|
24
|
-
tags?: string[];
|
|
25
|
-
inputs?: Array<{ id: string; name: string; type: string; dataType?: string }>;
|
|
26
|
-
outputs?: Array<{ id: string; name: string; type: string; dataType?: string }>;
|
|
27
|
-
};
|
|
28
|
-
onSave?: (config: ConfigValues) => void;
|
|
29
|
-
onCancel?: () => void;
|
|
30
|
-
onClose?: () => void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let props: Props = $props();
|
|
34
|
-
let localConfigValues = $state({ ...props.configValues });
|
|
35
|
-
let hasChanges = $derived(
|
|
36
|
-
JSON.stringify(localConfigValues) !== JSON.stringify(props.configValues)
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
// Focus management and body scroll control
|
|
40
|
-
$effect(() => {
|
|
41
|
-
if (props.isOpen) {
|
|
42
|
-
// Focus management - focus the sidebar when it opens
|
|
43
|
-
setTimeout(() => {
|
|
44
|
-
const sidebar = document.querySelector('.config-sidebar--open');
|
|
45
|
-
if (sidebar) {
|
|
46
|
-
(sidebar as HTMLElement).focus();
|
|
47
|
-
}
|
|
48
|
-
}, 100);
|
|
49
|
-
|
|
50
|
-
// Prevent body scroll
|
|
51
|
-
document.body.style.overflow = 'hidden';
|
|
52
|
-
} else {
|
|
53
|
-
// Restore body scroll
|
|
54
|
-
document.body.style.overflow = '';
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Handle input changes for different field types
|
|
60
|
-
*/
|
|
61
|
-
function handleInputChange(key: string, value: unknown, type: string): void {
|
|
62
|
-
let processedValue = value;
|
|
63
|
-
|
|
64
|
-
// Process value based on type
|
|
65
|
-
switch (type) {
|
|
66
|
-
case 'number':
|
|
67
|
-
case 'integer':
|
|
68
|
-
processedValue = value === '' ? undefined : Number(value);
|
|
69
|
-
break;
|
|
70
|
-
case 'boolean':
|
|
71
|
-
processedValue = Boolean(value);
|
|
72
|
-
break;
|
|
73
|
-
case 'array':
|
|
74
|
-
try {
|
|
75
|
-
processedValue = typeof value === 'string' ? JSON.parse(value) : value;
|
|
76
|
-
} catch {
|
|
77
|
-
processedValue = [];
|
|
78
|
-
}
|
|
79
|
-
break;
|
|
80
|
-
case 'object':
|
|
81
|
-
try {
|
|
82
|
-
processedValue = typeof value === 'string' ? JSON.parse(value) : value;
|
|
83
|
-
} catch {
|
|
84
|
-
processedValue = {};
|
|
85
|
-
}
|
|
86
|
-
break;
|
|
87
|
-
default:
|
|
88
|
-
processedValue = value;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
localConfigValues = {
|
|
92
|
-
...localConfigValues,
|
|
93
|
-
[key]: processedValue
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Handle save action
|
|
99
|
-
* Preserves hidden field values from the original configuration
|
|
100
|
-
*/
|
|
101
|
-
function handleSave(): void {
|
|
102
|
-
// Merge hidden field values from original props to ensure they're preserved
|
|
103
|
-
const hiddenFieldValues: ConfigValues = {};
|
|
104
|
-
if (props.configSchema?.properties) {
|
|
105
|
-
Object.entries(props.configSchema.properties).forEach(([key, property]) => {
|
|
106
|
-
if (property.format === 'hidden' && key in props.configValues) {
|
|
107
|
-
hiddenFieldValues[key] = props.configValues[key];
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const mergedConfig = { ...localConfigValues, ...hiddenFieldValues };
|
|
113
|
-
props.onSave?.(mergedConfig);
|
|
114
|
-
dispatch('save', { config: mergedConfig });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Handle cancel action
|
|
119
|
-
*/
|
|
120
|
-
function handleCancel(): void {
|
|
121
|
-
localConfigValues = { ...props.configValues };
|
|
122
|
-
hasChanges = false;
|
|
123
|
-
props.onCancel?.();
|
|
124
|
-
dispatch('cancel');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Handle close action
|
|
129
|
-
*/
|
|
130
|
-
function handleClose(): void {
|
|
131
|
-
if (hasChanges) {
|
|
132
|
-
const shouldClose = confirm('You have unsaved changes. Are you sure you want to close?');
|
|
133
|
-
if (!shouldClose) return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
localConfigValues = { ...props.configValues };
|
|
137
|
-
hasChanges = false;
|
|
138
|
-
props.onClose?.();
|
|
139
|
-
dispatch('close');
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Handle keyboard shortcuts
|
|
144
|
-
*/
|
|
145
|
-
function handleKeydown(event: KeyboardEvent): void {
|
|
146
|
-
if (event.key === 'Escape') {
|
|
147
|
-
handleClose();
|
|
148
|
-
} else if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
|
|
149
|
-
handleSave();
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/* Overlay event handlers removed since overlay is no longer used */
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Get input type for HTML input element
|
|
157
|
-
*/
|
|
158
|
-
function getInputType(schemaType: string): string {
|
|
159
|
-
switch (schemaType) {
|
|
160
|
-
case 'number':
|
|
161
|
-
case 'integer':
|
|
162
|
-
return 'number';
|
|
163
|
-
case 'boolean':
|
|
164
|
-
return 'checkbox';
|
|
165
|
-
default:
|
|
166
|
-
return 'text';
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Check if field should be rendered as textarea
|
|
172
|
-
*/
|
|
173
|
-
function isTextarea(property: ConfigProperty): boolean {
|
|
174
|
-
return (
|
|
175
|
-
property.format === 'multiline' || property.type === 'object' || property.type === 'array'
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Get display value for complex types
|
|
181
|
-
*/
|
|
182
|
-
function getDisplayValue(value: unknown, type: string): string {
|
|
183
|
-
if (type === 'object' || type === 'array') {
|
|
184
|
-
return typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
185
|
-
}
|
|
186
|
-
return String(value ?? '');
|
|
187
|
-
}
|
|
188
|
-
</script>
|
|
189
|
-
|
|
190
|
-
<!-- Overlay removed to avoid darkening the canvas -->
|
|
191
|
-
|
|
192
|
-
<!-- Sidebar -->
|
|
193
|
-
<div
|
|
194
|
-
class="config-sidebar"
|
|
195
|
-
class:config-sidebar--open={props.isOpen}
|
|
196
|
-
role="dialog"
|
|
197
|
-
aria-label="Configuration sidebar"
|
|
198
|
-
aria-modal="true"
|
|
199
|
-
tabindex="-1"
|
|
200
|
-
onkeydown={handleKeydown}
|
|
201
|
-
>
|
|
202
|
-
<!-- Header -->
|
|
203
|
-
<div class="config-sidebar__header">
|
|
204
|
-
<div class="config-sidebar__title-section">
|
|
205
|
-
<h2 class="config-sidebar__title">{props.title}</h2>
|
|
206
|
-
{#if hasChanges}
|
|
207
|
-
<div class="config-sidebar__changes-indicator" title="Unsaved changes">
|
|
208
|
-
<Icon icon="mdi:circle" />
|
|
209
|
-
</div>
|
|
210
|
-
{/if}
|
|
211
|
-
</div>
|
|
212
|
-
<button
|
|
213
|
-
class="config-sidebar__close-btn"
|
|
214
|
-
onclick={handleClose}
|
|
215
|
-
title="Close sidebar (Esc)"
|
|
216
|
-
aria-label="Close configuration sidebar"
|
|
217
|
-
>
|
|
218
|
-
<Icon icon="mdi:close" />
|
|
219
|
-
</button>
|
|
220
|
-
</div>
|
|
221
|
-
|
|
222
|
-
<!-- Content -->
|
|
223
|
-
<div class="config-sidebar__content">
|
|
224
|
-
<!-- Node Details Section -->
|
|
225
|
-
{#if props.nodeDetails}
|
|
226
|
-
<div class="config-sidebar__details">
|
|
227
|
-
<h3 class="config-sidebar__section-title">Node Details</h3>
|
|
228
|
-
|
|
229
|
-
<div class="config-sidebar__detail-grid">
|
|
230
|
-
<div class="config-sidebar__detail-item">
|
|
231
|
-
<span class="config-sidebar__detail-label">Type:</span>
|
|
232
|
-
<span class="config-sidebar__detail-value">{props.nodeDetails.type}</span>
|
|
233
|
-
</div>
|
|
234
|
-
|
|
235
|
-
<div class="config-sidebar__detail-item">
|
|
236
|
-
<span class="config-sidebar__detail-label">Category:</span>
|
|
237
|
-
<span class="config-sidebar__detail-value config-sidebar__detail-value--badge"
|
|
238
|
-
>{props.nodeDetails.category}</span
|
|
239
|
-
>
|
|
240
|
-
</div>
|
|
241
|
-
|
|
242
|
-
{#if props.nodeDetails.version}
|
|
243
|
-
<div class="config-sidebar__detail-item">
|
|
244
|
-
<span class="config-sidebar__detail-label">Version:</span>
|
|
245
|
-
<span class="config-sidebar__detail-value">{props.nodeDetails.version}</span>
|
|
246
|
-
</div>
|
|
247
|
-
{/if}
|
|
248
|
-
</div>
|
|
249
|
-
|
|
250
|
-
<div class="config-sidebar__detail-item config-sidebar__detail-item--full">
|
|
251
|
-
<span class="config-sidebar__detail-label">Description:</span>
|
|
252
|
-
<p class="config-sidebar__detail-description">{props.nodeDetails.description}</p>
|
|
253
|
-
</div>
|
|
254
|
-
|
|
255
|
-
{#if props.nodeDetails.inputs && props.nodeDetails.inputs.length > 0}
|
|
256
|
-
<div class="config-sidebar__detail-section">
|
|
257
|
-
<h4 class="config-sidebar__detail-subtitle">Input Ports</h4>
|
|
258
|
-
<div class="config-sidebar__ports">
|
|
259
|
-
{#each props.nodeDetails.inputs as input (input.id)}
|
|
260
|
-
<div class="config-sidebar__port config-sidebar__port--input">
|
|
261
|
-
<Icon icon="mdi:arrow-right" class="config-sidebar__port-icon" />
|
|
262
|
-
<span class="config-sidebar__port-name">{input.name}</span>
|
|
263
|
-
<span class="config-sidebar__port-type">{input.dataType || input.type}</span>
|
|
264
|
-
</div>
|
|
265
|
-
{/each}
|
|
266
|
-
</div>
|
|
267
|
-
</div>
|
|
268
|
-
{/if}
|
|
269
|
-
|
|
270
|
-
{#if props.nodeDetails.outputs && props.nodeDetails.outputs.length > 0}
|
|
271
|
-
<div class="config-sidebar__detail-section">
|
|
272
|
-
<h4 class="config-sidebar__detail-subtitle">Output Ports</h4>
|
|
273
|
-
<div class="config-sidebar__ports">
|
|
274
|
-
{#each props.nodeDetails.outputs as output (output.id)}
|
|
275
|
-
<div class="config-sidebar__port config-sidebar__port--output">
|
|
276
|
-
<Icon icon="mdi:arrow-left" class="config-sidebar__port-icon" />
|
|
277
|
-
<span class="config-sidebar__port-name">{output.name}</span>
|
|
278
|
-
<span class="config-sidebar__port-type">{output.dataType || output.type}</span>
|
|
279
|
-
</div>
|
|
280
|
-
{/each}
|
|
281
|
-
</div>
|
|
282
|
-
</div>
|
|
283
|
-
{/if}
|
|
284
|
-
|
|
285
|
-
{#if props.nodeDetails.tags && props.nodeDetails.tags.length > 0}
|
|
286
|
-
<div class="config-sidebar__detail-section">
|
|
287
|
-
<h4 class="config-sidebar__detail-subtitle">Tags</h4>
|
|
288
|
-
<div class="config-sidebar__tags">
|
|
289
|
-
{#each props.nodeDetails.tags as tag (tag)}
|
|
290
|
-
<span class="config-sidebar__tag">{tag}</span>
|
|
291
|
-
{/each}
|
|
292
|
-
</div>
|
|
293
|
-
</div>
|
|
294
|
-
{/if}
|
|
295
|
-
</div>
|
|
296
|
-
|
|
297
|
-
<!-- Separator between details and config -->
|
|
298
|
-
{#if props.configSchema?.properties}
|
|
299
|
-
<div class="config-sidebar__separator"></div>
|
|
300
|
-
{/if}
|
|
301
|
-
{/if}
|
|
302
|
-
|
|
303
|
-
<!-- Configuration Section -->
|
|
304
|
-
{#if props.configSchema?.properties}
|
|
305
|
-
<div class="config-sidebar__config-section">
|
|
306
|
-
<h3 class="config-sidebar__section-title">Configuration</h3>
|
|
307
|
-
<form
|
|
308
|
-
class="config-sidebar__form"
|
|
309
|
-
onsubmit={(e) => {
|
|
310
|
-
e.preventDefault();
|
|
311
|
-
handleSave();
|
|
312
|
-
}}
|
|
313
|
-
>
|
|
314
|
-
{#each Object.entries(props.configSchema.properties) as [key, property] (key)}
|
|
315
|
-
{#if property.format === 'hidden'}
|
|
316
|
-
<!-- Hidden field to preserve value -->
|
|
317
|
-
<input
|
|
318
|
-
type="hidden"
|
|
319
|
-
id="config-{key}"
|
|
320
|
-
value={typeof (localConfigValues[key] ?? property.default) === 'object'
|
|
321
|
-
? JSON.stringify(localConfigValues[key] ?? property.default ?? {})
|
|
322
|
-
: (localConfigValues[key] ?? property.default ?? '')}
|
|
323
|
-
/>
|
|
324
|
-
{:else}
|
|
325
|
-
<div class="config-sidebar__field">
|
|
326
|
-
<label class="config-sidebar__label" for="config-{key}">
|
|
327
|
-
{property.title || key}
|
|
328
|
-
{#if props.configSchema?.required?.includes(key)}
|
|
329
|
-
<span class="config-sidebar__required">*</span>
|
|
330
|
-
{/if}
|
|
331
|
-
</label>
|
|
332
|
-
|
|
333
|
-
{#if property.description}
|
|
334
|
-
<p class="config-sidebar__description">{property.description}</p>
|
|
335
|
-
{/if}
|
|
336
|
-
|
|
337
|
-
{#if property.enum && property.multiple}
|
|
338
|
-
<!-- Checkboxes for enum with multiple selection -->
|
|
339
|
-
<div class="config-sidebar__checkbox-group">
|
|
340
|
-
{#each property.enum as option (option)}
|
|
341
|
-
<div class="config-sidebar__checkbox-wrapper">
|
|
342
|
-
<input
|
|
343
|
-
type="checkbox"
|
|
344
|
-
id="config-{key}-{option}"
|
|
345
|
-
class="config-sidebar__checkbox"
|
|
346
|
-
value={option}
|
|
347
|
-
checked={Array.isArray(localConfigValues[key]) &&
|
|
348
|
-
localConfigValues[key].includes(option)}
|
|
349
|
-
onchange={(e) => {
|
|
350
|
-
const checked = (e.target as HTMLInputElement).checked;
|
|
351
|
-
const currentValues = Array.isArray(localConfigValues[key])
|
|
352
|
-
? [...localConfigValues[key]]
|
|
353
|
-
: [];
|
|
354
|
-
if (checked) {
|
|
355
|
-
if (!currentValues.includes(option)) {
|
|
356
|
-
handleInputChange(key, [...currentValues, option], property.type);
|
|
357
|
-
}
|
|
358
|
-
} else {
|
|
359
|
-
handleInputChange(
|
|
360
|
-
key,
|
|
361
|
-
currentValues.filter((v) => v !== option),
|
|
362
|
-
property.type
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
}}
|
|
366
|
-
/>
|
|
367
|
-
<label for="config-{key}-{option}" class="config-sidebar__checkbox-label">
|
|
368
|
-
{option}
|
|
369
|
-
</label>
|
|
370
|
-
</div>
|
|
371
|
-
{/each}
|
|
372
|
-
</div>
|
|
373
|
-
{:else if property.enum}
|
|
374
|
-
<!-- Dropdown for enum with single selection -->
|
|
375
|
-
<select
|
|
376
|
-
id="config-{key}"
|
|
377
|
-
class="config-sidebar__select"
|
|
378
|
-
value={localConfigValues[key] ?? property.default ?? ''}
|
|
379
|
-
onchange={(e) =>
|
|
380
|
-
handleInputChange(key, (e.target as HTMLSelectElement).value, property.type)}
|
|
381
|
-
>
|
|
382
|
-
{#each property.enum as option (option)}
|
|
383
|
-
<option value={option}>{option}</option>
|
|
384
|
-
{/each}
|
|
385
|
-
</select>
|
|
386
|
-
{:else if property.type === 'boolean'}
|
|
387
|
-
<!-- Checkbox for boolean -->
|
|
388
|
-
<div class="config-sidebar__checkbox-wrapper">
|
|
389
|
-
<input
|
|
390
|
-
id="config-{key}"
|
|
391
|
-
type="checkbox"
|
|
392
|
-
class="config-sidebar__checkbox"
|
|
393
|
-
checked={Boolean(localConfigValues[key] ?? property.default ?? false)}
|
|
394
|
-
onchange={(e) =>
|
|
395
|
-
handleInputChange(
|
|
396
|
-
key,
|
|
397
|
-
(e.target as HTMLInputElement).checked,
|
|
398
|
-
property.type
|
|
399
|
-
)}
|
|
400
|
-
/>
|
|
401
|
-
<label for="config-{key}" class="config-sidebar__checkbox-label">
|
|
402
|
-
{property.title || key}
|
|
403
|
-
</label>
|
|
404
|
-
</div>
|
|
405
|
-
{:else if isTextarea(property)}
|
|
406
|
-
<!-- Textarea for multiline, objects, arrays -->
|
|
407
|
-
<textarea
|
|
408
|
-
id="config-{key}"
|
|
409
|
-
class="config-sidebar__textarea"
|
|
410
|
-
placeholder={property.default ? String(property.default) : ''}
|
|
411
|
-
value={getDisplayValue(
|
|
412
|
-
localConfigValues[key] ?? property.default ?? '',
|
|
413
|
-
property.type
|
|
414
|
-
)}
|
|
415
|
-
oninput={(e) =>
|
|
416
|
-
handleInputChange(
|
|
417
|
-
key,
|
|
418
|
-
(e.target as HTMLTextAreaElement).value,
|
|
419
|
-
property.type
|
|
420
|
-
)}
|
|
421
|
-
rows="4"
|
|
422
|
-
></textarea>
|
|
423
|
-
{:else}
|
|
424
|
-
<!-- Regular input -->
|
|
425
|
-
<input
|
|
426
|
-
id="config-{key}"
|
|
427
|
-
type={getInputType(property.type)}
|
|
428
|
-
class="config-sidebar__input"
|
|
429
|
-
placeholder={property.default ? String(property.default) : ''}
|
|
430
|
-
value={localConfigValues[key] ?? property.default ?? ''}
|
|
431
|
-
min={property.minimum}
|
|
432
|
-
max={property.maximum}
|
|
433
|
-
step={property.type === 'number' ? 'any' : undefined}
|
|
434
|
-
oninput={(e) =>
|
|
435
|
-
handleInputChange(key, (e.target as HTMLInputElement).value, property.type)}
|
|
436
|
-
/>
|
|
437
|
-
{/if}
|
|
438
|
-
</div>
|
|
439
|
-
{/if}
|
|
440
|
-
{/each}
|
|
441
|
-
</form>
|
|
442
|
-
</div>
|
|
443
|
-
{:else if !props.nodeDetails}
|
|
444
|
-
<div class="config-sidebar__empty">
|
|
445
|
-
<Icon icon="mdi:cog-outline" class="config-sidebar__empty-icon" />
|
|
446
|
-
<p class="config-sidebar__empty-text">No configuration options available</p>
|
|
447
|
-
</div>
|
|
448
|
-
{/if}
|
|
449
|
-
</div>
|
|
450
|
-
|
|
451
|
-
<!-- Footer -->
|
|
452
|
-
<div class="config-sidebar__footer">
|
|
453
|
-
{#if hasChanges}
|
|
454
|
-
<p class="config-sidebar__changes-text">You have unsaved changes</p>
|
|
455
|
-
{/if}
|
|
456
|
-
<div class="config-sidebar__actions">
|
|
457
|
-
<button
|
|
458
|
-
type="button"
|
|
459
|
-
class="config-sidebar__btn config-sidebar__btn--secondary"
|
|
460
|
-
onclick={handleCancel}
|
|
461
|
-
disabled={!hasChanges}
|
|
462
|
-
>
|
|
463
|
-
<Icon icon="mdi:undo" />
|
|
464
|
-
Reset
|
|
465
|
-
</button>
|
|
466
|
-
<button
|
|
467
|
-
type="button"
|
|
468
|
-
class="config-sidebar__btn config-sidebar__btn--primary"
|
|
469
|
-
onclick={handleSave}
|
|
470
|
-
disabled={!hasChanges}
|
|
471
|
-
title="Save configuration (Ctrl+Enter)"
|
|
472
|
-
>
|
|
473
|
-
<Icon icon="mdi:check" />
|
|
474
|
-
Save Changes
|
|
475
|
-
</button>
|
|
476
|
-
</div>
|
|
477
|
-
</div>
|
|
478
|
-
</div>
|
|
479
|
-
|
|
480
|
-
<style>
|
|
481
|
-
/* Overlay styles removed to avoid darkening the canvas */
|
|
482
|
-
|
|
483
|
-
.config-sidebar {
|
|
484
|
-
z-index: -1;
|
|
485
|
-
display: flex;
|
|
486
|
-
flex-direction: column;
|
|
487
|
-
pointer-events: none;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/* Mobile responsive */
|
|
491
|
-
@media (max-width: 768px) {
|
|
492
|
-
.config-sidebar {
|
|
493
|
-
width: 100vw;
|
|
494
|
-
border-left: none;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
@media (max-width: 480px) {
|
|
499
|
-
.config-sidebar {
|
|
500
|
-
width: 100vw;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
.config-sidebar--open {
|
|
505
|
-
transform: translateX(0);
|
|
506
|
-
z-index: 999;
|
|
507
|
-
pointer-events: auto;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/* Off-canvas overlay styles removed to avoid darkening the canvas */
|
|
511
|
-
|
|
512
|
-
/* Prevent body scroll when sidebar is open */
|
|
513
|
-
:global(body:has(.config-sidebar--open)) {
|
|
514
|
-
overflow: hidden;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
.config-sidebar__header {
|
|
518
|
-
display: flex;
|
|
519
|
-
align-items: center;
|
|
520
|
-
justify-content: space-between;
|
|
521
|
-
padding: 1rem 1.5rem;
|
|
522
|
-
border-bottom: 1px solid #e5e7eb;
|
|
523
|
-
background-color: #f9fafb;
|
|
524
|
-
flex-shrink: 0;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
.config-sidebar__title-section {
|
|
528
|
-
display: flex;
|
|
529
|
-
align-items: center;
|
|
530
|
-
gap: 0.5rem;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
.config-sidebar__title {
|
|
534
|
-
font-size: 1.125rem;
|
|
535
|
-
font-weight: 600;
|
|
536
|
-
color: #111827;
|
|
537
|
-
margin: 0;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
.config-sidebar__changes-indicator {
|
|
541
|
-
color: #f59e0b;
|
|
542
|
-
font-size: 0.5rem;
|
|
543
|
-
animation: pulse 2s infinite;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
.config-sidebar__close-btn {
|
|
547
|
-
display: flex;
|
|
548
|
-
align-items: center;
|
|
549
|
-
justify-content: center;
|
|
550
|
-
width: 2rem;
|
|
551
|
-
height: 2rem;
|
|
552
|
-
border: none;
|
|
553
|
-
background: transparent;
|
|
554
|
-
color: #6b7280;
|
|
555
|
-
cursor: pointer;
|
|
556
|
-
border-radius: 0.375rem;
|
|
557
|
-
transition: all 0.2s ease-in-out;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
.config-sidebar__close-btn:hover {
|
|
561
|
-
background-color: #f3f4f6;
|
|
562
|
-
color: #374151;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
.config-sidebar__content {
|
|
566
|
-
flex: 1;
|
|
567
|
-
overflow-y: auto;
|
|
568
|
-
padding: 1.5rem;
|
|
569
|
-
min-height: 0; /* Allow flex item to shrink below content size */
|
|
570
|
-
max-height: calc(
|
|
571
|
-
100vh - var(--flowdrop-navbar-height, 60px) - 200px
|
|
572
|
-
); /* Reserve much more space for header and footer */
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/* Node Details Styles */
|
|
576
|
-
.config-sidebar__details {
|
|
577
|
-
margin-bottom: 1rem;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
.config-sidebar__section-title {
|
|
581
|
-
font-size: 1rem;
|
|
582
|
-
font-weight: 600;
|
|
583
|
-
color: #111827;
|
|
584
|
-
margin: 0 0 1rem 0;
|
|
585
|
-
display: flex;
|
|
586
|
-
align-items: center;
|
|
587
|
-
gap: 0.5rem;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
.config-sidebar__detail-grid {
|
|
591
|
-
display: grid;
|
|
592
|
-
grid-template-columns: 1fr 1fr;
|
|
593
|
-
gap: 0.75rem;
|
|
594
|
-
margin-bottom: 1rem;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
.config-sidebar__detail-item {
|
|
598
|
-
display: flex;
|
|
599
|
-
flex-direction: column;
|
|
600
|
-
gap: 0.25rem;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
.config-sidebar__detail-item--full {
|
|
604
|
-
grid-column: 1 / -1;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
.config-sidebar__detail-label {
|
|
608
|
-
font-size: 0.75rem;
|
|
609
|
-
font-weight: 500;
|
|
610
|
-
color: #6b7280;
|
|
611
|
-
text-transform: uppercase;
|
|
612
|
-
letter-spacing: 0.05em;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
.config-sidebar__detail-value {
|
|
616
|
-
font-size: 0.875rem;
|
|
617
|
-
color: #374151;
|
|
618
|
-
font-weight: 500;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
.config-sidebar__detail-value--badge {
|
|
622
|
-
background-color: #f3f4f6;
|
|
623
|
-
color: #374151;
|
|
624
|
-
padding: 0.25rem 0.5rem;
|
|
625
|
-
border-radius: 0.375rem;
|
|
626
|
-
font-size: 0.75rem;
|
|
627
|
-
width: fit-content;
|
|
628
|
-
text-transform: capitalize;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
.config-sidebar__detail-description {
|
|
632
|
-
font-size: 0.875rem;
|
|
633
|
-
color: #6b7280;
|
|
634
|
-
line-height: 1.5;
|
|
635
|
-
margin: 0;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
.config-sidebar__detail-section {
|
|
639
|
-
margin-top: 1rem;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
.config-sidebar__detail-subtitle {
|
|
643
|
-
font-size: 0.875rem;
|
|
644
|
-
font-weight: 600;
|
|
645
|
-
color: #374151;
|
|
646
|
-
margin: 0 0 0.5rem 0;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
.config-sidebar__ports {
|
|
650
|
-
display: flex;
|
|
651
|
-
flex-direction: column;
|
|
652
|
-
gap: 0.5rem;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
.config-sidebar__port {
|
|
656
|
-
display: flex;
|
|
657
|
-
align-items: center;
|
|
658
|
-
gap: 0.5rem;
|
|
659
|
-
padding: 0.5rem;
|
|
660
|
-
background-color: #f9fafb;
|
|
661
|
-
border-radius: 0.375rem;
|
|
662
|
-
border: 1px solid #e5e7eb;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
.config-sidebar__port--input {
|
|
666
|
-
border-left: 3px solid #3b82f6;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
.config-sidebar__port--output {
|
|
670
|
-
border-left: 3px solid #10b981;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
:global(.config-sidebar__port-icon) {
|
|
674
|
-
color: #6b7280;
|
|
675
|
-
font-size: 0.875rem;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
.config-sidebar__port-name {
|
|
679
|
-
font-size: 0.875rem;
|
|
680
|
-
font-weight: 500;
|
|
681
|
-
color: #374151;
|
|
682
|
-
flex: 1;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
.config-sidebar__port-type {
|
|
686
|
-
font-size: 0.75rem;
|
|
687
|
-
color: #6b7280;
|
|
688
|
-
background-color: #ffffff;
|
|
689
|
-
padding: 0.125rem 0.375rem;
|
|
690
|
-
border-radius: 0.25rem;
|
|
691
|
-
border: 1px solid #d1d5db;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
.config-sidebar__tags {
|
|
695
|
-
display: flex;
|
|
696
|
-
flex-wrap: wrap;
|
|
697
|
-
gap: 0.5rem;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
.config-sidebar__tag {
|
|
701
|
-
font-size: 0.75rem;
|
|
702
|
-
color: #374151;
|
|
703
|
-
background-color: #f3f4f6;
|
|
704
|
-
padding: 0.25rem 0.5rem;
|
|
705
|
-
border-radius: 0.375rem;
|
|
706
|
-
border: 1px solid #e5e7eb;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
.config-sidebar__separator {
|
|
710
|
-
height: 1px;
|
|
711
|
-
background-color: #e5e7eb;
|
|
712
|
-
margin: 1.5rem 0;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
.config-sidebar__config-section {
|
|
716
|
-
margin-top: 1rem;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
.config-sidebar__form {
|
|
720
|
-
display: flex;
|
|
721
|
-
flex-direction: column;
|
|
722
|
-
gap: 1.5rem;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
.config-sidebar__field {
|
|
726
|
-
display: flex;
|
|
727
|
-
flex-direction: column;
|
|
728
|
-
gap: 0.5rem;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
.config-sidebar__label {
|
|
732
|
-
font-size: 0.875rem;
|
|
733
|
-
font-weight: 500;
|
|
734
|
-
color: #374151;
|
|
735
|
-
display: flex;
|
|
736
|
-
align-items: center;
|
|
737
|
-
gap: 0.25rem;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
.config-sidebar__required {
|
|
741
|
-
color: #ef4444;
|
|
742
|
-
font-weight: 600;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
.config-sidebar__description {
|
|
746
|
-
font-size: 0.75rem;
|
|
747
|
-
color: #6b7280;
|
|
748
|
-
margin: 0;
|
|
749
|
-
line-height: 1.4;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
.config-sidebar__input,
|
|
753
|
-
.config-sidebar__select,
|
|
754
|
-
.config-sidebar__textarea {
|
|
755
|
-
padding: 0.5rem 0.75rem;
|
|
756
|
-
border: 1px solid #d1d5db;
|
|
757
|
-
border-radius: 0.375rem;
|
|
758
|
-
font-size: 0.875rem;
|
|
759
|
-
transition:
|
|
760
|
-
border-color 0.2s ease-in-out,
|
|
761
|
-
box-shadow 0.2s ease-in-out;
|
|
762
|
-
background-color: #ffffff;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
.config-sidebar__input:focus,
|
|
766
|
-
.config-sidebar__select:focus,
|
|
767
|
-
.config-sidebar__textarea:focus {
|
|
768
|
-
outline: none;
|
|
769
|
-
border-color: #3b82f6;
|
|
770
|
-
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
.config-sidebar__textarea {
|
|
774
|
-
resize: vertical;
|
|
775
|
-
min-height: 80px;
|
|
776
|
-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
777
|
-
line-height: 1.4;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
.config-sidebar__checkbox-group {
|
|
781
|
-
display: flex;
|
|
782
|
-
flex-direction: column;
|
|
783
|
-
gap: 0.5rem;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
.config-sidebar__checkbox-wrapper {
|
|
787
|
-
display: flex;
|
|
788
|
-
align-items: center;
|
|
789
|
-
gap: 0.5rem;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
.config-sidebar__checkbox {
|
|
793
|
-
width: 1rem;
|
|
794
|
-
height: 1rem;
|
|
795
|
-
accent-color: #3b82f6;
|
|
796
|
-
cursor: pointer;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
.config-sidebar__checkbox-label {
|
|
800
|
-
font-size: 0.875rem;
|
|
801
|
-
color: #374151;
|
|
802
|
-
cursor: pointer;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
.config-sidebar__empty {
|
|
806
|
-
display: flex;
|
|
807
|
-
flex-direction: column;
|
|
808
|
-
align-items: center;
|
|
809
|
-
justify-content: center;
|
|
810
|
-
padding: 3rem 1rem;
|
|
811
|
-
text-align: center;
|
|
812
|
-
color: #6b7280;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
:global(.config-sidebar__empty-icon) {
|
|
816
|
-
font-size: 3rem;
|
|
817
|
-
margin-bottom: 1rem;
|
|
818
|
-
opacity: 0.5;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
.config-sidebar__empty-text {
|
|
822
|
-
font-size: 0.875rem;
|
|
823
|
-
margin: 0;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
.config-sidebar__footer {
|
|
827
|
-
flex-shrink: 0;
|
|
828
|
-
display: flex;
|
|
829
|
-
align-items: center;
|
|
830
|
-
justify-content: flex-end;
|
|
831
|
-
gap: 0.75rem;
|
|
832
|
-
height: 40px;
|
|
833
|
-
align-items: center;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
.config-sidebar__actions {
|
|
837
|
-
display: flex;
|
|
838
|
-
gap: 0.75rem;
|
|
839
|
-
margin-bottom: 0.5rem;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
.config-sidebar__btn {
|
|
843
|
-
display: flex;
|
|
844
|
-
align-items: center;
|
|
845
|
-
gap: 0.5rem;
|
|
846
|
-
border: 1px solid;
|
|
847
|
-
border-radius: 0.375rem;
|
|
848
|
-
font-size: 0.875rem;
|
|
849
|
-
font-weight: 500;
|
|
850
|
-
cursor: pointer;
|
|
851
|
-
transition: all 0.2s ease-in-out;
|
|
852
|
-
flex: 1;
|
|
853
|
-
justify-content: center;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
.config-sidebar__btn:disabled {
|
|
857
|
-
opacity: 0.5;
|
|
858
|
-
cursor: not-allowed;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
.config-sidebar__btn--primary {
|
|
862
|
-
background-color: #3b82f6;
|
|
863
|
-
border-color: #3b82f6;
|
|
864
|
-
color: #ffffff;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
.config-sidebar__btn--primary:hover:not(:disabled) {
|
|
868
|
-
background-color: #2563eb;
|
|
869
|
-
border-color: #2563eb;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
.config-sidebar__btn--secondary {
|
|
873
|
-
background-color: transparent;
|
|
874
|
-
border-color: #d1d5db;
|
|
875
|
-
color: #374151;
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
.config-sidebar__btn--secondary:hover:not(:disabled) {
|
|
879
|
-
background-color: #f3f4f6;
|
|
880
|
-
border-color: #9ca3af;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
.config-sidebar__changes-text {
|
|
884
|
-
font-size: 0.75rem;
|
|
885
|
-
color: #f59e0b;
|
|
886
|
-
margin: 0;
|
|
887
|
-
text-align: center;
|
|
888
|
-
font-weight: 500;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
@keyframes pulse {
|
|
892
|
-
0%,
|
|
893
|
-
100% {
|
|
894
|
-
opacity: 1;
|
|
895
|
-
}
|
|
896
|
-
50% {
|
|
897
|
-
opacity: 0.5;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
/* Responsive design */
|
|
902
|
-
@media (max-width: 640px) {
|
|
903
|
-
.config-sidebar {
|
|
904
|
-
width: 100vw;
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
@media (max-width: 480px) {
|
|
909
|
-
.config-sidebar__header,
|
|
910
|
-
.config-sidebar__content,
|
|
911
|
-
.config-sidebar__footer {
|
|
912
|
-
padding-left: 1rem;
|
|
913
|
-
padding-right: 1rem;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
</style>
|