jazz-tools 0.15.2 → 0.15.3
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/.turbo/turbo-build.log +35 -35
- package/CHANGELOG.md +11 -0
- package/dist/browser-media-images/index.d.ts.map +1 -1
- package/dist/browser-media-images/index.js +53 -51
- package/dist/browser-media-images/index.js.map +1 -1
- package/dist/browser-media-images/index.test.browser.d.ts +2 -0
- package/dist/browser-media-images/index.test.browser.d.ts.map +1 -0
- package/package.json +7 -5
- package/src/browser-media-images/index.test.browser.ts +73 -0
- package/src/browser-media-images/index.ts +96 -95
- package/vitest.config.ts +25 -4
package/.turbo/turbo-build.log
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
> jazz-tools@0.15.
|
2
|
+
> jazz-tools@0.15.3 build /home/runner/_work/jazz/jazz/packages/jazz-tools
|
3
3
|
> tsup && pnpm types && pnpm build:svelte
|
4
4
|
|
5
5
|
[34mCLI[39m Building entry: {"index":"src/index.ts","testing":"src/testing.ts"}
|
@@ -109,83 +109,83 @@
|
|
109
109
|
[34mESM[39m Build start
|
110
110
|
[32mESM[39m [1mdist/worker/index.js [22m[32m2.34 KB[39m
|
111
111
|
[32mESM[39m [1mdist/worker/index.js.map [22m[32m4.82 KB[39m
|
112
|
-
[32mESM[39m ⚡️ Build success in
|
112
|
+
[32mESM[39m ⚡️ Build success in 22ms
|
113
113
|
[32mESM[39m [1mdist/tiptap/index.js [22m[32m564.00 B[39m
|
114
114
|
[32mESM[39m [1mdist/tiptap/index.js.map [22m[32m1.21 KB[39m
|
115
|
-
[32mESM[39m ⚡️ Build success in
|
116
|
-
[32mESM[39m [1mdist/browser-media-images/index.js [22m[
|
117
|
-
[32mESM[39m [1mdist/browser-media-images/index.js.map [22m[32m5.
|
118
|
-
[32mESM[39m ⚡️ Build success in
|
115
|
+
[32mESM[39m ⚡️ Build success in 23ms
|
116
|
+
[32mESM[39m [1mdist/browser-media-images/index.js [22m[32m2.55 KB[39m
|
117
|
+
[32mESM[39m [1mdist/browser-media-images/index.js.map [22m[32m5.44 KB[39m
|
118
|
+
[32mESM[39m ⚡️ Build success in 30ms
|
119
119
|
[32mESM[39m [1mdist/react-native-media-images/index.js [22m[32m6.19 KB[39m
|
120
120
|
[32mESM[39m [1mdist/react-native-media-images/index.js.map [22m[32m10.96 KB[39m
|
121
|
-
[32mESM[39m ⚡️ Build success in
|
121
|
+
[32mESM[39m ⚡️ Build success in 24ms
|
122
122
|
[32mESM[39m [1mdist/react-core/index.js [22m[32m8.37 KB[39m
|
123
|
-
[32mESM[39m [1mdist/react-core/chunk-7DYMJ74I.js [22m[32m279.00 B[39m
|
124
123
|
[32mESM[39m [1mdist/react-core/testing.js [22m[32m1.17 KB[39m
|
124
|
+
[32mESM[39m [1mdist/react-core/chunk-7DYMJ74I.js [22m[32m279.00 B[39m
|
125
125
|
[32mESM[39m [1mdist/react-core/index.js.map [22m[32m16.39 KB[39m
|
126
|
-
[32mESM[39m [1mdist/react-core/chunk-7DYMJ74I.js.map [22m[32m533.00 B[39m
|
127
126
|
[32mESM[39m [1mdist/react-core/testing.js.map [22m[32m1.82 KB[39m
|
128
|
-
[32mESM[39m
|
127
|
+
[32mESM[39m [1mdist/react-core/chunk-7DYMJ74I.js.map [22m[32m533.00 B[39m
|
128
|
+
[32mESM[39m ⚡️ Build success in 35ms
|
129
|
+
[32mESM[39m [1mdist/react-native/index.js [22m[32m2.53 KB[39m
|
130
|
+
[32mESM[39m [1mdist/react-native/testing.js [22m[32m120.00 B[39m
|
131
|
+
[32mESM[39m [1mdist/react-native/crypto.js [22m[32m117.00 B[39m
|
132
|
+
[32mESM[39m [1mdist/react-native/index.js.map [22m[32m5.68 KB[39m
|
133
|
+
[32mESM[39m [1mdist/react-native/testing.js.map [22m[32m176.00 B[39m
|
134
|
+
[32mESM[39m [1mdist/react-native/crypto.js.map [22m[32m174.00 B[39m
|
135
|
+
[32mESM[39m ⚡️ Build success in 35ms
|
129
136
|
[32mESM[39m [1mdist/expo/index.js [22m[32m4.65 KB[39m
|
130
137
|
[32mESM[39m [1mdist/expo/testing.js [22m[32m112.00 B[39m
|
131
138
|
[32mESM[39m [1mdist/expo/crypto.js [22m[32m109.00 B[39m
|
132
139
|
[32mESM[39m [1mdist/expo/index.js.map [22m[32m10.17 KB[39m
|
133
|
-
[32mESM[39m [1mdist/expo/crypto.js.map [22m[32m166.00 B[39m
|
134
140
|
[32mESM[39m [1mdist/expo/testing.js.map [22m[32m168.00 B[39m
|
135
|
-
[32mESM[39m
|
136
|
-
[32mESM[39m
|
137
|
-
[32mESM[39m [1mdist/react-native/crypto.js [22m[32m117.00 B[39m
|
138
|
-
[32mESM[39m [1mdist/react-native/testing.js [22m[32m120.00 B[39m
|
139
|
-
[32mESM[39m [1mdist/react-native/index.js.map [22m[32m5.68 KB[39m
|
140
|
-
[32mESM[39m [1mdist/react-native/crypto.js.map [22m[32m174.00 B[39m
|
141
|
-
[32mESM[39m [1mdist/react-native/testing.js.map [22m[32m176.00 B[39m
|
142
|
-
[32mESM[39m ⚡️ Build success in 22ms
|
141
|
+
[32mESM[39m [1mdist/expo/crypto.js.map [22m[32m166.00 B[39m
|
142
|
+
[32mESM[39m ⚡️ Build success in 39ms
|
143
143
|
[32mESM[39m [1mdist/browser/index.js [22m[32m13.36 KB[39m
|
144
144
|
[32mESM[39m [1mdist/browser/index.js.map [22m[32m28.57 KB[39m
|
145
|
-
[32mESM[39m ⚡️ Build success in
|
145
|
+
[32mESM[39m ⚡️ Build success in 42ms
|
146
146
|
[32mESM[39m [1mdist/react-native-core/index.js [22m[32m17.42 KB[39m
|
147
|
-
[32mESM[39m [1mdist/react-native-core/crypto.js [22m[32m2.10 KB[39m
|
148
147
|
[32mESM[39m [1mdist/react-native-core/testing.js [22m[32m119.00 B[39m
|
148
|
+
[32mESM[39m [1mdist/react-native-core/crypto.js [22m[32m2.10 KB[39m
|
149
149
|
[32mESM[39m [1mdist/react-native-core/index.js.map [22m[32m34.49 KB[39m
|
150
|
-
[32mESM[39m [1mdist/react-native-core/crypto.js.map [22m[32m4.25 KB[39m
|
151
150
|
[32mESM[39m [1mdist/react-native-core/testing.js.map [22m[32m175.00 B[39m
|
152
|
-
[32mESM[39m
|
151
|
+
[32mESM[39m [1mdist/react-native-core/crypto.js.map [22m[32m4.25 KB[39m
|
152
|
+
[32mESM[39m ⚡️ Build success in 50ms
|
153
153
|
[32mESM[39m [1mdist/react/index.js [22m[32m20.64 KB[39m
|
154
|
-
[32mESM[39m [1mdist/react/ssr.js [22m[32m688.00 B[39m
|
155
154
|
[32mESM[39m [1mdist/react/testing.js [22m[32m107.00 B[39m
|
155
|
+
[32mESM[39m [1mdist/react/ssr.js [22m[32m688.00 B[39m
|
156
156
|
[32mESM[39m [1mdist/react/index.js.map [22m[32m34.07 KB[39m
|
157
|
-
[32mESM[39m [1mdist/react/ssr.js.map [22m[32m1.12 KB[39m
|
158
157
|
[32mESM[39m [1mdist/react/testing.js.map [22m[32m163.00 B[39m
|
159
|
-
[32mESM[39m
|
158
|
+
[32mESM[39m [1mdist/react/ssr.js.map [22m[32m1.12 KB[39m
|
159
|
+
[32mESM[39m ⚡️ Build success in 53ms
|
160
160
|
[32mESM[39m [1mdist/prosemirror/index.js [22m[32m76.90 KB[39m
|
161
161
|
[32mESM[39m [1mdist/prosemirror/index.js.map [22m[32m305.53 KB[39m
|
162
|
-
[32mESM[39m ⚡️ Build success in
|
162
|
+
[32mESM[39m ⚡️ Build success in 54ms
|
163
163
|
[32mESM[39m [1mdist/react/index.js [22m[32m20.66 KB[39m
|
164
164
|
[32mESM[39m [1mdist/react/testing.js [22m[32m122.00 B[39m
|
165
|
-
[32mESM[39m [1mdist/react/testing.js.map [22m[32m165.00 B[39m
|
166
165
|
[32mESM[39m [1mdist/react/index.js.map [22m[32m34.07 KB[39m
|
167
|
-
[32mESM[39m
|
166
|
+
[32mESM[39m [1mdist/react/testing.js.map [22m[32m165.00 B[39m
|
167
|
+
[32mESM[39m ⚡️ Build success in 54ms
|
168
168
|
[32mESM[39m [1mdist/inspector/index.js [22m[32m60.37 KB[39m
|
169
169
|
[32mESM[39m [1mdist/inspector/index.js.map [22m[32m107.85 KB[39m
|
170
|
-
[32mESM[39m ⚡️ Build success in
|
171
|
-
[32mESM[39m [1mdist/index.js [22m[32m16.19 KB[39m
|
170
|
+
[32mESM[39m ⚡️ Build success in 72ms
|
172
171
|
[32mESM[39m [1mdist/testing.js [22m[32m6.32 KB[39m
|
172
|
+
[32mESM[39m [1mdist/index.js [22m[32m16.19 KB[39m
|
173
173
|
[32mESM[39m [1mdist/chunk-VBDJM6Z5.js [22m[32m140.80 KB[39m
|
174
174
|
[32mESM[39m [1mdist/testing.js.map [22m[32m12.22 KB[39m
|
175
175
|
[32mESM[39m [1mdist/index.js.map [22m[32m29.03 KB[39m
|
176
176
|
[32mESM[39m [1mdist/chunk-VBDJM6Z5.js.map [22m[32m320.73 KB[39m
|
177
|
-
[32mESM[39m ⚡️ Build success in
|
177
|
+
[32mESM[39m ⚡️ Build success in 88ms
|
178
178
|
[32mESM[39m [1mdist/inspector/register-custom-element.js [22m[32m218.00 B[39m
|
179
179
|
[32mESM[39m [1mdist/inspector/register-custom-element.js.map [22m[32m314.00 B[39m
|
180
180
|
[32mESM[39m [1mdist/inspector/custom-element-CWW72LEG.js [22m[32m1.47 MB[39m
|
181
181
|
[32mESM[39m [1mdist/inspector/custom-element-CWW72LEG.js.map [22m[32m2.26 MB[39m
|
182
|
-
[32mESM[39m ⚡️ Build success in
|
182
|
+
[32mESM[39m ⚡️ Build success in 149ms
|
183
183
|
|
184
|
-
> jazz-tools@0.15.
|
184
|
+
> jazz-tools@0.15.3 types /home/runner/_work/jazz/jazz/packages/jazz-tools
|
185
185
|
> tsc --outDir dist
|
186
186
|
|
187
187
|
|
188
|
-
> jazz-tools@0.15.
|
188
|
+
> jazz-tools@0.15.3 build:svelte /home/runner/_work/jazz/jazz/packages/jazz-tools
|
189
189
|
> rm -rf dist/svelte && svelte-package -i src/svelte -o dist/svelte --tsconfig tsconfig.svelte.json
|
190
190
|
|
191
191
|
src/svelte -> dist/svelte
|
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# jazz-tools
|
2
2
|
|
3
|
+
## 0.15.3
|
4
|
+
|
5
|
+
### Patch Changes
|
6
|
+
|
7
|
+
- 45f73a7: fix image `originalSize` to be coherent with the highest resolution
|
8
|
+
- Updated dependencies [535c460]
|
9
|
+
- cojson-storage-indexeddb@0.15.3
|
10
|
+
- cojson@0.15.3
|
11
|
+
- cojson-storage@0.15.3
|
12
|
+
- cojson-transport-ws@0.15.3
|
13
|
+
|
3
14
|
## 0.15.2
|
4
15
|
|
5
16
|
### Patch Changes
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser-media-images/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,OAAO,EAEP,KAAK,EACL,eAAe,EACf,MAAM,EACP,MAAM,YAAY,CAAC;AAKpB,+BAA+B;AAC/B,wBAAsB,WAAW,CAC/B,eAAe,EAAE,IAAI,GAAG,IAAI,EAC5B,OAAO,CAAC,EAAE;IACR,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;CAC7B,GACA,OAAO,CAAC,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC,
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser-media-images/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,OAAO,EAEP,KAAK,EACL,eAAe,EACf,MAAM,EACP,MAAM,YAAY,CAAC;AAKpB,+BAA+B;AAC/B,wBAAsB,WAAW,CAC/B,eAAe,EAAE,IAAI,GAAG,IAAI,EAC5B,OAAO,CAAC,EAAE;IACR,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;CAC7B,GACA,OAAO,CAAC,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC,CA8CzC"}
|
@@ -5,65 +5,67 @@ import {
|
|
5
5
|
ImageDefinition
|
6
6
|
} from "jazz-tools";
|
7
7
|
import Pica from "pica";
|
8
|
-
var
|
8
|
+
var reducer;
|
9
9
|
async function createImage(imageBlobOrFile, options) {
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
originalHeight = env.orientation & 4 ? env.image.width : env.image.height;
|
19
|
-
return Promise.resolve(env);
|
20
|
-
});
|
21
|
-
const placeholderDataURL = (await Reducer.toCanvas(imageBlobOrFile, { max: 8 })).toDataURL("image/png");
|
10
|
+
const { width: originalWidth, height: originalHeight } = await getImageSize(imageBlobOrFile);
|
11
|
+
const highestDimension = Math.max(originalWidth, originalHeight);
|
12
|
+
const resizes = [256, 1024, 2048, highestDimension].filter((s) => s <= (options?.maxSize ?? highestDimension)).toSorted((a, b) => a - b);
|
13
|
+
const { width: finalWidth, height: finalHeight } = getNewDimensions(
|
14
|
+
originalWidth,
|
15
|
+
originalHeight,
|
16
|
+
resizes.at(-1)
|
17
|
+
);
|
22
18
|
const imageDefinition = ImageDefinition.create(
|
23
|
-
{
|
24
|
-
originalSize: [originalWidth, originalHeight],
|
25
|
-
placeholderDataURL
|
26
|
-
},
|
19
|
+
{ originalSize: [finalWidth, finalHeight] },
|
27
20
|
options?.owner
|
28
21
|
);
|
29
22
|
const owner = imageDefinition._owner;
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
imageDefinition[`${width}x${height}`] = binaryStream;
|
37
|
-
}
|
38
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
39
|
-
if (options?.maxSize === 256) return;
|
40
|
-
const max1024 = await Reducer.toBlob(imageBlobOrFile, { max: 1024 });
|
41
|
-
if (originalWidth > 1024 || originalHeight > 1024) {
|
42
|
-
const width = originalWidth > originalHeight ? 1024 : Math.round(1024 * (originalWidth / originalHeight));
|
43
|
-
const height = originalHeight > originalWidth ? 1024 : Math.round(1024 * (originalHeight / originalWidth));
|
44
|
-
const binaryStream = await FileStream.createFromBlob(max1024, owner);
|
45
|
-
imageDefinition[`${width}x${height}`] = binaryStream;
|
46
|
-
}
|
47
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
48
|
-
if (options?.maxSize === 1024) return;
|
49
|
-
const max2048 = await Reducer.toBlob(imageBlobOrFile, { max: 2048 });
|
50
|
-
if (originalWidth > 2048 || originalHeight > 2048) {
|
51
|
-
const width = originalWidth > originalHeight ? 2048 : Math.round(2048 * (originalWidth / originalHeight));
|
52
|
-
const height = originalHeight > originalWidth ? 2048 : Math.round(2048 * (originalHeight / originalWidth));
|
53
|
-
const binaryStream = await FileStream.createFromBlob(max2048, owner);
|
54
|
-
imageDefinition[`${width}x${height}`] = binaryStream;
|
55
|
-
}
|
56
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
57
|
-
if (options?.maxSize === 2048) return;
|
58
|
-
const originalBinaryStream = await FileStream.createFromBlob(
|
59
|
-
imageBlobOrFile,
|
60
|
-
owner
|
23
|
+
imageDefinition.placeholderDataURL = await getPlaceholderBase64(imageBlobOrFile);
|
24
|
+
for (let size of resizes) {
|
25
|
+
const { width, height } = getNewDimensions(
|
26
|
+
originalWidth,
|
27
|
+
originalHeight,
|
28
|
+
size
|
61
29
|
);
|
62
|
-
|
63
|
-
|
64
|
-
|
30
|
+
const image = await resize(imageBlobOrFile, width, height);
|
31
|
+
const binaryStream = await FileStream.createFromBlob(image, owner);
|
32
|
+
imageDefinition[`${width}x${height}`] = binaryStream;
|
33
|
+
}
|
65
34
|
return imageDefinition;
|
66
35
|
}
|
36
|
+
async function getImageSize(imageBlobOrFile) {
|
37
|
+
const { width, height } = await new Promise((resolve, reject) => {
|
38
|
+
const img = new Image();
|
39
|
+
img.onload = () => {
|
40
|
+
resolve({ width: img.width, height: img.height });
|
41
|
+
URL.revokeObjectURL(img.src);
|
42
|
+
};
|
43
|
+
img.onerror = () => {
|
44
|
+
reject(new Error("Failed to load image"));
|
45
|
+
URL.revokeObjectURL(img.src);
|
46
|
+
};
|
47
|
+
img.src = URL.createObjectURL(imageBlobOrFile);
|
48
|
+
});
|
49
|
+
return { width, height };
|
50
|
+
}
|
51
|
+
async function getPlaceholderBase64(imageBlobOrFile) {
|
52
|
+
if (!reducer) {
|
53
|
+
reducer = new ImageBlobReduce({ pica: new Pica() });
|
54
|
+
}
|
55
|
+
const canvas = await reducer.toCanvas(imageBlobOrFile, { max: 8 });
|
56
|
+
return canvas.toDataURL("image/png");
|
57
|
+
}
|
58
|
+
async function resize(imageBlobOrFile, width, height) {
|
59
|
+
if (!reducer) {
|
60
|
+
reducer = new ImageBlobReduce({ pica: new Pica() });
|
61
|
+
}
|
62
|
+
return reducer.toBlob(imageBlobOrFile, { max: Math.max(width, height) });
|
63
|
+
}
|
64
|
+
var getNewDimensions = (originalWidth, originalHeight, maxSize) => {
|
65
|
+
const width = originalWidth > originalHeight ? maxSize : Math.round(maxSize * (originalWidth / originalHeight));
|
66
|
+
const height = originalHeight > originalWidth ? maxSize : Math.round(maxSize * (originalHeight / originalWidth));
|
67
|
+
return { width, height };
|
68
|
+
};
|
67
69
|
export {
|
68
70
|
createImage
|
69
71
|
};
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../../src/browser-media-images/index.ts"],"sourcesContent":["import ImageBlobReduce from \"image-blob-reduce\";\nimport {\n Account,\n FileStream,\n Group,\n ImageDefinition,\n Loaded,\n} from \"jazz-tools\";\nimport Pica from \"pica\";\n\nlet
|
1
|
+
{"version":3,"sources":["../../src/browser-media-images/index.ts"],"sourcesContent":["import ImageBlobReduce from \"image-blob-reduce\";\nimport {\n Account,\n FileStream,\n Group,\n ImageDefinition,\n Loaded,\n} from \"jazz-tools\";\nimport Pica from \"pica\";\n\nlet reducer: ImageBlobReduce.ImageBlobReduce | undefined;\n\n/** @category Image creation */\nexport async function createImage(\n imageBlobOrFile: Blob | File,\n options?: {\n owner?: Group | Account;\n maxSize?: 256 | 1024 | 2048;\n },\n): Promise<Loaded<typeof ImageDefinition>> {\n // Get the original size of the image\n const { width: originalWidth, height: originalHeight } =\n await getImageSize(imageBlobOrFile);\n\n const highestDimension = Math.max(originalWidth, originalHeight);\n\n // Calculate the sizes to resize the image to\n const resizes = [256, 1024, 2048, highestDimension]\n .filter((s) => s <= (options?.maxSize ?? highestDimension))\n .toSorted((a, b) => a - b);\n\n // Get the highest resolution to use as final original size\n // In case of options.maxSize, it's not the originalWidth/Height\n const { width: finalWidth, height: finalHeight } = getNewDimensions(\n originalWidth,\n originalHeight,\n resizes.at(-1)!,\n );\n\n const imageDefinition = ImageDefinition.create(\n { originalSize: [finalWidth, finalHeight] },\n options?.owner,\n );\n const owner = imageDefinition._owner;\n\n // Placeholder 8x8\n imageDefinition.placeholderDataURL =\n await getPlaceholderBase64(imageBlobOrFile);\n\n // Resizes for progressive loading\n for (let size of resizes) {\n // Calculate width and height respecting the aspect ratio\n const { width, height } = getNewDimensions(\n originalWidth,\n originalHeight,\n size,\n );\n\n const image = await resize(imageBlobOrFile, width, height);\n\n const binaryStream = await FileStream.createFromBlob(image, owner);\n imageDefinition[`${width}x${height}`] = binaryStream;\n }\n\n return imageDefinition;\n}\n\nasync function getImageSize(\n imageBlobOrFile: Blob | File,\n): Promise<{ width: number; height: number }> {\n const { width, height } = await new Promise<{\n width: number;\n height: number;\n }>((resolve, reject) => {\n const img = new Image();\n img.onload = () => {\n resolve({ width: img.width, height: img.height });\n URL.revokeObjectURL(img.src);\n };\n img.onerror = () => {\n reject(new Error(\"Failed to load image\"));\n URL.revokeObjectURL(img.src);\n };\n img.src = URL.createObjectURL(imageBlobOrFile);\n });\n\n return { width, height };\n}\n\nasync function getPlaceholderBase64(\n imageBlobOrFile: Blob | File,\n): Promise<string> {\n // Inizialize Reducer here to not have module side effects\n if (!reducer) {\n reducer = new ImageBlobReduce({ pica: new Pica() });\n }\n\n const canvas = await reducer.toCanvas(imageBlobOrFile, { max: 8 });\n return canvas.toDataURL(\"image/png\");\n}\n\nasync function resize(\n imageBlobOrFile: Blob | File,\n width: number,\n height: number,\n): Promise<Blob> {\n // Inizialize Reducer here to not have module side effects\n if (!reducer) {\n reducer = new ImageBlobReduce({ pica: new Pica() });\n }\n\n return reducer.toBlob(imageBlobOrFile, { max: Math.max(width, height) });\n}\n\nconst getNewDimensions = (\n originalWidth: number,\n originalHeight: number,\n maxSize: number,\n) => {\n const width =\n originalWidth > originalHeight\n ? maxSize\n : Math.round(maxSize * (originalWidth / originalHeight));\n\n const height =\n originalHeight > originalWidth\n ? maxSize\n : Math.round(maxSize * (originalHeight / originalWidth));\n\n return { width, height };\n};\n"],"mappings":";AAAA,OAAO,qBAAqB;AAC5B;AAAA,EAEE;AAAA,EAEA;AAAA,OAEK;AACP,OAAO,UAAU;AAEjB,IAAI;AAGJ,eAAsB,YACpB,iBACA,SAIyC;AAEzC,QAAM,EAAE,OAAO,eAAe,QAAQ,eAAe,IACnD,MAAM,aAAa,eAAe;AAEpC,QAAM,mBAAmB,KAAK,IAAI,eAAe,cAAc;AAG/D,QAAM,UAAU,CAAC,KAAK,MAAM,MAAM,gBAAgB,EAC/C,OAAO,CAAC,MAAM,MAAM,SAAS,WAAW,iBAAiB,EACzD,SAAS,CAAC,GAAG,MAAM,IAAI,CAAC;AAI3B,QAAM,EAAE,OAAO,YAAY,QAAQ,YAAY,IAAI;AAAA,IACjD;AAAA,IACA;AAAA,IACA,QAAQ,GAAG,EAAE;AAAA,EACf;AAEA,QAAM,kBAAkB,gBAAgB;AAAA,IACtC,EAAE,cAAc,CAAC,YAAY,WAAW,EAAE;AAAA,IAC1C,SAAS;AAAA,EACX;AACA,QAAM,QAAQ,gBAAgB;AAG9B,kBAAgB,qBACd,MAAM,qBAAqB,eAAe;AAG5C,WAAS,QAAQ,SAAS;AAExB,UAAM,EAAE,OAAO,OAAO,IAAI;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,QAAQ,MAAM,OAAO,iBAAiB,OAAO,MAAM;AAEzD,UAAM,eAAe,MAAM,WAAW,eAAe,OAAO,KAAK;AACjE,oBAAgB,GAAG,KAAK,IAAI,MAAM,EAAE,IAAI;AAAA,EAC1C;AAEA,SAAO;AACT;AAEA,eAAe,aACb,iBAC4C;AAC5C,QAAM,EAAE,OAAO,OAAO,IAAI,MAAM,IAAI,QAGjC,CAAC,SAAS,WAAW;AACtB,UAAM,MAAM,IAAI,MAAM;AACtB,QAAI,SAAS,MAAM;AACjB,cAAQ,EAAE,OAAO,IAAI,OAAO,QAAQ,IAAI,OAAO,CAAC;AAChD,UAAI,gBAAgB,IAAI,GAAG;AAAA,IAC7B;AACA,QAAI,UAAU,MAAM;AAClB,aAAO,IAAI,MAAM,sBAAsB,CAAC;AACxC,UAAI,gBAAgB,IAAI,GAAG;AAAA,IAC7B;AACA,QAAI,MAAM,IAAI,gBAAgB,eAAe;AAAA,EAC/C,CAAC;AAED,SAAO,EAAE,OAAO,OAAO;AACzB;AAEA,eAAe,qBACb,iBACiB;AAEjB,MAAI,CAAC,SAAS;AACZ,cAAU,IAAI,gBAAgB,EAAE,MAAM,IAAI,KAAK,EAAE,CAAC;AAAA,EACpD;AAEA,QAAM,SAAS,MAAM,QAAQ,SAAS,iBAAiB,EAAE,KAAK,EAAE,CAAC;AACjE,SAAO,OAAO,UAAU,WAAW;AACrC;AAEA,eAAe,OACb,iBACA,OACA,QACe;AAEf,MAAI,CAAC,SAAS;AACZ,cAAU,IAAI,gBAAgB,EAAE,MAAM,IAAI,KAAK,EAAE,CAAC;AAAA,EACpD;AAEA,SAAO,QAAQ,OAAO,iBAAiB,EAAE,KAAK,KAAK,IAAI,OAAO,MAAM,EAAE,CAAC;AACzE;AAEA,IAAM,mBAAmB,CACvB,eACA,gBACA,YACG;AACH,QAAM,QACJ,gBAAgB,iBACZ,UACA,KAAK,MAAM,WAAW,gBAAgB,eAAe;AAE3D,QAAM,SACJ,iBAAiB,gBACb,UACA,KAAK,MAAM,WAAW,iBAAiB,cAAc;AAE3D,SAAO,EAAE,OAAO,OAAO;AACzB;","names":[]}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.test.browser.d.ts","sourceRoot":"","sources":["../../src/browser-media-images/index.test.browser.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
@@ -139,7 +139,7 @@
|
|
139
139
|
},
|
140
140
|
"type": "module",
|
141
141
|
"license": "MIT",
|
142
|
-
"version": "0.15.
|
142
|
+
"version": "0.15.3",
|
143
143
|
"dependencies": {
|
144
144
|
"@manuscripts/prosemirror-recreate-steps": "^0.1.4",
|
145
145
|
"@op-engineering/op-sqlite": "^11.4.8",
|
@@ -164,10 +164,10 @@
|
|
164
164
|
"react-native-nitro-modules": "0.25.2",
|
165
165
|
"react-native-quick-crypto": "1.0.0-beta.16",
|
166
166
|
"zod": "3.25.28",
|
167
|
-
"cojson": "0.15.
|
168
|
-
"cojson-storage": "0.15.
|
169
|
-
"cojson-storage
|
170
|
-
"cojson-transport-ws": "0.15.
|
167
|
+
"cojson": "0.15.3",
|
168
|
+
"cojson-storage-indexeddb": "0.15.3",
|
169
|
+
"cojson-storage": "0.15.3",
|
170
|
+
"cojson-transport-ws": "0.15.3"
|
171
171
|
},
|
172
172
|
"devDependencies": {
|
173
173
|
"@scure/bip39": "^1.3.0",
|
@@ -177,6 +177,8 @@
|
|
177
177
|
"@testing-library/react": "16.2.0",
|
178
178
|
"@types/react": "19.0.0",
|
179
179
|
"@types/react-dom": "19.0.0",
|
180
|
+
"@vitest/browser": "^3.2.4",
|
181
|
+
"playwright": "^1.50.1",
|
180
182
|
"tsup": "8.5.0",
|
181
183
|
"typescript": "5.6.2",
|
182
184
|
"vitest": "3.1.3",
|
@@ -0,0 +1,73 @@
|
|
1
|
+
// @vitest-environment happy-dom
|
2
|
+
|
3
|
+
import { createJazzTestAccount } from "jazz-tools/testing";
|
4
|
+
import { describe, expect, it } from "vitest";
|
5
|
+
import { createImage } from "./index.js";
|
6
|
+
|
7
|
+
describe("createImage", () => {
|
8
|
+
it("should create an image with a single size if width/height < 256", async () => {
|
9
|
+
const OnePixel =
|
10
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
|
11
|
+
const imageBlob = new Blob(
|
12
|
+
[Uint8Array.from(atob(OnePixel), (c) => c.charCodeAt(0))],
|
13
|
+
{ type: "image/png" },
|
14
|
+
);
|
15
|
+
|
16
|
+
const account = await createJazzTestAccount();
|
17
|
+
|
18
|
+
const image = await createImage(imageBlob, { owner: account._owner });
|
19
|
+
expect(image).toBeDefined();
|
20
|
+
|
21
|
+
expect(image.originalSize).toEqual([1, 1]);
|
22
|
+
expect(image.placeholderDataURL).toBeDefined();
|
23
|
+
|
24
|
+
expect(image[`1x1`]).toBeDefined();
|
25
|
+
expect(image[`1x1`]!.getMetadata()!.mimeType).toBe("image/png");
|
26
|
+
expect(image["256x256"]).not.toBeDefined();
|
27
|
+
expect(image["1024x1024"]).not.toBeDefined();
|
28
|
+
});
|
29
|
+
|
30
|
+
it("should create an image with three sizes", async () => {
|
31
|
+
const imageBlob = new Blob(
|
32
|
+
[Uint8Array.from(White1920, (c) => c.charCodeAt(0))],
|
33
|
+
{ type: "image/png" },
|
34
|
+
);
|
35
|
+
|
36
|
+
const account = await createJazzTestAccount();
|
37
|
+
|
38
|
+
const image = await createImage(imageBlob, { owner: account._owner });
|
39
|
+
expect(image).toBeDefined();
|
40
|
+
|
41
|
+
expect(image.originalSize).toEqual([1920, 400]);
|
42
|
+
expect(image.placeholderDataURL).toBeDefined();
|
43
|
+
expect(image[`256x53`]).toBeDefined();
|
44
|
+
expect(image[`1024x213`]).toBeDefined();
|
45
|
+
expect(image[`1920x400`]).toBeDefined();
|
46
|
+
});
|
47
|
+
|
48
|
+
it("should lose the original size and create image based on maxSize", async () => {
|
49
|
+
const imageBlob = new Blob(
|
50
|
+
[Uint8Array.from(White1920, (c) => c.charCodeAt(0))],
|
51
|
+
{ type: "image/png" },
|
52
|
+
);
|
53
|
+
|
54
|
+
const account = await createJazzTestAccount();
|
55
|
+
|
56
|
+
const image = await createImage(imageBlob, {
|
57
|
+
owner: account._owner,
|
58
|
+
maxSize: 256,
|
59
|
+
});
|
60
|
+
expect(image).toBeDefined();
|
61
|
+
|
62
|
+
expect(image.originalSize).toEqual([256, 53]);
|
63
|
+
expect(image.placeholderDataURL).toBeDefined();
|
64
|
+
expect(image[`256x53`]).toBeDefined();
|
65
|
+
expect(image[`1024x213`]).not.toBeDefined();
|
66
|
+
expect(image[`1920x400`]).not.toBeDefined();
|
67
|
+
});
|
68
|
+
});
|
69
|
+
|
70
|
+
// Image 1920x400
|
71
|
+
const White1920 = atob(
|
72
|
+
"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////2wBDAf//////////////////////////////////////////////////////////////////////////////////////wAARCAGQB4ADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAP/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//2Q==",
|
73
|
+
);
|
@@ -8,7 +8,7 @@ import {
|
|
8
8
|
} from "jazz-tools";
|
9
9
|
import Pica from "pica";
|
10
10
|
|
11
|
-
let
|
11
|
+
let reducer: ImageBlobReduce.ImageBlobReduce | undefined;
|
12
12
|
|
13
13
|
/** @category Image creation */
|
14
14
|
export async function createImage(
|
@@ -18,113 +18,114 @@ export async function createImage(
|
|
18
18
|
maxSize?: 256 | 1024 | 2048;
|
19
19
|
},
|
20
20
|
): Promise<Loaded<typeof ImageDefinition>> {
|
21
|
-
//
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
const
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
});
|
40
|
-
|
41
|
-
const placeholderDataURL = (
|
42
|
-
await Reducer.toCanvas(imageBlobOrFile, { max: 8 })
|
43
|
-
).toDataURL("image/png");
|
21
|
+
// Get the original size of the image
|
22
|
+
const { width: originalWidth, height: originalHeight } =
|
23
|
+
await getImageSize(imageBlobOrFile);
|
24
|
+
|
25
|
+
const highestDimension = Math.max(originalWidth, originalHeight);
|
26
|
+
|
27
|
+
// Calculate the sizes to resize the image to
|
28
|
+
const resizes = [256, 1024, 2048, highestDimension]
|
29
|
+
.filter((s) => s <= (options?.maxSize ?? highestDimension))
|
30
|
+
.toSorted((a, b) => a - b);
|
31
|
+
|
32
|
+
// Get the highest resolution to use as final original size
|
33
|
+
// In case of options.maxSize, it's not the originalWidth/Height
|
34
|
+
const { width: finalWidth, height: finalHeight } = getNewDimensions(
|
35
|
+
originalWidth,
|
36
|
+
originalHeight,
|
37
|
+
resizes.at(-1)!,
|
38
|
+
);
|
44
39
|
|
45
40
|
const imageDefinition = ImageDefinition.create(
|
46
|
-
{
|
47
|
-
originalSize: [originalWidth, originalHeight],
|
48
|
-
placeholderDataURL,
|
49
|
-
},
|
41
|
+
{ originalSize: [finalWidth, finalHeight] },
|
50
42
|
options?.owner,
|
51
43
|
);
|
52
44
|
const owner = imageDefinition._owner;
|
53
45
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
const binaryStream = await FileStream.createFromBlob(max256, owner);
|
68
|
-
|
69
|
-
imageDefinition[`${width}x${height}`] = binaryStream;
|
70
|
-
}
|
71
|
-
|
72
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
73
|
-
|
74
|
-
if (options?.maxSize === 256) return;
|
75
|
-
|
76
|
-
const max1024 = await Reducer.toBlob(imageBlobOrFile, { max: 1024 });
|
77
|
-
|
78
|
-
if (originalWidth > 1024 || originalHeight > 1024) {
|
79
|
-
const width =
|
80
|
-
originalWidth > originalHeight
|
81
|
-
? 1024
|
82
|
-
: Math.round(1024 * (originalWidth / originalHeight));
|
83
|
-
const height =
|
84
|
-
originalHeight > originalWidth
|
85
|
-
? 1024
|
86
|
-
: Math.round(1024 * (originalHeight / originalWidth));
|
87
|
-
|
88
|
-
const binaryStream = await FileStream.createFromBlob(max1024, owner);
|
89
|
-
|
90
|
-
imageDefinition[`${width}x${height}`] = binaryStream;
|
91
|
-
}
|
92
|
-
|
93
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
94
|
-
|
95
|
-
if (options?.maxSize === 1024) return;
|
96
|
-
|
97
|
-
const max2048 = await Reducer.toBlob(imageBlobOrFile, { max: 2048 });
|
46
|
+
// Placeholder 8x8
|
47
|
+
imageDefinition.placeholderDataURL =
|
48
|
+
await getPlaceholderBase64(imageBlobOrFile);
|
49
|
+
|
50
|
+
// Resizes for progressive loading
|
51
|
+
for (let size of resizes) {
|
52
|
+
// Calculate width and height respecting the aspect ratio
|
53
|
+
const { width, height } = getNewDimensions(
|
54
|
+
originalWidth,
|
55
|
+
originalHeight,
|
56
|
+
size,
|
57
|
+
);
|
98
58
|
|
99
|
-
|
100
|
-
const width =
|
101
|
-
originalWidth > originalHeight
|
102
|
-
? 2048
|
103
|
-
: Math.round(2048 * (originalWidth / originalHeight));
|
104
|
-
const height =
|
105
|
-
originalHeight > originalWidth
|
106
|
-
? 2048
|
107
|
-
: Math.round(2048 * (originalHeight / originalWidth));
|
59
|
+
const image = await resize(imageBlobOrFile, width, height);
|
108
60
|
|
109
|
-
|
61
|
+
const binaryStream = await FileStream.createFromBlob(image, owner);
|
62
|
+
imageDefinition[`${width}x${height}`] = binaryStream;
|
63
|
+
}
|
110
64
|
|
111
|
-
|
112
|
-
|
65
|
+
return imageDefinition;
|
66
|
+
}
|
113
67
|
|
114
|
-
|
68
|
+
async function getImageSize(
|
69
|
+
imageBlobOrFile: Blob | File,
|
70
|
+
): Promise<{ width: number; height: number }> {
|
71
|
+
const { width, height } = await new Promise<{
|
72
|
+
width: number;
|
73
|
+
height: number;
|
74
|
+
}>((resolve, reject) => {
|
75
|
+
const img = new Image();
|
76
|
+
img.onload = () => {
|
77
|
+
resolve({ width: img.width, height: img.height });
|
78
|
+
URL.revokeObjectURL(img.src);
|
79
|
+
};
|
80
|
+
img.onerror = () => {
|
81
|
+
reject(new Error("Failed to load image"));
|
82
|
+
URL.revokeObjectURL(img.src);
|
83
|
+
};
|
84
|
+
img.src = URL.createObjectURL(imageBlobOrFile);
|
85
|
+
});
|
115
86
|
|
116
|
-
|
87
|
+
return { width, height };
|
88
|
+
}
|
117
89
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
90
|
+
async function getPlaceholderBase64(
|
91
|
+
imageBlobOrFile: Blob | File,
|
92
|
+
): Promise<string> {
|
93
|
+
// Inizialize Reducer here to not have module side effects
|
94
|
+
if (!reducer) {
|
95
|
+
reducer = new ImageBlobReduce({ pica: new Pica() });
|
96
|
+
}
|
122
97
|
|
123
|
-
|
124
|
-
|
125
|
-
|
98
|
+
const canvas = await reducer.toCanvas(imageBlobOrFile, { max: 8 });
|
99
|
+
return canvas.toDataURL("image/png");
|
100
|
+
}
|
126
101
|
|
127
|
-
|
102
|
+
async function resize(
|
103
|
+
imageBlobOrFile: Blob | File,
|
104
|
+
width: number,
|
105
|
+
height: number,
|
106
|
+
): Promise<Blob> {
|
107
|
+
// Inizialize Reducer here to not have module side effects
|
108
|
+
if (!reducer) {
|
109
|
+
reducer = new ImageBlobReduce({ pica: new Pica() });
|
110
|
+
}
|
128
111
|
|
129
|
-
return
|
112
|
+
return reducer.toBlob(imageBlobOrFile, { max: Math.max(width, height) });
|
130
113
|
}
|
114
|
+
|
115
|
+
const getNewDimensions = (
|
116
|
+
originalWidth: number,
|
117
|
+
originalHeight: number,
|
118
|
+
maxSize: number,
|
119
|
+
) => {
|
120
|
+
const width =
|
121
|
+
originalWidth > originalHeight
|
122
|
+
? maxSize
|
123
|
+
: Math.round(maxSize * (originalWidth / originalHeight));
|
124
|
+
|
125
|
+
const height =
|
126
|
+
originalHeight > originalWidth
|
127
|
+
? maxSize
|
128
|
+
: Math.round(maxSize * (originalHeight / originalWidth));
|
129
|
+
|
130
|
+
return { width, height };
|
131
|
+
};
|
package/vitest.config.ts
CHANGED
@@ -2,9 +2,30 @@ import { defineConfig } from "vitest/config";
|
|
2
2
|
|
3
3
|
export default defineConfig({
|
4
4
|
test: {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
workspace: [
|
6
|
+
{
|
7
|
+
test: {
|
8
|
+
typecheck: {
|
9
|
+
enabled: true,
|
10
|
+
checker: "tsc",
|
11
|
+
},
|
12
|
+
include: ["src/**/*.test.ts"],
|
13
|
+
name: "unit",
|
14
|
+
},
|
15
|
+
},
|
16
|
+
{
|
17
|
+
test: {
|
18
|
+
include: ["src/**/*.test.browser.ts"],
|
19
|
+
name: "browser",
|
20
|
+
browser: {
|
21
|
+
enabled: true,
|
22
|
+
provider: "playwright",
|
23
|
+
headless: true,
|
24
|
+
screenshotFailures: false,
|
25
|
+
instances: [{ browser: "chromium" }],
|
26
|
+
},
|
27
|
+
},
|
28
|
+
},
|
29
|
+
],
|
9
30
|
},
|
10
31
|
});
|