@weasel-js/diagram 1.10.1 → 1.11.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 +467 -25
- package/dist/{layout.entry-BUkiRKuX.d.ts → body-2lC7IiIA.d.ts} +245 -264
- package/dist/chunk-C4NAI6NV.js +2041 -0
- package/dist/chunk-C4NAI6NV.js.map +1 -0
- package/dist/chunk-NUMMW5BI.js +2865 -0
- package/dist/chunk-NUMMW5BI.js.map +1 -0
- package/dist/effect.d.ts +417 -0
- package/dist/effect.js +1283 -0
- package/dist/effect.js.map +1 -0
- package/dist/fromData-GKqRgPFs.d.ts +379 -0
- package/dist/index.d.ts +181 -327
- package/dist/index.js +2 -1434
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts +2 -1
- package/dist/layout.entry-BoWFcR4G.d.ts +285 -0
- package/dist/layout.js +1 -1
- package/package.json +10 -6
- package/dist/chunk-JZ4CVLAB.js +0 -1247
- package/dist/chunk-JZ4CVLAB.js.map +0 -1
package/dist/effect.js
ADDED
|
@@ -0,0 +1,1283 @@
|
|
|
1
|
+
import { diagramScene, DiagramView, edgeIdOf, LABEL_PLACEMENT, EDGE_DERIVE_PATH, labelCenter } from './chunk-C4NAI6NV.js';
|
|
2
|
+
import { SIDE_TRACK_GAP, portSitesOf } from './chunk-NUMMW5BI.js';
|
|
3
|
+
import { polygonFromPoints } from '@weasel-js/core/math';
|
|
4
|
+
import { useMemo, useEffect, useState, useRef } from 'react';
|
|
5
|
+
import { bitmapSrc, useLatest, pointAlongPath } from '@weasel-js/core';
|
|
6
|
+
import { releaseImageSource } from '@weasel-js/core/renderer';
|
|
7
|
+
import { jsx } from 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
// src/effect/check.ts
|
|
10
|
+
function splitProblem(stage) {
|
|
11
|
+
const split = stage.split;
|
|
12
|
+
if (split === void 0) return null;
|
|
13
|
+
if (!(stage.inputs ?? []).some((p) => p.id === split.input)) return `splits on "${split.input}", which is not one of its inputs`;
|
|
14
|
+
if (split.at.length === 0) return "splits with no cuts";
|
|
15
|
+
if (!split.at.every(Number.isFinite)) return "has a cut that is not a finite number";
|
|
16
|
+
if (split.at.some((cut, i) => i > 0 && cut <= split.at[i - 1])) return "has cuts that are not in ascending order";
|
|
17
|
+
const exits = (stage.outputs ?? []).length;
|
|
18
|
+
if (exits !== split.at.length + 1) return `has ${split.at.length} cuts and ${exits} outputs; it needs ${split.at.length + 1}`;
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function checkPipeline(pipeline) {
|
|
22
|
+
const out = [];
|
|
23
|
+
const stages = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const stage of pipeline.stages) {
|
|
25
|
+
if (stages.has(stage.id)) {
|
|
26
|
+
out.push({ kind: "duplicate-stage", stage: stage.id, message: `Stage "${stage.id}" appears more than once.` });
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
stages.set(stage.id, stage);
|
|
30
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31
|
+
for (const port of [...stage.inputs ?? [], ...stage.outputs ?? []]) {
|
|
32
|
+
if (seen.has(port.id)) {
|
|
33
|
+
out.push({ kind: "duplicate-port", stage: stage.id, port: port.id, message: `Stage "${stage.id}" has two ports called "${port.id}".` });
|
|
34
|
+
}
|
|
35
|
+
seen.add(port.id);
|
|
36
|
+
}
|
|
37
|
+
const split = splitProblem(stage);
|
|
38
|
+
if (split !== null) out.push({ kind: "bad-split", stage: stage.id, message: `Stage "${stage.id}" ${split}.` });
|
|
39
|
+
}
|
|
40
|
+
pipeline.wires.forEach((wire, index) => {
|
|
41
|
+
const ends = [["from", "outputs", "inputs"], ["to", "inputs", "outputs"]].map(([end, own, other]) => {
|
|
42
|
+
const ref = wire[end];
|
|
43
|
+
const stage = stages.get(ref.stage);
|
|
44
|
+
if (stage === void 0) {
|
|
45
|
+
out.push({ kind: "missing-stage", wire: index, stage: ref.stage, message: `Wire ${index} names stage "${ref.stage}", which does not exist.` });
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const port = (stage[own] ?? []).find((p) => p.id === ref.port);
|
|
49
|
+
if (port !== void 0) return port;
|
|
50
|
+
const wrong = (stage[other] ?? []).some((p) => p.id === ref.port);
|
|
51
|
+
out.push(wrong ? { kind: "wrong-direction", wire: index, stage: ref.stage, port: ref.port, message: `Wire ${index} ${end === "from" ? "starts at an input" : "ends at an output"}, "${ref.stage}.${ref.port}".` } : { kind: "missing-port", wire: index, stage: ref.stage, port: ref.port, message: `Wire ${index} names port "${ref.stage}.${ref.port}", which does not exist.` });
|
|
52
|
+
return null;
|
|
53
|
+
});
|
|
54
|
+
const [from, to] = ends;
|
|
55
|
+
if (from && to && from.type !== to.type) {
|
|
56
|
+
out.push({ kind: "type-mismatch", wire: index, message: `Wire ${index} joins a "${from.type}" output to a "${to.type}" input.` });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const groupIds = /* @__PURE__ */ new Set();
|
|
60
|
+
const groups = (pipeline.groups ?? []).filter((group) => {
|
|
61
|
+
const repeat = groupIds.has(group.id);
|
|
62
|
+
if (repeat || stages.has(group.id)) {
|
|
63
|
+
out.push({
|
|
64
|
+
kind: "duplicate-group",
|
|
65
|
+
group: group.id,
|
|
66
|
+
message: repeat ? `Group "${group.id}" appears more than once.` : `Group "${group.id}" has the id of a stage.`
|
|
67
|
+
});
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
groupIds.add(group.id);
|
|
71
|
+
return true;
|
|
72
|
+
});
|
|
73
|
+
for (const group of groups) {
|
|
74
|
+
for (const member of group.members) {
|
|
75
|
+
if (stages.has(member) || groupIds.has(member)) continue;
|
|
76
|
+
out.push({ kind: "missing-member", group: group.id, message: `Group "${group.id}" names "${member}", which is neither a stage nor a group.` });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/effect/draw.ts
|
|
83
|
+
var PALETTE = ["#6fb3ff", "#c58bff", "#f2c14e", "#5ad07f", "#ff8a6b", "#4fd1c5", "#e58fb8", "#b0bec5"];
|
|
84
|
+
var UNTYPED = "#8aa2b5";
|
|
85
|
+
var PROBLEM_COLOR = "#ff5d5d";
|
|
86
|
+
var NORMAL = { fill: "#16222c", stroke: "#3d566b", text: "#dbe7f2", strokeWidth: 1.5 };
|
|
87
|
+
var BYPASSED = { fill: "#121a21", stroke: "#3d566b", text: "#6d8496", strokeWidth: 1.5, dash: [4, 3] };
|
|
88
|
+
function typeColors(pipeline, given = {}) {
|
|
89
|
+
const colors = /* @__PURE__ */ new Map();
|
|
90
|
+
for (const stage of pipeline.stages) {
|
|
91
|
+
for (const port of [...stage.inputs ?? [], ...stage.outputs ?? []]) {
|
|
92
|
+
if (colors.has(port.type)) continue;
|
|
93
|
+
colors.set(port.type, given[port.type] ?? PALETTE[colors.size % PALETTE.length]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return (type) => type === void 0 ? UNTYPED : colors.get(type) ?? given[type] ?? UNTYPED;
|
|
97
|
+
}
|
|
98
|
+
function isControl(pipeline, type) {
|
|
99
|
+
return type !== void 0 && pipeline.types?.[type]?.control === true;
|
|
100
|
+
}
|
|
101
|
+
function stageStyle(stage) {
|
|
102
|
+
return stage.bypassed === true ? BYPASSED : NORMAL;
|
|
103
|
+
}
|
|
104
|
+
function wireStyle(color, control, problem) {
|
|
105
|
+
return {
|
|
106
|
+
stroke: problem ? PROBLEM_COLOR : color,
|
|
107
|
+
width: 2,
|
|
108
|
+
markerEnd: null,
|
|
109
|
+
...control ? { dash: [5, 4] } : {}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function uniqueStages(pipeline) {
|
|
113
|
+
const seen = /* @__PURE__ */ new Set();
|
|
114
|
+
return pipeline.stages.filter((s) => seen.has(s.id) ? false : (seen.add(s.id), true));
|
|
115
|
+
}
|
|
116
|
+
function uniqueGroups(pipeline) {
|
|
117
|
+
const seen = new Set(pipeline.stages.map((s) => s.id));
|
|
118
|
+
return (pipeline.groups ?? []).filter((g) => seen.has(g.id) ? false : (seen.add(g.id), true));
|
|
119
|
+
}
|
|
120
|
+
function portOn(stage, id) {
|
|
121
|
+
return [...stage?.inputs ?? [], ...stage?.outputs ?? []].find((p) => p.id === id);
|
|
122
|
+
}
|
|
123
|
+
function kindOf(stage) {
|
|
124
|
+
return stage.kind === "decision" || stage.kind === "source" || stage.kind === "sink" ? stage.kind : "stage";
|
|
125
|
+
}
|
|
126
|
+
function drawableWires(pipeline) {
|
|
127
|
+
const stages = new Map(uniqueStages(pipeline).map((s) => [s.id, s]));
|
|
128
|
+
const flagged = new Set(checkPipeline(pipeline).flatMap((p) => p.wire === void 0 ? [] : [p.wire]));
|
|
129
|
+
return pipeline.wires.flatMap((wire, index) => {
|
|
130
|
+
const fromStage = stages.get(wire.from.stage);
|
|
131
|
+
const toStage = stages.get(wire.to.stage);
|
|
132
|
+
const from = portOn(fromStage, wire.from.port);
|
|
133
|
+
if (fromStage === void 0 || toStage === void 0 || from === void 0 || portOn(toStage, wire.to.port) === void 0) return [];
|
|
134
|
+
return [{ wire, index, type: from.type, problem: flagged.has(index), fromStage, toStage }];
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
var NO_VALUE = "\u2013";
|
|
138
|
+
var VALUE_ROOM = "00000.000";
|
|
139
|
+
var valueRowId = (port) => `value:${port}`;
|
|
140
|
+
var previewRowId = (port) => `preview:${port}`;
|
|
141
|
+
var PREVIEW_SIZE = 48;
|
|
142
|
+
function runRowsOf(o) {
|
|
143
|
+
return {
|
|
144
|
+
...o.showValues !== void 0 ? { showValues: o.showValues } : {},
|
|
145
|
+
...o.showPreviews !== void 0 ? { showPreviews: o.showPreviews } : {},
|
|
146
|
+
...o.previewSize !== void 0 ? { previewSize: o.previewSize } : {}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function stageRows(stage, opts) {
|
|
150
|
+
const rows = [{ kind: "label", text: stage.title, style: { bold: true } }];
|
|
151
|
+
const side = (ports) => (ports ?? []).map((p) => ({ id: p.id, label: p.label ?? p.id, type: p.type }));
|
|
152
|
+
if (opts.withPorts && ((stage.inputs?.length ?? 0) > 0 || (stage.outputs?.length ?? 0) > 0)) {
|
|
153
|
+
rows.push({ kind: "ports", left: side(stage.inputs), right: side(stage.outputs) });
|
|
154
|
+
}
|
|
155
|
+
const size = opts.previewSize ?? PREVIEW_SIZE;
|
|
156
|
+
const preview = typeof size === "number" ? { width: size, height: size } : size;
|
|
157
|
+
const seen = /* @__PURE__ */ new Set();
|
|
158
|
+
for (const p of stage.outputs ?? []) {
|
|
159
|
+
if (seen.has(p.id)) continue;
|
|
160
|
+
seen.add(p.id);
|
|
161
|
+
if (opts.showValues === true) {
|
|
162
|
+
rows.push({ kind: "field", id: valueRowId(p.id), label: p.label ?? p.id, value: NO_VALUE, room: VALUE_ROOM });
|
|
163
|
+
}
|
|
164
|
+
if (opts.showPreviews === true) rows.push({ kind: "slot", id: previewRowId(p.id), ...preview });
|
|
165
|
+
}
|
|
166
|
+
if (opts.showParams) for (const p of stage.params ?? []) rows.push({ kind: "field", label: p.label, value: p.value });
|
|
167
|
+
return rows;
|
|
168
|
+
}
|
|
169
|
+
function exitLabel(d) {
|
|
170
|
+
if (d.wire.label !== void 0) return d.wire.label;
|
|
171
|
+
return kindOf(d.fromStage) === "decision" ? portOn(d.fromStage, d.wire.from.port)?.label ?? d.wire.from.port : void 0;
|
|
172
|
+
}
|
|
173
|
+
function boxNode(stage, opts) {
|
|
174
|
+
return {
|
|
175
|
+
id: stage.id,
|
|
176
|
+
outline: kindOf(stage) === "stage" ? "rect" : "stadium",
|
|
177
|
+
rows: stageRows(stage, { showParams: opts.showParams, withPorts: opts.rowPorts, ...runRowsOf(opts.run ?? {}) }),
|
|
178
|
+
// Always set: left off, a stage port named n, e, s or w would share its id with a compass default.
|
|
179
|
+
ports: opts.ports
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function wireEdge(from, to, label) {
|
|
183
|
+
return { from: from.stage, fromPort: from.port, to: to.stage, toPort: to.port, ...label !== void 0 ? { label } : {} };
|
|
184
|
+
}
|
|
185
|
+
function wireEdges(pipeline, color) {
|
|
186
|
+
return drawableWires(pipeline).map((d) => ({
|
|
187
|
+
edge: wireEdge(d.wire.from, d.wire.to, exitLabel(d)),
|
|
188
|
+
style: wireStyle(color(d.type), isControl(pipeline, d.type), d.problem)
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
function sceneStyles(stages, edges) {
|
|
192
|
+
const nodes = new Map(stages.map((s) => [s.id, stageStyle(s)]));
|
|
193
|
+
const wires = new Map(edges.map((e) => [edgeIdOf(e.edge), e.style]));
|
|
194
|
+
return {
|
|
195
|
+
nodeStyle: (n) => nodes.get(n.id) ?? NORMAL,
|
|
196
|
+
edgeStyle: (e) => wires.get(edgeIdOf(e)) ?? wireStyle(UNTYPED, false, false)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/effect/symbolParts.ts
|
|
201
|
+
function along(side, i, n) {
|
|
202
|
+
const t = (i + 1) / (n + 1);
|
|
203
|
+
if (side === "left") return { u: 0, v: t };
|
|
204
|
+
if (side === "right") return { u: 1, v: t };
|
|
205
|
+
return side === "top" ? { u: t, v: 0 } : { u: t, v: 1 };
|
|
206
|
+
}
|
|
207
|
+
var spread = (ports, side) => ports.map((p, i) => ({ id: p.id, type: p.type, at: along(side, i, ports.length) }));
|
|
208
|
+
function alongLine(points, t) {
|
|
209
|
+
const lengths = points.slice(1).map((p, i) => Math.hypot(p[0] - points[i][0], p[1] - points[i][1]));
|
|
210
|
+
let left = t * lengths.reduce((a, b) => a + b, 0);
|
|
211
|
+
for (const [i, len] of lengths.entries()) {
|
|
212
|
+
if (left <= len || i === lengths.length - 1) {
|
|
213
|
+
const k = len === 0 ? 0 : Math.min(left / len, 1);
|
|
214
|
+
const [a, b] = [points[i], points[i + 1]];
|
|
215
|
+
return { u: a[0] + (b[0] - a[0]) * k, v: a[1] + (b[1] - a[1]) * k };
|
|
216
|
+
}
|
|
217
|
+
left -= len;
|
|
218
|
+
}
|
|
219
|
+
return { u: points[0][0], v: points[0][1] };
|
|
220
|
+
}
|
|
221
|
+
var placeAlong = (sides) => (ports, side) => ports.map((p, i) => ({ id: p.id, type: p.type, at: alongLine(sides[side], (i + 1) / (ports.length + 1)) }));
|
|
222
|
+
function inputs(stage, pipeline, options, steer, place2 = spread) {
|
|
223
|
+
const all = stage.inputs ?? [];
|
|
224
|
+
const steering = all.filter((p) => isControl(pipeline, p.type) || p.id === steer && all.length > 1);
|
|
225
|
+
const side = options?.controlSide === "bottom" ? "bottom" : "top";
|
|
226
|
+
return [...place2(all.filter((p) => !steering.includes(p)), "left"), ...place2(steering, side)];
|
|
227
|
+
}
|
|
228
|
+
var title = (stage, options, gap = 4) => options?.controlSide === "bottom" ? { text: stage.title, at: { u: 0.5, v: 0 }, self: { u: 0.5, v: 1 }, offset: { x: 0, y: -gap } } : { text: stage.title, at: { u: 0.5, v: 1 }, self: { u: 0.5, v: 0 }, offset: { x: 0, y: gap } };
|
|
229
|
+
var DIAMOND_SIDES = placeAlong({
|
|
230
|
+
left: [[0, 0], [0, 1]],
|
|
231
|
+
top: [[0.5, 0], [0.5, 0]],
|
|
232
|
+
right: [[1, 0], [1, 1]],
|
|
233
|
+
bottom: [[0, 0.5], [0.5, 1]]
|
|
234
|
+
});
|
|
235
|
+
var diamond = (stage, pipeline, options) => {
|
|
236
|
+
const exits = stage.outputs ?? [];
|
|
237
|
+
const ins = inputs(stage, pipeline, options, void 0, options?.controlSide === "bottom" ? DIAMOND_SIDES : spread);
|
|
238
|
+
const topFree = !ins.some((p) => p.at.v === 0);
|
|
239
|
+
const out = exits.length === 2 ? [
|
|
240
|
+
{ id: exits[0].id, type: exits[0].type, at: { u: 1, v: 0.5 } },
|
|
241
|
+
{ id: exits[1].id, type: exits[1].type, at: { u: 0.5, v: 1 }, ...topFree ? { sites: [{ u: 0.5, v: 0 }] } : {} }
|
|
242
|
+
] : spread(exits, "right");
|
|
243
|
+
return { outline: "diamond", lines: [stage.title], ports: [...ins, ...out] };
|
|
244
|
+
};
|
|
245
|
+
var BAND_TINTS = ["#c58bff", "#6fb3ff", "#5ad07f", "#f2c14e"];
|
|
246
|
+
var styleOf = (stage, options) => options?.style ?? stageStyle(stage);
|
|
247
|
+
var tintsOf = (stage, options) => options?.tints ?? (stage.bypassed === true ? [styleOf(stage, options).stroke] : BAND_TINTS);
|
|
248
|
+
var canBand = (stage) => stage.split !== void 0 && splitProblem(stage) === null;
|
|
249
|
+
var twoExits = (stage) => (stage.outputs ?? []).length === 2;
|
|
250
|
+
var bandExits = (exits) => exits.map((p, i) => ({ id: p.id, type: p.type, at: { u: 1, v: 1 - (i + 0.5) / exits.length } }));
|
|
251
|
+
var cutLabels = (cuts, n) => cuts.map((cut, k) => ({
|
|
252
|
+
text: String(cut),
|
|
253
|
+
at: { u: 0, v: 1 - (k + 1) / n },
|
|
254
|
+
self: { u: 1, v: 0.5 },
|
|
255
|
+
offset: { x: -10, y: 0 }
|
|
256
|
+
}));
|
|
257
|
+
var dot = (cx, cy, r) => `M${cx - r} ${cy} A${r} ${r} 0 1 0 ${cx + r} ${cy} A${r} ${r} 0 1 0 ${cx - r} ${cy} Z`;
|
|
258
|
+
|
|
259
|
+
// src/effect/glyphs.ts
|
|
260
|
+
var outlineOf = (points) => polygonFromPoints(points.map(([x, y]) => ({ x, y })));
|
|
261
|
+
var POINT = 0.2;
|
|
262
|
+
var onHexagon = placeAlong({
|
|
263
|
+
left: [[POINT, 0], [0, 0.5], [POINT, 1]],
|
|
264
|
+
right: [[1 - POINT, 0], [1, 0.5], [1 - POINT, 1]],
|
|
265
|
+
top: [[POINT, 0], [1 - POINT, 0]],
|
|
266
|
+
bottom: [[POINT, 1], [1 - POINT, 1]]
|
|
267
|
+
});
|
|
268
|
+
var hexagon = (stage, pipeline, options) => ({
|
|
269
|
+
outline: "hexagon",
|
|
270
|
+
lines: [stage.title],
|
|
271
|
+
ports: [...inputs(stage, pipeline, options, void 0, onHexagon), ...onHexagon(stage.outputs ?? [], "right")]
|
|
272
|
+
});
|
|
273
|
+
var step = (stage, pipeline, options) => {
|
|
274
|
+
if (!canBand(stage)) return diamond(stage, pipeline, options);
|
|
275
|
+
const exits = stage.outputs ?? [];
|
|
276
|
+
const n = exits.length;
|
|
277
|
+
const ink = styleOf(stage, options).text;
|
|
278
|
+
const level = (i) => 1 - (i + 0.5) / n;
|
|
279
|
+
const riser = (k) => 0.1 + 0.8 * (k + 1) / n;
|
|
280
|
+
let stairs = `M0 ${level(0)}`;
|
|
281
|
+
for (let k = 0; k < n - 1; k++) stairs += ` H${riser(k)} V${level(k + 1)}`;
|
|
282
|
+
const marks = [{ d: `${stairs} H1`, stroke: ink, strokeWidth: 2 }];
|
|
283
|
+
for (let i = 0; i < n - 1; i++) marks.push({ d: `M${riser(i)} ${level(i)} H1`, stroke: ink, dash: [2, 2], opacity: 0.5 });
|
|
284
|
+
stage.split.at.forEach((cut, k) => marks.push({
|
|
285
|
+
text: String(cut),
|
|
286
|
+
at: { u: riser(k), v: 1 },
|
|
287
|
+
self: { u: 0.5, v: 0 },
|
|
288
|
+
offset: { x: 0, y: 3 },
|
|
289
|
+
style: { fontSize: 10 }
|
|
290
|
+
}));
|
|
291
|
+
marks.push(title(stage, options, 18));
|
|
292
|
+
return {
|
|
293
|
+
outline: "rect",
|
|
294
|
+
lines: [],
|
|
295
|
+
padding: 0,
|
|
296
|
+
width: 56,
|
|
297
|
+
height: Math.max(48, 24 * n),
|
|
298
|
+
ports: [...inputs(stage, pipeline, options, stage.split.input), ...bandExits(exits)],
|
|
299
|
+
marks
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
var TRIANGLE = [[0, 0], [1, 0.5], [0, 1]];
|
|
303
|
+
var COMPARATOR = outlineOf(TRIANGLE);
|
|
304
|
+
var EXIT_AT = 0.7;
|
|
305
|
+
var onTriangle = placeAlong({
|
|
306
|
+
left: [[0, 0], [0, 1]],
|
|
307
|
+
right: [[1, 0.5], [1, 0.5]],
|
|
308
|
+
// The first part of each slanted edge, short of the exits.
|
|
309
|
+
top: [[0, 0], [0.6, 0.3]],
|
|
310
|
+
bottom: [[0, 1], [0.6, 0.7]]
|
|
311
|
+
});
|
|
312
|
+
var comparator = (stage, pipeline, options) => {
|
|
313
|
+
if (!twoExits(stage)) return diamond(stage, pipeline, options);
|
|
314
|
+
const outs = stage.outputs;
|
|
315
|
+
const banded = canBand(stage);
|
|
316
|
+
const [upper, lower] = banded ? [outs[1], outs[0]] : [outs[0], outs[1]];
|
|
317
|
+
const ins = inputs(stage, pipeline, options, void 0, onTriangle);
|
|
318
|
+
const signal = ins.filter((p) => p.at.u === 0);
|
|
319
|
+
const ink = styleOf(stage, options).text;
|
|
320
|
+
const marks = [];
|
|
321
|
+
if (canBand(stage)) marks.push({ text: String(stage.split.at[0]), at: { u: 0.35, v: 0.5 }, self: { u: 0.5, v: 0.5 }, color: ink });
|
|
322
|
+
else if (signal.length === 2) {
|
|
323
|
+
signal.forEach((p, i) => marks.push({ text: i === 0 ? "+" : "\u2212", at: p.at, self: { u: 0, v: 0.5 }, offset: { x: 4, y: 0 }, color: ink }));
|
|
324
|
+
}
|
|
325
|
+
marks.push(title(stage, options));
|
|
326
|
+
const exit = (p, v) => ({ id: p.id, type: p.type, at: { u: EXIT_AT, v } });
|
|
327
|
+
return {
|
|
328
|
+
outline: COMPARATOR,
|
|
329
|
+
lines: [],
|
|
330
|
+
padding: 0,
|
|
331
|
+
width: 56,
|
|
332
|
+
height: 56,
|
|
333
|
+
ports: [...ins, exit(upper, EXIT_AT / 2), exit(lower, 1 - EXIT_AT / 2)],
|
|
334
|
+
marks
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
function response(i, n) {
|
|
338
|
+
const lo = 1 - i / n;
|
|
339
|
+
const hi = 1 - (i + 1) / n;
|
|
340
|
+
const skirt = 0.25 / n;
|
|
341
|
+
const rise = i === 0 ? "M0.75 1" : `M0.15 ${lo + skirt} C0.15 ${lo} 0.75 ${lo} 0.75 ${lo - skirt}`;
|
|
342
|
+
const fall = i === n - 1 ? " V0" : ` V${hi + skirt} C0.75 ${hi} 0.15 ${hi} 0.15 ${hi - skirt}`;
|
|
343
|
+
return rise + fall;
|
|
344
|
+
}
|
|
345
|
+
var crossover = (stage, pipeline, options) => {
|
|
346
|
+
if (!canBand(stage)) return diamond(stage, pipeline, options);
|
|
347
|
+
const exits = stage.outputs ?? [];
|
|
348
|
+
const n = exits.length;
|
|
349
|
+
const ink = styleOf(stage, options).text;
|
|
350
|
+
const tints = tintsOf(stage, options);
|
|
351
|
+
const marks = [];
|
|
352
|
+
exits.forEach((_, i) => {
|
|
353
|
+
marks.push({ d: response(i, n), stroke: tints[i % tints.length], strokeWidth: 1.5 });
|
|
354
|
+
marks.push({ d: `M0.75 ${1 - (i + 0.5) / n} H1`, stroke: ink });
|
|
355
|
+
});
|
|
356
|
+
marks.push(...cutLabels(stage.split.at, n), title(stage, options));
|
|
357
|
+
return {
|
|
358
|
+
outline: "rect",
|
|
359
|
+
lines: [],
|
|
360
|
+
padding: 0,
|
|
361
|
+
width: 56,
|
|
362
|
+
height: Math.max(56, 28 * n),
|
|
363
|
+
ports: [...inputs(stage, pipeline, options, stage.split.input), ...bandExits(exits)],
|
|
364
|
+
marks
|
|
365
|
+
};
|
|
366
|
+
};
|
|
367
|
+
var atPoint = (p) => [p, p];
|
|
368
|
+
var onDot = placeAlong({ left: atPoint([0, 0.5]), right: atPoint([1, 0.5]), top: atPoint([0.5, 0]), bottom: atPoint([0.5, 1]) });
|
|
369
|
+
var FORK = 12;
|
|
370
|
+
var fork = (stage, pipeline, options) => ({
|
|
371
|
+
outline: "stadium",
|
|
372
|
+
lines: [],
|
|
373
|
+
padding: 0,
|
|
374
|
+
width: FORK,
|
|
375
|
+
height: FORK,
|
|
376
|
+
ports: [...inputs(stage, pipeline, options, void 0, onDot), ...onDot(stage.outputs ?? [], "right")],
|
|
377
|
+
marks: [{ d: dot(0.5, 0.5, 0.5), fill: styleOf(stage, options).text }, title(stage, options)]
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// src/effect/symbols.ts
|
|
381
|
+
var bar = (stage, pipeline, options) => {
|
|
382
|
+
if (!canBand(stage)) return diamond(stage, pipeline, options);
|
|
383
|
+
const cuts = stage.split.at;
|
|
384
|
+
const exits = stage.outputs ?? [];
|
|
385
|
+
const n = exits.length;
|
|
386
|
+
const ink = styleOf(stage, options).text;
|
|
387
|
+
const tints = tintsOf(stage, options);
|
|
388
|
+
const marks = [];
|
|
389
|
+
exits.forEach((_, i) => {
|
|
390
|
+
const top = 1 - (i + 1) / n;
|
|
391
|
+
marks.push({ d: `M0 ${top} H1 V${1 - i / n} H0 Z`, fill: tints[i % tints.length], opacity: 0.3 });
|
|
392
|
+
});
|
|
393
|
+
cuts.forEach((_, k) => marks.push({ d: `M-0.25 ${1 - (k + 1) / n} H1.25`, stroke: ink }));
|
|
394
|
+
marks.push(...cutLabels(cuts, n), title(stage, options));
|
|
395
|
+
return {
|
|
396
|
+
outline: "rect",
|
|
397
|
+
lines: [],
|
|
398
|
+
padding: 0,
|
|
399
|
+
width: 24,
|
|
400
|
+
height: Math.max(72, 30 * n),
|
|
401
|
+
ports: [...inputs(stage, pipeline, options, stage.split.input), ...bandExits(exits)],
|
|
402
|
+
marks
|
|
403
|
+
};
|
|
404
|
+
};
|
|
405
|
+
var demux = (stage, pipeline, options) => {
|
|
406
|
+
const exits = stage.outputs ?? [];
|
|
407
|
+
return {
|
|
408
|
+
outline: "trapezoid",
|
|
409
|
+
lines: [],
|
|
410
|
+
padding: 0,
|
|
411
|
+
width: 44,
|
|
412
|
+
height: Math.max(64, 28 * exits.length),
|
|
413
|
+
ports: [...inputs(stage, pipeline, options), ...spread(exits, "right")],
|
|
414
|
+
marks: [title(stage, options)]
|
|
415
|
+
};
|
|
416
|
+
};
|
|
417
|
+
var LEVER_FIRST = "M0.2 0.5 L0.78 0.27";
|
|
418
|
+
var CONTACT_FIRST = dot(0.8, 0.25, 0.07);
|
|
419
|
+
var CONTACT_SECOND = dot(0.8, 0.75, 0.07);
|
|
420
|
+
var THROWN_SECOND = /* @__PURE__ */ new Map([
|
|
421
|
+
[LEVER_FIRST, "M0.2 0.5 L0.78 0.73"],
|
|
422
|
+
[CONTACT_FIRST, CONTACT_SECOND],
|
|
423
|
+
[CONTACT_SECOND, CONTACT_FIRST]
|
|
424
|
+
]);
|
|
425
|
+
var thrownToSecond = (d) => THROWN_SECOND.get(d);
|
|
426
|
+
var switchSymbol = (stage, pipeline, options) => {
|
|
427
|
+
if (!twoExits(stage)) return diamond(stage, pipeline, options);
|
|
428
|
+
const exits = stage.outputs ?? [];
|
|
429
|
+
const ink = styleOf(stage, options).text;
|
|
430
|
+
return {
|
|
431
|
+
outline: "rect",
|
|
432
|
+
lines: [],
|
|
433
|
+
padding: 0,
|
|
434
|
+
width: 48,
|
|
435
|
+
height: 48,
|
|
436
|
+
ports: [
|
|
437
|
+
...inputs(stage, pipeline, options),
|
|
438
|
+
...exits.map((p, i) => ({ id: p.id, type: p.type, at: { u: 1, v: i === 0 ? 0.25 : 0.75 } }))
|
|
439
|
+
],
|
|
440
|
+
marks: [
|
|
441
|
+
{ d: "M0 0.5 H0.2 M0.8 0.25 H1 M0.8 0.75 H1", stroke: ink },
|
|
442
|
+
{ d: LEVER_FIRST, stroke: ink, strokeWidth: 2.5 },
|
|
443
|
+
{ d: dot(0.2, 0.5, 0.07), fill: ink },
|
|
444
|
+
{ d: CONTACT_FIRST, fill: ink },
|
|
445
|
+
{ d: CONTACT_SECOND, stroke: ink },
|
|
446
|
+
title(stage, options)
|
|
447
|
+
]
|
|
448
|
+
};
|
|
449
|
+
};
|
|
450
|
+
var BRANCH_SYMBOLS = Object.freeze({
|
|
451
|
+
diamond,
|
|
452
|
+
bar,
|
|
453
|
+
demux,
|
|
454
|
+
switch: switchSymbol,
|
|
455
|
+
hexagon,
|
|
456
|
+
step,
|
|
457
|
+
comparator,
|
|
458
|
+
crossover,
|
|
459
|
+
fork
|
|
460
|
+
});
|
|
461
|
+
var NEEDS = {
|
|
462
|
+
bar: canBand,
|
|
463
|
+
step: canBand,
|
|
464
|
+
crossover: canBand,
|
|
465
|
+
switch: twoExits,
|
|
466
|
+
comparator: twoExits
|
|
467
|
+
};
|
|
468
|
+
function symbolNameFor(stage, choice = "auto") {
|
|
469
|
+
const want = choice === "auto" ? canBand(stage) ? "bar" : "diamond" : choice;
|
|
470
|
+
return NEEDS[want]?.(stage) === false ? "diamond" : want;
|
|
471
|
+
}
|
|
472
|
+
function branchNode(stage, pipeline, choice = "auto", options) {
|
|
473
|
+
return BRANCH_SYMBOLS[symbolNameFor(stage, choice)](stage, pipeline, options);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/effect/compact.ts
|
|
477
|
+
var COMPACT_PADDING = 4;
|
|
478
|
+
function sidePorts(stage) {
|
|
479
|
+
return [
|
|
480
|
+
...(stage.inputs ?? []).map((p) => ({ id: p.id, at: { u: 0, v: 0.5 } })),
|
|
481
|
+
...(stage.outputs ?? []).map((p) => ({ id: p.id, at: { u: 1, v: 0.5 } }))
|
|
482
|
+
];
|
|
483
|
+
}
|
|
484
|
+
var untyped = (ports) => (ports ?? []).map(({ type: _type, ...port }) => port);
|
|
485
|
+
function nodeOf(stage, pipeline, branch) {
|
|
486
|
+
if (kindOf(stage) !== "decision") {
|
|
487
|
+
return { ...boxNode(stage, { showParams: false, rowPorts: false, ports: sidePorts(stage) }), padding: COMPACT_PADDING };
|
|
488
|
+
}
|
|
489
|
+
const symbol = branchNode(stage, pipeline, branch);
|
|
490
|
+
return { id: stage.id, ...symbol, ports: untyped(symbol.ports), padding: symbol.padding ?? COMPACT_PADDING };
|
|
491
|
+
}
|
|
492
|
+
function lines(pipeline) {
|
|
493
|
+
const by = /* @__PURE__ */ new Map();
|
|
494
|
+
for (const d of drawableWires(pipeline)) {
|
|
495
|
+
const exit = kindOf(d.fromStage) === "decision" ? d.wire.from.port : "";
|
|
496
|
+
const key = JSON.stringify([d.wire.from.stage, exit, d.wire.to.stage]);
|
|
497
|
+
by.set(key, [...by.get(key) ?? [], d]);
|
|
498
|
+
}
|
|
499
|
+
return [...by.values()];
|
|
500
|
+
}
|
|
501
|
+
var compact = (pipeline, options = {}) => {
|
|
502
|
+
const color = typeColors(pipeline, options.colors);
|
|
503
|
+
const stages = uniqueStages(pipeline);
|
|
504
|
+
const nodes = stages.map((stage) => nodeOf(stage, pipeline, options.branch ?? "hexagon"));
|
|
505
|
+
const edges = lines(pipeline).map((line) => {
|
|
506
|
+
const first = line.find((d) => !isControl(pipeline, d.type)) ?? line[0];
|
|
507
|
+
return {
|
|
508
|
+
edge: wireEdge(first.wire.from, first.wire.to, exitLabel(first)),
|
|
509
|
+
style: wireStyle(color(first.type), line.every((d) => isControl(pipeline, d.type)), line.some((d) => d.problem))
|
|
510
|
+
};
|
|
511
|
+
});
|
|
512
|
+
return {
|
|
513
|
+
data: { nodes, edges: edges.map((e) => e.edge), groups: uniqueGroups(pipeline) },
|
|
514
|
+
scene: {
|
|
515
|
+
layout: "layered",
|
|
516
|
+
layoutOptions: { direction: "right", nodeGap: options.nodeGap ?? 16, rankGap: options.rankGap ?? 48 },
|
|
517
|
+
router: options.router ?? "bezier",
|
|
518
|
+
...sceneStyles(stages, edges)
|
|
519
|
+
},
|
|
520
|
+
color
|
|
521
|
+
};
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// src/effect/nodeGraph.ts
|
|
525
|
+
var nodeGraph = (pipeline, options = {}) => {
|
|
526
|
+
const color = typeColors(pipeline, options.colors);
|
|
527
|
+
const stages = uniqueStages(pipeline);
|
|
528
|
+
const nodes = stages.map((stage) => kindOf(stage) === "decision" ? { id: stage.id, ...branchNode(stage, pipeline, options.branch) } : boxNode(stage, { showParams: options.showParams !== false, rowPorts: true, ports: [], run: options }));
|
|
529
|
+
const edges = wireEdges(pipeline, color);
|
|
530
|
+
return {
|
|
531
|
+
data: { nodes, edges: edges.map((e) => e.edge), groups: uniqueGroups(pipeline) },
|
|
532
|
+
scene: {
|
|
533
|
+
layout: "layered",
|
|
534
|
+
layoutOptions: {
|
|
535
|
+
direction: "right",
|
|
536
|
+
...options.nodeGap !== void 0 ? { nodeGap: options.nodeGap } : {},
|
|
537
|
+
...options.rankGap !== void 0 ? { rankGap: options.rankGap } : {}
|
|
538
|
+
},
|
|
539
|
+
router: options.router ?? "bezier",
|
|
540
|
+
...sceneStyles(stages, edges)
|
|
541
|
+
},
|
|
542
|
+
color
|
|
543
|
+
};
|
|
544
|
+
};
|
|
545
|
+
var DIM = 0.3;
|
|
546
|
+
var COST_GAP = 2;
|
|
547
|
+
var COST_SCALE = 0.8;
|
|
548
|
+
function paintRun(pipeline, specs, run, exits = /* @__PURE__ */ new Map()) {
|
|
549
|
+
if (run === void 0) return [...specs];
|
|
550
|
+
const stages = new Map(uniqueStages(pipeline).map((s) => [s.id, s]));
|
|
551
|
+
const unreached = unreachedStages(pipeline, run);
|
|
552
|
+
const owner = ownerOf(stages);
|
|
553
|
+
const passed = /* @__PURE__ */ new Set();
|
|
554
|
+
for (const spec of specs) {
|
|
555
|
+
const ends = wireEnds(spec);
|
|
556
|
+
if (ends === null) continue;
|
|
557
|
+
const exit = exits.get(spec.id) ?? { stage: ends.from, port: ends.fromPort };
|
|
558
|
+
const taken = run.stages[exit.stage]?.taken;
|
|
559
|
+
if (unreached.has(ends.from) || unreached.has(ends.to) || unreached.has(exit.stage) || taken !== void 0 && exit.port !== taken) {
|
|
560
|
+
passed.add(spec.id);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const values = /* @__PURE__ */ new Map();
|
|
564
|
+
const previews = /* @__PURE__ */ new Map();
|
|
565
|
+
for (const [id, stage] of stages) {
|
|
566
|
+
for (const p of stage.outputs ?? []) {
|
|
567
|
+
values.set(`${id}/${valueRowId(p.id)}`, `${p.label ?? p.id}: ${run.stages[id]?.values?.[p.id] ?? NO_VALUE}`);
|
|
568
|
+
const preview = run.stages[id]?.previews?.[p.id];
|
|
569
|
+
if (preview !== void 0) previews.set(`${id}/${previewRowId(p.id)}`, preview);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const thrownSecond = new Set([...stages.values()].filter((s) => {
|
|
573
|
+
const outs = s.outputs ?? [];
|
|
574
|
+
return outs.length === 2 && run.stages[s.id]?.taken === outs[1].id;
|
|
575
|
+
}).map((s) => s.id));
|
|
576
|
+
const out = specs.map((spec) => {
|
|
577
|
+
const id = spec.id;
|
|
578
|
+
let next = spec;
|
|
579
|
+
const text = values.get(id);
|
|
580
|
+
if (text !== void 0 && spec.data.text !== text) next = { ...next, data: { ...next.data, text } };
|
|
581
|
+
const preview = previews.get(id);
|
|
582
|
+
if (preview !== void 0) next = { ...next, data: { image: { src: bitmapSrc(preview) } } };
|
|
583
|
+
const stage = owner(spec);
|
|
584
|
+
if (stage !== void 0 && thrownSecond.has(stage)) next = thrown(next);
|
|
585
|
+
const dependsOn = spec.dependsOn;
|
|
586
|
+
const onWire = Array.isArray(dependsOn) && dependsOn.length === 1 && passed.has(dependsOn[0]);
|
|
587
|
+
if (stage !== void 0 && unreached.has(stage) || passed.has(id) || onWire) next = { ...next, data: dimmed(next.data) };
|
|
588
|
+
return next;
|
|
589
|
+
});
|
|
590
|
+
for (const spec of specs) {
|
|
591
|
+
const id = spec.id;
|
|
592
|
+
const cost = stages.has(id) ? run.stages[id]?.cost : void 0;
|
|
593
|
+
if (cost !== void 0) out.push(costSpec(spec, specs.filter((s) => owner(s) === id), stages.get(id), cost));
|
|
594
|
+
}
|
|
595
|
+
return out;
|
|
596
|
+
}
|
|
597
|
+
function unreachedStages(pipeline, run) {
|
|
598
|
+
if (run === void 0) return /* @__PURE__ */ new Set();
|
|
599
|
+
return new Set(uniqueStages(pipeline).map((s) => s.id).filter((id) => run.stages[id] === void 0));
|
|
600
|
+
}
|
|
601
|
+
function ownerOf(stages) {
|
|
602
|
+
return (spec) => {
|
|
603
|
+
const id = spec.id;
|
|
604
|
+
if (stages.has(id)) return id;
|
|
605
|
+
const parent = spec.parent;
|
|
606
|
+
if (parent !== void 0 && stages.has(parent)) return parent;
|
|
607
|
+
const deps = spec.dependsOn;
|
|
608
|
+
const anchor = Array.isArray(deps) && deps.length === 1 ? deps[0] : void 0;
|
|
609
|
+
return anchor !== void 0 && stages.has(anchor) ? anchor : void 0;
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function wireEnds(spec) {
|
|
613
|
+
const trait = spec.data?.diagram;
|
|
614
|
+
const deps = spec.dependsOn;
|
|
615
|
+
if (trait?.from === void 0 || trait.to === void 0 || !Array.isArray(deps) || deps.length !== 2) return null;
|
|
616
|
+
return { from: deps[0], to: deps[1], fromPort: trait.from.port };
|
|
617
|
+
}
|
|
618
|
+
function thrown(spec) {
|
|
619
|
+
const trait = spec.data.diagram;
|
|
620
|
+
const d = trait?.mark === void 0 ? void 0 : thrownToSecond(trait.mark.d);
|
|
621
|
+
return d === void 0 ? spec : { ...spec, data: { ...spec.data, diagram: { ...trait, mark: { d } } } };
|
|
622
|
+
}
|
|
623
|
+
function dimmed(data) {
|
|
624
|
+
const fade = (part) => {
|
|
625
|
+
const paint = part?.paint;
|
|
626
|
+
if (paint === void 0) return part;
|
|
627
|
+
return { ...part, paint: { ...paint, opacity: (paint.opacity ?? 1) * DIM } };
|
|
628
|
+
};
|
|
629
|
+
const d = data;
|
|
630
|
+
return {
|
|
631
|
+
...data,
|
|
632
|
+
...d.fill !== void 0 ? { fill: fade(d.fill) } : {},
|
|
633
|
+
...d.stroke !== void 0 ? { stroke: fade(d.stroke) } : {}
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
function costSpec(stage, own, of, cost) {
|
|
637
|
+
const title2 = own.find((s) => s !== stage && s.data.text !== void 0);
|
|
638
|
+
const titleData = title2?.data ?? {};
|
|
639
|
+
const fontSize = Math.round((titleData.style?.fontSize ?? 12) * COST_SCALE);
|
|
640
|
+
const style = { fontFamily: titleData.style?.fontFamily ?? "sans-serif", fontSize };
|
|
641
|
+
const fill = titleData.fill ?? { paint: { color: stageStyle(of).text } };
|
|
642
|
+
const text = costText(cost);
|
|
643
|
+
const width = text.length * fontSize * 0.6;
|
|
644
|
+
const height = Math.ceil(fontSize * 1.2);
|
|
645
|
+
const box = stage.pose;
|
|
646
|
+
const top = Math.min(box.y, ...own.flatMap((s) => {
|
|
647
|
+
const p = s.pose;
|
|
648
|
+
return s !== stage && p !== void 0 && p.y + p.height <= box.y ? [p.y] : [];
|
|
649
|
+
}));
|
|
650
|
+
return {
|
|
651
|
+
id: `${stage.id}/cost`,
|
|
652
|
+
kind: "leaf",
|
|
653
|
+
layer: "main",
|
|
654
|
+
pickable: false,
|
|
655
|
+
pose: { x: box.x + box.width - width, y: top - height - COST_GAP, width, height },
|
|
656
|
+
data: { text, style, fill }
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function costText(ms) {
|
|
660
|
+
if (ms >= 100) return `${Math.round(ms)} ms`;
|
|
661
|
+
return `${Number(ms.toPrecision(3))} ms`;
|
|
662
|
+
}
|
|
663
|
+
var EFFECT_MIN_SCALE = 0.5;
|
|
664
|
+
var keys = /* @__PURE__ */ new WeakMap();
|
|
665
|
+
var nextKey = 0;
|
|
666
|
+
function keyOf(built) {
|
|
667
|
+
let key = keys.get(built);
|
|
668
|
+
if (key === void 0) {
|
|
669
|
+
key = nextKey++;
|
|
670
|
+
keys.set(built, key);
|
|
671
|
+
}
|
|
672
|
+
return key;
|
|
673
|
+
}
|
|
674
|
+
function contentOf(value) {
|
|
675
|
+
try {
|
|
676
|
+
return JSON.stringify(value) ?? null;
|
|
677
|
+
} catch {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
function useSameContent(value) {
|
|
682
|
+
const [kept, setKept] = useState(() => ({ value, content: contentOf(value) }));
|
|
683
|
+
if (kept.value === value) return value;
|
|
684
|
+
const content = contentOf(value);
|
|
685
|
+
if (content !== null && content === kept.content) return kept.value;
|
|
686
|
+
setKept({ value, content });
|
|
687
|
+
return value;
|
|
688
|
+
}
|
|
689
|
+
function useReleaseReplaced(run) {
|
|
690
|
+
const shown = useRef(/* @__PURE__ */ new Set());
|
|
691
|
+
useEffect(() => {
|
|
692
|
+
const now = new Set(Object.values(run?.stages ?? {}).flatMap((s) => Object.values(s.previews ?? {})));
|
|
693
|
+
for (const old of shown.current) if (!now.has(old)) releaseImageSource(old);
|
|
694
|
+
shown.current = now;
|
|
695
|
+
}, [run]);
|
|
696
|
+
useEffect(() => () => {
|
|
697
|
+
for (const old of shown.current) releaseImageSource(old);
|
|
698
|
+
shown.current = /* @__PURE__ */ new Set();
|
|
699
|
+
}, []);
|
|
700
|
+
}
|
|
701
|
+
function EffectChart({
|
|
702
|
+
pipeline: given,
|
|
703
|
+
run,
|
|
704
|
+
look,
|
|
705
|
+
options,
|
|
706
|
+
width,
|
|
707
|
+
height,
|
|
708
|
+
minScale = EFFECT_MIN_SCALE,
|
|
709
|
+
background,
|
|
710
|
+
selected,
|
|
711
|
+
onSelect,
|
|
712
|
+
onProblems,
|
|
713
|
+
className
|
|
714
|
+
}) {
|
|
715
|
+
const pipeline = useSameContent(given);
|
|
716
|
+
const problems = useMemo(() => checkPipeline(pipeline), [pipeline]);
|
|
717
|
+
const onProblemsRef = useLatest(onProblems);
|
|
718
|
+
useEffect(() => {
|
|
719
|
+
onProblemsRef.current?.(problems);
|
|
720
|
+
}, [onProblemsRef, problems]);
|
|
721
|
+
const stageIds = useMemo(() => new Set(pipeline.stages.map((s) => s.id)), [pipeline]);
|
|
722
|
+
const built = useMemo(() => {
|
|
723
|
+
const drawn = (look ?? nodeGraph)(pipeline, options);
|
|
724
|
+
const specs = diagramScene(drawn.data, drawn.scene).map((s) => s.pickable === false || stageIds.has(s.id) ? s : { ...s, pickable: false });
|
|
725
|
+
return { specs, color: drawn.color, exits: drawn.exits };
|
|
726
|
+
}, [pipeline, look, options, stageIds]);
|
|
727
|
+
const missed = JSON.stringify([...unreachedStages(pipeline, run)].sort());
|
|
728
|
+
const portOptions = useMemo(() => {
|
|
729
|
+
const faded = new Set(JSON.parse(missed));
|
|
730
|
+
return {
|
|
731
|
+
shape: "circle",
|
|
732
|
+
sizePx: 8,
|
|
733
|
+
// Every port a look declares has a value type; the compass defaults have none.
|
|
734
|
+
showsPort: (port) => port.type !== void 0,
|
|
735
|
+
fill: (port) => ({ paint: { color: built.color(port.type), ...faded.has(port.nodeId) ? { opacity: DIM } : {} } })
|
|
736
|
+
};
|
|
737
|
+
}, [built, missed]);
|
|
738
|
+
const painted = useMemo(() => paintRun(pipeline, built.specs, run, built.exits), [pipeline, built, run]);
|
|
739
|
+
useReleaseReplaced(run);
|
|
740
|
+
return /* @__PURE__ */ jsx(
|
|
741
|
+
DiagramView,
|
|
742
|
+
{
|
|
743
|
+
specs: painted,
|
|
744
|
+
width,
|
|
745
|
+
height,
|
|
746
|
+
minScale,
|
|
747
|
+
ports: "show",
|
|
748
|
+
portOptions,
|
|
749
|
+
anchor: "start",
|
|
750
|
+
...background !== void 0 ? { background } : {},
|
|
751
|
+
...selected !== void 0 ? { selected } : {},
|
|
752
|
+
...onSelect ? { onSelect: (id) => onSelect(id !== null && stageIds.has(id) ? id : null) } : {},
|
|
753
|
+
...className !== void 0 ? { className } : {}
|
|
754
|
+
},
|
|
755
|
+
keyOf(built)
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
var upright = (n) => n !== null && Math.abs(n.y) > Math.abs(n.x);
|
|
759
|
+
var facingY = (end, y) => [end, ...end.sites ?? []].find((s) => s.normal !== null && Math.sign(s.normal.y) === Math.sign(y - s.point.y) && upright(s.normal)) ?? end;
|
|
760
|
+
var TRACK_GAP = SIDE_TRACK_GAP;
|
|
761
|
+
var CLEAR = 3;
|
|
762
|
+
var covers = (boxes, y, to = y) => boxes.some((b) => b.y - CLEAR < Math.max(y, to) && Math.min(y, to) < b.y + b.height + CLEAR);
|
|
763
|
+
var laneAt = (grid, y) => Math.max(0, grid.edges.filter((e) => e <= y).length - 1);
|
|
764
|
+
function corridor(grid, columns, near) {
|
|
765
|
+
const ink = columns.flatMap((c) => grid.ink[c] ?? []);
|
|
766
|
+
const middle = laneAt(grid, near) + 0.5;
|
|
767
|
+
const clear = grid.edges.flatMap((y, k) => covers(ink, y) ? [] : [k]);
|
|
768
|
+
return clear.reduce((best, k) => Math.abs(k - middle) < Math.abs(best - middle) ? k : best, clear[0] ?? 0);
|
|
769
|
+
}
|
|
770
|
+
var between = (a, b) => Array.from({ length: Math.abs(b - a) + 1 }, (_, i) => a + Math.sign(b - a) * i);
|
|
771
|
+
function laneCourse(grid, from, to) {
|
|
772
|
+
if (from.column >= to.column) {
|
|
773
|
+
const columns2 = between(from.column, to.column);
|
|
774
|
+
const edge = corridor(grid, columns2, from.point.y);
|
|
775
|
+
return { from, to, back: true, legs: columns2.map((column) => ({ column, at: { edge } })) };
|
|
776
|
+
}
|
|
777
|
+
const columns = Array.from({ length: to.column - from.column - 1 }, (_, i) => from.column + 1 + i);
|
|
778
|
+
const blocked = (c, y, until = y) => covers(grid.ink[c] ?? [], y, until);
|
|
779
|
+
const first = columns.findIndex((c) => blocked(c, from.point.y, to.point.y));
|
|
780
|
+
if (first < 0) return void 0;
|
|
781
|
+
const want = columns.map((_, i) => i < first ? from.point.y : to.point.y);
|
|
782
|
+
const legs = columns.map((column, i) => ({ column, at: want[i] }));
|
|
783
|
+
for (let i = 0; i < columns.length; ) {
|
|
784
|
+
let j = i;
|
|
785
|
+
while (j < columns.length && blocked(columns[j], want[j])) j++;
|
|
786
|
+
if (j === i) {
|
|
787
|
+
i++;
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
const edge = corridor(grid, columns.slice(i, j), want[i]);
|
|
791
|
+
for (let k = i; k < j; k++) legs[k] = { column: columns[k], at: { edge } };
|
|
792
|
+
i = j;
|
|
793
|
+
}
|
|
794
|
+
return { from, to, back: false, legs };
|
|
795
|
+
}
|
|
796
|
+
function assignTracks(courses) {
|
|
797
|
+
const stretches = courses.map((course) => {
|
|
798
|
+
const out = [];
|
|
799
|
+
for (const { column, at } of course?.legs ?? []) {
|
|
800
|
+
if (typeof at === "number") continue;
|
|
801
|
+
const last = out[out.length - 1];
|
|
802
|
+
if (last !== void 0 && last.edge === at.edge && Math.abs(column - (course.back ? last.lo : last.hi)) === 1) {
|
|
803
|
+
if (course.back) last.lo = column;
|
|
804
|
+
else last.hi = column;
|
|
805
|
+
} else out.push({ edge: at.edge, lo: column, hi: column, track: 0 });
|
|
806
|
+
}
|
|
807
|
+
return out;
|
|
808
|
+
});
|
|
809
|
+
const ends = /* @__PURE__ */ new Map();
|
|
810
|
+
for (const s of stretches.flat().sort((a, b) => a.lo - b.lo || a.hi - b.hi)) {
|
|
811
|
+
const taken = ends.get(s.edge) ?? [];
|
|
812
|
+
const free = taken.findIndex((end) => end + 1 < s.lo);
|
|
813
|
+
s.track = free < 0 ? taken.length : free;
|
|
814
|
+
taken[s.track] = s.hi;
|
|
815
|
+
ends.set(s.edge, taken);
|
|
816
|
+
}
|
|
817
|
+
return { stretches, tracks: new Map([...ends].map(([edge, taken]) => [edge, taken.length])) };
|
|
818
|
+
}
|
|
819
|
+
function trackY(grid, k, j, n) {
|
|
820
|
+
const last = grid.edges.length - 1;
|
|
821
|
+
const y = grid.edges[k];
|
|
822
|
+
if (k === 0) return y + (j + 0.5) * TRACK_GAP;
|
|
823
|
+
if (k === last) return y - (j + 0.5) * TRACK_GAP;
|
|
824
|
+
return y + (j - (n - 1) / 2) * TRACK_GAP;
|
|
825
|
+
}
|
|
826
|
+
function straightened(points) {
|
|
827
|
+
return points.filter((p, i) => {
|
|
828
|
+
const a = points[i - 1];
|
|
829
|
+
const b = points[i + 1];
|
|
830
|
+
return a === void 0 || b === void 0 || !(a.y === p.y && p.y === b.y);
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
function waypointsOf(grid, course, ys) {
|
|
834
|
+
const { from, to } = course;
|
|
835
|
+
const edge = (c, side) => grid.columns[c][side];
|
|
836
|
+
if (course.back) return [{ x: edge(from.column, 1), y: ys[0] }, { x: edge(to.column, 0), y: ys[0] }];
|
|
837
|
+
const points = [];
|
|
838
|
+
const out = edge(from.column, 1);
|
|
839
|
+
if (from.normal?.x === 1 && from.point.x < out - 1) points.push({ x: (from.point.x + out) / 2, y: from.point.y }, { x: out, y: from.point.y });
|
|
840
|
+
const first = ys[0];
|
|
841
|
+
const leave = facingY(from, first);
|
|
842
|
+
const rise = Math.abs(first - leave.point.y);
|
|
843
|
+
if (upright(leave.normal) && leave.point.x + rise < out) points.push({ x: leave.point.x + rise, y: first }, { x: out, y: first });
|
|
844
|
+
course.legs.forEach(({ column }, i) => points.push({ x: edge(column, 0), y: ys[i] }, { x: edge(column, 1), y: ys[i] }));
|
|
845
|
+
const into = edge(to.column, 0);
|
|
846
|
+
const last = ys[ys.length - 1];
|
|
847
|
+
const arrive = facingY(to, last);
|
|
848
|
+
const fall = Math.abs(last - arrive.point.y);
|
|
849
|
+
if (upright(arrive.normal) && arrive.point.x - fall > into) points.push({ x: into, y: last }, { x: arrive.point.x - fall, y: last });
|
|
850
|
+
if (to.normal?.x === -1 && to.point.x > into + 1) points.push({ x: into, y: to.point.y }, { x: (to.point.x + into) / 2, y: to.point.y });
|
|
851
|
+
return straightened(points);
|
|
852
|
+
}
|
|
853
|
+
function routeOf(from, fromPort, to, toPort, router, waypoints) {
|
|
854
|
+
const dep = (s) => ({ node: { id: s.id, data: s.data }, pose: s.pose });
|
|
855
|
+
const edge = { data: { diagram: { from: { port: fromPort }, to: { port: toPort }, router, ...waypoints ? { waypoints } : {} } } };
|
|
856
|
+
return EDGE_DERIVE_PATH(edge, [dep(from), dep(to)]);
|
|
857
|
+
}
|
|
858
|
+
var overlaps = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
859
|
+
function clearLabel(path, size, offset, keepOut) {
|
|
860
|
+
for (const side of [offset, -offset]) {
|
|
861
|
+
for (const t of [0.5, 0.4, 0.6, 0.3, 0.7, 0.2, 0.8, 0.1, 0.9]) {
|
|
862
|
+
const near = pointAlongPath(path, t)?.point;
|
|
863
|
+
const center = near === void 0 ? null : labelCenter(path, { at: { near }, offset: side }, size);
|
|
864
|
+
if (near === void 0 || center === null) continue;
|
|
865
|
+
const box = { x: center.x - size.width / 2, y: center.y - size.height / 2, width: size.width, height: size.height };
|
|
866
|
+
if (!keepOut.some((k) => overlaps(box, k))) return { near, offset: side, box };
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return void 0;
|
|
870
|
+
}
|
|
871
|
+
var inkOf = (id, specs) => specs.filter((s) => s.id === id || s.parent === id || String(s.id).startsWith(`${id}/mark:`)).map((s) => s.pose);
|
|
872
|
+
function routeWires(wires, nodes, laid) {
|
|
873
|
+
const specs = diagramScene({ nodes, edges: wires.map((w) => w.edge) }, {
|
|
874
|
+
layout: "none",
|
|
875
|
+
router: laid.router,
|
|
876
|
+
...laid.measure !== void 0 ? { measure: laid.measure } : {}
|
|
877
|
+
});
|
|
878
|
+
const spec = new Map(specs.map((s) => [s.id, s]));
|
|
879
|
+
const ink = new Map(nodes.map((n) => [n.id, inkOf(n.id, specs)]));
|
|
880
|
+
const grid = { ...laid.grid, ink: laid.grid.columns.map((_, c) => [...ink].flatMap(([id, b]) => laid.column.get(id) === c ? b : [])) };
|
|
881
|
+
const end = (stage, port) => {
|
|
882
|
+
const s = spec.get(stage);
|
|
883
|
+
const [site, ...sites] = portSitesOf({ id: stage, data: s.data }, s.pose, port);
|
|
884
|
+
return { column: laid.column.get(stage), point: site?.point ?? { x: 0, y: 0 }, normal: site?.normal ?? null, sites };
|
|
885
|
+
};
|
|
886
|
+
const courses = wires.map(({ edge }) => edge.from === edge.to ? void 0 : laneCourse(grid, end(edge.from, edge.fromPort), end(edge.to, edge.toPort)));
|
|
887
|
+
const { stretches, tracks } = assignTracks(courses);
|
|
888
|
+
const [top, bottom] = [grid.edges[0] ?? 0, grid.edges[grid.edges.length - 1] ?? 0];
|
|
889
|
+
const keepOut = [...ink.values()].flat().concat({ x: -1e9, y: top - 1e9, width: 2e9, height: 1e9 }, { x: -1e9, y: bottom, width: 2e9, height: 1e9 });
|
|
890
|
+
const routes = wires.map(({ edge }, w) => {
|
|
891
|
+
const course = courses[w];
|
|
892
|
+
const ys = course?.legs.map(({ column, at }) => {
|
|
893
|
+
if (typeof at === "number") return at;
|
|
894
|
+
const s = stretches[w].find((t) => t.edge === at.edge && t.lo <= column && column <= t.hi);
|
|
895
|
+
return trackY(grid, at.edge, s.track, tracks.get(at.edge));
|
|
896
|
+
});
|
|
897
|
+
const waypoints = course === void 0 ? void 0 : waypointsOf(grid, course, ys);
|
|
898
|
+
return { waypoints, path: routeOf(spec.get(edge.from), edge.fromPort, spec.get(edge.to), edge.toPort, laid.router, waypoints) };
|
|
899
|
+
});
|
|
900
|
+
const segments = routes.map(({ path }) => {
|
|
901
|
+
const c = path?.kind === "polygon" ? path.coords : [];
|
|
902
|
+
return Array.from({ length: Math.max(0, c.length / 2 - 1) }, (_, i) => {
|
|
903
|
+
const [x0, y0, x1, y1] = [c[2 * i], c[2 * i + 1], c[2 * i + 2], c[2 * i + 3]];
|
|
904
|
+
return { x: Math.min(x0, x1) - 1, y: Math.min(y0, y1) - 1, width: Math.abs(x1 - x0) + 2, height: Math.abs(y1 - y0) + 2 };
|
|
905
|
+
});
|
|
906
|
+
});
|
|
907
|
+
const edges = wires.map(({ edge, style }, w) => {
|
|
908
|
+
const { waypoints, path } = routes[w];
|
|
909
|
+
const routed = waypoints === void 0 ? edge : { ...edge, routed: waypoints };
|
|
910
|
+
const label = spec.get(`${edgeIdOf(edge)}/label`)?.pose;
|
|
911
|
+
const others = keepOut.concat(segments.flatMap((boxes, k) => k === w ? [] : boxes));
|
|
912
|
+
const offset = LABEL_PLACEMENT.offset ?? 0;
|
|
913
|
+
const clear = path === null || label === void 0 ? void 0 : clearLabel(path, label, offset, others) ?? clearLabel(path, label, offset, keepOut);
|
|
914
|
+
if (clear === void 0) return { edge: routed, style };
|
|
915
|
+
keepOut.push(clear.box);
|
|
916
|
+
return { edge: { ...routed, labelPlacement: { at: { near: clear.near }, offset: clear.offset } }, style };
|
|
917
|
+
});
|
|
918
|
+
return { edges, tracks };
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/effect/lanes.ts
|
|
922
|
+
var LANE_INSET = 6;
|
|
923
|
+
var MARGIN = 16;
|
|
924
|
+
var LANE_STYLES = [
|
|
925
|
+
{ fill: "rgba(255, 255, 255, 0.035)", stroke: "rgba(255, 255, 255, 0.06)", text: "#9fbdd6", strokeWidth: 1 },
|
|
926
|
+
{ fill: "rgba(255, 255, 255, 0)", stroke: "rgba(255, 255, 255, 0.06)", text: "#9fbdd6", strokeWidth: 1 }
|
|
927
|
+
];
|
|
928
|
+
var laneId = (type) => `~lane:${type}`;
|
|
929
|
+
function laneTypes(stages, first = []) {
|
|
930
|
+
const used = [...new Set(stages.flatMap((s) => [...s.inputs ?? [], ...s.outputs ?? []].map((p) => p.type)))];
|
|
931
|
+
const lead = [...new Set(first)].filter((t) => used.includes(t));
|
|
932
|
+
return [...lead, ...used.filter((t) => !lead.includes(t))];
|
|
933
|
+
}
|
|
934
|
+
function spanOf(stage, lane) {
|
|
935
|
+
const at = [...stage.inputs ?? [], ...stage.outputs ?? []].map((p) => lane.get(p.type));
|
|
936
|
+
return at.length === 0 ? [0, 0] : [Math.min(...at), Math.max(...at)];
|
|
937
|
+
}
|
|
938
|
+
function columnsOf(stages, wires, spans) {
|
|
939
|
+
const feeds = new Map(stages.map((s) => [s.id, /* @__PURE__ */ new Set()]));
|
|
940
|
+
for (const d of wires) if (d.wire.from.stage !== d.wire.to.stage) feeds.get(d.wire.to.stage).add(d.wire.from.stage);
|
|
941
|
+
const column = /* @__PURE__ */ new Map();
|
|
942
|
+
const held = [];
|
|
943
|
+
const left = [...stages];
|
|
944
|
+
while (left.length > 0) {
|
|
945
|
+
const i = Math.max(0, left.findIndex((s) => [...feeds.get(s.id)].every((f) => column.has(f))));
|
|
946
|
+
const stage = left.splice(i, 1)[0];
|
|
947
|
+
const span = spans.get(stage.id);
|
|
948
|
+
let c = Math.max(0, ...[...feeds.get(stage.id)].flatMap((f) => column.has(f) ? [column.get(f) + 1] : []));
|
|
949
|
+
while (held[c]?.some(([lo, hi]) => lo <= span[1] && span[0] <= hi)) c++;
|
|
950
|
+
(held[c] ??= []).push(span);
|
|
951
|
+
column.set(stage.id, c);
|
|
952
|
+
}
|
|
953
|
+
return column;
|
|
954
|
+
}
|
|
955
|
+
function laneHeights(count, least, needs, insets) {
|
|
956
|
+
const heights = Array.from({ length: count }, () => least);
|
|
957
|
+
for (const { span: [lo, hi], height } of [...needs].sort((a, b) => a.span[1] - a.span[0] - (b.span[1] - b.span[0]))) {
|
|
958
|
+
const short = height + insets[lo] + insets[hi + 1] - heights.slice(lo, hi + 1).reduce((a, b) => a + b, 0);
|
|
959
|
+
if (short > 0) for (let i = lo; i <= hi; i++) heights[i] += short / (hi - lo + 1);
|
|
960
|
+
}
|
|
961
|
+
return heights;
|
|
962
|
+
}
|
|
963
|
+
function lanePorts(stage, box, lane, band) {
|
|
964
|
+
const side = (ports, u) => ports.map((p) => {
|
|
965
|
+
const same = ports.filter((q) => q.type === p.type);
|
|
966
|
+
const [top, bottom] = band(lane.get(p.type));
|
|
967
|
+
const a = Math.max(top, box.y);
|
|
968
|
+
const b = Math.min(bottom, box.y + box.height);
|
|
969
|
+
const y = a + (b - a) * (same.indexOf(p) + 1) / (same.length + 1);
|
|
970
|
+
return { id: p.id, type: p.type, at: { u, v: (y - box.y) / box.height }, normal: { x: u === 0 ? -1 : 1, y: 0 } };
|
|
971
|
+
});
|
|
972
|
+
return [...side(stage.inputs ?? [], 0), ...side(stage.outputs ?? [], 1)];
|
|
973
|
+
}
|
|
974
|
+
function controlSideOf(stage, pipeline, lane) {
|
|
975
|
+
const ports = [...stage.inputs ?? [], ...stage.outputs ?? []];
|
|
976
|
+
const mean = (ps) => ps.reduce((a, p) => a + lane.get(p.type), 0) / ps.length;
|
|
977
|
+
const control = ports.filter((p) => isControl(pipeline, p.type));
|
|
978
|
+
const signal = ports.filter((p) => !isControl(pipeline, p.type));
|
|
979
|
+
return control.length > 0 && signal.length > 0 && mean(control) > mean(signal) ? "bottom" : "top";
|
|
980
|
+
}
|
|
981
|
+
function sizesOf(nodes, measure) {
|
|
982
|
+
const specs = diagramScene({ nodes, edges: [] }, { layout: "none", ...measure !== void 0 ? { measure } : {} });
|
|
983
|
+
const pose = new Map(specs.map((s) => [s.id, s.pose]));
|
|
984
|
+
return new Map(nodes.map((n) => {
|
|
985
|
+
const box = pose.get(n.id);
|
|
986
|
+
const ink = inkOf(n.id, specs);
|
|
987
|
+
const over = {
|
|
988
|
+
left: box.x - Math.min(...ink.map((b) => b.x)),
|
|
989
|
+
top: box.y - Math.min(...ink.map((b) => b.y)),
|
|
990
|
+
right: Math.max(...ink.map((b) => b.x + b.width)) - box.x - box.width,
|
|
991
|
+
bottom: Math.max(...ink.map((b) => b.y + b.height)) - box.y - box.height
|
|
992
|
+
};
|
|
993
|
+
return [n.id, { box, over }];
|
|
994
|
+
}));
|
|
995
|
+
}
|
|
996
|
+
function laneNode(type, box, color) {
|
|
997
|
+
return {
|
|
998
|
+
id: laneId(type),
|
|
999
|
+
lines: [],
|
|
1000
|
+
ports: [],
|
|
1001
|
+
at: { x: box.x, y: box.y },
|
|
1002
|
+
width: box.width,
|
|
1003
|
+
height: box.height,
|
|
1004
|
+
marks: [{ text: type, at: { u: 0, v: 0.5 }, self: { u: 0, v: 0.5 }, offset: { x: MARGIN, y: 0 }, color: color(type), style: { bold: true } }]
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
function insetsFor(edges, tracks) {
|
|
1008
|
+
return Array.from({ length: edges }, (_, k) => {
|
|
1009
|
+
const n = tracks.get(k) ?? 0;
|
|
1010
|
+
return LANE_INSET + (k === 0 || k === edges - 1 ? n * TRACK_GAP : Math.max(0, n - 1) * TRACK_GAP / 2);
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
function place(plan, insets) {
|
|
1014
|
+
const { stages, size, spans, column } = plan;
|
|
1015
|
+
const inkHeight = (id) => size.get(id).box.height + size.get(id).over.top + size.get(id).over.bottom;
|
|
1016
|
+
const heights = laneHeights(plan.rows, plan.least, stages.map((s) => ({ span: spans.get(s.id), height: inkHeight(s.id) })), insets);
|
|
1017
|
+
const tops = heights.map((_, i) => heights.slice(0, i).reduce((a, b) => a + b, 0));
|
|
1018
|
+
const band = (i) => [tops[i], tops[i] + heights[i]];
|
|
1019
|
+
const stood = new Map(stages.map((stage) => {
|
|
1020
|
+
const { box: natural, over } = size.get(stage.id);
|
|
1021
|
+
const [lo, hi] = spans.get(stage.id);
|
|
1022
|
+
const top = band(lo)[0] + insets[lo];
|
|
1023
|
+
const span = band(hi)[1] - insets[hi + 1] - top;
|
|
1024
|
+
const kind = kindOf(stage);
|
|
1025
|
+
const height = kind === "decision" ? natural.height : span - over.top - over.bottom;
|
|
1026
|
+
const width = kind === "source" || kind === "sink" ? natural.width + Math.max(0, height - natural.height) : natural.width;
|
|
1027
|
+
return [stage.id, { y: top + (span - height - over.top - over.bottom) / 2 + over.top, height, width, over }];
|
|
1028
|
+
}));
|
|
1029
|
+
const inkWidth = (id) => stood.get(id).width + stood.get(id).over.left + stood.get(id).over.right;
|
|
1030
|
+
const widths = [];
|
|
1031
|
+
for (const s of stages) widths[column.get(s.id)] = Math.max(widths[column.get(s.id)] ?? 0, inkWidth(s.id));
|
|
1032
|
+
const lefts = Array.from(widths, (_, c) => plan.start + Array.from(widths.slice(0, c), (wd) => (wd ?? 0) + plan.gap).reduce((a, b) => a + b, 0));
|
|
1033
|
+
const nodes = stages.map((stage, k) => {
|
|
1034
|
+
const c = column.get(stage.id);
|
|
1035
|
+
const { y, height, width, over } = stood.get(stage.id);
|
|
1036
|
+
const box = { x: lefts[c] + (widths[c] - inkWidth(stage.id)) / 2 + over.left, y, width, height };
|
|
1037
|
+
return {
|
|
1038
|
+
...plan.drawn[k],
|
|
1039
|
+
at: { x: box.x, y: box.y },
|
|
1040
|
+
width: box.width,
|
|
1041
|
+
height: box.height,
|
|
1042
|
+
...kindOf(stage) === "decision" ? {} : { ports: lanePorts(stage, box, plan.lane, band) }
|
|
1043
|
+
};
|
|
1044
|
+
});
|
|
1045
|
+
const columns = Array.from(widths, (wd, c) => [lefts[c], lefts[c] + (wd ?? 0)]);
|
|
1046
|
+
const edges = [...tops, tops.length === 0 ? 0 : tops[tops.length - 1] + heights[heights.length - 1]];
|
|
1047
|
+
return { nodes, tops, heights, columns, edges };
|
|
1048
|
+
}
|
|
1049
|
+
var lanes = (pipeline, options = {}) => {
|
|
1050
|
+
const color = typeColors(pipeline, options.colors);
|
|
1051
|
+
const stages = uniqueStages(pipeline);
|
|
1052
|
+
const types = laneTypes(stages, options.lanes);
|
|
1053
|
+
const rows = types.length > 0 ? types : [""];
|
|
1054
|
+
const lane = new Map(types.map((t, i) => [t, i]));
|
|
1055
|
+
const spans = new Map(stages.map((s) => [s.id, spanOf(s, lane)]));
|
|
1056
|
+
const drawn = stages.map((stage) => kindOf(stage) === "decision" ? { id: stage.id, ...branchNode(stage, pipeline, options.branch, { controlSide: controlSideOf(stage, pipeline, lane) }) } : boxNode(stage, { showParams: options.showParams !== false, rowPorts: false, ports: [], run: options }));
|
|
1057
|
+
const labels = rows.map((t) => ({ id: laneId(t), lines: [t] }));
|
|
1058
|
+
const size = sizesOf([...drawn, ...labels], options.measure);
|
|
1059
|
+
const column = columnsOf(stages, drawableWires(pipeline), spans);
|
|
1060
|
+
const plan = {
|
|
1061
|
+
stages,
|
|
1062
|
+
drawn,
|
|
1063
|
+
size,
|
|
1064
|
+
spans,
|
|
1065
|
+
column,
|
|
1066
|
+
lane,
|
|
1067
|
+
rows: rows.length,
|
|
1068
|
+
least: Math.max(options.laneHeight ?? 48, ...labels.map((l) => size.get(l.id).box.height)),
|
|
1069
|
+
gap: options.rankGap ?? 40,
|
|
1070
|
+
start: 2 * MARGIN + Math.max(0, ...labels.map((l) => size.get(l.id).box.width))
|
|
1071
|
+
};
|
|
1072
|
+
const router = options.router ?? "bezier";
|
|
1073
|
+
const wires = wireEdges(pipeline, color);
|
|
1074
|
+
let insets = insetsFor(rows.length + 1, /* @__PURE__ */ new Map());
|
|
1075
|
+
let laid = place(plan, insets);
|
|
1076
|
+
let routed = routeWires(wires, laid.nodes, { grid: laid, column, router, measure: options.measure });
|
|
1077
|
+
for (let round = 0; round < 4; round++) {
|
|
1078
|
+
const need = insetsFor(rows.length + 1, routed.tracks);
|
|
1079
|
+
if (need.every((n, k) => n <= insets[k])) break;
|
|
1080
|
+
insets = need.map((n, k) => Math.max(n, insets[k]));
|
|
1081
|
+
laid = place(plan, insets);
|
|
1082
|
+
routed = routeWires(wires, laid.nodes, { grid: laid, column, router, measure: options.measure });
|
|
1083
|
+
}
|
|
1084
|
+
const { columns, tops, heights } = laid;
|
|
1085
|
+
const right = columns.length === 0 ? plan.start : columns[columns.length - 1][1] + MARGIN;
|
|
1086
|
+
const laneNodes = rows.map((t, i) => laneNode(t, { x: 0, y: tops[i], width: right, height: heights[i] }, color));
|
|
1087
|
+
const edges = routed.edges;
|
|
1088
|
+
const styles = sceneStyles(stages, edges);
|
|
1089
|
+
const laneStyle = new Map(rows.map((t, i) => [laneId(t), LANE_STYLES[i % LANE_STYLES.length]]));
|
|
1090
|
+
return {
|
|
1091
|
+
data: { nodes: [...laneNodes, ...laid.nodes], edges: edges.map((e) => e.edge), groups: uniqueGroups(pipeline) },
|
|
1092
|
+
scene: {
|
|
1093
|
+
layout: "none",
|
|
1094
|
+
router,
|
|
1095
|
+
...options.measure !== void 0 ? { measure: options.measure } : {},
|
|
1096
|
+
...styles,
|
|
1097
|
+
nodeStyle: (n) => laneStyle.get(n.id) ?? styles.nodeStyle(n)
|
|
1098
|
+
},
|
|
1099
|
+
color
|
|
1100
|
+
};
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
// src/effect/mainChain.ts
|
|
1104
|
+
function mainChain(pipeline) {
|
|
1105
|
+
const next = /* @__PURE__ */ new Map();
|
|
1106
|
+
for (const d of drawableWires(pipeline)) {
|
|
1107
|
+
if (isControl(pipeline, d.type)) continue;
|
|
1108
|
+
next.set(d.wire.from.stage, [...next.get(d.wire.from.stage) ?? [], d.wire.to.stage]);
|
|
1109
|
+
}
|
|
1110
|
+
const best = /* @__PURE__ */ new Map();
|
|
1111
|
+
const walking = /* @__PURE__ */ new Set();
|
|
1112
|
+
const from = (id) => {
|
|
1113
|
+
const known = best.get(id);
|
|
1114
|
+
if (known !== void 0) return known;
|
|
1115
|
+
if (walking.has(id)) return [];
|
|
1116
|
+
walking.add(id);
|
|
1117
|
+
let tail = [];
|
|
1118
|
+
for (const to of next.get(id) ?? []) {
|
|
1119
|
+
const path2 = from(to);
|
|
1120
|
+
if (path2.length > tail.length && !path2.includes(id)) tail = path2;
|
|
1121
|
+
}
|
|
1122
|
+
walking.delete(id);
|
|
1123
|
+
const path = [id, ...tail];
|
|
1124
|
+
best.set(id, path);
|
|
1125
|
+
return path;
|
|
1126
|
+
};
|
|
1127
|
+
let longest = [];
|
|
1128
|
+
for (const stage of uniqueStages(pipeline)) {
|
|
1129
|
+
const path = from(stage.id);
|
|
1130
|
+
if (path.length > longest.length) longest = path;
|
|
1131
|
+
}
|
|
1132
|
+
return longest;
|
|
1133
|
+
}
|
|
1134
|
+
function skipDecisions(pipeline, line) {
|
|
1135
|
+
const wires = drawableWires(pipeline);
|
|
1136
|
+
const stages = new Map(uniqueStages(pipeline).map((s) => [s.id, s]));
|
|
1137
|
+
const out = [];
|
|
1138
|
+
line.forEach((id, i) => {
|
|
1139
|
+
const decision = stages.get(id);
|
|
1140
|
+
if (decision === void 0 || kindOf(decision) !== "decision" || i === 0) return;
|
|
1141
|
+
if ((decision.inputs ?? []).length !== 1 || (decision.outputs ?? []).length !== 2) return;
|
|
1142
|
+
const entering = wires.filter((d) => d.wire.to.stage === id);
|
|
1143
|
+
const leaving = wires.filter((d) => d.wire.from.stage === id);
|
|
1144
|
+
if (entering.length !== 1 || leaving.length !== 2 || entering[0].wire.from.stage !== line[i - 1]) return;
|
|
1145
|
+
const through = leaving.find((d) => d.wire.to.stage === line[i + 1]);
|
|
1146
|
+
const skip = leaving.find((d) => d !== through && line.indexOf(d.wire.to.stage) > i + 1);
|
|
1147
|
+
const ordinary = (s) => s !== void 0 && kindOf(s) !== "decision";
|
|
1148
|
+
if (!ordinary(stages.get(line[i - 1])) || !ordinary(through?.toStage) || !ordinary(skip?.toStage)) return;
|
|
1149
|
+
if (through !== void 0 && skip !== void 0) out.push({ decision, into: entering[0], through, skip });
|
|
1150
|
+
});
|
|
1151
|
+
return out;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// src/effect/rack.ts
|
|
1155
|
+
var SKIP_OUT = "~skip-out";
|
|
1156
|
+
var SKIP_IN = "~skip-in";
|
|
1157
|
+
var SIDE_PORT_GAP = 24;
|
|
1158
|
+
var spreadAt = (ports, v) => ports.map((p, i) => ({ id: p.id, type: p.type, at: { u: (i + 1) / (ports.length + 1), v }, normal: { x: 0, y: v * 2 - 1 } }));
|
|
1159
|
+
function roomFor(ports) {
|
|
1160
|
+
const count = (v) => ports.filter((p) => p.at.v === v && p.at.u > 0 && p.at.u < 1).length;
|
|
1161
|
+
return (Math.max(count(0), count(1)) + 1) * SIDE_PORT_GAP;
|
|
1162
|
+
}
|
|
1163
|
+
function rackWires(pipeline, skips) {
|
|
1164
|
+
const eaten = new Set(skips.flatMap((s) => [s.into.index, s.through.index, s.skip.index]));
|
|
1165
|
+
const wires = drawableWires(pipeline).filter((d) => !eaten.has(d.index)).map((d) => {
|
|
1166
|
+
const label = exitLabel(d);
|
|
1167
|
+
return { from: d.wire.from, to: d.wire.to, type: d.type, problem: d.problem, ...label !== void 0 ? { label } : {} };
|
|
1168
|
+
});
|
|
1169
|
+
for (const s of skips) {
|
|
1170
|
+
wires.push({
|
|
1171
|
+
from: s.into.wire.from,
|
|
1172
|
+
to: s.through.wire.to,
|
|
1173
|
+
type: s.into.type,
|
|
1174
|
+
problem: s.into.problem || s.through.problem,
|
|
1175
|
+
exit: { stage: s.decision.id, port: s.through.wire.from.port }
|
|
1176
|
+
});
|
|
1177
|
+
wires.push({
|
|
1178
|
+
from: { stage: s.into.wire.from.stage, port: SKIP_OUT },
|
|
1179
|
+
to: { stage: s.skip.wire.to.stage, port: SKIP_IN },
|
|
1180
|
+
type: s.into.type,
|
|
1181
|
+
problem: s.into.problem || s.skip.problem,
|
|
1182
|
+
label: `${s.decision.title}: ${exitLabel(s.skip)}`,
|
|
1183
|
+
arc: true,
|
|
1184
|
+
exit: { stage: s.decision.id, port: s.skip.wire.from.port }
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
return wires;
|
|
1188
|
+
}
|
|
1189
|
+
function lineOf(pipeline, chain, wires) {
|
|
1190
|
+
const at = new Map(chain.map((id, i) => [id, i]));
|
|
1191
|
+
const mainIn = /* @__PURE__ */ new Map();
|
|
1192
|
+
const mainOut = /* @__PURE__ */ new Map();
|
|
1193
|
+
for (const wire of wires) {
|
|
1194
|
+
if (wire.arc || isControl(pipeline, wire.type)) continue;
|
|
1195
|
+
const a = at.get(wire.from.stage);
|
|
1196
|
+
const b = at.get(wire.to.stage);
|
|
1197
|
+
if (a === void 0 || b !== a + 1) continue;
|
|
1198
|
+
if (!mainOut.has(wire.from.stage)) mainOut.set(wire.from.stage, wire.from.port);
|
|
1199
|
+
if (!mainIn.has(wire.to.stage)) mainIn.set(wire.to.stage, wire.to.port);
|
|
1200
|
+
}
|
|
1201
|
+
return { at, mainIn, mainOut };
|
|
1202
|
+
}
|
|
1203
|
+
function portsOf(stage, line, wires, near, far) {
|
|
1204
|
+
if (!line.at.has(stage.id)) return [...spreadAt(stage.outputs ?? [], far), ...spreadAt(stage.inputs ?? [], near)];
|
|
1205
|
+
const typed = (p, anchor) => ({ id: p.id, type: p.type, at: anchor });
|
|
1206
|
+
const out = [];
|
|
1207
|
+
const side = [];
|
|
1208
|
+
for (const p of stage.inputs ?? []) {
|
|
1209
|
+
if (p.id === line.mainIn.get(stage.id)) out.push(typed(p, { u: 0, v: 0.5 }));
|
|
1210
|
+
else side.push(p);
|
|
1211
|
+
}
|
|
1212
|
+
for (const p of stage.outputs ?? []) {
|
|
1213
|
+
if (p.id === line.mainOut.get(stage.id)) out.push(typed(p, { u: 1, v: 0.5 }));
|
|
1214
|
+
else side.push(p);
|
|
1215
|
+
}
|
|
1216
|
+
out.push(...spreadAt(side, near));
|
|
1217
|
+
const type = portOn(stage, line.mainOut.get(stage.id) ?? line.mainIn.get(stage.id) ?? "")?.type;
|
|
1218
|
+
const arc = (id, u) => ({ id, at: { u, v: far }, ...type !== void 0 ? { type } : {} });
|
|
1219
|
+
if (wires.some((w) => w.arc && w.from.stage === stage.id)) out.push(arc(SKIP_OUT, 0.75));
|
|
1220
|
+
if (wires.some((w) => w.arc && w.to.stage === stage.id)) out.push(arc(SKIP_IN, 0.25));
|
|
1221
|
+
return out;
|
|
1222
|
+
}
|
|
1223
|
+
var rack = (pipeline, options = {}) => {
|
|
1224
|
+
const color = typeColors(pipeline, options.colors);
|
|
1225
|
+
const below = options.side !== "above";
|
|
1226
|
+
const near = below ? 1 : 0;
|
|
1227
|
+
const far = below ? 0 : 1;
|
|
1228
|
+
const stages = uniqueStages(pipeline);
|
|
1229
|
+
const given = options.chain?.filter((id) => stages.some((s) => s.id === id));
|
|
1230
|
+
const ordered = given !== void 0 && given.length > 0 ? given : mainChain(pipeline);
|
|
1231
|
+
const skips = options.collapseSkips === false ? [] : skipDecisions(pipeline, ordered);
|
|
1232
|
+
const gone = new Set(skips.map((s) => s.decision.id));
|
|
1233
|
+
const chain = ordered.filter((id) => !gone.has(id));
|
|
1234
|
+
const wires = rackWires(pipeline, skips);
|
|
1235
|
+
const line = lineOf(pipeline, chain, wires);
|
|
1236
|
+
const kept = stages.filter((s) => !gone.has(s.id));
|
|
1237
|
+
const controlSide = below ? "bottom" : "top";
|
|
1238
|
+
const nodes = kept.map((stage) => {
|
|
1239
|
+
if (kindOf(stage) === "decision") return { id: stage.id, ...branchNode(stage, pipeline, options.branch, { controlSide }) };
|
|
1240
|
+
const ports = portsOf(stage, line, wires, near, far);
|
|
1241
|
+
const node = boxNode(stage, { showParams: options.showParams !== false, rowPorts: false, ports, run: options });
|
|
1242
|
+
const room = roomFor(ports);
|
|
1243
|
+
return room > 2 * SIDE_PORT_GAP ? { ...node, width: room } : node;
|
|
1244
|
+
});
|
|
1245
|
+
const byId = new Map(kept.map((s) => [s.id, s]));
|
|
1246
|
+
const edges = wires.map((wire) => {
|
|
1247
|
+
const to = byId.get(wire.to.stage);
|
|
1248
|
+
const sideInput = to !== void 0 && kindOf(to) !== "decision" && !wire.arc && wire.to.port !== line.mainIn.get(wire.to.stage) && line.at.has(wire.to.stage);
|
|
1249
|
+
const label = wire.label ?? (sideInput ? portOn(to, wire.to.port)?.label ?? wire.to.port : void 0);
|
|
1250
|
+
return {
|
|
1251
|
+
edge: wireEdge(wire.from, wire.to, label),
|
|
1252
|
+
style: wireStyle(color(wire.type), wire.arc === true || isControl(pipeline, wire.type), wire.problem)
|
|
1253
|
+
};
|
|
1254
|
+
});
|
|
1255
|
+
return {
|
|
1256
|
+
data: {
|
|
1257
|
+
nodes,
|
|
1258
|
+
edges: edges.map((e) => e.edge),
|
|
1259
|
+
groups: uniqueGroups(pipeline).map((g) => ({ ...g, members: g.members.filter((m) => !gone.has(m)) }))
|
|
1260
|
+
},
|
|
1261
|
+
scene: {
|
|
1262
|
+
layout: "chain",
|
|
1263
|
+
layoutOptions: {
|
|
1264
|
+
direction: "right",
|
|
1265
|
+
chain,
|
|
1266
|
+
chainSide: below ? "after" : "before",
|
|
1267
|
+
...options.nodeGap !== void 0 ? { nodeGap: options.nodeGap } : {},
|
|
1268
|
+
...options.rankGap !== void 0 ? { rankGap: options.rankGap } : {}
|
|
1269
|
+
},
|
|
1270
|
+
router: "bezier",
|
|
1271
|
+
...sceneStyles(kept, edges)
|
|
1272
|
+
},
|
|
1273
|
+
color,
|
|
1274
|
+
exits: new Map(wires.flatMap((wire, i) => wire.exit === void 0 ? [] : [[edgeIdOf(edges[i].edge), wire.exit]]))
|
|
1275
|
+
};
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
// src/effect/looks.ts
|
|
1279
|
+
var EFFECT_LOOKS = Object.freeze({ nodeGraph, rack, lanes, compact });
|
|
1280
|
+
|
|
1281
|
+
export { BRANCH_SYMBOLS, COMPACT_PADDING, EFFECT_LOOKS, EFFECT_MIN_SCALE, EffectChart, LANE_INSET, NO_VALUE, PREVIEW_SIZE, PROBLEM_COLOR, branchNode, checkPipeline, compact, costText, drawableWires, exitLabel, isControl, kindOf, laneId, lanes, mainChain, nodeGraph, paintRun, portOn, previewRowId, rack, stageRows, stageStyle, symbolNameFor, typeColors, uniqueGroups, uniqueStages, valueRowId, wireEdges, wireStyle };
|
|
1282
|
+
//# sourceMappingURL=effect.js.map
|
|
1283
|
+
//# sourceMappingURL=effect.js.map
|