@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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/board/render/script-resolution.ts
|
|
2
|
+
const RESOLUTIONS = [
|
|
3
|
+
.125,
|
|
4
|
+
.25,
|
|
5
|
+
.5,
|
|
6
|
+
1,
|
|
7
|
+
2,
|
|
8
|
+
4
|
|
9
|
+
];
|
|
10
|
+
const MAX_EDGE = 2048;
|
|
11
|
+
const MAX_PIXELS = 2097152;
|
|
12
|
+
function scriptResolution(width, height, demand, current = 0) {
|
|
13
|
+
const target = Number.isFinite(demand) && demand > 0 ? demand : 1;
|
|
14
|
+
const tier = current > 0 && target <= current * 1.15 && target >= current * .4 ? current : RESOLUTIONS.find((value) => value >= target) ?? 4;
|
|
15
|
+
return Math.min(tier, MAX_EDGE / Math.max(1, width, height), Math.sqrt(MAX_PIXELS / Math.max(1, width * height)));
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { scriptResolution };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { WorldPoint } from "../model/geometry.js";
|
|
2
2
|
import { BoardRenderStyle, BoardShadowStyle } from "./style.js";
|
|
3
|
-
import { Graphics } from "pixi.js";
|
|
3
|
+
import { Graphics, Texture } from "pixi.js";
|
|
4
4
|
//#region src/board/render/surface.d.ts
|
|
5
5
|
declare const PIXEL_STEP = 2;
|
|
6
6
|
type CardRect = {
|
|
@@ -10,6 +10,13 @@ type CardRect = {
|
|
|
10
10
|
height: number;
|
|
11
11
|
};
|
|
12
12
|
declare function traceCardOutline(graphics: Graphics, rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, grow?: number): Graphics;
|
|
13
|
+
type CardCrop = {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
w: number;
|
|
17
|
+
h: number;
|
|
18
|
+
};
|
|
19
|
+
declare function fillCardPicture(graphics: Graphics, texture: Texture, picture: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, crop?: CardCrop | null): Graphics;
|
|
13
20
|
declare function tracePixelRect(graphics: Graphics, x: number, y: number, width: number, height: number, step: number): Graphics;
|
|
14
21
|
declare function cardOutlinePoints(rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, grow?: number): WorldPoint[];
|
|
15
22
|
declare function drawCardShadow(graphics: Graphics, rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, shadow: BoardShadowStyle | null): void;
|
|
@@ -31,4 +38,4 @@ declare function isBoardCard(item: {
|
|
|
31
38
|
type: string;
|
|
32
39
|
}): boolean;
|
|
33
40
|
//#endregion
|
|
34
|
-
export { CardElevation, CardRect, CardSurface, PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, isBoardCard, traceCardOutline, tracePixelRect };
|
|
41
|
+
export { CardCrop, CardElevation, CardRect, CardSurface, PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, fillCardPicture, isBoardCard, traceCardOutline, tracePixelRect };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { worldPoint } from "../model/geometry.js";
|
|
2
2
|
import { roundRectPoints } from "./stroke.js";
|
|
3
|
+
import { Matrix } from "pixi.js";
|
|
3
4
|
//#region src/board/render/surface.ts
|
|
4
5
|
const PIXEL_STEP = 2;
|
|
5
6
|
const SHADOW_LAYERS = 5;
|
|
@@ -12,6 +13,19 @@ function traceCardOutline(graphics, rect, style, grow = 0) {
|
|
|
12
13
|
const radius = Math.min(Math.max(0, style.radius + grow), width / 2, height / 2);
|
|
13
14
|
return radius > 0 ? graphics.roundRect(x, y, width, height, radius) : graphics.rect(x, y, width, height);
|
|
14
15
|
}
|
|
16
|
+
function fillCardPicture(graphics, texture, picture, style, crop) {
|
|
17
|
+
const sampling = texture.source.style;
|
|
18
|
+
if (sampling.addressMode !== "mirror-repeat") {
|
|
19
|
+
sampling.addressMode = "mirror-repeat";
|
|
20
|
+
sampling.update();
|
|
21
|
+
}
|
|
22
|
+
const matrix = crop ? new Matrix(1 / crop.w, 0, 0, 1 / crop.h, -crop.x / crop.w, -crop.y / crop.h) : void 0;
|
|
23
|
+
return traceCardOutline(graphics, picture, style).fill({
|
|
24
|
+
texture,
|
|
25
|
+
textureSpace: "local",
|
|
26
|
+
...matrix ? { matrix } : {}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
15
29
|
function tracePixelRect(graphics, x, y, width, height, step) {
|
|
16
30
|
const s = Math.min(step, width / 4, height / 4);
|
|
17
31
|
if (s <= 0) return graphics.rect(x, y, width, height);
|
|
@@ -129,4 +143,4 @@ function isBoardCard(item) {
|
|
|
129
143
|
return !FREE_FORM_TYPES.has(item.type);
|
|
130
144
|
}
|
|
131
145
|
//#endregion
|
|
132
|
-
export { PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, isBoardCard, traceCardOutline, tracePixelRect };
|
|
146
|
+
export { PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, fillCardPicture, isBoardCard, traceCardOutline, tracePixelRect };
|
|
@@ -6,15 +6,12 @@ declare function getBoardResolution(size?: {
|
|
|
6
6
|
}, devicePixelRatio?: number): number;
|
|
7
7
|
declare function textZoomBucket(zoom: number): number;
|
|
8
8
|
declare function boardCardDetailed(zoom: number): boolean;
|
|
9
|
-
/**
|
|
10
|
-
declare function
|
|
11
|
-
|
|
12
|
-
declare function syncTextResolution(text: Text, state: {
|
|
13
|
-
resolution: number;
|
|
14
|
-
}, zoom: number): void;
|
|
9
|
+
/** Exact screen density at rest; reuse or step the raster while moving. */
|
|
10
|
+
declare function boardTextResolution(target: number, current: number, settled: boolean): number;
|
|
11
|
+
declare function syncTextResolution(text: Text, resolution: number): void;
|
|
15
12
|
/** Defer expensive Pixi word-wrap rasterisation during a live resize. */
|
|
16
13
|
declare function syncTextWrapWidth(text: Text, state: {
|
|
17
14
|
wrapWidth: number;
|
|
18
15
|
}, width: number, defer: boolean): void;
|
|
19
16
|
//#endregion
|
|
20
|
-
export { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth,
|
|
17
|
+
export { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket };
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
//#region src/board/render/text-resolution.ts
|
|
2
2
|
const MAX_BOARD_RESOLUTION = 3;
|
|
3
|
-
const MAX_BOARD_PIXELS =
|
|
4
|
-
const
|
|
3
|
+
const MAX_BOARD_PIXELS = 1e7;
|
|
4
|
+
const MIN_TEXT_RESOLUTION = .125;
|
|
5
|
+
const MAX_TEXT_EDGE = 4096;
|
|
6
|
+
const TEXT_UPSCALE_TOLERANCE = 1.25;
|
|
5
7
|
function getBoardResolution(size, devicePixelRatio = globalThis.devicePixelRatio) {
|
|
6
8
|
const density = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;
|
|
7
9
|
const pixels = size ? size.width * size.height : 0;
|
|
8
|
-
const budget = pixels > 0 ? Math.sqrt(MAX_BOARD_PIXELS / pixels) : MAX_BOARD_RESOLUTION;
|
|
10
|
+
const budget = pixels > 0 ? Math.max(1, Math.sqrt(MAX_BOARD_PIXELS / pixels)) : MAX_BOARD_RESOLUTION;
|
|
9
11
|
return Math.min(density, MAX_BOARD_RESOLUTION, budget);
|
|
10
12
|
}
|
|
11
|
-
|
|
12
|
-
* Zoom buckets for text re-rasterisation. Pixi Text is a bitmap: at high zoom it
|
|
13
|
-
* looks soft unless we raise its resolution. We quantise zoom into a few
|
|
14
|
-
* buckets so small zoom changes don't thrash texture regeneration.
|
|
15
|
-
*/
|
|
16
|
-
const TEXT_ZOOM_BUCKETS = [
|
|
13
|
+
const ZOOM_BUCKETS = [
|
|
17
14
|
.5,
|
|
18
15
|
1,
|
|
19
16
|
1.5,
|
|
@@ -23,23 +20,24 @@ const TEXT_ZOOM_BUCKETS = [
|
|
|
23
20
|
];
|
|
24
21
|
function textZoomBucket(zoom) {
|
|
25
22
|
const clamped = Math.max(.1, Math.min(4, zoom));
|
|
26
|
-
for (const bucket of
|
|
27
|
-
return
|
|
23
|
+
for (const bucket of ZOOM_BUCKETS) if (clamped <= bucket) return bucket;
|
|
24
|
+
return ZOOM_BUCKETS[ZOOM_BUCKETS.length - 1] ?? 4;
|
|
28
25
|
}
|
|
29
26
|
function boardCardDetailed(zoom) {
|
|
30
27
|
return textZoomBucket(zoom) >= 1;
|
|
31
28
|
}
|
|
32
|
-
/**
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
/** Exact screen density at rest; reuse or step the raster while moving. */
|
|
30
|
+
function boardTextResolution(target, current, settled) {
|
|
31
|
+
if (!Number.isFinite(target) || target <= 0) return current > 0 ? current : 1;
|
|
32
|
+
if (settled || !(current > 0)) return target;
|
|
33
|
+
if (target <= current * TEXT_UPSCALE_TOLERANCE && target >= current / 2) return current;
|
|
34
|
+
return 2 ** Math.ceil(Math.log2(target));
|
|
36
35
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
text.resolution =
|
|
42
|
-
state.resolution = resolution;
|
|
36
|
+
function syncTextResolution(text, resolution) {
|
|
37
|
+
const { width, height } = text.bounds;
|
|
38
|
+
const limit = MAX_TEXT_EDGE / Math.max(1, Math.ceil(width + text.style.padding * 2), Math.ceil(height + text.style.padding * 2));
|
|
39
|
+
const next = Math.min(limit, Math.max(MIN_TEXT_RESOLUTION, resolution));
|
|
40
|
+
if (text.resolution !== next) text.resolution = next;
|
|
43
41
|
}
|
|
44
42
|
/** Defer expensive Pixi word-wrap rasterisation during a live resize. */
|
|
45
43
|
function syncTextWrapWidth(text, state, width, defer) {
|
|
@@ -48,4 +46,4 @@ function syncTextWrapWidth(text, state, width, defer) {
|
|
|
48
46
|
state.wrapWidth = width;
|
|
49
47
|
}
|
|
50
48
|
//#endregion
|
|
51
|
-
export { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth,
|
|
49
|
+
export { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { applyBoardDelta, applyBoardPatchToDocument, parseBoardDocument } from "../../protocol/dist/board-patch.js";
|
|
2
2
|
import { createBoardEntityId } from "../model/items/id.js";
|
|
3
|
+
import { shareBoardDocument } from "../model/document-share.js";
|
|
3
4
|
//#region src/board/replica/index.ts
|
|
4
5
|
function memoryStorage() {
|
|
5
6
|
return {
|
|
@@ -118,7 +119,9 @@ function createBoardReplica(options) {
|
|
|
118
119
|
title = result.title;
|
|
119
120
|
if (readPlaybackRevision === playbackRevision) playback = result.playback;
|
|
120
121
|
loaded = true;
|
|
121
|
-
|
|
122
|
+
const next = shareBoardDocument(server, parsed.document);
|
|
123
|
+
if (next === server && result.version === version) return;
|
|
124
|
+
adoptServer(next, result.version, true);
|
|
122
125
|
}
|
|
123
126
|
function refresh() {
|
|
124
127
|
if (refreshing) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ModelFormat } from "../../protocol/dist/fs/mime.js";
|
|
2
2
|
import { BoardSceneItem } from "../model/scene.js";
|
|
3
|
+
import { BoardAssetUrlOptions } from "../model/media-playback.js";
|
|
3
4
|
import "../model/index.js";
|
|
4
5
|
import { LruBudget } from "./lru.js";
|
|
5
6
|
import { BoardImageTexturePool } from "./texture-pool.js";
|
|
@@ -7,7 +8,12 @@ import { Texture } from "pixi.js";
|
|
|
7
8
|
//#region src/board/stage/assets.d.ts
|
|
8
9
|
type BoardUrlMedia = "image" | "video";
|
|
9
10
|
type BoardAssetMedia = BoardUrlMedia | "model";
|
|
11
|
+
declare function boardTextureEdge(item: BoardSceneItem, scale: number): number;
|
|
10
12
|
declare function boardAssetKey(item: BoardSceneItem): string | null;
|
|
13
|
+
type Size = {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
11
17
|
type BoardModelPreviewRequest = {
|
|
12
18
|
key: string;
|
|
13
19
|
src: string;
|
|
@@ -21,24 +27,22 @@ type BoardModelPreviews = {
|
|
|
21
27
|
type BoardAssetManager = {
|
|
22
28
|
assetKey: (item: BoardSceneItem) => string | null;
|
|
23
29
|
invalidatePath: (path: string) => void;
|
|
24
|
-
requestItem: (item: BoardSceneItem) => void;
|
|
30
|
+
requestItem: (item: BoardSceneItem, edge?: number) => void;
|
|
25
31
|
getTexture: (key: string) => Texture | null;
|
|
26
|
-
getNaturalSize: (key: string) =>
|
|
27
|
-
width: number;
|
|
28
|
-
height: number;
|
|
29
|
-
} | null;
|
|
32
|
+
getNaturalSize: (key: string) => Size | null;
|
|
30
33
|
hasError: (key: string) => boolean;
|
|
31
34
|
acquire: (key: string) => void;
|
|
32
35
|
release: (key: string) => void;
|
|
33
36
|
withTextures: <T>(items: BoardSceneItem[], use: (textures: Map<string, Texture>) => T | Promise<T>, options?: {
|
|
34
37
|
timeoutMs?: number;
|
|
38
|
+
edge?: (item: BoardSceneItem) => number;
|
|
35
39
|
}) => Promise<T>;
|
|
36
|
-
subscribe: (listener: () => void) => () => void;
|
|
40
|
+
subscribe: (listener: (key: string) => void) => () => void;
|
|
37
41
|
destroy: () => void;
|
|
38
42
|
};
|
|
39
43
|
type BoardAssetManagerOptions = {
|
|
40
44
|
/** Resolve a Space file path to a URL a texture can load from; null when unavailable. */
|
|
41
|
-
resolveFileUrl: (path: string) => Promise<string | null>;
|
|
45
|
+
resolveFileUrl: (path: string, options?: BoardAssetUrlOptions) => Promise<string | null>;
|
|
42
46
|
concurrency?: number;
|
|
43
47
|
videoConcurrency?: number;
|
|
44
48
|
loadVideoPreviews?: boolean;
|
|
@@ -51,4 +55,4 @@ type BoardAssetManagerOptions = {
|
|
|
51
55
|
};
|
|
52
56
|
declare function createBoardAssetManager(options: BoardAssetManagerOptions): BoardAssetManager;
|
|
53
57
|
//#endregion
|
|
54
|
-
export { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, boardAssetKey, createBoardAssetManager };
|
|
58
|
+
export { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, boardAssetKey, boardTextureEdge, createBoardAssetManager };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MEDIA_VARIANT_SIZES, imageVariantUrl, mediaVariantSize, probeMediaInfo, variantSource, videoFrameUrl } from "../../media.js";
|
|
2
2
|
import { parseBoardMediaSource } from "../../protocol/dist/board-model.js";
|
|
3
|
-
import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
|
|
3
|
+
import { featuredTaskArtifact, taskArtifactPreviewUrl } from "../../protocol/dist/board-task.js";
|
|
4
4
|
import { boardModelFormat, isBoardModelItem } from "../model/media.js";
|
|
5
5
|
import { boardImageKeySource, imageAssetKey } from "../model/image-key.js";
|
|
6
6
|
import { loadVideoThumbnailTexture, videoTextureNaturalSize } from "../render/video-thumbnail.js";
|
|
@@ -8,12 +8,23 @@ import { DEFAULT_LRU_BUDGET, selectLruEvictions } from "./lru.js";
|
|
|
8
8
|
import { boardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
|
|
9
9
|
import { Texture } from "pixi.js";
|
|
10
10
|
//#region src/board/stage/assets.ts
|
|
11
|
-
const TASK_TEXTURE_SIZE = 1536;
|
|
12
11
|
function taskPreviewKey(artifact) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
|
|
12
|
+
const cover = taskArtifactPreviewUrl(artifact);
|
|
13
|
+
if (cover) return `url:${cover}`;
|
|
14
|
+
if (artifact?.type !== "video") return null;
|
|
15
|
+
const frame = videoFrameUrl(artifact.url, 768);
|
|
16
|
+
return frame ? `url:${frame}` : `video-url:${encodeURIComponent(artifact.url)}`;
|
|
17
|
+
}
|
|
18
|
+
const LARGEST_VARIANT = MEDIA_VARIANT_SIZES[MEDIA_VARIANT_SIZES.length - 1];
|
|
19
|
+
function boardTextureEdge(item, scale) {
|
|
20
|
+
const crop = item.type === "image" ? item.props.crop : void 0;
|
|
21
|
+
const pixels = Math.max(item.frame.width / (crop?.w || 1), item.frame.height / (crop?.h || 1)) * scale;
|
|
22
|
+
return pixels > LARGEST_VARIANT ? Number.POSITIVE_INFINITY : mediaVariantSize(pixels);
|
|
23
|
+
}
|
|
24
|
+
function variantUrl(source, edge) {
|
|
25
|
+
if (!(edge <= LARGEST_VARIANT)) return null;
|
|
26
|
+
const url = imageVariantUrl(source, edge);
|
|
27
|
+
return url && url !== source ? url : null;
|
|
17
28
|
}
|
|
18
29
|
const STILL_MEDIA = ["video", "model"];
|
|
19
30
|
function stillKey(media, src, mtimeMs) {
|
|
@@ -70,7 +81,7 @@ function footprintOf(texture) {
|
|
|
70
81
|
return (texture.width || 0) * (texture.height || 0) * 4;
|
|
71
82
|
}
|
|
72
83
|
function createBoardAssetManager(options) {
|
|
73
|
-
const concurrency = Math.max(1, options.concurrency ??
|
|
84
|
+
const concurrency = Math.max(1, options.concurrency ?? 6);
|
|
74
85
|
const limits = {
|
|
75
86
|
image: concurrency,
|
|
76
87
|
video: Math.max(1, Math.min(options.videoConcurrency ?? 2, concurrency)),
|
|
@@ -103,12 +114,20 @@ function createBoardAssetManager(options) {
|
|
|
103
114
|
});
|
|
104
115
|
if (url) pendingUnloads.set(pendingKey, promise);
|
|
105
116
|
}
|
|
117
|
+
const retiring = /* @__PURE__ */ new Set();
|
|
118
|
+
function retire(key, url, texture) {
|
|
119
|
+
const release = () => {
|
|
120
|
+
if (retiring.delete(release)) releaseTexture(key, url, texture);
|
|
121
|
+
};
|
|
122
|
+
retiring.add(release);
|
|
123
|
+
if (typeof requestAnimationFrame === "function") requestAnimationFrame(release);
|
|
124
|
+
else setTimeout(release, 0);
|
|
125
|
+
}
|
|
106
126
|
const resolveFileUrl = options.resolveFileUrl;
|
|
107
127
|
const entries = /* @__PURE__ */ new Map();
|
|
108
128
|
const pathVersions = /* @__PURE__ */ new Map();
|
|
109
|
-
const
|
|
129
|
+
const starters = /* @__PURE__ */ new Map();
|
|
110
130
|
const failedModels = /* @__PURE__ */ new Set();
|
|
111
|
-
const inflight = /* @__PURE__ */ new Set();
|
|
112
131
|
const queue = [];
|
|
113
132
|
const listeners = /* @__PURE__ */ new Set();
|
|
114
133
|
let active = 0;
|
|
@@ -118,8 +137,8 @@ function createBoardAssetManager(options) {
|
|
|
118
137
|
model: 0
|
|
119
138
|
};
|
|
120
139
|
let disposed = false;
|
|
121
|
-
function notify() {
|
|
122
|
-
for (const listener of listeners) listener();
|
|
140
|
+
function notify(key) {
|
|
141
|
+
for (const listener of listeners) listener(key);
|
|
123
142
|
}
|
|
124
143
|
function managedAssetKey(item) {
|
|
125
144
|
const key = boardAssetKey(item);
|
|
@@ -141,8 +160,13 @@ function createBoardAssetManager(options) {
|
|
|
141
160
|
let entry = entries.get(key);
|
|
142
161
|
if (!entry) {
|
|
143
162
|
entry = {
|
|
163
|
+
media: keySource(key)?.media ?? "image",
|
|
164
|
+
source: null,
|
|
144
165
|
url: null,
|
|
145
166
|
texture: null,
|
|
167
|
+
edge: 0,
|
|
168
|
+
want: 0,
|
|
169
|
+
natural: void 0,
|
|
146
170
|
refs: 0,
|
|
147
171
|
loading: false,
|
|
148
172
|
error: false,
|
|
@@ -162,21 +186,15 @@ function createBoardAssetManager(options) {
|
|
|
162
186
|
entry.retryTimer = null;
|
|
163
187
|
if (disposed || entries.get(key) !== entry) return;
|
|
164
188
|
if (entry.refs <= 0 || entry.texture || entry.loading) return;
|
|
165
|
-
const
|
|
166
|
-
if (!
|
|
189
|
+
const start = starters.get(key);
|
|
190
|
+
if (!start) return;
|
|
167
191
|
entry.error = false;
|
|
168
|
-
|
|
169
|
-
queue.push({
|
|
170
|
-
key,
|
|
171
|
-
load
|
|
172
|
-
});
|
|
173
|
-
pump();
|
|
174
|
-
}
|
|
192
|
+
start(entry);
|
|
175
193
|
}, delay);
|
|
176
194
|
}
|
|
177
195
|
function evict(key, entry) {
|
|
178
196
|
clearRetry(entry);
|
|
179
|
-
|
|
197
|
+
starters.delete(key);
|
|
180
198
|
if (entry.url || entry.texture) releaseTexture(key, entry.url ?? "", entry.texture);
|
|
181
199
|
entries.delete(key);
|
|
182
200
|
}
|
|
@@ -193,69 +211,128 @@ function createBoardAssetManager(options) {
|
|
|
193
211
|
if (entry) evict(key, entry);
|
|
194
212
|
}
|
|
195
213
|
}
|
|
196
|
-
function
|
|
214
|
+
function adopt(key, entry, loaded, error) {
|
|
197
215
|
entry.loading = false;
|
|
198
|
-
inflight.delete(key);
|
|
199
|
-
active -= 1;
|
|
200
|
-
const media = keySource(key)?.media;
|
|
201
|
-
if (media) activeByMedia[media] -= 1;
|
|
202
216
|
if (entries.get(key) !== entry) {
|
|
203
|
-
if (
|
|
204
|
-
if (!disposed) pump();
|
|
217
|
+
if (loaded) releaseTexture(key, loaded.url, loaded.texture);
|
|
205
218
|
return;
|
|
206
219
|
}
|
|
207
|
-
if (
|
|
220
|
+
if (loaded) {
|
|
221
|
+
const replaced = entry.texture;
|
|
222
|
+
const replacedUrl = entry.url ?? "";
|
|
223
|
+
const { texture } = loaded;
|
|
208
224
|
entry.error = false;
|
|
209
225
|
entry.attempts = 0;
|
|
210
226
|
entry.texture = texture;
|
|
227
|
+
entry.url = loaded.url;
|
|
228
|
+
entry.edge = loaded.edge;
|
|
229
|
+
if (loaded.edge === Number.POSITIVE_INFINITY && !variantSource(loaded.url)) entry.natural = videoTextureNaturalSize(texture) ?? {
|
|
230
|
+
width: texture.width,
|
|
231
|
+
height: texture.height
|
|
232
|
+
};
|
|
211
233
|
entry.lastUsedAt = now();
|
|
212
234
|
if (entry.refs <= 0) trim();
|
|
213
|
-
else notify();
|
|
214
|
-
|
|
215
|
-
entry
|
|
216
|
-
|
|
217
|
-
entry.retryAt = now() + Math.min(1e3 * 2 ** entry.attempts, MAX_RETRY_DELAY);
|
|
218
|
-
console.warn(`[board] failed to load preview for ${key}`, error);
|
|
219
|
-
if (media === "model") failedModels.add(key);
|
|
220
|
-
if (entry.refs <= 0) evict(key, entry);
|
|
221
|
-
else {
|
|
222
|
-
if (media !== "model") scheduleRetry(key, entry);
|
|
223
|
-
notify();
|
|
224
|
-
}
|
|
235
|
+
else notify(key);
|
|
236
|
+
if (replaced) retire(key, replacedUrl, replaced);
|
|
237
|
+
refine(key, entry);
|
|
238
|
+
return;
|
|
225
239
|
}
|
|
226
|
-
if (
|
|
240
|
+
if (entry.texture) {
|
|
241
|
+
entry.edge = Number.POSITIVE_INFINITY;
|
|
242
|
+
notify(key);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const media = entry.media;
|
|
246
|
+
entry.error = true;
|
|
247
|
+
entry.attempts += 1;
|
|
248
|
+
entry.retryAt = now() + Math.min(1e3 * 2 ** entry.attempts, MAX_RETRY_DELAY);
|
|
249
|
+
console.warn(`[board] failed to load preview for ${key}`, error);
|
|
250
|
+
if (media === "model") failedModels.add(key);
|
|
251
|
+
if (entry.refs <= 0) evict(key, entry);
|
|
252
|
+
else {
|
|
253
|
+
if (media !== "model") scheduleRetry(key, entry);
|
|
254
|
+
notify(key);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function refinable(entry) {
|
|
258
|
+
return entry.media === "image" && entry.refs > 0 && entry.texture !== null && entry.source !== null && entry.want > entry.edge;
|
|
259
|
+
}
|
|
260
|
+
function refine(key, entry) {
|
|
261
|
+
if (refinable(entry) && !entry.loading && entry.source) enqueue(key, "image", textureLoad("image", entry.source));
|
|
262
|
+
}
|
|
263
|
+
function enqueue(key, media, load) {
|
|
264
|
+
if (queue.some((task) => task.key === key)) return;
|
|
265
|
+
queue.push({
|
|
266
|
+
key,
|
|
267
|
+
media,
|
|
268
|
+
load
|
|
269
|
+
});
|
|
270
|
+
pump();
|
|
227
271
|
}
|
|
228
272
|
function pump() {
|
|
229
273
|
while (!disposed && active < concurrency && queue.length > 0) {
|
|
230
|
-
const taskIndex = queue.findIndex(({
|
|
231
|
-
const media = keySource(key)?.media;
|
|
232
|
-
return !media || activeByMedia[media] < limits[media];
|
|
233
|
-
});
|
|
274
|
+
const taskIndex = queue.findIndex(({ media }) => activeByMedia[media] < limits[media]);
|
|
234
275
|
if (taskIndex < 0) return;
|
|
235
276
|
const [task] = queue.splice(taskIndex, 1);
|
|
236
277
|
if (!task) continue;
|
|
237
278
|
const entry = entries.get(task.key);
|
|
238
|
-
|
|
239
|
-
if (!entry || !source || entry.texture || entry.loading) continue;
|
|
279
|
+
if (!entry || entry.loading || entry.texture && !refinable(entry)) continue;
|
|
240
280
|
entry.loading = true;
|
|
241
|
-
inflight.add(task.key);
|
|
242
281
|
active += 1;
|
|
243
|
-
activeByMedia[
|
|
244
|
-
|
|
282
|
+
activeByMedia[task.media] += 1;
|
|
283
|
+
const done = (loaded, error) => {
|
|
284
|
+
active -= 1;
|
|
285
|
+
activeByMedia[task.media] -= 1;
|
|
286
|
+
adopt(task.key, entry, loaded, error);
|
|
287
|
+
if (!disposed) pump();
|
|
288
|
+
};
|
|
289
|
+
task.load(entry).then((loaded) => done(loaded), (error) => done(null, error));
|
|
245
290
|
}
|
|
246
291
|
}
|
|
247
|
-
function
|
|
248
|
-
const getUrl = source.kind === "url" ? () => Promise.resolve(source.value) : () => resolveFileUrl(source.value);
|
|
292
|
+
function textureLoad(media, source) {
|
|
249
293
|
return async (entry) => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const
|
|
294
|
+
entry.source = source;
|
|
295
|
+
const edge = entry.want;
|
|
296
|
+
const variant = media === "image" ? variantUrl(source, edge) : null;
|
|
297
|
+
const url = variant ?? source;
|
|
298
|
+
const pending = pendingUnloads.get(`${media}:${url}`);
|
|
254
299
|
if (pending) await pending;
|
|
255
300
|
if (disposed) return null;
|
|
256
|
-
|
|
301
|
+
const texture = await loadTexture(url, media);
|
|
302
|
+
if (!texture) return null;
|
|
303
|
+
return {
|
|
304
|
+
texture,
|
|
305
|
+
url,
|
|
306
|
+
edge: !variant || Math.max(texture.width, texture.height) < edge ? Number.POSITIVE_INFINITY : edge
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function resolveFile(key, media, path) {
|
|
311
|
+
return (entry) => {
|
|
312
|
+
const fresh = entry.attempts > 0;
|
|
313
|
+
entry.loading = true;
|
|
314
|
+
resolveFileUrl(path, fresh ? { fresh } : void 0).then((url) => {
|
|
315
|
+
if (disposed || entries.get(key) !== entry) return;
|
|
316
|
+
if (entry.refs <= 0) return evict(key, entry);
|
|
317
|
+
if (!url) return adopt(key, entry, null, /* @__PURE__ */ new Error(`Unavailable file: ${path}`));
|
|
318
|
+
entry.loading = false;
|
|
319
|
+
enqueue(key, media, textureLoad(media, url));
|
|
320
|
+
}, (error) => {
|
|
321
|
+
if (!disposed) adopt(key, entry, null, error);
|
|
322
|
+
});
|
|
257
323
|
};
|
|
258
324
|
}
|
|
325
|
+
function probeNaturalSize(key, entry, source) {
|
|
326
|
+
entry.natural = null;
|
|
327
|
+
probeMediaInfo(source, { type: "image" }).then(({ width, height }) => {
|
|
328
|
+
if (disposed || entries.get(key) !== entry || !width || !height) return;
|
|
329
|
+
entry.natural = {
|
|
330
|
+
width,
|
|
331
|
+
height
|
|
332
|
+
};
|
|
333
|
+
notify(key);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
259
336
|
function modelPreviewable(item) {
|
|
260
337
|
const previews = options.modelPreviews;
|
|
261
338
|
if (!previews) return false;
|
|
@@ -274,20 +351,35 @@ function createBoardAssetManager(options) {
|
|
|
274
351
|
format,
|
|
275
352
|
size: snapshot?.size
|
|
276
353
|
};
|
|
277
|
-
return async () =>
|
|
354
|
+
return async () => ({
|
|
355
|
+
texture: Texture.from(await previews.load(request)),
|
|
356
|
+
url: "",
|
|
357
|
+
edge: Number.POSITIVE_INFINITY
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
function starter(item, key, source) {
|
|
361
|
+
if (source.media === "model") {
|
|
362
|
+
const load = modelLoader(item, key);
|
|
363
|
+
return load ? () => enqueue(key, "model", load) : null;
|
|
364
|
+
}
|
|
365
|
+
const media = source.media;
|
|
366
|
+
if (source.kind === "file") return resolveFile(key, media, source.value);
|
|
367
|
+
const load = textureLoad(media, source.value);
|
|
368
|
+
return () => enqueue(key, media, load);
|
|
278
369
|
}
|
|
279
370
|
const manager = {
|
|
280
371
|
assetKey: managedAssetKey,
|
|
281
372
|
invalidatePath(path) {
|
|
282
373
|
if (disposed) return;
|
|
283
|
-
|
|
374
|
+
const stale = [...entries.keys()].filter((key) => {
|
|
284
375
|
const source = keySource(key);
|
|
285
376
|
return source?.kind === "file" && source.value === path;
|
|
286
|
-
})
|
|
377
|
+
});
|
|
378
|
+
if (stale.length === 0) return;
|
|
287
379
|
pathVersions.set(path, (pathVersions.get(path) ?? 0) + 1);
|
|
288
|
-
notify();
|
|
380
|
+
for (const key of stale) notify(key);
|
|
289
381
|
},
|
|
290
|
-
requestItem(item) {
|
|
382
|
+
requestItem(item, edge = Number.POSITIVE_INFINITY) {
|
|
291
383
|
if (disposed) return;
|
|
292
384
|
const key = managedAssetKey(item);
|
|
293
385
|
if (!key || failedModels.has(key)) return;
|
|
@@ -295,22 +387,16 @@ function createBoardAssetManager(options) {
|
|
|
295
387
|
if (!source) return;
|
|
296
388
|
const entry = ensureEntry(key);
|
|
297
389
|
entry.lastUsedAt = now();
|
|
298
|
-
if (
|
|
390
|
+
if (edge > entry.want) entry.want = edge;
|
|
391
|
+
if (entry.texture) return refine(key, entry);
|
|
392
|
+
if (entry.loading || entry.retryTimer) return;
|
|
299
393
|
if (entry.error && now() < entry.retryAt) return;
|
|
300
|
-
entry.error = false;
|
|
301
|
-
clearRetry(entry);
|
|
302
394
|
if (queue.some((queued) => queued.key === key)) return;
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
loaders.set(key, load);
|
|
309
|
-
queue.push({
|
|
310
|
-
key,
|
|
311
|
-
load
|
|
312
|
-
});
|
|
313
|
-
pump();
|
|
395
|
+
const start = starter(item, key, source);
|
|
396
|
+
if (!start) return;
|
|
397
|
+
entry.error = false;
|
|
398
|
+
starters.set(key, start);
|
|
399
|
+
start(entry);
|
|
314
400
|
},
|
|
315
401
|
getTexture(key) {
|
|
316
402
|
const entry = entries.get(key);
|
|
@@ -319,12 +405,10 @@ function createBoardAssetManager(options) {
|
|
|
319
405
|
return entry.texture;
|
|
320
406
|
},
|
|
321
407
|
getNaturalSize(key) {
|
|
322
|
-
const
|
|
323
|
-
if (!texture) return null;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
height: texture.height
|
|
327
|
-
};
|
|
408
|
+
const entry = entries.get(key);
|
|
409
|
+
if (!entry?.texture) return null;
|
|
410
|
+
if (entry.natural === void 0 && entry.source) probeNaturalSize(key, entry, entry.source);
|
|
411
|
+
return entry.natural ?? null;
|
|
328
412
|
},
|
|
329
413
|
hasError(key) {
|
|
330
414
|
return entries.get(key)?.error ?? false;
|
|
@@ -355,10 +439,10 @@ function createBoardAssetManager(options) {
|
|
|
355
439
|
if (keys.size === 0) return use(textures);
|
|
356
440
|
for (const key of keys) manager.acquire(key);
|
|
357
441
|
try {
|
|
358
|
-
for (const item of items) manager.requestItem(item);
|
|
442
|
+
for (const item of items) manager.requestItem(item, loadOptions?.edge?.(item));
|
|
359
443
|
const settled = () => [...keys].every((key) => {
|
|
360
444
|
const entry = entries.get(key);
|
|
361
|
-
return Boolean(entry?.
|
|
445
|
+
return Boolean(entry?.error) || Boolean(entry?.texture && !refinable(entry));
|
|
362
446
|
});
|
|
363
447
|
if (!settled()) {
|
|
364
448
|
const timeoutMs = loadOptions?.timeoutMs ?? 15e3;
|
|
@@ -394,13 +478,14 @@ function createBoardAssetManager(options) {
|
|
|
394
478
|
queue.length = 0;
|
|
395
479
|
listeners.clear();
|
|
396
480
|
pathVersions.clear();
|
|
397
|
-
|
|
481
|
+
starters.clear();
|
|
398
482
|
failedModels.clear();
|
|
399
483
|
for (const [key, entry] of entries) evict(key, entry);
|
|
400
484
|
entries.clear();
|
|
485
|
+
for (const release of [...retiring]) release();
|
|
401
486
|
}
|
|
402
487
|
};
|
|
403
488
|
return manager;
|
|
404
489
|
}
|
|
405
490
|
//#endregion
|
|
406
|
-
export { boardAssetKey, createBoardAssetManager };
|
|
491
|
+
export { boardAssetKey, boardTextureEdge, createBoardAssetManager };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/board/stage/canvas-size.d.ts
|
|
2
|
+
type BoardCanvasBox = {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
density: number;
|
|
6
|
+
device: {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
} | null;
|
|
10
|
+
};
|
|
11
|
+
declare function measureBoardCanvasBox(entry: ResizeObserverEntry, density: number): BoardCanvasBox;
|
|
12
|
+
declare function boardCanvasSize(box: BoardCanvasBox, density: number): {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
resolution: number;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { BoardCanvasBox, boardCanvasSize, measureBoardCanvasBox };
|