diff --git a/frontend/src/components/pageEditor/PageEditor.tsx b/frontend/src/components/pageEditor/PageEditor.tsx index 1711e5627..39c6c706b 100644 --- a/frontend/src/components/pageEditor/PageEditor.tsx +++ b/frontend/src/components/pageEditor/PageEditor.tsx @@ -385,6 +385,19 @@ const PageEditor = ({ undoManagerRef.current.executeCommand(pageBreakCommand); }, [selectedPageNumbers, displayDocument]); + const handleInsertFiles = useCallback(async (files: File[], insertAfterPage: number) => { + if (!displayDocument || files.length === 0) return; + + try { + const targetPage = displayDocument.pages.find(p => p.pageNumber === insertAfterPage); + if (!targetPage) return; + + await actions.addFiles(files, { insertAfterPageId: targetPage.id }); + } catch (error) { + console.error('Failed to insert files:', error); + } + }, [displayDocument, actions]); + const handleSelectAll = useCallback(() => { if (!displayDocument) return; const allPageNumbers = Array.from({ length: displayDocument.pages.length }, (_, i) => i + 1); @@ -416,12 +429,7 @@ const PageEditor = ({ const getSourceFiles = useCallback((): Map | null => { const sourceFiles = new Map(); - // Check if we have multiple files by looking at active file IDs - if (activeFileIds.length <= 1) { - return null; // Use single-file export method - } - - // Collect all source files + // Always include original files activeFileIds.forEach(fileId => { const file = selectors.getFile(fileId); if (file) { @@ -429,6 +437,14 @@ const PageEditor = ({ } }); + // Use multi-file export if we have multiple original files + const hasInsertedFiles = false; + const hasMultipleOriginalFiles = activeFileIds.length > 1; + + if (!hasInsertedFiles && !hasMultipleOriginalFiles) { + return null; // Use single-file export method + } + return sourceFiles.size > 0 ? sourceFiles : null; }, [activeFileIds, selectors]); @@ -755,6 +771,7 @@ const PageEditor = ({ pdfDocument={displayDocument} setPdfDocument={setEditedDocument} splitPositions={splitPositions} + onInsertFiles={handleInsertFiles} /> )} /> diff --git a/frontend/src/components/pageEditor/PageThumbnail.tsx b/frontend/src/components/pageEditor/PageThumbnail.tsx index bc8d9c467..e988b1557 100644 --- a/frontend/src/components/pageEditor/PageThumbnail.tsx +++ b/frontend/src/components/pageEditor/PageThumbnail.tsx @@ -6,9 +6,11 @@ import RotateLeftIcon from '@mui/icons-material/RotateLeft'; import RotateRightIcon from '@mui/icons-material/RotateRight'; import DeleteIcon from '@mui/icons-material/Delete'; import ContentCutIcon from '@mui/icons-material/ContentCut'; +import AddIcon from '@mui/icons-material/Add'; import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'; import { PDFPage, PDFDocument } from '../../types/pageEditor'; import { useThumbnailGeneration } from '../../hooks/useThumbnailGeneration'; +import { useFilesModalContext } from '../../contexts/FilesModalContext'; import styles from './PageEditor.module.css'; @@ -35,6 +37,7 @@ interface PageThumbnailProps { pdfDocument: PDFDocument; setPdfDocument: (doc: PDFDocument) => void; splitPositions: Set; + onInsertFiles?: (files: File[], insertAfterPage: number) => void; } const PageThumbnail: React.FC = ({ @@ -60,6 +63,7 @@ const PageThumbnail: React.FC = ({ pdfDocument, setPdfDocument, splitPositions, + onInsertFiles, }: PageThumbnailProps) => { const [isDragging, setIsDragging] = useState(false); const [isMouseDown, setIsMouseDown] = useState(false); @@ -67,6 +71,7 @@ const PageThumbnail: React.FC = ({ const dragElementRef = useRef(null); const [thumbnailUrl, setThumbnailUrl] = useState(page.thumbnail); const { getThumbnailFromCache, requestThumbnail } = useThumbnailGeneration(); + const { openFilesModal } = useFilesModalContext(); // Calculate document aspect ratio from first non-blank page const getDocumentAspectRatio = useCallback(() => { @@ -224,6 +229,27 @@ const PageThumbnail: React.FC = ({ onSetStatus(`Split marker ${action} after position ${index + 1}`); }, [index, splitPositions, onExecuteCommand, onSetStatus, createSplitCommand]); + const handleInsertFileAfter = useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + + if (onInsertFiles) { + // Open file manager modal with custom handler for page insertion + openFilesModal({ + insertAfterPage: page.pageNumber, + customHandler: (files: File[], insertAfterPage?: number) => { + if (insertAfterPage !== undefined) { + onInsertFiles(files, insertAfterPage); + } + } + }); + onSetStatus(`Select files to insert after page ${page.pageNumber}`); + } else { + // Fallback to normal file handling + openFilesModal({ insertAfterPage: page.pageNumber }); + onSetStatus(`Select files to insert after page ${page.pageNumber}`); + } + }, [openFilesModal, page.pageNumber, onSetStatus, onInsertFiles]); + // Handle click vs drag differentiation const handleMouseDown = useCallback((e: React.MouseEvent) => { setIsMouseDown(true); @@ -509,6 +535,17 @@ const PageThumbnail: React.FC = ({ )} + + + + + + diff --git a/frontend/src/components/pageEditor/commands/pageCommands.ts b/frontend/src/components/pageEditor/commands/pageCommands.ts index c9364dc27..1b7cb0932 100644 --- a/frontend/src/components/pageEditor/commands/pageCommands.ts +++ b/frontend/src/components/pageEditor/commands/pageCommands.ts @@ -568,6 +568,268 @@ export class BulkPageBreakCommand extends DOMCommand { } } +export class InsertFilesCommand extends DOMCommand { + private insertedPages: PDFPage[] = []; + private originalDocument: PDFDocument | null = null; + private fileDataMap = new Map(); // Store file data for thumbnail generation + private originalProcessedFile: any = null; // Store original ProcessedFile for undo + private insertedFileMap = new Map(); // Store inserted files for export + + constructor( + private files: File[], + private insertAfterPageNumber: number, + private getCurrentDocument: () => PDFDocument | null, + private setDocument: (doc: PDFDocument) => void, + private setSelectedPages: (pages: number[]) => void, + private getSelectedPages: () => number[], + private updateFileContext?: (updatedDocument: PDFDocument, insertedFiles?: Map) => void + ) { + super(); + } + + async execute(): Promise { + const currentDoc = this.getCurrentDocument(); + if (!currentDoc || this.files.length === 0) return; + + // Store original state for undo + this.originalDocument = { + ...currentDoc, + pages: currentDoc.pages.map(page => ({...page})) + }; + + try { + // Process each file to extract pages and wait for all to complete + const allNewPages: PDFPage[] = []; + + // Process all files and wait for their completion + const baseTimestamp = Date.now(); + const extractionPromises = this.files.map(async (file, index) => { + const fileId = `inserted-${file.name}-${baseTimestamp + index}`; + // Store inserted file for export + this.insertedFileMap.set(fileId, file); + // Use base timestamp + index to ensure unique but predictable file IDs + return await this.extractPagesFromFile(file, baseTimestamp + index); + }); + + const extractedPageArrays = await Promise.all(extractionPromises); + + // Flatten all extracted pages + for (const pages of extractedPageArrays) { + allNewPages.push(...pages); + } + + if (allNewPages.length === 0) return; + + // Find insertion point (after the specified page) + const insertIndex = this.insertAfterPageNumber; // Insert after page N means insert at index N + + // Create new pages array with inserted pages + const newPages: PDFPage[] = []; + let pageNumberCounter = 1; + + // Add pages before insertion point + for (let i = 0; i < insertIndex && i < currentDoc.pages.length; i++) { + const page = { ...currentDoc.pages[i], pageNumber: pageNumberCounter++ }; + newPages.push(page); + } + + // Add inserted pages + for (const newPage of allNewPages) { + const insertedPage: PDFPage = { + ...newPage, + pageNumber: pageNumberCounter++, + selected: false, + splitAfter: false + }; + newPages.push(insertedPage); + this.insertedPages.push(insertedPage); + } + + // Add remaining pages after insertion point + for (let i = insertIndex; i < currentDoc.pages.length; i++) { + const page = { ...currentDoc.pages[i], pageNumber: pageNumberCounter++ }; + newPages.push(page); + } + + // Update document + const updatedDocument: PDFDocument = { + ...currentDoc, + pages: newPages, + totalPages: newPages.length, + }; + + this.setDocument(updatedDocument); + + // Update FileContext with the new document structure and inserted files + if (this.updateFileContext) { + this.updateFileContext(updatedDocument, this.insertedFileMap); + } + + // Generate thumbnails for inserted pages (all files should be read by now) + this.generateThumbnailsForInsertedPages(updatedDocument); + + // Maintain existing selection by mapping original selected pages to their new positions + const originalSelection = this.getSelectedPages(); + const updatedSelection: number[] = []; + + originalSelection.forEach(originalPageNum => { + if (originalPageNum <= this.insertAfterPageNumber) { + // Pages before insertion point keep same number + updatedSelection.push(originalPageNum); + } else { + // Pages after insertion point are shifted by number of inserted pages + updatedSelection.push(originalPageNum + allNewPages.length); + } + }); + + this.setSelectedPages(updatedSelection); + + } catch (error) { + console.error('Failed to insert files:', error); + // Revert to original state if error occurs + if (this.originalDocument) { + this.setDocument(this.originalDocument); + } + } + } + + private async generateThumbnailsForInsertedPages(updatedDocument: PDFDocument): Promise { + try { + const { thumbnailGenerationService } = await import('../../../services/thumbnailGenerationService'); + + // Group pages by file ID to generate thumbnails efficiently + const pagesByFileId = new Map(); + + for (const page of this.insertedPages) { + const fileId = page.id.substring(0, page.id.lastIndexOf('-page-')); + if (!pagesByFileId.has(fileId)) { + pagesByFileId.set(fileId, []); + } + pagesByFileId.get(fileId)!.push(page); + } + + // Generate thumbnails for each file + for (const [fileId, pages] of pagesByFileId) { + const arrayBuffer = this.fileDataMap.get(fileId); + + console.log('Generating thumbnails for file:', fileId); + console.log('Pages:', pages.length); + console.log('ArrayBuffer size:', arrayBuffer?.byteLength || 'undefined'); + + if (arrayBuffer && arrayBuffer.byteLength > 0) { + // Extract page numbers for all pages from this file + const pageNumbers = pages.map(page => { + const pageNumMatch = page.id.match(/-page-(\d+)$/); + return pageNumMatch ? parseInt(pageNumMatch[1]) : 1; + }); + + console.log('Generating thumbnails for page numbers:', pageNumbers); + + // Generate thumbnails for all pages from this file at once + const results = await thumbnailGenerationService.generateThumbnails( + fileId, + arrayBuffer, + pageNumbers, + { scale: 0.2, quality: 0.8 } + ); + + console.log('Thumbnail generation results:', results.length, 'thumbnails generated'); + + // Update pages with generated thumbnails + for (let i = 0; i < results.length && i < pages.length; i++) { + const result = results[i]; + const page = pages[i]; + + if (result.success) { + const pageIndex = updatedDocument.pages.findIndex(p => p.id === page.id); + if (pageIndex >= 0) { + updatedDocument.pages[pageIndex].thumbnail = result.thumbnail; + console.log('Updated thumbnail for page:', page.id); + } + } + } + + // Trigger re-render by updating the document + this.setDocument({ ...updatedDocument }); + } else { + console.error('No valid ArrayBuffer found for file ID:', fileId); + } + } + } catch (error) { + console.error('Failed to generate thumbnails for inserted pages:', error); + } + } + + private async extractPagesFromFile(file: File, baseTimestamp: number): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = async (event) => { + try { + const arrayBuffer = event.target?.result as ArrayBuffer; + console.log('File reader onload - arrayBuffer size:', arrayBuffer?.byteLength || 'undefined'); + + if (!arrayBuffer) { + reject(new Error('Failed to read file')); + return; + } + + // Clone the ArrayBuffer before passing to PDF.js (it might consume it) + const clonedArrayBuffer = arrayBuffer.slice(0); + + // Use PDF.js via the worker manager to extract pages + const { pdfWorkerManager } = await import('../../../services/pdfWorkerManager'); + const pdf = await pdfWorkerManager.createDocument(clonedArrayBuffer); + + const pageCount = pdf.numPages; + const pages: PDFPage[] = []; + const fileId = `inserted-${file.name}-${baseTimestamp}`; + + console.log('Original ArrayBuffer size:', arrayBuffer.byteLength); + console.log('Storing ArrayBuffer for fileId:', fileId, 'size:', arrayBuffer.byteLength); + + // Store the original ArrayBuffer for thumbnail generation + this.fileDataMap.set(fileId, arrayBuffer); + + console.log('After storing - fileDataMap size:', this.fileDataMap.size); + console.log('Stored value size:', this.fileDataMap.get(fileId)?.byteLength || 'undefined'); + + for (let i = 1; i <= pageCount; i++) { + const pageId = `${fileId}-page-${i}`; + pages.push({ + id: pageId, + pageNumber: i, // Will be renumbered in execute() + originalPageNumber: i, + thumbnail: null, // Will be generated after insertion + rotation: 0, + selected: false, + splitAfter: false, + isBlankPage: false + }); + } + + // Clean up PDF document + pdfWorkerManager.destroyDocument(pdf); + + resolve(pages); + } catch (error) { + reject(error); + } + }; + reader.onerror = () => reject(new Error('Failed to read file')); + reader.readAsArrayBuffer(file); + }); + } + + undo(): void { + if (!this.originalDocument) return; + this.setDocument(this.originalDocument); + } + + get description(): string { + return `Insert ${this.files.length} file(s) after page ${this.insertAfterPageNumber}`; + } +} + // Simple undo manager for DOM commands export class UndoManager { private undoStack: DOMCommand[] = []; @@ -585,6 +847,13 @@ export class UndoManager { this.onStateChange?.(); } + // For async commands that need to be executed manually + addToUndoStack(command: DOMCommand): void { + this.undoStack.push(command); + this.redoStack = []; + this.onStateChange?.(); + } + undo(): boolean { const command = this.undoStack.pop(); if (command) { diff --git a/frontend/src/components/pageEditor/hooks/usePageDocument.ts b/frontend/src/components/pageEditor/hooks/usePageDocument.ts index 837ac4720..5a9d13f9f 100644 --- a/frontend/src/components/pageEditor/hooks/usePageDocument.ts +++ b/frontend/src/components/pageEditor/hooks/usePageDocument.ts @@ -49,33 +49,41 @@ export function usePageDocument(): PageDocumentHook { .map(id => (selectors.getFileRecord(id)?.name ?? 'file').replace(/\.pdf$/i, '')) .join(' + '); - // Debug logging for merged document creation - console.log(`🎬 PageEditor: Building merged document for ${name} with ${activeFileIds.length} files`); + // Build page insertion map from files with insertion positions + const insertionMap = new Map(); // insertAfterPageId -> fileIds + const originalFileIds: string[] = []; - // Collect pages from ALL active files, not just the primary file + activeFileIds.forEach(fileId => { + const record = selectors.getFileRecord(fileId); + if (record?.insertAfterPageId !== undefined) { + if (!insertionMap.has(record.insertAfterPageId)) { + insertionMap.set(record.insertAfterPageId, []); + } + insertionMap.get(record.insertAfterPageId)!.push(fileId); + } else { + originalFileIds.push(fileId); + } + }); + + // Build pages by interleaving original pages with insertions let pages: PDFPage[] = []; let totalPageCount = 0; - activeFileIds.forEach((fileId, fileIndex) => { + // Helper function to create pages from a file + const createPagesFromFile = (fileId: string, startPageNumber: number): PDFPage[] => { const fileRecord = selectors.getFileRecord(fileId); if (!fileRecord) { - console.warn(`🎬 PageEditor: No record found for file ${fileId}`); - return; + return []; } const processedFile = fileRecord.processedFile; - console.log(`🎬 PageEditor: Processing file ${fileIndex + 1}/${activeFileIds.length} (${fileRecord.name})`); - console.log(`🎬 ProcessedFile exists:`, !!processedFile); - console.log(`🎬 ProcessedFile pages:`, processedFile?.pages?.length || 0); - console.log(`🎬 ProcessedFile totalPages:`, processedFile?.totalPages || 'unknown'); - let filePages: PDFPage[] = []; if (processedFile?.pages && processedFile.pages.length > 0) { // Use fully processed pages with thumbnails filePages = processedFile.pages.map((page, pageIndex) => ({ id: `${fileId}-${page.pageNumber}`, - pageNumber: totalPageCount + pageIndex + 1, + pageNumber: startPageNumber + pageIndex, thumbnail: page.thumbnail || null, rotation: page.rotation || 0, selected: false, @@ -85,30 +93,62 @@ export function usePageDocument(): PageDocumentHook { })); } else if (processedFile?.totalPages) { // Fallback: create pages without thumbnails but with correct count - console.log(`🎬 PageEditor: Creating placeholder pages for ${fileRecord.name} (${processedFile.totalPages} pages)`); filePages = Array.from({ length: processedFile.totalPages }, (_, pageIndex) => ({ id: `${fileId}-${pageIndex + 1}`, - pageNumber: totalPageCount + pageIndex + 1, + pageNumber: startPageNumber + pageIndex, originalPageNumber: pageIndex + 1, originalFileId: fileId, rotation: 0, - thumbnail: null, // Will be generated later + thumbnail: null, selected: false, splitAfter: false, })); } - pages = pages.concat(filePages); - totalPageCount += filePages.length; + return filePages; + }; + + // Collect all pages from original files (without renumbering yet) + const originalFilePages: PDFPage[] = []; + originalFileIds.forEach(fileId => { + const filePages = createPagesFromFile(fileId, 1); // Temporary numbering + originalFilePages.push(...filePages); }); + + // Start with all original pages numbered sequentially + pages = originalFilePages.map((page, index) => ({ + ...page, + pageNumber: index + 1 + })); + + // Process each insertion by finding the page ID and inserting after it + for (const [insertAfterPageId, fileIds] of insertionMap.entries()) { + const targetPageIndex = pages.findIndex(p => p.id === insertAfterPageId); + + if (targetPageIndex === -1) continue; + + // Collect all pages to insert + const allNewPages: PDFPage[] = []; + fileIds.forEach(fileId => { + const insertedPages = createPagesFromFile(fileId, 1); + allNewPages.push(...insertedPages); + }); + + // Insert all new pages after the target page + pages.splice(targetPageIndex + 1, 0, ...allNewPages); + + // Renumber all pages after insertion + pages.forEach((page, index) => { + page.pageNumber = index + 1; + }); + } + + totalPageCount = pages.length; if (pages.length === 0) { - console.warn('🎬 PageEditor: No pages found in any files'); return null; } - console.log(`🎬 PageEditor: Created merged document with ${pages.length} total pages`); - const mergedDoc: PDFDocument = { id: activeFileIds.join('-'), name, diff --git a/frontend/src/components/tools/shared/FileStatusIndicator.tsx b/frontend/src/components/tools/shared/FileStatusIndicator.tsx index 20e7c4d7a..f54f66219 100644 --- a/frontend/src/components/tools/shared/FileStatusIndicator.tsx +++ b/frontend/src/components/tools/shared/FileStatusIndicator.tsx @@ -25,7 +25,7 @@ const FileStatusIndicator = ({ {t("files.noFiles", "No files uploaded. ")}{" "} openFilesModal()} style={{ cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: '4px' }} > @@ -42,7 +42,7 @@ const FileStatusIndicator = ({ {t("files.selectFromWorkbench", "Select files from the workbench or ") + " "} openFilesModal()} style={{ cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: '4px' }} > diff --git a/frontend/src/contexts/FileContext.tsx b/frontend/src/contexts/FileContext.tsx index 44adf6b28..285291539 100644 --- a/frontend/src/contexts/FileContext.tsx +++ b/frontend/src/contexts/FileContext.tsx @@ -73,8 +73,8 @@ function FileContextInner({ }, []); // File operations using unified addFiles helper with persistence - const addRawFiles = useCallback(async (files: File[]): Promise => { - const addedFilesWithIds = await addFiles('raw', { files }, stateRef, filesRef, dispatch, lifecycleManager); + const addRawFiles = useCallback(async (files: File[], options?: { insertAfterPageId?: string }): Promise => { + const addedFilesWithIds = await addFiles('raw', { files, ...options }, stateRef, filesRef, dispatch, lifecycleManager); // Persist to IndexedDB if enabled if (indexedDB && enablePersistence && addedFilesWithIds.length > 0) { diff --git a/frontend/src/contexts/FilesModalContext.tsx b/frontend/src/contexts/FilesModalContext.tsx index d7183eabf..2b210ce89 100644 --- a/frontend/src/contexts/FilesModalContext.tsx +++ b/frontend/src/contexts/FilesModalContext.tsx @@ -4,7 +4,7 @@ import { FileMetadata } from '../types/file'; interface FilesModalContextType { isFilesModalOpen: boolean; - openFilesModal: () => void; + openFilesModal: (options?: { insertAfterPage?: number; customHandler?: (files: File[], insertAfterPage?: number) => void }) => void; closeFilesModal: () => void; onFileSelect: (file: File) => void; onFilesSelect: (files: File[]) => void; @@ -19,30 +19,55 @@ export const FilesModalProvider: React.FC<{ children: React.ReactNode }> = ({ ch const { addToActiveFiles, addMultipleFiles, addStoredFiles } = useFileHandler(); const [isFilesModalOpen, setIsFilesModalOpen] = useState(false); const [onModalClose, setOnModalClose] = useState<(() => void) | undefined>(); + const [insertAfterPage, setInsertAfterPage] = useState(); + const [customHandler, setCustomHandler] = useState<((files: File[], insertAfterPage?: number) => void) | undefined>(); - const openFilesModal = useCallback(() => { + const openFilesModal = useCallback((options?: { insertAfterPage?: number; customHandler?: (files: File[], insertAfterPage?: number) => void }) => { + setInsertAfterPage(options?.insertAfterPage); + setCustomHandler(() => options?.customHandler); setIsFilesModalOpen(true); }, []); const closeFilesModal = useCallback(() => { setIsFilesModalOpen(false); + setInsertAfterPage(undefined); // Clear insertion position + setCustomHandler(undefined); // Clear custom handler onModalClose?.(); }, [onModalClose]); const handleFileSelect = useCallback((file: File) => { - addToActiveFiles(file); + if (customHandler) { + // Use custom handler for special cases (like page insertion) + customHandler([file], insertAfterPage); + } else { + // Use normal file handling + addToActiveFiles(file); + } closeFilesModal(); - }, [addToActiveFiles, closeFilesModal]); + }, [addToActiveFiles, closeFilesModal, insertAfterPage, customHandler]); const handleFilesSelect = useCallback((files: File[]) => { - addMultipleFiles(files); + if (customHandler) { + // Use custom handler for special cases (like page insertion) + customHandler(files, insertAfterPage); + } else { + // Use normal file handling + addMultipleFiles(files); + } closeFilesModal(); - }, [addMultipleFiles, closeFilesModal]); + }, [addMultipleFiles, closeFilesModal, insertAfterPage, customHandler]); const handleStoredFilesSelect = useCallback((filesWithMetadata: Array<{ file: File; originalId: string; metadata: FileMetadata }>) => { - addStoredFiles(filesWithMetadata); + if (customHandler) { + // Use custom handler for special cases (like page insertion) + const files = filesWithMetadata.map(item => item.file); + customHandler(files, insertAfterPage); + } else { + // Use normal file handling + addStoredFiles(filesWithMetadata); + } closeFilesModal(); - }, [addStoredFiles, closeFilesModal]); + }, [addStoredFiles, closeFilesModal, insertAfterPage, customHandler]); const setModalCloseCallback = useCallback((callback: () => void) => { setOnModalClose(() => callback); diff --git a/frontend/src/contexts/file/fileActions.ts b/frontend/src/contexts/file/fileActions.ts index 948b4f011..c8efac3db 100644 --- a/frontend/src/contexts/file/fileActions.ts +++ b/frontend/src/contexts/file/fileActions.ts @@ -84,6 +84,9 @@ interface AddFileOptions { // For 'stored' files filesWithMetadata?: Array<{ file: File; originalId: FileId; metadata: FileMetadata }>; + + // Insertion position + insertAfterPageId?: string; } /** @@ -164,6 +167,11 @@ export async function addFiles( } } + // Store insertion position if provided + if (options.insertAfterPageId !== undefined) { + record.insertAfterPageId = options.insertAfterPageId; + } + // Create initial processedFile metadata with page count if (pageCount > 0) { record.processedFile = createProcessedFile(pageCount, thumbnail); diff --git a/frontend/src/types/fileContext.ts b/frontend/src/types/fileContext.ts index 0425031c5..653909568 100644 --- a/frontend/src/types/fileContext.ts +++ b/frontend/src/types/fileContext.ts @@ -55,6 +55,7 @@ export interface FileRecord { blobUrl?: string; createdAt?: number; processedFile?: ProcessedFileMetadata; + insertAfterPageId?: string; // Page ID after which this file should be inserted isPinned?: boolean; // Note: File object stored in provider ref, not in state } @@ -216,7 +217,7 @@ export type FileContextAction = export interface FileContextActions { // File management - lightweight actions only - addFiles: (files: File[]) => Promise; + addFiles: (files: File[], options?: { insertAfterPageId?: string }) => Promise; addProcessedFiles: (filesWithThumbnails: Array<{ file: File; thumbnail?: string; pageCount?: number }>) => Promise; addStoredFiles: (filesWithMetadata: Array<{ file: File; originalId: FileId; metadata: FileMetadata }>) => Promise; removeFiles: (fileIds: FileId[], deleteFromStorage?: boolean) => Promise;