mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-07-27 07:35:22 +00:00
File handling fixes
This commit is contained in:
parent
25e9db2570
commit
2f90220b7b
@ -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<FileItem[]>([]);
|
||||
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
|
||||
const [status, setStatus] = useState<string | null>(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 = ({
|
||||
<FilePickerModal
|
||||
opened={showFilePickerModal}
|
||||
onClose={() => setShowFilePickerModal(false)}
|
||||
sharedFiles={sharedFiles || []}
|
||||
storedFiles={[]} // FileEditor doesn't have access to stored files, needs to be passed from parent
|
||||
onSelectFiles={handleLoadFromStorage}
|
||||
allowMultiple={true}
|
||||
/>
|
||||
|
||||
{status && (
|
||||
|
@ -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<PDFDocument | null>(null);
|
||||
// Multi-file state
|
||||
const [currentFileIndex, setCurrentFileIndex] = useState(0);
|
||||
const [processedFiles, setProcessedFiles] = useState<Map<string, PDFDocument>>(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<string>("");
|
||||
|
||||
// Page editor state
|
||||
const [selectedPages, setSelectedPages] = useState<string[]>([]);
|
||||
const [status, setStatus] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [csvInput, setCsvInput] = useState<string>("");
|
||||
const [selectionMode, setSelectionMode] = useState(false);
|
||||
const [filename, setFilename] = useState<string>("");
|
||||
|
||||
// Drag and drop state
|
||||
const [draggedPage, setDraggedPage] = useState<string | null>(null);
|
||||
const [dropTarget, setDropTarget] = useState<string | null>(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<string | null>(null);
|
||||
const [pagePositions, setPagePositions] = useState<Map<string, { x: number; y: number }>>(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<string, { x: number; y: number }>();
|
||||
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<string, { x: number; y: number }>();
|
||||
|
||||
// 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 (
|
||||
<Box pos="relative" h="100vh" style={{ overflow: 'auto' }}>
|
||||
<LoadingOverlay visible={loading || pdfLoading} />
|
||||
@ -610,7 +675,7 @@ const PageEditor = ({
|
||||
<FileUploadSelector
|
||||
title="Select a PDF to edit"
|
||||
subtitle="Choose a file from storage or upload a new PDF"
|
||||
sharedFiles={sharedFiles || []}
|
||||
sharedFiles={sharedFiles}
|
||||
onFileSelect={handleFileUpload}
|
||||
allowMultiple={false}
|
||||
accept={["application/pdf"]}
|
||||
@ -625,6 +690,37 @@ const PageEditor = ({
|
||||
<Box pos="relative" h="100vh" style={{ overflow: 'auto' }}>
|
||||
<LoadingOverlay visible={loading || pdfLoading} />
|
||||
|
||||
{/* File Switcher Tabs */}
|
||||
{activeFiles.length > 1 && (
|
||||
<Box p="md" pb={0} style={{ borderBottom: '1px solid var(--mantine-color-gray-3)' }}>
|
||||
<Group gap="xs">
|
||||
{activeFiles.map((file, index) => {
|
||||
const isActive = index === currentFileIndex;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={`${file.name}-${file.size}`}
|
||||
size="sm"
|
||||
variant={isActive ? "filled" : "light"}
|
||||
color={isActive ? "blue" : "gray"}
|
||||
onClick={() => setCurrentFileIndex(index)}
|
||||
styles={{
|
||||
root: {
|
||||
maxWidth: 200,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{file.name.replace('.pdf', '')}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Group>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box p="md" pt="xl">
|
||||
<Group mb="md">
|
||||
<TextInput
|
||||
@ -666,7 +762,7 @@ const PageEditor = ({
|
||||
)}
|
||||
|
||||
<DragDropGrid
|
||||
items={pdfDocument.pages}
|
||||
items={currentPdfDocument.pages}
|
||||
selectedItems={selectedPages}
|
||||
selectionMode={selectionMode}
|
||||
isAnimating={isAnimating}
|
||||
@ -685,7 +781,7 @@ const PageEditor = ({
|
||||
<PageThumbnail
|
||||
page={page}
|
||||
index={index}
|
||||
totalPages={pdfDocument.pages.length}
|
||||
totalPages={currentPdfDocument.pages.length}
|
||||
selectedPages={selectedPages}
|
||||
selectionMode={selectionMode}
|
||||
draggedPage={draggedPage}
|
||||
@ -707,7 +803,7 @@ const PageEditor = ({
|
||||
RotatePagesCommand={RotatePagesCommand}
|
||||
DeletePagesCommand={DeletePagesCommand}
|
||||
ToggleSplitCommand={ToggleSplitCommand}
|
||||
pdfDocument={pdfDocument}
|
||||
pdfDocument={currentPdfDocument}
|
||||
setPdfDocument={setPdfDocument}
|
||||
/>
|
||||
)}
|
||||
@ -753,7 +849,7 @@ const PageEditor = ({
|
||||
<Text fw={500}>{exportPreview.estimatedSize}</Text>
|
||||
</Group>
|
||||
|
||||
{pdfDocument && pdfDocument.pages.some(p => p.splitBefore) && (
|
||||
{currentPdfDocument && currentPdfDocument.pages.some(p => p.splitBefore) && (
|
||||
<Alert color="blue">
|
||||
This will create multiple PDF files based on split markers.
|
||||
</Alert>
|
||||
@ -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 = ({
|
||||
</Notification>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Notification
|
||||
color="red"
|
||||
mt="md"
|
||||
onClose={() => setError(null)}
|
||||
style={{ position: 'fixed', bottom: 70, right: 20, zIndex: 1000 }}
|
||||
>
|
||||
{error}
|
||||
</Notification>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageEditor;
|
||||
export default PageEditor;
|
@ -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 (
|
||||
<div
|
||||
ref={pageElementRef}
|
||||
data-page-id={page.id}
|
||||
className={`
|
||||
${styles.pageContainer}
|
||||
|
@ -22,6 +22,7 @@ interface FileManagerProps {
|
||||
allowMultiple?: boolean;
|
||||
setCurrentView?: (view: string) => 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 (
|
||||
<div style={{
|
||||
width: "100%",
|
||||
@ -379,6 +388,14 @@ const FileManager = ({
|
||||
>
|
||||
{t("fileManager.openInFileEditor", "Open in File Editor")}
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
onClick={handleOpenSelectedInPageEditor}
|
||||
disabled={selectedFiles.length === 0}
|
||||
>
|
||||
{t("fileManager.openInPageEditor", "Open in Page Editor")}
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
</Box>
|
||||
|
@ -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<string[]>([]);
|
||||
@ -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}
|
||||
>
|
||||
<Stack gap="md">
|
||||
{sharedFiles.length === 0 ? (
|
||||
{storedFiles.length === 0 ? (
|
||||
<Text c="dimmed" ta="center" py="xl">
|
||||
{t("fileUpload.noFilesInStorage", "No files available in storage. Upload some files first.")}
|
||||
</Text>
|
||||
@ -137,22 +144,27 @@ const FilePickerModal = ({
|
||||
{/* Selection controls */}
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c="dimmed">
|
||||
{sharedFiles.length} {t("fileUpload.filesAvailable", "files available")}
|
||||
{storedFiles.length} {t("fileUpload.filesAvailable", "files available")}
|
||||
{allowMultiple && selectedFileIds.length > 0 && (
|
||||
<> • {selectedFileIds.length} selected</>
|
||||
)}
|
||||
</Text>
|
||||
<Group gap="xs">
|
||||
<Button size="xs" variant="light" onClick={selectAll}>
|
||||
{t("pageEdit.selectAll", "Select All")}
|
||||
</Button>
|
||||
<Button size="xs" variant="light" onClick={selectNone}>
|
||||
{t("pageEdit.deselectAll", "Select None")}
|
||||
</Button>
|
||||
</Group>
|
||||
{allowMultiple && (
|
||||
<Group gap="xs">
|
||||
<Button size="xs" variant="light" onClick={selectAll}>
|
||||
{t("pageEdit.selectAll", "Select All")}
|
||||
</Button>
|
||||
<Button size="xs" variant="light" onClick={selectNone}>
|
||||
{t("pageEdit.deselectAll", "Select None")}
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{/* File grid */}
|
||||
<ScrollArea.Autosize mah={400}>
|
||||
<SimpleGrid cols={2} spacing="md">
|
||||
{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)}
|
||||
>
|
||||
<Group gap="sm" align="flex-start">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={() => toggleFileSelection(fileId)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
{allowMultiple ? (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={() => toggleFileSelection(fileId)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
type="radio"
|
||||
checked={isSelected}
|
||||
onChange={() => toggleFileSelection(fileId)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{ margin: '4px' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Thumbnail */}
|
||||
<Box
|
||||
|
@ -143,8 +143,9 @@ const FileUploadSelector = ({
|
||||
<FilePickerModal
|
||||
opened={showFilePickerModal}
|
||||
onClose={() => setShowFilePickerModal(false)}
|
||||
sharedFiles={sharedFiles}
|
||||
storedFiles={sharedFiles}
|
||||
onSelectFiles={handleStorageSelection}
|
||||
allowMultiple={allowMultiple}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -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]);
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ export function useToolParams(selectedToolKey: string, currentView: string) {
|
||||
});
|
||||
|
||||
setSearchParams(newParams, { replace: true });
|
||||
}, [selectedToolKey, currentView, setSearchParams, searchParams]);
|
||||
}, [selectedToolKey, currentView, setSearchParams]);
|
||||
|
||||
return {
|
||||
toolParams,
|
||||
|
@ -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 (
|
||||
<Group
|
||||
@ -288,6 +382,7 @@ export default function HomePage() {
|
||||
setFiles={setStoredFiles}
|
||||
setCurrentView={handleViewChange}
|
||||
onOpenFileEditor={handleOpenFileEditor}
|
||||
onOpenPageEditor={handleOpenPageEditor}
|
||||
onLoadFileToActive={addToActiveFiles}
|
||||
/>
|
||||
) : (currentView != "fileManager") && !activeFiles[0] ? (
|
||||
@ -309,8 +404,8 @@ export default function HomePage() {
|
||||
</Container>
|
||||
) : currentView === "fileEditor" ? (
|
||||
<FileEditor
|
||||
sharedFiles={activeFiles}
|
||||
setSharedFiles={setActiveFiles}
|
||||
activeFiles={activeFiles}
|
||||
setActiveFiles={setActiveFiles}
|
||||
preSelectedFiles={preSelectedFiles}
|
||||
onClearPreSelection={() => setPreSelectedFiles([])}
|
||||
onOpenPageEditor={(file) => {
|
||||
@ -339,18 +434,12 @@ export default function HomePage() {
|
||||
) : currentView === "pageEditor" ? (
|
||||
<>
|
||||
<PageEditor
|
||||
file={currentFileWithUrl}
|
||||
setFile={(fileObj) => {
|
||||
if (fileObj) {
|
||||
setCurrentActiveFile(fileObj.file);
|
||||
} else {
|
||||
setActiveFiles([]);
|
||||
}
|
||||
}}
|
||||
activeFiles={activeFiles}
|
||||
setActiveFiles={setActiveFiles}
|
||||
downloadUrl={downloadUrl}
|
||||
setDownloadUrl={setDownloadUrl}
|
||||
sharedFiles={storedFiles}
|
||||
onFunctionsReady={setPageEditorFunctions}
|
||||
sharedFiles={activeFiles}
|
||||
/>
|
||||
{activeFiles[0] && pageEditorFunctions && (
|
||||
<PageEditorControls
|
||||
@ -362,8 +451,8 @@ export default function HomePage() {
|
||||
onRotate={pageEditorFunctions.handleRotate}
|
||||
onDelete={pageEditorFunctions.handleDelete}
|
||||
onSplit={pageEditorFunctions.handleSplit}
|
||||
onExportSelected={() => 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}
|
||||
/>
|
||||
)}
|
||||
|
Loading…
x
Reference in New Issue
Block a user