@d34dman/flowdrop 0.0.15 → 0.0.17
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 +64 -1
- package/dist/api/enhanced-client.d.ts +119 -3
- package/dist/api/enhanced-client.js +233 -54
- package/dist/components/App.svelte +145 -33
- package/dist/components/App.svelte.d.ts +27 -1
- package/dist/components/FlowDropZone.svelte +4 -5
- package/dist/components/FlowDropZone.svelte.d.ts +1 -1
- package/dist/components/UniversalNode.svelte +94 -34
- package/dist/components/WorkflowEditor.svelte +63 -3
- package/dist/config/runtimeConfig.d.ts +2 -2
- package/dist/config/runtimeConfig.js +7 -7
- package/dist/data/samples.js +9 -9
- package/dist/examples/adapter-usage.js +1 -1
- package/dist/helpers/workflowEditorHelper.d.ts +44 -4
- package/dist/helpers/workflowEditorHelper.js +161 -30
- package/dist/index.d.ts +12 -2
- package/dist/index.js +20 -1
- package/dist/registry/builtinNodes.d.ts +77 -0
- package/dist/registry/builtinNodes.js +181 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.js +10 -0
- package/dist/registry/nodeComponentRegistry.d.ts +307 -0
- package/dist/registry/nodeComponentRegistry.js +315 -0
- package/dist/registry/plugin.d.ts +215 -0
- package/dist/registry/plugin.js +249 -0
- package/dist/services/draftStorage.d.ts +171 -0
- package/dist/services/draftStorage.js +298 -0
- package/dist/stores/workflowStore.d.ts +103 -0
- package/dist/stores/workflowStore.js +249 -29
- package/dist/styles/base.css +15 -0
- package/dist/svelte-app.d.ts +110 -28
- package/dist/svelte-app.js +150 -27
- package/dist/types/auth.d.ts +278 -0
- package/dist/types/auth.js +244 -0
- package/dist/types/events.d.ts +163 -0
- package/dist/types/events.js +30 -0
- package/dist/types/index.d.ts +38 -3
- package/dist/utils/nodeTypes.d.ts +76 -21
- package/dist/utils/nodeTypes.js +180 -32
- package/package.json +1 -2
|
@@ -17,24 +17,44 @@
|
|
|
17
17
|
import { sampleNodes } from '../data/samples.js';
|
|
18
18
|
import { createEndpointConfig } from '../config/endpoints.js';
|
|
19
19
|
import type { EndpointConfig } from '../config/endpoints.js';
|
|
20
|
-
import {
|
|
20
|
+
import type { AuthProvider } from '../types/auth.js';
|
|
21
|
+
import type { FlowDropEventHandlers, FlowDropFeatures } from '../types/events.js';
|
|
22
|
+
import { mergeFeatures } from '../types/events.js';
|
|
23
|
+
import {
|
|
24
|
+
workflowStore,
|
|
25
|
+
workflowActions,
|
|
26
|
+
workflowName,
|
|
27
|
+
markAsSaved
|
|
28
|
+
} from '../stores/workflowStore.js';
|
|
21
29
|
import { apiToasts, dismissToast } from '../services/toastService.js';
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Configuration props for runtime customization
|
|
33
|
+
*/
|
|
24
34
|
interface Props {
|
|
35
|
+
/** Initial workflow to load */
|
|
25
36
|
workflow?: Workflow;
|
|
37
|
+
/** Pre-loaded node types (if provided, skips API fetch) */
|
|
38
|
+
nodes?: NodeMetadata[];
|
|
39
|
+
/** Editor height */
|
|
26
40
|
height?: string | number;
|
|
41
|
+
/** Editor width */
|
|
27
42
|
width?: string | number;
|
|
43
|
+
/** Show the navbar */
|
|
28
44
|
showNavbar?: boolean;
|
|
29
|
-
|
|
45
|
+
/** Disable the node sidebar */
|
|
30
46
|
disableSidebar?: boolean;
|
|
47
|
+
/** Lock the workflow (prevent changes) */
|
|
31
48
|
lockWorkflow?: boolean;
|
|
49
|
+
/** Read-only mode */
|
|
32
50
|
readOnly?: boolean;
|
|
51
|
+
/** Node execution statuses */
|
|
33
52
|
nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
|
|
34
|
-
|
|
53
|
+
/** Pipeline ID for fetching node execution info */
|
|
35
54
|
pipelineId?: string;
|
|
36
|
-
|
|
55
|
+
/** Custom navbar title */
|
|
37
56
|
navbarTitle?: string;
|
|
57
|
+
/** Custom navbar actions */
|
|
38
58
|
navbarActions?: Array<{
|
|
39
59
|
label: string;
|
|
40
60
|
href: string;
|
|
@@ -42,13 +62,21 @@
|
|
|
42
62
|
variant?: 'primary' | 'secondary' | 'outline';
|
|
43
63
|
onclick?: (event: Event) => void;
|
|
44
64
|
}>;
|
|
45
|
-
|
|
65
|
+
/** API base URL */
|
|
46
66
|
apiBaseUrl?: string;
|
|
67
|
+
/** Endpoint configuration */
|
|
47
68
|
endpointConfig?: EndpointConfig;
|
|
69
|
+
/** Authentication provider */
|
|
70
|
+
authProvider?: AuthProvider;
|
|
71
|
+
/** Event handlers */
|
|
72
|
+
eventHandlers?: FlowDropEventHandlers;
|
|
73
|
+
/** Feature configuration */
|
|
74
|
+
features?: FlowDropFeatures;
|
|
48
75
|
}
|
|
49
76
|
|
|
50
77
|
let {
|
|
51
78
|
workflow: initialWorkflow,
|
|
79
|
+
nodes: propNodes,
|
|
52
80
|
height = '100vh',
|
|
53
81
|
width = '100%',
|
|
54
82
|
showNavbar = false,
|
|
@@ -60,9 +88,15 @@
|
|
|
60
88
|
navbarTitle,
|
|
61
89
|
navbarActions = [],
|
|
62
90
|
apiBaseUrl,
|
|
63
|
-
endpointConfig: propEndpointConfig
|
|
91
|
+
endpointConfig: propEndpointConfig,
|
|
92
|
+
authProvider,
|
|
93
|
+
eventHandlers,
|
|
94
|
+
features: propFeatures
|
|
64
95
|
}: Props = $props();
|
|
65
96
|
|
|
97
|
+
// Merge features with defaults
|
|
98
|
+
const features = mergeFeatures(propFeatures);
|
|
99
|
+
|
|
66
100
|
// Create breadcrumb-style title - at top level to avoid store subscription issues
|
|
67
101
|
let breadcrumbTitle = $derived(() => {
|
|
68
102
|
// Use custom navbar title if provided
|
|
@@ -129,10 +163,19 @@
|
|
|
129
163
|
|
|
130
164
|
/**
|
|
131
165
|
* Fetch node types from the server
|
|
166
|
+
*
|
|
167
|
+
* If propNodes is provided, uses those instead of fetching from API.
|
|
168
|
+
* This fixes the bug where propNodes was ignored.
|
|
132
169
|
*/
|
|
133
170
|
async function fetchNodeTypes(): Promise<void> {
|
|
134
|
-
//
|
|
135
|
-
|
|
171
|
+
// If nodes were provided as props, use them directly (skip API fetch)
|
|
172
|
+
if (propNodes && propNodes.length > 0) {
|
|
173
|
+
nodes = propNodes;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Show loading toast (if toasts are enabled)
|
|
178
|
+
const loadingToast = features.showToasts ? apiToasts.loading('Loading node types') : null;
|
|
136
179
|
try {
|
|
137
180
|
error = null;
|
|
138
181
|
|
|
@@ -142,13 +185,35 @@
|
|
|
142
185
|
error = null;
|
|
143
186
|
|
|
144
187
|
// Dismiss loading toast
|
|
145
|
-
|
|
188
|
+
if (loadingToast) {
|
|
189
|
+
dismissToast(loadingToast);
|
|
190
|
+
}
|
|
146
191
|
} catch (err) {
|
|
147
192
|
// Dismiss loading toast and show error toast
|
|
148
|
-
|
|
193
|
+
if (loadingToast) {
|
|
194
|
+
dismissToast(loadingToast);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
198
|
+
|
|
199
|
+
// Notify parent via event handler
|
|
200
|
+
if (eventHandlers?.onApiError) {
|
|
201
|
+
const suppressToast = eventHandlers.onApiError(
|
|
202
|
+
err instanceof Error ? err : new Error(errorMessage),
|
|
203
|
+
'fetchNodes'
|
|
204
|
+
);
|
|
205
|
+
if (suppressToast) {
|
|
206
|
+
// Parent handled the error, don't show default toast
|
|
207
|
+
nodes = sampleNodes;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
149
212
|
// Show error but don't block the UI
|
|
150
|
-
error = `API Error: ${
|
|
151
|
-
|
|
213
|
+
error = `API Error: ${errorMessage}. Using sample data.`;
|
|
214
|
+
if (features.showToasts) {
|
|
215
|
+
apiToasts.error('Load node types', errorMessage);
|
|
216
|
+
}
|
|
152
217
|
|
|
153
218
|
// Fallback to sample data
|
|
154
219
|
nodes = sampleNodes;
|
|
@@ -283,27 +348,37 @@
|
|
|
283
348
|
|
|
284
349
|
/**
|
|
285
350
|
* Save workflow - exposed API function
|
|
351
|
+
*
|
|
352
|
+
* Integrates with event handlers for enterprise customization.
|
|
286
353
|
*/
|
|
287
354
|
async function saveWorkflow(): Promise<void> {
|
|
288
355
|
// Wait for any pending DOM updates before saving
|
|
289
356
|
await tick();
|
|
290
357
|
|
|
291
|
-
//
|
|
292
|
-
const
|
|
358
|
+
// Use current workflow from global store
|
|
359
|
+
const workflowToSave = $workflowStore;
|
|
360
|
+
|
|
361
|
+
if (!workflowToSave) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Call onBeforeSave if provided - allows cancellation
|
|
366
|
+
if (eventHandlers?.onBeforeSave) {
|
|
367
|
+
const shouldContinue = await eventHandlers.onBeforeSave(workflowToSave);
|
|
368
|
+
if (shouldContinue === false) {
|
|
369
|
+
// Save cancelled by event handler
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Show loading toast (if enabled)
|
|
375
|
+
const loadingToast = features.showToasts ? apiToasts.loading('Saving workflow') : null;
|
|
293
376
|
|
|
294
377
|
try {
|
|
295
378
|
// Import necessary modules
|
|
296
379
|
const { workflowApi } = await import('../services/api.js');
|
|
297
380
|
const { v4: uuidv4 } = await import('uuid');
|
|
298
381
|
|
|
299
|
-
// Use current workflow from global store
|
|
300
|
-
const workflowToSave = $workflowStore;
|
|
301
|
-
|
|
302
|
-
if (!workflowToSave) {
|
|
303
|
-
dismissToast(loadingToast);
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
382
|
// Determine the workflow ID
|
|
308
383
|
let workflowId: string;
|
|
309
384
|
if (workflowToSave.id) {
|
|
@@ -313,7 +388,7 @@
|
|
|
313
388
|
}
|
|
314
389
|
|
|
315
390
|
// Create workflow object for saving
|
|
316
|
-
const finalWorkflow = {
|
|
391
|
+
const finalWorkflow: Workflow = {
|
|
317
392
|
id: workflowId,
|
|
318
393
|
name: workflowToSave.name || 'Untitled Workflow',
|
|
319
394
|
description: workflowToSave.description || '',
|
|
@@ -342,14 +417,45 @@
|
|
|
342
417
|
});
|
|
343
418
|
}
|
|
344
419
|
|
|
420
|
+
// Mark as saved (clears dirty state)
|
|
421
|
+
markAsSaved();
|
|
422
|
+
|
|
345
423
|
// Dismiss loading toast and show success
|
|
346
|
-
|
|
347
|
-
|
|
424
|
+
if (loadingToast) {
|
|
425
|
+
dismissToast(loadingToast);
|
|
426
|
+
}
|
|
427
|
+
if (features.showToasts) {
|
|
428
|
+
apiToasts.success('Save workflow', 'Workflow saved successfully');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Call onAfterSave if provided
|
|
432
|
+
if (eventHandlers?.onAfterSave) {
|
|
433
|
+
await eventHandlers.onAfterSave(savedWorkflow);
|
|
434
|
+
}
|
|
348
435
|
} catch (error) {
|
|
349
|
-
// Dismiss loading toast
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
436
|
+
// Dismiss loading toast
|
|
437
|
+
if (loadingToast) {
|
|
438
|
+
dismissToast(loadingToast);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const errorObj = error instanceof Error ? error : new Error('Unknown error occurred');
|
|
442
|
+
|
|
443
|
+
// Call onSaveError if provided
|
|
444
|
+
if (eventHandlers?.onSaveError && workflowToSave) {
|
|
445
|
+
await eventHandlers.onSaveError(errorObj, workflowToSave);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Check if parent wants to handle API errors
|
|
449
|
+
let suppressToast = false;
|
|
450
|
+
if (eventHandlers?.onApiError) {
|
|
451
|
+
suppressToast = eventHandlers.onApiError(errorObj, 'save') === true;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Show error toast if not suppressed
|
|
455
|
+
if (features.showToasts && !suppressToast) {
|
|
456
|
+
apiToasts.error('Save workflow', errorObj.message);
|
|
457
|
+
}
|
|
458
|
+
|
|
353
459
|
throw error; // Re-throw to allow calling code to handle if needed
|
|
354
460
|
}
|
|
355
461
|
}
|
|
@@ -398,9 +504,7 @@
|
|
|
398
504
|
|
|
399
505
|
// Expose save and export functions globally for external access
|
|
400
506
|
if (typeof window !== 'undefined') {
|
|
401
|
-
// @ts-expect-error - Adding to window for external access
|
|
402
507
|
window.flowdropSave = saveWorkflow;
|
|
403
|
-
// @ts-expect-error - Adding to window for external access
|
|
404
508
|
window.flowdropExport = exportWorkflow;
|
|
405
509
|
}
|
|
406
510
|
|
|
@@ -425,6 +529,11 @@
|
|
|
425
529
|
// Initialize the workflow store if we have an initial workflow
|
|
426
530
|
if (initialWorkflow) {
|
|
427
531
|
workflowActions.initialize(initialWorkflow);
|
|
532
|
+
|
|
533
|
+
// Emit onWorkflowLoad event
|
|
534
|
+
if (eventHandlers?.onWorkflowLoad) {
|
|
535
|
+
eventHandlers.onWorkflowLoad(initialWorkflow);
|
|
536
|
+
}
|
|
428
537
|
}
|
|
429
538
|
})();
|
|
430
539
|
|
|
@@ -635,7 +744,10 @@
|
|
|
635
744
|
<div class="flowdrop-config-sidebar__detail">
|
|
636
745
|
<span class="flowdrop-config-sidebar__detail-label">Node ID:</span>
|
|
637
746
|
<div class="flowdrop-config-sidebar__detail-value-with-copy">
|
|
638
|
-
<span
|
|
747
|
+
<span
|
|
748
|
+
class="flowdrop-config-sidebar__detail-value"
|
|
749
|
+
style="font-family: monospace;"
|
|
750
|
+
>
|
|
639
751
|
{selectedNodeForConfig().id}
|
|
640
752
|
</span>
|
|
641
753
|
<button
|
|
@@ -1,16 +1,34 @@
|
|
|
1
|
-
import type { Workflow } from '../types/index.js';
|
|
1
|
+
import type { NodeMetadata, Workflow } from '../types/index.js';
|
|
2
2
|
import type { EndpointConfig } from '../config/endpoints.js';
|
|
3
|
+
import type { AuthProvider } from '../types/auth.js';
|
|
4
|
+
import type { FlowDropEventHandlers, FlowDropFeatures } from '../types/events.js';
|
|
5
|
+
/**
|
|
6
|
+
* Configuration props for runtime customization
|
|
7
|
+
*/
|
|
3
8
|
interface Props {
|
|
9
|
+
/** Initial workflow to load */
|
|
4
10
|
workflow?: Workflow;
|
|
11
|
+
/** Pre-loaded node types (if provided, skips API fetch) */
|
|
12
|
+
nodes?: NodeMetadata[];
|
|
13
|
+
/** Editor height */
|
|
5
14
|
height?: string | number;
|
|
15
|
+
/** Editor width */
|
|
6
16
|
width?: string | number;
|
|
17
|
+
/** Show the navbar */
|
|
7
18
|
showNavbar?: boolean;
|
|
19
|
+
/** Disable the node sidebar */
|
|
8
20
|
disableSidebar?: boolean;
|
|
21
|
+
/** Lock the workflow (prevent changes) */
|
|
9
22
|
lockWorkflow?: boolean;
|
|
23
|
+
/** Read-only mode */
|
|
10
24
|
readOnly?: boolean;
|
|
25
|
+
/** Node execution statuses */
|
|
11
26
|
nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
|
|
27
|
+
/** Pipeline ID for fetching node execution info */
|
|
12
28
|
pipelineId?: string;
|
|
29
|
+
/** Custom navbar title */
|
|
13
30
|
navbarTitle?: string;
|
|
31
|
+
/** Custom navbar actions */
|
|
14
32
|
navbarActions?: Array<{
|
|
15
33
|
label: string;
|
|
16
34
|
href: string;
|
|
@@ -18,8 +36,16 @@ interface Props {
|
|
|
18
36
|
variant?: 'primary' | 'secondary' | 'outline';
|
|
19
37
|
onclick?: (event: Event) => void;
|
|
20
38
|
}>;
|
|
39
|
+
/** API base URL */
|
|
21
40
|
apiBaseUrl?: string;
|
|
41
|
+
/** Endpoint configuration */
|
|
22
42
|
endpointConfig?: EndpointConfig;
|
|
43
|
+
/** Authentication provider */
|
|
44
|
+
authProvider?: AuthProvider;
|
|
45
|
+
/** Event handlers */
|
|
46
|
+
eventHandlers?: FlowDropEventHandlers;
|
|
47
|
+
/** Feature configuration */
|
|
48
|
+
features?: FlowDropFeatures;
|
|
23
49
|
}
|
|
24
50
|
declare const App: import("svelte").Component<Props, {}, "">;
|
|
25
51
|
type App = ReturnType<typeof App>;
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
-->
|
|
6
6
|
|
|
7
7
|
<script lang="ts">
|
|
8
|
-
import { useSvelteFlow } from
|
|
9
|
-
import type { Snippet } from
|
|
8
|
+
import { useSvelteFlow } from '@xyflow/svelte';
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
12
|
ondrop: (nodeTypeData: string, position: { x: number; y: number }) => void;
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
function handleDragOver(e: DragEvent): void {
|
|
25
25
|
e.preventDefault();
|
|
26
26
|
if (e.dataTransfer) {
|
|
27
|
-
e.dataTransfer.dropEffect =
|
|
27
|
+
e.dataTransfer.dropEffect = 'copy';
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
e.preventDefault();
|
|
36
36
|
|
|
37
37
|
// Get the data from the drag event
|
|
38
|
-
const nodeTypeData = e.dataTransfer?.getData(
|
|
38
|
+
const nodeTypeData = e.dataTransfer?.getData('application/json');
|
|
39
39
|
if (nodeTypeData) {
|
|
40
40
|
// Convert screen coordinates to flow coordinates (accounts for zoom and pan)
|
|
41
41
|
const position = screenToFlowPosition({
|
|
@@ -65,4 +65,3 @@
|
|
|
65
65
|
height: 100%;
|
|
66
66
|
}
|
|
67
67
|
</style>
|
|
68
|
-
|
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
Universal Node Component
|
|
3
|
-
Renders any node type with automatic status overlay injection
|
|
4
|
-
This component can replace individual node components in SvelteFlow
|
|
3
|
+
Renders any node type with automatic status overlay injection.
|
|
4
|
+
This component can replace individual node components in SvelteFlow.
|
|
5
|
+
|
|
6
|
+
Uses the node component registry to resolve which component to render,
|
|
7
|
+
enabling custom node types to be registered and used dynamically.
|
|
5
8
|
-->
|
|
6
9
|
|
|
7
10
|
<script lang="ts">
|
|
8
11
|
import type { WorkflowNode } from '../types/index.js';
|
|
12
|
+
import { nodeComponentRegistry } from '../registry/nodeComponentRegistry.js';
|
|
13
|
+
import { resolveBuiltinAlias } from '../registry/builtinNodes.js';
|
|
14
|
+
import NodeStatusOverlay from './NodeStatusOverlay.svelte';
|
|
15
|
+
import { shouldShowNodeStatus } from '../utils/nodeWrapper.js';
|
|
16
|
+
import { resolveComponentName } from '../utils/nodeTypes.js';
|
|
17
|
+
|
|
18
|
+
// Fallback components for when registry is not available
|
|
19
|
+
// These are only used as last-resort fallbacks
|
|
9
20
|
import WorkflowNodeComponent from './WorkflowNode.svelte';
|
|
10
21
|
import NotesNode from './NotesNode.svelte';
|
|
11
22
|
import SimpleNode from './SimpleNode.svelte';
|
|
12
23
|
import SquareNode from './SquareNode.svelte';
|
|
13
24
|
import ToolNode from './ToolNode.svelte';
|
|
14
25
|
import GatewayNode from './GatewayNode.svelte';
|
|
15
|
-
import NodeStatusOverlay from './NodeStatusOverlay.svelte';
|
|
16
|
-
import {
|
|
17
|
-
shouldShowNodeStatus,
|
|
18
|
-
getOptimalStatusPosition,
|
|
19
|
-
getOptimalStatusSize
|
|
20
|
-
} from '../utils/nodeWrapper.js';
|
|
21
|
-
import { resolveComponentName } from '../utils/nodeTypes.js';
|
|
22
26
|
|
|
23
27
|
let {
|
|
24
28
|
data,
|
|
@@ -31,26 +35,60 @@
|
|
|
31
35
|
selected?: boolean;
|
|
32
36
|
} = $props();
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Determine which node component to render based on node type.
|
|
40
|
+
* Priority: config.nodeType > metadata.type
|
|
41
|
+
* Explicitly track config.nodeType to ensure reactivity.
|
|
42
|
+
*/
|
|
37
43
|
let configNodeType = $derived(data.config?.nodeType as string | undefined);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the component name from metadata and config.
|
|
47
|
+
* This handles the logic of choosing between config.nodeType and metadata.type.
|
|
48
|
+
*/
|
|
38
49
|
let resolvedComponentName = $derived(
|
|
39
50
|
data.metadata ? resolveComponentName(data.metadata, configNodeType) : 'workflowNode'
|
|
40
51
|
);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get the node component from the registry.
|
|
55
|
+
* Falls back to built-in components if registry lookup fails.
|
|
56
|
+
*/
|
|
41
57
|
let nodeComponent = $derived(getNodeComponent(resolvedComponentName));
|
|
42
58
|
|
|
43
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Get execution info for status overlay
|
|
61
|
+
*/
|
|
44
62
|
let executionInfo = $derived(data.executionInfo);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Determine if status overlay should be shown.
|
|
66
|
+
* Hide for note nodes as they have their own styling.
|
|
67
|
+
*/
|
|
45
68
|
let shouldShowStatus = $derived(
|
|
46
69
|
shouldShowNodeStatus(executionInfo) && resolvedComponentName !== 'note'
|
|
47
70
|
);
|
|
48
71
|
|
|
49
72
|
/**
|
|
50
|
-
* Get the
|
|
73
|
+
* Get the node component for the given type.
|
|
74
|
+
* First tries the registry, then falls back to hardcoded components.
|
|
75
|
+
*
|
|
76
|
+
* @param nodeType - The node type identifier
|
|
77
|
+
* @returns The Svelte component to render
|
|
51
78
|
*/
|
|
52
79
|
function getNodeComponent(nodeType: string) {
|
|
53
|
-
|
|
80
|
+
// Resolve any aliases (e.g., "default" -> "workflowNode")
|
|
81
|
+
const resolvedType = resolveBuiltinAlias(nodeType);
|
|
82
|
+
|
|
83
|
+
// Try registry first
|
|
84
|
+
const registeredComponent = nodeComponentRegistry.getComponent(resolvedType);
|
|
85
|
+
if (registeredComponent) {
|
|
86
|
+
return registeredComponent;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Fallback to hardcoded switch for backwards compatibility
|
|
90
|
+
// This ensures the component works even if registry fails to initialize
|
|
91
|
+
switch (resolvedType) {
|
|
54
92
|
case 'note':
|
|
55
93
|
return NotesNode;
|
|
56
94
|
case 'simple':
|
|
@@ -68,40 +106,62 @@
|
|
|
68
106
|
}
|
|
69
107
|
|
|
70
108
|
/**
|
|
71
|
-
* Get optimal status position for this node type
|
|
109
|
+
* Get optimal status position for this node type.
|
|
110
|
+
* Uses registry if available, otherwise falls back to defaults.
|
|
72
111
|
*/
|
|
73
112
|
function getStatusPosition(): 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' {
|
|
74
|
-
|
|
113
|
+
// Try registry first
|
|
114
|
+
const position = nodeComponentRegistry.getStatusPosition(resolvedComponentName);
|
|
115
|
+
if (position) {
|
|
116
|
+
return position;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Fallback based on node type
|
|
120
|
+
switch (resolvedComponentName) {
|
|
121
|
+
case 'tool':
|
|
122
|
+
return 'top-left';
|
|
123
|
+
case 'note':
|
|
124
|
+
return 'bottom-right';
|
|
125
|
+
case 'simple':
|
|
126
|
+
case 'square':
|
|
127
|
+
default:
|
|
128
|
+
return 'top-right';
|
|
129
|
+
}
|
|
75
130
|
}
|
|
76
131
|
|
|
77
132
|
/**
|
|
78
|
-
* Get optimal status size for this node type
|
|
133
|
+
* Get optimal status size for this node type.
|
|
134
|
+
* Uses registry if available, otherwise falls back to defaults.
|
|
79
135
|
*/
|
|
80
136
|
function getStatusSize(): 'sm' | 'md' | 'lg' {
|
|
81
|
-
|
|
137
|
+
// Try registry first
|
|
138
|
+
const size = nodeComponentRegistry.getStatusSize(resolvedComponentName);
|
|
139
|
+
if (size) {
|
|
140
|
+
return size;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Fallback based on node type
|
|
144
|
+
switch (resolvedComponentName) {
|
|
145
|
+
case 'tool':
|
|
146
|
+
case 'note':
|
|
147
|
+
case 'square':
|
|
148
|
+
return 'sm';
|
|
149
|
+
case 'simple':
|
|
150
|
+
default:
|
|
151
|
+
return 'md';
|
|
152
|
+
}
|
|
82
153
|
}
|
|
83
154
|
</script>
|
|
84
155
|
|
|
85
156
|
<div class="universal-node">
|
|
86
|
-
<!-- Render the
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
{:else if nodeComponent === NotesNode}
|
|
90
|
-
<NotesNode {data} {selected} />
|
|
91
|
-
{:else if nodeComponent === SimpleNode}
|
|
92
|
-
<SimpleNode {data} {selected} />
|
|
93
|
-
{:else if nodeComponent === SquareNode}
|
|
94
|
-
<SquareNode {data} {selected} />
|
|
95
|
-
{:else if nodeComponent === ToolNode}
|
|
96
|
-
<ToolNode {data} {selected} />
|
|
97
|
-
{:else if nodeComponent === GatewayNode}
|
|
98
|
-
<GatewayNode {data} {selected} />
|
|
99
|
-
{/if}
|
|
157
|
+
<!-- Render the node component dynamically -->
|
|
158
|
+
<!-- svelte-ignore binding_property_non_reactive -->
|
|
159
|
+
<svelte:component this={nodeComponent} {data} {selected} />
|
|
100
160
|
|
|
101
161
|
<!-- Status overlay - only show if there's meaningful status information -->
|
|
102
162
|
{#if shouldShowStatus}
|
|
103
163
|
<NodeStatusOverlay
|
|
104
|
-
nodeId={data.nodeId
|
|
164
|
+
nodeId={data.nodeId ?? 'unknown'}
|
|
105
165
|
{executionInfo}
|
|
106
166
|
position={getStatusPosition()}
|
|
107
167
|
size={getStatusSize()}
|
|
@@ -80,14 +80,21 @@
|
|
|
80
80
|
|
|
81
81
|
$effect(() => {
|
|
82
82
|
if (currentWorkflow) {
|
|
83
|
-
|
|
83
|
+
const nodesWithCallbacks = currentWorkflow.nodes.map((node) => ({
|
|
84
84
|
...node,
|
|
85
85
|
data: {
|
|
86
86
|
...node.data,
|
|
87
87
|
onConfigOpen: props.openConfigSidebar
|
|
88
88
|
}
|
|
89
89
|
}));
|
|
90
|
-
|
|
90
|
+
flowNodes = nodesWithCallbacks;
|
|
91
|
+
|
|
92
|
+
// Apply edge styling based on source port data type when loading workflow
|
|
93
|
+
const styledEdges = EdgeStylingHelper.updateEdgeStyles(
|
|
94
|
+
currentWorkflow.edges,
|
|
95
|
+
nodesWithCallbacks
|
|
96
|
+
);
|
|
97
|
+
flowEdges = styledEdges;
|
|
91
98
|
|
|
92
99
|
// Only load execution info if we have a pipelineId (pipeline status mode)
|
|
93
100
|
// and if the workflow or pipeline has changed
|
|
@@ -523,7 +530,60 @@
|
|
|
523
530
|
pointer-events: all;
|
|
524
531
|
cursor: crosshair;
|
|
525
532
|
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Edge Styling Based on Source Port Data Type
|
|
536
|
+
* Uses CSS tokens from base.css for consistent theming
|
|
537
|
+
* - Trigger edges: solid dark line (control flow)
|
|
538
|
+
* - Tool edges: dashed amber line (tool connections)
|
|
539
|
+
* - Data edges: normal gray line (data flow)
|
|
540
|
+
*/
|
|
541
|
+
|
|
542
|
+
/* Trigger Edge: Solid dark line for control flow */
|
|
543
|
+
:global(.flowdrop--edge--trigger path.svelte-flow__edge-path) {
|
|
544
|
+
stroke: var(--flowdrop-edge-trigger-color);
|
|
545
|
+
stroke-width: var(--flowdrop-edge-trigger-width);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
:global(.flowdrop--edge--trigger:hover path.svelte-flow__edge-path) {
|
|
549
|
+
stroke: var(--flowdrop-edge-trigger-color-hover);
|
|
550
|
+
stroke-width: var(--flowdrop-edge-trigger-width-hover);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
:global(.flowdrop--edge--trigger.selected path.svelte-flow__edge-path) {
|
|
554
|
+
stroke: var(--flowdrop-edge-trigger-color-selected);
|
|
555
|
+
stroke-width: var(--flowdrop-edge-trigger-width-hover);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/* Tool Edge: Dashed amber line for tool connections */
|
|
526
559
|
:global(.flowdrop--edge--tool path.svelte-flow__edge-path) {
|
|
527
|
-
stroke
|
|
560
|
+
stroke: var(--flowdrop-edge-tool-color);
|
|
561
|
+
stroke-dasharray: 5 3;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
:global(.flowdrop--edge--tool:hover path.svelte-flow__edge-path) {
|
|
565
|
+
stroke: var(--flowdrop-edge-tool-color-hover);
|
|
566
|
+
stroke-width: 2;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
:global(.flowdrop--edge--tool.selected path.svelte-flow__edge-path) {
|
|
570
|
+
stroke: var(--flowdrop-edge-tool-color-selected);
|
|
571
|
+
stroke-dasharray: 5 3;
|
|
572
|
+
stroke-width: 2;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/* Data Edge: Normal gray line for data flow (default) */
|
|
576
|
+
:global(.flowdrop--edge--data path.svelte-flow__edge-path) {
|
|
577
|
+
stroke: var(--flowdrop-edge-data-color);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
:global(.flowdrop--edge--data:hover path.svelte-flow__edge-path) {
|
|
581
|
+
stroke: var(--flowdrop-edge-data-color-hover);
|
|
582
|
+
stroke-width: 2;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
:global(.flowdrop--edge--data.selected path.svelte-flow__edge-path) {
|
|
586
|
+
stroke: var(--flowdrop-edge-data-color-selected);
|
|
587
|
+
stroke-width: 2;
|
|
528
588
|
}
|
|
529
589
|
</style>
|
|
@@ -9,11 +9,11 @@ export interface RuntimeConfig {
|
|
|
9
9
|
/** Base URL for the FlowDrop API */
|
|
10
10
|
apiBaseUrl: string;
|
|
11
11
|
/** Theme preference */
|
|
12
|
-
theme:
|
|
12
|
+
theme: 'light' | 'dark' | 'auto';
|
|
13
13
|
/** Request timeout in milliseconds */
|
|
14
14
|
timeout: number;
|
|
15
15
|
/** Authentication type */
|
|
16
|
-
authType:
|
|
16
|
+
authType: 'none' | 'bearer' | 'api_key' | 'custom';
|
|
17
17
|
/** Authentication token */
|
|
18
18
|
authToken?: string;
|
|
19
19
|
/** Application version */
|