From aa87c7e5e1c987b96a165c29e2dac031bf929cdf Mon Sep 17 00:00:00 2001 From: SnapOtter Date: Mon, 11 May 2026 21:14:17 +0800 Subject: [PATCH] feat: add expansion preview for content-aware crop mode --- apps/web/src/lib/tool-registry.tsx | 1 + apps/web/src/pages/tool-page.tsx | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/apps/web/src/lib/tool-registry.tsx b/apps/web/src/lib/tool-registry.tsx index 7ab686ca..9274da6c 100644 --- a/apps/web/src/lib/tool-registry.tsx +++ b/apps/web/src/lib/tool-registry.tsx @@ -36,6 +36,7 @@ export interface CropProps { onCropChange: (crop: Crop) => void; onAspectChange: (aspect: number | undefined) => void; onGridToggle: (show: boolean) => void; + onModeChange?: (mode: "standard" | "content-aware") => void; } export interface EraserProps { diff --git a/apps/web/src/pages/tool-page.tsx b/apps/web/src/pages/tool-page.tsx index 6c1b7d3f..1a8f0af8 100644 --- a/apps/web/src/pages/tool-page.tsx +++ b/apps/web/src/pages/tool-page.tsx @@ -229,6 +229,7 @@ export function ToolPage() { width: number; height: number; } | null>(null); + const [cropMode, setCropMode] = useState<"standard" | "content-aware">("standard"); const cropState = useMemo( () => ({ @@ -266,6 +267,7 @@ export function ToolPage() { setCropAspect(undefined); setCropShowGrid(true); setCropImgDimensions(null); + setCropMode("standard"); setEraserHasStrokes(false); setEraserBrushSize(30); setEraserSliderInitPos(null); @@ -363,8 +365,11 @@ export function ToolPage() { ? canBrowserPreview(processedUrl, currentEntry?.processedFilename) : false; // Use server-generated preview for non-previewable formats (HEIC, TIFF). - // Always a string when hasProcessed is true (processedUrl is non-null). - const displayUrl = (processedPreviewUrl ?? processedUrl) as string; + // Falls back to the upload-decoded blobUrl so TIFF/DNG always have a renderable src. + const displayUrl = (processedPreviewUrl ?? + (isProcessedPreviewable ? processedUrl : null) ?? + originalBlobUrl ?? + processedUrl) as string; // Build settings props const settingsProps = { @@ -379,6 +384,7 @@ export function ToolPage() { onCropChange: setCropCrop, onAspectChange: setCropAspect, onGridToggle: setCropShowGrid, + onModeChange: setCropMode, } : undefined, eraserProps: @@ -438,6 +444,11 @@ export function ToolPage() { } if (displayMode === "interactive-crop" && hasFile && !hasProcessed && originalBlobUrl) { + if (cropMode === "content-aware") { + const fname = selectedFileName ?? files[0].name; + const fsize = selectedFileSize ?? files[0].size; + return ; + } return (
@@ -705,7 +716,11 @@ export function ToolPage() { fileSize={processedSize} fileType={processedFileType} downloadUrl={processedUrl} - previewUrl={isProcessedPreviewable ? processedUrl : (processedPreviewUrl ?? undefined)} + previewUrl={ + isProcessedPreviewable + ? processedUrl + : (processedPreviewUrl ?? originalBlobUrl ?? undefined) + } onUndo={handleUndo} currentToolId={tool?.id ?? ""} />