mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 02:54:06 +02:00
Added Filewithid type Updated code where file was being used to use filewithid Updated places we identified files by name or composite keys to use UUID Updated places we should have been using quickkey Updated pageeditor issue where we parsed pagenumber from pageid instead of using pagenumber directly --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: James Brunton <[email protected]>
178 lines
6.1 KiB
TypeScript
178 lines
6.1 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { useFileState } from '../../../contexts/FileContext';
|
|
import { PDFDocument, PDFPage } from '../../../types/pageEditor';
|
|
import { FileId } from '../../../types/file';
|
|
|
|
export interface PageDocumentHook {
|
|
document: PDFDocument | null;
|
|
isVeryLargeDocument: boolean;
|
|
isLoading: boolean;
|
|
}
|
|
|
|
/**
|
|
* Hook for managing PDF document state and metadata in PageEditor
|
|
* Handles document merging, large document detection, and loading states
|
|
*/
|
|
export function usePageDocument(): PageDocumentHook {
|
|
const { state, selectors } = useFileState();
|
|
|
|
// Prefer IDs + selectors to avoid array identity churn
|
|
const activeFileIds = state.files.ids;
|
|
const primaryFileId = activeFileIds[0] ?? null;
|
|
|
|
// Stable signature for effects (prevents loops)
|
|
const filesSignature = selectors.getFilesSignature();
|
|
|
|
// UI state
|
|
const globalProcessing = state.ui.isProcessing;
|
|
|
|
// Get primary file record outside useMemo to track processedFile changes
|
|
const primaryStirlingFileStub = primaryFileId ? selectors.getStirlingFileStub(primaryFileId) : null;
|
|
const processedFilePages = primaryStirlingFileStub?.processedFile?.pages;
|
|
const processedFileTotalPages = primaryStirlingFileStub?.processedFile?.totalPages;
|
|
|
|
// Compute merged document with stable signature (prevents infinite loops)
|
|
const mergedPdfDocument = useMemo((): PDFDocument | null => {
|
|
if (activeFileIds.length === 0) return null;
|
|
|
|
const primaryFile = primaryFileId ? selectors.getFile(primaryFileId) : null;
|
|
|
|
// If we have file IDs but no file record, something is wrong - return null to show loading
|
|
if (!primaryStirlingFileStub) {
|
|
console.log('🎬 PageEditor: No primary file record found, showing loading');
|
|
return null;
|
|
}
|
|
|
|
const name =
|
|
activeFileIds.length === 1
|
|
? (primaryStirlingFileStub.name ?? 'document.pdf')
|
|
: activeFileIds
|
|
.map(id => (selectors.getStirlingFileStub(id)?.name ?? 'file').replace(/\.pdf$/i, ''))
|
|
.join(' + ');
|
|
|
|
// Build page insertion map from files with insertion positions
|
|
const insertionMap = new Map<string, FileId[]>(); // insertAfterPageId -> fileIds
|
|
const originalFileIds: FileId[] = [];
|
|
|
|
activeFileIds.forEach(fileId => {
|
|
const record = selectors.getStirlingFileStub(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;
|
|
|
|
// Helper function to create pages from a file
|
|
const createPagesFromFile = (fileId: FileId, startPageNumber: number): PDFPage[] => {
|
|
const stirlingFileStub = selectors.getStirlingFileStub(fileId);
|
|
if (!stirlingFileStub) {
|
|
return [];
|
|
}
|
|
|
|
const processedFile = stirlingFileStub.processedFile;
|
|
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: startPageNumber + pageIndex,
|
|
thumbnail: page.thumbnail || null,
|
|
rotation: page.rotation || 0,
|
|
selected: false,
|
|
splitAfter: page.splitAfter || false,
|
|
originalPageNumber: page.originalPageNumber || page.pageNumber || pageIndex + 1,
|
|
originalFileId: fileId,
|
|
}));
|
|
} else if (processedFile?.totalPages) {
|
|
// Fallback: create pages without thumbnails but with correct count
|
|
filePages = Array.from({ length: processedFile.totalPages }, (_, pageIndex) => ({
|
|
id: `${fileId}-${pageIndex + 1}`,
|
|
pageNumber: startPageNumber + pageIndex,
|
|
originalPageNumber: pageIndex + 1,
|
|
originalFileId: fileId,
|
|
rotation: 0,
|
|
thumbnail: null,
|
|
selected: false,
|
|
splitAfter: false,
|
|
}));
|
|
}
|
|
|
|
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) {
|
|
return null;
|
|
}
|
|
|
|
const mergedDoc: PDFDocument = {
|
|
id: activeFileIds.join('-'),
|
|
name,
|
|
file: primaryFile!,
|
|
pages,
|
|
totalPages: pages.length,
|
|
};
|
|
|
|
return mergedDoc;
|
|
}, [activeFileIds, primaryFileId, primaryStirlingFileStub, processedFilePages, processedFileTotalPages, selectors, filesSignature]);
|
|
|
|
// Large document detection for smart loading
|
|
const isVeryLargeDocument = useMemo(() => {
|
|
return mergedPdfDocument ? mergedPdfDocument.totalPages > 2000 : false;
|
|
}, [mergedPdfDocument?.totalPages]);
|
|
|
|
// Loading state
|
|
const isLoading = globalProcessing && !mergedPdfDocument;
|
|
|
|
return {
|
|
document: mergedPdfDocument,
|
|
isVeryLargeDocument,
|
|
isLoading
|
|
};
|
|
}
|