@neta-art/cohub 10.0.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +11 -2
  2. package/dist/board/editor/animation.d.ts +3 -0
  3. package/dist/board/editor/animation.js +16 -1
  4. package/dist/board/editor/clipboard.js +7 -0
  5. package/dist/board/editor/copy.js +23 -18
  6. package/dist/board/editor/creation.d.ts +5 -1
  7. package/dist/board/editor/creation.js +2 -1
  8. package/dist/board/editor/deletion.js +3 -3
  9. package/dist/board/editor/document.d.ts +8 -2
  10. package/dist/board/editor/document.js +54 -7
  11. package/dist/board/editor/editor.d.ts +18 -3
  12. package/dist/board/editor/editor.js +11 -1
  13. package/dist/board/editor/query.d.ts +5 -5
  14. package/dist/board/editor/query.js +14 -12
  15. package/dist/board/editor/selection.js +2 -1
  16. package/dist/board/export/index.d.ts +25 -4
  17. package/dist/board/export/index.js +45 -12
  18. package/dist/board/export/node/index.d.ts +5 -4
  19. package/dist/board/export/node/index.js +16 -11
  20. package/dist/board/export/node/scripts.d.ts +16 -0
  21. package/dist/board/export/node/scripts.js +258 -0
  22. package/dist/board/export/scene.d.ts +2 -1
  23. package/dist/board/export/scene.js +20 -6
  24. package/dist/board/model/animation.d.ts +4 -1
  25. package/dist/board/model/animation.js +34 -7
  26. package/dist/board/model/authoring.d.ts +20 -0
  27. package/dist/board/model/authoring.js +185 -0
  28. package/dist/board/model/export-plan.js +1 -0
  29. package/dist/board/model/file-preview.js +1 -1
  30. package/dist/board/model/file-snapshot.js +3 -2
  31. package/dist/board/model/image-key.js +6 -2
  32. package/dist/board/model/index.d.ts +13 -6
  33. package/dist/board/model/index.js +14 -8
  34. package/dist/board/model/items/builtin.js +2 -2
  35. package/dist/board/model/items/create.d.ts +4 -2
  36. package/dist/board/model/items/create.js +6 -5
  37. package/dist/board/model/items/index.d.ts +2 -2
  38. package/dist/board/model/items/index.js +2 -2
  39. package/dist/board/model/media-playback.d.ts +2 -1
  40. package/dist/board/model/media-playback.js +14 -4
  41. package/dist/board/model/media.js +7 -1
  42. package/dist/board/model/sampling.d.ts +19 -0
  43. package/dist/board/model/sampling.js +96 -0
  44. package/dist/board/model/script-example.d.ts +4 -0
  45. package/dist/board/model/script-example.js +21 -0
  46. package/dist/board/model/script-hit.d.ts +10 -0
  47. package/dist/board/model/script-hit.js +52 -0
  48. package/dist/board/model/script-index.d.ts +7 -0
  49. package/dist/board/model/script-index.js +15 -0
  50. package/dist/board/model/script-scene.d.ts +24 -0
  51. package/dist/board/model/script-scene.js +267 -0
  52. package/dist/board/model/script.d.ts +105 -0
  53. package/dist/board/model/script.js +1 -0
  54. package/dist/board/render/css-color.d.ts +6 -1
  55. package/dist/board/render/css-color.js +34 -20
  56. package/dist/board/render/diagnostics.d.ts +20 -0
  57. package/dist/board/render/diagnostics.js +117 -0
  58. package/dist/board/render/index.d.ts +9 -6
  59. package/dist/board/render/index.js +9 -6
  60. package/dist/board/render/palette.d.ts +2 -1
  61. package/dist/board/render/palette.js +6 -2
  62. package/dist/board/render/renderers/arrow-card-renderer.js +3 -2
  63. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  64. package/dist/board/render/renderers/board-renderer-registry.d.ts +10 -5
  65. package/dist/board/render/renderers/board-renderer-registry.js +3 -3
  66. package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
  67. package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +16 -15
  68. package/dist/board/render/renderers/draw-card-renderer.js +3 -2
  69. package/dist/board/render/renderers/effect-card-renderer.js +22 -5
  70. package/dist/board/render/renderers/frame-card-renderer.js +30 -14
  71. package/dist/board/render/renderers/image-card-renderer.js +43 -4
  72. package/dist/board/render/renderers/shape-card-renderer.js +10 -27
  73. package/dist/board/render/renderers/text-card-renderer.js +5 -3
  74. package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
  75. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  76. package/dist/board/render/sampling.d.ts +22 -0
  77. package/dist/board/render/sampling.js +21 -0
  78. package/dist/board/render/script-export.d.ts +11 -0
  79. package/dist/board/render/script-export.js +86 -0
  80. package/dist/board/render/script-host.d.ts +31 -0
  81. package/dist/board/render/script-host.js +421 -0
  82. package/dist/board/render/script-modules.d.ts +2 -0
  83. package/dist/board/render/script-modules.js +2 -0
  84. package/dist/board/render/script-runtime.d.ts +29 -0
  85. package/dist/board/render/script-runtime.js +213 -0
  86. package/dist/board/render/script-sandbox.d.ts +4 -0
  87. package/dist/board/render/script-sandbox.js +173 -0
  88. package/dist/board/render/stroke.d.ts +2 -1
  89. package/dist/board/render/stroke.js +22 -1
  90. package/dist/board/render/text-measurement.js +1 -1
  91. package/dist/board/stage/assets.js +6 -1
  92. package/dist/board/stage/maintenance.d.ts +5 -0
  93. package/dist/board/stage/maintenance.js +11 -0
  94. package/dist/board/stage/scene.js +5 -2
  95. package/dist/board/stage/script-pointer.d.ts +19 -0
  96. package/dist/board/stage/script-pointer.js +35 -0
  97. package/dist/board/stage/stage.d.ts +10 -0
  98. package/dist/board/stage/stage.js +249 -43
  99. package/dist/board/stage/texture-pool.js +2 -3
  100. package/dist/chunks/environment.d.ts +266 -23
  101. package/dist/chunks/host-bridge.d.ts +2 -2
  102. package/dist/chunks/http.d.ts +88 -7
  103. package/dist/chunks/http.js +155 -63
  104. package/dist/chunks/transport.js +3 -1
  105. package/dist/chunks/websocket.d.ts +9 -1
  106. package/dist/chunks/websocket.js +16 -2
  107. package/dist/host-bridge.js +2 -1
  108. package/dist/http.d.ts +2 -2
  109. package/dist/index.d.ts +182 -4
  110. package/dist/index.js +1374 -126
  111. package/dist/protocol/dist/board-assets.d.ts +1 -0
  112. package/dist/protocol/dist/board-constants.js +2 -2
  113. package/dist/protocol/dist/board-model.d.ts +201 -11
  114. package/dist/protocol/dist/board-model.js +33 -14
  115. package/dist/protocol/dist/board-patch.js +2 -1
  116. package/dist/protocol/dist/board-schema.d.ts +4 -2
  117. package/dist/protocol/dist/board-schema.js +18 -4
  118. package/dist/protocol/dist/board-script-modules.d.ts +25 -0
  119. package/dist/protocol/dist/board-script-modules.js +127 -0
  120. package/dist/protocol/dist/display/index.d.ts +1 -0
  121. package/dist/protocol/dist/index.d.ts +4 -2
  122. package/dist/protocol/dist/realtime/types.d.ts +1 -0
  123. package/dist/protocol/dist/rtc/index.d.ts +1 -0
  124. package/package.json +2 -1
  125. package/dist/board/export/node/sketch.d.ts +0 -19
  126. package/dist/board/export/node/sketch.js +0 -146
  127. package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
  128. package/dist/board/render/sketch-host.d.ts +0 -14
  129. package/dist/board/render/sketch-host.js +0 -216
