From ab6edd3196d3941a66b60d1773056f3d6f931a4e Mon Sep 17 00:00:00 2001 From: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:29:38 +0100 Subject: [PATCH] Feature/v2/toggle_for_auto_unzip (#4584) ## default {BF57458D-50A6-4057-94F1-D6AB4628EFD8} ## disabled {140DB87B-05CF-4E0E-A14A-ED15075BD2EE} ## unzip options {482CE185-73D5-4D90-91BB-B9305C711391} {4DFCA96D-792D-4370-8C62-4BA42C9F1A5F} ## pop up and maintains version metadata {7F2A785C-5717-4A79-9D45-74BDA46DF273} --------- Co-authored-by: Connor Yoh --- .../public/locales/en-GB/translation.json | 11 ++ frontend/src/App.tsx | 41 +++-- .../src/components/fileEditor/FileEditor.tsx | 46 ++++- .../fileEditor/FileEditorThumbnail.tsx | 17 ++ .../components/fileManager/FileListItem.tsx | 23 ++- .../shared/config/configNavSections.tsx | 7 + .../config/configSections/GeneralSection.tsx | 89 ++++++++++ frontend/src/contexts/FileManagerContext.tsx | 28 +++ frontend/src/contexts/PreferencesContext.tsx | 73 ++++++++ .../useScannerImageSplitOperation.ts | 57 +++--- .../hooks/tools/shared/useToolOperation.ts | 1 + .../hooks/tools/shared/useToolResources.ts | 50 ++++-- frontend/src/services/indexedDBManager.ts | 9 + frontend/src/services/preferencesService.ts | 129 ++++++++++++++ frontend/src/services/zipFileService.ts | 166 ++++++++++++++---- .../tests/convert/ConvertIntegration.test.tsx | 9 +- .../ConvertSmartDetectionIntegration.test.tsx | 9 +- 17 files changed, 661 insertions(+), 104 deletions(-) create mode 100644 frontend/src/components/shared/config/configSections/GeneralSection.tsx create mode 100644 frontend/src/contexts/PreferencesContext.tsx create mode 100644 frontend/src/services/preferencesService.ts diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index b28923f63..76a498d2d 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -257,6 +257,16 @@ "name": "Save form inputs", "help": "Enable to store previously used inputs for future runs" }, + "general": { + "title": "General", + "description": "Configure general application preferences.", + "autoUnzip": "Auto-unzip API responses", + "autoUnzipDescription": "Automatically extract files from ZIP responses", + "autoUnzipTooltip": "Automatically extract ZIP files returned from API operations. Disable to keep ZIP files intact. This does not affect automation workflows.", + "autoUnzipFileLimit": "Auto-unzip file limit", + "autoUnzipFileLimitDescription": "Maximum number of files to extract from ZIP", + "autoUnzipFileLimitTooltip": "Only unzip if the ZIP contains this many files or fewer. Set higher to extract larger ZIPs." + }, "hotkeys": { "title": "Keyboard Shortcuts", "description": "Hover a tool to see its shortcut or customise it below. Click \"Change shortcut\" and press a new key combination. Press Esc to cancel.", @@ -3187,6 +3197,7 @@ "lastModified": "Last Modified", "toolChain": "Tools Applied", "restore": "Restore", + "unzip": "Unzip", "searchFiles": "Search files...", "recent": "Recent", "localFiles": "Local Files", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d926b9f0d..88c19649f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,6 +6,7 @@ import { FilesModalProvider } from "./contexts/FilesModalContext"; import { ToolWorkflowProvider } from "./contexts/ToolWorkflowContext"; import { HotkeyProvider } from "./contexts/HotkeyContext"; import { SidebarProvider } from "./contexts/SidebarContext"; +import { PreferencesProvider } from "./contexts/PreferencesContext"; import ErrorBoundary from "./components/shared/ErrorBoundary"; import HomePage from "./pages/HomePage"; @@ -41,25 +42,27 @@ export default function App() { }> - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/components/fileEditor/FileEditor.tsx b/frontend/src/components/fileEditor/FileEditor.tsx index 305903b0c..626eaab4f 100644 --- a/frontend/src/components/fileEditor/FileEditor.tsx +++ b/frontend/src/components/fileEditor/FileEditor.tsx @@ -3,7 +3,7 @@ import { Text, Center, Box, LoadingOverlay, Stack, Group } from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; -import { useFileSelection, useFileState, useFileManagement } from '../../contexts/FileContext'; +import { useFileSelection, useFileState, useFileManagement, useFileActions } from '../../contexts/FileContext'; import { useNavigationActions } from '../../contexts/NavigationContext'; import { zipFileService } from '../../services/zipFileService'; import { detectFileExtension } from '../../utils/fileUtils'; @@ -37,6 +37,7 @@ const FileEditor = ({ // Use optimized FileContext hooks const { state, selectors } = useFileState(); const { addFiles, removeFiles, reorderFiles } = useFileManagement(); + const { actions } = useFileActions(); // Extract needed values from state (memoized to prevent infinite loops) const activeStirlingFileStubs = useMemo(() => selectors.getStirlingFileStubs(), [selectors.getFilesSignature()]); @@ -309,6 +310,48 @@ const FileEditor = ({ } }, [activeStirlingFileStubs, selectors, _setStatus]); + const handleUnzipFile = useCallback(async (fileId: FileId) => { + const record = activeStirlingFileStubs.find(r => r.id === fileId); + const file = record ? selectors.getFile(record.id) : null; + if (record && file) { + try { + // Extract and store files using shared service method + const result = await zipFileService.extractAndStoreFilesWithHistory(file, record); + + if (result.success && result.extractedStubs.length > 0) { + // Add extracted file stubs to FileContext + await actions.addStirlingFileStubs(result.extractedStubs); + + // Remove the original ZIP file + removeFiles([fileId], false); + + alert({ + alertType: 'success', + title: `Extracted ${result.extractedStubs.length} file(s) from ${file.name}`, + expandable: false, + durationMs: 3500 + }); + } else { + alert({ + alertType: 'error', + title: `Failed to extract files from ${file.name}`, + body: result.errors.join('\n'), + expandable: true, + durationMs: 3500 + }); + } + } catch (error) { + console.error('Failed to unzip file:', error); + alert({ + alertType: 'error', + title: `Error unzipping ${file.name}`, + expandable: false, + durationMs: 3500 + }); + } + } + }, [activeStirlingFileStubs, selectors, actions, removeFiles]); + const handleViewFile = useCallback((fileId: FileId) => { const record = activeStirlingFileStubs.find(r => r.id === fileId); if (record) { @@ -429,6 +472,7 @@ const FileEditor = ({ _onSetStatus={showStatus} onReorderFiles={handleReorderFiles} onDownloadFile={handleDownloadFile} + onUnzipFile={handleUnzipFile} toolMode={toolMode} isSupported={isFileSupported(record.name)} /> diff --git a/frontend/src/components/fileEditor/FileEditorThumbnail.tsx b/frontend/src/components/fileEditor/FileEditorThumbnail.tsx index e8f102101..f09bfeeb1 100644 --- a/frontend/src/components/fileEditor/FileEditorThumbnail.tsx +++ b/frontend/src/components/fileEditor/FileEditorThumbnail.tsx @@ -5,11 +5,13 @@ import { useTranslation } from 'react-i18next'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import UnarchiveIcon from '@mui/icons-material/Unarchive'; import PushPinIcon from '@mui/icons-material/PushPin'; import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'; import { StirlingFileStub } from '../../types/fileContext'; +import { zipFileService } from '../../services/zipFileService'; import styles from './FileEditor.module.css'; import { useFileContext } from '../../contexts/FileContext'; @@ -32,6 +34,7 @@ interface FileEditorThumbnailProps { _onSetStatus: (status: string) => void; onReorderFiles?: (sourceFileId: FileId, targetFileId: FileId, selectedFileIds: FileId[]) => void; onDownloadFile: (fileId: FileId) => void; + onUnzipFile?: (fileId: FileId) => void; toolMode?: boolean; isSupported?: boolean; } @@ -45,6 +48,7 @@ const FileEditorThumbnail = ({ _onSetStatus, onReorderFiles, onDownloadFile, + onUnzipFile, isSupported = true, }: FileEditorThumbnailProps) => { const { t } = useTranslation(); @@ -64,6 +68,9 @@ const FileEditorThumbnail = ({ }, [activeFiles, file.id]); const isPinned = actualFile ? isFilePinned(actualFile) : false; + // Check if this is a ZIP file + const isZipFile = zipFileService.isZipFileStub(file); + const pageCount = file.processedFile?.totalPages || 0; const handleRef = useRef(null); @@ -299,6 +306,16 @@ const FileEditorThumbnail = ({ {t('download', 'Download')} + {isZipFile && onUnzipFile && ( + + )} +