@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.
Files changed (79) hide show
  1. package/README.md +8 -5
  2. package/dist/board/editor/editor.d.ts +1 -0
  3. package/dist/board/editor/editor.js +4 -0
  4. package/dist/board/editor/snapshots.js +5 -57
  5. package/dist/board/export/index.d.ts +2 -2
  6. package/dist/board/export/node/fonts.js +4 -0
  7. package/dist/board/export/node/index.d.ts +2 -0
  8. package/dist/board/export/node/index.js +8 -1
  9. package/dist/board/export/scene.d.ts +2 -2
  10. package/dist/board/export/scene.js +2 -2
  11. package/dist/board/model/asset-key.d.ts +16 -0
  12. package/dist/board/model/asset-key.js +92 -0
  13. package/dist/board/model/export-assets.d.ts +7 -3
  14. package/dist/board/model/export-assets.js +34 -13
  15. package/dist/board/model/index.d.ts +7 -5
  16. package/dist/board/model/index.js +9 -7
  17. package/dist/board/model/items/create.js +1 -1
  18. package/dist/board/model/media-sizes.d.ts +28 -0
  19. package/dist/board/model/media-sizes.js +64 -0
  20. package/dist/board/model/palette.d.ts +1 -0
  21. package/dist/board/model/palette.js +20 -130
  22. package/dist/board/model/text-metrics.d.ts +8 -3
  23. package/dist/board/model/text-metrics.js +13 -2
  24. package/dist/board/model/themes.d.ts +488 -0
  25. package/dist/board/model/themes.js +495 -0
  26. package/dist/board/render/card-tag.js +2 -2
  27. package/dist/board/render/index.d.ts +6 -4
  28. package/dist/board/render/index.js +5 -3
  29. package/dist/board/render/lod.d.ts +22 -0
  30. package/dist/board/render/lod.js +27 -0
  31. package/dist/board/render/media-interaction.js +1 -1
  32. package/dist/board/render/palette.js +2 -25
  33. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  34. package/dist/board/render/renderers/board-renderer-registry.d.ts +10 -0
  35. package/dist/board/render/renderers/custom-card-renderer.js +5 -5
  36. package/dist/board/render/renderers/far-plate.d.ts +11 -1
  37. package/dist/board/render/renderers/far-plate.js +27 -8
  38. package/dist/board/render/renderers/file-card-renderer.js +3 -5
  39. package/dist/board/render/renderers/image-card-renderer.js +32 -6
  40. package/dist/board/render/renderers/model-card-renderer.js +1 -1
  41. package/dist/board/render/renderers/shape-card-renderer.js +25 -24
  42. package/dist/board/render/renderers/task-card-renderer.js +7 -8
  43. package/dist/board/render/renderers/text-card-renderer.js +86 -41
  44. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  45. package/dist/board/render/style.d.ts +7 -2
  46. package/dist/board/render/style.js +5 -1
  47. package/dist/board/render/surface.d.ts +5 -0
  48. package/dist/board/render/surface.js +13 -3
  49. package/dist/board/render/text-fit.js +8 -12
  50. package/dist/board/render/text-measurement.d.ts +15 -1
  51. package/dist/board/render/text-measurement.js +113 -20
  52. package/dist/board/render/text-resolution.d.ts +1 -6
  53. package/dist/board/render/text-resolution.js +1 -10
  54. package/dist/board/render/theme.d.ts +15 -0
  55. package/dist/board/render/theme.js +15 -0
  56. package/dist/board/stage/assets.d.ts +15 -4
  57. package/dist/board/stage/assets.js +155 -89
  58. package/dist/board/stage/index.d.ts +5 -5
  59. package/dist/board/stage/index.js +4 -4
  60. package/dist/board/stage/lru.d.ts +13 -9
  61. package/dist/board/stage/lru.js +38 -20
  62. package/dist/board/stage/scene.d.ts +11 -1
  63. package/dist/board/stage/scene.js +213 -138
  64. package/dist/board/stage/stage.d.ts +12 -1
  65. package/dist/board/stage/stage.js +97 -24
  66. package/dist/board/stage/texture-pool.d.ts +8 -1
  67. package/dist/board/stage/texture-pool.js +37 -1
  68. package/dist/index.js +137 -129
  69. package/dist/media.d.ts +3 -27
  70. package/dist/media.js +3 -82
  71. package/dist/protocol/dist/board-constants.js +3 -3
  72. package/dist/protocol/dist/board-layout.d.ts +17 -3
  73. package/dist/protocol/dist/board-layout.js +68 -11
  74. package/dist/protocol/dist/core/media.d.ts +35 -1
  75. package/dist/protocol/dist/core/media.js +123 -1
  76. package/dist/protocol/dist/index.d.ts +2 -2
  77. package/package.json +3 -2
  78. package/dist/board/model/image-key.d.ts +0 -9
  79. package/dist/board/model/image-key.js +0 -33
