@neta-art/cohub 10.0.0 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +42 -7
  2. package/dist/board/editor/animation.d.ts +3 -0
  3. package/dist/board/editor/animation.js +16 -1
  4. package/dist/board/editor/clipboard.js +7 -0
  5. package/dist/board/editor/copy.js +23 -18
  6. package/dist/board/editor/creation.d.ts +7 -3
  7. package/dist/board/editor/creation.js +5 -4
  8. package/dist/board/editor/deletion.js +3 -3
  9. package/dist/board/editor/document.d.ts +8 -2
  10. package/dist/board/editor/document.js +54 -7
  11. package/dist/board/editor/editor.d.ts +22 -7
  12. package/dist/board/editor/editor.js +12 -2
  13. package/dist/board/editor/index.d.ts +2 -2
  14. package/dist/board/editor/interaction.d.ts +8 -2
  15. package/dist/board/editor/interaction.js +34 -1
  16. package/dist/board/editor/query.d.ts +5 -5
  17. package/dist/board/editor/query.js +14 -12
  18. package/dist/board/editor/selection.js +2 -1
  19. package/dist/board/editor/snapshots.d.ts +0 -2
  20. package/dist/board/editor/snapshots.js +4 -20
  21. package/dist/board/export/index.d.ts +25 -4
  22. package/dist/board/export/index.js +45 -12
  23. package/dist/board/export/node/index.d.ts +5 -4
  24. package/dist/board/export/node/index.js +16 -11
  25. package/dist/board/export/node/scripts.d.ts +16 -0
  26. package/dist/board/export/node/scripts.js +258 -0
  27. package/dist/board/export/scene.d.ts +2 -1
  28. package/dist/board/export/scene.js +24 -7
  29. package/dist/board/model/animation.d.ts +4 -1
  30. package/dist/board/model/animation.js +34 -7
  31. package/dist/board/model/authoring.d.ts +20 -0
  32. package/dist/board/model/authoring.js +185 -0
  33. package/dist/board/model/export-plan.js +1 -0
  34. package/dist/board/model/file-preview.js +1 -1
  35. package/dist/board/model/file-snapshot.js +3 -2
  36. package/dist/board/model/image-key.js +7 -3
  37. package/dist/board/model/index.d.ts +15 -7
  38. package/dist/board/model/index.js +16 -9
  39. package/dist/board/model/items/builtin.js +2 -2
  40. package/dist/board/model/items/create.d.ts +19 -4
  41. package/dist/board/model/items/create.js +130 -9
  42. package/dist/board/model/items/index.d.ts +2 -2
  43. package/dist/board/model/items/index.js +2 -2
  44. package/dist/board/model/media-playback.d.ts +2 -1
  45. package/dist/board/model/media-playback.js +15 -5
  46. package/dist/board/model/media.js +7 -1
  47. package/dist/board/model/overlay-placement.d.ts +25 -0
  48. package/dist/board/model/overlay-placement.js +35 -0
  49. package/dist/board/model/replay.d.ts +1 -1
  50. package/dist/board/model/replay.js +1 -0
  51. package/dist/board/model/sampling.d.ts +19 -0
  52. package/dist/board/model/sampling.js +96 -0
  53. package/dist/board/model/script-example.d.ts +4 -0
  54. package/dist/board/model/script-example.js +21 -0
  55. package/dist/board/model/script-hit.d.ts +27 -0
  56. package/dist/board/model/script-hit.js +109 -0
  57. package/dist/board/model/script-index.d.ts +7 -0
  58. package/dist/board/model/script-index.js +15 -0
  59. package/dist/board/model/script-observers.d.ts +18 -0
  60. package/dist/board/model/script-observers.js +92 -0
  61. package/dist/board/model/script-scene.d.ts +24 -0
  62. package/dist/board/model/script-scene.js +294 -0
  63. package/dist/board/model/script.d.ts +149 -0
  64. package/dist/board/model/script.js +1 -0
  65. package/dist/board/render/board-background.js +5 -4
  66. package/dist/board/render/css-color.d.ts +6 -1
  67. package/dist/board/render/css-color.js +34 -20
  68. package/dist/board/render/diagnostics.d.ts +20 -0
  69. package/dist/board/render/diagnostics.js +117 -0
  70. package/dist/board/render/index.d.ts +9 -6
  71. package/dist/board/render/index.js +10 -7
  72. package/dist/board/render/media-interaction.js +1 -1
  73. package/dist/board/render/palette.d.ts +2 -1
  74. package/dist/board/render/palette.js +6 -2
  75. package/dist/board/render/renderers/arrow-card-renderer.js +3 -2
  76. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  77. package/dist/board/render/renderers/board-renderer-registry.d.ts +25 -5
  78. package/dist/board/render/renderers/board-renderer-registry.js +11 -4
  79. package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
  80. package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +18 -16
  81. package/dist/board/render/renderers/draw-card-renderer.js +3 -2
  82. package/dist/board/render/renderers/effect-card-renderer.js +22 -5
  83. package/dist/board/render/renderers/frame-card-renderer.js +30 -14
  84. package/dist/board/render/renderers/image-card-renderer.js +43 -4
  85. package/dist/board/render/renderers/shape-card-renderer.js +10 -27
  86. package/dist/board/render/renderers/task-card-renderer.d.ts +2 -1
  87. package/dist/board/render/renderers/task-card-renderer.js +220 -71
  88. package/dist/board/render/renderers/text-card-renderer.js +5 -3
  89. package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
  90. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  91. package/dist/board/render/sampling.d.ts +22 -0
  92. package/dist/board/render/sampling.js +21 -0
  93. package/dist/board/render/script-export.d.ts +11 -0
  94. package/dist/board/render/script-export.js +86 -0
  95. package/dist/board/render/script-host.d.ts +31 -0
  96. package/dist/board/render/script-host.js +446 -0
  97. package/dist/board/render/script-modules.d.ts +2 -0
  98. package/dist/board/render/script-modules.js +2 -0
  99. package/dist/board/render/script-runtime.d.ts +29 -0
  100. package/dist/board/render/script-runtime.js +223 -0
  101. package/dist/board/render/script-sandbox.d.ts +4 -0
  102. package/dist/board/render/script-sandbox.js +173 -0
  103. package/dist/board/render/script-texture.d.ts +11 -0
  104. package/dist/board/render/script-texture.js +33 -0
  105. package/dist/board/render/stroke.d.ts +2 -1
  106. package/dist/board/render/stroke.js +22 -1
  107. package/dist/board/render/text-measurement.js +1 -1
  108. package/dist/board/stage/assets.js +7 -2
  109. package/dist/board/stage/maintenance.d.ts +5 -0
  110. package/dist/board/stage/maintenance.js +11 -0
  111. package/dist/board/stage/scene.d.ts +2 -0
  112. package/dist/board/stage/scene.js +21 -2
  113. package/dist/board/stage/script-pointer.d.ts +20 -0
  114. package/dist/board/stage/script-pointer.js +41 -0
  115. package/dist/board/stage/stage.d.ts +21 -3
  116. package/dist/board/stage/stage.js +350 -47
  117. package/dist/board/stage/texture-pool.js +2 -3
  118. package/dist/chunks/environment.d.ts +305 -27
  119. package/dist/chunks/environment.js +12 -4
  120. package/dist/chunks/host-bridge.d.ts +2 -2
  121. package/dist/chunks/http.d.ts +90 -7
  122. package/dist/chunks/http.js +157 -64
  123. package/dist/chunks/transport.js +4 -2
  124. package/dist/chunks/websocket.d.ts +9 -1
  125. package/dist/chunks/websocket.js +16 -2
  126. package/dist/host-bridge.js +2 -1
  127. package/dist/http.d.ts +3 -3
  128. package/dist/index.d.ts +182 -4
  129. package/dist/index.js +1559 -299
  130. package/dist/protocol/dist/board-assets.d.ts +1 -0
  131. package/dist/protocol/dist/board-constants.js +2 -2
  132. package/dist/protocol/dist/board-model.d.ts +236 -11
  133. package/dist/protocol/dist/board-model.js +46 -17
  134. package/dist/protocol/dist/board-patch.js +2 -1
  135. package/dist/protocol/dist/board-schema.d.ts +4 -2
  136. package/dist/protocol/dist/board-schema.js +18 -4
  137. package/dist/protocol/dist/board-script-modules.d.ts +25 -0
  138. package/dist/protocol/dist/board-script-modules.js +127 -0
  139. package/dist/protocol/dist/board-task.d.ts +22 -0
  140. package/dist/{board/model/task.js → protocol/dist/board-task.js} +34 -7
  141. package/dist/protocol/dist/display/index.d.ts +1 -0
  142. package/dist/protocol/dist/generation/blocks.d.ts +8 -0
  143. package/dist/{generation-blocks.js → protocol/dist/generation/blocks.js} +14 -15
  144. package/dist/protocol/dist/index.d.ts +7 -2
  145. package/dist/protocol/dist/realtime/types.d.ts +1 -0
  146. package/dist/protocol/dist/rtc/index.d.ts +1 -0
  147. package/dist/protocol/dist/task/index.d.ts +4 -0
  148. package/package.json +2 -1
  149. package/dist/board/export/node/sketch.d.ts +0 -19
  150. package/dist/board/export/node/sketch.js +0 -146
  151. package/dist/board/model/task.d.ts +0 -11
  152. package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
  153. package/dist/board/render/sketch-host.d.ts +0 -14
  154. package/dist/board/render/sketch-host.js +0 -216
  155. package/dist/types.d.ts +0 -36
