@webiny/website-builder-sdk 6.6.0-alpha.0 → 6.6.0-alpha.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/ContentSdk.d.ts +13 -0
- package/ContentSdk.js +25 -0
- package/ContentSdk.js.map +1 -1
- package/ElementFactory.js +3 -1
- package/ElementFactory.js.map +1 -1
- package/Theme.js +2 -1
- package/Theme.js.map +1 -1
- package/asset/deliveryUrl.d.ts +41 -0
- package/asset/deliveryUrl.js +34 -0
- package/asset/deliveryUrl.js.map +1 -0
- package/asset/deliveryUrl.test.d.ts +1 -0
- package/asset/deliveryUrl.test.js +127 -0
- package/asset/deliveryUrl.test.js.map +1 -0
- package/asset/geometry.d.ts +7 -0
- package/asset/geometry.js +10 -0
- package/asset/geometry.js.map +1 -0
- package/asset/imageSrcSet.d.ts +63 -0
- package/asset/imageSrcSet.js +98 -0
- package/asset/imageSrcSet.js.map +1 -0
- package/asset/imageSrcSet.test.d.ts +1 -0
- package/asset/imageSrcSet.test.js +103 -0
- package/asset/imageSrcSet.test.js.map +1 -0
- package/asset/index.d.ts +5 -0
- package/asset/index.js +5 -0
- package/asset/normalize.d.ts +24 -0
- package/asset/normalize.js +102 -0
- package/asset/normalize.js.map +1 -0
- package/asset/normalize.test.d.ts +1 -0
- package/asset/normalize.test.js +256 -0
- package/asset/normalize.test.js.map +1 -0
- package/asset/types.d.ts +2 -0
- package/asset/types.js +0 -0
- package/createComponent.d.ts +5 -0
- package/createComponent.js +32 -0
- package/createComponent.js.map +1 -0
- package/dataProviders/ApiClient.d.ts +3 -1
- package/dataProviders/ApiClient.js +4 -2
- package/dataProviders/ApiClient.js.map +1 -1
- package/dataProviders/DefaultDataProvider.d.ts +4 -0
- package/dataProviders/DefaultDataProvider.js +35 -0
- package/dataProviders/DefaultDataProvider.js.map +1 -1
- package/dataProviders/GET_EXPERIMENT_PAUSED.d.ts +1 -0
- package/dataProviders/GET_EXPERIMENT_PAUSED.js +17 -0
- package/dataProviders/GET_EXPERIMENT_PAUSED.js.map +1 -0
- package/dataProviders/GET_PAGE_EXPERIMENT.d.ts +1 -0
- package/dataProviders/GET_PAGE_EXPERIMENT.js +32 -0
- package/dataProviders/GET_PAGE_EXPERIMENT.js.map +1 -0
- package/dataProviders/GET_VARIANT_CONTENT.d.ts +1 -0
- package/dataProviders/GET_VARIANT_CONTENT.js +24 -0
- package/dataProviders/GET_VARIANT_CONTENT.js.map +1 -0
- package/dataProviders/NullDataProvider.d.ts +4 -0
- package/dataProviders/NullDataProvider.js +9 -0
- package/dataProviders/NullDataProvider.js.map +1 -1
- package/experiments/PostHogAnalyticsProvider.d.ts +31 -0
- package/experiments/PostHogAnalyticsProvider.js +28 -0
- package/experiments/PostHogAnalyticsProvider.js.map +1 -0
- package/experiments/analyticsProvider.d.ts +4 -0
- package/experiments/analyticsProvider.js +11 -0
- package/experiments/analyticsProvider.js.map +1 -0
- package/experiments/bucketing.d.ts +27 -0
- package/experiments/bucketing.js +91 -0
- package/experiments/bucketing.js.map +1 -0
- package/experiments/bucketing.test.d.ts +1 -0
- package/experiments/bucketing.test.js +183 -0
- package/experiments/bucketing.test.js.map +1 -0
- package/experiments/index.d.ts +5 -0
- package/experiments/index.js +5 -0
- package/experiments/render.d.ts +79 -0
- package/experiments/render.js +149 -0
- package/experiments/render.js.map +1 -0
- package/experiments/types.d.ts +108 -0
- package/experiments/types.js +4 -0
- package/experiments/types.js.map +1 -0
- package/image/geometry.d.ts +54 -0
- package/image/geometry.js +112 -0
- package/image/geometry.js.map +1 -0
- package/image/geometry.test.d.ts +1 -0
- package/image/geometry.test.js +190 -0
- package/image/geometry.test.js.map +1 -0
- package/image/index.d.ts +3 -0
- package/image/index.js +3 -0
- package/image/presets.d.ts +6 -0
- package/image/presets.js +20 -0
- package/image/presets.js.map +1 -0
- package/image/types.d.ts +10 -0
- package/image/types.js +0 -0
- package/index.d.ts +5 -0
- package/index.js +5 -0
- package/package.json +6 -5
- package/types/WebsiteBuilderTheme.d.ts +5 -0
- package/types.d.ts +5 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { getAssetUrl } from "./deliveryUrl.js";
|
|
2
|
+
const IMAGE_RESIZE_WIDTHS = [
|
|
3
|
+
128,
|
|
4
|
+
384,
|
|
5
|
+
640,
|
|
6
|
+
750,
|
|
7
|
+
828,
|
|
8
|
+
1080,
|
|
9
|
+
1200,
|
|
10
|
+
1920,
|
|
11
|
+
2048,
|
|
12
|
+
3840
|
|
13
|
+
];
|
|
14
|
+
const parseAspectRatio = (input)=>{
|
|
15
|
+
if (void 0 === input) return;
|
|
16
|
+
if ("number" == typeof input) return input > 0 ? input : void 0;
|
|
17
|
+
const trimmed = input.trim();
|
|
18
|
+
if (trimmed.includes(":")) {
|
|
19
|
+
const [w, h] = trimmed.split(":").map(Number);
|
|
20
|
+
return w > 0 && h > 0 ? w / h : void 0;
|
|
21
|
+
}
|
|
22
|
+
const n = Number(trimmed);
|
|
23
|
+
return n > 0 ? n : void 0;
|
|
24
|
+
};
|
|
25
|
+
const toPx = (cssWidth)=>{
|
|
26
|
+
if (void 0 === cssWidth) return;
|
|
27
|
+
if ("number" == typeof cssWidth) return cssWidth > 0 ? cssWidth : void 0;
|
|
28
|
+
const trimmed = cssWidth.trim();
|
|
29
|
+
if (trimmed.endsWith("px")) {
|
|
30
|
+
const n = parseInt(trimmed, 10);
|
|
31
|
+
return n > 0 ? n : void 0;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const resolveWidths = (widths, cssWidth)=>{
|
|
35
|
+
const px = toPx(cssWidth);
|
|
36
|
+
if (!px) return [
|
|
37
|
+
...widths
|
|
38
|
+
];
|
|
39
|
+
const result = [];
|
|
40
|
+
for (const w of widths){
|
|
41
|
+
result.push(w);
|
|
42
|
+
if (w >= 2 * px) break;
|
|
43
|
+
}
|
|
44
|
+
return result.length ? result : [
|
|
45
|
+
widths[widths.length - 1]
|
|
46
|
+
];
|
|
47
|
+
};
|
|
48
|
+
const getImageDimensions = (asset, options = {})=>{
|
|
49
|
+
const img = asset?.image;
|
|
50
|
+
const iw = img?.width ?? 0;
|
|
51
|
+
const ih = img?.height ?? 0;
|
|
52
|
+
if (!iw || !ih) return {
|
|
53
|
+
width: 0,
|
|
54
|
+
height: 0
|
|
55
|
+
};
|
|
56
|
+
const crop = img?.crop;
|
|
57
|
+
const width = crop ? Math.round((1 - (crop.left ?? 0) - (crop.right ?? 0)) * iw) : iw;
|
|
58
|
+
const height = crop ? Math.round((1 - (crop.top ?? 0) - (crop.bottom ?? 0)) * ih) : ih;
|
|
59
|
+
const ar = parseAspectRatio(options.aspectRatio);
|
|
60
|
+
if (ar) return {
|
|
61
|
+
width,
|
|
62
|
+
height: Math.round(width / ar)
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
width,
|
|
66
|
+
height
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
const getImageSrcSet = (asset, options = {})=>{
|
|
70
|
+
const { widths = IMAGE_RESIZE_WIDTHS, cssWidth, ...urlOptions } = options;
|
|
71
|
+
const dims = getImageDimensions(asset, {
|
|
72
|
+
aspectRatio: urlOptions.aspectRatio
|
|
73
|
+
});
|
|
74
|
+
if (!asset?.src) return {
|
|
75
|
+
src: "",
|
|
76
|
+
srcSet: "",
|
|
77
|
+
width: dims.width,
|
|
78
|
+
height: dims.height
|
|
79
|
+
};
|
|
80
|
+
const selected = resolveWidths(widths, cssWidth);
|
|
81
|
+
const srcSet = selected.map((w)=>`${getAssetUrl(asset, {
|
|
82
|
+
...urlOptions,
|
|
83
|
+
width: w
|
|
84
|
+
})} ${w}w`).join(", ");
|
|
85
|
+
const src = getAssetUrl(asset, {
|
|
86
|
+
...urlOptions,
|
|
87
|
+
width: selected[selected.length - 1]
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
src,
|
|
91
|
+
srcSet,
|
|
92
|
+
width: dims.width,
|
|
93
|
+
height: dims.height
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
export { IMAGE_RESIZE_WIDTHS, getImageDimensions, getImageSrcSet };
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=imageSrcSet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset/imageSrcSet.js","sources":["../../src/asset/imageSrcSet.ts"],"sourcesContent":["/**\n * Framework-agnostic responsive-image helpers built on {@link getAssetUrl}.\n *\n * Every renderer (Next.js, React, Vue, Nuxt — and any future Angular / React Native\n * / vanilla adapter) needs the same three things from a `Asset`: a width\n * ladder, a `srcSet` with the per-usage crop/focal/format baked into each width, and\n * the delivered image's intrinsic (cropped) dimensions for layout-shift-free\n * rendering. Keeping that logic here — instead of re-deriving it per framework —\n * makes each renderer a thin adapter over the one delivery-URL contract.\n */\nimport type { Asset } from \"./types.js\";\nimport { getAssetUrl, type AssetUrlOptions } from \"./deliveryUrl.js\";\n\n/**\n * Canonical delivery-width ladder. Kept in lockstep with the server's resize ladder\n * (`imageResizeWidths` in `@webiny/api-file-manager`) so every width a renderer\n * requests snaps to a cached variant instead of forcing a fresh transform.\n */\nexport const IMAGE_RESIZE_WIDTHS = [128, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840] as const;\n\n/** Parse an aspect ratio given as a number, a `\"w:h\"` string, or a decimal string. */\nconst parseAspectRatio = (input: number | string | undefined): number | undefined => {\n if (input === undefined) {\n return undefined;\n }\n if (typeof input === \"number\") {\n return input > 0 ? input : undefined;\n }\n const trimmed = input.trim();\n if (trimmed.includes(\":\")) {\n const [w, h] = trimmed.split(\":\").map(Number);\n return w > 0 && h > 0 ? w / h : undefined;\n }\n const n = Number(trimmed);\n return n > 0 ? n : undefined;\n};\n\n/** Resolve a fixed CSS width to pixels, or `undefined` for fluid widths (`%`, `vw`, …). */\nconst toPx = (cssWidth: number | string | undefined): number | undefined => {\n if (cssWidth === undefined) {\n return undefined;\n }\n if (typeof cssWidth === \"number\") {\n return cssWidth > 0 ? cssWidth : undefined;\n }\n const trimmed = cssWidth.trim();\n if (trimmed.endsWith(\"px\")) {\n const n = parseInt(trimmed, 10);\n return n > 0 ? n : undefined;\n }\n return undefined;\n};\n\n/**\n * Trim the ladder to the smallest set that still covers a fixed CSS width, including\n * one step past `2×` for high-DPR screens. Fluid widths use the full ladder.\n */\nconst resolveWidths = (\n widths: readonly number[],\n cssWidth: number | string | undefined\n): number[] => {\n const px = toPx(cssWidth);\n if (!px) {\n return [...widths];\n }\n const result: number[] = [];\n for (const w of widths) {\n result.push(w);\n if (w >= px * 2) {\n break;\n }\n }\n return result.length ? result : [widths[widths.length - 1]];\n};\n\nexport interface ImageDimensions {\n /** Delivered (cropped) width in pixels, or `0` when the intrinsic size is unknown. */\n width: number;\n /** Delivered (cropped) height in pixels, or `0` when the intrinsic size is unknown. */\n height: number;\n}\n\n/**\n * The intrinsic dimensions of the *delivered* image — i.e. after the per-usage crop\n * (and an optional target aspect ratio) are applied. Renderers pass these to their\n * layout primitive (e.g. `next/image`'s `width`/`height`) so the box reserves the\n * right space and the page doesn't shift as the image loads. Returns `0`/`0` when the\n * asset's intrinsic size is unknown.\n */\nexport const getImageDimensions = (\n asset: Pick<Asset, \"image\"> | null | undefined,\n options: { aspectRatio?: number | string } = {}\n): ImageDimensions => {\n const img = asset?.image;\n const iw = img?.width ?? 0;\n const ih = img?.height ?? 0;\n if (!iw || !ih) {\n return { width: 0, height: 0 };\n }\n const crop = img?.crop;\n const width = crop ? Math.round((1 - (crop.left ?? 0) - (crop.right ?? 0)) * iw) : iw;\n const height = crop ? Math.round((1 - (crop.top ?? 0) - (crop.bottom ?? 0)) * ih) : ih;\n\n const ar = parseAspectRatio(options.aspectRatio);\n if (ar) {\n // Framed to a fixed ratio server-side: keep the cropped width, derive height.\n return { width, height: Math.round(width / ar) };\n }\n return { width, height };\n};\n\nexport interface ImageSrcSetOptions extends Omit<AssetUrlOptions, \"width\"> {\n /** Widths to emit. Defaults to {@link IMAGE_RESIZE_WIDTHS}, trimmed by `cssWidth`. */\n widths?: readonly number[];\n /**\n * A fixed CSS width for the rendered image (`\"320px\"` or `320`). When set, the\n * ladder is trimmed to the smallest set that still covers it (incl. high-DPR);\n * fluid widths (`%`, `vw`, unset) use the full ladder.\n */\n cssWidth?: number | string;\n}\n\nexport interface ImageSrcSet {\n /** Single-URL fallback for clients that ignore `srcSet` (the largest emitted width). */\n src: string;\n /** `\"<url> 128w, <url> 384w, …\"` — crop/focal/format baked into every width. */\n srcSet: string;\n /** Delivered (cropped) intrinsic dimensions; `0` when unknown. */\n width: number;\n height: number;\n}\n\n/**\n * Build the responsive descriptor for an asset: a `srcSet` across the width ladder\n * (each entry a delivery URL with the per-usage crop/focal/format baked in), a\n * single-URL `src` fallback, and the delivered dimensions. Framework-agnostic — hand\n * `src`/`srcSet` to any `<img>`-like primitive. Returns empty strings for a missing\n * asset.\n */\nexport const getImageSrcSet = (\n asset: (Pick<Asset, \"src\" | \"image\"> & { src?: string }) | null | undefined,\n options: ImageSrcSetOptions = {}\n): ImageSrcSet => {\n const { widths = IMAGE_RESIZE_WIDTHS, cssWidth, ...urlOptions } = options;\n const dims = getImageDimensions(asset, { aspectRatio: urlOptions.aspectRatio });\n\n if (!asset?.src) {\n return { src: \"\", srcSet: \"\", width: dims.width, height: dims.height };\n }\n\n const selected = resolveWidths(widths, cssWidth);\n const srcSet = selected\n .map(w => `${getAssetUrl(asset, { ...urlOptions, width: w })} ${w}w`)\n .join(\", \");\n const src = getAssetUrl(asset, { ...urlOptions, width: selected[selected.length - 1] });\n\n return { src, srcSet, width: dims.width, height: dims.height };\n};\n"],"names":["IMAGE_RESIZE_WIDTHS","parseAspectRatio","input","undefined","trimmed","w","h","Number","n","toPx","cssWidth","parseInt","resolveWidths","widths","px","result","getImageDimensions","asset","options","img","iw","ih","crop","width","Math","height","ar","getImageSrcSet","urlOptions","dims","selected","srcSet","getAssetUrl","src"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { IMAGE_RESIZE_WIDTHS, getImageDimensions, getImageSrcSet } from "./imageSrcSet.js";
|
|
3
|
+
const asset = (image)=>({
|
|
4
|
+
id: "f",
|
|
5
|
+
src: "https://cdn/x/pic.jpg",
|
|
6
|
+
name: "pic.jpg",
|
|
7
|
+
type: "image/jpeg",
|
|
8
|
+
size: 1,
|
|
9
|
+
image: {
|
|
10
|
+
width: 800,
|
|
11
|
+
height: 600,
|
|
12
|
+
...image
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
describe("getImageDimensions", ()=>{
|
|
16
|
+
it("returns intrinsic dimensions when there is no crop", ()=>{
|
|
17
|
+
expect(getImageDimensions(asset())).toEqual({
|
|
18
|
+
width: 800,
|
|
19
|
+
height: 600
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
it("shrinks by the crop insets", ()=>{
|
|
23
|
+
expect(getImageDimensions(asset({
|
|
24
|
+
crop: {
|
|
25
|
+
top: 0.1,
|
|
26
|
+
left: 0.25,
|
|
27
|
+
bottom: 0.1,
|
|
28
|
+
right: 0.25
|
|
29
|
+
}
|
|
30
|
+
}))).toEqual({
|
|
31
|
+
width: 400,
|
|
32
|
+
height: 480
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
it("derives height from a target aspect ratio", ()=>{
|
|
36
|
+
expect(getImageDimensions(asset({
|
|
37
|
+
crop: {
|
|
38
|
+
top: 0,
|
|
39
|
+
left: 0.25,
|
|
40
|
+
bottom: 0,
|
|
41
|
+
right: 0.25
|
|
42
|
+
}
|
|
43
|
+
}), {
|
|
44
|
+
aspectRatio: "16:9"
|
|
45
|
+
})).toEqual({
|
|
46
|
+
width: 400,
|
|
47
|
+
height: 225
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
it("returns 0/0 when the intrinsic size is unknown", ()=>{
|
|
51
|
+
expect(getImageDimensions({
|
|
52
|
+
image: {}
|
|
53
|
+
})).toEqual({
|
|
54
|
+
width: 0,
|
|
55
|
+
height: 0
|
|
56
|
+
});
|
|
57
|
+
expect(getImageDimensions(null)).toEqual({
|
|
58
|
+
width: 0,
|
|
59
|
+
height: 0
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("getImageSrcSet", ()=>{
|
|
64
|
+
it("emits the full ladder with crop + format baked into every width", ()=>{
|
|
65
|
+
const { srcSet, width, height } = getImageSrcSet(asset({
|
|
66
|
+
crop: {
|
|
67
|
+
top: 0.1,
|
|
68
|
+
left: 0,
|
|
69
|
+
bottom: 0,
|
|
70
|
+
right: 0
|
|
71
|
+
}
|
|
72
|
+
}), {
|
|
73
|
+
format: "auto"
|
|
74
|
+
});
|
|
75
|
+
const entries = srcSet.split(", ");
|
|
76
|
+
expect(entries).toHaveLength(IMAGE_RESIZE_WIDTHS.length);
|
|
77
|
+
expect(entries[0]).toBe("https://cdn/x/pic.jpg?crop=0.1,0,0,0&width=128&format=auto 128w");
|
|
78
|
+
expect(width).toBe(800);
|
|
79
|
+
expect(height).toBe(540);
|
|
80
|
+
});
|
|
81
|
+
it("trims the ladder to a fixed CSS width (incl. 2x DPR)", ()=>{
|
|
82
|
+
const { srcSet, src } = getImageSrcSet(asset(), {
|
|
83
|
+
cssWidth: "300px"
|
|
84
|
+
});
|
|
85
|
+
const widths = srcSet.split(", ").map((e)=>Number(e.match(/width=(\d+)/)[1]));
|
|
86
|
+
expect(widths).toEqual([
|
|
87
|
+
128,
|
|
88
|
+
384,
|
|
89
|
+
640
|
|
90
|
+
]);
|
|
91
|
+
expect(src).toBe("https://cdn/x/pic.jpg?width=640");
|
|
92
|
+
});
|
|
93
|
+
it("returns empty strings for a missing asset", ()=>{
|
|
94
|
+
expect(getImageSrcSet(null)).toEqual({
|
|
95
|
+
src: "",
|
|
96
|
+
srcSet: "",
|
|
97
|
+
width: 0,
|
|
98
|
+
height: 0
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=imageSrcSet.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset/imageSrcSet.test.js","sources":["../../src/asset/imageSrcSet.test.ts"],"sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport { IMAGE_RESIZE_WIDTHS, getImageDimensions, getImageSrcSet } from \"./imageSrcSet.js\";\n\nconst asset = (image?: any) => ({\n id: \"f\",\n src: \"https://cdn/x/pic.jpg\",\n name: \"pic.jpg\",\n type: \"image/jpeg\",\n size: 1,\n image: { width: 800, height: 600, ...image }\n});\n\ndescribe(\"getImageDimensions\", () => {\n it(\"returns intrinsic dimensions when there is no crop\", () => {\n expect(getImageDimensions(asset())).toEqual({ width: 800, height: 600 });\n });\n\n it(\"shrinks by the crop insets\", () => {\n expect(\n getImageDimensions(asset({ crop: { top: 0.1, left: 0.25, bottom: 0.1, right: 0.25 } }))\n ).toEqual({ width: 400, height: 480 });\n });\n\n it(\"derives height from a target aspect ratio\", () => {\n // Cropped width 400 @ 16:9 -> height 225.\n expect(\n getImageDimensions(asset({ crop: { top: 0, left: 0.25, bottom: 0, right: 0.25 } }), {\n aspectRatio: \"16:9\"\n })\n ).toEqual({ width: 400, height: 225 });\n });\n\n it(\"returns 0/0 when the intrinsic size is unknown\", () => {\n expect(getImageDimensions({ image: {} })).toEqual({ width: 0, height: 0 });\n expect(getImageDimensions(null)).toEqual({ width: 0, height: 0 });\n });\n});\n\ndescribe(\"getImageSrcSet\", () => {\n it(\"emits the full ladder with crop + format baked into every width\", () => {\n const { srcSet, width, height } = getImageSrcSet(\n asset({ crop: { top: 0.1, left: 0, bottom: 0, right: 0 } }),\n { format: \"auto\" }\n );\n const entries = srcSet.split(\", \");\n expect(entries).toHaveLength(IMAGE_RESIZE_WIDTHS.length);\n expect(entries[0]).toBe(\"https://cdn/x/pic.jpg?crop=0.1,0,0,0&width=128&format=auto 128w\");\n expect(width).toBe(800);\n expect(height).toBe(540);\n });\n\n it(\"trims the ladder to a fixed CSS width (incl. 2x DPR)\", () => {\n const { srcSet, src } = getImageSrcSet(asset(), { cssWidth: \"300px\" });\n const widths = srcSet.split(\", \").map(e => Number(e.match(/width=(\\d+)/)![1]));\n // Smallest set covering 300px up to 2x (600) -> 128, 384, 640.\n expect(widths).toEqual([128, 384, 640]);\n // `src` fallback is the largest emitted width.\n expect(src).toBe(\"https://cdn/x/pic.jpg?width=640\");\n });\n\n it(\"returns empty strings for a missing asset\", () => {\n expect(getImageSrcSet(null)).toEqual({ src: \"\", srcSet: \"\", width: 0, height: 0 });\n });\n});\n"],"names":["asset","image","describe","it","expect","getImageDimensions","srcSet","width","height","getImageSrcSet","entries","IMAGE_RESIZE_WIDTHS","src","widths","e","Number"],"mappings":";;AAGA,MAAMA,QAAQ,CAACC,QAAiB;QAC5B,IAAI;QACJ,KAAK;QACL,MAAM;QACN,MAAM;QACN,MAAM;QACN,OAAO;YAAE,OAAO;YAAK,QAAQ;YAAK,GAAGA,KAAK;QAAC;IAC/C;AAEAC,SAAS,sBAAsB;IAC3BC,GAAG,sDAAsD;QACrDC,OAAOC,mBAAmBL,UAAU,OAAO,CAAC;YAAE,OAAO;YAAK,QAAQ;QAAI;IAC1E;IAEAG,GAAG,8BAA8B;QAC7BC,OACIC,mBAAmBL,MAAM;YAAE,MAAM;gBAAE,KAAK;gBAAK,MAAM;gBAAM,QAAQ;gBAAK,OAAO;YAAK;QAAE,KACtF,OAAO,CAAC;YAAE,OAAO;YAAK,QAAQ;QAAI;IACxC;IAEAG,GAAG,6CAA6C;QAE5CC,OACIC,mBAAmBL,MAAM;YAAE,MAAM;gBAAE,KAAK;gBAAG,MAAM;gBAAM,QAAQ;gBAAG,OAAO;YAAK;QAAE,IAAI;YAChF,aAAa;QACjB,IACF,OAAO,CAAC;YAAE,OAAO;YAAK,QAAQ;QAAI;IACxC;IAEAG,GAAG,kDAAkD;QACjDC,OAAOC,mBAAmB;YAAE,OAAO,CAAC;QAAE,IAAI,OAAO,CAAC;YAAE,OAAO;YAAG,QAAQ;QAAE;QACxED,OAAOC,mBAAmB,OAAO,OAAO,CAAC;YAAE,OAAO;YAAG,QAAQ;QAAE;IACnE;AACJ;AAEAH,SAAS,kBAAkB;IACvBC,GAAG,mEAAmE;QAClE,MAAM,EAAEG,MAAM,EAAEC,KAAK,EAAEC,MAAM,EAAE,GAAGC,eAC9BT,MAAM;YAAE,MAAM;gBAAE,KAAK;gBAAK,MAAM;gBAAG,QAAQ;gBAAG,OAAO;YAAE;QAAE,IACzD;YAAE,QAAQ;QAAO;QAErB,MAAMU,UAAUJ,OAAO,KAAK,CAAC;QAC7BF,OAAOM,SAAS,YAAY,CAACC,oBAAoB,MAAM;QACvDP,OAAOM,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC;QACxBN,OAAOG,OAAO,IAAI,CAAC;QACnBH,OAAOI,QAAQ,IAAI,CAAC;IACxB;IAEAL,GAAG,wDAAwD;QACvD,MAAM,EAAEG,MAAM,EAAEM,GAAG,EAAE,GAAGH,eAAeT,SAAS;YAAE,UAAU;QAAQ;QACpE,MAAMa,SAASP,OAAO,KAAK,CAAC,MAAM,GAAG,CAACQ,CAAAA,IAAKC,OAAOD,EAAE,KAAK,CAAC,cAAe,CAAC,EAAE;QAE5EV,OAAOS,QAAQ,OAAO,CAAC;YAAC;YAAK;YAAK;SAAI;QAEtCT,OAAOQ,KAAK,IAAI,CAAC;IACrB;IAEAT,GAAG,6CAA6C;QAC5CC,OAAOK,eAAe,OAAO,OAAO,CAAC;YAAE,KAAK;YAAI,QAAQ;YAAI,OAAO;YAAG,QAAQ;QAAE;IACpF;AACJ"}
|
package/asset/index.d.ts
ADDED
package/asset/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Asset, AssetCategory, AssetImage } from "./types.js";
|
|
2
|
+
export declare function getAssetCategory(type?: string | null): AssetCategory;
|
|
3
|
+
interface LegacyImageEdit {
|
|
4
|
+
crop?: {
|
|
5
|
+
top: number;
|
|
6
|
+
left: number;
|
|
7
|
+
bottom: number;
|
|
8
|
+
right: number;
|
|
9
|
+
};
|
|
10
|
+
hotspot?: {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
};
|
|
16
|
+
alt?: string;
|
|
17
|
+
caption?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function assetImageFromLegacyEdit(edit?: LegacyImageEdit | null, dimensions?: {
|
|
20
|
+
width?: number | null;
|
|
21
|
+
height?: number | null;
|
|
22
|
+
}): AssetImage | undefined;
|
|
23
|
+
export declare function normalizeToAsset(input: unknown): Asset | null;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
function getAssetCategory(type) {
|
|
2
|
+
if ("string" == typeof type) {
|
|
3
|
+
if (type.startsWith("image/")) return "image";
|
|
4
|
+
if (type.startsWith("video/")) return "video";
|
|
5
|
+
}
|
|
6
|
+
return "document";
|
|
7
|
+
}
|
|
8
|
+
const isObject = (value)=>"object" == typeof value && null !== value;
|
|
9
|
+
const asNumber = (value)=>"number" != typeof value || Number.isNaN(value) ? void 0 : value;
|
|
10
|
+
const asString = (value)=>"string" == typeof value ? value : void 0;
|
|
11
|
+
function assetImageFromLegacyEdit(edit, dimensions) {
|
|
12
|
+
const image = {};
|
|
13
|
+
const width = asNumber(dimensions?.width);
|
|
14
|
+
const height = asNumber(dimensions?.height);
|
|
15
|
+
if (void 0 !== width) image.width = width;
|
|
16
|
+
if (void 0 !== height) image.height = height;
|
|
17
|
+
if (edit?.crop) image.crop = {
|
|
18
|
+
top: edit.crop.top ?? 0,
|
|
19
|
+
left: edit.crop.left ?? 0,
|
|
20
|
+
bottom: edit.crop.bottom ?? 0,
|
|
21
|
+
right: edit.crop.right ?? 0
|
|
22
|
+
};
|
|
23
|
+
if (edit?.hotspot) image.focalPoint = {
|
|
24
|
+
x: edit.hotspot.x,
|
|
25
|
+
y: edit.hotspot.y
|
|
26
|
+
};
|
|
27
|
+
if (edit?.alt) image.alt = edit.alt;
|
|
28
|
+
if (edit?.caption) image.caption = edit.caption;
|
|
29
|
+
return Object.keys(image).length > 0 ? image : void 0;
|
|
30
|
+
}
|
|
31
|
+
const normalizeAssetImage = (raw)=>{
|
|
32
|
+
if (!isObject(raw)) return;
|
|
33
|
+
const image = {};
|
|
34
|
+
const width = asNumber(raw.width);
|
|
35
|
+
const height = asNumber(raw.height);
|
|
36
|
+
if (void 0 !== width) image.width = width;
|
|
37
|
+
if (void 0 !== height) image.height = height;
|
|
38
|
+
if (isObject(raw.crop)) image.crop = {
|
|
39
|
+
top: asNumber(raw.crop.top) ?? 0,
|
|
40
|
+
left: asNumber(raw.crop.left) ?? 0,
|
|
41
|
+
bottom: asNumber(raw.crop.bottom) ?? 0,
|
|
42
|
+
right: asNumber(raw.crop.right) ?? 0
|
|
43
|
+
};
|
|
44
|
+
if (isObject(raw.focalPoint)) {
|
|
45
|
+
const x = asNumber(raw.focalPoint.x);
|
|
46
|
+
const y = asNumber(raw.focalPoint.y);
|
|
47
|
+
if (void 0 !== x && void 0 !== y) image.focalPoint = {
|
|
48
|
+
x,
|
|
49
|
+
y
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const alt = asString(raw.alt);
|
|
53
|
+
if (alt) image.alt = alt;
|
|
54
|
+
const caption = asString(raw.caption);
|
|
55
|
+
if (caption) image.caption = caption;
|
|
56
|
+
return Object.keys(image).length > 0 ? image : void 0;
|
|
57
|
+
};
|
|
58
|
+
const buildBase = (raw, type)=>{
|
|
59
|
+
const src = asString(raw.src) ?? "";
|
|
60
|
+
return {
|
|
61
|
+
id: asString(raw.id) ?? "",
|
|
62
|
+
src,
|
|
63
|
+
url: asString(raw.url) ?? src,
|
|
64
|
+
name: asString(raw.name) ?? "",
|
|
65
|
+
type,
|
|
66
|
+
size: asNumber(raw.size) ?? 0
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
function normalizeToAsset(input) {
|
|
70
|
+
if (!isObject(input)) return null;
|
|
71
|
+
const hasTypedSubObject = isObject(input.image) || isObject(input.document) || isObject(input.video);
|
|
72
|
+
if (!hasTypedSubObject) {
|
|
73
|
+
const type = asString(input.mimeType) ?? asString(input.type) ?? "";
|
|
74
|
+
const asset = buildBase(input, type);
|
|
75
|
+
if ("image" === getAssetCategory(type)) {
|
|
76
|
+
const image = assetImageFromLegacyEdit(input.edit, {
|
|
77
|
+
width: asNumber(input.width),
|
|
78
|
+
height: asNumber(input.height)
|
|
79
|
+
});
|
|
80
|
+
if (image) asset.image = image;
|
|
81
|
+
}
|
|
82
|
+
return asset;
|
|
83
|
+
}
|
|
84
|
+
const type = asString(input.type) ?? asString(input.mimeType) ?? "";
|
|
85
|
+
const asset = buildBase(input, type);
|
|
86
|
+
const category = getAssetCategory(type);
|
|
87
|
+
if ("image" === category) {
|
|
88
|
+
const image = normalizeAssetImage(input.image);
|
|
89
|
+
if (image) asset.image = image;
|
|
90
|
+
} else if ("video" === category) {
|
|
91
|
+
if (isObject(input.video)) asset.video = {
|
|
92
|
+
autoplay: "boolean" == typeof input.video.autoplay ? input.video.autoplay : void 0,
|
|
93
|
+
poster: asString(input.video.poster)
|
|
94
|
+
};
|
|
95
|
+
} else if (isObject(input.document)) asset.document = {
|
|
96
|
+
pages: asNumber(input.document.pages)
|
|
97
|
+
};
|
|
98
|
+
return asset;
|
|
99
|
+
}
|
|
100
|
+
export { assetImageFromLegacyEdit, getAssetCategory, normalizeToAsset };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset/normalize.js","sources":["../../src/asset/normalize.ts"],"sourcesContent":["import type { Asset, AssetCategory, AssetImage } from \"./types.js\";\n\nexport function getAssetCategory(type?: string | null): AssetCategory {\n if (typeof type === \"string\") {\n if (type.startsWith(\"image/\")) {\n return \"image\";\n }\n if (type.startsWith(\"video/\")) {\n return \"video\";\n }\n }\n return \"document\";\n}\n\nconst isObject = (value: unknown): value is Record<string, unknown> => {\n return typeof value === \"object\" && value !== null;\n};\n\nconst asNumber = (value: unknown): number | undefined => {\n return typeof value === \"number\" && !Number.isNaN(value) ? value : undefined;\n};\n\nconst asString = (value: unknown): string | undefined => {\n return typeof value === \"string\" ? value : undefined;\n};\n\ninterface LegacyImageEdit {\n crop?: { top: number; left: number; bottom: number; right: number };\n hotspot?: { x: number; y: number; width?: number; height?: number };\n alt?: string;\n caption?: string;\n}\n\nexport function assetImageFromLegacyEdit(\n edit?: LegacyImageEdit | null,\n dimensions?: { width?: number | null; height?: number | null }\n): AssetImage | undefined {\n const image: AssetImage = {};\n\n const width = asNumber(dimensions?.width);\n const height = asNumber(dimensions?.height);\n if (width !== undefined) {\n image.width = width;\n }\n if (height !== undefined) {\n image.height = height;\n }\n\n if (edit?.crop) {\n image.crop = {\n top: edit.crop.top ?? 0,\n left: edit.crop.left ?? 0,\n bottom: edit.crop.bottom ?? 0,\n right: edit.crop.right ?? 0\n };\n }\n if (edit?.hotspot) {\n image.focalPoint = { x: edit.hotspot.x, y: edit.hotspot.y };\n }\n if (edit?.alt) {\n image.alt = edit.alt;\n }\n if (edit?.caption) {\n image.caption = edit.caption;\n }\n\n return Object.keys(image).length > 0 ? image : undefined;\n}\n\nconst normalizeAssetImage = (raw: unknown): AssetImage | undefined => {\n if (!isObject(raw)) {\n return undefined;\n }\n const image: AssetImage = {};\n const width = asNumber(raw.width);\n const height = asNumber(raw.height);\n if (width !== undefined) {\n image.width = width;\n }\n if (height !== undefined) {\n image.height = height;\n }\n if (isObject(raw.crop)) {\n image.crop = {\n top: asNumber(raw.crop.top) ?? 0,\n left: asNumber(raw.crop.left) ?? 0,\n bottom: asNumber(raw.crop.bottom) ?? 0,\n right: asNumber(raw.crop.right) ?? 0\n };\n }\n if (isObject(raw.focalPoint)) {\n const x = asNumber(raw.focalPoint.x);\n const y = asNumber(raw.focalPoint.y);\n if (x !== undefined && y !== undefined) {\n image.focalPoint = { x, y };\n }\n }\n const alt = asString(raw.alt);\n if (alt) {\n image.alt = alt;\n }\n const caption = asString(raw.caption);\n if (caption) {\n image.caption = caption;\n }\n return Object.keys(image).length > 0 ? image : undefined;\n};\n\nconst buildBase = (raw: Record<string, unknown>, type: string): Asset => {\n const src = asString(raw.src) ?? \"\";\n return {\n id: asString(raw.id) ?? \"\",\n src,\n url: asString(raw.url) ?? src,\n name: asString(raw.name) ?? \"\",\n type,\n size: asNumber(raw.size) ?? 0\n };\n};\n\nexport function normalizeToAsset(input: unknown): Asset | null {\n if (!isObject(input)) {\n return null;\n }\n\n const hasTypedSubObject =\n isObject(input.image) || isObject(input.document) || isObject(input.video);\n\n if (!hasTypedSubObject) {\n const type = asString(input.mimeType) ?? asString(input.type) ?? \"\";\n const asset = buildBase(input, type);\n if (getAssetCategory(type) === \"image\") {\n const image = assetImageFromLegacyEdit(input.edit as LegacyImageEdit | undefined, {\n width: asNumber(input.width),\n height: asNumber(input.height)\n });\n if (image) {\n asset.image = image;\n }\n }\n return asset;\n }\n\n const type = asString(input.type) ?? asString(input.mimeType) ?? \"\";\n const asset = buildBase(input, type);\n const category = getAssetCategory(type);\n if (category === \"image\") {\n const image = normalizeAssetImage(input.image);\n if (image) {\n asset.image = image;\n }\n } else if (category === \"video\") {\n if (isObject(input.video)) {\n asset.video = {\n autoplay:\n typeof input.video.autoplay === \"boolean\" ? input.video.autoplay : undefined,\n poster: asString(input.video.poster)\n };\n }\n } else if (isObject(input.document)) {\n asset.document = { pages: asNumber(input.document.pages) };\n }\n return asset;\n}\n"],"names":["getAssetCategory","type","isObject","value","asNumber","Number","undefined","asString","assetImageFromLegacyEdit","edit","dimensions","image","width","height","Object","normalizeAssetImage","raw","x","y","alt","caption","buildBase","src","normalizeToAsset","input","hasTypedSubObject","asset","category"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { assetImageFromLegacyEdit, getAssetCategory, normalizeToAsset } from "./normalize.js";
|
|
3
|
+
import { resolveAssetImage } from "../image/geometry.js";
|
|
4
|
+
describe("getAssetCategory", ()=>{
|
|
5
|
+
it("buckets by MIME prefix", ()=>{
|
|
6
|
+
expect(getAssetCategory("image/jpeg")).toBe("image");
|
|
7
|
+
expect(getAssetCategory("image/svg+xml")).toBe("image");
|
|
8
|
+
expect(getAssetCategory("video/mp4")).toBe("video");
|
|
9
|
+
});
|
|
10
|
+
it("treats everything else as a document", ()=>{
|
|
11
|
+
expect(getAssetCategory("application/pdf")).toBe("document");
|
|
12
|
+
expect(getAssetCategory("application/zip")).toBe("document");
|
|
13
|
+
expect(getAssetCategory("")).toBe("document");
|
|
14
|
+
expect(getAssetCategory(void 0)).toBe("document");
|
|
15
|
+
expect(getAssetCategory(null)).toBe("document");
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
describe("assetImageFromLegacyEdit", ()=>{
|
|
19
|
+
it("maps hotspot -> focalPoint and keeps crop/alt/caption", ()=>{
|
|
20
|
+
const image = assetImageFromLegacyEdit({
|
|
21
|
+
crop: {
|
|
22
|
+
top: 0.1,
|
|
23
|
+
left: 0,
|
|
24
|
+
bottom: 0.1,
|
|
25
|
+
right: 0
|
|
26
|
+
},
|
|
27
|
+
hotspot: {
|
|
28
|
+
x: 0.3,
|
|
29
|
+
y: 0.7,
|
|
30
|
+
width: 0.5,
|
|
31
|
+
height: 0.5
|
|
32
|
+
},
|
|
33
|
+
alt: "A cat",
|
|
34
|
+
caption: "Meow"
|
|
35
|
+
}, {
|
|
36
|
+
width: 800,
|
|
37
|
+
height: 600
|
|
38
|
+
});
|
|
39
|
+
expect(image).toEqual({
|
|
40
|
+
width: 800,
|
|
41
|
+
height: 600,
|
|
42
|
+
crop: {
|
|
43
|
+
top: 0.1,
|
|
44
|
+
left: 0,
|
|
45
|
+
bottom: 0.1,
|
|
46
|
+
right: 0
|
|
47
|
+
},
|
|
48
|
+
focalPoint: {
|
|
49
|
+
x: 0.3,
|
|
50
|
+
y: 0.7
|
|
51
|
+
},
|
|
52
|
+
alt: "A cat",
|
|
53
|
+
caption: "Meow"
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
it("returns undefined when there is nothing to store", ()=>{
|
|
57
|
+
expect(assetImageFromLegacyEdit(void 0, {})).toBeUndefined();
|
|
58
|
+
expect(assetImageFromLegacyEdit(null, {
|
|
59
|
+
width: null,
|
|
60
|
+
height: void 0
|
|
61
|
+
})).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
it("keeps dimensions even without an edit", ()=>{
|
|
64
|
+
expect(assetImageFromLegacyEdit(void 0, {
|
|
65
|
+
width: 100,
|
|
66
|
+
height: 50
|
|
67
|
+
})).toEqual({
|
|
68
|
+
width: 100,
|
|
69
|
+
height: 50
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("normalizeToAsset — legacy Website Builder image value", ()=>{
|
|
74
|
+
const legacy = {
|
|
75
|
+
id: "file-1",
|
|
76
|
+
name: "cat.jpg",
|
|
77
|
+
size: 1234,
|
|
78
|
+
mimeType: "image/jpeg",
|
|
79
|
+
src: "https://cdn/x/cat.jpg",
|
|
80
|
+
width: 800,
|
|
81
|
+
height: 600,
|
|
82
|
+
edit: {
|
|
83
|
+
crop: {
|
|
84
|
+
top: 0.1,
|
|
85
|
+
left: 0.1,
|
|
86
|
+
bottom: 0.1,
|
|
87
|
+
right: 0.1
|
|
88
|
+
},
|
|
89
|
+
hotspot: {
|
|
90
|
+
x: 0.25,
|
|
91
|
+
y: 0.75,
|
|
92
|
+
width: 1,
|
|
93
|
+
height: 1
|
|
94
|
+
},
|
|
95
|
+
alt: "A cat"
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
it("upgrades mimeType -> type and edit -> image", ()=>{
|
|
99
|
+
const asset = normalizeToAsset(legacy);
|
|
100
|
+
expect(asset).toEqual({
|
|
101
|
+
id: "file-1",
|
|
102
|
+
src: "https://cdn/x/cat.jpg",
|
|
103
|
+
url: "https://cdn/x/cat.jpg",
|
|
104
|
+
name: "cat.jpg",
|
|
105
|
+
type: "image/jpeg",
|
|
106
|
+
size: 1234,
|
|
107
|
+
image: {
|
|
108
|
+
width: 800,
|
|
109
|
+
height: 600,
|
|
110
|
+
crop: {
|
|
111
|
+
top: 0.1,
|
|
112
|
+
left: 0.1,
|
|
113
|
+
bottom: 0.1,
|
|
114
|
+
right: 0.1
|
|
115
|
+
},
|
|
116
|
+
focalPoint: {
|
|
117
|
+
x: 0.25,
|
|
118
|
+
y: 0.75
|
|
119
|
+
},
|
|
120
|
+
alt: "A cat"
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
it("handles a legacy value with no edit (dimensions only)", ()=>{
|
|
125
|
+
const asset = normalizeToAsset({
|
|
126
|
+
...legacy,
|
|
127
|
+
edit: void 0
|
|
128
|
+
});
|
|
129
|
+
expect(asset?.image).toEqual({
|
|
130
|
+
width: 800,
|
|
131
|
+
height: 600
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
it("upgrades an original flat WB file value (type + top-level dims, no edit)", ()=>{
|
|
135
|
+
const asset = normalizeToAsset({
|
|
136
|
+
id: "f",
|
|
137
|
+
src: "https://cdn/x.jpg",
|
|
138
|
+
name: "x.jpg",
|
|
139
|
+
type: "image/jpeg",
|
|
140
|
+
size: 10,
|
|
141
|
+
width: 800,
|
|
142
|
+
height: 600
|
|
143
|
+
});
|
|
144
|
+
expect(asset).toEqual({
|
|
145
|
+
id: "f",
|
|
146
|
+
src: "https://cdn/x.jpg",
|
|
147
|
+
url: "https://cdn/x.jpg",
|
|
148
|
+
name: "x.jpg",
|
|
149
|
+
type: "image/jpeg",
|
|
150
|
+
size: 10,
|
|
151
|
+
image: {
|
|
152
|
+
width: 800,
|
|
153
|
+
height: 600
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
it("does not attach image data for a non-image legacy value", ()=>{
|
|
158
|
+
const asset = normalizeToAsset({
|
|
159
|
+
...legacy,
|
|
160
|
+
mimeType: "application/pdf",
|
|
161
|
+
edit: void 0,
|
|
162
|
+
width: void 0,
|
|
163
|
+
height: void 0
|
|
164
|
+
});
|
|
165
|
+
expect(asset?.type).toBe("application/pdf");
|
|
166
|
+
expect(asset?.image).toBeUndefined();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe("normalizeToAsset — already-unified asset", ()=>{
|
|
170
|
+
it("is idempotent for a well-formed image asset", ()=>{
|
|
171
|
+
const asset = {
|
|
172
|
+
id: "f2",
|
|
173
|
+
src: "https://cdn/y/pic.png",
|
|
174
|
+
url: "https://cdn/y/pic.png",
|
|
175
|
+
name: "pic.png",
|
|
176
|
+
type: "image/png",
|
|
177
|
+
size: 9,
|
|
178
|
+
image: {
|
|
179
|
+
width: 400,
|
|
180
|
+
height: 300,
|
|
181
|
+
crop: {
|
|
182
|
+
top: 0,
|
|
183
|
+
left: 0.2,
|
|
184
|
+
bottom: 0,
|
|
185
|
+
right: 0.2
|
|
186
|
+
},
|
|
187
|
+
focalPoint: {
|
|
188
|
+
x: 0.5,
|
|
189
|
+
y: 0.5
|
|
190
|
+
},
|
|
191
|
+
alt: "Pic"
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
expect(normalizeToAsset(asset)).toEqual(asset);
|
|
195
|
+
});
|
|
196
|
+
it("preserves video data", ()=>{
|
|
197
|
+
const asset = normalizeToAsset({
|
|
198
|
+
id: "v1",
|
|
199
|
+
src: "https://cdn/v/clip.mp4",
|
|
200
|
+
name: "clip.mp4",
|
|
201
|
+
type: "video/mp4",
|
|
202
|
+
size: 42,
|
|
203
|
+
video: {
|
|
204
|
+
autoplay: true,
|
|
205
|
+
poster: "https://cdn/v/poster.jpg"
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
expect(asset?.video).toEqual({
|
|
209
|
+
autoplay: true,
|
|
210
|
+
poster: "https://cdn/v/poster.jpg"
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
it("returns null for invalid input", ()=>{
|
|
214
|
+
expect(normalizeToAsset(null)).toBeNull();
|
|
215
|
+
expect(normalizeToAsset(void 0)).toBeNull();
|
|
216
|
+
expect(normalizeToAsset("nope")).toBeNull();
|
|
217
|
+
expect(normalizeToAsset(42)).toBeNull();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
describe("resolveAssetImage", ()=>{
|
|
221
|
+
it("prefers override per property, falling back to base", ()=>{
|
|
222
|
+
const merged = resolveAssetImage({
|
|
223
|
+
crop: {
|
|
224
|
+
top: 0.1,
|
|
225
|
+
left: 0,
|
|
226
|
+
bottom: 0,
|
|
227
|
+
right: 0
|
|
228
|
+
}
|
|
229
|
+
}, {
|
|
230
|
+
crop: {
|
|
231
|
+
top: 0.5,
|
|
232
|
+
left: 0,
|
|
233
|
+
bottom: 0,
|
|
234
|
+
right: 0
|
|
235
|
+
},
|
|
236
|
+
focalPoint: {
|
|
237
|
+
x: 0.2,
|
|
238
|
+
y: 0.2
|
|
239
|
+
},
|
|
240
|
+
alt: "base"
|
|
241
|
+
});
|
|
242
|
+
expect(merged.crop).toEqual({
|
|
243
|
+
top: 0.1,
|
|
244
|
+
left: 0,
|
|
245
|
+
bottom: 0,
|
|
246
|
+
right: 0
|
|
247
|
+
});
|
|
248
|
+
expect(merged.focalPoint).toEqual({
|
|
249
|
+
x: 0.2,
|
|
250
|
+
y: 0.2
|
|
251
|
+
});
|
|
252
|
+
expect(merged.alt).toBe("base");
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
//# sourceMappingURL=normalize.test.js.map
|