diff --git a/.claude/settings.local.json b/.claude/settings.local.json index cc5ded896..8032f1d50 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,8 @@ "Bash(find:*)", "Bash(npm test)", "Bash(npm test:*)", - "Bash(ls:*)" + "Bash(ls:*)", + "Bash(npx tsc:*)" ], "deny": [] } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 060a51d64..b2141beb7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -39,6 +39,7 @@ }, "devDependencies": { "@playwright/test": "^1.40.0", + "@types/node": "^24.2.0", "@types/react": "^19.1.4", "@types/react-dom": "^19.1.5", "@vitejs/plugin-react": "^4.5.0", @@ -2384,6 +2385,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz", + "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", + "dev": true, + "dependencies": { + "undici-types": "~7.10.0" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -7404,6 +7414,12 @@ "dev": true, "license": "MIT" }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4ff3484b3..b59be58e9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,8 +34,8 @@ "web-vitals": "^2.1.4" }, "scripts": { - "dev": "vite", - "build": "vite build", + "dev": "npx tsc --noEmit && vite", + "build": "npx tsc --noEmit && vite build", "preview": "vite preview", "generate-licenses": "node scripts/generate-licenses.js", "test": "vitest", @@ -65,6 +65,7 @@ }, "devDependencies": { "@playwright/test": "^1.40.0", + "@types/node": "^24.2.0", "@types/react": "^19.1.4", "@types/react-dom": "^19.1.5", "@vitejs/plugin-react": "^4.5.0", diff --git a/frontend/src/components/fileEditor/FileEditor.tsx b/frontend/src/components/fileEditor/FileEditor.tsx index ca5f594b8..1494dfa9a 100644 --- a/frontend/src/components/fileEditor/FileEditor.tsx +++ b/frontend/src/components/fileEditor/FileEditor.tsx @@ -70,11 +70,11 @@ const FileEditor = ({ } = fileContext; // Get file selection context - const { - selectedFiles: toolSelectedFiles, - setSelectedFiles: setToolSelectedFiles, - maxFiles, - isToolMode + const { + selectedFiles: toolSelectedFiles, + setSelectedFiles: setToolSelectedFiles, + maxFiles, + isToolMode } = useFileSelection(); const [files, setFiles] = useState([]); @@ -82,7 +82,7 @@ const FileEditor = ({ const [error, setError] = useState(null); const [localLoading, setLocalLoading] = useState(false); const [selectionMode, setSelectionMode] = useState(toolMode); - + // Enable selection mode automatically in tool mode React.useEffect(() => { if (toolMode) { @@ -115,7 +115,7 @@ const FileEditor = ({ // Get selected file IDs from context (defensive programming) const contextSelectedIds = Array.isArray(selectedFileIds) ? selectedFileIds : []; - + // Map context selections to local file IDs for UI display const localSelectedIds = files .filter(file => { @@ -144,33 +144,33 @@ const FileEditor = ({ // Check if the actual content has changed, not just references const currentActiveFileNames = activeFiles.map(f => f.name); const currentProcessedFilesSize = processedFiles.size; - + const activeFilesChanged = JSON.stringify(currentActiveFileNames) !== JSON.stringify(lastActiveFilesRef.current); const processedFilesChanged = currentProcessedFilesSize !== lastProcessedFilesRef.current; - + if (!activeFilesChanged && !processedFilesChanged) { return; } - + // Update refs lastActiveFilesRef.current = currentActiveFileNames; lastProcessedFilesRef.current = currentProcessedFilesSize; - + const convertActiveFiles = async () => { - + if (activeFiles.length > 0) { setLocalLoading(true); try { // Process files in chunks to avoid blocking UI const convertedFiles: FileItem[] = []; - + for (let i = 0; i < activeFiles.length; i++) { const file = activeFiles[i]; - + // Try to get thumbnail from processed file first const processedFile = processedFiles.get(file); let thumbnail = processedFile?.pages?.[0]?.thumbnail; - + // If no thumbnail from processed file, try to generate one if (!thumbnail) { try { @@ -180,28 +180,28 @@ const FileEditor = ({ thumbnail = undefined; // Use placeholder } } - + const convertedFile = { id: `file-${Date.now()}-${Math.random()}`, name: file.name, pageCount: processedFile?.totalPages || Math.floor(Math.random() * 20) + 1, - thumbnail, + thumbnail: thumbnail || '', size: file.size, file, }; - + convertedFiles.push(convertedFile); - + // Update progress setConversionProgress(((i + 1) / activeFiles.length) * 100); - + // Yield to main thread between files if (i < activeFiles.length - 1) { await new Promise(resolve => requestAnimationFrame(resolve)); } } - - + + setFiles(convertedFiles); } catch (err) { console.error('Error converting active files:', err); @@ -237,7 +237,7 @@ const FileEditor = ({ try { // Validate ZIP file first const validation = await zipFileService.validateZipFile(file); - + if (validation.isValid && validation.containsPDFs) { // ZIP contains PDFs - extract them setZipExtractionProgress({ @@ -269,7 +269,7 @@ const FileEditor = ({ if (extractionResult.success) { allExtractedFiles.push(...extractionResult.extractedFiles); - + // Record ZIP extraction operation const operationId = `zip-extract-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const operation: FileOperation = { @@ -289,10 +289,10 @@ const FileEditor = ({ } } }; - + recordOperation(file.name, operation); markOperationApplied(file.name, operationId); - + if (extractionResult.errors.length > 0) { errors.push(...extractionResult.errors); } @@ -344,7 +344,7 @@ const FileEditor = ({ } } }; - + recordOperation(file.name, operation); markOperationApplied(file.name, operationId); } @@ -357,7 +357,7 @@ const FileEditor = ({ const errorMessage = err instanceof Error ? err.message : 'Failed to process files'; setError(errorMessage); console.error('File processing error:', err); - + // Reset extraction progress on error setZipExtractionProgress({ isExtracting: false, @@ -377,7 +377,7 @@ const FileEditor = ({ const closeAllFiles = useCallback(() => { if (activeFiles.length === 0) return; - + // Record close all operation for each file activeFiles.forEach(file => { const operationId = `close-all-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; @@ -396,14 +396,14 @@ const FileEditor = ({ } } }; - + recordOperation(file.name, operation); markOperationApplied(file.name, operationId); }); - + // Remove all files from context but keep in storage removeFiles(activeFiles.map(f => (f as any).id || f.name), false); - + // Clear selections setContextSelectedFiles([]); }, [activeFiles, removeFiles, setContextSelectedFiles, recordOperation, markOperationApplied]); @@ -411,12 +411,12 @@ const FileEditor = ({ const toggleFile = useCallback((fileId: string) => { const targetFile = files.find(f => f.id === fileId); if (!targetFile) return; - + const contextFileId = (targetFile.file as any).id || targetFile.name; const isSelected = contextSelectedIds.includes(contextFileId); - + let newSelection: string[]; - + if (isSelected) { // Remove file from selection newSelection = contextSelectedIds.filter(id => id !== contextFileId); @@ -433,10 +433,10 @@ const FileEditor = ({ newSelection = [...contextSelectedIds, contextFileId]; } } - + // Update context setContextSelectedFiles(newSelection); - + // Update tool selection context if in tool mode if (isToolMode || toolMode) { const selectedFiles = files @@ -572,12 +572,12 @@ const FileEditor = ({ console.log('handleDeleteFile called with fileId:', fileId); const file = files.find(f => f.id === fileId); console.log('Found file:', file); - + if (file) { console.log('Attempting to remove file:', file.name); console.log('Actual file object:', file.file); console.log('Actual file.file.name:', file.file.name); - + // Record close operation const fileName = file.file.name; const fileId = (file.file as any).id || fileName; @@ -597,19 +597,16 @@ const FileEditor = ({ } } }; - + recordOperation(fileName, operation); - + // Remove file from context but keep in storage (close, don't delete) console.log('Calling removeFiles with:', [fileId]); removeFiles([fileId], false); - + // Remove from context selections - setContextSelectedFiles(prev => { - const safePrev = Array.isArray(prev) ? prev : []; - return safePrev.filter(id => id !== fileId); - }); - + const newSelection = contextSelectedIds.filter(id => id !== fileId); + setContextSelectedFiles(newSelection); // Mark operation as applied markOperationApplied(fileName, operationId); } else { @@ -670,7 +667,7 @@ const FileEditor = ({ accept={["*/*"]} multiple={true} maxSize={2 * 1024 * 1024 * 1024} - style={{ + style={{ height: '100vh', border: 'none', borderRadius: 0, @@ -707,7 +704,7 @@ const FileEditor = ({ ) : files.length === 0 && (localLoading || zipExtractionProgress.isExtracting) ? ( - + {/* ZIP Extraction Progress */} {zipExtractionProgress.isExtracting && ( @@ -721,10 +718,10 @@ const FileEditor = ({ {zipExtractionProgress.extractedCount} of {zipExtractionProgress.totalFiles} files extracted -
@@ -737,7 +734,7 @@ const FileEditor = ({
)} - + {/* Processing indicator */} {localLoading && ( @@ -745,10 +742,10 @@ const FileEditor = ({ Loading files... {Math.round(conversionProgress)}% -
@@ -761,27 +758,27 @@ const FileEditor = ({
)} - +
) : ( ( + onDragStart={handleDragStart as any /* FIX ME */} + onDragEnd={handleDragEnd} + onDragOver={handleDragOver} + onDragEnter={handleDragEnter as any /* FIX ME */} + onDragLeave={handleDragLeave} + onDrop={handleDrop as any /* FIX ME */} + onEndZoneDragEnter={handleEndZoneDragEnter} + draggedItem={draggedFile as any /* FIX ME */} + dropTarget={dropTarget as any /* FIX ME */} + multiItemDrag={multiFileDrag as any /* FIX ME */} + dragPosition={dragPosition} + renderItem={(file, index, refs) => ( setShowFilePickerModal(false)} storedFiles={[]} // FileEditor doesn't have access to stored files, needs to be passed from parent onSelectFiles={handleLoadFromStorage} - allowMultiple={true} /> {status && ( diff --git a/frontend/src/components/history/FileOperationHistory.tsx b/frontend/src/components/history/FileOperationHistory.tsx index 365b5a8f8..93b9cf015 100644 --- a/frontend/src/components/history/FileOperationHistory.tsx +++ b/frontend/src/components/history/FileOperationHistory.tsx @@ -27,9 +27,10 @@ const FileOperationHistory: React.FC = ({ maxHeight = 400 }) => { const { getFileHistory, getAppliedOperations } = useFileContext(); - + const history = getFileHistory(fileId); - const operations = showOnlyApplied ? getAppliedOperations(fileId) : history?.operations || []; + const allOperations = showOnlyApplied ? getAppliedOperations(fileId) : history?.operations || []; + const operations = allOperations.filter(op => 'fileIds' in op) as FileOperation[]; const formatTimestamp = (timestamp: number) => { return new Date(timestamp).toLocaleString(); @@ -62,7 +63,7 @@ const FileOperationHistory: React.FC = ({ } }; - const renderOperationDetails = (operation: FileOperation | PageOperation) => { + const renderOperationDetails = (operation: FileOperation) => { if ('metadata' in operation && operation.metadata) { const { metadata } = operation; return ( @@ -142,7 +143,7 @@ const FileOperationHistory: React.FC = ({ - + = ({ ); }; -export default FileOperationHistory; \ No newline at end of file +export default FileOperationHistory; diff --git a/frontend/src/components/layout/Workbench.tsx b/frontend/src/components/layout/Workbench.tsx index b0c984ee8..e6f101803 100644 --- a/frontend/src/components/layout/Workbench.tsx +++ b/frontend/src/components/layout/Workbench.tsx @@ -18,18 +18,18 @@ import LandingPage from '../shared/LandingPage'; export default function Workbench() { const { t } = useTranslation(); const { isRainbowMode } = useRainbowThemeContext(); - + // Use context-based hooks to eliminate all prop drilling const { activeFiles, currentView, setCurrentView } = useFileContext(); - const { - previewFile, - pageEditorFunctions, + const { + previewFile, + pageEditorFunctions, sidebarsVisible, - setPreviewFile, + setPreviewFile, setPageEditorFunctions, setSidebarsVisible } = useWorkbenchState(); - + const { selectedToolKey, selectedTool, handleToolSelect } = useToolSelection(); const { addToActiveFiles } = useFileHandler(); @@ -142,10 +142,10 @@ export default function Workbench() { {/* Top Controls */} - + {/* Main content area */} ); -} \ No newline at end of file +} diff --git a/frontend/src/components/pageEditor/DragDropGrid.tsx b/frontend/src/components/pageEditor/DragDropGrid.tsx index 39dbb396f..3639f74d9 100644 --- a/frontend/src/components/pageEditor/DragDropGrid.tsx +++ b/frontend/src/components/pageEditor/DragDropGrid.tsx @@ -22,7 +22,7 @@ interface DragDropGridProps { renderItem: (item: T, index: number, refs: React.MutableRefObject>) => React.ReactNode; renderSplitMarker?: (item: T, index: number) => React.ReactNode; draggedItem: number | null; - dropTarget: number | null; + dropTarget: number | 'end' | null; multiItemDrag: {pageNumbers: number[], count: number} | null; dragPosition: {x: number, y: number} | null; } diff --git a/frontend/src/components/pageEditor/FileThumbnail.tsx b/frontend/src/components/pageEditor/FileThumbnail.tsx index b129ce6d9..eba9a12c5 100644 --- a/frontend/src/components/pageEditor/FileThumbnail.tsx +++ b/frontend/src/components/pageEditor/FileThumbnail.tsx @@ -345,7 +345,7 @@ const FileThumbnail = ({ onClose={() => setShowHistory(false)} title={`Operation History - ${file.name}`} size="lg" - scrollAreaComponent="div" + scrollAreaComponent={'div' as any} > void; exportLoading: boolean; selectionMode: boolean; - selectedPages: string[]; + selectedPages: number[]; closePdf: () => void; }) => void; } @@ -56,7 +56,7 @@ const PageEditor = ({ // Get file context const fileContext = useFileContext(); const { file: currentFile, processedFile: currentProcessedFile } = useCurrentFile(); - + // Use file context state const { activeFiles, @@ -81,12 +81,12 @@ const PageEditor = ({ // Simple computed document from processed files (no caching needed) const mergedPdfDocument = useMemo(() => { if (activeFiles.length === 0) return null; - + if (activeFiles.length === 1) { // Single file const processedFile = processedFiles.get(activeFiles[0]); if (!processedFile) return null; - + return { id: processedFile.id, name: activeFiles[0].name, @@ -108,7 +108,7 @@ const PageEditor = ({ const processedFile = processedFiles.get(file); if (processedFile) { filenames.push(file.name.replace(/\.pdf$/i, '')); - + processedFile.pages.forEach((page, pageIndex) => { const newPage: PDFPage = { ...page, @@ -119,7 +119,7 @@ const PageEditor = ({ }; allPages.push(newPage); }); - + totalPages += processedFile.pages.length; } }); @@ -140,7 +140,7 @@ const PageEditor = ({ const displayDocument = editedDocument || mergedPdfDocument; const [filename, setFilename] = useState(""); - + // Page editor state (use context for selectedPages) const [status, setStatus] = useState(null); @@ -149,7 +149,7 @@ const PageEditor = ({ // Drag and drop state const [draggedPage, setDraggedPage] = useState(null); - const [dropTarget, setDropTarget] = useState(null); + const [dropTarget, setDropTarget] = useState(null); const [multiPageDrag, setMultiPageDrag] = useState<{pageNumbers: number[], count: number} | null>(null); const [dragPosition, setDragPosition] = useState<{x: number, y: number} | null>(null); @@ -200,54 +200,54 @@ const PageEditor = ({ const [thumbnailGenerationStarted, setThumbnailGenerationStarted] = useState(false); // Thumbnail generation (opt-in for visual tools) - const { + const { generateThumbnails, - addThumbnailToCache, - getThumbnailFromCache, + addThumbnailToCache, + getThumbnailFromCache, stopGeneration, - destroyThumbnails + destroyThumbnails } = useThumbnailGeneration(); // Start thumbnail generation process (separate from document loading) const startThumbnailGeneration = useCallback(() => { console.log('🎬 PageEditor: startThumbnailGeneration called'); console.log('🎬 Conditions - mergedPdfDocument:', !!mergedPdfDocument, 'activeFiles:', activeFiles.length, 'started:', thumbnailGenerationStarted); - + if (!mergedPdfDocument || activeFiles.length !== 1 || thumbnailGenerationStarted) { console.log('🎬 PageEditor: Skipping thumbnail generation due to conditions'); return; } - + const file = activeFiles[0]; const totalPages = mergedPdfDocument.totalPages; - + console.log('🎬 PageEditor: Starting thumbnail generation for', totalPages, 'pages'); setThumbnailGenerationStarted(true); - + // Run everything asynchronously to avoid blocking the main thread setTimeout(async () => { try { // Load PDF array buffer for Web Workers const arrayBuffer = await file.arrayBuffer(); - + // Generate page numbers for pages that don't have thumbnails yet const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1) .filter(pageNum => { const page = mergedPdfDocument.pages.find(p => p.pageNumber === pageNum); return !page?.thumbnail; // Only generate for pages without thumbnails }); - + console.log(`🎬 PageEditor: Generating thumbnails for ${pageNumbers.length} pages (out of ${totalPages} total):`, pageNumbers.slice(0, 10), pageNumbers.length > 10 ? '...' : ''); - + // If no pages need thumbnails, we're done if (pageNumbers.length === 0) { console.log('🎬 PageEditor: All pages already have thumbnails, no generation needed'); return; } - + // Calculate quality scale based on file size const scale = activeFiles.length === 1 ? calculateScaleFromFileSize(activeFiles[0].size) : 0.2; - + // Start parallel thumbnail generation WITHOUT blocking the main thread const generationPromise = generateThumbnails( arrayBuffer, @@ -267,11 +267,11 @@ const PageEditor = ({ // Check cache first, then send thumbnail const pageId = `${file.name}-page-${pageNumber}`; const cached = getThumbnailFromCache(pageId); - + if (!cached) { // Cache and send to component addThumbnailToCache(pageId, thumbnail); - + window.dispatchEvent(new CustomEvent('thumbnailReady', { detail: { pageNumber, thumbnail, pageId } })); @@ -292,7 +292,7 @@ const PageEditor = ({ console.error('✗ PageEditor: Web Worker thumbnail generation failed:', error); setThumbnailGenerationStarted(false); }); - + } catch (error) { console.error('Failed to start Web Worker thumbnail generation:', error); setThumbnailGenerationStarted(false); @@ -304,25 +304,25 @@ const PageEditor = ({ useEffect(() => { console.log('🎬 PageEditor: Thumbnail generation effect triggered'); console.log('🎬 Conditions - mergedPdfDocument:', !!mergedPdfDocument, 'started:', thumbnailGenerationStarted); - + if (mergedPdfDocument && !thumbnailGenerationStarted) { // Check if ALL pages already have thumbnails from processed files const totalPages = mergedPdfDocument.pages.length; const pagesWithThumbnails = mergedPdfDocument.pages.filter(page => page.thumbnail).length; const hasAllThumbnails = pagesWithThumbnails === totalPages; - + console.log('🎬 PageEditor: Thumbnail status:', { totalPages, pagesWithThumbnails, hasAllThumbnails, missingThumbnails: totalPages - pagesWithThumbnails }); - + if (hasAllThumbnails) { console.log('🎬 PageEditor: Skipping generation - all thumbnails already exist'); return; // Skip generation if ALL thumbnails already exist } - + console.log('🎬 PageEditor: Some thumbnails missing, proceeding with generation'); // Small delay to let document render, then start thumbnail generation console.log('🎬 PageEditor: Scheduling thumbnail generation in 500ms'); @@ -394,10 +394,10 @@ const PageEditor = ({ const togglePage = useCallback((pageNumber: number) => { console.log('🔄 Toggling page', pageNumber); - + // Check if currently selected and update accordingly const isCurrentlySelected = selectedPageNumbers.includes(pageNumber); - + if (isCurrentlySelected) { // Remove from selection console.log('🔄 Removing page', pageNumber); @@ -524,24 +524,24 @@ const PageEditor = ({ // Update PDF document state with edit tracking const setPdfDocument = useCallback((updatedDoc: PDFDocument) => { console.log('setPdfDocument called - setting edited state'); - + // Update local edit state for immediate visual feedback setEditedDocument(updatedDoc); setHasUnsavedChanges(true); // Use global state setHasUnsavedDraft(true); // Mark that we have unsaved draft changes - + // Auto-save to drafts (debounced) - only if we have new changes if (autoSaveTimer.current) { clearTimeout(autoSaveTimer.current); } - + autoSaveTimer.current = setTimeout(() => { if (hasUnsavedDraft) { saveDraftToIndexedDB(updatedDoc); setHasUnsavedDraft(false); // Mark draft as saved } }, 30000); // Auto-save after 30 seconds of inactivity - + return updatedDoc; }, [setHasUnsavedChanges, hasUnsavedDraft]); @@ -554,7 +554,7 @@ const PageEditor = ({ timestamp: Date.now(), originalFiles: activeFiles.map(f => f.name) }; - + // Save to 'pdf-drafts' store in IndexedDB const request = indexedDB.open('stirling-pdf-drafts', 1); request.onupgradeneeded = () => { @@ -563,7 +563,7 @@ const PageEditor = ({ db.createObjectStore('drafts'); } }; - + request.onsuccess = () => { const db = request.result; const transaction = db.transaction('drafts', 'readwrite'); @@ -581,7 +581,7 @@ const PageEditor = ({ try { const draftKey = `draft-${mergedPdfDocument?.id || 'merged'}`; const request = indexedDB.open('stirling-pdf-drafts', 1); - + request.onsuccess = () => { const db = request.result; const transaction = db.transaction('drafts', 'readwrite'); @@ -596,12 +596,12 @@ const PageEditor = ({ // Apply changes to create new processed file const applyChanges = useCallback(async () => { if (!editedDocument || !mergedPdfDocument) return; - + try { if (activeFiles.length === 1) { const file = activeFiles[0]; const currentProcessedFile = processedFiles.get(file); - + if (currentProcessedFile) { const updatedProcessedFile = { ...currentProcessedFile, @@ -614,14 +614,14 @@ const PageEditor = ({ totalPages: editedDocument.pages.length, lastModified: Date.now() }; - + updateProcessedFile(file, updatedProcessedFile); } } else if (activeFiles.length > 1) { setStatus('Apply changes for multiple files not yet supported'); return; } - + // Wait for the processed file update to complete before clearing edit state setTimeout(() => { setEditedDocument(null); @@ -630,7 +630,7 @@ const PageEditor = ({ cleanupDraft(); setStatus('Changes applied successfully'); }, 100); - + } catch (error) { console.error('Failed to apply changes:', error); setStatus('Failed to apply changes'); @@ -653,7 +653,7 @@ const PageEditor = ({ // Skip animation for large documents (500+ pages) to improve performance const isLargeDocument = displayDocument.pages.length > 500; - + if (isLargeDocument) { // For large documents, just execute the command without animation if (pagesToMove.length > 1) { @@ -678,7 +678,7 @@ const PageEditor = ({ // Only capture positions for potentially affected pages const currentPositions = new Map(); - + affectedPageIds.forEach(pageId => { const element = document.querySelector(`[data-page-number="${pageId}"]`); if (element) { @@ -728,14 +728,14 @@ const PageEditor = ({ if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) { elementsToAnimate.push(element); - + // Apply initial transform element.style.transform = `translate(${deltaX}px, ${deltaY}px)`; element.style.transition = 'none'; - + // Force reflow element.offsetHeight; - + // Animate to final position element.style.transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; element.style.transform = 'translate(0px, 0px)'; @@ -863,13 +863,13 @@ const PageEditor = ({ if (!mergedPdfDocument) return; // Convert page numbers to page IDs for export service - const exportPageIds = selectedOnly + const exportPageIds = selectedOnly ? selectedPageNumbers.map(pageNum => { const page = mergedPdfDocument.pages.find(p => p.pageNumber === pageNum); return page?.id || ''; }).filter(id => id) : []; - + const preview = pdfExportService.getExportInfo(mergedPdfDocument, exportPageIds, selectedOnly); setExportPreview(preview); setShowExportModal(true); @@ -881,16 +881,16 @@ const PageEditor = ({ setExportLoading(true); try { // Convert page numbers to page IDs for export service - const exportPageIds = selectedOnly + const exportPageIds = selectedOnly ? selectedPageNumbers.map(pageNum => { const page = mergedPdfDocument.pages.find(p => p.pageNumber === pageNum); return page?.id || ''; }).filter(id => id) : []; - + const errors = pdfExportService.validateExport(mergedPdfDocument, exportPageIds, selectedOnly); if (errors.length > 0) { - setError(errors.join(', ')); + setStatus(errors.join(', ')); return; } @@ -921,7 +921,7 @@ const PageEditor = ({ } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Export failed'; - setError(errorMessage); + setStatus(errorMessage); } finally { setExportLoading(false); } @@ -1009,26 +1009,26 @@ const PageEditor = ({ // Check for existing drafts const checkForDrafts = useCallback(async () => { if (!mergedPdfDocument) return; - + try { const draftKey = `draft-${mergedPdfDocument.id || 'merged'}`; const request = indexedDB.open('stirling-pdf-drafts', 1); - + request.onsuccess = () => { const db = request.result; if (!db.objectStoreNames.contains('drafts')) return; - + const transaction = db.transaction('drafts', 'readonly'); const store = transaction.objectStore('drafts'); const getRequest = store.get(draftKey); - + getRequest.onsuccess = () => { const draft = getRequest.result; if (draft && draft.timestamp) { // Check if draft is recent (within last 24 hours) const draftAge = Date.now() - draft.timestamp; const twentyFourHours = 24 * 60 * 60 * 1000; - + if (draftAge < twentyFourHours) { setFoundDraft(draft); setShowResumeModal(true); @@ -1066,12 +1066,12 @@ const PageEditor = ({ useEffect(() => { return () => { console.log('PageEditor unmounting - cleaning up resources'); - + // Clear auto-save timer if (autoSaveTimer.current) { clearTimeout(autoSaveTimer.current); } - + // Clean up draft if component unmounts with unsaved changes if (hasUnsavedChanges) { cleanupDraft(); @@ -1125,7 +1125,7 @@ const PageEditor = ({ {showLoading && ( - + {/* Progress indicator */} @@ -1136,10 +1136,10 @@ const PageEditor = ({ {Math.round(processingProgress || 0)}% -
@@ -1151,7 +1151,7 @@ const PageEditor = ({ }} />
- +
)} @@ -1165,10 +1165,10 @@ const PageEditor = ({ Processing thumbnails... {Math.round(processingProgress || 0)}% -
@@ -1210,7 +1210,7 @@ const PageEditor = ({ )} - + {/* Apply Changes Button */} {hasUnsavedChanges && ( - +
); -} \ No newline at end of file +} diff --git a/frontend/src/components/tools/convert/ConvertFromImageSettings.tsx b/frontend/src/components/tools/convert/ConvertFromImageSettings.tsx index 78d2e75a8..0681821fd 100644 --- a/frontend/src/components/tools/convert/ConvertFromImageSettings.tsx +++ b/frontend/src/components/tools/convert/ConvertFromImageSettings.tsx @@ -30,12 +30,12 @@ const ConvertFromImageSettings = ({ })} data={[ { value: COLOR_TYPES.COLOR, label: t("convert.color", "Color") }, - { value: COLOR_TYPES.GREYSCALE, label: t("convert.greyscale", "Greyscale") }, + { value: COLOR_TYPES.GRAYSCALE, label: t("convert.grayscale", "Grayscale") }, { value: COLOR_TYPES.BLACK_WHITE, label: t("convert.blackwhite", "Black & White") }, ]} disabled={disabled} /> - +