@jamesyong42/infinite-canvas 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -1
- package/dist/SelectionRenderer-DRtwHWJ0.d.cts +102 -0
- package/dist/SelectionRenderer-rGYPadnn.d.ts +102 -0
- package/dist/advanced.cjs +26 -25
- package/dist/advanced.cjs.map +1 -1
- package/dist/advanced.d.cts +4 -2
- package/dist/advanced.d.ts +4 -2
- package/dist/advanced.js +3 -2
- package/dist/advanced.js.map +1 -1
- package/dist/chunk-2KBYGER3.cjs +336 -0
- package/dist/chunk-2KBYGER3.cjs.map +1 -0
- package/dist/{chunk-VSHXWTJH.cjs → chunk-FUPKRQB2.cjs} +218 -421
- package/dist/chunk-FUPKRQB2.cjs.map +1 -0
- package/dist/chunk-NILAZG6O.js +292 -0
- package/dist/chunk-NILAZG6O.js.map +1 -0
- package/dist/{chunk-Z6JQQOWL.js → chunk-W2ZNA7HP.js} +57 -222
- package/dist/chunk-W2ZNA7HP.js.map +1 -0
- package/dist/devtools.cjs +628 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +19 -0
- package/dist/devtools.d.ts +19 -0
- package/dist/devtools.js +626 -0
- package/dist/devtools.js.map +1 -0
- package/dist/{SelectionRenderer-CeWSNZT8.d.cts → engine-DqgJ82tq.d.cts} +13 -99
- package/dist/{SelectionRenderer-CeWSNZT8.d.ts → engine-DqgJ82tq.d.ts} +13 -99
- package/dist/index.cjs +143 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -3
- package/dist/index.d.ts +28 -3
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- package/dist/chunk-VSHXWTJH.cjs.map +0 -1
- package/dist/chunk-Z6JQQOWL.js.map +0 -1
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Build Figma-style infinite canvases in React -- drag, resize, snap, zoom, nested
|
|
|
18
18
|
- **Hierarchical navigation** -- Enter and exit nested containers with camera state preservation
|
|
19
19
|
- **ECS architecture** -- Extensible via custom components, tags, and systems with topologically-sorted scheduling
|
|
20
20
|
- **Performance** -- SDF shaders for grid and selection chrome, RBush spatial indexing, viewport culling, per-system profiling
|
|
21
|
+
- **Live ECS editor** -- Drop-in `<EcsDevtools>` panel for spawning, inspecting, and editing components and tags at runtime
|
|
21
22
|
- **Dark mode** -- Full dark mode support across canvas, widgets, and UI chrome
|
|
22
23
|
|
|
23
24
|
## Quick Start
|
|
@@ -68,12 +69,13 @@ Widgets declare a **schema** (any [Standard Schema v1](https://standardschema.de
|
|
|
68
69
|
|
|
69
70
|
## Package
|
|
70
71
|
|
|
71
|
-
Everything ships in a single package: **`@jamesyong42/infinite-canvas`**. It exposes
|
|
72
|
+
Everything ships in a single package: **`@jamesyong42/infinite-canvas`**. It exposes three entry points:
|
|
72
73
|
|
|
73
74
|
| Import | Purpose |
|
|
74
75
|
|--------|---------|
|
|
75
76
|
| `@jamesyong42/infinite-canvas` | Main API -- `<InfiniteCanvas>`, `createLayoutEngine`, hooks, built-in components |
|
|
76
77
|
| `@jamesyong42/infinite-canvas/advanced` | WebGL renderers, serialization, profiler, spatial index |
|
|
78
|
+
| `@jamesyong42/infinite-canvas/devtools` | `<EcsDevtools>` live ECS editor (see [Devtools](#devtools)) |
|
|
77
79
|
|
|
78
80
|
The underlying ECS primitives (`defineComponent`, `defineSystem`, `World`, `SystemScheduler`) live in a separate package: [**`@jamesyong42/reactive-ecs`**](https://github.com/jamesyong-42/reactive-ecs).
|
|
79
81
|
|
|
@@ -103,6 +105,11 @@ The underlying ECS primitives (`defineComponent`, `defineSystem`, `World`, `Syst
|
|
|
103
105
|
| `useQuery(...types)` | Entity IDs matching component/tag types |
|
|
104
106
|
| `useTaggedEntities(type)` | All entity IDs with a specific tag |
|
|
105
107
|
| `useResource<T>(type)` | Read an ECS resource reactively |
|
|
108
|
+
| `useAllEntities()` | Every live entity ID (reactive on create/destroy) |
|
|
109
|
+
| `useEntityComponents(entityId)` | `ComponentType[]` currently on an entity |
|
|
110
|
+
| `useEntityTags(entityId)` | `TagType[]` currently on an entity |
|
|
111
|
+
| `useRegisteredComponents()` | Every `ComponentType` the world has observed |
|
|
112
|
+
| `useRegisteredTags()` | Every `TagType` the world has observed |
|
|
106
113
|
| `useLayoutEngine()` | Access the `LayoutEngine` instance from context |
|
|
107
114
|
|
|
108
115
|
### InfiniteCanvas Props
|
|
@@ -333,6 +340,42 @@ deserializeWorld(engine.world, saved, componentTypes, tagTypes);
|
|
|
333
340
|
engine.markDirty();
|
|
334
341
|
```
|
|
335
342
|
|
|
343
|
+
## Devtools
|
|
344
|
+
|
|
345
|
+
A live ECS editor ships in `@jamesyong42/infinite-canvas/devtools`. Drop it in during development to spawn widgets, inspect entities, edit components, and toggle tags at runtime — FLECS Explorer-style, but driven by the live React tree.
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
import { InfiniteCanvas } from '@jamesyong42/infinite-canvas';
|
|
349
|
+
import { EcsDevtools } from '@jamesyong42/infinite-canvas/devtools';
|
|
350
|
+
|
|
351
|
+
function App() {
|
|
352
|
+
const engine = useMemo(() => createLayoutEngine({ widgets: [MyWidget] }), []);
|
|
353
|
+
const [showDevtools, setShowDevtools] = useState(false);
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
<>
|
|
357
|
+
<InfiniteCanvas engine={engine} />
|
|
358
|
+
{showDevtools && <EcsDevtools engine={engine} onClose={() => setShowDevtools(false)} />}
|
|
359
|
+
</>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
What the panel does:
|
|
365
|
+
|
|
366
|
+
- **Spawn** any registered widget at the current viewport centre.
|
|
367
|
+
- **List** all widget entities (or all entities with `show all`).
|
|
368
|
+
- **Inspect** the canvas-selected entity: its components and tags.
|
|
369
|
+
- **Edit** component fields inline — primitive types get typed inputs, everything else falls back to a JSON input. `WidgetData.data` is edited field-by-field.
|
|
370
|
+
- **Add / remove** components and toggle tags without leaving the canvas.
|
|
371
|
+
- **Destroy** entities.
|
|
372
|
+
|
|
373
|
+
Pass `engine` as a prop when the devtools render outside the `<InfiniteCanvas>` subtree (the usual case, since the panel is typically absolute-positioned above the canvas). If rendered inside, the prop is optional — it reads from context.
|
|
374
|
+
|
|
375
|
+
Styling is self-contained (a single scoped `<style>` injected once, classnames prefixed `ic-ecs-`). Dark mode is auto via `prefers-color-scheme` or an ancestor `.dark` class. No stylesheet import required.
|
|
376
|
+
|
|
377
|
+
The devtools consume the same introspection primitives (`useAllEntities`, `useEntityComponents`, `useRegisteredComponents`, etc.) that are exported from the main entry point, so you can build your own inspector UI on top of them if you need something bespoke.
|
|
378
|
+
|
|
336
379
|
## Programmatic Control
|
|
337
380
|
|
|
338
381
|
### Camera
|
|
@@ -353,6 +396,27 @@ engine.redo();
|
|
|
353
396
|
engine.markDirty();
|
|
354
397
|
```
|
|
355
398
|
|
|
399
|
+
### Spawning & ECS mutation
|
|
400
|
+
|
|
401
|
+
Runtime spawning and component edits go through the engine so it can cascade handles and mark dirty in one step:
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
// Spawn at the viewport centre (sized from the widget/archetype default)
|
|
405
|
+
const id = engine.spawnAtCameraCenter('my-widget');
|
|
406
|
+
|
|
407
|
+
// Component mutation
|
|
408
|
+
engine.addComponent(id, Container, { enterable: true });
|
|
409
|
+
engine.removeComponent(id, Container);
|
|
410
|
+
engine.set(id, Transform2D, { x: 200 }); // partial merge
|
|
411
|
+
engine.addTag(id, Selected);
|
|
412
|
+
engine.removeTag(id, Draggable);
|
|
413
|
+
|
|
414
|
+
// Widget-aware introspection
|
|
415
|
+
engine.getSchemaFor(id); // Standard Schema for the widget's data, if declared
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
All of these mark the engine dirty internally — no separate `markDirty` call needed.
|
|
419
|
+
|
|
356
420
|
### Imperative Handle
|
|
357
421
|
|
|
358
422
|
Use a ref on `<InfiniteCanvas>` for imperative control from outside:
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { EntityId } from '@jamesyong42/reactive-ecs';
|
|
3
|
+
import { L as LayoutEngine, q as DomWidgetProps, R as R3FWidgetProps, _ as SnapGuide, s as EqualSpacingIndicator } from './engine-DqgJ82tq.cjs';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
|
|
6
|
+
declare const EngineProvider: react.Provider<LayoutEngine | null>;
|
|
7
|
+
declare const ContainerRefProvider: react.Provider<react.RefObject<HTMLDivElement | null> | null>;
|
|
8
|
+
declare function useContainerRef(): React.RefObject<HTMLDivElement | null> | null;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the LayoutEngine instance from the nearest InfiniteCanvas context.
|
|
11
|
+
* Throws if used outside an InfiniteCanvas provider.
|
|
12
|
+
*/
|
|
13
|
+
declare function useLayoutEngine(): LayoutEngine;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Discriminated resolution of a widget by type. The surface determines which
|
|
17
|
+
* layer renders the component and with what prop shape.
|
|
18
|
+
*/
|
|
19
|
+
type ResolvedWidget = {
|
|
20
|
+
surface: 'dom';
|
|
21
|
+
component: React.ComponentType<DomWidgetProps>;
|
|
22
|
+
} | {
|
|
23
|
+
surface: 'webgl';
|
|
24
|
+
component: React.ComponentType<R3FWidgetProps>;
|
|
25
|
+
};
|
|
26
|
+
type WidgetResolver = (entityId: EntityId, widgetType: string) => ResolvedWidget | null;
|
|
27
|
+
declare const WidgetResolverProvider: react.Provider<WidgetResolver | null>;
|
|
28
|
+
declare function useWidgetResolver(): WidgetResolver | null;
|
|
29
|
+
|
|
30
|
+
interface GridConfig {
|
|
31
|
+
/** World-unit spacings for up to 3 grid levels [fine, medium, coarse]. */
|
|
32
|
+
spacings: [number, number, number];
|
|
33
|
+
/** Dot RGB color as [r, g, b] in 0–1 range. */
|
|
34
|
+
dotColor: [number, number, number];
|
|
35
|
+
/** Base dot opacity multiplier (0–1). */
|
|
36
|
+
dotAlpha: number;
|
|
37
|
+
/** CSS-pixel range where a grid level fades in: [start, end]. */
|
|
38
|
+
fadeIn: [number, number];
|
|
39
|
+
/** CSS-pixel range where a grid level fades out: [start, end]. */
|
|
40
|
+
fadeOut: [number, number];
|
|
41
|
+
/** Dot radius range in CSS pixels [min, max]. Scaled by DPR internally. */
|
|
42
|
+
dotRadius: [number, number];
|
|
43
|
+
/** Per-level opacity weight: level i gets (base + i * step). */
|
|
44
|
+
levelWeight: [number, number];
|
|
45
|
+
}
|
|
46
|
+
declare const DEFAULT_GRID_CONFIG: GridConfig;
|
|
47
|
+
declare class GridRenderer {
|
|
48
|
+
private renderer;
|
|
49
|
+
private scene;
|
|
50
|
+
private camera;
|
|
51
|
+
private material;
|
|
52
|
+
private mesh;
|
|
53
|
+
constructor(canvas: HTMLCanvasElement);
|
|
54
|
+
/** Apply a (partial) grid config. Only provided fields are updated. */
|
|
55
|
+
setConfig(config: Partial<GridConfig>): void;
|
|
56
|
+
setSize(width: number, height: number, dpr?: number): void;
|
|
57
|
+
render(cameraX: number, cameraY: number, zoom: number): void;
|
|
58
|
+
dispose(): void;
|
|
59
|
+
/** Expose for future WebGL widget rendering */
|
|
60
|
+
getWebGLRenderer(): THREE.WebGLRenderer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface SelectionConfig {
|
|
64
|
+
/** Selection outline color [r,g,b] 0-1. Default: Figma blue. */
|
|
65
|
+
outlineColor: [number, number, number];
|
|
66
|
+
/** Selection outline width in screen px. */
|
|
67
|
+
outlineWidth: number;
|
|
68
|
+
/** Hover outline color [r,g,b] 0-1. */
|
|
69
|
+
hoverColor: [number, number, number];
|
|
70
|
+
/** Hover outline width in screen px. */
|
|
71
|
+
hoverWidth: number;
|
|
72
|
+
/** Handle size in screen px. */
|
|
73
|
+
handleSize: number;
|
|
74
|
+
/** Handle fill color [r,g,b] 0-1 (white). */
|
|
75
|
+
handleFill: [number, number, number];
|
|
76
|
+
/** Handle border color [r,g,b] 0-1 (same as outline). */
|
|
77
|
+
handleBorder: [number, number, number];
|
|
78
|
+
/** Handle border width in screen px. */
|
|
79
|
+
handleBorderWidth: number;
|
|
80
|
+
/** Group bbox dash length in screen px (0 = solid). */
|
|
81
|
+
groupDash: number;
|
|
82
|
+
}
|
|
83
|
+
declare const DEFAULT_SELECTION_CONFIG: SelectionConfig;
|
|
84
|
+
interface SelectionBounds {
|
|
85
|
+
x: number;
|
|
86
|
+
y: number;
|
|
87
|
+
width: number;
|
|
88
|
+
height: number;
|
|
89
|
+
}
|
|
90
|
+
declare class SelectionRenderer {
|
|
91
|
+
private material;
|
|
92
|
+
private mesh;
|
|
93
|
+
private scene;
|
|
94
|
+
private camera;
|
|
95
|
+
constructor();
|
|
96
|
+
setConfig(config: Partial<SelectionConfig>): void;
|
|
97
|
+
setSize(resolution: THREE.Vector2, dpr: number): void;
|
|
98
|
+
render(renderer: THREE.WebGLRenderer, cameraX: number, cameraY: number, zoom: number, selected: SelectionBounds[], hovered: SelectionBounds | null, guides?: SnapGuide[], spacings?: EqualSpacingIndicator[]): void;
|
|
99
|
+
dispose(): void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { ContainerRefProvider as C, DEFAULT_GRID_CONFIG as D, EngineProvider as E, GridRenderer as G, type ResolvedWidget as R, SelectionRenderer as S, WidgetResolverProvider as W, type GridConfig as a, type SelectionConfig as b, DEFAULT_SELECTION_CONFIG as c, type SelectionBounds as d, useLayoutEngine as e, useWidgetResolver as f, useContainerRef as u };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { EntityId } from '@jamesyong42/reactive-ecs';
|
|
3
|
+
import { L as LayoutEngine, q as DomWidgetProps, R as R3FWidgetProps, _ as SnapGuide, s as EqualSpacingIndicator } from './engine-DqgJ82tq.js';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
|
|
6
|
+
declare const EngineProvider: react.Provider<LayoutEngine | null>;
|
|
7
|
+
declare const ContainerRefProvider: react.Provider<react.RefObject<HTMLDivElement | null> | null>;
|
|
8
|
+
declare function useContainerRef(): React.RefObject<HTMLDivElement | null> | null;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the LayoutEngine instance from the nearest InfiniteCanvas context.
|
|
11
|
+
* Throws if used outside an InfiniteCanvas provider.
|
|
12
|
+
*/
|
|
13
|
+
declare function useLayoutEngine(): LayoutEngine;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Discriminated resolution of a widget by type. The surface determines which
|
|
17
|
+
* layer renders the component and with what prop shape.
|
|
18
|
+
*/
|
|
19
|
+
type ResolvedWidget = {
|
|
20
|
+
surface: 'dom';
|
|
21
|
+
component: React.ComponentType<DomWidgetProps>;
|
|
22
|
+
} | {
|
|
23
|
+
surface: 'webgl';
|
|
24
|
+
component: React.ComponentType<R3FWidgetProps>;
|
|
25
|
+
};
|
|
26
|
+
type WidgetResolver = (entityId: EntityId, widgetType: string) => ResolvedWidget | null;
|
|
27
|
+
declare const WidgetResolverProvider: react.Provider<WidgetResolver | null>;
|
|
28
|
+
declare function useWidgetResolver(): WidgetResolver | null;
|
|
29
|
+
|
|
30
|
+
interface GridConfig {
|
|
31
|
+
/** World-unit spacings for up to 3 grid levels [fine, medium, coarse]. */
|
|
32
|
+
spacings: [number, number, number];
|
|
33
|
+
/** Dot RGB color as [r, g, b] in 0–1 range. */
|
|
34
|
+
dotColor: [number, number, number];
|
|
35
|
+
/** Base dot opacity multiplier (0–1). */
|
|
36
|
+
dotAlpha: number;
|
|
37
|
+
/** CSS-pixel range where a grid level fades in: [start, end]. */
|
|
38
|
+
fadeIn: [number, number];
|
|
39
|
+
/** CSS-pixel range where a grid level fades out: [start, end]. */
|
|
40
|
+
fadeOut: [number, number];
|
|
41
|
+
/** Dot radius range in CSS pixels [min, max]. Scaled by DPR internally. */
|
|
42
|
+
dotRadius: [number, number];
|
|
43
|
+
/** Per-level opacity weight: level i gets (base + i * step). */
|
|
44
|
+
levelWeight: [number, number];
|
|
45
|
+
}
|
|
46
|
+
declare const DEFAULT_GRID_CONFIG: GridConfig;
|
|
47
|
+
declare class GridRenderer {
|
|
48
|
+
private renderer;
|
|
49
|
+
private scene;
|
|
50
|
+
private camera;
|
|
51
|
+
private material;
|
|
52
|
+
private mesh;
|
|
53
|
+
constructor(canvas: HTMLCanvasElement);
|
|
54
|
+
/** Apply a (partial) grid config. Only provided fields are updated. */
|
|
55
|
+
setConfig(config: Partial<GridConfig>): void;
|
|
56
|
+
setSize(width: number, height: number, dpr?: number): void;
|
|
57
|
+
render(cameraX: number, cameraY: number, zoom: number): void;
|
|
58
|
+
dispose(): void;
|
|
59
|
+
/** Expose for future WebGL widget rendering */
|
|
60
|
+
getWebGLRenderer(): THREE.WebGLRenderer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface SelectionConfig {
|
|
64
|
+
/** Selection outline color [r,g,b] 0-1. Default: Figma blue. */
|
|
65
|
+
outlineColor: [number, number, number];
|
|
66
|
+
/** Selection outline width in screen px. */
|
|
67
|
+
outlineWidth: number;
|
|
68
|
+
/** Hover outline color [r,g,b] 0-1. */
|
|
69
|
+
hoverColor: [number, number, number];
|
|
70
|
+
/** Hover outline width in screen px. */
|
|
71
|
+
hoverWidth: number;
|
|
72
|
+
/** Handle size in screen px. */
|
|
73
|
+
handleSize: number;
|
|
74
|
+
/** Handle fill color [r,g,b] 0-1 (white). */
|
|
75
|
+
handleFill: [number, number, number];
|
|
76
|
+
/** Handle border color [r,g,b] 0-1 (same as outline). */
|
|
77
|
+
handleBorder: [number, number, number];
|
|
78
|
+
/** Handle border width in screen px. */
|
|
79
|
+
handleBorderWidth: number;
|
|
80
|
+
/** Group bbox dash length in screen px (0 = solid). */
|
|
81
|
+
groupDash: number;
|
|
82
|
+
}
|
|
83
|
+
declare const DEFAULT_SELECTION_CONFIG: SelectionConfig;
|
|
84
|
+
interface SelectionBounds {
|
|
85
|
+
x: number;
|
|
86
|
+
y: number;
|
|
87
|
+
width: number;
|
|
88
|
+
height: number;
|
|
89
|
+
}
|
|
90
|
+
declare class SelectionRenderer {
|
|
91
|
+
private material;
|
|
92
|
+
private mesh;
|
|
93
|
+
private scene;
|
|
94
|
+
private camera;
|
|
95
|
+
constructor();
|
|
96
|
+
setConfig(config: Partial<SelectionConfig>): void;
|
|
97
|
+
setSize(resolution: THREE.Vector2, dpr: number): void;
|
|
98
|
+
render(renderer: THREE.WebGLRenderer, cameraX: number, cameraY: number, zoom: number, selected: SelectionBounds[], hovered: SelectionBounds | null, guides?: SnapGuide[], spacings?: EqualSpacingIndicator[]): void;
|
|
99
|
+
dispose(): void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { ContainerRefProvider as C, DEFAULT_GRID_CONFIG as D, EngineProvider as E, GridRenderer as G, type ResolvedWidget as R, SelectionRenderer as S, WidgetResolverProvider as W, type GridConfig as a, type SelectionConfig as b, DEFAULT_SELECTION_CONFIG as c, type SelectionBounds as d, useLayoutEngine as e, useWidgetResolver as f, useContainerRef as u };
|
package/dist/advanced.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkFUPKRQB2_cjs = require('./chunk-FUPKRQB2.cjs');
|
|
4
|
+
var chunk2KBYGER3_cjs = require('./chunk-2KBYGER3.cjs');
|
|
4
5
|
|
|
5
6
|
// src/serialization.ts
|
|
6
7
|
function serializeWorld(world, componentTypes, tagTypes, camera, navigationFrames) {
|
|
@@ -64,22 +65,22 @@ function deserializeWorld(world, doc, componentTypes, tagTypes) {
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
for (const [_oldId, newId] of idMap) {
|
|
67
|
-
const parent = world.getComponent(newId,
|
|
68
|
+
const parent = world.getComponent(newId, chunk2KBYGER3_cjs.Parent);
|
|
68
69
|
if (parent && idMap.has(parent.id)) {
|
|
69
70
|
const mappedId = idMap.get(parent.id);
|
|
70
71
|
if (mappedId !== void 0) {
|
|
71
|
-
world.setComponent(newId,
|
|
72
|
+
world.setComponent(newId, chunk2KBYGER3_cjs.Parent, { id: mappedId });
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
|
-
const children = world.getComponent(newId,
|
|
75
|
+
const children = world.getComponent(newId, chunk2KBYGER3_cjs.Children);
|
|
75
76
|
if (children) {
|
|
76
|
-
world.setComponent(newId,
|
|
77
|
+
world.setComponent(newId, chunk2KBYGER3_cjs.Children, {
|
|
77
78
|
ids: children.ids.map((id) => idMap.get(id) ?? id)
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
|
-
const handleSet = world.getComponent(newId,
|
|
81
|
+
const handleSet = world.getComponent(newId, chunk2KBYGER3_cjs.HandleSet);
|
|
81
82
|
if (handleSet) {
|
|
82
|
-
world.setComponent(newId,
|
|
83
|
+
world.setComponent(newId, chunk2KBYGER3_cjs.HandleSet, {
|
|
83
84
|
ids: handleSet.ids.map((id) => idMap.get(id) ?? id)
|
|
84
85
|
});
|
|
85
86
|
}
|
|
@@ -120,53 +121,53 @@ function serializeEntities(world, entityIds, componentTypes, tagTypes) {
|
|
|
120
121
|
return result;
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
Object.defineProperty(exports, "ContainerRefProvider", {
|
|
124
|
-
enumerable: true,
|
|
125
|
-
get: function () { return chunkVSHXWTJH_cjs.ContainerRefProvider; }
|
|
126
|
-
});
|
|
127
|
-
Object.defineProperty(exports, "EngineProvider", {
|
|
128
|
-
enumerable: true,
|
|
129
|
-
get: function () { return chunkVSHXWTJH_cjs.EngineProvider; }
|
|
130
|
-
});
|
|
131
124
|
Object.defineProperty(exports, "GridRenderer", {
|
|
132
125
|
enumerable: true,
|
|
133
|
-
get: function () { return
|
|
126
|
+
get: function () { return chunkFUPKRQB2_cjs.GridRenderer; }
|
|
134
127
|
});
|
|
135
128
|
Object.defineProperty(exports, "Profiler", {
|
|
136
129
|
enumerable: true,
|
|
137
|
-
get: function () { return
|
|
130
|
+
get: function () { return chunkFUPKRQB2_cjs.Profiler; }
|
|
138
131
|
});
|
|
139
132
|
Object.defineProperty(exports, "SelectionOverlaySlot", {
|
|
140
133
|
enumerable: true,
|
|
141
|
-
get: function () { return
|
|
134
|
+
get: function () { return chunkFUPKRQB2_cjs.SelectionOverlaySlot; }
|
|
142
135
|
});
|
|
143
136
|
Object.defineProperty(exports, "SelectionRenderer", {
|
|
144
137
|
enumerable: true,
|
|
145
|
-
get: function () { return
|
|
138
|
+
get: function () { return chunkFUPKRQB2_cjs.SelectionRenderer; }
|
|
146
139
|
});
|
|
147
140
|
Object.defineProperty(exports, "SpatialIndex", {
|
|
148
141
|
enumerable: true,
|
|
149
|
-
get: function () { return
|
|
142
|
+
get: function () { return chunkFUPKRQB2_cjs.SpatialIndex; }
|
|
150
143
|
});
|
|
151
144
|
Object.defineProperty(exports, "SpatialIndexResource", {
|
|
152
145
|
enumerable: true,
|
|
153
|
-
get: function () { return
|
|
146
|
+
get: function () { return chunkFUPKRQB2_cjs.SpatialIndexResource; }
|
|
154
147
|
});
|
|
155
148
|
Object.defineProperty(exports, "WebGLWidgetLayer", {
|
|
156
149
|
enumerable: true,
|
|
157
|
-
get: function () { return
|
|
150
|
+
get: function () { return chunkFUPKRQB2_cjs.WebGLWidgetLayer; }
|
|
158
151
|
});
|
|
159
152
|
Object.defineProperty(exports, "WebGLWidgetSlot", {
|
|
160
153
|
enumerable: true,
|
|
161
|
-
get: function () { return
|
|
154
|
+
get: function () { return chunkFUPKRQB2_cjs.WebGLWidgetSlot; }
|
|
162
155
|
});
|
|
163
156
|
Object.defineProperty(exports, "WidgetSlot", {
|
|
164
157
|
enumerable: true,
|
|
165
|
-
get: function () { return
|
|
158
|
+
get: function () { return chunkFUPKRQB2_cjs.WidgetSlot; }
|
|
166
159
|
});
|
|
167
160
|
Object.defineProperty(exports, "computeSnapGuides", {
|
|
168
161
|
enumerable: true,
|
|
169
|
-
get: function () { return
|
|
162
|
+
get: function () { return chunkFUPKRQB2_cjs.computeSnapGuides; }
|
|
163
|
+
});
|
|
164
|
+
Object.defineProperty(exports, "ContainerRefProvider", {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
get: function () { return chunk2KBYGER3_cjs.ContainerRefProvider; }
|
|
167
|
+
});
|
|
168
|
+
Object.defineProperty(exports, "EngineProvider", {
|
|
169
|
+
enumerable: true,
|
|
170
|
+
get: function () { return chunk2KBYGER3_cjs.EngineProvider; }
|
|
170
171
|
});
|
|
171
172
|
exports.deserializeWorld = deserializeWorld;
|
|
172
173
|
exports.serializeEntities = serializeEntities;
|
package/dist/advanced.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/serialization.ts"],"names":["Parent","Children","HandleSet"],"mappings":"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","file":"advanced.cjs","sourcesContent":["import type { ComponentType, EntityId, TagType, World } from '@jamesyong42/reactive-ecs';\nimport { Children, HandleSet, Parent } from './components.js';\nimport type { NavigationFrame } from './resources.js';\n\n// === Serialization Types ===\n\n/** JSON-serializable snapshot of the canvas state, including all entities and camera. */\nexport interface CanvasDocument {\n\tversion: number;\n\tentities: SerializedEntity[];\n\tresources: {\n\t\tcamera: { x: number; y: number; zoom: number };\n\t\tnavigationStack: NavigationFrame[];\n\t};\n}\n\n/** A single serialized entity with its components and tags. */\nexport interface SerializedEntity {\n\tid: EntityId;\n\tcomponents: Record<string, unknown>;\n\ttags: string[];\n}\n\n// === Serialize/Deserialize ===\n\n/**\n * Serializes all entities, components, and tags to a JSON-compatible document.\n * Requires registries of known component and tag types for enumeration.\n */\nexport function serializeWorld(\n\tworld: World,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n\tcamera: { x: number; y: number; zoom: number },\n\tnavigationFrames: NavigationFrame[],\n): CanvasDocument {\n\tconst entities: SerializedEntity[] = [];\n\n\t// Get all entity IDs (use a broad query)\n\tconst allEntities = world.query();\n\n\tfor (const entityId of allEntities) {\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\t// Skip runtime-only tags (Active, Visible — they're recomputed)\n\t\t\t\tif (type.name !== 'Active' && type.name !== 'Visible') {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(components).length > 0 || tags.length > 0) {\n\t\t\tentities.push({ id: entityId, components, tags });\n\t\t}\n\t}\n\n\treturn {\n\t\tversion: 1,\n\t\tentities,\n\t\tresources: {\n\t\t\tcamera: { ...camera },\n\t\t\tnavigationStack: structuredClone(navigationFrames),\n\t\t},\n\t};\n}\n\n/**\n * Restores entities from a serialized document into the world.\n * Clears existing state first and remaps entity IDs automatically.\n */\nexport function deserializeWorld(\n\tworld: World,\n\tdoc: CanvasDocument,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): void {\n\tif (doc.version !== 1) {\n\t\tthrow new Error(`Unsupported canvas document version: ${doc.version}. Expected version 1.`);\n\t}\n\n\t// Build lookup maps\n\tconst compByName = new Map<string, ComponentType>();\n\tfor (const t of componentTypes) compByName.set(t.name, t);\n\n\tconst tagByName = new Map<string, TagType>();\n\tfor (const t of tagTypes) tagByName.set(t.name, t);\n\n\t// Destroy all existing entities\n\tfor (const entityId of world.query()) {\n\t\tworld.destroyEntity(entityId);\n\t}\n\n\t// First pass: create entities and build old-to-new ID mapping\n\tconst idMap = new Map<EntityId, EntityId>();\n\n\tfor (const entry of doc.entities) {\n\t\tconst newId = world.createEntity();\n\t\tidMap.set(entry.id as EntityId, newId);\n\n\t\tfor (const [compName, data] of Object.entries(entry.components)) {\n\t\t\tconst type = compByName.get(compName);\n\t\t\tif (type) {\n\t\t\t\tworld.addComponent(newId, type, data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const tagName of entry.tags) {\n\t\t\tconst type = tagByName.get(tagName);\n\t\t\tif (type) {\n\t\t\t\tworld.addTag(newId, type);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Second pass: remap cross-reference components (Parent, Children, HandleSet)\n\tfor (const [_oldId, newId] of idMap) {\n\t\tconst parent = world.getComponent(newId, Parent);\n\t\tif (parent && idMap.has(parent.id)) {\n\t\t\tconst mappedId = idMap.get(parent.id);\n\t\t\tif (mappedId !== undefined) {\n\t\t\t\tworld.setComponent(newId, Parent, { id: mappedId });\n\t\t\t}\n\t\t}\n\n\t\tconst children = world.getComponent(newId, Children);\n\t\tif (children) {\n\t\t\tworld.setComponent(newId, Children, {\n\t\t\t\tids: children.ids.map((id: EntityId) => idMap.get(id) ?? id),\n\t\t\t});\n\t\t}\n\n\t\tconst handleSet = world.getComponent(newId, HandleSet);\n\t\tif (handleSet) {\n\t\t\tworld.setComponent(newId, HandleSet, {\n\t\t\t\tids: handleSet.ids.map((id: EntityId) => idMap.get(id) ?? id),\n\t\t\t});\n\t\t}\n\t}\n}\n\n/**\n * Serializes a subset of entities (e.g., for copy/paste).\n * Recursively includes children of the specified entities.\n */\nexport function serializeEntities(\n\tworld: World,\n\tentityIds: EntityId[],\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): SerializedEntity[] {\n\tconst result: SerializedEntity[] = [];\n\tconst visited = new Set<EntityId>();\n\n\tfunction visit(entityId: EntityId) {\n\t\tif (visited.has(entityId)) return;\n\t\tvisited.add(entityId);\n\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\tif (type.name !== 'Active' && type.name !== 'Visible') {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresult.push({ id: entityId, components, tags });\n\n\t\t// Recurse into children. components.Children is typed as unknown via\n\t\t// the Record<string, unknown> shape, so narrow through a cast.\n\t\tconst children = components.Children as { ids?: EntityId[] } | undefined;\n\t\tif (children?.ids) {\n\t\t\tfor (const childId of children.ids) {\n\t\t\t\tvisit(childId);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const id of entityIds) {\n\t\tvisit(id);\n\t}\n\n\treturn result;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/serialization.ts"],"names":["Parent","Children","HandleSet"],"mappings":"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","file":"advanced.cjs","sourcesContent":["import type { ComponentType, EntityId, TagType, World } from '@jamesyong42/reactive-ecs';\nimport { Children, HandleSet, Parent } from './components.js';\nimport type { NavigationFrame } from './resources.js';\n\n// === Serialization Types ===\n\n/** JSON-serializable snapshot of the canvas state, including all entities and camera. */\nexport interface CanvasDocument {\n\tversion: number;\n\tentities: SerializedEntity[];\n\tresources: {\n\t\tcamera: { x: number; y: number; zoom: number };\n\t\tnavigationStack: NavigationFrame[];\n\t};\n}\n\n/** A single serialized entity with its components and tags. */\nexport interface SerializedEntity {\n\tid: EntityId;\n\tcomponents: Record<string, unknown>;\n\ttags: string[];\n}\n\n// === Serialize/Deserialize ===\n\n/**\n * Serializes all entities, components, and tags to a JSON-compatible document.\n * Requires registries of known component and tag types for enumeration.\n */\nexport function serializeWorld(\n\tworld: World,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n\tcamera: { x: number; y: number; zoom: number },\n\tnavigationFrames: NavigationFrame[],\n): CanvasDocument {\n\tconst entities: SerializedEntity[] = [];\n\n\t// Get all entity IDs (use a broad query)\n\tconst allEntities = world.query();\n\n\tfor (const entityId of allEntities) {\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\t// Skip runtime-only tags (Active, Visible — they're recomputed)\n\t\t\t\tif (type.name !== 'Active' && type.name !== 'Visible') {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(components).length > 0 || tags.length > 0) {\n\t\t\tentities.push({ id: entityId, components, tags });\n\t\t}\n\t}\n\n\treturn {\n\t\tversion: 1,\n\t\tentities,\n\t\tresources: {\n\t\t\tcamera: { ...camera },\n\t\t\tnavigationStack: structuredClone(navigationFrames),\n\t\t},\n\t};\n}\n\n/**\n * Restores entities from a serialized document into the world.\n * Clears existing state first and remaps entity IDs automatically.\n */\nexport function deserializeWorld(\n\tworld: World,\n\tdoc: CanvasDocument,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): void {\n\tif (doc.version !== 1) {\n\t\tthrow new Error(`Unsupported canvas document version: ${doc.version}. Expected version 1.`);\n\t}\n\n\t// Build lookup maps\n\tconst compByName = new Map<string, ComponentType>();\n\tfor (const t of componentTypes) compByName.set(t.name, t);\n\n\tconst tagByName = new Map<string, TagType>();\n\tfor (const t of tagTypes) tagByName.set(t.name, t);\n\n\t// Destroy all existing entities\n\tfor (const entityId of world.query()) {\n\t\tworld.destroyEntity(entityId);\n\t}\n\n\t// First pass: create entities and build old-to-new ID mapping\n\tconst idMap = new Map<EntityId, EntityId>();\n\n\tfor (const entry of doc.entities) {\n\t\tconst newId = world.createEntity();\n\t\tidMap.set(entry.id as EntityId, newId);\n\n\t\tfor (const [compName, data] of Object.entries(entry.components)) {\n\t\t\tconst type = compByName.get(compName);\n\t\t\tif (type) {\n\t\t\t\tworld.addComponent(newId, type, data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const tagName of entry.tags) {\n\t\t\tconst type = tagByName.get(tagName);\n\t\t\tif (type) {\n\t\t\t\tworld.addTag(newId, type);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Second pass: remap cross-reference components (Parent, Children, HandleSet)\n\tfor (const [_oldId, newId] of idMap) {\n\t\tconst parent = world.getComponent(newId, Parent);\n\t\tif (parent && idMap.has(parent.id)) {\n\t\t\tconst mappedId = idMap.get(parent.id);\n\t\t\tif (mappedId !== undefined) {\n\t\t\t\tworld.setComponent(newId, Parent, { id: mappedId });\n\t\t\t}\n\t\t}\n\n\t\tconst children = world.getComponent(newId, Children);\n\t\tif (children) {\n\t\t\tworld.setComponent(newId, Children, {\n\t\t\t\tids: children.ids.map((id: EntityId) => idMap.get(id) ?? id),\n\t\t\t});\n\t\t}\n\n\t\tconst handleSet = world.getComponent(newId, HandleSet);\n\t\tif (handleSet) {\n\t\t\tworld.setComponent(newId, HandleSet, {\n\t\t\t\tids: handleSet.ids.map((id: EntityId) => idMap.get(id) ?? id),\n\t\t\t});\n\t\t}\n\t}\n}\n\n/**\n * Serializes a subset of entities (e.g., for copy/paste).\n * Recursively includes children of the specified entities.\n */\nexport function serializeEntities(\n\tworld: World,\n\tentityIds: EntityId[],\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): SerializedEntity[] {\n\tconst result: SerializedEntity[] = [];\n\tconst visited = new Set<EntityId>();\n\n\tfunction visit(entityId: EntityId) {\n\t\tif (visited.has(entityId)) return;\n\t\tvisited.add(entityId);\n\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\tif (type.name !== 'Active' && type.name !== 'Visible') {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresult.push({ id: entityId, components, tags });\n\n\t\t// Recurse into children. components.Children is typed as unknown via\n\t\t// the Record<string, unknown> shape, so narrow through a cast.\n\t\tconst children = components.Children as { ids?: EntityId[] } | undefined;\n\t\tif (children?.ids) {\n\t\t\tfor (const childId of children.ids) {\n\t\t\t\tvisit(childId);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const id of entityIds) {\n\t\tvisit(id);\n\t}\n\n\treturn result;\n}\n"]}
|
package/dist/advanced.d.cts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { L as LayoutEngine, R as
|
|
2
|
-
export {
|
|
1
|
+
import { L as LayoutEngine, R as R3FWidgetProps, N as NavigationFrame } from './engine-DqgJ82tq.cjs';
|
|
2
|
+
export { F as FrameSample, P as Profiler, a as ProfilerStats, S as SpatialIndex, b as SpatialIndexResource, c as computeSnapGuides } from './engine-DqgJ82tq.cjs';
|
|
3
|
+
import { R as ResolvedWidget } from './SelectionRenderer-DRtwHWJ0.cjs';
|
|
4
|
+
export { C as ContainerRefProvider, E as EngineProvider, G as GridRenderer, S as SelectionRenderer } from './SelectionRenderer-DRtwHWJ0.cjs';
|
|
3
5
|
import * as react from 'react';
|
|
4
6
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
7
|
import { EntityId, World, ComponentType, TagType } from '@jamesyong42/reactive-ecs';
|
package/dist/advanced.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { L as LayoutEngine, R as
|
|
2
|
-
export {
|
|
1
|
+
import { L as LayoutEngine, R as R3FWidgetProps, N as NavigationFrame } from './engine-DqgJ82tq.js';
|
|
2
|
+
export { F as FrameSample, P as Profiler, a as ProfilerStats, S as SpatialIndex, b as SpatialIndexResource, c as computeSnapGuides } from './engine-DqgJ82tq.js';
|
|
3
|
+
import { R as ResolvedWidget } from './SelectionRenderer-rGYPadnn.js';
|
|
4
|
+
export { C as ContainerRefProvider, E as EngineProvider, G as GridRenderer, S as SelectionRenderer } from './SelectionRenderer-rGYPadnn.js';
|
|
3
5
|
import * as react from 'react';
|
|
4
6
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
7
|
import { EntityId, World, ComponentType, TagType } from '@jamesyong42/reactive-ecs';
|
package/dist/advanced.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export { GridRenderer, Profiler, SelectionOverlaySlot, SelectionRenderer, SpatialIndex, SpatialIndexResource, WebGLWidgetLayer, WebGLWidgetSlot, WidgetSlot, computeSnapGuides } from './chunk-W2ZNA7HP.js';
|
|
2
|
+
import { Parent, Children, HandleSet } from './chunk-NILAZG6O.js';
|
|
3
|
+
export { ContainerRefProvider, EngineProvider } from './chunk-NILAZG6O.js';
|
|
3
4
|
|
|
4
5
|
// src/serialization.ts
|
|
5
6
|
function serializeWorld(world, componentTypes, tagTypes, camera, navigationFrames) {
|
package/dist/advanced.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/serialization.ts"],"names":[],"mappings":"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","file":"advanced.js","sourcesContent":["import type { ComponentType, EntityId, TagType, World } from '@jamesyong42/reactive-ecs';\nimport { Children, HandleSet, Parent } from './components.js';\nimport type { NavigationFrame } from './resources.js';\n\n// === Serialization Types ===\n\n/** JSON-serializable snapshot of the canvas state, including all entities and camera. */\nexport interface CanvasDocument {\n\tversion: number;\n\tentities: SerializedEntity[];\n\tresources: {\n\t\tcamera: { x: number; y: number; zoom: number };\n\t\tnavigationStack: NavigationFrame[];\n\t};\n}\n\n/** A single serialized entity with its components and tags. */\nexport interface SerializedEntity {\n\tid: EntityId;\n\tcomponents: Record<string, unknown>;\n\ttags: string[];\n}\n\n// === Serialize/Deserialize ===\n\n/**\n * Serializes all entities, components, and tags to a JSON-compatible document.\n * Requires registries of known component and tag types for enumeration.\n */\nexport function serializeWorld(\n\tworld: World,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n\tcamera: { x: number; y: number; zoom: number },\n\tnavigationFrames: NavigationFrame[],\n): CanvasDocument {\n\tconst entities: SerializedEntity[] = [];\n\n\t// Get all entity IDs (use a broad query)\n\tconst allEntities = world.query();\n\n\tfor (const entityId of allEntities) {\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\t// Skip runtime-only tags (Active, Visible — they're recomputed)\n\t\t\t\tif (type.name !== 'Active' && type.name !== 'Visible') {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(components).length > 0 || tags.length > 0) {\n\t\t\tentities.push({ id: entityId, components, tags });\n\t\t}\n\t}\n\n\treturn {\n\t\tversion: 1,\n\t\tentities,\n\t\tresources: {\n\t\t\tcamera: { ...camera },\n\t\t\tnavigationStack: structuredClone(navigationFrames),\n\t\t},\n\t};\n}\n\n/**\n * Restores entities from a serialized document into the world.\n * Clears existing state first and remaps entity IDs automatically.\n */\nexport function deserializeWorld(\n\tworld: World,\n\tdoc: CanvasDocument,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): void {\n\tif (doc.version !== 1) {\n\t\tthrow new Error(`Unsupported canvas document version: ${doc.version}. Expected version 1.`);\n\t}\n\n\t// Build lookup maps\n\tconst compByName = new Map<string, ComponentType>();\n\tfor (const t of componentTypes) compByName.set(t.name, t);\n\n\tconst tagByName = new Map<string, TagType>();\n\tfor (const t of tagTypes) tagByName.set(t.name, t);\n\n\t// Destroy all existing entities\n\tfor (const entityId of world.query()) {\n\t\tworld.destroyEntity(entityId);\n\t}\n\n\t// First pass: create entities and build old-to-new ID mapping\n\tconst idMap = new Map<EntityId, EntityId>();\n\n\tfor (const entry of doc.entities) {\n\t\tconst newId = world.createEntity();\n\t\tidMap.set(entry.id as EntityId, newId);\n\n\t\tfor (const [compName, data] of Object.entries(entry.components)) {\n\t\t\tconst type = compByName.get(compName);\n\t\t\tif (type) {\n\t\t\t\tworld.addComponent(newId, type, data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const tagName of entry.tags) {\n\t\t\tconst type = tagByName.get(tagName);\n\t\t\tif (type) {\n\t\t\t\tworld.addTag(newId, type);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Second pass: remap cross-reference components (Parent, Children, HandleSet)\n\tfor (const [_oldId, newId] of idMap) {\n\t\tconst parent = world.getComponent(newId, Parent);\n\t\tif (parent && idMap.has(parent.id)) {\n\t\t\tconst mappedId = idMap.get(parent.id);\n\t\t\tif (mappedId !== undefined) {\n\t\t\t\tworld.setComponent(newId, Parent, { id: mappedId });\n\t\t\t}\n\t\t}\n\n\t\tconst children = world.getComponent(newId, Children);\n\t\tif (children) {\n\t\t\tworld.setComponent(newId, Children, {\n\t\t\t\tids: children.ids.map((id: EntityId) => idMap.get(id) ?? id),\n\t\t\t});\n\t\t}\n\n\t\tconst handleSet = world.getComponent(newId, HandleSet);\n\t\tif (handleSet) {\n\t\t\tworld.setComponent(newId, HandleSet, {\n\t\t\t\tids: handleSet.ids.map((id: EntityId) => idMap.get(id) ?? id),\n\t\t\t});\n\t\t}\n\t}\n}\n\n/**\n * Serializes a subset of entities (e.g., for copy/paste).\n * Recursively includes children of the specified entities.\n */\nexport function serializeEntities(\n\tworld: World,\n\tentityIds: EntityId[],\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): SerializedEntity[] {\n\tconst result: SerializedEntity[] = [];\n\tconst visited = new Set<EntityId>();\n\n\tfunction visit(entityId: EntityId) {\n\t\tif (visited.has(entityId)) return;\n\t\tvisited.add(entityId);\n\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\tif (type.name !== 'Active' && type.name !== 'Visible') {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresult.push({ id: entityId, components, tags });\n\n\t\t// Recurse into children. components.Children is typed as unknown via\n\t\t// the Record<string, unknown> shape, so narrow through a cast.\n\t\tconst children = components.Children as { ids?: EntityId[] } | undefined;\n\t\tif (children?.ids) {\n\t\t\tfor (const childId of children.ids) {\n\t\t\t\tvisit(childId);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const id of entityIds) {\n\t\tvisit(id);\n\t}\n\n\treturn result;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/serialization.ts"],"names":[],"mappings":"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","file":"advanced.js","sourcesContent":["import type { ComponentType, EntityId, TagType, World } from '@jamesyong42/reactive-ecs';\nimport { Children, HandleSet, Parent } from './components.js';\nimport type { NavigationFrame } from './resources.js';\n\n// === Serialization Types ===\n\n/** JSON-serializable snapshot of the canvas state, including all entities and camera. */\nexport interface CanvasDocument {\n\tversion: number;\n\tentities: SerializedEntity[];\n\tresources: {\n\t\tcamera: { x: number; y: number; zoom: number };\n\t\tnavigationStack: NavigationFrame[];\n\t};\n}\n\n/** A single serialized entity with its components and tags. */\nexport interface SerializedEntity {\n\tid: EntityId;\n\tcomponents: Record<string, unknown>;\n\ttags: string[];\n}\n\n// === Serialize/Deserialize ===\n\n/**\n * Serializes all entities, components, and tags to a JSON-compatible document.\n * Requires registries of known component and tag types for enumeration.\n */\nexport function serializeWorld(\n\tworld: World,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n\tcamera: { x: number; y: number; zoom: number },\n\tnavigationFrames: NavigationFrame[],\n): CanvasDocument {\n\tconst entities: SerializedEntity[] = [];\n\n\t// Get all entity IDs (use a broad query)\n\tconst allEntities = world.query();\n\n\tfor (const entityId of allEntities) {\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\t// Skip runtime-only tags (Active, Visible — they're recomputed)\n\t\t\t\tif (type.name !== 'Active' && type.name !== 'Visible') {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(components).length > 0 || tags.length > 0) {\n\t\t\tentities.push({ id: entityId, components, tags });\n\t\t}\n\t}\n\n\treturn {\n\t\tversion: 1,\n\t\tentities,\n\t\tresources: {\n\t\t\tcamera: { ...camera },\n\t\t\tnavigationStack: structuredClone(navigationFrames),\n\t\t},\n\t};\n}\n\n/**\n * Restores entities from a serialized document into the world.\n * Clears existing state first and remaps entity IDs automatically.\n */\nexport function deserializeWorld(\n\tworld: World,\n\tdoc: CanvasDocument,\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): void {\n\tif (doc.version !== 1) {\n\t\tthrow new Error(`Unsupported canvas document version: ${doc.version}. Expected version 1.`);\n\t}\n\n\t// Build lookup maps\n\tconst compByName = new Map<string, ComponentType>();\n\tfor (const t of componentTypes) compByName.set(t.name, t);\n\n\tconst tagByName = new Map<string, TagType>();\n\tfor (const t of tagTypes) tagByName.set(t.name, t);\n\n\t// Destroy all existing entities\n\tfor (const entityId of world.query()) {\n\t\tworld.destroyEntity(entityId);\n\t}\n\n\t// First pass: create entities and build old-to-new ID mapping\n\tconst idMap = new Map<EntityId, EntityId>();\n\n\tfor (const entry of doc.entities) {\n\t\tconst newId = world.createEntity();\n\t\tidMap.set(entry.id as EntityId, newId);\n\n\t\tfor (const [compName, data] of Object.entries(entry.components)) {\n\t\t\tconst type = compByName.get(compName);\n\t\t\tif (type) {\n\t\t\t\tworld.addComponent(newId, type, data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const tagName of entry.tags) {\n\t\t\tconst type = tagByName.get(tagName);\n\t\t\tif (type) {\n\t\t\t\tworld.addTag(newId, type);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Second pass: remap cross-reference components (Parent, Children, HandleSet)\n\tfor (const [_oldId, newId] of idMap) {\n\t\tconst parent = world.getComponent(newId, Parent);\n\t\tif (parent && idMap.has(parent.id)) {\n\t\t\tconst mappedId = idMap.get(parent.id);\n\t\t\tif (mappedId !== undefined) {\n\t\t\t\tworld.setComponent(newId, Parent, { id: mappedId });\n\t\t\t}\n\t\t}\n\n\t\tconst children = world.getComponent(newId, Children);\n\t\tif (children) {\n\t\t\tworld.setComponent(newId, Children, {\n\t\t\t\tids: children.ids.map((id: EntityId) => idMap.get(id) ?? id),\n\t\t\t});\n\t\t}\n\n\t\tconst handleSet = world.getComponent(newId, HandleSet);\n\t\tif (handleSet) {\n\t\t\tworld.setComponent(newId, HandleSet, {\n\t\t\t\tids: handleSet.ids.map((id: EntityId) => idMap.get(id) ?? id),\n\t\t\t});\n\t\t}\n\t}\n}\n\n/**\n * Serializes a subset of entities (e.g., for copy/paste).\n * Recursively includes children of the specified entities.\n */\nexport function serializeEntities(\n\tworld: World,\n\tentityIds: EntityId[],\n\tcomponentTypes: ComponentType[],\n\ttagTypes: TagType[],\n): SerializedEntity[] {\n\tconst result: SerializedEntity[] = [];\n\tconst visited = new Set<EntityId>();\n\n\tfunction visit(entityId: EntityId) {\n\t\tif (visited.has(entityId)) return;\n\t\tvisited.add(entityId);\n\n\t\tconst components: Record<string, unknown> = {};\n\t\tconst tags: string[] = [];\n\n\t\tfor (const type of componentTypes) {\n\t\t\tconst data = world.getComponent(entityId, type);\n\t\t\tif (data !== undefined) {\n\t\t\t\tcomponents[type.name] = structuredClone(data);\n\t\t\t}\n\t\t}\n\n\t\tfor (const type of tagTypes) {\n\t\t\tif (world.hasTag(entityId, type)) {\n\t\t\t\tif (type.name !== 'Active' && type.name !== 'Visible') {\n\t\t\t\t\ttags.push(type.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresult.push({ id: entityId, components, tags });\n\n\t\t// Recurse into children. components.Children is typed as unknown via\n\t\t// the Record<string, unknown> shape, so narrow through a cast.\n\t\tconst children = components.Children as { ids?: EntityId[] } | undefined;\n\t\tif (children?.ids) {\n\t\t\tfor (const childId of children.ids) {\n\t\t\t\tvisit(childId);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const id of entityIds) {\n\t\tvisit(id);\n\t}\n\n\treturn result;\n}\n"]}
|