diff --git a/src/components/image-converter.tsx b/src/components/image-converter.tsx index 3c54fad..2287a63 100644 --- a/src/components/image-converter.tsx +++ b/src/components/image-converter.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useRef, ChangeEvent } from "react"; +import { useState, useRef, ChangeEvent, useEffect } from "react"; import { Card, CardContent, @@ -19,45 +19,125 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { Upload, Download, Image as ImageIcon } from "lucide-react"; +import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; +import { Upload, Download, Image as ImageIcon, X, Trash2 } from "lucide-react"; import { toast } from "sonner"; +import { cn } from "@/lib/utils"; export function ImageConverter() { - const [image, setImage] = useState(null); - const [previewUrl, setPreviewUrl] = useState(null); + const [images, setImages] = useState([]); + const [previewUrls, setPreviewUrls] = useState([]); + const [selectedImageIndex, setSelectedImageIndex] = useState( + null + ); const [width, setWidth] = useState(""); const [height, setHeight] = useState(""); const [format, setFormat] = useState<"png" | "jpeg" | "webp">("png"); const [isConverting, setIsConverting] = useState(false); + const [isDraggingOver, setIsDraggingOver] = useState(false); const fileInputRef = useRef(null); - const handleImageChange = (e: ChangeEvent) => { - const file = e.target.files?.[0]; - if (file && file.type.startsWith("image/")) { - setImage(file); - const reader = new FileReader(); - reader.onload = (event) => { - const img = new Image(); - img.onload = () => { - setWidth(img.width); - setHeight(img.height); - }; - img.src = event.target?.result as string; - setPreviewUrl(URL.createObjectURL(file)); + useEffect(() => { + // Clean up object URLs on component unmount + return () => { + previewUrls.forEach((url) => URL.revokeObjectURL(url)); + }; + }, [previewUrls]); + + useEffect(() => { + if (selectedImageIndex !== null && previewUrls[selectedImageIndex]) { + const img = new Image(); + img.onload = () => { + setWidth(img.width); + setHeight(img.height); }; - reader.readAsDataURL(file); + img.src = previewUrls[selectedImageIndex]; } else { - toast.error("Please select a valid image file."); + setWidth(""); + setHeight(""); + } + }, [selectedImageIndex, previewUrls]); + + const handleFiles = (files: FileList | null) => { + if (!files || files.length === 0) return; + + const imageFiles = Array.from(files).filter((file) => + file.type.startsWith("image/") + ); + + if (imageFiles.length === 0) { + toast.error("No valid image files found."); + return; + } + + const newImages = [...images, ...imageFiles]; + const newPreviewUrls = [ + ...previewUrls, + ...imageFiles.map((file) => URL.createObjectURL(file)), + ]; + + setImages(newImages); + setPreviewUrls(newPreviewUrls); + + if (selectedImageIndex === null) { + setSelectedImageIndex(images.length); + } + toast.success(`${imageFiles.length} image(s) added.`); + }; + + const handleImageChange = (e: ChangeEvent) => { + handleFiles(e.target.files); + if (e.target) e.target.value = ""; + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + setIsDraggingOver(true); + }; + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault(); + setIsDraggingOver(false); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setIsDraggingOver(false); + handleFiles(e.dataTransfer.files); + }; + + const handleRemoveImage = (indexToRemove: number) => { + URL.revokeObjectURL(previewUrls[indexToRemove]); + const newImages = images.filter((_, i) => i !== indexToRemove); + const newPreviewUrls = previewUrls.filter((_, i) => i !== indexToRemove); + + setImages(newImages); + setPreviewUrls(newPreviewUrls); + + if (selectedImageIndex === indexToRemove) { + setSelectedImageIndex(newImages.length > 0 ? Math.max(0, indexToRemove - 1) : null); + } else if (selectedImageIndex !== null && selectedImageIndex > indexToRemove) { + setSelectedImageIndex(selectedImageIndex - 1); } }; + const handleClearAll = () => { + previewUrls.forEach((url) => URL.revokeObjectURL(url)); + setImages([]); + setPreviewUrls([]); + setSelectedImageIndex(null); + toast.info("All images cleared."); + }; + const handleConvertAndDownload = () => { - if (!image || !previewUrl || !width || !height) { - toast.error("Please upload an image and set dimensions."); + if (selectedImageIndex === null || !width || !height) { + toast.error("Please select an image and set dimensions."); return; } setIsConverting(true); + const image = images[selectedImageIndex]; + const previewUrl = previewUrls[selectedImageIndex]; const img = new Image(); img.crossOrigin = "anonymous"; img.src = previewUrl; @@ -78,115 +158,97 @@ export function ImageConverter() { document.body.appendChild(link); link.click(); document.body.removeChild(link); - toast.success("Image exported successfully!"); + toast.success(`Exported ${image.name} successfully!`); } else { toast.error("Could not process the image."); } setIsConverting(false); }; - img.onerror = () => { toast.error("Failed to load image for conversion."); setIsConverting(false); }; }; + const hasImages = images.length > 0; + const isImageSelected = selectedImageIndex !== null; + return (
- {/* Left Column: Settings */} - - - Image Settings - - Adjust the resolution and format for your export. - - - -
-
- - setWidth(e.target.value)} - disabled={!image} - /> +
+ + + Image Settings + + Adjust resolution and format for the selected image. + + + +
+
+ + setWidth(e.target.value)} disabled={!isImageSelected} /> +
+
+ + setHeight(e.target.value)} disabled={!isImageSelected} /> +
- - setHeight(e.target.value)} - disabled={!image} - /> + +
-
+ + + + + + -
- - -
- - - - - - + {hasImages && ( + + +
+ Uploaded Images + +
+
+ + +
+ {previewUrls.map((url, index) => ( +
+ + +
+ ))} +
+ +
+
+
+ )} +
- {/* Right Column: Preview */} -
- {previewUrl ? ( - Image preview +
+ {isImageSelected && previewUrls[selectedImageIndex] ? ( + Selected image preview ) : ( -
fileInputRef.current?.click()} - > +
fileInputRef.current?.click()}> - Click to upload an image + Click or drag & drop images here
)} - +
);