@r0kshan/cairn 1.0.0-RC12 → 1.0.0-RC13
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 +110 -11
- package/bin/cairn.mjs +450 -91
- package/dist/cairn.mjs +441 -88
- package/package.json +3 -3
package/dist/cairn.mjs
CHANGED
|
@@ -92256,10 +92256,13 @@ function lex(src, diagnostics) {
|
|
|
92256
92256
|
col++;
|
|
92257
92257
|
continue;
|
|
92258
92258
|
}
|
|
92259
|
-
|
|
92260
|
-
|
|
92261
|
-
|
|
92262
|
-
|
|
92259
|
+
const arrowGlyph = ["-->", "..>", "->"].find(
|
|
92260
|
+
(glyph) => src.startsWith(glyph, position) && glyph[0] === char
|
|
92261
|
+
);
|
|
92262
|
+
if (arrowGlyph) {
|
|
92263
|
+
push("arrow", arrowGlyph, line, col);
|
|
92264
|
+
position += arrowGlyph.length;
|
|
92265
|
+
col += arrowGlyph.length;
|
|
92263
92266
|
continue;
|
|
92264
92267
|
}
|
|
92265
92268
|
const singleCharKind = SINGLE_CHAR_TOKENS[char];
|
|
@@ -92299,6 +92302,7 @@ function lex(src, diagnostics) {
|
|
|
92299
92302
|
}
|
|
92300
92303
|
|
|
92301
92304
|
// src/models/ast.ts
|
|
92305
|
+
var ATTACH_SIDES = ["left", "right", "top", "bottom"];
|
|
92302
92306
|
var defaultDiagramStyle = () => ({
|
|
92303
92307
|
crossingHops: true,
|
|
92304
92308
|
compact: false,
|
|
@@ -92935,9 +92939,9 @@ function indexElementsById(elements) {
|
|
|
92935
92939
|
// src/parser.ts
|
|
92936
92940
|
function parseFlow(p, sourceToken) {
|
|
92937
92941
|
const { matchToken, advance, reportError, lookAhead, syncToNextLine, model } = p;
|
|
92938
|
-
advance();
|
|
92942
|
+
const arrowToken = advance();
|
|
92939
92943
|
if (!matchToken("id")) {
|
|
92940
|
-
reportError(
|
|
92944
|
+
reportError(`target identifier expected after \`${arrowToken.text}\``, lookAhead().span);
|
|
92941
92945
|
syncToNextLine();
|
|
92942
92946
|
return;
|
|
92943
92947
|
}
|
|
@@ -92954,6 +92958,8 @@ function parseFlow(p, sourceToken) {
|
|
|
92954
92958
|
len: targetToken.span.col + targetToken.span.len - sourceToken.span.col
|
|
92955
92959
|
}
|
|
92956
92960
|
};
|
|
92961
|
+
if (arrowToken.text === "-->") flow.lineStyle = "dashed";
|
|
92962
|
+
else if (arrowToken.text === "..>") flow.lineStyle = "dotted";
|
|
92957
92963
|
if (matchToken("colon")) {
|
|
92958
92964
|
advance();
|
|
92959
92965
|
if (matchToken("str")) flow.label = advance().text;
|
|
@@ -93006,6 +93012,11 @@ function parseFlow(p, sourceToken) {
|
|
|
93006
93012
|
flow.style = {};
|
|
93007
93013
|
p.parseStyleEntries(null, flow.style);
|
|
93008
93014
|
}
|
|
93015
|
+
for (const split of [
|
|
93016
|
+
splitEndpoint(sourceToken, flow, "from"),
|
|
93017
|
+
splitEndpoint(targetToken, flow, "to")
|
|
93018
|
+
])
|
|
93019
|
+
if (split) p.endpointSplits.push(split);
|
|
93009
93020
|
model.flows.push(flow);
|
|
93010
93021
|
}
|
|
93011
93022
|
function parseElement(p, sourceToken, parent) {
|
|
@@ -93057,6 +93068,84 @@ function parseElement(p, sourceToken, parent) {
|
|
|
93057
93068
|
}
|
|
93058
93069
|
(parent ? parent.children : model.elements).push(element);
|
|
93059
93070
|
}
|
|
93071
|
+
function splitEndpoint(token, flow, role) {
|
|
93072
|
+
const dot = token.text.lastIndexOf(".");
|
|
93073
|
+
if (dot <= 0 || dot === token.text.length - 1) return null;
|
|
93074
|
+
const base = token.text.slice(0, dot);
|
|
93075
|
+
const side = token.text.slice(dot + 1);
|
|
93076
|
+
return {
|
|
93077
|
+
flow,
|
|
93078
|
+
role,
|
|
93079
|
+
raw: token.text,
|
|
93080
|
+
rawSpan: token.span,
|
|
93081
|
+
base,
|
|
93082
|
+
baseSpan: { line: token.span.line, col: token.span.col, len: base.length },
|
|
93083
|
+
side,
|
|
93084
|
+
sideSpan: { line: token.span.line, col: token.span.col + dot + 1, len: side.length }
|
|
93085
|
+
};
|
|
93086
|
+
}
|
|
93087
|
+
function resolveAttachSides(splits, model, diagnostics) {
|
|
93088
|
+
for (const split of splits) {
|
|
93089
|
+
if (model.index.has(split.raw)) {
|
|
93090
|
+
if (ATTACH_SIDES.includes(split.side))
|
|
93091
|
+
diagnostics.push({
|
|
93092
|
+
code: "W0571",
|
|
93093
|
+
severity: "warning",
|
|
93094
|
+
message: `\`${split.raw}\` is a declared element, so \`.${split.side}\` is not read as an attachment side`,
|
|
93095
|
+
span: split.rawSpan,
|
|
93096
|
+
note: "a declared id always wins over the `ID.side` reading",
|
|
93097
|
+
help: `rename the element if you meant to attach the flow to the ${split.side} side of \`${split.base}\``
|
|
93098
|
+
});
|
|
93099
|
+
continue;
|
|
93100
|
+
}
|
|
93101
|
+
if (!model.index.has(split.base)) continue;
|
|
93102
|
+
const known = ATTACH_SIDES.includes(split.side);
|
|
93103
|
+
if (!known)
|
|
93104
|
+
diagnostics.push({
|
|
93105
|
+
code: "E0223",
|
|
93106
|
+
severity: "error",
|
|
93107
|
+
message: `unknown attachment side \`${split.side}\``,
|
|
93108
|
+
span: split.sideSpan,
|
|
93109
|
+
note: "sides are named as the diagram is read",
|
|
93110
|
+
help: "use `left`, `right`, `top` or `bottom`, e.g. `APP.right -> DB.left`"
|
|
93111
|
+
});
|
|
93112
|
+
const side = known ? { value: split.side, span: split.sideSpan } : void 0;
|
|
93113
|
+
if (split.role === "from") {
|
|
93114
|
+
split.flow.from = split.base;
|
|
93115
|
+
split.flow.fromSpan = split.baseSpan;
|
|
93116
|
+
split.flow.fromSide = side;
|
|
93117
|
+
} else {
|
|
93118
|
+
split.flow.to = split.base;
|
|
93119
|
+
split.flow.toSpan = split.baseSpan;
|
|
93120
|
+
split.flow.toSide = side;
|
|
93121
|
+
}
|
|
93122
|
+
}
|
|
93123
|
+
}
|
|
93124
|
+
function tryOrderEntry(p, parent) {
|
|
93125
|
+
const { matchToken, advance, reportCoded, save, restore, syncToNextLine } = p;
|
|
93126
|
+
if (!parent || !matchToken("id", "order")) return false;
|
|
93127
|
+
const mark = save();
|
|
93128
|
+
const keyToken = advance();
|
|
93129
|
+
if (!matchToken("colon")) {
|
|
93130
|
+
restore(mark);
|
|
93131
|
+
return false;
|
|
93132
|
+
}
|
|
93133
|
+
advance();
|
|
93134
|
+
const valueToken = matchToken("num") ? advance() : null;
|
|
93135
|
+
const value = valueToken ? Number(valueToken.text) : Number.NaN;
|
|
93136
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
93137
|
+
reportCoded(
|
|
93138
|
+
"E0106",
|
|
93139
|
+
"`order` expects a whole number \u2265 0",
|
|
93140
|
+
(valueToken ?? keyToken).span,
|
|
93141
|
+
"e.g. `order: 1` \u2014 lower comes first in reading order"
|
|
93142
|
+
);
|
|
93143
|
+
syncToNextLine();
|
|
93144
|
+
return true;
|
|
93145
|
+
}
|
|
93146
|
+
parent.order = { value, span: valueToken.span };
|
|
93147
|
+
return true;
|
|
93148
|
+
}
|
|
93060
93149
|
function tryLegendBlock(p) {
|
|
93061
93150
|
const { matchToken, advance, reportError, lookAhead, skipNewlines, syncToNextLine, model } = p;
|
|
93062
93151
|
if (!matchToken("id", "legend")) return false;
|
|
@@ -93134,13 +93223,14 @@ function parse(src) {
|
|
|
93134
93223
|
const skipNewlines = () => {
|
|
93135
93224
|
while (matchToken("nl")) advance();
|
|
93136
93225
|
};
|
|
93137
|
-
const
|
|
93138
|
-
code
|
|
93226
|
+
const reportCoded = (code, message, span, help) => diagnostics.push({
|
|
93227
|
+
code,
|
|
93139
93228
|
severity: "error",
|
|
93140
93229
|
message,
|
|
93141
93230
|
span,
|
|
93142
93231
|
help
|
|
93143
93232
|
});
|
|
93233
|
+
const reportError = (message, span, help) => reportCoded("E0101", message, span, help);
|
|
93144
93234
|
const syncToNextLine = () => {
|
|
93145
93235
|
while (!matchToken("nl") && !matchToken("eof")) advance();
|
|
93146
93236
|
};
|
|
@@ -93153,6 +93243,7 @@ function parse(src) {
|
|
|
93153
93243
|
index: /* @__PURE__ */ new Map()
|
|
93154
93244
|
};
|
|
93155
93245
|
const flowSequence = { next: 0 };
|
|
93246
|
+
const endpointSplits = [];
|
|
93156
93247
|
skipNewlines();
|
|
93157
93248
|
if (matchToken("id", "diagram")) {
|
|
93158
93249
|
advance();
|
|
@@ -93244,12 +93335,14 @@ function parse(src) {
|
|
|
93244
93335
|
advance,
|
|
93245
93336
|
skipNewlines,
|
|
93246
93337
|
reportError,
|
|
93338
|
+
reportCoded,
|
|
93247
93339
|
syncToNextLine,
|
|
93248
93340
|
save: () => position,
|
|
93249
93341
|
restore: (mark) => {
|
|
93250
93342
|
position = mark;
|
|
93251
93343
|
},
|
|
93252
93344
|
flowSequence,
|
|
93345
|
+
endpointSplits,
|
|
93253
93346
|
parseStyleEntries,
|
|
93254
93347
|
parseElementBody
|
|
93255
93348
|
};
|
|
@@ -93259,6 +93352,7 @@ function parse(src) {
|
|
|
93259
93352
|
return;
|
|
93260
93353
|
}
|
|
93261
93354
|
if (tryStyleBlock(parent)) return;
|
|
93355
|
+
if (tryOrderEntry(parser, parent)) return;
|
|
93262
93356
|
if (!parent && tryLegendBlock(parser)) return;
|
|
93263
93357
|
if (!parent && tryBusinessObject(parser)) return;
|
|
93264
93358
|
parseFlowOrElement(parser, parent);
|
|
@@ -93271,6 +93365,7 @@ function parse(src) {
|
|
|
93271
93365
|
for (const [id, element] of indexElementsById(model.elements)) {
|
|
93272
93366
|
if (!model.index.has(id)) model.index.set(id, element);
|
|
93273
93367
|
}
|
|
93368
|
+
resolveAttachSides(endpointSplits, model, diagnostics);
|
|
93274
93369
|
return { model, diags: diagnostics };
|
|
93275
93370
|
}
|
|
93276
93371
|
var LINE_STYLES = /* @__PURE__ */ new Set(["solid", "dashed", "dotted"]);
|
|
@@ -93616,12 +93711,27 @@ var logicalView = {
|
|
|
93616
93711
|
};
|
|
93617
93712
|
var applicationView = {
|
|
93618
93713
|
name: "application",
|
|
93619
|
-
kinds: [
|
|
93620
|
-
|
|
93714
|
+
kinds: [
|
|
93715
|
+
"actor-group",
|
|
93716
|
+
"actor",
|
|
93717
|
+
"system",
|
|
93718
|
+
"application",
|
|
93719
|
+
"module",
|
|
93720
|
+
"queue",
|
|
93721
|
+
"datastore",
|
|
93722
|
+
"external"
|
|
93723
|
+
],
|
|
93724
|
+
containerKinds: ["actor-group", "system", "application", "external"],
|
|
93621
93725
|
// C4-style `(API_REST, JSON)`: the protocol half is worth tabulating, the port is not.
|
|
93622
|
-
|
|
93726
|
+
// `zoneOf` walks ancestors nearest-first, so an endpoint inside an application
|
|
93727
|
+
// still reads `Name (App)`; the system only annotates what sits directly in it.
|
|
93728
|
+
matrix: {
|
|
93729
|
+
zoneKinds: ["application", "system"],
|
|
93730
|
+
columns: ["num", "source", "dest", "proto", "nature"]
|
|
93731
|
+
},
|
|
93623
93732
|
partitions: {
|
|
93624
93733
|
"actor-group": 0,
|
|
93734
|
+
system: 1,
|
|
93625
93735
|
application: 1,
|
|
93626
93736
|
queue: 1,
|
|
93627
93737
|
datastore: 1,
|
|
@@ -93630,6 +93740,7 @@ var applicationView = {
|
|
|
93630
93740
|
legendNames: {
|
|
93631
93741
|
"actor-group": "Actor group",
|
|
93632
93742
|
actor: "Actor",
|
|
93743
|
+
system: "System",
|
|
93633
93744
|
application: "Application",
|
|
93634
93745
|
module: "Application module",
|
|
93635
93746
|
queue: "Message queue / broker",
|
|
@@ -93639,6 +93750,7 @@ var applicationView = {
|
|
|
93639
93750
|
legendNamesFr: {
|
|
93640
93751
|
"actor-group": "Groupe d'acteurs",
|
|
93641
93752
|
actor: "Acteur",
|
|
93753
|
+
system: "Syst\xE8me",
|
|
93642
93754
|
application: "Application",
|
|
93643
93755
|
module: "Module applicatif",
|
|
93644
93756
|
queue: "File de messages / broker",
|
|
@@ -93686,6 +93798,12 @@ var applicationView = {
|
|
|
93686
93798
|
fill: "#eef4fb",
|
|
93687
93799
|
stroke: { color: "#7a9cc4", style: "dashed", width: 1.2 }
|
|
93688
93800
|
},
|
|
93801
|
+
// Same palette as the logical view's `system`, so a system boundary reads
|
|
93802
|
+
// the same in both views.
|
|
93803
|
+
system: {
|
|
93804
|
+
fill: "#f6f2ea",
|
|
93805
|
+
stroke: { color: "#b09a6d", style: "solid", width: 1.2 }
|
|
93806
|
+
},
|
|
93689
93807
|
application: {
|
|
93690
93808
|
fill: "#e8f1f8",
|
|
93691
93809
|
stroke: { color: "#5b8db8", style: "solid", width: 1.2 }
|
|
@@ -93713,6 +93831,10 @@ var applicationView = {
|
|
|
93713
93831
|
fill: "#232a33",
|
|
93714
93832
|
stroke: { color: "#5c7fa8", style: "dashed", width: 1.2 }
|
|
93715
93833
|
},
|
|
93834
|
+
system: {
|
|
93835
|
+
fill: "#2b2822",
|
|
93836
|
+
stroke: { color: "#9c8558", style: "solid", width: 1.2 }
|
|
93837
|
+
},
|
|
93716
93838
|
application: {
|
|
93717
93839
|
fill: "#1f2a33",
|
|
93718
93840
|
stroke: { color: "#4a7ba6", style: "solid", width: 1.2 }
|
|
@@ -95108,6 +95230,14 @@ var pathLength = (points) => {
|
|
|
95108
95230
|
length += Math.abs(points[index + 1].x - points[index].x) + Math.abs(points[index + 1].y - points[index].y);
|
|
95109
95231
|
return length;
|
|
95110
95232
|
};
|
|
95233
|
+
var DETOUR_RATIO = 2.2;
|
|
95234
|
+
var DETOUR_WASTE = 400;
|
|
95235
|
+
var isLongDetour = (points, boxA, boxB) => {
|
|
95236
|
+
const direct = Math.abs(boxA.x + boxA.width / 2 - (boxB.x + boxB.width / 2)) + Math.abs(boxA.y + boxA.height / 2 - (boxB.y + boxB.height / 2));
|
|
95237
|
+
if (direct <= 0) return false;
|
|
95238
|
+
const length = pathLength(points);
|
|
95239
|
+
return length > DETOUR_RATIO * direct && length - direct > DETOUR_WASTE;
|
|
95240
|
+
};
|
|
95111
95241
|
var boxToPolylineSq = (box, points) => {
|
|
95112
95242
|
let best = Number.POSITIVE_INFINITY;
|
|
95113
95243
|
for (let index = 0; index + 1 < points.length; index++) {
|
|
@@ -95173,6 +95303,7 @@ function detourCandidates(scene, model) {
|
|
|
95173
95303
|
for (const edge of scene.edges) {
|
|
95174
95304
|
const flow = flowById.get(edge.id);
|
|
95175
95305
|
if (!flow || edge.pts.length < 2) continue;
|
|
95306
|
+
if (edge.pinned) continue;
|
|
95176
95307
|
const source = nodeById.get(flow.from);
|
|
95177
95308
|
const target = nodeById.get(flow.to);
|
|
95178
95309
|
if (!source || !target) continue;
|
|
@@ -95196,7 +95327,9 @@ function verticalConflict(segment, x, top, bottom) {
|
|
|
95196
95327
|
var blockedBelowBy = (leafBoxes, x, top) => leafBoxes.some(
|
|
95197
95328
|
(node) => x >= node.x - 2 && x <= node.x + node.width + 2 && node.y + node.height > top + 1
|
|
95198
95329
|
);
|
|
95199
|
-
var blockedAboveBy = (leafBoxes, titleBoxes, x, bottom) => leafBoxes.some(
|
|
95330
|
+
var blockedAboveBy = (leafBoxes, titleBoxes, x, bottom) => leafBoxes.some(
|
|
95331
|
+
(node) => x >= node.x - 2 && x <= node.x + node.width + 2 && node.y < bottom - 1
|
|
95332
|
+
) || titleBoxes.some(
|
|
95200
95333
|
(box) => x >= box.x - TITLE_CLEARANCE && x <= box.x + box.width + TITLE_CLEARANCE && box.y < bottom - 1
|
|
95201
95334
|
);
|
|
95202
95335
|
var horizontalBlocked = (leafBoxes, y, probe) => leafBoxes.some(
|
|
@@ -95614,9 +95747,7 @@ function laneOffsets(lc, subset, direction, anchor) {
|
|
|
95614
95747
|
const ownPosition = spanAnchor(lc.scene, { left, right, exempt }, direction, anchor) + direction * CHANNEL_GAP;
|
|
95615
95748
|
const start = lane === 0 ? ownPosition : direction > 0 ? Math.max(ownPosition, positions[lane - 1] + labelHeights[lane - 1] + 14) : Math.min(ownPosition, positions[lane - 1] - (labelHeights[lane - 1] + 14));
|
|
95616
95749
|
labelHeights.push(labelHeight);
|
|
95617
|
-
positions.push(
|
|
95618
|
-
resolveLanePosition(lc, { left, right, exempt, labelHeight, start }, direction)
|
|
95619
|
-
);
|
|
95750
|
+
positions.push(resolveLanePosition(lc, { left, right, exempt, labelHeight, start }, direction));
|
|
95620
95751
|
}
|
|
95621
95752
|
return positions;
|
|
95622
95753
|
}
|
|
@@ -97030,6 +97161,7 @@ function clearRunHug(ctx, edge, run, own) {
|
|
|
97030
97161
|
if (attemptHugFix(ctx, edge, { run, axis, target }, own)) return true;
|
|
97031
97162
|
}
|
|
97032
97163
|
if (run.i !== 0 && run.i + 1 !== edge.pts.length - 1) return false;
|
|
97164
|
+
if (edge.pinned?.[run.i === 0 ? "start" : "end"]) return false;
|
|
97033
97165
|
if (!sideOf(edge.pts[run.i === 0 ? 0 : edge.pts.length - 1], leaves)) return false;
|
|
97034
97166
|
return resideAtSide(ctx, edge, run, own);
|
|
97035
97167
|
}
|
|
@@ -97462,8 +97594,10 @@ function generateReaimCandidates(AWAY_TOL2, from, to) {
|
|
|
97462
97594
|
};
|
|
97463
97595
|
const normalOf = (side) => side === "north" ? { x: 0, y: -1 } : side === "south" ? { x: 0, y: 1 } : side === "west" ? { x: -1, y: 0 } : { x: 1, y: 0 };
|
|
97464
97596
|
const candidates = [];
|
|
97465
|
-
|
|
97466
|
-
|
|
97597
|
+
const srcSides = from.keepSide ? [from.keepSide] : facingSides(src, dstC);
|
|
97598
|
+
const dstSides = to.keepSide ? [to.keepSide] : facingSides(dst, srcC);
|
|
97599
|
+
for (const srcSide of srcSides)
|
|
97600
|
+
for (const dstSide of dstSides) {
|
|
97467
97601
|
const na = normalOf(srcSide);
|
|
97468
97602
|
const nb = normalOf(dstSide);
|
|
97469
97603
|
const horizontalA = na.x !== 0;
|
|
@@ -97607,8 +97741,8 @@ function reaimEdge(rctx, edge) {
|
|
|
97607
97741
|
const dstC = { x: dst.x + dst.width / 2, y: dst.y + dst.height / 2 };
|
|
97608
97742
|
const candidates = generateReaimCandidates(
|
|
97609
97743
|
AWAY_TOL2,
|
|
97610
|
-
{ node: src, centre: srcC },
|
|
97611
|
-
{ node: dst, centre: dstC }
|
|
97744
|
+
{ node: src, centre: srcC, keepSide: edge.pinned?.start ? srcSeat.side : null },
|
|
97745
|
+
{ node: dst, centre: dstC, keepSide: edge.pinned?.end ? dstSeat.side : null }
|
|
97612
97746
|
);
|
|
97613
97747
|
const outside = runsExcept(edge.id);
|
|
97614
97748
|
const labelBoxes = seatedLabelBoxes(edge);
|
|
@@ -97640,7 +97774,8 @@ function reaimEdge(rctx, edge) {
|
|
|
97640
97774
|
function reaimWrapAroundTerminals(ctx) {
|
|
97641
97775
|
const { scene } = ctx;
|
|
97642
97776
|
const rctx = createReaimContext(ctx);
|
|
97643
|
-
for (const edge of scene.edges)
|
|
97777
|
+
for (const edge of scene.edges)
|
|
97778
|
+
if (!(edge.pinned?.start && edge.pinned?.end)) reaimEdge(rctx, edge);
|
|
97644
97779
|
}
|
|
97645
97780
|
function shiftCoincidentRun(leaves, pts, b, newAt) {
|
|
97646
97781
|
if (b.vert) {
|
|
@@ -98086,8 +98221,10 @@ function bestUnweaveRoute(uctx, job) {
|
|
|
98086
98221
|
const { edge, srcSeat, dstSeat } = job;
|
|
98087
98222
|
let best = null;
|
|
98088
98223
|
let bestLength = Number.POSITIVE_INFINITY;
|
|
98089
|
-
|
|
98090
|
-
|
|
98224
|
+
const srcSides = edge.pinned?.start ? [srcSeat.side] : ALL_SIDES;
|
|
98225
|
+
const dstSides = edge.pinned?.end ? [dstSeat.side] : ALL_SIDES;
|
|
98226
|
+
for (const srcSide of srcSides)
|
|
98227
|
+
for (const dstSide of dstSides)
|
|
98091
98228
|
for (const srcOffset of SEAT_OFFSETS)
|
|
98092
98229
|
for (const dstOffset of SEAT_OFFSETS) {
|
|
98093
98230
|
const a = unweaveSeat(srcSeat.node, srcSide, srcOffset);
|
|
@@ -98140,6 +98277,7 @@ function unweaveAndClearContainers(ctx) {
|
|
|
98140
98277
|
for (const edge of [...scene.edges].sort(
|
|
98141
98278
|
(a, b) => (Number.parseInt(a.id.slice(1), 10) || 0) - (Number.parseInt(b.id.slice(1), 10) || 0)
|
|
98142
98279
|
)) {
|
|
98280
|
+
if (edge.pinned?.start && edge.pinned?.end) continue;
|
|
98143
98281
|
unweaveEdge(uctx, edge);
|
|
98144
98282
|
}
|
|
98145
98283
|
}
|
|
@@ -98445,7 +98583,7 @@ function createLaneModel(deps) {
|
|
|
98445
98583
|
const fromB = lane - (firstVertical ? pts[3].y : pts[3].x);
|
|
98446
98584
|
return fromA * fromB > 0;
|
|
98447
98585
|
};
|
|
98448
|
-
return { channelU, isChannelU };
|
|
98586
|
+
return { channelU, isChannelU, laneBeyond };
|
|
98449
98587
|
}
|
|
98450
98588
|
function optimiseRoutes(scene, titleBoxes = [], folded = false) {
|
|
98451
98589
|
const leaves = scene.nodes.filter((node) => !node.container);
|
|
@@ -98455,7 +98593,7 @@ function optimiseRoutes(scene, titleBoxes = [], folded = false) {
|
|
|
98455
98593
|
const preRouted = new Set(scene.edges.filter((edge) => edge.detour).map((edge) => edge.id));
|
|
98456
98594
|
const { SIDES, seatOffsetsFor, seatOn, outward, horizontalSide } = createSeatModel(titleBoxes);
|
|
98457
98595
|
const clock = { generation: 0 };
|
|
98458
|
-
const { channelU, isChannelU } = createLaneModel({
|
|
98596
|
+
const { channelU, isChannelU, laneBeyond } = createLaneModel({
|
|
98459
98597
|
scene,
|
|
98460
98598
|
inspector,
|
|
98461
98599
|
titleBoxes,
|
|
@@ -98465,7 +98603,10 @@ function optimiseRoutes(scene, titleBoxes = [], folded = false) {
|
|
|
98465
98603
|
const shapesFor = (a, b, sides, subject) => {
|
|
98466
98604
|
const aH = horizontalSide(sides.a);
|
|
98467
98605
|
const bH = horizontalSide(sides.b);
|
|
98468
|
-
if (aH !== bH)
|
|
98606
|
+
if (aH !== bH) {
|
|
98607
|
+
const plainL = [a, aH ? { x: b.x, y: a.y } : { x: a.x, y: b.y }, b];
|
|
98608
|
+
return [plainL, ...approachLanes(a, b, sides, subject)];
|
|
98609
|
+
}
|
|
98469
98610
|
if (outward(sides.a) === outward(sides.b)) return channelU(a, b, sides.a, subject);
|
|
98470
98611
|
if (aH)
|
|
98471
98612
|
return [
|
|
@@ -98479,6 +98620,40 @@ function optimiseRoutes(scene, titleBoxes = [], folded = false) {
|
|
|
98479
98620
|
Math.max(a.y, b.y) - CHANNEL_INSET
|
|
98480
98621
|
].map((mid) => [a, { x: a.x, y: mid }, { x: b.x, y: mid }, b]);
|
|
98481
98622
|
};
|
|
98623
|
+
const approachLanes = (a, b, sides, subject) => {
|
|
98624
|
+
if (!subject.edge.pinned) return [];
|
|
98625
|
+
const aH = horizontalSide(sides.a);
|
|
98626
|
+
const bH = horizontalSide(sides.b);
|
|
98627
|
+
const beforeA = sides.a === "west" || sides.a === "north";
|
|
98628
|
+
const crossLane = laneBeyond(
|
|
98629
|
+
{
|
|
98630
|
+
spanLo: Math.min(a.x, b.x),
|
|
98631
|
+
spanHi: Math.max(a.x, b.x),
|
|
98632
|
+
start: (aH ? a.x : a.y) + (beforeA ? -CHANNEL_STEP : CHANNEL_STEP),
|
|
98633
|
+
vertical: !aH,
|
|
98634
|
+
before: beforeA
|
|
98635
|
+
},
|
|
98636
|
+
subject,
|
|
98637
|
+
true
|
|
98638
|
+
);
|
|
98639
|
+
if (crossLane === null) return [];
|
|
98640
|
+
const beforeB = sides.b === "west" || sides.b === "north";
|
|
98641
|
+
const search = {
|
|
98642
|
+
spanLo: bH ? Math.min(crossLane, b.y) : Math.min(crossLane, b.x),
|
|
98643
|
+
spanHi: bH ? Math.max(crossLane, b.y) : Math.max(crossLane, b.x),
|
|
98644
|
+
start: (bH ? b.x : b.y) + (beforeB ? -CHANNEL_STEP : CHANNEL_STEP),
|
|
98645
|
+
// `laneBeyond` names the axis after the terminal legs: a lane at a
|
|
98646
|
+
// constant x is the `vertical: false` search.
|
|
98647
|
+
vertical: !bH,
|
|
98648
|
+
before: beforeB
|
|
98649
|
+
};
|
|
98650
|
+
const lanes = [laneBeyond(search, subject, false), laneBeyond(search, subject, true)].filter(
|
|
98651
|
+
(lane, index, all) => lane !== null && all.indexOf(lane) === index
|
|
98652
|
+
);
|
|
98653
|
+
return lanes.map(
|
|
98654
|
+
(lane) => bH ? [a, { x: a.x, y: crossLane }, { x: lane, y: crossLane }, { x: lane, y: b.y }, b] : [a, { x: crossLane, y: a.y }, { x: crossLane, y: lane }, { x: b.x, y: lane }, b]
|
|
98655
|
+
);
|
|
98656
|
+
};
|
|
98482
98657
|
const routeCache = /* @__PURE__ */ new Map();
|
|
98483
98658
|
const routesFor = (edge) => {
|
|
98484
98659
|
const cacheKey = `${clock.generation}|${edge.id}|${edge.pts[0].x},${edge.pts[0].y}|${edge.pts[edge.pts.length - 1].x},${edge.pts[edge.pts.length - 1].y}`;
|
|
@@ -98489,8 +98664,10 @@ function optimiseRoutes(scene, titleBoxes = [], folded = false) {
|
|
|
98489
98664
|
const subject = { ends, edge };
|
|
98490
98665
|
const out = [];
|
|
98491
98666
|
const seen = /* @__PURE__ */ new Set();
|
|
98492
|
-
|
|
98493
|
-
|
|
98667
|
+
const aSides = edge.pinned?.start ? [ends[0].side] : SIDES;
|
|
98668
|
+
const bSides = edge.pinned?.end ? [ends[1].side] : SIDES;
|
|
98669
|
+
for (const aSide of aSides)
|
|
98670
|
+
for (const bSide of bSides) {
|
|
98494
98671
|
const sides = { a: aSide, b: bSide };
|
|
98495
98672
|
for (const aOff of seatOffsetsFor(ends[0].node, aSide))
|
|
98496
98673
|
for (const bOff of seatOffsetsFor(ends[1].node, bSide))
|
|
@@ -98822,13 +98999,18 @@ function computeIngressExternalElements(model) {
|
|
|
98822
98999
|
}
|
|
98823
99000
|
return ingressExternalElements;
|
|
98824
99001
|
}
|
|
98825
|
-
|
|
99002
|
+
var orderOption = (element) => element.order ? { "elk.position": `(0,${element.order.value})` } : {};
|
|
99003
|
+
var semiInteractiveOption = (children) => children.some((child) => child.order) ? { "elk.layered.crossingMinimization.semiInteractive": "true" } : {};
|
|
99004
|
+
function toElkNode2(element, compact, fonts, root = false) {
|
|
99005
|
+
const { cont: containerFontSize, node: nodeFontSize } = fonts;
|
|
98826
99006
|
if (element.children.length) {
|
|
98827
99007
|
const lineCount = (element.label ?? element.id).split("\n").length;
|
|
98828
99008
|
return {
|
|
98829
99009
|
id: element.id,
|
|
98830
99010
|
layoutOptions: {
|
|
98831
|
-
"elk.padding": `[top=${(compact ? 11 : 13) + lineCount * 14},left=${compact ? 7 : 9},bottom=${compact ? 7 : 9},right=${compact ? 7 : 9}]
|
|
99011
|
+
"elk.padding": `[top=${(compact ? 11 : 13) + lineCount * 14},left=${compact ? 7 : 9},bottom=${compact ? 7 : 9},right=${compact ? 7 : 9}]`,
|
|
99012
|
+
...root ? {} : orderOption(element),
|
|
99013
|
+
...semiInteractiveOption(element.children)
|
|
98832
99014
|
},
|
|
98833
99015
|
labels: [
|
|
98834
99016
|
{
|
|
@@ -98837,7 +99019,7 @@ function toElkNode2(element, compact, containerFontSize, nodeFontSize) {
|
|
|
98837
99019
|
}
|
|
98838
99020
|
],
|
|
98839
99021
|
children: element.children.map(
|
|
98840
|
-
(child) => toElkNode2(child, compact,
|
|
99022
|
+
(child) => toElkNode2(child, compact, fonts)
|
|
98841
99023
|
)
|
|
98842
99024
|
};
|
|
98843
99025
|
}
|
|
@@ -98845,6 +99027,7 @@ function toElkNode2(element, compact, containerFontSize, nodeFontSize) {
|
|
|
98845
99027
|
const isActor = element.kind === "actor";
|
|
98846
99028
|
return {
|
|
98847
99029
|
id: element.id,
|
|
99030
|
+
...element.order && !root ? { layoutOptions: orderOption(element) } : {},
|
|
98848
99031
|
width: isActor ? Math.max(64, measure(element.label ?? element.id, nodeFontSize - 1.5).width + 8) : Math.max(compact ? 98 : 108, measured.width + (compact ? 10 : 12)),
|
|
98849
99032
|
height: isActor ? 54 + ((element.label ?? element.id).split("\n").length - 1) * 11 : Math.max(compact ? 36 : 38, measured.height + (compact ? 10 : 12))
|
|
98850
99033
|
};
|
|
@@ -98931,6 +99114,12 @@ function transpose(scene, titleBoxes) {
|
|
|
98931
99114
|
}
|
|
98932
99115
|
[scene.width, scene.height] = [scene.height, scene.width];
|
|
98933
99116
|
}
|
|
99117
|
+
var PORT_PASS_ROUNDS = 2;
|
|
99118
|
+
function beatsRelayout(round, best) {
|
|
99119
|
+
if (!best) return true;
|
|
99120
|
+
if (round.tier !== best.tier) return round.tier < best.tier;
|
|
99121
|
+
return round.detours < best.detours;
|
|
99122
|
+
}
|
|
98934
99123
|
function attachAwayOf(scene, model) {
|
|
98935
99124
|
const ATTACH_AWAY_TOL = 24;
|
|
98936
99125
|
const byId = new Map(scene.nodes.map((node) => [node.id, node]));
|
|
@@ -98941,21 +99130,35 @@ function attachAwayOf(scene, model) {
|
|
|
98941
99130
|
const from = byId.get(flow?.from ?? "");
|
|
98942
99131
|
const to = byId.get(flow?.to ?? "");
|
|
98943
99132
|
if (!from || !to) continue;
|
|
98944
|
-
const
|
|
99133
|
+
const centerOf2 = (n) => ({ x: n.x + n.width / 2, y: n.y + n.height / 2 });
|
|
98945
99134
|
const away = (seg, target) => Math.abs(seg.x) >= 0.5 && Math.abs(target.x) > ATTACH_AWAY_TOL && seg.x * target.x < 0 || Math.abs(seg.y) >= 0.5 && Math.abs(target.y) > ATTACH_AWAY_TOL && seg.y * target.y < 0;
|
|
98946
99135
|
const p0 = e.pts[0];
|
|
98947
99136
|
const p1 = e.pts[1];
|
|
98948
99137
|
const pn = e.pts[e.pts.length - 1];
|
|
98949
99138
|
const pm = e.pts[e.pts.length - 2];
|
|
98950
|
-
const toCenter =
|
|
98951
|
-
const fromCenter =
|
|
98952
|
-
if (away({ x: p1.x - p0.x, y: p1.y - p0.y }, { x: toCenter.x - p0.x, y: toCenter.y - p0.y }))
|
|
99139
|
+
const toCenter = centerOf2(to);
|
|
99140
|
+
const fromCenter = centerOf2(from);
|
|
99141
|
+
if (!e.pinned?.start && away({ x: p1.x - p0.x, y: p1.y - p0.y }, { x: toCenter.x - p0.x, y: toCenter.y - p0.y }))
|
|
98953
99142
|
flagged.add(e.id);
|
|
98954
|
-
if (away({ x: pn.x - pm.x, y: pn.y - pm.y }, { x: pn.x - fromCenter.x, y: pn.y - fromCenter.y }))
|
|
99143
|
+
if (!e.pinned?.end && away({ x: pn.x - pm.x, y: pn.y - pm.y }, { x: pn.x - fromCenter.x, y: pn.y - fromCenter.y }))
|
|
98955
99144
|
flagged.add(e.id);
|
|
99145
|
+
if (!e.pinned && isLongDetour(e.pts, from, to)) flagged.add(e.id);
|
|
98956
99146
|
}
|
|
98957
99147
|
return flagged;
|
|
98958
99148
|
}
|
|
99149
|
+
function longDetourCount(scene, model) {
|
|
99150
|
+
const byId = new Map(scene.nodes.map((node) => [node.id, node]));
|
|
99151
|
+
let count = 0;
|
|
99152
|
+
for (const edge of scene.edges) {
|
|
99153
|
+
if (edge.pts.length < 2) continue;
|
|
99154
|
+
const flow = model.flows.find((f) => f.id === edge.id);
|
|
99155
|
+
const from = byId.get(flow?.from ?? "");
|
|
99156
|
+
const to = byId.get(flow?.to ?? "");
|
|
99157
|
+
if (!from || !to) continue;
|
|
99158
|
+
if (isLongDetour(edge.pts, from, to)) count++;
|
|
99159
|
+
}
|
|
99160
|
+
return count;
|
|
99161
|
+
}
|
|
98959
99162
|
function selectionExtras(scene, model) {
|
|
98960
99163
|
const extra = /* @__PURE__ */ new Map();
|
|
98961
99164
|
const edgeEnds = new Map(model.flows.map((flow) => [flow.id, /* @__PURE__ */ new Set([flow.from, flow.to])]));
|
|
@@ -99013,6 +99216,28 @@ function relayoutVerdict(before, after) {
|
|
|
99013
99216
|
}
|
|
99014
99217
|
return -1;
|
|
99015
99218
|
}
|
|
99219
|
+
var centerOf = (n) => ({ x: n.x + n.width / 2, y: n.y + n.height / 2 });
|
|
99220
|
+
function sideToward(from, to) {
|
|
99221
|
+
const dx = centerOf(to).x - centerOf(from).x;
|
|
99222
|
+
const dy = centerOf(to).y - centerOf(from).y;
|
|
99223
|
+
return Math.abs(dx) >= Math.abs(dy) ? dx < 0 ? "WEST" : "EAST" : dy < 0 ? "NORTH" : "SOUTH";
|
|
99224
|
+
}
|
|
99225
|
+
function sideOn(p, n) {
|
|
99226
|
+
if (p.x > n.x - 2 && p.x < n.x + n.width + 2) {
|
|
99227
|
+
if (Math.abs(p.y - n.y) < 2) return "NORTH";
|
|
99228
|
+
if (Math.abs(p.y - (n.y + n.height)) < 2) return "SOUTH";
|
|
99229
|
+
}
|
|
99230
|
+
if (p.y > n.y - 2 && p.y < n.y + n.height + 2) {
|
|
99231
|
+
if (Math.abs(p.x - n.x) < 2) return "WEST";
|
|
99232
|
+
if (Math.abs(p.x - (n.x + n.width)) < 2) return "EAST";
|
|
99233
|
+
}
|
|
99234
|
+
return null;
|
|
99235
|
+
}
|
|
99236
|
+
function firstPassSide(scene, flowId, role, sceneNode) {
|
|
99237
|
+
const edge = scene.edges.find((candidate) => candidate.id === flowId);
|
|
99238
|
+
if (!edge || edge.pts.length < 2) return null;
|
|
99239
|
+
return sideOn(role === "src" ? edge.pts[0] : edge.pts[edge.pts.length - 1], sceneNode);
|
|
99240
|
+
}
|
|
99016
99241
|
function constrainPorts(graph, scene, flagged, model) {
|
|
99017
99242
|
const nodeById = new Map(scene.nodes.map((node) => [node.id, node]));
|
|
99018
99243
|
const elkById = /* @__PURE__ */ new Map();
|
|
@@ -99021,23 +99246,6 @@ function constrainPorts(graph, scene, flagged, model) {
|
|
|
99021
99246
|
for (const child of node.children ?? []) register(child);
|
|
99022
99247
|
};
|
|
99023
99248
|
register(graph);
|
|
99024
|
-
const centerOf = (n) => ({ x: n.x + n.width / 2, y: n.y + n.height / 2 });
|
|
99025
|
-
const sideToward = (from, to) => {
|
|
99026
|
-
const dx = centerOf(to).x - centerOf(from).x;
|
|
99027
|
-
const dy = centerOf(to).y - centerOf(from).y;
|
|
99028
|
-
return Math.abs(dx) >= Math.abs(dy) ? dx < 0 ? "WEST" : "EAST" : dy < 0 ? "NORTH" : "SOUTH";
|
|
99029
|
-
};
|
|
99030
|
-
const sideOn = (p, n) => {
|
|
99031
|
-
if (p.x > n.x - 2 && p.x < n.x + n.width + 2) {
|
|
99032
|
-
if (Math.abs(p.y - n.y) < 2) return "NORTH";
|
|
99033
|
-
if (Math.abs(p.y - (n.y + n.height)) < 2) return "SOUTH";
|
|
99034
|
-
}
|
|
99035
|
-
if (p.y > n.y - 2 && p.y < n.y + n.height + 2) {
|
|
99036
|
-
if (Math.abs(p.x - n.x) < 2) return "WEST";
|
|
99037
|
-
if (Math.abs(p.x - (n.x + n.width)) < 2) return "EAST";
|
|
99038
|
-
}
|
|
99039
|
-
return null;
|
|
99040
|
-
};
|
|
99041
99249
|
const flaggedNodes = /* @__PURE__ */ new Set();
|
|
99042
99250
|
for (const flow of model.flows)
|
|
99043
99251
|
if (flagged.has(flow.id)) {
|
|
@@ -99054,14 +99262,9 @@ function constrainPorts(graph, scene, flagged, model) {
|
|
|
99054
99262
|
const role = flow.from === nodeId ? "src" : "dst";
|
|
99055
99263
|
const other = nodeById.get(role === "src" ? flow.to : flow.from);
|
|
99056
99264
|
if (!other) continue;
|
|
99057
|
-
|
|
99058
|
-
if (!flagged.has(flow.id)) {
|
|
99059
|
-
const edge = scene.edges.find((e) => e.id === flow.id);
|
|
99060
|
-
const terminal = edge && edge.pts.length >= 2 ? role === "src" ? edge.pts[0] : edge.pts[edge.pts.length - 1] : null;
|
|
99061
|
-
const actual = terminal ? sideOn(terminal, sceneNode) : null;
|
|
99062
|
-
if (actual) side = actual;
|
|
99063
|
-
}
|
|
99265
|
+
const side = flagged.has(flow.id) ? sideToward(sceneNode, other) : firstPassSide(scene, flow.id, role, sceneNode) ?? sideToward(sceneNode, other);
|
|
99064
99266
|
const portId = `${flow.id}${role === "src" ? "#out" : "#in"}`;
|
|
99267
|
+
if ((elkNode.ports ?? []).some((port) => port.id === portId)) continue;
|
|
99065
99268
|
ports.push({ id: portId, width: 1, height: 1, layoutOptions: { "elk.port.side": side } });
|
|
99066
99269
|
const elkEdge = (graph.edges ?? []).find((edge) => edge.id === flow.id);
|
|
99067
99270
|
if (elkEdge) {
|
|
@@ -99077,6 +99280,7 @@ function constrainPorts(graph, scene, flagged, model) {
|
|
|
99077
99280
|
var INGRESS_PARTITION = -1;
|
|
99078
99281
|
var EGRESS_PARTITION = 900;
|
|
99079
99282
|
var COMPACT_WRAP = 10;
|
|
99283
|
+
var SLOT_SCALE = 1e3;
|
|
99080
99284
|
function elkPartitionOf(element, index, view, ingressExternal) {
|
|
99081
99285
|
if (!view.partitionByOrder) return view.partitions[element.kind] ?? 1;
|
|
99082
99286
|
if (element.kind === "actor" || element.kind === "actor-group") return INGRESS_PARTITION;
|
|
@@ -99085,6 +99289,52 @@ function elkPartitionOf(element, index, view, ingressExternal) {
|
|
|
99085
99289
|
if (view.partitions[element.kind] !== void 0) return 90 + view.partitions[element.kind];
|
|
99086
99290
|
return index;
|
|
99087
99291
|
}
|
|
99292
|
+
function rootAncestorOf(model, id) {
|
|
99293
|
+
let element = model.index.get(id);
|
|
99294
|
+
if (!element) return void 0;
|
|
99295
|
+
while (element.parent) element = element.parent;
|
|
99296
|
+
return element.id;
|
|
99297
|
+
}
|
|
99298
|
+
function readingSlots(model, view, ingressExternal) {
|
|
99299
|
+
const slotOf = /* @__PURE__ */ new Map();
|
|
99300
|
+
if (!model.elements.some((element) => element.order)) return slotOf;
|
|
99301
|
+
const bandOf = new Map(
|
|
99302
|
+
model.elements.map((element, index) => [
|
|
99303
|
+
element.id,
|
|
99304
|
+
elkPartitionOf(element, index, view, ingressExternal)
|
|
99305
|
+
])
|
|
99306
|
+
);
|
|
99307
|
+
const lowestOf = /* @__PURE__ */ new Map();
|
|
99308
|
+
for (const band of new Set(bandOf.values())) {
|
|
99309
|
+
const members = model.elements.filter((element) => bandOf.get(element.id) === band);
|
|
99310
|
+
const declared = [
|
|
99311
|
+
...new Set(members.filter((m) => m.order).map((m) => m.order.value))
|
|
99312
|
+
].sort((a, b) => a - b);
|
|
99313
|
+
const lowest = declared.length ? 1 : 0;
|
|
99314
|
+
lowestOf.set(band, lowest);
|
|
99315
|
+
for (const member of members)
|
|
99316
|
+
slotOf.set(
|
|
99317
|
+
member.id,
|
|
99318
|
+
member.order ? declared.indexOf(member.order.value) + 1 : lowest
|
|
99319
|
+
);
|
|
99320
|
+
}
|
|
99321
|
+
const ordered = new Set(model.elements.filter((element) => element.order).map((e) => e.id));
|
|
99322
|
+
for (let pass = 0; pass < model.elements.length; pass++) {
|
|
99323
|
+
let moved = false;
|
|
99324
|
+
for (const flow of model.flows) {
|
|
99325
|
+
const from = rootAncestorOf(model, flow.from);
|
|
99326
|
+
const to = rootAncestorOf(model, flow.to);
|
|
99327
|
+
if (!from || !to || from === to || ordered.has(to)) continue;
|
|
99328
|
+
if (bandOf.get(from) !== bandOf.get(to)) continue;
|
|
99329
|
+
const candidate = slotOf.get(from);
|
|
99330
|
+
if (candidate <= slotOf.get(to)) continue;
|
|
99331
|
+
slotOf.set(to, candidate);
|
|
99332
|
+
moved = true;
|
|
99333
|
+
}
|
|
99334
|
+
if (!moved) break;
|
|
99335
|
+
}
|
|
99336
|
+
return slotOf;
|
|
99337
|
+
}
|
|
99088
99338
|
function elkFlowEdge(flow, ctx, labelWrap) {
|
|
99089
99339
|
const { compact, numbered, fonts, businessObjectName } = ctx;
|
|
99090
99340
|
if (numbered)
|
|
@@ -99121,9 +99371,88 @@ function elkFlowEdge(flow, ctx, labelWrap) {
|
|
|
99121
99371
|
labels: text || chips.length ? [{ text, ...labelBox }] : []
|
|
99122
99372
|
};
|
|
99123
99373
|
}
|
|
99374
|
+
var SIDE_TO_ELK = {
|
|
99375
|
+
left: "WEST",
|
|
99376
|
+
right: "EAST",
|
|
99377
|
+
top: "NORTH",
|
|
99378
|
+
bottom: "SOUTH"
|
|
99379
|
+
};
|
|
99380
|
+
function applyDeclaredPorts(graph, model) {
|
|
99381
|
+
const pinned = model.flows.filter((flow) => flow.fromSide || flow.toSide);
|
|
99382
|
+
if (!pinned.length) return;
|
|
99383
|
+
const elkById = /* @__PURE__ */ new Map();
|
|
99384
|
+
const register = (node) => {
|
|
99385
|
+
elkById.set(node.id, node);
|
|
99386
|
+
for (const child of node.children ?? []) register(child);
|
|
99387
|
+
};
|
|
99388
|
+
register(graph);
|
|
99389
|
+
for (const flow of pinned) {
|
|
99390
|
+
const elkEdge = (graph.edges ?? []).find((edge) => edge.id === flow.id);
|
|
99391
|
+
if (!elkEdge) continue;
|
|
99392
|
+
for (const [role, declared, nodeId] of [
|
|
99393
|
+
["out", flow.fromSide, flow.from],
|
|
99394
|
+
["in", flow.toSide, flow.to]
|
|
99395
|
+
]) {
|
|
99396
|
+
if (!declared) continue;
|
|
99397
|
+
const elkNode = elkById.get(nodeId);
|
|
99398
|
+
if (!elkNode) continue;
|
|
99399
|
+
const portId = `${flow.id}#${role}`;
|
|
99400
|
+
elkNode.ports = [
|
|
99401
|
+
...elkNode.ports ?? [],
|
|
99402
|
+
{
|
|
99403
|
+
id: portId,
|
|
99404
|
+
width: 1,
|
|
99405
|
+
height: 1,
|
|
99406
|
+
layoutOptions: { "elk.port.side": SIDE_TO_ELK[declared.value] }
|
|
99407
|
+
}
|
|
99408
|
+
];
|
|
99409
|
+
elkNode.layoutOptions = { ...elkNode.layoutOptions, "elk.portConstraints": "FIXED_SIDE" };
|
|
99410
|
+
if (role === "out") elkEdge.sources = [portId];
|
|
99411
|
+
else elkEdge.targets = [portId];
|
|
99412
|
+
}
|
|
99413
|
+
}
|
|
99414
|
+
}
|
|
99415
|
+
function terminalSide(p, node) {
|
|
99416
|
+
if (p.x > node.x - 2 && p.x < node.x + node.width + 2) {
|
|
99417
|
+
if (Math.abs(p.y - node.y) < 2) return "top";
|
|
99418
|
+
if (Math.abs(p.y - (node.y + node.height)) < 2) return "bottom";
|
|
99419
|
+
}
|
|
99420
|
+
if (p.y > node.y - 2 && p.y < node.y + node.height + 2) {
|
|
99421
|
+
if (Math.abs(p.x - node.x) < 2) return "left";
|
|
99422
|
+
if (Math.abs(p.x - (node.x + node.width)) < 2) return "right";
|
|
99423
|
+
}
|
|
99424
|
+
return null;
|
|
99425
|
+
}
|
|
99426
|
+
function attachSideDiagnostics(scene, model) {
|
|
99427
|
+
const diagnostics = [];
|
|
99428
|
+
const nodeById = new Map(scene.nodes.map((node) => [node.id, node]));
|
|
99429
|
+
for (const flow of model.flows) {
|
|
99430
|
+
if (!flow.fromSide && !flow.toSide) continue;
|
|
99431
|
+
const edge = scene.edges.find((candidate) => candidate.id === flow.id);
|
|
99432
|
+
if (!edge || edge.pts.length < 2) continue;
|
|
99433
|
+
for (const [declared, nodeId, point, role] of [
|
|
99434
|
+
[flow.fromSide, flow.from, edge.pts[0], "leaves"],
|
|
99435
|
+
[flow.toSide, flow.to, edge.pts[edge.pts.length - 1], "arrives on"]
|
|
99436
|
+
]) {
|
|
99437
|
+
const node = declared ? nodeById.get(nodeId) : void 0;
|
|
99438
|
+
if (!declared || !node) continue;
|
|
99439
|
+
const actual = terminalSide(point, node);
|
|
99440
|
+
if (actual === declared.value) continue;
|
|
99441
|
+
diagnostics.push({
|
|
99442
|
+
code: "W0570",
|
|
99443
|
+
severity: "warning",
|
|
99444
|
+
message: `attachment side \`${declared.value}\` could not be honored`,
|
|
99445
|
+
span: declared.span,
|
|
99446
|
+
note: actual ? `the flow ${role} the ${actual} side of \`${nodeId}\`` : `the flow does not ${role} a side of \`${nodeId}\` cleanly`,
|
|
99447
|
+
help: "a side the layout cannot reach is dropped rather than forced \u2014 try the opposite endpoint, or `order:` to move the element instead"
|
|
99448
|
+
});
|
|
99449
|
+
}
|
|
99450
|
+
}
|
|
99451
|
+
return diagnostics;
|
|
99452
|
+
}
|
|
99124
99453
|
function buildElkGraph(ctx, direction, options) {
|
|
99125
|
-
const { model, view, ingressExternal, compact, numbered, fonts } = ctx;
|
|
99126
|
-
|
|
99454
|
+
const { model, view, ingressExternal, slotOf, compact, numbered, fonts } = ctx;
|
|
99455
|
+
const graph = {
|
|
99127
99456
|
id: "root",
|
|
99128
99457
|
layoutOptions: {
|
|
99129
99458
|
"elk.algorithm": "layered",
|
|
@@ -99161,17 +99490,23 @@ function buildElkGraph(ctx, direction, options) {
|
|
|
99161
99490
|
} : {}
|
|
99162
99491
|
},
|
|
99163
99492
|
children: model.elements.map((element, index) => {
|
|
99164
|
-
const elkNode = toElkNode2(element, compact, fonts
|
|
99493
|
+
const elkNode = toElkNode2(element, compact, fonts, true);
|
|
99494
|
+
const band = elkPartitionOf(element, index, view, ingressExternal);
|
|
99495
|
+
const slot = slotOf.get(element.id);
|
|
99165
99496
|
elkNode.layoutOptions = {
|
|
99166
99497
|
...elkNode.layoutOptions,
|
|
99498
|
+
// Without an `order:` anywhere the band is emitted as it always was, so
|
|
99499
|
+
// the drawing is byte-identical to one from before the hint existed.
|
|
99167
99500
|
"elk.partitioning.partition": String(
|
|
99168
|
-
|
|
99501
|
+
slot === void 0 ? band : band * SLOT_SCALE + slot
|
|
99169
99502
|
)
|
|
99170
99503
|
};
|
|
99171
99504
|
return elkNode;
|
|
99172
99505
|
}),
|
|
99173
99506
|
edges: model.flows.map((flow) => elkFlowEdge(flow, ctx, options?.labelWrap))
|
|
99174
99507
|
};
|
|
99508
|
+
applyDeclaredPorts(graph, model);
|
|
99509
|
+
return graph;
|
|
99175
99510
|
}
|
|
99176
99511
|
function recordRepairs(scene, routesBefore) {
|
|
99177
99512
|
for (const edge of scene.edges) {
|
|
@@ -99226,6 +99561,7 @@ async function layout(model, view) {
|
|
|
99226
99561
|
model,
|
|
99227
99562
|
view,
|
|
99228
99563
|
ingressExternal: ingressExternalElements,
|
|
99564
|
+
slotOf: readingSlots(model, view, ingressExternalElements),
|
|
99229
99565
|
compact,
|
|
99230
99566
|
numbered,
|
|
99231
99567
|
fonts: {
|
|
@@ -99246,18 +99582,25 @@ async function layout(model, view) {
|
|
|
99246
99582
|
const nodes = walkedNodes.map((walked) => walked.node);
|
|
99247
99583
|
for (const walked of walkedNodes) origins[walked.id] = { x: walked.x, y: walked.y };
|
|
99248
99584
|
const edges = collectSceneEdges(result2, origins, numbered, edgeFontSize);
|
|
99249
|
-
const
|
|
99585
|
+
const pinnedFlows = new Map(
|
|
99586
|
+
model.flows.filter((flow) => flow.fromSide || flow.toSide).map((flow) => [flow.id, { start: !!flow.fromSide, end: !!flow.toSide }])
|
|
99587
|
+
);
|
|
99588
|
+
for (const edge of edges) {
|
|
99589
|
+
const pinned = pinnedFlows.get(edge.id);
|
|
99590
|
+
if (pinned) edge.pinned = { ...pinned };
|
|
99591
|
+
}
|
|
99592
|
+
const scene = {
|
|
99250
99593
|
width: Math.ceil(result2.width),
|
|
99251
99594
|
height: Math.ceil(result2.height),
|
|
99252
99595
|
nodes,
|
|
99253
99596
|
edges,
|
|
99254
99597
|
layoutMs: layoutMs2
|
|
99255
99598
|
};
|
|
99256
|
-
runGeometryPasses(
|
|
99599
|
+
runGeometryPasses(scene, model, {
|
|
99257
99600
|
numbered,
|
|
99258
99601
|
sideways: disposition === "page" || disposition === "tall"
|
|
99259
99602
|
});
|
|
99260
|
-
return
|
|
99603
|
+
return scene;
|
|
99261
99604
|
};
|
|
99262
99605
|
const startTime = Date.now();
|
|
99263
99606
|
let result;
|
|
@@ -99303,28 +99646,37 @@ async function layout(model, view) {
|
|
|
99303
99646
|
result = await elk.layout(makeGraph(winnerDirection));
|
|
99304
99647
|
}
|
|
99305
99648
|
const layoutMs = Date.now() - startTime;
|
|
99306
|
-
const
|
|
99307
|
-
const away = attachAwayOf(scene, model);
|
|
99649
|
+
const base = sceneFromResult(result, layoutMs);
|
|
99308
99650
|
const skipPortPass = !!globalThis.process?.env?.CAIRN_NO_PORT_PASS;
|
|
99309
|
-
if (
|
|
99310
|
-
|
|
99311
|
-
const
|
|
99312
|
-
|
|
99313
|
-
const
|
|
99314
|
-
|
|
99315
|
-
|
|
99316
|
-
|
|
99317
|
-
|
|
99318
|
-
|
|
99319
|
-
|
|
99320
|
-
);
|
|
99321
|
-
|
|
99322
|
-
|
|
99323
|
-
|
|
99324
|
-
|
|
99325
|
-
|
|
99326
|
-
|
|
99651
|
+
if (skipPortPass) return base;
|
|
99652
|
+
const layoutProfile = (candidate) => {
|
|
99653
|
+
const everyEdge = new Set(candidate.edges.map((edge) => edge.id));
|
|
99654
|
+
const profile = inspect(candidate, titleBoxesOf(candidate, model)).local(everyEdge, /* @__PURE__ */ new Map());
|
|
99655
|
+
for (const [key, tier] of selectionExtras(candidate, model)) profile.set(key, tier);
|
|
99656
|
+
return profile;
|
|
99657
|
+
};
|
|
99658
|
+
const baseProfile = layoutProfile(base);
|
|
99659
|
+
let current = base;
|
|
99660
|
+
let best = null;
|
|
99661
|
+
for (let round = 0; round < PORT_PASS_ROUNDS; round++) {
|
|
99662
|
+
const flagged = attachAwayOf(current, model);
|
|
99663
|
+
if (!flagged.size) break;
|
|
99664
|
+
let candidate;
|
|
99665
|
+
try {
|
|
99666
|
+
const constrained = makeGraph(winnerDirection, winnerOptions);
|
|
99667
|
+
constrainPorts(constrained, current, flagged, model);
|
|
99668
|
+
const reresult = await elk.layout(constrained);
|
|
99669
|
+
candidate = sceneFromResult(reresult, Date.now() - startTime);
|
|
99670
|
+
} catch {
|
|
99671
|
+
break;
|
|
99672
|
+
}
|
|
99673
|
+
const tier = relayoutVerdict(baseProfile, layoutProfile(candidate));
|
|
99674
|
+
if (tier < 0) break;
|
|
99675
|
+
const scored = { scene: candidate, tier, detours: longDetourCount(candidate, model) };
|
|
99676
|
+
if (beatsRelayout(scored, best)) best = scored;
|
|
99677
|
+
current = candidate;
|
|
99327
99678
|
}
|
|
99679
|
+
return best?.scene ?? base;
|
|
99328
99680
|
}
|
|
99329
99681
|
|
|
99330
99682
|
// src/localization.ts
|
|
@@ -99980,7 +100332,7 @@ function createEdgePainter(paint) {
|
|
|
99980
100332
|
const flowStyle = flow?.style;
|
|
99981
100333
|
const color = flowColorOf(flow);
|
|
99982
100334
|
const headColor = style.flowColor === "by-source" ? color : defaultEdgeColor;
|
|
99983
|
-
const dash = dashArray(flowStyle?.stroke?.style ?? style.flowStroke.style);
|
|
100335
|
+
const dash = dashArray(flowStyle?.stroke?.style ?? flow?.lineStyle ?? style.flowStroke.style);
|
|
99984
100336
|
const width = flowStyle?.stroke?.width ?? style.flowStroke.width;
|
|
99985
100337
|
return `<path d="${edgePath(edge.pts)}" fill="none" stroke="${escAttr(color)}" stroke-width="${width}"${dash ? ` stroke-dasharray="${dash}"` : ""} marker-end="url(#${markerName(headColor)})"/>
|
|
99986
100338
|
`;
|
|
@@ -100311,6 +100663,7 @@ async function compile(source, options) {
|
|
|
100311
100663
|
const view = views[model.type];
|
|
100312
100664
|
const matrix = options?.matrix ? buildFlowMatrix(model, view) : null;
|
|
100313
100665
|
const scene = await layout(model, view);
|
|
100666
|
+
diags.push(...attachSideDiagnostics(scene, model));
|
|
100314
100667
|
const { svg, overlapsAfter } = render(model, view, scene);
|
|
100315
100668
|
return {
|
|
100316
100669
|
svg,
|
|
@@ -100326,7 +100679,7 @@ async function compile(source, options) {
|
|
|
100326
100679
|
}
|
|
100327
100680
|
|
|
100328
100681
|
// src/api.ts
|
|
100329
|
-
var version = true ? "1.0.0-
|
|
100682
|
+
var version = true ? "1.0.0-RC13" : pkg.version;
|
|
100330
100683
|
|
|
100331
100684
|
// src/playground.ts
|
|
100332
100685
|
var ElkClass = import_elk_bundled.default;
|