Skip to content

Commit eb3d731

Browse files
committed
fix: preview images not matching
1 parent 5487088 commit eb3d731

1 file changed

Lines changed: 92 additions & 154 deletions

File tree

src/app/(tools)/size-compressor/compressor-tool.tsx

Lines changed: 92 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { useState, type ChangeEvent, useEffect } from "react";
44
export default function ImageSizeCompressor() {
55
const [images, setImages] = useState<File[]>([]);
66
const [quality, setQuality] = useState(0.8);
7-
const [previews, setPreviews] = useState<string[]>([]);
87
const [compressedPreview, setCompressedPreview] = useState<string | null>(
98
null,
109
);
@@ -20,112 +19,76 @@ export default function ImageSizeCompressor() {
2019
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
2120
}
2221

23-
function handleImageUpload(e: ChangeEvent<HTMLInputElement>) {
24-
if (!e.target.files) return;
22+
async function compressImage(image: File, quality: number): Promise<File> {
23+
return new Promise((resolve, reject) => {
24+
const img = new Image();
25+
const imageUrl = URL.createObjectURL(image);
26+
27+
img.onload = () => {
28+
const canvas = document.createElement("canvas");
29+
let width = img.width;
30+
let height = img.height;
31+
const maxDimension = 1920;
32+
33+
if (width > maxDimension || height > maxDimension) {
34+
if (width > height) {
35+
height = (height / width) * maxDimension;
36+
width = maxDimension;
37+
} else {
38+
width = (width / height) * maxDimension;
39+
height = maxDimension;
40+
}
41+
}
2542

26-
const newFiles = Array.from(e.target.files);
27-
setImages((prev) => [...prev, ...newFiles]);
43+
canvas.width = width;
44+
canvas.height = height;
45+
46+
const ctx = canvas.getContext("2d");
47+
if (!ctx) return reject(new Error("Could not get canvas context"));
48+
49+
ctx.drawImage(img, 0, 0, width, height);
2850

29-
newFiles.forEach((file) => {
30-
const reader = new FileReader();
31-
reader.onloadend = () => {
32-
setPreviews((prev) => [...prev, reader.result as string]);
51+
canvas.toBlob(
52+
(blob) => {
53+
if (!blob) return reject(new Error("Could not create blob"));
54+
55+
const compressedFile = new File([blob], image.name, {
56+
type: "image/jpeg",
57+
lastModified: Date.now(),
58+
});
59+
60+
resolve(compressedFile);
61+
},
62+
"image/jpeg",
63+
quality,
64+
);
3365
};
34-
reader.readAsDataURL(file);
35-
});
3666

37-
if (newFiles[0]) {
38-
setOriginalSize(formatFileSize(newFiles[0].size));
39-
}
67+
img.onerror = () => reject(new Error("Could not load image"));
68+
img.src = imageUrl;
69+
});
4070
}
4171

42-
function removeImage(index: number) {
43-
setImages((prev) => prev.filter((_, i) => i !== index));
44-
setPreviews((prev) => prev.filter((_, i) => i !== index));
45-
if (index === 0) {
46-
setCompressedPreview(null);
47-
setOriginalSize("");
48-
setCompressedSize("");
49-
}
72+
function handleImageUpload(e: ChangeEvent<HTMLInputElement>) {
73+
if (!e.target.files) return;
74+
75+
const newFiles = Array.from(e.target.files);
76+
setImages((prev) => [...prev, ...newFiles]);
5077
}
5178

5279
useEffect(() => {
53-
let isMounted = true;
80+
if (images[0] === undefined) return;
81+
setOriginalSize(formatFileSize(images[0].size));
5482

5583
async function generateCompressedPreview() {
56-
if (images[0] === undefined) {
57-
setCompressedPreview(null);
58-
setCompressedSize("");
59-
return;
60-
}
61-
84+
if (images[0] === undefined) return;
85+
setIsCompressing(true);
6286
try {
63-
// Just a loading state
64-
setIsCompressing(true);
65-
66-
// Using the canvas
67-
68-
// Create an image element to load the original image
69-
const img = new Image();
70-
const imageUrl = URL.createObjectURL(images[0]);
71-
72-
img.onload = () => {
73-
// Create canvas
74-
const canvas = document.createElement("canvas");
75-
76-
// Calculate new dimensions while maintaining aspect ratio
77-
let width = img.width;
78-
let height = img.height;
79-
const maxDimension = 1920;
80-
81-
if (width > maxDimension || height > maxDimension) {
82-
if (width > height) {
83-
height = (height / width) * maxDimension;
84-
width = maxDimension;
85-
} else {
86-
width = (width / height) * maxDimension;
87-
height = maxDimension;
88-
}
89-
}
90-
91-
canvas.width = width;
92-
canvas.height = height;
93-
94-
// Draw image on canvas
95-
const ctx = canvas.getContext("2d");
96-
if (!ctx) return;
97-
98-
ctx.drawImage(img, 0, 0, width, height);
99-
100-
// Convert to blob with quality setting
101-
canvas.toBlob(
102-
(blob) => {
103-
if (!blob || !isMounted) return;
104-
105-
const compressedFile = new File([blob], images[0]!.name, {
106-
type: "image/jpeg",
107-
lastModified: Date.now(),
108-
});
109-
110-
setCompressedSize(formatFileSize(compressedFile.size));
111-
setCompressedPreview(URL.createObjectURL(compressedFile));
112-
},
113-
"image/jpeg",
114-
quality,
115-
);
116-
};
117-
118-
img.src = imageUrl;
119-
} catch (error) {
120-
console.error("Error generating preview:", error);
121-
if (isMounted) {
122-
setCompressedPreview(null);
123-
setCompressedSize("");
124-
}
87+
const compressedFile = await compressImage(images[0], quality);
88+
setCompressedPreview(URL.createObjectURL(compressedFile));
89+
setCompressedSize(formatFileSize(compressedFile.size));
12590
} finally {
126-
if (isMounted) {
127-
setIsCompressing(false);
128-
}
91+
setIsCompressing(false);
12992
}
13093
}
13194

@@ -134,64 +97,24 @@ export default function ImageSizeCompressor() {
13497
}, 300);
13598

13699
return () => {
137-
isMounted = false;
138100
clearTimeout(debounceTimeout);
139101
};
140-
}, [quality, images]);
102+
}, [images, quality]);
103+
104+
function removeImage(index: number) {
105+
setImages((prev) => prev.filter((_, i) => i !== index));
106+
if (index === 0) {
107+
setCompressedPreview(null);
108+
setOriginalSize("");
109+
setCompressedSize("");
110+
}
111+
}
141112

142113
async function handleCompress() {
143114
try {
115+
setIsCompressing(true);
144116
const compressedFiles = await Promise.all(
145-
images.map(async (image) => {
146-
return new Promise<File>((resolve, reject) => {
147-
const img = new Image();
148-
const imageUrl = URL.createObjectURL(image);
149-
150-
img.onload = () => {
151-
const canvas = document.createElement("canvas");
152-
let width = img.width;
153-
let height = img.height;
154-
const maxDimension = 1920;
155-
156-
if (width > maxDimension || height > maxDimension) {
157-
if (width > height) {
158-
height = (height / width) * maxDimension;
159-
width = maxDimension;
160-
} else {
161-
width = (width / height) * maxDimension;
162-
height = maxDimension;
163-
}
164-
}
165-
166-
canvas.width = width;
167-
canvas.height = height;
168-
169-
const ctx = canvas.getContext("2d");
170-
if (!ctx)
171-
return reject(new Error("Could not get canvas context"));
172-
173-
ctx.drawImage(img, 0, 0, width, height);
174-
175-
canvas.toBlob(
176-
(blob) => {
177-
if (!blob) return reject(new Error("Could not create blob"));
178-
179-
const compressedFile = new File([blob], image.name, {
180-
type: "image/jpeg",
181-
lastModified: Date.now(),
182-
});
183-
184-
resolve(compressedFile);
185-
},
186-
"image/jpeg",
187-
quality,
188-
);
189-
};
190-
191-
img.onerror = () => reject(new Error("Could not load image"));
192-
img.src = imageUrl;
193-
});
194-
}),
117+
images.map((image) => compressImage(image, quality)),
195118
);
196119

197120
compressedFiles.forEach((file, index) => {
@@ -203,6 +126,8 @@ export default function ImageSizeCompressor() {
203126
});
204127
} catch (error) {
205128
console.error("Error compressing images:", error);
129+
} finally {
130+
setIsCompressing(false);
206131
}
207132
}
208133

@@ -212,7 +137,6 @@ export default function ImageSizeCompressor() {
212137

213138
function onCancel() {
214139
setImages([]);
215-
setPreviews([]);
216140
setCompressedPreview(null);
217141
setOriginalSize("");
218142
setCompressedSize("");
@@ -241,10 +165,10 @@ export default function ImageSizeCompressor() {
241165
return (
242166
<div className="flex flex-col items-center justify-center gap-4 p-4 text-2xl">
243167
<div className="flex flex-wrap justify-center gap-4">
244-
{previews.map((preview, index) => (
168+
{images.map((image, index) => (
245169
<div key={index} className="relative">
246170
<img
247-
src={preview}
171+
src={URL.createObjectURL(image)}
248172
alt={`Preview ${index + 1}`}
249173
className="h-32 w-32 rounded-lg object-cover"
250174
/>
@@ -268,6 +192,7 @@ export default function ImageSizeCompressor() {
268192
value={quality}
269193
onChange={onChangeQuality}
270194
className="w-full"
195+
disabled={isCompressing}
271196
/>
272197
</div>
273198

@@ -276,7 +201,7 @@ export default function ImageSizeCompressor() {
276201
<div className="flex flex-col items-center gap-2">
277202
<span className="text-sm font-medium">Original</span>
278203
<img
279-
src={previews[0]}
204+
src={images[0] ? URL.createObjectURL(images[0]) : ""}
280205
alt="Original preview"
281206
className="h-64 w-64 rounded-lg object-cover"
282207
/>
@@ -286,7 +211,10 @@ export default function ImageSizeCompressor() {
286211
<span className="text-sm font-medium">Compressed Preview</span>
287212
<div className="relative h-64 w-64">
288213
<img
289-
src={compressedPreview ?? previews[0]}
214+
src={
215+
compressedPreview ??
216+
(images[0] ? URL.createObjectURL(images[0]) : "")
217+
}
290218
alt="Compressed preview"
291219
className="h-64 w-64 rounded-lg object-cover"
292220
/>
@@ -304,13 +232,23 @@ export default function ImageSizeCompressor() {
304232
<div className="flex gap-2">
305233
<button
306234
onClick={handleCompress}
307-
className="rounded-lg bg-green-700 px-4 py-2 text-sm font-semibold text-white shadow-md transition-colors duration-200 hover:bg-green-800 focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-opacity-75"
235+
disabled={isCompressing}
236+
className={`rounded-lg px-4 py-2 text-sm font-semibold text-white shadow-md transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-opacity-75 ${
237+
isCompressing
238+
? "cursor-not-allowed bg-gray-500"
239+
: "bg-green-700 hover:bg-green-800"
240+
}`}
308241
>
309-
Download Compressed Images
242+
{isCompressing ? "Compressing..." : "Download Compressed Images"}
310243
</button>
311244
<button
312245
onClick={onCancel}
313-
className="rounded-md bg-red-700 px-3 py-1 text-sm font-medium text-white transition-colors hover:bg-red-800"
246+
disabled={isCompressing}
247+
className={`rounded-md px-3 py-1 text-sm font-medium text-white transition-colors ${
248+
isCompressing
249+
? "cursor-not-allowed bg-gray-500"
250+
: "bg-red-700 hover:bg-red-800"
251+
}`}
314252
>
315253
Cancel
316254
</button>

0 commit comments

Comments
 (0)