[dyad] Create image export application - wrote 4 file(s)

This commit is contained in:
[dyad]
2026-01-18 10:48:31 +01:00
parent 7a640af8b4
commit 0f5db0a419
4 changed files with 230 additions and 10 deletions

View File

@@ -0,0 +1,190 @@
"use client";
import { useState, useRef, ChangeEvent } 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 { Upload, Download, Image as ImageIcon } from "lucide-react";
import { toast } from "sonner";
export function ImageConverter() {
const [image, setImage] = useState<File | null>(null);
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [width, setWidth] = useState<number | string>("");
const [height, setHeight] = useState<number | string>("");
const [format, setFormat] = useState<"png" | "jpeg" | "webp">("png");
const [isConverting, setIsConverting] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => {
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));
};
reader.readAsDataURL(file);
} else {
toast.error("Please select a valid image file.");
}
};
const handleConvertAndDownload = () => {
if (!image || !previewUrl || !width || !height) {
toast.error("Please upload an image and set dimensions.");
return;
}
setIsConverting(true);
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 originalName = image.name.substring(0, image.name.lastIndexOf('.'));
link.download = `${originalName}_${width}x${height}.${format}`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
toast.success("Image exported successfully!");
} else {
toast.error("Could not process the image.");
}
setIsConverting(false);
};
img.onerror = () => {
toast.error("Failed to load image for conversion.");
setIsConverting(false);
};
};
return (
<Card className="w-full">
<CardHeader>
<CardTitle>Image Settings</CardTitle>
<CardDescription>
Adjust the resolution and format for your export.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{previewUrl ? (
<div className="w-full aspect-video rounded-md overflow-hidden border flex items-center justify-center bg-gray-100 dark:bg-gray-800">
<img
src={previewUrl}
alt="Image preview"
className="max-w-full max-h-full object-contain"
/>
</div>
) : (
<div
className="w-full aspect-video rounded-md border-2 border-dashed flex flex-col items-center justify-center text-gray-500 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800"
onClick={() => fileInputRef.current?.click()}
>
<ImageIcon className="w-12 h-12 mb-2" />
<span>Click to upload an image</span>
</div>
)}
<Input
type="file"
ref={fileInputRef}
onChange={handleImageChange}
className="hidden"
accept="image/*"
/>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="width">Width (px)</Label>
<Input
id="width"
type="number"
placeholder="e.g., 1920"
value={width}
onChange={(e) => setWidth(e.target.value)}
disabled={!image}
/>
</div>
<div className="space-y-2">
<Label htmlFor="height">Height (px)</Label>
<Input
id="height"
type="number"
placeholder="e.g., 1080"
value={height}
onChange={(e) => setHeight(e.target.value)}
disabled={!image}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="format">Format</Label>
<Select
value={format}
onValueChange={(value: "png" | "jpeg" | "webp") => setFormat(value)}
disabled={!image}
>
<SelectTrigger id="format">
<SelectValue placeholder="Select format" />
</SelectTrigger>
<SelectContent>
<SelectItem value="png">PNG</SelectItem>
<SelectItem value="jpeg">JPEG</SelectItem>
<SelectItem value="webp">WEBP</SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
<CardFooter className="flex justify-between">
<Button
variant="outline"
onClick={() => fileInputRef.current?.click()}
>
<Upload className="mr-2 h-4 w-4" />
{image ? "Change Image" : "Upload Image"}
</Button>
<Button
onClick={handleConvertAndDownload}
disabled={!image || isConverting}
>
<Download className="mr-2 h-4 w-4" />
{isConverting ? "Converting..." : "Convert & Download"}
</Button>
</CardFooter>
</Card>
);
}

View File

@@ -0,0 +1,9 @@
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}