@neta-art/cohub 11.5.1 → 12.0.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 (73) hide show
  1. package/dist/board/editor/animation.js +2 -1
  2. package/dist/board/editor/creation.js +1 -1
  3. package/dist/board/editor/document-edits.d.ts +1 -1
  4. package/dist/board/editor/document-edits.js +1 -19
  5. package/dist/board/editor/document.js +2 -1
  6. package/dist/board/editor/index.d.ts +2 -1
  7. package/dist/board/editor/index.js +2 -1
  8. package/dist/board/editor/snapshots.js +11 -13
  9. package/dist/board/export/index.js +1 -1
  10. package/dist/board/export/scene.js +1 -1
  11. package/dist/board/model/client-geometry.d.ts +22 -0
  12. package/dist/board/model/client-geometry.js +94 -0
  13. package/dist/board/model/document-share.d.ts +5 -0
  14. package/dist/board/model/document-share.js +27 -0
  15. package/dist/board/model/index.d.ts +5 -4
  16. package/dist/board/model/index.js +4 -3
  17. package/dist/board/model/items/create.d.ts +5 -1
  18. package/dist/board/model/items/create.js +29 -58
  19. package/dist/board/model/items/index.d.ts +2 -2
  20. package/dist/board/model/items/index.js +2 -2
  21. package/dist/board/model/items/registry.js +2 -5
  22. package/dist/board/model/json.d.ts +4 -0
  23. package/dist/board/model/json.js +22 -0
  24. package/dist/board/model/media-playback.d.ts +6 -3
  25. package/dist/board/player/index.d.ts +6 -2
  26. package/dist/board/player/index.js +33 -15
  27. package/dist/board/render/index.d.ts +2 -2
  28. package/dist/board/render/index.js +6 -6
  29. package/dist/board/render/renderers/arrow-card-renderer.js +5 -1
  30. package/dist/board/render/renderers/audio-card-renderer.js +4 -1
  31. package/dist/board/render/renderers/board-renderer-registry.d.ts +4 -2
  32. package/dist/board/render/renderers/custom-card-renderer.js +5 -2
  33. package/dist/board/render/renderers/file-card-renderer.js +4 -1
  34. package/dist/board/render/renderers/frame-card-renderer.js +5 -1
  35. package/dist/board/render/renderers/image-card-renderer.js +13 -49
  36. package/dist/board/render/renderers/model-card-renderer.js +4 -1
  37. package/dist/board/render/renderers/shape-card-renderer.js +5 -1
  38. package/dist/board/render/renderers/task-card-renderer.js +6 -3
  39. package/dist/board/render/renderers/text-card-renderer.js +6 -2
  40. package/dist/board/render/renderers/unknown-card-renderer.js +4 -0
  41. package/dist/board/render/renderers/video-card-renderer.js +19 -18
  42. package/dist/board/render/script-host.d.ts +2 -0
  43. package/dist/board/render/script-host.js +44 -9
  44. package/dist/board/render/script-resolution.d.ts +4 -0
  45. package/dist/board/render/script-resolution.js +18 -0
  46. package/dist/board/render/surface.d.ts +9 -2
  47. package/dist/board/render/surface.js +15 -1
  48. package/dist/board/replica/index.js +4 -1
  49. package/dist/board/stage/assets.d.ts +12 -8
  50. package/dist/board/stage/assets.js +173 -88
  51. package/dist/board/stage/index.d.ts +3 -3
  52. package/dist/board/stage/index.js +3 -3
  53. package/dist/board/stage/lru.d.ts +3 -2
  54. package/dist/board/stage/lru.js +8 -4
  55. package/dist/board/stage/pointer.d.ts +1 -1
  56. package/dist/board/stage/pointer.js +4 -5
  57. package/dist/board/stage/scene.d.ts +3 -0
  58. package/dist/board/stage/scene.js +44 -8
  59. package/dist/board/stage/stage.d.ts +1 -0
  60. package/dist/board/stage/stage.js +123 -56
  61. package/dist/board/stage/views.d.ts +7 -2
  62. package/dist/board/stage/views.js +50 -21
  63. package/dist/chunks/environment.d.ts +16 -2
  64. package/dist/index.js +65 -22
  65. package/dist/protocol/dist/board-constants.js +56 -1
  66. package/dist/protocol/dist/board-layout.d.ts +8 -1
  67. package/dist/protocol/dist/board-layout.js +16 -4
  68. package/dist/protocol/dist/board-model.js +13 -19
  69. package/dist/protocol/dist/board-schema.d.ts +52 -0
  70. package/dist/protocol/dist/board-schema.js +8 -0
  71. package/dist/protocol/dist/core/media.js +1 -0
  72. package/dist/protocol/dist/index.d.ts +2 -2
  73. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
