@weasel-js/diagram 1.10.1 → 1.11.1
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 +1160 -26
- package/dist/chunk-43LDZ6L4.js +2053 -0
- package/dist/chunk-43LDZ6L4.js.map +1 -0
- package/dist/chunk-CI3I3I6T.js +3600 -0
- package/dist/chunk-CI3I3I6T.js.map +1 -0
- package/dist/chunk-IM6DHWZO.js +3220 -0
- package/dist/chunk-IM6DHWZO.js.map +1 -0
- package/dist/chunk-QKSWZEJH.js +312 -0
- package/dist/chunk-QKSWZEJH.js.map +1 -0
- package/dist/chunk-T6DQT553.js +1431 -0
- package/dist/chunk-T6DQT553.js.map +1 -0
- package/dist/chunk-WDIE35AK.js +3 -0
- package/dist/chunk-WDIE35AK.js.map +1 -0
- package/dist/dataTypes-B-iEwKZp.d.ts +553 -0
- package/dist/effect-audio.d.ts +138 -0
- package/dist/effect-audio.js +527 -0
- package/dist/effect-audio.js.map +1 -0
- package/dist/effect.d.ts +782 -0
- package/dist/effect.js +2542 -0
- package/dist/effect.js.map +1 -0
- package/dist/icons.table.d.ts +6 -0
- package/dist/icons.table.js +32 -0
- package/dist/icons.table.js.map +1 -0
- package/dist/icons.table.json +24 -0
- package/dist/index.d.ts +2270 -355
- package/dist/index.js +953 -1331
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts +3 -2
- package/dist/layout.entry-CH9gc0CY.d.ts +512 -0
- package/dist/layout.js +3 -1
- package/dist/stageTypes-DvnR8cKQ.d.ts +189 -0
- package/dist/{layout.entry-BUkiRKuX.d.ts → types-BbHunLXm.d.ts} +382 -261
- package/package.json +32 -7
- package/dist/chunk-JZ4CVLAB.js +0 -1247
- package/dist/chunk-JZ4CVLAB.js.map +0 -1
package/dist/effect.js
ADDED
|
@@ -0,0 +1,2542 @@
|
|
|
1
|
+
import { DiagramView, reorderContribution, ANYWHERE, isBodyEnd, sequenceSlots } from './chunk-CI3I3I6T.js';
|
|
2
|
+
import './chunk-T6DQT553.js';
|
|
3
|
+
import { stageFromType, LOOK_THEME, drawableWires, uniqueStages, shownPorts, valueRowId, NO_VALUE, previewRowId, formatParam, stageStyle, PROBLEM_COLOR, typeColors, kindOf, boxNode, portOn, wireStyle, isControl, wireEdge, sceneStyles, uniqueGroups, wireEdges, exitLabel, splitProblem, checkPipeline, PREVIEW_DENSITY, runOrder, PREVIEW_SIZE, closeBitmaps, previewsOf, paramValues } from './chunk-QKSWZEJH.js';
|
|
4
|
+
export { LOOK_THEME, NO_VALUE, PREVIEW_SIZE, PROBLEM_COLOR, checkPipeline, drawableWires, exitLabel, formatParam, isControl, kindOf, portOn, previewRowId, runOrder, shownPorts, stageFromType, stageRows, stageStyle, typeColors, uniqueGroups, uniqueStages, valueRowId, wireEdges, wireStyle } from './chunk-QKSWZEJH.js';
|
|
5
|
+
import { diagramScene, diagramPages, edgeIdOf, titleIdOf, groupLabelIdOf, nestGroups, LABEL_PLACEMENT, groupMinWidth, EDGE_DERIVE_PATH } from './chunk-43LDZ6L4.js';
|
|
6
|
+
import { portOf, DEFAULT_GROUP_INSET, SIDE_TRACK_GAP, widenForMinWidth, portSitesOf, labelCenter, insetOf, rankGaps } from './chunk-IM6DHWZO.js';
|
|
7
|
+
import { polygonFromPoints } from '@weasel-js/core/math';
|
|
8
|
+
import { forwardRef, useMemo, useRef, useState, useImperativeHandle, useEffect, useCallback, useSyncExternalStore } from 'react';
|
|
9
|
+
import { useLatest, bitmapSrc, ActionDisabledReason, pointAlongPath } from '@weasel-js/core';
|
|
10
|
+
import { releaseImageSource } from '@weasel-js/core/renderer';
|
|
11
|
+
import { jsx } from 'react/jsx-runtime';
|
|
12
|
+
import { useLatest as useLatest$1, adoptCssOnce, useVisibleRaf } from '@weasel-js/react';
|
|
13
|
+
|
|
14
|
+
// src/effect/groupEdit.ts
|
|
15
|
+
var groupsOf = (doc) => doc.pipeline.groups ?? [];
|
|
16
|
+
function withGroups(doc, groups, boxes = doc.groupBoxes) {
|
|
17
|
+
const ids = new Set(groups.map((g) => g.id));
|
|
18
|
+
const kept = Object.entries(boxes ?? {}).filter(([id]) => ids.has(id));
|
|
19
|
+
const { groupBoxes: _was, ...rest } = doc;
|
|
20
|
+
return { ...rest, pipeline: { ...doc.pipeline, groups }, ...kept.length > 0 ? { groupBoxes: Object.fromEntries(kept) } : {} };
|
|
21
|
+
}
|
|
22
|
+
function pruneGroups(groups, gone, keep = () => false) {
|
|
23
|
+
const out = new Set(gone);
|
|
24
|
+
let left = [...groups];
|
|
25
|
+
for (; ; ) {
|
|
26
|
+
const emptied = left.filter((g) => g.members.length > 0 && g.members.every((m) => out.has(m)) && !keep(g.id));
|
|
27
|
+
if (emptied.length === 0) return left.map((g) => ({ ...g, members: g.members.filter((m) => !out.has(m)) }));
|
|
28
|
+
for (const g of emptied) out.add(g.id);
|
|
29
|
+
left = left.filter((g) => !emptied.includes(g));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
var parentOf = (groups, id) => groups.find((g) => g.members.includes(id));
|
|
33
|
+
function nextGroupId(p) {
|
|
34
|
+
const used = /* @__PURE__ */ new Set([...p.stages.map((s) => s.id), ...(p.groups ?? []).map((g) => g.id)]);
|
|
35
|
+
if (!used.has("group")) return "group";
|
|
36
|
+
let n = 2;
|
|
37
|
+
while (used.has(`group-${n}`)) n++;
|
|
38
|
+
return `group-${n}`;
|
|
39
|
+
}
|
|
40
|
+
function groupStages(doc, ids, label) {
|
|
41
|
+
if (ids.length === 0) return doc;
|
|
42
|
+
const id = nextGroupId(doc.pipeline);
|
|
43
|
+
const groups = groupsOf(doc);
|
|
44
|
+
const parents = new Set(ids.map((m) => parentOf(groups, m)?.id));
|
|
45
|
+
const shared = parents.size === 1 ? [...parents][0] : void 0;
|
|
46
|
+
const taken = new Set(ids);
|
|
47
|
+
const made = { id, ...label ? { label } : {}, members: [...ids] };
|
|
48
|
+
const rest = shared === void 0 ? pruneGroups(groups, taken) : groups.map((g) => {
|
|
49
|
+
if (g.id !== shared) return g;
|
|
50
|
+
const at = g.members.findIndex((m) => taken.has(m));
|
|
51
|
+
const members = g.members.filter((m) => !taken.has(m));
|
|
52
|
+
members.splice(at, 0, id);
|
|
53
|
+
return { ...g, members };
|
|
54
|
+
});
|
|
55
|
+
return withGroups(doc, [...rest, made]);
|
|
56
|
+
}
|
|
57
|
+
function ungroup(doc, id) {
|
|
58
|
+
const groups = groupsOf(doc);
|
|
59
|
+
const gone = groups.find((g) => g.id === id);
|
|
60
|
+
if (gone === void 0) return doc;
|
|
61
|
+
const kept = groups.filter((g) => g.id !== id).map((g) => g.members.includes(id) ? { ...g, members: g.members.flatMap((m) => m === id ? gone.members : [m]) } : g);
|
|
62
|
+
return withGroups(doc, kept);
|
|
63
|
+
}
|
|
64
|
+
function renameGroup(doc, id, label) {
|
|
65
|
+
return withGroups(doc, groupsOf(doc).map((g) => {
|
|
66
|
+
if (g.id !== id) return g;
|
|
67
|
+
const { label: _was, ...rest } = g;
|
|
68
|
+
return label === "" ? rest : { ...rest, label };
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
function within(groups, inner, outer) {
|
|
72
|
+
for (let at = inner; at !== void 0; at = parentOf(groups, at)?.id) if (at === outer) return true;
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
function moveToGroup(doc, ids, group) {
|
|
76
|
+
const groups = groupsOf(doc);
|
|
77
|
+
if (group !== null && (!groups.some((g) => g.id === group) || ids.some((id) => within(groups, group, id)))) return doc;
|
|
78
|
+
const moving = new Set(ids);
|
|
79
|
+
const boxed = (id) => id === group || doc.groupBoxes?.[id] !== void 0;
|
|
80
|
+
const left = pruneGroups(groups, moving, boxed);
|
|
81
|
+
const placed = group === null ? left : left.map((g) => g.id === group ? { ...g, members: [...g.members, ...ids] } : g);
|
|
82
|
+
return withGroups(doc, placed);
|
|
83
|
+
}
|
|
84
|
+
function setGroupBox(doc, id, box) {
|
|
85
|
+
const { [id]: _was, ...rest } = doc.groupBoxes ?? {};
|
|
86
|
+
return withGroups(doc, groupsOf(doc), box === null ? rest : { ...rest, [id]: { x: box.x, y: box.y, width: box.width, height: box.height } });
|
|
87
|
+
}
|
|
88
|
+
function groupContents(doc, id) {
|
|
89
|
+
const groups = new Map(groupsOf(doc).map((g) => [g.id, g]));
|
|
90
|
+
const out = { stages: /* @__PURE__ */ new Set(), groups: /* @__PURE__ */ new Set() };
|
|
91
|
+
const visit = (g) => {
|
|
92
|
+
if (out.groups.has(g)) return;
|
|
93
|
+
out.groups.add(g);
|
|
94
|
+
for (const m of groups.get(g)?.members ?? []) {
|
|
95
|
+
if (groups.has(m)) visit(m);
|
|
96
|
+
else out.stages.add(m);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
if (groups.has(id)) visit(id);
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
function moveGroup(doc, id, offset) {
|
|
103
|
+
const { stages, groups } = groupContents(doc, id);
|
|
104
|
+
const by = (p) => ({ x: p.x + offset.x, y: p.y + offset.y });
|
|
105
|
+
const places = Object.fromEntries(Object.entries(doc.places).map(([s, p]) => [s, stages.has(s) ? by(p) : p]));
|
|
106
|
+
const inside = new Set(doc.pipeline.wires.flatMap((w) => w.id !== void 0 && stages.has(w.from.stage) && stages.has(w.to.stage) ? [w.id] : []));
|
|
107
|
+
const routes = doc.routes === void 0 ? void 0 : Object.fromEntries(Object.entries(doc.routes).map(([w, r]) => [w, inside.has(w) ? r.map(by) : r]));
|
|
108
|
+
const boxes = doc.groupBoxes === void 0 ? void 0 : Object.fromEntries(Object.entries(doc.groupBoxes).map(([g, b]) => [g, groups.has(g) ? { ...b, ...by(b) } : b]));
|
|
109
|
+
return {
|
|
110
|
+
...doc,
|
|
111
|
+
places,
|
|
112
|
+
...routes !== void 0 ? { routes } : {},
|
|
113
|
+
...boxes !== void 0 ? { groupBoxes: boxes } : {}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/effect/lookRoutes.ts
|
|
118
|
+
var EPS = 1e-3;
|
|
119
|
+
var near = (a, b) => Math.abs(a.x - b.x) <= EPS && Math.abs(a.y - b.y) <= EPS;
|
|
120
|
+
var copy = (p) => ({ x: p.x, y: p.y });
|
|
121
|
+
function withLookRoutes(doc, routes) {
|
|
122
|
+
const kept = Object.fromEntries(Object.entries(routes).filter(([, r]) => r !== void 0 && Object.keys(r).length > 0));
|
|
123
|
+
const { lookRoutes: _was, ...rest } = doc;
|
|
124
|
+
return Object.keys(kept).length > 0 ? { ...rest, lookRoutes: kept } : rest;
|
|
125
|
+
}
|
|
126
|
+
function setLookRoute(doc, look, wire, waypoints, ends) {
|
|
127
|
+
const { [wire]: _was, ...rest } = doc.lookRoutes?.[look] ?? {};
|
|
128
|
+
const route = { waypoints: waypoints.map(copy), ends: [copy(ends[0]), copy(ends[1])] };
|
|
129
|
+
return withLookRoutes(doc, { ...doc.lookRoutes, [look]: waypoints.length > 0 ? { ...rest, [wire]: route } : rest });
|
|
130
|
+
}
|
|
131
|
+
function pruneLookRoutes(doc, rewired = []) {
|
|
132
|
+
if (doc.lookRoutes === void 0) return doc;
|
|
133
|
+
const gone = new Set(rewired);
|
|
134
|
+
const ids = new Set(doc.pipeline.wires.flatMap((w) => w.id === void 0 || gone.has(w.id) ? [] : [w.id]));
|
|
135
|
+
const keep = (r) => r === void 0 ? void 0 : Object.fromEntries(Object.entries(r).filter(([id]) => ids.has(id)));
|
|
136
|
+
return withLookRoutes(doc, { rack: keep(doc.lookRoutes.rack), lanes: keep(doc.lookRoutes.lanes) });
|
|
137
|
+
}
|
|
138
|
+
function wireEnds(specs, edge) {
|
|
139
|
+
const spec = specs.find((s) => s.id === edge);
|
|
140
|
+
const trait = spec?.data?.diagram;
|
|
141
|
+
const [from, to] = spec?.dependsOn ?? [];
|
|
142
|
+
const point = (node, port) => {
|
|
143
|
+
const n = node === void 0 || port === void 0 ? void 0 : specs.find((s) => s.id === node);
|
|
144
|
+
return n === void 0 ? void 0 : portOf({ id: node, kind: n.kind ?? "leaf", data: n.data }, n.pose, port)?.point;
|
|
145
|
+
};
|
|
146
|
+
const a = point(from, trait?.from?.port);
|
|
147
|
+
const b = point(to, trait?.to?.port);
|
|
148
|
+
return a === void 0 || b === void 0 ? void 0 : [a, b];
|
|
149
|
+
}
|
|
150
|
+
function applyLookRoutes(specs, doc, look, built) {
|
|
151
|
+
const routes = look === "nodeGraph" ? void 0 : doc.lookRoutes?.[look];
|
|
152
|
+
if (routes === void 0) return specs;
|
|
153
|
+
let held = false;
|
|
154
|
+
const edges = built.data.edges.map((e) => {
|
|
155
|
+
const id = edgeIdOf(e);
|
|
156
|
+
const wire = built.wireOf(id);
|
|
157
|
+
const route = wire === void 0 ? void 0 : routes[wire];
|
|
158
|
+
const ends = route === void 0 ? void 0 : wireEnds(specs, id);
|
|
159
|
+
if (route === void 0 || ends === void 0 || !near(ends[0], route.ends[0]) || !near(ends[1], route.ends[1])) return e;
|
|
160
|
+
held = true;
|
|
161
|
+
return { ...e, waypoints: route.waypoints };
|
|
162
|
+
});
|
|
163
|
+
return held ? diagramScene({ ...built.data, edges }, built.scene) : specs;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/effect/edit.ts
|
|
167
|
+
var withPipeline = (doc, pipeline) => ({
|
|
168
|
+
...doc,
|
|
169
|
+
pipeline: { ...doc.pipeline, ...pipeline }
|
|
170
|
+
});
|
|
171
|
+
function nextStageId(p, base) {
|
|
172
|
+
const used = new Set(p.stages.map((s) => s.id));
|
|
173
|
+
if (!used.has(base)) return base;
|
|
174
|
+
let n = 2;
|
|
175
|
+
while (used.has(`${base}-${n}`)) n++;
|
|
176
|
+
return `${base}-${n}`;
|
|
177
|
+
}
|
|
178
|
+
function nextWireId(p) {
|
|
179
|
+
const used = new Set(p.wires.map((w) => w.id));
|
|
180
|
+
let n = 1;
|
|
181
|
+
while (used.has(`w${n}`)) n++;
|
|
182
|
+
return `w${n}`;
|
|
183
|
+
}
|
|
184
|
+
function addStage(doc, stage, at) {
|
|
185
|
+
const next = withPipeline(doc, { stages: [...doc.pipeline.stages, stage] });
|
|
186
|
+
return at ? { ...next, places: { ...doc.places, [stage.id]: { x: at.x, y: at.y } } } : next;
|
|
187
|
+
}
|
|
188
|
+
function removeStages(doc, ids) {
|
|
189
|
+
const gone = new Set(ids);
|
|
190
|
+
const { groups } = doc.pipeline;
|
|
191
|
+
const wires = doc.pipeline.wires.filter((w) => !gone.has(w.from.stage) && !gone.has(w.to.stage));
|
|
192
|
+
const kept = groups ? pruneGroups(groups, gone) : void 0;
|
|
193
|
+
const left = new Set(kept?.map((g) => g.id));
|
|
194
|
+
const boxes = doc.groupBoxes === void 0 ? void 0 : Object.entries(doc.groupBoxes).filter(([id]) => left.has(id));
|
|
195
|
+
const { groupBoxes: _was, ...rest } = doc;
|
|
196
|
+
return pruneRoutes({
|
|
197
|
+
...rest,
|
|
198
|
+
pipeline: {
|
|
199
|
+
...doc.pipeline,
|
|
200
|
+
stages: doc.pipeline.stages.filter((s) => !gone.has(s.id)),
|
|
201
|
+
wires,
|
|
202
|
+
...kept ? { groups: kept } : {}
|
|
203
|
+
},
|
|
204
|
+
places: Object.fromEntries(Object.entries(doc.places).filter(([id]) => !gone.has(id))),
|
|
205
|
+
...boxes !== void 0 && boxes.length > 0 ? { groupBoxes: Object.fromEntries(boxes) } : {}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function pruneRoutes(doc, rewired = []) {
|
|
209
|
+
const pruned = pruneLookRoutes(doc, rewired);
|
|
210
|
+
if (pruned.routes === void 0) return pruned;
|
|
211
|
+
const ids = new Set(doc.pipeline.wires.flatMap((w) => w.id === void 0 || rewired.includes(w.id) ? [] : [w.id]));
|
|
212
|
+
return { ...pruned, routes: Object.fromEntries(Object.entries(pruned.routes).filter(([id]) => ids.has(id))) };
|
|
213
|
+
}
|
|
214
|
+
function setWireRoute(doc, wire, points) {
|
|
215
|
+
const { [wire]: _was, ...rest } = doc.routes ?? {};
|
|
216
|
+
return { ...doc, routes: points.length > 0 ? { ...rest, [wire]: points.map((p) => ({ x: p.x, y: p.y })) } : rest };
|
|
217
|
+
}
|
|
218
|
+
function addWire(doc, wire) {
|
|
219
|
+
const w = wire.id === void 0 ? { ...wire, id: nextWireId(doc.pipeline) } : wire;
|
|
220
|
+
return withPipeline(doc, { wires: [...doc.pipeline.wires, w] });
|
|
221
|
+
}
|
|
222
|
+
function removeWires(doc, ids) {
|
|
223
|
+
const gone = new Set(ids);
|
|
224
|
+
return pruneRoutes(withPipeline(doc, { wires: doc.pipeline.wires.filter((w) => w.id === void 0 || !gone.has(w.id)) }));
|
|
225
|
+
}
|
|
226
|
+
function reconnectWire(doc, id, end, to) {
|
|
227
|
+
return withPipeline(doc, {
|
|
228
|
+
wires: doc.pipeline.wires.map((w) => w.id === id ? { ...w, [end]: { stage: to.stage, port: to.port } } : w)
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
var addressed = (p, key) => (p.id ?? p.label) === key;
|
|
232
|
+
function setParam(doc, stage, param2, control) {
|
|
233
|
+
return withPipeline(doc, {
|
|
234
|
+
stages: doc.pipeline.stages.map(
|
|
235
|
+
(s) => s.id !== stage || !s.params ? s : { ...s, params: s.params.map((p) => addressed(p, param2) ? { ...p, control, value: formatParam(control) } : p) }
|
|
236
|
+
)
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function moveStages(doc, moves) {
|
|
240
|
+
const places = { ...doc.places };
|
|
241
|
+
for (const m of moves) places[m.id] = { x: m.x, y: m.y };
|
|
242
|
+
return { ...doc, places };
|
|
243
|
+
}
|
|
244
|
+
function setLaneOrder(doc, order) {
|
|
245
|
+
return { ...doc, laneOrder: [...order] };
|
|
246
|
+
}
|
|
247
|
+
function ensureWireIds(doc) {
|
|
248
|
+
if (doc.pipeline.wires.every((w) => w.id !== void 0)) return doc;
|
|
249
|
+
const used = new Set(doc.pipeline.wires.map((w) => w.id));
|
|
250
|
+
let n = 1;
|
|
251
|
+
const wires = doc.pipeline.wires.map((w) => {
|
|
252
|
+
if (w.id !== void 0) return w;
|
|
253
|
+
while (used.has(`w${n}`)) n++;
|
|
254
|
+
const id = `w${n}`;
|
|
255
|
+
used.add(id);
|
|
256
|
+
return { ...w, id };
|
|
257
|
+
});
|
|
258
|
+
return withPipeline(doc, { wires });
|
|
259
|
+
}
|
|
260
|
+
function canWire(p, from, to) {
|
|
261
|
+
if (from.stage === to.stage) return false;
|
|
262
|
+
const out = p.stages.find((s) => s.id === from.stage)?.outputs?.find((x) => x.id === from.port);
|
|
263
|
+
const inn = p.stages.find((s) => s.id === to.stage)?.inputs?.find((x) => x.id === to.port);
|
|
264
|
+
return out !== void 0 && inn !== void 0 && out.type === inn.type;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/effect/mainChain.ts
|
|
268
|
+
function mainChain(pipeline) {
|
|
269
|
+
const next = /* @__PURE__ */ new Map();
|
|
270
|
+
for (const d of drawableWires(pipeline)) {
|
|
271
|
+
if (isControl(pipeline, d.type)) continue;
|
|
272
|
+
next.set(d.wire.from.stage, [...next.get(d.wire.from.stage) ?? [], d.wire.to.stage]);
|
|
273
|
+
}
|
|
274
|
+
const best = /* @__PURE__ */ new Map();
|
|
275
|
+
const walking = /* @__PURE__ */ new Set();
|
|
276
|
+
const from = (id) => {
|
|
277
|
+
const known = best.get(id);
|
|
278
|
+
if (known !== void 0) return known;
|
|
279
|
+
if (walking.has(id)) return [];
|
|
280
|
+
walking.add(id);
|
|
281
|
+
let tail = [];
|
|
282
|
+
for (const to of next.get(id) ?? []) {
|
|
283
|
+
const path2 = from(to);
|
|
284
|
+
if (path2.length > tail.length && !path2.includes(id)) tail = path2;
|
|
285
|
+
}
|
|
286
|
+
walking.delete(id);
|
|
287
|
+
const path = [id, ...tail];
|
|
288
|
+
best.set(id, path);
|
|
289
|
+
return path;
|
|
290
|
+
};
|
|
291
|
+
let longest = [];
|
|
292
|
+
for (const stage of uniqueStages(pipeline)) {
|
|
293
|
+
const path = from(stage.id);
|
|
294
|
+
if (path.length > longest.length) longest = path;
|
|
295
|
+
}
|
|
296
|
+
return longest;
|
|
297
|
+
}
|
|
298
|
+
function skipDecisions(pipeline, line) {
|
|
299
|
+
const wires = drawableWires(pipeline);
|
|
300
|
+
const stages = new Map(uniqueStages(pipeline).map((s) => [s.id, s]));
|
|
301
|
+
const out = [];
|
|
302
|
+
line.forEach((id, i) => {
|
|
303
|
+
const decision = stages.get(id);
|
|
304
|
+
if (decision === void 0 || kindOf(decision) !== "decision" || i === 0) return;
|
|
305
|
+
if ((decision.inputs ?? []).length !== 1 || (decision.outputs ?? []).length !== 2) return;
|
|
306
|
+
const entering = wires.filter((d) => d.wire.to.stage === id);
|
|
307
|
+
const leaving = wires.filter((d) => d.wire.from.stage === id);
|
|
308
|
+
if (entering.length !== 1 || leaving.length !== 2 || entering[0].wire.from.stage !== line[i - 1]) return;
|
|
309
|
+
const through = leaving.find((d) => d.wire.to.stage === line[i + 1]);
|
|
310
|
+
const skip = leaving.find((d) => d !== through && line.indexOf(d.wire.to.stage) > i + 1);
|
|
311
|
+
const ordinary = (s) => s !== void 0 && kindOf(s) !== "decision";
|
|
312
|
+
if (!ordinary(stages.get(line[i - 1])) || !ordinary(through?.toStage) || !ordinary(skip?.toStage)) return;
|
|
313
|
+
if (through !== void 0 && skip !== void 0) out.push({ decision, into: entering[0], through, skip });
|
|
314
|
+
});
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/effect/chainEdit.ts
|
|
319
|
+
function chainLinks(pipeline, chain) {
|
|
320
|
+
const at = new Map(chain.map((id, i) => [id, i]));
|
|
321
|
+
const mainIn = /* @__PURE__ */ new Map();
|
|
322
|
+
const mainOut = /* @__PURE__ */ new Map();
|
|
323
|
+
const between2 = Array.from({ length: Math.max(0, chain.length - 1) }, () => void 0);
|
|
324
|
+
for (const d of drawableWires(pipeline)) {
|
|
325
|
+
if (isControl(pipeline, d.type)) continue;
|
|
326
|
+
const a = at.get(d.wire.from.stage);
|
|
327
|
+
if (a === void 0 || at.get(d.wire.to.stage) !== a + 1 || between2[a] !== void 0) continue;
|
|
328
|
+
between2[a] = d.index;
|
|
329
|
+
mainOut.set(d.wire.from.stage, d.wire.from.port);
|
|
330
|
+
mainIn.set(d.wire.to.stage, d.wire.to.port);
|
|
331
|
+
}
|
|
332
|
+
return { mainIn, mainOut, between: between2 };
|
|
333
|
+
}
|
|
334
|
+
var portsOf = (p, stage, side) => p.stages.find((s) => s.id === stage)?.[side] ?? [];
|
|
335
|
+
function movedChain(chain, i, j, to) {
|
|
336
|
+
const run = chain.slice(i, j + 1);
|
|
337
|
+
const out = [...chain.slice(0, i), ...chain.slice(j + 1)];
|
|
338
|
+
out.splice(to > j ? to - run.length : to, 0, ...run);
|
|
339
|
+
return out;
|
|
340
|
+
}
|
|
341
|
+
function moveInChain(doc, stage, to) {
|
|
342
|
+
return moveRunInChain(doc, stage, stage, to);
|
|
343
|
+
}
|
|
344
|
+
function moveRunInChain(doc, first, last, to) {
|
|
345
|
+
const p = doc.pipeline;
|
|
346
|
+
const chain = mainChain(p);
|
|
347
|
+
const i = chain.indexOf(first);
|
|
348
|
+
const j = chain.indexOf(last);
|
|
349
|
+
if (i < 0 || j < i || !Number.isInteger(to) || to < 0 || to > chain.length || to >= i && to <= j + 1) return null;
|
|
350
|
+
const { mainIn, mainOut, between: between2 } = chainLinks(p, chain);
|
|
351
|
+
const wIn = i > 0 ? between2[i - 1] : void 0;
|
|
352
|
+
const wOut = j < chain.length - 1 ? between2[j] : void 0;
|
|
353
|
+
const wAB = to > 0 && to < chain.length ? between2[to - 1] : void 0;
|
|
354
|
+
const gone = new Set([wIn, wOut, wAB].filter((k) => k !== void 0));
|
|
355
|
+
const kept = p.wires.filter((_, k) => !gone.has(k));
|
|
356
|
+
const free = (ref) => !kept.some((w) => w.to.stage === ref.stage && w.to.port === ref.port);
|
|
357
|
+
const pick = (id, side, type) => portsOf(p, id, side).find((port) => (type === void 0 || port.type === type) && (side === "outputs" || free({ stage: id, port: port.id })))?.id;
|
|
358
|
+
const typeOf = (id, port) => [...portsOf(p, id, "inputs"), ...portsOf(p, id, "outputs")].find((x) => x.id === port)?.type;
|
|
359
|
+
const made = [];
|
|
360
|
+
const prev = chain[i - 1];
|
|
361
|
+
const next = chain[j + 1];
|
|
362
|
+
if (prev !== void 0 && next !== void 0) made.push({ reuse: wIn, from: p.wires[wIn].from, to: p.wires[wOut].to });
|
|
363
|
+
const a = chain[to - 1];
|
|
364
|
+
const b = chain[to];
|
|
365
|
+
const sIn = mainIn.get(first) ?? pick(first, "inputs");
|
|
366
|
+
const sOut = mainOut.get(last) ?? pick(last, "outputs");
|
|
367
|
+
if (a !== void 0) {
|
|
368
|
+
const aOut = wAB !== void 0 ? p.wires[wAB].from.port : mainOut.get(a) ?? pick(a, "outputs", typeOf(first, sIn));
|
|
369
|
+
if (aOut === void 0 || sIn === void 0) return null;
|
|
370
|
+
made.push({ reuse: wAB, from: { stage: a, port: aOut }, to: { stage: first, port: sIn } });
|
|
371
|
+
}
|
|
372
|
+
if (b !== void 0) {
|
|
373
|
+
const bIn = wAB !== void 0 ? p.wires[wAB].to.port : mainIn.get(b) ?? pick(b, "inputs", typeOf(last, sOut));
|
|
374
|
+
if (bIn === void 0 || sOut === void 0) return null;
|
|
375
|
+
made.push({ reuse: wOut, from: { stage: last, port: sOut }, to: { stage: b, port: bIn } });
|
|
376
|
+
}
|
|
377
|
+
if (made.some((m) => !canWire(p, m.from, m.to) || !free(m.to))) return null;
|
|
378
|
+
if (new Set(made.map((m) => `${m.to.stage}\0${m.to.port}`)).size !== made.length) return null;
|
|
379
|
+
const wires = p.wires.flatMap((w, k) => {
|
|
380
|
+
if (!gone.has(k)) return [w];
|
|
381
|
+
const m = made.find((x) => x.reuse === k);
|
|
382
|
+
return m === void 0 ? [] : [{ ...w, from: m.from, to: m.to }];
|
|
383
|
+
});
|
|
384
|
+
for (const m of made) {
|
|
385
|
+
if (m.reuse !== void 0) continue;
|
|
386
|
+
wires.push({ id: nextWireId({ ...p, wires }), from: m.from, to: m.to });
|
|
387
|
+
}
|
|
388
|
+
const pipeline = { ...p, wires };
|
|
389
|
+
const want = movedChain(chain, i, j, to);
|
|
390
|
+
const got = mainChain(pipeline);
|
|
391
|
+
if (got.length !== want.length || got.some((id, k) => id !== want[k])) return null;
|
|
392
|
+
return pruneRoutes({ ...doc, pipeline }, [...gone].flatMap((k) => p.wires[k].id ?? []));
|
|
393
|
+
}
|
|
394
|
+
function canMoveInChain(doc, stage, to) {
|
|
395
|
+
return moveInChain(doc, stage, to) !== null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/effect/symbolParts.ts
|
|
399
|
+
function along(side, i, n) {
|
|
400
|
+
const t = (i + 1) / (n + 1);
|
|
401
|
+
if (side === "left") return { u: 0, v: t };
|
|
402
|
+
if (side === "right") return { u: 1, v: t };
|
|
403
|
+
return side === "top" ? { u: t, v: 0 } : { u: t, v: 1 };
|
|
404
|
+
}
|
|
405
|
+
var spread = (ports, side) => ports.map((p, i) => ({ id: p.id, type: p.type, at: along(side, i, ports.length) }));
|
|
406
|
+
function alongLine(points, t) {
|
|
407
|
+
const lengths = points.slice(1).map((p, i) => Math.hypot(p[0] - points[i][0], p[1] - points[i][1]));
|
|
408
|
+
let left = t * lengths.reduce((a, b) => a + b, 0);
|
|
409
|
+
for (const [i, len] of lengths.entries()) {
|
|
410
|
+
if (left <= len || i === lengths.length - 1) {
|
|
411
|
+
const k = len === 0 ? 0 : Math.min(left / len, 1);
|
|
412
|
+
const [a, b] = [points[i], points[i + 1]];
|
|
413
|
+
return { u: a[0] + (b[0] - a[0]) * k, v: a[1] + (b[1] - a[1]) * k };
|
|
414
|
+
}
|
|
415
|
+
left -= len;
|
|
416
|
+
}
|
|
417
|
+
return { u: points[0][0], v: points[0][1] };
|
|
418
|
+
}
|
|
419
|
+
var placeAlong = (sides) => (ports, side) => ports.map((p, i) => ({ id: p.id, type: p.type, at: alongLine(sides[side], (i + 1) / (ports.length + 1)) }));
|
|
420
|
+
function inputs(stage, pipeline, options, steer, place3 = spread) {
|
|
421
|
+
const all = stage.inputs ?? [];
|
|
422
|
+
const steering = all.filter((p) => isControl(pipeline, p.type) || p.id === steer && all.length > 1);
|
|
423
|
+
const side = options?.controlSide === "bottom" ? "bottom" : "top";
|
|
424
|
+
return [...place3(all.filter((p) => !steering.includes(p)), "left"), ...place3(steering, side)];
|
|
425
|
+
}
|
|
426
|
+
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 } };
|
|
427
|
+
var DIAMOND_SIDES = placeAlong({
|
|
428
|
+
left: [[0, 0], [0, 1]],
|
|
429
|
+
top: [[0.5, 0], [0.5, 0]],
|
|
430
|
+
right: [[1, 0], [1, 1]],
|
|
431
|
+
bottom: [[0, 0.5], [0.5, 1]]
|
|
432
|
+
});
|
|
433
|
+
var diamond = (stage, pipeline, options) => {
|
|
434
|
+
const exits = stage.outputs ?? [];
|
|
435
|
+
const ins = inputs(stage, pipeline, options, void 0, options?.controlSide === "bottom" ? DIAMOND_SIDES : spread);
|
|
436
|
+
const topFree = !ins.some((p) => p.at.v === 0);
|
|
437
|
+
const out = exits.length === 2 ? [
|
|
438
|
+
{ id: exits[0].id, type: exits[0].type, at: { u: 1, v: 0.5 } },
|
|
439
|
+
{ id: exits[1].id, type: exits[1].type, at: { u: 0.5, v: 1 }, ...topFree ? { sites: [{ u: 0.5, v: 0 }] } : {} }
|
|
440
|
+
] : spread(exits, "right");
|
|
441
|
+
return { outline: "diamond", lines: [stage.title], ports: [...ins, ...out] };
|
|
442
|
+
};
|
|
443
|
+
var BAND_TINTS = ["#c58bff", "#6fb3ff", "#5ad07f", "#f2c14e"];
|
|
444
|
+
var styleOf = (stage, options) => options?.style ?? stageStyle(stage);
|
|
445
|
+
var tintsOf = (stage, options) => options?.tints ?? (stage.bypassed === true ? [styleOf(stage, options).stroke] : BAND_TINTS);
|
|
446
|
+
var canBand = (stage) => stage.split !== void 0 && splitProblem(stage) === null;
|
|
447
|
+
var twoExits = (stage) => (stage.outputs ?? []).length === 2;
|
|
448
|
+
var bandExits = (exits) => exits.map((p, i) => ({ id: p.id, type: p.type, at: { u: 1, v: 1 - (i + 0.5) / exits.length } }));
|
|
449
|
+
var cutLabels = (cuts, n) => cuts.map((cut, k) => ({
|
|
450
|
+
text: String(cut),
|
|
451
|
+
at: { u: 0, v: 1 - (k + 1) / n },
|
|
452
|
+
self: { u: 1, v: 0.5 },
|
|
453
|
+
offset: { x: -10, y: 0 }
|
|
454
|
+
}));
|
|
455
|
+
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`;
|
|
456
|
+
|
|
457
|
+
// src/effect/glyphs.ts
|
|
458
|
+
var outlineOf = (points) => polygonFromPoints(points.map(([x, y]) => ({ x, y })));
|
|
459
|
+
var POINT = 0.2;
|
|
460
|
+
var onHexagon = placeAlong({
|
|
461
|
+
left: [[POINT, 0], [0, 0.5], [POINT, 1]],
|
|
462
|
+
right: [[1 - POINT, 0], [1, 0.5], [1 - POINT, 1]],
|
|
463
|
+
top: [[POINT, 0], [1 - POINT, 0]],
|
|
464
|
+
bottom: [[POINT, 1], [1 - POINT, 1]]
|
|
465
|
+
});
|
|
466
|
+
var hexagon = (stage, pipeline, options) => ({
|
|
467
|
+
outline: "hexagon",
|
|
468
|
+
lines: [stage.title],
|
|
469
|
+
ports: [...inputs(stage, pipeline, options, void 0, onHexagon), ...onHexagon(stage.outputs ?? [], "right")]
|
|
470
|
+
});
|
|
471
|
+
var step = (stage, pipeline, options) => {
|
|
472
|
+
if (!canBand(stage)) return diamond(stage, pipeline, options);
|
|
473
|
+
const exits = stage.outputs ?? [];
|
|
474
|
+
const n = exits.length;
|
|
475
|
+
const ink = styleOf(stage, options).text;
|
|
476
|
+
const level = (i) => 1 - (i + 0.5) / n;
|
|
477
|
+
const riser = (k) => 0.1 + 0.8 * (k + 1) / n;
|
|
478
|
+
let stairs = `M0 ${level(0)}`;
|
|
479
|
+
for (let k = 0; k < n - 1; k++) stairs += ` H${riser(k)} V${level(k + 1)}`;
|
|
480
|
+
const marks = [{ d: `${stairs} H1`, stroke: ink, strokeWidth: 2 }];
|
|
481
|
+
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 });
|
|
482
|
+
stage.split.at.forEach((cut, k) => marks.push({
|
|
483
|
+
text: String(cut),
|
|
484
|
+
at: { u: riser(k), v: 1 },
|
|
485
|
+
self: { u: 0.5, v: 0 },
|
|
486
|
+
offset: { x: 0, y: 3 },
|
|
487
|
+
style: { fontSize: 10 }
|
|
488
|
+
}));
|
|
489
|
+
marks.push(title(stage, options, 18));
|
|
490
|
+
return {
|
|
491
|
+
outline: "rect",
|
|
492
|
+
lines: [],
|
|
493
|
+
padding: 0,
|
|
494
|
+
width: 56,
|
|
495
|
+
height: Math.max(48, 24 * n),
|
|
496
|
+
ports: [...inputs(stage, pipeline, options, stage.split.input), ...bandExits(exits)],
|
|
497
|
+
marks
|
|
498
|
+
};
|
|
499
|
+
};
|
|
500
|
+
var TRIANGLE = [[0, 0], [1, 0.5], [0, 1]];
|
|
501
|
+
var COMPARATOR = outlineOf(TRIANGLE);
|
|
502
|
+
var EXIT_AT = 0.7;
|
|
503
|
+
var onTriangle = placeAlong({
|
|
504
|
+
left: [[0, 0], [0, 1]],
|
|
505
|
+
right: [[1, 0.5], [1, 0.5]],
|
|
506
|
+
// The first part of each slanted edge, short of the exits.
|
|
507
|
+
top: [[0, 0], [0.6, 0.3]],
|
|
508
|
+
bottom: [[0, 1], [0.6, 0.7]]
|
|
509
|
+
});
|
|
510
|
+
var comparator = (stage, pipeline, options) => {
|
|
511
|
+
if (!twoExits(stage)) return diamond(stage, pipeline, options);
|
|
512
|
+
const outs = stage.outputs;
|
|
513
|
+
const banded = canBand(stage);
|
|
514
|
+
const [upper, lower] = banded ? [outs[1], outs[0]] : [outs[0], outs[1]];
|
|
515
|
+
const ins = inputs(stage, pipeline, options, void 0, onTriangle);
|
|
516
|
+
const signal = ins.filter((p) => p.at.u === 0);
|
|
517
|
+
const ink = styleOf(stage, options).text;
|
|
518
|
+
const marks = [];
|
|
519
|
+
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 });
|
|
520
|
+
else if (signal.length === 2) {
|
|
521
|
+
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 }));
|
|
522
|
+
}
|
|
523
|
+
marks.push(title(stage, options));
|
|
524
|
+
const exit = (p, v) => ({ id: p.id, type: p.type, at: { u: EXIT_AT, v } });
|
|
525
|
+
return {
|
|
526
|
+
outline: COMPARATOR,
|
|
527
|
+
lines: [],
|
|
528
|
+
padding: 0,
|
|
529
|
+
width: 56,
|
|
530
|
+
height: 56,
|
|
531
|
+
ports: [...ins, exit(upper, EXIT_AT / 2), exit(lower, 1 - EXIT_AT / 2)],
|
|
532
|
+
marks
|
|
533
|
+
};
|
|
534
|
+
};
|
|
535
|
+
function response(i, n) {
|
|
536
|
+
const lo = 1 - i / n;
|
|
537
|
+
const hi = 1 - (i + 1) / n;
|
|
538
|
+
const skirt = 0.25 / n;
|
|
539
|
+
const rise = i === 0 ? "M0.75 1" : `M0.15 ${lo + skirt} C0.15 ${lo} 0.75 ${lo} 0.75 ${lo - skirt}`;
|
|
540
|
+
const fall = i === n - 1 ? " V0" : ` V${hi + skirt} C0.75 ${hi} 0.15 ${hi} 0.15 ${hi - skirt}`;
|
|
541
|
+
return rise + fall;
|
|
542
|
+
}
|
|
543
|
+
var crossover = (stage, pipeline, options) => {
|
|
544
|
+
if (!canBand(stage)) return diamond(stage, pipeline, options);
|
|
545
|
+
const exits = stage.outputs ?? [];
|
|
546
|
+
const n = exits.length;
|
|
547
|
+
const ink = styleOf(stage, options).text;
|
|
548
|
+
const tints = tintsOf(stage, options);
|
|
549
|
+
const marks = [];
|
|
550
|
+
exits.forEach((_, i) => {
|
|
551
|
+
marks.push({ d: response(i, n), stroke: tints[i % tints.length], strokeWidth: 1.5 });
|
|
552
|
+
marks.push({ d: `M0.75 ${1 - (i + 0.5) / n} H1`, stroke: ink });
|
|
553
|
+
});
|
|
554
|
+
marks.push(...cutLabels(stage.split.at, n), title(stage, options));
|
|
555
|
+
return {
|
|
556
|
+
outline: "rect",
|
|
557
|
+
lines: [],
|
|
558
|
+
padding: 0,
|
|
559
|
+
width: 56,
|
|
560
|
+
height: Math.max(56, 28 * n),
|
|
561
|
+
ports: [...inputs(stage, pipeline, options, stage.split.input), ...bandExits(exits)],
|
|
562
|
+
marks
|
|
563
|
+
};
|
|
564
|
+
};
|
|
565
|
+
var atPoint = (p) => [p, p];
|
|
566
|
+
var onDot = placeAlong({ left: atPoint([0, 0.5]), right: atPoint([1, 0.5]), top: atPoint([0.5, 0]), bottom: atPoint([0.5, 1]) });
|
|
567
|
+
var FORK = 12;
|
|
568
|
+
var fork = (stage, pipeline, options) => ({
|
|
569
|
+
outline: "stadium",
|
|
570
|
+
lines: [],
|
|
571
|
+
padding: 0,
|
|
572
|
+
width: FORK,
|
|
573
|
+
height: FORK,
|
|
574
|
+
ports: [...inputs(stage, pipeline, options, void 0, onDot), ...onDot(stage.outputs ?? [], "right")],
|
|
575
|
+
marks: [{ d: dot(0.5, 0.5, 0.5), fill: styleOf(stage, options).text }, title(stage, options)]
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
// src/effect/symbols.ts
|
|
579
|
+
var bar = (stage, pipeline, options) => {
|
|
580
|
+
if (!canBand(stage)) return diamond(stage, pipeline, options);
|
|
581
|
+
const cuts = stage.split.at;
|
|
582
|
+
const exits = stage.outputs ?? [];
|
|
583
|
+
const n = exits.length;
|
|
584
|
+
const ink = styleOf(stage, options).text;
|
|
585
|
+
const tints = tintsOf(stage, options);
|
|
586
|
+
const marks = [];
|
|
587
|
+
exits.forEach((_, i) => {
|
|
588
|
+
const top = 1 - (i + 1) / n;
|
|
589
|
+
marks.push({ d: `M0 ${top} H1 V${1 - i / n} H0 Z`, fill: tints[i % tints.length], opacity: 0.3 });
|
|
590
|
+
});
|
|
591
|
+
cuts.forEach((_, k) => marks.push({ d: `M-0.25 ${1 - (k + 1) / n} H1.25`, stroke: ink }));
|
|
592
|
+
marks.push(...cutLabels(cuts, n), title(stage, options));
|
|
593
|
+
return {
|
|
594
|
+
outline: "rect",
|
|
595
|
+
lines: [],
|
|
596
|
+
padding: 0,
|
|
597
|
+
width: 24,
|
|
598
|
+
height: Math.max(72, 30 * n),
|
|
599
|
+
ports: [...inputs(stage, pipeline, options, stage.split.input), ...bandExits(exits)],
|
|
600
|
+
marks
|
|
601
|
+
};
|
|
602
|
+
};
|
|
603
|
+
var demux = (stage, pipeline, options) => {
|
|
604
|
+
const exits = stage.outputs ?? [];
|
|
605
|
+
return {
|
|
606
|
+
outline: "trapezoid",
|
|
607
|
+
lines: [],
|
|
608
|
+
padding: 0,
|
|
609
|
+
width: 44,
|
|
610
|
+
height: Math.max(64, 28 * exits.length),
|
|
611
|
+
ports: [...inputs(stage, pipeline, options), ...spread(exits, "right")],
|
|
612
|
+
marks: [title(stage, options)]
|
|
613
|
+
};
|
|
614
|
+
};
|
|
615
|
+
var LEVER_FIRST = "M0.2 0.5 L0.78 0.27";
|
|
616
|
+
var CONTACT_FIRST = dot(0.8, 0.25, 0.07);
|
|
617
|
+
var CONTACT_SECOND = dot(0.8, 0.75, 0.07);
|
|
618
|
+
var THROWN_SECOND = /* @__PURE__ */ new Map([
|
|
619
|
+
[LEVER_FIRST, "M0.2 0.5 L0.78 0.73"],
|
|
620
|
+
[CONTACT_FIRST, CONTACT_SECOND],
|
|
621
|
+
[CONTACT_SECOND, CONTACT_FIRST]
|
|
622
|
+
]);
|
|
623
|
+
var thrownToSecond = (d) => THROWN_SECOND.get(d);
|
|
624
|
+
var switchSymbol = (stage, pipeline, options) => {
|
|
625
|
+
if (!twoExits(stage)) return diamond(stage, pipeline, options);
|
|
626
|
+
const exits = stage.outputs ?? [];
|
|
627
|
+
const ink = styleOf(stage, options).text;
|
|
628
|
+
return {
|
|
629
|
+
outline: "rect",
|
|
630
|
+
lines: [],
|
|
631
|
+
padding: 0,
|
|
632
|
+
width: 48,
|
|
633
|
+
height: 48,
|
|
634
|
+
ports: [
|
|
635
|
+
...inputs(stage, pipeline, options),
|
|
636
|
+
...exits.map((p, i) => ({ id: p.id, type: p.type, at: { u: 1, v: i === 0 ? 0.25 : 0.75 } }))
|
|
637
|
+
],
|
|
638
|
+
marks: [
|
|
639
|
+
{ d: "M0 0.5 H0.2 M0.8 0.25 H1 M0.8 0.75 H1", stroke: ink },
|
|
640
|
+
{ d: LEVER_FIRST, stroke: ink, strokeWidth: 2.5 },
|
|
641
|
+
{ d: dot(0.2, 0.5, 0.07), fill: ink },
|
|
642
|
+
{ d: CONTACT_FIRST, fill: ink },
|
|
643
|
+
{ d: CONTACT_SECOND, stroke: ink },
|
|
644
|
+
title(stage, options)
|
|
645
|
+
]
|
|
646
|
+
};
|
|
647
|
+
};
|
|
648
|
+
var BRANCH_SYMBOLS = Object.freeze({
|
|
649
|
+
diamond,
|
|
650
|
+
bar,
|
|
651
|
+
demux,
|
|
652
|
+
switch: switchSymbol,
|
|
653
|
+
hexagon,
|
|
654
|
+
step,
|
|
655
|
+
comparator,
|
|
656
|
+
crossover,
|
|
657
|
+
fork
|
|
658
|
+
});
|
|
659
|
+
var NEEDS = {
|
|
660
|
+
bar: canBand,
|
|
661
|
+
step: canBand,
|
|
662
|
+
crossover: canBand,
|
|
663
|
+
switch: twoExits,
|
|
664
|
+
comparator: twoExits
|
|
665
|
+
};
|
|
666
|
+
function symbolNameFor(stage, choice = "auto") {
|
|
667
|
+
const want = choice === "auto" ? canBand(stage) ? "bar" : "diamond" : choice;
|
|
668
|
+
return NEEDS[want]?.(stage) === false ? "diamond" : want;
|
|
669
|
+
}
|
|
670
|
+
function branchNode(stage, pipeline, choice = "auto", options) {
|
|
671
|
+
return BRANCH_SYMBOLS[symbolNameFor(stage, choice)](stage, pipeline, options);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// src/effect/compact.ts
|
|
675
|
+
var COMPACT_PADDING = 4;
|
|
676
|
+
function sidePorts(stage) {
|
|
677
|
+
return [
|
|
678
|
+
...(stage.inputs ?? []).map((p) => ({ id: p.id, at: { u: 0, v: 0.5 } })),
|
|
679
|
+
...(stage.outputs ?? []).map((p) => ({ id: p.id, at: { u: 1, v: 0.5 } }))
|
|
680
|
+
];
|
|
681
|
+
}
|
|
682
|
+
var untyped = (ports) => (ports ?? []).map(({ type: _type, ...port }) => port);
|
|
683
|
+
function nodeOf(stage, pipeline, branch, theme) {
|
|
684
|
+
if (kindOf(stage) !== "decision") {
|
|
685
|
+
return { ...boxNode(stage, { showParams: false, rowPorts: false, ports: sidePorts(stage) }), padding: COMPACT_PADDING };
|
|
686
|
+
}
|
|
687
|
+
const symbol = branchNode(stage, pipeline, branch, { style: stageStyle(stage, theme) });
|
|
688
|
+
return { id: stage.id, ...symbol, ports: untyped(symbol.ports), padding: symbol.padding ?? COMPACT_PADDING };
|
|
689
|
+
}
|
|
690
|
+
function lines(pipeline) {
|
|
691
|
+
const by = /* @__PURE__ */ new Map();
|
|
692
|
+
for (const d of drawableWires(pipeline)) {
|
|
693
|
+
const exit = kindOf(d.fromStage) === "decision" ? d.wire.from.port : "";
|
|
694
|
+
const key = JSON.stringify([d.wire.from.stage, exit, d.wire.to.stage]);
|
|
695
|
+
by.set(key, [...by.get(key) ?? [], d]);
|
|
696
|
+
}
|
|
697
|
+
return [...by.values()];
|
|
698
|
+
}
|
|
699
|
+
var compact = (pipeline, options = {}) => {
|
|
700
|
+
const theme = options.theme ?? LOOK_THEME;
|
|
701
|
+
const color = typeColors(pipeline, options.colors, theme.series);
|
|
702
|
+
const stages = uniqueStages(pipeline);
|
|
703
|
+
const nodes = stages.map((stage) => nodeOf(stage, pipeline, options.branch ?? "hexagon", theme));
|
|
704
|
+
const edges = lines(pipeline).map((line) => {
|
|
705
|
+
const first = line.find((d) => !isControl(pipeline, d.type)) ?? line[0];
|
|
706
|
+
return {
|
|
707
|
+
edge: wireEdge(first.wire.from, first.wire.to, exitLabel(first)),
|
|
708
|
+
style: wireStyle(color(first.type), line.every((d) => isControl(pipeline, d.type)), line.some((d) => d.problem))
|
|
709
|
+
};
|
|
710
|
+
});
|
|
711
|
+
return {
|
|
712
|
+
data: { nodes, edges: edges.map((e) => e.edge), groups: uniqueGroups(pipeline) },
|
|
713
|
+
scene: {
|
|
714
|
+
layout: "layered",
|
|
715
|
+
layoutOptions: { direction: "right", nodeGap: options.nodeGap ?? 16, rankGap: options.rankGap ?? 48 },
|
|
716
|
+
router: options.router ?? "bezier",
|
|
717
|
+
...sceneStyles(stages, edges, theme)
|
|
718
|
+
},
|
|
719
|
+
color
|
|
720
|
+
};
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
// src/effect/nodeGraph.ts
|
|
724
|
+
var nodeGraph = (pipeline, options = {}) => {
|
|
725
|
+
const theme = options.theme ?? LOOK_THEME;
|
|
726
|
+
const color = typeColors(pipeline, options.colors, theme.series);
|
|
727
|
+
const stages = uniqueStages(pipeline);
|
|
728
|
+
const nodes = stages.map((stage) => kindOf(stage) === "decision" ? { id: stage.id, ...branchNode(stage, pipeline, options.branch, { style: stageStyle(stage, theme) }) } : boxNode(stage, { showParams: options.showParams !== false, rowPorts: true, ports: [], run: options }));
|
|
729
|
+
const edges = wireEdges(pipeline, color);
|
|
730
|
+
return {
|
|
731
|
+
data: { nodes, edges: edges.map((e) => e.edge), groups: uniqueGroups(pipeline) },
|
|
732
|
+
scene: {
|
|
733
|
+
layout: "layered",
|
|
734
|
+
layoutOptions: {
|
|
735
|
+
direction: "right",
|
|
736
|
+
...options.nodeGap !== void 0 ? { nodeGap: options.nodeGap } : {},
|
|
737
|
+
...options.rankGap !== void 0 ? { rankGap: options.rankGap } : {}
|
|
738
|
+
},
|
|
739
|
+
router: options.router ?? "bezier",
|
|
740
|
+
...sceneStyles(stages, edges, theme)
|
|
741
|
+
},
|
|
742
|
+
color
|
|
743
|
+
};
|
|
744
|
+
};
|
|
745
|
+
function useReleaseReplaced(run) {
|
|
746
|
+
const shown = useRef(/* @__PURE__ */ new Set());
|
|
747
|
+
useEffect(() => {
|
|
748
|
+
const now = new Set(Object.values(run?.stages ?? {}).flatMap((s) => Object.values(s.previews ?? {})));
|
|
749
|
+
for (const old of shown.current) if (!now.has(old)) releaseImageSource(old);
|
|
750
|
+
shown.current = now;
|
|
751
|
+
}, [run]);
|
|
752
|
+
useEffect(() => () => {
|
|
753
|
+
for (const old of shown.current) releaseImageSource(old);
|
|
754
|
+
shown.current = /* @__PURE__ */ new Set();
|
|
755
|
+
}, []);
|
|
756
|
+
}
|
|
757
|
+
var DIM = 0.3;
|
|
758
|
+
var AT_COLOR = "#ffc94d";
|
|
759
|
+
var AT_WIDTH = 3;
|
|
760
|
+
var COST_GAP = 2;
|
|
761
|
+
var COST_SCALE = 0.8;
|
|
762
|
+
function paintRun(pipeline, specs, run, exits = /* @__PURE__ */ new Map()) {
|
|
763
|
+
if (run === void 0) return [...specs];
|
|
764
|
+
const stages = new Map(uniqueStages(pipeline).map((s) => [s.id, s]));
|
|
765
|
+
const unreached = unreachedStages(pipeline, run);
|
|
766
|
+
const owner = ownerOf(stages);
|
|
767
|
+
const passed = /* @__PURE__ */ new Set();
|
|
768
|
+
for (const spec of specs) {
|
|
769
|
+
const ends = wireEnds2(spec);
|
|
770
|
+
if (ends === null) continue;
|
|
771
|
+
const exit = exits.get(spec.id) ?? { stage: ends.from, port: ends.fromPort };
|
|
772
|
+
const taken = run.stages[exit.stage]?.taken;
|
|
773
|
+
if (unreached.has(ends.from) || unreached.has(ends.to) || unreached.has(exit.stage) || taken !== void 0 && exit.port !== taken) {
|
|
774
|
+
passed.add(spec.id);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
const values = /* @__PURE__ */ new Map();
|
|
778
|
+
const previews = /* @__PURE__ */ new Map();
|
|
779
|
+
for (const [id, stage] of stages) {
|
|
780
|
+
for (const p of shownPorts(stage)) {
|
|
781
|
+
values.set(`${id}/${valueRowId(p.id)}`, `${p.label ?? p.id}: ${run.stages[id]?.values?.[p.id] ?? NO_VALUE}`);
|
|
782
|
+
const preview = run.stages[id]?.previews?.[p.id];
|
|
783
|
+
if (preview !== void 0) previews.set(`${id}/${previewRowId(p.id)}`, preview);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const thrownSecond = new Set([...stages.values()].filter((s) => {
|
|
787
|
+
const outs = s.outputs ?? [];
|
|
788
|
+
return outs.length === 2 && run.stages[s.id]?.taken === outs[1].id;
|
|
789
|
+
}).map((s) => s.id));
|
|
790
|
+
const out = specs.map((spec) => {
|
|
791
|
+
const id = spec.id;
|
|
792
|
+
let next = spec;
|
|
793
|
+
const text = values.get(id);
|
|
794
|
+
if (text !== void 0 && spec.data.text !== text) next = { ...next, data: { ...next.data, text } };
|
|
795
|
+
const preview = previews.get(id);
|
|
796
|
+
if (preview !== void 0) next = { ...next, data: { image: { src: bitmapSrc(preview) } } };
|
|
797
|
+
const stage = owner(spec);
|
|
798
|
+
if (stage !== void 0 && thrownSecond.has(stage)) next = thrown(next);
|
|
799
|
+
const dependsOn = spec.dependsOn;
|
|
800
|
+
const onWire = Array.isArray(dependsOn) && dependsOn.length === 1 && passed.has(dependsOn[0]);
|
|
801
|
+
if (stage !== void 0 && unreached.has(stage) || passed.has(id) || onWire) next = { ...next, data: dimmed(next.data) };
|
|
802
|
+
if (id === run.at && next.data.stroke !== void 0) {
|
|
803
|
+
next = { ...next, data: { ...next.data, stroke: { paint: { color: AT_COLOR }, width: AT_WIDTH } } };
|
|
804
|
+
}
|
|
805
|
+
return next;
|
|
806
|
+
});
|
|
807
|
+
for (const spec of specs) {
|
|
808
|
+
const id = spec.id;
|
|
809
|
+
const entry = stages.has(id) ? run.stages[id] : void 0;
|
|
810
|
+
const note = entry?.error ?? (entry?.cost !== void 0 ? costText(entry.cost) : void 0);
|
|
811
|
+
if (note !== void 0) out.push(costSpec(spec, specs.filter((s) => owner(s) === id), stages.get(id), note, entry?.error !== void 0));
|
|
812
|
+
}
|
|
813
|
+
return out;
|
|
814
|
+
}
|
|
815
|
+
function unreachedStages(pipeline, run) {
|
|
816
|
+
if (run === void 0) return /* @__PURE__ */ new Set();
|
|
817
|
+
return new Set(uniqueStages(pipeline).map((s) => s.id).filter((id) => run.stages[id] === void 0));
|
|
818
|
+
}
|
|
819
|
+
function ownerOf(stages) {
|
|
820
|
+
return (spec) => {
|
|
821
|
+
const id = spec.id;
|
|
822
|
+
if (stages.has(id)) return id;
|
|
823
|
+
const parent = spec.parent;
|
|
824
|
+
if (parent !== void 0 && stages.has(parent)) return parent;
|
|
825
|
+
const deps = spec.dependsOn;
|
|
826
|
+
const anchor = Array.isArray(deps) && deps.length === 1 ? deps[0] : void 0;
|
|
827
|
+
return anchor !== void 0 && stages.has(anchor) ? anchor : void 0;
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function wireEnds2(spec) {
|
|
831
|
+
const trait = spec.data?.diagram;
|
|
832
|
+
const deps = spec.dependsOn;
|
|
833
|
+
if (trait?.from === void 0 || trait.to === void 0 || !Array.isArray(deps) || deps.length !== 2) return null;
|
|
834
|
+
return { from: deps[0], to: deps[1], fromPort: trait.from.port };
|
|
835
|
+
}
|
|
836
|
+
function thrown(spec) {
|
|
837
|
+
const trait = spec.data.diagram;
|
|
838
|
+
const d = trait?.mark === void 0 ? void 0 : thrownToSecond(trait.mark.d);
|
|
839
|
+
return d === void 0 ? spec : { ...spec, data: { ...spec.data, diagram: { ...trait, mark: { d } } } };
|
|
840
|
+
}
|
|
841
|
+
function dimmed(data) {
|
|
842
|
+
const fade = (part) => {
|
|
843
|
+
const paint = part?.paint;
|
|
844
|
+
if (paint === void 0) return part;
|
|
845
|
+
return { ...part, paint: { ...paint, opacity: (paint.opacity ?? 1) * DIM } };
|
|
846
|
+
};
|
|
847
|
+
const d = data;
|
|
848
|
+
return {
|
|
849
|
+
...data,
|
|
850
|
+
...d.fill !== void 0 ? { fill: fade(d.fill) } : {},
|
|
851
|
+
...d.stroke !== void 0 ? { stroke: fade(d.stroke) } : {}
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
function costSpec(stage, own, of, text, problem) {
|
|
855
|
+
const title2 = own.find((s) => s !== stage && s.data.text !== void 0);
|
|
856
|
+
const titleData = title2?.data ?? {};
|
|
857
|
+
const fontSize = Math.round((titleData.style?.fontSize ?? 12) * COST_SCALE);
|
|
858
|
+
const style = { fontFamily: titleData.style?.fontFamily ?? "sans-serif", fontSize };
|
|
859
|
+
const fill = problem ? { paint: { color: PROBLEM_COLOR } } : titleData.fill ?? { paint: { color: stageStyle(of).text } };
|
|
860
|
+
const width = text.length * fontSize * 0.6;
|
|
861
|
+
const height = Math.ceil(fontSize * 1.2);
|
|
862
|
+
const box = stage.pose;
|
|
863
|
+
const top = Math.min(box.y, ...own.flatMap((s) => {
|
|
864
|
+
const p = s.pose;
|
|
865
|
+
return s !== stage && p !== void 0 && p.y + p.height <= box.y ? [p.y] : [];
|
|
866
|
+
}));
|
|
867
|
+
return {
|
|
868
|
+
id: `${stage.id}/cost`,
|
|
869
|
+
kind: "leaf",
|
|
870
|
+
layer: "main",
|
|
871
|
+
pickable: false,
|
|
872
|
+
pose: { x: box.x + box.width - width, y: top - height - COST_GAP, width, height },
|
|
873
|
+
data: { text, style, fill }
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function costText(ms) {
|
|
877
|
+
if (ms >= 100) return `${Math.round(ms)} ms`;
|
|
878
|
+
return `${Number(ms.toPrecision(3))} ms`;
|
|
879
|
+
}
|
|
880
|
+
function useRunPortOptions(color, pipeline, run) {
|
|
881
|
+
const missed = JSON.stringify([...unreachedStages(pipeline, run)].sort());
|
|
882
|
+
return useMemo(() => {
|
|
883
|
+
const faded = new Set(JSON.parse(missed));
|
|
884
|
+
return {
|
|
885
|
+
shape: "circle",
|
|
886
|
+
sizePx: 8,
|
|
887
|
+
// Every port a look declares has a value type; the compass defaults have none.
|
|
888
|
+
showsPort: (port) => port.type !== void 0,
|
|
889
|
+
fill: (port) => ({ paint: { color: color(port.type), ...faded.has(port.nodeId) ? { opacity: DIM } : {} } })
|
|
890
|
+
};
|
|
891
|
+
}, [color, missed]);
|
|
892
|
+
}
|
|
893
|
+
var EFFECT_MIN_SCALE = 0.5;
|
|
894
|
+
var keys = /* @__PURE__ */ new WeakMap();
|
|
895
|
+
var nextKey = 0;
|
|
896
|
+
function keyOf(built) {
|
|
897
|
+
let key = keys.get(built);
|
|
898
|
+
if (key === void 0) {
|
|
899
|
+
key = nextKey++;
|
|
900
|
+
keys.set(built, key);
|
|
901
|
+
}
|
|
902
|
+
return key;
|
|
903
|
+
}
|
|
904
|
+
function contentOf(value) {
|
|
905
|
+
try {
|
|
906
|
+
return JSON.stringify(value) ?? null;
|
|
907
|
+
} catch {
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function useSameContent(value) {
|
|
912
|
+
const [kept, setKept] = useState(() => ({ value, content: contentOf(value) }));
|
|
913
|
+
if (kept.value === value) return value;
|
|
914
|
+
const content = contentOf(value);
|
|
915
|
+
if (content !== null && content === kept.content) return kept.value;
|
|
916
|
+
setKept({ value, content });
|
|
917
|
+
return value;
|
|
918
|
+
}
|
|
919
|
+
function EffectChart({
|
|
920
|
+
pipeline: given,
|
|
921
|
+
run,
|
|
922
|
+
look,
|
|
923
|
+
options,
|
|
924
|
+
width,
|
|
925
|
+
height,
|
|
926
|
+
minScale = EFFECT_MIN_SCALE,
|
|
927
|
+
background,
|
|
928
|
+
selected,
|
|
929
|
+
onSelect,
|
|
930
|
+
onProblems,
|
|
931
|
+
className
|
|
932
|
+
}) {
|
|
933
|
+
const pipeline = useSameContent(given);
|
|
934
|
+
const problems = useMemo(() => checkPipeline(pipeline), [pipeline]);
|
|
935
|
+
const onProblemsRef = useLatest(onProblems);
|
|
936
|
+
useEffect(() => {
|
|
937
|
+
onProblemsRef.current?.(problems);
|
|
938
|
+
}, [onProblemsRef, problems]);
|
|
939
|
+
const stageIds = useMemo(() => new Set(pipeline.stages.map((s) => s.id)), [pipeline]);
|
|
940
|
+
const built = useMemo(() => {
|
|
941
|
+
const drawn = (look ?? nodeGraph)(pipeline, options);
|
|
942
|
+
const specs = diagramScene(drawn.data, drawn.scene).map((s) => s.pickable === false || stageIds.has(s.id) ? s : { ...s, pickable: false });
|
|
943
|
+
return { specs, color: drawn.color, exits: drawn.exits };
|
|
944
|
+
}, [pipeline, look, options, stageIds]);
|
|
945
|
+
const portOptions = useRunPortOptions(built.color, pipeline, run);
|
|
946
|
+
const painted = useMemo(() => paintRun(pipeline, built.specs, run, built.exits), [pipeline, built, run]);
|
|
947
|
+
useReleaseReplaced(run);
|
|
948
|
+
return /* @__PURE__ */ jsx(
|
|
949
|
+
DiagramView,
|
|
950
|
+
{
|
|
951
|
+
specs: painted,
|
|
952
|
+
width,
|
|
953
|
+
height,
|
|
954
|
+
minScale,
|
|
955
|
+
ports: "show",
|
|
956
|
+
portOptions,
|
|
957
|
+
anchor: "start",
|
|
958
|
+
...background !== void 0 ? { background } : {},
|
|
959
|
+
...selected !== void 0 ? { selected } : {},
|
|
960
|
+
...onSelect ? { onSelect: (id) => onSelect(id !== null && stageIds.has(id) ? id : null) } : {},
|
|
961
|
+
...className !== void 0 ? { className } : {}
|
|
962
|
+
},
|
|
963
|
+
keyOf(built)
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/effect/groupHandlers.ts
|
|
968
|
+
var parentGroup = (doc, id) => doc.pipeline.groups?.find((g) => g.members.includes(id))?.id;
|
|
969
|
+
function dragEdit(doc, built, reports, boxOf) {
|
|
970
|
+
let next = doc;
|
|
971
|
+
let resized = false;
|
|
972
|
+
let touched = false;
|
|
973
|
+
const carried = /* @__PURE__ */ new Set();
|
|
974
|
+
const reparent = (id, parent) => {
|
|
975
|
+
if (parent === void 0) return;
|
|
976
|
+
if (parent === null || built.groupOf(parent) !== void 0) next = moveToGroup(next, [id], parent);
|
|
977
|
+
};
|
|
978
|
+
const depth = (id) => {
|
|
979
|
+
let n = 0;
|
|
980
|
+
for (let at = parentGroup(doc, id); at !== void 0; at = parentGroup(doc, at)) n++;
|
|
981
|
+
return n;
|
|
982
|
+
};
|
|
983
|
+
for (const edit of [...reports.boxes].sort((a, b) => depth(a.id) - depth(b.id))) {
|
|
984
|
+
const group = built.groupOf(edit.id);
|
|
985
|
+
const was = boxOf(edit.id);
|
|
986
|
+
if (group === void 0 || was === void 0 || carried.has(group)) continue;
|
|
987
|
+
touched = true;
|
|
988
|
+
reparent(group, edit.parent);
|
|
989
|
+
if (edit.box.width === was.width && edit.box.height === was.height) {
|
|
990
|
+
next = moveGroup(next, group, { x: edit.box.x - was.x, y: edit.box.y - was.y });
|
|
991
|
+
const inside = groupContents(doc, group);
|
|
992
|
+
for (const id of [...inside.stages, ...inside.groups]) carried.add(id);
|
|
993
|
+
} else {
|
|
994
|
+
next = setGroupBox(next, group, edit.box);
|
|
995
|
+
resized = true;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
const moves = reports.moves.filter((m) => built.stageOf(m.id) !== void 0 && !carried.has(m.id));
|
|
999
|
+
for (const m of moves) reparent(m.id, m.parent);
|
|
1000
|
+
if (moves.length > 0) {
|
|
1001
|
+
next = moveStages(next, moves.map(({ id, x, y }) => ({ id, x, y })));
|
|
1002
|
+
touched = true;
|
|
1003
|
+
}
|
|
1004
|
+
return touched ? { doc: next, label: resized && moves.length === 0 ? "Resize group" : "Move" } : null;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/effect/editorHandlers.ts
|
|
1008
|
+
function orient(p, a, b) {
|
|
1009
|
+
if (canWire(p, a, b)) return { from: a, to: b };
|
|
1010
|
+
if (canWire(p, b, a)) return { from: b, to: a };
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
1013
|
+
var fed = (p, to, except) => p.wires.some((w) => w.to.stage === to.stage && w.to.port === to.port && (except === void 0 || w.id !== except));
|
|
1014
|
+
function wireFor(p, a, b, except) {
|
|
1015
|
+
const wire = orient(p, a, b);
|
|
1016
|
+
return wire !== null && !fed(p, wire.to, except) ? wire : null;
|
|
1017
|
+
}
|
|
1018
|
+
function pinShown(doc, specs) {
|
|
1019
|
+
const shown = Object.fromEntries([...stageBoxes(doc, specs)].map(([id, b]) => [id, { x: b.x, y: b.y }]));
|
|
1020
|
+
return { ...doc, places: { ...shown, ...doc.places } };
|
|
1021
|
+
}
|
|
1022
|
+
function stageBoxes(doc, specs) {
|
|
1023
|
+
const ids = new Set(doc.pipeline.stages.map((s) => s.id));
|
|
1024
|
+
return new Map(specs.filter((s) => ids.has(s.id)).map((s) => [s.id, s.pose]));
|
|
1025
|
+
}
|
|
1026
|
+
function editHandlers(now, unpick, rename = () => {
|
|
1027
|
+
}) {
|
|
1028
|
+
const commit = (label, edit) => {
|
|
1029
|
+
const { doc, built, onChange } = now();
|
|
1030
|
+
const d = ensureWireIds(doc);
|
|
1031
|
+
const next = edit(d, built);
|
|
1032
|
+
if (next !== null) onChange(next, label);
|
|
1033
|
+
};
|
|
1034
|
+
const drag = { moves: [], boxes: [] };
|
|
1035
|
+
let held = false;
|
|
1036
|
+
const hold = () => {
|
|
1037
|
+
if (held) return;
|
|
1038
|
+
held = true;
|
|
1039
|
+
queueMicrotask(() => {
|
|
1040
|
+
held = false;
|
|
1041
|
+
const reports = { moves: drag.moves.splice(0), boxes: drag.boxes.splice(0) };
|
|
1042
|
+
const { doc, built, specs, onChange } = now();
|
|
1043
|
+
const boxes = new Map(specs.map((s) => [s.id, s.pose]));
|
|
1044
|
+
const out = dragEdit(pinShown(ensureWireIds(doc), specs), built, reports, (id) => boxes.get(id));
|
|
1045
|
+
if (out !== null) onChange(out.doc, out.label);
|
|
1046
|
+
});
|
|
1047
|
+
};
|
|
1048
|
+
const canConnect = (from, to, moving) => {
|
|
1049
|
+
if (isBodyEnd(from) || isBodyEnd(to)) return false;
|
|
1050
|
+
const { doc, built } = now();
|
|
1051
|
+
const except = moving === void 0 ? void 0 : built.wireOf(moving.edge);
|
|
1052
|
+
return wireFor(doc.pipeline, { stage: from.nodeId, port: from.id }, { stage: to.nodeId, port: to.id }, except) !== null;
|
|
1053
|
+
};
|
|
1054
|
+
return {
|
|
1055
|
+
canConnect,
|
|
1056
|
+
onConnect: (e) => commit("Connect", (d) => {
|
|
1057
|
+
if (e.fromPort === void 0 || e.toPort === void 0) return null;
|
|
1058
|
+
const wire = wireFor(d.pipeline, { stage: e.from, port: e.fromPort }, { stage: e.to, port: e.toPort });
|
|
1059
|
+
return wire === null ? null : addWire(d, wire);
|
|
1060
|
+
}),
|
|
1061
|
+
onReconnect: (c) => commit("Reconnect", (d, b) => {
|
|
1062
|
+
const id = b.wireOf(c.edge);
|
|
1063
|
+
const wire = d.pipeline.wires.find((w) => w.id === id);
|
|
1064
|
+
if (id === void 0 || wire === void 0 || c.port === void 0) return null;
|
|
1065
|
+
const moved = { stage: c.node, port: c.port };
|
|
1066
|
+
const [from, to] = c.end === "from" ? [moved, wire.to] : [wire.from, moved];
|
|
1067
|
+
return canWire(d.pipeline, from, to) && !fed(d.pipeline, to, id) ? reconnectWire(d, id, c.end, moved) : null;
|
|
1068
|
+
}),
|
|
1069
|
+
onEditRoute: (c) => commit("Edit route", (d, b) => {
|
|
1070
|
+
const id = b.wireOf(c.edge);
|
|
1071
|
+
const { look, specs } = now();
|
|
1072
|
+
if (id === void 0) return null;
|
|
1073
|
+
if (look === "nodeGraph") return setWireRoute(d, id, c.waypoints);
|
|
1074
|
+
if (c.waypoints.length === 0) {
|
|
1075
|
+
const was = d.lookRoutes?.[look]?.[id];
|
|
1076
|
+
return was === void 0 ? null : setLookRoute(d, look, id, [], was.ends);
|
|
1077
|
+
}
|
|
1078
|
+
const ends = wireEnds(specs, c.edge);
|
|
1079
|
+
return ends === void 0 ? null : setLookRoute(d, look, id, c.waypoints, ends);
|
|
1080
|
+
}),
|
|
1081
|
+
onDelete: (r) => commit("Delete", (d, b) => {
|
|
1082
|
+
unpick();
|
|
1083
|
+
const stages = r.nodes.flatMap((n) => b.stageOf(n) ?? []);
|
|
1084
|
+
const wires = r.edges.flatMap((e) => b.wireOf(e) ?? []);
|
|
1085
|
+
return stages.length === 0 && wires.length === 0 ? null : removeWires(removeStages(d, stages), wires);
|
|
1086
|
+
}),
|
|
1087
|
+
onMove: (moves) => {
|
|
1088
|
+
drag.moves.push(...moves);
|
|
1089
|
+
hold();
|
|
1090
|
+
},
|
|
1091
|
+
onEditContainer: (edit) => {
|
|
1092
|
+
if (edit.kind === "title") {
|
|
1093
|
+
const group = now().built.groupOf(edit.id);
|
|
1094
|
+
if (group !== void 0) rename(group);
|
|
1095
|
+
} else if (edit.kind === "box") {
|
|
1096
|
+
drag.boxes.push(edit);
|
|
1097
|
+
hold();
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
var isPart = (built, id) => built.stageOf(id) !== void 0 || built.groupOf(id) !== void 0;
|
|
1103
|
+
function useEditorPicks(built, given) {
|
|
1104
|
+
const [stage, setStage] = useState(null);
|
|
1105
|
+
const [group, setGroup] = useState(null);
|
|
1106
|
+
const [edge, setEdge] = useState(null);
|
|
1107
|
+
const latest = useLatest$1({ built, given });
|
|
1108
|
+
const pick = useCallback((id) => {
|
|
1109
|
+
const { built: b, given: g } = latest.current;
|
|
1110
|
+
const s = id === null ? void 0 : b.stageOf(id);
|
|
1111
|
+
const k = id === null ? void 0 : b.groupOf(id);
|
|
1112
|
+
setStage(s ?? null);
|
|
1113
|
+
setGroup(k ?? null);
|
|
1114
|
+
setEdge(id !== null && s === void 0 && k === void 0 && b.wireOf(id) !== void 0 ? id : null);
|
|
1115
|
+
g.onSelect?.(s ?? null);
|
|
1116
|
+
g.onSelectGroup?.(k ?? null);
|
|
1117
|
+
}, [latest]);
|
|
1118
|
+
const unpick = useCallback(() => setEdge(null), []);
|
|
1119
|
+
const shownStage = given.selected !== void 0 ? given.selected : stage;
|
|
1120
|
+
const shownGroup = given.selectedGroup !== void 0 ? given.selectedGroup : group;
|
|
1121
|
+
const wire = edge === null || built.wireOf(edge) === void 0 ? null : edge;
|
|
1122
|
+
const controlled = given.selected !== void 0 || given.selectedGroup !== void 0;
|
|
1123
|
+
const live = (id) => id !== null && isPart(built, id) ? id : null;
|
|
1124
|
+
const shown = controlled ? live(shownStage) ?? live(shownGroup) ?? wire : void 0;
|
|
1125
|
+
return { pick, unpick, shown };
|
|
1126
|
+
}
|
|
1127
|
+
var FIELD_RULE = ":where([data-wzl-group-title]) { width: 16ch; font: inherit; font-weight: 700; text-align: center; }";
|
|
1128
|
+
var TOP = { side: "top", align: "center" };
|
|
1129
|
+
var adoptFieldRule = (el) => {
|
|
1130
|
+
if (el === null) return;
|
|
1131
|
+
adoptCssOnce(el, FIELD_RULE);
|
|
1132
|
+
el.focus();
|
|
1133
|
+
el.select();
|
|
1134
|
+
};
|
|
1135
|
+
function useGroupTitle(doc, look, renaming, setRenaming, onChange) {
|
|
1136
|
+
const latest = useLatest$1({ doc, onChange });
|
|
1137
|
+
const group = renaming === null ? void 0 : doc.pipeline.groups?.find((g) => g.id === renaming);
|
|
1138
|
+
const id = group?.id;
|
|
1139
|
+
const was = group?.label ?? "";
|
|
1140
|
+
const over = id === void 0 ? void 0 : look === "nodeGraph" ? titleIdOf(id) : was ? groupLabelIdOf(id) : id;
|
|
1141
|
+
return useMemo(() => {
|
|
1142
|
+
if (id === void 0 || over === void 0) return null;
|
|
1143
|
+
const done = (text) => {
|
|
1144
|
+
setRenaming(null);
|
|
1145
|
+
if (text !== null && text !== was) latest.current.onChange(renameGroup(latest.current.doc, id, text), "Rename group");
|
|
1146
|
+
};
|
|
1147
|
+
const placement = over === id ? TOP : "center";
|
|
1148
|
+
return [{ target: over, placement, content: /* @__PURE__ */ jsx(GroupTitleField, { initial: was, onDone: done }, id) }];
|
|
1149
|
+
}, [id, was, over, setRenaming, latest]);
|
|
1150
|
+
}
|
|
1151
|
+
function GroupTitleField({ initial, onDone }) {
|
|
1152
|
+
const [text, setText] = useState(initial);
|
|
1153
|
+
const [done, setDone] = useState(false);
|
|
1154
|
+
const finish = (value) => {
|
|
1155
|
+
if (done) return;
|
|
1156
|
+
setDone(true);
|
|
1157
|
+
onDone(value);
|
|
1158
|
+
};
|
|
1159
|
+
return /* @__PURE__ */ jsx(
|
|
1160
|
+
"input",
|
|
1161
|
+
{
|
|
1162
|
+
ref: adoptFieldRule,
|
|
1163
|
+
"data-wzl-group-title": "",
|
|
1164
|
+
"aria-label": "Group name",
|
|
1165
|
+
value: text,
|
|
1166
|
+
onChange: (e) => setText(e.target.value),
|
|
1167
|
+
onKeyDown: (e) => {
|
|
1168
|
+
e.stopPropagation();
|
|
1169
|
+
if (e.key === "Enter") finish(text.trim());
|
|
1170
|
+
else if (e.key === "Escape") finish(null);
|
|
1171
|
+
},
|
|
1172
|
+
onBlur: () => finish(text.trim())
|
|
1173
|
+
}
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// src/effect/laneColumns.ts
|
|
1178
|
+
function laneColumns(stages, wires, spans, groups, titles = /* @__PURE__ */ new Map()) {
|
|
1179
|
+
const order = new Map(stages.map((s, i) => [s.id, i]));
|
|
1180
|
+
const nested = nestGroups(groups, (id) => order.has(id));
|
|
1181
|
+
const byId = new Map(nested.map((g) => [g.id, g]));
|
|
1182
|
+
const grouped = new Set(nested.flatMap((g) => g.members));
|
|
1183
|
+
const first = (id) => Math.min(...(byId.get(id)?.members ?? [id]).map((m) => order.get(m)));
|
|
1184
|
+
const itemsOf = (ids, depth) => [...ids].sort((a, b) => first(a) - first(b)).map((id) => {
|
|
1185
|
+
const g = byId.get(id);
|
|
1186
|
+
if (g === void 0) return { stages: [id] };
|
|
1187
|
+
const inside = place(itemsOf(g.direct, depth + 1), wires, spans);
|
|
1188
|
+
const minWidth = titles.has(g.id) ? groupMinWidth(titles.get(g.id), DEFAULT_GROUP_INSET) : 0;
|
|
1189
|
+
const own = { group: g.id, first: 0, last: inside.width - 1, depth, minWidth };
|
|
1190
|
+
return { stages: g.members, inner: { ...inside, blocks: [own, ...inside.blocks] } };
|
|
1191
|
+
});
|
|
1192
|
+
const top = [...stages.map((s) => s.id).filter((id) => !grouped.has(id)), ...nested.filter((g) => g.parent === void 0).map((g) => g.id)];
|
|
1193
|
+
const { column, blocks } = place(itemsOf(top, 0), wires, spans);
|
|
1194
|
+
return { column, blocks };
|
|
1195
|
+
}
|
|
1196
|
+
function place(items, wires, spans) {
|
|
1197
|
+
const owner = /* @__PURE__ */ new Map();
|
|
1198
|
+
items.forEach((item, k) => item.stages.forEach((id) => owner.set(id, k)));
|
|
1199
|
+
const feeds = items.map(() => /* @__PURE__ */ new Set());
|
|
1200
|
+
for (const { wire: { from, to } } of wires) {
|
|
1201
|
+
const [a, b] = [owner.get(from.stage), owner.get(to.stage)];
|
|
1202
|
+
if (a !== void 0 && b !== void 0 && a !== b) feeds[b].add(from.stage);
|
|
1203
|
+
}
|
|
1204
|
+
const column = /* @__PURE__ */ new Map();
|
|
1205
|
+
const blocks = [];
|
|
1206
|
+
const held = [];
|
|
1207
|
+
const whole = /* @__PURE__ */ new Set();
|
|
1208
|
+
const fits = (item, c) => {
|
|
1209
|
+
if (item.inner === void 0) {
|
|
1210
|
+
const span = spans.get(item.stages[0]);
|
|
1211
|
+
return !whole.has(c) && !held[c]?.some(([lo, hi]) => lo <= span[1] && span[0] <= hi);
|
|
1212
|
+
}
|
|
1213
|
+
for (let j = c; j < c + item.inner.width; j++) if (whole.has(j) || (held[j]?.length ?? 0) > 0) return false;
|
|
1214
|
+
return true;
|
|
1215
|
+
};
|
|
1216
|
+
const left = items.map((_, k) => k);
|
|
1217
|
+
while (left.length > 0) {
|
|
1218
|
+
const i = Math.max(0, left.findIndex((k2) => [...feeds[k2]].every((f) => column.has(f))));
|
|
1219
|
+
const k = left.splice(i, 1)[0];
|
|
1220
|
+
const item = items[k];
|
|
1221
|
+
let c = Math.max(0, ...[...feeds[k]].flatMap((f) => column.has(f) ? [column.get(f) + 1] : []));
|
|
1222
|
+
while (!fits(item, c)) c++;
|
|
1223
|
+
if (item.inner === void 0) {
|
|
1224
|
+
(held[c] ??= []).push(spans.get(item.stages[0]));
|
|
1225
|
+
column.set(item.stages[0], c);
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
for (let j = c; j < c + item.inner.width; j++) whole.add(j);
|
|
1229
|
+
for (const [id, at] of item.inner.column) column.set(id, c + at);
|
|
1230
|
+
for (const b of item.inner.blocks) blocks.push({ ...b, first: b.first + c, last: b.last + c });
|
|
1231
|
+
}
|
|
1232
|
+
return { width: Math.max(0, ...column.values()) + (column.size > 0 ? 1 : 0), column, blocks };
|
|
1233
|
+
}
|
|
1234
|
+
function columnLefts(widths, column, blocks, gap, start, extent) {
|
|
1235
|
+
const count = widths.length;
|
|
1236
|
+
const room = blockRoom(widths.length, column, blocks, gap);
|
|
1237
|
+
const leftsNow = (now) => {
|
|
1238
|
+
let x = start;
|
|
1239
|
+
return Array.from(widths, (wd, c) => {
|
|
1240
|
+
x += now[c];
|
|
1241
|
+
const at = x;
|
|
1242
|
+
x += wd ?? 0;
|
|
1243
|
+
return at;
|
|
1244
|
+
});
|
|
1245
|
+
};
|
|
1246
|
+
const byId = new Map(blocks.map((b) => [b.group, b]));
|
|
1247
|
+
const span = (group, now) => {
|
|
1248
|
+
const b = byId.get(group);
|
|
1249
|
+
const lefts2 = leftsNow(now);
|
|
1250
|
+
let [lo, hi] = [Infinity, -Infinity];
|
|
1251
|
+
for (const [id, c] of column) {
|
|
1252
|
+
if (c >= b.first && c <= b.last) [lo, hi] = [Math.min(lo, lefts2[c] + extent(id).lo), Math.max(hi, lefts2[c] + extent(id).hi)];
|
|
1253
|
+
}
|
|
1254
|
+
return { lo, hi };
|
|
1255
|
+
};
|
|
1256
|
+
const inset = { lo: DEFAULT_GROUP_INSET, hi: DEFAULT_GROUP_INSET };
|
|
1257
|
+
widenForMinWidth([...blocks].sort((p, q) => q.depth - p.depth).map((b) => ({
|
|
1258
|
+
id: b.group,
|
|
1259
|
+
minWidth: b.minWidth,
|
|
1260
|
+
inset,
|
|
1261
|
+
children: blocks.filter((k) => k.depth === b.depth + 1 && k.first >= b.first && k.last <= b.last).map((k) => k.group),
|
|
1262
|
+
before: b.first,
|
|
1263
|
+
after: b.last + 1
|
|
1264
|
+
})), room, span);
|
|
1265
|
+
const lefts = leftsNow(room);
|
|
1266
|
+
return { lefts, end: count === 0 ? start : lefts[count - 1] + (widths[count - 1] ?? 0) + room[count] };
|
|
1267
|
+
}
|
|
1268
|
+
function blockRoom(count, column, blocks, gap) {
|
|
1269
|
+
if (blocks.length === 0) return Array.from({ length: count + 1 }, (_, c) => c === 0 || c === count ? 0 : gap);
|
|
1270
|
+
const rows = Array.from({ length: count + 2 }, () => []);
|
|
1271
|
+
for (const [id, c] of column) rows[c + 1].push({ id });
|
|
1272
|
+
const inside = (b) => [...column].filter(([, c]) => c >= b.first && c <= b.last).map(([id]) => id);
|
|
1273
|
+
const groups = blocks.map((b) => {
|
|
1274
|
+
const parent = blocks.find((p) => p.depth === b.depth - 1 && p.first <= b.first && b.last <= p.last);
|
|
1275
|
+
return { id: b.group, members: inside(b), inset: insetOf(DEFAULT_GROUP_INSET), ...parent !== void 0 ? { parent: parent.group } : {} };
|
|
1276
|
+
});
|
|
1277
|
+
const gaps = rankGaps({ groups }, rows, { rank: "x", cross: "y", sign: 1 }, gap);
|
|
1278
|
+
return gaps.map((g, c) => c === 0 || c === count ? g - gap : g);
|
|
1279
|
+
}
|
|
1280
|
+
var upright = (n) => n !== null && Math.abs(n.y) > Math.abs(n.x);
|
|
1281
|
+
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;
|
|
1282
|
+
var TRACK_GAP = SIDE_TRACK_GAP;
|
|
1283
|
+
var CLEAR = 3;
|
|
1284
|
+
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);
|
|
1285
|
+
var laneAt = (grid, y) => Math.max(0, grid.edges.filter((e) => e <= y).length - 1);
|
|
1286
|
+
function corridor(grid, columns, near2) {
|
|
1287
|
+
const ink = columns.flatMap((c) => grid.ink[c] ?? []);
|
|
1288
|
+
const middle = laneAt(grid, near2) + 0.5;
|
|
1289
|
+
const clear2 = grid.edges.flatMap((y, k) => covers(ink, y) ? [] : [k]);
|
|
1290
|
+
return clear2.reduce((best, k) => Math.abs(k - middle) < Math.abs(best - middle) ? k : best, clear2[0] ?? 0);
|
|
1291
|
+
}
|
|
1292
|
+
var between = (a, b) => Array.from({ length: Math.abs(b - a) + 1 }, (_, i) => a + Math.sign(b - a) * i);
|
|
1293
|
+
function laneCourse(grid, from, to) {
|
|
1294
|
+
if (from.column >= to.column) {
|
|
1295
|
+
const columns2 = between(from.column, to.column);
|
|
1296
|
+
const edge = corridor(grid, columns2, from.point.y);
|
|
1297
|
+
return { from, to, back: true, legs: columns2.map((column) => ({ column, at: { edge } })) };
|
|
1298
|
+
}
|
|
1299
|
+
const columns = Array.from({ length: to.column - from.column - 1 }, (_, i) => from.column + 1 + i);
|
|
1300
|
+
const blocked = (c, y, until = y) => covers(grid.ink[c] ?? [], y, until);
|
|
1301
|
+
const first = columns.findIndex((c) => blocked(c, from.point.y, to.point.y));
|
|
1302
|
+
if (first < 0) return void 0;
|
|
1303
|
+
const want = columns.map((_, i) => i < first ? from.point.y : to.point.y);
|
|
1304
|
+
const legs = columns.map((column, i) => ({ column, at: want[i] }));
|
|
1305
|
+
for (let i = 0; i < columns.length; ) {
|
|
1306
|
+
let j = i;
|
|
1307
|
+
while (j < columns.length && blocked(columns[j], want[j])) j++;
|
|
1308
|
+
if (j === i) {
|
|
1309
|
+
i++;
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
const edge = corridor(grid, columns.slice(i, j), want[i]);
|
|
1313
|
+
for (let k = i; k < j; k++) legs[k] = { column: columns[k], at: { edge } };
|
|
1314
|
+
i = j;
|
|
1315
|
+
}
|
|
1316
|
+
return { from, to, back: false, legs };
|
|
1317
|
+
}
|
|
1318
|
+
function assignTracks(courses) {
|
|
1319
|
+
const stretches = courses.map((course) => {
|
|
1320
|
+
const out = [];
|
|
1321
|
+
for (const { column, at } of course?.legs ?? []) {
|
|
1322
|
+
if (typeof at === "number") continue;
|
|
1323
|
+
const last = out[out.length - 1];
|
|
1324
|
+
if (last !== void 0 && last.edge === at.edge && Math.abs(column - (course.back ? last.lo : last.hi)) === 1) {
|
|
1325
|
+
if (course.back) last.lo = column;
|
|
1326
|
+
else last.hi = column;
|
|
1327
|
+
} else out.push({ edge: at.edge, lo: column, hi: column, track: 0 });
|
|
1328
|
+
}
|
|
1329
|
+
return out;
|
|
1330
|
+
});
|
|
1331
|
+
const ends = /* @__PURE__ */ new Map();
|
|
1332
|
+
for (const s of stretches.flat().sort((a, b) => a.lo - b.lo || a.hi - b.hi)) {
|
|
1333
|
+
const taken = ends.get(s.edge) ?? [];
|
|
1334
|
+
const free = taken.findIndex((end) => end + 1 < s.lo);
|
|
1335
|
+
s.track = free < 0 ? taken.length : free;
|
|
1336
|
+
taken[s.track] = s.hi;
|
|
1337
|
+
ends.set(s.edge, taken);
|
|
1338
|
+
}
|
|
1339
|
+
return { stretches, tracks: new Map([...ends].map(([edge, taken]) => [edge, taken.length])) };
|
|
1340
|
+
}
|
|
1341
|
+
function trackY(grid, k, j, n) {
|
|
1342
|
+
const last = grid.edges.length - 1;
|
|
1343
|
+
const y = grid.edges[k];
|
|
1344
|
+
if (k === 0) return y + (j + 0.5) * TRACK_GAP;
|
|
1345
|
+
if (k === last) return y - (j + 0.5) * TRACK_GAP;
|
|
1346
|
+
return y + (j - (n - 1) / 2) * TRACK_GAP;
|
|
1347
|
+
}
|
|
1348
|
+
function straightened(points) {
|
|
1349
|
+
return points.filter((p, i) => {
|
|
1350
|
+
const a = points[i - 1];
|
|
1351
|
+
const b = points[i + 1];
|
|
1352
|
+
return a === void 0 || b === void 0 || !(a.y === p.y && p.y === b.y);
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
function waypointsOf(grid, course, ys) {
|
|
1356
|
+
const { from, to } = course;
|
|
1357
|
+
const edge = (c, side) => grid.columns[c][side];
|
|
1358
|
+
if (course.back) return [{ x: edge(from.column, 1), y: ys[0] }, { x: edge(to.column, 0), y: ys[0] }];
|
|
1359
|
+
const points = [];
|
|
1360
|
+
const out = edge(from.column, 1);
|
|
1361
|
+
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 });
|
|
1362
|
+
const first = ys[0];
|
|
1363
|
+
const leave = facingY(from, first);
|
|
1364
|
+
const rise = Math.abs(first - leave.point.y);
|
|
1365
|
+
if (upright(leave.normal) && leave.point.x + rise < out) points.push({ x: leave.point.x + rise, y: first }, { x: out, y: first });
|
|
1366
|
+
course.legs.forEach(({ column }, i) => points.push({ x: edge(column, 0), y: ys[i] }, { x: edge(column, 1), y: ys[i] }));
|
|
1367
|
+
const into = edge(to.column, 0);
|
|
1368
|
+
const last = ys[ys.length - 1];
|
|
1369
|
+
const arrive = facingY(to, last);
|
|
1370
|
+
const fall = Math.abs(last - arrive.point.y);
|
|
1371
|
+
if (upright(arrive.normal) && arrive.point.x - fall > into) points.push({ x: into, y: last }, { x: arrive.point.x - fall, y: last });
|
|
1372
|
+
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 });
|
|
1373
|
+
return straightened(points);
|
|
1374
|
+
}
|
|
1375
|
+
function routeOf(from, fromPort, to, toPort, router, waypoints) {
|
|
1376
|
+
const dep = (s) => ({ node: { id: s.id, data: s.data }, pose: s.pose });
|
|
1377
|
+
const edge = { data: { diagram: { from: { port: fromPort }, to: { port: toPort }, router, ...waypoints ? { waypoints } : {} } } };
|
|
1378
|
+
return EDGE_DERIVE_PATH(edge, [dep(from), dep(to)]);
|
|
1379
|
+
}
|
|
1380
|
+
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;
|
|
1381
|
+
function clearLabel(path, size, offset, keepOut) {
|
|
1382
|
+
for (const side of [offset, -offset]) {
|
|
1383
|
+
for (const t of [0.5, 0.4, 0.6, 0.3, 0.7, 0.2, 0.8, 0.1, 0.9]) {
|
|
1384
|
+
const near2 = pointAlongPath(path, t)?.point;
|
|
1385
|
+
const center = near2 === void 0 ? null : labelCenter(path, { at: { near: near2 }, offset: side }, size);
|
|
1386
|
+
if (near2 === void 0 || center === null) continue;
|
|
1387
|
+
const box = { x: center.x - size.width / 2, y: center.y - size.height / 2, width: size.width, height: size.height };
|
|
1388
|
+
if (!keepOut.some((k) => overlaps(box, k))) return { near: near2, offset: side, box };
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
return void 0;
|
|
1392
|
+
}
|
|
1393
|
+
var inkOf = (id, specs) => specs.filter((s) => s.id === id || s.parent === id || String(s.id).startsWith(`${id}/mark:`)).map((s) => s.pose);
|
|
1394
|
+
function routeWires(wires, nodes, laid) {
|
|
1395
|
+
const specs = diagramScene({ nodes, edges: wires.map((w) => w.edge) }, {
|
|
1396
|
+
layout: "none",
|
|
1397
|
+
router: laid.router,
|
|
1398
|
+
...laid.measure !== void 0 ? { measure: laid.measure } : {}
|
|
1399
|
+
});
|
|
1400
|
+
const spec = new Map(specs.map((s) => [s.id, s]));
|
|
1401
|
+
const ink = new Map(nodes.map((n) => [n.id, inkOf(n.id, specs)]));
|
|
1402
|
+
const grid = { ...laid.grid, ink: laid.grid.columns.map((_, c) => [...ink].flatMap(([id, b]) => laid.column.get(id) === c ? b : [])) };
|
|
1403
|
+
const end = (stage, port) => {
|
|
1404
|
+
const s = spec.get(stage);
|
|
1405
|
+
const [site, ...sites] = portSitesOf({ id: stage, data: s.data }, s.pose, port);
|
|
1406
|
+
return { column: laid.column.get(stage), point: site?.point ?? { x: 0, y: 0 }, normal: site?.normal ?? null, sites };
|
|
1407
|
+
};
|
|
1408
|
+
const courses = wires.map(({ edge }) => edge.from === edge.to ? void 0 : laneCourse(grid, end(edge.from, edge.fromPort), end(edge.to, edge.toPort)));
|
|
1409
|
+
const { stretches, tracks } = assignTracks(courses);
|
|
1410
|
+
const [top, bottom] = [grid.edges[0] ?? 0, grid.edges[grid.edges.length - 1] ?? 0];
|
|
1411
|
+
const keepOut = [...ink.values()].flat().concat({ x: -1e9, y: top - 1e9, width: 2e9, height: 1e9 }, { x: -1e9, y: bottom, width: 2e9, height: 1e9 });
|
|
1412
|
+
const routes = wires.map(({ edge }, w) => {
|
|
1413
|
+
const course = courses[w];
|
|
1414
|
+
const ys = course?.legs.map(({ column, at }) => {
|
|
1415
|
+
if (typeof at === "number") return at;
|
|
1416
|
+
const s = stretches[w].find((t) => t.edge === at.edge && t.lo <= column && column <= t.hi);
|
|
1417
|
+
return trackY(grid, at.edge, s.track, tracks.get(at.edge));
|
|
1418
|
+
});
|
|
1419
|
+
const waypoints = course === void 0 ? void 0 : waypointsOf(grid, course, ys);
|
|
1420
|
+
return { waypoints, path: routeOf(spec.get(edge.from), edge.fromPort, spec.get(edge.to), edge.toPort, laid.router, waypoints) };
|
|
1421
|
+
});
|
|
1422
|
+
const segments = routes.map(({ path }) => {
|
|
1423
|
+
const c = path?.kind === "polygon" ? path.coords : [];
|
|
1424
|
+
return Array.from({ length: Math.max(0, c.length / 2 - 1) }, (_, i) => {
|
|
1425
|
+
const [x0, y0, x1, y1] = [c[2 * i], c[2 * i + 1], c[2 * i + 2], c[2 * i + 3]];
|
|
1426
|
+
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 };
|
|
1427
|
+
});
|
|
1428
|
+
});
|
|
1429
|
+
const edges = wires.map(({ edge, style }, w) => {
|
|
1430
|
+
const { waypoints, path } = routes[w];
|
|
1431
|
+
const routed = waypoints === void 0 ? edge : { ...edge, routed: waypoints };
|
|
1432
|
+
const label = spec.get(`${edgeIdOf(edge)}/label`)?.pose;
|
|
1433
|
+
const others = keepOut.concat(segments.flatMap((boxes, k) => k === w ? [] : boxes));
|
|
1434
|
+
const offset = LABEL_PLACEMENT.offset ?? 0;
|
|
1435
|
+
const clear2 = path === null || label === void 0 ? void 0 : clearLabel(path, label, offset, others) ?? clearLabel(path, label, offset, keepOut);
|
|
1436
|
+
if (clear2 === void 0) return { edge: routed, style };
|
|
1437
|
+
keepOut.push(clear2.box);
|
|
1438
|
+
return { edge: { ...routed, labelPlacement: { at: { near: clear2.near }, offset: clear2.offset } }, style };
|
|
1439
|
+
});
|
|
1440
|
+
return { edges, tracks };
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// src/effect/lanes.ts
|
|
1444
|
+
var LANE_INSET = 6;
|
|
1445
|
+
var MARGIN = 16;
|
|
1446
|
+
var laneId = (type) => `~lane:${type}`;
|
|
1447
|
+
var laneTypeOf = (id) => id.startsWith("~lane:") ? id.slice("~lane:".length) : void 0;
|
|
1448
|
+
function laneTypes(stages, first = []) {
|
|
1449
|
+
const used = [...new Set(stages.flatMap((s) => [...s.inputs ?? [], ...s.outputs ?? []].map((p) => p.type)))];
|
|
1450
|
+
const lead = [...new Set(first)].filter((t) => used.includes(t));
|
|
1451
|
+
return [...lead, ...used.filter((t) => !lead.includes(t))];
|
|
1452
|
+
}
|
|
1453
|
+
function spanOf(stage, lane) {
|
|
1454
|
+
const at = [...stage.inputs ?? [], ...stage.outputs ?? []].map((p) => lane.get(p.type));
|
|
1455
|
+
return at.length === 0 ? [0, 0] : [Math.min(...at), Math.max(...at)];
|
|
1456
|
+
}
|
|
1457
|
+
function laneHeights(count, least, needs, insets) {
|
|
1458
|
+
const heights = Array.from({ length: count }, () => least);
|
|
1459
|
+
for (const { span: [lo, hi], height } of [...needs].sort((a, b) => a.span[1] - a.span[0] - (b.span[1] - b.span[0]))) {
|
|
1460
|
+
const short = height + insets[lo] + insets[hi + 1] - heights.slice(lo, hi + 1).reduce((a, b) => a + b, 0);
|
|
1461
|
+
if (short > 0) for (let i = lo; i <= hi; i++) heights[i] += short / (hi - lo + 1);
|
|
1462
|
+
}
|
|
1463
|
+
return heights;
|
|
1464
|
+
}
|
|
1465
|
+
function lanePorts(stage, box, lane, band) {
|
|
1466
|
+
const side = (ports, u) => ports.map((p) => {
|
|
1467
|
+
const same = ports.filter((q) => q.type === p.type);
|
|
1468
|
+
const [top, bottom] = band(lane.get(p.type));
|
|
1469
|
+
const a = Math.max(top, box.y);
|
|
1470
|
+
const b = Math.min(bottom, box.y + box.height);
|
|
1471
|
+
const y = a + (b - a) * (same.indexOf(p) + 1) / (same.length + 1);
|
|
1472
|
+
return { id: p.id, type: p.type, at: { u, v: (y - box.y) / box.height }, normal: { x: u === 0 ? -1 : 1, y: 0 } };
|
|
1473
|
+
});
|
|
1474
|
+
return [...side(stage.inputs ?? [], 0), ...side(stage.outputs ?? [], 1)];
|
|
1475
|
+
}
|
|
1476
|
+
function controlSideOf(stage, pipeline, lane) {
|
|
1477
|
+
const ports = [...stage.inputs ?? [], ...stage.outputs ?? []];
|
|
1478
|
+
const mean = (ps) => ps.reduce((a, p) => a + lane.get(p.type), 0) / ps.length;
|
|
1479
|
+
const control = ports.filter((p) => isControl(pipeline, p.type));
|
|
1480
|
+
const signal = ports.filter((p) => !isControl(pipeline, p.type));
|
|
1481
|
+
return control.length > 0 && signal.length > 0 && mean(control) > mean(signal) ? "bottom" : "top";
|
|
1482
|
+
}
|
|
1483
|
+
function sizesOf(nodes, groups, measure) {
|
|
1484
|
+
const specs = diagramScene({ nodes, edges: [], groups }, { layout: "none", ...measure !== void 0 ? { measure } : {} });
|
|
1485
|
+
const pose = new Map(specs.map((s) => [s.id, s.pose]));
|
|
1486
|
+
const titles = new Map(groups.flatMap((g) => {
|
|
1487
|
+
const title2 = pose.get(groupLabelIdOf(g.id));
|
|
1488
|
+
return title2 === void 0 ? [] : [[g.id, title2.width]];
|
|
1489
|
+
}));
|
|
1490
|
+
const size = new Map(nodes.map((n) => {
|
|
1491
|
+
const box = pose.get(n.id);
|
|
1492
|
+
const ink = inkOf(n.id, specs);
|
|
1493
|
+
const over = {
|
|
1494
|
+
left: box.x - Math.min(...ink.map((b) => b.x)),
|
|
1495
|
+
top: box.y - Math.min(...ink.map((b) => b.y)),
|
|
1496
|
+
right: Math.max(...ink.map((b) => b.x + b.width)) - box.x - box.width,
|
|
1497
|
+
bottom: Math.max(...ink.map((b) => b.y + b.height)) - box.y - box.height
|
|
1498
|
+
};
|
|
1499
|
+
return [n.id, { box, over }];
|
|
1500
|
+
}));
|
|
1501
|
+
return { size, titles };
|
|
1502
|
+
}
|
|
1503
|
+
function laneNode(type, box, color) {
|
|
1504
|
+
return {
|
|
1505
|
+
id: laneId(type),
|
|
1506
|
+
lines: [],
|
|
1507
|
+
ports: [],
|
|
1508
|
+
at: { x: box.x, y: box.y },
|
|
1509
|
+
width: box.width,
|
|
1510
|
+
height: box.height,
|
|
1511
|
+
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 } }]
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
function insetsFor(edges, tracks) {
|
|
1515
|
+
return Array.from({ length: edges }, (_, k) => {
|
|
1516
|
+
const n = tracks.get(k) ?? 0;
|
|
1517
|
+
return LANE_INSET + (k === 0 || k === edges - 1 ? n * TRACK_GAP : Math.max(0, n - 1) * TRACK_GAP / 2);
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
function place2(plan, insets) {
|
|
1521
|
+
const { stages, size, spans, column } = plan;
|
|
1522
|
+
const inkHeight = (id) => size.get(id).box.height + size.get(id).over.top + size.get(id).over.bottom;
|
|
1523
|
+
const heights = laneHeights(plan.rows, plan.least, stages.map((s) => ({ span: spans.get(s.id), height: inkHeight(s.id) })), insets);
|
|
1524
|
+
const tops = heights.map((_, i) => heights.slice(0, i).reduce((a, b) => a + b, 0));
|
|
1525
|
+
const band = (i) => [tops[i], tops[i] + heights[i]];
|
|
1526
|
+
const stood = new Map(stages.map((stage) => {
|
|
1527
|
+
const { box: natural, over } = size.get(stage.id);
|
|
1528
|
+
const [lo, hi] = spans.get(stage.id);
|
|
1529
|
+
const top = band(lo)[0] + insets[lo];
|
|
1530
|
+
const span = band(hi)[1] - insets[hi + 1] - top;
|
|
1531
|
+
const kind = kindOf(stage);
|
|
1532
|
+
const height = kind === "decision" ? natural.height : span - over.top - over.bottom;
|
|
1533
|
+
const width = kind === "source" || kind === "sink" ? natural.width + Math.max(0, height - natural.height) : natural.width;
|
|
1534
|
+
return [stage.id, { y: top + (span - height - over.top - over.bottom) / 2 + over.top, height, width, over }];
|
|
1535
|
+
}));
|
|
1536
|
+
const inkWidth = (id) => stood.get(id).width + stood.get(id).over.left + stood.get(id).over.right;
|
|
1537
|
+
const widths = [];
|
|
1538
|
+
for (const s of stages) widths[column.get(s.id)] = Math.max(widths[column.get(s.id)] ?? 0, inkWidth(s.id));
|
|
1539
|
+
const offset = (id) => (widths[column.get(id)] - inkWidth(id)) / 2 + stood.get(id).over.left;
|
|
1540
|
+
const extent = (id) => ({ lo: offset(id), hi: offset(id) + stood.get(id).width });
|
|
1541
|
+
const { lefts, end } = columnLefts(widths, column, plan.blocks, plan.gap, plan.start, extent);
|
|
1542
|
+
const nodes = stages.map((stage, k) => {
|
|
1543
|
+
const c = column.get(stage.id);
|
|
1544
|
+
const { y, height, width } = stood.get(stage.id);
|
|
1545
|
+
const box = { x: lefts[c] + offset(stage.id), y, width, height };
|
|
1546
|
+
return {
|
|
1547
|
+
...plan.drawn[k],
|
|
1548
|
+
at: { x: box.x, y: box.y },
|
|
1549
|
+
width: box.width,
|
|
1550
|
+
height: box.height,
|
|
1551
|
+
...kindOf(stage) === "decision" ? {} : { ports: lanePorts(stage, box, plan.lane, band) }
|
|
1552
|
+
};
|
|
1553
|
+
});
|
|
1554
|
+
const columns = Array.from(widths, (wd, c) => [lefts[c], lefts[c] + (wd ?? 0)]);
|
|
1555
|
+
const edges = [...tops, tops.length === 0 ? 0 : tops[tops.length - 1] + heights[heights.length - 1]];
|
|
1556
|
+
return { nodes, tops, heights, columns, edges, end };
|
|
1557
|
+
}
|
|
1558
|
+
var lanes = (pipeline, options = {}) => {
|
|
1559
|
+
const theme = options.theme ?? LOOK_THEME;
|
|
1560
|
+
const color = typeColors(pipeline, options.colors, theme.series);
|
|
1561
|
+
const stages = uniqueStages(pipeline);
|
|
1562
|
+
const types = laneTypes(stages, options.lanes);
|
|
1563
|
+
const rows = types.length > 0 ? types : [""];
|
|
1564
|
+
const lane = new Map(types.map((t, i) => [t, i]));
|
|
1565
|
+
const spans = new Map(stages.map((s) => [s.id, spanOf(s, lane)]));
|
|
1566
|
+
const drawn = stages.map((stage) => kindOf(stage) === "decision" ? { id: stage.id, ...branchNode(stage, pipeline, options.branch, { controlSide: controlSideOf(stage, pipeline, lane), style: stageStyle(stage, theme) }) } : boxNode(stage, { showParams: options.showParams !== false, rowPorts: false, ports: [], run: options }));
|
|
1567
|
+
const labels = rows.map((t) => ({ id: laneId(t), lines: [t] }));
|
|
1568
|
+
const groups = uniqueGroups(pipeline);
|
|
1569
|
+
const { size, titles } = sizesOf([...drawn, ...labels], groups, options.measure);
|
|
1570
|
+
const { column, blocks } = laneColumns(stages, drawableWires(pipeline), spans, groups, titles);
|
|
1571
|
+
const plan = {
|
|
1572
|
+
stages,
|
|
1573
|
+
drawn,
|
|
1574
|
+
size,
|
|
1575
|
+
spans,
|
|
1576
|
+
column,
|
|
1577
|
+
blocks,
|
|
1578
|
+
lane,
|
|
1579
|
+
rows: rows.length,
|
|
1580
|
+
least: Math.max(options.laneHeight ?? 48, ...labels.map((l) => size.get(l.id).box.height)),
|
|
1581
|
+
gap: options.rankGap ?? 40,
|
|
1582
|
+
start: 2 * MARGIN + Math.max(0, ...labels.map((l) => size.get(l.id).box.width))
|
|
1583
|
+
};
|
|
1584
|
+
const router = options.router ?? "bezier";
|
|
1585
|
+
const wires = wireEdges(pipeline, color);
|
|
1586
|
+
let insets = insetsFor(rows.length + 1, /* @__PURE__ */ new Map());
|
|
1587
|
+
let laid = place2(plan, insets);
|
|
1588
|
+
let routed = routeWires(wires, laid.nodes, { grid: laid, column, router, measure: options.measure });
|
|
1589
|
+
for (let round = 0; round < 4; round++) {
|
|
1590
|
+
const need2 = insetsFor(rows.length + 1, routed.tracks);
|
|
1591
|
+
if (need2.every((n, k) => n <= insets[k])) break;
|
|
1592
|
+
insets = need2.map((n, k) => Math.max(n, insets[k]));
|
|
1593
|
+
laid = place2(plan, insets);
|
|
1594
|
+
routed = routeWires(wires, laid.nodes, { grid: laid, column, router, measure: options.measure });
|
|
1595
|
+
}
|
|
1596
|
+
const { columns, tops, heights } = laid;
|
|
1597
|
+
const right = columns.length === 0 ? plan.start : laid.end + MARGIN;
|
|
1598
|
+
const laneNodes = rows.map((t, i) => laneNode(t, { x: 0, y: tops[i], width: right, height: heights[i] }, color));
|
|
1599
|
+
const edges = routed.edges;
|
|
1600
|
+
const styles = sceneStyles(stages, edges, theme);
|
|
1601
|
+
const bodies = theme.lane.bodies;
|
|
1602
|
+
const laneStyle = new Map(rows.map((t, i) => [laneId(t), bodies[i % bodies.length]]));
|
|
1603
|
+
return {
|
|
1604
|
+
data: { nodes: [...laneNodes, ...laid.nodes], edges: edges.map((e) => e.edge), groups },
|
|
1605
|
+
scene: {
|
|
1606
|
+
layout: "none",
|
|
1607
|
+
router,
|
|
1608
|
+
...options.measure !== void 0 ? { measure: options.measure } : {},
|
|
1609
|
+
...styles,
|
|
1610
|
+
nodeStyle: (n) => laneStyle.get(n.id) ?? styles.nodeStyle(n)
|
|
1611
|
+
},
|
|
1612
|
+
color,
|
|
1613
|
+
order: { axis: "x", items: mainChain(pipeline).map((id) => ({ id })) },
|
|
1614
|
+
...types.length > 0 ? {
|
|
1615
|
+
laneOrder: {
|
|
1616
|
+
axis: "y",
|
|
1617
|
+
items: types.map((t, i) => ({ id: laneId(t), grip: { x: 0, y: tops[i], width: plan.start, height: heights[i] } }))
|
|
1618
|
+
}
|
|
1619
|
+
} : {}
|
|
1620
|
+
};
|
|
1621
|
+
};
|
|
1622
|
+
|
|
1623
|
+
// src/effect/rack.ts
|
|
1624
|
+
var SKIP_OUT = "~skip-out";
|
|
1625
|
+
var SKIP_IN = "~skip-in";
|
|
1626
|
+
var SIDE_PORT_GAP = 24;
|
|
1627
|
+
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 } }));
|
|
1628
|
+
function roomFor(ports) {
|
|
1629
|
+
const count = (v) => ports.filter((p) => p.at.v === v && p.at.u > 0 && p.at.u < 1).length;
|
|
1630
|
+
return (Math.max(count(0), count(1)) + 1) * SIDE_PORT_GAP;
|
|
1631
|
+
}
|
|
1632
|
+
function rackWires(pipeline, skips) {
|
|
1633
|
+
const eaten = new Set(skips.flatMap((s) => [s.into.index, s.through.index, s.skip.index]));
|
|
1634
|
+
const wires = drawableWires(pipeline).filter((d) => !eaten.has(d.index)).map((d) => {
|
|
1635
|
+
const label = exitLabel(d);
|
|
1636
|
+
return {
|
|
1637
|
+
...d.wire.id !== void 0 ? { id: d.wire.id } : {},
|
|
1638
|
+
from: d.wire.from,
|
|
1639
|
+
to: d.wire.to,
|
|
1640
|
+
type: d.type,
|
|
1641
|
+
problem: d.problem,
|
|
1642
|
+
...label !== void 0 ? { label } : {}
|
|
1643
|
+
};
|
|
1644
|
+
});
|
|
1645
|
+
for (const s of skips) {
|
|
1646
|
+
wires.push({
|
|
1647
|
+
from: s.into.wire.from,
|
|
1648
|
+
to: s.through.wire.to,
|
|
1649
|
+
type: s.into.type,
|
|
1650
|
+
problem: s.into.problem || s.through.problem,
|
|
1651
|
+
exit: { stage: s.decision.id, port: s.through.wire.from.port }
|
|
1652
|
+
});
|
|
1653
|
+
wires.push({
|
|
1654
|
+
from: { stage: s.into.wire.from.stage, port: SKIP_OUT },
|
|
1655
|
+
to: { stage: s.skip.wire.to.stage, port: SKIP_IN },
|
|
1656
|
+
type: s.into.type,
|
|
1657
|
+
problem: s.into.problem || s.skip.problem,
|
|
1658
|
+
label: `${s.decision.title}: ${exitLabel(s.skip)}`,
|
|
1659
|
+
arc: true,
|
|
1660
|
+
exit: { stage: s.decision.id, port: s.skip.wire.from.port }
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
return wires;
|
|
1664
|
+
}
|
|
1665
|
+
function lineOf(pipeline, ordered, chain) {
|
|
1666
|
+
const { mainIn, mainOut } = chainLinks(pipeline, ordered);
|
|
1667
|
+
return { at: new Map(chain.map((id, i) => [id, i])), mainIn, mainOut };
|
|
1668
|
+
}
|
|
1669
|
+
function portsOf2(stage, line, wires, near2, far) {
|
|
1670
|
+
if (!line.at.has(stage.id)) return [...spreadAt(stage.outputs ?? [], far), ...spreadAt(stage.inputs ?? [], near2)];
|
|
1671
|
+
const typed = (p, anchor) => ({ id: p.id, type: p.type, at: anchor });
|
|
1672
|
+
const out = [];
|
|
1673
|
+
const side = [];
|
|
1674
|
+
for (const p of stage.inputs ?? []) {
|
|
1675
|
+
if (p.id === line.mainIn.get(stage.id)) out.push(typed(p, { u: 0, v: 0.5 }));
|
|
1676
|
+
else side.push(p);
|
|
1677
|
+
}
|
|
1678
|
+
for (const p of stage.outputs ?? []) {
|
|
1679
|
+
if (p.id === line.mainOut.get(stage.id)) out.push(typed(p, { u: 1, v: 0.5 }));
|
|
1680
|
+
else side.push(p);
|
|
1681
|
+
}
|
|
1682
|
+
out.push(...spreadAt(side, near2));
|
|
1683
|
+
const type = portOn(stage, line.mainOut.get(stage.id) ?? line.mainIn.get(stage.id) ?? "")?.type;
|
|
1684
|
+
const arc = (id, u) => ({ id, at: { u, v: far }, ...type !== void 0 ? { type } : {} });
|
|
1685
|
+
if (wires.some((w) => w.arc && w.from.stage === stage.id)) out.push(arc(SKIP_OUT, 0.75));
|
|
1686
|
+
if (wires.some((w) => w.arc && w.to.stage === stage.id)) out.push(arc(SKIP_IN, 0.25));
|
|
1687
|
+
return out;
|
|
1688
|
+
}
|
|
1689
|
+
var rack = (pipeline, options = {}) => {
|
|
1690
|
+
const theme = options.theme ?? LOOK_THEME;
|
|
1691
|
+
const color = typeColors(pipeline, options.colors, theme.series);
|
|
1692
|
+
const below = options.side !== "above";
|
|
1693
|
+
const near2 = below ? 1 : 0;
|
|
1694
|
+
const far = below ? 0 : 1;
|
|
1695
|
+
const stages = uniqueStages(pipeline);
|
|
1696
|
+
const given = options.chain?.filter((id) => stages.some((s) => s.id === id));
|
|
1697
|
+
const ordered = given !== void 0 && given.length > 0 ? given : mainChain(pipeline);
|
|
1698
|
+
const skips = options.collapseSkips === false ? [] : skipDecisions(pipeline, ordered);
|
|
1699
|
+
const gone = new Set(skips.map((s) => s.decision.id));
|
|
1700
|
+
const chain = ordered.filter((id) => !gone.has(id));
|
|
1701
|
+
const wires = rackWires(pipeline, skips);
|
|
1702
|
+
const line = lineOf(pipeline, ordered, chain);
|
|
1703
|
+
const kept = stages.filter((s) => !gone.has(s.id));
|
|
1704
|
+
const controlSide = below ? "bottom" : "top";
|
|
1705
|
+
const nodes = kept.map((stage) => {
|
|
1706
|
+
if (kindOf(stage) === "decision") return { id: stage.id, ...branchNode(stage, pipeline, options.branch, { controlSide, style: stageStyle(stage, theme) }) };
|
|
1707
|
+
const ports = portsOf2(stage, line, wires, near2, far);
|
|
1708
|
+
const node = boxNode(stage, { showParams: options.showParams !== false, rowPorts: false, ports, run: options });
|
|
1709
|
+
const room = roomFor(ports);
|
|
1710
|
+
return room > 2 * SIDE_PORT_GAP ? { ...node, width: room } : node;
|
|
1711
|
+
});
|
|
1712
|
+
const byId = new Map(kept.map((s) => [s.id, s]));
|
|
1713
|
+
const edges = wires.map((wire) => {
|
|
1714
|
+
const to = byId.get(wire.to.stage);
|
|
1715
|
+
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);
|
|
1716
|
+
const label = wire.label ?? (sideInput ? portOn(to, wire.to.port)?.label ?? wire.to.port : void 0);
|
|
1717
|
+
return {
|
|
1718
|
+
edge: wireEdge(wire.from, wire.to, label, wire.id),
|
|
1719
|
+
style: wireStyle(color(wire.type), wire.arc === true || isControl(pipeline, wire.type), wire.problem)
|
|
1720
|
+
};
|
|
1721
|
+
});
|
|
1722
|
+
return {
|
|
1723
|
+
data: {
|
|
1724
|
+
nodes,
|
|
1725
|
+
edges: edges.map((e) => e.edge),
|
|
1726
|
+
groups: uniqueGroups(pipeline).map((g) => ({ ...g, members: g.members.filter((m) => !gone.has(m)) }))
|
|
1727
|
+
},
|
|
1728
|
+
scene: {
|
|
1729
|
+
layout: "chain",
|
|
1730
|
+
layoutOptions: {
|
|
1731
|
+
direction: "right",
|
|
1732
|
+
chain,
|
|
1733
|
+
chainSide: below ? "after" : "before",
|
|
1734
|
+
...options.nodeGap !== void 0 ? { nodeGap: options.nodeGap } : {},
|
|
1735
|
+
...options.rankGap !== void 0 ? { rankGap: options.rankGap } : {}
|
|
1736
|
+
},
|
|
1737
|
+
router: "bezier",
|
|
1738
|
+
...sceneStyles(kept, edges, theme)
|
|
1739
|
+
},
|
|
1740
|
+
color,
|
|
1741
|
+
order: { axis: "x", items: chain.map((id) => ({ id })) },
|
|
1742
|
+
exits: new Map(wires.flatMap((wire, i) => wire.exit === void 0 ? [] : [[edgeIdOf(edges[i].edge), wire.exit]]))
|
|
1743
|
+
};
|
|
1744
|
+
};
|
|
1745
|
+
|
|
1746
|
+
// src/effect/editorMapping.ts
|
|
1747
|
+
var endsKey = (from, to) => JSON.stringify([from.stage, from.port, to.stage, to.port]);
|
|
1748
|
+
var edgeEnds = (e) => endsKey({ stage: e.from, port: e.fromPort ?? "" }, { stage: e.to, port: e.toPort ?? "" });
|
|
1749
|
+
function editorScene(doc, look = "nodeGraph", options) {
|
|
1750
|
+
const drawn = drawLook(doc, look, options);
|
|
1751
|
+
const stages = new Set(doc.pipeline.stages.map((s) => s.id));
|
|
1752
|
+
const groups = new Set((doc.pipeline.groups ?? []).map((g) => g.id));
|
|
1753
|
+
const ids = ensureWireIds(doc).pipeline.wires.map((w) => w.id);
|
|
1754
|
+
const byEnds = /* @__PURE__ */ new Map();
|
|
1755
|
+
for (const d of drawableWires(doc.pipeline)) {
|
|
1756
|
+
const key = endsKey(d.wire.from, d.wire.to);
|
|
1757
|
+
if (!byEnds.has(key)) byEnds.set(key, ids[d.index]);
|
|
1758
|
+
}
|
|
1759
|
+
const wires = new Map(drawn.data.edges.flatMap((e) => {
|
|
1760
|
+
const id = byEnds.get(edgeEnds(e));
|
|
1761
|
+
return id === void 0 ? [] : [[edgeIdOf(e), id]];
|
|
1762
|
+
}));
|
|
1763
|
+
const routes = look === "nodeGraph" ? doc.routes : void 0;
|
|
1764
|
+
const edges = routes === void 0 ? drawn.data.edges : drawn.data.edges.map((e) => {
|
|
1765
|
+
const id = wires.get(edgeIdOf(e));
|
|
1766
|
+
const route = id === void 0 ? void 0 : routes[id];
|
|
1767
|
+
return route === void 0 ? e : { ...e, waypoints: route };
|
|
1768
|
+
});
|
|
1769
|
+
return {
|
|
1770
|
+
...drawn,
|
|
1771
|
+
data: { ...drawn.data, edges },
|
|
1772
|
+
stageOf: (node) => stages.has(node) ? node : void 0,
|
|
1773
|
+
groupOf: (node) => groups.has(node) && !stages.has(node) ? node : void 0,
|
|
1774
|
+
wireOf: (edge) => wires.get(edge)
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
function drawLook(doc, look, options) {
|
|
1778
|
+
if (look === "rack") return rack(doc.pipeline, options);
|
|
1779
|
+
if (look === "lanes") {
|
|
1780
|
+
const opts = options;
|
|
1781
|
+
const order = doc.laneOrder ?? opts?.lanes;
|
|
1782
|
+
return lanes(doc.pipeline, { ...opts, ...order !== void 0 ? { lanes: order } : {} });
|
|
1783
|
+
}
|
|
1784
|
+
const drawn = nodeGraph(doc.pipeline, options);
|
|
1785
|
+
const nodes = drawn.data.nodes.map((n) => {
|
|
1786
|
+
const at = doc.places[n.id];
|
|
1787
|
+
return at === void 0 ? n : { ...n, at: { x: at.x, y: at.y }, pinned: true };
|
|
1788
|
+
});
|
|
1789
|
+
const { groups = [], ...rest } = drawn.data;
|
|
1790
|
+
const containers = groups.map((g) => {
|
|
1791
|
+
const box = doc.groupBoxes?.[g.id];
|
|
1792
|
+
return {
|
|
1793
|
+
id: g.id,
|
|
1794
|
+
role: "container",
|
|
1795
|
+
label: g.label ?? "",
|
|
1796
|
+
members: g.members,
|
|
1797
|
+
...box !== void 0 ? { box: { ...box }, pinned: true } : {}
|
|
1798
|
+
};
|
|
1799
|
+
});
|
|
1800
|
+
return {
|
|
1801
|
+
...drawn,
|
|
1802
|
+
data: {
|
|
1803
|
+
...rest,
|
|
1804
|
+
nodes,
|
|
1805
|
+
...containers.length > 0 ? { containers } : {},
|
|
1806
|
+
...doc.pages !== void 0 ? { pages: doc.pages } : {}
|
|
1807
|
+
}
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// src/effect/groupOrder.ts
|
|
1812
|
+
function groupRuns(pipeline, chain) {
|
|
1813
|
+
const at = new Map(chain.map((id, i) => [id, i]));
|
|
1814
|
+
const doc = { pipeline};
|
|
1815
|
+
const runs = /* @__PURE__ */ new Map();
|
|
1816
|
+
for (const g of pipeline.groups ?? []) {
|
|
1817
|
+
const on = [...groupContents(doc, g.id).stages].flatMap((s) => at.get(s) ?? []);
|
|
1818
|
+
if (on.length === 0) continue;
|
|
1819
|
+
const first = Math.min(...on);
|
|
1820
|
+
const last = Math.max(...on);
|
|
1821
|
+
runs.set(g.id, last - first + 1 === on.length ? { first, last } : null);
|
|
1822
|
+
}
|
|
1823
|
+
return runs;
|
|
1824
|
+
}
|
|
1825
|
+
var holds = (b, p) => p.x >= b.x && p.x <= b.x + b.width && p.y >= b.y && p.y <= b.y + b.height;
|
|
1826
|
+
function groupGripOf(doc, group, boxOf) {
|
|
1827
|
+
const box = boxOf(group);
|
|
1828
|
+
const members = doc.pipeline.groups?.find((g) => g.id === group)?.members ?? [];
|
|
1829
|
+
const tops = members.flatMap((m) => boxOf(m)?.y ?? []);
|
|
1830
|
+
if (box === void 0 || tops.length === 0) return void 0;
|
|
1831
|
+
const height = Math.min(...tops) - box.y;
|
|
1832
|
+
return height > 0 ? { x: box.x, y: box.y, width: box.width, height } : void 0;
|
|
1833
|
+
}
|
|
1834
|
+
function unitStages(doc, unit) {
|
|
1835
|
+
return doc.pipeline.groups?.some((g) => g.id === unit) ? groupContents(doc, unit).stages : /* @__PURE__ */ new Set([unit]);
|
|
1836
|
+
}
|
|
1837
|
+
function parentsOf(doc) {
|
|
1838
|
+
const parent = /* @__PURE__ */ new Map();
|
|
1839
|
+
for (const g of doc.pipeline.groups ?? []) for (const m of g.members) parent.set(m, g.id);
|
|
1840
|
+
return parent;
|
|
1841
|
+
}
|
|
1842
|
+
function groupAt(doc, boxOf, at, among = () => true) {
|
|
1843
|
+
const parent = parentsOf(doc);
|
|
1844
|
+
const depth = (id) => parent.has(id) ? 1 + depth(parent.get(id)) : 0;
|
|
1845
|
+
const held = (doc.pipeline.groups ?? []).filter((g) => {
|
|
1846
|
+
const grip = among(g.id) ? groupGripOf(doc, g.id, boxOf) : void 0;
|
|
1847
|
+
return grip !== void 0 && holds(grip, at);
|
|
1848
|
+
});
|
|
1849
|
+
return held.sort((a, b) => depth(b.id) - depth(a.id))[0]?.id;
|
|
1850
|
+
}
|
|
1851
|
+
function groupSlotsAt(source, at) {
|
|
1852
|
+
const { doc, order, boxOf } = source;
|
|
1853
|
+
const groups = doc.pipeline.groups ?? [];
|
|
1854
|
+
if (order === void 0 || groups.length === 0) return null;
|
|
1855
|
+
const parent = parentsOf(doc);
|
|
1856
|
+
const runs = groupRuns(doc.pipeline, mainChain(doc.pipeline));
|
|
1857
|
+
const held = groupAt(doc, boxOf, at, (g) => runs.get(g) != null);
|
|
1858
|
+
if (held === void 0) return null;
|
|
1859
|
+
const up = parent.get(held);
|
|
1860
|
+
const siblings = up !== void 0 ? groups.find((g) => g.id === up).members : [...groups, ...doc.pipeline.stages].flatMap((u) => parent.has(u.id) ? [] : [u.id]);
|
|
1861
|
+
const drawnAt = new Map(order.items.map((i, k) => [i.id, k]));
|
|
1862
|
+
const placed = siblings.flatMap((id) => {
|
|
1863
|
+
const drawn = [...unitStages(doc, id)].flatMap((s) => drawnAt.get(s) ?? []);
|
|
1864
|
+
const bounds = boxOf(id);
|
|
1865
|
+
return drawn.length === 0 || bounds === void 0 ? [] : [{ id, bounds, first: Math.min(...drawn) }];
|
|
1866
|
+
}).sort((a, b) => a.first - b.first);
|
|
1867
|
+
const isGroup = new Set(groups.map((g) => g.id));
|
|
1868
|
+
const items = placed.map(({ id, bounds }) => {
|
|
1869
|
+
if (!isGroup.has(id)) return { id, bounds, grip: null };
|
|
1870
|
+
const inside = groupContents(doc, id);
|
|
1871
|
+
const carries = [...inside.groups, ...inside.stages, ...[...inside.groups].map(groupLabelIdOf)];
|
|
1872
|
+
return { id, bounds, grip: groupGripOf(doc, id, boxOf) ?? null, carries };
|
|
1873
|
+
});
|
|
1874
|
+
return items.length < 2 ? null : sequenceSlots(order.axis, items);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// src/effect/editorReorder.ts
|
|
1878
|
+
var REORDER_LANES_ACTION_ID = "diagram.reorderLanes";
|
|
1879
|
+
var REORDER_GROUPS_ACTION_ID = "diagram.reorderGroups";
|
|
1880
|
+
function slotsOf(order, boxOf) {
|
|
1881
|
+
if (order === void 0) return null;
|
|
1882
|
+
const items = order.items.flatMap((i) => {
|
|
1883
|
+
const bounds = boxOf(i.id);
|
|
1884
|
+
return bounds === void 0 ? [] : [{ id: i.id, bounds, ...i.grip !== void 0 ? { grip: i.grip } : {} }];
|
|
1885
|
+
});
|
|
1886
|
+
return items.length < 2 ? null : sequenceSlots(order.axis, items);
|
|
1887
|
+
}
|
|
1888
|
+
function keepsGroups(doc, next) {
|
|
1889
|
+
if (next === null) return null;
|
|
1890
|
+
const after = groupRuns(next.pipeline, mainChain(next.pipeline));
|
|
1891
|
+
for (const [id, run] of groupRuns(doc.pipeline, mainChain(doc.pipeline))) if (run !== null && after.get(id) === null) return null;
|
|
1892
|
+
return next;
|
|
1893
|
+
}
|
|
1894
|
+
function dropInGap(doc, chain, drawn, to, at) {
|
|
1895
|
+
const tries = [to];
|
|
1896
|
+
for (let k = to; k > 0 && !drawn.has(chain[k - 1]); k--) tries.push(k - 1);
|
|
1897
|
+
for (let k = to; k < chain.length && !drawn.has(chain[k]); k++) tries.push(k + 1);
|
|
1898
|
+
for (const t of tries) {
|
|
1899
|
+
const next = keepsGroups(doc, at(t));
|
|
1900
|
+
if (next !== null) return next;
|
|
1901
|
+
}
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
var drawnOf = (order) => new Set(order.items.map((i) => i.id));
|
|
1905
|
+
function stageMove(doc, order, move) {
|
|
1906
|
+
const chain = mainChain(doc.pipeline);
|
|
1907
|
+
const before = order.items[move.to];
|
|
1908
|
+
const to = before === void 0 ? chain.length : chain.indexOf(before.id);
|
|
1909
|
+
const wired = ensureWireIds(doc);
|
|
1910
|
+
return dropInGap(doc, chain, drawnOf(order), to, (t) => moveInChain(wired, move.id, t));
|
|
1911
|
+
}
|
|
1912
|
+
function groupMove(doc, order, units, move) {
|
|
1913
|
+
const chain = mainChain(doc.pipeline);
|
|
1914
|
+
const drawn = drawnOf(order);
|
|
1915
|
+
const indices = (unit, drawnOnly) => [...unitStages(doc, unit)].flatMap((s) => chain.includes(s) && (!drawnOnly || drawn.has(s)) ? [chain.indexOf(s)] : []);
|
|
1916
|
+
const run = indices(move.id, false);
|
|
1917
|
+
if (run.length === 0) return null;
|
|
1918
|
+
const [i, j] = [Math.min(...run), Math.max(...run)];
|
|
1919
|
+
if (j - i + 1 !== run.length) return null;
|
|
1920
|
+
const before = units[move.to];
|
|
1921
|
+
const edge = before !== void 0 ? indices(before, true) : indices(units[units.length - 1] ?? move.id, true).map((k) => k + 1);
|
|
1922
|
+
if (edge.length === 0) return null;
|
|
1923
|
+
const to = before !== void 0 ? Math.min(...edge) : Math.max(...edge);
|
|
1924
|
+
const wired = ensureWireIds(doc);
|
|
1925
|
+
return dropInGap(doc, chain, drawn, to, (t) => moveRunInChain(wired, chain[i], chain[j], t));
|
|
1926
|
+
}
|
|
1927
|
+
function laneMove(doc, order, move) {
|
|
1928
|
+
const types = order.items.flatMap((i) => laneTypeOf(i.id) ?? []);
|
|
1929
|
+
const moved = types.filter((_, k) => k !== move.from);
|
|
1930
|
+
moved.splice(move.to > move.from ? move.to - 1 : move.to, 0, types[move.from]);
|
|
1931
|
+
return setLaneOrder(doc, moved);
|
|
1932
|
+
}
|
|
1933
|
+
function unitsBefore(move) {
|
|
1934
|
+
const units = move.order.filter((id) => id !== move.id);
|
|
1935
|
+
units.splice(move.from, 0, move.id);
|
|
1936
|
+
return units;
|
|
1937
|
+
}
|
|
1938
|
+
function groupReorder(now) {
|
|
1939
|
+
const moved = (move) => {
|
|
1940
|
+
const { doc, order } = now();
|
|
1941
|
+
return order === void 0 ? null : groupMove(doc, order, unitsBefore(move), move);
|
|
1942
|
+
};
|
|
1943
|
+
return reorderContribution({
|
|
1944
|
+
id: REORDER_GROUPS_ACTION_ID,
|
|
1945
|
+
label: "Reorder group",
|
|
1946
|
+
slots: (_, at) => groupSlotsAt(now(), at),
|
|
1947
|
+
canDrop: (move) => moved(move) !== null,
|
|
1948
|
+
commit: (move) => {
|
|
1949
|
+
const next = moved(move);
|
|
1950
|
+
if (next !== null) now().onChange(next, "Reorder group");
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
function editorReorders(now) {
|
|
1955
|
+
return [
|
|
1956
|
+
reorderContribution({
|
|
1957
|
+
label: "Reorder",
|
|
1958
|
+
slots: () => slotsOf(now().order, now().boxOf),
|
|
1959
|
+
canDrop: (move) => {
|
|
1960
|
+
const { doc, order } = now();
|
|
1961
|
+
return order !== void 0 && stageMove(doc, order, move) !== null;
|
|
1962
|
+
},
|
|
1963
|
+
commit: (move) => {
|
|
1964
|
+
const { doc, order, onChange } = now();
|
|
1965
|
+
const next = order === void 0 ? null : stageMove(doc, order, move);
|
|
1966
|
+
if (next !== null) onChange(next, "Reorder");
|
|
1967
|
+
}
|
|
1968
|
+
}),
|
|
1969
|
+
reorderContribution({
|
|
1970
|
+
id: REORDER_LANES_ACTION_ID,
|
|
1971
|
+
label: "Reorder lanes",
|
|
1972
|
+
slots: () => slotsOf(now().laneOrder, now().boxOf),
|
|
1973
|
+
commit: (move) => {
|
|
1974
|
+
const { doc, laneOrder, onChange } = now();
|
|
1975
|
+
if (laneOrder !== void 0) onChange(laneMove(doc, laneOrder, move), "Reorder lanes");
|
|
1976
|
+
}
|
|
1977
|
+
}),
|
|
1978
|
+
groupReorder(now)
|
|
1979
|
+
];
|
|
1980
|
+
}
|
|
1981
|
+
var RENAME_GROUP_ACTION_ID = "effect.renameGroup";
|
|
1982
|
+
function renameGroupContribution(now, rename) {
|
|
1983
|
+
const under = (at) => {
|
|
1984
|
+
const { doc, boxOf } = now();
|
|
1985
|
+
return groupAt(doc, boxOf, at);
|
|
1986
|
+
};
|
|
1987
|
+
return {
|
|
1988
|
+
id: `weasel-diagram-effect:${RENAME_GROUP_ACTION_ID}`,
|
|
1989
|
+
eligibility: { always: true },
|
|
1990
|
+
actions: [{
|
|
1991
|
+
id: RENAME_GROUP_ACTION_ID,
|
|
1992
|
+
label: "Rename group",
|
|
1993
|
+
group: "diagram",
|
|
1994
|
+
enabled: (_, at) => at === void 0 || under(at) !== void 0 ? true : ActionDisabledReason.NotApplicable,
|
|
1995
|
+
invoker: {
|
|
1996
|
+
timing: "immediate",
|
|
1997
|
+
run(_, params) {
|
|
1998
|
+
const group = under({ x: Number(params?.worldX), y: Number(params?.worldY) });
|
|
1999
|
+
if (group !== void 0) rename(group);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}],
|
|
2003
|
+
// Empty space included: an unlabeled group's grip is padding.
|
|
2004
|
+
bindings: [{ spec: { kind: "doubleClick", target: { kindOf: ANYWHERE } }, actionId: RENAME_GROUP_ACTION_ID }]
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// src/effect/place.ts
|
|
2009
|
+
var PLACE_GAP = 24;
|
|
2010
|
+
var STEP = 8;
|
|
2011
|
+
var clear = (x, y, size, boxes) => boxes.every((b) => x >= b.x + b.width + PLACE_GAP || x + size.width + PLACE_GAP <= b.x || y >= b.y + b.height + PLACE_GAP || y + size.height + PLACE_GAP <= b.y);
|
|
2012
|
+
function freeSpot(boxes, size, view) {
|
|
2013
|
+
if (view !== null) {
|
|
2014
|
+
const right = view.x + view.width - PLACE_GAP - size.width;
|
|
2015
|
+
const bottom = view.y + view.height - PLACE_GAP - size.height;
|
|
2016
|
+
for (let y = view.y + PLACE_GAP; y <= bottom; y += STEP) {
|
|
2017
|
+
for (let x = view.x + PLACE_GAP; x <= right; x += STEP) {
|
|
2018
|
+
if (clear(x, y, size, boxes)) return { x, y };
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
if (boxes.length === 0) return { x: (view?.x ?? 0) + PLACE_GAP, y: (view?.y ?? 0) + PLACE_GAP };
|
|
2023
|
+
const last = boxes.reduce((a, b) => b.x + b.width > a.x + a.width ? b : a);
|
|
2024
|
+
return { x: last.x + last.width + PLACE_GAP, y: last.y };
|
|
2025
|
+
}
|
|
2026
|
+
var EffectEditor = forwardRef(function EffectEditor2({ doc, onChange, run, selected, onSelect, selectedGroup, onSelectGroup, look = "nodeGraph", options, width, height, background, className }, ref) {
|
|
2027
|
+
const built = useMemo(() => editorScene(doc, look, options), [doc, look, options]);
|
|
2028
|
+
const specs = useMemo(() => applyLookRoutes(diagramScene(built.data, built.scene), doc, look, built).map((s) => s.pickable === false || isPart(built, s.id) ? s : { ...s, pickable: false }), [built, doc, look]);
|
|
2029
|
+
const painted = useMemo(() => paintRun(doc.pipeline, specs, run, built.exits), [doc.pipeline, specs, run, built]);
|
|
2030
|
+
useReleaseReplaced(run);
|
|
2031
|
+
const portOptions = useRunPortOptions(built.color, doc.pipeline, run);
|
|
2032
|
+
const picks = useEditorPicks(built, { selected, selectedGroup, onSelect, onSelectGroup });
|
|
2033
|
+
const now = useLatest({ doc, built, specs, look, options, onChange });
|
|
2034
|
+
const view = useRef(null);
|
|
2035
|
+
const [renaming, setRenaming] = useState(null);
|
|
2036
|
+
const handlers = useMemo(() => editHandlers(() => now.current, picks.unpick, setRenaming), [now, picks.unpick]);
|
|
2037
|
+
const ambient = useMemo(() => {
|
|
2038
|
+
const source = () => {
|
|
2039
|
+
const { doc: d, built: b, specs: shown, onChange: report } = now.current;
|
|
2040
|
+
const boxes = new Map(shown.map((s) => [s.id, s.pose]));
|
|
2041
|
+
return { doc: d, order: b.order, laneOrder: b.laneOrder, boxOf: (id) => boxes.get(id), onChange: report };
|
|
2042
|
+
};
|
|
2043
|
+
return [...editorReorders(source), renameGroupContribution(source, setRenaming)];
|
|
2044
|
+
}, [now]);
|
|
2045
|
+
useImperativeHandle(ref, () => ({
|
|
2046
|
+
addStage: (type, client) => {
|
|
2047
|
+
const { doc: d, specs: shown, look: l, options: opts, onChange: report } = now.current;
|
|
2048
|
+
const stage = stageFromType(type, nextStageId(d.pipeline, type.type));
|
|
2049
|
+
if (l !== "nodeGraph") {
|
|
2050
|
+
report(addStage(ensureWireIds(d), stage), "Add stage");
|
|
2051
|
+
return stage.id;
|
|
2052
|
+
}
|
|
2053
|
+
const pinned = ensureWireIds(pinShown(d, shown));
|
|
2054
|
+
if (client && view.current) {
|
|
2055
|
+
report(addStage(pinned, stage, view.current.worldAt(client.x, client.y)), "Add stage");
|
|
2056
|
+
} else {
|
|
2057
|
+
const boxes = stageBoxes(d, shown);
|
|
2058
|
+
const alone = { pipeline: { ...d.pipeline, stages: [stage], wires: [], groups: [] }, places: {} };
|
|
2059
|
+
const size = stageBoxes(alone, drawnSpecs(alone, opts)).get(stage.id) ?? { width: 0, height: 0 };
|
|
2060
|
+
const at = freeSpot([...boxes.values()], size, view.current?.visible() ?? null);
|
|
2061
|
+
report(addStage(pinned, stage, at), "Add stage");
|
|
2062
|
+
}
|
|
2063
|
+
return stage.id;
|
|
2064
|
+
},
|
|
2065
|
+
group: (ids) => {
|
|
2066
|
+
const { doc: d, specs: shown, look: l, onChange: report } = now.current;
|
|
2067
|
+
if (ids.length === 0) return null;
|
|
2068
|
+
const base = l === "nodeGraph" ? pinShown(ensureWireIds(d), shown) : d;
|
|
2069
|
+
report(groupStages(base, ids), "Group");
|
|
2070
|
+
return nextGroupId(d.pipeline);
|
|
2071
|
+
},
|
|
2072
|
+
ungroup: (id) => {
|
|
2073
|
+
const { doc: d, onChange: report } = now.current;
|
|
2074
|
+
if (d.pipeline.groups?.some((g) => g.id === id)) report(ungroup(d, id), "Ungroup");
|
|
2075
|
+
},
|
|
2076
|
+
renameGroup: (id) => setRenaming(id)
|
|
2077
|
+
}), [now]);
|
|
2078
|
+
const title2 = useGroupTitle(doc, look, renaming, setRenaming, onChange);
|
|
2079
|
+
const fill = (look === "nodeGraph" ? options?.theme ?? LOOK_THEME : LOOK_THEME).page;
|
|
2080
|
+
const placed = useMemo(() => diagramPages(built.data), [built.data.pages, built.data.pageLayout]);
|
|
2081
|
+
const pages = useMemo(() => placed.length > 0 ? { placed, fill } : void 0, [placed, fill]);
|
|
2082
|
+
return /* @__PURE__ */ jsx(
|
|
2083
|
+
DiagramView,
|
|
2084
|
+
{
|
|
2085
|
+
ref: view,
|
|
2086
|
+
specs: painted,
|
|
2087
|
+
width,
|
|
2088
|
+
height,
|
|
2089
|
+
minScale: EFFECT_MIN_SCALE,
|
|
2090
|
+
anchor: "start",
|
|
2091
|
+
ports: "connect",
|
|
2092
|
+
portOptions,
|
|
2093
|
+
canConnect: handlers.canConnect,
|
|
2094
|
+
onConnect: handlers.onConnect,
|
|
2095
|
+
onReconnect: handlers.onReconnect,
|
|
2096
|
+
onEditRoute: handlers.onEditRoute,
|
|
2097
|
+
onDelete: handlers.onDelete,
|
|
2098
|
+
...look === "nodeGraph" ? { onMove: handlers.onMove } : { ambient },
|
|
2099
|
+
...look === "nodeGraph" ? { onEditContainer: handlers.onEditContainer } : {},
|
|
2100
|
+
onSelect: picks.pick,
|
|
2101
|
+
...picks.shown !== void 0 ? { selected: picks.shown } : {},
|
|
2102
|
+
...pages !== void 0 ? { pages } : {},
|
|
2103
|
+
...title2 !== null ? { overlays: title2 } : {},
|
|
2104
|
+
...background !== void 0 ? { background } : {},
|
|
2105
|
+
...className !== void 0 ? { className } : {}
|
|
2106
|
+
},
|
|
2107
|
+
look
|
|
2108
|
+
);
|
|
2109
|
+
});
|
|
2110
|
+
function drawnSpecs(doc, options) {
|
|
2111
|
+
const built = editorScene(doc, "nodeGraph", options);
|
|
2112
|
+
return diagramScene(built.data, built.scene);
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// src/effect/image/evaluators.ts
|
|
2116
|
+
var BLEND = { normal: "source-over", screen: "screen", multiply: "multiply" };
|
|
2117
|
+
function draw(width, height, paint) {
|
|
2118
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
2119
|
+
paint(canvas.getContext("2d"));
|
|
2120
|
+
return canvas.transferToImageBitmap();
|
|
2121
|
+
}
|
|
2122
|
+
function need(ctx, port) {
|
|
2123
|
+
const value = ctx.inputs[port];
|
|
2124
|
+
if (value === void 0) throw new Error(`${port} is not connected`);
|
|
2125
|
+
return value;
|
|
2126
|
+
}
|
|
2127
|
+
function decide(ctx, value) {
|
|
2128
|
+
const outs = ctx.stage.outputs ?? [];
|
|
2129
|
+
const split = ctx.stage.split;
|
|
2130
|
+
const index = split !== void 0 ? split.at.filter((cut) => value >= cut).length : value > 0 ? 0 : 1;
|
|
2131
|
+
return outs[Math.min(index, outs.length - 1)]?.id;
|
|
2132
|
+
}
|
|
2133
|
+
var imageEvaluators = {
|
|
2134
|
+
source: ({ sources }) => {
|
|
2135
|
+
if (sources.image === void 0) throw new Error("no source image");
|
|
2136
|
+
return { outputs: { out: sources.image } };
|
|
2137
|
+
},
|
|
2138
|
+
blur: (ctx) => {
|
|
2139
|
+
const image2 = need(ctx, "in");
|
|
2140
|
+
const radius = Number(ctx.params.radius ?? 0);
|
|
2141
|
+
return {
|
|
2142
|
+
outputs: {
|
|
2143
|
+
out: draw(image2.width, image2.height, (g) => {
|
|
2144
|
+
g.filter = `blur(${radius}px)`;
|
|
2145
|
+
g.drawImage(image2, 0, 0);
|
|
2146
|
+
})
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
},
|
|
2150
|
+
composite: (ctx) => {
|
|
2151
|
+
const base = need(ctx, "base");
|
|
2152
|
+
const top = need(ctx, "top");
|
|
2153
|
+
return {
|
|
2154
|
+
outputs: {
|
|
2155
|
+
out: draw(base.width, base.height, (g) => {
|
|
2156
|
+
g.drawImage(base, 0, 0);
|
|
2157
|
+
g.globalCompositeOperation = BLEND[String(ctx.params.blend)] ?? "source-over";
|
|
2158
|
+
g.drawImage(top, 0, 0, base.width, base.height);
|
|
2159
|
+
})
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
2162
|
+
},
|
|
2163
|
+
mask: (ctx) => {
|
|
2164
|
+
const image2 = need(ctx, "in");
|
|
2165
|
+
const mask = need(ctx, "mask");
|
|
2166
|
+
return {
|
|
2167
|
+
outputs: {
|
|
2168
|
+
out: draw(image2.width, image2.height, (g) => {
|
|
2169
|
+
g.drawImage(image2, 0, 0);
|
|
2170
|
+
g.globalCompositeOperation = "destination-in";
|
|
2171
|
+
g.drawImage(mask, 0, 0, image2.width, image2.height);
|
|
2172
|
+
})
|
|
2173
|
+
}
|
|
2174
|
+
};
|
|
2175
|
+
},
|
|
2176
|
+
oscillator: ({ params, time }) => ({ outputs: { value: Math.sin(2 * Math.PI * Number(params.rate ?? 1) * time) } }),
|
|
2177
|
+
decision: (ctx) => {
|
|
2178
|
+
const tested = ctx.stage.split?.input ?? ctx.stage.inputs?.find((p) => p.type === "number")?.id ?? "test";
|
|
2179
|
+
const taken = decide(ctx, need(ctx, tested));
|
|
2180
|
+
const type = ctx.stage.outputs?.find((p) => p.id === taken)?.type;
|
|
2181
|
+
const from = ctx.stage.inputs?.find((p) => p.type === type && ctx.inputs[p.id] !== void 0);
|
|
2182
|
+
return { outputs: taken !== void 0 && from !== void 0 ? { [taken]: ctx.inputs[from.id] } : {}, ...taken !== void 0 ? { taken } : {} };
|
|
2183
|
+
},
|
|
2184
|
+
out: () => ({ outputs: {} })
|
|
2185
|
+
};
|
|
2186
|
+
|
|
2187
|
+
// src/effect/image/stageTypes.ts
|
|
2188
|
+
var param = (label, control) => ({ label, value: formatParam(control), control });
|
|
2189
|
+
var image = (id, label) => ({ id, type: "image", ...label !== void 0 ? { label } : {} });
|
|
2190
|
+
var IMAGE_STAGE_TYPES = [
|
|
2191
|
+
{ type: "source", title: "Source", kind: "source", outputs: [image("out")] },
|
|
2192
|
+
{
|
|
2193
|
+
type: "blur",
|
|
2194
|
+
title: "Blur",
|
|
2195
|
+
inputs: [image("in")],
|
|
2196
|
+
outputs: [image("out")],
|
|
2197
|
+
params: [param("radius", { kind: "number", value: 4, min: 0, max: 50, step: 1, unit: "px" })]
|
|
2198
|
+
},
|
|
2199
|
+
{
|
|
2200
|
+
type: "composite",
|
|
2201
|
+
title: "Composite",
|
|
2202
|
+
inputs: [image("base"), image("top")],
|
|
2203
|
+
outputs: [image("out")],
|
|
2204
|
+
params: [param("blend", {
|
|
2205
|
+
kind: "enum",
|
|
2206
|
+
value: "normal",
|
|
2207
|
+
options: [{ value: "normal", label: "Normal" }, { value: "screen", label: "Screen" }, { value: "multiply", label: "Multiply" }]
|
|
2208
|
+
})]
|
|
2209
|
+
},
|
|
2210
|
+
{
|
|
2211
|
+
type: "mask",
|
|
2212
|
+
title: "Mask",
|
|
2213
|
+
inputs: [image("in"), { id: "mask", type: "mask" }],
|
|
2214
|
+
outputs: [image("out")]
|
|
2215
|
+
},
|
|
2216
|
+
{
|
|
2217
|
+
type: "oscillator",
|
|
2218
|
+
title: "Oscillator",
|
|
2219
|
+
outputs: [{ id: "value", type: "number" }],
|
|
2220
|
+
params: [param("rate", { kind: "number", value: 1, min: 0.1, max: 20, step: 0.1, unit: "Hz" })]
|
|
2221
|
+
},
|
|
2222
|
+
{
|
|
2223
|
+
type: "decision",
|
|
2224
|
+
title: "Decision",
|
|
2225
|
+
kind: "decision",
|
|
2226
|
+
inputs: [image("in"), { id: "test", type: "number" }],
|
|
2227
|
+
outputs: [image("yes", "yes"), image("no", "no")]
|
|
2228
|
+
},
|
|
2229
|
+
{ type: "out", title: "Out", kind: "sink", inputs: [image("in")] }
|
|
2230
|
+
];
|
|
2231
|
+
|
|
2232
|
+
// src/effect/image/testCard.ts
|
|
2233
|
+
var BARS = ["#c0c0c0", "#c0c000", "#00c0c0", "#00c000", "#c000c0", "#c00000", "#0000c0"];
|
|
2234
|
+
function testCard(size) {
|
|
2235
|
+
const { width, height } = typeof size === "number" ? { width: size, height: size } : size;
|
|
2236
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
2237
|
+
const g = canvas.getContext("2d");
|
|
2238
|
+
const barHeight = height * 0.7;
|
|
2239
|
+
BARS.forEach((color, i) => {
|
|
2240
|
+
g.fillStyle = color;
|
|
2241
|
+
const x = Math.floor(i * width / BARS.length);
|
|
2242
|
+
g.fillRect(x, 0, Math.ceil(width / BARS.length) + 1, barHeight);
|
|
2243
|
+
});
|
|
2244
|
+
const ramp = g.createLinearGradient(0, 0, width, 0);
|
|
2245
|
+
ramp.addColorStop(0, "#000");
|
|
2246
|
+
ramp.addColorStop(1, "#fff");
|
|
2247
|
+
g.fillStyle = ramp;
|
|
2248
|
+
g.fillRect(0, barHeight, width, height - barHeight);
|
|
2249
|
+
g.strokeStyle = "#fff";
|
|
2250
|
+
g.lineWidth = Math.max(1, Math.min(width, height) / 32);
|
|
2251
|
+
g.beginPath();
|
|
2252
|
+
g.arc(width / 2, height / 2, Math.min(width, height) * 0.3, 0, Math.PI * 2);
|
|
2253
|
+
g.stroke();
|
|
2254
|
+
return canvas.transferToImageBitmap();
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// src/effect/image/valueKinds.ts
|
|
2258
|
+
var bitmapKind = {
|
|
2259
|
+
show: (value) => `${value.width}\xD7${value.height}`,
|
|
2260
|
+
preview(value, size) {
|
|
2261
|
+
const width = Math.max(1, Math.round(size.width * PREVIEW_DENSITY));
|
|
2262
|
+
const height = Math.max(1, Math.round(size.height * PREVIEW_DENSITY));
|
|
2263
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
2264
|
+
canvas.getContext("2d").drawImage(value, 0, 0, width, height);
|
|
2265
|
+
return canvas.transferToImageBitmap();
|
|
2266
|
+
},
|
|
2267
|
+
dispose: (value) => value.close()
|
|
2268
|
+
};
|
|
2269
|
+
var imageValueKinds = {
|
|
2270
|
+
image: bitmapKind,
|
|
2271
|
+
mask: bitmapKind,
|
|
2272
|
+
number: { show: (value) => value.toFixed(3) }
|
|
2273
|
+
};
|
|
2274
|
+
|
|
2275
|
+
// src/effect/looks.ts
|
|
2276
|
+
var EFFECT_LOOKS = Object.freeze({ nodeGraph, rack, lanes, compact });
|
|
2277
|
+
|
|
2278
|
+
// src/effect/runner/pass.ts
|
|
2279
|
+
function createPass(pipeline, opts) {
|
|
2280
|
+
const stages = new Map(uniqueStages(pipeline).map((s) => [s.id, s]));
|
|
2281
|
+
const queue = runOrder(pipeline).order;
|
|
2282
|
+
const made = /* @__PURE__ */ new Map();
|
|
2283
|
+
const sources = opts.sources ?? {};
|
|
2284
|
+
const size = typeof opts.previewSize === "object" ? opts.previewSize : { width: opts.previewSize ?? PREVIEW_SIZE, height: opts.previewSize ?? PREVIEW_SIZE };
|
|
2285
|
+
const signal = opts.signal ?? new AbortController().signal;
|
|
2286
|
+
let run = { stages: {} };
|
|
2287
|
+
const inputsOf = (stage) => {
|
|
2288
|
+
const inputs2 = {};
|
|
2289
|
+
for (const w of pipeline.wires) {
|
|
2290
|
+
if (w.to.stage !== stage.id || inputs2[w.to.port] !== void 0) continue;
|
|
2291
|
+
const value = made.get(w.from.stage)?.[w.from.port];
|
|
2292
|
+
if (value !== void 0) inputs2[w.to.port] = value;
|
|
2293
|
+
}
|
|
2294
|
+
return inputs2;
|
|
2295
|
+
};
|
|
2296
|
+
const record = async (stage, inputs2, outputs, entry) => {
|
|
2297
|
+
made.set(stage.id, outputs);
|
|
2298
|
+
const values = {};
|
|
2299
|
+
const previews = {};
|
|
2300
|
+
const seen = { ...inputs2, ...outputs };
|
|
2301
|
+
for (const port of shownPorts(stage)) {
|
|
2302
|
+
const value = seen[port.id];
|
|
2303
|
+
if (value === void 0) continue;
|
|
2304
|
+
const kind = opts.kinds?.[port.type];
|
|
2305
|
+
const text = kind?.show !== void 0 ? kind.show(value) : plainText(value);
|
|
2306
|
+
if (text !== void 0) values[port.id] = text;
|
|
2307
|
+
const preview = opts.previews === false ? void 0 : await kind?.preview?.(value, size);
|
|
2308
|
+
if (preview !== void 0) previews[port.id] = preview;
|
|
2309
|
+
}
|
|
2310
|
+
const full = {
|
|
2311
|
+
...entry,
|
|
2312
|
+
...Object.keys(values).length > 0 ? { values } : {},
|
|
2313
|
+
...Object.keys(previews).length > 0 ? { previews } : {}
|
|
2314
|
+
};
|
|
2315
|
+
run = { stages: { ...run.stages, [stage.id]: full } };
|
|
2316
|
+
};
|
|
2317
|
+
const evaluate = async (stage, inputs2) => {
|
|
2318
|
+
if (stage.bypassed === true) return record(stage, inputs2, passThrough(stage, inputs2), {});
|
|
2319
|
+
const evaluator = stage.type === void 0 ? void 0 : opts.evaluators[stage.type];
|
|
2320
|
+
const params = paramValues(stage);
|
|
2321
|
+
const start = performance.now();
|
|
2322
|
+
try {
|
|
2323
|
+
if (evaluator === void 0) throw new Error(`no evaluator for type "${stage.type ?? ""}"`);
|
|
2324
|
+
const result = await evaluator({ stage, inputs: inputs2, params, sources, time: opts.time ?? 0, signal });
|
|
2325
|
+
const cost = performance.now() - start;
|
|
2326
|
+
return record(stage, inputs2, result.outputs, { cost, ...result.taken !== void 0 ? { taken: result.taken } : {} });
|
|
2327
|
+
} catch (err) {
|
|
2328
|
+
signal.throwIfAborted();
|
|
2329
|
+
const cost = performance.now() - start;
|
|
2330
|
+
run = { stages: { ...run.stages, [stage.id]: { cost, error: err instanceof Error ? err.message : String(err) } } };
|
|
2331
|
+
}
|
|
2332
|
+
};
|
|
2333
|
+
return {
|
|
2334
|
+
get run() {
|
|
2335
|
+
return run;
|
|
2336
|
+
},
|
|
2337
|
+
async step() {
|
|
2338
|
+
while (queue.length > 0) {
|
|
2339
|
+
signal.throwIfAborted();
|
|
2340
|
+
const stage = stages.get(queue.shift());
|
|
2341
|
+
const inputs2 = inputsOf(stage);
|
|
2342
|
+
if ((stage.inputs?.length ?? 0) > 0 && Object.keys(inputs2).length === 0) continue;
|
|
2343
|
+
await evaluate(stage, inputs2);
|
|
2344
|
+
signal.throwIfAborted();
|
|
2345
|
+
return stage.id;
|
|
2346
|
+
}
|
|
2347
|
+
return void 0;
|
|
2348
|
+
},
|
|
2349
|
+
dispose() {
|
|
2350
|
+
const given = new Set(Object.values(sources));
|
|
2351
|
+
const done = /* @__PURE__ */ new Set();
|
|
2352
|
+
for (const [id, outputs] of made) {
|
|
2353
|
+
for (const port of stages.get(id)?.outputs ?? []) {
|
|
2354
|
+
const value = outputs[port.id];
|
|
2355
|
+
if (value === void 0 || given.has(value) || done.has(value)) continue;
|
|
2356
|
+
done.add(value);
|
|
2357
|
+
opts.kinds?.[port.type]?.dispose?.(value);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
made.clear();
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
function passThrough(stage, inputs2) {
|
|
2365
|
+
const out = {};
|
|
2366
|
+
for (const port of stage.outputs ?? []) {
|
|
2367
|
+
const from = (stage.inputs ?? []).find((i) => i.type === port.type && inputs2[i.id] !== void 0);
|
|
2368
|
+
if (from !== void 0) out[port.id] = inputs2[from.id];
|
|
2369
|
+
}
|
|
2370
|
+
return out;
|
|
2371
|
+
}
|
|
2372
|
+
function plainText(value) {
|
|
2373
|
+
return typeof value === "number" || typeof value === "string" || typeof value === "boolean" ? String(value) : void 0;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// src/effect/runner/runPipeline.ts
|
|
2377
|
+
async function runPipeline(pipeline, opts) {
|
|
2378
|
+
const pass = createPass(pipeline, opts);
|
|
2379
|
+
try {
|
|
2380
|
+
while (await pass.step() !== void 0) ;
|
|
2381
|
+
return pass.run;
|
|
2382
|
+
} finally {
|
|
2383
|
+
pass.dispose();
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
// src/effect/runner/runner.ts
|
|
2388
|
+
function createEffectRunner(opts) {
|
|
2389
|
+
let { pipeline, ...options } = opts;
|
|
2390
|
+
let state = { run: void 0, playing: false, time: 0 };
|
|
2391
|
+
let stepping = null;
|
|
2392
|
+
let controller = new AbortController();
|
|
2393
|
+
let tail = Promise.resolve();
|
|
2394
|
+
let pending = 0;
|
|
2395
|
+
let published = [];
|
|
2396
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2397
|
+
const set = (next) => {
|
|
2398
|
+
state = { ...state, ...next };
|
|
2399
|
+
for (const l of [...listeners]) l();
|
|
2400
|
+
};
|
|
2401
|
+
const publish = (run) => {
|
|
2402
|
+
published.push(run);
|
|
2403
|
+
set({ run });
|
|
2404
|
+
};
|
|
2405
|
+
const passOptions = (time) => ({ ...options, time, signal: controller.signal });
|
|
2406
|
+
const dropStepping = () => {
|
|
2407
|
+
stepping?.dispose();
|
|
2408
|
+
stepping = null;
|
|
2409
|
+
};
|
|
2410
|
+
const enqueue = (task) => {
|
|
2411
|
+
const signal = controller.signal;
|
|
2412
|
+
pending++;
|
|
2413
|
+
const done = tail.then(() => signal.aborted ? void 0 : task(signal)).catch((err) => {
|
|
2414
|
+
if (!signal.aborted) throw err;
|
|
2415
|
+
}).finally(() => {
|
|
2416
|
+
pending--;
|
|
2417
|
+
});
|
|
2418
|
+
tail = done.catch(() => {
|
|
2419
|
+
});
|
|
2420
|
+
return done;
|
|
2421
|
+
};
|
|
2422
|
+
const wholeRun = (time) => async (signal) => {
|
|
2423
|
+
dropStepping();
|
|
2424
|
+
const run = await runPipeline(pipeline, passOptions(time));
|
|
2425
|
+
if (signal.aborted) closeBitmaps(previewsOf(run));
|
|
2426
|
+
else publish(run);
|
|
2427
|
+
};
|
|
2428
|
+
const stepOnce = (time) => async () => {
|
|
2429
|
+
let pass = stepping ??= createPass(pipeline, passOptions(time));
|
|
2430
|
+
let at = await pass.step();
|
|
2431
|
+
if (at === void 0) {
|
|
2432
|
+
dropStepping();
|
|
2433
|
+
pass = stepping = createPass(pipeline, passOptions(time));
|
|
2434
|
+
at = await pass.step();
|
|
2435
|
+
}
|
|
2436
|
+
publish(at === void 0 ? pass.run : { ...pass.run, at });
|
|
2437
|
+
};
|
|
2438
|
+
return {
|
|
2439
|
+
get state() {
|
|
2440
|
+
return state;
|
|
2441
|
+
},
|
|
2442
|
+
run: () => enqueue(wholeRun(state.time)),
|
|
2443
|
+
step() {
|
|
2444
|
+
return enqueue(stepOnce(state.time));
|
|
2445
|
+
},
|
|
2446
|
+
play: () => {
|
|
2447
|
+
if (!state.playing) set({ playing: true });
|
|
2448
|
+
},
|
|
2449
|
+
pause: () => {
|
|
2450
|
+
if (state.playing) set({ playing: false });
|
|
2451
|
+
},
|
|
2452
|
+
reset() {
|
|
2453
|
+
controller.abort();
|
|
2454
|
+
controller = new AbortController();
|
|
2455
|
+
dropStepping();
|
|
2456
|
+
set({ run: void 0, playing: false, time: 0 });
|
|
2457
|
+
},
|
|
2458
|
+
advance(seconds) {
|
|
2459
|
+
if (!state.playing) return;
|
|
2460
|
+
state = { ...state, time: state.time + seconds };
|
|
2461
|
+
if (pending === 0) void enqueue(wholeRun(state.time));
|
|
2462
|
+
},
|
|
2463
|
+
setPipeline(next) {
|
|
2464
|
+
if (next === pipeline) return;
|
|
2465
|
+
pipeline = next;
|
|
2466
|
+
dropStepping();
|
|
2467
|
+
},
|
|
2468
|
+
setSources(sources) {
|
|
2469
|
+
if (sources === options.sources) return;
|
|
2470
|
+
options = { ...options, sources };
|
|
2471
|
+
dropStepping();
|
|
2472
|
+
},
|
|
2473
|
+
setOptions(next) {
|
|
2474
|
+
options = { ...options, ...next };
|
|
2475
|
+
},
|
|
2476
|
+
drawn(run) {
|
|
2477
|
+
const at = run === void 0 ? published.length : published.indexOf(run);
|
|
2478
|
+
if (at <= 0) return;
|
|
2479
|
+
const keep = new Set(run === void 0 ? [] : previewsOf(run));
|
|
2480
|
+
closeBitmaps(published.slice(0, at).flatMap(previewsOf).filter((b) => !keep.has(b)));
|
|
2481
|
+
published = published.slice(at);
|
|
2482
|
+
},
|
|
2483
|
+
subscribe(listener) {
|
|
2484
|
+
listeners.add(listener);
|
|
2485
|
+
return () => {
|
|
2486
|
+
listeners.delete(listener);
|
|
2487
|
+
};
|
|
2488
|
+
},
|
|
2489
|
+
dispose() {
|
|
2490
|
+
this.reset();
|
|
2491
|
+
closeBitmaps(published.flatMap(previewsOf));
|
|
2492
|
+
published = [];
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
function useEffectRunner(opts) {
|
|
2497
|
+
const { pipeline, sources, evaluators, kinds, previews, previewSize, target, requestFrame, cancelFrame } = opts;
|
|
2498
|
+
const [runner] = useState(() => createEffectRunner(opts));
|
|
2499
|
+
useEffect(() => {
|
|
2500
|
+
runner.setPipeline(pipeline);
|
|
2501
|
+
}, [runner, pipeline]);
|
|
2502
|
+
useEffect(() => {
|
|
2503
|
+
if (sources !== void 0) runner.setSources(sources);
|
|
2504
|
+
}, [runner, sources]);
|
|
2505
|
+
useEffect(() => {
|
|
2506
|
+
runner.setOptions({
|
|
2507
|
+
evaluators,
|
|
2508
|
+
...kinds !== void 0 ? { kinds } : {},
|
|
2509
|
+
...previews !== void 0 ? { previews } : {},
|
|
2510
|
+
...previewSize !== void 0 ? { previewSize } : {}
|
|
2511
|
+
});
|
|
2512
|
+
}, [runner, evaluators, kinds, previews, previewSize]);
|
|
2513
|
+
const state = useSyncExternalStore(runner.subscribe, () => runner.state);
|
|
2514
|
+
const last = useRef(null);
|
|
2515
|
+
const loop = useVisibleRaf((now, self) => {
|
|
2516
|
+
if (!runner.state.playing) return;
|
|
2517
|
+
runner.advance(last.current === null ? 0 : (now - last.current) / 1e3);
|
|
2518
|
+
last.current = now;
|
|
2519
|
+
self.request();
|
|
2520
|
+
}, {
|
|
2521
|
+
...target !== void 0 ? { target } : {},
|
|
2522
|
+
...requestFrame !== void 0 ? { requestFrame } : {},
|
|
2523
|
+
...cancelFrame !== void 0 ? { cancelFrame } : {},
|
|
2524
|
+
onResume: () => {
|
|
2525
|
+
last.current = null;
|
|
2526
|
+
}
|
|
2527
|
+
});
|
|
2528
|
+
useEffect(() => {
|
|
2529
|
+
last.current = null;
|
|
2530
|
+
if (state.playing) loop.request();
|
|
2531
|
+
else loop.cancel();
|
|
2532
|
+
}, [state.playing, loop]);
|
|
2533
|
+
useEffect(() => {
|
|
2534
|
+
runner.drawn(state.run);
|
|
2535
|
+
}, [runner, state.run]);
|
|
2536
|
+
useEffect(() => () => runner.dispose(), [runner]);
|
|
2537
|
+
return { ...state, runner };
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
export { AT_COLOR, BRANCH_SYMBOLS, COMPACT_PADDING, EFFECT_LOOKS, EFFECT_MIN_SCALE, EffectChart, EffectEditor, IMAGE_STAGE_TYPES, LANE_INSET, REORDER_LANES_ACTION_ID, addStage, addWire, applyLookRoutes, branchNode, canMoveInChain, canWire, chainLinks, compact, costText, createEffectRunner, createPass, editorScene, ensureWireIds, groupContents, groupStages, imageEvaluators, imageValueKinds, laneId, laneTypeOf, lanes, mainChain, moveGroup, moveInChain, moveStages, moveToGroup, nextGroupId, nextStageId, nextWireId, nodeGraph, paintRun, pruneLookRoutes, rack, reconnectWire, removeStages, removeWires, renameGroup, runPipeline, setGroupBox, setLaneOrder, setLookRoute, setParam, setWireRoute, symbolNameFor, testCard, ungroup, useEffectRunner, wireEnds };
|
|
2541
|
+
//# sourceMappingURL=effect.js.map
|
|
2542
|
+
//# sourceMappingURL=effect.js.map
|