@d34dman/flowdrop 0.0.1 → 0.0.3

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.
Files changed (120) hide show
  1. package/README.md +307 -215
  2. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  3. package/dist/adapters/WorkflowAdapter.js +30 -30
  4. package/dist/api/client.d.ts +24 -1
  5. package/dist/api/client.js +55 -38
  6. package/dist/api/enhanced-client.d.ts +46 -0
  7. package/dist/api/enhanced-client.js +211 -0
  8. package/dist/clients/ApiClient.d.ts +19 -23
  9. package/dist/clients/ApiClient.js +36 -34
  10. package/dist/components/App.svelte +1299 -230
  11. package/dist/components/App.svelte.d.ts +21 -1
  12. package/dist/components/CanvasBanner.svelte +50 -44
  13. package/dist/components/CanvasBanner.svelte.d.ts +5 -19
  14. package/dist/components/ConfigForm.svelte +555 -0
  15. package/dist/components/ConfigForm.svelte.d.ts +32 -0
  16. package/dist/components/ConfigModal.svelte +261 -0
  17. package/dist/components/ConfigModal.svelte.d.ts +31 -0
  18. package/dist/components/ConfigSidebar.svelte +934 -0
  19. package/dist/components/ConfigSidebar.svelte.d.ts +51 -0
  20. package/dist/components/ConnectionLine.svelte +32 -0
  21. package/dist/components/ConnectionLine.svelte.d.ts +3 -0
  22. package/dist/components/GatewayNode.svelte +471 -0
  23. package/dist/components/GatewayNode.svelte.d.ts +15 -0
  24. package/dist/components/LoadingSpinner.svelte +23 -23
  25. package/dist/components/LoadingSpinner.svelte.d.ts +1 -1
  26. package/dist/components/Logo.svelte +82 -0
  27. package/dist/components/Logo.svelte.d.ts +26 -0
  28. package/dist/components/LogsSidebar.svelte +565 -0
  29. package/dist/components/LogsSidebar.svelte.d.ts +34 -0
  30. package/dist/components/MarkdownDisplay.svelte +28 -0
  31. package/dist/components/MarkdownDisplay.svelte.d.ts +7 -0
  32. package/dist/components/Navbar.svelte +663 -0
  33. package/dist/components/Navbar.svelte.d.ts +21 -0
  34. package/dist/components/NodeSidebar.svelte +629 -488
  35. package/dist/components/NodeSidebar.svelte.d.ts +1 -2
  36. package/dist/components/NodeStatusOverlay.svelte +327 -0
  37. package/dist/components/NodeStatusOverlay.svelte.d.ts +11 -0
  38. package/dist/components/NotesNode.svelte +566 -0
  39. package/dist/components/NotesNode.svelte.d.ts +43 -0
  40. package/dist/components/PipelineStatus.svelte +331 -0
  41. package/dist/components/PipelineStatus.svelte.d.ts +18 -0
  42. package/dist/components/SimpleNode.svelte +447 -0
  43. package/dist/components/SimpleNode.svelte.d.ts +24 -0
  44. package/dist/components/SquareNode.svelte +346 -0
  45. package/dist/components/SquareNode.svelte.d.ts +24 -0
  46. package/dist/components/StatusIcon.svelte +112 -0
  47. package/dist/components/StatusIcon.svelte.d.ts +10 -0
  48. package/dist/components/StatusLabel.svelte +33 -0
  49. package/dist/components/StatusLabel.svelte.d.ts +7 -0
  50. package/dist/components/ToolNode.svelte +385 -0
  51. package/dist/components/ToolNode.svelte.d.ts +36 -0
  52. package/dist/components/UniversalNode.svelte +126 -0
  53. package/dist/components/UniversalNode.svelte.d.ts +15 -0
  54. package/dist/components/WorkflowEditor.svelte +871 -528
  55. package/dist/components/WorkflowEditor.svelte.d.ts +15 -5
  56. package/dist/components/WorkflowNode.svelte +428 -542
  57. package/dist/components/WorkflowNode.svelte.d.ts +7 -3
  58. package/dist/config/apiConfig.d.ts +33 -0
  59. package/dist/config/apiConfig.js +39 -0
  60. package/dist/config/defaultPortConfig.d.ts +6 -0
  61. package/dist/config/defaultPortConfig.js +192 -0
  62. package/dist/config/demo.d.ts +58 -0
  63. package/dist/config/demo.js +142 -0
  64. package/dist/config/endpoints.d.ts +106 -0
  65. package/dist/config/endpoints.js +128 -0
  66. package/dist/data/samples.d.ts +38 -4
  67. package/dist/data/samples.js +2789 -737
  68. package/dist/examples/adapter-usage.d.ts +4 -4
  69. package/dist/examples/adapter-usage.js +21 -26
  70. package/dist/examples/api-client-usage.d.ts +6 -6
  71. package/dist/examples/api-client-usage.js +55 -54
  72. package/dist/index.d.ts +23 -15
  73. package/dist/index.js +23 -15
  74. package/dist/mocks/app-environment.d.ts +8 -0
  75. package/dist/mocks/app-environment.js +16 -0
  76. package/dist/mocks/app-forms.d.ts +2 -0
  77. package/dist/mocks/app-forms.js +21 -0
  78. package/dist/mocks/app-navigation.d.ts +5 -0
  79. package/dist/mocks/app-navigation.js +34 -0
  80. package/dist/mocks/app-stores.d.ts +14 -0
  81. package/dist/mocks/app-stores.js +26 -0
  82. package/dist/services/api.d.ts +13 -3
  83. package/dist/services/api.js +91 -36
  84. package/dist/services/globalSave.d.ts +20 -0
  85. package/dist/services/globalSave.js +165 -0
  86. package/dist/services/nodeExecutionService.d.ts +63 -0
  87. package/dist/services/nodeExecutionService.js +261 -0
  88. package/dist/services/portConfigApi.d.ts +14 -0
  89. package/dist/services/portConfigApi.js +69 -0
  90. package/dist/services/toastService.d.ts +147 -0
  91. package/dist/services/toastService.js +235 -0
  92. package/dist/services/workflowStorage.d.ts +2 -2
  93. package/dist/services/workflowStorage.js +10 -10
  94. package/dist/stores/workflowStore.d.ts +53 -0
  95. package/dist/stores/workflowStore.js +264 -0
  96. package/dist/styles/base.css +896 -363
  97. package/dist/svelte-app.d.ts +52 -5
  98. package/dist/svelte-app.js +128 -6
  99. package/dist/types/config.d.ts +291 -0
  100. package/dist/types/config.js +4 -0
  101. package/dist/types/index.d.ts +231 -19
  102. package/dist/types/index.js +1 -1
  103. package/dist/utils/colors.d.ts +67 -33
  104. package/dist/utils/colors.js +183 -118
  105. package/dist/utils/config.d.ts +41 -0
  106. package/dist/utils/config.js +248 -0
  107. package/dist/utils/connections.d.ts +40 -3
  108. package/dist/utils/connections.js +115 -44
  109. package/dist/utils/icons.d.ts +1 -1
  110. package/dist/utils/icons.js +71 -70
  111. package/dist/utils/nodeStatus.d.ts +53 -0
  112. package/dist/utils/nodeStatus.js +183 -0
  113. package/dist/utils/nodeTypes.d.ts +57 -0
  114. package/dist/utils/nodeTypes.js +109 -0
  115. package/dist/utils/nodeWrapper.d.ts +39 -0
  116. package/dist/utils/nodeWrapper.js +62 -0
  117. package/package.json +132 -97
  118. package/dist/app.css +0 -0
  119. package/dist/components/Node.svelte +0 -38
  120. package/dist/components/Node.svelte.d.ts +0 -4
