@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.
Files changed (240) hide show
  1. package/README.md +108 -102
  2. package/dist/board/editor/align.d.ts +11 -0
  3. package/dist/board/editor/align.js +72 -0
  4. package/dist/board/editor/animation.d.ts +17 -0
  5. package/dist/board/editor/animation.js +187 -0
  6. package/dist/board/editor/camera.d.ts +32 -0
  7. package/dist/board/editor/camera.js +137 -0
  8. package/dist/board/editor/clipboard.d.ts +41 -0
  9. package/dist/board/editor/clipboard.js +129 -0
  10. package/dist/board/editor/connection-ports.d.ts +19 -0
  11. package/dist/board/editor/connection-ports.js +85 -0
  12. package/dist/board/editor/context.d.ts +111 -0
  13. package/dist/board/editor/context.js +30 -0
  14. package/dist/board/editor/copy.d.ts +17 -0
  15. package/dist/board/editor/copy.js +121 -0
  16. package/dist/board/editor/creation.d.ts +51 -0
  17. package/dist/board/editor/creation.js +225 -0
  18. package/dist/board/editor/deletion.d.ts +9 -0
  19. package/dist/board/editor/deletion.js +90 -0
  20. package/dist/board/editor/document-edits.d.ts +22 -0
  21. package/dist/board/editor/document-edits.js +193 -0
  22. package/dist/board/editor/document.d.ts +41 -0
  23. package/dist/board/editor/document.js +302 -0
  24. package/dist/board/editor/editor.d.ts +225 -0
  25. package/dist/board/editor/editor.js +439 -0
  26. package/dist/board/editor/index.d.ts +18 -0
  27. package/dist/board/editor/index.js +12 -0
  28. package/dist/board/editor/interaction.d.ts +115 -0
  29. package/dist/board/editor/interaction.js +584 -0
  30. package/dist/board/editor/query.d.ts +27 -0
  31. package/dist/board/editor/query.js +203 -0
  32. package/dist/board/editor/remote.d.ts +11 -0
  33. package/dist/board/editor/remote.js +55 -0
  34. package/dist/board/editor/selection-transform.d.ts +24 -0
  35. package/dist/board/editor/selection-transform.js +81 -0
  36. package/dist/board/editor/selection.d.ts +33 -0
  37. package/dist/board/editor/selection.js +197 -0
  38. package/dist/board/editor/snapping.d.ts +33 -0
  39. package/dist/board/editor/snapping.js +75 -0
  40. package/dist/board/editor/snapshots.d.ts +27 -0
  41. package/dist/board/editor/snapshots.js +132 -0
  42. package/dist/board/editor/spatial.d.ts +41 -0
  43. package/dist/board/editor/spatial.js +191 -0
  44. package/dist/board/editor/state.d.ts +12 -0
  45. package/dist/board/editor/state.js +49 -0
  46. package/dist/board/editor/tool.d.ts +10 -0
  47. package/dist/board/editor/tool.js +19 -0
  48. package/dist/board/editor/wheel.d.ts +5 -0
  49. package/dist/board/editor/wheel.js +17 -0
  50. package/dist/board/export/index.d.ts +28 -6
  51. package/dist/board/export/index.js +47 -13
  52. package/dist/board/export/node/index.d.ts +56 -0
  53. package/dist/board/{headless → export/node}/index.js +26 -21
  54. package/dist/board/export/node/scripts.d.ts +16 -0
  55. package/dist/board/export/node/scripts.js +258 -0
  56. package/dist/board/export/scene.d.ts +8 -5
  57. package/dist/board/export/scene.js +24 -9
  58. package/dist/board/{animation.d.ts → model/animation.d.ts} +8 -5
  59. package/dist/board/{animation.js → model/animation.js} +37 -10
  60. package/dist/board/{core → model}/arrow-geometry.d.ts +2 -2
  61. package/dist/board/{core → model}/arrow-geometry.js +2 -2
  62. package/dist/board/model/authoring.d.ts +20 -0
  63. package/dist/board/model/authoring.js +185 -0
  64. package/dist/board/{core → model}/draw-geometry.d.ts +2 -2
  65. package/dist/board/{core → model}/draw-geometry.js +1 -1
  66. package/dist/board/model/draw-input.d.ts +14 -0
  67. package/dist/board/model/draw-input.js +28 -0
  68. package/dist/board/{core → model}/export-assets.d.ts +1 -1
  69. package/dist/board/{core → model}/export-assets.js +1 -1
  70. package/dist/board/{core → model}/export-plan.d.ts +2 -2
  71. package/dist/board/{core → model}/export-plan.js +3 -2
  72. package/dist/board/{core → model}/file-preview.d.ts +1 -1
  73. package/dist/board/{core → model}/file-preview.js +2 -2
  74. package/dist/board/{core → model}/file-snapshot.d.ts +1 -1
  75. package/dist/board/{core → model}/file-snapshot.js +4 -3
  76. package/dist/board/{geometry.d.ts → model/geometry.d.ts} +3 -3
  77. package/dist/board/{geometry.js → model/geometry.js} +1 -1
  78. package/dist/board/{image-key.d.ts → model/image-key.d.ts} +2 -2
  79. package/dist/board/{image-key.js → model/image-key.js} +7 -3
  80. package/dist/board/model/index.d.ts +42 -0
  81. package/dist/board/model/index.js +39 -0
  82. package/dist/board/model/items/builtin.d.ts +5 -0
  83. package/dist/board/model/items/builtin.js +119 -0
  84. package/dist/board/model/items/create.d.ts +61 -0
  85. package/dist/board/model/items/create.js +272 -0
  86. package/dist/board/model/items/definition.d.ts +35 -0
  87. package/dist/board/model/items/definition.js +18 -0
  88. package/dist/board/model/items/id.d.ts +5 -0
  89. package/dist/board/model/items/id.js +11 -0
  90. package/dist/board/model/items/index.d.ts +6 -0
  91. package/dist/board/model/items/index.js +6 -0
  92. package/dist/board/model/items/registry.d.ts +35 -0
  93. package/dist/board/model/items/registry.js +84 -0
  94. package/dist/board/{media-playback.d.ts → model/media-playback.d.ts} +4 -3
  95. package/dist/board/{media-playback.js → model/media-playback.js} +15 -5
  96. package/dist/board/{media.d.ts → model/media.d.ts} +1 -1
  97. package/dist/board/{media.js → model/media.js} +8 -2
  98. package/dist/board/{core → model}/palette.d.ts +1 -1
  99. package/dist/board/{core → model}/palette.js +1 -1
  100. package/dist/board/{presets.d.ts → model/presets.d.ts} +3 -3
  101. package/dist/board/{presets.js → model/presets.js} +1 -1
  102. package/dist/board/{replay.d.ts → model/replay.d.ts} +5 -5
  103. package/dist/board/{replay.js → model/replay.js} +2 -2
  104. package/dist/board/model/sampling.d.ts +19 -0
  105. package/dist/board/model/sampling.js +96 -0
  106. package/dist/board/{core → model}/scene.d.ts +1 -1
  107. package/dist/board/{core → model}/scene.js +1 -1
  108. package/dist/board/model/script-example.d.ts +4 -0
  109. package/dist/board/model/script-example.js +21 -0
  110. package/dist/board/model/script-hit.d.ts +10 -0
  111. package/dist/board/model/script-hit.js +52 -0
  112. package/dist/board/model/script-index.d.ts +7 -0
  113. package/dist/board/model/script-index.js +15 -0
  114. package/dist/board/model/script-scene.d.ts +24 -0
  115. package/dist/board/model/script-scene.js +267 -0
  116. package/dist/board/model/script.d.ts +105 -0
  117. package/dist/board/model/script.js +1 -0
  118. package/dist/board/model/shape-types.d.ts +8 -0
  119. package/dist/board/model/shape-types.js +8 -0
  120. package/dist/board/{task.d.ts → model/task.d.ts} +4 -4
  121. package/dist/board/{task.js → model/task.js} +3 -3
  122. package/dist/board/{core → model}/text-metrics.d.ts +1 -1
  123. package/dist/board/{core → model}/text-metrics.js +1 -1
  124. package/dist/board/{core → model}/tool-styles.d.ts +1 -1
  125. package/dist/board/{core → model}/tool-styles.js +1 -1
  126. package/dist/board/{runtime/board-player.d.ts → player/index.d.ts} +2 -2
  127. package/dist/board/{runtime/board-player.js → player/index.js} +5 -4
  128. package/dist/board/render/board-background.d.ts +1 -1
  129. package/dist/board/render/clip.d.ts +1 -1
  130. package/dist/board/render/css-color.d.ts +6 -1
  131. package/dist/board/render/css-color.js +34 -20
  132. package/dist/board/render/diagnostics.d.ts +20 -0
  133. package/dist/board/render/diagnostics.js +117 -0
  134. package/dist/board/render/index.d.ts +9 -6
  135. package/dist/board/render/index.js +10 -7
  136. package/dist/board/render/media-interaction.d.ts +2 -2
  137. package/dist/board/render/media-interaction.js +1 -1
  138. package/dist/board/render/palette.d.ts +3 -2
  139. package/dist/board/render/palette.js +7 -3
  140. package/dist/board/render/renderers/arrow-card-renderer.js +4 -3
  141. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  142. package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -10
  143. package/dist/board/render/renderers/board-renderer-registry.js +17 -17
  144. package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
  145. package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +16 -15
  146. package/dist/board/render/renderers/draw-card-renderer.js +4 -3
  147. package/dist/board/render/renderers/effect-card-renderer.js +22 -5
  148. package/dist/board/render/renderers/far-plate.js +1 -1
  149. package/dist/board/render/renderers/file-card-renderer.d.ts +1 -1
  150. package/dist/board/render/renderers/file-card-renderer.js +2 -2
  151. package/dist/board/render/renderers/frame-card-renderer.js +30 -14
  152. package/dist/board/render/renderers/image-card-renderer.d.ts +1 -1
  153. package/dist/board/render/renderers/image-card-renderer.js +43 -4
  154. package/dist/board/render/renderers/shape-card-renderer.js +11 -28
  155. package/dist/board/render/renderers/task-card-renderer.js +1 -1
  156. package/dist/board/render/renderers/text-card-renderer.js +6 -4
  157. package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
  158. package/dist/board/render/renderers/video-card-renderer.d.ts +1 -1
  159. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  160. package/dist/board/render/sampling.d.ts +22 -0
  161. package/dist/board/render/sampling.js +21 -0
  162. package/dist/board/render/script-export.d.ts +11 -0
  163. package/dist/board/render/script-export.js +86 -0
  164. package/dist/board/render/script-host.d.ts +31 -0
  165. package/dist/board/render/script-host.js +421 -0
  166. package/dist/board/render/script-modules.d.ts +2 -0
  167. package/dist/board/render/script-modules.js +2 -0
  168. package/dist/board/render/script-runtime.d.ts +29 -0
  169. package/dist/board/render/script-runtime.js +213 -0
  170. package/dist/board/render/script-sandbox.d.ts +4 -0
  171. package/dist/board/render/script-sandbox.js +173 -0
  172. package/dist/board/render/stroke.d.ts +3 -2
  173. package/dist/board/render/stroke.js +23 -2
  174. package/dist/board/render/text-measurement.js +2 -2
  175. package/dist/board/replica/index.d.ts +70 -0
  176. package/dist/board/replica/index.js +276 -0
  177. package/dist/board/stage/assets.d.ts +41 -0
  178. package/dist/board/stage/assets.js +355 -0
  179. package/dist/board/stage/index.d.ts +8 -0
  180. package/dist/board/stage/index.js +8 -0
  181. package/dist/board/stage/lru.d.ts +32 -0
  182. package/dist/board/stage/lru.js +29 -0
  183. package/dist/board/stage/maintenance.d.ts +5 -0
  184. package/dist/board/stage/maintenance.js +11 -0
  185. package/dist/board/stage/pointer.d.ts +8 -0
  186. package/dist/board/stage/pointer.js +33 -0
  187. package/dist/board/stage/scene.d.ts +85 -0
  188. package/dist/board/stage/scene.js +515 -0
  189. package/dist/board/stage/script-pointer.d.ts +19 -0
  190. package/dist/board/stage/script-pointer.js +35 -0
  191. package/dist/board/stage/stage.d.ts +76 -0
  192. package/dist/board/stage/stage.js +774 -0
  193. package/dist/board/stage/texture-pool.d.ts +24 -0
  194. package/dist/board/stage/texture-pool.js +153 -0
  195. package/dist/board/stage/views.d.ts +38 -0
  196. package/dist/board/stage/views.js +93 -0
  197. package/dist/chunks/environment.d.ts +289 -23
  198. package/dist/chunks/host-bridge.d.ts +2 -2
  199. package/dist/chunks/http.d.ts +185 -71
  200. package/dist/chunks/http.js +368 -269
  201. package/dist/chunks/transport.js +3 -1
  202. package/dist/chunks/websocket.d.ts +9 -1
  203. package/dist/chunks/websocket.js +16 -2
  204. package/dist/host-bridge.js +2 -1
  205. package/dist/http.d.ts +2 -2
  206. package/dist/index.d.ts +182 -4
  207. package/dist/index.js +1393 -129
  208. package/dist/media.d.ts +1 -10
  209. package/dist/media.js +16 -27
  210. package/dist/protocol/dist/board-assets.d.ts +1 -0
  211. package/dist/protocol/dist/board-constants.js +2 -2
  212. package/dist/protocol/dist/board-model.d.ts +201 -11
  213. package/dist/protocol/dist/board-model.js +33 -14
  214. package/dist/protocol/dist/board-patch.js +3 -2
  215. package/dist/protocol/dist/board-schema.d.ts +4 -2
  216. package/dist/protocol/dist/board-schema.js +18 -4
  217. package/dist/protocol/dist/board-script-modules.d.ts +25 -0
  218. package/dist/protocol/dist/board-script-modules.js +127 -0
  219. package/dist/protocol/dist/board.d.ts +28 -2
  220. package/dist/protocol/dist/board.js +3 -1
  221. package/dist/protocol/dist/core/media.d.ts +5 -0
  222. package/dist/protocol/dist/core/media.js +39 -0
  223. package/dist/protocol/dist/display/index.d.ts +1 -0
  224. package/dist/protocol/dist/index.d.ts +7 -5
  225. package/dist/protocol/dist/realtime/types.d.ts +1 -0
  226. package/dist/protocol/dist/rtc/index.d.ts +1 -0
  227. package/dist/types.d.ts +1 -1
  228. package/package.json +21 -8
  229. package/dist/board/core/shape-definition.d.ts +0 -23
  230. package/dist/board/core/shape-definition.js +0 -45
  231. package/dist/board/core/shape-types.d.ts +0 -38
  232. package/dist/board/core/shape-types.js +0 -22
  233. package/dist/board/headless/index.d.ts +0 -55
  234. package/dist/board/headless/sketch.d.ts +0 -19
  235. package/dist/board/headless/sketch.js +0 -146
  236. package/dist/board/index.d.ts +0 -30
  237. package/dist/board/index.js +0 -29
  238. package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
  239. package/dist/board/render/sketch-host.d.ts +0 -14
  240. 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 "../core/text-metrics.js";
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 };