@almadar/ui 1.0.1 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-4AIGHVQK.js +2834 -0
- package/dist/chunk-7NEWMNNU.js +147 -0
- package/dist/chunk-AQREMI4N.js +426 -0
- package/dist/chunk-DTKTZ3UR.js +190 -0
- package/dist/chunk-KKCVDUK7.js +104 -0
- package/dist/chunk-N7MVUW4R.js +194 -0
- package/dist/chunk-QFAKJLOK.js +14 -0
- package/dist/chunk-S7EYY36U.js +13 -0
- package/dist/chunk-TTXKOHDO.js +270 -0
- package/dist/chunk-XSEDIUM6.js +93 -0
- package/dist/components/index.d.ts +44 -6170
- package/dist/components/index.js +210 -26068
- package/dist/context/index.js +6 -342
- package/dist/hooks/index.d.ts +193 -129
- package/dist/hooks/index.js +6 -2267
- package/dist/lib/index.d.ts +144 -1
- package/dist/lib/index.js +677 -189
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +8 -912
- package/dist/renderer/index.js +5 -430
- package/dist/stores/index.js +2 -196
- package/package.json +15 -14
- package/LICENSE +0 -72
- package/dist/components/index.js.map +0 -1
- package/dist/context/index.js.map +0 -1
- package/dist/hooks/index.js.map +0 -1
- package/dist/lib/index.js.map +0 -1
- package/dist/providers/index.js.map +0 -1
- package/dist/renderer/index.js.map +0 -1
- package/dist/stores/index.js.map +0 -1
package/dist/lib/index.js
CHANGED
|
@@ -1,171 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// lib/cn.ts
|
|
5
|
-
function cn(...inputs) {
|
|
6
|
-
return twMerge(clsx(inputs));
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// lib/api-client.ts
|
|
10
|
-
var API_BASE_URL = typeof import.meta !== "undefined" && import.meta.env?.VITE_API_URL ? import.meta.env.VITE_API_URL : "/api";
|
|
11
|
-
var ApiError = class extends Error {
|
|
12
|
-
constructor(status, statusText, message) {
|
|
13
|
-
super(message || `API Error: ${status} ${statusText}`);
|
|
14
|
-
this.status = status;
|
|
15
|
-
this.statusText = statusText;
|
|
16
|
-
this.name = "ApiError";
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
async function handleResponse(response) {
|
|
20
|
-
if (!response.ok) {
|
|
21
|
-
let message;
|
|
22
|
-
try {
|
|
23
|
-
const errorData = await response.json();
|
|
24
|
-
message = errorData.message || errorData.error;
|
|
25
|
-
} catch {
|
|
26
|
-
}
|
|
27
|
-
throw new ApiError(response.status, response.statusText, message);
|
|
28
|
-
}
|
|
29
|
-
const text = await response.text();
|
|
30
|
-
if (!text) {
|
|
31
|
-
return void 0;
|
|
32
|
-
}
|
|
33
|
-
return JSON.parse(text);
|
|
34
|
-
}
|
|
35
|
-
function getHeaders() {
|
|
36
|
-
const headers = {
|
|
37
|
-
"Content-Type": "application/json"
|
|
38
|
-
};
|
|
39
|
-
const token = typeof localStorage !== "undefined" ? localStorage.getItem("authToken") : null;
|
|
40
|
-
if (token) {
|
|
41
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
42
|
-
}
|
|
43
|
-
return headers;
|
|
44
|
-
}
|
|
45
|
-
var apiClient = {
|
|
46
|
-
/**
|
|
47
|
-
* GET request
|
|
48
|
-
*/
|
|
49
|
-
async get(endpoint) {
|
|
50
|
-
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
|
51
|
-
method: "GET",
|
|
52
|
-
headers: getHeaders()
|
|
53
|
-
});
|
|
54
|
-
return handleResponse(response);
|
|
55
|
-
},
|
|
56
|
-
/**
|
|
57
|
-
* POST request
|
|
58
|
-
*/
|
|
59
|
-
async post(endpoint, data) {
|
|
60
|
-
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
|
61
|
-
method: "POST",
|
|
62
|
-
headers: getHeaders(),
|
|
63
|
-
body: data ? JSON.stringify(data) : void 0
|
|
64
|
-
});
|
|
65
|
-
return handleResponse(response);
|
|
66
|
-
},
|
|
67
|
-
/**
|
|
68
|
-
* PUT request
|
|
69
|
-
*/
|
|
70
|
-
async put(endpoint, data) {
|
|
71
|
-
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
|
72
|
-
method: "PUT",
|
|
73
|
-
headers: getHeaders(),
|
|
74
|
-
body: data ? JSON.stringify(data) : void 0
|
|
75
|
-
});
|
|
76
|
-
return handleResponse(response);
|
|
77
|
-
},
|
|
78
|
-
/**
|
|
79
|
-
* PATCH request
|
|
80
|
-
*/
|
|
81
|
-
async patch(endpoint, data) {
|
|
82
|
-
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
|
83
|
-
method: "PATCH",
|
|
84
|
-
headers: getHeaders(),
|
|
85
|
-
body: data ? JSON.stringify(data) : void 0
|
|
86
|
-
});
|
|
87
|
-
return handleResponse(response);
|
|
88
|
-
},
|
|
89
|
-
/**
|
|
90
|
-
* DELETE request
|
|
91
|
-
*/
|
|
92
|
-
async delete(endpoint) {
|
|
93
|
-
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
|
94
|
-
method: "DELETE",
|
|
95
|
-
headers: getHeaders()
|
|
96
|
-
});
|
|
97
|
-
return handleResponse(response);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// lib/debug.ts
|
|
102
|
-
var DEBUG_ENABLED = typeof window !== "undefined" && (localStorage.getItem("debug") === "true" || process.env.NODE_ENV === "development");
|
|
103
|
-
function isDebugEnabled() {
|
|
104
|
-
return DEBUG_ENABLED;
|
|
105
|
-
}
|
|
106
|
-
function debug(...args) {
|
|
107
|
-
if (DEBUG_ENABLED) {
|
|
108
|
-
console.log("[DEBUG]", ...args);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function debugGroup(label) {
|
|
112
|
-
if (DEBUG_ENABLED) {
|
|
113
|
-
console.group(`[DEBUG] ${label}`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
function debugGroupEnd() {
|
|
117
|
-
if (DEBUG_ENABLED) {
|
|
118
|
-
console.groupEnd();
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function debugWarn(...args) {
|
|
122
|
-
if (DEBUG_ENABLED) {
|
|
123
|
-
console.warn("[DEBUG]", ...args);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
function debugError(...args) {
|
|
127
|
-
if (DEBUG_ENABLED) {
|
|
128
|
-
console.error("[DEBUG]", ...args);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function debugTable(data) {
|
|
132
|
-
if (DEBUG_ENABLED) {
|
|
133
|
-
console.table(data);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
function debugTime(label) {
|
|
137
|
-
if (DEBUG_ENABLED) {
|
|
138
|
-
console.time(`[DEBUG] ${label}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function debugTimeEnd(label) {
|
|
142
|
-
if (DEBUG_ENABLED) {
|
|
143
|
-
console.timeEnd(`[DEBUG] ${label}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
function debugInput(inputType, data) {
|
|
147
|
-
if (DEBUG_ENABLED) {
|
|
148
|
-
console.log(`[DEBUG:INPUT] ${inputType}:`, data);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function debugCollision(entityA, entityB, details) {
|
|
152
|
-
if (DEBUG_ENABLED) {
|
|
153
|
-
console.log(
|
|
154
|
-
`[DEBUG:COLLISION] ${entityA.type || entityA.id} <-> ${entityB.type || entityB.id}`,
|
|
155
|
-
details ?? ""
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
function debugPhysics(entityId, physics) {
|
|
160
|
-
if (DEBUG_ENABLED) {
|
|
161
|
-
console.log(`[DEBUG:PHYSICS] ${entityId}:`, physics);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function debugGameState(stateName, value) {
|
|
165
|
-
if (DEBUG_ENABLED) {
|
|
166
|
-
console.log(`[DEBUG:GAME_STATE] ${stateName}:`, value);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
1
|
+
export { cn, debug, debugCollision, debugError, debugGameState, debugGroup, debugGroupEnd, debugInput, debugPhysics, debugTable, debugTime, debugTimeEnd, debugWarn, formatNestedFieldLabel, getNestedValue, isDebugEnabled } from '../chunk-KKCVDUK7.js';
|
|
2
|
+
export { ApiError, apiClient } from '../chunk-XSEDIUM6.js';
|
|
3
|
+
import '../chunk-S7EYY36U.js';
|
|
169
4
|
|
|
170
5
|
// lib/debugUtils.ts
|
|
171
6
|
var DEBUG_STORAGE_KEY = "orbital-debug";
|
|
@@ -426,32 +261,685 @@ function clearTraits() {
|
|
|
426
261
|
notifyListeners4();
|
|
427
262
|
}
|
|
428
263
|
|
|
429
|
-
// lib/
|
|
430
|
-
function
|
|
431
|
-
if (
|
|
432
|
-
|
|
264
|
+
// lib/visualizer/index.ts
|
|
265
|
+
function formatSExprGuardToDomain(guard, _entityName) {
|
|
266
|
+
if (Array.isArray(guard)) {
|
|
267
|
+
const [op, ...args] = guard;
|
|
268
|
+
return `${op}(${args.map((a) => JSON.stringify(a)).join(", ")})`;
|
|
433
269
|
}
|
|
434
|
-
|
|
435
|
-
|
|
270
|
+
return JSON.stringify(guard);
|
|
271
|
+
}
|
|
272
|
+
function formatSExprEffectToDomain(effect, _entityName) {
|
|
273
|
+
if (Array.isArray(effect)) {
|
|
274
|
+
const [op, ...args] = effect;
|
|
275
|
+
return `${op}(${args.map((a) => JSON.stringify(a)).join(", ")})`;
|
|
436
276
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
277
|
+
return JSON.stringify(effect);
|
|
278
|
+
}
|
|
279
|
+
function isArraySExpr(expr) {
|
|
280
|
+
return Array.isArray(expr);
|
|
281
|
+
}
|
|
282
|
+
var DEFAULT_CONFIG = {
|
|
283
|
+
nodeRadius: 70,
|
|
284
|
+
nodeSpacing: 650,
|
|
285
|
+
// Increased to give more room for transitions
|
|
286
|
+
initialIndicatorOffset: 45,
|
|
287
|
+
arrowSize: 12,
|
|
288
|
+
colors: {
|
|
289
|
+
background: "#0d1117",
|
|
290
|
+
node: "#161b22",
|
|
291
|
+
nodeBorder: "#30363d",
|
|
292
|
+
nodeText: "#e6edf3",
|
|
293
|
+
initialNode: "#238636",
|
|
294
|
+
finalNode: "#f85149",
|
|
295
|
+
arrow: "#8b949e",
|
|
296
|
+
arrowText: "#8b949e",
|
|
297
|
+
effectText: "#ffb86c",
|
|
298
|
+
guardText: "#ff79c6",
|
|
299
|
+
initial: "#238636"
|
|
300
|
+
},
|
|
301
|
+
fonts: {
|
|
302
|
+
node: '18px "Inter", sans-serif',
|
|
303
|
+
event: '16px "JetBrains Mono", monospace',
|
|
304
|
+
effect: '14px "JetBrains Mono", monospace'
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
function isBinding(val) {
|
|
308
|
+
return typeof val === "string" && val.startsWith("@");
|
|
309
|
+
}
|
|
310
|
+
function parseBinding(binding) {
|
|
311
|
+
if (!isBinding(binding)) return null;
|
|
312
|
+
const withoutAt = binding.substring(1);
|
|
313
|
+
const parts = withoutAt.split(".");
|
|
314
|
+
return {
|
|
315
|
+
root: parts[0],
|
|
316
|
+
path: parts.slice(1),
|
|
317
|
+
raw: binding
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function formatGuard(guard) {
|
|
321
|
+
let text = "";
|
|
322
|
+
if (typeof guard === "string") {
|
|
323
|
+
text = guard;
|
|
324
|
+
} else if (Array.isArray(guard)) {
|
|
325
|
+
text = formatSExprCompact(guard);
|
|
326
|
+
}
|
|
327
|
+
return text ? `[${text}]` : "";
|
|
328
|
+
}
|
|
329
|
+
function formatGuardHuman(guard, entityName) {
|
|
330
|
+
if (!guard) return "";
|
|
331
|
+
if (typeof guard === "string") {
|
|
332
|
+
return `if ${guard}`;
|
|
333
|
+
}
|
|
334
|
+
if (isArraySExpr(guard)) {
|
|
335
|
+
return formatSExprGuardToDomain(guard);
|
|
336
|
+
}
|
|
337
|
+
return "";
|
|
338
|
+
}
|
|
339
|
+
function formatEffectsHuman(effects, entityName) {
|
|
340
|
+
if (!Array.isArray(effects) || effects.length === 0) return [];
|
|
341
|
+
return effects.map((effect) => {
|
|
342
|
+
if (isArraySExpr(effect)) {
|
|
343
|
+
return formatSExprEffectToDomain(effect);
|
|
344
|
+
}
|
|
345
|
+
return String(effect);
|
|
346
|
+
}).filter(Boolean);
|
|
347
|
+
}
|
|
348
|
+
function formatSExprCompact(expr) {
|
|
349
|
+
if (!Array.isArray(expr) || expr.length === 0) return "[]";
|
|
350
|
+
const op = expr[0];
|
|
351
|
+
const args = expr.slice(1);
|
|
352
|
+
const formattedArgs = args.map((a) => {
|
|
353
|
+
if (isBinding(a)) {
|
|
354
|
+
const parsed = parseBinding(a);
|
|
355
|
+
if (parsed && parsed.path.length > 0) {
|
|
356
|
+
return `${parsed.root}.${parsed.path.join(".")}`;
|
|
357
|
+
}
|
|
358
|
+
return parsed?.root || a;
|
|
359
|
+
}
|
|
360
|
+
if (typeof a === "string") return a;
|
|
361
|
+
if (typeof a === "number" || typeof a === "boolean") return String(a);
|
|
362
|
+
if (Array.isArray(a)) return formatSExprCompact(a);
|
|
363
|
+
return "{...}";
|
|
364
|
+
});
|
|
365
|
+
return `${op} ${formattedArgs.join(" ")}`;
|
|
366
|
+
}
|
|
367
|
+
function getEffectSummary(effects) {
|
|
368
|
+
if (!Array.isArray(effects) || effects.length === 0) return "";
|
|
369
|
+
const setFields = [];
|
|
370
|
+
const otherEffects = [];
|
|
371
|
+
effects.forEach((effect) => {
|
|
372
|
+
if (!Array.isArray(effect)) return;
|
|
373
|
+
const op = effect[0];
|
|
374
|
+
if (op === "set" && effect[1] && typeof effect[1] === "string") {
|
|
375
|
+
const parsed = parseBinding(effect[1]);
|
|
376
|
+
if (parsed && parsed.path.length > 0) {
|
|
377
|
+
setFields.push(parsed.path[parsed.path.length - 1]);
|
|
378
|
+
} else {
|
|
379
|
+
setFields.push("field");
|
|
380
|
+
}
|
|
381
|
+
} else {
|
|
382
|
+
otherEffects.push(effect);
|
|
442
383
|
}
|
|
443
|
-
|
|
444
|
-
|
|
384
|
+
});
|
|
385
|
+
const summaries = [];
|
|
386
|
+
if (setFields.length > 0) {
|
|
387
|
+
summaries.push(`\u2192 ${setFields.join(", ")}`);
|
|
388
|
+
}
|
|
389
|
+
otherEffects.forEach((effect) => {
|
|
390
|
+
const op = effect[0];
|
|
391
|
+
switch (op) {
|
|
392
|
+
case "emit":
|
|
393
|
+
summaries.push(`\u2191 ${effect[1] || "event"}`);
|
|
394
|
+
break;
|
|
395
|
+
case "notify":
|
|
396
|
+
summaries.push(`\u{1F4E7} ${effect[1] || ""}`);
|
|
397
|
+
break;
|
|
398
|
+
case "persist":
|
|
399
|
+
summaries.push(`\u{1F4BE} ${effect[1] || "save"}`);
|
|
400
|
+
break;
|
|
401
|
+
case "navigate":
|
|
402
|
+
summaries.push(`\u{1F517} nav`);
|
|
403
|
+
break;
|
|
404
|
+
case "spawn":
|
|
405
|
+
summaries.push(`+ ${effect[1] || "spawn"}`);
|
|
406
|
+
break;
|
|
407
|
+
case "despawn":
|
|
408
|
+
summaries.push(`- despawn`);
|
|
409
|
+
break;
|
|
410
|
+
default:
|
|
411
|
+
summaries.push(op);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
return summaries.join(" | ");
|
|
415
|
+
}
|
|
416
|
+
function extractOutputsFromTransitions(transitions) {
|
|
417
|
+
const outputs = /* @__PURE__ */ new Set();
|
|
418
|
+
transitions.forEach((t) => {
|
|
419
|
+
if (t.effects) {
|
|
420
|
+
t.effects.forEach((effect) => {
|
|
421
|
+
if (Array.isArray(effect)) {
|
|
422
|
+
const op = effect[0];
|
|
423
|
+
if (["emit", "notify", "persist", "navigate", "call-service"].includes(op)) {
|
|
424
|
+
if (isArraySExpr(effect)) {
|
|
425
|
+
const humanText = formatSExprEffectToDomain(effect);
|
|
426
|
+
outputs.add(humanText);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
return Array.from(outputs);
|
|
434
|
+
}
|
|
435
|
+
function getNodeRadius(stateName, config) {
|
|
436
|
+
const baseRadius = config.nodeRadius;
|
|
437
|
+
const textLength = stateName.length;
|
|
438
|
+
if (textLength > 12) return baseRadius + 25;
|
|
439
|
+
if (textLength > 8) return baseRadius + 15;
|
|
440
|
+
if (textLength > 6) return baseRadius + 8;
|
|
441
|
+
return baseRadius;
|
|
442
|
+
}
|
|
443
|
+
function calculateLayout(states, transitions, options, config) {
|
|
444
|
+
const positions = {};
|
|
445
|
+
const entityBoxWidth = options.hasEntity ? 200 : 0;
|
|
446
|
+
const outputBoxWidth = options.hasOutputs ? 200 : 0;
|
|
447
|
+
const leftOffset = 100 + entityBoxWidth;
|
|
448
|
+
const initialState = states.find((s) => s.isInitial) || states[0];
|
|
449
|
+
states.filter((s) => s.isFinal);
|
|
450
|
+
states.filter((s) => !s.isInitial && !s.isFinal);
|
|
451
|
+
let maxLabelLength = 0;
|
|
452
|
+
transitions.forEach((t) => {
|
|
453
|
+
if (t.effects && t.effects.length > 0) {
|
|
454
|
+
const summary = getEffectSummary(t.effects);
|
|
455
|
+
maxLabelLength = Math.max(maxLabelLength, summary.length);
|
|
456
|
+
}
|
|
457
|
+
if (t.guard) {
|
|
458
|
+
const guardStr = formatGuard(t.guard);
|
|
459
|
+
maxLabelLength = Math.max(maxLabelLength, guardStr.length);
|
|
460
|
+
}
|
|
461
|
+
if (t.event) {
|
|
462
|
+
maxLabelLength = Math.max(maxLabelLength, t.event.length);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
const labelWidth = Math.min(maxLabelLength * 10, 350);
|
|
466
|
+
const dynamicSpacing = Math.min(Math.max(config.nodeSpacing, labelWidth + 100), 400);
|
|
467
|
+
const stateColumn = {};
|
|
468
|
+
if (initialState) {
|
|
469
|
+
const queue = [{ name: initialState.name, col: 0 }];
|
|
470
|
+
const visited = /* @__PURE__ */ new Set();
|
|
471
|
+
while (queue.length > 0) {
|
|
472
|
+
const { name, col } = queue.shift();
|
|
473
|
+
if (visited.has(name)) continue;
|
|
474
|
+
visited.add(name);
|
|
475
|
+
if (stateColumn[name] === void 0) {
|
|
476
|
+
stateColumn[name] = col;
|
|
477
|
+
}
|
|
478
|
+
transitions.forEach((t) => {
|
|
479
|
+
if (t.from === name && t.from !== t.to && !visited.has(t.to)) {
|
|
480
|
+
queue.push({ name: t.to, col: col + 1 });
|
|
481
|
+
}
|
|
482
|
+
});
|
|
445
483
|
}
|
|
446
|
-
value = value[part];
|
|
447
484
|
}
|
|
448
|
-
|
|
485
|
+
states.forEach((s) => {
|
|
486
|
+
if (stateColumn[s.name] === void 0) {
|
|
487
|
+
stateColumn[s.name] = 0;
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
const columns = {};
|
|
491
|
+
Object.entries(stateColumn).forEach(([name, col]) => {
|
|
492
|
+
if (!columns[col]) columns[col] = [];
|
|
493
|
+
columns[col].push(name);
|
|
494
|
+
});
|
|
495
|
+
Object.values(columns).forEach((stateNames) => {
|
|
496
|
+
stateNames.sort((a, b) => {
|
|
497
|
+
const stateA = states.find((s) => s.name === a);
|
|
498
|
+
const stateB = states.find((s) => s.name === b);
|
|
499
|
+
if (stateA?.isInitial) return -1;
|
|
500
|
+
if (stateB?.isInitial) return 1;
|
|
501
|
+
if (stateA?.isFinal && !stateB?.isFinal) return 1;
|
|
502
|
+
if (stateB?.isFinal && !stateA?.isFinal) return -1;
|
|
503
|
+
return a.localeCompare(b);
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
const numColumns = Math.max(...Object.keys(columns).map(Number)) + 1;
|
|
507
|
+
const maxRowsInColumn = Math.max(...Object.values(columns).map((arr) => arr.length));
|
|
508
|
+
const minVerticalSpacing = 420;
|
|
509
|
+
const tooltipPadding = 150;
|
|
510
|
+
const width = Math.max(1400, numColumns * dynamicSpacing + entityBoxWidth + outputBoxWidth + 400);
|
|
511
|
+
const height = Math.max(1e3, maxRowsInColumn * minVerticalSpacing + 350 + tooltipPadding);
|
|
512
|
+
Object.entries(columns).forEach(([colStr, stateNames]) => {
|
|
513
|
+
const col = parseInt(colStr);
|
|
514
|
+
const x = leftOffset + col * dynamicSpacing;
|
|
515
|
+
const numInColumn = stateNames.length;
|
|
516
|
+
const verticalSpacing = Math.max(minVerticalSpacing, height / (numInColumn + 1));
|
|
517
|
+
stateNames.forEach((stateName, rowIndex) => {
|
|
518
|
+
const state = states.find((s) => s.name === stateName);
|
|
519
|
+
if (state) {
|
|
520
|
+
const y = verticalSpacing * (rowIndex + 1);
|
|
521
|
+
positions[stateName] = { x, y, state };
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
return { positions, width, height: height + 60 };
|
|
526
|
+
}
|
|
527
|
+
function escapeXml(unsafe) {
|
|
528
|
+
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
529
|
+
}
|
|
530
|
+
function createArrowMarkerSvg(id, color, config) {
|
|
531
|
+
return `<marker id="${id}" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="${config.arrowSize}" markerHeight="${config.arrowSize}" orient="auto-start-reverse">
|
|
532
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill="${color}"/>
|
|
533
|
+
</marker>`;
|
|
534
|
+
}
|
|
535
|
+
function drawStateSvg(name, x, y, state, config) {
|
|
536
|
+
const radius = getNodeRadius(name, config);
|
|
537
|
+
let borderColor = config.colors.nodeBorder;
|
|
538
|
+
let borderWidth = 2;
|
|
539
|
+
if (state.isInitial) {
|
|
540
|
+
borderColor = config.colors.initialNode;
|
|
541
|
+
borderWidth = 3;
|
|
542
|
+
} else if (state.isFinal) {
|
|
543
|
+
borderColor = config.colors.finalNode;
|
|
544
|
+
borderWidth = 3;
|
|
545
|
+
}
|
|
546
|
+
let svg = `<g class="state-node">
|
|
547
|
+
<circle cx="${x}" cy="${y}" r="${radius}" fill="${config.colors.node}" stroke="${borderColor}" stroke-width="${borderWidth}"/>`;
|
|
548
|
+
if (state.isFinal) {
|
|
549
|
+
svg += `<circle cx="${x}" cy="${y}" r="${radius - 6}" fill="none" stroke="${borderColor}" stroke-width="2"/>`;
|
|
550
|
+
}
|
|
551
|
+
if (state.isInitial) {
|
|
552
|
+
svg += `<path d="M ${x - radius - config.initialIndicatorOffset} ${y} L ${x - radius - 5} ${y}" stroke="${config.colors.initial}" stroke-width="2" fill="none" marker-end="url(#arrow-initial)"/>`;
|
|
553
|
+
}
|
|
554
|
+
svg += `<text x="${x}" y="${y + 7}" text-anchor="middle" fill="${config.colors.nodeText}" font-family="Inter, sans-serif" font-size="18px" font-weight="600">${escapeXml(name)}</text>`;
|
|
555
|
+
svg += `</g>`;
|
|
556
|
+
return svg;
|
|
557
|
+
}
|
|
558
|
+
function drawTransitionPathSvg(from, to, transitions, positions, config) {
|
|
559
|
+
const fromPos = positions[from];
|
|
560
|
+
const toPos = positions[to];
|
|
561
|
+
if (!fromPos || !toPos) return "";
|
|
562
|
+
const relevantTransitions = transitions.filter((t) => t.from === from && t.to === to);
|
|
563
|
+
if (relevantTransitions.length === 0) return "";
|
|
564
|
+
const fromRadius = getNodeRadius(from, config);
|
|
565
|
+
const toRadius = getNodeRadius(to, config);
|
|
566
|
+
const dx = toPos.x - fromPos.x;
|
|
567
|
+
const dy = toPos.y - fromPos.y;
|
|
568
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
569
|
+
if (dist === 0) return "";
|
|
570
|
+
const nx = dx / dist;
|
|
571
|
+
const ny = dy / dist;
|
|
572
|
+
const startX = fromPos.x + nx * fromRadius;
|
|
573
|
+
const startY = fromPos.y + ny * fromRadius;
|
|
574
|
+
const endX = toPos.x - nx * (toRadius + 5);
|
|
575
|
+
const endY = toPos.y - ny * (toRadius + 5);
|
|
576
|
+
const hasReverse = transitions.some((t) => t.from === to && t.to === from);
|
|
577
|
+
const isReverse = hasReverse && from > to;
|
|
578
|
+
const baseOffset = hasReverse ? 50 : 30;
|
|
579
|
+
const curveOffset = baseOffset + (relevantTransitions.length > 1 ? 20 : 0);
|
|
580
|
+
const curveDirection = isReverse ? 1 : -1;
|
|
581
|
+
const midX = (startX + endX) / 2;
|
|
582
|
+
const midY = (startY + endY) / 2 + curveOffset * curveDirection;
|
|
583
|
+
return `<path class="transition-path" data-from="${from}" data-to="${to}" d="M ${startX} ${startY} Q ${midX} ${midY} ${endX} ${endY}" stroke="${config.colors.arrow}" stroke-width="1.5" fill="none" marker-end="url(#arrow)"/>`;
|
|
584
|
+
}
|
|
585
|
+
function drawTransitionLabelsSvg(from, to, transitions, positions, config) {
|
|
586
|
+
const fromPos = positions[from];
|
|
587
|
+
const toPos = positions[to];
|
|
588
|
+
if (!fromPos || !toPos) return "";
|
|
589
|
+
const relevantTransitions = transitions.filter((t) => t.from === from && t.to === to);
|
|
590
|
+
if (relevantTransitions.length === 0) return "";
|
|
591
|
+
const fromRadius = getNodeRadius(from, config);
|
|
592
|
+
const toRadius = getNodeRadius(to, config);
|
|
593
|
+
const dx = toPos.x - fromPos.x;
|
|
594
|
+
const dy = toPos.y - fromPos.y;
|
|
595
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
596
|
+
if (dist === 0) return "";
|
|
597
|
+
const nx = dx / dist;
|
|
598
|
+
const ny = dy / dist;
|
|
599
|
+
const startX = fromPos.x + nx * fromRadius;
|
|
600
|
+
const startY = fromPos.y + ny * fromRadius;
|
|
601
|
+
const endX = toPos.x - nx * (toRadius + 5);
|
|
602
|
+
const endY = toPos.y - ny * (toRadius + 5);
|
|
603
|
+
const hasReverse = transitions.some((t) => t.from === to && t.to === from);
|
|
604
|
+
const isReverse = hasReverse && from > to;
|
|
605
|
+
const baseOffset = hasReverse ? 50 : 40;
|
|
606
|
+
const curveOffset = baseOffset + (relevantTransitions.length > 1 ? 25 : 0);
|
|
607
|
+
const curveDirection = isReverse ? 1 : -1;
|
|
608
|
+
const midX = (startX + endX) / 2;
|
|
609
|
+
const midY = (startY + endY) / 2 + curveOffset * curveDirection;
|
|
610
|
+
let svg = "";
|
|
611
|
+
relevantTransitions.forEach((transition, index) => {
|
|
612
|
+
const blockOffset = index * 60 * curveDirection;
|
|
613
|
+
const dataAttrs = `data-from="${from}" data-to="${to}" data-event="${transition.event}"`;
|
|
614
|
+
const labelY = midY + curveDirection * 5 + blockOffset;
|
|
615
|
+
svg += `<g class="transition-group" ${dataAttrs}>`;
|
|
616
|
+
svg += `<text class="transition-label transition-event" x="${midX}" y="${labelY}" text-anchor="middle" fill="${config.colors.arrowText}" font-family="JetBrains Mono, monospace" font-size="14px" font-weight="600">${escapeXml(transition.event)}</text>`;
|
|
617
|
+
const hasGuard = !!transition.guard;
|
|
618
|
+
const guardText = hasGuard ? formatGuardHuman(transition.guard) : "";
|
|
619
|
+
const effectLines = transition.effects ? formatEffectsHuman(transition.effects) : [];
|
|
620
|
+
const hasEffects = effectLines.length > 0;
|
|
621
|
+
if (hasGuard || hasEffects) {
|
|
622
|
+
const tooltipStartY = labelY + 20 * curveDirection;
|
|
623
|
+
const lineHeight = 18;
|
|
624
|
+
const padding = 12;
|
|
625
|
+
let maxTextWidth = 0;
|
|
626
|
+
if (guardText) maxTextWidth = Math.max(maxTextWidth, guardText.length * 7);
|
|
627
|
+
effectLines.forEach((line) => {
|
|
628
|
+
maxTextWidth = Math.max(maxTextWidth, line.length * 7);
|
|
629
|
+
});
|
|
630
|
+
const boxWidth = Math.max(180, Math.min(maxTextWidth + padding * 2 + 20, 400));
|
|
631
|
+
const numLines = (hasGuard ? 1 : 0) + effectLines.length;
|
|
632
|
+
const boxHeight = numLines * lineHeight + padding * 2;
|
|
633
|
+
const boxY = curveDirection > 0 ? tooltipStartY : tooltipStartY - boxHeight;
|
|
634
|
+
svg += `<g class="transition-detail">`;
|
|
635
|
+
svg += `<rect x="${midX - boxWidth / 2}" y="${boxY}" width="${boxWidth}" height="${boxHeight}" fill="rgba(22, 27, 34, 0.95)" stroke="${config.colors.nodeBorder}" stroke-width="1" rx="6"/>`;
|
|
636
|
+
let currentY = boxY + padding + 12;
|
|
637
|
+
if (hasGuard && guardText) {
|
|
638
|
+
svg += `<text x="${midX - boxWidth / 2 + padding}" y="${currentY}" fill="${config.colors.guardText}" font-family="Inter, sans-serif" font-size="12px">`;
|
|
639
|
+
svg += `<tspan font-weight="600">Guard:</tspan> ${escapeXml(guardText)}</text>`;
|
|
640
|
+
currentY += lineHeight;
|
|
641
|
+
}
|
|
642
|
+
if (hasEffects) {
|
|
643
|
+
effectLines.forEach((effectText, idx) => {
|
|
644
|
+
const prefix = idx === 0 ? "Then: " : " ";
|
|
645
|
+
svg += `<text x="${midX - boxWidth / 2 + padding}" y="${currentY}" fill="${config.colors.effectText}" font-family="Inter, sans-serif" font-size="12px">`;
|
|
646
|
+
svg += `<tspan font-weight="${idx === 0 ? "600" : "400"}">${prefix}</tspan>${escapeXml(effectText)}</text>`;
|
|
647
|
+
currentY += lineHeight;
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
svg += `</g>`;
|
|
651
|
+
}
|
|
652
|
+
svg += `</g>`;
|
|
653
|
+
});
|
|
654
|
+
return svg;
|
|
655
|
+
}
|
|
656
|
+
function drawEntityInputSvg(entity, x, y, _height) {
|
|
657
|
+
const fieldCount = entity.fields ? entity.fields.length : 0;
|
|
658
|
+
const boxWidth = 160;
|
|
659
|
+
const boxHeight = Math.max(80, fieldCount * 22 + 50);
|
|
660
|
+
const boxY = y - boxHeight / 2;
|
|
661
|
+
let svg = `<g class="entity-input">
|
|
662
|
+
<rect x="${x}" y="${boxY}" width="${boxWidth}" height="${boxHeight}" fill="#1a1f2e" stroke="#4a9eff" stroke-width="2" rx="8"/>
|
|
663
|
+
<text x="${x + boxWidth / 2}" y="${boxY + 24}" text-anchor="middle" fill="#4a9eff" font-family="Inter, sans-serif" font-size="14px" font-weight="600">\u{1F4E6} ${escapeXml(entity.name || "Entity")}</text>`;
|
|
664
|
+
if (entity.fields && entity.fields.length > 0) {
|
|
665
|
+
entity.fields.forEach((field, idx) => {
|
|
666
|
+
const fieldName = typeof field === "string" ? field : field.name;
|
|
667
|
+
svg += `<text x="${x + 12}" y="${boxY + 48 + idx * 20}" fill="#8b949e" font-family="JetBrains Mono, monospace" font-size="11px">\u2022 ${escapeXml(fieldName)}</text>`;
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
svg += `<path d="M ${x + boxWidth + 5} ${y} L ${x + boxWidth + 40} ${y}" stroke="#4a9eff" stroke-width="2" fill="none" marker-end="url(#arrow-input)"/>`;
|
|
671
|
+
svg += `</g>`;
|
|
672
|
+
return svg;
|
|
673
|
+
}
|
|
674
|
+
function drawOutputsSvg(outputs, x, y, height) {
|
|
675
|
+
if (!outputs || outputs.length === 0) return "";
|
|
676
|
+
const maxOutputLength = Math.max(...outputs.map((o) => o.length));
|
|
677
|
+
const boxWidth = Math.max(200, maxOutputLength * 7 + 30);
|
|
678
|
+
const lineHeight = 22;
|
|
679
|
+
const boxHeight = outputs.length * lineHeight + 50;
|
|
680
|
+
const boxY = y - boxHeight / 2;
|
|
681
|
+
let svg = `<g class="outputs">
|
|
682
|
+
<rect x="${x}" y="${boxY}" width="${boxWidth}" height="${boxHeight}" fill="#1a1f2e" stroke="#ffb86c" stroke-width="2" rx="8"/>
|
|
683
|
+
<text x="${x + boxWidth / 2}" y="${boxY + 24}" text-anchor="middle" fill="#ffb86c" font-family="Inter, sans-serif" font-size="13px" font-weight="600">\u{1F4E4} External Effects</text>`;
|
|
684
|
+
outputs.forEach((output, idx) => {
|
|
685
|
+
svg += `<text x="${x + 12}" y="${boxY + 48 + idx * lineHeight}" fill="#e6edf3" font-family="Inter, sans-serif" font-size="11px">\u2022 ${escapeXml(output)}</text>`;
|
|
686
|
+
});
|
|
687
|
+
svg += `</g>`;
|
|
688
|
+
return svg;
|
|
689
|
+
}
|
|
690
|
+
function drawLegendSvg(y, config) {
|
|
691
|
+
const items = [
|
|
692
|
+
{ label: "Initial", color: config.colors.initialNode },
|
|
693
|
+
{ label: "Final", color: config.colors.finalNode },
|
|
694
|
+
{ label: "State", color: config.colors.nodeBorder }
|
|
695
|
+
];
|
|
696
|
+
let svg = `<g class="legend">`;
|
|
697
|
+
let x = 20;
|
|
698
|
+
items.forEach((item) => {
|
|
699
|
+
svg += `<circle cx="${x}" cy="${y}" r="6" fill="${config.colors.node}" stroke="${item.color}" stroke-width="2"/>`;
|
|
700
|
+
svg += `<text x="${x + 12}" y="${y + 4}" fill="${config.colors.arrowText}" font-family="Inter, sans-serif" font-size="10px">${escapeXml(item.label)}</text>`;
|
|
701
|
+
x += 70;
|
|
702
|
+
});
|
|
703
|
+
svg += `</g>`;
|
|
704
|
+
return svg;
|
|
705
|
+
}
|
|
706
|
+
function renderStateMachineToSvg(stateMachine, options = {}, config = DEFAULT_CONFIG) {
|
|
707
|
+
const states = stateMachine.states || [];
|
|
708
|
+
const transitions = stateMachine.transitions || [];
|
|
709
|
+
const title = options.title || "";
|
|
710
|
+
const entity = options.entity;
|
|
711
|
+
const outputs = extractOutputsFromTransitions(transitions);
|
|
712
|
+
const layoutOptions = {
|
|
713
|
+
hasEntity: !!entity,
|
|
714
|
+
hasOutputs: outputs.length > 0
|
|
715
|
+
};
|
|
716
|
+
const { positions, width, height } = calculateLayout(states, transitions, layoutOptions, config);
|
|
717
|
+
let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height + 40}" viewBox="0 0 ${width} ${height + 40}" class="orbital-state-machine" style="display: block; max-width: none;">`;
|
|
718
|
+
svg += `<defs>`;
|
|
719
|
+
svg += createArrowMarkerSvg("arrow", config.colors.arrow, config);
|
|
720
|
+
svg += createArrowMarkerSvg("arrow-initial", config.colors.initial, config);
|
|
721
|
+
svg += createArrowMarkerSvg("arrow-input", "#4a9eff", config);
|
|
722
|
+
svg += createArrowMarkerSvg("arrow-output", "#ffb86c", config);
|
|
723
|
+
svg += `</defs>`;
|
|
724
|
+
svg += `<rect x="0" y="0" width="${width}" height="${height + 40}" fill="${config.colors.background}" rx="8"/>`;
|
|
725
|
+
if (title) {
|
|
726
|
+
svg += `<text x="${width / 2}" y="20" text-anchor="middle" fill="${config.colors.nodeText}" font-family="Inter, sans-serif" font-size="14px" font-weight="600">${escapeXml(title)}</text>`;
|
|
727
|
+
}
|
|
728
|
+
const offsetY = title ? 30 : 0;
|
|
729
|
+
svg += `<g transform="translate(0, ${offsetY})">`;
|
|
730
|
+
if (entity) {
|
|
731
|
+
svg += drawEntityInputSvg(entity, 20, height / 2);
|
|
732
|
+
}
|
|
733
|
+
const drawnPairs = /* @__PURE__ */ new Set();
|
|
734
|
+
transitions.forEach((transition) => {
|
|
735
|
+
const pairKey = `${transition.from}->${transition.to}`;
|
|
736
|
+
if (!drawnPairs.has(pairKey)) {
|
|
737
|
+
drawnPairs.add(pairKey);
|
|
738
|
+
svg += drawTransitionPathSvg(transition.from, transition.to, transitions, positions, config);
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
for (const [name, pos] of Object.entries(positions)) {
|
|
742
|
+
svg += drawStateSvg(name, pos.x, pos.y, pos.state, config);
|
|
743
|
+
}
|
|
744
|
+
drawnPairs.clear();
|
|
745
|
+
transitions.forEach((transition) => {
|
|
746
|
+
const pairKey = `${transition.from}->${transition.to}`;
|
|
747
|
+
if (!drawnPairs.has(pairKey)) {
|
|
748
|
+
drawnPairs.add(pairKey);
|
|
749
|
+
svg += drawTransitionLabelsSvg(transition.from, transition.to, transitions, positions, config);
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
if (outputs.length > 0) {
|
|
753
|
+
const maxX = Math.max(...Object.values(positions).map((p) => p.x));
|
|
754
|
+
svg += drawOutputsSvg(outputs, maxX + config.nodeRadius + 60, height / 2);
|
|
755
|
+
}
|
|
756
|
+
svg += `</g>`;
|
|
757
|
+
svg += drawLegendSvg(height + 25, config);
|
|
758
|
+
svg += `</svg>`;
|
|
759
|
+
return svg;
|
|
760
|
+
}
|
|
761
|
+
function extractStateMachine(data) {
|
|
762
|
+
if (!data || typeof data !== "object") return null;
|
|
763
|
+
const obj = data;
|
|
764
|
+
if (obj.states && obj.transitions) {
|
|
765
|
+
return obj;
|
|
766
|
+
}
|
|
767
|
+
if (obj.stateMachine) {
|
|
768
|
+
return obj.stateMachine;
|
|
769
|
+
}
|
|
770
|
+
if (Array.isArray(obj.traits)) {
|
|
771
|
+
const traitWithSM = obj.traits.find(
|
|
772
|
+
(t) => typeof t === "object" && t !== null && "stateMachine" in t
|
|
773
|
+
);
|
|
774
|
+
if (traitWithSM && typeof traitWithSM === "object" && "stateMachine" in traitWithSM) {
|
|
775
|
+
return traitWithSM.stateMachine;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
function calculateTransitionPathData(from, to, transitions, positions, config) {
|
|
781
|
+
const fromPos = positions[from];
|
|
782
|
+
const toPos = positions[to];
|
|
783
|
+
if (!fromPos || !toPos) return null;
|
|
784
|
+
const relevantTransitions = transitions.filter((t) => t.from === from && t.to === to);
|
|
785
|
+
if (relevantTransitions.length === 0) return null;
|
|
786
|
+
const fromRadius = getNodeRadius(from, config);
|
|
787
|
+
const toRadius = getNodeRadius(to, config);
|
|
788
|
+
if (from === to) {
|
|
789
|
+
const loopRadius = 50;
|
|
790
|
+
const cx = fromPos.x;
|
|
791
|
+
const cy = fromPos.y - fromRadius - loopRadius;
|
|
792
|
+
const startAngle = -0.5;
|
|
793
|
+
const endAngle = 0.5;
|
|
794
|
+
const startX2 = fromPos.x + Math.cos(-Math.PI / 2 + startAngle) * fromRadius;
|
|
795
|
+
const startY2 = fromPos.y + Math.sin(-Math.PI / 2 + startAngle) * fromRadius;
|
|
796
|
+
const endX2 = fromPos.x + Math.cos(-Math.PI / 2 + endAngle) * fromRadius;
|
|
797
|
+
const endY2 = fromPos.y + Math.sin(-Math.PI / 2 + endAngle) * fromRadius;
|
|
798
|
+
const pathData = `M ${startX2} ${startY2} A ${loopRadius} ${loopRadius} 0 1 1 ${endX2} ${endY2}`;
|
|
799
|
+
return {
|
|
800
|
+
pathData,
|
|
801
|
+
labelX: cx,
|
|
802
|
+
labelY: cy - loopRadius * 0.5
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
const dx = toPos.x - fromPos.x;
|
|
806
|
+
const dy = toPos.y - fromPos.y;
|
|
807
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
808
|
+
if (dist === 0) return null;
|
|
809
|
+
const nx = dx / dist;
|
|
810
|
+
const ny = dy / dist;
|
|
811
|
+
const startX = fromPos.x + nx * fromRadius;
|
|
812
|
+
const startY = fromPos.y + ny * fromRadius;
|
|
813
|
+
const endX = toPos.x - nx * (toRadius + 5);
|
|
814
|
+
const endY = toPos.y - ny * (toRadius + 5);
|
|
815
|
+
const hasReverse = transitions.some((t) => t.from === to && t.to === from);
|
|
816
|
+
const isReverse = hasReverse && from > to;
|
|
817
|
+
const baseOffset = hasReverse ? 50 : 30;
|
|
818
|
+
const curveOffset = baseOffset + (relevantTransitions.length > 1 ? 20 : 0);
|
|
819
|
+
const curveDirection = isReverse ? 1 : -1;
|
|
820
|
+
const midX = (startX + endX) / 2;
|
|
821
|
+
const midY = (startY + endY) / 2 + curveOffset * curveDirection;
|
|
822
|
+
return {
|
|
823
|
+
pathData: `M ${startX} ${startY} Q ${midX} ${midY} ${endX} ${endY}`,
|
|
824
|
+
labelX: midX,
|
|
825
|
+
labelY: midY + curveDirection * 5
|
|
826
|
+
};
|
|
449
827
|
}
|
|
450
|
-
function
|
|
451
|
-
const
|
|
452
|
-
|
|
828
|
+
function renderStateMachineToDomData(stateMachine, options = {}, config = DEFAULT_CONFIG) {
|
|
829
|
+
const states = stateMachine.states || [];
|
|
830
|
+
const transitions = stateMachine.transitions || [];
|
|
831
|
+
const title = options.title || "";
|
|
832
|
+
const entity = options.entity;
|
|
833
|
+
const outputs = extractOutputsFromTransitions(transitions);
|
|
834
|
+
const layoutOptions = {
|
|
835
|
+
hasEntity: !!entity,
|
|
836
|
+
hasOutputs: outputs.length > 0
|
|
837
|
+
};
|
|
838
|
+
const { positions, width, height } = calculateLayout(states, transitions, layoutOptions, config);
|
|
839
|
+
const domStates = Object.entries(positions).map(([name, pos]) => ({
|
|
840
|
+
id: `state-${name}`,
|
|
841
|
+
name,
|
|
842
|
+
x: pos.x,
|
|
843
|
+
y: pos.y,
|
|
844
|
+
radius: getNodeRadius(name, config),
|
|
845
|
+
isInitial: pos.state.isInitial ?? false,
|
|
846
|
+
isFinal: pos.state.isFinal ?? false,
|
|
847
|
+
description: pos.state.description
|
|
848
|
+
}));
|
|
849
|
+
const domPaths = [];
|
|
850
|
+
const domLabels = [];
|
|
851
|
+
const drawnPairs = /* @__PURE__ */ new Set();
|
|
852
|
+
transitions.forEach((transition, idx) => {
|
|
853
|
+
const pairKey = `${transition.from}->${transition.to}`;
|
|
854
|
+
if (!drawnPairs.has(pairKey)) {
|
|
855
|
+
drawnPairs.add(pairKey);
|
|
856
|
+
const pathData2 = calculateTransitionPathData(
|
|
857
|
+
transition.from,
|
|
858
|
+
transition.to,
|
|
859
|
+
transitions,
|
|
860
|
+
positions,
|
|
861
|
+
config
|
|
862
|
+
);
|
|
863
|
+
if (pathData2) {
|
|
864
|
+
domPaths.push({
|
|
865
|
+
id: `path-${transition.from}-${transition.to}`,
|
|
866
|
+
from: transition.from,
|
|
867
|
+
to: transition.to,
|
|
868
|
+
pathData: pathData2.pathData,
|
|
869
|
+
labelX: pathData2.labelX,
|
|
870
|
+
labelY: pathData2.labelY
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const guardText = transition.guard ? formatGuardHuman(transition.guard) : void 0;
|
|
875
|
+
const effectTexts = transition.effects ? formatEffectsHuman(transition.effects) : [];
|
|
876
|
+
const hasDetails = !!guardText || effectTexts.length > 0;
|
|
877
|
+
const pathData = calculateTransitionPathData(
|
|
878
|
+
transition.from,
|
|
879
|
+
transition.to,
|
|
880
|
+
transitions,
|
|
881
|
+
positions,
|
|
882
|
+
config
|
|
883
|
+
);
|
|
884
|
+
if (pathData) {
|
|
885
|
+
const sameEventIndex = domLabels.filter(
|
|
886
|
+
(l) => l.from === transition.from && l.to === transition.to
|
|
887
|
+
).length;
|
|
888
|
+
const labelOffset = sameEventIndex * 60;
|
|
889
|
+
domLabels.push({
|
|
890
|
+
id: `label-${transition.from}-${transition.to}-${idx}`,
|
|
891
|
+
from: transition.from,
|
|
892
|
+
to: transition.to,
|
|
893
|
+
event: transition.event,
|
|
894
|
+
x: pathData.labelX,
|
|
895
|
+
y: pathData.labelY + labelOffset,
|
|
896
|
+
guardText,
|
|
897
|
+
effectTexts,
|
|
898
|
+
hasDetails
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
let domEntity;
|
|
903
|
+
if (entity) {
|
|
904
|
+
const fieldCount = entity.fields ? entity.fields.length : 0;
|
|
905
|
+
const boxWidth = 160;
|
|
906
|
+
const boxHeight = Math.max(80, fieldCount * 22 + 50);
|
|
907
|
+
domEntity = {
|
|
908
|
+
name: entity.name || "Entity",
|
|
909
|
+
fields: entity.fields?.map((f) => typeof f === "string" ? f : f.name) || [],
|
|
910
|
+
x: 20,
|
|
911
|
+
y: height / 2 - boxHeight / 2,
|
|
912
|
+
width: boxWidth,
|
|
913
|
+
height: boxHeight
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
let domOutputs;
|
|
917
|
+
if (outputs.length > 0) {
|
|
918
|
+
const maxX = Math.max(...Object.values(positions).map((p) => p.x));
|
|
919
|
+
const maxOutputLength = Math.max(...outputs.map((o) => o.length));
|
|
920
|
+
const boxWidth = Math.max(200, maxOutputLength * 7 + 30);
|
|
921
|
+
const lineHeight = 22;
|
|
922
|
+
const boxHeight = outputs.length * lineHeight + 50;
|
|
923
|
+
domOutputs = {
|
|
924
|
+
outputs,
|
|
925
|
+
x: maxX + config.nodeRadius + 300,
|
|
926
|
+
// Increased further to avoid overlap with curved transitions
|
|
927
|
+
y: height / 2 - boxHeight / 2,
|
|
928
|
+
width: boxWidth,
|
|
929
|
+
height: boxHeight
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
return {
|
|
933
|
+
width,
|
|
934
|
+
height: height + 40,
|
|
935
|
+
title: title || void 0,
|
|
936
|
+
states: domStates,
|
|
937
|
+
paths: domPaths,
|
|
938
|
+
labels: domLabels,
|
|
939
|
+
entity: domEntity,
|
|
940
|
+
outputs: domOutputs,
|
|
941
|
+
config
|
|
942
|
+
};
|
|
453
943
|
}
|
|
454
944
|
|
|
455
|
-
export {
|
|
456
|
-
//# sourceMappingURL=index.js.map
|
|
457
|
-
//# sourceMappingURL=index.js.map
|
|
945
|
+
export { DEFAULT_CONFIG, clearDebugEvents, clearEntityProvider, clearGuardHistory, clearTicks, clearTraits, extractOutputsFromTransitions, extractStateMachine, formatGuard, getAllTicks, getAllTraits, getDebugEvents, getEffectSummary, getEntitiesByType, getEntityById, getEntitySnapshot, getEventsBySource, getEventsByType, getGuardEvaluationsForTrait, getGuardHistory, getRecentEvents, getRecentGuardEvaluations, getTick, getTrait, initDebugShortcut, logDebugEvent, logEffectExecuted, logError, logEventFired, logInfo, logStateChange, logWarning, onDebugToggle, recordGuardEvaluation, registerTick, registerTrait, renderStateMachineToDomData, renderStateMachineToSvg, setDebugEnabled, setEntityProvider, setTickActive, subscribeToDebugEvents, subscribeToGuardChanges, subscribeToTickChanges, subscribeToTraitChanges, toggleDebug, unregisterTick, unregisterTrait, updateGuardResult, updateTickExecution, updateTraitState };
|