From d007684208c37ab877ddc7780eaa153d94009ad3 Mon Sep 17 00:00:00 2001 From: andre mcgruder Date: Fri, 3 Jan 2025 12:18:37 -0600 Subject: [PATCH 1/7] issue73 custom resize NaN input issue73-custom-resize-NaN-input --- src/components/svg-scale-selector.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/components/svg-scale-selector.tsx b/src/components/svg-scale-selector.tsx index f2eb628..e1aa9e4 100644 --- a/src/components/svg-scale-selector.tsx +++ b/src/components/svg-scale-selector.tsx @@ -67,19 +67,29 @@ export function SVGScaleSelector({ {selected === "custom" && ( { - const value = Math.min(64, parseFloat(e.target.value)); + // Clear input if first character is 0 + if (e.target.value.startsWith("0")) { + e.target.value = ""; + return; + } + + const parsed = parseFloat(e.target.value); + const value = Math.min( + 64, + Math.max(1, isNaN(parsed) ? 1 : parsed), + ); onCustomValueChange?.(value); }} onKeyDown={(e) => { if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return; e.preventDefault(); - const currentValue = customValue ?? 0; + const currentValue = customValue ?? 1; let step = 1; if (e.shiftKey) step = 10; @@ -90,7 +100,7 @@ export function SVGScaleSelector({ const clampedValue = Math.min( 64, - Math.max(0, Number(newValue.toFixed(1))), + Math.max(1, Number(newValue.toFixed(1))), ); onCustomValueChange?.(clampedValue); }} From 88115912ba799b18d0813be7198c89848edda5b6 Mon Sep 17 00:00:00 2001 From: andre mcgruder Date: Sun, 5 Jan 2025 23:32:57 -0600 Subject: [PATCH 2/7] issue-106-batch-svg-to-png issue-106-batch-svg-to-png --- src/app/(tools)/svg-to-png/svg-tool.tsx | 116 +++++++++++++++++++++++- 1 file changed, 113 insertions(+), 3 deletions(-) diff --git a/src/app/(tools)/svg-to-png/svg-tool.tsx b/src/app/(tools)/svg-to-png/svg-tool.tsx index 137d778..674abca 100644 --- a/src/app/(tools)/svg-to-png/svg-tool.tsx +++ b/src/app/(tools)/svg-to-png/svg-tool.tsx @@ -46,9 +46,9 @@ function useSvgConverter(props: { if (!ctx) throw new Error("Failed to get canvas context"); // Trigger a "save image" of the resulting canvas content - const saveImage = () => { + const saveImage = () => { if (props.canvas) { - const dataURL = props.canvas.toDataURL("image/png"); + const dataURL = props.canvas.toDataURL("image/png"); const link = document.createElement("a"); link.href = dataURL; const svgFileName = props.imageMetadata.name ?? "svg_converted"; @@ -96,14 +96,95 @@ function SVGRenderer({ svgContent }: SVGRendererProps) { return
; } +// Add new type for batch items +interface BatchItem { + id: string; + scale: number; + fileName: string; + svgContent: string; + imageMetadata: { width: number; height: number; name: string }; +} + +// Modify the batch conversion utility to not use hooks +async function convertSvgToPng(item: BatchItem) { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + if (!ctx) throw new Error("Failed to get canvas context"); + + // Scale the SVG + const scaledSvg = scaleSvg(item.svgContent, item.scale); + + // Set canvas dimensions + canvas.width = item.imageMetadata.width * item.scale; + canvas.height = item.imageMetadata.height * item.scale; + + // Create and load image + return new Promise((resolve) => { + const img = new Image(); + img.onload = () => { + ctx.drawImage(img, 0, 0); + const dataURL = canvas.toDataURL("image/png"); + const link = document.createElement("a"); + link.href = dataURL; + link.download = item.fileName; + link.click(); + resolve(); + }; + img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(scaledSvg)}`; + }); +} + +// Update BatchPreview to use the new conversion function +function BatchPreview({ items, onRemove }: { items: BatchItem[]; onRemove: (id: string) => void }) { + if (items.length === 0) return null; + + const handleDownloadAll = async () => { + for (const item of items) { + await convertSvgToPng(item); + } + }; + + return ( +
+
+
+

Batch Queue ({items.length})

+ +
+
+ {items.map((item) => ( +
+ {item.fileName} ({item.scale}x) + +
+ ))} +
+
+
+ ); +} + +// Modify SaveAsPngButton to include batch functionality function SaveAsPngButton({ svgContent, scale, imageMetadata, + onAddToBatch, }: { svgContent: string; scale: number; imageMetadata: { width: number; height: number; name: string }; + onAddToBatch: (item: BatchItem) => void; }) { const [canvasRef, setCanvasRef] = useState(null); const { convertToPng, canvasProps } = useSvgConverter({ @@ -116,7 +197,7 @@ function SaveAsPngButton({ const plausible = usePlausible(); return ( -
+
); } @@ -150,6 +246,16 @@ function SVGToolCore(props: { fileUploaderProps: FileUploaderResult }) { // Get the actual numeric scale value const effectiveScale = scale === "custom" ? customScale : scale; + const [batchItems, setBatchItems] = useState([]); + + const addToBatch = (item: BatchItem) => { + setBatchItems((prev) => [...prev, item]); + }; + + const removeFromBatch = (id: string) => { + setBatchItems((prev) => prev.filter((item) => item.id !== id)); + }; + if (!imageMetadata) return (
+ + {/* Batch Preview */} +
); } From dfeee1eccf768879f63a09892b2075a5e7c667d4 Mon Sep 17 00:00:00 2001 From: andre mcgruder Date: Mon, 6 Jan 2025 19:38:35 -0600 Subject: [PATCH 3/7] issue 106 prevent duplicate png in batch issue 106 prevent duplicate png in batch --- src/app/(tools)/svg-to-png/svg-tool.tsx | 89 ++++++++++++++++++------- 1 file changed, 64 insertions(+), 25 deletions(-) diff --git a/src/app/(tools)/svg-to-png/svg-tool.tsx b/src/app/(tools)/svg-to-png/svg-tool.tsx index 674abca..c31c0fb 100644 --- a/src/app/(tools)/svg-to-png/svg-tool.tsx +++ b/src/app/(tools)/svg-to-png/svg-tool.tsx @@ -46,9 +46,9 @@ function useSvgConverter(props: { if (!ctx) throw new Error("Failed to get canvas context"); // Trigger a "save image" of the resulting canvas content - const saveImage = () => { + const saveImage = () => { if (props.canvas) { - const dataURL = props.canvas.toDataURL("image/png"); + const dataURL = props.canvas.toDataURL("image/png"); const link = document.createElement("a"); link.href = dataURL; const svgFileName = props.imageMetadata.name ?? "svg_converted"; @@ -107,13 +107,13 @@ interface BatchItem { // Modify the batch conversion utility to not use hooks async function convertSvgToPng(item: BatchItem) { - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); if (!ctx) throw new Error("Failed to get canvas context"); // Scale the SVG const scaledSvg = scaleSvg(item.svgContent, item.scale); - + // Set canvas dimensions canvas.width = item.imageMetadata.width * item.scale; canvas.height = item.imageMetadata.height * item.scale; @@ -128,14 +128,20 @@ async function convertSvgToPng(item: BatchItem) { link.href = dataURL; link.download = item.fileName; link.click(); - resolve(); + resolve(); }; img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(scaledSvg)}`; }); } // Update BatchPreview to use the new conversion function -function BatchPreview({ items, onRemove }: { items: BatchItem[]; onRemove: (id: string) => void }) { +function BatchPreview({ + items, + onRemove, +}: { + items: BatchItem[]; + onRemove: (id: string) => void; +}) { if (items.length === 0) return null; const handleDownloadAll = async () => { @@ -148,7 +154,9 @@ function BatchPreview({ items, onRemove }: { items: BatchItem[]; onRemove: (id:
-

Batch Queue ({items.length})

+

+ Batch Queue ({items.length}) +

{items.map((item) => ( -
- {item.fileName} ({item.scale}x) +
+ + {item.fileName} ({item.scale}x) + - + {/* Action Buttons and Toast Container */} +
+
+ + +
+ + {/* Duplicate Toast */} + {duplicateToast && ( +
+ This size conversion is already in the batch +
+ )}
{/* Batch Preview */} From 479f5504d8cfc524b4d02c18b16a4fc492853859 Mon Sep 17 00:00:00 2001 From: andre mcgruder Date: Mon, 6 Jan 2025 19:39:28 -0600 Subject: [PATCH 4/7] issue 106 prevent duplicate png in batch issue 106 prevent duplicate png in batch --- src/app/globals.css | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/app/globals.css b/src/app/globals.css index 6a8b964..5cdb655 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -12,3 +12,18 @@ body { background: var(--background); font-family: Arial, Helvetica, sans-serif; } + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-in { + animation: fadeIn 0.2s ease-out; +} From 923fe63f8a89af9da5df88982e3533bc62b0c835 Mon Sep 17 00:00:00 2001 From: andre mcgruder Date: Mon, 6 Jan 2025 20:04:51 -0600 Subject: [PATCH 5/7] revert to original clone revert to original clone --- src/components/svg-scale-selector.tsx | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/components/svg-scale-selector.tsx b/src/components/svg-scale-selector.tsx index e1aa9e4..f2eb628 100644 --- a/src/components/svg-scale-selector.tsx +++ b/src/components/svg-scale-selector.tsx @@ -67,29 +67,19 @@ export function SVGScaleSelector({ {selected === "custom" && ( { - // Clear input if first character is 0 - if (e.target.value.startsWith("0")) { - e.target.value = ""; - return; - } - - const parsed = parseFloat(e.target.value); - const value = Math.min( - 64, - Math.max(1, isNaN(parsed) ? 1 : parsed), - ); + const value = Math.min(64, parseFloat(e.target.value)); onCustomValueChange?.(value); }} onKeyDown={(e) => { if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return; e.preventDefault(); - const currentValue = customValue ?? 1; + const currentValue = customValue ?? 0; let step = 1; if (e.shiftKey) step = 10; @@ -100,7 +90,7 @@ export function SVGScaleSelector({ const clampedValue = Math.min( 64, - Math.max(1, Number(newValue.toFixed(1))), + Math.max(0, Number(newValue.toFixed(1))), ); onCustomValueChange?.(clampedValue); }} From d3e4ff11e795048c1b2a49783da61ee9f86acbd0 Mon Sep 17 00:00:00 2001 From: andre mcgruder Date: Mon, 6 Jan 2025 20:15:02 -0600 Subject: [PATCH 6/7] reset to origin code reset to origin code --- src/app/(tools)/svg-to-png/svg-tool.tsx | 177 ++---------------------- 1 file changed, 14 insertions(+), 163 deletions(-) diff --git a/src/app/(tools)/svg-to-png/svg-tool.tsx b/src/app/(tools)/svg-to-png/svg-tool.tsx index c31c0fb..137d778 100644 --- a/src/app/(tools)/svg-to-png/svg-tool.tsx +++ b/src/app/(tools)/svg-to-png/svg-tool.tsx @@ -96,108 +96,14 @@ function SVGRenderer({ svgContent }: SVGRendererProps) { return
; } -// Add new type for batch items -interface BatchItem { - id: string; - scale: number; - fileName: string; - svgContent: string; - imageMetadata: { width: number; height: number; name: string }; -} - -// Modify the batch conversion utility to not use hooks -async function convertSvgToPng(item: BatchItem) { - const canvas = document.createElement("canvas"); - const ctx = canvas.getContext("2d"); - if (!ctx) throw new Error("Failed to get canvas context"); - - // Scale the SVG - const scaledSvg = scaleSvg(item.svgContent, item.scale); - - // Set canvas dimensions - canvas.width = item.imageMetadata.width * item.scale; - canvas.height = item.imageMetadata.height * item.scale; - - // Create and load image - return new Promise((resolve) => { - const img = new Image(); - img.onload = () => { - ctx.drawImage(img, 0, 0); - const dataURL = canvas.toDataURL("image/png"); - const link = document.createElement("a"); - link.href = dataURL; - link.download = item.fileName; - link.click(); - resolve(); - }; - img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(scaledSvg)}`; - }); -} - -// Update BatchPreview to use the new conversion function -function BatchPreview({ - items, - onRemove, -}: { - items: BatchItem[]; - onRemove: (id: string) => void; -}) { - if (items.length === 0) return null; - - const handleDownloadAll = async () => { - for (const item of items) { - await convertSvgToPng(item); - } - }; - - return ( -
-
-
-

- Batch Queue ({items.length}) -

- -
-
- {items.map((item) => ( -
- - {item.fileName} ({item.scale}x) - - -
- ))} -
-
-
- ); -} - -// Modify SaveAsPngButton to include batch functionality function SaveAsPngButton({ svgContent, scale, imageMetadata, - onAddToBatch, }: { svgContent: string; scale: number; imageMetadata: { width: number; height: number; name: string }; - onAddToBatch: (item: BatchItem) => void; }) { const [canvasRef, setCanvasRef] = useState(null); const { convertToPng, canvasProps } = useSvgConverter({ @@ -210,7 +116,7 @@ function SaveAsPngButton({ const plausible = usePlausible(); return ( -
+
); } @@ -259,33 +150,6 @@ function SVGToolCore(props: { fileUploaderProps: FileUploaderResult }) { // Get the actual numeric scale value const effectiveScale = scale === "custom" ? customScale : scale; - const [batchItems, setBatchItems] = useState([]); - const [duplicateToast, setDuplicateToast] = useState(false); - - const addToBatch = (item: BatchItem) => { - setBatchItems((prev) => { - const isDuplicate = prev.some( - (existingItem) => - existingItem.scale === item.scale && - existingItem.fileName === item.fileName, - ); - - if (isDuplicate) { - // Show toast - setDuplicateToast(true); - // Hide toast after 3 seconds - setTimeout(() => setDuplicateToast(false), 3000); - return prev; - } - - return [...prev, item]; - }); - }; - - const removeFromBatch = (id: string) => { - setBatchItems((prev) => prev.filter((item) => item.id !== id)); - }; - if (!imageMetadata) return ( - {/* Action Buttons and Toast Container */} -
-
- - -
- - {/* Duplicate Toast */} - {duplicateToast && ( -
- This size conversion is already in the batch -
- )} + {/* Action Buttons */} +
+ +
- - {/* Batch Preview */} -
); } From 2d9836dd98521227b247ec9d8fb7f35fecd0e04d Mon Sep 17 00:00:00 2001 From: andre mcgruder Date: Mon, 6 Jan 2025 20:15:23 -0600 Subject: [PATCH 7/7] reset to origin code reset to origin code --- src/app/globals.css | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 5cdb655..6a8b964 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -12,18 +12,3 @@ body { background: var(--background); font-family: Arial, Helvetica, sans-serif; } - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.animate-fade-in { - animation: fadeIn 0.2s ease-out; -}