From 564b14e3e27b9e19fa898de76fc814166c6cbd32 Mon Sep 17 00:00:00 2001 From: Reece Browne Date: Wed, 3 Sep 2025 14:35:50 +0100 Subject: [PATCH] Merge --- .claude/settings.local.json | 6 +- frontend/package.json | 1 + .../public/locales/en-GB/translation.json | 3 + .../src/components/fileEditor/FileEditor.tsx | 4 +- .../tools/shared/ReviewToolStep.tsx | 86 +++-- .../tools/shared/createToolFlow.tsx | 4 +- frontend/src/contexts/FileContext.tsx | 86 +++-- frontend/src/contexts/NavigationContext.tsx | 5 + frontend/src/contexts/file/FileReducer.ts | 139 ++++---- frontend/src/contexts/file/fileActions.ts | 318 +++++++++++++----- frontend/src/contexts/file/fileHooks.ts | 43 ++- .../useAddPasswordOperation.test.ts | 1 + .../useChangePermissionsOperation.test.ts | 1 + .../useRemovePasswordOperation.test.ts | 1 + .../src/hooks/tools/shared/useBaseTool.ts | 126 +++++++ .../hooks/tools/shared/useToolOperation.ts | 108 +++++- frontend/src/hooks/useFileHandler.ts | 6 +- frontend/src/tools/AddPassword.tsx | 6 + frontend/src/tools/AddWatermark.tsx | 6 + frontend/src/tools/Automate.tsx | 14 +- frontend/src/tools/ChangePermissions.tsx | 84 ++--- frontend/src/tools/Compress.tsx | 83 ++--- frontend/src/tools/Convert.tsx | 6 + frontend/src/tools/OCR.tsx | 6 + frontend/src/tools/RemoveCertificateSign.tsx | 74 +--- frontend/src/tools/RemovePassword.tsx | 83 ++--- frontend/src/tools/Repair.tsx | 72 +--- frontend/src/tools/Sanitize.tsx | 80 ++--- frontend/src/tools/SingleLargePage.tsx | 74 +--- frontend/src/tools/Split.tsx | 92 ++--- frontend/src/tools/UnlockPdfForms.tsx | 74 +--- frontend/src/types/fileContext.ts | 62 ++-- 32 files changed, 944 insertions(+), 810 deletions(-) create mode 100644 frontend/src/hooks/tools/shared/useBaseTool.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 54c5f7b19..ec79534e2 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -13,9 +13,11 @@ "Bash(npx tsc:*)", "Bash(node:*)", "Bash(npm run dev:*)", - "Bash(sed:*)" + "Bash(sed:*)", + "Bash(npm run typecheck:*)", + "Bash(git checkout:*)" ], "deny": [], "defaultMode": "acceptEdits" } -} +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index eaa5f20d4..9e9e37dab 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -41,6 +41,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 7fa1036db..17959bf51 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}}", diff --git a/frontend/src/components/fileEditor/FileEditor.tsx b/frontend/src/components/fileEditor/FileEditor.tsx index cc76c74bf..d6eef549e 100644 --- a/frontend/src/components/fileEditor/FileEditor.tsx +++ b/frontend/src/components/fileEditor/FileEditor.tsx @@ -350,9 +350,9 @@ const FileEditor = ({ if (record) { // Set the file as selected in context and switch to viewer for preview setSelectedFiles([fileId as FileId]); - navActions.setMode('viewer'); + navActions.setWorkbench('viewer'); } - }, [activeFileRecords, setSelectedFiles, navActions.setMode]); + }, [activeFileRecords, setSelectedFiles, navActions.setWorkbench]); const handleMergeFromHere = useCallback((fileId: string) => { const startIndex = activeFileRecords.findIndex(r => r.id === fileId); 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 && (