diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87e..710fbc6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,8 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import { ThemeProvider } from "@/components/theme-provider"; +import { Toaster } from "@/components/ui/sonner"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -13,8 +15,8 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Image Web Exporter", + description: "Upload a picture, then export it in a different resolution and format.", }; export default function RootLayout({ @@ -23,12 +25,20 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - {children} + + {children} + + ); -} +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 0d42a08..14fac05 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,12 +1,23 @@ +import { ImageConverter } from "@/components/image-converter"; import { MadeWithDyad } from "@/components/made-with-dyad"; export default function Home() { return ( -
-
-

Blank page

+
+
+
+

+ Image Web Exporter +

+

+ Upload a picture, then export it in a different resolution and format. +

+
+
- +
+ +
); -} +} \ No newline at end of file diff --git a/src/components/image-converter.tsx b/src/components/image-converter.tsx new file mode 100644 index 0000000..9ba97d6 --- /dev/null +++ b/src/components/image-converter.tsx @@ -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(null); + const [previewUrl, setPreviewUrl] = useState(null); + const [width, setWidth] = useState(""); + const [height, setHeight] = useState(""); + const [format, setFormat] = useState<"png" | "jpeg" | "webp">("png"); + const [isConverting, setIsConverting] = 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)); + }; + 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 ( + + + Image Settings + + Adjust the resolution and format for your export. + + + + {previewUrl ? ( +
+ Image preview +
+ ) : ( +
fileInputRef.current?.click()} + > + + Click to upload an image +
+ )} + + + +
+
+ + setWidth(e.target.value)} + disabled={!image} + /> +
+
+ + setHeight(e.target.value)} + disabled={!image} + /> +
+
+ +
+ + +
+
+ + + + +
+ ); +} \ No newline at end of file diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx new file mode 100644 index 0000000..4203e6a --- /dev/null +++ b/src/components/theme-provider.tsx @@ -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 {children} +} \ No newline at end of file