"use client"; import { useState, useRef, ChangeEvent, useEffect } from "react"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Upload, Download, X, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { Switch } from "@/components/ui/switch"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; export function ImageConverter() { const [images, setImages] = useState([]); const [previewUrls, setPreviewUrls] = useState([]); const [filenames, setFilenames] = useState([]); const [width, setWidth] = useState(""); const [height, setHeight] = useState(""); const [format, setFormat] = useState<"png" | "jpeg" | "webp">("png"); const [prefix, setPrefix] = useState(""); const [suffix, setSuffix] = useState(""); const [useCounter, setUseCounter] = useState(false); const [counterStart, setCounterStart] = useState(1); const [counterDigits, setCounterDigits] = useState(3); const [isConverting, setIsConverting] = useState(false); const [isDraggingOver, setIsDraggingOver] = useState(false); const fileInputRef = useRef(null); useEffect(() => { return () => { previewUrls.forEach((url) => URL.revokeObjectURL(url)); }; }, [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)), ]; const newFilenames = [ ...filenames, ...imageFiles.map((file) => file.name.substring(0, file.name.lastIndexOf(".")) ), ]; setImages(newImages); setPreviewUrls(newPreviewUrls); setFilenames(newFilenames); 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); const newFilenames = filenames.filter((_, i) => i !== indexToRemove); setImages(newImages); setPreviewUrls(newPreviewUrls); setFilenames(newFilenames); }; const handleClearAll = () => { previewUrls.forEach((url) => URL.revokeObjectURL(url)); setImages([]); setPreviewUrls([]); setFilenames([]); toast.info("All images cleared."); }; const handleFilenameChange = (index: number, newName: string) => { const newFilenames = [...filenames]; newFilenames[index] = newName; setFilenames(newFilenames); }; const generateFinalFilename = (index: number) => { if (useCounter) { const counter = (index + counterStart).toString().padStart(counterDigits, '0'); return `${prefix}${counter}${suffix}`; } const baseName = filenames[index] || "filename"; return `${prefix}${baseName}${suffix}`; }; const handleConvertAndDownload = async () => { if (images.length === 0 || !width || !height) { toast.error("Please upload images and set dimensions."); return; } setIsConverting(true); toast.info(`Starting conversion for ${images.length} images...`); const conversionPromises = images.map((image, index) => { return new Promise((resolve, reject) => { const previewUrl = previewUrls[index]; const img = new Image(); img.crossOrigin = "anonymous"; img.src = previewUrl; img.onload = () => { const canvas = document.createElement("canvas"); canvas.width = Number(width); canvas.height = Number(height); const ctx = canvas.getContext("2d"); if (ctx) { ctx.drawImage(img, 0, 0, Number(width), Number(height)); const dataUrl = canvas.toDataURL(`image/${format}`); const link = document.createElement("a"); link.href = dataUrl; const finalFilename = generateFinalFilename(index); link.download = `${finalFilename}_${width}x${height}.${format}`; document.body.appendChild(link); link.click(); document.body.removeChild(link); resolve(); } else { reject(new Error(`Could not process ${image.name}.`)); } }; img.onerror = () => { reject(new Error(`Failed to load ${image.name} for conversion.`)); }; }); }); try { await Promise.all(conversionPromises); toast.success(`Successfully exported all ${images.length} images!`); } catch (error) { if (error instanceof Error) { toast.error(error.message); } else { toast.error("An unknown error occurred during conversion."); } } finally { setIsConverting(false); } }; const hasImages = images.length > 0; return (
Image Settings Adjust resolution and format for all uploaded images.
setWidth(e.target.value)} />
setHeight(e.target.value)} />
Filename Settings Customize the output filenames.
setPrefix(e.target.value)} />
setSuffix(e.target.value)} />
{useCounter && (
setCounterStart(Math.max(0, Number(e.target.value)))} min="0" />
setCounterDigits(Math.max(1, Number(e.target.value)))} min="1" />
)}
fileInputRef.current?.click()} >

Click to upload or drag and drop

Select files from your computer

{hasImages && (
Uploaded Images
{previewUrls.map((url, index) => (
{`Preview
handleFilenameChange(index, e.target.value)} disabled={useCounter} className="text-sm font-medium h-8 mt-1" />

Final name: {generateFinalFilename(index)}.{format}

))}
)}
); }