schematex 0.5.0 → 0.6.0
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 +89 -7
- package/dist/ai/ai-sdk.cjs +32 -26
- package/dist/ai/ai-sdk.cjs.map +1 -1
- package/dist/ai/ai-sdk.d.cts +4 -3
- package/dist/ai/ai-sdk.d.ts +4 -3
- package/dist/ai/ai-sdk.js +28 -22
- package/dist/ai/ai-sdk.js.map +1 -1
- package/dist/ai/index.cjs +37 -25
- package/dist/ai/index.d.cts +20 -153
- package/dist/ai/index.d.ts +20 -153
- package/dist/ai/index.js +17 -17
- package/dist/{api-C5UcmT7n.d.cts → api-XWHHAhQI.d.ts} +12 -2
- package/dist/{api-C5UcmT7n.d.ts → api-qVDutqXH.d.cts} +12 -2
- package/dist/browser.cjs +37 -19
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +12 -3
- package/dist/browser.d.ts +12 -3
- package/dist/browser.js +27 -19
- package/dist/browser.js.map +1 -1
- package/dist/{chunk-3YUUC6RN.cjs → chunk-25ZON47K.cjs} +4105 -267
- package/dist/chunk-25ZON47K.cjs.map +1 -0
- package/dist/{chunk-N7W5KZK7.cjs → chunk-2L4YXZAZ.cjs} +4 -4
- package/dist/{chunk-N7W5KZK7.cjs.map → chunk-2L4YXZAZ.cjs.map} +1 -1
- package/dist/{chunk-ZYRCPSBU.js → chunk-3IE7KZY4.js} +4 -4
- package/dist/{chunk-ZYRCPSBU.js.map → chunk-3IE7KZY4.js.map} +1 -1
- package/dist/{chunk-ZX74KJPM.js → chunk-3VB5AT4R.js} +3 -3
- package/dist/{chunk-ZX74KJPM.js.map → chunk-3VB5AT4R.js.map} +1 -1
- package/dist/{chunk-S2KJRHDZ.cjs → chunk-4UFR2LB6.cjs} +13 -13
- package/dist/{chunk-S2KJRHDZ.cjs.map → chunk-4UFR2LB6.cjs.map} +1 -1
- package/dist/{chunk-QTNPMIO2.cjs → chunk-6JI6FWLZ.cjs} +341 -35
- package/dist/chunk-6JI6FWLZ.cjs.map +1 -0
- package/dist/{chunk-GTDQAN2Z.js → chunk-7AFW2J6J.js} +4075 -247
- package/dist/chunk-7AFW2J6J.js.map +1 -0
- package/dist/{chunk-UFTYX73U.js → chunk-7F76AWOI.js} +339 -35
- package/dist/chunk-7F76AWOI.js.map +1 -0
- package/dist/{chunk-BW4KGTV7.cjs → chunk-C5C5EF3W.cjs} +4 -4
- package/dist/{chunk-BW4KGTV7.cjs.map → chunk-C5C5EF3W.cjs.map} +1 -1
- package/dist/{chunk-VJGMEGMR.js → chunk-DOK7LKLO.js} +125 -13
- package/dist/chunk-DOK7LKLO.js.map +1 -0
- package/dist/{chunk-3M6WB62Y.cjs → chunk-ECD5XHBM.cjs} +139 -7
- package/dist/chunk-ECD5XHBM.cjs.map +1 -0
- package/dist/{chunk-L3CTXXVZ.js → chunk-FBS3PACU.js} +3 -3
- package/dist/{chunk-L3CTXXVZ.js.map → chunk-FBS3PACU.js.map} +1 -1
- package/dist/{chunk-QUKVGHN4.cjs → chunk-FKJBXGWP.cjs} +4 -4
- package/dist/{chunk-QUKVGHN4.cjs.map → chunk-FKJBXGWP.cjs.map} +1 -1
- package/dist/{chunk-SZK376QB.js → chunk-H4MT5TJP.js} +3 -3
- package/dist/{chunk-SZK376QB.js.map → chunk-H4MT5TJP.js.map} +1 -1
- package/dist/{chunk-3M6T7KB4.js → chunk-HAZALB7U.js} +3 -3
- package/dist/{chunk-3M6T7KB4.js.map → chunk-HAZALB7U.js.map} +1 -1
- package/dist/{chunk-VFZOPRQP.cjs → chunk-HWVBHU3O.cjs} +5 -5
- package/dist/{chunk-VFZOPRQP.cjs.map → chunk-HWVBHU3O.cjs.map} +1 -1
- package/dist/{chunk-D7EHZFK4.cjs → chunk-L7POWM5B.cjs} +138 -2
- package/dist/chunk-L7POWM5B.cjs.map +1 -0
- package/dist/{chunk-6OSUNBZY.js → chunk-LGABFD3L.js} +135 -7
- package/dist/chunk-LGABFD3L.js.map +1 -0
- package/dist/{chunk-2VNMKOUO.js → chunk-LRI4RH2N.js} +135 -3
- package/dist/chunk-LRI4RH2N.js.map +1 -0
- package/dist/{chunk-VDSYMSUY.js → chunk-MVIEIKOI.js} +3 -3
- package/dist/{chunk-VDSYMSUY.js.map → chunk-MVIEIKOI.js.map} +1 -1
- package/dist/{chunk-ZL5RB4UV.js → chunk-N5B242WY.js} +3 -3
- package/dist/{chunk-ZL5RB4UV.js.map → chunk-N5B242WY.js.map} +1 -1
- package/dist/{chunk-IM4RCUHA.js → chunk-P63S7P6N.js} +1309 -69
- package/dist/chunk-P63S7P6N.js.map +1 -0
- package/dist/{chunk-YLEVMOK2.cjs → chunk-R66QG3XT.cjs} +5 -4
- package/dist/{chunk-YLEVMOK2.cjs.map → chunk-R66QG3XT.cjs.map} +1 -1
- package/dist/{chunk-I55HO32M.js → chunk-RJMCWT7Z.js} +3 -3
- package/dist/{chunk-I55HO32M.js.map → chunk-RJMCWT7Z.js.map} +1 -1
- package/dist/{chunk-TZTCIAYW.cjs → chunk-S3RMAXH5.cjs} +137 -25
- package/dist/chunk-S3RMAXH5.cjs.map +1 -0
- package/dist/{chunk-IBRW3UOA.js → chunk-TWLKXV2O.js} +3 -3
- package/dist/{chunk-IBRW3UOA.js.map → chunk-TWLKXV2O.js.map} +1 -1
- package/dist/{chunk-HUPDIRBX.js → chunk-UWA5MWCI.js} +949 -918
- package/dist/chunk-UWA5MWCI.js.map +1 -0
- package/dist/{chunk-RYVV5UVI.cjs → chunk-V4GILQR6.cjs} +4 -4
- package/dist/{chunk-RYVV5UVI.cjs.map → chunk-V4GILQR6.cjs.map} +1 -1
- package/dist/{chunk-XRCY75UV.cjs → chunk-V4RO5KYY.cjs} +951 -918
- package/dist/chunk-V4RO5KYY.cjs.map +1 -0
- package/dist/{chunk-VZ5LDNHK.cjs → chunk-VTSH4YPT.cjs} +12 -12
- package/dist/{chunk-VZ5LDNHK.cjs.map → chunk-VTSH4YPT.cjs.map} +1 -1
- package/dist/{chunk-UUBNQV2T.cjs → chunk-WQDIZH2Z.cjs} +12 -12
- package/dist/{chunk-UUBNQV2T.cjs.map → chunk-WQDIZH2Z.cjs.map} +1 -1
- package/dist/{chunk-EGSUMHCS.cjs → chunk-YB4XJY5L.cjs} +12 -12
- package/dist/{chunk-EGSUMHCS.cjs.map → chunk-YB4XJY5L.cjs.map} +1 -1
- package/dist/{chunk-JHDR56XO.js → chunk-YS6CGUNH.js} +3 -3
- package/dist/{chunk-JHDR56XO.js.map → chunk-YS6CGUNH.js.map} +1 -1
- package/dist/{chunk-NWPCY65Z.cjs → chunk-YVDUEUFV.cjs} +1311 -68
- package/dist/chunk-YVDUEUFV.cjs.map +1 -0
- package/dist/{types-BOAsqHoU.d.ts → diagnostics-DRxhodP6.d.cts} +74 -2
- package/dist/{types-BOAsqHoU.d.cts → diagnostics-DRxhodP6.d.ts} +74 -2
- package/dist/diagrams/blockdiagram/index.d.cts +1 -1
- package/dist/diagrams/blockdiagram/index.d.ts +1 -1
- package/dist/diagrams/circuit/index.cjs +8 -8
- package/dist/diagrams/circuit/index.d.cts +1 -1
- package/dist/diagrams/circuit/index.d.ts +1 -1
- package/dist/diagrams/circuit/index.js +2 -2
- package/dist/diagrams/ecomap/index.cjs +7 -7
- package/dist/diagrams/ecomap/index.d.cts +1 -1
- package/dist/diagrams/ecomap/index.d.ts +1 -1
- package/dist/diagrams/ecomap/index.js +2 -2
- package/dist/diagrams/entity/index.cjs +6 -6
- package/dist/diagrams/entity/index.d.cts +1 -1
- package/dist/diagrams/entity/index.d.ts +1 -1
- package/dist/diagrams/entity/index.js +2 -2
- package/dist/diagrams/fishbone/index.cjs +8 -8
- package/dist/diagrams/fishbone/index.d.cts +1 -1
- package/dist/diagrams/fishbone/index.d.ts +1 -1
- package/dist/diagrams/fishbone/index.js +2 -2
- package/dist/diagrams/flowchart/index.cjs +8 -8
- package/dist/diagrams/flowchart/index.d.cts +2 -2
- package/dist/diagrams/flowchart/index.d.ts +2 -2
- package/dist/diagrams/flowchart/index.js +2 -2
- package/dist/diagrams/genogram/index.cjs +9 -9
- package/dist/diagrams/genogram/index.d.cts +1 -1
- package/dist/diagrams/genogram/index.d.ts +1 -1
- package/dist/diagrams/genogram/index.js +2 -2
- package/dist/diagrams/ladder/index.cjs +6 -6
- package/dist/diagrams/ladder/index.d.cts +1 -1
- package/dist/diagrams/ladder/index.d.ts +1 -1
- package/dist/diagrams/ladder/index.js +2 -2
- package/dist/diagrams/logic/index.cjs +6 -6
- package/dist/diagrams/logic/index.d.cts +1 -1
- package/dist/diagrams/logic/index.d.ts +1 -1
- package/dist/diagrams/logic/index.js +2 -2
- package/dist/diagrams/orgchart/index.cjs +7 -7
- package/dist/diagrams/orgchart/index.d.cts +1 -1
- package/dist/diagrams/orgchart/index.d.ts +1 -1
- package/dist/diagrams/orgchart/index.js +2 -2
- package/dist/diagrams/pedigree/index.cjs +7 -7
- package/dist/diagrams/pedigree/index.d.cts +1 -1
- package/dist/diagrams/pedigree/index.d.ts +1 -1
- package/dist/diagrams/pedigree/index.js +2 -2
- package/dist/diagrams/phylo/index.cjs +7 -7
- package/dist/diagrams/phylo/index.d.cts +1 -1
- package/dist/diagrams/phylo/index.d.ts +1 -1
- package/dist/diagrams/phylo/index.js +2 -2
- package/dist/diagrams/sld/index.cjs +14 -6
- package/dist/diagrams/sld/index.d.cts +14 -2
- package/dist/diagrams/sld/index.d.ts +14 -2
- package/dist/diagrams/sld/index.js +2 -2
- package/dist/diagrams/sociogram/index.cjs +6 -6
- package/dist/diagrams/sociogram/index.d.cts +1 -1
- package/dist/diagrams/sociogram/index.d.ts +1 -1
- package/dist/diagrams/sociogram/index.js +2 -2
- package/dist/diagrams/timing/index.d.cts +1 -1
- package/dist/diagrams/timing/index.d.ts +1 -1
- package/dist/diagrams/venn/index.cjs +9 -9
- package/dist/diagrams/venn/index.d.cts +1 -1
- package/dist/diagrams/venn/index.d.ts +1 -1
- package/dist/diagrams/venn/index.js +2 -2
- package/dist/{index-CJai_TEZ.d.ts → index-BRIkOPnd.d.cts} +60 -2
- package/dist/{index-C9A0h-CB.d.cts → index-C7SN-FB3.d.ts} +60 -2
- package/dist/index.cjs +351 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -4
- package/dist/index.d.ts +38 -4
- package/dist/index.js +283 -16
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +21 -23
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +3 -2
- package/dist/react.d.ts +3 -2
- package/dist/react.js +21 -23
- package/dist/react.js.map +1 -1
- package/dist/tools-BVeUNdsU.d.ts +161 -0
- package/dist/tools-DdhP1kWY.d.cts +161 -0
- package/package.json +2 -2
- package/dist/chunk-2VNMKOUO.js.map +0 -1
- package/dist/chunk-3M6WB62Y.cjs.map +0 -1
- package/dist/chunk-3YUUC6RN.cjs.map +0 -1
- package/dist/chunk-6OSUNBZY.js.map +0 -1
- package/dist/chunk-D7EHZFK4.cjs.map +0 -1
- package/dist/chunk-GTDQAN2Z.js.map +0 -1
- package/dist/chunk-HUPDIRBX.js.map +0 -1
- package/dist/chunk-IM4RCUHA.js.map +0 -1
- package/dist/chunk-NWPCY65Z.cjs.map +0 -1
- package/dist/chunk-QTNPMIO2.cjs.map +0 -1
- package/dist/chunk-TZTCIAYW.cjs.map +0 -1
- package/dist/chunk-UFTYX73U.js.map +0 -1
- package/dist/chunk-VJGMEGMR.js.map +0 -1
- package/dist/chunk-XRCY75UV.cjs.map +0 -1
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { orgchart } from './chunk-
|
|
2
|
-
import { circuit } from './chunk-
|
|
1
|
+
import { orgchart } from './chunk-RJMCWT7Z.js';
|
|
2
|
+
import { circuit } from './chunk-UWA5MWCI.js';
|
|
3
3
|
import { blockdiagram } from './chunk-EPKIJEH7.js';
|
|
4
|
-
import { ladder } from './chunk-
|
|
5
|
-
import { sld } from './chunk-
|
|
6
|
-
import { entity } from './chunk-
|
|
7
|
-
import { fishbone } from './chunk-
|
|
8
|
-
import { venn } from './chunk-
|
|
9
|
-
import { flowchart, layoutFlowchart } from './chunk-
|
|
10
|
-
import { genogram } from './chunk-
|
|
11
|
-
import { ecomap } from './chunk-
|
|
12
|
-
import { pedigree } from './chunk-
|
|
4
|
+
import { ladder } from './chunk-TWLKXV2O.js';
|
|
5
|
+
import { sld } from './chunk-LGABFD3L.js';
|
|
6
|
+
import { entity } from './chunk-H4MT5TJP.js';
|
|
7
|
+
import { fishbone } from './chunk-HAZALB7U.js';
|
|
8
|
+
import { venn } from './chunk-FBS3PACU.js';
|
|
9
|
+
import { flowchart, layoutFlowchart } from './chunk-7F76AWOI.js';
|
|
10
|
+
import { genogram } from './chunk-DOK7LKLO.js';
|
|
11
|
+
import { ecomap } from './chunk-YS6CGUNH.js';
|
|
12
|
+
import { pedigree } from './chunk-N5B242WY.js';
|
|
13
13
|
import { parseFrontmatter } from './chunk-2KTQ75LN.js';
|
|
14
|
-
import { phylo } from './chunk-
|
|
15
|
-
import { sociogram } from './chunk-
|
|
14
|
+
import { phylo } from './chunk-MVIEIKOI.js';
|
|
15
|
+
import { sociogram } from './chunk-3VB5AT4R.js';
|
|
16
16
|
import { timing } from './chunk-6URNSB6G.js';
|
|
17
|
-
import { logic } from './chunk-
|
|
18
|
-
import { resolveBaseTheme, resolveTimelineTheme, cssCustomProperties, resolveMindmapTheme } from './chunk-
|
|
19
|
-
import { matchQuotedTitle } from './chunk-5IKOLUWK.js';
|
|
20
|
-
import { el, escapeXml,
|
|
17
|
+
import { logic } from './chunk-3IE7KZY4.js';
|
|
18
|
+
import { resolveBaseTheme, resolveTimelineTheme, cssCustomProperties, resolvePetriTheme, resolveNetworkTheme, resolveMindmapTheme } from './chunk-LRI4RH2N.js';
|
|
19
|
+
import { matchQuotedTitle, stripQuotes } from './chunk-5IKOLUWK.js';
|
|
20
|
+
import { el, escapeXml, group, rect, text, line, path, circle, polygon, title, desc, svgRoot, defs, multilineText } from './chunk-SYYBKDL7.js';
|
|
21
21
|
|
|
22
22
|
// src/diagrams/decisiontree/parser.ts
|
|
23
23
|
var DTreeParseError = class extends Error {
|
|
@@ -3935,7 +3935,7 @@ function renderLayout(layout) {
|
|
|
3935
3935
|
var state = {
|
|
3936
3936
|
type: "state",
|
|
3937
3937
|
detect(text2) {
|
|
3938
|
-
return /^\s*state\b/i.test(text2);
|
|
3938
|
+
return /^\s*(?:state\b|stateDiagram(?:-v2)?\b)/i.test(text2);
|
|
3939
3939
|
},
|
|
3940
3940
|
parse: parseStateDiagram,
|
|
3941
3941
|
render(text2, config) {
|
|
@@ -5459,6 +5459,113 @@ function parseTag(tag) {
|
|
|
5459
5459
|
return { letter: tag.slice(0, idx), number: tag.slice(idx + 1) };
|
|
5460
5460
|
}
|
|
5461
5461
|
|
|
5462
|
+
// src/diagrams/pid/lint.ts
|
|
5463
|
+
function lintPid(text2) {
|
|
5464
|
+
let ast;
|
|
5465
|
+
try {
|
|
5466
|
+
ast = parsePid(text2);
|
|
5467
|
+
} catch {
|
|
5468
|
+
return [];
|
|
5469
|
+
}
|
|
5470
|
+
return lintPidAst(ast);
|
|
5471
|
+
}
|
|
5472
|
+
var SIGNAL_TYPES = /* @__PURE__ */ new Set([
|
|
5473
|
+
"pneumatic",
|
|
5474
|
+
"electric",
|
|
5475
|
+
"software",
|
|
5476
|
+
"capillary",
|
|
5477
|
+
"hydraulic",
|
|
5478
|
+
"mechanical"
|
|
5479
|
+
]);
|
|
5480
|
+
function idLetters(tag) {
|
|
5481
|
+
const head = tag.split("-")[0] ?? tag;
|
|
5482
|
+
return head.replace(/[^A-Za-z]/g, "").toUpperCase();
|
|
5483
|
+
}
|
|
5484
|
+
function isController(tag) {
|
|
5485
|
+
return idLetters(tag).slice(1).includes("C");
|
|
5486
|
+
}
|
|
5487
|
+
function isTransmitter(tag) {
|
|
5488
|
+
return idLetters(tag).slice(1).includes("T");
|
|
5489
|
+
}
|
|
5490
|
+
function lintPidAst(ast) {
|
|
5491
|
+
const out = [];
|
|
5492
|
+
const instByTag = /* @__PURE__ */ new Map();
|
|
5493
|
+
for (const inst of ast.instruments) instByTag.set(inst.tag, inst);
|
|
5494
|
+
const controlValveIds = new Set(
|
|
5495
|
+
ast.equipment.filter((e) => e.equipType === "valve_control").map((e) => e.id)
|
|
5496
|
+
);
|
|
5497
|
+
const signalAdj = /* @__PURE__ */ new Map();
|
|
5498
|
+
const addEdge = (a, b) => {
|
|
5499
|
+
if (!signalAdj.has(a)) signalAdj.set(a, /* @__PURE__ */ new Set());
|
|
5500
|
+
signalAdj.get(a).add(b);
|
|
5501
|
+
};
|
|
5502
|
+
for (const ln of ast.lines) {
|
|
5503
|
+
if (!SIGNAL_TYPES.has(ln.lineType)) continue;
|
|
5504
|
+
const a = ln.from.id;
|
|
5505
|
+
const b = ln.to.id;
|
|
5506
|
+
if (instByTag.has(a) && instByTag.has(b)) {
|
|
5507
|
+
addEdge(a, b);
|
|
5508
|
+
addEdge(b, a);
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
5511
|
+
const canReachController = (start) => {
|
|
5512
|
+
const seen = /* @__PURE__ */ new Set([start]);
|
|
5513
|
+
const queue = [start];
|
|
5514
|
+
while (queue.length) {
|
|
5515
|
+
const cur = queue.shift();
|
|
5516
|
+
if (cur !== start && isController(cur)) return true;
|
|
5517
|
+
for (const nxt of signalAdj.get(cur) ?? []) {
|
|
5518
|
+
if (!seen.has(nxt)) {
|
|
5519
|
+
seen.add(nxt);
|
|
5520
|
+
queue.push(nxt);
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
return false;
|
|
5525
|
+
};
|
|
5526
|
+
for (const inst of ast.instruments) {
|
|
5527
|
+
if (!inst.measures || isController(inst.tag)) continue;
|
|
5528
|
+
if (!canReachController(inst.tag)) {
|
|
5529
|
+
out.push({
|
|
5530
|
+
severity: "warning",
|
|
5531
|
+
code: "PID_LOOP_INCOMPLETE",
|
|
5532
|
+
message: `instrument loop ${inst.tag} has no signal path to a controller`,
|
|
5533
|
+
hint: `${inst.tag} measures '${inst.measures}' but no signal line (electric/pneumatic/\u2026) connects it to a controller instrument (e.g. a tag with a 'C' function letter such as FIC). Add a signal line from ${inst.tag} to the controller.`,
|
|
5534
|
+
fatal: false
|
|
5535
|
+
});
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
for (const ln of ast.lines) {
|
|
5539
|
+
if (!SIGNAL_TYPES.has(ln.lineType)) continue;
|
|
5540
|
+
const a = ln.from.id;
|
|
5541
|
+
const b = ln.to.id;
|
|
5542
|
+
const aInst = instByTag.get(a);
|
|
5543
|
+
const bInst = instByTag.get(b);
|
|
5544
|
+
const txToCtrl = aInst && isTransmitter(a) && !isController(a) && bInst && isController(b) || bInst && isTransmitter(b) && !isController(b) && aInst && isController(a);
|
|
5545
|
+
if (txToCtrl && ln.lineType !== "electric") {
|
|
5546
|
+
out.push({
|
|
5547
|
+
severity: "warning",
|
|
5548
|
+
code: "PID_SIGNAL_TYPE_MISMATCH",
|
|
5549
|
+
message: `signal line ${a}\u2192${b} is '${ln.lineType}' but a transmitter\u2192controller link should be 'electric' (ISA-5.1 \xA75.2)`,
|
|
5550
|
+
hint: `Set [type: electric] on this line, or rename the devices if the connection is not a transmitter\u2192controller measurement signal.`,
|
|
5551
|
+
fatal: false
|
|
5552
|
+
});
|
|
5553
|
+
continue;
|
|
5554
|
+
}
|
|
5555
|
+
const ctrlToValve = aInst && isController(a) && controlValveIds.has(b) || bInst && isController(b) && controlValveIds.has(a);
|
|
5556
|
+
if (ctrlToValve && ln.lineType !== "pneumatic") {
|
|
5557
|
+
out.push({
|
|
5558
|
+
severity: "warning",
|
|
5559
|
+
code: "PID_SIGNAL_TYPE_MISMATCH",
|
|
5560
|
+
message: `signal line ${a}\u2192${b} is '${ln.lineType}' but a controller\u2192control-valve link should be 'pneumatic' (ISA-5.1 \xA75.2)`,
|
|
5561
|
+
hint: `Set [type: pneumatic] on this line, or use [type: electric] only if the valve has an electric actuator.`,
|
|
5562
|
+
fatal: false
|
|
5563
|
+
});
|
|
5564
|
+
}
|
|
5565
|
+
}
|
|
5566
|
+
return out;
|
|
5567
|
+
}
|
|
5568
|
+
|
|
5462
5569
|
// src/diagrams/pid/index.ts
|
|
5463
5570
|
var pid = {
|
|
5464
5571
|
type: "pid",
|
|
@@ -5466,6 +5573,7 @@ var pid = {
|
|
|
5466
5573
|
return /^\s*pid\b/i.test(text2);
|
|
5467
5574
|
},
|
|
5468
5575
|
parse: parsePid,
|
|
5576
|
+
lint: lintPid,
|
|
5469
5577
|
render(text2, config) {
|
|
5470
5578
|
const ast = parsePid(text2);
|
|
5471
5579
|
return renderPidAST(ast);
|
|
@@ -6510,7 +6618,7 @@ function layoutPrisma(ast) {
|
|
|
6510
6618
|
lines: incResized.wrappedLines
|
|
6511
6619
|
});
|
|
6512
6620
|
cursorY = incY + incResized.height + PRISMA_CONST.OUTER_PAD_BOTTOM;
|
|
6513
|
-
const
|
|
6621
|
+
const arrow2 = (x1, y1, x2, y2) => `M ${x1} ${y1} L ${x2} ${y2}`;
|
|
6514
6622
|
const mainColRight = mainColLeft + mainW;
|
|
6515
6623
|
const rightColArrowEnd = rightColLeft - PRISMA_CONST.ARROWHEAD_LEN;
|
|
6516
6624
|
if (idRemovedSpec) {
|
|
@@ -6519,7 +6627,7 @@ function layoutPrisma(ast) {
|
|
|
6519
6627
|
kind: "exclusion",
|
|
6520
6628
|
from: "id-databases",
|
|
6521
6629
|
to: "id-removed",
|
|
6522
|
-
d:
|
|
6630
|
+
d: arrow2(mainColRight, ay, rightColArrowEnd, ay)
|
|
6523
6631
|
});
|
|
6524
6632
|
}
|
|
6525
6633
|
if (dual && idRightSpec) {
|
|
@@ -6528,7 +6636,7 @@ function layoutPrisma(ast) {
|
|
|
6528
6636
|
kind: "merge-trunk",
|
|
6529
6637
|
from: "id-databases",
|
|
6530
6638
|
to: "screening",
|
|
6531
|
-
d:
|
|
6639
|
+
d: arrow2(mainColCenterX, idRowY + idDbBox.height, mainColCenterX, screeningY - PRISMA_CONST.ARROWHEAD_LEN)
|
|
6532
6640
|
});
|
|
6533
6641
|
edges.push({
|
|
6534
6642
|
kind: "merge-leg",
|
|
@@ -6541,7 +6649,7 @@ function layoutPrisma(ast) {
|
|
|
6541
6649
|
kind: "main",
|
|
6542
6650
|
from: "id-databases",
|
|
6543
6651
|
to: "screening",
|
|
6544
|
-
d:
|
|
6652
|
+
d: arrow2(mainColCenterX, idRowY + idDbBox.height, mainColCenterX, screeningY - PRISMA_CONST.ARROWHEAD_LEN)
|
|
6545
6653
|
});
|
|
6546
6654
|
}
|
|
6547
6655
|
if (previousBoxBottom >= 0) {
|
|
@@ -6549,34 +6657,34 @@ function layoutPrisma(ast) {
|
|
|
6549
6657
|
kind: "previous",
|
|
6550
6658
|
from: "previous",
|
|
6551
6659
|
to: "id-databases",
|
|
6552
|
-
d:
|
|
6660
|
+
d: arrow2(previousBoxCenterX, previousBoxBottom, mainColCenterX, headerRowY - PRISMA_CONST.ARROWHEAD_LEN)
|
|
6553
6661
|
});
|
|
6554
6662
|
}
|
|
6555
6663
|
edges.push({
|
|
6556
6664
|
kind: "main",
|
|
6557
6665
|
from: "screening",
|
|
6558
6666
|
to: "eligibility",
|
|
6559
|
-
d:
|
|
6667
|
+
d: arrow2(mainColCenterX, screeningY + screeningResized.height, mainColCenterX, eligY - PRISMA_CONST.ARROWHEAD_LEN)
|
|
6560
6668
|
});
|
|
6561
6669
|
edges.push({
|
|
6562
6670
|
kind: "main",
|
|
6563
6671
|
from: "eligibility",
|
|
6564
6672
|
to: "included",
|
|
6565
|
-
d:
|
|
6673
|
+
d: arrow2(mainColCenterX, eligY + eligResized.height, mainColCenterX, incY - PRISMA_CONST.ARROWHEAD_LEN)
|
|
6566
6674
|
});
|
|
6567
6675
|
const screeningArrowY = screeningY + screeningResized.height / 2;
|
|
6568
6676
|
edges.push({
|
|
6569
6677
|
kind: "exclusion",
|
|
6570
6678
|
from: "screening",
|
|
6571
6679
|
to: "screening-excluded",
|
|
6572
|
-
d:
|
|
6680
|
+
d: arrow2(mainColRight, screeningArrowY, rightColArrowEnd, screeningArrowY)
|
|
6573
6681
|
});
|
|
6574
6682
|
const eligArrowY = eligY + eligResized.height / 2;
|
|
6575
6683
|
edges.push({
|
|
6576
6684
|
kind: "exclusion",
|
|
6577
6685
|
from: "eligibility",
|
|
6578
6686
|
to: "eligibility-excluded",
|
|
6579
|
-
d:
|
|
6687
|
+
d: arrow2(mainColRight, eligArrowY, rightColArrowEnd, eligArrowY)
|
|
6580
6688
|
});
|
|
6581
6689
|
let pageHeight = cursorY;
|
|
6582
6690
|
if (warnings.length > 0) {
|
|
@@ -7184,7 +7292,7 @@ function parseHeader(state2) {
|
|
|
7184
7292
|
if (!m) break;
|
|
7185
7293
|
const key = m[1].toLowerCase();
|
|
7186
7294
|
const valueRaw = m[2].trim();
|
|
7187
|
-
const value =
|
|
7295
|
+
const value = stripQuotes2(valueRaw);
|
|
7188
7296
|
if (key === "title") state2.ast.title = value;
|
|
7189
7297
|
else if (key === "system") state2.ast.system = value;
|
|
7190
7298
|
else if (key === "direction") {
|
|
@@ -7202,7 +7310,7 @@ function parseHeader(state2) {
|
|
|
7202
7310
|
state2.i++;
|
|
7203
7311
|
}
|
|
7204
7312
|
}
|
|
7205
|
-
function
|
|
7313
|
+
function stripQuotes2(s) {
|
|
7206
7314
|
const t = s.trim();
|
|
7207
7315
|
if (t.startsWith('"') && t.endsWith('"') && t.length >= 2) return t.slice(1, -1);
|
|
7208
7316
|
return t;
|
|
@@ -7239,7 +7347,7 @@ function parseActorDecl(ln, state2) {
|
|
|
7239
7347
|
id = asMatch[1];
|
|
7240
7348
|
rest = rest.slice(0, asMatch.index).trim();
|
|
7241
7349
|
}
|
|
7242
|
-
const name =
|
|
7350
|
+
const name = stripQuotes2(rest);
|
|
7243
7351
|
if (!name) throw new UsecaseParseError(`actor declaration missing name`, ln.line);
|
|
7244
7352
|
const actorId = id ?? defaultIdFor(name);
|
|
7245
7353
|
declareId(state2, actorId, "actor", ln.line);
|
|
@@ -7275,7 +7383,7 @@ function parseUsecaseDecl(ln, state2) {
|
|
|
7275
7383
|
id = asMatch[1];
|
|
7276
7384
|
rest = rest.slice(0, asMatch.index).trim();
|
|
7277
7385
|
}
|
|
7278
|
-
const name =
|
|
7386
|
+
const name = stripQuotes2(rest);
|
|
7279
7387
|
if (!name) throw new UsecaseParseError(`usecase declaration missing name`, ln.line);
|
|
7280
7388
|
const ucId = id ?? defaultIdFor(name);
|
|
7281
7389
|
declareId(state2, ucId, "usecase", ln.line);
|
|
@@ -8030,13 +8138,13 @@ function layoutUsecase(ast) {
|
|
|
8030
8138
|
pb = perimeter(r.target, a.cx, a.cy);
|
|
8031
8139
|
}
|
|
8032
8140
|
const dashed = r.kind === "include" || r.kind === "extend";
|
|
8033
|
-
let
|
|
8034
|
-
if (r.kind === "directed" || r.kind === "include" || r.kind === "extend")
|
|
8035
|
-
else if (r.kind === "generalization")
|
|
8141
|
+
let arrowKind2 = "none";
|
|
8142
|
+
if (r.kind === "directed" || r.kind === "include" || r.kind === "extend") arrowKind2 = "open";
|
|
8143
|
+
else if (r.kind === "generalization") arrowKind2 = "hollow";
|
|
8036
8144
|
const edge = {
|
|
8037
8145
|
relation: r,
|
|
8038
8146
|
d: `M ${round(pa.x)} ${round(pa.y)} L ${round(pb.x)} ${round(pb.y)}`,
|
|
8039
|
-
arrowKind,
|
|
8147
|
+
arrowKind: arrowKind2,
|
|
8040
8148
|
dashed
|
|
8041
8149
|
};
|
|
8042
8150
|
if (r.kind === "include" || r.kind === "extend") {
|
|
@@ -8519,7 +8627,7 @@ function preprocess7(src) {
|
|
|
8519
8627
|
}
|
|
8520
8628
|
return out;
|
|
8521
8629
|
}
|
|
8522
|
-
function
|
|
8630
|
+
function stripQuotes3(s) {
|
|
8523
8631
|
const t = s.trim();
|
|
8524
8632
|
if (t.length >= 2 && (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'"))) {
|
|
8525
8633
|
return t.slice(1, -1);
|
|
@@ -8537,7 +8645,7 @@ function parseHeaderLine(ln, ast) {
|
|
|
8537
8645
|
if (!m) return false;
|
|
8538
8646
|
const key = m[1].toLowerCase();
|
|
8539
8647
|
const valueRaw = m[2].trim();
|
|
8540
|
-
const value =
|
|
8648
|
+
const value = stripQuotes3(valueRaw);
|
|
8541
8649
|
switch (key) {
|
|
8542
8650
|
case "title":
|
|
8543
8651
|
ast.title = value;
|
|
@@ -8666,23 +8774,23 @@ function parseTaskLine(ln, ast) {
|
|
|
8666
8774
|
milestone = true;
|
|
8667
8775
|
return "";
|
|
8668
8776
|
}).trim();
|
|
8669
|
-
const
|
|
8777
|
+
const markers5 = [];
|
|
8670
8778
|
KEY_RE.lastIndex = 0;
|
|
8671
8779
|
let mm;
|
|
8672
8780
|
while ((mm = KEY_RE.exec(rest)) !== null) {
|
|
8673
|
-
|
|
8781
|
+
markers5.push({ key: mm[1].toLowerCase(), start: mm.index, valStart: mm.index + mm[0].length });
|
|
8674
8782
|
}
|
|
8675
8783
|
const values = {};
|
|
8676
|
-
for (let i = 0; i <
|
|
8677
|
-
const cur =
|
|
8678
|
-
const nextStart = i + 1 <
|
|
8784
|
+
for (let i = 0; i < markers5.length; i++) {
|
|
8785
|
+
const cur = markers5[i];
|
|
8786
|
+
const nextStart = i + 1 < markers5.length ? markers5[i + 1].start : rest.length;
|
|
8679
8787
|
const val = rest.slice(cur.valStart, nextStart).trim();
|
|
8680
8788
|
if (cur.key in values) {
|
|
8681
8789
|
throw new PertParseError(`duplicate '${cur.key}:' on task '${id}'`, ln.line);
|
|
8682
8790
|
}
|
|
8683
8791
|
values[cur.key] = val;
|
|
8684
8792
|
}
|
|
8685
|
-
const preamble = (
|
|
8793
|
+
const preamble = (markers5.length ? rest.slice(0, markers5[0].start) : rest).trim();
|
|
8686
8794
|
if (preamble) {
|
|
8687
8795
|
throw new PertParseError(`unexpected text '${preamble}' in task '${id}'`, ln.line);
|
|
8688
8796
|
}
|
|
@@ -8710,7 +8818,7 @@ function parseTaskLine(ln, ast) {
|
|
|
8710
8818
|
}
|
|
8711
8819
|
const tags = values.tags ? values.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
8712
8820
|
const className = values.class ? values.class.trim() : void 0;
|
|
8713
|
-
const lane = values.lane ?
|
|
8821
|
+
const lane = values.lane ? stripQuotes3(values.lane) : void 0;
|
|
8714
8822
|
const task = {
|
|
8715
8823
|
id,
|
|
8716
8824
|
label,
|
|
@@ -10085,186 +10193,3832 @@ function renderLanes(lanes, width) {
|
|
|
10085
10193
|
text({ class: "sx-pert-lane-label", x: 16, y: ln.y + ln.height / 2 + 4, "text-anchor": "start" }, ln.name)
|
|
10086
10194
|
);
|
|
10087
10195
|
}
|
|
10088
|
-
return group({ class: "sx-pert-lane" }, parts);
|
|
10196
|
+
return group({ class: "sx-pert-lane" }, parts);
|
|
10197
|
+
}
|
|
10198
|
+
function renderSentinel(s) {
|
|
10199
|
+
return group({ class: "sx-pert-sentinel", "data-id": s.id }, [
|
|
10200
|
+
circle({ cx: s.cx, cy: s.cy, r: s.r }),
|
|
10201
|
+
text({ x: s.cx, y: s.cy + 3.5, "text-anchor": "middle" }, s.label)
|
|
10202
|
+
]);
|
|
10203
|
+
}
|
|
10204
|
+
function renderAxis2(axis, gridTop) {
|
|
10205
|
+
const parts = [];
|
|
10206
|
+
for (const tk of axis.ticks) {
|
|
10207
|
+
if (tk.major) {
|
|
10208
|
+
parts.push(line({ class: "sx-pert-grid", x1: tk.pos, y1: gridTop, x2: tk.pos, y2: axis.baseline }));
|
|
10209
|
+
}
|
|
10210
|
+
}
|
|
10211
|
+
parts.push(line({ class: "baseline", x1: axis.start, y1: axis.baseline, x2: axis.end, y2: axis.baseline }));
|
|
10212
|
+
for (const tk of axis.ticks) {
|
|
10213
|
+
const len = tk.major ? 7 : 4;
|
|
10214
|
+
parts.push(line({ x1: tk.pos, y1: axis.baseline, x2: tk.pos, y2: axis.baseline + len }));
|
|
10215
|
+
if (tk.major) {
|
|
10216
|
+
parts.push(
|
|
10217
|
+
text({ x: tk.pos, y: axis.baseline + 18, "text-anchor": "middle" }, fmtVal(tk.value))
|
|
10218
|
+
);
|
|
10219
|
+
}
|
|
10220
|
+
}
|
|
10221
|
+
return group({ class: "sx-pert-axis" }, parts);
|
|
10222
|
+
}
|
|
10223
|
+
function renderAoa(aoa) {
|
|
10224
|
+
const arcEls = [];
|
|
10225
|
+
const labelEls = [];
|
|
10226
|
+
for (const a of aoa.arcs) {
|
|
10227
|
+
const cls = `sx-pert-aoa-arc${a.dummy ? " dummy" : ""}${a.critical ? " critical" : ""}`;
|
|
10228
|
+
const marker = a.critical ? "url(#sx-pert-arrow-crit)" : "url(#sx-pert-arrow)";
|
|
10229
|
+
arcEls.push(
|
|
10230
|
+
path({
|
|
10231
|
+
class: cls,
|
|
10232
|
+
d: a.d,
|
|
10233
|
+
"marker-end": marker,
|
|
10234
|
+
"data-from": a.from,
|
|
10235
|
+
"data-to": a.to,
|
|
10236
|
+
"data-task": a.taskId ?? "",
|
|
10237
|
+
"data-dummy": String(a.dummy),
|
|
10238
|
+
"data-critical": String(a.critical)
|
|
10239
|
+
})
|
|
10240
|
+
);
|
|
10241
|
+
if (!a.dummy && a.label) {
|
|
10242
|
+
const critCls = a.critical ? " critical" : "";
|
|
10243
|
+
const durStr = fmtVal(a.duration ?? 0);
|
|
10244
|
+
const w = Math.max(a.label.length * 6.2, durStr.length * 6) + 8;
|
|
10245
|
+
labelEls.push(
|
|
10246
|
+
rect({ class: "sx-pert-edge-halo", x: a.labelX - w / 2, y: a.labelY - 16, width: w, height: 31, rx: 3, ry: 3 })
|
|
10247
|
+
);
|
|
10248
|
+
labelEls.push(
|
|
10249
|
+
text({ class: `sx-pert-aoa-name${critCls}`, x: a.labelX, y: a.labelY - 4, "text-anchor": "middle" }, a.label)
|
|
10250
|
+
);
|
|
10251
|
+
labelEls.push(
|
|
10252
|
+
text({ class: `sx-pert-aoa-dur${critCls}`, x: a.labelX, y: a.labelY + 12, "text-anchor": "middle" }, durStr)
|
|
10253
|
+
);
|
|
10254
|
+
}
|
|
10255
|
+
}
|
|
10256
|
+
const eventEls = [];
|
|
10257
|
+
for (const e of aoa.events) {
|
|
10258
|
+
eventEls.push(
|
|
10259
|
+
group({ class: `sx-pert-aoa-event${e.critical ? " critical" : ""}`, "data-event": e.id }, [
|
|
10260
|
+
circle({ cx: e.x, cy: e.y, r: e.r }),
|
|
10261
|
+
text({ x: e.x, y: e.y + 4.5, "text-anchor": "middle" }, String(e.id))
|
|
10262
|
+
])
|
|
10263
|
+
);
|
|
10264
|
+
}
|
|
10265
|
+
return group({ class: "sx-pert-aoa" }, [
|
|
10266
|
+
group({ class: "sx-pert-aoa-arcs" }, arcEls),
|
|
10267
|
+
group({ class: "sx-pert-aoa-labels" }, labelEls),
|
|
10268
|
+
group({ class: "sx-pert-aoa-events" }, eventEls)
|
|
10269
|
+
]);
|
|
10270
|
+
}
|
|
10271
|
+
function summaryText(summary) {
|
|
10272
|
+
const u = unitWord(summary.unit);
|
|
10273
|
+
const dur = `${fmtVal(summary.projectDuration)}${u ? " " + u : ""}`;
|
|
10274
|
+
const sigma = summary.projectStdDev !== void 0 ? ` \xB7 \u03C3 \u2248 ${fmtVal(summary.projectStdDev)}` : "";
|
|
10275
|
+
const plain = `Project duration ${dur} \xB7 ${summary.taskCount} tasks \xB7 ${summary.depCount} dependencies \xB7 ${summary.criticalCount} critical${sigma}`;
|
|
10276
|
+
const critPath = summary.criticalPath.map((id) => id).join(" \u2192 ");
|
|
10277
|
+
return { plain, critPath };
|
|
10278
|
+
}
|
|
10279
|
+
function renderPertLayout(layout, config) {
|
|
10280
|
+
const t = resolveBaseTheme(config?.theme ?? "default");
|
|
10281
|
+
const children = [];
|
|
10282
|
+
const cp = layout.summary.criticalPath;
|
|
10283
|
+
children.push(title(`PERT network${layout.title ? " \u2014 " + layout.title : ""}`));
|
|
10284
|
+
children.push(
|
|
10285
|
+
desc(
|
|
10286
|
+
`${layout.summary.taskCount} activities, project duration ${fmtVal(layout.summary.projectDuration)} ${unitWord(layout.unit)}` + (cp.length ? `, critical path ${cp.join(" \u2192 ")}` : "") + "."
|
|
10287
|
+
)
|
|
10288
|
+
);
|
|
10289
|
+
children.push(el("style", {}, buildCss4(t)));
|
|
10290
|
+
children.push(markers2());
|
|
10291
|
+
if (layout.title) {
|
|
10292
|
+
children.push(
|
|
10293
|
+
text({ x: layout.width / 2, y: 26, class: "sx-pert-title", "text-anchor": "middle" }, layout.title)
|
|
10294
|
+
);
|
|
10295
|
+
}
|
|
10296
|
+
if (layout.aoa) {
|
|
10297
|
+
children.push(renderAoa(layout.aoa));
|
|
10298
|
+
} else {
|
|
10299
|
+
if (layout.lanes && layout.lanes.length) {
|
|
10300
|
+
children.push(renderLanes(layout.lanes, layout.width));
|
|
10301
|
+
}
|
|
10302
|
+
if (layout.axis) {
|
|
10303
|
+
let minBoxY = Infinity;
|
|
10304
|
+
for (const b of layout.boxes) minBoxY = Math.min(minBoxY, b.y);
|
|
10305
|
+
children.push(renderAxis2(layout.axis, isFinite(minBoxY) ? minBoxY : layout.axis.baseline - 20));
|
|
10306
|
+
}
|
|
10307
|
+
const edgeEls = [];
|
|
10308
|
+
for (const e of layout.edges) edgeEls.push(renderEdge3(e));
|
|
10309
|
+
children.push(group({ class: "sx-pert-edges" }, edgeEls));
|
|
10310
|
+
if (layout.sentinels.length) {
|
|
10311
|
+
children.push(group({ class: "sx-pert-sentinels" }, layout.sentinels.map(renderSentinel)));
|
|
10312
|
+
}
|
|
10313
|
+
const boxEls = [];
|
|
10314
|
+
for (const b of layout.boxes) boxEls.push(renderBox2(b, layout.mode));
|
|
10315
|
+
children.push(group({ class: "sx-pert-tasks" }, boxEls));
|
|
10316
|
+
const labelEls = [];
|
|
10317
|
+
for (const e of layout.edges) {
|
|
10318
|
+
const lbl = renderEdgeLabel2(e);
|
|
10319
|
+
if (lbl) labelEls.push(lbl);
|
|
10320
|
+
}
|
|
10321
|
+
children.push(group({ class: "sx-pert-labels" }, labelEls));
|
|
10322
|
+
}
|
|
10323
|
+
const { plain, critPath } = summaryText(layout.summary);
|
|
10324
|
+
const footerY = layout.height - 12;
|
|
10325
|
+
const footerParts = [
|
|
10326
|
+
text({ class: "sx-pert-summary", x: PERT_PAD, y: footerY, "text-anchor": "start" }, plain)
|
|
10327
|
+
];
|
|
10328
|
+
if (critPath) {
|
|
10329
|
+
footerParts.push(
|
|
10330
|
+
text(
|
|
10331
|
+
{ class: "sx-pert-summary crit", x: layout.width - PERT_PAD, y: footerY, "text-anchor": "end" },
|
|
10332
|
+
`Critical path: ${critPath}`
|
|
10333
|
+
)
|
|
10334
|
+
);
|
|
10335
|
+
}
|
|
10336
|
+
children.push(group({ class: "sx-pert-footer" }, footerParts));
|
|
10337
|
+
return svgRoot(
|
|
10338
|
+
{
|
|
10339
|
+
class: "sx-pert",
|
|
10340
|
+
role: "img",
|
|
10341
|
+
"aria-label": escapeXml(layout.title ?? "PERT network diagram"),
|
|
10342
|
+
width: layout.width,
|
|
10343
|
+
height: layout.height,
|
|
10344
|
+
viewBox: `0 0 ${layout.width} ${layout.height}`,
|
|
10345
|
+
"data-diagram-type": "pert"
|
|
10346
|
+
},
|
|
10347
|
+
children
|
|
10348
|
+
);
|
|
10349
|
+
}
|
|
10350
|
+
var PERT_PAD = 24;
|
|
10351
|
+
function renderPert(textOrAst, config) {
|
|
10352
|
+
const ast = typeof textOrAst === "string" ? parsePert(textOrAst) : textOrAst;
|
|
10353
|
+
const schedule = schedulePert(ast);
|
|
10354
|
+
const layout = layoutPert(ast, schedule);
|
|
10355
|
+
return renderPertLayout(layout, config);
|
|
10356
|
+
}
|
|
10357
|
+
|
|
10358
|
+
// src/diagrams/pert/index.ts
|
|
10359
|
+
var pert = {
|
|
10360
|
+
type: "pert",
|
|
10361
|
+
detect(text2) {
|
|
10362
|
+
for (const raw of text2.split(/\r?\n/)) {
|
|
10363
|
+
const t = raw.trim();
|
|
10364
|
+
if (!t) continue;
|
|
10365
|
+
if (t.startsWith("#") || t.startsWith("//")) continue;
|
|
10366
|
+
return /^pert\b/i.test(t);
|
|
10367
|
+
}
|
|
10368
|
+
return false;
|
|
10369
|
+
},
|
|
10370
|
+
parse: parsePert,
|
|
10371
|
+
render(text2, config) {
|
|
10372
|
+
return renderPert(text2, config);
|
|
10373
|
+
}
|
|
10374
|
+
};
|
|
10375
|
+
|
|
10376
|
+
// src/diagrams/sequence/parser.ts
|
|
10377
|
+
var SequenceParseError = class extends Error {
|
|
10378
|
+
line;
|
|
10379
|
+
constructor(message, line2) {
|
|
10380
|
+
super(line2 ? `${message} (line ${line2})` : message);
|
|
10381
|
+
this.name = "SequenceParseError";
|
|
10382
|
+
this.line = line2;
|
|
10383
|
+
}
|
|
10384
|
+
};
|
|
10385
|
+
var KINDS = /* @__PURE__ */ new Set([
|
|
10386
|
+
"participant",
|
|
10387
|
+
"actor",
|
|
10388
|
+
"boundary",
|
|
10389
|
+
"control",
|
|
10390
|
+
"entity",
|
|
10391
|
+
"database",
|
|
10392
|
+
"collections",
|
|
10393
|
+
"queue"
|
|
10394
|
+
]);
|
|
10395
|
+
var SIMPLE_FRAG = /* @__PURE__ */ new Set([
|
|
10396
|
+
"opt",
|
|
10397
|
+
"loop",
|
|
10398
|
+
"break",
|
|
10399
|
+
"critical",
|
|
10400
|
+
"neg",
|
|
10401
|
+
"ignore",
|
|
10402
|
+
"consider",
|
|
10403
|
+
"assert"
|
|
10404
|
+
]);
|
|
10405
|
+
var MULTI_FRAG = /* @__PURE__ */ new Set(["par", "seq", "strict"]);
|
|
10406
|
+
var MSGSET_FRAG = /* @__PURE__ */ new Set(["ignore", "consider"]);
|
|
10407
|
+
var ALL_FRAG = /* @__PURE__ */ new Set([
|
|
10408
|
+
"alt",
|
|
10409
|
+
...SIMPLE_FRAG,
|
|
10410
|
+
...MULTI_FRAG
|
|
10411
|
+
]);
|
|
10412
|
+
var ARROW_RE = /(-->|->>|o->|-x|->)/;
|
|
10413
|
+
function arrowKind(token) {
|
|
10414
|
+
switch (token) {
|
|
10415
|
+
case "-->":
|
|
10416
|
+
return "reply";
|
|
10417
|
+
case "->>":
|
|
10418
|
+
return "async";
|
|
10419
|
+
case "o->":
|
|
10420
|
+
return "found";
|
|
10421
|
+
case "-x":
|
|
10422
|
+
return "lost";
|
|
10423
|
+
default:
|
|
10424
|
+
return "sync";
|
|
10425
|
+
}
|
|
10426
|
+
}
|
|
10427
|
+
function firstWord(s) {
|
|
10428
|
+
const m = /^(\S+)/.exec(s);
|
|
10429
|
+
return m ? m[1] : "";
|
|
10430
|
+
}
|
|
10431
|
+
function extractGuard(rest) {
|
|
10432
|
+
const t = rest.trim();
|
|
10433
|
+
if (!t) return void 0;
|
|
10434
|
+
if (t.startsWith("[") && t.endsWith("]")) return t.slice(1, -1).trim();
|
|
10435
|
+
if (t.startsWith("(") && t.endsWith(")")) return t.slice(1, -1).trim();
|
|
10436
|
+
return t;
|
|
10437
|
+
}
|
|
10438
|
+
var SequenceParser = class {
|
|
10439
|
+
lines;
|
|
10440
|
+
i = 0;
|
|
10441
|
+
order = [];
|
|
10442
|
+
byId = /* @__PURE__ */ new Map();
|
|
10443
|
+
warnings = [];
|
|
10444
|
+
constructor(source) {
|
|
10445
|
+
this.lines = source.split(/\r?\n/).map((raw, idx) => ({
|
|
10446
|
+
text: raw.trim(),
|
|
10447
|
+
n: idx + 1
|
|
10448
|
+
}));
|
|
10449
|
+
}
|
|
10450
|
+
parse() {
|
|
10451
|
+
const header = this.consumeHeader();
|
|
10452
|
+
const ast = {
|
|
10453
|
+
type: "sequence",
|
|
10454
|
+
participants: [],
|
|
10455
|
+
statements: [],
|
|
10456
|
+
warnings: this.warnings
|
|
10457
|
+
};
|
|
10458
|
+
if (header.title) ast.title = header.title;
|
|
10459
|
+
const statements = this.parseBlock();
|
|
10460
|
+
const leftover = this.peek();
|
|
10461
|
+
if (leftover) {
|
|
10462
|
+
const fw = firstWord(leftover.text).toLowerCase();
|
|
10463
|
+
throw new SequenceParseError(
|
|
10464
|
+
`'${fw}' without a matching combined fragment`,
|
|
10465
|
+
leftover.n
|
|
10466
|
+
);
|
|
10467
|
+
}
|
|
10468
|
+
if (this.autonumberSpec) ast.autonumber = this.autonumberSpec;
|
|
10469
|
+
ast.statements = statements;
|
|
10470
|
+
ast.participants = this.order.map((id) => this.byId.get(id));
|
|
10471
|
+
return ast;
|
|
10472
|
+
}
|
|
10473
|
+
autonumberSpec;
|
|
10474
|
+
// ── line cursor ──────────────────────────────────────────────
|
|
10475
|
+
peek() {
|
|
10476
|
+
while (this.i < this.lines.length) {
|
|
10477
|
+
const ln = this.lines[this.i];
|
|
10478
|
+
if (ln.text === "" || ln.text.startsWith("#") || ln.text.startsWith("//")) {
|
|
10479
|
+
this.i++;
|
|
10480
|
+
continue;
|
|
10481
|
+
}
|
|
10482
|
+
return ln;
|
|
10483
|
+
}
|
|
10484
|
+
return null;
|
|
10485
|
+
}
|
|
10486
|
+
next() {
|
|
10487
|
+
const ln = this.peek();
|
|
10488
|
+
if (ln) this.i++;
|
|
10489
|
+
return ln;
|
|
10490
|
+
}
|
|
10491
|
+
// ── header ───────────────────────────────────────────────────
|
|
10492
|
+
consumeHeader() {
|
|
10493
|
+
const ln = this.next();
|
|
10494
|
+
if (!ln || !/^sequence\b/i.test(ln.text)) {
|
|
10495
|
+
throw new SequenceParseError(
|
|
10496
|
+
"A sequence diagram must start with the keyword 'sequence'",
|
|
10497
|
+
ln?.n
|
|
10498
|
+
);
|
|
10499
|
+
}
|
|
10500
|
+
const title2 = matchQuotedTitle(ln.text);
|
|
10501
|
+
return title2 ? { title: title2 } : {};
|
|
10502
|
+
}
|
|
10503
|
+
// ── participants ─────────────────────────────────────────────
|
|
10504
|
+
ensure(id, kind = "participant", line2) {
|
|
10505
|
+
let p = this.byId.get(id);
|
|
10506
|
+
if (!p) {
|
|
10507
|
+
p = { id, name: id, kind, line: line2 };
|
|
10508
|
+
this.byId.set(id, p);
|
|
10509
|
+
this.order.push(id);
|
|
10510
|
+
}
|
|
10511
|
+
return p;
|
|
10512
|
+
}
|
|
10513
|
+
declare(line2) {
|
|
10514
|
+
const fw = firstWord(line2.text);
|
|
10515
|
+
const kind = fw.toLowerCase();
|
|
10516
|
+
let rest = line2.text.slice(fw.length).trim();
|
|
10517
|
+
let stereotype;
|
|
10518
|
+
const stereoMatch = /«([^»]*)»|<<([^>]*)>>/.exec(rest);
|
|
10519
|
+
if (stereoMatch) {
|
|
10520
|
+
stereotype = (stereoMatch[1] ?? stereoMatch[2] ?? "").trim();
|
|
10521
|
+
rest = (rest.slice(0, stereoMatch.index) + rest.slice(stereoMatch.index + stereoMatch[0].length)).trim();
|
|
10522
|
+
}
|
|
10523
|
+
let id;
|
|
10524
|
+
let name;
|
|
10525
|
+
const asMatch = /\sas\s/i.exec(rest);
|
|
10526
|
+
if (asMatch) {
|
|
10527
|
+
id = stripQuotes(rest.slice(0, asMatch.index).trim());
|
|
10528
|
+
name = stripQuotes(rest.slice(asMatch.index + asMatch[0].length).trim());
|
|
10529
|
+
} else {
|
|
10530
|
+
id = stripQuotes(rest);
|
|
10531
|
+
name = id;
|
|
10532
|
+
}
|
|
10533
|
+
if (!id) {
|
|
10534
|
+
throw new SequenceParseError(`'${fw}' declaration needs a name`, line2.n);
|
|
10535
|
+
}
|
|
10536
|
+
const existing = this.byId.get(id);
|
|
10537
|
+
if (existing) {
|
|
10538
|
+
existing.kind = kind;
|
|
10539
|
+
existing.name = name;
|
|
10540
|
+
if (stereotype) existing.stereotype = stereotype;
|
|
10541
|
+
} else {
|
|
10542
|
+
const p = { id, name, kind, line: line2.n };
|
|
10543
|
+
if (stereotype) p.stereotype = stereotype;
|
|
10544
|
+
this.byId.set(id, p);
|
|
10545
|
+
this.order.push(id);
|
|
10546
|
+
}
|
|
10547
|
+
}
|
|
10548
|
+
// ── block / statement dispatch ───────────────────────────────
|
|
10549
|
+
/** Parse statements until a terminator (`end`/`else`/`and`) or EOF. Terminators are left unconsumed. */
|
|
10550
|
+
parseBlock() {
|
|
10551
|
+
const out = [];
|
|
10552
|
+
for (; ; ) {
|
|
10553
|
+
const ln = this.peek();
|
|
10554
|
+
if (!ln) break;
|
|
10555
|
+
const fw = firstWord(ln.text).toLowerCase();
|
|
10556
|
+
if (fw === "end" || fw === "else" || fw === "and") break;
|
|
10557
|
+
if (KINDS.has(fw)) {
|
|
10558
|
+
this.i++;
|
|
10559
|
+
this.declare(ln);
|
|
10560
|
+
continue;
|
|
10561
|
+
}
|
|
10562
|
+
if (ALL_FRAG.has(fw)) {
|
|
10563
|
+
out.push(this.parseFragment(ln));
|
|
10564
|
+
continue;
|
|
10565
|
+
}
|
|
10566
|
+
this.i++;
|
|
10567
|
+
const stmt = this.parseSimpleStatement(ln);
|
|
10568
|
+
if (stmt) out.push(stmt);
|
|
10569
|
+
}
|
|
10570
|
+
return out;
|
|
10571
|
+
}
|
|
10572
|
+
parseFragment(open) {
|
|
10573
|
+
const fw = firstWord(open.text).toLowerCase();
|
|
10574
|
+
this.i++;
|
|
10575
|
+
let rest = open.text.slice(fw.length);
|
|
10576
|
+
let messageSet;
|
|
10577
|
+
if (MSGSET_FRAG.has(fw)) {
|
|
10578
|
+
const bm = /\{([^}]*)\}/.exec(rest);
|
|
10579
|
+
if (bm) {
|
|
10580
|
+
messageSet = bm[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
10581
|
+
rest = rest.slice(0, bm.index) + rest.slice(bm.index + bm[0].length);
|
|
10582
|
+
}
|
|
10583
|
+
}
|
|
10584
|
+
const firstGuard = extractGuard(rest);
|
|
10585
|
+
const operands = [
|
|
10586
|
+
{ guard: firstGuard, statements: this.parseBlock() }
|
|
10587
|
+
];
|
|
10588
|
+
for (; ; ) {
|
|
10589
|
+
const ln = this.peek();
|
|
10590
|
+
if (!ln) {
|
|
10591
|
+
throw new SequenceParseError(`Unterminated '${fw}' fragment`, open.n);
|
|
10592
|
+
}
|
|
10593
|
+
const term = firstWord(ln.text).toLowerCase();
|
|
10594
|
+
if (term === "end") {
|
|
10595
|
+
this.i++;
|
|
10596
|
+
break;
|
|
10597
|
+
}
|
|
10598
|
+
if (term === "else") {
|
|
10599
|
+
if (fw !== "alt") {
|
|
10600
|
+
throw new SequenceParseError(
|
|
10601
|
+
`'else' is only valid inside an 'alt' fragment, not '${fw}'`,
|
|
10602
|
+
ln.n
|
|
10603
|
+
);
|
|
10604
|
+
}
|
|
10605
|
+
this.i++;
|
|
10606
|
+
operands.push({
|
|
10607
|
+
guard: extractGuard(ln.text.slice(4)),
|
|
10608
|
+
statements: this.parseBlock()
|
|
10609
|
+
});
|
|
10610
|
+
continue;
|
|
10611
|
+
}
|
|
10612
|
+
if (term === "and") {
|
|
10613
|
+
if (!MULTI_FRAG.has(fw)) {
|
|
10614
|
+
throw new SequenceParseError(
|
|
10615
|
+
`'and' is only valid inside par/seq/strict, not '${fw}'`,
|
|
10616
|
+
ln.n
|
|
10617
|
+
);
|
|
10618
|
+
}
|
|
10619
|
+
this.i++;
|
|
10620
|
+
const label = ln.text.slice(3).trim();
|
|
10621
|
+
operands.push({
|
|
10622
|
+
guard: label ? extractGuard(label) : void 0,
|
|
10623
|
+
statements: this.parseBlock()
|
|
10624
|
+
});
|
|
10625
|
+
continue;
|
|
10626
|
+
}
|
|
10627
|
+
throw new SequenceParseError(`Unexpected '${term}' in '${fw}'`, ln.n);
|
|
10628
|
+
}
|
|
10629
|
+
const frag = { kind: "fragment", op: fw, operands, line: open.n };
|
|
10630
|
+
if (messageSet && messageSet.length) frag.messageSet = messageSet;
|
|
10631
|
+
return frag;
|
|
10632
|
+
}
|
|
10633
|
+
/** Non-fragment, non-participant statements. */
|
|
10634
|
+
parseSimpleStatement(ln) {
|
|
10635
|
+
const text2 = ln.text;
|
|
10636
|
+
const fw = firstWord(text2).toLowerCase();
|
|
10637
|
+
if (fw === "activate" || fw === "deactivate") {
|
|
10638
|
+
const m = /^(?:de)?activate\s+(\S+)$/i.exec(text2);
|
|
10639
|
+
if (!m) throw new SequenceParseError(`Malformed ${fw} statement`, ln.n);
|
|
10640
|
+
this.ensure(m[1], "participant", ln.n);
|
|
10641
|
+
return { kind: fw, id: m[1], line: ln.n };
|
|
10642
|
+
}
|
|
10643
|
+
if (fw === "note") {
|
|
10644
|
+
return this.parseNote(ln);
|
|
10645
|
+
}
|
|
10646
|
+
if (fw === "ref") {
|
|
10647
|
+
const m = /^ref\s+over\s+(.+?)\s*:\s*(.*)$/i.exec(text2);
|
|
10648
|
+
if (!m) throw new SequenceParseError("Malformed ref (expected: ref over A, B : text)", ln.n);
|
|
10649
|
+
const ids = m[1].split(",").map((s) => stripQuotes(s.trim())).filter(Boolean);
|
|
10650
|
+
ids.forEach((id) => this.ensure(id, "participant", ln.n));
|
|
10651
|
+
return { kind: "ref", ids, text: m[2].trim(), line: ln.n };
|
|
10652
|
+
}
|
|
10653
|
+
if (text2.startsWith("==")) {
|
|
10654
|
+
const m = /^==\s*(.*?)\s*==\s*$/.exec(text2);
|
|
10655
|
+
return { kind: "divider", text: m ? m[1].trim() : "", line: ln.n };
|
|
10656
|
+
}
|
|
10657
|
+
if (fw === "state") {
|
|
10658
|
+
const m = /^state\s+(\S+)\s*:\s*(.*)$/i.exec(text2);
|
|
10659
|
+
if (!m) throw new SequenceParseError("Malformed state invariant (expected: state A : text)", ln.n);
|
|
10660
|
+
this.ensure(m[1], "participant", ln.n);
|
|
10661
|
+
return { kind: "invariant", id: m[1], text: m[2].trim(), line: ln.n };
|
|
10662
|
+
}
|
|
10663
|
+
if (fw === "destroy") {
|
|
10664
|
+
const m = /^destroy\s+(\S+)$/i.exec(text2);
|
|
10665
|
+
if (!m) throw new SequenceParseError("Malformed destroy statement", ln.n);
|
|
10666
|
+
this.ensure(m[1], "participant", ln.n);
|
|
10667
|
+
return { kind: "destroy", id: m[1], line: ln.n };
|
|
10668
|
+
}
|
|
10669
|
+
if (fw === "autonumber") {
|
|
10670
|
+
const m = /^autonumber(?:\s+(\d+))?(?:\s+(\d+))?$/i.exec(text2);
|
|
10671
|
+
const start = m && m[1] ? parseInt(m[1], 10) : 1;
|
|
10672
|
+
const step = m && m[2] ? parseInt(m[2], 10) : 1;
|
|
10673
|
+
this.autonumberSpec = { start, step };
|
|
10674
|
+
return null;
|
|
10675
|
+
}
|
|
10676
|
+
return this.parseMessage(ln);
|
|
10677
|
+
}
|
|
10678
|
+
parseNote(ln) {
|
|
10679
|
+
const m = /^note\s+(over|left of|right of)\s+(.+?)\s*:\s*(.*)$/i.exec(ln.text);
|
|
10680
|
+
if (!m) {
|
|
10681
|
+
throw new SequenceParseError(
|
|
10682
|
+
"Malformed note (expected: note over|left of|right of A[, B] : text)",
|
|
10683
|
+
ln.n
|
|
10684
|
+
);
|
|
10685
|
+
}
|
|
10686
|
+
const placementRaw = m[1].toLowerCase();
|
|
10687
|
+
const placement = placementRaw === "over" ? "over" : placementRaw.startsWith("left") ? "left" : "right";
|
|
10688
|
+
const ids = m[2].split(",").map((s) => stripQuotes(s.trim())).filter(Boolean);
|
|
10689
|
+
ids.forEach((id) => this.ensure(id, "participant", ln.n));
|
|
10690
|
+
return { kind: "note", placement, ids, text: m[3].trim(), line: ln.n };
|
|
10691
|
+
}
|
|
10692
|
+
parseMessage(ln) {
|
|
10693
|
+
const text2 = ln.text;
|
|
10694
|
+
const arrowMatch = ARROW_RE.exec(text2);
|
|
10695
|
+
if (!arrowMatch) {
|
|
10696
|
+
throw new SequenceParseError(`Cannot parse statement: "${text2}"`, ln.n);
|
|
10697
|
+
}
|
|
10698
|
+
const token = arrowMatch[1];
|
|
10699
|
+
const at = arrowMatch.index;
|
|
10700
|
+
const leftRaw = text2.slice(0, at);
|
|
10701
|
+
let rightRaw = text2.slice(at + token.length);
|
|
10702
|
+
let label;
|
|
10703
|
+
const colon = rightRaw.indexOf(":");
|
|
10704
|
+
if (colon >= 0) {
|
|
10705
|
+
label = rightRaw.slice(colon + 1).trim();
|
|
10706
|
+
rightRaw = rightRaw.slice(0, colon);
|
|
10707
|
+
}
|
|
10708
|
+
let activateTarget = false;
|
|
10709
|
+
let deactivateSource = false;
|
|
10710
|
+
let left = leftRaw.trim();
|
|
10711
|
+
if (left.endsWith("+")) {
|
|
10712
|
+
activateTarget = true;
|
|
10713
|
+
left = left.slice(0, -1).trim();
|
|
10714
|
+
} else if (left.endsWith("-")) {
|
|
10715
|
+
deactivateSource = true;
|
|
10716
|
+
left = left.slice(0, -1).trim();
|
|
10717
|
+
}
|
|
10718
|
+
let right = rightRaw.trim();
|
|
10719
|
+
if (right.startsWith("+")) {
|
|
10720
|
+
activateTarget = true;
|
|
10721
|
+
right = right.slice(1).trim();
|
|
10722
|
+
} else if (right.startsWith("-")) {
|
|
10723
|
+
deactivateSource = true;
|
|
10724
|
+
right = right.slice(1).trim();
|
|
10725
|
+
}
|
|
10726
|
+
const arrow2 = arrowKind(token);
|
|
10727
|
+
let create = false;
|
|
10728
|
+
if (right.startsWith("*")) {
|
|
10729
|
+
create = true;
|
|
10730
|
+
right = right.slice(1).trim();
|
|
10731
|
+
}
|
|
10732
|
+
const from = arrow2 === "found" ? "" : stripQuotes(left);
|
|
10733
|
+
const to = arrow2 === "lost" ? "" : stripQuotes(right);
|
|
10734
|
+
if (from) this.ensure(from, "participant", ln.n);
|
|
10735
|
+
if (to) {
|
|
10736
|
+
const p = this.ensure(to, "participant", ln.n);
|
|
10737
|
+
if (create) p.createdInline = true;
|
|
10738
|
+
}
|
|
10739
|
+
const msg = {
|
|
10740
|
+
kind: "message",
|
|
10741
|
+
from,
|
|
10742
|
+
to,
|
|
10743
|
+
arrow: arrow2,
|
|
10744
|
+
line: ln.n
|
|
10745
|
+
};
|
|
10746
|
+
if (label) msg.label = label;
|
|
10747
|
+
if (activateTarget) msg.activateTarget = true;
|
|
10748
|
+
if (deactivateSource) msg.deactivateSource = true;
|
|
10749
|
+
if (create) msg.create = true;
|
|
10750
|
+
return msg;
|
|
10751
|
+
}
|
|
10752
|
+
};
|
|
10753
|
+
function parseSequence(text2) {
|
|
10754
|
+
return new SequenceParser(text2).parse();
|
|
10755
|
+
}
|
|
10756
|
+
|
|
10757
|
+
// src/diagrams/sequence/layout.ts
|
|
10758
|
+
var SEQ_CONST = {
|
|
10759
|
+
HEAD_W_MIN: 90,
|
|
10760
|
+
HEAD_H: 38,
|
|
10761
|
+
HEAD_PAD_X: 14,
|
|
10762
|
+
LIFELINE_GAP: 140,
|
|
10763
|
+
LEFT_MARGIN: 24,
|
|
10764
|
+
RIGHT_PAD: 28,
|
|
10765
|
+
TOP_PAD: 16,
|
|
10766
|
+
HEAD_TO_FIRST: 30,
|
|
10767
|
+
EVENT_GAP: 38,
|
|
10768
|
+
BOTTOM_PAD: 30,
|
|
10769
|
+
SELF_GAP: 30,
|
|
10770
|
+
SELF_LOOP_W: 48,
|
|
10771
|
+
ACT_W: 10,
|
|
10772
|
+
ACT_NEST_DX: 6,
|
|
10773
|
+
ACT_STEP: 30,
|
|
10774
|
+
FRAG_PAD_X: 10,
|
|
10775
|
+
FRAG_LABEL_H: 18,
|
|
10776
|
+
FRAG_PAD_TOP: 16,
|
|
10777
|
+
FRAG_PAD_BOTTOM: 12,
|
|
10778
|
+
FRAG_GAP_BEFORE: 12,
|
|
10779
|
+
FRAG_OPERAND_GAP: 18,
|
|
10780
|
+
FRAG_NEST_INSET: 8,
|
|
10781
|
+
REF_H: 40,
|
|
10782
|
+
REF_GAP_BEFORE: 12,
|
|
10783
|
+
NOTE_GAP_BEFORE: 10,
|
|
10784
|
+
NOTE_PAD: 8,
|
|
10785
|
+
NOTE_MIN_W: 70,
|
|
10786
|
+
NOTE_LINE_H: 15,
|
|
10787
|
+
NOTE_SIDE_GAP: 16,
|
|
10788
|
+
DIV_H: 26,
|
|
10789
|
+
DIV_GAP_BEFORE: 8,
|
|
10790
|
+
INV_GAP_BEFORE: 10,
|
|
10791
|
+
INV_PAD: 10,
|
|
10792
|
+
INV_H: 24,
|
|
10793
|
+
LABEL_GAP_PAD: 26,
|
|
10794
|
+
LOST_LEN: 72
|
|
10795
|
+
};
|
|
10796
|
+
var LABEL_SIZE = 11;
|
|
10797
|
+
var HEAD_TEXT_SIZE = 12.5;
|
|
10798
|
+
function textWidth(s, size) {
|
|
10799
|
+
let w = 0;
|
|
10800
|
+
for (const ch of s) {
|
|
10801
|
+
w += /[⺀-]/.test(ch) ? size : size * 0.55;
|
|
10802
|
+
}
|
|
10803
|
+
return w;
|
|
10804
|
+
}
|
|
10805
|
+
var SequenceLayout = class {
|
|
10806
|
+
ast;
|
|
10807
|
+
lifelines = [];
|
|
10808
|
+
colOf = /* @__PURE__ */ new Map();
|
|
10809
|
+
llById = /* @__PURE__ */ new Map();
|
|
10810
|
+
x = [];
|
|
10811
|
+
headW = [];
|
|
10812
|
+
y = 0;
|
|
10813
|
+
open = /* @__PURE__ */ new Map();
|
|
10814
|
+
colAccStack = [];
|
|
10815
|
+
messages = [];
|
|
10816
|
+
activations = [];
|
|
10817
|
+
fragments = [];
|
|
10818
|
+
refs = [];
|
|
10819
|
+
notes = [];
|
|
10820
|
+
dividers = [];
|
|
10821
|
+
invariants = [];
|
|
10822
|
+
destroys = [];
|
|
10823
|
+
warnings = [];
|
|
10824
|
+
autoCounter = null;
|
|
10825
|
+
contentRight = 0;
|
|
10826
|
+
constructor(ast) {
|
|
10827
|
+
this.ast = ast;
|
|
10828
|
+
}
|
|
10829
|
+
run() {
|
|
10830
|
+
this.buildColumns();
|
|
10831
|
+
if (this.ast.autonumber) this.autoCounter = this.ast.autonumber.start;
|
|
10832
|
+
const headBottom = SEQ_CONST.TOP_PAD + SEQ_CONST.HEAD_H;
|
|
10833
|
+
this.y = headBottom + SEQ_CONST.HEAD_TO_FIRST - SEQ_CONST.EVENT_GAP;
|
|
10834
|
+
this.walk(this.ast.statements, 0);
|
|
10835
|
+
const bottomY = this.y + SEQ_CONST.EVENT_GAP;
|
|
10836
|
+
for (const [id, stack] of this.open) {
|
|
10837
|
+
while (stack.length) {
|
|
10838
|
+
const b = stack.pop();
|
|
10839
|
+
this.activations.push({ id, x: b.x, yTop: b.yTop, yBottom: bottomY, level: b.level });
|
|
10840
|
+
}
|
|
10841
|
+
}
|
|
10842
|
+
const bodyBottom = bottomY + SEQ_CONST.BOTTOM_PAD;
|
|
10843
|
+
for (const ll of this.lifelines) {
|
|
10844
|
+
if (!ll.destroyed) ll.axisBottom = bodyBottom;
|
|
10845
|
+
}
|
|
10846
|
+
const lastCenter = this.x[this.x.length - 1] ?? SEQ_CONST.LEFT_MARGIN;
|
|
10847
|
+
const lastHalf = (this.headW[this.headW.length - 1] ?? SEQ_CONST.HEAD_W_MIN) / 2;
|
|
10848
|
+
this.contentRight = Math.max(this.contentRight, lastCenter + lastHalf);
|
|
10849
|
+
const width = Math.round(this.contentRight + SEQ_CONST.RIGHT_PAD);
|
|
10850
|
+
const height = Math.round(bodyBottom);
|
|
10851
|
+
for (const d of this.dividers) d.width = width;
|
|
10852
|
+
const result = {
|
|
10853
|
+
width,
|
|
10854
|
+
height,
|
|
10855
|
+
lifelines: this.lifelines,
|
|
10856
|
+
messages: this.messages,
|
|
10857
|
+
activations: this.activations,
|
|
10858
|
+
fragments: this.fragments,
|
|
10859
|
+
refs: this.refs,
|
|
10860
|
+
notes: this.notes,
|
|
10861
|
+
dividers: this.dividers,
|
|
10862
|
+
invariants: this.invariants,
|
|
10863
|
+
destroys: this.destroys,
|
|
10864
|
+
warnings: this.warnings.concat(this.ast.warnings),
|
|
10865
|
+
ast: this.ast
|
|
10866
|
+
};
|
|
10867
|
+
if (this.ast.title) result.title = this.ast.title;
|
|
10868
|
+
return result;
|
|
10869
|
+
}
|
|
10870
|
+
// ── horizontal pass ──────────────────────────────────────────
|
|
10871
|
+
buildColumns() {
|
|
10872
|
+
const ps = this.ast.participants;
|
|
10873
|
+
this.headW = ps.map(
|
|
10874
|
+
(p) => Math.max(SEQ_CONST.HEAD_W_MIN, Math.ceil(textWidth(p.name, HEAD_TEXT_SIZE)) + SEQ_CONST.HEAD_PAD_X * 2)
|
|
10875
|
+
);
|
|
10876
|
+
ps.forEach((p, i) => this.colOf.set(p.id, i));
|
|
10877
|
+
const n = ps.length;
|
|
10878
|
+
const gaps = new Array(Math.max(0, n - 1)).fill(SEQ_CONST.LIFELINE_GAP);
|
|
10879
|
+
for (let k = 0; k < gaps.length; k++) {
|
|
10880
|
+
gaps[k] = Math.max(gaps[k], this.headW[k] / 2 + this.headW[k + 1] / 2 + 28);
|
|
10881
|
+
}
|
|
10882
|
+
const msgs = collectMessages(this.ast.statements);
|
|
10883
|
+
for (const m of msgs) {
|
|
10884
|
+
const label = m.label ?? "";
|
|
10885
|
+
const w = textWidth(label, LABEL_SIZE) + SEQ_CONST.LABEL_GAP_PAD;
|
|
10886
|
+
const cf = this.colOf.get(m.from);
|
|
10887
|
+
const ct = this.colOf.get(m.to);
|
|
10888
|
+
if (cf === void 0 && ct === void 0) continue;
|
|
10889
|
+
if (cf === void 0 || ct === void 0) continue;
|
|
10890
|
+
if (cf === ct) {
|
|
10891
|
+
if (cf < gaps.length) gaps[cf] = Math.max(gaps[cf], SEQ_CONST.SELF_LOOP_W + w);
|
|
10892
|
+
continue;
|
|
10893
|
+
}
|
|
10894
|
+
const i = Math.min(cf, ct);
|
|
10895
|
+
const j = Math.max(cf, ct);
|
|
10896
|
+
if (j === i + 1) {
|
|
10897
|
+
gaps[i] = Math.max(gaps[i], w);
|
|
10898
|
+
} else {
|
|
10899
|
+
let span = 0;
|
|
10900
|
+
for (let k = i; k < j; k++) span += gaps[k];
|
|
10901
|
+
if (span < w) {
|
|
10902
|
+
const add = (w - span) / (j - i);
|
|
10903
|
+
for (let k = i; k < j; k++) gaps[k] += add;
|
|
10904
|
+
}
|
|
10905
|
+
}
|
|
10906
|
+
}
|
|
10907
|
+
this.x = new Array(n);
|
|
10908
|
+
let cx = SEQ_CONST.LEFT_MARGIN + (this.headW[0] ?? SEQ_CONST.HEAD_W_MIN) / 2;
|
|
10909
|
+
for (let i = 0; i < n; i++) {
|
|
10910
|
+
this.x[i] = cx;
|
|
10911
|
+
if (i < gaps.length) cx += gaps[i];
|
|
10912
|
+
}
|
|
10913
|
+
ps.forEach((p, i) => {
|
|
10914
|
+
const created = p.createdInline === true;
|
|
10915
|
+
const headY = created ? -1 : SEQ_CONST.TOP_PAD;
|
|
10916
|
+
const ll = {
|
|
10917
|
+
participant: p,
|
|
10918
|
+
index: i,
|
|
10919
|
+
x: this.x[i],
|
|
10920
|
+
headX: this.x[i] - this.headW[i] / 2,
|
|
10921
|
+
headY,
|
|
10922
|
+
headW: this.headW[i],
|
|
10923
|
+
headH: SEQ_CONST.HEAD_H,
|
|
10924
|
+
axisTop: created ? -1 : SEQ_CONST.TOP_PAD + SEQ_CONST.HEAD_H,
|
|
10925
|
+
axisBottom: 0,
|
|
10926
|
+
destroyed: false
|
|
10927
|
+
};
|
|
10928
|
+
this.lifelines.push(ll);
|
|
10929
|
+
this.llById.set(p.id, ll);
|
|
10930
|
+
});
|
|
10931
|
+
}
|
|
10932
|
+
// ── column accounting (for fragment extents) ─────────────────
|
|
10933
|
+
touch(col) {
|
|
10934
|
+
if (col === void 0) return;
|
|
10935
|
+
for (const acc of this.colAccStack) {
|
|
10936
|
+
if (col < acc.min) acc.min = col;
|
|
10937
|
+
if (col > acc.max) acc.max = col;
|
|
10938
|
+
}
|
|
10939
|
+
}
|
|
10940
|
+
noteRight(x, w) {
|
|
10941
|
+
this.contentRight = Math.max(this.contentRight, x + w);
|
|
10942
|
+
}
|
|
10943
|
+
// ── activation bars ──────────────────────────────────────────
|
|
10944
|
+
openBar(id) {
|
|
10945
|
+
const col = this.colOf.get(id);
|
|
10946
|
+
if (col === void 0) return;
|
|
10947
|
+
const stack = this.open.get(id) ?? [];
|
|
10948
|
+
const level = stack.length;
|
|
10949
|
+
const x = this.x[col] - SEQ_CONST.ACT_W / 2 + level * SEQ_CONST.ACT_NEST_DX;
|
|
10950
|
+
stack.push({ x, yTop: this.y, level });
|
|
10951
|
+
this.open.set(id, stack);
|
|
10952
|
+
}
|
|
10953
|
+
closeBar(id) {
|
|
10954
|
+
const stack = this.open.get(id);
|
|
10955
|
+
if (!stack || stack.length === 0) {
|
|
10956
|
+
this.warnings.push(`deactivate '${id}' with no matching activation`);
|
|
10957
|
+
return;
|
|
10958
|
+
}
|
|
10959
|
+
const b = stack.pop();
|
|
10960
|
+
this.activations.push({ id, x: b.x, yTop: b.yTop, yBottom: this.y, level: b.level });
|
|
10961
|
+
}
|
|
10962
|
+
faceEdge(id, col, towardRight, extraLevel = 0) {
|
|
10963
|
+
const stack = this.open.get(id) ?? [];
|
|
10964
|
+
const levels = stack.length + extraLevel;
|
|
10965
|
+
if (levels <= 0) return this.x[col];
|
|
10966
|
+
if (!towardRight) return this.x[col] - SEQ_CONST.ACT_W / 2;
|
|
10967
|
+
return this.x[col] + SEQ_CONST.ACT_W / 2 + (levels - 1) * SEQ_CONST.ACT_NEST_DX;
|
|
10968
|
+
}
|
|
10969
|
+
// ── vertical walk ────────────────────────────────────────────
|
|
10970
|
+
walk(stmts, depth) {
|
|
10971
|
+
for (const s of stmts) {
|
|
10972
|
+
switch (s.kind) {
|
|
10973
|
+
case "message":
|
|
10974
|
+
this.placeMessage(s);
|
|
10975
|
+
break;
|
|
10976
|
+
case "activate":
|
|
10977
|
+
this.y += SEQ_CONST.ACT_STEP;
|
|
10978
|
+
this.openBar(s.id);
|
|
10979
|
+
this.touch(this.colOf.get(s.id));
|
|
10980
|
+
break;
|
|
10981
|
+
case "deactivate":
|
|
10982
|
+
this.y += SEQ_CONST.ACT_STEP;
|
|
10983
|
+
this.closeBar(s.id);
|
|
10984
|
+
this.touch(this.colOf.get(s.id));
|
|
10985
|
+
break;
|
|
10986
|
+
case "note":
|
|
10987
|
+
this.placeNote(s);
|
|
10988
|
+
break;
|
|
10989
|
+
case "ref":
|
|
10990
|
+
this.placeRef(s.ids, s.text);
|
|
10991
|
+
break;
|
|
10992
|
+
case "divider":
|
|
10993
|
+
this.y += SEQ_CONST.DIV_GAP_BEFORE;
|
|
10994
|
+
this.dividers.push({ text: s.text, y: this.y + SEQ_CONST.DIV_H / 2, width: 0 });
|
|
10995
|
+
this.y += SEQ_CONST.DIV_H;
|
|
10996
|
+
break;
|
|
10997
|
+
case "invariant":
|
|
10998
|
+
this.placeInvariant(s.id, s.text);
|
|
10999
|
+
break;
|
|
11000
|
+
case "destroy":
|
|
11001
|
+
this.placeDestroy(s.id);
|
|
11002
|
+
break;
|
|
11003
|
+
case "fragment":
|
|
11004
|
+
this.placeFragment(s.op, s.operands, depth, s.messageSet);
|
|
11005
|
+
break;
|
|
11006
|
+
}
|
|
11007
|
+
}
|
|
11008
|
+
}
|
|
11009
|
+
nextNumber() {
|
|
11010
|
+
if (this.autoCounter === null || !this.ast.autonumber) return void 0;
|
|
11011
|
+
const n = this.autoCounter;
|
|
11012
|
+
this.autoCounter += this.ast.autonumber.step;
|
|
11013
|
+
return n;
|
|
11014
|
+
}
|
|
11015
|
+
placeMessage(m) {
|
|
11016
|
+
this.y += SEQ_CONST.EVENT_GAP;
|
|
11017
|
+
const y = this.y;
|
|
11018
|
+
const cf = this.colOf.get(m.from);
|
|
11019
|
+
const ct = this.colOf.get(m.to);
|
|
11020
|
+
const self = m.from !== "" && m.from === m.to;
|
|
11021
|
+
let x1;
|
|
11022
|
+
let x2;
|
|
11023
|
+
if (m.arrow === "found") {
|
|
11024
|
+
const c = ct;
|
|
11025
|
+
x2 = this.faceEdge(m.to, c, false);
|
|
11026
|
+
x1 = x2 - SEQ_CONST.LOST_LEN;
|
|
11027
|
+
} else if (m.arrow === "lost") {
|
|
11028
|
+
const c = cf;
|
|
11029
|
+
x1 = this.faceEdge(m.from, c, true);
|
|
11030
|
+
x2 = x1 + SEQ_CONST.LOST_LEN;
|
|
11031
|
+
} else if (self) {
|
|
11032
|
+
const c = cf;
|
|
11033
|
+
x1 = this.faceEdge(m.from, c, true);
|
|
11034
|
+
x2 = x1 + SEQ_CONST.SELF_LOOP_W;
|
|
11035
|
+
} else {
|
|
11036
|
+
const fromRight = (ct ?? 0) > (cf ?? 0);
|
|
11037
|
+
x1 = this.faceEdge(m.from, cf, fromRight);
|
|
11038
|
+
if (m.create) {
|
|
11039
|
+
const halfW = this.headW[ct] / 2;
|
|
11040
|
+
x2 = fromRight ? this.x[ct] - halfW : this.x[ct] + halfW;
|
|
11041
|
+
} else {
|
|
11042
|
+
x2 = this.faceEdge(m.to, ct, !fromRight, m.activateTarget ? 1 : 0);
|
|
11043
|
+
}
|
|
11044
|
+
}
|
|
11045
|
+
const row = { message: m, y, x1, x2, self };
|
|
11046
|
+
const num = this.nextNumber();
|
|
11047
|
+
if (num !== void 0) row.number = num;
|
|
11048
|
+
if (self) {
|
|
11049
|
+
row.selfBottomY = y + SEQ_CONST.SELF_GAP;
|
|
11050
|
+
this.y = row.selfBottomY;
|
|
11051
|
+
}
|
|
11052
|
+
this.messages.push(row);
|
|
11053
|
+
this.contentRight = Math.max(this.contentRight, x1, x2);
|
|
11054
|
+
this.touch(cf);
|
|
11055
|
+
this.touch(ct);
|
|
11056
|
+
if (m.create && ct !== void 0) {
|
|
11057
|
+
const ll = this.llById.get(m.to);
|
|
11058
|
+
ll.headY = y - SEQ_CONST.HEAD_H / 2;
|
|
11059
|
+
ll.axisTop = ll.headY + SEQ_CONST.HEAD_H;
|
|
11060
|
+
}
|
|
11061
|
+
if (m.activateTarget && ct !== void 0) this.openBar(m.to);
|
|
11062
|
+
if (m.deactivateSource && cf !== void 0) this.closeBar(m.from);
|
|
11063
|
+
}
|
|
11064
|
+
placeNote(note) {
|
|
11065
|
+
this.y += SEQ_CONST.NOTE_GAP_BEFORE;
|
|
11066
|
+
const lines = note.text.split(/<br\s*\/?>|\\n/);
|
|
11067
|
+
const textW = lines.reduce((mx, l) => Math.max(mx, textWidth(l, LABEL_SIZE)), 0);
|
|
11068
|
+
const w = Math.max(SEQ_CONST.NOTE_MIN_W, Math.ceil(textW) + SEQ_CONST.NOTE_PAD * 2);
|
|
11069
|
+
const h = lines.length * SEQ_CONST.NOTE_LINE_H + SEQ_CONST.NOTE_PAD * 2;
|
|
11070
|
+
const top = this.y;
|
|
11071
|
+
const cols = note.ids.map((id) => this.colOf.get(id)).filter((c) => c !== void 0);
|
|
11072
|
+
cols.forEach((c) => this.touch(c));
|
|
11073
|
+
let x;
|
|
11074
|
+
let boxW = w;
|
|
11075
|
+
if (note.placement === "over" && cols.length >= 2) {
|
|
11076
|
+
const a = this.x[Math.min(...cols)];
|
|
11077
|
+
const b = this.x[Math.max(...cols)];
|
|
11078
|
+
const span = b - a + this.headW[Math.min(...cols)] / 2 + this.headW[Math.max(...cols)] / 2;
|
|
11079
|
+
boxW = Math.max(w, span);
|
|
11080
|
+
x = (a + b) / 2 - boxW / 2;
|
|
11081
|
+
} else if (note.placement === "left" && cols.length) {
|
|
11082
|
+
x = this.x[cols[0]] - SEQ_CONST.NOTE_SIDE_GAP - w;
|
|
11083
|
+
} else if (note.placement === "right" && cols.length) {
|
|
11084
|
+
x = this.x[cols[0]] + SEQ_CONST.NOTE_SIDE_GAP;
|
|
11085
|
+
} else {
|
|
11086
|
+
const c = cols[0] ?? 0;
|
|
11087
|
+
x = this.x[c] - w / 2;
|
|
11088
|
+
}
|
|
11089
|
+
this.notes.push({ note, x, y: top, width: boxW, height: h });
|
|
11090
|
+
this.noteRight(x, boxW);
|
|
11091
|
+
this.y = top + h;
|
|
11092
|
+
}
|
|
11093
|
+
placeRef(ids, text2) {
|
|
11094
|
+
this.y += SEQ_CONST.REF_GAP_BEFORE;
|
|
11095
|
+
const cols = ids.map((id) => this.colOf.get(id)).filter((c) => c !== void 0);
|
|
11096
|
+
cols.forEach((c) => this.touch(c));
|
|
11097
|
+
const minC = cols.length ? Math.min(...cols) : 0;
|
|
11098
|
+
const maxC = cols.length ? Math.max(...cols) : 0;
|
|
11099
|
+
const left = this.x[minC] - SEQ_CONST.FRAG_PAD_X;
|
|
11100
|
+
const right = this.x[maxC] + SEQ_CONST.FRAG_PAD_X;
|
|
11101
|
+
const top = this.y;
|
|
11102
|
+
this.refs.push({ text: text2, x: left, y: top, width: right - left, height: SEQ_CONST.REF_H });
|
|
11103
|
+
this.noteRight(left, right - left);
|
|
11104
|
+
this.y = top + SEQ_CONST.REF_H;
|
|
11105
|
+
}
|
|
11106
|
+
placeInvariant(id, text2) {
|
|
11107
|
+
this.y += SEQ_CONST.INV_GAP_BEFORE;
|
|
11108
|
+
const col = this.colOf.get(id);
|
|
11109
|
+
this.touch(col);
|
|
11110
|
+
const w = Math.ceil(textWidth(text2, LABEL_SIZE)) + SEQ_CONST.INV_PAD * 2;
|
|
11111
|
+
const cx = col !== void 0 ? this.x[col] : SEQ_CONST.LEFT_MARGIN;
|
|
11112
|
+
const top = this.y;
|
|
11113
|
+
this.invariants.push({ text: text2, cx, y: top, width: w, height: SEQ_CONST.INV_H });
|
|
11114
|
+
this.noteRight(cx - w / 2, w);
|
|
11115
|
+
this.y = top + SEQ_CONST.INV_H;
|
|
11116
|
+
}
|
|
11117
|
+
placeDestroy(id) {
|
|
11118
|
+
this.y += SEQ_CONST.EVENT_GAP / 2;
|
|
11119
|
+
const ll = this.llById.get(id);
|
|
11120
|
+
const col = this.colOf.get(id);
|
|
11121
|
+
if (ll && col !== void 0) {
|
|
11122
|
+
ll.destroyed = true;
|
|
11123
|
+
ll.axisBottom = this.y;
|
|
11124
|
+
this.destroys.push({ x: this.x[col], y: this.y });
|
|
11125
|
+
this.touch(col);
|
|
11126
|
+
}
|
|
11127
|
+
}
|
|
11128
|
+
placeFragment(op, operands, depth, messageSet) {
|
|
11129
|
+
const frameTop = this.y + SEQ_CONST.FRAG_GAP_BEFORE;
|
|
11130
|
+
const acc = { min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY };
|
|
11131
|
+
this.colAccStack.push(acc);
|
|
11132
|
+
const operandGeom = [];
|
|
11133
|
+
this.y = frameTop + SEQ_CONST.FRAG_LABEL_H + SEQ_CONST.FRAG_PAD_TOP - SEQ_CONST.EVENT_GAP;
|
|
11134
|
+
const firstGeom = {};
|
|
11135
|
+
if (operands[0]?.guard) firstGeom.guard = operands[0].guard;
|
|
11136
|
+
operandGeom.push(firstGeom);
|
|
11137
|
+
this.walk(operands[0]?.statements ?? [], depth + 1);
|
|
11138
|
+
for (let oi = 1; oi < operands.length; oi++) {
|
|
11139
|
+
const sepY = this.y + SEQ_CONST.FRAG_OPERAND_GAP;
|
|
11140
|
+
const geom = { sepY };
|
|
11141
|
+
if (operands[oi].guard) geom.guard = operands[oi].guard;
|
|
11142
|
+
operandGeom.push(geom);
|
|
11143
|
+
this.y = sepY + SEQ_CONST.FRAG_PAD_TOP - SEQ_CONST.EVENT_GAP;
|
|
11144
|
+
this.walk(operands[oi].statements, depth + 1);
|
|
11145
|
+
}
|
|
11146
|
+
const frameBottom = this.y + SEQ_CONST.FRAG_PAD_BOTTOM;
|
|
11147
|
+
this.colAccStack.pop();
|
|
11148
|
+
let minC = acc.min;
|
|
11149
|
+
let maxC = acc.max;
|
|
11150
|
+
if (!isFinite(minC)) {
|
|
11151
|
+
minC = 0;
|
|
11152
|
+
maxC = Math.max(0, this.lifelines.length - 1);
|
|
11153
|
+
}
|
|
11154
|
+
const inset = depth * SEQ_CONST.FRAG_NEST_INSET;
|
|
11155
|
+
let left = this.x[minC] - SEQ_CONST.FRAG_PAD_X + inset;
|
|
11156
|
+
let right = this.x[maxC] + SEQ_CONST.FRAG_PAD_X - inset;
|
|
11157
|
+
if (right - left < 40) {
|
|
11158
|
+
const mid = (left + right) / 2;
|
|
11159
|
+
left = mid - 20;
|
|
11160
|
+
right = mid + 20;
|
|
11161
|
+
}
|
|
11162
|
+
const frame = {
|
|
11163
|
+
op,
|
|
11164
|
+
x: left,
|
|
11165
|
+
y: frameTop,
|
|
11166
|
+
width: right - left,
|
|
11167
|
+
height: frameBottom - frameTop,
|
|
11168
|
+
operands: operandGeom
|
|
11169
|
+
};
|
|
11170
|
+
if (messageSet && messageSet.length) frame.messageSet = messageSet;
|
|
11171
|
+
this.fragments.push(frame);
|
|
11172
|
+
this.noteRight(left, right - left);
|
|
11173
|
+
this.y = frameBottom;
|
|
11174
|
+
}
|
|
11175
|
+
};
|
|
11176
|
+
function collectMessages(stmts) {
|
|
11177
|
+
const out = [];
|
|
11178
|
+
for (const s of stmts) {
|
|
11179
|
+
if (s.kind === "message") out.push(s);
|
|
11180
|
+
else if (s.kind === "fragment") {
|
|
11181
|
+
for (const op of s.operands) out.push(...collectMessages(op.statements));
|
|
11182
|
+
}
|
|
11183
|
+
}
|
|
11184
|
+
return out;
|
|
11185
|
+
}
|
|
11186
|
+
function layoutSequence(ast) {
|
|
11187
|
+
return new SequenceLayout(ast).run();
|
|
11188
|
+
}
|
|
11189
|
+
|
|
11190
|
+
// src/diagrams/sequence/renderer.ts
|
|
11191
|
+
var HEAD = "#e8f0fb";
|
|
11192
|
+
var HEAD_STROKE = "#5b85c0";
|
|
11193
|
+
function buildCss5(t) {
|
|
11194
|
+
return `
|
|
11195
|
+
.sx-seq { font-family: system-ui, -apple-system, sans-serif; }
|
|
11196
|
+
.sx-seq-axis { stroke: ${t.neutral}; stroke-width: 1; stroke-dasharray: 4 4; }
|
|
11197
|
+
.sx-seq-head rect, .sx-seq-head path { fill: ${HEAD}; stroke: ${HEAD_STROKE}; stroke-width: 1.4; }
|
|
11198
|
+
.sx-seq-head-name { font: 600 12.5px sans-serif; fill: ${t.text}; }
|
|
11199
|
+
.sx-seq-head-stereo { font: italic 10px sans-serif; fill: ${t.textMuted}; }
|
|
11200
|
+
.sx-seq-actor line, .sx-seq-actor circle { stroke: ${t.stroke}; stroke-width: 1.6; fill: none; stroke-linecap: round; }
|
|
11201
|
+
.sx-seq-actor-head { fill: ${t.bg}; }
|
|
11202
|
+
.sx-seq-icon { fill: ${t.bg}; stroke: ${HEAD_STROKE}; stroke-width: 1.6; }
|
|
11203
|
+
.sx-seq-icon-line { stroke: ${HEAD_STROKE}; stroke-width: 1.6; fill: none; stroke-linecap: round; }
|
|
11204
|
+
.sx-seq-icon-fill { fill: ${HEAD_STROKE}; stroke: none; }
|
|
11205
|
+
.sx-seq-act { fill: ${t.bg}; stroke: ${HEAD_STROKE}; stroke-width: 1.2; }
|
|
11206
|
+
.sx-seq-msg { stroke: ${t.stroke}; stroke-width: 1.5; fill: none; }
|
|
11207
|
+
.sx-seq-msg-reply { stroke: ${t.stroke}; stroke-width: 1.4; fill: none; stroke-dasharray: 6 4; }
|
|
11208
|
+
.sx-seq-msg-label { font: 11px sans-serif; fill: ${t.text}; }
|
|
11209
|
+
.sx-seq-msg-num { font: 600 10px sans-serif; fill: ${t.textMuted}; }
|
|
11210
|
+
.sx-seq-endpoint { fill: ${t.stroke}; }
|
|
11211
|
+
.sx-seq-frame { fill: none; stroke: ${t.neutral}; stroke-width: 1.2; }
|
|
11212
|
+
.sx-seq-frame-neg { fill: ${t.negative}; fill-opacity: 0.06; }
|
|
11213
|
+
.sx-seq-frame-sep { stroke: ${t.neutral}; stroke-width: 1; stroke-dasharray: 5 4; }
|
|
11214
|
+
.sx-seq-frame-tab { fill: ${t.fillMuted}; stroke: ${t.neutral}; stroke-width: 1.2; }
|
|
11215
|
+
.sx-seq-frame-op { font: 700 11px sans-serif; fill: ${t.text}; }
|
|
11216
|
+
.sx-seq-guard { font: italic 10.5px sans-serif; fill: ${t.textMuted}; }
|
|
11217
|
+
.sx-seq-ref-name { font: 600 12px sans-serif; fill: ${t.text}; }
|
|
11218
|
+
.sx-seq-note rect { fill: #fdf6da; stroke: #d9c97e; stroke-width: 1.1; }
|
|
11219
|
+
.sx-seq-note path { fill: #efe2a6; stroke: #d9c97e; stroke-width: 1.1; }
|
|
11220
|
+
.sx-seq-note-text { font: 11px sans-serif; fill: ${t.text}; }
|
|
11221
|
+
.sx-seq-div line { stroke: ${t.neutral}; stroke-width: 1; }
|
|
11222
|
+
.sx-seq-div rect { fill: ${t.fillMuted}; stroke: ${t.neutral}; stroke-width: 1; }
|
|
11223
|
+
.sx-seq-div-text { font: 600 11px sans-serif; fill: ${t.textMuted}; }
|
|
11224
|
+
.sx-seq-inv rect { fill: ${t.bg}; stroke: ${t.neutral}; stroke-width: 1.1; }
|
|
11225
|
+
.sx-seq-inv-text { font: italic 10.5px sans-serif; fill: ${t.textMuted}; }
|
|
11226
|
+
.sx-seq-destroy { stroke: ${t.negative}; stroke-width: 2; }
|
|
11227
|
+
.sx-seq-title { font: 700 16px sans-serif; fill: ${t.text}; }
|
|
11228
|
+
`.trim();
|
|
11229
|
+
}
|
|
11230
|
+
function markers3(t) {
|
|
11231
|
+
return defs([
|
|
11232
|
+
el(
|
|
11233
|
+
"marker",
|
|
11234
|
+
{ id: "sx-seq-filled", viewBox: "0 0 12 10", refX: 10, refY: 5, markerWidth: 11, markerHeight: 9, orient: "auto" },
|
|
11235
|
+
[el("polygon", { points: "0,0 11,5 0,10", fill: t.stroke })]
|
|
11236
|
+
),
|
|
11237
|
+
el(
|
|
11238
|
+
"marker",
|
|
11239
|
+
{ id: "sx-seq-open", viewBox: "0 0 12 10", refX: 10, refY: 5, markerWidth: 12, markerHeight: 10, orient: "auto" },
|
|
11240
|
+
[el("polyline", { points: "0,0 10,5 0,10", fill: "none", stroke: t.stroke, "stroke-width": 1.5 })]
|
|
11241
|
+
)
|
|
11242
|
+
]);
|
|
11243
|
+
}
|
|
11244
|
+
var CHAR_W = 6;
|
|
11245
|
+
function renderLifeline(ll) {
|
|
11246
|
+
const parts = [];
|
|
11247
|
+
parts.push(line({ class: "sx-seq-axis", x1: ll.x, y1: ll.axisTop, x2: ll.x, y2: ll.axisBottom }));
|
|
11248
|
+
parts.push(renderHead(ll));
|
|
11249
|
+
return group({ class: "sx-seq-lifeline", "data-id": ll.participant.id, "data-kind": ll.participant.kind }, parts);
|
|
11250
|
+
}
|
|
11251
|
+
function renderHead(ll) {
|
|
11252
|
+
const p = ll.participant;
|
|
11253
|
+
const cx = ll.x;
|
|
11254
|
+
const stereo = p.stereotype ? `\xAB${p.stereotype}\xBB` : null;
|
|
11255
|
+
if (p.kind === "actor") {
|
|
11256
|
+
const topY = ll.headY;
|
|
11257
|
+
const fig = [];
|
|
11258
|
+
if (stereo) {
|
|
11259
|
+
fig.push(text({ class: "sx-seq-head-stereo", x: cx, y: topY - 4, "text-anchor": "middle" }, stereo));
|
|
11260
|
+
}
|
|
11261
|
+
fig.push(
|
|
11262
|
+
circle({ class: "sx-seq-actor-head", cx, cy: topY + 6, r: 4.5 }),
|
|
11263
|
+
line({ x1: cx, y1: topY + 10, x2: cx, y2: topY + 22 }),
|
|
11264
|
+
line({ x1: cx - 8, y1: topY + 14, x2: cx + 8, y2: topY + 14 }),
|
|
11265
|
+
line({ x1: cx, y1: topY + 22, x2: cx - 7, y2: topY + 32 }),
|
|
11266
|
+
line({ x1: cx, y1: topY + 22, x2: cx + 7, y2: topY + 32 }),
|
|
11267
|
+
text({ class: "sx-seq-head-name", x: cx, y: topY + 44, "text-anchor": "middle" }, p.name)
|
|
11268
|
+
);
|
|
11269
|
+
return group({ class: "sx-seq-actor sx-seq-head" }, fig);
|
|
11270
|
+
}
|
|
11271
|
+
if (p.kind === "boundary" || p.kind === "control" || p.kind === "entity") {
|
|
11272
|
+
return renderRobustnessIcon(ll, stereo);
|
|
11273
|
+
}
|
|
11274
|
+
if (p.kind === "database") {
|
|
11275
|
+
const w = ll.headW;
|
|
11276
|
+
const h = ll.headH;
|
|
11277
|
+
const x = ll.headX;
|
|
11278
|
+
const y = ll.headY;
|
|
11279
|
+
const ry = 5;
|
|
11280
|
+
const d = `M ${x} ${y + ry} a ${w / 2} ${ry} 0 0 1 ${w} 0 v ${h - 2 * ry} a ${w / 2} ${ry} 0 0 1 ${-w} 0 Z M ${x} ${y + ry} a ${w / 2} ${ry} 0 0 0 ${w} 0`;
|
|
11281
|
+
return group({ class: "sx-seq-head" }, [
|
|
11282
|
+
path({ d }),
|
|
11283
|
+
text({ class: "sx-seq-head-name", x: cx, y: y + h / 2 + 6, "text-anchor": "middle" }, p.name)
|
|
11284
|
+
]);
|
|
11285
|
+
}
|
|
11286
|
+
const parts = [
|
|
11287
|
+
rect({ x: ll.headX, y: ll.headY, width: ll.headW, height: ll.headH, rx: 3, ry: 3 })
|
|
11288
|
+
];
|
|
11289
|
+
const stereoText = stereo ?? (p.kind === "collections" || p.kind === "queue" ? `\xAB${p.kind}\xBB` : null);
|
|
11290
|
+
if (stereoText) {
|
|
11291
|
+
parts.push(
|
|
11292
|
+
text({ class: "sx-seq-head-stereo", x: cx, y: ll.headY + 14, "text-anchor": "middle" }, stereoText)
|
|
11293
|
+
);
|
|
11294
|
+
parts.push(
|
|
11295
|
+
text({ class: "sx-seq-head-name", x: cx, y: ll.headY + 28, "text-anchor": "middle" }, p.name)
|
|
11296
|
+
);
|
|
11297
|
+
} else {
|
|
11298
|
+
parts.push(
|
|
11299
|
+
text({ class: "sx-seq-head-name", x: cx, y: ll.headY + ll.headH / 2 + 5, "text-anchor": "middle" }, p.name)
|
|
11300
|
+
);
|
|
11301
|
+
}
|
|
11302
|
+
return group({ class: "sx-seq-head" }, parts);
|
|
11303
|
+
}
|
|
11304
|
+
function renderRobustnessIcon(ll, stereo) {
|
|
11305
|
+
const p = ll.participant;
|
|
11306
|
+
const cx = ll.x;
|
|
11307
|
+
const R = 11;
|
|
11308
|
+
const cy = ll.headY + 14;
|
|
11309
|
+
const parts = [];
|
|
11310
|
+
if (stereo) {
|
|
11311
|
+
parts.push(text({ class: "sx-seq-head-stereo", x: cx, y: ll.headY - 2, "text-anchor": "middle" }, stereo));
|
|
11312
|
+
}
|
|
11313
|
+
parts.push(circle({ class: "sx-seq-icon", cx, cy, r: R }));
|
|
11314
|
+
if (p.kind === "boundary") {
|
|
11315
|
+
const bx = cx - R - 9;
|
|
11316
|
+
parts.push(line({ class: "sx-seq-icon-line", x1: bx, y1: cy - R, x2: bx, y2: cy + R }));
|
|
11317
|
+
parts.push(line({ class: "sx-seq-icon-line", x1: bx, y1: cy, x2: cx - R, y2: cy }));
|
|
11318
|
+
} else if (p.kind === "entity") {
|
|
11319
|
+
parts.push(line({ class: "sx-seq-icon-line", x1: cx - R - 2, y1: cy + R + 1, x2: cx + R + 2, y2: cy + R + 1 }));
|
|
11320
|
+
} else {
|
|
11321
|
+
parts.push(
|
|
11322
|
+
polygon({ class: "sx-seq-icon-fill", points: `${cx - 2},${cy - R - 6} ${cx + 5},${cy - R - 1} ${cx - 3},${cy - R + 2}` })
|
|
11323
|
+
);
|
|
11324
|
+
}
|
|
11325
|
+
parts.push(text({ class: "sx-seq-head-name", x: cx, y: ll.headY + 44, "text-anchor": "middle" }, p.name));
|
|
11326
|
+
return group({ class: "sx-seq-head sx-seq-icon-head", "data-icon": p.kind }, parts);
|
|
11327
|
+
}
|
|
11328
|
+
function renderActivation(a) {
|
|
11329
|
+
const h = Math.max(6, a.yBottom - a.yTop);
|
|
11330
|
+
return rect({
|
|
11331
|
+
class: "sx-seq-act",
|
|
11332
|
+
"data-id": a.id,
|
|
11333
|
+
x: a.x,
|
|
11334
|
+
y: a.yTop,
|
|
11335
|
+
width: 10,
|
|
11336
|
+
height: h
|
|
11337
|
+
});
|
|
11338
|
+
}
|
|
11339
|
+
function renderMessage(m) {
|
|
11340
|
+
const k = m.message.arrow;
|
|
11341
|
+
const lineClass2 = k === "reply" ? "sx-seq-msg-reply" : "sx-seq-msg";
|
|
11342
|
+
const markerEnd = k === "async" || k === "reply" ? "url(#sx-seq-open)" : "url(#sx-seq-filled)";
|
|
11343
|
+
const parts = [];
|
|
11344
|
+
if (m.self) {
|
|
11345
|
+
const bottom = m.selfBottomY ?? m.y + 28;
|
|
11346
|
+
const right = m.x2;
|
|
11347
|
+
const d = `M ${m.x1} ${m.y} H ${right} V ${bottom} H ${m.x1}`;
|
|
11348
|
+
parts.push(path({ class: lineClass2, d, "marker-end": markerEnd }));
|
|
11349
|
+
if (m.message.label) {
|
|
11350
|
+
parts.push(
|
|
11351
|
+
text(
|
|
11352
|
+
{ class: "sx-seq-msg-label", x: right + 6, y: (m.y + bottom) / 2 + 3 },
|
|
11353
|
+
numbered(m, m.message.label)
|
|
11354
|
+
)
|
|
11355
|
+
);
|
|
11356
|
+
}
|
|
11357
|
+
} else {
|
|
11358
|
+
parts.push(line({ class: lineClass2, x1: m.x1, y1: m.y, x2: m.x2, y2: m.y, "marker-end": markerEnd }));
|
|
11359
|
+
if (k === "lost") parts.push(circle({ class: "sx-seq-endpoint", cx: m.x2, cy: m.y, r: 4 }));
|
|
11360
|
+
if (k === "found") parts.push(circle({ class: "sx-seq-endpoint", cx: m.x1, cy: m.y, r: 4 }));
|
|
11361
|
+
if (m.message.label) {
|
|
11362
|
+
const mid = (m.x1 + m.x2) / 2;
|
|
11363
|
+
parts.push(
|
|
11364
|
+
text(
|
|
11365
|
+
{ class: "sx-seq-msg-label", x: mid, y: m.y - 6, "text-anchor": "middle" },
|
|
11366
|
+
numbered(m, m.message.label)
|
|
11367
|
+
)
|
|
11368
|
+
);
|
|
11369
|
+
}
|
|
11370
|
+
}
|
|
11371
|
+
return group(
|
|
11372
|
+
{
|
|
11373
|
+
class: "sx-seq-message",
|
|
11374
|
+
"data-kind": k,
|
|
11375
|
+
"data-from": m.message.from,
|
|
11376
|
+
"data-to": m.message.to
|
|
11377
|
+
},
|
|
11378
|
+
parts
|
|
11379
|
+
);
|
|
11380
|
+
}
|
|
11381
|
+
function numbered(m, label) {
|
|
11382
|
+
return m.number !== void 0 ? `${m.number}. ${label}` : label;
|
|
11383
|
+
}
|
|
11384
|
+
function tabWidth(f) {
|
|
11385
|
+
return Math.max(40, f.op.length * 8 + 22);
|
|
11386
|
+
}
|
|
11387
|
+
function renderFragmentBox(f) {
|
|
11388
|
+
const frameClass = f.op === "neg" ? "sx-seq-frame sx-seq-frame-neg" : "sx-seq-frame";
|
|
11389
|
+
const parts = [
|
|
11390
|
+
rect({ class: frameClass, x: f.x, y: f.y, width: f.width, height: f.height, rx: 2, ry: 2 })
|
|
11391
|
+
];
|
|
11392
|
+
for (const op of f.operands) {
|
|
11393
|
+
if (op.sepY !== void 0) {
|
|
11394
|
+
parts.push(line({ class: "sx-seq-frame-sep", x1: f.x, y1: op.sepY, x2: f.x + f.width, y2: op.sepY }));
|
|
11395
|
+
}
|
|
11396
|
+
}
|
|
11397
|
+
return group({ class: "sx-seq-fragment", "data-op": f.op }, parts);
|
|
11398
|
+
}
|
|
11399
|
+
function renderFragmentTab(f) {
|
|
11400
|
+
const tabW = tabWidth(f);
|
|
11401
|
+
const tabH = 18;
|
|
11402
|
+
const fold = 6;
|
|
11403
|
+
const tx = f.x;
|
|
11404
|
+
const ty = f.y;
|
|
11405
|
+
const tab = `M ${tx} ${ty} H ${tx + tabW} V ${ty + tabH - fold} L ${tx + tabW - fold} ${ty + tabH} H ${tx} Z`;
|
|
11406
|
+
const parts = [
|
|
11407
|
+
path({ class: "sx-seq-frame-tab", d: tab }),
|
|
11408
|
+
text({ class: "sx-seq-frame-op", x: tx + 8, y: ty + 13 }, f.op)
|
|
11409
|
+
];
|
|
11410
|
+
if (f.messageSet && f.messageSet.length) {
|
|
11411
|
+
parts.push(
|
|
11412
|
+
text({ class: "sx-seq-guard", x: tx + tabW + 8, y: ty + 13 }, `{${f.messageSet.join(", ")}}`)
|
|
11413
|
+
);
|
|
11414
|
+
}
|
|
11415
|
+
f.operands.forEach((op, i) => {
|
|
11416
|
+
if (!op.guard) return;
|
|
11417
|
+
const gx = i === 0 ? f.x + tabW + 8 : f.x + 8;
|
|
11418
|
+
const gy = i === 0 ? f.y + 13 : (op.sepY ?? f.y) + 14;
|
|
11419
|
+
parts.push(text({ class: "sx-seq-guard", x: gx, y: gy }, `[${op.guard}]`));
|
|
11420
|
+
});
|
|
11421
|
+
return group({ class: "sx-seq-fragment-tab-g", "data-op": f.op }, parts);
|
|
11422
|
+
}
|
|
11423
|
+
function renderRef(r) {
|
|
11424
|
+
const tabW = 40;
|
|
11425
|
+
const tabH = 18;
|
|
11426
|
+
const fold = 6;
|
|
11427
|
+
const tab = `M ${r.x} ${r.y} H ${r.x + tabW} V ${r.y + tabH - fold} L ${r.x + tabW - fold} ${r.y + tabH} H ${r.x} Z`;
|
|
11428
|
+
return group({ class: "sx-seq-fragment", "data-op": "ref" }, [
|
|
11429
|
+
rect({ class: "sx-seq-frame", x: r.x, y: r.y, width: r.width, height: r.height, rx: 2, ry: 2 }),
|
|
11430
|
+
path({ class: "sx-seq-frame-tab", d: tab }),
|
|
11431
|
+
text({ class: "sx-seq-frame-op", x: r.x + 8, y: r.y + 13 }, "ref"),
|
|
11432
|
+
text(
|
|
11433
|
+
{ class: "sx-seq-ref-name", x: r.x + r.width / 2, y: r.y + r.height / 2 + 8, "text-anchor": "middle" },
|
|
11434
|
+
r.text
|
|
11435
|
+
)
|
|
11436
|
+
]);
|
|
11437
|
+
}
|
|
11438
|
+
function renderNote2(nb) {
|
|
11439
|
+
const fold = 8;
|
|
11440
|
+
const x = nb.x;
|
|
11441
|
+
const y = nb.y;
|
|
11442
|
+
const w = nb.width;
|
|
11443
|
+
const h = nb.height;
|
|
11444
|
+
const body = `M ${x} ${y} H ${x + w - fold} L ${x + w} ${y + fold} V ${y + h} H ${x} Z`;
|
|
11445
|
+
const corner = `M ${x + w - fold} ${y} V ${y + fold} H ${x + w} Z`;
|
|
11446
|
+
const lines = nb.note.text.split(/<br\s*\/?>|\\n/);
|
|
11447
|
+
const startY = y + h / 2 - (lines.length - 1) * 15 / 2 + 4;
|
|
11448
|
+
const texts = lines.map(
|
|
11449
|
+
(l, i) => text({ class: "sx-seq-note-text", x: x + w / 2, y: startY + i * 15, "text-anchor": "middle" }, l)
|
|
11450
|
+
);
|
|
11451
|
+
return group({ class: "sx-seq-note" }, [path({ d: body }), path({ d: corner }), ...texts]);
|
|
11452
|
+
}
|
|
11453
|
+
function renderDivider(d) {
|
|
11454
|
+
const labelW = Math.max(60, d.text.length * CHAR_W + 24);
|
|
11455
|
+
const cx = d.width / 2;
|
|
11456
|
+
const parts = [
|
|
11457
|
+
line({ x1: 8, y1: d.y, x2: cx - labelW / 2, y2: d.y }),
|
|
11458
|
+
line({ x1: cx + labelW / 2, y1: d.y, x2: d.width - 8, y2: d.y }),
|
|
11459
|
+
rect({ x: cx - labelW / 2, y: d.y - 11, width: labelW, height: 22, rx: 4, ry: 4 }),
|
|
11460
|
+
text({ class: "sx-seq-div-text", x: cx, y: d.y + 4, "text-anchor": "middle" }, d.text)
|
|
11461
|
+
];
|
|
11462
|
+
return group({ class: "sx-seq-div" }, parts);
|
|
11463
|
+
}
|
|
11464
|
+
function renderInvariant(iv) {
|
|
11465
|
+
return group({ class: "sx-seq-inv" }, [
|
|
11466
|
+
rect({ x: iv.cx - iv.width / 2, y: iv.y, width: iv.width, height: iv.height, rx: iv.height / 2, ry: iv.height / 2 }),
|
|
11467
|
+
text({ class: "sx-seq-inv-text", x: iv.cx, y: iv.y + iv.height / 2 + 4, "text-anchor": "middle" }, `{${iv.text}}`)
|
|
11468
|
+
]);
|
|
11469
|
+
}
|
|
11470
|
+
function renderDestroy(d) {
|
|
11471
|
+
const r = 7;
|
|
11472
|
+
return group({ class: "sx-seq-destroy-g" }, [
|
|
11473
|
+
line({ class: "sx-seq-destroy", x1: d.x - r, y1: d.y - r, x2: d.x + r, y2: d.y + r }),
|
|
11474
|
+
line({ class: "sx-seq-destroy", x1: d.x - r, y1: d.y + r, x2: d.x + r, y2: d.y - r })
|
|
11475
|
+
]);
|
|
11476
|
+
}
|
|
11477
|
+
function renderSequenceLayout(layout, config) {
|
|
11478
|
+
const t = resolveBaseTheme(config?.theme ?? "default");
|
|
11479
|
+
const children = [];
|
|
11480
|
+
const nMsg = layout.messages.length;
|
|
11481
|
+
const nFrag = layout.fragments.length;
|
|
11482
|
+
children.push(title(`Sequence Diagram${layout.title ? " \u2014 " + layout.title : ""}`));
|
|
11483
|
+
children.push(
|
|
11484
|
+
desc(
|
|
11485
|
+
`${layout.lifelines.length} participants, ${nMsg} messages, ${nFrag} combined fragments.`
|
|
11486
|
+
)
|
|
11487
|
+
);
|
|
11488
|
+
children.push(el("style", {}, buildCss5(t)));
|
|
11489
|
+
children.push(markers3(t));
|
|
11490
|
+
const titleBand = layout.title ? 32 : 0;
|
|
11491
|
+
if (layout.title) {
|
|
11492
|
+
children.push(
|
|
11493
|
+
text({ x: layout.width / 2, y: 22, class: "sx-seq-title", "text-anchor": "middle" }, layout.title)
|
|
11494
|
+
);
|
|
11495
|
+
}
|
|
11496
|
+
const body = [];
|
|
11497
|
+
body.push(group({ class: "sx-seq-lifelines" }, layout.lifelines.map(renderLifeline)));
|
|
11498
|
+
body.push(group({ class: "sx-seq-frames" }, layout.fragments.map(renderFragmentBox)));
|
|
11499
|
+
body.push(group({ class: "sx-seq-acts" }, layout.activations.map(renderActivation)));
|
|
11500
|
+
body.push(group({ class: "sx-seq-frame-tabs" }, layout.fragments.map(renderFragmentTab)));
|
|
11501
|
+
body.push(group({ class: "sx-seq-refs" }, layout.refs.map(renderRef)));
|
|
11502
|
+
body.push(group({ class: "sx-seq-messages" }, layout.messages.map(renderMessage)));
|
|
11503
|
+
body.push(group({ class: "sx-seq-notes" }, layout.notes.map(renderNote2)));
|
|
11504
|
+
body.push(group({ class: "sx-seq-divs" }, layout.dividers.map(renderDivider)));
|
|
11505
|
+
body.push(group({ class: "sx-seq-invs" }, layout.invariants.map(renderInvariant)));
|
|
11506
|
+
body.push(group({ class: "sx-seq-destroys" }, layout.destroys.map(renderDestroy)));
|
|
11507
|
+
children.push(
|
|
11508
|
+
titleBand ? group({ transform: `translate(0, ${titleBand})` }, body) : group({}, body)
|
|
11509
|
+
);
|
|
11510
|
+
const height = layout.height + titleBand;
|
|
11511
|
+
return svgRoot(
|
|
11512
|
+
{
|
|
11513
|
+
class: "sx-seq",
|
|
11514
|
+
role: "img",
|
|
11515
|
+
"aria-label": escapeXml(layout.title ?? "UML sequence diagram"),
|
|
11516
|
+
width: layout.width,
|
|
11517
|
+
height,
|
|
11518
|
+
viewBox: `0 0 ${layout.width} ${height}`,
|
|
11519
|
+
"data-diagram-type": "sequence"
|
|
11520
|
+
},
|
|
11521
|
+
children
|
|
11522
|
+
);
|
|
11523
|
+
}
|
|
11524
|
+
function renderSequence(textOrAst, config) {
|
|
11525
|
+
const ast = typeof textOrAst === "string" ? parseSequence(textOrAst) : textOrAst;
|
|
11526
|
+
const layout = layoutSequence(ast);
|
|
11527
|
+
return renderSequenceLayout(layout, config);
|
|
11528
|
+
}
|
|
11529
|
+
|
|
11530
|
+
// src/diagrams/sequence/index.ts
|
|
11531
|
+
var sequence = {
|
|
11532
|
+
type: "sequence",
|
|
11533
|
+
detect(text2) {
|
|
11534
|
+
for (const raw of text2.split(/\r?\n/)) {
|
|
11535
|
+
const t = raw.trim();
|
|
11536
|
+
if (!t) continue;
|
|
11537
|
+
if (t.startsWith("#") || t.startsWith("//")) continue;
|
|
11538
|
+
return /^sequence\b/i.test(t);
|
|
11539
|
+
}
|
|
11540
|
+
return false;
|
|
11541
|
+
},
|
|
11542
|
+
parse: parseSequence,
|
|
11543
|
+
render(text2, config) {
|
|
11544
|
+
return renderSequence(text2, config);
|
|
11545
|
+
}
|
|
11546
|
+
};
|
|
11547
|
+
|
|
11548
|
+
// src/diagrams/petri/parser.ts
|
|
11549
|
+
var PetriParseError = class extends Error {
|
|
11550
|
+
line;
|
|
11551
|
+
constructor(message, line2) {
|
|
11552
|
+
super(line2 !== void 0 ? `${message} (line ${line2})` : message);
|
|
11553
|
+
this.name = "PetriParseError";
|
|
11554
|
+
this.line = line2;
|
|
11555
|
+
}
|
|
11556
|
+
};
|
|
11557
|
+
var OPENERS = ['"', "\u201C", "\u300C", "\u300E", "\xAB"];
|
|
11558
|
+
var CLOSERS = ['"', "\u201D", "\u300D", "\u300F", "\xBB"];
|
|
11559
|
+
function tokenize3(s) {
|
|
11560
|
+
const out = [];
|
|
11561
|
+
let i = 0;
|
|
11562
|
+
while (i < s.length) {
|
|
11563
|
+
const c = s[i];
|
|
11564
|
+
if (c === " " || c === " ") {
|
|
11565
|
+
i++;
|
|
11566
|
+
continue;
|
|
11567
|
+
}
|
|
11568
|
+
const oi = OPENERS.indexOf(c);
|
|
11569
|
+
if (oi >= 0) {
|
|
11570
|
+
const close = CLOSERS[oi];
|
|
11571
|
+
let j2 = i + 1;
|
|
11572
|
+
let buf2 = "";
|
|
11573
|
+
while (j2 < s.length && s[j2] !== close && s[j2] !== '"') {
|
|
11574
|
+
buf2 += s[j2];
|
|
11575
|
+
j2++;
|
|
11576
|
+
}
|
|
11577
|
+
out.push({ text: buf2, quoted: true });
|
|
11578
|
+
i = j2 + 1;
|
|
11579
|
+
continue;
|
|
11580
|
+
}
|
|
11581
|
+
let j = i;
|
|
11582
|
+
let buf = "";
|
|
11583
|
+
while (j < s.length && s[j] !== " " && s[j] !== " ") {
|
|
11584
|
+
buf += s[j];
|
|
11585
|
+
j++;
|
|
11586
|
+
}
|
|
11587
|
+
out.push({ text: buf, quoted: false });
|
|
11588
|
+
i = j;
|
|
11589
|
+
}
|
|
11590
|
+
return out;
|
|
11591
|
+
}
|
|
11592
|
+
function normalizeKeyNums(line2) {
|
|
11593
|
+
return line2.replace(/([A-Za-z]+)\s*:\s*(-?\d+(?:\.\d+)?)/g, "$1:$2");
|
|
11594
|
+
}
|
|
11595
|
+
var DOT_RE = /^[•●・]+$/;
|
|
11596
|
+
var ARC_RE = /^(\S+?)\s*(->|-o|=>|--)\s*(\S+)(.*)$/;
|
|
11597
|
+
function toInt(raw, what, line2) {
|
|
11598
|
+
if (!/^-?\d+$/.test(raw)) {
|
|
11599
|
+
throw new PetriParseError(`${what} must be an integer, got "${raw}"`, line2);
|
|
11600
|
+
}
|
|
11601
|
+
return parseInt(raw, 10);
|
|
11602
|
+
}
|
|
11603
|
+
function parsePetri(text2) {
|
|
11604
|
+
const rawLines = text2.split(/\r?\n/);
|
|
11605
|
+
const ast = {
|
|
11606
|
+
type: "petri",
|
|
11607
|
+
direction: "lr",
|
|
11608
|
+
tokenStyle: "auto",
|
|
11609
|
+
places: [],
|
|
11610
|
+
transitions: [],
|
|
11611
|
+
arcs: [],
|
|
11612
|
+
fireSequence: [],
|
|
11613
|
+
warnings: []
|
|
11614
|
+
};
|
|
11615
|
+
const placeIds = /* @__PURE__ */ new Set();
|
|
11616
|
+
const transIds = /* @__PURE__ */ new Set();
|
|
11617
|
+
let sawHeader = false;
|
|
11618
|
+
for (let ln = 0; ln < rawLines.length; ln++) {
|
|
11619
|
+
const rawLine = rawLines[ln];
|
|
11620
|
+
const lineNo = ln + 1;
|
|
11621
|
+
const trimmed = rawLine.trim();
|
|
11622
|
+
if (!trimmed) continue;
|
|
11623
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
|
|
11624
|
+
if (!sawHeader && /^petri(net)?\b/i.test(trimmed)) {
|
|
11625
|
+
sawHeader = true;
|
|
11626
|
+
const toks = tokenize3(trimmed);
|
|
11627
|
+
const titleTok = toks.find((t, idx) => idx > 0 && t.quoted);
|
|
11628
|
+
if (titleTok) ast.title = titleTok.text;
|
|
11629
|
+
else if (toks[1] && !toks[1].quoted) ast.title = toks.slice(1).map((t) => t.text).join(" ");
|
|
11630
|
+
continue;
|
|
11631
|
+
}
|
|
11632
|
+
if (!sawHeader) {
|
|
11633
|
+
if (/^petri(net)?\b/i.test(trimmed)) {
|
|
11634
|
+
sawHeader = true;
|
|
11635
|
+
continue;
|
|
11636
|
+
}
|
|
11637
|
+
}
|
|
11638
|
+
const lower = trimmed.toLowerCase();
|
|
11639
|
+
if (lower.startsWith("layout:")) {
|
|
11640
|
+
const v = trimmed.slice(trimmed.indexOf(":") + 1).trim().toLowerCase();
|
|
11641
|
+
if (v === "lr" || v === "tb") ast.direction = v;
|
|
11642
|
+
else ast.warnings.push(`Unknown layout "${v}" (line ${lineNo}); using lr.`);
|
|
11643
|
+
continue;
|
|
11644
|
+
}
|
|
11645
|
+
if (lower.startsWith("tokens:")) {
|
|
11646
|
+
const v = trimmed.slice(trimmed.indexOf(":") + 1).trim().toLowerCase();
|
|
11647
|
+
if (v === "dots" || v === "count" || v === "auto") ast.tokenStyle = v;
|
|
11648
|
+
else ast.warnings.push(`Unknown token style "${v}" (line ${lineNo}); using auto.`);
|
|
11649
|
+
continue;
|
|
11650
|
+
}
|
|
11651
|
+
if (lower.startsWith("marking:")) {
|
|
11652
|
+
parseMarking(trimmed.slice(trimmed.indexOf(":") + 1), lineNo, ast, placeIds);
|
|
11653
|
+
continue;
|
|
11654
|
+
}
|
|
11655
|
+
if (lower.startsWith("fire:")) {
|
|
11656
|
+
const ids = trimmed.slice(trimmed.indexOf(":") + 1).split(",").map((s) => s.trim()).filter(Boolean);
|
|
11657
|
+
ast.fireSequence.push(...ids);
|
|
11658
|
+
continue;
|
|
11659
|
+
}
|
|
11660
|
+
if (/^place\b/i.test(trimmed)) {
|
|
11661
|
+
ast.places.push(parsePlace(trimmed, lineNo, placeIds, transIds));
|
|
11662
|
+
continue;
|
|
11663
|
+
}
|
|
11664
|
+
if (/^transition\b/i.test(trimmed)) {
|
|
11665
|
+
ast.transitions.push(parseTransition(rawLine.trim(), lineNo, placeIds, transIds));
|
|
11666
|
+
continue;
|
|
11667
|
+
}
|
|
11668
|
+
const arcM = ARC_RE.exec(trimmed);
|
|
11669
|
+
if (arcM) {
|
|
11670
|
+
ast.arcs.push(parseArc(arcM, lineNo, placeIds, transIds));
|
|
11671
|
+
continue;
|
|
11672
|
+
}
|
|
11673
|
+
ast.warnings.push(`Unrecognized line ${lineNo}: "${trimmed}"`);
|
|
11674
|
+
}
|
|
11675
|
+
for (const id of ast.fireSequence) {
|
|
11676
|
+
if (!transIds.has(id)) {
|
|
11677
|
+
throw new PetriParseError(`fire: references unknown transition "${id}"`);
|
|
11678
|
+
}
|
|
11679
|
+
}
|
|
11680
|
+
return ast;
|
|
11681
|
+
}
|
|
11682
|
+
function declareDup(id, lineNo, placeIds, transIds) {
|
|
11683
|
+
if (placeIds.has(id) || transIds.has(id)) {
|
|
11684
|
+
throw new PetriParseError(`duplicate node id "${id}"`, lineNo);
|
|
11685
|
+
}
|
|
11686
|
+
}
|
|
11687
|
+
function parsePlace(line2, lineNo, placeIds, transIds) {
|
|
11688
|
+
const toks = tokenize3(normalizeKeyNums(line2));
|
|
11689
|
+
const idTok = toks[1];
|
|
11690
|
+
if (!idTok || idTok.quoted) throw new PetriParseError(`place is missing an id`, lineNo);
|
|
11691
|
+
const id = idTok.text;
|
|
11692
|
+
declareDup(id, lineNo, placeIds, transIds);
|
|
11693
|
+
const place = { id, tokens: 0, line: lineNo };
|
|
11694
|
+
const labelParts = [];
|
|
11695
|
+
for (let i = 2; i < toks.length; i++) {
|
|
11696
|
+
const t = toks[i];
|
|
11697
|
+
if (t.quoted) {
|
|
11698
|
+
labelParts.push(t.text);
|
|
11699
|
+
continue;
|
|
11700
|
+
}
|
|
11701
|
+
const txt = t.text;
|
|
11702
|
+
if (/^\*\d+$/.test(txt)) {
|
|
11703
|
+
place.tokens = parseInt(txt.slice(1), 10);
|
|
11704
|
+
} else if (DOT_RE.test(txt)) {
|
|
11705
|
+
place.tokens = [...txt].length;
|
|
11706
|
+
} else if (/^tokens:/i.test(txt)) {
|
|
11707
|
+
place.tokens = toInt(txt.slice(txt.indexOf(":") + 1), "tokens", lineNo);
|
|
11708
|
+
} else if (/^capacity:/i.test(txt)) {
|
|
11709
|
+
const k = toInt(txt.slice(txt.indexOf(":") + 1), "capacity", lineNo);
|
|
11710
|
+
if (k <= 0) throw new PetriParseError(`capacity must be a positive integer`, lineNo);
|
|
11711
|
+
place.capacity = k;
|
|
11712
|
+
} else {
|
|
11713
|
+
labelParts.push(txt);
|
|
11714
|
+
}
|
|
11715
|
+
}
|
|
11716
|
+
if (place.tokens < 0) throw new PetriParseError(`token count cannot be negative`, lineNo);
|
|
11717
|
+
if (labelParts.length) place.label = labelParts.join(" ");
|
|
11718
|
+
placeIds.add(id);
|
|
11719
|
+
return place;
|
|
11720
|
+
}
|
|
11721
|
+
function parseTransition(line2, lineNo, placeIds, transIds) {
|
|
11722
|
+
let guard;
|
|
11723
|
+
const stripped = line2.replace(/\[([^\]]*)\]/, (_m, g) => {
|
|
11724
|
+
guard = String(g).trim();
|
|
11725
|
+
return " ";
|
|
11726
|
+
});
|
|
11727
|
+
const toks = tokenize3(normalizeKeyNums(stripped));
|
|
11728
|
+
const idTok = toks[1];
|
|
11729
|
+
if (!idTok || idTok.quoted) throw new PetriParseError(`transition is missing an id`, lineNo);
|
|
11730
|
+
const id = idTok.text;
|
|
11731
|
+
declareDup(id, lineNo, placeIds, transIds);
|
|
11732
|
+
const tr = { id, kind: "immediate", line: lineNo };
|
|
11733
|
+
if (guard) tr.guard = guard;
|
|
11734
|
+
const labelParts = [];
|
|
11735
|
+
for (let i = 2; i < toks.length; i++) {
|
|
11736
|
+
const t = toks[i];
|
|
11737
|
+
if (t.quoted) {
|
|
11738
|
+
labelParts.push(t.text);
|
|
11739
|
+
continue;
|
|
11740
|
+
}
|
|
11741
|
+
const txt = t.text;
|
|
11742
|
+
const low = txt.toLowerCase();
|
|
11743
|
+
if (low === "immediate") tr.kind = "immediate";
|
|
11744
|
+
else if (low === "timed") tr.kind = "timed";
|
|
11745
|
+
else if (/^rate:/i.test(txt)) {
|
|
11746
|
+
tr.rate = parseFloat(txt.slice(txt.indexOf(":") + 1));
|
|
11747
|
+
tr.kind = "timed";
|
|
11748
|
+
} else if (/^(prio|priority):/i.test(txt)) {
|
|
11749
|
+
tr.priority = toInt(txt.slice(txt.indexOf(":") + 1), "priority", lineNo);
|
|
11750
|
+
} else {
|
|
11751
|
+
labelParts.push(txt);
|
|
11752
|
+
}
|
|
11753
|
+
}
|
|
11754
|
+
if (labelParts.length) tr.label = labelParts.join(" ");
|
|
11755
|
+
transIds.add(id);
|
|
11756
|
+
return tr;
|
|
11757
|
+
}
|
|
11758
|
+
var ARROW_TYPE = {
|
|
11759
|
+
"->": "standard",
|
|
11760
|
+
"-o": "inhibitor",
|
|
11761
|
+
"--": "read",
|
|
11762
|
+
"=>": "reset"
|
|
11763
|
+
};
|
|
11764
|
+
function parseArc(m, lineNo, placeIds, transIds) {
|
|
11765
|
+
const from = m[1];
|
|
11766
|
+
const arrow2 = m[2];
|
|
11767
|
+
const to = m[3];
|
|
11768
|
+
const rest = m[4] ?? "";
|
|
11769
|
+
const type = ARROW_TYPE[arrow2];
|
|
11770
|
+
const known = (id) => placeIds.has(id) ? "place" : transIds.has(id) ? "transition" : null;
|
|
11771
|
+
const kf = known(from);
|
|
11772
|
+
const kt = known(to);
|
|
11773
|
+
if (!kf) throw new PetriParseError(`arc references unknown node "${from}" \u2014 declare it as a place or transition first`, lineNo);
|
|
11774
|
+
if (!kt) throw new PetriParseError(`arc references unknown node "${to}" \u2014 declare it as a place or transition first`, lineNo);
|
|
11775
|
+
if (kf === kt) {
|
|
11776
|
+
throw new PetriParseError(
|
|
11777
|
+
`arc connects two ${kf}s \u2014 a Petri net arc must go place\u2192transition or transition\u2192place`,
|
|
11778
|
+
lineNo
|
|
11779
|
+
);
|
|
11780
|
+
}
|
|
11781
|
+
if ((type === "inhibitor" || type === "reset") && kf !== "place") {
|
|
11782
|
+
throw new PetriParseError(`${type} arcs are place\u2192transition only`, lineNo);
|
|
11783
|
+
}
|
|
11784
|
+
const arc = { from, to, type, weight: 1, line: lineNo };
|
|
11785
|
+
const toks = tokenize3(normalizeKeyNums(rest));
|
|
11786
|
+
const labelParts = [];
|
|
11787
|
+
for (const t of toks) {
|
|
11788
|
+
if (t.quoted) {
|
|
11789
|
+
labelParts.push(t.text);
|
|
11790
|
+
continue;
|
|
11791
|
+
}
|
|
11792
|
+
const txt = t.text;
|
|
11793
|
+
if (/^\*\d+$/.test(txt)) arc.weight = parseInt(txt.slice(1), 10);
|
|
11794
|
+
else if (/^weight:/i.test(txt)) arc.weight = toInt(txt.slice(txt.indexOf(":") + 1), "weight", lineNo);
|
|
11795
|
+
else labelParts.push(txt);
|
|
11796
|
+
}
|
|
11797
|
+
if (arc.weight <= 0) throw new PetriParseError(`arc weight must be a positive integer`, lineNo);
|
|
11798
|
+
if (labelParts.length) arc.label = labelParts.join(" ");
|
|
11799
|
+
return arc;
|
|
11800
|
+
}
|
|
11801
|
+
function parseMarking(body, lineNo, ast, placeIds) {
|
|
11802
|
+
for (const part of body.split(",")) {
|
|
11803
|
+
const p = part.trim();
|
|
11804
|
+
if (!p) continue;
|
|
11805
|
+
const eq = p.indexOf("=");
|
|
11806
|
+
if (eq < 0) {
|
|
11807
|
+
ast.warnings.push(`Bad marking entry "${p}" (line ${lineNo}); expected id=n.`);
|
|
11808
|
+
continue;
|
|
11809
|
+
}
|
|
11810
|
+
const id = p.slice(0, eq).trim();
|
|
11811
|
+
const n = toInt(p.slice(eq + 1).trim(), "marking", lineNo);
|
|
11812
|
+
if (!placeIds.has(id)) {
|
|
11813
|
+
throw new PetriParseError(`marking references unknown place "${id}"`, lineNo);
|
|
11814
|
+
}
|
|
11815
|
+
const place = ast.places.find((pl) => pl.id === id);
|
|
11816
|
+
if (place) place.tokens = n;
|
|
11817
|
+
}
|
|
11818
|
+
}
|
|
11819
|
+
|
|
11820
|
+
// src/diagrams/petri/layout.ts
|
|
11821
|
+
var PETRI_CONST = {
|
|
11822
|
+
PLACE_R: 18,
|
|
11823
|
+
TRANS_BAR_W: 8,
|
|
11824
|
+
TRANS_BAR_H: 44,
|
|
11825
|
+
TRANS_BOX_W: 26,
|
|
11826
|
+
TRANS_BOX_H: 40,
|
|
11827
|
+
LAYER_GAP: 70,
|
|
11828
|
+
RANK_GAP: 46,
|
|
11829
|
+
TOKEN_R: 3.5,
|
|
11830
|
+
TOKEN_COUNT_MAX_DOTS: 4,
|
|
11831
|
+
ARC_WEIGHT_OFFSET: 9,
|
|
11832
|
+
LABEL_GAP: 6,
|
|
11833
|
+
MARGIN: 22,
|
|
11834
|
+
BACKEDGE_BOW: 30,
|
|
11835
|
+
LABEL_LINE_H: 13,
|
|
11836
|
+
CHAR_W: 6.2
|
|
11837
|
+
};
|
|
11838
|
+
function layoutPetri(ast) {
|
|
11839
|
+
const C2 = PETRI_CONST;
|
|
11840
|
+
const dir = ast.direction;
|
|
11841
|
+
const warnings = [...ast.warnings];
|
|
11842
|
+
const kindOf = /* @__PURE__ */ new Map();
|
|
11843
|
+
for (const p of ast.places) kindOf.set(p.id, "place");
|
|
11844
|
+
for (const t of ast.transitions) kindOf.set(t.id, "transition");
|
|
11845
|
+
const ids = [...ast.places.map((p) => p.id), ...ast.transitions.map((t) => t.id)];
|
|
11846
|
+
const declOrder = /* @__PURE__ */ new Map();
|
|
11847
|
+
ids.forEach((id, i) => declOrder.set(id, i));
|
|
11848
|
+
const edges = ast.arcs.map((a) => ({ from: a.from, to: a.to, reversed: false }));
|
|
11849
|
+
const adj = /* @__PURE__ */ new Map();
|
|
11850
|
+
ids.forEach((id) => adj.set(id, []));
|
|
11851
|
+
edges.forEach((e, i) => adj.get(e.from).push(i));
|
|
11852
|
+
const state2 = /* @__PURE__ */ new Map();
|
|
11853
|
+
ids.forEach((id) => state2.set(id, 0));
|
|
11854
|
+
const dfs = (u) => {
|
|
11855
|
+
state2.set(u, 1);
|
|
11856
|
+
for (const ei of adj.get(u)) {
|
|
11857
|
+
const e = edges[ei];
|
|
11858
|
+
const s = state2.get(e.to);
|
|
11859
|
+
if (s === 1) e.reversed = true;
|
|
11860
|
+
else if (s === 0) dfs(e.to);
|
|
11861
|
+
}
|
|
11862
|
+
state2.set(u, 2);
|
|
11863
|
+
};
|
|
11864
|
+
for (const id of ids) if (state2.get(id) === 0) dfs(id);
|
|
11865
|
+
const succ = /* @__PURE__ */ new Map();
|
|
11866
|
+
const indeg = /* @__PURE__ */ new Map();
|
|
11867
|
+
ids.forEach((id) => {
|
|
11868
|
+
succ.set(id, []);
|
|
11869
|
+
indeg.set(id, 0);
|
|
11870
|
+
});
|
|
11871
|
+
for (const e of edges) {
|
|
11872
|
+
const [from, to] = e.reversed ? [e.to, e.from] : [e.from, e.to];
|
|
11873
|
+
succ.get(from).push(to);
|
|
11874
|
+
indeg.set(to, indeg.get(to) + 1);
|
|
11875
|
+
}
|
|
11876
|
+
const layer = /* @__PURE__ */ new Map();
|
|
11877
|
+
ids.forEach((id) => layer.set(id, 0));
|
|
11878
|
+
const queue = ids.filter((id) => indeg.get(id) === 0);
|
|
11879
|
+
const indegWork = new Map(indeg);
|
|
11880
|
+
let qi = 0;
|
|
11881
|
+
const q = [...queue];
|
|
11882
|
+
while (qi < q.length) {
|
|
11883
|
+
const u = q[qi++];
|
|
11884
|
+
for (const v of succ.get(u)) {
|
|
11885
|
+
layer.set(v, Math.max(layer.get(v), layer.get(u) + 1));
|
|
11886
|
+
indegWork.set(v, indegWork.get(v) - 1);
|
|
11887
|
+
if (indegWork.get(v) === 0) q.push(v);
|
|
11888
|
+
}
|
|
11889
|
+
}
|
|
11890
|
+
const maxLayer = Math.max(0, ...ids.map((id) => layer.get(id)));
|
|
11891
|
+
const layers = Array.from({ length: maxLayer + 1 }, () => []);
|
|
11892
|
+
for (const id of ids) layers[layer.get(id)].push(id);
|
|
11893
|
+
layers.forEach((arr) => arr.sort((a, b) => declOrder.get(a) - declOrder.get(b)));
|
|
11894
|
+
const pos = /* @__PURE__ */ new Map();
|
|
11895
|
+
const reindex = () => layers.forEach((arr) => arr.forEach((id, i) => pos.set(id, i)));
|
|
11896
|
+
reindex();
|
|
11897
|
+
const preds = /* @__PURE__ */ new Map();
|
|
11898
|
+
const sucs = /* @__PURE__ */ new Map();
|
|
11899
|
+
ids.forEach((id) => {
|
|
11900
|
+
preds.set(id, []);
|
|
11901
|
+
sucs.set(id, []);
|
|
11902
|
+
});
|
|
11903
|
+
for (const e of edges) {
|
|
11904
|
+
const [from, to] = e.reversed ? [e.to, e.from] : [e.from, e.to];
|
|
11905
|
+
sucs.get(from).push(to);
|
|
11906
|
+
preds.get(to).push(from);
|
|
11907
|
+
}
|
|
11908
|
+
const bary = (id, neigh) => {
|
|
11909
|
+
const ns = neigh.get(id);
|
|
11910
|
+
if (!ns.length) return pos.get(id);
|
|
11911
|
+
return ns.reduce((s, n) => s + pos.get(n), 0) / ns.length;
|
|
11912
|
+
};
|
|
11913
|
+
for (let sweep = 0; sweep < 4; sweep++) {
|
|
11914
|
+
const downward = sweep % 2 === 0;
|
|
11915
|
+
const range = downward ? [...Array(layers.length).keys()].slice(1) : [...Array(layers.length).keys()].slice(0, -1).reverse();
|
|
11916
|
+
for (const L of range) {
|
|
11917
|
+
const neigh = downward ? preds : sucs;
|
|
11918
|
+
layers[L].sort((a, b) => bary(a, neigh) - bary(b, neigh) || declOrder.get(a) - declOrder.get(b));
|
|
11919
|
+
reindex();
|
|
11920
|
+
}
|
|
11921
|
+
}
|
|
11922
|
+
const sizeOf2 = (id) => {
|
|
11923
|
+
if (kindOf.get(id) === "place") return { halfW: C2.PLACE_R, halfH: C2.PLACE_R, r: C2.PLACE_R };
|
|
11924
|
+
const tr = ast.transitions.find((t) => t.id === id);
|
|
11925
|
+
const long = tr.kind === "timed" ? C2.TRANS_BOX_H : C2.TRANS_BAR_H;
|
|
11926
|
+
const thin = tr.kind === "timed" ? C2.TRANS_BOX_W : C2.TRANS_BAR_W;
|
|
11927
|
+
const halfW = dir === "lr" ? thin / 2 : long / 2;
|
|
11928
|
+
const halfH = dir === "lr" ? long / 2 : thin / 2;
|
|
11929
|
+
return { halfW, halfH, r: 0 };
|
|
11930
|
+
};
|
|
11931
|
+
const flowHalf = (id) => {
|
|
11932
|
+
const s = sizeOf2(id);
|
|
11933
|
+
return dir === "lr" ? s.halfW : s.halfH;
|
|
11934
|
+
};
|
|
11935
|
+
const crossHalf = (id) => {
|
|
11936
|
+
const s = sizeOf2(id);
|
|
11937
|
+
return dir === "lr" ? s.halfH : s.halfW;
|
|
11938
|
+
};
|
|
11939
|
+
const layerHalf = layers.map((arr) => Math.max(0, ...arr.map(flowHalf)));
|
|
11940
|
+
const slot = Math.max(0, ...ids.map(crossHalf)) * 2 + C2.RANK_GAP;
|
|
11941
|
+
const maxCount = Math.max(1, ...layers.map((a) => a.length));
|
|
11942
|
+
const crossCenter = C2.MARGIN + C2.LABEL_LINE_H * 2 + maxCount * slot / 2;
|
|
11943
|
+
const flowCenter = [];
|
|
11944
|
+
let acc = C2.MARGIN + C2.LABEL_LINE_H * 2;
|
|
11945
|
+
for (let L = 0; L < layers.length; L++) {
|
|
11946
|
+
acc += layerHalf[L];
|
|
11947
|
+
flowCenter[L] = acc;
|
|
11948
|
+
acc += layerHalf[L] + C2.LAYER_GAP;
|
|
11949
|
+
}
|
|
11950
|
+
const geom = /* @__PURE__ */ new Map();
|
|
11951
|
+
layers.forEach((arr, L) => {
|
|
11952
|
+
const n = arr.length;
|
|
11953
|
+
const total = (n - 1) * slot;
|
|
11954
|
+
arr.forEach((id, i) => {
|
|
11955
|
+
const cross = crossCenter - total / 2 + i * slot;
|
|
11956
|
+
const flow = flowCenter[L];
|
|
11957
|
+
const cx = dir === "lr" ? flow : cross;
|
|
11958
|
+
const cy = dir === "lr" ? cross : flow;
|
|
11959
|
+
const s = sizeOf2(id);
|
|
11960
|
+
geom.set(id, { id, kind: kindOf.get(id), cx, cy, halfW: s.halfW, halfH: s.halfH, r: s.r, layer: L });
|
|
11961
|
+
});
|
|
11962
|
+
});
|
|
11963
|
+
const marking = /* @__PURE__ */ new Map();
|
|
11964
|
+
for (const p of ast.places) marking.set(p.id, p.tokens);
|
|
11965
|
+
const inArcs = (tid) => ast.arcs.filter((a) => a.to === tid);
|
|
11966
|
+
const outArcs = (tid) => ast.arcs.filter((a) => a.from === tid);
|
|
11967
|
+
const capOf = (pid2) => ast.places.find((p) => p.id === pid2)?.capacity;
|
|
11968
|
+
const isEnabled = (tid, M) => {
|
|
11969
|
+
for (const a of inArcs(tid)) {
|
|
11970
|
+
const have = M.get(a.from) ?? 0;
|
|
11971
|
+
if (a.type === "standard" || a.type === "read") {
|
|
11972
|
+
if (have < a.weight) return false;
|
|
11973
|
+
} else if (a.type === "inhibitor") {
|
|
11974
|
+
if (have >= a.weight) return false;
|
|
11975
|
+
}
|
|
11976
|
+
}
|
|
11977
|
+
for (const a of outArcs(tid)) {
|
|
11978
|
+
const cap = capOf(a.to);
|
|
11979
|
+
if (cap !== void 0 && (M.get(a.to) ?? 0) + a.weight > cap) return false;
|
|
11980
|
+
}
|
|
11981
|
+
return true;
|
|
11982
|
+
};
|
|
11983
|
+
const applyFire = (tid, M) => {
|
|
11984
|
+
for (const a of inArcs(tid)) {
|
|
11985
|
+
if (a.type === "standard") M.set(a.from, (M.get(a.from) ?? 0) - a.weight);
|
|
11986
|
+
else if (a.type === "reset") M.set(a.from, 0);
|
|
11987
|
+
}
|
|
11988
|
+
for (const a of outArcs(tid)) M.set(a.to, (M.get(a.to) ?? 0) + a.weight);
|
|
11989
|
+
};
|
|
11990
|
+
ast.fireSequence.forEach((tid, i) => {
|
|
11991
|
+
if (isEnabled(tid, marking)) applyFire(tid, marking);
|
|
11992
|
+
else warnings.push(`fire step ${i + 1}: transition "${tid}" is not enabled in the current marking; skipped.`);
|
|
11993
|
+
});
|
|
11994
|
+
const producers = (pid2) => ast.arcs.filter((a) => a.to === pid2 && a.type === "standard").length;
|
|
11995
|
+
const enabledIds = [];
|
|
11996
|
+
const deadIds = /* @__PURE__ */ new Set();
|
|
11997
|
+
for (const tr of ast.transitions) {
|
|
11998
|
+
if (isEnabled(tr.id, marking)) {
|
|
11999
|
+
enabledIds.push(tr.id);
|
|
12000
|
+
continue;
|
|
12001
|
+
}
|
|
12002
|
+
const dead = inArcs(tr.id).some(
|
|
12003
|
+
(a) => (a.type === "standard" || a.type === "read") && (marking.get(a.from) ?? 0) < a.weight && producers(a.from) === 0
|
|
12004
|
+
);
|
|
12005
|
+
if (dead) deadIds.add(tr.id);
|
|
12006
|
+
}
|
|
12007
|
+
const boundary = (g, dx2, dy2) => {
|
|
12008
|
+
const len = Math.hypot(dx2, dy2) || 1;
|
|
12009
|
+
const ux = dx2 / len;
|
|
12010
|
+
const uy = dy2 / len;
|
|
12011
|
+
if (g.kind === "place") return { x: g.cx + ux * g.r, y: g.cy + uy * g.r };
|
|
12012
|
+
const tx = ux !== 0 ? g.halfW / Math.abs(ux) : Infinity;
|
|
12013
|
+
const ty = uy !== 0 ? g.halfH / Math.abs(uy) : Infinity;
|
|
12014
|
+
const t = Math.min(tx, ty);
|
|
12015
|
+
return { x: g.cx + ux * t, y: g.cy + uy * t };
|
|
12016
|
+
};
|
|
12017
|
+
const bandMaxCross = Math.max(...[...geom.values()].map((g) => dir === "lr" ? g.cy + g.halfH : g.cx + g.halfW));
|
|
12018
|
+
const arcGeoms = ast.arcs.map((a, i) => {
|
|
12019
|
+
const A = geom.get(a.from);
|
|
12020
|
+
const B = geom.get(a.to);
|
|
12021
|
+
const reversed = edges[i].reversed;
|
|
12022
|
+
let points;
|
|
12023
|
+
if (!reversed) {
|
|
12024
|
+
const pA = boundary(A, B.cx - A.cx, B.cy - A.cy);
|
|
12025
|
+
const pB = boundary(B, A.cx - B.cx, A.cy - B.cy);
|
|
12026
|
+
points = [pA, pB];
|
|
12027
|
+
} else {
|
|
12028
|
+
if (dir === "lr") {
|
|
12029
|
+
const pA = boundary(A, 0, 1);
|
|
12030
|
+
const pB = boundary(B, 0, 1);
|
|
12031
|
+
const bowY = bandMaxCross + C2.BACKEDGE_BOW;
|
|
12032
|
+
points = [pA, { x: pA.x, y: bowY }, { x: pB.x, y: bowY }, pB];
|
|
12033
|
+
} else {
|
|
12034
|
+
const pA = boundary(A, 1, 0);
|
|
12035
|
+
const pB = boundary(B, 1, 0);
|
|
12036
|
+
const bowX = bandMaxCross + C2.BACKEDGE_BOW;
|
|
12037
|
+
points = [pA, { x: bowX, y: pA.y }, { x: bowX, y: pB.y }, pB];
|
|
12038
|
+
}
|
|
12039
|
+
}
|
|
12040
|
+
const p0 = points[0];
|
|
12041
|
+
const p1 = points[points.length - 1];
|
|
12042
|
+
const mx = (p0.x + p1.x) / 2;
|
|
12043
|
+
const my = (p0.y + p1.y) / 2;
|
|
12044
|
+
const ddx = p1.x - p0.x;
|
|
12045
|
+
const ddy = p1.y - p0.y;
|
|
12046
|
+
const dl = Math.hypot(ddx, ddy) || 1;
|
|
12047
|
+
const labelX = mx - ddy / dl * C2.ARC_WEIGHT_OFFSET;
|
|
12048
|
+
const labelY = my + ddx / dl * C2.ARC_WEIGHT_OFFSET;
|
|
12049
|
+
return { arc: a, type: a.type, weight: a.weight, points, reversed, labelX, labelY };
|
|
12050
|
+
});
|
|
12051
|
+
const hasIncoming = (pid2) => ast.arcs.some((a) => a.to === pid2);
|
|
12052
|
+
const hasOutgoing = (pid2) => ast.arcs.some((a) => a.from === pid2);
|
|
12053
|
+
const placeBoxes = ast.places.map((p) => {
|
|
12054
|
+
const g = geom.get(p.id);
|
|
12055
|
+
return {
|
|
12056
|
+
place: p,
|
|
12057
|
+
cx: g.cx,
|
|
12058
|
+
cy: g.cy,
|
|
12059
|
+
r: g.r,
|
|
12060
|
+
tokens: marking.get(p.id) ?? 0,
|
|
12061
|
+
isSource: !hasIncoming(p.id),
|
|
12062
|
+
isSink: !hasOutgoing(p.id)
|
|
12063
|
+
};
|
|
12064
|
+
});
|
|
12065
|
+
const transBoxes = ast.transitions.map((t) => {
|
|
12066
|
+
const g = geom.get(t.id);
|
|
12067
|
+
return {
|
|
12068
|
+
transition: t,
|
|
12069
|
+
cx: g.cx,
|
|
12070
|
+
cy: g.cy,
|
|
12071
|
+
w: g.halfW * 2,
|
|
12072
|
+
h: g.halfH * 2,
|
|
12073
|
+
enabled: enabledIds.includes(t.id),
|
|
12074
|
+
dead: deadIds.has(t.id)
|
|
12075
|
+
};
|
|
12076
|
+
});
|
|
12077
|
+
const bb = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity };
|
|
12078
|
+
const addBox = (x0, y0, x1, y1) => {
|
|
12079
|
+
bb.minX = Math.min(bb.minX, x0, x1);
|
|
12080
|
+
bb.minY = Math.min(bb.minY, y0, y1);
|
|
12081
|
+
bb.maxX = Math.max(bb.maxX, x0, x1);
|
|
12082
|
+
bb.maxY = Math.max(bb.maxY, y0, y1);
|
|
12083
|
+
};
|
|
12084
|
+
const addPt = (p) => addBox(p.x, p.y, p.x, p.y);
|
|
12085
|
+
const labelW = (s) => s ? s.length * C2.CHAR_W : 0;
|
|
12086
|
+
for (const pb of placeBoxes) {
|
|
12087
|
+
addBox(pb.cx - pb.r, pb.cy - pb.r, pb.cx + pb.r, pb.cy + pb.r);
|
|
12088
|
+
const lw = Math.max(labelW(pb.place.id), labelW(pb.place.label)) / 2;
|
|
12089
|
+
addBox(pb.cx - lw, pb.cy - pb.r - C2.LABEL_LINE_H * 2, pb.cx + lw, pb.cy);
|
|
12090
|
+
if (pb.place.capacity !== void 0) addBox(pb.cx, pb.cy + pb.r, pb.cx + 24, pb.cy + pb.r + C2.LABEL_LINE_H);
|
|
12091
|
+
}
|
|
12092
|
+
for (const tb of transBoxes) {
|
|
12093
|
+
addBox(tb.cx - tb.w / 2, tb.cy - tb.h / 2, tb.cx + tb.w / 2, tb.cy + tb.h / 2);
|
|
12094
|
+
const lw = Math.max(labelW(tb.transition.id), labelW(tb.transition.label)) / 2;
|
|
12095
|
+
addBox(tb.cx - lw, tb.cy - tb.h / 2 - C2.LABEL_LINE_H * 2, tb.cx + lw, tb.cy);
|
|
12096
|
+
}
|
|
12097
|
+
for (const ag of arcGeoms) {
|
|
12098
|
+
ag.points.forEach(addPt);
|
|
12099
|
+
if (ag.weight > 1) addBox(ag.labelX - 6, ag.labelY - 8, ag.labelX + 6, ag.labelY + 4);
|
|
12100
|
+
}
|
|
12101
|
+
const dx = C2.MARGIN - bb.minX;
|
|
12102
|
+
const dy = C2.MARGIN - bb.minY;
|
|
12103
|
+
const shift = (p) => ({ x: p.x + dx, y: p.y + dy });
|
|
12104
|
+
placeBoxes.forEach((pb) => {
|
|
12105
|
+
pb.cx += dx;
|
|
12106
|
+
pb.cy += dy;
|
|
12107
|
+
});
|
|
12108
|
+
transBoxes.forEach((tb) => {
|
|
12109
|
+
tb.cx += dx;
|
|
12110
|
+
tb.cy += dy;
|
|
12111
|
+
});
|
|
12112
|
+
arcGeoms.forEach((ag) => {
|
|
12113
|
+
ag.points = ag.points.map(shift);
|
|
12114
|
+
ag.labelX += dx;
|
|
12115
|
+
ag.labelY += dy;
|
|
12116
|
+
});
|
|
12117
|
+
const width = bb.maxX - bb.minX + 2 * C2.MARGIN;
|
|
12118
|
+
const height = bb.maxY - bb.minY + 2 * C2.MARGIN;
|
|
12119
|
+
const subclass = detectSubclass(ast);
|
|
12120
|
+
return {
|
|
12121
|
+
width: Math.round(width),
|
|
12122
|
+
height: Math.round(height),
|
|
12123
|
+
title: ast.title,
|
|
12124
|
+
direction: dir,
|
|
12125
|
+
places: placeBoxes,
|
|
12126
|
+
transitions: transBoxes,
|
|
12127
|
+
arcs: arcGeoms,
|
|
12128
|
+
subclass,
|
|
12129
|
+
enabledIds,
|
|
12130
|
+
warnings,
|
|
12131
|
+
ast
|
|
12132
|
+
};
|
|
12133
|
+
}
|
|
12134
|
+
function detectSubclass(ast) {
|
|
12135
|
+
if (!ast.transitions.length || !ast.places.length) return void 0;
|
|
12136
|
+
const inT = (tid) => ast.arcs.filter((a) => a.to === tid).length;
|
|
12137
|
+
const outT = (tid) => ast.arcs.filter((a) => a.from === tid).length;
|
|
12138
|
+
const inP = (pid2) => ast.arcs.filter((a) => a.to === pid2).length;
|
|
12139
|
+
const outP = (pid2) => ast.arcs.filter((a) => a.from === pid2).length;
|
|
12140
|
+
const stateMachine = ast.transitions.every((t) => inT(t.id) === 1 && outT(t.id) === 1);
|
|
12141
|
+
const markedGraph = ast.places.every((p) => inP(p.id) === 1 && outP(p.id) === 1);
|
|
12142
|
+
const sources = ast.places.filter((p) => inP(p.id) === 0);
|
|
12143
|
+
const sinks = ast.places.filter((p) => outP(p.id) === 0);
|
|
12144
|
+
const workflowNet = sources.length === 1 && sinks.length === 1;
|
|
12145
|
+
const tags = [];
|
|
12146
|
+
if (stateMachine) tags.push("state machine");
|
|
12147
|
+
if (markedGraph) tags.push("marked graph");
|
|
12148
|
+
if (workflowNet) tags.push("workflow net");
|
|
12149
|
+
return tags.length ? tags.join(", ") : void 0;
|
|
12150
|
+
}
|
|
12151
|
+
|
|
12152
|
+
// src/diagrams/petri/renderer.ts
|
|
12153
|
+
function buildCss6(t) {
|
|
12154
|
+
return `
|
|
12155
|
+
.sx-petri { font-family: system-ui, -apple-system, sans-serif; }
|
|
12156
|
+
.sx-petri-place { fill: ${t.placeFill}; stroke: ${t.placeStroke}; stroke-width: 2; }
|
|
12157
|
+
.sx-petri-place-cap { stroke-dasharray: 4 3; }
|
|
12158
|
+
.sx-petri-token { fill: ${t.tokenFill}; stroke: none; }
|
|
12159
|
+
.sx-petri-token-num { font: 600 11px sans-serif; fill: ${t.tokenFill}; }
|
|
12160
|
+
.sx-petri-bar { fill: ${t.transitionBarFill}; stroke: ${t.transitionStroke}; stroke-width: 2; }
|
|
12161
|
+
.sx-petri-box { fill: ${t.transitionBoxFill}; stroke: ${t.transitionStroke}; stroke-width: 2; }
|
|
12162
|
+
.sx-petri-enabled { fill: ${t.enabledFill}; stroke: ${t.enabledStroke}; stroke-width: 3; }
|
|
12163
|
+
.sx-petri-dead .sx-petri-bar, .sx-petri-dead .sx-petri-box { stroke: ${t.deadStroke}; }
|
|
12164
|
+
.sx-petri-dead { opacity: 0.7; }
|
|
12165
|
+
.sx-petri-label { font: 12px sans-serif; fill: ${t.text}; }
|
|
12166
|
+
.sx-petri-sublabel { font: italic 10px sans-serif; fill: ${t.textMuted}; }
|
|
12167
|
+
.sx-petri-cap { font: 9px sans-serif; fill: ${t.textMuted}; }
|
|
12168
|
+
.sx-petri-rate { font: italic 9px sans-serif; fill: ${t.textMuted}; }
|
|
12169
|
+
.sx-petri-arc { stroke: ${t.arcStroke}; stroke-width: 2; fill: none; }
|
|
12170
|
+
.sx-petri-arc-inhibitor, .sx-petri-arc-reset { stroke: ${t.inhibitorStroke}; }
|
|
12171
|
+
.sx-petri-inhibitor-dot { fill: ${t.placeFill}; stroke: ${t.inhibitorStroke}; stroke-width: 2; }
|
|
12172
|
+
.sx-petri-weight { font: 600 9px sans-serif; fill: ${t.weightLabel}; }
|
|
12173
|
+
.sx-petri-title { font: 700 16px sans-serif; fill: ${t.text}; }
|
|
12174
|
+
`.trim();
|
|
12175
|
+
}
|
|
12176
|
+
function markers4(t) {
|
|
12177
|
+
return defs([
|
|
12178
|
+
el(
|
|
12179
|
+
"marker",
|
|
12180
|
+
{ id: "sx-petri-head", viewBox: "0 0 10 10", refX: 9, refY: 5, markerWidth: 8, markerHeight: 8, orient: "auto-start-reverse" },
|
|
12181
|
+
[polygon({ points: "0,0 9,5 0,10", fill: t.arcStroke })]
|
|
12182
|
+
),
|
|
12183
|
+
el(
|
|
12184
|
+
"marker",
|
|
12185
|
+
{ id: "sx-petri-head-reset", viewBox: "0 0 16 10", refX: 15, refY: 5, markerWidth: 13, markerHeight: 9, orient: "auto-start-reverse" },
|
|
12186
|
+
[
|
|
12187
|
+
polygon({ points: "0,0 7,5 0,10", fill: t.inhibitorStroke }),
|
|
12188
|
+
polygon({ points: "7,0 14,5 7,10", fill: t.inhibitorStroke })
|
|
12189
|
+
]
|
|
12190
|
+
)
|
|
12191
|
+
]);
|
|
12192
|
+
}
|
|
12193
|
+
function tokenPositions(n) {
|
|
12194
|
+
const d = PETRI_CONST.TOKEN_R + 2.2;
|
|
12195
|
+
switch (n) {
|
|
12196
|
+
case 1:
|
|
12197
|
+
return [{ x: 0, y: 0 }];
|
|
12198
|
+
case 2:
|
|
12199
|
+
return [
|
|
12200
|
+
{ x: -d, y: 0 },
|
|
12201
|
+
{ x: d, y: 0 }
|
|
12202
|
+
];
|
|
12203
|
+
case 3:
|
|
12204
|
+
return [
|
|
12205
|
+
{ x: 0, y: -d },
|
|
12206
|
+
{ x: -d, y: d },
|
|
12207
|
+
{ x: d, y: d }
|
|
12208
|
+
];
|
|
12209
|
+
default:
|
|
12210
|
+
return [
|
|
12211
|
+
{ x: -d, y: -d },
|
|
12212
|
+
{ x: d, y: -d },
|
|
12213
|
+
{ x: -d, y: d },
|
|
12214
|
+
{ x: d, y: d }
|
|
12215
|
+
];
|
|
12216
|
+
}
|
|
12217
|
+
}
|
|
12218
|
+
function renderTokens(pb, style) {
|
|
12219
|
+
const n = pb.tokens;
|
|
12220
|
+
if (n <= 0) return "";
|
|
12221
|
+
const asDots = style !== "count" && n <= PETRI_CONST.TOKEN_COUNT_MAX_DOTS;
|
|
12222
|
+
if (asDots) {
|
|
12223
|
+
return tokenPositions(n).map((p) => circle({ class: "sx-petri-token", cx: pb.cx + p.x, cy: pb.cy + p.y, r: PETRI_CONST.TOKEN_R })).join("");
|
|
12224
|
+
}
|
|
12225
|
+
return text(
|
|
12226
|
+
{ class: "sx-petri-token-num", x: pb.cx, y: pb.cy + 4, "text-anchor": "middle" },
|
|
12227
|
+
String(n)
|
|
12228
|
+
);
|
|
12229
|
+
}
|
|
12230
|
+
function renderPlace(pb, style) {
|
|
12231
|
+
const cls = pb.place.capacity !== void 0 ? "sx-petri-place sx-petri-place-cap" : "sx-petri-place";
|
|
12232
|
+
const parts = [circle({ class: cls, cx: pb.cx, cy: pb.cy, r: pb.r })];
|
|
12233
|
+
parts.push(renderTokens(pb, style));
|
|
12234
|
+
const labelY = pb.cy - pb.r - PETRI_CONST.LABEL_GAP;
|
|
12235
|
+
parts.push(text({ class: "sx-petri-label", x: pb.cx, y: labelY, "text-anchor": "middle" }, pb.place.id));
|
|
12236
|
+
if (pb.place.label) {
|
|
12237
|
+
parts.push(
|
|
12238
|
+
text({ class: "sx-petri-sublabel", x: pb.cx, y: labelY - PETRI_CONST.LABEL_LINE_H, "text-anchor": "middle" }, pb.place.label)
|
|
12239
|
+
);
|
|
12240
|
+
}
|
|
12241
|
+
if (pb.place.capacity !== void 0) {
|
|
12242
|
+
parts.push(
|
|
12243
|
+
text({ class: "sx-petri-cap", x: pb.cx + pb.r + 3, y: pb.cy + pb.r + 9 }, `K=${pb.place.capacity}`)
|
|
12244
|
+
);
|
|
12245
|
+
}
|
|
12246
|
+
const attrs = {
|
|
12247
|
+
class: "sx-petri-place-g",
|
|
12248
|
+
"data-id": pb.place.id,
|
|
12249
|
+
"data-tokens": pb.tokens
|
|
12250
|
+
};
|
|
12251
|
+
if (pb.place.capacity !== void 0) attrs["data-capacity"] = pb.place.capacity;
|
|
12252
|
+
if (pb.isSource) attrs["data-source"] = "true";
|
|
12253
|
+
if (pb.isSink) attrs["data-sink"] = "true";
|
|
12254
|
+
return group(attrs, parts);
|
|
12255
|
+
}
|
|
12256
|
+
function renderTransition(tb) {
|
|
12257
|
+
const x = tb.cx - tb.w / 2;
|
|
12258
|
+
const y = tb.cy - tb.h / 2;
|
|
12259
|
+
const parts = [];
|
|
12260
|
+
if (tb.enabled) {
|
|
12261
|
+
parts.push(
|
|
12262
|
+
rect({ class: "sx-petri-enabled", x: x - 4, y: y - 4, width: tb.w + 8, height: tb.h + 8, rx: 4, ry: 4 })
|
|
12263
|
+
);
|
|
12264
|
+
}
|
|
12265
|
+
const shapeClass = tb.transition.kind === "timed" ? "sx-petri-box" : "sx-petri-bar";
|
|
12266
|
+
parts.push(rect({ class: shapeClass, x, y, width: tb.w, height: tb.h, rx: 1, ry: 1 }));
|
|
12267
|
+
const labelY = y - PETRI_CONST.LABEL_GAP;
|
|
12268
|
+
parts.push(text({ class: "sx-petri-label", x: tb.cx, y: labelY, "text-anchor": "middle" }, tb.transition.id));
|
|
12269
|
+
if (tb.transition.label) {
|
|
12270
|
+
parts.push(
|
|
12271
|
+
text({ class: "sx-petri-sublabel", x: tb.cx, y: labelY - PETRI_CONST.LABEL_LINE_H, "text-anchor": "middle" }, tb.transition.label)
|
|
12272
|
+
);
|
|
12273
|
+
}
|
|
12274
|
+
if (tb.transition.kind === "timed" && tb.transition.rate !== void 0) {
|
|
12275
|
+
parts.push(text({ class: "sx-petri-rate", x: tb.cx, y: y + tb.h + 10, "text-anchor": "middle" }, `\u03BB=${tb.transition.rate}`));
|
|
12276
|
+
}
|
|
12277
|
+
if (tb.transition.guard) {
|
|
12278
|
+
parts.push(text({ class: "sx-petri-rate", x: tb.cx + tb.w / 2 + 4, y: tb.cy + 3 }, `[${tb.transition.guard}]`));
|
|
12279
|
+
}
|
|
12280
|
+
return group(
|
|
12281
|
+
{
|
|
12282
|
+
class: tb.dead ? "sx-petri-trans-g sx-petri-dead" : "sx-petri-trans-g",
|
|
12283
|
+
"data-id": tb.transition.id,
|
|
12284
|
+
"data-kind": tb.transition.kind,
|
|
12285
|
+
"data-enabled": tb.enabled ? "true" : "false"
|
|
12286
|
+
},
|
|
12287
|
+
parts
|
|
12288
|
+
);
|
|
12289
|
+
}
|
|
12290
|
+
function arcPath(ag) {
|
|
12291
|
+
const p = ag.points;
|
|
12292
|
+
if (p.length === 4) {
|
|
12293
|
+
return `M ${p[0].x} ${p[0].y} C ${p[1].x} ${p[1].y} ${p[2].x} ${p[2].y} ${p[3].x} ${p[3].y}`;
|
|
12294
|
+
}
|
|
12295
|
+
return `M ${p[0].x} ${p[0].y} L ${p[p.length - 1].x} ${p[p.length - 1].y}`;
|
|
12296
|
+
}
|
|
12297
|
+
function renderArc(ag) {
|
|
12298
|
+
const parts = [];
|
|
12299
|
+
let cls = "sx-petri-arc";
|
|
12300
|
+
let markerEnd;
|
|
12301
|
+
if (ag.type === "standard") {
|
|
12302
|
+
markerEnd = "url(#sx-petri-head)";
|
|
12303
|
+
} else if (ag.type === "reset") {
|
|
12304
|
+
cls += " sx-petri-arc-reset";
|
|
12305
|
+
markerEnd = "url(#sx-petri-head-reset)";
|
|
12306
|
+
} else if (ag.type === "inhibitor") {
|
|
12307
|
+
cls += " sx-petri-arc-inhibitor";
|
|
12308
|
+
}
|
|
12309
|
+
parts.push(path({ class: cls, d: arcPath(ag), ...markerEnd ? { "marker-end": markerEnd } : {} }));
|
|
12310
|
+
if (ag.type === "inhibitor") {
|
|
12311
|
+
const pts = ag.points;
|
|
12312
|
+
const last = pts[pts.length - 1];
|
|
12313
|
+
const prev = pts[pts.length - 2];
|
|
12314
|
+
const dx = last.x - prev.x;
|
|
12315
|
+
const dy = last.y - prev.y;
|
|
12316
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
12317
|
+
const r = 4;
|
|
12318
|
+
const cx = last.x - dx / len * r;
|
|
12319
|
+
const cy = last.y - dy / len * r;
|
|
12320
|
+
parts.push(circle({ class: "sx-petri-inhibitor-dot", cx, cy, r }));
|
|
12321
|
+
}
|
|
12322
|
+
if (ag.weight > 1) {
|
|
12323
|
+
parts.push(
|
|
12324
|
+
text({ class: "sx-petri-weight", x: ag.labelX, y: ag.labelY + 3, "text-anchor": "middle" }, String(ag.weight))
|
|
12325
|
+
);
|
|
12326
|
+
}
|
|
12327
|
+
return group(
|
|
12328
|
+
{ class: "sx-petri-arc-g", "data-from": ag.arc.from, "data-to": ag.arc.to, "data-type": ag.type, "data-weight": ag.weight },
|
|
12329
|
+
parts
|
|
12330
|
+
);
|
|
12331
|
+
}
|
|
12332
|
+
function renderPetriLayout(layout, config) {
|
|
12333
|
+
const t = resolvePetriTheme(config?.theme ?? "default");
|
|
12334
|
+
const children = [];
|
|
12335
|
+
const marking = layout.places.filter((p) => p.tokens > 0).map((p) => `${p.place.id}:${p.tokens}`).join(", ");
|
|
12336
|
+
const descParts = [
|
|
12337
|
+
`${layout.places.length} places, ${layout.transitions.length} transitions, ${layout.arcs.length} arcs.`,
|
|
12338
|
+
marking ? `Marking {${marking}}.` : "Empty marking.",
|
|
12339
|
+
layout.enabledIds.length ? `Enabled: ${layout.enabledIds.join(", ")}.` : "No enabled transitions.",
|
|
12340
|
+
layout.subclass ? `Class: ${layout.subclass}.` : ""
|
|
12341
|
+
].filter(Boolean);
|
|
12342
|
+
children.push(title(`Petri net${layout.title ? " \u2014 " + layout.title : ""}`));
|
|
12343
|
+
children.push(desc(descParts.join(" ")));
|
|
12344
|
+
children.push(el("style", {}, buildCss6(t)));
|
|
12345
|
+
children.push(markers4(t));
|
|
12346
|
+
const titleBand = layout.title ? 32 : 0;
|
|
12347
|
+
if (layout.title) {
|
|
12348
|
+
children.push(text({ x: layout.width / 2, y: 22, class: "sx-petri-title", "text-anchor": "middle" }, layout.title));
|
|
12349
|
+
}
|
|
12350
|
+
const body = [];
|
|
12351
|
+
body.push(group({ class: "sx-petri-arcs" }, layout.arcs.map(renderArc)));
|
|
12352
|
+
body.push(group({ class: "sx-petri-places" }, layout.places.map((p) => renderPlace(p, layout.ast.tokenStyle))));
|
|
12353
|
+
body.push(group({ class: "sx-petri-transitions" }, layout.transitions.map(renderTransition)));
|
|
12354
|
+
children.push(titleBand ? group({ transform: `translate(0, ${titleBand})` }, body) : group({}, body));
|
|
12355
|
+
const height = layout.height + titleBand;
|
|
12356
|
+
return svgRoot(
|
|
12357
|
+
{
|
|
12358
|
+
class: "sx-petri",
|
|
12359
|
+
role: "img",
|
|
12360
|
+
"aria-label": escapeXml(layout.title ?? "Petri net"),
|
|
12361
|
+
width: layout.width,
|
|
12362
|
+
height,
|
|
12363
|
+
viewBox: `0 0 ${layout.width} ${height}`,
|
|
12364
|
+
"data-diagram-type": "petri"
|
|
12365
|
+
},
|
|
12366
|
+
children
|
|
12367
|
+
);
|
|
12368
|
+
}
|
|
12369
|
+
function renderPetri(textOrAst, config) {
|
|
12370
|
+
const ast = typeof textOrAst === "string" ? parsePetri(textOrAst) : textOrAst;
|
|
12371
|
+
const layout = layoutPetri(ast);
|
|
12372
|
+
return renderPetriLayout(layout, config);
|
|
12373
|
+
}
|
|
12374
|
+
|
|
12375
|
+
// src/diagrams/petri/index.ts
|
|
12376
|
+
var petri = {
|
|
12377
|
+
type: "petri",
|
|
12378
|
+
detect(text2) {
|
|
12379
|
+
for (const raw of text2.split(/\r?\n/)) {
|
|
12380
|
+
const t = raw.trim();
|
|
12381
|
+
if (!t) continue;
|
|
12382
|
+
if (t.startsWith("#") || t.startsWith("//")) continue;
|
|
12383
|
+
return /^petri(net)?\b/i.test(t);
|
|
12384
|
+
}
|
|
12385
|
+
return false;
|
|
12386
|
+
},
|
|
12387
|
+
parse: parsePetri,
|
|
12388
|
+
render(text2, config) {
|
|
12389
|
+
return renderPetri(text2, config);
|
|
12390
|
+
}
|
|
12391
|
+
};
|
|
12392
|
+
|
|
12393
|
+
// src/diagrams/network/parser.ts
|
|
12394
|
+
var NetworkParseError = class extends Error {
|
|
12395
|
+
name = "NetworkParseError";
|
|
12396
|
+
};
|
|
12397
|
+
var DEVICE_KINDS = /* @__PURE__ */ new Set([
|
|
12398
|
+
"router",
|
|
12399
|
+
"switch",
|
|
12400
|
+
"l3switch",
|
|
12401
|
+
"firewall",
|
|
12402
|
+
"loadbalancer",
|
|
12403
|
+
"ap",
|
|
12404
|
+
"wlc",
|
|
12405
|
+
"gateway",
|
|
12406
|
+
"modem",
|
|
12407
|
+
"ids",
|
|
12408
|
+
"proxy",
|
|
12409
|
+
"vpngw",
|
|
12410
|
+
"server",
|
|
12411
|
+
"serverfarm",
|
|
12412
|
+
"pc",
|
|
12413
|
+
"laptop",
|
|
12414
|
+
"mobile",
|
|
12415
|
+
"ipphone",
|
|
12416
|
+
"printer",
|
|
12417
|
+
"storage",
|
|
12418
|
+
"camera",
|
|
12419
|
+
"nvr",
|
|
12420
|
+
"dvr",
|
|
12421
|
+
"poeswitch",
|
|
12422
|
+
"encoder",
|
|
12423
|
+
"monitor",
|
|
12424
|
+
"internet",
|
|
12425
|
+
"wan",
|
|
12426
|
+
"cloud",
|
|
12427
|
+
"pstn",
|
|
12428
|
+
"lan"
|
|
12429
|
+
]);
|
|
12430
|
+
var KIND_ALIASES = {
|
|
12431
|
+
multilayer: "l3switch",
|
|
12432
|
+
wifi: "ap",
|
|
12433
|
+
workstation: "pc",
|
|
12434
|
+
phone: "mobile",
|
|
12435
|
+
voip: "ipphone",
|
|
12436
|
+
nas: "storage",
|
|
12437
|
+
san: "storage",
|
|
12438
|
+
servers: "serverfarm",
|
|
12439
|
+
ips: "ids",
|
|
12440
|
+
decoder: "encoder",
|
|
12441
|
+
videowall: "monitor",
|
|
12442
|
+
segment: "lan"
|
|
12443
|
+
};
|
|
12444
|
+
var GROUP_KINDS = /* @__PURE__ */ new Set(["site", "building", "campus", "rack", "subnet", "vlan", "zone", "dmz"]);
|
|
12445
|
+
var GROUP_KIND_ALIAS = { building: "site", campus: "site" };
|
|
12446
|
+
var TIERS = /* @__PURE__ */ new Set(["edge", "core", "distribution", "access"]);
|
|
12447
|
+
var CAMERA_TYPES = /* @__PURE__ */ new Set(["fixed", "bullet", "dome", "ptz", "turret"]);
|
|
12448
|
+
var LAYOUT_MODES = /* @__PURE__ */ new Set([
|
|
12449
|
+
"tiered",
|
|
12450
|
+
"tree",
|
|
12451
|
+
"star",
|
|
12452
|
+
"ring",
|
|
12453
|
+
"bus",
|
|
12454
|
+
"mesh",
|
|
12455
|
+
"spine-leaf",
|
|
12456
|
+
"manual"
|
|
12457
|
+
]);
|
|
12458
|
+
var LINK_TYPE_KEYWORDS = {
|
|
12459
|
+
copper: "copper",
|
|
12460
|
+
ethernet: "copper",
|
|
12461
|
+
fiber: "fiber",
|
|
12462
|
+
fibre: "fiber",
|
|
12463
|
+
wireless: "wireless",
|
|
12464
|
+
wifi: "wireless",
|
|
12465
|
+
serial: "serial",
|
|
12466
|
+
wan: "serial",
|
|
12467
|
+
poe: "poe",
|
|
12468
|
+
vpn: "vpn",
|
|
12469
|
+
lag: "lag",
|
|
12470
|
+
portchannel: "lag"
|
|
12471
|
+
};
|
|
12472
|
+
var CONNECTORS = /* @__PURE__ */ new Set(["--", "->", "=="]);
|
|
12473
|
+
var SPEED_RE = /^\d+(?:\.\d+)?[MGT]$/;
|
|
12474
|
+
var QUOTE_RE = /"([^"]*)"|「([^」]*)」|『([^』]*)』|“([^”]*)”|«([^»]*)»|'([^']*)'/g;
|
|
12475
|
+
function stripComment2(line2) {
|
|
12476
|
+
let inQuote = false;
|
|
12477
|
+
let close = "";
|
|
12478
|
+
const pairs = { '"': '"', "\u300C": "\u300D", "\u300E": "\u300F", "\u201C": "\u201D", "\xAB": "\xBB", "'": "'" };
|
|
12479
|
+
for (let i = 0; i < line2.length; i++) {
|
|
12480
|
+
const ch = line2[i];
|
|
12481
|
+
if (inQuote) {
|
|
12482
|
+
if (ch === close) inQuote = false;
|
|
12483
|
+
continue;
|
|
12484
|
+
}
|
|
12485
|
+
if (pairs[ch]) {
|
|
12486
|
+
inQuote = true;
|
|
12487
|
+
close = pairs[ch];
|
|
12488
|
+
continue;
|
|
12489
|
+
}
|
|
12490
|
+
if (ch === "#") return line2.slice(0, i);
|
|
12491
|
+
if (ch === "/" && line2[i + 1] === "/") return line2.slice(0, i);
|
|
12492
|
+
}
|
|
12493
|
+
return line2;
|
|
12494
|
+
}
|
|
12495
|
+
function splitStatements(line2) {
|
|
12496
|
+
const out = [];
|
|
12497
|
+
let buf = "";
|
|
12498
|
+
let inQuote = false;
|
|
12499
|
+
let close = "";
|
|
12500
|
+
const pairs = { '"': '"', "\u300C": "\u300D", "\u300E": "\u300F", "\u201C": "\u201D", "\xAB": "\xBB", "'": "'" };
|
|
12501
|
+
for (let i = 0; i < line2.length; i++) {
|
|
12502
|
+
const ch = line2[i];
|
|
12503
|
+
if (inQuote) {
|
|
12504
|
+
if (ch === close) inQuote = false;
|
|
12505
|
+
buf += ch;
|
|
12506
|
+
continue;
|
|
12507
|
+
}
|
|
12508
|
+
if (pairs[ch]) {
|
|
12509
|
+
inQuote = true;
|
|
12510
|
+
close = pairs[ch];
|
|
12511
|
+
buf += ch;
|
|
12512
|
+
continue;
|
|
12513
|
+
}
|
|
12514
|
+
if (ch === ";") {
|
|
12515
|
+
out.push(buf);
|
|
12516
|
+
buf = "";
|
|
12517
|
+
continue;
|
|
12518
|
+
}
|
|
12519
|
+
buf += ch;
|
|
12520
|
+
}
|
|
12521
|
+
out.push(buf);
|
|
12522
|
+
return out;
|
|
12523
|
+
}
|
|
12524
|
+
function tokenize4(raw) {
|
|
12525
|
+
const line2 = stripComment2(raw);
|
|
12526
|
+
const strings = [];
|
|
12527
|
+
const masked = line2.replace(QUOTE_RE, (...m) => {
|
|
12528
|
+
const inner = m.slice(1, 7).find((g) => g !== void 0) ?? "";
|
|
12529
|
+
strings.push(inner);
|
|
12530
|
+
return ` @@${strings.length - 1}@@ `;
|
|
12531
|
+
});
|
|
12532
|
+
const spaced = masked.replace(/(--|->|==)/g, " $1 ");
|
|
12533
|
+
const out = [];
|
|
12534
|
+
for (const w of spaced.split(/\s+/)) {
|
|
12535
|
+
if (!w) continue;
|
|
12536
|
+
const sm = /^@@(\d+)@@$/.exec(w);
|
|
12537
|
+
if (sm) {
|
|
12538
|
+
out.push({ value: strings[Number(sm[1])] ?? "", str: true });
|
|
12539
|
+
} else {
|
|
12540
|
+
out.push({ value: w, str: false });
|
|
12541
|
+
}
|
|
12542
|
+
}
|
|
12543
|
+
return out;
|
|
12544
|
+
}
|
|
12545
|
+
function levenshtein(a, b) {
|
|
12546
|
+
const m = a.length, n = b.length;
|
|
12547
|
+
const d = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
|
|
12548
|
+
for (let j = 0; j <= n; j++) d[0][j] = j;
|
|
12549
|
+
for (let i = 1; i <= m; i++) {
|
|
12550
|
+
for (let j = 1; j <= n; j++) {
|
|
12551
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
12552
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
|
12553
|
+
}
|
|
12554
|
+
}
|
|
12555
|
+
return d[m][n];
|
|
12556
|
+
}
|
|
12557
|
+
function nearestKind(bad) {
|
|
12558
|
+
let best;
|
|
12559
|
+
let bestDist = Infinity;
|
|
12560
|
+
for (const k of [...DEVICE_KINDS, ...Object.keys(KIND_ALIASES)]) {
|
|
12561
|
+
const dist = levenshtein(bad.toLowerCase(), k);
|
|
12562
|
+
if (dist < bestDist) {
|
|
12563
|
+
bestDist = dist;
|
|
12564
|
+
best = k;
|
|
12565
|
+
}
|
|
12566
|
+
}
|
|
12567
|
+
return bestDist <= 3 ? best : void 0;
|
|
12568
|
+
}
|
|
12569
|
+
function ipToInt(ip) {
|
|
12570
|
+
const parts = ip.split(".");
|
|
12571
|
+
if (parts.length !== 4) return null;
|
|
12572
|
+
let v = 0;
|
|
12573
|
+
for (const p of parts) {
|
|
12574
|
+
const n = Number(p);
|
|
12575
|
+
if (!Number.isInteger(n) || n < 0 || n > 255) return null;
|
|
12576
|
+
v = v * 256 + n;
|
|
12577
|
+
}
|
|
12578
|
+
return v >>> 0;
|
|
12579
|
+
}
|
|
12580
|
+
function ipInCidr(ip, cidr) {
|
|
12581
|
+
const m = /^(\d+\.\d+\.\d+\.\d+)\s*\/\s*(\d+)$/.exec(cidr.trim());
|
|
12582
|
+
if (!m) return null;
|
|
12583
|
+
const netInt = ipToInt(m[1]);
|
|
12584
|
+
const prefix = Number(m[2]);
|
|
12585
|
+
const hostInt = ipToInt(ip.split("/")[0]);
|
|
12586
|
+
if (netInt === null || hostInt === null || prefix < 0 || prefix > 32) return null;
|
|
12587
|
+
const mask = prefix === 0 ? 0 : 4294967295 << 32 - prefix >>> 0;
|
|
12588
|
+
return (netInt & mask) === (hostInt & mask);
|
|
12589
|
+
}
|
|
12590
|
+
var SWITCH_CLASS = /* @__PURE__ */ new Set(["switch", "l3switch", "poeswitch"]);
|
|
12591
|
+
function parseNetwork(text2) {
|
|
12592
|
+
const ast = {
|
|
12593
|
+
type: "network",
|
|
12594
|
+
layout: "tiered",
|
|
12595
|
+
direction: "tb",
|
|
12596
|
+
spines: [],
|
|
12597
|
+
leaves: [],
|
|
12598
|
+
devices: [],
|
|
12599
|
+
links: [],
|
|
12600
|
+
groups: [],
|
|
12601
|
+
warnings: []
|
|
12602
|
+
};
|
|
12603
|
+
const deviceById = /* @__PURE__ */ new Map();
|
|
12604
|
+
const groupById = /* @__PURE__ */ new Map();
|
|
12605
|
+
const groupStack = [];
|
|
12606
|
+
let headerSeen = false;
|
|
12607
|
+
const rawLines = text2.split(/\r?\n/);
|
|
12608
|
+
const addDevice = (d, lineNo) => {
|
|
12609
|
+
if (deviceById.has(d.id)) {
|
|
12610
|
+
throw new NetworkParseError(`device id "${d.id}" already declared (line ${lineNo})`);
|
|
12611
|
+
}
|
|
12612
|
+
deviceById.set(d.id, d);
|
|
12613
|
+
ast.devices.push(d);
|
|
12614
|
+
const top = groupStack[groupStack.length - 1];
|
|
12615
|
+
if (top && !top.members.includes(d.id)) top.members.push(d.id);
|
|
12616
|
+
};
|
|
12617
|
+
const ensureDevice = (id, kind) => {
|
|
12618
|
+
let d = deviceById.get(id);
|
|
12619
|
+
if (!d) {
|
|
12620
|
+
d = { id, kind, groups: [] };
|
|
12621
|
+
deviceById.set(id, d);
|
|
12622
|
+
ast.devices.push(d);
|
|
12623
|
+
}
|
|
12624
|
+
return d;
|
|
12625
|
+
};
|
|
12626
|
+
const applyAttrs = (d, toks, start, lineNo) => {
|
|
12627
|
+
let i = start;
|
|
12628
|
+
while (i < toks.length) {
|
|
12629
|
+
const tk = toks[i];
|
|
12630
|
+
if (tk.str) {
|
|
12631
|
+
i++;
|
|
12632
|
+
continue;
|
|
12633
|
+
}
|
|
12634
|
+
const key = tk.value.endsWith(":") ? tk.value.slice(0, -1) : null;
|
|
12635
|
+
if (key === null) {
|
|
12636
|
+
i++;
|
|
12637
|
+
continue;
|
|
12638
|
+
}
|
|
12639
|
+
const val = toks[i + 1];
|
|
12640
|
+
i += 2;
|
|
12641
|
+
if (!val) continue;
|
|
12642
|
+
switch (key) {
|
|
12643
|
+
case "tier":
|
|
12644
|
+
if (TIERS.has(val.value)) d.tier = val.value;
|
|
12645
|
+
break;
|
|
12646
|
+
case "type":
|
|
12647
|
+
if (CAMERA_TYPES.has(val.value)) d.cameraType = val.value;
|
|
12648
|
+
break;
|
|
12649
|
+
case "ip":
|
|
12650
|
+
d.ip = val.value;
|
|
12651
|
+
break;
|
|
12652
|
+
case "model":
|
|
12653
|
+
d.model = val.value;
|
|
12654
|
+
break;
|
|
12655
|
+
case "count": {
|
|
12656
|
+
const n = Number(val.value);
|
|
12657
|
+
if (Number.isFinite(n)) d.count = n;
|
|
12658
|
+
break;
|
|
12659
|
+
}
|
|
12660
|
+
case "icon":
|
|
12661
|
+
d.icon = val.value;
|
|
12662
|
+
break;
|
|
12663
|
+
case "at": {
|
|
12664
|
+
const mm = /^(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)$/.exec(val.value);
|
|
12665
|
+
if (mm) d.at = { x: Number(mm[1]), y: Number(mm[2]) };
|
|
12666
|
+
break;
|
|
12667
|
+
}
|
|
12668
|
+
default:
|
|
12669
|
+
ast.warnings.push(`unknown attribute "${key}:" on device "${d.id}" (line ${lineNo})`);
|
|
12670
|
+
}
|
|
12671
|
+
}
|
|
12672
|
+
};
|
|
12673
|
+
const statements = [];
|
|
12674
|
+
rawLines.forEach((raw, i) => {
|
|
12675
|
+
for (const seg of splitStatements(stripComment2(raw))) statements.push({ text: seg, no: i + 1 });
|
|
12676
|
+
});
|
|
12677
|
+
for (const stmt of statements) {
|
|
12678
|
+
const lineNo = stmt.no;
|
|
12679
|
+
const toks = tokenize4(stmt.text);
|
|
12680
|
+
if (toks.length === 0) continue;
|
|
12681
|
+
const t0 = toks[0];
|
|
12682
|
+
if (!headerSeen) {
|
|
12683
|
+
if (!t0.str && /^(network|topology)$/i.test(t0.value)) {
|
|
12684
|
+
headerSeen = true;
|
|
12685
|
+
if (toks[1]?.str) ast.title = toks[1].value;
|
|
12686
|
+
continue;
|
|
12687
|
+
}
|
|
12688
|
+
throw new NetworkParseError(
|
|
12689
|
+
`network diagram must start with "network" or "topology" (line ${lineNo})`
|
|
12690
|
+
);
|
|
12691
|
+
}
|
|
12692
|
+
if (toks.length === 1 && t0.value === "}") {
|
|
12693
|
+
if (groupStack.length === 0) {
|
|
12694
|
+
throw new NetworkParseError(`unmatched "}" (line ${lineNo})`);
|
|
12695
|
+
}
|
|
12696
|
+
groupStack.pop();
|
|
12697
|
+
continue;
|
|
12698
|
+
}
|
|
12699
|
+
if (!t0.str && t0.value.endsWith(":")) {
|
|
12700
|
+
const key = t0.value.slice(0, -1).toLowerCase();
|
|
12701
|
+
const rest = toks.slice(1);
|
|
12702
|
+
switch (key) {
|
|
12703
|
+
case "layout": {
|
|
12704
|
+
const v = rest[0]?.value;
|
|
12705
|
+
if (v && LAYOUT_MODES.has(v)) ast.layout = v;
|
|
12706
|
+
else if (v) ast.warnings.push(`unknown layout "${v}" \u2014 using "tiered" (line ${lineNo})`);
|
|
12707
|
+
break;
|
|
12708
|
+
}
|
|
12709
|
+
case "direction": {
|
|
12710
|
+
const v = rest[0]?.value?.toLowerCase();
|
|
12711
|
+
if (v === "tb" || v === "lr") ast.direction = v;
|
|
12712
|
+
break;
|
|
12713
|
+
}
|
|
12714
|
+
case "title":
|
|
12715
|
+
if (rest[0]) ast.title = rest[0].value;
|
|
12716
|
+
break;
|
|
12717
|
+
case "spines":
|
|
12718
|
+
ast.spines.push(...rest.filter((r) => !r.str).map((r) => r.value));
|
|
12719
|
+
break;
|
|
12720
|
+
case "leaves":
|
|
12721
|
+
ast.leaves.push(...rest.filter((r) => !r.str).map((r) => r.value));
|
|
12722
|
+
break;
|
|
12723
|
+
case "legend":
|
|
12724
|
+
break;
|
|
12725
|
+
// parsed-and-ignored in v0.1
|
|
12726
|
+
default:
|
|
12727
|
+
ast.warnings.push(`unknown directive "${key}:" (line ${lineNo})`);
|
|
12728
|
+
}
|
|
12729
|
+
continue;
|
|
12730
|
+
}
|
|
12731
|
+
const ci = toks.findIndex((t) => !t.str && CONNECTORS.has(t.value));
|
|
12732
|
+
if (ci >= 0) {
|
|
12733
|
+
const fromT = toks[ci - 1];
|
|
12734
|
+
const toT = toks[ci + 1];
|
|
12735
|
+
if (!fromT || !toT || fromT.str || toT.str) {
|
|
12736
|
+
throw new NetworkParseError(`malformed link (line ${lineNo})`);
|
|
12737
|
+
}
|
|
12738
|
+
const conn = toks[ci].value;
|
|
12739
|
+
const link = {
|
|
12740
|
+
from: fromT.value,
|
|
12741
|
+
to: toT.value,
|
|
12742
|
+
directed: conn === "->",
|
|
12743
|
+
linkType: conn === "==" ? "lag" : "copper",
|
|
12744
|
+
line: lineNo
|
|
12745
|
+
};
|
|
12746
|
+
let si = ci + 2;
|
|
12747
|
+
if (toks[si] && !toks[si].str && toks[si].value === ":") si++;
|
|
12748
|
+
while (si < toks.length) {
|
|
12749
|
+
const tk = toks[si];
|
|
12750
|
+
if (tk.str) {
|
|
12751
|
+
link.label = tk.value;
|
|
12752
|
+
si++;
|
|
12753
|
+
continue;
|
|
12754
|
+
}
|
|
12755
|
+
const low = tk.value.toLowerCase();
|
|
12756
|
+
if (LINK_TYPE_KEYWORDS[low]) {
|
|
12757
|
+
link.linkType = LINK_TYPE_KEYWORDS[low];
|
|
12758
|
+
si++;
|
|
12759
|
+
continue;
|
|
12760
|
+
}
|
|
12761
|
+
if (low === "trunk" || low === "access") {
|
|
12762
|
+
link.mode = low;
|
|
12763
|
+
si++;
|
|
12764
|
+
continue;
|
|
12765
|
+
}
|
|
12766
|
+
if (SPEED_RE.test(tk.value)) {
|
|
12767
|
+
link.speed = tk.value;
|
|
12768
|
+
si++;
|
|
12769
|
+
continue;
|
|
12770
|
+
}
|
|
12771
|
+
if (low === "vlan:") {
|
|
12772
|
+
const v = toks[si + 1];
|
|
12773
|
+
if (v) {
|
|
12774
|
+
link.vlans = v.value.split(",").map((s) => Number(s.trim())).filter((n) => Number.isFinite(n));
|
|
12775
|
+
for (const vid of link.vlans) {
|
|
12776
|
+
if (vid < 1 || vid > 4094) {
|
|
12777
|
+
ast.warnings.push(`VLAN id ${vid} out of range 1\u20134094 (line ${lineNo})`);
|
|
12778
|
+
}
|
|
12779
|
+
}
|
|
12780
|
+
}
|
|
12781
|
+
si += 2;
|
|
12782
|
+
continue;
|
|
12783
|
+
}
|
|
12784
|
+
if (low === "port:") {
|
|
12785
|
+
const v = toks[si + 1];
|
|
12786
|
+
if (v) {
|
|
12787
|
+
const [near, far] = v.value.split(">");
|
|
12788
|
+
link.portNear = near;
|
|
12789
|
+
if (far) link.portFar = far;
|
|
12790
|
+
}
|
|
12791
|
+
si += 2;
|
|
12792
|
+
continue;
|
|
12793
|
+
}
|
|
12794
|
+
if (low === "ip:") {
|
|
12795
|
+
link.ip = toks[si + 1]?.value;
|
|
12796
|
+
si += 2;
|
|
12797
|
+
continue;
|
|
12798
|
+
}
|
|
12799
|
+
si++;
|
|
12800
|
+
}
|
|
12801
|
+
ast.links.push(link);
|
|
12802
|
+
continue;
|
|
12803
|
+
}
|
|
12804
|
+
if (!t0.str && GROUP_KINDS.has(t0.value) && toks[toks.length - 1].value === "{") {
|
|
12805
|
+
const idT = toks[1];
|
|
12806
|
+
if (!idT || idT.str) throw new NetworkParseError(`group needs an id (line ${lineNo})`);
|
|
12807
|
+
const kind = GROUP_KIND_ALIAS[t0.value] ?? t0.value;
|
|
12808
|
+
const label = toks[2]?.str ? toks[2].value : void 0;
|
|
12809
|
+
if (groupById.has(idT.value)) {
|
|
12810
|
+
throw new NetworkParseError(`group id "${idT.value}" already declared (line ${lineNo})`);
|
|
12811
|
+
}
|
|
12812
|
+
const parent = groupStack[groupStack.length - 1];
|
|
12813
|
+
const g = {
|
|
12814
|
+
id: idT.value,
|
|
12815
|
+
kind,
|
|
12816
|
+
label,
|
|
12817
|
+
members: [],
|
|
12818
|
+
children: [],
|
|
12819
|
+
parent: parent?.id,
|
|
12820
|
+
line: lineNo
|
|
12821
|
+
};
|
|
12822
|
+
if (parent) parent.children.push(g.id);
|
|
12823
|
+
groupById.set(g.id, g);
|
|
12824
|
+
ast.groups.push(g);
|
|
12825
|
+
groupStack.push(g);
|
|
12826
|
+
continue;
|
|
12827
|
+
}
|
|
12828
|
+
const canonKind = KIND_ALIASES[t0.value] ?? t0.value;
|
|
12829
|
+
if (!t0.str && DEVICE_KINDS.has(canonKind)) {
|
|
12830
|
+
const idT = toks[1];
|
|
12831
|
+
if (!idT || idT.str) throw new NetworkParseError(`device "${t0.value}" needs an id (line ${lineNo})`);
|
|
12832
|
+
let idx = 2;
|
|
12833
|
+
let label;
|
|
12834
|
+
if (toks[2]?.str) {
|
|
12835
|
+
label = toks[2].value;
|
|
12836
|
+
idx = 3;
|
|
12837
|
+
}
|
|
12838
|
+
const d = { id: idT.value, kind: canonKind, label, groups: groupStack.map((g) => g.id) };
|
|
12839
|
+
applyAttrs(d, toks, idx, lineNo);
|
|
12840
|
+
addDevice(d, lineNo);
|
|
12841
|
+
continue;
|
|
12842
|
+
}
|
|
12843
|
+
const sep = toks.findIndex((t) => !t.str && t.value === ":");
|
|
12844
|
+
if (sep > 0) {
|
|
12845
|
+
const ids = toks.slice(0, sep);
|
|
12846
|
+
const kindT = toks[sep + 1];
|
|
12847
|
+
if (kindT && !kindT.str && ids.every((t) => !t.str)) {
|
|
12848
|
+
const k = KIND_ALIASES[kindT.value] ?? kindT.value;
|
|
12849
|
+
if (!DEVICE_KINDS.has(k)) {
|
|
12850
|
+
const hint = nearestKind(kindT.value);
|
|
12851
|
+
throw new NetworkParseError(
|
|
12852
|
+
`unknown device kind "${kindT.value}"${hint ? ` \u2014 did you mean "${hint}"?` : ""} (line ${lineNo})`
|
|
12853
|
+
);
|
|
12854
|
+
}
|
|
12855
|
+
for (const idT of ids) {
|
|
12856
|
+
const d = { id: idT.value, kind: k, groups: groupStack.map((g) => g.id) };
|
|
12857
|
+
applyAttrs(d, toks, sep + 2, lineNo);
|
|
12858
|
+
addDevice(d, lineNo);
|
|
12859
|
+
}
|
|
12860
|
+
continue;
|
|
12861
|
+
}
|
|
12862
|
+
}
|
|
12863
|
+
if (toks.length === 1 && !t0.str && groupStack.length > 0) {
|
|
12864
|
+
const top = groupStack[groupStack.length - 1];
|
|
12865
|
+
if (!top.members.includes(t0.value)) top.members.push(t0.value);
|
|
12866
|
+
continue;
|
|
12867
|
+
}
|
|
12868
|
+
if (!t0.str && toks.length >= 2 && !toks[1].str) {
|
|
12869
|
+
const hint = nearestKind(t0.value);
|
|
12870
|
+
throw new NetworkParseError(
|
|
12871
|
+
`unknown device kind "${t0.value}"${hint ? ` \u2014 did you mean "${hint}"?` : ""} (line ${lineNo})`
|
|
12872
|
+
);
|
|
12873
|
+
}
|
|
12874
|
+
throw new NetworkParseError(`cannot parse line ${lineNo}: "${stmt.text.trim()}"`);
|
|
12875
|
+
}
|
|
12876
|
+
if (groupStack.length > 0) {
|
|
12877
|
+
throw new NetworkParseError(`unclosed group "${groupStack[groupStack.length - 1].id}"`);
|
|
12878
|
+
}
|
|
12879
|
+
for (const id of ast.spines) ensureDevice(id, "l3switch");
|
|
12880
|
+
for (const id of ast.leaves) ensureDevice(id, "switch");
|
|
12881
|
+
for (const link of ast.links) {
|
|
12882
|
+
for (const end of [link.from, link.to]) {
|
|
12883
|
+
if (!deviceById.has(end)) {
|
|
12884
|
+
throw new NetworkParseError(
|
|
12885
|
+
`link references undeclared device "${end}" (line ${link.line ?? "?"})`
|
|
12886
|
+
);
|
|
12887
|
+
}
|
|
12888
|
+
}
|
|
12889
|
+
if (link.mode === "trunk") {
|
|
12890
|
+
const a = deviceById.get(link.from), b = deviceById.get(link.to);
|
|
12891
|
+
if (!SWITCH_CLASS.has(a.kind) && !SWITCH_CLASS.has(b.kind)) {
|
|
12892
|
+
ast.warnings.push(`trunk link ${link.from}\u2013${link.to} connects no switch-class device (line ${link.line ?? "?"})`);
|
|
12893
|
+
}
|
|
12894
|
+
}
|
|
12895
|
+
}
|
|
12896
|
+
for (const g of ast.groups) {
|
|
12897
|
+
if (g.kind !== "subnet" || !g.label) continue;
|
|
12898
|
+
for (const memberId of g.members) {
|
|
12899
|
+
const d = deviceById.get(memberId);
|
|
12900
|
+
if (!d?.ip) continue;
|
|
12901
|
+
const inside = ipInCidr(d.ip, g.label);
|
|
12902
|
+
if (inside === false) {
|
|
12903
|
+
throw new NetworkParseError(
|
|
12904
|
+
`${d.id} ip ${d.ip} is not inside subnet ${g.label} (line ${d.line ?? g.line ?? "?"})`
|
|
12905
|
+
);
|
|
12906
|
+
}
|
|
12907
|
+
}
|
|
12908
|
+
}
|
|
12909
|
+
return ast;
|
|
12910
|
+
}
|
|
12911
|
+
|
|
12912
|
+
// src/diagrams/network/symbols.ts
|
|
12913
|
+
var BODY = "sx-net-body";
|
|
12914
|
+
var DET = "sx-net-detail";
|
|
12915
|
+
var GLY = "sx-net-glyph";
|
|
12916
|
+
var GLYL = "sx-net-glyph-line";
|
|
12917
|
+
var ITX = "sx-net-icontext";
|
|
12918
|
+
var CLOUD = "sx-net-cloud-body";
|
|
12919
|
+
var CTX = "sx-net-cloudtext";
|
|
12920
|
+
var r2 = (n) => Math.round(n * 100) / 100;
|
|
12921
|
+
function arrow(x1, y1, x2, y2, hs = 4) {
|
|
12922
|
+
const ang = Math.atan2(y2 - y1, x2 - x1);
|
|
12923
|
+
const a1 = ang + Math.PI - 0.5;
|
|
12924
|
+
const a2 = ang + Math.PI + 0.5;
|
|
12925
|
+
const p1x = r2(x2 + hs * Math.cos(a1)), p1y = r2(y2 + hs * Math.sin(a1));
|
|
12926
|
+
const p2x = r2(x2 + hs * Math.cos(a2)), p2y = r2(y2 + hs * Math.sin(a2));
|
|
12927
|
+
return line({ class: GLYL, x1: r2(x1), y1: r2(y1), x2: r2(x2), y2: r2(y2) }) + polygon({ class: GLY, points: `${r2(x2)},${r2(y2)} ${p1x},${p1y} ${p2x},${p2y}` });
|
|
12928
|
+
}
|
|
12929
|
+
function router(b) {
|
|
12930
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
|
|
12931
|
+
const rw = b.w * 0.78, rh = b.h * 0.62;
|
|
12932
|
+
const x = cx - rw / 2, y = cy - rh / 2;
|
|
12933
|
+
const parts = [
|
|
12934
|
+
rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: rh / 2, ry: rh / 2 })
|
|
12935
|
+
];
|
|
12936
|
+
parts.push(arrow(cx - 2, cy - rh * 0.18, x + rw - 5, cy - rh * 0.18));
|
|
12937
|
+
parts.push(arrow(cx + 2, cy + rh * 0.18, x + 5, cy + rh * 0.18));
|
|
12938
|
+
parts.push(arrow(cx + rw * 0.18, cy + 2, cx + rw * 0.18, y + rh - 4));
|
|
12939
|
+
parts.push(arrow(cx - rw * 0.18, cy - 2, cx - rw * 0.18, y + 4));
|
|
12940
|
+
return group({}, parts);
|
|
12941
|
+
}
|
|
12942
|
+
function switchBox(b, glyph) {
|
|
12943
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
|
|
12944
|
+
const rw = b.w * 0.86, rh = b.h * 0.5;
|
|
12945
|
+
const x = cx - rw / 2, y = cy - rh / 2;
|
|
12946
|
+
const parts = [
|
|
12947
|
+
rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 3, ry: 3 })
|
|
12948
|
+
];
|
|
12949
|
+
if (glyph === "circular") {
|
|
12950
|
+
parts.push(arrow(x + rw * 0.25, cy - rh * 0.16, x + rw * 0.75, cy - rh * 0.16));
|
|
12951
|
+
parts.push(arrow(x + rw * 0.75, cy + rh * 0.16, x + rw * 0.25, cy + rh * 0.16));
|
|
12952
|
+
parts.push(arrow(x + rw * 0.62, cy - rh * 0.16, x + rw * 0.78, cy - rh * 0.16));
|
|
12953
|
+
} else {
|
|
12954
|
+
parts.push(arrow(x + rw * 0.2, cy - rh * 0.18, x + rw * 0.8, cy - rh * 0.18));
|
|
12955
|
+
parts.push(arrow(x + rw * 0.8, cy + rh * 0.18, x + rw * 0.2, cy + rh * 0.18));
|
|
12956
|
+
}
|
|
12957
|
+
return group({}, parts);
|
|
12958
|
+
}
|
|
12959
|
+
function poeSwitch(b) {
|
|
12960
|
+
const cx = b.x + b.w / 2;
|
|
12961
|
+
return group({}, [switchBox(b, "straight"), text({ class: ITX, x: r2(cx), y: r2(b.y + b.h * 0.78 + 8), "text-anchor": "middle" }, "PoE")]);
|
|
12962
|
+
}
|
|
12963
|
+
function firewall(b) {
|
|
12964
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
|
|
12965
|
+
const rw = b.w * 0.78, rh = b.h * 0.62;
|
|
12966
|
+
const x = cx - rw / 2, y = cy - rh / 2;
|
|
12967
|
+
const parts = [rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 2, ry: 2 })];
|
|
12968
|
+
const rows = 3;
|
|
12969
|
+
const rhh = rh / rows;
|
|
12970
|
+
for (let i = 1; i < rows; i++) parts.push(line({ class: DET, x1: r2(x), y1: r2(y + i * rhh), x2: r2(x + rw), y2: r2(y + i * rhh) }));
|
|
12971
|
+
for (let i = 0; i < rows; i++) {
|
|
12972
|
+
const yy = y + i * rhh;
|
|
12973
|
+
const offset = i % 2 === 0 ? rw / 3 : rw / 6;
|
|
12974
|
+
for (let vx = x + offset; vx < x + rw - 2; vx += rw / 3) {
|
|
12975
|
+
parts.push(line({ class: DET, x1: r2(vx), y1: r2(yy), x2: r2(vx), y2: r2(yy + rhh) }));
|
|
12976
|
+
}
|
|
12977
|
+
}
|
|
12978
|
+
return group({}, parts);
|
|
12979
|
+
}
|
|
12980
|
+
function brickless(b, glyph) {
|
|
12981
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
|
|
12982
|
+
const rw = b.w * 0.78, rh = b.h * 0.6;
|
|
12983
|
+
const x = cx - rw / 2, y = cy - rh / 2;
|
|
12984
|
+
return group({}, [
|
|
12985
|
+
rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 3, ry: 3 }),
|
|
12986
|
+
text({ class: ITX, x: r2(cx), y: r2(cy + 4), "text-anchor": "middle" }, glyph)
|
|
12987
|
+
]);
|
|
12988
|
+
}
|
|
12989
|
+
function accessPoint(b) {
|
|
12990
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h * 0.62;
|
|
12991
|
+
const rw = b.w * 0.5, rh = b.h * 0.34;
|
|
12992
|
+
const parts = [
|
|
12993
|
+
rect({ class: BODY, x: r2(cx - rw / 2), y: r2(cy - rh / 2), width: r2(rw), height: r2(rh), rx: rh / 2, ry: rh / 2 })
|
|
12994
|
+
];
|
|
12995
|
+
for (let i = 1; i <= 2; i++) {
|
|
12996
|
+
const rr = i * b.w * 0.16;
|
|
12997
|
+
parts.push(path({ class: GLYL, d: `M ${r2(cx - rr)} ${r2(cy - rh / 2 - 2)} A ${r2(rr)} ${r2(rr)} 0 0 1 ${r2(cx + rr)} ${r2(cy - rh / 2 - 2)}` }));
|
|
12998
|
+
}
|
|
12999
|
+
return group({}, parts);
|
|
13000
|
+
}
|
|
13001
|
+
function server(b) {
|
|
13002
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
|
|
13003
|
+
const rw = b.w * 0.46, rh = b.h * 0.82;
|
|
13004
|
+
const x = cx - rw / 2, y = cy - rh / 2;
|
|
13005
|
+
const parts = [rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 2, ry: 2 })];
|
|
13006
|
+
for (let i = 1; i <= 3; i++) parts.push(line({ class: DET, x1: r2(x + 4), y1: r2(y + i * (rh / 4)), x2: r2(x + rw - 4), y2: r2(y + i * (rh / 4)) }));
|
|
13007
|
+
parts.push(circle({ class: GLY, cx: r2(x + rw - 7), cy: r2(y + 6), r: 1.6 }));
|
|
13008
|
+
return group({}, parts);
|
|
13009
|
+
}
|
|
13010
|
+
function serverFarm(b, d) {
|
|
13011
|
+
const parts = [];
|
|
13012
|
+
const off = Math.min(6, b.w * 0.1);
|
|
13013
|
+
for (let i = 2; i >= 0; i--) {
|
|
13014
|
+
parts.push(server({ x: b.x + i * off, y: b.y - i * off * 0.5, w: b.w - 2 * off, h: b.h - off }));
|
|
13015
|
+
}
|
|
13016
|
+
if (d.count) parts.push(text({ class: ITX, x: r2(b.x + b.w - 6), y: r2(b.y + b.h - 2), "text-anchor": "end" }, `\xD7${d.count}`));
|
|
13017
|
+
return group({}, parts);
|
|
13018
|
+
}
|
|
13019
|
+
function pc(b) {
|
|
13020
|
+
const cx = b.x + b.w / 2;
|
|
13021
|
+
const sw = b.w * 0.7, sh = b.h * 0.52;
|
|
13022
|
+
const sx = cx - sw / 2, sy = b.y + b.h * 0.12;
|
|
13023
|
+
return group({}, [
|
|
13024
|
+
rect({ class: BODY, x: r2(sx), y: r2(sy), width: r2(sw), height: r2(sh), rx: 2, ry: 2 }),
|
|
13025
|
+
rect({ class: DET, x: r2(sx + 3), y: r2(sy + 3), width: r2(sw - 6), height: r2(sh - 6), rx: 1, ry: 1, fill: "none" }),
|
|
13026
|
+
line({ class: DET, x1: r2(cx), y1: r2(sy + sh), x2: r2(cx), y2: r2(sy + sh + b.h * 0.16) }),
|
|
13027
|
+
line({ class: DET, x1: r2(cx - sw * 0.28), y1: r2(b.y + b.h - 2), x2: r2(cx + sw * 0.28), y2: r2(b.y + b.h - 2) })
|
|
13028
|
+
]);
|
|
13029
|
+
}
|
|
13030
|
+
function laptop(b) {
|
|
13031
|
+
const cx = b.x + b.w / 2;
|
|
13032
|
+
const sw = b.w * 0.6, sh = b.h * 0.46;
|
|
13033
|
+
const sx = cx - sw / 2, sy = b.y + b.h * 0.16;
|
|
13034
|
+
return group({}, [
|
|
13035
|
+
rect({ class: BODY, x: r2(sx), y: r2(sy), width: r2(sw), height: r2(sh), rx: 2, ry: 2 }),
|
|
13036
|
+
polygon({ class: BODY, points: `${r2(sx - sw * 0.18)},${r2(sy + sh + b.h * 0.18)} ${r2(sx + sw + sw * 0.18)},${r2(sy + sh + b.h * 0.18)} ${r2(sx + sw)},${r2(sy + sh)} ${r2(sx)},${r2(sy + sh)}` })
|
|
13037
|
+
]);
|
|
13038
|
+
}
|
|
13039
|
+
function mobile(b) {
|
|
13040
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
|
|
13041
|
+
const rw = b.w * 0.32, rh = b.h * 0.82;
|
|
13042
|
+
return group({}, [
|
|
13043
|
+
rect({ class: BODY, x: r2(cx - rw / 2), y: r2(cy - rh / 2), width: r2(rw), height: r2(rh), rx: 3, ry: 3 }),
|
|
13044
|
+
line({ class: DET, x1: r2(cx - rw * 0.2), y1: r2(cy + rh / 2 - 4), x2: r2(cx + rw * 0.2), y2: r2(cy + rh / 2 - 4) })
|
|
13045
|
+
]);
|
|
13046
|
+
}
|
|
13047
|
+
function ipphone(b) {
|
|
13048
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
|
|
13049
|
+
const rw = b.w * 0.6, rh = b.h * 0.6;
|
|
13050
|
+
const x = cx - rw / 2, y = cy - rh / 2;
|
|
13051
|
+
return group({}, [
|
|
13052
|
+
rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh * 0.7), rx: 2, ry: 2 }),
|
|
13053
|
+
path({ class: GLYL, d: `M ${r2(x + rw * 0.2)} ${r2(y + 5)} q ${r2(rw * 0.3)} ${r2(rh * 0.5)} ${r2(rw * 0.6)} 0` })
|
|
13054
|
+
]);
|
|
13055
|
+
}
|
|
13056
|
+
function printer(b) {
|
|
13057
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
|
|
13058
|
+
const rw = b.w * 0.7, rh = b.h * 0.42;
|
|
13059
|
+
const x = cx - rw / 2, y = cy - rh * 0.1;
|
|
13060
|
+
return group({}, [
|
|
13061
|
+
rect({ class: DET, x: r2(x + rw * 0.18), y: r2(y - rh * 0.7), width: r2(rw * 0.64), height: r2(rh * 0.7), fill: "none" }),
|
|
13062
|
+
rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 2, ry: 2 }),
|
|
13063
|
+
rect({ class: DET, x: r2(x + rw * 0.18), y: r2(y + rh - 2), width: r2(rw * 0.64), height: r2(rh * 0.5), fill: "none" }),
|
|
13064
|
+
circle({ class: GLY, cx: r2(x + rw - 7), cy: r2(y + rh / 2), r: 1.6 })
|
|
13065
|
+
]);
|
|
13066
|
+
}
|
|
13067
|
+
function storage(b) {
|
|
13068
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
|
|
13069
|
+
const rw = b.w * 0.5, rh = b.h * 0.78;
|
|
13070
|
+
const x = cx - rw / 2, y = cy - rh / 2;
|
|
13071
|
+
const ry = rw * 0.18;
|
|
13072
|
+
const parts = [
|
|
13073
|
+
path({ class: BODY, d: `M ${r2(x)} ${r2(y + ry)} A ${r2(rw / 2)} ${r2(ry)} 0 0 1 ${r2(x + rw)} ${r2(y + ry)} L ${r2(x + rw)} ${r2(y + rh - ry)} A ${r2(rw / 2)} ${r2(ry)} 0 0 1 ${r2(x)} ${r2(y + rh - ry)} Z` }),
|
|
13074
|
+
path({ class: DET, fill: "none", d: `M ${r2(x)} ${r2(y + ry)} A ${r2(rw / 2)} ${r2(ry)} 0 0 0 ${r2(x + rw)} ${r2(y + ry)}` })
|
|
13075
|
+
];
|
|
13076
|
+
return group({}, parts);
|
|
13077
|
+
}
|
|
13078
|
+
function camera(b, d) {
|
|
13079
|
+
const t = d.cameraType ?? "fixed";
|
|
13080
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
|
|
13081
|
+
if (t === "dome" || t === "ptz") {
|
|
13082
|
+
const rr = b.w * (t === "ptz" ? 0.34 : 0.28);
|
|
13083
|
+
const parts = [
|
|
13084
|
+
path({ class: BODY, d: `M ${r2(cx - rr)} ${r2(cy + rr * 0.2)} A ${r2(rr)} ${r2(rr)} 0 0 1 ${r2(cx + rr)} ${r2(cy + rr * 0.2)} Z` }),
|
|
13085
|
+
rect({ class: BODY, x: r2(cx - rr * 1.15), y: r2(cy + rr * 0.2), width: r2(rr * 2.3), height: r2(b.h * 0.12), rx: 1, ry: 1 }),
|
|
13086
|
+
circle({ class: GLY, cx: r2(cx), cy: r2(cy - rr * 0.15), r: r2(rr * 0.3) })
|
|
13087
|
+
];
|
|
13088
|
+
return group({}, parts);
|
|
13089
|
+
}
|
|
13090
|
+
if (t === "bullet") {
|
|
13091
|
+
const bw2 = b.w * 0.6, bh2 = b.h * 0.34;
|
|
13092
|
+
const x2 = cx - bw2 / 2, y2 = cy - bh2 / 2;
|
|
13093
|
+
return group({}, [
|
|
13094
|
+
rect({ class: BODY, x: r2(x2), y: r2(y2), width: r2(bw2), height: r2(bh2), rx: bh2 / 2, ry: bh2 / 2 }),
|
|
13095
|
+
circle({ class: GLY, cx: r2(x2 + bw2 - 4), cy: r2(cy), r: r2(bh2 * 0.28) }),
|
|
13096
|
+
line({ class: DET, x1: r2(cx), y1: r2(y2 + bh2), x2: r2(cx), y2: r2(y2 + bh2 + b.h * 0.16) })
|
|
13097
|
+
]);
|
|
13098
|
+
}
|
|
13099
|
+
const bw = b.w * 0.5, bh = b.h * 0.42;
|
|
13100
|
+
const x = cx - bw / 2, y = cy - bh / 2;
|
|
13101
|
+
return group({}, [
|
|
13102
|
+
rect({ class: BODY, x: r2(x), y: r2(y), width: r2(bw), height: r2(bh), rx: 2, ry: 2 }),
|
|
13103
|
+
circle({ class: GLY, cx: r2(cx), cy: r2(cy), r: r2(bh * 0.3) })
|
|
13104
|
+
]);
|
|
13105
|
+
}
|
|
13106
|
+
function recorder(b, label) {
|
|
13107
|
+
const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
|
|
13108
|
+
const rw = b.w * 0.82, rh = b.h * 0.46;
|
|
13109
|
+
const x = cx - rw / 2, y = cy - rh / 2;
|
|
13110
|
+
return group({}, [
|
|
13111
|
+
rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 2, ry: 2 }),
|
|
13112
|
+
circle({ class: GLY, cx: r2(x + rw * 0.22), cy: r2(y + rh * 0.32), r: 2 }),
|
|
13113
|
+
circle({ class: GLY, cx: r2(x + rw * 0.4), cy: r2(y + rh * 0.32), r: 2 }),
|
|
13114
|
+
text({ class: ITX, x: r2(cx), y: r2(y + rh * 0.85), "text-anchor": "middle" }, label)
|
|
13115
|
+
]);
|
|
13116
|
+
}
|
|
13117
|
+
function monitor(b, grid) {
|
|
13118
|
+
const cx = b.x + b.w / 2;
|
|
13119
|
+
const sw = b.w * 0.84, sh = b.h * 0.56;
|
|
13120
|
+
const sx = cx - sw / 2, sy = b.y + b.h * 0.1;
|
|
13121
|
+
const parts = [rect({ class: BODY, x: r2(sx), y: r2(sy), width: r2(sw), height: r2(sh), rx: 2, ry: 2 })];
|
|
13122
|
+
if (grid) {
|
|
13123
|
+
parts.push(line({ class: DET, x1: r2(cx), y1: r2(sy), x2: r2(cx), y2: r2(sy + sh) }));
|
|
13124
|
+
parts.push(line({ class: DET, x1: r2(sx), y1: r2(sy + sh / 2), x2: r2(sx + sw), y2: r2(sy + sh / 2) }));
|
|
13125
|
+
}
|
|
13126
|
+
parts.push(line({ class: DET, x1: r2(cx), y1: r2(sy + sh), x2: r2(cx), y2: r2(sy + sh + b.h * 0.14) }));
|
|
13127
|
+
parts.push(line({ class: DET, x1: r2(cx - sw * 0.2), y1: r2(b.y + b.h - 2), x2: r2(cx + sw * 0.2), y2: r2(b.y + b.h - 2) }));
|
|
13128
|
+
return group({}, parts);
|
|
13129
|
+
}
|
|
13130
|
+
function cloud(b, label) {
|
|
13131
|
+
const mx = (dx) => r2(b.x + dx / 200 * b.w);
|
|
13132
|
+
const my = (dy) => r2(b.y + dy / 110 * b.h);
|
|
13133
|
+
const d = `M ${mx(50)} ${my(96)} C ${mx(24)} ${my(96)} ${mx(24)} ${my(62)} ${mx(50)} ${my(58)} C ${mx(50)} ${my(30)} ${mx(94)} ${my(26)} ${mx(100)} ${my(50)} C ${mx(106)} ${my(26)} ${mx(150)} ${my(30)} ${mx(150)} ${my(58)} C ${mx(176)} ${my(62)} ${mx(176)} ${my(96)} ${mx(150)} ${my(96)} Z`;
|
|
13134
|
+
return group({}, [
|
|
13135
|
+
path({ class: CLOUD, d }),
|
|
13136
|
+
text({ class: CTX, x: r2(b.x + b.w / 2), y: my(80), "text-anchor": "middle" }, label)
|
|
13137
|
+
]);
|
|
13138
|
+
}
|
|
13139
|
+
function busBar(b) {
|
|
13140
|
+
const y = b.y + b.h / 2;
|
|
13141
|
+
return line({ class: "sx-net-bus", x1: r2(b.x), y1: r2(y), x2: r2(b.x + b.w), y2: r2(y) });
|
|
13142
|
+
}
|
|
13143
|
+
function iconSize(kind) {
|
|
13144
|
+
if (kind === "internet" || kind === "wan" || kind === "cloud" || kind === "pstn") return { w: 110, h: 64 };
|
|
13145
|
+
if (kind === "lan") return { w: 150, h: 24 };
|
|
13146
|
+
if (kind === "serverfarm") return { w: 74, h: 58 };
|
|
13147
|
+
return { w: 64, h: 48 };
|
|
13148
|
+
}
|
|
13149
|
+
function drawDeviceIcon(d, b) {
|
|
13150
|
+
switch (d.kind) {
|
|
13151
|
+
case "router":
|
|
13152
|
+
return router(b);
|
|
13153
|
+
case "gateway":
|
|
13154
|
+
return group({}, [router(b), text({ class: ITX, x: r2(b.x + b.w / 2), y: r2(b.y + b.h - 1), "text-anchor": "middle" }, "GW")]);
|
|
13155
|
+
case "vpngw":
|
|
13156
|
+
return group({}, [router(b), text({ class: ITX, x: r2(b.x + b.w / 2), y: r2(b.y + b.h - 1), "text-anchor": "middle" }, "VPN")]);
|
|
13157
|
+
case "switch":
|
|
13158
|
+
return switchBox(b, "straight");
|
|
13159
|
+
case "l3switch":
|
|
13160
|
+
return switchBox(b, "circular");
|
|
13161
|
+
case "poeswitch":
|
|
13162
|
+
return poeSwitch(b);
|
|
13163
|
+
case "firewall":
|
|
13164
|
+
return firewall(b);
|
|
13165
|
+
case "loadbalancer":
|
|
13166
|
+
return brickless(b, "LB");
|
|
13167
|
+
case "ids":
|
|
13168
|
+
return brickless(b, "IDS");
|
|
13169
|
+
case "proxy":
|
|
13170
|
+
return brickless(b, "PXY");
|
|
13171
|
+
case "modem":
|
|
13172
|
+
return brickless(b, "MDM");
|
|
13173
|
+
case "wlc":
|
|
13174
|
+
return brickless(b, "WLC");
|
|
13175
|
+
case "ap":
|
|
13176
|
+
return accessPoint(b);
|
|
13177
|
+
case "server":
|
|
13178
|
+
return server(b);
|
|
13179
|
+
case "serverfarm":
|
|
13180
|
+
return serverFarm(b, d);
|
|
13181
|
+
case "pc":
|
|
13182
|
+
return pc(b);
|
|
13183
|
+
case "laptop":
|
|
13184
|
+
return laptop(b);
|
|
13185
|
+
case "mobile":
|
|
13186
|
+
return mobile(b);
|
|
13187
|
+
case "ipphone":
|
|
13188
|
+
return ipphone(b);
|
|
13189
|
+
case "printer":
|
|
13190
|
+
return printer(b);
|
|
13191
|
+
case "storage":
|
|
13192
|
+
return storage(b);
|
|
13193
|
+
case "camera":
|
|
13194
|
+
return camera(b, d);
|
|
13195
|
+
case "nvr":
|
|
13196
|
+
return recorder(b, "NVR");
|
|
13197
|
+
case "dvr":
|
|
13198
|
+
return recorder(b, "DVR");
|
|
13199
|
+
case "encoder":
|
|
13200
|
+
return brickless(b, "ENC");
|
|
13201
|
+
case "monitor":
|
|
13202
|
+
return monitor(b, d.icon === "videowall");
|
|
13203
|
+
case "internet":
|
|
13204
|
+
return cloud(b, d.label ?? "Internet");
|
|
13205
|
+
case "wan":
|
|
13206
|
+
return cloud(b, d.label ?? "WAN");
|
|
13207
|
+
case "pstn":
|
|
13208
|
+
return cloud(b, d.label ?? "PSTN");
|
|
13209
|
+
case "cloud":
|
|
13210
|
+
return cloud(b, d.label ?? "Cloud");
|
|
13211
|
+
case "lan":
|
|
13212
|
+
return busBar(b);
|
|
13213
|
+
default:
|
|
13214
|
+
return brickless(b, "?");
|
|
13215
|
+
}
|
|
13216
|
+
}
|
|
13217
|
+
function isCloudKind(kind) {
|
|
13218
|
+
return kind === "internet" || kind === "wan" || kind === "pstn" || kind === "cloud";
|
|
13219
|
+
}
|
|
13220
|
+
|
|
13221
|
+
// src/diagrams/network/layout.ts
|
|
13222
|
+
var NET_CONST = {
|
|
13223
|
+
DEVICE_W: 64,
|
|
13224
|
+
DEVICE_H: 48,
|
|
13225
|
+
TIER_BAND_GAP: 104,
|
|
13226
|
+
SIBLING_GAP: 44,
|
|
13227
|
+
RING_RADIUS_MIN: 120,
|
|
13228
|
+
STAR_HUB_GAP: 130,
|
|
13229
|
+
SPINE_LEAF_GAP: 140,
|
|
13230
|
+
LABEL_GAP: 6,
|
|
13231
|
+
LABEL_H: 15,
|
|
13232
|
+
SUBLABEL_H: 12,
|
|
13233
|
+
GROUP_PAD: 18,
|
|
13234
|
+
GROUP_LABEL_INSET: 12,
|
|
13235
|
+
GROUP_HEADER: 16,
|
|
13236
|
+
CHAR_W: 6.3,
|
|
13237
|
+
PAD: 30
|
|
13238
|
+
};
|
|
13239
|
+
var ENDPOINT_KINDS = /* @__PURE__ */ new Set([
|
|
13240
|
+
"pc",
|
|
13241
|
+
"laptop",
|
|
13242
|
+
"mobile",
|
|
13243
|
+
"ipphone",
|
|
13244
|
+
"printer",
|
|
13245
|
+
"camera",
|
|
13246
|
+
"server",
|
|
13247
|
+
"serverfarm",
|
|
13248
|
+
"storage",
|
|
13249
|
+
"monitor",
|
|
13250
|
+
"nvr",
|
|
13251
|
+
"dvr"
|
|
13252
|
+
]);
|
|
13253
|
+
function labelExtra(d) {
|
|
13254
|
+
if (isCloudKind(d.kind)) return 0;
|
|
13255
|
+
let h = NET_CONST.LABEL_GAP + NET_CONST.LABEL_H;
|
|
13256
|
+
if (d.ip || d.model) h += NET_CONST.SUBLABEL_H;
|
|
13257
|
+
return h;
|
|
13258
|
+
}
|
|
13259
|
+
function labelText(d) {
|
|
13260
|
+
return d.label ?? d.id;
|
|
13261
|
+
}
|
|
13262
|
+
function deviceFootprint(d) {
|
|
13263
|
+
return iconSize(d.kind);
|
|
13264
|
+
}
|
|
13265
|
+
function effBox(box) {
|
|
13266
|
+
const labelW = Math.max(box.w, labelText(box.device).length * NET_CONST.CHAR_W + 6);
|
|
13267
|
+
const half = labelW / 2;
|
|
13268
|
+
return {
|
|
13269
|
+
left: box.cx - half,
|
|
13270
|
+
top: box.y,
|
|
13271
|
+
right: box.cx + half,
|
|
13272
|
+
bottom: box.y + box.h + labelExtra(box.device)
|
|
13273
|
+
};
|
|
13274
|
+
}
|
|
13275
|
+
function edgePoint(box, tx, ty) {
|
|
13276
|
+
const dx = tx - box.cx;
|
|
13277
|
+
const dy = ty - box.cy;
|
|
13278
|
+
if (dx === 0 && dy === 0) return { x: box.cx, y: box.cy };
|
|
13279
|
+
const hw = box.w / 2;
|
|
13280
|
+
const hh = box.h / 2;
|
|
13281
|
+
const sx = dx !== 0 ? hw / Math.abs(dx) : Infinity;
|
|
13282
|
+
const sy = dy !== 0 ? hh / Math.abs(dy) : Infinity;
|
|
13283
|
+
const s = Math.min(sx, sy);
|
|
13284
|
+
return { x: box.cx + dx * s, y: box.cy + dy * s };
|
|
13285
|
+
}
|
|
13286
|
+
function placeBanded(ast, ranks) {
|
|
13287
|
+
const lr = ast.direction === "lr";
|
|
13288
|
+
const byRank = /* @__PURE__ */ new Map();
|
|
13289
|
+
for (const d of ast.devices) {
|
|
13290
|
+
const r = ranks.get(d.id) ?? 0;
|
|
13291
|
+
if (!byRank.has(r)) byRank.set(r, []);
|
|
13292
|
+
byRank.get(r).push(d);
|
|
13293
|
+
}
|
|
13294
|
+
const rankValues = [...byRank.keys()].sort((a, b) => a - b);
|
|
13295
|
+
const pos = /* @__PURE__ */ new Map();
|
|
13296
|
+
let maxRowSpan = 0;
|
|
13297
|
+
const rowSpans = /* @__PURE__ */ new Map();
|
|
13298
|
+
for (const r of rankValues) {
|
|
13299
|
+
const devs = byRank.get(r);
|
|
13300
|
+
let span = 0;
|
|
13301
|
+
devs.forEach((d, i) => {
|
|
13302
|
+
const fp = deviceFootprint(d);
|
|
13303
|
+
const cross = lr ? fp.h + labelExtra(d) : Math.max(fp.w, labelText(d).length * NET_CONST.CHAR_W + 6);
|
|
13304
|
+
span += cross + (i > 0 ? NET_CONST.SIBLING_GAP : 0);
|
|
13305
|
+
});
|
|
13306
|
+
rowSpans.set(r, span);
|
|
13307
|
+
maxRowSpan = Math.max(maxRowSpan, span);
|
|
13308
|
+
}
|
|
13309
|
+
rankValues.forEach((r, rowIdx) => {
|
|
13310
|
+
const devs = byRank.get(r);
|
|
13311
|
+
const along = rowIdx * NET_CONST.TIER_BAND_GAP;
|
|
13312
|
+
let cursor = (maxRowSpan - rowSpans.get(r)) / 2;
|
|
13313
|
+
for (const d of devs) {
|
|
13314
|
+
const fp = deviceFootprint(d);
|
|
13315
|
+
const cross = lr ? fp.h + labelExtra(d) : Math.max(fp.w, labelText(d).length * NET_CONST.CHAR_W + 6);
|
|
13316
|
+
const center = cursor + cross / 2;
|
|
13317
|
+
if (lr) pos.set(d.id, { x: along, y: center });
|
|
13318
|
+
else pos.set(d.id, { x: center, y: along });
|
|
13319
|
+
cursor += cross + NET_CONST.SIBLING_GAP;
|
|
13320
|
+
}
|
|
13321
|
+
});
|
|
13322
|
+
return pos;
|
|
13323
|
+
}
|
|
13324
|
+
function adjacency(devices, links) {
|
|
13325
|
+
const adj = /* @__PURE__ */ new Map();
|
|
13326
|
+
for (const d of devices) adj.set(d.id, /* @__PURE__ */ new Set());
|
|
13327
|
+
for (const l of links) {
|
|
13328
|
+
adj.get(l.from)?.add(l.to);
|
|
13329
|
+
adj.get(l.to)?.add(l.from);
|
|
13330
|
+
}
|
|
13331
|
+
return adj;
|
|
13332
|
+
}
|
|
13333
|
+
function tieredRanks(ast, links) {
|
|
13334
|
+
const TIER_RANK = { edge: 1, core: 2, distribution: 3, access: 4 };
|
|
13335
|
+
const rank = /* @__PURE__ */ new Map();
|
|
13336
|
+
for (const d of ast.devices) {
|
|
13337
|
+
if (isCloudKind(d.kind)) rank.set(d.id, 0);
|
|
13338
|
+
else if (d.tier) rank.set(d.id, TIER_RANK[d.tier]);
|
|
13339
|
+
}
|
|
13340
|
+
const adj = adjacency(ast.devices, links);
|
|
13341
|
+
for (let pass = 0; pass < ast.devices.length + 4; pass++) {
|
|
13342
|
+
let changed = false;
|
|
13343
|
+
for (const d of ast.devices) {
|
|
13344
|
+
if (rank.has(d.id)) continue;
|
|
13345
|
+
const known = [];
|
|
13346
|
+
for (const n of adj.get(d.id) ?? []) {
|
|
13347
|
+
const r = rank.get(n);
|
|
13348
|
+
if (r !== void 0) known.push(r);
|
|
13349
|
+
}
|
|
13350
|
+
if (known.length === 0) continue;
|
|
13351
|
+
const next = ENDPOINT_KINDS.has(d.kind) ? Math.max(...known) + 1 : Math.min(...known) + 1;
|
|
13352
|
+
rank.set(d.id, next);
|
|
13353
|
+
changed = true;
|
|
13354
|
+
}
|
|
13355
|
+
if (!changed) break;
|
|
13356
|
+
}
|
|
13357
|
+
const maxR = Math.max(0, ...[...rank.values()]);
|
|
13358
|
+
for (const d of ast.devices) {
|
|
13359
|
+
if (!rank.has(d.id)) rank.set(d.id, ENDPOINT_KINDS.has(d.kind) ? maxR + 1 : 2);
|
|
13360
|
+
}
|
|
13361
|
+
return rank;
|
|
13362
|
+
}
|
|
13363
|
+
function treeRanks(ast, links) {
|
|
13364
|
+
const adj = adjacency(ast.devices, links);
|
|
13365
|
+
const rank = /* @__PURE__ */ new Map();
|
|
13366
|
+
let root = ast.devices.find((d) => isCloudKind(d.kind));
|
|
13367
|
+
if (!root) {
|
|
13368
|
+
let best = -1;
|
|
13369
|
+
for (const d of ast.devices) {
|
|
13370
|
+
const deg = adj.get(d.id)?.size ?? 0;
|
|
13371
|
+
if (deg > best) {
|
|
13372
|
+
best = deg;
|
|
13373
|
+
root = d;
|
|
13374
|
+
}
|
|
13375
|
+
}
|
|
13376
|
+
}
|
|
13377
|
+
if (!root) return rank;
|
|
13378
|
+
const queue = [root.id];
|
|
13379
|
+
rank.set(root.id, 0);
|
|
13380
|
+
while (queue.length) {
|
|
13381
|
+
const id = queue.shift();
|
|
13382
|
+
const r = rank.get(id);
|
|
13383
|
+
for (const n of adj.get(id) ?? []) {
|
|
13384
|
+
if (!rank.has(n)) {
|
|
13385
|
+
rank.set(n, r + 1);
|
|
13386
|
+
queue.push(n);
|
|
13387
|
+
}
|
|
13388
|
+
}
|
|
13389
|
+
}
|
|
13390
|
+
for (const d of ast.devices) if (!rank.has(d.id)) rank.set(d.id, 0);
|
|
13391
|
+
return rank;
|
|
13392
|
+
}
|
|
13393
|
+
function placeCircle(ast, radiusBase) {
|
|
13394
|
+
const pos = /* @__PURE__ */ new Map();
|
|
13395
|
+
const n = ast.devices.length;
|
|
13396
|
+
const radius = Math.max(radiusBase, n * 56 / (2 * Math.PI));
|
|
13397
|
+
ast.devices.forEach((d, i) => {
|
|
13398
|
+
const ang = i / Math.max(1, n) * 2 * Math.PI - Math.PI / 2;
|
|
13399
|
+
pos.set(d.id, { x: radius * Math.cos(ang), y: radius * Math.sin(ang) });
|
|
13400
|
+
});
|
|
13401
|
+
return pos;
|
|
13402
|
+
}
|
|
13403
|
+
function placeStar(ast, links) {
|
|
13404
|
+
const adj = adjacency(ast.devices, links);
|
|
13405
|
+
let hub = ast.devices[0];
|
|
13406
|
+
let best = -1;
|
|
13407
|
+
for (const d of ast.devices) {
|
|
13408
|
+
const deg = adj.get(d.id)?.size ?? 0;
|
|
13409
|
+
if (deg > best) {
|
|
13410
|
+
best = deg;
|
|
13411
|
+
hub = d;
|
|
13412
|
+
}
|
|
13413
|
+
}
|
|
13414
|
+
const pos = /* @__PURE__ */ new Map();
|
|
13415
|
+
if (!hub) return pos;
|
|
13416
|
+
pos.set(hub.id, { x: 0, y: 0 });
|
|
13417
|
+
const spokes = ast.devices.filter((d) => d.id !== hub.id);
|
|
13418
|
+
const radius = Math.max(NET_CONST.STAR_HUB_GAP, spokes.length * 50 / (2 * Math.PI));
|
|
13419
|
+
spokes.forEach((d, i) => {
|
|
13420
|
+
const ang = i / Math.max(1, spokes.length) * 2 * Math.PI - Math.PI / 2;
|
|
13421
|
+
pos.set(d.id, { x: radius * Math.cos(ang), y: radius * Math.sin(ang) });
|
|
13422
|
+
});
|
|
13423
|
+
return pos;
|
|
13424
|
+
}
|
|
13425
|
+
function placeBus(ast) {
|
|
13426
|
+
const pos = /* @__PURE__ */ new Map();
|
|
13427
|
+
let cursor = 0;
|
|
13428
|
+
for (const d of ast.devices) {
|
|
13429
|
+
const fp = deviceFootprint(d);
|
|
13430
|
+
pos.set(d.id, { x: cursor + fp.w / 2, y: 0 });
|
|
13431
|
+
cursor += fp.w + NET_CONST.SIBLING_GAP;
|
|
13432
|
+
}
|
|
13433
|
+
return pos;
|
|
13434
|
+
}
|
|
13435
|
+
function placeSpineLeaf(ast, links) {
|
|
13436
|
+
const pos = /* @__PURE__ */ new Map();
|
|
13437
|
+
const spineSet = new Set(ast.spines);
|
|
13438
|
+
const leafSet = new Set(ast.leaves);
|
|
13439
|
+
const rowGap = NET_CONST.SPINE_LEAF_GAP;
|
|
13440
|
+
const step = NET_CONST.DEVICE_W + NET_CONST.SIBLING_GAP;
|
|
13441
|
+
const centerRow = (ids, y) => {
|
|
13442
|
+
const span = (ids.length - 1) * step;
|
|
13443
|
+
ids.forEach((id, i) => pos.set(id, { x: i * step - span / 2, y }));
|
|
13444
|
+
};
|
|
13445
|
+
centerRow(ast.spines, 0);
|
|
13446
|
+
centerRow(ast.leaves, rowGap);
|
|
13447
|
+
const adj = adjacency(ast.devices, links);
|
|
13448
|
+
const hosts = ast.devices.filter((d) => !spineSet.has(d.id) && !leafSet.has(d.id));
|
|
13449
|
+
let seq = 0;
|
|
13450
|
+
for (const h of hosts) {
|
|
13451
|
+
let anchorX;
|
|
13452
|
+
for (const n of adj.get(h.id) ?? []) {
|
|
13453
|
+
if (leafSet.has(n)) {
|
|
13454
|
+
anchorX = pos.get(n)?.x;
|
|
13455
|
+
break;
|
|
13456
|
+
}
|
|
13457
|
+
}
|
|
13458
|
+
pos.set(h.id, { x: anchorX ?? seq++ * step, y: rowGap * 2 });
|
|
13459
|
+
}
|
|
13460
|
+
return pos;
|
|
13461
|
+
}
|
|
13462
|
+
function placeManual(ast) {
|
|
13463
|
+
const pos = /* @__PURE__ */ new Map();
|
|
13464
|
+
let fallback = 0;
|
|
13465
|
+
for (const d of ast.devices) {
|
|
13466
|
+
if (d.at) pos.set(d.id, { x: d.at.x, y: d.at.y });
|
|
13467
|
+
else {
|
|
13468
|
+
pos.set(d.id, { x: fallback * 120, y: 0 });
|
|
13469
|
+
fallback++;
|
|
13470
|
+
}
|
|
13471
|
+
}
|
|
13472
|
+
return pos;
|
|
13473
|
+
}
|
|
13474
|
+
function withAutoLinks(ast) {
|
|
13475
|
+
if (ast.layout !== "spine-leaf" || ast.spines.length === 0 || ast.leaves.length === 0) {
|
|
13476
|
+
return ast.links;
|
|
13477
|
+
}
|
|
13478
|
+
const have = new Set(ast.links.map((l) => [l.from, l.to].sort().join("\u2194")));
|
|
13479
|
+
const extra = [];
|
|
13480
|
+
for (const s of ast.spines) {
|
|
13481
|
+
for (const lf of ast.leaves) {
|
|
13482
|
+
const key = [s, lf].sort().join("\u2194");
|
|
13483
|
+
if (!have.has(key)) {
|
|
13484
|
+
extra.push({ from: s, to: lf, directed: false, linkType: "copper", auto: true });
|
|
13485
|
+
have.add(key);
|
|
13486
|
+
}
|
|
13487
|
+
}
|
|
13488
|
+
}
|
|
13489
|
+
return [...ast.links, ...extra];
|
|
13490
|
+
}
|
|
13491
|
+
function classify(ast) {
|
|
13492
|
+
if (ast.devices.length === 2 && ast.links.length === 1) return "point-to-point";
|
|
13493
|
+
switch (ast.layout) {
|
|
13494
|
+
case "star":
|
|
13495
|
+
return "star";
|
|
13496
|
+
case "ring":
|
|
13497
|
+
return "ring";
|
|
13498
|
+
case "bus":
|
|
13499
|
+
return "bus";
|
|
13500
|
+
case "mesh":
|
|
13501
|
+
return "mesh";
|
|
13502
|
+
case "spine-leaf":
|
|
13503
|
+
return "spine-leaf";
|
|
13504
|
+
case "tree":
|
|
13505
|
+
return "tree";
|
|
13506
|
+
case "tiered":
|
|
13507
|
+
return ast.devices.some((d) => d.tier) ? "hierarchical" : "tree";
|
|
13508
|
+
default:
|
|
13509
|
+
return "general";
|
|
13510
|
+
}
|
|
13511
|
+
}
|
|
13512
|
+
function layoutNetwork2(ast) {
|
|
13513
|
+
const links = withAutoLinks(ast);
|
|
13514
|
+
let centers;
|
|
13515
|
+
switch (ast.layout) {
|
|
13516
|
+
case "tree":
|
|
13517
|
+
centers = placeBanded(ast, treeRanks(ast, links));
|
|
13518
|
+
break;
|
|
13519
|
+
case "star":
|
|
13520
|
+
centers = placeStar(ast, links);
|
|
13521
|
+
break;
|
|
13522
|
+
case "ring":
|
|
13523
|
+
centers = placeCircle(ast, NET_CONST.RING_RADIUS_MIN);
|
|
13524
|
+
break;
|
|
13525
|
+
case "mesh":
|
|
13526
|
+
centers = placeCircle(ast, NET_CONST.RING_RADIUS_MIN);
|
|
13527
|
+
break;
|
|
13528
|
+
case "bus":
|
|
13529
|
+
centers = placeBus(ast);
|
|
13530
|
+
break;
|
|
13531
|
+
case "spine-leaf":
|
|
13532
|
+
centers = placeSpineLeaf(ast, links);
|
|
13533
|
+
break;
|
|
13534
|
+
case "manual":
|
|
13535
|
+
centers = placeManual(ast);
|
|
13536
|
+
break;
|
|
13537
|
+
case "tiered":
|
|
13538
|
+
default:
|
|
13539
|
+
centers = placeBanded(ast, tieredRanks(ast, links));
|
|
13540
|
+
break;
|
|
13541
|
+
}
|
|
13542
|
+
const boxes = ast.devices.map((d) => {
|
|
13543
|
+
const fp = deviceFootprint(d);
|
|
13544
|
+
const c = centers.get(d.id) ?? { x: 0, y: 0 };
|
|
13545
|
+
return {
|
|
13546
|
+
device: d,
|
|
13547
|
+
cx: c.x,
|
|
13548
|
+
cy: c.y,
|
|
13549
|
+
x: c.x - fp.w / 2,
|
|
13550
|
+
y: c.y - fp.h / 2,
|
|
13551
|
+
w: fp.w,
|
|
13552
|
+
h: fp.h,
|
|
13553
|
+
band: 0
|
|
13554
|
+
};
|
|
13555
|
+
});
|
|
13556
|
+
const boxById = new Map(boxes.map((b) => [b.device.id, b]));
|
|
13557
|
+
const groupBoxesRaw = /* @__PURE__ */ new Map();
|
|
13558
|
+
const depthOf = (id) => {
|
|
13559
|
+
let depth = 0;
|
|
13560
|
+
let g = ast.groups.find((x) => x.id === id);
|
|
13561
|
+
while (g?.parent) {
|
|
13562
|
+
depth++;
|
|
13563
|
+
g = ast.groups.find((x) => x.id === g.parent);
|
|
13564
|
+
}
|
|
13565
|
+
return depth;
|
|
13566
|
+
};
|
|
13567
|
+
const groupsByDepth = [...ast.groups].sort((a, b) => depthOf(b.id) - depthOf(a.id));
|
|
13568
|
+
for (const g of groupsByDepth) {
|
|
13569
|
+
let l = Infinity, t = Infinity, r = -Infinity, bm = -Infinity;
|
|
13570
|
+
const addBox = (e) => {
|
|
13571
|
+
l = Math.min(l, e.left);
|
|
13572
|
+
t = Math.min(t, e.top);
|
|
13573
|
+
r = Math.max(r, e.right);
|
|
13574
|
+
bm = Math.max(bm, e.bottom);
|
|
13575
|
+
};
|
|
13576
|
+
for (const mid of g.members) {
|
|
13577
|
+
const mb = boxById.get(mid);
|
|
13578
|
+
if (mb) addBox(effBox(mb));
|
|
13579
|
+
}
|
|
13580
|
+
for (const cid of g.children) {
|
|
13581
|
+
const cb = groupBoxesRaw.get(cid);
|
|
13582
|
+
if (cb) addBox(cb);
|
|
13583
|
+
}
|
|
13584
|
+
if (l === Infinity) continue;
|
|
13585
|
+
const depth = depthOf(g.id);
|
|
13586
|
+
const pad = NET_CONST.GROUP_PAD;
|
|
13587
|
+
groupBoxesRaw.set(g.id, {
|
|
13588
|
+
left: l - pad,
|
|
13589
|
+
top: t - pad - NET_CONST.GROUP_HEADER,
|
|
13590
|
+
right: r + pad,
|
|
13591
|
+
bottom: bm + pad,
|
|
13592
|
+
depth
|
|
13593
|
+
});
|
|
13594
|
+
}
|
|
13595
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
13596
|
+
for (const b of boxes) {
|
|
13597
|
+
const e = effBox(b);
|
|
13598
|
+
minX = Math.min(minX, e.left);
|
|
13599
|
+
minY = Math.min(minY, e.top);
|
|
13600
|
+
maxX = Math.max(maxX, e.right);
|
|
13601
|
+
maxY = Math.max(maxY, e.bottom);
|
|
13602
|
+
}
|
|
13603
|
+
for (const gb of groupBoxesRaw.values()) {
|
|
13604
|
+
minX = Math.min(minX, gb.left);
|
|
13605
|
+
minY = Math.min(minY, gb.top);
|
|
13606
|
+
maxX = Math.max(maxX, gb.right);
|
|
13607
|
+
maxY = Math.max(maxY, gb.bottom);
|
|
13608
|
+
}
|
|
13609
|
+
if (!Number.isFinite(minX)) {
|
|
13610
|
+
minX = 0;
|
|
13611
|
+
minY = 0;
|
|
13612
|
+
maxX = NET_CONST.DEVICE_W;
|
|
13613
|
+
maxY = NET_CONST.DEVICE_H;
|
|
13614
|
+
}
|
|
13615
|
+
const dx = NET_CONST.PAD - minX;
|
|
13616
|
+
const dy = NET_CONST.PAD - minY;
|
|
13617
|
+
for (const b of boxes) {
|
|
13618
|
+
b.x += dx;
|
|
13619
|
+
b.y += dy;
|
|
13620
|
+
b.cx += dx;
|
|
13621
|
+
b.cy += dy;
|
|
13622
|
+
}
|
|
13623
|
+
const groups = [];
|
|
13624
|
+
for (const g of ast.groups) {
|
|
13625
|
+
const gb = groupBoxesRaw.get(g.id);
|
|
13626
|
+
if (!gb) continue;
|
|
13627
|
+
groups.push({
|
|
13628
|
+
group: g,
|
|
13629
|
+
x: gb.left + dx,
|
|
13630
|
+
y: gb.top + dy,
|
|
13631
|
+
w: gb.right - gb.left,
|
|
13632
|
+
h: gb.bottom - gb.top,
|
|
13633
|
+
depth: gb.depth
|
|
13634
|
+
});
|
|
13635
|
+
}
|
|
13636
|
+
const linkGeoms = links.map((link) => {
|
|
13637
|
+
const a = boxById.get(link.from);
|
|
13638
|
+
const b = boxById.get(link.to);
|
|
13639
|
+
const p1 = edgePoint(a, b.cx, b.cy);
|
|
13640
|
+
const p2 = edgePoint(b, a.cx, a.cy);
|
|
13641
|
+
return {
|
|
13642
|
+
link,
|
|
13643
|
+
points: [p1, p2],
|
|
13644
|
+
labelX: (p1.x + p2.x) / 2,
|
|
13645
|
+
labelY: (p1.y + p2.y) / 2
|
|
13646
|
+
};
|
|
13647
|
+
});
|
|
13648
|
+
const width = maxX - minX + 2 * NET_CONST.PAD;
|
|
13649
|
+
const height = maxY - minY + 2 * NET_CONST.PAD;
|
|
13650
|
+
return {
|
|
13651
|
+
ast,
|
|
13652
|
+
width: Math.max(width, 120),
|
|
13653
|
+
height: Math.max(height, 80),
|
|
13654
|
+
devices: boxes,
|
|
13655
|
+
links: linkGeoms,
|
|
13656
|
+
groups,
|
|
13657
|
+
topologyClass: classify(ast),
|
|
13658
|
+
warnings: ast.warnings,
|
|
13659
|
+
title: ast.title
|
|
13660
|
+
};
|
|
13661
|
+
}
|
|
13662
|
+
|
|
13663
|
+
// src/diagrams/network/renderer.ts
|
|
13664
|
+
var r22 = (n) => Math.round(n * 100) / 100;
|
|
13665
|
+
function buildCss7(t) {
|
|
13666
|
+
return `
|
|
13667
|
+
.sx-net { font-family: system-ui, -apple-system, sans-serif; }
|
|
13668
|
+
.sx-net-body { fill: ${t.deviceFill}; stroke: ${t.deviceStroke}; stroke-width: 2; }
|
|
13669
|
+
.sx-net-detail { fill: none; stroke: ${t.deviceStroke}; stroke-width: 1; }
|
|
13670
|
+
.sx-net-glyph { fill: ${t.deviceAccent}; stroke: none; }
|
|
13671
|
+
.sx-net-glyph-line { fill: none; stroke: ${t.deviceAccent}; stroke-width: 1.4; }
|
|
13672
|
+
.sx-net-icontext { font: 700 8px sans-serif; fill: ${t.deviceAccent}; }
|
|
13673
|
+
.sx-net-cloud-body { fill: ${t.cloudFill}; stroke: ${t.cloudStroke}; stroke-width: 2; }
|
|
13674
|
+
.sx-net-cloudtext { font: 600 13px sans-serif; fill: ${t.text}; }
|
|
13675
|
+
.sx-net-bus { stroke: ${t.deviceStroke}; stroke-width: 4; stroke-linecap: round; }
|
|
13676
|
+
.sx-net-label { font: 12px sans-serif; fill: ${t.label}; }
|
|
13677
|
+
.sx-net-sublabel { font: 10px sans-serif; fill: ${t.subLabel}; }
|
|
13678
|
+
.sx-net-link { fill: none; stroke-width: 2; }
|
|
13679
|
+
.sx-net-link-wireless, .sx-net-link-vpn { stroke-dasharray: 5 4; }
|
|
13680
|
+
.sx-net-link-lag { stroke-width: 3; }
|
|
13681
|
+
.sx-net-linklabel { font: 9px sans-serif; fill: ${t.linkLabel}; paint-order: stroke; stroke: ${t.bg}; stroke-width: 3px; stroke-linejoin: round; }
|
|
13682
|
+
.sx-net-port { font: 8px sans-serif; fill: ${t.linkLabel}; paint-order: stroke; stroke: ${t.bg}; stroke-width: 2.5px; stroke-linejoin: round; }
|
|
13683
|
+
.sx-net-boundary-site { fill: none; stroke: ${t.siteStroke}; stroke-width: 1.5; }
|
|
13684
|
+
.sx-net-boundary-rack { fill: none; stroke: ${t.siteStroke}; stroke-width: 1.5; stroke-dasharray: 1 2; }
|
|
13685
|
+
.sx-net-boundary-subnet, .sx-net-boundary-vlan { fill: ${t.subnetFill}; stroke: ${t.subnetStroke}; stroke-width: 1.2; stroke-dasharray: 5 3; }
|
|
13686
|
+
.sx-net-boundary-zone, .sx-net-boundary-dmz { fill: none; stroke: ${t.zoneStroke}; stroke-width: 1.2; stroke-dasharray: 5 3; }
|
|
13687
|
+
.sx-net-boundary-label { font: 600 10px sans-serif; }
|
|
13688
|
+
.sx-net-title { font: 700 16px sans-serif; fill: ${t.text}; }
|
|
13689
|
+
`.trim();
|
|
13690
|
+
}
|
|
13691
|
+
function linkColor(t, link) {
|
|
13692
|
+
if (link.linkType === "copper" && link.vlans && link.vlans.length === 1) {
|
|
13693
|
+
const vid = link.vlans[0];
|
|
13694
|
+
const pal = t.vlanPalette.filter((c) => c !== t.negative);
|
|
13695
|
+
const safe = pal.length ? pal : t.vlanPalette;
|
|
13696
|
+
return safe[vid % safe.length];
|
|
13697
|
+
}
|
|
13698
|
+
switch (link.linkType) {
|
|
13699
|
+
case "fiber":
|
|
13700
|
+
return t.linkFiber;
|
|
13701
|
+
case "wireless":
|
|
13702
|
+
return t.linkWireless;
|
|
13703
|
+
case "serial":
|
|
13704
|
+
return t.linkSerial;
|
|
13705
|
+
case "poe":
|
|
13706
|
+
return t.linkPoe;
|
|
13707
|
+
case "vpn":
|
|
13708
|
+
return t.linkVpn;
|
|
13709
|
+
case "lag":
|
|
13710
|
+
return t.linkLag;
|
|
13711
|
+
default:
|
|
13712
|
+
return t.linkCopper;
|
|
13713
|
+
}
|
|
10089
13714
|
}
|
|
10090
|
-
function
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
13715
|
+
function annotation(link) {
|
|
13716
|
+
const parts = [];
|
|
13717
|
+
if (link.mode) parts.push(link.mode === "trunk" ? "Trunk" : "Access");
|
|
13718
|
+
if (link.vlans?.length) parts.push(`VLAN ${link.vlans.join(",")}`);
|
|
13719
|
+
if (link.speed) parts.push(link.speed);
|
|
13720
|
+
if (link.linkType === "poe") parts.push("PoE");
|
|
13721
|
+
if (link.linkType === "vpn") parts.push("VPN");
|
|
13722
|
+
if (link.label) parts.push(link.label);
|
|
13723
|
+
return parts.join(" \xB7 ");
|
|
13724
|
+
}
|
|
13725
|
+
function arrowHead(x1, y1, x2, y2, color, hs = 6) {
|
|
13726
|
+
const ang = Math.atan2(y2 - y1, x2 - x1);
|
|
13727
|
+
const a1 = ang + Math.PI - 0.45;
|
|
13728
|
+
const a2 = ang + Math.PI + 0.45;
|
|
13729
|
+
return polygon({
|
|
13730
|
+
fill: color,
|
|
13731
|
+
points: `${r22(x2)},${r22(y2)} ${r22(x2 + hs * Math.cos(a1))},${r22(y2 + hs * Math.sin(a1))} ${r22(x2 + hs * Math.cos(a2))},${r22(y2 + hs * Math.sin(a2))}`
|
|
13732
|
+
});
|
|
10095
13733
|
}
|
|
10096
|
-
function
|
|
13734
|
+
function renderLink(lg, t) {
|
|
13735
|
+
const { link } = lg;
|
|
13736
|
+
const p1 = lg.points[0];
|
|
13737
|
+
const p2 = lg.points[lg.points.length - 1];
|
|
13738
|
+
const color = linkColor(t, link);
|
|
13739
|
+
const cls = `sx-net-link sx-net-link-${link.linkType}`;
|
|
10097
13740
|
const parts = [];
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
13741
|
+
if (link.linkType === "lag") {
|
|
13742
|
+
const ang = Math.atan2(p2.y - p1.y, p2.x - p1.x) + Math.PI / 2;
|
|
13743
|
+
const ox = 1.8 * Math.cos(ang);
|
|
13744
|
+
const oy = 1.8 * Math.sin(ang);
|
|
13745
|
+
parts.push(line({ class: cls, stroke: color, x1: r22(p1.x + ox), y1: r22(p1.y + oy), x2: r22(p2.x + ox), y2: r22(p2.y + oy) }));
|
|
13746
|
+
parts.push(line({ class: cls, stroke: color, x1: r22(p1.x - ox), y1: r22(p1.y - oy), x2: r22(p2.x - ox), y2: r22(p2.y - oy) }));
|
|
13747
|
+
} else {
|
|
13748
|
+
parts.push(el("polyline", { class: cls, stroke: color, points: lg.points.map((p) => `${r22(p.x)},${r22(p.y)}`).join(" ") }));
|
|
10102
13749
|
}
|
|
10103
|
-
parts.push(
|
|
10104
|
-
|
|
10105
|
-
const
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
);
|
|
13750
|
+
if (link.directed) parts.push(arrowHead(p1.x, p1.y, p2.x, p2.y, color));
|
|
13751
|
+
if (link.linkType === "fiber") {
|
|
13752
|
+
const ang = Math.atan2(p2.y - p1.y, p2.x - p1.x) + Math.PI / 2;
|
|
13753
|
+
for (const f of [0.4, 0.55]) {
|
|
13754
|
+
const mx = p1.x + (p2.x - p1.x) * f;
|
|
13755
|
+
const my = p1.y + (p2.y - p1.y) * f;
|
|
13756
|
+
parts.push(line({ class: cls, stroke: color, x1: r22(mx - 3 * Math.cos(ang)), y1: r22(my - 3 * Math.sin(ang)), x2: r22(mx + 3 * Math.cos(ang)), y2: r22(my + 3 * Math.sin(ang)) }));
|
|
10111
13757
|
}
|
|
10112
13758
|
}
|
|
10113
|
-
|
|
13759
|
+
const ann = annotation(link);
|
|
13760
|
+
if (ann) parts.push(text({ class: "sx-net-linklabel", x: r22(lg.labelX), y: r22(lg.labelY - 3), "text-anchor": "middle" }, ann));
|
|
13761
|
+
if (link.portNear) parts.push(text({ class: "sx-net-port", x: r22(p1.x + (p2.x - p1.x) * 0.16), y: r22(p1.y + (p2.y - p1.y) * 0.16 - 3), "text-anchor": "middle" }, link.portNear));
|
|
13762
|
+
if (link.portFar) parts.push(text({ class: "sx-net-port", x: r22(p2.x + (p1.x - p2.x) * 0.16), y: r22(p2.y + (p1.y - p2.y) * 0.16 - 3), "text-anchor": "middle" }, link.portFar));
|
|
13763
|
+
return group(
|
|
13764
|
+
{
|
|
13765
|
+
class: "sx-net-link-g",
|
|
13766
|
+
"data-from": link.from,
|
|
13767
|
+
"data-to": link.to,
|
|
13768
|
+
"data-type": link.linkType,
|
|
13769
|
+
...link.vlans?.length ? { "data-vlan": link.vlans.join(",") } : {},
|
|
13770
|
+
...link.speed ? { "data-speed": link.speed } : {},
|
|
13771
|
+
...link.mode ? { "data-mode": link.mode } : {}
|
|
13772
|
+
},
|
|
13773
|
+
parts
|
|
13774
|
+
);
|
|
10114
13775
|
}
|
|
10115
|
-
function
|
|
10116
|
-
const
|
|
10117
|
-
const
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
"data-task": a.taskId ?? "",
|
|
10129
|
-
"data-dummy": String(a.dummy),
|
|
10130
|
-
"data-critical": String(a.critical)
|
|
10131
|
-
})
|
|
10132
|
-
);
|
|
10133
|
-
if (!a.dummy && a.label) {
|
|
10134
|
-
const critCls = a.critical ? " critical" : "";
|
|
10135
|
-
const durStr = fmtVal(a.duration ?? 0);
|
|
10136
|
-
const w = Math.max(a.label.length * 6.2, durStr.length * 6) + 8;
|
|
10137
|
-
labelEls.push(
|
|
10138
|
-
rect({ class: "sx-pert-edge-halo", x: a.labelX - w / 2, y: a.labelY - 16, width: w, height: 31, rx: 3, ry: 3 })
|
|
10139
|
-
);
|
|
10140
|
-
labelEls.push(
|
|
10141
|
-
text({ class: `sx-pert-aoa-name${critCls}`, x: a.labelX, y: a.labelY - 4, "text-anchor": "middle" }, a.label)
|
|
10142
|
-
);
|
|
10143
|
-
labelEls.push(
|
|
10144
|
-
text({ class: `sx-pert-aoa-dur${critCls}`, x: a.labelX, y: a.labelY + 12, "text-anchor": "middle" }, durStr)
|
|
10145
|
-
);
|
|
10146
|
-
}
|
|
10147
|
-
}
|
|
10148
|
-
const eventEls = [];
|
|
10149
|
-
for (const e of aoa.events) {
|
|
10150
|
-
eventEls.push(
|
|
10151
|
-
group({ class: `sx-pert-aoa-event${e.critical ? " critical" : ""}`, "data-event": e.id }, [
|
|
10152
|
-
circle({ cx: e.x, cy: e.y, r: e.r }),
|
|
10153
|
-
text({ x: e.x, y: e.y + 4.5, "text-anchor": "middle" }, String(e.id))
|
|
10154
|
-
])
|
|
10155
|
-
);
|
|
10156
|
-
}
|
|
10157
|
-
return group({ class: "sx-pert-aoa" }, [
|
|
10158
|
-
group({ class: "sx-pert-aoa-arcs" }, arcEls),
|
|
10159
|
-
group({ class: "sx-pert-aoa-labels" }, labelEls),
|
|
10160
|
-
group({ class: "sx-pert-aoa-events" }, eventEls)
|
|
10161
|
-
]);
|
|
13776
|
+
function renderGroup(gb, t) {
|
|
13777
|
+
const k = gb.group.kind;
|
|
13778
|
+
const cls = `sx-net-boundary-${k}`;
|
|
13779
|
+
const labelColor = k === "zone" || k === "dmz" ? t.zoneStroke : k === "subnet" || k === "vlan" ? t.subnetStroke : t.siteStroke;
|
|
13780
|
+
const label = gb.group.label ?? gb.group.id;
|
|
13781
|
+
const tag = k === "vlan" ? `VLAN ${label}` : label;
|
|
13782
|
+
return group(
|
|
13783
|
+
{ class: "sx-net-boundary", "data-kind": k, "data-label": escapeXml(label) },
|
|
13784
|
+
[
|
|
13785
|
+
rect({ class: cls, x: r22(gb.x), y: r22(gb.y), width: r22(gb.w), height: r22(gb.h), rx: 8, ry: 8 }),
|
|
13786
|
+
text({ class: "sx-net-boundary-label", fill: labelColor, x: r22(gb.x + NET_CONST.GROUP_LABEL_INSET), y: r22(gb.y + 13) }, tag)
|
|
13787
|
+
]
|
|
13788
|
+
);
|
|
10162
13789
|
}
|
|
10163
|
-
function
|
|
10164
|
-
const
|
|
10165
|
-
const
|
|
10166
|
-
|
|
10167
|
-
|
|
10168
|
-
|
|
10169
|
-
|
|
13790
|
+
function renderDevice(b, t) {
|
|
13791
|
+
const d = b.device;
|
|
13792
|
+
const parts = [drawDeviceIcon(d, { x: b.x, y: b.y, w: b.w, h: b.h })];
|
|
13793
|
+
if (!isCloudKind(d.kind)) {
|
|
13794
|
+
const labelY = b.y + b.h + NET_CONST.LABEL_GAP + 11;
|
|
13795
|
+
parts.push(text({ class: "sx-net-label", x: r22(b.cx), y: r22(labelY), "text-anchor": "middle" }, d.label ?? d.id));
|
|
13796
|
+
const sub = d.ip ?? d.model;
|
|
13797
|
+
if (sub) parts.push(text({ class: "sx-net-sublabel", x: r22(b.cx), y: r22(labelY + NET_CONST.SUBLABEL_H), "text-anchor": "middle" }, sub));
|
|
13798
|
+
}
|
|
13799
|
+
const attrs = {
|
|
13800
|
+
class: "sx-net-device",
|
|
13801
|
+
"data-id": d.id,
|
|
13802
|
+
"data-kind": d.kind
|
|
13803
|
+
};
|
|
13804
|
+
if (d.tier) attrs["data-tier"] = d.tier;
|
|
13805
|
+
if (d.ip) attrs["data-ip"] = d.ip;
|
|
13806
|
+
if (d.cameraType) attrs["data-type"] = d.cameraType;
|
|
13807
|
+
return group(attrs, parts);
|
|
10170
13808
|
}
|
|
10171
|
-
function
|
|
10172
|
-
const t =
|
|
13809
|
+
function renderNetworkLayout(layout, config) {
|
|
13810
|
+
const t = resolveNetworkTheme(config?.theme ?? "default");
|
|
10173
13811
|
const children = [];
|
|
10174
|
-
const
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
13812
|
+
const counts = /* @__PURE__ */ new Map();
|
|
13813
|
+
for (const b of layout.devices) counts.set(b.device.kind, (counts.get(b.device.kind) ?? 0) + 1);
|
|
13814
|
+
const linkTypes = /* @__PURE__ */ new Map();
|
|
13815
|
+
for (const l of layout.links) linkTypes.set(l.link.linkType, (linkTypes.get(l.link.linkType) ?? 0) + 1);
|
|
13816
|
+
const descParts = [
|
|
13817
|
+
`${layout.devices.length} devices, ${layout.links.length} links, ${layout.groups.length} boundaries.`,
|
|
13818
|
+
`Topology: ${layout.topologyClass}.`,
|
|
13819
|
+
linkTypes.size ? `Links: ${[...linkTypes].map(([k, n]) => `${n} ${k}`).join(", ")}.` : "",
|
|
13820
|
+
layout.warnings.length ? `Warnings: ${layout.warnings.join("; ")}.` : ""
|
|
13821
|
+
].filter(Boolean);
|
|
13822
|
+
children.push(title(`Network diagram${layout.title ? " \u2014 " + layout.title : ""}`));
|
|
13823
|
+
children.push(desc(descParts.join(" ")));
|
|
13824
|
+
children.push(el("style", {}, buildCss7(t)));
|
|
13825
|
+
const titleBand = layout.title ? 30 : 0;
|
|
10183
13826
|
if (layout.title) {
|
|
10184
|
-
children.push(
|
|
10185
|
-
text({ x: layout.width / 2, y: 26, class: "sx-pert-title", "text-anchor": "middle" }, layout.title)
|
|
10186
|
-
);
|
|
10187
|
-
}
|
|
10188
|
-
if (layout.aoa) {
|
|
10189
|
-
children.push(renderAoa(layout.aoa));
|
|
10190
|
-
} else {
|
|
10191
|
-
if (layout.lanes && layout.lanes.length) {
|
|
10192
|
-
children.push(renderLanes(layout.lanes, layout.width));
|
|
10193
|
-
}
|
|
10194
|
-
if (layout.axis) {
|
|
10195
|
-
let minBoxY = Infinity;
|
|
10196
|
-
for (const b of layout.boxes) minBoxY = Math.min(minBoxY, b.y);
|
|
10197
|
-
children.push(renderAxis2(layout.axis, isFinite(minBoxY) ? minBoxY : layout.axis.baseline - 20));
|
|
10198
|
-
}
|
|
10199
|
-
const edgeEls = [];
|
|
10200
|
-
for (const e of layout.edges) edgeEls.push(renderEdge3(e));
|
|
10201
|
-
children.push(group({ class: "sx-pert-edges" }, edgeEls));
|
|
10202
|
-
if (layout.sentinels.length) {
|
|
10203
|
-
children.push(group({ class: "sx-pert-sentinels" }, layout.sentinels.map(renderSentinel)));
|
|
10204
|
-
}
|
|
10205
|
-
const boxEls = [];
|
|
10206
|
-
for (const b of layout.boxes) boxEls.push(renderBox2(b, layout.mode));
|
|
10207
|
-
children.push(group({ class: "sx-pert-tasks" }, boxEls));
|
|
10208
|
-
const labelEls = [];
|
|
10209
|
-
for (const e of layout.edges) {
|
|
10210
|
-
const lbl = renderEdgeLabel2(e);
|
|
10211
|
-
if (lbl) labelEls.push(lbl);
|
|
10212
|
-
}
|
|
10213
|
-
children.push(group({ class: "sx-pert-labels" }, labelEls));
|
|
13827
|
+
children.push(text({ x: r22(layout.width / 2), y: 21, class: "sx-net-title", "text-anchor": "middle" }, layout.title));
|
|
10214
13828
|
}
|
|
10215
|
-
const
|
|
10216
|
-
const
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
text(
|
|
10223
|
-
{ class: "sx-pert-summary crit", x: layout.width - PERT_PAD, y: footerY, "text-anchor": "end" },
|
|
10224
|
-
`Critical path: ${critPath}`
|
|
10225
|
-
)
|
|
10226
|
-
);
|
|
10227
|
-
}
|
|
10228
|
-
children.push(group({ class: "sx-pert-footer" }, footerParts));
|
|
13829
|
+
const body = [];
|
|
13830
|
+
const sortedGroups = [...layout.groups].sort((a, b) => a.depth - b.depth);
|
|
13831
|
+
body.push(group({ class: "sx-net-boundaries" }, sortedGroups.map((g) => renderGroup(g, t))));
|
|
13832
|
+
body.push(group({ class: "sx-net-links" }, layout.links.map((l) => renderLink(l, t))));
|
|
13833
|
+
body.push(group({ class: "sx-net-devices" }, layout.devices.map((b) => renderDevice(b))));
|
|
13834
|
+
children.push(titleBand ? group({ transform: `translate(0, ${titleBand})` }, body) : group({}, body));
|
|
13835
|
+
const height = layout.height + titleBand;
|
|
10229
13836
|
return svgRoot(
|
|
10230
13837
|
{
|
|
10231
|
-
class: "sx-
|
|
13838
|
+
class: "sx-net",
|
|
10232
13839
|
role: "img",
|
|
10233
|
-
"aria-label": escapeXml(layout.title ?? "
|
|
10234
|
-
width: layout.width,
|
|
10235
|
-
height:
|
|
10236
|
-
viewBox: `0 0 ${layout.width} ${
|
|
10237
|
-
"data-diagram-type": "
|
|
13840
|
+
"aria-label": escapeXml(layout.title ?? "Network diagram"),
|
|
13841
|
+
width: r22(layout.width),
|
|
13842
|
+
height: r22(height),
|
|
13843
|
+
viewBox: `0 0 ${r22(layout.width)} ${r22(height)}`,
|
|
13844
|
+
"data-diagram-type": "network"
|
|
10238
13845
|
},
|
|
10239
13846
|
children
|
|
10240
13847
|
);
|
|
10241
13848
|
}
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
const
|
|
10245
|
-
|
|
10246
|
-
const layout = layoutPert(ast, schedule);
|
|
10247
|
-
return renderPertLayout(layout, config);
|
|
13849
|
+
function renderNetwork(textOrAst, config) {
|
|
13850
|
+
const ast = typeof textOrAst === "string" ? parseNetwork(textOrAst) : textOrAst;
|
|
13851
|
+
const layout = layoutNetwork2(ast);
|
|
13852
|
+
return renderNetworkLayout(layout, config);
|
|
10248
13853
|
}
|
|
10249
13854
|
|
|
10250
|
-
// src/diagrams/
|
|
10251
|
-
var
|
|
10252
|
-
type: "
|
|
13855
|
+
// src/diagrams/network/index.ts
|
|
13856
|
+
var network = {
|
|
13857
|
+
type: "network",
|
|
10253
13858
|
detect(text2) {
|
|
10254
13859
|
for (const raw of text2.split(/\r?\n/)) {
|
|
10255
13860
|
const t = raw.trim();
|
|
10256
13861
|
if (!t) continue;
|
|
10257
13862
|
if (t.startsWith("#") || t.startsWith("//")) continue;
|
|
10258
|
-
return /^
|
|
13863
|
+
return /^(network|topology)\b/i.test(t);
|
|
10259
13864
|
}
|
|
10260
13865
|
return false;
|
|
10261
13866
|
},
|
|
10262
|
-
parse:
|
|
13867
|
+
parse: parseNetwork,
|
|
10263
13868
|
render(text2, config) {
|
|
10264
|
-
return
|
|
13869
|
+
return renderNetwork(text2, config);
|
|
10265
13870
|
}
|
|
10266
13871
|
};
|
|
10267
13872
|
|
|
13873
|
+
// src/core/diagnostics.ts
|
|
13874
|
+
var ENGINE_BUG_NAMES = /* @__PURE__ */ new Set([
|
|
13875
|
+
"ReferenceError",
|
|
13876
|
+
"TypeError",
|
|
13877
|
+
"RangeError"
|
|
13878
|
+
]);
|
|
13879
|
+
function diagnosticFromError(err) {
|
|
13880
|
+
if (err instanceof Error) {
|
|
13881
|
+
const anyErr = err;
|
|
13882
|
+
const hasParseFields = typeof anyErr.line === "number";
|
|
13883
|
+
const isEngineBug = !hasParseFields && ENGINE_BUG_NAMES.has(err.name);
|
|
13884
|
+
const source = typeof anyErr.source === "string" ? anyErr.source : isEngineBug ? firstStackFrame(err.stack) : void 0;
|
|
13885
|
+
return {
|
|
13886
|
+
severity: "error",
|
|
13887
|
+
code: isEngineBug ? "ENGINE_BUG" : "DSL_INVALID",
|
|
13888
|
+
line: typeof anyErr.line === "number" ? anyErr.line : void 0,
|
|
13889
|
+
column: typeof anyErr.column === "number" ? anyErr.column : void 0,
|
|
13890
|
+
source,
|
|
13891
|
+
message: isEngineBug ? `[engine bug: ${err.name}] ${err.message}` : err.message,
|
|
13892
|
+
hint: typeof anyErr.hint === "string" ? anyErr.hint : isEngineBug ? "This looks like a Schematex internal error rather than a DSL syntax problem. Keep the failing DSL and file an issue." : void 0,
|
|
13893
|
+
fatal: true
|
|
13894
|
+
};
|
|
13895
|
+
}
|
|
13896
|
+
return {
|
|
13897
|
+
severity: "error",
|
|
13898
|
+
code: "UNKNOWN_THROW",
|
|
13899
|
+
message: String(err),
|
|
13900
|
+
fatal: true
|
|
13901
|
+
};
|
|
13902
|
+
}
|
|
13903
|
+
function renderDiagnosticSvg(diagnostics, type, config = {}) {
|
|
13904
|
+
const headline = type ? `${type} preview could not be rendered` : "Diagram preview could not be rendered";
|
|
13905
|
+
const detail = diagnostics[0]?.message ?? "Schematex could not parse this DSL.";
|
|
13906
|
+
const lines = wrapText3(detail, 88).slice(0, 5);
|
|
13907
|
+
const source = diagnostics[0]?.source ? wrapText3(`Source: ${diagnostics[0].source}`, 88).slice(0, 2) : [];
|
|
13908
|
+
const foot = "Strict validation failed before a diagram could be drawn.";
|
|
13909
|
+
const width = 760;
|
|
13910
|
+
const lineHeight = 20;
|
|
13911
|
+
const height = 174 + (lines.length + source.length) * lineHeight;
|
|
13912
|
+
const fontFamily = config.fontFamily ?? "system-ui, -apple-system, sans-serif";
|
|
13913
|
+
const children = [
|
|
13914
|
+
title(headline),
|
|
13915
|
+
desc(`${headline}. ${detail}`),
|
|
13916
|
+
el(
|
|
13917
|
+
"style",
|
|
13918
|
+
{},
|
|
13919
|
+
`
|
|
13920
|
+
.schematex-preview-error { font-family: ${escapeXml(fontFamily)}; }
|
|
13921
|
+
.schematex-preview-error-frame { fill: #fff7ed; stroke: #c2410c; stroke-width: 1.5; }
|
|
13922
|
+
.schematex-preview-error-mark { fill: #c2410c; }
|
|
13923
|
+
.schematex-preview-error-title { fill: #7c2d12; font-size: 18px; font-weight: 600; }
|
|
13924
|
+
.schematex-preview-error-copy { fill: #431407; font-size: 13px; }
|
|
13925
|
+
.schematex-preview-error-muted { fill: #9a3412; font-size: 12px; }
|
|
13926
|
+
`
|
|
13927
|
+
),
|
|
13928
|
+
el("g", { class: "schematex-preview-error" }, [
|
|
13929
|
+
rect({
|
|
13930
|
+
x: 1,
|
|
13931
|
+
y: 1,
|
|
13932
|
+
width: width - 2,
|
|
13933
|
+
height: height - 2,
|
|
13934
|
+
rx: 6,
|
|
13935
|
+
class: "schematex-preview-error-frame"
|
|
13936
|
+
}),
|
|
13937
|
+
rect({
|
|
13938
|
+
x: 28,
|
|
13939
|
+
y: 30,
|
|
13940
|
+
width: 8,
|
|
13941
|
+
height: 34,
|
|
13942
|
+
rx: 4,
|
|
13943
|
+
class: "schematex-preview-error-mark"
|
|
13944
|
+
}),
|
|
13945
|
+
rect({
|
|
13946
|
+
x: 28,
|
|
13947
|
+
y: 72,
|
|
13948
|
+
width: 8,
|
|
13949
|
+
height: 8,
|
|
13950
|
+
rx: 4,
|
|
13951
|
+
class: "schematex-preview-error-mark"
|
|
13952
|
+
}),
|
|
13953
|
+
text(
|
|
13954
|
+
{ x: 58, y: 49, class: "schematex-preview-error-title" },
|
|
13955
|
+
headline
|
|
13956
|
+
),
|
|
13957
|
+
text(
|
|
13958
|
+
{ x: 58, y: 76, class: "schematex-preview-error-muted" },
|
|
13959
|
+
"The DSL is still available to repair or retry."
|
|
13960
|
+
),
|
|
13961
|
+
...renderRows(lines, 58, 112, lineHeight, "schematex-preview-error-copy"),
|
|
13962
|
+
...renderRows(
|
|
13963
|
+
source,
|
|
13964
|
+
58,
|
|
13965
|
+
112 + lines.length * lineHeight + 10,
|
|
13966
|
+
lineHeight,
|
|
13967
|
+
"schematex-preview-error-muted"
|
|
13968
|
+
),
|
|
13969
|
+
text(
|
|
13970
|
+
{ x: 58, y: height - 28, class: "schematex-preview-error-muted" },
|
|
13971
|
+
foot
|
|
13972
|
+
)
|
|
13973
|
+
])
|
|
13974
|
+
];
|
|
13975
|
+
return svgRoot(
|
|
13976
|
+
{
|
|
13977
|
+
width,
|
|
13978
|
+
height,
|
|
13979
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
13980
|
+
role: "img",
|
|
13981
|
+
"aria-label": headline,
|
|
13982
|
+
"data-schematex-status": "invalid"
|
|
13983
|
+
},
|
|
13984
|
+
children
|
|
13985
|
+
);
|
|
13986
|
+
}
|
|
13987
|
+
function renderRows(rows, x, y, lineHeight, className) {
|
|
13988
|
+
return rows.map(
|
|
13989
|
+
(row, idx) => text({ x, y: y + idx * lineHeight, class: className }, row)
|
|
13990
|
+
);
|
|
13991
|
+
}
|
|
13992
|
+
function wrapText3(value, max) {
|
|
13993
|
+
const words = value.replace(/\s+/g, " ").trim().split(" ");
|
|
13994
|
+
const rows = [];
|
|
13995
|
+
let current = "";
|
|
13996
|
+
for (const word of words) {
|
|
13997
|
+
if (!current) {
|
|
13998
|
+
current = word;
|
|
13999
|
+
continue;
|
|
14000
|
+
}
|
|
14001
|
+
if (current.length + word.length + 1 <= max) {
|
|
14002
|
+
current += ` ${word}`;
|
|
14003
|
+
continue;
|
|
14004
|
+
}
|
|
14005
|
+
rows.push(current);
|
|
14006
|
+
current = word;
|
|
14007
|
+
}
|
|
14008
|
+
if (current) rows.push(current);
|
|
14009
|
+
return rows.length > 0 ? rows : [value];
|
|
14010
|
+
}
|
|
14011
|
+
function firstStackFrame(stack) {
|
|
14012
|
+
if (!stack) return void 0;
|
|
14013
|
+
for (const line2 of stack.split("\n")) {
|
|
14014
|
+
const trimmed = line2.trim();
|
|
14015
|
+
if (trimmed.startsWith("at ")) {
|
|
14016
|
+
return trimmed.replace(/\((?:.*\/)?([^/]+)\)/, "($1)");
|
|
14017
|
+
}
|
|
14018
|
+
}
|
|
14019
|
+
return void 0;
|
|
14020
|
+
}
|
|
14021
|
+
|
|
10268
14022
|
// src/diagrams/mindmap/inline.ts
|
|
10269
14023
|
var RE_TASK = /^\[( |x|X)\]\s+(.*)$/;
|
|
10270
14024
|
function tokenizeInline(raw) {
|
|
@@ -11234,7 +14988,7 @@ function newAST() {
|
|
|
11234
14988
|
config: { ...DEFAULT_CONFIG }
|
|
11235
14989
|
};
|
|
11236
14990
|
}
|
|
11237
|
-
function
|
|
14991
|
+
function stripQuotes4(s) {
|
|
11238
14992
|
const t = s.trim();
|
|
11239
14993
|
if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
|
|
11240
14994
|
return t.slice(1, -1);
|
|
@@ -11255,11 +15009,11 @@ function readQuoted2(line2, from) {
|
|
|
11255
15009
|
function parseAxis(raw) {
|
|
11256
15010
|
const arrowMatch = raw.match(/\s*(→|↑|->|>|↓|←|<-|<)\s*/);
|
|
11257
15011
|
if (arrowMatch) {
|
|
11258
|
-
const
|
|
15012
|
+
const arrow2 = arrowMatch[1];
|
|
11259
15013
|
const idx = arrowMatch.index;
|
|
11260
15014
|
const left = raw.slice(0, idx).trim();
|
|
11261
15015
|
const right = raw.slice(idx + arrowMatch[0].length).trim();
|
|
11262
|
-
const reversed =
|
|
15016
|
+
const reversed = arrow2 === "\u2190" || arrow2 === "<-" || arrow2 === "<";
|
|
11263
15017
|
if (reversed) {
|
|
11264
15018
|
return { low: right, high: left, reversed: true };
|
|
11265
15019
|
}
|
|
@@ -11270,7 +15024,7 @@ function parseAxis(raw) {
|
|
|
11270
15024
|
function parseNumberList2(raw) {
|
|
11271
15025
|
const t = raw.trim();
|
|
11272
15026
|
const inner = t.startsWith("[") && t.endsWith("]") ? t.slice(1, -1) : t;
|
|
11273
|
-
return inner.split(",").map((s) =>
|
|
15027
|
+
return inner.split(",").map((s) => stripQuotes4(s.trim())).filter((s) => s.length > 0);
|
|
11274
15028
|
}
|
|
11275
15029
|
function parseProperties2(raw, point) {
|
|
11276
15030
|
let i = 0;
|
|
@@ -11385,7 +15139,7 @@ function parseHeader2(line2, ast) {
|
|
|
11385
15139
|
ast.cols = Number(heatMatch[1]);
|
|
11386
15140
|
ast.rows = Number(heatMatch[2]);
|
|
11387
15141
|
const title2 = heatMatch[3].trim();
|
|
11388
|
-
if (title2) ast.title =
|
|
15142
|
+
if (title2) ast.title = stripQuotes4(title2);
|
|
11389
15143
|
return void 0;
|
|
11390
15144
|
}
|
|
11391
15145
|
const corrMatch = rest.match(/^correlation\s*(?:(\d+)\s*x\s*(\d+))?\s*(.*)$/i);
|
|
@@ -11397,7 +15151,7 @@ function parseHeader2(line2, ast) {
|
|
|
11397
15151
|
ast.rows = Number(corrMatch[2]);
|
|
11398
15152
|
}
|
|
11399
15153
|
const title2 = corrMatch[3].trim();
|
|
11400
|
-
if (title2) ast.title =
|
|
15154
|
+
if (title2) ast.title = stripQuotes4(title2);
|
|
11401
15155
|
return void 0;
|
|
11402
15156
|
}
|
|
11403
15157
|
const tokenMatch = rest.match(/^([a-zA-Z0-9_-]+)\s*(.*)$/);
|
|
@@ -11405,13 +15159,13 @@ function parseHeader2(line2, ast) {
|
|
|
11405
15159
|
const tok = tokenMatch[1].toLowerCase();
|
|
11406
15160
|
const remainder = tokenMatch[2].trim();
|
|
11407
15161
|
if (TEMPLATE_NAMES.has(tok)) {
|
|
11408
|
-
if (remainder) ast.title =
|
|
15162
|
+
if (remainder) ast.title = stripQuotes4(remainder);
|
|
11409
15163
|
return tok;
|
|
11410
15164
|
}
|
|
11411
15165
|
if (rest.startsWith('"') || rest.startsWith("'")) {
|
|
11412
|
-
ast.title =
|
|
15166
|
+
ast.title = stripQuotes4(rest);
|
|
11413
15167
|
} else if (rest.length > 0) {
|
|
11414
|
-
ast.title =
|
|
15168
|
+
ast.title = stripQuotes4(rest);
|
|
11415
15169
|
}
|
|
11416
15170
|
}
|
|
11417
15171
|
return void 0;
|
|
@@ -11447,7 +15201,7 @@ function parseMatrix(text2) {
|
|
|
11447
15201
|
inConfig = false;
|
|
11448
15202
|
}
|
|
11449
15203
|
if (/^title\s*:/i.test(line2)) {
|
|
11450
|
-
st.ast.title =
|
|
15204
|
+
st.ast.title = stripQuotes4(line2.replace(/^title\s*:\s*/i, ""));
|
|
11451
15205
|
continue;
|
|
11452
15206
|
}
|
|
11453
15207
|
if (/^x-axis\s*:/i.test(line2)) {
|
|
@@ -11532,7 +15286,7 @@ function parseMatrix(text2) {
|
|
|
11532
15286
|
if (qShort) {
|
|
11533
15287
|
const q = Number(qShort[1]);
|
|
11534
15288
|
const labelRaw = qShort[2].trim();
|
|
11535
|
-
const label =
|
|
15289
|
+
const label = stripQuotes4(labelRaw);
|
|
11536
15290
|
const cell = quadrantToCell(q);
|
|
11537
15291
|
st.ast.cellLabels.push({ col: cell.col, row: cell.row, label });
|
|
11538
15292
|
continue;
|
|
@@ -11574,11 +15328,11 @@ var CANVAS_W = 720;
|
|
|
11574
15328
|
var CANVAS_H = 560;
|
|
11575
15329
|
var PADDING_X = 110;
|
|
11576
15330
|
var PADDING_Y = 90;
|
|
11577
|
-
var
|
|
15331
|
+
var CHAR_W2 = 6.2;
|
|
11578
15332
|
var LABEL_H = 14;
|
|
11579
15333
|
function estimateWidth(text2) {
|
|
11580
15334
|
const cjk = (text2.match(/[\u3000-\u9fff]/g) ?? []).length;
|
|
11581
|
-
return (text2.length - cjk) *
|
|
15335
|
+
return (text2.length - cjk) * CHAR_W2 + cjk * 12 + 8;
|
|
11582
15336
|
}
|
|
11583
15337
|
function clamp01(v) {
|
|
11584
15338
|
return Math.max(0.02, Math.min(0.98, v));
|
|
@@ -12629,7 +16383,7 @@ var ErdParseError = class extends Error {
|
|
|
12629
16383
|
}
|
|
12630
16384
|
lineNumber;
|
|
12631
16385
|
};
|
|
12632
|
-
function
|
|
16386
|
+
function stripComment3(s) {
|
|
12633
16387
|
let out = "";
|
|
12634
16388
|
let inQuote = false;
|
|
12635
16389
|
for (let i = 0; i < s.length; i++) {
|
|
@@ -12683,7 +16437,7 @@ function parseCardToken(raw, side) {
|
|
|
12683
16437
|
}
|
|
12684
16438
|
function lex(text2) {
|
|
12685
16439
|
return text2.split(/\r?\n/).map((raw, i) => ({
|
|
12686
|
-
text:
|
|
16440
|
+
text: stripComment3(raw).trim(),
|
|
12687
16441
|
lineNumber: i + 1
|
|
12688
16442
|
})).filter((l) => l.text.length > 0);
|
|
12689
16443
|
}
|
|
@@ -13334,7 +17088,7 @@ function sideAnchor(e, side, col) {
|
|
|
13334
17088
|
}
|
|
13335
17089
|
|
|
13336
17090
|
// src/diagrams/erd/renderer.ts
|
|
13337
|
-
function
|
|
17091
|
+
function buildCss8(t) {
|
|
13338
17092
|
return `
|
|
13339
17093
|
.lt-erd { font-family: system-ui, -apple-system, sans-serif; }
|
|
13340
17094
|
.lt-erd-title { font: bold 16px sans-serif; fill: ${t.text}; }
|
|
@@ -13420,10 +17174,10 @@ function renderEntity(e) {
|
|
|
13420
17174
|
a.name
|
|
13421
17175
|
)
|
|
13422
17176
|
);
|
|
13423
|
-
const
|
|
17177
|
+
const markers5 = attrMarkers(a);
|
|
13424
17178
|
const markerW = 26;
|
|
13425
17179
|
const markerGap = 4;
|
|
13426
|
-
const markersBlockW =
|
|
17180
|
+
const markersBlockW = markers5.length * markerW + (markers5.length - 1) * markerGap;
|
|
13427
17181
|
const markersStartX = e.x + e.width - C2.ENTITY_PADDING_X - markersBlockW;
|
|
13428
17182
|
if (a.type) {
|
|
13429
17183
|
body.push(
|
|
@@ -13438,8 +17192,8 @@ function renderEntity(e) {
|
|
|
13438
17192
|
)
|
|
13439
17193
|
);
|
|
13440
17194
|
}
|
|
13441
|
-
for (let j = 0; j <
|
|
13442
|
-
const m =
|
|
17195
|
+
for (let j = 0; j < markers5.length; j++) {
|
|
17196
|
+
const m = markers5[j];
|
|
13443
17197
|
const px = markersStartX + j * (markerW + markerGap);
|
|
13444
17198
|
body.push(
|
|
13445
17199
|
rect({
|
|
@@ -13644,7 +17398,7 @@ function renderEdge4(edge) {
|
|
|
13644
17398
|
function renderErdAst(result, themeName = "default") {
|
|
13645
17399
|
const theme = resolveBaseTheme(themeName);
|
|
13646
17400
|
const { entities, edges, width, height, ast } = result;
|
|
13647
|
-
const cssBlock = el("style", {},
|
|
17401
|
+
const cssBlock = el("style", {}, buildCss8(theme));
|
|
13648
17402
|
const titleNode = title(ast.title ?? "Schematex ERD");
|
|
13649
17403
|
const descNode = desc(
|
|
13650
17404
|
`Entity-Relationship Diagram with ${entities.length} entities and ${edges.length} relationships.`
|
|
@@ -13711,7 +17465,7 @@ var BreadboardParseError = class extends Error {
|
|
|
13711
17465
|
}
|
|
13712
17466
|
lineNumber;
|
|
13713
17467
|
};
|
|
13714
|
-
function
|
|
17468
|
+
function stripComment4(s) {
|
|
13715
17469
|
let out = "";
|
|
13716
17470
|
let inQuote = false;
|
|
13717
17471
|
for (let i = 0; i < s.length; i++) {
|
|
@@ -13732,7 +17486,7 @@ function unquote6(v) {
|
|
|
13732
17486
|
}
|
|
13733
17487
|
function lex2(text2) {
|
|
13734
17488
|
return text2.split(/\r?\n/).map((raw, i) => ({
|
|
13735
|
-
text:
|
|
17489
|
+
text: stripComment4(raw).trimEnd().replace(/^\s+/, ""),
|
|
13736
17490
|
lineNumber: i + 1
|
|
13737
17491
|
})).filter((l) => l.text.length > 0);
|
|
13738
17492
|
}
|
|
@@ -13779,7 +17533,7 @@ function parsePlacement(rest, lineNumber) {
|
|
|
13779
17533
|
}
|
|
13780
17534
|
return { kind: "point", at: parseCoord(body, lineNumber) };
|
|
13781
17535
|
}
|
|
13782
|
-
var
|
|
17536
|
+
var KIND_ALIASES2 = {
|
|
13783
17537
|
resistor: "resistor",
|
|
13784
17538
|
led: "led",
|
|
13785
17539
|
cap: "cap-elec",
|
|
@@ -13844,7 +17598,7 @@ function resolveKind(tokens, lineNumber) {
|
|
|
13844
17598
|
if (!ACTUATOR_SUBTYPES[sub]) throw new BreadboardParseError(`Unknown actuator subtype '${sub}'`, lineNumber);
|
|
13845
17599
|
return { kind: ACTUATOR_SUBTYPES[sub], consumed: 2 };
|
|
13846
17600
|
}
|
|
13847
|
-
if (
|
|
17601
|
+
if (KIND_ALIASES2[head]) return { kind: KIND_ALIASES2[head], consumed: 1 };
|
|
13848
17602
|
throw new BreadboardParseError(`Unknown part kind '${head}'`, lineNumber);
|
|
13849
17603
|
}
|
|
13850
17604
|
function parsePart(rawLine, lineNumber) {
|
|
@@ -14714,7 +18468,7 @@ var WIRE_COLOR_MAP = {
|
|
|
14714
18468
|
brown: "#78350f",
|
|
14715
18469
|
grey: "#64748b"
|
|
14716
18470
|
};
|
|
14717
|
-
function
|
|
18471
|
+
function buildCss9(t) {
|
|
14718
18472
|
return `
|
|
14719
18473
|
.lt-bb { font-family: system-ui, -apple-system, sans-serif; }
|
|
14720
18474
|
.lt-bb-title { font: 600 16px sans-serif; fill: ${t.text}; }
|
|
@@ -14828,12 +18582,12 @@ function renderSubstrate(sub) {
|
|
|
14828
18582
|
function renderPart(lp) {
|
|
14829
18583
|
const spec = partSpec(lp.part.kind, lp.part.args);
|
|
14830
18584
|
const body = spec.body(lp.part, lp.width, lp.height);
|
|
14831
|
-
const
|
|
14832
|
-
const labelEl =
|
|
18585
|
+
const labelText2 = lp.part.label ?? defaultPartLabel(lp);
|
|
18586
|
+
const labelEl = labelText2 ? text({
|
|
14833
18587
|
x: lp.x + lp.width / 2,
|
|
14834
18588
|
y: spec.category === "side" ? lp.y - 6 : lp.y - 4,
|
|
14835
18589
|
class: "lt-bb-part-label"
|
|
14836
|
-
},
|
|
18590
|
+
}, labelText2) : "";
|
|
14837
18591
|
return group(
|
|
14838
18592
|
{ class: `lt-bb-part lt-bb-part-${lp.part.kind}`, transform: `translate(${lp.x.toFixed(2)} ${lp.y.toFixed(2)})` },
|
|
14839
18593
|
[body]
|
|
@@ -14862,7 +18616,7 @@ function renderWire(lw) {
|
|
|
14862
18616
|
}
|
|
14863
18617
|
function renderBreadboardLayout(layout, config) {
|
|
14864
18618
|
const theme = resolveBaseTheme(config?.theme ?? "default");
|
|
14865
|
-
const css =
|
|
18619
|
+
const css = buildCss9(theme);
|
|
14866
18620
|
const titleStr = layout.ast.title ?? "Breadboard";
|
|
14867
18621
|
const titleNode = layout.ast.title ? text({ x: BB_CONST.MARGIN, y: 22, class: "lt-bb-title" }, layout.ast.title) : "";
|
|
14868
18622
|
const substrate = renderSubstrate(layout.substrate);
|
|
@@ -17060,13 +20814,13 @@ function parseFbd(text2) {
|
|
|
17060
20814
|
parseStatement(state2, stmt);
|
|
17061
20815
|
i++;
|
|
17062
20816
|
}
|
|
17063
|
-
const
|
|
20817
|
+
const network2 = {
|
|
17064
20818
|
index: idx,
|
|
17065
20819
|
blocks: state2.blocks.slice(blocksBefore),
|
|
17066
20820
|
wires: state2.wires.slice(wiresBefore)
|
|
17067
20821
|
};
|
|
17068
|
-
if (nh.title !== void 0)
|
|
17069
|
-
networks.push(
|
|
20822
|
+
if (nh.title !== void 0) network2.title = nh.title;
|
|
20823
|
+
networks.push(network2);
|
|
17070
20824
|
}
|
|
17071
20825
|
for (const block of state2.blocks) {
|
|
17072
20826
|
if (!block.instance) continue;
|
|
@@ -17205,18 +20959,18 @@ function makePort(p, side, edgeX, x, y) {
|
|
|
17205
20959
|
if (p.negated) port.negated = true;
|
|
17206
20960
|
return port;
|
|
17207
20961
|
}
|
|
17208
|
-
function buildGraph2(
|
|
20962
|
+
function buildGraph2(network2) {
|
|
17209
20963
|
const incoming = /* @__PURE__ */ new Map();
|
|
17210
20964
|
const outgoing = /* @__PURE__ */ new Map();
|
|
17211
20965
|
const inputVars = /* @__PURE__ */ new Map();
|
|
17212
20966
|
const outputVars = /* @__PURE__ */ new Map();
|
|
17213
|
-
for (const b of
|
|
20967
|
+
for (const b of network2.blocks) {
|
|
17214
20968
|
incoming.set(b.id, /* @__PURE__ */ new Set());
|
|
17215
20969
|
outgoing.set(b.id, /* @__PURE__ */ new Set());
|
|
17216
20970
|
inputVars.set(b.id, /* @__PURE__ */ new Set());
|
|
17217
20971
|
outputVars.set(b.id, /* @__PURE__ */ new Set());
|
|
17218
20972
|
}
|
|
17219
|
-
for (const w of
|
|
20973
|
+
for (const w of network2.wires) {
|
|
17220
20974
|
if (w.from.kind === "port" && w.to.kind === "port") {
|
|
17221
20975
|
incoming.get(w.to.blockId)?.add(w.from.blockId);
|
|
17222
20976
|
outgoing.get(w.from.blockId)?.add(w.to.blockId);
|
|
@@ -17228,7 +20982,7 @@ function buildGraph2(network) {
|
|
|
17228
20982
|
}
|
|
17229
20983
|
return { incoming, outgoing, inputVars, outputVars };
|
|
17230
20984
|
}
|
|
17231
|
-
function assignLayers(
|
|
20985
|
+
function assignLayers(network2, graph) {
|
|
17232
20986
|
const layer = /* @__PURE__ */ new Map();
|
|
17233
20987
|
const visiting = /* @__PURE__ */ new Set();
|
|
17234
20988
|
const visit = (id) => {
|
|
@@ -17245,20 +20999,20 @@ function assignLayers(network, graph) {
|
|
|
17245
20999
|
layer.set(id, max);
|
|
17246
21000
|
return max;
|
|
17247
21001
|
};
|
|
17248
|
-
for (const b of
|
|
21002
|
+
for (const b of network2.blocks) visit(b.id);
|
|
17249
21003
|
return layer;
|
|
17250
21004
|
}
|
|
17251
|
-
function
|
|
17252
|
-
const graph = buildGraph2(
|
|
21005
|
+
function layoutNetwork3(network2, originX, originY) {
|
|
21006
|
+
const graph = buildGraph2(network2);
|
|
17253
21007
|
const sizes = /* @__PURE__ */ new Map();
|
|
17254
|
-
for (const b of
|
|
17255
|
-
const layerOf = assignLayers(
|
|
21008
|
+
for (const b of network2.blocks) sizes.set(b.id, computeBlockSize(b));
|
|
21009
|
+
const layerOf = assignLayers(network2, graph);
|
|
17256
21010
|
const maxLayer = Math.max(0, ...Array.from(layerOf.values()));
|
|
17257
21011
|
const byLayer = Array.from({ length: maxLayer + 1 }, () => []);
|
|
17258
|
-
for (const b of
|
|
21012
|
+
for (const b of network2.blocks) byLayer[layerOf.get(b.id)].push(b.id);
|
|
17259
21013
|
const inputVarsAll = /* @__PURE__ */ new Set();
|
|
17260
21014
|
const outputVarsAll = /* @__PURE__ */ new Set();
|
|
17261
|
-
for (const b of
|
|
21015
|
+
for (const b of network2.blocks) {
|
|
17262
21016
|
for (const v of graph.inputVars.get(b.id) ?? []) inputVarsAll.add(v);
|
|
17263
21017
|
for (const v of graph.outputVars.get(b.id) ?? []) outputVarsAll.add(v);
|
|
17264
21018
|
}
|
|
@@ -17291,7 +21045,7 @@ function layoutNetwork2(network, originX, originY) {
|
|
|
17291
21045
|
yMax = Math.max(yMax, y);
|
|
17292
21046
|
}
|
|
17293
21047
|
const layoutBlocks = [];
|
|
17294
|
-
for (const b of
|
|
21048
|
+
for (const b of network2.blocks) {
|
|
17295
21049
|
const bb = blockBboxes.get(b.id);
|
|
17296
21050
|
layoutBlocks.push({
|
|
17297
21051
|
block: b,
|
|
@@ -17339,7 +21093,7 @@ function layoutNetwork2(network, originX, originY) {
|
|
|
17339
21093
|
const wires = [];
|
|
17340
21094
|
const junctions = [];
|
|
17341
21095
|
const fanOutCounts = /* @__PURE__ */ new Map();
|
|
17342
|
-
for (const w of
|
|
21096
|
+
for (const w of network2.wires) {
|
|
17343
21097
|
if (w.from.kind === "port") {
|
|
17344
21098
|
const key = `${w.from.blockId}.${w.from.portName}`;
|
|
17345
21099
|
fanOutCounts.set(key, (fanOutCounts.get(key) ?? 0) + 1);
|
|
@@ -17357,7 +21111,7 @@ function layoutNetwork2(network, originX, originY) {
|
|
|
17357
21111
|
const varTermByName = /* @__PURE__ */ new Map();
|
|
17358
21112
|
for (const t of varTerms) varTermByName.set(t.name, t);
|
|
17359
21113
|
const colCursor = /* @__PURE__ */ new Map();
|
|
17360
|
-
for (const w of
|
|
21114
|
+
for (const w of network2.wires) {
|
|
17361
21115
|
const path2 = routeWire(w, portByKey, varTermByName, colCursor, layerX, layerWidths);
|
|
17362
21116
|
if (path2) {
|
|
17363
21117
|
wires.push({ wire: w, path: path2 });
|
|
@@ -17382,7 +21136,7 @@ function layoutNetwork2(network, originX, originY) {
|
|
|
17382
21136
|
FBD_CONST.block_min_height + FBD_CONST.network_label_h + FBD_CONST.network_padding_y * 2
|
|
17383
21137
|
);
|
|
17384
21138
|
return {
|
|
17385
|
-
network,
|
|
21139
|
+
network: network2,
|
|
17386
21140
|
x: originX,
|
|
17387
21141
|
y: originY,
|
|
17388
21142
|
width,
|
|
@@ -17443,7 +21197,7 @@ function layoutFbd(ast) {
|
|
|
17443
21197
|
let cursorY = 0;
|
|
17444
21198
|
let maxWidth = 0;
|
|
17445
21199
|
for (const net of ast.networks) {
|
|
17446
|
-
const ln =
|
|
21200
|
+
const ln = layoutNetwork3(net, 0, cursorY);
|
|
17447
21201
|
networks.push(ln);
|
|
17448
21202
|
cursorY = ln.y + ln.height + FBD_CONST.network_gap_y;
|
|
17449
21203
|
maxWidth = Math.max(maxWidth, ln.width);
|
|
@@ -17593,7 +21347,7 @@ function renderBlock(lb) {
|
|
|
17593
21347
|
parts
|
|
17594
21348
|
);
|
|
17595
21349
|
}
|
|
17596
|
-
function
|
|
21350
|
+
function renderNetwork2(ln) {
|
|
17597
21351
|
const parts = [];
|
|
17598
21352
|
parts.push(rect({
|
|
17599
21353
|
class: "lt-fbd-network-frame",
|
|
@@ -17659,7 +21413,7 @@ function renderFbdLayout(layout) {
|
|
|
17659
21413
|
const descEl = desc(`FBD with ${layout.networks.length} network(s).`);
|
|
17660
21414
|
const root = group(
|
|
17661
21415
|
{ transform: `translate(${margin},${margin})` },
|
|
17662
|
-
layout.networks.map(
|
|
21416
|
+
layout.networks.map(renderNetwork2)
|
|
17663
21417
|
);
|
|
17664
21418
|
return svgRoot(
|
|
17665
21419
|
{
|
|
@@ -18644,7 +22398,10 @@ var plugins = [
|
|
|
18644
22398
|
sfc,
|
|
18645
22399
|
prisma,
|
|
18646
22400
|
usecase,
|
|
18647
|
-
pert
|
|
22401
|
+
pert,
|
|
22402
|
+
sequence,
|
|
22403
|
+
petri,
|
|
22404
|
+
network
|
|
18648
22405
|
];
|
|
18649
22406
|
function detectPlugin(text2, config) {
|
|
18650
22407
|
if (config?.type) {
|
|
@@ -18655,7 +22412,7 @@ function detectPlugin(text2, config) {
|
|
|
18655
22412
|
if (plugin.detect(text2)) return plugin;
|
|
18656
22413
|
}
|
|
18657
22414
|
throw new Error(
|
|
18658
|
-
"Cannot detect diagram type. Start your text with 'genogram', 'ecomap', 'pedigree', 'phylo', 'sociogram', 'timing', 'logic', 'circuit', 'blockdiagram', 'ladder', 'sld', 'entity-structure', 'fishbone', 'venn', 'flowchart', 'mindmap', 'matrix', 'orgchart', 'state', 'pid', 'erd', 'breadboard', 'bpmn', 'fbd', 'sfc', 'prisma', 'usecase', or '
|
|
22415
|
+
"Cannot detect diagram type. Start your text with 'genogram', 'ecomap', 'pedigree', 'phylo', 'sociogram', 'timing', 'logic', 'circuit', 'blockdiagram', 'ladder', 'sld', 'entity-structure', 'fishbone', 'venn', 'flowchart', 'mindmap', 'matrix', 'orgchart', 'state', 'pid', 'erd', 'breadboard', 'bpmn', 'fbd', 'sfc', 'prisma', 'usecase', 'pert', 'sequence', 'petri', or 'network'."
|
|
18659
22416
|
);
|
|
18660
22417
|
}
|
|
18661
22418
|
function preprocess8(text2) {
|
|
@@ -18680,9 +22437,80 @@ function parse(text2, config) {
|
|
|
18680
22437
|
`Diagram type '${plugin.type}' does not yet expose a parse() method.`
|
|
18681
22438
|
);
|
|
18682
22439
|
}
|
|
22440
|
+
function parseResult(text2, config) {
|
|
22441
|
+
let plugin;
|
|
22442
|
+
try {
|
|
22443
|
+
const prepared = preprocess8(text2);
|
|
22444
|
+
plugin = detectPlugin(prepared, config);
|
|
22445
|
+
if (!plugin.parse) {
|
|
22446
|
+
throw new Error(
|
|
22447
|
+
`Diagram type '${plugin.type}' does not yet expose a parse() method.`
|
|
22448
|
+
);
|
|
22449
|
+
}
|
|
22450
|
+
const ast = plugin.parse(prepared);
|
|
22451
|
+
const diagnostics = runLint(plugin, prepared);
|
|
22452
|
+
return {
|
|
22453
|
+
ok: true,
|
|
22454
|
+
status: diagnostics.length > 0 ? "partial" : "valid",
|
|
22455
|
+
type: plugin.type,
|
|
22456
|
+
ast,
|
|
22457
|
+
diagnostics
|
|
22458
|
+
};
|
|
22459
|
+
} catch (err) {
|
|
22460
|
+
return {
|
|
22461
|
+
ok: false,
|
|
22462
|
+
status: "invalid",
|
|
22463
|
+
type: plugin?.type ?? config?.type ?? null,
|
|
22464
|
+
diagnostics: [diagnosticFromError(err)]
|
|
22465
|
+
};
|
|
22466
|
+
}
|
|
22467
|
+
}
|
|
22468
|
+
function runLint(plugin, prepared) {
|
|
22469
|
+
if (!plugin.lint) return [];
|
|
22470
|
+
try {
|
|
22471
|
+
return plugin.lint(prepared);
|
|
22472
|
+
} catch {
|
|
22473
|
+
return [];
|
|
22474
|
+
}
|
|
22475
|
+
}
|
|
18683
22476
|
function render(text2, config) {
|
|
22477
|
+
if (config?.mode === "preview") return renderResult(text2, config).svg;
|
|
18684
22478
|
const prepared = preprocess8(text2);
|
|
18685
22479
|
const plugin = detectPlugin(prepared, config);
|
|
22480
|
+
return renderWithPlugin(prepared, plugin, config);
|
|
22481
|
+
}
|
|
22482
|
+
function renderResult(text2, config) {
|
|
22483
|
+
let plugin;
|
|
22484
|
+
try {
|
|
22485
|
+
const prepared = preprocess8(text2);
|
|
22486
|
+
plugin = detectPlugin(prepared, config);
|
|
22487
|
+
const svg = renderWithPlugin(prepared, plugin, config);
|
|
22488
|
+
const diagnostics = runLint(plugin, prepared);
|
|
22489
|
+
return {
|
|
22490
|
+
ok: true,
|
|
22491
|
+
status: diagnostics.length > 0 ? "partial" : "valid",
|
|
22492
|
+
type: plugin.type,
|
|
22493
|
+
svg,
|
|
22494
|
+
diagnostics
|
|
22495
|
+
};
|
|
22496
|
+
} catch (err) {
|
|
22497
|
+
const type = plugin?.type ?? config?.type ?? null;
|
|
22498
|
+
const diagnostics = [diagnosticFromError(err)];
|
|
22499
|
+
return {
|
|
22500
|
+
ok: false,
|
|
22501
|
+
status: "invalid",
|
|
22502
|
+
type,
|
|
22503
|
+
svg: renderDiagnosticSvg(diagnostics, type, {
|
|
22504
|
+
fontFamily: config?.fontFamily
|
|
22505
|
+
}),
|
|
22506
|
+
diagnostics
|
|
22507
|
+
};
|
|
22508
|
+
}
|
|
22509
|
+
}
|
|
22510
|
+
function renderPreview(text2, config) {
|
|
22511
|
+
return renderResult(text2, config).svg;
|
|
22512
|
+
}
|
|
22513
|
+
function renderWithPlugin(prepared, plugin, config) {
|
|
18686
22514
|
const renderConfig = {
|
|
18687
22515
|
fontFamily: config?.fontFamily ?? "system-ui, -apple-system, sans-serif",
|
|
18688
22516
|
fontSize: 12,
|
|
@@ -18692,6 +22520,6 @@ function render(text2, config) {
|
|
|
18692
22520
|
return plugin.render(prepared, renderConfig);
|
|
18693
22521
|
}
|
|
18694
22522
|
|
|
18695
|
-
export { decisiontree, parse, pert, pid, prisma, render, state, timeline, usecase };
|
|
18696
|
-
//# sourceMappingURL=chunk-
|
|
18697
|
-
//# sourceMappingURL=chunk-
|
|
22523
|
+
export { GEOMETRY, decisiontree, drawDeviceIcon, iconSize, network, parse, parseResult, pert, petri, pid, prisma, render, renderEquip, renderPreview, renderResult, sequence, state, timeline, usecase };
|
|
22524
|
+
//# sourceMappingURL=chunk-7AFW2J6J.js.map
|
|
22525
|
+
//# sourceMappingURL=chunk-7AFW2J6J.js.map
|