@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.
- package/README.md +42 -7
- package/dist/board/editor/animation.d.ts +3 -0
- package/dist/board/editor/animation.js +16 -1
- package/dist/board/editor/clipboard.js +7 -0
- package/dist/board/editor/copy.js +23 -18
- package/dist/board/editor/creation.d.ts +7 -3
- package/dist/board/editor/creation.js +5 -4
- package/dist/board/editor/deletion.js +3 -3
- package/dist/board/editor/document.d.ts +8 -2
- package/dist/board/editor/document.js +54 -7
- package/dist/board/editor/editor.d.ts +22 -7
- package/dist/board/editor/editor.js +12 -2
- package/dist/board/editor/index.d.ts +2 -2
- package/dist/board/editor/interaction.d.ts +8 -2
- package/dist/board/editor/interaction.js +34 -1
- package/dist/board/editor/query.d.ts +5 -5
- package/dist/board/editor/query.js +14 -12
- package/dist/board/editor/selection.js +2 -1
- package/dist/board/editor/snapshots.d.ts +0 -2
- package/dist/board/editor/snapshots.js +4 -20
- package/dist/board/export/index.d.ts +25 -4
- package/dist/board/export/index.js +45 -12
- package/dist/board/export/node/index.d.ts +5 -4
- package/dist/board/export/node/index.js +16 -11
- package/dist/board/export/node/scripts.d.ts +16 -0
- package/dist/board/export/node/scripts.js +258 -0
- package/dist/board/export/scene.d.ts +2 -1
- package/dist/board/export/scene.js +24 -7
- package/dist/board/model/animation.d.ts +4 -1
- package/dist/board/model/animation.js +34 -7
- package/dist/board/model/authoring.d.ts +20 -0
- package/dist/board/model/authoring.js +185 -0
- package/dist/board/model/export-plan.js +1 -0
- package/dist/board/model/file-preview.js +1 -1
- package/dist/board/model/file-snapshot.js +3 -2
- package/dist/board/model/image-key.js +7 -3
- package/dist/board/model/index.d.ts +15 -7
- package/dist/board/model/index.js +16 -9
- package/dist/board/model/items/builtin.js +2 -2
- package/dist/board/model/items/create.d.ts +19 -4
- package/dist/board/model/items/create.js +130 -9
- package/dist/board/model/items/index.d.ts +2 -2
- package/dist/board/model/items/index.js +2 -2
- package/dist/board/model/media-playback.d.ts +2 -1
- package/dist/board/model/media-playback.js +15 -5
- package/dist/board/model/media.js +7 -1
- package/dist/board/model/overlay-placement.d.ts +25 -0
- package/dist/board/model/overlay-placement.js +35 -0
- package/dist/board/model/replay.d.ts +1 -1
- package/dist/board/model/replay.js +1 -0
- package/dist/board/model/sampling.d.ts +19 -0
- package/dist/board/model/sampling.js +96 -0
- package/dist/board/model/script-example.d.ts +4 -0
- package/dist/board/model/script-example.js +21 -0
- package/dist/board/model/script-hit.d.ts +27 -0
- package/dist/board/model/script-hit.js +109 -0
- package/dist/board/model/script-index.d.ts +7 -0
- package/dist/board/model/script-index.js +15 -0
- package/dist/board/model/script-observers.d.ts +18 -0
- package/dist/board/model/script-observers.js +92 -0
- package/dist/board/model/script-scene.d.ts +24 -0
- package/dist/board/model/script-scene.js +294 -0
- package/dist/board/model/script.d.ts +149 -0
- package/dist/board/model/script.js +1 -0
- package/dist/board/render/board-background.js +5 -4
- package/dist/board/render/css-color.d.ts +6 -1
- package/dist/board/render/css-color.js +34 -20
- package/dist/board/render/diagnostics.d.ts +20 -0
- package/dist/board/render/diagnostics.js +117 -0
- package/dist/board/render/index.d.ts +9 -6
- package/dist/board/render/index.js +10 -7
- package/dist/board/render/media-interaction.js +1 -1
- package/dist/board/render/palette.d.ts +2 -1
- package/dist/board/render/palette.js +6 -2
- package/dist/board/render/renderers/arrow-card-renderer.js +3 -2
- package/dist/board/render/renderers/audio-card-renderer.js +2 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +25 -5
- package/dist/board/render/renderers/board-renderer-registry.js +11 -4
- package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +18 -16
- package/dist/board/render/renderers/draw-card-renderer.js +3 -2
- package/dist/board/render/renderers/effect-card-renderer.js +22 -5
- package/dist/board/render/renderers/frame-card-renderer.js +30 -14
- package/dist/board/render/renderers/image-card-renderer.js +43 -4
- package/dist/board/render/renderers/shape-card-renderer.js +10 -27
- package/dist/board/render/renderers/task-card-renderer.d.ts +2 -1
- package/dist/board/render/renderers/task-card-renderer.js +220 -71
- package/dist/board/render/renderers/text-card-renderer.js +5 -3
- package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
- package/dist/board/render/renderers/video-card-renderer.js +2 -1
- package/dist/board/render/sampling.d.ts +22 -0
- package/dist/board/render/sampling.js +21 -0
- package/dist/board/render/script-export.d.ts +11 -0
- package/dist/board/render/script-export.js +86 -0
- package/dist/board/render/script-host.d.ts +31 -0
- package/dist/board/render/script-host.js +446 -0
- package/dist/board/render/script-modules.d.ts +2 -0
- package/dist/board/render/script-modules.js +2 -0
- package/dist/board/render/script-runtime.d.ts +29 -0
- package/dist/board/render/script-runtime.js +223 -0
- package/dist/board/render/script-sandbox.d.ts +4 -0
- package/dist/board/render/script-sandbox.js +173 -0
- package/dist/board/render/script-texture.d.ts +11 -0
- package/dist/board/render/script-texture.js +33 -0
- package/dist/board/render/stroke.d.ts +2 -1
- package/dist/board/render/stroke.js +22 -1
- package/dist/board/render/text-measurement.js +1 -1
- package/dist/board/stage/assets.js +7 -2
- package/dist/board/stage/maintenance.d.ts +5 -0
- package/dist/board/stage/maintenance.js +11 -0
- package/dist/board/stage/scene.d.ts +2 -0
- package/dist/board/stage/scene.js +21 -2
- package/dist/board/stage/script-pointer.d.ts +20 -0
- package/dist/board/stage/script-pointer.js +41 -0
- package/dist/board/stage/stage.d.ts +21 -3
- package/dist/board/stage/stage.js +350 -47
- package/dist/board/stage/texture-pool.js +2 -3
- package/dist/chunks/environment.d.ts +305 -27
- package/dist/chunks/environment.js +12 -4
- package/dist/chunks/host-bridge.d.ts +2 -2
- package/dist/chunks/http.d.ts +90 -7
- package/dist/chunks/http.js +157 -64
- package/dist/chunks/transport.js +4 -2
- package/dist/chunks/websocket.d.ts +9 -1
- package/dist/chunks/websocket.js +16 -2
- package/dist/host-bridge.js +2 -1
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +182 -4
- package/dist/index.js +1559 -299
- package/dist/protocol/dist/board-assets.d.ts +1 -0
- package/dist/protocol/dist/board-constants.js +2 -2
- package/dist/protocol/dist/board-model.d.ts +236 -11
- package/dist/protocol/dist/board-model.js +46 -17
- package/dist/protocol/dist/board-patch.js +2 -1
- package/dist/protocol/dist/board-schema.d.ts +4 -2
- package/dist/protocol/dist/board-schema.js +18 -4
- package/dist/protocol/dist/board-script-modules.d.ts +25 -0
- package/dist/protocol/dist/board-script-modules.js +127 -0
- package/dist/protocol/dist/board-task.d.ts +22 -0
- package/dist/{board/model/task.js → protocol/dist/board-task.js} +34 -7
- package/dist/protocol/dist/display/index.d.ts +1 -0
- package/dist/protocol/dist/generation/blocks.d.ts +8 -0
- package/dist/{generation-blocks.js → protocol/dist/generation/blocks.js} +14 -15
- package/dist/protocol/dist/index.d.ts +7 -2
- package/dist/protocol/dist/realtime/types.d.ts +1 -0
- package/dist/protocol/dist/rtc/index.d.ts +1 -0
- package/dist/protocol/dist/task/index.d.ts +4 -0
- package/package.json +2 -1
- package/dist/board/export/node/sketch.d.ts +0 -19
- package/dist/board/export/node/sketch.js +0 -146
- package/dist/board/model/task.d.ts +0 -11
- package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
- package/dist/board/render/sketch-host.d.ts +0 -14
- package/dist/board/render/sketch-host.js +0 -216
- 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,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 {
|
|
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")
|
|
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,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
|
|
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 };
|