@neta-art/cohub 11.5.1 → 12.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 (82) 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/scene.js +1 -0
  10. package/dist/board/model/client-geometry.d.ts +22 -0
  11. package/dist/board/model/client-geometry.js +94 -0
  12. package/dist/board/model/document-share.d.ts +5 -0
  13. package/dist/board/model/document-share.js +27 -0
  14. package/dist/board/model/index.d.ts +5 -4
  15. package/dist/board/model/index.js +4 -3
  16. package/dist/board/model/items/create.d.ts +5 -1
  17. package/dist/board/model/items/create.js +29 -58
  18. package/dist/board/model/items/index.d.ts +2 -2
  19. package/dist/board/model/items/index.js +2 -2
  20. package/dist/board/model/items/registry.js +2 -5
  21. package/dist/board/model/json.d.ts +4 -0
  22. package/dist/board/model/json.js +22 -0
  23. package/dist/board/model/media-playback.d.ts +6 -3
  24. package/dist/board/player/index.d.ts +6 -2
  25. package/dist/board/player/index.js +33 -15
  26. package/dist/board/render/card-tag.d.ts +0 -1
  27. package/dist/board/render/card-tag.js +15 -17
  28. package/dist/board/render/index.d.ts +3 -3
  29. package/dist/board/render/index.js +3 -3
  30. package/dist/board/render/renderers/arrow-card-renderer.js +8 -6
  31. package/dist/board/render/renderers/audio-card-renderer.js +10 -6
  32. package/dist/board/render/renderers/base-card-renderer.d.ts +1 -1
  33. package/dist/board/render/renderers/base-card-renderer.js +2 -3
  34. package/dist/board/render/renderers/board-renderer-registry.d.ts +6 -2
  35. package/dist/board/render/renderers/custom-card-renderer.js +23 -18
  36. package/dist/board/render/renderers/file-card-renderer.js +14 -9
  37. package/dist/board/render/renderers/frame-card-renderer.js +7 -4
  38. package/dist/board/render/renderers/image-card-renderer.js +13 -49
  39. package/dist/board/render/renderers/model-card-renderer.js +4 -1
  40. package/dist/board/render/renderers/shape-card-renderer.js +8 -6
  41. package/dist/board/render/renderers/task-card-renderer.js +13 -10
  42. package/dist/board/render/renderers/text-card-renderer.js +7 -5
  43. package/dist/board/render/renderers/unknown-card-renderer.js +8 -6
  44. package/dist/board/render/renderers/video-card-renderer.js +19 -18
  45. package/dist/board/render/script-host.d.ts +2 -0
  46. package/dist/board/render/script-host.js +44 -9
  47. package/dist/board/render/script-resolution.d.ts +4 -0
  48. package/dist/board/render/script-resolution.js +18 -0
  49. package/dist/board/render/surface.d.ts +9 -2
  50. package/dist/board/render/surface.js +15 -1
  51. package/dist/board/render/text-resolution.d.ts +4 -7
  52. package/dist/board/render/text-resolution.js +20 -22
  53. package/dist/board/replica/index.js +4 -1
  54. package/dist/board/stage/assets.d.ts +12 -8
  55. package/dist/board/stage/assets.js +173 -88
  56. package/dist/board/stage/canvas-size.d.ts +18 -0
  57. package/dist/board/stage/canvas-size.js +31 -0
  58. package/dist/board/stage/index.d.ts +3 -3
  59. package/dist/board/stage/index.js +3 -3
  60. package/dist/board/stage/lru.d.ts +3 -2
  61. package/dist/board/stage/lru.js +8 -4
  62. package/dist/board/stage/pointer.d.ts +1 -1
  63. package/dist/board/stage/pointer.js +4 -5
  64. package/dist/board/stage/scene.d.ts +8 -1
  65. package/dist/board/stage/scene.js +72 -10
  66. package/dist/board/stage/stage.d.ts +1 -0
  67. package/dist/board/stage/stage.js +151 -63
  68. package/dist/board/stage/text-resolution-tracker.d.ts +8 -0
  69. package/dist/board/stage/text-resolution-tracker.js +33 -0
  70. package/dist/board/stage/views.d.ts +7 -2
  71. package/dist/board/stage/views.js +50 -21
  72. package/dist/chunks/environment.d.ts +16 -2
  73. package/dist/index.js +65 -22
  74. package/dist/protocol/dist/board-constants.js +56 -1
  75. package/dist/protocol/dist/board-layout.d.ts +8 -1
  76. package/dist/protocol/dist/board-layout.js +16 -4
  77. package/dist/protocol/dist/board-model.js +13 -19
  78. package/dist/protocol/dist/board-schema.d.ts +52 -0
  79. package/dist/protocol/dist/board-schema.js +8 -0
  80. package/dist/protocol/dist/core/media.js +1 -0
  81. package/dist/protocol/dist/index.d.ts +2 -2
  82. package/package.json +1 -1
