@r0kshan/cairn 1.0.0-RC14 → 1.0.0-RC15
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/bin/cairn.mjs +405 -132
- package/dist/cairn.mjs +395 -125
- package/package.json +1 -1
package/bin/cairn.mjs
CHANGED
|
@@ -92340,11 +92340,12 @@ var init_lexer = __esm({
|
|
|
92340
92340
|
});
|
|
92341
92341
|
|
|
92342
92342
|
// src/models/ast.ts
|
|
92343
|
-
var ATTACH_SIDES, defaultDiagramStyle, explanations;
|
|
92343
|
+
var ATTACH_SIDES, ATTACH_ROLES, defaultDiagramStyle, explanations;
|
|
92344
92344
|
var init_ast = __esm({
|
|
92345
92345
|
"src/models/ast.ts"() {
|
|
92346
92346
|
"use strict";
|
|
92347
92347
|
ATTACH_SIDES = ["left", "right", "top", "bottom"];
|
|
92348
|
+
ATTACH_ROLES = ["producer", "consumer"];
|
|
92348
92349
|
defaultDiagramStyle = () => ({
|
|
92349
92350
|
crossingHops: true,
|
|
92350
92351
|
compact: false,
|
|
@@ -92384,7 +92385,9 @@ var init_ast = __esm({
|
|
|
92384
92385
|
E0215: "A deployed application (app-instance) must sit on a server or in a zone. It shows WHERE an application runs, without its internal detail (C4 deployment convention).",
|
|
92385
92386
|
E0216: "Network zones belong to a site (or nest inside a larger zone): DMZ and LAN only mean something relative to a perimeter.",
|
|
92386
92387
|
W0540: 'C4 container-diagram practice: inter-process relationships should be labelled with their technology/protocol ("the how, not just the what"). Human/actor interactions are exempt. Add `(API_REST, JSON)` after the label, or ignore if the diagram is intentionally functional-only.',
|
|
92387
|
-
E0223: "Unknown
|
|
92388
|
+
E0223: "Unknown endpoint suffix. A flow endpoint may name the side it attaches to \u2014 `APP.right -> DB.left` \u2014 using the diagram as it is read: `left`, `right`, `top`, `bottom`. It may instead name its role towards a queue at the other end: `producer` or `consumer`. A declared side is a hint, not a guarantee: one the layout cannot reach is dropped with a W0570 warning rather than forced.",
|
|
92389
|
+
E0224: "An endpoint role says what this element does with the *queue* at the other end of the flow (`CAPTURE.producer -> EVENTS`), so the other end has to be one. Point the flow at a queue, or drop the role \u2014 between two ordinary elements the arrow already says everything a role would.",
|
|
92390
|
+
E0225: "A flow's queue endpoint carries both a declared side and a role at the other end, and they are two answers to one question: `producer` already means the queue's left cap and `consumer` its right. Keep the role and drop the side, or keep the side and drop the role.",
|
|
92388
92391
|
E0240: "The infrastructure view requires every flow to carry its protocol (and port if relevant): the flow matrix is the primary output of this view. Add `(HTTPS/443)` after the label.",
|
|
92389
92392
|
E0221: 'Unknown business-object reference: a flow carries `[AN_ID]` that no `business-object` declaration defines. Declare it once (`business-object ID "Name" "description"`) so the registry and the chips stay consistent.',
|
|
92390
92393
|
E0222: "Business objects are a logical-view concept (what data circulates between functional blocks). They are not part of the application or infrastructure views \u2014 model the exchange with the flow label and technical tail instead. Remove the `business-object` declaration and its `[refs]`, or switch the diagram to `logical`.",
|
|
@@ -93136,56 +93139,60 @@ function parseElement(p, sourceToken, parent) {
|
|
|
93136
93139
|
}
|
|
93137
93140
|
(parent ? parent.children : model.elements).push(element);
|
|
93138
93141
|
}
|
|
93139
|
-
function splitEndpoint(token, flow,
|
|
93142
|
+
function splitEndpoint(token, flow, end) {
|
|
93140
93143
|
const dot = token.text.lastIndexOf(".");
|
|
93141
93144
|
if (dot <= 0 || dot === token.text.length - 1) return null;
|
|
93142
93145
|
const base = token.text.slice(0, dot);
|
|
93143
|
-
const
|
|
93146
|
+
const suffix = token.text.slice(dot + 1);
|
|
93144
93147
|
return {
|
|
93145
93148
|
flow,
|
|
93146
|
-
|
|
93149
|
+
end,
|
|
93147
93150
|
raw: token.text,
|
|
93148
93151
|
rawSpan: token.span,
|
|
93149
93152
|
base,
|
|
93150
93153
|
baseSpan: { line: token.span.line, col: token.span.col, len: base.length },
|
|
93151
|
-
|
|
93152
|
-
|
|
93154
|
+
suffix,
|
|
93155
|
+
suffixSpan: { line: token.span.line, col: token.span.col + dot + 1, len: suffix.length }
|
|
93153
93156
|
};
|
|
93154
93157
|
}
|
|
93155
|
-
function
|
|
93158
|
+
function resolveEndpointSuffixes(splits, model, diagnostics) {
|
|
93156
93159
|
for (const split of splits) {
|
|
93160
|
+
const isSide = ATTACH_SIDES.includes(split.suffix);
|
|
93161
|
+
const isRole = ATTACH_ROLES.includes(split.suffix);
|
|
93157
93162
|
if (model.index.has(split.raw)) {
|
|
93158
|
-
if (
|
|
93163
|
+
if (isSide || isRole)
|
|
93159
93164
|
diagnostics.push({
|
|
93160
93165
|
code: "W0571",
|
|
93161
93166
|
severity: "warning",
|
|
93162
|
-
message: `\`${split.raw}\` is a declared element, so \`.${split.
|
|
93167
|
+
message: `\`${split.raw}\` is a declared element, so \`.${split.suffix}\` is not read as ${isSide ? "an attachment side" : "a role"}`,
|
|
93163
93168
|
span: split.rawSpan,
|
|
93164
93169
|
note: "a declared id always wins over the `ID.side` reading",
|
|
93165
|
-
help: `rename the element if you meant to attach the flow to the ${split.
|
|
93170
|
+
help: isSide ? `rename the element if you meant to attach the flow to the ${split.suffix} side of \`${split.base}\`` : `rename the element if you meant \`${split.base}\` to be the ${split.suffix} of the queue at the other end`
|
|
93166
93171
|
});
|
|
93167
93172
|
continue;
|
|
93168
93173
|
}
|
|
93169
93174
|
if (!model.index.has(split.base)) continue;
|
|
93170
|
-
|
|
93171
|
-
if (!known)
|
|
93175
|
+
if (!isSide && !isRole)
|
|
93172
93176
|
diagnostics.push({
|
|
93173
93177
|
code: "E0223",
|
|
93174
93178
|
severity: "error",
|
|
93175
|
-
message: `unknown
|
|
93176
|
-
span: split.
|
|
93177
|
-
note: "
|
|
93178
|
-
help: "use `left`, `right`, `top` or `bottom
|
|
93179
|
+
message: `unknown endpoint suffix \`${split.suffix}\``,
|
|
93180
|
+
span: split.suffixSpan,
|
|
93181
|
+
note: "an endpoint names either the side it attaches to, as the diagram is read, or its role towards a queue",
|
|
93182
|
+
help: "use `left`, `right`, `top` or `bottom` \u2014 `APP.right -> DB.left` \u2014 or `producer` / `consumer`, e.g. `CAPTURE.producer -> EVENTS`"
|
|
93179
93183
|
});
|
|
93180
|
-
const side =
|
|
93181
|
-
|
|
93184
|
+
const side = isSide ? { value: split.suffix, span: split.suffixSpan } : void 0;
|
|
93185
|
+
const role = isRole ? { value: split.suffix, span: split.suffixSpan } : void 0;
|
|
93186
|
+
if (split.end === "from") {
|
|
93182
93187
|
split.flow.from = split.base;
|
|
93183
93188
|
split.flow.fromSpan = split.baseSpan;
|
|
93184
93189
|
split.flow.fromSide = side;
|
|
93190
|
+
split.flow.fromRole = role;
|
|
93185
93191
|
} else {
|
|
93186
93192
|
split.flow.to = split.base;
|
|
93187
93193
|
split.flow.toSpan = split.baseSpan;
|
|
93188
93194
|
split.flow.toSide = side;
|
|
93195
|
+
split.flow.toRole = role;
|
|
93189
93196
|
}
|
|
93190
93197
|
}
|
|
93191
93198
|
}
|
|
@@ -93462,7 +93469,7 @@ function parse(src) {
|
|
|
93462
93469
|
for (const [id, element] of indexElementsById(model.elements)) {
|
|
93463
93470
|
if (!model.index.has(id)) model.index.set(id, element);
|
|
93464
93471
|
}
|
|
93465
|
-
|
|
93472
|
+
resolveEndpointSuffixes(endpointSplits, model, diagnostics);
|
|
93466
93473
|
return { model, diags: diagnostics };
|
|
93467
93474
|
}
|
|
93468
93475
|
function applyStyleEntry(entry) {
|
|
@@ -93694,10 +93701,11 @@ var init_parser = __esm({
|
|
|
93694
93701
|
});
|
|
93695
93702
|
|
|
93696
93703
|
// src/views.ts
|
|
93697
|
-
var logicalView, applicationView, infrastructureView, views;
|
|
93704
|
+
var LINE_STYLES2, logicalView, applicationView, infrastructureView, views;
|
|
93698
93705
|
var init_views = __esm({
|
|
93699
93706
|
"src/views.ts"() {
|
|
93700
93707
|
"use strict";
|
|
93708
|
+
LINE_STYLES2 = ["solid", "dashed", "dotted"];
|
|
93701
93709
|
logicalView = {
|
|
93702
93710
|
name: "logical",
|
|
93703
93711
|
laneKinds: ["external"],
|
|
@@ -93729,6 +93737,16 @@ var init_views = __esm({
|
|
|
93729
93737
|
},
|
|
93730
93738
|
legendFlowLabel: "Functional flow (label = exchanged data)",
|
|
93731
93739
|
legendFlowLabelFr: "Flux fonctionnel (libell\xE9 = donn\xE9es \xE9chang\xE9es)",
|
|
93740
|
+
legendLineStyles: {
|
|
93741
|
+
solid: "Direct exchange",
|
|
93742
|
+
dashed: "Asynchronous or event-driven exchange",
|
|
93743
|
+
dotted: "Dependency \u2014 no data exchanged"
|
|
93744
|
+
},
|
|
93745
|
+
legendLineStylesFr: {
|
|
93746
|
+
solid: "\xC9change direct",
|
|
93747
|
+
dashed: "\xC9change asynchrone ou \xE9v\xE9nementiel",
|
|
93748
|
+
dotted: "D\xE9pendance \u2014 aucune donn\xE9e \xE9chang\xE9e"
|
|
93749
|
+
},
|
|
93732
93750
|
flowTechRequired: null,
|
|
93733
93751
|
flowTechRecommended: null,
|
|
93734
93752
|
businessObjects: true,
|
|
@@ -93824,6 +93842,7 @@ var init_views = __esm({
|
|
|
93824
93842
|
applicationView = {
|
|
93825
93843
|
name: "application",
|
|
93826
93844
|
laneKinds: ["external"],
|
|
93845
|
+
hubKinds: ["queue"],
|
|
93827
93846
|
kinds: [
|
|
93828
93847
|
"actor-group",
|
|
93829
93848
|
"actor",
|
|
@@ -93900,6 +93919,16 @@ var init_views = __esm({
|
|
|
93900
93919
|
},
|
|
93901
93920
|
legendFlowLabel: "Application flow \u2014 (protocol, format) under the label",
|
|
93902
93921
|
legendFlowLabelFr: "Flux applicatif \u2014 (protocole, format) sous le libell\xE9",
|
|
93922
|
+
legendLineStyles: {
|
|
93923
|
+
solid: "Synchronous call (request / response)",
|
|
93924
|
+
dashed: "Asynchronous exchange (message, event)",
|
|
93925
|
+
dotted: "Dependency \u2014 no direct call"
|
|
93926
|
+
},
|
|
93927
|
+
legendLineStylesFr: {
|
|
93928
|
+
solid: "Appel synchrone (requ\xEAte / r\xE9ponse)",
|
|
93929
|
+
dashed: "\xC9change asynchrone (message, \xE9v\xE9nement)",
|
|
93930
|
+
dotted: "D\xE9pendance \u2014 sans appel direct"
|
|
93931
|
+
},
|
|
93903
93932
|
flowLabelRequired: null,
|
|
93904
93933
|
flowTechRequired: null,
|
|
93905
93934
|
flowTechRecommended: {
|
|
@@ -94023,6 +94052,7 @@ var init_views = __esm({
|
|
|
94023
94052
|
infrastructureView = {
|
|
94024
94053
|
name: "infrastructure",
|
|
94025
94054
|
laneKinds: ["external"],
|
|
94055
|
+
hubKinds: ["queue"],
|
|
94026
94056
|
kinds: [
|
|
94027
94057
|
"actor",
|
|
94028
94058
|
"device",
|
|
@@ -94085,6 +94115,16 @@ var init_views = __esm({
|
|
|
94085
94115
|
},
|
|
94086
94116
|
legendFlowLabel: "Technical flow (protocol, port)",
|
|
94087
94117
|
legendFlowLabelFr: "Flux technique (protocole, port)",
|
|
94118
|
+
legendLineStyles: {
|
|
94119
|
+
solid: "Permanent link \u2014 nominal traffic",
|
|
94120
|
+
dashed: "Asynchronous or intermittent link",
|
|
94121
|
+
dotted: "Dependency \u2014 outside nominal traffic"
|
|
94122
|
+
},
|
|
94123
|
+
legendLineStylesFr: {
|
|
94124
|
+
solid: "Lien permanent \u2014 trafic nominal",
|
|
94125
|
+
dashed: "Lien asynchrone ou intermittent",
|
|
94126
|
+
dotted: "D\xE9pendance \u2014 hors trafic nominal"
|
|
94127
|
+
},
|
|
94088
94128
|
flowLabelRequired: null,
|
|
94089
94129
|
flowTechRecommended: null,
|
|
94090
94130
|
flowTechRequired: {
|
|
@@ -94423,6 +94463,7 @@ function validate(model) {
|
|
|
94423
94463
|
...checkMissingLabels(elements),
|
|
94424
94464
|
...checkNesting(elements, view),
|
|
94425
94465
|
...checkFlows(model, view),
|
|
94466
|
+
...checkEndpointRoles(model, view),
|
|
94426
94467
|
...checkLogos(elements, view),
|
|
94427
94468
|
...checkBusinessObjects(model, view),
|
|
94428
94469
|
...checkMinimumCounts(elements, model, view),
|
|
@@ -94541,6 +94582,42 @@ function checkNesting(elements, view) {
|
|
|
94541
94582
|
}
|
|
94542
94583
|
return diagnostics;
|
|
94543
94584
|
}
|
|
94585
|
+
function checkEndpointRoles(model, view) {
|
|
94586
|
+
const diagnostics = [];
|
|
94587
|
+
const hubKinds = new Set(view.hubKinds ?? []);
|
|
94588
|
+
const isHub = (id) => hubKinds.has(model.index.get(id)?.kind ?? "");
|
|
94589
|
+
for (const flow of model.flows) {
|
|
94590
|
+
for (const [role, hubId, hubSide] of [
|
|
94591
|
+
[flow.fromRole, flow.to, flow.toSide],
|
|
94592
|
+
[flow.toRole, flow.from, flow.fromSide]
|
|
94593
|
+
]) {
|
|
94594
|
+
if (!role) continue;
|
|
94595
|
+
if (!model.index.has(hubId)) continue;
|
|
94596
|
+
if (!isHub(hubId)) {
|
|
94597
|
+
const kinds = [...hubKinds];
|
|
94598
|
+
diagnostics.push({
|
|
94599
|
+
code: "E0224",
|
|
94600
|
+
severity: "error",
|
|
94601
|
+
message: `\`${role.value}\` needs a ${kinds.join(" or ") || "queue"} at the other end of the flow`,
|
|
94602
|
+
span: role.span,
|
|
94603
|
+
note: `\`${hubId}\` is ${model.index.get(hubId)?.kind ? `a \`${model.index.get(hubId).kind}\`` : "not one"}`,
|
|
94604
|
+
help: "a role says what this element does with a queue \u2014 between two ordinary elements the arrow already says it"
|
|
94605
|
+
});
|
|
94606
|
+
continue;
|
|
94607
|
+
}
|
|
94608
|
+
if (hubSide)
|
|
94609
|
+
diagnostics.push({
|
|
94610
|
+
code: "E0225",
|
|
94611
|
+
severity: "error",
|
|
94612
|
+
message: `\`${hubId}.${hubSide.value}\` and \`${role.value}\` both decide where this flow meets the queue`,
|
|
94613
|
+
span: hubSide.span,
|
|
94614
|
+
note: "`producer` means the queue's left cap and `consumer` its right",
|
|
94615
|
+
help: "keep the role and drop the side, or keep the side and drop the role"
|
|
94616
|
+
});
|
|
94617
|
+
}
|
|
94618
|
+
}
|
|
94619
|
+
return diagnostics;
|
|
94620
|
+
}
|
|
94544
94621
|
function checkFlows(model, view) {
|
|
94545
94622
|
const diagnostics = [];
|
|
94546
94623
|
const isActor = (id) => {
|
|
@@ -96252,13 +96329,34 @@ function compactVertical(scene) {
|
|
|
96252
96329
|
}
|
|
96253
96330
|
scene.height = Math.ceil(heightAfter + bottomMargin);
|
|
96254
96331
|
}
|
|
96255
|
-
|
|
96332
|
+
function fitCanvas(scene) {
|
|
96333
|
+
let maxX = 0;
|
|
96334
|
+
let maxY = 0;
|
|
96335
|
+
for (const node of scene.nodes) {
|
|
96336
|
+
maxX = Math.max(maxX, node.x + node.width);
|
|
96337
|
+
maxY = Math.max(maxY, node.y + node.height);
|
|
96338
|
+
}
|
|
96339
|
+
for (const edge of scene.edges) {
|
|
96340
|
+
for (const point of edge.pts) {
|
|
96341
|
+
maxX = Math.max(maxX, point.x);
|
|
96342
|
+
maxY = Math.max(maxY, point.y);
|
|
96343
|
+
}
|
|
96344
|
+
for (const label of edge.labels) {
|
|
96345
|
+
maxX = Math.max(maxX, label.x + label.width);
|
|
96346
|
+
maxY = Math.max(maxY, label.y + label.height);
|
|
96347
|
+
}
|
|
96348
|
+
}
|
|
96349
|
+
scene.width = Math.max(scene.width, Math.ceil(maxX) + CANVAS_MARGIN);
|
|
96350
|
+
scene.height = Math.max(scene.height, Math.ceil(maxY) + CANVAS_MARGIN);
|
|
96351
|
+
}
|
|
96352
|
+
var KEEP_GAP, EDGE_MARGIN, MIN_SAVING, CANVAS_MARGIN;
|
|
96256
96353
|
var init_compact = __esm({
|
|
96257
96354
|
"src/compact.ts"() {
|
|
96258
96355
|
"use strict";
|
|
96259
96356
|
KEEP_GAP = 14;
|
|
96260
96357
|
EDGE_MARGIN = 22;
|
|
96261
96358
|
MIN_SAVING = 4;
|
|
96359
|
+
CANVAS_MARGIN = 10;
|
|
96262
96360
|
}
|
|
96263
96361
|
});
|
|
96264
96362
|
|
|
@@ -98936,8 +99034,8 @@ function optimiseRoutes(scene, titleBoxes = [], folded = false) {
|
|
|
98936
99034
|
const subject = { ends, edge };
|
|
98937
99035
|
const out = [];
|
|
98938
99036
|
const seen = /* @__PURE__ */ new Set();
|
|
98939
|
-
const aSides = edge
|
|
98940
|
-
const bSides = edge
|
|
99037
|
+
const aSides = sideFixed(edge, "start") ? [ends[0].side] : SIDES;
|
|
99038
|
+
const bSides = sideFixed(edge, "end") ? [ends[1].side] : SIDES;
|
|
98941
99039
|
for (const aSide of aSides)
|
|
98942
99040
|
for (const bSide of bSides) {
|
|
98943
99041
|
const sides = { a: aSide, b: bSide };
|
|
@@ -98996,12 +99094,13 @@ function optimiseRoutes(scene, titleBoxes = [], folded = false) {
|
|
|
98996
99094
|
}
|
|
98997
99095
|
for (const edge of scene.edges) if (edge.pts.length >= 2) enforceOrthogonal(edge);
|
|
98998
99096
|
}
|
|
98999
|
-
var ORTHOGONAL_EPSILON, MIN_HUG_SPAN, CHANNEL_INSET, SNAP, JOG_SNAP, SIDE_INSET, MIN_SIDE_INSET, FAN_REACH2, SEAT_OFFSETS, segmentsCross, segmentLength, wouldMerge, seatWithAlong, crossingsOf, SEAT_GRID, SEAT_CLEAR, CHANNEL_MARGIN, CHANNEL_STEP, CHANNEL_CLEAR;
|
|
99097
|
+
var sideFixed, ORTHOGONAL_EPSILON, MIN_HUG_SPAN, CHANNEL_INSET, SNAP, JOG_SNAP, SIDE_INSET, MIN_SIDE_INSET, FAN_REACH2, SEAT_OFFSETS, segmentsCross, segmentLength, wouldMerge, seatWithAlong, crossingsOf, SEAT_GRID, SEAT_CLEAR, CHANNEL_MARGIN, CHANNEL_STEP, CHANNEL_CLEAR;
|
|
99000
99098
|
var init_edge_tidy = __esm({
|
|
99001
99099
|
"src/edge-tidy.ts"() {
|
|
99002
99100
|
"use strict";
|
|
99003
99101
|
init_readability();
|
|
99004
99102
|
init_geometry();
|
|
99103
|
+
sideFixed = (edge, end) => !!(edge.pinned?.[end] || edge.hubSided?.[end]);
|
|
99005
99104
|
ORTHOGONAL_EPSILON = 0.5;
|
|
99006
99105
|
MIN_HUG_SPAN = 24;
|
|
99007
99106
|
CHANNEL_INSET = 24;
|
|
@@ -99660,8 +99759,7 @@ function constrainPorts(graph, scene, flagged, model) {
|
|
|
99660
99759
|
}
|
|
99661
99760
|
function elkPartitionOf(element, index, view, ingressExternal) {
|
|
99662
99761
|
if (!view.partitionByOrder) return view.partitions[element.kind] ?? 1;
|
|
99663
|
-
if ((view.ingressKinds ?? DEFAULT_INGRESS_KINDS).includes(element.kind))
|
|
99664
|
-
return INGRESS_PARTITION;
|
|
99762
|
+
if ((view.ingressKinds ?? DEFAULT_INGRESS_KINDS).includes(element.kind)) return INGRESS_PARTITION;
|
|
99665
99763
|
if (element.kind === "external")
|
|
99666
99764
|
return ingressExternal.has(element.id) ? INGRESS_PARTITION : EGRESS_PARTITION;
|
|
99667
99765
|
if (view.partitions[element.kind] !== void 0) return 90 + view.partitions[element.kind];
|
|
@@ -99710,13 +99808,15 @@ function readingSlots(model, view, ingressExternal) {
|
|
|
99710
99808
|
}
|
|
99711
99809
|
return slotOf;
|
|
99712
99810
|
}
|
|
99811
|
+
function elkEnds(flow) {
|
|
99812
|
+
return laidOutReversed(flow) ? { sources: [flow.to], targets: [flow.from] } : { sources: [flow.from], targets: [flow.to] };
|
|
99813
|
+
}
|
|
99713
99814
|
function elkFlowEdge(flow, ctx, labelWrap) {
|
|
99714
99815
|
const { compact, numbered, fonts, businessObjectName } = ctx;
|
|
99715
99816
|
if (numbered)
|
|
99716
99817
|
return {
|
|
99717
99818
|
id: flow.id,
|
|
99718
|
-
|
|
99719
|
-
targets: [flow.to],
|
|
99819
|
+
...elkEnds(flow),
|
|
99720
99820
|
labels: [
|
|
99721
99821
|
{
|
|
99722
99822
|
text: String(parseInt(flow.id.slice(1), 10)),
|
|
@@ -99741,13 +99841,35 @@ function elkFlowEdge(flow, ctx, labelWrap) {
|
|
|
99741
99841
|
});
|
|
99742
99842
|
return {
|
|
99743
99843
|
id: flow.id,
|
|
99744
|
-
|
|
99745
|
-
targets: [flow.to],
|
|
99844
|
+
...elkEnds(flow),
|
|
99746
99845
|
labels: text || chips.length ? [{ text, ...labelBox }] : []
|
|
99747
99846
|
};
|
|
99748
99847
|
}
|
|
99749
|
-
function
|
|
99750
|
-
|
|
99848
|
+
function laidOutReversed(flow) {
|
|
99849
|
+
return flow.fromRole?.value === "consumer" || flow.toRole?.value === "producer";
|
|
99850
|
+
}
|
|
99851
|
+
function roleCap(role) {
|
|
99852
|
+
if (!role) return null;
|
|
99853
|
+
return role.value === "producer" ? "left" : "right";
|
|
99854
|
+
}
|
|
99855
|
+
function hubFlowSides(model, view) {
|
|
99856
|
+
const derived = /* @__PURE__ */ new Map();
|
|
99857
|
+
const hubKinds = new Set(view.hubKinds ?? []);
|
|
99858
|
+
if (!hubKinds.size) return derived;
|
|
99859
|
+
const hubs = new Set(
|
|
99860
|
+
indexElementsById(model.elements).filter(([, element]) => hubKinds.has(element.kind)).map(([id]) => id)
|
|
99861
|
+
);
|
|
99862
|
+
if (!hubs.size) return derived;
|
|
99863
|
+
for (const flow of model.flows) {
|
|
99864
|
+
const sides = {};
|
|
99865
|
+
if (hubs.has(flow.to) && !flow.toSide) sides.to = roleCap(flow.fromRole) ?? "left";
|
|
99866
|
+
if (hubs.has(flow.from) && !flow.fromSide) sides.from = roleCap(flow.toRole) ?? "right";
|
|
99867
|
+
if (sides.to || sides.from) derived.set(flow.id, sides);
|
|
99868
|
+
}
|
|
99869
|
+
return derived;
|
|
99870
|
+
}
|
|
99871
|
+
function applyDeclaredPorts(graph, model, derived) {
|
|
99872
|
+
const pinned = model.flows.filter((flow) => flow.fromSide || flow.toSide || derived.has(flow.id));
|
|
99751
99873
|
if (!pinned.length) return;
|
|
99752
99874
|
const elkById = /* @__PURE__ */ new Map();
|
|
99753
99875
|
const register = (node) => {
|
|
@@ -99758,14 +99880,15 @@ function applyDeclaredPorts(graph, model) {
|
|
|
99758
99880
|
for (const flow of pinned) {
|
|
99759
99881
|
const elkEdge = (graph.edges ?? []).find((edge) => edge.id === flow.id);
|
|
99760
99882
|
if (!elkEdge) continue;
|
|
99883
|
+
const hub = derived.get(flow.id);
|
|
99761
99884
|
for (const [role, declared, nodeId] of [
|
|
99762
|
-
["out", flow.fromSide, flow.from],
|
|
99763
|
-
["in", flow.toSide, flow.to]
|
|
99885
|
+
["out", flow.fromSide ?? sideRequest(hub?.from), flow.from],
|
|
99886
|
+
["in", flow.toSide ?? sideRequest(hub?.to), flow.to]
|
|
99764
99887
|
]) {
|
|
99765
99888
|
if (!declared) continue;
|
|
99766
99889
|
const elkNode = elkById.get(nodeId);
|
|
99767
99890
|
if (!elkNode) continue;
|
|
99768
|
-
const portId = `${flow.id}#${role}`;
|
|
99891
|
+
const portId = `${flow.id}#${role === "out" !== laidOutReversed(flow) ? "out" : "in"}`;
|
|
99769
99892
|
elkNode.ports = [
|
|
99770
99893
|
...elkNode.ports ?? [],
|
|
99771
99894
|
{
|
|
@@ -99776,7 +99899,7 @@ function applyDeclaredPorts(graph, model) {
|
|
|
99776
99899
|
}
|
|
99777
99900
|
];
|
|
99778
99901
|
elkNode.layoutOptions = { ...elkNode.layoutOptions, "elk.portConstraints": "FIXED_SIDE" };
|
|
99779
|
-
if (role === "out") elkEdge.sources = [portId];
|
|
99902
|
+
if (role === "out" !== laidOutReversed(flow)) elkEdge.sources = [portId];
|
|
99780
99903
|
else elkEdge.targets = [portId];
|
|
99781
99904
|
}
|
|
99782
99905
|
}
|
|
@@ -99838,7 +99961,7 @@ function buildElkGraph(ctx, direction, options) {
|
|
|
99838
99961
|
"elk.edgeRouting": "ORTHOGONAL",
|
|
99839
99962
|
"elk.partitioning.activate": "true",
|
|
99840
99963
|
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
|
99841
|
-
"elk.layered.compaction.postCompaction.strategy": "EDGE_LENGTH",
|
|
99964
|
+
"elk.layered.compaction.postCompaction.strategy": options?.postCompaction ?? "EDGE_LENGTH",
|
|
99842
99965
|
"elk.layered.feedbackEdges": "true",
|
|
99843
99966
|
"elk.layered.thoroughness": "30",
|
|
99844
99967
|
"elk.separateConnectedComponents": "false",
|
|
@@ -99884,7 +100007,11 @@ function buildElkGraph(ctx, direction, options) {
|
|
|
99884
100007
|
}),
|
|
99885
100008
|
edges: model.flows.map((flow) => elkFlowEdge(flow, ctx, options?.labelWrap))
|
|
99886
100009
|
};
|
|
99887
|
-
applyDeclaredPorts(
|
|
100010
|
+
applyDeclaredPorts(
|
|
100011
|
+
graph,
|
|
100012
|
+
model,
|
|
100013
|
+
options?.hubPorts === false ? /* @__PURE__ */ new Map() : hubFlowSides(model, view)
|
|
100014
|
+
);
|
|
99888
100015
|
return graph;
|
|
99889
100016
|
}
|
|
99890
100017
|
function recordRepairs(scene, routesBefore) {
|
|
@@ -99953,7 +100080,9 @@ function snapLanes(scene, laneOf, axis) {
|
|
|
99953
100080
|
if (!laneOf.size) return;
|
|
99954
100081
|
const leaves = leafBoxesOf(scene);
|
|
99955
100082
|
const tilted = (edges) => edges.reduce(
|
|
99956
|
-
(total, edge) => total + edge.pts.filter(
|
|
100083
|
+
(total, edge) => total + edge.pts.filter(
|
|
100084
|
+
(point, i) => i > 0 && Math.abs(point.x - edge.pts[i - 1].x) >= 0.5 && Math.abs(point.y - edge.pts[i - 1].y) >= 0.5
|
|
100085
|
+
).length,
|
|
99957
100086
|
0
|
|
99958
100087
|
);
|
|
99959
100088
|
const touching = /* @__PURE__ */ new Map();
|
|
@@ -99997,7 +100126,9 @@ function snapLanes(scene, laneOf, axis) {
|
|
|
99997
100126
|
point[axis] += delta;
|
|
99998
100127
|
const broke = crossingsAround(scene, edges) > crossingsBefore || shortJogs(edges) > jogsBefore || tilted(edges) > tiltsBefore || edges.some((edge) => crossesLeaf(edge, leaves, attachedTo.get(edge.id) ?? /* @__PURE__ */ new Set())) || // Containers included: a lane member is top-level, so it has no
|
|
99999
100128
|
// legitimate container ancestor and may not land on one.
|
|
100000
|
-
scene.nodes.some(
|
|
100129
|
+
scene.nodes.some(
|
|
100130
|
+
(other) => other !== node && !node.container && other.x < node.x + node.width && node.x < other.x + other.width && other.y < node.y + node.height && node.y < other.y + other.height
|
|
100131
|
+
);
|
|
100001
100132
|
if (broke) {
|
|
100002
100133
|
node[axis] = target - delta;
|
|
100003
100134
|
edges.forEach((edge, i) => {
|
|
@@ -100032,6 +100163,7 @@ function runGeometryPasses(scene, model, options) {
|
|
|
100032
100163
|
clearSideHugs(scene, settledTitles);
|
|
100033
100164
|
anchorFlowLabels(scene, settledTitles);
|
|
100034
100165
|
swapCrossingSiblingSeats(scene);
|
|
100166
|
+
fitCanvas(scene);
|
|
100035
100167
|
}
|
|
100036
100168
|
function laneAssignment(model, view) {
|
|
100037
100169
|
const laneOf = /* @__PURE__ */ new Map();
|
|
@@ -100089,6 +100221,38 @@ function laneAssignment(model, view) {
|
|
|
100089
100221
|
}
|
|
100090
100222
|
return laneOf;
|
|
100091
100223
|
}
|
|
100224
|
+
async function withHubPortFallback(elk, makeGraph, direction, options) {
|
|
100225
|
+
const run = async (spec) => await elk.layout(makeGraph(direction, spec));
|
|
100226
|
+
const rungs = options?.hubPorts === false ? [] : [
|
|
100227
|
+
{ ...options, postCompaction: "EDGE_LENGTH_CONSTRAINT_LOCKING" },
|
|
100228
|
+
{ ...options, postCompaction: "NONE" },
|
|
100229
|
+
{ ...options, hubPorts: false }
|
|
100230
|
+
];
|
|
100231
|
+
try {
|
|
100232
|
+
return { result: await run(options), options };
|
|
100233
|
+
} catch (error) {
|
|
100234
|
+
for (const [index, rung] of rungs.entries()) {
|
|
100235
|
+
try {
|
|
100236
|
+
return { result: await run(rung), options: rung };
|
|
100237
|
+
} catch (rungError) {
|
|
100238
|
+
if (index === rungs.length - 1) throw rungError;
|
|
100239
|
+
}
|
|
100240
|
+
}
|
|
100241
|
+
throw error;
|
|
100242
|
+
}
|
|
100243
|
+
}
|
|
100244
|
+
function markDeclaredTerminals(edges, model, view, hubPorts) {
|
|
100245
|
+
const pinnedFlows = new Map(
|
|
100246
|
+
model.flows.filter((flow) => flow.fromSide || flow.toSide).map((flow) => [flow.id, { start: !!flow.fromSide, end: !!flow.toSide }])
|
|
100247
|
+
);
|
|
100248
|
+
const hubSides = hubPorts ? hubFlowSides(model, view) : /* @__PURE__ */ new Map();
|
|
100249
|
+
for (const edge of edges) {
|
|
100250
|
+
const pinned = pinnedFlows.get(edge.id);
|
|
100251
|
+
if (pinned) edge.pinned = { ...pinned };
|
|
100252
|
+
const hub = hubSides.get(edge.id);
|
|
100253
|
+
if (hub) edge.hubSided = { start: !!hub.from, end: !!hub.to };
|
|
100254
|
+
}
|
|
100255
|
+
}
|
|
100092
100256
|
async function layout(model, view) {
|
|
100093
100257
|
const elk = await getElk();
|
|
100094
100258
|
const businessObjectName = new Map(model.businessObjects.map((bo) => [bo.id, bo.name]));
|
|
@@ -100120,7 +100284,7 @@ async function layout(model, view) {
|
|
|
100120
100284
|
};
|
|
100121
100285
|
const makeGraph = (direction, options) => buildElkGraph(graphContext, direction, options);
|
|
100122
100286
|
const kindOf = new Map(indexElementsById(model.elements));
|
|
100123
|
-
const sceneFromResult = (result2, layoutMs2, lanes = true) => {
|
|
100287
|
+
const sceneFromResult = (result2, layoutMs2, laidOutWith, lanes = true) => {
|
|
100124
100288
|
const origins = {
|
|
100125
100289
|
root: { x: 0, y: 0 }
|
|
100126
100290
|
};
|
|
@@ -100128,13 +100292,9 @@ async function layout(model, view) {
|
|
|
100128
100292
|
const nodes = walkedNodes.map((walked) => walked.node);
|
|
100129
100293
|
for (const walked of walkedNodes) origins[walked.id] = { x: walked.x, y: walked.y };
|
|
100130
100294
|
const edges = collectSceneEdges(result2, origins, numbered, edgeFontSize);
|
|
100131
|
-
const
|
|
100132
|
-
|
|
100133
|
-
);
|
|
100134
|
-
for (const edge of edges) {
|
|
100135
|
-
const pinned = pinnedFlows.get(edge.id);
|
|
100136
|
-
if (pinned) edge.pinned = { ...pinned };
|
|
100137
|
-
}
|
|
100295
|
+
const reversed = new Set(model.flows.filter(laidOutReversed).map((flow) => flow.id));
|
|
100296
|
+
for (const edge of edges) if (reversed.has(edge.id)) edge.pts.reverse();
|
|
100297
|
+
markDeclaredTerminals(edges, model, view, laidOutWith?.hubPorts !== false);
|
|
100138
100298
|
const scene = {
|
|
100139
100299
|
width: Math.ceil(result2.width),
|
|
100140
100300
|
height: Math.ceil(result2.height),
|
|
@@ -100151,6 +100311,7 @@ async function layout(model, view) {
|
|
|
100151
100311
|
};
|
|
100152
100312
|
const laneOf = laneAssignment(model, view);
|
|
100153
100313
|
const startTime = Date.now();
|
|
100314
|
+
const layoutGraph = (direction, options) => withHubPortFallback(elk, makeGraph, direction, options);
|
|
100154
100315
|
let result;
|
|
100155
100316
|
let winnerDirection;
|
|
100156
100317
|
let winnerOptions;
|
|
@@ -100161,9 +100322,10 @@ async function layout(model, view) {
|
|
|
100161
100322
|
{ direction: "RIGHT", options: { labelWrap: 14, tight: true } },
|
|
100162
100323
|
{ direction: "RIGHT", options: { labelWrap: 14, tight: true, minLayers: true } }
|
|
100163
100324
|
] : [{ direction: "DOWN" }, { direction: "DOWN", options: { labelWrap: 16 } }];
|
|
100164
|
-
const
|
|
100165
|
-
graphSpecs.map((spec) =>
|
|
100325
|
+
const laidOutSpecs = await Promise.all(
|
|
100326
|
+
graphSpecs.map((spec) => layoutGraph(spec.direction, spec.options))
|
|
100166
100327
|
);
|
|
100328
|
+
const candidates = laidOutSpecs.map((laidOut) => laidOut.result);
|
|
100167
100329
|
const preferWide = disposition === "slide";
|
|
100168
100330
|
const orientedLayouts = candidates.map((layoutResult, index) => ({ layoutResult, index })).filter(
|
|
100169
100331
|
({ layoutResult }) => preferWide ? layoutResult.width >= layoutResult.height : layoutResult.height >= layoutResult.width
|
|
@@ -100176,7 +100338,7 @@ async function layout(model, view) {
|
|
|
100176
100338
|
);
|
|
100177
100339
|
result = winner.layoutResult;
|
|
100178
100340
|
winnerDirection = graphSpecs[winner.index].direction;
|
|
100179
|
-
winnerOptions =
|
|
100341
|
+
winnerOptions = laidOutSpecs[winner.index].options;
|
|
100180
100342
|
if (disposition === "slide") {
|
|
100181
100343
|
const folded = await foldedLayout(model, view, elk);
|
|
100182
100344
|
if (folded && fitScore(result) >= fitScore(folded) * 1.1) {
|
|
@@ -100190,11 +100352,10 @@ async function layout(model, view) {
|
|
|
100190
100352
|
}
|
|
100191
100353
|
} else {
|
|
100192
100354
|
winnerDirection = disposition === "tall" ? "DOWN" : "RIGHT";
|
|
100193
|
-
winnerOptions =
|
|
100194
|
-
result = await elk.layout(makeGraph(winnerDirection));
|
|
100355
|
+
({ result, options: winnerOptions } = await layoutGraph(winnerDirection));
|
|
100195
100356
|
}
|
|
100196
100357
|
const layoutMs = Date.now() - startTime;
|
|
100197
|
-
let base = sceneFromResult(result, layoutMs);
|
|
100358
|
+
let base = sceneFromResult(result, layoutMs, winnerOptions);
|
|
100198
100359
|
const layoutProfile = (candidate) => {
|
|
100199
100360
|
const everyEdge = new Set(candidate.edges.map((edge) => edge.id));
|
|
100200
100361
|
const profile = inspect(candidate, titleBoxesOf(candidate, model)).local(everyEdge, /* @__PURE__ */ new Map());
|
|
@@ -100204,6 +100365,8 @@ async function layout(model, view) {
|
|
|
100204
100365
|
if (!aspectTarget && nodeCoverage(base) < DENSE_ENOUGH) {
|
|
100205
100366
|
const denser = await denserLayout(base, {
|
|
100206
100367
|
layout: (spec) => elk.layout(makeGraph(winnerDirection, spec)),
|
|
100368
|
+
// Its specs carry the hub ports (none of them sets `hubPorts: false`), and
|
|
100369
|
+
// a candidate elk refuses is dropped rather than retried without them.
|
|
100207
100370
|
toScene: (laidOut) => sceneFromResult(laidOut, Date.now() - startTime),
|
|
100208
100371
|
profile: layoutProfile
|
|
100209
100372
|
});
|
|
@@ -100225,7 +100388,7 @@ async function layout(model, view) {
|
|
|
100225
100388
|
const constrained = makeGraph(winnerDirection, winnerOptions);
|
|
100226
100389
|
constrainPorts(constrained, current, flagged, model);
|
|
100227
100390
|
const reresult = await elk.layout(constrained);
|
|
100228
|
-
candidate = sceneFromResult(reresult, Date.now() - startTime);
|
|
100391
|
+
candidate = sceneFromResult(reresult, Date.now() - startTime, winnerOptions);
|
|
100229
100392
|
} catch {
|
|
100230
100393
|
break;
|
|
100231
100394
|
}
|
|
@@ -100237,7 +100400,7 @@ async function layout(model, view) {
|
|
|
100237
100400
|
}
|
|
100238
100401
|
return best?.scene ?? base;
|
|
100239
100402
|
}
|
|
100240
|
-
var orderOption, semiInteractiveOption, PORT_PASS_ROUNDS, centerOf, DENSE_ENOUGH, DENSITY_GAIN, INGRESS_PARTITION, DEFAULT_INGRESS_KINDS, EGRESS_PARTITION, COMPACT_WRAP, SLOT_SCALE, SIDE_TO_ELK, ASPECT_TARGETS;
|
|
100403
|
+
var orderOption, semiInteractiveOption, PORT_PASS_ROUNDS, centerOf, DENSE_ENOUGH, DENSITY_GAIN, INGRESS_PARTITION, DEFAULT_INGRESS_KINDS, EGRESS_PARTITION, COMPACT_WRAP, SLOT_SCALE, SIDE_TO_ELK, sideRequest, ASPECT_TARGETS;
|
|
100241
100404
|
var init_scene_layout = __esm({
|
|
100242
100405
|
"src/scene-layout.ts"() {
|
|
100243
100406
|
"use strict";
|
|
@@ -100268,6 +100431,7 @@ var init_scene_layout = __esm({
|
|
|
100268
100431
|
top: "NORTH",
|
|
100269
100432
|
bottom: "SOUTH"
|
|
100270
100433
|
};
|
|
100434
|
+
sideRequest = (side) => side ? { value: side } : void 0;
|
|
100271
100435
|
ASPECT_TARGETS = {
|
|
100272
100436
|
slide: 16 / 9,
|
|
100273
100437
|
page: 0.71
|
|
@@ -100365,6 +100529,19 @@ var init_logo_attribution = __esm({
|
|
|
100365
100529
|
});
|
|
100366
100530
|
|
|
100367
100531
|
// src/svg-render.ts
|
|
100532
|
+
function bandLines(text, from, right, fontSize) {
|
|
100533
|
+
const chars = Math.floor((right - from) / (fontSize * RENDER_CHAR_WIDTH));
|
|
100534
|
+
return bandWrap(text, Math.max(8, chars));
|
|
100535
|
+
}
|
|
100536
|
+
function bandWrap(text, maxChars) {
|
|
100537
|
+
return wrapText(text, maxChars).split("\n").flatMap((line) => {
|
|
100538
|
+
if (line.length <= maxChars) return [line];
|
|
100539
|
+
const parts = [];
|
|
100540
|
+
for (let index = 0; index < line.length; index += maxChars)
|
|
100541
|
+
parts.push(line.slice(index, index + maxChars));
|
|
100542
|
+
return parts;
|
|
100543
|
+
});
|
|
100544
|
+
}
|
|
100368
100545
|
function assignSourceHues(model, hues) {
|
|
100369
100546
|
const sourceHue = /* @__PURE__ */ new Map();
|
|
100370
100547
|
for (const flow of model.flows) {
|
|
@@ -100417,19 +100594,39 @@ function auditRouteRepairs(deps) {
|
|
|
100417
100594
|
for (let tier = 0; tier < 5; tier++) if (a[tier] !== b[tier]) return a[tier] < b[tier];
|
|
100418
100595
|
return false;
|
|
100419
100596
|
};
|
|
100597
|
+
const flowOfBreach = (key) => {
|
|
100598
|
+
if (key.startsWith("adrift:"))
|
|
100599
|
+
return labels[Number.parseInt(key.slice("adrift:".length), 10)]?.flowId ?? null;
|
|
100600
|
+
const body = key.slice(key.indexOf(":") + 1);
|
|
100601
|
+
const cut = body.search(/[~:]/);
|
|
100602
|
+
return cut < 0 ? body : body.slice(0, cut);
|
|
100603
|
+
};
|
|
100420
100604
|
const withRepair = stateHarm();
|
|
100421
100605
|
const breachesWith = breaches();
|
|
100422
100606
|
const repairedRoutes = repaired.map((edge) => edge.pts);
|
|
100423
|
-
|
|
100424
|
-
|
|
100425
|
-
const withoutRepair = stateHarm();
|
|
100426
|
-
const breachesWithout = breaches();
|
|
100427
|
-
const breaksAPromise = [...breachesWith].some((key) => !breachesWithout.has(key));
|
|
100428
|
-
if (!breaksAPromise && !lessDamaged(withoutRepair, withRepair)) {
|
|
100607
|
+
const replacedRoutes = repaired.map((edge) => edge.repairedFrom);
|
|
100608
|
+
const applyKeep = (keep2) => {
|
|
100429
100609
|
repaired.forEach((edge, index) => {
|
|
100430
|
-
edge.pts = repairedRoutes[index];
|
|
100610
|
+
edge.pts = keep2[index] ? repairedRoutes[index] : replacedRoutes[index];
|
|
100431
100611
|
});
|
|
100432
100612
|
resettle();
|
|
100613
|
+
};
|
|
100614
|
+
applyKeep(repaired.map(() => false));
|
|
100615
|
+
const withoutRepair = stateHarm();
|
|
100616
|
+
const breachesWithout = breaches();
|
|
100617
|
+
const broken = [...breachesWith].filter((key) => !breachesWithout.has(key));
|
|
100618
|
+
if (!broken.length) {
|
|
100619
|
+
if (!lessDamaged(withoutRepair, withRepair)) applyKeep(repaired.map(() => true));
|
|
100620
|
+
for (const edge of repaired) edge.repairedFrom = void 0;
|
|
100621
|
+
return;
|
|
100622
|
+
}
|
|
100623
|
+
const blamed = new Set(broken.map(flowOfBreach).filter((id) => id !== null));
|
|
100624
|
+
const keep = repaired.map((edge) => !blamed.has(edge.id));
|
|
100625
|
+
if (keep.some(Boolean) && keep.some((kept) => !kept)) {
|
|
100626
|
+
applyKeep(keep);
|
|
100627
|
+
const partly = stateHarm();
|
|
100628
|
+
const stillBroken = [...breaches()].some((key) => !breachesWithout.has(key));
|
|
100629
|
+
if (stillBroken || lessDamaged(withoutRepair, partly)) applyKeep(repaired.map(() => false));
|
|
100433
100630
|
}
|
|
100434
100631
|
for (const edge of repaired) edge.repairedFrom = void 0;
|
|
100435
100632
|
}
|
|
@@ -100693,15 +100890,20 @@ function createLabelSettler(deps) {
|
|
|
100693
100890
|
const a = edge.pts[index];
|
|
100694
100891
|
const b = edge.pts[index + 1];
|
|
100695
100892
|
const vertical = Math.abs(a.x - b.x) < Math.abs(a.y - b.y);
|
|
100696
|
-
const
|
|
100697
|
-
const
|
|
100698
|
-
|
|
100699
|
-
|
|
100700
|
-
|
|
100701
|
-
|
|
100702
|
-
|
|
100703
|
-
|
|
100704
|
-
|
|
100893
|
+
const size = vertical ? label.height : label.width;
|
|
100894
|
+
const start = vertical ? Math.min(a.y, b.y) : Math.min(a.x, b.x);
|
|
100895
|
+
const end = vertical ? Math.max(a.y, b.y) : Math.max(a.x, b.x);
|
|
100896
|
+
const seat = vertical ? (a.y + b.y) / 2 - lead : (a.x + b.x) / 2 - label.width / 2;
|
|
100897
|
+
const back = Math.max(0, seat - start);
|
|
100898
|
+
const ahead = Math.max(0, end - size - seat);
|
|
100899
|
+
if (back === 0 && ahead === 0) continue;
|
|
100900
|
+
for (const fraction of [0.25, 0.5, 0.75, 1])
|
|
100901
|
+
for (const room of [-back, ahead]) {
|
|
100902
|
+
const shift = room * fraction;
|
|
100903
|
+
seats.push(
|
|
100904
|
+
vertical ? { x: (a.x + b.x) / 2 - label.width / 2, y: seat + shift } : { x: seat + shift, y: (a.y + b.y) / 2 - lead }
|
|
100905
|
+
);
|
|
100906
|
+
}
|
|
100705
100907
|
}
|
|
100706
100908
|
return seats;
|
|
100707
100909
|
};
|
|
@@ -100748,27 +100950,103 @@ function createLabelSettler(deps) {
|
|
|
100748
100950
|
settleLabelPositions
|
|
100749
100951
|
};
|
|
100750
100952
|
}
|
|
100953
|
+
function kindKeysSvg(paint, kinds, y, x) {
|
|
100954
|
+
const { scene, palette, legendNames, scaled, resolveStyle } = paint;
|
|
100955
|
+
let svg = "";
|
|
100956
|
+
let keyX = x;
|
|
100957
|
+
let bandY = y;
|
|
100958
|
+
const LINE_H = scaled(12);
|
|
100959
|
+
for (const kind of kinds) {
|
|
100960
|
+
const nodeStyle = resolveStyle(kind, "");
|
|
100961
|
+
if (kind === "actor") {
|
|
100962
|
+
const stroke = nodeStyle.stroke?.color ?? palette.actorStroke;
|
|
100963
|
+
svg += `<circle cx="${keyX + scaled(13)}" cy="${bandY + scaled(5)}" r="${scaled(3)}" fill="none" stroke="${stroke}" stroke-width="1.2"/>
|
|
100964
|
+
`;
|
|
100965
|
+
svg += `<path d="M ${keyX + scaled(8)} ${bandY + scaled(15)} q ${scaled(5)} ${scaled(-7)} ${scaled(10)} 0" fill="none" stroke="${stroke}" stroke-width="1.2"/>
|
|
100966
|
+
`;
|
|
100967
|
+
} else {
|
|
100968
|
+
const dash = dashArray(nodeStyle.stroke?.style);
|
|
100969
|
+
const stroke = nodeStyle.stroke?.color ?? palette.nodeStroke;
|
|
100970
|
+
svg += `<rect x="${keyX}" y="${bandY + 2}" width="${scaled(26)}" height="${scaled(14)}" rx="3" fill="${nodeStyle.fill ?? palette.nodeFill}" stroke="${escAttr(stroke)}"${dash ? ` stroke-dasharray="${dash}"` : ""}/>
|
|
100971
|
+
`;
|
|
100972
|
+
const glyphScale = (scaled(14) - scaled(4)) / GLYPH_BOX.height;
|
|
100973
|
+
svg += glyphSvg(kind, escAttr(stroke), {
|
|
100974
|
+
x: keyX + (scaled(26) - GLYPH_BOX.width * glyphScale) / 2,
|
|
100975
|
+
y: bandY + 2 + (scaled(14) - GLYPH_BOX.height * glyphScale) / 2,
|
|
100976
|
+
scale: glyphScale
|
|
100977
|
+
});
|
|
100978
|
+
}
|
|
100979
|
+
const name = legendNames[kind];
|
|
100980
|
+
const lines = bandLines(name, keyX + scaled(32), bandRightMargin(scene), scaled(10));
|
|
100981
|
+
for (const [row, line] of lines.entries())
|
|
100982
|
+
svg += `<text x="${keyX + scaled(32)}" y="${bandY + scaled(13) + row * LINE_H}" font-size="${scaled(10)}" fill="${palette.bandText}">${esc(line)}</text>
|
|
100983
|
+
`;
|
|
100984
|
+
keyX += scaled(40) + Math.ceil(name.length * scaled(10) * RENDER_CHAR_WIDTH) + scaled(24);
|
|
100985
|
+
bandY += (lines.length - 1) * LINE_H;
|
|
100986
|
+
if (lines.length > 1 || keyX > scene.width - 220) {
|
|
100987
|
+
keyX = x;
|
|
100988
|
+
bandY += scaled(22);
|
|
100989
|
+
}
|
|
100990
|
+
}
|
|
100991
|
+
return { svg, bandY: kinds.length ? bandY + scaled(24) : bandY };
|
|
100992
|
+
}
|
|
100993
|
+
function lineStyleKeysSvg(paint, y, x) {
|
|
100994
|
+
const { scene, model, style, palette, legendLineStyles, scaled, defaultEdgeColor, markerName } = paint;
|
|
100995
|
+
const styles = LINE_STYLES2.filter(
|
|
100996
|
+
(lineStyle) => model.flows.some((flow) => lineStyleOf(flow, style) === lineStyle)
|
|
100997
|
+
);
|
|
100998
|
+
if (styles.length < 2) return { svg: "", bandY: y };
|
|
100999
|
+
const maxX = scene.width - 20;
|
|
101000
|
+
let svg = "";
|
|
101001
|
+
let keyX = x;
|
|
101002
|
+
let bandY = y;
|
|
101003
|
+
const LINE_H = scaled(12);
|
|
101004
|
+
for (const [index, lineStyle] of styles.entries()) {
|
|
101005
|
+
const meaning = legendLineStyles[lineStyle];
|
|
101006
|
+
const keyWidth = scaled(40) + Math.ceil(meaning.length * scaled(10) * RENDER_CHAR_WIDTH) + scaled(24);
|
|
101007
|
+
if (keyX > x && keyX + keyWidth > maxX) {
|
|
101008
|
+
keyX = x;
|
|
101009
|
+
bandY += scaled(22);
|
|
101010
|
+
}
|
|
101011
|
+
const dash = dashArray(lineStyle);
|
|
101012
|
+
svg += `<line x1="${keyX}" y1="${bandY + 8}" x2="${keyX + scaled(26)}" y2="${bandY + 8}" stroke="${escAttr(defaultEdgeColor)}" stroke-width="1.3"${dash ? ` stroke-dasharray="${dash}"` : ""} marker-end="url(#${markerName(defaultEdgeColor)})"/>
|
|
101013
|
+
`;
|
|
101014
|
+
const lines = bandLines(meaning, keyX + scaled(40), maxX, scaled(10));
|
|
101015
|
+
for (const [row, line] of lines.entries())
|
|
101016
|
+
svg += `<text x="${keyX + scaled(32)}" y="${bandY + scaled(12) + row * LINE_H}" font-size="${scaled(10)}" fill="${palette.bandText}">${esc(line)}</text>
|
|
101017
|
+
`;
|
|
101018
|
+
if (lines.length === 1) {
|
|
101019
|
+
keyX += keyWidth;
|
|
101020
|
+
continue;
|
|
101021
|
+
}
|
|
101022
|
+
bandY += (lines.length - 1) * LINE_H;
|
|
101023
|
+
if (index < styles.length - 1) {
|
|
101024
|
+
bandY += scaled(22);
|
|
101025
|
+
keyX = x;
|
|
101026
|
+
}
|
|
101027
|
+
}
|
|
101028
|
+
return { svg, bandY: bandY + scaled(24) };
|
|
101029
|
+
}
|
|
100751
101030
|
function createBandRenderers(paint) {
|
|
100752
101031
|
const {
|
|
100753
101032
|
scene,
|
|
100754
101033
|
model,
|
|
100755
|
-
view,
|
|
100756
101034
|
style,
|
|
100757
101035
|
palette,
|
|
100758
101036
|
annot,
|
|
100759
101037
|
ui,
|
|
100760
|
-
legendNames,
|
|
100761
101038
|
legendFlowLabel,
|
|
100762
101039
|
scaled,
|
|
100763
101040
|
objectName,
|
|
100764
|
-
resolveStyle,
|
|
100765
101041
|
defaultEdgeColor,
|
|
100766
101042
|
markerName,
|
|
100767
101043
|
numbered
|
|
100768
101044
|
} = paint;
|
|
100769
101045
|
let bandY = scene.height;
|
|
100770
101046
|
let bandsSvg = "";
|
|
100771
|
-
const
|
|
101047
|
+
const rightMargin = bandRightMargin(scene);
|
|
101048
|
+
const stackedTitle = rightMargin - BAND_CONTENT_X < MIN_BAND_TEXT;
|
|
101049
|
+
const contentX = stackedTitle ? 20 : BAND_CONTENT_X;
|
|
100772
101050
|
const chip = (x, y, name) => {
|
|
100773
101051
|
const width = chipW(name, annot.scale);
|
|
100774
101052
|
return {
|
|
@@ -100783,7 +101061,7 @@ function createBandRenderers(paint) {
|
|
|
100783
101061
|
`;
|
|
100784
101062
|
bandsSvg += `<text x="20" y="${bandY + scaled(32)}" font-size="${scaled(11)}" font-weight="bold" fill="${palette.bandTitle}">${esc(title)}</text>
|
|
100785
101063
|
`;
|
|
100786
|
-
bandY += scaled(20);
|
|
101064
|
+
bandY += scaled(stackedTitle ? 34 : 20);
|
|
100787
101065
|
};
|
|
100788
101066
|
const flowChipsSvg = (flow, startX, startY) => {
|
|
100789
101067
|
if (!flow.objects?.length) return "";
|
|
@@ -100815,7 +101093,7 @@ function createBandRenderers(paint) {
|
|
|
100815
101093
|
const textW = Math.max(60, colW - BADGE - (chipsW ? chipsW + 6 : 0));
|
|
100816
101094
|
const maxChars = Math.max(6, Math.floor(textW / (scaled(10) * RENDER_CHAR_WIDTH)));
|
|
100817
101095
|
const raw = (flow.label ?? "") + (tech ? " " + tech : "");
|
|
100818
|
-
const lines = raw.split("\n").flatMap((segment) =>
|
|
101096
|
+
const lines = raw.split("\n").flatMap((segment) => bandWrap(segment, maxChars));
|
|
100819
101097
|
return { flow, lines };
|
|
100820
101098
|
});
|
|
100821
101099
|
const rows = Math.ceil(entries.length / cols);
|
|
@@ -100842,66 +101120,50 @@ function createBandRenderers(paint) {
|
|
|
100842
101120
|
};
|
|
100843
101121
|
const renderObjectsBand = () => {
|
|
100844
101122
|
beginBand(ui.objects);
|
|
100845
|
-
|
|
100846
|
-
|
|
101123
|
+
const LINE_H = scaled(12);
|
|
101124
|
+
const chipRow = (name, reading) => {
|
|
101125
|
+
const chipResult = chip(contentX, bandY + 2, name);
|
|
100847
101126
|
bandsSvg += chipResult.svg;
|
|
100848
|
-
|
|
100849
|
-
|
|
101127
|
+
const textX = contentX + chipResult.width + 10;
|
|
101128
|
+
const lines = reading ? bandLines(reading, textX, rightMargin, scaled(10)) : [];
|
|
101129
|
+
for (const [row, line] of lines.entries())
|
|
101130
|
+
bandsSvg += `<text x="${textX}" y="${bandY + scaled(13) + row * LINE_H}" font-size="${scaled(10)}" fill="${palette.bandMuted}">${esc(line)}</text>
|
|
100850
101131
|
`;
|
|
100851
|
-
bandY += scaled(24);
|
|
100852
|
-
}
|
|
100853
|
-
|
|
100854
|
-
|
|
100855
|
-
|
|
100856
|
-
|
|
100857
|
-
bandY +=
|
|
101132
|
+
bandY += scaled(24) + Math.max(0, lines.length - 1) * LINE_H;
|
|
101133
|
+
};
|
|
101134
|
+
for (const bo of model.businessObjects)
|
|
101135
|
+
chipRow(bo.name, bo.description ? `\u2014 ${bo.description}` : "");
|
|
101136
|
+
if (model.flows.some((flow) => flow.objects?.length))
|
|
101137
|
+
chipRow(ui.businessObject, ui.carriedByFlow);
|
|
101138
|
+
bandY += 6;
|
|
100858
101139
|
};
|
|
100859
101140
|
const renderLegendBand = () => {
|
|
101141
|
+
const kinds = legendKinds(paint);
|
|
101142
|
+
if (!kinds.length && !model.flows.length && !model.legendNotes.length) return;
|
|
100860
101143
|
beginBand(ui.legend);
|
|
100861
|
-
|
|
100862
|
-
|
|
100863
|
-
|
|
100864
|
-
)
|
|
100865
|
-
|
|
100866
|
-
const nodeStyle = resolveStyle(kind, "");
|
|
100867
|
-
if (kind === "actor") {
|
|
100868
|
-
const stroke = nodeStyle.stroke?.color ?? palette.actorStroke;
|
|
100869
|
-
bandsSvg += `<circle cx="${lx + scaled(13)}" cy="${bandY + scaled(5)}" r="${scaled(3)}" fill="none" stroke="${stroke}" stroke-width="1.2"/>
|
|
100870
|
-
`;
|
|
100871
|
-
bandsSvg += `<path d="M ${lx + scaled(8)} ${bandY + scaled(15)} q ${scaled(5)} ${scaled(-7)} ${scaled(10)} 0" fill="none" stroke="${stroke}" stroke-width="1.2"/>
|
|
100872
|
-
`;
|
|
100873
|
-
} else {
|
|
100874
|
-
const dash = dashArray(nodeStyle.stroke?.style);
|
|
100875
|
-
const stroke = nodeStyle.stroke?.color ?? palette.nodeStroke;
|
|
100876
|
-
bandsSvg += `<rect x="${lx}" y="${bandY + 2}" width="${scaled(26)}" height="${scaled(14)}" rx="3" fill="${nodeStyle.fill ?? palette.nodeFill}" stroke="${escAttr(stroke)}"${dash ? ` stroke-dasharray="${dash}"` : ""}/>
|
|
101144
|
+
const kindKeys = kindKeysSvg(paint, kinds, bandY, contentX);
|
|
101145
|
+
bandsSvg += kindKeys.svg;
|
|
101146
|
+
bandY = kindKeys.bandY;
|
|
101147
|
+
if (model.flows.length) {
|
|
101148
|
+
bandsSvg += `<line x1="${contentX}" y1="${bandY + 8}" x2="${contentX + scaled(26)}" y2="${bandY + 8}" stroke="${escAttr(defaultEdgeColor)}" stroke-width="1.3" marker-end="url(#${markerName(defaultEdgeColor)})"/>
|
|
100877
101149
|
`;
|
|
100878
|
-
|
|
100879
|
-
|
|
100880
|
-
|
|
100881
|
-
|
|
100882
|
-
|
|
100883
|
-
});
|
|
100884
|
-
}
|
|
100885
|
-
const name = legendNames[kind];
|
|
100886
|
-
bandsSvg += `<text x="${lx + scaled(32)}" y="${bandY + scaled(13)}" font-size="${scaled(10)}" fill="${palette.bandText}">${esc(name)}</text>
|
|
101150
|
+
const flowLabelText = (numbered ? legendFlowLabel + " \u2014 " + ui.numberedSuffix : legendFlowLabel) + (style.flowColor === "by-source" ? style.lang === "fr" ? " \u2014 couleur = source" : " \u2014 colour = source" : "");
|
|
101151
|
+
const flowKeyX = contentX + scaled(32);
|
|
101152
|
+
const flowKeyLines = bandLines(flowLabelText, flowKeyX, rightMargin, scaled(10));
|
|
101153
|
+
for (const [row, line] of flowKeyLines.entries())
|
|
101154
|
+
bandsSvg += `<text x="${flowKeyX}" y="${bandY + scaled(12) + row * scaled(12)}" font-size="${scaled(10)}" fill="${palette.bandText}">${esc(line)}</text>
|
|
100887
101155
|
`;
|
|
100888
|
-
|
|
100889
|
-
|
|
100890
|
-
|
|
100891
|
-
|
|
100892
|
-
}
|
|
101156
|
+
bandY += scaled(24) + (flowKeyLines.length - 1) * scaled(12);
|
|
101157
|
+
const lineStyleKeys = lineStyleKeysSvg(paint, bandY, contentX);
|
|
101158
|
+
bandsSvg += lineStyleKeys.svg;
|
|
101159
|
+
bandY = lineStyleKeys.bandY;
|
|
100893
101160
|
}
|
|
100894
|
-
bandY += scaled(24);
|
|
100895
|
-
bandsSvg += `<line x1="${contentX}" y1="${bandY + 8}" x2="${contentX + scaled(26)}" y2="${bandY + 8}" stroke="${escAttr(defaultEdgeColor)}" stroke-width="1.3" marker-end="url(#${markerName(defaultEdgeColor)})"/>
|
|
100896
|
-
`;
|
|
100897
|
-
const flowLabelText = (numbered ? legendFlowLabel + " \u2014 " + ui.numberedSuffix : legendFlowLabel) + (style.flowColor === "by-source" ? style.lang === "fr" ? " \u2014 couleur = source" : " \u2014 colour = source" : "");
|
|
100898
|
-
bandsSvg += `<text x="${contentX + scaled(32)}" y="${bandY + scaled(12)}" font-size="${scaled(10)}" fill="${palette.bandText}">${esc(flowLabelText)}</text>
|
|
100899
|
-
`;
|
|
100900
|
-
bandY += scaled(24);
|
|
100901
101161
|
for (const note of model.legendNotes) {
|
|
100902
|
-
|
|
101162
|
+
const lines = bandLines(note, contentX, rightMargin, scaled(10));
|
|
101163
|
+
for (const [row, line] of lines.entries())
|
|
101164
|
+
bandsSvg += `<text x="${contentX}" y="${bandY + scaled(12) + row * scaled(12)}" font-size="${scaled(10)}" fill="${palette.bandText}" font-style="italic">${esc(line)}</text>
|
|
100903
101165
|
`;
|
|
100904
|
-
bandY += scaled(20);
|
|
101166
|
+
bandY += scaled(20) + (lines.length - 1) * scaled(12);
|
|
100905
101167
|
}
|
|
100906
101168
|
};
|
|
100907
101169
|
return {
|
|
@@ -101000,7 +101262,7 @@ function createEdgePainter(paint) {
|
|
|
101000
101262
|
const flowStyle = flow?.style;
|
|
101001
101263
|
const color = flowColorOf(flow);
|
|
101002
101264
|
const headColor = style.flowColor === "by-source" ? color : defaultEdgeColor;
|
|
101003
|
-
const dash = dashArray(
|
|
101265
|
+
const dash = dashArray(lineStyleOf(flow, style));
|
|
101004
101266
|
const width = flowStyle?.stroke?.width ?? style.flowStroke.width;
|
|
101005
101267
|
return `<path d="${edgePath(edge.pts)}" fill="none" stroke="${escAttr(color)}" stroke-width="${width}"${dash ? ` stroke-dasharray="${dash}"` : ""} marker-end="url(#${markerName(headColor)})"/>
|
|
101006
101268
|
`;
|
|
@@ -101073,6 +101335,7 @@ function render(model, view, scene, options) {
|
|
|
101073
101335
|
const ui = UI[style.lang] ?? UI.en;
|
|
101074
101336
|
const legendNames = style.lang === "fr" ? view.legendNamesFr : view.legendNames;
|
|
101075
101337
|
const legendFlowLabel = style.lang === "fr" ? view.legendFlowLabelFr : view.legendFlowLabel;
|
|
101338
|
+
const legendLineStyles = style.lang === "fr" ? view.legendLineStylesFr : view.legendLineStyles;
|
|
101076
101339
|
const elementStyle = /* @__PURE__ */ new Map();
|
|
101077
101340
|
const elementLogo = /* @__PURE__ */ new Map();
|
|
101078
101341
|
for (const entry of collectElementStyles(model.elements)) {
|
|
@@ -101115,6 +101378,7 @@ function render(model, view, scene, options) {
|
|
|
101115
101378
|
});
|
|
101116
101379
|
const overlapsAfter = countLabelOverlaps();
|
|
101117
101380
|
compactVertical(scene);
|
|
101381
|
+
fitCanvas(scene);
|
|
101118
101382
|
const drawnLogos = /* @__PURE__ */ new Set();
|
|
101119
101383
|
const { renderContainerNode, renderLeafNode } = createNodeRenderers({
|
|
101120
101384
|
palette,
|
|
@@ -101154,6 +101418,7 @@ function render(model, view, scene, options) {
|
|
|
101154
101418
|
ui,
|
|
101155
101419
|
legendNames,
|
|
101156
101420
|
legendFlowLabel,
|
|
101421
|
+
legendLineStyles,
|
|
101157
101422
|
scaled,
|
|
101158
101423
|
objectName,
|
|
101159
101424
|
resolveStyle,
|
|
@@ -101183,10 +101448,11 @@ function render(model, view, scene, options) {
|
|
|
101183
101448
|
});
|
|
101184
101449
|
return { svg, overlapsBefore, overlapsAfter };
|
|
101185
101450
|
}
|
|
101186
|
-
var HOP_RADIUS, LABEL_HALO, RENDER_CHAR_WIDTH, dashArray, round1, ADRIFT_SQ, ATTACHED_SQ, GLYPH_BOX, GLYPHS, LOGO_BOX;
|
|
101451
|
+
var HOP_RADIUS, LABEL_HALO, RENDER_CHAR_WIDTH, BAND_CONTENT_X, MIN_BAND_TEXT, bandRightMargin, dashArray, lineStyleOf, round1, ADRIFT_SQ, ATTACHED_SQ, GLYPH_BOX, GLYPHS, LOGO_BOX, legendKinds;
|
|
101187
101452
|
var init_svg_render = __esm({
|
|
101188
101453
|
"src/svg-render.ts"() {
|
|
101189
101454
|
"use strict";
|
|
101455
|
+
init_views();
|
|
101190
101456
|
init_themes();
|
|
101191
101457
|
init_localization();
|
|
101192
101458
|
init_xml_escape();
|
|
@@ -101202,7 +101468,11 @@ var init_svg_render = __esm({
|
|
|
101202
101468
|
HOP_RADIUS = 5;
|
|
101203
101469
|
LABEL_HALO = 4;
|
|
101204
101470
|
RENDER_CHAR_WIDTH = 0.52;
|
|
101471
|
+
BAND_CONTENT_X = 150;
|
|
101472
|
+
MIN_BAND_TEXT = 200;
|
|
101473
|
+
bandRightMargin = (scene) => scene.width - 20;
|
|
101205
101474
|
dashArray = (lineStyle) => lineStyle === "dashed" ? "5 3" : lineStyle === "dotted" ? "2 2.5" : void 0;
|
|
101475
|
+
lineStyleOf = (flow, style) => flow?.style?.stroke?.style ?? flow?.lineStyle ?? style.flowStroke.style;
|
|
101206
101476
|
round1 = (n) => Math.round(n * 10) / 10;
|
|
101207
101477
|
ADRIFT_SQ = 20 * 20;
|
|
101208
101478
|
ATTACHED_SQ = 6 * 6;
|
|
@@ -101222,6 +101492,9 @@ var init_svg_render = __esm({
|
|
|
101222
101492
|
device: ({ x, y, r, line }) => `<rect x="${x(2)}" y="${y(1)}" width="${r(14)}" height="${r(9)}" rx="${r(1)}" ${line}/><path d="M ${x(9)} ${y(10)} V ${y(13)}" ${line}/><path d="M ${x(4)} ${y(13)} H ${x(14)}" ${line}/>`
|
|
101223
101493
|
};
|
|
101224
101494
|
LOGO_BOX = { size: 18, right: 7, top: 6 };
|
|
101495
|
+
legendKinds = (paint) => [...new Set(paint.scene.nodes.map((node) => node.kind))].filter(
|
|
101496
|
+
(kind) => paint.legendNames[kind] && (kind !== "actor" || paint.view.actorLegend)
|
|
101497
|
+
);
|
|
101225
101498
|
}
|
|
101226
101499
|
});
|
|
101227
101500
|
|
|
@@ -102159,7 +102432,7 @@ CORE_I -> PARTNER : "Nightly export" (SFTP/22)
|
|
|
102159
102432
|
};
|
|
102160
102433
|
args = process.argv.slice(2);
|
|
102161
102434
|
command = args[0];
|
|
102162
|
-
version = true ? "1.0.0-
|
|
102435
|
+
version = true ? "1.0.0-RC15" : pkg.version;
|
|
102163
102436
|
VALUE_FLAGS = /* @__PURE__ */ new Set(["-o", "--format", "--theme"]);
|
|
102164
102437
|
positionalFile = () => {
|
|
102165
102438
|
for (let index = 0; index < args.length; index++) {
|