From eba93a3b6c4d04053a6b3a7436acf8406b28cc6e Mon Sep 17 00:00:00 2001 From: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:02:05 +0100 Subject: [PATCH 1/5] Frontend V2 Ui Tweaks (#4590) * Top Controls only show when files > 0 * Moved content down so top controls don't obscure * Viewer background set to match workbench and shadow around pages added so that page boundaries are visible * unsaved-changes modal rework --------- Co-authored-by: Connor Yoh --- .../public/locales/en-GB/translation.json | 7 +- .../src/components/fileEditor/FileEditor.tsx | 4 +- frontend/src/components/layout/Workbench.tsx | 11 +- .../src/components/pageEditor/PageEditor.tsx | 2 +- .../shared/NavigationWarningModal.tsx | 101 ++++++++---------- .../src/components/viewer/LocalEmbedPDF.tsx | 5 +- 6 files changed, 64 insertions(+), 66 deletions(-) diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 71f0bb90b..b28923f63 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -1,9 +1,10 @@ { - "unsavedChanges": "You have unsaved changes to your PDF. What would you like to do?", + "unsavedChanges": "You have unsaved changes to your PDF.", + "areYouSure": "Are you sure you want to leave?", "unsavedChangesTitle": "Unsaved Changes", "keepWorking": "Keep Working", - "discardChanges": "Discard Changes", - "applyAndContinue": "Apply & Continue", + "discardChanges": "Discard & Leave", + "applyAndContinue": "Save & Leave", "exportAndContinue": "Export & Continue", "language": { "direction": "ltr" diff --git a/frontend/src/components/fileEditor/FileEditor.tsx b/frontend/src/components/fileEditor/FileEditor.tsx index 00bf22480..305903b0c 100644 --- a/frontend/src/components/fileEditor/FileEditor.tsx +++ b/frontend/src/components/fileEditor/FileEditor.tsx @@ -348,7 +348,7 @@ const FileEditor = ({ - + {activeStirlingFileStubs.length === 0 && !zipExtractionProgress.isExtracting ? ( @@ -446,7 +446,7 @@ const FileEditor = ({ onSelectFiles={handleLoadFromStorage} /> - + ); diff --git a/frontend/src/components/layout/Workbench.tsx b/frontend/src/components/layout/Workbench.tsx index 8089d1a44..f0f9a542e 100644 --- a/frontend/src/components/layout/Workbench.tsx +++ b/frontend/src/components/layout/Workbench.tsx @@ -146,10 +146,12 @@ export default function Workbench() { } > {/* Top Controls */} - + {activeFiles.length > 0 && ( + + )} {/* Dismiss All Errors Button */} @@ -159,6 +161,7 @@ export default function Workbench() { className="flex-1 min-h-0 relative z-10 workbench-scrollable " style={{ transition: 'opacity 0.15s ease-in-out', + paddingTop: activeFiles.length > 0 ? '3.5rem' : '0', }} > {renderMainContent()} diff --git a/frontend/src/components/pageEditor/PageEditor.tsx b/frontend/src/components/pageEditor/PageEditor.tsx index ee77d142d..0cab1f947 100644 --- a/frontend/src/components/pageEditor/PageEditor.tsx +++ b/frontend/src/components/pageEditor/PageEditor.tsx @@ -662,7 +662,7 @@ const PageEditor = ({ const displayedPages = displayDocument?.pages || []; return ( - + {!mergedPdfDocument && !globalProcessing && activeFileIds.length === 0 && ( diff --git a/frontend/src/components/shared/NavigationWarningModal.tsx b/frontend/src/components/shared/NavigationWarningModal.tsx index b1b935738..8188be435 100644 --- a/frontend/src/components/shared/NavigationWarningModal.tsx +++ b/frontend/src/components/shared/NavigationWarningModal.tsx @@ -1,25 +1,19 @@ -import { Modal, Text, Button, Group, Stack } from '@mantine/core'; -import { useNavigationGuard } from '../../contexts/NavigationContext'; -import { useTranslation } from 'react-i18next'; +import { Modal, Text, Button, Group, Stack } from "@mantine/core"; +import { useNavigationGuard } from "../../contexts/NavigationContext"; +import { useTranslation } from "react-i18next"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; +import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; +import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline"; interface NavigationWarningModalProps { onApplyAndContinue?: () => Promise; onExportAndContinue?: () => Promise; } -const NavigationWarningModal = ({ - onApplyAndContinue, - onExportAndContinue -}: NavigationWarningModalProps) => { - +const NavigationWarningModal = ({ onApplyAndContinue, onExportAndContinue }: NavigationWarningModalProps) => { const { t } = useTranslation(); - const { - showNavigationWarning, - hasUnsavedChanges, - cancelNavigation, - confirmNavigation, - setHasUnsavedChanges - } = useNavigationGuard(); + const { showNavigationWarning, hasUnsavedChanges, cancelNavigation, confirmNavigation, setHasUnsavedChanges } = + useNavigationGuard(); const handleKeepWorking = () => { cancelNavigation(); @@ -38,13 +32,14 @@ const NavigationWarningModal = ({ confirmNavigation(); }; - const handleExportAndContinue = async () => { + const _handleExportAndContinue = async () => { if (onExportAndContinue) { await onExportAndContinue(); } setHasUnsavedChanges(false); confirmNavigation(); }; + const BUTTON_WIDTH = "10rem"; if (!hasUnsavedChanges) { return null; @@ -56,55 +51,53 @@ const NavigationWarningModal = ({ onClose={handleKeepWorking} title={t("unsavedChangesTitle", "Unsaved Changes")} centered - size="xl" - closeOnClickOutside={false} - closeOnEscape={false} + size="auto" + closeOnClickOutside={true} + closeOnEscape={true} > - - - {t("unsavedChanges", "You have unsaved changes to your PDF. What would you like to do?")} + + + + {t("unsavedChanges", "You have unsaved changes to your PDF.")} + + {t("areYouSure", "Are you sure you want to leave?")} + + - - - - - - - - {onExportAndContinue && ( - - )} - + + {onApplyAndContinue && ( - )} + + {/* Mobile layout: centered stack of 4 buttons */} + + + + {onApplyAndContinue && ( + + )} + ); diff --git a/frontend/src/components/viewer/LocalEmbedPDF.tsx b/frontend/src/components/viewer/LocalEmbedPDF.tsx index c0ef68990..bc65c5557 100644 --- a/frontend/src/components/viewer/LocalEmbedPDF.tsx +++ b/frontend/src/components/viewer/LocalEmbedPDF.tsx @@ -274,7 +274,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur e.preventDefault()} From c9e1b8eec5436323d39845baa4671e1488d62ce7 Mon Sep 17 00:00:00 2001 From: "stirlingbot[bot]" <195170888+stirlingbot[bot]@users.noreply.github.com> Date: Sat, 4 Oct 2025 10:53:58 +0100 Subject: [PATCH 2/5] :globe_with_meridians: [V2] Sync Translations + Update README Progress Table (#4578) ### Description of Changes This Pull Request was automatically generated to synchronize updates to translation files and documentation for the **V2 branch**. Below are the details of the changes made: #### **1. Synchronization of Translation Files** - Updated translation files (`frontend/public/locales/*/translation.json`) to reflect changes in the reference file `en-GB/translation.json`. - Ensured consistency and synchronization across all supported language files. - Highlighted any missing or incomplete translations. #### **2. Update README.md** - Generated the translation progress table in `README.md`. - Added a summary of the current translation status for all supported languages. - Included up-to-date statistics on translation coverage. #### **Why these changes are necessary** - Keeps translation files aligned with the latest reference updates. - Ensures the documentation reflects the current translation progress. --- Auto-generated by [create-pull-request][1]. [1]: https://github.com/peter-evans/create-pull-request Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com> --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 804346223..a75ae1046 100644 --- a/README.md +++ b/README.md @@ -122,11 +122,11 @@ Stirling-PDF currently supports 40 languages! | Catalan (Català) (ca_CA) | ![38%](https://geps.dev/progress/38) | | Croatian (Hrvatski) (hr_HR) | ![35%](https://geps.dev/progress/35) | | Czech (Česky) (cs_CZ) | ![39%](https://geps.dev/progress/39) | -| Danish (Dansk) (da_DK) | ![35%](https://geps.dev/progress/35) | +| Danish (Dansk) (da_DK) | ![34%](https://geps.dev/progress/34) | | Dutch (Nederlands) (nl_NL) | ![34%](https://geps.dev/progress/34) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) | -| French (Français) (fr_FR) | ![96%](https://geps.dev/progress/96) | +| French (Français) (fr_FR) | ![95%](https://geps.dev/progress/95) | | German (Deutsch) (de_DE) | ![97%](https://geps.dev/progress/97) | | Greek (Ελληνικά) (el_GR) | ![39%](https://geps.dev/progress/39) | | Hindi (हिंदी) (hi_IN) | ![39%](https://geps.dev/progress/39) | @@ -136,14 +136,14 @@ Stirling-PDF currently supports 40 languages! | Italian (Italiano) (it_IT) | ![97%](https://geps.dev/progress/97) | | Japanese (日本語) (ja_JP) | ![72%](https://geps.dev/progress/72) | | Korean (한국어) (ko_KR) | ![39%](https://geps.dev/progress/39) | -| Norwegian (Norsk) (no_NB) | ![37%](https://geps.dev/progress/37) | +| Norwegian (Norsk) (no_NB) | ![36%](https://geps.dev/progress/36) | | Persian (فارسی) (fa_IR) | ![38%](https://geps.dev/progress/38) | | Polish (Polski) (pl_PL) | ![41%](https://geps.dev/progress/41) | | Portuguese (Português) (pt_PT) | ![39%](https://geps.dev/progress/39) | | Portuguese Brazilian (Português) (pt_BR) | ![97%](https://geps.dev/progress/97) | | Romanian (Română) (ro_RO) | ![33%](https://geps.dev/progress/33) | | Russian (Русский) (ru_RU) | ![96%](https://geps.dev/progress/96) | -| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![43%](https://geps.dev/progress/43) | +| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![42%](https://geps.dev/progress/42) | | Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) | | Slovakian (Slovensky) (sk_SK) | ![29%](https://geps.dev/progress/29) | | Slovenian (Slovenščina) (sl_SI) | ![40%](https://geps.dev/progress/40) | From be7e79be5504975fe65266d710cfc6a91dcd1b45 Mon Sep 17 00:00:00 2001 From: Ludy Date: Mon, 6 Oct 2025 13:24:12 +0200 Subject: [PATCH 3/5] fix(viewer): make initial zoom setup robust and clear timers in `ZoomAPIBridge` (#4588) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes **What was changed** - Reworked the initial zoom `useEffect`: - Early return when `zoom` is unavailable or initial zoom was already applied. - Extracted an `attemptInitialZoom` function with a single retry path. - Introduced proper timeout handles (`timer`, `retryTimer`) and added a cleanup function to clear them on unmount/re-render. - Expanded the effect dependency array to include `zoomState` to avoid stale state issues. - Switched to nullish coalescing for safer defaulting of `currentZoomLevel` (`zoomState.currentZoomLevel ?? 1.4`). - Minor logging adjustments to clarify delayed/failed initialization paths. **Why the change was made** - The previous implementation risked leaving dangling timers and could re-attempt zoom unnecessarily, causing flicker or inconsistent initial zoom when the viewport wasn’t ready. - Including `zoomState` in dependencies ensures the component reacts to state changes correctly and avoids stale reads. - Cleanup of timers prevents memory leaks and race conditions during rapid mounts/unmounts or navigation. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com> --- .../src/components/viewer/ZoomAPIBridge.tsx | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/viewer/ZoomAPIBridge.tsx b/frontend/src/components/viewer/ZoomAPIBridge.tsx index fa47b1c8a..129cf00bd 100644 --- a/frontend/src/components/viewer/ZoomAPIBridge.tsx +++ b/frontend/src/components/viewer/ZoomAPIBridge.tsx @@ -12,30 +12,42 @@ export function ZoomAPIBridge() { // Set initial zoom once when plugin is ready useEffect(() => { - if (zoom && !hasSetInitialZoom.current) { - hasSetInitialZoom.current = true; - setTimeout(() => { - try { - zoom.requestZoom(1.4); - } catch (error) { - console.log('Zoom initialization delayed, viewport not ready:', error); - // Retry after a longer delay - setTimeout(() => { - try { - zoom.requestZoom(1.4); - } catch (retryError) { - console.log('Zoom initialization failed:', retryError); - } - }, 200); - } - }, 50); + if (!zoom || hasSetInitialZoom.current) { + return; } - }, [zoom]); + + let retryTimer: ReturnType | undefined; + const attemptInitialZoom = () => { + try { + zoom.requestZoom(1.4); + hasSetInitialZoom.current = true; + } catch (error) { + console.log('Zoom initialization delayed, viewport not ready:', error); + retryTimer = setTimeout(() => { + try { + zoom.requestZoom(1.4); + hasSetInitialZoom.current = true; + } catch (retryError) { + console.log('Zoom initialization failed:', retryError); + } + }, 200); + } + }; + + const timer = setTimeout(attemptInitialZoom, 50); + + return () => { + clearTimeout(timer); + if (retryTimer) { + clearTimeout(retryTimer); + } + }; + }, [zoom, zoomState]); useEffect(() => { if (zoom && zoomState) { // Update local state - const currentZoomLevel = zoomState.currentZoomLevel || 1.4; + const currentZoomLevel = zoomState.currentZoomLevel ?? 1.4; const newState = { currentZoom: currentZoomLevel, zoomPercent: Math.round(currentZoomLevel * 100), 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 4/5] 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 && ( + + )} +