From 2f90220b7bdba21b356bf4cb818951c2c5c9d6d0 Mon Sep 17 00:00:00 2001 From: Reece Date: Tue, 24 Jun 2025 20:25:03 +0100 Subject: [PATCH 1/2] File handling fixes --- frontend/src/components/editor/FileEditor.tsx | 80 ++++-- frontend/src/components/editor/PageEditor.tsx | 260 ++++++++++++------ .../src/components/editor/PageThumbnail.tsx | 12 +- .../components/fileManagement/FileManager.tsx | 17 ++ .../src/components/shared/FilePickerModal.tsx | 142 ++++++---- .../components/shared/FileUploadSelector.tsx | 3 +- frontend/src/hooks/useFileWithUrl.ts | 27 +- frontend/src/hooks/useToolParams.ts | 2 +- frontend/src/pages/HomePage.tsx | 128 +++++++-- 9 files changed, 487 insertions(+), 184 deletions(-) diff --git a/frontend/src/components/editor/FileEditor.tsx b/frontend/src/components/editor/FileEditor.tsx index bd23b2ea0..b60ccba81 100644 --- a/frontend/src/components/editor/FileEditor.tsx +++ b/frontend/src/components/editor/FileEditor.tsx @@ -27,8 +27,8 @@ interface FileItem { interface FileEditorProps { onOpenPageEditor?: (file: File) => void; onMergeFiles?: (files: File[]) => void; - sharedFiles?: { file: File; url: string }[]; - setSharedFiles?: (files: { file: File; url: string }[]) => void; + activeFiles?: File[]; + setActiveFiles?: (files: File[]) => void; preSelectedFiles?: { file: File; url: string }[]; onClearPreSelection?: () => void; } @@ -36,15 +36,14 @@ interface FileEditorProps { const FileEditor = ({ onOpenPageEditor, onMergeFiles, - sharedFiles = [], - setSharedFiles, + activeFiles = [], + setActiveFiles, preSelectedFiles = [], onClearPreSelection }: FileEditorProps) => { const { t } = useTranslation(); - const files = sharedFiles; // Use sharedFiles as the source of truth - + const [files, setFiles] = useState([]); const [selectedFiles, setSelectedFiles] = useState([]); const [status, setStatus] = useState(null); const [loading, setLoading] = useState(false); @@ -74,6 +73,39 @@ const FileEditor = ({ }; }, []); + // Convert activeFiles to FileItem format + useEffect(() => { + const convertActiveFiles = async () => { + if (activeFiles.length > 0) { + setLoading(true); + try { + const convertedFiles = await Promise.all( + activeFiles.map(async (file) => { + const thumbnail = await generateThumbnailForFile(file); + return { + id: `file-${Date.now()}-${Math.random()}`, + name: file.name.replace(/\.pdf$/i, ''), + pageCount: Math.floor(Math.random() * 20) + 1, // Mock for now + thumbnail, + size: file.size, + file, + }; + }) + ); + setFiles(convertedFiles); + } catch (err) { + console.error('Error converting active files:', err); + } finally { + setLoading(false); + } + } else { + setFiles([]); + } + }; + + convertActiveFiles(); + }, [activeFiles]); + // Only load shared files when explicitly passed (not on mount) useEffect(() => { const loadSharedFiles = async () => { @@ -84,7 +116,10 @@ const FileEditor = ({ const convertedFiles = await Promise.all( preSelectedFiles.map(convertToFileItem) ); - setFiles(convertedFiles); + if (setActiveFiles) { + const updatedActiveFiles = convertedFiles.map(fileItem => fileItem.file); + setActiveFiles(updatedActiveFiles); + } } catch (err) { console.error('Error converting pre-selected files:', err); } finally { @@ -137,8 +172,8 @@ const FileEditor = ({ await fileStorage.storeFile(file, thumbnail); } - if (setSharedFiles) { - setSharedFiles(prev => [...prev, ...newFiles]); + if (setActiveFiles) { + setActiveFiles(prev => [...prev, ...newFiles.map(f => f.file)]); } setStatus(`Added ${newFiles.length} files`); @@ -149,7 +184,7 @@ const FileEditor = ({ } finally { setLoading(false); } - }, [setSharedFiles]); + }, [setActiveFiles]); const selectAll = useCallback(() => { setSelectedFiles(files.map(f => f.id)); @@ -283,8 +318,9 @@ const FileEditor = ({ ? selectedFiles : [draggedFile]; - if (setSharedFiles) { - setSharedFiles(prev => { + if (setActiveFiles) { + // Update the local files state and sync with activeFiles + setFiles(prev => { const newFiles = [...prev]; const movedFiles = filesToMove.map(id => newFiles.find(f => f.id === id)!).filter(Boolean); @@ -296,6 +332,10 @@ const FileEditor = ({ // Insert at target position newFiles.splice(targetIndex, 0, ...movedFiles); + + // Update activeFiles with the reordered File objects + setActiveFiles(newFiles.map(f => f.file)); + return newFiles; }); } @@ -304,7 +344,7 @@ const FileEditor = ({ setStatus(`${moveCount > 1 ? `${moveCount} files` : 'File'} reordered`); handleDragEnd(); - }, [draggedFile, files, selectionMode, selectedFiles, multiFileDrag, handleDragEnd, setSharedFiles]); + }, [draggedFile, files, selectionMode, selectedFiles, multiFileDrag, handleDragEnd, setActiveFiles]); const handleEndZoneDragEnter = useCallback(() => { if (draggedFile) { @@ -314,11 +354,16 @@ const FileEditor = ({ // File operations const handleDeleteFile = useCallback((fileId: string) => { - if (setSharedFiles) { - setSharedFiles(prev => prev.filter(f => f.id !== fileId)); + if (setActiveFiles) { + // Remove from local files and sync with activeFiles + setFiles(prev => { + const newFiles = prev.filter(f => f.id !== fileId); + setActiveFiles(newFiles.map(f => f.file)); + return newFiles; + }); } setSelectedFiles(prev => prev.filter(id => id !== fileId)); - }, [setSharedFiles]); + }, [setActiveFiles]); const handleViewFile = useCallback((fileId: string) => { const file = files.find(f => f.id === fileId); @@ -483,8 +528,9 @@ const FileEditor = ({ setShowFilePickerModal(false)} - sharedFiles={sharedFiles || []} + 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/editor/PageEditor.tsx b/frontend/src/components/editor/PageEditor.tsx index 9a84ab6d4..0adc4aba3 100644 --- a/frontend/src/components/editor/PageEditor.tsx +++ b/frontend/src/components/editor/PageEditor.tsx @@ -27,13 +27,13 @@ import FilePickerModal from '../shared/FilePickerModal'; import FileUploadSelector from '../shared/FileUploadSelector'; export interface PageEditorProps { - file: { file: File; url: string } | null; - setFile?: (file: { file: File; url: string } | null) => void; + activeFiles: File[]; + setActiveFiles: (files: File[]) => void; downloadUrl?: string | null; setDownloadUrl?: (url: string | null) => void; - sharedFiles?: { file: File; url: string }[]; + sharedFiles?: any[]; // For FileUploadSelector when no files loaded - // Optional callbacks to expose internal functions + // Optional callbacks to expose internal functions for PageEditorControls onFunctionsReady?: (functions: { handleUndo: () => void; handleRedo: () => void; @@ -43,6 +43,8 @@ export interface PageEditorProps { handleDelete: () => void; handleSplit: () => void; showExportPreview: (selectedOnly: boolean) => void; + onExportSelected: () => void; + onExportAll: () => void; exportLoading: boolean; selectionMode: boolean; selectedPages: string[]; @@ -51,31 +53,46 @@ export interface PageEditorProps { } const PageEditor = ({ - file, - setFile, + activeFiles, + setActiveFiles, downloadUrl, setDownloadUrl, + sharedFiles = [], onFunctionsReady, - sharedFiles, }: PageEditorProps) => { const { t } = useTranslation(); const { processPDFFile, loading: pdfLoading } = usePDFProcessor(); - const [pdfDocument, setPdfDocument] = useState(null); + // Multi-file state + const [currentFileIndex, setCurrentFileIndex] = useState(0); + const [processedFiles, setProcessedFiles] = useState>(new Map()); + + // Current file references + const currentFile = activeFiles[currentFileIndex] || null; + const currentFileKey = currentFile ? `${currentFile.name}-${currentFile.size}` : null; + const currentPdfDocument = currentFileKey ? processedFiles.get(currentFileKey) : null; + const [filename, setFilename] = useState(""); + + // Page editor state const [selectedPages, setSelectedPages] = useState([]); const [status, setStatus] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [csvInput, setCsvInput] = useState(""); const [selectionMode, setSelectionMode] = useState(false); - const [filename, setFilename] = useState(""); + + // Drag and drop state const [draggedPage, setDraggedPage] = useState(null); const [dropTarget, setDropTarget] = useState(null); const [multiPageDrag, setMultiPageDrag] = useState<{pageIds: string[], count: number} | null>(null); const [dragPosition, setDragPosition] = useState<{x: number, y: number} | null>(null); + + // Export state const [exportLoading, setExportLoading] = useState(false); const [showExportModal, setShowExportModal] = useState(false); const [exportPreview, setExportPreview] = useState<{pageCount: number; splitCount: number; estimatedSize: string} | null>(null); + + // Animation state const [movingPage, setMovingPage] = useState(null); const [pagePositions, setPagePositions] = useState>(new Map()); const [isAnimating, setIsAnimating] = useState(false); @@ -122,14 +139,26 @@ const PageEditor = ({ return; } + const fileKey = `${fileToProcess.name}-${fileToProcess.size}`; + + // Skip if already processed + if (processedFiles.has(fileKey)) return; + setLoading(true); setError(null); try { const document = await processPDFFile(fileToProcess); - setPdfDocument(document); + + // Store processed document + setProcessedFiles(prev => new Map(prev).set(fileKey, document)); setFilename(fileToProcess.name.replace(/\.pdf$/i, '')); setSelectedPages([]); + + // Add to activeFiles if not already there + if (!activeFiles.some(f => f.name === fileToProcess.name && f.size === fileToProcess.size)) { + setActiveFiles([...activeFiles, fileToProcess]); + } if (document.pages.length > 0) { // Only store if it's a new file (not from storage) @@ -138,12 +167,7 @@ const PageEditor = ({ await fileStorage.storeFile(fileToProcess, thumbnail); } } - - if (setFile) { - const fileUrl = URL.createObjectURL(fileToProcess); - setFile({ file: fileToProcess, url: fileUrl }); - } - + setStatus(`PDF loaded successfully with ${document.totalPages} pages`); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to process PDF'; @@ -152,13 +176,38 @@ const PageEditor = ({ } finally { setLoading(false); } - }, [processPDFFile, setFile]); + }, [processPDFFile, activeFiles, setActiveFiles, processedFiles]); + // Auto-process files from activeFiles useEffect(() => { - if (file?.file && !pdfDocument) { - handleFileUpload(file.file); + activeFiles.forEach(file => { + const fileKey = `${file.name}-${file.size}`; + if (!processedFiles.has(fileKey)) { + handleFileUpload(file); + } + }); + }, [activeFiles, processedFiles, handleFileUpload]); + + // Reset current file index when activeFiles changes + useEffect(() => { + if (currentFileIndex >= activeFiles.length) { + setCurrentFileIndex(0); } - }, [file, pdfDocument, handleFileUpload]); + }, [activeFiles.length, currentFileIndex]); + + // Clear selections when switching files + useEffect(() => { + setSelectedPages([]); + setCsvInput(""); + setSelectionMode(false); + }, [currentFileIndex]); + + // Update filename when current file changes + useEffect(() => { + if (currentFile) { + setFilename(currentFile.name.replace(/\.pdf$/i, '')); + } + }, [currentFile]); // Global drag cleanup to handle drops outside valid areas useEffect(() => { @@ -187,10 +236,10 @@ const PageEditor = ({ }, [draggedPage]); const selectAll = useCallback(() => { - if (pdfDocument) { - setSelectedPages(pdfDocument.pages.map(p => p.id)); + if (currentPdfDocument) { + setSelectedPages(currentPdfDocument.pages.map(p => p.id)); } - }, [pdfDocument]); + }, [currentPdfDocument]); const deselectAll = useCallback(() => setSelectedPages([]), []); @@ -215,7 +264,7 @@ const PageEditor = ({ }, []); const parseCSVInput = useCallback((csv: string) => { - if (!pdfDocument) return []; + if (!currentPdfDocument) return []; const pageIds: string[] = []; const ranges = csv.split(',').map(s => s.trim()).filter(Boolean); @@ -223,23 +272,23 @@ const PageEditor = ({ ranges.forEach(range => { if (range.includes('-')) { const [start, end] = range.split('-').map(n => parseInt(n.trim())); - for (let i = start; i <= end && i <= pdfDocument.totalPages; i++) { + for (let i = start; i <= end && i <= currentPdfDocument.totalPages; i++) { if (i > 0) { - const page = pdfDocument.pages.find(p => p.pageNumber === i); + const page = currentPdfDocument.pages.find(p => p.pageNumber === i); if (page) pageIds.push(page.id); } } } else { const pageNum = parseInt(range); - if (pageNum > 0 && pageNum <= pdfDocument.totalPages) { - const page = pdfDocument.pages.find(p => p.pageNumber === pageNum); + if (pageNum > 0 && pageNum <= currentPdfDocument.totalPages) { + const page = currentPdfDocument.pages.find(p => p.pageNumber === pageNum); if (page) pageIds.push(page.id); } } }); return pageIds; - }, [pdfDocument]); + }, [currentPdfDocument]); const updatePagesFromCSV = useCallback(() => { const pageIds = parseCSVInput(csvInput); @@ -313,22 +362,29 @@ const PageEditor = ({ // Don't clear drop target on drag leave - let dragover handle it }, []); + // Create setPdfDocument wrapper for current file + const setPdfDocument = useCallback((updatedDoc: PDFDocument) => { + if (currentFileKey) { + setProcessedFiles(prev => new Map(prev).set(currentFileKey, updatedDoc)); + } + }, [currentFileKey]); + const animateReorder = useCallback((pageId: string, targetIndex: number) => { - if (!pdfDocument || isAnimating) return; + if (!currentPdfDocument || isAnimating) return; // In selection mode, if the dragged page is selected, move all selected pages const pagesToMove = selectionMode && selectedPages.includes(pageId) ? selectedPages : [pageId]; - const originalIndex = pdfDocument.pages.findIndex(p => p.id === pageId); + const originalIndex = currentPdfDocument.pages.findIndex(p => p.id === pageId); if (originalIndex === -1 || originalIndex === targetIndex) return; setIsAnimating(true); // Get current positions of all pages const currentPositions = new Map(); - pdfDocument.pages.forEach((page) => { + currentPdfDocument.pages.forEach((page) => { const element = pageRefs.current.get(page.id); if (element) { const rect = element.getBoundingClientRect(); @@ -339,11 +395,11 @@ const PageEditor = ({ // Execute the reorder - for multi-page, we use a different command if (pagesToMove.length > 1) { // Multi-page move - use MovePagesCommand - const command = new MovePagesCommand(pdfDocument, setPdfDocument, pagesToMove, targetIndex); + const command = new MovePagesCommand(currentPdfDocument, setPdfDocument, pagesToMove, targetIndex); executeCommand(command); } else { // Single page move - const command = new ReorderPageCommand(pdfDocument, setPdfDocument, pageId, targetIndex); + const command = new ReorderPageCommand(currentPdfDocument, setPdfDocument, pageId, targetIndex); executeCommand(command); } @@ -353,8 +409,7 @@ const PageEditor = ({ const newPositions = new Map(); // Get the updated document from the state after command execution - // The command has already updated the document, so we need to get the new order - const currentDoc = pdfDocument; // This should be the updated version after command + const currentDoc = currentPdfDocument; // This should be the updated version after command currentDoc.pages.forEach((page) => { const element = pageRefs.current.get(page.id); @@ -400,17 +455,17 @@ const PageEditor = ({ }, 400); }); }); - }, [pdfDocument, isAnimating, executeCommand, selectionMode, selectedPages]); + }, [currentPdfDocument, isAnimating, executeCommand, selectionMode, selectedPages, setPdfDocument]); const handleDrop = useCallback((e: React.DragEvent, targetPageId: string | 'end') => { e.preventDefault(); - if (!draggedPage || !pdfDocument || draggedPage === targetPageId) return; + if (!draggedPage || !currentPdfDocument || draggedPage === targetPageId) return; let targetIndex: number; if (targetPageId === 'end') { - targetIndex = pdfDocument.pages.length; + targetIndex = currentPdfDocument.pages.length; } else { - targetIndex = pdfDocument.pages.findIndex(p => p.id === targetPageId); + targetIndex = currentPdfDocument.pages.findIndex(p => p.id === targetPageId); if (targetIndex === -1) return; } @@ -423,7 +478,7 @@ const PageEditor = ({ const moveCount = multiPageDrag ? multiPageDrag.count : 1; setStatus(`${moveCount > 1 ? `${moveCount} pages` : 'Page'} reordered`); - }, [draggedPage, pdfDocument, animateReorder, multiPageDrag]); + }, [draggedPage, currentPdfDocument, animateReorder, multiPageDrag]); const handleEndZoneDragEnter = useCallback(() => { if (draggedPage) { @@ -432,38 +487,38 @@ const PageEditor = ({ }, [draggedPage]); const handleRotate = useCallback((direction: 'left' | 'right') => { - if (!pdfDocument) return; + if (!currentPdfDocument) return; const rotation = direction === 'left' ? -90 : 90; const pagesToRotate = selectionMode ? selectedPages - : pdfDocument.pages.map(p => p.id); + : currentPdfDocument.pages.map(p => p.id); if (selectionMode && selectedPages.length === 0) return; const command = new RotatePagesCommand( - pdfDocument, + currentPdfDocument, setPdfDocument, pagesToRotate, rotation ); executeCommand(command); - const pageCount = selectionMode ? selectedPages.length : pdfDocument.pages.length; + const pageCount = selectionMode ? selectedPages.length : currentPdfDocument.pages.length; setStatus(`Rotated ${pageCount} pages ${direction}`); - }, [pdfDocument, selectedPages, selectionMode, executeCommand]); + }, [currentPdfDocument, selectedPages, selectionMode, executeCommand, setPdfDocument]); const handleDelete = useCallback(() => { - if (!pdfDocument) return; + if (!currentPdfDocument) return; const pagesToDelete = selectionMode ? selectedPages - : pdfDocument.pages.map(p => p.id); + : currentPdfDocument.pages.map(p => p.id); if (selectionMode && selectedPages.length === 0) return; const command = new DeletePagesCommand( - pdfDocument, + currentPdfDocument, setPdfDocument, pagesToDelete ); @@ -472,55 +527,55 @@ const PageEditor = ({ if (selectionMode) { setSelectedPages([]); } - const pageCount = selectionMode ? selectedPages.length : pdfDocument.pages.length; + const pageCount = selectionMode ? selectedPages.length : currentPdfDocument.pages.length; setStatus(`Deleted ${pageCount} pages`); - }, [pdfDocument, selectedPages, selectionMode, executeCommand]); + }, [currentPdfDocument, selectedPages, selectionMode, executeCommand, setPdfDocument]); const handleSplit = useCallback(() => { - if (!pdfDocument) return; + if (!currentPdfDocument) return; const pagesToSplit = selectionMode ? selectedPages - : pdfDocument.pages.map(p => p.id); + : currentPdfDocument.pages.map(p => p.id); if (selectionMode && selectedPages.length === 0) return; const command = new ToggleSplitCommand( - pdfDocument, + currentPdfDocument, setPdfDocument, pagesToSplit ); executeCommand(command); - const pageCount = selectionMode ? selectedPages.length : pdfDocument.pages.length; + const pageCount = selectionMode ? selectedPages.length : currentPdfDocument.pages.length; setStatus(`Split markers toggled for ${pageCount} pages`); - }, [pdfDocument, selectedPages, selectionMode, executeCommand]); + }, [currentPdfDocument, selectedPages, selectionMode, executeCommand, setPdfDocument]); const showExportPreview = useCallback((selectedOnly: boolean = false) => { - if (!pdfDocument) return; + if (!currentPdfDocument) return; const exportPageIds = selectedOnly ? selectedPages : []; - const preview = pdfExportService.getExportInfo(pdfDocument, exportPageIds, selectedOnly); + const preview = pdfExportService.getExportInfo(currentPdfDocument, exportPageIds, selectedOnly); setExportPreview(preview); setShowExportModal(true); - }, [pdfDocument, selectedPages]); + }, [currentPdfDocument, selectedPages]); const handleExport = useCallback(async (selectedOnly: boolean = false) => { - if (!pdfDocument) return; + if (!currentPdfDocument) return; setExportLoading(true); try { const exportPageIds = selectedOnly ? selectedPages : []; - const errors = pdfExportService.validateExport(pdfDocument, exportPageIds, selectedOnly); + const errors = pdfExportService.validateExport(currentPdfDocument, exportPageIds, selectedOnly); if (errors.length > 0) { setError(errors.join(', ')); return; } - const hasSplitMarkers = pdfDocument.pages.some(page => page.splitBefore); + const hasSplitMarkers = currentPdfDocument.pages.some(page => page.splitBefore); if (hasSplitMarkers) { - const result = await pdfExportService.exportPDF(pdfDocument, exportPageIds, { + const result = await pdfExportService.exportPDF(currentPdfDocument, exportPageIds, { selectedOnly, filename, splitDocuments: true @@ -534,7 +589,7 @@ const PageEditor = ({ setStatus(`Exported ${result.blobs.length} split documents`); } else { - const result = await pdfExportService.exportPDF(pdfDocument, exportPageIds, { + const result = await pdfExportService.exportPDF(currentPdfDocument, exportPageIds, { selectedOnly, filename }) as { blob: Blob; filename: string }; @@ -548,7 +603,7 @@ const PageEditor = ({ } finally { setExportLoading(false); } - }, [pdfDocument, selectedPages, filename]); + }, [currentPdfDocument, selectedPages, filename]); const handleUndo = useCallback(() => { if (undo()) { @@ -563,11 +618,17 @@ const PageEditor = ({ }, [redo]); const closePdf = useCallback(() => { - setPdfDocument(null); - setFile && setFile(null); - }, [setFile]); + setCurrentFileIndex(0); + setActiveFiles([]); + setProcessedFiles(new Map()); + setSelectedPages([]); + }, [setActiveFiles]); - // Expose functions to parent component + // PageEditorControls needs onExportSelected and onExportAll + const onExportSelected = useCallback(() => showExportPreview(true), [showExportPreview]); + const onExportAll = useCallback(() => showExportPreview(false), [showExportPreview]); + + // Expose functions to parent component for PageEditorControls useEffect(() => { if (onFunctionsReady) { onFunctionsReady({ @@ -579,6 +640,8 @@ const PageEditor = ({ handleDelete, handleSplit, showExportPreview, + onExportSelected, + onExportAll, exportLoading, selectionMode, selectedPages, @@ -595,13 +658,15 @@ const PageEditor = ({ handleDelete, handleSplit, showExportPreview, + onExportSelected, + onExportAll, exportLoading, selectionMode, selectedPages, closePdf ]); - if (!pdfDocument) { + if (!currentPdfDocument) { return ( @@ -610,7 +675,7 @@ const PageEditor = ({ + {/* File Switcher Tabs */} + {activeFiles.length > 1 && ( + + + {activeFiles.map((file, index) => { + const isActive = index === currentFileIndex; + + return ( + + ); + })} + + + )} + )} @@ -753,7 +849,7 @@ const PageEditor = ({ {exportPreview.estimatedSize} - {pdfDocument && pdfDocument.pages.some(p => p.splitBefore) && ( + {currentPdfDocument && currentPdfDocument.pages.some(p => p.splitBefore) && ( This will create multiple PDF files based on split markers. @@ -771,7 +867,7 @@ const PageEditor = ({ loading={exportLoading} onClick={() => { setShowExportModal(false); - const selectedOnly = exportPreview.pageCount < (pdfDocument?.totalPages || 0); + const selectedOnly = exportPreview.pageCount < (currentPdfDocument?.totalPages || 0); handleExport(selectedOnly); }} > @@ -800,9 +896,19 @@ const PageEditor = ({ )} + {error && ( + setError(null)} + style={{ position: 'fixed', bottom: 70, right: 20, zIndex: 1000 }} + > + {error} + + )} ); }; -export default PageEditor; +export default PageEditor; \ No newline at end of file diff --git a/frontend/src/components/editor/PageThumbnail.tsx b/frontend/src/components/editor/PageThumbnail.tsx index 89d0811b1..1dc70843d 100644 --- a/frontend/src/components/editor/PageThumbnail.tsx +++ b/frontend/src/components/editor/PageThumbnail.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { Text, Checkbox, Tooltip, ActionIcon } from '@mantine/core'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; @@ -67,8 +67,18 @@ const PageThumbnail = ({ pdfDocument, setPdfDocument, }: PageThumbnailProps) => { + // Register this component with pageRefs for animations + const pageElementRef = useCallback((element: HTMLDivElement | null) => { + if (element) { + pageRefs.current.set(page.id, element); + } else { + pageRefs.current.delete(page.id); + } + }, [page.id, pageRefs]); + return (
void; onOpenFileEditor?: (selectedFiles?: FileWithUrl[]) => void; + onOpenPageEditor?: (selectedFiles?: FileWithUrl[]) => void; onLoadFileToActive?: (file: File) => void; } @@ -31,6 +32,7 @@ const FileManager = ({ allowMultiple = true, setCurrentView, onOpenFileEditor, + onOpenPageEditor, onLoadFileToActive, }: FileManagerProps) => { const { t } = useTranslation(); @@ -335,6 +337,13 @@ const FileManager = ({ } }; + const handleOpenSelectedInPageEditor = () => { + if (onOpenPageEditor && selectedFiles.length > 0) { + const selected = files.filter(f => selectedFiles.includes(f.id || f.name)); + onOpenPageEditor(selected); + } + }; + return (
{t("fileManager.openInFileEditor", "Open in File Editor")} + diff --git a/frontend/src/components/shared/FilePickerModal.tsx b/frontend/src/components/shared/FilePickerModal.tsx index 2a5cfa76e..cad7fd373 100644 --- a/frontend/src/components/shared/FilePickerModal.tsx +++ b/frontend/src/components/shared/FilePickerModal.tsx @@ -19,15 +19,17 @@ import { useTranslation } from 'react-i18next'; interface FilePickerModalProps { opened: boolean; onClose: () => void; - sharedFiles: any[]; - onSelectFiles: (selectedFiles: any[]) => void; + storedFiles: any[]; // Files from storage (FileWithUrl format) + onSelectFiles: (selectedFiles: File[]) => void; + allowMultiple?: boolean; } const FilePickerModal = ({ opened, onClose, - sharedFiles, + storedFiles, onSelectFiles, + allowMultiple = true, }: FilePickerModalProps) => { const { t } = useTranslation(); const [selectedFileIds, setSelectedFileIds] = useState([]); @@ -40,15 +42,22 @@ const FilePickerModal = ({ }, [opened]); const toggleFileSelection = (fileId: string) => { - setSelectedFileIds(prev => - prev.includes(fileId) - ? prev.filter(id => id !== fileId) - : [...prev, fileId] - ); + setSelectedFileIds(prev => { + if (allowMultiple) { + return prev.includes(fileId) + ? prev.filter(id => id !== fileId) + : [...prev, fileId]; + } else { + // Single selection mode + return prev.includes(fileId) ? [] : [fileId]; + } + }); }; const selectAll = () => { - setSelectedFileIds(sharedFiles.map(f => f.id || f.name)); + if (allowMultiple) { + setSelectedFileIds(storedFiles.map(f => f.id || f.name)); + } }; const selectNone = () => { @@ -56,56 +65,54 @@ const FilePickerModal = ({ }; const handleConfirm = async () => { - const selectedFiles = sharedFiles.filter(f => + const selectedFiles = storedFiles.filter(f => selectedFileIds.includes(f.id || f.name) ); - // Convert FileWithUrl objects to proper File objects if needed + // Convert stored files to File objects const convertedFiles = await Promise.all( selectedFiles.map(async (fileItem) => { - console.log('Converting file item:', fileItem); - - // If it's already a File object, return as is - if (fileItem instanceof File) { - console.log('File is already a File object'); - return fileItem; - } - - // If it has a file property, use that - if (fileItem.file && fileItem.file instanceof File) { - console.log('Using .file property'); - return fileItem.file; - } - - // If it's a FileWithUrl from storage, reconstruct the File - if (fileItem.arrayBuffer && typeof fileItem.arrayBuffer === 'function') { - try { - console.log('Reconstructing file from storage:', fileItem.name, fileItem); + try { + // If it's already a File object, return as is + if (fileItem instanceof File) { + return fileItem; + } + + // If it has a file property, use that + if (fileItem.file && fileItem.file instanceof File) { + return fileItem.file; + } + + // If it's from IndexedDB storage, reconstruct the File + if (fileItem.arrayBuffer && typeof fileItem.arrayBuffer === 'function') { const arrayBuffer = await fileItem.arrayBuffer(); - console.log('Got arrayBuffer:', arrayBuffer); - const blob = new Blob([arrayBuffer], { type: fileItem.type || 'application/pdf' }); - console.log('Created blob:', blob); - - const reconstructedFile = new File([blob], fileItem.name, { + return new File([blob], fileItem.name, { type: fileItem.type || 'application/pdf', lastModified: fileItem.lastModified || Date.now() }); - console.log('Reconstructed file:', reconstructedFile, 'instanceof File:', reconstructedFile instanceof File); - return reconstructedFile; - } catch (error) { - console.error('Error reconstructing file:', error, fileItem); - return null; } + + // If it has data property, reconstruct the File + if (fileItem.data) { + const blob = new Blob([fileItem.data], { type: fileItem.type || 'application/pdf' }); + return new File([blob], fileItem.name, { + type: fileItem.type || 'application/pdf', + lastModified: fileItem.lastModified || Date.now() + }); + } + + console.warn('Could not convert file item:', fileItem); + return null; + } catch (error) { + console.error('Error converting file:', error, fileItem); + return null; } - - console.log('No valid conversion method found for:', fileItem); - return null; // Don't return invalid objects }) ); - // Filter out any null values from failed conversions - const validFiles = convertedFiles.filter(f => f !== null); + // Filter out any null values and return valid Files + const validFiles = convertedFiles.filter((f): f is File => f !== null); onSelectFiles(validFiles); onClose(); @@ -128,7 +135,7 @@ const FilePickerModal = ({ scrollAreaComponent={ScrollArea.Autosize} > - {sharedFiles.length === 0 ? ( + {storedFiles.length === 0 ? ( {t("fileUpload.noFilesInStorage", "No files available in storage. Upload some files first.")} @@ -137,22 +144,27 @@ const FilePickerModal = ({ {/* Selection controls */} - {sharedFiles.length} {t("fileUpload.filesAvailable", "files available")} + {storedFiles.length} {t("fileUpload.filesAvailable", "files available")} + {allowMultiple && selectedFileIds.length > 0 && ( + <> • {selectedFileIds.length} selected + )} - - - - + {allowMultiple && ( + + + + + )} {/* File grid */} - {sharedFiles.map((file) => { + {storedFiles.map((file) => { const fileId = file.id || file.name; const isSelected = selectedFileIds.includes(fileId); @@ -174,11 +186,21 @@ const FilePickerModal = ({ onClick={() => toggleFileSelection(fileId)} > - toggleFileSelection(fileId)} - onClick={(e) => e.stopPropagation()} - /> + {allowMultiple ? ( + toggleFileSelection(fileId)} + onClick={(e) => e.stopPropagation()} + /> + ) : ( + toggleFileSelection(fileId)} + onClick={(e) => e.stopPropagation()} + style={{ margin: '4px' }} + /> + )} {/* Thumbnail */} setShowFilePickerModal(false)} - sharedFiles={sharedFiles} + storedFiles={sharedFiles} onSelectFiles={handleStorageSelection} + allowMultiple={allowMultiple} /> ); diff --git a/frontend/src/hooks/useFileWithUrl.ts b/frontend/src/hooks/useFileWithUrl.ts index d06cb73f2..aeb954cd1 100644 --- a/frontend/src/hooks/useFileWithUrl.ts +++ b/frontend/src/hooks/useFileWithUrl.ts @@ -8,15 +8,26 @@ export function useFileWithUrl(file: File | null): { file: File; url: string } | return useMemo(() => { if (!file) return null; - const url = URL.createObjectURL(file); + // Validate that file is a proper File or Blob object + if (!(file instanceof File) && !(file instanceof Blob)) { + console.warn('useFileWithUrl: Expected File or Blob, got:', file); + return null; + } - // Return object with cleanup function - const result = { file, url }; - - // Store cleanup function for later use - (result as any)._cleanup = () => URL.revokeObjectURL(url); - - return result; + try { + const url = URL.createObjectURL(file); + + // Return object with cleanup function + const result = { file, url }; + + // Store cleanup function for later use + (result as any)._cleanup = () => URL.revokeObjectURL(url); + + return result; + } catch (error) { + console.error('useFileWithUrl: Failed to create object URL:', error, file); + return null; + } }, [file]); } diff --git a/frontend/src/hooks/useToolParams.ts b/frontend/src/hooks/useToolParams.ts index be6145b3e..9c422da14 100644 --- a/frontend/src/hooks/useToolParams.ts +++ b/frontend/src/hooks/useToolParams.ts @@ -121,7 +121,7 @@ export function useToolParams(selectedToolKey: string, currentView: string) { }); setSearchParams(newParams, { replace: true }); - }, [selectedToolKey, currentView, setSearchParams, searchParams]); + }, [selectedToolKey, currentView, setSearchParams]); return { toolParams, diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index acd8a85b6..fdc823532 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -173,15 +173,109 @@ export default function HomePage() { }, [addToActiveFiles]); // Handle opening file editor with selected files - const handleOpenFileEditor = useCallback((selectedFiles) => { - setPreSelectedFiles(selectedFiles || []); - handleViewChange("fileEditor"); - }, [handleViewChange]); + const handleOpenFileEditor = useCallback(async (selectedFiles) => { + if (!selectedFiles || selectedFiles.length === 0) { + setPreSelectedFiles([]); + handleViewChange("fileEditor"); + return; + } + + // Convert FileWithUrl[] to File[] and add to activeFiles + try { + const convertedFiles = await Promise.all( + selectedFiles.map(async (fileItem) => { + // If it's already a File, return as is + if (fileItem instanceof File) { + return fileItem; + } + + // If it has a file property, use that + if (fileItem.file && fileItem.file instanceof File) { + return fileItem.file; + } + + // If it's from IndexedDB storage, reconstruct the File + if (fileItem.arrayBuffer && typeof fileItem.arrayBuffer === 'function') { + const arrayBuffer = await fileItem.arrayBuffer(); + const blob = new Blob([arrayBuffer], { type: fileItem.type || 'application/pdf' }); + const file = new File([blob], fileItem.name, { + type: fileItem.type || 'application/pdf', + lastModified: fileItem.lastModified || Date.now() + }); + // Mark as from storage to avoid re-storing + (file as any).storedInIndexedDB = true; + return file; + } + + console.warn('Could not convert file item:', fileItem); + return null; + }) + ); + + // Filter out nulls and add to activeFiles + const validFiles = convertedFiles.filter((f): f is File => f !== null); + setActiveFiles(validFiles); + setPreSelectedFiles([]); // Clear preselected since we're using activeFiles now + handleViewChange("fileEditor"); + } catch (error) { + console.error('Error converting selected files:', error); + } + }, [handleViewChange, setActiveFiles]); + + // Handle opening page editor with selected files + const handleOpenPageEditor = useCallback(async (selectedFiles) => { + if (!selectedFiles || selectedFiles.length === 0) { + handleViewChange("pageEditor"); + return; + } + + // Convert FileWithUrl[] to File[] and add to activeFiles + try { + const convertedFiles = await Promise.all( + selectedFiles.map(async (fileItem) => { + // If it's already a File, return as is + if (fileItem instanceof File) { + return fileItem; + } + + // If it has a file property, use that + if (fileItem.file && fileItem.file instanceof File) { + return fileItem.file; + } + + // If it's from IndexedDB storage, reconstruct the File + if (fileItem.arrayBuffer && typeof fileItem.arrayBuffer === 'function') { + const arrayBuffer = await fileItem.arrayBuffer(); + const blob = new Blob([arrayBuffer], { type: fileItem.type || 'application/pdf' }); + const file = new File([blob], fileItem.name, { + type: fileItem.type || 'application/pdf', + lastModified: fileItem.lastModified || Date.now() + }); + // Mark as from storage to avoid re-storing + (file as any).storedInIndexedDB = true; + return file; + } + + console.warn('Could not convert file item:', fileItem); + return null; + }) + ); + + // Filter out nulls and add to activeFiles + const validFiles = convertedFiles.filter((f): f is File => f !== null); + setActiveFiles(validFiles); + handleViewChange("pageEditor"); + } catch (error) { + console.error('Error converting selected files for page editor:', error); + } + }, [handleViewChange, setActiveFiles]); const selectedTool = toolRegistry[selectedToolKey]; - // Convert current active file to format expected by Viewer/PageEditor - const currentFileWithUrl = useFileWithUrl(activeFiles[0] || null); + // For Viewer - convert first active file to expected format (only when needed) + const currentFileWithUrl = useFileWithUrl( + (currentView === "viewer" && activeFiles[0]) ? activeFiles[0] : null + ); return ( ) : (currentView != "fileManager") && !activeFiles[0] ? ( @@ -309,8 +404,8 @@ export default function HomePage() { ) : currentView === "fileEditor" ? ( setPreSelectedFiles([])} onOpenPageEditor={(file) => { @@ -339,18 +434,12 @@ export default function HomePage() { ) : currentView === "pageEditor" ? ( <> { - if (fileObj) { - setCurrentActiveFile(fileObj.file); - } else { - setActiveFiles([]); - } - }} + activeFiles={activeFiles} + setActiveFiles={setActiveFiles} downloadUrl={downloadUrl} setDownloadUrl={setDownloadUrl} + sharedFiles={storedFiles} onFunctionsReady={setPageEditorFunctions} - sharedFiles={activeFiles} /> {activeFiles[0] && pageEditorFunctions && ( pageEditorFunctions.showExportPreview(true)} - onExportAll={() => pageEditorFunctions.showExportPreview(false)} + onExportSelected={pageEditorFunctions.onExportSelected} + onExportAll={pageEditorFunctions.onExportAll} exportLoading={pageEditorFunctions.exportLoading} selectionMode={pageEditorFunctions.selectionMode} selectedPages={pageEditorFunctions.selectedPages} @@ -376,6 +465,7 @@ export default function HomePage() { setFiles={setStoredFiles} setCurrentView={handleViewChange} onOpenFileEditor={handleOpenFileEditor} + onOpenPageEditor={handleOpenPageEditor} onLoadFileToActive={addToActiveFiles} /> )} From ffe5b9577bdcdc24966901a691ec059117128013 Mon Sep 17 00:00:00 2001 From: Reece Date: Tue, 24 Jun 2025 23:31:21 +0100 Subject: [PATCH 2/2] Page editor fixes and improvements and restructuring --- .../BulkSelectionPanel.tsx | 0 .../shared => pageEditor}/DragDropGrid.tsx | 10 +- .../{editor => pageEditor}/FileEditor.tsx | 2 +- .../{editor => pageEditor}/FileThumbnail.tsx | 0 .../PageEditor.module.css | 0 .../{editor => pageEditor}/PageEditor.tsx | 402 ++++++++++-------- .../PageEditorControls.tsx | 0 .../{editor => pageEditor}/PageThumbnail.tsx | 4 +- .../src/components/shared/FilePickerModal.tsx | 57 +-- .../components/shared/FileUploadSelector.tsx | 75 ++-- frontend/src/pages/HomePage.tsx | 28 +- 11 files changed, 313 insertions(+), 265 deletions(-) rename frontend/src/components/{editor => pageEditor}/BulkSelectionPanel.tsx (100%) rename frontend/src/components/{editor/shared => pageEditor}/DragDropGrid.tsx (95%) rename frontend/src/components/{editor => pageEditor}/FileEditor.tsx (99%) rename frontend/src/components/{editor => pageEditor}/FileThumbnail.tsx (100%) rename frontend/src/components/{editor => pageEditor}/PageEditor.module.css (100%) rename frontend/src/components/{editor => pageEditor}/PageEditor.tsx (70%) rename frontend/src/components/{editor => pageEditor}/PageEditorControls.tsx (100%) rename frontend/src/components/{editor => pageEditor}/PageThumbnail.tsx (99%) diff --git a/frontend/src/components/editor/BulkSelectionPanel.tsx b/frontend/src/components/pageEditor/BulkSelectionPanel.tsx similarity index 100% rename from frontend/src/components/editor/BulkSelectionPanel.tsx rename to frontend/src/components/pageEditor/BulkSelectionPanel.tsx diff --git a/frontend/src/components/editor/shared/DragDropGrid.tsx b/frontend/src/components/pageEditor/DragDropGrid.tsx similarity index 95% rename from frontend/src/components/editor/shared/DragDropGrid.tsx rename to frontend/src/components/pageEditor/DragDropGrid.tsx index 30bfe26bd..18ccda8f9 100644 --- a/frontend/src/components/editor/shared/DragDropGrid.tsx +++ b/frontend/src/components/pageEditor/DragDropGrid.tsx @@ -1,6 +1,6 @@ import React, { useState, useCallback, useRef, useEffect } from 'react'; import { Box } from '@mantine/core'; -import styles from '../PageEditor.module.css'; +import styles from './PageEditor.module.css'; interface DragDropItem { id: string; @@ -84,7 +84,7 @@ const DragDropGrid = ({ {/* Split marker */} {renderSplitMarker && item.splitBefore && index > 0 && renderSplitMarker(item, index)} - + {/* Item */} {renderItem(item, index, itemRefs)} @@ -95,8 +95,8 @@ const DragDropGrid = ({
({ ); }; -export default DragDropGrid; \ No newline at end of file +export default DragDropGrid; diff --git a/frontend/src/components/editor/FileEditor.tsx b/frontend/src/components/pageEditor/FileEditor.tsx similarity index 99% rename from frontend/src/components/editor/FileEditor.tsx rename to frontend/src/components/pageEditor/FileEditor.tsx index b60ccba81..7224f6453 100644 --- a/frontend/src/components/editor/FileEditor.tsx +++ b/frontend/src/components/pageEditor/FileEditor.tsx @@ -11,7 +11,7 @@ import { generateThumbnailForFile } from '../../utils/thumbnailUtils'; import styles from './PageEditor.module.css'; import FileThumbnail from './FileThumbnail'; import BulkSelectionPanel from './BulkSelectionPanel'; -import DragDropGrid from './shared/DragDropGrid'; +import DragDropGrid from './DragDropGrid'; import FilePickerModal from '../shared/FilePickerModal'; interface FileItem { diff --git a/frontend/src/components/editor/FileThumbnail.tsx b/frontend/src/components/pageEditor/FileThumbnail.tsx similarity index 100% rename from frontend/src/components/editor/FileThumbnail.tsx rename to frontend/src/components/pageEditor/FileThumbnail.tsx diff --git a/frontend/src/components/editor/PageEditor.module.css b/frontend/src/components/pageEditor/PageEditor.module.css similarity index 100% rename from frontend/src/components/editor/PageEditor.module.css rename to frontend/src/components/pageEditor/PageEditor.module.css diff --git a/frontend/src/components/editor/PageEditor.tsx b/frontend/src/components/pageEditor/PageEditor.tsx similarity index 70% rename from frontend/src/components/editor/PageEditor.tsx rename to frontend/src/components/pageEditor/PageEditor.tsx index 0adc4aba3..fc4f93e3d 100644 --- a/frontend/src/components/editor/PageEditor.tsx +++ b/frontend/src/components/pageEditor/PageEditor.tsx @@ -19,10 +19,10 @@ import { ToggleSplitCommand } from "../../commands/pageCommands"; import { pdfExportService } from "../../services/pdfExportService"; -import styles from './PageEditor.module.css'; +import styles from './pageEditor.module.css'; import PageThumbnail from './PageThumbnail'; import BulkSelectionPanel from './BulkSelectionPanel'; -import DragDropGrid from './shared/DragDropGrid'; +import DragDropGrid from './DragDropGrid'; import FilePickerModal from '../shared/FilePickerModal'; import FileUploadSelector from '../shared/FileUploadSelector'; @@ -63,14 +63,9 @@ const PageEditor = ({ const { t } = useTranslation(); const { processPDFFile, loading: pdfLoading } = usePDFProcessor(); - // Multi-file state - const [currentFileIndex, setCurrentFileIndex] = useState(0); + // Single merged document state + const [mergedPdfDocument, setMergedPdfDocument] = useState(null); const [processedFiles, setProcessedFiles] = useState>(new Map()); - - // Current file references - const currentFile = activeFiles[currentFileIndex] || null; - const currentFileKey = currentFile ? `${currentFile.name}-${currentFile.size}` : null; - const currentPdfDocument = currentFileKey ? processedFiles.get(currentFileKey) : null; const [filename, setFilename] = useState(""); // Page editor state @@ -80,18 +75,18 @@ const PageEditor = ({ const [error, setError] = useState(null); const [csvInput, setCsvInput] = useState(""); const [selectionMode, setSelectionMode] = useState(false); - + // Drag and drop state const [draggedPage, setDraggedPage] = useState(null); const [dropTarget, setDropTarget] = useState(null); const [multiPageDrag, setMultiPageDrag] = useState<{pageIds: string[], count: number} | null>(null); const [dragPosition, setDragPosition] = useState<{x: number, y: number} | null>(null); - + // Export state const [exportLoading, setExportLoading] = useState(false); const [showExportModal, setShowExportModal] = useState(false); const [exportPreview, setExportPreview] = useState<{pageCount: number; splitCount: number; estimatedSize: string} | null>(null); - + // Animation state const [movingPage, setMovingPage] = useState(null); const [pagePositions, setPagePositions] = useState>(new Map()); @@ -140,8 +135,8 @@ const PageEditor = ({ } const fileKey = `${fileToProcess.name}-${fileToProcess.size}`; - - // Skip if already processed + + // Skip processing if already processed if (processedFiles.has(fileKey)) return; setLoading(true); @@ -149,16 +144,12 @@ const PageEditor = ({ try { const document = await processPDFFile(fileToProcess); - + // Store processed document setProcessedFiles(prev => new Map(prev).set(fileKey, document)); setFilename(fileToProcess.name.replace(/\.pdf$/i, '')); setSelectedPages([]); - - // Add to activeFiles if not already there - if (!activeFiles.some(f => f.name === fileToProcess.name && f.size === fileToProcess.size)) { - setActiveFiles([...activeFiles, fileToProcess]); - } + if (document.pages.length > 0) { // Only store if it's a new file (not from storage) @@ -167,7 +158,7 @@ const PageEditor = ({ await fileStorage.storeFile(fileToProcess, thumbnail); } } - + setStatus(`PDF loaded successfully with ${document.totalPages} pages`); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to process PDF'; @@ -178,36 +169,111 @@ const PageEditor = ({ } }, [processPDFFile, activeFiles, setActiveFiles, processedFiles]); + // Process multiple uploaded files - just add them to activeFiles like FileManager does + const handleMultipleFileUpload = useCallback((uploadedFiles: File[]) => { + if (!uploadedFiles || uploadedFiles.length === 0) { + setError('No files provided'); + return; + } + + // Simply set the activeFiles to the selected files (same as FileManager approach) + setActiveFiles(uploadedFiles); + }, []); + + // Merge multiple PDF documents into one + const mergeAllPDFs = useCallback(() => { + if (activeFiles.length === 0) { + setMergedPdfDocument(null); + return; + } + + if (activeFiles.length === 1) { + // Single file - use it directly + const fileKey = `${activeFiles[0].name}-${activeFiles[0].size}`; + const pdfDoc = processedFiles.get(fileKey); + if (pdfDoc) { + setMergedPdfDocument(pdfDoc); + setFilename(activeFiles[0].name.replace(/\.pdf$/i, '')); + } + } else { + // Multiple files - merge them + const allPages: PDFPage[] = []; + let totalPages = 0; + const filenames: string[] = []; + + activeFiles.forEach((file, fileIndex) => { + const fileKey = `${file.name}-${file.size}`; + const pdfDoc = processedFiles.get(fileKey); + if (pdfDoc) { + filenames.push(file.name.replace(/\.pdf$/i, '')); + pdfDoc.pages.forEach((page, pageIndex) => { + // Create new page with updated IDs and page numbers for merged document + const newPage: PDFPage = { + ...page, + id: `${fileIndex}-${page.id}`, // Unique ID across all files + pageNumber: totalPages + pageIndex + 1, + sourceFile: file.name // Track which file this page came from + }; + allPages.push(newPage); + }); + totalPages += pdfDoc.pages.length; + } + }); + + const mergedDocument: PDFDocument = { + pages: allPages, + totalPages: totalPages, + title: filenames.join(' + '), + metadata: { + title: filenames.join(' + '), + createdAt: new Date().toISOString(), + modifiedAt: new Date().toISOString(), + } + }; + + setMergedPdfDocument(mergedDocument); + setFilename(filenames.join('_')); + } + }, [activeFiles, processedFiles]); + // Auto-process files from activeFiles useEffect(() => { + console.log('Auto-processing effect triggered:', { + activeFilesCount: activeFiles.length, + processedFilesCount: processedFiles.size, + activeFileNames: activeFiles.map(f => f.name) + }); + activeFiles.forEach(file => { const fileKey = `${file.name}-${file.size}`; + console.log(`Checking file ${file.name}: processed =`, processedFiles.has(fileKey)); if (!processedFiles.has(fileKey)) { + console.log('Processing file:', file.name); handleFileUpload(file); } }); }, [activeFiles, processedFiles, handleFileUpload]); - // Reset current file index when activeFiles changes + // Merge multiple PDF documents into one when all files are processed useEffect(() => { - if (currentFileIndex >= activeFiles.length) { - setCurrentFileIndex(0); - } - }, [activeFiles.length, currentFileIndex]); + if (activeFiles.length > 0) { + const allProcessed = activeFiles.every(file => { + const fileKey = `${file.name}-${file.size}`; + return processedFiles.has(fileKey); + }); - // Clear selections when switching files + if (allProcessed && activeFiles.length > 0) { + mergeAllPDFs(); + } + } + }, [activeFiles, processedFiles, mergeAllPDFs]); + + // Clear selections when files change useEffect(() => { setSelectedPages([]); setCsvInput(""); setSelectionMode(false); - }, [currentFileIndex]); - - // Update filename when current file changes - useEffect(() => { - if (currentFile) { - setFilename(currentFile.name.replace(/\.pdf$/i, '')); - } - }, [currentFile]); + }, [activeFiles]); // Global drag cleanup to handle drops outside valid areas useEffect(() => { @@ -236,10 +302,10 @@ const PageEditor = ({ }, [draggedPage]); const selectAll = useCallback(() => { - if (currentPdfDocument) { - setSelectedPages(currentPdfDocument.pages.map(p => p.id)); + if (mergedPdfDocument) { + setSelectedPages(mergedPdfDocument.pages.map(p => p.id)); } - }, [currentPdfDocument]); + }, [mergedPdfDocument]); const deselectAll = useCallback(() => setSelectedPages([]), []); @@ -264,7 +330,7 @@ const PageEditor = ({ }, []); const parseCSVInput = useCallback((csv: string) => { - if (!currentPdfDocument) return []; + if (!mergedPdfDocument) return []; const pageIds: string[] = []; const ranges = csv.split(',').map(s => s.trim()).filter(Boolean); @@ -272,23 +338,23 @@ const PageEditor = ({ ranges.forEach(range => { if (range.includes('-')) { const [start, end] = range.split('-').map(n => parseInt(n.trim())); - for (let i = start; i <= end && i <= currentPdfDocument.totalPages; i++) { + for (let i = start; i <= end && i <= mergedPdfDocument.totalPages; i++) { if (i > 0) { - const page = currentPdfDocument.pages.find(p => p.pageNumber === i); + const page = mergedPdfDocument.pages.find(p => p.pageNumber === i); if (page) pageIds.push(page.id); } } } else { const pageNum = parseInt(range); - if (pageNum > 0 && pageNum <= currentPdfDocument.totalPages) { - const page = currentPdfDocument.pages.find(p => p.pageNumber === pageNum); + if (pageNum > 0 && pageNum <= mergedPdfDocument.totalPages) { + const page = mergedPdfDocument.pages.find(p => p.pageNumber === pageNum); if (page) pageIds.push(page.id); } } }); return pageIds; - }, [currentPdfDocument]); + }, [mergedPdfDocument]); const updatePagesFromCSV = useCallback(() => { const pageIds = parseCSVInput(csvInput); @@ -362,110 +428,127 @@ const PageEditor = ({ // Don't clear drop target on drag leave - let dragover handle it }, []); - // Create setPdfDocument wrapper for current file + // Create setPdfDocument wrapper for merged document const setPdfDocument = useCallback((updatedDoc: PDFDocument) => { - if (currentFileKey) { - setProcessedFiles(prev => new Map(prev).set(currentFileKey, updatedDoc)); - } - }, [currentFileKey]); + setMergedPdfDocument(updatedDoc); + // Return the updated document for immediate use in animations + return updatedDoc; + }, []); const animateReorder = useCallback((pageId: string, targetIndex: number) => { - if (!currentPdfDocument || isAnimating) return; + if (!mergedPdfDocument || isAnimating) return; + // In selection mode, if the dragged page is selected, move all selected pages const pagesToMove = selectionMode && selectedPages.includes(pageId) ? selectedPages : [pageId]; - const originalIndex = currentPdfDocument.pages.findIndex(p => p.id === pageId); + const originalIndex = mergedPdfDocument.pages.findIndex(p => p.id === pageId); if (originalIndex === -1 || originalIndex === targetIndex) return; setIsAnimating(true); - // Get current positions of all pages + // Get current positions of all pages by querying DOM directly const currentPositions = new Map(); - currentPdfDocument.pages.forEach((page) => { - const element = pageRefs.current.get(page.id); - if (element) { + const allCurrentElements = Array.from(document.querySelectorAll('[data-page-id]')); + + + // Capture positions from actual DOM elements + allCurrentElements.forEach((element) => { + const pageId = element.getAttribute('data-page-id'); + if (pageId) { const rect = element.getBoundingClientRect(); - currentPositions.set(page.id, { x: rect.left, y: rect.top }); + currentPositions.set(pageId, { x: rect.left, y: rect.top }); } }); + // Execute the reorder - for multi-page, we use a different command if (pagesToMove.length > 1) { // Multi-page move - use MovePagesCommand - const command = new MovePagesCommand(currentPdfDocument, setPdfDocument, pagesToMove, targetIndex); + const command = new MovePagesCommand(mergedPdfDocument, setPdfDocument, pagesToMove, targetIndex); executeCommand(command); } else { // Single page move - const command = new ReorderPageCommand(currentPdfDocument, setPdfDocument, pageId, targetIndex); + const command = new ReorderPageCommand(mergedPdfDocument, setPdfDocument, pageId, targetIndex); executeCommand(command); } - // Wait for DOM to update, then get new positions and animate - requestAnimationFrame(() => { + // Wait for state update and DOM to update, then get new positions and animate + setTimeout(() => { requestAnimationFrame(() => { - const newPositions = new Map(); + requestAnimationFrame(() => { + const newPositions = new Map(); - // Get the updated document from the state after command execution - const currentDoc = currentPdfDocument; // This should be the updated version after command + // Re-get all page elements after state update + const allPageElements = Array.from(document.querySelectorAll('[data-page-id]')); - currentDoc.pages.forEach((page) => { - const element = pageRefs.current.get(page.id); - if (element) { - const rect = element.getBoundingClientRect(); - newPositions.set(page.id, { x: rect.left, y: rect.top }); - } - }); - - // Calculate and apply animations - currentDoc.pages.forEach((page) => { - const element = pageRefs.current.get(page.id); - const currentPos = currentPositions.get(page.id); - const newPos = newPositions.get(page.id); - - if (element && currentPos && newPos) { - const deltaX = currentPos.x - newPos.x; - const deltaY = currentPos.y - newPos.y; - - // Apply initial transform (from new position back to old position) - 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.4s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; - element.style.transform = 'translate(0px, 0px)'; - } - }); - - // Clean up after animation - setTimeout(() => { - currentDoc.pages.forEach((page) => { - const element = pageRefs.current.get(page.id); - if (element) { - element.style.transform = ''; - element.style.transition = ''; + allPageElements.forEach((element) => { + const pageId = element.getAttribute('data-page-id'); + if (pageId) { + const rect = element.getBoundingClientRect(); + newPositions.set(pageId, { x: rect.left, y: rect.top }); } }); - setIsAnimating(false); - }, 400); + + let animationCount = 0; + + // Calculate and apply animations using DOM elements directly + allPageElements.forEach((element) => { + const pageId = element.getAttribute('data-page-id'); + if (!pageId) return; + + const currentPos = currentPositions.get(pageId); + const newPos = newPositions.get(pageId); + + if (element && currentPos && newPos) { + const deltaX = currentPos.x - newPos.x; + const deltaY = currentPos.y - newPos.y; + + + if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) { + animationCount++; + const htmlElement = element as HTMLElement; + // Apply initial transform (from new position back to old position) + htmlElement.style.transform = `translate(${deltaX}px, ${deltaY}px)`; + htmlElement.style.transition = 'none'; + + // Force reflow + htmlElement.offsetHeight; + + // Animate to final position + htmlElement.style.transition = 'transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; + htmlElement.style.transform = 'translate(0px, 0px)'; + } + } + }); + + + // Clean up after animation + setTimeout(() => { + const elementsToCleanup = Array.from(document.querySelectorAll('[data-page-id]')); + elementsToCleanup.forEach((element) => { + const htmlElement = element as HTMLElement; + htmlElement.style.transform = ''; + htmlElement.style.transition = ''; + }); + setIsAnimating(false); + }, 400); + }); }); - }); - }, [currentPdfDocument, isAnimating, executeCommand, selectionMode, selectedPages, setPdfDocument]); + }, 10); // Small delay to allow state update + }, [mergedPdfDocument, isAnimating, executeCommand, selectionMode, selectedPages, setPdfDocument]); const handleDrop = useCallback((e: React.DragEvent, targetPageId: string | 'end') => { e.preventDefault(); - if (!draggedPage || !currentPdfDocument || draggedPage === targetPageId) return; + if (!draggedPage || !mergedPdfDocument || draggedPage === targetPageId) return; let targetIndex: number; if (targetPageId === 'end') { - targetIndex = currentPdfDocument.pages.length; + targetIndex = mergedPdfDocument.pages.length; } else { - targetIndex = currentPdfDocument.pages.findIndex(p => p.id === targetPageId); + targetIndex = mergedPdfDocument.pages.findIndex(p => p.id === targetPageId); if (targetIndex === -1) return; } @@ -478,7 +561,7 @@ const PageEditor = ({ const moveCount = multiPageDrag ? multiPageDrag.count : 1; setStatus(`${moveCount > 1 ? `${moveCount} pages` : 'Page'} reordered`); - }, [draggedPage, currentPdfDocument, animateReorder, multiPageDrag]); + }, [draggedPage, mergedPdfDocument, animateReorder, multiPageDrag]); const handleEndZoneDragEnter = useCallback(() => { if (draggedPage) { @@ -487,38 +570,38 @@ const PageEditor = ({ }, [draggedPage]); const handleRotate = useCallback((direction: 'left' | 'right') => { - if (!currentPdfDocument) return; + if (!mergedPdfDocument) return; const rotation = direction === 'left' ? -90 : 90; const pagesToRotate = selectionMode ? selectedPages - : currentPdfDocument.pages.map(p => p.id); + : mergedPdfDocument.pages.map(p => p.id); if (selectionMode && selectedPages.length === 0) return; const command = new RotatePagesCommand( - currentPdfDocument, + mergedPdfDocument, setPdfDocument, pagesToRotate, rotation ); executeCommand(command); - const pageCount = selectionMode ? selectedPages.length : currentPdfDocument.pages.length; + const pageCount = selectionMode ? selectedPages.length : mergedPdfDocument.pages.length; setStatus(`Rotated ${pageCount} pages ${direction}`); - }, [currentPdfDocument, selectedPages, selectionMode, executeCommand, setPdfDocument]); + }, [mergedPdfDocument, selectedPages, selectionMode, executeCommand, setPdfDocument]); const handleDelete = useCallback(() => { - if (!currentPdfDocument) return; + if (!mergedPdfDocument) return; const pagesToDelete = selectionMode ? selectedPages - : currentPdfDocument.pages.map(p => p.id); + : mergedPdfDocument.pages.map(p => p.id); if (selectionMode && selectedPages.length === 0) return; const command = new DeletePagesCommand( - currentPdfDocument, + mergedPdfDocument, setPdfDocument, pagesToDelete ); @@ -527,55 +610,55 @@ const PageEditor = ({ if (selectionMode) { setSelectedPages([]); } - const pageCount = selectionMode ? selectedPages.length : currentPdfDocument.pages.length; + const pageCount = selectionMode ? selectedPages.length : mergedPdfDocument.pages.length; setStatus(`Deleted ${pageCount} pages`); - }, [currentPdfDocument, selectedPages, selectionMode, executeCommand, setPdfDocument]); + }, [mergedPdfDocument, selectedPages, selectionMode, executeCommand, setPdfDocument]); const handleSplit = useCallback(() => { - if (!currentPdfDocument) return; + if (!mergedPdfDocument) return; const pagesToSplit = selectionMode ? selectedPages - : currentPdfDocument.pages.map(p => p.id); + : mergedPdfDocument.pages.map(p => p.id); if (selectionMode && selectedPages.length === 0) return; const command = new ToggleSplitCommand( - currentPdfDocument, + mergedPdfDocument, setPdfDocument, pagesToSplit ); executeCommand(command); - const pageCount = selectionMode ? selectedPages.length : currentPdfDocument.pages.length; + const pageCount = selectionMode ? selectedPages.length : mergedPdfDocument.pages.length; setStatus(`Split markers toggled for ${pageCount} pages`); - }, [currentPdfDocument, selectedPages, selectionMode, executeCommand, setPdfDocument]); + }, [mergedPdfDocument, selectedPages, selectionMode, executeCommand, setPdfDocument]); const showExportPreview = useCallback((selectedOnly: boolean = false) => { - if (!currentPdfDocument) return; + if (!mergedPdfDocument) return; const exportPageIds = selectedOnly ? selectedPages : []; - const preview = pdfExportService.getExportInfo(currentPdfDocument, exportPageIds, selectedOnly); + const preview = pdfExportService.getExportInfo(mergedPdfDocument, exportPageIds, selectedOnly); setExportPreview(preview); setShowExportModal(true); - }, [currentPdfDocument, selectedPages]); + }, [mergedPdfDocument, selectedPages]); const handleExport = useCallback(async (selectedOnly: boolean = false) => { - if (!currentPdfDocument) return; + if (!mergedPdfDocument) return; setExportLoading(true); try { const exportPageIds = selectedOnly ? selectedPages : []; - const errors = pdfExportService.validateExport(currentPdfDocument, exportPageIds, selectedOnly); + const errors = pdfExportService.validateExport(mergedPdfDocument, exportPageIds, selectedOnly); if (errors.length > 0) { setError(errors.join(', ')); return; } - const hasSplitMarkers = currentPdfDocument.pages.some(page => page.splitBefore); + const hasSplitMarkers = mergedPdfDocument.pages.some(page => page.splitBefore); if (hasSplitMarkers) { - const result = await pdfExportService.exportPDF(currentPdfDocument, exportPageIds, { + const result = await pdfExportService.exportPDF(mergedPdfDocument, exportPageIds, { selectedOnly, filename, splitDocuments: true @@ -589,7 +672,7 @@ const PageEditor = ({ setStatus(`Exported ${result.blobs.length} split documents`); } else { - const result = await pdfExportService.exportPDF(currentPdfDocument, exportPageIds, { + const result = await pdfExportService.exportPDF(mergedPdfDocument, exportPageIds, { selectedOnly, filename }) as { blob: Blob; filename: string }; @@ -603,7 +686,7 @@ const PageEditor = ({ } finally { setExportLoading(false); } - }, [currentPdfDocument, selectedPages, filename]); + }, [mergedPdfDocument, selectedPages, filename]); const handleUndo = useCallback(() => { if (undo()) { @@ -618,9 +701,9 @@ const PageEditor = ({ }, [redo]); const closePdf = useCallback(() => { - setCurrentFileIndex(0); setActiveFiles([]); setProcessedFiles(new Map()); + setMergedPdfDocument(null); setSelectedPages([]); }, [setActiveFiles]); @@ -666,18 +749,17 @@ const PageEditor = ({ closePdf ]); - if (!currentPdfDocument) { + if (!mergedPdfDocument) { return ( @@ -690,36 +772,6 @@ const PageEditor = ({ - {/* File Switcher Tabs */} - {activeFiles.length > 1 && ( - - - {activeFiles.map((file, index) => { - const isActive = index === currentFileIndex; - - return ( - - ); - })} - - - )} @@ -762,7 +814,7 @@ const PageEditor = ({ )} )} @@ -849,7 +901,7 @@ const PageEditor = ({ {exportPreview.estimatedSize} - {currentPdfDocument && currentPdfDocument.pages.some(p => p.splitBefore) && ( + {mergedPdfDocument && mergedPdfDocument.pages.some(p => p.splitBefore) && ( This will create multiple PDF files based on split markers. @@ -867,7 +919,7 @@ const PageEditor = ({ loading={exportLoading} onClick={() => { setShowExportModal(false); - const selectedOnly = exportPreview.pageCount < (currentPdfDocument?.totalPages || 0); + const selectedOnly = exportPreview.pageCount < (mergedPdfDocument?.totalPages || 0); handleExport(selectedOnly); }} > @@ -911,4 +963,4 @@ const PageEditor = ({ ); }; -export default PageEditor; \ No newline at end of file +export default PageEditor; diff --git a/frontend/src/components/editor/PageEditorControls.tsx b/frontend/src/components/pageEditor/PageEditorControls.tsx similarity index 100% rename from frontend/src/components/editor/PageEditorControls.tsx rename to frontend/src/components/pageEditor/PageEditorControls.tsx diff --git a/frontend/src/components/editor/PageThumbnail.tsx b/frontend/src/components/pageEditor/PageThumbnail.tsx similarity index 99% rename from frontend/src/components/editor/PageThumbnail.tsx rename to frontend/src/components/pageEditor/PageThumbnail.tsx index 1dc70843d..53626fd05 100644 --- a/frontend/src/components/editor/PageThumbnail.tsx +++ b/frontend/src/components/pageEditor/PageThumbnail.tsx @@ -7,7 +7,7 @@ import RotateRightIcon from '@mui/icons-material/RotateRight'; import DeleteIcon from '@mui/icons-material/Delete'; import ContentCutIcon from '@mui/icons-material/ContentCut'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; -import { PDFPage } from '../../types/pageEditor'; +import { PDFPage } from '../../../types/pageEditor'; import styles from './PageEditor.module.css'; interface PageThumbnailProps { @@ -355,4 +355,4 @@ const PageThumbnail = ({ ); }; -export default PageThumbnail; \ No newline at end of file +export default PageThumbnail; diff --git a/frontend/src/components/shared/FilePickerModal.tsx b/frontend/src/components/shared/FilePickerModal.tsx index cad7fd373..f489c5a11 100644 --- a/frontend/src/components/shared/FilePickerModal.tsx +++ b/frontend/src/components/shared/FilePickerModal.tsx @@ -21,7 +21,6 @@ interface FilePickerModalProps { onClose: () => void; storedFiles: any[]; // Files from storage (FileWithUrl format) onSelectFiles: (selectedFiles: File[]) => void; - allowMultiple?: boolean; } const FilePickerModal = ({ @@ -29,7 +28,6 @@ const FilePickerModal = ({ onClose, storedFiles, onSelectFiles, - allowMultiple = true, }: FilePickerModalProps) => { const { t } = useTranslation(); const [selectedFileIds, setSelectedFileIds] = useState([]); @@ -43,21 +41,14 @@ const FilePickerModal = ({ const toggleFileSelection = (fileId: string) => { setSelectedFileIds(prev => { - if (allowMultiple) { - return prev.includes(fileId) - ? prev.filter(id => id !== fileId) - : [...prev, fileId]; - } else { - // Single selection mode - return prev.includes(fileId) ? [] : [fileId]; - } + return prev.includes(fileId) + ? prev.filter(id => id !== fileId) + : [...prev, fileId]; }); }; const selectAll = () => { - if (allowMultiple) { - setSelectedFileIds(storedFiles.map(f => f.id || f.name)); - } + setSelectedFileIds(storedFiles.map(f => f.id || f.name)); }; const selectNone = () => { @@ -145,20 +136,18 @@ const FilePickerModal = ({ {storedFiles.length} {t("fileUpload.filesAvailable", "files available")} - {allowMultiple && selectedFileIds.length > 0 && ( + {selectedFileIds.length > 0 && ( <> • {selectedFileIds.length} selected )} - {allowMultiple && ( - - - - - )} + + + + {/* File grid */} @@ -186,21 +175,11 @@ const FilePickerModal = ({ onClick={() => toggleFileSelection(fileId)} > - {allowMultiple ? ( - toggleFileSelection(fileId)} - onClick={(e) => e.stopPropagation()} - /> - ) : ( - toggleFileSelection(fileId)} - onClick={(e) => e.stopPropagation()} - style={{ margin: '4px' }} - /> - )} + toggleFileSelection(fileId)} + onClick={(e) => e.stopPropagation()} + /> {/* Thumbnail */} void; - onFilesSelect?: (files: File[]) => void; - allowMultiple?: boolean; + onFileSelect?: (file: File) => void; + onFilesSelect: (files: File[]) => void; accept?: string[]; // Loading state @@ -30,39 +29,54 @@ const FileUploadSelector = ({ sharedFiles = [], onFileSelect, onFilesSelect, - allowMultiple = false, accept = ["application/pdf"], loading = false, disabled = false, }: FileUploadSelectorProps) => { const { t } = useTranslation(); const [showFilePickerModal, setShowFilePickerModal] = useState(false); + const fileInputRef = useRef(null); const handleFileUpload = useCallback((uploadedFiles: File[]) => { if (uploadedFiles.length === 0) return; - if (allowMultiple && onFilesSelect) { + if (onFilesSelect) { onFilesSelect(uploadedFiles); - } else { + } else if (onFileSelect) { onFileSelect(uploadedFiles[0]); } - }, [allowMultiple, onFileSelect, onFilesSelect]); + }, [onFileSelect, onFilesSelect]); + + const handleFileInputChange = useCallback((event: React.ChangeEvent) => { + const files = event.target.files; + if (files && files.length > 0) { + const fileArray = Array.from(files); + console.log('File input change:', fileArray.length, 'files'); + handleFileUpload(fileArray); + } + // Reset input + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }, [handleFileUpload]); + + const openFileDialog = useCallback(() => { + fileInputRef.current?.click(); + }, []); const handleStorageSelection = useCallback((selectedFiles: File[]) => { if (selectedFiles.length === 0) return; - if (allowMultiple && onFilesSelect) { + if (onFilesSelect) { onFilesSelect(selectedFiles); - } else { + } else if (onFileSelect) { onFileSelect(selectedFiles[0]); } - }, [allowMultiple, onFileSelect, onFilesSelect]); + }, [onFileSelect, onFilesSelect]); // Get default title and subtitle from translations if not provided - const displayTitle = title || t(allowMultiple ? "fileUpload.selectFiles" : "fileUpload.selectFile", - allowMultiple ? "Select files" : "Select a file"); - const displaySubtitle = subtitle || t(allowMultiple ? "fileUpload.chooseFromStorageMultiple" : "fileUpload.chooseFromStorage", - allowMultiple ? "Choose files from storage or upload new PDFs" : "Choose a file from storage or upload a new PDF"); + const displayTitle = title || t("fileUpload.selectFiles", "Select files"); + const displaySubtitle = subtitle || t("fileUpload.chooseFromStorageMultiple", "Choose files from storage or upload new PDFs"); return ( <> @@ -98,15 +112,15 @@ const FileUploadSelector = ({
- {t(allowMultiple ? "fileUpload.dropFilesHere" : "fileUpload.dropFileHere", - allowMultiple ? "Drop files here or click to upload" : "Drop file here or click to upload")} + {t("fileUpload.dropFilesHere", "Drop files here or click to upload")} {accept.includes('application/pdf') @@ -118,23 +132,27 @@ const FileUploadSelector = ({
) : ( - + - + + {/* Manual file input as backup */} + + )} @@ -145,7 +163,6 @@ const FileUploadSelector = ({ onClose={() => setShowFilePickerModal(false)} storedFiles={sharedFiles} onSelectFiles={handleStorageSelection} - allowMultiple={allowMultiple} /> ); diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index fdc823532..515676bd8 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -14,9 +14,9 @@ import rainbowStyles from '../styles/rainbow.module.css'; import ToolPicker from "../components/tools/ToolPicker"; import TopControls from "../components/shared/TopControls"; import FileManager from "../components/fileManagement/FileManager"; -import FileEditor from "../components/editor/FileEditor"; -import PageEditor from "../components/editor/PageEditor"; -import PageEditorControls from "../components/editor/PageEditorControls"; +import FileEditor from "../components/pageEditor/FileEditor"; +import PageEditor from "../components/pageEditor/PageEditor"; +import PageEditorControls from "../components/pageEditor/PageEditorControls"; import Viewer from "../components/viewer/Viewer"; import FileUploadSelector from "../components/shared/FileUploadSelector"; import SplitPdfPanel from "../tools/Split"; @@ -188,12 +188,12 @@ export default function HomePage() { if (fileItem instanceof File) { return fileItem; } - + // If it has a file property, use that if (fileItem.file && fileItem.file instanceof File) { return fileItem.file; } - + // If it's from IndexedDB storage, reconstruct the File if (fileItem.arrayBuffer && typeof fileItem.arrayBuffer === 'function') { const arrayBuffer = await fileItem.arrayBuffer(); @@ -206,12 +206,12 @@ export default function HomePage() { (file as any).storedInIndexedDB = true; return file; } - + console.warn('Could not convert file item:', fileItem); return null; }) ); - + // Filter out nulls and add to activeFiles const validFiles = convertedFiles.filter((f): f is File => f !== null); setActiveFiles(validFiles); @@ -222,7 +222,7 @@ export default function HomePage() { } }, [handleViewChange, setActiveFiles]); - // Handle opening page editor with selected files + // Handle opening page editor with selected files const handleOpenPageEditor = useCallback(async (selectedFiles) => { if (!selectedFiles || selectedFiles.length === 0) { handleViewChange("pageEditor"); @@ -237,12 +237,12 @@ export default function HomePage() { if (fileItem instanceof File) { return fileItem; } - + // If it has a file property, use that if (fileItem.file && fileItem.file instanceof File) { return fileItem.file; } - + // If it's from IndexedDB storage, reconstruct the File if (fileItem.arrayBuffer && typeof fileItem.arrayBuffer === 'function') { const arrayBuffer = await fileItem.arrayBuffer(); @@ -255,12 +255,12 @@ export default function HomePage() { (file as any).storedInIndexedDB = true; return file; } - + console.warn('Could not convert file item:', fileItem); return null; }) ); - + // Filter out nulls and add to activeFiles const validFiles = convertedFiles.filter((f): f is File => f !== null); setActiveFiles(validFiles); @@ -388,8 +388,8 @@ export default function HomePage() { ) : (currentView != "fileManager") && !activeFiles[0] ? (