@synergenius/flow-weaver 0.9.0 → 0.9.1
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/cli/flow-weaver.mjs
CHANGED
|
@@ -44825,38 +44825,39 @@ function assignImplicitPortOrders(ports) {
|
|
|
44825
44825
|
scopeGroups.get(scopeKey).push([portName, portDef]);
|
|
44826
44826
|
}
|
|
44827
44827
|
for (const [scope, portsInScope] of scopeGroups.entries()) {
|
|
44828
|
+
let nextSlot2 = function(from) {
|
|
44829
|
+
while (occupied.has(from)) from++;
|
|
44830
|
+
occupied.add(from);
|
|
44831
|
+
return from;
|
|
44832
|
+
};
|
|
44833
|
+
var nextSlot = nextSlot2;
|
|
44828
44834
|
const isScoped = scope !== void 0;
|
|
44829
|
-
const
|
|
44830
|
-
const
|
|
44831
|
-
let minRegularExplicitOrder = Infinity;
|
|
44832
|
-
for (const [, portDef] of regularPorts) {
|
|
44835
|
+
const occupied = /* @__PURE__ */ new Set();
|
|
44836
|
+
for (const [, portDef] of portsInScope) {
|
|
44833
44837
|
const order = portDef.metadata?.order;
|
|
44834
44838
|
if (typeof order === "number") {
|
|
44835
|
-
|
|
44839
|
+
occupied.add(order);
|
|
44836
44840
|
}
|
|
44837
44841
|
}
|
|
44838
|
-
|
|
44839
|
-
|
|
44840
|
-
|
|
44841
|
-
|
|
44842
|
-
|
|
44843
|
-
|
|
44844
|
-
|
|
44845
|
-
|
|
44846
|
-
|
|
44847
|
-
|
|
44848
|
-
|
|
44849
|
-
|
|
44850
|
-
}
|
|
44842
|
+
const mandatoryNeedOrder = portsInScope.filter(
|
|
44843
|
+
([name, def]) => isMandatoryPort(name, isScoped) && def.metadata?.order === void 0
|
|
44844
|
+
);
|
|
44845
|
+
const regularNeedOrder = portsInScope.filter(
|
|
44846
|
+
([name, def]) => !isMandatoryPort(name, isScoped) && def.metadata?.order === void 0
|
|
44847
|
+
);
|
|
44848
|
+
let slot = -mandatoryNeedOrder.length;
|
|
44849
|
+
for (const [, portDef] of mandatoryNeedOrder) {
|
|
44850
|
+
if (!portDef.metadata) portDef.metadata = {};
|
|
44851
|
+
slot = nextSlot2(slot);
|
|
44852
|
+
portDef.metadata.order = slot;
|
|
44853
|
+
slot++;
|
|
44851
44854
|
}
|
|
44852
|
-
|
|
44853
|
-
for (const [, portDef] of
|
|
44854
|
-
if (portDef.metadata
|
|
44855
|
-
|
|
44856
|
-
|
|
44857
|
-
|
|
44858
|
-
portDef.metadata.order = currentRegularOrder++;
|
|
44859
|
-
}
|
|
44855
|
+
slot = Math.max(slot, 0);
|
|
44856
|
+
for (const [, portDef] of regularNeedOrder) {
|
|
44857
|
+
if (!portDef.metadata) portDef.metadata = {};
|
|
44858
|
+
slot = nextSlot2(slot);
|
|
44859
|
+
portDef.metadata.order = slot;
|
|
44860
|
+
slot++;
|
|
44860
44861
|
}
|
|
44861
44862
|
}
|
|
44862
44863
|
}
|
|
@@ -60592,7 +60593,7 @@ function renderSVG(graph, options = {}) {
|
|
|
60592
60593
|
function renderConnection(parts2, conn, gradIndex) {
|
|
60593
60594
|
const dashAttr = conn.isStepConnection ? "" : ' stroke-dasharray="8 4"';
|
|
60594
60595
|
parts2.push(
|
|
60595
|
-
` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="3"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}"/>`
|
|
60596
|
+
` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="3"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`
|
|
60596
60597
|
);
|
|
60597
60598
|
}
|
|
60598
60599
|
function renderScopeConnection(parts2, conn, allConnections) {
|
|
@@ -60600,7 +60601,7 @@ function renderScopeConnection(parts2, conn, allConnections) {
|
|
|
60600
60601
|
if (gradIndex < 0) return;
|
|
60601
60602
|
const dashAttr = conn.isStepConnection ? "" : ' stroke-dasharray="8 4"';
|
|
60602
60603
|
parts2.push(
|
|
60603
|
-
` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}"/>`
|
|
60604
|
+
` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`
|
|
60604
60605
|
);
|
|
60605
60606
|
}
|
|
60606
60607
|
function renderNodeBody(parts2, node, theme, indent) {
|
|
@@ -60681,7 +60682,7 @@ function renderPortDots(parts2, nodeId, inputs, outputs, themeName) {
|
|
|
60681
60682
|
const color = getPortColor(port.dataType, port.isFailure, themeName);
|
|
60682
60683
|
const ringColor = getPortRingColor(port.dataType, port.isFailure, themeName);
|
|
60683
60684
|
const dir = port.direction === "INPUT" ? "input" : "output";
|
|
60684
|
-
parts2.push(` <circle cx="${port.cx}" cy="${port.cy}" r="${PORT_RADIUS}" fill="${color}" stroke="${ringColor}" stroke-width="2" data-port-id="${escapeXml(nodeId)}.${escapeXml(port.name)}" data-direction="${dir}"/>`);
|
|
60685
|
+
parts2.push(` <circle cx="${port.cx}" cy="${port.cy}" r="${PORT_RADIUS}" fill="${color}" stroke="${ringColor}" stroke-width="2" data-port-id="${escapeXml(nodeId)}.${escapeXml(port.name)}:${dir}" data-direction="${dir}"/>`);
|
|
60685
60686
|
}
|
|
60686
60687
|
}
|
|
60687
60688
|
function renderPortLabels(parts2, nodeId, inputs, outputs, theme, themeName) {
|
|
@@ -60689,7 +60690,8 @@ function renderPortLabels(parts2, nodeId, inputs, outputs, theme, themeName) {
|
|
|
60689
60690
|
const color = getPortColor(port.dataType, port.isFailure, themeName);
|
|
60690
60691
|
const isInput = port.direction === "INPUT";
|
|
60691
60692
|
const abbrev = TYPE_ABBREVIATIONS[port.dataType] ?? port.dataType;
|
|
60692
|
-
const
|
|
60693
|
+
const dir = isInput ? "input" : "output";
|
|
60694
|
+
const portId = `${escapeXml(nodeId)}.${escapeXml(port.name)}:${dir}`;
|
|
60693
60695
|
const portLabel = port.label;
|
|
60694
60696
|
const typeWidth = measureText(abbrev);
|
|
60695
60697
|
const labelWidth = measureText(portLabel);
|
|
@@ -60983,43 +60985,79 @@ body.node-active .connections path.dimmed { opacity: 0.15; }
|
|
|
60983
60985
|
else if (e.key === 'Escape') deselectNode();
|
|
60984
60986
|
});
|
|
60985
60987
|
|
|
60986
|
-
// ---- Port label visibility
|
|
60987
|
-
var
|
|
60988
|
-
|
|
60988
|
+
// ---- Port label visibility ----
|
|
60989
|
+
var labelMap = {};
|
|
60990
|
+
content.querySelectorAll('.labels g[data-port-label]').forEach(function(lbl) {
|
|
60991
|
+
labelMap[lbl.getAttribute('data-port-label')] = lbl;
|
|
60992
|
+
});
|
|
60989
60993
|
|
|
60990
|
-
|
|
60991
|
-
|
|
60992
|
-
|
|
60993
|
-
|
|
60994
|
-
|
|
60995
|
-
|
|
60996
|
-
|
|
60994
|
+
// Build adjacency: portId \u2192 array of connected portIds
|
|
60995
|
+
var portConnections = {};
|
|
60996
|
+
content.querySelectorAll('.connections path').forEach(function(p) {
|
|
60997
|
+
var src = p.getAttribute('data-source');
|
|
60998
|
+
var tgt = p.getAttribute('data-target');
|
|
60999
|
+
if (!src || !tgt) return;
|
|
61000
|
+
if (!portConnections[src]) portConnections[src] = [];
|
|
61001
|
+
if (!portConnections[tgt]) portConnections[tgt] = [];
|
|
61002
|
+
portConnections[src].push(tgt);
|
|
61003
|
+
portConnections[tgt].push(src);
|
|
61004
|
+
});
|
|
61005
|
+
|
|
61006
|
+
var allLabelIds = Object.keys(labelMap);
|
|
61007
|
+
var hoveredPort = null;
|
|
61008
|
+
|
|
61009
|
+
function showLabel(id) { var l = labelMap[id]; if (l) { l.style.opacity = '1'; l.style.pointerEvents = 'auto'; } }
|
|
61010
|
+
function hideLabel(id) { var l = labelMap[id]; if (l) { l.style.opacity = '0'; l.style.pointerEvents = 'none'; } }
|
|
61011
|
+
|
|
61012
|
+
function showLabelsFor(nodeId) {
|
|
61013
|
+
allLabelIds.forEach(function(id) {
|
|
61014
|
+
if (id.indexOf(nodeId + '.') === 0) showLabel(id);
|
|
60997
61015
|
});
|
|
60998
61016
|
}
|
|
60999
|
-
function hideLabelsFor(
|
|
61000
|
-
|
|
61001
|
-
|
|
61002
|
-
if (portId.indexOf(id + '.') === 0) {
|
|
61003
|
-
lbl.style.opacity = '0';
|
|
61004
|
-
lbl.style.pointerEvents = 'none';
|
|
61005
|
-
}
|
|
61017
|
+
function hideLabelsFor(nodeId) {
|
|
61018
|
+
allLabelIds.forEach(function(id) {
|
|
61019
|
+
if (id.indexOf(nodeId + '.') === 0) hideLabel(id);
|
|
61006
61020
|
});
|
|
61007
61021
|
}
|
|
61008
61022
|
|
|
61023
|
+
// Node hover: show all port labels for the hovered node
|
|
61024
|
+
var nodeEls = content.querySelectorAll('.nodes g[data-node-id]');
|
|
61009
61025
|
nodeEls.forEach(function(nodeG) {
|
|
61010
61026
|
var nodeId = nodeG.getAttribute('data-node-id');
|
|
61011
61027
|
var parentNodeG = nodeG.parentElement ? nodeG.parentElement.closest('g[data-node-id]') : null;
|
|
61012
61028
|
var parentId = parentNodeG ? parentNodeG.getAttribute('data-node-id') : null;
|
|
61013
61029
|
nodeG.addEventListener('mouseenter', function() {
|
|
61030
|
+
if (hoveredPort) return; // port hover takes priority
|
|
61014
61031
|
if (parentId) hideLabelsFor(parentId);
|
|
61015
61032
|
showLabelsFor(nodeId);
|
|
61016
61033
|
});
|
|
61017
61034
|
nodeG.addEventListener('mouseleave', function() {
|
|
61035
|
+
if (hoveredPort) return;
|
|
61018
61036
|
hideLabelsFor(nodeId);
|
|
61019
61037
|
if (parentId) showLabelsFor(parentId);
|
|
61020
61038
|
});
|
|
61021
61039
|
});
|
|
61022
61040
|
|
|
61041
|
+
// Port hover: show this port's label + all connected port labels
|
|
61042
|
+
content.querySelectorAll('[data-port-id]').forEach(function(portEl) {
|
|
61043
|
+
var portId = portEl.getAttribute('data-port-id');
|
|
61044
|
+
var nodeId = portId.split('.')[0];
|
|
61045
|
+
var peers = (portConnections[portId] || []).concat(portId);
|
|
61046
|
+
|
|
61047
|
+
portEl.addEventListener('mouseenter', function() {
|
|
61048
|
+
hoveredPort = portId;
|
|
61049
|
+
// Hide all labels for this node first, then show only the relevant ones
|
|
61050
|
+
hideLabelsFor(nodeId);
|
|
61051
|
+
peers.forEach(showLabel);
|
|
61052
|
+
});
|
|
61053
|
+
portEl.addEventListener('mouseleave', function() {
|
|
61054
|
+
hoveredPort = null;
|
|
61055
|
+
peers.forEach(hideLabel);
|
|
61056
|
+
// Restore all labels for the node since we're still inside it
|
|
61057
|
+
showLabelsFor(nodeId);
|
|
61058
|
+
});
|
|
61059
|
+
});
|
|
61060
|
+
|
|
61023
61061
|
// ---- Click to inspect node ----
|
|
61024
61062
|
function deselectNode() {
|
|
61025
61063
|
selectedNodeId = null;
|
|
@@ -61046,7 +61084,7 @@ body.node-active .connections path.dimmed { opacity: 0.15; }
|
|
|
61046
61084
|
ports.forEach(function(p) {
|
|
61047
61085
|
var id = p.getAttribute('data-port-id');
|
|
61048
61086
|
var dir = p.getAttribute('data-direction');
|
|
61049
|
-
var name = id.split('.').slice(1).join('.');
|
|
61087
|
+
var name = id.split('.').slice(1).join('.').replace(/:(?:input|output)$/, '');
|
|
61050
61088
|
if (dir === 'input') inputs.push(name);
|
|
61051
61089
|
else outputs.push(name);
|
|
61052
61090
|
});
|
|
@@ -95710,7 +95748,7 @@ function displayInstalledPackage(pkg) {
|
|
|
95710
95748
|
}
|
|
95711
95749
|
|
|
95712
95750
|
// src/cli/index.ts
|
|
95713
|
-
var version2 = true ? "0.9.
|
|
95751
|
+
var version2 = true ? "0.9.1" : "0.0.0-dev";
|
|
95714
95752
|
var program2 = new Command();
|
|
95715
95753
|
program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").version(version2, "-v, --version", "Output the current version");
|
|
95716
95754
|
program2.configureOutput({
|
|
@@ -266,43 +266,79 @@ body.node-active .connections path.dimmed { opacity: 0.15; }
|
|
|
266
266
|
else if (e.key === 'Escape') deselectNode();
|
|
267
267
|
});
|
|
268
268
|
|
|
269
|
-
// ---- Port label visibility
|
|
270
|
-
var
|
|
271
|
-
|
|
269
|
+
// ---- Port label visibility ----
|
|
270
|
+
var labelMap = {};
|
|
271
|
+
content.querySelectorAll('.labels g[data-port-label]').forEach(function(lbl) {
|
|
272
|
+
labelMap[lbl.getAttribute('data-port-label')] = lbl;
|
|
273
|
+
});
|
|
272
274
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
// Build adjacency: portId → array of connected portIds
|
|
276
|
+
var portConnections = {};
|
|
277
|
+
content.querySelectorAll('.connections path').forEach(function(p) {
|
|
278
|
+
var src = p.getAttribute('data-source');
|
|
279
|
+
var tgt = p.getAttribute('data-target');
|
|
280
|
+
if (!src || !tgt) return;
|
|
281
|
+
if (!portConnections[src]) portConnections[src] = [];
|
|
282
|
+
if (!portConnections[tgt]) portConnections[tgt] = [];
|
|
283
|
+
portConnections[src].push(tgt);
|
|
284
|
+
portConnections[tgt].push(src);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
var allLabelIds = Object.keys(labelMap);
|
|
288
|
+
var hoveredPort = null;
|
|
289
|
+
|
|
290
|
+
function showLabel(id) { var l = labelMap[id]; if (l) { l.style.opacity = '1'; l.style.pointerEvents = 'auto'; } }
|
|
291
|
+
function hideLabel(id) { var l = labelMap[id]; if (l) { l.style.opacity = '0'; l.style.pointerEvents = 'none'; } }
|
|
292
|
+
|
|
293
|
+
function showLabelsFor(nodeId) {
|
|
294
|
+
allLabelIds.forEach(function(id) {
|
|
295
|
+
if (id.indexOf(nodeId + '.') === 0) showLabel(id);
|
|
280
296
|
});
|
|
281
297
|
}
|
|
282
|
-
function hideLabelsFor(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (portId.indexOf(id + '.') === 0) {
|
|
286
|
-
lbl.style.opacity = '0';
|
|
287
|
-
lbl.style.pointerEvents = 'none';
|
|
288
|
-
}
|
|
298
|
+
function hideLabelsFor(nodeId) {
|
|
299
|
+
allLabelIds.forEach(function(id) {
|
|
300
|
+
if (id.indexOf(nodeId + '.') === 0) hideLabel(id);
|
|
289
301
|
});
|
|
290
302
|
}
|
|
291
303
|
|
|
304
|
+
// Node hover: show all port labels for the hovered node
|
|
305
|
+
var nodeEls = content.querySelectorAll('.nodes g[data-node-id]');
|
|
292
306
|
nodeEls.forEach(function(nodeG) {
|
|
293
307
|
var nodeId = nodeG.getAttribute('data-node-id');
|
|
294
308
|
var parentNodeG = nodeG.parentElement ? nodeG.parentElement.closest('g[data-node-id]') : null;
|
|
295
309
|
var parentId = parentNodeG ? parentNodeG.getAttribute('data-node-id') : null;
|
|
296
310
|
nodeG.addEventListener('mouseenter', function() {
|
|
311
|
+
if (hoveredPort) return; // port hover takes priority
|
|
297
312
|
if (parentId) hideLabelsFor(parentId);
|
|
298
313
|
showLabelsFor(nodeId);
|
|
299
314
|
});
|
|
300
315
|
nodeG.addEventListener('mouseleave', function() {
|
|
316
|
+
if (hoveredPort) return;
|
|
301
317
|
hideLabelsFor(nodeId);
|
|
302
318
|
if (parentId) showLabelsFor(parentId);
|
|
303
319
|
});
|
|
304
320
|
});
|
|
305
321
|
|
|
322
|
+
// Port hover: show this port's label + all connected port labels
|
|
323
|
+
content.querySelectorAll('[data-port-id]').forEach(function(portEl) {
|
|
324
|
+
var portId = portEl.getAttribute('data-port-id');
|
|
325
|
+
var nodeId = portId.split('.')[0];
|
|
326
|
+
var peers = (portConnections[portId] || []).concat(portId);
|
|
327
|
+
|
|
328
|
+
portEl.addEventListener('mouseenter', function() {
|
|
329
|
+
hoveredPort = portId;
|
|
330
|
+
// Hide all labels for this node first, then show only the relevant ones
|
|
331
|
+
hideLabelsFor(nodeId);
|
|
332
|
+
peers.forEach(showLabel);
|
|
333
|
+
});
|
|
334
|
+
portEl.addEventListener('mouseleave', function() {
|
|
335
|
+
hoveredPort = null;
|
|
336
|
+
peers.forEach(hideLabel);
|
|
337
|
+
// Restore all labels for the node since we're still inside it
|
|
338
|
+
showLabelsFor(nodeId);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
306
342
|
// ---- Click to inspect node ----
|
|
307
343
|
function deselectNode() {
|
|
308
344
|
selectedNodeId = null;
|
|
@@ -329,7 +365,7 @@ body.node-active .connections path.dimmed { opacity: 0.15; }
|
|
|
329
365
|
ports.forEach(function(p) {
|
|
330
366
|
var id = p.getAttribute('data-port-id');
|
|
331
367
|
var dir = p.getAttribute('data-direction');
|
|
332
|
-
var name = id.split('.').slice(1).join('.');
|
|
368
|
+
var name = id.split('.').slice(1).join('.').replace(/:(?:input|output)$/, '');
|
|
333
369
|
if (dir === 'input') inputs.push(name);
|
|
334
370
|
else outputs.push(name);
|
|
335
371
|
});
|
package/dist/diagram/renderer.js
CHANGED
|
@@ -90,14 +90,14 @@ export function renderSVG(graph, options = {}) {
|
|
|
90
90
|
// ---- Connection rendering ----
|
|
91
91
|
function renderConnection(parts, conn, gradIndex) {
|
|
92
92
|
const dashAttr = conn.isStepConnection ? '' : ' stroke-dasharray="8 4"';
|
|
93
|
-
parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="3"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}"/>`);
|
|
93
|
+
parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="3"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`);
|
|
94
94
|
}
|
|
95
95
|
function renderScopeConnection(parts, conn, allConnections) {
|
|
96
96
|
const gradIndex = allConnections.indexOf(conn);
|
|
97
97
|
if (gradIndex < 0)
|
|
98
98
|
return;
|
|
99
99
|
const dashAttr = conn.isStepConnection ? '' : ' stroke-dasharray="8 4"';
|
|
100
|
-
parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}"/>`);
|
|
100
|
+
parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`);
|
|
101
101
|
}
|
|
102
102
|
// ---- Node rendering ----
|
|
103
103
|
/** Render node body rect + icon */
|
|
@@ -183,7 +183,7 @@ function renderPortDots(parts, nodeId, inputs, outputs, themeName) {
|
|
|
183
183
|
const color = getPortColor(port.dataType, port.isFailure, themeName);
|
|
184
184
|
const ringColor = getPortRingColor(port.dataType, port.isFailure, themeName);
|
|
185
185
|
const dir = port.direction === 'INPUT' ? 'input' : 'output';
|
|
186
|
-
parts.push(` <circle cx="${port.cx}" cy="${port.cy}" r="${PORT_RADIUS}" fill="${color}" stroke="${ringColor}" stroke-width="2" data-port-id="${escapeXml(nodeId)}.${escapeXml(port.name)}" data-direction="${dir}"/>`);
|
|
186
|
+
parts.push(` <circle cx="${port.cx}" cy="${port.cy}" r="${PORT_RADIUS}" fill="${color}" stroke="${ringColor}" stroke-width="2" data-port-id="${escapeXml(nodeId)}.${escapeXml(port.name)}:${dir}" data-direction="${dir}"/>`);
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
/** Render only port label badges (no dots) */
|
|
@@ -192,7 +192,8 @@ function renderPortLabels(parts, nodeId, inputs, outputs, theme, themeName) {
|
|
|
192
192
|
const color = getPortColor(port.dataType, port.isFailure, themeName);
|
|
193
193
|
const isInput = port.direction === 'INPUT';
|
|
194
194
|
const abbrev = TYPE_ABBREVIATIONS[port.dataType] ?? port.dataType;
|
|
195
|
-
const
|
|
195
|
+
const dir = isInput ? 'input' : 'output';
|
|
196
|
+
const portId = `${escapeXml(nodeId)}.${escapeXml(port.name)}:${dir}`;
|
|
196
197
|
const portLabel = port.label;
|
|
197
198
|
const typeWidth = measureText(abbrev);
|
|
198
199
|
const labelWidth = measureText(portLabel);
|
|
@@ -14,11 +14,11 @@ export declare function isMandatoryPort(portName: string, isScoped: boolean): bo
|
|
|
14
14
|
*
|
|
15
15
|
* Rules:
|
|
16
16
|
* 1. Ports are grouped by scope (undefined = external, string = scoped)
|
|
17
|
-
* 2.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* 2. Explicit order metadata is always preserved
|
|
18
|
+
* 3. Mandatory ports without explicit orders get negative slots (-N, ..., -1)
|
|
19
|
+
* so they always sort before any user-specified [order:0] data port
|
|
20
|
+
* 4. Regular ports without explicit orders fill non-negative slots (0+),
|
|
21
|
+
* skipping any slots already occupied by explicit orders
|
|
22
22
|
*
|
|
23
23
|
* @param ports - Record of port definitions to process (mutated in place)
|
|
24
24
|
*/
|
|
@@ -19,11 +19,11 @@ export function isMandatoryPort(portName, isScoped) {
|
|
|
19
19
|
*
|
|
20
20
|
* Rules:
|
|
21
21
|
* 1. Ports are grouped by scope (undefined = external, string = scoped)
|
|
22
|
-
* 2.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* 2. Explicit order metadata is always preserved
|
|
23
|
+
* 3. Mandatory ports without explicit orders get negative slots (-N, ..., -1)
|
|
24
|
+
* so they always sort before any user-specified [order:0] data port
|
|
25
|
+
* 4. Regular ports without explicit orders fill non-negative slots (0+),
|
|
26
|
+
* skipping any slots already occupied by explicit orders
|
|
27
27
|
*
|
|
28
28
|
* @param ports - Record of port definitions to process (mutated in place)
|
|
29
29
|
*/
|
|
@@ -40,48 +40,41 @@ export function assignImplicitPortOrders(ports) {
|
|
|
40
40
|
// Process each scope group independently
|
|
41
41
|
for (const [scope, portsInScope] of scopeGroups.entries()) {
|
|
42
42
|
const isScoped = scope !== undefined;
|
|
43
|
-
//
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
// Find minimum explicit order among regular ports (if any)
|
|
47
|
-
let minRegularExplicitOrder = Infinity;
|
|
48
|
-
for (const [, portDef] of regularPorts) {
|
|
43
|
+
// Collect all explicitly occupied order slots
|
|
44
|
+
const occupied = new Set();
|
|
45
|
+
for (const [, portDef] of portsInScope) {
|
|
49
46
|
const order = portDef.metadata?.order;
|
|
50
47
|
if (typeof order === "number") {
|
|
51
|
-
|
|
48
|
+
occupied.add(order);
|
|
52
49
|
}
|
|
53
50
|
}
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const regularPortsWithOrder0 = regularPorts.filter(([, p]) => p.metadata?.order === 0);
|
|
61
|
-
mandatoryStartOrder = regularPortsWithOrder0.length;
|
|
51
|
+
// Helper: find next available slot starting from `from`
|
|
52
|
+
function nextSlot(from) {
|
|
53
|
+
while (occupied.has(from))
|
|
54
|
+
from++;
|
|
55
|
+
occupied.add(from);
|
|
56
|
+
return from;
|
|
62
57
|
}
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
58
|
+
// Separate mandatory from regular ports (only those needing implicit orders)
|
|
59
|
+
const mandatoryNeedOrder = portsInScope.filter(([name, def]) => isMandatoryPort(name, isScoped) && def.metadata?.order === undefined);
|
|
60
|
+
const regularNeedOrder = portsInScope.filter(([name, def]) => !isMandatoryPort(name, isScoped) && def.metadata?.order === undefined);
|
|
61
|
+
// Mandatory ports fill negative slots so they always sort before [order:0] data ports
|
|
62
|
+
let slot = -mandatoryNeedOrder.length;
|
|
63
|
+
for (const [, portDef] of mandatoryNeedOrder) {
|
|
64
|
+
if (!portDef.metadata)
|
|
65
|
+
portDef.metadata = {};
|
|
66
|
+
slot = nextSlot(slot);
|
|
67
|
+
portDef.metadata.order = slot;
|
|
68
|
+
slot++;
|
|
73
69
|
}
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
portDef.metadata.order = currentRegularOrder++;
|
|
84
|
-
}
|
|
70
|
+
// Regular ports fill non-negative slots, skipping occupied ones
|
|
71
|
+
slot = Math.max(slot, 0);
|
|
72
|
+
for (const [, portDef] of regularNeedOrder) {
|
|
73
|
+
if (!portDef.metadata)
|
|
74
|
+
portDef.metadata = {};
|
|
75
|
+
slot = nextSlot(slot);
|
|
76
|
+
portDef.metadata.order = slot;
|
|
77
|
+
slot++;
|
|
85
78
|
}
|
|
86
79
|
}
|
|
87
80
|
}
|
package/package.json
CHANGED