@@ -13,7 +13,6 @@ function sync(container, item, context) {
13
13
  if (!parts) return;
14
14
  const { frame, style, props, locked } = item;
15
15
  positionShell(parts.root, item);
16
- syncTextResolution(parts.label, parts, context.zoom);
17
16
  const fill = itemColor(context, style.fill ?? style.stroke, "neutral", "fill");
18
17
  const fillAlpha = (style.fillOpacity ?? (style.fill ? 1 : DEFAULT_WASH)) * itemAlpha(context, style.fill ?? style.stroke);
19
18
  const baseStroke = itemColor(context, style.stroke, "neutral");
@@ -69,6 +68,7 @@ function sync(container, item, context) {
69
68
  parts.label.style.fill = labelColor;
70
69
  parts.label.style.fontFamily = context.style.font;
71
70
  }
71
+ syncTextResolution(parts.label, context.textResolution);
72
72
  parts.label.position.set(4, -18);
73
73
  }
74
74
  const frameCardRenderer = {
@@ -82,15 +82,14 @@ const frameCardRenderer = {
82
82
  fontFamily: BOARD_FONT_STACK,
83
83
  fontSize: 12,
84
84
  fontWeight: "600"
85
- });
85
+ }, context.textResolution);
86
86
  root.addChild(box, label);
87
87
  partsByContainer.set(root, {
88
88
  root,
89
89
  box,
90
90
  label,
91
91
  visualSig: "",
92
- textSig: "",
93
- resolution: label.resolution
92
+ textSig: ""
94
93
  });
95
94
  if (item.type === "frame") sync(root, item, context);
96
95
  return root;
