libpetri 2.0.0 → 2.3.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/dist/{chunk-4L6JVKH4.js → chunk-B2CV5M3L.js} +64 -2
- package/dist/chunk-B2CV5M3L.js.map +1 -0
- package/dist/chunk-RNWTYK5B.js +419 -0
- package/dist/chunk-RNWTYK5B.js.map +1 -0
- package/dist/debug/index.d.ts +2 -2
- package/dist/debug/index.js +1 -1
- package/dist/doclet/index.d.ts +1 -1
- package/dist/doclet/index.js +2 -2
- package/dist/doclet/resources/petrinet-diagrams.css +263 -0
- package/dist/doclet/resources/petrinet-diagrams.js +32 -6
- package/dist/dot-exporter-WJMCJEFK.js +8 -0
- package/dist/{event-store-2zkXeQkd.d.ts → event-store-I3QiQABh.d.ts} +1 -1
- package/dist/export/index.d.ts +3 -3
- package/dist/export/index.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -1
- package/dist/{petri-net-BDrj4XZE.d.ts → petri-net-CKZYPOsL.d.ts} +30 -0
- package/dist/render-QK57X4TP.js +73 -0
- package/dist/render-QK57X4TP.js.map +1 -0
- package/dist/verification/index.d.ts +2 -2
- package/dist/viewer/index.d.ts +68 -2
- package/dist/viewer/index.js +582 -75
- package/dist/viewer/index.js.map +1 -1
- package/dist/viewer/layout/index.d.ts +200 -0
- package/dist/viewer/layout/index.js +15 -0
- package/dist/viewer/layout/index.js.map +1 -0
- package/dist/viewer/viewer-static.iife.js +4 -2
- package/dist/viewer/viewer.css +263 -0
- package/dist/viewer/viewer.iife.js +32 -6
- package/package.json +12 -1
- package/dist/chunk-4L6JVKH4.js.map +0 -1
- package/dist/dot-exporter-PMHOQHZ3.js +0 -8
- package/dist/render-P6GROU7J.js +0 -16
- package/dist/render-P6GROU7J.js.map +0 -1
- /package/dist/{dot-exporter-PMHOQHZ3.js.map → dot-exporter-WJMCJEFK.js.map} +0 -0
package/dist/viewer/index.js
CHANGED
|
@@ -178,6 +178,447 @@ function directChild(parent, tagName) {
|
|
|
178
178
|
return null;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
// src/viewer/c0-annotations.ts
|
|
182
|
+
function ownTitle(g) {
|
|
183
|
+
for (const child of Array.from(g.children)) {
|
|
184
|
+
if (child.tagName === "title") return child.textContent ?? "";
|
|
185
|
+
}
|
|
186
|
+
return "";
|
|
187
|
+
}
|
|
188
|
+
function enclosingClusterShortName(node) {
|
|
189
|
+
const cluster = node.parentElement?.closest("g.cluster");
|
|
190
|
+
if (!cluster) return "";
|
|
191
|
+
return ownTitle(cluster).replace(/^cluster_/, "");
|
|
192
|
+
}
|
|
193
|
+
function annotateSvgForC0(svg) {
|
|
194
|
+
const nodes = Array.from(svg.querySelectorAll("g.node"));
|
|
195
|
+
for (const node of nodes) {
|
|
196
|
+
const id = ownTitle(node);
|
|
197
|
+
if (!id) continue;
|
|
198
|
+
node.setAttribute("data-id", id);
|
|
199
|
+
const replicaM = /__rep__([A-Za-z0-9_]+)$/.exec(id);
|
|
200
|
+
if (replicaM) {
|
|
201
|
+
node.setAttribute("data-instance", replicaM[1]);
|
|
202
|
+
} else if (!node.hasAttribute("data-instance")) {
|
|
203
|
+
const inst = enclosingClusterShortName(node);
|
|
204
|
+
if (inst) node.setAttribute("data-instance", inst);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
208
|
+
for (const n of nodes) {
|
|
209
|
+
const id = n.getAttribute("data-id");
|
|
210
|
+
if (id) nodeIndex.set(id, n);
|
|
211
|
+
}
|
|
212
|
+
for (const edge of Array.from(svg.querySelectorAll("g.edge"))) {
|
|
213
|
+
const title = ownTitle(edge);
|
|
214
|
+
const arrow = title.indexOf("->");
|
|
215
|
+
if (arrow <= 0) continue;
|
|
216
|
+
const src = title.slice(0, arrow).trim();
|
|
217
|
+
const dst = title.slice(arrow + 2).trim();
|
|
218
|
+
edge.setAttribute("data-src", src);
|
|
219
|
+
edge.setAttribute("data-dst", dst);
|
|
220
|
+
const srcInst = nodeIndex.get(src)?.getAttribute("data-instance") ?? "";
|
|
221
|
+
const dstInst = nodeIndex.get(dst)?.getAttribute("data-instance") ?? "";
|
|
222
|
+
edge.setAttribute("data-src-cluster", srcInst);
|
|
223
|
+
edge.setAttribute("data-dst-cluster", dstInst);
|
|
224
|
+
edge.classList.remove("intra-cluster", "cross-cluster");
|
|
225
|
+
if (srcInst !== "" && srcInst === dstInst) {
|
|
226
|
+
edge.classList.add("intra-cluster");
|
|
227
|
+
edge.setAttribute("data-cluster", srcInst);
|
|
228
|
+
} else {
|
|
229
|
+
edge.classList.add("cross-cluster");
|
|
230
|
+
edge.removeAttribute("data-cluster");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/viewer/chrome/sidebar.ts
|
|
236
|
+
var SIDEBAR_CLASS = "libpetri-sidebar";
|
|
237
|
+
var CHIP_CLASS = "libpetri-sidebar-chip";
|
|
238
|
+
var CHIP_OFF_CLASS = "is-off";
|
|
239
|
+
function mountSidebar(container, clusters, callbacks) {
|
|
240
|
+
const visibleClusters = /* @__PURE__ */ new Set();
|
|
241
|
+
for (const prefix of clusters.keys()) visibleClusters.add(prefix);
|
|
242
|
+
let includeSharedPlaces = true;
|
|
243
|
+
let highlightMode = true;
|
|
244
|
+
const emit = () => {
|
|
245
|
+
callbacks.onVisibilityChange({
|
|
246
|
+
visibleClusters: new Set(visibleClusters),
|
|
247
|
+
includeSharedPlaces
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
const root = document.createElement("aside");
|
|
251
|
+
root.className = SIDEBAR_CLASS;
|
|
252
|
+
const title = document.createElement("h4");
|
|
253
|
+
title.className = "libpetri-sidebar-title";
|
|
254
|
+
title.textContent = `Subnets (${clusters.size})`;
|
|
255
|
+
root.appendChild(title);
|
|
256
|
+
const actions = document.createElement("div");
|
|
257
|
+
actions.className = "libpetri-sidebar-actions";
|
|
258
|
+
const showAll = document.createElement("button");
|
|
259
|
+
showAll.type = "button";
|
|
260
|
+
showAll.textContent = "show all";
|
|
261
|
+
const hideAll = document.createElement("button");
|
|
262
|
+
hideAll.type = "button";
|
|
263
|
+
hideAll.textContent = "hide all";
|
|
264
|
+
actions.appendChild(showAll);
|
|
265
|
+
actions.appendChild(hideAll);
|
|
266
|
+
root.appendChild(actions);
|
|
267
|
+
const sharedLabel = document.createElement("label");
|
|
268
|
+
sharedLabel.className = "libpetri-sidebar-checkbox";
|
|
269
|
+
const sharedCb = document.createElement("input");
|
|
270
|
+
sharedCb.type = "checkbox";
|
|
271
|
+
sharedCb.checked = includeSharedPlaces;
|
|
272
|
+
sharedLabel.appendChild(sharedCb);
|
|
273
|
+
sharedLabel.appendChild(document.createTextNode(" Shared places"));
|
|
274
|
+
sharedCb.addEventListener("change", () => {
|
|
275
|
+
includeSharedPlaces = sharedCb.checked;
|
|
276
|
+
emit();
|
|
277
|
+
});
|
|
278
|
+
root.appendChild(sharedLabel);
|
|
279
|
+
const hlLabel = document.createElement("label");
|
|
280
|
+
hlLabel.className = "libpetri-sidebar-checkbox";
|
|
281
|
+
const hlCb = document.createElement("input");
|
|
282
|
+
hlCb.type = "checkbox";
|
|
283
|
+
hlCb.checked = highlightMode;
|
|
284
|
+
hlLabel.appendChild(hlCb);
|
|
285
|
+
hlLabel.appendChild(document.createTextNode(" Click node \u2192 highlight"));
|
|
286
|
+
hlCb.addEventListener("change", () => {
|
|
287
|
+
highlightMode = hlCb.checked;
|
|
288
|
+
callbacks.onHighlightModeChange(highlightMode);
|
|
289
|
+
});
|
|
290
|
+
root.appendChild(hlLabel);
|
|
291
|
+
const hint = document.createElement("div");
|
|
292
|
+
hint.className = "libpetri-sidebar-hint";
|
|
293
|
+
hint.innerHTML = "Click chip: toggle.<br>Shift-click: isolate.<br>Cmd/Ctrl-click: multi-select.<br>Click empty: clear highlight.";
|
|
294
|
+
root.appendChild(hint);
|
|
295
|
+
const chipList = document.createElement("ul");
|
|
296
|
+
chipList.className = "libpetri-sidebar-chips";
|
|
297
|
+
const chipMap = /* @__PURE__ */ new Map();
|
|
298
|
+
for (const cluster of clusters.values()) {
|
|
299
|
+
const li = document.createElement("li");
|
|
300
|
+
li.className = CHIP_CLASS;
|
|
301
|
+
li.style.borderLeftColor = cluster.color;
|
|
302
|
+
li.dataset.prefix = cluster.prefix;
|
|
303
|
+
const dot = document.createElement("span");
|
|
304
|
+
dot.className = "libpetri-sidebar-chip-dot";
|
|
305
|
+
dot.style.background = cluster.color;
|
|
306
|
+
const name = document.createElement("span");
|
|
307
|
+
name.className = "libpetri-sidebar-chip-name";
|
|
308
|
+
name.textContent = cluster.prefix;
|
|
309
|
+
const count = document.createElement("span");
|
|
310
|
+
count.className = "libpetri-sidebar-chip-count";
|
|
311
|
+
count.textContent = String(cluster.nodeCount);
|
|
312
|
+
li.appendChild(dot);
|
|
313
|
+
li.appendChild(name);
|
|
314
|
+
li.appendChild(count);
|
|
315
|
+
li.addEventListener("click", (ev) => {
|
|
316
|
+
const prefix = cluster.prefix;
|
|
317
|
+
if (ev.shiftKey) {
|
|
318
|
+
const allOff = [...clusters.keys()].every(
|
|
319
|
+
(k) => k === prefix ? visibleClusters.has(k) : !visibleClusters.has(k)
|
|
320
|
+
);
|
|
321
|
+
visibleClusters.clear();
|
|
322
|
+
if (allOff) for (const k of clusters.keys()) visibleClusters.add(k);
|
|
323
|
+
else visibleClusters.add(prefix);
|
|
324
|
+
} else if (ev.ctrlKey || ev.metaKey) {
|
|
325
|
+
const anyHidden = [...clusters.keys()].some((k) => !visibleClusters.has(k));
|
|
326
|
+
if (!anyHidden) {
|
|
327
|
+
visibleClusters.clear();
|
|
328
|
+
visibleClusters.add(prefix);
|
|
329
|
+
} else if (visibleClusters.has(prefix)) {
|
|
330
|
+
visibleClusters.delete(prefix);
|
|
331
|
+
} else {
|
|
332
|
+
visibleClusters.add(prefix);
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
if (visibleClusters.has(prefix)) visibleClusters.delete(prefix);
|
|
336
|
+
else visibleClusters.add(prefix);
|
|
337
|
+
}
|
|
338
|
+
refreshChips();
|
|
339
|
+
emit();
|
|
340
|
+
});
|
|
341
|
+
chipList.appendChild(li);
|
|
342
|
+
chipMap.set(cluster.prefix, li);
|
|
343
|
+
}
|
|
344
|
+
root.appendChild(chipList);
|
|
345
|
+
function refreshChips() {
|
|
346
|
+
for (const [prefix, li] of chipMap) {
|
|
347
|
+
li.classList.toggle(CHIP_OFF_CLASS, !visibleClusters.has(prefix));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
showAll.addEventListener("click", () => {
|
|
351
|
+
visibleClusters.clear();
|
|
352
|
+
for (const k of clusters.keys()) visibleClusters.add(k);
|
|
353
|
+
includeSharedPlaces = true;
|
|
354
|
+
sharedCb.checked = true;
|
|
355
|
+
refreshChips();
|
|
356
|
+
emit();
|
|
357
|
+
});
|
|
358
|
+
hideAll.addEventListener("click", () => {
|
|
359
|
+
visibleClusters.clear();
|
|
360
|
+
includeSharedPlaces = false;
|
|
361
|
+
sharedCb.checked = false;
|
|
362
|
+
refreshChips();
|
|
363
|
+
emit();
|
|
364
|
+
});
|
|
365
|
+
container.appendChild(root);
|
|
366
|
+
emit();
|
|
367
|
+
return {
|
|
368
|
+
root,
|
|
369
|
+
dispose() {
|
|
370
|
+
if (root.parentNode === container) container.removeChild(root);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/viewer/highlight.ts
|
|
376
|
+
var HIGHLIGHT_CLASSES = ["is-highlight", "is-neighbor", "is-faded"];
|
|
377
|
+
function clearHighlightClasses(svg) {
|
|
378
|
+
for (const cls of HIGHLIGHT_CLASSES) {
|
|
379
|
+
for (const el of Array.from(svg.querySelectorAll("." + cls))) {
|
|
380
|
+
el.classList.remove(cls);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function findAllCopies(svg, focusId) {
|
|
385
|
+
const focus = svg.querySelector(`g.node[data-id="${cssEscape2(focusId)}"]`);
|
|
386
|
+
if (!focus) return /* @__PURE__ */ new Set([focusId]);
|
|
387
|
+
const copies = /* @__PURE__ */ new Set([focusId]);
|
|
388
|
+
const origLabel = focus.getAttribute("data-replica-of") ?? (focusId.startsWith("p_") ? focusId.slice(2) : null);
|
|
389
|
+
if (origLabel) {
|
|
390
|
+
const orig = svg.querySelector(`g.node[data-id="${cssEscape2("p_" + origLabel)}"]`);
|
|
391
|
+
if (orig) {
|
|
392
|
+
const id = orig.getAttribute("data-id");
|
|
393
|
+
if (id) copies.add(id);
|
|
394
|
+
}
|
|
395
|
+
for (const sibling of Array.from(
|
|
396
|
+
svg.querySelectorAll(`g.node[data-replica-of="${cssEscape2(origLabel)}"]`)
|
|
397
|
+
)) {
|
|
398
|
+
const id = sibling.getAttribute("data-id");
|
|
399
|
+
if (id) copies.add(id);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return copies;
|
|
403
|
+
}
|
|
404
|
+
function applyHighlight(svg, focusId) {
|
|
405
|
+
clearHighlightClasses(svg);
|
|
406
|
+
if (focusId === null) return;
|
|
407
|
+
const copies = findAllCopies(svg, focusId);
|
|
408
|
+
for (const id of copies) {
|
|
409
|
+
const n = svg.querySelector(`g.node[data-id="${cssEscape2(id)}"]`);
|
|
410
|
+
if (n && !n.classList.contains("is-hidden")) n.classList.add("is-highlight");
|
|
411
|
+
}
|
|
412
|
+
const neighbors = /* @__PURE__ */ new Set();
|
|
413
|
+
const pathJunctions = /* @__PURE__ */ new Set();
|
|
414
|
+
const pathEdges = [];
|
|
415
|
+
const queue = Array.from(copies);
|
|
416
|
+
const visited = new Set(copies);
|
|
417
|
+
const allEdges = Array.from(svg.querySelectorAll("g.edge"));
|
|
418
|
+
while (queue.length > 0) {
|
|
419
|
+
const nodeId = queue.shift();
|
|
420
|
+
for (const e of allEdges) {
|
|
421
|
+
if (e.classList.contains("is-hidden")) continue;
|
|
422
|
+
const s = e.getAttribute("data-src");
|
|
423
|
+
const d = e.getAttribute("data-dst");
|
|
424
|
+
if (s !== nodeId && d !== nodeId) continue;
|
|
425
|
+
const otherId = s === nodeId ? d : s;
|
|
426
|
+
if (!otherId) continue;
|
|
427
|
+
pathEdges.push(e);
|
|
428
|
+
if (visited.has(otherId)) continue;
|
|
429
|
+
visited.add(otherId);
|
|
430
|
+
if (otherId.startsWith("j_")) {
|
|
431
|
+
pathJunctions.add(otherId);
|
|
432
|
+
queue.push(otherId);
|
|
433
|
+
} else {
|
|
434
|
+
neighbors.add(otherId);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
for (const e of pathEdges) e.classList.add("is-highlight");
|
|
439
|
+
for (const e of allEdges) {
|
|
440
|
+
if (e.classList.contains("is-hidden") || e.classList.contains("is-highlight")) continue;
|
|
441
|
+
e.classList.add("is-faded");
|
|
442
|
+
}
|
|
443
|
+
for (const n of Array.from(svg.querySelectorAll("g.node"))) {
|
|
444
|
+
if (n.classList.contains("is-hidden")) continue;
|
|
445
|
+
const id = n.getAttribute("data-id") ?? "";
|
|
446
|
+
if (copies.has(id)) continue;
|
|
447
|
+
if (neighbors.has(id)) n.classList.add("is-neighbor");
|
|
448
|
+
else if (pathJunctions.has(id)) continue;
|
|
449
|
+
else n.classList.add("is-faded");
|
|
450
|
+
}
|
|
451
|
+
for (const c of Array.from(
|
|
452
|
+
svg.querySelectorAll("g.cluster:not(.cluster-orchestrator)")
|
|
453
|
+
)) {
|
|
454
|
+
if (c.classList.contains("is-hidden")) continue;
|
|
455
|
+
c.classList.add("is-faded");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function cssEscape2(value) {
|
|
459
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/viewer/replica-tagging.ts
|
|
463
|
+
var REPLICA_RE = /^(p_[A-Za-z0-9_]+)__rep__/;
|
|
464
|
+
function semanticName(placeId) {
|
|
465
|
+
return placeId.replace(/^p_/, "");
|
|
466
|
+
}
|
|
467
|
+
function tagReplicas(svg) {
|
|
468
|
+
const originalsWithCopies = /* @__PURE__ */ new Set();
|
|
469
|
+
const allNodes = Array.from(svg.querySelectorAll("g.node"));
|
|
470
|
+
for (const g of allNodes) {
|
|
471
|
+
const title = g.querySelector("title")?.textContent ?? "";
|
|
472
|
+
const m = REPLICA_RE.exec(title);
|
|
473
|
+
if (m) originalsWithCopies.add(m[1]);
|
|
474
|
+
}
|
|
475
|
+
for (const g of allNodes) {
|
|
476
|
+
const title = g.querySelector("title")?.textContent ?? "";
|
|
477
|
+
const replicaMatch = REPLICA_RE.exec(title);
|
|
478
|
+
let semantic = null;
|
|
479
|
+
if (replicaMatch) {
|
|
480
|
+
semantic = semanticName(replicaMatch[1]);
|
|
481
|
+
} else if (originalsWithCopies.has(title)) {
|
|
482
|
+
semantic = semanticName(title);
|
|
483
|
+
}
|
|
484
|
+
if (semantic === null) continue;
|
|
485
|
+
g.classList.add("petri-replica");
|
|
486
|
+
g.setAttribute("data-replica-of", semantic);
|
|
487
|
+
appendSharedGlyph(g);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
491
|
+
var GLYPH_CLASS = "libpetri-replica-glyph";
|
|
492
|
+
function appendSharedGlyph(g) {
|
|
493
|
+
if (g.querySelector(`text.${GLYPH_CLASS}`)) return;
|
|
494
|
+
const shape = g.querySelector("ellipse, circle");
|
|
495
|
+
if (!shape) return;
|
|
496
|
+
let cx = 0, cy = 0, rx = 0, ry = 0;
|
|
497
|
+
if (shape.tagName === "ellipse") {
|
|
498
|
+
cx = parseFloat(shape.getAttribute("cx") ?? "0");
|
|
499
|
+
cy = parseFloat(shape.getAttribute("cy") ?? "0");
|
|
500
|
+
rx = parseFloat(shape.getAttribute("rx") ?? "0");
|
|
501
|
+
ry = parseFloat(shape.getAttribute("ry") ?? "0");
|
|
502
|
+
} else {
|
|
503
|
+
cx = parseFloat(shape.getAttribute("cx") ?? "0");
|
|
504
|
+
cy = parseFloat(shape.getAttribute("cy") ?? "0");
|
|
505
|
+
rx = parseFloat(shape.getAttribute("r") ?? "0");
|
|
506
|
+
ry = rx;
|
|
507
|
+
}
|
|
508
|
+
const text = document.createElementNS(SVG_NS, "text");
|
|
509
|
+
text.setAttribute("class", GLYPH_CLASS);
|
|
510
|
+
text.setAttribute("x", (cx + rx + 2).toFixed(2));
|
|
511
|
+
text.setAttribute("y", (cy - ry + 2).toFixed(2));
|
|
512
|
+
text.setAttribute("text-anchor", "start");
|
|
513
|
+
text.setAttribute("font-size", "9");
|
|
514
|
+
text.textContent = "\u21C4";
|
|
515
|
+
g.appendChild(text);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/viewer/visibility.ts
|
|
519
|
+
function computeVisibleOrphans(svg, visibleClusters) {
|
|
520
|
+
if (visibleClusters.size === 0) return /* @__PURE__ */ new Set();
|
|
521
|
+
const orphanNodes = Array.from(
|
|
522
|
+
svg.querySelectorAll("g.node:not([data-instance])")
|
|
523
|
+
);
|
|
524
|
+
const orphanIds = /* @__PURE__ */ new Set();
|
|
525
|
+
for (const n of orphanNodes) {
|
|
526
|
+
const id = n.getAttribute("data-id");
|
|
527
|
+
if (id) orphanIds.add(id);
|
|
528
|
+
}
|
|
529
|
+
const fwdAdj = /* @__PURE__ */ new Map();
|
|
530
|
+
const bwdAdj = /* @__PURE__ */ new Map();
|
|
531
|
+
const link = (m, k, v) => {
|
|
532
|
+
let s = m.get(k);
|
|
533
|
+
if (!s) {
|
|
534
|
+
s = /* @__PURE__ */ new Set();
|
|
535
|
+
m.set(k, s);
|
|
536
|
+
}
|
|
537
|
+
s.add(v);
|
|
538
|
+
};
|
|
539
|
+
for (const e of Array.from(svg.querySelectorAll("g.edge"))) {
|
|
540
|
+
const s = e.getAttribute("data-src") ?? "";
|
|
541
|
+
const d = e.getAttribute("data-dst") ?? "";
|
|
542
|
+
if (orphanIds.has(s) && orphanIds.has(d)) {
|
|
543
|
+
link(fwdAdj, s, d);
|
|
544
|
+
link(bwdAdj, d, s);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const seedUpstream = /* @__PURE__ */ new Set();
|
|
548
|
+
const seedDownstream = /* @__PURE__ */ new Set();
|
|
549
|
+
for (const e of Array.from(
|
|
550
|
+
svg.querySelectorAll("g.edge.cross-cluster")
|
|
551
|
+
)) {
|
|
552
|
+
const s = e.getAttribute("data-src") ?? "";
|
|
553
|
+
const d = e.getAttribute("data-dst") ?? "";
|
|
554
|
+
const sCl = e.getAttribute("data-src-cluster") ?? "";
|
|
555
|
+
const dCl = e.getAttribute("data-dst-cluster") ?? "";
|
|
556
|
+
if (orphanIds.has(s) && visibleClusters.has(dCl)) seedUpstream.add(s);
|
|
557
|
+
if (orphanIds.has(d) && visibleClusters.has(sCl)) seedDownstream.add(d);
|
|
558
|
+
}
|
|
559
|
+
const visible = /* @__PURE__ */ new Set();
|
|
560
|
+
const bfs = (seeds, adj) => {
|
|
561
|
+
const q = [];
|
|
562
|
+
for (const s of seeds) {
|
|
563
|
+
visible.add(s);
|
|
564
|
+
q.push(s);
|
|
565
|
+
}
|
|
566
|
+
while (q.length) {
|
|
567
|
+
const cur = q.shift();
|
|
568
|
+
const next = adj.get(cur);
|
|
569
|
+
if (!next) continue;
|
|
570
|
+
for (const x of next) {
|
|
571
|
+
if (!visible.has(x)) {
|
|
572
|
+
visible.add(x);
|
|
573
|
+
q.push(x);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
bfs(seedUpstream, bwdAdj);
|
|
579
|
+
bfs(seedDownstream, fwdAdj);
|
|
580
|
+
return visible;
|
|
581
|
+
}
|
|
582
|
+
function applyVisibility(svg, state) {
|
|
583
|
+
annotateSvgForC0(svg);
|
|
584
|
+
const { visibleClusters, includeSharedPlaces } = state;
|
|
585
|
+
for (const c of Array.from(
|
|
586
|
+
svg.querySelectorAll("g.cluster:not(.cluster-orchestrator)")
|
|
587
|
+
)) {
|
|
588
|
+
const titleEl = c.querySelector(":scope > title");
|
|
589
|
+
const shortName = (titleEl?.textContent ?? "").replace(/^cluster_/, "");
|
|
590
|
+
c.classList.toggle("is-hidden", !visibleClusters.has(shortName));
|
|
591
|
+
}
|
|
592
|
+
for (const n of Array.from(svg.querySelectorAll("g.node[data-instance]"))) {
|
|
593
|
+
const inst = n.getAttribute("data-instance") ?? "";
|
|
594
|
+
n.classList.toggle("is-hidden", !visibleClusters.has(inst));
|
|
595
|
+
}
|
|
596
|
+
const visOrphans = computeVisibleOrphans(svg, visibleClusters);
|
|
597
|
+
for (const n of Array.from(
|
|
598
|
+
svg.querySelectorAll("g.node:not([data-instance])")
|
|
599
|
+
)) {
|
|
600
|
+
const id = n.getAttribute("data-id") ?? "";
|
|
601
|
+
n.classList.toggle("is-hidden", !visOrphans.has(id));
|
|
602
|
+
}
|
|
603
|
+
if (!includeSharedPlaces) {
|
|
604
|
+
for (const n of Array.from(svg.querySelectorAll("g.node.petri-replica"))) {
|
|
605
|
+
n.classList.add("is-hidden");
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
609
|
+
for (const n of Array.from(svg.querySelectorAll("g.node"))) {
|
|
610
|
+
const id = n.getAttribute("data-id");
|
|
611
|
+
if (id) nodeById.set(id, n);
|
|
612
|
+
}
|
|
613
|
+
for (const e of Array.from(svg.querySelectorAll("g.edge"))) {
|
|
614
|
+
const s = e.getAttribute("data-src") ?? "";
|
|
615
|
+
const d = e.getAttribute("data-dst") ?? "";
|
|
616
|
+
const sHidden = nodeById.get(s)?.classList.contains("is-hidden") ?? false;
|
|
617
|
+
const dHidden = nodeById.get(d)?.classList.contains("is-hidden") ?? false;
|
|
618
|
+
e.classList.toggle("is-hidden", sHidden || dHidden);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
181
622
|
// src/viewer/styles.ts
|
|
182
623
|
var VIEWER_CSS_VARIABLES = [
|
|
183
624
|
"--lpv-bg",
|
|
@@ -194,14 +635,66 @@ var VIEWER_CSS_VARIABLES = [
|
|
|
194
635
|
"--lpv-active-filter-outline",
|
|
195
636
|
"--lpv-legend-bg",
|
|
196
637
|
"--lpv-chip-bg",
|
|
197
|
-
"--lpv-chip-active-bg"
|
|
638
|
+
"--lpv-chip-active-bg",
|
|
639
|
+
// C0 sidebar + replica + highlight (Stage 5)
|
|
640
|
+
"--lpv-sidebar-bg",
|
|
641
|
+
"--lpv-sidebar-border",
|
|
642
|
+
"--lpv-sidebar-text",
|
|
643
|
+
"--lpv-sidebar-muted",
|
|
644
|
+
"--lpv-sidebar-chip-bg",
|
|
645
|
+
"--lpv-sidebar-chip-hover-bg",
|
|
646
|
+
"--lpv-sidebar-chip-off-opacity",
|
|
647
|
+
"--lpv-shared-glyph-color",
|
|
648
|
+
"--lpv-replica-fill",
|
|
649
|
+
"--lpv-replica-stroke",
|
|
650
|
+
"--lpv-highlight-stroke",
|
|
651
|
+
"--lpv-highlight-glow",
|
|
652
|
+
"--lpv-neighbor-stroke",
|
|
653
|
+
"--lpv-faded-node-opacity",
|
|
654
|
+
"--lpv-faded-edge-opacity",
|
|
655
|
+
"--lpv-faded-cluster-opacity"
|
|
198
656
|
];
|
|
199
657
|
|
|
658
|
+
// src/viewer/dot-flatten.ts
|
|
659
|
+
var CLUSTER_OPEN = /^\s*subgraph\s+cluster_[A-Za-z0-9_]+\s*\{\s*$/;
|
|
660
|
+
var ONLY_CLOSE_BRACE = /^\s*\}\s*$/;
|
|
661
|
+
function flattenClusters(dot) {
|
|
662
|
+
const lines = dot.split("\n");
|
|
663
|
+
const out = [];
|
|
664
|
+
let clusterDepth = 0;
|
|
665
|
+
for (const line of lines) {
|
|
666
|
+
if (CLUSTER_OPEN.test(line)) {
|
|
667
|
+
clusterDepth++;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (clusterDepth > 0) {
|
|
671
|
+
if (ONLY_CLOSE_BRACE.test(line)) {
|
|
672
|
+
clusterDepth--;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (line.includes("[") || line.includes("->")) {
|
|
676
|
+
out.push(stripClusterEdgeAttrs(line));
|
|
677
|
+
}
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (line.includes("->") && (line.includes("ltail=") || line.includes("lhead="))) {
|
|
681
|
+
out.push(stripClusterEdgeAttrs(line));
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
out.push(line);
|
|
685
|
+
}
|
|
686
|
+
return out.join("\n");
|
|
687
|
+
}
|
|
688
|
+
function stripClusterEdgeAttrs(line) {
|
|
689
|
+
return line.replace(/,\s*ltail="cluster_[^"]*"/g, "").replace(/,\s*lhead="cluster_[^"]*"/g, "").replace(/\[\s*ltail="cluster_[^"]*"\s*,\s*/g, "[").replace(/\[\s*lhead="cluster_[^"]*"\s*,\s*/g, "[").replace(/\[\s*ltail="cluster_[^"]*"\s*\]/g, "[]").replace(/\[\s*lhead="cluster_[^"]*"\s*\]/g, "[]");
|
|
690
|
+
}
|
|
691
|
+
|
|
200
692
|
// src/viewer/index.ts
|
|
201
693
|
async function mount(dotSource, container, opts = {}) {
|
|
202
694
|
const previousHandle = opts.previousHandle ?? null;
|
|
203
695
|
const preservedCollapsed = previousHandle ? new Set(previousHandle.collapsedPrefixes) : /* @__PURE__ */ new Set();
|
|
204
696
|
const preservedFilter = previousHandle?.activeFilter ?? null;
|
|
697
|
+
const subnetsMode = opts.subnets ?? previousHandle?.subnets ?? "show";
|
|
205
698
|
if (previousHandle) {
|
|
206
699
|
previousHandle.dispose();
|
|
207
700
|
}
|
|
@@ -213,8 +706,10 @@ async function mount(dotSource, container, opts = {}) {
|
|
|
213
706
|
}
|
|
214
707
|
svg = existing;
|
|
215
708
|
} else {
|
|
216
|
-
const
|
|
217
|
-
|
|
709
|
+
const renderModule = await import("../render-QK57X4TP.js");
|
|
710
|
+
const useElk = (opts.layout ?? "elk") === "elk";
|
|
711
|
+
const renderedDot = subnetsMode === "hide" ? flattenClusters(dotSource) : dotSource;
|
|
712
|
+
svg = useElk ? await renderModule.renderDotToSvgWithElkLayout(renderedDot) : await renderModule.renderDotToSvg(renderedDot);
|
|
218
713
|
container.innerHTML = "";
|
|
219
714
|
container.appendChild(svg);
|
|
220
715
|
}
|
|
@@ -222,10 +717,18 @@ async function mount(dotSource, container, opts = {}) {
|
|
|
222
717
|
const panzoomInstance = attachPanzoom(svg, opts.panzoom);
|
|
223
718
|
const clusters = discoverClusters(svg);
|
|
224
719
|
paintClusterBorders(clusters);
|
|
720
|
+
const layoutMode = opts.layout ?? "elk";
|
|
721
|
+
if (layoutMode === "elk") {
|
|
722
|
+
tagReplicas(svg);
|
|
723
|
+
annotateSvgForC0(svg);
|
|
724
|
+
}
|
|
225
725
|
const collapsedPrefixes = /* @__PURE__ */ new Set();
|
|
226
726
|
let activeFilter = null;
|
|
727
|
+
let highlightedNodeId = null;
|
|
227
728
|
let disposed = false;
|
|
228
729
|
let chromeRoot = null;
|
|
730
|
+
let sidebarHandle = null;
|
|
731
|
+
let highlightModeEnabled = true;
|
|
229
732
|
function ensureChrome() {
|
|
230
733
|
if (!opts.chrome) return;
|
|
231
734
|
if (chromeRoot && chromeRoot.parentNode === container) return;
|
|
@@ -234,16 +737,6 @@ async function mount(dotSource, container, opts = {}) {
|
|
|
234
737
|
chromeRoot.style.position = "absolute";
|
|
235
738
|
chromeRoot.style.inset = "0";
|
|
236
739
|
chromeRoot.style.pointerEvents = "none";
|
|
237
|
-
const legend = document.createElement("div");
|
|
238
|
-
legend.className = "diagram-legend";
|
|
239
|
-
legend.style.pointerEvents = "auto";
|
|
240
|
-
legend.innerHTML = '<div class="legend-title">Clusters</div>';
|
|
241
|
-
chromeRoot.appendChild(legend);
|
|
242
|
-
const strip = document.createElement("div");
|
|
243
|
-
strip.className = "diagram-filter-strip";
|
|
244
|
-
strip.style.pointerEvents = "auto";
|
|
245
|
-
strip.innerHTML = '<span class="filter-strip-label">Show only:</span>';
|
|
246
|
-
chromeRoot.appendChild(strip);
|
|
247
740
|
const controls = document.createElement("div");
|
|
248
741
|
controls.className = "diagram-controls";
|
|
249
742
|
controls.style.pointerEvents = "auto";
|
|
@@ -259,6 +752,19 @@ async function mount(dotSource, container, opts = {}) {
|
|
|
259
752
|
fsBtn.title = "Toggle fullscreen";
|
|
260
753
|
fsBtn.textContent = "Fullscreen";
|
|
261
754
|
fsBtn.addEventListener("click", () => toggleFullscreen(container, fsBtn));
|
|
755
|
+
const subnetsBtn = document.createElement("button");
|
|
756
|
+
subnetsBtn.type = "button";
|
|
757
|
+
subnetsBtn.className = "diagram-btn btn-subnets";
|
|
758
|
+
subnetsBtn.title = subnetsMode === "show" ? "Hide subnet groupings" : "Show subnet groupings";
|
|
759
|
+
subnetsBtn.textContent = subnetsMode === "show" ? "Flat view" : "Subnets view";
|
|
760
|
+
if (dotSource == null) {
|
|
761
|
+
subnetsBtn.disabled = true;
|
|
762
|
+
subnetsBtn.title = "Subnet toggle unavailable for pre-rendered SVG";
|
|
763
|
+
}
|
|
764
|
+
subnetsBtn.addEventListener("click", () => {
|
|
765
|
+
void handle.toggleSubnets();
|
|
766
|
+
});
|
|
767
|
+
controls.appendChild(subnetsBtn);
|
|
262
768
|
controls.appendChild(resetBtn);
|
|
263
769
|
controls.appendChild(fsBtn);
|
|
264
770
|
chromeRoot.appendChild(controls);
|
|
@@ -266,73 +772,21 @@ async function mount(dotSource, container, opts = {}) {
|
|
|
266
772
|
container.style.position = "relative";
|
|
267
773
|
}
|
|
268
774
|
container.appendChild(chromeRoot);
|
|
269
|
-
|
|
270
|
-
|
|
775
|
+
if (layoutMode === "elk" && subnetsMode === "show" && clusters.size > 0) {
|
|
776
|
+
sidebarHandle = mountSidebar(container, clusters, {
|
|
777
|
+
onVisibilityChange: (state) => handle.setVisibility(state),
|
|
778
|
+
onHighlightModeChange: (enabled) => {
|
|
779
|
+
highlightModeEnabled = enabled;
|
|
780
|
+
if (!enabled) handle.highlight(null);
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
}
|
|
271
784
|
}
|
|
272
785
|
function toggleFullscreen(host, btn) {
|
|
273
786
|
const isFs = host.classList.toggle("diagram-fullscreen");
|
|
274
787
|
btn.textContent = isFs ? "Exit fullscreen" : "Fullscreen";
|
|
275
788
|
requestAnimationFrame(() => handle.fit());
|
|
276
789
|
}
|
|
277
|
-
function renderLegend(legend) {
|
|
278
|
-
legend.innerHTML = '<div class="legend-title">Clusters</div>';
|
|
279
|
-
for (const cluster of clusters.values()) {
|
|
280
|
-
const row = document.createElement("div");
|
|
281
|
-
row.className = "legend-row";
|
|
282
|
-
row.innerHTML = '<span class="legend-ribbon" style="background:' + cluster.color + '"></span><span class="legend-label"></span><span class="legend-count"></span>';
|
|
283
|
-
row.querySelector(".legend-label").textContent = cluster.prefix;
|
|
284
|
-
row.querySelector(".legend-count").textContent = String(cluster.nodeCount);
|
|
285
|
-
row.addEventListener("click", () => {
|
|
286
|
-
if (collapsedPrefixes.has(cluster.prefix)) {
|
|
287
|
-
handle.expand(cluster.prefix);
|
|
288
|
-
} else {
|
|
289
|
-
handle.collapse(cluster.prefix);
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
legend.appendChild(row);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
function renderFilterStrip(strip) {
|
|
296
|
-
strip.innerHTML = '<span class="filter-strip-label">Show only:</span>';
|
|
297
|
-
const allChip = document.createElement("button");
|
|
298
|
-
allChip.type = "button";
|
|
299
|
-
allChip.className = "filter-chip filter-chip-all filter-chip-active";
|
|
300
|
-
allChip.textContent = "all";
|
|
301
|
-
allChip.addEventListener("click", () => {
|
|
302
|
-
handle.filter(null);
|
|
303
|
-
});
|
|
304
|
-
strip.appendChild(allChip);
|
|
305
|
-
for (const cluster of clusters.values()) {
|
|
306
|
-
const chip = document.createElement("button");
|
|
307
|
-
chip.type = "button";
|
|
308
|
-
chip.className = "filter-chip";
|
|
309
|
-
chip.style.borderColor = cluster.color;
|
|
310
|
-
chip.dataset.prefix = cluster.prefix;
|
|
311
|
-
chip.innerHTML = '<span class="chip-dot" style="background:' + cluster.color + '"></span>';
|
|
312
|
-
chip.append(document.createTextNode(cluster.prefix));
|
|
313
|
-
chip.addEventListener("click", () => {
|
|
314
|
-
if (activeFilter === cluster.prefix) {
|
|
315
|
-
handle.filter(null);
|
|
316
|
-
} else {
|
|
317
|
-
handle.filter(cluster.prefix);
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
strip.appendChild(chip);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
function refreshChromeActiveStates() {
|
|
324
|
-
if (!chromeRoot) return;
|
|
325
|
-
const strip = chromeRoot.querySelector(".diagram-filter-strip");
|
|
326
|
-
if (!strip) return;
|
|
327
|
-
strip.querySelectorAll(".filter-chip").forEach((chip) => {
|
|
328
|
-
chip.classList.remove("filter-chip-active");
|
|
329
|
-
});
|
|
330
|
-
if (activeFilter === null) {
|
|
331
|
-
strip.querySelector(".filter-chip-all")?.classList.add("filter-chip-active");
|
|
332
|
-
} else {
|
|
333
|
-
strip.querySelector(`.filter-chip[data-prefix="${activeFilter}"]`)?.classList.add("filter-chip-active");
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
790
|
const handle = {
|
|
337
791
|
svg,
|
|
338
792
|
panzoom: panzoomInstance,
|
|
@@ -343,6 +797,29 @@ async function mount(dotSource, container, opts = {}) {
|
|
|
343
797
|
get activeFilter() {
|
|
344
798
|
return activeFilter;
|
|
345
799
|
},
|
|
800
|
+
get subnets() {
|
|
801
|
+
return subnetsMode;
|
|
802
|
+
},
|
|
803
|
+
setSubnets(mode) {
|
|
804
|
+
if (dotSource == null) return Promise.resolve(handle);
|
|
805
|
+
if (mode === subnetsMode) return Promise.resolve(handle);
|
|
806
|
+
return mount(dotSource, container, {
|
|
807
|
+
...opts,
|
|
808
|
+
previousHandle: handle,
|
|
809
|
+
subnets: mode
|
|
810
|
+
}).then((next) => {
|
|
811
|
+
container.dispatchEvent(
|
|
812
|
+
new CustomEvent("libpetri-viewer:remount", {
|
|
813
|
+
bubbles: true,
|
|
814
|
+
detail: { handle: next, subnets: mode }
|
|
815
|
+
})
|
|
816
|
+
);
|
|
817
|
+
return next;
|
|
818
|
+
});
|
|
819
|
+
},
|
|
820
|
+
toggleSubnets() {
|
|
821
|
+
return handle.setSubnets(subnetsMode === "show" ? "hide" : "show");
|
|
822
|
+
},
|
|
346
823
|
collapse(prefix) {
|
|
347
824
|
if (disposed) return;
|
|
348
825
|
const cluster = clusters.get(prefix);
|
|
@@ -388,7 +865,6 @@ async function mount(dotSource, container, opts = {}) {
|
|
|
388
865
|
activeFilter = prefix;
|
|
389
866
|
applyFilter(svg, prefix);
|
|
390
867
|
opts.onClusterFilter?.(prefix);
|
|
391
|
-
refreshChromeActiveStates();
|
|
392
868
|
},
|
|
393
869
|
resetZoom() {
|
|
394
870
|
if (disposed) return;
|
|
@@ -409,6 +885,20 @@ async function mount(dotSource, container, opts = {}) {
|
|
|
409
885
|
isInsideCollapsedCluster(graphId) {
|
|
410
886
|
return isInsideCollapsedCluster(svg, clusters, collapsedPrefixes, graphId);
|
|
411
887
|
},
|
|
888
|
+
get highlightedNodeId() {
|
|
889
|
+
return highlightedNodeId;
|
|
890
|
+
},
|
|
891
|
+
highlight(nodeId) {
|
|
892
|
+
if (disposed) return;
|
|
893
|
+
if (layoutMode !== "elk") return;
|
|
894
|
+
highlightedNodeId = nodeId;
|
|
895
|
+
applyHighlight(svg, nodeId);
|
|
896
|
+
},
|
|
897
|
+
setVisibility(state) {
|
|
898
|
+
if (disposed) return;
|
|
899
|
+
if (layoutMode !== "elk") return;
|
|
900
|
+
applyVisibility(svg, state);
|
|
901
|
+
},
|
|
412
902
|
dispose() {
|
|
413
903
|
if (disposed) return;
|
|
414
904
|
disposed = true;
|
|
@@ -416,6 +906,8 @@ async function mount(dotSource, container, opts = {}) {
|
|
|
416
906
|
panzoomInstance.dispose();
|
|
417
907
|
} catch {
|
|
418
908
|
}
|
|
909
|
+
sidebarHandle?.dispose();
|
|
910
|
+
sidebarHandle = null;
|
|
419
911
|
if (chromeRoot && chromeRoot.parentNode === container) {
|
|
420
912
|
container.removeChild(chromeRoot);
|
|
421
913
|
}
|
|
@@ -428,6 +920,21 @@ async function mount(dotSource, container, opts = {}) {
|
|
|
428
920
|
if (preservedFilter !== null) {
|
|
429
921
|
handle.filter(preservedFilter);
|
|
430
922
|
}
|
|
923
|
+
if (layoutMode === "elk") {
|
|
924
|
+
svg.addEventListener("click", (ev) => {
|
|
925
|
+
if (!highlightModeEnabled) return;
|
|
926
|
+
const target = ev.target;
|
|
927
|
+
if (!(target instanceof Element)) return;
|
|
928
|
+
const nodeG = target.closest("g.node");
|
|
929
|
+
if (!nodeG) {
|
|
930
|
+
handle.highlight(null);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
const id = nodeG.getAttribute("data-id");
|
|
934
|
+
if (!id) return;
|
|
935
|
+
handle.highlight(id === highlightedNodeId ? null : id);
|
|
936
|
+
});
|
|
937
|
+
}
|
|
431
938
|
ensureChrome();
|
|
432
939
|
requestAnimationFrame(() => handle.fit());
|
|
433
940
|
return handle;
|