diff --git a/frontend/src/components/editor/FileEditor.tsx b/frontend/src/components/editor/FileEditor.tsx index 02c9a4cee..bd23b2ea0 100644 --- a/frontend/src/components/editor/FileEditor.tsx +++ b/frontend/src/components/editor/FileEditor.tsx @@ -27,9 +27,9 @@ interface FileItem { interface FileEditorProps { onOpenPageEditor?: (file: File) => void; onMergeFiles?: (files: File[]) => void; - sharedFiles?: any[]; - setSharedFiles?: (files: any[]) => void; - preSelectedFiles?: any[]; + sharedFiles?: { file: File; url: string }[]; + setSharedFiles?: (files: { file: File; url: string }[]) => void; + preSelectedFiles?: { file: File; url: string }[]; onClearPreSelection?: () => void; } diff --git a/frontend/src/components/editor/PageEditor.tsx b/frontend/src/components/editor/PageEditor.tsx index 0897266f5..9a84ab6d4 100644 --- a/frontend/src/components/editor/PageEditor.tsx +++ b/frontend/src/components/editor/PageEditor.tsx @@ -31,6 +31,7 @@ export interface PageEditorProps { setFile?: (file: { file: File; url: string } | null) => void; downloadUrl?: string | null; setDownloadUrl?: (url: string | null) => void; + sharedFiles?: { file: File; url: string }[]; // Optional callbacks to expose internal functions onFunctionsReady?: (functions: { diff --git a/frontend/src/components/editor/PageThumbnail.tsx b/frontend/src/components/editor/PageThumbnail.tsx index b820742ae..89d0811b1 100644 --- a/frontend/src/components/editor/PageThumbnail.tsx +++ b/frontend/src/components/editor/PageThumbnail.tsx @@ -69,13 +69,6 @@ const PageThumbnail = ({ }: PageThumbnailProps) => { return (
{ - if (el) { - pageRefs.current.set(page.id, el); - } else { - pageRefs.current.delete(page.id); - } - }} data-page-id={page.id} className={` ${styles.pageContainer} diff --git a/frontend/src/components/fileManagement/FileManager.tsx b/frontend/src/components/fileManagement/FileManager.tsx index e9904a708..8b6f91b33 100644 --- a/frontend/src/components/fileManagement/FileManager.tsx +++ b/frontend/src/components/fileManagement/FileManager.tsx @@ -22,6 +22,7 @@ interface FileManagerProps { allowMultiple?: boolean; setCurrentView?: (view: string) => void; onOpenFileEditor?: (selectedFiles?: FileWithUrl[]) => void; + onLoadFileToActive?: (file: File) => void; } const FileManager = ({ @@ -30,6 +31,7 @@ const FileManager = ({ allowMultiple = true, setCurrentView, onOpenFileEditor, + onLoadFileToActive, }: FileManagerProps) => { const { t } = useTranslation(); const [loading, setLoading] = useState(false); @@ -210,28 +212,56 @@ const FileManager = ({ const handleFileDoubleClick = async (file: FileWithUrl) => { try { - const url = await createBlobUrlForFile(file); - // Add file to the beginning of files array and switch to viewer - setFiles(prev => [{ file: file, url: url }, ...prev.filter(f => f.id !== file.id)]); - setCurrentView && setCurrentView("viewer"); + // Reconstruct File object from storage and add to active files + if (onLoadFileToActive) { + const reconstructedFile = await reconstructFileFromStorage(file); + onLoadFileToActive(reconstructedFile); + setCurrentView && setCurrentView("viewer"); + } } catch (error) { - console.error('Failed to create blob URL for file:', error); + console.error('Failed to load file to active set:', error); setNotification('Failed to open file. It may have been removed from storage.'); } }; const handleFileView = async (file: FileWithUrl) => { try { - const url = await createBlobUrlForFile(file); - // Add file to the beginning of files array and switch to viewer - setFiles(prev => [{ file: file, url: url }, ...prev.filter(f => f.id !== file.id)]); - setCurrentView && setCurrentView("viewer"); + // Reconstruct File object from storage and add to active files + if (onLoadFileToActive) { + const reconstructedFile = await reconstructFileFromStorage(file); + onLoadFileToActive(reconstructedFile); + setCurrentView && setCurrentView("viewer"); + } } catch (error) { - console.error('Failed to create blob URL for file:', error); + console.error('Failed to load file to active set:', error); setNotification('Failed to open file. It may have been removed from storage.'); } }; + const reconstructFileFromStorage = async (fileWithUrl: FileWithUrl): Promise => { + // If it's already a regular file, return it + if (fileWithUrl instanceof File) { + return fileWithUrl; + } + + // Reconstruct from IndexedDB + const arrayBuffer = await createBlobUrlForFile(fileWithUrl); + if (typeof arrayBuffer === 'string') { + // createBlobUrlForFile returned a blob URL, we need the actual data + const response = await fetch(arrayBuffer); + const data = await response.arrayBuffer(); + return new File([data], fileWithUrl.name, { + type: fileWithUrl.type || 'application/pdf', + lastModified: fileWithUrl.lastModified || Date.now() + }); + } else { + return new File([arrayBuffer], fileWithUrl.name, { + type: fileWithUrl.type || 'application/pdf', + lastModified: fileWithUrl.lastModified || Date.now() + }); + } + }; + const handleFileEdit = (file: FileWithUrl) => { if (onOpenFileEditor) { onOpenFileEditor([file]); @@ -309,8 +339,11 @@ const FileManager = ({ subtitle="Add files to your storage for easy access across tools" sharedFiles={[]} // FileManager is the source, so no shared files onFilesSelect={(uploadedFiles) => { - // Handle multiple files + // Handle multiple files - add to storage AND active set handleDrop(uploadedFiles); + if (onLoadFileToActive && uploadedFiles.length > 0) { + uploadedFiles.forEach(onLoadFileToActive); + } }} allowMultiple={allowMultiple} accept={["application/pdf"]} diff --git a/frontend/src/components/shared/FileUploadSelector.tsx b/frontend/src/components/shared/FileUploadSelector.tsx index 929a3af49..18bfa282a 100644 --- a/frontend/src/components/shared/FileUploadSelector.tsx +++ b/frontend/src/components/shared/FileUploadSelector.tsx @@ -10,14 +10,14 @@ interface FileUploadSelectorProps { title?: string; subtitle?: string; showDropzone?: boolean; - + // File handling sharedFiles?: any[]; onFileSelect: (file: File) => void; onFilesSelect?: (files: File[]) => void; allowMultiple?: boolean; accept?: string[]; - + // Loading state loading?: boolean; disabled?: boolean; @@ -40,7 +40,7 @@ const FileUploadSelector = ({ const handleFileUpload = useCallback((uploadedFiles: File[]) => { if (uploadedFiles.length === 0) return; - + if (allowMultiple && onFilesSelect) { onFilesSelect(uploadedFiles); } else { @@ -50,7 +50,7 @@ const FileUploadSelector = ({ const handleStorageSelection = useCallback((selectedFiles: File[]) => { if (selectedFiles.length === 0) return; - + if (allowMultiple && onFilesSelect) { onFilesSelect(selectedFiles); } else { @@ -81,13 +81,13 @@ const FileUploadSelector = ({ disabled={disabled || sharedFiles.length === 0} loading={loading} > - Load from Storage ({sharedFiles.length} files available) + {loading ? "Loading..." : `Load from Storage (${sharedFiles.length} files available)`} - + or - + {showDropzone ? ( - +
+ + {/* Tool title */} +
+

{selectedTool?.name}

+
+ + {/* Tool content */} +
+ +
+ + )} + + + + {/* Main View */} {/* Main content area */} - - {currentView === "fileManager" ? ( - - ) : (currentView !== "fileManager") && !files[0] ? ( - - + {currentView === "fileManager" ? ( + - - ) : currentView === "fileEditor" ? ( - setPreSelectedFiles([])} - onOpenPageEditor={(file) => { - const fileObj = { file, url: URL.createObjectURL(file) }; - setFiles([fileObj]); - setCurrentView("pageEditor"); - }} - onMergeFiles={(filesToMerge) => { - setFiles(filesToMerge.map(f => ({ file: f, url: URL.createObjectURL(f) }))); - setCurrentView("viewer"); - }} - /> - ) : currentView === "viewer" ? ( - setFiles([fileObj])} - sidebarsVisible={sidebarsVisible} - setSidebarsVisible={setSidebarsVisible} - /> - ) : currentView === "pageEditor" ? ( - <> - setFiles([fileObj])} - downloadUrl={downloadUrl} - setDownloadUrl={setDownloadUrl} - onFunctionsReady={setPageEditorFunctions} - sharedFiles={files} - /> - {files[0] && pageEditorFunctions && ( - pageEditorFunctions.showExportPreview(true)} - onExportAll={() => pageEditorFunctions.showExportPreview(false)} - exportLoading={pageEditorFunctions.exportLoading} - selectionMode={pageEditorFunctions.selectionMode} - selectedPages={pageEditorFunctions.selectedPages} + ) : (currentView != "fileManager") && !activeFiles[0] ? ( + + { + addToActiveFiles(file); + }} + allowMultiple={false} + accept={["application/pdf"]} + loading={false} /> - )} - - ) : ( - - )} - + + ) : currentView === "fileEditor" ? ( + setPreSelectedFiles([])} + onOpenPageEditor={(file) => { + setCurrentActiveFile(file); + handleViewChange("pageEditor"); + }} + onMergeFiles={(filesToMerge) => { + // Add merged files to active set + filesToMerge.forEach(addToActiveFiles); + handleViewChange("viewer"); + }} + /> + ) : currentView === "viewer" ? ( + { + if (fileObj) { + setCurrentActiveFile(fileObj.file); + } else { + setActiveFiles([]); + } + }} + sidebarsVisible={sidebarsVisible} + setSidebarsVisible={setSidebarsVisible} + /> + ) : currentView === "pageEditor" ? ( + <> + { + if (fileObj) { + setCurrentActiveFile(fileObj.file); + } else { + setActiveFiles([]); + } + }} + downloadUrl={downloadUrl} + setDownloadUrl={setDownloadUrl} + onFunctionsReady={setPageEditorFunctions} + sharedFiles={activeFiles} + /> + {activeFiles[0] && pageEditorFunctions && ( + pageEditorFunctions.showExportPreview(true)} + onExportAll={() => pageEditorFunctions.showExportPreview(false)} + exportLoading={pageEditorFunctions.exportLoading} + selectionMode={pageEditorFunctions.selectionMode} + selectedPages={pageEditorFunctions.selectedPages} + /> + )} + + ) : ( + + )} + );