From 6c1f8363bfc8d53f30b536188119f28796f420a5 Mon Sep 17 00:00:00 2001 From: SnapOtter Date: Mon, 27 Apr 2026 22:10:20 +0800 Subject: [PATCH] feat(web): add target file size controls to image-to-pdf settings --- .../tools/image-to-pdf-settings.tsx | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/tools/image-to-pdf-settings.tsx b/apps/web/src/components/tools/image-to-pdf-settings.tsx index 0df9391d..62be4eb2 100644 --- a/apps/web/src/components/tools/image-to-pdf-settings.tsx +++ b/apps/web/src/components/tools/image-to-pdf-settings.tsx @@ -111,6 +111,13 @@ export function ImageToPdfSettings() { const [orientation, setOrientation] = useState<"portrait" | "landscape">("portrait"); const [margin, setMargin] = useState(20); const [downloadUrl, setDownloadUrl] = useState(null); + const [targetSizeValue, setTargetSizeValue] = useState(""); + const [targetSizeUnit, setTargetSizeUnit] = useState<"KB" | "MB">("MB"); + const [compressionResult, setCompressionResult] = useState<{ + targetMet: boolean; + jpegQuality: number; + targetRequested: number; + } | null>(null); // Local processing state so the ProgressCard renders reliably const [busy, setBusy] = useState(false); @@ -151,6 +158,7 @@ export function ImageToPdfSettings() { setProcessing(true); setError(null); setDownloadUrl(null); + setCompressionResult(null); setProgress({ phase: "uploading", percent: 0, elapsed: 0 }); }); @@ -163,7 +171,14 @@ export function ImageToPdfSettings() { for (const file of files) { formData.append("file", file); } - formData.append("settings", JSON.stringify({ pageSize, orientation, margin })); + const settings: Record = { pageSize, orientation, margin }; + if (targetSizeValue.trim() !== "") { + const numVal = Number.parseFloat(targetSizeValue); + if (!Number.isNaN(numVal) && numVal > 0) { + settings.targetSize = { value: numVal, unit: targetSizeUnit }; + } + } + formData.append("settings", JSON.stringify(settings)); const xhr = new XMLHttpRequest(); xhrRef.current = xhr; @@ -194,6 +209,7 @@ export function ImageToPdfSettings() { try { const result = JSON.parse(xhr.responseText); setDownloadUrl(result.downloadUrl); + setCompressionResult(result.compression ?? null); setProgress((prev) => ({ ...prev, phase: "complete", percent: 100 })); } catch { setError("Failed to parse server response"); @@ -224,7 +240,16 @@ export function ImageToPdfSettings() { xhr.setRequestHeader(key, value); }); xhr.send(formData); - }, [files, pageSize, orientation, margin, setProcessing, setError]); + }, [ + files, + pageSize, + orientation, + margin, + targetSizeValue, + targetSizeUnit, + setProcessing, + setError, + ]); const hasFiles = files.length > 0; @@ -290,6 +315,35 @@ export function ImageToPdfSettings() { /> +
+ +
+ setTargetSizeValue(e.target.value)} + className="flex-1 px-2 py-1.5 rounded border border-border bg-background text-sm text-foreground" + data-testid="image-to-pdf-target-size-value" + /> + +
+

Leave empty for maximum quality

+
+ )} + + {compressionResult && ( +
+ {compressionResult.targetMet ? ( + Target met (JPEG quality: {compressionResult.jpegQuality}%) + ) : ( + Could not reach target. File compressed to minimum quality. + )} +
+ )} ); }