@cardenelabs/dragon 0.7.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 -0
- package/README.md +173 -0
- package/dist/index.cjs +4916 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1451 -0
- package/dist/index.d.ts +1451 -0
- package/dist/index.js +4870 -0
- package/dist/index.js.map +1 -0
- package/examples/quick-start.md +142 -0
- package/package.json +79 -0
- package/src/canvas-bounds.ts +52 -0
- package/src/color.ts +172 -0
- package/src/compile.ts +3739 -0
- package/src/focus.ts +46 -0
- package/src/index.ts +226 -0
- package/src/input-size.ts +154 -0
- package/src/json-parser.ts +434 -0
- package/src/keywords.ts +114 -0
- package/src/notation-lint.ts +304 -0
- package/src/parser.ts +354 -0
- package/src/relative-pos.ts +182 -0
- package/src/schema.ts +24 -0
- package/src/schemas/diagram.json +133 -0
- package/src/types.ts +294 -0
- package/src/v05/index.ts +9 -0
- package/src/v05/parser.ts +1678 -0
- package/src/write-position.ts +270 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4870 @@
|
|
|
1
|
+
import { NODE_KINDS, TONES, layout, diagram, rendersRows, requiredRowsHeight, requiredRowsWidth, sequence, flow, swimlane, er, stateMachine, topology } from '@cardenelabs/cdl';
|
|
2
|
+
|
|
3
|
+
// src/keywords.ts
|
|
4
|
+
var HEADERS = {
|
|
5
|
+
title: ["\u30BF\u30A4\u30C8\u30EB", "title"],
|
|
6
|
+
type: ["\u7A2E\u985E", "type"],
|
|
7
|
+
actors: ["\u767B\u5834\u4EBA\u7269", "actors"],
|
|
8
|
+
flow: ["\u6D41\u308C", "flow", "steps"],
|
|
9
|
+
animate: ["\u30A2\u30CB\u30E1\u30FC\u30B7\u30E7\u30F3", "animate", "animation"]
|
|
10
|
+
};
|
|
11
|
+
var PRESET_NAMES = ["sequence", "flow", "swimlane", "er", "state", "topology"];
|
|
12
|
+
var NODE_KIND_ALIAS = {
|
|
13
|
+
// 日本語
|
|
14
|
+
\u4EBA: "actor",
|
|
15
|
+
\u95A2\u6570: "function",
|
|
16
|
+
\u30B9\u30C8\u30EC\u30FC\u30B8: "storage",
|
|
17
|
+
\u30A4\u30D9\u30F3\u30C8: "event",
|
|
18
|
+
// 英語 (NodeKind そのまま)
|
|
19
|
+
actor: "actor",
|
|
20
|
+
function: "function",
|
|
21
|
+
storage: "storage",
|
|
22
|
+
event: "event",
|
|
23
|
+
cdn: "cdn",
|
|
24
|
+
service: "service",
|
|
25
|
+
database: "database",
|
|
26
|
+
cache: "cache",
|
|
27
|
+
queue: "queue"
|
|
28
|
+
// ... 残り 29 NodeKind は parser 内で types.NodeKind を直接受理
|
|
29
|
+
};
|
|
30
|
+
var TONE_ALIAS = {
|
|
31
|
+
// 日本語
|
|
32
|
+
\u6210\u529F: "success",
|
|
33
|
+
\u5931\u6557: "error",
|
|
34
|
+
\u8B66\u544A: "warning",
|
|
35
|
+
\u60C5\u5831: "info",
|
|
36
|
+
\u4E2D\u7ACB: "accent",
|
|
37
|
+
// 英語
|
|
38
|
+
success: "success",
|
|
39
|
+
error: "error",
|
|
40
|
+
warning: "warning",
|
|
41
|
+
info: "info",
|
|
42
|
+
neutral: "accent",
|
|
43
|
+
accent: "accent",
|
|
44
|
+
teal: "teal"
|
|
45
|
+
};
|
|
46
|
+
var ANIM_SUBKEYS = {
|
|
47
|
+
state: ["\u72B6\u614B", "state"],
|
|
48
|
+
step: ["\u30B9\u30C6\u30C3\u30D7", "step"],
|
|
49
|
+
highlight: ["\u5F37\u8ABF", "highlight", "active", "activate"],
|
|
50
|
+
tween: ["\u9077\u79FB", "tween"],
|
|
51
|
+
set: ["\u5207\u66FF", "set"],
|
|
52
|
+
body: ["\u8AAC\u660E", "body", "description"],
|
|
53
|
+
badge: ["\u30D0\u30C3\u30B8", "badge"]
|
|
54
|
+
};
|
|
55
|
+
var ARROW_PATTERNS = ["\u2192", "->", "=>", ">>", "->>", "-->>", "->>"];
|
|
56
|
+
function normalizeArrow(s) {
|
|
57
|
+
let r = s;
|
|
58
|
+
for (const p of ARROW_PATTERNS) {
|
|
59
|
+
r = r.split(p).join("\u2192");
|
|
60
|
+
}
|
|
61
|
+
return r;
|
|
62
|
+
}
|
|
63
|
+
function resolveHeader(s) {
|
|
64
|
+
const lower = s.toLowerCase().trim();
|
|
65
|
+
for (const [canon, aliases] of Object.entries(HEADERS)) {
|
|
66
|
+
if (aliases.some((a) => a.toLowerCase() === lower)) {
|
|
67
|
+
return canon;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function resolveAnimSubkey(s) {
|
|
73
|
+
const lower = s.toLowerCase().trim();
|
|
74
|
+
for (const [canon, aliases] of Object.entries(ANIM_SUBKEYS)) {
|
|
75
|
+
if (aliases.some((a) => a.toLowerCase() === lower)) {
|
|
76
|
+
return canon;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
function parseDuration(s) {
|
|
82
|
+
const trimmed = s.trim();
|
|
83
|
+
const sec = trimmed.match(/^([\d.]+)\s*秒$/);
|
|
84
|
+
if (sec) return Math.round(parseFloat(sec[1]) * 1e3);
|
|
85
|
+
const ms = trimmed.match(/^([\d.]+)\s*ms$/i);
|
|
86
|
+
if (ms) return Math.round(parseFloat(ms[1]));
|
|
87
|
+
const en = trimmed.match(/^([\d.]+)\s*s$/i);
|
|
88
|
+
if (en) return Math.round(parseFloat(en[1]) * 1e3);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/parser.ts
|
|
93
|
+
function indentOf(s) {
|
|
94
|
+
let n = 0;
|
|
95
|
+
for (const c of s) {
|
|
96
|
+
if (c === " ") n += 1;
|
|
97
|
+
else if (c === " ") n += 2;
|
|
98
|
+
else break;
|
|
99
|
+
}
|
|
100
|
+
return n;
|
|
101
|
+
}
|
|
102
|
+
function splitLines(src) {
|
|
103
|
+
return src.split("\n").map((raw, i) => {
|
|
104
|
+
const noComment = raw.replace(/\s*#.*$/, "");
|
|
105
|
+
return {
|
|
106
|
+
raw,
|
|
107
|
+
trimmed: noComment.trim(),
|
|
108
|
+
indent: indentOf(noComment),
|
|
109
|
+
lineNo: i + 1
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function isBlank(l) {
|
|
114
|
+
return l.trimmed === "";
|
|
115
|
+
}
|
|
116
|
+
function splitKv(s) {
|
|
117
|
+
const idx = s.indexOf(":");
|
|
118
|
+
if (idx < 0) return null;
|
|
119
|
+
return [s.slice(0, idx).trim(), s.slice(idx + 1).trim()];
|
|
120
|
+
}
|
|
121
|
+
function parseListItem(s) {
|
|
122
|
+
const m = s.match(/^-\s*(.+?)(?:\s*\(\s*(.+?)\s*\))?\s*$/);
|
|
123
|
+
if (!m) return null;
|
|
124
|
+
return { name: m[1].trim(), paren: m[2]?.trim() };
|
|
125
|
+
}
|
|
126
|
+
function parseTextDsl(src) {
|
|
127
|
+
const lines = splitLines(src);
|
|
128
|
+
const errors = [];
|
|
129
|
+
let title;
|
|
130
|
+
let type;
|
|
131
|
+
const actors = [];
|
|
132
|
+
const steps = [];
|
|
133
|
+
let animate;
|
|
134
|
+
let i = 0;
|
|
135
|
+
while (i < lines.length) {
|
|
136
|
+
const l = lines[i];
|
|
137
|
+
if (isBlank(l)) {
|
|
138
|
+
i += 1;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const kv = splitKv(l.trimmed);
|
|
142
|
+
if (kv && l.indent === 0) {
|
|
143
|
+
const header = resolveHeader(kv[0]);
|
|
144
|
+
if (header === "title") {
|
|
145
|
+
title = kv[1];
|
|
146
|
+
i += 1;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (header === "type") {
|
|
150
|
+
if (!PRESET_NAMES.includes(kv[1].toLowerCase())) {
|
|
151
|
+
errors.push({
|
|
152
|
+
line: l.lineNo,
|
|
153
|
+
message: `\u672A\u77E5\u306E\u7A2E\u985E "${kv[1]}"`,
|
|
154
|
+
hint: `\u6B21\u304B\u3089\u9078\u3093\u3067\u304F\u3060\u3055\u3044: ${PRESET_NAMES.join(" / ")}`
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
type = kv[1].toLowerCase();
|
|
158
|
+
i += 1;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (header === "actors") {
|
|
162
|
+
i += 1;
|
|
163
|
+
while (i < lines.length && (lines[i].indent > 0 || isBlank(lines[i]))) {
|
|
164
|
+
if (!isBlank(lines[i])) {
|
|
165
|
+
const a = parseActor(lines[i], errors);
|
|
166
|
+
if (a) actors.push(a);
|
|
167
|
+
}
|
|
168
|
+
i += 1;
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (header === "flow") {
|
|
173
|
+
i += 1;
|
|
174
|
+
while (i < lines.length && (lines[i].indent > 0 || isBlank(lines[i]))) {
|
|
175
|
+
if (!isBlank(lines[i])) {
|
|
176
|
+
const s = parseStep(lines[i], errors, actors);
|
|
177
|
+
if (s) steps.push(s);
|
|
178
|
+
}
|
|
179
|
+
i += 1;
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (header === "animate") {
|
|
184
|
+
const r = parseAnimate(lines, i + 1, errors);
|
|
185
|
+
animate = r.anim;
|
|
186
|
+
i = r.nextIndex;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
i += 1;
|
|
191
|
+
}
|
|
192
|
+
if (!title) errors.push({ line: 1, message: "\u30BF\u30A4\u30C8\u30EB: \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093", hint: "\u30D5\u30A1\u30A4\u30EB\u5148\u982D\u306B `\u30BF\u30A4\u30C8\u30EB: <\u540D\u524D>` \u3092\u8FFD\u52A0" });
|
|
193
|
+
if (!type) errors.push({ line: 1, message: "\u7A2E\u985E: \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093", hint: "\u30D5\u30A1\u30A4\u30EB\u306B `\u7A2E\u985E: sequence` \u7B49\u3092\u8FFD\u52A0" });
|
|
194
|
+
if (actors.length === 0) errors.push({ line: 1, message: "\u767B\u5834\u4EBA\u7269: \u30D6\u30ED\u30C3\u30AF\u304C\u7A7A\u307E\u305F\u306F\u898B\u3064\u304B\u308A\u307E\u305B\u3093", hint: "`\u767B\u5834\u4EBA\u7269:` \u306E\u4E0B\u306B `- \u540D\u524D (\u7A2E\u985E)` \u3092\u8FFD\u52A0" });
|
|
195
|
+
if (errors.length > 0) return { ok: false, errors };
|
|
196
|
+
return {
|
|
197
|
+
ok: true,
|
|
198
|
+
doc: {
|
|
199
|
+
title,
|
|
200
|
+
type,
|
|
201
|
+
actors,
|
|
202
|
+
flow: steps,
|
|
203
|
+
animate,
|
|
204
|
+
pos: { line: 1 }
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function parseActor(l, errors) {
|
|
209
|
+
const item = parseListItem(l.trimmed);
|
|
210
|
+
if (!item) {
|
|
211
|
+
errors.push({ line: l.lineNo, message: `\u767B\u5834\u4EBA\u7269\u306E\u66F8\u5F0F\u30A8\u30E9\u30FC: "${l.trimmed}"`, hint: "`- \u540D\u524D (\u7A2E\u985E)` \u5F62\u5F0F\u3067\u66F8\u3044\u3066\u304F\u3060\u3055\u3044" });
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
let kind = "actor";
|
|
215
|
+
let kindWritten = false;
|
|
216
|
+
if (item.paren) {
|
|
217
|
+
const resolved = NODE_KIND_ALIAS[item.paren] ?? item.paren;
|
|
218
|
+
kind = resolved;
|
|
219
|
+
kindWritten = true;
|
|
220
|
+
}
|
|
221
|
+
return { name: item.name, kind, kindWritten, pos: { line: l.lineNo } };
|
|
222
|
+
}
|
|
223
|
+
function parseStep(l, errors, actors) {
|
|
224
|
+
const m = l.trimmed.match(/^(\d+)\.\s*(.+)$/);
|
|
225
|
+
if (!m) {
|
|
226
|
+
errors.push({ line: l.lineNo, message: `\u6D41\u308C\u306E\u66F8\u5F0F\u30A8\u30E9\u30FC: "${l.trimmed}"`, hint: "`\u756A\u53F7. <from> \u2192 <to>: <\u30E9\u30D9\u30EB>` \u5F62\u5F0F" });
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
const no = parseInt(m[1], 10);
|
|
230
|
+
const rest = normalizeArrow(m[2]);
|
|
231
|
+
const m2 = rest.match(/^(.+?)\s*→\s*(.+?)\s*:\s*(.+)$/);
|
|
232
|
+
if (!m2) {
|
|
233
|
+
errors.push({ line: l.lineNo, message: `\u77E2\u5370\u307E\u305F\u306F \u30E9\u30D9\u30EB \u306A\u3057: "${l.trimmed}"`, hint: "`<from> \u2192 <to>: <\u30E9\u30D9\u30EB>` \u5F62\u5F0F" });
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const from = m2[1].trim();
|
|
237
|
+
const to = m2[2].trim();
|
|
238
|
+
let labelPart = m2[3].trim();
|
|
239
|
+
let tone;
|
|
240
|
+
const toneMatch = labelPart.match(/^(.+?)\s*\(\s*(.+?)\s*\)\s*$/);
|
|
241
|
+
if (toneMatch) {
|
|
242
|
+
const resolved = TONE_ALIAS[toneMatch[2]];
|
|
243
|
+
if (resolved) {
|
|
244
|
+
tone = resolved;
|
|
245
|
+
labelPart = toneMatch[1].trim();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
let sub;
|
|
249
|
+
if (!tone && toneMatch) {
|
|
250
|
+
sub = toneMatch[2];
|
|
251
|
+
labelPart = toneMatch[1].trim();
|
|
252
|
+
}
|
|
253
|
+
const names = new Set(actors.map((a) => a.name));
|
|
254
|
+
if (!names.has(from)) {
|
|
255
|
+
errors.push({ line: l.lineNo, message: `"${from}" \u304C\u767B\u5834\u4EBA\u7269\u306B\u3044\u307E\u305B\u3093`, hint: `\u767B\u5834\u4EBA\u7269: \u306B "- ${from}" \u3092\u8FFD\u52A0` });
|
|
256
|
+
}
|
|
257
|
+
if (!names.has(to)) {
|
|
258
|
+
errors.push({ line: l.lineNo, message: `"${to}" \u304C\u767B\u5834\u4EBA\u7269\u306B\u3044\u307E\u305B\u3093`, hint: `\u767B\u5834\u4EBA\u7269: \u306B "- ${to}" \u3092\u8FFD\u52A0` });
|
|
259
|
+
}
|
|
260
|
+
return { no, from, to, label: labelPart, sub, tone, pos: { line: l.lineNo } };
|
|
261
|
+
}
|
|
262
|
+
function parseAnimate(lines, startIdx, errors) {
|
|
263
|
+
const states = [];
|
|
264
|
+
const phases = [];
|
|
265
|
+
const anim = { states, phases, pos: { line: startIdx + 1 } };
|
|
266
|
+
let i = startIdx;
|
|
267
|
+
while (i < lines.length) {
|
|
268
|
+
const l = lines[i];
|
|
269
|
+
if (isBlank(l)) {
|
|
270
|
+
i += 1;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (l.indent === 0) break;
|
|
274
|
+
const kv = splitKv(l.trimmed);
|
|
275
|
+
if (kv) {
|
|
276
|
+
let sub = null;
|
|
277
|
+
const head = kv[0].trim();
|
|
278
|
+
if (/^(ステップ|step|STEP)/i.test(head)) sub = "step";
|
|
279
|
+
else sub = resolveAnimSubkey(head);
|
|
280
|
+
if (sub === "state") {
|
|
281
|
+
const m = kv[1].match(/^(.+?)\s*=\s*(.+)$/);
|
|
282
|
+
if (!m) {
|
|
283
|
+
errors.push({ line: l.lineNo, message: `\u72B6\u614B \u66F8\u5F0F\u30A8\u30E9\u30FC: "${kv[1]}"`, hint: "`\u72B6\u614B: <\u540D\u524D> = <\u521D\u671F\u5024>`" });
|
|
284
|
+
} else {
|
|
285
|
+
const name = m[1].trim();
|
|
286
|
+
const raw = m[2].trim();
|
|
287
|
+
const num = parseFloat(raw);
|
|
288
|
+
const initial = isNaN(num) ? raw.replace(/^["']|["']$/g, "") : num;
|
|
289
|
+
states.push({ name, initial, pos: { line: l.lineNo } });
|
|
290
|
+
}
|
|
291
|
+
i += 1;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (sub === "step") {
|
|
295
|
+
const afterKeyword = kv[0].replace(/^(ステップ|step|STEP)\s*/i, "").trim();
|
|
296
|
+
const nameMatch = afterKeyword.match(/^[「"](.+?)[」"]\s*(.+)$/);
|
|
297
|
+
if (!nameMatch) {
|
|
298
|
+
errors.push({ line: l.lineNo, message: `\u30B9\u30C6\u30C3\u30D7 \u66F8\u5F0F\u30A8\u30E9\u30FC: "${kv[0]}"`, hint: `\`\u30B9\u30C6\u30C3\u30D7\u300C<\u540D\u524D>\u300D <\u6642\u9593>:\` \u5F62\u5F0F` });
|
|
299
|
+
i += 1;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const phaseName = nameMatch[1];
|
|
303
|
+
const durStr = nameMatch[2].trim();
|
|
304
|
+
const durMs = parseDuration(durStr);
|
|
305
|
+
if (durMs === null) {
|
|
306
|
+
errors.push({ line: l.lineNo, message: `\u6642\u9593 \u89E3\u91C8\u4E0D\u80FD: "${durStr}"`, hint: "`1.5 \u79D2` / `1500ms` / `2s` \u7B49" });
|
|
307
|
+
i += 1;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const phase = { name: phaseName, durationMs: durMs, pos: { line: l.lineNo } };
|
|
311
|
+
i += 1;
|
|
312
|
+
const phaseBaseIndent = l.indent;
|
|
313
|
+
while (i < lines.length && (lines[i].indent > phaseBaseIndent || isBlank(lines[i]))) {
|
|
314
|
+
if (!isBlank(lines[i])) {
|
|
315
|
+
applyPhaseSubLine(lines[i], phase, errors);
|
|
316
|
+
}
|
|
317
|
+
i += 1;
|
|
318
|
+
}
|
|
319
|
+
phases.push(phase);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
i += 1;
|
|
324
|
+
}
|
|
325
|
+
return { anim, nextIndex: i };
|
|
326
|
+
}
|
|
327
|
+
function applyPhaseSubLine(l, phase, errors) {
|
|
328
|
+
const kv = splitKv(l.trimmed);
|
|
329
|
+
if (!kv) return;
|
|
330
|
+
const sub = resolveAnimSubkey(kv[0]);
|
|
331
|
+
if (sub === "highlight") {
|
|
332
|
+
phase.highlight = kv[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (sub === "tween") {
|
|
336
|
+
const rest = normalizeArrow(kv[1]);
|
|
337
|
+
const m = rest.match(/^(.+?)\s*:\s*([\d.]+)\s*→\s*([\d.]+)$/);
|
|
338
|
+
if (!m) {
|
|
339
|
+
errors.push({ line: l.lineNo, message: `\u9077\u79FB \u66F8\u5F0F\u30A8\u30E9\u30FC: "${kv[1]}"`, hint: "`\u9077\u79FB: <state>: <from> \u2192 <to>`" });
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
phase.tweens = phase.tweens ?? [];
|
|
343
|
+
phase.tweens.push({
|
|
344
|
+
state: m[1].trim(),
|
|
345
|
+
from: parseFloat(m[2]),
|
|
346
|
+
to: parseFloat(m[3]),
|
|
347
|
+
pos: { line: l.lineNo }
|
|
348
|
+
});
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (sub === "set") {
|
|
352
|
+
const m = kv[1].match(/^(.+?)\s*:\s*(.+)$/);
|
|
353
|
+
if (!m) {
|
|
354
|
+
errors.push({ line: l.lineNo, message: `\u5207\u66FF \u66F8\u5F0F\u30A8\u30E9\u30FC: "${kv[1]}"`, hint: "`\u5207\u66FF: <state>: <\u5024>`" });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const raw = m[2].trim();
|
|
358
|
+
const num = parseFloat(raw);
|
|
359
|
+
const value = isNaN(num) ? raw : num;
|
|
360
|
+
phase.sets = phase.sets ?? [];
|
|
361
|
+
phase.sets.push({ state: m[1].trim(), value, pos: { line: l.lineNo } });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (sub === "body") {
|
|
365
|
+
phase.body = kv[1];
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (sub === "badge") {
|
|
369
|
+
phase.badge = kv[1];
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/focus.ts
|
|
375
|
+
var ARROW = /^(.+?)\s*(?:->|→)\s*(.+)$/;
|
|
376
|
+
function parseFocusEntry(raw, knownNames) {
|
|
377
|
+
const item = raw.trim();
|
|
378
|
+
if (knownNames?.has(item)) return { kind: "node", name: item };
|
|
379
|
+
const m = item.match(ARROW);
|
|
380
|
+
if (!m) return { kind: "node", name: item };
|
|
381
|
+
return { kind: "edge", from: m[1].trim(), to: m[2].trim() };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/color.ts
|
|
385
|
+
var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
|
|
386
|
+
"aliceblue",
|
|
387
|
+
"antiquewhite",
|
|
388
|
+
"aqua",
|
|
389
|
+
"aquamarine",
|
|
390
|
+
"azure",
|
|
391
|
+
"beige",
|
|
392
|
+
"bisque",
|
|
393
|
+
"black",
|
|
394
|
+
"blanchedalmond",
|
|
395
|
+
"blue",
|
|
396
|
+
"blueviolet",
|
|
397
|
+
"brown",
|
|
398
|
+
"burlywood",
|
|
399
|
+
"cadetblue",
|
|
400
|
+
"chartreuse",
|
|
401
|
+
"chocolate",
|
|
402
|
+
"coral",
|
|
403
|
+
"cornflowerblue",
|
|
404
|
+
"cornsilk",
|
|
405
|
+
"crimson",
|
|
406
|
+
"cyan",
|
|
407
|
+
"darkblue",
|
|
408
|
+
"darkcyan",
|
|
409
|
+
"darkgoldenrod",
|
|
410
|
+
"darkgray",
|
|
411
|
+
"darkgreen",
|
|
412
|
+
"darkgrey",
|
|
413
|
+
"darkkhaki",
|
|
414
|
+
"darkmagenta",
|
|
415
|
+
"darkolivegreen",
|
|
416
|
+
"darkorange",
|
|
417
|
+
"darkorchid",
|
|
418
|
+
"darkred",
|
|
419
|
+
"darksalmon",
|
|
420
|
+
"darkseagreen",
|
|
421
|
+
"darkslateblue",
|
|
422
|
+
"darkslategray",
|
|
423
|
+
"darkslategrey",
|
|
424
|
+
"darkturquoise",
|
|
425
|
+
"darkviolet",
|
|
426
|
+
"deeppink",
|
|
427
|
+
"deepskyblue",
|
|
428
|
+
"dimgray",
|
|
429
|
+
"dimgrey",
|
|
430
|
+
"dodgerblue",
|
|
431
|
+
"firebrick",
|
|
432
|
+
"floralwhite",
|
|
433
|
+
"forestgreen",
|
|
434
|
+
"fuchsia",
|
|
435
|
+
"gainsboro",
|
|
436
|
+
"ghostwhite",
|
|
437
|
+
"gold",
|
|
438
|
+
"goldenrod",
|
|
439
|
+
"gray",
|
|
440
|
+
"green",
|
|
441
|
+
"greenyellow",
|
|
442
|
+
"grey",
|
|
443
|
+
"honeydew",
|
|
444
|
+
"hotpink",
|
|
445
|
+
"indianred",
|
|
446
|
+
"indigo",
|
|
447
|
+
"ivory",
|
|
448
|
+
"khaki",
|
|
449
|
+
"lavender",
|
|
450
|
+
"lavenderblush",
|
|
451
|
+
"lawngreen",
|
|
452
|
+
"lemonchiffon",
|
|
453
|
+
"lightblue",
|
|
454
|
+
"lightcoral",
|
|
455
|
+
"lightcyan",
|
|
456
|
+
"lightgoldenrodyellow",
|
|
457
|
+
"lightgray",
|
|
458
|
+
"lightgreen",
|
|
459
|
+
"lightgrey",
|
|
460
|
+
"lightpink",
|
|
461
|
+
"lightsalmon",
|
|
462
|
+
"lightseagreen",
|
|
463
|
+
"lightskyblue",
|
|
464
|
+
"lightslategray",
|
|
465
|
+
"lightslategrey",
|
|
466
|
+
"lightsteelblue",
|
|
467
|
+
"lightyellow",
|
|
468
|
+
"lime",
|
|
469
|
+
"limegreen",
|
|
470
|
+
"linen",
|
|
471
|
+
"magenta",
|
|
472
|
+
"maroon",
|
|
473
|
+
"mediumaquamarine",
|
|
474
|
+
"mediumblue",
|
|
475
|
+
"mediumorchid",
|
|
476
|
+
"mediumpurple",
|
|
477
|
+
"mediumseagreen",
|
|
478
|
+
"mediumslateblue",
|
|
479
|
+
"mediumspringgreen",
|
|
480
|
+
"mediumturquoise",
|
|
481
|
+
"mediumvioletred",
|
|
482
|
+
"midnightblue",
|
|
483
|
+
"mintcream",
|
|
484
|
+
"mistyrose",
|
|
485
|
+
"moccasin",
|
|
486
|
+
"navajowhite",
|
|
487
|
+
"navy",
|
|
488
|
+
"oldlace",
|
|
489
|
+
"olive",
|
|
490
|
+
"olivedrab",
|
|
491
|
+
"orange",
|
|
492
|
+
"orangered",
|
|
493
|
+
"orchid",
|
|
494
|
+
"palegoldenrod",
|
|
495
|
+
"palegreen",
|
|
496
|
+
"paleturquoise",
|
|
497
|
+
"palevioletred",
|
|
498
|
+
"papayawhip",
|
|
499
|
+
"peachpuff",
|
|
500
|
+
"peru",
|
|
501
|
+
"pink",
|
|
502
|
+
"plum",
|
|
503
|
+
"powderblue",
|
|
504
|
+
"purple",
|
|
505
|
+
"rebeccapurple",
|
|
506
|
+
"red",
|
|
507
|
+
"rosybrown",
|
|
508
|
+
"royalblue",
|
|
509
|
+
"saddlebrown",
|
|
510
|
+
"salmon",
|
|
511
|
+
"sandybrown",
|
|
512
|
+
"seagreen",
|
|
513
|
+
"seashell",
|
|
514
|
+
"sienna",
|
|
515
|
+
"silver",
|
|
516
|
+
"skyblue",
|
|
517
|
+
"slateblue",
|
|
518
|
+
"slategray",
|
|
519
|
+
"slategrey",
|
|
520
|
+
"snow",
|
|
521
|
+
"springgreen",
|
|
522
|
+
"steelblue",
|
|
523
|
+
"tan",
|
|
524
|
+
"teal",
|
|
525
|
+
"thistle",
|
|
526
|
+
"tomato",
|
|
527
|
+
"transparent",
|
|
528
|
+
"turquoise",
|
|
529
|
+
"violet",
|
|
530
|
+
"wheat",
|
|
531
|
+
"white",
|
|
532
|
+
"whitesmoke",
|
|
533
|
+
"yellow",
|
|
534
|
+
"yellowgreen",
|
|
535
|
+
// 塗らないことを表す値。 色ではないが、 色を書く位置に置ける正当な値
|
|
536
|
+
"none",
|
|
537
|
+
"currentcolor"
|
|
538
|
+
]);
|
|
539
|
+
function isColorValue(v) {
|
|
540
|
+
if (typeof v !== "string") return false;
|
|
541
|
+
const s = v.trim();
|
|
542
|
+
if (/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(s)) return true;
|
|
543
|
+
return CSS_COLOR_NAMES.has(s.toLowerCase());
|
|
544
|
+
}
|
|
545
|
+
function pointsOutside(v) {
|
|
546
|
+
if (typeof v !== "string") return false;
|
|
547
|
+
const compact = v.replace(/\s+/g, "").toLowerCase();
|
|
548
|
+
const m = compact.match(/url\(([^)]*)/);
|
|
549
|
+
if (m) {
|
|
550
|
+
const inner = (m[1] ?? "").replace(/^["']/, "");
|
|
551
|
+
return !inner.startsWith("#");
|
|
552
|
+
}
|
|
553
|
+
return /^(https?:)?\/\//.test(compact) || compact.startsWith("data:");
|
|
554
|
+
}
|
|
555
|
+
var PAINT_KEYS = /* @__PURE__ */ new Set([
|
|
556
|
+
"fill",
|
|
557
|
+
"stroke",
|
|
558
|
+
"color",
|
|
559
|
+
"bg",
|
|
560
|
+
"background",
|
|
561
|
+
"fillbind",
|
|
562
|
+
"strokebind",
|
|
563
|
+
"colorbind",
|
|
564
|
+
"stfill",
|
|
565
|
+
"strokecolor",
|
|
566
|
+
"fillcolor"
|
|
567
|
+
]);
|
|
568
|
+
var SAFE_PAINT = "none";
|
|
569
|
+
function stripExternalPaint(diagram2) {
|
|
570
|
+
const stripped = [];
|
|
571
|
+
walk(diagram2, "", false, stripped);
|
|
572
|
+
return stripped;
|
|
573
|
+
}
|
|
574
|
+
function walk(node, path, inStateValue, out) {
|
|
575
|
+
if (node === null || typeof node !== "object") return;
|
|
576
|
+
if (Array.isArray(node)) {
|
|
577
|
+
for (let i = 0; i < node.length; i++) {
|
|
578
|
+
walk(node[i], `${path}[${i}]`, inStateValue, out);
|
|
579
|
+
}
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const obj = node;
|
|
583
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
584
|
+
const here = path ? `${path}.${key}` : key;
|
|
585
|
+
const lower = key.toLowerCase();
|
|
586
|
+
const nextInState = inStateValue || lower === "states" || lower === "sets" || lower === "tweens";
|
|
587
|
+
if (typeof value === "string") {
|
|
588
|
+
const isPaint = PAINT_KEYS.has(lower) || nextInState && (lower === "initial" || lower === "to" || lower === "from" || !isReservedStateKey(lower));
|
|
589
|
+
if (isPaint && pointsOutside(value)) {
|
|
590
|
+
obj[key] = SAFE_PAINT;
|
|
591
|
+
out.push({ path: here, value });
|
|
592
|
+
}
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
walk(value, here, nextInState, out);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function isReservedStateKey(lowerKey) {
|
|
599
|
+
return lowerKey === "id" || lowerKey === "stateid" || lowerKey === "duration" || lowerKey === "title" || lowerKey === "body" || lowerKey === "badge" || lowerKey === "kind";
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/input-size.ts
|
|
603
|
+
var MAX_INPUT_ELEMENTS = 2e3;
|
|
604
|
+
var MAX_INPUT_BYTES = 512 * 1024;
|
|
605
|
+
function countDocElements(doc) {
|
|
606
|
+
const phases = doc.animate?.phases ?? [];
|
|
607
|
+
const phaseChildren = phases.reduce(
|
|
608
|
+
(acc, p) => acc + (p.highlight?.length ?? 0) + (p.tweens?.length ?? 0) + (p.sets?.length ?? 0),
|
|
609
|
+
0
|
|
610
|
+
);
|
|
611
|
+
return doc.actors.length + doc.flow.length + phases.length + phaseChildren + (doc.animate?.states.length ?? 0) + (doc.groups ? Object.keys(doc.groups).length : 0) + (doc.lanes ? Object.keys(doc.lanes).length : 0);
|
|
612
|
+
}
|
|
613
|
+
function countDiagramElements(diagram2) {
|
|
614
|
+
const phases = Array.isArray(diagram2.phases) ? diagram2.phases : [];
|
|
615
|
+
const phaseChildren = phases.reduce((acc, p) => {
|
|
616
|
+
const ph = p;
|
|
617
|
+
return acc + (Array.isArray(ph?.activate) ? ph.activate.length : 0) + (Array.isArray(ph?.highlight) ? ph.highlight.length : 0) + (Array.isArray(ph?.tweens) ? ph.tweens.length : 0) + (Array.isArray(ph?.sets) ? ph.sets.length : 0);
|
|
618
|
+
}, 0);
|
|
619
|
+
return (diagram2.nodes?.length ?? 0) + (diagram2.edges?.length ?? 0) + (diagram2.lanes?.length ?? 0) + (diagram2.states?.length ?? 0) + phases.length + phaseChildren + // 図が持てる並びは全部数える。 1 つでも外すと、そこに寄せた図が素通りする
|
|
620
|
+
(diagram2.readouts?.length ?? 0) + (diagram2.inputs?.length ?? 0) + (diagram2.formulas?.length ?? 0) + (diagram2.scrollTriggers?.length ?? 0) + (diagram2.eventBindings?.length ?? 0);
|
|
621
|
+
}
|
|
622
|
+
function countBytes(src) {
|
|
623
|
+
return new TextEncoder().encode(src).length;
|
|
624
|
+
}
|
|
625
|
+
function describeOversize(size) {
|
|
626
|
+
if (size.elements > MAX_INPUT_ELEMENTS) {
|
|
627
|
+
return `\u8981\u7D20\u304C ${size.elements.toLocaleString()} \u4EF6\u3042\u308A\u307E\u3059\u3002 ${MAX_INPUT_ELEMENTS.toLocaleString()} \u4EF6\u307E\u3067\u306B\u3057\u3066\u304F\u3060\u3055\u3044 (\u3053\u308C\u4EE5\u4E0A\u306F\u7D44\u307F\u7ACB\u3066\u306B\u6642\u9593\u304C\u304B\u304B\u308A\u3001 \u753B\u9762\u304C\u6B62\u307E\u308A\u307E\u3059)`;
|
|
628
|
+
}
|
|
629
|
+
if (size.bytes > MAX_INPUT_BYTES) {
|
|
630
|
+
const kb = Math.ceil(size.bytes / 1024);
|
|
631
|
+
const limitKb = Math.floor(MAX_INPUT_BYTES / 1024);
|
|
632
|
+
return `\u672C\u6587\u304C ${kb.toLocaleString()}KB \u3042\u308A\u307E\u3059\u3002 ${limitKb.toLocaleString()}KB \u307E\u3067\u306B\u3057\u3066\u304F\u3060\u3055\u3044 (\u3053\u308C\u4EE5\u4E0A\u306F\u8AAD\u307F\u53D6\u308A\u3060\u3051\u3067\u5F85\u305F\u3055\u308C\u3001 \u8A18\u61B6\u3082\u5927\u304D\u304F\u4F7F\u3044\u307E\u3059)`;
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
function describeOversizeSource(src) {
|
|
637
|
+
return describeOversize({ elements: 0, bytes: countBytes(src) });
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/relative-pos.ts
|
|
641
|
+
var RELATIVE_GAP_DEFAULT = 160;
|
|
642
|
+
var DIRECTION_WORDS = {
|
|
643
|
+
\u53F3: "right",
|
|
644
|
+
\u5DE6: "left",
|
|
645
|
+
\u4E0A: "above",
|
|
646
|
+
\u4E0B: "below",
|
|
647
|
+
right: "right",
|
|
648
|
+
left: "left",
|
|
649
|
+
above: "above",
|
|
650
|
+
below: "below"
|
|
651
|
+
};
|
|
652
|
+
var GAP_NUM = String.raw`(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?`;
|
|
653
|
+
var RE_JA = new RegExp(String.raw`^(.+?)\s*の\s*(右|左|上|下)(?:\s*(${GAP_NUM}))?$`);
|
|
654
|
+
var RE_EN = new RegExp(String.raw`^(.+?)\s+(right|left|above|below)(?:\s+(${GAP_NUM}))?$`, "i");
|
|
655
|
+
function parseRelativePos(raw) {
|
|
656
|
+
const s = raw.trim();
|
|
657
|
+
if (s === "") return null;
|
|
658
|
+
const m = s.match(RE_JA) ?? s.match(RE_EN);
|
|
659
|
+
if (!m) return null;
|
|
660
|
+
const anchor = m[1].trim();
|
|
661
|
+
if (anchor === "") return null;
|
|
662
|
+
const dir = DIRECTION_WORDS[m[2].toLowerCase()];
|
|
663
|
+
if (dir === void 0) return null;
|
|
664
|
+
const gapRaw = m[3];
|
|
665
|
+
if (gapRaw === void 0) return { anchor, dir };
|
|
666
|
+
const gap = Number(gapRaw);
|
|
667
|
+
return Number.isFinite(gap) ? { anchor, dir, gap } : { anchor, dir };
|
|
668
|
+
}
|
|
669
|
+
function resolveRelativePos(rel, anchor, target) {
|
|
670
|
+
const raw = rel.gap ?? RELATIVE_GAP_DEFAULT;
|
|
671
|
+
const gap = Number.isFinite(raw) && raw >= 0 ? raw : RELATIVE_GAP_DEFAULT;
|
|
672
|
+
const dx = anchor.w / 2 + gap + target.w / 2;
|
|
673
|
+
const dy = anchor.h / 2 + gap + target.h / 2;
|
|
674
|
+
switch (rel.dir) {
|
|
675
|
+
case "right":
|
|
676
|
+
return { posX: anchor.cx + dx, posY: anchor.cy };
|
|
677
|
+
case "left":
|
|
678
|
+
return { posX: anchor.cx - dx, posY: anchor.cy };
|
|
679
|
+
case "below":
|
|
680
|
+
return { posX: anchor.cx, posY: anchor.cy + dy };
|
|
681
|
+
case "above":
|
|
682
|
+
return { posX: anchor.cx, posY: anchor.cy - dy };
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function orderByDependency(items) {
|
|
686
|
+
const relOf = /* @__PURE__ */ new Map();
|
|
687
|
+
const known = /* @__PURE__ */ new Set();
|
|
688
|
+
for (const it of items) {
|
|
689
|
+
known.add(it.name);
|
|
690
|
+
if (it.rel) relOf.set(it.name, it.rel);
|
|
691
|
+
}
|
|
692
|
+
const order = [];
|
|
693
|
+
const done = /* @__PURE__ */ new Set();
|
|
694
|
+
const cyclic = /* @__PURE__ */ new Set();
|
|
695
|
+
for (const it of items) {
|
|
696
|
+
if (done.has(it.name) || cyclic.has(it.name)) continue;
|
|
697
|
+
const path = [];
|
|
698
|
+
const onPath = /* @__PURE__ */ new Set();
|
|
699
|
+
let cur = it.name;
|
|
700
|
+
while (cur !== void 0) {
|
|
701
|
+
if (done.has(cur) || cyclic.has(cur)) break;
|
|
702
|
+
if (onPath.has(cur)) {
|
|
703
|
+
for (const n of path.slice(path.indexOf(cur))) cyclic.add(n);
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
path.push(cur);
|
|
707
|
+
onPath.add(cur);
|
|
708
|
+
const rel = relOf.get(cur);
|
|
709
|
+
if (rel === void 0 || !known.has(rel.anchor)) break;
|
|
710
|
+
cur = rel.anchor;
|
|
711
|
+
}
|
|
712
|
+
for (let i = path.length - 1; i >= 0; i -= 1) {
|
|
713
|
+
const n = path[i];
|
|
714
|
+
if (cyclic.has(n) || done.has(n)) continue;
|
|
715
|
+
done.add(n);
|
|
716
|
+
order.push(n);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return { order, cyclic: [...cyclic] };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/compile.ts
|
|
723
|
+
function compileToCdl(doc, opts) {
|
|
724
|
+
const oversize = describeOversize({ elements: countDocElements(doc), bytes: 0 });
|
|
725
|
+
if (oversize) throw new Error(oversize);
|
|
726
|
+
let diagram2;
|
|
727
|
+
switch (doc.type) {
|
|
728
|
+
case "sequence":
|
|
729
|
+
diagram2 = compileSequence(doc);
|
|
730
|
+
break;
|
|
731
|
+
case "flow":
|
|
732
|
+
diagram2 = compileFlow(doc);
|
|
733
|
+
break;
|
|
734
|
+
case "swimlane":
|
|
735
|
+
diagram2 = compileSwimlane(doc);
|
|
736
|
+
break;
|
|
737
|
+
case "er":
|
|
738
|
+
diagram2 = compileEr(doc);
|
|
739
|
+
break;
|
|
740
|
+
case "state":
|
|
741
|
+
diagram2 = compileState(doc);
|
|
742
|
+
break;
|
|
743
|
+
case "topology":
|
|
744
|
+
diagram2 = compileTopology(doc);
|
|
745
|
+
break;
|
|
746
|
+
case "solidity":
|
|
747
|
+
diagram2 = compileSolidity(doc);
|
|
748
|
+
break;
|
|
749
|
+
case "gantt":
|
|
750
|
+
diagram2 = compileGantt(doc);
|
|
751
|
+
break;
|
|
752
|
+
case "class":
|
|
753
|
+
diagram2 = compileClass(doc);
|
|
754
|
+
break;
|
|
755
|
+
case "pie":
|
|
756
|
+
diagram2 = compilePie(doc);
|
|
757
|
+
break;
|
|
758
|
+
case "c4":
|
|
759
|
+
diagram2 = compileC4(doc);
|
|
760
|
+
break;
|
|
761
|
+
case "mind":
|
|
762
|
+
diagram2 = compileMind(doc);
|
|
763
|
+
break;
|
|
764
|
+
default:
|
|
765
|
+
throw new Error(`unknown type: ${String(doc.type)}`);
|
|
766
|
+
}
|
|
767
|
+
const edgeSourceLines = opts?.onEdgeSource ? /* @__PURE__ */ new Map() : void 0;
|
|
768
|
+
applyEdgeInlineOptions(diagram2, doc, edgeSourceLines);
|
|
769
|
+
if (edgeSourceLines) fillFlowEdgeSources(diagram2, doc, edgeSourceLines);
|
|
770
|
+
applyGroupContainers(diagram2, doc);
|
|
771
|
+
applyNodeTones(diagram2, doc);
|
|
772
|
+
reportMissingFocusTargets(doc, opts?.onNotice);
|
|
773
|
+
const placed = resolveRelativeDoc(diagram2, doc, opts?.onNotice, opts?.partsCatalog);
|
|
774
|
+
applyCanvasPivotPositions(diagram2, placed);
|
|
775
|
+
const extended = applyV05Extensions(diagram2, placed);
|
|
776
|
+
const merged = mergePartsFromActors(extended, placed, opts?.partsCatalog, opts?.onNotice);
|
|
777
|
+
if (edgeSourceLines && opts?.onEdgeSource) {
|
|
778
|
+
const alive = new Set(merged.edges.map((e) => e.id));
|
|
779
|
+
for (const [id, line] of edgeSourceLines) {
|
|
780
|
+
if (alive.has(id)) opts.onEdgeSource(id, line);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
injectStaticPhase(merged);
|
|
784
|
+
for (const dropped of stripExternalPaint(merged)) {
|
|
785
|
+
opts?.onNotice?.({
|
|
786
|
+
kind: "external-paint-dropped",
|
|
787
|
+
actor: dropped.path,
|
|
788
|
+
line: 0,
|
|
789
|
+
message: `\u56F3\u306E\u5916\u3092\u6307\u3059\u5024 (${truncateForMessage(dropped.value)}) \u306F\u8272\u3068\u3057\u3066\u4F7F\u3048\u306A\u3044\u305F\u3081\u5916\u3057\u307E\u3057\u305F`,
|
|
790
|
+
hint: "\u8272\u306F `#ff0000` \u306E\u3088\u3046\u306A\u8272\u756A\u53F7\u304B\u3001 `red` \u306E\u3088\u3046\u306A\u8272\u540D\u3067\u66F8\u304F"
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
return merged;
|
|
794
|
+
}
|
|
795
|
+
function injectStaticPhase(diagram2) {
|
|
796
|
+
if (diagram2.phases.length > 0) return;
|
|
797
|
+
const activate = [...diagram2.nodes.map((n) => n.id), ...diagram2.edges.map((e) => e.id)];
|
|
798
|
+
diagram2.phases.push({
|
|
799
|
+
id: "static",
|
|
800
|
+
duration: 1e3,
|
|
801
|
+
title: diagram2.topic ?? "\u5168\u4F53",
|
|
802
|
+
body: "",
|
|
803
|
+
activate,
|
|
804
|
+
tweens: [],
|
|
805
|
+
sets: []
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
function truncateForMessage(v) {
|
|
809
|
+
const s = v.trim();
|
|
810
|
+
return s.length <= 40 ? s : `${s.slice(0, 37)}...`;
|
|
811
|
+
}
|
|
812
|
+
function reportMissingFocusTargets(doc, onNotice) {
|
|
813
|
+
if (!onNotice || !doc.animate) return;
|
|
814
|
+
const names = new Set(doc.actors.map((a) => a.name));
|
|
815
|
+
const accepted = new Set(names);
|
|
816
|
+
const slugCount = /* @__PURE__ */ new Map();
|
|
817
|
+
for (const n of names) {
|
|
818
|
+
const sl = slugify(n);
|
|
819
|
+
slugCount.set(sl, (slugCount.get(sl) ?? 0) + 1);
|
|
820
|
+
}
|
|
821
|
+
for (const [sl, count] of slugCount) if (count === 1) accepted.add(sl);
|
|
822
|
+
const steps = /* @__PURE__ */ new Map();
|
|
823
|
+
for (const st of doc.flow) {
|
|
824
|
+
const tos = steps.get(st.from) ?? /* @__PURE__ */ new Set();
|
|
825
|
+
tos.add(st.to);
|
|
826
|
+
steps.set(st.from, tos);
|
|
827
|
+
}
|
|
828
|
+
for (const phase of doc.animate.phases) {
|
|
829
|
+
for (const raw of phase.highlight ?? []) {
|
|
830
|
+
const entry = parseFocusEntry(raw, names);
|
|
831
|
+
const found = entry.kind === "edge" ? steps.get(entry.from)?.has(entry.to) ?? false : accepted.has(entry.name);
|
|
832
|
+
if (found) continue;
|
|
833
|
+
onNotice({
|
|
834
|
+
kind: "focus-target-missing",
|
|
835
|
+
actor: raw,
|
|
836
|
+
line: phase.pos.line,
|
|
837
|
+
message: entry.kind === "edge" ? `\u5149\u3089\u305B\u308B\u77E2\u5370\u304C\u6D41\u308C\u306B\u3042\u308A\u307E\u305B\u3093: "${raw}"` : `\u5149\u3089\u305B\u308B\u76F8\u624B\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: "${raw}"`,
|
|
838
|
+
hint: entry.kind === "edge" ? "flow: \u306B\u66F8\u3044\u305F\u77E2\u5370\u3068\u540C\u3058\u5411\u304D\u3067\u66F8\u304F" : `actors: \u306B\u66F8\u304B\u308C\u3066\u3044\u308B\u540D\u524D = ${[...names].join(", ")}`
|
|
839
|
+
// 縦列の id は受理しないので、 その旨は hint に出さない (光らせられないため)
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function resolveRelativeDoc(diagram2, doc, onNotice, partsCatalog) {
|
|
845
|
+
if (!doc.actors.some((a) => a.posRel !== void 0)) return doc;
|
|
846
|
+
const measured = measureActorBoxes(withPositions(diagram2, doc, /* @__PURE__ */ new Map()));
|
|
847
|
+
const baseBoxes = new Map(measured);
|
|
848
|
+
if (partsCatalog) {
|
|
849
|
+
for (const [name, box] of partBoxes(diagram2, doc, partsCatalog)) baseBoxes.set(name, box);
|
|
850
|
+
}
|
|
851
|
+
const sizeOverride = partsCatalog ? partSizes(doc, partsCatalog) : /* @__PURE__ */ new Map();
|
|
852
|
+
const want = desiredCenters(doc, baseBoxes, sizeOverride);
|
|
853
|
+
if (want.size === 0) return doc;
|
|
854
|
+
const naive = new Map(
|
|
855
|
+
[...want].map(([name, c]) => {
|
|
856
|
+
const off = sizeOverride.get(name);
|
|
857
|
+
return [name, { posX: c.cx - (off?.dx ?? 0), posY: c.cy - (off?.dy ?? 0) }];
|
|
858
|
+
})
|
|
859
|
+
);
|
|
860
|
+
const isPart = new Set(doc.actors.filter((a) => a.partId !== void 0).map((a) => a.name));
|
|
861
|
+
const partOverride = /* @__PURE__ */ new Map();
|
|
862
|
+
for (const [name, box] of baseBoxes) {
|
|
863
|
+
if (isPart.has(name)) partOverride.set(name, box);
|
|
864
|
+
}
|
|
865
|
+
for (const [name, c] of want) {
|
|
866
|
+
if (!isPart.has(name)) continue;
|
|
867
|
+
partOverride.set(name, c);
|
|
868
|
+
}
|
|
869
|
+
const placedBoxes = measureActorBoxes(withPositions(diagram2, doc, naive));
|
|
870
|
+
const fixed = /* @__PURE__ */ new Map();
|
|
871
|
+
for (const [name, pos] of naive) {
|
|
872
|
+
if (isPart.has(name)) {
|
|
873
|
+
fixed.set(name, pos);
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
const got = placedBoxes.get(name);
|
|
877
|
+
const target = want.get(name);
|
|
878
|
+
if (!got) {
|
|
879
|
+
fixed.set(name, pos);
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
fixed.set(name, {
|
|
883
|
+
posX: pos.posX + (target.cx - got.cx),
|
|
884
|
+
posY: pos.posY + (target.cy - got.cy)
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
return withDocPositions(doc, verifyPlacement(diagram2, doc, fixed, partOverride, onNotice));
|
|
888
|
+
}
|
|
889
|
+
function verifyPlacement(diagram2, doc, assign, partOverride, onNotice) {
|
|
890
|
+
const boxes = measureActorBoxes(withPositions(diagram2, doc, assign));
|
|
891
|
+
for (const [name, box] of partOverride) boxes.set(name, box);
|
|
892
|
+
const kept = new Map(assign);
|
|
893
|
+
for (const actor of doc.actors) {
|
|
894
|
+
const rel = actor.posRel;
|
|
895
|
+
const pos = assign.get(actor.name);
|
|
896
|
+
if (!rel || !pos) continue;
|
|
897
|
+
const self = boxes.get(actor.name);
|
|
898
|
+
const anchor = boxes.get(rel.anchor);
|
|
899
|
+
if (!self || !anchor) continue;
|
|
900
|
+
const expect = resolveRelativePos(rel, anchor, self);
|
|
901
|
+
const offX = Math.abs(expect.posX - self.cx);
|
|
902
|
+
const offY = Math.abs(expect.posY - self.cy);
|
|
903
|
+
if (offX <= PLACEMENT_TOLERANCE && offY <= PLACEMENT_TOLERANCE) continue;
|
|
904
|
+
kept.delete(actor.name);
|
|
905
|
+
onNotice?.({
|
|
906
|
+
kind: "relative-position-ignored",
|
|
907
|
+
actor: actor.name,
|
|
908
|
+
line: actor.pos.line,
|
|
909
|
+
message: `"${actor.name}" \u306E\u4F4D\u7F6E (${rel.anchor} \u306E${DIRECTION_LABEL[rel.dir]}) \u306F${doc.type}\u56F3\u3067\u306F\u52B9\u304D\u307E\u305B\u3093`,
|
|
910
|
+
hint: "\u5EA7\u6A19 (`\u4F4D\u7F6E: 300,200`) \u3067\u7F6E\u304F\u304B\u3001 \u81EA\u52D5\u914D\u7F6E\u306B\u4EFB\u305B\u308B"
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
return kept;
|
|
914
|
+
}
|
|
915
|
+
var DIRECTION_LABEL = {
|
|
916
|
+
right: "\u53F3",
|
|
917
|
+
left: "\u5DE6",
|
|
918
|
+
above: "\u4E0A",
|
|
919
|
+
below: "\u4E0B"
|
|
920
|
+
};
|
|
921
|
+
var PLACEMENT_TOLERANCE = 1;
|
|
922
|
+
function desiredCenters(doc, boxes, sizeOverride = /* @__PURE__ */ new Map()) {
|
|
923
|
+
const byName = new Map(doc.actors.map((a) => [a.name, a]));
|
|
924
|
+
const { order } = orderByDependency(doc.actors.map((a) => ({ name: a.name, rel: a.posRel })));
|
|
925
|
+
const centers = new Map(boxes);
|
|
926
|
+
const out = /* @__PURE__ */ new Map();
|
|
927
|
+
for (const name of order) {
|
|
928
|
+
const actor = byName.get(name);
|
|
929
|
+
const override = sizeOverride.get(name);
|
|
930
|
+
const measuredSelf = boxes.get(name);
|
|
931
|
+
const self = override ? { cx: measuredSelf?.cx ?? 0, cy: measuredSelf?.cy ?? 0, w: override.w, h: override.h } : measuredSelf;
|
|
932
|
+
if (!actor?.posRel || !self) continue;
|
|
933
|
+
const anchor = centers.get(actor.posRel.anchor);
|
|
934
|
+
if (!anchor) continue;
|
|
935
|
+
const p = resolveRelativePos(actor.posRel, anchor, self);
|
|
936
|
+
const center = { cx: p.posX, cy: p.posY, w: self.w, h: self.h };
|
|
937
|
+
centers.set(name, center);
|
|
938
|
+
out.set(name, center);
|
|
939
|
+
}
|
|
940
|
+
return out;
|
|
941
|
+
}
|
|
942
|
+
function measureActorBoxes(diagram2, laidHint) {
|
|
943
|
+
const laid = laidHint ?? layout(diagram2);
|
|
944
|
+
const bounds = /* @__PURE__ */ new Map();
|
|
945
|
+
for (const n of laid.nodes) {
|
|
946
|
+
const title = n.title;
|
|
947
|
+
if (!title) continue;
|
|
948
|
+
const x0 = n.cx - n.w / 2;
|
|
949
|
+
const y0 = n.cy - n.h / 2;
|
|
950
|
+
const x1 = n.cx + n.w / 2;
|
|
951
|
+
const y1 = n.cy + n.h / 2;
|
|
952
|
+
const cur = bounds.get(title);
|
|
953
|
+
if (cur) {
|
|
954
|
+
cur.x0 = Math.min(cur.x0, x0);
|
|
955
|
+
cur.y0 = Math.min(cur.y0, y0);
|
|
956
|
+
cur.x1 = Math.max(cur.x1, x1);
|
|
957
|
+
cur.y1 = Math.max(cur.y1, y1);
|
|
958
|
+
} else {
|
|
959
|
+
bounds.set(title, { x0, y0, x1, y1 });
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
const out = /* @__PURE__ */ new Map();
|
|
963
|
+
for (const [name, b] of bounds) {
|
|
964
|
+
out.set(name, { cx: (b.x0 + b.x1) / 2, cy: (b.y0 + b.y1) / 2, w: b.x1 - b.x0, h: b.y1 - b.y0 });
|
|
965
|
+
}
|
|
966
|
+
for (const lane of laid.lanes) {
|
|
967
|
+
const label = lane.label;
|
|
968
|
+
if (!label || out.has(label)) continue;
|
|
969
|
+
const y = lane.y ?? 0;
|
|
970
|
+
const h = lane.height ?? 0;
|
|
971
|
+
out.set(label, { cx: (lane.x ?? 0) + lane.width / 2, cy: y + h / 2, w: lane.width, h });
|
|
972
|
+
}
|
|
973
|
+
return out;
|
|
974
|
+
}
|
|
975
|
+
function withDocPositions(doc, assign) {
|
|
976
|
+
if (assign.size === 0) return doc;
|
|
977
|
+
return {
|
|
978
|
+
...doc,
|
|
979
|
+
actors: doc.actors.map((a) => {
|
|
980
|
+
const p = assign.get(a.name);
|
|
981
|
+
return p ? { ...a, posX: p.posX, posY: p.posY } : a;
|
|
982
|
+
})
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
function withPositions(diagram2, doc, assign) {
|
|
986
|
+
const probe = {
|
|
987
|
+
...diagram2,
|
|
988
|
+
lanes: diagram2.lanes.map((l) => ({ ...l })),
|
|
989
|
+
nodes: diagram2.nodes.map((n) => ({ ...n }))
|
|
990
|
+
};
|
|
991
|
+
applyCanvasPivotPositions(probe, withDocPositions(doc, assign));
|
|
992
|
+
return probe;
|
|
993
|
+
}
|
|
994
|
+
function applyNodeTones(diagram2, doc) {
|
|
995
|
+
for (const actor of doc.actors) {
|
|
996
|
+
if (actor.tone === void 0) continue;
|
|
997
|
+
for (const node of diagram2.nodes) {
|
|
998
|
+
if (node.title === actor.name) node.tone = actor.tone;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
function applyCanvasPivotPositions(diagram2, doc) {
|
|
1003
|
+
for (const actor of doc.actors) {
|
|
1004
|
+
if (actor.partId !== void 0) continue;
|
|
1005
|
+
const aliasSlug = slugify(actor.name);
|
|
1006
|
+
if (actor.posX !== void 0 && actor.posY !== void 0) {
|
|
1007
|
+
for (const lane of diagram2.lanes) {
|
|
1008
|
+
if (lane.id === aliasSlug || lane.id === actor.name) {
|
|
1009
|
+
lane.posX = actor.posX;
|
|
1010
|
+
lane.posY = actor.posY;
|
|
1011
|
+
if (actor.posW !== void 0) lane.posW = actor.posW;
|
|
1012
|
+
if (actor.posH !== void 0) lane.posH = actor.posH;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
for (const node of diagram2.nodes) {
|
|
1016
|
+
if (node.id === aliasSlug || node.id === actor.name) {
|
|
1017
|
+
node.posX = actor.posX;
|
|
1018
|
+
node.posY = actor.posY;
|
|
1019
|
+
if (actor.posW !== void 0) node.posW = actor.posW;
|
|
1020
|
+
if (actor.posH !== void 0) node.posH = actor.posH;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (actor.nodes) {
|
|
1025
|
+
for (const [subKey, override] of Object.entries(actor.nodes)) {
|
|
1026
|
+
if (override.posX === void 0 || override.posY === void 0) continue;
|
|
1027
|
+
for (const node of diagram2.nodes) {
|
|
1028
|
+
if (node.id === `${aliasSlug}-${subKey}` || node.id === `${subKey}-${aliasSlug}`) {
|
|
1029
|
+
node.posX = override.posX;
|
|
1030
|
+
node.posY = override.posY;
|
|
1031
|
+
if (override.posW !== void 0) node.posW = override.posW;
|
|
1032
|
+
if (override.posH !== void 0) node.posH = override.posH;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function lookupPart(partsCatalog, partId) {
|
|
1040
|
+
const found = lookupPartRaw(partsCatalog, partId);
|
|
1041
|
+
if (found === void 0) return void 0;
|
|
1042
|
+
return partIsMeasurable(found) ? found : void 0;
|
|
1043
|
+
}
|
|
1044
|
+
function lookupPartRaw(partsCatalog, partId) {
|
|
1045
|
+
if (typeof partId !== "string" || partId.length === 0) return void 0;
|
|
1046
|
+
if (Object.hasOwn(partsCatalog, partId)) return partsCatalog[partId];
|
|
1047
|
+
if (Object.hasOwn(partsCatalog, `parts-${partId}`)) return partsCatalog[`parts-${partId}`];
|
|
1048
|
+
return void 0;
|
|
1049
|
+
}
|
|
1050
|
+
function partIsMeasurable(part) {
|
|
1051
|
+
return countDiagramElements(part) <= MAX_INPUT_ELEMENTS;
|
|
1052
|
+
}
|
|
1053
|
+
var CDL_DEFAULT_NODE_W = 340;
|
|
1054
|
+
var CDL_DEFAULT_NODE_H = 200;
|
|
1055
|
+
function positiveOr(value, fallback) {
|
|
1056
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
1057
|
+
}
|
|
1058
|
+
function maxOf(values, fallback) {
|
|
1059
|
+
if (values.length === 0) return fallback;
|
|
1060
|
+
let out = values[0];
|
|
1061
|
+
for (const v of values) if (v > out) out = v;
|
|
1062
|
+
return out;
|
|
1063
|
+
}
|
|
1064
|
+
function minOf(values, fallback) {
|
|
1065
|
+
if (values.length === 0) return fallback;
|
|
1066
|
+
let out = values[0];
|
|
1067
|
+
for (const v of values) if (v < out) out = v;
|
|
1068
|
+
return out;
|
|
1069
|
+
}
|
|
1070
|
+
var PART_STACK_PITCH = 220;
|
|
1071
|
+
var MAX_PART_SCALE = 1e3;
|
|
1072
|
+
function normalizePartScale(value) {
|
|
1073
|
+
if (!Number.isFinite(value) || value <= 0) return 1;
|
|
1074
|
+
return Math.min(value, MAX_PART_SCALE);
|
|
1075
|
+
}
|
|
1076
|
+
function partScaleFactor(part, posW, posH, scale) {
|
|
1077
|
+
const base = partScaleBase(part);
|
|
1078
|
+
const k = scale === void 0 ? 1 : normalizePartScale(scale);
|
|
1079
|
+
const rx = posW !== void 0 && posW > 0 ? posW / base.w : 1;
|
|
1080
|
+
const ry = posH !== void 0 && posH > 0 ? posH / base.h : 1;
|
|
1081
|
+
return { x: normalizePartScale(rx * k), y: normalizePartScale(ry * k) };
|
|
1082
|
+
}
|
|
1083
|
+
function partTargetSize(part, posW, posH, scale) {
|
|
1084
|
+
if (posW === void 0 && posH === void 0 && scale === void 0) {
|
|
1085
|
+
return { w: void 0, h: void 0 };
|
|
1086
|
+
}
|
|
1087
|
+
const base = partScaleBase(part);
|
|
1088
|
+
const f = partScaleFactor(part, posW, posH, scale);
|
|
1089
|
+
return {
|
|
1090
|
+
w: posW === void 0 && scale === void 0 ? void 0 : base.w * f.x,
|
|
1091
|
+
h: posH === void 0 && scale === void 0 ? void 0 : base.h * f.y
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
function partScaleBase(part) {
|
|
1095
|
+
const lanes = Array.isArray(part.lanes) ? part.lanes : [];
|
|
1096
|
+
const nodes = Array.isArray(part.nodes) ? part.nodes : [];
|
|
1097
|
+
const lefts = [];
|
|
1098
|
+
const rights = [];
|
|
1099
|
+
for (const l of lanes) {
|
|
1100
|
+
const lx = typeof l.x === "number" && Number.isFinite(l.x) ? l.x : 0;
|
|
1101
|
+
const lw = positiveOr(l.width, 400);
|
|
1102
|
+
lefts.push(lx);
|
|
1103
|
+
rights.push(lx + lw);
|
|
1104
|
+
}
|
|
1105
|
+
const stacks = nodes.map(
|
|
1106
|
+
(n) => typeof n.stack === "number" && Number.isFinite(n.stack) ? n.stack : 0
|
|
1107
|
+
);
|
|
1108
|
+
return {
|
|
1109
|
+
w: positiveOr(maxOf(rights, 400) - minOf(lefts, 0), 400),
|
|
1110
|
+
h: Math.max(1, (maxOf(stacks, 0) - minOf(stacks, 0) + 1) * PART_STACK_PITCH)
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
function partTargetScale(part, targetW, targetH) {
|
|
1114
|
+
const none = { x: 1, y: 1 };
|
|
1115
|
+
if (!Array.isArray(part.lanes) || !Array.isArray(part.nodes)) return none;
|
|
1116
|
+
return partScaleFactor(part, targetW, targetH, void 0);
|
|
1117
|
+
}
|
|
1118
|
+
function partExtent(part, targetW, targetH) {
|
|
1119
|
+
const fallback = { w: 400, h: 200, dx: 0, dy: 0 };
|
|
1120
|
+
if (!Array.isArray(part.lanes) || !Array.isArray(part.nodes)) return fallback;
|
|
1121
|
+
if (part.nodes.length === 0) return fallback;
|
|
1122
|
+
const lanes = /* @__PURE__ */ new Map();
|
|
1123
|
+
const laneLefts = [];
|
|
1124
|
+
const laneRights = [];
|
|
1125
|
+
for (const l of part.lanes) {
|
|
1126
|
+
const x = typeof l.x === "number" && Number.isFinite(l.x) ? l.x : 0;
|
|
1127
|
+
const w = positiveOr(l.width, 400);
|
|
1128
|
+
lanes.set(l.id, { x, w });
|
|
1129
|
+
laneLefts.push(x);
|
|
1130
|
+
laneRights.push(x + w);
|
|
1131
|
+
}
|
|
1132
|
+
const bboxW = positiveOr(maxOf(laneRights, 400) - minOf(laneLefts, 0), 400);
|
|
1133
|
+
const bboxCenterX = minOf(laneLefts, 0) + bboxW / 2;
|
|
1134
|
+
const { x: scaleX, y: scaleY } = partTargetScale(part, targetW, targetH);
|
|
1135
|
+
const stacks = part.nodes.map(
|
|
1136
|
+
(n) => typeof n.stack === "number" && Number.isFinite(n.stack) ? n.stack : 0
|
|
1137
|
+
);
|
|
1138
|
+
const maxStack = maxOf(stacks, 0);
|
|
1139
|
+
const minStack = minOf(stacks, 0);
|
|
1140
|
+
const centerStack = (minStack + maxStack) / 2;
|
|
1141
|
+
const tops = [];
|
|
1142
|
+
const bottoms = [];
|
|
1143
|
+
const lefts = [];
|
|
1144
|
+
const rights = [];
|
|
1145
|
+
part.nodes.forEach((n, i) => {
|
|
1146
|
+
const lane = lanes.get(n.lane) ?? { x: 0, w: 320 };
|
|
1147
|
+
const cx = (lane.x + lane.w / 2 - bboxCenterX) * scaleX;
|
|
1148
|
+
const cy = ((stacks[i] ?? 0) - centerStack) * PART_STACK_PITCH * scaleY;
|
|
1149
|
+
const halfW = positiveOr(n.w, CDL_DEFAULT_NODE_W) * scaleX / 2;
|
|
1150
|
+
const halfH = positiveOr(n.h, CDL_DEFAULT_NODE_H) * scaleY / 2;
|
|
1151
|
+
lefts.push(cx - halfW);
|
|
1152
|
+
rights.push(cx + halfW);
|
|
1153
|
+
tops.push(cy - halfH);
|
|
1154
|
+
bottoms.push(cy + halfH);
|
|
1155
|
+
});
|
|
1156
|
+
const x0 = minOf(lefts, 0);
|
|
1157
|
+
const x1 = maxOf(rights, 400);
|
|
1158
|
+
const y0 = minOf(tops, 0);
|
|
1159
|
+
const y1 = maxOf(bottoms, 200);
|
|
1160
|
+
return {
|
|
1161
|
+
w: positiveOr(x1 - x0, 400),
|
|
1162
|
+
h: positiveOr(y1 - y0, 200),
|
|
1163
|
+
dx: Number.isFinite((x0 + x1) / 2) ? (x0 + x1) / 2 : 0,
|
|
1164
|
+
dy: Number.isFinite((y0 + y1) / 2) ? (y0 + y1) / 2 : 0
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
function partRenderSize(part) {
|
|
1168
|
+
const g = partFrameGeometry(part);
|
|
1169
|
+
return { w: g.w, h: g.h };
|
|
1170
|
+
}
|
|
1171
|
+
function partDrawsInDiagram(part) {
|
|
1172
|
+
const readouts = part.readouts;
|
|
1173
|
+
if (!Array.isArray(readouts) || readouts.length === 0) return true;
|
|
1174
|
+
const g = partFrameGeometry(part);
|
|
1175
|
+
if (!(g.w > 0) || !(g.h > 0)) return true;
|
|
1176
|
+
const ratio = g.boxW / g.w * (g.boxH / g.h);
|
|
1177
|
+
if (!Number.isFinite(ratio)) return true;
|
|
1178
|
+
return ratio >= 0.01;
|
|
1179
|
+
}
|
|
1180
|
+
function partBoxInFrame(part) {
|
|
1181
|
+
const g = partFrameGeometry(part);
|
|
1182
|
+
return { w: g.boxW, h: g.boxH, left: g.left, top: g.top };
|
|
1183
|
+
}
|
|
1184
|
+
var PART_FRAME_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1185
|
+
function partFrameGeometry(part) {
|
|
1186
|
+
const cached = PART_FRAME_CACHE.get(part);
|
|
1187
|
+
if (cached) return cached;
|
|
1188
|
+
const fallback = { w: 400, h: 200, left: 0, top: 0, boxW: 400, boxH: 200 };
|
|
1189
|
+
let out = fallback;
|
|
1190
|
+
if (countDiagramElements(part) <= MAX_INPUT_ELEMENTS) {
|
|
1191
|
+
try {
|
|
1192
|
+
const own = layout(part);
|
|
1193
|
+
const vb = own.viewBox;
|
|
1194
|
+
const w = positiveOr(vb.w, 400);
|
|
1195
|
+
const h = positiveOr(vb.h, 200);
|
|
1196
|
+
if (own.nodes.length === 0) {
|
|
1197
|
+
out = { w, h, left: 0, top: 0, boxW: w, boxH: h };
|
|
1198
|
+
} else {
|
|
1199
|
+
let x0 = Infinity;
|
|
1200
|
+
let y0 = Infinity;
|
|
1201
|
+
let x1 = -Infinity;
|
|
1202
|
+
let y1 = -Infinity;
|
|
1203
|
+
for (const n of own.nodes) {
|
|
1204
|
+
x0 = Math.min(x0, n.cx - n.w / 2);
|
|
1205
|
+
x1 = Math.max(x1, n.cx + n.w / 2);
|
|
1206
|
+
y0 = Math.min(y0, n.cy - n.h / 2);
|
|
1207
|
+
y1 = Math.max(y1, n.cy + n.h / 2);
|
|
1208
|
+
}
|
|
1209
|
+
const left = x0 - vb.x;
|
|
1210
|
+
const top = y0 - vb.y;
|
|
1211
|
+
out = {
|
|
1212
|
+
w,
|
|
1213
|
+
h,
|
|
1214
|
+
left: Number.isFinite(left) ? left : 0,
|
|
1215
|
+
top: Number.isFinite(top) ? top : 0,
|
|
1216
|
+
boxW: positiveOr(x1 - x0, w),
|
|
1217
|
+
boxH: positiveOr(y1 - y0, h)
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
} catch {
|
|
1221
|
+
out = fallback;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
PART_FRAME_CACHE.set(part, out);
|
|
1225
|
+
return out;
|
|
1226
|
+
}
|
|
1227
|
+
function partFrameExtent(part, targetW, targetH) {
|
|
1228
|
+
const box = partExtent(part, targetW, targetH);
|
|
1229
|
+
const geom = partFrameGeometry(part);
|
|
1230
|
+
const t = partTargetScale(part, targetW, targetH);
|
|
1231
|
+
const frame = {
|
|
1232
|
+
w: geom.w * t.x,
|
|
1233
|
+
h: geom.h * t.y,
|
|
1234
|
+
left: geom.left * t.x,
|
|
1235
|
+
top: geom.top * t.y
|
|
1236
|
+
};
|
|
1237
|
+
const frameDx = box.dx + frame.w / 2 - frame.left - box.w / 2;
|
|
1238
|
+
const frameDy = box.dy + frame.h / 2 - frame.top - box.h / 2;
|
|
1239
|
+
const x0 = Math.min(frameDx - frame.w / 2, box.dx - box.w / 2);
|
|
1240
|
+
const x1 = Math.max(frameDx + frame.w / 2, box.dx + box.w / 2);
|
|
1241
|
+
const y0 = Math.min(frameDy - frame.h / 2, box.dy - box.h / 2);
|
|
1242
|
+
const y1 = Math.max(frameDy + frame.h / 2, box.dy + box.h / 2);
|
|
1243
|
+
return {
|
|
1244
|
+
w: positiveOr(x1 - x0, frame.w),
|
|
1245
|
+
h: positiveOr(y1 - y0, frame.h),
|
|
1246
|
+
dx: Number.isFinite((x0 + x1) / 2) ? (x0 + x1) / 2 : frameDx,
|
|
1247
|
+
dy: Number.isFinite((y0 + y1) / 2) ? (y0 + y1) / 2 : frameDy
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
function partVisualSize(part, targetW, targetH) {
|
|
1251
|
+
const e = partExtent(part, targetW, targetH);
|
|
1252
|
+
return { w: e.w, h: e.h };
|
|
1253
|
+
}
|
|
1254
|
+
var PARTS_PER_ROW = 3;
|
|
1255
|
+
var PARTS_GAP = 120;
|
|
1256
|
+
var STACK_PITCH = 280;
|
|
1257
|
+
function partsGridCenters(baseNodeCount, items) {
|
|
1258
|
+
const out = /* @__PURE__ */ new Map();
|
|
1259
|
+
if (items.length === 0) return out;
|
|
1260
|
+
const safeCount = Number.isSafeInteger(baseNodeCount) && baseNodeCount >= 0 ? baseNodeCount : 0;
|
|
1261
|
+
const top = safeCount * STACK_PITCH + PARTS_GAP * 2;
|
|
1262
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1263
|
+
const unique = items.filter((i) => {
|
|
1264
|
+
if (seen.has(i.id)) return false;
|
|
1265
|
+
seen.add(i.id);
|
|
1266
|
+
return true;
|
|
1267
|
+
});
|
|
1268
|
+
const cellW = maxOf(
|
|
1269
|
+
unique.map((i) => positiveOr(i.w, 400)),
|
|
1270
|
+
400
|
|
1271
|
+
);
|
|
1272
|
+
const rowTops = [];
|
|
1273
|
+
{
|
|
1274
|
+
let y = top;
|
|
1275
|
+
for (let i = 0; i < unique.length; i += PARTS_PER_ROW) {
|
|
1276
|
+
rowTops.push(y);
|
|
1277
|
+
const rowH = maxOf(
|
|
1278
|
+
unique.slice(i, i + PARTS_PER_ROW).map((x) => positiveOr(x.h, 200)),
|
|
1279
|
+
200
|
|
1280
|
+
);
|
|
1281
|
+
y += rowH + PARTS_GAP;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
unique.forEach((item, i) => {
|
|
1285
|
+
const col = i % PARTS_PER_ROW;
|
|
1286
|
+
const row = Math.floor(i / PARTS_PER_ROW);
|
|
1287
|
+
const cx = col * (cellW + PARTS_GAP) + cellW / 2;
|
|
1288
|
+
const cy = (rowTops[row] ?? top) + positiveOr(item.h, 200) / 2;
|
|
1289
|
+
if (!Number.isFinite(cx) || !Number.isFinite(cy)) return;
|
|
1290
|
+
out.set(item.id, { cx, cy });
|
|
1291
|
+
});
|
|
1292
|
+
return out;
|
|
1293
|
+
}
|
|
1294
|
+
function partsBudget(target, partsActors, partsCatalog) {
|
|
1295
|
+
const accepted = /* @__PURE__ */ new Set();
|
|
1296
|
+
let used = countDiagramElements(target);
|
|
1297
|
+
partsActors.forEach((a, i) => {
|
|
1298
|
+
const part = lookupPart(partsCatalog, a.partId);
|
|
1299
|
+
if (part === void 0) return;
|
|
1300
|
+
const cost = countDiagramElements(part);
|
|
1301
|
+
if (used + cost > MAX_INPUT_ELEMENTS) return;
|
|
1302
|
+
used += cost;
|
|
1303
|
+
accepted.add(i);
|
|
1304
|
+
});
|
|
1305
|
+
return accepted;
|
|
1306
|
+
}
|
|
1307
|
+
function partGridCenters(target, doc, partsCatalog, accepted, acceptedFrom) {
|
|
1308
|
+
const partsActors = doc.actors.filter((a) => a.partId !== void 0);
|
|
1309
|
+
const autoActors = partsActors.filter(
|
|
1310
|
+
(a) => a.posX === void 0 && a.posY === void 0 && a.posRel === void 0
|
|
1311
|
+
);
|
|
1312
|
+
if (autoActors.length === 0) return /* @__PURE__ */ new Map();
|
|
1313
|
+
const otherActorNames = new Set(
|
|
1314
|
+
doc.actors.filter((a) => a.partId === void 0).map((a) => a.name)
|
|
1315
|
+
);
|
|
1316
|
+
const partsActorNames = new Set(
|
|
1317
|
+
partsActors.map((a) => a.name).filter((n) => !otherActorNames.has(n))
|
|
1318
|
+
);
|
|
1319
|
+
const sharedLaneIds = new Set(
|
|
1320
|
+
partsActors.map((a) => a.lane).filter((l) => l !== void 0)
|
|
1321
|
+
);
|
|
1322
|
+
const partsLaneIds = /* @__PURE__ */ new Set();
|
|
1323
|
+
for (const l of target.lanes) {
|
|
1324
|
+
if (l.label === void 0) continue;
|
|
1325
|
+
if (!partsActorNames.has(l.label)) continue;
|
|
1326
|
+
if (sharedLaneIds.has(l.id)) continue;
|
|
1327
|
+
partsLaneIds.add(l.id);
|
|
1328
|
+
}
|
|
1329
|
+
const baseNodes = target.nodes.filter(
|
|
1330
|
+
(n) => !partsActorNames.has(n.title) && !partsLaneIds.has(n.lane)
|
|
1331
|
+
);
|
|
1332
|
+
const acceptedNames = accepted === void 0 || acceptedFrom === void 0 ? void 0 : new Set(acceptedFrom.filter((_, i) => accepted.has(i)).map((a) => a.name));
|
|
1333
|
+
const placedActors = autoActors.filter(
|
|
1334
|
+
(a) => lookupPart(partsCatalog, a.partId) !== void 0 && (acceptedNames === void 0 || acceptedNames.has(a.name))
|
|
1335
|
+
);
|
|
1336
|
+
const extents = /* @__PURE__ */ new Map();
|
|
1337
|
+
for (const a of placedActors) {
|
|
1338
|
+
const part = lookupPart(partsCatalog, a.partId);
|
|
1339
|
+
const t = partTargetSize(part, a.posW, a.posH, a.scale);
|
|
1340
|
+
extents.set(a.name, partFrameExtent(part, t.w, t.h));
|
|
1341
|
+
}
|
|
1342
|
+
const centers = partsGridCenters(
|
|
1343
|
+
baseNodes.length,
|
|
1344
|
+
placedActors.map((a) => ({ id: a.name, ...extents.get(a.name) }))
|
|
1345
|
+
);
|
|
1346
|
+
const out = /* @__PURE__ */ new Map();
|
|
1347
|
+
for (const [name, c] of centers) {
|
|
1348
|
+
const e = extents.get(name);
|
|
1349
|
+
out.set(name, { cx: c.cx - e.dx, cy: c.cy - e.dy });
|
|
1350
|
+
}
|
|
1351
|
+
return out;
|
|
1352
|
+
}
|
|
1353
|
+
function partSizes(doc, partsCatalog) {
|
|
1354
|
+
const out = /* @__PURE__ */ new Map();
|
|
1355
|
+
for (const a of doc.actors) {
|
|
1356
|
+
if (a.partId === void 0) continue;
|
|
1357
|
+
const part = lookupPart(partsCatalog, a.partId);
|
|
1358
|
+
if (part) {
|
|
1359
|
+
const t = partTargetSize(part, a.posW, a.posH, a.scale);
|
|
1360
|
+
out.set(a.name, partExtent(part, t.w, t.h));
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
return out;
|
|
1364
|
+
}
|
|
1365
|
+
function partBoxes(target, doc, partsCatalog) {
|
|
1366
|
+
const grid = partGridCenters(target, doc, partsCatalog);
|
|
1367
|
+
const out = /* @__PURE__ */ new Map();
|
|
1368
|
+
for (const a of doc.actors) {
|
|
1369
|
+
if (a.partId === void 0) continue;
|
|
1370
|
+
const part = lookupPart(partsCatalog, a.partId);
|
|
1371
|
+
if (!part) continue;
|
|
1372
|
+
const t = partTargetSize(part, a.posW, a.posH, a.scale);
|
|
1373
|
+
const size = partExtent(part, t.w, t.h);
|
|
1374
|
+
const placed = a.posX !== void 0 && a.posY !== void 0 ? { cx: a.posX, cy: a.posY } : grid.get(a.name);
|
|
1375
|
+
if (!placed) continue;
|
|
1376
|
+
out.set(a.name, { cx: placed.cx + size.dx, cy: placed.cy + size.dy, w: size.w, h: size.h });
|
|
1377
|
+
}
|
|
1378
|
+
return out;
|
|
1379
|
+
}
|
|
1380
|
+
function cleanupPlaceholderActor(target, doc, a) {
|
|
1381
|
+
const aliasSlug = slugify(a.name);
|
|
1382
|
+
const ownedLaneIds = /* @__PURE__ */ new Set();
|
|
1383
|
+
if (doc.type === "sequence" || doc.type === "solidity") {
|
|
1384
|
+
for (const l of target.lanes) {
|
|
1385
|
+
if (a.lane !== void 0 && l.id === a.lane) continue;
|
|
1386
|
+
if (l.label === a.name) ownedLaneIds.add(l.id);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
const ownedNodeIds = /* @__PURE__ */ new Set();
|
|
1390
|
+
for (const n of target.nodes) {
|
|
1391
|
+
if (ownedLaneIds.has(n.lane)) ownedNodeIds.add(n.id);
|
|
1392
|
+
}
|
|
1393
|
+
const matchesAliasSlug = (id) => {
|
|
1394
|
+
if (id === aliasSlug) return true;
|
|
1395
|
+
if (id.startsWith(`${aliasSlug}-`)) return true;
|
|
1396
|
+
if (/^s\d+-/.test(id) && id.endsWith(`-${aliasSlug}`)) return true;
|
|
1397
|
+
return false;
|
|
1398
|
+
};
|
|
1399
|
+
const relatedToActor = (id) => ownedLaneIds.size > 0 ? ownedNodeIds.has(id) : matchesAliasSlug(id);
|
|
1400
|
+
target.nodes = target.nodes.filter((n) => !relatedToActor(n.id));
|
|
1401
|
+
const removedEdgeIds = /* @__PURE__ */ new Set();
|
|
1402
|
+
target.edges = target.edges.filter((e) => {
|
|
1403
|
+
const drop = relatedToActor(e.from) || relatedToActor(e.to);
|
|
1404
|
+
if (drop) removedEdgeIds.add(e.id);
|
|
1405
|
+
return !drop;
|
|
1406
|
+
});
|
|
1407
|
+
if (doc.type === "sequence" || doc.type === "solidity") {
|
|
1408
|
+
target.lanes = target.lanes.filter((l) => {
|
|
1409
|
+
if (a.lane !== void 0 && l.id === a.lane) return true;
|
|
1410
|
+
if (l.label === a.name) return false;
|
|
1411
|
+
if (l.id === aliasSlug) return false;
|
|
1412
|
+
return true;
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
for (const phase of target.phases) {
|
|
1416
|
+
phase.activate = phase.activate.filter((id) => !relatedToActor(id) && !removedEdgeIds.has(id));
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
function mergePartsFromActors(target, doc, partsCatalog, onNotice) {
|
|
1420
|
+
const partsActors = doc.actors.filter((a) => a.partId !== void 0);
|
|
1421
|
+
if (partsActors.length === 0) return target;
|
|
1422
|
+
if (!partsCatalog) {
|
|
1423
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
1424
|
+
const names = partsActors.map((a) => `${a.name} (kind: ${a.partId ?? "?"})`).join(", ");
|
|
1425
|
+
console.warn(`[dragon] parts kind actors detected but no partsCatalog provided: ${names}`);
|
|
1426
|
+
}
|
|
1427
|
+
return target;
|
|
1428
|
+
}
|
|
1429
|
+
const budget = partsBudget(target, partsActors, partsCatalog);
|
|
1430
|
+
const gridCenters = partGridCenters(target, doc, partsCatalog, budget, partsActors);
|
|
1431
|
+
for (const [actorIndex, actor] of partsActors.entries()) {
|
|
1432
|
+
const partId = actor.partId;
|
|
1433
|
+
if (typeof partId !== "string" || partId.length === 0) continue;
|
|
1434
|
+
const found = lookupPartRaw(partsCatalog, partId);
|
|
1435
|
+
if (found !== void 0 && !budget.has(actorIndex)) {
|
|
1436
|
+
const overOne = !partIsMeasurable(found);
|
|
1437
|
+
onNotice?.({
|
|
1438
|
+
kind: "part-not-drawn",
|
|
1439
|
+
actor: actor.name,
|
|
1440
|
+
line: 0,
|
|
1441
|
+
message: `"${actor.name}" (${partId}) \u306F\u5927\u304D\u3059\u304E\u308B\u305F\u3081\u53D6\u308A\u8FBC\u307F\u307E\u305B\u3093\u3002`,
|
|
1442
|
+
hint: overOne ? `\u8981\u7D20\u6570\u304C\u4E0A\u9650 (${MAX_INPUT_ELEMENTS}) \u3092\u8D85\u3048\u3066\u3044\u307E\u3059` : `\u56F3\u5168\u4F53\u306E\u8981\u7D20\u6570\u304C\u4E0A\u9650 (${MAX_INPUT_ELEMENTS}) \u3092\u8D85\u3048\u307E\u3059`
|
|
1443
|
+
});
|
|
1444
|
+
cleanupPlaceholderActor(target, doc, actor);
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
const part = found;
|
|
1448
|
+
if (!part) {
|
|
1449
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
1450
|
+
console.warn(`[dragon] parts kind "${partId}" not found in partsCatalog (actor: ${actor.name})`);
|
|
1451
|
+
}
|
|
1452
|
+
continue;
|
|
1453
|
+
}
|
|
1454
|
+
cleanupPlaceholderActor(target, doc, actor);
|
|
1455
|
+
const merged = applyColorHex(part, actor.colorHex, actor.stateOverride ?? {});
|
|
1456
|
+
let placeX = actor.posX;
|
|
1457
|
+
let placeY = actor.posY;
|
|
1458
|
+
if (placeX === void 0 && placeY === void 0) {
|
|
1459
|
+
const center = gridCenters.get(actor.name);
|
|
1460
|
+
placeX = center?.cx;
|
|
1461
|
+
placeY = center?.cy;
|
|
1462
|
+
}
|
|
1463
|
+
const written = new Set(actor.scaleKeys ?? []);
|
|
1464
|
+
if (written.size > 0) {
|
|
1465
|
+
const clashed = (part.states ?? []).find((st) => written.has(String(st.id ?? "")));
|
|
1466
|
+
if (clashed) {
|
|
1467
|
+
onNotice?.({
|
|
1468
|
+
kind: "scale-reserved",
|
|
1469
|
+
actor: actor.name,
|
|
1470
|
+
line: actor.pos?.line ?? 0,
|
|
1471
|
+
message: `"${clashed.id}" \u306F\u898B\u672C\u306E\u5927\u304D\u3055\u3092\u5909\u3048\u308B\u9805\u76EE\u3068\u3057\u3066\u6271\u3044\u307E\u3057\u305F (${clashed.id} \u3068\u3044\u3046\u540D\u524D\u306E\u72B6\u614B\u306F\u5909\u3048\u3066\u3044\u307E\u305B\u3093)`,
|
|
1472
|
+
hint: `\u72B6\u614B\u3092\u5909\u3048\u305F\u3044\u6642\u306F \`state: { ${clashed.id}: ... }\` \u3068\u66F8\u304F`
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
const t = partTargetSize(part, actor.posW, actor.posH, actor.scale);
|
|
1477
|
+
mergePartIntoDiagram(target, part, actor.name, merged, actor.lane, placeX, placeY, t.w, t.h, onNotice, actor.pos?.line ?? 0);
|
|
1478
|
+
}
|
|
1479
|
+
return target;
|
|
1480
|
+
}
|
|
1481
|
+
function resolveStateOverride(original, override) {
|
|
1482
|
+
if (override === void 0) return { initial: original, rejected: false };
|
|
1483
|
+
if (isColorValue(original) && !isColorValue(override)) return { initial: original, rejected: true };
|
|
1484
|
+
return { initial: override, rejected: false };
|
|
1485
|
+
}
|
|
1486
|
+
function applyColorHex(part, colorHex, stateOverride) {
|
|
1487
|
+
if (!colorHex) return stateOverride;
|
|
1488
|
+
const colorStates = part.states.filter((st) => isColorValue(st.initial));
|
|
1489
|
+
if (colorStates.length === 0) return stateOverride;
|
|
1490
|
+
const out = { ...stateOverride };
|
|
1491
|
+
for (const st of colorStates) {
|
|
1492
|
+
if (out[st.id] === void 0) out[st.id] = colorHex;
|
|
1493
|
+
}
|
|
1494
|
+
return out;
|
|
1495
|
+
}
|
|
1496
|
+
function mergePartIntoDiagram(target, part, alias, stateOverride, laneMapping, offsetX, offsetY, targetW, targetH, onNotice, noticeLine = 0) {
|
|
1497
|
+
const prefix = (id) => `${alias}__${id}`;
|
|
1498
|
+
const stateIdSet = new Set(part.states.map((s) => s.id));
|
|
1499
|
+
const rewriteTemplate = (s) => {
|
|
1500
|
+
if (!s) return s;
|
|
1501
|
+
return s.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (m, name) => {
|
|
1502
|
+
return stateIdSet.has(name) ? `{${prefix(name)}}` : m;
|
|
1503
|
+
});
|
|
1504
|
+
};
|
|
1505
|
+
const targetLaneId = laneMapping;
|
|
1506
|
+
const laneIdMap = /* @__PURE__ */ new Map();
|
|
1507
|
+
const PARTS_LANE_GAP = 300;
|
|
1508
|
+
const existingLaneMaxX = target.lanes.length > 0 ? Math.max(...target.lanes.map((l) => (l.x ?? 0) + l.width)) : 0;
|
|
1509
|
+
const partLaneGeom = /* @__PURE__ */ new Map();
|
|
1510
|
+
for (const l of part.lanes) {
|
|
1511
|
+
partLaneGeom.set(l.id, {
|
|
1512
|
+
x: typeof l.x === "number" && Number.isFinite(l.x) ? l.x : 0,
|
|
1513
|
+
w: positiveOr(l.width, 400)
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
const laneXs = [...partLaneGeom.values()].map((g) => g.x);
|
|
1517
|
+
const laneRights = [...partLaneGeom.values()].map((g) => g.x + g.w);
|
|
1518
|
+
const partMinLaneX = minOf(laneXs, 0);
|
|
1519
|
+
const partMaxLaneRight = maxOf(laneRights, 400);
|
|
1520
|
+
const rawBboxW = partMaxLaneRight - partMinLaneX;
|
|
1521
|
+
const partsBboxW = rawBboxW > 0 ? rawBboxW : 1;
|
|
1522
|
+
const rawLaneScaleX = targetW !== void 0 && targetW > 0 ? targetW / partsBboxW : 1;
|
|
1523
|
+
const laneScaleX = Number.isFinite(rawLaneScaleX) && rawLaneScaleX > 0 ? rawLaneScaleX : 1;
|
|
1524
|
+
const partOrigBboxCenterX = partMinLaneX + partsBboxW / 2;
|
|
1525
|
+
const dropCenterX = offsetX !== void 0 ? offsetX : existingLaneMaxX + PARTS_LANE_GAP + partsBboxW * laneScaleX / 2;
|
|
1526
|
+
const mapLaneX = (x) => (x - partOrigBboxCenterX) * laneScaleX + dropCenterX;
|
|
1527
|
+
for (const laneOrig of part.lanes) {
|
|
1528
|
+
if (targetLaneId) {
|
|
1529
|
+
laneIdMap.set(laneOrig.id, targetLaneId);
|
|
1530
|
+
} else {
|
|
1531
|
+
const newLaneId = prefix(laneOrig.id);
|
|
1532
|
+
laneIdMap.set(laneOrig.id, newLaneId);
|
|
1533
|
+
const geom = partLaneGeom.get(laneOrig.id) ?? { x: 0, w: 400 };
|
|
1534
|
+
target.lanes.push({
|
|
1535
|
+
...laneOrig,
|
|
1536
|
+
id: newLaneId,
|
|
1537
|
+
label: laneOrig.label ?? alias,
|
|
1538
|
+
x: mapLaneX(geom.x),
|
|
1539
|
+
width: geom.w * laneScaleX
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
const STACK_ISOLATION_OFFSET = 1e3;
|
|
1544
|
+
const shouldForcePos = offsetX !== void 0 || offsetY !== void 0;
|
|
1545
|
+
const targetMaxStack = shouldForcePos && target.nodes.length > 0 ? Math.max(...target.nodes.map((n) => n.stack ?? 0)) : 0;
|
|
1546
|
+
const stackShiftBase = shouldForcePos ? targetMaxStack + STACK_ISOLATION_OFFSET : 0;
|
|
1547
|
+
const STACK_PITCH_APPROX = 220;
|
|
1548
|
+
const partStacks = part.nodes.map(
|
|
1549
|
+
(n) => typeof n.stack === "number" && Number.isFinite(n.stack) ? n.stack : 0
|
|
1550
|
+
);
|
|
1551
|
+
const minStack = partStacks.length > 0 ? Math.min(...partStacks) : 0;
|
|
1552
|
+
const maxStack = partStacks.length > 0 ? Math.max(...partStacks) : 0;
|
|
1553
|
+
const partCenterStack = (minStack + maxStack) / 2;
|
|
1554
|
+
const partOrigH = Math.max(1, (maxStack - minStack + 1) * STACK_PITCH_APPROX);
|
|
1555
|
+
const scaleX = laneScaleX;
|
|
1556
|
+
const rawScaleY = targetH !== void 0 && targetH > 0 ? targetH / partOrigH : 1;
|
|
1557
|
+
const scaleY = Number.isFinite(rawScaleY) && rawScaleY > 0 ? rawScaleY : 1;
|
|
1558
|
+
for (const nodeOrig of part.nodes) {
|
|
1559
|
+
const mappedLane = laneIdMap.get(nodeOrig.lane) ?? nodeOrig.lane;
|
|
1560
|
+
let newShape = nodeOrig.shape ? deepRewriteStrings(nodeOrig.shape, rewriteTemplate) : void 0;
|
|
1561
|
+
if (newShape && (scaleX !== 1 || scaleY !== 1)) {
|
|
1562
|
+
const shapeScale = Math.min(scaleX, scaleY);
|
|
1563
|
+
const geomKeys = /* @__PURE__ */ new Set(["radius", "outerRadius", "innerRadius", "thickness"]);
|
|
1564
|
+
const scaleGeom = (obj) => {
|
|
1565
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
1566
|
+
if (Array.isArray(obj)) return obj.map(scaleGeom);
|
|
1567
|
+
const out = {};
|
|
1568
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1569
|
+
if (geomKeys.has(k) && typeof v === "number") {
|
|
1570
|
+
out[k] = v * shapeScale;
|
|
1571
|
+
} else if (typeof v === "object" && v !== null) {
|
|
1572
|
+
out[k] = scaleGeom(v);
|
|
1573
|
+
} else {
|
|
1574
|
+
out[k] = v;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
return out;
|
|
1578
|
+
};
|
|
1579
|
+
newShape = scaleGeom(newShape);
|
|
1580
|
+
}
|
|
1581
|
+
let nodePosX = nodeOrig.posX !== void 0 ? mapLaneX(nodeOrig.posX) : void 0;
|
|
1582
|
+
let nodePosY = nodeOrig.posY !== void 0 ? nodeOrig.posY + (offsetY ?? 0) : void 0;
|
|
1583
|
+
if (shouldForcePos && nodePosX === void 0) {
|
|
1584
|
+
const geom = partLaneGeom.get(nodeOrig.lane) ?? { x: 0, w: 320 };
|
|
1585
|
+
nodePosX = mapLaneX(geom.x + geom.w / 2);
|
|
1586
|
+
}
|
|
1587
|
+
if (shouldForcePos && nodePosY === void 0) {
|
|
1588
|
+
const stack = nodeOrig.stack ?? 0;
|
|
1589
|
+
nodePosY = (stack - partCenterStack) * STACK_PITCH_APPROX * scaleY + (offsetY ?? 0);
|
|
1590
|
+
}
|
|
1591
|
+
const rawNodeW = nodeOrig.w !== void 0 ? positiveOr(nodeOrig.w, 200) : void 0;
|
|
1592
|
+
const rawNodeH = nodeOrig.h !== void 0 ? positiveOr(nodeOrig.h, 200) : void 0;
|
|
1593
|
+
const nodeW = rawNodeW !== void 0 && (scaleX !== 1 || scaleY !== 1) ? rawNodeW * scaleX : rawNodeW;
|
|
1594
|
+
const nodeH = rawNodeH !== void 0 && (scaleX !== 1 || scaleY !== 1) ? rawNodeH * scaleY : rawNodeH;
|
|
1595
|
+
target.nodes.push({
|
|
1596
|
+
...nodeOrig,
|
|
1597
|
+
id: prefix(nodeOrig.id),
|
|
1598
|
+
lane: mappedLane,
|
|
1599
|
+
title: rewriteTemplate(nodeOrig.title) ?? nodeOrig.title,
|
|
1600
|
+
subtitle: rewriteTemplate(nodeOrig.subtitle),
|
|
1601
|
+
value: rewriteTemplate(nodeOrig.value),
|
|
1602
|
+
// parts stack を target 側と分離 (D2 fix、 posX/posY 明示との 2 段防御)
|
|
1603
|
+
stack: (nodeOrig.stack ?? 0) + stackShiftBase,
|
|
1604
|
+
...newShape ? { shape: newShape } : {},
|
|
1605
|
+
...nodePosX !== void 0 ? { posX: nodePosX } : {},
|
|
1606
|
+
...nodePosY !== void 0 ? { posY: nodePosY } : {},
|
|
1607
|
+
...nodeW !== void 0 ? { w: nodeW } : {},
|
|
1608
|
+
...nodeH !== void 0 ? { h: nodeH } : {}
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
for (const stateOrig of part.states) {
|
|
1612
|
+
const { initial, rejected } = resolveStateOverride(stateOrig.initial, stateOverride[stateOrig.id]);
|
|
1613
|
+
if (rejected) {
|
|
1614
|
+
onNotice?.({
|
|
1615
|
+
kind: "state-override-rejected",
|
|
1616
|
+
actor: alias,
|
|
1617
|
+
line: noticeLine,
|
|
1618
|
+
message: `"${alias}" \u306E ${stateOrig.id} \u306B\u66F8\u3044\u305F\u5024\u306F\u8272\u3068\u3057\u3066\u8AAD\u3081\u306A\u3044\u305F\u3081\u4F7F\u3044\u307E\u305B\u3093`,
|
|
1619
|
+
hint: "\u8272\u306F `#ff0000` \u306E\u3088\u3046\u306A\u8272\u756A\u53F7\u304B\u3001 `red` \u306E\u3088\u3046\u306A\u8272\u540D\u3067\u66F8\u304F"
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
target.states.push({ id: prefix(stateOrig.id), initial });
|
|
1623
|
+
}
|
|
1624
|
+
for (const edgeOrig of part.edges) {
|
|
1625
|
+
target.edges.push({
|
|
1626
|
+
...edgeOrig,
|
|
1627
|
+
id: prefix(edgeOrig.id),
|
|
1628
|
+
from: prefix(edgeOrig.from),
|
|
1629
|
+
to: prefix(edgeOrig.to)
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
if (part.readouts && part.readouts.length > 0) {
|
|
1633
|
+
if (!target.readouts) target.readouts = [];
|
|
1634
|
+
for (const readoutOrig of part.readouts) {
|
|
1635
|
+
const rewritten = deepRewriteStrings(readoutOrig, rewriteTemplate);
|
|
1636
|
+
target.readouts.push({
|
|
1637
|
+
...rewritten,
|
|
1638
|
+
id: prefix(rewritten.id)
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
const phaseOptOut = stateOverride["phase"] === false;
|
|
1643
|
+
if (phaseOptOut) {
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
if (target.phases.length === 0) {
|
|
1647
|
+
for (const phaseOrig of part.phases) {
|
|
1648
|
+
target.phases.push({
|
|
1649
|
+
...phaseOrig,
|
|
1650
|
+
id: prefix(phaseOrig.id),
|
|
1651
|
+
activate: phaseOrig.activate.map(prefix),
|
|
1652
|
+
tweens: phaseOrig.tweens.map((t) => ({ ...t, stateId: prefix(t.stateId) })),
|
|
1653
|
+
sets: phaseOrig.sets.map((s) => ({ ...s, stateId: prefix(s.stateId) }))
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
} else {
|
|
1657
|
+
const targetLen = target.phases.length;
|
|
1658
|
+
const partsLen = part.phases.length;
|
|
1659
|
+
const commonLen = Math.min(targetLen, partsLen);
|
|
1660
|
+
for (let i = 0; i < commonLen; i++) {
|
|
1661
|
+
const targetPhase = target.phases[i];
|
|
1662
|
+
const partPhase = part.phases[i];
|
|
1663
|
+
targetPhase.duration = Math.max(targetPhase.duration, partPhase.duration);
|
|
1664
|
+
targetPhase.activate = [...targetPhase.activate, ...partPhase.activate.map(prefix)];
|
|
1665
|
+
targetPhase.tweens = [...targetPhase.tweens, ...partPhase.tweens.map((t) => ({ ...t, stateId: prefix(t.stateId) }))];
|
|
1666
|
+
targetPhase.sets = [...targetPhase.sets, ...partPhase.sets.map((s) => ({ ...s, stateId: prefix(s.stateId) }))];
|
|
1667
|
+
}
|
|
1668
|
+
for (let i = commonLen; i < partsLen; i++) {
|
|
1669
|
+
const phaseOrig = part.phases[i];
|
|
1670
|
+
target.phases.push({
|
|
1671
|
+
...phaseOrig,
|
|
1672
|
+
id: prefix(phaseOrig.id),
|
|
1673
|
+
activate: phaseOrig.activate.map(prefix),
|
|
1674
|
+
tweens: phaseOrig.tweens.map((t) => ({ ...t, stateId: prefix(t.stateId) })),
|
|
1675
|
+
sets: phaseOrig.sets.map((s) => ({ ...s, stateId: prefix(s.stateId) }))
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
function deepRewriteStrings(value, rewrite, seen = /* @__PURE__ */ new WeakSet()) {
|
|
1681
|
+
if (typeof value === "string") return rewrite(value) ?? value;
|
|
1682
|
+
if (value === null || typeof value !== "object") return value;
|
|
1683
|
+
if (seen.has(value)) return value;
|
|
1684
|
+
seen.add(value);
|
|
1685
|
+
if (Array.isArray(value)) {
|
|
1686
|
+
return value.map((v) => deepRewriteStrings(v, rewrite, seen));
|
|
1687
|
+
}
|
|
1688
|
+
const out = {};
|
|
1689
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1690
|
+
out[k] = deepRewriteStrings(v, rewrite, seen);
|
|
1691
|
+
}
|
|
1692
|
+
return out;
|
|
1693
|
+
}
|
|
1694
|
+
function applyEdgeInlineOptions(diagram2, doc, sourceLines) {
|
|
1695
|
+
const used = /* @__PURE__ */ new Set();
|
|
1696
|
+
const isSeqLike = doc.type === "sequence" || doc.type === "solidity";
|
|
1697
|
+
doc.flow.forEach((s, stepIdx) => {
|
|
1698
|
+
const fromId = slugify(s.from);
|
|
1699
|
+
const toId = slugify(s.to);
|
|
1700
|
+
const target = diagram2.edges.find((e) => {
|
|
1701
|
+
if (used.has(e.id)) return false;
|
|
1702
|
+
if (isSeqLike) {
|
|
1703
|
+
return (e.from === `s${stepIdx}-${fromId}` || e.from === fromId) && (e.to === `s${stepIdx}-${toId}` || e.from === e.to);
|
|
1704
|
+
}
|
|
1705
|
+
return e.from === fromId && e.to === toId;
|
|
1706
|
+
});
|
|
1707
|
+
if (!target) return;
|
|
1708
|
+
used.add(target.id);
|
|
1709
|
+
sourceLines?.set(target.id, s.pos.line);
|
|
1710
|
+
if (s.guard !== void 0) {
|
|
1711
|
+
target.guard = s.guard;
|
|
1712
|
+
if (doc.type === "state" && target.sub === void 0) target.sub = s.guard;
|
|
1713
|
+
}
|
|
1714
|
+
if (s.cardinality !== void 0) {
|
|
1715
|
+
target.cardinality = s.cardinality;
|
|
1716
|
+
if (doc.type === "er" && !target.label.includes(s.cardinality)) {
|
|
1717
|
+
target.label = target.label ? `${target.label} (${s.cardinality})` : `(${s.cardinality})`;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
if (s.labelOffsetX !== void 0) target.labelOffsetX = s.labelOffsetX;
|
|
1721
|
+
if (s.labelOffsetY !== void 0) target.labelOffsetY = s.labelOffsetY;
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
var DRAWABLE_KINDS = new Set(NODE_KINDS);
|
|
1725
|
+
var KIND_ALIAS = {
|
|
1726
|
+
eoa: "shape-wallet",
|
|
1727
|
+
wallet: "shape-wallet",
|
|
1728
|
+
multisig: "signer",
|
|
1729
|
+
contract: "shape-smart-contract",
|
|
1730
|
+
proxy: "shape-smart-contract",
|
|
1731
|
+
library: "shape-code-block",
|
|
1732
|
+
interface: "shape-code-block"
|
|
1733
|
+
};
|
|
1734
|
+
var SINGLE_BOX_KINDS = /* @__PURE__ */ new Set([
|
|
1735
|
+
"chart-pie",
|
|
1736
|
+
"chart-line",
|
|
1737
|
+
"chart-bar",
|
|
1738
|
+
"gantt-timeline",
|
|
1739
|
+
"mind-map",
|
|
1740
|
+
"mind-radial",
|
|
1741
|
+
"funnel-stages",
|
|
1742
|
+
"quadrant-matrix",
|
|
1743
|
+
"tree-hierarchy",
|
|
1744
|
+
"journey-map"
|
|
1745
|
+
]);
|
|
1746
|
+
function drawableKind(kind) {
|
|
1747
|
+
if (kind === void 0) return void 0;
|
|
1748
|
+
const mapped = KIND_ALIAS[kind] ?? kind;
|
|
1749
|
+
return DRAWABLE_KINDS.has(mapped) ? mapped : void 0;
|
|
1750
|
+
}
|
|
1751
|
+
function alignSeqHeaderHeights(diagram2, doc) {
|
|
1752
|
+
if (doc.type !== "sequence" && doc.type !== "solidity") return;
|
|
1753
|
+
const isEnd = (n) => n.id === `${n.lane}-header` || n.id === `${n.lane}-footer`;
|
|
1754
|
+
const ends = diagram2.nodes.filter(isEnd);
|
|
1755
|
+
if (ends.length === 0) return;
|
|
1756
|
+
const auto = ends.filter((n) => n.posH === void 0);
|
|
1757
|
+
if (auto.length === 0) return;
|
|
1758
|
+
const tallest = Math.max(...auto.map((n) => n.h ?? 0));
|
|
1759
|
+
if (tallest <= 0) return;
|
|
1760
|
+
for (const n of auto) n.h = tallest;
|
|
1761
|
+
}
|
|
1762
|
+
var LABEL_MIN_H = {
|
|
1763
|
+
// `shape-` のうち、 高さを上げれば下のはみ出しが消える 4 種 (#1067)。 値は「収まる最小の高さ」
|
|
1764
|
+
// で、 1 手前 (値 - 1) では 0.9-1 はみ出すことを実測した
|
|
1765
|
+
"shape-person": 228,
|
|
1766
|
+
// 名札 72 で下へ 155.9
|
|
1767
|
+
"shape-server-rack": 166,
|
|
1768
|
+
// 94
|
|
1769
|
+
"shape-website": 98,
|
|
1770
|
+
// 26
|
|
1771
|
+
"shape-warehouse": 79
|
|
1772
|
+
// 7
|
|
1773
|
+
};
|
|
1774
|
+
var LABEL_NEVER_FITS = /* @__PURE__ */ new Set([
|
|
1775
|
+
// 左右にはみ出す 24 種。 横幅は高さで変わらないため直らない
|
|
1776
|
+
"shape-api-gateway",
|
|
1777
|
+
"shape-atm",
|
|
1778
|
+
"shape-auditor",
|
|
1779
|
+
"shape-bank",
|
|
1780
|
+
"shape-bitcoin-chain",
|
|
1781
|
+
"shape-blockchain",
|
|
1782
|
+
"shape-blockchain-block",
|
|
1783
|
+
"shape-blockchain-node",
|
|
1784
|
+
"shape-brokerage",
|
|
1785
|
+
"shape-code-block",
|
|
1786
|
+
"shape-customer-service",
|
|
1787
|
+
"shape-ethereum-chain",
|
|
1788
|
+
"shape-hexagon",
|
|
1789
|
+
"shape-kanban-card",
|
|
1790
|
+
"shape-lawyer",
|
|
1791
|
+
"shape-network-node",
|
|
1792
|
+
"shape-nft",
|
|
1793
|
+
"shape-notary",
|
|
1794
|
+
"shape-regulator",
|
|
1795
|
+
"shape-satellite",
|
|
1796
|
+
"shape-smart-contract",
|
|
1797
|
+
"shape-terminal",
|
|
1798
|
+
"shape-trader",
|
|
1799
|
+
"shape-trust-bank",
|
|
1800
|
+
// 下のはみ出しが高さに依らない 6 種
|
|
1801
|
+
"shape-cylinder",
|
|
1802
|
+
"shape-diamond",
|
|
1803
|
+
"shape-file",
|
|
1804
|
+
"shape-folder",
|
|
1805
|
+
"shape-mobile-device",
|
|
1806
|
+
"shape-stack",
|
|
1807
|
+
// 上へ出る絵が名札の大きさでは読めない。 `#1067` では「上は何ともぶつからない」 として残したが、
|
|
1808
|
+
// 実際には絵が小さく潰れて名前と重なり、 横に並べた時も 1 本だけ頭が浮く (user 実機確認)
|
|
1809
|
+
"shape-wallet"
|
|
1810
|
+
]);
|
|
1811
|
+
function hasAuthoredText(n) {
|
|
1812
|
+
if (n.subtitle !== void 0 || n.eyebrow !== void 0 || n.value !== void 0) return true;
|
|
1813
|
+
return rendersRows(n.kind) && (n.rows?.length ?? 0) > 0;
|
|
1814
|
+
}
|
|
1815
|
+
function dropUnfittableEndKinds(diagram2, doc) {
|
|
1816
|
+
if (doc.type !== "sequence" && doc.type !== "solidity") return;
|
|
1817
|
+
const ends = /* @__PURE__ */ new Map();
|
|
1818
|
+
for (const n of diagram2.nodes) {
|
|
1819
|
+
if (n.id !== `${n.lane}-header` && n.id !== `${n.lane}-footer`) continue;
|
|
1820
|
+
const pair = ends.get(n.lane);
|
|
1821
|
+
if (pair === void 0) ends.set(n.lane, [n]);
|
|
1822
|
+
else pair.push(n);
|
|
1823
|
+
}
|
|
1824
|
+
for (const pair of ends.values()) {
|
|
1825
|
+
if (pair.some(hasAuthoredText)) continue;
|
|
1826
|
+
const \u53CE\u307E\u3089\u306A\u3044 = pair.some((n) => {
|
|
1827
|
+
if (LABEL_NEVER_FITS.has(n.kind)) return true;
|
|
1828
|
+
const need = LABEL_MIN_H[n.kind];
|
|
1829
|
+
if (need === void 0) return false;
|
|
1830
|
+
const h = (n.posX !== void 0 && n.posY !== void 0 ? n.posH : void 0) ?? n.h;
|
|
1831
|
+
return h !== void 0 && h < need;
|
|
1832
|
+
});
|
|
1833
|
+
if (!\u53CE\u307E\u3089\u306A\u3044) continue;
|
|
1834
|
+
for (const n of pair) {
|
|
1835
|
+
if (LABEL_NEVER_FITS.has(n.kind) || LABEL_MIN_H[n.kind] !== void 0) n.kind = "card";
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
function fillFlowEdgeSources(diagram2, doc, sourceLines) {
|
|
1840
|
+
if (doc.type !== "flow") return;
|
|
1841
|
+
if (doc.animate && doc.animate.phases.length > 0) return;
|
|
1842
|
+
diagram2.edges.forEach((e, idx) => {
|
|
1843
|
+
const to = doc.actors[idx + 1];
|
|
1844
|
+
if (to === void 0) return;
|
|
1845
|
+
const step = doc.flow.find((s) => s.to === to.name);
|
|
1846
|
+
if (step === void 0) return;
|
|
1847
|
+
sourceLines.set(e.id, step.pos.line);
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1850
|
+
function applyGroupContainers(diagram2, doc) {
|
|
1851
|
+
if (!doc.groups || Object.keys(doc.groups).length === 0) return;
|
|
1852
|
+
for (const [id, g] of Object.entries(doc.groups)) {
|
|
1853
|
+
const containerId = `group-${id}`;
|
|
1854
|
+
if (diagram2.lanes.some((l) => l.id === containerId)) continue;
|
|
1855
|
+
diagram2.lanes.push({
|
|
1856
|
+
id: containerId,
|
|
1857
|
+
width: 800,
|
|
1858
|
+
label: g.label ?? id,
|
|
1859
|
+
contain: true,
|
|
1860
|
+
// 束ねる lane 群に重ねて描く枠。 横に並べる lane ではないので、 engine の間隔調整
|
|
1861
|
+
// (lane を詰めた分を幅で埋め合わせる処理) の対象から外す。
|
|
1862
|
+
role: "overlay"
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
function compileSolidity(doc) {
|
|
1867
|
+
const kindOrder = {
|
|
1868
|
+
eoa: 0,
|
|
1869
|
+
actor: 0,
|
|
1870
|
+
multisig: 0,
|
|
1871
|
+
signer: 0,
|
|
1872
|
+
wallet: 0,
|
|
1873
|
+
contract: 1,
|
|
1874
|
+
proxy: 1,
|
|
1875
|
+
library: 1,
|
|
1876
|
+
interface: 1,
|
|
1877
|
+
storage: 2,
|
|
1878
|
+
event: 3
|
|
1879
|
+
};
|
|
1880
|
+
const sorted = [...doc.actors].sort(
|
|
1881
|
+
(a, b) => (kindOrder[a.kind] ?? 5) - (kindOrder[b.kind] ?? 5)
|
|
1882
|
+
);
|
|
1883
|
+
const sortedDoc = { ...doc, actors: sorted };
|
|
1884
|
+
return compileSequence(sortedDoc);
|
|
1885
|
+
}
|
|
1886
|
+
function compileGantt(doc) {
|
|
1887
|
+
const b = diagram(slugify(doc.title), { topic: doc.title });
|
|
1888
|
+
const CHART_W = 720;
|
|
1889
|
+
b.lane("gantt", { width: CHART_W, label: doc.title });
|
|
1890
|
+
const \u76EE\u76DB\u308A = [];
|
|
1891
|
+
const \u76EE\u76DB\u308A\u306A\u3057 = [];
|
|
1892
|
+
const \u30BF\u30B9\u30AF = [];
|
|
1893
|
+
for (const a of doc.actors) {
|
|
1894
|
+
const label = (a.value ?? a.subtitle ?? "").trim();
|
|
1895
|
+
if (label === "") {
|
|
1896
|
+
\u76EE\u76DB\u308A\u306A\u3057.push(a.name);
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1899
|
+
if (!\u76EE\u76DB\u308A.includes(label)) \u76EE\u76DB\u308A.push(label);
|
|
1900
|
+
\u30BF\u30B9\u30AF.push({ name: a.name, label, ...a.tone !== void 0 ? { tone: a.tone } : {} });
|
|
1901
|
+
}
|
|
1902
|
+
if (\u76EE\u76DB\u308A\u306A\u3057.length > 0 && typeof console !== "undefined" && console.warn) {
|
|
1903
|
+
console.warn(
|
|
1904
|
+
`[dragon] type: gantt \u3067\u6642\u671F\u3092\u8AAD\u3081\u306A\u3044\u9805\u76EE\u304C\u3042\u308A\u307E\u3059 (\u5E2F\u306B\u8F09\u305B\u307E\u305B\u3093): ${\u76EE\u76DB\u308A\u306A\u3057.join(", ")}\u3002 \`- \u8A2D\u8A08: "Q1"\` \u306E\u5F62\u3067\u66F8\u3044\u3066\u304F\u3060\u3055\u3044`
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
const \u30BF\u30B9\u30AF\u540D = new Set(\u30BF\u30B9\u30AF.map((t) => t.name));
|
|
1908
|
+
const \u4F9D\u5B58\u5143 = /* @__PURE__ */ new Map();
|
|
1909
|
+
const \u5C45\u306A\u3044 = [];
|
|
1910
|
+
const \u88C5\u98FE\u3064\u304D = [];
|
|
1911
|
+
for (const s of doc.flow) {
|
|
1912
|
+
if (!\u30BF\u30B9\u30AF\u540D.has(s.from) || !\u30BF\u30B9\u30AF\u540D.has(s.to)) {
|
|
1913
|
+
\u5C45\u306A\u3044.push(`${s.from} -> ${s.to}`);
|
|
1914
|
+
continue;
|
|
1915
|
+
}
|
|
1916
|
+
\u4F9D\u5B58\u5143.set(s.to, s.from);
|
|
1917
|
+
if ((s.label ?? "") !== "" || (s.sub ?? "") !== "" || s.tone !== void 0 || s.style !== void 0) {
|
|
1918
|
+
\u88C5\u98FE\u3064\u304D.push(`${s.from} -> ${s.to}`);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
if (\u5C45\u306A\u3044.length > 0 && typeof console !== "undefined" && console.warn) {
|
|
1922
|
+
console.warn(
|
|
1923
|
+
`[dragon] type: gantt \u3067\u4F9D\u5B58\u3092\u7D50\u3079\u306A\u3044\u77E2\u5370\u304C\u3042\u308A\u307E\u3059 (\u5C45\u306A\u3044\u9805\u76EE\u304B\u6642\u671F\u306A\u3057): ${\u5C45\u306A\u3044.join(", ")}`
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
if (\u88C5\u98FE\u3064\u304D.length > 0 && typeof console !== "undefined" && console.warn) {
|
|
1927
|
+
console.warn(
|
|
1928
|
+
`[dragon] type: gantt \u306E\u77E2\u5370\u306F\u524D\u5F8C\u306E\u95A2\u4FC2\u3060\u3051\u3092\u4F7F\u3044\u307E\u3059 (\u6587\u5B57 / \u8272 / \u7DDA\u7A2E\u306F\u63CF\u3051\u307E\u305B\u3093): ${\u88C5\u98FE\u3064\u304D.join(", ")}`
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
const CHART_H = Math.max(360, 48 * \u30BF\u30B9\u30AF.length + 96);
|
|
1932
|
+
b.node(`${slugify(doc.title)}-chart`, {
|
|
1933
|
+
lane: "gantt",
|
|
1934
|
+
stack: 0,
|
|
1935
|
+
kind: "gantt-timeline",
|
|
1936
|
+
title: doc.title,
|
|
1937
|
+
w: CHART_W,
|
|
1938
|
+
h: CHART_H,
|
|
1939
|
+
ganttData: \u30BF\u30B9\u30AF.map((t) => {
|
|
1940
|
+
const idx = \u76EE\u76DB\u308A.indexOf(t.label);
|
|
1941
|
+
const from = \u4F9D\u5B58\u5143.get(t.name);
|
|
1942
|
+
return {
|
|
1943
|
+
id: slugify(t.name),
|
|
1944
|
+
title: t.name,
|
|
1945
|
+
startIdx: idx,
|
|
1946
|
+
endIdx: idx,
|
|
1947
|
+
startLabel: t.label,
|
|
1948
|
+
endLabel: t.label,
|
|
1949
|
+
...from !== void 0 ? { dependsOn: slugify(from) } : {},
|
|
1950
|
+
...t.tone !== void 0 ? { tone: t.tone } : {}
|
|
1951
|
+
};
|
|
1952
|
+
})
|
|
1953
|
+
});
|
|
1954
|
+
return b.build();
|
|
1955
|
+
}
|
|
1956
|
+
function compileClass(doc) {
|
|
1957
|
+
const b = diagram(slugify(doc.title), { topic: doc.title });
|
|
1958
|
+
const CLASS_W = 400;
|
|
1959
|
+
if (doc.actors.length === 0) return b.build();
|
|
1960
|
+
b.lane("class-stack", { width: CLASS_W, label: doc.title });
|
|
1961
|
+
doc.actors.forEach((a, idx) => {
|
|
1962
|
+
const nodeId = slugify(a.name);
|
|
1963
|
+
b.node(nodeId, {
|
|
1964
|
+
lane: "class-stack",
|
|
1965
|
+
stack: idx,
|
|
1966
|
+
kind: "storage",
|
|
1967
|
+
title: a.name,
|
|
1968
|
+
w: CLASS_W
|
|
1969
|
+
});
|
|
1970
|
+
});
|
|
1971
|
+
for (const s of doc.flow) {
|
|
1972
|
+
const fromId = slugify(s.from);
|
|
1973
|
+
const toId = slugify(s.to);
|
|
1974
|
+
b.edge(fromId, toId, {
|
|
1975
|
+
label: s.label,
|
|
1976
|
+
...s.sub ? { sub: s.sub } : {},
|
|
1977
|
+
...s.tone ? { tone: s.tone } : {},
|
|
1978
|
+
...s.style ? { style: s.style } : {}
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
return b.build();
|
|
1982
|
+
}
|
|
1983
|
+
function parseShareValue(raw) {
|
|
1984
|
+
if (raw === void 0) return null;
|
|
1985
|
+
const m = raw.trim().match(/^(\d+(?:\.\d+)?)\s*%?$/);
|
|
1986
|
+
if (m === null) return null;
|
|
1987
|
+
const v = Number(m[1]);
|
|
1988
|
+
return Number.isFinite(v) ? v : null;
|
|
1989
|
+
}
|
|
1990
|
+
function compilePie(doc) {
|
|
1991
|
+
const b = diagram(slugify(doc.title), { topic: doc.title });
|
|
1992
|
+
const CHART_W = 640;
|
|
1993
|
+
const CHART_H = 320;
|
|
1994
|
+
b.lane("chart", { width: CHART_W + 64, label: doc.title });
|
|
1995
|
+
const data = [];
|
|
1996
|
+
const \u8AAD\u3081\u306A\u3044 = [];
|
|
1997
|
+
for (const a of doc.actors) {
|
|
1998
|
+
const value = parseShareValue(a.value ?? a.subtitle);
|
|
1999
|
+
if (value === null) {
|
|
2000
|
+
\u8AAD\u3081\u306A\u3044.push(a.name);
|
|
2001
|
+
continue;
|
|
2002
|
+
}
|
|
2003
|
+
data.push({ label: a.name, value, ...a.tone !== void 0 ? { tone: a.tone } : {} });
|
|
2004
|
+
}
|
|
2005
|
+
if (\u8AAD\u3081\u306A\u3044.length > 0 && typeof console !== "undefined" && console.warn) {
|
|
2006
|
+
console.warn(
|
|
2007
|
+
`[dragon] type: pie \u3067\u5272\u5408\u3092\u8AAD\u3081\u306A\u3044\u9805\u76EE\u304C\u3042\u308A\u307E\u3059 (\u5186\u306B\u8F09\u305B\u307E\u305B\u3093): ${\u8AAD\u3081\u306A\u3044.join(", ")}\u3002 \`- \u540D\u524D: "45%"\` \u306E\u5F62\u3067\u66F8\u3044\u3066\u304F\u3060\u3055\u3044`
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
if (doc.flow.length > 0 && typeof console !== "undefined" && console.warn) {
|
|
2011
|
+
console.warn(
|
|
2012
|
+
`[dragon] type: pie \u3067\u306F\u77E2\u5370\u3092\u63CF\u3051\u307E\u305B\u3093 (${doc.flow.length} \u672C\u3092\u7121\u8996\u3057\u307E\u3057\u305F)\u3002 \u95A2\u4FC2\u3092\u63CF\u304F\u306A\u3089 type: flow \u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044`
|
|
2013
|
+
);
|
|
2014
|
+
}
|
|
2015
|
+
b.node(`${slugify(doc.title)}-chart`, {
|
|
2016
|
+
lane: "chart",
|
|
2017
|
+
stack: 0,
|
|
2018
|
+
kind: "chart-pie",
|
|
2019
|
+
title: doc.title,
|
|
2020
|
+
w: CHART_W,
|
|
2021
|
+
h: CHART_H,
|
|
2022
|
+
chartData: data
|
|
2023
|
+
});
|
|
2024
|
+
return b.build();
|
|
2025
|
+
}
|
|
2026
|
+
function \u6BB5\u3092\u8AAD\u307F\u53D6\u308B(subtitle) {
|
|
2027
|
+
const \u5143 = (subtitle ?? "").trim();
|
|
2028
|
+
const m = \u5143.match(/^L([123])(?![0-9A-Za-z])/i);
|
|
2029
|
+
if (m === null) return { \u6BB5: 1, \u8AAC\u660E: subtitle };
|
|
2030
|
+
const \u6B8B\u308A = \u5143.slice(m[0].length).replace(/^[::\s]+/, "").trim();
|
|
2031
|
+
return { \u6BB5: Number(m[1]), \u8AAC\u660E: \u6B8B\u308A === "" ? void 0 : \u6B8B\u308A };
|
|
2032
|
+
}
|
|
2033
|
+
function compileC4(doc) {
|
|
2034
|
+
const b = diagram(slugify(doc.title), { topic: doc.title });
|
|
2035
|
+
const LANE_W = 400;
|
|
2036
|
+
const LANE_GAP = 80;
|
|
2037
|
+
const \u6BB5\u306E\u540D\u524D = { 1: "System Context", 2: "Container", 3: "Component" };
|
|
2038
|
+
const \u5272\u5F53 = doc.actors.map((a, idx) => {
|
|
2039
|
+
const { \u6BB5, \u8AAC\u660E } = \u6BB5\u3092\u8AAD\u307F\u53D6\u308B(a.subtitle);
|
|
2040
|
+
return { \u6BB5, \u8AAC\u660E, id: slugify(a.name), actor: a };
|
|
2041
|
+
});
|
|
2042
|
+
const \u4F7F\u3046\u6BB5 = [1, 2, 3].filter((lv) => \u5272\u5F53.some((x) => x.\u6BB5 === lv));
|
|
2043
|
+
\u4F7F\u3046\u6BB5.forEach((lv, i) => {
|
|
2044
|
+
b.lane(`c4-l${lv}`, {
|
|
2045
|
+
// 空の段を飛ばした分だけ左に詰める。 飛ばした位置に隙間を残すと、 やはり
|
|
2046
|
+
// 「何かが抜けている」 ように見える
|
|
2047
|
+
x: i * (LANE_W + LANE_GAP),
|
|
2048
|
+
width: LANE_W,
|
|
2049
|
+
label: \u6BB5\u306E\u540D\u524D[lv],
|
|
2050
|
+
contain: true
|
|
2051
|
+
});
|
|
2052
|
+
});
|
|
2053
|
+
const stackPerLane = { "c4-l1": 0, "c4-l2": 0, "c4-l3": 0 };
|
|
2054
|
+
for (const x of \u5272\u5F53) {
|
|
2055
|
+
const lid = `c4-l${x.\u6BB5}`;
|
|
2056
|
+
const stack = stackPerLane[lid];
|
|
2057
|
+
stackPerLane[lid] = stack + 1;
|
|
2058
|
+
b.node(x.id, {
|
|
2059
|
+
lane: lid,
|
|
2060
|
+
stack,
|
|
2061
|
+
kind: x.actor.kind,
|
|
2062
|
+
title: x.actor.name
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
for (const s of doc.flow) {
|
|
2066
|
+
const fromId = slugify(s.from);
|
|
2067
|
+
const toId = slugify(s.to);
|
|
2068
|
+
b.edge(fromId, toId, {
|
|
2069
|
+
label: s.label,
|
|
2070
|
+
...s.sub ? { sub: s.sub } : {},
|
|
2071
|
+
...s.tone ? { tone: s.tone } : {},
|
|
2072
|
+
...s.style ? { style: s.style } : {}
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
return b.build();
|
|
2076
|
+
}
|
|
2077
|
+
function compileMind(doc) {
|
|
2078
|
+
const b = diagram(slugify(doc.title), { topic: doc.title });
|
|
2079
|
+
const LEAF_W = 280;
|
|
2080
|
+
const ROOT_W = 320;
|
|
2081
|
+
const GAP = 80;
|
|
2082
|
+
const \u679D\u306E\u6570 = Math.max(0, doc.actors.length - 1);
|
|
2083
|
+
const \u5DE6\u306B\u7F6E\u304F\u6570 = Math.ceil(\u679D\u306E\u6570 / 2);
|
|
2084
|
+
const \u53F3\u306B\u7F6E\u304F\u6570 = \u679D\u306E\u6570 - \u5DE6\u306B\u7F6E\u304F\u6570;
|
|
2085
|
+
const \u5DE6\u3092\u4F7F\u3046 = \u5DE6\u306B\u7F6E\u304F\u6570 > 0;
|
|
2086
|
+
const \u53F3\u3092\u4F7F\u3046 = \u53F3\u306B\u7F6E\u304F\u6570 > 0;
|
|
2087
|
+
if (doc.actors.length === 0) return b.build();
|
|
2088
|
+
let x = 0;
|
|
2089
|
+
if (\u5DE6\u3092\u4F7F\u3046) {
|
|
2090
|
+
b.lane("mind-left", { x, width: LEAF_W, label: "" });
|
|
2091
|
+
x += LEAF_W + GAP;
|
|
2092
|
+
}
|
|
2093
|
+
b.lane("mind-center", { x, width: ROOT_W, label: doc.title });
|
|
2094
|
+
x += ROOT_W + GAP;
|
|
2095
|
+
if (\u53F3\u3092\u4F7F\u3046) {
|
|
2096
|
+
b.lane("mind-right", { x, width: LEAF_W, label: "" });
|
|
2097
|
+
}
|
|
2098
|
+
const root = doc.actors[0];
|
|
2099
|
+
const rootId = slugify(root.name);
|
|
2100
|
+
const leafCount = doc.actors.length - 1;
|
|
2101
|
+
const rootStack = Math.floor(leafCount / 2);
|
|
2102
|
+
b.node(rootId, {
|
|
2103
|
+
lane: "mind-center",
|
|
2104
|
+
stack: rootStack,
|
|
2105
|
+
kind: "card",
|
|
2106
|
+
title: root.name,
|
|
2107
|
+
w: ROOT_W
|
|
2108
|
+
});
|
|
2109
|
+
const stackLeft = { v: 0 };
|
|
2110
|
+
const stackRight = { v: 0 };
|
|
2111
|
+
doc.actors.slice(1).forEach((a, i) => {
|
|
2112
|
+
const isLeft = i % 2 === 0;
|
|
2113
|
+
const lid = isLeft ? "mind-left" : "mind-right";
|
|
2114
|
+
const counter = isLeft ? stackLeft : stackRight;
|
|
2115
|
+
const nodeId = slugify(a.name);
|
|
2116
|
+
b.node(nodeId, {
|
|
2117
|
+
lane: lid,
|
|
2118
|
+
stack: counter.v,
|
|
2119
|
+
kind: "card",
|
|
2120
|
+
title: a.name,
|
|
2121
|
+
w: LEAF_W
|
|
2122
|
+
});
|
|
2123
|
+
counter.v += 1;
|
|
2124
|
+
});
|
|
2125
|
+
if (doc.flow.length === 0 && doc.actors.length > 1) {
|
|
2126
|
+
doc.actors.slice(1).forEach((a) => {
|
|
2127
|
+
const leafId = slugify(a.name);
|
|
2128
|
+
b.edge(rootId, leafId, { label: "" });
|
|
2129
|
+
});
|
|
2130
|
+
} else {
|
|
2131
|
+
for (const s of doc.flow) {
|
|
2132
|
+
const fromId = slugify(s.from);
|
|
2133
|
+
const toId = slugify(s.to);
|
|
2134
|
+
b.edge(fromId, toId, {
|
|
2135
|
+
label: s.label,
|
|
2136
|
+
...s.sub ? { sub: s.sub } : {},
|
|
2137
|
+
...s.tone ? { tone: s.tone } : {},
|
|
2138
|
+
...s.style ? { style: s.style } : {}
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
return b.build();
|
|
2143
|
+
}
|
|
2144
|
+
function applyV05Extensions(diagram2, doc) {
|
|
2145
|
+
const isSeqLike = doc.type === "sequence" || doc.type === "solidity";
|
|
2146
|
+
for (const a of doc.actors) {
|
|
2147
|
+
const dragonSlug = slugify(a.name);
|
|
2148
|
+
let primaryNodes;
|
|
2149
|
+
if (isSeqLike) {
|
|
2150
|
+
const ownedLaneIds = new Set(
|
|
2151
|
+
diagram2.lanes.filter((l) => l.label === a.name).map((l) => l.id)
|
|
2152
|
+
);
|
|
2153
|
+
primaryNodes = ownedLaneIds.size > 0 ? diagram2.nodes.filter((n) => ownedLaneIds.has(n.lane) && n.id === `${n.lane}-header`) : diagram2.nodes.filter((n) => n.id === `${dragonSlug}-header`);
|
|
2154
|
+
} else {
|
|
2155
|
+
primaryNodes = diagram2.nodes.filter((n) => n.id === dragonSlug);
|
|
2156
|
+
}
|
|
2157
|
+
for (const node of primaryNodes) {
|
|
2158
|
+
const \u8AAC\u660E = doc.type === "c4" ? \u6BB5\u3092\u8AAD\u307F\u53D6\u308B(a.subtitle).\u8AAC\u660E : a.subtitle;
|
|
2159
|
+
if (\u8AAC\u660E !== void 0) node.subtitle = \u8AAC\u660E;
|
|
2160
|
+
if (a.eyebrow !== void 0) node.eyebrow = a.eyebrow;
|
|
2161
|
+
if (a.value !== void 0) node.value = a.value;
|
|
2162
|
+
if (a.rows !== void 0) node.rows = a.rows;
|
|
2163
|
+
const drawn = isSeqLike && a.kindWritten !== false ? drawableKind(a.kind) : void 0;
|
|
2164
|
+
if (drawn !== void 0) {
|
|
2165
|
+
node.kind = drawn;
|
|
2166
|
+
const footer = diagram2.nodes.find((n) => n.id === `${node.lane}-footer`);
|
|
2167
|
+
if (footer) footer.kind = drawn;
|
|
2168
|
+
}
|
|
2169
|
+
if (isSeqLike && a.rows !== void 0 && a.rows.length > 0 && rendersRows(a.kind)) {
|
|
2170
|
+
node.h = Math.max(node.h ?? 0, requiredRowsHeight(a.kind, a.rows.length) ?? 0);
|
|
2171
|
+
node.w = Math.max(node.w ?? 0, requiredRowsWidth(a.rows, a.kind));
|
|
2172
|
+
}
|
|
2173
|
+
if (isSeqLike) {
|
|
2174
|
+
if (a.posW !== void 0) node.w = a.posW;
|
|
2175
|
+
if (a.posH !== void 0) node.h = a.posH;
|
|
2176
|
+
const footer = diagram2.nodes.find((n) => n.id === `${node.lane}-footer`);
|
|
2177
|
+
if (footer && a.posW !== void 0) footer.w = a.posW;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
alignSeqHeaderHeights(diagram2, doc);
|
|
2182
|
+
dropUnfittableEndKinds(diagram2, doc);
|
|
2183
|
+
if (doc.animate && doc.animate.phases.length > 0 && diagram2.phases.length === 0) {
|
|
2184
|
+
injectPhasesFallback(diagram2, doc);
|
|
2185
|
+
}
|
|
2186
|
+
if (doc.lanes) {
|
|
2187
|
+
for (const [id, laneOpt] of Object.entries(doc.lanes)) {
|
|
2188
|
+
const lane = diagram2.lanes.find((l) => l.id === id);
|
|
2189
|
+
if (lane) {
|
|
2190
|
+
if (laneOpt.x !== void 0) lane.x = laneOpt.x;
|
|
2191
|
+
if (laneOpt.width !== void 0) lane.width = laneOpt.width;
|
|
2192
|
+
if (laneOpt.label !== void 0) lane.label = laneOpt.label;
|
|
2193
|
+
if (laneOpt.contain !== void 0) lane.contain = laneOpt.contain;
|
|
2194
|
+
if (laneOpt.lifeline !== void 0) lane.lifeline = laneOpt.lifeline;
|
|
2195
|
+
} else {
|
|
2196
|
+
diagram2.lanes.push({
|
|
2197
|
+
id,
|
|
2198
|
+
x: laneOpt.x ?? 0,
|
|
2199
|
+
width: laneOpt.width ?? 320,
|
|
2200
|
+
label: laneOpt.label,
|
|
2201
|
+
contain: laneOpt.contain,
|
|
2202
|
+
lifeline: laneOpt.lifeline
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
if (doc.viewport?.laneWidth !== void 0) {
|
|
2208
|
+
for (const lane of diagram2.lanes) {
|
|
2209
|
+
lane.width = doc.viewport.laneWidth;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
if (doc.viewport) {
|
|
2213
|
+
diagram2.viewport = {
|
|
2214
|
+
...diagram2.viewport ?? {},
|
|
2215
|
+
...doc.viewport.width !== void 0 ? { width: doc.viewport.width } : {},
|
|
2216
|
+
...doc.viewport.height !== void 0 ? { height: doc.viewport.height } : {},
|
|
2217
|
+
...doc.viewport.gap !== void 0 ? { gap: doc.viewport.gap } : {},
|
|
2218
|
+
...doc.viewport.laneGap !== void 0 ? { laneGap: doc.viewport.laneGap } : {},
|
|
2219
|
+
...doc.viewport.nodeGap !== void 0 ? { nodeGap: doc.viewport.nodeGap } : {},
|
|
2220
|
+
...doc.viewport.scale !== void 0 ? { scale: doc.viewport.scale } : {},
|
|
2221
|
+
...doc.viewport.labelMargin !== void 0 ? { labelMargin: doc.viewport.labelMargin } : {}
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
return diagram2;
|
|
2225
|
+
}
|
|
2226
|
+
function injectPhasesFallback(diagram2, doc) {
|
|
2227
|
+
if (!doc.animate) return;
|
|
2228
|
+
const existingStateIds = new Set(diagram2.states.map((s) => s.id));
|
|
2229
|
+
for (const st of doc.animate.states) {
|
|
2230
|
+
if (!existingStateIds.has(st.name)) {
|
|
2231
|
+
diagram2.states.push({ id: st.name, initial: st.initial });
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
const knownNames = new Set(doc.actors.map((a) => a.name));
|
|
2235
|
+
const singleBoxNodes = diagram2.nodes.filter((n) => SINGLE_BOX_KINDS.has(String(n.kind)));
|
|
2236
|
+
const singleBoxNode = singleBoxNodes.length === 1 ? singleBoxNodes[0] : void 0;
|
|
2237
|
+
const resolveIds = (highlight) => {
|
|
2238
|
+
const out = [];
|
|
2239
|
+
for (const h of highlight) {
|
|
2240
|
+
const entry = parseFocusEntry(h, knownNames);
|
|
2241
|
+
if (entry.kind === "edge") {
|
|
2242
|
+
const fromSlug = slugify(entry.from);
|
|
2243
|
+
const toSlug = slugify(entry.to);
|
|
2244
|
+
let \u898B\u3064\u304B\u3063\u305F = false;
|
|
2245
|
+
for (const e of diagram2.edges) {
|
|
2246
|
+
if (e.from === fromSlug && e.to === toSlug) {
|
|
2247
|
+
out.push(e.id);
|
|
2248
|
+
\u898B\u3064\u304B\u3063\u305F = true;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
if (!\u898B\u3064\u304B\u3063\u305F && singleBoxNode !== void 0 && knownNames.has(entry.from) && knownNames.has(entry.to)) {
|
|
2252
|
+
out.push(singleBoxNode.id);
|
|
2253
|
+
}
|
|
2254
|
+
continue;
|
|
2255
|
+
}
|
|
2256
|
+
const nodeSlug = slugify(entry.name);
|
|
2257
|
+
const node = diagram2.nodes.find((n) => n.id === nodeSlug || n.id === `${nodeSlug}-header`);
|
|
2258
|
+
if (node) {
|
|
2259
|
+
out.push(node.id);
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
if (knownNames.has(entry.name) && singleBoxNode !== void 0) out.push(singleBoxNode.id);
|
|
2263
|
+
}
|
|
2264
|
+
return [...new Set(out)];
|
|
2265
|
+
};
|
|
2266
|
+
for (const p of doc.animate.phases) {
|
|
2267
|
+
const activateIds = [...resolveIds(p.highlight ?? [])];
|
|
2268
|
+
diagram2.phases.push({
|
|
2269
|
+
id: slugify(p.name),
|
|
2270
|
+
duration: p.durationMs,
|
|
2271
|
+
title: p.name,
|
|
2272
|
+
body: p.body ?? "",
|
|
2273
|
+
activate: activateIds,
|
|
2274
|
+
tweens: (p.tweens ?? []).map((t) => ({ stateId: t.state, from: t.from, to: t.to })),
|
|
2275
|
+
sets: (p.sets ?? []).map((s) => ({ stateId: s.state, value: s.value })),
|
|
2276
|
+
...p.badge ? { badge: p.badge } : {}
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
function compileSequence(doc) {
|
|
2281
|
+
if (doc.animate && doc.animate.phases.length > 0) {
|
|
2282
|
+
return compileSequenceWithAnimate(doc);
|
|
2283
|
+
}
|
|
2284
|
+
const seqBuilder = sequence({
|
|
2285
|
+
id: slugify(doc.title),
|
|
2286
|
+
topic: doc.title,
|
|
2287
|
+
actors: doc.actors.map((a) => a.name)
|
|
2288
|
+
});
|
|
2289
|
+
for (const s of doc.flow) {
|
|
2290
|
+
seqBuilder.step({
|
|
2291
|
+
from: s.from,
|
|
2292
|
+
to: s.to,
|
|
2293
|
+
label: s.label,
|
|
2294
|
+
...s.sub ? { sub: s.sub } : {},
|
|
2295
|
+
...s.tone ? { tone: s.tone } : {},
|
|
2296
|
+
...s.style ? { style: s.style } : {}
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2299
|
+
return seqBuilder.build();
|
|
2300
|
+
}
|
|
2301
|
+
function compileSequenceWithAnimate(doc) {
|
|
2302
|
+
const b = diagram(slugify(doc.title), { topic: doc.title });
|
|
2303
|
+
const laneW = 340;
|
|
2304
|
+
const actorIds = /* @__PURE__ */ new Map();
|
|
2305
|
+
doc.actors.forEach((a, i) => {
|
|
2306
|
+
const id = slugify(a.name);
|
|
2307
|
+
actorIds.set(a.name, id);
|
|
2308
|
+
actorIds.set(id, id);
|
|
2309
|
+
const laneOpts = { width: laneW, label: a.name, lifeline: true };
|
|
2310
|
+
if (a.posX !== void 0 && a.posY !== void 0) {
|
|
2311
|
+
laneOpts.posX = a.posX;
|
|
2312
|
+
laneOpts.posY = a.posY;
|
|
2313
|
+
if (a.posW !== void 0) laneOpts.posW = a.posW;
|
|
2314
|
+
if (a.posH !== void 0) laneOpts.posH = a.posH;
|
|
2315
|
+
}
|
|
2316
|
+
b.lane(id, laneOpts);
|
|
2317
|
+
const headerId = `${id}-header`;
|
|
2318
|
+
const actorW = Math.max(140, a.name.length * 22 + 52);
|
|
2319
|
+
b.node(headerId, { lane: id, stack: 0, kind: "card", title: a.name, w: actorW, h: 72 });
|
|
2320
|
+
const spacerId = `${id}-spacer`;
|
|
2321
|
+
b.node(spacerId, { lane: id, stack: 1, kind: "card", title: "", w: 2, h: 40 });
|
|
2322
|
+
});
|
|
2323
|
+
doc.flow.forEach((s, idx) => {
|
|
2324
|
+
const fromLaneId = actorIds.get(s.from) ?? s.from;
|
|
2325
|
+
const toLaneId = actorIds.get(s.to) ?? s.to;
|
|
2326
|
+
const stack = idx + 2;
|
|
2327
|
+
const fromBoxId = `s${idx}-${fromLaneId}`;
|
|
2328
|
+
const toBoxId = `s${idx}-${toLaneId}`;
|
|
2329
|
+
b.node(fromBoxId, { lane: fromLaneId, stack, kind: "card", title: "", w: 2, h: 2 });
|
|
2330
|
+
if (fromLaneId !== toLaneId) {
|
|
2331
|
+
b.node(toBoxId, { lane: toLaneId, stack, kind: "card", title: "", w: 2, h: 2 });
|
|
2332
|
+
}
|
|
2333
|
+
const edgeId = `e${idx}-${fromLaneId}-${toLaneId}`;
|
|
2334
|
+
b.edge(fromBoxId, fromLaneId === toLaneId ? fromBoxId : toBoxId, {
|
|
2335
|
+
id: edgeId,
|
|
2336
|
+
label: s.label,
|
|
2337
|
+
...s.sub ? { sub: s.sub } : {},
|
|
2338
|
+
...s.tone ? { tone: s.tone } : {},
|
|
2339
|
+
...s.style ? { style: s.style } : {},
|
|
2340
|
+
...s.guard ? { guard: s.guard } : {},
|
|
2341
|
+
...s.cardinality ? { cardinality: s.cardinality } : {},
|
|
2342
|
+
...s.labelOffsetX !== void 0 ? { labelOffsetX: s.labelOffsetX } : {},
|
|
2343
|
+
...s.labelOffsetY !== void 0 ? { labelOffsetY: s.labelOffsetY } : {}
|
|
2344
|
+
});
|
|
2345
|
+
});
|
|
2346
|
+
const footerStack = doc.flow.length + 2;
|
|
2347
|
+
doc.actors.forEach((a) => {
|
|
2348
|
+
const laneId = actorIds.get(a.name) ?? slugify(a.name);
|
|
2349
|
+
const footerId = `${laneId}-footer`;
|
|
2350
|
+
const actorW = Math.max(140, a.name.length * 22 + 52);
|
|
2351
|
+
b.node(footerId, { lane: laneId, stack: footerStack, kind: "card", title: a.name, w: actorW, h: 72 });
|
|
2352
|
+
});
|
|
2353
|
+
for (const st of doc.animate.states) {
|
|
2354
|
+
b.state(st.name, { initial: st.initial });
|
|
2355
|
+
}
|
|
2356
|
+
for (const p of doc.animate.phases) {
|
|
2357
|
+
b.phase(
|
|
2358
|
+
slugify(p.name),
|
|
2359
|
+
{
|
|
2360
|
+
duration: p.durationMs,
|
|
2361
|
+
title: p.name,
|
|
2362
|
+
body: p.body ?? ""
|
|
2363
|
+
},
|
|
2364
|
+
(pb) => {
|
|
2365
|
+
const activateIds = resolveHighlight(p, doc, actorIds);
|
|
2366
|
+
if (activateIds.length > 0) {
|
|
2367
|
+
pb.activate(...activateIds);
|
|
2368
|
+
}
|
|
2369
|
+
for (const t of p.tweens ?? []) {
|
|
2370
|
+
pb.tween(t.state, t.from, t.to);
|
|
2371
|
+
}
|
|
2372
|
+
for (const s of p.sets ?? []) {
|
|
2373
|
+
pb.set(s.state, s.value);
|
|
2374
|
+
}
|
|
2375
|
+
if (p.badge) {
|
|
2376
|
+
pb.badge(p.badge);
|
|
2377
|
+
}
|
|
2378
|
+
return pb;
|
|
2379
|
+
}
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2382
|
+
return b.build();
|
|
2383
|
+
}
|
|
2384
|
+
function resolveHighlight(phase, doc, actorIds, _stepEdgeIds) {
|
|
2385
|
+
const out = [];
|
|
2386
|
+
const knownNames = new Set(actorIds.keys());
|
|
2387
|
+
for (const raw of phase.highlight ?? []) {
|
|
2388
|
+
const entry = parseFocusEntry(raw, knownNames);
|
|
2389
|
+
if (entry.kind === "edge") {
|
|
2390
|
+
const fromLaneId = actorIds.get(entry.from) ?? slugify(entry.from);
|
|
2391
|
+
const toLaneId = actorIds.get(entry.to) ?? slugify(entry.to);
|
|
2392
|
+
doc.flow.forEach((s, idx) => {
|
|
2393
|
+
const sFromId = actorIds.get(s.from) ?? slugify(s.from);
|
|
2394
|
+
const sToId = actorIds.get(s.to) ?? slugify(s.to);
|
|
2395
|
+
if (sFromId === fromLaneId && sToId === toLaneId) {
|
|
2396
|
+
out.push(`e${idx}-${fromLaneId}-${toLaneId}`);
|
|
2397
|
+
}
|
|
2398
|
+
});
|
|
2399
|
+
const stackIdx = doc.flow.findIndex((s) => {
|
|
2400
|
+
const sFromId = actorIds.get(s.from) ?? slugify(s.from);
|
|
2401
|
+
const sToId = actorIds.get(s.to) ?? slugify(s.to);
|
|
2402
|
+
return sFromId === fromLaneId && sToId === toLaneId;
|
|
2403
|
+
});
|
|
2404
|
+
if (stackIdx >= 0) {
|
|
2405
|
+
out.push(`s${stackIdx}-${fromLaneId}`);
|
|
2406
|
+
if (fromLaneId !== toLaneId) out.push(`s${stackIdx}-${toLaneId}`);
|
|
2407
|
+
}
|
|
2408
|
+
continue;
|
|
2409
|
+
}
|
|
2410
|
+
const laneId = actorIds.get(entry.name) ?? slugLookup(actorIds, entry.name);
|
|
2411
|
+
if (laneId) {
|
|
2412
|
+
out.push(`${laneId}-header`);
|
|
2413
|
+
out.push(`${laneId}-footer`);
|
|
2414
|
+
doc.flow.forEach((s, idx) => {
|
|
2415
|
+
const sFromId = actorIds.get(s.from) ?? slugify(s.from);
|
|
2416
|
+
const sToId = actorIds.get(s.to) ?? slugify(s.to);
|
|
2417
|
+
if (sFromId === laneId || sToId === laneId) {
|
|
2418
|
+
out.push(`s${idx}-${laneId}`);
|
|
2419
|
+
}
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
return out;
|
|
2424
|
+
}
|
|
2425
|
+
function slugLookup(byName, wanted) {
|
|
2426
|
+
let hit;
|
|
2427
|
+
for (const [name, id] of byName) {
|
|
2428
|
+
if (slugify(name) !== wanted) continue;
|
|
2429
|
+
if (hit !== void 0) return void 0;
|
|
2430
|
+
hit = id;
|
|
2431
|
+
}
|
|
2432
|
+
return hit;
|
|
2433
|
+
}
|
|
2434
|
+
function compileFlow(doc) {
|
|
2435
|
+
if (doc.animate && doc.animate.phases.length > 0) {
|
|
2436
|
+
return compileGenericWithAnimate(doc, { kind: "flow", laneId: "main", laneWidth: 400 });
|
|
2437
|
+
}
|
|
2438
|
+
if (doc.actors.length === 0) {
|
|
2439
|
+
return diagram(slugify(doc.title), { topic: doc.title }).build();
|
|
2440
|
+
}
|
|
2441
|
+
const flowBuilder = flow({
|
|
2442
|
+
id: slugify(doc.title),
|
|
2443
|
+
topic: doc.title
|
|
2444
|
+
});
|
|
2445
|
+
for (let i = 0; i < doc.actors.length; i++) {
|
|
2446
|
+
const a = doc.actors[i];
|
|
2447
|
+
const incomingEdge = doc.flow.find((s) => s.to === a.name);
|
|
2448
|
+
const edgeLabel = incomingEdge?.label;
|
|
2449
|
+
flowBuilder.step(
|
|
2450
|
+
{
|
|
2451
|
+
id: slugify(a.name),
|
|
2452
|
+
kind: a.kind,
|
|
2453
|
+
title: a.name
|
|
2454
|
+
},
|
|
2455
|
+
edgeLabel
|
|
2456
|
+
);
|
|
2457
|
+
}
|
|
2458
|
+
return flowBuilder.build();
|
|
2459
|
+
}
|
|
2460
|
+
function compileSwimlane(doc) {
|
|
2461
|
+
if (doc.animate && doc.animate.phases.length > 0) {
|
|
2462
|
+
return compileGenericWithAnimate(doc, { kind: "swimlane", laneWidth: 400 });
|
|
2463
|
+
}
|
|
2464
|
+
const swim = swimlane({
|
|
2465
|
+
id: slugify(doc.title),
|
|
2466
|
+
topic: doc.title,
|
|
2467
|
+
lanes: doc.actors.map((a) => a.name)
|
|
2468
|
+
});
|
|
2469
|
+
const placedNodes = /* @__PURE__ */ new Set();
|
|
2470
|
+
const laneStackCount = /* @__PURE__ */ new Map();
|
|
2471
|
+
let edgeIdx = 0;
|
|
2472
|
+
for (const s of doc.flow) {
|
|
2473
|
+
for (const actorName of [s.from, s.to]) {
|
|
2474
|
+
if (placedNodes.has(actorName)) continue;
|
|
2475
|
+
const laneId = swim.laneId(actorName);
|
|
2476
|
+
const actor = doc.actors.find((a) => a.name === actorName);
|
|
2477
|
+
const stack = laneStackCount.get(laneId) ?? 0;
|
|
2478
|
+
const nodeId = slugify(actorName);
|
|
2479
|
+
swim.node(nodeId, {
|
|
2480
|
+
lane: laneId,
|
|
2481
|
+
stack,
|
|
2482
|
+
kind: actor?.kind ?? "actor",
|
|
2483
|
+
title: actorName
|
|
2484
|
+
});
|
|
2485
|
+
laneStackCount.set(laneId, stack + 1);
|
|
2486
|
+
placedNodes.add(actorName);
|
|
2487
|
+
}
|
|
2488
|
+
const fromId = slugify(s.from);
|
|
2489
|
+
const toId = slugify(s.to);
|
|
2490
|
+
swim.edge(fromId, toId, {
|
|
2491
|
+
id: `e${edgeIdx++}-${fromId}-${toId}`,
|
|
2492
|
+
label: s.label,
|
|
2493
|
+
...s.sub ? { sub: s.sub } : {},
|
|
2494
|
+
...s.tone ? { tone: s.tone } : {},
|
|
2495
|
+
...s.style ? { style: s.style } : {},
|
|
2496
|
+
...s.guard ? { guard: s.guard } : {},
|
|
2497
|
+
...s.cardinality ? { cardinality: s.cardinality } : {},
|
|
2498
|
+
...s.labelOffsetX !== void 0 ? { labelOffsetX: s.labelOffsetX } : {},
|
|
2499
|
+
...s.labelOffsetY !== void 0 ? { labelOffsetY: s.labelOffsetY } : {}
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
return swim.build();
|
|
2503
|
+
}
|
|
2504
|
+
function compileEr(doc) {
|
|
2505
|
+
if (doc.animate && doc.animate.phases.length > 0) {
|
|
2506
|
+
return compileGenericWithAnimate(doc, { kind: "er", laneWidth: 450 });
|
|
2507
|
+
}
|
|
2508
|
+
const erBuilder = er({
|
|
2509
|
+
id: slugify(doc.title),
|
|
2510
|
+
topic: doc.title
|
|
2511
|
+
});
|
|
2512
|
+
for (const a of doc.actors) {
|
|
2513
|
+
erBuilder.entity({
|
|
2514
|
+
id: slugify(a.name),
|
|
2515
|
+
title: a.name,
|
|
2516
|
+
rows: []
|
|
2517
|
+
// v0.2 では rows なし
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
for (const s of doc.flow) {
|
|
2521
|
+
erBuilder.relation({
|
|
2522
|
+
from: slugify(s.from),
|
|
2523
|
+
to: slugify(s.to),
|
|
2524
|
+
cardinality: parseCardinalityFromLabel(s.label) ?? "1:N",
|
|
2525
|
+
label: stripCardinality(s.label),
|
|
2526
|
+
...s.tone ? { tone: s.tone } : {}
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
return erBuilder.build();
|
|
2530
|
+
}
|
|
2531
|
+
function compileState(doc) {
|
|
2532
|
+
if (doc.animate && doc.animate.phases.length > 0) {
|
|
2533
|
+
return compileGenericWithAnimate(doc, { kind: "state", laneWidth: 360 });
|
|
2534
|
+
}
|
|
2535
|
+
const fsm = stateMachine({
|
|
2536
|
+
id: slugify(doc.title),
|
|
2537
|
+
topic: doc.title
|
|
2538
|
+
});
|
|
2539
|
+
for (let i = 0; i < doc.actors.length; i++) {
|
|
2540
|
+
const a = doc.actors[i];
|
|
2541
|
+
const initial = i === 0;
|
|
2542
|
+
const final = i === doc.actors.length - 1 && doc.actors.length > 1;
|
|
2543
|
+
fsm.state({
|
|
2544
|
+
id: slugify(a.name),
|
|
2545
|
+
title: a.name,
|
|
2546
|
+
...initial ? { initial: true } : {},
|
|
2547
|
+
...final ? { final: true } : {}
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
for (const s of doc.flow) {
|
|
2551
|
+
fsm.transition({
|
|
2552
|
+
from: slugify(s.from),
|
|
2553
|
+
to: slugify(s.to),
|
|
2554
|
+
trigger: s.label,
|
|
2555
|
+
...s.sub ? { guard: s.sub } : {},
|
|
2556
|
+
...s.tone ? { tone: s.tone } : {}
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
return fsm.build();
|
|
2560
|
+
}
|
|
2561
|
+
function compileTopology(doc) {
|
|
2562
|
+
if (doc.animate && doc.animate.phases.length > 0) {
|
|
2563
|
+
return compileGenericWithAnimate(doc, { kind: "topology", laneWidth: 460 });
|
|
2564
|
+
}
|
|
2565
|
+
if (doc.actors.length === 0) {
|
|
2566
|
+
return diagram(slugify(doc.title), { topic: doc.title }).build();
|
|
2567
|
+
}
|
|
2568
|
+
const topo = topology({
|
|
2569
|
+
id: slugify(doc.title),
|
|
2570
|
+
topic: doc.title
|
|
2571
|
+
});
|
|
2572
|
+
const groupId = "main";
|
|
2573
|
+
const groupBuilder = topo.group(groupId, { label: doc.title });
|
|
2574
|
+
for (const a of doc.actors) {
|
|
2575
|
+
groupBuilder.add({
|
|
2576
|
+
id: slugify(a.name),
|
|
2577
|
+
kind: a.kind,
|
|
2578
|
+
title: a.name
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
for (const s of doc.flow) {
|
|
2582
|
+
topo.connect(slugify(s.from), slugify(s.to), {
|
|
2583
|
+
label: s.label,
|
|
2584
|
+
...s.sub ? { sub: s.sub } : {},
|
|
2585
|
+
...s.tone ? { tone: s.tone } : {},
|
|
2586
|
+
...s.style ? { style: s.style } : {}
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
return topo.build();
|
|
2590
|
+
}
|
|
2591
|
+
function compileGenericWithAnimate(doc, opts) {
|
|
2592
|
+
const b = diagram(slugify(doc.title), { topic: doc.title });
|
|
2593
|
+
const { kind, laneWidth } = opts;
|
|
2594
|
+
const actorToNodeId = /* @__PURE__ */ new Map();
|
|
2595
|
+
if (kind === "flow" || kind === "topology") {
|
|
2596
|
+
const lid = opts.laneId ?? "main";
|
|
2597
|
+
b.lane(lid, { width: laneWidth, label: doc.title, ...kind === "topology" ? { contain: true } : {} });
|
|
2598
|
+
doc.actors.forEach((a, idx) => {
|
|
2599
|
+
const id = slugify(a.name);
|
|
2600
|
+
actorToNodeId.set(a.name, id);
|
|
2601
|
+
b.node(id, { lane: lid, stack: idx, kind: a.kind, title: a.name });
|
|
2602
|
+
});
|
|
2603
|
+
} else {
|
|
2604
|
+
doc.actors.forEach((a, idx) => {
|
|
2605
|
+
const lid = `lane-${slugify(a.name)}`;
|
|
2606
|
+
b.lane(lid, { width: laneWidth, label: a.name });
|
|
2607
|
+
const id = slugify(a.name);
|
|
2608
|
+
actorToNodeId.set(a.name, id);
|
|
2609
|
+
const isInitial = kind === "state" && idx === 0;
|
|
2610
|
+
const isFinal = kind === "state" && idx === doc.actors.length - 1 && doc.actors.length > 1;
|
|
2611
|
+
b.node(id, {
|
|
2612
|
+
lane: lid,
|
|
2613
|
+
stack: 0,
|
|
2614
|
+
kind: a.kind,
|
|
2615
|
+
title: a.name,
|
|
2616
|
+
...isInitial ? { eyebrow: "\u521D\u671F" } : {},
|
|
2617
|
+
...isFinal ? { eyebrow: "\u6700\u7D42" } : {}
|
|
2618
|
+
});
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
const edgeIds = [];
|
|
2622
|
+
doc.flow.forEach((s, idx) => {
|
|
2623
|
+
const fromId = actorToNodeId.get(s.from) ?? slugify(s.from);
|
|
2624
|
+
const toId = actorToNodeId.get(s.to) ?? slugify(s.to);
|
|
2625
|
+
const edgeId = `e${idx}-${fromId}-${toId}`;
|
|
2626
|
+
const labelWithCard = kind === "er" && s.cardinality && !s.label.includes(s.cardinality) ? s.label ? `${s.label} (${s.cardinality})` : `(${s.cardinality})` : s.label;
|
|
2627
|
+
b.edge(fromId, toId, {
|
|
2628
|
+
id: edgeId,
|
|
2629
|
+
label: labelWithCard,
|
|
2630
|
+
...s.sub ? { sub: s.sub } : {},
|
|
2631
|
+
...s.tone ? { tone: s.tone } : {},
|
|
2632
|
+
...s.style ? { style: s.style } : {},
|
|
2633
|
+
...s.guard ? { guard: s.guard } : {},
|
|
2634
|
+
...s.cardinality ? { cardinality: s.cardinality } : {},
|
|
2635
|
+
...s.labelOffsetX !== void 0 ? { labelOffsetX: s.labelOffsetX } : {},
|
|
2636
|
+
...s.labelOffsetY !== void 0 ? { labelOffsetY: s.labelOffsetY } : {}
|
|
2637
|
+
});
|
|
2638
|
+
edgeIds.push(edgeId);
|
|
2639
|
+
});
|
|
2640
|
+
for (const st of doc.animate.states) {
|
|
2641
|
+
b.state(st.name, { initial: st.initial });
|
|
2642
|
+
}
|
|
2643
|
+
for (const p of doc.animate.phases) {
|
|
2644
|
+
b.phase(
|
|
2645
|
+
slugify(p.name),
|
|
2646
|
+
{
|
|
2647
|
+
duration: p.durationMs,
|
|
2648
|
+
title: p.name,
|
|
2649
|
+
body: p.body ?? ""
|
|
2650
|
+
},
|
|
2651
|
+
(pb) => {
|
|
2652
|
+
const activateIds = resolveHighlightGeneric(p, doc, actorToNodeId, edgeIds);
|
|
2653
|
+
if (activateIds.length > 0) {
|
|
2654
|
+
pb.activate(...activateIds);
|
|
2655
|
+
}
|
|
2656
|
+
for (const t of p.tweens ?? []) {
|
|
2657
|
+
pb.tween(t.state, t.from, t.to);
|
|
2658
|
+
}
|
|
2659
|
+
for (const s of p.sets ?? []) {
|
|
2660
|
+
pb.set(s.state, s.value);
|
|
2661
|
+
}
|
|
2662
|
+
if (p.badge) {
|
|
2663
|
+
pb.badge(p.badge);
|
|
2664
|
+
}
|
|
2665
|
+
return pb;
|
|
2666
|
+
}
|
|
2667
|
+
);
|
|
2668
|
+
}
|
|
2669
|
+
return b.build();
|
|
2670
|
+
}
|
|
2671
|
+
function resolveHighlightGeneric(phase, doc, actorToNodeId, edgeIds) {
|
|
2672
|
+
const out = [];
|
|
2673
|
+
const knownNames = new Set(actorToNodeId.keys());
|
|
2674
|
+
for (const raw of phase.highlight ?? []) {
|
|
2675
|
+
const entry = parseFocusEntry(raw, knownNames);
|
|
2676
|
+
if (entry.kind === "edge") {
|
|
2677
|
+
const fromId = actorToNodeId.get(entry.from) ?? slugify(entry.from);
|
|
2678
|
+
const toId = actorToNodeId.get(entry.to) ?? slugify(entry.to);
|
|
2679
|
+
for (const edgeId of edgeIds) {
|
|
2680
|
+
if (edgeId.endsWith(`-${fromId}-${toId}`)) {
|
|
2681
|
+
out.push(edgeId);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
continue;
|
|
2685
|
+
}
|
|
2686
|
+
const nodeId = actorToNodeId.get(entry.name) ?? slugLookup(actorToNodeId, entry.name);
|
|
2687
|
+
if (nodeId) {
|
|
2688
|
+
out.push(nodeId);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
return out;
|
|
2692
|
+
}
|
|
2693
|
+
function slugify(s) {
|
|
2694
|
+
return s.toLowerCase().normalize("NFKC").replace(/[^a-z0-9ぁ-んァ-ヶ一-龯\-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "n";
|
|
2695
|
+
}
|
|
2696
|
+
var CARDINALITY_PATTERNS = [
|
|
2697
|
+
[/1:1/, "1:1"],
|
|
2698
|
+
[/1:N/i, "1:N"],
|
|
2699
|
+
[/N:1/i, "N:1"],
|
|
2700
|
+
[/N:M/i, "N:M"],
|
|
2701
|
+
[/0\.\.1/, "0..1"],
|
|
2702
|
+
[/1\.\.\*/, "1..*"]
|
|
2703
|
+
];
|
|
2704
|
+
function boundedCardinalityRegExp(pattern, extraFlags = "") {
|
|
2705
|
+
const base = pattern.flags.includes("i") ? "i" : "";
|
|
2706
|
+
return new RegExp(`(?<![A-Za-z0-9_])(?:${pattern.source})(?![A-Za-z0-9_])`, base + extraFlags);
|
|
2707
|
+
}
|
|
2708
|
+
function parseCardinalityFromLabel(label) {
|
|
2709
|
+
for (const [pattern, card] of CARDINALITY_PATTERNS) {
|
|
2710
|
+
if (boundedCardinalityRegExp(pattern).test(label)) return card;
|
|
2711
|
+
}
|
|
2712
|
+
return null;
|
|
2713
|
+
}
|
|
2714
|
+
var HORIZONTAL_WS = " \\t\\u3000";
|
|
2715
|
+
var HWS = `[${HORIZONTAL_WS}]`;
|
|
2716
|
+
function stripCardinality(label) {
|
|
2717
|
+
let r = label;
|
|
2718
|
+
let removed = false;
|
|
2719
|
+
for (const [pattern] of CARDINALITY_PATTERNS) {
|
|
2720
|
+
const src = pattern.source;
|
|
2721
|
+
const flags = pattern.flags.includes("i") ? "gi" : "g";
|
|
2722
|
+
const before = r;
|
|
2723
|
+
r = r.replace(new RegExp(`\\(${HWS}*${src}${HWS}*\\)`, flags), "");
|
|
2724
|
+
r = r.replace(boundedCardinalityRegExp(pattern, "g"), "");
|
|
2725
|
+
if (r !== before) removed = true;
|
|
2726
|
+
}
|
|
2727
|
+
if (!removed) return label;
|
|
2728
|
+
r = r.replace(new RegExp(`${HWS}{2,}`, "g"), " ").replace(new RegExp(`${HWS}*([\\r\\n])${HWS}*`, "g"), "$1").replace(new RegExp(`^${HWS}+|${HWS}+$`, "g"), "");
|
|
2729
|
+
const hasVisible = /[^\p{White_Space}\p{Cf}\p{Cc}\p{Default_Ignorable_Code_Point}]/u.test(r);
|
|
2730
|
+
return hasVisible ? r : label;
|
|
2731
|
+
}
|
|
2732
|
+
var PRESET_TYPES = /* @__PURE__ */ new Set([
|
|
2733
|
+
"sequence",
|
|
2734
|
+
"flow",
|
|
2735
|
+
"swimlane",
|
|
2736
|
+
"er",
|
|
2737
|
+
"state",
|
|
2738
|
+
"topology",
|
|
2739
|
+
"solidity",
|
|
2740
|
+
"gantt",
|
|
2741
|
+
"class",
|
|
2742
|
+
"pie",
|
|
2743
|
+
"c4",
|
|
2744
|
+
"mind"
|
|
2745
|
+
]);
|
|
2746
|
+
var NODE_KIND_DEFAULT = "actor";
|
|
2747
|
+
var DSL_ONLY_KINDS = [
|
|
2748
|
+
"entity",
|
|
2749
|
+
"state",
|
|
2750
|
+
"contract",
|
|
2751
|
+
"eoa",
|
|
2752
|
+
"multisig",
|
|
2753
|
+
"proxy",
|
|
2754
|
+
"library",
|
|
2755
|
+
"interface"
|
|
2756
|
+
];
|
|
2757
|
+
var INFRA_KIND_ALIAS = {
|
|
2758
|
+
alb: "shape-api-gateway",
|
|
2759
|
+
// 入口で振り分ける
|
|
2760
|
+
browser: "frontend",
|
|
2761
|
+
// 画面側
|
|
2762
|
+
ecs: "microservice",
|
|
2763
|
+
// コンテナ群
|
|
2764
|
+
iam: "admin",
|
|
2765
|
+
// 権限を守る
|
|
2766
|
+
kms: "admin",
|
|
2767
|
+
// 鍵を守る
|
|
2768
|
+
lambda: "function",
|
|
2769
|
+
// 呼ぶと動く
|
|
2770
|
+
rds: "database",
|
|
2771
|
+
// 表を持つ
|
|
2772
|
+
s3: "storage",
|
|
2773
|
+
// 置き場
|
|
2774
|
+
secret: "storage",
|
|
2775
|
+
// 機密の置き場
|
|
2776
|
+
user: "person",
|
|
2777
|
+
// 人
|
|
2778
|
+
container: "service"
|
|
2779
|
+
// 動かす単位 (C4 の container)
|
|
2780
|
+
};
|
|
2781
|
+
var NODE_KIND_VALID = /* @__PURE__ */ new Set([
|
|
2782
|
+
...NODE_KINDS,
|
|
2783
|
+
...DSL_ONLY_KINDS,
|
|
2784
|
+
...Object.keys(INFRA_KIND_ALIAS)
|
|
2785
|
+
]);
|
|
2786
|
+
var TONE_VALID = new Set(TONES);
|
|
2787
|
+
var STYLE_VALID = /* @__PURE__ */ new Set(["solid", "dotted-flow"]);
|
|
2788
|
+
function parseTextDslV05(src) {
|
|
2789
|
+
const errors = [];
|
|
2790
|
+
const lines = tokenize(src);
|
|
2791
|
+
let title = null;
|
|
2792
|
+
let type = null;
|
|
2793
|
+
let actors = [];
|
|
2794
|
+
const flow2 = [];
|
|
2795
|
+
let animate = void 0;
|
|
2796
|
+
let viewport = void 0;
|
|
2797
|
+
let lanesMap = void 0;
|
|
2798
|
+
let groupsMap = void 0;
|
|
2799
|
+
let i = 0;
|
|
2800
|
+
while (i < lines.length) {
|
|
2801
|
+
const line = lines[i];
|
|
2802
|
+
if (!line.trimmed || line.trimmed.startsWith("#")) {
|
|
2803
|
+
i += 1;
|
|
2804
|
+
continue;
|
|
2805
|
+
}
|
|
2806
|
+
const head = matchTopHeader(line.trimmed);
|
|
2807
|
+
if (!head) {
|
|
2808
|
+
errors.push({
|
|
2809
|
+
line: line.no,
|
|
2810
|
+
message: `unknown top-level key: "${line.trimmed}"`,
|
|
2811
|
+
hint: "expected one of: title, type, actors, flow, states, animation, viewport, lanes, groups"
|
|
2812
|
+
});
|
|
2813
|
+
i += 1;
|
|
2814
|
+
continue;
|
|
2815
|
+
}
|
|
2816
|
+
if (head.key === "title") {
|
|
2817
|
+
title = head.value ?? null;
|
|
2818
|
+
if (!title) {
|
|
2819
|
+
errors.push({ line: line.no, message: "title is required", hint: 'use `title: "..."`' });
|
|
2820
|
+
}
|
|
2821
|
+
i += 1;
|
|
2822
|
+
continue;
|
|
2823
|
+
}
|
|
2824
|
+
if (head.key === "type") {
|
|
2825
|
+
const v = (head.value ?? "").trim().toLowerCase();
|
|
2826
|
+
if (!PRESET_TYPES.has(v)) {
|
|
2827
|
+
errors.push({
|
|
2828
|
+
line: line.no,
|
|
2829
|
+
message: `unknown type: "${v}"`,
|
|
2830
|
+
hint: `expected: ${Array.from(PRESET_TYPES).join(", ")}`
|
|
2831
|
+
});
|
|
2832
|
+
} else {
|
|
2833
|
+
type = v;
|
|
2834
|
+
}
|
|
2835
|
+
i += 1;
|
|
2836
|
+
continue;
|
|
2837
|
+
}
|
|
2838
|
+
if (head.key === "actors") {
|
|
2839
|
+
const { items, next } = collectActorEntries(lines, i + 1, line.indent);
|
|
2840
|
+
actors = [];
|
|
2841
|
+
for (const entry of items) {
|
|
2842
|
+
const base = parseActor2(entry[0], errors);
|
|
2843
|
+
if (base === null) {
|
|
2844
|
+
errors.push({
|
|
2845
|
+
line: entry[0].no,
|
|
2846
|
+
message: `invalid actor entry: "${entry[0].trimmed}"`,
|
|
2847
|
+
hint: "use `- Client` or `- Client: storage`"
|
|
2848
|
+
});
|
|
2849
|
+
continue;
|
|
2850
|
+
}
|
|
2851
|
+
actors.push(applyContinuationLines(base, entry.slice(1), errors));
|
|
2852
|
+
}
|
|
2853
|
+
validateRelativePositions(actors, errors);
|
|
2854
|
+
i = next;
|
|
2855
|
+
continue;
|
|
2856
|
+
}
|
|
2857
|
+
if (head.key === "flow") {
|
|
2858
|
+
const { items, next } = collectIndentedList(lines, i + 1, line.indent);
|
|
2859
|
+
let stepNo = 1;
|
|
2860
|
+
for (const it of items) {
|
|
2861
|
+
const step = parseFlowStep(it, stepNo);
|
|
2862
|
+
if (step) {
|
|
2863
|
+
flow2.push(step);
|
|
2864
|
+
stepNo += 1;
|
|
2865
|
+
} else {
|
|
2866
|
+
errors.push({
|
|
2867
|
+
line: it.no,
|
|
2868
|
+
message: `invalid flow entry: "${it.trimmed}"`,
|
|
2869
|
+
hint: 'use `- A -> B: "label"` or `- A -> B: "label" (success)`'
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
i = next;
|
|
2874
|
+
continue;
|
|
2875
|
+
}
|
|
2876
|
+
if (head.key === "states") {
|
|
2877
|
+
const inlineMatch = head.value?.trim();
|
|
2878
|
+
if (inlineMatch && inlineMatch.startsWith("{") && inlineMatch.endsWith("}")) {
|
|
2879
|
+
const inner = inlineMatch.slice(1, -1).trim();
|
|
2880
|
+
animate = ensureAnimate(animate, line.no);
|
|
2881
|
+
for (const pair of splitTopLevelCommas(inner)) {
|
|
2882
|
+
const st = parseStateEntry(pair, line.no);
|
|
2883
|
+
if (st) animate.states.push(st);
|
|
2884
|
+
}
|
|
2885
|
+
i += 1;
|
|
2886
|
+
continue;
|
|
2887
|
+
}
|
|
2888
|
+
const { items, next } = collectIndentedList(lines, i + 1, line.indent);
|
|
2889
|
+
animate = ensureAnimate(animate, line.no);
|
|
2890
|
+
for (const it of items) {
|
|
2891
|
+
const st = parseStateEntry(it.trimmed.replace(/^-\s*/, ""), it.no);
|
|
2892
|
+
if (st) animate.states.push(st);
|
|
2893
|
+
else errors.push({ line: it.no, message: `invalid state entry: "${it.trimmed}"`, hint: "use `name: initial`" });
|
|
2894
|
+
}
|
|
2895
|
+
i = next;
|
|
2896
|
+
continue;
|
|
2897
|
+
}
|
|
2898
|
+
if (head.key === "animation") {
|
|
2899
|
+
const { items: stepBlocks, next } = collectAnimationSteps(lines, i + 1, line.indent);
|
|
2900
|
+
animate = ensureAnimate(animate, line.no);
|
|
2901
|
+
for (const block of stepBlocks) {
|
|
2902
|
+
const ph = parsePhase(block, errors);
|
|
2903
|
+
if (ph) animate.phases.push(ph);
|
|
2904
|
+
}
|
|
2905
|
+
i = next;
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
if (head.key === "viewport") {
|
|
2909
|
+
const inline = head.value?.trim();
|
|
2910
|
+
if (inline && inline.startsWith("{") && inline.endsWith("}")) {
|
|
2911
|
+
const opts2 = parseInlineMapping(inline.slice(1, -1));
|
|
2912
|
+
viewport = {
|
|
2913
|
+
width: numberOrUndef(opts2.width),
|
|
2914
|
+
height: numberOrUndef(opts2.height),
|
|
2915
|
+
laneWidth: numberOrUndef(opts2.laneWidth),
|
|
2916
|
+
gap: numberOrUndef(opts2.gap),
|
|
2917
|
+
laneGap: numberOrUndef(opts2.laneGap),
|
|
2918
|
+
nodeGap: numberOrUndef(opts2.nodeGap),
|
|
2919
|
+
scale: numberOrUndef(opts2.scale),
|
|
2920
|
+
labelMargin: numberOrUndef(opts2.labelMargin),
|
|
2921
|
+
pos: { line: line.no }
|
|
2922
|
+
};
|
|
2923
|
+
i += 1;
|
|
2924
|
+
continue;
|
|
2925
|
+
}
|
|
2926
|
+
const { items, next } = collectIndentedList(lines, i + 1, line.indent);
|
|
2927
|
+
const opts = {};
|
|
2928
|
+
for (const it of items) {
|
|
2929
|
+
const m = it.trimmed.match(/^([a-zA-Z][a-zA-Z0-9_]*)\s*:\s*(.+)$/);
|
|
2930
|
+
if (m) opts[m[1]] = stripQuotes(m[2].trim());
|
|
2931
|
+
}
|
|
2932
|
+
viewport = {
|
|
2933
|
+
width: numberOrUndef(opts.width),
|
|
2934
|
+
height: numberOrUndef(opts.height),
|
|
2935
|
+
laneWidth: numberOrUndef(opts.laneWidth),
|
|
2936
|
+
gap: numberOrUndef(opts.gap),
|
|
2937
|
+
laneGap: numberOrUndef(opts.laneGap),
|
|
2938
|
+
nodeGap: numberOrUndef(opts.nodeGap),
|
|
2939
|
+
scale: numberOrUndef(opts.scale),
|
|
2940
|
+
labelMargin: numberOrUndef(opts.labelMargin),
|
|
2941
|
+
pos: { line: line.no }
|
|
2942
|
+
};
|
|
2943
|
+
i = next;
|
|
2944
|
+
continue;
|
|
2945
|
+
}
|
|
2946
|
+
if (head.key === "lanes") {
|
|
2947
|
+
const { items, next } = collectIndentedList(lines, i + 1, line.indent);
|
|
2948
|
+
lanesMap = {};
|
|
2949
|
+
for (const it of items) {
|
|
2950
|
+
const m = it.trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*\{([^}]*)\}\s*$/);
|
|
2951
|
+
if (m) {
|
|
2952
|
+
const id = m[1];
|
|
2953
|
+
const opts = parseInlineMapping(m[2]);
|
|
2954
|
+
lanesMap[id] = {
|
|
2955
|
+
id,
|
|
2956
|
+
x: numberOrUndef(opts.x),
|
|
2957
|
+
width: numberOrUndef(opts.width),
|
|
2958
|
+
label: opts.label,
|
|
2959
|
+
contain: boolOrUndef(opts.contain),
|
|
2960
|
+
lifeline: boolOrUndef(opts.lifeline),
|
|
2961
|
+
pos: { line: it.no }
|
|
2962
|
+
};
|
|
2963
|
+
} else {
|
|
2964
|
+
errors.push({
|
|
2965
|
+
line: it.no,
|
|
2966
|
+
message: `invalid lane entry: "${it.trimmed}"`,
|
|
2967
|
+
hint: 'use `id: { x: 0, width: 320, label: "..." }`'
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
i = next;
|
|
2972
|
+
continue;
|
|
2973
|
+
}
|
|
2974
|
+
if (head.key === "groups") {
|
|
2975
|
+
const { items, next } = collectIndentedList(lines, i + 1, line.indent);
|
|
2976
|
+
groupsMap = {};
|
|
2977
|
+
for (const it of items) {
|
|
2978
|
+
const m = it.trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*\{([^}]*)\}\s*$/);
|
|
2979
|
+
if (m) {
|
|
2980
|
+
const id = m[1];
|
|
2981
|
+
const opts = parseInlineMapping(m[2]);
|
|
2982
|
+
const lanesList = (opts.lanes ?? "").replace(/^\[|\]$/g, "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
2983
|
+
groupsMap[id] = {
|
|
2984
|
+
id,
|
|
2985
|
+
label: opts.label,
|
|
2986
|
+
lanes: lanesList,
|
|
2987
|
+
pos: { line: it.no }
|
|
2988
|
+
};
|
|
2989
|
+
} else {
|
|
2990
|
+
errors.push({
|
|
2991
|
+
line: it.no,
|
|
2992
|
+
message: `invalid group entry: "${it.trimmed}"`,
|
|
2993
|
+
hint: 'use `id: { label: "...", lanes: [a, b] }`'
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
i = next;
|
|
2998
|
+
continue;
|
|
2999
|
+
}
|
|
3000
|
+
i += 1;
|
|
3001
|
+
}
|
|
3002
|
+
if (!title) errors.push({ line: 1, message: "title is required", hint: 'add `title: "..."` at top' });
|
|
3003
|
+
if (!type) errors.push({ line: 1, message: "type is required", hint: "add `type: sequence|flow|swimlane|er|state|topology|solidity|gantt|class|pie|c4|mind`" });
|
|
3004
|
+
if (errors.length > 0) return { ok: false, errors };
|
|
3005
|
+
return {
|
|
3006
|
+
ok: true,
|
|
3007
|
+
doc: {
|
|
3008
|
+
title,
|
|
3009
|
+
type,
|
|
3010
|
+
actors,
|
|
3011
|
+
flow: flow2,
|
|
3012
|
+
animate,
|
|
3013
|
+
viewport,
|
|
3014
|
+
lanes: lanesMap,
|
|
3015
|
+
groups: groupsMap,
|
|
3016
|
+
pos: { line: 1 }
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
function tokenize(src) {
|
|
3021
|
+
const out = [];
|
|
3022
|
+
const raw = src.split("\n");
|
|
3023
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
3024
|
+
const r = raw[i] ?? "";
|
|
3025
|
+
const trimmed = r.trim();
|
|
3026
|
+
const indent = r.length - r.trimStart().length;
|
|
3027
|
+
out.push({ raw: r, trimmed, indent, no: i + 1 });
|
|
3028
|
+
}
|
|
3029
|
+
return out;
|
|
3030
|
+
}
|
|
3031
|
+
function matchTopHeader(trimmed) {
|
|
3032
|
+
const m = trimmed.match(/^([a-zA-Z][a-zA-Z0-9_]*)\s*:\s*(.*)$/);
|
|
3033
|
+
if (!m) return null;
|
|
3034
|
+
const value = (m[2] ?? "").trim();
|
|
3035
|
+
return { key: (m[1] ?? "").toLowerCase(), value: value.length ? stripQuotes(value) : null };
|
|
3036
|
+
}
|
|
3037
|
+
function stripQuotes(s) {
|
|
3038
|
+
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
|
|
3039
|
+
return s.slice(1, -1);
|
|
3040
|
+
}
|
|
3041
|
+
return s;
|
|
3042
|
+
}
|
|
3043
|
+
function lastTopLevelColon(s) {
|
|
3044
|
+
let depth = 0;
|
|
3045
|
+
let quote = "";
|
|
3046
|
+
let last = -1;
|
|
3047
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
3048
|
+
const c = s[i];
|
|
3049
|
+
if (quote) {
|
|
3050
|
+
if (c === quote) quote = "";
|
|
3051
|
+
continue;
|
|
3052
|
+
}
|
|
3053
|
+
if (c === '"' || c === "'") {
|
|
3054
|
+
quote = c;
|
|
3055
|
+
continue;
|
|
3056
|
+
}
|
|
3057
|
+
if (c === "[" || c === "{") depth += 1;
|
|
3058
|
+
else if (c === "]" || c === "}") depth -= 1;
|
|
3059
|
+
else if (c === ":" && depth === 0) last = i;
|
|
3060
|
+
}
|
|
3061
|
+
return last;
|
|
3062
|
+
}
|
|
3063
|
+
function splitValues(s) {
|
|
3064
|
+
const out = [];
|
|
3065
|
+
let buf = "";
|
|
3066
|
+
let depth = 0;
|
|
3067
|
+
let quote = "";
|
|
3068
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
3069
|
+
const c = s[i];
|
|
3070
|
+
if (quote) {
|
|
3071
|
+
buf += c;
|
|
3072
|
+
if (c === quote) quote = "";
|
|
3073
|
+
continue;
|
|
3074
|
+
}
|
|
3075
|
+
if (c === '"' || c === "'") {
|
|
3076
|
+
quote = c;
|
|
3077
|
+
buf += c;
|
|
3078
|
+
continue;
|
|
3079
|
+
}
|
|
3080
|
+
if (c === "[" || c === "{") {
|
|
3081
|
+
depth += 1;
|
|
3082
|
+
buf += c;
|
|
3083
|
+
continue;
|
|
3084
|
+
}
|
|
3085
|
+
if (c === "]" || c === "}") {
|
|
3086
|
+
depth -= 1;
|
|
3087
|
+
buf += c;
|
|
3088
|
+
continue;
|
|
3089
|
+
}
|
|
3090
|
+
if (/\s/.test(c) && depth === 0) {
|
|
3091
|
+
if (buf) {
|
|
3092
|
+
out.push(buf);
|
|
3093
|
+
buf = "";
|
|
3094
|
+
}
|
|
3095
|
+
continue;
|
|
3096
|
+
}
|
|
3097
|
+
buf += c;
|
|
3098
|
+
}
|
|
3099
|
+
if (buf) out.push(buf);
|
|
3100
|
+
return out;
|
|
3101
|
+
}
|
|
3102
|
+
function classifyValues(values) {
|
|
3103
|
+
const out = { kind: "" };
|
|
3104
|
+
const scaleWritten = /* @__PURE__ */ new Map();
|
|
3105
|
+
const kindWords = [];
|
|
3106
|
+
for (const v of values) {
|
|
3107
|
+
if (v.startsWith('"') && v.endsWith('"') && v.length > 1 || v.startsWith("'") && v.endsWith("'") && v.length > 1) {
|
|
3108
|
+
if (out.subtitle === void 0) out.subtitle = stripQuotes(v);
|
|
3109
|
+
else if (out.value === void 0) out.value = stripQuotes(v);
|
|
3110
|
+
continue;
|
|
3111
|
+
}
|
|
3112
|
+
if (v.startsWith("[") && v.endsWith("]")) {
|
|
3113
|
+
out.rows = v.slice(1, -1).split(/,(?![^[]*\])/).map((x) => stripQuotes(x.trim())).filter(Boolean);
|
|
3114
|
+
continue;
|
|
3115
|
+
}
|
|
3116
|
+
const at = v.match(/^@(-?\d+(?:\.\d+)?)\s*[,、]\s*(-?\d+(?:\.\d+)?)$/);
|
|
3117
|
+
if (at) {
|
|
3118
|
+
out.posX = Number(at[1]);
|
|
3119
|
+
out.posY = Number(at[2]);
|
|
3120
|
+
continue;
|
|
3121
|
+
}
|
|
3122
|
+
const eq = v.indexOf("=");
|
|
3123
|
+
if (eq > 0) {
|
|
3124
|
+
const key = v.slice(0, eq);
|
|
3125
|
+
const raw = stripQuotes(v.slice(eq + 1));
|
|
3126
|
+
if (SCALE_KEYS.has(key)) {
|
|
3127
|
+
scaleWritten.set(key, raw);
|
|
3128
|
+
continue;
|
|
3129
|
+
}
|
|
3130
|
+
if (/^[A-Za-z_][\w-]*$/.test(key)) {
|
|
3131
|
+
out.state = { ...out.state ?? {}, [key]: coerceStateValue(raw) };
|
|
3132
|
+
continue;
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
const tone = toneOrUndef(v);
|
|
3136
|
+
if (tone) {
|
|
3137
|
+
out.tone = tone;
|
|
3138
|
+
continue;
|
|
3139
|
+
}
|
|
3140
|
+
kindWords.push(v);
|
|
3141
|
+
}
|
|
3142
|
+
out.kind = kindWords.join(" ").toLowerCase();
|
|
3143
|
+
const s = resolveScale(scaleWritten);
|
|
3144
|
+
out.scale = s.scale;
|
|
3145
|
+
out.scaleKeys = s.keys;
|
|
3146
|
+
return out;
|
|
3147
|
+
}
|
|
3148
|
+
function resolveKind(raw) {
|
|
3149
|
+
if (raw === "") return NODE_KIND_DEFAULT;
|
|
3150
|
+
return Object.hasOwn(INFRA_KIND_ALIAS, raw) ? INFRA_KIND_ALIAS[raw] : raw;
|
|
3151
|
+
}
|
|
3152
|
+
function numberOrUndef(s) {
|
|
3153
|
+
if (s === void 0 || s === "") return void 0;
|
|
3154
|
+
const n = Number(s);
|
|
3155
|
+
return Number.isFinite(n) ? n : void 0;
|
|
3156
|
+
}
|
|
3157
|
+
function boolOrUndef(s) {
|
|
3158
|
+
if (s === void 0) return void 0;
|
|
3159
|
+
const lower = s.toLowerCase();
|
|
3160
|
+
if (lower === "true") return true;
|
|
3161
|
+
if (lower === "false") return false;
|
|
3162
|
+
return void 0;
|
|
3163
|
+
}
|
|
3164
|
+
function toneOrUndef(s) {
|
|
3165
|
+
if (s === void 0) return void 0;
|
|
3166
|
+
const raw = stripQuotes(s.trim());
|
|
3167
|
+
const lower = raw.toLowerCase();
|
|
3168
|
+
const resolved = Object.hasOwn(TONE_ALIAS, raw) ? TONE_ALIAS[raw] : Object.hasOwn(TONE_ALIAS, lower) ? TONE_ALIAS[lower] : void 0;
|
|
3169
|
+
if (resolved !== void 0 && TONE_VALID.has(resolved)) return resolved;
|
|
3170
|
+
return TONE_VALID.has(lower) ? lower : void 0;
|
|
3171
|
+
}
|
|
3172
|
+
function matchActorInlineMapping(raw) {
|
|
3173
|
+
const colonIdx = raw.indexOf(":");
|
|
3174
|
+
if (colonIdx < 0) return null;
|
|
3175
|
+
const name = raw.slice(0, colonIdx);
|
|
3176
|
+
const rest = raw.slice(colonIdx + 1).trim();
|
|
3177
|
+
if (!rest.startsWith("{")) return null;
|
|
3178
|
+
let depth = 0;
|
|
3179
|
+
let endIdx = -1;
|
|
3180
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
3181
|
+
const c = rest[i];
|
|
3182
|
+
if (c === "{") depth += 1;
|
|
3183
|
+
else if (c === "}") {
|
|
3184
|
+
depth -= 1;
|
|
3185
|
+
if (depth === 0) {
|
|
3186
|
+
endIdx = i;
|
|
3187
|
+
break;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
if (endIdx < 0) return null;
|
|
3192
|
+
const inner = rest.slice(1, endIdx);
|
|
3193
|
+
return { name, inner };
|
|
3194
|
+
}
|
|
3195
|
+
function parseInlineMapping(inner) {
|
|
3196
|
+
const out = {};
|
|
3197
|
+
for (const p of splitInlineFields(inner)) {
|
|
3198
|
+
const m = p.match(/^\s*([^\s:,{}[\]"']+)\s*:\s*(.+?)\s*$/);
|
|
3199
|
+
if (m) {
|
|
3200
|
+
const key = m[1];
|
|
3201
|
+
out[key] = stripQuotes(m[2].trim());
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
return out;
|
|
3205
|
+
}
|
|
3206
|
+
function writtenScaleFields(inner) {
|
|
3207
|
+
const out = /* @__PURE__ */ new Map();
|
|
3208
|
+
for (const field of splitInlineFields(inner)) {
|
|
3209
|
+
const idx = field.indexOf(":");
|
|
3210
|
+
if (idx < 0) continue;
|
|
3211
|
+
const key = field.slice(0, idx).trim();
|
|
3212
|
+
if (!SCALE_KEYS.has(key)) continue;
|
|
3213
|
+
out.set(key, stripQuotes(field.slice(idx + 1).trim()));
|
|
3214
|
+
}
|
|
3215
|
+
return out;
|
|
3216
|
+
}
|
|
3217
|
+
function splitInlineFields(inner) {
|
|
3218
|
+
let depth = 0;
|
|
3219
|
+
let buf = "";
|
|
3220
|
+
const parts = [];
|
|
3221
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
3222
|
+
const c = inner[i];
|
|
3223
|
+
if (c === "[" || c === "{") depth += 1;
|
|
3224
|
+
else if (c === "]" || c === "}") depth -= 1;
|
|
3225
|
+
if (c === "," && depth === 0) {
|
|
3226
|
+
parts.push(buf);
|
|
3227
|
+
buf = "";
|
|
3228
|
+
continue;
|
|
3229
|
+
}
|
|
3230
|
+
buf += c;
|
|
3231
|
+
}
|
|
3232
|
+
if (buf.trim()) parts.push(buf);
|
|
3233
|
+
return parts;
|
|
3234
|
+
}
|
|
3235
|
+
function collectIndentedList(lines, start, parentIndent) {
|
|
3236
|
+
const items = [];
|
|
3237
|
+
let i = start;
|
|
3238
|
+
while (i < lines.length) {
|
|
3239
|
+
const ln = lines[i];
|
|
3240
|
+
if (!ln.trimmed) {
|
|
3241
|
+
i += 1;
|
|
3242
|
+
continue;
|
|
3243
|
+
}
|
|
3244
|
+
if (ln.indent <= parentIndent) break;
|
|
3245
|
+
if (ln.trimmed.startsWith("- ")) {
|
|
3246
|
+
items.push({ ...ln, trimmed: ln.trimmed.slice(2).trim() });
|
|
3247
|
+
} else if (ln.trimmed.includes(":")) {
|
|
3248
|
+
items.push(ln);
|
|
3249
|
+
}
|
|
3250
|
+
i += 1;
|
|
3251
|
+
}
|
|
3252
|
+
return { items, next: i };
|
|
3253
|
+
}
|
|
3254
|
+
function splitColorValue(raw) {
|
|
3255
|
+
const v = stripQuotes(raw.trim());
|
|
3256
|
+
if (v.startsWith("#")) return { hex: v };
|
|
3257
|
+
const tone = toneOrUndef(v);
|
|
3258
|
+
return tone ? { tone } : {};
|
|
3259
|
+
}
|
|
3260
|
+
var COLOR_KEYS = /* @__PURE__ */ new Set(["\u8272", "color", "tone"]);
|
|
3261
|
+
var SCALE_KEYS = /* @__PURE__ */ new Set(["scale", "\u500D\u7387"]);
|
|
3262
|
+
var SCALE_ORDER = ["scale", "\u500D\u7387"];
|
|
3263
|
+
function resolveScale(written) {
|
|
3264
|
+
const keys = [...written.keys()];
|
|
3265
|
+
for (const key of SCALE_ORDER) {
|
|
3266
|
+
const raw = written.get(key);
|
|
3267
|
+
if (raw !== void 0) return { scale: numberOrUndef(raw), keys };
|
|
3268
|
+
}
|
|
3269
|
+
return { keys };
|
|
3270
|
+
}
|
|
3271
|
+
function applyContinuationLines(actor, rest, errors) {
|
|
3272
|
+
if (rest.length === 0) return actor;
|
|
3273
|
+
const out = { ...actor };
|
|
3274
|
+
const state = { ...actor.stateOverride ?? {} };
|
|
3275
|
+
let touchedState = false;
|
|
3276
|
+
const scaleWritten = /* @__PURE__ */ new Map();
|
|
3277
|
+
const unknownKeys = [];
|
|
3278
|
+
for (const ln of rest) {
|
|
3279
|
+
const idx = ln.trimmed.indexOf(":");
|
|
3280
|
+
if (idx < 0) continue;
|
|
3281
|
+
const key = ln.trimmed.slice(0, idx).trim();
|
|
3282
|
+
const raw = ln.trimmed.slice(idx + 1).trim();
|
|
3283
|
+
if (!key) continue;
|
|
3284
|
+
if (SCALE_KEYS.has(key)) {
|
|
3285
|
+
scaleWritten.set(key, stripQuotes(raw));
|
|
3286
|
+
unknownKeys.push({ key, line: ln.no });
|
|
3287
|
+
continue;
|
|
3288
|
+
}
|
|
3289
|
+
if (!raw) continue;
|
|
3290
|
+
if (COLOR_KEYS.has(key)) {
|
|
3291
|
+
const { tone, hex } = splitColorValue(raw);
|
|
3292
|
+
if (tone) out.tone = tone;
|
|
3293
|
+
if (hex) out.colorHex = hex;
|
|
3294
|
+
continue;
|
|
3295
|
+
}
|
|
3296
|
+
switch (key) {
|
|
3297
|
+
case "kind":
|
|
3298
|
+
case "\u7A2E\u985E": {
|
|
3299
|
+
const k = stripQuotes(raw).toLowerCase();
|
|
3300
|
+
const isPart = k !== "" && !NODE_KIND_VALID.has(k);
|
|
3301
|
+
out.kind = isPart ? NODE_KIND_DEFAULT : resolveKind(k);
|
|
3302
|
+
out.kindWritten = k !== "" && !isPart;
|
|
3303
|
+
out.partId = isPart ? k : void 0;
|
|
3304
|
+
break;
|
|
3305
|
+
}
|
|
3306
|
+
case "subtitle":
|
|
3307
|
+
case "\u88DC\u8DB3":
|
|
3308
|
+
out.subtitle = stripQuotes(raw);
|
|
3309
|
+
break;
|
|
3310
|
+
case "value":
|
|
3311
|
+
case "\u5024":
|
|
3312
|
+
out.value = stripQuotes(raw);
|
|
3313
|
+
break;
|
|
3314
|
+
case "rows":
|
|
3315
|
+
case "\u884C":
|
|
3316
|
+
out.rows = raw.replace(/^\[|\]$/g, "").split(/,(?![^[]*\])/).map((x) => stripQuotes(x.trim())).filter(Boolean);
|
|
3317
|
+
break;
|
|
3318
|
+
case "\u4F4D\u7F6E":
|
|
3319
|
+
case "pos": {
|
|
3320
|
+
const value = stripQuotes(raw);
|
|
3321
|
+
const m = value.match(/^(-?\d+(?:\.\d+)?)\s*[,、]\s*(-?\d+(?:\.\d+)?)$/);
|
|
3322
|
+
if (m) {
|
|
3323
|
+
out.posX = Number(m[1]);
|
|
3324
|
+
out.posY = Number(m[2]);
|
|
3325
|
+
out.posRel = void 0;
|
|
3326
|
+
break;
|
|
3327
|
+
}
|
|
3328
|
+
const rel = parseRelativePos(value);
|
|
3329
|
+
if (rel) {
|
|
3330
|
+
out.posRel = rel;
|
|
3331
|
+
out.posX = void 0;
|
|
3332
|
+
out.posY = void 0;
|
|
3333
|
+
break;
|
|
3334
|
+
}
|
|
3335
|
+
const negative = /^(.+?)\s*(?:の\s*(?:右|左|上|下)|\s(?:right|left|above|below))\s*-\s*[\d.]/i.test(value);
|
|
3336
|
+
errors.push({
|
|
3337
|
+
line: ln.no,
|
|
3338
|
+
message: negative ? `\u9593\u9694\u306B\u8CA0\u306E\u6570\u306F\u66F8\u3051\u307E\u305B\u3093: "${value}"` : `\u4F4D\u7F6E\u306E\u66F8\u304D\u65B9\u304C\u8AAD\u3081\u307E\u305B\u3093: "${value}"`,
|
|
3339
|
+
hint: negative ? "\u5411\u304D\u3092\u5909\u3048\u305F\u3044\u6642\u306F `\u53F3` / `\u5DE6` / `\u4E0A` / `\u4E0B` \u3092\u66F8\u304D\u63DB\u3048\u308B" : "`\u4F4D\u7F6E: 300,200` (\u5EA7\u6A19) \u304B `\u4F4D\u7F6E: Web \u306E\u53F3 200` (\u4ED6\u306E\u767B\u5834\u4EBA\u7269\u304B\u3089\u306E\u76F8\u5BFE)"
|
|
3340
|
+
});
|
|
3341
|
+
break;
|
|
3342
|
+
}
|
|
3343
|
+
case "posX":
|
|
3344
|
+
out.posX = numberOrUndef(raw);
|
|
3345
|
+
break;
|
|
3346
|
+
case "posY":
|
|
3347
|
+
out.posY = numberOrUndef(raw);
|
|
3348
|
+
break;
|
|
3349
|
+
case "\u5927\u304D\u3055":
|
|
3350
|
+
case "size": {
|
|
3351
|
+
const value = stripQuotes(raw);
|
|
3352
|
+
const m = value.match(/^(-?\d+(?:\.\d+)?)\s*[,、]\s*(-?\d+(?:\.\d+)?)$/);
|
|
3353
|
+
if (m) {
|
|
3354
|
+
out.posW = Number(m[1]);
|
|
3355
|
+
out.posH = Number(m[2]);
|
|
3356
|
+
break;
|
|
3357
|
+
}
|
|
3358
|
+
errors.push({
|
|
3359
|
+
line: ln.no,
|
|
3360
|
+
message: `\u5927\u304D\u3055\u306E\u66F8\u304D\u65B9\u304C\u8AAD\u3081\u307E\u305B\u3093: "${value}"`,
|
|
3361
|
+
hint: "`\u5927\u304D\u3055: 400,200` (\u5E45, \u9AD8\u3055) \u306E\u5F62\u3067\u66F8\u304F"
|
|
3362
|
+
});
|
|
3363
|
+
break;
|
|
3364
|
+
}
|
|
3365
|
+
case "lane":
|
|
3366
|
+
out.lane = stripQuotes(raw);
|
|
3367
|
+
break;
|
|
3368
|
+
case "stack":
|
|
3369
|
+
out.stack = numberOrUndef(raw);
|
|
3370
|
+
break;
|
|
3371
|
+
default:
|
|
3372
|
+
state[key] = coerceStateValue(stripQuotes(raw));
|
|
3373
|
+
touchedState = true;
|
|
3374
|
+
unknownKeys.push({ key, line: ln.no });
|
|
3375
|
+
break;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
if (scaleWritten.size > 0) {
|
|
3379
|
+
const s = resolveScale(scaleWritten);
|
|
3380
|
+
out.scale = s.scale;
|
|
3381
|
+
out.scaleKeys = [.../* @__PURE__ */ new Set([...actor.scaleKeys ?? [], ...s.keys])];
|
|
3382
|
+
}
|
|
3383
|
+
if (out.partId !== void 0) {
|
|
3384
|
+
if (touchedState) out.stateOverride = state;
|
|
3385
|
+
return out;
|
|
3386
|
+
}
|
|
3387
|
+
for (const u of unknownKeys) {
|
|
3388
|
+
errors.push({
|
|
3389
|
+
line: u.line,
|
|
3390
|
+
message: `\u9805\u76EE\u540D\u304C\u8AAD\u3081\u307E\u305B\u3093: "${u.key}"`,
|
|
3391
|
+
hint: `\u4F7F\u3048\u308B\u9805\u76EE = ${[...ACTOR_ITEM_KEYS].join(", ")}`
|
|
3392
|
+
});
|
|
3393
|
+
}
|
|
3394
|
+
return out;
|
|
3395
|
+
}
|
|
3396
|
+
var ACTOR_ITEM_KEYS = /* @__PURE__ */ new Set([
|
|
3397
|
+
...COLOR_KEYS,
|
|
3398
|
+
"kind",
|
|
3399
|
+
"\u7A2E\u985E",
|
|
3400
|
+
"subtitle",
|
|
3401
|
+
"\u88DC\u8DB3",
|
|
3402
|
+
"value",
|
|
3403
|
+
"\u5024",
|
|
3404
|
+
"rows",
|
|
3405
|
+
"\u884C",
|
|
3406
|
+
"\u4F4D\u7F6E",
|
|
3407
|
+
"pos",
|
|
3408
|
+
"posX",
|
|
3409
|
+
"posY",
|
|
3410
|
+
"\u5927\u304D\u3055",
|
|
3411
|
+
"size",
|
|
3412
|
+
"\u500D\u7387",
|
|
3413
|
+
"scale",
|
|
3414
|
+
"lane",
|
|
3415
|
+
"stack"
|
|
3416
|
+
]);
|
|
3417
|
+
function validateRelativePositions(actors, errors) {
|
|
3418
|
+
const named = new Set(actors.map((a) => a.name));
|
|
3419
|
+
const broken = /* @__PURE__ */ new Set();
|
|
3420
|
+
for (const a of actors) {
|
|
3421
|
+
const rel = a.posRel;
|
|
3422
|
+
if (!rel) continue;
|
|
3423
|
+
if (rel.anchor === a.name) {
|
|
3424
|
+
errors.push({
|
|
3425
|
+
line: a.pos.line,
|
|
3426
|
+
message: `\u4F4D\u7F6E\u306E\u57FA\u6E96\u304C\u81EA\u5206\u81EA\u8EAB\u3067\u3059: "${a.name}"`,
|
|
3427
|
+
hint: "\u5225\u306E\u767B\u5834\u4EBA\u7269\u306E\u540D\u524D\u3092\u66F8\u304F"
|
|
3428
|
+
});
|
|
3429
|
+
broken.add(a.name);
|
|
3430
|
+
continue;
|
|
3431
|
+
}
|
|
3432
|
+
if (!named.has(rel.anchor)) {
|
|
3433
|
+
errors.push({
|
|
3434
|
+
line: a.pos.line,
|
|
3435
|
+
message: `\u4F4D\u7F6E\u306E\u57FA\u6E96\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: "${rel.anchor}"`,
|
|
3436
|
+
hint: named.size > 0 ? `actors: \u306B\u66F8\u304B\u308C\u3066\u3044\u308B\u540D\u524D = ${[...named].join(", ")}` : "actors: \u306B\u57FA\u6E96\u306B\u3059\u308B\u767B\u5834\u4EBA\u7269\u3092\u66F8\u304F"
|
|
3437
|
+
});
|
|
3438
|
+
broken.add(a.name);
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
const { cyclic } = orderByDependency(
|
|
3442
|
+
actors.map((a) => ({ name: a.name, rel: broken.has(a.name) ? void 0 : a.posRel }))
|
|
3443
|
+
);
|
|
3444
|
+
for (const name of cyclic) {
|
|
3445
|
+
const a = actors.find((x) => x.name === name);
|
|
3446
|
+
errors.push({
|
|
3447
|
+
line: a?.pos.line ?? 1,
|
|
3448
|
+
message: `\u4F4D\u7F6E\u306E\u57FA\u6E96\u304C\u4E92\u3044\u3092\u6307\u3057\u3066\u3044\u307E\u3059: "${name}"`,
|
|
3449
|
+
hint: "\u3069\u308C\u304B 1 \u3064\u306F\u5EA7\u6A19 (`\u4F4D\u7F6E: 300,200`) \u304B\u81EA\u52D5\u914D\u7F6E\u306B\u3059\u308B"
|
|
3450
|
+
});
|
|
3451
|
+
broken.add(name);
|
|
3452
|
+
}
|
|
3453
|
+
for (const a of actors) {
|
|
3454
|
+
if (broken.has(a.name)) a.posRel = void 0;
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
function collectActorEntries(lines, start, parentIndent) {
|
|
3458
|
+
const items = [];
|
|
3459
|
+
let cur = null;
|
|
3460
|
+
let headIndent = -1;
|
|
3461
|
+
let i = start;
|
|
3462
|
+
while (i < lines.length) {
|
|
3463
|
+
const ln = lines[i];
|
|
3464
|
+
if (!ln.trimmed) {
|
|
3465
|
+
i += 1;
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3468
|
+
if (ln.indent <= parentIndent) break;
|
|
3469
|
+
if (ln.trimmed.startsWith("- ")) {
|
|
3470
|
+
if (cur) items.push(cur);
|
|
3471
|
+
cur = [{ ...ln, trimmed: ln.trimmed.slice(2).trim() }];
|
|
3472
|
+
headIndent = ln.indent;
|
|
3473
|
+
} else if (cur && ln.indent > headIndent) {
|
|
3474
|
+
cur.push(ln);
|
|
3475
|
+
}
|
|
3476
|
+
i += 1;
|
|
3477
|
+
}
|
|
3478
|
+
if (cur) items.push(cur);
|
|
3479
|
+
return { items, next: i };
|
|
3480
|
+
}
|
|
3481
|
+
function collectAnimationSteps(lines, start, parentIndent) {
|
|
3482
|
+
const out = [];
|
|
3483
|
+
let i = start;
|
|
3484
|
+
let cur = null;
|
|
3485
|
+
while (i < lines.length) {
|
|
3486
|
+
const ln = lines[i];
|
|
3487
|
+
if (!ln.trimmed) {
|
|
3488
|
+
i += 1;
|
|
3489
|
+
continue;
|
|
3490
|
+
}
|
|
3491
|
+
if (ln.indent <= parentIndent) break;
|
|
3492
|
+
if (ln.trimmed.startsWith("- step")) {
|
|
3493
|
+
if (cur) out.push(cur);
|
|
3494
|
+
cur = [{ ...ln, trimmed: ln.trimmed.slice(2).trim() }];
|
|
3495
|
+
} else if (cur) {
|
|
3496
|
+
cur.push(ln);
|
|
3497
|
+
}
|
|
3498
|
+
i += 1;
|
|
3499
|
+
}
|
|
3500
|
+
if (cur) out.push(cur);
|
|
3501
|
+
return { items: out, next: i };
|
|
3502
|
+
}
|
|
3503
|
+
var ACTOR_RESERVED_FIELDS = /* @__PURE__ */ new Set([
|
|
3504
|
+
"kind",
|
|
3505
|
+
"subtitle",
|
|
3506
|
+
"eyebrow",
|
|
3507
|
+
"value",
|
|
3508
|
+
"rows",
|
|
3509
|
+
"lane",
|
|
3510
|
+
"stack",
|
|
3511
|
+
"initial",
|
|
3512
|
+
"final",
|
|
3513
|
+
"state",
|
|
3514
|
+
// canvas pivot 新 spec = 絶対座標 4 field (dragon canvas pivot spec §layout-role-conversion)
|
|
3515
|
+
"posX",
|
|
3516
|
+
"posY",
|
|
3517
|
+
"posW",
|
|
3518
|
+
"posH",
|
|
3519
|
+
// canvas pivot UX 修正 (B1) = sub-node 単位 override map (nested `nodes: { header: {...} }`)
|
|
3520
|
+
"nodes",
|
|
3521
|
+
// 図形の倍率 (#1026)。 状態の名前としては読まない
|
|
3522
|
+
"scale",
|
|
3523
|
+
"\u500D\u7387",
|
|
3524
|
+
// 日本語の項目名 (#1026)。 中括弧の形が日本語の項目名を読めるようになったため、
|
|
3525
|
+
// ここに載せないと状態の名前として拾われる。 縦に並べた形での意味 (位置 / 大きさ 等) は
|
|
3526
|
+
// 中括弧の形では未対応なので、これまでどおり落とす方に揃える
|
|
3527
|
+
"\u7A2E\u985E",
|
|
3528
|
+
"\u88DC\u8DB3",
|
|
3529
|
+
"\u5024",
|
|
3530
|
+
"\u884C",
|
|
3531
|
+
"\u4F4D\u7F6E",
|
|
3532
|
+
"\u5927\u304D\u3055",
|
|
3533
|
+
"\u8272"
|
|
3534
|
+
]);
|
|
3535
|
+
function extractStateOverride(opts) {
|
|
3536
|
+
const out = {};
|
|
3537
|
+
let count = 0;
|
|
3538
|
+
const explicit = opts.state;
|
|
3539
|
+
if (explicit && explicit.startsWith("{") && explicit.endsWith("}")) {
|
|
3540
|
+
const inner = parseInlineMapping(explicit.slice(1, -1));
|
|
3541
|
+
for (const [k, v] of Object.entries(inner)) {
|
|
3542
|
+
out[k] = coerceStateValue(v);
|
|
3543
|
+
count += 1;
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
for (const [k, v] of Object.entries(opts)) {
|
|
3547
|
+
if (ACTOR_RESERVED_FIELDS.has(k)) continue;
|
|
3548
|
+
if (k in out) continue;
|
|
3549
|
+
out[k] = coerceStateValue(v);
|
|
3550
|
+
count += 1;
|
|
3551
|
+
}
|
|
3552
|
+
return count > 0 ? out : void 0;
|
|
3553
|
+
}
|
|
3554
|
+
function parseActorNodesField(raw) {
|
|
3555
|
+
if (!raw) return void 0;
|
|
3556
|
+
const trimmed = raw.trim();
|
|
3557
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return void 0;
|
|
3558
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
3559
|
+
if (!inner) return void 0;
|
|
3560
|
+
const parts = [];
|
|
3561
|
+
let depth = 0;
|
|
3562
|
+
let buf = "";
|
|
3563
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
3564
|
+
const c = inner[i];
|
|
3565
|
+
if (c === "[" || c === "{") depth += 1;
|
|
3566
|
+
else if (c === "]" || c === "}") depth -= 1;
|
|
3567
|
+
if (c === "," && depth === 0) {
|
|
3568
|
+
parts.push(buf);
|
|
3569
|
+
buf = "";
|
|
3570
|
+
continue;
|
|
3571
|
+
}
|
|
3572
|
+
buf += c;
|
|
3573
|
+
}
|
|
3574
|
+
if (buf.trim()) parts.push(buf);
|
|
3575
|
+
const out = {};
|
|
3576
|
+
for (const p of parts) {
|
|
3577
|
+
const colonIdx = p.indexOf(":");
|
|
3578
|
+
if (colonIdx < 0) continue;
|
|
3579
|
+
const key = p.slice(0, colonIdx).trim();
|
|
3580
|
+
const val = p.slice(colonIdx + 1).trim();
|
|
3581
|
+
if (!key || !val.startsWith("{") || !val.endsWith("}")) continue;
|
|
3582
|
+
const nodeOpts = parseInlineMapping(val.slice(1, -1));
|
|
3583
|
+
out[key] = {
|
|
3584
|
+
posX: numberOrUndef(nodeOpts.posX),
|
|
3585
|
+
posY: numberOrUndef(nodeOpts.posY),
|
|
3586
|
+
posW: numberOrUndef(nodeOpts.posW),
|
|
3587
|
+
posH: numberOrUndef(nodeOpts.posH)
|
|
3588
|
+
};
|
|
3589
|
+
}
|
|
3590
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
3591
|
+
}
|
|
3592
|
+
function coerceStateValue(raw) {
|
|
3593
|
+
const stripped = stripQuotes(raw);
|
|
3594
|
+
if (stripped === "true") return true;
|
|
3595
|
+
if (stripped === "false") return false;
|
|
3596
|
+
const n = Number(stripped);
|
|
3597
|
+
if (Number.isFinite(n) && stripped !== "" && !isNaN(n)) return n;
|
|
3598
|
+
return stripped;
|
|
3599
|
+
}
|
|
3600
|
+
function reportScaleOnNonPart(isPart, key, line, errors) {
|
|
3601
|
+
if (isPart || key === void 0) return;
|
|
3602
|
+
errors.push({
|
|
3603
|
+
line,
|
|
3604
|
+
message: `\u9805\u76EE\u540D\u304C\u8AAD\u3081\u307E\u305B\u3093: "${key}"`,
|
|
3605
|
+
hint: `\u4F7F\u3048\u308B\u9805\u76EE = ${[...ACTOR_ITEM_KEYS].join(", ")}`
|
|
3606
|
+
});
|
|
3607
|
+
}
|
|
3608
|
+
var INLINE_ACTOR_KEYS = /* @__PURE__ */ new Set([
|
|
3609
|
+
"kind",
|
|
3610
|
+
"subtitle",
|
|
3611
|
+
"eyebrow",
|
|
3612
|
+
"value",
|
|
3613
|
+
"rows",
|
|
3614
|
+
"lane",
|
|
3615
|
+
"stack",
|
|
3616
|
+
"initial",
|
|
3617
|
+
"final",
|
|
3618
|
+
"tone",
|
|
3619
|
+
"nodes",
|
|
3620
|
+
"posX",
|
|
3621
|
+
"posY",
|
|
3622
|
+
"posW",
|
|
3623
|
+
"posH",
|
|
3624
|
+
// 倍率は別経路 (`reportScaleOnNonPart`) が知らせる。 ここでも読める扱いにしないと
|
|
3625
|
+
// 同じ名前で 2 度知らせることになる
|
|
3626
|
+
"scale",
|
|
3627
|
+
"\u500D\u7387"
|
|
3628
|
+
]);
|
|
3629
|
+
function reportUnknownInlineKeys(isPart, inner, line, errors) {
|
|
3630
|
+
if (isPart) return;
|
|
3631
|
+
for (const field of splitInlineFields(inner)) {
|
|
3632
|
+
const idx = field.indexOf(":");
|
|
3633
|
+
if (idx < 0) continue;
|
|
3634
|
+
const key = field.slice(0, idx).trim();
|
|
3635
|
+
if (!key) continue;
|
|
3636
|
+
if (INLINE_ACTOR_KEYS.has(key)) continue;
|
|
3637
|
+
errors.push({
|
|
3638
|
+
line,
|
|
3639
|
+
message: `\u9805\u76EE\u540D\u304C\u8AAD\u3081\u307E\u305B\u3093: "${key}"`,
|
|
3640
|
+
hint: `\u4F7F\u3048\u308B\u9805\u76EE = ${[...INLINE_ACTOR_KEYS].join(", ")}`
|
|
3641
|
+
});
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
function parseActor2(line, errors) {
|
|
3645
|
+
const raw = line.trimmed.trim();
|
|
3646
|
+
if (!raw) return null;
|
|
3647
|
+
const mapMatch = matchActorInlineMapping(raw);
|
|
3648
|
+
if (mapMatch) {
|
|
3649
|
+
const namePart2 = stripQuotes(mapMatch.name.trim());
|
|
3650
|
+
if (!namePart2) return null;
|
|
3651
|
+
const opts = parseInlineMapping(mapMatch.inner);
|
|
3652
|
+
const kindRaw = (opts.kind ?? "").toLowerCase();
|
|
3653
|
+
const isPart = kindRaw !== "" && !NODE_KIND_VALID.has(kindRaw);
|
|
3654
|
+
const inlineScale = resolveScale(writtenScaleFields(mapMatch.inner));
|
|
3655
|
+
reportScaleOnNonPart(isPart, inlineScale.keys[0], line.no, errors);
|
|
3656
|
+
reportUnknownInlineKeys(isPart, mapMatch.inner, line.no, errors);
|
|
3657
|
+
const kind = isPart ? NODE_KIND_DEFAULT : resolveKind(NODE_KIND_VALID.has(kindRaw) ? kindRaw : "");
|
|
3658
|
+
return {
|
|
3659
|
+
name: namePart2,
|
|
3660
|
+
kind,
|
|
3661
|
+
// parts 候補は `kind` を既定に倒して `partId` へ退避するため、 名札に載せる種類としては
|
|
3662
|
+
// 「書かなかった」 と同じ扱いにする (#1058)
|
|
3663
|
+
kindWritten: kindRaw !== "" && !isPart,
|
|
3664
|
+
subtitle: opts.subtitle,
|
|
3665
|
+
eyebrow: opts.eyebrow,
|
|
3666
|
+
value: opts.value,
|
|
3667
|
+
rows: opts.rows ? opts.rows.replace(/^\[|\]$/g, "").split(/,(?![^[]*\])/).map((x) => stripQuotes(x.trim())).filter(Boolean) : void 0,
|
|
3668
|
+
lane: opts.lane,
|
|
3669
|
+
stack: numberOrUndef(opts.stack),
|
|
3670
|
+
initial: boolOrUndef(opts.initial),
|
|
3671
|
+
final: boolOrUndef(opts.final),
|
|
3672
|
+
// parts では `tone` を状態の上書きとして従来から使えるため、 色として横取りしない
|
|
3673
|
+
tone: isPart ? void 0 : toneOrUndef(opts.tone),
|
|
3674
|
+
partId: isPart ? kindRaw : void 0,
|
|
3675
|
+
stateOverride: isPart ? extractStateOverride(opts) : void 0,
|
|
3676
|
+
// canvas pivot 新 spec = 絶対座標 field を actor に格納、 compile 経由で CDL に受け渡す
|
|
3677
|
+
posX: numberOrUndef(opts.posX),
|
|
3678
|
+
posY: numberOrUndef(opts.posY),
|
|
3679
|
+
posW: numberOrUndef(opts.posW),
|
|
3680
|
+
posH: numberOrUndef(opts.posH),
|
|
3681
|
+
// 図形の倍率 (#1026)。 どれが効くかは `resolveScale` が 1 箇所で決める
|
|
3682
|
+
scale: inlineScale.scale,
|
|
3683
|
+
scaleKeys: inlineScale.keys.length ? inlineScale.keys : void 0,
|
|
3684
|
+
// canvas pivot UX 修正 (B1) = sub-node 単位 override map (`nodes: { header: {posX:..., ...}, ...}`)
|
|
3685
|
+
nodes: parseActorNodesField(opts.nodes),
|
|
3686
|
+
pos: { line: line.no }
|
|
3687
|
+
};
|
|
3688
|
+
}
|
|
3689
|
+
if (lastTopLevelColon(raw) >= 0) {
|
|
3690
|
+
const idx = lastTopLevelColon(raw);
|
|
3691
|
+
const namePart2 = stripQuotes(raw.slice(0, idx).trim());
|
|
3692
|
+
const rest = raw.slice(idx + 1).trim();
|
|
3693
|
+
if (!namePart2) return null;
|
|
3694
|
+
const v = classifyValues(splitValues(rest));
|
|
3695
|
+
const isPart = v.kind !== "" && !NODE_KIND_VALID.has(v.kind);
|
|
3696
|
+
reportScaleOnNonPart(isPart, v.scaleKeys?.[0], line.no, errors);
|
|
3697
|
+
const kind = isPart ? NODE_KIND_DEFAULT : resolveKind(NODE_KIND_VALID.has(v.kind) ? v.kind : "");
|
|
3698
|
+
return {
|
|
3699
|
+
name: namePart2,
|
|
3700
|
+
kind,
|
|
3701
|
+
// parts 候補は `kind` を既定に倒して `partId` へ退避するため、 名札に載せる種類としては
|
|
3702
|
+
// 「書かなかった」 と同じ扱いにする (#1058)
|
|
3703
|
+
kindWritten: v.kind !== "" && !isPart,
|
|
3704
|
+
// parts では `tone` を状態の上書きとして扱うため、 色として渡さない
|
|
3705
|
+
tone: isPart ? void 0 : v.tone,
|
|
3706
|
+
subtitle: v.subtitle,
|
|
3707
|
+
rows: v.rows,
|
|
3708
|
+
value: v.value,
|
|
3709
|
+
posX: v.posX,
|
|
3710
|
+
posY: v.posY,
|
|
3711
|
+
scale: v.scale,
|
|
3712
|
+
scaleKeys: v.scaleKeys?.length ? v.scaleKeys : void 0,
|
|
3713
|
+
partId: isPart ? v.kind : void 0,
|
|
3714
|
+
stateOverride: isPart ? v.state : void 0,
|
|
3715
|
+
pos: { line: line.no }
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
const namePart = stripQuotes(raw);
|
|
3719
|
+
if (!namePart) return null;
|
|
3720
|
+
return { name: namePart, kind: NODE_KIND_DEFAULT, kindWritten: false, pos: { line: line.no } };
|
|
3721
|
+
}
|
|
3722
|
+
function parseFlowStep(line, no) {
|
|
3723
|
+
const raw = line.trimmed;
|
|
3724
|
+
const arrowIdx = raw.indexOf("->");
|
|
3725
|
+
if (arrowIdx < 0) return null;
|
|
3726
|
+
const from = raw.slice(0, arrowIdx).trim();
|
|
3727
|
+
let rest = raw.slice(arrowIdx + 2).trim();
|
|
3728
|
+
let label = "";
|
|
3729
|
+
let tone;
|
|
3730
|
+
let style;
|
|
3731
|
+
let sub;
|
|
3732
|
+
let guard;
|
|
3733
|
+
let cardinality;
|
|
3734
|
+
let labelOffsetX;
|
|
3735
|
+
let labelOffsetY;
|
|
3736
|
+
const mapMatch = rest.match(/\s*\{([^}]*)\}\s*$/);
|
|
3737
|
+
if (mapMatch) {
|
|
3738
|
+
const opts = parseInlineMapping(mapMatch[1]);
|
|
3739
|
+
sub = opts.sub;
|
|
3740
|
+
guard = opts.guard;
|
|
3741
|
+
cardinality = opts.cardinality;
|
|
3742
|
+
labelOffsetX = numberOrUndef(opts.labelOffsetX);
|
|
3743
|
+
labelOffsetY = numberOrUndef(opts.labelOffsetY);
|
|
3744
|
+
rest = rest.slice(0, mapMatch.index ?? 0).trim();
|
|
3745
|
+
}
|
|
3746
|
+
const optMatch = rest.match(/\s*\(([^)]*)\)\s*$/);
|
|
3747
|
+
if (optMatch) {
|
|
3748
|
+
const opts = (optMatch[1] ?? "").split(",").map((s) => s.trim());
|
|
3749
|
+
for (const opt of opts) {
|
|
3750
|
+
const resolvedTone = toneOrUndef(opt);
|
|
3751
|
+
if (resolvedTone !== void 0) tone = resolvedTone;
|
|
3752
|
+
else if (STYLE_VALID.has(opt.toLowerCase())) style = opt.toLowerCase();
|
|
3753
|
+
}
|
|
3754
|
+
rest = rest.slice(0, optMatch.index ?? 0).trim();
|
|
3755
|
+
} else {
|
|
3756
|
+
const words = splitValues(rest);
|
|
3757
|
+
while (words.length > 1) {
|
|
3758
|
+
const last = words[words.length - 1];
|
|
3759
|
+
if (last.startsWith('"') || last.startsWith("'")) break;
|
|
3760
|
+
const resolvedTone = toneOrUndef(last);
|
|
3761
|
+
if (resolvedTone !== void 0) {
|
|
3762
|
+
tone = resolvedTone;
|
|
3763
|
+
words.pop();
|
|
3764
|
+
continue;
|
|
3765
|
+
}
|
|
3766
|
+
if (STYLE_VALID.has(last.toLowerCase())) {
|
|
3767
|
+
style = last.toLowerCase();
|
|
3768
|
+
words.pop();
|
|
3769
|
+
continue;
|
|
3770
|
+
}
|
|
3771
|
+
break;
|
|
3772
|
+
}
|
|
3773
|
+
rest = words.join(" ");
|
|
3774
|
+
}
|
|
3775
|
+
let to = rest;
|
|
3776
|
+
const labelMatch = rest.match(/^(.+?):\s*(.+)$/);
|
|
3777
|
+
if (labelMatch) {
|
|
3778
|
+
to = (labelMatch[1] ?? "").trim();
|
|
3779
|
+
label = stripQuotes((labelMatch[2] ?? "").trim());
|
|
3780
|
+
}
|
|
3781
|
+
if (!from || !to) return null;
|
|
3782
|
+
return {
|
|
3783
|
+
no,
|
|
3784
|
+
from: stripQuotes(from),
|
|
3785
|
+
to: stripQuotes(to),
|
|
3786
|
+
label,
|
|
3787
|
+
tone,
|
|
3788
|
+
style,
|
|
3789
|
+
sub,
|
|
3790
|
+
guard,
|
|
3791
|
+
cardinality,
|
|
3792
|
+
labelOffsetX,
|
|
3793
|
+
labelOffsetY,
|
|
3794
|
+
pos: { line: line.no }
|
|
3795
|
+
};
|
|
3796
|
+
}
|
|
3797
|
+
function parseStateEntry(text, lineNo) {
|
|
3798
|
+
const m = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.+)$/);
|
|
3799
|
+
if (!m) return null;
|
|
3800
|
+
const name = m[1] ?? "";
|
|
3801
|
+
const raw = (m[2] ?? "").trim();
|
|
3802
|
+
const stripped = stripQuotes(raw);
|
|
3803
|
+
const asNum = Number(stripped);
|
|
3804
|
+
const initial = Number.isFinite(asNum) && stripped !== "" && !isNaN(asNum) ? asNum : stripped;
|
|
3805
|
+
return { name, initial, pos: { line: lineNo } };
|
|
3806
|
+
}
|
|
3807
|
+
function splitTopLevelCommas(s) {
|
|
3808
|
+
return s.split(",").map((x) => x.trim()).filter(Boolean);
|
|
3809
|
+
}
|
|
3810
|
+
function ensureAnimate(a, lineNo) {
|
|
3811
|
+
if (a) return a;
|
|
3812
|
+
return { states: [], phases: [], pos: { line: lineNo } };
|
|
3813
|
+
}
|
|
3814
|
+
function parsePhase(block, errors) {
|
|
3815
|
+
const head = block[0];
|
|
3816
|
+
const m = head.trimmed.match(/^step\s*:\s*(.+)$/);
|
|
3817
|
+
if (!m) {
|
|
3818
|
+
errors.push({ line: head.no, message: `invalid step header: "${head.trimmed}"`, hint: 'use `- step: "name" 1.5s`' });
|
|
3819
|
+
return null;
|
|
3820
|
+
}
|
|
3821
|
+
const headRest = (m[1] ?? "").trim();
|
|
3822
|
+
const headParse = parseStepHead(headRest);
|
|
3823
|
+
if (!headParse) {
|
|
3824
|
+
errors.push({ line: head.no, message: `invalid step value: "${headRest}"`, hint: 'use `"name" 1.5s` (duration in s)' });
|
|
3825
|
+
return null;
|
|
3826
|
+
}
|
|
3827
|
+
const phase = {
|
|
3828
|
+
name: headParse.name,
|
|
3829
|
+
durationMs: headParse.durationMs,
|
|
3830
|
+
pos: { line: head.no },
|
|
3831
|
+
highlight: [],
|
|
3832
|
+
tweens: [],
|
|
3833
|
+
sets: []
|
|
3834
|
+
};
|
|
3835
|
+
let i = 1;
|
|
3836
|
+
while (i < block.length) {
|
|
3837
|
+
const ln = block[i];
|
|
3838
|
+
const t = ln.trimmed;
|
|
3839
|
+
const propMatch = t.match(/^([a-zA-Z][a-zA-Z0-9_]*)\s*:\s*(.*)$/);
|
|
3840
|
+
if (!propMatch) {
|
|
3841
|
+
i += 1;
|
|
3842
|
+
continue;
|
|
3843
|
+
}
|
|
3844
|
+
const key = (propMatch[1] ?? "").toLowerCase();
|
|
3845
|
+
const value = (propMatch[2] ?? "").trim();
|
|
3846
|
+
if (key === "focus") {
|
|
3847
|
+
phase.highlight = parseFocusList(value);
|
|
3848
|
+
i += 1;
|
|
3849
|
+
continue;
|
|
3850
|
+
}
|
|
3851
|
+
if (key === "badge") {
|
|
3852
|
+
phase.badge = stripQuotes(value);
|
|
3853
|
+
i += 1;
|
|
3854
|
+
continue;
|
|
3855
|
+
}
|
|
3856
|
+
if (key === "description" || key === "body") {
|
|
3857
|
+
phase.body = stripQuotes(value);
|
|
3858
|
+
i += 1;
|
|
3859
|
+
continue;
|
|
3860
|
+
}
|
|
3861
|
+
if (key === "tween") {
|
|
3862
|
+
if (value) {
|
|
3863
|
+
const tw = parseTweenLine(value, ln.no);
|
|
3864
|
+
if (tw) phase.tweens.push(tw);
|
|
3865
|
+
else errors.push({ line: ln.no, message: `invalid tween: "${value}"`, hint: "use `tween: name 100 -> 90`" });
|
|
3866
|
+
i += 1;
|
|
3867
|
+
continue;
|
|
3868
|
+
}
|
|
3869
|
+
const baseIndent = ln.indent;
|
|
3870
|
+
let j = i + 1;
|
|
3871
|
+
while (j < block.length) {
|
|
3872
|
+
const nx = block[j];
|
|
3873
|
+
if (nx.indent <= baseIndent) break;
|
|
3874
|
+
const tw = parseTweenLine(nx.trimmed, nx.no);
|
|
3875
|
+
if (tw) phase.tweens.push(tw);
|
|
3876
|
+
else errors.push({ line: nx.no, message: `invalid tween entry: "${nx.trimmed}"`, hint: "use `name: 100 -> 90`" });
|
|
3877
|
+
j += 1;
|
|
3878
|
+
}
|
|
3879
|
+
i = j;
|
|
3880
|
+
continue;
|
|
3881
|
+
}
|
|
3882
|
+
if (key === "set") {
|
|
3883
|
+
if (value) {
|
|
3884
|
+
const st = parseSetLine(value, ln.no);
|
|
3885
|
+
if (st) phase.sets.push(st);
|
|
3886
|
+
i += 1;
|
|
3887
|
+
continue;
|
|
3888
|
+
}
|
|
3889
|
+
const baseIndent = ln.indent;
|
|
3890
|
+
let j = i + 1;
|
|
3891
|
+
while (j < block.length) {
|
|
3892
|
+
const nx = block[j];
|
|
3893
|
+
if (nx.indent <= baseIndent) break;
|
|
3894
|
+
const st = parseSetLine(nx.trimmed, nx.no);
|
|
3895
|
+
if (st) phase.sets.push(st);
|
|
3896
|
+
j += 1;
|
|
3897
|
+
}
|
|
3898
|
+
i = j;
|
|
3899
|
+
continue;
|
|
3900
|
+
}
|
|
3901
|
+
i += 1;
|
|
3902
|
+
}
|
|
3903
|
+
return phase;
|
|
3904
|
+
}
|
|
3905
|
+
function parseStepHead(s) {
|
|
3906
|
+
let rest = s.trim();
|
|
3907
|
+
let name = "";
|
|
3908
|
+
if (rest.startsWith('"') || rest.startsWith("'")) {
|
|
3909
|
+
const q = rest[0] ?? '"';
|
|
3910
|
+
const end = rest.indexOf(q, 1);
|
|
3911
|
+
if (end < 0) return null;
|
|
3912
|
+
name = rest.slice(1, end);
|
|
3913
|
+
rest = rest.slice(end + 1).trim();
|
|
3914
|
+
} else {
|
|
3915
|
+
const spaceIdx = rest.indexOf(" ");
|
|
3916
|
+
if (spaceIdx < 0) return null;
|
|
3917
|
+
name = rest.slice(0, spaceIdx);
|
|
3918
|
+
rest = rest.slice(spaceIdx + 1).trim();
|
|
3919
|
+
}
|
|
3920
|
+
const dm = rest.match(/^(\d+(?:\.\d+)?)\s*(ms|s)?$/);
|
|
3921
|
+
if (!dm) return null;
|
|
3922
|
+
const n = parseFloat(dm[1] ?? "0");
|
|
3923
|
+
const unit = dm[2] ?? "s";
|
|
3924
|
+
const durationMs = unit === "ms" ? Math.round(n) : Math.round(n * 1e3);
|
|
3925
|
+
return { name, durationMs };
|
|
3926
|
+
}
|
|
3927
|
+
function parseFocusList(s) {
|
|
3928
|
+
let body = s.trim();
|
|
3929
|
+
if (body.startsWith("[") && body.endsWith("]")) body = body.slice(1, -1);
|
|
3930
|
+
const parts = [];
|
|
3931
|
+
let buf = "";
|
|
3932
|
+
let quote = null;
|
|
3933
|
+
for (const ch of body) {
|
|
3934
|
+
if (quote) {
|
|
3935
|
+
if (ch === quote) {
|
|
3936
|
+
quote = null;
|
|
3937
|
+
continue;
|
|
3938
|
+
}
|
|
3939
|
+
buf += ch;
|
|
3940
|
+
continue;
|
|
3941
|
+
}
|
|
3942
|
+
if (ch === '"' || ch === "'") {
|
|
3943
|
+
quote = ch;
|
|
3944
|
+
continue;
|
|
3945
|
+
}
|
|
3946
|
+
if (ch === ",") {
|
|
3947
|
+
const t = buf.trim();
|
|
3948
|
+
if (t) parts.push(t);
|
|
3949
|
+
buf = "";
|
|
3950
|
+
continue;
|
|
3951
|
+
}
|
|
3952
|
+
buf += ch;
|
|
3953
|
+
}
|
|
3954
|
+
const tail = buf.trim();
|
|
3955
|
+
if (tail) parts.push(tail);
|
|
3956
|
+
const out = [];
|
|
3957
|
+
for (const p of parts) {
|
|
3958
|
+
if (/[-→][>]?/.test(p) && /\s/.test(p)) {
|
|
3959
|
+
out.push(p);
|
|
3960
|
+
continue;
|
|
3961
|
+
}
|
|
3962
|
+
if (/\s/.test(p)) {
|
|
3963
|
+
for (const x of p.split(/\s+/)) {
|
|
3964
|
+
if (x) out.push(x);
|
|
3965
|
+
}
|
|
3966
|
+
continue;
|
|
3967
|
+
}
|
|
3968
|
+
out.push(p);
|
|
3969
|
+
}
|
|
3970
|
+
return out;
|
|
3971
|
+
}
|
|
3972
|
+
function parseTweenLine(s, lineNo) {
|
|
3973
|
+
const cleaned = s.replace(/^-\s*/, "").trim();
|
|
3974
|
+
const m = cleaned.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*[:\s]\s*(-?\d+(?:\.\d+)?)\s*->\s*(-?\d+(?:\.\d+)?)$/);
|
|
3975
|
+
if (!m) return null;
|
|
3976
|
+
return {
|
|
3977
|
+
state: m[1] ?? "",
|
|
3978
|
+
from: parseFloat(m[2] ?? "0"),
|
|
3979
|
+
to: parseFloat(m[3] ?? "0"),
|
|
3980
|
+
pos: { line: lineNo }
|
|
3981
|
+
};
|
|
3982
|
+
}
|
|
3983
|
+
function parseSetLine(s, lineNo) {
|
|
3984
|
+
const cleaned = s.replace(/^-\s*/, "").trim();
|
|
3985
|
+
const m = cleaned.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*[:\s]\s*(.+)$/);
|
|
3986
|
+
if (!m) return null;
|
|
3987
|
+
const raw = (m[2] ?? "").trim();
|
|
3988
|
+
const stripped = stripQuotes(raw);
|
|
3989
|
+
const asNum = Number(stripped);
|
|
3990
|
+
const value = Number.isFinite(asNum) && stripped !== "" && !isNaN(asNum) ? asNum : stripped;
|
|
3991
|
+
return { state: m[1] ?? "", value, pos: { line: lineNo } };
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
// src/notation-lint.ts
|
|
3995
|
+
var REDUNDANT_TOPIC_PATTERNS = [
|
|
3996
|
+
{ pattern: /\bpreset\s*\(/i, hint: "\u300C\u301C preset (\u8A73\u7D30)\u300D \u306F\u5B9F\u88C5\u8868\u73FE\u3001 \u300C\u301C \u3092\u793A\u3059\u56F3\u300D \u306E\u3088\u3046\u306B\u8AAD\u8005\u5411\u3051\u8AAC\u660E\u306B" },
|
|
3997
|
+
{ pattern: /render\s*未実装/, hint: "\u300Crender \u672A\u5B9F\u88C5\u300D \u306F\u958B\u767A\u8005\u5411\u3051\u5185\u90E8\u30E1\u30E2\u3001 catalog \u8868\u793A\u3067\u306F\u7701\u304F" },
|
|
3998
|
+
{ pattern: /SVG\s+(polyline|arc|rect|path)/i, hint: "\u300CSVG polyline / arc / rect / path\u300D \u306F\u5B9F\u88C5\u8A73\u7D30\u3001 \u300C\u301C \u3092\u793A\u3059\u56F3\u300D \u306B\u7F6E\u63DB" },
|
|
3999
|
+
{ pattern: /\bpolygon\b/i, hint: "\u300Cpolygon\u300D \u306F\u5B9F\u88C5\u7528\u8A9E\u3001 \u56F3\u306E\u610F\u5473\u3092\u8AAC\u660E\u3059\u308B\u81EA\u7136\u6587\u306B\u7F6E\u63DB" }
|
|
4000
|
+
];
|
|
4001
|
+
function lintDiagram(d) {
|
|
4002
|
+
const issues = [];
|
|
4003
|
+
issues.push(...ruleTopicRedundancy(d));
|
|
4004
|
+
issues.push(...ruleEmptyChartData(d));
|
|
4005
|
+
issues.push(...ruleGanttUnknownDependsOn(d));
|
|
4006
|
+
issues.push(...ruleMindMapParentReference(d));
|
|
4007
|
+
issues.push(...ruleTreeParentReference(d));
|
|
4008
|
+
issues.push(...ruleQuadrantMissingItems(d));
|
|
4009
|
+
issues.push(...ruleFunnelMonotonicCount(d));
|
|
4010
|
+
return {
|
|
4011
|
+
diagramId: d.id,
|
|
4012
|
+
issues,
|
|
4013
|
+
autoFixableCount: issues.filter((i) => i.autoFixable).length
|
|
4014
|
+
};
|
|
4015
|
+
}
|
|
4016
|
+
function autoFix(d) {
|
|
4017
|
+
const patched = {
|
|
4018
|
+
...d,
|
|
4019
|
+
topic: applyTopicAutoFix(d.topic),
|
|
4020
|
+
nodes: d.nodes.map((n) => ({ ...n }))
|
|
4021
|
+
};
|
|
4022
|
+
return patched;
|
|
4023
|
+
}
|
|
4024
|
+
var KIND_TO_JA = {
|
|
4025
|
+
chart: "\u7D71\u8A08\u30C1\u30E3\u30FC\u30C8",
|
|
4026
|
+
"line chart": "\u6298\u308C\u7DDA\u30B0\u30E9\u30D5",
|
|
4027
|
+
"pie chart": "\u5186\u30B0\u30E9\u30D5",
|
|
4028
|
+
"bar chart": "\u68D2\u30B0\u30E9\u30D5",
|
|
4029
|
+
flow: "\u51E6\u7406\u306E\u6D41\u308C",
|
|
4030
|
+
swimlane: "\u30B9\u30A4\u30E0\u30EC\u30FC\u30F3 (\u5F79\u5272\u5225\u30EC\u30FC\u30F3)",
|
|
4031
|
+
sequence: "\u6642\u7CFB\u5217\u306E\u3084\u308A\u53D6\u308A",
|
|
4032
|
+
topology: "\u30B7\u30B9\u30C6\u30E0\u69CB\u6210",
|
|
4033
|
+
er: "\u30C6\u30FC\u30D6\u30EB\u95A2\u4FC2 (ER \u56F3)",
|
|
4034
|
+
stateMachine: "\u72B6\u614B\u9077\u79FB (\u30B9\u30C6\u30FC\u30C8\u56F3)",
|
|
4035
|
+
stateMachine2: "\u62E1\u5F35\u30B9\u30C6\u30FC\u30C8\u56F3 (\u968E\u5C64\u72B6\u614B)",
|
|
4036
|
+
infrastructure: "\u30AF\u30E9\u30A6\u30C9\u69CB\u6210",
|
|
4037
|
+
classDiagram: "UML \u30AF\u30E9\u30B9\u56F3",
|
|
4038
|
+
tree: "\u968E\u5C64\u30C4\u30EA\u30FC",
|
|
4039
|
+
userJourney: "\u30E6\u30FC\u30B6\u30FC\u30B8\u30E3\u30FC\u30CB\u30FC",
|
|
4040
|
+
mindMap: "\u30DE\u30A4\u30F3\u30C9\u30DE\u30C3\u30D7",
|
|
4041
|
+
mindMapRadial: "\u653E\u5C04\u72B6\u30DE\u30A4\u30F3\u30C9\u30DE\u30C3\u30D7",
|
|
4042
|
+
funnel: "\u30D5\u30A1\u30CD\u30EB (\u6BB5\u968E\u5225\u96E2\u8131)",
|
|
4043
|
+
quadrant: "\u56DB\u8C61\u9650\u30DE\u30C8\u30EA\u30AF\u30B9",
|
|
4044
|
+
gantt: "\u30AC\u30F3\u30C8\u30C1\u30E3\u30FC\u30C8",
|
|
4045
|
+
flowchart: "\u5206\u5C90\u30D5\u30ED\u30FC\u30C1\u30E3\u30FC\u30C8",
|
|
4046
|
+
network: "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u69CB\u6210"
|
|
4047
|
+
};
|
|
4048
|
+
function applyTopicAutoFix(topic) {
|
|
4049
|
+
const kindMatch = topic.match(
|
|
4050
|
+
/^\s*(chart|flow|swimlane|sequence|topology|er|stateMachine2?|infrastructure|classDiagram|tree|userJourney|mindMap(?:Radial)?|funnel|quadrant|gantt|flowchart|network|line chart|pie chart|bar chart)\b/i
|
|
4051
|
+
);
|
|
4052
|
+
if (kindMatch) {
|
|
4053
|
+
const kind = kindMatch[1].toLowerCase();
|
|
4054
|
+
const canonical = Object.keys(KIND_TO_JA).find((k) => k.toLowerCase() === kind);
|
|
4055
|
+
if (canonical) {
|
|
4056
|
+
return `${KIND_TO_JA[canonical]} \u3092\u793A\u3059\u56F3`;
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
let out = topic;
|
|
4060
|
+
out = out.replace(/\s*\([^)]*(preset|render|SVG|polygon|polyline|arc|rect|path)[^)]*\)/gi, "");
|
|
4061
|
+
out = out.replace(/\b(preset|render)\b/gi, "");
|
|
4062
|
+
out = out.replace(/\s+/g, " ").trim();
|
|
4063
|
+
if (out.length < 3) return "\u56F3\u306E\u8AAC\u660E";
|
|
4064
|
+
return out;
|
|
4065
|
+
}
|
|
4066
|
+
function ruleTopicRedundancy(d) {
|
|
4067
|
+
const out = [];
|
|
4068
|
+
for (const { pattern, hint } of REDUNDANT_TOPIC_PATTERNS) {
|
|
4069
|
+
if (pattern.test(d.topic)) {
|
|
4070
|
+
out.push({
|
|
4071
|
+
rule: "topic-redundant-implementation-detail",
|
|
4072
|
+
severity: "warn",
|
|
4073
|
+
target: d.id,
|
|
4074
|
+
message: `topic \u306B\u5B9F\u88C5\u8A73\u7D30\u304C\u542B\u307E\u308C\u308B: "${d.topic}"`,
|
|
4075
|
+
suggestion: hint,
|
|
4076
|
+
autoFixable: true
|
|
4077
|
+
});
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
return out;
|
|
4081
|
+
}
|
|
4082
|
+
function ruleEmptyChartData(d) {
|
|
4083
|
+
const out = [];
|
|
4084
|
+
for (const n of d.nodes) {
|
|
4085
|
+
if (n.kind === "chart-line" || n.kind === "chart-pie" || n.kind === "chart-bar") {
|
|
4086
|
+
const data = n.chartData ?? [];
|
|
4087
|
+
if (data.length === 0) {
|
|
4088
|
+
out.push({
|
|
4089
|
+
rule: "chart-empty-datum",
|
|
4090
|
+
severity: "warn",
|
|
4091
|
+
target: n.id,
|
|
4092
|
+
message: `chart node "${n.id}" \u304C datum 0 \u4EF6\u3001 chart \u306F\u975E\u8868\u793A\u306B\u306A\u308B`,
|
|
4093
|
+
suggestion: `.datum({ id: ..., label: ..., value: ... }) \u3092 1 \u4EF6\u4EE5\u4E0A\u8FFD\u52A0`,
|
|
4094
|
+
autoFixable: false
|
|
4095
|
+
});
|
|
4096
|
+
}
|
|
4097
|
+
if (data.length === 1) {
|
|
4098
|
+
out.push({
|
|
4099
|
+
rule: "chart-single-datum",
|
|
4100
|
+
severity: "info",
|
|
4101
|
+
target: n.id,
|
|
4102
|
+
message: `chart node "${n.id}" \u304C datum 1 \u4EF6\u3001 \u6BD4\u8F03 / \u63A8\u79FB\u3068\u3057\u3066\u610F\u5473\u304C\u8584\u3044`,
|
|
4103
|
+
suggestion: `2 \u4EF6\u4EE5\u4E0A\u306E datum \u3092\u63A8\u5968 (line \u7CFB\u306F 3 \u4EF6\u4EE5\u4E0A\u3067 trend \u304C\u898B\u3048\u308B)`,
|
|
4104
|
+
autoFixable: false
|
|
4105
|
+
});
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
return out;
|
|
4110
|
+
}
|
|
4111
|
+
function ruleGanttUnknownDependsOn(d) {
|
|
4112
|
+
const out = [];
|
|
4113
|
+
for (const n of d.nodes) {
|
|
4114
|
+
if (n.kind === "gantt-timeline") {
|
|
4115
|
+
const tasks = n.ganttData ?? [];
|
|
4116
|
+
const ids = new Set(tasks.map((t) => t.id));
|
|
4117
|
+
for (const t of tasks) {
|
|
4118
|
+
if (t.dependsOn && !ids.has(t.dependsOn)) {
|
|
4119
|
+
out.push({
|
|
4120
|
+
rule: "gantt-unknown-depends-on",
|
|
4121
|
+
severity: "warn",
|
|
4122
|
+
target: t.id,
|
|
4123
|
+
message: `task "${t.id}" \u304C\u672A\u5B9A\u7FA9 task "${t.dependsOn}" \u306B dependsOn \u53C2\u7167`,
|
|
4124
|
+
suggestion: `\u53C2\u7167\u5148 id \u3092\u4FEE\u6B63 or dependsOn \u3092\u9664\u53BB`,
|
|
4125
|
+
autoFixable: false
|
|
4126
|
+
});
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
return out;
|
|
4132
|
+
}
|
|
4133
|
+
function ruleMindMapParentReference(d) {
|
|
4134
|
+
const out = [];
|
|
4135
|
+
for (const n of d.nodes) {
|
|
4136
|
+
if ((n.kind === "mind-map" || n.kind === "mind-radial") && n.mindData) {
|
|
4137
|
+
const known = /* @__PURE__ */ new Set([n.mindData.rootId]);
|
|
4138
|
+
for (const b of n.mindData.branches) known.add(b.id);
|
|
4139
|
+
for (const b of n.mindData.branches) {
|
|
4140
|
+
if (!known.has(b.parent)) {
|
|
4141
|
+
out.push({
|
|
4142
|
+
rule: "mindmap-unknown-parent",
|
|
4143
|
+
severity: "warn",
|
|
4144
|
+
target: b.id,
|
|
4145
|
+
message: `branch "${b.id}" \u304C\u672A\u5B9A\u7FA9 parent "${b.parent}" \u3092\u53C2\u7167`,
|
|
4146
|
+
suggestion: `parent \u3092 rootId ("${n.mindData.rootId}") \u307E\u305F\u306F\u65E2\u5B58 branch id \u306B\u4FEE\u6B63`,
|
|
4147
|
+
autoFixable: false
|
|
4148
|
+
});
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
return out;
|
|
4154
|
+
}
|
|
4155
|
+
function ruleTreeParentReference(d) {
|
|
4156
|
+
const out = [];
|
|
4157
|
+
for (const n of d.nodes) {
|
|
4158
|
+
if (n.kind === "tree-hierarchy" && n.treeData) {
|
|
4159
|
+
const ids = new Set(n.treeData.map((t) => t.id));
|
|
4160
|
+
for (const t of n.treeData) {
|
|
4161
|
+
if (t.parent && !ids.has(t.parent)) {
|
|
4162
|
+
out.push({
|
|
4163
|
+
rule: "tree-unknown-parent",
|
|
4164
|
+
severity: "warn",
|
|
4165
|
+
target: t.id,
|
|
4166
|
+
message: `tree node "${t.id}" \u304C\u672A\u5B9A\u7FA9 parent "${t.parent}" \u3092\u53C2\u7167`,
|
|
4167
|
+
suggestion: `parent id \u3092\u65E2\u5B58 tree node \u306B\u4FEE\u6B63 or parent \u9664\u53BB (root \u306B\u3059\u308B)`,
|
|
4168
|
+
autoFixable: false
|
|
4169
|
+
});
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
return out;
|
|
4175
|
+
}
|
|
4176
|
+
function ruleQuadrantMissingItems(d) {
|
|
4177
|
+
const out = [];
|
|
4178
|
+
for (const n of d.nodes) {
|
|
4179
|
+
if (n.kind === "quadrant-matrix" && n.quadrantData) {
|
|
4180
|
+
const items = n.quadrantData.items;
|
|
4181
|
+
if (items.length === 0) {
|
|
4182
|
+
out.push({
|
|
4183
|
+
rule: "quadrant-empty",
|
|
4184
|
+
severity: "warn",
|
|
4185
|
+
target: n.id,
|
|
4186
|
+
message: `quadrant "${n.id}" \u304C item 0 \u4EF6\u3001 \u8EF8\u306E\u307F\u8868\u793A\u3055\u308C\u308B`,
|
|
4187
|
+
suggestion: `.item({ id: ..., title: ..., quadrant: "topLeft" | ... }) \u3092 1 \u4EF6\u4EE5\u4E0A\u8FFD\u52A0`,
|
|
4188
|
+
autoFixable: false
|
|
4189
|
+
});
|
|
4190
|
+
}
|
|
4191
|
+
const bySlot = new Set(items.map((it) => it.quadrant));
|
|
4192
|
+
if (items.length >= 4 && bySlot.size === 1) {
|
|
4193
|
+
out.push({
|
|
4194
|
+
rule: "quadrant-single-quadrant",
|
|
4195
|
+
severity: "info",
|
|
4196
|
+
target: n.id,
|
|
4197
|
+
message: `quadrant "${n.id}" \u306E item \u304C 1 \u8C61\u9650\u306B\u96C6\u4E2D\u3001 \u30DE\u30C8\u30EA\u30AF\u30B9\u306E\u610F\u5473\u304C\u8584\u3044`,
|
|
4198
|
+
suggestion: `2 \u8C61\u9650\u4EE5\u4E0A\u306B item \u3092\u5206\u6563 (SWOT / Priority matrix \u7B49\u306F 4 \u8C61\u9650 balanced \u3092\u63A8\u5968)`,
|
|
4199
|
+
autoFixable: false
|
|
4200
|
+
});
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
return out;
|
|
4205
|
+
}
|
|
4206
|
+
function ruleFunnelMonotonicCount(d) {
|
|
4207
|
+
const out = [];
|
|
4208
|
+
for (const n of d.nodes) {
|
|
4209
|
+
if (n.kind === "funnel-stages" && n.funnelData) {
|
|
4210
|
+
const stages = n.funnelData;
|
|
4211
|
+
for (let i = 1; i < stages.length; i++) {
|
|
4212
|
+
if (stages[i].count > stages[i - 1].count) {
|
|
4213
|
+
out.push({
|
|
4214
|
+
rule: "funnel-increasing-count",
|
|
4215
|
+
severity: "warn",
|
|
4216
|
+
target: stages[i].id,
|
|
4217
|
+
message: `stage "${stages[i].id}" (${stages[i].count}) \u304C\u524D\u6BB5 (${stages[i - 1].count}) \u3088\u308A\u5897\u52A0\u3001 funnel \u306F\u5358\u8ABF\u6E1B\u5C11\u304C\u671F\u5F85\u3055\u308C\u308B`,
|
|
4218
|
+
suggestion: `stage \u9806\u3092\u518D\u78BA\u8A8D\u3001 \u5897\u52A0 pattern \u306A\u3089\u5225 preset (chart-line \u7B49) \u3092\u691C\u8A0E`,
|
|
4219
|
+
autoFixable: false
|
|
4220
|
+
});
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
return out;
|
|
4226
|
+
}
|
|
4227
|
+
var DIAGRAM_BOUNDARY_PADDING = 20;
|
|
4228
|
+
function computeDiagramBoundingBox(diag) {
|
|
4229
|
+
const laid = layout(diag);
|
|
4230
|
+
const vb = laid.viewBox;
|
|
4231
|
+
return {
|
|
4232
|
+
x: vb.x - DIAGRAM_BOUNDARY_PADDING,
|
|
4233
|
+
y: vb.y - DIAGRAM_BOUNDARY_PADDING,
|
|
4234
|
+
width: vb.w + DIAGRAM_BOUNDARY_PADDING * 2,
|
|
4235
|
+
height: vb.h + DIAGRAM_BOUNDARY_PADDING * 2
|
|
4236
|
+
};
|
|
4237
|
+
}
|
|
4238
|
+
function rectsOverlap(a, b) {
|
|
4239
|
+
const aRight = a.x + a.width;
|
|
4240
|
+
const aBottom = a.y + a.height;
|
|
4241
|
+
const bRight = b.x + b.width;
|
|
4242
|
+
const bBottom = b.y + b.height;
|
|
4243
|
+
return a.x < bRight && aRight > b.x && a.y < bBottom && aBottom > b.y;
|
|
4244
|
+
}
|
|
4245
|
+
|
|
4246
|
+
// src/write-position.ts
|
|
4247
|
+
function writeActorPosition(src, actorName, posX, posY) {
|
|
4248
|
+
if (!Number.isFinite(posX) || !Number.isFinite(posY)) return null;
|
|
4249
|
+
const x = Math.round(posX);
|
|
4250
|
+
const y = Math.round(posY);
|
|
4251
|
+
const seg = src.split(/(\r\n|\n)/);
|
|
4252
|
+
const lineAt = (i) => seg[i * 2] ?? "";
|
|
4253
|
+
const lineCount = Math.ceil(seg.length / 2);
|
|
4254
|
+
const span = actorsSpan(lineAt, lineCount);
|
|
4255
|
+
if (!span) return null;
|
|
4256
|
+
for (let i = span.from; i < span.to; i += 1) {
|
|
4257
|
+
const head = matchActorHead(lineAt(i));
|
|
4258
|
+
if (!head || head.name !== actorName) continue;
|
|
4259
|
+
if (head.rest.startsWith("{") && head.rest.endsWith("}")) {
|
|
4260
|
+
seg[i * 2] = `${head.prefix}{ ${upsertInlineFields(head.rest.slice(1, -1), x, y)} }`;
|
|
4261
|
+
return seg.join("");
|
|
4262
|
+
}
|
|
4263
|
+
if (head.hasColon && head.rest === "") {
|
|
4264
|
+
return writeVerticalForm(seg, i, span.to, head.indent, x, y);
|
|
4265
|
+
}
|
|
4266
|
+
if (head.hasColon) {
|
|
4267
|
+
seg[i * 2] = `${head.prefix}${upsertAtToken(head.rest, x, y)}`;
|
|
4268
|
+
return seg.join("");
|
|
4269
|
+
}
|
|
4270
|
+
seg[i * 2] = `${head.indentText}- ${head.rawName}: @${x},${y}`;
|
|
4271
|
+
return seg.join("");
|
|
4272
|
+
}
|
|
4273
|
+
return null;
|
|
4274
|
+
}
|
|
4275
|
+
function actorsSpan(lineAt, lineCount) {
|
|
4276
|
+
let from = -1;
|
|
4277
|
+
let to = lineCount;
|
|
4278
|
+
for (let i = 0; i < lineCount; i += 1) {
|
|
4279
|
+
const line = lineAt(i);
|
|
4280
|
+
if (/^actors[ \t]*:[ \t]*$/.test(line)) {
|
|
4281
|
+
from = i + 1;
|
|
4282
|
+
to = lineCount;
|
|
4283
|
+
continue;
|
|
4284
|
+
}
|
|
4285
|
+
if (from >= 0 && to === lineCount && /^[^\s#][^:]*:/.test(line)) to = i;
|
|
4286
|
+
}
|
|
4287
|
+
return from < 0 ? null : { from, to };
|
|
4288
|
+
}
|
|
4289
|
+
function matchActorHead(line) {
|
|
4290
|
+
const NAME = String.raw`"(?:[^"\\]|\\.)*"|[^:\s][^:]*?`;
|
|
4291
|
+
const withColon = line.match(new RegExp(String.raw`^(\s*)-[ \t]+(${NAME})[ \t]*:[ \t]*(.*)$`));
|
|
4292
|
+
if (withColon) {
|
|
4293
|
+
const [, indentText2 = "", rawName2 = "", rest = ""] = withColon;
|
|
4294
|
+
return {
|
|
4295
|
+
// 元の空白の入れ方を壊さないよう、 行頭から値の直前までをそのまま持つ
|
|
4296
|
+
prefix: line.slice(0, line.length - rest.length),
|
|
4297
|
+
indentText: indentText2,
|
|
4298
|
+
indent: indentText2.length,
|
|
4299
|
+
rawName: rawName2,
|
|
4300
|
+
name: unquote(rawName2),
|
|
4301
|
+
hasColon: true,
|
|
4302
|
+
rest: rest.trimEnd()
|
|
4303
|
+
};
|
|
4304
|
+
}
|
|
4305
|
+
const nameOnly = line.match(new RegExp(String.raw`^(\s*)-[ \t]+("(?:[^"\\]|\\.)*"|\S+)[ \t]*$`));
|
|
4306
|
+
if (!nameOnly) return null;
|
|
4307
|
+
const [, indentText = "", rawName = ""] = nameOnly;
|
|
4308
|
+
return {
|
|
4309
|
+
prefix: line,
|
|
4310
|
+
indentText,
|
|
4311
|
+
indent: indentText.length,
|
|
4312
|
+
rawName,
|
|
4313
|
+
name: unquote(rawName),
|
|
4314
|
+
hasColon: false,
|
|
4315
|
+
rest: ""
|
|
4316
|
+
};
|
|
4317
|
+
}
|
|
4318
|
+
function unquote(raw) {
|
|
4319
|
+
if (!raw.startsWith('"') || !raw.endsWith('"') || raw.length < 2) return raw;
|
|
4320
|
+
return raw.slice(1, -1).replace(/\\(.)/g, "$1");
|
|
4321
|
+
}
|
|
4322
|
+
function upsertAtToken(rest, x, y) {
|
|
4323
|
+
const kept = splitOutsideQuotes(rest).filter((t) => !/^@-?\d+(\.\d+)?\s*[,、]\s*-?\d+(\.\d+)?$/.test(t));
|
|
4324
|
+
kept.push(`@${x},${y}`);
|
|
4325
|
+
return kept.join(" ");
|
|
4326
|
+
}
|
|
4327
|
+
function splitOutsideQuotes(s) {
|
|
4328
|
+
const out = [];
|
|
4329
|
+
let buf = "";
|
|
4330
|
+
let quote = null;
|
|
4331
|
+
let depth = 0;
|
|
4332
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
4333
|
+
const c = s[i];
|
|
4334
|
+
if (quote) {
|
|
4335
|
+
buf += c;
|
|
4336
|
+
if (quote === '"' && c === "\\") {
|
|
4337
|
+
buf += s[i + 1] ?? "";
|
|
4338
|
+
i += 1;
|
|
4339
|
+
continue;
|
|
4340
|
+
}
|
|
4341
|
+
if (c === quote) quote = null;
|
|
4342
|
+
continue;
|
|
4343
|
+
}
|
|
4344
|
+
if (c === '"' || c === "'") {
|
|
4345
|
+
quote = c;
|
|
4346
|
+
buf += c;
|
|
4347
|
+
continue;
|
|
4348
|
+
}
|
|
4349
|
+
if (c === "[") depth += 1;
|
|
4350
|
+
if (c === "]") depth -= 1;
|
|
4351
|
+
if (/\s/.test(c) && depth === 0) {
|
|
4352
|
+
if (buf) out.push(buf);
|
|
4353
|
+
buf = "";
|
|
4354
|
+
continue;
|
|
4355
|
+
}
|
|
4356
|
+
buf += c;
|
|
4357
|
+
}
|
|
4358
|
+
if (buf) out.push(buf);
|
|
4359
|
+
return out;
|
|
4360
|
+
}
|
|
4361
|
+
function upsertInlineFields(inner, x, y) {
|
|
4362
|
+
const kept = splitTopLevel(inner).filter((f) => {
|
|
4363
|
+
const key = f.slice(0, f.indexOf(":")).trim();
|
|
4364
|
+
return key !== "posX" && key !== "posY";
|
|
4365
|
+
});
|
|
4366
|
+
kept.push(`posX: ${x}`, `posY: ${y}`);
|
|
4367
|
+
return kept.join(", ");
|
|
4368
|
+
}
|
|
4369
|
+
function splitTopLevel(inner) {
|
|
4370
|
+
const out = [];
|
|
4371
|
+
let depth = 0;
|
|
4372
|
+
let start = 0;
|
|
4373
|
+
let quote = null;
|
|
4374
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
4375
|
+
const c = inner[i];
|
|
4376
|
+
if (quote) {
|
|
4377
|
+
if (quote === '"' && c === "\\") {
|
|
4378
|
+
i += 1;
|
|
4379
|
+
continue;
|
|
4380
|
+
}
|
|
4381
|
+
if (c === quote) quote = null;
|
|
4382
|
+
continue;
|
|
4383
|
+
}
|
|
4384
|
+
if (c === '"' || c === "'") quote = c;
|
|
4385
|
+
else if (c === "{" || c === "[") depth += 1;
|
|
4386
|
+
else if (c === "}" || c === "]") depth -= 1;
|
|
4387
|
+
else if (c === "," && depth === 0) {
|
|
4388
|
+
out.push(inner.slice(start, i));
|
|
4389
|
+
start = i + 1;
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
out.push(inner.slice(start));
|
|
4393
|
+
return out.map((f) => f.trim()).filter((f) => f.length > 0);
|
|
4394
|
+
}
|
|
4395
|
+
function writeVerticalForm(seg, headIdx, lineCount, headIndent, x, y) {
|
|
4396
|
+
let lastChild = -1;
|
|
4397
|
+
let childIndent = -1;
|
|
4398
|
+
for (let j = headIdx + 1; j < lineCount; j += 1) {
|
|
4399
|
+
const line = seg[j * 2] ?? "";
|
|
4400
|
+
if (line.trim() === "") continue;
|
|
4401
|
+
const indent2 = line.length - line.trimStart().length;
|
|
4402
|
+
if (indent2 <= headIndent) break;
|
|
4403
|
+
if (childIndent < 0) childIndent = indent2;
|
|
4404
|
+
lastChild = j;
|
|
4405
|
+
const key = indent2 === childIndent ? line.trim().match(/^(位置|pos)\s*:/) : null;
|
|
4406
|
+
if (key) {
|
|
4407
|
+
seg[j * 2] = `${" ".repeat(indent2)}${key[1]}: ${x},${y}`;
|
|
4408
|
+
return seg.join("");
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
const indent = childIndent >= 0 ? childIndent : headIndent + 4;
|
|
4412
|
+
const newLine = `${" ".repeat(indent)}\u4F4D\u7F6E: ${x},${y}`;
|
|
4413
|
+
const insertAt = (lastChild >= 0 ? lastChild : headIdx) + 1;
|
|
4414
|
+
const sep = seg[insertAt * 2 - 1] ?? seg[seg.length - 2] ?? "\n";
|
|
4415
|
+
if (insertAt * 2 >= seg.length) seg.push(sep, newLine);
|
|
4416
|
+
else seg.splice(insertAt * 2, 0, newLine, sep);
|
|
4417
|
+
return seg.join("");
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
// src/json-parser.ts
|
|
4421
|
+
var VALID_KIND_SET = /* @__PURE__ */ new Set([
|
|
4422
|
+
"actor",
|
|
4423
|
+
"function",
|
|
4424
|
+
"storage",
|
|
4425
|
+
"event",
|
|
4426
|
+
"cdn",
|
|
4427
|
+
"service",
|
|
4428
|
+
"database",
|
|
4429
|
+
"cache",
|
|
4430
|
+
"queue",
|
|
4431
|
+
"api",
|
|
4432
|
+
"person",
|
|
4433
|
+
"entity",
|
|
4434
|
+
"state",
|
|
4435
|
+
"container",
|
|
4436
|
+
"card",
|
|
4437
|
+
"lambda",
|
|
4438
|
+
"kms",
|
|
4439
|
+
"secret",
|
|
4440
|
+
"alb",
|
|
4441
|
+
"ecs",
|
|
4442
|
+
"rds",
|
|
4443
|
+
"s3",
|
|
4444
|
+
"iam",
|
|
4445
|
+
"user",
|
|
4446
|
+
"browser",
|
|
4447
|
+
"contract",
|
|
4448
|
+
"eoa",
|
|
4449
|
+
"multisig",
|
|
4450
|
+
"proxy",
|
|
4451
|
+
"library",
|
|
4452
|
+
"interface"
|
|
4453
|
+
]);
|
|
4454
|
+
var VALID_PRESETS = [
|
|
4455
|
+
"sequence",
|
|
4456
|
+
"flow",
|
|
4457
|
+
"swimlane",
|
|
4458
|
+
"er",
|
|
4459
|
+
"state",
|
|
4460
|
+
"topology",
|
|
4461
|
+
"solidity",
|
|
4462
|
+
"gantt",
|
|
4463
|
+
"class",
|
|
4464
|
+
"pie",
|
|
4465
|
+
"c4",
|
|
4466
|
+
"mind"
|
|
4467
|
+
];
|
|
4468
|
+
function validateLayoutPos(v, path, errors) {
|
|
4469
|
+
if (v === void 0) return;
|
|
4470
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) {
|
|
4471
|
+
errors.push({ path, message: "pos must be an object with x and y numbers" });
|
|
4472
|
+
return;
|
|
4473
|
+
}
|
|
4474
|
+
const p = v;
|
|
4475
|
+
if (typeof p.x !== "number" || !Number.isFinite(p.x)) {
|
|
4476
|
+
errors.push({ path: `${path}.x`, message: "pos.x must be a finite number" });
|
|
4477
|
+
}
|
|
4478
|
+
if (typeof p.y !== "number" || !Number.isFinite(p.y)) {
|
|
4479
|
+
errors.push({ path: `${path}.y`, message: "pos.y must be a finite number" });
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
function validateJson(json) {
|
|
4483
|
+
const errors = [];
|
|
4484
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
4485
|
+
return { ok: false, errors: [{ path: "$", message: "root must be a JSON object" }] };
|
|
4486
|
+
}
|
|
4487
|
+
const j = json;
|
|
4488
|
+
if (typeof j.title !== "string" || j.title.length === 0) {
|
|
4489
|
+
errors.push({ path: "$.title", message: "title must be a non-empty string" });
|
|
4490
|
+
}
|
|
4491
|
+
if (j.layout !== void 0 && j.layout !== "auto" && j.layout !== "manual") {
|
|
4492
|
+
errors.push({ path: "$.layout", message: 'layout must be "auto" or "manual" if present' });
|
|
4493
|
+
}
|
|
4494
|
+
if (typeof j.type !== "string" || !VALID_PRESETS.includes(j.type)) {
|
|
4495
|
+
errors.push({
|
|
4496
|
+
path: "$.type",
|
|
4497
|
+
message: `type must be one of: ${VALID_PRESETS.join(", ")}`,
|
|
4498
|
+
hint: typeof j.type === "string" ? `got "${j.type}"` : void 0
|
|
4499
|
+
});
|
|
4500
|
+
}
|
|
4501
|
+
if (!Array.isArray(j.actors) || j.actors.length === 0) {
|
|
4502
|
+
errors.push({ path: "$.actors", message: "actors must be a non-empty array" });
|
|
4503
|
+
} else {
|
|
4504
|
+
j.actors.forEach((a, i) => {
|
|
4505
|
+
if (typeof a === "string") return;
|
|
4506
|
+
if (!a || typeof a !== "object" || Array.isArray(a)) {
|
|
4507
|
+
errors.push({ path: `$.actors[${i}]`, message: "actor must be string or object" });
|
|
4508
|
+
return;
|
|
4509
|
+
}
|
|
4510
|
+
const ao = a;
|
|
4511
|
+
if (typeof ao.name !== "string" || ao.name.length === 0) {
|
|
4512
|
+
errors.push({ path: `$.actors[${i}].name`, message: "actor.name must be a non-empty string" });
|
|
4513
|
+
}
|
|
4514
|
+
if (ao.kind !== void 0 && (typeof ao.kind !== "string" || ao.kind.length === 0)) {
|
|
4515
|
+
errors.push({ path: `$.actors[${i}].kind`, message: "actor.kind must be a non-empty string" });
|
|
4516
|
+
}
|
|
4517
|
+
if (ao.state !== void 0) {
|
|
4518
|
+
if (!ao.state || typeof ao.state !== "object" || Array.isArray(ao.state)) {
|
|
4519
|
+
errors.push({ path: `$.actors[${i}].state`, message: "actor.state must be a plain object" });
|
|
4520
|
+
} else {
|
|
4521
|
+
for (const [sk, sv] of Object.entries(ao.state)) {
|
|
4522
|
+
const svType = typeof sv;
|
|
4523
|
+
if (svType !== "number" && svType !== "string" && svType !== "boolean") {
|
|
4524
|
+
errors.push({
|
|
4525
|
+
path: `$.actors[${i}].state.${sk}`,
|
|
4526
|
+
message: `actor.state.${sk} must be number / string / boolean (got ${sv === null ? "null" : svType})`
|
|
4527
|
+
});
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
validateLayoutPos(ao.pos, `$.actors[${i}].pos`, errors);
|
|
4533
|
+
});
|
|
4534
|
+
}
|
|
4535
|
+
if (!Array.isArray(j.flow)) {
|
|
4536
|
+
errors.push({ path: "$.flow", message: "flow must be an array" });
|
|
4537
|
+
} else {
|
|
4538
|
+
j.flow.forEach((s, i) => {
|
|
4539
|
+
if (!s || typeof s !== "object" || Array.isArray(s)) {
|
|
4540
|
+
errors.push({ path: `$.flow[${i}]`, message: "step must be an object" });
|
|
4541
|
+
return;
|
|
4542
|
+
}
|
|
4543
|
+
const so = s;
|
|
4544
|
+
if (typeof so.from !== "string") errors.push({ path: `$.flow[${i}].from`, message: "step.from must be a string" });
|
|
4545
|
+
if (typeof so.to !== "string") errors.push({ path: `$.flow[${i}].to`, message: "step.to must be a string" });
|
|
4546
|
+
if (typeof so.label !== "string") errors.push({ path: `$.flow[${i}].label`, message: "step.label must be a string" });
|
|
4547
|
+
validateLayoutPos(so.pos, `$.flow[${i}].pos`, errors);
|
|
4548
|
+
});
|
|
4549
|
+
}
|
|
4550
|
+
if (j.lanes !== void 0 && j.lanes && typeof j.lanes === "object" && !Array.isArray(j.lanes)) {
|
|
4551
|
+
for (const [laneId, lane] of Object.entries(j.lanes)) {
|
|
4552
|
+
if (lane && typeof lane === "object" && !Array.isArray(lane)) {
|
|
4553
|
+
validateLayoutPos(lane.pos, `$.lanes.${laneId}.pos`, errors);
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
if (j.animation !== void 0) {
|
|
4558
|
+
if (!Array.isArray(j.animation)) {
|
|
4559
|
+
errors.push({ path: "$.animation", message: "animation must be an array if present" });
|
|
4560
|
+
} else {
|
|
4561
|
+
j.animation.forEach((p, i) => {
|
|
4562
|
+
if (!p || typeof p !== "object" || Array.isArray(p)) {
|
|
4563
|
+
errors.push({ path: `$.animation[${i}]`, message: "phase must be an object" });
|
|
4564
|
+
return;
|
|
4565
|
+
}
|
|
4566
|
+
const po = p;
|
|
4567
|
+
if (typeof po.step !== "string" || po.step.length === 0) {
|
|
4568
|
+
errors.push({ path: `$.animation[${i}].step`, message: "phase.step must be a non-empty string" });
|
|
4569
|
+
}
|
|
4570
|
+
});
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
if (errors.length > 0) return { ok: false, errors };
|
|
4574
|
+
return { ok: true, data: j };
|
|
4575
|
+
}
|
|
4576
|
+
function jsonToDoc(json) {
|
|
4577
|
+
const p0 = { line: 0 };
|
|
4578
|
+
const actors = json.actors.map((a) => {
|
|
4579
|
+
if (typeof a === "string") {
|
|
4580
|
+
return { name: a, kind: "actor", kindWritten: false, pos: p0 };
|
|
4581
|
+
}
|
|
4582
|
+
const kindStr = a.kind ?? "actor";
|
|
4583
|
+
const isPart = kindStr !== "actor" && !VALID_KIND_SET.has(kindStr);
|
|
4584
|
+
return {
|
|
4585
|
+
name: a.name,
|
|
4586
|
+
kind: isPart ? "actor" : a.kind ?? "actor",
|
|
4587
|
+
// parts 候補は `kind` を `actor` に倒して `partId` へ退避するため、 名札に載せる種類としては
|
|
4588
|
+
// 「書かなかった」 と同じ扱いにする (#1058)
|
|
4589
|
+
kindWritten: a.kind !== void 0 && !isPart,
|
|
4590
|
+
subtitle: a.subtitle,
|
|
4591
|
+
eyebrow: a.eyebrow,
|
|
4592
|
+
value: a.value,
|
|
4593
|
+
rows: a.rows,
|
|
4594
|
+
lane: a.lane,
|
|
4595
|
+
stack: a.stack,
|
|
4596
|
+
initial: a.initial,
|
|
4597
|
+
final: a.final,
|
|
4598
|
+
partId: isPart ? kindStr : void 0,
|
|
4599
|
+
stateOverride: isPart ? a.state : void 0,
|
|
4600
|
+
// CAR-1693 Phase 1: DSL 表面 pos → 内部 AST layoutPos の 2 層 mapping (naming collision 回避)
|
|
4601
|
+
layoutPos: a.pos,
|
|
4602
|
+
pos: p0
|
|
4603
|
+
};
|
|
4604
|
+
});
|
|
4605
|
+
const flow2 = json.flow.map((s, i) => ({
|
|
4606
|
+
no: i + 1,
|
|
4607
|
+
from: s.from,
|
|
4608
|
+
to: s.to,
|
|
4609
|
+
label: s.label,
|
|
4610
|
+
sub: s.sub,
|
|
4611
|
+
tone: s.tone,
|
|
4612
|
+
style: s.style,
|
|
4613
|
+
guard: s.guard,
|
|
4614
|
+
cardinality: s.cardinality,
|
|
4615
|
+
labelOffsetX: s.labelOffsetX,
|
|
4616
|
+
labelOffsetY: s.labelOffsetY,
|
|
4617
|
+
// CAR-1693 Phase 1: DSL 表面 pos → 内部 AST layoutPos
|
|
4618
|
+
layoutPos: s.pos,
|
|
4619
|
+
pos: p0
|
|
4620
|
+
}));
|
|
4621
|
+
let animate;
|
|
4622
|
+
if (json.animation && json.animation.length > 0) {
|
|
4623
|
+
const phases = json.animation.map((p) => ({
|
|
4624
|
+
name: p.step,
|
|
4625
|
+
durationMs: Math.round((p.duration ?? 1.4) * 1e3),
|
|
4626
|
+
highlight: p.focus,
|
|
4627
|
+
body: p.body,
|
|
4628
|
+
badge: p.badge,
|
|
4629
|
+
pos: p0
|
|
4630
|
+
}));
|
|
4631
|
+
animate = { states: [], phases, pos: p0 };
|
|
4632
|
+
}
|
|
4633
|
+
return {
|
|
4634
|
+
title: json.title,
|
|
4635
|
+
type: json.type,
|
|
4636
|
+
actors,
|
|
4637
|
+
flow: flow2,
|
|
4638
|
+
animate,
|
|
4639
|
+
viewport: json.viewport ? { ...json.viewport, pos: p0 } : void 0,
|
|
4640
|
+
lanes: json.lanes ? Object.fromEntries(
|
|
4641
|
+
Object.entries(json.lanes).map(([id, l]) => {
|
|
4642
|
+
const { pos: layoutPos, ...laneRest } = l;
|
|
4643
|
+
return [id, { id, ...laneRest, layoutPos, pos: p0 }];
|
|
4644
|
+
})
|
|
4645
|
+
) : void 0,
|
|
4646
|
+
groups: json.groups ? Object.fromEntries(
|
|
4647
|
+
Object.entries(json.groups).map(([id, g]) => [id, { id, label: g.label, lanes: g.lanes, pos: p0 }])
|
|
4648
|
+
) : void 0,
|
|
4649
|
+
// CAR-1693 Phase 1: diagram-level layout mode (auto|manual)、 未指定は undefined = auto default
|
|
4650
|
+
layout: json.layout,
|
|
4651
|
+
pos: p0
|
|
4652
|
+
};
|
|
4653
|
+
}
|
|
4654
|
+
function jsonToDiagram(json, opts) {
|
|
4655
|
+
const v = validateJson(json);
|
|
4656
|
+
if (!v.ok) {
|
|
4657
|
+
const msg = v.errors.map((e) => ` ${e.path}: ${e.message}${e.hint ? ` (${e.hint})` : ""}`).join("\n");
|
|
4658
|
+
throw new Error(`Dragon JSON DSL validation error:
|
|
4659
|
+
${msg}`);
|
|
4660
|
+
}
|
|
4661
|
+
const doc = jsonToDoc(v.data);
|
|
4662
|
+
return compileToCdl(doc, opts);
|
|
4663
|
+
}
|
|
4664
|
+
function validateDragonJson(json) {
|
|
4665
|
+
return validateJson(json);
|
|
4666
|
+
}
|
|
4667
|
+
|
|
4668
|
+
// src/schemas/diagram.json
|
|
4669
|
+
var diagram_default = {
|
|
4670
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
4671
|
+
$id: "https://github.com/cardene777/dragon/schemas/diagram.json",
|
|
4672
|
+
title: "Dragon DSL Diagram",
|
|
4673
|
+
description: "LLM \u5411\u3051 Dragon DSL \u306E JSON Schema\u3002 Anthropic Claude tool use / OpenAI GPT structured output \u306E schema field \u306B\u6CE8\u5165\u3057\u3066\u4F7F\u3046\u3002 \u751F\u6210\u3055\u308C\u305F JSON \u306F jsonToDiagram() \u306B\u6E21\u3057\u3066 CdlDiagram \u306B\u5909\u63DB\u3059\u308B\u3002",
|
|
4674
|
+
type: "object",
|
|
4675
|
+
required: ["title", "type", "actors", "flow"],
|
|
4676
|
+
additionalProperties: false,
|
|
4677
|
+
properties: {
|
|
4678
|
+
title: {
|
|
4679
|
+
type: "string",
|
|
4680
|
+
minLength: 1,
|
|
4681
|
+
description: "\u56F3\u306E title (\u753B\u9762\u4E0A\u90E8\u306B\u8868\u793A\u3055\u308C\u308B\u898B\u51FA\u3057)"
|
|
4682
|
+
},
|
|
4683
|
+
type: {
|
|
4684
|
+
type: "string",
|
|
4685
|
+
enum: ["sequence", "flow", "swimlane", "er", "state", "topology", "solidity", "gantt", "class", "pie", "c4", "mind"],
|
|
4686
|
+
description: "preset type = \u56F3\u306E\u7A2E\u985E\u3002 sequence (\u6642\u7CFB\u5217 API \u547C\u3073\u51FA\u3057) / flow (\u51E6\u7406 flow) / swimlane (\u8CAC\u52D9\u5206\u62C5 flow) / er (DB schema) / state (\u72B6\u614B\u9077\u79FB) / topology (network) / solidity (contract) / gantt (schedule) / class (UML class) / pie (\u5186\u30B0\u30E9\u30D5) / c4 (architecture) / mind (mind map)"
|
|
4687
|
+
},
|
|
4688
|
+
actors: {
|
|
4689
|
+
type: "array",
|
|
4690
|
+
minItems: 1,
|
|
4691
|
+
description: "\u767B\u5834\u4EBA\u7269 (node) \u306E\u914D\u5217\u3002 \u6587\u5B57\u5217\u3067 name \u306E\u307F\u3001 \u307E\u305F\u306F object \u3067 kind / subtitle \u7B49\u3092\u6307\u5B9A\u3002",
|
|
4692
|
+
items: {
|
|
4693
|
+
oneOf: [
|
|
4694
|
+
{
|
|
4695
|
+
type: "string",
|
|
4696
|
+
description: "actor \u540D\u306E\u307F (kind \u306F default 'actor')"
|
|
4697
|
+
},
|
|
4698
|
+
{
|
|
4699
|
+
type: "object",
|
|
4700
|
+
required: ["name"],
|
|
4701
|
+
additionalProperties: false,
|
|
4702
|
+
properties: {
|
|
4703
|
+
name: { type: "string", minLength: 1, description: "actor \u540D (flow \u306E from / to \u3067\u53C2\u7167\u3055\u308C\u308B)" },
|
|
4704
|
+
kind: {
|
|
4705
|
+
type: "string",
|
|
4706
|
+
description: "node kind\u3002 shape-* \u3092\u6307\u5B9A\u3057\u3066 49 shape \u4E2D\u304B\u3089\u9078\u3076\u3002 \u4F8B: 'shape-wallet' / 'shape-smart-contract' / 'shape-cloud' / 'shape-file' / 'actor' (default) / 'function' / 'storage' / 'event' \u7B49"
|
|
4707
|
+
},
|
|
4708
|
+
subtitle: { type: "string", description: "node \u4E0B\u90E8\u306E\u88DC\u8DB3\u30C6\u30AD\u30B9\u30C8" },
|
|
4709
|
+
eyebrow: { type: "string", description: "node \u4E0A\u90E8\u306E\u5206\u985E\u30E9\u30D9\u30EB (accent \u8272)" },
|
|
4710
|
+
value: { type: "string", description: "actor \u6570\u5024\u8868\u793A (kind: actor \u7528)" },
|
|
4711
|
+
rows: { type: "array", items: { type: "string" }, description: "storage \u5185\u306E column \u5217 (kind: storage \u7528)" },
|
|
4712
|
+
lane: { type: "string", description: "swimlane / topology \u3067\u5C5E\u3059\u308B lane id" },
|
|
4713
|
+
stack: { type: "integer", minimum: 0, description: "lane \u5185\u306E\u7E26\u4F4D\u7F6E (0-based)" },
|
|
4714
|
+
initial: { type: "boolean", description: "state \u958B\u59CB\u70B9 (kind: state \u7528)" },
|
|
4715
|
+
final: { type: "boolean", description: "state \u7D42\u4E86\u70B9 (kind: state \u7528)" }
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
]
|
|
4719
|
+
}
|
|
4720
|
+
},
|
|
4721
|
+
flow: {
|
|
4722
|
+
type: "array",
|
|
4723
|
+
description: "step \u306E\u914D\u5217 (\u77E2\u5370\u3067 actor \u9593\u3092\u7E4B\u3050)\u3002 sequence \u3067\u306F\u9806\u5E8F\u304C\u610F\u5473\u3092\u6301\u3064\u3002",
|
|
4724
|
+
items: {
|
|
4725
|
+
type: "object",
|
|
4726
|
+
required: ["from", "to", "label"],
|
|
4727
|
+
additionalProperties: false,
|
|
4728
|
+
properties: {
|
|
4729
|
+
from: { type: "string", description: "\u8D77\u70B9 actor \u540D" },
|
|
4730
|
+
to: { type: "string", description: "\u7D42\u70B9 actor \u540D" },
|
|
4731
|
+
label: { type: "string", description: "\u77E2\u5370\u4E0A\u306E\u30E9\u30D9\u30EB (\u95A2\u6570\u547C\u51FA\u3057\u540D / \u30E1\u30C3\u30BB\u30FC\u30B8\u7B49)" },
|
|
4732
|
+
sub: { type: "string", description: "\u30E9\u30D9\u30EB\u4E0B\u306E\u88DC\u8DB3" },
|
|
4733
|
+
tone: { type: "string", enum: ["accent", "success", "warning", "danger"], description: "\u77E2\u5370\u306E\u8272\u8ABF" },
|
|
4734
|
+
style: { type: "string", enum: ["solid", "dashed", "dotted", "dotted-flow"], description: "\u77E2\u5370\u306E\u7DDA\u7A2E" },
|
|
4735
|
+
guard: { type: "string", description: "state \u9077\u79FB\u306E\u6761\u4EF6 (kind: state \u7528)" },
|
|
4736
|
+
cardinality: { type: "string", description: "ER \u306E\u591A\u91CD\u5EA6 (1..N \u7B49\u3001 kind: er \u7528)" },
|
|
4737
|
+
labelOffsetX: { type: "number", description: "\u30E9\u30D9\u30EB\u4F4D\u7F6E x \u8ABF\u6574" },
|
|
4738
|
+
labelOffsetY: { type: "number", description: "\u30E9\u30D9\u30EB\u4F4D\u7F6E y \u8ABF\u6574" }
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
},
|
|
4742
|
+
animation: {
|
|
4743
|
+
type: "array",
|
|
4744
|
+
description: "\u30A2\u30CB\u30E1\u30FC\u30B7\u30E7\u30F3 phase \u914D\u5217\u3002 \u5404 phase \u3067 focus \u5BFE\u8C61\u3092 highlight \u3059\u308B\u3002 optional\u3002",
|
|
4745
|
+
items: {
|
|
4746
|
+
type: "object",
|
|
4747
|
+
required: ["step"],
|
|
4748
|
+
additionalProperties: false,
|
|
4749
|
+
properties: {
|
|
4750
|
+
step: { type: "string", minLength: 1, description: "phase \u540D" },
|
|
4751
|
+
duration: { type: "number", minimum: 0.1, description: "phase \u6642\u9593 (\u79D2\u3001 default 1.4)" },
|
|
4752
|
+
focus: { type: "array", items: { type: "string" }, description: "\u3053\u306E phase \u3067 active \u5316\u3059\u308B actor \u540D or edge 'A -> B' \u306E\u914D\u5217" },
|
|
4753
|
+
body: { type: "string", description: "phase \u8AAC\u660E\u6587" },
|
|
4754
|
+
badge: { type: "string", description: "phase \u4E2D\u306B\u8868\u793A\u3059\u308B badge label" }
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
},
|
|
4758
|
+
viewport: {
|
|
4759
|
+
type: "object",
|
|
4760
|
+
description: "\u5168\u4F53 canvas \u30B5\u30A4\u30BA + gap \u306E\u8ABF\u6574\u3002 optional\u3002",
|
|
4761
|
+
additionalProperties: false,
|
|
4762
|
+
properties: {
|
|
4763
|
+
width: { type: "number", minimum: 100 },
|
|
4764
|
+
height: { type: "number", minimum: 100 },
|
|
4765
|
+
laneWidth: { type: "number", minimum: 100 },
|
|
4766
|
+
gap: { type: "number", minimum: 0 },
|
|
4767
|
+
laneGap: { type: "number", minimum: 0 },
|
|
4768
|
+
nodeGap: { type: "number", minimum: 0 },
|
|
4769
|
+
labelMargin: { type: "number", minimum: 0 }
|
|
4770
|
+
}
|
|
4771
|
+
},
|
|
4772
|
+
lanes: {
|
|
4773
|
+
type: "object",
|
|
4774
|
+
description: "swimlane / topology \u3067\u4F7F\u3046 lane \u5BA3\u8A00\u3002 key = lane id\u3001 value = lane \u5B9A\u7FA9\u3002",
|
|
4775
|
+
additionalProperties: {
|
|
4776
|
+
type: "object",
|
|
4777
|
+
additionalProperties: false,
|
|
4778
|
+
properties: {
|
|
4779
|
+
x: { type: "number" },
|
|
4780
|
+
width: { type: "number", minimum: 50 },
|
|
4781
|
+
label: { type: "string" },
|
|
4782
|
+
contain: { type: "boolean" },
|
|
4783
|
+
lifeline: { type: "boolean" }
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
},
|
|
4787
|
+
groups: {
|
|
4788
|
+
type: "object",
|
|
4789
|
+
description: "topology \u3067\u4F7F\u3046 group \u5BA3\u8A00\u3002 key = group id\u3001 value = { label, lanes }\u3002",
|
|
4790
|
+
additionalProperties: {
|
|
4791
|
+
type: "object",
|
|
4792
|
+
required: ["lanes"],
|
|
4793
|
+
additionalProperties: false,
|
|
4794
|
+
properties: {
|
|
4795
|
+
label: { type: "string" },
|
|
4796
|
+
lanes: { type: "array", items: { type: "string" }, minItems: 1 }
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
};
|
|
4802
|
+
|
|
4803
|
+
// src/schema.ts
|
|
4804
|
+
var diagramJsonSchema = diagram_default;
|
|
4805
|
+
|
|
4806
|
+
// src/index.ts
|
|
4807
|
+
function textDslToDiagram(src, opts) {
|
|
4808
|
+
const oversize = describeOversizeSource(src);
|
|
4809
|
+
if (oversize) throw new Error(oversize);
|
|
4810
|
+
if (isV05Source(src)) {
|
|
4811
|
+
const r2 = parseTextDslV05(src);
|
|
4812
|
+
if (!r2.ok) {
|
|
4813
|
+
const msg = r2.errors.map((e) => ` L${e.line}: ${e.message}${e.hint ? `
|
|
4814
|
+
hint: ${e.hint}` : ""}`).join("\n");
|
|
4815
|
+
throw new Error(`Dragon DSL v0.5 parse error:
|
|
4816
|
+
${msg}`);
|
|
4817
|
+
}
|
|
4818
|
+
return compileToCdl(r2.doc, opts);
|
|
4819
|
+
}
|
|
4820
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
4821
|
+
console.warn(
|
|
4822
|
+
"[dragon] v0.4 syntax (Japanese keywords) is deprecated. Please migrate to v0.5 (English keywords) by 2026-12-31."
|
|
4823
|
+
);
|
|
4824
|
+
}
|
|
4825
|
+
const r = parseTextDsl(src);
|
|
4826
|
+
if (!r.ok) {
|
|
4827
|
+
const msg = r.errors.map((e) => ` L${e.line}: ${e.message}${e.hint ? `
|
|
4828
|
+
\u30D2\u30F3\u30C8: ${e.hint}` : ""}`).join("\n");
|
|
4829
|
+
throw new Error(`Dragon DSL parse error:
|
|
4830
|
+
${msg}`);
|
|
4831
|
+
}
|
|
4832
|
+
return compileToCdl(r.doc, opts);
|
|
4833
|
+
}
|
|
4834
|
+
function isV05Source(src) {
|
|
4835
|
+
const V04_KEYWORDS = [
|
|
4836
|
+
// Japanese v0.4 専用 keyword (v0.5 は英語のみ)
|
|
4837
|
+
/^\s*タイトル\s*[::]/,
|
|
4838
|
+
// title (JA)
|
|
4839
|
+
/^\s*種類\s*[::]/,
|
|
4840
|
+
// type (JA)
|
|
4841
|
+
/^\s*登場人物\s*[::]/,
|
|
4842
|
+
// actors (JA)
|
|
4843
|
+
/^\s*流れ\s*[::]/,
|
|
4844
|
+
// flow (JA)
|
|
4845
|
+
/^\s*アニメーション\s*[::]/,
|
|
4846
|
+
// animation (JA)
|
|
4847
|
+
/^\s*動作\s*[::]/,
|
|
4848
|
+
// step (JA)
|
|
4849
|
+
/^\s*状態\s*[::]/,
|
|
4850
|
+
// state (JA)
|
|
4851
|
+
/^\s*ステップ\s*[「『]/,
|
|
4852
|
+
// step (JA)
|
|
4853
|
+
// v0.4 English-specific syntax = colon なし step (v0.5 は step: 必須)
|
|
4854
|
+
/^\s*step\s+"[^"]+"\s+[\d.]+\s*s\b/,
|
|
4855
|
+
/^\s*step\s+'[^']+'\s+[\d.]+\s*s\b/,
|
|
4856
|
+
// v0.4 numbered flow (`1. A → B`、 v0.5 は `- A -> B`)
|
|
4857
|
+
/^\s*\d+\.\s+\S+\s*(?:→|->)\s*\S+/
|
|
4858
|
+
];
|
|
4859
|
+
const lines = src.split("\n");
|
|
4860
|
+
for (const ln of lines) {
|
|
4861
|
+
for (const kw of V04_KEYWORDS) {
|
|
4862
|
+
if (kw.test(ln)) return false;
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
return true;
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4868
|
+
export { DIAGRAM_BOUNDARY_PADDING, MAX_INPUT_BYTES, MAX_INPUT_ELEMENTS, MAX_PART_SCALE, NODE_KIND_ALIAS, NODE_KIND_VALID, PRESET_TYPES, RELATIVE_GAP_DEFAULT, TONE_ALIAS, autoFix, compileToCdl, computeDiagramBoundingBox, countBytes, countDiagramElements, countDocElements, describeOversize, describeOversizeSource, diagramJsonSchema, isColorValue, jsonToDiagram, lintDiagram, measureActorBoxes, normalizePartScale, orderByDependency, parseFocusEntry, parseRelativePos, parseTextDsl, parseTextDslV05, partBoxInFrame, partDrawsInDiagram, partIsMeasurable, partRenderSize, partScaleFactor, partTargetScale, partTargetSize, partVisualSize, partsGridCenters, pointsOutside, rectsOverlap, resolveRelativePos, stripExternalPaint, stripQuotes, textDslToDiagram, validateDragonJson, writeActorPosition };
|
|
4869
|
+
//# sourceMappingURL=index.js.map
|
|
4870
|
+
//# sourceMappingURL=index.js.map
|