@the-package-labs/idocs-core 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +2 -2
- package/dist/chunk-3QBMUILO.js +127 -0
- package/dist/{chunk-FRVCGXIG.js → chunk-ITBBPZE6.js} +29 -1
- package/dist/{chunk-5BMICZRC.js → chunk-S7M3BRFI.js} +34 -32
- package/dist/compiler.d.ts +11 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +252 -1
- package/dist/nested-layout.d.ts +66 -0
- package/dist/nesting.d.ts +24 -0
- package/dist/node.d.ts +1 -0
- package/dist/node.js +15 -3
- package/dist/outline.d.ts +63 -0
- package/dist/phases.d.ts +16 -0
- package/dist/schema.d.ts +22 -0
- package/dist/suggest.d.ts +1 -0
- package/dist/types.d.ts +18 -0
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
didYouMean
|
|
3
|
+
} from "./chunk-ITBBPZE6.js";
|
|
4
|
+
|
|
5
|
+
// src/nesting.ts
|
|
6
|
+
function nestedDiagrams(all, name) {
|
|
7
|
+
const out = {};
|
|
8
|
+
const queue = [name];
|
|
9
|
+
for (let cur = queue.shift(); cur !== void 0; cur = queue.shift()) {
|
|
10
|
+
for (const node of Object.values(all[cur]?.nodes ?? {})) {
|
|
11
|
+
const next = node.diagram?.name;
|
|
12
|
+
if (next === void 0 || next === name || out[next] || !all[next]) continue;
|
|
13
|
+
out[next] = all[next];
|
|
14
|
+
queue.push(next);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
function attachNested(all) {
|
|
20
|
+
const flat = {};
|
|
21
|
+
for (const [name, diagram] of Object.entries(all)) {
|
|
22
|
+
const { nested: _, ...rest } = diagram;
|
|
23
|
+
flat[name] = rest;
|
|
24
|
+
}
|
|
25
|
+
const out = {};
|
|
26
|
+
for (const [name, diagram] of Object.entries(flat)) {
|
|
27
|
+
const nested = nestedDiagrams(flat, name);
|
|
28
|
+
out[name] = Object.keys(nested).length ? { ...diagram, nested } : diagram;
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
function linkDiagrams(entries) {
|
|
33
|
+
const out = [];
|
|
34
|
+
const names = Object.keys(entries).sort();
|
|
35
|
+
const known = names.filter((n) => entries[n].diagram);
|
|
36
|
+
const report = (from, path, message, hint) => {
|
|
37
|
+
const at = entries[from].where?.(path);
|
|
38
|
+
out.push({
|
|
39
|
+
severity: "error",
|
|
40
|
+
message,
|
|
41
|
+
file: at?.file ?? `diagrams/${from}/diagram.yaml`,
|
|
42
|
+
line: at?.line,
|
|
43
|
+
col: at?.col,
|
|
44
|
+
path: path.join("."),
|
|
45
|
+
hint
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const edges = /* @__PURE__ */ new Map();
|
|
49
|
+
for (const from of names) {
|
|
50
|
+
const diagram = entries[from].diagram;
|
|
51
|
+
if (!diagram) continue;
|
|
52
|
+
for (const node of Object.values(diagram.nodes)) {
|
|
53
|
+
const ref = node.diagram;
|
|
54
|
+
if (!ref) continue;
|
|
55
|
+
const at = ["nodes", node.id, "diagram"];
|
|
56
|
+
const sub = (key) => [...at, key];
|
|
57
|
+
const target = entries[ref.name];
|
|
58
|
+
if (!target) {
|
|
59
|
+
report(
|
|
60
|
+
from,
|
|
61
|
+
at,
|
|
62
|
+
`Node "${node.id}" opens diagram "${ref.name}", which does not exist`,
|
|
63
|
+
didYouMean(ref.name, names) ?? (names.length > 1 ? `Diagrams: ${names.filter((n) => n !== from).join(", ")}` : void 0)
|
|
64
|
+
);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!target.diagram) continue;
|
|
68
|
+
edges.set(from, (edges.get(from) ?? /* @__PURE__ */ new Set()).add(ref.name));
|
|
69
|
+
const views = target.diagram.views.map((v) => v.id);
|
|
70
|
+
const scenario = ref.scenario ? target.diagram.scenarios.find((s) => s.id === ref.scenario) : void 0;
|
|
71
|
+
if (ref.view && !views.includes(ref.view)) {
|
|
72
|
+
report(
|
|
73
|
+
from,
|
|
74
|
+
sub("view"),
|
|
75
|
+
`Diagram "${ref.name}" has no view "${ref.view}"`,
|
|
76
|
+
didYouMean(ref.view, views) ?? `Views: ${views.join(", ")}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (ref.scenario && !scenario) {
|
|
80
|
+
const ids = target.diagram.scenarios.map((s) => s.id);
|
|
81
|
+
report(
|
|
82
|
+
from,
|
|
83
|
+
sub("scenario"),
|
|
84
|
+
`Diagram "${ref.name}" has no scenario "${ref.scenario}"`,
|
|
85
|
+
didYouMean(ref.scenario, ids) ?? (ids.length ? `Scenarios: ${ids.join(", ")}` : "It has no scenarios.")
|
|
86
|
+
);
|
|
87
|
+
} else if (scenario && ref.view && scenario.view !== ref.view) {
|
|
88
|
+
report(
|
|
89
|
+
from,
|
|
90
|
+
sub("scenario"),
|
|
91
|
+
`Scenario "${scenario.id}" plays on view "${scenario.view}", not "${ref.view}"`,
|
|
92
|
+
"Drop `view`, and the diagram opens on the scenario's own view."
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const state = /* @__PURE__ */ new Map();
|
|
98
|
+
const stack = [];
|
|
99
|
+
const visit = (name) => {
|
|
100
|
+
state.set(name, "open");
|
|
101
|
+
stack.push(name);
|
|
102
|
+
for (const next of edges.get(name) ?? /* @__PURE__ */ new Set()) {
|
|
103
|
+
if (state.get(next) === "open") {
|
|
104
|
+
const circle = [...stack.slice(stack.indexOf(next)), next];
|
|
105
|
+
const node = Object.values(entries[name].diagram.nodes).find(
|
|
106
|
+
(n) => n.diagram?.name === next
|
|
107
|
+
);
|
|
108
|
+
report(
|
|
109
|
+
name,
|
|
110
|
+
["nodes", node.id, "diagram"],
|
|
111
|
+
next === name ? `Node "${node.id}" opens its own diagram "${name}"` : `Node "${node.id}" opens "${next}", which leads back to "${name}"`,
|
|
112
|
+
`A diagram cannot be inside itself: ${circle.join(" \u2192 ")}`
|
|
113
|
+
);
|
|
114
|
+
} else if (!state.has(next)) visit(next);
|
|
115
|
+
}
|
|
116
|
+
stack.pop();
|
|
117
|
+
state.set(name, "done");
|
|
118
|
+
};
|
|
119
|
+
for (const name of known) if (!state.has(name)) visit(name);
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export {
|
|
124
|
+
nestedDiagrams,
|
|
125
|
+
attachNested,
|
|
126
|
+
linkDiagrams
|
|
127
|
+
};
|
|
@@ -104,6 +104,33 @@ function letterKey(index) {
|
|
|
104
104
|
return out;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// src/suggest.ts
|
|
108
|
+
function didYouMean(word, options) {
|
|
109
|
+
let best;
|
|
110
|
+
let bestDist = Math.max(2, Math.floor(word.length / 3)) + 1;
|
|
111
|
+
for (const o of options) {
|
|
112
|
+
const d = levenshtein(word.toLowerCase(), o.toLowerCase());
|
|
113
|
+
if (d < bestDist) {
|
|
114
|
+
best = o;
|
|
115
|
+
bestDist = d;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return best ? `Did you mean "${best}"?` : void 0;
|
|
119
|
+
}
|
|
120
|
+
function levenshtein(a, b) {
|
|
121
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
122
|
+
for (let i = 1; i <= a.length; i++) {
|
|
123
|
+
let prev = row[0];
|
|
124
|
+
row[0] = i;
|
|
125
|
+
for (let j = 1; j <= b.length; j++) {
|
|
126
|
+
const tmp = row[j];
|
|
127
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
128
|
+
prev = tmp;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return row[b.length];
|
|
132
|
+
}
|
|
133
|
+
|
|
107
134
|
export {
|
|
108
135
|
BUILTIN_KINDS,
|
|
109
136
|
BUILTIN_EDGE_KINDS,
|
|
@@ -113,5 +140,6 @@ export {
|
|
|
113
140
|
dist,
|
|
114
141
|
pointAt,
|
|
115
142
|
roundedPath,
|
|
116
|
-
letterKey
|
|
143
|
+
letterKey,
|
|
144
|
+
didYouMean
|
|
117
145
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BUILTIN_EDGE_KINDS,
|
|
3
3
|
BUILTIN_KINDS,
|
|
4
|
+
didYouMean,
|
|
4
5
|
letterKey
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ITBBPZE6.js";
|
|
6
7
|
|
|
7
8
|
// src/icon-resolver.ts
|
|
8
9
|
import { getIconData, iconToSVG } from "@iconify/utils";
|
|
@@ -231,7 +232,23 @@ var NodeSchema = z.strictObject({
|
|
|
231
232
|
doc: z.string().optional().describe("Markdown file for the drawer. Defaults to nodes/<id>.md when present."),
|
|
232
233
|
links: z.array(z.strictObject({ label: z.string(), url: z.string() })).optional(),
|
|
233
234
|
refs: z.array(z.string()).optional().describe("Source files or URLs this node is based on."),
|
|
234
|
-
tags: z.array(z.string()).optional()
|
|
235
|
+
tags: z.array(z.string()).optional(),
|
|
236
|
+
diagram: z.union([
|
|
237
|
+
z.string().min(1),
|
|
238
|
+
z.strictObject({
|
|
239
|
+
name: z.string().min(1).describe("Folder name of the diagram, under diagrams/."),
|
|
240
|
+
view: Id.optional().describe(
|
|
241
|
+
"View to open first. Defaults to the scenario view, then the first."
|
|
242
|
+
),
|
|
243
|
+
scenario: Id.optional().describe("Scenario to select when the diagram opens."),
|
|
244
|
+
mode: z.enum(["both", "expand", "open"]).optional().describe(
|
|
245
|
+
"both (default): the reader can expand it in place or open it. expand: only in place. open: only move into it."
|
|
246
|
+
),
|
|
247
|
+
expanded: z.boolean().optional().describe("Start expanded in place, so the whole inner diagram shows as a group.")
|
|
248
|
+
})
|
|
249
|
+
]).optional().describe(
|
|
250
|
+
"Another diagram (its folder name) that shows the inside of this node. The card gets buttons to expand it in place, as a group, or to open it one level deeper, with a way back."
|
|
251
|
+
)
|
|
235
252
|
});
|
|
236
253
|
var GroupSchema = z.strictObject({
|
|
237
254
|
label: z.string(),
|
|
@@ -449,15 +466,17 @@ function compileDiagram(src, assets) {
|
|
|
449
466
|
scenarios[f.id] = body;
|
|
450
467
|
scenarioSources.set(f.id, s);
|
|
451
468
|
}
|
|
452
|
-
const
|
|
469
|
+
const where = (path) => {
|
|
453
470
|
let source = main;
|
|
454
471
|
let p = path;
|
|
455
472
|
if (path[0] === "scenarios" && typeof path[1] === "string" && scenarioSources.has(path[1])) {
|
|
456
473
|
source = scenarioSources.get(path[1]);
|
|
457
474
|
p = path.slice(2);
|
|
458
475
|
}
|
|
459
|
-
|
|
460
|
-
|
|
476
|
+
return { file: source.file, ...locate(source, p) };
|
|
477
|
+
};
|
|
478
|
+
const report = (severity, message, path, hint) => {
|
|
479
|
+
diagnostics.push({ severity, message, ...where(path), path: path.join("."), hint });
|
|
461
480
|
};
|
|
462
481
|
const error = (message, path, hint) => report("error", message, path, hint);
|
|
463
482
|
const warn = (message, path, hint) => report("warning", message, path, hint);
|
|
@@ -473,7 +492,7 @@ function compileDiagram(src, assets) {
|
|
|
473
492
|
}
|
|
474
493
|
const diagram = build(parsed.data, src, error, warn, assets);
|
|
475
494
|
if (diagnostics.some((d) => d.severity === "error")) return { diagnostics };
|
|
476
|
-
return { diagram, diagnostics };
|
|
495
|
+
return Object.defineProperty({ diagram, diagnostics }, "where", { value: where });
|
|
477
496
|
}
|
|
478
497
|
function build(data, src, error, warn, { resolveIcon }) {
|
|
479
498
|
const kinds = { ...BUILTIN_KINDS };
|
|
@@ -586,6 +605,13 @@ function build(data, src, error, warn, { resolveIcon }) {
|
|
|
586
605
|
didYouMean(n.in, Object.keys(groups))
|
|
587
606
|
);
|
|
588
607
|
}
|
|
608
|
+
if (typeof n.diagram === "object" && n.diagram.expanded && n.diagram.mode === "open") {
|
|
609
|
+
warn(
|
|
610
|
+
"`expanded` has no effect with `mode: open`",
|
|
611
|
+
["nodes", id, "diagram", "expanded"],
|
|
612
|
+
"Use `mode: expand` or `mode: both`, or drop `expanded`."
|
|
613
|
+
);
|
|
614
|
+
}
|
|
589
615
|
const docPath = n.doc ?? `nodes/${id}.md`;
|
|
590
616
|
const docText = src.readFile?.(docPath);
|
|
591
617
|
if (n.doc && docText === void 0)
|
|
@@ -608,7 +634,8 @@ function build(data, src, error, warn, { resolveIcon }) {
|
|
|
608
634
|
docHtml: docHtml || void 0,
|
|
609
635
|
links: n.links ?? [],
|
|
610
636
|
refs: n.refs ?? [],
|
|
611
|
-
tags: n.tags ?? []
|
|
637
|
+
tags: n.tags ?? [],
|
|
638
|
+
diagram: typeof n.diagram === "string" ? { name: n.diagram } : n.diagram
|
|
612
639
|
};
|
|
613
640
|
}
|
|
614
641
|
const edges = {};
|
|
@@ -1031,31 +1058,6 @@ function allowedKeysFor(path) {
|
|
|
1031
1058
|
return void 0;
|
|
1032
1059
|
}
|
|
1033
1060
|
}
|
|
1034
|
-
function didYouMean(word, options) {
|
|
1035
|
-
let best;
|
|
1036
|
-
let bestDist = Math.max(2, Math.floor(word.length / 3)) + 1;
|
|
1037
|
-
for (const o of options) {
|
|
1038
|
-
const d = levenshtein(word.toLowerCase(), o.toLowerCase());
|
|
1039
|
-
if (d < bestDist) {
|
|
1040
|
-
best = o;
|
|
1041
|
-
bestDist = d;
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
return best ? `Did you mean "${best}"?` : void 0;
|
|
1045
|
-
}
|
|
1046
|
-
function levenshtein(a, b) {
|
|
1047
|
-
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
1048
|
-
for (let i = 1; i <= a.length; i++) {
|
|
1049
|
-
let prev = row[0];
|
|
1050
|
-
row[0] = i;
|
|
1051
|
-
for (let j = 1; j <= b.length; j++) {
|
|
1052
|
-
const tmp = row[j];
|
|
1053
|
-
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
1054
|
-
prev = tmp;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
return row[b.length];
|
|
1058
|
-
}
|
|
1059
1061
|
|
|
1060
1062
|
export {
|
|
1061
1063
|
createIconResolver,
|
package/dist/compiler.d.ts
CHANGED
|
@@ -23,9 +23,20 @@ export interface DiagramSource {
|
|
|
23
23
|
/** Read a file relative to the diagram directory (docs, local icons). Undefined when missing. */
|
|
24
24
|
readFile?: (relPath: string) => string | undefined;
|
|
25
25
|
}
|
|
26
|
+
/** Where a dotted path of the diagram lives in the files it was compiled from. */
|
|
27
|
+
export type Where = (path: (string | number)[]) => {
|
|
28
|
+
file: string;
|
|
29
|
+
line?: number;
|
|
30
|
+
col?: number;
|
|
31
|
+
};
|
|
26
32
|
export interface CompileResult {
|
|
27
33
|
diagram?: CompiledDiagram;
|
|
28
34
|
diagnostics: Diagnostic[];
|
|
35
|
+
/**
|
|
36
|
+
* Lets a later pass (linking diagrams to each other) report at a line. A closure over the parsed
|
|
37
|
+
* files, so it is not enumerable: spreading, cloning or serialising a result leaves it behind.
|
|
38
|
+
*/
|
|
39
|
+
readonly where?: Where;
|
|
29
40
|
}
|
|
30
41
|
export interface CompilerAssets {
|
|
31
42
|
resolveIcon: (spec: string) => IconResult;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export * from './defaults';
|
|
2
2
|
export * from './geometry';
|
|
3
3
|
export * from './layout';
|
|
4
|
+
export * from './nested-layout';
|
|
5
|
+
export * from './nesting';
|
|
6
|
+
export * from './outline';
|
|
7
|
+
export * from './phases';
|
|
4
8
|
export * from './route';
|
|
5
9
|
export * from './story';
|
|
6
10
|
export * from './svg';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachNested,
|
|
3
|
+
linkDiagrams,
|
|
4
|
+
nestedDiagrams
|
|
5
|
+
} from "./chunk-3QBMUILO.js";
|
|
1
6
|
import {
|
|
2
7
|
BUILTIN_EDGE_KINDS,
|
|
3
8
|
BUILTIN_KINDS,
|
|
@@ -8,7 +13,7 @@ import {
|
|
|
8
13
|
pointAt,
|
|
9
14
|
polylineLength,
|
|
10
15
|
roundedPath
|
|
11
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-ITBBPZE6.js";
|
|
12
17
|
|
|
13
18
|
// src/layout.ts
|
|
14
19
|
import ELK from "elkjs/lib/elk.bundled.js";
|
|
@@ -951,6 +956,236 @@ function overlap(a, b, pad) {
|
|
|
951
956
|
return a.x < b.x + b.w + pad && a.x + a.w + pad > b.x && a.y < b.y + b.h + pad && a.y + a.h + pad > b.y;
|
|
952
957
|
}
|
|
953
958
|
|
|
959
|
+
// src/nested-layout.ts
|
|
960
|
+
var INLINE_PAD = { top: 40, left: 4, right: 4, bottom: 4 };
|
|
961
|
+
function openingView(diagram, ref) {
|
|
962
|
+
const has = (id) => diagram.views.some((v) => v.id === id);
|
|
963
|
+
if (has(ref.view)) return ref.view;
|
|
964
|
+
const fromScenario = diagram.scenarios.find((s) => s.id === ref.scenario)?.view;
|
|
965
|
+
return has(fromScenario) ? fromScenario : diagram.views[0].id;
|
|
966
|
+
}
|
|
967
|
+
var canExpand = (ref, diagrams) => !!ref && ref.mode !== "open" && !!diagrams[ref.name];
|
|
968
|
+
var canOpen = (ref, diagrams) => !!ref && ref.mode !== "expand" && !!diagrams[ref.name];
|
|
969
|
+
var pathJoin = (prefix, id) => `${prefix}${id}`;
|
|
970
|
+
function defaultExpanded(diagram, diagrams, prefix = "", seen = []) {
|
|
971
|
+
const out = [];
|
|
972
|
+
for (const node of Object.values(diagram.nodes)) {
|
|
973
|
+
const ref = node.diagram;
|
|
974
|
+
const child = ref && diagrams[ref.name];
|
|
975
|
+
if (!ref || !child || seen.includes(ref.name) || !ref.expanded || !canExpand(ref, diagrams))
|
|
976
|
+
continue;
|
|
977
|
+
const path = pathJoin(prefix, node.id);
|
|
978
|
+
out.push(path, ...defaultExpanded(child, diagrams, `${path}/`, [...seen, diagram.name]));
|
|
979
|
+
}
|
|
980
|
+
return out;
|
|
981
|
+
}
|
|
982
|
+
function measurable(diagram, viewId, expanded, diagrams, prefix = "") {
|
|
983
|
+
const view = diagram.views.find((v) => v.id === viewId);
|
|
984
|
+
if (!view) return [];
|
|
985
|
+
const out = [];
|
|
986
|
+
for (const id of view.nodeIds) {
|
|
987
|
+
const node = diagram.nodes[id];
|
|
988
|
+
const path = pathJoin(prefix, id);
|
|
989
|
+
out.push({ key: path, node });
|
|
990
|
+
const ref = node.diagram;
|
|
991
|
+
const child = ref && expanded.has(path) && canExpand(ref, diagrams) ? diagrams[ref.name] : void 0;
|
|
992
|
+
if (ref && child) {
|
|
993
|
+
out.push(...measurable(child, openingView(child, ref), expanded, diagrams, `${path}/`));
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return out;
|
|
997
|
+
}
|
|
998
|
+
async function layoutNested(diagram, viewId, { sizes = {}, expanded, diagrams = {}, prefix = "" } = {}) {
|
|
999
|
+
const view = diagram.views.find((v) => v.id === viewId);
|
|
1000
|
+
if (!view) throw new Error(`Unknown view "${viewId}"`);
|
|
1001
|
+
const own = {};
|
|
1002
|
+
for (const id of view.nodeIds) {
|
|
1003
|
+
const size = sizes[pathJoin(prefix, id)];
|
|
1004
|
+
if (size) own[id] = size;
|
|
1005
|
+
}
|
|
1006
|
+
const inner = {};
|
|
1007
|
+
await Promise.all(
|
|
1008
|
+
view.nodeIds.map(async (id) => {
|
|
1009
|
+
const ref = diagram.nodes[id].diagram;
|
|
1010
|
+
const path = pathJoin(prefix, id);
|
|
1011
|
+
const child = ref && expanded?.has(path) && canExpand(ref, diagrams) ? diagrams[ref.name] : void 0;
|
|
1012
|
+
if (!ref || !child) return;
|
|
1013
|
+
const childView = openingView(child, ref);
|
|
1014
|
+
const layout = await layoutNested(child, childView, {
|
|
1015
|
+
sizes,
|
|
1016
|
+
expanded,
|
|
1017
|
+
diagrams,
|
|
1018
|
+
prefix: `${path}/`
|
|
1019
|
+
});
|
|
1020
|
+
inner[id] = {
|
|
1021
|
+
name: ref.name,
|
|
1022
|
+
viewId: childView,
|
|
1023
|
+
offset: { x: INLINE_PAD.left, y: INLINE_PAD.top },
|
|
1024
|
+
layout
|
|
1025
|
+
};
|
|
1026
|
+
own[id] = {
|
|
1027
|
+
w: layout.layout.width + INLINE_PAD.left + INLINE_PAD.right,
|
|
1028
|
+
h: layout.layout.height + INLINE_PAD.top + INLINE_PAD.bottom
|
|
1029
|
+
};
|
|
1030
|
+
})
|
|
1031
|
+
);
|
|
1032
|
+
return { layout: await layoutView(diagram, viewId, own), inner };
|
|
1033
|
+
}
|
|
1034
|
+
function rectOfPath(nested, path) {
|
|
1035
|
+
let level = nested;
|
|
1036
|
+
let at = { x: 0, y: 0 };
|
|
1037
|
+
const ids = path.split("/");
|
|
1038
|
+
for (const [i, id] of ids.entries()) {
|
|
1039
|
+
const r = level?.layout.nodes[id];
|
|
1040
|
+
if (!level || !r) return void 0;
|
|
1041
|
+
if (i === ids.length - 1) return { x: at.x + r.x, y: at.y + r.y, w: r.w, h: r.h };
|
|
1042
|
+
const inner = level.inner[id];
|
|
1043
|
+
if (!inner) return void 0;
|
|
1044
|
+
at = { x: at.x + r.x + inner.offset.x, y: at.y + r.y + inner.offset.y };
|
|
1045
|
+
level = inner.layout;
|
|
1046
|
+
}
|
|
1047
|
+
return void 0;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// src/outline.ts
|
|
1051
|
+
function buildOutline(diagram, view, { diagrams = {}, expanded = /* @__PURE__ */ new Set(), prefix = "", seen = [] } = {}) {
|
|
1052
|
+
const inView = new Set(view.nodeIds);
|
|
1053
|
+
const groups = new Set(view.groupIds);
|
|
1054
|
+
const drawn = view.edgeIds.map((id) => diagram.edges[id]).filter((e) => !e.hidden);
|
|
1055
|
+
const connectionsOf = (id) => {
|
|
1056
|
+
const out = [];
|
|
1057
|
+
for (const e of drawn) {
|
|
1058
|
+
const sends = e.from === id;
|
|
1059
|
+
const gets = e.to === id;
|
|
1060
|
+
if (!sends && !gets) continue;
|
|
1061
|
+
const other = sends ? e.to : e.from;
|
|
1062
|
+
out.push({
|
|
1063
|
+
edge: e.id,
|
|
1064
|
+
dir: e.both ? "both" : sends ? "out" : "in",
|
|
1065
|
+
other,
|
|
1066
|
+
otherTitle: diagram.nodes[other]?.title ?? other,
|
|
1067
|
+
label: e.label,
|
|
1068
|
+
kind: e.kind,
|
|
1069
|
+
status: e.status
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
const rank = { out: 0, both: 1, in: 2 };
|
|
1073
|
+
return out.sort((a, b) => rank[a.dir] - rank[b.dir]);
|
|
1074
|
+
};
|
|
1075
|
+
const nodeItem = (id) => {
|
|
1076
|
+
const node = diagram.nodes[id];
|
|
1077
|
+
const path = pathJoin(prefix, id);
|
|
1078
|
+
const item = {
|
|
1079
|
+
type: "node",
|
|
1080
|
+
path,
|
|
1081
|
+
id,
|
|
1082
|
+
title: node.title,
|
|
1083
|
+
sub: node.sub,
|
|
1084
|
+
icon: node.icon,
|
|
1085
|
+
status: node.status,
|
|
1086
|
+
connections: connectionsOf(id)
|
|
1087
|
+
};
|
|
1088
|
+
const ref = node.diagram;
|
|
1089
|
+
const child = ref && diagrams[ref.name];
|
|
1090
|
+
if (ref && child && !seen.includes(ref.name) && (canExpand(ref, diagrams) || canOpen(ref, diagrams))) {
|
|
1091
|
+
const shown = canExpand(ref, diagrams) && expanded.has(path);
|
|
1092
|
+
const childView = child.views.find((v) => v.id === openingView(child, ref));
|
|
1093
|
+
item.inside = {
|
|
1094
|
+
title: child.title,
|
|
1095
|
+
expand: canExpand(ref, diagrams),
|
|
1096
|
+
open: canOpen(ref, diagrams),
|
|
1097
|
+
expanded: shown,
|
|
1098
|
+
items: shown && childView ? buildOutline(child, childView, {
|
|
1099
|
+
diagrams,
|
|
1100
|
+
expanded,
|
|
1101
|
+
prefix: `${path}/`,
|
|
1102
|
+
seen: [...seen, diagram.name]
|
|
1103
|
+
}) : []
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
return item;
|
|
1107
|
+
};
|
|
1108
|
+
const childGroups = /* @__PURE__ */ new Map();
|
|
1109
|
+
for (const gid of view.groupIds) {
|
|
1110
|
+
const parent = diagram.groups[gid]?.parent;
|
|
1111
|
+
const key = parent && groups.has(parent) ? parent : "";
|
|
1112
|
+
childGroups.set(key, [...childGroups.get(key) ?? [], gid]);
|
|
1113
|
+
}
|
|
1114
|
+
const childNodes = /* @__PURE__ */ new Map();
|
|
1115
|
+
for (const id of view.nodeIds) {
|
|
1116
|
+
if (!inView.has(id)) continue;
|
|
1117
|
+
const group = diagram.nodes[id].group;
|
|
1118
|
+
const key = group && groups.has(group) ? group : "";
|
|
1119
|
+
childNodes.set(key, [...childNodes.get(key) ?? [], id]);
|
|
1120
|
+
}
|
|
1121
|
+
const position = new Map(view.nodeIds.map((id, i) => [id, i]));
|
|
1122
|
+
const firstNode = (gid) => Math.min(
|
|
1123
|
+
...(childNodes.get(gid) ?? []).map((n) => position.get(n)),
|
|
1124
|
+
...(childGroups.get(gid) ?? []).map(firstNode)
|
|
1125
|
+
);
|
|
1126
|
+
const groupItem = (gid) => {
|
|
1127
|
+
const g = diagram.groups[gid];
|
|
1128
|
+
const items = fill(gid);
|
|
1129
|
+
return {
|
|
1130
|
+
type: "group",
|
|
1131
|
+
path: pathJoin(prefix, `#${gid}`),
|
|
1132
|
+
id: gid,
|
|
1133
|
+
title: g.label,
|
|
1134
|
+
caption: g.caption,
|
|
1135
|
+
icon: g.icon,
|
|
1136
|
+
status: g.status,
|
|
1137
|
+
items,
|
|
1138
|
+
count: items.reduce((n, i) => n + (i.type === "group" ? i.count : 1), 0)
|
|
1139
|
+
};
|
|
1140
|
+
};
|
|
1141
|
+
function fill(key) {
|
|
1142
|
+
const ranked = [
|
|
1143
|
+
...(childGroups.get(key) ?? []).map((gid) => ({
|
|
1144
|
+
at: firstNode(gid),
|
|
1145
|
+
make: () => groupItem(gid)
|
|
1146
|
+
})),
|
|
1147
|
+
...(childNodes.get(key) ?? []).map((id) => ({
|
|
1148
|
+
at: position.get(id),
|
|
1149
|
+
make: () => nodeItem(id)
|
|
1150
|
+
}))
|
|
1151
|
+
];
|
|
1152
|
+
return ranked.sort((a, b) => a.at - b.at).map((r) => r.make());
|
|
1153
|
+
}
|
|
1154
|
+
return fill("");
|
|
1155
|
+
}
|
|
1156
|
+
function outlineNodes(items) {
|
|
1157
|
+
const out = [];
|
|
1158
|
+
for (const item of items) {
|
|
1159
|
+
if (item.type === "group") out.push(...outlineNodes(item.items));
|
|
1160
|
+
else out.push(item, ...outlineNodes(item.inside?.items ?? []));
|
|
1161
|
+
}
|
|
1162
|
+
return out;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// src/phases.ts
|
|
1166
|
+
var hasPhases = (scenario) => scenario.phases.some((p) => p.title);
|
|
1167
|
+
function phaseSpans(scenario) {
|
|
1168
|
+
const out = [];
|
|
1169
|
+
scenario.steps.forEach((step, i) => {
|
|
1170
|
+
const last = out[out.length - 1];
|
|
1171
|
+
if (last && last.index === step.phase) {
|
|
1172
|
+
last.to = i;
|
|
1173
|
+
last.count++;
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const phase = scenario.phases[step.phase];
|
|
1177
|
+
out.push({
|
|
1178
|
+
index: step.phase,
|
|
1179
|
+
title: phase?.title ?? "",
|
|
1180
|
+
caption: phase?.caption,
|
|
1181
|
+
from: i,
|
|
1182
|
+
to: i,
|
|
1183
|
+
count: 1
|
|
1184
|
+
});
|
|
1185
|
+
});
|
|
1186
|
+
return out;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
954
1189
|
// src/story.ts
|
|
955
1190
|
function buildStory(diagram, scenario) {
|
|
956
1191
|
const steps = scenario.steps;
|
|
@@ -1250,17 +1485,33 @@ export {
|
|
|
1250
1485
|
BUILTIN_EDGE_KINDS,
|
|
1251
1486
|
BUILTIN_KINDS,
|
|
1252
1487
|
DEFAULT_NODE_WIDTH,
|
|
1488
|
+
INLINE_PAD,
|
|
1253
1489
|
STEP_KIND_COLORS,
|
|
1490
|
+
attachNested,
|
|
1491
|
+
buildOutline,
|
|
1254
1492
|
buildStory,
|
|
1255
1493
|
buildTimeline,
|
|
1494
|
+
canExpand,
|
|
1495
|
+
canOpen,
|
|
1496
|
+
defaultExpanded,
|
|
1256
1497
|
dist,
|
|
1257
1498
|
frameAt,
|
|
1499
|
+
hasPhases,
|
|
1258
1500
|
labelWidth,
|
|
1501
|
+
layoutNested,
|
|
1259
1502
|
layoutView,
|
|
1260
1503
|
letterKey,
|
|
1504
|
+
linkDiagrams,
|
|
1505
|
+
measurable,
|
|
1506
|
+
nestedDiagrams,
|
|
1507
|
+
openingView,
|
|
1508
|
+
outlineNodes,
|
|
1261
1509
|
packGrid,
|
|
1510
|
+
pathJoin,
|
|
1511
|
+
phaseSpans,
|
|
1262
1512
|
pointAt,
|
|
1263
1513
|
polylineLength,
|
|
1514
|
+
rectOfPath,
|
|
1264
1515
|
roundedPath,
|
|
1265
1516
|
routeEdges,
|
|
1266
1517
|
runBounds,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type NodeSize } from './layout';
|
|
2
|
+
import type { CompiledDiagram, CompiledNode, Layout, NodeDiagram, Pt, Rect } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* What an expanded node adds around the inner diagram: the header that names it, and a margin.
|
|
5
|
+
* The inner layout already keeps a margin of its own, so the sides stay thin.
|
|
6
|
+
*/
|
|
7
|
+
export declare const INLINE_PAD: {
|
|
8
|
+
top: number;
|
|
9
|
+
left: number;
|
|
10
|
+
right: number;
|
|
11
|
+
bottom: number;
|
|
12
|
+
};
|
|
13
|
+
/** A diagram laid out together with the diagrams expanded inside it, at any depth. */
|
|
14
|
+
export interface NestedLayout {
|
|
15
|
+
layout: Layout;
|
|
16
|
+
/** By node id: the layout of the diagram that node shows in place. */
|
|
17
|
+
inner: Record<string, InnerLayout>;
|
|
18
|
+
}
|
|
19
|
+
export interface InnerLayout {
|
|
20
|
+
/** Name of the diagram shown. */
|
|
21
|
+
name: string;
|
|
22
|
+
viewId: string;
|
|
23
|
+
/** Where the inner diagram's origin sits inside the expanded node's rectangle. */
|
|
24
|
+
offset: Pt;
|
|
25
|
+
layout: NestedLayout;
|
|
26
|
+
}
|
|
27
|
+
export type Diagrams = Record<string, CompiledDiagram | undefined>;
|
|
28
|
+
/** The view a node's diagram shows when opened: the one named, the scenario's, or the first. */
|
|
29
|
+
export declare function openingView(diagram: CompiledDiagram, ref: NodeDiagram): string;
|
|
30
|
+
/** Whether a reader may show this node's diagram in place: it is at hand and not limited to `open`. */
|
|
31
|
+
export declare const canExpand: (ref: NodeDiagram | undefined, diagrams: Diagrams) => boolean;
|
|
32
|
+
/** Whether a reader may move into this node's diagram: it is at hand and not limited to `expand`. */
|
|
33
|
+
export declare const canOpen: (ref: NodeDiagram | undefined, diagrams: Diagrams) => boolean;
|
|
34
|
+
/** Paths are node ids joined with `/` (ids cannot contain it): `orders`, `orders/stock`. */
|
|
35
|
+
export declare const pathJoin: (prefix: string, id: string) => string;
|
|
36
|
+
/**
|
|
37
|
+
* The paths the author asked to start expanded (`expanded: true`), at every depth. A node counts
|
|
38
|
+
* wherever it sits in its diagram, in whichever view, so switching views loses nothing.
|
|
39
|
+
*/
|
|
40
|
+
export declare function defaultExpanded(diagram: CompiledDiagram, diagrams: Diagrams, prefix?: string, seen?: readonly string[]): string[];
|
|
41
|
+
/**
|
|
42
|
+
* Every node whose card has to be measured to lay `viewId` out with these paths expanded: the
|
|
43
|
+
* view's own, and those of each diagram shown in place. `key` is the path, which is what the
|
|
44
|
+
* layout reads sizes by.
|
|
45
|
+
*/
|
|
46
|
+
export declare function measurable(diagram: CompiledDiagram, viewId: string, expanded: ReadonlySet<string>, diagrams: Diagrams, prefix?: string): {
|
|
47
|
+
key: string;
|
|
48
|
+
node: CompiledNode;
|
|
49
|
+
}[];
|
|
50
|
+
export interface NestedOptions {
|
|
51
|
+
/** Measured card sizes by path. A plain node id for the diagram itself, `orders/api` inside. */
|
|
52
|
+
sizes?: Record<string, Partial<NodeSize>>;
|
|
53
|
+
/** Paths shown in place. */
|
|
54
|
+
expanded?: ReadonlySet<string>;
|
|
55
|
+
/** Where the diagrams a node opens are found, by name. */
|
|
56
|
+
diagrams?: Diagrams;
|
|
57
|
+
prefix?: string;
|
|
58
|
+
}
|
|
59
|
+
/** Lay out `viewId` with the nodes in `expanded` shown in place, each as a group around its diagram. */
|
|
60
|
+
export declare function layoutNested(diagram: CompiledDiagram, viewId: string, { sizes, expanded, diagrams, prefix }?: NestedOptions): Promise<NestedLayout>;
|
|
61
|
+
/**
|
|
62
|
+
* Where a node sits in the world of the diagram laid out first, however deep it is: the rectangle
|
|
63
|
+
* of `orders/stock` is `stock`'s inside `orders`, moved to where `orders` is. Undefined when a
|
|
64
|
+
* step of the path is not laid out (it is not expanded, or not in the view).
|
|
65
|
+
*/
|
|
66
|
+
export declare function rectOfPath(nested: NestedLayout, path: string): Rect | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Diagnostic, Where } from './compiler';
|
|
2
|
+
import type { CompiledDiagram } from './types';
|
|
3
|
+
/** What the linker needs to know about one diagram folder. `diagram` is missing when it failed to compile. */
|
|
4
|
+
export interface LinkEntry {
|
|
5
|
+
diagram?: CompiledDiagram;
|
|
6
|
+
where?: Where;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Every diagram that `name` can reach by opening nodes, directly or through other diagrams, but
|
|
10
|
+
* not `name` itself.
|
|
11
|
+
*/
|
|
12
|
+
export declare function nestedDiagrams(all: Record<string, CompiledDiagram>, name: string): Record<string, CompiledDiagram>;
|
|
13
|
+
/**
|
|
14
|
+
* Give every diagram that opens others the diagrams it can reach, as `nested`, so wherever a
|
|
15
|
+
* compiled diagram goes its insides go with it and the viewer is told nothing. Diagrams that open
|
|
16
|
+
* nothing are returned as they are. Safe to call twice.
|
|
17
|
+
*/
|
|
18
|
+
export declare function attachNested(all: Record<string, CompiledDiagram>): Record<string, CompiledDiagram>;
|
|
19
|
+
/**
|
|
20
|
+
* Check what no single diagram can: that a node opens a diagram which exists, that the view and
|
|
21
|
+
* scenario it asks for exist there, and that diagrams do not open each other in a circle (a node
|
|
22
|
+
* cannot contain itself).
|
|
23
|
+
*/
|
|
24
|
+
export declare function linkDiagrams(entries: Record<string, LinkEntry>): Diagnostic[];
|
package/dist/node.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { CompiledDiagram } from './types';
|
|
|
3
3
|
export * from './compile';
|
|
4
4
|
export { loadIconSets, resolveIcon, searchIcons } from './icons';
|
|
5
5
|
export { renderMarkdown } from './markdown';
|
|
6
|
+
export { attachNested, type LinkEntry, linkDiagrams, nestedDiagrams } from './nesting';
|
|
6
7
|
export { authoringJsonSchema, FamilySchema, KNOWN_KEYS, RootSchema } from './schema';
|
|
7
8
|
export interface LoadedProject {
|
|
8
9
|
diagrams: Record<string, CompiledDiagram>;
|
package/dist/node.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachNested,
|
|
3
|
+
linkDiagrams,
|
|
4
|
+
nestedDiagrams
|
|
5
|
+
} from "./chunk-3QBMUILO.js";
|
|
1
6
|
import {
|
|
2
7
|
FamilySchema,
|
|
3
8
|
KNOWN_KEYS,
|
|
@@ -6,8 +11,8 @@ import {
|
|
|
6
11
|
compileDiagram,
|
|
7
12
|
createIconResolver,
|
|
8
13
|
renderMarkdown
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-S7M3BRFI.js";
|
|
15
|
+
import "./chunk-ITBBPZE6.js";
|
|
11
16
|
|
|
12
17
|
// src/node.ts
|
|
13
18
|
import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
@@ -48,6 +53,7 @@ function loadProject(root, dir = "diagrams") {
|
|
|
48
53
|
const base = diagramsDir(root, dir);
|
|
49
54
|
const result = { diagrams: {}, diagnostics: [], watch: [base] };
|
|
50
55
|
if (!existsSync(base)) return result;
|
|
56
|
+
const entries = {};
|
|
51
57
|
for (const name of readdirSync(base).sort()) {
|
|
52
58
|
const folder = join(base, name);
|
|
53
59
|
if (!statSync(folder).isDirectory()) continue;
|
|
@@ -59,7 +65,7 @@ function loadProject(root, dir = "diagrams") {
|
|
|
59
65
|
file: join(scenarioDir, f),
|
|
60
66
|
text: readFileSync2(join(scenarioDir, f), "utf8")
|
|
61
67
|
})) : [];
|
|
62
|
-
const { diagram, diagnostics } = compileDiagram2({
|
|
68
|
+
const { diagram, diagnostics, where } = compileDiagram2({
|
|
63
69
|
name,
|
|
64
70
|
file: relative(root, file),
|
|
65
71
|
text: readFileSync2(file, "utf8"),
|
|
@@ -68,8 +74,11 @@ function loadProject(root, dir = "diagrams") {
|
|
|
68
74
|
});
|
|
69
75
|
result.diagnostics.push(...diagnostics);
|
|
70
76
|
if (diagram) result.diagrams[name] = diagram;
|
|
77
|
+
entries[name] = { diagram, where };
|
|
71
78
|
result.watch.push(folder);
|
|
72
79
|
}
|
|
80
|
+
result.diagnostics.push(...linkDiagrams(entries));
|
|
81
|
+
result.diagrams = attachNested(result.diagrams);
|
|
73
82
|
return result;
|
|
74
83
|
}
|
|
75
84
|
function readInside(folder, rel) {
|
|
@@ -91,12 +100,15 @@ export {
|
|
|
91
100
|
FamilySchema,
|
|
92
101
|
KNOWN_KEYS,
|
|
93
102
|
RootSchema,
|
|
103
|
+
attachNested,
|
|
94
104
|
authoringJsonSchema,
|
|
95
105
|
compileDiagram2 as compileDiagram,
|
|
96
106
|
diagramsDir,
|
|
97
107
|
formatDiagnostic,
|
|
108
|
+
linkDiagrams,
|
|
98
109
|
loadIconSets,
|
|
99
110
|
loadProject,
|
|
111
|
+
nestedDiagrams,
|
|
100
112
|
renderMarkdown,
|
|
101
113
|
resolveIcon,
|
|
102
114
|
searchIcons
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type Diagrams } from './nested-layout';
|
|
2
|
+
import type { CompiledDiagram, CompiledView, IconData, Status } from './types';
|
|
3
|
+
/** One edge of a node, seen from that node. */
|
|
4
|
+
export interface OutlineConnection {
|
|
5
|
+
edge: string;
|
|
6
|
+
/** `out`: this node sends to `other`; `in`: it receives from `other`; `both`: edge goes either way. */
|
|
7
|
+
dir: 'out' | 'in' | 'both';
|
|
8
|
+
/** Id of the node at the other end, in the same diagram. */
|
|
9
|
+
other: string;
|
|
10
|
+
/** Its title, so a row needs nothing else to be read. */
|
|
11
|
+
otherTitle: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
kind: string;
|
|
14
|
+
status: Status;
|
|
15
|
+
}
|
|
16
|
+
export interface OutlineNode {
|
|
17
|
+
type: 'node';
|
|
18
|
+
/** The node id, or ids joined with `/` for a node inside one shown in place (`orders/stock`). */
|
|
19
|
+
path: string;
|
|
20
|
+
id: string;
|
|
21
|
+
title: string;
|
|
22
|
+
sub?: string;
|
|
23
|
+
icon?: IconData;
|
|
24
|
+
status: Status;
|
|
25
|
+
connections: OutlineConnection[];
|
|
26
|
+
/** Present when the node holds a diagram the reader can reach. */
|
|
27
|
+
inside?: {
|
|
28
|
+
title: string;
|
|
29
|
+
/** The reader may show it in place, and may move into it. */
|
|
30
|
+
expand: boolean;
|
|
31
|
+
open: boolean;
|
|
32
|
+
/** Shown in place right now; `items` are then the nodes of that diagram. */
|
|
33
|
+
expanded: boolean;
|
|
34
|
+
items: OutlineItem[];
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface OutlineGroup {
|
|
38
|
+
type: 'group';
|
|
39
|
+
/** Group id, prefixed like a node path so it is unique across the levels shown in place. */
|
|
40
|
+
path: string;
|
|
41
|
+
id: string;
|
|
42
|
+
title: string;
|
|
43
|
+
caption?: string;
|
|
44
|
+
icon?: IconData;
|
|
45
|
+
status: Status;
|
|
46
|
+
items: OutlineItem[];
|
|
47
|
+
/** How many nodes it holds, however deep. */
|
|
48
|
+
count: number;
|
|
49
|
+
}
|
|
50
|
+
export type OutlineItem = OutlineNode | OutlineGroup;
|
|
51
|
+
export interface OutlineOptions {
|
|
52
|
+
/** Where the diagrams a node holds are found, by name. */
|
|
53
|
+
diagrams?: Diagrams;
|
|
54
|
+
/** Paths shown in place. */
|
|
55
|
+
expanded?: ReadonlySet<string>;
|
|
56
|
+
prefix?: string;
|
|
57
|
+
/** Diagrams already on the way down, so a loop cannot run forever. */
|
|
58
|
+
seen?: readonly string[];
|
|
59
|
+
}
|
|
60
|
+
/** The nodes and groups of `view` as a tree, in the order the author wrote them. */
|
|
61
|
+
export declare function buildOutline(diagram: CompiledDiagram, view: CompiledView, { diagrams, expanded, prefix, seen }?: OutlineOptions): OutlineItem[];
|
|
62
|
+
/** Every node of an outline, in reading order, at any depth. */
|
|
63
|
+
export declare function outlineNodes(items: readonly OutlineItem[]): OutlineNode[];
|
package/dist/phases.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CompiledScenario } from './types';
|
|
2
|
+
export interface PhaseSpan {
|
|
3
|
+
/** Index in `scenario.phases`. */
|
|
4
|
+
index: number;
|
|
5
|
+
title: string;
|
|
6
|
+
caption?: string;
|
|
7
|
+
/** Index of its first and last step in `scenario.steps`. */
|
|
8
|
+
from: number;
|
|
9
|
+
to: number;
|
|
10
|
+
/** How many steps it holds. */
|
|
11
|
+
count: number;
|
|
12
|
+
}
|
|
13
|
+
/** Whether the author named the phases. A scenario written as plain `steps` has one nameless phase. */
|
|
14
|
+
export declare const hasPhases: (scenario: CompiledScenario) => boolean;
|
|
15
|
+
/** The phases that hold steps, in order. Steps follow their phase, so each phase is one run. */
|
|
16
|
+
export declare function phaseSpans(scenario: CompiledScenario): PhaseSpan[];
|
package/dist/schema.d.ts
CHANGED
|
@@ -65,6 +65,17 @@ export declare const NodeSchema: z.ZodObject<{
|
|
|
65
65
|
}, z.core.$strict>>>;
|
|
66
66
|
refs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
67
67
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
68
|
+
diagram: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
69
|
+
name: z.ZodString;
|
|
70
|
+
view: z.ZodOptional<z.ZodString>;
|
|
71
|
+
scenario: z.ZodOptional<z.ZodString>;
|
|
72
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
73
|
+
both: "both";
|
|
74
|
+
expand: "expand";
|
|
75
|
+
open: "open";
|
|
76
|
+
}>>;
|
|
77
|
+
expanded: z.ZodOptional<z.ZodBoolean>;
|
|
78
|
+
}, z.core.$strict>]>>;
|
|
68
79
|
}, z.core.$strict>;
|
|
69
80
|
export declare const GroupSchema: z.ZodObject<{
|
|
70
81
|
label: z.ZodString;
|
|
@@ -521,6 +532,17 @@ export declare const RootSchema: z.ZodObject<{
|
|
|
521
532
|
}, z.core.$strict>>>;
|
|
522
533
|
refs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
523
534
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
535
|
+
diagram: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
536
|
+
name: z.ZodString;
|
|
537
|
+
view: z.ZodOptional<z.ZodString>;
|
|
538
|
+
scenario: z.ZodOptional<z.ZodString>;
|
|
539
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
540
|
+
both: "both";
|
|
541
|
+
expand: "expand";
|
|
542
|
+
open: "open";
|
|
543
|
+
}>>;
|
|
544
|
+
expanded: z.ZodOptional<z.ZodBoolean>;
|
|
545
|
+
}, z.core.$strict>]>>;
|
|
524
546
|
}, z.core.$strict>>;
|
|
525
547
|
edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
526
548
|
id: z.ZodOptional<z.ZodString>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function didYouMean(word: string, options: readonly string[]): string | undefined;
|
package/dist/types.d.ts
CHANGED
|
@@ -19,6 +19,16 @@ export interface NodeLink {
|
|
|
19
19
|
label: string;
|
|
20
20
|
url: string;
|
|
21
21
|
}
|
|
22
|
+
/** Another diagram that shows the inside of a node. Resolved by name against the project's diagrams. */
|
|
23
|
+
export interface NodeDiagram {
|
|
24
|
+
name: string;
|
|
25
|
+
view?: string;
|
|
26
|
+
scenario?: string;
|
|
27
|
+
/** How a reader can get at it: shown in place (`expand`), moved into (`open`), or either. */
|
|
28
|
+
mode?: 'both' | 'expand' | 'open';
|
|
29
|
+
/** Starts shown in place. */
|
|
30
|
+
expanded?: boolean;
|
|
31
|
+
}
|
|
22
32
|
export interface CompiledNode {
|
|
23
33
|
id: string;
|
|
24
34
|
title: string;
|
|
@@ -37,6 +47,8 @@ export interface CompiledNode {
|
|
|
37
47
|
links: NodeLink[];
|
|
38
48
|
refs: string[];
|
|
39
49
|
tags: string[];
|
|
50
|
+
/** Opens one level deeper: the diagram that describes this node's inside. */
|
|
51
|
+
diagram?: NodeDiagram;
|
|
40
52
|
}
|
|
41
53
|
export interface CompiledGroup {
|
|
42
54
|
id: string;
|
|
@@ -145,6 +157,12 @@ export interface CompiledDiagram {
|
|
|
145
157
|
edgeKinds: Record<string, EdgeKindDef>;
|
|
146
158
|
views: CompiledView[];
|
|
147
159
|
scenarios: CompiledScenario[];
|
|
160
|
+
/**
|
|
161
|
+
* The diagrams its nodes open, by name, and the ones those open in turn. Added once every
|
|
162
|
+
* diagram of the project has compiled (`attachNested`), so a diagram is all a viewer needs. The
|
|
163
|
+
* entries themselves carry no `nested`.
|
|
164
|
+
*/
|
|
165
|
+
nested?: Record<string, CompiledDiagram>;
|
|
148
166
|
}
|
|
149
167
|
export interface Pt {
|
|
150
168
|
x: number;
|