void;
- sharedFiles: any[];
- onSelectFiles: (selectedFiles: any[]) => void;
+ storedFiles: any[]; // Files from storage (FileWithUrl format)
+ onSelectFiles: (selectedFiles: File[]) => void;
}
const FilePickerModal = ({
opened,
onClose,
- sharedFiles,
+ storedFiles,
onSelectFiles,
}: FilePickerModalProps) => {
const { t } = useTranslation();
@@ -40,15 +40,15 @@ const FilePickerModal = ({
}, [opened]);
const toggleFileSelection = (fileId: string) => {
- setSelectedFileIds(prev =>
- prev.includes(fileId)
+ setSelectedFileIds(prev => {
+ return prev.includes(fileId)
? prev.filter(id => id !== fileId)
- : [...prev, fileId]
- );
+ : [...prev, fileId];
+ });
};
const selectAll = () => {
- setSelectedFileIds(sharedFiles.map(f => f.id || f.name));
+ setSelectedFileIds(storedFiles.map(f => f.id || f.name));
};
const selectNone = () => {
@@ -56,56 +56,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 +126,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,7 +135,10 @@ const FilePickerModal = ({
{/* Selection controls */}
- {sharedFiles.length} {t("fileUpload.filesAvailable", "files available")}
+ {storedFiles.length} {t("fileUpload.filesAvailable", "files available")}
+ {selectedFileIds.length > 0 && (
+ <> • {selectedFileIds.length} selected>
+ )}
)}
@@ -143,7 +161,7 @@ const FileUploadSelector = ({
setShowFilePickerModal(false)}
- sharedFiles={sharedFiles}
+ storedFiles={sharedFiles}
onSelectFiles={handleStorageSelection}
/>
>
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..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";
@@ -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] ? (
) : 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}
/>
)}