@@ -0,0 +1,223 @@
1
+ //#region src/board/render/script-runtime.ts
2
+ /** Self-contained so browser workers and Node execute exactly the same kernel. */
3
+ function createScriptRuntime(module, initial, host) {
4
+ if ("mount" in module) throw new Error("Board scripts do not support DOM mount(). Use draw() or native scene items.");
5
+ let input = initial;
6
+ let disposed = false;
7
+ let revision = 0;
8
+ let randomState = 2166136261;
9
+ for (const char of initial.item.script?.seed ?? initial.id) randomState = Math.imul(randomState ^ char.charCodeAt(0), 16777619) >>> 0;
10
+ const parameterDefaults = Object.fromEntries(Object.entries(module.parameters ?? {}).filter(([, spec]) => spec.default !== void 0).map(([key, spec]) => [key, spec.default]));
11
+ let params = {
12
+ ...parameterDefaults,
13
+ ...initial.item.script?.params
14
+ };
15
+ const state = structuredClone(initial.snapshot?.state ?? {});
16
+ if (initial.snapshot) randomState = initial.snapshot.random;
17
+ let time = initial.snapshot?.time ?? 0;
18
+ let started = Boolean(initial.snapshot);
19
+ const stepMs = 1e3 / 60;
20
+ let frame = {
21
+ t: time,
22
+ dt: 0,
23
+ width: initial.width,
24
+ height: initial.height,
25
+ seed: initial.item.script?.seed ?? initial.id,
26
+ params,
27
+ state
28
+ };
29
+ const handlers = /* @__PURE__ */ new Map();
30
+ const frames = /* @__PURE__ */ new Set();
31
+ const cleanups = [];
32
+ const effects = structuredClone(initial.snapshot?.effects);
33
+ let patches = effects?.patches ?? {};
34
+ let items = effects?.items ?? {};
35
+ let space = effects?.space ?? "world";
36
+ let anchor = effects?.anchor;
37
+ let view = null;
38
+ let hits = effects?.hits ?? {};
39
+ const invalidate = () => {
40
+ if (disposed) return;
41
+ revision++;
42
+ host.invalidate();
43
+ };
44
+ const present = (id, patch) => {
45
+ if (patch === null) delete patches[id];
46
+ else patches[id] = structuredClone(patch);
47
+ invalidate();
48
+ };
49
+ const context = {
50
+ self: {
51
+ id: initial.id,
52
+ get base() {
53
+ return input.item;
54
+ },
55
+ present: (patch) => present(initial.id, patch)
56
+ },
57
+ get params() {
58
+ return params;
59
+ },
60
+ get targets() {
61
+ return input.item.script?.targets ?? {};
62
+ },
63
+ async emit(target, type, data) {
64
+ await host.call("board.emit", {
65
+ target,
66
+ type,
67
+ data
68
+ });
69
+ },
70
+ state,
71
+ get frame() {
72
+ return frame;
73
+ },
74
+ get view() {
75
+ return view;
76
+ },
77
+ random() {
78
+ randomState = Math.imul(randomState, 1664525) + 1013904223 >>> 0;
79
+ return randomState / 4294967296;
80
+ },
81
+ on(type, handler) {
82
+ let listeners = handlers.get(type);
83
+ if (!listeners) {
84
+ listeners = /* @__PURE__ */ new Set();
85
+ handlers.set(type, listeners);
86
+ }
87
+ const listener = (event) => handler(event);
88
+ listeners.add(listener);
89
+ invalidate();
90
+ return () => {
91
+ listeners.delete(listener);
92
+ invalidate();
93
+ };
94
+ },
95
+ onFrame(handler) {
96
+ frames.add(handler);
97
+ invalidate();
98
+ return () => {
99
+ frames.delete(handler);
100
+ invalidate();
101
+ };
102
+ },
103
+ onDispose: (handler) => cleanups.push(handler),
104
+ invalidate,
105
+ get: (id) => input.items[id],
106
+ present,
107
+ scene(next, options) {
108
+ items = structuredClone(next);
109
+ space = options?.space ?? "world";
110
+ anchor = options?.anchor ? structuredClone(options.anchor) : void 0;
111
+ invalidate();
112
+ },
113
+ hitRegions(next) {
114
+ hits = structuredClone(next);
115
+ invalidate();
116
+ },
117
+ async commit(patch) {
118
+ await host.call("board.commit", patch);
119
+ },
120
+ capabilities: {
121
+ has: (name) => (input.capabilities ?? []).includes(name),
122
+ call: (name, value) => host.call(name, value)
123
+ }
124
+ };
125
+ const setup = module.default?.(context);
126
+ if (typeof setup === "function") cleanups.push(setup);
127
+ return {
128
+ get animated() {
129
+ return frames.size > 0 || module.animated === true;
130
+ },
131
+ update(next) {
132
+ input = next;
133
+ params = {
134
+ ...parameterDefaults,
135
+ ...next.item.script?.params
136
+ };
137
+ },
138
+ step(t) {
139
+ if (disposed) return;
140
+ if (!Number.isFinite(t) || t < time - .001) throw new Error("Reset the instance before seeking backwards.");
141
+ const update = (at, dt) => {
142
+ frame = {
143
+ ...frame,
144
+ t: at,
145
+ dt,
146
+ width: input.width,
147
+ height: input.height,
148
+ params: context.params
149
+ };
150
+ for (const handler of frames) handler(frame);
151
+ };
152
+ if (!started) {
153
+ started = true;
154
+ update(time, 0);
155
+ }
156
+ if (frames.size) while (time + stepMs <= t + 1e-6) {
157
+ time += stepMs;
158
+ update(time, stepMs);
159
+ }
160
+ else time = t;
161
+ frame = {
162
+ ...frame,
163
+ t,
164
+ dt: 0,
165
+ width: input.width,
166
+ height: input.height,
167
+ params: context.params
168
+ };
169
+ },
170
+ event(event) {
171
+ if (disposed) return;
172
+ if (event.type === "viewchange") view = event.data;
173
+ const listeners = handlers.get(event.type);
174
+ if (!listeners?.size) return;
175
+ for (const handler of listeners) handler(event);
176
+ invalidate();
177
+ },
178
+ draw(canvas) {
179
+ const result = module.draw?.(canvas, frame);
180
+ if (result && typeof result.then === "function") throw new Error("draw() must be synchronous.");
181
+ },
182
+ output() {
183
+ return {
184
+ parameters: module.parameters,
185
+ space,
186
+ anchor,
187
+ patches,
188
+ items,
189
+ hits,
190
+ listens: [...handlers].filter(([, callbacks]) => callbacks.size).map(([type]) => type),
191
+ animated: this.animated,
192
+ updating: frames.size > 0,
193
+ version: revision
194
+ };
195
+ },
196
+ snapshot(includeEffects = true, clone = true) {
197
+ const snapshot = {
198
+ state,
199
+ time,
200
+ random: randomState,
201
+ effects: includeEffects ? {
202
+ patches,
203
+ items,
204
+ hits,
205
+ space,
206
+ anchor
207
+ } : void 0
208
+ };
209
+ return clone ? structuredClone(snapshot) : snapshot;
210
+ },
211
+ destroy() {
212
+ disposed = true;
213
+ for (const cleanup of cleanups.reverse()) cleanup();
214
+ handlers.clear();
215
+ frames.clear();
216
+ patches = {};
217
+ items = {};
218
+ hits = {};
219
+ }
220
+ };
221
+ }
222
+ //#endregion
223
+ export { createScriptRuntime };
@@ -0,0 +1,4 @@
1
+ //#region src/board/render/script-sandbox.d.ts
2
+ declare function boardScriptFrameSource(): string;
3
+ //#endregion
4
+ export { boardScriptFrameSource };
@@ -0,0 +1,173 @@
1
+ import { scriptModuleUrls } from "../../protocol/dist/board-script-modules.js";
2
+ import { createScriptRuntime } from "./script-runtime.js";
3
+ //#region src/board/render/script-sandbox.ts
4
+ function scriptAgent(runtimeFactory, moduleUrls, load) {
5
+ let runtime = null;
6
+ let input = null;
7
+ let module = null;
8
+ let urls = null;
9
+ let outputVersion = -1;
10
+ let canvas = null;
11
+ let callId = 0;
12
+ const calls = /* @__PURE__ */ new Map();
13
+ const send = (data, transfer = []) => {
14
+ self.postMessage(data, transfer);
15
+ };
16
+ const call = (name, input) => new Promise((resolve, reject) => {
17
+ const id = ++callId;
18
+ calls.set(id, {
19
+ resolve,
20
+ reject
21
+ });
22
+ send({
23
+ kind: "call",
24
+ call: id,
25
+ name,
26
+ input
27
+ });
28
+ });
29
+ let processing = false;
30
+ const queue = [];
31
+ async function process(data) {
32
+ try {
33
+ if (data.input) input = data.input;
34
+ if (!input) throw new Error("Missing script input.");
35
+ if (!runtime) {
36
+ if (!data.sources) throw new Error("Missing script modules.");
37
+ urls = moduleUrls(data.sources);
38
+ const src = input.item.script?.src;
39
+ const url = src ? urls.urls[src] : void 0;
40
+ if (!url) throw new Error("Missing entry module.");
41
+ module = await load(url);
42
+ runtime = runtimeFactory(module, input, {
43
+ call,
44
+ invalidate: () => {
45
+ if (!processing) send({ kind: "dirty" });
46
+ }
47
+ });
48
+ }
49
+ if (data.input) runtime.update(input);
50
+ for (const event of data.events ?? []) runtime.event(event);
51
+ runtime.step(data.t);
52
+ let bitmap;
53
+ if (module?.draw && data.draw !== false) {
54
+ const width = Math.max(1, Math.round(input.width * data.scale));
55
+ const height = Math.max(1, Math.round(input.height * data.scale));
56
+ canvas ??= new OffscreenCanvas(width, height);
57
+ if (canvas.width !== width) canvas.width = width;
58
+ if (canvas.height !== height) canvas.height = height;
59
+ const context = canvas.getContext("2d");
60
+ if (!context) throw new Error("Canvas 2D is unavailable.");
61
+ context.reset();
62
+ context.scale(data.scale, data.scale);
63
+ runtime.draw(context);
64
+ bitmap = canvas.transferToImageBitmap();
65
+ }
66
+ const output = runtime.output();
67
+ const changed = output.version !== outputVersion;
68
+ outputVersion = output.version;
69
+ send({
70
+ kind: "frame",
71
+ request: data.request,
72
+ output: changed ? output : void 0,
73
+ bitmap
74
+ }, bitmap ? [bitmap] : []);
75
+ } catch (error) {
76
+ send({
77
+ kind: "error",
78
+ request: data.request,
79
+ error: error instanceof Error ? error.stack ?? error.message : String(error)
80
+ });
81
+ }
82
+ }
83
+ async function drain() {
84
+ if (processing) return;
85
+ processing = true;
86
+ while (queue.length) {
87
+ const request = queue.shift();
88
+ if (request) await process(request);
89
+ }
90
+ processing = false;
91
+ }
92
+ self.addEventListener("message", (event) => {
93
+ const data = event.data;
94
+ if (data.kind === "snapshot") {
95
+ try {
96
+ for (const event of data.events ?? []) runtime?.event(event);
97
+ send({
98
+ kind: "snapshot",
99
+ snapshot: runtime?.snapshot(true, false) ?? null
100
+ });
101
+ } catch (error) {
102
+ send({
103
+ kind: "snapshot",
104
+ error: String(error)
105
+ });
106
+ }
107
+ return;
108
+ }
109
+ if (data.kind === "dispose") {
110
+ try {
111
+ runtime?.destroy();
112
+ urls?.dispose();
113
+ } finally {
114
+ send({ kind: "disposed" });
115
+ }
116
+ return;
117
+ }
118
+ if (data.kind === "result" && data.call !== void 0) {
119
+ const pending = calls.get(data.call);
120
+ calls.delete(data.call);
121
+ if (data.error) pending?.reject(new Error(data.error));
122
+ else pending?.resolve(data.value);
123
+ return;
124
+ }
125
+ queue.push(data);
126
+ drain();
127
+ });
128
+ self.addEventListener("unhandledrejection", (event) => {
129
+ send({
130
+ kind: "error",
131
+ error: String(event.reason)
132
+ });
133
+ });
134
+ }
135
+ function boardScriptFrameSource() {
136
+ const runtime = createScriptRuntime.toString();
137
+ const urls = scriptModuleUrls.toString();
138
+ const agent = scriptAgent.toString();
139
+ const prelude = "const __name = (value, name) => Object.defineProperty(value, 'name', { value: name, configurable: true });";
140
+ const worker = `${prelude} (${agent})(${runtime}, ${urls}, url => import(url));`;
141
+ return `<!doctype html><meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' blob:; worker-src blob:; connect-src 'none'; form-action 'none'; base-uri 'none'"><script>
142
+ ${prelude}
143
+ const workerUrl = URL.createObjectURL(new Blob([${JSON.stringify(worker)}], {type:'text/javascript'}));
144
+ const workers = new Map();
145
+ addEventListener('message', event => {
146
+ if(event.source !== parent) return;
147
+ const {token, data} = event.data;
148
+ if(typeof token !== 'number' || !data) return;
149
+ let entry = workers.get(token);
150
+ if(data.kind === 'terminate') {
151
+ if(entry) { clearTimeout(entry.timer); entry.worker.terminate(); workers.delete(token); }
152
+ return;
153
+ }
154
+ if(!entry) {
155
+ if(!data.sources) return;
156
+ const worker = new Worker(workerUrl);
157
+ entry = {worker}; workers.set(token, entry);
158
+ worker.onmessage = event => {
159
+ parent.postMessage({...event.data, token}, '*', event.data.bitmap ? [event.data.bitmap] : []);
160
+ if(event.data.kind === 'disposed') {
161
+ clearTimeout(entry.timer); worker.terminate(); workers.delete(token);
162
+ }
163
+ };
164
+ worker.onerror = event => parent.postMessage({token,kind:'error',error:event.message || 'Script worker failed to start.'}, '*');
165
+ }
166
+ entry.worker.postMessage(data);
167
+ if(data.kind === 'dispose') entry.timer = setTimeout(() => {entry.worker.terminate(); workers.delete(token);},100);
168
+ });
169
+ parent.postMessage({kind:'ready'}, '*');
170
+ <\/script>`;
171
+ }
172
+ //#endregion
173
+ export { boardScriptFrameSource };
@@ -0,0 +1,11 @@
1
+ import { Texture } from "pixi.js";
2
+ //#region src/board/render/script-texture.d.ts
3
+ declare class BoardScriptTexture {
4
+ private texture;
5
+ private bitmap;
6
+ get current(): Texture | null;
7
+ update(bitmap: ImageBitmap): void;
8
+ destroy(): void;
9
+ }
10
+ //#endregion
11
+ export { BoardScriptTexture };
@@ -0,0 +1,33 @@
1
+ import { ImageSource, Texture } from "pixi.js";
2
+ //#region src/board/render/script-texture.ts
3
+ var BoardScriptTexture = class {
4
+ texture = null;
5
+ bitmap = null;
6
+ get current() {
7
+ return this.texture;
8
+ }
9
+ update(bitmap) {
10
+ const previous = this.bitmap;
11
+ this.bitmap = bitmap;
12
+ if (!this.texture) {
13
+ this.texture = new Texture({
14
+ source: new ImageSource({ resource: bitmap }),
15
+ dynamic: true
16
+ });
17
+ return;
18
+ }
19
+ const source = this.texture.source;
20
+ source.resource = bitmap;
21
+ source.update();
22
+ if (previous !== bitmap) previous?.close();
23
+ }
24
+ destroy() {
25
+ if (!this.texture) return;
26
+ this.texture.destroy(true);
27
+ this.bitmap?.close();
28
+ this.bitmap = null;
29
+ this.texture = null;
30
+ }
31
+ };
32
+ //#endregion
33
+ export { BoardScriptTexture };
@@ -8,5 +8,6 @@ declare function trimPolyline(path: readonly WorldPoint[], trim: number | undefi
8
8
  declare function tracePolyline(graphics: Graphics, path: readonly WorldPoint[], pattern: [number, number] | null, closed?: boolean): void;
9
9
  declare function traceArrowhead(graphics: Graphics, tip: WorldPoint, angle: number, size: number): void;
10
10
  declare function endAngle(path: readonly WorldPoint[], atStart: boolean): number | null;
11
+ declare function roundRectPoints(width: number, height: number, radius: number): WorldPoint[];
11
12
  //#endregion
12
- export { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline };
13
+ export { dashPattern, endAngle, roundRectPoints, traceArrowhead, tracePolyline, trimPolyline };
@@ -86,5 +86,26 @@ function endAngle(path, atStart) {
86
86
  }
87
87
  return null;
88
88
  }
