@flowdrop/flowdrop 1.2.1 → 1.3.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/ConfigForm.svelte +421 -0
- package/dist/components/NodeSidebar.svelte +29 -7
- package/dist/components/WorkflowEditor.svelte +16 -6
- package/dist/components/nodes/GatewayNode.svelte +8 -9
- package/dist/components/nodes/SimpleNode.svelte +80 -107
- package/dist/components/nodes/SquareNode.svelte +81 -107
- package/dist/components/nodes/TerminalNode.svelte +6 -7
- package/dist/components/nodes/WorkflowNode.svelte +54 -19
- package/dist/schemas/v1/workflow.schema.json +22 -107
- package/dist/stores/portCoordinateStore.svelte.js +25 -7
- 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
|
Simple Node Component
|
|
3
3
|
A simple 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">
|
|
@@ -21,6 +23,11 @@
|
|
|
21
23
|
getCategoryColorToken,
|
|
22
24
|
} from "../../utils/colors.js";
|
|
23
25
|
import { getConnectedHandles } from "../../stores/workflowStore.svelte.js";
|
|
26
|
+
import {
|
|
27
|
+
applyPortOrder,
|
|
28
|
+
getPortTop,
|
|
29
|
+
isPortVisible,
|
|
30
|
+
} from "../../utils/portUtils.js";
|
|
24
31
|
import CogIcon from "../icons/CogIcon.svelte";
|
|
25
32
|
import AlertCircleIcon from "../icons/AlertCircleIcon.svelte";
|
|
26
33
|
|
|
@@ -43,15 +50,25 @@
|
|
|
43
50
|
}>();
|
|
44
51
|
|
|
45
52
|
/**
|
|
46
|
-
* Get
|
|
47
|
-
* Merges node type defaults with instance overrides
|
|
53
|
+
* Get UI extension settings from extensions, merging node type defaults with instance overrides.
|
|
48
54
|
*/
|
|
49
|
-
const hideUnconnectedHandles = $derived(
|
|
50
|
-
|
|
51
|
-
props.data.metadata?.extensions?.ui?.hideUnconnectedHandles ??
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const hideUnconnectedHandles = $derived(
|
|
56
|
+
props.data.extensions?.ui?.hideUnconnectedHandles ??
|
|
57
|
+
props.data.metadata?.extensions?.ui?.hideUnconnectedHandles ??
|
|
58
|
+
false,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const hiddenPorts = $derived(
|
|
62
|
+
props.data.extensions?.ui?.hiddenPorts ??
|
|
63
|
+
props.data.metadata?.extensions?.ui?.hiddenPorts ??
|
|
64
|
+
{},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const portOrder = $derived(
|
|
68
|
+
props.data.extensions?.ui?.portOrder ??
|
|
69
|
+
props.data.metadata?.extensions?.ui?.portOrder ??
|
|
70
|
+
{},
|
|
71
|
+
);
|
|
55
72
|
|
|
56
73
|
// Prioritize metadata icon over config icon for simple nodes (metadata is the node definition)
|
|
57
74
|
let nodeIcon = $derived(
|
|
@@ -117,110 +134,67 @@
|
|
|
117
134
|
}
|
|
118
135
|
|
|
119
136
|
/**
|
|
120
|
-
*
|
|
121
|
-
* @param portId - The port ID to check
|
|
122
|
-
* @param type - Whether this is an 'input' or 'output' port
|
|
123
|
-
* @returns true if the port is connected
|
|
137
|
+
* All visible input ports in user-defined order.
|
|
124
138
|
*/
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
type: "input" | "output",
|
|
137
|
-
): boolean {
|
|
138
|
-
if (!hideUnconnectedHandles()) {
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
|
-
return isPortConnected(portId, type);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Get first input/output ports for simple node representation
|
|
145
|
-
// Special handling for trigger ports - they should always be shown if present
|
|
146
|
-
let triggerInputPort = $derived(
|
|
147
|
-
props.data.metadata?.inputs?.find(
|
|
148
|
-
(port: NodePort) => port.dataType === "trigger",
|
|
149
|
-
),
|
|
150
|
-
);
|
|
151
|
-
let triggerOutputPort = $derived(
|
|
152
|
-
props.data.metadata?.outputs?.find(
|
|
153
|
-
(port: NodePort) => port.dataType === "trigger",
|
|
139
|
+
const visibleInputPorts = $derived(
|
|
140
|
+
applyPortOrder(props.data.metadata?.inputs ?? [], portOrder.inputs).filter(
|
|
141
|
+
(p: NodePort) =>
|
|
142
|
+
isPortVisible(
|
|
143
|
+
p,
|
|
144
|
+
"input",
|
|
145
|
+
hiddenPorts,
|
|
146
|
+
hideUnconnectedHandles,
|
|
147
|
+
getConnectedHandles(),
|
|
148
|
+
props.data.nodeId,
|
|
149
|
+
),
|
|
154
150
|
),
|
|
155
151
|
);
|
|
156
152
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
153
|
+
/**
|
|
154
|
+
* All visible output ports in user-defined order.
|
|
155
|
+
*/
|
|
156
|
+
const visibleOutputPorts = $derived(
|
|
157
|
+
applyPortOrder(
|
|
158
|
+
props.data.metadata?.outputs ?? [],
|
|
159
|
+
portOrder.outputs,
|
|
160
|
+
).filter((p: NodePort) =>
|
|
161
|
+
isPortVisible(
|
|
162
|
+
p,
|
|
163
|
+
"output",
|
|
164
|
+
hiddenPorts,
|
|
165
|
+
hideUnconnectedHandles,
|
|
166
|
+
getConnectedHandles(),
|
|
167
|
+
props.data.nodeId,
|
|
168
|
+
),
|
|
168
169
|
),
|
|
169
170
|
);
|
|
170
171
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Dynamic node min-height so handles never render outside the node body.
|
|
174
|
+
*/
|
|
175
|
+
const nodeMinHeight = $derived(
|
|
176
|
+
(() => {
|
|
177
|
+
const maxPorts = Math.max(
|
|
178
|
+
visibleInputPorts.length,
|
|
179
|
+
visibleOutputPorts.length,
|
|
180
|
+
1,
|
|
181
|
+
);
|
|
182
|
+
return maxPorts <= 1 ? 80 : 20 + maxPorts * 40;
|
|
183
|
+
})(),
|
|
181
184
|
);
|
|
182
|
-
|
|
183
|
-
let inputPorts = $derived.by(() => {
|
|
184
|
-
return [
|
|
185
|
-
...(firstConnectedDataInputPort
|
|
186
|
-
? [firstConnectedDataInputPort]
|
|
187
|
-
: firstDataInputPort
|
|
188
|
-
? [firstDataInputPort]
|
|
189
|
-
: []),
|
|
190
|
-
...(triggerInputPort &&
|
|
191
|
-
shouldShowTriggerPort(triggerInputPort.id, "input")
|
|
192
|
-
? [triggerInputPort]
|
|
193
|
-
: []),
|
|
194
|
-
];
|
|
195
|
-
});
|
|
196
|
-
let outputPorts = $derived.by(() => {
|
|
197
|
-
return [
|
|
198
|
-
...(firstConnectedDataOutputPort
|
|
199
|
-
? [firstConnectedDataOutputPort]
|
|
200
|
-
: firstDataOutputPort
|
|
201
|
-
? [firstDataOutputPort]
|
|
202
|
-
: []),
|
|
203
|
-
...(triggerOutputPort &&
|
|
204
|
-
shouldShowTriggerPort(triggerOutputPort.id, "output")
|
|
205
|
-
? [triggerOutputPort]
|
|
206
|
-
: []),
|
|
207
|
-
];
|
|
208
|
-
});
|
|
209
185
|
</script>
|
|
210
186
|
|
|
211
|
-
<!-- Input Handles:
|
|
212
|
-
{#each
|
|
187
|
+
<!-- Input Handles: 1 port centered at 40px; N ports at 20px start, 40px gap -->
|
|
188
|
+
{#each visibleInputPorts as port, index}
|
|
213
189
|
<Handle
|
|
214
190
|
type="target"
|
|
215
191
|
position={Position.Left}
|
|
216
192
|
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
217
193
|
port.dataType,
|
|
218
|
-
)}); --fd-handle-border-color: var(--fd-handle-border); top: {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
: 60
|
|
223
|
-
: 40}px; transform: translateY(-50%); z-index: 30;"
|
|
194
|
+
)}); --fd-handle-border-color: var(--fd-handle-border); top: {getPortTop(
|
|
195
|
+
index,
|
|
196
|
+
visibleInputPorts.length,
|
|
197
|
+
)}px; transform: translateY(-50%); z-index: 30;"
|
|
224
198
|
id={`${props.data.nodeId}-input-${port.id}`}
|
|
225
199
|
/>
|
|
226
200
|
{/each}
|
|
@@ -231,6 +205,7 @@
|
|
|
231
205
|
class:flowdrop-simple-node--selected={props.selected}
|
|
232
206
|
class:flowdrop-simple-node--processing={props.isProcessing}
|
|
233
207
|
class:flowdrop-simple-node--error={props.isError}
|
|
208
|
+
style="min-height: {nodeMinHeight}px"
|
|
234
209
|
onclick={handleClick}
|
|
235
210
|
ondblclick={handleDoubleClick}
|
|
236
211
|
onkeydown={handleKeydown}
|
|
@@ -290,19 +265,17 @@
|
|
|
290
265
|
</button>
|
|
291
266
|
</div>
|
|
292
267
|
|
|
293
|
-
<!-- Output Handles:
|
|
294
|
-
{#each
|
|
268
|
+
<!-- Output Handles: 1 port centered at 40px; N ports at 20px start, 40px gap -->
|
|
269
|
+
{#each visibleOutputPorts as port, index}
|
|
295
270
|
<Handle
|
|
296
271
|
type="source"
|
|
297
272
|
position={Position.Right}
|
|
298
273
|
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
299
274
|
port.dataType,
|
|
300
|
-
)}); --fd-handle-border-color: var(--fd-handle-border); top: {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
: 60
|
|
305
|
-
: 40}px; transform: translateY(-50%); z-index: 30;"
|
|
275
|
+
)}); --fd-handle-border-color: var(--fd-handle-border); top: {getPortTop(
|
|
276
|
+
index,
|
|
277
|
+
visibleOutputPorts.length,
|
|
278
|
+
)}px; transform: translateY(-50%); z-index: 30;"
|
|
306
279
|
id={`${props.data.nodeId}-output-${port.id}`}
|
|
307
280
|
/>
|
|
308
281
|
{/each}
|
|
@@ -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">
|
|
@@ -22,6 +24,11 @@
|
|
|
22
24
|
} from "../../utils/colors.js";
|
|
23
25
|
import { getNodeIcon } from "../../utils/icons.js";
|
|
24
26
|
import { getConnectedHandles } from "../../stores/workflowStore.svelte.js";
|
|
27
|
+
import {
|
|
28
|
+
applyPortOrder,
|
|
29
|
+
getPortTop,
|
|
30
|
+
isPortVisible,
|
|
31
|
+
} from "../../utils/portUtils.js";
|
|
25
32
|
import CogIcon from "../icons/CogIcon.svelte";
|
|
26
33
|
import AlertCircleIcon from "../icons/AlertCircleIcon.svelte";
|
|
27
34
|
|
|
@@ -44,15 +51,25 @@
|
|
|
44
51
|
}>();
|
|
45
52
|
|
|
46
53
|
/**
|
|
47
|
-
* Get
|
|
48
|
-
* Merges node type defaults with instance overrides
|
|
54
|
+
* Get UI extension settings from extensions, merging node type defaults with instance overrides.
|
|
49
55
|
*/
|
|
50
|
-
const hideUnconnectedHandles = $derived(
|
|
51
|
-
|
|
52
|
-
props.data.metadata?.extensions?.ui?.hideUnconnectedHandles ??
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
const hideUnconnectedHandles = $derived(
|
|
57
|
+
props.data.extensions?.ui?.hideUnconnectedHandles ??
|
|
58
|
+
props.data.metadata?.extensions?.ui?.hideUnconnectedHandles ??
|
|
59
|
+
false,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const hiddenPorts = $derived(
|
|
63
|
+
props.data.extensions?.ui?.hiddenPorts ??
|
|
64
|
+
props.data.metadata?.extensions?.ui?.hiddenPorts ??
|
|
65
|
+
{},
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const portOrder = $derived(
|
|
69
|
+
props.data.extensions?.ui?.portOrder ??
|
|
70
|
+
props.data.metadata?.extensions?.ui?.portOrder ??
|
|
71
|
+
{},
|
|
72
|
+
);
|
|
56
73
|
|
|
57
74
|
/**
|
|
58
75
|
* Get icon using the same resolution as WorkflowNode
|
|
@@ -101,110 +118,68 @@
|
|
|
101
118
|
}
|
|
102
119
|
}
|
|
103
120
|
/**
|
|
104
|
-
*
|
|
105
|
-
* @param portId - The port ID to check
|
|
106
|
-
* @param type - Whether this is an 'input' or 'output' port
|
|
107
|
-
* @returns true if the port is connected
|
|
121
|
+
* All visible input ports in user-defined order.
|
|
108
122
|
*/
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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",
|
|
123
|
+
const visibleInputPorts = $derived(
|
|
124
|
+
applyPortOrder(props.data.metadata?.inputs ?? [], portOrder.inputs).filter(
|
|
125
|
+
(p: NodePort) =>
|
|
126
|
+
isPortVisible(
|
|
127
|
+
p,
|
|
128
|
+
"input",
|
|
129
|
+
hiddenPorts,
|
|
130
|
+
hideUnconnectedHandles,
|
|
131
|
+
getConnectedHandles(),
|
|
132
|
+
props.data.nodeId,
|
|
133
|
+
),
|
|
138
134
|
),
|
|
139
135
|
);
|
|
140
136
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
137
|
+
/**
|
|
138
|
+
* All visible output ports in user-defined order.
|
|
139
|
+
*/
|
|
140
|
+
const visibleOutputPorts = $derived(
|
|
141
|
+
applyPortOrder(
|
|
142
|
+
props.data.metadata?.outputs ?? [],
|
|
143
|
+
portOrder.outputs,
|
|
144
|
+
).filter((p: NodePort) =>
|
|
145
|
+
isPortVisible(
|
|
146
|
+
p,
|
|
147
|
+
"output",
|
|
148
|
+
hiddenPorts,
|
|
149
|
+
hideUnconnectedHandles,
|
|
150
|
+
getConnectedHandles(),
|
|
151
|
+
props.data.nodeId,
|
|
152
|
+
),
|
|
152
153
|
),
|
|
153
154
|
);
|
|
154
155
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Dynamic node size so handles never render outside the node body.
|
|
158
|
+
* Overrides the fixed CSS height/width when more than 2 ports are visible on either side.
|
|
159
|
+
*/
|
|
160
|
+
const nodeSize = $derived(
|
|
161
|
+
(() => {
|
|
162
|
+
const maxPorts = Math.max(
|
|
163
|
+
visibleInputPorts.length,
|
|
164
|
+
visibleOutputPorts.length,
|
|
165
|
+
1,
|
|
166
|
+
);
|
|
167
|
+
return maxPorts <= 1 ? 80 : 20 + maxPorts * 40;
|
|
168
|
+
})(),
|
|
165
169
|
);
|
|
166
|
-
|
|
167
|
-
let inputPorts = $derived.by(() => {
|
|
168
|
-
return [
|
|
169
|
-
...(firstConnectedDataInputPort
|
|
170
|
-
? [firstConnectedDataInputPort]
|
|
171
|
-
: firstDataInputPort
|
|
172
|
-
? [firstDataInputPort]
|
|
173
|
-
: []),
|
|
174
|
-
...(triggerInputPort &&
|
|
175
|
-
shouldShowTriggerPort(triggerInputPort.id, "input")
|
|
176
|
-
? [triggerInputPort]
|
|
177
|
-
: []),
|
|
178
|
-
];
|
|
179
|
-
});
|
|
180
|
-
let outputPorts = $derived.by(() => {
|
|
181
|
-
return [
|
|
182
|
-
...(firstConnectedDataOutputPort
|
|
183
|
-
? [firstConnectedDataOutputPort]
|
|
184
|
-
: firstDataOutputPort
|
|
185
|
-
? [firstDataOutputPort]
|
|
186
|
-
: []),
|
|
187
|
-
...(triggerOutputPort &&
|
|
188
|
-
shouldShowTriggerPort(triggerOutputPort.id, "output")
|
|
189
|
-
? [triggerOutputPort]
|
|
190
|
-
: []),
|
|
191
|
-
];
|
|
192
|
-
});
|
|
193
170
|
</script>
|
|
194
171
|
|
|
195
|
-
<!-- Input Handles:
|
|
196
|
-
{#each
|
|
172
|
+
<!-- Input Handles: 1 port centered at 40px; N ports at 20px start, 40px gap -->
|
|
173
|
+
{#each visibleInputPorts as port, index}
|
|
197
174
|
<Handle
|
|
198
175
|
type="target"
|
|
199
176
|
position={Position.Left}
|
|
200
177
|
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
201
178
|
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;"
|
|
179
|
+
)}); --fd-handle-border-color: var(--fd-handle-border); top: {getPortTop(
|
|
180
|
+
index,
|
|
181
|
+
visibleInputPorts.length,
|
|
182
|
+
)}px; transform: translateY(-50%); z-index: 30;"
|
|
208
183
|
id={`${props.data.nodeId}-input-${port.id}`}
|
|
209
184
|
/>
|
|
210
185
|
{/each}
|
|
@@ -215,6 +190,7 @@
|
|
|
215
190
|
class:flowdrop-square-node--selected={props.selected}
|
|
216
191
|
class:flowdrop-square-node--processing={props.isProcessing}
|
|
217
192
|
class:flowdrop-square-node--error={props.isError}
|
|
193
|
+
style="height: {nodeSize}px; width: {nodeSize}px"
|
|
218
194
|
onclick={handleClick}
|
|
219
195
|
ondblclick={handleDoubleClick}
|
|
220
196
|
onkeydown={handleKeydown}
|
|
@@ -261,19 +237,17 @@
|
|
|
261
237
|
</button>
|
|
262
238
|
</div>
|
|
263
239
|
|
|
264
|
-
<!-- Output Handles:
|
|
265
|
-
{#each
|
|
240
|
+
<!-- Output Handles: 1 port centered at 40px; N ports at 20px start, 40px gap -->
|
|
241
|
+
{#each visibleOutputPorts as port, index}
|
|
266
242
|
<Handle
|
|
267
243
|
type="source"
|
|
268
244
|
position={Position.Right}
|
|
269
245
|
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
270
246
|
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;"
|
|
247
|
+
)}); --fd-handle-border-color: var(--fd-handle-border); top: {getPortTop(
|
|
248
|
+
index,
|
|
249
|
+
visibleOutputPorts.length,
|
|
250
|
+
)}px; transform: translateY(-50%); z-index: 30;"
|
|
277
251
|
id={`${props.data.nodeId}-output-${port.id}`}
|
|
278
252
|
/>
|
|
279
253
|
{/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) {
|
|
@@ -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
|