@neta-art/cohub 12.1.1 → 12.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/board/editor/editor.d.ts +1 -0
- package/dist/board/editor/editor.js +4 -0
- package/dist/board/editor/snapshots.js +5 -57
- package/dist/board/export/index.d.ts +2 -2
- package/dist/board/export/node/fonts.js +4 -0
- package/dist/board/export/node/index.d.ts +2 -0
- package/dist/board/export/node/index.js +8 -1
- package/dist/board/export/scene.d.ts +2 -2
- package/dist/board/export/scene.js +2 -2
- package/dist/board/model/asset-key.d.ts +16 -0
- package/dist/board/model/asset-key.js +92 -0
- package/dist/board/model/export-assets.d.ts +7 -3
- package/dist/board/model/export-assets.js +34 -13
- package/dist/board/model/index.d.ts +7 -5
- package/dist/board/model/index.js +9 -7
- package/dist/board/model/items/create.js +1 -1
- package/dist/board/model/media-sizes.d.ts +28 -0
- package/dist/board/model/media-sizes.js +64 -0
- package/dist/board/model/palette.d.ts +1 -0
- package/dist/board/model/palette.js +20 -130
- package/dist/board/model/text-metrics.d.ts +8 -3
- package/dist/board/model/text-metrics.js +13 -2
- package/dist/board/model/themes.d.ts +488 -0
- package/dist/board/model/themes.js +495 -0
- package/dist/board/render/card-tag.js +2 -2
- package/dist/board/render/index.d.ts +6 -4
- package/dist/board/render/index.js +5 -3
- package/dist/board/render/lod.d.ts +22 -0
- package/dist/board/render/lod.js +27 -0
- package/dist/board/render/media-interaction.js +1 -1
- package/dist/board/render/palette.js +2 -25
- package/dist/board/render/renderers/audio-card-renderer.js +2 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +10 -0
- package/dist/board/render/renderers/custom-card-renderer.js +5 -5
- package/dist/board/render/renderers/far-plate.d.ts +11 -1
- package/dist/board/render/renderers/far-plate.js +27 -8
- package/dist/board/render/renderers/file-card-renderer.js +3 -5
- package/dist/board/render/renderers/image-card-renderer.js +32 -6
- package/dist/board/render/renderers/model-card-renderer.js +1 -1
- package/dist/board/render/renderers/shape-card-renderer.js +25 -24
- package/dist/board/render/renderers/task-card-renderer.js +7 -8
- package/dist/board/render/renderers/text-card-renderer.js +86 -41
- package/dist/board/render/renderers/video-card-renderer.js +2 -1
- package/dist/board/render/style.d.ts +7 -2
- package/dist/board/render/style.js +5 -1
- package/dist/board/render/surface.d.ts +5 -0
- package/dist/board/render/surface.js +13 -3
- package/dist/board/render/text-fit.js +8 -12
- package/dist/board/render/text-measurement.d.ts +15 -1
- package/dist/board/render/text-measurement.js +113 -20
- package/dist/board/render/text-resolution.d.ts +1 -6
- package/dist/board/render/text-resolution.js +1 -10
- package/dist/board/render/theme.d.ts +15 -0
- package/dist/board/render/theme.js +15 -0
- package/dist/board/stage/assets.d.ts +15 -4
- package/dist/board/stage/assets.js +155 -89
- package/dist/board/stage/index.d.ts +5 -5
- package/dist/board/stage/index.js +4 -4
- package/dist/board/stage/lru.d.ts +13 -9
- package/dist/board/stage/lru.js +38 -20
- package/dist/board/stage/scene.d.ts +11 -1
- package/dist/board/stage/scene.js +213 -138
- package/dist/board/stage/stage.d.ts +12 -1
- package/dist/board/stage/stage.js +97 -24
- package/dist/board/stage/texture-pool.d.ts +8 -1
- package/dist/board/stage/texture-pool.js +37 -1
- package/dist/index.js +137 -129
- package/dist/media.d.ts +3 -27
- package/dist/media.js +3 -82
- package/dist/protocol/dist/board-constants.js +3 -3
- package/dist/protocol/dist/board-layout.d.ts +17 -3
- package/dist/protocol/dist/board-layout.js +68 -11
- package/dist/protocol/dist/core/media.d.ts +35 -1
- package/dist/protocol/dist/core/media.js +123 -1
- package/dist/protocol/dist/index.d.ts +2 -2
- package/package.json +3 -2
- package/dist/board/model/image-key.d.ts +0 -9
- package/dist/board/model/image-key.js +0 -33
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import { applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
|
|
2
1
|
import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
|
|
2
|
+
import { applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
|
|
3
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
|
-
import {
|
|
8
|
+
import { boardTextureEdge } from "../model/asset-key.js";
|
|
9
|
+
import { pickBoardColor, resolveItemColor } from "../model/palette.js";
|
|
9
10
|
import { elementClientToLocal } from "../model/client-geometry.js";
|
|
10
11
|
import { isCreationBoardTool } from "../editor/tool.js";
|
|
11
12
|
import { resizeCursorForHandle } from "../editor/selection-transform.js";
|
|
12
13
|
import { parseBoardCssColor } from "../render/css-color.js";
|
|
13
|
-
import {
|
|
14
|
+
import { itemAlpha } from "../render/palette.js";
|
|
15
|
+
import { onBoardTextFontsChange } from "../render/text-measurement.js";
|
|
14
16
|
import { createBoardBackground, updateBoardBackground } from "../render/board-background.js";
|
|
15
17
|
import { getBoardResolution } from "../render/text-resolution.js";
|
|
16
18
|
import { DEFAULT_BOARD_RENDER_LABELS, getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
|
|
17
19
|
import { createBoardScriptHost } from "../render/script-host.js";
|
|
18
20
|
import { defaultBoardRenderStyle } from "../render/style.js";
|
|
19
|
-
import {
|
|
21
|
+
import { boardRenderTheme } from "../render/theme.js";
|
|
20
22
|
import { createBoardScriptObservers } from "../model/script-observers.js";
|
|
21
|
-
import {
|
|
23
|
+
import { createBoardPlayer } from "../player/index.js";
|
|
22
24
|
import { boardCanvasSize, measureBoardCanvasBox } from "./canvas-size.js";
|
|
23
25
|
import { observeDisplayDensity } from "./display-density.js";
|
|
24
26
|
import { toBoardPointerEvent } from "./pointer.js";
|
|
@@ -42,18 +44,17 @@ const LIVE_CHANNELS = [
|
|
|
42
44
|
"editing",
|
|
43
45
|
"playback"
|
|
44
46
|
];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
colors: buildFallbackShapeColors("dark"),
|
|
50
|
-
colorScheme: "dark"
|
|
51
|
-
};
|
|
52
|
-
}
|
|
47
|
+
const DEFAULT_THEME = {
|
|
48
|
+
key: "dark",
|
|
49
|
+
...boardRenderTheme("dark")
|
|
50
|
+
};
|
|
53
51
|
const NO_IDS = /* @__PURE__ */ new Set();
|
|
54
52
|
const MARCH_STEP_MS = 160;
|
|
55
53
|
const MARCH_SMOOTH_MS = 40;
|
|
56
54
|
const TEXTURE_SETTLE_MS = 150;
|
|
55
|
+
const ADOPT_PIXELS_PER_FRAME = 4194304;
|
|
56
|
+
const LOAD_LEAD_MS = 300;
|
|
57
|
+
const CULL_PADDING = 96;
|
|
57
58
|
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`;
|
|
58
59
|
const defaultStyles = /* @__PURE__ */ new Map();
|
|
59
60
|
function styleOf(theme) {
|
|
@@ -66,7 +67,7 @@ function styleOf(theme) {
|
|
|
66
67
|
}
|
|
67
68
|
function mountBoardStage(host, options) {
|
|
68
69
|
const { editor, assets } = options;
|
|
69
|
-
const theme = options.theme ??
|
|
70
|
+
const theme = options.theme ?? (() => DEFAULT_THEME);
|
|
70
71
|
const resolveRenderer = options.renderers ?? getBoardCardRenderer;
|
|
71
72
|
let app = null;
|
|
72
73
|
let world = null;
|
|
@@ -115,6 +116,8 @@ function mountBoardStage(host, options) {
|
|
|
115
116
|
let resizeFrame = 0;
|
|
116
117
|
let canvasBox = null;
|
|
117
118
|
let cardEpoch = 0;
|
|
119
|
+
let fontEpoch = 0;
|
|
120
|
+
let shownEditingId = null;
|
|
118
121
|
let surface = {
|
|
119
122
|
width: 0,
|
|
120
123
|
height: 0
|
|
@@ -192,6 +195,49 @@ function mountBoardStage(host, options) {
|
|
|
192
195
|
}
|
|
193
196
|
}, TEXTURE_SETTLE_MS);
|
|
194
197
|
}
|
|
198
|
+
function cullView(camera) {
|
|
199
|
+
const view = visibleWorldRect(camera, surface.width, surface.height);
|
|
200
|
+
const pad = CULL_PADDING / camera.zoom;
|
|
201
|
+
return {
|
|
202
|
+
x: view.x - pad,
|
|
203
|
+
y: view.y - pad,
|
|
204
|
+
width: view.width + pad * 2,
|
|
205
|
+
height: view.height + pad * 2
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const arrivals = /* @__PURE__ */ new Set();
|
|
209
|
+
function adoptArrivals() {
|
|
210
|
+
let pixels = 0;
|
|
211
|
+
for (const key of arrivals) {
|
|
212
|
+
const texture = assets.getTexture(key);
|
|
213
|
+
const size = texture ? texture.width * texture.height : 0;
|
|
214
|
+
if (pixels > 0 && pixels + size > ADOPT_PIXELS_PER_FRAME) {
|
|
215
|
+
scheduleTick();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
pixels += size;
|
|
219
|
+
arrivals.delete(key);
|
|
220
|
+
for (const id of scene?.holders(key) ?? []) {
|
|
221
|
+
staleIds.add(id);
|
|
222
|
+
naturalIds.add(id);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
let lastCenter = null;
|
|
227
|
+
function loadFocus(center, camera) {
|
|
228
|
+
const time = performance.now();
|
|
229
|
+
const previous = lastCenter;
|
|
230
|
+
lastCenter = {
|
|
231
|
+
...center,
|
|
232
|
+
time
|
|
233
|
+
};
|
|
234
|
+
const elapsed = previous ? time - previous.time : 0;
|
|
235
|
+
if (!previous || elapsed <= 0 || elapsed > 100) return center;
|
|
236
|
+
const lead = LOAD_LEAD_MS / elapsed;
|
|
237
|
+
const reach = Math.max(surface.width, surface.height) / 2 / camera.zoom;
|
|
238
|
+
const ahead = (from, to) => to + Math.max(-reach, Math.min(reach, (to - from) * lead));
|
|
239
|
+
return worldPoint(ahead(previous.x, center.x), ahead(previous.y, center.y));
|
|
240
|
+
}
|
|
195
241
|
const naturalIds = /* @__PURE__ */ new Set();
|
|
196
242
|
let liveVersion = -1;
|
|
197
243
|
function adoptNaturalSizes() {
|
|
@@ -237,11 +283,12 @@ function mountBoardStage(host, options) {
|
|
|
237
283
|
hoveredId: editor.hoverId,
|
|
238
284
|
resizingIds: interaction.type === "resizing" ? new Set(interaction.origin.keys()) : /* @__PURE__ */ new Set(),
|
|
239
285
|
liftedIds: liftedIds(),
|
|
286
|
+
editingId: editor.editingId,
|
|
240
287
|
palette: current.palette,
|
|
241
288
|
colors: current.colors,
|
|
242
289
|
colorScheme: current.colorScheme,
|
|
243
290
|
style: styleOf(current),
|
|
244
|
-
themeKey: current.key
|
|
291
|
+
themeKey: `${current.key}:${fontEpoch}`,
|
|
245
292
|
rendererType: app?.renderer.type === RendererType.CANVAS ? "canvas" : "gpu",
|
|
246
293
|
zoom: renderCamera().zoom,
|
|
247
294
|
textResolution: textResolution.value,
|
|
@@ -636,6 +683,9 @@ function mountBoardStage(host, options) {
|
|
|
636
683
|
world.position.set(camera.x, camera.y);
|
|
637
684
|
world.scale.set(camera.zoom);
|
|
638
685
|
const ids = visibleIds();
|
|
686
|
+
const center = pointToWorld(screenPoint(surface.width / 2, surface.height / 2), camera);
|
|
687
|
+
assets.focus(loadFocus(center, camera));
|
|
688
|
+
adoptArrivals();
|
|
639
689
|
textResolution.update(textureScale());
|
|
640
690
|
const context = renderContext(current, renderScene, frame.time);
|
|
641
691
|
if (animationId && effectSampler) {
|
|
@@ -646,6 +696,10 @@ function mountBoardStage(host, options) {
|
|
|
646
696
|
for (const item of renderScene.items) if (item.type === "effect" && item.props.kind === "trail" && renderScene.opacity(item.id) > 0) pinnedIds.add(item.id);
|
|
647
697
|
for (const id of previews.keys()) pinnedIds.add(id);
|
|
648
698
|
if (editor.editingId) pinnedIds.add(editor.editingId);
|
|
699
|
+
if (editor.editingId !== shownEditingId) {
|
|
700
|
+
for (const id of [shownEditingId, editor.editingId]) if (id) staleIds.add(id);
|
|
701
|
+
shownEditingId = editor.editingId;
|
|
702
|
+
}
|
|
649
703
|
for (const id of frame.items.keys()) {
|
|
650
704
|
pinnedIds.add(id);
|
|
651
705
|
for (const child of renderScene.descendants(id)) pinnedIds.add(child);
|
|
@@ -659,12 +713,17 @@ function mountBoardStage(host, options) {
|
|
|
659
713
|
getItem: (id) => renderScene.get(id) ?? null,
|
|
660
714
|
visibleIds: ids,
|
|
661
715
|
pinnedIds,
|
|
662
|
-
globalSig: [
|
|
716
|
+
globalSig: [
|
|
717
|
+
cardEpoch,
|
|
718
|
+
current.key,
|
|
719
|
+
fontEpoch
|
|
720
|
+
].join("|"),
|
|
663
721
|
staleIds,
|
|
664
722
|
structureVersion: editor.structureVersion,
|
|
665
|
-
geometryVersion: editor.geometryVersion,
|
|
666
723
|
gestureActive: editor.gestureActive && editor.interaction.type !== "panning",
|
|
667
|
-
focus:
|
|
724
|
+
focus: center,
|
|
725
|
+
view: ids ? cullView(camera) : void 0,
|
|
726
|
+
lod: options.lod?.()
|
|
668
727
|
});
|
|
669
728
|
staleIds.clear();
|
|
670
729
|
if (scene.liveVersion !== liveVersion) {
|
|
@@ -855,13 +914,14 @@ function mountBoardStage(host, options) {
|
|
|
855
914
|
["dblclick", handleDoubleClick]
|
|
856
915
|
];
|
|
857
916
|
const stopEditor = editor.subscribe(LIVE_CHANNELS, scheduleTick);
|
|
917
|
+
const stopFonts = onBoardTextFontsChange(() => {
|
|
918
|
+
editor.relayoutText();
|
|
919
|
+
fontEpoch += 1;
|
|
920
|
+
scene?.redrawText();
|
|
921
|
+
scheduleTick();
|
|
922
|
+
});
|
|
858
923
|
const stopAssets = assets.subscribe((key) => {
|
|
859
|
-
|
|
860
|
-
if (!ids?.size) return;
|
|
861
|
-
for (const id of ids) {
|
|
862
|
-
staleIds.add(id);
|
|
863
|
-
naturalIds.add(id);
|
|
864
|
-
}
|
|
924
|
+
arrivals.add(key);
|
|
865
925
|
scheduleTick();
|
|
866
926
|
});
|
|
867
927
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
@@ -1004,6 +1064,18 @@ function mountBoardStage(host, options) {
|
|
|
1004
1064
|
scheduleTick();
|
|
1005
1065
|
},
|
|
1006
1066
|
isMaterialized: (id) => Boolean(scene?.getNode(id)),
|
|
1067
|
+
stats() {
|
|
1068
|
+
const { textures, cooling, bytes } = assets.stats();
|
|
1069
|
+
return {
|
|
1070
|
+
...scene?.counts() ?? {
|
|
1071
|
+
live: 0,
|
|
1072
|
+
far: 0
|
|
1073
|
+
},
|
|
1074
|
+
textures,
|
|
1075
|
+
cooling,
|
|
1076
|
+
textureBytes: bytes
|
|
1077
|
+
};
|
|
1078
|
+
},
|
|
1007
1079
|
toWorld: (clientX, clientY) => pointToWorld(localPoint(clientX, clientY), renderCamera()),
|
|
1008
1080
|
destroy() {
|
|
1009
1081
|
disposed = true;
|
|
@@ -1018,6 +1090,7 @@ function mountBoardStage(host, options) {
|
|
|
1018
1090
|
stopMaintenance?.();
|
|
1019
1091
|
stopDensity?.();
|
|
1020
1092
|
stopEditor();
|
|
1093
|
+
stopFonts();
|
|
1021
1094
|
stopAssets();
|
|
1022
1095
|
resizeObserver.disconnect();
|
|
1023
1096
|
cancelAnimationFrame(scriptTickFrame);
|
|
@@ -7,6 +7,13 @@ type BoardImageTexturePool = {
|
|
|
7
7
|
/** A CDN variant that fails to process falls back to the media it came from. */
|
|
8
8
|
declare function loadBoardImageTexture(url: string): Promise<Texture>;
|
|
9
9
|
declare function unloadBoardImageTexture(url: string, texture: Texture | null): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Decode an image no larger than `edge` along its long side, for sources
|
|
12
|
+
* without CDN variants. The texture is unshared; destroy it to release it.
|
|
13
|
+
*/
|
|
14
|
+
declare function loadFittedImageTexture(url: string, edge: number): Promise<Texture>;
|
|
15
|
+
/** A smaller unshared copy of a decoded texture, or null when it cannot be read back. */
|
|
16
|
+
declare function shrinkBoardTexture(texture: Texture, edge: number): Promise<Texture | null>;
|
|
10
17
|
/**
|
|
11
18
|
* Share image textures by resolved URL without sharing Board ownership.
|
|
12
19
|
*
|
|
@@ -21,4 +28,4 @@ declare function createBoardImageTexturePool(options?: {
|
|
|
21
28
|
}): BoardImageTexturePool;
|
|
22
29
|
declare const boardImageTexturePool: BoardImageTexturePool;
|
|
23
30
|
//#endregion
|
|
24
|
-
export { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture };
|
|
31
|
+
export { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, loadFittedImageTexture, shrinkBoardTexture, unloadBoardImageTexture };
|
|
@@ -62,6 +62,42 @@ async function unloadBoardImageTexture(url, texture) {
|
|
|
62
62
|
await Assets.unload(url).catch(() => {});
|
|
63
63
|
if (!texture?.destroyed) texture?.destroy(true);
|
|
64
64
|
}
|
|
65
|
+
function fit(width, height, edge) {
|
|
66
|
+
const scale = edge / Math.max(width, height);
|
|
67
|
+
return scale < 1 ? {
|
|
68
|
+
resizeWidth: Math.max(1, Math.round(width * scale)),
|
|
69
|
+
resizeHeight: Math.max(1, Math.round(height * scale))
|
|
70
|
+
} : null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Decode an image no larger than `edge` along its long side, for sources
|
|
74
|
+
* without CDN variants. The texture is unshared; destroy it to release it.
|
|
75
|
+
*/
|
|
76
|
+
async function loadFittedImageTexture(url, edge) {
|
|
77
|
+
const response = await fetch(url);
|
|
78
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
79
|
+
const image = await createImageBitmap(await response.blob());
|
|
80
|
+
const size = fit(image.width, image.height, edge);
|
|
81
|
+
if (!size) return Texture.from(image, true);
|
|
82
|
+
try {
|
|
83
|
+
return Texture.from(await createImageBitmap(image, {
|
|
84
|
+
...size,
|
|
85
|
+
resizeQuality: "high"
|
|
86
|
+
}), true);
|
|
87
|
+
} finally {
|
|
88
|
+
image.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** A smaller unshared copy of a decoded texture, or null when it cannot be read back. */
|
|
92
|
+
async function shrinkBoardTexture(texture, edge) {
|
|
93
|
+
const resource = texture.source.resource;
|
|
94
|
+
const size = fit(texture.width, texture.height, edge);
|
|
95
|
+
if (!resource || !size || typeof createImageBitmap !== "function") return null;
|
|
96
|
+
return Texture.from(await createImageBitmap(resource, {
|
|
97
|
+
...size,
|
|
98
|
+
resizeQuality: "medium"
|
|
99
|
+
}), true);
|
|
100
|
+
}
|
|
65
101
|
/**
|
|
66
102
|
* Share image textures by resolved URL without sharing Board ownership.
|
|
67
103
|
*
|
|
@@ -150,4 +186,4 @@ function createBoardImageTexturePool(options) {
|
|
|
150
186
|
}
|
|
151
187
|
const boardImageTexturePool = createBoardImageTexturePool();
|
|
152
188
|
//#endregion
|
|
153
|
-
export { boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture };
|
|
189
|
+
export { boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, loadFittedImageTexture, shrinkBoardTexture, unloadBoardImageTexture };
|
package/dist/index.js
CHANGED
|
@@ -865,6 +865,128 @@ function boardJsonEquals(a, b) {
|
|
|
865
865
|
return keys.length === Object.keys(bRecord).length && keys.every((key) => Object.hasOwn(bRecord, key) && boardJsonEquals(aRecord[key], bRecord[key]));
|
|
866
866
|
}
|
|
867
867
|
//#endregion
|
|
868
|
+
//#region ../protocol/dist/generation/blocks.js
|
|
869
|
+
const EXCERPT_LIMIT = 240;
|
|
870
|
+
function asRecord(value) {
|
|
871
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
872
|
+
}
|
|
873
|
+
function cleanExcerpt(value, limit = EXCERPT_LIMIT) {
|
|
874
|
+
if (typeof value !== "string") return void 0;
|
|
875
|
+
const clean = value.replace(/\s+/g, " ").trim();
|
|
876
|
+
if (!clean) return void 0;
|
|
877
|
+
return clean.length > limit ? `${clean.slice(0, limit - 3).trimEnd()}...` : clean;
|
|
878
|
+
}
|
|
879
|
+
function blockText(block) {
|
|
880
|
+
return cleanExcerpt(block.text ?? block.content ?? block.value);
|
|
881
|
+
}
|
|
882
|
+
function blockUrl(block) {
|
|
883
|
+
const source = asRecord(block.source);
|
|
884
|
+
return normalizeBoardRemoteUrl(source?.url ?? source?.src ?? block.url ?? block.src);
|
|
885
|
+
}
|
|
886
|
+
function blockMimeType(block) {
|
|
887
|
+
const source = asRecord(block.source);
|
|
888
|
+
const value = source?.mediaType ?? source?.media_type ?? source?.mimeType ?? block.mediaType ?? block.media_type ?? block.mimeType;
|
|
889
|
+
return typeof value === "string" ? value : void 0;
|
|
890
|
+
}
|
|
891
|
+
function positiveNumber(value) {
|
|
892
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
893
|
+
}
|
|
894
|
+
function blockNaturalSize(block) {
|
|
895
|
+
const source = asRecord(block.source);
|
|
896
|
+
const naturalWidth = positiveNumber(source?.width ?? source?.naturalWidth ?? block.width ?? block.naturalWidth);
|
|
897
|
+
const naturalHeight = positiveNumber(source?.height ?? source?.naturalHeight ?? block.height ?? block.naturalHeight);
|
|
898
|
+
return {
|
|
899
|
+
...naturalWidth ? { naturalWidth } : {},
|
|
900
|
+
...naturalHeight ? { naturalHeight } : {}
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
function contentBlocks(value) {
|
|
904
|
+
return Array.isArray(value) ? value.filter((item) => asRecord(item)) : [];
|
|
905
|
+
}
|
|
906
|
+
function taskData(run) {
|
|
907
|
+
const payload = asRecord(run.payload);
|
|
908
|
+
return asRecord(payload?.data) ?? payload;
|
|
909
|
+
}
|
|
910
|
+
function generationOutput(run) {
|
|
911
|
+
return contentBlocks(asRecord(run.result)?.output);
|
|
912
|
+
}
|
|
913
|
+
function generationPrompt(run) {
|
|
914
|
+
for (const block of contentBlocks(taskData(run)?.content)) {
|
|
915
|
+
const text = blockText(block);
|
|
916
|
+
if (text) return text;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function blockMeta(block) {
|
|
920
|
+
return asRecord(block.meta);
|
|
921
|
+
}
|
|
922
|
+
function blockIdentity(block) {
|
|
923
|
+
const meta = blockMeta(block);
|
|
924
|
+
const value = meta?.id ?? meta?.clip_id ?? meta?.clipId ?? block.id ?? block.clip_id ?? block.clipId;
|
|
925
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
926
|
+
return cleanExcerpt(String(value), 220);
|
|
927
|
+
}
|
|
928
|
+
function blockTitle(block) {
|
|
929
|
+
return cleanExcerpt(blockMeta(block)?.title ?? block.title ?? block.name, 240);
|
|
930
|
+
}
|
|
931
|
+
function blockDurationMs(block) {
|
|
932
|
+
const source = asRecord(block.source);
|
|
933
|
+
const meta = blockMeta(block);
|
|
934
|
+
const milliseconds = positiveNumber(source?.durationMs ?? meta?.durationMs ?? block.durationMs);
|
|
935
|
+
if (milliseconds) return Math.round(milliseconds);
|
|
936
|
+
const seconds = positiveNumber(source?.duration ?? meta?.duration ?? block.duration);
|
|
937
|
+
return seconds ? Math.round(seconds * 1e3) : void 0;
|
|
938
|
+
}
|
|
939
|
+
function blockPreviewUrl(block) {
|
|
940
|
+
const source = asRecord(block.source);
|
|
941
|
+
return normalizeBoardRemoteUrl(source?.poster ?? source?.thumbnail ?? source?.previewUrl ?? block.poster ?? block.thumbnail ?? block.previewUrl);
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Image roles that depict playable media, by the media types they may cover;
|
|
945
|
+
* `last_frame` stays a result of its own.
|
|
946
|
+
*/
|
|
947
|
+
const COVER_ROLES = /* @__PURE__ */ new Map([
|
|
948
|
+
["first_frame", /* @__PURE__ */ new Set(["video"])],
|
|
949
|
+
["cover", /* @__PURE__ */ new Set(["video", "audio"])],
|
|
950
|
+
["poster", /* @__PURE__ */ new Set(["video", "audio"])],
|
|
951
|
+
["thumbnail", /* @__PURE__ */ new Set(["video", "audio"])]
|
|
952
|
+
]);
|
|
953
|
+
function isPlayable(block) {
|
|
954
|
+
return block.type === "video" || block.type === "audio";
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Pair playable media with its cover image, returning media index → cover
|
|
958
|
+
* index. An image sharing the provider id wins (music tracks and their art,
|
|
959
|
+
* shared by every media of that id); otherwise cover-role images pair in
|
|
960
|
+
* order with remaining media their role fits (a video and its `first_frame`).
|
|
961
|
+
* Covers need a remote URL, so an inline image is never folded away.
|
|
962
|
+
*/
|
|
963
|
+
function generationCovers(blocks) {
|
|
964
|
+
const covers = /* @__PURE__ */ new Map();
|
|
965
|
+
const isCover = (block) => block.type === "image" && blockUrl(block) !== void 0 && blockMeta(block)?.role !== "last_frame";
|
|
966
|
+
blocks.forEach((block, index) => {
|
|
967
|
+
const id = blockIdentity(block);
|
|
968
|
+
if (!isPlayable(block) || id === void 0) return;
|
|
969
|
+
const cover = blocks.findIndex((candidate) => isCover(candidate) && blockIdentity(candidate) === id);
|
|
970
|
+
if (cover >= 0) covers.set(index, cover);
|
|
971
|
+
});
|
|
972
|
+
const paired = new Set(covers.values());
|
|
973
|
+
const roleCovers = blocks.flatMap((block, index) => {
|
|
974
|
+
const role = blockMeta(block)?.role;
|
|
975
|
+
const types = typeof role === "string" ? COVER_ROLES.get(role) : void 0;
|
|
976
|
+
return isCover(block) && !paired.has(index) && types ? [{
|
|
977
|
+
index,
|
|
978
|
+
types
|
|
979
|
+
}] : [];
|
|
980
|
+
});
|
|
981
|
+
blocks.forEach((block, index) => {
|
|
982
|
+
if (!isPlayable(block) || covers.has(index)) return;
|
|
983
|
+
const at = roleCovers.findIndex(({ types }) => types.has(String(block.type)));
|
|
984
|
+
const [cover] = at < 0 ? [] : roleCovers.splice(at, 1);
|
|
985
|
+
if (cover) covers.set(index, cover.index);
|
|
986
|
+
});
|
|
987
|
+
return covers;
|
|
988
|
+
}
|
|
989
|
+
//#endregion
|
|
868
990
|
//#region ../protocol/dist/board-layout.js
|
|
869
991
|
const IDENTITY_MATRIX = [
|
|
870
992
|
1,
|
|
@@ -927,8 +1049,14 @@ function estimateTextWidth(text, fontSize) {
|
|
|
927
1049
|
}
|
|
928
1050
|
return ems * fontSize;
|
|
929
1051
|
}
|
|
930
|
-
function
|
|
931
|
-
|
|
1052
|
+
function estimateLines(text, { fontSize }, maxWidth) {
|
|
1053
|
+
const measure = (line) => estimateTextWidth(line, fontSize);
|
|
1054
|
+
const paragraphs = text.split("\n");
|
|
1055
|
+
const lines = maxWidth ? paragraphs.flatMap((paragraph) => wrapParagraph(paragraph, maxWidth, measure)) : paragraphs;
|
|
1056
|
+
return {
|
|
1057
|
+
lines,
|
|
1058
|
+
width: lines.reduce((max, line) => Math.max(max, measure(line)), 0)
|
|
1059
|
+
};
|
|
932
1060
|
}
|
|
933
1061
|
function wrapParagraph(paragraph, width, measure) {
|
|
934
1062
|
if (!paragraph) return [""];
|
|
@@ -957,13 +1085,15 @@ function layoutBoardText(props) {
|
|
|
957
1085
|
const key = `${fontSize}|${props.fontWeight}|${props.font}|${props.lineHeight}|${props.width ?? ""}|${props.text}`;
|
|
958
1086
|
const cached = textSizeCache.get(key);
|
|
959
1087
|
if (cached) return cached;
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
1088
|
+
const font = {
|
|
1089
|
+
fontSize,
|
|
1090
|
+
fontWeight: props.fontWeight ?? 500,
|
|
1091
|
+
font: props.font ?? "sans"
|
|
1092
|
+
};
|
|
1093
|
+
const { lines, width } = estimateLines(props.text || "", font, props.width || void 0);
|
|
963
1094
|
const lineHeight = fontSize * (props.lineHeight ?? 4 / 3);
|
|
964
|
-
const widest = lines.reduce((max, line) => Math.max(max, measure(line)), 0);
|
|
965
1095
|
const layout = {
|
|
966
|
-
width: Math.max(props.width ?? 0,
|
|
1096
|
+
width: Math.max(props.width ?? 0, width, fontSize * .5),
|
|
967
1097
|
height: Math.max(1, lines.length) * lineHeight,
|
|
968
1098
|
lines,
|
|
969
1099
|
lineHeight
|
|
@@ -1918,128 +2048,6 @@ function parseBoardDocument(input) {
|
|
|
1918
2048
|
} : result;
|
|
1919
2049
|
}
|
|
1920
2050
|
//#endregion
|
|
1921
|
-
//#region ../protocol/dist/generation/blocks.js
|
|
1922
|
-
const EXCERPT_LIMIT = 240;
|
|
1923
|
-
function asRecord(value) {
|
|
1924
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1925
|
-
}
|
|
1926
|
-
function cleanExcerpt(value, limit = EXCERPT_LIMIT) {
|
|
1927
|
-
if (typeof value !== "string") return void 0;
|
|
1928
|
-
const clean = value.replace(/\s+/g, " ").trim();
|
|
1929
|
-
if (!clean) return void 0;
|
|
1930
|
-
return clean.length > limit ? `${clean.slice(0, limit - 3).trimEnd()}...` : clean;
|
|
1931
|
-
}
|
|
1932
|
-
function blockText(block) {
|
|
1933
|
-
return cleanExcerpt(block.text ?? block.content ?? block.value);
|
|
1934
|
-
}
|
|
1935
|
-
function blockUrl(block) {
|
|
1936
|
-
const source = asRecord(block.source);
|
|
1937
|
-
return normalizeBoardRemoteUrl(source?.url ?? source?.src ?? block.url ?? block.src);
|
|
1938
|
-
}
|
|
1939
|
-
function blockMimeType(block) {
|
|
1940
|
-
const source = asRecord(block.source);
|
|
1941
|
-
const value = source?.mediaType ?? source?.media_type ?? source?.mimeType ?? block.mediaType ?? block.media_type ?? block.mimeType;
|
|
1942
|
-
return typeof value === "string" ? value : void 0;
|
|
1943
|
-
}
|
|
1944
|
-
function positiveNumber(value) {
|
|
1945
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
1946
|
-
}
|
|
1947
|
-
function blockNaturalSize(block) {
|
|
1948
|
-
const source = asRecord(block.source);
|
|
1949
|
-
const naturalWidth = positiveNumber(source?.width ?? source?.naturalWidth ?? block.width ?? block.naturalWidth);
|
|
1950
|
-
const naturalHeight = positiveNumber(source?.height ?? source?.naturalHeight ?? block.height ?? block.naturalHeight);
|
|
1951
|
-
return {
|
|
1952
|
-
...naturalWidth ? { naturalWidth } : {},
|
|
1953
|
-
...naturalHeight ? { naturalHeight } : {}
|
|
1954
|
-
};
|
|
1955
|
-
}
|
|
1956
|
-
function contentBlocks(value) {
|
|
1957
|
-
return Array.isArray(value) ? value.filter((item) => asRecord(item)) : [];
|
|
1958
|
-
}
|
|
1959
|
-
function taskData(run) {
|
|
1960
|
-
const payload = asRecord(run.payload);
|
|
1961
|
-
return asRecord(payload?.data) ?? payload;
|
|
1962
|
-
}
|
|
1963
|
-
function generationOutput(run) {
|
|
1964
|
-
return contentBlocks(asRecord(run.result)?.output);
|
|
1965
|
-
}
|
|
1966
|
-
function generationPrompt(run) {
|
|
1967
|
-
for (const block of contentBlocks(taskData(run)?.content)) {
|
|
1968
|
-
const text = blockText(block);
|
|
1969
|
-
if (text) return text;
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
function blockMeta(block) {
|
|
1973
|
-
return asRecord(block.meta);
|
|
1974
|
-
}
|
|
1975
|
-
function blockIdentity(block) {
|
|
1976
|
-
const meta = blockMeta(block);
|
|
1977
|
-
const value = meta?.id ?? meta?.clip_id ?? meta?.clipId ?? block.id ?? block.clip_id ?? block.clipId;
|
|
1978
|
-
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
1979
|
-
return cleanExcerpt(String(value), 220);
|
|
1980
|
-
}
|
|
1981
|
-
function blockTitle(block) {
|
|
1982
|
-
return cleanExcerpt(blockMeta(block)?.title ?? block.title ?? block.name, 240);
|
|
1983
|
-
}
|
|
1984
|
-
function blockDurationMs(block) {
|
|
1985
|
-
const source = asRecord(block.source);
|
|
1986
|
-
const meta = blockMeta(block);
|
|
1987
|
-
const milliseconds = positiveNumber(source?.durationMs ?? meta?.durationMs ?? block.durationMs);
|
|
1988
|
-
if (milliseconds) return Math.round(milliseconds);
|
|
1989
|
-
const seconds = positiveNumber(source?.duration ?? meta?.duration ?? block.duration);
|
|
1990
|
-
return seconds ? Math.round(seconds * 1e3) : void 0;
|
|
1991
|
-
}
|
|
1992
|
-
function blockPreviewUrl(block) {
|
|
1993
|
-
const source = asRecord(block.source);
|
|
1994
|
-
return normalizeBoardRemoteUrl(source?.poster ?? source?.thumbnail ?? source?.previewUrl ?? block.poster ?? block.thumbnail ?? block.previewUrl);
|
|
1995
|
-
}
|
|
1996
|
-
/**
|
|
1997
|
-
* Image roles that depict playable media, by the media types they may cover;
|
|
1998
|
-
* `last_frame` stays a result of its own.
|
|
1999
|
-
*/
|
|
2000
|
-
const COVER_ROLES = /* @__PURE__ */ new Map([
|
|
2001
|
-
["first_frame", /* @__PURE__ */ new Set(["video"])],
|
|
2002
|
-
["cover", /* @__PURE__ */ new Set(["video", "audio"])],
|
|
2003
|
-
["poster", /* @__PURE__ */ new Set(["video", "audio"])],
|
|
2004
|
-
["thumbnail", /* @__PURE__ */ new Set(["video", "audio"])]
|
|
2005
|
-
]);
|
|
2006
|
-
function isPlayable(block) {
|
|
2007
|
-
return block.type === "video" || block.type === "audio";
|
|
2008
|
-
}
|
|
2009
|
-
/**
|
|
2010
|
-
* Pair playable media with its cover image, returning media index → cover
|
|
2011
|
-
* index. An image sharing the provider id wins (music tracks and their art,
|
|
2012
|
-
* shared by every media of that id); otherwise cover-role images pair in
|
|
2013
|
-
* order with remaining media their role fits (a video and its `first_frame`).
|
|
2014
|
-
* Covers need a remote URL, so an inline image is never folded away.
|
|
2015
|
-
*/
|
|
2016
|
-
function generationCovers(blocks) {
|
|
2017
|
-
const covers = /* @__PURE__ */ new Map();
|
|
2018
|
-
const isCover = (block) => block.type === "image" && blockUrl(block) !== void 0 && blockMeta(block)?.role !== "last_frame";
|
|
2019
|
-
blocks.forEach((block, index) => {
|
|
2020
|
-
const id = blockIdentity(block);
|
|
2021
|
-
if (!isPlayable(block) || id === void 0) return;
|
|
2022
|
-
const cover = blocks.findIndex((candidate) => isCover(candidate) && blockIdentity(candidate) === id);
|
|
2023
|
-
if (cover >= 0) covers.set(index, cover);
|
|
2024
|
-
});
|
|
2025
|
-
const paired = new Set(covers.values());
|
|
2026
|
-
const roleCovers = blocks.flatMap((block, index) => {
|
|
2027
|
-
const role = blockMeta(block)?.role;
|
|
2028
|
-
const types = typeof role === "string" ? COVER_ROLES.get(role) : void 0;
|
|
2029
|
-
return isCover(block) && !paired.has(index) && types ? [{
|
|
2030
|
-
index,
|
|
2031
|
-
types
|
|
2032
|
-
}] : [];
|
|
2033
|
-
});
|
|
2034
|
-
blocks.forEach((block, index) => {
|
|
2035
|
-
if (!isPlayable(block) || covers.has(index)) return;
|
|
2036
|
-
const at = roleCovers.findIndex(({ types }) => types.has(String(block.type)));
|
|
2037
|
-
const [cover] = at < 0 ? [] : roleCovers.splice(at, 1);
|
|
2038
|
-
if (cover) covers.set(index, cover.index);
|
|
2039
|
-
});
|
|
2040
|
-
return covers;
|
|
2041
|
-
}
|
|
2042
|
-
//#endregion
|
|
2043
2051
|
//#region ../protocol/dist/turn-origin.js
|
|
2044
2052
|
const record$1 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2045
2053
|
const normalizeSessionTurnOrigin = (value) => {
|
package/dist/media.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ossProcessUrl } from "./protocol/dist/core/media.js";
|
|
1
|
+
import { MediaInfo, mediaInfoFromHeaders, mergeMediaInfo, ossProcessUrl, probeMediaInfo, probeMediaSize, probeVideoFrameSize } from "./protocol/dist/core/media.js";
|
|
2
2
|
//#region src/media.d.ts
|
|
3
3
|
type MediaType = "image" | "video" | "audio";
|
|
4
4
|
/**
|
|
@@ -7,18 +7,9 @@ type MediaType = "image" | "video" | "audio";
|
|
|
7
7
|
* keep the original's rendered size.
|
|
8
8
|
*/
|
|
9
9
|
type MediaFit = "cover" | "contain" | "width";
|
|
10
|
-
type MediaInfo = {
|
|
11
|
-
width?: number;
|
|
12
|
-
height?: number;
|
|
13
|
-
durationMs?: number;
|
|
14
|
-
frameCount?: number;
|
|
15
|
-
firstFrameUrl?: string;
|
|
16
|
-
lastFrameUrl?: string;
|
|
17
|
-
mimeType?: string;
|
|
18
|
-
bytes?: number;
|
|
19
|
-
};
|
|
20
10
|
/** Fixed variant widths keep CDN cache hits high; pick with `mediaVariantSize`. */
|
|
21
11
|
declare const MEDIA_VARIANT_SIZES: readonly [192, 384, 768, 1536];
|
|
12
|
+
declare const MEDIA_VARIANT_MAX = 1536;
|
|
22
13
|
type MediaVariantSize = (typeof MEDIA_VARIANT_SIZES)[number];
|
|
23
14
|
/** Smallest variant covering `pixels` device pixels, capped at the largest. */
|
|
24
15
|
declare function mediaVariantSize(pixels: number): MediaVariantSize;
|
|
@@ -53,20 +44,5 @@ declare function mediaPreviewCandidates(media: MediaPreviewSource, options: {
|
|
|
53
44
|
size: MediaVariantSize;
|
|
54
45
|
fit?: MediaFit;
|
|
55
46
|
}, info?: Pick<MediaInfo, "firstFrameUrl">): string[];
|
|
56
|
-
/** Media facts from response headers; `x-oss-meta-*` need CORS exposure in browsers. */
|
|
57
|
-
declare function mediaInfoFromHeaders(headers: Headers, url: string): MediaInfo;
|
|
58
|
-
/** Fields of `primary` win; `fallback` fills the rest. */
|
|
59
|
-
declare function mergeMediaInfo(primary: MediaInfo, fallback: MediaInfo): MediaInfo;
|
|
60
|
-
type ProbeOptions = {
|
|
61
|
-
type: MediaType;
|
|
62
|
-
fetch?: typeof globalThis.fetch;
|
|
63
|
-
signal?: AbortSignal;
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Probe remote media: OSS meta headers (one HEAD, the only source of video
|
|
67
|
-
* duration and frames), then OSS `image/info` for image dimensions. Unknown
|
|
68
|
-
* hosts and failed steps resolve to what was learned so far; never throws.
|
|
69
|
-
*/
|
|
70
|
-
declare function probeMediaInfo(url: string, options: ProbeOptions): Promise<MediaInfo>;
|
|
71
47
|
//#endregion
|
|
72
|
-
export { MEDIA_VARIANT_SIZES, MediaFit, MediaInfo, MediaPreviewSource, MediaType, MediaVariantSize, imageVariantUrl, mediaInfoFromHeaders, mediaPreviewCandidates, mediaVariantSize, mergeMediaInfo, ossProcessUrl, probeMediaInfo, variantSource, videoFrameUrl };
|
|
48
|
+
export { MEDIA_VARIANT_MAX, MEDIA_VARIANT_SIZES, MediaFit, type MediaInfo, MediaPreviewSource, MediaType, MediaVariantSize, imageVariantUrl, mediaInfoFromHeaders, mediaPreviewCandidates, mediaVariantSize, mergeMediaInfo, ossProcessUrl, probeMediaInfo, probeMediaSize, probeVideoFrameSize, variantSource, videoFrameUrl };
|