89
+ function roundRectPoints(width, height, radius) {
90
+ const r = Math.min(radius, width / 2, height / 2);
91
+ if (r <= 0) return [
92
+ worldPoint(0, 0),
93
+ worldPoint(width, 0),
94
+ worldPoint(width, height),
95
+ worldPoint(0, height),
96
+ worldPoint(0, 0)
97
+ ];
98
+ const corner = (cx, cy, from) => Array.from({ length: 9 }, (_, index) => {
99
+ const angle = from + index / 8 * (Math.PI / 2);
100
+ return worldPoint(cx + Math.cos(angle) * r, cy + Math.sin(angle) * r);
101
+ });
102
+ return [
103
+ ...corner(width - r, r, -Math.PI / 2),
104
+ ...corner(width - r, height - r, 0),
105
+ ...corner(r, height - r, Math.PI / 2),
106
+ ...corner(r, r, Math.PI),
107
+ worldPoint(width - r, 0)
108
+ ];
109
+ }
89
110
  //#endregion
90
- export { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline };
111
+ export { dashPattern, endAngle, roundRectPoints, traceArrowhead, tracePolyline, trimPolyline };
@@ -5,7 +5,7 @@ import { DOMAdapter } from "pixi.js";
5
5
  //#region src/board/render/text-measurement.ts
