@sebastianwessel/isostate 0.5.0 → 0.6.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/dist/animation/animation-engine.d.ts +2 -0
- package/dist/animation/animation-engine.d.ts.map +1 -1
- package/dist/animation/controller.d.ts +4 -2
- package/dist/animation/controller.d.ts.map +1 -1
- package/dist/assets/software-architecture/IMAGEGEN-PROMPTS.md +66 -0
- package/dist/assets/software-architecture/LICENSE +21 -0
- package/dist/assets/software-architecture/NOTICE.md +17 -0
- package/dist/assets/software-architecture/README.md +105 -0
- package/dist/assets/software-architecture/agentic/AGENT-VISUAL-PROMPTS.md +42 -0
- package/dist/assets/software-architecture/agentic/HANDOFF-PROMPTS.md +19 -0
- package/dist/assets/software-architecture/agentic/PROCESS-VISUAL-PROMPTS.md +109 -0
- package/dist/assets/software-architecture/agentic/agents-sprites.png +0 -0
- package/dist/assets/software-architecture/agentic/ai-capabilities-sprites.png +0 -0
- package/dist/assets/software-architecture/agentic/ai-to-human-handoff.png +0 -0
- package/dist/assets/software-architecture/agentic/channels-sprites.png +0 -0
- package/dist/assets/software-architecture/agentic/human-to-ai-handoff.png +0 -0
- package/dist/assets/software-architecture/agentic/humans-sprites.png +0 -0
- package/dist/assets/software-architecture/agentic/infrastructure-sprites.png +0 -0
- package/dist/assets/software-architecture/agentic/lifecycle-sprites.png +0 -0
- package/dist/assets/software-architecture/agentic/orchestration-sprites.png +0 -0
- package/dist/assets/software-architecture/agentic/providers-sprites.png +0 -0
- package/dist/assets/software-architecture/agentic/servicenow-sprites.png +0 -0
- package/dist/assets/software-architecture/architecture/architecture-sprites.png +0 -0
- package/dist/assets/software-architecture/manifest.json +2095 -0
- package/dist/assets/software-architecture/workflow/workflow-process-sprites.png +0 -0
- package/dist/browser/isostate.runtime.js +4 -4
- package/dist/browser/isostate.runtime.js.map +9 -8
- package/dist/chunks/errors-2yA81Vfb.js +23 -0
- package/dist/chunks/{errors-DyqEkrm5.js.map → errors-2yA81Vfb.js.map} +1 -1
- package/dist/chunks/index-WmsNtuaZ.js +1685 -0
- package/dist/chunks/index-WmsNtuaZ.js.map +1 -0
- package/dist/chunks/rendering-engine-DLaM1XbU.js +1425 -0
- package/dist/chunks/rendering-engine-DLaM1XbU.js.map +1 -0
- package/dist/chunks/scene-parser-BNXXIAqj.js +3033 -0
- package/dist/chunks/scene-parser-BNXXIAqj.js.map +1 -0
- package/dist/chunks/sha256-BkgWUdh1.js +123 -0
- package/dist/chunks/sha256-BkgWUdh1.js.map +1 -0
- package/dist/dsl/browser.js +3 -3
- package/dist/dsl/index.js +3 -3
- package/dist/dsl/scene-parser.d.ts.map +1 -1
- package/dist/dsl/scene-validator.d.ts.map +1 -1
- package/dist/dsl/workflow-effects-validation.d.ts +8 -0
- package/dist/dsl/workflow-effects-validation.d.ts.map +1 -0
- package/dist/editor-support/adapter.d.ts.map +1 -1
- package/dist/editor-support/index.js +347 -394
- package/dist/editor-support/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +356 -424
- package/dist/index.js.map +1 -1
- package/dist/rendering/animation-css.d.ts.map +1 -1
- package/dist/rendering/rendering-engine.d.ts +2 -0
- package/dist/rendering/rendering-engine.d.ts.map +1 -1
- package/dist/rendering/scene-effects.d.ts +15 -0
- package/dist/rendering/scene-effects.d.ts.map +1 -0
- package/dist/runtime/index.js +4 -4
- package/dist/runtime/interactivity.d.ts.map +1 -1
- package/dist/types/asset-registry.d.ts.map +1 -1
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/node.d.ts +40 -1
- package/dist/types/node.d.ts.map +1 -1
- package/package.json +9 -8
- package/dist/chunks/errors-DyqEkrm5.js +0 -29
- package/dist/chunks/index-CB6XEsbY.js +0 -1756
- package/dist/chunks/index-CB6XEsbY.js.map +0 -1
- package/dist/chunks/rendering-engine-nkqYx0qc.js +0 -1303
- package/dist/chunks/rendering-engine-nkqYx0qc.js.map +0 -1
- package/dist/chunks/scene-parser-F69XGE0l.js +0 -2743
- package/dist/chunks/scene-parser-F69XGE0l.js.map +0 -1
- package/dist/chunks/sha256-4JuurR-b.js +0 -69
- package/dist/chunks/sha256-4JuurR-b.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,52 +1,42 @@
|
|
|
1
|
-
export { A as AnimationController, a as AnimationEngine, e as easeInCubic, b as easeInOutCubic, c as easeOutCubic, l as linear, m as mountScene, r as resolveEasing } from './chunks/index-
|
|
2
|
-
import { g as getResolvedProjectionLayout, a as getResolvedViewBox, f as projectToScreen, e as updateElementTransforms,
|
|
3
|
-
export { A as AssetRegistryImpl, D as DEFAULT_CELL_SIZE, i as applyThemeToElement, j as buildSceneDOM, k as calculateTransform, l as calculateVisualSize, m as composeTheme, n as createAssetRegistry, o as createDefaultRegistry, r as resolveTheme } from './chunks/rendering-engine-
|
|
4
|
-
import { R as RenderError } from './chunks/errors-
|
|
5
|
-
export { A as AnimationError, C as ControllerError, P as ParseError, V as ValidationErrorClass } from './chunks/errors-
|
|
6
|
-
import './chunks/sha256-
|
|
1
|
+
export { A as AnimationController, a as AnimationEngine, e as easeInCubic, b as easeInOutCubic, c as easeOutCubic, l as linear, m as mountScene, r as resolveEasing } from './chunks/index-WmsNtuaZ.js';
|
|
2
|
+
import { g as getResolvedProjectionLayout, a as getResolvedViewBox, f as projectToScreen, e as updateElementTransforms, b as getElementState, h as hideElementAfterExit, u as unhideElementOnReadd, d as getConnectorState } from './chunks/rendering-engine-DLaM1XbU.js';
|
|
3
|
+
export { A as AssetRegistryImpl, D as DEFAULT_CELL_SIZE, i as applyThemeToElement, j as buildSceneDOM, k as calculateTransform, l as calculateVisualSize, m as composeTheme, n as createAssetRegistry, o as createDefaultRegistry, r as resolveTheme } from './chunks/rendering-engine-DLaM1XbU.js';
|
|
4
|
+
import { R as RenderError } from './chunks/errors-2yA81Vfb.js';
|
|
5
|
+
export { A as AnimationError, C as ControllerError, P as ParseError, V as ValidationErrorClass } from './chunks/errors-2yA81Vfb.js';
|
|
6
|
+
import './chunks/sha256-BkgWUdh1.js';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"none",
|
|
8
|
+
const ENTRY_ANIMATIONS = /* @__PURE__ */ new Set([
|
|
9
|
+
"fade-in",
|
|
10
|
+
"fade-in-grow",
|
|
11
|
+
"fall-in",
|
|
12
|
+
"rise-from-ground",
|
|
13
|
+
"slide-in-left",
|
|
14
|
+
"slide-in-right",
|
|
15
|
+
"flip-in",
|
|
16
|
+
"none"
|
|
18
17
|
]);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"none",
|
|
18
|
+
const EXIT_ANIMATIONS = /* @__PURE__ */ new Set([
|
|
19
|
+
"fade-out",
|
|
20
|
+
"fade-out-shrink",
|
|
21
|
+
"fall-through-ground",
|
|
22
|
+
"rise-away",
|
|
23
|
+
"slide-out-left",
|
|
24
|
+
"slide-out-right",
|
|
25
|
+
"flip-out",
|
|
26
|
+
"none"
|
|
29
27
|
]);
|
|
30
|
-
|
|
31
|
-
const LIFECYCLE_STATUSES = new Set(["entering", "present", "exiting", "removed"]);
|
|
32
|
-
// ── Type guards ──────────────────────────────────────────────────────────────
|
|
33
|
-
/** Narrow a raw string to EntryAnimation. Returns undefined if invalid. */
|
|
28
|
+
const LIFECYCLE_STATUSES = /* @__PURE__ */ new Set(["entering", "present", "exiting", "removed"]);
|
|
34
29
|
function guardEntryAnimation(v) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return ENTRY_ANIMATIONS.has(v) ? v : undefined;
|
|
30
|
+
if (typeof v !== "string") return void 0;
|
|
31
|
+
return ENTRY_ANIMATIONS.has(v) ? v : void 0;
|
|
38
32
|
}
|
|
39
|
-
/** Narrow a raw string to ExitAnimation. Returns undefined if invalid. */
|
|
40
33
|
function guardExitAnimation(v) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return EXIT_ANIMATIONS.has(v) ? v : undefined;
|
|
34
|
+
if (typeof v !== "string") return void 0;
|
|
35
|
+
return EXIT_ANIMATIONS.has(v) ? v : void 0;
|
|
44
36
|
}
|
|
45
|
-
/** Narrow a raw string to LifecycleStatus. Returns undefined if invalid. */
|
|
46
37
|
function guardLifecycleStatus(v) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return LIFECYCLE_STATUSES.has(v) ? v : undefined;
|
|
38
|
+
if (typeof v !== "string") return void 0;
|
|
39
|
+
return LIFECYCLE_STATUSES.has(v) ? v : void 0;
|
|
50
40
|
}
|
|
51
41
|
|
|
52
42
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
@@ -55,462 +45,404 @@ const GRID_STROKE = "var(--iso-diag-grid, rgba(37, 99, 235, 0.35))";
|
|
|
55
45
|
const TEXT_FILL = "var(--iso-diag-text, #1e3a8a)";
|
|
56
46
|
const ANCHOR_FILL = "var(--iso-diag-anchor, #dc2626)";
|
|
57
47
|
const ROUTE_FILL = "var(--iso-diag-route, #059669)";
|
|
58
|
-
const registryBySvg = new WeakMap();
|
|
59
|
-
/**
|
|
60
|
-
* Attach a development-time diagnostics overlay (grid, coordinates, element
|
|
61
|
-
* anchors, connector route points, and a scene/progress readout) to a mounted
|
|
62
|
-
* scene. See `specs/02-capabilities/diagnostics-overlay.md` for the full
|
|
63
|
-
* normative behavior.
|
|
64
|
-
*/
|
|
48
|
+
const registryBySvg = /* @__PURE__ */ new WeakMap();
|
|
65
49
|
function attachDiagnosticsOverlay(mounted, options = {}) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
registry.current = { token, handle };
|
|
95
|
-
return handle;
|
|
50
|
+
assertMounted(mounted);
|
|
51
|
+
const registry = registryBySvg.get(mounted.svg) ?? {};
|
|
52
|
+
registryBySvg.set(mounted.svg, registry);
|
|
53
|
+
registry.current?.handle.destroy();
|
|
54
|
+
const token = /* @__PURE__ */ Symbol("diagnostics-overlay");
|
|
55
|
+
const group = document.createElementNS(SVG_NS, "g");
|
|
56
|
+
group.setAttribute(DIAGNOSTICS_ATTR, "");
|
|
57
|
+
mounted.svg.appendChild(group);
|
|
58
|
+
const render = () => renderOverlay(group, mounted, options);
|
|
59
|
+
render();
|
|
60
|
+
const unsubscribe = subscribeToLiveUpdates(mounted, render);
|
|
61
|
+
const isCurrent = () => registry.current?.token === token;
|
|
62
|
+
const handle = {
|
|
63
|
+
update() {
|
|
64
|
+
if (!isCurrent()) return;
|
|
65
|
+
render();
|
|
66
|
+
},
|
|
67
|
+
destroy() {
|
|
68
|
+
if (!isCurrent()) return;
|
|
69
|
+
unsubscribe();
|
|
70
|
+
group.parentNode?.removeChild(group);
|
|
71
|
+
registry.current = void 0;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
registry.current = { token, handle };
|
|
75
|
+
return handle;
|
|
96
76
|
}
|
|
97
77
|
function assertMounted(mounted) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
78
|
+
if (!mounted.svg.isConnected) {
|
|
79
|
+
throw new RenderError("MOUNT_DESTROYED", "Cannot attach a diagnostics overlay to a destroyed mount");
|
|
80
|
+
}
|
|
101
81
|
}
|
|
102
|
-
/**
|
|
103
|
-
* When the mount has a controller, re-render on `progress-change` and
|
|
104
|
-
* `camera-change` so the overlay tracks live scroll/camera state. Without a
|
|
105
|
-
* controller, consumers call `update()` manually — returns a no-op teardown.
|
|
106
|
-
*/
|
|
107
82
|
function subscribeToLiveUpdates(mounted, render) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
// ── Rendering ────────────────────────────────────────────────────────────────
|
|
83
|
+
const controller = mounted.controller;
|
|
84
|
+
if (!controller) return () => void 0;
|
|
85
|
+
const onProgressChange = () => render();
|
|
86
|
+
const onCameraChange = () => render();
|
|
87
|
+
controller.on("progress-change", onProgressChange);
|
|
88
|
+
controller.on("camera-change", onCameraChange);
|
|
89
|
+
return () => {
|
|
90
|
+
controller.off("progress-change", onProgressChange);
|
|
91
|
+
controller.off("camera-change", onCameraChange);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
121
94
|
function renderOverlay(group, mounted, options) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
95
|
+
clearChildren(group);
|
|
96
|
+
const bundle = mounted.engine.bundle;
|
|
97
|
+
if (!bundle) return;
|
|
98
|
+
const layout = getResolvedProjectionLayout(bundle);
|
|
99
|
+
const project = (x, y) => {
|
|
100
|
+
const screen = projectToScreen(
|
|
101
|
+
x,
|
|
102
|
+
y,
|
|
103
|
+
layout.cellSize,
|
|
104
|
+
layout.selectedBounds.minX,
|
|
105
|
+
layout.selectedBounds.minY,
|
|
106
|
+
layout.padding.x,
|
|
107
|
+
layout.padding.y
|
|
108
|
+
);
|
|
109
|
+
return { x: screen.screenX, y: screen.screenY };
|
|
110
|
+
};
|
|
111
|
+
const showGrid = options.grid ?? true;
|
|
112
|
+
const showCoordinates = options.coordinates ?? false;
|
|
113
|
+
const showAnchors = options.anchors ?? true;
|
|
114
|
+
const showRoutes = options.routes ?? true;
|
|
115
|
+
const showReadout = options.readout ?? true;
|
|
116
|
+
if (showGrid) appendGrid(group, bundle, project);
|
|
117
|
+
if (showCoordinates) appendCoordinates(group, bundle, project);
|
|
118
|
+
if (showAnchors) appendAnchors(group, mounted, project);
|
|
119
|
+
if (showRoutes) appendRoutes(group, mounted, project);
|
|
120
|
+
if (showReadout) appendReadout(group, mounted, bundle);
|
|
146
121
|
}
|
|
147
122
|
function appendGrid(group, bundle, project) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
123
|
+
const [originX, originY] = bundle.floor.origin;
|
|
124
|
+
const [columns, rows] = bundle.floor.size;
|
|
125
|
+
for (let x = 0; x <= columns; x++) {
|
|
126
|
+
appendGridLine(group, project(originX + x, originY), project(originX + x, originY + rows));
|
|
127
|
+
}
|
|
128
|
+
for (let y = 0; y <= rows; y++) {
|
|
129
|
+
appendGridLine(group, project(originX, originY + y), project(originX + columns, originY + y));
|
|
130
|
+
}
|
|
156
131
|
}
|
|
157
132
|
function appendGridLine(group, start, end) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
133
|
+
const path = document.createElementNS(SVG_NS, "path");
|
|
134
|
+
path.setAttribute("d", `M ${start.x} ${start.y} L ${end.x} ${end.y}`);
|
|
135
|
+
path.setAttribute("stroke", GRID_STROKE);
|
|
136
|
+
path.setAttribute("stroke-width", "1");
|
|
137
|
+
path.setAttribute("fill", "none");
|
|
138
|
+
path.setAttribute("vector-effect", "non-scaling-stroke");
|
|
139
|
+
group.appendChild(path);
|
|
165
140
|
}
|
|
166
141
|
function appendCoordinates(group, bundle, project) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
142
|
+
const [originX, originY] = bundle.floor.origin;
|
|
143
|
+
const [columns, rows] = bundle.floor.size;
|
|
144
|
+
for (let x = 0; x <= columns; x++) {
|
|
145
|
+
for (let y = 0; y <= rows; y++) {
|
|
146
|
+
const gridX = originX + x;
|
|
147
|
+
const gridY = originY + y;
|
|
148
|
+
const point = project(gridX, gridY);
|
|
149
|
+
const text = document.createElementNS(SVG_NS, "text");
|
|
150
|
+
text.setAttribute("x", String(point.x));
|
|
151
|
+
text.setAttribute("y", String(point.y));
|
|
152
|
+
text.setAttribute("font-size", "8");
|
|
153
|
+
text.setAttribute("fill", TEXT_FILL);
|
|
154
|
+
text.textContent = `${gridX},${gridY}`;
|
|
155
|
+
group.appendChild(text);
|
|
182
156
|
}
|
|
157
|
+
}
|
|
183
158
|
}
|
|
184
159
|
function appendAnchors(group, mounted, project) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
circle.setAttribute("cy", String(point.y));
|
|
196
|
-
circle.setAttribute("r", "3");
|
|
197
|
-
circle.setAttribute("fill", ANCHOR_FILL);
|
|
198
|
-
group.appendChild(circle);
|
|
199
|
-
}
|
|
160
|
+
for (const element of mounted.engine.getFrameUpdates()) {
|
|
161
|
+
if (element.lifecycle === "removed") continue;
|
|
162
|
+
const point = project(element.pos[0] + element.size, element.pos[1] + element.size);
|
|
163
|
+
const circle = document.createElementNS(SVG_NS, "circle");
|
|
164
|
+
circle.setAttribute("cx", String(point.x));
|
|
165
|
+
circle.setAttribute("cy", String(point.y));
|
|
166
|
+
circle.setAttribute("r", "3");
|
|
167
|
+
circle.setAttribute("fill", ANCHOR_FILL);
|
|
168
|
+
group.appendChild(circle);
|
|
169
|
+
}
|
|
200
170
|
}
|
|
201
171
|
function appendRoutes(group, mounted, project) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
group.appendChild(rect);
|
|
214
|
-
}
|
|
172
|
+
for (const connector of mounted.engine.getConnectorFrameUpdates()) {
|
|
173
|
+
if (connector.lifecycle === "removed") continue;
|
|
174
|
+
for (const [x, y] of connector.route) {
|
|
175
|
+
const point = project(x, y);
|
|
176
|
+
const rect = document.createElementNS(SVG_NS, "rect");
|
|
177
|
+
rect.setAttribute("x", String(point.x - 2));
|
|
178
|
+
rect.setAttribute("y", String(point.y - 2));
|
|
179
|
+
rect.setAttribute("width", "4");
|
|
180
|
+
rect.setAttribute("height", "4");
|
|
181
|
+
rect.setAttribute("fill", ROUTE_FILL);
|
|
182
|
+
group.appendChild(rect);
|
|
215
183
|
}
|
|
184
|
+
}
|
|
216
185
|
}
|
|
217
186
|
function appendReadout(group, mounted, bundle) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
text.setAttribute("fill", TEXT_FILL);
|
|
229
|
-
text.textContent = sceneId
|
|
230
|
-
? `scene ${sceneId} · progress ${roundProgress(progress)}`
|
|
231
|
-
: `progress ${roundProgress(progress)}`;
|
|
232
|
-
group.appendChild(text);
|
|
187
|
+
const viewBox = mounted.controller ? mounted.controller.getCameraState().viewBox : getResolvedViewBox(bundle);
|
|
188
|
+
const progress = mounted.controller ? mounted.controller.getProgress() : mounted.engine.getProgress();
|
|
189
|
+
const sceneId = mounted.controller ? mounted.engine.getCurrentState()?.id : void 0;
|
|
190
|
+
const text = document.createElementNS(SVG_NS, "text");
|
|
191
|
+
text.setAttribute("x", String(viewBox.minX + 4));
|
|
192
|
+
text.setAttribute("y", String(viewBox.minY + 10));
|
|
193
|
+
text.setAttribute("font-size", "10");
|
|
194
|
+
text.setAttribute("fill", TEXT_FILL);
|
|
195
|
+
text.textContent = sceneId ? `scene ${sceneId} \xB7 progress ${roundProgress(progress)}` : `progress ${roundProgress(progress)}`;
|
|
196
|
+
group.appendChild(text);
|
|
233
197
|
}
|
|
234
198
|
function roundProgress(progress) {
|
|
235
|
-
|
|
199
|
+
return Math.round(progress * 1e3) / 1e3;
|
|
236
200
|
}
|
|
237
201
|
function clearChildren(node) {
|
|
238
|
-
|
|
239
|
-
node.removeChild(node.firstChild);
|
|
202
|
+
while (node.firstChild) node.removeChild(node.firstChild);
|
|
240
203
|
}
|
|
241
204
|
|
|
242
205
|
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
243
206
|
const XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
|
|
244
207
|
const DIAGNOSTICS_OVERLAY_SELECTOR = "[data-iso-diagnostics]";
|
|
245
|
-
/**
|
|
246
|
-
* Serialize a mounted scene to a standalone SVG document string.
|
|
247
|
-
*
|
|
248
|
-
* The exported SVG carries the runtime stylesheet and current visual state
|
|
249
|
-
* as-is. Ambient/entry/exit CSS animations are not restarted or awaited by
|
|
250
|
-
* the export; the snapshot reflects the un-animated base state at the
|
|
251
|
-
* resolved progress.
|
|
252
|
-
*/
|
|
253
208
|
async function exportSceneSvg(mounted, options = {}) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
209
|
+
assertNotDestroyed(mounted);
|
|
210
|
+
assertValidSnapshotOptions(options);
|
|
211
|
+
return withRestoredProgress(mounted, options.progress, async () => {
|
|
212
|
+
const clone = prepareClone(mounted.svg, options);
|
|
213
|
+
if (options.inlineAssets ?? true) {
|
|
214
|
+
await inlineImageAssets(clone);
|
|
215
|
+
}
|
|
216
|
+
return serializeSvg(clone);
|
|
217
|
+
});
|
|
263
218
|
}
|
|
264
|
-
/** Rasterize a mounted scene to a PNG blob. */
|
|
265
219
|
async function exportScenePng(mounted, options = {}) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
});
|
|
220
|
+
assertNotDestroyed(mounted);
|
|
221
|
+
assertValidPngSnapshotOptions(options);
|
|
222
|
+
const scale = options.scale ?? 2;
|
|
223
|
+
return withRestoredProgress(mounted, options.progress, async () => {
|
|
224
|
+
const clone = prepareClone(mounted.svg, options);
|
|
225
|
+
await inlineImageAssets(clone);
|
|
226
|
+
const svgString = serializeSvg(clone);
|
|
227
|
+
const viewBox = readViewBox(clone);
|
|
228
|
+
return rasterize(svgString, viewBox, scale);
|
|
229
|
+
});
|
|
277
230
|
}
|
|
278
|
-
// ── Shared step 1-3: destroyed check, option validation, progress restore ──
|
|
279
231
|
function assertNotDestroyed(mounted) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
232
|
+
if (!mounted.svg.isConnected) {
|
|
233
|
+
throw new RenderError("EXPORT_TARGET_DESTROYED", "Cannot export a scene whose mount was destroyed");
|
|
234
|
+
}
|
|
283
235
|
}
|
|
284
236
|
function assertValidSnapshotOptions(options) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
237
|
+
if (options.progress !== void 0 && !isValidProgress(options.progress)) {
|
|
238
|
+
throw new RenderError("EXPORT_INVALID_OPTIONS", "progress must be a finite number within [0, 1]", {
|
|
239
|
+
progress: options.progress
|
|
240
|
+
});
|
|
241
|
+
}
|
|
290
242
|
}
|
|
291
243
|
function assertValidPngSnapshotOptions(options) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
244
|
+
assertValidSnapshotOptions(options);
|
|
245
|
+
if (options.scale !== void 0 && !isValidScale(options.scale)) {
|
|
246
|
+
throw new RenderError("EXPORT_INVALID_OPTIONS", "scale must be a finite number greater than 0", {
|
|
247
|
+
scale: options.scale
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (options.inlineAssets === false) {
|
|
251
|
+
throw new RenderError(
|
|
252
|
+
"EXPORT_INVALID_OPTIONS",
|
|
253
|
+
"exportScenePng() always inlines assets; inlineAssets: false is unsupported for PNG export"
|
|
254
|
+
);
|
|
255
|
+
}
|
|
301
256
|
}
|
|
302
257
|
function isValidProgress(progress) {
|
|
303
|
-
|
|
258
|
+
return Number.isFinite(progress) && progress >= 0 && progress <= 1;
|
|
304
259
|
}
|
|
305
260
|
function isValidScale(scale) {
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Runs `run` at the requested `progress`, restoring the engine's prior
|
|
310
|
-
* progress afterwards on every code path (success or rejection). When
|
|
311
|
-
* `progress` is omitted, `run` executes at the scene's current progress and
|
|
312
|
-
* no restoration is needed.
|
|
313
|
-
*/
|
|
261
|
+
return Number.isFinite(scale) && scale > 0;
|
|
262
|
+
}
|
|
314
263
|
async function withRestoredProgress(mounted, progress, run) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
264
|
+
if (progress === void 0) {
|
|
265
|
+
return run();
|
|
266
|
+
}
|
|
267
|
+
const previousProgress = mounted.engine.getProgress();
|
|
268
|
+
try {
|
|
269
|
+
mounted.engine.setProgress(progress);
|
|
270
|
+
applyControllerFrame(mounted);
|
|
271
|
+
return await run();
|
|
272
|
+
} finally {
|
|
273
|
+
mounted.engine.setProgress(previousProgress);
|
|
274
|
+
applyControllerFrame(mounted);
|
|
275
|
+
}
|
|
328
276
|
}
|
|
329
|
-
/**
|
|
330
|
-
* Applies the engine's current frame to the live SVG so DOM state (element
|
|
331
|
-
* transforms, generated content, connector routes, and lifecycle visibility)
|
|
332
|
-
* matches the engine's resolved progress. Mirrors the non-animated portion of
|
|
333
|
-
* `AnimationController`'s frame-apply path: transforms/content via
|
|
334
|
-
* `updateElementTransforms()`, plus visibility driven by the resolved
|
|
335
|
-
* lifecycle state. CSS entry/exit animation classes are intentionally not
|
|
336
|
-
* (re)triggered here, matching the spec's requirement that ambient/entry
|
|
337
|
-
* animations are irrelevant to export output.
|
|
338
|
-
*/
|
|
339
277
|
function applyControllerFrame(mounted) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
278
|
+
const svg = mounted.svg;
|
|
279
|
+
const elementUpdates = mounted.engine.getFrameUpdates().map(
|
|
280
|
+
(update) => ({
|
|
281
|
+
id: update.id,
|
|
282
|
+
asset: update.asset,
|
|
283
|
+
pos: update.pos,
|
|
284
|
+
size: update.size,
|
|
285
|
+
layer: update.layer,
|
|
286
|
+
presence: update.lifecycle,
|
|
287
|
+
enter: update.entry,
|
|
288
|
+
exit: update.exit,
|
|
289
|
+
ambient: update.ambient,
|
|
290
|
+
text: update.text,
|
|
291
|
+
primitive: update.primitive,
|
|
292
|
+
activity: update.activity
|
|
293
|
+
})
|
|
294
|
+
);
|
|
295
|
+
const connectorUpdates = mounted.engine.getConnectorFrameUpdates().map(
|
|
296
|
+
(update) => ({
|
|
297
|
+
id: update.id,
|
|
298
|
+
route: update.route,
|
|
299
|
+
layer: update.layer,
|
|
300
|
+
presence: update.lifecycle,
|
|
301
|
+
style: update.style,
|
|
302
|
+
start: update.start,
|
|
303
|
+
end: update.end,
|
|
304
|
+
direction: update.direction,
|
|
305
|
+
message: update.message,
|
|
306
|
+
enter: update.entry,
|
|
307
|
+
exit: update.exit,
|
|
308
|
+
ambient: update.ambient
|
|
309
|
+
})
|
|
310
|
+
);
|
|
311
|
+
updateElementTransforms(svg, elementUpdates, connectorUpdates);
|
|
312
|
+
applyElementVisibility(svg, elementUpdates);
|
|
313
|
+
applyConnectorVisibility(svg, connectorUpdates);
|
|
370
314
|
}
|
|
371
315
|
function applyElementVisibility(svg, elements) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
state.isHidden = false;
|
|
382
|
-
unhideElementOnReadd(state.node);
|
|
383
|
-
}
|
|
316
|
+
for (const element of elements) {
|
|
317
|
+
const state = getElementState(svg, element.id);
|
|
318
|
+
if (!state) continue;
|
|
319
|
+
if (element.presence === "removed") {
|
|
320
|
+
state.isHidden = true;
|
|
321
|
+
hideElementAfterExit(state.node);
|
|
322
|
+
} else if (state.isHidden) {
|
|
323
|
+
state.isHidden = false;
|
|
324
|
+
unhideElementOnReadd(state.node);
|
|
384
325
|
}
|
|
326
|
+
}
|
|
385
327
|
}
|
|
386
328
|
function applyConnectorVisibility(svg, connectors) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
state.isHidden = false;
|
|
397
|
-
unhideElementOnReadd(state.node);
|
|
398
|
-
}
|
|
329
|
+
for (const connector of connectors) {
|
|
330
|
+
const state = getConnectorState(svg, connector.id);
|
|
331
|
+
if (!state) continue;
|
|
332
|
+
if (connector.presence === "removed") {
|
|
333
|
+
state.isHidden = true;
|
|
334
|
+
hideElementAfterExit(state.node);
|
|
335
|
+
} else if (state.isHidden) {
|
|
336
|
+
state.isHidden = false;
|
|
337
|
+
unhideElementOnReadd(state.node);
|
|
399
338
|
}
|
|
339
|
+
}
|
|
400
340
|
}
|
|
401
|
-
// ── Step 4-5: clone and prepare ────────────────────────────────────────────
|
|
402
341
|
function prepareClone(svg, options) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
342
|
+
const clone = svg.cloneNode(true);
|
|
343
|
+
const viewBox = readViewBox(clone);
|
|
344
|
+
clone.setAttribute("width", String(viewBox.width));
|
|
345
|
+
clone.setAttribute("height", String(viewBox.height));
|
|
346
|
+
clone.setAttribute("xmlns", SVG_NAMESPACE);
|
|
347
|
+
clone.setAttribute("xmlns:xlink", XLINK_NAMESPACE);
|
|
348
|
+
if (options.background) {
|
|
349
|
+
const rect = document.createElementNS(SVG_NAMESPACE, "rect");
|
|
350
|
+
rect.setAttribute("x", String(viewBox.minX));
|
|
351
|
+
rect.setAttribute("y", String(viewBox.minY));
|
|
352
|
+
rect.setAttribute("width", String(viewBox.width));
|
|
353
|
+
rect.setAttribute("height", String(viewBox.height));
|
|
354
|
+
rect.setAttribute("fill", options.background);
|
|
355
|
+
insertAsFirstChild(clone, rect);
|
|
356
|
+
}
|
|
357
|
+
const diagnosticsOverlay = clone.querySelector(DIAGNOSTICS_OVERLAY_SELECTOR);
|
|
358
|
+
diagnosticsOverlay?.parentNode?.removeChild(diagnosticsOverlay);
|
|
359
|
+
return clone;
|
|
421
360
|
}
|
|
422
361
|
function insertAsFirstChild(parent, child) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
362
|
+
if (parent.firstChild) {
|
|
363
|
+
parent.insertBefore(child, parent.firstChild);
|
|
364
|
+
} else {
|
|
365
|
+
parent.appendChild(child);
|
|
366
|
+
}
|
|
429
367
|
}
|
|
430
368
|
function readViewBox(svg) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
369
|
+
const raw = svg.getAttribute("viewBox");
|
|
370
|
+
const parts = (raw ?? "").trim().split(/\s+/).map(Number);
|
|
371
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isFinite(part))) {
|
|
372
|
+
return { minX: 0, minY: 0, width: 0, height: 0 };
|
|
373
|
+
}
|
|
374
|
+
const [minX, minY, width, height] = parts;
|
|
375
|
+
return { minX, minY, width, height };
|
|
438
376
|
}
|
|
439
|
-
// ── Step 6: asset inlining ──────────────────────────────────────────────────
|
|
440
377
|
async function inlineImageAssets(clone) {
|
|
441
|
-
|
|
442
|
-
|
|
378
|
+
const images = [...clone.querySelectorAll("image")];
|
|
379
|
+
await Promise.all(images.map((image) => inlineImageHref(image)));
|
|
443
380
|
}
|
|
444
381
|
async function inlineImageHref(image) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
image.setAttributeNS(XLINK_NAMESPACE, "href", dataUri);
|
|
382
|
+
const href = image.getAttribute("href") ?? image.getAttributeNS(XLINK_NAMESPACE, "href");
|
|
383
|
+
if (!href || href.startsWith("data:")) return;
|
|
384
|
+
const dataUri = await fetchAsDataUri(href);
|
|
385
|
+
image.setAttribute("href", dataUri);
|
|
386
|
+
image.setAttributeNS(XLINK_NAMESPACE, "href", dataUri);
|
|
451
387
|
}
|
|
452
388
|
async function fetchAsDataUri(url) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
return `data:${contentType};base64,${arrayBufferToBase64(buffer)}`;
|
|
389
|
+
let response;
|
|
390
|
+
try {
|
|
391
|
+
response = await fetch(url);
|
|
392
|
+
} catch {
|
|
393
|
+
throw new RenderError("EXPORT_ASSET_FETCH_FAILED", `Failed to fetch asset for inlining: ${url}`, { url });
|
|
394
|
+
}
|
|
395
|
+
if (!response.ok) {
|
|
396
|
+
throw new RenderError("EXPORT_ASSET_FETCH_FAILED", `Failed to fetch asset for inlining: ${url}`, { url });
|
|
397
|
+
}
|
|
398
|
+
const contentType = response.headers.get("content-type") ?? "application/octet-stream";
|
|
399
|
+
const buffer = await response.arrayBuffer();
|
|
400
|
+
return `data:${contentType};base64,${arrayBufferToBase64(buffer)}`;
|
|
466
401
|
}
|
|
467
402
|
function arrayBufferToBase64(buffer) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
return btoa(binary);
|
|
403
|
+
let binary = "";
|
|
404
|
+
const bytes = new Uint8Array(buffer);
|
|
405
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
406
|
+
return btoa(binary);
|
|
473
407
|
}
|
|
474
|
-
// ── Step 7: SVG serialization ───────────────────────────────────────────────
|
|
475
408
|
function serializeSvg(clone) {
|
|
476
|
-
|
|
477
|
-
|
|
409
|
+
const serializer = new XMLSerializer();
|
|
410
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
411
|
+
${serializer.serializeToString(clone)}`;
|
|
478
412
|
}
|
|
479
|
-
// ── Step 8: PNG rasterization ────────────────────────────────────────────────
|
|
480
413
|
async function rasterize(svgString, viewBox, scale) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
context.drawImage(image, 0, 0, width, height);
|
|
495
|
-
const blob = await new Promise((resolve) => {
|
|
496
|
-
canvas.toBlob(resolve, "image/png");
|
|
497
|
-
});
|
|
498
|
-
if (!blob) {
|
|
499
|
-
throw new RenderError("EXPORT_RASTERIZE_FAILED", "Canvas failed to encode PNG output");
|
|
500
|
-
}
|
|
501
|
-
return blob;
|
|
414
|
+
const width = Math.ceil(viewBox.width * scale);
|
|
415
|
+
const height = Math.ceil(viewBox.height * scale);
|
|
416
|
+
const svgBlob = new Blob([svgString], { type: "image/svg+xml" });
|
|
417
|
+
const objectUrl = URL.createObjectURL(svgBlob);
|
|
418
|
+
try {
|
|
419
|
+
const image = await loadImage(objectUrl);
|
|
420
|
+
const canvas = document.createElement("canvas");
|
|
421
|
+
canvas.width = width;
|
|
422
|
+
canvas.height = height;
|
|
423
|
+
const context = canvas.getContext("2d");
|
|
424
|
+
if (!context) {
|
|
425
|
+
throw new RenderError("EXPORT_RASTERIZE_FAILED", "Canvas 2D context is unavailable");
|
|
502
426
|
}
|
|
503
|
-
|
|
504
|
-
|
|
427
|
+
context.drawImage(image, 0, 0, width, height);
|
|
428
|
+
const blob = await new Promise((resolve) => {
|
|
429
|
+
canvas.toBlob(resolve, "image/png");
|
|
430
|
+
});
|
|
431
|
+
if (!blob) {
|
|
432
|
+
throw new RenderError("EXPORT_RASTERIZE_FAILED", "Canvas failed to encode PNG output");
|
|
505
433
|
}
|
|
434
|
+
return blob;
|
|
435
|
+
} finally {
|
|
436
|
+
URL.revokeObjectURL(objectUrl);
|
|
437
|
+
}
|
|
506
438
|
}
|
|
507
439
|
function loadImage(src) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
440
|
+
return new Promise((resolve, reject) => {
|
|
441
|
+
const image = new Image();
|
|
442
|
+
image.onload = () => resolve(image);
|
|
443
|
+
image.onerror = () => reject(new RenderError("EXPORT_RASTERIZE_FAILED", "Failed to load rasterization source image"));
|
|
444
|
+
image.src = src;
|
|
445
|
+
});
|
|
514
446
|
}
|
|
515
447
|
|
|
516
448
|
export { RenderError, attachDiagnosticsOverlay, exportScenePng, exportSceneSvg, guardEntryAnimation, guardExitAnimation, guardLifecycleStatus, projectToScreen };
|