@akinon/pz-virtual-try-on 2.0.40-rc.0 → 2.0.40
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/CHANGELOG.md +6 -31
- package/jest.config.js +7 -0
- package/package.json +5 -2
- package/src/utils/image-compression.test.ts +26 -0
- package/src/utils/image-compression.ts +36 -13
- package/src/utils/index.ts +2 -1
- package/src/views/basket-async-modal.tsx +3 -3
- package/src/views/virtual-try-on-multiple-result-modal.tsx +2 -5
- package/src/views/virtual-try-on-result-modal.tsx +2 -2
- package/src/views/virtual-try-on-upload-modal.tsx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,36 +1,11 @@
|
|
|
1
1
|
# @akinon/pz-virtual-try-on
|
|
2
2
|
|
|
3
|
-
## 2.0.40
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
- Updated dependencies [324f97d5]
|
|
10
|
-
- Updated dependencies [51ea0688]
|
|
11
|
-
- Updated dependencies
|
|
12
|
-
- Updated dependencies [2fea4143]
|
|
13
|
-
- Updated dependencies [b55acb768]
|
|
14
|
-
- Updated dependencies [760258c1]
|
|
15
|
-
- Updated dependencies [143be2b9d]
|
|
16
|
-
- Updated dependencies [7889b08f]
|
|
17
|
-
- Updated dependencies [9f8cd3bc5]
|
|
18
|
-
- Updated dependencies [bfafa3f4]
|
|
19
|
-
- Updated dependencies [57d7eb30]
|
|
20
|
-
- Updated dependencies [d99a6a7d5]
|
|
21
|
-
- Updated dependencies [9db81a71]
|
|
22
|
-
- Updated dependencies [591e345e1]
|
|
23
|
-
- Updated dependencies [4de5303c5]
|
|
24
|
-
- Updated dependencies [95b139dc1]
|
|
25
|
-
- Updated dependencies [1d00f2d0]
|
|
26
|
-
- Updated dependencies [4ac7b2a1]
|
|
27
|
-
- Updated dependencies [e9598c71]
|
|
28
|
-
- Updated dependencies [4998a963]
|
|
29
|
-
- Updated dependencies [804d2bd6]
|
|
30
|
-
- Updated dependencies [3909d3224]
|
|
31
|
-
- Updated dependencies [6a3d8a63]
|
|
32
|
-
- Updated dependencies [e18836b2]
|
|
33
|
-
- @akinon/next@2.0.40-rc.0
|
|
3
|
+
## 2.0.40
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7a0e0c17: ZERO-4720: Preserve uploaded and generated image proportions during resizing and display.
|
|
8
|
+
- @akinon/next@2.0.40
|
|
34
9
|
|
|
35
10
|
## 2.0.39
|
|
36
11
|
|
package/jest.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/pz-virtual-try-on",
|
|
3
|
-
"version": "2.0.40
|
|
3
|
+
"version": "2.0.40",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest"
|
|
8
|
+
},
|
|
6
9
|
"peerDependencies": {
|
|
7
10
|
"next-auth": "^5.0.0-beta",
|
|
8
11
|
"react": "^18.0.0 || ^19.0.0",
|
|
9
12
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
10
13
|
},
|
|
11
14
|
"dependencies": {
|
|
12
|
-
"@akinon/next": "2.0.40
|
|
15
|
+
"@akinon/next": "2.0.40",
|
|
13
16
|
"clsx": "^2.0.0",
|
|
14
17
|
"tailwind-merge": "^2.0.0",
|
|
15
18
|
"react-image-crop": "^11.0.5",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getScaledImageDimensions } from './image-compression';
|
|
2
|
+
|
|
3
|
+
describe('getScaledImageDimensions', () => {
|
|
4
|
+
it.each([
|
|
5
|
+
[4000, 3000, 2048, 2048, 1536],
|
|
6
|
+
[3000, 4000, 2048, 1536, 2048],
|
|
7
|
+
[1200, 1600, 2048, 1200, 1600]
|
|
8
|
+
])(
|
|
9
|
+
'preserves the aspect ratio for %sx%s images',
|
|
10
|
+
(width, height, maxDimension, expectedWidth, expectedHeight) => {
|
|
11
|
+
const result = getScaledImageDimensions(width, height, maxDimension);
|
|
12
|
+
|
|
13
|
+
expect(result).toEqual({
|
|
14
|
+
width: expectedWidth,
|
|
15
|
+
height: expectedHeight
|
|
16
|
+
});
|
|
17
|
+
expect(result.width / result.height).toBeCloseTo(width / height, 5);
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
it('rejects invalid dimensions before drawing to canvas', () => {
|
|
22
|
+
expect(() => getScaledImageDimensions(0, 1000, 2048)).toThrow(
|
|
23
|
+
'Image dimensions must be positive'
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -1,26 +1,42 @@
|
|
|
1
|
+
export const getScaledImageDimensions = (
|
|
2
|
+
width: number,
|
|
3
|
+
height: number,
|
|
4
|
+
maxDimension: number
|
|
5
|
+
): { width: number; height: number } => {
|
|
6
|
+
if (width <= 0 || height <= 0 || maxDimension <= 0) {
|
|
7
|
+
throw new Error('Image dimensions must be positive');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const scale = Math.min(1, maxDimension / Math.max(width, height));
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
width: Math.max(1, Math.round(width * scale)),
|
|
14
|
+
height: Math.max(1, Math.round(height * scale))
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
1
18
|
export const compressImageToUnder1MB = (file: File): Promise<File> => {
|
|
2
19
|
return new Promise((resolve, reject) => {
|
|
3
20
|
const canvas = document.createElement('canvas');
|
|
4
21
|
const ctx = canvas.getContext('2d');
|
|
5
22
|
const img = new Image();
|
|
23
|
+
const imageUrl = URL.createObjectURL(file);
|
|
6
24
|
|
|
7
25
|
img.onload = () => {
|
|
8
26
|
const MAX_SIZE = 1048576;
|
|
27
|
+
URL.revokeObjectURL(imageUrl);
|
|
9
28
|
|
|
10
29
|
if (!ctx) {
|
|
11
30
|
reject(new Error('Canvas context not available'));
|
|
12
31
|
return;
|
|
13
32
|
}
|
|
14
33
|
|
|
15
|
-
let { width, height } = img;
|
|
16
|
-
let quality = 0.9;
|
|
17
|
-
|
|
18
34
|
const maxDimension = 2048;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
const { width, height } = getScaledImageDimensions(
|
|
36
|
+
img.naturalWidth,
|
|
37
|
+
img.naturalHeight,
|
|
38
|
+
maxDimension
|
|
39
|
+
);
|
|
24
40
|
|
|
25
41
|
canvas.width = width;
|
|
26
42
|
canvas.height = height;
|
|
@@ -55,12 +71,14 @@ export const compressImageToUnder1MB = (file: File): Promise<File> => {
|
|
|
55
71
|
);
|
|
56
72
|
};
|
|
57
73
|
|
|
58
|
-
attemptCompression(
|
|
74
|
+
attemptCompression(0.9);
|
|
59
75
|
};
|
|
60
76
|
|
|
61
|
-
img.onerror = () =>
|
|
77
|
+
img.onerror = () => {
|
|
78
|
+
URL.revokeObjectURL(imageUrl);
|
|
62
79
|
reject(new Error('Failed to load image for compression'));
|
|
63
|
-
|
|
80
|
+
};
|
|
81
|
+
img.src = imageUrl;
|
|
64
82
|
});
|
|
65
83
|
};
|
|
66
84
|
|
|
@@ -69,8 +87,10 @@ export const convertWebPToJPEG = (file: File): Promise<File> => {
|
|
|
69
87
|
const canvas = document.createElement('canvas');
|
|
70
88
|
const ctx = canvas.getContext('2d');
|
|
71
89
|
const img = new Image();
|
|
90
|
+
const imageUrl = URL.createObjectURL(file);
|
|
72
91
|
|
|
73
92
|
img.onload = () => {
|
|
93
|
+
URL.revokeObjectURL(imageUrl);
|
|
74
94
|
canvas.width = img.naturalWidth;
|
|
75
95
|
canvas.height = img.naturalHeight;
|
|
76
96
|
|
|
@@ -104,7 +124,10 @@ export const convertWebPToJPEG = (file: File): Promise<File> => {
|
|
|
104
124
|
);
|
|
105
125
|
};
|
|
106
126
|
|
|
107
|
-
img.onerror = () =>
|
|
108
|
-
|
|
127
|
+
img.onerror = () => {
|
|
128
|
+
URL.revokeObjectURL(imageUrl);
|
|
129
|
+
reject(new Error('Failed to load image'));
|
|
130
|
+
};
|
|
131
|
+
img.src = imageUrl;
|
|
109
132
|
});
|
|
110
133
|
};
|
package/src/utils/index.ts
CHANGED
|
@@ -276,7 +276,7 @@ export function BasketAsyncModal({
|
|
|
276
276
|
src={hookData.uploadedImage}
|
|
277
277
|
alt="Uploaded"
|
|
278
278
|
className={twMerge(
|
|
279
|
-
'rounded-[4px] w-auto',
|
|
279
|
+
'rounded-[4px] max-w-full h-auto object-contain',
|
|
280
280
|
settings?.customStyles
|
|
281
281
|
?.basketAsyncModalProcessingImage
|
|
282
282
|
)}
|
|
@@ -350,7 +350,7 @@ export function BasketAsyncModal({
|
|
|
350
350
|
src={hookData.uploadedImage}
|
|
351
351
|
alt="Uploaded"
|
|
352
352
|
className={twMerge(
|
|
353
|
-
'w-full rounded-[4px]',
|
|
353
|
+
'w-full h-auto object-contain rounded-[4px]',
|
|
354
354
|
settings?.customStyles?.basketAsyncModalErrorImage
|
|
355
355
|
)}
|
|
356
356
|
/>
|
|
@@ -515,7 +515,7 @@ export function BasketAsyncModal({
|
|
|
515
515
|
src={result.generated}
|
|
516
516
|
alt={`Result ${index + 1}`}
|
|
517
517
|
className={twMerge(
|
|
518
|
-
'w-full h-full group-hover:scale-105 transition-transform duration-200',
|
|
518
|
+
'w-full h-full object-contain group-hover:scale-105 transition-transform duration-200',
|
|
519
519
|
settings?.customStyles
|
|
520
520
|
?.basketAsyncModalResultImage
|
|
521
521
|
)}
|
|
@@ -9,7 +9,6 @@ import type {
|
|
|
9
9
|
VirtualTryOnPluginSettings,
|
|
10
10
|
VirtualTryOnResponse
|
|
11
11
|
} from '../types';
|
|
12
|
-
import { Image } from '@akinon/next/components/image';
|
|
13
12
|
import { useSubmitVirtualTryOnFeedbackMutation } from '../data/endpoints';
|
|
14
13
|
import { parseVirtualTryOnError } from '../utils';
|
|
15
14
|
import { useLocalization } from '@akinon/next/hooks';
|
|
@@ -398,12 +397,10 @@ export function VirtualTryOnMultipleResultModal({
|
|
|
398
397
|
</p>
|
|
399
398
|
</div>
|
|
400
399
|
) : (
|
|
401
|
-
<
|
|
400
|
+
<img
|
|
402
401
|
src={result.generated}
|
|
403
402
|
alt={`Try-on result for ${product.name}`}
|
|
404
|
-
|
|
405
|
-
aspectRatio={3 / 4}
|
|
406
|
-
sizes="(max-width: 768px) 100vw, 33vw"
|
|
403
|
+
className="absolute inset-0 w-full h-full object-contain"
|
|
407
404
|
/>
|
|
408
405
|
)}
|
|
409
406
|
</div>
|
|
@@ -408,7 +408,7 @@ export function VirtualTryOnResultModal({
|
|
|
408
408
|
imageUrl: tryOnResult.generated,
|
|
409
409
|
alt: 'Virtual try-on result',
|
|
410
410
|
className: twMerge(
|
|
411
|
-
'w-full rounded-[4px]',
|
|
411
|
+
'w-full h-auto object-contain rounded-[4px]',
|
|
412
412
|
settings?.customStyles?.resultImage
|
|
413
413
|
)
|
|
414
414
|
}
|
|
@@ -418,7 +418,7 @@ export function VirtualTryOnResultModal({
|
|
|
418
418
|
src={tryOnResult.generated}
|
|
419
419
|
alt="Virtual try-on result"
|
|
420
420
|
className={twMerge(
|
|
421
|
-
'w-full rounded-[4px]',
|
|
421
|
+
'w-full h-auto object-contain rounded-[4px]',
|
|
422
422
|
settings?.customStyles?.resultImage
|
|
423
423
|
)}
|
|
424
424
|
/>
|
|
@@ -1034,7 +1034,7 @@ export function VirtualTryOnUploadModal({
|
|
|
1034
1034
|
src={uploadedImage || ''}
|
|
1035
1035
|
alt="Uploaded image"
|
|
1036
1036
|
className={twMerge(
|
|
1037
|
-
'rounded-[4px] w-auto pointer-events-none select-none',
|
|
1037
|
+
'rounded-[4px] max-w-full h-auto object-contain pointer-events-none select-none',
|
|
1038
1038
|
settings?.customStyles?.cropImage
|
|
1039
1039
|
)}
|
|
1040
1040
|
/>
|