@coldtea/pr-lens-renderer 0.1.3 → 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 +19 -1
- package/package.json +2 -2
- package/src/atlas.ts +56 -0
- package/src/bounds.ts +13 -16
- package/src/corrections.ts +9 -2
- package/src/geometry.ts +4 -3
- package/src/index.ts +4 -0
- package/src/layout/dataflow.ts +1 -1
- package/src/render.ts +20 -3
- package/src/svg/architecture.ts +23 -1
- package/src/svg/dataflow.ts +81 -2
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,24 @@ pnpm test # compares against the goldens
|
|
|
58
58
|
UPDATE_GOLDENS=1 pnpm test # records them, for a human to read the diff of
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
## The atlas
|
|
62
|
+
|
|
63
|
+
Every render comes back with an `atlas`: where each lane, node, edge and flow step of that picture landed, in the same viewBox units the file is written in.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const { svg, atlas } = render(doc, { lens: "architecture", theme: "dark" });
|
|
67
|
+
atlas.nodes["send-broadcast-bulk"]; // { x, y, width, height }
|
|
68
|
+
atlas.messages["send-pipeline"]?.["batch-post"];
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A surface that plays a document's walkthrough has to put a rim around what a step points at, and a step names its elements by id and nothing else. Only the renderer knows where any of them ended up, so the geometry travels beside the picture rather than being measured back out of it by a reader that would have to guess.
|
|
72
|
+
|
|
73
|
+
Flow steps are keyed by flow first, because a step's id is unique only inside its own flow: two flows may each carry one called `retry`, and neither document is wrong for it.
|
|
74
|
+
|
|
75
|
+
The atlas is as deterministic as the SVG. Same document, same boxes, in the same key order, each coordinate rounded exactly as the one written into the file beside it, so a box and the rectangle it stands for cannot disagree in the last place. The theme never moves anything, so one atlas answers for both halves of a `<picture>` pair.
|
|
76
|
+
|
|
77
|
+
Each lens fills in what it draws. The architecture lens places lanes, cards and edges and no flow step; the data-flow lens places the cards heading its columns and the steps of each flow, and no lane or edge. A node the same render draws twice, heading a column in two stacked flows, gets the box covering both of them: a reader sent to that node is being sent to all of it. A card's box is the card, not the badge strip above it, which is context a veil may dim.
|
|
78
|
+
|
|
61
79
|
## Corrections
|
|
62
80
|
|
|
63
81
|
A repository's `.github/pr-lens.yml` is an overlay, applied here before layout and never written back into inference, so a correction keeps holding as the code moves:
|
|
@@ -68,7 +86,7 @@ render(doc, { lens: "architecture", theme: "dark", config });
|
|
|
68
86
|
|
|
69
87
|
`rename`, `lane` and `group` address nodes by `id:<node-id>` or by a glob over the paths backing them. A `lane` correction may name a band the document never declared; it gets created, with the id for a label, so write `lane: infrastructure` rather than `lane: l3`.
|
|
70
88
|
|
|
71
|
-
`exclude` takes with it every edge and flow step that touched what it removed, every view that pointed at nothing else,
|
|
89
|
+
`exclude` takes with it every edge and flow step that touched what it removed, every view that pointed at nothing else, every layout hint left naming something that is gone, and every walkthrough step whose diagram or last focused element went with it. A walkthrough cut below two steps goes too, because one step is a caption. Half an arrow is worse than none, and the corrected document is a document like any other, so it still parses.
|
|
72
90
|
|
|
73
91
|
## Addresses
|
|
74
92
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coldtea/pr-lens-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The PR Lens renderer: a schema-valid graph document in, a self-contained animated SVG out.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Coldtea AI",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"node": ">=20.11"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@coldtea/pr-lens-schema": "0.
|
|
43
|
+
"@coldtea/pr-lens-schema": "0.2.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^20.19.0",
|
package/src/atlas.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { covering, type Canvas } from "./bounds.js";
|
|
2
|
+
import { roundCoord, type Box } from "./geometry.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Where everything a render drew ended up, in the viewBox units of the file
|
|
6
|
+
* beside it.
|
|
7
|
+
*
|
|
8
|
+
* A surface that plays a walkthrough has to put a rim around what a step
|
|
9
|
+
* points at, and a step points at lanes, nodes, edges and flow steps by id
|
|
10
|
+
* alone. Only the renderer knows where any of them landed, and it knows it
|
|
11
|
+
* while it is drawing them, so the geometry travels with the picture rather
|
|
12
|
+
* than being measured back out of it by something that would have to guess.
|
|
13
|
+
*/
|
|
14
|
+
export type RenderAtlas = {
|
|
15
|
+
lanes: Record<string, Box>;
|
|
16
|
+
nodes: Record<string, Box>;
|
|
17
|
+
edges: Record<string, Box>;
|
|
18
|
+
/** Keyed by flow, then by step: a flow step's id is unique only inside its own flow. */
|
|
19
|
+
messages: Record<string, Record<string, Box>>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** An atlas for a drawing that has placed nothing. */
|
|
23
|
+
export const emptyAtlas = (): RenderAtlas => ({ lanes: {}, nodes: {}, edges: {}, messages: {} });
|
|
24
|
+
|
|
25
|
+
export type AtlasEntry = { id: string; box: Box };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Boxes onto the canvas and into a record, in the order they were drawn.
|
|
29
|
+
*
|
|
30
|
+
* Each coordinate is rounded on its own, exactly as the one written into the
|
|
31
|
+
* file beside it is, so a box here and the rectangle it stands for cannot
|
|
32
|
+
* disagree in the last place. An id drawn more than once — a participant
|
|
33
|
+
* heading a column in two stacked flows — keeps the box covering every
|
|
34
|
+
* drawing of it, because a reader sent to that node is being sent to all of
|
|
35
|
+
* them.
|
|
36
|
+
*/
|
|
37
|
+
export const atlasBoxes = (
|
|
38
|
+
entries: readonly AtlasEntry[],
|
|
39
|
+
canvas: Canvas,
|
|
40
|
+
): Record<string, Box> => {
|
|
41
|
+
const grown = new Map<string, Box>();
|
|
42
|
+
for (const { id, box } of entries) {
|
|
43
|
+
const current = grown.get(id);
|
|
44
|
+
grown.set(id, current === undefined ? box : covering(current, box));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const boxes: Record<string, Box> = {};
|
|
48
|
+
for (const [id, box] of grown)
|
|
49
|
+
boxes[id] = {
|
|
50
|
+
x: roundCoord(box.x + canvas.shiftX),
|
|
51
|
+
y: roundCoord(box.y + canvas.shiftY),
|
|
52
|
+
width: roundCoord(box.width),
|
|
53
|
+
height: roundCoord(box.height),
|
|
54
|
+
};
|
|
55
|
+
return boxes;
|
|
56
|
+
};
|
package/src/bounds.ts
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
import type { Box } from "./geometry.js";
|
|
2
2
|
|
|
3
|
-
/** The smallest box containing
|
|
4
|
-
export const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let top = first.y;
|
|
10
|
-
let right = first.x + first.width;
|
|
11
|
-
let bottom = first.y + first.height;
|
|
12
|
-
|
|
13
|
-
for (const box of boxes) {
|
|
14
|
-
left = Math.min(left, box.x);
|
|
15
|
-
top = Math.min(top, box.y);
|
|
16
|
-
right = Math.max(right, box.x + box.width);
|
|
17
|
-
bottom = Math.max(bottom, box.y + box.height);
|
|
18
|
-
}
|
|
3
|
+
/** The smallest box containing both of them. */
|
|
4
|
+
export const covering = (a: Box, b: Box): Box => {
|
|
5
|
+
const left = Math.min(a.x, b.x);
|
|
6
|
+
const top = Math.min(a.y, b.y);
|
|
7
|
+
const right = Math.max(a.x + a.width, b.x + b.width);
|
|
8
|
+
const bottom = Math.max(a.y + a.height, b.y + b.height);
|
|
19
9
|
|
|
20
10
|
return { x: left, y: top, width: right - left, height: bottom - top };
|
|
21
11
|
};
|
|
22
12
|
|
|
13
|
+
/** The smallest box containing all of them, or nothing when there are none. */
|
|
14
|
+
export const union = (boxes: readonly Box[]): Box | undefined =>
|
|
15
|
+
boxes.reduce<Box | undefined>(
|
|
16
|
+
(grown, box) => (grown === undefined ? box : covering(grown, box)),
|
|
17
|
+
undefined,
|
|
18
|
+
);
|
|
19
|
+
|
|
23
20
|
export type Canvas = { width: number; height: number; shiftX: number; shiftY: number };
|
|
24
21
|
|
|
25
22
|
/**
|
package/src/corrections.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
View,
|
|
10
10
|
ViewScope,
|
|
11
11
|
} from "@coldtea/pr-lens-schema";
|
|
12
|
-
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
12
|
+
import { assertNever, pruneWalkthrough } from "@coldtea/pr-lens-schema";
|
|
13
13
|
import { PrLensRenderError } from "./errors.js";
|
|
14
14
|
import { matchesGlob } from "./glob.js";
|
|
15
15
|
|
|
@@ -129,13 +129,20 @@ export const applyCorrections = (doc: GraphDoc, corrections: MapCorrections): Gr
|
|
|
129
129
|
flow: (id: string) => flows.some((flow) => flow.id === id),
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
+
const views = narrowViews(doc.views, survives);
|
|
133
|
+
|
|
132
134
|
return {
|
|
133
135
|
...doc,
|
|
134
136
|
lanes,
|
|
135
137
|
nodes,
|
|
136
138
|
edges,
|
|
137
139
|
flows,
|
|
138
|
-
views
|
|
140
|
+
views,
|
|
141
|
+
/**
|
|
142
|
+
* Narrowed against the document that came through, views included: a step
|
|
143
|
+
* can play over a view the overlay has just emptied out.
|
|
144
|
+
*/
|
|
145
|
+
walkthrough: pruneWalkthrough(doc.walkthrough, { lanes, nodes, edges, flows, views }),
|
|
139
146
|
layout: narrowLayout(doc.layout, survives),
|
|
140
147
|
};
|
|
141
148
|
};
|
package/src/geometry.ts
CHANGED
|
@@ -10,12 +10,13 @@ export type Side = "top" | "right" | "bottom" | "left";
|
|
|
10
10
|
* arithmetic that differs in the last bit would otherwise change the bytes of
|
|
11
11
|
* the document without changing the picture, and the render hash with it.
|
|
12
12
|
*/
|
|
13
|
-
export const
|
|
13
|
+
export const roundCoord = (value: number): number => {
|
|
14
14
|
const rounded = Math.round(value * 100) / 100;
|
|
15
|
-
|
|
16
|
-
return String(normalised);
|
|
15
|
+
return Object.is(rounded, -0) ? 0 : rounded;
|
|
17
16
|
};
|
|
18
17
|
|
|
18
|
+
export const coord = (value: number): string => String(roundCoord(value));
|
|
19
|
+
|
|
19
20
|
export const boxCentre = (box: Box): Point => ({
|
|
20
21
|
x: box.x + box.width / 2,
|
|
21
22
|
y: box.y + box.height / 2,
|
package/src/index.ts
CHANGED
|
@@ -10,8 +10,12 @@ export {
|
|
|
10
10
|
type RenderOptions,
|
|
11
11
|
} from "./render.js";
|
|
12
12
|
|
|
13
|
+
export { emptyAtlas, type RenderAtlas } from "./atlas.js";
|
|
14
|
+
|
|
13
15
|
export { PrLensRenderError, type RenderErrorCode } from "./errors.js";
|
|
14
16
|
|
|
17
|
+
export type { Box } from "./geometry.js";
|
|
18
|
+
|
|
15
19
|
export { paletteFor, THEMES, type Palette, type Theme } from "./theme.js";
|
|
16
20
|
|
|
17
21
|
export {
|
package/src/layout/dataflow.ts
CHANGED
package/src/render.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Config, GraphDoc, Lens, RenderAsset, RenderManifest, View, ViewScope } from "@coldtea/pr-lens-schema";
|
|
2
2
|
import { assertNever, MAX_RENDER_ASSETS } from "@coldtea/pr-lens-schema";
|
|
3
|
+
import type { RenderAtlas } from "./atlas.js";
|
|
3
4
|
import { applyCorrections } from "./corrections.js";
|
|
4
5
|
import { PrLensRenderError } from "./errors.js";
|
|
5
6
|
import { buildManifest, contentHash, renderAssetFileName, renderAssetId } from "./manifest.js";
|
|
@@ -26,6 +27,8 @@ export type RenderedSvg = {
|
|
|
26
27
|
theme: Theme;
|
|
27
28
|
view: string | undefined;
|
|
28
29
|
animated: boolean;
|
|
30
|
+
/** Where every lane, node, edge and flow step of this picture landed. */
|
|
31
|
+
atlas: RenderAtlas;
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
const WHOLE_DOCUMENT: ViewScope = { kind: "all" };
|
|
@@ -38,7 +41,7 @@ const paint = (
|
|
|
38
41
|
graph: ScopedGraph,
|
|
39
42
|
doc: GraphDoc,
|
|
40
43
|
theme: Theme,
|
|
41
|
-
): { width: number; height: number; body: string; animated: boolean } => {
|
|
44
|
+
): { width: number; height: number; body: string; animated: boolean; atlas: RenderAtlas } => {
|
|
42
45
|
const palette = paletteFor(theme);
|
|
43
46
|
|
|
44
47
|
switch (lens) {
|
|
@@ -84,7 +87,12 @@ export const render = (doc: GraphDoc, options: RenderOptions): RenderedSvg => {
|
|
|
84
87
|
const scope = view?.scope ?? WHOLE_DOCUMENT;
|
|
85
88
|
const graph = resolveScope(prepared, scope);
|
|
86
89
|
|
|
87
|
-
const { width, height, body, animated } = paint(
|
|
90
|
+
const { width, height, body, animated, atlas } = paint(
|
|
91
|
+
options.lens,
|
|
92
|
+
graph,
|
|
93
|
+
prepared,
|
|
94
|
+
options.theme,
|
|
95
|
+
);
|
|
88
96
|
|
|
89
97
|
const svg = svgDocument({
|
|
90
98
|
width,
|
|
@@ -95,7 +103,16 @@ export const render = (doc: GraphDoc, options: RenderOptions): RenderedSvg => {
|
|
|
95
103
|
body,
|
|
96
104
|
});
|
|
97
105
|
|
|
98
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
svg,
|
|
108
|
+
width,
|
|
109
|
+
height,
|
|
110
|
+
lens: options.lens,
|
|
111
|
+
theme: options.theme,
|
|
112
|
+
view: view?.id,
|
|
113
|
+
animated,
|
|
114
|
+
atlas,
|
|
115
|
+
};
|
|
99
116
|
};
|
|
100
117
|
|
|
101
118
|
const requireView = (views: readonly View[], id: string): View => {
|
package/src/svg/architecture.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
LANE_RADIUS,
|
|
17
17
|
SUBTITLE_SIZE,
|
|
18
18
|
} from "../design.js";
|
|
19
|
+
import { atlasBoxes, emptyAtlas, type RenderAtlas } from "../atlas.js";
|
|
19
20
|
import { canvasFor, union } from "../bounds.js";
|
|
20
21
|
import { DIAGRAM_MARGIN } from "../design.js";
|
|
21
22
|
import { coord, type Box } from "../geometry.js";
|
|
@@ -228,7 +229,12 @@ export const paintLabelPill = (text: string, box: Box, tone: Tone): string =>
|
|
|
228
229
|
),
|
|
229
230
|
);
|
|
230
231
|
|
|
231
|
-
export type ArchitecturePainting = {
|
|
232
|
+
export type ArchitecturePainting = {
|
|
233
|
+
width: number;
|
|
234
|
+
height: number;
|
|
235
|
+
body: string;
|
|
236
|
+
atlas: RenderAtlas;
|
|
237
|
+
};
|
|
232
238
|
|
|
233
239
|
export const paintArchitecture = (
|
|
234
240
|
graph: ScopedGraph,
|
|
@@ -290,5 +296,21 @@ export const paintArchitecture = (
|
|
|
290
296
|
width: canvas.width,
|
|
291
297
|
height: canvas.height,
|
|
292
298
|
body: shifted(canvas, painted),
|
|
299
|
+
atlas: {
|
|
300
|
+
...emptyAtlas(),
|
|
301
|
+
lanes: atlasBoxes(
|
|
302
|
+
layout.lanes.map(({ lane, box }) => ({ id: lane.id, box })),
|
|
303
|
+
canvas,
|
|
304
|
+
),
|
|
305
|
+
/** The card, not the badge strip above it: a badge is context the veil may dim. */
|
|
306
|
+
nodes: atlasBoxes(
|
|
307
|
+
layout.nodes.map(({ node, box }) => ({ id: node.id, box })),
|
|
308
|
+
canvas,
|
|
309
|
+
),
|
|
310
|
+
edges: atlasBoxes(
|
|
311
|
+
routed.map(({ edge, curve }) => ({ id: edge.id, box: curveBounds(curve) })),
|
|
312
|
+
canvas,
|
|
313
|
+
),
|
|
314
|
+
},
|
|
293
315
|
};
|
|
294
316
|
};
|
package/src/svg/dataflow.ts
CHANGED
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
PILL_TEXT_SIZE,
|
|
13
13
|
TITLE_SIZE,
|
|
14
14
|
} from "../design.js";
|
|
15
|
-
import {
|
|
15
|
+
import { atlasBoxes, emptyAtlas, type RenderAtlas } from "../atlas.js";
|
|
16
|
+
import { canvasFor, covering, union, type Canvas } from "../bounds.js";
|
|
16
17
|
import type { Box } from "../geometry.js";
|
|
17
18
|
import { measure } from "../text.js";
|
|
18
19
|
import { coord } from "../geometry.js";
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
FLOW_BAND_PAD_Y,
|
|
23
24
|
layoutDataFlow,
|
|
24
25
|
MARKER_INSET,
|
|
26
|
+
messagePitch,
|
|
25
27
|
PARTICIPANT_TOP,
|
|
26
28
|
SELF_LOOP_CORNER,
|
|
27
29
|
SELF_LOOP_DROP,
|
|
@@ -378,7 +380,68 @@ const flowBounds = (layout: FlowLayout, columnWidth: number): Box[] => {
|
|
|
378
380
|
return [...bands, ...pills, ...loops];
|
|
379
381
|
};
|
|
380
382
|
|
|
381
|
-
|
|
383
|
+
/**
|
|
384
|
+
* The row a step owns: what it draws, grown to the pitch the layout gave it.
|
|
385
|
+
*
|
|
386
|
+
* An arrow is a line and a loop is barely taller, so the tight bounds of
|
|
387
|
+
* either make a poor thing to put a rim around. The row is the honest unit —
|
|
388
|
+
* it is the space the layout set aside for this step and no other, so two
|
|
389
|
+
* neighbouring steps can never claim the same band. The label comes with it:
|
|
390
|
+
* a step lit without its own words is a step a reader cannot name.
|
|
391
|
+
*/
|
|
392
|
+
const messageBox = (placed: PlacedMessage, activeAt: ActiveAt): Box => {
|
|
393
|
+
const direction = travelDirection(placed.message.kind, placed.fromX, placed.toX);
|
|
394
|
+
|
|
395
|
+
const drawn =
|
|
396
|
+
direction === 0
|
|
397
|
+
? selfDrawn(placed, activeAt(placed.message.from, placed.y))
|
|
398
|
+
: covering(straightDrawn(placed), pillBox(placed, endsFor(placed, activeAt, direction)));
|
|
399
|
+
|
|
400
|
+
const pitch = messagePitch(placed.message);
|
|
401
|
+
return {
|
|
402
|
+
x: drawn.x,
|
|
403
|
+
y: drawn.y + drawn.height / 2 - pitch / 2,
|
|
404
|
+
width: drawn.width,
|
|
405
|
+
height: pitch,
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const straightDrawn = (placed: PlacedMessage): Box => ({
|
|
410
|
+
x: Math.min(placed.fromX, placed.toX),
|
|
411
|
+
y: placed.y,
|
|
412
|
+
width: Math.abs(placed.toX - placed.fromX),
|
|
413
|
+
height: 0,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const selfDrawn = (placed: PlacedMessage, activated: boolean): Box =>
|
|
417
|
+
covering(
|
|
418
|
+
{
|
|
419
|
+
x: placed.fromX,
|
|
420
|
+
y: placed.y,
|
|
421
|
+
width:
|
|
422
|
+
(activated ? ACTIVATION_HALF_WIDTH : 0) + SELF_LOOP_REACH + SELF_LOOP_CORNER,
|
|
423
|
+
height: SELF_LOOP_EXTENT,
|
|
424
|
+
},
|
|
425
|
+
selfPillBox(placed, activated),
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
const flowAtlas = (layout: FlowLayout, canvas: Canvas): Record<string, Box> => {
|
|
429
|
+
const activeAt = activationLookup(layout);
|
|
430
|
+
return atlasBoxes(
|
|
431
|
+
layout.messages.map((placed) => ({
|
|
432
|
+
id: placed.message.id,
|
|
433
|
+
box: messageBox(placed, activeAt),
|
|
434
|
+
})),
|
|
435
|
+
canvas,
|
|
436
|
+
);
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
export type DataFlowPainting = {
|
|
440
|
+
width: number;
|
|
441
|
+
height: number;
|
|
442
|
+
body: string;
|
|
443
|
+
atlas: RenderAtlas;
|
|
444
|
+
};
|
|
382
445
|
|
|
383
446
|
export const paintDataFlow = (
|
|
384
447
|
flows: readonly Flow[],
|
|
@@ -404,5 +467,21 @@ export const paintDataFlow = (
|
|
|
404
467
|
),
|
|
405
468
|
),
|
|
406
469
|
),
|
|
470
|
+
atlas: {
|
|
471
|
+
...emptyAtlas(),
|
|
472
|
+
/** The card heading a column, which is where this lens draws a node. */
|
|
473
|
+
nodes: atlasBoxes(
|
|
474
|
+
layout.flows.flatMap((flow) =>
|
|
475
|
+
flow.participants.map((participant) => ({
|
|
476
|
+
id: participant.node.id,
|
|
477
|
+
box: participant.card,
|
|
478
|
+
})),
|
|
479
|
+
),
|
|
480
|
+
canvas,
|
|
481
|
+
),
|
|
482
|
+
messages: Object.fromEntries(
|
|
483
|
+
layout.flows.map((flow) => [flow.flow.id, flowAtlas(flow, canvas)]),
|
|
484
|
+
),
|
|
485
|
+
},
|
|
407
486
|
};
|
|
408
487
|
};
|
package/src/version.ts
CHANGED