@milehimikey/em 1.20.0 → 1.22.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/.claude/agents/em-implementer.md +10 -0
- package/.claude/agents/em-validator.md +1 -0
- package/.claude/skills/event-modeling/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/SKILL.md +7 -4
- package/.claude/skills/event-modeling-conform/reference/conform.md +82 -4
- package/.claude/skills/event-modeling-design/SKILL.md +23 -8
- package/.claude/skills/event-modeling-discover/SKILL.md +2 -2
- package/.claude/skills/event-modeling-discover/reference/extract.md +5 -1
- package/.claude/skills/event-modeling-engagement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-engagement/reference/engagement.md +1 -0
- package/.claude/skills/event-modeling-implement/SKILL.md +5 -3
- package/.claude/skills/event-modeling-implement/reference/implement.md +14 -0
- package/.claude/skills/event-modeling-review/SKILL.md +19 -7
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +54 -9
- package/.claude/skills/event-modeling-shared/reference/methodology.md +35 -2
- package/.claude/skills/event-modeling-shared/reference/operating-principles.md +26 -15
- package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +2 -1
- package/.claude/skills/event-modeling-shared/templates/model-readme.md +10 -2
- package/.claude/skills/event-modeling-shared/templates/slice.md +7 -1
- package/.claude-plugin/marketplace.json +2 -2
- package/dist/catalog/build.js +6 -5
- package/dist/catalog/build.js.map +1 -1
- package/dist/catalog/coverage.d.ts +3 -0
- package/dist/catalog/coverage.js +12 -0
- package/dist/catalog/coverage.js.map +1 -1
- package/dist/catalog/docLocate.d.ts +17 -0
- package/dist/catalog/docLocate.js +97 -0
- package/dist/catalog/docLocate.js.map +1 -0
- package/dist/catalog/frontmatterCoherenceValidate.js +16 -1
- package/dist/catalog/frontmatterCoherenceValidate.js.map +1 -1
- package/dist/catalog/orphanedSliceDocValidate.js +15 -10
- package/dist/catalog/orphanedSliceDocValidate.js.map +1 -1
- package/dist/catalog/readSliceDoc.js +5 -4
- package/dist/catalog/readSliceDoc.js.map +1 -1
- package/dist/catalog/scenarioQualityValidate.js +2 -2
- package/dist/catalog/scenarioQualityValidate.js.map +1 -1
- package/dist/catalog/sliceDoc.d.ts +8 -0
- package/dist/catalog/sliceDoc.js +9 -0
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceSections.d.ts +18 -1
- package/dist/catalog/sliceSections.js +38 -4
- package/dist/catalog/sliceSections.js.map +1 -1
- package/dist/cli/api.d.ts +84 -0
- package/dist/cli/api.js +146 -15
- package/dist/cli/api.js.map +1 -1
- package/dist/cli/bindings.d.ts +152 -0
- package/dist/cli/bindings.js +462 -0
- package/dist/cli/bindings.js.map +1 -0
- package/dist/cli/conformScope.d.ts +32 -0
- package/dist/cli/conformScope.js +68 -5
- package/dist/cli/conformScope.js.map +1 -1
- package/dist/cli/defer.js +3 -2
- package/dist/cli/defer.js.map +1 -1
- package/dist/cli/engagement.d.ts +24 -1
- package/dist/cli/engagement.js +50 -6
- package/dist/cli/engagement.js.map +1 -1
- package/dist/cli/engagementBrief.d.ts +127 -0
- package/dist/cli/engagementBrief.js +313 -0
- package/dist/cli/engagementBrief.js.map +1 -0
- package/dist/cli/engagementFile.d.ts +14 -0
- package/dist/cli/engagementFile.js +54 -0
- package/dist/cli/engagementFile.js.map +1 -1
- package/dist/cli/ledgerCheck.js +11 -8
- package/dist/cli/ledgerCheck.js.map +1 -1
- package/dist/cli/markImplemented.js +2 -2
- package/dist/cli/markImplemented.js.map +1 -1
- package/dist/cli/ratify.js +2 -2
- package/dist/cli/ratify.js.map +1 -1
- package/dist/cli/refsCheck.js +3 -2
- package/dist/cli/refsCheck.js.map +1 -1
- package/dist/cli/render-inputs.d.ts +1 -1
- package/dist/cli/render-inputs.js +3 -2
- package/dist/cli/render-inputs.js.map +1 -1
- package/dist/cli/review.js +2 -2
- package/dist/cli/review.js.map +1 -1
- package/dist/cli/revise.js +2 -2
- package/dist/cli/revise.js.map +1 -1
- package/dist/cli/skillDirs.d.ts +22 -0
- package/dist/cli/skillDirs.js +24 -0
- package/dist/cli/skillDirs.js.map +1 -1
- package/dist/cli/sliceConform.js +2 -1
- package/dist/cli/sliceConform.js.map +1 -1
- package/dist/cli/sliceIndex.d.ts +19 -3
- package/dist/cli/sliceIndex.js +81 -17
- package/dist/cli/sliceIndex.js.map +1 -1
- package/dist/cli/sliceNew.js +2 -0
- package/dist/cli/sliceNew.js.map +1 -1
- package/dist/cli/sliceStubAll.js +7 -2
- package/dist/cli/sliceStubAll.js.map +1 -1
- package/dist/cli/sliceSync.d.ts +12 -0
- package/dist/cli/sliceSync.js +42 -4
- package/dist/cli/sliceSync.js.map +1 -1
- package/dist/cli/status.d.ts +8 -4
- package/dist/cli/status.js +6 -4
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/storylineSplit.d.ts +18 -0
- package/dist/cli/storylineSplit.js +110 -0
- package/dist/cli/storylineSplit.js.map +1 -0
- package/dist/cli/upgrade.js +47 -23
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/cli/watchStoryline.d.ts +6 -0
- package/dist/cli/watchStoryline.js +10 -0
- package/dist/cli/watchStoryline.js.map +1 -1
- package/dist/cli.js +408 -78
- package/dist/cli.js.map +1 -1
- package/dist/emit/api.d.ts +8 -1
- package/dist/emit/api.js +68 -24
- package/dist/emit/api.js.map +1 -1
- package/dist/emit/apiSurfaceJson.d.ts +3 -0
- package/dist/emit/apiSurfaceJson.js +26 -0
- package/dist/emit/apiSurfaceJson.js.map +1 -0
- package/dist/emit/engagementJson.d.ts +8 -3
- package/dist/emit/engagementJson.js +31 -4
- package/dist/emit/engagementJson.js.map +1 -1
- package/dist/emit/json.d.ts +22 -1
- package/dist/emit/json.js +12 -1
- package/dist/emit/json.js.map +1 -1
- package/dist/mcp/server.js +111 -9
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/edges.d.ts +1 -1
- package/dist/model/edges.js +8 -1
- package/dist/model/edges.js.map +1 -1
- package/dist/model/loadModel.d.ts +38 -0
- package/dist/model/loadModel.js +122 -0
- package/dist/model/loadModel.js.map +1 -0
- package/dist/model/model.d.ts +26 -9
- package/dist/model/model.js +22 -17
- package/dist/model/model.js.map +1 -1
- package/dist/model/rules.d.ts +23 -0
- package/dist/model/rules.js +23 -0
- package/dist/model/rules.js.map +1 -1
- package/dist/model/shapeLint.js +95 -0
- package/dist/model/shapeLint.js.map +1 -1
- package/dist/model/sliceDocIndex.js +13 -5
- package/dist/model/sliceDocIndex.js.map +1 -1
- package/dist/model/sliceDocPath.d.ts +19 -0
- package/dist/model/sliceDocPath.js +49 -0
- package/dist/model/sliceDocPath.js.map +1 -0
- package/dist/model/storylineMap.d.ts +20 -0
- package/dist/model/storylineMap.js +55 -0
- package/dist/model/storylineMap.js.map +1 -0
- package/dist/model/validate.d.ts +7 -0
- package/dist/model/validate.js +8 -0
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +19 -0
- package/dist/parser/parser.d.ts +17 -1
- package/dist/parser/parser.js +91 -2
- package/dist/parser/parser.js.map +1 -1
- package/dist/pipeline.d.ts +16 -0
- package/dist/pipeline.js +19 -2
- package/dist/pipeline.js.map +1 -1
- package/dist/render/drawEdges.d.ts +5 -1
- package/dist/render/drawEdges.js +57 -4
- package/dist/render/drawEdges.js.map +1 -1
- package/dist/render/drawNotes.d.ts +4 -1
- package/dist/render/drawNotes.js +40 -10
- package/dist/render/drawNotes.js.map +1 -1
- package/dist/render/render.d.ts +11 -0
- package/dist/render/render.js +94 -11
- package/dist/render/render.js.map +1 -1
- package/dist/render/serve.d.ts +5 -0
- package/dist/render/serve.js +18 -2
- package/dist/render/serve.js.map +1 -1
- package/dist/render/sliceDiagram.js +3 -0
- package/dist/render/sliceDiagram.js.map +1 -1
- package/dist/render/sliceOverlay.d.ts +4 -5
- package/dist/render/sliceOverlay.js +4 -7
- package/dist/render/sliceOverlay.js.map +1 -1
- package/dist/render/sliceStatus.d.ts +8 -0
- package/dist/render/sliceStatus.js +22 -15
- package/dist/render/sliceStatus.js.map +1 -1
- package/dist/render/storylineBands.d.ts +0 -3
- package/dist/render/storylineBands.js +2 -22
- package/dist/render/storylineBands.js.map +1 -1
- package/dist/render/storylineDiagram.js +17 -69
- package/dist/render/storylineDiagram.js.map +1 -1
- package/dist/render/storylineMap.d.ts +18 -0
- package/dist/render/storylineMap.js +263 -0
- package/dist/render/storylineMap.js.map +1 -0
- package/dist/render/svgGeometry.d.ts +9 -0
- package/dist/render/svgGeometry.js +16 -0
- package/dist/render/svgGeometry.js.map +1 -1
- package/dist/render/viewerHtml.js +1 -1
- package/dist/render/viewerHtml.js.map +1 -1
- package/dist/templates.d.ts +5 -1
- package/dist/templates.js +39 -2
- package/dist/templates.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/agents/em-implementer.md +10 -0
- package/plugin/agents/em-validator.md +1 -0
- package/plugin/shared/reference/em-dsl.md +54 -9
- package/plugin/shared/reference/methodology.md +35 -2
- package/plugin/shared/reference/operating-principles.md +26 -15
- package/plugin/shared/reference/slice-doc-schema.md +2 -1
- package/plugin/shared/templates/model-readme.md +10 -2
- package/plugin/shared/templates/slice.md +7 -1
- package/plugin/skills/conform/SKILL.md +7 -4
- package/plugin/skills/conform/reference/conform.md +82 -4
- package/plugin/skills/design/SKILL.md +23 -8
- package/plugin/skills/discover/SKILL.md +2 -2
- package/plugin/skills/discover/reference/extract.md +5 -1
- package/plugin/skills/engagement/SKILL.md +1 -1
- package/plugin/skills/engagement/reference/engagement.md +1 -0
- package/plugin/skills/event-modeling/SKILL.md +1 -1
- package/plugin/skills/implement/SKILL.md +5 -3
- package/plugin/skills/implement/reference/implement.md +14 -0
- package/plugin/skills/review/SKILL.md +19 -7
|
@@ -4,6 +4,14 @@ import { SliceDoc } from "../catalog/sliceDoc.js";
|
|
|
4
4
|
* MIL-208 originating slice's own file, or null), same order as model.slices (and so the same
|
|
5
5
|
* order as the header row's columns — see layout/grid.ts's sliceNames). */
|
|
6
6
|
export declare function readSliceDocs(model: NormalizedModel, baseDir: string): (SliceDoc | null)[];
|
|
7
|
+
/** A resolved slice doc and where it is: `path` is relative to `baseDir`, posix separators
|
|
8
|
+
* (`slices/<key>.md`), so a render can link a slice's header cell to it (owner ruling
|
|
9
|
+
* 2026-11-03: every slice header in an SVG is a link to its doc). */
|
|
10
|
+
export interface SliceDocFile {
|
|
11
|
+
path: string;
|
|
12
|
+
doc: SliceDoc;
|
|
13
|
+
}
|
|
14
|
+
export declare function readSliceDocFiles(model: NormalizedModel, baseDir: string): (SliceDocFile | null)[];
|
|
7
15
|
/** One status per slice, same order/resolution as readSliceDocs. null means no doc was found
|
|
8
16
|
* (own or covering), or the doc has no `- **Status:** ...` line. */
|
|
9
17
|
export declare function readSliceStatuses(model: NormalizedModel, baseDir: string): (string | null)[];
|
|
@@ -20,35 +20,42 @@
|
|
|
20
20
|
// directly — same predicate/resolution as catalog/docJoin.ts's note-gated join, model/
|
|
21
21
|
// continuation.ts, just applied to this module's filename-convention resolution instead of
|
|
22
22
|
// notes.
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
import { relative, sep } from "node:path";
|
|
25
25
|
import { computeRefs } from "../model/refs.js";
|
|
26
26
|
import { continuationOf } from "../model/continuation.js";
|
|
27
|
-
import {
|
|
27
|
+
import { readCoverageDocsAll } from "../catalog/coverage.js";
|
|
28
|
+
import { locateDoc } from "../catalog/docLocate.js";
|
|
28
29
|
import { parseSliceDoc } from "../catalog/sliceDoc.js";
|
|
29
30
|
/** Each slice's resolved design doc (own `slices/<key>.md`, the MIL-121 covering doc, the
|
|
30
31
|
* MIL-208 originating slice's own file, or null), same order as model.slices (and so the same
|
|
31
32
|
* order as the header row's columns — see layout/grid.ts's sliceNames). */
|
|
32
33
|
export function readSliceDocs(model, baseDir) {
|
|
34
|
+
return readSliceDocFiles(model, baseDir).map((f) => f?.doc ?? null);
|
|
35
|
+
}
|
|
36
|
+
export function readSliceDocFiles(model, baseDir) {
|
|
33
37
|
const refs = computeRefs(model);
|
|
34
38
|
const { sliceKeys } = refs;
|
|
35
|
-
const slicesDir = join(baseDir, "slices");
|
|
36
39
|
let coverage = null; // built lazily, only if ever needed
|
|
40
|
+
// GH #421 step 2: a doc sits beside the file that declares its slice; docLocate finds it by key
|
|
41
|
+
// and `path` is where it IS, relative to the root (`<dir>/slices/<key>.md`), so a header link
|
|
42
|
+
// from any output location resolves to the real file.
|
|
43
|
+
const file = (key) => {
|
|
44
|
+
const abs = locateDoc(baseDir, `slices/${key}.md`);
|
|
45
|
+
return abs ? { path: relative(baseDir, abs).split(sep).join("/"), doc: parseSliceDoc(readFileSync(abs, "utf8")) } : null;
|
|
46
|
+
};
|
|
37
47
|
return model.slices.map((_slice, i) => {
|
|
38
48
|
const sliceKey = sliceKeys[i];
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
return
|
|
42
|
-
coverage ??=
|
|
43
|
-
const covering = coverage.get(sliceKey)
|
|
49
|
+
const own = file(sliceKey);
|
|
50
|
+
if (own)
|
|
51
|
+
return own;
|
|
52
|
+
coverage ??= readCoverageDocsAll(baseDir);
|
|
53
|
+
const covering = coverage.get(sliceKey);
|
|
44
54
|
if (covering)
|
|
45
|
-
return covering;
|
|
55
|
+
return file(covering.key) ?? { path: `slices/${covering.key}.md`, doc: covering.doc };
|
|
46
56
|
const continuation = continuationOf(model, refs, i);
|
|
47
|
-
if (continuation)
|
|
48
|
-
|
|
49
|
-
if (existsSync(originatingPath))
|
|
50
|
-
return parseSliceDoc(readFileSync(originatingPath, "utf8"));
|
|
51
|
-
}
|
|
57
|
+
if (continuation)
|
|
58
|
+
return file(continuation.sliceKey);
|
|
52
59
|
return null;
|
|
53
60
|
});
|
|
54
61
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sliceStatus.js","sourceRoot":"","sources":["../../src/render/sliceStatus.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,qEAAqE;AACrE,kFAAkF;AAClF,kFAAkF;AAClF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,+EAA+E;AAC/E,EAAE;AACF,gGAAgG;AAChG,4FAA4F;AAC5F,+FAA+F;AAC/F,qFAAqF;AACrF,EAAE;AACF,6FAA6F;AAC7F,yFAAyF;AACzF,uFAAuF;AACvF,2FAA2F;AAC3F,SAAS;AAET,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"sliceStatus.js","sourceRoot":"","sources":["../../src/render/sliceStatus.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,3 @@ export declare function storylineBandIds(grid: Grid): string[];
|
|
|
6
6
|
/** One `<g class="em-storyline-band">` per run, in graph space (append inside the graph group,
|
|
7
7
|
* after the nodes). Returns "" when the model has no storylines. */
|
|
8
8
|
export declare function buildStorylineBands(model: NormalizedModel, grid: Grid, rects: Map<string, Rect>): string;
|
|
9
|
-
/** Dim every node of the stub columns (`Slice.stub`) — the header cell and each element — by
|
|
10
|
-
* adding an opacity attribute to its node group. */
|
|
11
|
-
export declare function dimStubColumns(svg: string, model: NormalizedModel): string;
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Graphviz lays the storyline row out as one filled box per column belonging to a storyline
|
|
5
5
|
// (emit/dot.ts). This reads those boxes' rectangles back out and draws one continuous labelled
|
|
6
|
-
// band per run of columns — bridging the inter-column gaps
|
|
7
|
-
|
|
8
|
-
import { STORYLINE_KEY, headerCellId, placeholderId } from "../layout/grid.js";
|
|
6
|
+
// band per run of columns — bridging the inter-column gaps.
|
|
7
|
+
import { STORYLINE_KEY, placeholderId } from "../layout/grid.js";
|
|
9
8
|
import { storylineStyle } from "../emit/theme.js";
|
|
10
9
|
import { storylineRuns } from "../model/storylines.js";
|
|
11
10
|
/** Row index of the storyline band row, or -1 when the grid has none. */
|
|
@@ -58,23 +57,4 @@ export function buildStorylineBands(model, grid, rects) {
|
|
|
58
57
|
}
|
|
59
58
|
return parts.join("");
|
|
60
59
|
}
|
|
61
|
-
const STUB_OPACITY = 0.4;
|
|
62
|
-
/** Dim every node of the stub columns (`Slice.stub`) — the header cell and each element — by
|
|
63
|
-
* adding an opacity attribute to its node group. */
|
|
64
|
-
export function dimStubColumns(svg, model) {
|
|
65
|
-
let out = svg;
|
|
66
|
-
model.slices.forEach((slice, c) => {
|
|
67
|
-
if (!slice.stub)
|
|
68
|
-
return;
|
|
69
|
-
const ids = [headerCellId(c), ...slice.elements.map((e) => e.id)];
|
|
70
|
-
for (const id of ids) {
|
|
71
|
-
const re = new RegExp(`(<g\\b[^>]*class="node")([^>]*>\\s*<title>${escapeRe(id)}</title>)`);
|
|
72
|
-
out = out.replace(re, `$1 opacity="${STUB_OPACITY}"$2`);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
return out;
|
|
76
|
-
}
|
|
77
|
-
function escapeRe(s) {
|
|
78
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
79
|
-
}
|
|
80
60
|
//# sourceMappingURL=storylineBands.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storylineBands.js","sourceRoot":"","sources":["../../src/render/storylineBands.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,mEAAmE;AACnE,EAAE;AACF,4FAA4F;AAC5F,+FAA+F;AAC/F,
|
|
1
|
+
{"version":3,"file":"storylineBands.js","sourceRoot":"","sources":["../../src/render/storylineBands.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
// Renders ONE storyline alone (`em render --storyline <name>`, GH #277): a subset of the
|
|
3
3
|
// normal timeline's columns, not a redrawn pattern shape (that is `--slice`).
|
|
4
4
|
//
|
|
5
|
-
// The columns are the storyline's own slices
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// and runs it through the unmodified layout()/emitDot()/composeSvg() pipeline.
|
|
5
|
+
// The columns are the storyline's own slices and nothing else (owner ruling 2026-11-03: a
|
|
6
|
+
// storyline render shows only what is in the storyline — no dimmed "stub" columns borrowed from
|
|
7
|
+
// the slices it reads from outside). An element the storyline reads from another storyline has
|
|
8
|
+
// no box here, so its arrow is not drawn; the storyline map carries that flow between stories.
|
|
9
|
+
// Like render/sliceDiagram.ts this builds a small synthetic NormalizedModel from shallow clones
|
|
10
|
+
// of the real Elements (same ids, fields, notes) and runs it through the unmodified
|
|
11
|
+
// layout()/emitDot()/composeSvg() pipeline.
|
|
13
12
|
import { AUTOMATION_KINDS } from "../parser/ast.js";
|
|
14
13
|
import { normalizeName } from "../model/model.js";
|
|
15
|
-
import {
|
|
16
|
-
import { findStoryline, latestOnTimeline, composedRank } from "../model/storylines.js";
|
|
14
|
+
import { findStoryline, composedRank } from "../model/storylines.js";
|
|
17
15
|
import { layout } from "../layout/grid.js";
|
|
18
16
|
import { emitDot } from "../emit/dot.js";
|
|
19
17
|
/** The storyline named `name`, or an error message naming the available ones. */
|
|
@@ -30,34 +28,23 @@ export function resolveStorylineArg(model, name) {
|
|
|
30
28
|
};
|
|
31
29
|
}
|
|
32
30
|
export function buildStorylineDiagram(model, storyline, opts = {}) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// the composed timeline (model/storylines.ts's relationOnTimeline — the ordering validation
|
|
38
|
-
// uses), file order as the fallback. The storylines this one comes `after` precede it on that
|
|
39
|
-
// timeline, so their end-state stubs land on the left naturally, while a stub from a slice
|
|
40
|
-
// between two blocks of this storyline is interleaved with its own columns: no arrow in the
|
|
41
|
-
// subset points right-to-left.
|
|
42
|
-
const columns = [
|
|
43
|
-
...own.map((i) => ({ i, stub: false })),
|
|
44
|
-
...[...stubs.keys()].map((i) => ({ i, stub: true })),
|
|
45
|
-
].sort((x, y) => {
|
|
46
|
-
return composedRank(model, x.i) - composedRank(model, y.i);
|
|
47
|
-
});
|
|
31
|
+
// Left to right on the composed timeline (model/storylines.ts's relationOnTimeline — the
|
|
32
|
+
// ordering validation uses), file order as the fallback: a storyline's blocks stay in the
|
|
33
|
+
// order the whole model reads them.
|
|
34
|
+
const own = [...storyline.sliceIndexes].sort((a, b) => composedRank(model, a) - composedRank(model, b));
|
|
48
35
|
const slices = [];
|
|
49
36
|
const elements = [];
|
|
50
37
|
const byId = new Map();
|
|
51
38
|
const byName = new Map();
|
|
52
|
-
const
|
|
39
|
+
for (const i of own) {
|
|
40
|
+
const origin = model.slices[i];
|
|
53
41
|
const index = slices.length;
|
|
54
|
-
const cloned =
|
|
42
|
+
const cloned = origin.elements.map((el) => ({ ...el, sliceIndex: index }));
|
|
55
43
|
slices.push({
|
|
56
44
|
name: origin.name,
|
|
57
45
|
index,
|
|
58
46
|
source: origin.source,
|
|
59
|
-
storyline:
|
|
60
|
-
stub: isStub || undefined,
|
|
47
|
+
storyline: storyline.name,
|
|
61
48
|
elements: cloned,
|
|
62
49
|
line: origin.line,
|
|
63
50
|
});
|
|
@@ -71,9 +58,6 @@ export function buildStorylineDiagram(model, storyline, opts = {}) {
|
|
|
71
58
|
else
|
|
72
59
|
byName.set(key, [el]);
|
|
73
60
|
}
|
|
74
|
-
};
|
|
75
|
-
for (const { i, stub } of columns) {
|
|
76
|
-
addColumn(model.slices[i], stub ? [...stubs.get(i).values()] : model.slices[i].elements, stub);
|
|
77
61
|
}
|
|
78
62
|
const dedupe = (values) => [...new Set(values.filter((v) => !!v))];
|
|
79
63
|
const synthetic = {
|
|
@@ -88,7 +72,7 @@ export function buildStorylineDiagram(model, storyline, opts = {}) {
|
|
|
88
72
|
name: storyline.name,
|
|
89
73
|
source: storyline.source,
|
|
90
74
|
after: storyline.after,
|
|
91
|
-
sliceIndexes: slices.
|
|
75
|
+
sliceIndexes: slices.map((s) => s.index),
|
|
92
76
|
line: storyline.line,
|
|
93
77
|
},
|
|
94
78
|
],
|
|
@@ -103,40 +87,4 @@ export function buildStorylineDiagram(model, storyline, opts = {}) {
|
|
|
103
87
|
const grid = layout(synthetic, opts);
|
|
104
88
|
return { model: synthetic, grid, dot: emitDot(synthetic, grid) };
|
|
105
89
|
}
|
|
106
|
-
/** origin slice index -> the elements of that slice the storyline needs as stubs. */
|
|
107
|
-
function collectStubs(model, storyline, ownSet) {
|
|
108
|
-
const stubs = new Map();
|
|
109
|
-
const add = (el) => {
|
|
110
|
-
if (!el || ownSet.has(el.sliceIndex))
|
|
111
|
-
return;
|
|
112
|
-
let bucket = stubs.get(el.sliceIndex);
|
|
113
|
-
if (!bucket)
|
|
114
|
-
stubs.set(el.sliceIndex, (bucket = new Map()));
|
|
115
|
-
bucket.set(el.id, el);
|
|
116
|
-
};
|
|
117
|
-
for (const edge of semanticEdges(model)) {
|
|
118
|
-
const from = model.byId.get(edge.from);
|
|
119
|
-
const to = model.byId.get(edge.to);
|
|
120
|
-
if (!from || !to)
|
|
121
|
-
continue;
|
|
122
|
-
if (edge.source === "loops-to") {
|
|
123
|
-
if (ownSet.has(from.sliceIndex))
|
|
124
|
-
add(to); // the earlier view a loop re-feeds
|
|
125
|
-
}
|
|
126
|
-
else if (ownSet.has(to.sliceIndex)) {
|
|
127
|
-
add(from); // something the storyline reads
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
// The referenced storyline's end state: the latest earlier instance of every read model this
|
|
131
|
-
// storyline continues with `again`.
|
|
132
|
-
for (const i of storyline.sliceIndexes) {
|
|
133
|
-
for (const el of model.slices[i].elements) {
|
|
134
|
-
if (el.kind !== "view" || !el.again)
|
|
135
|
-
continue;
|
|
136
|
-
const earlier = model.elements.filter((x) => x.kind === "view" && x.logicalId === el.logicalId && x.id !== el.id);
|
|
137
|
-
add(latestOnTimeline(model, earlier, el.sliceIndex, true));
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return stubs;
|
|
141
|
-
}
|
|
142
90
|
//# sourceMappingURL=storylineDiagram.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storylineDiagram.js","sourceRoot":"","sources":["../../src/render/storylineDiagram.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,yFAAyF;AACzF,8EAA8E;AAC9E,EAAE;AACF,+FAA+F;AAC/F
|
|
1
|
+
{"version":3,"file":"storylineDiagram.js","sourceRoot":"","sources":["../../src/render/storylineDiagram.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NormalizedModel } from "../model/model.js";
|
|
2
|
+
import { StorylineMap } from "../model/storylineMap.js";
|
|
3
|
+
export interface StorylineFrame {
|
|
4
|
+
name: string;
|
|
5
|
+
slug: string;
|
|
6
|
+
sliceCount: number;
|
|
7
|
+
/** The storyline's own rendered SVG (render/storylineDiagram.ts through composeSvg). */
|
|
8
|
+
svg: string;
|
|
9
|
+
/** Where the frame's open link goes. Default `./<slug>.svg` (the file `em render --map` writes
|
|
10
|
+
* beside the page); the live viewer passes `/?svg=<subset file>` (GH #422). */
|
|
11
|
+
href?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface StorylineMapOptions {
|
|
14
|
+
/** GH #422: served by `em watch --serve` — open links drill down in the same tab, the page
|
|
15
|
+
* reloads itself on the server's reload push and shows a render error in its bar. */
|
|
16
|
+
live?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function buildStorylineMapHtml(model: NormalizedModel, frames: StorylineFrame[], map: StorylineMap, opts?: StorylineMapOptions): string;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// The storyline canvas (`em render --map`, owner ruling 2026-11-03): one self-contained HTML page
|
|
3
|
+
// with every storyline's real diagram in a draggable frame and the flow between storylines drawn
|
|
4
|
+
// as connectors between frames. No server, no external assets: open `storylines/map.html` from
|
|
5
|
+
// disk. Frame positions are remembered per model in the browser's localStorage ("Reset layout"
|
|
6
|
+
// forgets them); the first layout places root storylines left and every `after` storyline one
|
|
7
|
+
// column to the right of what it follows.
|
|
8
|
+
//
|
|
9
|
+
// GH #422: `em watch --serve` serves the same page live at /map (src/render/serve.ts) — built by
|
|
10
|
+
// this one function with `live: true`: each frame's open link drills into that storyline's own
|
|
11
|
+
// subset in the viewer (`/?svg=<frame.href>`), and the page listens on the server's /__events
|
|
12
|
+
// stream and reloads itself after every successful render (positions and camera survive the
|
|
13
|
+
// reload: they live in localStorage). No second canvas implementation.
|
|
14
|
+
/** GH #422: the served canvas follows the same /__events stream the viewer does (serve.ts) — a
|
|
15
|
+
* successful render reloads the page (positions and camera are in localStorage, so the layout
|
|
16
|
+
* holds); a failed render lands in the bar without touching the frames on screen. */
|
|
17
|
+
const LIVE_SCRIPT = `
|
|
18
|
+
// --- live (em watch --serve): reload on the server's push, keep the last good canvas on an error
|
|
19
|
+
const liveEl = document.getElementById("live"), errEl = document.getElementById("liveError");
|
|
20
|
+
const es = new EventSource("/__events");
|
|
21
|
+
es.onopen = () => { liveEl.textContent = "live"; };
|
|
22
|
+
es.onerror = () => { liveEl.textContent = "reconnecting…"; };
|
|
23
|
+
es.onmessage = () => { location.reload(); };
|
|
24
|
+
es.addEventListener("renderError", (e) => { errEl.textContent = e.data; errEl.hidden = false; errEl.title = e.data; });
|
|
25
|
+
`;
|
|
26
|
+
function esc(s) {
|
|
27
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
28
|
+
}
|
|
29
|
+
export function buildStorylineMapHtml(model, frames, map, opts = {}) {
|
|
30
|
+
const title = `${model.name} — storylines`;
|
|
31
|
+
const live = opts.live === true;
|
|
32
|
+
const data = {
|
|
33
|
+
model: model.name,
|
|
34
|
+
nodes: map.nodes.map((n) => ({ name: n.name, slug: n.slug, after: n.after })),
|
|
35
|
+
edges: map.edges.map((e) => ({
|
|
36
|
+
from: e.from,
|
|
37
|
+
to: e.to,
|
|
38
|
+
kind: e.kind,
|
|
39
|
+
labels: e.elementIds.map((id) => model.byId.get(id)?.name ?? id),
|
|
40
|
+
})),
|
|
41
|
+
};
|
|
42
|
+
const frameHtml = frames
|
|
43
|
+
.map((f) => `<section class="frame" data-name="${esc(f.name)}" data-slug="${esc(f.slug)}">` +
|
|
44
|
+
`<header><span class="title">${esc(f.name)}</span><span class="meta">${f.sliceCount} slice${f.sliceCount === 1 ? "" : "s"}</span>` +
|
|
45
|
+
`<a class="open" href="${esc(f.href ?? `./${f.slug}.svg`)}"${live ? "" : ' target="_blank"'} title="${live ? "open this storyline in the live viewer" : "open this storyline's diagram"}">${live ? "open ›" : "↗"}</a></header>` +
|
|
46
|
+
`<div class="body">${f.svg}</div></section>`)
|
|
47
|
+
.join("\n");
|
|
48
|
+
// `</script>` can't appear in the JSON (names are model text; the escape keeps it safe anyway).
|
|
49
|
+
const json = JSON.stringify(data).replace(/<\//g, "<\\/");
|
|
50
|
+
return `<!doctype html>
|
|
51
|
+
<html lang="en">
|
|
52
|
+
<head>
|
|
53
|
+
<meta charset="utf-8" />
|
|
54
|
+
<title>${esc(title)}</title>
|
|
55
|
+
<style>
|
|
56
|
+
:root { color-scheme: light; }
|
|
57
|
+
html, body { height: 100%; margin: 0; }
|
|
58
|
+
body { font-family: system-ui, sans-serif; background: #f3f4f6; overflow: hidden; }
|
|
59
|
+
header.bar { position: fixed; top: 0; left: 0; right: 0; height: 40px; display: flex; align-items: center; gap: .75rem;
|
|
60
|
+
padding: 0 .9rem; background: #1f2933; color: #fff; font-size: 14px; z-index: 10; }
|
|
61
|
+
header.bar .title { font-weight: 600; }
|
|
62
|
+
header.bar .hint { opacity: .7; margin-left: auto; }
|
|
63
|
+
header.bar button { background: #334155; color: #fff; border: 0; border-radius: 4px; padding: .25rem .6rem; cursor: pointer; font-size: 13px; }
|
|
64
|
+
header.bar button:hover { background: #475569; }
|
|
65
|
+
header.bar .live { opacity: .7; }
|
|
66
|
+
header.bar .error { background: #b91c1c; color: #fff; padding: .2rem .6rem; border-radius: 4px; max-width: 40%;
|
|
67
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
68
|
+
header.bar .error[hidden] { display: none; }
|
|
69
|
+
#viewport { position: absolute; inset: 40px 0 0 0; overflow: hidden; cursor: grab; }
|
|
70
|
+
#viewport.panning { cursor: grabbing; }
|
|
71
|
+
#world { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
|
|
72
|
+
svg.links { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; }
|
|
73
|
+
svg.links text { font-size: 12px; font-family: system-ui, sans-serif; }
|
|
74
|
+
.frame { position: absolute; background: #fff; border: 2px solid #4a6fb5; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,.12);
|
|
75
|
+
display: flex; flex-direction: column; overflow: hidden; }
|
|
76
|
+
.frame header { display: flex; align-items: center; gap: .6rem; padding: .35rem .6rem; background: #dbe7fb; color: #1e3a8a;
|
|
77
|
+
font-size: 13px; cursor: move; user-select: none; }
|
|
78
|
+
.frame header .title { font-weight: 700; }
|
|
79
|
+
.frame header .meta { opacity: .75; }
|
|
80
|
+
.frame header .open { margin-left: auto; color: #1e3a8a; text-decoration: none; font-weight: 700; cursor: pointer; }
|
|
81
|
+
.frame .body { flex: 1; min-height: 0; background: #fff; padding: 16px; box-sizing: border-box; }
|
|
82
|
+
.frame .body svg { width: 100%; height: 100%; display: block; }
|
|
83
|
+
.frame.dragging { box-shadow: 0 6px 24px rgba(0,0,0,.25); z-index: 5; }
|
|
84
|
+
</style>
|
|
85
|
+
</head>
|
|
86
|
+
<body>
|
|
87
|
+
<header class="bar">
|
|
88
|
+
<span class="title">${esc(title)}</span>
|
|
89
|
+
<button id="fit" type="button">Fit</button>
|
|
90
|
+
<button id="reset" type="button">Reset layout</button>${live ? `\n <span class="live" id="live">live</span><span class="error" id="liveError" hidden></span>` : ""}
|
|
91
|
+
<span class="hint">drag a frame's title to move it · drag the background to pan · wheel to zoom${live ? " · click a storyline's “open ›” to step through it" : ""}</span>
|
|
92
|
+
</header>
|
|
93
|
+
<div id="viewport"><div id="world"><svg class="links" width="1" height="1">
|
|
94
|
+
<defs>
|
|
95
|
+
<marker id="m-after" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#4a6fb5"/></marker>
|
|
96
|
+
<marker id="m-reads" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#b36b00"/></marker>
|
|
97
|
+
</defs>
|
|
98
|
+
<g id="link-paths"></g><g id="link-labels"></g>
|
|
99
|
+
</svg>
|
|
100
|
+
${frameHtml}
|
|
101
|
+
</div></div>
|
|
102
|
+
<script id="map-data" type="application/json">${json}</script>
|
|
103
|
+
<script>
|
|
104
|
+
(() => {
|
|
105
|
+
const DATA = JSON.parse(document.getElementById("map-data").textContent);
|
|
106
|
+
const KEY = "em-storyline-map:" + DATA.model;
|
|
107
|
+
const SCALE = 0.3; // diagram units -> frame pixels at zoom 1
|
|
108
|
+
const PAD = 16, HEAD = 34; // frame body padding (each side) and the title bar's height
|
|
109
|
+
const GAP_X = 160, GAP_Y = 90;
|
|
110
|
+
const viewport = document.getElementById("viewport");
|
|
111
|
+
const world = document.getElementById("world");
|
|
112
|
+
const links = world.querySelector("svg.links");
|
|
113
|
+
const paths = document.getElementById("link-paths");
|
|
114
|
+
const labels = document.getElementById("link-labels");
|
|
115
|
+
const frames = [...world.querySelectorAll(".frame")];
|
|
116
|
+
const byName = new Map(frames.map((f) => [f.dataset.name, f]));
|
|
117
|
+
|
|
118
|
+
// --- frame size from each diagram's own viewBox, so a 3-slice story is small and a 29-slice one is wide
|
|
119
|
+
for (const f of frames) {
|
|
120
|
+
const svg = f.querySelector("svg");
|
|
121
|
+
const vb = (svg && svg.getAttribute("viewBox") || "0 0 1200 600").split(/\\s+/).map(Number);
|
|
122
|
+
const w = Math.max(240, vb[2] * SCALE) + 2 * PAD, h = Math.max(120, vb[3] * SCALE) + 2 * PAD + HEAD;
|
|
123
|
+
f.style.width = w + "px"; f.style.height = h + "px";
|
|
124
|
+
if (svg) { svg.removeAttribute("width"); svg.removeAttribute("height"); svg.setAttribute("preserveAspectRatio", "xMinYMin meet"); }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- state: positions + camera, remembered per model
|
|
128
|
+
let state = { pos: {}, cam: { x: 24, y: 24, s: 1 } };
|
|
129
|
+
try { const saved = JSON.parse(localStorage.getItem(KEY) || "null"); if (saved && saved.pos) state = saved; } catch {}
|
|
130
|
+
const save = () => { try { localStorage.setItem(KEY, JSON.stringify(state)); } catch {} };
|
|
131
|
+
|
|
132
|
+
function autoLayout() {
|
|
133
|
+
// column = depth along the after chain; rows stack within a column
|
|
134
|
+
const depth = new Map();
|
|
135
|
+
const nodeByName = new Map(DATA.nodes.map((n) => [n.name, n]));
|
|
136
|
+
const depthOf = (name, seen = new Set()) => {
|
|
137
|
+
if (depth.has(name)) return depth.get(name);
|
|
138
|
+
const n = nodeByName.get(name);
|
|
139
|
+
let d = 0;
|
|
140
|
+
if (n && n.after && nodeByName.has(n.after) && !seen.has(name)) { seen.add(name); d = depthOf(n.after, seen) + 1; }
|
|
141
|
+
depth.set(name, d); return d;
|
|
142
|
+
};
|
|
143
|
+
for (const n of DATA.nodes) depthOf(n.name);
|
|
144
|
+
const cols = new Map();
|
|
145
|
+
for (const n of DATA.nodes) { const d = depth.get(n.name) || 0; if (!cols.has(d)) cols.set(d, []); cols.get(d).push(n.name); }
|
|
146
|
+
let x = 0;
|
|
147
|
+
state.pos = {};
|
|
148
|
+
for (const d of [...cols.keys()].sort((a, b) => a - b)) {
|
|
149
|
+
let y = 0, colW = 0;
|
|
150
|
+
for (const name of cols.get(d)) {
|
|
151
|
+
const f = byName.get(name); if (!f) continue;
|
|
152
|
+
state.pos[f.dataset.slug] = { x, y };
|
|
153
|
+
y += f.offsetHeight + GAP_Y; colW = Math.max(colW, f.offsetWidth);
|
|
154
|
+
}
|
|
155
|
+
x += colW + GAP_X;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!frames.every((f) => state.pos[f.dataset.slug])) autoLayout();
|
|
159
|
+
|
|
160
|
+
function applyPositions() {
|
|
161
|
+
for (const f of frames) { const p = state.pos[f.dataset.slug] || { x: 0, y: 0 }; f.style.left = p.x + "px"; f.style.top = p.y + "px"; }
|
|
162
|
+
drawLinks();
|
|
163
|
+
}
|
|
164
|
+
function applyCamera() { world.style.transform = "translate(" + state.cam.x + "px," + state.cam.y + "px) scale(" + state.cam.s + ")"; }
|
|
165
|
+
|
|
166
|
+
// --- connectors: centre-to-centre line clipped to each frame's border; labels at the midpoint
|
|
167
|
+
function rectOf(f) { const p = state.pos[f.dataset.slug] || { x: 0, y: 0 }; return { x: p.x, y: p.y, w: f.offsetWidth, h: f.offsetHeight }; }
|
|
168
|
+
function exitPoint(r, tx, ty) {
|
|
169
|
+
const cx = r.x + r.w / 2, cy = r.y + r.h / 2, dx = tx - cx, dy = ty - cy;
|
|
170
|
+
if (dx === 0 && dy === 0) return { x: cx, y: cy };
|
|
171
|
+
const sx = dx !== 0 ? (r.w / 2) / Math.abs(dx) : Infinity, sy = dy !== 0 ? (r.h / 2) / Math.abs(dy) : Infinity;
|
|
172
|
+
const t = Math.min(sx, sy);
|
|
173
|
+
return { x: cx + dx * t, y: cy + dy * t };
|
|
174
|
+
}
|
|
175
|
+
function drawLinks() {
|
|
176
|
+
paths.innerHTML = ""; labels.innerHTML = "";
|
|
177
|
+
const NS = "http://www.w3.org/2000/svg";
|
|
178
|
+
for (const e of DATA.edges) {
|
|
179
|
+
const a = byName.get(e.from), b = byName.get(e.to); if (!a || !b) continue;
|
|
180
|
+
const ra = rectOf(a), rb = rectOf(b);
|
|
181
|
+
const bc = { x: rb.x + rb.w / 2, y: rb.y + rb.h / 2 }, ac = { x: ra.x + ra.w / 2, y: ra.y + ra.h / 2 };
|
|
182
|
+
const p1 = exitPoint(ra, bc.x, bc.y), p2 = exitPoint(rb, ac.x, ac.y);
|
|
183
|
+
const path = document.createElementNS(NS, "path");
|
|
184
|
+
path.setAttribute("d", "M" + p1.x + "," + p1.y + " L" + p2.x + "," + p2.y);
|
|
185
|
+
path.setAttribute("fill", "none");
|
|
186
|
+
path.setAttribute("stroke", e.kind === "after" ? "#4a6fb5" : "#b36b00");
|
|
187
|
+
path.setAttribute("stroke-width", e.kind === "after" ? "4" : "2");
|
|
188
|
+
if (e.kind !== "after") path.setAttribute("stroke-dasharray", "8 6");
|
|
189
|
+
path.setAttribute("marker-end", "url(#m-" + e.kind + ")");
|
|
190
|
+
paths.appendChild(path);
|
|
191
|
+
const mx = (p1.x + p2.x) / 2, my = (p1.y + p2.y) / 2;
|
|
192
|
+
const text = e.kind === "after" ? "after" : (e.labels.slice(0, 3).join(", ") + (e.labels.length > 3 ? " +" + (e.labels.length - 3) : ""));
|
|
193
|
+
const t = document.createElementNS(NS, "text");
|
|
194
|
+
t.setAttribute("x", mx); t.setAttribute("y", my - 6); t.setAttribute("text-anchor", "middle");
|
|
195
|
+
t.setAttribute("fill", e.kind === "after" ? "#4a6fb5" : "#7a4a00");
|
|
196
|
+
if (e.kind === "after") t.setAttribute("font-weight", "700");
|
|
197
|
+
t.setAttribute("paint-order", "stroke"); t.setAttribute("stroke", "#f3f4f6"); t.setAttribute("stroke-width", "4");
|
|
198
|
+
t.textContent = text;
|
|
199
|
+
labels.appendChild(t);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// --- interaction: drag frames by their header, pan the background, zoom with the wheel
|
|
204
|
+
let drag = null;
|
|
205
|
+
for (const f of frames) {
|
|
206
|
+
f.querySelector("header").addEventListener("pointerdown", (ev) => {
|
|
207
|
+
if (ev.target.closest("a")) return;
|
|
208
|
+
const p = state.pos[f.dataset.slug];
|
|
209
|
+
drag = { f, sx: ev.clientX, sy: ev.clientY, ox: p.x, oy: p.y };
|
|
210
|
+
f.classList.add("dragging"); f.setPointerCapture(ev.pointerId); ev.preventDefault(); ev.stopPropagation();
|
|
211
|
+
});
|
|
212
|
+
f.addEventListener("pointermove", (ev) => {
|
|
213
|
+
if (!drag || drag.f !== f) return;
|
|
214
|
+
state.pos[f.dataset.slug] = { x: drag.ox + (ev.clientX - drag.sx) / state.cam.s, y: drag.oy + (ev.clientY - drag.sy) / state.cam.s };
|
|
215
|
+
applyPositions();
|
|
216
|
+
});
|
|
217
|
+
const end = () => { if (drag && drag.f === f) { f.classList.remove("dragging"); drag = null; save(); } };
|
|
218
|
+
f.addEventListener("pointerup", end); f.addEventListener("pointercancel", end);
|
|
219
|
+
}
|
|
220
|
+
let pan = null;
|
|
221
|
+
viewport.addEventListener("pointerdown", (ev) => {
|
|
222
|
+
if (ev.target.closest(".frame")) return;
|
|
223
|
+
pan = { sx: ev.clientX, sy: ev.clientY, ox: state.cam.x, oy: state.cam.y };
|
|
224
|
+
viewport.classList.add("panning"); viewport.setPointerCapture(ev.pointerId);
|
|
225
|
+
});
|
|
226
|
+
viewport.addEventListener("pointermove", (ev) => {
|
|
227
|
+
if (!pan) return;
|
|
228
|
+
state.cam.x = pan.ox + (ev.clientX - pan.sx); state.cam.y = pan.oy + (ev.clientY - pan.sy); applyCamera();
|
|
229
|
+
});
|
|
230
|
+
const endPan = () => { if (pan) { pan = null; viewport.classList.remove("panning"); save(); } };
|
|
231
|
+
viewport.addEventListener("pointerup", endPan); viewport.addEventListener("pointercancel", endPan);
|
|
232
|
+
viewport.addEventListener("wheel", (ev) => {
|
|
233
|
+
ev.preventDefault();
|
|
234
|
+
const rect = viewport.getBoundingClientRect();
|
|
235
|
+
const mx = ev.clientX - rect.left, my = ev.clientY - rect.top;
|
|
236
|
+
const factor = Math.exp(-ev.deltaY * 0.0015);
|
|
237
|
+
const s = Math.min(4, Math.max(0.05, state.cam.s * factor));
|
|
238
|
+
state.cam.x = mx - (mx - state.cam.x) * (s / state.cam.s);
|
|
239
|
+
state.cam.y = my - (my - state.cam.y) * (s / state.cam.s);
|
|
240
|
+
state.cam.s = s; applyCamera(); save();
|
|
241
|
+
}, { passive: false });
|
|
242
|
+
|
|
243
|
+
function fit() {
|
|
244
|
+
if (frames.length === 0) return;
|
|
245
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
246
|
+
for (const f of frames) { const r = rectOf(f); minX = Math.min(minX, r.x); minY = Math.min(minY, r.y); maxX = Math.max(maxX, r.x + r.w); maxY = Math.max(maxY, r.y + r.h); }
|
|
247
|
+
const vw = viewport.clientWidth, vh = viewport.clientHeight, pad = 32;
|
|
248
|
+
const s = Math.min(4, Math.max(0.05, Math.min((vw - 2 * pad) / (maxX - minX), (vh - 2 * pad) / (maxY - minY))));
|
|
249
|
+
state.cam = { s, x: pad - minX * s + ((vw - 2 * pad) - (maxX - minX) * s) / 2, y: pad - minY * s + ((vh - 2 * pad) - (maxY - minY) * s) / 2 };
|
|
250
|
+
applyCamera(); save();
|
|
251
|
+
}
|
|
252
|
+
document.getElementById("fit").addEventListener("click", fit);
|
|
253
|
+
document.getElementById("reset").addEventListener("click", () => { autoLayout(); applyPositions(); fit(); });
|
|
254
|
+
|
|
255
|
+
applyPositions();
|
|
256
|
+
if (!localStorage.getItem(KEY)) fit(); else applyCamera();
|
|
257
|
+
${live ? LIVE_SCRIPT : ""}})();
|
|
258
|
+
</script>
|
|
259
|
+
</body>
|
|
260
|
+
</html>
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=storylineMap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storylineMap.js","sourceRoot":"","sources":["../../src/render/storylineMap.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,15 @@ export declare const NODE_GROUP: RegExp;
|
|
|
10
10
|
export declare const TITLE: RegExp;
|
|
11
11
|
/** Map each requested node id to its rectangle in SVG coordinates. */
|
|
12
12
|
export declare function parseNodeRects(svg: string, ids: Set<string>): Map<string, Rect>;
|
|
13
|
+
/** One `<text>` line inside a node group: its decoded text and baseline y (SVG user space). */
|
|
14
|
+
export interface TextRow {
|
|
15
|
+
text: string;
|
|
16
|
+
y: number;
|
|
17
|
+
}
|
|
18
|
+
/** The text lines of one node, top to bottom, with their baselines — how a box that lists
|
|
19
|
+
* alternatives (GH #278, one Graphviz HTML table per group) tells which row is which
|
|
20
|
+
* alternative, so an arrow or a marker can anchor to that alternative's own row (GH #324). */
|
|
21
|
+
export declare function parseNodeTextRows(svg: string, id: string): TextRow[];
|
|
13
22
|
/** A bounding box in the graph's own (post-transform) coordinate space. */
|
|
14
23
|
export interface Box {
|
|
15
24
|
minX: number;
|