@neta-art/cohub-cli 10.12.1 → 10.12.2
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 +1 -0
- package/dist/avatar.d.ts +7 -1
- package/dist/avatar.js +5 -4
- package/dist/board-export.js +9 -15
- package/dist/board-kernel.js +411 -143
- package/dist/board-media.js +5 -25
- package/dist/commands/boards.js +4 -1
- package/dist/commands/public.js +18 -0
- package/dist/commands/spaces.js +2 -0
- package/dist/local-media.d.ts +2 -0
- package/dist/local-media.js +30 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -323,6 +323,7 @@ Use the filters so a large Board is not read whole:
|
|
|
323
323
|
cohub -s <spaceId> boards get <boardId> --items title,note
|
|
324
324
|
cohub -s <spaceId> boards get <boardId> --within s1
|
|
325
325
|
cohub -s <spaceId> boards get <boardId> --rect 0,0,960,540
|
|
326
|
+
cohub -s <spaceId> boards get <boardId> --since 12 # only the changes after version 12
|
|
326
327
|
```
|
|
327
328
|
|
|
328
329
|
Start from an example and write it with `apply`, a JSON Merge Patch where fields
|
package/dist/avatar.d.ts
CHANGED
|
@@ -18,7 +18,11 @@ export declare function uploadAvatarAsset(input: {
|
|
|
18
18
|
uploadMethod: "PUT";
|
|
19
19
|
uploadHeaders?: Record<string, string>;
|
|
20
20
|
}>;
|
|
21
|
-
export declare function normalizeChatImageFile(path: string): Promise<
|
|
21
|
+
export declare function normalizeChatImageFile(path: string): Promise<{
|
|
22
|
+
body: Buffer<ArrayBuffer>;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
}>;
|
|
22
26
|
export declare function uploadChatImageAsset(input: {
|
|
23
27
|
client: CohubHttpClient;
|
|
24
28
|
spaceId?: string;
|
|
@@ -32,5 +36,7 @@ export declare function uploadChatImageAsset(input: {
|
|
|
32
36
|
uploadMethod: "PUT";
|
|
33
37
|
uploadHeaders?: Record<string, string>;
|
|
34
38
|
size: number;
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
35
41
|
}>;
|
|
36
42
|
export {};
|
package/dist/avatar.js
CHANGED
|
@@ -51,14 +51,15 @@ const CHAT_IMAGE_MAX_EDGE = 1984;
|
|
|
51
51
|
const CHAT_IMAGE_QUALITY = 86;
|
|
52
52
|
export async function normalizeChatImageFile(path) {
|
|
53
53
|
const sharp = await loadSharp();
|
|
54
|
-
|
|
54
|
+
const { data, info } = await sharp(path)
|
|
55
55
|
.rotate()
|
|
56
56
|
.resize(CHAT_IMAGE_MAX_EDGE, CHAT_IMAGE_MAX_EDGE, { fit: "inside", withoutEnlargement: true })
|
|
57
57
|
.webp({ quality: CHAT_IMAGE_QUALITY })
|
|
58
|
-
.toBuffer();
|
|
58
|
+
.toBuffer({ resolveWithObject: true });
|
|
59
|
+
return { body: data, width: info.width, height: info.height };
|
|
59
60
|
}
|
|
60
61
|
export async function uploadChatImageAsset(input) {
|
|
61
|
-
const body = await normalizeChatImageFile(input.path);
|
|
62
|
+
const { body, width, height } = await normalizeChatImageFile(input.path);
|
|
62
63
|
const asset = await input.client.publicAssets.uploadChatImageAttachment({
|
|
63
64
|
spaceId: input.spaceId,
|
|
64
65
|
sessionId: input.sessionId,
|
|
@@ -66,5 +67,5 @@ export async function uploadChatImageAsset(input) {
|
|
|
66
67
|
mimeType: "image/webp",
|
|
67
68
|
filename: "image.webp",
|
|
68
69
|
});
|
|
69
|
-
return { ...asset, size: body.byteLength };
|
|
70
|
+
return { ...asset, size: body.byteLength, width, height };
|
|
70
71
|
}
|
package/dist/board-export.js
CHANGED
|
@@ -9,7 +9,7 @@ import { dirname, join } from "node:path";
|
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
const execFile = promisify(execFileCallback);
|
|
12
|
-
import { parseBoardMediaSource } from "@neta-art/cohub/board";
|
|
12
|
+
import { boardMediaNaturalSize, parseBoardMediaSource } from "@neta-art/cohub/board";
|
|
13
13
|
import { imageVariantUrl, MEDIA_VARIANT_MAX, mediaVariantSize, videoFrameUrl } from "@neta-art/cohub/media";
|
|
14
14
|
import { hasBoardScripts, boardAssetKey, boardAssetKeySource, boardDocumentAt, boardRenderTheme, boardTextureEdge, boardTexturePixels, createBoardRenderSampler, buildBoardScene, createNodeBoardRenderer, createNodeBoardScriptHost, exportBoardImageBytes, parseBoardDocument, planBoardExport, prepareBoardExport, selectBoardExportAssets, } from "./board-kernel.js";
|
|
15
15
|
import { settleBoardDocument } from "./board-media.js";
|
|
@@ -135,12 +135,10 @@ async function fetchBoardPicture(client, spaceId, source, item, edge) {
|
|
|
135
135
|
const url = source.kind === "url"
|
|
136
136
|
? source.value
|
|
137
137
|
: video || variantEdge
|
|
138
|
-
? await client.space(spaceId).files.
|
|
138
|
+
? (await client.space(spaceId).files.resolve(source.value, { purpose: "playback" }).catch(() => null))?.url
|
|
139
139
|
: null;
|
|
140
140
|
if (video) {
|
|
141
|
-
const
|
|
142
|
-
const width = w && h ? Math.min(w, (edge * w) / Math.max(w, h)) : edge;
|
|
143
|
-
const frame = url && videoFrameUrl(url, mediaVariantSize(width));
|
|
141
|
+
const frame = url && videoFrameUrl(url, mediaVariantSize(edge), { natural: boardMediaNaturalSize(item) });
|
|
144
142
|
return frame ? downloadPublicImage(frame) : null;
|
|
145
143
|
}
|
|
146
144
|
const variant = url && variantEdge && imageVariantUrl(url, variantEdge);
|
|
@@ -309,13 +307,12 @@ export async function runBoardExport(options) {
|
|
|
309
307
|
const warnings = [];
|
|
310
308
|
const textures = textureCache.textures;
|
|
311
309
|
let omittedKeys = new Set();
|
|
312
|
-
const
|
|
313
|
-
if (options.withImages &&
|
|
314
|
-
options.background === "paper" &&
|
|
315
|
-
declaredBackground.kind === "image" &&
|
|
316
|
-
declaredBackground.imageUrl) {
|
|
310
|
+
const backgroundSource = document.board.background.image && parseBoardMediaSource(document.board.background.image.src);
|
|
311
|
+
if (options.withImages && options.background === "paper" && backgroundSource) {
|
|
317
312
|
try {
|
|
318
|
-
const { bytes, mimeType } =
|
|
313
|
+
const { bytes, mimeType } = backgroundSource.kind === "url"
|
|
314
|
+
? await downloadPublicImage(backgroundSource.value)
|
|
315
|
+
: await readSpaceFileBytes(createClient(), options.spaceId, backgroundSource.value);
|
|
319
316
|
backgroundTexture = await renderer.decodeImage(bytes, mimeType);
|
|
320
317
|
}
|
|
321
318
|
catch (error) {
|
|
@@ -333,9 +330,6 @@ export async function runBoardExport(options) {
|
|
|
333
330
|
},
|
|
334
331
|
});
|
|
335
332
|
const host = scriptHost;
|
|
336
|
-
const backgroundImage = backgroundTexture
|
|
337
|
-
? { texture: backgroundTexture, fit: declaredBackground.fit ?? "cover", position: "center", opacity: declaredBackground.opacity ?? 1 }
|
|
338
|
-
: undefined;
|
|
339
333
|
const assetKey = (item) => {
|
|
340
334
|
const key = boardExportAssetKey(item);
|
|
341
335
|
return key && omittedKeys.has(key) ? null : key;
|
|
@@ -389,7 +383,7 @@ export async function runBoardExport(options) {
|
|
|
389
383
|
background: options.background,
|
|
390
384
|
textures,
|
|
391
385
|
scripts: scriptHost ?? undefined,
|
|
392
|
-
|
|
386
|
+
backgroundTexture,
|
|
393
387
|
assetKey,
|
|
394
388
|
format: options.format,
|
|
395
389
|
quality: options.quality,
|
package/dist/board-kernel.js
CHANGED
|
@@ -6281,27 +6281,28 @@ const BOARD_ENTER_PRESETS$1 = [
|
|
|
6281
6281
|
"deal"
|
|
6282
6282
|
];
|
|
6283
6283
|
const BoardBackgroundSchema$1 = object({
|
|
6284
|
-
kind: _enum([
|
|
6285
|
-
"solid",
|
|
6286
|
-
"dots",
|
|
6287
|
-
"grid",
|
|
6288
|
-
"image"
|
|
6289
|
-
]).default("dots"),
|
|
6290
6284
|
color: BoardColorSchema$1.optional(),
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
"
|
|
6294
|
-
"
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6285
|
+
pattern: _enum([
|
|
6286
|
+
"none",
|
|
6287
|
+
"dots",
|
|
6288
|
+
"grid"
|
|
6289
|
+
]).optional().describe("Spaced by grid.size; omit to follow the theme."),
|
|
6290
|
+
image: object({
|
|
6291
|
+
src: BoardMediaSourceSchema$1,
|
|
6292
|
+
fit: _enum([
|
|
6293
|
+
"cover",
|
|
6294
|
+
"contain",
|
|
6295
|
+
"repeat"
|
|
6296
|
+
]).default("cover").describe("cover and contain fill the view; repeat tiles the Board at one board unit per image pixel."),
|
|
6297
|
+
opacity: ratio$1.optional()
|
|
6298
|
+
}).strict().optional()
|
|
6299
|
+
}).strict().describe("Layers drawn under every item: color, then image, then pattern. Without a color or image the theme's backdrop shows.");
|
|
6299
6300
|
const BoardSettingsSchema$1 = object({
|
|
6300
6301
|
title: string().min(1).max(255).optional(),
|
|
6301
|
-
background: BoardBackgroundSchema$1.default({
|
|
6302
|
+
background: BoardBackgroundSchema$1.default({}),
|
|
6302
6303
|
grid: object({
|
|
6303
|
-
|
|
6304
|
-
|
|
6304
|
+
size: finite$1.min(4).max(512).default(24).describe("Pattern spacing and snap step, in board units."),
|
|
6305
|
+
snap: boolean().default(false).describe("Snap moved and resized items to the grid.")
|
|
6305
6306
|
}).strict().optional(),
|
|
6306
6307
|
enter: object({
|
|
6307
6308
|
preset: _enum(BOARD_ENTER_PRESETS$1),
|
|
@@ -7455,10 +7456,14 @@ object({
|
|
|
7455
7456
|
items: array(string().min(1)).max(5e4).optional(),
|
|
7456
7457
|
animations: array(string().min(1)).max(1e4).optional(),
|
|
7457
7458
|
limit: number$1().int().min(1).max(5e4).optional(),
|
|
7458
|
-
cursor: string().min(1).optional()
|
|
7459
|
+
cursor: string().min(1).optional(),
|
|
7460
|
+
since: number$1().int().nonnegative().optional()
|
|
7459
7461
|
}).strict().refine((read) => !(read.cursor && (read.rect || read.within)), {
|
|
7460
7462
|
path: ["cursor"],
|
|
7461
7463
|
message: "cursor pages by id, so it cannot be combined with rect or within"
|
|
7464
|
+
}).refine((read) => read.since === void 0 || Object.entries(read).every(([key, value]) => key === "since" || value === void 0), {
|
|
7465
|
+
path: ["since"],
|
|
7466
|
+
message: "since syncs the whole Board, so it cannot be combined with other read options"
|
|
7462
7467
|
});
|
|
7463
7468
|
object({
|
|
7464
7469
|
patch: BoardPatchSchema$1,
|
|
@@ -9305,57 +9310,7 @@ function createStrokeRibbonBuilder(size) {
|
|
|
9305
9310
|
}
|
|
9306
9311
|
//#endregion
|
|
9307
9312
|
//#region ../protocol/src/core/media.ts
|
|
9308
|
-
|
|
9309
|
-
["router-files.neta.art", {
|
|
9310
|
-
transform: "oss",
|
|
9311
|
-
metaHeaders: true
|
|
9312
|
-
}],
|
|
9313
|
-
["oss.talesofai.com", { transform: "oss" }],
|
|
9314
|
-
["public.cohub.live", { transform: "oss" }],
|
|
9315
|
-
["public.cohub.run", { transform: "oss" }],
|
|
9316
|
-
["sessions.cohub.live", { transform: "oss" }],
|
|
9317
|
-
["works.cohub.live", { transform: "oss" }],
|
|
9318
|
-
["works.cohub.run", { transform: "oss" }],
|
|
9319
|
-
["cca.neta.art", { transform: "cloudflare" }]
|
|
9320
|
-
]);
|
|
9321
|
-
const parseHttpsUrl = (url) => {
|
|
9322
|
-
if (!/^https:\/\//i.test(url)) return null;
|
|
9323
|
-
try {
|
|
9324
|
-
return new URL(url);
|
|
9325
|
-
} catch {
|
|
9326
|
-
return null;
|
|
9327
|
-
}
|
|
9328
|
-
};
|
|
9329
|
-
function mediaHost(url) {
|
|
9330
|
-
const parsed = parseHttpsUrl(url);
|
|
9331
|
-
return parsed ? MEDIA_HOSTS.get(parsed.hostname) : void 0;
|
|
9332
|
-
}
|
|
9333
|
-
const OSS_PROCESS_PATTERN = /[?&]x-oss-process=/;
|
|
9334
|
-
/** Append an OSS process to `url`, keeping its query and hash. */
|
|
9335
|
-
function ossProcessUrl(url, process) {
|
|
9336
|
-
if (/^(data|blob):/i.test(url) || OSS_PROCESS_PATTERN.test(url)) return url;
|
|
9337
|
-
const hashIndex = url.indexOf("#");
|
|
9338
|
-
const base = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
|
|
9339
|
-
const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
|
|
9340
|
-
return `${base}${base.includes("?") ? "&" : "?"}x-oss-process=${process}${hash}`;
|
|
9341
|
-
}
|
|
9342
|
-
const { maxEdge, quality } = {
|
|
9343
|
-
maxEdge: 1280,
|
|
9344
|
-
quality: 75
|
|
9345
|
-
};
|
|
9346
|
-
`${maxEdge}${maxEdge}${quality}`;
|
|
9347
|
-
`${maxEdge}${maxEdge}${quality}`;
|
|
9348
|
-
//#endregion
|
|
9349
|
-
//#region ../sdk/src/media.ts
|
|
9350
|
-
/**
|
|
9351
|
-
* Remote media delivery. Previews and metadata resolve through ordered
|
|
9352
|
-
* strategies — the cheapest trustworthy source first, each later one only
|
|
9353
|
-
* filling what the earlier ones left — so every consumer degrades the same way.
|
|
9354
|
-
*
|
|
9355
|
-
* URL rewrites happen locally without a request, so they apply only to hosts
|
|
9356
|
-
* known to serve Aliyun OSS processing; any other URL is used as is.
|
|
9357
|
-
*/
|
|
9358
|
-
/** Fixed variant widths keep CDN cache hits high; pick with `mediaVariantSize`. */
|
|
9313
|
+
/** Fixed variant edges keep CDN cache hits high; pick one with `mediaVariantSize`. */
|
|
9359
9314
|
const MEDIA_VARIANT_SIZES = [
|
|
9360
9315
|
192,
|
|
9361
9316
|
384,
|
|
@@ -9366,15 +9321,7 @@ const MEDIA_VARIANT_SIZES = [
|
|
|
9366
9321
|
function mediaVariantSize(pixels) {
|
|
9367
9322
|
return MEDIA_VARIANT_SIZES.find((size) => size >= pixels) ?? 1536;
|
|
9368
9323
|
}
|
|
9369
|
-
|
|
9370
|
-
const host = mediaHost(url);
|
|
9371
|
-
return host?.transform === "oss" ? host : void 0;
|
|
9372
|
-
}
|
|
9373
|
-
/** A still JPEG of a video's first frame, `width` wide. */
|
|
9374
|
-
function videoFrameUrl(url, width) {
|
|
9375
|
-
if (!ossHost(url)) return null;
|
|
9376
|
-
return ossProcessUrl(url, `video/snapshot,t_0,f_jpg,w_${width},m_fast`);
|
|
9377
|
-
}
|
|
9324
|
+
Number.POSITIVE_INFINITY;
|
|
9378
9325
|
//#endregion
|
|
9379
9326
|
//#region ../sdk/src/board/model/media.ts
|
|
9380
9327
|
function getMediaResourceTitle(value) {
|
|
@@ -9430,9 +9377,10 @@ function taskPreviewKey(item) {
|
|
|
9430
9377
|
const artifact = featuredTaskArtifact(item.props.snapshot.artifacts);
|
|
9431
9378
|
const cover = taskArtifactPreviewUrl(artifact);
|
|
9432
9379
|
if (cover) return `url:${cover}`;
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9380
|
+
return artifact?.type === "video" ? stillKey("video", {
|
|
9381
|
+
kind: "url",
|
|
9382
|
+
value: artifact.url
|
|
9383
|
+
}, void 0) : null;
|
|
9436
9384
|
}
|
|
9437
9385
|
function boardAssetKey(item) {
|
|
9438
9386
|
if (item.type === "task") return taskPreviewKey(item);
|
|
@@ -9913,7 +9861,13 @@ const BOARD_THEME_DATA = {
|
|
|
9913
9861
|
label: 15330286
|
|
9914
9862
|
}
|
|
9915
9863
|
},
|
|
9916
|
-
style: {}
|
|
9864
|
+
style: {},
|
|
9865
|
+
backdrop: { pattern: {
|
|
9866
|
+
kind: "dots",
|
|
9867
|
+
pitch: 24,
|
|
9868
|
+
color: 2566444,
|
|
9869
|
+
size: 2
|
|
9870
|
+
} }
|
|
9917
9871
|
},
|
|
9918
9872
|
light: {
|
|
9919
9873
|
colorScheme: "light",
|
|
@@ -9976,7 +9930,13 @@ const BOARD_THEME_DATA = {
|
|
|
9976
9930
|
label: 66050
|
|
9977
9931
|
}
|
|
9978
9932
|
},
|
|
9979
|
-
style: {}
|
|
9933
|
+
style: {},
|
|
9934
|
+
backdrop: { pattern: {
|
|
9935
|
+
kind: "dots",
|
|
9936
|
+
pitch: 24,
|
|
9937
|
+
color: 15001321,
|
|
9938
|
+
size: 2
|
|
9939
|
+
} }
|
|
9980
9940
|
},
|
|
9981
9941
|
"solarized-dark": {
|
|
9982
9942
|
colorScheme: "dark",
|
|
@@ -10039,7 +9999,13 @@ const BOARD_THEME_DATA = {
|
|
|
10039
9999
|
label: 12572374
|
|
10040
10000
|
}
|
|
10041
10001
|
},
|
|
10042
|
-
style: {}
|
|
10002
|
+
style: {},
|
|
10003
|
+
backdrop: { pattern: {
|
|
10004
|
+
kind: "dots",
|
|
10005
|
+
pitch: 24,
|
|
10006
|
+
color: 1122594,
|
|
10007
|
+
size: 2
|
|
10008
|
+
} }
|
|
10043
10009
|
},
|
|
10044
10010
|
"solarized-light": {
|
|
10045
10011
|
colorScheme: "light",
|
|
@@ -10102,7 +10068,13 @@ const BOARD_THEME_DATA = {
|
|
|
10102
10068
|
label: 2110772
|
|
10103
10069
|
}
|
|
10104
10070
|
},
|
|
10105
|
-
style: {}
|
|
10071
|
+
style: {},
|
|
10072
|
+
backdrop: { pattern: {
|
|
10073
|
+
kind: "dots",
|
|
10074
|
+
pitch: 24,
|
|
10075
|
+
color: 15130570,
|
|
10076
|
+
size: 2
|
|
10077
|
+
} }
|
|
10106
10078
|
},
|
|
10107
10079
|
"neta-studio": {
|
|
10108
10080
|
colorScheme: "dark",
|
|
@@ -10252,7 +10224,14 @@ const BOARD_THEME_DATA = {
|
|
|
10252
10224
|
motion: "stepped",
|
|
10253
10225
|
font: "\"Figtree Variable\", \"PingFang SC\", \"Noto Sans CJK SC\", \"Microsoft YaHei\", system-ui, sans-serif",
|
|
10254
10226
|
frameLabelSize: 16
|
|
10255
|
-
}
|
|
10227
|
+
},
|
|
10228
|
+
backdrop: { pattern: {
|
|
10229
|
+
kind: "dots",
|
|
10230
|
+
pitch: 24,
|
|
10231
|
+
color: 14935011,
|
|
10232
|
+
size: 2,
|
|
10233
|
+
shape: "square"
|
|
10234
|
+
} }
|
|
10256
10235
|
},
|
|
10257
10236
|
"neta-pixel-dark": {
|
|
10258
10237
|
colorScheme: "dark",
|
|
@@ -10336,7 +10315,14 @@ const BOARD_THEME_DATA = {
|
|
|
10336
10315
|
motion: "stepped",
|
|
10337
10316
|
font: "\"Figtree Variable\", \"PingFang SC\", \"Noto Sans CJK SC\", \"Microsoft YaHei\", system-ui, sans-serif",
|
|
10338
10317
|
frameLabelSize: 16
|
|
10339
|
-
}
|
|
10318
|
+
},
|
|
10319
|
+
backdrop: { pattern: {
|
|
10320
|
+
kind: "dots",
|
|
10321
|
+
pitch: 24,
|
|
10322
|
+
color: 3816512,
|
|
10323
|
+
size: 2,
|
|
10324
|
+
shape: "square"
|
|
10325
|
+
} }
|
|
10340
10326
|
}
|
|
10341
10327
|
};
|
|
10342
10328
|
//#endregion
|
|
@@ -33089,6 +33075,288 @@ function boardTextSize(item) {
|
|
|
33089
33075
|
return clampBoardFontSize(item.props.fontSize ?? 24) * scale;
|
|
33090
33076
|
}
|
|
33091
33077
|
//#endregion
|
|
33078
|
+
//#region ../sdk/src/board/render/backdrop.ts
|
|
33079
|
+
const PATTERN_DEFAULTS = {
|
|
33080
|
+
dots: {
|
|
33081
|
+
alpha: .5,
|
|
33082
|
+
size: 2
|
|
33083
|
+
},
|
|
33084
|
+
grid: {
|
|
33085
|
+
alpha: 1,
|
|
33086
|
+
size: 1
|
|
33087
|
+
}
|
|
33088
|
+
};
|
|
33089
|
+
function resolveBoardBackdrop(settings, theme, purpose = "stage") {
|
|
33090
|
+
const { color, image, pattern } = settings.background;
|
|
33091
|
+
const themed = !(color !== void 0 || image !== void 0) && purpose === "stage" ? theme.backdrop : void 0;
|
|
33092
|
+
return {
|
|
33093
|
+
color: backgroundColor(settings.background.color, theme),
|
|
33094
|
+
image: image ? {
|
|
33095
|
+
src: image.src,
|
|
33096
|
+
fit: image.fit,
|
|
33097
|
+
opacity: image.opacity ?? 1
|
|
33098
|
+
} : themed?.image ?? null,
|
|
33099
|
+
pattern: purpose === "export" || pattern === "none" ? null : pattern ? patternStyle(theme, pattern, settings.grid?.size ?? 24, false) : themed?.pattern ? patternStyle(theme, themed.pattern.kind, themed.pattern.pitch, true) : null
|
|
33100
|
+
};
|
|
33101
|
+
}
|
|
33102
|
+
function backgroundColor(color, theme) {
|
|
33103
|
+
const value = typeof color === "object" ? color[theme.colorScheme] : color;
|
|
33104
|
+
if (!value) return theme.palette.bg;
|
|
33105
|
+
const token = value.trim().toLowerCase();
|
|
33106
|
+
if (isBoardColorId(token)) return pickBoardColor(theme.colors, token, theme.colorScheme).fill;
|
|
33107
|
+
return parseBoardCssColor(value) ?? theme.palette.bg;
|
|
33108
|
+
}
|
|
33109
|
+
function patternStyle(theme, kind, pitch, subdivide) {
|
|
33110
|
+
const own = theme.backdrop?.pattern?.kind === kind ? theme.backdrop.pattern : void 0;
|
|
33111
|
+
const defaults = PATTERN_DEFAULTS[kind];
|
|
33112
|
+
return {
|
|
33113
|
+
kind,
|
|
33114
|
+
pitch,
|
|
33115
|
+
color: own?.color ?? (kind === "grid" ? theme.palette.border : theme.palette.muted),
|
|
33116
|
+
alpha: own ? own.alpha ?? 1 : defaults.alpha,
|
|
33117
|
+
size: own?.size ?? defaults.size,
|
|
33118
|
+
shape: own?.shape ?? "round",
|
|
33119
|
+
subdivide
|
|
33120
|
+
};
|
|
33121
|
+
}
|
|
33122
|
+
const MIN_GAP = 8;
|
|
33123
|
+
const FADE = .25;
|
|
33124
|
+
function boardPatternLevel(pattern, zoom) {
|
|
33125
|
+
const gap = Math.max(pattern.pitch / 1.25, MIN_GAP);
|
|
33126
|
+
const unit = pattern.pitch * Math.max(zoom, 1e-6);
|
|
33127
|
+
const level = Math.ceil(Math.log2(gap / unit));
|
|
33128
|
+
const pitch = unit * 2 ** (pattern.subdivide ? level : Math.max(0, level));
|
|
33129
|
+
const t = Math.min(1, Math.max(0, (pitch / gap - 1) / FADE));
|
|
33130
|
+
return {
|
|
33131
|
+
pitch,
|
|
33132
|
+
fade: t * t * (3 - 2 * t)
|
|
33133
|
+
};
|
|
33134
|
+
}
|
|
33135
|
+
//#endregion
|
|
33136
|
+
//#region ../sdk/src/board/render/backdrop-layer.ts
|
|
33137
|
+
const VERTEX = `
|
|
33138
|
+
in vec2 aPosition;
|
|
33139
|
+
out vec2 vPosition;
|
|
33140
|
+
|
|
33141
|
+
uniform mat3 uProjectionMatrix;
|
|
33142
|
+
uniform mat3 uWorldTransformMatrix;
|
|
33143
|
+
uniform mat3 uTransformMatrix;
|
|
33144
|
+
|
|
33145
|
+
void main() {
|
|
33146
|
+
mat3 mvp = uProjectionMatrix * uWorldTransformMatrix * uTransformMatrix;
|
|
33147
|
+
gl_Position = vec4((mvp * vec3(aPosition, 1.0)).xy, 0.0, 1.0);
|
|
33148
|
+
vPosition = aPosition;
|
|
33149
|
+
}
|
|
33150
|
+
`;
|
|
33151
|
+
const FRAGMENT = `
|
|
33152
|
+
in vec2 vPosition;
|
|
33153
|
+
out vec4 finalColor;
|
|
33154
|
+
|
|
33155
|
+
uniform float uDensity;
|
|
33156
|
+
uniform vec2 uOrigin;
|
|
33157
|
+
uniform float uPitch;
|
|
33158
|
+
uniform float uFade;
|
|
33159
|
+
uniform float uSize;
|
|
33160
|
+
uniform float uSquare;
|
|
33161
|
+
uniform float uLines;
|
|
33162
|
+
uniform vec4 uInk;
|
|
33163
|
+
|
|
33164
|
+
vec2 snap(vec2 center) {
|
|
33165
|
+
return mod(uSize, 2.0) == 1.0 ? floor(center) + 0.5 : floor(center + 0.5);
|
|
33166
|
+
}
|
|
33167
|
+
|
|
33168
|
+
float coverage(vec2 p, float pitch) {
|
|
33169
|
+
vec2 d = abs(p - snap(uOrigin + floor((p - uOrigin) / pitch + 0.5) * pitch));
|
|
33170
|
+
float radius = uSize * 0.5;
|
|
33171
|
+
if (uLines > 0.5) return min(d.x, d.y) < radius ? 1.0 : 0.0;
|
|
33172
|
+
if (uSquare > 0.5) return max(d.x, d.y) < radius ? 1.0 : 0.0;
|
|
33173
|
+
float edge = 0.5 * uDensity;
|
|
33174
|
+
return clamp((radius + edge - length(d)) / edge, 0.0, 1.0);
|
|
33175
|
+
}
|
|
33176
|
+
|
|
33177
|
+
void main() {
|
|
33178
|
+
vec2 p = vPosition * uDensity;
|
|
33179
|
+
float alpha = uInk.a * max(coverage(p, uPitch * 2.0), coverage(p, uPitch) * uFade);
|
|
33180
|
+
finalColor = vec4(uInk.rgb * alpha, alpha);
|
|
33181
|
+
}
|
|
33182
|
+
`;
|
|
33183
|
+
function wrap(value, period) {
|
|
33184
|
+
return (value % period + period) % period;
|
|
33185
|
+
}
|
|
33186
|
+
function createPatternMesh() {
|
|
33187
|
+
const positions = /* @__PURE__ */ new Float32Array(8);
|
|
33188
|
+
const geometry = new Geometry({
|
|
33189
|
+
attributes: { aPosition: {
|
|
33190
|
+
buffer: positions,
|
|
33191
|
+
format: "float32x2"
|
|
33192
|
+
} },
|
|
33193
|
+
indexBuffer: new Uint32Array([
|
|
33194
|
+
0,
|
|
33195
|
+
1,
|
|
33196
|
+
2,
|
|
33197
|
+
0,
|
|
33198
|
+
2,
|
|
33199
|
+
3
|
|
33200
|
+
])
|
|
33201
|
+
});
|
|
33202
|
+
const uniforms = new UniformGroup({
|
|
33203
|
+
uDensity: {
|
|
33204
|
+
value: 1,
|
|
33205
|
+
type: "f32"
|
|
33206
|
+
},
|
|
33207
|
+
uOrigin: {
|
|
33208
|
+
value: /* @__PURE__ */ new Float32Array(2),
|
|
33209
|
+
type: "vec2<f32>"
|
|
33210
|
+
},
|
|
33211
|
+
uPitch: {
|
|
33212
|
+
value: 1,
|
|
33213
|
+
type: "f32"
|
|
33214
|
+
},
|
|
33215
|
+
uFade: {
|
|
33216
|
+
value: 0,
|
|
33217
|
+
type: "f32"
|
|
33218
|
+
},
|
|
33219
|
+
uSize: {
|
|
33220
|
+
value: 1,
|
|
33221
|
+
type: "f32"
|
|
33222
|
+
},
|
|
33223
|
+
uSquare: {
|
|
33224
|
+
value: 0,
|
|
33225
|
+
type: "f32"
|
|
33226
|
+
},
|
|
33227
|
+
uLines: {
|
|
33228
|
+
value: 0,
|
|
33229
|
+
type: "f32"
|
|
33230
|
+
},
|
|
33231
|
+
uInk: {
|
|
33232
|
+
value: /* @__PURE__ */ new Float32Array(4),
|
|
33233
|
+
type: "vec4<f32>"
|
|
33234
|
+
}
|
|
33235
|
+
});
|
|
33236
|
+
const shader = Shader.from({
|
|
33237
|
+
gl: {
|
|
33238
|
+
name: "board-backdrop-pattern",
|
|
33239
|
+
vertex: VERTEX,
|
|
33240
|
+
fragment: FRAGMENT,
|
|
33241
|
+
preferredFragmentPrecision: "highp"
|
|
33242
|
+
},
|
|
33243
|
+
resources: { patternUniforms: uniforms }
|
|
33244
|
+
});
|
|
33245
|
+
const mesh = new Mesh({
|
|
33246
|
+
geometry,
|
|
33247
|
+
shader,
|
|
33248
|
+
label: "board-backdrop-pattern"
|
|
33249
|
+
});
|
|
33250
|
+
let width = 0;
|
|
33251
|
+
let height = 0;
|
|
33252
|
+
return {
|
|
33253
|
+
mesh,
|
|
33254
|
+
sync(pattern, view) {
|
|
33255
|
+
if (view.width !== width || view.height !== height) {
|
|
33256
|
+
width = view.width;
|
|
33257
|
+
height = view.height;
|
|
33258
|
+
positions.set([
|
|
33259
|
+
0,
|
|
33260
|
+
0,
|
|
33261
|
+
width,
|
|
33262
|
+
0,
|
|
33263
|
+
width,
|
|
33264
|
+
height,
|
|
33265
|
+
0,
|
|
33266
|
+
height
|
|
33267
|
+
]);
|
|
33268
|
+
geometry.getBuffer("aPosition").update();
|
|
33269
|
+
}
|
|
33270
|
+
const level = boardPatternLevel(pattern, view.zoom);
|
|
33271
|
+
const pitch = level.pitch * view.density;
|
|
33272
|
+
const values = uniforms.uniforms;
|
|
33273
|
+
values.uDensity = view.density;
|
|
33274
|
+
values.uOrigin[0] = wrap(view.x * view.density, pitch * 2);
|
|
33275
|
+
values.uOrigin[1] = wrap(view.y * view.density, pitch * 2);
|
|
33276
|
+
values.uPitch = pitch;
|
|
33277
|
+
values.uFade = level.fade;
|
|
33278
|
+
values.uSize = Math.max(1, Math.round(pattern.size * view.density));
|
|
33279
|
+
values.uSquare = pattern.shape === "square" ? 1 : 0;
|
|
33280
|
+
values.uLines = pattern.kind === "grid" ? 1 : 0;
|
|
33281
|
+
values.uInk[0] = (pattern.color >> 16 & 255) / 255;
|
|
33282
|
+
values.uInk[1] = (pattern.color >> 8 & 255) / 255;
|
|
33283
|
+
values.uInk[2] = (pattern.color & 255) / 255;
|
|
33284
|
+
values.uInk[3] = pattern.alpha;
|
|
33285
|
+
},
|
|
33286
|
+
destroy() {
|
|
33287
|
+
mesh.destroy();
|
|
33288
|
+
geometry.destroy();
|
|
33289
|
+
shader.destroy();
|
|
33290
|
+
}
|
|
33291
|
+
};
|
|
33292
|
+
}
|
|
33293
|
+
function placeImage(parts, image, texture, view, alpha) {
|
|
33294
|
+
if (image.fit === "repeat") {
|
|
33295
|
+
if (parts.picture) parts.picture.visible = false;
|
|
33296
|
+
parts.tiles ??= parts.container.addChildAt(new TilingSprite({
|
|
33297
|
+
texture,
|
|
33298
|
+
label: "board-backdrop-tiles"
|
|
33299
|
+
}), 0);
|
|
33300
|
+
const tiles = parts.tiles;
|
|
33301
|
+
const tileWidth = (image.tile?.width ?? texture.width) * view.zoom;
|
|
33302
|
+
const tileHeight = (image.tile?.height ?? texture.height) * view.zoom;
|
|
33303
|
+
tiles.texture = texture;
|
|
33304
|
+
tiles.visible = true;
|
|
33305
|
+
tiles.alpha = alpha;
|
|
33306
|
+
tiles.width = view.width;
|
|
33307
|
+
tiles.height = view.height;
|
|
33308
|
+
tiles.tileScale.set(tileWidth / texture.width, tileHeight / texture.height);
|
|
33309
|
+
tiles.tilePosition.set(wrap(view.x, tileWidth), wrap(view.y, tileHeight));
|
|
33310
|
+
return;
|
|
33311
|
+
}
|
|
33312
|
+
if (parts.tiles) parts.tiles.visible = false;
|
|
33313
|
+
parts.picture ??= parts.container.addChildAt(new Sprite({
|
|
33314
|
+
texture,
|
|
33315
|
+
label: "board-backdrop-picture"
|
|
33316
|
+
}), 0);
|
|
33317
|
+
const picture = parts.picture;
|
|
33318
|
+
const scale = (image.fit === "cover" ? Math.max : Math.min)(view.width / texture.width, view.height / texture.height);
|
|
33319
|
+
picture.texture = texture;
|
|
33320
|
+
picture.visible = true;
|
|
33321
|
+
picture.alpha = alpha;
|
|
33322
|
+
picture.width = texture.width * scale;
|
|
33323
|
+
picture.height = texture.height * scale;
|
|
33324
|
+
picture.position.set((view.width - picture.width) / 2, (view.height - picture.height) / 2);
|
|
33325
|
+
}
|
|
33326
|
+
function createBoardBackdropLayer(options) {
|
|
33327
|
+
const container = new Container({ label: "board-backdrop" });
|
|
33328
|
+
const parts = {
|
|
33329
|
+
tiles: null,
|
|
33330
|
+
picture: null,
|
|
33331
|
+
container
|
|
33332
|
+
};
|
|
33333
|
+
let pattern = null;
|
|
33334
|
+
return {
|
|
33335
|
+
container,
|
|
33336
|
+
sync(backdrop, view, texture, imageAlpha = 1) {
|
|
33337
|
+
if (backdrop.image && texture) placeImage(parts, backdrop.image, texture, view, backdrop.image.opacity * imageAlpha);
|
|
33338
|
+
else {
|
|
33339
|
+
if (parts.tiles) parts.tiles.visible = false;
|
|
33340
|
+
if (parts.picture) parts.picture.visible = false;
|
|
33341
|
+
}
|
|
33342
|
+
const shown = options.patterns && backdrop.pattern !== null;
|
|
33343
|
+
if (shown && backdrop.pattern) {
|
|
33344
|
+
if (!pattern) {
|
|
33345
|
+
pattern = createPatternMesh();
|
|
33346
|
+
container.addChild(pattern.mesh);
|
|
33347
|
+
}
|
|
33348
|
+
pattern.sync(backdrop.pattern, view);
|
|
33349
|
+
}
|
|
33350
|
+
if (pattern) pattern.mesh.visible = shown;
|
|
33351
|
+
},
|
|
33352
|
+
destroy() {
|
|
33353
|
+
pattern?.destroy();
|
|
33354
|
+
pattern = null;
|
|
33355
|
+
container.destroy({ children: true });
|
|
33356
|
+
}
|
|
33357
|
+
};
|
|
33358
|
+
}
|
|
33359
|
+
//#endregion
|
|
33092
33360
|
//#region ../sdk/src/board/render/text-fit.ts
|
|
33093
33361
|
function fitTextToLines(text, value, maxLines, wrapWidth) {
|
|
33094
33362
|
text.style.wordWrap = false;
|
|
@@ -36929,27 +37197,28 @@ const BOARD_ENTER_PRESETS = [
|
|
|
36929
37197
|
"deal"
|
|
36930
37198
|
];
|
|
36931
37199
|
const BoardBackgroundSchema = object({
|
|
36932
|
-
kind: _enum([
|
|
36933
|
-
"solid",
|
|
36934
|
-
"dots",
|
|
36935
|
-
"grid",
|
|
36936
|
-
"image"
|
|
36937
|
-
]).default("dots"),
|
|
36938
37200
|
color: BoardColorSchema.optional(),
|
|
36939
|
-
|
|
36940
|
-
|
|
36941
|
-
"
|
|
36942
|
-
"
|
|
36943
|
-
|
|
36944
|
-
|
|
36945
|
-
|
|
36946
|
-
|
|
37201
|
+
pattern: _enum([
|
|
37202
|
+
"none",
|
|
37203
|
+
"dots",
|
|
37204
|
+
"grid"
|
|
37205
|
+
]).optional().describe("Spaced by grid.size; omit to follow the theme."),
|
|
37206
|
+
image: object({
|
|
37207
|
+
src: BoardMediaSourceSchema,
|
|
37208
|
+
fit: _enum([
|
|
37209
|
+
"cover",
|
|
37210
|
+
"contain",
|
|
37211
|
+
"repeat"
|
|
37212
|
+
]).default("cover").describe("cover and contain fill the view; repeat tiles the Board at one board unit per image pixel."),
|
|
37213
|
+
opacity: ratio.optional()
|
|
37214
|
+
}).strict().optional()
|
|
37215
|
+
}).strict().describe("Layers drawn under every item: color, then image, then pattern. Without a color or image the theme's backdrop shows.");
|
|
36947
37216
|
object({
|
|
36948
37217
|
title: string().min(1).max(255).optional(),
|
|
36949
|
-
background: BoardBackgroundSchema.default({
|
|
37218
|
+
background: BoardBackgroundSchema.default({}),
|
|
36950
37219
|
grid: object({
|
|
36951
|
-
|
|
36952
|
-
|
|
37220
|
+
size: finite.min(4).max(512).default(24).describe("Pattern spacing and snap step, in board units."),
|
|
37221
|
+
snap: boolean().default(false).describe("Snap moved and resized items to the grid.")
|
|
36953
37222
|
}).strict().optional(),
|
|
36954
37223
|
enter: object({
|
|
36955
37224
|
preset: _enum(BOARD_ENTER_PRESETS),
|
|
@@ -37349,12 +37618,14 @@ function resolveBoardRenderStyle(mode, style) {
|
|
|
37349
37618
|
//#region ../sdk/src/board/render/theme.ts
|
|
37350
37619
|
/** A built-in Cohub theme as the app draws it; spread it into export or sampler options. */
|
|
37351
37620
|
function boardRenderTheme(id) {
|
|
37352
|
-
const
|
|
37621
|
+
const data = BOARD_THEME_DATA[id];
|
|
37622
|
+
const { colorScheme, palette, colors, style } = data;
|
|
37353
37623
|
return {
|
|
37354
37624
|
colorScheme,
|
|
37355
37625
|
palette: { ...palette },
|
|
37356
37626
|
colors: Object.fromEntries(Object.entries(colors).map(([key, value]) => [key, { ...value }])),
|
|
37357
|
-
style: resolveBoardRenderStyle(colorScheme, style)
|
|
37627
|
+
style: resolveBoardRenderStyle(colorScheme, style),
|
|
37628
|
+
..."backdrop" in data ? { backdrop: structuredClone(data.backdrop) } : {}
|
|
37358
37629
|
};
|
|
37359
37630
|
}
|
|
37360
37631
|
//#endregion
|
|
@@ -37405,31 +37676,23 @@ function createBoardExportScene(input) {
|
|
|
37405
37676
|
const root = new Container({ label: "board-export-root" });
|
|
37406
37677
|
const outputWidth = input.world.width * input.scale;
|
|
37407
37678
|
const outputHeight = input.world.height * input.scale;
|
|
37408
|
-
|
|
37409
|
-
|
|
37410
|
-
|
|
37411
|
-
|
|
37412
|
-
|
|
37413
|
-
|
|
37414
|
-
|
|
37415
|
-
|
|
37679
|
+
let backdrop = null;
|
|
37680
|
+
if (input.backdrop) {
|
|
37681
|
+
root.addChild(new Graphics().rect(0, 0, outputWidth, outputHeight).fill({
|
|
37682
|
+
color: input.backdrop.color,
|
|
37683
|
+
alpha: 1
|
|
37684
|
+
}));
|
|
37685
|
+
backdrop = createBoardBackdropLayer({ patterns: false });
|
|
37686
|
+
const view = {
|
|
37687
|
+
x: -input.world.x * input.scale,
|
|
37688
|
+
y: -input.world.y * input.scale,
|
|
37689
|
+
zoom: input.scale,
|
|
37416
37690
|
width: outputWidth,
|
|
37417
37691
|
height: outputHeight,
|
|
37418
|
-
|
|
37419
|
-
}
|
|
37420
|
-
|
|
37421
|
-
|
|
37422
|
-
texture,
|
|
37423
|
-
alpha: opacity
|
|
37424
|
-
});
|
|
37425
|
-
const scale = fit === "cover" ? Math.max(outputWidth / texture.width, outputHeight / texture.height) : Math.min(outputWidth / texture.width, outputHeight / texture.height);
|
|
37426
|
-
sprite.width = texture.width * scale;
|
|
37427
|
-
sprite.height = texture.height * scale;
|
|
37428
|
-
const x = position === "left" ? 0 : position === "right" ? outputWidth - sprite.width : (outputWidth - sprite.width) / 2;
|
|
37429
|
-
const y = position === "top" ? 0 : position === "bottom" ? outputHeight - sprite.height : (outputHeight - sprite.height) / 2;
|
|
37430
|
-
sprite.position.set(x, y);
|
|
37431
|
-
root.addChild(sprite);
|
|
37432
|
-
}
|
|
37692
|
+
density: 1
|
|
37693
|
+
};
|
|
37694
|
+
backdrop.sync(input.backdrop, view, input.backdropTexture ?? null);
|
|
37695
|
+
root.addChild(backdrop.container);
|
|
37433
37696
|
}
|
|
37434
37697
|
const world = new Container({
|
|
37435
37698
|
isRenderGroup: true,
|
|
@@ -37468,17 +37731,21 @@ function createBoardExportScene(input) {
|
|
|
37468
37731
|
return {
|
|
37469
37732
|
root,
|
|
37470
37733
|
missingImageKeys: [...missing],
|
|
37471
|
-
destroy: () =>
|
|
37734
|
+
destroy: () => {
|
|
37735
|
+
backdrop?.destroy();
|
|
37736
|
+
root.destroy({ children: true });
|
|
37737
|
+
}
|
|
37472
37738
|
};
|
|
37473
37739
|
}
|
|
37474
37740
|
//#endregion
|
|
37475
37741
|
//#region ../sdk/src/board/export/index.ts
|
|
37476
|
-
function
|
|
37477
|
-
if (background === "transparent") return null;
|
|
37478
|
-
|
|
37479
|
-
|
|
37480
|
-
|
|
37481
|
-
|
|
37742
|
+
function resolveBackdrop(options, document, theme) {
|
|
37743
|
+
if (options.background === "transparent") return null;
|
|
37744
|
+
const backdrop = resolveBoardBackdrop(document.board, theme, "export");
|
|
37745
|
+
return typeof options.background === "number" ? {
|
|
37746
|
+
...backdrop,
|
|
37747
|
+
color: options.background
|
|
37748
|
+
} : backdrop;
|
|
37482
37749
|
}
|
|
37483
37750
|
function prepareBoardExport(input, options = {}) {
|
|
37484
37751
|
ensureBoardTextMeasurement();
|
|
@@ -37545,8 +37812,12 @@ function renderBoardExport(renderer, input, options = {}) {
|
|
|
37545
37812
|
scripts: options.scripts,
|
|
37546
37813
|
assetKey: options.assetKey,
|
|
37547
37814
|
renderers: options.renderers,
|
|
37548
|
-
|
|
37549
|
-
|
|
37815
|
+
backdrop: resolveBackdrop(options, document, {
|
|
37816
|
+
palette,
|
|
37817
|
+
colors: options.colors ?? buildFallbackShapeColors(colorScheme),
|
|
37818
|
+
colorScheme
|
|
37819
|
+
}),
|
|
37820
|
+
backdropTexture: options.backgroundTexture
|
|
37550
37821
|
});
|
|
37551
37822
|
try {
|
|
37552
37823
|
const texture = renderer.textureGenerator.generateTexture({
|
|
@@ -38029,15 +38300,12 @@ const MIME = {
|
|
|
38029
38300
|
webp: "image/webp"
|
|
38030
38301
|
};
|
|
38031
38302
|
function exportBoardImageBytes(renderer, document, options = {}) {
|
|
38032
|
-
const { format = "png", quality = .92, textures, scripts,
|
|
38303
|
+
const { format = "png", quality = .92, textures, scripts, backgroundTexture, ...rest } = options;
|
|
38033
38304
|
const result = renderBoardExport(renderer.renderer, document, {
|
|
38034
38305
|
...rest,
|
|
38035
38306
|
...scripts ? { scripts } : {},
|
|
38036
38307
|
...textures ? { textures } : {},
|
|
38037
|
-
...
|
|
38038
|
-
...backgroundImage,
|
|
38039
|
-
texture: backgroundImage.texture
|
|
38040
|
-
} } : {}
|
|
38308
|
+
...backgroundTexture ? { backgroundTexture } : {}
|
|
38041
38309
|
});
|
|
38042
38310
|
if (!result) return null;
|
|
38043
38311
|
const canvas = result.canvas;
|
package/dist/board-media.js
CHANGED
|
@@ -1,37 +1,17 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { resolve } from "node:path";
|
|
3
|
-
import { promisify } from "node:util";
|
|
4
2
|
import { diffMergePatch, mergePatch, parseBoardItem, settleBoardMediaSizes, } from "@neta-art/cohub/board";
|
|
5
3
|
import { createClient } from "./client.js";
|
|
4
|
+
import { localMediaFacts } from "./local-media.js";
|
|
6
5
|
import { WORKSPACE_ROOT } from "./space-files/operands.js";
|
|
7
|
-
const run = promisify(execFile);
|
|
8
6
|
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9
|
-
const oriented = (width, height, turned) => width > 0 && height > 0 ? (turned ? { width: height, height: width } : { width, height }) : null;
|
|
10
|
-
async function localMediaSize(file, type, signal) {
|
|
11
|
-
try {
|
|
12
|
-
if (type === "image") {
|
|
13
|
-
const { default: sharp } = await import("sharp");
|
|
14
|
-
const { width = 0, height = 0, orientation = 1 } = await sharp(file).metadata();
|
|
15
|
-
return oriented(width, height, orientation >= 5);
|
|
16
|
-
}
|
|
17
|
-
const { stdout } = await run("ffprobe", [
|
|
18
|
-
"-v", "error", "-select_streams", "v:0",
|
|
19
|
-
"-show_entries", "stream=width,height:stream_side_data=rotation", "-of", "json", file,
|
|
20
|
-
], { signal, timeout: 5_000 });
|
|
21
|
-
const stream = JSON.parse(stdout).streams?.[0];
|
|
22
|
-
const rotation = stream?.side_data_list?.find((data) => data.rotation !== undefined)?.rotation ?? 0;
|
|
23
|
-
return oriented(stream?.width ?? 0, stream?.height ?? 0, Math.abs(rotation) % 180 === 90);
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
7
|
export function boardMediaFileResolver(spaceId) {
|
|
30
8
|
const local = process.env.COHUB_SPACE_ID === spaceId;
|
|
31
9
|
return async (path, type, signal) => {
|
|
32
10
|
const file = resolve(WORKSPACE_ROOT, path);
|
|
33
|
-
const
|
|
34
|
-
|
|
11
|
+
const facts = local && file.startsWith(`${WORKSPACE_ROOT}/`) ? await localMediaFacts(file, type, signal) : null;
|
|
12
|
+
const resolved = facts ? null : await createClient().space(spaceId).files.resolve(path, { purpose: "playback", signal }).catch(() => null);
|
|
13
|
+
const size = facts ?? resolved?.media;
|
|
14
|
+
return size ? { width: size.width, height: size.height } : (resolved?.url ?? null);
|
|
35
15
|
};
|
|
36
16
|
}
|
|
37
17
|
export async function settleBoardDocument(spaceId, document) {
|
package/dist/commands/boards.js
CHANGED
|
@@ -361,10 +361,12 @@ Times are milliseconds; 500ms, 12.5s and 2m also work on the command line.`);
|
|
|
361
361
|
.option("--animations <ids>", "Only these animations")
|
|
362
362
|
.option("--limit <n>", "Page size for items")
|
|
363
363
|
.option("--cursor <cursor>", "Continue from a previous page's next")
|
|
364
|
+
.option("--since <version>", "Only the changes after this version; the whole Board when they are unavailable")
|
|
364
365
|
.addHelpText("after", `
|
|
365
366
|
cohub boards get plan.board
|
|
366
367
|
cohub boards get plan.board --within s2
|
|
367
|
-
cohub boards get plan.board --only board
|
|
368
|
+
cohub boards get plan.board --only board
|
|
369
|
+
cohub boards get plan.board --since 12`)
|
|
368
370
|
.action(async (target, options) => {
|
|
369
371
|
try {
|
|
370
372
|
const only = list(options.only);
|
|
@@ -379,6 +381,7 @@ Times are milliseconds; 500ms, 12.5s and 2m also work on the command line.`);
|
|
|
379
381
|
...(list(options.animations) ? { animations: list(options.animations) } : {}),
|
|
380
382
|
...(options.limit ? { limit: parseNumber(options.limit, "--limit", { min: 1, integer: true }) } : {}),
|
|
381
383
|
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
384
|
+
...(options.since ? { since: parseNumber(options.since, "--since", { min: 0, integer: true }) } : {}),
|
|
382
385
|
};
|
|
383
386
|
const result = await (await boardClient(boards, target)).get(input);
|
|
384
387
|
if (!sections)
|
package/dist/commands/public.js
CHANGED
|
@@ -2,8 +2,10 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { lstat, readdir } from "node:fs/promises";
|
|
3
3
|
import { basename, relative, resolve } from "node:path";
|
|
4
4
|
import { isTextMime, mimeTypeForPath, } from "@neta-art/cohub";
|
|
5
|
+
import { mediaFacts } from "@neta-art/cohub/media";
|
|
5
6
|
import { createClient } from "../client.js";
|
|
6
7
|
import { HttpPutError, putLocalFile } from "../http-put.js";
|
|
8
|
+
import { localMediaFacts } from "../local-media.js";
|
|
7
9
|
import { error, handleHttp, json as outJson, jsonRequested } from "../output.js";
|
|
8
10
|
import { resolveSpace } from "../space.js";
|
|
9
11
|
const UPLOAD_CONCURRENCY = 4;
|
|
@@ -136,6 +138,20 @@ async function putPublicFile(file, plan, fetchImpl) {
|
|
|
136
138
|
throw error;
|
|
137
139
|
}
|
|
138
140
|
}
|
|
141
|
+
async function publicMediaFacts(files) {
|
|
142
|
+
const facts = new Map();
|
|
143
|
+
const media = files.flatMap((file) => {
|
|
144
|
+
const type = file.mimeType.split("/")[0];
|
|
145
|
+
return type === "image" || type === "video" ? [{ file, type: type }] : [];
|
|
146
|
+
});
|
|
147
|
+
await mapSettledWithConcurrency(media, UPLOAD_CONCURRENCY, async ({ file, type }) => {
|
|
148
|
+
// Facts only help previews; out-of-range ones are dropped rather than failing the upload.
|
|
149
|
+
const found = mediaFacts(await localMediaFacts(file.localPath, type));
|
|
150
|
+
if (found)
|
|
151
|
+
facts.set(file.id, found);
|
|
152
|
+
});
|
|
153
|
+
return facts;
|
|
154
|
+
}
|
|
139
155
|
function uploadFailure(errors) {
|
|
140
156
|
if (errors.length === 1)
|
|
141
157
|
return errors[0];
|
|
@@ -155,6 +171,7 @@ async function uploadPublic(command, source, destination, opts, deps) {
|
|
|
155
171
|
const spaceId = await resolveSpace(command, { home: true });
|
|
156
172
|
try {
|
|
157
173
|
const upload = await collectPublicUpload(source, destination);
|
|
174
|
+
const media = await publicMediaFacts(upload.files);
|
|
158
175
|
const plan = await client.space(spaceId).publicFiles.createUpload({
|
|
159
176
|
overwrite: Boolean(opts.overwrite),
|
|
160
177
|
entries: upload.files.map((file) => ({
|
|
@@ -162,6 +179,7 @@ async function uploadPublic(command, source, destination, opts, deps) {
|
|
|
162
179
|
relativePath: file.publicPath,
|
|
163
180
|
size: file.size,
|
|
164
181
|
mimeType: file.mimeType,
|
|
182
|
+
...(media.has(file.id) ? { media: media.get(file.id) } : {}),
|
|
165
183
|
})),
|
|
166
184
|
});
|
|
167
185
|
if (opts.overwrite && !jsonRequested(opts)) {
|
package/dist/commands/spaces.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const run = promisify(execFile);
|
|
4
|
+
const oriented = (width, height, turned) => (turned ? { width: height, height: width } : { width, height });
|
|
5
|
+
export async function localMediaFacts(file, type, signal) {
|
|
6
|
+
try {
|
|
7
|
+
if (type === "image") {
|
|
8
|
+
const { default: sharp } = await import("sharp");
|
|
9
|
+
const { width = 0, height = 0, orientation = 1 } = await sharp(file).metadata();
|
|
10
|
+
return width > 0 && height > 0 ? oriented(width, height, orientation >= 5) : null;
|
|
11
|
+
}
|
|
12
|
+
const { stdout } = await run("ffprobe", [
|
|
13
|
+
"-v", "error", "-select_streams", "v:0",
|
|
14
|
+
"-show_entries", "stream=width,height:stream_side_data=rotation:format=duration", "-of", "json", file,
|
|
15
|
+
], { signal, timeout: 5_000 });
|
|
16
|
+
const { streams, format } = JSON.parse(stdout);
|
|
17
|
+
const stream = streams?.[0];
|
|
18
|
+
if (!stream?.width || !stream.height)
|
|
19
|
+
return null;
|
|
20
|
+
const rotation = stream.side_data_list?.find((data) => data.rotation !== undefined)?.rotation ?? 0;
|
|
21
|
+
const durationMs = Math.round(Number(format?.duration) * 1000);
|
|
22
|
+
return {
|
|
23
|
+
...oriented(stream.width, stream.height, Math.abs(rotation) % 180 === 90),
|
|
24
|
+
...(durationMs > 0 ? { durationMs } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "10.12.
|
|
3
|
+
"version": "10.12.2",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"NOTICE"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@neta-art/cohub": "
|
|
21
|
+
"@neta-art/cohub": "13.0.0",
|
|
22
22
|
"@neta-art/generation": "^0.1.40",
|
|
23
23
|
"commander": "^15.0.0",
|
|
24
24
|
"sharp": "^0.35.4",
|