- import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
1
+ import { cardElevation, containRect, drawCardSurface, fillCardPicture } from "../surface.js";
2
2
  import { positionShell } from "./base-card-renderer.js";
3
3
  import { drawFarPlate } from "./far-plate.js";
4
- import { Container, Graphics, Rectangle, Sprite, Texture } from "pixi.js";
4
+ import { Container, Graphics } from "pixi.js";
5
5
  //#region src/board/render/renderers/image-card-renderer.ts
6
6
  const partsByContainer = /* @__PURE__ */ new WeakMap();
7
7
  function cropRect(crop) {
@@ -15,28 +15,6 @@ function cropRect(crop) {
15
15
  h
16
16
  } : null;
17
17
  }
18
- function showTexture(parts, texture, crop) {
19
- const rect = texture ? cropRect(crop) : null;
20
- const key = rect ? `${rect.x}|${rect.y}|${rect.w}|${rect.h}` : "";
21
- const previous = parts.cropped;
22
- const reusable = previous && parts.cropSource === texture && parts.cropKey === key;
23
- if (texture && rect && !reusable) {
24
- const { frame } = texture;
25
- const width = Math.max(1, frame.width * rect.w);
26
- const height = Math.max(1, frame.height * rect.h);
27
- const x = frame.x + Math.min(frame.width * rect.x, frame.width - width);
28
- const y = frame.y + Math.min(frame.height * rect.y, frame.height - height);
29
- parts.cropped = new Texture({
30
- source: texture.source,
31
- frame: new Rectangle(x, y, width, height)
32
- });
33
- } else if (!rect) parts.cropped = null;
34
- parts.cropSource = rect ? texture : null;
35
- parts.cropKey = key;
36
- const display = parts.cropped ?? texture ?? Texture.EMPTY;
37
- if (parts.sprite.texture !== display) parts.sprite.texture = display;
38
- if (previous && previous !== parts.cropped) previous.destroy(false);
39
- }
40
18
  const PRINT_MARGIN = 6;
