@synergenius/flow-weaver 0.21.5 → 0.21.7
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 +3 -3
- package/dist/cli/flow-weaver.mjs +671 -192
- package/dist/cli/pack-commands.js +1 -1
- package/dist/diagram/geometry.d.ts +2 -0
- package/dist/diagram/geometry.js +36 -4
- package/dist/diagram/html-viewer.js +270 -38
- package/dist/diagram/renderer.js +29 -5
- package/dist/diagram/types.d.ts +10 -0
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/dist/marketplace/registry.js +2 -4
- package/dist/marketplace/validator.js +1 -1
- package/docs/reference/cli-reference.md +1 -1
- package/docs/reference/deployment.md +6 -6
- package/docs/reference/marketplace.md +18 -18
- package/package.json +1 -1
|
@@ -39,7 +39,7 @@ function checkPackEngineVersion(pkg) {
|
|
|
39
39
|
*/
|
|
40
40
|
function deriveNamespace(packageName) {
|
|
41
41
|
const base = packageName.replace(/^@[^/]+\//, '');
|
|
42
|
-
return base.replace(/^
|
|
42
|
+
return base.replace(/^flow-weaver-pack-/, '');
|
|
43
43
|
}
|
|
44
44
|
export async function registerPackCommands(program) {
|
|
45
45
|
const projectDir = process.cwd();
|
|
@@ -18,6 +18,8 @@ export declare const SCOPE_PADDING_Y = 40;
|
|
|
18
18
|
export declare const SCOPE_PORT_COLUMN = 50;
|
|
19
19
|
export declare const SCOPE_INNER_GAP_X = 240;
|
|
20
20
|
export declare const ORTHOGONAL_DISTANCE_THRESHOLD = 300;
|
|
21
|
+
export declare const STUB_DISTANCE_THRESHOLD = 500;
|
|
22
|
+
export declare const STUB_LENGTH = 30;
|
|
21
23
|
/** Measure text width using pre-computed Montserrat 600/10px SVG character widths */
|
|
22
24
|
export declare function measureText(text: string): number;
|
|
23
25
|
/** Compute the full badge width for a port label (matches renderer badge layout) */
|
package/dist/diagram/geometry.js
CHANGED
|
@@ -25,6 +25,12 @@ export const SCOPE_INNER_GAP_X = 240; // horizontal gap between children inside
|
|
|
25
25
|
// Routing mode threshold — connections longer than this use orthogonal routing
|
|
26
26
|
// (midpoint of original 250–350 hysteresis thresholds)
|
|
27
27
|
export const ORTHOGONAL_DISTANCE_THRESHOLD = 300;
|
|
28
|
+
// Connections beyond this x-distance show as stubs only (no full path).
|
|
29
|
+
// Must be higher than ORTHOGONAL_DISTANCE_THRESHOLD so adjacent-layer connections
|
|
30
|
+
// still render their full orthogonal path.
|
|
31
|
+
export const STUB_DISTANCE_THRESHOLD = 500;
|
|
32
|
+
// Stub length for long-distance connections (short segment from port center outward)
|
|
33
|
+
export const STUB_LENGTH = 30;
|
|
28
34
|
// ---- Font metrics (Montserrat 600-weight, 10px — measured via SVG getBBox) ----
|
|
29
35
|
const CHAR_WIDTHS = {
|
|
30
36
|
' ': 2.78, '!': 3.34, '"': 4.74, '#': 5.56, '$': 5.56, '%': 8.9, '&': 7.23,
|
|
@@ -999,23 +1005,49 @@ export function buildDiagramGraph(ast, options = {}) {
|
|
|
999
1005
|
const dy = ty - sy;
|
|
1000
1006
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
1001
1007
|
const useCurve = forceCurveSet.has(pc);
|
|
1008
|
+
const sourceColor = getPortColor(pc.sourcePort.dataType, pc.sourcePort.isFailure, themeName);
|
|
1009
|
+
const targetColor = getPortColor(pc.targetPort.dataType, pc.targetPort.isFailure, themeName);
|
|
1010
|
+
const xDistance = Math.abs(tx - sx);
|
|
1011
|
+
// Always compute stubs so the HTML viewer can toggle between path/stubs on drag.
|
|
1012
|
+
// In static SVG (labels always visible), stubs start after the port label badge.
|
|
1013
|
+
// In HTML, stubs start from port center by default and push out when labels appear.
|
|
1014
|
+
const dashed = pc.sourcePort.dataType !== 'STEP';
|
|
1015
|
+
const srcLabelEnd = portLabelExtent(pc.sourcePort);
|
|
1016
|
+
const tgtLabelEnd = portLabelExtent(pc.targetPort);
|
|
1017
|
+
const sourceStub = {
|
|
1018
|
+
x: sx + srcLabelEnd, y: sy,
|
|
1019
|
+
endX: sx + srcLabelEnd + STUB_LENGTH,
|
|
1020
|
+
labelOffset: srcLabelEnd,
|
|
1021
|
+
color: sourceColor,
|
|
1022
|
+
dashed,
|
|
1023
|
+
};
|
|
1024
|
+
const targetStub = {
|
|
1025
|
+
x: tx - tgtLabelEnd, y: ty,
|
|
1026
|
+
endX: tx - tgtLabelEnd - STUB_LENGTH,
|
|
1027
|
+
labelOffset: tgtLabelEnd,
|
|
1028
|
+
color: targetColor,
|
|
1029
|
+
dashed,
|
|
1030
|
+
};
|
|
1002
1031
|
let path;
|
|
1003
|
-
if (
|
|
1004
|
-
//
|
|
1032
|
+
if (xDistance > STUB_DISTANCE_THRESHOLD) {
|
|
1033
|
+
// Long-distance: static SVG hides the full path, only shows stubs
|
|
1034
|
+
path = '';
|
|
1035
|
+
}
|
|
1036
|
+
else if (!useCurve && distance > ORTHOGONAL_DISTANCE_THRESHOLD) {
|
|
1005
1037
|
const orthoPath = calculateOrthogonalPathSafe([sx, sy], [tx, ty], nodeBoxes, pc.fromNodeId, pc.toNodeId, { fromPortIndex: pc.fromPortIndex, toPortIndex: pc.toPortIndex }, allocator);
|
|
1006
1038
|
path = orthoPath ?? computeConnectionPath(sx, sy, tx, ty);
|
|
1007
1039
|
}
|
|
1008
1040
|
else {
|
|
1009
1041
|
path = computeConnectionPath(sx, sy, tx, ty);
|
|
1010
1042
|
}
|
|
1011
|
-
const sourceColor = getPortColor(pc.sourcePort.dataType, pc.sourcePort.isFailure, themeName);
|
|
1012
|
-
const targetColor = getPortColor(pc.targetPort.dataType, pc.targetPort.isFailure, themeName);
|
|
1013
1043
|
connections.push({
|
|
1014
1044
|
fromNode: pc.fromNodeId, fromPort: pc.fromPortName,
|
|
1015
1045
|
toNode: pc.toNodeId, toPort: pc.toPortName,
|
|
1016
1046
|
sourceColor, targetColor,
|
|
1017
1047
|
isStepConnection: pc.sourcePort.dataType === 'STEP',
|
|
1018
1048
|
path,
|
|
1049
|
+
sourceStub,
|
|
1050
|
+
targetStub,
|
|
1019
1051
|
});
|
|
1020
1052
|
}
|
|
1021
1053
|
// Recompute scope connection paths with the same routing logic as external connections
|
|
@@ -67,19 +67,19 @@ body {
|
|
|
67
67
|
transition: opacity 0.15s ease-in-out;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
/* Connection hover & dimming (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
body.node-active
|
|
74
|
-
body.port-active
|
|
75
|
-
body.port-hovered
|
|
70
|
+
/* Connection hover & dimming (covers paths + stub lines) */
|
|
71
|
+
[data-source] { transition: opacity 0.2s ease, stroke-width 0.15s ease; }
|
|
72
|
+
[data-source]:hover { stroke-width: 4; cursor: pointer; }
|
|
73
|
+
body.node-active [data-source].dimmed,
|
|
74
|
+
body.port-active [data-source].dimmed { opacity: 0.1; }
|
|
75
|
+
body.port-hovered [data-source].dimmed { opacity: 0.25; }
|
|
76
76
|
|
|
77
77
|
/* Port circles are interactive */
|
|
78
78
|
circle[data-port-id] { cursor: pointer; }
|
|
79
79
|
circle[data-port-id]:hover { stroke-width: 3; filter: brightness(1.3); }
|
|
80
80
|
|
|
81
81
|
/* Port-click highlighting */
|
|
82
|
-
|
|
82
|
+
[data-source].highlighted { opacity: 1; }
|
|
83
83
|
circle[data-port-id].port-selected { filter: drop-shadow(0 0 6px currentColor); stroke-width: 4; }
|
|
84
84
|
|
|
85
85
|
/* Node selection glow */
|
|
@@ -91,7 +91,7 @@ circle[data-port-id].port-selected { filter: drop-shadow(0 0 6px currentColor);
|
|
|
91
91
|
.node-glow { fill: none; pointer-events: none; animation: select-pop 0.3s ease-out forwards; }
|
|
92
92
|
|
|
93
93
|
/* Port hover path highlight */
|
|
94
|
-
|
|
94
|
+
[data-source].port-hover { opacity: 1; }
|
|
95
95
|
|
|
96
96
|
/* Node hover glow + draggable cursor */
|
|
97
97
|
.nodes g[data-node-id] { cursor: grab; }
|
|
@@ -541,12 +541,16 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
541
541
|
labelMap[lbl.getAttribute('data-port-label')] = lbl;
|
|
542
542
|
});
|
|
543
543
|
|
|
544
|
-
// Build adjacency: portId -> array of connected portIds
|
|
544
|
+
// Build adjacency: portId -> array of connected portIds (covers paths + stubs)
|
|
545
545
|
var portConnections = {};
|
|
546
|
-
|
|
546
|
+
var seenEdges = {};
|
|
547
|
+
content.querySelectorAll('[data-source]').forEach(function(p) {
|
|
547
548
|
var src = p.getAttribute('data-source');
|
|
548
549
|
var tgt = p.getAttribute('data-target');
|
|
549
550
|
if (!src || !tgt) return;
|
|
551
|
+
var key = src + '|' + tgt;
|
|
552
|
+
if (seenEdges[key]) return; // avoid duplicates from path+stub pairs
|
|
553
|
+
seenEdges[key] = true;
|
|
550
554
|
if (!portConnections[src]) portConnections[src] = [];
|
|
551
555
|
if (!portConnections[tgt]) portConnections[tgt] = [];
|
|
552
556
|
portConnections[src].push(tgt);
|
|
@@ -556,6 +560,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
556
560
|
// ---- Connection path computation (bezier from geometry.ts + orthogonal router) ----
|
|
557
561
|
var TRACK_SPACING = 15, EDGE_OFFSET = 5, MAX_CANDIDATES = 5;
|
|
558
562
|
var MIN_SEG_LEN = 3, JOG_THRESHOLD = 10, ORTHO_THRESHOLD = 300;
|
|
563
|
+
var STUB_THRESHOLD = 500, STUB_LEN = 30;
|
|
559
564
|
|
|
560
565
|
function quadCurveControl(ax, ay, bx, by, ux, uy) {
|
|
561
566
|
var dn = Math.abs(ay - by);
|
|
@@ -905,8 +910,66 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
905
910
|
var srcIdx = portIndexMap[srcNode] ? portIndexMap[srcNode].output.indexOf(src) : 0;
|
|
906
911
|
var tgtIdx = portIndexMap[tgtNode] ? portIndexMap[tgtNode].input.indexOf(tgt) : 0;
|
|
907
912
|
connIndex.push({ el: p, src: src, tgt: tgt, srcNode: srcNode, tgtNode: tgtNode,
|
|
908
|
-
scopeOf: p.getAttribute('data-scope') || null, srcIdx: Math.max(0, srcIdx), tgtIdx: Math.max(0, tgtIdx)
|
|
913
|
+
scopeOf: p.getAttribute('data-scope') || null, srcIdx: Math.max(0, srcIdx), tgtIdx: Math.max(0, tgtIdx),
|
|
914
|
+
stubs: null });
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
// Build stub index and link to connIndex entries.
|
|
918
|
+
// Compute labelOffset per stub (distance from port center to SVG stub start).
|
|
919
|
+
// In HTML, stubs start from port center; on label hover they push out past the badge.
|
|
920
|
+
var stubMap = {};
|
|
921
|
+
content.querySelectorAll('.stubs g.stub[data-source]').forEach(function(el) {
|
|
922
|
+
var src = el.getAttribute('data-source');
|
|
923
|
+
var tgt = el.getAttribute('data-target');
|
|
924
|
+
var key = src + '|' + tgt;
|
|
925
|
+
if (!stubMap[key]) stubMap[key] = { src: null, tgt: null, srcOff: 0, tgtOff: 0 };
|
|
926
|
+
var dir = el.getAttribute('data-stub');
|
|
927
|
+
var line = el.querySelector('line');
|
|
928
|
+
if (dir === 'source') {
|
|
929
|
+
stubMap[key].src = el;
|
|
930
|
+
if (line) {
|
|
931
|
+
var pp = portPositions[src];
|
|
932
|
+
if (pp) stubMap[key].srcOff = parseFloat(line.getAttribute('x1')) - pp.cx;
|
|
933
|
+
}
|
|
934
|
+
} else {
|
|
935
|
+
stubMap[key].tgt = el;
|
|
936
|
+
if (line) {
|
|
937
|
+
var pp = portPositions[tgt];
|
|
938
|
+
if (pp) stubMap[key].tgtOff = parseFloat(line.getAttribute('x1')) - pp.cx;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
909
941
|
});
|
|
942
|
+
for (var ci = 0; ci < connIndex.length; ci++) {
|
|
943
|
+
var entry = connIndex[ci];
|
|
944
|
+
var stubKey = entry.src + '|' + entry.tgt;
|
|
945
|
+
if (stubMap[stubKey]) entry.stubs = stubMap[stubKey];
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Pull all visible stubs to port center on init (labels are hidden in HTML)
|
|
949
|
+
for (var si = 0; si < connIndex.length; si++) {
|
|
950
|
+
var sc = connIndex[si];
|
|
951
|
+
if (!sc.stubs) continue;
|
|
952
|
+
if (sc.stubs.src) {
|
|
953
|
+
var sLine = sc.stubs.src.querySelector('line');
|
|
954
|
+
var sCirc = sc.stubs.src.querySelector('circle');
|
|
955
|
+
var spp = portPositions[sc.src];
|
|
956
|
+
if (sLine && spp) {
|
|
957
|
+
sLine.setAttribute('x1', spp.cx); sLine.setAttribute('y1', spp.cy);
|
|
958
|
+
sLine.setAttribute('x2', spp.cx + STUB_LEN); sLine.setAttribute('y2', spp.cy);
|
|
959
|
+
if (sCirc) { sCirc.setAttribute('cx', spp.cx + STUB_LEN); sCirc.setAttribute('cy', spp.cy); }
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (sc.stubs.tgt) {
|
|
963
|
+
var tLine = sc.stubs.tgt.querySelector('line');
|
|
964
|
+
var tCirc = sc.stubs.tgt.querySelector('circle');
|
|
965
|
+
var tpp = portPositions[sc.tgt];
|
|
966
|
+
if (tLine && tpp) {
|
|
967
|
+
tLine.setAttribute('x1', tpp.cx); tLine.setAttribute('y1', tpp.cy);
|
|
968
|
+
tLine.setAttribute('x2', tpp.cx - STUB_LEN); tLine.setAttribute('y2', tpp.cy);
|
|
969
|
+
if (tCirc) { tCirc.setAttribute('cx', tpp.cx - STUB_LEN); tCirc.setAttribute('cy', tpp.cy); }
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
910
973
|
|
|
911
974
|
// Snapshot of original port positions for reset
|
|
912
975
|
var origPortPositions = {};
|
|
@@ -919,8 +982,10 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
919
982
|
origNodeBoxMap[nid] = { id: b.id, x: b.x, y: b.y, w: b.w, h: b.h };
|
|
920
983
|
}
|
|
921
984
|
|
|
922
|
-
// ---- Recalculate all connection paths using orthogonal + bezier routing ----
|
|
985
|
+
// ---- Recalculate all connection paths + stubs using orthogonal + bezier routing ----
|
|
923
986
|
function recalcAllPaths() {
|
|
987
|
+
// Cancel all running stub animations since we're hard-repositioning
|
|
988
|
+
for (var ak in activeAnims) { cancelAnimationFrame(activeAnims[ak]); delete activeAnims[ak]; }
|
|
924
989
|
var boxes = [];
|
|
925
990
|
for (var nid in nodeBoxMap) boxes.push(nodeBoxMap[nid]);
|
|
926
991
|
var sorted = connIndex.slice().sort(function(a, b) {
|
|
@@ -943,15 +1008,46 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
943
1008
|
var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
|
|
944
1009
|
sx -= pOff.dx; sy -= pOff.dy; tx -= pOff.dx; ty -= pOff.dy;
|
|
945
1010
|
}
|
|
946
|
-
var
|
|
947
|
-
|
|
948
|
-
if (
|
|
949
|
-
|
|
950
|
-
|
|
1011
|
+
var xDist = Math.abs(tx - sx);
|
|
1012
|
+
|
|
1013
|
+
if (xDist > STUB_THRESHOLD) {
|
|
1014
|
+
// Stub mode: hide path, show and reposition stubs
|
|
1015
|
+
c.el.setAttribute('display', 'none');
|
|
1016
|
+
if (c.stubs) {
|
|
1017
|
+
if (c.stubs.src) {
|
|
1018
|
+
var sLen = isLabelVisible(c.src) ? c.stubs.srcOff + STUB_LEN : STUB_LEN;
|
|
1019
|
+
var sLine = c.stubs.src.querySelector('line');
|
|
1020
|
+
var sCirc = c.stubs.src.querySelector('circle');
|
|
1021
|
+
if (sLine) { sLine.setAttribute('x1', sx); sLine.setAttribute('y1', sy); sLine.setAttribute('x2', sx + sLen); sLine.setAttribute('y2', sy); }
|
|
1022
|
+
if (sCirc) { sCirc.setAttribute('cx', sx + sLen); sCirc.setAttribute('cy', sy); }
|
|
1023
|
+
c.stubs.src.removeAttribute('display');
|
|
1024
|
+
}
|
|
1025
|
+
if (c.stubs.tgt) {
|
|
1026
|
+
var tLen = isLabelVisible(c.tgt) ? c.stubs.tgtOff - STUB_LEN : -STUB_LEN;
|
|
1027
|
+
var tLine = c.stubs.tgt.querySelector('line');
|
|
1028
|
+
var tCirc = c.stubs.tgt.querySelector('circle');
|
|
1029
|
+
if (tLine) { tLine.setAttribute('x1', tx); tLine.setAttribute('y1', ty); tLine.setAttribute('x2', tx + tLen); tLine.setAttribute('y2', ty); }
|
|
1030
|
+
if (tCirc) { tCirc.setAttribute('cx', tx + tLen); tCirc.setAttribute('cy', ty); }
|
|
1031
|
+
c.stubs.tgt.removeAttribute('display');
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
951
1034
|
} else {
|
|
952
|
-
|
|
1035
|
+
// Path mode: show path, hide stubs
|
|
1036
|
+
var ddx = tx - sx, ddy = ty - sy, dist = Math.sqrt(ddx * ddx + ddy * ddy);
|
|
1037
|
+
var path;
|
|
1038
|
+
if (dist > ORTHO_THRESHOLD) {
|
|
1039
|
+
path = calcOrthogonalPath([sx, sy], [tx, ty], boxes, c.srcNode, c.tgtNode, c.srcIdx, c.tgtIdx, alloc);
|
|
1040
|
+
if (!path) path = computeConnectionPath(sx, sy, tx, ty);
|
|
1041
|
+
} else {
|
|
1042
|
+
path = computeConnectionPath(sx, sy, tx, ty);
|
|
1043
|
+
}
|
|
1044
|
+
c.el.setAttribute('d', path);
|
|
1045
|
+
c.el.removeAttribute('display');
|
|
1046
|
+
if (c.stubs) {
|
|
1047
|
+
if (c.stubs.src) c.stubs.src.setAttribute('display', 'none');
|
|
1048
|
+
if (c.stubs.tgt) c.stubs.tgt.setAttribute('display', 'none');
|
|
1049
|
+
}
|
|
953
1050
|
}
|
|
954
|
-
c.el.setAttribute('d', path);
|
|
955
1051
|
}
|
|
956
1052
|
}
|
|
957
1053
|
|
|
@@ -1069,9 +1165,75 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
1069
1165
|
|
|
1070
1166
|
var allLabelIds = Object.keys(labelMap);
|
|
1071
1167
|
var hoveredPort = null;
|
|
1168
|
+
var hoveredNode = null;
|
|
1169
|
+
var activeAnims = {};
|
|
1170
|
+
|
|
1171
|
+
// Build reverse map: portId -> array of { connEntry, isSource }
|
|
1172
|
+
var portStubMap = {};
|
|
1173
|
+
for (var psi = 0; psi < connIndex.length; psi++) {
|
|
1174
|
+
var psc = connIndex[psi];
|
|
1175
|
+
if (!psc.stubs) continue;
|
|
1176
|
+
if (psc.stubs.src) {
|
|
1177
|
+
if (!portStubMap[psc.src]) portStubMap[psc.src] = [];
|
|
1178
|
+
portStubMap[psc.src].push({ c: psc, isSource: true });
|
|
1179
|
+
}
|
|
1180
|
+
if (psc.stubs.tgt) {
|
|
1181
|
+
if (!portStubMap[psc.tgt]) portStubMap[psc.tgt] = [];
|
|
1182
|
+
portStubMap[psc.tgt].push({ c: psc, isSource: false });
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function isLabelVisible(id) {
|
|
1187
|
+
var l = labelMap[id];
|
|
1188
|
+
return l && l.style.opacity === '1';
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// Batch mode: defer stub sync until all label changes are done
|
|
1192
|
+
var stubSyncDeferred = false;
|
|
1193
|
+
var stubSyncPorts = {};
|
|
1072
1194
|
|
|
1073
|
-
function showLabel(id) {
|
|
1074
|
-
|
|
1195
|
+
function showLabel(id) {
|
|
1196
|
+
var l = labelMap[id];
|
|
1197
|
+
if (l) { l.style.opacity = '1'; l.style.pointerEvents = 'auto'; }
|
|
1198
|
+
if (stubSyncDeferred) { stubSyncPorts[id] = true; }
|
|
1199
|
+
else { syncStubsForPort(id); }
|
|
1200
|
+
}
|
|
1201
|
+
function hideLabel(id) {
|
|
1202
|
+
var l = labelMap[id];
|
|
1203
|
+
if (l) { l.style.opacity = '0'; l.style.pointerEvents = 'none'; }
|
|
1204
|
+
if (stubSyncDeferred) { stubSyncPorts[id] = true; }
|
|
1205
|
+
else { syncStubsForPort(id); }
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// Batch label changes: do all shows/hides, then sync stubs once
|
|
1209
|
+
function batchLabelChanges(fn) {
|
|
1210
|
+
stubSyncDeferred = true;
|
|
1211
|
+
stubSyncPorts = {};
|
|
1212
|
+
fn();
|
|
1213
|
+
stubSyncDeferred = false;
|
|
1214
|
+
for (var pid in stubSyncPorts) syncStubsForPort(pid);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function syncStubsForPort(portId) {
|
|
1218
|
+
var entries = portStubMap[portId];
|
|
1219
|
+
if (!entries) return;
|
|
1220
|
+
var visible = isLabelVisible(portId);
|
|
1221
|
+
for (var ei = 0; ei < entries.length; ei++) {
|
|
1222
|
+
growStub(entries[ei].c.stubs, entries[ei].c, entries[ei].isSource, visible);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// Safety net: verify all stubs match their port label visibility.
|
|
1227
|
+
// Runs as a rAF so it fires after all events and microtasks settle.
|
|
1228
|
+
var stubVerifyScheduled = false;
|
|
1229
|
+
function scheduleStubVerify() {
|
|
1230
|
+
if (stubVerifyScheduled) return;
|
|
1231
|
+
stubVerifyScheduled = true;
|
|
1232
|
+
requestAnimationFrame(function() {
|
|
1233
|
+
stubVerifyScheduled = false;
|
|
1234
|
+
for (var pid in portStubMap) syncStubsForPort(pid);
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1075
1237
|
|
|
1076
1238
|
function showLabelsFor(nodeId) {
|
|
1077
1239
|
allLabelIds.forEach(function(id) {
|
|
@@ -1084,6 +1246,52 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
1084
1246
|
});
|
|
1085
1247
|
}
|
|
1086
1248
|
|
|
1249
|
+
// Animate a stub growing/shrinking. The line x1 stays at port center,
|
|
1250
|
+
// x2 and circle cx extend to targetLen from port center (signed: positive=right, negative=left).
|
|
1251
|
+
function animateStub(stubG, targetLen, key) {
|
|
1252
|
+
if (!stubG) return;
|
|
1253
|
+
var line = stubG.querySelector('line');
|
|
1254
|
+
var circ = stubG.querySelector('circle');
|
|
1255
|
+
if (!line) return;
|
|
1256
|
+
var x1 = parseFloat(line.getAttribute('x1'));
|
|
1257
|
+
var curX2 = parseFloat(line.getAttribute('x2'));
|
|
1258
|
+
var curLen = curX2 - x1;
|
|
1259
|
+
var goalX2 = x1 + targetLen;
|
|
1260
|
+
if (Math.abs(curLen - targetLen) < 0.5) {
|
|
1261
|
+
line.setAttribute('x2', goalX2);
|
|
1262
|
+
if (circ) circ.setAttribute('cx', goalX2);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
if (activeAnims[key]) cancelAnimationFrame(activeAnims[key]);
|
|
1266
|
+
var start = null, duration = 150, startLen = curLen;
|
|
1267
|
+
function step(ts) {
|
|
1268
|
+
if (!start) start = ts;
|
|
1269
|
+
var t = Math.min((ts - start) / duration, 1);
|
|
1270
|
+
t = t * (2 - t); // ease-out quad
|
|
1271
|
+
var len = startLen + (targetLen - startLen) * t;
|
|
1272
|
+
var x2 = x1 + len;
|
|
1273
|
+
line.setAttribute('x2', x2);
|
|
1274
|
+
if (circ) circ.setAttribute('cx', x2);
|
|
1275
|
+
if (t < 1) activeAnims[key] = requestAnimationFrame(step);
|
|
1276
|
+
else delete activeAnims[key];
|
|
1277
|
+
}
|
|
1278
|
+
activeAnims[key] = requestAnimationFrame(step);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function growStub(stubs, connEntry, isSource, grow) {
|
|
1282
|
+
if (!stubs) return;
|
|
1283
|
+
var stubG = isSource ? stubs.src : stubs.tgt;
|
|
1284
|
+
if (!stubG) return;
|
|
1285
|
+
var off = isSource ? stubs.srcOff : stubs.tgtOff;
|
|
1286
|
+
// Source stubs grow right (+), target stubs grow left (-).
|
|
1287
|
+
// off is already signed (positive for source, negative for target).
|
|
1288
|
+
var shortLen = isSource ? STUB_LEN : -STUB_LEN;
|
|
1289
|
+
var fullLen = off + shortLen;
|
|
1290
|
+
var targetLen = grow ? fullLen : shortLen;
|
|
1291
|
+
var key = connEntry.src + '|' + connEntry.tgt + (isSource ? ':s' : ':t');
|
|
1292
|
+
animateStub(stubG, targetLen, key);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1087
1295
|
// Node hover: show all port labels for the hovered node
|
|
1088
1296
|
var nodeEls = content.querySelectorAll('.nodes g[data-node-id]');
|
|
1089
1297
|
nodeEls.forEach(function(nodeG) {
|
|
@@ -1091,14 +1299,27 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
1091
1299
|
var parentNodeG = nodeG.parentElement ? nodeG.parentElement.closest('g[data-node-id]') : null;
|
|
1092
1300
|
var parentId = parentNodeG ? parentNodeG.getAttribute('data-node-id') : null;
|
|
1093
1301
|
nodeG.addEventListener('mouseenter', function() {
|
|
1302
|
+
hoveredNode = nodeId;
|
|
1094
1303
|
if (hoveredPort) return;
|
|
1095
|
-
|
|
1096
|
-
|
|
1304
|
+
batchLabelChanges(function() {
|
|
1305
|
+
if (parentId) hideLabelsFor(parentId);
|
|
1306
|
+
showLabelsFor(nodeId);
|
|
1307
|
+
});
|
|
1308
|
+
scheduleStubVerify();
|
|
1097
1309
|
});
|
|
1098
1310
|
nodeG.addEventListener('mouseleave', function() {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
if (
|
|
1311
|
+
// Defer so port mouseenter can set hoveredPort first
|
|
1312
|
+
var nid = nodeId, pid = parentId;
|
|
1313
|
+
if (hoveredNode === nodeId) hoveredNode = null;
|
|
1314
|
+
Promise.resolve().then(function() {
|
|
1315
|
+
if (hoveredPort) return;
|
|
1316
|
+
if (hoveredNode === nid) return; // re-entered the node
|
|
1317
|
+
batchLabelChanges(function() {
|
|
1318
|
+
hideLabelsFor(nid);
|
|
1319
|
+
if (pid) showLabelsFor(pid);
|
|
1320
|
+
});
|
|
1321
|
+
scheduleStubVerify();
|
|
1322
|
+
});
|
|
1102
1323
|
});
|
|
1103
1324
|
});
|
|
1104
1325
|
|
|
@@ -1110,10 +1331,13 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
1110
1331
|
|
|
1111
1332
|
portEl.addEventListener('mouseenter', function() {
|
|
1112
1333
|
hoveredPort = portId;
|
|
1113
|
-
|
|
1114
|
-
|
|
1334
|
+
batchLabelChanges(function() {
|
|
1335
|
+
hideLabelsFor(nodeId);
|
|
1336
|
+
peers.forEach(showLabel);
|
|
1337
|
+
});
|
|
1338
|
+
scheduleStubVerify();
|
|
1115
1339
|
document.body.classList.add('port-hovered');
|
|
1116
|
-
content.querySelectorAll('
|
|
1340
|
+
content.querySelectorAll('[data-source]').forEach(function(p) {
|
|
1117
1341
|
if (p.getAttribute('data-source') === portId || p.getAttribute('data-target') === portId) {
|
|
1118
1342
|
p.classList.remove('dimmed');
|
|
1119
1343
|
} else {
|
|
@@ -1123,11 +1347,19 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
1123
1347
|
});
|
|
1124
1348
|
portEl.addEventListener('mouseleave', function() {
|
|
1125
1349
|
hoveredPort = null;
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1350
|
+
// Defer so if entering another port, its mouseenter sets hoveredPort first
|
|
1351
|
+
var myPeers = peers, myNodeId = nodeId;
|
|
1352
|
+
Promise.resolve().then(function() {
|
|
1353
|
+
if (hoveredPort) return; // moved to another port
|
|
1354
|
+
batchLabelChanges(function() {
|
|
1355
|
+
myPeers.forEach(hideLabel);
|
|
1356
|
+
showLabelsFor(myNodeId);
|
|
1357
|
+
});
|
|
1358
|
+
document.body.classList.remove('port-hovered');
|
|
1359
|
+
content.querySelectorAll('[data-source].dimmed').forEach(function(p) {
|
|
1360
|
+
p.classList.remove('dimmed');
|
|
1361
|
+
});
|
|
1362
|
+
scheduleStubVerify();
|
|
1131
1363
|
});
|
|
1132
1364
|
});
|
|
1133
1365
|
});
|
|
@@ -1162,7 +1394,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
1162
1394
|
content.querySelectorAll('circle.port-selected').forEach(function(c) {
|
|
1163
1395
|
c.classList.remove('port-selected');
|
|
1164
1396
|
});
|
|
1165
|
-
content.querySelectorAll('
|
|
1397
|
+
content.querySelectorAll('[data-source].dimmed, [data-source].highlighted').forEach(function(p) {
|
|
1166
1398
|
p.classList.remove('dimmed');
|
|
1167
1399
|
p.classList.remove('highlighted');
|
|
1168
1400
|
});
|
|
@@ -1178,7 +1410,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
1178
1410
|
var portEl = content.querySelector('[data-port-id="' + CSS.escape(portId) + '"]');
|
|
1179
1411
|
if (portEl) portEl.classList.add('port-selected');
|
|
1180
1412
|
|
|
1181
|
-
content.querySelectorAll('
|
|
1413
|
+
content.querySelectorAll('[data-source]').forEach(function(p) {
|
|
1182
1414
|
if (p.getAttribute('data-source') === portId || p.getAttribute('data-target') === portId) {
|
|
1183
1415
|
p.classList.add('highlighted');
|
|
1184
1416
|
} else {
|
|
@@ -1198,7 +1430,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
1198
1430
|
infoPanel.classList.remove('fullscreen');
|
|
1199
1431
|
btnExpand.innerHTML = expandIcon;
|
|
1200
1432
|
removeNodeGlow();
|
|
1201
|
-
content.querySelectorAll('
|
|
1433
|
+
content.querySelectorAll('[data-source].dimmed').forEach(function(p) {
|
|
1202
1434
|
p.classList.remove('dimmed');
|
|
1203
1435
|
});
|
|
1204
1436
|
}
|
|
@@ -1226,7 +1458,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
1226
1458
|
});
|
|
1227
1459
|
|
|
1228
1460
|
// Connected paths
|
|
1229
|
-
var allPaths = content.querySelectorAll('
|
|
1461
|
+
var allPaths = content.querySelectorAll('[data-source]');
|
|
1230
1462
|
var connectedNodes = new Set();
|
|
1231
1463
|
allPaths.forEach(function(p) {
|
|
1232
1464
|
var src = p.getAttribute('data-source') || '';
|
package/dist/diagram/renderer.js
CHANGED
|
@@ -56,11 +56,21 @@ export function renderSVG(graph, options = {}) {
|
|
|
56
56
|
// Background
|
|
57
57
|
parts.push(`<rect x="${vbX}" y="${vbY}" width="${vbWidth}" height="${vbHeight}" fill="${theme.background}"/>`);
|
|
58
58
|
parts.push(`<rect x="${vbX}" y="${vbY}" width="${vbWidth}" height="${vbHeight}" fill="url(#dot-grid)"/>`);
|
|
59
|
-
// Connections
|
|
59
|
+
// Connections + stubs (same layer, below nodes and labels)
|
|
60
60
|
parts.push(`<g class="connections">`);
|
|
61
61
|
for (let i = 0; i < graph.connections.length; i++) {
|
|
62
|
-
renderConnection(parts, graph.connections[i], i);
|
|
62
|
+
renderConnection(parts, graph.connections[i], i, !graph.connections[i].path);
|
|
63
63
|
}
|
|
64
|
+
// Stubs sit alongside connection paths; short-distance ones start hidden for HTML viewer toggling
|
|
65
|
+
parts.push(` <g class="stubs">`);
|
|
66
|
+
for (const conn of graph.connections) {
|
|
67
|
+
const hideStubs = !!conn.path;
|
|
68
|
+
if (conn.sourceStub)
|
|
69
|
+
renderStub(parts, conn.sourceStub, conn, hideStubs);
|
|
70
|
+
if (conn.targetStub)
|
|
71
|
+
renderStub(parts, conn.targetStub, conn, hideStubs);
|
|
72
|
+
}
|
|
73
|
+
parts.push(` </g>`);
|
|
64
74
|
parts.push(`</g>`);
|
|
65
75
|
// Nodes (bodies, icons, port dots — no labels)
|
|
66
76
|
parts.push(`<g class="nodes">`);
|
|
@@ -99,9 +109,23 @@ export function renderSVG(graph, options = {}) {
|
|
|
99
109
|
return parts.join('\n');
|
|
100
110
|
}
|
|
101
111
|
// ---- Connection rendering ----
|
|
102
|
-
function renderConnection(parts, conn, gradIndex) {
|
|
112
|
+
function renderConnection(parts, conn, gradIndex, hidden = false) {
|
|
103
113
|
const dashAttr = conn.isStepConnection ? '' : ' stroke-dasharray="8 4"';
|
|
104
|
-
|
|
114
|
+
const displayAttr = hidden ? ' display="none"' : '';
|
|
115
|
+
const pathD = conn.path || 'M0,0';
|
|
116
|
+
parts.push(` <path d="${pathD}" 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"${displayAttr}/>`);
|
|
117
|
+
}
|
|
118
|
+
function renderStub(parts, stub, conn, hidden = false) {
|
|
119
|
+
const dashAttr = stub.dashed ? ' stroke-dasharray="6 3"' : '';
|
|
120
|
+
const displayAttr = hidden ? ' display="none"' : '';
|
|
121
|
+
const dataAttrs = `data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"`;
|
|
122
|
+
const isSource = stub.endX > stub.x; // source stubs go right
|
|
123
|
+
const stubDir = isSource ? 'source' : 'target';
|
|
124
|
+
parts.push(` <g class="stub" data-stub="${stubDir}" ${dataAttrs}${displayAttr}>`);
|
|
125
|
+
const linecap = stub.dashed ? 'butt' : 'round';
|
|
126
|
+
parts.push(` <line x1="${stub.x}" y1="${stub.y}" x2="${stub.endX}" y2="${stub.y}" stroke="${stub.color}" stroke-width="2"${dashAttr} stroke-linecap="${linecap}"/>`);
|
|
127
|
+
parts.push(` <circle cx="${stub.endX}" cy="${stub.y}" r="3" fill="${stub.color}"/>`);
|
|
128
|
+
parts.push(` </g>`);
|
|
105
129
|
}
|
|
106
130
|
function renderScopeConnection(parts, conn, allConnections, parentNodeId) {
|
|
107
131
|
const gradIndex = allConnections.indexOf(conn);
|
|
@@ -177,7 +201,7 @@ function renderNodeLabel(parts, node, theme) {
|
|
|
177
201
|
const labelTextX = isScoped ? node.x + 8 : node.x + node.width / 2;
|
|
178
202
|
const labelAnchor = isScoped ? 'start' : 'middle';
|
|
179
203
|
parts.push(` <g data-label-for="${escapeXml(node.id)}">`);
|
|
180
|
-
parts.push(` <rect x="${labelBgX}" y="${labelBgY}" width="${labelBgWidth}" height="${labelBgHeight}" rx="6" fill="${theme.labelBadgeFill}" opacity="0.
|
|
204
|
+
parts.push(` <rect x="${labelBgX}" y="${labelBgY}" width="${labelBgWidth}" height="${labelBgHeight}" rx="6" fill="${theme.labelBadgeFill}" opacity="0.95"/>`);
|
|
181
205
|
parts.push(` <text class="node-label" x="${labelTextX}" y="${labelBgY + labelBgHeight / 2 + 6}" text-anchor="${labelAnchor}" fill="${node.color !== NODE_DEFAULT_COLOR ? node.color : theme.labelColor}">${labelText}</text>`);
|
|
182
206
|
parts.push(` </g>`);
|
|
183
207
|
}
|
package/dist/diagram/types.d.ts
CHANGED
|
@@ -36,6 +36,14 @@ export interface DiagramNode {
|
|
|
36
36
|
outputs: DiagramPort[];
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
+
export interface DiagramStub {
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
endX: number;
|
|
43
|
+
labelOffset: number;
|
|
44
|
+
color: string;
|
|
45
|
+
dashed: boolean;
|
|
46
|
+
}
|
|
39
47
|
export interface DiagramConnection {
|
|
40
48
|
fromNode: string;
|
|
41
49
|
fromPort: string;
|
|
@@ -45,6 +53,8 @@ export interface DiagramConnection {
|
|
|
45
53
|
targetColor: string;
|
|
46
54
|
isStepConnection: boolean;
|
|
47
55
|
path: string;
|
|
56
|
+
sourceStub?: DiagramStub;
|
|
57
|
+
targetStub?: DiagramStub;
|
|
48
58
|
}
|
|
49
59
|
export interface DiagramGraph {
|
|
50
60
|
nodes: DiagramNode[];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.21.
|
|
1
|
+
export declare const VERSION = "0.21.7";
|
|
2
2
|
//# sourceMappingURL=generated-version.d.ts.map
|
|
@@ -10,7 +10,7 @@ import * as path from 'path';
|
|
|
10
10
|
import { glob } from 'glob';
|
|
11
11
|
const MARKETPLACE_KEYWORD = 'flowweaver-marketplace-pack';
|
|
12
12
|
const NPM_SEARCH_URL = 'https://registry.npmjs.org/-/v1/search';
|
|
13
|
-
const PACK_NAME_RE = /^(@[^/]+\/)?
|
|
13
|
+
const PACK_NAME_RE = /^(@[^/]+\/)?flow-weaver-pack-.+$/;
|
|
14
14
|
/**
|
|
15
15
|
* Search the npm registry for marketplace packages.
|
|
16
16
|
*/
|
|
@@ -49,10 +49,8 @@ export async function listInstalledPackages(projectDir) {
|
|
|
49
49
|
const nodeModules = path.join(projectDir, 'node_modules');
|
|
50
50
|
if (!fs.existsSync(nodeModules))
|
|
51
51
|
return [];
|
|
52
|
-
// Look for
|
|
52
|
+
// Look for flow-weaver-pack-* and @*/flow-weaver-pack-* directories
|
|
53
53
|
const patterns = [
|
|
54
|
-
path.join(nodeModules, 'flowweaver-pack-*', 'flowweaver.manifest.json'),
|
|
55
|
-
path.join(nodeModules, '@*', 'flowweaver-pack-*', 'flowweaver.manifest.json'),
|
|
56
54
|
path.join(nodeModules, 'flow-weaver-pack-*', 'flowweaver.manifest.json'),
|
|
57
55
|
path.join(nodeModules, '@*', 'flow-weaver-pack-*', 'flowweaver.manifest.json'),
|
|
58
56
|
];
|
|
@@ -9,7 +9,7 @@ import { parseWorkflow, validateWorkflow } from '../api/index.js';
|
|
|
9
9
|
function issue(code, severity, message) {
|
|
10
10
|
return { code, severity, message };
|
|
11
11
|
}
|
|
12
|
-
const PACK_NAME_RE = /^(@[^/]+\/)?
|
|
12
|
+
const PACK_NAME_RE = /^(@[^/]+\/)?flow-weaver-pack-.+$/;
|
|
13
13
|
const MARKETPLACE_KEYWORD = 'flowweaver-marketplace-pack';
|
|
14
14
|
// ── Package-level rules ──────────────────────────────────────────────────────
|
|
15
15
|
function validatePackageJson(pkg, directory) {
|
|
@@ -696,7 +696,7 @@ flow-weaver export workflow.ts --target inngest --output dist/ --durable-steps
|
|
|
696
696
|
flow-weaver export workflow.ts --target cloudflare --output worker/
|
|
697
697
|
```
|
|
698
698
|
|
|
699
|
-
> Available targets depend on installed `
|
|
699
|
+
> Available targets depend on installed `flow-weaver-pack-*` packages. See [Deployment](deployment) for installation instructions and target-specific details.
|
|
700
700
|
|
|
701
701
|
---
|
|
702
702
|
|