@neta-art/cohub 12.1.0 → 12.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/board/editor/editor.d.ts +1 -0
- package/dist/board/editor/editor.js +4 -0
- package/dist/board/editor/snapshots.js +5 -57
- package/dist/board/export/index.d.ts +2 -2
- package/dist/board/export/node/fonts.js +4 -0
- package/dist/board/export/node/index.d.ts +3 -0
- package/dist/board/export/node/index.js +13 -1
- package/dist/board/export/scene.d.ts +2 -2
- package/dist/board/export/scene.js +2 -2
- package/dist/board/model/asset-key.d.ts +16 -0
- package/dist/board/model/asset-key.js +92 -0
- package/dist/board/model/export-assets.d.ts +7 -3
- package/dist/board/model/export-assets.js +34 -13
- package/dist/board/model/index.d.ts +7 -5
- package/dist/board/model/index.js +9 -7
- package/dist/board/model/items/create.js +1 -1
- package/dist/board/model/media-sizes.d.ts +28 -0
- package/dist/board/model/media-sizes.js +64 -0
- package/dist/board/model/palette.d.ts +1 -0
- package/dist/board/model/palette.js +20 -130
- package/dist/board/model/text-metrics.d.ts +8 -3
- package/dist/board/model/text-metrics.js +13 -2
- package/dist/board/model/themes.d.ts +488 -0
- package/dist/board/model/themes.js +495 -0
- package/dist/board/render/card-tag.js +2 -2
- package/dist/board/render/index.d.ts +6 -4
- package/dist/board/render/index.js +5 -3
- package/dist/board/render/lod.d.ts +22 -0
- package/dist/board/render/lod.js +27 -0
- package/dist/board/render/media-interaction.js +1 -1
- package/dist/board/render/palette.js +2 -25
- package/dist/board/render/renderers/audio-card-renderer.js +2 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +10 -0
- package/dist/board/render/renderers/custom-card-renderer.js +5 -5
- package/dist/board/render/renderers/far-plate.d.ts +11 -1
- package/dist/board/render/renderers/far-plate.js +27 -8
- package/dist/board/render/renderers/file-card-renderer.js +3 -5
- package/dist/board/render/renderers/image-card-renderer.js +32 -6
- package/dist/board/render/renderers/model-card-renderer.js +1 -1
- package/dist/board/render/renderers/shape-card-renderer.js +25 -24
- package/dist/board/render/renderers/task-card-renderer.js +7 -8
- package/dist/board/render/renderers/text-card-renderer.js +86 -41
- package/dist/board/render/renderers/video-card-renderer.js +2 -1
- package/dist/board/render/style.d.ts +7 -2
- package/dist/board/render/style.js +5 -1
- package/dist/board/render/surface.d.ts +5 -0
- package/dist/board/render/surface.js +13 -3
- package/dist/board/render/text-fit.js +8 -12
- package/dist/board/render/text-measurement.d.ts +15 -1
- package/dist/board/render/text-measurement.js +113 -20
- package/dist/board/render/text-resolution.d.ts +1 -6
- package/dist/board/render/text-resolution.js +1 -10
- package/dist/board/render/theme.d.ts +15 -0
- package/dist/board/render/theme.js +15 -0
- package/dist/board/stage/assets.d.ts +15 -4
- package/dist/board/stage/assets.js +155 -89
- package/dist/board/stage/index.d.ts +5 -5
- package/dist/board/stage/index.js +4 -4
- package/dist/board/stage/lru.d.ts +13 -9
- package/dist/board/stage/lru.js +38 -20
- package/dist/board/stage/scene.d.ts +11 -1
- package/dist/board/stage/scene.js +213 -138
- package/dist/board/stage/stage.d.ts +12 -1
- package/dist/board/stage/stage.js +97 -24
- package/dist/board/stage/texture-pool.d.ts +8 -1
- package/dist/board/stage/texture-pool.js +37 -1
- package/dist/chunks/app-realtime.js +1 -1
- package/dist/chunks/{transport.js → http-error.js} +37 -1
- package/dist/chunks/http.js +30 -10
- package/dist/http.js +1 -1
- package/dist/index.js +139 -131
- package/dist/media.d.ts +3 -27
- package/dist/media.js +3 -82
- package/dist/protocol/dist/board-constants.js +3 -3
- package/dist/protocol/dist/board-layout.d.ts +17 -3
- package/dist/protocol/dist/board-layout.js +68 -11
- package/dist/protocol/dist/core/media.d.ts +35 -1
- package/dist/protocol/dist/core/media.js +123 -1
- package/dist/protocol/dist/index.d.ts +2 -2
- package/dist/websocket.js +951 -1
- package/package.json +3 -2
- package/dist/board/model/image-key.d.ts +0 -9
- package/dist/board/model/image-key.js +0 -33
- package/dist/chunks/websocket.js +0 -988
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { c as AppRtcError, i as WorkRoom, n as AppRoom, o as AppRoomRtc, r as WorkRealtimeApi, s as AppRtcChannel, t as AppRealtimeApi } from "./chunks/app-realtime.js";
|
|
2
2
|
import { $ as isTerminalDesktopCommandStatus, A as BoardClient, B as CronJobsApi, C as SessionPatchReducer, D as connectDisplay, E as DisplayConnection, F as PublicAssetsApi, G as DESKTOP_COMMAND_OPEN_TIMEOUT_MS, H as DESKTOP_COMMAND_ACCEPT_TIMEOUT_MS, I as SkillsApi, J as DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, K as DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, L as PromptsApi, M as SessionAccessApi, N as ReferencesApi, P as SearchApi, Q as isDesktopCallMethod, R as ModelsApi, S as parseAssistantMessageCommit, T as SpaceDisplaysApi, U as DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, V as ChannelsApi, W as DESKTOP_COMMAND_MAX_TIMEOUT_MS, X as DESKTOP_COMMAND_VERSION, Y as DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, Z as defaultDesktopCommandTimeoutMs, _ as PublicInviteApi, a as WorkCommerceApi, b as SessionGenerationStreamClient, c as AppsApi, ct as normalizeDisplayPoint, d as UserApi, et as parseDesktopCommand, f as TasksApi, g as SpacesApi, h as SpaceWebhooksApi, i as AppCommerceApi, it as DISPLAY_ELEMENT_REF_PATTERN, j as ensureRealtimeConnected, l as ReferralsApi, lt as splitDisplayText, m as SpacePublicFilesApi, n as createHttpClient, nt as resolveOpenSurface, o as DesktopCommandsApi, ot as DISPLAY_INPUT_MAX_TEXT, p as SpaceClient, q as DESKTOP_COMMAND_PENDING_TTL_SECONDS, r as RoomsApi, rt as NAVIGATION_ERROR_MESSAGE_MAX_LENGTH, s as UiCommandsApi, st as DISPLAY_SYSTEM_ACTIONS, t as CohubHttpClient, tt as parseDesktopCommandId, u as UsersApi, v as buildSpaceInvitePath, w as createSessionPatchReducer, x as createSessionGenerationStreamClient, y as buildSpacePath, z as GenerationsApi } from "./chunks/http.js";
|
|
3
|
-
import { A as
|
|
3
|
+
import { A as isRequestSourceClientId, B as isUuidLike, D as COHUB_SOURCE_HEADER_NAMES, E as COHUB_SOURCE_HEADER, F as parseRequestSourceFromHeaders, I as readRequestSourceFromEnv, L as requestSourceToHeaders, M as isRequestSourceUuid, N as mergeRequestSourceIntoMeta, O as REQUEST_SOURCE_VIA_MAX_LENGTH, P as normalizeRequestSource, R as resolveRequestSourceChannel, V as isUuidOrShortUuid, a as isBillingAccessBlockedError, c as HttpError, d as matchesUnauthorizedErrorToken, f as sanitizeAccessToken, i as isBillingAccessBlockedCode, j as isRequestSourceEmpty, k as hasRequestSourceIdentity, l as HttpTransport, n as FEATURE_NOT_ENTITLED_ERROR_CODE, o as isFeatureNotEntitledError, r as extractBillingPayload, s as isHttpErrorCode, t as BILLING_ACCESS_BLOCKED_ERROR_CODE, u as joinApiUrl, z as isUuid } from "./chunks/http-error.js";
|
|
4
4
|
import { a as normalizeWebsocketUrl, c as resolveExecutionAppId, d as resolveWebBaseUrl, f as resolveWebsocketUrl, g as scopeListHasPermission, h as isUserLevelPermission, i as normalizeVoiceInputWebsocketUrl, l as resolveExecutionToken, m as USER_LEVEL_PERMISSIONS, n as getCohubContext, o as resolveApiBaseUrl, p as PERMISSIONS, r as normalizeBaseUrl, s as resolveCohubEnvironment, t as COHUB_ENVIRONMENTS, u as resolveVoiceInputWebsocketUrl } from "./chunks/environment.js";
|
|
5
|
-
import {
|
|
5
|
+
import { WebsocketClient, createWebsocketClient } from "./websocket.js";
|
|
6
6
|
import { VoiceApi, VoiceInputClient, createVoiceInputClient } from "./voice-input.js";
|
|
7
7
|
import { filterSpacePickerItems, normalizeSpacePickerQuery, orderSpacePickerItems, selectSpacePickerItems } from "./space-picker.js";
|
|
8
8
|
import { z } from "zod";
|
|
@@ -865,6 +865,128 @@ function boardJsonEquals(a, b) {
|
|
|
865
865
|
return keys.length === Object.keys(bRecord).length && keys.every((key) => Object.hasOwn(bRecord, key) && boardJsonEquals(aRecord[key], bRecord[key]));
|
|
866
866
|
}
|
|
867
867
|
//#endregion
|
|
868
|
+
//#region ../protocol/dist/generation/blocks.js
|
|
869
|
+
const EXCERPT_LIMIT = 240;
|
|
870
|
+
function asRecord(value) {
|
|
871
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
872
|
+
}
|
|
873
|
+
function cleanExcerpt(value, limit = EXCERPT_LIMIT) {
|
|
874
|
+
if (typeof value !== "string") return void 0;
|
|
875
|
+
const clean = value.replace(/\s+/g, " ").trim();
|
|
876
|
+
if (!clean) return void 0;
|
|
877
|
+
return clean.length > limit ? `${clean.slice(0, limit - 3).trimEnd()}...` : clean;
|
|
878
|
+
}
|
|
879
|
+
function blockText(block) {
|
|
880
|
+
return cleanExcerpt(block.text ?? block.content ?? block.value);
|
|
881
|
+
}
|
|
882
|
+
function blockUrl(block) {
|
|
883
|
+
const source = asRecord(block.source);
|
|
884
|
+
return normalizeBoardRemoteUrl(source?.url ?? source?.src ?? block.url ?? block.src);
|
|
885
|
+
}
|
|
886
|
+
function blockMimeType(block) {
|
|
887
|
+
const source = asRecord(block.source);
|
|
888
|
+
const value = source?.mediaType ?? source?.media_type ?? source?.mimeType ?? block.mediaType ?? block.media_type ?? block.mimeType;
|
|
889
|
+
return typeof value === "string" ? value : void 0;
|
|
890
|
+
}
|
|
891
|
+
function positiveNumber(value) {
|
|
892
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
893
|
+
}
|
|
894
|
+
function blockNaturalSize(block) {
|
|
895
|
+
const source = asRecord(block.source);
|
|
896
|
+
const naturalWidth = positiveNumber(source?.width ?? source?.naturalWidth ?? block.width ?? block.naturalWidth);
|
|
897
|
+
const naturalHeight = positiveNumber(source?.height ?? source?.naturalHeight ?? block.height ?? block.naturalHeight);
|
|
898
|
+
return {
|
|
899
|
+
...naturalWidth ? { naturalWidth } : {},
|
|
900
|
+
...naturalHeight ? { naturalHeight } : {}
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
function contentBlocks(value) {
|
|
904
|
+
return Array.isArray(value) ? value.filter((item) => asRecord(item)) : [];
|
|
905
|
+
}
|
|
906
|
+
function taskData(run) {
|
|
907
|
+
const payload = asRecord(run.payload);
|
|
908
|
+
return asRecord(payload?.data) ?? payload;
|
|
909
|
+
}
|
|
910
|
+
function generationOutput(run) {
|
|
911
|
+
return contentBlocks(asRecord(run.result)?.output);
|
|
912
|
+
}
|
|
913
|
+
function generationPrompt(run) {
|
|
914
|
+
for (const block of contentBlocks(taskData(run)?.content)) {
|
|
915
|
+
const text = blockText(block);
|
|
916
|
+
if (text) return text;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function blockMeta(block) {
|
|
920
|
+
return asRecord(block.meta);
|
|
921
|
+
}
|
|
922
|
+
function blockIdentity(block) {
|
|
923
|
+
const meta = blockMeta(block);
|
|
924
|
+
const value = meta?.id ?? meta?.clip_id ?? meta?.clipId ?? block.id ?? block.clip_id ?? block.clipId;
|
|
925
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
926
|
+
return cleanExcerpt(String(value), 220);
|
|
927
|
+
}
|
|
928
|
+
function blockTitle(block) {
|
|
929
|
+
return cleanExcerpt(blockMeta(block)?.title ?? block.title ?? block.name, 240);
|
|
930
|
+
}
|
|
931
|
+
function blockDurationMs(block) {
|
|
932
|
+
const source = asRecord(block.source);
|
|
933
|
+
const meta = blockMeta(block);
|
|
934
|
+
const milliseconds = positiveNumber(source?.durationMs ?? meta?.durationMs ?? block.durationMs);
|
|
935
|
+
if (milliseconds) return Math.round(milliseconds);
|
|
936
|
+
const seconds = positiveNumber(source?.duration ?? meta?.duration ?? block.duration);
|
|
937
|
+
return seconds ? Math.round(seconds * 1e3) : void 0;
|
|
938
|
+
}
|
|
939
|
+
function blockPreviewUrl(block) {
|
|
940
|
+
const source = asRecord(block.source);
|
|
941
|
+
return normalizeBoardRemoteUrl(source?.poster ?? source?.thumbnail ?? source?.previewUrl ?? block.poster ?? block.thumbnail ?? block.previewUrl);
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Image roles that depict playable media, by the media types they may cover;
|
|
945
|
+
* `last_frame` stays a result of its own.
|
|
946
|
+
*/
|
|
947
|
+
const COVER_ROLES = /* @__PURE__ */ new Map([
|
|
948
|
+
["first_frame", /* @__PURE__ */ new Set(["video"])],
|
|
949
|
+
["cover", /* @__PURE__ */ new Set(["video", "audio"])],
|
|
950
|
+
["poster", /* @__PURE__ */ new Set(["video", "audio"])],
|
|
951
|
+
["thumbnail", /* @__PURE__ */ new Set(["video", "audio"])]
|
|
952
|
+
]);
|
|
953
|
+
function isPlayable(block) {
|
|
954
|
+
return block.type === "video" || block.type === "audio";
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Pair playable media with its cover image, returning media index → cover
|
|
958
|
+
* index. An image sharing the provider id wins (music tracks and their art,
|
|
959
|
+
* shared by every media of that id); otherwise cover-role images pair in
|
|
960
|
+
* order with remaining media their role fits (a video and its `first_frame`).
|
|
961
|
+
* Covers need a remote URL, so an inline image is never folded away.
|
|
962
|
+
*/
|
|
963
|
+
function generationCovers(blocks) {
|
|
964
|
+
const covers = /* @__PURE__ */ new Map();
|
|
965
|
+
const isCover = (block) => block.type === "image" && blockUrl(block) !== void 0 && blockMeta(block)?.role !== "last_frame";
|
|
966
|
+
blocks.forEach((block, index) => {
|
|
967
|
+
const id = blockIdentity(block);
|
|
968
|
+
if (!isPlayable(block) || id === void 0) return;
|
|
969
|
+
const cover = blocks.findIndex((candidate) => isCover(candidate) && blockIdentity(candidate) === id);
|
|
970
|
+
if (cover >= 0) covers.set(index, cover);
|
|
971
|
+
});
|
|
972
|
+
const paired = new Set(covers.values());
|
|
973
|
+
const roleCovers = blocks.flatMap((block, index) => {
|
|
974
|
+
const role = blockMeta(block)?.role;
|
|
975
|
+
const types = typeof role === "string" ? COVER_ROLES.get(role) : void 0;
|
|
976
|
+
return isCover(block) && !paired.has(index) && types ? [{
|
|
977
|
+
index,
|
|
978
|
+
types
|
|
979
|
+
}] : [];
|
|
980
|
+
});
|
|
981
|
+
blocks.forEach((block, index) => {
|
|
982
|
+
if (!isPlayable(block) || covers.has(index)) return;
|
|
983
|
+
const at = roleCovers.findIndex(({ types }) => types.has(String(block.type)));
|
|
984
|
+
const [cover] = at < 0 ? [] : roleCovers.splice(at, 1);
|
|
985
|
+
if (cover) covers.set(index, cover.index);
|
|
986
|
+
});
|
|
987
|
+
return covers;
|
|
988
|
+
}
|
|
989
|
+
//#endregion
|
|
868
990
|
//#region ../protocol/dist/board-layout.js
|
|
869
991
|
const IDENTITY_MATRIX = [
|
|
870
992
|
1,
|
|
@@ -927,8 +1049,14 @@ function estimateTextWidth(text, fontSize) {
|
|
|
927
1049
|
}
|
|
928
1050
|
return ems * fontSize;
|
|
929
1051
|
}
|
|
930
|
-
function
|
|
931
|
-
|
|
1052
|
+
function estimateLines(text, { fontSize }, maxWidth) {
|
|
1053
|
+
const measure = (line) => estimateTextWidth(line, fontSize);
|
|
1054
|
+
const paragraphs = text.split("\n");
|
|
1055
|
+
const lines = maxWidth ? paragraphs.flatMap((paragraph) => wrapParagraph(paragraph, maxWidth, measure)) : paragraphs;
|
|
1056
|
+
return {
|
|
1057
|
+
lines,
|
|
1058
|
+
width: lines.reduce((max, line) => Math.max(max, measure(line)), 0)
|
|
1059
|
+
};
|
|
932
1060
|
}
|
|
933
1061
|
function wrapParagraph(paragraph, width, measure) {
|
|
934
1062
|
if (!paragraph) return [""];
|
|
@@ -957,13 +1085,15 @@ function layoutBoardText(props) {
|
|
|
957
1085
|
const key = `${fontSize}|${props.fontWeight}|${props.font}|${props.lineHeight}|${props.width ?? ""}|${props.text}`;
|
|
958
1086
|
const cached = textSizeCache.get(key);
|
|
959
1087
|
if (cached) return cached;
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
1088
|
+
const font = {
|
|
1089
|
+
fontSize,
|
|
1090
|
+
fontWeight: props.fontWeight ?? 500,
|
|
1091
|
+
font: props.font ?? "sans"
|
|
1092
|
+
};
|
|
1093
|
+
const { lines, width } = estimateLines(props.text || "", font, props.width || void 0);
|
|
963
1094
|
const lineHeight = fontSize * (props.lineHeight ?? 4 / 3);
|
|
964
|
-
const widest = lines.reduce((max, line) => Math.max(max, measure(line)), 0);
|
|
965
1095
|
const layout = {
|
|
966
|
-
width: Math.max(props.width ?? 0,
|
|
1096
|
+
width: Math.max(props.width ?? 0, width, fontSize * .5),
|
|
967
1097
|
height: Math.max(1, lines.length) * lineHeight,
|
|
968
1098
|
lines,
|
|
969
1099
|
lineHeight
|
|
@@ -1918,128 +2048,6 @@ function parseBoardDocument(input) {
|
|
|
1918
2048
|
} : result;
|
|
1919
2049
|
}
|
|
1920
2050
|
//#endregion
|
|
1921
|
-
//#region ../protocol/dist/generation/blocks.js
|
|
1922
|
-
const EXCERPT_LIMIT = 240;
|
|
1923
|
-
function asRecord(value) {
|
|
1924
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1925
|
-
}
|
|
1926
|
-
function cleanExcerpt(value, limit = EXCERPT_LIMIT) {
|
|
1927
|
-
if (typeof value !== "string") return void 0;
|
|
1928
|
-
const clean = value.replace(/\s+/g, " ").trim();
|
|
1929
|
-
if (!clean) return void 0;
|
|
1930
|
-
return clean.length > limit ? `${clean.slice(0, limit - 3).trimEnd()}...` : clean;
|
|
1931
|
-
}
|
|
1932
|
-
function blockText(block) {
|
|
1933
|
-
return cleanExcerpt(block.text ?? block.content ?? block.value);
|
|
1934
|
-
}
|
|
1935
|
-
function blockUrl(block) {
|
|
1936
|
-
const source = asRecord(block.source);
|
|
1937
|
-
return normalizeBoardRemoteUrl(source?.url ?? source?.src ?? block.url ?? block.src);
|
|
1938
|
-
}
|
|
1939
|
-
function blockMimeType(block) {
|
|
1940
|
-
const source = asRecord(block.source);
|
|
1941
|
-
const value = source?.mediaType ?? source?.media_type ?? source?.mimeType ?? block.mediaType ?? block.media_type ?? block.mimeType;
|
|
1942
|
-
return typeof value === "string" ? value : void 0;
|
|
1943
|
-
}
|
|
1944
|
-
function positiveNumber(value) {
|
|
1945
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
1946
|
-
}
|
|
1947
|
-
function blockNaturalSize(block) {
|
|
1948
|
-
const source = asRecord(block.source);
|
|
1949
|
-
const naturalWidth = positiveNumber(source?.width ?? source?.naturalWidth ?? block.width ?? block.naturalWidth);
|
|
1950
|
-
const naturalHeight = positiveNumber(source?.height ?? source?.naturalHeight ?? block.height ?? block.naturalHeight);
|
|
1951
|
-
return {
|
|
1952
|
-
...naturalWidth ? { naturalWidth } : {},
|
|
1953
|
-
...naturalHeight ? { naturalHeight } : {}
|
|
1954
|
-
};
|
|
1955
|
-
}
|
|
1956
|
-
function contentBlocks(value) {
|
|
1957
|
-
return Array.isArray(value) ? value.filter((item) => asRecord(item)) : [];
|
|
1958
|
-
}
|
|
1959
|
-
function taskData(run) {
|
|
1960
|
-
const payload = asRecord(run.payload);
|
|
1961
|
-
return asRecord(payload?.data) ?? payload;
|
|
1962
|
-
}
|
|
1963
|
-
function generationOutput(run) {
|
|
1964
|
-
return contentBlocks(asRecord(run.result)?.output);
|
|
1965
|
-
}
|
|
1966
|
-
function generationPrompt(run) {
|
|
1967
|
-
for (const block of contentBlocks(taskData(run)?.content)) {
|
|
1968
|
-
const text = blockText(block);
|
|
1969
|
-
if (text) return text;
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
function blockMeta(block) {
|
|
1973
|
-
return asRecord(block.meta);
|
|
1974
|
-
}
|
|
1975
|
-
function blockIdentity(block) {
|
|
1976
|
-
const meta = blockMeta(block);
|
|
1977
|
-
const value = meta?.id ?? meta?.clip_id ?? meta?.clipId ?? block.id ?? block.clip_id ?? block.clipId;
|
|
1978
|
-
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
1979
|
-
return cleanExcerpt(String(value), 220);
|
|
1980
|
-
}
|
|
1981
|
-
function blockTitle(block) {
|
|
1982
|
-
return cleanExcerpt(blockMeta(block)?.title ?? block.title ?? block.name, 240);
|
|
1983
|
-
}
|
|
1984
|
-
function blockDurationMs(block) {
|
|
1985
|
-
const source = asRecord(block.source);
|
|
1986
|
-
const meta = blockMeta(block);
|
|
1987
|
-
const milliseconds = positiveNumber(source?.durationMs ?? meta?.durationMs ?? block.durationMs);
|
|
1988
|
-
if (milliseconds) return Math.round(milliseconds);
|
|
1989
|
-
const seconds = positiveNumber(source?.duration ?? meta?.duration ?? block.duration);
|
|
1990
|
-
return seconds ? Math.round(seconds * 1e3) : void 0;
|
|
1991
|
-
}
|
|
1992
|
-
function blockPreviewUrl(block) {
|
|
1993
|
-
const source = asRecord(block.source);
|
|
1994
|
-
return normalizeBoardRemoteUrl(source?.poster ?? source?.thumbnail ?? source?.previewUrl ?? block.poster ?? block.thumbnail ?? block.previewUrl);
|
|
1995
|
-
}
|
|
1996
|
-
/**
|
|
1997
|
-
* Image roles that depict playable media, by the media types they may cover;
|
|
1998
|
-
* `last_frame` stays a result of its own.
|
|
1999
|
-
*/
|
|
2000
|
-
const COVER_ROLES = /* @__PURE__ */ new Map([
|
|
2001
|
-
["first_frame", /* @__PURE__ */ new Set(["video"])],
|
|
2002
|
-
["cover", /* @__PURE__ */ new Set(["video", "audio"])],
|
|
2003
|
-
["poster", /* @__PURE__ */ new Set(["video", "audio"])],
|
|
2004
|
-
["thumbnail", /* @__PURE__ */ new Set(["video", "audio"])]
|
|
2005
|
-
]);
|
|
2006
|
-
function isPlayable(block) {
|
|
2007
|
-
return block.type === "video" || block.type === "audio";
|
|
2008
|
-
}
|
|
2009
|
-
/**
|
|
2010
|
-
* Pair playable media with its cover image, returning media index → cover
|
|
2011
|
-
* index. An image sharing the provider id wins (music tracks and their art,
|
|
2012
|
-
* shared by every media of that id); otherwise cover-role images pair in
|
|
2013
|
-
* order with remaining media their role fits (a video and its `first_frame`).
|
|
2014
|
-
* Covers need a remote URL, so an inline image is never folded away.
|
|
2015
|
-
*/
|
|
2016
|
-
function generationCovers(blocks) {
|
|
2017
|
-
const covers = /* @__PURE__ */ new Map();
|
|
2018
|
-
const isCover = (block) => block.type === "image" && blockUrl(block) !== void 0 && blockMeta(block)?.role !== "last_frame";
|
|
2019
|
-
blocks.forEach((block, index) => {
|
|
2020
|
-
const id = blockIdentity(block);
|
|
2021
|
-
if (!isPlayable(block) || id === void 0) return;
|
|
2022
|
-
const cover = blocks.findIndex((candidate) => isCover(candidate) && blockIdentity(candidate) === id);
|
|
2023
|
-
if (cover >= 0) covers.set(index, cover);
|
|
2024
|
-
});
|
|
2025
|
-
const paired = new Set(covers.values());
|
|
2026
|
-
const roleCovers = blocks.flatMap((block, index) => {
|
|
2027
|
-
const role = blockMeta(block)?.role;
|
|
2028
|
-
const types = typeof role === "string" ? COVER_ROLES.get(role) : void 0;
|
|
2029
|
-
return isCover(block) && !paired.has(index) && types ? [{
|
|
2030
|
-
index,
|
|
2031
|
-
types
|
|
2032
|
-
}] : [];
|
|
2033
|
-
});
|
|
2034
|
-
blocks.forEach((block, index) => {
|
|
2035
|
-
if (!isPlayable(block) || covers.has(index)) return;
|
|
2036
|
-
const at = roleCovers.findIndex(({ types }) => types.has(String(block.type)));
|
|
2037
|
-
const [cover] = at < 0 ? [] : roleCovers.splice(at, 1);
|
|
2038
|
-
if (cover) covers.set(index, cover.index);
|
|
2039
|
-
});
|
|
2040
|
-
return covers;
|
|
2041
|
-
}
|
|
2042
|
-
//#endregion
|
|
2043
2051
|
//#region ../protocol/dist/turn-origin.js
|
|
2044
2052
|
const record$1 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2045
2053
|
const normalizeSessionTurnOrigin = (value) => {
|
package/dist/media.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ossProcessUrl } from "./protocol/dist/core/media.js";
|
|
1
|
+
import { MediaInfo, mediaInfoFromHeaders, mergeMediaInfo, ossProcessUrl, probeMediaInfo, probeMediaSize, probeVideoFrameSize } from "./protocol/dist/core/media.js";
|
|
2
2
|
//#region src/media.d.ts
|
|
3
3
|
type MediaType = "image" | "video" | "audio";
|
|
4
4
|
/**
|
|
@@ -7,18 +7,9 @@ type MediaType = "image" | "video" | "audio";
|
|
|
7
7
|
* keep the original's rendered size.
|
|
8
8
|
*/
|
|
9
9
|
type MediaFit = "cover" | "contain" | "width";
|
|
10
|
-
type MediaInfo = {
|
|
11
|
-
width?: number;
|
|
12
|
-
height?: number;
|
|
13
|
-
durationMs?: number;
|
|
14
|
-
frameCount?: number;
|
|
15
|
-
firstFrameUrl?: string;
|
|
16
|
-
lastFrameUrl?: string;
|
|
17
|
-
mimeType?: string;
|
|
18
|
-
bytes?: number;
|
|
19
|
-
};
|
|
20
10
|
/** Fixed variant widths keep CDN cache hits high; pick with `mediaVariantSize`. */
|
|
21
11
|
declare const MEDIA_VARIANT_SIZES: readonly [192, 384, 768, 1536];
|
|
12
|
+
declare const MEDIA_VARIANT_MAX = 1536;
|
|
22
13
|
type MediaVariantSize = (typeof MEDIA_VARIANT_SIZES)[number];
|
|
23
14
|
/** Smallest variant covering `pixels` device pixels, capped at the largest. */
|
|
24
15
|
declare function mediaVariantSize(pixels: number): MediaVariantSize;
|
|
@@ -53,20 +44,5 @@ declare function mediaPreviewCandidates(media: MediaPreviewSource, options: {
|
|
|
53
44
|
size: MediaVariantSize;
|
|
54
45
|
fit?: MediaFit;
|
|
55
46
|
}, info?: Pick<MediaInfo, "firstFrameUrl">): string[];
|
|
56
|
-
/** Media facts from response headers; `x-oss-meta-*` need CORS exposure in browsers. */
|
|
57
|
-
declare function mediaInfoFromHeaders(headers: Headers, url: string): MediaInfo;
|
|
58
|
-
/** Fields of `primary` win; `fallback` fills the rest. */
|
|
59
|
-
declare function mergeMediaInfo(primary: MediaInfo, fallback: MediaInfo): MediaInfo;
|
|
60
|
-
type ProbeOptions = {
|
|
61
|
-
type: MediaType;
|
|
62
|
-
fetch?: typeof globalThis.fetch;
|
|
63
|
-
signal?: AbortSignal;
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Probe remote media: OSS meta headers (one HEAD, the only source of video
|
|
67
|
-
* duration and frames), then OSS `image/info` for image dimensions. Unknown
|
|
68
|
-
* hosts and failed steps resolve to what was learned so far; never throws.
|
|
69
|
-
*/
|
|
70
|
-
declare function probeMediaInfo(url: string, options: ProbeOptions): Promise<MediaInfo>;
|
|
71
47
|
//#endregion
|
|
72
|
-
export { MEDIA_VARIANT_SIZES, MediaFit, MediaInfo, MediaPreviewSource, MediaType, MediaVariantSize, imageVariantUrl, mediaInfoFromHeaders, mediaPreviewCandidates, mediaVariantSize, mergeMediaInfo, ossProcessUrl, probeMediaInfo, variantSource, videoFrameUrl };
|
|
48
|
+
export { MEDIA_VARIANT_MAX, MEDIA_VARIANT_SIZES, MediaFit, type MediaInfo, MediaPreviewSource, MediaType, MediaVariantSize, imageVariantUrl, mediaInfoFromHeaders, mediaPreviewCandidates, mediaVariantSize, mergeMediaInfo, ossProcessUrl, probeMediaInfo, probeMediaSize, probeVideoFrameSize, variantSource, videoFrameUrl };
|
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 };
|