process-graph 1.0.6 → 1.1.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 +85 -17
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +92 -0
- package/dist/cli.d.ts +512 -0
- package/dist/cli.js +3 -0
- package/dist/control-aDk9560d.js +294 -0
- package/dist/detail-panel-B1ZmMuJT.js +341 -0
- package/dist/dist-BpCBmnMl.js +582 -0
- package/dist/dist-HLlW8vgT.js +1388 -0
- package/dist/dist-IjrgWhHQ.js +3 -0
- package/dist/errors-BZe5WzCn.js +19 -0
- package/dist/exit-codes-0ultCqdS.js +23 -0
- package/dist/graphql-C1j5wQFz.d.ts +631 -0
- package/dist/index-CCfe0csl.d.ts +353 -0
- package/dist/index-CmZOswkx.d.ts +512 -0
- package/dist/index-CoA6vXqe.d.ts +404 -0
- package/dist/index-CptUHq6F.d.ts +58 -0
- package/dist/index-CrM5qWUA.d.ts +110 -0
- package/dist/index-DhbvDgK4.d.ts +1582 -0
- package/dist/index-DricKcyl.d.ts +421 -0
- package/dist/index.d.ts +111 -4
- package/dist/index.js +929 -5
- package/dist/layout-CCM-KBdp.js +307 -0
- package/dist/layout.d.ts +8 -0
- package/dist/layout.js +2 -0
- package/dist/node-AchI1T-K.js +40 -0
- package/dist/open-CyRXJ4bt.js +29 -0
- package/dist/parse-CuipC_2K.js +41 -0
- package/dist/queries-CCexJAE2.js +2202 -0
- package/dist/react-flow/style.css +658 -0
- package/dist/react-flow.d.ts +173 -0
- package/dist/react-flow.js +450 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +107 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/router-D4gtTlXm.js +188 -0
- package/dist/schema-CVnS6HiX.js +234 -0
- package/dist/selection-BW_xXWOy.js +314 -0
- package/dist/source/github/node.d.ts +16 -0
- package/dist/source/github/node.js +2 -0
- package/dist/source/github.d.ts +49 -0
- package/dist/source/github.js +201 -0
- package/dist/source-DRDpEA8Q.js +1316 -0
- package/dist/status-Bm1CGu9C.js +11 -0
- package/dist/store/node.d.ts +24 -0
- package/dist/store/node.js +2 -0
- package/dist/store/wasm/worker.d.ts +16 -0
- package/dist/store/wasm/worker.js +98 -0
- package/dist/store/wasm.d.ts +9 -0
- package/dist/store/wasm.js +11 -0
- package/dist/store-BcR70CaO.js +1287 -0
- package/dist/store.d.ts +1328 -0
- package/dist/store.js +238 -0
- package/dist/ui/style.css +351 -0
- package/dist/ui.d.ts +235 -0
- package/dist/ui.js +2 -0
- package/dist/validate-DF3mQZww.js +125 -0
- package/package.json +150 -15
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
//#region ../../core/graph/dag/dist/errors.js
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when an operation that is only defined on an acyclic graph is given one that contains a cycle.
|
|
4
|
+
*/
|
|
5
|
+
var CycleError = class extends Error {
|
|
6
|
+
/**
|
|
7
|
+
* One closed path per strongly connected component that contains a cycle, exactly as `findCycles` reports them.
|
|
8
|
+
*/
|
|
9
|
+
cycles;
|
|
10
|
+
/**
|
|
11
|
+
* @param cycles - The cycles found, as returned by `findCycles`. Must not be empty.
|
|
12
|
+
*/
|
|
13
|
+
constructor(cycles) {
|
|
14
|
+
super(`The graph contains a cycle: ${cycles.map((path) => path.join(" -> ")).join("; ")}`);
|
|
15
|
+
this.name = "CycleError";
|
|
16
|
+
this.cycles = cycles;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Thrown when an operation is asked about an id that is not a node of the graph it was given.
|
|
21
|
+
*/
|
|
22
|
+
var UnknownNodeError = class extends Error {
|
|
23
|
+
/**
|
|
24
|
+
* The id that is not a node of the graph.
|
|
25
|
+
*/
|
|
26
|
+
id;
|
|
27
|
+
/**
|
|
28
|
+
* @param id - The id that is not a node of the graph.
|
|
29
|
+
*/
|
|
30
|
+
constructor(id) {
|
|
31
|
+
super(`"${String(id)}" is not a node of the graph`);
|
|
32
|
+
this.name = "UnknownNodeError";
|
|
33
|
+
this.id = id;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region ../../core/graph/dag/dist/dag.js
|
|
38
|
+
/**
|
|
39
|
+
* Reads a value keyed by a node id out of a map built over a graph's nodes.
|
|
40
|
+
* @throws UnknownNodeError when the id is not a node of that graph.
|
|
41
|
+
*/
|
|
42
|
+
function lookup(map, id) {
|
|
43
|
+
const value = map.get(id);
|
|
44
|
+
if (value === void 0) throw new UnknownNodeError(id);
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Assembles a graph from nodes and already-resolved direct needs, deriving the inverse relation. Internal: every needs target must be a node.
|
|
49
|
+
*/
|
|
50
|
+
function assembleDag(nodes, needs, unknownReferences) {
|
|
51
|
+
const dependents = new Map(nodes.map((id) => [id, []]));
|
|
52
|
+
for (const id of nodes) for (const target of lookup(needs, id)) lookup(dependents, target).push(id);
|
|
53
|
+
return {
|
|
54
|
+
nodes,
|
|
55
|
+
needs,
|
|
56
|
+
dependents,
|
|
57
|
+
unknownReferences
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Builds a graph once from a node set and a `needs` accessor.
|
|
62
|
+
*
|
|
63
|
+
* `nodes` is iterated once and duplicates are dropped, keeping the first occurrence. `needs` is called exactly once per distinct node, in node order, and its result is iterated once. A target that is not a node is recorded in `unknownReferences` and is not an edge. A node may need itself; that is a cycle of one, reported by `findCycles`.
|
|
64
|
+
*
|
|
65
|
+
* Never throws for malformed structure: cycles and unknown references are data on the result and in the analyses.
|
|
66
|
+
* @param nodes - Every node of the graph.
|
|
67
|
+
* @param needs - The direct needs of a node: the nodes that must come before it.
|
|
68
|
+
*/
|
|
69
|
+
function buildDag(nodes, needs) {
|
|
70
|
+
const ordered = [...new Set(nodes)];
|
|
71
|
+
const known = new Set(ordered);
|
|
72
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
73
|
+
const unknownReferences = [];
|
|
74
|
+
for (const id of ordered) {
|
|
75
|
+
const direct = /* @__PURE__ */ new Set();
|
|
76
|
+
const missing = /* @__PURE__ */ new Set();
|
|
77
|
+
for (const target of needs(id)) if (known.has(target)) direct.add(target);
|
|
78
|
+
else missing.add(target);
|
|
79
|
+
resolved.set(id, [...direct]);
|
|
80
|
+
for (const target of missing) unknownReferences.push({
|
|
81
|
+
from: id,
|
|
82
|
+
missing: target
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return assembleDag(ordered, resolved, unknownReferences);
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region ../../core/graph/dag/dist/reachability.js
|
|
89
|
+
/**
|
|
90
|
+
* Every node reachable from `from` by following `edges` one or more times, in node order.
|
|
91
|
+
*/
|
|
92
|
+
function reachable(dag, edges, from) {
|
|
93
|
+
const found = /* @__PURE__ */ new Set();
|
|
94
|
+
const pending = [...lookup(edges, from)];
|
|
95
|
+
for (let node = pending.pop(); node !== void 0; node = pending.pop()) {
|
|
96
|
+
if (found.has(node)) continue;
|
|
97
|
+
found.add(node);
|
|
98
|
+
pending.push(...lookup(edges, node));
|
|
99
|
+
}
|
|
100
|
+
return dag.nodes.filter((id) => found.has(id));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Every node `id` depends on, directly or through other nodes: the transitive closure of `needs` from `id`, in node order.
|
|
104
|
+
*
|
|
105
|
+
* `id` itself is included exactly when it lies on a cycle, since it then depends on itself. Unknown references are not edges. Iterative, so depth cannot overflow the call stack.
|
|
106
|
+
* @throws UnknownNodeError when `id` is not a node of the graph.
|
|
107
|
+
*/
|
|
108
|
+
function ancestorsOf(dag, id) {
|
|
109
|
+
return reachable(dag, dag.needs, id);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Every node that depends on `id`, directly or through other nodes: the transitive closure of the inverse of `needs` from `id`, in node order.
|
|
113
|
+
*
|
|
114
|
+
* `id` itself is included exactly when it lies on a cycle. Iterative, so depth cannot overflow the call stack.
|
|
115
|
+
* @throws UnknownNodeError when `id` is not a node of the graph.
|
|
116
|
+
*/
|
|
117
|
+
function descendantsOf(dag, id) {
|
|
118
|
+
return reachable(dag, dag.dependents, id);
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region ../../core/graph/dag/dist/cycles.js
|
|
122
|
+
/**
|
|
123
|
+
* Every strongly connected component of the graph: the maximal sets of nodes in which each node reaches every other along `needs` edges. Every node is in exactly one component, and a node on no cycle is a component of its own. Found by Tarjan's algorithm with an explicit stack, so depth is bounded by memory, not by the call stack, and each node and edge is visited once.
|
|
124
|
+
*
|
|
125
|
+
* A component lists its nodes in the order Tarjan's algorithm completes them, its root last. Components come out in reverse topological order of the condensed graph along `needs`: a component appears before every component that needs it. Since mutual reachability is symmetric, the components are the same whichever direction the edges are read in.
|
|
126
|
+
* @param dag - The graph to partition.
|
|
127
|
+
* @returns Every component, each a non-empty list of nodes.
|
|
128
|
+
*/
|
|
129
|
+
function stronglyConnectedComponents(dag) {
|
|
130
|
+
const index = /* @__PURE__ */ new Map();
|
|
131
|
+
const lowLink = /* @__PURE__ */ new Map();
|
|
132
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
133
|
+
const stack = [];
|
|
134
|
+
const components = [];
|
|
135
|
+
const popComponent = (root) => {
|
|
136
|
+
const component = [];
|
|
137
|
+
for (let member = stack.pop(); member !== void 0; member = stack.pop()) {
|
|
138
|
+
onStack.delete(member);
|
|
139
|
+
component.push(member);
|
|
140
|
+
if (member === root) break;
|
|
141
|
+
}
|
|
142
|
+
return component;
|
|
143
|
+
};
|
|
144
|
+
const visit = (node) => {
|
|
145
|
+
index.set(node, index.size);
|
|
146
|
+
lowLink.set(node, lookup(index, node));
|
|
147
|
+
stack.push(node);
|
|
148
|
+
onStack.add(node);
|
|
149
|
+
};
|
|
150
|
+
for (const root of dag.nodes) {
|
|
151
|
+
if (index.has(root)) continue;
|
|
152
|
+
visit(root);
|
|
153
|
+
const frames = [{
|
|
154
|
+
node: root,
|
|
155
|
+
next: 0
|
|
156
|
+
}];
|
|
157
|
+
for (let frame = frames.at(-1); frame !== void 0; frame = frames.at(-1)) {
|
|
158
|
+
const target = lookup(dag.needs, frame.node)[frame.next];
|
|
159
|
+
if (target !== void 0) {
|
|
160
|
+
frame.next += 1;
|
|
161
|
+
if (!index.has(target)) {
|
|
162
|
+
visit(target);
|
|
163
|
+
frames.push({
|
|
164
|
+
node: target,
|
|
165
|
+
next: 0
|
|
166
|
+
});
|
|
167
|
+
} else if (onStack.has(target)) lowLink.set(frame.node, Math.min(lookup(lowLink, frame.node), lookup(index, target)));
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
frames.pop();
|
|
171
|
+
const parent = frames.at(-1);
|
|
172
|
+
if (parent !== void 0) lowLink.set(parent.node, Math.min(lookup(lowLink, parent.node), lookup(lowLink, frame.node)));
|
|
173
|
+
if (lookup(lowLink, frame.node) === lookup(index, frame.node)) components.push(popComponent(frame.node));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return components;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The shortest closed path along `needs` edges from `start` back to itself, staying inside `members`. Ties between equally short paths go to the one found first when each node's needs are read in order.
|
|
180
|
+
*/
|
|
181
|
+
function shortestCycleThrough(dag, start, members) {
|
|
182
|
+
const parent = /* @__PURE__ */ new Map();
|
|
183
|
+
const queue = [start];
|
|
184
|
+
for (const current of queue) for (const target of lookup(dag.needs, current)) {
|
|
185
|
+
if (target === start) {
|
|
186
|
+
const between = [];
|
|
187
|
+
for (let node = current; node !== start; node = lookup(parent, node)) between.push(node);
|
|
188
|
+
return [
|
|
189
|
+
start,
|
|
190
|
+
...between.reverse(),
|
|
191
|
+
start
|
|
192
|
+
];
|
|
193
|
+
}
|
|
194
|
+
if (members.has(target) && !parent.has(target)) {
|
|
195
|
+
parent.set(target, current);
|
|
196
|
+
queue.push(target);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
throw new Error(`Invariant violated: "${String(start)}" is in a strongly connected component but no cycle passes through it`);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Reports every cycle in the graph: exactly one closed path per strongly connected component that contains a cycle (more than one node, or a node that needs itself).
|
|
203
|
+
*
|
|
204
|
+
* A path lists nodes such that each needs the next and the last needs the first, which is repeated at the end: `[a, b, c, a]` means a needs b, b needs c and c needs a. A self-loop is `[a, a]`. The path is a shortest cycle through the component's first node in node order, ties going to the earliest needs. Paths are ordered by the position of their first node. A component can contain more cycles than the one reported; removing the reported cycle's edges and calling again finds the rest.
|
|
205
|
+
*
|
|
206
|
+
* Returns an empty array exactly when the graph is acyclic. Iterative, so a deep graph cannot overflow the call stack.
|
|
207
|
+
*/
|
|
208
|
+
function findCycles(dag) {
|
|
209
|
+
const position = new Map(dag.nodes.map((id, order) => [id, order]));
|
|
210
|
+
const found = [];
|
|
211
|
+
for (const component of stronglyConnectedComponents(dag)) {
|
|
212
|
+
const [only] = component;
|
|
213
|
+
if (component.length === 1 && only !== void 0 && !lookup(dag.needs, only).includes(only)) continue;
|
|
214
|
+
const members = new Set(component);
|
|
215
|
+
const [earliest] = component.map((id) => ({
|
|
216
|
+
id,
|
|
217
|
+
order: lookup(position, id)
|
|
218
|
+
})).sort((a, b) => a.order - b.order);
|
|
219
|
+
if (earliest === void 0) continue;
|
|
220
|
+
found.push({
|
|
221
|
+
first: earliest.order,
|
|
222
|
+
path: shortestCycleThrough(dag, earliest.id, members)
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return found.sort((a, b) => a.first - b.first).map(({ path }) => path);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Throws a `CycleError` unless the graph is acyclic.
|
|
229
|
+
* @throws CycleError carrying `findCycles(dag)` when the graph contains a cycle.
|
|
230
|
+
*/
|
|
231
|
+
function assertAcyclic(dag) {
|
|
232
|
+
const cycles = findCycles(dag);
|
|
233
|
+
if (cycles.length > 0) throw new CycleError(cycles);
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region ../../core/graph/domain/dist/control.js
|
|
237
|
+
/**
|
|
238
|
+
* Builds the dependency graph over every element, leaving out the transitions in `excluded`. A node needs the source of each remaining incoming transition and, for an activity, the producer of each artefact it consumes.
|
|
239
|
+
*/
|
|
240
|
+
const buildControl = (model, excluded) => {
|
|
241
|
+
const sources = new Map(model.elements.map(({ id }) => [id, []]));
|
|
242
|
+
for (const transition of model.transitions) {
|
|
243
|
+
const incoming = sources.get(transition.target);
|
|
244
|
+
if (incoming !== void 0 && !excluded.has(transition.id)) incoming.push(transition.source);
|
|
245
|
+
}
|
|
246
|
+
const producers = /* @__PURE__ */ new Map();
|
|
247
|
+
for (const element of model.elements) {
|
|
248
|
+
if (element.kind !== "activity") continue;
|
|
249
|
+
for (const artefact of element.produces ?? []) {
|
|
250
|
+
const known = producers.get(artefact);
|
|
251
|
+
if (known === void 0) producers.set(artefact, [element.id]);
|
|
252
|
+
else known.push(element.id);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const consumed = new Map(model.elements.flatMap((element) => element.kind === "activity" && element.consumes !== void 0 ? [[element.id, element.consumes]] : []));
|
|
256
|
+
return buildDag(model.elements.map(({ id }) => id), (id) => [...sources.get(id) ?? [], ...(consumed.get(id) ?? []).flatMap((artefact) => producers.get(artefact) ?? [])]);
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* The transitions that re-enter earlier work: a transition leaving a `decision` element whose target can reach its source (or is its source), so that it closes a cycle through the decision.
|
|
260
|
+
*
|
|
261
|
+
* Reachability is over the whole graph, transitions and artefact dependencies alike. Only a decision's own outgoing transitions are candidates: a cycle that contains no decision has no loop edge and is illegal.
|
|
262
|
+
* @param model - The model to read.
|
|
263
|
+
* @returns The loop transitions, in transition order.
|
|
264
|
+
* @throws UnknownNodeError when such a transition's target is not an element of the model.
|
|
265
|
+
*/
|
|
266
|
+
const loopTransitions = (model) => {
|
|
267
|
+
const decisions = new Set(model.elements.flatMap((element) => element.kind === "decision" ? [element.id] : []));
|
|
268
|
+
const whole = buildControl(model, /* @__PURE__ */ new Set());
|
|
269
|
+
return model.transitions.filter((transition) => decisions.has(transition.source) && (transition.target === transition.source || descendantsOf(whole, transition.target).includes(transition.source))).map(({ id }) => id);
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* The control graph the whole analysis is computed on, with the loop edges removed.
|
|
273
|
+
*
|
|
274
|
+
* Its nodes are the element ids. A node needs the source of every incoming transition that is not a loop edge (see `loopTransitions`) and, for an activity, the producer of every artefact in `consumes`. Excluding loop edges makes a legal retry or review loop acyclic here, so readiness, layers and the critical path are well defined: a loop edge is a way back to earlier work, never a prerequisite of it. A node whose only incoming transitions are loop edges therefore has no needs.
|
|
275
|
+
*
|
|
276
|
+
* The graph is not guaranteed acyclic: cycles that no decision closes remain and are reported by `illegalCycles`. A transition whose source is not an element appears in `unknownReferences`.
|
|
277
|
+
* @param model - The model to read.
|
|
278
|
+
* @returns The graph over element ids, as a `graph-dag` `Dag`.
|
|
279
|
+
* @throws UnknownNodeError when a decision's transition targets something that is not an element.
|
|
280
|
+
*/
|
|
281
|
+
const controlDag = (model) => buildControl(model, new Set(loopTransitions(model)));
|
|
282
|
+
/**
|
|
283
|
+
* The cycles of the control graph: every cycle that is not a legal decision loop.
|
|
284
|
+
*
|
|
285
|
+
* A cycle that passes through a `decision` element is a legal loop and is not reported; the loop edge that closes it is left out of `controlDag`. Any cycle that remains has no decision in it, so it can never make progress, and is reported with its path.
|
|
286
|
+
*
|
|
287
|
+
* A path follows `graph-dag`'s convention: each element needs the next one and the last needs the first, which is repeated at the end. `["a", "b", "a"]` means a needs b and b needs a. One path is reported per strongly connected component; break it and call again for the rest.
|
|
288
|
+
* @param model - The model to read.
|
|
289
|
+
* @returns The illegal cycles, empty exactly when every cycle is a legal decision loop.
|
|
290
|
+
* @throws UnknownNodeError when a decision's transition targets something that is not an element.
|
|
291
|
+
*/
|
|
292
|
+
const illegalCycles = (model) => findCycles(controlDag(model));
|
|
293
|
+
//#endregion
|
|
294
|
+
export { findCycles as a, descendantsOf as c, lookup as d, CycleError as f, assertAcyclic as i, assembleDag as l, illegalCycles as n, stronglyConnectedComponents as o, UnknownNodeError as p, loopTransitions as r, ancestorsOf as s, controlDag as t, buildDag as u };
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { t as statuses } from "./status-Bm1CGu9C.js";
|
|
2
|
+
import { useId } from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
//#region ../../ui/graph/react/dist/labels.js
|
|
5
|
+
/** The text each status is shown with, so every component names a status the same way. */
|
|
6
|
+
const statusLabels = {
|
|
7
|
+
done: "Done",
|
|
8
|
+
ready: "Ready",
|
|
9
|
+
blocked: "Blocked",
|
|
10
|
+
skipped: "Skipped",
|
|
11
|
+
unknown: "Unknown"
|
|
12
|
+
};
|
|
13
|
+
/** One sentence per status rule, saying why an element has the status that rule decided. */
|
|
14
|
+
const ruleDescriptions = {
|
|
15
|
+
"overlay-done": "The run reports it done.",
|
|
16
|
+
"overlay-unknown": "The run reports its state as unknown.",
|
|
17
|
+
"unknown-need": "Something it needs has an unknown state or an undecided condition.",
|
|
18
|
+
"all-cut": "Every path into it was cut, so it will not run.",
|
|
19
|
+
"needs-met": "Everything it needs is done.",
|
|
20
|
+
waiting: "It is still waiting on unfinished work."
|
|
21
|
+
};
|
|
22
|
+
/** The labels the projections give pseudo-states, which carry no label of their own. */
|
|
23
|
+
const pseudoLabels = {
|
|
24
|
+
initial: "Start",
|
|
25
|
+
final: "End",
|
|
26
|
+
fork: "Fork",
|
|
27
|
+
join: "Join",
|
|
28
|
+
history: "History"
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The text an element is shown with: its own label, or for a pseudo-state the name of its role.
|
|
32
|
+
* @param element - The element to name.
|
|
33
|
+
* @returns The element's display label.
|
|
34
|
+
*/
|
|
35
|
+
const elementLabel = (element) => element.kind === "pseudo" ? pseudoLabels[element.role] : element.label;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region ../../ui/graph/react/dist/status-badge.js
|
|
38
|
+
/**
|
|
39
|
+
* A status as text, carrying the status in `data-status` so a stylesheet can colour it.
|
|
40
|
+
* @param props - The status and an optional class.
|
|
41
|
+
* @returns A `span` naming the status.
|
|
42
|
+
*/
|
|
43
|
+
const StatusBadge = (props) => {
|
|
44
|
+
const { status, className } = props;
|
|
45
|
+
return jsx("span", {
|
|
46
|
+
className,
|
|
47
|
+
"data-status": status,
|
|
48
|
+
children: statusLabels[status]
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region ../../ui/graph/react/dist/element-card.js
|
|
53
|
+
/**
|
|
54
|
+
* One element: its label, its status, whether it is on the critical path, and what it is blocked by. The root carries `data-element-id`, `data-kind`, `data-status` and, on the critical path, `data-critical`.
|
|
55
|
+
* @param props - The element, its status and what to show beside it.
|
|
56
|
+
* @returns The card.
|
|
57
|
+
*/
|
|
58
|
+
const ElementCard = (props) => {
|
|
59
|
+
const { element, status, critical = false, blockedBy = [], item, classNames = {} } = props;
|
|
60
|
+
return jsxs("div", {
|
|
61
|
+
...item,
|
|
62
|
+
className: classNames.root,
|
|
63
|
+
"data-element-id": element.id,
|
|
64
|
+
"data-kind": element.kind,
|
|
65
|
+
"data-status": status,
|
|
66
|
+
"data-critical": critical ? "" : void 0,
|
|
67
|
+
children: [
|
|
68
|
+
jsx("span", {
|
|
69
|
+
className: classNames.label,
|
|
70
|
+
"data-part": "label",
|
|
71
|
+
children: elementLabel(element)
|
|
72
|
+
}),
|
|
73
|
+
jsx(StatusBadge, {
|
|
74
|
+
status,
|
|
75
|
+
className: classNames.status
|
|
76
|
+
}),
|
|
77
|
+
critical ? jsx("span", {
|
|
78
|
+
className: classNames.critical,
|
|
79
|
+
"data-part": "critical",
|
|
80
|
+
children: "Critical path"
|
|
81
|
+
}) : null,
|
|
82
|
+
blockedBy.length > 0 ? jsxs("span", {
|
|
83
|
+
className: classNames.blockers,
|
|
84
|
+
"data-part": "blockers",
|
|
85
|
+
children: ["Blocked by ", blockedBy.map((blocker) => blocker.label).join(", ")]
|
|
86
|
+
}) : null
|
|
87
|
+
]
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region ../../ui/graph/react/dist/transition-item.js
|
|
92
|
+
/**
|
|
93
|
+
* One transition as a list item: where it comes from, where it goes and its label. The item carries `data-transition-id` and, as applicable, `data-handoff` and `data-critical`; a hand-off says so in its text.
|
|
94
|
+
* @param props - The transition's ends and label.
|
|
95
|
+
* @returns An `li`, to place in a list.
|
|
96
|
+
*/
|
|
97
|
+
const TransitionItem = (props) => {
|
|
98
|
+
const { id, from, to, label, handoff = false, critical = false, classNames = {} } = props;
|
|
99
|
+
return jsxs("li", {
|
|
100
|
+
className: classNames.root,
|
|
101
|
+
"data-transition-id": id,
|
|
102
|
+
"data-handoff": handoff ? "" : void 0,
|
|
103
|
+
"data-critical": critical ? "" : void 0,
|
|
104
|
+
children: [
|
|
105
|
+
jsx("span", {
|
|
106
|
+
className: classNames.from,
|
|
107
|
+
"data-part": "from",
|
|
108
|
+
children: from
|
|
109
|
+
}),
|
|
110
|
+
" ",
|
|
111
|
+
"to",
|
|
112
|
+
" ",
|
|
113
|
+
jsx("span", {
|
|
114
|
+
className: classNames.to,
|
|
115
|
+
"data-part": "to",
|
|
116
|
+
children: to
|
|
117
|
+
}),
|
|
118
|
+
label === void 0 ? null : jsxs("span", {
|
|
119
|
+
className: classNames.label,
|
|
120
|
+
"data-part": "label",
|
|
121
|
+
children: [
|
|
122
|
+
" ",
|
|
123
|
+
"(",
|
|
124
|
+
label,
|
|
125
|
+
")"
|
|
126
|
+
]
|
|
127
|
+
}),
|
|
128
|
+
handoff ? " (hand-off)" : null
|
|
129
|
+
]
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region ../../ui/graph/react/dist/lane.js
|
|
134
|
+
/**
|
|
135
|
+
* A swimlane: a group named by its heading, holding the work of one participant. The group carries `data-lane` with the participant's id.
|
|
136
|
+
* @param props - The participant, its label and the lane's content.
|
|
137
|
+
* @returns The lane.
|
|
138
|
+
*/
|
|
139
|
+
const Lane = (props) => {
|
|
140
|
+
const { participant, label, children, classNames = {} } = props;
|
|
141
|
+
const heading = useId();
|
|
142
|
+
return jsxs("div", {
|
|
143
|
+
role: "group",
|
|
144
|
+
"aria-labelledby": heading,
|
|
145
|
+
className: classNames.root,
|
|
146
|
+
"data-lane": participant,
|
|
147
|
+
children: [jsx("h3", {
|
|
148
|
+
id: heading,
|
|
149
|
+
className: classNames.heading,
|
|
150
|
+
"data-part": "heading",
|
|
151
|
+
children: label
|
|
152
|
+
}), jsx("div", {
|
|
153
|
+
className: classNames.body,
|
|
154
|
+
"data-part": "body",
|
|
155
|
+
children
|
|
156
|
+
})]
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region ../../ui/graph/react/dist/legend.js
|
|
161
|
+
/**
|
|
162
|
+
* A key to the statuses a view colours: a labelled list with a swatch per status. Each item and its swatch carry `data-status`, or `data-critical` for the critical path entry, so a stylesheet colours the swatch by attribute.
|
|
163
|
+
* @param props - The statuses to list and the classes to give its parts.
|
|
164
|
+
* @returns The legend.
|
|
165
|
+
*/
|
|
166
|
+
const Legend = (props) => {
|
|
167
|
+
const { statuses: shown = statuses, critical = false, classNames = {} } = props;
|
|
168
|
+
return jsxs("ul", {
|
|
169
|
+
"aria-label": "Legend",
|
|
170
|
+
className: classNames.root,
|
|
171
|
+
children: [shown.map((status) => jsxs("li", {
|
|
172
|
+
className: classNames.item,
|
|
173
|
+
"data-status": status,
|
|
174
|
+
children: [jsx("span", {
|
|
175
|
+
"aria-hidden": "true",
|
|
176
|
+
className: classNames.swatch,
|
|
177
|
+
"data-part": "swatch",
|
|
178
|
+
"data-status": status
|
|
179
|
+
}), jsx("span", {
|
|
180
|
+
className: classNames.label,
|
|
181
|
+
"data-part": "label",
|
|
182
|
+
children: statusLabels[status]
|
|
183
|
+
})]
|
|
184
|
+
}, status)), critical ? jsxs("li", {
|
|
185
|
+
className: classNames.item,
|
|
186
|
+
"data-critical": "",
|
|
187
|
+
children: [jsx("span", {
|
|
188
|
+
"aria-hidden": "true",
|
|
189
|
+
className: classNames.swatch,
|
|
190
|
+
"data-part": "swatch",
|
|
191
|
+
"data-critical": ""
|
|
192
|
+
}), jsx("span", {
|
|
193
|
+
className: classNames.label,
|
|
194
|
+
"data-part": "label",
|
|
195
|
+
children: "Critical path"
|
|
196
|
+
})]
|
|
197
|
+
}) : null]
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region ../../ui/graph/react/dist/filter-panel.js
|
|
202
|
+
const Group = ({ name, legend, options, checked, onToggle, classNames }) => options.length === 0 ? null : jsxs("fieldset", {
|
|
203
|
+
className: classNames.group,
|
|
204
|
+
"data-filter": name,
|
|
205
|
+
children: [jsx("legend", {
|
|
206
|
+
className: classNames.legend,
|
|
207
|
+
children: legend
|
|
208
|
+
}), options.map((option) => jsxs("label", {
|
|
209
|
+
className: classNames.option,
|
|
210
|
+
children: [jsx("input", {
|
|
211
|
+
type: "checkbox",
|
|
212
|
+
className: classNames.checkbox,
|
|
213
|
+
value: option.value,
|
|
214
|
+
checked: checked.has(option.value),
|
|
215
|
+
onChange: () => {
|
|
216
|
+
onToggle(option.value);
|
|
217
|
+
}
|
|
218
|
+
}), option.label]
|
|
219
|
+
}, option.value))]
|
|
220
|
+
});
|
|
221
|
+
/**
|
|
222
|
+
* Checkboxes for filtering a view by status, actor and source, and a button that clears them. Each group is a `fieldset` carrying `data-filter` with `status`, `actor` or `source`.
|
|
223
|
+
* @param props - The current filter, what to offer, and the handlers that change it.
|
|
224
|
+
* @returns A search form.
|
|
225
|
+
*/
|
|
226
|
+
const FilterPanel = (props) => {
|
|
227
|
+
const { filter, options, statuses: offered = statuses, onToggleStatus, onToggleActor, onToggleSource, onClear, classNames = {} } = props;
|
|
228
|
+
return jsxs("form", {
|
|
229
|
+
role: "search",
|
|
230
|
+
"aria-label": "Filter elements",
|
|
231
|
+
className: classNames.root,
|
|
232
|
+
onSubmit: (event) => {
|
|
233
|
+
event.preventDefault();
|
|
234
|
+
},
|
|
235
|
+
children: [
|
|
236
|
+
jsx(Group, {
|
|
237
|
+
name: "status",
|
|
238
|
+
legend: "Status",
|
|
239
|
+
options: offered.map((status) => ({
|
|
240
|
+
value: status,
|
|
241
|
+
label: statusLabels[status]
|
|
242
|
+
})),
|
|
243
|
+
checked: filter.statuses,
|
|
244
|
+
onToggle: onToggleStatus,
|
|
245
|
+
classNames
|
|
246
|
+
}),
|
|
247
|
+
jsx(Group, {
|
|
248
|
+
name: "actor",
|
|
249
|
+
legend: "Actor",
|
|
250
|
+
options: options.actors,
|
|
251
|
+
checked: filter.actors,
|
|
252
|
+
onToggle: onToggleActor,
|
|
253
|
+
classNames
|
|
254
|
+
}),
|
|
255
|
+
jsx(Group, {
|
|
256
|
+
name: "source",
|
|
257
|
+
legend: "Source",
|
|
258
|
+
options: options.sources,
|
|
259
|
+
checked: filter.sources,
|
|
260
|
+
onToggle: onToggleSource,
|
|
261
|
+
classNames
|
|
262
|
+
}),
|
|
263
|
+
jsx("button", {
|
|
264
|
+
type: "button",
|
|
265
|
+
className: classNames.clear,
|
|
266
|
+
onClick: onClear,
|
|
267
|
+
children: "Clear filters"
|
|
268
|
+
})
|
|
269
|
+
]
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
//#endregion
|
|
273
|
+
//#region ../../ui/graph/react/dist/detail-panel.js
|
|
274
|
+
const Section = ({ name, heading, items, classNames }) => {
|
|
275
|
+
const id = useId();
|
|
276
|
+
return items.length === 0 ? null : jsxs("section", {
|
|
277
|
+
"aria-labelledby": id,
|
|
278
|
+
className: classNames.section,
|
|
279
|
+
"data-section": name,
|
|
280
|
+
children: [jsx("h3", {
|
|
281
|
+
id,
|
|
282
|
+
className: classNames.sectionHeading,
|
|
283
|
+
children: heading
|
|
284
|
+
}), jsx("ul", {
|
|
285
|
+
className: classNames.list,
|
|
286
|
+
children: items.map((item) => jsx("li", {
|
|
287
|
+
"data-element-id": item.id,
|
|
288
|
+
children: item.label
|
|
289
|
+
}, item.id))
|
|
290
|
+
})]
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* The details of one element: its status, the rule that decided it, what it is blocked by and what depends on it. The root is a complementary region named by the element's label; each list is a section carrying `data-section` with `blocked-by` or `impact`, shown only when it is not empty.
|
|
295
|
+
* @param props - The element, its explanation, blockers and impact.
|
|
296
|
+
* @returns The panel.
|
|
297
|
+
*/
|
|
298
|
+
const DetailPanel = (props) => {
|
|
299
|
+
const { element, explanation, blockers, impact, labelOf, classNames = {} } = props;
|
|
300
|
+
const heading = useId();
|
|
301
|
+
const named = (ids) => ids.map((id) => ({
|
|
302
|
+
id,
|
|
303
|
+
label: labelOf(id)
|
|
304
|
+
}));
|
|
305
|
+
return jsxs("aside", {
|
|
306
|
+
"aria-labelledby": heading,
|
|
307
|
+
className: classNames.root,
|
|
308
|
+
"data-element-id": element.id,
|
|
309
|
+
"data-status": explanation.status,
|
|
310
|
+
children: [
|
|
311
|
+
jsx("h2", {
|
|
312
|
+
id: heading,
|
|
313
|
+
className: classNames.heading,
|
|
314
|
+
children: elementLabel(element)
|
|
315
|
+
}),
|
|
316
|
+
jsx(StatusBadge, {
|
|
317
|
+
status: explanation.status,
|
|
318
|
+
className: classNames.status
|
|
319
|
+
}),
|
|
320
|
+
jsx("p", {
|
|
321
|
+
className: classNames.reason,
|
|
322
|
+
"data-rule": explanation.rule,
|
|
323
|
+
children: ruleDescriptions[explanation.rule]
|
|
324
|
+
}),
|
|
325
|
+
jsx(Section, {
|
|
326
|
+
name: "blocked-by",
|
|
327
|
+
heading: "Blocked by",
|
|
328
|
+
items: named(blockers),
|
|
329
|
+
classNames
|
|
330
|
+
}),
|
|
331
|
+
jsx(Section, {
|
|
332
|
+
name: "impact",
|
|
333
|
+
heading: "Impact",
|
|
334
|
+
items: named(impact),
|
|
335
|
+
classNames
|
|
336
|
+
})
|
|
337
|
+
]
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
//#endregion
|
|
341
|
+
export { TransitionItem as a, elementLabel as c, Lane as i, ruleDescriptions as l, FilterPanel as n, ElementCard as o, Legend as r, StatusBadge as s, DetailPanel as t, statusLabels as u };
|