@@ -1,18 +1,24 @@
1
- import { cameraForFocus, cameraForState, screenPoint, screenToWorld, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
1
+ import { applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
2
+ import { screenPoint, screenToWorld, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
3
+ import { boardScriptScope, composeBoardScriptItems, prepareBoardScriptOutput, validateBoardScriptCommit } from "../model/script-scene.js";
4
+ import { boardScriptTarget } from "../model/script-hit.js";
5
+ import { createBoardAnimationSampler, resolveBoardAnimationCamera } from "../model/sampling.js";
2
6
  import { resolveSceneArrow } from "../model/arrow-geometry.js";
3
7
  import { buildFallbackShapeColors, pickBoardColor, resolveItemColor } from "../model/palette.js";
4
8
  import { featuredTaskArtifact } from "../model/task.js";
5
9
  import { isCreationBoardTool } from "../editor/tool.js";
6
10
  import { resizeCursorForHandle } from "../editor/selection-transform.js";
7
11
  import { parseBoardCssColor } from "../render/css-color.js";
12
+ import { defaultBoardPalette, itemAlpha } from "../render/palette.js";
8
13
  import { createBoardBackground, updateBoardBackground } from "../render/board-background.js";
9
14
  import { getBoardResolution, textZoomBucket } from "../render/text-resolution.js";
10
- import { defaultBoardPalette } from "../render/palette.js";
11
15
  import { getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
12
- import { createBoardSketchHost } from "../render/sketch-host.js";
16
+ import { createBoardScriptHost } from "../render/script-host.js";
13
17
  import { createBoardPlayer } from "../player/index.js";
14
18
  import { toBoardPointerEvent } from "./pointer.js";
15
19
  import { createBoardScene } from "./scene.js";
20
+ import { scheduleBoardMaintenance } from "./maintenance.js";
21
+ import { createBoardScriptPointerRouter } from "./script-pointer.js";
16
22
  import { createBoardItemViewLayer } from "./views.js";
17
23
  import { Application, Container, Graphics, RendererType } from "pixi.js";
18
24
  //#region src/board/stage/stage.ts
@@ -45,7 +51,21 @@ function mountBoardStage(host, options) {
45
51
  let overlay = null;
46
52
  let background = null;
47
53
  let scene = null;
48
- let sketches = null;
54
+ let scripts = null;
55
+ let scriptMode = editor.readonly ? "interact" : "edit";
56
+ let scriptTime = 0;
57
+ let scriptLastTick = performance.now();
58
+ const scriptOutputs = /* @__PURE__ */ new Map();
59
+ const scriptListeners = /* @__PURE__ */ new Set();
60
+ let scriptVersion = 0;
61
+ let composedVersion = -1;
62
+ let composedZoom = 0;
63
+ let composedPlayhead = "";
64
+ let composedMoving = false;
65
+ let composedDocument = null;
66
+ let scriptHover = null;
67
+ const scriptPointers = createBoardScriptPointerRouter();
68
+ let stopMaintenance = null;
49
69
  let active = true;
50
70
  let disposed = false;
51
71
  let renderFrame = 0;
@@ -63,7 +83,10 @@ function mountBoardStage(host, options) {
63
83
  const views = createBoardItemViewLayer(host, editor, options.views ?? [], renderCamera);
64
84
  const player = createBoardPlayer({ resolveColor: (color) => {
65
85
  const current = theme();
66
- return resolveItemColor(color, "brand", current.colors, current.colorScheme, parseBoardCssColor);
86
+ return {
87
+ color: resolveItemColor(color, "brand", current.colors, current.colorScheme, parseBoardCssColor),
88
+ alpha: itemAlpha(current, color)
89
+ };
67
90
  } });
68
91
  let cull = {
69
92
  rect: null,
@@ -77,7 +100,7 @@ function mountBoardStage(host, options) {
77
100
  if (same && cull.scene === editor.scene) return cull.ids;
78
101
  cull = {
79
102
  rect: same ? cull.rect : rect,
80
- ids: new Set(editor.idsInRect(rect)),
103
+ ids: new Set(editor.idsInRect(rect, true)),
81
104
  scene: editor.scene
82
105
  };
83
106
  options.onVisibleChange?.(cull.ids);
@@ -112,7 +135,7 @@ function mountBoardStage(host, options) {
112
135
  settings: editor.settings,
113
136
  scene: renderScene,
114
137
  time,
115
- ...sketches ? { sketches } : {},
138
+ ...scripts ? { scripts } : {},
116
139
  selectedIds: new Set(editor.selection),
117
140
  hoveredId: editor.hoverId,
118
141
  resizingIds: interaction.type === "resizing" ? new Set(interaction.origin.keys()) : /* @__PURE__ */ new Set(),
@@ -175,28 +198,11 @@ function mountBoardStage(host, options) {
175
198
  return strokes;
176
199
  }
177
200
  function followCamera(frame, renderScene) {
178
- let camera = { ...editor.camera };
179
- const focus = frame.camera.focus;
180
- if (focus !== void 0 && surface.width > 0 && surface.height > 0) camera = cameraForFocus(focus, (id) => renderScene.get(id)?.frame, surface, { padding: 48 }) ?? camera;
181
- if (frame.camera.zoom !== void 0) camera = {
182
- ...camera,
183
- zoom: frame.camera.zoom
184
- };
185
- if (frame.camera.center) camera = cameraForState({
186
- centerX: frame.camera.center.x,
187
- centerY: frame.camera.center.y,
188
- zoom: camera.zoom
189
- }, surface);
190
- if (frame.camera.shake) {
191
- const t = frame.time / 1e3;
192
- camera = {
193
- ...camera,
194
- x: camera.x + Math.sin(t * 91.3) * frame.camera.shake,
195
- y: camera.y + Math.cos(t * 77.9) * frame.camera.shake
196
- };
197
- }
198
- return camera;
201
+ return resolveBoardAnimationCamera(frame.camera, (id) => renderScene.get(id)?.frame, surface, frame.time, editor.camera);
199
202
  }
203
+ let effectSampler;
204
+ let effectDocument;
205
+ let effectAnimation = null;
200
206
  function arrowHead(target, from, to, color, width, inv, alpha) {
201
207
  const angle = Math.atan2(to.y - from.y, to.x - from.x);
202
208
  const head = Math.max(14, 16 * inv);
@@ -248,6 +254,10 @@ function mountBoardStage(host, options) {
248
254
  }
249
255
  }
250
256
  function cursorFor() {
257
+ if (scriptMode === "interact" && scriptHover) {
258
+ const output = scriptOutputs.get(scriptHover.owner);
259
+ return output?.hits[scriptHover.target]?.cursor ?? (output?.listens.includes("click") ? "pointer" : "default");
260
+ }
251
261
  const interaction = editor.interaction;
252
262
  switch (interaction.type) {
253
263
  case "panning":
@@ -279,9 +289,63 @@ function mountBoardStage(host, options) {
279
289
  sync();
280
290
  });
281
291
  }
292
+ function createScripts() {
293
+ return createBoardScriptHost({
294
+ host,
295
+ capabilities: {
296
+ ...options.scriptCapabilities,
297
+ "board.emit": async (id, value) => {
298
+ const message = value;
299
+ if (!message || typeof message.target !== "string" || typeof message.type !== "string" || !boardScriptScope(editor.document, id).has(message.target)) throw new Error("Invalid script event target.");
300
+ scripts?.event(message.target, {
301
+ type: message.type,
302
+ data: message.data
303
+ });
304
+ },
305
+ ...!editor.readonly ? { "board.commit": async (id, value) => {
306
+ validateBoardScriptCommit(editor.document, id, value);
307
+ await editor.commitPatch(value);
308
+ } } : {}
309
+ },
310
+ onOutput: (id, output) => {
311
+ if (output) {
312
+ prepareBoardScriptOutput(editor.document, id, output);
313
+ scriptOutputs.set(id, output);
314
+ } else scriptOutputs.delete(id);
315
+ scriptVersion++;
316
+ scheduleTick();
317
+ },
318
+ readModule: async (src) => {
319
+ const url = await options.source.resolveFileUrl(src);
320
+ if (!url) throw new Error(`${src} is not available.`);
321
+ const response = await fetch(url, { signal: AbortSignal.timeout(15e3) });
322
+ if (!response.ok) throw new Error(`${src} could not be read.`);
323
+ return response.text();
324
+ },
325
+ onFrame: () => {
326
+ scheduleTick();
327
+ for (const listener of scriptListeners) listener();
328
+ }
329
+ });
330
+ }
282
331
  function sync() {
283
332
  if (!app || !world || !scene || !overlay) return;
284
333
  const current = theme();
334
+ const hasScripts = editor.hasScripts;
335
+ if (hasScripts && !scripts) {
336
+ scripts = createScripts();
337
+ scriptTime = 0;
338
+ scriptLastTick = performance.now();
339
+ } else if (!hasScripts && scripts) {
340
+ const previous = scripts;
341
+ scripts = null;
342
+ previous.destroy();
343
+ scriptOutputs.clear();
344
+ editor.setScriptItems(/* @__PURE__ */ new Map());
345
+ scriptHover = null;
346
+ composedVersion = -1;
347
+ composedDocument = null;
348
+ }
285
349
  syncBackground(current);
286
350
  player.enter(editor.consumeRecentlyAdded(), editor.document);
287
351
  const playback = options.playback?.() ?? null;
@@ -290,15 +354,51 @@ function mountBoardStage(host, options) {
290
354
  const moving = new Map(previews);
291
355
  for (const [id, item] of frame.items) moving.set(id, item);
292
356
  editor.setPlayedItems(moving);
357
+ if (scripts && (scriptOutputs.size || composedVersion !== scriptVersion)) {
358
+ const playheadKey = `${editor.playhead?.animationId ?? ""}:${editor.playhead?.time ?? ""}`;
359
+ if (scriptVersion !== composedVersion || composedDocument !== editor.document || moving.size || composedMoving || composedZoom !== editor.camera.zoom || composedPlayhead !== playheadKey) {
360
+ const animationItems = editor.animationItems();
361
+ const composed = composeBoardScriptItems(editor.document, editor.scene, scriptOutputs, animationItems, editor.camera, (owner, error) => scripts?.fault(owner, error instanceof Error ? error.message : String(error)));
362
+ const scriptedIds = new Set([...scriptOutputs.values()].flatMap((output) => Object.keys(output.patches)));
363
+ for (const id of animationItems.keys()) if (!scriptedIds.has(id)) composed.delete(id);
364
+ editor.setScriptItems(composed);
365
+ composedMoving = moving.size > 0;
366
+ composedPlayhead = playheadKey;
367
+ composedZoom = editor.camera.zoom;
368
+ composedVersion = scriptVersion;
369
+ composedDocument = editor.document;
370
+ }
371
+ }
293
372
  const renderScene = editor.scene;
373
+ const animationId = editor.playhead?.animationId ?? (frame.playbackTime !== null ? playback?.animationId : null) ?? null;
374
+ if (animationId && (effectDocument !== editor.document || effectAnimation !== animationId)) {
375
+ effectDocument = editor.document;
376
+ effectAnimation = animationId;
377
+ effectSampler = createBoardAnimationSampler(editor.document, animationId);
378
+ }
379
+ if (editor.playhead && effectSampler) {
380
+ frame.camera = effectSampler.at(editor.playhead.time).evaluation.camera;
381
+ frame.time = editor.playhead.time;
382
+ }
294
383
  playbackCamera = editor.cameraPolicy === "follow" && Object.keys(frame.camera).length > 0 ? followCamera(frame, renderScene) : null;
295
384
  const camera = renderCamera();
385
+ if (scripts) {
386
+ const now = performance.now();
387
+ if (scriptMode !== "paused") scriptTime += Math.min(250, Math.max(0, now - scriptLastTick));
388
+ scriptLastTick = now;
389
+ scripts.sync(editor.document, renderScene, camera, scriptTime, scriptMode === "interact", visibleIds());
390
+ }
296
391
  world.position.set(camera.x, camera.y);
297
392
  world.scale.set(camera.zoom);
298
393
  const ids = visibleIds();
299
394
  syncAssets(ids);
300
395
  const context = renderContext(current, renderScene, frame.time);
396
+ if (animationId && effectSampler) {
397
+ context.time = editor.playhead?.time ?? frame.time;
398
+ context.frameAt = effectSampler.frameAt;
399
+ }
301
400
  const pinnedIds = new Set(editor.selection);
401
+ for (const item of renderScene.items) if (item.type === "effect" && item.props.kind === "trail" && renderScene.opacity(item.id) > 0) pinnedIds.add(item.id);
302
402
  for (const id of previews.keys()) pinnedIds.add(id);
303
403
  if (editor.editingId) pinnedIds.add(editor.editingId);
304
404
  for (const id of frame.items.keys()) {
@@ -366,7 +466,7 @@ function mountBoardStage(host, options) {
366
466
  const nextCursor = cursorFor();
367
467
  if (nextCursor !== cursor) host.style.cursor = cursor = nextCursor;
368
468
  scheduleRender();
369
- if (frame.running || scene.animated) scheduleTick();
469
+ if (frame.running || scene.animated || scriptMode !== "paused" && scripts?.animated) scheduleTick();
370
470
  }
371
471
  function resize() {
372
472
  cancelAnimationFrame(resizeFrame);
@@ -387,10 +487,75 @@ function mountBoardStage(host, options) {
387
487
  function toInput(event) {
388
488
  return toBoardPointerEvent(event, rectOf(), renderCamera(), event.type === "pointermove" && editor.interaction.type === "drawing");
389
489
  }
490
+ function scriptTarget(input) {
491
+ if (!scriptOutputs.size) return null;
492
+ const point = input.world;
493
+ const candidates = editor.idsInRect({
494
+ x: point.x - .01,
495
+ y: point.y - .01,
496
+ width: .02,
497
+ height: .02
498
+ }, true);
499
+ return boardScriptTarget(editor.scene, candidates, point, scriptOutputs, editor.registry.hitTest);
500
+ }
501
+ function sendScriptEvent(target, type, event, input, related) {
502
+ if (!scripts) return;
503
+ const point = applyMatrix(invertMatrix(editor.scene.layout.matrix(target.owner)), input.world);
504
+ scripts?.event(target.owner, {
505
+ type,
506
+ target: target.target,
507
+ relatedTarget: related?.target,
508
+ relatedOwner: related?.owner,
509
+ point,
510
+ pointerId: event.pointerId,
511
+ pointerType: event.pointerType,
512
+ buttons: event.buttons,
513
+ shiftKey: event.shiftKey,
514
+ altKey: event.altKey,
515
+ ctrlKey: event.ctrlKey,
516
+ metaKey: event.metaKey
517
+ });
518
+ }
519
+ function scriptPointer(type, event, input) {
520
+ if (!scriptPointers.needsRouting(event.pointerId, scripts !== null)) return false;
521
+ const enabled = scriptMode === "interact" && !editor.gestureActive && !editor.spaceHeld && event.button !== 1;
522
+ const target = type === "leave" || !enabled || scriptPointers.owner(event.pointerId) === "editor" ? null : scriptTarget(input);
523
+ if (target?.owner !== scriptHover?.owner || target?.target !== scriptHover?.target) {
524
+ if (scriptHover) sendScriptEvent(scriptHover, "pointerleave", event, input, target);
525
+ if (target) sendScriptEvent(target, "pointerenter", event, input, scriptHover);
526
+ scriptHover = target;
527
+ }
528
+ const { recipient, click } = scriptPointers.route(type, event.pointerId, target, {
529
+ x: event.clientX,
530
+ y: event.clientY
531
+ }, event.type !== "pointerup");
532
+ if (type === "down" && recipient) {
533
+ host.setPointerCapture(event.pointerId);
534
+ host.focus({ preventScroll: true });
535
+ }
536
+ if (recipient && type !== "leave") sendScriptEvent(recipient, input.cancelled ? "pointercancel" : `pointer${type}`, event, input);
537
+ if (click) sendScriptEvent(click, "click", event, input);
538
+ return recipient !== null;
539
+ }
540
+ function handleScriptKey(event) {
541
+ if (scriptMode !== "interact" || !scriptHover || event.target !== host) return;
542
+ const input = {
543
+ type: event.type,
544
+ target: scriptHover.target,
545
+ key: event.key,
546
+ shiftKey: event.shiftKey,
547
+ altKey: event.altKey,
548
+ ctrlKey: event.ctrlKey,
549
+ metaKey: event.metaKey
550
+ };
551
+ scripts?.event(scriptHover.owner, input);
552
+ event.stopPropagation();
553
+ }
390
554
  function handlePointerDown(event) {
391
555
  if (inViews(event)) return;
392
556
  editor.takeCameraControl();
393
557
  const input = toInput(event);
558
+ if (scriptPointer("down", event, input)) return;
394
559
  if (options.onPointer?.("down", event, input) === false) return;
395
560
  host.setPointerCapture(event.pointerId);
396
561
  editor.pointerDown(input);
@@ -398,18 +563,21 @@ function mountBoardStage(host, options) {
398
563
  function handlePointerMove(event) {
399
564
  if (inViews(event)) return;
400
565
  const input = toInput(event);
566
+ if (scriptPointer("move", event, input)) return;
401
567
  if (options.onPointer?.("move", event, input) === false) return;
402
568
  editor.pointerMove(input);
403
569
  }
404
570
  function handlePointerUp(event) {
405
571
  if (inViews(event)) return;
406
572
  const input = toInput(event);
573
+ if (scriptPointer("up", event, input)) return;
407
574
  if (options.onPointer?.("up", event, input) === false) return;
408
575
  editor.pointerUp(input);
409
576
  if (event.type === "pointercancel" || event.pointerType !== "mouse") editor.pointerLeave();
410
577
  }
411
578
  function handlePointerLeave(event) {
412
579
  if (event.buttons !== 0) return;
580
+ scriptPointer("leave", event, toInput(event));
413
581
  if (options.onPointer?.("leave", event, toInput(event)) === false) return;
414
582
  editor.pointerLeave();
415
583
  }
@@ -436,6 +604,8 @@ function mountBoardStage(host, options) {
436
604
  else if (!item) editor.beginTextDraft(point);
437
605
  }
438
606
  const listeners = [
607
+ ["keydown", handleScriptKey],
608
+ ["keyup", handleScriptKey],
439
609
  ["pointerdown", handlePointerDown],
440
610
  ["pointermove", handlePointerMove],
441
611
  ["pointerup", handlePointerUp],
@@ -478,13 +648,22 @@ function mountBoardStage(host, options) {
478
648
  return false;
479
649
  }
480
650
  app = instance;
481
- instance.canvas.style.cssText = "position:relative;z-index:1;display:block";
651
+ stopMaintenance = scheduleBoardMaintenance(instance.renderer);
652
+ Object.assign(instance.canvas.style, {
653
+ position: "relative",
654
+ zIndex: "1",
655
+ display: "block",
656
+ touchAction: "none"
657
+ });
482
658
  host.insertBefore(instance.canvas, views.element);
483
659
  world = new Container({
484
660
  isRenderGroup: true,
485
661
  label: "board-world"
486
662
  });
487
- const nodes = new Container({ label: "board-nodes" });
663
+ const nodes = new Container({
664
+ isRenderGroup: true,
665
+ label: "board-nodes"
666
+ });
488
667
  overlay = new Graphics({ label: "board-interaction-overlay" });
489
668
  world.addChild(nodes, overlay);
490
669
  instance.stage.addChild(world);
@@ -494,16 +673,6 @@ function mountBoardStage(host, options) {
494
673
  getRenderer: resolveRenderer,
495
674
  onFarLayerFrame: scheduleTick
496
675
  });
497
- sketches = createBoardSketchHost({
498
- readModule: async (src) => {
499
- const url = await options.source.resolveFileUrl(src);
500
- if (!url) throw new Error(`${src} is not available.`);
501
- const response = await fetch(url);
502
- if (!response.ok) throw new Error(`${src} could not be read.`);
503
- return response.text();
504
- },
505
- onFrame: () => sync()
506
- });
507
676
  for (const [type, listener, listenerOptions] of listeners) host.addEventListener(type, listener, listenerOptions);
508
677
  resizeObserver.observe(host);
509
678
  resize();
@@ -518,6 +687,38 @@ function mountBoardStage(host, options) {
518
687
  get theme() {
519
688
  return theme();
520
689
  },
690
+ setScriptMode(mode) {
691
+ if (mode === scriptMode) return;
692
+ scriptMode = mode;
693
+ scriptLastTick = performance.now();
694
+ scheduleTick();
695
+ },
696
+ resetScripts() {
697
+ scriptTime = 0;
698
+ scripts?.invalidate();
699
+ },
700
+ invalidateScript(path) {
701
+ scripts?.invalidate(path);
702
+ },
703
+ scriptOutput: (id) => scripts?.output(id) ?? null,
704
+ async snapshotScripts() {
705
+ const result = {};
706
+ await Promise.all([...scriptOutputs.keys()].map(async (id) => {
707
+ const snapshot = await scripts?.snapshot(id);
708
+ if (snapshot) result[id] = snapshot;
709
+ }));
710
+ return result;
711
+ },
712
+ restoreScript(id, snapshot) {
713
+ scripts?.restore(id, snapshot);
714
+ },
715
+ subscribeScripts(listener) {
716
+ scriptListeners.add(listener);
717
+ return () => {
718
+ scriptListeners.delete(listener);
719
+ };
720
+ },
721
+ scriptError: (id) => scripts?.error(id) ?? null,
521
722
  setActive(next) {
522
723
  if (active === next) return;
523
724
  active = next;
@@ -529,6 +730,7 @@ function mountBoardStage(host, options) {
529
730
  return;
530
731
  }
531
732
  player.reset();
733
+ scriptLastTick = performance.now();
532
734
  resize();
533
735
  },
534
736
  invalidate(invalidation) {
@@ -542,6 +744,9 @@ function mountBoardStage(host, options) {
542
744
  toWorld: (clientX, clientY) => screenToWorld(clientX, clientY, rectOf(), renderCamera()),
543
745
  destroy() {
544
746
  disposed = true;
747
+ scriptPointers.clear();
748
+ scriptListeners.clear();
749
+ stopMaintenance?.();
545
750
  stopEditor();
546
751
  stopAssets();
547
752
  resizeObserver.disconnect();
@@ -550,10 +755,11 @@ function mountBoardStage(host, options) {
550
755
  cancelAnimationFrame(tickFrame);
551
756
  for (const [type, listener] of listeners) host.removeEventListener(type, listener);
552
757
  editor.setPlayedItems(/* @__PURE__ */ new Map());
758
+ editor.setScriptItems(/* @__PURE__ */ new Map());
553
759
  scene?.destroy(renderContext(theme(), editor.scene, 0));
554
760
  scene = null;
555
- sketches?.destroy();
556
- sketches = null;
761
+ scripts?.destroy();
762
+ scripts = null;
557
763
  background?.destroy({ children: true });
558
764
  background = null;
559
765
  views.destroy();
@@ -34,11 +34,10 @@ async function loadUncachedTexture(url) {
34
34
  */
35
35
  async function loadImageTexture(url) {
36
36
  try {
37
- const asset = /[?&]x-oss-process=/.test(url) ? {
37
+ return await Assets.load({
38
38
  src: url,
39
39
  parser: "texture"
40
- } : url;
41
- return await Assets.load(asset);
40
+ });
42
41
  } catch (workerError) {
43
42
  const errors = [workerError];
44
43
  for (const load of [loadImageElementTexture, loadUncachedTexture]) try {