6
6
  const BOARD_FONT_STACKS = {
7
7
  sans: BOARD_FONT_STACK,
8
- serif: "ui-serif, Georgia, \"Noto Serif CJK SC\", \"Songti SC\", serif",
8
+ serif: "ui-serif, Georgia, \"Noto Serif CJK SC\", \"Songti SC\", \"Noto Sans Math\", \"DejaVu Sans\", \"Noto Sans CJK SC\", \"FreeSans\", \"FreeSerif\", sans-serif",
9
9
  mono: BOARD_MONO_FONT_STACK
10
10
  };
11
11
  let measureContext;
@@ -1,5 +1,6 @@
1
1
  import { mediaPreviewCandidates } from "../../media.js";
2
- import { featuredTaskArtifact } from "../model/task.js";
2
+ import { parseBoardMediaSource } from "../../protocol/dist/board-model.js";
3
+ import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
3
4
  import { boardImageKeySource, imageAssetKey } from "../model/image-key.js";
4
5
  import { loadVideoThumbnailTexture, videoTextureNaturalSize } from "../render/video-thumbnail.js";
5
6
  import { DEFAULT_LRU_BUDGET, selectLruEvictions } from "./lru.js";
@@ -14,7 +15,11 @@ function taskPreviewKey(artifact) {
14
15
  }