package/dist/media.js CHANGED
@@ -1,4 +1,4 @@
1
- import { OSS_PROCESS_PATTERN, mediaHost, ossProcessUrl } from "./protocol/dist/core/media.js";
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\", system-ui, -apple-system, \"Noto Sans CJK SC\", \"Noto Sans SC\", \"PingFang SC\", \"Microsoft YaHei\", \"Noto Sans Math\", \"Noto Sans Symbols 2\", \"DejaVu Sans\", \"FreeSans\", \"FreeSerif\", sans-serif";
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 BoardTextMeasurer = (text: string, fontSize: number, fontWeight: number, font: string) => number | null;
19
- declare function setBoardTextMeasurer(measurer?: BoardTextMeasurer): void;
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, BoardTextMeasurer, IDENTITY_MATRIX, anchorPoint, applyMatrix, arrowBindings, autoAnchorSide, bendControl, clampBoardFontSize, createBoardLayout, createDocumentLayout, drawPointsBounds, drawSampleRadius, estimateTextWidth, fitBoardMediaSize, framePoint, invertMatrix, isArrowBinding, layoutBoardText, multiplyMatrix, pointsRect, rotateAround, scaleVector, setBoardTextMeasurer, sideAnchor };
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 textMeasurer;
49
- function setBoardTextMeasurer(measurer) {
50
- textMeasurer = measurer;
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 measureLine(text, fontSize, fontWeight, font) {
70
- return textMeasurer?.(text, fontSize, fontWeight, font) ?? estimateTextWidth(text, fontSize);
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 measure = (text) => measureLine(text, fontSize, props.fontWeight ?? 500, props.font ?? "sans");
100
- const paragraphs = (props.text || "").split("\n");
101
- const lines = props.width ? paragraphs.flatMap((paragraph) => wrapParagraph(paragraph, props.width, measure)) : paragraphs;
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, widest, fontSize * .5),
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, setBoardTextMeasurer, sideAnchor };
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 };
@@ -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 };
@@ -36,5 +36,127 @@ const MODEL_IMAGE = {
36
36
  const { maxEdge, quality } = MODEL_IMAGE;
37
37
  `${maxEdge}${maxEdge}${quality}`;
38
38
  `${maxEdge}${maxEdge}${quality}`;
39
+ function positive(value) {
40
+ const number = typeof value === "string" ? Number(value) : value;
41
+ return typeof number === "number" && Number.isFinite(number) && number > 0 ? number : void 0;
42
+ }
43
+ function sameOriginUrl(value, origin) {
44
+ if (!value) return void 0;
45
+ try {
46
+ const url = new URL(value);
47
+ return url.protocol === "https:" && url.origin === origin ? url.href : void 0;
48
+ } catch {
49
+ return;
50
+ }
51
+ }
52
+ function compact(info) {
53
+ return Object.fromEntries(Object.entries(info).filter(([, value]) => value !== void 0));
54
+ }
55
+ /** Media facts from response headers; `x-oss-meta-*` need CORS exposure in browsers. */
56
+ function mediaInfoFromHeaders(headers, url) {
57
+ const origin = new URL(url).origin;
58
+ const meta = (name) => headers.get(`x-oss-meta-${name}`);
59
+ const seconds = positive(meta("duration"));
60
+ const frameCount = positive(meta("nb-frames"));
61
+ return compact({
62
+ width: positive(meta("width")),
63
+ height: positive(meta("height")),
64
+ durationMs: seconds ? Math.round(seconds * 1e3) : void 0,
65
+ frameCount: frameCount ? Math.round(frameCount) : void 0,
66
+ firstFrameUrl: sameOriginUrl(meta("first-frame"), origin),
67
+ lastFrameUrl: sameOriginUrl(meta("last-frame"), origin),
68
+ mimeType: headers.get("content-type")?.split(";")[0]?.trim() || void 0,
69
+ bytes: positive(headers.get("content-length"))
70
+ });
71
+ }
72
+ function mergeMediaInfo(primary, fallback) {
73
+ return compact({
74
+ ...fallback,
75
+ ...compact(primary)
76
+ });
77
+ }
78
+ function hasDimensions(info) {
79
+ return info.width !== void 0 && info.height !== void 0;
80
+ }
81
+ async function probeHeaders(url, options) {
82
+ const response = await (options.fetch ?? globalThis.fetch)(url, {
83
+ method: "HEAD",
84
+ signal: options.signal
85
+ });
86
+ return response.ok ? mediaInfoFromHeaders(response.headers, url) : {};
87
+ }
88
+ async function probeImageInfo(url, options) {
89
+ if (OSS_PROCESS_PATTERN.test(url)) return {};
90
+ const response = await (options.fetch ?? globalThis.fetch)(ossProcessUrl(url, "image/info"), { signal: options.signal });
91
+ if (!response.ok) return {};
92
+ const body = await response.json();
93
+ return compact({
94
+ width: positive(body.ImageWidth?.value),
95
+ height: positive(body.ImageHeight?.value),
96
+ bytes: positive(body.FileSize?.value)
97
+ });
98
+ }
99
+ async function probeMediaInfo(url, options) {
100
+ const host = mediaHost(url);
101
+ if (host?.transform !== "oss") return {};
102
+ let info = {};
103
+ const attempt = async (strategy) => {
104
+ try {
105
+ info = mergeMediaInfo(info, await strategy());
106
+ } catch {}
107
+ };
108
+ if (host.metaHeaders) await attempt(() => probeHeaders(url, options));
109
+ if (options.type === "image" && !hasDimensions(info)) await attempt(() => probeImageInfo(url, options));
110
+ return info;
111
+ }
112
+ const PNG_SIGNATURE = [
113
+ 137,
114
+ 80,
115
+ 78,
116
+ 71,
117
+ 13,
118
+ 10,
119
+ 26,
120
+ 10
121
+ ];
122
+ /**
123
+ * A video's dimensions, read from the header of its first frame rendered as a
124
+ * full-size PNG by OSS; only the first 24 bytes are read. Throws on failure.
125
+ */
126
+ async function probeVideoFrameSize(url, options = {}) {
127
+ if (mediaHost(url)?.transform !== "oss" || OSS_PROCESS_PATTERN.test(url)) return {};
128
+ const response = await (options.fetch ?? globalThis.fetch)(ossProcessUrl(url, "video/snapshot,t_0,f_png,w_0,h_0,m_fast"), {
129
+ headers: { Range: "bytes=0-23" },
130
+ signal: options.signal
131
+ });
132
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
133
+ const header = /* @__PURE__ */ new Uint8Array(24);
134
+ let length = 0;
135
+ const reader = response.body?.getReader();
136
+ while (reader && length < header.length) {
137
+ const { done, value } = await reader.read();
138
+ if (done) break;
139
+ header.set(value.subarray(0, header.length - length), length);
140
+ length += Math.min(value.length, header.length - length);
141
+ }
142
+ await reader?.cancel().catch(() => void 0);
143
+ if (length < header.length || PNG_SIGNATURE.some((byte, index) => header[index] !== byte)) return {};
144
+ const view = new DataView(header.buffer);
145
+ return compact({
146
+ width: positive(view.getUint32(16)),
147
+ height: positive(view.getUint32(20))
148
+ });
149
+ }
150
+ async function probeMediaSize(url, type, options = {}) {
151
+ const info = await probeMediaInfo(url, {
152
+ ...options,
153
+ type
154
+ });
155
+ const { width, height } = info.width || type !== "video" ? info : await probeVideoFrameSize(url, options).catch(() => info);
156
+ return width && height ? {
157
+ width,
158
+ height
159
+ } : null;
160
+ }
39
161
  //#endregion
40
- export { MODEL_IMAGE, OSS_PROCESS_PATTERN, mediaHost, ossProcessUrl };
162
+ export { MODEL_IMAGE, OSS_PROCESS_PATTERN, mediaHost, mediaInfoFromHeaders, mergeMediaInfo, ossProcessUrl, probeMediaInfo, probeMediaSize, probeVideoFrameSize };
@@ -1,5 +1,5 @@
1
1
  import { 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 } from "./board-model.js";
2
- import { BoardFrame, BoardItemLookup, BoardLayout, BoardMatrix, BoardRect, BoardTextLayout, BoardTextMeasurer, IDENTITY_MATRIX, anchorPoint, applyMatrix, arrowBindings, autoAnchorSide, bendControl, clampBoardFontSize, createBoardLayout, createDocumentLayout, drawPointsBounds, drawSampleRadius, estimateTextWidth, fitBoardMediaSize, framePoint, invertMatrix, isArrowBinding, layoutBoardText, multiplyMatrix, pointsRect, rotateAround, scaleVector, setBoardTextMeasurer, sideAnchor } from "./board-layout.js";
2
+ import { 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 } from "./board-layout.js";
3
3
  import { BoardApplyOptions, BoardPatchResult, BoardPropertySpec, BoardState, BoardValueKind, applyBoardDelta, applyBoardPatch, applyBoardPatchToDocument, boardPatchTouches, createDocumentState, diffBoardDocuments, diffMergePatch, isEmptyBoardPatch, listBoardProperties, mergePatch, parseBoardDocument, pruneBoardTrack, resolveBoardProperty, trackReferences } from "./board-patch.js";
4
4
  import "./app-authorization.js";
5
5
  import { RequestSource, RequestSourceVia } from "./provenance.js";
@@ -24,4 +24,4 @@ import "./app.js";
24
24
  import "./realtime/types.js";
25
25
  import "./app-catalog.js";
26
26
  import "./host-bridge.js";
27
- export { BOARD_ANCHOR_SIDES, BOARD_ARROW_ROUTES, BOARD_CAMERA_TARGET, BOARD_CLIPBOARD_KIND, BOARD_CLIPBOARD_MIME, 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_SCHEMA_TARGETS, BOARD_SHAPE_GEOMETRIES, BOARD_STROKE_MAX_SIZE, BOARD_STROKE_MIN_SIZE, BOARD_TASK_ARTIFACT_LIMIT, BOARD_TASK_SOURCE_PREFIX, BOARD_TEXT_ALIGNS, BoardAnchorSide, BoardAnimation, BoardAnimationHeader, BoardAnimationHeaderSchema, BoardAnimationSchema, BoardApplyOptions, BoardApplyResult, BoardArrowAnchor, BoardArrowAnchorSchema, BoardArrowBinding, BoardArrowBindingSchema, BoardArrowEnd, BoardArrowEndSchema, BoardArrowItem, BoardArrowItemSchema, BoardArrowRoute, BoardAudioItem, BoardAudioItemSchema, BoardBackgroundSchema, BoardBuiltinItem, BoardBuiltinItemType, BoardChangeSummary, BoardColor, BoardColorSchema, BoardColorToken, BoardCropSchema, BoardCustomItem, BoardCustomItemSchema, BoardDash, BoardDelta, BoardDiagnostic, BoardDocument, BoardDrawItem, BoardDrawItemSchema, BoardDrawPoint, BoardDrawPointSchema, BoardEaseSchema, BoardEffectItem, BoardEffectItemSchema, BoardEffectKind, BoardEnterPreset, BoardExtensionItem, BoardExtensionItemSchema, BoardFileBackedItem, BoardFileItem, BoardFileItemSchema, BoardFrame, BoardFrameItem, BoardFrameItemSchema, BoardHistoryPage, BoardIdSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemLookup, BoardItemType, BoardJsonSchemaResult, BoardKeySchema, BoardKeyframe, BoardKeyframeSchema, BoardLayout, BoardManifest, BoardManifestSchema, BoardMarker, BoardMarkerSchema, BoardMatrix, BoardMediaOrigin, BoardMediaOriginSchema, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardMediaSource, BoardMediaSourceSchema, BoardPatch, BoardPatchResult, BoardPatchSchema, BoardPlayMode, BoardPlaybackCommand, BoardPlaybackCommandSchema, BoardPlaybackSnapshot, BoardPlaybackStatus, BoardPropertySpec, BoardReadResult, BoardRect, BoardScript, BoardScriptSchema, BoardSettings, BoardSettingsSchema, BoardShapeGeometry, BoardShapeItem, BoardShapeItemSchema, BoardSize, BoardSizeSchema, BoardState, BoardStyle, BoardStyleSchema, BoardTaskArtifact, BoardTaskArtifactSchema, BoardTaskItem, BoardTaskItemSchema, BoardTaskRunSource, BoardTaskSnapshot, BoardTaskSnapshotSchema, BoardTextItem, BoardTextItemSchema, BoardTextLayout, BoardTextMeasurer, BoardTrack, BoardTrackSchema, BoardTransactionRecord, BoardValueKind, BoardVec2, BoardVec2Schema, BoardVideoItem, BoardVideoItemSchema, GenerationRunPayload, IDENTITY_MATRIX, ModelFormat, RequestSource, RequestSourceVia, TaskRunStatus, anchorPoint, applyBoardDelta, applyBoardPatch, applyBoardPatchToDocument, applyMatrix, arrowBindings, autoAnchorSide, bendControl, boardItemSchema, boardJsonSchema, boardPatchTouches, boardSchemaOverview, boardTaskSource, clampBoardFontSize, clampBoardStrokeSize, createBoardLayout, createDocumentLayout, createDocumentState, diffBoardDocuments, diffMergePatch, drawPointsBounds, drawSampleRadius, emptyBoardDocument, estimateTextWidth, featuredTaskArtifact, fitBoardMediaSize, framePoint, hasAuthoredSize, invertMatrix, isArrowBinding, isBoardColorString, isBoardEase, isBoardPath, isBuiltinItem, isBuiltinItemType, isEmptyBoardDelta, isEmptyBoardPatch, isFileBackedItem, isMediaItem, isPublicBoardRemoteAddress, isSafeBoardSourcePath, layoutBoardText, listBoardProperties, mergePatch, multiplyMatrix, normalizeBoardRemoteUrl, parseBoardDocument, parseBoardItem, parseBoardManifest, parseBoardMediaSource, pointsRect, pruneBoardTrack, rankedTaskArtifacts, resolveBoardProperty, rotateAround, scaleVector, serializeBoardManifest, setBoardTextMeasurer, sideAnchor, taskArtifactPreviewUrl, taskArtifacts, taskRunArtifacts, taskRunToBoardTaskSnapshot, trackReferences, withObservedArtifactFacts };
27
+ export { BOARD_ANCHOR_SIDES, BOARD_ARROW_ROUTES, BOARD_CAMERA_TARGET, BOARD_CLIPBOARD_KIND, BOARD_CLIPBOARD_MIME, 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_SCHEMA_TARGETS, BOARD_SHAPE_GEOMETRIES, BOARD_STROKE_MAX_SIZE, BOARD_STROKE_MIN_SIZE, BOARD_TASK_ARTIFACT_LIMIT, BOARD_TASK_SOURCE_PREFIX, BOARD_TEXT_ALIGNS, BoardAnchorSide, BoardAnimation, BoardAnimationHeader, BoardAnimationHeaderSchema, BoardAnimationSchema, BoardApplyOptions, BoardApplyResult, BoardArrowAnchor, BoardArrowAnchorSchema, BoardArrowBinding, BoardArrowBindingSchema, BoardArrowEnd, BoardArrowEndSchema, BoardArrowItem, BoardArrowItemSchema, BoardArrowRoute, BoardAudioItem, BoardAudioItemSchema, BoardBackgroundSchema, BoardBuiltinItem, BoardBuiltinItemType, BoardChangeSummary, BoardColor, BoardColorSchema, BoardColorToken, BoardCropSchema, BoardCustomItem, BoardCustomItemSchema, BoardDash, BoardDelta, BoardDiagnostic, BoardDocument, BoardDrawItem, BoardDrawItemSchema, BoardDrawPoint, BoardDrawPointSchema, BoardEaseSchema, BoardEffectItem, BoardEffectItemSchema, BoardEffectKind, BoardEnterPreset, BoardExtensionItem, BoardExtensionItemSchema, BoardFileBackedItem, BoardFileItem, BoardFileItemSchema, BoardFrame, BoardFrameItem, BoardFrameItemSchema, BoardHistoryPage, BoardIdSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemLookup, BoardItemType, BoardJsonSchemaResult, BoardKeySchema, BoardKeyframe, BoardKeyframeSchema, BoardLayout, BoardManifest, BoardManifestSchema, BoardMarker, BoardMarkerSchema, BoardMatrix, BoardMediaOrigin, BoardMediaOriginSchema, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardMediaSource, BoardMediaSourceSchema, BoardPatch, BoardPatchResult, BoardPatchSchema, BoardPlayMode, BoardPlaybackCommand, BoardPlaybackCommandSchema, BoardPlaybackSnapshot, BoardPlaybackStatus, BoardPropertySpec, BoardReadResult, BoardRect, BoardScript, BoardScriptSchema, BoardSettings, BoardSettingsSchema, BoardShapeGeometry, BoardShapeItem, BoardShapeItemSchema, BoardSize, BoardSizeSchema, BoardState, BoardStyle, BoardStyleSchema, BoardTaskArtifact, BoardTaskArtifactSchema, BoardTaskItem, BoardTaskItemSchema, BoardTaskRunSource, BoardTaskSnapshot, BoardTaskSnapshotSchema, BoardTextFont, BoardTextItem, BoardTextItemSchema, BoardTextLayout, BoardTextLayouter, BoardTextLines, BoardTrack, BoardTrackSchema, BoardTransactionRecord, BoardValueKind, BoardVec2, BoardVec2Schema, BoardVideoItem, BoardVideoItemSchema, GenerationRunPayload, IDENTITY_MATRIX, ModelFormat, RequestSource, RequestSourceVia, TaskRunStatus, adoptBoardNaturalSize, anchorPoint, applyBoardDelta, applyBoardPatch, applyBoardPatchToDocument, applyMatrix, arrowBindings, autoAnchorSide, bendControl, boardItemSchema, boardJsonSchema, boardPatchTouches, boardSchemaOverview, boardTaskSource, clampBoardFontSize, clampBoardStrokeSize, createBoardLayout, createDocumentLayout, createDocumentState, diffBoardDocuments, diffMergePatch, drawPointsBounds, drawSampleRadius, emptyBoardDocument, estimateTextWidth, featuredTaskArtifact, fitBoardMediaSize, framePoint, hasAuthoredSize, invertMatrix, isArrowBinding, isBoardColorString, isBoardEase, isBoardPath, isBuiltinItem, isBuiltinItemType, isEmptyBoardDelta, isEmptyBoardPatch, isFileBackedItem, isMediaItem, isPublicBoardRemoteAddress, isSafeBoardSourcePath, layoutBoardText, listBoardProperties, mergePatch, multiplyMatrix, normalizeBoardRemoteUrl, parseBoardDocument, parseBoardItem, parseBoardManifest, parseBoardMediaSource, pointsRect, pruneBoardTrack, rankedTaskArtifacts, resolveBoardProperty, rotateAround, scaleVector, serializeBoardManifest, setBoardTextLayouter, sideAnchor, taskArtifactPreviewUrl, taskArtifacts, taskRunArtifacts, taskRunToBoardTaskSnapshot, trackReferences, withObservedArtifactFacts };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub",
3
- "version": "12.1.1",
3
+ "version": "12.2.0",
4
4
  "description": "Cohub SDK for spaces, sessions, boards, and realtime agent collaboration.",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,
@@ -98,6 +98,7 @@
98
98
  "NOTICE"
99
99
  ],
