@neta-art/cohub 10.0.0 → 11.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.
Files changed (155) hide show
  1. package/README.md +42 -7
  2. package/dist/board/editor/animation.d.ts +3 -0
  3. package/dist/board/editor/animation.js +16 -1
  4. package/dist/board/editor/clipboard.js +7 -0
  5. package/dist/board/editor/copy.js +23 -18
  6. package/dist/board/editor/creation.d.ts +7 -3
  7. package/dist/board/editor/creation.js +5 -4
  8. package/dist/board/editor/deletion.js +3 -3
  9. package/dist/board/editor/document.d.ts +8 -2
  10. package/dist/board/editor/document.js +54 -7
  11. package/dist/board/editor/editor.d.ts +22 -7
  12. package/dist/board/editor/editor.js +12 -2
  13. package/dist/board/editor/index.d.ts +2 -2
  14. package/dist/board/editor/interaction.d.ts +8 -2
  15. package/dist/board/editor/interaction.js +34 -1
  16. package/dist/board/editor/query.d.ts +5 -5
  17. package/dist/board/editor/query.js +14 -12
  18. package/dist/board/editor/selection.js +2 -1
  19. package/dist/board/editor/snapshots.d.ts +0 -2
  20. package/dist/board/editor/snapshots.js +4 -20
  21. package/dist/board/export/index.d.ts +25 -4
  22. package/dist/board/export/index.js +45 -12
  23. package/dist/board/export/node/index.d.ts +5 -4
  24. package/dist/board/export/node/index.js +16 -11
  25. package/dist/board/export/node/scripts.d.ts +16 -0
  26. package/dist/board/export/node/scripts.js +258 -0
  27. package/dist/board/export/scene.d.ts +2 -1
  28. package/dist/board/export/scene.js +24 -7
  29. package/dist/board/model/animation.d.ts +4 -1
  30. package/dist/board/model/animation.js +34 -7
  31. package/dist/board/model/authoring.d.ts +20 -0
  32. package/dist/board/model/authoring.js +185 -0
  33. package/dist/board/model/export-plan.js +1 -0
  34. package/dist/board/model/file-preview.js +1 -1
  35. package/dist/board/model/file-snapshot.js +3 -2
  36. package/dist/board/model/image-key.js +7 -3
  37. package/dist/board/model/index.d.ts +15 -7
  38. package/dist/board/model/index.js +16 -9
  39. package/dist/board/model/items/builtin.js +2 -2
  40. package/dist/board/model/items/create.d.ts +19 -4
  41. package/dist/board/model/items/create.js +130 -9
  42. package/dist/board/model/items/index.d.ts +2 -2
  43. package/dist/board/model/items/index.js +2 -2
  44. package/dist/board/model/media-playback.d.ts +2 -1
  45. package/dist/board/model/media-playback.js +15 -5
  46. package/dist/board/model/media.js +7 -1
  47. package/dist/board/model/overlay-placement.d.ts +25 -0
  48. package/dist/board/model/overlay-placement.js +35 -0
  49. package/dist/board/model/replay.d.ts +1 -1
  50. package/dist/board/model/replay.js +1 -0
  51. package/dist/board/model/sampling.d.ts +19 -0
  52. package/dist/board/model/sampling.js +96 -0
  53. package/dist/board/model/script-example.d.ts +4 -0
  54. package/dist/board/model/script-example.js +21 -0
  55. package/dist/board/model/script-hit.d.ts +27 -0
  56. package/dist/board/model/script-hit.js +109 -0
  57. package/dist/board/model/script-index.d.ts +7 -0
  58. package/dist/board/model/script-index.js +15 -0
  59. package/dist/board/model/script-observers.d.ts +18 -0
  60. package/dist/board/model/script-observers.js +92 -0
  61. package/dist/board/model/script-scene.d.ts +24 -0
  62. package/dist/board/model/script-scene.js +294 -0
  63. package/dist/board/model/script.d.ts +149 -0
  64. package/dist/board/model/script.js +1 -0
  65. package/dist/board/render/board-background.js +5 -4
  66. package/dist/board/render/css-color.d.ts +6 -1
  67. package/dist/board/render/css-color.js +34 -20
  68. package/dist/board/render/diagnostics.d.ts +20 -0
  69. package/dist/board/render/diagnostics.js +117 -0
  70. package/dist/board/render/index.d.ts +9 -6
  71. package/dist/board/render/index.js +10 -7
  72. package/dist/board/render/media-interaction.js +1 -1
  73. package/dist/board/render/palette.d.ts +2 -1
  74. package/dist/board/render/palette.js +6 -2
  75. package/dist/board/render/renderers/arrow-card-renderer.js +3 -2
  76. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  77. package/dist/board/render/renderers/board-renderer-registry.d.ts +25 -5
  78. package/dist/board/render/renderers/board-renderer-registry.js +11 -4
  79. package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
  80. package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +18 -16
  81. package/dist/board/render/renderers/draw-card-renderer.js +3 -2
  82. package/dist/board/render/renderers/effect-card-renderer.js +22 -5
  83. package/dist/board/render/renderers/frame-card-renderer.js +30 -14
  84. package/dist/board/render/renderers/image-card-renderer.js +43 -4
  85. package/dist/board/render/renderers/shape-card-renderer.js +10 -27
  86. package/dist/board/render/renderers/task-card-renderer.d.ts +2 -1
  87. package/dist/board/render/renderers/task-card-renderer.js +220 -71
  88. package/dist/board/render/renderers/text-card-renderer.js +5 -3
  89. package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
  90. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  91. package/dist/board/render/sampling.d.ts +22 -0
  92. package/dist/board/render/sampling.js +21 -0
  93. package/dist/board/render/script-export.d.ts +11 -0
  94. package/dist/board/render/script-export.js +86 -0
  95. package/dist/board/render/script-host.d.ts +31 -0
  96. package/dist/board/render/script-host.js +446 -0
  97. package/dist/board/render/script-modules.d.ts +2 -0
  98. package/dist/board/render/script-modules.js +2 -0
  99. package/dist/board/render/script-runtime.d.ts +29 -0
  100. package/dist/board/render/script-runtime.js +223 -0
  101. package/dist/board/render/script-sandbox.d.ts +4 -0
  102. package/dist/board/render/script-sandbox.js +173 -0
  103. package/dist/board/render/script-texture.d.ts +11 -0
  104. package/dist/board/render/script-texture.js +33 -0
  105. package/dist/board/render/stroke.d.ts +2 -1
  106. package/dist/board/render/stroke.js +22 -1
  107. package/dist/board/render/text-measurement.js +1 -1
  108. package/dist/board/stage/assets.js +7 -2
  109. package/dist/board/stage/maintenance.d.ts +5 -0
  110. package/dist/board/stage/maintenance.js +11 -0
  111. package/dist/board/stage/scene.d.ts +2 -0
  112. package/dist/board/stage/scene.js +21 -2
  113. package/dist/board/stage/script-pointer.d.ts +20 -0
  114. package/dist/board/stage/script-pointer.js +41 -0
  115. package/dist/board/stage/stage.d.ts +21 -3
  116. package/dist/board/stage/stage.js +350 -47
  117. package/dist/board/stage/texture-pool.js +2 -3
  118. package/dist/chunks/environment.d.ts +305 -27
  119. package/dist/chunks/environment.js +12 -4
  120. package/dist/chunks/host-bridge.d.ts +2 -2
  121. package/dist/chunks/http.d.ts +90 -7
  122. package/dist/chunks/http.js +157 -64
  123. package/dist/chunks/transport.js +4 -2
  124. package/dist/chunks/websocket.d.ts +9 -1
  125. package/dist/chunks/websocket.js +16 -2
  126. package/dist/host-bridge.js +2 -1
  127. package/dist/http.d.ts +3 -3
  128. package/dist/index.d.ts +182 -4
  129. package/dist/index.js +1559 -299
  130. package/dist/protocol/dist/board-assets.d.ts +1 -0
  131. package/dist/protocol/dist/board-constants.js +2 -2
  132. package/dist/protocol/dist/board-model.d.ts +236 -11
  133. package/dist/protocol/dist/board-model.js +46 -17
  134. package/dist/protocol/dist/board-patch.js +2 -1
  135. package/dist/protocol/dist/board-schema.d.ts +4 -2
  136. package/dist/protocol/dist/board-schema.js +18 -4
  137. package/dist/protocol/dist/board-script-modules.d.ts +25 -0
  138. package/dist/protocol/dist/board-script-modules.js +127 -0
  139. package/dist/protocol/dist/board-task.d.ts +22 -0
  140. package/dist/{board/model/task.js → protocol/dist/board-task.js} +34 -7
  141. package/dist/protocol/dist/display/index.d.ts +1 -0
  142. package/dist/protocol/dist/generation/blocks.d.ts +8 -0
  143. package/dist/{generation-blocks.js → protocol/dist/generation/blocks.js} +14 -15
  144. package/dist/protocol/dist/index.d.ts +7 -2
  145. package/dist/protocol/dist/realtime/types.d.ts +1 -0
  146. package/dist/protocol/dist/rtc/index.d.ts +1 -0
  147. package/dist/protocol/dist/task/index.d.ts +4 -0
  148. package/package.json +2 -1
  149. package/dist/board/export/node/sketch.d.ts +0 -19
  150. package/dist/board/export/node/sketch.js +0 -146
  151. package/dist/board/model/task.d.ts +0 -11
  152. package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
  153. package/dist/board/render/sketch-host.d.ts +0 -14
  154. package/dist/board/render/sketch-host.js +0 -216
  155. package/dist/types.d.ts +0 -36
