@cyoda/workflow-viewer 0.1.0 → 0.2.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 +60 -9
- package/dist/index.cjs +146 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -6
- package/dist/index.d.ts +27 -6
- package/dist/index.js +147 -64
- package/dist/index.js.map +1 -1
- package/dist/theme/index.cjs +3 -10
- package/dist/theme/index.cjs.map +1 -1
- package/dist/theme/index.d.cts +1 -2
- package/dist/theme/index.d.ts +1 -2
- package/dist/theme/index.js +3 -10
- package/dist/theme/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# `@cyoda/workflow-viewer`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Slim read-only SVG viewer for Cyoda workflow graphs. No React Flow, no
|
|
4
|
+
Monaco, no editor-only dependencies.
|
|
5
5
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
@@ -9,17 +9,68 @@ applications.
|
|
|
9
9
|
npm install @cyoda/workflow-core @cyoda/workflow-graph @cyoda/workflow-viewer react react-dom
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Usage
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
```tsx
|
|
15
|
+
import { parseImportPayload } from "@cyoda/workflow-core";
|
|
16
|
+
import { projectToGraph } from "@cyoda/workflow-graph";
|
|
17
|
+
import { WorkflowViewer } from "@cyoda/workflow-viewer";
|
|
18
|
+
|
|
19
|
+
const { document } = parseImportPayload(workflowJson);
|
|
20
|
+
|
|
21
|
+
export function Embed() {
|
|
22
|
+
return (
|
|
23
|
+
<WorkflowViewer
|
|
24
|
+
graph={projectToGraph(document)}
|
|
25
|
+
width="100%"
|
|
26
|
+
height={600}
|
|
27
|
+
onSelectionChange={(id) => console.log("selected", id)}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Optional ELK layout
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { layoutGraph } from "@cyoda/workflow-layout";
|
|
37
|
+
|
|
38
|
+
const layout = await layoutGraph(graph, { preset: "configuratorReadable" });
|
|
39
|
+
<WorkflowViewer graph={graph} layout={layout} />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Without a `layout` prop the viewer uses its own simple fallback layout.
|
|
43
|
+
|
|
44
|
+
## What this package provides
|
|
45
|
+
|
|
46
|
+
- SVG rendering of states and transitions using Cyoda visual conventions
|
|
47
|
+
(initial marker, terminal pill, role-coloured borders, dashed loopbacks,
|
|
48
|
+
manual/disabled/criteria/processor badges).
|
|
49
|
+
- Pan and zoom via mouse drag and Ctrl+wheel.
|
|
50
|
+
- Click-to-select; selection value is the synthetic node UUID.
|
|
51
|
+
- Theme tokens from `@cyoda/workflow-viewer/theme` (override via CSS
|
|
52
|
+
custom properties).
|
|
53
|
+
|
|
54
|
+
## What this package does NOT provide
|
|
55
|
+
|
|
56
|
+
- No drag-connect, delete, or edit affordances — use `@cyoda/workflow-react`.
|
|
57
|
+
- No JSON editor — pair with `@cyoda/workflow-monaco`.
|
|
58
|
+
- No React Flow — this package is intentionally free of React Flow to keep
|
|
59
|
+
the display-only bundle small.
|
|
60
|
+
- No editor metadata (layout positions, comments) — those live in
|
|
61
|
+
`@cyoda/workflow-core`'s `WorkflowUiMeta` and are managed by the editor
|
|
62
|
+
shell, not the viewer.
|
|
63
|
+
|
|
64
|
+
## Bundle boundary guarantee
|
|
65
|
+
|
|
66
|
+
`@cyoda/workflow-viewer` depends only on `@cyoda/workflow-graph` and React.
|
|
67
|
+
It has no dependency on `@cyoda/workflow-react`, `@cyoda/workflow-layout`,
|
|
68
|
+
`@cyoda/workflow-monaco`, or `reactflow`. This boundary is enforced by
|
|
69
|
+
the package manifest and verified in the bundle audit.
|
|
17
70
|
|
|
18
71
|
## Documentation
|
|
19
72
|
|
|
20
|
-
See the
|
|
21
|
-
[repository README](https://github.com/Cyoda-platform/cyoda-workflow-editor#readme)
|
|
22
|
-
for package relationships, usage examples, and release notes.
|
|
73
|
+
See the [repository README](https://github.com/Cyoda-platform/cyoda-workflow-editor#readme).
|
|
23
74
|
|
|
24
75
|
## License
|
|
25
76
|
|
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
27
27
|
|
|
28
28
|
// src/components/WorkflowViewer.tsx
|
|
29
29
|
var import_react2 = require("react");
|
|
30
|
+
var import_workflow_graph = require("@cyoda/workflow-graph");
|
|
30
31
|
|
|
31
32
|
// src/theme/tokens.ts
|
|
32
33
|
var workflowPalette = {
|
|
@@ -152,6 +153,29 @@ function simpleLayout(graph) {
|
|
|
152
153
|
}
|
|
153
154
|
return { positions, width: maxWidth + 24, height: yCursor };
|
|
154
155
|
}
|
|
156
|
+
function nudgeLabels(items) {
|
|
157
|
+
const sorted = [...items].sort((a, b) => a.midX - b.midX || a.midY - b.midY);
|
|
158
|
+
const placed = [];
|
|
159
|
+
const result = /* @__PURE__ */ new Map();
|
|
160
|
+
for (const item of sorted) {
|
|
161
|
+
const { midX } = item;
|
|
162
|
+
let { midY } = item;
|
|
163
|
+
const halfW = item.pillW / 2;
|
|
164
|
+
const halfH = item.pillH / 2;
|
|
165
|
+
let attempts = 0;
|
|
166
|
+
while (attempts < 20) {
|
|
167
|
+
const overlaps = placed.some(
|
|
168
|
+
(p) => midX + halfW > p.x - p.w / 2 && midX - halfW < p.x + p.w / 2 && midY + halfH > p.y - p.h / 2 && midY - halfH < p.y + p.h / 2
|
|
169
|
+
);
|
|
170
|
+
if (!overlaps) break;
|
|
171
|
+
midY += item.pillH + 4;
|
|
172
|
+
attempts++;
|
|
173
|
+
}
|
|
174
|
+
placed.push({ x: midX, y: midY, w: item.pillW, h: item.pillH });
|
|
175
|
+
result.set(item.id, { midX, midY });
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
155
179
|
function groupByWorkflow(nodes) {
|
|
156
180
|
const out = /* @__PURE__ */ new Map();
|
|
157
181
|
for (const n of nodes) {
|
|
@@ -272,8 +296,7 @@ function laneColor(edge, opts) {
|
|
|
272
296
|
return e.automated;
|
|
273
297
|
}
|
|
274
298
|
function laneDashArray(edge) {
|
|
275
|
-
if (edge.
|
|
276
|
-
if (edge.isLoopback) return "6 4";
|
|
299
|
+
if (edge.manual) return "2 4";
|
|
277
300
|
return void 0;
|
|
278
301
|
}
|
|
279
302
|
|
|
@@ -288,9 +311,9 @@ function polylineToPath(points) {
|
|
|
288
311
|
}
|
|
289
312
|
function computeEdgeGeometry(edge, source, target) {
|
|
290
313
|
const sx = source.x + source.width / 2;
|
|
291
|
-
const sy = source.y + source.height
|
|
314
|
+
const sy = source.y + source.height;
|
|
292
315
|
const tx = target.x + target.width / 2;
|
|
293
|
-
const ty = target.y
|
|
316
|
+
const ty = target.y;
|
|
294
317
|
if (edge.isSelf) {
|
|
295
318
|
const rightX = source.x + source.width;
|
|
296
319
|
const topY = source.y + source.height / 3;
|
|
@@ -326,7 +349,6 @@ function EdgePath({
|
|
|
326
349
|
const d = route && route.points.length >= 2 ? polylineToPath(route.points) : computeEdgeGeometry(edge, source, target).d;
|
|
327
350
|
const strokeWidth = selected || highlighted ? geometry.edge.strokeWidth + 0.8 : edge.isLoopback ? geometry.edge.loopStrokeWidth : geometry.edge.strokeWidth;
|
|
328
351
|
const opacity = dimmed ? 0.25 : 1;
|
|
329
|
-
const isManualSolid = edge.manual && !edge.disabled && !edge.isLoopback;
|
|
330
352
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
331
353
|
"g",
|
|
332
354
|
{
|
|
@@ -351,16 +373,6 @@ function EdgePath({
|
|
|
351
373
|
strokeDasharray: dash,
|
|
352
374
|
markerEnd: `url(#wf-arrow-${colorKey(color)})`
|
|
353
375
|
}
|
|
354
|
-
),
|
|
355
|
-
isManualSolid && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
356
|
-
"path",
|
|
357
|
-
{
|
|
358
|
-
d,
|
|
359
|
-
fill: "none",
|
|
360
|
-
stroke: workflowPalette.neutrals.white,
|
|
361
|
-
strokeWidth: 0.6,
|
|
362
|
-
pointerEvents: "none"
|
|
363
|
-
}
|
|
364
376
|
)
|
|
365
377
|
]
|
|
366
378
|
}
|
|
@@ -449,6 +461,7 @@ function StartMarker({ position }) {
|
|
|
449
461
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("g", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
450
462
|
"circle",
|
|
451
463
|
{
|
|
464
|
+
"data-testid": "start-marker",
|
|
452
465
|
cx,
|
|
453
466
|
cy,
|
|
454
467
|
r,
|
|
@@ -464,7 +477,7 @@ function roleCategoryLabel(node) {
|
|
|
464
477
|
if (node.role === "initial" || node.role === "initial-terminal") return "INITIAL";
|
|
465
478
|
if (node.role === "terminal") return "TERMINAL";
|
|
466
479
|
if (node.category === "MANUAL_REVIEW") return "MANUAL REVIEW";
|
|
467
|
-
if (node.category === "PROCESSING_STATE") return "PROCESSING
|
|
480
|
+
if (node.category === "PROCESSING_STATE") return "PROCESSING";
|
|
468
481
|
return "STATE";
|
|
469
482
|
}
|
|
470
483
|
|
|
@@ -481,7 +494,6 @@ function paletteFor(node) {
|
|
|
481
494
|
// src/theme/badges.ts
|
|
482
495
|
function badgesFor(summary, flags) {
|
|
483
496
|
const out = [];
|
|
484
|
-
if (flags.manual) out.push({ key: "manual", label: "Manual" });
|
|
485
497
|
if (summary.processor) {
|
|
486
498
|
if (summary.processor.kind === "single") {
|
|
487
499
|
out.push({ key: "processor", label: summary.processor.name });
|
|
@@ -497,11 +509,6 @@ function badgesFor(summary, flags) {
|
|
|
497
509
|
out.push({ key: "criterion", label: "Criterion" });
|
|
498
510
|
}
|
|
499
511
|
}
|
|
500
|
-
if (summary.execution?.kind === "sync") {
|
|
501
|
-
out.push({ key: "execution", label: "SYNC" });
|
|
502
|
-
} else if (summary.execution?.kind === "asyncSameTx") {
|
|
503
|
-
out.push({ key: "execution", label: "ASYNC_SAME_TX" });
|
|
504
|
-
}
|
|
505
512
|
if (flags.disabled) out.push({ key: "disabled", label: "Disabled" });
|
|
506
513
|
return out;
|
|
507
514
|
}
|
|
@@ -745,25 +752,47 @@ function pickBadgePalette(key) {
|
|
|
745
752
|
// src/components/WorkflowViewer.tsx
|
|
746
753
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
747
754
|
function WorkflowViewer({
|
|
748
|
-
graph,
|
|
755
|
+
graph: graphInput,
|
|
756
|
+
document,
|
|
749
757
|
layout,
|
|
750
758
|
width = "100%",
|
|
751
759
|
height = "100%",
|
|
752
760
|
selectedId,
|
|
753
761
|
onSelectionChange,
|
|
762
|
+
surface = "website",
|
|
763
|
+
layoutMode,
|
|
764
|
+
viewerLayout,
|
|
765
|
+
interaction = "hover-highlight",
|
|
766
|
+
onInspect,
|
|
767
|
+
showStartMarker = false,
|
|
754
768
|
className
|
|
755
769
|
}) {
|
|
770
|
+
const graph = (0, import_react2.useMemo)(() => {
|
|
771
|
+
if (graphInput) return graphInput;
|
|
772
|
+
if (document) return (0, import_workflow_graph.projectToGraph)(document);
|
|
773
|
+
throw new Error("WorkflowViewer requires either graph or document.");
|
|
774
|
+
}, [graphInput, document]);
|
|
775
|
+
const visibleGraph = (0, import_react2.useMemo)(() => {
|
|
776
|
+
if (showStartMarker) return graph;
|
|
777
|
+
return {
|
|
778
|
+
...graph,
|
|
779
|
+
nodes: graph.nodes.filter((node) => node.kind !== "startMarker"),
|
|
780
|
+
edges: graph.edges.filter((edge) => edge.kind !== "startMarker")
|
|
781
|
+
};
|
|
782
|
+
}, [graph, showStartMarker]);
|
|
783
|
+
const graphLayout = typeof layout === "string" ? void 0 : layout;
|
|
784
|
+
const productLayout = viewerLayout ?? layoutMode ?? (typeof layout === "string" ? layout : void 0) ?? "embedded";
|
|
756
785
|
const effectiveLayout = (0, import_react2.useMemo)(
|
|
757
|
-
() =>
|
|
758
|
-
[
|
|
786
|
+
() => normalizeLayoutForVisibleGraph(graphLayout, visibleGraph) ?? simpleLayout(visibleGraph),
|
|
787
|
+
[graphLayout, visibleGraph]
|
|
759
788
|
);
|
|
760
789
|
const pan = usePanZoom();
|
|
761
790
|
const [internalSelection, setInternalSelection] = (0, import_react2.useState)(null);
|
|
762
791
|
const [hovered, setHovered] = (0, import_react2.useState)(null);
|
|
763
792
|
const selection = selectedId ?? internalSelection;
|
|
764
793
|
const stateNodes = (0, import_react2.useMemo)(
|
|
765
|
-
() =>
|
|
766
|
-
[
|
|
794
|
+
() => visibleGraph.nodes.filter((n) => n.kind === "state"),
|
|
795
|
+
[visibleGraph.nodes]
|
|
767
796
|
);
|
|
768
797
|
const stateById = (0, import_react2.useMemo)(() => {
|
|
769
798
|
const m = /* @__PURE__ */ new Map();
|
|
@@ -771,22 +800,71 @@ function WorkflowViewer({
|
|
|
771
800
|
return m;
|
|
772
801
|
}, [stateNodes]);
|
|
773
802
|
const transitionEdges = (0, import_react2.useMemo)(
|
|
774
|
-
() =>
|
|
775
|
-
[
|
|
776
|
-
);
|
|
777
|
-
const highlightSet = (0, import_react2.useMemo)(
|
|
778
|
-
() => computeHighlightSet(hovered ?? selection, graph.nodes, graph.edges),
|
|
779
|
-
[hovered, selection, graph.nodes, graph.edges]
|
|
803
|
+
() => visibleGraph.edges.filter((e) => e.kind === "transition"),
|
|
804
|
+
[visibleGraph.edges]
|
|
780
805
|
);
|
|
806
|
+
(0, import_react2.useEffect)(() => {
|
|
807
|
+
if (process.env.NODE_ENV === "production" || graphLayout) return;
|
|
808
|
+
const sourceCounts = /* @__PURE__ */ new Map();
|
|
809
|
+
for (const e of graph.edges) {
|
|
810
|
+
if (e.kind !== "transition") continue;
|
|
811
|
+
sourceCounts.set(e.sourceId, (sourceCounts.get(e.sourceId) ?? 0) + 1);
|
|
812
|
+
}
|
|
813
|
+
if ([...sourceCounts.values()].some((n) => n > 1)) {
|
|
814
|
+
console.warn(
|
|
815
|
+
"[WorkflowViewer] Rendering without an ELK layout \u2014 branching graphs may not look polished. Pass a layout from `layoutGraph()` (@cyoda/workflow-layout) for best results."
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
}, [graphLayout, visibleGraph.edges]);
|
|
819
|
+
const fallbackLabelPositions = (0, import_react2.useMemo)(() => {
|
|
820
|
+
if (effectiveLayout.edges) return null;
|
|
821
|
+
const CHAR_W = 6.5;
|
|
822
|
+
const PILL_H = 24;
|
|
823
|
+
const items = transitionEdges.flatMap((edge) => {
|
|
824
|
+
const source = effectiveLayout.positions.get(edge.sourceId);
|
|
825
|
+
const target = effectiveLayout.positions.get(edge.targetId);
|
|
826
|
+
if (!source || !target) return [];
|
|
827
|
+
const { midX, midY } = computeEdgeGeometry(edge, source, target);
|
|
828
|
+
const pillW = Math.max(40, edge.summary.display.length * CHAR_W + 12);
|
|
829
|
+
return [{ id: edge.id, midX, midY, pillW, pillH: PILL_H }];
|
|
830
|
+
});
|
|
831
|
+
return nudgeLabels(items);
|
|
832
|
+
}, [effectiveLayout, transitionEdges]);
|
|
833
|
+
const focusId = hovered ?? selection;
|
|
834
|
+
const highlightSet = (0, import_react2.useMemo)(() => {
|
|
835
|
+
if (interaction === "none") return null;
|
|
836
|
+
if (interaction === "select") {
|
|
837
|
+
return (0, import_workflow_graph.computeHighlightSet)(selection, visibleGraph.nodes, visibleGraph.edges);
|
|
838
|
+
}
|
|
839
|
+
return (0, import_workflow_graph.computeHighlightSet)(focusId, visibleGraph.nodes, visibleGraph.edges);
|
|
840
|
+
}, [interaction, focusId, selection, visibleGraph.nodes, visibleGraph.edges]);
|
|
781
841
|
const anythingFocused = highlightSet !== null;
|
|
782
842
|
const handleSelect = (id) => {
|
|
843
|
+
if (interaction === "none") return;
|
|
783
844
|
setInternalSelection(id);
|
|
784
845
|
onSelectionChange?.(id);
|
|
785
846
|
};
|
|
786
847
|
const handleBackgroundClick = () => {
|
|
848
|
+
if (interaction === "none") return;
|
|
787
849
|
setInternalSelection(null);
|
|
788
850
|
onSelectionChange?.(null);
|
|
789
851
|
};
|
|
852
|
+
const handleHoverEnter = (id) => {
|
|
853
|
+
if (interaction === "hover-highlight" || interaction === "hover-path") {
|
|
854
|
+
setHovered(id);
|
|
855
|
+
}
|
|
856
|
+
if (interaction === "hover-path") {
|
|
857
|
+
onInspect?.((0, import_workflow_graph.inspectGraphFocus)(visibleGraph, id));
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
const handleHoverLeave = () => {
|
|
861
|
+
if (interaction === "hover-highlight" || interaction === "hover-path") {
|
|
862
|
+
setHovered(null);
|
|
863
|
+
}
|
|
864
|
+
if (interaction === "hover-path") {
|
|
865
|
+
onInspect?.(null);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
790
868
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
791
869
|
"svg",
|
|
792
870
|
{
|
|
@@ -804,8 +882,12 @@ function WorkflowViewer({
|
|
|
804
882
|
style: {
|
|
805
883
|
background: workflowPalette.neutrals.white,
|
|
806
884
|
fontFamily: "inherit",
|
|
807
|
-
userSelect: "none"
|
|
885
|
+
userSelect: "none",
|
|
886
|
+
...productLayout === "fullWidth" ? { display: "block", width: "100%", height: "100%" } : null
|
|
808
887
|
},
|
|
888
|
+
"data-surface": surface,
|
|
889
|
+
"data-layout": productLayout,
|
|
890
|
+
"data-interaction": interaction,
|
|
809
891
|
"data-testid": "workflow-viewer",
|
|
810
892
|
children: [
|
|
811
893
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Defs, {}),
|
|
@@ -835,8 +917,8 @@ function WorkflowViewer({
|
|
|
835
917
|
dimmed: isDimmed,
|
|
836
918
|
selected: isEdgeSelected,
|
|
837
919
|
onSelect: handleSelect,
|
|
838
|
-
onHoverEnter:
|
|
839
|
-
onHoverLeave:
|
|
920
|
+
onHoverEnter: handleHoverEnter,
|
|
921
|
+
onHoverLeave: handleHoverLeave
|
|
840
922
|
},
|
|
841
923
|
edge.id
|
|
842
924
|
);
|
|
@@ -846,7 +928,7 @@ function WorkflowViewer({
|
|
|
846
928
|
const target = effectiveLayout.positions.get(edge.targetId);
|
|
847
929
|
if (!source || !target) return null;
|
|
848
930
|
const route = effectiveLayout.edges?.get(edge.id);
|
|
849
|
-
const labelPos = route ? { midX: route.labelX, midY: route.labelY } : computeEdgeGeometry(edge, source, target);
|
|
931
|
+
const labelPos = route ? { midX: route.labelX, midY: route.labelY } : fallbackLabelPositions?.get(edge.id) ?? computeEdgeGeometry(edge, source, target);
|
|
850
932
|
const isHighlighted = highlightSet?.has(edge.id) ?? false;
|
|
851
933
|
const isDimmed = anythingFocused && !isHighlighted;
|
|
852
934
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -862,13 +944,13 @@ function WorkflowViewer({
|
|
|
862
944
|
`label-${edge.id}`
|
|
863
945
|
);
|
|
864
946
|
}),
|
|
865
|
-
|
|
947
|
+
visibleGraph.nodes.map((node) => renderNode(node, effectiveLayout, {
|
|
866
948
|
selection,
|
|
867
949
|
highlightSet,
|
|
868
950
|
anythingFocused,
|
|
869
951
|
onSelect: handleSelect,
|
|
870
|
-
onHoverEnter:
|
|
871
|
-
onHoverLeave:
|
|
952
|
+
onHoverEnter: handleHoverEnter,
|
|
953
|
+
onHoverLeave: handleHoverLeave
|
|
872
954
|
}))
|
|
873
955
|
]
|
|
874
956
|
}
|
|
@@ -877,6 +959,30 @@ function WorkflowViewer({
|
|
|
877
959
|
}
|
|
878
960
|
);
|
|
879
961
|
}
|
|
962
|
+
function normalizeLayoutForVisibleGraph(layout, graph) {
|
|
963
|
+
if (!layout) return void 0;
|
|
964
|
+
const visibleNodeIds = new Set(graph.nodes.map((node) => node.id));
|
|
965
|
+
const positions = new Map(
|
|
966
|
+
Array.from(layout.positions.entries()).filter(([nodeId]) => visibleNodeIds.has(nodeId))
|
|
967
|
+
);
|
|
968
|
+
const visibleEdgeIds = new Set(graph.edges.map((edge) => edge.id));
|
|
969
|
+
const edges = layout.edges ? new Map(Array.from(layout.edges.entries()).filter(([edgeId]) => visibleEdgeIds.has(edgeId))) : void 0;
|
|
970
|
+
return {
|
|
971
|
+
...layout,
|
|
972
|
+
positions,
|
|
973
|
+
edges,
|
|
974
|
+
width: computeLayoutBound(positions, "x"),
|
|
975
|
+
height: computeLayoutBound(positions, "y")
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
function computeLayoutBound(positions, axis) {
|
|
979
|
+
let max = 0;
|
|
980
|
+
for (const position of positions.values()) {
|
|
981
|
+
const bound = axis === "x" ? position.x + position.width : position.y + position.height;
|
|
982
|
+
if (bound > max) max = bound;
|
|
983
|
+
}
|
|
984
|
+
return Math.max(max + 24, 72);
|
|
985
|
+
}
|
|
880
986
|
function renderNode(node, layout, ctx) {
|
|
881
987
|
const pos = layout.positions.get(node.id);
|
|
882
988
|
if (!pos) return null;
|
|
@@ -910,29 +1016,6 @@ function smallPositionForMarker(pos) {
|
|
|
910
1016
|
height: size
|
|
911
1017
|
};
|
|
912
1018
|
}
|
|
913
|
-
function computeHighlightSet(focusedId, nodes, edges) {
|
|
914
|
-
if (!focusedId) return null;
|
|
915
|
-
const set = /* @__PURE__ */ new Set();
|
|
916
|
-
set.add(focusedId);
|
|
917
|
-
const node = nodes.find((n) => n.id === focusedId);
|
|
918
|
-
if (node) {
|
|
919
|
-
for (const e of edges) {
|
|
920
|
-
if (e.kind !== "transition") continue;
|
|
921
|
-
if (e.sourceId === focusedId || e.targetId === focusedId) {
|
|
922
|
-
set.add(e.id);
|
|
923
|
-
set.add(e.sourceId);
|
|
924
|
-
set.add(e.targetId);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
return set;
|
|
928
|
-
}
|
|
929
|
-
const edge = edges.find((e) => e.id === focusedId);
|
|
930
|
-
if (edge && edge.kind === "transition") {
|
|
931
|
-
set.add(edge.sourceId);
|
|
932
|
-
set.add(edge.targetId);
|
|
933
|
-
}
|
|
934
|
-
return set;
|
|
935
|
-
}
|
|
936
1019
|
// Annotate the CommonJS export names for ESM import in node:
|
|
937
1020
|
0 && (module.exports = {
|
|
938
1021
|
WorkflowViewer,
|