From fe8dcc4d2b20fdbe48dfac86f6fe7cbacd156ecf Mon Sep 17 00:00:00 2001 From: Dhanush Reddy Chilakala Date: Mon, 11 Nov 2024 10:57:19 -0500 Subject: [PATCH 1/3] feat:changed layout --- .../(tools)/rounded-border/rounded-tool.tsx | 420 +++++++++--------- src/app/(tools)/square-image/square-tool.tsx | 261 +++++------ src/app/(tools)/svg-to-png/svg-tool.tsx | 363 +++++++-------- 3 files changed, 524 insertions(+), 520 deletions(-) diff --git a/src/app/(tools)/rounded-border/rounded-tool.tsx b/src/app/(tools)/rounded-border/rounded-tool.tsx index a5dbf34..027892b 100644 --- a/src/app/(tools)/rounded-border/rounded-tool.tsx +++ b/src/app/(tools)/rounded-border/rounded-tool.tsx @@ -6,8 +6,8 @@ import { UploadBox } from "@/components/shared/upload-box"; import { OptionSelector } from "@/components/shared/option-selector"; import { BorderRadiusSelector } from "@/components/border-radius-selector"; import { - useFileUploader, - type FileUploaderResult, + useFileUploader, + type FileUploaderResult, } from "@/hooks/use-file-uploader"; import { FileDropzone } from "@/components/shared/file-dropzone"; @@ -16,239 +16,241 @@ type Radius = number; type BackgroundOption = "white" | "black" | "transparent"; function useImageConverter(props: { - canvas: HTMLCanvasElement | null; - imageContent: string; - radius: Radius; - background: BackgroundOption; - fileName?: string; - imageMetadata: { width: number; height: number; name: string }; + canvas: HTMLCanvasElement | null; + imageContent: string; + radius: Radius; + background: BackgroundOption; + fileName?: string; + imageMetadata: { width: number; height: number; name: string }; }) { - const { width, height } = useMemo(() => { - return { - width: props.imageMetadata.width, - height: props.imageMetadata.height, - }; - }, [props.imageMetadata]); - - const convertToPng = async () => { - const ctx = props.canvas?.getContext("2d"); - if (!ctx) throw new Error("Failed to get canvas context"); - - const saveImage = () => { - if (props.canvas) { - const dataURL = props.canvas.toDataURL("image/png"); - const link = document.createElement("a"); - link.href = dataURL; - const imageFileName = props.imageMetadata.name ?? "image_converted"; - link.download = `${imageFileName.replace(/\..+$/, "")}.png`; - link.click(); - } - }; + const { width, height } = useMemo(() => { + return { + width: props.imageMetadata.width, + height: props.imageMetadata.height, + }; + }, [props.imageMetadata]); - const img = new Image(); - img.onload = () => { - ctx.clearRect(0, 0, width, height); - ctx.fillStyle = props.background; - ctx.fillRect(0, 0, width, height); - ctx.beginPath(); - ctx.moveTo(props.radius, 0); - ctx.lineTo(width - props.radius, 0); - ctx.quadraticCurveTo(width, 0, width, props.radius); - ctx.lineTo(width, height - props.radius); - ctx.quadraticCurveTo(width, height, width - props.radius, height); - ctx.lineTo(props.radius, height); - ctx.quadraticCurveTo(0, height, 0, height - props.radius); - ctx.lineTo(0, props.radius); - ctx.quadraticCurveTo(0, 0, props.radius, 0); - ctx.closePath(); - ctx.clip(); - ctx.drawImage(img, 0, 0, width, height); - saveImage(); - }; + const convertToPng = async () => { + const ctx = props.canvas?.getContext("2d"); + if (!ctx) throw new Error("Failed to get canvas context"); + + const saveImage = () => { + if (props.canvas) { + const dataURL = props.canvas.toDataURL("image/png"); + const link = document.createElement("a"); + link.href = dataURL; + const imageFileName = props.imageMetadata.name ?? "image_converted"; + link.download = `${imageFileName.replace(/\..+$/, "")}.png`; + link.click(); + } + }; - img.src = props.imageContent; - }; + const img = new Image(); + img.onload = () => { + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = props.background; + ctx.fillRect(0, 0, width, height); + ctx.beginPath(); + ctx.moveTo(props.radius, 0); + ctx.lineTo(width - props.radius, 0); + ctx.quadraticCurveTo(width, 0, width, props.radius); + ctx.lineTo(width, height - props.radius); + ctx.quadraticCurveTo(width, height, width - props.radius, height); + ctx.lineTo(props.radius, height); + ctx.quadraticCurveTo(0, height, 0, height - props.radius); + ctx.lineTo(0, props.radius); + ctx.quadraticCurveTo(0, 0, props.radius, 0); + ctx.closePath(); + ctx.clip(); + ctx.drawImage(img, 0, 0, width, height); + saveImage(); + }; - return { - convertToPng, - canvasProps: { width: width, height: height }, - }; + img.src = props.imageContent; + }; + + return { + convertToPng, + canvasProps: { width: width, height: height }, + }; } interface ImageRendererProps { - imageContent: string; - radius: Radius; - background: BackgroundOption; + imageContent: string; + radius: Radius; + background: BackgroundOption; } const ImageRenderer = ({ - imageContent, - radius, - background, + imageContent, + radius, + background, }: ImageRendererProps) => { - const containerRef = useRef(null); - - useEffect(() => { - if (containerRef.current) { - const imgElement = containerRef.current.querySelector("img"); - if (imgElement) { - imgElement.style.borderRadius = `${radius}px`; - } - } - }, [imageContent, radius]); - - return ( -
-
- Preview -
- ); + const containerRef = useRef(null); + + useEffect(() => { + if (containerRef.current) { + const imgElement = containerRef.current.querySelector("img"); + if (imgElement) { + imgElement.style.borderRadius = `${radius}px`; + } + } + }, [imageContent, radius]); + + return ( +
+
+ Preview +
+ ); }; function SaveAsPngButton({ - imageContent, - radius, - background, - imageMetadata, -}: { - imageContent: string; - radius: Radius; - background: BackgroundOption; - imageMetadata: { width: number; height: number; name: string }; -}) { - const [canvasRef, setCanvasRef] = useState(null); - const { convertToPng, canvasProps } = useImageConverter({ - canvas: canvasRef, imageContent, radius, background, imageMetadata, - }); - - const plausible = usePlausible(); - - return ( -
-
- ); +}: { + imageContent: string; + radius: Radius; + background: BackgroundOption; + imageMetadata: { width: number; height: number; name: string }; +}) { + const [canvasRef, setCanvasRef] = useState(null); + const { convertToPng, canvasProps } = useImageConverter({ + canvas: canvasRef, + imageContent, + radius, + background, + imageMetadata, + }); + + const plausible = usePlausible(); + + return ( +
+
+ ); } function RoundedToolCore(props: { fileUploaderProps: FileUploaderResult }) { - const { imageContent, imageMetadata, handleFileUploadEvent, cancel } = - props.fileUploaderProps; - const [radius, setRadius] = useLocalStorage("roundedTool_radius", 2); - const [isCustomRadius, setIsCustomRadius] = useState(false); - const [background, setBackground] = useLocalStorage( - "roundedTool_background", - "transparent", - ); - - const handleRadiusChange = (value: number | "custom") => { - if (value === "custom") { - setIsCustomRadius(true); - } else { - setRadius(value); - setIsCustomRadius(false); + const { imageContent, imageMetadata, handleFileUploadEvent, cancel } = + props.fileUploaderProps; + const [radius, setRadius] = useLocalStorage("roundedTool_radius", 2); + const [isCustomRadius, setIsCustomRadius] = useState(false); + const [background, setBackground] = useLocalStorage( + "roundedTool_background", + "transparent", + ); + + const handleRadiusChange = (value: number | "custom") => { + if (value === "custom") { + setIsCustomRadius(true); + } else { + setRadius(value); + setIsCustomRadius(false); + } + }; + + if (!imageMetadata) { + return ( + + ); } - }; - if (!imageMetadata) { return ( - - ); - } - - return ( -
-
- -

- {imageMetadata.name} -

-
- -
- Original Size - - {imageMetadata.width} × {imageMetadata.height} - -
- - - - - option.charAt(0).toUpperCase() + option.slice(1) - } - /> +
+
+ +

+ {imageMetadata.name} +

+
-
- - -
-
- ); +
+
+ Original Size + + {imageMetadata.width} × {imageMetadata.height} + +
+ + + + + option.charAt(0).toUpperCase() + option.slice(1) + } + /> + +
+ + +
+
+
+ ); } export function RoundedTool() { - const fileUploaderProps = useFileUploader(); - - return ( - - - - ); + const fileUploaderProps = useFileUploader(); + + return ( + + + + ); } diff --git a/src/app/(tools)/square-image/square-tool.tsx b/src/app/(tools)/square-image/square-tool.tsx index 8264309..fc620c3 100644 --- a/src/app/(tools)/square-image/square-tool.tsx +++ b/src/app/(tools)/square-image/square-tool.tsx @@ -6,147 +6,148 @@ import { UploadBox } from "@/components/shared/upload-box"; import { OptionSelector } from "@/components/shared/option-selector"; import { FileDropzone } from "@/components/shared/file-dropzone"; import { - type FileUploaderResult, - useFileUploader, + type FileUploaderResult, + useFileUploader, } from "@/hooks/use-file-uploader"; import { useEffect, useState } from "react"; function SquareToolCore(props: { fileUploaderProps: FileUploaderResult }) { - const { imageContent, imageMetadata, handleFileUploadEvent, cancel } = - props.fileUploaderProps; - - const [backgroundColor, setBackgroundColor] = useLocalStorage< - "black" | "white" - >("squareTool_backgroundColor", "white"); - - const [squareImageContent, setSquareImageContent] = useState( - null, - ); - - useEffect(() => { - if (imageContent && imageMetadata) { - const canvas = document.createElement("canvas"); - const size = Math.max(imageMetadata.width, imageMetadata.height); - canvas.width = size; - canvas.height = size; - - const ctx = canvas.getContext("2d"); - if (!ctx) return; - - // Fill background - ctx.fillStyle = backgroundColor; - ctx.fillRect(0, 0, size, size); - - // Load and center the image - const img = new Image(); - img.onload = () => { - const x = (size - imageMetadata.width) / 2; - const y = (size - imageMetadata.height) / 2; - ctx.drawImage(img, x, y); - setSquareImageContent(canvas.toDataURL("image/png")); - }; - img.src = imageContent; - } - }, [imageContent, imageMetadata, backgroundColor]); - - const handleSaveImage = () => { - if (squareImageContent && imageMetadata) { - const link = document.createElement("a"); - link.href = squareImageContent; - const originalFileName = imageMetadata.name; - const fileNameWithoutExtension = - originalFileName.substring(0, originalFileName.lastIndexOf(".")) || - originalFileName; - link.download = `${fileNameWithoutExtension}-squared.png`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } - }; + const { imageContent, imageMetadata, handleFileUploadEvent, cancel } = + props.fileUploaderProps; - const plausible = usePlausible(); + const [backgroundColor, setBackgroundColor] = useLocalStorage< + "black" | "white" + >("squareTool_backgroundColor", "white"); - if (!imageMetadata) { - return ( - + const [squareImageContent, setSquareImageContent] = useState( + null, ); - } - - return ( -
-
- {squareImageContent && ( - Preview - )} -

- {imageMetadata.name} -

-
- -
-
- Original - - {imageMetadata.width} × {imageMetadata.height} - -
-
- Square Size - - {Math.max(imageMetadata.width, imageMetadata.height)} ×{" "} - {Math.max(imageMetadata.width, imageMetadata.height)} - -
-
- - - option.charAt(0).toUpperCase() + option.slice(1) + useEffect(() => { + if (imageContent && imageMetadata) { + const canvas = document.createElement("canvas"); + const size = Math.max(imageMetadata.width, imageMetadata.height); + canvas.width = size; + canvas.height = size; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + // Fill background + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, size, size); + + // Load and center the image + const img = new Image(); + img.onload = () => { + const x = (size - imageMetadata.width) / 2; + const y = (size - imageMetadata.height) / 2; + ctx.drawImage(img, x, y); + setSquareImageContent(canvas.toDataURL("image/png")); + }; + img.src = imageContent; + } + }, [imageContent, imageMetadata, backgroundColor]); + + const handleSaveImage = () => { + if (squareImageContent && imageMetadata) { + const link = document.createElement("a"); + link.href = squareImageContent; + const originalFileName = imageMetadata.name; + const fileNameWithoutExtension = + originalFileName.substring(0, originalFileName.lastIndexOf(".")) || + originalFileName; + link.download = `${fileNameWithoutExtension}-squared.png`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); } - /> + }; + + const plausible = usePlausible(); + + if (!imageMetadata) { + return ( + + ); + } -
- - -
-
- ); + return ( +
+
+ {squareImageContent && ( + Preview + )} +

+ {imageMetadata.name} +

+
+
+
+
+ Original + + {imageMetadata.width} × {imageMetadata.height} + +
+ +
+ Square Size + + {Math.max(imageMetadata.width, imageMetadata.height)} ×{" "} + {Math.max(imageMetadata.width, imageMetadata.height)} + +
+
+ + + option.charAt(0).toUpperCase() + option.slice(1) + } + /> + +
+ + +
+
+
+ ); } export function SquareTool() { - const fileUploaderProps = useFileUploader(); - - return ( - - - - ); + const fileUploaderProps = useFileUploader(); + + return ( + + + + ); } diff --git a/src/app/(tools)/svg-to-png/svg-tool.tsx b/src/app/(tools)/svg-to-png/svg-tool.tsx index 137d778..a9aae45 100644 --- a/src/app/(tools)/svg-to-png/svg-tool.tsx +++ b/src/app/(tools)/svg-to-png/svg-tool.tsx @@ -9,222 +9,223 @@ import { SVGScaleSelector } from "@/components/svg-scale-selector"; export type Scale = "custom" | number; function scaleSvg(svgContent: string, scale: number) { - const parser = new DOMParser(); - const svgDoc = parser.parseFromString(svgContent, "image/svg+xml"); - const svgElement = svgDoc.documentElement; - const width = parseInt(svgElement.getAttribute("width") ?? "300"); - const height = parseInt(svgElement.getAttribute("height") ?? "150"); + const parser = new DOMParser(); + const svgDoc = parser.parseFromString(svgContent, "image/svg+xml"); + const svgElement = svgDoc.documentElement; + const width = parseInt(svgElement.getAttribute("width") ?? "300"); + const height = parseInt(svgElement.getAttribute("height") ?? "150"); - const scaledWidth = width * scale; - const scaledHeight = height * scale; + const scaledWidth = width * scale; + const scaledHeight = height * scale; - svgElement.setAttribute("width", scaledWidth.toString()); - svgElement.setAttribute("height", scaledHeight.toString()); + svgElement.setAttribute("width", scaledWidth.toString()); + svgElement.setAttribute("height", scaledHeight.toString()); - return new XMLSerializer().serializeToString(svgDoc); + return new XMLSerializer().serializeToString(svgDoc); } function useSvgConverter(props: { - canvas: HTMLCanvasElement | null; - svgContent: string; - scale: number; - fileName?: string; - imageMetadata: { width: number; height: number; name: string }; + canvas: HTMLCanvasElement | null; + svgContent: string; + scale: number; + fileName?: string; + imageMetadata: { width: number; height: number; name: string }; }) { - const { width, height, scaledSvg } = useMemo(() => { - const scaledSvg = scaleSvg(props.svgContent, props.scale); - - return { - width: props.imageMetadata.width * props.scale, - height: props.imageMetadata.height * props.scale, - scaledSvg, - }; - }, [props.svgContent, props.scale, props.imageMetadata]); - - const convertToPng = async () => { - const ctx = props.canvas?.getContext("2d"); - if (!ctx) throw new Error("Failed to get canvas context"); - - // Trigger a "save image" of the resulting canvas content - const saveImage = () => { - if (props.canvas) { - const dataURL = props.canvas.toDataURL("image/png"); - const link = document.createElement("a"); - link.href = dataURL; - const svgFileName = props.imageMetadata.name ?? "svg_converted"; - - // Remove the .svg extension - link.download = `${svgFileName.replace(".svg", "")}-${props.scale}x.png`; - link.click(); - } + const { width, height, scaledSvg } = useMemo(() => { + const scaledSvg = scaleSvg(props.svgContent, props.scale); + + return { + width: props.imageMetadata.width * props.scale, + height: props.imageMetadata.height * props.scale, + scaledSvg, + }; + }, [props.svgContent, props.scale, props.imageMetadata]); + + const convertToPng = async () => { + const ctx = props.canvas?.getContext("2d"); + if (!ctx) throw new Error("Failed to get canvas context"); + + // Trigger a "save image" of the resulting canvas content + const saveImage = () => { + if (props.canvas) { + const dataURL = props.canvas.toDataURL("image/png"); + const link = document.createElement("a"); + link.href = dataURL; + const svgFileName = props.imageMetadata.name ?? "svg_converted"; + + // Remove the .svg extension + link.download = `${svgFileName.replace(".svg", "")}-${props.scale}x.png`; + link.click(); + } + }; + + const img = new Image(); + // Call saveImage after the image has been drawn + img.onload = () => { + ctx.drawImage(img, 0, 0); + saveImage(); + }; + + img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(scaledSvg)}`; }; - const img = new Image(); - // Call saveImage after the image has been drawn - img.onload = () => { - ctx.drawImage(img, 0, 0); - saveImage(); + return { + convertToPng, + canvasProps: { width: width, height: height }, }; - - img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(scaledSvg)}`; - }; - - return { - convertToPng, - canvasProps: { width: width, height: height }, - }; } interface SVGRendererProps { - svgContent: string; + svgContent: string; } function SVGRenderer({ svgContent }: SVGRendererProps) { - const containerRef = useRef(null); - - useEffect(() => { - if (containerRef.current) { - containerRef.current.innerHTML = svgContent; - const svgElement = containerRef.current.querySelector("svg"); - if (svgElement) { - svgElement.setAttribute("width", "100%"); - svgElement.setAttribute("height", "100%"); - } - } - }, [svgContent]); - - return
; + const containerRef = useRef(null); + + useEffect(() => { + if (containerRef.current) { + containerRef.current.innerHTML = svgContent; + const svgElement = containerRef.current.querySelector("svg"); + if (svgElement) { + svgElement.setAttribute("width", "100%"); + svgElement.setAttribute("height", "100%"); + } + } + }, [svgContent]); + + return
; } function SaveAsPngButton({ - svgContent, - scale, - imageMetadata, -}: { - svgContent: string; - scale: number; - imageMetadata: { width: number; height: number; name: string }; -}) { - const [canvasRef, setCanvasRef] = useState(null); - const { convertToPng, canvasProps } = useSvgConverter({ - canvas: canvasRef, svgContent, scale, imageMetadata, - }); - - const plausible = usePlausible(); - - return ( -
-
- ); +}: { + svgContent: string; + scale: number; + imageMetadata: { width: number; height: number; name: string }; +}) { + const [canvasRef, setCanvasRef] = useState(null); + const { convertToPng, canvasProps } = useSvgConverter({ + canvas: canvasRef, + svgContent, + scale, + imageMetadata, + }); + + const plausible = usePlausible(); + + return ( +
+
+ ); } import { - type FileUploaderResult, - useFileUploader, + type FileUploaderResult, + useFileUploader, } from "@/hooks/use-file-uploader"; import { FileDropzone } from "@/components/shared/file-dropzone"; function SVGToolCore(props: { fileUploaderProps: FileUploaderResult }) { - const { rawContent, imageMetadata, handleFileUploadEvent, cancel } = - props.fileUploaderProps; - - const [scale, setScale] = useLocalStorage("svgTool_scale", 1); - const [customScale, setCustomScale] = useLocalStorage( - "svgTool_customScale", - 1, - ); - - // Get the actual numeric scale value - const effectiveScale = scale === "custom" ? customScale : scale; + const { rawContent, imageMetadata, handleFileUploadEvent, cancel } = + props.fileUploaderProps; - if (!imageMetadata) - return ( - + const [scale, setScale] = useLocalStorage("svgTool_scale", 1); + const [customScale, setCustomScale] = useLocalStorage( + "svgTool_customScale", + 1, ); - return ( -
- {/* Preview Section */} -
- -

- {imageMetadata.name} -

-
- - {/* Size Information */} -
-
- Original - - {imageMetadata.width} × {imageMetadata.height} - -
+ // Get the actual numeric scale value + const effectiveScale = scale === "custom" ? customScale : scale; + + if (!imageMetadata) + return ( + + ); -
- Scaled - - {imageMetadata.width * effectiveScale} ×{" "} - {imageMetadata.height * effectiveScale} - + return ( +
+ {/* Preview Section */} +
+ +

+ {imageMetadata.name} +

+
+
+ {/* Size Information */} +
+
+ Original + + {imageMetadata.width} × {imageMetadata.height} + +
+ +
+ Scaled + + {imageMetadata.width * effectiveScale} ×{" "} + {imageMetadata.height * effectiveScale} + +
+
+ + {/* Scale Controls */} + + + {/* Action Buttons */} +
+ + +
+
-
- - {/* Scale Controls */} - - - {/* Action Buttons */} -
- - -
-
- ); + ); } export function SVGTool() { - const fileUploaderProps = useFileUploader(); - return ( - - - - ); + const fileUploaderProps = useFileUploader(); + return ( + + + + ); } From 4546315b4fdbf2cd39c956d053dd56eaaa6884dc Mon Sep 17 00:00:00 2001 From: Dhanush Reddy Chilakala Date: Mon, 11 Nov 2024 11:03:15 -0500 Subject: [PATCH 2/3] bug:fixed styling issues --- .../(tools)/rounded-border/rounded-tool.tsx | 398 +++++++++--------- src/app/(tools)/square-image/square-tool.tsx | 268 ++++++------ src/app/(tools)/svg-to-png/svg-tool.tsx | 364 ++++++++-------- 3 files changed, 517 insertions(+), 513 deletions(-) diff --git a/src/app/(tools)/rounded-border/rounded-tool.tsx b/src/app/(tools)/rounded-border/rounded-tool.tsx index 027892b..2138ddf 100644 --- a/src/app/(tools)/rounded-border/rounded-tool.tsx +++ b/src/app/(tools)/rounded-border/rounded-tool.tsx @@ -6,8 +6,8 @@ import { UploadBox } from "@/components/shared/upload-box"; import { OptionSelector } from "@/components/shared/option-selector"; import { BorderRadiusSelector } from "@/components/border-radius-selector"; import { - useFileUploader, - type FileUploaderResult, + useFileUploader, + type FileUploaderResult, } from "@/hooks/use-file-uploader"; import { FileDropzone } from "@/components/shared/file-dropzone"; @@ -16,241 +16,241 @@ type Radius = number; type BackgroundOption = "white" | "black" | "transparent"; function useImageConverter(props: { - canvas: HTMLCanvasElement | null; - imageContent: string; - radius: Radius; - background: BackgroundOption; - fileName?: string; - imageMetadata: { width: number; height: number; name: string }; + canvas: HTMLCanvasElement | null; + imageContent: string; + radius: Radius; + background: BackgroundOption; + fileName?: string; + imageMetadata: { width: number; height: number; name: string }; }) { - const { width, height } = useMemo(() => { - return { - width: props.imageMetadata.width, - height: props.imageMetadata.height, - }; - }, [props.imageMetadata]); - - const convertToPng = async () => { - const ctx = props.canvas?.getContext("2d"); - if (!ctx) throw new Error("Failed to get canvas context"); - - const saveImage = () => { - if (props.canvas) { - const dataURL = props.canvas.toDataURL("image/png"); - const link = document.createElement("a"); - link.href = dataURL; - const imageFileName = props.imageMetadata.name ?? "image_converted"; - link.download = `${imageFileName.replace(/\..+$/, "")}.png`; - link.click(); - } - }; + const { width, height } = useMemo(() => { + return { + width: props.imageMetadata.width, + height: props.imageMetadata.height, + }; + }, [props.imageMetadata]); - const img = new Image(); - img.onload = () => { - ctx.clearRect(0, 0, width, height); - ctx.fillStyle = props.background; - ctx.fillRect(0, 0, width, height); - ctx.beginPath(); - ctx.moveTo(props.radius, 0); - ctx.lineTo(width - props.radius, 0); - ctx.quadraticCurveTo(width, 0, width, props.radius); - ctx.lineTo(width, height - props.radius); - ctx.quadraticCurveTo(width, height, width - props.radius, height); - ctx.lineTo(props.radius, height); - ctx.quadraticCurveTo(0, height, 0, height - props.radius); - ctx.lineTo(0, props.radius); - ctx.quadraticCurveTo(0, 0, props.radius, 0); - ctx.closePath(); - ctx.clip(); - ctx.drawImage(img, 0, 0, width, height); - saveImage(); - }; + const convertToPng = async () => { + const ctx = props.canvas?.getContext("2d"); + if (!ctx) throw new Error("Failed to get canvas context"); - img.src = props.imageContent; + const saveImage = () => { + if (props.canvas) { + const dataURL = props.canvas.toDataURL("image/png"); + const link = document.createElement("a"); + link.href = dataURL; + const imageFileName = props.imageMetadata.name ?? "image_converted"; + link.download = `${imageFileName.replace(/\..+$/, "")}.png`; + link.click(); + } }; - return { - convertToPng, - canvasProps: { width: width, height: height }, + const img = new Image(); + img.onload = () => { + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = props.background; + ctx.fillRect(0, 0, width, height); + ctx.beginPath(); + ctx.moveTo(props.radius, 0); + ctx.lineTo(width - props.radius, 0); + ctx.quadraticCurveTo(width, 0, width, props.radius); + ctx.lineTo(width, height - props.radius); + ctx.quadraticCurveTo(width, height, width - props.radius, height); + ctx.lineTo(props.radius, height); + ctx.quadraticCurveTo(0, height, 0, height - props.radius); + ctx.lineTo(0, props.radius); + ctx.quadraticCurveTo(0, 0, props.radius, 0); + ctx.closePath(); + ctx.clip(); + ctx.drawImage(img, 0, 0, width, height); + saveImage(); }; + + img.src = props.imageContent; + }; + + return { + convertToPng, + canvasProps: { width: width, height: height }, + }; } interface ImageRendererProps { - imageContent: string; - radius: Radius; - background: BackgroundOption; + imageContent: string; + radius: Radius; + background: BackgroundOption; } const ImageRenderer = ({ - imageContent, - radius, - background, + imageContent, + radius, + background, }: ImageRendererProps) => { - const containerRef = useRef(null); + const containerRef = useRef(null); - useEffect(() => { - if (containerRef.current) { - const imgElement = containerRef.current.querySelector("img"); - if (imgElement) { - imgElement.style.borderRadius = `${radius}px`; - } - } - }, [imageContent, radius]); + useEffect(() => { + if (containerRef.current) { + const imgElement = containerRef.current.querySelector("img"); + if (imgElement) { + imgElement.style.borderRadius = `${radius}px`; + } + } + }, [imageContent, radius]); - return ( -
-
- Preview -
- ); + return ( +
+
+ Preview +
+ ); }; function SaveAsPngButton({ + imageContent, + radius, + background, + imageMetadata, +}: { + imageContent: string; + radius: Radius; + background: BackgroundOption; + imageMetadata: { width: number; height: number; name: string }; +}) { + const [canvasRef, setCanvasRef] = useState(null); + const { convertToPng, canvasProps } = useImageConverter({ + canvas: canvasRef, imageContent, radius, background, imageMetadata, -}: { - imageContent: string; - radius: Radius; - background: BackgroundOption; - imageMetadata: { width: number; height: number; name: string }; -}) { - const [canvasRef, setCanvasRef] = useState(null); - const { convertToPng, canvasProps } = useImageConverter({ - canvas: canvasRef, - imageContent, - radius, - background, - imageMetadata, - }); + }); - const plausible = usePlausible(); + const plausible = usePlausible(); - return ( -
-
- ); + return ( +
+
+ ); } function RoundedToolCore(props: { fileUploaderProps: FileUploaderResult }) { - const { imageContent, imageMetadata, handleFileUploadEvent, cancel } = - props.fileUploaderProps; - const [radius, setRadius] = useLocalStorage("roundedTool_radius", 2); - const [isCustomRadius, setIsCustomRadius] = useState(false); - const [background, setBackground] = useLocalStorage( - "roundedTool_background", - "transparent", - ); + const { imageContent, imageMetadata, handleFileUploadEvent, cancel } = + props.fileUploaderProps; + const [radius, setRadius] = useLocalStorage("roundedTool_radius", 2); + const [isCustomRadius, setIsCustomRadius] = useState(false); + const [background, setBackground] = useLocalStorage( + "roundedTool_background", + "transparent", + ); - const handleRadiusChange = (value: number | "custom") => { - if (value === "custom") { - setIsCustomRadius(true); - } else { - setRadius(value); - setIsCustomRadius(false); - } - }; - - if (!imageMetadata) { - return ( - - ); + const handleRadiusChange = (value: number | "custom") => { + if (value === "custom") { + setIsCustomRadius(true); + } else { + setRadius(value); + setIsCustomRadius(false); } + }; + if (!imageMetadata) { return ( -
-
- -

- {imageMetadata.name} -

-
+ + ); + } -
-
- Original Size - - {imageMetadata.width} × {imageMetadata.height} - -
+ return ( +
+
+ +

+ {imageMetadata.name} +

+
+ +
+
+ Original Size + + {imageMetadata.width} × {imageMetadata.height} + +
- + - - option.charAt(0).toUpperCase() + option.slice(1) - } - /> + + option.charAt(0).toUpperCase() + option.slice(1) + } + /> -
- - -
-
+
+ +
- ); +
+
+ ); } export function RoundedTool() { - const fileUploaderProps = useFileUploader(); + const fileUploaderProps = useFileUploader(); - return ( - - - - ); + return ( + + + + ); } diff --git a/src/app/(tools)/square-image/square-tool.tsx b/src/app/(tools)/square-image/square-tool.tsx index fc620c3..0a5d44f 100644 --- a/src/app/(tools)/square-image/square-tool.tsx +++ b/src/app/(tools)/square-image/square-tool.tsx @@ -6,148 +6,152 @@ import { UploadBox } from "@/components/shared/upload-box"; import { OptionSelector } from "@/components/shared/option-selector"; import { FileDropzone } from "@/components/shared/file-dropzone"; import { - type FileUploaderResult, - useFileUploader, + type FileUploaderResult, + useFileUploader, } from "@/hooks/use-file-uploader"; import { useEffect, useState } from "react"; function SquareToolCore(props: { fileUploaderProps: FileUploaderResult }) { - const { imageContent, imageMetadata, handleFileUploadEvent, cancel } = - props.fileUploaderProps; + const { imageContent, imageMetadata, handleFileUploadEvent, cancel } = + props.fileUploaderProps; + + const [backgroundColor, setBackgroundColor] = useLocalStorage< + "black" | "white" + >("squareTool_backgroundColor", "white"); + + const [squareImageContent, setSquareImageContent] = useState( + null, + ); + + useEffect(() => { + if (imageContent && imageMetadata) { + const canvas = document.createElement("canvas"); + const size = Math.max(imageMetadata.width, imageMetadata.height); + canvas.width = size; + canvas.height = size; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + // Fill background + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, size, size); + + // Load and center the image + const img = new Image(); + img.onload = () => { + const x = (size - imageMetadata.width) / 2; + const y = (size - imageMetadata.height) / 2; + ctx.drawImage(img, x, y); + setSquareImageContent(canvas.toDataURL("image/png")); + }; + img.src = imageContent; + } + }, [imageContent, imageMetadata, backgroundColor]); + + const handleSaveImage = () => { + if (squareImageContent && imageMetadata) { + const link = document.createElement("a"); + link.href = squareImageContent; + const originalFileName = imageMetadata.name; + const fileNameWithoutExtension = + originalFileName.substring(0, originalFileName.lastIndexOf(".")) || + originalFileName; + link.download = `${fileNameWithoutExtension}-squared.png`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + }; - const [backgroundColor, setBackgroundColor] = useLocalStorage< - "black" | "white" - >("squareTool_backgroundColor", "white"); + const plausible = usePlausible(); - const [squareImageContent, setSquareImageContent] = useState( - null, + if (!imageMetadata) { + return ( + ); + } + + return ( +
+
+ {squareImageContent && ( + Preview + )} +

+ {imageMetadata.name} +

+
+
+
+
+ Original + + {imageMetadata.width} × {imageMetadata.height} + +
+ +
+ Square Size + + {Math.max(imageMetadata.width, imageMetadata.height)} ×{" "} + {Math.max(imageMetadata.width, imageMetadata.height)} + +
+
- useEffect(() => { - if (imageContent && imageMetadata) { - const canvas = document.createElement("canvas"); - const size = Math.max(imageMetadata.width, imageMetadata.height); - canvas.width = size; - canvas.height = size; - - const ctx = canvas.getContext("2d"); - if (!ctx) return; - - // Fill background - ctx.fillStyle = backgroundColor; - ctx.fillRect(0, 0, size, size); - - // Load and center the image - const img = new Image(); - img.onload = () => { - const x = (size - imageMetadata.width) / 2; - const y = (size - imageMetadata.height) / 2; - ctx.drawImage(img, x, y); - setSquareImageContent(canvas.toDataURL("image/png")); - }; - img.src = imageContent; - } - }, [imageContent, imageMetadata, backgroundColor]); - - const handleSaveImage = () => { - if (squareImageContent && imageMetadata) { - const link = document.createElement("a"); - link.href = squareImageContent; - const originalFileName = imageMetadata.name; - const fileNameWithoutExtension = - originalFileName.substring(0, originalFileName.lastIndexOf(".")) || - originalFileName; - link.download = `${fileNameWithoutExtension}-squared.png`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } - }; - - const plausible = usePlausible(); - - if (!imageMetadata) { - return ( - - ); - } - - return ( -
-
- {squareImageContent && ( - Preview - )} -

- {imageMetadata.name} -

-
-
-
-
- Original - - {imageMetadata.width} × {imageMetadata.height} - -
- -
- Square Size - - {Math.max(imageMetadata.width, imageMetadata.height)} ×{" "} - {Math.max(imageMetadata.width, imageMetadata.height)} - -
-
- - - option.charAt(0).toUpperCase() + option.slice(1) - } - /> - -
- - -
-
+ + option.charAt(0).toUpperCase() + option.slice(1) + } + /> + +
+ +
- ); +
+
+ ); } export function SquareTool() { - const fileUploaderProps = useFileUploader(); - - return ( - - - - ); + const fileUploaderProps = useFileUploader(); + + return ( + + + + ); } diff --git a/src/app/(tools)/svg-to-png/svg-tool.tsx b/src/app/(tools)/svg-to-png/svg-tool.tsx index a9aae45..90a8605 100644 --- a/src/app/(tools)/svg-to-png/svg-tool.tsx +++ b/src/app/(tools)/svg-to-png/svg-tool.tsx @@ -9,223 +9,223 @@ import { SVGScaleSelector } from "@/components/svg-scale-selector"; export type Scale = "custom" | number; function scaleSvg(svgContent: string, scale: number) { - const parser = new DOMParser(); - const svgDoc = parser.parseFromString(svgContent, "image/svg+xml"); - const svgElement = svgDoc.documentElement; - const width = parseInt(svgElement.getAttribute("width") ?? "300"); - const height = parseInt(svgElement.getAttribute("height") ?? "150"); + const parser = new DOMParser(); + const svgDoc = parser.parseFromString(svgContent, "image/svg+xml"); + const svgElement = svgDoc.documentElement; + const width = parseInt(svgElement.getAttribute("width") ?? "300"); + const height = parseInt(svgElement.getAttribute("height") ?? "150"); - const scaledWidth = width * scale; - const scaledHeight = height * scale; + const scaledWidth = width * scale; + const scaledHeight = height * scale; - svgElement.setAttribute("width", scaledWidth.toString()); - svgElement.setAttribute("height", scaledHeight.toString()); + svgElement.setAttribute("width", scaledWidth.toString()); + svgElement.setAttribute("height", scaledHeight.toString()); - return new XMLSerializer().serializeToString(svgDoc); + return new XMLSerializer().serializeToString(svgDoc); } function useSvgConverter(props: { - canvas: HTMLCanvasElement | null; - svgContent: string; - scale: number; - fileName?: string; - imageMetadata: { width: number; height: number; name: string }; + canvas: HTMLCanvasElement | null; + svgContent: string; + scale: number; + fileName?: string; + imageMetadata: { width: number; height: number; name: string }; }) { - const { width, height, scaledSvg } = useMemo(() => { - const scaledSvg = scaleSvg(props.svgContent, props.scale); - - return { - width: props.imageMetadata.width * props.scale, - height: props.imageMetadata.height * props.scale, - scaledSvg, - }; - }, [props.svgContent, props.scale, props.imageMetadata]); - - const convertToPng = async () => { - const ctx = props.canvas?.getContext("2d"); - if (!ctx) throw new Error("Failed to get canvas context"); - - // Trigger a "save image" of the resulting canvas content - const saveImage = () => { - if (props.canvas) { - const dataURL = props.canvas.toDataURL("image/png"); - const link = document.createElement("a"); - link.href = dataURL; - const svgFileName = props.imageMetadata.name ?? "svg_converted"; - - // Remove the .svg extension - link.download = `${svgFileName.replace(".svg", "")}-${props.scale}x.png`; - link.click(); - } - }; - - const img = new Image(); - // Call saveImage after the image has been drawn - img.onload = () => { - ctx.drawImage(img, 0, 0); - saveImage(); - }; - - img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(scaledSvg)}`; - }; + const { width, height, scaledSvg } = useMemo(() => { + const scaledSvg = scaleSvg(props.svgContent, props.scale); return { - convertToPng, - canvasProps: { width: width, height: height }, + width: props.imageMetadata.width * props.scale, + height: props.imageMetadata.height * props.scale, + scaledSvg, + }; + }, [props.svgContent, props.scale, props.imageMetadata]); + + const convertToPng = async () => { + const ctx = props.canvas?.getContext("2d"); + if (!ctx) throw new Error("Failed to get canvas context"); + + // Trigger a "save image" of the resulting canvas content + const saveImage = () => { + if (props.canvas) { + const dataURL = props.canvas.toDataURL("image/png"); + const link = document.createElement("a"); + link.href = dataURL; + const svgFileName = props.imageMetadata.name ?? "svg_converted"; + + // Remove the .svg extension + link.download = `${svgFileName.replace(".svg", "")}-${props.scale}x.png`; + link.click(); + } + }; + + const img = new Image(); + // Call saveImage after the image has been drawn + img.onload = () => { + ctx.drawImage(img, 0, 0); + saveImage(); }; + + img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(scaledSvg)}`; + }; + + return { + convertToPng, + canvasProps: { width: width, height: height }, + }; } interface SVGRendererProps { - svgContent: string; + svgContent: string; } function SVGRenderer({ svgContent }: SVGRendererProps) { - const containerRef = useRef(null); - - useEffect(() => { - if (containerRef.current) { - containerRef.current.innerHTML = svgContent; - const svgElement = containerRef.current.querySelector("svg"); - if (svgElement) { - svgElement.setAttribute("width", "100%"); - svgElement.setAttribute("height", "100%"); - } - } - }, [svgContent]); - - return
; + const containerRef = useRef(null); + + useEffect(() => { + if (containerRef.current) { + containerRef.current.innerHTML = svgContent; + const svgElement = containerRef.current.querySelector("svg"); + if (svgElement) { + svgElement.setAttribute("width", "100%"); + svgElement.setAttribute("height", "100%"); + } + } + }, [svgContent]); + + return
; } function SaveAsPngButton({ + svgContent, + scale, + imageMetadata, +}: { + svgContent: string; + scale: number; + imageMetadata: { width: number; height: number; name: string }; +}) { + const [canvasRef, setCanvasRef] = useState(null); + const { convertToPng, canvasProps } = useSvgConverter({ + canvas: canvasRef, svgContent, scale, imageMetadata, -}: { - svgContent: string; - scale: number; - imageMetadata: { width: number; height: number; name: string }; -}) { - const [canvasRef, setCanvasRef] = useState(null); - const { convertToPng, canvasProps } = useSvgConverter({ - canvas: canvasRef, - svgContent, - scale, - imageMetadata, - }); - - const plausible = usePlausible(); - - return ( -
-
- ); + }); + + const plausible = usePlausible(); + + return ( +
+
+ ); } import { - type FileUploaderResult, - useFileUploader, + type FileUploaderResult, + useFileUploader, } from "@/hooks/use-file-uploader"; import { FileDropzone } from "@/components/shared/file-dropzone"; function SVGToolCore(props: { fileUploaderProps: FileUploaderResult }) { - const { rawContent, imageMetadata, handleFileUploadEvent, cancel } = - props.fileUploaderProps; + const { rawContent, imageMetadata, handleFileUploadEvent, cancel } = + props.fileUploaderProps; - const [scale, setScale] = useLocalStorage("svgTool_scale", 1); - const [customScale, setCustomScale] = useLocalStorage( - "svgTool_customScale", - 1, - ); - - // Get the actual numeric scale value - const effectiveScale = scale === "custom" ? customScale : scale; + const [scale, setScale] = useLocalStorage("svgTool_scale", 1); + const [customScale, setCustomScale] = useLocalStorage( + "svgTool_customScale", + 1, + ); - if (!imageMetadata) - return ( - - ); + // Get the actual numeric scale value + const effectiveScale = scale === "custom" ? customScale : scale; + if (!imageMetadata) return ( -
- {/* Preview Section */} -
- -

- {imageMetadata.name} -

-
-
- {/* Size Information */} -
-
- Original - - {imageMetadata.width} × {imageMetadata.height} - -
- -
- Scaled - - {imageMetadata.width * effectiveScale} ×{" "} - {imageMetadata.height * effectiveScale} - -
-
- - {/* Scale Controls */} - - - {/* Action Buttons */} -
- - -
-
-
+ ); + + return ( +
+ {/* Preview Section */} +
+ +

+ {imageMetadata.name} +

+
+
+ {/* Size Information */} +
+
+ Original + + {imageMetadata.width} × {imageMetadata.height} + +
+ +
+ Scaled + + {imageMetadata.width * effectiveScale} ×{" "} + {imageMetadata.height * effectiveScale} + +
+
+ + {/* Scale Controls */} + + + {/* Action Buttons */} +
+ + +
+
+
+ ); } export function SVGTool() { - const fileUploaderProps = useFileUploader(); - return ( - - - - ); + const fileUploaderProps = useFileUploader(); + return ( + + + + ); } From 648206eaa8d34e5d9ff64ec62976738347899213 Mon Sep 17 00:00:00 2001 From: Dhanush Reddy Chilakala Date: Wed, 13 Nov 2024 01:17:10 -0500 Subject: [PATCH 3/3] centered components --- src/app/(tools)/rounded-border/rounded-tool.tsx | 4 ++-- src/app/(tools)/square-image/square-tool.tsx | 6 +++--- src/app/(tools)/svg-to-png/svg-tool.tsx | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/(tools)/rounded-border/rounded-tool.tsx b/src/app/(tools)/rounded-border/rounded-tool.tsx index 2138ddf..043bd4a 100644 --- a/src/app/(tools)/rounded-border/rounded-tool.tsx +++ b/src/app/(tools)/rounded-border/rounded-tool.tsx @@ -183,8 +183,8 @@ function RoundedToolCore(props: { fileUploaderProps: FileUploaderResult }) { } return ( -
-
+
+
-
+
+
{squareImageContent && (
-
+
Original diff --git a/src/app/(tools)/svg-to-png/svg-tool.tsx b/src/app/(tools)/svg-to-png/svg-tool.tsx index 90a8605..cffdec4 100644 --- a/src/app/(tools)/svg-to-png/svg-tool.tsx +++ b/src/app/(tools)/svg-to-png/svg-tool.tsx @@ -161,9 +161,9 @@ function SVGToolCore(props: { fileUploaderProps: FileUploaderResult }) { ); return ( -
+
{/* Preview Section */} -
+

{imageMetadata.name}