@@ -90,6 +90,11 @@ type BoardPointerEvent = {
90
90
  pressure: number;
91
91
  samples?: readonly BoardDrawInputSample[];
92
92
  };
93
+ type BoardTransformEnd = {
94
+ ids: string[];
95
+ operation: "move" | "resize" | "rotate" | "arrow";
96
+ cancelled: boolean;
97
+ };
93
98
  declare function createInteractionModule(ctx: EditorContext): {
94
99
  marquee: () => Rect | null;
95
100
  hoveredControl: () => BoardTransformControl | null;
@@ -108,8 +113,9 @@ declare function createInteractionModule(ctx: EditorContext): {
108
113
  pointerUp: (event: BoardPointerEvent) => void;
109
114
  pointerLeave: () => void;
110
115
  cancel: () => void;
111
- destroy: () => void;
116
+ subscribeTransforms(listener: (event: BoardTransformEnd) => void): () => void;
117
+ destroy(): void;
112
118
  };
113
119
  type InteractionModule = ReturnType<typeof createInteractionModule>;
114
120
  //#endregion
115
- export { BoardInteraction, BoardPointerEvent, InteractionModule, createInteractionModule };
121
+ export { BoardInteraction, BoardPointerEvent, BoardTransformEnd, InteractionModule, createInteractionModule };
@@ -15,6 +15,23 @@ function angleFrom(center, point) {
15
15
  return Math.atan2(point.y - center.y, point.x - center.x) * 180 / Math.PI;
16
16
  }
17
17
  function createInteractionModule(ctx) {
18
+ const transformListeners = /* @__PURE__ */ new Set();
19
+ function transformEnd(gesture, cancelled) {
20
+ if (!transformListeners.size || !("moved" in gesture) || !gesture.moved) return;
21
+ let event;
22
+ if (gesture.type === "translating" || gesture.type === "resizing" || gesture.type === "rotating") event = {
23
+ ids: [...gesture.origin.keys()],
24
+ operation: gesture.type === "translating" ? "move" : gesture.type === "resizing" ? "resize" : "rotate",
25
+ cancelled
26
+ };
27
+ else if (gesture.type === "draggingArrowHandle") event = {
28
+ ids: [gesture.arrowId],
29
+ operation: "arrow",
30
+ cancelled
31
+ };
32
+ else return;
33
+ for (const listener of transformListeners) listener(event);
34
+ }
18
35
  const { state, options, registry } = ctx;
19
36
  const activePointers = /* @__PURE__ */ new Map();
20
37
  let pinch = null;
@@ -493,6 +510,10 @@ function createInteractionModule(ctx) {
493
510
  if (activePointers.size > 0) return;
494
511
  const gesture = state.interaction;
495
512
  if (gesture.type === "drawing" && gesture.pointerId !== event.pointerId) return;
513
+ if (event.cancelled) {
514
+ cancel();
515
+ return;
516
+ }
496
517
  state.snapGuides = [];
497
518
  state.interaction = { type: "idle" };
498
519
  switch (gesture.type) {
@@ -531,6 +552,7 @@ function createInteractionModule(ctx) {
531
552
  case "creatingBox": ctx.creation.commitBox(gesture);
532
553
  }
533
554
  ctx.remote.flushPending();
555
+ transformEnd(gesture, false);
534
556
  }
535
557
  function pointerLeave() {
536
558
  if (state.interaction.type !== "idle") return;
@@ -538,12 +560,14 @@ function createInteractionModule(ctx) {
538
560
  state.hoverId = null;
539
561
  }
540
562
  function cancel() {
563
+ const gesture = state.interaction;
541
564
  activePointers.clear();
542
565
  pinch = null;
543
566
  state.snapGuides = [];
544
567
  state.interaction = { type: "idle" };
545
568
  ctx.document.discardDraft();
546
569
  ctx.remote.flushPending();
570
+ transformEnd(gesture, true);
547
571
  }
548
572
  const ports = memo(() => [
549
573
  state.scene,
@@ -577,7 +601,16 @@ function createInteractionModule(ctx) {
577
601
  pointerUp,
578
602
  pointerLeave,
579
603
  cancel,
580
- destroy: () => activePointers.clear()
604
+ subscribeTransforms(listener) {
605
+ transformListeners.add(listener);
606
+ return () => {
607
+ transformListeners.delete(listener);
608
+ };
609
+ },
610
+ destroy() {
611
+ activePointers.clear();
612
+ transformListeners.clear();
613
+ }
581
614
  };
582
615
  }
583
616
  //#endregion
@@ -1,11 +1,11 @@
1
1
  import { Rect, WorldPoint } from "../model/geometry.js";
2
- import { BoardSceneItem } from "../model/scene.js";
2
+ import { BoardScene, BoardSceneItem } from "../model/scene.js";
3
3
  import "../model/index.js";
4
4
  import { EditorContext } from "./context.js";
5
5
  //#region src/board/editor/query.d.ts
6
6
  declare function createQueryModule(ctx: EditorContext): {
7
7
  invalidate: (ids?: readonly string[]) => void;
8
- boundsOf: (item: BoardSceneItem) => Rect;
8
+ boundsOf: (item: BoardSceneItem, scene?: BoardScene) => Rect;
9
9
  itemById: (id: string) => BoardSceneItem | null;
10
10
  isLocked: (item: {
11
11
  locked?: boolean;
@@ -14,10 +14,10 @@ declare function createQueryModule(ctx: EditorContext): {
14
14
  rootsOf: (ids: Iterable<string>) => string[];
15
15
  withSubtrees: (ids: Iterable<string>) => Set<string>;
16
16
  mediaIdsForPath: (path: string) => ReadonlySet<string>;
17
- contentBounds: (ids?: Iterable<string>) => Rect | null;
18
- idsInRect: (rect: Rect) => string[];
17
+ contentBounds: (ids?: Iterable<string>, scene?: BoardScene) => Rect | null;
18
+ idsInRect: (rect: Rect, includeTemporary?: boolean) => string[];
19
19
  frameAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => BoardSceneItem | null;
20
- itemAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => BoardSceneItem | null;
20
+ itemAt: (point: WorldPoint, exclude?: ReadonlySet<string>, includeTemporary?: boolean) => BoardSceneItem | null;
21
21
  labelItemAt: (point: WorldPoint) => BoardSceneItem | null;
22
22
  arrowHandleAt: (point: WorldPoint) => "start" | "end" | "mid" | null;
23
23
  connectTargetAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => string | null;
@@ -1,3 +1,4 @@
1
+ import { parseBoardMediaSource } from "../../protocol/dist/board-model.js";
1
2
  import { rectContainsPoint, unionRects } from "../model/geometry.js";
2
3
  import { arrowHitRadius, arrowPathBounds, distanceToArrow, resolveSceneArrow } from "../model/arrow-geometry.js";
3
4
  import { createSpatialIndex } from "./spatial.js";
@@ -53,8 +54,8 @@ function createQueryModule(ctx) {
53
54
  }
54
55
  spatial.upsert(upserts);
55
56
  }
56
- function boundsOf(item) {
57
- if (item.type === "arrow") return arrowPathBounds(resolveSceneArrow(item, state.scene), item.style.strokeWidth);
57
+ function boundsOf(item, scene = state.scene) {
58
+ if (item.type === "arrow") return arrowPathBounds(resolveSceneArrow(item, scene), item.style.strokeWidth);
58
59
  return registry.bounds(item);
59
60
  }
60
61
  function itemById(id) {
@@ -87,7 +88,7 @@ function createQueryModule(ctx) {
87
88
  const result = /* @__PURE__ */ new Set();
88
89
  for (const id of ids) {
89
90
  result.add(id);
90
- for (const child of state.scene.descendants(id)) result.add(child);
91
+ for (const child of state.scene.descendants(id)) if (state.base.items[child]) result.add(child);
91
92
  }
92
93
  return result;
93
94
  }
@@ -97,6 +98,7 @@ function createQueryModule(ctx) {
97
98
  for (const item of state.scene.items) {
98
99
  if (item.type !== "image" && item.type !== "video" && item.type !== "audio") continue;
99
100
  const src = item.props.src;
101
+ if (parseBoardMediaSource(src)?.kind !== "file") continue;
100
102
  const ids = mediaIdsByPath.get(src) ?? /* @__PURE__ */ new Set();
101
103
  ids.add(item.id);
102
104
  mediaIdsByPath.set(src, ids);
@@ -104,20 +106,20 @@ function createQueryModule(ctx) {
104
106
  }
105
107
  return mediaIdsByPath.get(path) ?? NO_IDS;
106
108
  }
107
- function contentBounds(ids) {
108
- const scene = state.scene;
109
+ function contentBounds(ids, scene = state.scene) {
109
110
  const list = ids ? [...ids].flatMap((id) => scene.get(id) ? [scene.get(id)] : []) : scene.items;
110
- return unionRects(list.map(boundsOf));
111
+ return unionRects((ctx.document.hasScriptItems ? list.filter((item) => Boolean(state.base.items[item.id])) : list).map((item) => boundsOf(item, scene)));
111
112
  }
112
- function idsInRect(rect) {
113
+ function idsInRect(rect, includeTemporary = false) {
113
114
  ensureSpatial();
114
- return spatial.idsInRect(rect);
115
+ const ids = spatial.idsInRect(rect);
116
+ return includeTemporary || !ctx.document.hasScriptItems ? ids : ids.filter((id) => Boolean(state.base.items[id]));
115
117
  }
116
118
  function frameAt(point, exclude) {
117
119
  ensureSpatial();
118
120
  for (const id of spatial.idsAtPoint(point)) {
119
121
  const item = state.scene.get(id);
120
- if (item?.type !== "frame" || exclude?.has(id)) continue;
122
+ if (!state.base.items[id] || item?.type !== "frame" || exclude?.has(id)) continue;
121
123
  if (registry.hitTest(item, point)) return item;
122
124
  }
123
125
  return null;
@@ -129,11 +131,11 @@ function createQueryModule(ctx) {
129
131
  }
130
132
  return registry.hitTest(item, point);
131
133
  }
132
- function itemAt(point, exclude) {
134
+ function itemAt(point, exclude, includeTemporary = false) {
133
135
  ensureSpatial();
134
136
  for (const id of spatial.idsAtPoint(point)) {
135
137
  const item = state.scene.get(id);
136
- if (item && !exclude?.has(id) && hitsItem(item, point)) return item;
138
+ if (item && (includeTemporary || state.base.items[id]) && !exclude?.has(id) && hitsItem(item, point)) return item;
137
139
  }
138
140
  return null;
139
141
  }
@@ -144,7 +146,7 @@ function createQueryModule(ctx) {
144
146
  const candidates = selection.length === 1 ? [...selection, ...nearby] : nearby;
145
147
  for (const id of new Set(candidates)) {
146
148
  const item = scene.get(id);
147
- if (!item || isLocked(item)) continue;
149
+ if (!item || !state.base.items[id] || isLocked(item)) continue;
148
150
  if (item.type === "frame" && rectContainsPoint({
149
151
  x: item.frame.x,
150
152
  y: item.frame.y - 22,
@@ -19,9 +19,10 @@ function createSelectionModule(ctx) {
19
19
  const bounds = memo(() => [items()], (selected) => selectionBounds(selected.map((item) => item.frame)));
20
20
  const transform = memo(() => [items(), bounds()], (selected, rect) => resolveSelectionTransform(registry, selected, rect));
21
21
  function set(ids) {
22
- state.selection = ids;
22
+ state.selection = ids.filter((id) => Boolean(state.base.items[id]));
23
23
  }
24
24
  function toggle(id) {
25
+ if (!state.base.items[id]) return;
25
26
  const { selection } = state;
26
27
  state.selection = selection.includes(id) ? selection.filter((selected) => selected !== id) : [...selection, id];
27
28
  }
@@ -1,4 +1,3 @@
1
- import { BoardTaskSnapshot } from "../../protocol/dist/board-model.js";
2
1
  import { BoardFileSnapshotFacts } from "../model/file-snapshot.js";
3
2
  import "../model/index.js";
4
3
  import { EditorContext } from "./context.js";
@@ -20,7 +19,6 @@ declare function createSnapshotsModule(ctx: EditorContext): {
20
19
  snapshot: BoardFileSnapshotFacts;
21
20
  replace?: boolean;
22
21
  }>) => void;
23
- applyTaskSnapshots: (snapshots: ReadonlyMap<string, BoardTaskSnapshot>) => void;
24
22
  };
25
23
  type SnapshotsModule = ReturnType<typeof createSnapshotsModule>;
26
24
  //#endregion
@@ -1,15 +1,16 @@
1
+ import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
1
2
  import { mergeFileSnapshot } from "../model/file-preview.js";
2
- import { featuredTaskArtifact } from "../model/task.js";
3
3
  import { sameJson } from "./document-edits.js";
4
4
  //#region src/board/editor/snapshots.ts
5
5
  function fitHeight(item, natural) {
6
6
  const height = item.size.width * natural.height / natural.width;
7
7
  if (!Number.isFinite(height) || height <= 0) return null;
8
+ const shift = item.type === "task" ? 0 : (item.size.height - height) / 2;
8
9
  return {
9
10
  ...item,
10
11
  position: {
11
12
  x: item.position.x,
12
- y: item.position.y + (item.size.height - height) / 2
13
+ y: item.position.y + shift
13
14
  },
14
15
  size: {
15
16
  ...item.size,
@@ -105,27 +106,10 @@ function createSnapshotsModule(ctx) {
105
106
  }
106
107
  commit(changes);
107
108
  }
108
- function applyTaskSnapshots(snapshots) {
109
- if (snapshots.size === 0) return;
110
- const changes = /* @__PURE__ */ new Map();
111
- for (const [id, item] of Object.entries(state.base.items)) {
112
- if (item.type !== "task") continue;
113
- const snapshot = snapshots.get(item.props.taskRunId);
114
- if (snapshot && !sameJson(snapshot, item.props.snapshot)) changes.set(id, {
115
- ...item,
116
- props: {
117
- ...item.props,
118
- snapshot
119
- }
120
- });
121
- }
122
- commit(changes);
123
- }
124
109
  return {
125
110
  applyMediaFileChange,
126
111
  adoptMediaNaturalSizes,
127
- applyFileSnapshots,
128
- applyTaskSnapshots
112
+ applyFileSnapshots
129
113
  };
130
114
  }
131
115
  //#endregion
@@ -1,9 +1,12 @@
1
1
  import { BoardDocument } from "../../protocol/dist/board-model.js";
2
+ import { BoardFrame } from "../../protocol/dist/board-layout.js";
2
3
  import "../../protocol/dist/index.js";
3
- import { BoardSceneItem } from "../model/scene.js";
4
+ import { BoardScene, BoardSceneItem } from "../model/scene.js";
5
+ import { BoardEvaluation } from "../model/animation.js";
6
+ import { BoardAnimationSampler } from "../model/sampling.js";
4
7
  import { BoardExportPlan, BoardExportPlanInput, BoardExportRegion } from "../model/export-plan.js";
5
8
  import { BoardShapeColors } from "../model/palette.js";
6
- import { BoardCardRendererResolver, BoardRenderPalette, BoardSketchHost } from "../render/renderers/board-renderer-registry.js";
9
+ import { BoardCardRendererResolver, BoardRenderPalette, BoardScriptHost } from "../render/renderers/board-renderer-registry.js";
7
10
  import "../render/index.js";
8
11
  import { BoardExportScene, BoardExportSceneInput, createBoardExportScene } from "./scene.js";
9
12
  import { ICanvas, Renderer, Texture } from "pixi.js";
@@ -11,6 +14,11 @@ import { ICanvas, Renderer, Texture } from "pixi.js";
11
14
  type BoardExportBackground = "paper" | "transparent" | number;
12
15
  type BoardExportOptions = {
13
16
  region?: BoardExportRegion;
17
+ camera?: {
18
+ width: number;
19
+ height: number;
20
+ };
21
+ sampler?: BoardAnimationSampler;
14
22
  at?: {
15
23
  animation?: string;
16
24
  time: number;
@@ -22,7 +30,7 @@ type BoardExportOptions = {
22
30
  palette?: Partial<BoardRenderPalette>;
23
31
  colors?: BoardShapeColors;
24
32
  textures?: Map<string, Texture>;
25
- sketches?: BoardSketchHost;
33
+ scripts?: BoardScriptHost;
26
34
  assetKey?: (item: BoardSceneItem) => string | null;
27
35
  renderers?: BoardCardRendererResolver;
28
36
  backgroundImage?: {
@@ -50,7 +58,20 @@ type BoardExportResult = {
50
58
  plan: BoardExportPlan;
51
59
  warnings: BoardExportWarning[];
52
60
  };
61
+ declare function prepareBoardExport(input: BoardDocument, options?: BoardExportOptions): {
62
+ document: BoardDocument;
63
+ scene: BoardScene;
64
+ plan: BoardExportPlan;
65
+ sampler: {
66
+ frameAt(id: string, time: number): BoardFrame | undefined;
67
+ at(time: number): {
68
+ document: BoardDocument;
69
+ evaluation: BoardEvaluation;
70
+ scene: BoardScene;
71
+ };
72
+ } | undefined;
73
+ } | null;
53
74
  declare function renderBoardExport(renderer: Renderer, input: BoardDocument, options?: BoardExportOptions): BoardExportResult | null;
54
75
  declare function describeBoardExportWarning(warning: BoardExportWarning): string;
55
76
  //#endregion
56
- export { BoardExportBackground, BoardExportOptions, type BoardExportPlan, type BoardExportPlanInput, type BoardExportRegion, BoardExportResult, type BoardExportScene, type BoardExportSceneInput, BoardExportWarning, createBoardExportScene, describeBoardExportWarning, renderBoardExport };
77
+ export { BoardExportBackground, BoardExportOptions, type BoardExportPlan, type BoardExportPlanInput, type BoardExportRegion, BoardExportResult, type BoardExportScene, type BoardExportSceneInput, BoardExportWarning, createBoardExportScene, describeBoardExportWarning, prepareBoardExport, renderBoardExport };
@@ -1,8 +1,9 @@
1
- import { boardDocumentAt } from "../model/animation.js";
2
- import { normalizeBoardDocument, planBoardExport } from "../model/export-plan.js";
3
1
  import { buildBoardScene } from "../model/scene.js";
2
+ import { resolveBoardAnimationCamera } from "../model/sampling.js";
3
+ import { normalizeBoardDocument, planBoardExport } from "../model/export-plan.js";
4
4
  import { parseBoardCssColor } from "../render/css-color.js";
5
5
  import { defaultBoardPalette } from "../render/palette.js";
6
+ import { createBoardRenderSampler } from "../render/sampling.js";
6
7
  import { ensureBoardTextMeasurement } from "../render/text-measurement.js";
7
8
  import { createBoardExportScene } from "./scene.js";
8
9
  import { Rectangle } from "pixi.js";
@@ -14,20 +15,51 @@ function resolveBackground(background, palette, document, colorScheme) {
14
15
  const css = typeof declared === "object" ? declared[colorScheme] : declared;
15
16
  return css ? parseBoardCssColor(css) ?? palette.bg : palette.bg;
16
17
  }
17
- function renderBoardExport(renderer, input, options = {}) {
18
+ function prepareBoardExport(input, options = {}) {
18
19
  ensureBoardTextMeasurement();
19
- const base = normalizeBoardDocument(input);
20
- const document = options.at ? boardDocumentAt(base, options.at.animation ?? null, options.at.time).document : base;
21
- const scene = buildBoardScene(document);
20
+ const base = options.sampler ? input : normalizeBoardDocument(input);
21
+ if (options.camera && options.region && options.region.kind !== "all") throw new Error("Camera export cannot be combined with a fixed region.");
22
+ if (options.camera && !options.at) throw new Error("Camera export needs an animation time.");
23
+ if (options.camera && ![options.camera.width, options.camera.height].every((n) => Number.isSafeInteger(n) && n > 0)) throw new Error("Camera dimensions must be positive integers.");
24
+ const sampler = options.at ? options.sampler ?? createBoardRenderSampler(base, options.at.animation ?? null, options) : void 0;
25
+ const sample = options.at ? sampler?.at(options.at.time) : void 0;
26
+ const sampled = sample?.document ?? base;
27
+ const document = options.scripts?.document?.(sampled, options.at?.time ?? 0) ?? sampled;
28
+ const scene = document === sampled ? sample?.scene ?? buildBoardScene(document) : buildBoardScene(document);
29
+ const camera = options.camera ? resolveBoardAnimationCamera(sample?.evaluation.camera ?? {}, (id) => scene.get(id)?.frame, options.camera, options.at?.time ?? 0) : void 0;
30
+ const region = camera && options.camera ? {
31
+ kind: "rect",
32
+ rect: {
33
+ x: -camera.x / camera.zoom,
34
+ y: -camera.y / camera.zoom,
35
+ width: options.camera.width / camera.zoom,
36
+ height: options.camera.height / camera.zoom
37
+ }
38
+ } : options.region ?? { kind: "all" };
22
39
  const plan = planBoardExport({
23
40
  scene,
24
- region: options.region ?? { kind: "all" },
25
- scale: options.scale,
26
- padding: options.padding,
41
+ region,
42
+ scale: camera?.zoom ?? options.scale,
43
+ padding: camera ? 0 : options.padding,
27
44
  maxEdge: options.maxEdge,
28
45
  maxPixels: options.maxPixels
29
46
  });
30
- if (!plan) return null;
47
+ if (plan && camera && options.camera) {
48
+ const ratio = plan.clamped ? plan.scale / camera.zoom : 1;
49
+ plan.width = Math.max(1, Math.floor(options.camera.width * ratio));
50
+ plan.height = Math.max(1, Math.floor(options.camera.height * ratio));
51
+ }
52
+ return plan ? {
53
+ document,
54
+ scene,
55
+ plan,
56
+ sampler
57
+ } : null;
58
+ }
59
+ function renderBoardExport(renderer, input, options = {}) {
60
+ const prepared = prepareBoardExport(input, options);
61
+ if (!prepared) return null;
62
+ const { document, scene, plan, sampler } = prepared;
31
63
  const colorScheme = options.colorScheme ?? "dark";
32
64
  const palette = {
33
65
  ...defaultBoardPalette(colorScheme),
@@ -38,13 +70,14 @@ function renderBoardExport(renderer, input, options = {}) {
38
70
  scene,
39
71
  items: plan.items,
40
72
  time: options.at?.time ?? 0,
73
+ frameAt: sampler?.frameAt,
41
74
  world: plan.world,
42
75
  scale: plan.scale,
43
76
  colorScheme,
44
77
  palette,
45
78
  colors: options.colors,
46
79
  textures: options.textures,
47
- sketches: options.sketches,
80
+ scripts: options.scripts,
48
81
  assetKey: options.assetKey,
49
82
  renderers: options.renderers,
50
83
  background: resolveBackground(options.background, palette, document, colorScheme),
@@ -89,4 +122,4 @@ function describeBoardExportWarning(warning) {
89
122
  }
90
123
  }
91
124
  //#endregion
92
- export { createBoardExportScene, describeBoardExportWarning, renderBoardExport };
125
+ export { createBoardExportScene, describeBoardExportWarning, prepareBoardExport, renderBoardExport };
@@ -1,8 +1,8 @@
1
1
  import { BoardDocument } from "../../../protocol/dist/board-model.js";
2
2
  import "../../../protocol/dist/index.js";
3
- import { BoardSketchHost } from "../../render/renderers/board-renderer-registry.js";
3
+ import { BoardScriptHost } from "../../render/renderers/board-renderer-registry.js";
4
4
  import { BoardExportOptions, BoardExportResult } from "../index.js";
5
- import { createNodeBoardSketchHost } from "./sketch.js";
5
+ import { createNodeBoardScriptHost } from "./scripts.js";
6
6
  import { Renderer } from "pixi.js";
7
7
  //#region src/board/export/node/index.d.ts
8
8
  type NodeCanvasModule = {
@@ -24,6 +24,7 @@ type BoardNodeRendererOptions = {
24
24
  };
25
25
  type BoardNodeTexture = {
26
26
  readonly __boardTexture: unique symbol;
27
+ destroy: (destroySource?: boolean) => void;
27
28
  };
28
29
  type BoardNodeRenderer = {
29
30
  renderer: Renderer;
@@ -41,7 +42,7 @@ type BoardNodeExportOptions = Omit<BoardExportOptions, "textures" | "backgroundI
41
42
  position: "center" | "top" | "bottom" | "left" | "right";
42
43
  opacity: number;
43
44
  };
44
- sketches?: BoardSketchHost;
45
+ scripts?: BoardScriptHost;
45
46
  format?: BoardNodeExportFormat;
46
47
  quality?: number;
47
48
  };
@@ -52,4 +53,4 @@ type BoardNodeExportResult = Omit<BoardExportResult, "canvas"> & {
52
53
  declare function boardImageMimeType(format: BoardNodeExportFormat): string;
53
54
  declare function exportBoardImageBytes(renderer: BoardNodeRenderer, document: BoardDocument, options?: BoardNodeExportOptions): BoardNodeExportResult | null;
54
55
  //#endregion
55
- export { BoardNodeExportFormat, BoardNodeExportOptions, BoardNodeExportResult, BoardNodeFont, BoardNodeRenderer, BoardNodeRendererOptions, BoardNodeTexture, NodeCanvasModule, boardImageMimeType, createNodeBoardRenderer, createNodeBoardSketchHost, exportBoardImageBytes };
56
+ export { BoardNodeExportFormat, BoardNodeExportOptions, BoardNodeExportResult, BoardNodeFont, BoardNodeRenderer, BoardNodeRendererOptions, BoardNodeTexture, NodeCanvasModule, boardImageMimeType, createNodeBoardRenderer, createNodeBoardScriptHost, exportBoardImageBytes };
@@ -1,7 +1,7 @@
1
1
  import "../../../protocol/dist/board-constants.js";
2
2
  import { installBoardTextMeasurement } from "../../render/text-measurement.js";
3
3
  import { renderBoardExport } from "../index.js";
4
- import { createNodeBoardSketchHost } from "./sketch.js";
4
+ import { createNodeBoardScriptHost } from "./scripts.js";
5
5
  import { CanvasGraphicsContextSystem, CanvasGraphicsPipe, CanvasRenderer, CanvasRendererTextSystem, CanvasTextPipe, CanvasTextSystem, DOMAdapter, GraphicsContextSystem, GraphicsPipe, ImageSource, Texture, extensions } from "pixi.js";
6
6
  //#region src/board/export/node/index.ts
7
7
  const SYSTEM_UI_FAMILIES = [
@@ -131,10 +131,10 @@ function boardImageMimeType(format) {
131
131
  return MIME[format];
132
132
  }
133
133
  function exportBoardImageBytes(renderer, document, options = {}) {
134
- const { format = "png", quality = .92, textures, sketches, backgroundImage, ...rest } = options;
134
+ const { format = "png", quality = .92, textures, scripts, backgroundImage, ...rest } = options;
135
135
  const result = renderBoardExport(renderer.renderer, document, {
136
136
  ...rest,
137
- ...sketches ? { sketches } : {},
137
+ ...scripts ? { scripts } : {},
138
138
  ...textures ? { textures } : {},
139
139
  ...backgroundImage ? { backgroundImage: {
140
140
  ...backgroundImage,
@@ -143,13 +143,18 @@ function exportBoardImageBytes(renderer, document, options = {}) {
143
143
  });
144
144
  if (!result) return null;
145
145
  const canvas = result.canvas;
146
- const bytes = format === "png" ? canvas.toBuffer(MIME.png) : canvas.toBuffer(MIME[format], Math.round(quality * 100));
147
- return {
148
- bytes: new Uint8Array(bytes),
149
- plan: result.plan,
150
- warnings: result.warnings,
151
- format
152
- };
146
+ try {
147
+ const bytes = format === "png" ? canvas.toBuffer(MIME.png) : canvas.toBuffer(MIME[format], Math.round(quality * 100));
148
+ return {
149
+ bytes: new Uint8Array(bytes),
150
+ plan: result.plan,
151
+ warnings: result.warnings,
152
+ format
153
+ };
154
+ } finally {
155
+ canvas.width = 1;
156
+ canvas.height = 1;
157
+ }
153
158
  }
154
159
  //#endregion
155
- export { boardImageMimeType, createNodeBoardRenderer, createNodeBoardSketchHost, exportBoardImageBytes };
160
+ export { boardImageMimeType, createNodeBoardRenderer, createNodeBoardScriptHost, exportBoardImageBytes };
@@ -0,0 +1,16 @@
1
+ import { BoardSceneItem } from "../../model/scene.js";
2
+ import { BoardScriptHost } from "../../render/renderers/board-renderer-registry.js";
3
+ import { BoardNodeRenderer } from "./index.js";
4
+ //#region src/board/export/node/scripts.d.ts
5
+ type BoardNodeScriptOptions = {
6
+ readModule(src: string): Promise<string>;
7
+ timeoutMs?: number;
8
+ readTimeoutMs?: number;
9
+ };
10
+ declare function createNodeBoardScriptHost(renderer: BoardNodeRenderer, options: BoardNodeScriptOptions): BoardScriptHost & {
11
+ prepare(items: readonly BoardSceneItem[], times: readonly number[], zoom: number): Promise<void>;
12
+ clearFrames(): void;
13
+ destroy(): void;
14
+ };
15
+ //#endregion
16
+ export { BoardNodeScriptOptions, createNodeBoardScriptHost };