@@ -0,0 +1,934 @@
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
+ <div class="config-sidebar__actions">
454
+ <button
455
+ type="button"
456
+ class="config-sidebar__btn config-sidebar__btn--secondary"
457
+ onclick={handleCancel}
458
+ disabled={!hasChanges}
459
+ >
460
+ <Icon icon="mdi:undo" />
461
+ Reset
462
+ </button>
463
+ <button
464
+ type="button"
465
+ class="config-sidebar__btn config-sidebar__btn--primary"
466
+ onclick={handleSave}
467
+ disabled={!hasChanges}
468
+ title="Save configuration (Ctrl+Enter)"
469
+ >
470
+ <Icon icon="mdi:check" />
471
+ Save Changes
472
+ </button>
473
+ </div>
474
+
475
+ {#if hasChanges}
476
+ <p class="config-sidebar__changes-text">You have unsaved changes</p>
477
+ {/if}
478
+ </div>
479
+ </div>
480
+
481
+ <style>
482
+ /* Overlay styles removed to avoid darkening the canvas */
483
+
484
+ .config-sidebar {
485
+ position: fixed;
486
+ top: var(--flowdrop-navbar-height, 60px); /* Start below navbar */
487
+ right: 0;
488
+ width: 400px;
489
+ height: calc(100vh - var(--flowdrop-navbar-height, 60px)); /* Account for navbar height */
490
+ background-color: #ffffff;
491
+ border-left: 1px solid #e5e7eb;
492
+ box-shadow: -4px 0 20px rgba(0, 0, 0, 0.15);
493
+ transform: translateX(100%);
494
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
495
+ z-index: -1;
496
+ display: flex;
497
+ flex-direction: column;
498
+ pointer-events: none;
499
+ }
500
+
501
+ /* Mobile responsive */
502
+ @media (max-width: 768px) {
503
+ .config-sidebar {
504
+ width: 100vw;
505
+ border-left: none;
506
+ }
507
+ }
508
+
509
+ @media (max-width: 480px) {
510
+ .config-sidebar {
511
+ width: 100vw;
512
+ }
513
+ }
514
+
515
+ .config-sidebar--open {
516
+ transform: translateX(0);
517
+ z-index: 999;
518
+ pointer-events: auto;
519
+ }
520
+
521
+ /* Off-canvas overlay styles removed to avoid darkening the canvas */
522
+
523
+ /* Prevent body scroll when sidebar is open */
524
+ :global(body:has(.config-sidebar--open)) {
525
+ overflow: hidden;
526
+ }
527
+
528
+ .config-sidebar__header {
529
+ display: flex;
530
+ align-items: center;
531
+ justify-content: space-between;
532
+ padding: 1rem 1.5rem;
533
+ border-bottom: 1px solid #e5e7eb;
534
+ background-color: #f9fafb;
535
+ flex-shrink: 0;
536
+ }
537
+
538
+ .config-sidebar__title-section {
539
+ display: flex;
540
+ align-items: center;
541
+ gap: 0.5rem;
542
+ }
543
+
544
+ .config-sidebar__title {
545
+ font-size: 1.125rem;
546
+ font-weight: 600;
547
+ color: #111827;
548
+ margin: 0;
549
+ }
550
+
551
+ .config-sidebar__changes-indicator {
552
+ color: #f59e0b;
553
+ font-size: 0.5rem;
554
+ animation: pulse 2s infinite;
555
+ }
556
+
557
+ .config-sidebar__close-btn {
558
+ display: flex;
559
+ align-items: center;
560
+ justify-content: center;
561
+ width: 2rem;
562
+ height: 2rem;
563
+ border: none;
564
+ background: transparent;
565
+ color: #6b7280;
566
+ cursor: pointer;
567
+ border-radius: 0.375rem;
568
+ transition: all 0.2s ease-in-out;
569
+ }
570
+
571
+ .config-sidebar__close-btn:hover {
572
+ background-color: #f3f4f6;
573
+ color: #374151;
574
+ }
575
+
576
+ .config-sidebar__content {
577
+ flex: 1;
578
+ overflow-y: auto;
579
+ padding: 1.5rem;
580
+ min-height: 0; /* Allow flex item to shrink below content size */
581
+ max-height: calc(
582
+ 100vh - var(--flowdrop-navbar-height, 60px) - 200px
583
+ ); /* Reserve much more space for header and footer */
584
+ }
585
+
586
+ /* Node Details Styles */
587
+ .config-sidebar__details {
588
+ margin-bottom: 1rem;
589
+ }
590
+
591
+ .config-sidebar__section-title {
592
+ font-size: 1rem;
593
+ font-weight: 600;
594
+ color: #111827;
595
+ margin: 0 0 1rem 0;
596
+ display: flex;
597
+ align-items: center;
598
+ gap: 0.5rem;
599
+ }
600
+
601
+ .config-sidebar__detail-grid {
602
+ display: grid;
603
+ grid-template-columns: 1fr 1fr;
604
+ gap: 0.75rem;
605
+ margin-bottom: 1rem;
606
+ }
607
+
608
+ .config-sidebar__detail-item {
609
+ display: flex;
610
+ flex-direction: column;
611
+ gap: 0.25rem;
612
+ }
613
+
614
+ .config-sidebar__detail-item--full {
615
+ grid-column: 1 / -1;
616
+ }
617
+
618
+ .config-sidebar__detail-label {
619
+ font-size: 0.75rem;
620
+ font-weight: 500;
621
+ color: #6b7280;
622
+ text-transform: uppercase;
623
+ letter-spacing: 0.05em;
624
+ }
625
+
626
+ .config-sidebar__detail-value {
627
+ font-size: 0.875rem;
628
+ color: #374151;
629
+ font-weight: 500;
630
+ }
631
+
632
+ .config-sidebar__detail-value--badge {
633
+ background-color: #f3f4f6;
634
+ color: #374151;
635
+ padding: 0.25rem 0.5rem;
636
+ border-radius: 0.375rem;
637
+ font-size: 0.75rem;
638
+ width: fit-content;
639
+ text-transform: capitalize;
640
+ }
641
+
642
+ .config-sidebar__detail-description {
643
+ font-size: 0.875rem;
644
+ color: #6b7280;
645
+ line-height: 1.5;
646
+ margin: 0;
647
+ }
648
+
649
+ .config-sidebar__detail-section {
650
+ margin-top: 1rem;
651
+ }
652
+
653
+ .config-sidebar__detail-subtitle {
654
+ font-size: 0.875rem;
655
+ font-weight: 600;
656
+ color: #374151;
657
+ margin: 0 0 0.5rem 0;
658
+ }
659
+
660
+ .config-sidebar__ports {
661
+ display: flex;
662
+ flex-direction: column;
663
+ gap: 0.5rem;
664
+ }
665
+
666
+ .config-sidebar__port {
667
+ display: flex;
668
+ align-items: center;
669
+ gap: 0.5rem;
670
+ padding: 0.5rem;
671
+ background-color: #f9fafb;
672
+ border-radius: 0.375rem;
673
+ border: 1px solid #e5e7eb;
674
+ }
675
+
676
+ .config-sidebar__port--input {
677
+ border-left: 3px solid #3b82f6;
678
+ }
679
+
680
+ .config-sidebar__port--output {
681
+ border-left: 3px solid #10b981;
682
+ }
683
+
684
+ :global(.config-sidebar__port-icon) {
685
+ color: #6b7280;
686
+ font-size: 0.875rem;
687
+ }
688
+
689
+ .config-sidebar__port-name {
690
+ font-size: 0.875rem;
691
+ font-weight: 500;
692
+ color: #374151;
693
+ flex: 1;
694
+ }
695
+
696
+ .config-sidebar__port-type {
697
+ font-size: 0.75rem;
698
+ color: #6b7280;
699
+ background-color: #ffffff;
700
+ padding: 0.125rem 0.375rem;
701
+ border-radius: 0.25rem;
702
+ border: 1px solid #d1d5db;
703
+ }
704
+
705
+ .config-sidebar__tags {
706
+ display: flex;
707
+ flex-wrap: wrap;
708
+ gap: 0.5rem;
709
+ }
710
+
711
+ .config-sidebar__tag {
712
+ font-size: 0.75rem;
713
+ color: #374151;
714
+ background-color: #f3f4f6;
715
+ padding: 0.25rem 0.5rem;
716
+ border-radius: 0.375rem;
717
+ border: 1px solid #e5e7eb;
718
+ }
719
+
720
+ .config-sidebar__separator {
721
+ height: 1px;
722
+ background-color: #e5e7eb;
723
+ margin: 1.5rem 0;
724
+ }
725
+
726
+ .config-sidebar__config-section {
727
+ margin-top: 1rem;
728
+ }
729
+
730
+ .config-sidebar__form {
731
+ display: flex;
732
+ flex-direction: column;
733
+ gap: 1.5rem;
734
+ }
735
+
736
+ .config-sidebar__field {
737
+ display: flex;
738
+ flex-direction: column;
739
+ gap: 0.5rem;
740
+ }
741
+
742
+ .config-sidebar__label {
743
+ font-size: 0.875rem;
744
+ font-weight: 500;
745
+ color: #374151;
746
+ display: flex;
747
+ align-items: center;
748
+ gap: 0.25rem;
749
+ }
750
+
751
+ .config-sidebar__required {
752
+ color: #ef4444;
753
+ font-weight: 600;
754
+ }
755
+
756
+ .config-sidebar__description {
757
+ font-size: 0.75rem;
758
+ color: #6b7280;
759
+ margin: 0;
760
+ line-height: 1.4;
761
+ }
762
+
763
+ .config-sidebar__input,
764
+ .config-sidebar__select,
765
+ .config-sidebar__textarea {
766
+ padding: 0.5rem 0.75rem;
767
+ border: 1px solid #d1d5db;
768
+ border-radius: 0.375rem;
769
+ font-size: 0.875rem;
770
+ transition:
771
+ border-color 0.2s ease-in-out,
772
+ box-shadow 0.2s ease-in-out;
773
+ background-color: #ffffff;
774
+ }
775
+
776
+ .config-sidebar__input:focus,
777
+ .config-sidebar__select:focus,
778
+ .config-sidebar__textarea:focus {
779
+ outline: none;
780
+ border-color: #3b82f6;
781
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
782
+ }
783
+
784
+ .config-sidebar__textarea {
785
+ resize: vertical;
786
+ min-height: 80px;
787
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
788
+ line-height: 1.4;
789
+ }
790
+
791
+ .config-sidebar__checkbox-group {
792
+ display: flex;
793
+ flex-direction: column;
794
+ gap: 0.5rem;
795
+ }
796
+
797
+ .config-sidebar__checkbox-wrapper {
798
+ display: flex;
799
+ align-items: center;
800
+ gap: 0.5rem;
801
+ }
802
+
803
+ .config-sidebar__checkbox {
804
+ width: 1rem;
805
+ height: 1rem;
806
+ accent-color: #3b82f6;
807
+ cursor: pointer;
808
+ }
809
+
810
+ .config-sidebar__checkbox-label {
811
+ font-size: 0.875rem;
812
+ color: #374151;
813
+ cursor: pointer;
814
+ }
815
+
816
+ .config-sidebar__empty {
817
+ display: flex;
818
+ flex-direction: column;
819
+ align-items: center;
820
+ justify-content: center;
821
+ padding: 3rem 1rem;
822
+ text-align: center;
823
+ color: #6b7280;
824
+ }
825
+
826
+ :global(.config-sidebar__empty-icon) {
827
+ font-size: 3rem;
828
+ margin-bottom: 1rem;
829
+ opacity: 0.5;
830
+ }
831
+
832
+ .config-sidebar__empty-text {
833
+ font-size: 0.875rem;
834
+ margin: 0;
835
+ }
836
+
837
+ .config-sidebar__footer {
838
+ padding: 1rem 1.5rem;
839
+ border-top: 1px solid #e5e7eb;
840
+ background-color: #f9fafb;
841
+ flex-shrink: 0;
842
+ position: sticky;
843
+ bottom: 0;
844
+ z-index: 10; /* Ensure footer stays on top */
845
+ height: 80px; /* Increased height for footer */
846
+ display: flex;
847
+ align-items: center;
848
+ justify-content: flex-end;
849
+ gap: 0.75rem;
850
+ min-height: 80px; /* Ensure minimum height */
851
+ }
852
+
853
+ .config-sidebar__actions {
854
+ display: flex;
855
+ gap: 0.75rem;
856
+ margin-bottom: 0.5rem;
857
+ }
858
+
859
+ .config-sidebar__btn {
860
+ display: flex;
861
+ align-items: center;
862
+ gap: 0.5rem;
863
+ padding: 0.5rem 1rem;
864
+ border: 1px solid;
865
+ border-radius: 0.375rem;
866
+ font-size: 0.875rem;
867
+ font-weight: 500;
868
+ cursor: pointer;
869
+ transition: all 0.2s ease-in-out;
870
+ flex: 1;
871
+ justify-content: center;
872
+ }
873
+
874
+ .config-sidebar__btn:disabled {
875
+ opacity: 0.5;
876
+ cursor: not-allowed;
877
+ }
878
+
879
+ .config-sidebar__btn--primary {
880
+ background-color: #3b82f6;
881
+ border-color: #3b82f6;
882
+ color: #ffffff;
883
+ }
884
+
885
+ .config-sidebar__btn--primary:hover:not(:disabled) {
886
+ background-color: #2563eb;
887
+ border-color: #2563eb;
888
+ }
889
+
890
+ .config-sidebar__btn--secondary {
891
+ background-color: transparent;
892
+ border-color: #d1d5db;
893
+ color: #374151;
894
+ }
895
+
896
+ .config-sidebar__btn--secondary:hover:not(:disabled) {
897
+ background-color: #f3f4f6;
898
+ border-color: #9ca3af;
899
+ }
900
+
901
+ .config-sidebar__changes-text {
902
+ font-size: 0.75rem;
903
+ color: #f59e0b;
904
+ margin: 0;
905
+ text-align: center;
906
+ font-weight: 500;
907
+ }
908
+
909
+ @keyframes pulse {
910
+ 0%,
911
+ 100% {
912
+ opacity: 1;
913
+ }
914
+ 50% {
915
+ opacity: 0.5;
916
+ }
917
+ }
918
+
919
+ /* Responsive design */
920
+ @media (max-width: 640px) {
921
+ .config-sidebar {
922
+ width: 100vw;
923
+ }
924
+ }
925
+
926
+ @media (max-width: 480px) {
927
+ .config-sidebar__header,
928
+ .config-sidebar__content,
929
+ .config-sidebar__footer {
930
+ padding-left: 1rem;
931
+ padding-right: 1rem;
932
+ }
933
+ }
934
+ </style>