From 989eea9e24f4dbba6b62f30f9264a69639d5a6a7 Mon Sep 17 00:00:00 2001 From: Reece Browne <74901996+reecebrowne@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:40:18 +0100 Subject: [PATCH 1/2] Feature/viewer annotation toggle (#4557) # Description of Changes --- ## 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: James Brunton --- CLAUDE.md | 5 + .../public/locales/en-GB/translation.json | 7 +- .../shared/NavigationWarningModal.tsx | 11 +- frontend/src/components/shared/RightRail.tsx | 4 + .../components/shared/rightRail/RightRail.css | 6 +- .../rightRail/ViewerAnnotationControls.tsx | 222 ++++++++++++++++++ .../src/components/viewer/EmbedPdfViewer.tsx | 9 +- .../src/components/viewer/LocalEmbedPDF.tsx | 16 +- .../viewer/LocalEmbedPDFWithAnnotations.tsx | 4 +- .../components/viewer/SignatureAPIBridge.tsx | 8 +- .../src/components/viewer/ZoomAPIBridge.tsx | 14 +- frontend/src/contexts/ViewerContext.tsx | 30 +++ frontend/src/contexts/file/fileActions.ts | 4 - frontend/src/hooks/useThumbnailGeneration.ts | 4 +- 14 files changed, 307 insertions(+), 37 deletions(-) create mode 100644 frontend/src/components/shared/rightRail/ViewerAnnotationControls.tsx diff --git a/CLAUDE.md b/CLAUDE.md index bc6af38c9..d111f8da3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -192,6 +192,11 @@ return useToolOperation({ - **Preview System**: Tool results can be previewed without polluting file context (Split tool example) - **Performance**: Web Worker thumbnails, IndexedDB persistence, background processing +## Translation Rules + +- **CRITICAL**: Always update translations in `en-GB` only, never `en-US` +- Translation files are located in `frontend/public/locales/` + ## Important Notes - **Java Version**: Minimum JDK 17, supports and recommends JDK 21 diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 45d36a371..826c48960 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -3079,7 +3079,12 @@ "panMode": "Pan Mode", "rotateLeft": "Rotate Left", "rotateRight": "Rotate Right", - "toggleSidebar": "Toggle Sidebar" + "toggleSidebar": "Toggle Sidebar", + "exportSelected": "Export Selected Pages", + "toggleAnnotations": "Toggle Annotations Visibility", + "annotationMode": "Toggle Annotation Mode", + "draw": "Draw", + "save": "Save" }, "search": { "title": "Search PDF", diff --git a/frontend/src/components/shared/NavigationWarningModal.tsx b/frontend/src/components/shared/NavigationWarningModal.tsx index e9622d6d4..203e66ff7 100644 --- a/frontend/src/components/shared/NavigationWarningModal.tsx +++ b/frontend/src/components/shared/NavigationWarningModal.tsx @@ -8,7 +8,7 @@ interface NavigationWarningModalProps { } const NavigationWarningModal = ({ - onApplyAndContinue, + onApplyAndContinue: _onApplyAndContinue, onExportAndContinue }: NavigationWarningModalProps) => { @@ -30,13 +30,6 @@ const NavigationWarningModal = ({ confirmNavigation(); }; - const _handleApplyAndContinue = async () => { - if (onApplyAndContinue) { - await onApplyAndContinue(); - } - setHasUnsavedChanges(false); - confirmNavigation(); - }; const handleExportAndContinue = async () => { if (onExportAndContinue) { @@ -85,7 +78,7 @@ const NavigationWarningModal = ({ {/* TODO:: Add this back in when it works */} - {/* {onApplyAndContinue && ( + {/* {_onApplyAndContinue && ( + + + + {/* Instruction Text */} + + {t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} + + + + + ); +}; + +export default AddFileCard; \ No newline at end of file diff --git a/frontend/src/components/fileEditor/FileEditor.module.css b/frontend/src/components/fileEditor/FileEditor.module.css index ccabc2fa7..173738c29 100644 --- a/frontend/src/components/fileEditor/FileEditor.module.css +++ b/frontend/src/components/fileEditor/FileEditor.module.css @@ -304,4 +304,84 @@ /* Light mode selected header stroke override */ :global([data-mantine-color-scheme="light"]) .card[data-selected="true"] { outline-color: #3B4B6E; +} + +/* ========================= + Add File Card Styles + ========================= */ + +.addFileCard { + background: var(--file-card-bg); + border: 2px dashed var(--border-default); + border-radius: 0.0625rem; + cursor: pointer; + transition: all 0.18s ease; + max-width: 100%; + max-height: 100%; + overflow: hidden; + margin-left: 0.5rem; + margin-right: 0.5rem; + opacity: 0.7; +} + +.addFileCard:hover { + opacity: 1; + border-color: var(--color-blue-500); + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.addFileCard:focus { + outline: 2px solid var(--color-blue-500); + outline-offset: 2px; +} + +.addFileHeader { + background: var(--bg-subtle); + color: var(--text-secondary); + border-bottom: 1px solid var(--border-default); +} + +.addFileCard:hover .addFileHeader { + background: var(--color-blue-500); + color: white; +} + +.addFileContent { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 1.5rem 1rem; + gap: 0.5rem; +} + +.addFileIcon { + display: flex; + align-items: center; + justify-content: center; + width: 5rem; + height: 5rem; + border-radius: 50%; + background: var(--bg-subtle); + transition: background-color 0.18s ease; +} + +.addFileCard:hover .addFileIcon { + background: var(--color-blue-50); +} + +.addFileText { + font-weight: 500; + transition: color 0.18s ease; +} + +.addFileCard:hover .addFileText { + color: var(--text-primary); +} + +.addFileSubtext { + font-size: 0.875rem; + opacity: 0.8; } \ No newline at end of file diff --git a/frontend/src/components/fileEditor/FileEditor.tsx b/frontend/src/components/fileEditor/FileEditor.tsx index 8dbd83480..00bf22480 100644 --- a/frontend/src/components/fileEditor/FileEditor.tsx +++ b/frontend/src/components/fileEditor/FileEditor.tsx @@ -8,6 +8,7 @@ import { useNavigationActions } from '../../contexts/NavigationContext'; import { zipFileService } from '../../services/zipFileService'; import { detectFileExtension } from '../../utils/fileUtils'; import FileEditorThumbnail from './FileEditorThumbnail'; +import AddFileCard from './AddFileCard'; import FilePickerModal from '../shared/FilePickerModal'; import SkeletonLoader from '../shared/SkeletonLoader'; import { FileId, StirlingFile } from '../../types/fileContext'; @@ -171,8 +172,8 @@ const FileEditor = ({ // Process all extracted files if (allExtractedFiles.length > 0) { - // Add files to context (they will be processed automatically) - await addFiles(allExtractedFiles); + // Add files to context and select them automatically + await addFiles(allExtractedFiles, { selectFiles: true }); showStatus(`Added ${allExtractedFiles.length} files`, 'success'); } } catch (err) { @@ -405,6 +406,14 @@ const FileEditor = ({ pointerEvents: 'auto' }} > + {/* Add File Card - only show when files exist */} + {activeStirlingFileStubs.length > 0 && ( + + )} + {activeStirlingFileStubs.map((record, index) => { return ( { + const { t } = useTranslation(); + const { colorScheme } = useMantineColorScheme(); + const { onLocalFileClick } = useFileManagerContext(); + const [isUploadHover, setIsUploadHover] = useState(false); + + const handleUploadClick = () => { + onLocalFileClick(); + }; + + return ( +
+ {/* Container */} +
+ {/* No Recent Files Message */} + + + + {t('fileManager.noRecentFiles', 'No recent files')} + + + + {/* Stirling PDF Logo */} + + Stirling PDF + + + {/* Upload Button */} +
setIsUploadHover(false)} + > + +
+ + {/* Instruction Text */} + + {t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} + +
+
+ ); +}; + +export default EmptyFilesState; diff --git a/frontend/src/components/fileManager/FileListArea.tsx b/frontend/src/components/fileManager/FileListArea.tsx index 842d0bf0e..556ecc4f1 100644 --- a/frontend/src/components/fileManager/FileListArea.tsx +++ b/frontend/src/components/fileManager/FileListArea.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { Center, ScrollArea, Text, Stack } from '@mantine/core'; import CloudIcon from '@mui/icons-material/Cloud'; -import HistoryIcon from '@mui/icons-material/History'; import { useTranslation } from 'react-i18next'; import FileListItem from './FileListItem'; import FileHistoryGroup from './FileHistoryGroup'; +import EmptyFilesState from './EmptyFilesState'; import { useFileManagerContext } from '../../contexts/FileManagerContext'; interface FileListAreaProps { @@ -29,6 +29,7 @@ const FileListArea: React.FC = ({ onFileDoubleClick, onDownloadSingle, isFileSupported, + isLoading, } = useFileManagerContext(); const { t } = useTranslation(); @@ -43,15 +44,11 @@ const FileListArea: React.FC = ({ scrollbarSize={8} > - {recentFiles.length === 0 ? ( + {recentFiles.length === 0 && !isLoading ? ( + + ) : recentFiles.length === 0 && isLoading ? (
- - - {t('fileManager.noRecentFiles', 'No recent files')} - - {t('fileManager.dropFilesHint', 'Drop files anywhere to upload')} - - + {t('fileManager.loadingFiles', 'Loading files...')}
) : ( filteredFiles.map((file, index) => { diff --git a/frontend/src/components/fileManager/HiddenFileInput.tsx b/frontend/src/components/fileManager/HiddenFileInput.tsx index 05f35aae1..8dee9e278 100644 --- a/frontend/src/components/fileManager/HiddenFileInput.tsx +++ b/frontend/src/components/fileManager/HiddenFileInput.tsx @@ -9,7 +9,6 @@ const HiddenFileInput: React.FC = () => { ref={fileInputRef} type="file" multiple={true} - accept={["*/*"] as any} onChange={onFileInputChange} style={{ display: 'none' }} data-testid="file-input" diff --git a/frontend/src/components/shared/LandingPage.tsx b/frontend/src/components/shared/LandingPage.tsx index 787b0e565..5f1fe8d8e 100644 --- a/frontend/src/components/shared/LandingPage.tsx +++ b/frontend/src/components/shared/LandingPage.tsx @@ -41,7 +41,7 @@ const LandingPage = () => { {/* White PDF Page Background */} { ref={fileInputRef} type="file" multiple - accept=".pdf,.zip" + accept="*/*" onChange={handleFileSelect} style={{ display: 'none' }} /> diff --git a/frontend/src/contexts/FileManagerContext.tsx b/frontend/src/contexts/FileManagerContext.tsx index 6f3afe21b..fb82e9071 100644 --- a/frontend/src/contexts/FileManagerContext.tsx +++ b/frontend/src/contexts/FileManagerContext.tsx @@ -18,6 +18,7 @@ interface FileManagerContextValue { expandedFileIds: Set; fileGroups: Map; loadedHistoryFiles: Map; + isLoading: boolean; // Handlers onSourceChange: (source: 'recent' | 'local' | 'drive') => void; @@ -58,6 +59,7 @@ interface FileManagerProviderProps { onFileRemove: (index: number) => void; modalHeight: string; refreshRecentFiles: () => Promise; + isLoading: boolean; } export const FileManagerProvider: React.FC = ({ @@ -71,6 +73,7 @@ export const FileManagerProvider: React.FC = ({ onFileRemove, modalHeight, refreshRecentFiles, + isLoading, }) => { const [activeSource, setActiveSource] = useState<'recent' | 'local' | 'drive'>('recent'); const [selectedFileIds, setSelectedFileIds] = useState([]); @@ -574,6 +577,7 @@ export const FileManagerProvider: React.FC = ({ expandedFileIds, fileGroups, loadedHistoryFiles, + isLoading, // Handlers onSourceChange: handleSourceChange, @@ -607,6 +611,7 @@ export const FileManagerProvider: React.FC = ({ expandedFileIds, fileGroups, loadedHistoryFiles, + isLoading, handleSourceChange, handleLocalFileClick, handleFileSelect,