@neta-art/cohub 10.0.0 → 11.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 (129) hide show
  1. package/README.md +11 -2
  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 +5 -1
  7. package/dist/board/editor/creation.js +2 -1
  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 +18 -3
  12. package/dist/board/editor/editor.js +11 -1
  13. package/dist/board/editor/query.d.ts +5 -5
  14. package/dist/board/editor/query.js +14 -12
  15. package/dist/board/editor/selection.js +2 -1
  16. package/dist/board/export/index.d.ts +25 -4
  17. package/dist/board/export/index.js +45 -12
  18. package/dist/board/export/node/index.d.ts +5 -4
  19. package/dist/board/export/node/index.js +16 -11
  20. package/dist/board/export/node/scripts.d.ts +16 -0
  21. package/dist/board/export/node/scripts.js +258 -0
  22. package/dist/board/export/scene.d.ts +2 -1
  23. package/dist/board/export/scene.js +20 -6
  24. package/dist/board/model/animation.d.ts +4 -1
  25. package/dist/board/model/animation.js +34 -7
  26. package/dist/board/model/authoring.d.ts +20 -0
  27. package/dist/board/model/authoring.js +185 -0
  28. package/dist/board/model/export-plan.js +1 -0
  29. package/dist/board/model/file-preview.js +1 -1
  30. package/dist/board/model/file-snapshot.js +3 -2
  31. package/dist/board/model/image-key.js +6 -2
  32. package/dist/board/model/index.d.ts +13 -6
  33. package/dist/board/model/index.js +14 -8
  34. package/dist/board/model/items/builtin.js +2 -2
  35. package/dist/board/model/items/create.d.ts +4 -2
  36. package/dist/board/model/items/create.js +6 -5
  37. package/dist/board/model/items/index.d.ts +2 -2
  38. package/dist/board/model/items/index.js +2 -2
  39. package/dist/board/model/media-playback.d.ts +2 -1
  40. package/dist/board/model/media-playback.js +14 -4
  41. package/dist/board/model/media.js +7 -1
  42. package/dist/board/model/sampling.d.ts +19 -0
  43. package/dist/board/model/sampling.js +96 -0
  44. package/dist/board/model/script-example.d.ts +4 -0
  45. package/dist/board/model/script-example.js +21 -0
  46. package/dist/board/model/script-hit.d.ts +10 -0
  47. package/dist/board/model/script-hit.js +52 -0
  48. package/dist/board/model/script-index.d.ts +7 -0
  49. package/dist/board/model/script-index.js +15 -0
  50. package/dist/board/model/script-scene.d.ts +24 -0
  51. package/dist/board/model/script-scene.js +267 -0
  52. package/dist/board/model/script.d.ts +105 -0
  53. package/dist/board/model/script.js +1 -0
  54. package/dist/board/render/css-color.d.ts +6 -1
  55. package/dist/board/render/css-color.js +34 -20
  56. package/dist/board/render/diagnostics.d.ts +20 -0
  57. package/dist/board/render/diagnostics.js +117 -0
  58. package/dist/board/render/index.d.ts +9 -6
  59. package/dist/board/render/index.js +9 -6
  60. package/dist/board/render/palette.d.ts +2 -1
  61. package/dist/board/render/palette.js +6 -2
  62. package/dist/board/render/renderers/arrow-card-renderer.js +3 -2
  63. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  64. package/dist/board/render/renderers/board-renderer-registry.d.ts +10 -5
  65. package/dist/board/render/renderers/board-renderer-registry.js +3 -3
  66. package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
  67. package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +16 -15
  68. package/dist/board/render/renderers/draw-card-renderer.js +3 -2
  69. package/dist/board/render/renderers/effect-card-renderer.js +22 -5
  70. package/dist/board/render/renderers/frame-card-renderer.js +30 -14
  71. package/dist/board/render/renderers/image-card-renderer.js +43 -4
  72. package/dist/board/render/renderers/shape-card-renderer.js +10 -27
  73. package/dist/board/render/renderers/text-card-renderer.js +5 -3
  74. package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
  75. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  76. package/dist/board/render/sampling.d.ts +22 -0
  77. package/dist/board/render/sampling.js +21 -0
  78. package/dist/board/render/script-export.d.ts +11 -0
  79. package/dist/board/render/script-export.js +86 -0
  80. package/dist/board/render/script-host.d.ts +31 -0
  81. package/dist/board/render/script-host.js +421 -0
  82. package/dist/board/render/script-modules.d.ts +2 -0
  83. package/dist/board/render/script-modules.js +2 -0
  84. package/dist/board/render/script-runtime.d.ts +29 -0
  85. package/dist/board/render/script-runtime.js +213 -0
  86. package/dist/board/render/script-sandbox.d.ts +4 -0
  87. package/dist/board/render/script-sandbox.js +173 -0
  88. package/dist/board/render/stroke.d.ts +2 -1
  89. package/dist/board/render/stroke.js +22 -1
  90. package/dist/board/render/text-measurement.js +1 -1
  91. package/dist/board/stage/assets.js +6 -1
  92. package/dist/board/stage/maintenance.d.ts +5 -0
  93. package/dist/board/stage/maintenance.js +11 -0
  94. package/dist/board/stage/scene.js +5 -2
  95. package/dist/board/stage/script-pointer.d.ts +19 -0
  96. package/dist/board/stage/script-pointer.js +35 -0
  97. package/dist/board/stage/stage.d.ts +10 -0
  98. package/dist/board/stage/stage.js +249 -43
  99. package/dist/board/stage/texture-pool.js +2 -3
  100. package/dist/chunks/environment.d.ts +266 -23
  101. package/dist/chunks/host-bridge.d.ts +2 -2
  102. package/dist/chunks/http.d.ts +88 -7
  103. package/dist/chunks/http.js +155 -63
  104. package/dist/chunks/transport.js +3 -1
  105. package/dist/chunks/websocket.d.ts +9 -1
  106. package/dist/chunks/websocket.js +16 -2
  107. package/dist/host-bridge.js +2 -1
  108. package/dist/http.d.ts +2 -2
  109. package/dist/index.d.ts +182 -4
  110. package/dist/index.js +1374 -126
  111. package/dist/protocol/dist/board-assets.d.ts +1 -0
  112. package/dist/protocol/dist/board-constants.js +2 -2
  113. package/dist/protocol/dist/board-model.d.ts +201 -11
  114. package/dist/protocol/dist/board-model.js +33 -14
  115. package/dist/protocol/dist/board-patch.js +2 -1
  116. package/dist/protocol/dist/board-schema.d.ts +4 -2
  117. package/dist/protocol/dist/board-schema.js +18 -4
  118. package/dist/protocol/dist/board-script-modules.d.ts +25 -0
  119. package/dist/protocol/dist/board-script-modules.js +127 -0
  120. package/dist/protocol/dist/display/index.d.ts +1 -0
  121. package/dist/protocol/dist/index.d.ts +4 -2
  122. package/dist/protocol/dist/realtime/types.d.ts +1 -0
  123. package/dist/protocol/dist/rtc/index.d.ts +1 -0
  124. package/package.json +2 -1
  125. package/dist/board/export/node/sketch.d.ts +0 -19
  126. package/dist/board/export/node/sketch.js +0 -146
  127. package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
  128. package/dist/board/render/sketch-host.d.ts +0 -14
  129. package/dist/board/render/sketch-host.js +0 -216