41
19
  function sync(container, item, context) {
42
20
  const parts = partsByContainer.get(container);
@@ -51,7 +29,7 @@ function sync(container, item, context) {
51
29
  key ?? "",
52
30
  width,
53
31
  height,
54
- texture ? `${texture.width}x${texture.height}` : "none",
32
+ texture?.uid ?? "none",
55
33
  JSON.stringify(item.props.crop ?? null),
56
34
  failed,
57
35
  elevation,
@@ -59,8 +37,7 @@ function sync(container, item, context) {
59
37
  ].join("|");
60
38
  if (sig === parts.sig) return;
61
39
  parts.sig = sig;
62
- showTexture(parts, texture, item.props.crop);
63
- const display = parts.sprite.texture;
40
+ const crop = cropRect(item.props.crop);
64
41
  const framed = context.style.media === "framed";
65
42
  const margin = framed ? Math.min(PRINT_MARGIN, width / 8, height / 8) : 0;
66
43
  const area = {
@@ -69,7 +46,7 @@ function sync(container, item, context) {
69
46
  width: width - margin * 2,
70
47
  height: height - margin * 2
71
48
  };
72
- const fitted = texture ? containRect(area.width, area.height, display.width, display.height) : {
49
+ const fitted = texture ? containRect(area.width, area.height, texture.width * (crop?.w ?? 1), texture.height * (crop?.h ?? 1)) : {
73
50
  ...area,
74
51
  x: 0,
75
52
  y: 0
@@ -86,16 +63,11 @@ function sync(container, item, context) {
86
63
  width: picture.width + margin * 2,
87
64
  height: picture.height + margin * 2
88
65
  } : picture;
89
- parts.sprite.visible = Boolean(texture);
90
- if (texture) {
91
- parts.sprite.position.set(picture.x, picture.y);
92
- parts.sprite.width = picture.width;
93
- parts.sprite.height = picture.height;
94
- }
95
- traceCardOutline(parts.mask.clear(), picture, framed ? {
66
+ parts.picture.clear();
67
+ if (texture) fillCardPicture(parts.picture, texture, picture, framed ? {
96
68
  corner: "round",
97
69
  radius: 0
98
- } : context.style).fill({ color: 16777215 });
70
+ } : context.style, crop);
99
71
  parts.placeholder.clear();
100
72
  if (texture && !framed) return;
101
73
  drawCardSurface(parts.placeholder, card, context.style, {
@@ -122,20 +94,14 @@ const imageCardRenderer = {
122
94
  canRender: (item) => item.type === "image",
123
95
  create: (item, context) => {
124
96
  const root = new Container();
125
- const sprite = new Sprite(Texture.EMPTY);
126
97
  const placeholder = new Graphics();
127
- const mask = new Graphics();
128
- root.addChild(placeholder, sprite, mask);
129
- sprite.mask = mask;
98
+ const picture = new Graphics();
99
+ root.addChild(placeholder, picture);
130
100
  partsByContainer.set(root, {
131
101
  root,
132
- sprite,
133
102
  placeholder,
134
- mask,
135
- sig: "",
136
- cropped: null,
137
- cropSource: null,
138
- cropKey: ""
103
+ picture,
104
+ sig: ""
139
105
  });
140
106
  if (item.type === "image") sync(root, item, context);
141
107
  return root;
@@ -150,9 +116,7 @@ const imageCardRenderer = {
150
116
  });
151
117
  },
152
118
  destroy: (container) => {
153
- const parts = partsByContainer.get(container);
154
- parts?.root.destroy({ children: true });
155
- parts?.cropped?.destroy(false);
119
+ partsByContainer.get(container)?.root.destroy({ children: true });
156
120
  partsByContainer.delete(container);
157
121
  }
158
122
  };
@@ -42,7 +42,7 @@ function sync(container, item, context) {
42
42
  const elevation = cardElevation(context, item.id);
43
43
  const sig = [
44
44
  key ?? "",
45
- texture ? `${texture.width}x${texture.height}` : "none",
45
+ texture?.uid ?? "none",
46
46
  failed,
47
47
  width,
48
48
  height,
@@ -111,6 +111,9 @@ const modelCardRenderer = {
111
111
  update: (container, item, context) => {
112
112
  if (item.type === "file") sync(container, item, context);
113
113
  },
114
+ resolution: (container, item, context) => {
115
+ if (item.type === "file") sync(container, item, context);
116
+ },
114
117
  zoom: (container, zoom) => {
115
118
  const tag = partsByContainer.get(container)?.tag;
116
119
  if (tag) zoomCardTag(tag, zoom);
@@ -1,7 +1,7 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { worldPoint } from "../../model/geometry.js";
3
- import { itemAlpha, itemColor } from "../palette.js";
4
3
  import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
4
+ import { itemAlpha, itemColor } from "../palette.js";
5
5
  import { dashPattern, roundRectPoints, tracePolyline, trimPolyline } from "../stroke.js";
6
6
  import { positionShell } from "./base-card-renderer.js";
7
7
  import { drawFarPlate } from "./far-plate.js";
@@ -199,6 +199,10 @@ const shapeCardRenderer = {
199
199
  update: (container, item, context) => {
200
200
  if (item.type === "shape") sync(container, item, context);
201
201
  },
202
+ resolution: (container, _item, context) => {
203
+ const parts = partsByContainer.get(container);
204
+ if (parts) syncTextResolution(parts.label, parts, context.zoom);
205
+ },
202
206
  renderFar: (graphics, item, context) => {
203
207
  if (item.type !== "shape") return;
204
208
  drawFarPlate(graphics, item.frame, {
@@ -1,9 +1,9 @@
1
1
  import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { featuredTaskArtifact } from "../../../protocol/dist/board-task.js";
3
+ import { drawAudioWaveform } from "../audio-waveform.js";
3
4
  import { fitTextToLines } from "../text-fit.js";
4
5
  import { boardCardDetailed, syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
5
6
  import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
6
- import { drawAudioWaveform } from "../audio-waveform.js";
7
7
  import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
8
8
  import { positionShell } from "./base-card-renderer.js";
9
9
  import { drawFarPlate } from "./far-plate.js";
@@ -259,7 +259,7 @@ function sync(container, item, context) {
259
259
  if (key !== parts.assetKey) {
260
260
  if (parts.assetKey) context.releaseTexture(parts.assetKey);
261
261
  parts.assetKey = key;
262
- if (key) context.acquireTexture(key);
262
+ if (key) context.acquireTexture(key, item);
263
263
  }
264
264
  const texture = key ? context.getTexture(key) : null;
265
265
  const previewFailed = Boolean(key && !texture && context.hasError(key));
@@ -332,7 +332,7 @@ function sync(container, item, context) {
332
332
  artifact?.type ?? "none",
333
333
  artifact?.id ?? "none",
334
334
  item.props.taskRunId,
335
- texture ? `${texture.width}x${texture.height}` : "none",
335
+ texture?.uid ?? "none",
336
336
  context.themeKey
337
337
  ].join("|");
338
338
  if (visualSig !== parts.visualSig) {
@@ -501,6 +501,9 @@ const taskCardRenderer = {
501
501
  const parts = partsByContainer.get(container);
502
502
  return parts ? advance(parts, clock) : null;
503
503
  },
504
+ resolution: (container, item, context) => {
505
+ if (item.type === "task") sync(container, item, context);
506
+ },
504
507
  zoom: (container, zoom) => {
505
508
  const tag = partsByContainer.get(container)?.tag;
506
509
  if (tag) zoomCardTag(tag, zoom);
@@ -1,8 +1,8 @@
1
1
  import { layoutBoardText } from "../../../protocol/dist/board-layout.js";
2
2
  import { boardFontFamily } from "../../model/text-metrics.js";
3
- import { itemAlpha, itemColor } from "../palette.js";
4
- import { BOARD_FONT_STACKS } from "../text-measurement.js";
5
3
  import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
4
+ import { BOARD_FONT_STACKS } from "../text-measurement.js";
5
+ import { itemAlpha, itemColor } from "../palette.js";
6
6
  import { positionShell } from "./base-card-renderer.js";
7
7
  import { drawFarPlate } from "./far-plate.js";
8
8
  import { Container, Graphics, Text } from "pixi.js";
@@ -91,6 +91,10 @@ const textCardRenderer = {
91
91
  update: (container, item, context) => {
92
92
  if (item.type === "text") sync(container, item, context);
93
93
  },
94
+ resolution: (container, _item, context) => {
95
+ const parts = partsByContainer.get(container);
96
+ if (parts) syncTextResolution(parts.body, parts, context.zoom * Math.max(1, parts.body.scale.x));
97
+ },
94
98
  renderFar: (graphics, item, context) => {
95
99
  if (item.type !== "text") return;
96
100
  drawFarPlate(graphics, item.frame, {
@@ -83,6 +83,10 @@ const unknownCardRenderer = {
83
83
  update: (container, item, context) => {
84
84
  sync(container, item, context);
85
85
  },
86
+ resolution: (container, _item, context) => {
87
+ const parts = partsByContainer.get(container);
88
+ if (parts) syncTextResolution(parts.label, parts, context.zoom);
89
+ },
86
90
  renderFar: (graphics, item, context) => {
87
91
  drawFarPlate(graphics, item.frame, {
88
92
  fill: context.palette.muted,
@@ -1,11 +1,11 @@
1
1
  import { getMediaResourceTitle } from "../../model/media.js";
2
2
  import { boardCardDetailed } from "../text-resolution.js";
3
3
  import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
4
- import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
4
+ import { cardElevation, containRect, drawCardSurface, fillCardPicture } from "../surface.js";
5
5
  import { positionShell } from "./base-card-renderer.js";
6
6
  import { drawFarPlate } from "./far-plate.js";
7
7
  import { drawMediaPlayKey } from "./media-key.js";
8
- import { Container, Graphics, Sprite, Texture } from "pixi.js";
8
+ import { Container, Graphics } from "pixi.js";
9
9
  //#region src/board/render/renderers/video-card-renderer.ts
10
10
  const partsByContainer = /* @__PURE__ */ new WeakMap();
11
11
  const FILM_RAIL = 10;
@@ -31,7 +31,7 @@ function sync(container, item, context) {
31
31
  const elevation = cardElevation(context, item.id);
32
32
  const sig = [
33
33
  key ?? "",
34
- texture ? `${texture.width}x${texture.height}` : "none",
34
+ texture?.uid ?? "none",
35
35
  width,
36
36
  height,
37
37
  elevation,
@@ -61,17 +61,18 @@ function sync(container, item, context) {
61
61
  elevation
62
62
  });
63
63
  if (framed) drawFilmRails(parts.plate, width, height, rail, context.palette.bg);
64
- traceCardOutline(parts.mask.clear(), framed ? screen : rect, framed ? {
65
- corner: "round",
66
- radius: 1
67
- } : context.style).fill({ color: 16777215 });
68
- if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
69
- parts.sprite.visible = Boolean(texture);
64
+ parts.picture.clear();
70
65
  if (texture) {
71
66
  const fitted = containRect(screen.width, screen.height, texture.width, texture.height);
72
- parts.sprite.position.set(screen.x + fitted.x, screen.y + fitted.y);
73
- parts.sprite.width = fitted.width;
74
- parts.sprite.height = fitted.height;
67
+ const picture = {
68
+ ...fitted,
69
+ x: screen.x + fitted.x,
70
+ y: screen.y + fitted.y
71
+ };
72
+ fillCardPicture(parts.picture, texture, picture, framed ? {
73
+ corner: "round",
74
+ radius: 1
75
+ } : context.style);
75
76
  }
76
77
  parts.chrome.clear();
77
78
  if (boardCardDetailed(context.zoom)) drawMediaPlayKey(parts.chrome, screen.x + screen.width / 2, screen.y + screen.height / 2, Math.min(18, Math.min(screen.width, screen.height) * .16), context);
@@ -96,17 +97,14 @@ const videoCardRenderer = {
96
97
  create: (item, context) => {
97
98
  const root = new Container();
98
99
  const plate = new Graphics();
99
- const sprite = new Sprite(Texture.EMPTY);
100
- const mask = new Graphics();
100
+ const picture = new Graphics();
101
101
  const chrome = new Graphics();
102
102
  const tag = createCardTag();
103
- root.addChild(plate, sprite, mask, chrome, tag.root);
104
- sprite.mask = mask;
103
+ root.addChild(plate, picture, chrome, tag.root);
105
104
  partsByContainer.set(root, {
106
105
  root,
107
106
  plate,
108
- sprite,
109
- mask,
107
+ picture,
110
108
  chrome,
111
109
  tag,
112
110
  sig: ""
@@ -117,6 +115,9 @@ const videoCardRenderer = {
117
115
  update: (container, item, context) => {
118
116
  if (item.type === "video") sync(container, item, context);
119
117
  },
118
+ resolution: (container, item, context) => {
119
+ if (item.type === "video") sync(container, item, context);
120
+ },
120
121
  zoom: (container, zoom) => {
121
122
  const tag = partsByContainer.get(container)?.tag;
122
123
  if (tag) zoomCardTag(tag, zoom);
@@ -9,6 +9,8 @@ type BoardScriptHostOptions = {
9
9
  readModule(path: string): Promise<string>;
10
10
  host: HTMLElement;
11
11
  onFrame(): void;
12
+ onSchedule?(): void;
13
+ isMaterialized?(id: string): boolean;
12
14
  onOutput(id: string, output: BoardScriptOutput | null): void;
13
15
  capabilities?: Record<string, (id: string, input: unknown) => Promise<unknown>>;
14
16
  timeoutMs?: number;
@@ -1,7 +1,9 @@
1
1
  import { boardScriptItems } from "../model/script-index.js";
2
2
  import { boardScriptScope } from "../model/script-scene.js";
3
3
  import { boardScriptHitVisible, resolveBoardScriptHit } from "../model/script-hit.js";
4
+ import { getBoardResolution } from "./text-resolution.js";
4
5
  import { BoardScriptTexture } from "./script-texture.js";
6
+ import { scriptResolution } from "./script-resolution.js";
5
7
  import { createBoardScriptModuleReader, loadBoardScriptSources } from "../../protocol/dist/board-script-modules.js";
6
8
  import { boardScriptFrameSource } from "./script-sandbox.js";
7
9
  //#region src/board/render/script-host.ts
@@ -33,6 +35,11 @@ function createBoardScriptHost(options) {
33
35
  layer.dataset.boardScripts = "";
34
36
  options.host.appendChild(layer);
35
37
  let disposed = false;
38
+ const schedule = () => (options.onSchedule ?? options.onFrame)();
39
+ let lastZoom = NaN;
40
+ let settledAt = 0;
41
+ let resolutionTimer;
42
+ const needsTick = (entry) => !entry.error && entry.ready && entry.sources !== null && !entry.pending && (entry.dirty || Boolean(entry.output?.updating || entry.output?.animated && entry.draw));
36
43
  const retiredTextures = /* @__PURE__ */ new Set();
37
44
  let retirementFrame = 0;
38
45
  function retireTexture(texture) {
@@ -127,7 +134,7 @@ function createBoardScriptHost(options) {
127
134
  }
128
135
  if (data.kind === "dirty") {
129
136
  entry.dirty = true;
130
- options.onFrame();
137
+ schedule();
131
138
  return;
132
139
  }
133
140
  if (data.kind === "call") {
@@ -168,8 +175,11 @@ function createBoardScriptHost(options) {
168
175
  }
169
176
  clearTimeout(entry.timer);
170
177
  entry.pending = false;
171
- revisions.set(entry.id, (revisions.get(entry.id) ?? 0) + 1);
172
- if (data.bitmap) entry.texture.update(data.bitmap);
178
+ const acceptsBitmap = Boolean(data.bitmap) && (options.export || !options.isMaterialized || options.isMaterialized(entry.id)) && entry.draw;
179
+ if (acceptsBitmap) entry.texture.update(data.bitmap);
180
+ else data.bitmap?.close?.();
181
+ const changed = acceptsBitmap || Boolean(data.output);
182
+ if (changed) revisions.set(entry.id, (revisions.get(entry.id) ?? 0) + 1);
173
183
  try {
174
184
  const output = data.output;
175
185
  if (output) {
@@ -180,7 +190,8 @@ function createBoardScriptHost(options) {
180
190
  fail(entry, error instanceof Error ? error.message : String(error));
181
191
  return;
182
192
  }
183
- options.onFrame();
193
+ if (changed) options.onFrame();
194
+ else if (needsTick(entry)) schedule();
184
195
  };
185
196
  window.addEventListener("message", onMessage);
186
197
  return {
@@ -229,11 +240,20 @@ function createBoardScriptHost(options) {
229
240
  },
230
241
  output: (id) => entries.get(id)?.output ?? null,
231
242
  get animated() {
232
- return [...entries.values()].some((entry) => !entry.error && entry.output?.animated && (entry.visible || entry.output.updating) && !entry.pending);
243
+ for (const entry of entries.values()) if (needsTick(entry)) return true;
244
+ return false;
233
245
  },
234
246
  sync(board, scene, camera, time, inputEnabled, visibleIds) {
235
247
  if (disposed) return;
236
248
  interactive = inputEnabled;
249
+ const now = performance.now();
250
+ if (!options.export && camera.zoom !== lastZoom) {
251
+ const first = Number.isNaN(lastZoom);
252
+ lastZoom = camera.zoom;
253
+ settledAt = first ? now : now + 120;
254
+ clearTimeout(resolutionTimer);
255
+ if (!first) resolutionTimer = setTimeout(schedule, 120);
256
+ }
237
257
  const alive = /* @__PURE__ */ new Set();
238
258
  const liveFocusTargets = /* @__PURE__ */ new Set();
239
259
  let focusSurface;
@@ -282,7 +302,9 @@ function createBoardScriptHost(options) {
282
302
  pending: false,
283
303
  time: -1,
284
304
  sentSources: false,
285
- visible: true
305
+ visible: true,
306
+ draw: false,
307
+ scale: 0
286
308
  };
287
309
  entries.set(id, entry);
288
310
  restored.delete(id);
@@ -313,6 +335,18 @@ function createBoardScriptHost(options) {
313
335
  entry.visible = visible;
314
336
  if (visible) entry.dirty = true;
315
337
  }
338
+ const draw = Boolean(options.export || visible && (options.isMaterialized?.(id) ?? true));
339
+ if (entry.draw !== draw) {
340
+ entry.draw = draw;
341
+ if (draw) entry.dirty = true;
342
+ }
343
+ const frame = scene.get(id)?.frame;
344
+ const itemScale = Math.max((frame?.width ?? box.width) / Math.max(1, box.width), (frame?.height ?? box.height) / Math.max(1, box.height));
345
+ const scale = options.export ? Math.min(2, 2048 / Math.max(1, box.width, box.height)) : scriptResolution(box.width, box.height, now < settledAt && entry.scale > 0 ? entry.scale : camera.zoom * getBoardResolution() * itemScale, entry.scale);
346
+ if (scale !== entry.scale) {
347
+ entry.scale = scale;
348
+ if (draw) entry.dirty = true;
349
+ }
316
350
  if (entry.input !== input || entry.inputDocument !== board) {
317
351
  const inputKey = JSON.stringify(input);
318
352
  if (entry.inputKey !== inputKey) {
@@ -370,7 +404,7 @@ function createBoardScriptHost(options) {
370
404
  }
371
405
  }
372
406
  }
373
- if (!entry.ready || !entry.sources || entry.pending || !entry.dirty && (!entry.output?.animated || !visible && !entry.output.updating || entry.time === time)) continue;
407
+ if (!entry.ready || !entry.sources || entry.pending || !entry.dirty && (!entry.output?.animated || !entry.draw && !entry.output.updating || entry.time === time)) continue;
374
408
  entry.pending = true;
375
409
  entry.dirty = false;
376
410
  entry.time = time;
@@ -382,9 +416,9 @@ function createBoardScriptHost(options) {
382
416
  request: entry.request,
383
417
  input: entry.sentInput === entry.input ? void 0 : entry.input,
384
418
  sources: entry.sentSources ? void 0 : entry.sources,
385
- draw: visible,
419
+ draw: entry.draw,
386
420
  t: time - entry.startedAt,
387
- scale: Math.min(2, 2048 / Math.max(1, box.width, box.height)),
421
+ scale: entry.scale,
388
422
  events: entry.events.splice(0)
389
423
  });
390
424
  entry.sentInput = entry.input;
@@ -428,6 +462,7 @@ function createBoardScriptHost(options) {
428
462
  },
429
463
  destroy() {
430
464
  disposed = true;
465
+ clearTimeout(resolutionTimer);
431
466
  window.removeEventListener("message", onMessage);
432
467
  for (const entry of entries.values()) release(entry);
433
468
  layer.remove();
@@ -0,0 +1,4 @@
1
+ //#region src/board/render/script-resolution.d.ts
2
+ declare function scriptResolution(width: number, height: number, demand: number, current?: number): number;
3
+ //#endregion
4
+ export { scriptResolution };
@@ -0,0 +1,18 @@
1
+ //#region src/board/render/script-resolution.ts
2
+ const RESOLUTIONS = [
3
+ .125,
4
+ .25,
5
+ .5,
6
+ 1,
7
+ 2,
8
+ 4
9
+ ];
10
+ const MAX_EDGE = 2048;
11
+ const MAX_PIXELS = 2097152;
12
+ function scriptResolution(width, height, demand, current = 0) {
13
+ const target = Number.isFinite(demand) && demand > 0 ? demand : 1;
14
+ const tier = current > 0 && target <= current * 1.15 && target >= current * .4 ? current : RESOLUTIONS.find((value) => value >= target) ?? 4;
15
+ return Math.min(tier, MAX_EDGE / Math.max(1, width, height), Math.sqrt(MAX_PIXELS / Math.max(1, width * height)));
16
+ }
17
+ //#endregion
18
+ export { scriptResolution };
@@ -1,6 +1,6 @@
1
1
  import { WorldPoint } from "../model/geometry.js";
2
2
  import { BoardRenderStyle, BoardShadowStyle } from "./style.js";
3
- import { Graphics } from "pixi.js";
3
+ import { Graphics, Texture } from "pixi.js";
4
4
  //#region src/board/render/surface.d.ts
5
5
  declare const PIXEL_STEP = 2;
6
6
  type CardRect = {
@@ -10,6 +10,13 @@ type CardRect = {
10
10
  height: number;
11
11
  };
12
12
  declare function traceCardOutline(graphics: Graphics, rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, grow?: number): Graphics;
13
+ type CardCrop = {
14
+ x: number;
15
+ y: number;
16
+ w: number;
17
+ h: number;
18
+ };
19
+ declare function fillCardPicture(graphics: Graphics, texture: Texture, picture: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, crop?: CardCrop | null): Graphics;
13
20
  declare function tracePixelRect(graphics: Graphics, x: number, y: number, width: number, height: number, step: number): Graphics;
14
21
  declare function cardOutlinePoints(rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, grow?: number): WorldPoint[];
15
22
  declare function drawCardShadow(graphics: Graphics, rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, shadow: BoardShadowStyle | null): void;
@@ -31,4 +38,4 @@ declare function isBoardCard(item: {
31
38
  type: string;
32
39
  }): boolean;
33
40
  //#endregion
34
- export { CardElevation, CardRect, CardSurface, PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, isBoardCard, traceCardOutline, tracePixelRect };
41
+ export { CardCrop, CardElevation, CardRect, CardSurface, PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, fillCardPicture, isBoardCard, traceCardOutline, tracePixelRect };
@@ -1,5 +1,6 @@
1
1
  import { worldPoint } from "../model/geometry.js";
2
2
  import { roundRectPoints } from "./stroke.js";
3
+ import { Matrix } from "pixi.js";
3
4
  //#region src/board/render/surface.ts
4
5
  const PIXEL_STEP = 2;
5
6
  const SHADOW_LAYERS = 5;
@@ -12,6 +13,19 @@ function traceCardOutline(graphics, rect, style, grow = 0) {
12
13
  const radius = Math.min(Math.max(0, style.radius + grow), width / 2, height / 2);
13
14
  return radius > 0 ? graphics.roundRect(x, y, width, height, radius) : graphics.rect(x, y, width, height);
14
15
  }
16
+ function fillCardPicture(graphics, texture, picture, style, crop) {
17
+ const sampling = texture.source.style;
18
+ if (sampling.addressMode !== "mirror-repeat") {
19
+ sampling.addressMode = "mirror-repeat";
20
+ sampling.update();
21
+ }
22
+ const matrix = crop ? new Matrix(1 / crop.w, 0, 0, 1 / crop.h, -crop.x / crop.w, -crop.y / crop.h) : void 0;
23
+ return traceCardOutline(graphics, picture, style).fill({
24
+ texture,
25
+ textureSpace: "local",
26
+ ...matrix ? { matrix } : {}
27
+ });
28
+ }
15
29
  function tracePixelRect(graphics, x, y, width, height, step) {
16
30
  const s = Math.min(step, width / 4, height / 4);
17
31
  if (s <= 0) return graphics.rect(x, y, width, height);
@@ -129,4 +143,4 @@ function isBoardCard(item) {
129
143
  return !FREE_FORM_TYPES.has(item.type);
130
144
  }
131
145
  //#endregion
132
- export { PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, isBoardCard, traceCardOutline, tracePixelRect };
146
+ export { PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, fillCardPicture, isBoardCard, traceCardOutline, tracePixelRect };
@@ -1,5 +1,6 @@
1
1
  import { applyBoardDelta, applyBoardPatchToDocument, parseBoardDocument } from "../../protocol/dist/board-patch.js";
2
2
  import { createBoardEntityId } from "../model/items/id.js";
3
+ import { shareBoardDocument } from "../model/document-share.js";
3
4
  //#region src/board/replica/index.ts
4
5
  function memoryStorage() {
5
6
  return {
@@ -118,7 +119,9 @@ function createBoardReplica(options) {
118
119
  title = result.title;
119
120
  if (readPlaybackRevision === playbackRevision) playback = result.playback;
120
121
  loaded = true;
121
- adoptServer(parsed.document, result.version, true);
122
+ const next = shareBoardDocument(server, parsed.document);
123
+ if (next === server && result.version === version) return;
124
+ adoptServer(next, result.version, true);
122
125
  }
123
126
  function refresh() {
124
127
  if (refreshing) {
@@ -1,5 +1,6 @@
1
1
  import { ModelFormat } from "../../protocol/dist/fs/mime.js";
2
2
  import { BoardSceneItem } from "../model/scene.js";
3
+ import { BoardAssetUrlOptions } from "../model/media-playback.js";
3
4
  import "../model/index.js";
4
5
  import { LruBudget } from "./lru.js";
5
6
  import { BoardImageTexturePool } from "./texture-pool.js";
@@ -7,7 +8,12 @@ import { Texture } from "pixi.js";
7
8
  //#region src/board/stage/assets.d.ts
8
9
  type BoardUrlMedia = "image" | "video";
9
10
  type BoardAssetMedia = BoardUrlMedia | "model";
11
+ declare function boardTextureEdge(item: BoardSceneItem, scale: number): number;
10
12
  declare function boardAssetKey(item: BoardSceneItem): string | null;
13
+ type Size = {
14
+ width: number;
15
+ height: number;
16
+ };
11
17
  type BoardModelPreviewRequest = {
12
18
  key: string;
13
19
  src: string;
@@ -21,24 +27,22 @@ type BoardModelPreviews = {
21
27
  type BoardAssetManager = {
22
28
  assetKey: (item: BoardSceneItem) => string | null;
23
29
  invalidatePath: (path: string) => void;
24
- requestItem: (item: BoardSceneItem) => void;
30
+ requestItem: (item: BoardSceneItem, edge?: number) => void;
25
31
  getTexture: (key: string) => Texture | null;
26
- getNaturalSize: (key: string) => {
27
- width: number;
28
- height: number;
29
- } | null;
32
+ getNaturalSize: (key: string) => Size | null;
30
33
  hasError: (key: string) => boolean;
31
34
  acquire: (key: string) => void;
32
35
  release: (key: string) => void;
33
36
  withTextures: <T>(items: BoardSceneItem[], use: (textures: Map<string, Texture>) => T | Promise<T>, options?: {
34
37
  timeoutMs?: number;
38
+ edge?: (item: BoardSceneItem) => number;
35
39
  }) => Promise<T>;
36
- subscribe: (listener: () => void) => () => void;
40
+ subscribe: (listener: (key: string) => void) => () => void;
37
41
  destroy: () => void;
38
42
  };
39
43
  type BoardAssetManagerOptions = {
40
44
  /** Resolve a Space file path to a URL a texture can load from; null when unavailable. */
41
- resolveFileUrl: (path: string) => Promise<string | null>;
45
+ resolveFileUrl: (path: string, options?: BoardAssetUrlOptions) => Promise<string | null>;
42
46
  concurrency?: number;
43
47
  videoConcurrency?: number;
44
48
  loadVideoPreviews?: boolean;
@@ -51,4 +55,4 @@ type BoardAssetManagerOptions = {
51
55
  };
52
56
  declare function createBoardAssetManager(options: BoardAssetManagerOptions): BoardAssetManager;
53
57
  //#endregion
54
- export { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, boardAssetKey, createBoardAssetManager };
58
+ export { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, boardAssetKey, boardTextureEdge, createBoardAssetManager };