@neta-art/cohub 11.5.1 → 12.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board/editor/animation.js +2 -1
- package/dist/board/editor/creation.js +1 -1
- package/dist/board/editor/document-edits.d.ts +1 -1
- package/dist/board/editor/document-edits.js +1 -19
- package/dist/board/editor/document.js +2 -1
- package/dist/board/editor/index.d.ts +2 -1
- package/dist/board/editor/index.js +2 -1
- package/dist/board/editor/snapshots.js +11 -13
- package/dist/board/export/scene.js +1 -0
- package/dist/board/model/client-geometry.d.ts +22 -0
- package/dist/board/model/client-geometry.js +94 -0
- package/dist/board/model/document-share.d.ts +5 -0
- package/dist/board/model/document-share.js +27 -0
- package/dist/board/model/index.d.ts +5 -4
- package/dist/board/model/index.js +4 -3
- package/dist/board/model/items/create.d.ts +5 -1
- package/dist/board/model/items/create.js +29 -58
- package/dist/board/model/items/index.d.ts +2 -2
- package/dist/board/model/items/index.js +2 -2
- package/dist/board/model/items/registry.js +2 -5
- package/dist/board/model/json.d.ts +4 -0
- package/dist/board/model/json.js +22 -0
- package/dist/board/model/media-playback.d.ts +6 -3
- package/dist/board/player/index.d.ts +6 -2
- package/dist/board/player/index.js +33 -15
- package/dist/board/render/card-tag.d.ts +0 -1
- package/dist/board/render/card-tag.js +15 -17
- package/dist/board/render/index.d.ts +3 -3
- package/dist/board/render/index.js +3 -3
- package/dist/board/render/renderers/arrow-card-renderer.js +8 -6
- package/dist/board/render/renderers/audio-card-renderer.js +10 -6
- package/dist/board/render/renderers/base-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/base-card-renderer.js +2 -3
- package/dist/board/render/renderers/board-renderer-registry.d.ts +6 -2
- package/dist/board/render/renderers/custom-card-renderer.js +23 -18
- package/dist/board/render/renderers/file-card-renderer.js +14 -9
- package/dist/board/render/renderers/frame-card-renderer.js +7 -4
- package/dist/board/render/renderers/image-card-renderer.js +13 -49
- package/dist/board/render/renderers/model-card-renderer.js +4 -1
- package/dist/board/render/renderers/shape-card-renderer.js +8 -6
- package/dist/board/render/renderers/task-card-renderer.js +13 -10
- package/dist/board/render/renderers/text-card-renderer.js +7 -5
- package/dist/board/render/renderers/unknown-card-renderer.js +8 -6
- package/dist/board/render/renderers/video-card-renderer.js +19 -18
- package/dist/board/render/script-host.d.ts +2 -0
- package/dist/board/render/script-host.js +44 -9
- package/dist/board/render/script-resolution.d.ts +4 -0
- package/dist/board/render/script-resolution.js +18 -0
- package/dist/board/render/surface.d.ts +9 -2
- package/dist/board/render/surface.js +15 -1
- package/dist/board/render/text-resolution.d.ts +4 -7
- package/dist/board/render/text-resolution.js +20 -22
- package/dist/board/replica/index.js +4 -1
- package/dist/board/stage/assets.d.ts +12 -8
- package/dist/board/stage/assets.js +173 -88
- package/dist/board/stage/canvas-size.d.ts +18 -0
- package/dist/board/stage/canvas-size.js +31 -0
- package/dist/board/stage/index.d.ts +3 -3
- package/dist/board/stage/index.js +3 -3
- package/dist/board/stage/lru.d.ts +3 -2
- package/dist/board/stage/lru.js +8 -4
- package/dist/board/stage/pointer.d.ts +1 -1
- package/dist/board/stage/pointer.js +4 -5
- package/dist/board/stage/scene.d.ts +8 -1
- package/dist/board/stage/scene.js +72 -10
- package/dist/board/stage/stage.d.ts +1 -0
- package/dist/board/stage/stage.js +151 -63
- package/dist/board/stage/text-resolution-tracker.d.ts +8 -0
- package/dist/board/stage/text-resolution-tracker.js +33 -0
- package/dist/board/stage/views.d.ts +7 -2
- package/dist/board/stage/views.js +50 -21
- package/dist/chunks/environment.d.ts +16 -2
- package/dist/index.js +65 -22
- package/dist/protocol/dist/board-constants.js +56 -1
- package/dist/protocol/dist/board-layout.d.ts +8 -1
- package/dist/protocol/dist/board-layout.js +16 -4
- package/dist/protocol/dist/board-model.js +13 -19
- package/dist/protocol/dist/board-schema.d.ts +52 -0
- package/dist/protocol/dist/board-schema.js +8 -0
- package/dist/protocol/dist/core/media.js +1 -0
- package/dist/protocol/dist/index.d.ts +2 -2
- package/package.json +1 -1
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import { applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
|
|
2
2
|
import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
|
|
3
|
-
import {
|
|
3
|
+
import { pointToWorld, screenPoint, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
|
|
4
4
|
import { boardScriptScope, composeBoardScriptItems, prepareBoardScriptOutput, validateBoardScriptCommit } from "../model/script-scene.js";
|
|
5
5
|
import { boardScriptTarget } from "../model/script-hit.js";
|
|
6
6
|
import { createBoardAnimationSampler, resolveBoardAnimationCamera } from "../model/sampling.js";
|
|
7
7
|
import { resolveSceneArrow } from "../model/arrow-geometry.js";
|
|
8
8
|
import { buildFallbackShapeColors, pickBoardColor, resolveItemColor } from "../model/palette.js";
|
|
9
|
+
import { elementClientToLocal } from "../model/client-geometry.js";
|
|
9
10
|
import { isCreationBoardTool } from "../editor/tool.js";
|
|
10
11
|
import { resizeCursorForHandle } from "../editor/selection-transform.js";
|
|
11
12
|
import { parseBoardCssColor } from "../render/css-color.js";
|
|
12
13
|
import { defaultBoardPalette, itemAlpha } from "../render/palette.js";
|
|
13
14
|
import { createBoardBackground, updateBoardBackground } from "../render/board-background.js";
|
|
14
|
-
import { getBoardResolution
|
|
15
|
+
import { getBoardResolution } from "../render/text-resolution.js";
|
|
15
16
|
import { DEFAULT_BOARD_RENDER_LABELS, getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
|
|
16
17
|
import { createBoardScriptHost } from "../render/script-host.js";
|
|
17
18
|
import { defaultBoardRenderStyle } from "../render/style.js";
|
|
18
|
-
import { createBoardScriptObservers } from "../model/script-observers.js";
|
|
19
19
|
import { createBoardPlayer } from "../player/index.js";
|
|
20
|
+
import { createBoardScriptObservers } from "../model/script-observers.js";
|
|
21
|
+
import { boardTextureEdge } from "./assets.js";
|
|
22
|
+
import { boardCanvasSize, measureBoardCanvasBox } from "./canvas-size.js";
|
|
20
23
|
import { observeDisplayDensity } from "./display-density.js";
|
|
21
24
|
import { toBoardPointerEvent } from "./pointer.js";
|
|
22
25
|
import { createBoardPointerGestures } from "./pointer-gestures.js";
|
|
@@ -24,6 +27,7 @@ import { createBoardScene } from "./scene.js";
|
|
|
24
27
|
import { scheduleBoardMaintenance } from "./maintenance.js";
|
|
25
28
|
import { createBoardScriptPointerRouter } from "./script-pointer.js";
|
|
26
29
|
import { drawBoardOverlay } from "./overlay.js";
|
|
30
|
+
import { createTextResolutionTracker } from "./text-resolution-tracker.js";
|
|
27
31
|
import { createBoardItemViewLayer } from "./views.js";
|
|
28
32
|
import { Application, Container, Graphics, RendererType } from "pixi.js";
|
|
29
33
|
//#region src/board/stage/stage.ts
|
|
@@ -49,6 +53,7 @@ function defaultTheme() {
|
|
|
49
53
|
const NO_IDS = /* @__PURE__ */ new Set();
|
|
50
54
|
const MARCH_STEP_MS = 160;
|
|
51
55
|
const MARCH_SMOOTH_MS = 40;
|
|
56
|
+
const TEXTURE_SETTLE_MS = 150;
|
|
52
57
|
const ROTATE_CURSOR = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M21 12a9 9 0 1 1-2.64-6.36L21 8M21 3v5h-5' fill='none' stroke='%23fff' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M21 12a9 9 0 1 1-2.64-6.36L21 8M21 3v5h-5' fill='none' stroke='%231d1d1f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 12 12, crosshair`;
|
|
53
58
|
const defaultStyles = /* @__PURE__ */ new Map();
|
|
54
59
|
function styleOf(theme) {
|
|
@@ -102,13 +107,14 @@ function mountBoardStage(host, options) {
|
|
|
102
107
|
let disposed = false;
|
|
103
108
|
let renderFrame = 0;
|
|
104
109
|
let tickFrame = 0;
|
|
110
|
+
let scriptTickFrame = 0;
|
|
105
111
|
let cardTickFrame = 0;
|
|
106
112
|
let cardTickTimer = null;
|
|
107
113
|
let cardTickDue = 0;
|
|
108
114
|
const staleIds = /* @__PURE__ */ new Set();
|
|
109
115
|
let resizeFrame = 0;
|
|
116
|
+
let canvasBox = null;
|
|
110
117
|
let cardEpoch = 0;
|
|
111
|
-
let assetVersion = 0;
|
|
112
118
|
let surface = {
|
|
113
119
|
width: 0,
|
|
114
120
|
height: 0
|
|
@@ -116,7 +122,7 @@ function mountBoardStage(host, options) {
|
|
|
116
122
|
let playbackCamera = null;
|
|
117
123
|
let cursor = "";
|
|
118
124
|
const renderCamera = () => playbackCamera ?? editor.camera;
|
|
119
|
-
const views = createBoardItemViewLayer(host, editor, options.views ?? [], renderCamera);
|
|
125
|
+
const views = createBoardItemViewLayer(host, editor, options.views ?? [], renderCamera, scheduleTick);
|
|
120
126
|
const player = createBoardPlayer({ resolveColor: (color) => {
|
|
121
127
|
const current = theme();
|
|
122
128
|
return {
|
|
@@ -140,29 +146,71 @@ function mountBoardStage(host, options) {
|
|
|
140
146
|
scene: editor.scene
|
|
141
147
|
};
|
|
142
148
|
options.onVisibleChange?.(cull.ids);
|
|
143
|
-
requestAssets = true;
|
|
144
149
|
return cull.ids;
|
|
145
150
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
const textureScale = () => renderCamera().zoom * (app?.renderer.resolution ?? 1);
|
|
152
|
+
const textResolution = createTextResolutionTracker(TEXTURE_SETTLE_MS, scheduleTick);
|
|
153
|
+
let refined = {
|
|
154
|
+
scale: 0,
|
|
155
|
+
geometry: 0
|
|
156
|
+
};
|
|
157
|
+
let pendingRefine = "";
|
|
158
|
+
let refineTimer = null;
|
|
159
|
+
function stopRefine() {
|
|
160
|
+
if (refineTimer !== null) clearTimeout(refineTimer);
|
|
161
|
+
refineTimer = null;
|
|
162
|
+
refined = {
|
|
163
|
+
scale: 0,
|
|
164
|
+
geometry: 0
|
|
165
|
+
};
|
|
166
|
+
pendingRefine = "";
|
|
167
|
+
}
|
|
168
|
+
function scheduleRefine() {
|
|
169
|
+
const scale = textureScale();
|
|
170
|
+
const geometry = editor.geometryVersion;
|
|
171
|
+
const key = `${scale}:${geometry}`;
|
|
172
|
+
if (key === pendingRefine) return;
|
|
173
|
+
pendingRefine = key;
|
|
174
|
+
if (refineTimer !== null) clearTimeout(refineTimer);
|
|
175
|
+
refineTimer = null;
|
|
176
|
+
if (refined.scale === 0 || scale <= refined.scale && geometry === refined.geometry) {
|
|
177
|
+
refined = {
|
|
178
|
+
scale,
|
|
179
|
+
geometry
|
|
180
|
+
};
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
refineTimer = setTimeout(() => {
|
|
184
|
+
refineTimer = null;
|
|
185
|
+
refined = {
|
|
186
|
+
scale,
|
|
187
|
+
geometry
|
|
188
|
+
};
|
|
189
|
+
for (const id of scene?.liveIds() ?? []) {
|
|
190
|
+
const item = editor.scene.get(id);
|
|
191
|
+
if (item) assets.requestItem(item, boardTextureEdge(item, scale));
|
|
192
|
+
}
|
|
193
|
+
}, TEXTURE_SETTLE_MS);
|
|
194
|
+
}
|
|
195
|
+
const naturalIds = /* @__PURE__ */ new Set();
|
|
196
|
+
let liveVersion = -1;
|
|
197
|
+
function adoptNaturalSizes() {
|
|
198
|
+
if (naturalIds.size === 0) return;
|
|
150
199
|
const sizes = [];
|
|
151
|
-
for (const id of
|
|
200
|
+
for (const id of naturalIds) {
|
|
152
201
|
const item = editor.itemById(id);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (!key) continue;
|
|
156
|
-
assets.requestItem(item);
|
|
202
|
+
const key = item && assets.assetKey(item);
|
|
203
|
+
if (!item || !key) continue;
|
|
157
204
|
const artifact = item.type === "task" ? featuredTaskArtifact(item.props.snapshot.artifacts) : null;
|
|
158
205
|
const recorded = artifact?.type === "image" || artifact?.type === "video" ? artifact : item.type === "image" || item.type === "video" ? item.props.snapshot : null;
|
|
159
206
|
if (!recorded || recorded.naturalWidth && recorded.naturalHeight) continue;
|
|
160
207
|
const natural = assets.getNaturalSize(key);
|
|
161
208
|
if (natural?.width && natural.height) sizes.push({
|
|
162
|
-
id
|
|
209
|
+
id,
|
|
163
210
|
...natural
|
|
164
211
|
});
|
|
165
212
|
}
|
|
213
|
+
naturalIds.clear();
|
|
166
214
|
if (sizes.length > 0) editor.adoptMediaNaturalSizes(sizes);
|
|
167
215
|
}
|
|
168
216
|
let lifted = null;
|
|
@@ -196,6 +244,7 @@ function mountBoardStage(host, options) {
|
|
|
196
244
|
themeKey: current.key,
|
|
197
245
|
rendererType: app?.renderer.type === RendererType.CANVAS ? "canvas" : "gpu",
|
|
198
246
|
zoom: renderCamera().zoom,
|
|
247
|
+
textResolution: textResolution.value,
|
|
199
248
|
reducedMotion: options.reducedMotion?.() ?? false,
|
|
200
249
|
labels: options.labels?.() ?? DEFAULT_BOARD_RENDER_LABELS,
|
|
201
250
|
taskRun: options.taskRun ?? (() => null),
|
|
@@ -203,7 +252,11 @@ function mountBoardStage(host, options) {
|
|
|
203
252
|
getTexture: (key) => assets.getTexture(key),
|
|
204
253
|
hasError: (key) => assets.hasError(key),
|
|
205
254
|
fileState: (path) => options.fileState?.(path) ?? "ok",
|
|
206
|
-
acquireTexture: (key) =>
|
|
255
|
+
acquireTexture: (key, item) => {
|
|
256
|
+
assets.acquire(key);
|
|
257
|
+
assets.requestItem(item, boardTextureEdge(item, textureScale()));
|
|
258
|
+
if (assets.getTexture(key)) naturalIds.add(item.id);
|
|
259
|
+
},
|
|
207
260
|
releaseTexture: (key) => assets.release(key)
|
|
208
261
|
};
|
|
209
262
|
}
|
|
@@ -442,9 +495,37 @@ function mountBoardStage(host, options) {
|
|
|
442
495
|
}, owner, retained);
|
|
443
496
|
if (!retained || scriptHover && scriptHover.owner === owner && !Object.hasOwn(retained, scriptHover.target)) scriptHover = null;
|
|
444
497
|
}
|
|
498
|
+
function syncScripts() {
|
|
499
|
+
if (!scripts) return;
|
|
500
|
+
const now = performance.now();
|
|
501
|
+
if (scriptMode !== "paused") scriptTime += Math.min(250, Math.max(0, now - scriptLastTick));
|
|
502
|
+
scriptLastTick = now;
|
|
503
|
+
const camera = renderCamera();
|
|
504
|
+
if (scriptMode !== "paused" && scriptObservers.size) scriptObservers.sync(editor.document, editor.selection, () => ({
|
|
505
|
+
camera: { ...camera },
|
|
506
|
+
...surface,
|
|
507
|
+
safeRect: options.safeRect?.() ?? {
|
|
508
|
+
x: 0,
|
|
509
|
+
y: 0,
|
|
510
|
+
...surface
|
|
511
|
+
}
|
|
512
|
+
}));
|
|
513
|
+
scripts.sync(editor.document, editor.scene, camera, scriptTime, scriptMode === "interact", visibleIds());
|
|
514
|
+
}
|
|
515
|
+
function scheduleScriptTick() {
|
|
516
|
+
if (scriptTickFrame || !active || disposed) return;
|
|
517
|
+
scriptTickFrame = requestAnimationFrame(() => {
|
|
518
|
+
scriptTickFrame = 0;
|
|
519
|
+
if (tickFrame) return;
|
|
520
|
+
syncScripts();
|
|
521
|
+
if (scriptMode !== "paused" && scripts?.animated) scheduleScriptTick();
|
|
522
|
+
});
|
|
523
|
+
}
|
|
445
524
|
function createScripts() {
|
|
446
525
|
return createBoardScriptHost({
|
|
447
526
|
host,
|
|
527
|
+
isMaterialized: (id) => Boolean(scene?.getNode(id)),
|
|
528
|
+
onSchedule: scheduleScriptTick,
|
|
448
529
|
capabilities: {
|
|
449
530
|
...options.scriptCapabilities,
|
|
450
531
|
"board.emit": async (id, value) => {
|
|
@@ -552,25 +633,10 @@ function mountBoardStage(host, options) {
|
|
|
552
633
|
}
|
|
553
634
|
}
|
|
554
635
|
const renderScene = editor.scene;
|
|
555
|
-
if (scripts) {
|
|
556
|
-
const now = performance.now();
|
|
557
|
-
if (scriptMode !== "paused") scriptTime += Math.min(250, Math.max(0, now - scriptLastTick));
|
|
558
|
-
scriptLastTick = now;
|
|
559
|
-
if (scriptMode !== "paused" && scriptObservers.size) scriptObservers.sync(editor.document, editor.selection, () => ({
|
|
560
|
-
camera: { ...camera },
|
|
561
|
-
...surface,
|
|
562
|
-
safeRect: options.safeRect?.() ?? {
|
|
563
|
-
x: 0,
|
|
564
|
-
y: 0,
|
|
565
|
-
...surface
|
|
566
|
-
}
|
|
567
|
-
}));
|
|
568
|
-
scripts.sync(editor.document, renderScene, camera, scriptTime, scriptMode === "interact", visibleIds());
|
|
569
|
-
}
|
|
570
636
|
world.position.set(camera.x, camera.y);
|
|
571
637
|
world.scale.set(camera.zoom);
|
|
572
638
|
const ids = visibleIds();
|
|
573
|
-
|
|
639
|
+
textResolution.update(textureScale());
|
|
574
640
|
const context = renderContext(current, renderScene, frame.time);
|
|
575
641
|
if (animationId && effectSampler) {
|
|
576
642
|
context.time = editor.playhead?.time ?? frame.time;
|
|
@@ -593,19 +659,21 @@ function mountBoardStage(host, options) {
|
|
|
593
659
|
getItem: (id) => renderScene.get(id) ?? null,
|
|
594
660
|
visibleIds: ids,
|
|
595
661
|
pinnedIds,
|
|
596
|
-
globalSig: [
|
|
597
|
-
assetVersion,
|
|
598
|
-
cardEpoch,
|
|
599
|
-
current.key,
|
|
600
|
-
textZoomBucket(camera.zoom),
|
|
601
|
-
getBoardResolution()
|
|
602
|
-
].join("|"),
|
|
662
|
+
globalSig: [cardEpoch, current.key].join("|"),
|
|
603
663
|
staleIds,
|
|
604
664
|
structureVersion: editor.structureVersion,
|
|
605
665
|
geometryVersion: editor.geometryVersion,
|
|
606
|
-
gestureActive: editor.gestureActive && editor.interaction.type !== "panning"
|
|
666
|
+
gestureActive: editor.gestureActive && editor.interaction.type !== "panning",
|
|
667
|
+
focus: pointToWorld(screenPoint(surface.width / 2, surface.height / 2), camera)
|
|
607
668
|
});
|
|
608
669
|
staleIds.clear();
|
|
670
|
+
if (scene.liveVersion !== liveVersion) {
|
|
671
|
+
liveVersion = scene.liveVersion;
|
|
672
|
+
options.onLiveChange?.(scene.liveIds());
|
|
673
|
+
}
|
|
674
|
+
adoptNaturalSizes();
|
|
675
|
+
scheduleRefine();
|
|
676
|
+
syncScripts();
|
|
609
677
|
drawInteraction();
|
|
610
678
|
views.sync({
|
|
611
679
|
ids,
|
|
@@ -617,7 +685,8 @@ function mountBoardStage(host, options) {
|
|
|
617
685
|
const nextCursor = cursorFor();
|
|
618
686
|
if (nextCursor !== cursor) host.style.cursor = cursor = nextCursor;
|
|
619
687
|
scheduleRender();
|
|
620
|
-
if (frame.running || scene.animated
|
|
688
|
+
if (frame.running || scene.animated) scheduleTick();
|
|
689
|
+
if (scriptMode !== "paused" && scripts?.animated) scheduleScriptTick();
|
|
621
690
|
const cardWait = scene.tick(performance.now());
|
|
622
691
|
if (cardWait !== null) scheduleCardTick(cardWait);
|
|
623
692
|
}
|
|
@@ -630,10 +699,14 @@ function mountBoardStage(host, options) {
|
|
|
630
699
|
height: host.clientHeight
|
|
631
700
|
};
|
|
632
701
|
if (size.width <= 0 || size.height <= 0) return;
|
|
633
|
-
const
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
702
|
+
const density = host.ownerDocument.defaultView?.devicePixelRatio ?? 1;
|
|
703
|
+
const canvas = boardCanvasSize(canvasBox ?? {
|
|
704
|
+
...size,
|
|
705
|
+
density,
|
|
706
|
+
device: null
|
|
707
|
+
}, density);
|
|
708
|
+
if (canvas.width !== app.screen.width || canvas.height !== app.screen.height || canvas.resolution !== app.renderer.resolution) app.renderer.resize(canvas.width, canvas.height, canvas.resolution);
|
|
709
|
+
if (size.width !== surface.width || size.height !== surface.height) {
|
|
637
710
|
surface = size;
|
|
638
711
|
editor.surfaceSize = surface;
|
|
639
712
|
options.onSurfaceChange?.(surface);
|
|
@@ -641,10 +714,10 @@ function mountBoardStage(host, options) {
|
|
|
641
714
|
sync();
|
|
642
715
|
});
|
|
643
716
|
}
|
|
644
|
-
const
|
|
717
|
+
const localPoint = (x, y) => elementClientToLocal(host)(x, y);
|
|
645
718
|
const inViews = (event) => event.target instanceof Node && views.element.contains(event.target);
|
|
646
719
|
function toInput(event) {
|
|
647
|
-
return toBoardPointerEvent(event,
|
|
720
|
+
return toBoardPointerEvent(event, host, renderCamera(), event.type === "pointermove" && editor.interaction.type === "drawing");
|
|
648
721
|
}
|
|
649
722
|
function scriptTarget(input) {
|
|
650
723
|
if (!scriptOutputs.size) return null;
|
|
@@ -751,13 +824,12 @@ function mountBoardStage(host, options) {
|
|
|
751
824
|
if (inViews(event)) return;
|
|
752
825
|
event.preventDefault();
|
|
753
826
|
editor.takeCameraControl();
|
|
754
|
-
const
|
|
755
|
-
const point = screenPoint(event.clientX - rect.left, event.clientY - rect.top);
|
|
827
|
+
const point = localPoint(event.clientX, event.clientY);
|
|
756
828
|
editor.wheel(point, event.deltaX, event.deltaY, event.ctrlKey || event.metaKey, event.deltaMode);
|
|
757
829
|
}
|
|
758
830
|
function handleDoubleClick(event) {
|
|
759
831
|
if (inViews(event)) return;
|
|
760
|
-
const point =
|
|
832
|
+
const point = pointToWorld(localPoint(event.clientX, event.clientY), renderCamera());
|
|
761
833
|
const item = editor.itemAt(point);
|
|
762
834
|
if (options.onDoubleClick?.(item, point) === true || editor.readonly) return;
|
|
763
835
|
if (item && !item.locked && editor.registry.capabilities(item).canEdit) {
|
|
@@ -783,19 +855,26 @@ function mountBoardStage(host, options) {
|
|
|
783
855
|
["dblclick", handleDoubleClick]
|
|
784
856
|
];
|
|
785
857
|
const stopEditor = editor.subscribe(LIVE_CHANNELS, scheduleTick);
|
|
786
|
-
const stopAssets = assets.subscribe(() => {
|
|
787
|
-
|
|
788
|
-
|
|
858
|
+
const stopAssets = assets.subscribe((key) => {
|
|
859
|
+
const ids = scene?.holders(key);
|
|
860
|
+
if (!ids?.size) return;
|
|
861
|
+
for (const id of ids) {
|
|
862
|
+
staleIds.add(id);
|
|
863
|
+
naturalIds.add(id);
|
|
864
|
+
}
|
|
789
865
|
scheduleTick();
|
|
790
866
|
});
|
|
791
|
-
const resizeObserver = new ResizeObserver(
|
|
867
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
868
|
+
const entry = entries[entries.length - 1];
|
|
869
|
+
if (entry) canvasBox = measureBoardCanvasBox(entry, host.ownerDocument.defaultView?.devicePixelRatio ?? 1);
|
|
870
|
+
resize();
|
|
871
|
+
});
|
|
792
872
|
return {
|
|
793
873
|
ready: (async () => {
|
|
794
874
|
const instance = new Application();
|
|
795
875
|
try {
|
|
796
876
|
await instance.init({
|
|
797
877
|
antialias: true,
|
|
798
|
-
autoDensity: true,
|
|
799
878
|
backgroundAlpha: 0,
|
|
800
879
|
width: host.clientWidth,
|
|
801
880
|
height: host.clientHeight,
|
|
@@ -820,6 +899,8 @@ function mountBoardStage(host, options) {
|
|
|
820
899
|
position: "relative",
|
|
821
900
|
zIndex: "1",
|
|
822
901
|
display: "block",
|
|
902
|
+
width: "100%",
|
|
903
|
+
height: "100%",
|
|
823
904
|
touchAction: "none"
|
|
824
905
|
});
|
|
825
906
|
host.insertBefore(instance.canvas, views.element);
|
|
@@ -838,10 +919,14 @@ function mountBoardStage(host, options) {
|
|
|
838
919
|
world: nodes,
|
|
839
920
|
overlay,
|
|
840
921
|
getRenderer: resolveRenderer,
|
|
841
|
-
|
|
922
|
+
requestFrame: scheduleTick
|
|
842
923
|
});
|
|
843
924
|
for (const [type, listener, listenerOptions] of listeners) host.addEventListener(type, listener, listenerOptions);
|
|
844
|
-
|
|
925
|
+
try {
|
|
926
|
+
resizeObserver.observe(instance.canvas, { box: "device-pixel-content-box" });
|
|
927
|
+
} catch {
|
|
928
|
+
resizeObserver.observe(instance.canvas);
|
|
929
|
+
}
|
|
845
930
|
const window = host.ownerDocument.defaultView;
|
|
846
931
|
if (window) stopDensity = observeDisplayDensity(window, resize);
|
|
847
932
|
resize();
|
|
@@ -895,7 +980,10 @@ function mountBoardStage(host, options) {
|
|
|
895
980
|
if (!active) {
|
|
896
981
|
pointers.cancel();
|
|
897
982
|
stopMarch();
|
|
983
|
+
stopRefine();
|
|
898
984
|
cancelScriptPointers();
|
|
985
|
+
cancelAnimationFrame(scriptTickFrame);
|
|
986
|
+
scriptTickFrame = 0;
|
|
899
987
|
cancelAnimationFrame(renderFrame);
|
|
900
988
|
cancelAnimationFrame(tickFrame);
|
|
901
989
|
cancelAnimationFrame(cardTickFrame);
|
|
@@ -911,19 +999,18 @@ function mountBoardStage(host, options) {
|
|
|
911
999
|
resize();
|
|
912
1000
|
},
|
|
913
1001
|
invalidate(invalidation) {
|
|
914
|
-
if (invalidation?.cards)
|
|
915
|
-
cardEpoch += 1;
|
|
916
|
-
requestAssets = true;
|
|
917
|
-
}
|
|
1002
|
+
if (invalidation?.cards) cardEpoch += 1;
|
|
918
1003
|
for (const id of invalidation?.items ?? []) staleIds.add(id);
|
|
919
1004
|
scheduleTick();
|
|
920
1005
|
},
|
|
921
1006
|
isMaterialized: (id) => Boolean(scene?.getNode(id)),
|
|
922
|
-
toWorld: (clientX, clientY) =>
|
|
1007
|
+
toWorld: (clientX, clientY) => pointToWorld(localPoint(clientX, clientY), renderCamera()),
|
|
923
1008
|
destroy() {
|
|
924
1009
|
disposed = true;
|
|
925
1010
|
pointers.cancel();
|
|
926
1011
|
stopMarch();
|
|
1012
|
+
stopRefine();
|
|
1013
|
+
textResolution.dispose();
|
|
927
1014
|
scriptPointers.clear();
|
|
928
1015
|
stopTransforms?.();
|
|
929
1016
|
scriptObservers.clear();
|
|
@@ -933,6 +1020,7 @@ function mountBoardStage(host, options) {
|
|
|
933
1020
|
stopEditor();
|
|
934
1021
|
stopAssets();
|
|
935
1022
|
resizeObserver.disconnect();
|
|
1023
|
+
cancelAnimationFrame(scriptTickFrame);
|
|
936
1024
|
cancelAnimationFrame(resizeFrame);
|
|
937
1025
|
cancelAnimationFrame(renderFrame);
|
|
938
1026
|
cancelAnimationFrame(tickFrame);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/board/stage/text-resolution-tracker.d.ts
|
|
2
|
+
declare function createTextResolutionTracker(settleMs: number, onSettle: () => void): {
|
|
3
|
+
readonly value: number;
|
|
4
|
+
update(next: number): number;
|
|
5
|
+
dispose(): void;
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { createTextResolutionTracker };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { boardTextResolution } from "../render/text-resolution.js";
|
|
2
|
+
//#region src/board/stage/text-resolution-tracker.ts
|
|
3
|
+
function createTextResolutionTracker(settleMs, onSettle) {
|
|
4
|
+
let target = 0;
|
|
5
|
+
let value = 0;
|
|
6
|
+
let settled = false;
|
|
7
|
+
let timer = null;
|
|
8
|
+
return {
|
|
9
|
+
get value() {
|
|
10
|
+
return value;
|
|
11
|
+
},
|
|
12
|
+
update(next) {
|
|
13
|
+
if (next !== target) {
|
|
14
|
+
target = next;
|
|
15
|
+
settled = false;
|
|
16
|
+
if (timer !== null) clearTimeout(timer);
|
|
17
|
+
timer = setTimeout(() => {
|
|
18
|
+
timer = null;
|
|
19
|
+
settled = true;
|
|
20
|
+
onSettle();
|
|
21
|
+
}, settleMs);
|
|
22
|
+
}
|
|
23
|
+
value = boardTextResolution(target, value, settled);
|
|
24
|
+
return value;
|
|
25
|
+
},
|
|
26
|
+
dispose() {
|
|
27
|
+
if (timer !== null) clearTimeout(timer);
|
|
28
|
+
timer = null;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createTextResolutionTracker };
|
|
@@ -7,7 +7,7 @@ import "../editor/index.js";
|
|
|
7
7
|
//#region src/board/stage/views.d.ts
|
|
8
8
|
type BoardItemViewContext = {
|
|
9
9
|
editor: BoardEditor;
|
|
10
|
-
|
|
10
|
+
detailed: boolean;
|
|
11
11
|
selected: boolean;
|
|
12
12
|
toPointerEvent(event: PointerEvent): BoardPointerEvent;
|
|
13
13
|
};
|
|
@@ -17,6 +17,11 @@ type BoardItemViewInstance = {
|
|
|
17
17
|
};
|
|
18
18
|
type BoardItemView = {
|
|
19
19
|
type: string | ((item: BoardSceneItem) => boolean);
|
|
20
|
+
/** Optional screen-pixel threshold for expensive content, with built-in hysteresis. */
|
|
21
|
+
contentSize?: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
};
|
|
20
25
|
mount(element: HTMLElement, item: BoardSceneItem, context: BoardItemViewContext): BoardItemViewInstance;
|
|
21
26
|
};
|
|
22
27
|
type BoardItemViewLayer = {
|
|
@@ -33,6 +38,6 @@ type BoardItemViewLayer = {
|
|
|
33
38
|
}): void;
|
|
34
39
|
destroy(): void;
|
|
35
40
|
};
|
|
36
|
-
declare function createBoardItemViewLayer(host: HTMLElement, editor: BoardEditor, views: readonly BoardItemView[], camera: () => BoardViewport): BoardItemViewLayer;
|
|
41
|
+
declare function createBoardItemViewLayer(host: HTMLElement, editor: BoardEditor, views: readonly BoardItemView[], camera: () => BoardViewport, onInvalidate?: () => void): BoardItemViewLayer;
|
|
37
42
|
//#endregion
|
|
38
43
|
export { BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, createBoardItemViewLayer };
|
|
@@ -1,55 +1,80 @@
|
|
|
1
|
+
import { itemBounds } from "../model/geometry.js";
|
|
1
2
|
import { toBoardPointerEvent } from "./pointer.js";
|
|
2
3
|
//#region src/board/stage/views.ts
|
|
3
4
|
const VISIBLE_MARGIN = 240;
|
|
4
|
-
function
|
|
5
|
-
const { zoom } = camera;
|
|
5
|
+
function worldStyle(frame, order) {
|
|
6
6
|
return [
|
|
7
7
|
`z-index:${order}`,
|
|
8
|
-
`left:${frame.x
|
|
9
|
-
`top:${frame.y
|
|
10
|
-
`width:${frame.width
|
|
11
|
-
`height:${frame.height
|
|
12
|
-
`--board-zoom:${zoom}`,
|
|
8
|
+
`left:${frame.x}px`,
|
|
9
|
+
`top:${frame.y}px`,
|
|
10
|
+
`width:${frame.width}px`,
|
|
11
|
+
`height:${frame.height}px`,
|
|
13
12
|
`transform:rotate(${frame.rotation}deg)`
|
|
14
13
|
].join(";");
|
|
15
14
|
}
|
|
16
15
|
function onScreen(frame, camera, surface) {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
16
|
+
const bounds = itemBounds(frame);
|
|
17
|
+
const left = bounds.x * camera.zoom + camera.x;
|
|
18
|
+
const top = bounds.y * camera.zoom + camera.y;
|
|
19
|
+
return left + bounds.width * camera.zoom >= -240 && top + bounds.height * camera.zoom >= -240 && left <= surface.width + VISIBLE_MARGIN && top <= surface.height + VISIBLE_MARGIN;
|
|
20
20
|
}
|
|
21
|
-
function createBoardItemViewLayer(host, editor, views, camera) {
|
|
21
|
+
function createBoardItemViewLayer(host, editor, views, camera, onInvalidate = () => {}) {
|
|
22
22
|
const element = host.ownerDocument.createElement("div");
|
|
23
23
|
element.dataset.boardViews = "";
|
|
24
24
|
element.style.cssText = "position:absolute;inset:0;z-index:2;overflow:hidden;pointer-events:none";
|
|
25
25
|
host.appendChild(element);
|
|
26
|
+
const world = host.ownerDocument.createElement("div");
|
|
27
|
+
world.style.cssText = "position:absolute;inset:0;transform-origin:0 0;pointer-events:none";
|
|
28
|
+
element.appendChild(world);
|
|
29
|
+
let cameraStyle = "";
|
|
26
30
|
const mounted = /* @__PURE__ */ new Map();
|
|
27
|
-
const
|
|
31
|
+
const pointers = /* @__PURE__ */ new Map();
|
|
32
|
+
const retained = (id, entry) => entry.element.contains(host.ownerDocument.activeElement) || [...pointers.values()].includes(id);
|
|
33
|
+
const pointerDown = (event) => {
|
|
34
|
+
const id = event.target?.closest("[data-board-item]")?.dataset.boardItem;
|
|
35
|
+
if (id && mounted.has(id)) pointers.set(event.pointerId, id);
|
|
36
|
+
};
|
|
37
|
+
const pointerEnd = (event) => {
|
|
38
|
+
if (pointers.delete(event.pointerId)) onInvalidate();
|
|
39
|
+
};
|
|
40
|
+
element.addEventListener("pointerdown", pointerDown, true);
|
|
41
|
+
element.addEventListener("focusout", onInvalidate);
|
|
42
|
+
host.ownerDocument.addEventListener("pointerup", pointerEnd, true);
|
|
43
|
+
host.ownerDocument.addEventListener("pointercancel", pointerEnd, true);
|
|
44
|
+
const toPointerEvent = (event) => toBoardPointerEvent(event, host, camera());
|
|
28
45
|
const viewFor = (item) => views.find((view) => typeof view.type === "string" ? view.type === item.type : view.type(item));
|
|
29
46
|
function unmount(id, entry) {
|
|
30
47
|
entry.instance.destroy?.();
|
|
31
48
|
entry.element.remove();
|
|
32
49
|
mounted.delete(id);
|
|
50
|
+
for (const [pointer, owner] of pointers) if (owner === id) pointers.delete(pointer);
|
|
33
51
|
}
|
|
34
52
|
return {
|
|
35
53
|
element,
|
|
36
54
|
sync({ ids, scene, camera: view, surface, selection }) {
|
|
37
55
|
if (views.length === 0) return;
|
|
56
|
+
const transform = `translate(${view.x}px,${view.y}px) scale(${view.zoom})`;
|
|
57
|
+
if (transform !== cameraStyle) world.style.transform = cameraStyle = transform;
|
|
38
58
|
const live = /* @__PURE__ */ new Set();
|
|
39
|
-
|
|
59
|
+
const candidates = new Set(ids ?? scene.items.map((item) => item.id));
|
|
60
|
+
for (const [id, entry] of mounted) if (retained(id, entry)) candidates.add(id);
|
|
61
|
+
for (const id of candidates) {
|
|
40
62
|
const item = scene.get(id);
|
|
41
63
|
if (!item) continue;
|
|
42
64
|
const itemView = viewFor(item);
|
|
43
|
-
|
|
65
|
+
let entry = mounted.get(item.id);
|
|
66
|
+
const held = entry ? retained(id, entry) : false;
|
|
67
|
+
if (!itemView || !held && !onScreen(item.frame, view, surface)) continue;
|
|
44
68
|
live.add(item.id);
|
|
69
|
+
const style = worldStyle(item.frame, scene.indexOf(item.id));
|
|
70
|
+
const minimum = itemView.contentSize;
|
|
71
|
+
const threshold = entry?.detailed ? .85 : 1;
|
|
45
72
|
const context = {
|
|
46
73
|
editor,
|
|
47
|
-
|
|
74
|
+
detailed: held || !minimum || item.frame.width * view.zoom >= minimum.width * threshold && item.frame.height * view.zoom >= minimum.height * threshold,
|
|
48
75
|
selected: selection.has(item.id),
|
|
49
76
|
toPointerEvent
|
|
50
77
|
};
|
|
51
|
-
const style = screenStyle(item.frame, view, scene.indexOf(item.id));
|
|
52
|
-
let entry = mounted.get(item.id);
|
|
53
78
|
if (entry && entry.view !== itemView) {
|
|
54
79
|
unmount(item.id, entry);
|
|
55
80
|
entry = void 0;
|
|
@@ -58,13 +83,13 @@ function createBoardItemViewLayer(host, editor, views, camera) {
|
|
|
58
83
|
const node = host.ownerDocument.createElement("div");
|
|
59
84
|
node.dataset.boardItem = item.id;
|
|
60
85
|
node.style.cssText = `position:absolute;pointer-events:none;transform-origin:center;${style}`;
|
|
61
|
-
|
|
86
|
+
world.appendChild(node);
|
|
62
87
|
mounted.set(item.id, {
|
|
63
88
|
view: itemView,
|
|
64
89
|
element: node,
|
|
65
90
|
instance: itemView.mount(node, item, context),
|
|
66
91
|
item,
|
|
67
|
-
|
|
92
|
+
detailed: context.detailed,
|
|
68
93
|
selected: context.selected,
|
|
69
94
|
style
|
|
70
95
|
});
|
|
@@ -74,9 +99,9 @@ function createBoardItemViewLayer(host, editor, views, camera) {
|
|
|
74
99
|
entry.element.style.cssText = `position:absolute;pointer-events:none;transform-origin:center;${style}`;
|
|
75
100
|
entry.style = style;
|
|
76
101
|
}
|
|
77
|
-
if (entry.item !== item || entry.
|
|
102
|
+
if (entry.item !== item || entry.detailed !== context.detailed || entry.selected !== context.selected) {
|
|
78
103
|
entry.item = item;
|
|
79
|
-
entry.
|
|
104
|
+
entry.detailed = context.detailed;
|
|
80
105
|
entry.selected = context.selected;
|
|
81
106
|
entry.instance.update?.(item, context);
|
|
82
107
|
}
|
|
@@ -84,6 +109,10 @@ function createBoardItemViewLayer(host, editor, views, camera) {
|
|
|
84
109
|
for (const [id, entry] of mounted) if (!live.has(id)) unmount(id, entry);
|
|
85
110
|
},
|
|
86
111
|
destroy() {
|
|
112
|
+
element.removeEventListener("pointerdown", pointerDown, true);
|
|
113
|
+
element.removeEventListener("focusout", onInvalidate);
|
|
114
|
+
host.ownerDocument.removeEventListener("pointerup", pointerEnd, true);
|
|
115
|
+
host.ownerDocument.removeEventListener("pointercancel", pointerEnd, true);
|
|
87
116
|
for (const [id, entry] of mounted) unmount(id, entry);
|
|
88
117
|
element.remove();
|
|
89
118
|
}
|