15
16
  function boardAssetKey(item) {
16
17
  if (item.type === "task") return taskPreviewKey(featuredTaskArtifact(item.props.snapshot.artifacts));
17
- if (item.type === "video") return `video:${encodeURIComponent(item.props.src)}:${item.props.snapshot?.mtimeMs ?? "unknown"}`;
18
+ if (item.type === "video") {
19
+ const source = parseBoardMediaSource(item.props.src);
20
+ if (!source) return null;
21
+ return source.kind === "url" ? `video-url:${encodeURIComponent(source.value)}` : `video:${encodeURIComponent(source.value)}:${item.props.snapshot?.mtimeMs ?? "unknown"}`;
22
+ }
18
23
  return imageAssetKey(item);
19
24
  }
20
25
  function keySource(key) {
@@ -0,0 +1,5 @@
1
+ import { Renderer } from "pixi.js";
2
+ //#region src/board/stage/maintenance.d.ts
3
+ declare function scheduleBoardMaintenance(renderer: Renderer): () => void;
4
+ //#endregion
5
+ export { scheduleBoardMaintenance };
@@ -0,0 +1,11 @@
1
+ import { Ticker } from "pixi.js";
2
+ //#region src/board/stage/maintenance.ts
3
+ function scheduleBoardMaintenance(renderer) {
4
+ renderer.events.setTargetElement(null);
5
+ const scheduler = renderer.scheduler;
6
+ Ticker.system.remove(scheduler._update, scheduler);
7
+ const timer = setInterval(() => scheduler._update(), 1e3);
8
+ return () => clearInterval(timer);
9
+ }
10
+ //#endregion
11
+ export { scheduleBoardMaintenance };
@@ -24,6 +24,7 @@ type SceneSyncInput = {
24
24
  visibleIds: Set<string> | null;
25
25
  pinnedIds: Set<string>;
26
26
  globalSig: string;
27
+ staleIds?: ReadonlySet<string>;
27
28
  structureVersion: number;
28
29
  geometryVersion: number;
29
30
  gestureActive: boolean;
@@ -72,6 +73,7 @@ type BoardScene = {
72
73
  sync: (input: SceneSyncInput) => void;
73
74
  getNode: (itemId: string) => BoardSceneNode | null;
74
75
  readonly animated: boolean;
76
+ tick: (clock: number) => number | null;
75
77
  drawOverlay: (input: SceneOverlayInput, palette: BoardRenderPalette) => void;
76
78
  destroy: (context: BoardRenderContext) => void;
77
79
  };
@@ -48,6 +48,7 @@ function createBoardScene(options) {
48
48
  const cards = /* @__PURE__ */ new Map();
49
49
  const clips = /* @__PURE__ */ new Map();
50
50
  const animatedIds = /* @__PURE__ */ new Set();
51
+ const tickingIds = /* @__PURE__ */ new Set();
51
52
  const pools = /* @__PURE__ */ new Map();
52
53
  const heldKeys = /* @__PURE__ */ new Map();
53
54
  let orderById = /* @__PURE__ */ new Map();
@@ -72,6 +73,7 @@ function createBoardScene(options) {
72
73
  setHeldKey(context, id, null);
73
74
  cards.delete(id);
74
75
  animatedIds.delete(id);
76
+ tickingIds.delete(id);
75
77
  if (destroyed) return;
76
78
  entry.container.parent?.removeChild(entry.container);
77
79
  entry.container.visible = false;
@@ -123,6 +125,7 @@ function createBoardScene(options) {
123
125
  container.alpha = scene.opacity(item.id);
124
126
  hostFor(item, scene).addChild(container);
125
127
  if (renderer.animated) animatedIds.add(item.id);
128
+ if (renderer.tick) tickingIds.add(item.id);
126
129
  const entry = {
127
130
  item,
128
131
  container,
@@ -130,7 +133,8 @@ function createBoardScene(options) {
130
133
  selected: context.selectedIds.has(item.id),
131
134
  hovered: context.hoveredId === item.id,
132
135
  resizing: context.resizingIds.has(item.id),
133
- globalSig
136
+ globalSig,
137
+ scriptRevision: context.scripts?.revision?.(item.id) ?? 0
134
138
  };
135
139
  cards.set(item.id, entry);
136
140
  setHeldKey(context, item.id, context.assetKey(item));
@@ -350,7 +354,9 @@ function createBoardScene(options) {
350
354
  const selected = context.selectedIds.has(id);
351
355
  const hovered = context.hoveredId === id;
352
356
  const resizing = context.resizingIds.has(id);
353
- const changed = item !== entry.item || entry.renderer.animated || selected !== entry.selected || hovered !== entry.hovered || resizing !== entry.resizing || globalSig !== entry.globalSig;
357
+ const scriptRevision = context.scripts?.revision?.(id) ?? 0;
358
+ const changed = input.staleIds?.has(id) || scriptRevision !== entry.scriptRevision || item !== entry.item || entry.renderer.animated || selected !== entry.selected || hovered !== entry.hovered || resizing !== entry.resizing || globalSig !== entry.globalSig;
359
+ entry.scriptRevision = scriptRevision;
354
360
  entry.item = item;
355
361
  entry.selected = selected;
356
362
  entry.hovered = hovered;
@@ -360,12 +366,23 @@ function createBoardScene(options) {
360
366
  if (changed) {
361
367
  entry.renderer.update(entry.container, item, context);
362
368
  entry.container.alpha = scene.opacity(id);
369
+ if (entry.renderer.tick) tickingIds.add(id);
363
370
  }
364
371
  }
365
372
  if (structureChanged || liveSetChanged) syncClips(scene);
366
373
  if (structureChanged || farModeChanged || liveSetChanged) applyChildOrder();
367
374
  syncLiveStrokes(input);
368
375
  }
376
+ function tick(clock) {
377
+ let next = null;
378
+ for (const id of tickingIds) {
379
+ const entry = cards.get(id);
380
+ const wait = entry?.container.visible ? entry.renderer.tick?.(entry.container, clock) ?? null : null;
381
+ if (wait === null) tickingIds.delete(id);
382
+ else next = next === null ? wait : Math.min(next, wait);
383
+ }
384
+ return next;
385
+ }
369
386
  function applyChildOrder() {
370
387
  for (const [id, entry] of cards) entry.container.zIndex = orderById.get(id) ?? 0;
371
388
  world.sortableChildren = true;
@@ -486,6 +503,7 @@ function createBoardScene(options) {
486
503
  for (const entry of clips.values()) entry.group.destroy({ children: true });
487
504
  clips.clear();
488
505
  animatedIds.clear();
506
+ tickingIds.clear();
489
507
  for (const id of [...liveStrokes.keys()]) removeLiveStroke(id, context);
490
508
  resetFarLayer();
491
509
  farActive = false;
@@ -504,6 +522,7 @@ function createBoardScene(options) {
504
522
  get animated() {
505
523
  return animatedIds.size > 0;
506
524
  },
525
+ tick,
507
526
  drawOverlay,
508
527
  destroy
509
528
  };
@@ -0,0 +1,20 @@
1
+ //#region src/board/stage/script-pointer.d.ts
2
+ type BoardScriptPointerTarget = {
3
+ owner: string;
4
+ target: string;
5
+ };
6
+ declare function createBoardScriptPointerRouter(): {
7
+ needsRouting(pointerId: number, scriptsEnabled: boolean): boolean;
8
+ owner(pointerId: number): "editor" | "script" | null;
9
+ route(phase: "down" | "move" | "up" | "leave", pointerId: number, target: BoardScriptPointerTarget | null, point: {
10
+ x: number;
11
+ y: number;
12
+ }, cancelled?: boolean): {
13
+ recipient: BoardScriptPointerTarget | null;
14
+ click: BoardScriptPointerTarget | null;
15
+ };
16
+ cancelScripts(cancel: (pointerId: number, target: BoardScriptPointerTarget) => void, owner?: string, retained?: Readonly<Record<string, unknown>>): void;
17
+ clear(): void;
18
+ };
19
+ //#endregion
20
+ export { BoardScriptPointerTarget, createBoardScriptPointerRouter };