@@ -98,6 +97,10 @@ const frameCardRenderer = {
98
97
  update: (container, item, context) => {
99
98
  if (item.type === "frame") sync(container, item, context);
100
99
  },
100
+ resolution: (container, _item, context) => {
101
+ const parts = partsByContainer.get(container);
102
+ if (parts) syncTextResolution(parts.label, context.textResolution);
103
+ },
101
104
  renderFar: (graphics, item, context) => {
102
105
  if (item.type !== "frame" || item.props.decoration === "none") return;
103
106
  drawFarPlate(graphics, item.frame, {
@@ -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
3
  import { itemAlpha, itemColor } from "../palette.js";
4
- import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
4
+ import { syncTextResolution, syncTextWrapWidth } from "../text-resolution.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";
@@ -100,7 +100,6 @@ function sync(container, item, context) {
100
100
  const fillOpacity = (style.fillOpacity ?? (style.fill ? 1 : 0)) * itemAlpha(context, style.fill ?? style.stroke);
101
101
  const strokeAlpha = itemAlpha(context, style.stroke ?? style.fill);
102
102
  const strokeWidth = style.strokeWidth ?? 1.75;
103
- syncTextResolution(parts.label, parts, context.zoom);
104
103
  const visualSig = [
105
104
  width,
106
105
  height,
@@ -159,6 +158,7 @@ function sync(container, item, context) {
159
158
  parts.label.style.align = props.align;
160
159
  }
161
160
  syncTextWrapWidth(parts.label, parts, Math.max(1, width - 16), context.resizingIds.has(item.id));
161
+ syncTextResolution(parts.label, context.textResolution);
162
162
  parts.label.position.set(width / 2, height / 2);
163
163
  }
164
164
  const shapeCardRenderer = {
@@ -167,7 +167,6 @@ const shapeCardRenderer = {
167
167
  create: (item, context) => {
168
168
  const root = new Container();
169
169
  const shape = new Graphics();
170
- const resolution = textResolutionForZoom(context.zoom);
171
170
  const label = new Text({
172
171
  text: "",
173
172
  style: {
@@ -179,7 +178,7 @@ const shapeCardRenderer = {
179
178
  wordWrap: true,
180
179
  lineHeight: 28
181
180
  },
182
- resolution,
181
+ resolution: context.textResolution,
183
182
  roundPixels: true
184
183
  });
185
184
  label.anchor.set(.5);
@@ -190,8 +189,7 @@ const shapeCardRenderer = {
190
189
  label,
191
190
  visualSig: "",
192
191
  textSig: "",
193
- wrapWidth: 0,
194
- resolution
192
+ wrapWidth: 0
195
193
  });
196
194
  if (item.type === "shape") sync(root, item, context);
197
195
  return root;
@@ -199,6 +197,10 @@ const shapeCardRenderer = {
199
197
  update: (container, item, context) => {
200
198
  if (item.type === "shape") sync(container, item, context);
201
199
  },
200
+ resolution: (container, _item, context) => {
201
+ const parts = partsByContainer.get(container);
202
+ if (parts) syncTextResolution(parts.label, context.textResolution);
203
+ },
202
204
  renderFar: (graphics, item, context) => {
203
205
  if (item.type !== "shape") return;
204
206
  drawFarPlate(graphics, item.frame, {
@@ -1,7 +1,7 @@
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
3
  import { fitTextToLines } from "../text-fit.js";
4
- import { boardCardDetailed, syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
4
+ import { boardCardDetailed, syncTextResolution } from "../text-resolution.js";
5
5
  import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
6
6
  import { drawAudioWaveform } from "../audio-waveform.js";
7
7
  import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.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));
@@ -283,10 +283,6 @@ function sync(container, item, context) {
283
283
  const detailText = !full || !surface ? "" : status === "failed" ? snapshot.errorExcerpt ?? "" : working ? snapshot.promptExcerpt ?? "" : "";
284
284
  const textArtifact = kind === "text" && artifact?.type === "text";
285
285
  const bodyText = textArtifact ? artifact.textExcerpt : counting ? `${label} · ${elapsed}` : label;
286
- syncTextResolution(parts.body, parts, context.zoom);
287
- syncTextResolution(parts.detail, parts, context.zoom);
288
- syncTextResolution(parts.meta, parts, context.zoom);
289
- syncTextResolution(parts.badge, parts, context.zoom);
290
286
  const textSig = [
291
287
  counting ? label : bodyText,
292
288
  detailText,
@@ -317,6 +313,12 @@ function sync(container, item, context) {
317
313
  parts.badge.style.fill = context.palette.text;
318
314
  if (parts.badge.text !== count) parts.badge.text = count;
319
315
  }
316
+ for (const text of [
317
+ parts.body,
318
+ parts.detail,
319
+ parts.meta,
320
+ parts.badge
321
+ ]) syncTextResolution(text, context.textResolution);
320
322
  const badgeWidth = Math.max(22, parts.badge.width + 12);
321
323
  const visualSig = [
322
324
  width,
@@ -332,7 +334,7 @@ function sync(container, item, context) {
332
334
  artifact?.type ?? "none",
333
335
  artifact?.id ?? "none",
334
336
  item.props.taskRunId,
335
- texture ? `${texture.width}x${texture.height}` : "none",
337
+ texture?.uid ?? "none",
336
338
  context.themeKey
337
339
  ].join("|");
338
340
  if (visualSig !== parts.visualSig) {
@@ -434,7 +436,6 @@ const taskCardRenderer = {
434
436
  const previewMask = new Graphics();
435
437
  const shimmer = new Graphics();
436
438
  const previewArt = new Graphics();
437
- const resolution = textResolutionForZoom(context.zoom);
438
439
  const text = (fontFamily, fontSize, fontWeight) => new Text({
439
440
  text: "",
440
441
  style: {
@@ -444,7 +445,7 @@ const taskCardRenderer = {
444
445
  wordWrap: true,
445
446
  breakWords: true
446
447
  },
447
- resolution,
448
+ resolution: context.textResolution,
448
449
  roundPixels: true
449
450
  });
450
451
  const body = text(BOARD_FONT_STACK, 11.5, "500");
@@ -478,7 +479,6 @@ const taskCardRenderer = {
478
479
  assetKey: null,
479
480
  visualSig: "",
480
481
  textSig: "",
481
- resolution,
482
482
  motion: {
483
483
  itemId: "",
484
484
  width: 0,
@@ -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);
@@ -2,7 +2,7 @@ import { layoutBoardText } from "../../../protocol/dist/board-layout.js";
2
2
  import { boardFontFamily } from "../../model/text-metrics.js";
3
3
  import { itemAlpha, itemColor } from "../palette.js";
4
4
  import { BOARD_FONT_STACKS } from "../text-measurement.js";
5
- import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
5
+ import { syncTextResolution } from "../text-resolution.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";
@@ -32,7 +32,6 @@ function sync(container, item, context) {
32
32
  const anchorX = props.align === "center" ? .5 : props.align === "right" ? 1 : 0;
33
33
  parts.body.anchor.set(anchorX, 0);
34
34
  parts.body.position.set(item.frame.width * anchorX, 0);
35
- syncTextResolution(parts.body, parts, context.zoom * Math.max(1, previewScale));
36
35
  parts.body.alpha = itemAlpha(context, item.style.fill);
37
36
  parts.caret.alpha = parts.body.alpha;
38
37
  const ink = itemColor(context, item.style.fill, "neutral");
@@ -62,6 +61,7 @@ function sync(container, item, context) {
62
61
  breakWords: true
63
62
  });
64
63
  }
64
+ syncTextResolution(parts.body, context.textResolution * previewScale);
65
65
  syncCaret(parts, item, context);
66
66
  }
67
67
  const textCardRenderer = {
@@ -69,11 +69,10 @@ const textCardRenderer = {
69
69
  canRender: (item) => item.type === "text",
70
70
  create: (item, context) => {
71
71
  const root = new Container();
72
- const resolution = textResolutionForZoom(context.zoom);
73
72
  const body = new Text({
74
73
  text: "",
75
74
  style: { fontFamily: BOARD_FONT_STACKS.sans },
76
- resolution,
75
+ resolution: context.textResolution,
77
76
  roundPixels: true
78
77
  });
79
78
  const caret = new Graphics();
@@ -82,7 +81,6 @@ const textCardRenderer = {
82
81
  root,
83
82
  body,
84
83
  caret,
85
- resolution,
86
84
  contentSig: ""
87
85
  });
88
86
  if (item.type === "text") sync(root, item, context);
@@ -91,6 +89,10 @@ const textCardRenderer = {
91
89
  update: (container, item, context) => {
92
90
  if (item.type === "text") sync(container, item, context);
93
91
  },
92
+ resolution: (container, _item, context) => {
93
+ const parts = partsByContainer.get(container);
94
+ if (parts) syncTextResolution(parts.body, context.textResolution * parts.body.scale.x);
95
+ },
94
96
  renderFar: (graphics, item, context) => {
95
97
  if (item.type !== "text") return;
96
98
  drawFarPlate(graphics, item.frame, {
@@ -1,5 +1,5 @@
1
1
  import { BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
- import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
2
+ import { syncTextResolution } from "../text-resolution.js";
3
3
  import { cardElevation, drawCardSurface, traceCardOutline } from "../surface.js";
4
4
  import { positionShell } from "./base-card-renderer.js";
5
5
  import { drawFarPlate } from "./far-plate.js";
@@ -12,7 +12,6 @@ function sync(container, item, context) {
12
12
  positionShell(parts.root, item);
13
13
  const { width, height } = item.frame;
14
14
  const realType = item.type;
15
- syncTextResolution(parts.label, parts, context.zoom);
16
15
  const elevation = cardElevation(context, item.id);
17
16
  const visualSig = [
18
17
  width,
@@ -47,6 +46,7 @@ function sync(container, item, context) {
47
46
  parts.label.text = realType;
48
47
  parts.label.style.fill = context.palette.muted;
49
48
  }
49
+ syncTextResolution(parts.label, context.textResolution);
50
50
  parts.label.position.set(width / 2, height / 2);
51
51
  }
52
52
  const unknownCardRenderer = {
@@ -56,7 +56,6 @@ const unknownCardRenderer = {
56
56
  const root = new Container();
57
57
  const box = new Graphics();
58
58
  box.context.batchMode = "batch";
59
- const resolution = textResolutionForZoom(context.zoom);
60
59
  const label = new Text({
61
60
  text: "",
62
61
  style: {
@@ -64,7 +63,7 @@ const unknownCardRenderer = {
64
63
  fontSize: 11,
65
64
  fontWeight: "600"
66
65
  },
67
- resolution,
66
+ resolution: context.textResolution,
68
67
  roundPixels: true
69
68
  });
70
69
  label.anchor.set(.5);
@@ -74,8 +73,7 @@ const unknownCardRenderer = {
74
73
  box,
75
74
  label,
76
75
  visualSig: "",
77
- textSig: "",
78
- resolution
76
+ textSig: ""
79
77
  });
80
78
  sync(root, item, context);
81
79
  return root;
@@ -83,6 +81,10 @@ const unknownCardRenderer = {
83
81
  update: (container, item, context) => {
84
82
  sync(container, item, context);
85
83
  },
84
+ resolution: (container, _item, context) => {
85
+ const parts = partsByContainer.get(container);
86
+ if (parts) syncTextResolution(parts.label, context.textResolution);
87
+ },
86
88
  renderFar: (graphics, item, context) => {
87
89
  drawFarPlate(graphics, item.frame, {
88
90
  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 };