@neta-art/cohub 12.1.1 → 12.3.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 +85 -8
- package/dist/board/editor/clipboard.js +1 -1
- package/dist/board/editor/creation.js +1 -1
- package/dist/board/editor/document.d.ts +3 -1
- package/dist/board/editor/document.js +8 -6
- package/dist/board/editor/editor.d.ts +8 -3
- package/dist/board/editor/editor.js +8 -2
- package/dist/board/editor/index.d.ts +3 -3
- package/dist/board/editor/index.js +2 -2
- package/dist/board/editor/interaction.d.ts +23 -2
- package/dist/board/editor/interaction.js +93 -4
- package/dist/board/editor/query.d.ts +1 -1
- package/dist/board/editor/query.js +3 -7
- package/dist/board/editor/selection.js +1 -1
- package/dist/board/editor/snapshots.js +5 -57
- package/dist/board/editor/tool.d.ts +4 -1
- package/dist/board/editor/tool.js +6 -1
- package/dist/board/export/index.d.ts +2 -2
- package/dist/board/export/index.js +1 -1
- 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/node/scripts.js +1 -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 +24 -0
- package/dist/board/model/asset-key.js +104 -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 +9 -7
- package/dist/board/model/index.js +22 -20
- package/dist/board/model/items/create.js +3 -3
- package/dist/board/model/items/definition.d.ts +9 -1
- package/dist/board/model/items/index.d.ts +2 -2
- package/dist/board/model/items/registry.d.ts +2 -1
- package/dist/board/model/items/registry.js +1 -0
- 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/sampling.js +1 -1
- package/dist/board/model/text-metrics.d.ts +28 -3
- package/dist/board/model/text-metrics.js +36 -2
- package/dist/board/model/themes.d.ts +495 -0
- package/dist/board/model/themes.js +500 -0
- package/dist/board/render/card-tag.js +2 -2
- package/dist/board/render/index.d.ts +11 -7
- package/dist/board/render/index.js +9 -5
- package/dist/board/render/labels.d.ts +15 -0
- package/dist/board/render/labels.js +14 -0
- package/dist/board/render/lod.d.ts +30 -0
- package/dist/board/render/lod.js +33 -0
- package/dist/board/render/media-interaction.d.ts +5 -2
- package/dist/board/render/media-interaction.js +15 -3
- package/dist/board/render/palette.js +2 -25
- package/dist/board/render/renderers/arrow-card-renderer.js +7 -5
- package/dist/board/render/renderers/audio-card-renderer.js +3 -2
- package/dist/board/render/renderers/board-renderer-registry.d.ts +22 -1
- package/dist/board/render/renderers/board-renderer-registry.js +9 -4
- package/dist/board/render/renderers/custom-card-renderer.js +5 -5
- package/dist/board/render/renderers/draw-card-renderer.js +1 -1
- package/dist/board/render/renderers/far-plate.d.ts +14 -1
- package/dist/board/render/renderers/far-plate.js +31 -8
- package/dist/board/render/renderers/file-card-renderer.js +49 -31
- package/dist/board/render/renderers/frame-card-renderer.js +9 -3
- package/dist/board/render/renderers/image-card-renderer.js +34 -8
- package/dist/board/render/renderers/model-card-renderer.js +6 -6
- package/dist/board/render/renderers/shape-card-renderer.js +25 -24
- package/dist/board/render/renderers/task-card-renderer.js +9 -10
- package/dist/board/render/renderers/text-card-renderer.js +86 -41
- package/dist/board/render/renderers/video-card-renderer.js +5 -4
- package/dist/board/render/script-export.js +1 -1
- package/dist/board/render/script-host.js +3 -3
- package/dist/board/render/style.d.ts +8 -2
- package/dist/board/render/style.js +8 -2
- package/dist/board/render/surface.d.ts +5 -0
- package/dist/board/render/surface.js +13 -3
- package/dist/board/render/text-edit.d.ts +32 -0
- package/dist/board/render/text-edit.js +134 -0
- package/dist/board/render/text-fit.d.ts +1 -3
- package/dist/board/render/text-fit.js +6 -49
- package/dist/board/render/text-measurement.d.ts +25 -1
- package/dist/board/render/text-measurement.js +157 -19
- 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/replica/index.d.ts +4 -2
- package/dist/board/replica/index.js +28 -17
- package/dist/board/replica/indexeddb.d.ts +16 -0
- package/dist/board/replica/indexeddb.js +155 -0
- package/dist/board/stage/assets.d.ts +28 -5
- package/dist/board/stage/assets.js +293 -102
- package/dist/board/stage/bench.d.ts +35 -0
- package/dist/board/stage/bench.js +153 -0
- package/dist/board/stage/device.d.ts +41 -0
- package/dist/board/stage/device.js +62 -0
- package/dist/board/stage/frame-budget.d.ts +19 -0
- package/dist/board/stage/frame-budget.js +69 -0
- package/dist/board/stage/idle.d.ts +4 -0
- package/dist/board/stage/idle.js +26 -0
- package/dist/board/stage/index.d.ts +11 -7
- package/dist/board/stage/index.js +7 -4
- package/dist/board/stage/lru.d.ts +13 -9
- package/dist/board/stage/lru.js +38 -20
- package/dist/board/stage/presses.d.ts +21 -0
- package/dist/board/stage/presses.js +66 -0
- package/dist/board/stage/scene.d.ts +15 -1
- package/dist/board/stage/scene.js +248 -141
- package/dist/board/stage/stage.d.ts +28 -2
- package/dist/board/stage/stage.js +404 -112
- package/dist/board/stage/texture-pool.d.ts +10 -1
- package/dist/board/stage/texture-pool.js +72 -2
- package/dist/chunks/environment.d.ts +6 -0
- package/dist/chunks/http.d.ts +9 -1
- package/dist/chunks/http.js +791 -4
- package/dist/index.d.ts +52 -1
- package/dist/index.js +202 -862
- 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/board-model.d.ts +15 -1
- package/dist/protocol/dist/board-model.js +12 -2
- package/dist/protocol/dist/board-patch.js +1 -1
- package/dist/protocol/dist/board-schema.d.ts +2 -1
- package/dist/protocol/dist/board-schema.js +4 -1
- package/dist/protocol/dist/core/media.d.ts +35 -1
- package/dist/protocol/dist/core/media.js +127 -1
- package/dist/protocol/dist/index.d.ts +3 -3
- package/package.json +4 -2
- package/dist/board/model/image-key.d.ts +0 -9
- package/dist/board/model/image-key.js +0 -33
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 };
|
package/dist/media.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mediaHost, mediaInfoFromHeaders, mergeMediaInfo, ossProcessUrl, probeMediaInfo, probeMediaSize, probeVideoFrameSize } from "./protocol/dist/core/media.js";
|
|
2
2
|
//#region src/media.ts
|
|
3
3
|
/**
|
|
4
4
|
* Remote media delivery. Previews and metadata resolve through ordered
|
|
@@ -22,6 +22,7 @@ const MEDIA_VARIANT_SIZES = [
|
|
|
22
22
|
768,
|
|
23
23
|
1536
|
|
24
24
|
];
|
|
25
|
+
const MEDIA_VARIANT_MAX = 1536;
|
|
25
26
|
/** Smallest variant covering `pixels` device pixels, capped at the largest. */
|
|
26
27
|
function mediaVariantSize(pixels) {
|
|
27
28
|
return MEDIA_VARIANT_SIZES.find((size) => size >= pixels) ?? 1536;
|
|
@@ -82,85 +83,5 @@ function mediaPreviewCandidates(media, options, info) {
|
|
|
82
83
|
}
|
|
83
84
|
return candidates;
|
|
84
85
|
}
|
|
85
|
-
function positive(value) {
|
|
86
|
-
const number = typeof value === "string" ? Number(value) : value;
|
|
87
|
-
return typeof number === "number" && Number.isFinite(number) && number > 0 ? number : void 0;
|
|
88
|
-
}
|
|
89
|
-
/** Frame URLs must stay on the media's own origin. */
|
|
90
|
-
function sameOriginUrl(value, origin) {
|
|
91
|
-
if (!value) return void 0;
|
|
92
|
-
try {
|
|
93
|
-
const url = new URL(value);
|
|
94
|
-
return url.protocol === "https:" && url.origin === origin ? url.href : void 0;
|
|
95
|
-
} catch {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function compact(info) {
|
|
100
|
-
return Object.fromEntries(Object.entries(info).filter(([, value]) => value !== void 0));
|
|
101
|
-
}
|
|
102
|
-
/** Media facts from response headers; `x-oss-meta-*` need CORS exposure in browsers. */
|
|
103
|
-
function mediaInfoFromHeaders(headers, url) {
|
|
104
|
-
const origin = new URL(url).origin;
|
|
105
|
-
const meta = (name) => headers.get(`x-oss-meta-${name}`);
|
|
106
|
-
const seconds = positive(meta("duration"));
|
|
107
|
-
const frameCount = positive(meta("nb-frames"));
|
|
108
|
-
return compact({
|
|
109
|
-
width: positive(meta("width")),
|
|
110
|
-
height: positive(meta("height")),
|
|
111
|
-
durationMs: seconds ? Math.round(seconds * 1e3) : void 0,
|
|
112
|
-
frameCount: frameCount ? Math.round(frameCount) : void 0,
|
|
113
|
-
firstFrameUrl: sameOriginUrl(meta("first-frame"), origin),
|
|
114
|
-
lastFrameUrl: sameOriginUrl(meta("last-frame"), origin),
|
|
115
|
-
mimeType: headers.get("content-type")?.split(";")[0]?.trim() || void 0,
|
|
116
|
-
bytes: positive(headers.get("content-length"))
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
/** Fields of `primary` win; `fallback` fills the rest. */
|
|
120
|
-
function mergeMediaInfo(primary, fallback) {
|
|
121
|
-
return compact({
|
|
122
|
-
...fallback,
|
|
123
|
-
...compact(primary)
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
function hasDimensions(info) {
|
|
127
|
-
return info.width !== void 0 && info.height !== void 0;
|
|
128
|
-
}
|
|
129
|
-
async function probeHeaders(url, options) {
|
|
130
|
-
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
131
|
-
method: "HEAD",
|
|
132
|
-
signal: options.signal
|
|
133
|
-
});
|
|
134
|
-
return response.ok ? mediaInfoFromHeaders(response.headers, url) : {};
|
|
135
|
-
}
|
|
136
|
-
async function probeImageInfo(url, options) {
|
|
137
|
-
if (OSS_PROCESS_PATTERN.test(url)) return {};
|
|
138
|
-
const response = await (options.fetch ?? globalThis.fetch)(ossProcessUrl(url, "image/info"), { signal: options.signal });
|
|
139
|
-
if (!response.ok) return {};
|
|
140
|
-
const body = await response.json();
|
|
141
|
-
return compact({
|
|
142
|
-
width: positive(body.ImageWidth?.value),
|
|
143
|
-
height: positive(body.ImageHeight?.value),
|
|
144
|
-
bytes: positive(body.FileSize?.value)
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Probe remote media: OSS meta headers (one HEAD, the only source of video
|
|
149
|
-
* duration and frames), then OSS `image/info` for image dimensions. Unknown
|
|
150
|
-
* hosts and failed steps resolve to what was learned so far; never throws.
|
|
151
|
-
*/
|
|
152
|
-
async function probeMediaInfo(url, options) {
|
|
153
|
-
const host = ossHost(url);
|
|
154
|
-
if (!host) return {};
|
|
155
|
-
let info = {};
|
|
156
|
-
const attempt = async (strategy) => {
|
|
157
|
-
try {
|
|
158
|
-
info = mergeMediaInfo(info, await strategy());
|
|
159
|
-
} catch {}
|
|
160
|
-
};
|
|
161
|
-
if (host.metaHeaders) await attempt(() => probeHeaders(url, options));
|
|
162
|
-
if (options.type === "image" && !hasDimensions(info)) await attempt(() => probeImageInfo(url, options));
|
|
163
|
-
return info;
|
|
164
|
-
}
|
|
165
86
|
//#endregion
|
|
166
|
-
export { MEDIA_VARIANT_SIZES, imageVariantUrl, mediaInfoFromHeaders, mediaPreviewCandidates, mediaVariantSize, mergeMediaInfo, ossProcessUrl, probeMediaInfo, variantSource, videoFrameUrl };
|
|
87
|
+
export { MEDIA_VARIANT_MAX, MEDIA_VARIANT_SIZES, imageVariantUrl, mediaInfoFromHeaders, mediaPreviewCandidates, mediaVariantSize, mergeMediaInfo, ossProcessUrl, probeMediaInfo, probeMediaSize, probeVideoFrameSize, variantSource, videoFrameUrl };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region ../protocol/dist/board-constants.js
|
|
2
|
-
const BOARD_TEXT_FONT_FAMILY = "Geist";
|
|
2
|
+
const BOARD_TEXT_FONT_FAMILY = "Geist Variable";
|
|
3
3
|
const BOARD_ARROW_STROKE_SIZE = 2.5;
|
|
4
4
|
const BOARD_PAGE_SIZE = {
|
|
5
5
|
width: 960,
|
|
@@ -62,7 +62,7 @@ function clampBoardStrokeSize(size) {
|
|
|
62
62
|
if (!Number.isFinite(size)) return BOARD_ARROW_STROKE_SIZE;
|
|
63
63
|
return Math.min(64, Math.max(1, size));
|
|
64
64
|
}
|
|
65
|
-
const BOARD_FONT_STACK = "\"Geist\",
|
|
66
|
-
const BOARD_MONO_FONT_STACK = "\"Geist Mono\", \"Fira Code\", ui-monospace, \"Noto Sans Mono CJK SC\", \"Noto Sans Math\", \"DejaVu Sans\", \"FreeSans\", \"FreeSerif\", monospace";
|
|
65
|
+
const BOARD_FONT_STACK = "\"Geist Variable\", \"Geist\", \"Noto Sans CJK SC\", \"Noto Sans SC\", \"PingFang SC\", \"Microsoft YaHei\", system-ui, -apple-system, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Noto Color Emoji\", \"Noto Sans Math\", \"Noto Sans Symbols 2\", \"DejaVu Sans\", \"FreeSans\", \"FreeSerif\", sans-serif";
|
|
66
|
+
const BOARD_MONO_FONT_STACK = "\"Geist Mono Variable\", \"Geist Mono\", \"Fira Code\", ui-monospace, \"Noto Sans Mono CJK SC\", \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Noto Color Emoji\", \"Noto Sans Math\", \"DejaVu Sans\", \"FreeSans\", \"FreeSerif\", monospace";
|
|
67
67
|
//#endregion
|
|
68
68
|
export { BOARD_ARROW_STROKE_SIZE, BOARD_DEFAULT_ITEM_SIZE, BOARD_DEFAULT_SIZES, BOARD_FONT_STACK, BOARD_MONO_FONT_STACK, BOARD_PAGE_SIZE, BOARD_STROKE_MAX_SIZE, BOARD_STROKE_MIN_SIZE, BOARD_TEXT_FONT_FAMILY, BOARD_TEXT_SCALE, boardDefaultSize, clampBoardStrokeSize };
|
|
@@ -15,8 +15,17 @@ declare function multiplyMatrix(parent: BoardMatrix, child: BoardMatrix): BoardM
|
|
|
15
15
|
declare function invertMatrix(matrix: BoardMatrix): BoardMatrix;
|
|
16
16
|
declare function applyMatrix(matrix: BoardMatrix, point: BoardVec2): BoardVec2;
|
|
17
17
|
declare function scaleVector(scale: BoardItem["scale"]): BoardVec2;
|
|
18
|
-
type
|
|
19
|
-
|
|
18
|
+
type BoardTextFont = {
|
|
19
|
+
fontSize: number;
|
|
20
|
+
fontWeight: number;
|
|
21
|
+
font: string;
|
|
22
|
+
};
|
|
23
|
+
type BoardTextLines = {
|
|
24
|
+
lines: string[];
|
|
25
|
+
width: number;
|
|
26
|
+
};
|
|
27
|
+
type BoardTextLayouter = (text: string, font: BoardTextFont, maxWidth: number | undefined) => BoardTextLines;
|
|
28
|
+
declare function setBoardTextLayouter(layouter?: BoardTextLayouter): void;
|
|
20
29
|
declare function estimateTextWidth(text: string, fontSize: number): number;
|
|
21
30
|
type BoardTextLayout = {
|
|
22
31
|
width: number;
|
|
@@ -33,6 +42,11 @@ declare function fitBoardMediaSize(natural: {
|
|
|
33
42
|
width: number;
|
|
34
43
|
height: number;
|
|
35
44
|
} | null;
|
|
45
|
+
/** Fits media items to their natural aspect ratio using the shared Board rule. */
|
|
46
|
+
declare function adoptBoardNaturalSize(item: BoardItem, natural: {
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
}): BoardItem | null;
|
|
36
50
|
declare function drawSampleRadius(strokeWidth: number, pressure: number): number;
|
|
37
51
|
declare function drawPointsBounds(points: readonly BoardDrawPoint[], strokeWidth: number): BoardRect;
|
|
38
52
|
declare function pointsRect(points: readonly BoardVec2[]): BoardRect;
|
|
@@ -63,4 +77,4 @@ declare function sideAnchor(side: "top" | "right" | "bottom" | "left", offset: n
|
|
|
63
77
|
declare function anchorPoint(frame: BoardFrame, anchor: BoardArrowAnchor, toward: BoardVec2): BoardVec2;
|
|
64
78
|
declare function createDocumentLayout(items: Readonly<Record<string, BoardItem>>): BoardLayout;
|
|
65
79
|
//#endregion
|
|
66
|
-
export { BoardFrame, BoardItemLookup, BoardLayout, BoardMatrix, BoardRect, BoardTextLayout,
|
|
80
|
+
export { BoardFrame, BoardItemLookup, BoardLayout, BoardMatrix, BoardRect, BoardTextFont, BoardTextLayout, BoardTextLayouter, BoardTextLines, IDENTITY_MATRIX, adoptBoardNaturalSize, anchorPoint, applyMatrix, arrowBindings, autoAnchorSide, bendControl, clampBoardFontSize, createBoardLayout, createDocumentLayout, drawPointsBounds, drawSampleRadius, estimateTextWidth, fitBoardMediaSize, framePoint, invertMatrix, isArrowBinding, layoutBoardText, multiplyMatrix, pointsRect, rotateAround, scaleVector, setBoardTextLayouter, sideAnchor };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { boardDefaultSize } from "./board-constants.js";
|
|
2
|
+
import { featuredTaskArtifact } from "./board-task.js";
|
|
2
3
|
//#region ../protocol/dist/board-layout.js
|
|
3
4
|
const IDENTITY_MATRIX = [
|
|
4
5
|
1,
|
|
@@ -45,9 +46,9 @@ function scaleVector(scale) {
|
|
|
45
46
|
y: scale
|
|
46
47
|
} : scale;
|
|
47
48
|
}
|
|
48
|
-
let
|
|
49
|
-
function
|
|
50
|
-
|
|
49
|
+
let textLayouter;
|
|
50
|
+
function setBoardTextLayouter(layouter) {
|
|
51
|
+
textLayouter = layouter;
|
|
51
52
|
textSizeCache.clear();
|
|
52
53
|
}
|
|
53
54
|
function isWideCharacter(code) {
|
|
@@ -66,8 +67,14 @@ function estimateTextWidth(text, fontSize) {
|
|
|
66
67
|
}
|
|
67
68
|
return ems * fontSize;
|
|
68
69
|
}
|
|
69
|
-
function
|
|
70
|
-
|
|
70
|
+
function estimateLines(text, { fontSize }, maxWidth) {
|
|
71
|
+
const measure = (line) => estimateTextWidth(line, fontSize);
|
|
72
|
+
const paragraphs = text.split("\n");
|
|
73
|
+
const lines = maxWidth ? paragraphs.flatMap((paragraph) => wrapParagraph(paragraph, maxWidth, measure)) : paragraphs;
|
|
74
|
+
return {
|
|
75
|
+
lines,
|
|
76
|
+
width: lines.reduce((max, line) => Math.max(max, measure(line)), 0)
|
|
77
|
+
};
|
|
71
78
|
}
|
|
72
79
|
function wrapParagraph(paragraph, width, measure) {
|
|
73
80
|
if (!paragraph) return [""];
|
|
@@ -96,13 +103,15 @@ function layoutBoardText(props) {
|
|
|
96
103
|
const key = `${fontSize}|${props.fontWeight}|${props.font}|${props.lineHeight}|${props.width ?? ""}|${props.text}`;
|
|
97
104
|
const cached = textSizeCache.get(key);
|
|
98
105
|
if (cached) return cached;
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
const font = {
|
|
107
|
+
fontSize,
|
|
108
|
+
fontWeight: props.fontWeight ?? 500,
|
|
109
|
+
font: props.font ?? "sans"
|
|
110
|
+
};
|
|
111
|
+
const { lines, width } = (textLayouter ?? estimateLines)(props.text || "", font, props.width || void 0);
|
|
102
112
|
const lineHeight = fontSize * (props.lineHeight ?? 4 / 3);
|
|
103
|
-
const widest = lines.reduce((max, line) => Math.max(max, measure(line)), 0);
|
|
104
113
|
const layout = {
|
|
105
|
-
width: Math.max(props.width ?? 0,
|
|
114
|
+
width: Math.max(props.width ?? 0, width, fontSize * .5),
|
|
106
115
|
height: Math.max(1, lines.length) * lineHeight,
|
|
107
116
|
lines,
|
|
108
117
|
lineHeight
|
|
@@ -124,6 +133,54 @@ function fitBoardMediaSize(natural, edge) {
|
|
|
124
133
|
height: Math.max(1, Math.round(height * scale))
|
|
125
134
|
};
|
|
126
135
|
}
|
|
136
|
+
/** Fits media items to their natural aspect ratio using the shared Board rule. */
|
|
137
|
+
function adoptBoardNaturalSize(item, natural) {
|
|
138
|
+
const dimensions = {
|
|
139
|
+
naturalWidth: natural.width,
|
|
140
|
+
naturalHeight: natural.height
|
|
141
|
+
};
|
|
142
|
+
if (item.type === "task") {
|
|
143
|
+
const artifact = featuredTaskArtifact(item.props.snapshot.artifacts);
|
|
144
|
+
if (artifact?.type !== "image" && artifact?.type !== "video" || artifact.naturalWidth && artifact.naturalHeight) return null;
|
|
145
|
+
const size = fitBoardMediaSize(natural, Math.max(item.size.width, item.size.height));
|
|
146
|
+
if (!size) return null;
|
|
147
|
+
const artifacts = item.props.snapshot.artifacts.map((entry) => entry.id === artifact.id ? {
|
|
148
|
+
...entry,
|
|
149
|
+
...dimensions
|
|
150
|
+
} : entry);
|
|
151
|
+
return {
|
|
152
|
+
...item,
|
|
153
|
+
size,
|
|
154
|
+
props: {
|
|
155
|
+
...item.props,
|
|
156
|
+
snapshot: {
|
|
157
|
+
...item.props.snapshot,
|
|
158
|
+
artifacts
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (item.type !== "image" && item.type !== "video") return null;
|
|
164
|
+
if (item.props.snapshot?.naturalWidth && item.props.snapshot.naturalHeight) return null;
|
|
165
|
+
const size = fitBoardMediaSize(natural, Math.max(item.size.width, item.size.height));
|
|
166
|
+
if (!size) return null;
|
|
167
|
+
const position = {
|
|
168
|
+
x: item.position.x + (item.size.width - size.width) / 2,
|
|
169
|
+
y: item.position.y + (item.size.height - size.height) / 2
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
...item,
|
|
173
|
+
position,
|
|
174
|
+
size,
|
|
175
|
+
props: {
|
|
176
|
+
...item.props,
|
|
177
|
+
snapshot: {
|
|
178
|
+
...item.props.snapshot,
|
|
179
|
+
...dimensions
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
127
184
|
function drawSampleRadius(strokeWidth, pressure) {
|
|
128
185
|
const clamped = Math.min(1, Math.max(0, pressure));
|
|
129
186
|
return Math.max(.5, strokeWidth / 2 * (.5 + clamped));
|
|
@@ -491,4 +548,4 @@ function createDocumentLayout(items) {
|
|
|
491
548
|
return createBoardLayout((id) => items[id], dependents);
|
|
492
549
|
}
|
|
493
550
|
//#endregion
|
|
494
|
-
export { IDENTITY_MATRIX, anchorPoint, applyMatrix, arrowBindings, autoAnchorSide, bendControl, clampBoardFontSize, createBoardLayout, createDocumentLayout, drawPointsBounds, drawSampleRadius, estimateTextWidth, fitBoardMediaSize, framePoint, invertMatrix, isArrowBinding, layoutBoardText, multiplyMatrix, pointsRect, rotateAround, scaleVector,
|
|
551
|
+
export { IDENTITY_MATRIX, adoptBoardNaturalSize, anchorPoint, applyMatrix, arrowBindings, autoAnchorSide, bendControl, clampBoardFontSize, createBoardLayout, createDocumentLayout, drawPointsBounds, drawSampleRadius, estimateTextWidth, fitBoardMediaSize, framePoint, invertMatrix, isArrowBinding, layoutBoardText, multiplyMatrix, pointsRect, rotateAround, scaleVector, setBoardTextLayouter, sideAnchor };
|
|
@@ -1139,6 +1139,13 @@ declare const BoardCustomItemSchema: z.ZodObject<{
|
|
|
1139
1139
|
props: z.ZodDefault<z.ZodObject<{}, z.core.$strict>>;
|
|
1140
1140
|
}, z.core.$strict>;
|
|
1141
1141
|
declare const BOARD_EXTENSION_TYPE_PATTERN: RegExp;
|
|
1142
|
+
declare const BoardItemPreviewSchema: z.ZodObject<{
|
|
1143
|
+
title: z.ZodOptional<z.ZodString>;
|
|
1144
|
+
excerpt: z.ZodOptional<z.ZodString>;
|
|
1145
|
+
cover: z.ZodOptional<z.ZodString>;
|
|
1146
|
+
src: z.ZodOptional<z.ZodString>;
|
|
1147
|
+
}, z.core.$strict>;
|
|
1148
|
+
type BoardItemPreview = z.infer<typeof BoardItemPreviewSchema>;
|
|
1142
1149
|
declare const BoardExtensionItemSchema: z.ZodObject<{
|
|
1143
1150
|
script: z.ZodOptional<z.ZodObject<{
|
|
1144
1151
|
src: z.ZodString;
|
|
@@ -1190,6 +1197,12 @@ declare const BoardExtensionItemSchema: z.ZodObject<{
|
|
|
1190
1197
|
}, z.core.$strict>>;
|
|
1191
1198
|
type: z.ZodString;
|
|
1192
1199
|
props: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
1200
|
+
preview: z.ZodOptional<z.ZodObject<{
|
|
1201
|
+
title: z.ZodOptional<z.ZodString>;
|
|
1202
|
+
excerpt: z.ZodOptional<z.ZodString>;
|
|
1203
|
+
cover: z.ZodOptional<z.ZodString>;
|
|
1204
|
+
src: z.ZodOptional<z.ZodString>;
|
|
1205
|
+
}, z.core.$strict>>;
|
|
1193
1206
|
}, z.core.$strict>;
|
|
1194
1207
|
declare const BOARD_ITEM_SCHEMAS: {
|
|
1195
1208
|
readonly frame: z.ZodObject<{
|
|
@@ -2070,6 +2083,7 @@ type BoardItemType = BoardItem["type"];
|
|
|
2070
2083
|
type BoardFileBackedItem = BoardImageItem | BoardVideoItem | BoardAudioItem | BoardFileItem;
|
|
2071
2084
|
declare function isBuiltinItemType(type: string): type is BoardBuiltinItemType;
|
|
2072
2085
|
declare function isBuiltinItem(item: BoardItem): item is BoardBuiltinItem;
|
|
2086
|
+
declare function boardItemPreview(item: BoardItem): BoardItemPreview | null;
|
|
2073
2087
|
declare function isFileBackedItem(item: BoardItem): item is BoardFileBackedItem;
|
|
2074
2088
|
declare function isMediaItem(item: BoardItem): item is BoardImageItem | BoardVideoItem | BoardAudioItem;
|
|
2075
2089
|
declare const BOARD_DERIVED_SIZE_TYPES: readonly ["text", "draw", "arrow"];
|
|
@@ -2274,4 +2288,4 @@ type BoardDelta = {
|
|
|
2274
2288
|
};
|
|
2275
2289
|
declare function isEmptyBoardDelta(delta: BoardDelta): boolean;
|
|
2276
2290
|
//#endregion
|
|
2277
|
-
export { BOARD_ANCHOR_SIDES, BOARD_ARROW_ROUTES, BOARD_CAMERA_TARGET, BOARD_COLOR_TOKENS, BOARD_DASHES, BOARD_DERIVED_SIZE_TYPES, BOARD_EFFECT_KINDS, BOARD_EFFECT_KIND_INFO, BOARD_ENTER_PRESETS, BOARD_EXTENSION_TYPE_PATTERN, BOARD_GRID_DEFAULT_SIZE, BOARD_GRID_MAX_SIZE, BOARD_GRID_MIN_SIZE, BOARD_ID_MAX_LENGTH, BOARD_ITEM_SCHEMAS, BOARD_ITEM_TYPES, BOARD_PLAY_MODES, BOARD_SHAPE_GEOMETRIES, BOARD_TASK_ARTIFACT_LIMIT, BOARD_TEXT_ALIGNS, BoardAnchorSide, BoardAnimation, BoardAnimationHeader, BoardAnimationHeaderSchema, BoardAnimationSchema, BoardArrowAnchor, BoardArrowAnchorSchema, BoardArrowBinding, BoardArrowBindingSchema, BoardArrowEnd, BoardArrowEndSchema, BoardArrowItem, BoardArrowItemSchema, BoardArrowRoute, BoardAudioItem, BoardAudioItemSchema, BoardBackgroundSchema, BoardBuiltinItem, BoardBuiltinItemType, BoardColor, BoardColorSchema, BoardColorToken, BoardCropSchema, BoardCustomItem, BoardCustomItemSchema, BoardDash, BoardDelta, BoardDiagnostic, BoardDocument, BoardDrawItem, BoardDrawItemSchema, BoardDrawPoint, BoardDrawPointSchema, BoardEaseSchema, BoardEffectItem, BoardEffectItemSchema, BoardEffectKind, BoardEnterPreset, BoardExtensionItem, BoardExtensionItemSchema, BoardFileBackedItem, BoardFileItem, BoardFileItemSchema, BoardFrameItem, BoardFrameItemSchema, BoardIdSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemType, BoardKeySchema, BoardKeyframe, BoardKeyframeSchema, BoardMarker, BoardMarkerSchema, BoardMediaOrigin, BoardMediaOriginSchema, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardMediaSource, BoardMediaSourceSchema, BoardPatch, BoardPatchSchema, BoardPlayMode, BoardScript, BoardScriptSchema, BoardSettings, BoardSettingsSchema, BoardShapeGeometry, BoardShapeItem, BoardShapeItemSchema, BoardSize, BoardSizeSchema, BoardStyle, BoardStyleSchema, BoardTaskArtifact, BoardTaskArtifactSchema, BoardTaskItem, BoardTaskItemSchema, BoardTaskSnapshot, BoardTaskSnapshotSchema, BoardTextItem, BoardTextItemSchema, BoardTrack, BoardTrackSchema, BoardVec2, BoardVec2Schema, BoardVideoItem, BoardVideoItemSchema, boardItemSchema, emptyBoardDocument, hasAuthoredSize, isBoardColorString, isBoardEase, isBuiltinItem, isBuiltinItemType, isEmptyBoardDelta, isFileBackedItem, isMediaItem, isSafeBoardSourcePath, parseBoardItem, parseBoardMediaSource };
|
|
2291
|
+
export { BOARD_ANCHOR_SIDES, BOARD_ARROW_ROUTES, BOARD_CAMERA_TARGET, BOARD_COLOR_TOKENS, BOARD_DASHES, BOARD_DERIVED_SIZE_TYPES, BOARD_EFFECT_KINDS, BOARD_EFFECT_KIND_INFO, BOARD_ENTER_PRESETS, BOARD_EXTENSION_TYPE_PATTERN, BOARD_GRID_DEFAULT_SIZE, BOARD_GRID_MAX_SIZE, BOARD_GRID_MIN_SIZE, BOARD_ID_MAX_LENGTH, BOARD_ITEM_SCHEMAS, BOARD_ITEM_TYPES, BOARD_PLAY_MODES, BOARD_SHAPE_GEOMETRIES, BOARD_TASK_ARTIFACT_LIMIT, BOARD_TEXT_ALIGNS, BoardAnchorSide, BoardAnimation, BoardAnimationHeader, BoardAnimationHeaderSchema, BoardAnimationSchema, BoardArrowAnchor, BoardArrowAnchorSchema, BoardArrowBinding, BoardArrowBindingSchema, BoardArrowEnd, BoardArrowEndSchema, BoardArrowItem, BoardArrowItemSchema, BoardArrowRoute, BoardAudioItem, BoardAudioItemSchema, BoardBackgroundSchema, BoardBuiltinItem, BoardBuiltinItemType, BoardColor, BoardColorSchema, BoardColorToken, BoardCropSchema, BoardCustomItem, BoardCustomItemSchema, BoardDash, BoardDelta, BoardDiagnostic, BoardDocument, BoardDrawItem, BoardDrawItemSchema, BoardDrawPoint, BoardDrawPointSchema, BoardEaseSchema, BoardEffectItem, BoardEffectItemSchema, BoardEffectKind, BoardEnterPreset, BoardExtensionItem, BoardExtensionItemSchema, BoardFileBackedItem, BoardFileItem, BoardFileItemSchema, BoardFrameItem, BoardFrameItemSchema, BoardIdSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemPreview, BoardItemPreviewSchema, BoardItemType, BoardKeySchema, BoardKeyframe, BoardKeyframeSchema, BoardMarker, BoardMarkerSchema, BoardMediaOrigin, BoardMediaOriginSchema, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardMediaSource, BoardMediaSourceSchema, BoardPatch, BoardPatchSchema, BoardPlayMode, BoardScript, BoardScriptSchema, BoardSettings, BoardSettingsSchema, BoardShapeGeometry, BoardShapeItem, BoardShapeItemSchema, BoardSize, BoardSizeSchema, BoardStyle, BoardStyleSchema, BoardTaskArtifact, BoardTaskArtifactSchema, BoardTaskItem, BoardTaskItemSchema, BoardTaskSnapshot, BoardTaskSnapshotSchema, BoardTextItem, BoardTextItemSchema, BoardTrack, BoardTrackSchema, BoardVec2, BoardVec2Schema, BoardVideoItem, BoardVideoItemSchema, boardItemPreview, boardItemSchema, emptyBoardDocument, hasAuthoredSize, isBoardColorString, isBoardEase, isBuiltinItem, isBuiltinItemType, isEmptyBoardDelta, isFileBackedItem, isMediaItem, isSafeBoardSourcePath, parseBoardItem, parseBoardMediaSource };
|
|
@@ -406,10 +406,17 @@ const BoardCustomItemSchema = z.object({
|
|
|
406
406
|
props: z.object({}).strict().default({})
|
|
407
407
|
}).strict();
|
|
408
408
|
const BOARD_EXTENSION_TYPE_PATTERN = /^[a-z][a-z0-9]*(?:\.[a-z0-9-]+)+$/;
|
|
409
|
+
const BoardItemPreviewSchema = z.object({
|
|
410
|
+
title: z.string().max(500).optional(),
|
|
411
|
+
excerpt: z.string().max(2e3).optional(),
|
|
412
|
+
cover: BoardMediaSourceSchema.optional().describe("An image for the card."),
|
|
413
|
+
src: BoardMediaSourceSchema.optional().describe("The file or page the card stands for.")
|
|
414
|
+
}).strict().describe("How a client without this type's renderer shows the card, and what it refers to. The type's own client keeps it current.");
|
|
409
415
|
const BoardExtensionItemSchema = z.object({
|
|
410
416
|
...boxed(BOARD_DEFAULT_ITEM_SIZE),
|
|
411
417
|
type: z.string().regex(BOARD_EXTENSION_TYPE_PATTERN),
|
|
412
|
-
props: z.record(z.string(), z.unknown()).default({})
|
|
418
|
+
props: z.record(z.string(), z.unknown()).default({}),
|
|
419
|
+
preview: BoardItemPreviewSchema.optional()
|
|
413
420
|
}).strict();
|
|
414
421
|
const BOARD_ITEM_SCHEMAS = {
|
|
415
422
|
frame: BoardFrameItemSchema,
|
|
@@ -432,6 +439,9 @@ function isBuiltinItemType(type) {
|
|
|
432
439
|
function isBuiltinItem(item) {
|
|
433
440
|
return isBuiltinItemType(item.type);
|
|
434
441
|
}
|
|
442
|
+
function boardItemPreview(item) {
|
|
443
|
+
return isBuiltinItem(item) ? null : item.preview ?? null;
|
|
444
|
+
}
|
|
435
445
|
function isFileBackedItem(item) {
|
|
436
446
|
return item.type === "image" || item.type === "video" || item.type === "audio" || item.type === "file";
|
|
437
447
|
}
|
|
@@ -612,4 +622,4 @@ function isEmptyBoardDelta(delta) {
|
|
|
612
622
|
return !delta.board && Object.keys(delta.items ?? {}).length === 0 && Object.keys(delta.animations ?? {}).length === 0 && Object.keys(delta.tracks ?? {}).length === 0;
|
|
613
623
|
}
|
|
614
624
|
//#endregion
|
|
615
|
-
export { BOARD_ANCHOR_SIDES, BOARD_ARROW_ROUTES, BOARD_CAMERA_TARGET, BOARD_COLOR_TOKENS, BOARD_DASHES, BOARD_DERIVED_SIZE_TYPES, BOARD_EFFECT_KINDS, BOARD_EFFECT_KIND_INFO, BOARD_ENTER_PRESETS, BOARD_EXTENSION_TYPE_PATTERN, BOARD_GRID_DEFAULT_SIZE, BOARD_GRID_MAX_SIZE, BOARD_GRID_MIN_SIZE, BOARD_ID_MAX_LENGTH, BOARD_ITEM_SCHEMAS, BOARD_ITEM_TYPES, BOARD_PLAY_MODES, BOARD_SHAPE_GEOMETRIES, BOARD_TASK_ARTIFACT_LIMIT, BOARD_TEXT_ALIGNS, BoardAnimationHeaderSchema, BoardAnimationSchema, BoardArrowAnchorSchema, BoardArrowBindingSchema, BoardArrowEndSchema, BoardArrowItemSchema, BoardAudioItemSchema, BoardBackgroundSchema, BoardColorSchema, BoardCropSchema, BoardCustomItemSchema, BoardDrawItemSchema, BoardDrawPointSchema, BoardEaseSchema, BoardEffectItemSchema, BoardExtensionItemSchema, BoardFileItemSchema, BoardFrameItemSchema, BoardIdSchema, BoardImageItemSchema, BoardKeySchema, BoardKeyframeSchema, BoardMarkerSchema, BoardMediaOriginSchema, BoardMediaSnapshotSchema, BoardMediaSourceSchema, BoardPatchSchema, BoardScriptSchema, BoardSettingsSchema, BoardShapeItemSchema, BoardSizeSchema, BoardStyleSchema, BoardTaskArtifactSchema, BoardTaskItemSchema, BoardTaskSnapshotSchema, BoardTextItemSchema, BoardTrackSchema, BoardVec2Schema, BoardVideoItemSchema, boardItemSchema, emptyBoardDocument, hasAuthoredSize, isBoardColorString, isBoardEase, isBuiltinItem, isBuiltinItemType, isEmptyBoardDelta, isFileBackedItem, isMediaItem, isSafeBoardSourcePath, parseBoardItem, parseBoardMediaSource };
|
|
625
|
+
export { BOARD_ANCHOR_SIDES, BOARD_ARROW_ROUTES, BOARD_CAMERA_TARGET, BOARD_COLOR_TOKENS, BOARD_DASHES, BOARD_DERIVED_SIZE_TYPES, BOARD_EFFECT_KINDS, BOARD_EFFECT_KIND_INFO, BOARD_ENTER_PRESETS, BOARD_EXTENSION_TYPE_PATTERN, BOARD_GRID_DEFAULT_SIZE, BOARD_GRID_MAX_SIZE, BOARD_GRID_MIN_SIZE, BOARD_ID_MAX_LENGTH, BOARD_ITEM_SCHEMAS, BOARD_ITEM_TYPES, BOARD_PLAY_MODES, BOARD_SHAPE_GEOMETRIES, BOARD_TASK_ARTIFACT_LIMIT, BOARD_TEXT_ALIGNS, BoardAnimationHeaderSchema, BoardAnimationSchema, BoardArrowAnchorSchema, BoardArrowBindingSchema, BoardArrowEndSchema, BoardArrowItemSchema, BoardAudioItemSchema, BoardBackgroundSchema, BoardColorSchema, BoardCropSchema, BoardCustomItemSchema, BoardDrawItemSchema, BoardDrawPointSchema, BoardEaseSchema, BoardEffectItemSchema, BoardExtensionItemSchema, BoardFileItemSchema, BoardFrameItemSchema, BoardIdSchema, BoardImageItemSchema, BoardItemPreviewSchema, BoardKeySchema, BoardKeyframeSchema, BoardMarkerSchema, BoardMediaOriginSchema, BoardMediaSnapshotSchema, BoardMediaSourceSchema, BoardPatchSchema, BoardScriptSchema, BoardSettingsSchema, BoardShapeItemSchema, BoardSizeSchema, BoardStyleSchema, BoardTaskArtifactSchema, BoardTaskItemSchema, BoardTaskSnapshotSchema, BoardTextItemSchema, BoardTrackSchema, BoardVec2Schema, BoardVideoItemSchema, boardItemPreview, boardItemSchema, emptyBoardDocument, hasAuthoredSize, isBoardColorString, isBoardEase, isBuiltinItem, isBuiltinItemType, isEmptyBoardDelta, isFileBackedItem, isMediaItem, isSafeBoardSourcePath, parseBoardItem, parseBoardMediaSource };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BOARD_CAMERA_TARGET, BoardAnimationHeaderSchema, BoardColorSchema, BoardPatchSchema, BoardSettingsSchema, BoardTrackSchema, BoardVec2Schema, boardItemSchema, isBuiltinItemType, parseBoardItem } from "./board-model.js";
|
|
2
|
-
import { applyMatrix, arrowBindings, createBoardLayout, invertMatrix, isArrowBinding } from "./board-layout.js";
|
|
3
2
|
import { boardJsonEquals } from "./board-json.js";
|
|
3
|
+
import { applyMatrix, arrowBindings, createBoardLayout, invertMatrix, isArrowBinding } from "./board-layout.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region ../protocol/dist/board-patch.js
|
|
6
6
|
function slot(map, key) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region ../protocol/dist/board-schema.d.ts
|
|
2
2
|
type JsonSchema = Record<string, unknown>;
|
|
3
|
-
declare const BOARD_SCHEMA_TARGETS: readonly ["board", "animation", "track", "script", "camera", ...("arrow" | "audio" | "custom" | "draw" | "effect" | "file" | "frame" | "image" | "shape" | "task" | "text" | "video")[]];
|
|
3
|
+
declare const BOARD_SCHEMA_TARGETS: readonly ["board", "animation", "track", "script", "extension", "camera", ...("arrow" | "audio" | "custom" | "draw" | "effect" | "file" | "frame" | "image" | "shape" | "task" | "text" | "video")[]];
|
|
4
4
|
type BoardJsonSchemaResult = {
|
|
5
5
|
schema?: JsonSchema;
|
|
6
6
|
properties?: Array<{
|
|
@@ -73,6 +73,7 @@ declare function boardSchemaOverview(): {
|
|
|
73
73
|
};
|
|
74
74
|
};
|
|
75
75
|
types: ("arrow" | "audio" | "custom" | "draw" | "effect" | "file" | "frame" | "image" | "shape" | "task" | "text" | "video")[];
|
|
76
|
+
extensionTypes: string;
|
|
76
77
|
colors: {
|
|
77
78
|
tokens: readonly ["brand", "neutral", "black", "white", "blue", "green", "amber", "violet", "rose"];
|
|
78
79
|
css: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BOARD_DEFAULT_SIZES, BOARD_PAGE_SIZE, BOARD_TEXT_SCALE } from "./board-constants.js";
|
|
2
|
-
import { BOARD_CAMERA_TARGET, BOARD_COLOR_TOKENS, BOARD_EFFECT_KINDS, BOARD_EFFECT_KIND_INFO, BOARD_ITEM_SCHEMAS, BOARD_ITEM_TYPES, BoardAnimationHeaderSchema, BoardKeySchema, BoardScriptSchema, BoardSettingsSchema, BoardTrackSchema, isBuiltinItemType } from "./board-model.js";
|
|
2
|
+
import { BOARD_CAMERA_TARGET, BOARD_COLOR_TOKENS, BOARD_EFFECT_KINDS, BOARD_EFFECT_KIND_INFO, BOARD_ITEM_SCHEMAS, BOARD_ITEM_TYPES, BoardAnimationHeaderSchema, BoardExtensionItemSchema, BoardKeySchema, BoardScriptSchema, BoardSettingsSchema, BoardTrackSchema, isBuiltinItemType } from "./board-model.js";
|
|
3
3
|
import { listBoardProperties, resolveBoardProperty } from "./board-patch.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region ../protocol/dist/board-schema.js
|
|
@@ -15,6 +15,7 @@ const BOARD_SCHEMA_TARGETS = [
|
|
|
15
15
|
"animation",
|
|
16
16
|
"track",
|
|
17
17
|
"script",
|
|
18
|
+
"extension",
|
|
18
19
|
BOARD_CAMERA_TARGET,
|
|
19
20
|
...BOARD_ITEM_TYPES
|
|
20
21
|
];
|
|
@@ -26,6 +27,7 @@ function boardJsonSchema(target) {
|
|
|
26
27
|
};
|
|
27
28
|
if (target === "track") return { schema: toJsonSchema(BoardTrackSchema) };
|
|
28
29
|
if (target === "script") return { schema: toJsonSchema(BoardScriptSchema) };
|
|
30
|
+
if (target === "extension") return { schema: toJsonSchema(BoardExtensionItemSchema) };
|
|
29
31
|
if (target === "camera") {
|
|
30
32
|
const properties = listBoardProperties(target);
|
|
31
33
|
const fields = Object.fromEntries(properties.map(({ property }) => {
|
|
@@ -61,6 +63,7 @@ function boardSchemaOverview() {
|
|
|
61
63
|
itemSizes: BOARD_DEFAULT_SIZES
|
|
62
64
|
},
|
|
63
65
|
types: BOARD_ITEM_TYPES,
|
|
66
|
+
extensionTypes: "Namespaced types like acme.order keep props as written; give them a preview (title, excerpt, cover, src) so every client can show them. See schema extension.",
|
|
64
67
|
colors: {
|
|
65
68
|
tokens: BOARD_COLOR_TOKENS,
|
|
66
69
|
css: "any CSS color",
|
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
//#region ../protocol/dist/core/media.d.ts
|
|
2
2
|
/** Append an OSS process to `url`, keeping its query and hash. */
|
|
3
3
|
declare function ossProcessUrl(url: string, process: string): string;
|
|
4
|
+
type MediaInfo = {
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
durationMs?: number;
|
|
8
|
+
frameCount?: number;
|
|
9
|
+
firstFrameUrl?: string;
|
|
10
|
+
lastFrameUrl?: string;
|
|
11
|
+
mimeType?: string;
|
|
12
|
+
bytes?: number;
|
|
13
|
+
};
|
|
14
|
+
/** Media facts from response headers; `x-oss-meta-*` need CORS exposure in browsers. */
|
|
15
|
+
declare function mediaInfoFromHeaders(headers: Headers, url: string): MediaInfo;
|
|
16
|
+
declare function mergeMediaInfo(primary: MediaInfo, fallback: MediaInfo): MediaInfo;
|
|
17
|
+
type ProbeOptions = {
|
|
18
|
+
type: "image" | "video" | "audio";
|
|
19
|
+
fetch?: typeof globalThis.fetch;
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
};
|
|
22
|
+
declare function probeMediaInfo(url: string, options: ProbeOptions): Promise<MediaInfo>;
|
|
23
|
+
/**
|
|
24
|
+
* A video's dimensions, read from the header of its first frame rendered as a
|
|
25
|
+
* full-size PNG by OSS; only the first 24 bytes are read. Throws on failure.
|
|
26
|
+
*/
|
|
27
|
+
declare function probeVideoFrameSize(url: string, options?: {
|
|
28
|
+
fetch?: typeof globalThis.fetch;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}): Promise<Pick<MediaInfo, "width" | "height">>;
|
|
31
|
+
declare function probeMediaSize(url: string, type: "image" | "video", options?: {
|
|
32
|
+
fetch?: typeof globalThis.fetch;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
} | null>;
|
|
4
38
|
//#endregion
|
|
5
|
-
export { ossProcessUrl };
|
|
39
|
+
export { MediaInfo, mediaInfoFromHeaders, mergeMediaInfo, ossProcessUrl, probeMediaInfo, probeMediaSize, probeVideoFrameSize };
|
|
@@ -4,8 +4,12 @@ const MEDIA_HOSTS = /* @__PURE__ */ new Map([
|
|
|
4
4
|
transform: "oss",
|
|
5
5
|
metaHeaders: true
|
|
6
6
|
}],
|
|
7
|
+
["oss.talesofai.com", { transform: "oss" }],
|
|
7
8
|
["public.cohub.live", { transform: "oss" }],
|
|
9
|
+
["public.cohub.run", { transform: "oss" }],
|
|
8
10
|
["sessions.cohub.live", { transform: "oss" }],
|
|
11
|
+
["works.cohub.live", { transform: "oss" }],
|
|
12
|
+
["works.cohub.run", { transform: "oss" }],
|
|
9
13
|
["cca.neta.art", { transform: "cloudflare" }]
|
|
10
14
|
]);
|
|
11
15
|
const parseHttpsUrl = (url) => {
|
|
@@ -36,5 +40,127 @@ const MODEL_IMAGE = {
|
|
|
36
40
|
const { maxEdge, quality } = MODEL_IMAGE;
|
|
37
41
|
`${maxEdge}${maxEdge}${quality}`;
|
|
38
42
|
`${maxEdge}${maxEdge}${quality}`;
|
|
43
|
+
function positive(value) {
|
|
44
|
+
const number = typeof value === "string" ? Number(value) : value;
|
|
45
|
+
return typeof number === "number" && Number.isFinite(number) && number > 0 ? number : void 0;
|
|
46
|
+
}
|
|
47
|
+
function sameOriginUrl(value, origin) {
|
|
48
|
+
if (!value) return void 0;
|
|
49
|
+
try {
|
|
50
|
+
const url = new URL(value);
|
|
51
|
+
return url.protocol === "https:" && url.origin === origin ? url.href : void 0;
|
|
52
|
+
} catch {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function compact(info) {
|
|
57
|
+
return Object.fromEntries(Object.entries(info).filter(([, value]) => value !== void 0));
|
|
58
|
+
}
|
|
59
|
+
/** Media facts from response headers; `x-oss-meta-*` need CORS exposure in browsers. */
|
|
60
|
+
function mediaInfoFromHeaders(headers, url) {
|
|
61
|
+
const origin = new URL(url).origin;
|
|
62
|
+
const meta = (name) => headers.get(`x-oss-meta-${name}`);
|
|
63
|
+
const seconds = positive(meta("duration"));
|
|
64
|
+
const frameCount = positive(meta("nb-frames"));
|
|
65
|
+
return compact({
|
|
66
|
+
width: positive(meta("width")),
|
|
67
|
+
height: positive(meta("height")),
|
|
68
|
+
durationMs: seconds ? Math.round(seconds * 1e3) : void 0,
|
|
69
|
+
frameCount: frameCount ? Math.round(frameCount) : void 0,
|
|
70
|
+
firstFrameUrl: sameOriginUrl(meta("first-frame"), origin),
|
|
71
|
+
lastFrameUrl: sameOriginUrl(meta("last-frame"), origin),
|
|
72
|
+
mimeType: headers.get("content-type")?.split(";")[0]?.trim() || void 0,
|
|
73
|
+
bytes: positive(headers.get("content-length"))
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function mergeMediaInfo(primary, fallback) {
|
|
77
|
+
return compact({
|
|
78
|
+
...fallback,
|
|
79
|
+
...compact(primary)
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function hasDimensions(info) {
|
|
83
|
+
return info.width !== void 0 && info.height !== void 0;
|
|
84
|
+
}
|
|
85
|
+
async function probeHeaders(url, options) {
|
|
86
|
+
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
87
|
+
method: "HEAD",
|
|
88
|
+
signal: options.signal
|
|
89
|
+
});
|
|
90
|
+
return response.ok ? mediaInfoFromHeaders(response.headers, url) : {};
|
|
91
|
+
}
|
|
92
|
+
async function probeImageInfo(url, options) {
|
|
93
|
+
if (OSS_PROCESS_PATTERN.test(url)) return {};
|
|
94
|
+
const response = await (options.fetch ?? globalThis.fetch)(ossProcessUrl(url, "image/info"), { signal: options.signal });
|
|
95
|
+
if (!response.ok) return {};
|
|
96
|
+
const body = await response.json();
|
|
97
|
+
return compact({
|
|
98
|
+
width: positive(body.ImageWidth?.value),
|
|
99
|
+
height: positive(body.ImageHeight?.value),
|
|
100
|
+
bytes: positive(body.FileSize?.value)
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async function probeMediaInfo(url, options) {
|
|
104
|
+
const host = mediaHost(url);
|
|
105
|
+
if (host?.transform !== "oss") return {};
|
|
106
|
+
let info = {};
|
|
107
|
+
const attempt = async (strategy) => {
|
|
108
|
+
try {
|
|
109
|
+
info = mergeMediaInfo(info, await strategy());
|
|
110
|
+
} catch {}
|
|
111
|
+
};
|
|
112
|
+
if (host.metaHeaders) await attempt(() => probeHeaders(url, options));
|
|
113
|
+
if (options.type === "image" && !hasDimensions(info)) await attempt(() => probeImageInfo(url, options));
|
|
114
|
+
return info;
|
|
115
|
+
}
|
|
116
|
+
const PNG_SIGNATURE = [
|
|
117
|
+
137,
|
|
118
|
+
80,
|
|
119
|
+
78,
|
|
120
|
+
71,
|
|
121
|
+
13,
|
|
122
|
+
10,
|
|
123
|
+
26,
|
|
124
|
+
10
|
|
125
|
+
];
|
|
126
|
+
/**
|
|
127
|
+
* A video's dimensions, read from the header of its first frame rendered as a
|
|
128
|
+
* full-size PNG by OSS; only the first 24 bytes are read. Throws on failure.
|
|
129
|
+
*/
|
|
130
|
+
async function probeVideoFrameSize(url, options = {}) {
|
|
131
|
+
if (mediaHost(url)?.transform !== "oss" || OSS_PROCESS_PATTERN.test(url)) return {};
|
|
132
|
+
const response = await (options.fetch ?? globalThis.fetch)(ossProcessUrl(url, "video/snapshot,t_0,f_png,w_0,h_0,m_fast"), {
|
|
133
|
+
headers: { Range: "bytes=0-23" },
|
|
134
|
+
signal: options.signal
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
137
|
+
const header = /* @__PURE__ */ new Uint8Array(24);
|
|
138
|
+
let length = 0;
|
|
139
|
+
const reader = response.body?.getReader();
|
|
140
|
+
while (reader && length < header.length) {
|
|
141
|
+
const { done, value } = await reader.read();
|
|
142
|
+
if (done) break;
|
|
143
|
+
header.set(value.subarray(0, header.length - length), length);
|
|
144
|
+
length += Math.min(value.length, header.length - length);
|
|
145
|
+
}
|
|
146
|
+
await reader?.cancel().catch(() => void 0);
|
|
147
|
+
if (length < header.length || PNG_SIGNATURE.some((byte, index) => header[index] !== byte)) return {};
|
|
148
|
+
const view = new DataView(header.buffer);
|
|
149
|
+
return compact({
|
|
150
|
+
width: positive(view.getUint32(16)),
|
|
151
|
+
height: positive(view.getUint32(20))
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async function probeMediaSize(url, type, options = {}) {
|
|
155
|
+
const info = await probeMediaInfo(url, {
|
|
156
|
+
...options,
|
|
157
|
+
type
|
|
158
|
+
});
|
|
159
|
+
const { width, height } = info.width || type !== "video" ? info : await probeVideoFrameSize(url, options).catch(() => info);
|
|
160
|
+
return width && height ? {
|
|
161
|
+
width,
|
|
162
|
+
height
|
|
163
|
+
} : null;
|
|
164
|
+
}
|
|
39
165
|
//#endregion
|
|
40
|
-
export { MODEL_IMAGE, OSS_PROCESS_PATTERN, mediaHost, ossProcessUrl };
|
|
166
|
+
export { MODEL_IMAGE, OSS_PROCESS_PATTERN, mediaHost, mediaInfoFromHeaders, mergeMediaInfo, ossProcessUrl, probeMediaInfo, probeMediaSize, probeVideoFrameSize };
|