jazz-tools 0.16.6 → 0.17.1
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/.svelte-kit/__package__/index.d.ts +1 -0
- package/.svelte-kit/__package__/index.d.ts.map +1 -1
- package/.svelte-kit/__package__/index.js +1 -0
- package/.svelte-kit/__package__/media/image.svelte +131 -0
- package/.svelte-kit/__package__/media/image.svelte.d.ts +10 -0
- package/.svelte-kit/__package__/media/image.svelte.d.ts.map +1 -0
- package/.svelte-kit/__package__/media/index.d.ts +2 -0
- package/.svelte-kit/__package__/media/index.d.ts.map +1 -0
- package/.svelte-kit/__package__/media/index.js +1 -0
- package/.svelte-kit/__package__/tests/media/image.svelte.test.d.ts +2 -0
- package/.svelte-kit/__package__/tests/media/image.svelte.test.d.ts.map +1 -0
- package/.svelte-kit/__package__/tests/media/image.svelte.test.js +430 -0
- package/.svelte-kit/__package__/tests/testUtils.d.ts +11 -0
- package/.svelte-kit/__package__/tests/testUtils.d.ts.map +1 -0
- package/.svelte-kit/__package__/tests/testUtils.js +17 -0
- package/.svelte-kit/__package__/tests/types.d.ts +3 -0
- package/.turbo/turbo-build.log +47 -51
- package/CHANGELOG.md +24 -0
- package/dist/{chunk-R2VNCMG6.js → chunk-2SH44VLX.js} +33 -38
- package/dist/chunk-2SH44VLX.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/media/chunk-JBLT443O.js +211 -0
- package/dist/media/chunk-JBLT443O.js.map +1 -0
- package/dist/media/create-image.d.ts +48 -0
- package/dist/media/create-image.d.ts.map +1 -0
- package/dist/media/create-image.test.d.ts +2 -0
- package/dist/media/create-image.test.d.ts.map +1 -0
- package/dist/media/index.browser.d.ts +15 -0
- package/dist/media/index.browser.d.ts.map +1 -0
- package/dist/media/index.browser.js +113 -0
- package/dist/media/index.browser.js.map +1 -0
- package/dist/media/index.d.ts +53 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +13 -0
- package/dist/media/index.js.map +1 -0
- package/dist/media/index.native.d.ts +17 -0
- package/dist/media/index.native.d.ts.map +1 -0
- package/dist/media/index.native.js +126 -0
- package/dist/media/index.native.js.map +1 -0
- package/dist/media/utils.d.ts +17 -0
- package/dist/media/utils.d.ts.map +1 -0
- package/dist/media/utils.test.d.ts +2 -0
- package/dist/media/utils.test.d.ts.map +1 -0
- package/dist/react/index.d.ts +1 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +176 -59
- package/dist/react/index.js.map +1 -1
- package/dist/react/media/image.d.ts +62 -0
- package/dist/react/media/image.d.ts.map +1 -0
- package/dist/react/tests/media/image.test.d.ts +2 -0
- package/dist/react/tests/media/image.test.d.ts.map +1 -0
- package/dist/react-core/tests/useDemoAuth.test.d.ts +2 -0
- package/dist/react-core/tests/useDemoAuth.test.d.ts.map +1 -0
- package/dist/react-native-core/index.d.ts +1 -1
- package/dist/react-native-core/index.d.ts.map +1 -1
- package/dist/react-native-core/index.js +84 -66
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/media/image.d.ts +93 -0
- package/dist/react-native-core/media/image.d.ts.map +1 -0
- package/dist/react-native-core/testing.d.ts +2 -0
- package/dist/react-native-core/testing.d.ts.map +1 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -0
- package/dist/svelte/media/image.svelte +131 -0
- package/dist/svelte/media/image.svelte.d.ts +10 -0
- package/dist/svelte/media/image.svelte.d.ts.map +1 -0
- package/dist/svelte/media/index.d.ts +2 -0
- package/dist/svelte/media/index.d.ts.map +1 -0
- package/dist/svelte/media/index.js +1 -0
- package/dist/svelte/tests/media/image.svelte.test.d.ts +2 -0
- package/dist/svelte/tests/media/image.svelte.test.d.ts.map +1 -0
- package/dist/svelte/tests/media/image.svelte.test.js +430 -0
- package/dist/svelte/tests/testUtils.d.ts +11 -0
- package/dist/svelte/tests/testUtils.d.ts.map +1 -0
- package/dist/svelte/tests/testUtils.js +17 -0
- package/dist/svelte/tests/types.d.ts +3 -0
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/coFeed.d.ts +15 -0
- package/dist/tools/coValues/coFeed.d.ts.map +1 -1
- package/dist/tools/coValues/extensions/imageDef.d.ts +3 -9
- package/dist/tools/coValues/extensions/imageDef.d.ts.map +1 -1
- package/dist/tools/coValues/request.d.ts +1 -1
- package/dist/tools/coValues/request.d.ts.map +1 -1
- package/dist/tools/exports.d.ts +1 -1
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +1 -0
- package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/media/create-image.test.ts +195 -0
- package/src/media/create-image.ts +180 -0
- package/src/media/index.browser.ts +150 -0
- package/src/media/index.native.ts +153 -0
- package/src/media/index.ts +61 -0
- package/src/media/utils.test.ts +373 -0
- package/src/media/utils.ts +205 -0
- package/src/react/index.ts +1 -2
- package/src/react/media/image.tsx +210 -0
- package/src/react/tests/media/image.test.tsx +588 -0
- package/src/react-native-core/index.ts +1 -1
- package/src/react-native-core/media/image.tsx +159 -0
- package/src/svelte/index.ts +1 -0
- package/src/svelte/media/image.svelte +131 -0
- package/src/svelte/media/index.ts +1 -0
- package/src/svelte/tests/media/image.svelte.test.ts +583 -0
- package/src/svelte/tests/testUtils.ts +33 -0
- package/src/svelte/tests/types.d.ts +3 -0
- package/src/tools/coValues/coFeed.ts +37 -5
- package/src/tools/coValues/extensions/imageDef.ts +3 -49
- package/src/tools/coValues/request.ts +1 -1
- package/src/tools/exports.ts +1 -0
- package/src/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.ts +6 -0
- package/src/tools/tests/coMap.record.test.ts +3 -2
- package/src/tools/tests/coOptional.test.ts +3 -1
- package/tsconfig.json +1 -0
- package/tsup.config.ts +4 -9
- package/vitest.config.ts +14 -1
- package/dist/browser-media-images/index.d.ts +0 -9
- package/dist/browser-media-images/index.d.ts.map +0 -1
- package/dist/browser-media-images/index.js +0 -72
- package/dist/browser-media-images/index.js.map +0 -1
- package/dist/chunk-R2VNCMG6.js.map +0 -1
- package/dist/react/media.d.ts +0 -24
- package/dist/react/media.d.ts.map +0 -1
- package/dist/react-native-core/media.d.ts +0 -24
- package/dist/react-native-core/media.d.ts.map +0 -1
- package/dist/react-native-media-images/index.d.ts +0 -7
- package/dist/react-native-media-images/index.d.ts.map +0 -1
- package/dist/react-native-media-images/index.js +0 -177
- package/dist/react-native-media-images/index.js.map +0 -1
- package/dist/tools/tests/imageDef.test.d.ts +0 -2
- package/dist/tools/tests/imageDef.test.d.ts.map +0 -1
- package/src/browser-media-images/index.ts +0 -131
- package/src/react/media.tsx +0 -74
- package/src/react/scratch.tsx +0 -50
- package/src/react-native-core/media.tsx +0 -79
- package/src/react-native-media-images/index.ts +0 -238
- package/src/tools/tests/imageDef.test.ts +0 -278
@@ -0,0 +1,153 @@
|
|
1
|
+
import ImageResizer from "@bam.tech/react-native-image-resizer";
|
2
|
+
import type { Account, Group, ImageDefinition } from "jazz-tools";
|
3
|
+
import { FileStream } from "jazz-tools";
|
4
|
+
import { Image } from "react-native";
|
5
|
+
import {
|
6
|
+
CreateImageOptions,
|
7
|
+
SourceType,
|
8
|
+
createImageFactory,
|
9
|
+
} from "./create-image.js";
|
10
|
+
|
11
|
+
export { highestResAvailable, loadImage, loadImageBySize } from "./utils.js";
|
12
|
+
export { createImageFactory };
|
13
|
+
|
14
|
+
export async function createImage(
|
15
|
+
imageBlobOrFile: Blob | File | string,
|
16
|
+
options?: CreateImageOptions,
|
17
|
+
) {
|
18
|
+
return createImageFactory({
|
19
|
+
getImageSize,
|
20
|
+
getPlaceholderBase64,
|
21
|
+
createFileStreamFromSource,
|
22
|
+
resize,
|
23
|
+
})(imageBlobOrFile, options || {});
|
24
|
+
}
|
25
|
+
|
26
|
+
async function getImageSize(
|
27
|
+
filePath: SourceType,
|
28
|
+
): Promise<{ width: number; height: number }> {
|
29
|
+
if (typeof filePath !== "string") {
|
30
|
+
throw new Error(
|
31
|
+
"createImage(Blob | File) is not supported on this platform",
|
32
|
+
);
|
33
|
+
}
|
34
|
+
|
35
|
+
const { width, height } = await Image.getSize(filePath);
|
36
|
+
|
37
|
+
return { width, height };
|
38
|
+
}
|
39
|
+
|
40
|
+
async function getPlaceholderBase64(filePath: SourceType): Promise<string> {
|
41
|
+
if (typeof filePath !== "string") {
|
42
|
+
throw new Error(
|
43
|
+
"createImage(Blob | File) is not supported on this platform",
|
44
|
+
);
|
45
|
+
}
|
46
|
+
|
47
|
+
if (typeof ImageResizer === "undefined" || ImageResizer === null) {
|
48
|
+
throw new Error(
|
49
|
+
"ImageResizer is not installed, please run `npm install @bam.tech/react-native-image-resizer`",
|
50
|
+
);
|
51
|
+
}
|
52
|
+
|
53
|
+
const { uri } = await ImageResizer.createResizedImage(
|
54
|
+
filePath,
|
55
|
+
8,
|
56
|
+
8,
|
57
|
+
"PNG",
|
58
|
+
100,
|
59
|
+
);
|
60
|
+
|
61
|
+
return imageUrlToBase64(uri);
|
62
|
+
}
|
63
|
+
|
64
|
+
async function resize(
|
65
|
+
filePath: SourceType,
|
66
|
+
width: number,
|
67
|
+
height: number,
|
68
|
+
): Promise<string> {
|
69
|
+
if (typeof filePath !== "string") {
|
70
|
+
throw new Error(
|
71
|
+
"createImage(Blob | File) is not supported on this platform",
|
72
|
+
);
|
73
|
+
}
|
74
|
+
|
75
|
+
if (typeof ImageResizer === "undefined" || ImageResizer === null) {
|
76
|
+
throw new Error(
|
77
|
+
"ImageResizer is not installed, please run `npm install @bam.tech/react-native-image-resizer`",
|
78
|
+
);
|
79
|
+
}
|
80
|
+
|
81
|
+
const mimeType = await getMimeType(filePath);
|
82
|
+
|
83
|
+
const { uri } = await ImageResizer.createResizedImage(
|
84
|
+
filePath,
|
85
|
+
width,
|
86
|
+
height,
|
87
|
+
contentTypeToFormat(mimeType),
|
88
|
+
80,
|
89
|
+
);
|
90
|
+
|
91
|
+
return uri;
|
92
|
+
}
|
93
|
+
|
94
|
+
function getMimeType(filePath: string): Promise<string> {
|
95
|
+
return fetch(filePath)
|
96
|
+
.then((res) => res.blob())
|
97
|
+
.then((blob) => blob.type);
|
98
|
+
}
|
99
|
+
|
100
|
+
function contentTypeToFormat(contentType: string) {
|
101
|
+
if (contentType.includes("image/png")) return "PNG";
|
102
|
+
if (contentType.includes("image/jpeg")) return "JPEG";
|
103
|
+
if (contentType.includes("image/webp")) return "WEBP";
|
104
|
+
return "PNG";
|
105
|
+
}
|
106
|
+
|
107
|
+
export async function createFileStreamFromSource(
|
108
|
+
filePath: SourceType,
|
109
|
+
owner?: Account | Group,
|
110
|
+
): Promise<FileStream> {
|
111
|
+
if (typeof filePath !== "string") {
|
112
|
+
throw new Error(
|
113
|
+
"createImage(Blob | File) is not supported on this platform",
|
114
|
+
);
|
115
|
+
}
|
116
|
+
|
117
|
+
const blob = await fetch(filePath).then((res) => res.blob());
|
118
|
+
const arrayBuffer = await toArrayBuffer(blob);
|
119
|
+
|
120
|
+
return FileStream.createFromArrayBuffer(arrayBuffer, blob.type, undefined, {
|
121
|
+
owner,
|
122
|
+
});
|
123
|
+
}
|
124
|
+
|
125
|
+
// TODO: look for more efficient way to do this as React Native hasn't blob.arrayBuffer()
|
126
|
+
function toArrayBuffer(blob: Blob): Promise<ArrayBuffer> {
|
127
|
+
return new Promise((resolve, reject) => {
|
128
|
+
const reader = new FileReader();
|
129
|
+
reader.onloadend = () => {
|
130
|
+
resolve(reader.result as ArrayBuffer);
|
131
|
+
};
|
132
|
+
reader.onerror = (error) => {
|
133
|
+
reject(error);
|
134
|
+
};
|
135
|
+
reader.readAsArrayBuffer(blob);
|
136
|
+
});
|
137
|
+
}
|
138
|
+
|
139
|
+
async function imageUrlToBase64(url: string): Promise<string> {
|
140
|
+
const response = await fetch(url);
|
141
|
+
const blob = await response.blob();
|
142
|
+
return new Promise((onSuccess, onError) => {
|
143
|
+
try {
|
144
|
+
const reader = new FileReader();
|
145
|
+
reader.onload = function () {
|
146
|
+
onSuccess(reader.result as string);
|
147
|
+
};
|
148
|
+
reader.readAsDataURL(blob);
|
149
|
+
} catch (e) {
|
150
|
+
onError(e);
|
151
|
+
}
|
152
|
+
});
|
153
|
+
}
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import type { ImageDefinition } from "jazz-tools";
|
2
|
+
import {
|
3
|
+
CreateImageOptions,
|
4
|
+
SourceType,
|
5
|
+
createImageFactory,
|
6
|
+
} from "./create-image.js";
|
7
|
+
|
8
|
+
export { highestResAvailable, loadImage, loadImageBySize } from "./utils.js";
|
9
|
+
export { createImageFactory };
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Creates an ImageDefinition from an image file or blob with built-in UX features.
|
13
|
+
*
|
14
|
+
* This function creates a specialized CoValue for managing images in Jazz applications.
|
15
|
+
* It supports blurry placeholders, built-in resizing, and progressive loading patterns.
|
16
|
+
*
|
17
|
+
* @returns Promise that resolves to an ImageDefinition
|
18
|
+
*
|
19
|
+
* @example
|
20
|
+
* ```ts
|
21
|
+
* import { createImage } from "jazz-tools/media";
|
22
|
+
*
|
23
|
+
* // Create an image from a file input
|
24
|
+
* async function handleFileUpload(event: React.ChangeEvent<HTMLInputElement>) {
|
25
|
+
* const file = event.target.files?.[0];
|
26
|
+
* if (file) {
|
27
|
+
* // Creates ImageDefinition with a blurry placeholder, limited to 1024px
|
28
|
+
* // on the longest side, and multiple resolutions automatically
|
29
|
+
* const image = await createImage(file, {
|
30
|
+
* owner: me._owner,
|
31
|
+
* maxSize: 1024,
|
32
|
+
* placeholder: "blur",
|
33
|
+
* progressive: true,
|
34
|
+
* });
|
35
|
+
*
|
36
|
+
* // Store the image in your application data
|
37
|
+
* me.profile.image = image;
|
38
|
+
* }
|
39
|
+
* }
|
40
|
+
* ```
|
41
|
+
*
|
42
|
+
* @example
|
43
|
+
* ```ts
|
44
|
+
* // React Native example
|
45
|
+
* import { createImage } from "jazz-tools/media";
|
46
|
+
*
|
47
|
+
* async function uploadImageFromCamera(imagePath: string) {
|
48
|
+
* const image = await createImage(imagePath, {
|
49
|
+
* maxSize: 800,
|
50
|
+
* placeholder: "blur",
|
51
|
+
* progressive: false,
|
52
|
+
* });
|
53
|
+
*
|
54
|
+
* return image;
|
55
|
+
* }
|
56
|
+
* ```
|
57
|
+
*/
|
58
|
+
export declare function createImage(
|
59
|
+
imageBlobOrFile: SourceType,
|
60
|
+
options?: CreateImageOptions,
|
61
|
+
): Promise<ImageDefinition>;
|
@@ -0,0 +1,373 @@
|
|
1
|
+
import { Account, FileStream, Group, ImageDefinition } from "jazz-tools";
|
2
|
+
import {
|
3
|
+
createJazzTestAccount,
|
4
|
+
setActiveAccount,
|
5
|
+
setupJazzTestSync,
|
6
|
+
} from "jazz-tools/testing";
|
7
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
8
|
+
import { highestResAvailable, loadImageBySize } from "./utils.js";
|
9
|
+
|
10
|
+
const createFileStream = (account: any, blobSize?: number) => {
|
11
|
+
return FileStream.createFromBlob(
|
12
|
+
new Blob([new Uint8Array(blobSize || 1)], { type: "image/png" }),
|
13
|
+
{
|
14
|
+
owner: account,
|
15
|
+
},
|
16
|
+
);
|
17
|
+
};
|
18
|
+
|
19
|
+
describe("highestResAvailable", async () => {
|
20
|
+
let account: Account;
|
21
|
+
|
22
|
+
beforeEach(async () => {
|
23
|
+
account = await createJazzTestAccount({
|
24
|
+
isCurrentActiveAccount: true,
|
25
|
+
});
|
26
|
+
vi.spyOn(Account, "getMe").mockReturnValue(account);
|
27
|
+
await setupJazzTestSync();
|
28
|
+
});
|
29
|
+
|
30
|
+
it("returns original if progressive is false", async () => {
|
31
|
+
const original = await createFileStream(account._owner);
|
32
|
+
const imageDef = ImageDefinition.create(
|
33
|
+
{
|
34
|
+
originalSize: [1920, 1080],
|
35
|
+
progressive: false,
|
36
|
+
original,
|
37
|
+
},
|
38
|
+
{ owner: account._owner },
|
39
|
+
);
|
40
|
+
|
41
|
+
imageDef["1920x1080"] = original;
|
42
|
+
|
43
|
+
const result = highestResAvailable(imageDef, 256, 256);
|
44
|
+
expect(result?.image.id).toBe(original.id);
|
45
|
+
});
|
46
|
+
|
47
|
+
it("returns original if progressive is true but no resizes present", async () => {
|
48
|
+
const original = await createFileStream(account._owner, 1);
|
49
|
+
const imageDef = ImageDefinition.create(
|
50
|
+
{
|
51
|
+
originalSize: [1920, 1080],
|
52
|
+
progressive: true,
|
53
|
+
original,
|
54
|
+
},
|
55
|
+
{ owner: account._owner },
|
56
|
+
);
|
57
|
+
|
58
|
+
imageDef["1920x1080"] = original;
|
59
|
+
|
60
|
+
const result = highestResAvailable(imageDef, 256, 256);
|
61
|
+
expect(result?.image.id).toBe(original.id);
|
62
|
+
});
|
63
|
+
|
64
|
+
it("returns closest available resize if progressive is true", async () => {
|
65
|
+
const original = await createFileStream(account._owner);
|
66
|
+
const resize256 = await createFileStream(account._owner, 1);
|
67
|
+
const imageDef = ImageDefinition.create(
|
68
|
+
{
|
69
|
+
originalSize: [1920, 1080],
|
70
|
+
progressive: true,
|
71
|
+
original,
|
72
|
+
},
|
73
|
+
{ owner: account._owner },
|
74
|
+
);
|
75
|
+
|
76
|
+
imageDef["1920x1080"] = original;
|
77
|
+
imageDef["256x256"] = resize256;
|
78
|
+
|
79
|
+
const result = highestResAvailable(imageDef, 256, 256);
|
80
|
+
expect(result?.image.id).toBe(resize256.id);
|
81
|
+
});
|
82
|
+
|
83
|
+
it("returns original if wanted size matches original size", async () => {
|
84
|
+
const original = await createFileStream(account._owner);
|
85
|
+
const imageDef = ImageDefinition.create(
|
86
|
+
{
|
87
|
+
originalSize: [1024, 1024],
|
88
|
+
progressive: true,
|
89
|
+
original,
|
90
|
+
},
|
91
|
+
{ owner: account._owner },
|
92
|
+
);
|
93
|
+
|
94
|
+
imageDef["1024x1024"] = original;
|
95
|
+
|
96
|
+
const result = highestResAvailable(imageDef, 1024, 1024);
|
97
|
+
expect(result?.image.id).toBe(original.id);
|
98
|
+
});
|
99
|
+
|
100
|
+
it("returns best fit among multiple resizes", async () => {
|
101
|
+
const original = await createFileStream(account._owner);
|
102
|
+
const resize256 = await createFileStream(account._owner, 1);
|
103
|
+
const resize1024 = await createFileStream(account._owner, 1);
|
104
|
+
const resize2048 = await createFileStream(account._owner, 1);
|
105
|
+
const imageDef = ImageDefinition.create(
|
106
|
+
{
|
107
|
+
originalSize: [2048, 2048],
|
108
|
+
progressive: true,
|
109
|
+
original,
|
110
|
+
},
|
111
|
+
{ owner: account._owner },
|
112
|
+
);
|
113
|
+
|
114
|
+
imageDef["256x256"] = resize256;
|
115
|
+
imageDef["1024x1024"] = resize1024;
|
116
|
+
imageDef["2048x2048"] = resize2048;
|
117
|
+
|
118
|
+
// Closest to 900x900 is 1024
|
119
|
+
const result = highestResAvailable(imageDef, 900, 900);
|
120
|
+
expect(result?.image.id).toBe(resize1024.id);
|
121
|
+
});
|
122
|
+
|
123
|
+
it("returns the best fit resolution", async () => {
|
124
|
+
const original = await createFileStream(account._owner, 1);
|
125
|
+
const resize256 = await createFileStream(account._owner, 1);
|
126
|
+
const resize2048 = await createFileStream(account._owner, 1);
|
127
|
+
// 1024 is not loaded yet
|
128
|
+
const resize1024 = FileStream.create({ owner: account._owner });
|
129
|
+
resize1024.start({ mimeType: "image/jpeg" });
|
130
|
+
// Don't end resize1024, so it has no chunks
|
131
|
+
|
132
|
+
const imageDef = ImageDefinition.create(
|
133
|
+
{
|
134
|
+
originalSize: [2048, 2048],
|
135
|
+
progressive: true,
|
136
|
+
original,
|
137
|
+
},
|
138
|
+
{ owner: account._owner },
|
139
|
+
);
|
140
|
+
imageDef["256x256"] = resize256;
|
141
|
+
imageDef["1024x1024"] = resize1024;
|
142
|
+
imageDef["2048x2048"] = resize2048;
|
143
|
+
|
144
|
+
// Closest to 900x900 is 1024
|
145
|
+
const result = highestResAvailable(imageDef, 900, 900);
|
146
|
+
expect(result?.image.id).toBe(resize2048.id);
|
147
|
+
});
|
148
|
+
|
149
|
+
it("returns original if no resizes are loaded (missing chunks)", async () => {
|
150
|
+
const original = await createFileStream(account._owner);
|
151
|
+
const imageDef = ImageDefinition.create(
|
152
|
+
{
|
153
|
+
originalSize: [256, 256],
|
154
|
+
progressive: true,
|
155
|
+
original,
|
156
|
+
},
|
157
|
+
{ owner: account._owner },
|
158
|
+
);
|
159
|
+
|
160
|
+
imageDef["256x256"] = original;
|
161
|
+
// 1024 is not loaded yet
|
162
|
+
const resize1024 = FileStream.create({ owner: account._owner });
|
163
|
+
resize1024.start({ mimeType: "image/jpeg" });
|
164
|
+
// Don't end resize1024, so it has no chunks
|
165
|
+
imageDef["1024x1024"] = resize1024;
|
166
|
+
|
167
|
+
const result = highestResAvailable(imageDef, 1024, 1024);
|
168
|
+
// Only original is valid
|
169
|
+
expect(result?.image.id).toBe(original.id);
|
170
|
+
});
|
171
|
+
|
172
|
+
it("returns the first loaded resize if original is not loaded yet(missing chunks)", async () => {
|
173
|
+
const original = FileStream.create({ owner: account._owner });
|
174
|
+
original.start({ mimeType: "image/jpeg" });
|
175
|
+
// Don't call .end(), so it has no chunks
|
176
|
+
|
177
|
+
const imageDef = ImageDefinition.create(
|
178
|
+
{
|
179
|
+
originalSize: [300, 300],
|
180
|
+
progressive: true,
|
181
|
+
original,
|
182
|
+
},
|
183
|
+
{ owner: account._owner },
|
184
|
+
);
|
185
|
+
|
186
|
+
imageDef["256x256"] = await createFileStream(account._owner, 1);
|
187
|
+
|
188
|
+
const result = highestResAvailable(imageDef, 1024, 1024);
|
189
|
+
// Only original is valid
|
190
|
+
expect(result?.image.id).toBe(imageDef["256x256"].id);
|
191
|
+
});
|
192
|
+
|
193
|
+
it("returns the highest resolution if no good match is found", async () => {
|
194
|
+
const original = await createFileStream(account._owner, 1);
|
195
|
+
|
196
|
+
const imageDef = ImageDefinition.create(
|
197
|
+
{
|
198
|
+
originalSize: [300, 300],
|
199
|
+
progressive: true,
|
200
|
+
original,
|
201
|
+
},
|
202
|
+
{ owner: account._owner },
|
203
|
+
);
|
204
|
+
|
205
|
+
imageDef["256x256"] = await createFileStream(account._owner, 1);
|
206
|
+
imageDef["300x300"] = original;
|
207
|
+
|
208
|
+
const result = highestResAvailable(imageDef, 1024, 1024);
|
209
|
+
expect(result?.image.id).toBe(original.id);
|
210
|
+
});
|
211
|
+
});
|
212
|
+
|
213
|
+
describe("loadImageBySize", async () => {
|
214
|
+
let account: Account;
|
215
|
+
beforeEach(async () => {
|
216
|
+
account = await setupJazzTestSync();
|
217
|
+
setActiveAccount(account);
|
218
|
+
});
|
219
|
+
|
220
|
+
const createImageDef = async (
|
221
|
+
sizes: Array<[number, number]>,
|
222
|
+
progressive = true,
|
223
|
+
owner: Account | Group = account,
|
224
|
+
) => {
|
225
|
+
if (sizes.length === 0) throw new Error("sizes array must not be empty");
|
226
|
+
|
227
|
+
const originalSize = sizes[sizes.length - 1]!;
|
228
|
+
sizes = sizes.slice(0, -1);
|
229
|
+
|
230
|
+
const original = await createFileStream(owner, 1);
|
231
|
+
// Ensure sizes array is not empty
|
232
|
+
const imageDef = ImageDefinition.create(
|
233
|
+
{
|
234
|
+
originalSize,
|
235
|
+
progressive,
|
236
|
+
original,
|
237
|
+
},
|
238
|
+
{ owner },
|
239
|
+
);
|
240
|
+
imageDef[`${originalSize[0]}x${originalSize[1]}`] = original;
|
241
|
+
|
242
|
+
for (const size of sizes) {
|
243
|
+
if (!size) continue;
|
244
|
+
const [w, h] = size;
|
245
|
+
imageDef[`${w}x${h}`] = await createFileStream(owner, 1);
|
246
|
+
}
|
247
|
+
return imageDef;
|
248
|
+
};
|
249
|
+
|
250
|
+
it("returns original if progressive is false", async () => {
|
251
|
+
const imageDef = await createImageDef([[1920, 1080]], false);
|
252
|
+
const result = await loadImageBySize(imageDef, 256, 256);
|
253
|
+
expect(result?.image.id).toBe(imageDef["1920x1080"]!.id);
|
254
|
+
});
|
255
|
+
|
256
|
+
it("returns the original image already loaded", async () => {
|
257
|
+
const account = await setupJazzTestSync({ asyncPeers: true });
|
258
|
+
const account2 = await createJazzTestAccount();
|
259
|
+
|
260
|
+
setActiveAccount(account);
|
261
|
+
|
262
|
+
const group = Group.create();
|
263
|
+
group.addMember("everyone", "reader");
|
264
|
+
|
265
|
+
const imageDef = await createImageDef([[1920, 1080]], false, group);
|
266
|
+
setActiveAccount(account2);
|
267
|
+
|
268
|
+
const result = await loadImageBySize(imageDef, 256, 256);
|
269
|
+
expect(result?.image.id).toBe(imageDef["1920x1080"]!.id);
|
270
|
+
expect(result?.image.isBinaryStreamEnded()).toBe(true);
|
271
|
+
expect(result?.image.asBase64()).toStrictEqual(expect.any(String));
|
272
|
+
});
|
273
|
+
|
274
|
+
it("returns null if no sizes are available", async () => {
|
275
|
+
const original = await createFileStream(account._owner, 1);
|
276
|
+
const imageDef = ImageDefinition.create(
|
277
|
+
{
|
278
|
+
originalSize: [1920, 1080],
|
279
|
+
progressive: true,
|
280
|
+
original,
|
281
|
+
},
|
282
|
+
{ owner: account._owner },
|
283
|
+
);
|
284
|
+
const result = await loadImageBySize(imageDef, 256, 256);
|
285
|
+
expect(result).toBeNull();
|
286
|
+
});
|
287
|
+
|
288
|
+
it("returns the closest available resize if progressive is true", async () => {
|
289
|
+
const imageDef = await createImageDef([
|
290
|
+
[256, 256],
|
291
|
+
[1920, 1080],
|
292
|
+
]);
|
293
|
+
const result = await loadImageBySize(imageDef.id, 256, 256);
|
294
|
+
expect(result?.image.id).toBe(imageDef["256x256"]!.id);
|
295
|
+
expect(result?.width).toBe(256);
|
296
|
+
expect(result?.height).toBe(256);
|
297
|
+
});
|
298
|
+
|
299
|
+
it("returns the best fit among multiple resizes", async () => {
|
300
|
+
const imageDef = await createImageDef([
|
301
|
+
[256, 256],
|
302
|
+
[1024, 1024],
|
303
|
+
[2048, 2048],
|
304
|
+
]);
|
305
|
+
const result = await loadImageBySize(imageDef, 900, 900);
|
306
|
+
expect(result?.image.id).toBe(imageDef["1024x1024"]!.id);
|
307
|
+
expect(result?.width).toBe(1024);
|
308
|
+
expect(result?.height).toBe(1024);
|
309
|
+
});
|
310
|
+
|
311
|
+
it("returns the highest resolution if no good match is found", async () => {
|
312
|
+
const imageDef = await createImageDef([
|
313
|
+
[256, 256],
|
314
|
+
[300, 300],
|
315
|
+
]);
|
316
|
+
const result = await loadImageBySize(imageDef, 1024, 1024);
|
317
|
+
expect(result?.image.id).toBe(imageDef["300x300"]!.id);
|
318
|
+
expect(result?.width).toBe(300);
|
319
|
+
expect(result?.height).toBe(300);
|
320
|
+
});
|
321
|
+
|
322
|
+
it("returns null if the best target is not loaded", async () => {
|
323
|
+
const original = await createFileStream(account._owner, 1);
|
324
|
+
const imageDef = ImageDefinition.create(
|
325
|
+
{
|
326
|
+
originalSize: [256, 256],
|
327
|
+
progressive: true,
|
328
|
+
original,
|
329
|
+
},
|
330
|
+
{ owner: account._owner },
|
331
|
+
);
|
332
|
+
// No resizes added
|
333
|
+
const result = await loadImageBySize(imageDef, 1024, 1024);
|
334
|
+
expect(result).toBeNull();
|
335
|
+
});
|
336
|
+
|
337
|
+
it("returns the correct size when wanted size matches available size exactly", async () => {
|
338
|
+
const imageDef = await createImageDef([
|
339
|
+
[512, 512],
|
340
|
+
[1024, 1024],
|
341
|
+
]);
|
342
|
+
const result = await loadImageBySize(imageDef, 1024, 1024);
|
343
|
+
expect(result?.image.id).toBe(imageDef["1024x1024"]!.id);
|
344
|
+
expect(result?.width).toBe(1024);
|
345
|
+
expect(result?.height).toBe(1024);
|
346
|
+
});
|
347
|
+
|
348
|
+
it("returns the image already loaded", async () => {
|
349
|
+
const account = await setupJazzTestSync({ asyncPeers: true });
|
350
|
+
const account2 = await createJazzTestAccount();
|
351
|
+
|
352
|
+
setActiveAccount(account);
|
353
|
+
|
354
|
+
const group = Group.create();
|
355
|
+
group.addMember("everyone", "reader");
|
356
|
+
|
357
|
+
const imageDef = await createImageDef(
|
358
|
+
[
|
359
|
+
[512, 512],
|
360
|
+
[1024, 1024],
|
361
|
+
],
|
362
|
+
undefined,
|
363
|
+
group,
|
364
|
+
);
|
365
|
+
|
366
|
+
setActiveAccount(account2);
|
367
|
+
|
368
|
+
const result = await loadImageBySize(imageDef, 1024, 1024);
|
369
|
+
expect(result?.image.id).toBe(imageDef["1024x1024"]!.id);
|
370
|
+
expect(result?.image.isBinaryStreamEnded()).toBe(true);
|
371
|
+
expect(result?.image.asBase64()).toStrictEqual(expect.any(String));
|
372
|
+
});
|
373
|
+
});
|