@flowdrop/flowdrop 1.2.2 → 1.4.0
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 +80 -5
- package/dist/components/App.svelte.d.ts +7 -0
- package/dist/components/CanvasBanner.stories.svelte +6 -2
- package/dist/components/ConfigForm.svelte +421 -0
- package/dist/components/FlowDropEdge.svelte +7 -48
- package/dist/components/Logo.svelte +14 -14
- package/dist/components/Navbar.svelte +58 -10
- package/dist/components/Navbar.svelte.d.ts +7 -0
- package/dist/components/NodeSidebar.svelte +236 -304
- package/dist/components/WorkflowEditor.svelte +16 -6
- package/dist/components/nodes/GatewayNode.svelte +8 -9
- package/dist/components/nodes/SimpleNode.stories.svelte +64 -0
- package/dist/components/nodes/SimpleNode.svelte +93 -104
- package/dist/components/nodes/SquareNode.stories.svelte +45 -0
- package/dist/components/nodes/SquareNode.svelte +94 -104
- package/dist/components/nodes/TerminalNode.svelte +6 -7
- package/dist/components/nodes/WorkflowNode.stories.svelte +63 -0
- package/dist/components/nodes/WorkflowNode.svelte +54 -19
- package/dist/schemas/v1/workflow.schema.json +22 -107
- package/dist/skins/slate.js +16 -0
- package/dist/stories/EdgeDecorator.svelte +4 -4
- package/dist/styles/base.css +48 -0
- package/dist/svelte-app.d.ts +7 -1
- package/dist/svelte-app.js +4 -1
- package/dist/types/index.d.ts +17 -0
- package/dist/utils/portUtils.d.ts +24 -0
- package/dist/utils/portUtils.js +42 -0
- package/package.json +3 -3
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
Square Node Component
|
|
3
3
|
A simple square node with optional input and output ports
|
|
4
4
|
Styled with BEM syntax
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
UI Extensions Support:
|
|
7
|
-
- hideUnconnectedHandles: Hides
|
|
7
|
+
- hideUnconnectedHandles: Hides ports that are not connected to reduce visual clutter
|
|
8
|
+
- hiddenPorts: Manually hide individual ports (visual-only, no effect on execution)
|
|
9
|
+
- portOrder: Reorder ports by ID array (unspecified ports appear at end in original order)
|
|
8
10
|
-->
|
|
9
11
|
|
|
10
12
|
<script lang="ts">
|
|
@@ -14,7 +16,9 @@
|
|
|
14
16
|
NodeMetadata,
|
|
15
17
|
NodeExtensions,
|
|
16
18
|
NodePort,
|
|
19
|
+
DynamicPort,
|
|
17
20
|
} from "../../types/index.js";
|
|
21
|
+
import { dynamicPortToNodePort } from "../../types/index.js";
|
|
18
22
|
import Icon from "@iconify/svelte";
|
|
19
23
|
import {
|
|
20
24
|
getDataTypeColor,
|
|
@@ -22,6 +26,11 @@
|
|
|
22
26
|
} from "../../utils/colors.js";
|
|
23
27
|
import { getNodeIcon } from "../../utils/icons.js";
|
|
24
28
|
import { getConnectedHandles } from "../../stores/workflowStore.svelte.js";
|
|
29
|
+
import {
|
|
30
|
+
applyPortOrder,
|
|
31
|
+
getPortTop,
|
|
32
|
+
isPortVisible,
|
|
33
|
+
} from "../../utils/portUtils.js";
|
|
25
34
|
import CogIcon from "../icons/CogIcon.svelte";
|
|
26
35
|
import AlertCircleIcon from "../icons/AlertCircleIcon.svelte";
|
|
27
36
|
|
|
@@ -44,15 +53,25 @@
|
|
|
44
53
|
}>();
|
|
45
54
|
|
|
46
55
|
/**
|
|
47
|
-
* Get
|
|
48
|
-
* Merges node type defaults with instance overrides
|
|
56
|
+
* Get UI extension settings from extensions, merging node type defaults with instance overrides.
|
|
49
57
|
*/
|
|
50
|
-
const hideUnconnectedHandles = $derived(
|
|
51
|
-
|
|
52
|
-
props.data.metadata?.extensions?.ui?.hideUnconnectedHandles ??
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
const hideUnconnectedHandles = $derived(
|
|
59
|
+
props.data.extensions?.ui?.hideUnconnectedHandles ??
|
|
60
|
+
props.data.metadata?.extensions?.ui?.hideUnconnectedHandles ??
|
|
61
|
+
false,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const hiddenPorts = $derived(
|
|
65
|
+
props.data.extensions?.ui?.hiddenPorts ??
|
|
66
|
+
props.data.metadata?.extensions?.ui?.hiddenPorts ??
|
|
67
|
+
{},
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const portOrder = $derived(
|
|
71
|
+
props.data.extensions?.ui?.portOrder ??
|
|
72
|
+
props.data.metadata?.extensions?.ui?.portOrder ??
|
|
73
|
+
{},
|
|
74
|
+
);
|
|
56
75
|
|
|
57
76
|
/**
|
|
58
77
|
* Get icon using the same resolution as WorkflowNode
|
|
@@ -100,111 +119,83 @@
|
|
|
100
119
|
openConfigSidebar();
|
|
101
120
|
}
|
|
102
121
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
* @param type - Whether this is an 'input' or 'output' port
|
|
107
|
-
* @returns true if the port is connected
|
|
108
|
-
*/
|
|
109
|
-
function isPortConnected(portId: string, type: "input" | "output"): boolean {
|
|
110
|
-
const handleId = `${props.data.nodeId}-${type}-${portId}`;
|
|
111
|
-
return getConnectedHandles().has(handleId);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Check if a trigger port should be visible
|
|
116
|
-
* Always shows if hideUnconnectedHandles is disabled or if port is connected
|
|
117
|
-
*/
|
|
118
|
-
function shouldShowTriggerPort(
|
|
119
|
-
portId: string,
|
|
120
|
-
type: "input" | "output",
|
|
121
|
-
): boolean {
|
|
122
|
-
if (!hideUnconnectedHandles()) {
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
return isPortConnected(portId, type);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Get first input/output ports for square node representation
|
|
129
|
-
// Special handling for trigger ports - they should always be shown if present
|
|
130
|
-
let triggerInputPort = $derived(
|
|
131
|
-
props.data.metadata?.inputs?.find(
|
|
132
|
-
(port: NodePort) => port.dataType === "trigger",
|
|
133
|
-
),
|
|
134
|
-
);
|
|
135
|
-
let triggerOutputPort = $derived(
|
|
136
|
-
props.data.metadata?.outputs?.find(
|
|
137
|
-
(port: NodePort) => port.dataType === "trigger",
|
|
122
|
+
const dynamicInputs = $derived(
|
|
123
|
+
((props.data.config?.dynamicInputs as DynamicPort[]) || []).map((port) =>
|
|
124
|
+
dynamicPortToNodePort(port, "input"),
|
|
138
125
|
),
|
|
139
126
|
);
|
|
140
127
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
(port: NodePort) =>
|
|
145
|
-
port.dataType !== "trigger" && isPortConnected(port.id, "input"),
|
|
128
|
+
const dynamicOutputs = $derived(
|
|
129
|
+
((props.data.config?.dynamicOutputs as DynamicPort[]) || []).map((port) =>
|
|
130
|
+
dynamicPortToNodePort(port, "output"),
|
|
146
131
|
),
|
|
147
132
|
);
|
|
148
133
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
134
|
+
/**
|
|
135
|
+
* All visible input ports in user-defined order.
|
|
136
|
+
*/
|
|
137
|
+
const visibleInputPorts = $derived(
|
|
138
|
+
applyPortOrder(
|
|
139
|
+
[...(props.data.metadata?.inputs ?? []), ...dynamicInputs],
|
|
140
|
+
portOrder.inputs,
|
|
141
|
+
).filter((p: NodePort) =>
|
|
142
|
+
isPortVisible(
|
|
143
|
+
p,
|
|
144
|
+
"input",
|
|
145
|
+
hiddenPorts,
|
|
146
|
+
hideUnconnectedHandles,
|
|
147
|
+
getConnectedHandles(),
|
|
148
|
+
props.data.nodeId,
|
|
149
|
+
),
|
|
152
150
|
),
|
|
153
151
|
);
|
|
154
152
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
(
|
|
153
|
+
/**
|
|
154
|
+
* All visible output ports in user-defined order.
|
|
155
|
+
*/
|
|
156
|
+
const visibleOutputPorts = $derived(
|
|
157
|
+
applyPortOrder(
|
|
158
|
+
[...(props.data.metadata?.outputs ?? []), ...dynamicOutputs],
|
|
159
|
+
portOrder.outputs,
|
|
160
|
+
).filter((p: NodePort) =>
|
|
161
|
+
isPortVisible(
|
|
162
|
+
p,
|
|
163
|
+
"output",
|
|
164
|
+
hiddenPorts,
|
|
165
|
+
hideUnconnectedHandles,
|
|
166
|
+
getConnectedHandles(),
|
|
167
|
+
props.data.nodeId,
|
|
168
|
+
),
|
|
164
169
|
),
|
|
165
170
|
);
|
|
166
171
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return [
|
|
182
|
-
...(firstConnectedDataOutputPort
|
|
183
|
-
? [firstConnectedDataOutputPort]
|
|
184
|
-
: firstDataOutputPort
|
|
185
|
-
? [firstDataOutputPort]
|
|
186
|
-
: []),
|
|
187
|
-
...(triggerOutputPort &&
|
|
188
|
-
shouldShowTriggerPort(triggerOutputPort.id, "output")
|
|
189
|
-
? [triggerOutputPort]
|
|
190
|
-
: []),
|
|
191
|
-
];
|
|
192
|
-
});
|
|
172
|
+
/**
|
|
173
|
+
* Dynamic node size so handles never render outside the node body.
|
|
174
|
+
* Overrides the fixed CSS height/width when more than 2 ports are visible on either side.
|
|
175
|
+
*/
|
|
176
|
+
const nodeSize = $derived(
|
|
177
|
+
(() => {
|
|
178
|
+
const maxPorts = Math.max(
|
|
179
|
+
visibleInputPorts.length,
|
|
180
|
+
visibleOutputPorts.length,
|
|
181
|
+
1,
|
|
182
|
+
);
|
|
183
|
+
return maxPorts <= 1 ? 80 : 20 + maxPorts * 40;
|
|
184
|
+
})(),
|
|
185
|
+
);
|
|
193
186
|
</script>
|
|
194
187
|
|
|
195
|
-
<!-- Input Handles:
|
|
196
|
-
{#each
|
|
188
|
+
<!-- Input Handles: 1 port centered at 40px; N ports at 20px start, 40px gap -->
|
|
189
|
+
{#each visibleInputPorts as port, index}
|
|
197
190
|
<Handle
|
|
198
191
|
type="target"
|
|
199
192
|
position={Position.Left}
|
|
200
193
|
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
201
194
|
port.dataType,
|
|
202
|
-
)}); --fd-handle-border-color: var(--fd-handle-border); top: {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
: 60
|
|
207
|
-
: 40}px; transform: translateY(-50%); z-index: 30;"
|
|
195
|
+
)}); --fd-handle-border-color: var(--fd-handle-border); top: {getPortTop(
|
|
196
|
+
index,
|
|
197
|
+
visibleInputPorts.length,
|
|
198
|
+
)}px; transform: translateY(-50%); z-index: 30;"
|
|
208
199
|
id={`${props.data.nodeId}-input-${port.id}`}
|
|
209
200
|
/>
|
|
210
201
|
{/each}
|
|
@@ -215,6 +206,7 @@
|
|
|
215
206
|
class:flowdrop-square-node--selected={props.selected}
|
|
216
207
|
class:flowdrop-square-node--processing={props.isProcessing}
|
|
217
208
|
class:flowdrop-square-node--error={props.isError}
|
|
209
|
+
style="height: {nodeSize}px; width: {nodeSize}px"
|
|
218
210
|
onclick={handleClick}
|
|
219
211
|
ondblclick={handleDoubleClick}
|
|
220
212
|
onkeydown={handleKeydown}
|
|
@@ -261,19 +253,17 @@
|
|
|
261
253
|
</button>
|
|
262
254
|
</div>
|
|
263
255
|
|
|
264
|
-
<!-- Output Handles:
|
|
265
|
-
{#each
|
|
256
|
+
<!-- Output Handles: 1 port centered at 40px; N ports at 20px start, 40px gap -->
|
|
257
|
+
{#each visibleOutputPorts as port, index}
|
|
266
258
|
<Handle
|
|
267
259
|
type="source"
|
|
268
260
|
position={Position.Right}
|
|
269
261
|
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
270
262
|
port.dataType,
|
|
271
|
-
)}); --fd-handle-border-color: var(--fd-handle-border); top: {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
: 60
|
|
276
|
-
: 40}px; transform: translateY(-50%); z-index: 30;"
|
|
263
|
+
)}); --fd-handle-border-color: var(--fd-handle-border); top: {getPortTop(
|
|
264
|
+
index,
|
|
265
|
+
visibleOutputPorts.length,
|
|
266
|
+
)}px; transform: translateY(-50%); z-index: 30;"
|
|
277
267
|
id={`${props.data.nodeId}-output-${port.id}`}
|
|
278
268
|
/>
|
|
279
269
|
{/each}
|
|
@@ -155,18 +155,17 @@
|
|
|
155
155
|
* Get the hideUnconnectedHandles setting from extensions
|
|
156
156
|
* Merges node type defaults with instance overrides
|
|
157
157
|
*/
|
|
158
|
-
const hideUnconnectedHandles = $derived(
|
|
159
|
-
|
|
160
|
-
props.data.metadata?.extensions?.ui?.hideUnconnectedHandles ??
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
});
|
|
158
|
+
const hideUnconnectedHandles = $derived(
|
|
159
|
+
props.data.extensions?.ui?.hideUnconnectedHandles ??
|
|
160
|
+
props.data.metadata?.extensions?.ui?.hideUnconnectedHandles ??
|
|
161
|
+
false,
|
|
162
|
+
);
|
|
164
163
|
|
|
165
164
|
/**
|
|
166
165
|
* Check if a port should be visible based on connection state and settings
|
|
167
166
|
*/
|
|
168
167
|
function isPortVisible(port: NodePort, type: "input" | "output"): boolean {
|
|
169
|
-
if (!hideUnconnectedHandles
|
|
168
|
+
if (!hideUnconnectedHandles) {
|
|
170
169
|
return true;
|
|
171
170
|
}
|
|
172
171
|
if (port.required) {
|
|
@@ -77,3 +77,66 @@
|
|
|
77
77
|
selected={true}
|
|
78
78
|
/>
|
|
79
79
|
</Story>
|
|
80
|
+
|
|
81
|
+
<Story name="Dynamic Ports">
|
|
82
|
+
<NodeDecorator
|
|
83
|
+
data={createSampleNodeData({
|
|
84
|
+
label: "Custom Function",
|
|
85
|
+
config: {
|
|
86
|
+
dynamicInputs: [
|
|
87
|
+
{
|
|
88
|
+
name: "input_1",
|
|
89
|
+
label: "First Input",
|
|
90
|
+
description: "The first input parameter",
|
|
91
|
+
dataType: "string",
|
|
92
|
+
required: true,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "input_2",
|
|
96
|
+
label: "Second Input",
|
|
97
|
+
description: "The second input parameter",
|
|
98
|
+
dataType: "number",
|
|
99
|
+
required: false,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
dynamicOutputs: [
|
|
103
|
+
{
|
|
104
|
+
name: "output_1",
|
|
105
|
+
label: "Primary Output",
|
|
106
|
+
description: "The main output value",
|
|
107
|
+
dataType: "string",
|
|
108
|
+
required: false,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
metadata: {
|
|
113
|
+
id: "custom-function",
|
|
114
|
+
name: "Custom Function",
|
|
115
|
+
description:
|
|
116
|
+
"Execute a custom function with dynamic inputs and outputs",
|
|
117
|
+
category: "processing",
|
|
118
|
+
version: "1.0.0",
|
|
119
|
+
type: "workflow",
|
|
120
|
+
icon: "mdi:function-variant",
|
|
121
|
+
inputs: [
|
|
122
|
+
{
|
|
123
|
+
id: "trigger",
|
|
124
|
+
name: "Trigger",
|
|
125
|
+
type: "input",
|
|
126
|
+
dataType: "trigger",
|
|
127
|
+
required: false,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
outputs: [
|
|
131
|
+
{
|
|
132
|
+
id: "done",
|
|
133
|
+
name: "Done",
|
|
134
|
+
type: "output",
|
|
135
|
+
dataType: "trigger",
|
|
136
|
+
required: false,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
})}
|
|
141
|
+
/>
|
|
142
|
+
</Story>
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
UI Extensions Support:
|
|
8
8
|
- hideUnconnectedHandles: Hides ports that are not connected to reduce visual clutter
|
|
9
|
+
- portOrder: Visual-only reordering of input/output ports (no effect on execution)
|
|
10
|
+
- hiddenPorts: Manually hidden ports per direction (required ports cannot be hidden)
|
|
9
11
|
-->
|
|
10
12
|
|
|
11
13
|
<script lang="ts">
|
|
@@ -25,6 +27,7 @@
|
|
|
25
27
|
getPortBackgroundColor,
|
|
26
28
|
} from "../../utils/colors.js";
|
|
27
29
|
import { getConnectedHandles } from "../../stores/workflowStore.svelte.js";
|
|
30
|
+
import { applyPortOrder } from "../../utils/portUtils.js";
|
|
28
31
|
|
|
29
32
|
interface Props {
|
|
30
33
|
data: WorkflowNode["data"] & {
|
|
@@ -64,12 +67,31 @@
|
|
|
64
67
|
* Get the hideUnconnectedHandles setting from extensions
|
|
65
68
|
* Merges node type defaults with instance overrides
|
|
66
69
|
*/
|
|
67
|
-
const hideUnconnectedHandles = $derived(
|
|
68
|
-
|
|
69
|
-
props.data.metadata?.extensions?.ui?.hideUnconnectedHandles ??
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
const hideUnconnectedHandles = $derived(
|
|
71
|
+
props.data.extensions?.ui?.hideUnconnectedHandles ??
|
|
72
|
+
props.data.metadata?.extensions?.ui?.hideUnconnectedHandles ??
|
|
73
|
+
false,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get the portOrder setting from extensions (visual-only, no effect on execution)
|
|
78
|
+
* Merges node type defaults with instance overrides
|
|
79
|
+
*/
|
|
80
|
+
const portOrder = $derived(
|
|
81
|
+
props.data.extensions?.ui?.portOrder ??
|
|
82
|
+
props.data.metadata?.extensions?.ui?.portOrder ??
|
|
83
|
+
{},
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the hiddenPorts setting from extensions (visual-only, no effect on execution)
|
|
88
|
+
* Merges node type defaults with instance overrides
|
|
89
|
+
*/
|
|
90
|
+
const hiddenPorts = $derived(
|
|
91
|
+
props.data.extensions?.ui?.hiddenPorts ??
|
|
92
|
+
props.data.metadata?.extensions?.ui?.hiddenPorts ??
|
|
93
|
+
{},
|
|
94
|
+
);
|
|
73
95
|
|
|
74
96
|
/**
|
|
75
97
|
* Dynamic inputs from config - user-defined input ports
|
|
@@ -92,20 +114,26 @@
|
|
|
92
114
|
);
|
|
93
115
|
|
|
94
116
|
/**
|
|
95
|
-
* Combined input ports: static metadata inputs + dynamic config inputs
|
|
117
|
+
* Combined input ports: static metadata inputs + dynamic config inputs,
|
|
118
|
+
* sorted by portOrder if set (visual-only)
|
|
96
119
|
*/
|
|
97
|
-
const allInputPorts = $derived(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
const allInputPorts = $derived(
|
|
121
|
+
applyPortOrder(
|
|
122
|
+
[...props.data.metadata.inputs, ...dynamicInputs],
|
|
123
|
+
portOrder.inputs,
|
|
124
|
+
),
|
|
125
|
+
);
|
|
101
126
|
|
|
102
127
|
/**
|
|
103
|
-
* Combined output ports: static metadata outputs + dynamic config outputs
|
|
128
|
+
* Combined output ports: static metadata outputs + dynamic config outputs,
|
|
129
|
+
* sorted by portOrder if set (visual-only)
|
|
104
130
|
*/
|
|
105
|
-
const allOutputPorts = $derived(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
131
|
+
const allOutputPorts = $derived(
|
|
132
|
+
applyPortOrder(
|
|
133
|
+
[...props.data.metadata.outputs, ...dynamicOutputs],
|
|
134
|
+
portOrder.outputs,
|
|
135
|
+
),
|
|
136
|
+
);
|
|
109
137
|
|
|
110
138
|
/**
|
|
111
139
|
* Check if a port should be visible based on connection state and settings
|
|
@@ -114,8 +142,15 @@
|
|
|
114
142
|
* @returns true if the port should be visible
|
|
115
143
|
*/
|
|
116
144
|
function isPortVisible(port: NodePort, type: "input" | "output"): boolean {
|
|
145
|
+
// Manual hide takes precedence (required ports are prevented from being hidden in ConfigForm)
|
|
146
|
+
const manuallyHidden =
|
|
147
|
+
type === "input"
|
|
148
|
+
? hiddenPorts.inputs?.includes(port.id)
|
|
149
|
+
: hiddenPorts.outputs?.includes(port.id);
|
|
150
|
+
if (manuallyHidden) return false;
|
|
151
|
+
|
|
117
152
|
// Always show if hideUnconnectedHandles is disabled
|
|
118
|
-
if (!hideUnconnectedHandles
|
|
153
|
+
if (!hideUnconnectedHandles) {
|
|
119
154
|
return true;
|
|
120
155
|
}
|
|
121
156
|
|
|
@@ -245,7 +280,7 @@
|
|
|
245
280
|
{#if visibleInputPorts.length > 0}
|
|
246
281
|
<div class="flowdrop-workflow-node__ports">
|
|
247
282
|
<div class="flowdrop-workflow-node__ports-list">
|
|
248
|
-
{#each visibleInputPorts as port
|
|
283
|
+
{#each visibleInputPorts as port (port.id)}
|
|
249
284
|
<div class="flowdrop-workflow-node__port">
|
|
250
285
|
<!-- Input Handle: centered in row, at node edge (ports have no padding) -->
|
|
251
286
|
<Handle
|
|
@@ -308,7 +343,7 @@
|
|
|
308
343
|
{#if visibleOutputPorts.length > 0}
|
|
309
344
|
<div class="flowdrop-workflow-node__ports">
|
|
310
345
|
<div class="flowdrop-workflow-node__ports-list">
|
|
311
|
-
{#each visibleOutputPorts as port
|
|
346
|
+
{#each visibleOutputPorts as port (port.id)}
|
|
312
347
|
<div class="flowdrop-workflow-node__port">
|
|
313
348
|
<!-- Port Info: padding lives here so handle position is simple -->
|
|
314
349
|
<div
|