@synergenius/flow-weaver 0.19.0 → 0.19.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 +473 -50
- package/dist/diagram/geometry.js +101 -11
- package/dist/diagram/html-viewer.js +377 -39
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/package.json +1 -1
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -9671,7 +9671,7 @@ var VERSION;
|
|
|
9671
9671
|
var init_generated_version = __esm({
|
|
9672
9672
|
"src/generated-version.ts"() {
|
|
9673
9673
|
"use strict";
|
|
9674
|
-
VERSION = "0.19.
|
|
9674
|
+
VERSION = "0.19.1";
|
|
9675
9675
|
}
|
|
9676
9676
|
});
|
|
9677
9677
|
|
|
@@ -51065,6 +51065,13 @@ function buildDiagramGraph(ast, options = {}) {
|
|
|
51065
51065
|
width: node.width,
|
|
51066
51066
|
height: node.height
|
|
51067
51067
|
}));
|
|
51068
|
+
for (const node of nodes) {
|
|
51069
|
+
if (node.scopeChildren) {
|
|
51070
|
+
for (const child of node.scopeChildren) {
|
|
51071
|
+
nodeBoxes.push({ id: child.id, x: child.x, y: child.y, width: child.width, height: child.height });
|
|
51072
|
+
}
|
|
51073
|
+
}
|
|
51074
|
+
}
|
|
51068
51075
|
pendingConnections.sort((a, b) => {
|
|
51069
51076
|
const aSpan = Math.abs(a.targetPort.cx - a.sourcePort.cx);
|
|
51070
51077
|
const bSpan = Math.abs(b.targetPort.cx - b.sourcePort.cx);
|
|
@@ -51138,15 +51145,93 @@ function buildDiagramGraph(ast, options = {}) {
|
|
|
51138
51145
|
});
|
|
51139
51146
|
}
|
|
51140
51147
|
for (const node of nodes) {
|
|
51141
|
-
if (node.scopeConnections
|
|
51142
|
-
|
|
51143
|
-
|
|
51144
|
-
|
|
51145
|
-
|
|
51146
|
-
|
|
51147
|
-
|
|
51148
|
-
|
|
51149
|
-
|
|
51148
|
+
if (!node.scopeConnections || !node.scopePorts || !node.scopeChildren) continue;
|
|
51149
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
51150
|
+
for (const c of node.scopeChildren) childMap.set(c.id, c);
|
|
51151
|
+
const scopePending = [];
|
|
51152
|
+
for (const conn of node.scopeConnections) {
|
|
51153
|
+
const sPort = findScopePort(conn.fromNode, conn.fromPort, node, childMap, "output");
|
|
51154
|
+
const tPort = findScopePort(conn.toNode, conn.toPort, node, childMap, "input");
|
|
51155
|
+
if (!sPort || !tPort) continue;
|
|
51156
|
+
let fromPortIndex = 0;
|
|
51157
|
+
let toPortIndex = 0;
|
|
51158
|
+
if (conn.fromNode === node.id) {
|
|
51159
|
+
fromPortIndex = node.scopePorts.outputs.indexOf(sPort);
|
|
51160
|
+
} else {
|
|
51161
|
+
const fromChild = childMap.get(conn.fromNode);
|
|
51162
|
+
if (fromChild) fromPortIndex = fromChild.outputs.indexOf(sPort);
|
|
51163
|
+
}
|
|
51164
|
+
if (conn.toNode === node.id) {
|
|
51165
|
+
toPortIndex = node.scopePorts.inputs.indexOf(tPort);
|
|
51166
|
+
} else {
|
|
51167
|
+
const toChild = childMap.get(conn.toNode);
|
|
51168
|
+
if (toChild) toPortIndex = toChild.inputs.indexOf(tPort);
|
|
51169
|
+
}
|
|
51170
|
+
scopePending.push({
|
|
51171
|
+
conn,
|
|
51172
|
+
sourcePort: sPort,
|
|
51173
|
+
targetPort: tPort,
|
|
51174
|
+
fromNodeId: conn.fromNode,
|
|
51175
|
+
toNodeId: conn.toNode,
|
|
51176
|
+
fromPortIndex: Math.max(0, fromPortIndex),
|
|
51177
|
+
toPortIndex: Math.max(0, toPortIndex)
|
|
51178
|
+
});
|
|
51179
|
+
}
|
|
51180
|
+
scopePending.sort((a, b) => {
|
|
51181
|
+
const aSpan = Math.abs(a.targetPort.cx - a.sourcePort.cx);
|
|
51182
|
+
const bSpan = Math.abs(b.targetPort.cx - b.sourcePort.cx);
|
|
51183
|
+
if (Math.abs(aSpan - bSpan) > 1) return aSpan - bSpan;
|
|
51184
|
+
if (Math.abs(a.sourcePort.cx - b.sourcePort.cx) > 1) return a.sourcePort.cx - b.sourcePort.cx;
|
|
51185
|
+
return a.sourcePort.cy - b.sourcePort.cy;
|
|
51186
|
+
});
|
|
51187
|
+
const spSrcKey = (sp) => `s:${sp.fromNodeId}.${sp.conn.fromPort}`;
|
|
51188
|
+
const spTgtKey = (sp) => `t:${sp.toNodeId}.${sp.conn.toPort}`;
|
|
51189
|
+
const spTgtNodeKey = (sp) => `n:${sp.toNodeId}`;
|
|
51190
|
+
const spGroups = /* @__PURE__ */ new Map();
|
|
51191
|
+
for (const sp of scopePending) {
|
|
51192
|
+
for (const key of [spSrcKey(sp), spTgtKey(sp), spTgtNodeKey(sp)]) {
|
|
51193
|
+
if (!spGroups.has(key)) spGroups.set(key, []);
|
|
51194
|
+
spGroups.get(key).push(sp);
|
|
51195
|
+
}
|
|
51196
|
+
}
|
|
51197
|
+
const spForceCurveKeys = /* @__PURE__ */ new Set();
|
|
51198
|
+
for (const [key, group] of spGroups) {
|
|
51199
|
+
if (group.length < 2) continue;
|
|
51200
|
+
const anyShort = group.some((sp) => {
|
|
51201
|
+
const ddx = sp.targetPort.cx - sp.sourcePort.cx;
|
|
51202
|
+
const ddy = sp.targetPort.cy - sp.sourcePort.cy;
|
|
51203
|
+
return Math.sqrt(ddx * ddx + ddy * ddy) <= ORTHOGONAL_DISTANCE_THRESHOLD;
|
|
51204
|
+
});
|
|
51205
|
+
if (anyShort) spForceCurveKeys.add(key);
|
|
51206
|
+
}
|
|
51207
|
+
const spForceCurveSet = /* @__PURE__ */ new Set();
|
|
51208
|
+
for (const sp of scopePending) {
|
|
51209
|
+
if (spForceCurveKeys.has(spSrcKey(sp)) || spForceCurveKeys.has(spTgtKey(sp)) || spForceCurveKeys.has(spTgtNodeKey(sp))) {
|
|
51210
|
+
spForceCurveSet.add(sp);
|
|
51211
|
+
}
|
|
51212
|
+
}
|
|
51213
|
+
for (const sp of scopePending) {
|
|
51214
|
+
const sx = sp.sourcePort.cx;
|
|
51215
|
+
const sy = sp.sourcePort.cy;
|
|
51216
|
+
const tx = sp.targetPort.cx;
|
|
51217
|
+
const ty = sp.targetPort.cy;
|
|
51218
|
+
const ddx = tx - sx;
|
|
51219
|
+
const ddy = ty - sy;
|
|
51220
|
+
const dist = Math.sqrt(ddx * ddx + ddy * ddy);
|
|
51221
|
+
const useCurve = spForceCurveSet.has(sp);
|
|
51222
|
+
if (!useCurve && dist > ORTHOGONAL_DISTANCE_THRESHOLD) {
|
|
51223
|
+
const orthoPath = calculateOrthogonalPathSafe(
|
|
51224
|
+
[sx, sy],
|
|
51225
|
+
[tx, ty],
|
|
51226
|
+
nodeBoxes,
|
|
51227
|
+
sp.fromNodeId,
|
|
51228
|
+
sp.toNodeId,
|
|
51229
|
+
{ fromPortIndex: sp.fromPortIndex, toPortIndex: sp.toPortIndex },
|
|
51230
|
+
allocator
|
|
51231
|
+
);
|
|
51232
|
+
sp.conn.path = orthoPath ?? computeConnectionPath(sx, sy, tx, ty);
|
|
51233
|
+
} else {
|
|
51234
|
+
sp.conn.path = computeConnectionPath(sx, sy, tx, ty);
|
|
51150
51235
|
}
|
|
51151
51236
|
}
|
|
51152
51237
|
}
|
|
@@ -52580,7 +52665,10 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
52580
52665
|
portConnections[tgt].push(src);
|
|
52581
52666
|
});
|
|
52582
52667
|
|
|
52583
|
-
// ---- Connection path computation (from geometry.ts) ----
|
|
52668
|
+
// ---- Connection path computation (bezier from geometry.ts + orthogonal router) ----
|
|
52669
|
+
var TRACK_SPACING = 15, EDGE_OFFSET = 5, MAX_CANDIDATES = 5;
|
|
52670
|
+
var MIN_SEG_LEN = 3, JOG_THRESHOLD = 10, ORTHO_THRESHOLD = 300;
|
|
52671
|
+
|
|
52584
52672
|
function quadCurveControl(ax, ay, bx, by, ux, uy) {
|
|
52585
52673
|
var dn = Math.abs(ay - by);
|
|
52586
52674
|
return [bx + (ux * dn) / Math.abs(uy), ay];
|
|
@@ -52610,18 +52698,326 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
52610
52698
|
' L ' + hx + ',' + hy;
|
|
52611
52699
|
}
|
|
52612
52700
|
|
|
52613
|
-
// ----
|
|
52701
|
+
// ---- Orthogonal router (ported from orthogonal-router.ts) ----
|
|
52702
|
+
function createAllocator() {
|
|
52703
|
+
var claims = [], vclaims = [];
|
|
52704
|
+
function isOcc(xn, xx, y) {
|
|
52705
|
+
for (var i = 0; i < claims.length; i++) {
|
|
52706
|
+
var c = claims[i];
|
|
52707
|
+
if (c.xn < xx && c.xx > xn && Math.abs(c.y - y) < TRACK_SPACING) return true;
|
|
52708
|
+
}
|
|
52709
|
+
return false;
|
|
52710
|
+
}
|
|
52711
|
+
function isOccV(yn, yx, x) {
|
|
52712
|
+
for (var i = 0; i < vclaims.length; i++) {
|
|
52713
|
+
var c = vclaims[i];
|
|
52714
|
+
if (c.yn < yx && c.yx > yn && Math.abs(c.x - x) < TRACK_SPACING) return true;
|
|
52715
|
+
}
|
|
52716
|
+
return false;
|
|
52717
|
+
}
|
|
52718
|
+
function blockedByNode(xn, xx, y, boxes) {
|
|
52719
|
+
for (var i = 0; i < boxes.length; i++) {
|
|
52720
|
+
var b = boxes[i]; if (xn < b.r && xx > b.l && y >= b.t && y <= b.b) return true;
|
|
52721
|
+
}
|
|
52722
|
+
return false;
|
|
52723
|
+
}
|
|
52724
|
+
function blockedByNodeV(yn, yx, x, boxes) {
|
|
52725
|
+
for (var i = 0; i < boxes.length; i++) {
|
|
52726
|
+
var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return true;
|
|
52727
|
+
}
|
|
52728
|
+
return false;
|
|
52729
|
+
}
|
|
52730
|
+
function countHCross(xn, xx, y) {
|
|
52731
|
+
var n = 0;
|
|
52732
|
+
for (var i = 0; i < vclaims.length; i++) {
|
|
52733
|
+
var c = vclaims[i]; if (c.x > xn && c.x < xx && y >= c.yn && y <= c.yx) n++;
|
|
52734
|
+
}
|
|
52735
|
+
return n;
|
|
52736
|
+
}
|
|
52737
|
+
function countVCross(yn, yx, x) {
|
|
52738
|
+
var n = 0;
|
|
52739
|
+
for (var i = 0; i < claims.length; i++) {
|
|
52740
|
+
var c = claims[i]; if (c.y > yn && c.y < yx && x >= c.xn && x <= c.xx) n++;
|
|
52741
|
+
}
|
|
52742
|
+
return n;
|
|
52743
|
+
}
|
|
52744
|
+
return {
|
|
52745
|
+
findFreeY: function(xn, xx, cy, nb) {
|
|
52746
|
+
var free = function(y) { return !isOcc(xn, xx, y) && (!nb || !blockedByNode(xn, xx, y, nb)); };
|
|
52747
|
+
if (free(cy)) return cy;
|
|
52748
|
+
var cands = [];
|
|
52749
|
+
for (var off = TRACK_SPACING; off < 800 && cands.length < MAX_CANDIDATES * 2; off += TRACK_SPACING) {
|
|
52750
|
+
if (free(cy - off)) cands.push({ y: cy - off, d: off });
|
|
52751
|
+
if (free(cy + off)) cands.push({ y: cy + off, d: off });
|
|
52752
|
+
}
|
|
52753
|
+
if (!cands.length) return cy;
|
|
52754
|
+
var best = cands[0].y, bc = countHCross(xn, xx, cands[0].y), bd = cands[0].d;
|
|
52755
|
+
for (var i = 1; i < cands.length; i++) {
|
|
52756
|
+
var cr = countHCross(xn, xx, cands[i].y);
|
|
52757
|
+
if (cr < bc || (cr === bc && cands[i].d < bd)) { best = cands[i].y; bc = cr; bd = cands[i].d; }
|
|
52758
|
+
}
|
|
52759
|
+
return best;
|
|
52760
|
+
},
|
|
52761
|
+
findFreeX: function(yn, yx, cx, nb) {
|
|
52762
|
+
var free = function(x) { return !isOccV(yn, yx, x) && (!nb || !blockedByNodeV(yn, yx, x, nb)); };
|
|
52763
|
+
if (free(cx)) return cx;
|
|
52764
|
+
var cands = [];
|
|
52765
|
+
for (var off = TRACK_SPACING; off < 800 && cands.length < MAX_CANDIDATES * 2; off += TRACK_SPACING) {
|
|
52766
|
+
if (free(cx - off)) cands.push({ x: cx - off, d: off });
|
|
52767
|
+
if (free(cx + off)) cands.push({ x: cx + off, d: off });
|
|
52768
|
+
}
|
|
52769
|
+
if (!cands.length) return cx;
|
|
52770
|
+
var best = cands[0].x, bc = countVCross(yn, yx, cands[0].x), bd = cands[0].d;
|
|
52771
|
+
for (var i = 1; i < cands.length; i++) {
|
|
52772
|
+
var cr = countVCross(yn, yx, cands[i].x);
|
|
52773
|
+
if (cr < bc || (cr === bc && cands[i].d < bd)) { best = cands[i].x; bc = cr; bd = cands[i].d; }
|
|
52774
|
+
}
|
|
52775
|
+
return best;
|
|
52776
|
+
},
|
|
52777
|
+
claim: function(xn, xx, y) { claims.push({ xn: xn, xx: xx, y: y }); },
|
|
52778
|
+
claimV: function(yn, yx, x) { vclaims.push({ yn: yn, yx: yx, x: x }); }
|
|
52779
|
+
};
|
|
52780
|
+
}
|
|
52781
|
+
|
|
52782
|
+
function inflateBox(b, pad) { return { l: b.x - pad, r: b.x + b.w + pad, t: b.y - pad, b: b.y + b.h + pad }; }
|
|
52783
|
+
function segOvlp(xn, xx, y, bx) { return xn < bx.r && xx > bx.l && y >= bx.t && y <= bx.b; }
|
|
52784
|
+
function vSegClear(x, yn, yx, boxes) {
|
|
52785
|
+
for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return false; }
|
|
52786
|
+
return true;
|
|
52787
|
+
}
|
|
52788
|
+
|
|
52789
|
+
function findClearY(xn, xx, cy, boxes) {
|
|
52790
|
+
var blocked = function(y) { for (var i = 0; i < boxes.length; i++) if (segOvlp(xn, xx, y, boxes[i])) return true; return false; };
|
|
52791
|
+
if (!blocked(cy)) return cy;
|
|
52792
|
+
var edges = [];
|
|
52793
|
+
for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (xn < b.r && xx > b.l) { edges.push(b.t); edges.push(b.b); } }
|
|
52794
|
+
if (!edges.length) return cy;
|
|
52795
|
+
edges.sort(function(a, b) { return a - b; });
|
|
52796
|
+
var best = cy, bd = Infinity;
|
|
52797
|
+
for (var i = 0; i < edges.length; i++) {
|
|
52798
|
+
var vals = [edges[i] - EDGE_OFFSET, edges[i] + EDGE_OFFSET];
|
|
52799
|
+
for (var j = 0; j < 2; j++) { if (!blocked(vals[j])) { var d = Math.abs(vals[j] - cy); if (d < bd) { bd = d; best = vals[j]; } } }
|
|
52800
|
+
}
|
|
52801
|
+
if (bd === Infinity) {
|
|
52802
|
+
var mn = Math.min.apply(null, edges) - EDGE_OFFSET * 2, mx = Math.max.apply(null, edges) + EDGE_OFFSET * 2;
|
|
52803
|
+
best = Math.abs(mn - cy) <= Math.abs(mx - cy) ? mn : mx;
|
|
52804
|
+
if (blocked(best)) { for (var off = TRACK_SPACING; off < 800; off += TRACK_SPACING) { if (!blocked(best - off)) { best -= off; break; } if (!blocked(best + off)) { best += off; break; } } }
|
|
52805
|
+
}
|
|
52806
|
+
return best;
|
|
52807
|
+
}
|
|
52808
|
+
|
|
52809
|
+
function findClearX(yn, yx, cx, boxes) {
|
|
52810
|
+
var blocked = function(x) { for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return true; } return false; };
|
|
52811
|
+
if (!blocked(cx)) return cx;
|
|
52812
|
+
var edges = [];
|
|
52813
|
+
for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (yn < b.b && yx > b.t) { edges.push(b.l); edges.push(b.r); } }
|
|
52814
|
+
if (!edges.length) return cx;
|
|
52815
|
+
edges.sort(function(a, b) { return a - b; });
|
|
52816
|
+
var best = cx, bd = Infinity;
|
|
52817
|
+
for (var i = 0; i < edges.length; i++) {
|
|
52818
|
+
var vals = [edges[i] - EDGE_OFFSET, edges[i] + EDGE_OFFSET];
|
|
52819
|
+
for (var j = 0; j < 2; j++) { if (!blocked(vals[j])) { var d = Math.abs(vals[j] - cx); if (d < bd) { bd = d; best = vals[j]; } } }
|
|
52820
|
+
}
|
|
52821
|
+
if (bd === Infinity) {
|
|
52822
|
+
var mn = Math.min.apply(null, edges) - EDGE_OFFSET * 2, mx = Math.max.apply(null, edges) + EDGE_OFFSET * 2;
|
|
52823
|
+
best = Math.abs(mn - cx) <= Math.abs(mx - cx) ? mn : mx;
|
|
52824
|
+
if (blocked(best)) { for (var off = TRACK_SPACING; off < 800; off += TRACK_SPACING) { if (!blocked(best - off)) { best -= off; break; } if (!blocked(best + off)) { best += off; break; } } }
|
|
52825
|
+
}
|
|
52826
|
+
return best;
|
|
52827
|
+
}
|
|
52828
|
+
|
|
52829
|
+
function simplifyWaypoints(pts) {
|
|
52830
|
+
if (pts.length <= 2) return pts;
|
|
52831
|
+
var jogFound = true;
|
|
52832
|
+
while (jogFound) {
|
|
52833
|
+
jogFound = false;
|
|
52834
|
+
for (var i = 0; i < pts.length - 3; i++) {
|
|
52835
|
+
var a = pts[i], b = pts[i+1], c = pts[i+2], d = pts[i+3];
|
|
52836
|
+
var jogH = Math.abs(b[1] - c[1]);
|
|
52837
|
+
if (Math.abs(a[1] - b[1]) < 0.5 && Math.abs(b[0] - c[0]) < 0.5 && Math.abs(c[1] - d[1]) < 0.5 && jogH > 0.5 && jogH < JOG_THRESHOLD) {
|
|
52838
|
+
var mid = (b[1] + c[1]) / 2, snap = Math.abs(a[1] - mid) <= Math.abs(d[1] - mid) ? a[1] : d[1];
|
|
52839
|
+
pts = pts.slice(); pts[i+1] = [b[0], snap]; pts[i+2] = [c[0], snap]; jogFound = true; break;
|
|
52840
|
+
}
|
|
52841
|
+
var jogW = Math.abs(b[0] - c[0]);
|
|
52842
|
+
if (Math.abs(a[0] - b[0]) < 0.5 && Math.abs(b[1] - c[1]) < 0.5 && Math.abs(c[0] - d[0]) < 0.5 && jogW > 0.5 && jogW < JOG_THRESHOLD) {
|
|
52843
|
+
var mid = (b[0] + c[0]) / 2, snap = Math.abs(a[0] - mid) <= Math.abs(d[0] - mid) ? a[0] : d[0];
|
|
52844
|
+
pts = pts.slice(); pts[i+1] = [snap, b[1]]; pts[i+2] = [snap, c[1]]; jogFound = true; break;
|
|
52845
|
+
}
|
|
52846
|
+
}
|
|
52847
|
+
}
|
|
52848
|
+
var res = [pts[0]];
|
|
52849
|
+
for (var i = 1; i < pts.length - 1; i++) {
|
|
52850
|
+
var prev = res[res.length - 1], cur = pts[i], next = pts[i+1];
|
|
52851
|
+
if (Math.abs(prev[0] - cur[0]) + Math.abs(prev[1] - cur[1]) < MIN_SEG_LEN) continue;
|
|
52852
|
+
var sameX = Math.abs(prev[0] - cur[0]) < 0.01 && Math.abs(cur[0] - next[0]) < 0.01;
|
|
52853
|
+
var sameY = Math.abs(prev[1] - cur[1]) < 0.01 && Math.abs(cur[1] - next[1]) < 0.01;
|
|
52854
|
+
if (!sameX && !sameY) res.push(cur);
|
|
52855
|
+
}
|
|
52856
|
+
res.push(pts[pts.length - 1]);
|
|
52857
|
+
return res;
|
|
52858
|
+
}
|
|
52859
|
+
|
|
52860
|
+
function waypointsToPath(wp, cr) {
|
|
52861
|
+
if (wp.length < 2) return '';
|
|
52862
|
+
if (wp.length === 2) return 'M ' + wp[0][0] + ',' + wp[0][1] + ' L ' + wp[1][0] + ',' + wp[1][1];
|
|
52863
|
+
var radii = [];
|
|
52864
|
+
for (var i = 0; i < wp.length; i++) radii[i] = 0;
|
|
52865
|
+
for (var i = 1; i < wp.length - 1; i++) {
|
|
52866
|
+
var p = wp[i-1], c = wp[i], n = wp[i+1];
|
|
52867
|
+
var lp = Math.sqrt((p[0]-c[0])*(p[0]-c[0]) + (p[1]-c[1])*(p[1]-c[1]));
|
|
52868
|
+
var ln = Math.sqrt((n[0]-c[0])*(n[0]-c[0]) + (n[1]-c[1])*(n[1]-c[1]));
|
|
52869
|
+
radii[i] = (lp < 0.01 || ln < 0.01) ? 0 : Math.min(cr, lp / 2, ln / 2);
|
|
52870
|
+
}
|
|
52871
|
+
for (var i = 1; i < wp.length - 2; i++) {
|
|
52872
|
+
var c = wp[i], n = wp[i+1];
|
|
52873
|
+
var sl = Math.sqrt((n[0]-c[0])*(n[0]-c[0]) + (n[1]-c[1])*(n[1]-c[1]));
|
|
52874
|
+
var tot = radii[i] + radii[i+1];
|
|
52875
|
+
if (tot > sl && tot > 0) { var sc = sl / tot; radii[i] *= sc; radii[i+1] *= sc; }
|
|
52876
|
+
}
|
|
52877
|
+
var path = 'M ' + wp[0][0] + ',' + wp[0][1];
|
|
52878
|
+
for (var i = 1; i < wp.length - 1; i++) {
|
|
52879
|
+
var p = wp[i-1], c = wp[i], n = wp[i+1], r = radii[i];
|
|
52880
|
+
if (r < 2) { path += ' L ' + c[0] + ',' + c[1]; continue; }
|
|
52881
|
+
var dpx = p[0]-c[0], dpy = p[1]-c[1], dnx = n[0]-c[0], dny = n[1]-c[1];
|
|
52882
|
+
var lp = Math.sqrt(dpx*dpx + dpy*dpy), ln = Math.sqrt(dnx*dnx + dny*dny);
|
|
52883
|
+
var upx = dpx/lp, upy = dpy/lp, unx = dnx/ln, uny = dny/ln;
|
|
52884
|
+
var asx = c[0] + upx*r, asy = c[1] + upy*r, aex = c[0] + unx*r, aey = c[1] + uny*r;
|
|
52885
|
+
var cross = dpx*dny - dpy*dnx, sweep = cross > 0 ? 0 : 1;
|
|
52886
|
+
path += ' L ' + asx + ',' + asy + ' A ' + r + ' ' + r + ' 0 0 ' + sweep + ' ' + aex + ',' + aey;
|
|
52887
|
+
}
|
|
52888
|
+
path += ' L ' + wp[wp.length-1][0] + ',' + wp[wp.length-1][1];
|
|
52889
|
+
return path;
|
|
52890
|
+
}
|
|
52891
|
+
|
|
52892
|
+
function computeWaypoints(from, to, nboxes, srcId, tgtId, pad, exitStub, entryStub, alloc) {
|
|
52893
|
+
var isSelf = srcId === tgtId;
|
|
52894
|
+
var iboxes = [];
|
|
52895
|
+
for (var i = 0; i < nboxes.length; i++) {
|
|
52896
|
+
var b = nboxes[i];
|
|
52897
|
+
if (isSelf || (b.id !== srcId && b.id !== tgtId)) iboxes.push(inflateBox(b, pad));
|
|
52898
|
+
}
|
|
52899
|
+
var se = [from[0] + exitStub, from[1]], sn = [to[0] - entryStub, to[1]];
|
|
52900
|
+
var xn = Math.min(se[0], sn[0]), xx = Math.max(se[0], sn[0]);
|
|
52901
|
+
|
|
52902
|
+
if (!isSelf && to[0] > from[0]) {
|
|
52903
|
+
var cy = (from[1] + to[1]) / 2;
|
|
52904
|
+
var intBoxes = [];
|
|
52905
|
+
for (var i = 0; i < iboxes.length; i++) { var b = iboxes[i]; if (b.l < xx && b.r > xn) intBoxes.push(b); }
|
|
52906
|
+
if (intBoxes.length >= 2) {
|
|
52907
|
+
var ct = Infinity, cb = -Infinity;
|
|
52908
|
+
for (var i = 0; i < intBoxes.length; i++) { ct = Math.min(ct, intBoxes[i].t); cb = Math.max(cb, intBoxes[i].b); }
|
|
52909
|
+
if (cy > ct && cy < cb) { cy = (cy - ct <= cb - cy) ? ct - pad : cb + pad; }
|
|
52910
|
+
}
|
|
52911
|
+
var clearY = findClearY(xn, xx, cy, iboxes);
|
|
52912
|
+
if (Math.abs(from[1] - to[1]) < JOG_THRESHOLD && Math.abs(clearY - from[1]) < JOG_THRESHOLD) return null;
|
|
52913
|
+
|
|
52914
|
+
var midX = (se[0] + sn[0]) / 2, ymn = Math.min(from[1], to[1]), ymx = Math.max(from[1], to[1]);
|
|
52915
|
+
var cmx = findClearX(ymn, ymx, midX, iboxes);
|
|
52916
|
+
var fmx = alloc.findFreeX(ymn, ymx, cmx, iboxes);
|
|
52917
|
+
if (ymx - ymn >= JOG_THRESHOLD && fmx > se[0] && fmx < sn[0] &&
|
|
52918
|
+
vSegClear(fmx, ymn, ymx, iboxes) &&
|
|
52919
|
+
alloc.findFreeY(from[0], fmx, from[1], iboxes) === from[1] &&
|
|
52920
|
+
alloc.findFreeY(fmx, to[0], to[1], iboxes) === to[1]) {
|
|
52921
|
+
alloc.claim(from[0], fmx, from[1]); alloc.claim(fmx, to[0], to[1]); alloc.claimV(ymn, ymx, fmx);
|
|
52922
|
+
return simplifyWaypoints([from, [fmx, from[1]], [fmx, to[1]], to]);
|
|
52923
|
+
}
|
|
52924
|
+
|
|
52925
|
+
clearY = alloc.findFreeY(xn, xx, clearY, iboxes);
|
|
52926
|
+
if (Math.abs(clearY - from[1]) < JOG_THRESHOLD && !iboxes.some(function(b) { return segOvlp(xn, xx, from[1], b); })) clearY = from[1];
|
|
52927
|
+
else if (Math.abs(clearY - to[1]) < JOG_THRESHOLD && !iboxes.some(function(b) { return segOvlp(xn, xx, to[1], b); })) clearY = to[1];
|
|
52928
|
+
alloc.claim(xn, xx, clearY);
|
|
52929
|
+
|
|
52930
|
+
var eymn = Math.min(from[1], clearY), eymx = Math.max(from[1], clearY);
|
|
52931
|
+
var exX = findClearX(eymn, eymx, se[0], iboxes); exX = alloc.findFreeX(eymn, eymx, exX, iboxes);
|
|
52932
|
+
if (exX < from[0]) { exX = se[0]; if (!vSegClear(exX, eymn, eymx, iboxes)) { exX = findClearX(eymn, eymx, se[0] + TRACK_SPACING, iboxes); exX = alloc.findFreeX(eymn, eymx, exX, iboxes); } }
|
|
52933
|
+
alloc.claimV(eymn, eymx, exX);
|
|
52934
|
+
|
|
52935
|
+
var nymn = Math.min(to[1], clearY), nymx = Math.max(to[1], clearY);
|
|
52936
|
+
var nxX = findClearX(nymn, nymx, sn[0], iboxes); nxX = alloc.findFreeX(nymn, nymx, nxX, iboxes);
|
|
52937
|
+
if (nxX > to[0]) { nxX = sn[0]; if (!vSegClear(nxX, nymn, nymx, iboxes)) { nxX = findClearX(nymn, nymx, sn[0] - TRACK_SPACING, iboxes); nxX = alloc.findFreeX(nymn, nymx, nxX, iboxes); } }
|
|
52938
|
+
alloc.claimV(nymn, nymx, nxX);
|
|
52939
|
+
|
|
52940
|
+
return simplifyWaypoints([from, [exX, from[1]], [exX, clearY], [nxX, clearY], [nxX, to[1]], to]);
|
|
52941
|
+
} else {
|
|
52942
|
+
var srcBox = null, tgtBox = null;
|
|
52943
|
+
for (var i = 0; i < nboxes.length; i++) { if (nboxes[i].id === srcId) srcBox = nboxes[i]; if (nboxes[i].id === tgtId) tgtBox = nboxes[i]; }
|
|
52944
|
+
var corBoxes = []; for (var i = 0; i < iboxes.length; i++) { var b = iboxes[i]; if (b.l < xx && b.r > xn) corBoxes.push(b); }
|
|
52945
|
+
var bots = corBoxes.map(function(b) { return b.b; }), tops = corBoxes.map(function(b) { return b.t; });
|
|
52946
|
+
if (srcBox) { bots.push(srcBox.y + srcBox.h + pad); tops.push(srcBox.y - pad); }
|
|
52947
|
+
if (tgtBox) { bots.push(tgtBox.y + tgtBox.h + pad); tops.push(tgtBox.y - pad); }
|
|
52948
|
+
var maxBot = Math.max.apply(null, bots.concat([from[1] + 50, to[1] + 50]));
|
|
52949
|
+
var minTop = Math.min.apply(null, tops.concat([from[1] - 50, to[1] - 50]));
|
|
52950
|
+
var avgY = (from[1] + to[1]) / 2;
|
|
52951
|
+
var escBelow = maxBot + pad, escAbove = minTop - pad;
|
|
52952
|
+
var escY = Math.abs(escAbove - avgY) <= Math.abs(escBelow - avgY) ? escAbove : escBelow;
|
|
52953
|
+
escY = findClearY(xn, xx, escY, iboxes); escY = alloc.findFreeY(xn, xx, escY, iboxes); alloc.claim(xn, xx, escY);
|
|
52954
|
+
|
|
52955
|
+
var bymn = Math.min(from[1], escY), bymx = Math.max(from[1], escY);
|
|
52956
|
+
var bexX = findClearX(bymn, bymx, se[0], iboxes); bexX = alloc.findFreeX(bymn, bymx, bexX, iboxes); alloc.claimV(bymn, bymx, bexX);
|
|
52957
|
+
|
|
52958
|
+
var bnmn = Math.min(to[1], escY), bnmx = Math.max(to[1], escY);
|
|
52959
|
+
var bnxX = findClearX(bnmn, bnmx, sn[0], iboxes); bnxX = alloc.findFreeX(bnmn, bnmx, bnxX, iboxes); alloc.claimV(bnmn, bnmx, bnxX);
|
|
52960
|
+
|
|
52961
|
+
return simplifyWaypoints([from, [bexX, from[1]], [bexX, escY], [bnxX, escY], [bnxX, to[1]], to]);
|
|
52962
|
+
}
|
|
52963
|
+
}
|
|
52964
|
+
|
|
52965
|
+
function calcOrthogonalPath(from, to, nboxes, srcId, tgtId, fromIdx, toIdx, alloc) {
|
|
52966
|
+
var pad = 15, stubLen = 20, stubSpc = 12, maxStub = 80, cr = 10;
|
|
52967
|
+
var exitStub = Math.min(stubLen + fromIdx * stubSpc, maxStub);
|
|
52968
|
+
var entryStub = Math.min(stubLen + toIdx * stubSpc, maxStub);
|
|
52969
|
+
try {
|
|
52970
|
+
var wp = computeWaypoints(from, to, nboxes, srcId, tgtId, pad, exitStub, entryStub, alloc);
|
|
52971
|
+
if (!wp) return null;
|
|
52972
|
+
var p = waypointsToPath(wp, cr);
|
|
52973
|
+
return (p && p.length >= 5) ? p : null;
|
|
52974
|
+
} catch (e) { return null; }
|
|
52975
|
+
}
|
|
52976
|
+
|
|
52977
|
+
// ---- Port position + node box + connection path indexes ----
|
|
52614
52978
|
var portPositions = {};
|
|
52615
52979
|
content.querySelectorAll('[data-port-id]').forEach(function(el) {
|
|
52616
52980
|
var id = el.getAttribute('data-port-id');
|
|
52617
52981
|
portPositions[id] = { cx: parseFloat(el.getAttribute('cx')), cy: parseFloat(el.getAttribute('cy')) };
|
|
52618
52982
|
});
|
|
52619
52983
|
|
|
52984
|
+
// Extract node bounding boxes from SVG rect elements
|
|
52985
|
+
var nodeBoxMap = {};
|
|
52986
|
+
content.querySelectorAll('.nodes [data-node-id]').forEach(function(g) {
|
|
52987
|
+
var nid = g.getAttribute('data-node-id');
|
|
52988
|
+
var rect = g.querySelector(':scope > rect');
|
|
52989
|
+
if (!rect) return;
|
|
52990
|
+
nodeBoxMap[nid] = {
|
|
52991
|
+
id: nid,
|
|
52992
|
+
x: parseFloat(rect.getAttribute('x')),
|
|
52993
|
+
y: parseFloat(rect.getAttribute('y')),
|
|
52994
|
+
w: parseFloat(rect.getAttribute('width')),
|
|
52995
|
+
h: parseFloat(rect.getAttribute('height'))
|
|
52996
|
+
};
|
|
52997
|
+
});
|
|
52998
|
+
|
|
52999
|
+
// Build port-to-node mapping and compute port indices within each node
|
|
53000
|
+
var portNodeMap = {};
|
|
53001
|
+
var portIndexMap = {};
|
|
53002
|
+
content.querySelectorAll('[data-port-id]').forEach(function(el) {
|
|
53003
|
+
var id = el.getAttribute('data-port-id');
|
|
53004
|
+
var dir = el.getAttribute('data-direction');
|
|
53005
|
+
var parts = id.split('.');
|
|
53006
|
+
var nodeId = parts[0];
|
|
53007
|
+
portNodeMap[id] = nodeId;
|
|
53008
|
+
if (!portIndexMap[nodeId]) portIndexMap[nodeId] = { input: [], output: [] };
|
|
53009
|
+
portIndexMap[nodeId][dir].push(id);
|
|
53010
|
+
});
|
|
53011
|
+
|
|
52620
53012
|
var nodeOffsets = {};
|
|
52621
53013
|
var connIndex = [];
|
|
52622
53014
|
content.querySelectorAll('path[data-source]').forEach(function(p) {
|
|
52623
53015
|
var src = p.getAttribute('data-source'), tgt = p.getAttribute('data-target');
|
|
52624
|
-
|
|
53016
|
+
var srcNode = src.split('.')[0], tgtNode = tgt.split('.')[0];
|
|
53017
|
+
var srcIdx = portIndexMap[srcNode] ? portIndexMap[srcNode].output.indexOf(src) : 0;
|
|
53018
|
+
var tgtIdx = portIndexMap[tgtNode] ? portIndexMap[tgtNode].input.indexOf(tgt) : 0;
|
|
53019
|
+
connIndex.push({ el: p, src: src, tgt: tgt, srcNode: srcNode, tgtNode: tgtNode,
|
|
53020
|
+
scopeOf: p.getAttribute('data-scope') || null, srcIdx: Math.max(0, srcIdx), tgtIdx: Math.max(0, tgtIdx) });
|
|
52625
53021
|
});
|
|
52626
53022
|
|
|
52627
53023
|
// Snapshot of original port positions for reset
|
|
@@ -52629,6 +53025,47 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
52629
53025
|
for (var pid in portPositions) {
|
|
52630
53026
|
origPortPositions[pid] = { cx: portPositions[pid].cx, cy: portPositions[pid].cy };
|
|
52631
53027
|
}
|
|
53028
|
+
var origNodeBoxMap = {};
|
|
53029
|
+
for (var nid in nodeBoxMap) {
|
|
53030
|
+
var b = nodeBoxMap[nid];
|
|
53031
|
+
origNodeBoxMap[nid] = { id: b.id, x: b.x, y: b.y, w: b.w, h: b.h };
|
|
53032
|
+
}
|
|
53033
|
+
|
|
53034
|
+
// ---- Recalculate all connection paths using orthogonal + bezier routing ----
|
|
53035
|
+
function recalcAllPaths() {
|
|
53036
|
+
var boxes = [];
|
|
53037
|
+
for (var nid in nodeBoxMap) boxes.push(nodeBoxMap[nid]);
|
|
53038
|
+
var sorted = connIndex.slice().sort(function(a, b) {
|
|
53039
|
+
var spa = portPositions[a.src] && portPositions[a.tgt] ? Math.abs(portPositions[a.tgt].cx - portPositions[a.src].cx) : 0;
|
|
53040
|
+
var spb = portPositions[b.src] && portPositions[b.tgt] ? Math.abs(portPositions[b.tgt].cx - portPositions[b.src].cx) : 0;
|
|
53041
|
+
if (Math.abs(spa - spb) > 1) return spa - spb;
|
|
53042
|
+
var sxa = portPositions[a.src] ? portPositions[a.src].cx : 0, sxb = portPositions[b.src] ? portPositions[b.src].cx : 0;
|
|
53043
|
+
if (Math.abs(sxa - sxb) > 1) return sxa - sxb;
|
|
53044
|
+
var sya = portPositions[a.src] ? portPositions[a.src].cy : 0, syb = portPositions[b.src] ? portPositions[b.src].cy : 0;
|
|
53045
|
+
return sya - syb;
|
|
53046
|
+
});
|
|
53047
|
+
var alloc = createAllocator();
|
|
53048
|
+
for (var i = 0; i < sorted.length; i++) {
|
|
53049
|
+
var c = sorted[i];
|
|
53050
|
+
var sp = portPositions[c.src], tp = portPositions[c.tgt];
|
|
53051
|
+
if (!sp || !tp) continue;
|
|
53052
|
+
var sx = sp.cx, sy = sp.cy, tx = tp.cx, ty = tp.cy;
|
|
53053
|
+
// For scope connections, use parent-local coords
|
|
53054
|
+
if (c.scopeOf) {
|
|
53055
|
+
var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
|
|
53056
|
+
sx -= pOff.dx; sy -= pOff.dy; tx -= pOff.dx; ty -= pOff.dy;
|
|
53057
|
+
}
|
|
53058
|
+
var ddx = tx - sx, ddy = ty - sy, dist = Math.sqrt(ddx * ddx + ddy * ddy);
|
|
53059
|
+
var path;
|
|
53060
|
+
if (dist > ORTHO_THRESHOLD) {
|
|
53061
|
+
path = calcOrthogonalPath([sx, sy], [tx, ty], boxes, c.srcNode, c.tgtNode, c.srcIdx, c.tgtIdx, alloc);
|
|
53062
|
+
if (!path) path = computeConnectionPath(sx, sy, tx, ty);
|
|
53063
|
+
} else {
|
|
53064
|
+
path = computeConnectionPath(sx, sy, tx, ty);
|
|
53065
|
+
}
|
|
53066
|
+
c.el.setAttribute('d', path);
|
|
53067
|
+
}
|
|
53068
|
+
}
|
|
52632
53069
|
|
|
52633
53070
|
function resetLayout() {
|
|
52634
53071
|
for (var nid in nodeOffsets) {
|
|
@@ -52648,10 +53085,11 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
52648
53085
|
for (var pid in origPortPositions) {
|
|
52649
53086
|
portPositions[pid] = { cx: origPortPositions[pid].cx, cy: origPortPositions[pid].cy };
|
|
52650
53087
|
}
|
|
52651
|
-
|
|
52652
|
-
var
|
|
52653
|
-
|
|
52654
|
-
}
|
|
53088
|
+
for (var nid in origNodeBoxMap) {
|
|
53089
|
+
var b = origNodeBoxMap[nid];
|
|
53090
|
+
nodeBoxMap[nid] = { id: b.id, x: b.x, y: b.y, w: b.w, h: b.h };
|
|
53091
|
+
}
|
|
53092
|
+
recalcAllPaths();
|
|
52655
53093
|
fitToView();
|
|
52656
53094
|
}
|
|
52657
53095
|
document.getElementById('btn-reset').addEventListener('click', resetLayout);
|
|
@@ -52721,39 +53159,24 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
52721
53159
|
});
|
|
52722
53160
|
}
|
|
52723
53161
|
|
|
52724
|
-
//
|
|
52725
|
-
|
|
52726
|
-
|
|
52727
|
-
if (
|
|
52728
|
-
|
|
52729
|
-
|
|
52730
|
-
|
|
52731
|
-
|
|
52732
|
-
|
|
52733
|
-
|
|
52734
|
-
|
|
52735
|
-
|
|
52736
|
-
}
|
|
53162
|
+
// Update node box positions for orthogonal routing
|
|
53163
|
+
if (nodeBoxMap[nodeId]) {
|
|
53164
|
+
var nb = origNodeBoxMap[nodeId];
|
|
53165
|
+
if (nb) nodeBoxMap[nodeId] = { id: nb.id, x: nb.x + off.dx, y: nb.y + off.dy, w: nb.w, h: nb.h };
|
|
53166
|
+
}
|
|
53167
|
+
if (nodeG) {
|
|
53168
|
+
var children = nodeG.querySelectorAll(':scope > g[data-node-id]');
|
|
53169
|
+
children.forEach(function(childG) {
|
|
53170
|
+
var childId = childG.getAttribute('data-node-id');
|
|
53171
|
+
var cnb = origNodeBoxMap[childId];
|
|
53172
|
+
if (cnb && nodeOffsets[childId]) {
|
|
53173
|
+
nodeBoxMap[childId] = { id: cnb.id, x: cnb.x + nodeOffsets[childId].dx, y: cnb.y + nodeOffsets[childId].dy, w: cnb.w, h: cnb.h };
|
|
52737
53174
|
}
|
|
52738
|
-
}
|
|
52739
|
-
|
|
52740
|
-
|
|
52741
|
-
|
|
52742
|
-
|
|
52743
|
-
if (c.srcNode === childId || c.tgtNode === childId) {
|
|
52744
|
-
var sp = portPositions[c.src], tp = portPositions[c.tgt];
|
|
52745
|
-
if (sp && tp) {
|
|
52746
|
-
if (c.scopeOf) {
|
|
52747
|
-
var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
|
|
52748
|
-
c.el.setAttribute('d', computeConnectionPath(sp.cx - pOff.dx, sp.cy - pOff.dy, tp.cx - pOff.dx, tp.cy - pOff.dy));
|
|
52749
|
-
} else {
|
|
52750
|
-
c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
|
|
52751
|
-
}
|
|
52752
|
-
}
|
|
52753
|
-
}
|
|
52754
|
-
});
|
|
52755
|
-
}
|
|
52756
|
-
});
|
|
53175
|
+
});
|
|
53176
|
+
}
|
|
53177
|
+
|
|
53178
|
+
// Recalculate all connection paths with orthogonal routing
|
|
53179
|
+
recalcAllPaths();
|
|
52757
53180
|
}
|
|
52758
53181
|
|
|
52759
53182
|
var allLabelIds = Object.keys(labelMap);
|
|
@@ -106002,7 +106425,7 @@ function displayInstalledPackage(pkg) {
|
|
|
106002
106425
|
// src/cli/index.ts
|
|
106003
106426
|
init_logger();
|
|
106004
106427
|
init_error_utils();
|
|
106005
|
-
var version2 = true ? "0.19.
|
|
106428
|
+
var version2 = true ? "0.19.1" : "0.0.0-dev";
|
|
106006
106429
|
var program2 = new Command();
|
|
106007
106430
|
program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").option("-v, --version", "Output the current version").option("--no-color", "Disable colors").option("--color", "Force colors").on("option:version", () => {
|
|
106008
106431
|
logger.banner(version2);
|
package/dist/diagram/geometry.js
CHANGED
|
@@ -926,6 +926,7 @@ export function buildDiagramGraph(ast, options = {}) {
|
|
|
926
926
|
}
|
|
927
927
|
}
|
|
928
928
|
// Build NodeBox array for orthogonal routing (after coordinate normalization)
|
|
929
|
+
// Include scope children so the router can route around them
|
|
929
930
|
const nodeBoxes = nodes.map(node => ({
|
|
930
931
|
id: node.id,
|
|
931
932
|
x: node.x,
|
|
@@ -933,6 +934,13 @@ export function buildDiagramGraph(ast, options = {}) {
|
|
|
933
934
|
width: node.width,
|
|
934
935
|
height: node.height,
|
|
935
936
|
}));
|
|
937
|
+
for (const node of nodes) {
|
|
938
|
+
if (node.scopeChildren) {
|
|
939
|
+
for (const child of node.scopeChildren) {
|
|
940
|
+
nodeBoxes.push({ id: child.id, x: child.x, y: child.y, width: child.width, height: child.height });
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
936
944
|
// Sort connections: short spans first, then by source X, then by source Y
|
|
937
945
|
// (matching original editor for deterministic track allocation)
|
|
938
946
|
pendingConnections.sort((a, b) => {
|
|
@@ -1010,18 +1018,100 @@ export function buildDiagramGraph(ast, options = {}) {
|
|
|
1010
1018
|
path,
|
|
1011
1019
|
});
|
|
1012
1020
|
}
|
|
1013
|
-
// Recompute scope connection paths
|
|
1021
|
+
// Recompute scope connection paths with the same routing logic as external connections
|
|
1014
1022
|
for (const node of nodes) {
|
|
1015
|
-
if (node.scopeConnections
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1023
|
+
if (!node.scopeConnections || !node.scopePorts || !node.scopeChildren)
|
|
1024
|
+
continue;
|
|
1025
|
+
const childMap = new Map();
|
|
1026
|
+
for (const c of node.scopeChildren)
|
|
1027
|
+
childMap.set(c.id, c);
|
|
1028
|
+
const scopePending = [];
|
|
1029
|
+
for (const conn of node.scopeConnections) {
|
|
1030
|
+
const sPort = findScopePort(conn.fromNode, conn.fromPort, node, childMap, 'output');
|
|
1031
|
+
const tPort = findScopePort(conn.toNode, conn.toPort, node, childMap, 'input');
|
|
1032
|
+
if (!sPort || !tPort)
|
|
1033
|
+
continue;
|
|
1034
|
+
let fromPortIndex = 0;
|
|
1035
|
+
let toPortIndex = 0;
|
|
1036
|
+
if (conn.fromNode === node.id) {
|
|
1037
|
+
fromPortIndex = node.scopePorts.outputs.indexOf(sPort);
|
|
1038
|
+
}
|
|
1039
|
+
else {
|
|
1040
|
+
const fromChild = childMap.get(conn.fromNode);
|
|
1041
|
+
if (fromChild)
|
|
1042
|
+
fromPortIndex = fromChild.outputs.indexOf(sPort);
|
|
1043
|
+
}
|
|
1044
|
+
if (conn.toNode === node.id) {
|
|
1045
|
+
toPortIndex = node.scopePorts.inputs.indexOf(tPort);
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1048
|
+
const toChild = childMap.get(conn.toNode);
|
|
1049
|
+
if (toChild)
|
|
1050
|
+
toPortIndex = toChild.inputs.indexOf(tPort);
|
|
1051
|
+
}
|
|
1052
|
+
scopePending.push({
|
|
1053
|
+
conn, sourcePort: sPort, targetPort: tPort,
|
|
1054
|
+
fromNodeId: conn.fromNode, toNodeId: conn.toNode,
|
|
1055
|
+
fromPortIndex: Math.max(0, fromPortIndex),
|
|
1056
|
+
toPortIndex: Math.max(0, toPortIndex),
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
// Sort: short spans first, then by source X, then source Y
|
|
1060
|
+
scopePending.sort((a, b) => {
|
|
1061
|
+
const aSpan = Math.abs(a.targetPort.cx - a.sourcePort.cx);
|
|
1062
|
+
const bSpan = Math.abs(b.targetPort.cx - b.sourcePort.cx);
|
|
1063
|
+
if (Math.abs(aSpan - bSpan) > 1)
|
|
1064
|
+
return aSpan - bSpan;
|
|
1065
|
+
if (Math.abs(a.sourcePort.cx - b.sourcePort.cx) > 1)
|
|
1066
|
+
return a.sourcePort.cx - b.sourcePort.cx;
|
|
1067
|
+
return a.sourcePort.cy - b.sourcePort.cy;
|
|
1068
|
+
});
|
|
1069
|
+
// Fan-out/fan-in consistency: force all to curves if any in the group is short
|
|
1070
|
+
const spSrcKey = (sp) => `s:${sp.fromNodeId}.${sp.conn.fromPort}`;
|
|
1071
|
+
const spTgtKey = (sp) => `t:${sp.toNodeId}.${sp.conn.toPort}`;
|
|
1072
|
+
const spTgtNodeKey = (sp) => `n:${sp.toNodeId}`;
|
|
1073
|
+
const spGroups = new Map();
|
|
1074
|
+
for (const sp of scopePending) {
|
|
1075
|
+
for (const key of [spSrcKey(sp), spTgtKey(sp), spTgtNodeKey(sp)]) {
|
|
1076
|
+
if (!spGroups.has(key))
|
|
1077
|
+
spGroups.set(key, []);
|
|
1078
|
+
spGroups.get(key).push(sp);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
const spForceCurveKeys = new Set();
|
|
1082
|
+
for (const [key, group] of spGroups) {
|
|
1083
|
+
if (group.length < 2)
|
|
1084
|
+
continue;
|
|
1085
|
+
const anyShort = group.some(sp => {
|
|
1086
|
+
const ddx = sp.targetPort.cx - sp.sourcePort.cx;
|
|
1087
|
+
const ddy = sp.targetPort.cy - sp.sourcePort.cy;
|
|
1088
|
+
return Math.sqrt(ddx * ddx + ddy * ddy) <= ORTHOGONAL_DISTANCE_THRESHOLD;
|
|
1089
|
+
});
|
|
1090
|
+
if (anyShort)
|
|
1091
|
+
spForceCurveKeys.add(key);
|
|
1092
|
+
}
|
|
1093
|
+
const spForceCurveSet = new Set();
|
|
1094
|
+
for (const sp of scopePending) {
|
|
1095
|
+
if (spForceCurveKeys.has(spSrcKey(sp)) || spForceCurveKeys.has(spTgtKey(sp)) || spForceCurveKeys.has(spTgtNodeKey(sp))) {
|
|
1096
|
+
spForceCurveSet.add(sp);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
// Route each scope connection
|
|
1100
|
+
for (const sp of scopePending) {
|
|
1101
|
+
const sx = sp.sourcePort.cx;
|
|
1102
|
+
const sy = sp.sourcePort.cy;
|
|
1103
|
+
const tx = sp.targetPort.cx;
|
|
1104
|
+
const ty = sp.targetPort.cy;
|
|
1105
|
+
const ddx = tx - sx;
|
|
1106
|
+
const ddy = ty - sy;
|
|
1107
|
+
const dist = Math.sqrt(ddx * ddx + ddy * ddy);
|
|
1108
|
+
const useCurve = spForceCurveSet.has(sp);
|
|
1109
|
+
if (!useCurve && dist > ORTHOGONAL_DISTANCE_THRESHOLD) {
|
|
1110
|
+
const orthoPath = calculateOrthogonalPathSafe([sx, sy], [tx, ty], nodeBoxes, sp.fromNodeId, sp.toNodeId, { fromPortIndex: sp.fromPortIndex, toPortIndex: sp.toPortIndex }, allocator);
|
|
1111
|
+
sp.conn.path = orthoPath ?? computeConnectionPath(sx, sy, tx, ty);
|
|
1112
|
+
}
|
|
1113
|
+
else {
|
|
1114
|
+
sp.conn.path = computeConnectionPath(sx, sy, tx, ty);
|
|
1025
1115
|
}
|
|
1026
1116
|
}
|
|
1027
1117
|
}
|
|
@@ -553,7 +553,10 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
553
553
|
portConnections[tgt].push(src);
|
|
554
554
|
});
|
|
555
555
|
|
|
556
|
-
// ---- Connection path computation (from geometry.ts) ----
|
|
556
|
+
// ---- Connection path computation (bezier from geometry.ts + orthogonal router) ----
|
|
557
|
+
var TRACK_SPACING = 15, EDGE_OFFSET = 5, MAX_CANDIDATES = 5;
|
|
558
|
+
var MIN_SEG_LEN = 3, JOG_THRESHOLD = 10, ORTHO_THRESHOLD = 300;
|
|
559
|
+
|
|
557
560
|
function quadCurveControl(ax, ay, bx, by, ux, uy) {
|
|
558
561
|
var dn = Math.abs(ay - by);
|
|
559
562
|
return [bx + (ux * dn) / Math.abs(uy), ay];
|
|
@@ -583,18 +586,326 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
583
586
|
' L ' + hx + ',' + hy;
|
|
584
587
|
}
|
|
585
588
|
|
|
586
|
-
// ----
|
|
589
|
+
// ---- Orthogonal router (ported from orthogonal-router.ts) ----
|
|
590
|
+
function createAllocator() {
|
|
591
|
+
var claims = [], vclaims = [];
|
|
592
|
+
function isOcc(xn, xx, y) {
|
|
593
|
+
for (var i = 0; i < claims.length; i++) {
|
|
594
|
+
var c = claims[i];
|
|
595
|
+
if (c.xn < xx && c.xx > xn && Math.abs(c.y - y) < TRACK_SPACING) return true;
|
|
596
|
+
}
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
function isOccV(yn, yx, x) {
|
|
600
|
+
for (var i = 0; i < vclaims.length; i++) {
|
|
601
|
+
var c = vclaims[i];
|
|
602
|
+
if (c.yn < yx && c.yx > yn && Math.abs(c.x - x) < TRACK_SPACING) return true;
|
|
603
|
+
}
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
function blockedByNode(xn, xx, y, boxes) {
|
|
607
|
+
for (var i = 0; i < boxes.length; i++) {
|
|
608
|
+
var b = boxes[i]; if (xn < b.r && xx > b.l && y >= b.t && y <= b.b) return true;
|
|
609
|
+
}
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
function blockedByNodeV(yn, yx, x, boxes) {
|
|
613
|
+
for (var i = 0; i < boxes.length; i++) {
|
|
614
|
+
var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return true;
|
|
615
|
+
}
|
|
616
|
+
return false;
|
|
617
|
+
}
|
|
618
|
+
function countHCross(xn, xx, y) {
|
|
619
|
+
var n = 0;
|
|
620
|
+
for (var i = 0; i < vclaims.length; i++) {
|
|
621
|
+
var c = vclaims[i]; if (c.x > xn && c.x < xx && y >= c.yn && y <= c.yx) n++;
|
|
622
|
+
}
|
|
623
|
+
return n;
|
|
624
|
+
}
|
|
625
|
+
function countVCross(yn, yx, x) {
|
|
626
|
+
var n = 0;
|
|
627
|
+
for (var i = 0; i < claims.length; i++) {
|
|
628
|
+
var c = claims[i]; if (c.y > yn && c.y < yx && x >= c.xn && x <= c.xx) n++;
|
|
629
|
+
}
|
|
630
|
+
return n;
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
findFreeY: function(xn, xx, cy, nb) {
|
|
634
|
+
var free = function(y) { return !isOcc(xn, xx, y) && (!nb || !blockedByNode(xn, xx, y, nb)); };
|
|
635
|
+
if (free(cy)) return cy;
|
|
636
|
+
var cands = [];
|
|
637
|
+
for (var off = TRACK_SPACING; off < 800 && cands.length < MAX_CANDIDATES * 2; off += TRACK_SPACING) {
|
|
638
|
+
if (free(cy - off)) cands.push({ y: cy - off, d: off });
|
|
639
|
+
if (free(cy + off)) cands.push({ y: cy + off, d: off });
|
|
640
|
+
}
|
|
641
|
+
if (!cands.length) return cy;
|
|
642
|
+
var best = cands[0].y, bc = countHCross(xn, xx, cands[0].y), bd = cands[0].d;
|
|
643
|
+
for (var i = 1; i < cands.length; i++) {
|
|
644
|
+
var cr = countHCross(xn, xx, cands[i].y);
|
|
645
|
+
if (cr < bc || (cr === bc && cands[i].d < bd)) { best = cands[i].y; bc = cr; bd = cands[i].d; }
|
|
646
|
+
}
|
|
647
|
+
return best;
|
|
648
|
+
},
|
|
649
|
+
findFreeX: function(yn, yx, cx, nb) {
|
|
650
|
+
var free = function(x) { return !isOccV(yn, yx, x) && (!nb || !blockedByNodeV(yn, yx, x, nb)); };
|
|
651
|
+
if (free(cx)) return cx;
|
|
652
|
+
var cands = [];
|
|
653
|
+
for (var off = TRACK_SPACING; off < 800 && cands.length < MAX_CANDIDATES * 2; off += TRACK_SPACING) {
|
|
654
|
+
if (free(cx - off)) cands.push({ x: cx - off, d: off });
|
|
655
|
+
if (free(cx + off)) cands.push({ x: cx + off, d: off });
|
|
656
|
+
}
|
|
657
|
+
if (!cands.length) return cx;
|
|
658
|
+
var best = cands[0].x, bc = countVCross(yn, yx, cands[0].x), bd = cands[0].d;
|
|
659
|
+
for (var i = 1; i < cands.length; i++) {
|
|
660
|
+
var cr = countVCross(yn, yx, cands[i].x);
|
|
661
|
+
if (cr < bc || (cr === bc && cands[i].d < bd)) { best = cands[i].x; bc = cr; bd = cands[i].d; }
|
|
662
|
+
}
|
|
663
|
+
return best;
|
|
664
|
+
},
|
|
665
|
+
claim: function(xn, xx, y) { claims.push({ xn: xn, xx: xx, y: y }); },
|
|
666
|
+
claimV: function(yn, yx, x) { vclaims.push({ yn: yn, yx: yx, x: x }); }
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function inflateBox(b, pad) { return { l: b.x - pad, r: b.x + b.w + pad, t: b.y - pad, b: b.y + b.h + pad }; }
|
|
671
|
+
function segOvlp(xn, xx, y, bx) { return xn < bx.r && xx > bx.l && y >= bx.t && y <= bx.b; }
|
|
672
|
+
function vSegClear(x, yn, yx, boxes) {
|
|
673
|
+
for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return false; }
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function findClearY(xn, xx, cy, boxes) {
|
|
678
|
+
var blocked = function(y) { for (var i = 0; i < boxes.length; i++) if (segOvlp(xn, xx, y, boxes[i])) return true; return false; };
|
|
679
|
+
if (!blocked(cy)) return cy;
|
|
680
|
+
var edges = [];
|
|
681
|
+
for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (xn < b.r && xx > b.l) { edges.push(b.t); edges.push(b.b); } }
|
|
682
|
+
if (!edges.length) return cy;
|
|
683
|
+
edges.sort(function(a, b) { return a - b; });
|
|
684
|
+
var best = cy, bd = Infinity;
|
|
685
|
+
for (var i = 0; i < edges.length; i++) {
|
|
686
|
+
var vals = [edges[i] - EDGE_OFFSET, edges[i] + EDGE_OFFSET];
|
|
687
|
+
for (var j = 0; j < 2; j++) { if (!blocked(vals[j])) { var d = Math.abs(vals[j] - cy); if (d < bd) { bd = d; best = vals[j]; } } }
|
|
688
|
+
}
|
|
689
|
+
if (bd === Infinity) {
|
|
690
|
+
var mn = Math.min.apply(null, edges) - EDGE_OFFSET * 2, mx = Math.max.apply(null, edges) + EDGE_OFFSET * 2;
|
|
691
|
+
best = Math.abs(mn - cy) <= Math.abs(mx - cy) ? mn : mx;
|
|
692
|
+
if (blocked(best)) { for (var off = TRACK_SPACING; off < 800; off += TRACK_SPACING) { if (!blocked(best - off)) { best -= off; break; } if (!blocked(best + off)) { best += off; break; } } }
|
|
693
|
+
}
|
|
694
|
+
return best;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function findClearX(yn, yx, cx, boxes) {
|
|
698
|
+
var blocked = function(x) { for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (x >= b.l && x <= b.r && yn < b.b && yx > b.t) return true; } return false; };
|
|
699
|
+
if (!blocked(cx)) return cx;
|
|
700
|
+
var edges = [];
|
|
701
|
+
for (var i = 0; i < boxes.length; i++) { var b = boxes[i]; if (yn < b.b && yx > b.t) { edges.push(b.l); edges.push(b.r); } }
|
|
702
|
+
if (!edges.length) return cx;
|
|
703
|
+
edges.sort(function(a, b) { return a - b; });
|
|
704
|
+
var best = cx, bd = Infinity;
|
|
705
|
+
for (var i = 0; i < edges.length; i++) {
|
|
706
|
+
var vals = [edges[i] - EDGE_OFFSET, edges[i] + EDGE_OFFSET];
|
|
707
|
+
for (var j = 0; j < 2; j++) { if (!blocked(vals[j])) { var d = Math.abs(vals[j] - cx); if (d < bd) { bd = d; best = vals[j]; } } }
|
|
708
|
+
}
|
|
709
|
+
if (bd === Infinity) {
|
|
710
|
+
var mn = Math.min.apply(null, edges) - EDGE_OFFSET * 2, mx = Math.max.apply(null, edges) + EDGE_OFFSET * 2;
|
|
711
|
+
best = Math.abs(mn - cx) <= Math.abs(mx - cx) ? mn : mx;
|
|
712
|
+
if (blocked(best)) { for (var off = TRACK_SPACING; off < 800; off += TRACK_SPACING) { if (!blocked(best - off)) { best -= off; break; } if (!blocked(best + off)) { best += off; break; } } }
|
|
713
|
+
}
|
|
714
|
+
return best;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function simplifyWaypoints(pts) {
|
|
718
|
+
if (pts.length <= 2) return pts;
|
|
719
|
+
var jogFound = true;
|
|
720
|
+
while (jogFound) {
|
|
721
|
+
jogFound = false;
|
|
722
|
+
for (var i = 0; i < pts.length - 3; i++) {
|
|
723
|
+
var a = pts[i], b = pts[i+1], c = pts[i+2], d = pts[i+3];
|
|
724
|
+
var jogH = Math.abs(b[1] - c[1]);
|
|
725
|
+
if (Math.abs(a[1] - b[1]) < 0.5 && Math.abs(b[0] - c[0]) < 0.5 && Math.abs(c[1] - d[1]) < 0.5 && jogH > 0.5 && jogH < JOG_THRESHOLD) {
|
|
726
|
+
var mid = (b[1] + c[1]) / 2, snap = Math.abs(a[1] - mid) <= Math.abs(d[1] - mid) ? a[1] : d[1];
|
|
727
|
+
pts = pts.slice(); pts[i+1] = [b[0], snap]; pts[i+2] = [c[0], snap]; jogFound = true; break;
|
|
728
|
+
}
|
|
729
|
+
var jogW = Math.abs(b[0] - c[0]);
|
|
730
|
+
if (Math.abs(a[0] - b[0]) < 0.5 && Math.abs(b[1] - c[1]) < 0.5 && Math.abs(c[0] - d[0]) < 0.5 && jogW > 0.5 && jogW < JOG_THRESHOLD) {
|
|
731
|
+
var mid = (b[0] + c[0]) / 2, snap = Math.abs(a[0] - mid) <= Math.abs(d[0] - mid) ? a[0] : d[0];
|
|
732
|
+
pts = pts.slice(); pts[i+1] = [snap, b[1]]; pts[i+2] = [snap, c[1]]; jogFound = true; break;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
var res = [pts[0]];
|
|
737
|
+
for (var i = 1; i < pts.length - 1; i++) {
|
|
738
|
+
var prev = res[res.length - 1], cur = pts[i], next = pts[i+1];
|
|
739
|
+
if (Math.abs(prev[0] - cur[0]) + Math.abs(prev[1] - cur[1]) < MIN_SEG_LEN) continue;
|
|
740
|
+
var sameX = Math.abs(prev[0] - cur[0]) < 0.01 && Math.abs(cur[0] - next[0]) < 0.01;
|
|
741
|
+
var sameY = Math.abs(prev[1] - cur[1]) < 0.01 && Math.abs(cur[1] - next[1]) < 0.01;
|
|
742
|
+
if (!sameX && !sameY) res.push(cur);
|
|
743
|
+
}
|
|
744
|
+
res.push(pts[pts.length - 1]);
|
|
745
|
+
return res;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function waypointsToPath(wp, cr) {
|
|
749
|
+
if (wp.length < 2) return '';
|
|
750
|
+
if (wp.length === 2) return 'M ' + wp[0][0] + ',' + wp[0][1] + ' L ' + wp[1][0] + ',' + wp[1][1];
|
|
751
|
+
var radii = [];
|
|
752
|
+
for (var i = 0; i < wp.length; i++) radii[i] = 0;
|
|
753
|
+
for (var i = 1; i < wp.length - 1; i++) {
|
|
754
|
+
var p = wp[i-1], c = wp[i], n = wp[i+1];
|
|
755
|
+
var lp = Math.sqrt((p[0]-c[0])*(p[0]-c[0]) + (p[1]-c[1])*(p[1]-c[1]));
|
|
756
|
+
var ln = Math.sqrt((n[0]-c[0])*(n[0]-c[0]) + (n[1]-c[1])*(n[1]-c[1]));
|
|
757
|
+
radii[i] = (lp < 0.01 || ln < 0.01) ? 0 : Math.min(cr, lp / 2, ln / 2);
|
|
758
|
+
}
|
|
759
|
+
for (var i = 1; i < wp.length - 2; i++) {
|
|
760
|
+
var c = wp[i], n = wp[i+1];
|
|
761
|
+
var sl = Math.sqrt((n[0]-c[0])*(n[0]-c[0]) + (n[1]-c[1])*(n[1]-c[1]));
|
|
762
|
+
var tot = radii[i] + radii[i+1];
|
|
763
|
+
if (tot > sl && tot > 0) { var sc = sl / tot; radii[i] *= sc; radii[i+1] *= sc; }
|
|
764
|
+
}
|
|
765
|
+
var path = 'M ' + wp[0][0] + ',' + wp[0][1];
|
|
766
|
+
for (var i = 1; i < wp.length - 1; i++) {
|
|
767
|
+
var p = wp[i-1], c = wp[i], n = wp[i+1], r = radii[i];
|
|
768
|
+
if (r < 2) { path += ' L ' + c[0] + ',' + c[1]; continue; }
|
|
769
|
+
var dpx = p[0]-c[0], dpy = p[1]-c[1], dnx = n[0]-c[0], dny = n[1]-c[1];
|
|
770
|
+
var lp = Math.sqrt(dpx*dpx + dpy*dpy), ln = Math.sqrt(dnx*dnx + dny*dny);
|
|
771
|
+
var upx = dpx/lp, upy = dpy/lp, unx = dnx/ln, uny = dny/ln;
|
|
772
|
+
var asx = c[0] + upx*r, asy = c[1] + upy*r, aex = c[0] + unx*r, aey = c[1] + uny*r;
|
|
773
|
+
var cross = dpx*dny - dpy*dnx, sweep = cross > 0 ? 0 : 1;
|
|
774
|
+
path += ' L ' + asx + ',' + asy + ' A ' + r + ' ' + r + ' 0 0 ' + sweep + ' ' + aex + ',' + aey;
|
|
775
|
+
}
|
|
776
|
+
path += ' L ' + wp[wp.length-1][0] + ',' + wp[wp.length-1][1];
|
|
777
|
+
return path;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function computeWaypoints(from, to, nboxes, srcId, tgtId, pad, exitStub, entryStub, alloc) {
|
|
781
|
+
var isSelf = srcId === tgtId;
|
|
782
|
+
var iboxes = [];
|
|
783
|
+
for (var i = 0; i < nboxes.length; i++) {
|
|
784
|
+
var b = nboxes[i];
|
|
785
|
+
if (isSelf || (b.id !== srcId && b.id !== tgtId)) iboxes.push(inflateBox(b, pad));
|
|
786
|
+
}
|
|
787
|
+
var se = [from[0] + exitStub, from[1]], sn = [to[0] - entryStub, to[1]];
|
|
788
|
+
var xn = Math.min(se[0], sn[0]), xx = Math.max(se[0], sn[0]);
|
|
789
|
+
|
|
790
|
+
if (!isSelf && to[0] > from[0]) {
|
|
791
|
+
var cy = (from[1] + to[1]) / 2;
|
|
792
|
+
var intBoxes = [];
|
|
793
|
+
for (var i = 0; i < iboxes.length; i++) { var b = iboxes[i]; if (b.l < xx && b.r > xn) intBoxes.push(b); }
|
|
794
|
+
if (intBoxes.length >= 2) {
|
|
795
|
+
var ct = Infinity, cb = -Infinity;
|
|
796
|
+
for (var i = 0; i < intBoxes.length; i++) { ct = Math.min(ct, intBoxes[i].t); cb = Math.max(cb, intBoxes[i].b); }
|
|
797
|
+
if (cy > ct && cy < cb) { cy = (cy - ct <= cb - cy) ? ct - pad : cb + pad; }
|
|
798
|
+
}
|
|
799
|
+
var clearY = findClearY(xn, xx, cy, iboxes);
|
|
800
|
+
if (Math.abs(from[1] - to[1]) < JOG_THRESHOLD && Math.abs(clearY - from[1]) < JOG_THRESHOLD) return null;
|
|
801
|
+
|
|
802
|
+
var midX = (se[0] + sn[0]) / 2, ymn = Math.min(from[1], to[1]), ymx = Math.max(from[1], to[1]);
|
|
803
|
+
var cmx = findClearX(ymn, ymx, midX, iboxes);
|
|
804
|
+
var fmx = alloc.findFreeX(ymn, ymx, cmx, iboxes);
|
|
805
|
+
if (ymx - ymn >= JOG_THRESHOLD && fmx > se[0] && fmx < sn[0] &&
|
|
806
|
+
vSegClear(fmx, ymn, ymx, iboxes) &&
|
|
807
|
+
alloc.findFreeY(from[0], fmx, from[1], iboxes) === from[1] &&
|
|
808
|
+
alloc.findFreeY(fmx, to[0], to[1], iboxes) === to[1]) {
|
|
809
|
+
alloc.claim(from[0], fmx, from[1]); alloc.claim(fmx, to[0], to[1]); alloc.claimV(ymn, ymx, fmx);
|
|
810
|
+
return simplifyWaypoints([from, [fmx, from[1]], [fmx, to[1]], to]);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
clearY = alloc.findFreeY(xn, xx, clearY, iboxes);
|
|
814
|
+
if (Math.abs(clearY - from[1]) < JOG_THRESHOLD && !iboxes.some(function(b) { return segOvlp(xn, xx, from[1], b); })) clearY = from[1];
|
|
815
|
+
else if (Math.abs(clearY - to[1]) < JOG_THRESHOLD && !iboxes.some(function(b) { return segOvlp(xn, xx, to[1], b); })) clearY = to[1];
|
|
816
|
+
alloc.claim(xn, xx, clearY);
|
|
817
|
+
|
|
818
|
+
var eymn = Math.min(from[1], clearY), eymx = Math.max(from[1], clearY);
|
|
819
|
+
var exX = findClearX(eymn, eymx, se[0], iboxes); exX = alloc.findFreeX(eymn, eymx, exX, iboxes);
|
|
820
|
+
if (exX < from[0]) { exX = se[0]; if (!vSegClear(exX, eymn, eymx, iboxes)) { exX = findClearX(eymn, eymx, se[0] + TRACK_SPACING, iboxes); exX = alloc.findFreeX(eymn, eymx, exX, iboxes); } }
|
|
821
|
+
alloc.claimV(eymn, eymx, exX);
|
|
822
|
+
|
|
823
|
+
var nymn = Math.min(to[1], clearY), nymx = Math.max(to[1], clearY);
|
|
824
|
+
var nxX = findClearX(nymn, nymx, sn[0], iboxes); nxX = alloc.findFreeX(nymn, nymx, nxX, iboxes);
|
|
825
|
+
if (nxX > to[0]) { nxX = sn[0]; if (!vSegClear(nxX, nymn, nymx, iboxes)) { nxX = findClearX(nymn, nymx, sn[0] - TRACK_SPACING, iboxes); nxX = alloc.findFreeX(nymn, nymx, nxX, iboxes); } }
|
|
826
|
+
alloc.claimV(nymn, nymx, nxX);
|
|
827
|
+
|
|
828
|
+
return simplifyWaypoints([from, [exX, from[1]], [exX, clearY], [nxX, clearY], [nxX, to[1]], to]);
|
|
829
|
+
} else {
|
|
830
|
+
var srcBox = null, tgtBox = null;
|
|
831
|
+
for (var i = 0; i < nboxes.length; i++) { if (nboxes[i].id === srcId) srcBox = nboxes[i]; if (nboxes[i].id === tgtId) tgtBox = nboxes[i]; }
|
|
832
|
+
var corBoxes = []; for (var i = 0; i < iboxes.length; i++) { var b = iboxes[i]; if (b.l < xx && b.r > xn) corBoxes.push(b); }
|
|
833
|
+
var bots = corBoxes.map(function(b) { return b.b; }), tops = corBoxes.map(function(b) { return b.t; });
|
|
834
|
+
if (srcBox) { bots.push(srcBox.y + srcBox.h + pad); tops.push(srcBox.y - pad); }
|
|
835
|
+
if (tgtBox) { bots.push(tgtBox.y + tgtBox.h + pad); tops.push(tgtBox.y - pad); }
|
|
836
|
+
var maxBot = Math.max.apply(null, bots.concat([from[1] + 50, to[1] + 50]));
|
|
837
|
+
var minTop = Math.min.apply(null, tops.concat([from[1] - 50, to[1] - 50]));
|
|
838
|
+
var avgY = (from[1] + to[1]) / 2;
|
|
839
|
+
var escBelow = maxBot + pad, escAbove = minTop - pad;
|
|
840
|
+
var escY = Math.abs(escAbove - avgY) <= Math.abs(escBelow - avgY) ? escAbove : escBelow;
|
|
841
|
+
escY = findClearY(xn, xx, escY, iboxes); escY = alloc.findFreeY(xn, xx, escY, iboxes); alloc.claim(xn, xx, escY);
|
|
842
|
+
|
|
843
|
+
var bymn = Math.min(from[1], escY), bymx = Math.max(from[1], escY);
|
|
844
|
+
var bexX = findClearX(bymn, bymx, se[0], iboxes); bexX = alloc.findFreeX(bymn, bymx, bexX, iboxes); alloc.claimV(bymn, bymx, bexX);
|
|
845
|
+
|
|
846
|
+
var bnmn = Math.min(to[1], escY), bnmx = Math.max(to[1], escY);
|
|
847
|
+
var bnxX = findClearX(bnmn, bnmx, sn[0], iboxes); bnxX = alloc.findFreeX(bnmn, bnmx, bnxX, iboxes); alloc.claimV(bnmn, bnmx, bnxX);
|
|
848
|
+
|
|
849
|
+
return simplifyWaypoints([from, [bexX, from[1]], [bexX, escY], [bnxX, escY], [bnxX, to[1]], to]);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function calcOrthogonalPath(from, to, nboxes, srcId, tgtId, fromIdx, toIdx, alloc) {
|
|
854
|
+
var pad = 15, stubLen = 20, stubSpc = 12, maxStub = 80, cr = 10;
|
|
855
|
+
var exitStub = Math.min(stubLen + fromIdx * stubSpc, maxStub);
|
|
856
|
+
var entryStub = Math.min(stubLen + toIdx * stubSpc, maxStub);
|
|
857
|
+
try {
|
|
858
|
+
var wp = computeWaypoints(from, to, nboxes, srcId, tgtId, pad, exitStub, entryStub, alloc);
|
|
859
|
+
if (!wp) return null;
|
|
860
|
+
var p = waypointsToPath(wp, cr);
|
|
861
|
+
return (p && p.length >= 5) ? p : null;
|
|
862
|
+
} catch (e) { return null; }
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// ---- Port position + node box + connection path indexes ----
|
|
587
866
|
var portPositions = {};
|
|
588
867
|
content.querySelectorAll('[data-port-id]').forEach(function(el) {
|
|
589
868
|
var id = el.getAttribute('data-port-id');
|
|
590
869
|
portPositions[id] = { cx: parseFloat(el.getAttribute('cx')), cy: parseFloat(el.getAttribute('cy')) };
|
|
591
870
|
});
|
|
592
871
|
|
|
872
|
+
// Extract node bounding boxes from SVG rect elements
|
|
873
|
+
var nodeBoxMap = {};
|
|
874
|
+
content.querySelectorAll('.nodes [data-node-id]').forEach(function(g) {
|
|
875
|
+
var nid = g.getAttribute('data-node-id');
|
|
876
|
+
var rect = g.querySelector(':scope > rect');
|
|
877
|
+
if (!rect) return;
|
|
878
|
+
nodeBoxMap[nid] = {
|
|
879
|
+
id: nid,
|
|
880
|
+
x: parseFloat(rect.getAttribute('x')),
|
|
881
|
+
y: parseFloat(rect.getAttribute('y')),
|
|
882
|
+
w: parseFloat(rect.getAttribute('width')),
|
|
883
|
+
h: parseFloat(rect.getAttribute('height'))
|
|
884
|
+
};
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
// Build port-to-node mapping and compute port indices within each node
|
|
888
|
+
var portNodeMap = {};
|
|
889
|
+
var portIndexMap = {};
|
|
890
|
+
content.querySelectorAll('[data-port-id]').forEach(function(el) {
|
|
891
|
+
var id = el.getAttribute('data-port-id');
|
|
892
|
+
var dir = el.getAttribute('data-direction');
|
|
893
|
+
var parts = id.split('.');
|
|
894
|
+
var nodeId = parts[0];
|
|
895
|
+
portNodeMap[id] = nodeId;
|
|
896
|
+
if (!portIndexMap[nodeId]) portIndexMap[nodeId] = { input: [], output: [] };
|
|
897
|
+
portIndexMap[nodeId][dir].push(id);
|
|
898
|
+
});
|
|
899
|
+
|
|
593
900
|
var nodeOffsets = {};
|
|
594
901
|
var connIndex = [];
|
|
595
902
|
content.querySelectorAll('path[data-source]').forEach(function(p) {
|
|
596
903
|
var src = p.getAttribute('data-source'), tgt = p.getAttribute('data-target');
|
|
597
|
-
|
|
904
|
+
var srcNode = src.split('.')[0], tgtNode = tgt.split('.')[0];
|
|
905
|
+
var srcIdx = portIndexMap[srcNode] ? portIndexMap[srcNode].output.indexOf(src) : 0;
|
|
906
|
+
var tgtIdx = portIndexMap[tgtNode] ? portIndexMap[tgtNode].input.indexOf(tgt) : 0;
|
|
907
|
+
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) });
|
|
598
909
|
});
|
|
599
910
|
|
|
600
911
|
// Snapshot of original port positions for reset
|
|
@@ -602,6 +913,47 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
602
913
|
for (var pid in portPositions) {
|
|
603
914
|
origPortPositions[pid] = { cx: portPositions[pid].cx, cy: portPositions[pid].cy };
|
|
604
915
|
}
|
|
916
|
+
var origNodeBoxMap = {};
|
|
917
|
+
for (var nid in nodeBoxMap) {
|
|
918
|
+
var b = nodeBoxMap[nid];
|
|
919
|
+
origNodeBoxMap[nid] = { id: b.id, x: b.x, y: b.y, w: b.w, h: b.h };
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// ---- Recalculate all connection paths using orthogonal + bezier routing ----
|
|
923
|
+
function recalcAllPaths() {
|
|
924
|
+
var boxes = [];
|
|
925
|
+
for (var nid in nodeBoxMap) boxes.push(nodeBoxMap[nid]);
|
|
926
|
+
var sorted = connIndex.slice().sort(function(a, b) {
|
|
927
|
+
var spa = portPositions[a.src] && portPositions[a.tgt] ? Math.abs(portPositions[a.tgt].cx - portPositions[a.src].cx) : 0;
|
|
928
|
+
var spb = portPositions[b.src] && portPositions[b.tgt] ? Math.abs(portPositions[b.tgt].cx - portPositions[b.src].cx) : 0;
|
|
929
|
+
if (Math.abs(spa - spb) > 1) return spa - spb;
|
|
930
|
+
var sxa = portPositions[a.src] ? portPositions[a.src].cx : 0, sxb = portPositions[b.src] ? portPositions[b.src].cx : 0;
|
|
931
|
+
if (Math.abs(sxa - sxb) > 1) return sxa - sxb;
|
|
932
|
+
var sya = portPositions[a.src] ? portPositions[a.src].cy : 0, syb = portPositions[b.src] ? portPositions[b.src].cy : 0;
|
|
933
|
+
return sya - syb;
|
|
934
|
+
});
|
|
935
|
+
var alloc = createAllocator();
|
|
936
|
+
for (var i = 0; i < sorted.length; i++) {
|
|
937
|
+
var c = sorted[i];
|
|
938
|
+
var sp = portPositions[c.src], tp = portPositions[c.tgt];
|
|
939
|
+
if (!sp || !tp) continue;
|
|
940
|
+
var sx = sp.cx, sy = sp.cy, tx = tp.cx, ty = tp.cy;
|
|
941
|
+
// For scope connections, use parent-local coords
|
|
942
|
+
if (c.scopeOf) {
|
|
943
|
+
var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
|
|
944
|
+
sx -= pOff.dx; sy -= pOff.dy; tx -= pOff.dx; ty -= pOff.dy;
|
|
945
|
+
}
|
|
946
|
+
var ddx = tx - sx, ddy = ty - sy, dist = Math.sqrt(ddx * ddx + ddy * ddy);
|
|
947
|
+
var path;
|
|
948
|
+
if (dist > ORTHO_THRESHOLD) {
|
|
949
|
+
path = calcOrthogonalPath([sx, sy], [tx, ty], boxes, c.srcNode, c.tgtNode, c.srcIdx, c.tgtIdx, alloc);
|
|
950
|
+
if (!path) path = computeConnectionPath(sx, sy, tx, ty);
|
|
951
|
+
} else {
|
|
952
|
+
path = computeConnectionPath(sx, sy, tx, ty);
|
|
953
|
+
}
|
|
954
|
+
c.el.setAttribute('d', path);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
605
957
|
|
|
606
958
|
function resetLayout() {
|
|
607
959
|
for (var nid in nodeOffsets) {
|
|
@@ -621,10 +973,11 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
621
973
|
for (var pid in origPortPositions) {
|
|
622
974
|
portPositions[pid] = { cx: origPortPositions[pid].cx, cy: origPortPositions[pid].cy };
|
|
623
975
|
}
|
|
624
|
-
|
|
625
|
-
var
|
|
626
|
-
|
|
627
|
-
}
|
|
976
|
+
for (var nid in origNodeBoxMap) {
|
|
977
|
+
var b = origNodeBoxMap[nid];
|
|
978
|
+
nodeBoxMap[nid] = { id: b.id, x: b.x, y: b.y, w: b.w, h: b.h };
|
|
979
|
+
}
|
|
980
|
+
recalcAllPaths();
|
|
628
981
|
fitToView();
|
|
629
982
|
}
|
|
630
983
|
document.getElementById('btn-reset').addEventListener('click', resetLayout);
|
|
@@ -694,39 +1047,24 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
694
1047
|
});
|
|
695
1048
|
}
|
|
696
1049
|
|
|
697
|
-
//
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
if (
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
}
|
|
1050
|
+
// Update node box positions for orthogonal routing
|
|
1051
|
+
if (nodeBoxMap[nodeId]) {
|
|
1052
|
+
var nb = origNodeBoxMap[nodeId];
|
|
1053
|
+
if (nb) nodeBoxMap[nodeId] = { id: nb.id, x: nb.x + off.dx, y: nb.y + off.dy, w: nb.w, h: nb.h };
|
|
1054
|
+
}
|
|
1055
|
+
if (nodeG) {
|
|
1056
|
+
var children = nodeG.querySelectorAll(':scope > g[data-node-id]');
|
|
1057
|
+
children.forEach(function(childG) {
|
|
1058
|
+
var childId = childG.getAttribute('data-node-id');
|
|
1059
|
+
var cnb = origNodeBoxMap[childId];
|
|
1060
|
+
if (cnb && nodeOffsets[childId]) {
|
|
1061
|
+
nodeBoxMap[childId] = { id: cnb.id, x: cnb.x + nodeOffsets[childId].dx, y: cnb.y + nodeOffsets[childId].dy, w: cnb.w, h: cnb.h };
|
|
710
1062
|
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
if (c.srcNode === childId || c.tgtNode === childId) {
|
|
717
|
-
var sp = portPositions[c.src], tp = portPositions[c.tgt];
|
|
718
|
-
if (sp && tp) {
|
|
719
|
-
if (c.scopeOf) {
|
|
720
|
-
var pOff = nodeOffsets[c.scopeOf] || { dx: 0, dy: 0 };
|
|
721
|
-
c.el.setAttribute('d', computeConnectionPath(sp.cx - pOff.dx, sp.cy - pOff.dy, tp.cx - pOff.dx, tp.cy - pOff.dy));
|
|
722
|
-
} else {
|
|
723
|
-
c.el.setAttribute('d', computeConnectionPath(sp.cx, sp.cy, tp.cx, tp.cy));
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
});
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Recalculate all connection paths with orthogonal routing
|
|
1067
|
+
recalcAllPaths();
|
|
730
1068
|
}
|
|
731
1069
|
|
|
732
1070
|
var allLabelIds = Object.keys(labelMap);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.19.
|
|
1
|
+
export declare const VERSION = "0.19.1";
|
|
2
2
|
//# sourceMappingURL=generated-version.d.ts.map
|
package/package.json
CHANGED