@diagrammo/dgmo 0.8.19 → 0.8.21
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/cli.cjs +92 -131
- package/dist/editor.cjs +13 -1
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js +13 -1
- package/dist/editor.js.map +1 -1
- package/dist/highlight.cjs +13 -1
- package/dist/highlight.cjs.map +1 -1
- package/dist/highlight.js +13 -1
- package/dist/highlight.js.map +1 -1
- package/dist/index.cjs +4524 -1511
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +427 -186
- package/dist/index.d.ts +427 -186
- package/dist/index.js +4526 -1503
- package/dist/index.js.map +1 -1
- package/docs/guide/chart-mindmap.md +198 -0
- package/docs/guide/chart-sequence.md +23 -1
- package/docs/guide/chart-wireframe.md +100 -0
- package/docs/guide/index.md +8 -0
- package/docs/language-reference.md +210 -2
- package/package.json +22 -9
- package/src/boxes-and-lines/collapse.ts +21 -3
- package/src/boxes-and-lines/layout.ts +51 -9
- package/src/boxes-and-lines/parser.ts +16 -4
- package/src/boxes-and-lines/renderer.ts +121 -23
- package/src/boxes-and-lines/types.ts +1 -0
- package/src/c4/parser.ts +8 -7
- package/src/class/parser.ts +6 -0
- package/src/cli.ts +1 -9
- package/src/completion.ts +26 -0
- package/src/d3.ts +169 -266
- package/src/dgmo-router.ts +103 -5
- package/src/diagnostics.ts +16 -6
- package/src/echarts.ts +43 -10
- package/src/editor/keywords.ts +12 -0
- package/src/er/parser.ts +22 -2
- package/src/gantt/renderer.ts +2 -2
- package/src/graph/flowchart-parser.ts +89 -52
- package/src/graph/layout.ts +73 -9
- package/src/graph/state-collapse.ts +78 -0
- package/src/graph/state-parser.ts +60 -35
- package/src/graph/state-renderer.ts +139 -34
- package/src/index.ts +41 -16
- package/src/infra/parser.ts +9 -2
- package/src/kanban/renderer.ts +305 -59
- package/src/mindmap/collapse.ts +88 -0
- package/src/mindmap/layout.ts +605 -0
- package/src/mindmap/parser.ts +379 -0
- package/src/mindmap/renderer.ts +543 -0
- package/src/mindmap/text-wrap.ts +207 -0
- package/src/mindmap/types.ts +55 -0
- package/src/palettes/color-utils.ts +4 -12
- package/src/palettes/index.ts +0 -4
- package/src/render.ts +31 -20
- package/src/sequence/parser.ts +7 -2
- package/src/sequence/renderer.ts +141 -21
- package/src/sharing.ts +2 -0
- package/src/sitemap/layout.ts +35 -12
- package/src/sitemap/renderer.ts +1 -6
- package/src/utils/arrows.ts +180 -11
- package/src/utils/d3-types.ts +4 -0
- package/src/utils/export-container.ts +3 -2
- package/src/utils/legend-constants.ts +0 -4
- package/src/utils/legend-d3.ts +1 -0
- package/src/utils/legend-layout.ts +2 -2
- package/src/utils/parsing.ts +2 -0
- package/src/utils/time-ticks.ts +213 -0
- package/src/wireframe/layout.ts +460 -0
- package/src/wireframe/parser.ts +956 -0
- package/src/wireframe/renderer.ts +1293 -0
- package/src/wireframe/types.ts +110 -0
- package/src/branding.ts +0 -67
- package/src/dgmo-mermaid.ts +0 -262
- package/src/palettes/mermaid-bridge.ts +0 -220
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Wireframe Diagram Types
|
|
3
|
+
// ============================================================
|
|
4
|
+
|
|
5
|
+
import type { DgmoError } from '../diagnostics';
|
|
6
|
+
import type { TagGroup } from '../utils/tag-groups';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* All wireframe element types.
|
|
10
|
+
* Visual-mnemonic elements are inferred from bracket syntax;
|
|
11
|
+
* keyword elements use a small vocabulary (9 keywords).
|
|
12
|
+
*/
|
|
13
|
+
export type WireframeElementType =
|
|
14
|
+
| 'group'
|
|
15
|
+
| 'textInput'
|
|
16
|
+
| 'button'
|
|
17
|
+
| 'dropdown'
|
|
18
|
+
| 'checkbox'
|
|
19
|
+
| 'radio'
|
|
20
|
+
| 'heading'
|
|
21
|
+
| 'divider'
|
|
22
|
+
| 'text'
|
|
23
|
+
| 'listItem'
|
|
24
|
+
| 'nav'
|
|
25
|
+
| 'tabs'
|
|
26
|
+
| 'table'
|
|
27
|
+
| 'image'
|
|
28
|
+
| 'modal'
|
|
29
|
+
| 'skeleton'
|
|
30
|
+
| 'alert'
|
|
31
|
+
| 'progress'
|
|
32
|
+
| 'chart';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Single flat interface for all wireframe elements (ADR-8).
|
|
36
|
+
* No separate WireframeGroup — all elements carry group fields
|
|
37
|
+
* with sensible defaults (isContainer=false, orientation='vertical', isSkeleton=false).
|
|
38
|
+
*/
|
|
39
|
+
export interface WireframeElement {
|
|
40
|
+
id: string;
|
|
41
|
+
type: WireframeElementType;
|
|
42
|
+
/** Display label / placeholder text / heading text */
|
|
43
|
+
label: string;
|
|
44
|
+
/** Child elements (non-empty only when isContainer=true) */
|
|
45
|
+
children: WireframeElement[];
|
|
46
|
+
/** Pipe metadata key-value pairs */
|
|
47
|
+
metadata: Record<string, string>;
|
|
48
|
+
/** State keywords: disabled, active, ghost, destructive, etc. */
|
|
49
|
+
states: string[];
|
|
50
|
+
/** Free-text annotations from pipe metadata */
|
|
51
|
+
annotations: string[];
|
|
52
|
+
/** 1-based line number in source */
|
|
53
|
+
lineNumber: number;
|
|
54
|
+
/** Measured indentation (column) */
|
|
55
|
+
indent: number;
|
|
56
|
+
/** True when element has children (set during parse via indent stack) */
|
|
57
|
+
isContainer: boolean;
|
|
58
|
+
/** Stacking direction for group children */
|
|
59
|
+
orientation: 'vertical' | 'horizontal';
|
|
60
|
+
/** True when inside a skeleton block */
|
|
61
|
+
isSkeleton: boolean;
|
|
62
|
+
|
|
63
|
+
// ── Type-specific fields ─────────────────────────────────
|
|
64
|
+
/** Heading level: 1 for `#`, 2 for `##` */
|
|
65
|
+
headingLevel?: number;
|
|
66
|
+
/** Dropdown options (for type='dropdown') */
|
|
67
|
+
options?: string[];
|
|
68
|
+
/** Checked state (for type='checkbox') */
|
|
69
|
+
checked?: boolean;
|
|
70
|
+
/** Selected state (for type='radio') */
|
|
71
|
+
selected?: boolean;
|
|
72
|
+
/** Image hint: 'default' | 'round' | 'wide' */
|
|
73
|
+
imageHint?: 'default' | 'round' | 'wide';
|
|
74
|
+
/** Progress value 0-100 (for type='progress') */
|
|
75
|
+
progressValue?: number;
|
|
76
|
+
/** Chart hint: 'line' | 'bar' | 'pie' */
|
|
77
|
+
chartHint?: 'line' | 'bar' | 'pie';
|
|
78
|
+
/** Table dimensions for skeleton shorthand (for type='table') */
|
|
79
|
+
tableRows?: number;
|
|
80
|
+
tableCols?: number;
|
|
81
|
+
/** Table header row labels (for type='table') */
|
|
82
|
+
tableHeaders?: string[];
|
|
83
|
+
/** Table data rows — each row is an array of cell content strings (for type='table') */
|
|
84
|
+
tableData?: string[][];
|
|
85
|
+
/** Inline elements on the same line (multi-element line) */
|
|
86
|
+
inlineElements?: WireframeElement[];
|
|
87
|
+
/** Label element for label-field pairing */
|
|
88
|
+
labelFor?: WireframeElement;
|
|
89
|
+
/** Color from tag system */
|
|
90
|
+
color?: string;
|
|
91
|
+
/** Field variant: password, textarea */
|
|
92
|
+
fieldVariant?: 'password' | 'textarea';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Form factor / layout mode */
|
|
96
|
+
export type WireframeFormFactor = 'desktop' | 'mobile';
|
|
97
|
+
|
|
98
|
+
export interface ParsedWireframe {
|
|
99
|
+
title: string | null;
|
|
100
|
+
titleLineNumber: number | null;
|
|
101
|
+
formFactor: WireframeFormFactor;
|
|
102
|
+
/** Top-level elements (roots of the hierarchy) */
|
|
103
|
+
roots: WireframeElement[];
|
|
104
|
+
/** Modal elements (rendered separately below main) */
|
|
105
|
+
modals: WireframeElement[];
|
|
106
|
+
tagGroups: TagGroup[];
|
|
107
|
+
options: Record<string, string>;
|
|
108
|
+
diagnostics: DgmoError[];
|
|
109
|
+
error: string | null;
|
|
110
|
+
}
|
package/src/branding.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { FONT_FAMILY } from './fonts';
|
|
2
|
-
|
|
3
|
-
const BRANDING_HEIGHT = 20;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Injects `diagrammo.app` branding text into an SVG string.
|
|
7
|
-
* Extends the SVG height by 20px and places the text at the bottom-right.
|
|
8
|
-
*/
|
|
9
|
-
export function injectBranding(svgHtml: string, mutedColor: string): string {
|
|
10
|
-
if (!svgHtml) return svgHtml;
|
|
11
|
-
|
|
12
|
-
const brandingText = `<text x="0" y="0" font-size="10" font-family="${FONT_FAMILY}" fill="${mutedColor}" opacity="0.5" text-anchor="end">diagrammo.app</text>`;
|
|
13
|
-
|
|
14
|
-
// Parse viewBox
|
|
15
|
-
const vbMatch = svgHtml.match(/viewBox="([^"]+)"/);
|
|
16
|
-
const heightAttrMatch = svgHtml.match(/height="([^"]+)"/);
|
|
17
|
-
|
|
18
|
-
if (vbMatch) {
|
|
19
|
-
const parts = vbMatch[1].split(/\s+/).map(Number);
|
|
20
|
-
if (parts.length === 4) {
|
|
21
|
-
const [vx, vy, vw, vh] = parts;
|
|
22
|
-
const newVh = vh + BRANDING_HEIGHT;
|
|
23
|
-
const textX = vx + vw - 4;
|
|
24
|
-
const textY = vy + vh + BRANDING_HEIGHT - 6;
|
|
25
|
-
const positioned = brandingText.replace('x="0" y="0"', `x="${textX}" y="${textY}"`);
|
|
26
|
-
|
|
27
|
-
let result = svgHtml.replace(
|
|
28
|
-
/viewBox="[^"]+"/,
|
|
29
|
-
`viewBox="${vx} ${vy} ${vw} ${newVh}"`
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
// Update height attribute if present
|
|
33
|
-
if (heightAttrMatch) {
|
|
34
|
-
const oldH = parseFloat(heightAttrMatch[1]);
|
|
35
|
-
if (!isNaN(oldH)) {
|
|
36
|
-
result = result.replace(
|
|
37
|
-
`height="${heightAttrMatch[1]}"`,
|
|
38
|
-
`height="${oldH + BRANDING_HEIGHT}"`
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
result = result.replace('</svg>', `${positioned}</svg>`);
|
|
44
|
-
return result;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Fallback: no viewBox, try width/height attributes
|
|
49
|
-
if (heightAttrMatch) {
|
|
50
|
-
const widthMatch = svgHtml.match(/width="([^"]+)"/);
|
|
51
|
-
const w = widthMatch ? parseFloat(widthMatch[1]) : 800;
|
|
52
|
-
const h = parseFloat(heightAttrMatch[1]);
|
|
53
|
-
if (!isNaN(h) && !isNaN(w)) {
|
|
54
|
-
const textX = w - 4;
|
|
55
|
-
const textY = h + BRANDING_HEIGHT - 6;
|
|
56
|
-
const positioned = brandingText.replace('x="0" y="0"', `x="${textX}" y="${textY}"`);
|
|
57
|
-
let result = svgHtml.replace(
|
|
58
|
-
`height="${heightAttrMatch[1]}"`,
|
|
59
|
-
`height="${h + BRANDING_HEIGHT}"`
|
|
60
|
-
);
|
|
61
|
-
result = result.replace('</svg>', `${positioned}</svg>`);
|
|
62
|
-
return result;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return svgHtml;
|
|
67
|
-
}
|
package/src/dgmo-mermaid.ts
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// .dgmo → Mermaid Translation Layer
|
|
3
|
-
// Parses dgmo quadrant syntax and generates valid Mermaid code.
|
|
4
|
-
// ============================================================
|
|
5
|
-
|
|
6
|
-
import { resolveColorWithDiagnostic } from './colors';
|
|
7
|
-
import type { DgmoError } from './diagnostics';
|
|
8
|
-
import { makeDgmoError, formatDgmoError } from './diagnostics';
|
|
9
|
-
|
|
10
|
-
// ============================================================
|
|
11
|
-
// Types
|
|
12
|
-
// ============================================================
|
|
13
|
-
|
|
14
|
-
interface QuadrantLabel {
|
|
15
|
-
text: string;
|
|
16
|
-
color: string | null;
|
|
17
|
-
lineNumber: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface ParsedQuadrant {
|
|
21
|
-
title: string | null;
|
|
22
|
-
titleLineNumber: number | null;
|
|
23
|
-
xAxis: [string, string] | null;
|
|
24
|
-
xAxisLineNumber: number | null;
|
|
25
|
-
yAxis: [string, string] | null;
|
|
26
|
-
yAxisLineNumber: number | null;
|
|
27
|
-
quadrants: {
|
|
28
|
-
topRight: QuadrantLabel | null;
|
|
29
|
-
topLeft: QuadrantLabel | null;
|
|
30
|
-
bottomLeft: QuadrantLabel | null;
|
|
31
|
-
bottomRight: QuadrantLabel | null;
|
|
32
|
-
};
|
|
33
|
-
points: { label: string; x: number; y: number; lineNumber: number }[];
|
|
34
|
-
diagnostics: DgmoError[];
|
|
35
|
-
error: string | null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ============================================================
|
|
39
|
-
// Parser
|
|
40
|
-
// ============================================================
|
|
41
|
-
|
|
42
|
-
/** Regex for quadrant label lines: `top-right Promote (green)` */
|
|
43
|
-
const QUADRANT_LABEL_RE = /^(.+?)(?:\s*\(([^)]+)\))?\s*$/;
|
|
44
|
-
|
|
45
|
-
/** Regex for data point lines: `Label 0.9, 0.5` */
|
|
46
|
-
const DATA_POINT_RE = /^(.+?)\s+([0-9]*\.?[0-9]+)\s*,\s*([0-9]*\.?[0-9]+)\s*$/;
|
|
47
|
-
|
|
48
|
-
const QUADRANT_POSITIONS = new Set([
|
|
49
|
-
'top-right',
|
|
50
|
-
'top-left',
|
|
51
|
-
'bottom-left',
|
|
52
|
-
'bottom-right',
|
|
53
|
-
]);
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Parses a .dgmo quadrant document into a structured object.
|
|
57
|
-
* Lines are processed sequentially; unknown lines are silently skipped.
|
|
58
|
-
*/
|
|
59
|
-
export function parseQuadrant(content: string): ParsedQuadrant {
|
|
60
|
-
const result: ParsedQuadrant = {
|
|
61
|
-
title: null,
|
|
62
|
-
titleLineNumber: null,
|
|
63
|
-
xAxis: null,
|
|
64
|
-
xAxisLineNumber: null,
|
|
65
|
-
yAxis: null,
|
|
66
|
-
yAxisLineNumber: null,
|
|
67
|
-
quadrants: {
|
|
68
|
-
topRight: null,
|
|
69
|
-
topLeft: null,
|
|
70
|
-
bottomLeft: null,
|
|
71
|
-
bottomRight: null,
|
|
72
|
-
},
|
|
73
|
-
points: [],
|
|
74
|
-
diagnostics: [],
|
|
75
|
-
error: null,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const lines = content.split('\n');
|
|
79
|
-
|
|
80
|
-
for (let i = 0; i < lines.length; i++) {
|
|
81
|
-
const line = lines[i].trim();
|
|
82
|
-
const lineNumber = i + 1; // 1-indexed for editor
|
|
83
|
-
|
|
84
|
-
// Skip empty lines and comments
|
|
85
|
-
if (!line || line.startsWith('//')) continue;
|
|
86
|
-
|
|
87
|
-
// Skip the chart: directive (already consumed by router)
|
|
88
|
-
if (/^chart\s*:/i.test(line)) continue;
|
|
89
|
-
|
|
90
|
-
// title <text>
|
|
91
|
-
const titleMatch = line.match(/^title\s+(.+)/i);
|
|
92
|
-
if (titleMatch) {
|
|
93
|
-
result.title = titleMatch[1].trim();
|
|
94
|
-
result.titleLineNumber = lineNumber;
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// x-label Low, High
|
|
99
|
-
const xMatch = line.match(/^x-label\s+(.+)/i);
|
|
100
|
-
if (xMatch) {
|
|
101
|
-
const parts = xMatch[1].split(',').map((s) => s.trim());
|
|
102
|
-
if (parts.length >= 2) {
|
|
103
|
-
result.xAxis = [parts[0], parts[1]];
|
|
104
|
-
result.xAxisLineNumber = lineNumber;
|
|
105
|
-
}
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// y-label Low, High
|
|
110
|
-
const yMatch = line.match(/^y-label\s+(.+)/i);
|
|
111
|
-
if (yMatch) {
|
|
112
|
-
const parts = yMatch[1].split(',').map((s) => s.trim());
|
|
113
|
-
if (parts.length >= 2) {
|
|
114
|
-
result.yAxis = [parts[0], parts[1]];
|
|
115
|
-
result.yAxisLineNumber = lineNumber;
|
|
116
|
-
}
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Quadrant position labels: top-right Label (color)
|
|
121
|
-
const posMatch = line.match(
|
|
122
|
-
/^(top-right|top-left|bottom-left|bottom-right)\s+(.+)/i
|
|
123
|
-
);
|
|
124
|
-
if (posMatch) {
|
|
125
|
-
const position = posMatch[1].toLowerCase();
|
|
126
|
-
const labelMatch = posMatch[2].match(QUADRANT_LABEL_RE);
|
|
127
|
-
if (labelMatch) {
|
|
128
|
-
const label: QuadrantLabel = {
|
|
129
|
-
text: labelMatch[1].trim(),
|
|
130
|
-
color: labelMatch[2]
|
|
131
|
-
? (resolveColorWithDiagnostic(
|
|
132
|
-
labelMatch[2].trim(),
|
|
133
|
-
lineNumber,
|
|
134
|
-
result.diagnostics
|
|
135
|
-
) ?? null)
|
|
136
|
-
: null,
|
|
137
|
-
lineNumber,
|
|
138
|
-
};
|
|
139
|
-
if (position === 'top-right') result.quadrants.topRight = label;
|
|
140
|
-
else if (position === 'top-left') result.quadrants.topLeft = label;
|
|
141
|
-
else if (position === 'bottom-left')
|
|
142
|
-
result.quadrants.bottomLeft = label;
|
|
143
|
-
else if (position === 'bottom-right')
|
|
144
|
-
result.quadrants.bottomRight = label;
|
|
145
|
-
}
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Data points: Label x, y
|
|
150
|
-
const pointMatch = line.match(DATA_POINT_RE);
|
|
151
|
-
if (pointMatch) {
|
|
152
|
-
// Make sure this isn't a quadrant position keyword
|
|
153
|
-
const key = pointMatch[1].trim().toLowerCase();
|
|
154
|
-
if (!QUADRANT_POSITIONS.has(key)) {
|
|
155
|
-
result.points.push({
|
|
156
|
-
label: pointMatch[1].trim(),
|
|
157
|
-
x: parseFloat(pointMatch[2]),
|
|
158
|
-
y: parseFloat(pointMatch[3]),
|
|
159
|
-
lineNumber,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (result.points.length === 0) {
|
|
167
|
-
const diag = makeDgmoError(
|
|
168
|
-
1,
|
|
169
|
-
'No data points found. Add lines like: Label 0.5, 0.7'
|
|
170
|
-
);
|
|
171
|
-
result.diagnostics.push(diag);
|
|
172
|
-
result.error = formatDgmoError(diag);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return result;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ============================================================
|
|
179
|
-
// Mermaid Builder
|
|
180
|
-
// ============================================================
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Generates valid Mermaid quadrantChart syntax from a parsed quadrant.
|
|
184
|
-
* Returns a string ready for the Mermaid renderer.
|
|
185
|
-
*/
|
|
186
|
-
export function buildMermaidQuadrant(
|
|
187
|
-
parsed: ParsedQuadrant,
|
|
188
|
-
options: {
|
|
189
|
-
isDark?: boolean;
|
|
190
|
-
textColor?: string;
|
|
191
|
-
mutedTextColor?: string;
|
|
192
|
-
} = {}
|
|
193
|
-
): string {
|
|
194
|
-
const { isDark = false, textColor, mutedTextColor } = options;
|
|
195
|
-
const lines: string[] = [];
|
|
196
|
-
|
|
197
|
-
// %%{init}%% block — fill colors with reduced opacity + text color overrides
|
|
198
|
-
const fillAlpha = isDark ? '30' : '55';
|
|
199
|
-
const primaryText = textColor ?? (isDark ? '#d0d0d0' : '#333333');
|
|
200
|
-
const quadrantLabelText = mutedTextColor ?? (isDark ? '#888888' : '#666666');
|
|
201
|
-
|
|
202
|
-
const colorMap: Record<string, string> = {};
|
|
203
|
-
if (parsed.quadrants.topRight?.color)
|
|
204
|
-
colorMap.quadrant1Fill = parsed.quadrants.topRight.color + fillAlpha;
|
|
205
|
-
if (parsed.quadrants.topLeft?.color)
|
|
206
|
-
colorMap.quadrant2Fill = parsed.quadrants.topLeft.color + fillAlpha;
|
|
207
|
-
if (parsed.quadrants.bottomLeft?.color)
|
|
208
|
-
colorMap.quadrant3Fill = parsed.quadrants.bottomLeft.color + fillAlpha;
|
|
209
|
-
if (parsed.quadrants.bottomRight?.color)
|
|
210
|
-
colorMap.quadrant4Fill = parsed.quadrants.bottomRight.color + fillAlpha;
|
|
211
|
-
|
|
212
|
-
// Quadrant labels use muted color, points use primary text color
|
|
213
|
-
colorMap.quadrant1TextFill = quadrantLabelText;
|
|
214
|
-
colorMap.quadrant2TextFill = quadrantLabelText;
|
|
215
|
-
colorMap.quadrant3TextFill = quadrantLabelText;
|
|
216
|
-
colorMap.quadrant4TextFill = quadrantLabelText;
|
|
217
|
-
colorMap.quadrantPointTextFill = primaryText;
|
|
218
|
-
colorMap.quadrantXAxisTextFill = primaryText;
|
|
219
|
-
colorMap.quadrantYAxisTextFill = primaryText;
|
|
220
|
-
colorMap.quadrantTitleFill = primaryText;
|
|
221
|
-
|
|
222
|
-
const vars = JSON.stringify(colorMap);
|
|
223
|
-
lines.push(`%%{init: {"themeVariables": ${vars}}}%%`);
|
|
224
|
-
|
|
225
|
-
lines.push('quadrantChart');
|
|
226
|
-
|
|
227
|
-
if (parsed.title) {
|
|
228
|
-
lines.push(` title ${parsed.title}`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (parsed.xAxis) {
|
|
232
|
-
lines.push(` x-axis ${parsed.xAxis[0]} --> ${parsed.xAxis[1]}`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (parsed.yAxis) {
|
|
236
|
-
lines.push(` y-axis ${parsed.yAxis[0]} --> ${parsed.yAxis[1]}`);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Helper to quote labels that need it (contain spaces or special chars)
|
|
240
|
-
const quote = (s: string): string => (/[\s,:[\]]/.test(s) ? `"${s}"` : s);
|
|
241
|
-
|
|
242
|
-
// Quadrant labels: 1=top-right, 2=top-left, 3=bottom-left, 4=bottom-right
|
|
243
|
-
if (parsed.quadrants.topRight) {
|
|
244
|
-
lines.push(` quadrant-1 ${quote(parsed.quadrants.topRight.text)}`);
|
|
245
|
-
}
|
|
246
|
-
if (parsed.quadrants.topLeft) {
|
|
247
|
-
lines.push(` quadrant-2 ${quote(parsed.quadrants.topLeft.text)}`);
|
|
248
|
-
}
|
|
249
|
-
if (parsed.quadrants.bottomLeft) {
|
|
250
|
-
lines.push(` quadrant-3 ${quote(parsed.quadrants.bottomLeft.text)}`);
|
|
251
|
-
}
|
|
252
|
-
if (parsed.quadrants.bottomRight) {
|
|
253
|
-
lines.push(` quadrant-4 ${quote(parsed.quadrants.bottomRight.text)}`);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Data points
|
|
257
|
-
for (const point of parsed.points) {
|
|
258
|
-
lines.push(` ${quote(point.label)}: [${point.x}, ${point.y}]`);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return lines.join('\n');
|
|
262
|
-
}
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import type { PaletteColors } from './types';
|
|
2
|
-
import { mute, tint, shade, contrastText } from './color-utils';
|
|
3
|
-
|
|
4
|
-
// ============================================================
|
|
5
|
-
// Mermaid Theme Variable Generator
|
|
6
|
-
// ============================================================
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Generates ~121 Mermaid theme variables from palette tokens.
|
|
10
|
-
* Replaces the hardcoded lightThemeVars/darkThemeVars objects.
|
|
11
|
-
*
|
|
12
|
-
* Dark mode fills use `mute()` to derive desaturated variants
|
|
13
|
-
* that are readable with light text.
|
|
14
|
-
*/
|
|
15
|
-
export function buildMermaidThemeVars(
|
|
16
|
-
colors: PaletteColors,
|
|
17
|
-
isDark: boolean
|
|
18
|
-
): Record<string, string> {
|
|
19
|
-
const c = colors.colors;
|
|
20
|
-
|
|
21
|
-
// Ordered accent array for pie/cScale/fillType/actor slots
|
|
22
|
-
const accentOrder = [
|
|
23
|
-
c.blue,
|
|
24
|
-
c.red,
|
|
25
|
-
c.green,
|
|
26
|
-
c.yellow,
|
|
27
|
-
c.purple,
|
|
28
|
-
c.orange,
|
|
29
|
-
c.teal,
|
|
30
|
-
c.cyan,
|
|
31
|
-
colors.secondary,
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
// Dark mode fills use muted variants for readability
|
|
35
|
-
const fills = isDark ? accentOrder.map(mute) : accentOrder;
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
// ── Backgrounds ──
|
|
39
|
-
background: isDark ? colors.overlay : colors.border,
|
|
40
|
-
mainBkg: colors.surface,
|
|
41
|
-
|
|
42
|
-
// ── Primary/Secondary/Tertiary nodes ──
|
|
43
|
-
primaryColor: isDark ? colors.primary : colors.surface,
|
|
44
|
-
primaryTextColor: colors.text,
|
|
45
|
-
primaryBorderColor: isDark ? colors.secondary : colors.border,
|
|
46
|
-
secondaryColor: colors.secondary,
|
|
47
|
-
secondaryTextColor: contrastText(colors.secondary, colors.text, colors.bg),
|
|
48
|
-
secondaryBorderColor: colors.primary,
|
|
49
|
-
tertiaryColor: colors.accent,
|
|
50
|
-
tertiaryTextColor: contrastText(colors.accent, colors.text, colors.bg),
|
|
51
|
-
tertiaryBorderColor: colors.border,
|
|
52
|
-
|
|
53
|
-
// ── Lines & text ──
|
|
54
|
-
lineColor: colors.textMuted,
|
|
55
|
-
textColor: colors.text,
|
|
56
|
-
|
|
57
|
-
// ── Clusters ──
|
|
58
|
-
clusterBkg: colors.bg,
|
|
59
|
-
clusterBorder: isDark ? colors.border : colors.textMuted,
|
|
60
|
-
titleColor: colors.text,
|
|
61
|
-
|
|
62
|
-
// ── Labels ──
|
|
63
|
-
edgeLabelBackground: 'transparent',
|
|
64
|
-
|
|
65
|
-
// ── Notes (sequence diagrams) ──
|
|
66
|
-
noteBkgColor: colors.bg,
|
|
67
|
-
noteTextColor: colors.text,
|
|
68
|
-
noteBorderColor: isDark ? colors.border : colors.textMuted,
|
|
69
|
-
|
|
70
|
-
// ── Actors (sequence diagrams) ──
|
|
71
|
-
actorBkg: colors.surface,
|
|
72
|
-
actorTextColor: colors.text,
|
|
73
|
-
actorBorder: isDark ? colors.border : colors.textMuted,
|
|
74
|
-
actorLineColor: colors.textMuted,
|
|
75
|
-
|
|
76
|
-
// ── Signals (sequence diagrams) ──
|
|
77
|
-
signalColor: colors.textMuted,
|
|
78
|
-
signalTextColor: colors.text,
|
|
79
|
-
|
|
80
|
-
// ── Labels ──
|
|
81
|
-
labelColor: colors.text,
|
|
82
|
-
labelTextColor: colors.text,
|
|
83
|
-
labelBoxBkgColor: colors.surface,
|
|
84
|
-
labelBoxBorderColor: isDark ? colors.border : colors.textMuted,
|
|
85
|
-
|
|
86
|
-
// ── Loop boxes ──
|
|
87
|
-
loopTextColor: colors.text,
|
|
88
|
-
|
|
89
|
-
// ── Activation (sequence diagrams) ──
|
|
90
|
-
activationBkgColor: isDark ? colors.overlay : colors.border,
|
|
91
|
-
activationBorderColor: isDark ? colors.border : colors.textMuted,
|
|
92
|
-
|
|
93
|
-
// ── Sequence numbers ──
|
|
94
|
-
sequenceNumberColor: isDark ? colors.text : colors.bg,
|
|
95
|
-
|
|
96
|
-
// ── State diagrams ──
|
|
97
|
-
labelBackgroundColor: colors.surface,
|
|
98
|
-
|
|
99
|
-
// ── Pie chart (9 slices) ──
|
|
100
|
-
// Dark mode: use muted fills so light pieSectionTextColor stays readable
|
|
101
|
-
...Object.fromEntries(
|
|
102
|
-
(isDark ? fills : accentOrder).map((col, i) => [`pie${i + 1}`, col])
|
|
103
|
-
),
|
|
104
|
-
pieTitleTextColor: colors.text,
|
|
105
|
-
pieSectionTextColor: isDark ? colors.text : colors.bg,
|
|
106
|
-
pieLegendTextColor: colors.text,
|
|
107
|
-
pieStrokeColor: 'transparent',
|
|
108
|
-
pieOuterStrokeWidth: '0px',
|
|
109
|
-
pieOuterStrokeColor: 'transparent',
|
|
110
|
-
|
|
111
|
-
// ── cScale (9 tiers) — muted in dark mode ──
|
|
112
|
-
...Object.fromEntries(fills.map((f, i) => [`cScale${i}`, f])),
|
|
113
|
-
...Object.fromEntries(
|
|
114
|
-
fills.map((_, i) => [
|
|
115
|
-
`cScaleLabel${i}`,
|
|
116
|
-
isDark ? colors.text : i < 2 || i > 6 ? colors.bg : colors.text,
|
|
117
|
-
])
|
|
118
|
-
),
|
|
119
|
-
|
|
120
|
-
// ── fillType (8 slots) ──
|
|
121
|
-
...Object.fromEntries(
|
|
122
|
-
[0, 1, 2, 3, 4, 5, 6, 7].map((i) => [
|
|
123
|
-
`fillType${i}`,
|
|
124
|
-
fills[i % fills.length],
|
|
125
|
-
])
|
|
126
|
-
),
|
|
127
|
-
|
|
128
|
-
// ── Journey actors (6 slots) ──
|
|
129
|
-
...Object.fromEntries(
|
|
130
|
-
[c.red, c.green, c.yellow, c.purple, c.orange, c.teal].map((color, i) => [
|
|
131
|
-
`actor${i}`,
|
|
132
|
-
color,
|
|
133
|
-
])
|
|
134
|
-
),
|
|
135
|
-
|
|
136
|
-
// ── Flowchart ──
|
|
137
|
-
nodeBorder: isDark ? colors.border : colors.textMuted,
|
|
138
|
-
nodeTextColor: colors.text,
|
|
139
|
-
|
|
140
|
-
// ── Gantt ──
|
|
141
|
-
gridColor: isDark ? colors.textMuted : colors.border,
|
|
142
|
-
doneTaskBkgColor: c.green,
|
|
143
|
-
doneTaskBorderColor: isDark ? colors.border : colors.textMuted,
|
|
144
|
-
activeTaskBkgColor: colors.secondary,
|
|
145
|
-
activeTaskBorderColor: colors.primary,
|
|
146
|
-
critBkgColor: c.orange,
|
|
147
|
-
critBorderColor: c.red,
|
|
148
|
-
taskBkgColor: colors.surface,
|
|
149
|
-
taskBorderColor: isDark ? colors.border : colors.textMuted,
|
|
150
|
-
taskTextColor: contrastText(colors.surface, colors.text, colors.bg),
|
|
151
|
-
taskTextDarkColor: colors.bg,
|
|
152
|
-
taskTextLightColor: colors.text,
|
|
153
|
-
taskTextOutsideColor: colors.text,
|
|
154
|
-
doneTaskTextColor: contrastText(c.green, colors.text, colors.bg),
|
|
155
|
-
activeTaskTextColor: contrastText(colors.secondary, colors.text, colors.bg),
|
|
156
|
-
critTaskTextColor: contrastText(c.orange, colors.text, colors.bg),
|
|
157
|
-
sectionBkgColor: isDark
|
|
158
|
-
? shade(colors.primary, colors.bg, 0.6)
|
|
159
|
-
: tint(colors.primary, 0.6),
|
|
160
|
-
altSectionBkgColor: colors.bg,
|
|
161
|
-
sectionBkgColor2: isDark
|
|
162
|
-
? shade(colors.primary, colors.bg, 0.6)
|
|
163
|
-
: tint(colors.primary, 0.6),
|
|
164
|
-
todayLineColor: c.yellow,
|
|
165
|
-
|
|
166
|
-
// ── Quadrant ──
|
|
167
|
-
quadrant1Fill: isDark
|
|
168
|
-
? shade(c.green, colors.bg, 0.75)
|
|
169
|
-
: tint(c.green, 0.75),
|
|
170
|
-
quadrant2Fill: isDark ? shade(c.blue, colors.bg, 0.75) : tint(c.blue, 0.75),
|
|
171
|
-
quadrant3Fill: isDark ? shade(c.red, colors.bg, 0.75) : tint(c.red, 0.75),
|
|
172
|
-
quadrant4Fill: isDark
|
|
173
|
-
? shade(c.yellow, colors.bg, 0.75)
|
|
174
|
-
: tint(c.yellow, 0.75),
|
|
175
|
-
quadrant1TextFill: colors.text,
|
|
176
|
-
quadrant2TextFill: colors.text,
|
|
177
|
-
quadrant3TextFill: colors.text,
|
|
178
|
-
quadrant4TextFill: colors.text,
|
|
179
|
-
quadrantPointFill: isDark ? c.cyan : c.blue,
|
|
180
|
-
quadrantPointTextFill: colors.text,
|
|
181
|
-
quadrantXAxisTextFill: colors.text,
|
|
182
|
-
quadrantYAxisTextFill: colors.text,
|
|
183
|
-
quadrantTitleFill: colors.text,
|
|
184
|
-
quadrantInternalBorderStrokeFill: colors.border,
|
|
185
|
-
quadrantExternalBorderStrokeFill: colors.border,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ============================================================
|
|
190
|
-
// Mermaid Theme CSS Generator
|
|
191
|
-
// ============================================================
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Generates custom CSS overrides for Mermaid SVGs.
|
|
195
|
-
* Handles git graph label backgrounds and dark-mode text readability.
|
|
196
|
-
*/
|
|
197
|
-
export function buildThemeCSS(palette: PaletteColors, isDark: boolean): string {
|
|
198
|
-
const base = `
|
|
199
|
-
.branchLabelBkg { fill: transparent !important; stroke: transparent !important; }
|
|
200
|
-
.commit-label-bkg { fill: transparent !important; stroke: transparent !important; }
|
|
201
|
-
.tag-label-bkg { fill: transparent !important; stroke: transparent !important; }
|
|
202
|
-
|
|
203
|
-
/* GitGraph: ensure commit and branch label text matches palette */
|
|
204
|
-
.commit-label { fill: ${palette.text} !important; }
|
|
205
|
-
.branch-label { fill: ${palette.text} !important; }
|
|
206
|
-
.tag-label { fill: ${palette.text} !important; }
|
|
207
|
-
`;
|
|
208
|
-
|
|
209
|
-
if (!isDark) return base;
|
|
210
|
-
|
|
211
|
-
return (
|
|
212
|
-
base +
|
|
213
|
-
`
|
|
214
|
-
/* Flowchart: ensure node and edge label text is readable */
|
|
215
|
-
.nodeLabel, .label { color: ${palette.text} !important; fill: ${palette.text} !important; }
|
|
216
|
-
.edgeLabel { color: ${palette.text} !important; fill: ${palette.text} !important; }
|
|
217
|
-
.edgeLabel .label { color: ${palette.text} !important; fill: ${palette.text} !important; }
|
|
218
|
-
`
|
|
219
|
-
);
|
|
220
|
-
}
|