@@ -1,36 +1,50 @@
1
1
  import { DOMAdapter } from "pixi.js";
2
2
  //#region src/board/render/css-color.ts
3
- const CACHE_LIMIT = 256;
4
3
  const cache = /* @__PURE__ */ new Map();
5
4
  let canvas = null;
6
- function parseBoardCssColor(value) {
5
+ function parseBoardCssPaint(value) {
7
6
  const key = value.trim();
8
7
  if (!key) return null;
9
- const cached = cache.get(key);
10
- if (cached !== void 0 || cache.has(key)) return cached ?? null;
8
+ if (key.toLowerCase() === "transparent") return {
9
+ color: 0,
10
+ alpha: 0
11
+ };
12
+ const hex = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(key)?.[1];
13
+ if (hex) {
14
+ const digits = hex.length <= 4 ? [...hex].map((digit) => digit + digit).join("") : hex;
15
+ return {
16
+ color: Number.parseInt(digits.slice(0, 6), 16),
17
+ alpha: digits.length === 8 ? Number.parseInt(digits.slice(6), 16) / 255 : 1
18
+ };
19
+ }
20
+ if (cache.has(key)) return cache.get(key) ?? null;
11
21
  let result = null;
12
22
  try {
13
- const direct = /^#([0-9a-f]{6})$/i.exec(key);
14
- if (direct?.[1]) result = Number.parseInt(direct[1], 16);
15
- else {
16
- canvas ??= DOMAdapter.get().createCanvas(1, 1);
17
- const context = canvas.getContext("2d");
18
- if (context) {
19
- context.fillStyle = "#010203";
20
- context.fillStyle = key;
21
- const first = String(context.fillStyle);
22
- context.fillStyle = "#040506";
23
- context.fillStyle = key;
24
- const match = first === String(context.fillStyle) ? /^#([0-9a-f]{6})$/i.exec(first) : null;
25
- if (match?.[1]) result = Number.parseInt(match[1], 16);
26
- }
23
+ canvas ??= DOMAdapter.get().createCanvas(1, 1);
24
+ const context = canvas.getContext("2d");
25
+ context.fillStyle = "#010203";
26
+ context.fillStyle = key;
27
+ const first = context.fillStyle;
28
+ context.fillStyle = "#040506";
29
+ context.fillStyle = key;
30
+ if (first === context.fillStyle) {
31
+ context.clearRect(0, 0, 1, 1);
32
+ context.fillRect(0, 0, 1, 1);
33
+ const [r = 0, g = 0, b = 0, a = 0] = context.getImageData(0, 0, 1, 1).data;
34
+ result = {
35
+ color: r << 16 | g << 8 | b,
36
+ alpha: a / 255
37
+ };
27
38
  }
28
39
  } catch {
29
40
  result = null;
30
41
  }
31
- if (cache.size >= CACHE_LIMIT) cache.clear();
42
+ if (cache.size >= 256) cache.clear();
32
43
  cache.set(key, result);
33
44
  return result;
34
45
  }
46
+ function parseBoardCssColor(value) {
47
+ return parseBoardCssPaint(value)?.color ?? null;
48
+ }
35
49
  //#endregion
36
- export { parseBoardCssColor };
50
+ export { parseBoardCssColor, parseBoardCssPaint };
@@ -0,0 +1,20 @@
1
+ import { BoardTextItem } from "../../protocol/dist/board-model.js";
2
+ import "../../protocol/dist/index.js";
3
+ import { BoardScene } from "../model/scene.js";
4
+ //#region src/board/render/diagnostics.d.ts
5
+ type BoardRenderDiagnostic = {
6
+ code: "text-clipped" | "text-overlap" | "glyph-unavailable" | "effect-follow-unused";
7
+ item: string;
8
+ related?: string;
9
+ characters?: string;
10
+ };
11
+ declare function measureBoardText(props: BoardTextItem["props"]): {
12
+ width: number;
13
+ height: number;
14
+ lines: string[];
15
+ lineHeight: number;
16
+ missingCharacters: string;
17
+ };
18
+ declare function inspectBoardScene(scene: BoardScene, limit?: number): BoardRenderDiagnostic[];
19
+ //#endregion
20
+ export { BoardRenderDiagnostic, inspectBoardScene, measureBoardText };
@@ -0,0 +1,117 @@
1
+ import { layoutBoardText } from "../../protocol/dist/board-layout.js";
2
+ import { itemBounds, rectsIntersect } from "../model/geometry.js";
3
+ import { boardFontFamily } from "../model/text-metrics.js";
4
+ import { BOARD_FONT_STACKS, ensureBoardTextMeasurement } from "./text-measurement.js";
5
+ import { DOMAdapter } from "pixi.js";
6
+ //#region src/board/render/diagnostics.ts
7
+ const glyphCache = /* @__PURE__ */ new Map();
8
+ let glyphContext;
9
+ function missingGlyph(character, font) {
10
+ if (/\s/u.test(character)) return false;
11
+ const key = `${font}:${character}`;
12
+ const cached = glyphCache.get(key);
13
+ if (cached !== void 0) return cached;
14
+ try {
15
+ glyphContext ??= DOMAdapter.get().createCanvas(64, 64).getContext("2d");
16
+ const ctx = glyphContext;
17
+ ctx.font = `32px ${font}`;
18
+ const absent = "􏿿";
19
+ if (ctx.measureText(character).width !== ctx.measureText(absent).width) {
20
+ if (glyphCache.size >= 2048) glyphCache.clear();
21
+ glyphCache.set(key, false);
22
+ return false;
23
+ }
24
+ const pixels = (text) => {
25
+ ctx.clearRect(0, 0, 64, 64);
26
+ ctx.fillStyle = "#000000";
27
+ ctx.fillText(text, 0, 40);
28
+ return ctx.getImageData(0, 0, 64, 64).data;
29
+ };
30
+ const a = pixels(character);
31
+ const b = pixels(absent);
32
+ const missing = a.some((n) => n !== 0) && a.every((n, i) => n === b[i]);
33
+ if (glyphCache.size >= 2048) glyphCache.clear();
34
+ glyphCache.set(key, missing);
35
+ return missing;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+ function measureBoardText(props) {
41
+ ensureBoardTextMeasurement();
42
+ const font = boardFontFamily(props.font, BOARD_FONT_STACKS);
43
+ const characters = [...new Set(props.text)].slice(0, 512).filter((char) => missingGlyph(char, font)).join("");
44
+ return {
45
+ ...layoutBoardText(props),
46
+ missingCharacters: characters
47
+ };
48
+ }
49
+ function inspectBoardScene(scene, limit = 100) {
50
+ ensureBoardTextMeasurement();
51
+ const diagnostics = [];
52
+ const texts = scene.items.filter((item) => item.type === "text" && scene.opacity(item.id) > .01);
53
+ const push = (entry) => {
54
+ if (diagnostics.length < limit) diagnostics.push(entry);
55
+ };
56
+ for (const item of scene.items) {
57
+ if (diagnostics.length >= limit) break;
58
+ if (item.type === "effect" && item.props.follow && item.props.kind !== "trail") push({
59
+ code: "effect-follow-unused",
60
+ item: item.id
61
+ });
62
+ }
63
+ let glyphBudget = 256;
64
+ for (const item of texts) {
65
+ if (diagnostics.length >= limit || item.type !== "text") break;
66
+ if (glyphBudget-- > 0) {
67
+ const measured = measureBoardText(item.props);
68
+ if (measured.missingCharacters) push({
69
+ code: "glyph-unavailable",
70
+ item: item.id,
71
+ characters: measured.missingCharacters
72
+ });
73
+ }
74
+ const bounds = itemBounds(item.frame);
75
+ let parentId = item.parent;
76
+ while (parentId) {
77
+ const parent = scene.get(parentId);
78
+ if (!parent) break;
79
+ if (parent.type === "frame" && parent.props.clip) {
80
+ const clip = itemBounds(parent.frame);
81
+ if (bounds.x < clip.x - 1 || bounds.y < clip.y - 1 || bounds.x + bounds.width > clip.x + clip.width + 1 || bounds.y + bounds.height > clip.y + clip.height + 1) {
82
+ push({
83
+ code: "text-clipped",
84
+ item: item.id,
85
+ related: parentId
86
+ });
87
+ break;
88
+ }
89
+ }
90
+ parentId = parent.parent;
91
+ }
92
+ }
93
+ const sorted = texts.map((item) => ({
94
+ item,
95
+ rect: itemBounds(item.frame)
96
+ })).sort((a, b) => a.rect.x - b.rect.x);
97
+ let comparisons = 0;
98
+ for (let i = 0; i < sorted.length && diagnostics.length < limit; i++) {
99
+ const a = sorted[i];
100
+ if (!a) continue;
101
+ for (let j = i + 1; j < sorted.length; j++) {
102
+ const b = sorted[j];
103
+ if (!b) continue;
104
+ if (b.rect.x >= a.rect.x + a.rect.width) break;
105
+ if (++comparisons > 1e4) return diagnostics;
106
+ if (a.item.parent === b.item.parent && rectsIntersect(a.rect, b.rect)) push({
107
+ code: "text-overlap",
108
+ item: a.item.id,
109
+ related: b.item.id
110
+ });
111
+ if (diagnostics.length >= limit) break;
112
+ }
113
+ }
114
+ return diagnostics;
115
+ }
116
+ //#endregion
117
+ export { inspectBoardScene, measureBoardText };
@@ -1,14 +1,17 @@
1
- import { BoardCardRenderer, BoardCardRendererResolver, BoardRenderContext, BoardRenderPalette, BoardSketchHost, createBoardCardRendererResolver, getBoardCardRenderer } from "./renderers/board-renderer-registry.js";
1
+ import { BoardCardRenderer, BoardCardRendererResolver, BoardRenderContext, BoardRenderPalette, BoardScriptHost, createBoardCardRendererResolver, getBoardCardRenderer } from "./renderers/board-renderer-registry.js";
2
+ import { createBoardRenderSampler } from "./sampling.js";
3
+ import { BoardRenderDiagnostic, inspectBoardScene, measureBoardText } from "./diagnostics.js";
2
4
  import { BoardBackgroundContext, createBoardBackground, updateBoardBackground } from "./board-background.js";
3
5
  import { ClipGroup, clippingAncestor, isClippingFrame, syncClipGroup, traceFrameOutline } from "./clip.js";
4
- import { parseBoardCssColor } from "./css-color.js";
6
+ import { BoardCssPaint, parseBoardCssColor, parseBoardCssPaint } from "./css-color.js";
5
7
  import { BoardMediaAction, boardMediaActionAt, mediaPlayBadgeHit, mediaPlayBadgeVisible } from "./media-interaction.js";
6
- import { defaultBoardPalette, itemColor } from "./palette.js";
7
- import { BoardSketchHostOptions, BrowserBoardSketchHost, createBoardSketchHost } from "./sketch-host.js";
8
- import { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline } from "./stroke.js";
8
+ import { defaultBoardPalette, itemAlpha, itemColor } from "./palette.js";
9
+ import { BoardScriptHostOptions, BrowserBoardScriptHost, createBoardScriptHost } from "./script-host.js";
10
+ import { prepareBrowserBoardScripts } from "./script-export.js";
11
+ import { dashPattern, endAngle, roundRectPoints, traceArrowhead, tracePolyline, trimPolyline } from "./stroke.js";
9
12
  import { BOARD_EFFECT_MAX_PARTICLES } from "./renderers/effect-card-renderer.js";
10
13
  import { TASK_CARD_FULL_DETAIL_ZOOM } from "./renderers/task-card-renderer.js";
11
14
  import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
12
15
  import { getBoardResolution, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket } from "./text-resolution.js";
13
16
  import { VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, loadVideoThumbnailTexture, videoTextureNaturalSize, videoThumbnailSize } from "./video-thumbnail.js";
14
- export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, BoardBackgroundContext, BoardCardRenderer, BoardCardRendererResolver, BoardMediaAction, BoardRenderContext, BoardRenderPalette, BoardSketchHost, BoardSketchHostOptions, BrowserBoardSketchHost, ClipGroup, TASK_CARD_FULL_DETAIL_ZOOM, VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, boardMediaActionAt, clippingAncestor, createBoardBackground, createBoardCardRendererResolver, createBoardSketchHost, dashPattern, defaultBoardPalette, endAngle, ensureBoardTextMeasurement, getBoardCardRenderer, getBoardResolution, installBoardTextMeasurement, isClippingFrame, itemColor, loadVideoThumbnailTexture, mediaPlayBadgeHit, mediaPlayBadgeVisible, parseBoardCssColor, syncClipGroup, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket, traceArrowhead, traceFrameOutline, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize };
17
+ export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, BoardBackgroundContext, BoardCardRenderer, BoardCardRendererResolver, BoardCssPaint, BoardMediaAction, BoardRenderContext, BoardRenderDiagnostic, BoardRenderPalette, BoardScriptHost, BoardScriptHostOptions, BrowserBoardScriptHost, ClipGroup, TASK_CARD_FULL_DETAIL_ZOOM, VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, boardMediaActionAt, clippingAncestor, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, dashPattern, defaultBoardPalette, endAngle, ensureBoardTextMeasurement, getBoardCardRenderer, getBoardResolution, inspectBoardScene, installBoardTextMeasurement, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, roundRectPoints, syncClipGroup, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket, traceArrowhead, traceFrameOutline, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize };
@@ -1,14 +1,17 @@
1
- import { parseBoardCssColor } from "./css-color.js";
1
+ import { parseBoardCssColor, parseBoardCssPaint } from "./css-color.js";
2
+ import { defaultBoardPalette, itemAlpha, itemColor } from "./palette.js";
3
+ import { createBoardRenderSampler } from "./sampling.js";
4
+ import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
5
+ import { inspectBoardScene, measureBoardText } from "./diagnostics.js";
2
6
  import { createBoardBackground, updateBoardBackground } from "./board-background.js";
3
7
  import { clippingAncestor, isClippingFrame, syncClipGroup, traceFrameOutline } from "./clip.js";
4
8
  import { getBoardResolution, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket } from "./text-resolution.js";
5
9
  import { TASK_CARD_FULL_DETAIL_ZOOM } from "./renderers/task-card-renderer.js";
6
10
  import { boardMediaActionAt, mediaPlayBadgeHit, mediaPlayBadgeVisible } from "./media-interaction.js";
7
- import { defaultBoardPalette, itemColor } from "./palette.js";
8
- import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
9
- import { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline } from "./stroke.js";
11
+ import { dashPattern, endAngle, roundRectPoints, traceArrowhead, tracePolyline, trimPolyline } from "./stroke.js";
10
12
  import { BOARD_EFFECT_MAX_PARTICLES } from "./renderers/effect-card-renderer.js";
11
13
  import { createBoardCardRendererResolver, getBoardCardRenderer } from "./renderers/board-renderer-registry.js";
12
- import { createBoardSketchHost } from "./sketch-host.js";
14
+ import { createBoardScriptHost } from "./script-host.js";
15
+ import { prepareBrowserBoardScripts } from "./script-export.js";
13
16
  import { VIDEO_THUMBNAIL_MAX_EDGE, loadVideoThumbnailTexture, videoTextureNaturalSize, videoThumbnailSize } from "./video-thumbnail.js";
14
- export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, TASK_CARD_FULL_DETAIL_ZOOM, VIDEO_THUMBNAIL_MAX_EDGE, boardMediaActionAt, clippingAncestor, createBoardBackground, createBoardCardRendererResolver, createBoardSketchHost, dashPattern, defaultBoardPalette, endAngle, ensureBoardTextMeasurement, getBoardCardRenderer, getBoardResolution, installBoardTextMeasurement, isClippingFrame, itemColor, loadVideoThumbnailTexture, mediaPlayBadgeHit, mediaPlayBadgeVisible, parseBoardCssColor, syncClipGroup, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket, traceArrowhead, traceFrameOutline, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize };
17
+ export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, TASK_CARD_FULL_DETAIL_ZOOM, VIDEO_THUMBNAIL_MAX_EDGE, boardMediaActionAt, clippingAncestor, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, dashPattern, defaultBoardPalette, endAngle, ensureBoardTextMeasurement, getBoardCardRenderer, getBoardResolution, inspectBoardScene, installBoardTextMeasurement, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, roundRectPoints, syncClipGroup, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket, traceArrowhead, traceFrameOutline, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize };
@@ -4,6 +4,7 @@ import { BoardColorId } from "../model/palette.js";
4
4
  import { BoardRenderContext, BoardRenderPalette } from "./renderers/board-renderer-registry.js";