100
100
  "dependencies": {
101
+ "@chenglou/pretext": "1.0.0",
101
102
  "es-module-lexer": "2.3.2",
102
103
  "perfect-freehand": "1.2.3",
103
104
  "zod": "^4.6.5"
@@ -105,7 +106,7 @@
105
106
  "devDependencies": {
106
107
  "@biomejs/biome": "2.5.15",
107
108
  "@cohub/protocol": "2.0.0",
108
- "@napi-rs/canvas": "^1.0.8",
109
+ "@napi-rs/canvas": "^1.0.13",
109
110
  "@types/node": "26.6.4",
110
111
  "pixi.js": "^8.20.1",
111
112
  "tsdown": "^0.22.14",
@@ -1,9 +0,0 @@
1
- import { BoardSceneItem } from "./scene.js";
2
- //#region src/board/model/image-key.d.ts
3
- declare function imageAssetKey(item: BoardSceneItem): string | null;
4
- declare function boardImageKeySource(key: string): {
5
- kind: "file" | "url";
6
- value: string;
7
- } | null;
8
- //#endregion
9
- export { boardImageKeySource, imageAssetKey };
@@ -1,33 +0,0 @@
1
- import { parseBoardMediaSource } from "../../protocol/dist/board-model.js";
2
- import { featuredTaskArtifact, taskArtifactPreviewUrl } from "../../protocol/dist/board-task.js";
3
- //#region src/board/model/image-key.ts
4
- function imageAssetKey(item) {
5
- if (item.type === "image") {
6
- const source = parseBoardMediaSource(item.props.src);
7
- return source ? `${source.kind}:${source.value}` : null;
8
- }
9
- if (item.type === "file") {
10
- const snapshot = item.props.snapshot;
11
- if (snapshot?.coverUrl) return `url:${snapshot.coverUrl}`;
12
- if (snapshot?.coverPath) return `file:${snapshot.coverPath}`;
13
- return null;
14
- }
15
- if (item.type === "task") {
16
- const url = taskArtifactPreviewUrl(featuredTaskArtifact(item.props.snapshot.artifacts));
17
- return url ? `url:${url}` : null;
18
- }
19
- return null;
20
- }
21
- function boardImageKeySource(key) {
22
- if (key.startsWith("file:")) return {
23
- kind: "file",
24
- value: key.slice(5)
25
- };
26
- if (key.startsWith("url:")) return {
27
- kind: "url",
28
- value: key.slice(4)
29
- };
30
- return null;
31
- }
32
- //#endregion
33
- export { boardImageKeySource, imageAssetKey };