@neta-art/cohub 9.0.1 → 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.
- package/README.md +108 -102
- package/dist/board/editor/align.d.ts +11 -0
- package/dist/board/editor/align.js +72 -0
- package/dist/board/editor/animation.d.ts +17 -0
- package/dist/board/editor/animation.js +187 -0
- package/dist/board/editor/camera.d.ts +32 -0
- package/dist/board/editor/camera.js +137 -0
- package/dist/board/editor/clipboard.d.ts +41 -0
- package/dist/board/editor/clipboard.js +129 -0
- package/dist/board/editor/connection-ports.d.ts +19 -0
- package/dist/board/editor/connection-ports.js +85 -0
- package/dist/board/editor/context.d.ts +111 -0
- package/dist/board/editor/context.js +30 -0
- package/dist/board/editor/copy.d.ts +17 -0
- package/dist/board/editor/copy.js +121 -0
- package/dist/board/editor/creation.d.ts +51 -0
- package/dist/board/editor/creation.js +225 -0
- package/dist/board/editor/deletion.d.ts +9 -0
- package/dist/board/editor/deletion.js +90 -0
- package/dist/board/editor/document-edits.d.ts +22 -0
- package/dist/board/editor/document-edits.js +193 -0
- package/dist/board/editor/document.d.ts +41 -0
- package/dist/board/editor/document.js +302 -0
- package/dist/board/editor/editor.d.ts +225 -0
- package/dist/board/editor/editor.js +439 -0
- package/dist/board/editor/index.d.ts +18 -0
- package/dist/board/editor/index.js +12 -0
- package/dist/board/editor/interaction.d.ts +115 -0
- package/dist/board/editor/interaction.js +584 -0
- package/dist/board/editor/query.d.ts +27 -0
- package/dist/board/editor/query.js +203 -0
- package/dist/board/editor/remote.d.ts +11 -0
- package/dist/board/editor/remote.js +55 -0
- package/dist/board/editor/selection-transform.d.ts +24 -0
- package/dist/board/editor/selection-transform.js +81 -0
- package/dist/board/editor/selection.d.ts +33 -0
- package/dist/board/editor/selection.js +197 -0
- package/dist/board/editor/snapping.d.ts +33 -0
- package/dist/board/editor/snapping.js +75 -0
- package/dist/board/editor/snapshots.d.ts +27 -0
- package/dist/board/editor/snapshots.js +132 -0
- package/dist/board/editor/spatial.d.ts +41 -0
- package/dist/board/editor/spatial.js +191 -0
- package/dist/board/editor/state.d.ts +12 -0
- package/dist/board/editor/state.js +49 -0
- package/dist/board/editor/tool.d.ts +10 -0
- package/dist/board/editor/tool.js +19 -0
- package/dist/board/editor/wheel.d.ts +5 -0
- package/dist/board/editor/wheel.js +17 -0
- package/dist/board/export/index.d.ts +28 -6
- package/dist/board/export/index.js +47 -13
- package/dist/board/export/node/index.d.ts +56 -0
- package/dist/board/{headless → export/node}/index.js +26 -21
- 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 +8 -5
- package/dist/board/export/scene.js +24 -9
- package/dist/board/{animation.d.ts → model/animation.d.ts} +8 -5
- package/dist/board/{animation.js → model/animation.js} +37 -10
- package/dist/board/{core → model}/arrow-geometry.d.ts +2 -2
- package/dist/board/{core → model}/arrow-geometry.js +2 -2
- package/dist/board/model/authoring.d.ts +20 -0
- package/dist/board/model/authoring.js +185 -0
- package/dist/board/{core → model}/draw-geometry.d.ts +2 -2
- package/dist/board/{core → model}/draw-geometry.js +1 -1
- package/dist/board/model/draw-input.d.ts +14 -0
- package/dist/board/model/draw-input.js +28 -0
- package/dist/board/{core → model}/export-assets.d.ts +1 -1
- package/dist/board/{core → model}/export-assets.js +1 -1
- package/dist/board/{core → model}/export-plan.d.ts +2 -2
- package/dist/board/{core → model}/export-plan.js +3 -2
- package/dist/board/{core → model}/file-preview.d.ts +1 -1
- package/dist/board/{core → model}/file-preview.js +2 -2
- package/dist/board/{core → model}/file-snapshot.d.ts +1 -1
- package/dist/board/{core → model}/file-snapshot.js +4 -3
- package/dist/board/{geometry.d.ts → model/geometry.d.ts} +3 -3
- package/dist/board/{geometry.js → model/geometry.js} +1 -1
- package/dist/board/{image-key.d.ts → model/image-key.d.ts} +2 -2
- package/dist/board/{image-key.js → model/image-key.js} +7 -3
- package/dist/board/model/index.d.ts +42 -0
- package/dist/board/model/index.js +39 -0
- package/dist/board/model/items/builtin.d.ts +5 -0
- package/dist/board/model/items/builtin.js +119 -0
- package/dist/board/model/items/create.d.ts +61 -0
- package/dist/board/model/items/create.js +272 -0
- package/dist/board/model/items/definition.d.ts +35 -0
- package/dist/board/model/items/definition.js +18 -0
- package/dist/board/model/items/id.d.ts +5 -0
- package/dist/board/model/items/id.js +11 -0
- package/dist/board/model/items/index.d.ts +6 -0
- package/dist/board/model/items/index.js +6 -0
- package/dist/board/model/items/registry.d.ts +35 -0
- package/dist/board/model/items/registry.js +84 -0
- package/dist/board/{media-playback.d.ts → model/media-playback.d.ts} +4 -3
- package/dist/board/{media-playback.js → model/media-playback.js} +15 -5
- package/dist/board/{media.d.ts → model/media.d.ts} +1 -1
- package/dist/board/{media.js → model/media.js} +8 -2
- package/dist/board/{core → model}/palette.d.ts +1 -1
- package/dist/board/{core → model}/palette.js +1 -1
- package/dist/board/{presets.d.ts → model/presets.d.ts} +3 -3
- package/dist/board/{presets.js → model/presets.js} +1 -1
- package/dist/board/{replay.d.ts → model/replay.d.ts} +5 -5
- package/dist/board/{replay.js → model/replay.js} +2 -2
- package/dist/board/model/sampling.d.ts +19 -0
- package/dist/board/model/sampling.js +96 -0
- package/dist/board/{core → model}/scene.d.ts +1 -1
- package/dist/board/{core → model}/scene.js +1 -1
- 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 +10 -0
- package/dist/board/model/script-hit.js +52 -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-scene.d.ts +24 -0
- package/dist/board/model/script-scene.js +267 -0
- package/dist/board/model/script.d.ts +105 -0
- package/dist/board/model/script.js +1 -0
- package/dist/board/model/shape-types.d.ts +8 -0
- package/dist/board/model/shape-types.js +8 -0
- package/dist/board/{task.d.ts → model/task.d.ts} +4 -4
- package/dist/board/{task.js → model/task.js} +3 -3
- package/dist/board/{core → model}/text-metrics.d.ts +1 -1
- package/dist/board/{core → model}/text-metrics.js +1 -1
- package/dist/board/{core → model}/tool-styles.d.ts +1 -1
- package/dist/board/{core → model}/tool-styles.js +1 -1
- package/dist/board/{runtime/board-player.d.ts → player/index.d.ts} +2 -2
- package/dist/board/{runtime/board-player.js → player/index.js} +5 -4
- package/dist/board/render/board-background.d.ts +1 -1
- package/dist/board/render/clip.d.ts +1 -1
- 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.d.ts +2 -2
- package/dist/board/render/media-interaction.js +1 -1
- package/dist/board/render/palette.d.ts +3 -2
- package/dist/board/render/palette.js +7 -3
- package/dist/board/render/renderers/arrow-card-renderer.js +4 -3
- package/dist/board/render/renderers/audio-card-renderer.js +2 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -10
- package/dist/board/render/renderers/board-renderer-registry.js +17 -17
- 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} +16 -15
- package/dist/board/render/renderers/draw-card-renderer.js +4 -3
- package/dist/board/render/renderers/effect-card-renderer.js +22 -5
- package/dist/board/render/renderers/far-plate.js +1 -1
- package/dist/board/render/renderers/file-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/file-card-renderer.js +2 -2
- package/dist/board/render/renderers/frame-card-renderer.js +30 -14
- package/dist/board/render/renderers/image-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/image-card-renderer.js +43 -4
- package/dist/board/render/renderers/shape-card-renderer.js +11 -28
- package/dist/board/render/renderers/task-card-renderer.js +1 -1
- package/dist/board/render/renderers/text-card-renderer.js +6 -4
- package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
- package/dist/board/render/renderers/video-card-renderer.d.ts +1 -1
- 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 +421 -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 +213 -0
- package/dist/board/render/script-sandbox.d.ts +4 -0
- package/dist/board/render/script-sandbox.js +173 -0
- package/dist/board/render/stroke.d.ts +3 -2
- package/dist/board/render/stroke.js +23 -2
- package/dist/board/render/text-measurement.js +2 -2
- package/dist/board/replica/index.d.ts +70 -0
- package/dist/board/replica/index.js +276 -0
- package/dist/board/stage/assets.d.ts +41 -0
- package/dist/board/stage/assets.js +355 -0
- package/dist/board/stage/index.d.ts +8 -0
- package/dist/board/stage/index.js +8 -0
- package/dist/board/stage/lru.d.ts +32 -0
- package/dist/board/stage/lru.js +29 -0
- package/dist/board/stage/maintenance.d.ts +5 -0
- package/dist/board/stage/maintenance.js +11 -0
- package/dist/board/stage/pointer.d.ts +8 -0
- package/dist/board/stage/pointer.js +33 -0
- package/dist/board/stage/scene.d.ts +85 -0
- package/dist/board/stage/scene.js +515 -0
- package/dist/board/stage/script-pointer.d.ts +19 -0
- package/dist/board/stage/script-pointer.js +35 -0
- package/dist/board/stage/stage.d.ts +76 -0
- package/dist/board/stage/stage.js +774 -0
- package/dist/board/stage/texture-pool.d.ts +24 -0
- package/dist/board/stage/texture-pool.js +153 -0
- package/dist/board/stage/views.d.ts +38 -0
- package/dist/board/stage/views.js +93 -0
- package/dist/chunks/environment.d.ts +289 -23
- package/dist/chunks/host-bridge.d.ts +2 -2
- package/dist/chunks/http.d.ts +185 -71
- package/dist/chunks/http.js +368 -269
- package/dist/chunks/transport.js +3 -1
- 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 +2 -2
- package/dist/index.d.ts +182 -4
- package/dist/index.js +1393 -129
- package/dist/media.d.ts +1 -10
- package/dist/media.js +16 -27
- 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 +201 -11
- package/dist/protocol/dist/board-model.js +33 -14
- package/dist/protocol/dist/board-patch.js +3 -2
- 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.d.ts +28 -2
- package/dist/protocol/dist/board.js +3 -1
- package/dist/protocol/dist/core/media.d.ts +5 -0
- package/dist/protocol/dist/core/media.js +39 -0
- package/dist/protocol/dist/display/index.d.ts +1 -0
- package/dist/protocol/dist/index.d.ts +7 -5
- package/dist/protocol/dist/realtime/types.d.ts +1 -0
- package/dist/protocol/dist/rtc/index.d.ts +1 -0
- package/dist/types.d.ts +1 -1
- package/package.json +21 -8
- package/dist/board/core/shape-definition.d.ts +0 -23
- package/dist/board/core/shape-definition.js +0 -45
- package/dist/board/core/shape-types.d.ts +0 -38
- package/dist/board/core/shape-types.js +0 -22
- package/dist/board/headless/index.d.ts +0 -55
- package/dist/board/headless/sketch.d.ts +0 -19
- package/dist/board/headless/sketch.js +0 -146
- package/dist/board/index.d.ts +0 -30
- package/dist/board/index.js +0 -29
- 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
|
@@ -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 };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BoardDash } from "../../protocol/dist/board-model.js";
|
|
2
2
|
import "../../protocol/dist/index.js";
|
|
3
|
-
import { WorldPoint } from "../geometry.js";
|
|
3
|
+
import { WorldPoint } from "../model/geometry.js";
|
|
4
4
|
import { Graphics } from "pixi.js";
|
|
5
5
|
//#region src/board/render/stroke.d.ts
|
|
6
6
|
declare function dashPattern(dash: BoardDash | undefined, width: number): [number, number] | null;
|
|
@@ -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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { worldPoint } from "../geometry.js";
|
|
1
|
+
import { worldPoint } from "../model/geometry.js";
|
|
2
2
|
//#region src/board/render/stroke.ts
|
|
3
3
|
const HEAD_SPREAD = Math.PI / 6;
|
|
4
4
|
function dashPattern(dash, width) {
|
|
@@ -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 };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../protocol/dist/board-constants.js";
|
|
2
2
|
import { setBoardTextMeasurer } from "../../protocol/dist/board-layout.js";
|
|
3
|
-
import { boardFontFamily } from "../
|
|
3
|
+
import { boardFontFamily } from "../model/text-metrics.js";
|
|
4
4
|
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;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { BoardDelta, BoardDocument, BoardPatch } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { BoardApplyResult, BoardPlaybackSnapshot, BoardReadResult } from "../../protocol/dist/board.js";
|
|
3
|
+
import "../../protocol/dist/index.js";
|
|
4
|
+
//#region src/board/replica/index.d.ts
|
|
5
|
+
type BoardPendingPatch = {
|
|
6
|
+
mutationId: string;
|
|
7
|
+
patch: BoardPatch;
|
|
8
|
+
};
|
|
9
|
+
type BoardChangedPayload = {
|
|
10
|
+
mutationId: string;
|
|
11
|
+
baseVersion: number;
|
|
12
|
+
version: number;
|
|
13
|
+
after?: BoardDelta;
|
|
14
|
+
};
|
|
15
|
+
type BoardRemote = {
|
|
16
|
+
get(): Promise<BoardReadResult>;
|
|
17
|
+
apply(patch: BoardPatch, options: {
|
|
18
|
+
mutationId: string;
|
|
19
|
+
clientId?: string;
|
|
20
|
+
}): Promise<BoardApplyResult>;
|
|
21
|
+
subscribe?(handlers: {
|
|
22
|
+
changed?: (event: {
|
|
23
|
+
payload: BoardChangedPayload;
|
|
24
|
+
}) => void;
|
|
25
|
+
playback?: (event: {
|
|
26
|
+
payload: {
|
|
27
|
+
playback: BoardPlaybackSnapshot | null;
|
|
28
|
+
};
|
|
29
|
+
}) => void;
|
|
30
|
+
}): () => void;
|
|
31
|
+
};
|
|
32
|
+
type BoardReplicaStorage = {
|
|
33
|
+
listPending(): Promise<BoardPendingPatch[]>;
|
|
34
|
+
putPending(entry: BoardPendingPatch): Promise<void>;
|
|
35
|
+
deletePending(mutationId: string): Promise<void>;
|
|
36
|
+
readDocument(): Promise<{
|
|
37
|
+
version: number;
|
|
38
|
+
document: Record<string, unknown>;
|
|
39
|
+
} | null>;
|
|
40
|
+
writeDocument(version: number, document: BoardDocument): Promise<void>;
|
|
41
|
+
};
|
|
42
|
+
type BoardReplicaState = {
|
|
43
|
+
document: BoardDocument | null;
|
|
44
|
+
version: number;
|
|
45
|
+
title: string | null;
|
|
46
|
+
playback: BoardPlaybackSnapshot | null;
|
|
47
|
+
loaded: boolean;
|
|
48
|
+
pending: number;
|
|
49
|
+
error: string | null;
|
|
50
|
+
};
|
|
51
|
+
type BoardReplicaOptions = {
|
|
52
|
+
remote: BoardRemote;
|
|
53
|
+
storage?: BoardReplicaStorage;
|
|
54
|
+
clientId?: string;
|
|
55
|
+
persistIntervalMs?: number;
|
|
56
|
+
};
|
|
57
|
+
declare function createBoardReplica(options: BoardReplicaOptions): {
|
|
58
|
+
readonly state: BoardReplicaState;
|
|
59
|
+
subscribe(listener: (state: BoardReplicaState) => void): () => void;
|
|
60
|
+
start(): Promise<void>;
|
|
61
|
+
apply(patch: BoardPatch): Promise<void>;
|
|
62
|
+
receive: (payload: BoardChangedPayload) => void;
|
|
63
|
+
receivePlayback: (next: BoardPlaybackSnapshot | null) => void;
|
|
64
|
+
refresh: () => Promise<void>;
|
|
65
|
+
retry(): Promise<void>;
|
|
66
|
+
dispose(): void;
|
|
67
|
+
};
|
|
68
|
+
type BoardReplica = ReturnType<typeof createBoardReplica>;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { BoardChangedPayload, BoardPendingPatch, BoardRemote, BoardReplica, BoardReplicaOptions, BoardReplicaState, BoardReplicaStorage, createBoardReplica };
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { applyBoardDelta, applyBoardPatchToDocument, parseBoardDocument } from "../../protocol/dist/board-patch.js";
|
|
2
|
+
import { createBoardEntityId } from "../model/items/id.js";
|
|
3
|
+
//#region src/board/replica/index.ts
|
|
4
|
+
function memoryStorage() {
|
|
5
|
+
return {
|
|
6
|
+
listPending: async () => [],
|
|
7
|
+
putPending: async () => void 0,
|
|
8
|
+
deletePending: async () => void 0,
|
|
9
|
+
readDocument: async () => null,
|
|
10
|
+
writeDocument: async () => void 0
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const RETRY_BASE_MS = 1e3;
|
|
14
|
+
const RETRY_MAX_MS = 3e4;
|
|
15
|
+
const PERSIST_INTERVAL_MS = 500;
|
|
16
|
+
function isRejection(error) {
|
|
17
|
+
const status = error.status;
|
|
18
|
+
return typeof status === "number" && status >= 400 && status < 500 && ![
|
|
19
|
+
401,
|
|
20
|
+
403,
|
|
21
|
+
408,
|
|
22
|
+
429
|
|
23
|
+
].includes(status);
|
|
24
|
+
}
|
|
25
|
+
function errorMessage(error) {
|
|
26
|
+
return error instanceof Error ? error.message : "Board sync failed.";
|
|
27
|
+
}
|
|
28
|
+
function createBoardReplica(options) {
|
|
29
|
+
const remote = options.remote;
|
|
30
|
+
const storage = options.storage ?? memoryStorage();
|
|
31
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
32
|
+
let unsubscribeRemote = null;
|
|
33
|
+
let server = null;
|
|
34
|
+
let version = 0;
|
|
35
|
+
let title = null;
|
|
36
|
+
let playback = null;
|
|
37
|
+
let playbackRevision = 0;
|
|
38
|
+
let loaded = false;
|
|
39
|
+
let readError = null;
|
|
40
|
+
let writeError = null;
|
|
41
|
+
let pending = [];
|
|
42
|
+
let document = null;
|
|
43
|
+
let sending = false;
|
|
44
|
+
let refreshing = null;
|
|
45
|
+
let refreshAgain = false;
|
|
46
|
+
let retryTimer = null;
|
|
47
|
+
let retryDelay = RETRY_BASE_MS;
|
|
48
|
+
let disposed = false;
|
|
49
|
+
let unpersisted = null;
|
|
50
|
+
let persistTimer = null;
|
|
51
|
+
function subscribeRemote() {
|
|
52
|
+
if (!remote.subscribe) return null;
|
|
53
|
+
try {
|
|
54
|
+
return remote.subscribe({
|
|
55
|
+
changed: (event) => receive(event.payload),
|
|
56
|
+
playback: (event) => receivePlayback(event.payload.playback)
|
|
57
|
+
});
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function recompute() {
|
|
63
|
+
if (!server) {
|
|
64
|
+
document = null;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
let next = server;
|
|
68
|
+
for (const entry of pending) {
|
|
69
|
+
const result = applyBoardPatchToDocument(next, entry.patch);
|
|
70
|
+
if (result.ok) next = result.document;
|
|
71
|
+
}
|
|
72
|
+
document = next;
|
|
73
|
+
}
|
|
74
|
+
function snapshot() {
|
|
75
|
+
return {
|
|
76
|
+
document,
|
|
77
|
+
version,
|
|
78
|
+
title,
|
|
79
|
+
playback,
|
|
80
|
+
loaded,
|
|
81
|
+
pending: pending.length,
|
|
82
|
+
error: writeError ?? readError
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
let current = snapshot();
|
|
86
|
+
function emit() {
|
|
87
|
+
if (disposed) return;
|
|
88
|
+
current = snapshot();
|
|
89
|
+
for (const listener of [...listeners]) listener(current);
|
|
90
|
+
}
|
|
91
|
+
function adoptServer(next, nextVersion, persist) {
|
|
92
|
+
server = next;
|
|
93
|
+
version = nextVersion;
|
|
94
|
+
recompute();
|
|
95
|
+
if (persist) schedulePersist(nextVersion, next);
|
|
96
|
+
}
|
|
97
|
+
function schedulePersist(nextVersion, next) {
|
|
98
|
+
unpersisted = {
|
|
99
|
+
version: nextVersion,
|
|
100
|
+
document: next
|
|
101
|
+
};
|
|
102
|
+
persistTimer ??= setTimeout(persistNow, options.persistIntervalMs ?? PERSIST_INTERVAL_MS);
|
|
103
|
+
}
|
|
104
|
+
function persistNow() {
|
|
105
|
+
if (persistTimer) clearTimeout(persistTimer);
|
|
106
|
+
persistTimer = null;
|
|
107
|
+
const entry = unpersisted;
|
|
108
|
+
unpersisted = null;
|
|
109
|
+
if (entry) storage.writeDocument(entry.version, entry.document).catch(() => void 0);
|
|
110
|
+
}
|
|
111
|
+
function adoptRead(result, readPlaybackRevision) {
|
|
112
|
+
const parsed = parseBoardDocument({
|
|
113
|
+
board: result.board,
|
|
114
|
+
items: result.items ?? {},
|
|
115
|
+
animations: result.animations ?? {}
|
|
116
|
+
});
|
|
117
|
+
if (!parsed.ok) throw new Error(`Board ${result.id} could not be read.`);
|
|
118
|
+
title = result.title;
|
|
119
|
+
if (readPlaybackRevision === playbackRevision) playback = result.playback;
|
|
120
|
+
loaded = true;
|
|
121
|
+
adoptServer(parsed.document, result.version, true);
|
|
122
|
+
}
|
|
123
|
+
function refresh() {
|
|
124
|
+
if (refreshing) {
|
|
125
|
+
refreshAgain = true;
|
|
126
|
+
return refreshing;
|
|
127
|
+
}
|
|
128
|
+
refreshing = (async () => {
|
|
129
|
+
try {
|
|
130
|
+
do {
|
|
131
|
+
refreshAgain = false;
|
|
132
|
+
const readPlaybackRevision = playbackRevision;
|
|
133
|
+
const result = await remote.get();
|
|
134
|
+
if (disposed) return;
|
|
135
|
+
if (result.version >= version || !loaded) adoptRead(result, readPlaybackRevision);
|
|
136
|
+
readError = null;
|
|
137
|
+
emit();
|
|
138
|
+
} while (refreshAgain && !disposed);
|
|
139
|
+
} catch (cause) {
|
|
140
|
+
readError = errorMessage(cause);
|
|
141
|
+
emit();
|
|
142
|
+
} finally {
|
|
143
|
+
refreshing = null;
|
|
144
|
+
}
|
|
145
|
+
})();
|
|
146
|
+
return refreshing;
|
|
147
|
+
}
|
|
148
|
+
function scheduleRetry() {
|
|
149
|
+
if (retryTimer || disposed) return;
|
|
150
|
+
retryTimer = setTimeout(() => {
|
|
151
|
+
retryTimer = null;
|
|
152
|
+
flush();
|
|
153
|
+
}, retryDelay);
|
|
154
|
+
retryDelay = Math.min(RETRY_MAX_MS, retryDelay * 2);
|
|
155
|
+
}
|
|
156
|
+
async function flush() {
|
|
157
|
+
if (sending || disposed || !loaded) return;
|
|
158
|
+
sending = true;
|
|
159
|
+
try {
|
|
160
|
+
while (pending.length && !disposed) {
|
|
161
|
+
const entry = pending[0];
|
|
162
|
+
try {
|
|
163
|
+
const result = await remote.apply(entry.patch, {
|
|
164
|
+
mutationId: entry.mutationId,
|
|
165
|
+
...options.clientId ? { clientId: options.clientId } : {}
|
|
166
|
+
});
|
|
167
|
+
const stillPending = pending.some((item) => item.mutationId === entry.mutationId);
|
|
168
|
+
pending = pending.filter((item) => item.mutationId !== entry.mutationId);
|
|
169
|
+
storage.deletePending(entry.mutationId).catch(() => void 0);
|
|
170
|
+
if (stillPending && server && !result.replayed && result.status === "applied" && result.version === version + 1) {
|
|
171
|
+
const next = applyBoardPatchToDocument(server, entry.patch);
|
|
172
|
+
if (next.ok) adoptServer(next.document, result.version, true);
|
|
173
|
+
else refresh();
|
|
174
|
+
} else if (result.version > version) refresh();
|
|
175
|
+
else recompute();
|
|
176
|
+
retryDelay = RETRY_BASE_MS;
|
|
177
|
+
writeError = null;
|
|
178
|
+
emit();
|
|
179
|
+
} catch (cause) {
|
|
180
|
+
if (isRejection(cause)) {
|
|
181
|
+
pending = pending.filter((item) => item.mutationId !== entry.mutationId);
|
|
182
|
+
storage.deletePending(entry.mutationId).catch(() => void 0);
|
|
183
|
+
writeError = errorMessage(cause);
|
|
184
|
+
recompute();
|
|
185
|
+
emit();
|
|
186
|
+
refresh();
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
writeError = errorMessage(cause);
|
|
190
|
+
emit();
|
|
191
|
+
scheduleRetry();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} finally {
|
|
196
|
+
sending = false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function receive(payload) {
|
|
200
|
+
if (!loaded) {
|
|
201
|
+
refresh();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (payload.version <= version) return;
|
|
205
|
+
if (payload.baseVersion !== version || !payload.after || !server) {
|
|
206
|
+
refresh();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
adoptServer(applyBoardDelta(server, payload.after), payload.version, true);
|
|
210
|
+
if (pending.some((entry) => entry.mutationId === payload.mutationId)) {
|
|
211
|
+
pending = pending.filter((entry) => entry.mutationId !== payload.mutationId);
|
|
212
|
+
storage.deletePending(payload.mutationId).catch(() => void 0);
|
|
213
|
+
recompute();
|
|
214
|
+
}
|
|
215
|
+
emit();
|
|
216
|
+
}
|
|
217
|
+
function receivePlayback(next) {
|
|
218
|
+
playbackRevision += 1;
|
|
219
|
+
playback = next;
|
|
220
|
+
emit();
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
get state() {
|
|
224
|
+
return current;
|
|
225
|
+
},
|
|
226
|
+
subscribe(listener) {
|
|
227
|
+
listeners.add(listener);
|
|
228
|
+
return () => listeners.delete(listener);
|
|
229
|
+
},
|
|
230
|
+
async start() {
|
|
231
|
+
unsubscribeRemote ??= subscribeRemote();
|
|
232
|
+
const [cached, stored] = await Promise.all([storage.readDocument().catch(() => null), storage.listPending().catch(() => [])]);
|
|
233
|
+
if (disposed) return;
|
|
234
|
+
pending = stored;
|
|
235
|
+
if (cached && !server) {
|
|
236
|
+
const parsed = parseBoardDocument(cached.document);
|
|
237
|
+
if (parsed.ok) adoptServer(parsed.document, cached.version, false);
|
|
238
|
+
}
|
|
239
|
+
emit();
|
|
240
|
+
await refresh();
|
|
241
|
+
await flush();
|
|
242
|
+
},
|
|
243
|
+
apply(patch) {
|
|
244
|
+
const entry = {
|
|
245
|
+
mutationId: createBoardEntityId(),
|
|
246
|
+
patch
|
|
247
|
+
};
|
|
248
|
+
pending = [...pending, entry];
|
|
249
|
+
recompute();
|
|
250
|
+
emit();
|
|
251
|
+
return storage.putPending(entry).catch(() => void 0).then(() => flush());
|
|
252
|
+
},
|
|
253
|
+
receive,
|
|
254
|
+
receivePlayback,
|
|
255
|
+
refresh,
|
|
256
|
+
retry() {
|
|
257
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
258
|
+
retryTimer = null;
|
|
259
|
+
retryDelay = RETRY_BASE_MS;
|
|
260
|
+
readError = null;
|
|
261
|
+
writeError = null;
|
|
262
|
+
emit();
|
|
263
|
+
return refresh().then(flush);
|
|
264
|
+
},
|
|
265
|
+
dispose() {
|
|
266
|
+
disposed = true;
|
|
267
|
+
unsubscribeRemote?.();
|
|
268
|
+
unsubscribeRemote = null;
|
|
269
|
+
listeners.clear();
|
|
270
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
271
|
+
persistNow();
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
export { createBoardReplica };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BoardSceneItem } from "../model/scene.js";
|
|
2
|
+
import "../model/index.js";
|
|
3
|
+
import { LruBudget } from "./lru.js";
|
|
4
|
+
import { BoardImageTexturePool } from "./texture-pool.js";
|
|
5
|
+
import { Texture } from "pixi.js";
|
|
6
|
+
//#region src/board/stage/assets.d.ts
|
|
7
|
+
type BoardAssetMedia = "image" | "video";
|
|
8
|
+
declare function boardAssetKey(item: BoardSceneItem): string | null;
|
|
9
|
+
type BoardAssetManager = {
|
|
10
|
+
assetKey: (item: BoardSceneItem) => string | null;
|
|
11
|
+
invalidatePath: (path: string) => void;
|
|
12
|
+
requestItem: (item: BoardSceneItem) => void;
|
|
13
|
+
getTexture: (key: string) => Texture | null;
|
|
14
|
+
getNaturalSize: (key: string) => {
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
} | null;
|
|
18
|
+
hasError: (key: string) => boolean;
|
|
19
|
+
acquire: (key: string) => void;
|
|
20
|
+
release: (key: string) => void;
|
|
21
|
+
withTextures: <T>(items: BoardSceneItem[], use: (textures: Map<string, Texture>) => T | Promise<T>, options?: {
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
}) => Promise<T>;
|
|
24
|
+
subscribe: (listener: () => void) => () => void;
|
|
25
|
+
destroy: () => void;
|
|
26
|
+
};
|
|
27
|
+
type BoardAssetManagerOptions = {
|
|
28
|
+
/** Resolve a Space file path to a URL a texture can load from; null when unavailable. */
|
|
29
|
+
resolveFileUrl: (path: string) => Promise<string | null>;
|
|
30
|
+
concurrency?: number;
|
|
31
|
+
videoConcurrency?: number;
|
|
32
|
+
loadVideoPreviews?: boolean;
|
|
33
|
+
lruBudget?: LruBudget;
|
|
34
|
+
imageTexturePool?: BoardImageTexturePool;
|
|
35
|
+
loadTexture?: (url: string, media: BoardAssetMedia) => Promise<Texture | null>;
|
|
36
|
+
unloadTexture?: (url: string, texture: Texture | null, media: BoardAssetMedia) => Promise<void>;
|
|
37
|
+
now?: () => number;
|
|
38
|
+
};
|
|
39
|
+
declare function createBoardAssetManager(options: BoardAssetManagerOptions): BoardAssetManager;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { BoardAssetManager, BoardAssetManagerOptions, boardAssetKey, createBoardAssetManager };
|