5
5
  //#region src/board/render/palette.d.ts
6
6
  declare function defaultBoardPalette(mode: "dark" | "light"): BoardRenderPalette;
7
+ declare function itemAlpha(context: Pick<BoardRenderContext, "colorScheme">, color: BoardColor | undefined): number;
7
8
  declare function itemColor(context: Pick<BoardRenderContext, "colors" | "colorScheme" | "palette">, color: BoardColor | undefined, fallback: BoardColorId, part?: "stroke" | "fill" | "label"): number;
8
9
  //#endregion
9
- export { defaultBoardPalette, itemColor };
10
+ export { defaultBoardPalette, itemAlpha, itemColor };
@@ -1,5 +1,5 @@
1
1
  import { resolveItemColor } from "../model/palette.js";
2
- import { parseBoardCssColor } from "./css-color.js";
2
+ import { parseBoardCssColor, parseBoardCssPaint } from "./css-color.js";
3
3
  //#region src/board/render/palette.ts
4
4
  const DARK = {
5
5
  bg: 1315860,
@@ -28,9 +28,13 @@ const LIGHT = {
28
28
  function defaultBoardPalette(mode) {
29
29
  return { ...mode === "light" ? LIGHT : DARK };
30
30
  }
31
+ function itemAlpha(context, color) {
32
+ const value = typeof color === "object" && color ? color[context.colorScheme] : color;
33
+ return value ? parseBoardCssPaint(value)?.alpha ?? 1 : 1;
34
+ }
31
35
  function itemColor(context, color, fallback, part = "stroke") {
32
36
  if (((typeof color === "object" && color ? color[context.colorScheme] : color) ?? fallback) === "neutral" && part !== "fill") return context.palette.text;
33
37
  return resolveItemColor(color, fallback, context.colors, context.colorScheme, parseBoardCssColor, part);
34
38
  }
35
39
  //#endregion
36
- export { defaultBoardPalette, itemColor };
40
+ export { defaultBoardPalette, itemAlpha, itemColor };
@@ -1,8 +1,8 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { pathPointAt, resolveSceneArrow } from "../../model/arrow-geometry.js";
3
+ import { itemAlpha, itemColor } from "../palette.js";
3
4
  import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
4
5
  import { drawFarStroke } from "./far-plate.js";
5
- import { itemColor } from "../palette.js";
6
6
  import { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline } from "../stroke.js";
7
7
  import { Container, Graphics, Text } from "pixi.js";
8
8
  //#region src/board/render/renderers/arrow-card-renderer.ts
@@ -16,6 +16,7 @@ function sync(container, item, context) {
16
16
  const { props, style } = item;
17
17
  const selected = context.selectedIds.has(item.id);
18
18
  const hovered = context.hoveredId === item.id;
19
+ parts.line.alpha = itemAlpha(context, style.stroke);
19
20
  const color = itemColor(context, style.stroke, "brand");
20
21
  const size = style.strokeWidth ?? 2.5;
21
22
  syncTextResolution(parts.label, parts, context.zoom);
@@ -130,7 +131,7 @@ const arrowCardRenderer = {
130
131
  drawFarStroke(graphics, trimPolyline(resolved.path, arrow.style.trim), {
131
132
  color: itemColor(context, arrow.style.stroke, "brand"),
132
133
  width: arrow.style.strokeWidth ?? 2.5,
133
- alpha: .85
134
+ alpha: .85 * itemAlpha(context, arrow.style.stroke)
134
135
  });
135
136
  },
136
137
  destroy: (container) => {
@@ -1,4 +1,5 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
+ import { getMediaResourceTitle } from "../../model/media.js";
2
3
  import { drawAudioWaveform } from "../audio-waveform.js";
3
4
  import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
4
5
  import { positionShell } from "./base-card-renderer.js";
@@ -15,7 +16,7 @@ function sync(container, item, context) {
15
16
  const { width, height } = item.frame;
16
17
  const selected = context.selectedIds.has(item.id);
17
18
  const hovered = context.hoveredId === item.id;
18
- const title = item.props.snapshot?.title ?? item.props.src.split("/").pop() ?? "Audio";
19
+ const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
19
20
  syncTextResolution(parts.label, parts, context.zoom);
20
21
  if (parts.label.text !== title) parts.label.text = title;
21
22
  syncTextWrapWidth(parts.label, parts, Math.max(1, width - 24), false);
@@ -1,4 +1,5 @@
1
- import { BoardSettings, BoardSketchItem } from "../../../protocol/dist/board-model.js";
1
+ import { BoardCustomItem, BoardDocument, BoardSettings } from "../../../protocol/dist/board-model.js";
2
+ import { BoardFrame } from "../../../protocol/dist/board-layout.js";
2
3
  import "../../../protocol/dist/index.js";
3
4
  import { BoardScene, BoardSceneItem, SceneItem } from "../../model/scene.js";
4
5
  import { BoardShapeColors } from "../../model/palette.js";
@@ -16,8 +17,11 @@ type BoardRenderPalette = {
16
17
  epic: number;
17
18
  legendary: number;
18
19
  };
19
- type BoardSketchHost = {
20
- frame: (item: SceneItem<BoardSketchItem>, time: number, resolution: number) => Texture | null;
20
+ type BoardScriptHost = {
21
+ document?: (document: BoardDocument, time: number) => BoardDocument;
22
+ ready?: (id: string) => boolean;
23
+ revision?: (id: string) => number;
24
+ frame: (item: SceneItem<BoardCustomItem>, time: number, resolution: number) => Texture | null;
21
25
  error: (id: string) => string | null;
22
26
  release: (id: string) => void;
23
27
  };
@@ -33,7 +37,8 @@ type BoardRenderContext = {
33
37
  rendererType: "gpu" | "canvas";
34
38
  zoom: number;
35
39
  time: number;
36
- sketches?: BoardSketchHost;
40
+ frameAt?: (id: string, time: number) => BoardFrame | undefined;
41
+ scripts?: BoardScriptHost;
37
42
  assetKey: (item: BoardSceneItem) => string | null;
38
43
  getTexture: (key: string) => Texture | null;
39
44
  hasError: (key: string) => boolean;
@@ -59,4 +64,4 @@ declare function createBoardCardRendererResolver(renderers?: Iterable<BoardCardR
59
64
  /** The built-in renderers alone. */
60
65
  declare const getBoardCardRenderer: BoardCardRendererResolver;
61
66
  //#endregion
62
- export { BoardCardRenderer, BoardCardRendererResolver, BoardRenderContext, BoardRenderPalette, BoardSketchHost, createBoardCardRendererResolver, getBoardCardRenderer };
67
+ export { BoardCardRenderer, BoardCardRendererResolver, BoardRenderContext, BoardRenderPalette, BoardScriptHost, createBoardCardRendererResolver, getBoardCardRenderer };
@@ -1,13 +1,13 @@
1
+ import { ensureBoardTextMeasurement } from "../text-measurement.js";
1
2
  import { fileCardRenderer } from "./file-card-renderer.js";
2
3
  import { taskCardRenderer } from "./task-card-renderer.js";
3
- import { ensureBoardTextMeasurement } from "../text-measurement.js";
4
4
  import { arrowCardRenderer } from "./arrow-card-renderer.js";
5
5
  import { audioCardRenderer } from "./audio-card-renderer.js";
6
6
  import { drawCardRenderer } from "./draw-card-renderer.js";
7
7
  import { effectCardRenderer } from "./effect-card-renderer.js";
8
8
  import { frameCardRenderer } from "./frame-card-renderer.js";
9
9
  import { shapeCardRenderer } from "./shape-card-renderer.js";
10
- import { sketchCardRenderer } from "./sketch-card-renderer.js";
10
+ import { customCardRenderer } from "./custom-card-renderer.js";
11
11
  import { imageCardRenderer } from "./image-card-renderer.js";
12
12
  import { textCardRenderer } from "./text-card-renderer.js";
13
13
  import { unknownCardRenderer } from "./unknown-card-renderer.js";
@@ -25,7 +25,7 @@ const builtinCardRenderers = [
25
25
  arrowCardRenderer,
26
26
  frameCardRenderer,
27
27
  effectCardRenderer,
28
- sketchCardRenderer
28
+ customCardRenderer
29
29
  ];
30
30
  /**
31
31
  * Pick a renderer per item: `renderers` first, then the built-ins. Items no
@@ -0,0 +1,5 @@
1
+ import { BoardCardRenderer } from "./board-renderer-registry.js";
2
+ //#region src/board/render/renderers/custom-card-renderer.d.ts
3
+ declare const customCardRenderer: BoardCardRenderer;
4
+ //#endregion
5
+ export { customCardRenderer };
@@ -3,26 +3,27 @@ import { getBoardResolution } from "../text-resolution.js";
3
3
  import { positionShell } from "./base-card-renderer.js";
4
4
  import { drawFarPlate } from "./far-plate.js";
5
5
  import { Container, Graphics, Sprite, Text, Texture } from "pixi.js";
6
- //#region src/board/render/renderers/sketch-card-renderer.ts
6
+ //#region src/board/render/renderers/custom-card-renderer.ts
7
7
  const partsByContainer = /* @__PURE__ */ new WeakMap();
8
8
  function sync(container, item, context) {
9
9
  const parts = partsByContainer.get(container);
10
10
  if (!parts) return;
11
11
  if (parts.id !== item.id) {
12
- context.sketches?.release(parts.id);
12
+ context.scripts?.release(parts.id);
13
13
  parts.id = item.id;
14
14
  }
15
15
  positionShell(parts.root, item);
16
16
  const { width, height } = item.frame;
17
17
  const resolution = Math.min(4, Math.max(.25, context.zoom * getBoardResolution()));
18
- const texture = context.sketches?.frame(item, context.time, resolution) ?? null;
19
- const error = context.sketches?.error(item.id) ?? null;
18
+ const texture = context.scripts?.frame(item, context.time, resolution) ?? null;
19
+ const error = context.scripts?.error(item.id) ?? null;
20
20
  parts.sprite.visible = Boolean(texture);
21
21
  if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
22
22
  parts.sprite.width = width;
23
23
  parts.sprite.height = height;
24
24
  const selected = context.selectedIds.has(item.id);
25
- const message = texture ? "" : error ? `${item.props.src}\n${error}` : item.props.src;
25
+ const ready = Boolean(texture) || context.scripts?.ready?.(item.id) === true;
26
+ const message = error ? `${item.script.src}\n${error}` : ready ? "" : item.script.src;
26
27
  const sig = [
27
28
  width,
28
29
  height,
@@ -35,21 +36,21 @@ function sync(container, item, context) {
35
36
  if (sig === parts.sig) return;
36
37
  parts.sig = sig;
37
38
  parts.placeholder.clear();
38
- if (!texture || selected) parts.placeholder.rect(0, 0, width, height).stroke({
39
+ if (!ready || selected || error) parts.placeholder.rect(0, 0, width, height).stroke({
39
40
  color: selected ? context.palette.brand : context.palette.border,
40
41
  width: selected ? 2 : 1,
41
42
  alpha: .8
42
43
  });
43
- parts.label.visible = !texture;
44
+ parts.label.visible = !ready || Boolean(error);
44
45
  parts.label.text = message;
45
46
  parts.label.style.fill = error ? context.colors.rose.stroke : context.palette.muted;
46
47
  parts.label.style.wordWrapWidth = Math.max(40, width - 16);
47
48
  parts.label.position.set(width / 2, height / 2);
48
49
  }
49
- const sketchCardRenderer = {
50
- id: "sketch-card",
51
- canRender: (item) => item.type === "sketch",
52
- animated: true,
50
+ const customCardRenderer = {
51
+ id: "custom-card",
52
+ canRender: (item) => item.type === "custom",
53
+ animated: false,
53
54
  create: (item, context) => {
54
55
  const root = new Container();
55
56
  const sprite = new Sprite(Texture.EMPTY);
@@ -75,11 +76,11 @@ const sketchCardRenderer = {
75
76
  label,
76
77
  sig: ""
77
78
  });
78
- if (item.type === "sketch") sync(root, item, context);
79
+ if (item.type === "custom") sync(root, item, context);
79
80
  return root;
80
81
  },
81
82
  update: (container, item, context) => {
82
- if (item.type === "sketch") sync(container, item, context);
83
+ if (item.type === "custom") sync(container, item, context);
83
84
  },
84
85
  renderFar: (graphics, item, context) => {
85
86
  drawFarPlate(graphics, item.frame, {
@@ -89,10 +90,10 @@ const sketchCardRenderer = {
89
90
  },
90
91
  destroy: (container, context) => {
91
92
  const parts = partsByContainer.get(container);
92
- if (parts) context.sketches?.release(parts.id);
93
+ if (parts) context.scripts?.release(parts.id);
93
94
  parts?.root.destroy({ children: true });
94
95
  partsByContainer.delete(container);
95
96
  }
96
97
  };
97
98
  //#endregion
98
- export { sketchCardRenderer };
99
+ export { customCardRenderer };
@@ -1,8 +1,8 @@
1
1
  import { applyMatrix } from "../../../protocol/dist/board-layout.js";
2
2
  import { computeDrawBounds, createStrokeRibbonBuilder, trimDrawPoints } from "../../model/draw-geometry.js";
3
+ import { itemAlpha, itemColor } from "../palette.js";
3
4
  import { positionShell } from "./base-card-renderer.js";
4
5
  import { drawFarStroke, farStrokeSamples } from "./far-plate.js";
5
- import { itemColor } from "../palette.js";
6
6
  import { Container, Graphics, Mesh, MeshGeometry, Texture } from "pixi.js";
7
7
  //#region src/board/render/renderers/draw-card-renderer.ts
8
8
  const partsByContainer = /* @__PURE__ */ new WeakMap();
@@ -77,6 +77,7 @@ function sync(container, item, context) {
77
77
  }
78
78
  const stroke = parts.stroke;
79
79
  if (!stroke) return;
80
+ stroke.alpha = itemAlpha(context, item.style.stroke);
80
81
  if (stroke instanceof Mesh) stroke.tint = color;
81
82
  const { box } = parts;
82
83
  const sx = item.frame.width / box.width;
@@ -120,7 +121,7 @@ const drawCardRenderer = {
120
121
  drawFarStroke(graphics, samples.map((point) => applyMatrix(matrix, point)), {
121
122
  color: itemColor(context, draw.style.stroke, "brand"),
122
123
  width: draw.style.strokeWidth ?? 4,
123
- alpha: 1
124
+ alpha: itemAlpha(context, draw.style.stroke)
124
125
  });
125
126
  },
126
127
  destroy: (container) => {
@@ -1,6 +1,6 @@
1
+ import { itemAlpha, itemColor } from "../palette.js";
1
2
  import { positionShell } from "./base-card-renderer.js";
2
3
  import { drawFarPlate } from "./far-plate.js";
3
- import { itemColor } from "../palette.js";
4
4
  import { Container, Graphics } from "pixi.js";
5
5
  //#region src/board/render/renderers/effect-card-renderer.ts
6
6
  const BOARD_EFFECT_MAX_PARTICLES = 4e3;
@@ -109,11 +109,27 @@ function drawTrail(graphics, parts, item, context, color) {
109
109
  parts.trail.length = 0;
110
110
  return;
111
111
  }
112
- const head = {
113
- x: target.frame.x + target.frame.width / 2 - item.frame.x,
114
- y: target.frame.y + target.frame.height / 2 - item.frame.y,
115
- t: time
112
+ const angle = item.frame.rotation * Math.PI / 180;
113
+ const localPoint = (frame, t) => {
114
+ const dx = frame.x + frame.width / 2 - item.frame.x - item.frame.width / 2;
115
+ const dy = frame.y + frame.height / 2 - item.frame.y - item.frame.height / 2;
116
+ return {
117
+ x: dx * Math.cos(angle) + dy * Math.sin(angle) + item.frame.width / 2,
118
+ y: -dx * Math.sin(angle) + dy * Math.cos(angle) + item.frame.height / 2,
119
+ t
120
+ };
116
121
  };
122
+ const head = localPoint(target.frame, time);
123
+ if (context.frameAt) {
124
+ parts.trail.length = 0;
125
+ const start = Math.max(0, time - item.props.life);
126
+ const step = Math.max(1e3 / 30, item.props.life / 63);
127
+ for (let t = Math.ceil(start / step) * step; t < time; t += step) {
128
+ const past = context.frameAt(target.id, t);
129
+ if (past) parts.trail.push(localPoint(past, t));
130
+ }
131
+ parts.trail.push(head);
132
+ }
117
133
  const last = parts.trail.at(-1);
118
134
  if (last && (time < last.t || time - last.t > item.props.life)) parts.trail.length = 0;
119
135
  if (!last || Math.hypot(head.x - last.x, head.y - last.y) > .5) parts.trail.push(head);
@@ -140,6 +156,7 @@ function sync(container, item, context) {
140
156
  const seed = hashString(props.seed ?? item.id);
141
157
  const time = Math.max(0, context.time);
142
158
  const graphics = parts.graphics;
159
+ graphics.alpha = itemAlpha(context, item.style.fill ?? item.style.stroke);
143
160
  graphics.clear();
144
161
  switch (props.kind) {
145
162
  case "particles":