From 1a3e8e7ecf70f70d34ac4f35dbf7268edac04ce6 Mon Sep 17 00:00:00 2001 From: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com> Date: Tue, 2 Sep 2025 15:09:05 +0100 Subject: [PATCH] Undo Button -> review state. Result files added to recents (#4337) Produced PDFs go into recent files Undo button added to review state Undo causes undoConsume which replaces result files with source files. Removes result files from recent files too --------- Co-authored-by: Connor Yoh --- .claude/settings.local.json | 5 +- frontend/package.json | 1 + .../public/locales/en-GB/translation.json | 5 +- .../tools/shared/ReviewToolStep.tsx | 86 ++++++--- .../tools/shared/createToolFlow.tsx | 4 +- frontend/src/contexts/FileContext.tsx | 14 +- frontend/src/contexts/file/FileReducer.ts | 79 ++++---- frontend/src/contexts/file/fileActions.ts | 178 ++++++++++++++++-- frontend/src/contexts/file/fileHooks.ts | 7 +- .../useAddPasswordOperation.test.ts | 1 + .../useChangePermissionsOperation.test.ts | 1 + .../useRemovePasswordOperation.test.ts | 1 + .../src/hooks/tools/shared/useBaseTool.ts | 7 + .../hooks/tools/shared/useToolOperation.ts | 102 +++++++++- frontend/src/tools/AddPassword.tsx | 6 + frontend/src/tools/AddWatermark.tsx | 6 + frontend/src/tools/Automate.tsx | 8 +- frontend/src/tools/ChangePermissions.tsx | 1 + frontend/src/tools/Compress.tsx | 1 + frontend/src/tools/Convert.tsx | 6 + frontend/src/tools/OCR.tsx | 6 + frontend/src/tools/RemoveCertificateSign.tsx | 1 + frontend/src/tools/RemovePassword.tsx | 1 + frontend/src/tools/Repair.tsx | 1 + frontend/src/tools/Sanitize.tsx | 1 + frontend/src/tools/SingleLargePage.tsx | 1 + frontend/src/tools/Split.tsx | 1 + frontend/src/tools/UnlockPdfForms.tsx | 1 + frontend/src/types/fileContext.ts | 4 +- 29 files changed, 440 insertions(+), 96 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 54c5f7b19..877d932ef 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -13,9 +13,10 @@ "Bash(npx tsc:*)", "Bash(node:*)", "Bash(npm run dev:*)", - "Bash(sed:*)" + "Bash(sed:*)", + "Bash(npm run typecheck:*)" ], "deny": [], "defaultMode": "acceptEdits" } -} +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 13c795fc2..160c96c18 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,6 +42,7 @@ "prebuild": "npm run generate-icons", "build": "npx tsc --noEmit && vite build", "preview": "vite preview", + "typecheck": "tsc --noEmit", "generate-licenses": "node scripts/generate-licenses.js", "generate-icons": "node scripts/generate-icons.js", "generate-icons:verbose": "node scripts/generate-icons.js --verbose", diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 70aeefbf4..637ab59e1 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -41,6 +41,9 @@ "save": "Save", "saveToBrowser": "Save to Browser", "download": "Download", + "undoOperationTooltip": "Click to undo the last operation and restore the original files", + "undo": "Undo", + "moreOptions": "More Options", "editYourNewFiles": "Edit your new file(s)", "close": "Close", "fileSelected": "Selected: {{filename}}", @@ -2382,4 +2385,4 @@ "processImagesDesc": "Converts multiple image files into a single PDF document, then applies OCR technology to extract searchable text from the images." } } -} \ No newline at end of file +} diff --git a/frontend/src/components/tools/shared/ReviewToolStep.tsx b/frontend/src/components/tools/shared/ReviewToolStep.tsx index 475715704..364077e4f 100644 --- a/frontend/src/components/tools/shared/ReviewToolStep.tsx +++ b/frontend/src/components/tools/shared/ReviewToolStep.tsx @@ -1,27 +1,48 @@ -import React, { useEffect, useRef } from 'react'; -import { Button, Stack, Text } from '@mantine/core'; -import { useTranslation } from 'react-i18next'; -import DownloadIcon from '@mui/icons-material/Download'; -import ErrorNotification from './ErrorNotification'; -import ResultsPreview from './ResultsPreview'; -import { SuggestedToolsSection } from './SuggestedToolsSection'; -import { ToolOperationHook } from '../../../hooks/tools/shared/useToolOperation'; +import React, { useEffect, useRef, useState } from "react"; +import { Button, Group, Stack } from "@mantine/core"; +import { useTranslation } from "react-i18next"; +import DownloadIcon from "@mui/icons-material/Download"; +import UndoIcon from "@mui/icons-material/Undo"; +import ErrorNotification from "./ErrorNotification"; +import ResultsPreview from "./ResultsPreview"; +import { SuggestedToolsSection } from "./SuggestedToolsSection"; +import { ToolOperationHook } from "../../../hooks/tools/shared/useToolOperation"; +import { Tooltip } from "../../shared/Tooltip"; export interface ReviewToolStepProps { isVisible: boolean; operation: ToolOperationHook; title?: string; onFileClick?: (file: File) => void; + onUndo: () => void; } -function ReviewStepContent({ operation, onFileClick }: { operation: ToolOperationHook; onFileClick?: (file: File) => void }) { +function ReviewStepContent({ + operation, + onFileClick, + onUndo, +}: { + operation: ToolOperationHook; + onFileClick?: (file: File) => void; + onUndo: () => void; +}) { const { t } = useTranslation(); const stepRef = useRef(null); - const previewFiles = operation.files?.map((file, index) => ({ - file, - thumbnail: operation.thumbnails[index] - })) || []; + const handleUndo = async () => { + try { + onUndo(); + } catch (error) { + // Error is already handled by useToolOperation, just reset loading state + console.error("Undo operation failed:", error); + } + }; + + const previewFiles = + operation.files?.map((file, index) => ({ + file, + thumbnail: operation.thumbnails[index], + })) || []; // Auto-scroll to bottom when content appears useEffect(() => { @@ -31,7 +52,7 @@ function ReviewStepContent({ operation, onFileClick }: { oper setTimeout(() => { scrollableContainer.scrollTo({ top: scrollableContainer.scrollHeight, - behavior: 'smooth' + behavior: "smooth", }); }, 100); // Small delay to ensure content is rendered } @@ -40,10 +61,7 @@ function ReviewStepContent({ operation, onFileClick }: { oper return ( - + {previewFiles.length > 0 && ( ({ operation, onFileClick }: { oper /> )} - {operation.downloadUrl && ( + + + + {operation.downloadUrl && (