@d34dman/flowdrop 0.0.16 → 0.0.18
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 +106 -0
- package/dist/api/enhanced-client.d.ts +3 -3
- package/dist/api/enhanced-client.js +57 -57
- package/dist/components/FlowDropZone.svelte +4 -5
- package/dist/components/FlowDropZone.svelte.d.ts +1 -1
- package/dist/components/TerminalNode.svelte +565 -0
- package/dist/components/TerminalNode.svelte.d.ts +24 -0
- 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/helpers/workflowEditorHelper.d.ts +44 -4
- package/dist/helpers/workflowEditorHelper.js +161 -30
- package/dist/index.d.ts +16 -13
- package/dist/index.js +19 -8
- package/dist/registry/builtinNodes.d.ts +77 -0
- package/dist/registry/builtinNodes.js +194 -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 +1 -1
- package/dist/services/draftStorage.js +5 -5
- package/dist/stores/workflowStore.d.ts +2 -2
- package/dist/stores/workflowStore.js +16 -16
- package/dist/styles/base.css +15 -0
- package/dist/svelte-app.d.ts +6 -6
- package/dist/svelte-app.js +25 -25
- package/dist/types/auth.d.ts +2 -2
- package/dist/types/auth.js +7 -7
- package/dist/types/events.d.ts +2 -2
- package/dist/types/index.d.ts +38 -3
- package/dist/utils/nodeTypes.d.ts +76 -21
- package/dist/utils/nodeTypes.js +182 -32
- package/package.json +2 -2
|
@@ -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 */
|
|
@@ -24,7 +24,7 @@ export async function fetchRuntimeConfig(force = false) {
|
|
|
24
24
|
return cachedConfig;
|
|
25
25
|
}
|
|
26
26
|
try {
|
|
27
|
-
const response = await fetch(
|
|
27
|
+
const response = await fetch('/api/config');
|
|
28
28
|
if (!response.ok) {
|
|
29
29
|
throw new Error(`Failed to fetch runtime config: ${response.statusText}`);
|
|
30
30
|
}
|
|
@@ -35,15 +35,15 @@ export async function fetchRuntimeConfig(force = false) {
|
|
|
35
35
|
return config;
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
|
-
console.error(
|
|
38
|
+
console.error('Failed to fetch runtime configuration:', error);
|
|
39
39
|
// Return default configuration if fetch fails
|
|
40
40
|
const defaultConfig = {
|
|
41
|
-
apiBaseUrl:
|
|
42
|
-
theme:
|
|
41
|
+
apiBaseUrl: '/api/flowdrop',
|
|
42
|
+
theme: 'auto',
|
|
43
43
|
timeout: 30000,
|
|
44
|
-
authType:
|
|
45
|
-
version:
|
|
46
|
-
environment:
|
|
44
|
+
authType: 'none',
|
|
45
|
+
version: '1.0.0',
|
|
46
|
+
environment: 'production'
|
|
47
47
|
};
|
|
48
48
|
// Cache the default config to avoid repeated failed requests
|
|
49
49
|
if (!cachedConfig) {
|
|
@@ -11,13 +11,53 @@ import type { EndpointConfig } from '../config/endpoints.js';
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function generateNodeId(nodeTypeId: string, existingNodes: WorkflowNodeType[]): string;
|
|
13
13
|
/**
|
|
14
|
-
* Edge styling
|
|
14
|
+
* Edge category type for styling purposes
|
|
15
|
+
* - trigger: For control flow connections (dataType: "trigger")
|
|
16
|
+
* - tool: Dashed amber line for tool connections (dataType: "tool")
|
|
17
|
+
* - data: Normal gray line for all other data connections
|
|
18
|
+
*/
|
|
19
|
+
export type EdgeCategory = 'trigger' | 'tool' | 'data';
|
|
20
|
+
/**
|
|
21
|
+
* Edge styling configuration based on source port data type
|
|
15
22
|
*/
|
|
16
23
|
export declare class EdgeStylingHelper {
|
|
17
24
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
25
|
+
* Extract the port ID from a handle ID
|
|
26
|
+
* Supports two formats:
|
|
27
|
+
* 1. New format: "${nodeId}-output-${portId}" or "${nodeId}-input-${portId}"
|
|
28
|
+
* 2. Legacy format: just the portId (e.g., "text", "trigger")
|
|
29
|
+
* @param handleId - The handle ID string (e.g., "sample-node.1-output-trigger" or "trigger")
|
|
30
|
+
* @returns The port ID (e.g., "trigger") or the handleId itself for legacy format
|
|
31
|
+
*/
|
|
32
|
+
static extractPortIdFromHandle(handleId: string | undefined): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Check if a port ID matches a dynamic branch in a Gateway node
|
|
35
|
+
* Gateway nodes store branches in config.branches array
|
|
36
|
+
* @param node - The workflow node to check
|
|
37
|
+
* @param portId - The port ID to look up
|
|
38
|
+
* @returns true if the portId matches a gateway branch
|
|
39
|
+
*/
|
|
40
|
+
static isGatewayBranch(node: WorkflowNodeType, portId: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Get the data type of a port from a node's metadata
|
|
43
|
+
* Also handles dynamic ports like Gateway branches
|
|
44
|
+
* @param node - The workflow node containing the port
|
|
45
|
+
* @param portId - The port ID to look up
|
|
46
|
+
* @param portType - Whether to look in "inputs" or "outputs"
|
|
47
|
+
* @returns The port's dataType or null if not found
|
|
48
|
+
*/
|
|
49
|
+
static getPortDataType(node: WorkflowNodeType, portId: string, portType: 'input' | 'output'): string | null;
|
|
50
|
+
/**
|
|
51
|
+
* Determine the edge category based on source port data type
|
|
52
|
+
* @param sourcePortDataType - The data type of the source output port
|
|
53
|
+
* @returns The edge category for styling
|
|
54
|
+
*/
|
|
55
|
+
static getEdgeCategory(sourcePortDataType: string | null): EdgeCategory;
|
|
56
|
+
/**
|
|
57
|
+
* Apply custom styling to connection edges based on source port data type:
|
|
58
|
+
* - Trigger ports: Solid black line with arrow
|
|
59
|
+
* - Tool ports: Dashed amber line with arrow
|
|
60
|
+
* - Data ports: Normal gray line with arrow
|
|
21
61
|
*/
|
|
22
62
|
static applyConnectionStyling(edge: WorkflowEdge, sourceNode: WorkflowNodeType, targetNode: WorkflowNodeType): void;
|
|
23
63
|
/**
|
|
@@ -30,44 +30,166 @@ export function generateNodeId(nodeTypeId, existingNodes) {
|
|
|
30
30
|
return `${nodeTypeId}.${nextNumber}`;
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
* Edge styling configuration
|
|
33
|
+
* Edge styling configuration based on source port data type
|
|
34
34
|
*/
|
|
35
35
|
export class EdgeStylingHelper {
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* Extract the port ID from a handle ID
|
|
38
|
+
* Supports two formats:
|
|
39
|
+
* 1. New format: "${nodeId}-output-${portId}" or "${nodeId}-input-${portId}"
|
|
40
|
+
* 2. Legacy format: just the portId (e.g., "text", "trigger")
|
|
41
|
+
* @param handleId - The handle ID string (e.g., "sample-node.1-output-trigger" or "trigger")
|
|
42
|
+
* @returns The port ID (e.g., "trigger") or the handleId itself for legacy format
|
|
40
43
|
*/
|
|
41
|
-
static
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
static extractPortIdFromHandle(handleId) {
|
|
45
|
+
if (!handleId) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
// Try new format: "${nodeId}-output-${portId}" or "${nodeId}-input-${portId}"
|
|
49
|
+
// We need to find the last occurrence of "-output-" or "-input-" and get what follows
|
|
50
|
+
const outputMatch = handleId.lastIndexOf('-output-');
|
|
51
|
+
const inputMatch = handleId.lastIndexOf('-input-');
|
|
52
|
+
if (outputMatch !== -1) {
|
|
53
|
+
return handleId.substring(outputMatch + '-output-'.length);
|
|
54
|
+
}
|
|
55
|
+
if (inputMatch !== -1) {
|
|
56
|
+
return handleId.substring(inputMatch + '-input-'.length);
|
|
57
|
+
}
|
|
58
|
+
// Legacy format: the handleId IS the port ID
|
|
59
|
+
return handleId;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if a port ID matches a dynamic branch in a Gateway node
|
|
63
|
+
* Gateway nodes store branches in config.branches array
|
|
64
|
+
* @param node - The workflow node to check
|
|
65
|
+
* @param portId - The port ID to look up
|
|
66
|
+
* @returns true if the portId matches a gateway branch
|
|
67
|
+
*/
|
|
68
|
+
static isGatewayBranch(node, portId) {
|
|
69
|
+
// Check if this is a gateway node with dynamic branches
|
|
70
|
+
const nodeType = node.data?.metadata?.type || node.type;
|
|
71
|
+
if (nodeType !== 'gateway') {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
// Check if the portId matches a branch name in config.branches
|
|
75
|
+
const branches = node.data?.config?.branches;
|
|
76
|
+
if (!branches || !Array.isArray(branches)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return branches.some((branch) => branch.name === portId);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the data type of a port from a node's metadata
|
|
83
|
+
* Also handles dynamic ports like Gateway branches
|
|
84
|
+
* @param node - The workflow node containing the port
|
|
85
|
+
* @param portId - The port ID to look up
|
|
86
|
+
* @param portType - Whether to look in "inputs" or "outputs"
|
|
87
|
+
* @returns The port's dataType or null if not found
|
|
88
|
+
*/
|
|
89
|
+
static getPortDataType(node, portId, portType) {
|
|
90
|
+
// First, check static ports in metadata
|
|
91
|
+
const ports = portType === 'output' ? node.data?.metadata?.outputs : node.data?.metadata?.inputs;
|
|
92
|
+
if (ports && Array.isArray(ports)) {
|
|
93
|
+
const port = ports.find((p) => p.id === portId);
|
|
94
|
+
if (port?.dataType) {
|
|
95
|
+
return port.dataType;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// For output ports, also check dynamic Gateway branches
|
|
99
|
+
// Gateway branches are always trigger type (control flow)
|
|
100
|
+
if (portType === 'output' && this.isGatewayBranch(node, portId)) {
|
|
101
|
+
return 'trigger';
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Determine the edge category based on source port data type
|
|
107
|
+
* @param sourcePortDataType - The data type of the source output port
|
|
108
|
+
* @returns The edge category for styling
|
|
109
|
+
*/
|
|
110
|
+
static getEdgeCategory(sourcePortDataType) {
|
|
111
|
+
if (sourcePortDataType === 'trigger') {
|
|
112
|
+
return 'trigger';
|
|
113
|
+
}
|
|
114
|
+
if (sourcePortDataType === 'tool') {
|
|
115
|
+
return 'tool';
|
|
50
116
|
}
|
|
51
|
-
|
|
52
|
-
|
|
117
|
+
// All other data types (string, number, boolean, array, etc.) are "data" edges
|
|
118
|
+
return 'data';
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Apply custom styling to connection edges based on source port data type:
|
|
122
|
+
* - Trigger ports: Solid black line with arrow
|
|
123
|
+
* - Tool ports: Dashed amber line with arrow
|
|
124
|
+
* - Data ports: Normal gray line with arrow
|
|
125
|
+
*/
|
|
126
|
+
static applyConnectionStyling(edge, sourceNode, targetNode) {
|
|
127
|
+
// Extract port ID from sourceHandle
|
|
128
|
+
const sourcePortId = this.extractPortIdFromHandle(edge.sourceHandle);
|
|
129
|
+
// Get the source port's data type
|
|
130
|
+
const sourcePortDataType = sourcePortId
|
|
131
|
+
? this.getPortDataType(sourceNode, sourcePortId, 'output')
|
|
132
|
+
: null;
|
|
133
|
+
// Determine edge category based on source port data type
|
|
134
|
+
const edgeCategory = this.getEdgeCategory(sourcePortDataType);
|
|
135
|
+
// Edge color constants (matching CSS tokens in base.css)
|
|
136
|
+
const EDGE_COLORS = {
|
|
137
|
+
trigger: '#111827', // --color-ref-gray-900
|
|
138
|
+
tool: '#f59e0b', // --color-ref-amber-500
|
|
139
|
+
data: '#9ca3af' // --color-ref-gray-400
|
|
140
|
+
};
|
|
141
|
+
// Apply styling based on edge category
|
|
142
|
+
// CSS classes handle styling via tokens; inline styles are fallback
|
|
143
|
+
switch (edgeCategory) {
|
|
144
|
+
case 'trigger':
|
|
145
|
+
// Trigger edges: solid dark line for control flow
|
|
146
|
+
edge.style =
|
|
147
|
+
'stroke: var(--flowdrop-edge-trigger-color); stroke-width: var(--flowdrop-edge-trigger-width);';
|
|
148
|
+
edge.class = 'flowdrop--edge--trigger';
|
|
149
|
+
edge.markerEnd = {
|
|
150
|
+
type: MarkerType.ArrowClosed,
|
|
151
|
+
width: 16,
|
|
152
|
+
height: 16,
|
|
153
|
+
color: EDGE_COLORS.trigger
|
|
154
|
+
};
|
|
155
|
+
break;
|
|
156
|
+
case 'tool':
|
|
157
|
+
// Tool edges: dashed amber line
|
|
158
|
+
edge.style = 'stroke: var(--flowdrop-edge-tool-color); stroke-dasharray: 5 3;';
|
|
159
|
+
edge.class = 'flowdrop--edge--tool';
|
|
160
|
+
edge.markerEnd = {
|
|
161
|
+
type: MarkerType.ArrowClosed,
|
|
162
|
+
width: 16,
|
|
163
|
+
height: 16,
|
|
164
|
+
color: EDGE_COLORS.tool
|
|
165
|
+
};
|
|
166
|
+
break;
|
|
167
|
+
case 'data':
|
|
168
|
+
default:
|
|
169
|
+
// Data edges: normal gray line
|
|
170
|
+
edge.style = 'stroke: var(--flowdrop-edge-data-color);';
|
|
171
|
+
edge.class = 'flowdrop--edge--data';
|
|
172
|
+
edge.markerEnd = {
|
|
173
|
+
type: MarkerType.ArrowClosed,
|
|
174
|
+
width: 16,
|
|
175
|
+
height: 16,
|
|
176
|
+
color: EDGE_COLORS.data
|
|
177
|
+
};
|
|
178
|
+
break;
|
|
53
179
|
}
|
|
54
|
-
// Store metadata in edge data for
|
|
180
|
+
// Store metadata in edge data for API and persistence
|
|
55
181
|
edge.data = {
|
|
56
182
|
...edge.data,
|
|
57
|
-
|
|
183
|
+
metadata: {
|
|
184
|
+
...(edge.data?.metadata || {}),
|
|
185
|
+
edgeType: edgeCategory,
|
|
186
|
+
sourcePortDataType: sourcePortDataType || undefined
|
|
187
|
+
},
|
|
188
|
+
// Keep legacy fields for backward compatibility
|
|
189
|
+
isToolConnection: edgeCategory === 'tool',
|
|
58
190
|
targetNodeType: targetNode.type,
|
|
59
191
|
targetCategory: targetNode.data.metadata.category
|
|
60
192
|
};
|
|
61
|
-
// Rule 2: Always add arrow pointing towards input port
|
|
62
|
-
// This replaces the default arrows we removed
|
|
63
|
-
if (!isToolNode) {
|
|
64
|
-
edge.markerEnd = {
|
|
65
|
-
type: MarkerType.ArrowClosed,
|
|
66
|
-
width: 16,
|
|
67
|
-
height: 16,
|
|
68
|
-
color: 'grey'
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
193
|
}
|
|
72
194
|
/**
|
|
73
195
|
* Update existing edges with custom styling rules
|
|
@@ -77,11 +199,20 @@ export class EdgeStylingHelper {
|
|
|
77
199
|
// Find source and target nodes
|
|
78
200
|
const sourceNode = nodes.find((node) => node.id === edge.source);
|
|
79
201
|
const targetNode = nodes.find((node) => node.id === edge.target);
|
|
202
|
+
// Create a copy of the edge
|
|
203
|
+
const updatedEdge = { ...edge };
|
|
80
204
|
if (!sourceNode || !targetNode) {
|
|
81
|
-
|
|
205
|
+
// Set default edgeType even when nodes are not found
|
|
206
|
+
updatedEdge.data = {
|
|
207
|
+
...updatedEdge.data,
|
|
208
|
+
metadata: {
|
|
209
|
+
...(updatedEdge.data?.metadata || {}),
|
|
210
|
+
edgeType: 'data'
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
return updatedEdge;
|
|
82
214
|
}
|
|
83
|
-
//
|
|
84
|
-
const updatedEdge = { ...edge };
|
|
215
|
+
// Apply full styling when nodes are available
|
|
85
216
|
this.applyConnectionStyling(updatedEdge, sourceNode, targetNode);
|
|
86
217
|
return updatedEdge;
|
|
87
218
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
* FlowDrop - Visual Workflow Editor Library
|
|
3
3
|
* A Svelte 5 component library built on @xyflow/svelte for creating node-based workflow editors
|
|
4
4
|
*/
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
export type {
|
|
8
|
-
export type {
|
|
9
|
-
export {
|
|
10
|
-
export
|
|
11
|
-
export {
|
|
5
|
+
import './styles/base.css';
|
|
6
|
+
import './registry/builtinNodes.js';
|
|
7
|
+
export type { NodeCategory, NodeDataType, NodePort, NodeMetadata, NodeConfig, WorkflowNode, WorkflowEdge, Workflow, ApiResponse, NodesResponse, WorkflowResponse, WorkflowsResponse, ExecutionStatus, ExecutionResult, FlowDropConfig, WorkflowEvents, BuiltinNodeType } from './types/index.js';
|
|
8
|
+
export type { WorkflowEditorConfig, EditorFeatures, UIConfig, APIConfig, ExecutionConfig, StorageConfig, NodeType, WorkflowData, ExecutionResult as EditorExecutionResult, EditorState } from './types/config.js';
|
|
9
|
+
export type { AuthProvider, StaticAuthConfig, CallbackAuthConfig } from './types/auth.js';
|
|
10
|
+
export { StaticAuthProvider, CallbackAuthProvider, NoAuthProvider, createAuthProviderFromLegacyConfig } from './types/auth.js';
|
|
11
|
+
export type { WorkflowChangeType, FlowDropEventHandlers, FlowDropFeatures } from './types/events.js';
|
|
12
|
+
export { DEFAULT_FEATURES, mergeFeatures } from './types/events.js';
|
|
12
13
|
export { FlowDropApiClient } from './api/client.js';
|
|
13
14
|
export { EnhancedFlowDropApiClient } from './api/enhanced-client.js';
|
|
14
15
|
export { default as WorkflowEditor } from './components/WorkflowEditor.svelte';
|
|
@@ -43,16 +44,18 @@ export * from './utils/nodeTypes.js';
|
|
|
43
44
|
export { getStatusColor, getStatusIcon, getStatusLabel, getStatusBackgroundColor, getStatusTextColor, createDefaultExecutionInfo, updateExecutionStart, updateExecutionComplete, updateExecutionFailed, resetExecutionInfo, formatExecutionDuration, formatLastExecuted } from './utils/nodeStatus.js';
|
|
44
45
|
export { createNodeWrapperConfig, shouldShowNodeStatus, getOptimalStatusPosition, getOptimalStatusSize, DEFAULT_NODE_STATUS_CONFIG } from './utils/nodeWrapper.js';
|
|
45
46
|
export type { NodeStatusConfig } from './utils/nodeWrapper.js';
|
|
47
|
+
export { nodeComponentRegistry, createNamespacedType, parseNamespacedType, BUILTIN_NODE_COMPONENTS, BUILTIN_NODE_TYPES, FLOWDROP_SOURCE, registerBuiltinNodes, areBuiltinsRegistered, isBuiltinType, getBuiltinTypes, resolveBuiltinAlias, registerFlowDropPlugin, unregisterFlowDropPlugin, registerCustomNode, createPlugin, isValidNamespace, getRegisteredPlugins, getPluginNodeCount } from './registry/index.js';
|
|
48
|
+
export type { NodeComponentProps, NodeComponentRegistration, NodeComponentCategory, StatusPosition, StatusSize, NodeRegistrationFilter, FlowDropPluginConfig, PluginNodeDefinition, PluginRegistrationResult } from './registry/index.js';
|
|
46
49
|
export * from './services/api.js';
|
|
47
50
|
export { showSuccess, showError, showWarning, showInfo, showLoading, dismissToast, dismissAllToasts, showPromise, showConfirmation, apiToasts, workflowToasts, pipelineToasts } from './services/toastService.js';
|
|
48
51
|
export type { ToastType, ToastOptions } from './services/toastService.js';
|
|
49
52
|
export { NodeExecutionService, nodeExecutionService } from './services/nodeExecutionService.js';
|
|
50
53
|
export { saveWorkflow, updateWorkflow, getWorkflow, getWorkflows, deleteWorkflow, getWorkflowCount, initializeSampleWorkflows } from './services/workflowStorage.js';
|
|
51
54
|
export { globalSaveWorkflow, globalExportWorkflow, initializeGlobalSave } from './services/globalSave.js';
|
|
52
|
-
export { fetchPortConfig, validatePortConfig } from
|
|
53
|
-
export { getDraftStorageKey, saveDraft, loadDraft, deleteDraft, hasDraft, getDraftMetadata, DraftAutoSaveManager } from
|
|
55
|
+
export { fetchPortConfig, validatePortConfig } from './services/portConfigApi.js';
|
|
56
|
+
export { getDraftStorageKey, saveDraft, loadDraft, deleteDraft, hasDraft, getDraftMetadata, DraftAutoSaveManager } from './services/draftStorage.js';
|
|
54
57
|
export { EdgeStylingHelper, NodeOperationsHelper, WorkflowOperationsHelper, ConfigurationHelper } from './helpers/workflowEditorHelper.js';
|
|
55
|
-
export { workflowStore, workflowActions, workflowId, workflowName, workflowNodes, workflowEdges, workflowMetadata, workflowChanged, workflowValidation, workflowMetadataChanged, isDirtyStore, isDirty, markAsSaved, getWorkflow as getWorkflowFromStore, setOnDirtyStateChange, setOnWorkflowChange } from
|
|
58
|
+
export { workflowStore, workflowActions, workflowId, workflowName, workflowNodes, workflowEdges, workflowMetadata, workflowChanged, workflowValidation, workflowMetadataChanged, isDirtyStore, isDirty, markAsSaved, getWorkflow as getWorkflowFromStore, setOnDirtyStateChange, setOnWorkflowChange } from './stores/workflowStore.js';
|
|
56
59
|
export * from './config/endpoints.js';
|
|
57
60
|
export { defaultApiConfig, getEndpointUrl } from './config/apiConfig.js';
|
|
58
61
|
export type { ApiConfig } from './config/apiConfig.js';
|
|
@@ -60,6 +63,6 @@ export { DEFAULT_PORT_CONFIG } from './config/defaultPortConfig.js';
|
|
|
60
63
|
export * from './config/runtimeConfig.js';
|
|
61
64
|
export * from './adapters/WorkflowAdapter.js';
|
|
62
65
|
export * from './clients/ApiClient.js';
|
|
63
|
-
export { mountWorkflowEditor, unmountWorkflowEditor, mountFlowDropApp, unmountFlowDropApp } from
|
|
64
|
-
export type { FlowDropMountOptions, MountedFlowDropApp, NavbarAction } from
|
|
65
|
-
export { ApiError } from
|
|
66
|
+
export { mountWorkflowEditor, unmountWorkflowEditor, mountFlowDropApp, unmountFlowDropApp } from './svelte-app.js';
|
|
67
|
+
export type { FlowDropMountOptions, MountedFlowDropApp, NavbarAction } from './svelte-app.js';
|
|
68
|
+
export { ApiError } from './api/enhanced-client.js';
|