jazz-tools 0.16.6 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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 +42 -46
- package/CHANGELOG.md +12 -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/media/chunk-BBSS3NEY.js +211 -0
- package/dist/media/chunk-BBSS3NEY.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/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 +327 -0
- package/src/media/utils.ts +202 -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/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,195 @@
|
|
1
|
+
import { FileStream } from "jazz-tools";
|
2
|
+
import { createJazzTestAccount } from "jazz-tools/testing";
|
3
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
4
|
+
import { createImageFactory } from "./create-image.js";
|
5
|
+
|
6
|
+
describe("createImage", async () => {
|
7
|
+
const account = await createJazzTestAccount();
|
8
|
+
|
9
|
+
const getImageSize = vi.fn();
|
10
|
+
const getPlaceholderBase64 = vi.fn();
|
11
|
+
const createFileStreamFromSource = vi
|
12
|
+
.fn()
|
13
|
+
.mockResolvedValue(FileStream.create({ owner: account._owner }));
|
14
|
+
const resize = vi.fn();
|
15
|
+
|
16
|
+
const createImage = createImageFactory({
|
17
|
+
getImageSize,
|
18
|
+
getPlaceholderBase64,
|
19
|
+
createFileStreamFromSource,
|
20
|
+
resize,
|
21
|
+
});
|
22
|
+
|
23
|
+
afterEach(() => {
|
24
|
+
vi.clearAllMocks();
|
25
|
+
});
|
26
|
+
|
27
|
+
it("should create a single original image if all settings are off", async () => {
|
28
|
+
const imageBlob = new Blob(
|
29
|
+
[Uint8Array.from(OnePixel, (c) => c.charCodeAt(0))],
|
30
|
+
{ type: "image/png" },
|
31
|
+
);
|
32
|
+
|
33
|
+
getImageSize.mockResolvedValue({ width: 1, height: 1 });
|
34
|
+
|
35
|
+
const image = await createImage(imageBlob, {
|
36
|
+
owner: account._owner,
|
37
|
+
placeholder: false,
|
38
|
+
progressive: false,
|
39
|
+
});
|
40
|
+
|
41
|
+
expect(image).toBeDefined();
|
42
|
+
|
43
|
+
expect(image.originalSize).toEqual([1, 1]);
|
44
|
+
expect(image.placeholderDataURL).not.toBeDefined();
|
45
|
+
expect(image.progressive).toBe(false);
|
46
|
+
expect(image.original).toBeDefined();
|
47
|
+
expect(image[`1x1`]).toBeDefined();
|
48
|
+
expect(image[`1x1`]).toStrictEqual(image.original);
|
49
|
+
});
|
50
|
+
|
51
|
+
it("should create the image with original and placeholder", async () => {
|
52
|
+
const imageBlob = new Blob(
|
53
|
+
[Uint8Array.from(OnePixel, (c) => c.charCodeAt(0))],
|
54
|
+
{ type: "image/png" },
|
55
|
+
);
|
56
|
+
|
57
|
+
getImageSize.mockResolvedValue({ width: 1, height: 1 });
|
58
|
+
getPlaceholderBase64.mockResolvedValue(
|
59
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
|
60
|
+
);
|
61
|
+
|
62
|
+
const image = await createImage(imageBlob, {
|
63
|
+
owner: account._owner,
|
64
|
+
placeholder: "blur",
|
65
|
+
progressive: false,
|
66
|
+
});
|
67
|
+
|
68
|
+
expect(image).toBeDefined();
|
69
|
+
|
70
|
+
expect(image.originalSize).toEqual([1, 1]);
|
71
|
+
expect(image.placeholderDataURL).toBeDefined();
|
72
|
+
expect(image.progressive).toBe(false);
|
73
|
+
expect(image.original).toBeDefined();
|
74
|
+
});
|
75
|
+
|
76
|
+
it("should create a resized image if maxSize is set", async () => {
|
77
|
+
const imageBlob = new Blob(
|
78
|
+
[Uint8Array.from(White1920, (c) => c.charCodeAt(0))],
|
79
|
+
{ type: "image/png" },
|
80
|
+
);
|
81
|
+
|
82
|
+
getImageSize.mockResolvedValue({ width: 1920, height: 400 });
|
83
|
+
resize.mockResolvedValue(new Blob([White1920], { type: "image/png" }));
|
84
|
+
|
85
|
+
const image = await createImage(imageBlob, {
|
86
|
+
owner: account._owner,
|
87
|
+
placeholder: false,
|
88
|
+
progressive: false,
|
89
|
+
maxSize: 256,
|
90
|
+
});
|
91
|
+
|
92
|
+
expect(image).toBeDefined();
|
93
|
+
|
94
|
+
expect(image.originalSize).toEqual([256, 53]);
|
95
|
+
expect(image.placeholderDataURL).not.toBeDefined();
|
96
|
+
expect(image.progressive).toBe(false);
|
97
|
+
expect(image.original).toBeDefined();
|
98
|
+
expect(image[`256x53`]).toStrictEqual(image.original);
|
99
|
+
|
100
|
+
expect(resize).toHaveBeenCalledWith(imageBlob, 256, 53);
|
101
|
+
});
|
102
|
+
|
103
|
+
it("should not resize the original image if maxSize is higher than the original size", async () => {
|
104
|
+
const imageBlob = new Blob(
|
105
|
+
[Uint8Array.from(OnePixel, (c) => c.charCodeAt(0))],
|
106
|
+
{ type: "image/png" },
|
107
|
+
);
|
108
|
+
|
109
|
+
getImageSize.mockResolvedValue({ width: 1, height: 1 });
|
110
|
+
|
111
|
+
const image = await createImage(imageBlob, {
|
112
|
+
owner: account._owner,
|
113
|
+
placeholder: false,
|
114
|
+
progressive: false,
|
115
|
+
maxSize: 256,
|
116
|
+
});
|
117
|
+
|
118
|
+
expect(image).toBeDefined();
|
119
|
+
|
120
|
+
expect(image.originalSize).toEqual([1, 1]);
|
121
|
+
expect(image.placeholderDataURL).not.toBeDefined();
|
122
|
+
expect(image.progressive).toBe(false);
|
123
|
+
expect(image.original).toBeDefined();
|
124
|
+
expect(image[`1x1`]).toStrictEqual(image.original);
|
125
|
+
|
126
|
+
expect(resize).not.toHaveBeenCalled();
|
127
|
+
});
|
128
|
+
|
129
|
+
it("should create an image with intermediate sizes for progressive loading", async () => {
|
130
|
+
const imageBlob = new Blob(
|
131
|
+
[Uint8Array.from(White1920, (c) => c.charCodeAt(0))],
|
132
|
+
{ type: "image/png" },
|
133
|
+
);
|
134
|
+
|
135
|
+
getImageSize.mockResolvedValue({ width: 1920, height: 400 });
|
136
|
+
resize.mockResolvedValue(new Blob([White1920], { type: "image/png" }));
|
137
|
+
|
138
|
+
const image = await createImage(imageBlob, {
|
139
|
+
owner: account._owner,
|
140
|
+
progressive: true,
|
141
|
+
placeholder: false,
|
142
|
+
});
|
143
|
+
|
144
|
+
expect(image).toBeDefined();
|
145
|
+
expect(image.originalSize).toEqual([1920, 400]);
|
146
|
+
expect(image.placeholderDataURL).not.toBeDefined();
|
147
|
+
|
148
|
+
expect(image[`256x53`]).toBeDefined();
|
149
|
+
expect(image[`1024x213`]).toBeDefined();
|
150
|
+
expect(image[`2048x427`]).not.toBeDefined();
|
151
|
+
|
152
|
+
expect(resize).toHaveBeenCalledWith(imageBlob, 256, 53);
|
153
|
+
expect(resize).toHaveBeenCalledWith(imageBlob, 1024, 213);
|
154
|
+
expect(resize).not.toHaveBeenCalledWith(imageBlob, 2048, 427);
|
155
|
+
});
|
156
|
+
|
157
|
+
it("should lose the original size and create image based on maxSize", async () => {
|
158
|
+
const imageBlob = new Blob(
|
159
|
+
[Uint8Array.from(White1920, (c) => c.charCodeAt(0))],
|
160
|
+
{ type: "image/png" },
|
161
|
+
);
|
162
|
+
|
163
|
+
getImageSize.mockResolvedValue({ width: 1920, height: 400 });
|
164
|
+
resize.mockResolvedValue(new Blob([White1920], { type: "image/png" }));
|
165
|
+
|
166
|
+
const image = await createImage(imageBlob, {
|
167
|
+
owner: account._owner,
|
168
|
+
maxSize: 256,
|
169
|
+
placeholder: false,
|
170
|
+
progressive: true,
|
171
|
+
});
|
172
|
+
|
173
|
+
expect(image).toBeDefined();
|
174
|
+
|
175
|
+
expect(image.originalSize).toEqual([256, 53]);
|
176
|
+
expect(image.placeholderDataURL).not.toBeDefined();
|
177
|
+
expect(image[`256x53`]).toBeDefined();
|
178
|
+
expect(image[`1024x213`]).not.toBeDefined();
|
179
|
+
expect(image[`2048x427`]).not.toBeDefined();
|
180
|
+
|
181
|
+
expect(resize).toHaveBeenCalledWith(imageBlob, 256, 53);
|
182
|
+
expect(resize).not.toHaveBeenCalledWith(imageBlob, 1024, 213);
|
183
|
+
expect(resize).not.toHaveBeenCalledWith(imageBlob, 2048, 427);
|
184
|
+
});
|
185
|
+
});
|
186
|
+
|
187
|
+
// 1x1 png
|
188
|
+
const OnePixel = atob(
|
189
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
|
190
|
+
);
|
191
|
+
|
192
|
+
// Image 1920x400
|
193
|
+
const White1920 = atob(
|
194
|
+
"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////2wBDAf//////////////////////////////////////////////////////////////////////////////////////wAARCAGQB4ADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAP/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//2Q==",
|
195
|
+
);
|
@@ -0,0 +1,180 @@
|
|
1
|
+
import {
|
2
|
+
Account,
|
3
|
+
FileStream,
|
4
|
+
Group,
|
5
|
+
ImageDefinition,
|
6
|
+
type Loaded,
|
7
|
+
} from "jazz-tools";
|
8
|
+
|
9
|
+
export type SourceType = Blob | File | string;
|
10
|
+
|
11
|
+
export type CreateImageOptions = {
|
12
|
+
/** The owner of the image. Can be either a Group or Account. If not specified, the current user will be the owner. */
|
13
|
+
owner?: Group | Account;
|
14
|
+
/**
|
15
|
+
* Controls placeholder generation for the image.
|
16
|
+
* - `"blur"`: Generates a blurred placeholder image (default)
|
17
|
+
* - `false`: No placeholder is generated
|
18
|
+
* @default "blur"
|
19
|
+
*/
|
20
|
+
placeholder?: "blur" | false;
|
21
|
+
/**
|
22
|
+
* Maximum size constraint for the image. The image will be resized to fit within this size while maintaining aspect ratio.
|
23
|
+
* If the image is smaller than maxSize in both dimensions, no resizing occurs.
|
24
|
+
* @example 1024 // Resizes image to fit within 1024px in the largest dimension
|
25
|
+
*/
|
26
|
+
maxSize?: number; // | [number, number];
|
27
|
+
/**
|
28
|
+
* The progressive loading pattern is a technique that allows images to load incrementally, starting with a small version and gradually replacing it with a larger version as it becomes available.
|
29
|
+
* This is useful for improving the user experience by showing a placeholder while the image is loading.
|
30
|
+
*
|
31
|
+
* Passing progressive: true to createImage() will create internal smaller versions of the image for future uses.
|
32
|
+
*
|
33
|
+
* @default false
|
34
|
+
*/
|
35
|
+
progressive?: boolean;
|
36
|
+
};
|
37
|
+
|
38
|
+
export type CreateImageImpl = {
|
39
|
+
createFileStreamFromSource: (
|
40
|
+
imageBlobOrFile: SourceType,
|
41
|
+
owner?: Group | Account,
|
42
|
+
) => Promise<FileStream>;
|
43
|
+
getImageSize: (
|
44
|
+
imageBlobOrFile: SourceType,
|
45
|
+
) => Promise<{ width: number; height: number }>;
|
46
|
+
getPlaceholderBase64: (imageBlobOrFile: SourceType) => Promise<string>;
|
47
|
+
resize: (
|
48
|
+
imageBlobOrFile: SourceType,
|
49
|
+
width: number,
|
50
|
+
height: number,
|
51
|
+
) => Promise<Blob | string>;
|
52
|
+
};
|
53
|
+
|
54
|
+
export function createImageFactory(impl: CreateImageImpl) {
|
55
|
+
return (source: SourceType, options: CreateImageOptions) =>
|
56
|
+
createImage(source, options, impl);
|
57
|
+
}
|
58
|
+
|
59
|
+
async function createImage(
|
60
|
+
imageBlobOrFile: SourceType,
|
61
|
+
options: CreateImageOptions,
|
62
|
+
impl: CreateImageImpl,
|
63
|
+
): Promise<Loaded<typeof ImageDefinition, { $each: true }>> {
|
64
|
+
// Get the original size of the image
|
65
|
+
const { width: originalWidth, height: originalHeight } =
|
66
|
+
await impl.getImageSize(imageBlobOrFile);
|
67
|
+
|
68
|
+
const def: {
|
69
|
+
originalSize: [number, number];
|
70
|
+
progressive: boolean;
|
71
|
+
placeholderDataURL: string | undefined;
|
72
|
+
original?: FileStream;
|
73
|
+
files: Record<string, FileStream>;
|
74
|
+
} = {
|
75
|
+
originalSize: [originalWidth, originalHeight],
|
76
|
+
progressive: false,
|
77
|
+
placeholderDataURL: undefined,
|
78
|
+
files: {},
|
79
|
+
};
|
80
|
+
|
81
|
+
// Placeholder
|
82
|
+
if (options?.placeholder === "blur") {
|
83
|
+
def.placeholderDataURL = await impl.getPlaceholderBase64(imageBlobOrFile);
|
84
|
+
}
|
85
|
+
|
86
|
+
/**
|
87
|
+
* Original
|
88
|
+
*
|
89
|
+
* Save the original image.
|
90
|
+
* If the maxSize is set, resize the image to the maxSize if needed
|
91
|
+
*/
|
92
|
+
if (options?.maxSize === undefined) {
|
93
|
+
def.original = await impl.createFileStreamFromSource(
|
94
|
+
imageBlobOrFile,
|
95
|
+
options?.owner,
|
96
|
+
);
|
97
|
+
def.files[`${originalWidth}x${originalHeight}`] = def.original;
|
98
|
+
} else if (
|
99
|
+
options?.maxSize >= originalWidth &&
|
100
|
+
options?.maxSize >= originalHeight
|
101
|
+
) {
|
102
|
+
// no resizes required, just return the original image
|
103
|
+
def.original = await impl.createFileStreamFromSource(
|
104
|
+
imageBlobOrFile,
|
105
|
+
options?.owner,
|
106
|
+
);
|
107
|
+
def.files[`${originalWidth}x${originalHeight}`] = def.original;
|
108
|
+
} else {
|
109
|
+
const { width, height } = getNewDimensions(
|
110
|
+
originalWidth,
|
111
|
+
originalHeight,
|
112
|
+
options.maxSize,
|
113
|
+
);
|
114
|
+
|
115
|
+
const blob = await impl.resize(imageBlobOrFile, width, height);
|
116
|
+
def.originalSize = [width, height];
|
117
|
+
def.original = await impl.createFileStreamFromSource(blob, options?.owner);
|
118
|
+
def.files[`${width}x${height}`] = def.original;
|
119
|
+
}
|
120
|
+
|
121
|
+
const imageCoValue = ImageDefinition.create(
|
122
|
+
{
|
123
|
+
originalSize: def.originalSize,
|
124
|
+
progressive: def.progressive,
|
125
|
+
placeholderDataURL: def.placeholderDataURL,
|
126
|
+
original: def.original,
|
127
|
+
...def.files,
|
128
|
+
},
|
129
|
+
options?.owner,
|
130
|
+
);
|
131
|
+
|
132
|
+
/**
|
133
|
+
* Progressive loading
|
134
|
+
*
|
135
|
+
* Save a set of resized images using three sizes: 256, 1024, 2048
|
136
|
+
*
|
137
|
+
* On the client side, the image will be loaded progressively, starting from the smallest size and increasing the size until the original size is reached.
|
138
|
+
*/
|
139
|
+
if (options?.progressive) {
|
140
|
+
imageCoValue.progressive = true;
|
141
|
+
|
142
|
+
const resizes = ([256, 1024, 2048] as const).filter(
|
143
|
+
(s) =>
|
144
|
+
s <
|
145
|
+
Math.max(imageCoValue.originalSize[0], imageCoValue.originalSize[1]),
|
146
|
+
);
|
147
|
+
|
148
|
+
for (const size of resizes) {
|
149
|
+
const { width, height } = getNewDimensions(
|
150
|
+
originalWidth,
|
151
|
+
originalHeight,
|
152
|
+
size,
|
153
|
+
);
|
154
|
+
|
155
|
+
const blob = await impl.resize(imageBlobOrFile, width, height);
|
156
|
+
imageCoValue[`${width}x${height}`] =
|
157
|
+
await impl.createFileStreamFromSource(blob, options?.owner);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
return imageCoValue;
|
162
|
+
}
|
163
|
+
|
164
|
+
const getNewDimensions = (
|
165
|
+
originalWidth: number,
|
166
|
+
originalHeight: number,
|
167
|
+
maxSize: number,
|
168
|
+
) => {
|
169
|
+
if (originalWidth > originalHeight) {
|
170
|
+
return {
|
171
|
+
width: maxSize,
|
172
|
+
height: Math.round(maxSize * (originalHeight / originalWidth)),
|
173
|
+
};
|
174
|
+
}
|
175
|
+
|
176
|
+
return {
|
177
|
+
width: Math.round(maxSize * (originalWidth / originalHeight)),
|
178
|
+
height: maxSize,
|
179
|
+
};
|
180
|
+
};
|
@@ -0,0 +1,150 @@
|
|
1
|
+
import { Account, FileStream, Group, ImageDefinition } from "jazz-tools";
|
2
|
+
import { CreateImageOptions, createImageFactory } from "./create-image.js";
|
3
|
+
|
4
|
+
export { highestResAvailable, loadImage, loadImageBySize } from "./utils.js";
|
5
|
+
|
6
|
+
export { createImageFactory };
|
7
|
+
|
8
|
+
export async function createImage(
|
9
|
+
imageBlobOrFile: Blob | File | string,
|
10
|
+
options?: CreateImageOptions,
|
11
|
+
) {
|
12
|
+
return createImageFactory({
|
13
|
+
createFileStreamFromSource,
|
14
|
+
getImageSize,
|
15
|
+
getPlaceholderBase64,
|
16
|
+
resize,
|
17
|
+
})(imageBlobOrFile, options || {});
|
18
|
+
}
|
19
|
+
|
20
|
+
// Image Manipulations
|
21
|
+
async function createFileStreamFromSource(
|
22
|
+
imageBlobOrFile: Blob | File | string,
|
23
|
+
owner?: Account | Group,
|
24
|
+
): Promise<FileStream> {
|
25
|
+
if (typeof imageBlobOrFile === "string") {
|
26
|
+
throw new Error(
|
27
|
+
"createFileStreamFromSource(string) is not supported on this platform",
|
28
|
+
);
|
29
|
+
}
|
30
|
+
|
31
|
+
return FileStream.createFromBlob(imageBlobOrFile, owner);
|
32
|
+
}
|
33
|
+
|
34
|
+
// using createImageBitmap is ~10x slower than Image object
|
35
|
+
// Image object: 640 milliseconds
|
36
|
+
// createImageBitmap: 8128 milliseconds
|
37
|
+
function getImageFromBlob(blob: Blob): Promise<HTMLImageElement> {
|
38
|
+
return new Promise((resolve, reject) => {
|
39
|
+
const img = new Image();
|
40
|
+
img.onload = () => {
|
41
|
+
resolve(img);
|
42
|
+
URL.revokeObjectURL(img.src);
|
43
|
+
};
|
44
|
+
img.onerror = () => {
|
45
|
+
reject(new Error("Failed to load image"));
|
46
|
+
URL.revokeObjectURL(img.src);
|
47
|
+
};
|
48
|
+
img.src = URL.createObjectURL(blob);
|
49
|
+
});
|
50
|
+
}
|
51
|
+
|
52
|
+
async function getImageSize(
|
53
|
+
imageBlobOrFile: Blob | File | string,
|
54
|
+
): Promise<{ width: number; height: number }> {
|
55
|
+
if (typeof imageBlobOrFile === "string") {
|
56
|
+
throw new Error("getImageSize(string) is not supported on browser");
|
57
|
+
}
|
58
|
+
|
59
|
+
const image = await getImageFromBlob(imageBlobOrFile);
|
60
|
+
|
61
|
+
return { width: image.width, height: image.height };
|
62
|
+
}
|
63
|
+
|
64
|
+
async function getPlaceholderBase64(
|
65
|
+
imageBlobOrFile: Blob | File | string,
|
66
|
+
): Promise<string> {
|
67
|
+
if (typeof imageBlobOrFile === "string") {
|
68
|
+
throw new Error("getPlaceholderBase64(string) is not supported on browser");
|
69
|
+
}
|
70
|
+
|
71
|
+
const image = await getImageFromBlob(imageBlobOrFile);
|
72
|
+
|
73
|
+
const { width, height } = resizeDimensionsKeepingAspectRatio(
|
74
|
+
image.width,
|
75
|
+
image.height,
|
76
|
+
8,
|
77
|
+
);
|
78
|
+
|
79
|
+
const canvas = document.createElement("canvas");
|
80
|
+
canvas.width = width;
|
81
|
+
canvas.height = height;
|
82
|
+
|
83
|
+
const ctx = canvas.getContext("2d");
|
84
|
+
|
85
|
+
if (!ctx) {
|
86
|
+
throw new Error("Failed to get context");
|
87
|
+
}
|
88
|
+
|
89
|
+
ctx.drawImage(image, 0, 0, width, height);
|
90
|
+
|
91
|
+
return canvas.toDataURL("image/png");
|
92
|
+
}
|
93
|
+
|
94
|
+
const resizeDimensionsKeepingAspectRatio = (
|
95
|
+
width: number,
|
96
|
+
height: number,
|
97
|
+
maxSize: number,
|
98
|
+
): { width: number; height: number } => {
|
99
|
+
if (width <= maxSize && height <= maxSize) {
|
100
|
+
return { width, height };
|
101
|
+
}
|
102
|
+
|
103
|
+
const aspectRatio = width / height;
|
104
|
+
|
105
|
+
if (width >= height) {
|
106
|
+
return { width: maxSize, height: Math.round(maxSize / aspectRatio) };
|
107
|
+
} else {
|
108
|
+
return { width: Math.round(maxSize * aspectRatio), height: maxSize };
|
109
|
+
}
|
110
|
+
};
|
111
|
+
|
112
|
+
async function resize(
|
113
|
+
imageBlobOrFile: Blob | File | string,
|
114
|
+
width: number,
|
115
|
+
height: number,
|
116
|
+
): Promise<Blob> {
|
117
|
+
if (typeof imageBlobOrFile === "string") {
|
118
|
+
throw new Error("resize(string) is not supported on browser");
|
119
|
+
}
|
120
|
+
|
121
|
+
const mimeType = imageBlobOrFile.type;
|
122
|
+
|
123
|
+
const image = await getImageFromBlob(imageBlobOrFile);
|
124
|
+
|
125
|
+
const canvas = document.createElement("canvas");
|
126
|
+
canvas.width = width;
|
127
|
+
canvas.height = height;
|
128
|
+
|
129
|
+
const ctx = canvas.getContext("2d");
|
130
|
+
|
131
|
+
if (!ctx) {
|
132
|
+
throw new Error("Failed to get context");
|
133
|
+
}
|
134
|
+
|
135
|
+
ctx.drawImage(image, 0, 0, width, height);
|
136
|
+
|
137
|
+
return new Promise<Blob>((resolve, reject) => {
|
138
|
+
canvas.toBlob(
|
139
|
+
(blob) => {
|
140
|
+
if (!blob) {
|
141
|
+
reject(new Error("Failed to convert canvas to blob"));
|
142
|
+
return;
|
143
|
+
}
|
144
|
+
resolve(blob);
|
145
|
+
},
|
146
|
+
mimeType,
|
147
|
+
0.8,
|
148
|
+
);
|
149
|
+
});
|
150
|
+
}
|
@@ -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
|
+
}
|