@transitrix/diagrams 1.0.0 → 1.1.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/LICENSE +21 -21
- package/README.md +47 -47
- package/dist/__tests__/edge-path.test.js +8 -0
- package/dist/activities/cpm.js +11 -2
- package/dist/activities/gantt.js +6 -3
- package/dist/activities/types.d.ts +1 -0
- package/dist/activities/validate.js +3 -3
- package/dist/activity-card/__tests__/example.test.js +20 -1
- package/dist/activity-card/__tests__/layout.test.js +102 -11
- package/dist/activity-card/__tests__/resolver.test.js +161 -7
- package/dist/activity-card/index.d.ts +1 -1
- package/dist/activity-card/layout.d.ts +1 -1
- package/dist/activity-card/layout.js +220 -103
- package/dist/activity-card/resolver.js +115 -28
- package/dist/activity-card/types.d.ts +150 -27
- package/dist/activity-card/validate.js +3 -0
- package/dist/actor/__tests__/validate.test.js +63 -31
- package/dist/actor/index.d.ts +1 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/types.d.ts +7 -3
- package/dist/actor/types.js +17 -3
- package/dist/actor/validate.js +55 -26
- package/dist/assertion/types.d.ts +3 -1
- package/dist/assertion/types.js +1 -1
- package/dist/change/__tests__/validate.test.js +40 -34
- package/dist/change/index.d.ts +1 -0
- package/dist/change/types.d.ts +1 -1
- package/dist/change/types.js +2 -2
- package/dist/change/validate.d.ts +6 -1
- package/dist/change/validate.js +68 -41
- package/dist/compliance/__tests__/classify.test.js +34 -0
- package/dist/compliance/__tests__/coverage-metric.test.js +146 -46
- package/dist/compliance/__tests__/html.test.js +2 -2
- package/dist/compliance/__tests__/impact.test.js +509 -6
- package/dist/compliance/classify.d.ts +6 -0
- package/dist/compliance/classify.js +9 -1
- package/dist/compliance/coverage-metric.d.ts +31 -11
- package/dist/compliance/coverage-metric.js +149 -39
- package/dist/compliance/html.js +78 -81
- package/dist/compliance/impact.d.ts +100 -16
- package/dist/compliance/impact.js +272 -28
- package/dist/compliance/index.d.ts +2 -2
- package/dist/compliance/index.js +1 -1
- package/dist/compliance/markdown.js +1 -0
- package/dist/compliance/types.d.ts +25 -6
- package/dist/edge-path.d.ts +8 -4
- package/dist/edge-path.js +11 -6
- package/dist/factor/__tests__/validate.test.js +75 -46
- package/dist/factor/index.d.ts +3 -2
- package/dist/factor/index.js +1 -1
- package/dist/factor/types.d.ts +5 -8
- package/dist/factor/types.js +10 -5
- package/dist/factor/validate.d.ts +6 -1
- package/dist/factor/validate.js +68 -50
- package/dist/fgca/__tests__/chain-table.test.js +2 -2
- package/dist/fgca/__tests__/layout.test.js +11 -11
- package/dist/fgca/__tests__/preview-layout.test.js +7 -7
- package/dist/fgca/__tests__/resolver.test.js +185 -0
- package/dist/fgca/chain-table.d.ts +1 -1
- package/dist/fgca/chain-table.js +4 -4
- package/dist/fgca/index.d.ts +4 -2
- package/dist/fgca/index.js +2 -1
- package/dist/fgca/layout.d.ts +2 -2
- package/dist/fgca/layout.js +9 -9
- package/dist/fgca/nodeTypes.d.ts +1 -1
- package/dist/fgca/nodeTypes.js +2 -2
- package/dist/fgca/nodes/FGCAFactorNode.d.ts +3 -3
- package/dist/fgca/nodes/FGCAFactorNode.js +5 -5
- package/dist/fgca/parse-canonical.js +15 -10
- package/dist/fgca/preview-layout.d.ts +1 -1
- package/dist/fgca/preview-layout.js +4 -4
- package/dist/fgca/resolver.d.ts +49 -0
- package/dist/fgca/resolver.js +126 -0
- package/dist/fgca/types.d.ts +11 -11
- package/dist/fgca/types.js +2 -2
- package/dist/fgca/validate.d.ts +2 -2
- package/dist/fgca/validate.js +17 -14
- package/dist/goals/index.d.ts +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/process-blueprint/__tests__/layout.test.js +37 -0
- package/dist/process-blueprint/index.d.ts +1 -1
- package/dist/process-blueprint/layout.js +66 -35
- package/dist/process-blueprint/types.d.ts +36 -1
- package/dist/scenarios/index.d.ts +1 -1
- package/dist/scenarios/types.d.ts +4 -4
- package/dist/stakeholder/__tests__/validate.test.js +49 -47
- package/dist/stakeholder/index.d.ts +1 -0
- package/dist/stakeholder/types.d.ts +3 -4
- package/dist/stakeholder/types.js +2 -3
- package/dist/stakeholder/validate.d.ts +6 -1
- package/dist/stakeholder/validate.js +71 -50
- package/dist/target-state/__tests__/validate.test.js +43 -27
- package/dist/target-state/index.d.ts +2 -0
- package/dist/target-state/index.js +1 -0
- package/dist/target-state/types.d.ts +6 -1
- package/dist/target-state/types.js +8 -7
- package/dist/target-state/validate.d.ts +6 -1
- package/dist/target-state/validate.js +69 -45
- package/dist/theme/themes.js +71 -69
- package/dist/theme/tokens.d.ts +7 -1
- package/dist/theme/tokens.js +3 -1
- package/dist/webview/__tests__/entry-notations.test.js +2 -0
- package/dist/webview/__tests__/entry.test.js +11 -11
- package/dist/webview/__tests__/render-process.test.js +463 -0
- package/dist/webview/__tests__/render-util.test.d.ts +1 -0
- package/dist/webview/__tests__/render-util.test.js +54 -0
- package/dist/webview/entry.d.ts +1 -1
- package/dist/webview/entry.js +9 -5
- package/dist/webview/render-activities.d.ts +24 -2
- package/dist/webview/render-activities.js +92 -57
- package/dist/webview/render-activity-card.d.ts +11 -1
- package/dist/webview/render-activity-card.js +115 -79
- package/dist/webview/render-blocks.d.ts +24 -1
- package/dist/webview/render-blocks.js +58 -24
- package/dist/webview/render-fgca.d.ts +31 -0
- package/dist/webview/render-fgca.js +30 -19
- package/dist/webview/render-goals.d.ts +27 -1
- package/dist/webview/render-goals.js +42 -32
- package/dist/webview/render-process-blueprint.d.ts +11 -1
- package/dist/webview/render-process-blueprint.js +118 -53
- package/dist/webview/render-process.d.ts +95 -0
- package/dist/webview/render-process.js +392 -0
- package/dist/webview/render-scenarios.js +2 -2
- package/dist/webview/render-util.d.ts +19 -6
- package/dist/webview/render-util.js +23 -15
- package/package.json +54 -40
- package/src/__tests__/edge-path.test.ts +58 -46
- package/src/__tests__/scope.test.ts +27 -27
- package/src/__tests__/yaml-normalize.test.ts +158 -158
- package/src/activities/__tests__/cpm.test.ts +164 -164
- package/src/activities/__tests__/gantt.test.ts +245 -245
- package/src/activities/__tests__/layout.test.ts +112 -112
- package/src/activities/__tests__/validate.test.ts +409 -409
- package/src/activities/cpm.ts +107 -95
- package/src/activities/gantt.ts +277 -276
- package/src/activities/index.ts +28 -28
- package/src/activities/layout.ts +112 -112
- package/src/activities/types.ts +149 -148
- package/src/activities/validate.ts +357 -357
- package/src/activity-card/__tests__/example.test.ts +85 -66
- package/src/activity-card/__tests__/layout.test.ts +169 -63
- package/src/activity-card/__tests__/resolver.test.ts +330 -161
- package/src/activity-card/__tests__/validate.test.ts +131 -131
- package/src/activity-card/index.ts +38 -32
- package/src/activity-card/layout.ts +324 -194
- package/src/activity-card/resolver.ts +422 -337
- package/src/activity-card/types.ts +376 -237
- package/src/activity-card/validate.ts +162 -158
- package/src/actor/__tests__/validate.test.ts +70 -31
- package/src/actor/index.ts +1 -1
- package/src/actor/types.ts +21 -7
- package/src/actor/validate.ts +59 -27
- package/src/applications/__tests__/validate.test.ts +256 -256
- package/src/applications/index.ts +11 -11
- package/src/applications/types.ts +43 -43
- package/src/applications/validate.ts +131 -131
- package/src/assertion/__tests__/example.test.ts +37 -37
- package/src/assertion/__tests__/validate.test.ts +142 -142
- package/src/assertion/index.ts +12 -12
- package/src/assertion/types.ts +60 -58
- package/src/assertion/validate.ts +145 -145
- package/src/blocks/__tests__/layout.test.ts +169 -169
- package/src/blocks/__tests__/validate.test.ts +260 -260
- package/src/blocks/index.ts +17 -17
- package/src/blocks/layout.ts +186 -186
- package/src/blocks/types.ts +62 -62
- package/src/blocks/validate.ts +206 -206
- package/src/capability-map/__tests__/validate.test.ts +238 -238
- package/src/capability-map/index.ts +8 -8
- package/src/capability-map/types.ts +29 -29
- package/src/capability-map/validate.ts +127 -127
- package/src/change/__tests__/validate.test.ts +43 -34
- package/src/change/index.ts +1 -0
- package/src/change/types.ts +5 -3
- package/src/change/validate.ts +75 -43
- package/src/compliance/__tests__/classify.test.ts +64 -27
- package/src/compliance/__tests__/confidence.test.ts +98 -98
- package/src/compliance/__tests__/coverage-metric.test.ts +300 -188
- package/src/compliance/__tests__/demo-scenario.test.ts +273 -273
- package/src/compliance/__tests__/example.test.ts +57 -57
- package/src/compliance/__tests__/gap-report.test.ts +128 -128
- package/src/compliance/__tests__/html.test.ts +141 -141
- package/src/compliance/__tests__/impact.test.ts +1059 -481
- package/src/compliance/__tests__/markdown.test.ts +94 -94
- package/src/compliance/__tests__/views.test.ts +64 -64
- package/src/compliance/classify.ts +100 -84
- package/src/compliance/confidence.ts +69 -69
- package/src/compliance/coverage-metric.ts +404 -263
- package/src/compliance/gap-report.ts +94 -94
- package/src/compliance/html.ts +291 -295
- package/src/compliance/impact.ts +979 -677
- package/src/compliance/index.ts +57 -56
- package/src/compliance/markdown.ts +136 -135
- package/src/compliance/reverse-index.ts +33 -33
- package/src/compliance/types.ts +136 -120
- package/src/compliance/views.ts +46 -46
- package/src/compliance-matrix/__tests__/build.test.ts +200 -200
- package/src/compliance-matrix/__tests__/example.test.ts +57 -57
- package/src/compliance-matrix/index.ts +11 -11
- package/src/compliance-matrix/types.ts +102 -102
- package/src/edge-path.ts +50 -44
- package/src/factor/__tests__/validate.test.ts +81 -46
- package/src/factor/index.ts +3 -7
- package/src/factor/types.ts +15 -19
- package/src/factor/validate.ts +77 -51
- package/src/fgca/__tests__/chain-table.test.ts +172 -172
- package/src/fgca/__tests__/layout.test.ts +179 -179
- package/src/fgca/__tests__/parse-canonical.test.ts +248 -248
- package/src/fgca/__tests__/preview-layout.test.ts +124 -124
- package/src/fgca/__tests__/resolver.test.ts +209 -0
- package/src/fgca/__tests__/validate.test.ts +142 -142
- package/src/fgca/chain-table.ts +225 -225
- package/src/fgca/index.ts +47 -45
- package/src/fgca/layout.ts +204 -204
- package/src/fgca/nodeTypes.ts +11 -11
- package/src/fgca/nodes/FGCAActivityNode.tsx +65 -65
- package/src/fgca/nodes/FGCAChangeNode.tsx +64 -64
- package/src/fgca/nodes/FGCAFactorNode.tsx +64 -64
- package/src/fgca/nodes/FGCAGoalNode.tsx +72 -72
- package/src/fgca/parse-canonical.ts +405 -400
- package/src/fgca/preview-layout.ts +217 -217
- package/src/fgca/resolver.ts +159 -0
- package/src/fgca/types.ts +50 -50
- package/src/fgca/validate.ts +129 -126
- package/src/geometry.ts +9 -9
- package/src/goals/__tests__/layout.test.ts +175 -175
- package/src/goals/__tests__/mutations.test.ts +111 -111
- package/src/goals/__tests__/parse-canonical.test.ts +145 -145
- package/src/goals/__tests__/validate.test.ts +202 -202
- package/src/goals/index.ts +20 -21
- package/src/goals/layout.ts +172 -172
- package/src/goals/mutations.ts +123 -123
- package/src/goals/parse-canonical.ts +286 -286
- package/src/goals/types.ts +85 -85
- package/src/goals/validate.ts +136 -136
- package/src/index.ts +28 -23
- package/src/process-blueprint/__tests__/compliance-lane.test.ts +320 -320
- package/src/process-blueprint/__tests__/layout.test.ts +240 -195
- package/src/process-blueprint/__tests__/validate.test.ts +310 -310
- package/src/process-blueprint/index.ts +32 -31
- package/src/process-blueprint/layout.ts +564 -531
- package/src/process-blueprint/types.ts +278 -242
- package/src/process-blueprint/validate.ts +181 -181
- package/src/process-map/__tests__/validate.test.ts +223 -223
- package/src/process-map/index.ts +10 -10
- package/src/process-map/types.ts +36 -36
- package/src/process-map/validate.ts +135 -135
- package/src/products/__tests__/validate.test.ts +239 -239
- package/src/products/index.ts +9 -9
- package/src/products/types.ts +31 -31
- package/src/products/validate.ts +112 -112
- package/src/repo-validate/__tests__/validate-repo.test.ts +149 -149
- package/src/repo-validate/index.ts +2 -2
- package/src/repo-validate/types.ts +50 -50
- package/src/repo-validate/validate-repo.ts +196 -196
- package/src/requirement/__tests__/example.test.ts +24 -24
- package/src/requirement/__tests__/validate.test.ts +96 -96
- package/src/requirement/index.ts +4 -4
- package/src/requirement/types.ts +35 -35
- package/src/requirement/validate.ts +92 -92
- package/src/scenarios/__tests__/validate.test.ts +209 -209
- package/src/scenarios/index.ts +15 -15
- package/src/scenarios/types.ts +37 -37
- package/src/scenarios/validate.ts +122 -122
- package/src/schema-version.ts +16 -16
- package/src/scope.ts +46 -46
- package/src/stakeholder/__tests__/validate.test.ts +51 -47
- package/src/stakeholder/index.ts +1 -0
- package/src/stakeholder/types.ts +7 -7
- package/src/stakeholder/validate.ts +79 -52
- package/src/target-state/__tests__/validate.test.ts +48 -27
- package/src/target-state/index.ts +2 -0
- package/src/target-state/types.ts +11 -7
- package/src/target-state/validate.ts +76 -46
- package/src/theme/index.ts +4 -4
- package/src/theme/themes.ts +239 -237
- package/src/theme/tokens.ts +135 -133
- package/src/typed-id.ts +56 -56
- package/src/validation-types.ts +36 -36
- package/src/webview/__tests__/entry-notations.test.ts +79 -77
- package/src/webview/__tests__/entry.test.ts +64 -64
- package/src/webview/__tests__/render-goals.test.ts +72 -72
- package/src/webview/__tests__/render-process.test.ts +538 -0
- package/src/webview/__tests__/render-util.test.ts +66 -0
- package/src/webview/entry.ts +298 -292
- package/src/webview/render-activities.ts +111 -69
- package/src/webview/render-activity-card.ts +275 -234
- package/src/webview/render-blocks.ts +78 -28
- package/src/webview/render-fgca.ts +53 -28
- package/src/webview/render-goals.ts +133 -110
- package/src/webview/render-process-blueprint.ts +281 -207
- package/src/webview/render-process.ts +507 -0
- package/src/webview/render-scenarios.ts +3 -3
- package/src/webview/render-util.ts +27 -14
- package/src/webview/styles.css +322 -323
- package/src/yaml-normalize.ts +56 -56
- package/dist/element-envelope.d.ts +0 -15
- package/dist/element-envelope.js +0 -55
- package/dist/issues/__tests__/layout.test.js +0 -62
- package/dist/issues/__tests__/validate.test.js +0 -82
- package/dist/issues/index.d.ts +0 -4
- package/dist/issues/index.js +0 -2
- package/dist/issues/layout.d.ts +0 -10
- package/dist/issues/layout.js +0 -82
- package/dist/issues/types.d.ts +0 -61
- package/dist/issues/types.js +0 -6
- package/dist/issues/validate.d.ts +0 -3
- package/dist/issues/validate.js +0 -143
- package/dist/webview/transitrix-render.css +0 -323
- package/dist/webview/transitrix-render.js +0 -6452
- package/dist/webview/transitrix-render.js.map +0 -7
- package/src/element-envelope.ts +0 -76
- /package/dist/{issues/__tests__/layout.test.d.ts → fgca/__tests__/resolver.test.d.ts} +0 -0
- /package/dist/{issues/__tests__/validate.test.d.ts → webview/__tests__/render-process.test.d.ts} +0 -0
|
@@ -14,11 +14,16 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { layoutNestedBlocks } from '../blocks/layout.js';
|
|
16
16
|
import type { BlocksFile, BlocksLayout, LaidOutBlock } from '../blocks/types.js';
|
|
17
|
-
import { generateSvgEmbedCss } from '../theme/index.js';
|
|
17
|
+
import { generateSvgEmbedCss, type ThemeId } from '../theme/index.js';
|
|
18
18
|
import { escXml } from './render-util.js';
|
|
19
19
|
|
|
20
20
|
const PAD = 24;
|
|
21
21
|
|
|
22
|
+
// Approximate character width (px) at text-primary size (12px/600).
|
|
23
|
+
const CHAR_W = 7;
|
|
24
|
+
// Approximate character width (px) at text-id size (10px/600) — used for ID suffix.
|
|
25
|
+
const CHAR_W_ID = 6;
|
|
26
|
+
|
|
22
27
|
export interface RenderBlocksOptions {
|
|
23
28
|
title?: string;
|
|
24
29
|
}
|
|
@@ -36,27 +41,89 @@ function levelClassForDepth(depth: number): string {
|
|
|
36
41
|
return `level-${idx}`;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
/** Truncate at a word boundary so labels never break in the middle of a word. */
|
|
45
|
+
function wordTruncate(text: string, maxChars: number): string {
|
|
40
46
|
if (text.length <= maxChars) return text;
|
|
41
|
-
|
|
47
|
+
const cut = text.slice(0, maxChars - 1);
|
|
48
|
+
const lastSpace = cut.lastIndexOf(' ');
|
|
49
|
+
return (lastSpace > maxChars / 2 ? cut.slice(0, lastSpace) : cut) + '…';
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
function emitBlockSvg(b: LaidOutBlock, ox: number, oy: number, parts: string[]): void {
|
|
45
53
|
const cls = levelClassForDepth(b.depth);
|
|
54
|
+
const cx = b.x + ox + b.width / 2;
|
|
46
55
|
parts.push(
|
|
47
56
|
`<rect class="diagram-node ${cls}" x="${b.x + ox}" y="${b.y + oy}" width="${b.width}" height="${b.height}" rx="6"/>`,
|
|
48
57
|
);
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
const isLeaf = b.children.length === 0;
|
|
60
|
+
if (isLeaf) {
|
|
61
|
+
// Leaf block: name on upper line, ID on lower line — both centred.
|
|
62
|
+
const nameY = b.y + oy + Math.round(b.height * 0.38);
|
|
63
|
+
const idY = b.y + oy + Math.round(b.height * 0.66);
|
|
64
|
+
const maxChars = Math.max(4, Math.floor(b.width / CHAR_W));
|
|
65
|
+
parts.push(
|
|
66
|
+
`<text class="text-primary" x="${cx}" y="${nameY}" text-anchor="middle" dominant-baseline="central">${escXml(wordTruncate(b.name, maxChars))}</text>`,
|
|
67
|
+
`<text class="text-id" x="${cx}" y="${idY}" text-anchor="middle" dominant-baseline="central">${escXml(b.id)}</text>`,
|
|
68
|
+
);
|
|
69
|
+
} else {
|
|
70
|
+
// Container block: name + (ID) on the single header line; ID in grey.
|
|
71
|
+
const headerY = b.y + oy + b.headerHeight / 2;
|
|
72
|
+
const idSuffix = ` (${b.id})`;
|
|
73
|
+
const idSuffixW = idSuffix.length * CHAR_W_ID;
|
|
74
|
+
const nameMaxChars = Math.max(4, Math.floor((b.width - idSuffixW) / CHAR_W));
|
|
75
|
+
const nameText = wordTruncate(b.name, nameMaxChars);
|
|
76
|
+
parts.push(
|
|
77
|
+
`<text class="text-primary" x="${cx}" y="${headerY}" text-anchor="middle" dominant-baseline="central">${escXml(nameText)}<tspan fill="var(--ts-text-secondary, #64748b)" font-size="10" font-weight="600">${escXml(idSuffix)}</tspan></text>`,
|
|
78
|
+
);
|
|
79
|
+
for (const c of b.children) emitBlockSvg(c, ox, oy, parts);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
56
82
|
|
|
57
|
-
|
|
83
|
+
export interface RenderBlocksLayoutOptions {
|
|
84
|
+
/** Extra vertical space reserved at the top of the canvas (e.g. for a title block). */
|
|
85
|
+
topInset?: number;
|
|
86
|
+
/** Raw SVG injected immediately after the opening tag — a header line or a full title block. */
|
|
87
|
+
title?: string;
|
|
88
|
+
/** When set, the theme CSS is embedded as `<style>` so the SVG is self-contained. */
|
|
89
|
+
embedCssTheme?: ThemeId;
|
|
58
90
|
}
|
|
59
91
|
|
|
92
|
+
/**
|
|
93
|
+
* The single Nested Blocks SVG emitter shared by every host. Takes an
|
|
94
|
+
* already-computed {@link BlocksLayout} (callers decide the layout options) and
|
|
95
|
+
* produces the `<svg>`. Hosts wrap it with their own chrome:
|
|
96
|
+
* - IntelliJ/UI via {@link renderBlocksSvg} (embedded CSS + simple header);
|
|
97
|
+
* - VS Code's blocks preview (rich title block, no embedded CSS — the webview
|
|
98
|
+
* and the export path own styling).
|
|
99
|
+
*/
|
|
100
|
+
export function renderBlocksLayoutSvg(
|
|
101
|
+
layout: BlocksLayout,
|
|
102
|
+
options: RenderBlocksLayoutOptions = {},
|
|
103
|
+
): string {
|
|
104
|
+
const { topInset = 0, title = '', embedCssTheme } = options;
|
|
105
|
+
|
|
106
|
+
const w = layout.bounds.width + PAD * 2;
|
|
107
|
+
const h = layout.bounds.height + PAD * 2 + topInset;
|
|
108
|
+
const ox = -layout.bounds.x + PAD;
|
|
109
|
+
const oy = -layout.bounds.y + PAD + topInset;
|
|
110
|
+
|
|
111
|
+
const parts: string[] = [];
|
|
112
|
+
for (const top of layout.blocks) emitBlockSvg(top, ox, oy, parts);
|
|
113
|
+
|
|
114
|
+
const styleLine = embedCssTheme ? `\n<style>${generateSvgEmbedCss(embedCssTheme)}</style>` : '';
|
|
115
|
+
|
|
116
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">${styleLine}
|
|
117
|
+
${title}
|
|
118
|
+
${parts.join('\n')}
|
|
119
|
+
</svg>`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Host-neutral blocks renderer (IntelliJ/UI). Lays the doc out with the default
|
|
124
|
+
* spacing, then delegates the actual SVG emission to {@link renderBlocksLayoutSvg}
|
|
125
|
+
* with the shared theme CSS embedded so the output is self-contained.
|
|
126
|
+
*/
|
|
60
127
|
export function renderBlocksSvg(doc: BlocksFile, options: RenderBlocksOptions = {}): string {
|
|
61
128
|
const { title = '' } = options;
|
|
62
129
|
|
|
@@ -66,26 +133,9 @@ export function renderBlocksSvg(doc: BlocksFile, options: RenderBlocksOptions =
|
|
|
66
133
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
67
134
|
}
|
|
68
135
|
|
|
69
|
-
const w = layout.bounds.width + PAD * 2;
|
|
70
|
-
const h = layout.bounds.height + PAD * 2;
|
|
71
|
-
const ox = -layout.bounds.x + PAD;
|
|
72
|
-
const oy = -layout.bounds.y + PAD;
|
|
73
|
-
|
|
74
|
-
const parts: string[] = [];
|
|
75
|
-
for (const top of layout.blocks) emitBlockSvg(top, ox, oy, parts);
|
|
76
|
-
|
|
77
136
|
const titleSvg = title
|
|
78
137
|
? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(`Nested Blocks — ${title}`)}</text>`
|
|
79
138
|
: '';
|
|
80
139
|
|
|
81
|
-
|
|
82
|
-
// self-contained — the JCEF host page only needs to drop the SVG into the
|
|
83
|
-
// DOM and styling resolves without any cooperation from the host stylesheet.
|
|
84
|
-
const embedCss = generateSvgEmbedCss('transitrix');
|
|
85
|
-
|
|
86
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
87
|
-
<style>${embedCss}</style>
|
|
88
|
-
${titleSvg}
|
|
89
|
-
${parts.join('\n')}
|
|
90
|
-
</svg>`;
|
|
140
|
+
return renderBlocksLayoutSvg(layout, { title: titleSvg, embedCssTheme: 'transitrix' });
|
|
91
141
|
}
|
|
@@ -26,29 +26,30 @@ import { generateSvgEmbedCss } from '../theme/index.js';
|
|
|
26
26
|
import { escXml } from './render-util.js';
|
|
27
27
|
|
|
28
28
|
const COL_LABELS: Record<FGCAPreviewColumn, string> = {
|
|
29
|
-
|
|
29
|
+
driver: 'Drivers (D)',
|
|
30
30
|
goal: 'Goals (G)',
|
|
31
31
|
change: 'Changes (C)',
|
|
32
32
|
activity: 'Activities (A)',
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
/** `'fga'` hides the Changes column; defaults to `'fgca'`. */
|
|
37
|
-
variant?: 'fgca' | 'fga';
|
|
38
|
-
/** Optional heading rendered as a left-anchored `text-header` line. */
|
|
39
|
-
title?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): string {
|
|
43
|
-
const { variant = 'fgca', title = '' } = options;
|
|
44
|
-
const hideChanges = variant === 'fga';
|
|
45
|
-
|
|
46
|
-
const { nodes, edges, columns, width, height } = layoutFGCAPreview(doc, { hideChanges });
|
|
47
|
-
|
|
48
|
-
if (nodes.length === 0) {
|
|
49
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
50
|
-
}
|
|
35
|
+
type FgcaLayout = ReturnType<typeof layoutFGCAPreview>;
|
|
51
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Canonical FGCA/FGA body — the column headers, node rects and edge paths that
|
|
39
|
+
* go inside the `<svg>`, shared verbatim by the VS Code preview (`buildSvg`)
|
|
40
|
+
* and the host-neutral wrapper below. Excludes the host-specific title block
|
|
41
|
+
* and the (identical) `<defs>` arrow marker. Coordinates are absolute (the
|
|
42
|
+
* layout's own); the VS Code path wraps this in a `translate(0, titleH)` group
|
|
43
|
+
* to make room for its title block, while the host-neutral wrapper renders it
|
|
44
|
+
* untranslated.
|
|
45
|
+
*/
|
|
46
|
+
export function renderFgcaBody(
|
|
47
|
+
columns: FgcaLayout['columns'],
|
|
48
|
+
nodes: FgcaLayout['nodes'],
|
|
49
|
+
edges: FgcaLayout['edges'],
|
|
50
|
+
curvature: number,
|
|
51
|
+
entryCurvature: number | undefined,
|
|
52
|
+
): string {
|
|
52
53
|
const headerSvg = columns
|
|
53
54
|
.map(({ col, x }) =>
|
|
54
55
|
[
|
|
@@ -58,13 +59,10 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
|
|
|
58
59
|
)
|
|
59
60
|
.join('\n');
|
|
60
61
|
|
|
61
|
-
// Cubic bezier with horizontal control handles (shared geometry in
|
|
62
|
-
// @transitrix/diagrams). Uses the package default curvature — the
|
|
63
|
-
// interactive curvature control is a VS Code-only concern.
|
|
64
62
|
const edgeSvg = edges
|
|
65
63
|
.map(
|
|
66
64
|
(e) =>
|
|
67
|
-
`<path d="${horizontalCubicEdgePath(e.sx, e.sy, e.tx, e.ty,
|
|
65
|
+
`<path d="${horizontalCubicEdgePath(e.sx, e.sy, e.tx, e.ty, curvature, entryCurvature)}" class="diagram-edge" marker-end="url(#arrow)"/>`,
|
|
68
66
|
)
|
|
69
67
|
.join('\n');
|
|
70
68
|
|
|
@@ -84,20 +82,49 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
|
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
const twoLines = line2.length > 0;
|
|
87
|
-
const y1 = twoLines ? n.y +
|
|
88
|
-
const y2 =
|
|
85
|
+
const y1 = twoLines ? n.y + 16 : n.y + 26;
|
|
86
|
+
const y2 = n.y + 32;
|
|
87
|
+
const idY = twoLines ? n.y + 54 : n.y + 50;
|
|
88
|
+
const entityId = n.id.slice(n.id.indexOf('_') + 1);
|
|
89
89
|
return [
|
|
90
90
|
`<rect class="diagram-node layer-${n.col}" x="${n.x}" y="${n.y}" width="${NODE_W}" height="${NODE_H}" rx="8"/>`,
|
|
91
|
-
`<text class="text-primary" x="${n.x + NODE_W / 2}" y="${y1}" text-anchor="middle">${escXml(line1)}</text>`,
|
|
91
|
+
`<text class="text-primary" x="${n.x + NODE_W / 2}" y="${y1}" text-anchor="middle" dominant-baseline="central">${escXml(line1)}</text>`,
|
|
92
92
|
twoLines
|
|
93
|
-
? `<text class="text-secondary" x="${n.x + NODE_W / 2}" y="${y2}" text-anchor="middle">${escXml(line2)}</text>`
|
|
93
|
+
? `<text class="text-secondary" x="${n.x + NODE_W / 2}" y="${y2}" text-anchor="middle" dominant-baseline="central">${escXml(line2)}</text>`
|
|
94
94
|
: '',
|
|
95
|
+
`<text class="text-id" x="${n.x + NODE_W / 2}" y="${idY}" text-anchor="middle" dominant-baseline="central">${escXml(entityId)}</text>`,
|
|
95
96
|
]
|
|
96
97
|
.filter(Boolean)
|
|
97
98
|
.join('\n');
|
|
98
99
|
})
|
|
99
100
|
.join('\n');
|
|
100
101
|
|
|
102
|
+
return `${headerSvg}\n${nodeSvg}\n${edgeSvg}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface RenderFgcaOptions {
|
|
106
|
+
/** `'fga'` hides the Changes column; defaults to `'fgca'`. */
|
|
107
|
+
variant?: 'fgca' | 'fga';
|
|
108
|
+
/** Optional heading rendered as a left-anchored `text-header` line. */
|
|
109
|
+
title?: string;
|
|
110
|
+
/** Exit edge curvature; 1 = default, 0 = straight, higher = stronger arc. */
|
|
111
|
+
curvature?: number;
|
|
112
|
+
/** Entry curvature at the target node; defaults to `curvature` when omitted. */
|
|
113
|
+
entryCurvature?: number;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): string {
|
|
117
|
+
const { variant = 'fgca', title = '', curvature = DEFAULT_EDGE_CURVATURE, entryCurvature } = options;
|
|
118
|
+
const hideChanges = variant === 'fga';
|
|
119
|
+
|
|
120
|
+
const { nodes, edges, columns, width, height } = layoutFGCAPreview(doc, { hideChanges });
|
|
121
|
+
|
|
122
|
+
if (nodes.length === 0) {
|
|
123
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const body = renderFgcaBody(columns, nodes, edges, curvature, entryCurvature);
|
|
127
|
+
|
|
101
128
|
const titleSvg = title
|
|
102
129
|
? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(title)}</text>`
|
|
103
130
|
: '';
|
|
@@ -115,8 +142,6 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
|
|
|
115
142
|
</marker>
|
|
116
143
|
</defs>
|
|
117
144
|
${titleSvg}
|
|
118
|
-
${
|
|
119
|
-
${nodeSvg}
|
|
120
|
-
${edgeSvg}
|
|
145
|
+
${body}
|
|
121
146
|
</svg>`;
|
|
122
147
|
}
|
|
@@ -1,110 +1,133 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-safe SVG renderer for the canonical Goals notation.
|
|
3
|
-
*
|
|
4
|
-
* Step 3 of the IntelliJ epic (ADR 0001): the webview bundle must turn a
|
|
5
|
-
* validated GoalTree into renderable SVG so JCEF can drop it into the preview
|
|
6
|
-
* panel. The VS Code path lives in `extension/src/goals-preview.ts` and pulls
|
|
7
|
-
* in VS Code-specific concerns (themes, title block, save dialogs); this
|
|
8
|
-
* module is the host-neutral subset — pure layout → SVG with no VS Code APIs,
|
|
9
|
-
* no `node:fs`, no `node:path`.
|
|
10
|
-
*
|
|
11
|
-
* Step 4 will follow the same shape for the remaining ten notations.
|
|
12
|
-
*/
|
|
13
|
-
import { layoutGoalTree } from '../goals/layout.js';
|
|
14
|
-
import type { GoalTree, GoalTreeLayout, LaidOutEdge } from '../goals/types.js';
|
|
15
|
-
import { horizontalCubicEdgePath, DEFAULT_EDGE_CURVATURE } from '../edge-path.js';
|
|
16
|
-
import { generateSvgEmbedCss } from '../theme/index.js';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe SVG renderer for the canonical Goals notation.
|
|
3
|
+
*
|
|
4
|
+
* Step 3 of the IntelliJ epic (ADR 0001): the webview bundle must turn a
|
|
5
|
+
* validated GoalTree into renderable SVG so JCEF can drop it into the preview
|
|
6
|
+
* panel. The VS Code path lives in `extension/src/goals-preview.ts` and pulls
|
|
7
|
+
* in VS Code-specific concerns (themes, title block, save dialogs); this
|
|
8
|
+
* module is the host-neutral subset — pure layout → SVG with no VS Code APIs,
|
|
9
|
+
* no `node:fs`, no `node:path`.
|
|
10
|
+
*
|
|
11
|
+
* Step 4 will follow the same shape for the remaining ten notations.
|
|
12
|
+
*/
|
|
13
|
+
import { layoutGoalTree } from '../goals/layout.js';
|
|
14
|
+
import type { GoalTree, GoalTreeLayout, LaidOutEdge } from '../goals/types.js';
|
|
15
|
+
import { horizontalCubicEdgePath, DEFAULT_EDGE_CURVATURE } from '../edge-path.js';
|
|
16
|
+
import { generateSvgEmbedCss, type ThemeId } from '../theme/index.js';
|
|
17
|
+
import { escXml } from './render-util.js';
|
|
18
|
+
|
|
19
|
+
const NODE_W = 250;
|
|
20
|
+
const NODE_H = 60;
|
|
21
|
+
const RANK_SEP = 100;
|
|
22
|
+
const NODE_SEP = 24;
|
|
23
|
+
const PAD = 24;
|
|
24
|
+
const LABEL_MAX = 36;
|
|
25
|
+
const LABEL_TRIM = 34;
|
|
26
|
+
|
|
27
|
+
export interface RenderGoalsOptions {
|
|
28
|
+
treeName?: string;
|
|
29
|
+
curvature?: number;
|
|
30
|
+
entryCurvature?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Host-neutral goals renderer (IntelliJ/UI). Lays the tree out with the default
|
|
35
|
+
* spacing, then delegates the actual SVG emission to {@link renderGoalsLayoutSvg}
|
|
36
|
+
* with the shared theme CSS embedded so the output is self-contained.
|
|
37
|
+
*/
|
|
38
|
+
export function renderGoalsSvg(tree: GoalTree, options: RenderGoalsOptions = {}): string {
|
|
39
|
+
const { treeName = '', curvature = DEFAULT_EDGE_CURVATURE, entryCurvature } = options;
|
|
40
|
+
|
|
41
|
+
const layout: GoalTreeLayout = layoutGoalTree(tree, {
|
|
42
|
+
nodeWidth: NODE_W,
|
|
43
|
+
nodeHeight: NODE_H,
|
|
44
|
+
rankSep: RANK_SEP,
|
|
45
|
+
nodeSep: NODE_SEP,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (layout.nodes.length === 0) {
|
|
49
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const title = treeName
|
|
53
|
+
? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(`Goal tree — ${treeName}`)}</text>`
|
|
54
|
+
: '';
|
|
55
|
+
|
|
56
|
+
// Embed the shared theme CSS so the JCEF host page only needs to drop the SVG
|
|
57
|
+
// into the DOM — styling resolves without help from the host stylesheet. The
|
|
58
|
+
// VS Code preview omits this (its webview supplies the CSS) and embeds it only
|
|
59
|
+
// on export via `prepareSvgForExport`.
|
|
60
|
+
return renderGoalsLayoutSvg(layout, { curvature, entryCurvature, title, embedCssTheme: 'transitrix' });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RenderGoalsLayoutOptions {
|
|
64
|
+
curvature?: number;
|
|
65
|
+
entryCurvature?: number;
|
|
66
|
+
/** Extra vertical space reserved at the top of the canvas (e.g. for a title block). */
|
|
67
|
+
topInset?: number;
|
|
68
|
+
/** Raw SVG injected immediately after `<defs>` — a header line or a full title block. */
|
|
69
|
+
title?: string;
|
|
70
|
+
/** When set, the theme CSS is embedded as `<style>` so the SVG is self-contained. */
|
|
71
|
+
embedCssTheme?: ThemeId;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The single goals SVG emitter shared by every host. Takes an already-computed
|
|
76
|
+
* {@link GoalTreeLayout} (callers decide spacing/scope) and produces the `<svg>`.
|
|
77
|
+
* Hosts wrap it with their own chrome:
|
|
78
|
+
* - IntelliJ/UI via {@link renderGoalsSvg} (embedded CSS + simple header);
|
|
79
|
+
* - VS Code's goals preview (rich title block, no embedded CSS — the webview
|
|
80
|
+
* and the export path own styling).
|
|
81
|
+
*/
|
|
82
|
+
export function renderGoalsLayoutSvg(layout: GoalTreeLayout, options: RenderGoalsLayoutOptions = {}): string {
|
|
83
|
+
const { curvature = DEFAULT_EDGE_CURVATURE, entryCurvature, topInset = 0, title = '', embedCssTheme } = options;
|
|
84
|
+
|
|
85
|
+
const w = layout.bounds.width + PAD * 2;
|
|
86
|
+
const h = layout.bounds.height + PAD * 2 + topInset;
|
|
87
|
+
const ox = -layout.bounds.x + PAD;
|
|
88
|
+
const oy = -layout.bounds.y + PAD + topInset;
|
|
89
|
+
|
|
90
|
+
const nodeMap = new Map(layout.nodes.map((n) => [n.id, n]));
|
|
91
|
+
|
|
92
|
+
function edgePath(e: LaidOutEdge): string {
|
|
93
|
+
const s = nodeMap.get(e.source);
|
|
94
|
+
const t = nodeMap.get(e.target);
|
|
95
|
+
if (!s || !t) return '';
|
|
96
|
+
const sx = s.x + ox + s.width;
|
|
97
|
+
const sy = s.y + oy + s.height / 2;
|
|
98
|
+
const tx = t.x + ox;
|
|
99
|
+
const ty = t.y + oy + t.height / 2;
|
|
100
|
+
return horizontalCubicEdgePath(sx, sy, tx, ty, curvature, entryCurvature);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const edgeSvg = layout.edges
|
|
104
|
+
.map((e) => `<path d="${edgePath(e)}" class="diagram-edge" marker-end="url(#arrow)"/>`)
|
|
105
|
+
.join('\n');
|
|
106
|
+
|
|
107
|
+
const nodeSvg = layout.nodes
|
|
108
|
+
.map((n) => {
|
|
109
|
+
const x = n.x + ox;
|
|
110
|
+
const y = n.y + oy;
|
|
111
|
+
const level = n.data.level % 8;
|
|
112
|
+
const labelText = n.data.name ?? String(n.id);
|
|
113
|
+
const label = labelText.length > LABEL_MAX ? labelText.slice(0, LABEL_TRIM) + '…' : labelText;
|
|
114
|
+
return `<g>
|
|
115
|
+
<rect class="diagram-node level-${level}" x="${x}" y="${y}" width="${n.width}" height="${n.height}" rx="8"/>
|
|
116
|
+
<text class="text-primary" x="${x + n.width / 2}" y="${y + n.height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(label)}</text>
|
|
117
|
+
</g>`;
|
|
118
|
+
})
|
|
119
|
+
.join('\n');
|
|
120
|
+
|
|
121
|
+
const styleLine = embedCssTheme ? `\n<style>${generateSvgEmbedCss(embedCssTheme)}</style>` : '';
|
|
122
|
+
|
|
123
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">${styleLine}
|
|
124
|
+
<defs>
|
|
125
|
+
<marker id="arrow" markerWidth="8" markerHeight="8" refX="8" refY="3" orient="auto">
|
|
126
|
+
<path d="M0,0 L0,6 L8,3 z" class="arrow-fill"/>
|
|
127
|
+
</marker>
|
|
128
|
+
</defs>
|
|
129
|
+
${title}
|
|
130
|
+
${nodeSvg}
|
|
131
|
+
${edgeSvg}
|
|
132
|
+
</svg>`;
|
|
133
|
+
}
|