mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Fix page editor multi-file support with dynamic worker management
- Update PageEditor to merge pages from all active files, not just primary file - Implement dynamic PDF worker allocation based on number of files (up to 12 workers) - Add sequential file processing to prevent worker contention - Include cache polling in PageThumbnail for real-time UI updates - Increase PDFWorkerManager limit to 15 workers for multi-file scenarios Resolves issues where only the first file's pages were shown in page editor and thumbnails failed to generate due to worker pool exhaustion. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
949ffa01ad
commit
c8714a279e
@ -123,95 +123,116 @@ const PageEditor = ({
|
|||||||
.map(id => (selectors.getFileRecord(id)?.name ?? 'file').replace(/\.pdf$/i, ''))
|
.map(id => (selectors.getFileRecord(id)?.name ?? 'file').replace(/\.pdf$/i, ''))
|
||||||
.join(' + ');
|
.join(' + ');
|
||||||
|
|
||||||
// Get pages from processed file data
|
// Debug logging for merged document creation
|
||||||
const processedFile = primaryFileRecord.processedFile;
|
console.log(`🎬 PageEditor: Building merged document for ${name} with ${activeFileIds.length} files`);
|
||||||
|
|
||||||
// Debug logging for processed file data
|
// Collect pages from ALL active files, not just the primary file
|
||||||
console.log(`🎬 PageEditor: Building document for ${name}`);
|
|
||||||
console.log(`🎬 ProcessedFile exists:`, !!processedFile);
|
|
||||||
console.log(`🎬 ProcessedFile pages:`, processedFile?.pages?.length || 0);
|
|
||||||
console.log(`🎬 ProcessedFile totalPages:`, processedFile?.totalPages || 'unknown');
|
|
||||||
if (processedFile?.pages) {
|
|
||||||
console.log(`🎬 Pages structure:`, processedFile.pages.map(p => ({ pageNumber: p.pageNumber || 'unknown', hasThumbnail: !!p.thumbnail })));
|
|
||||||
}
|
|
||||||
console.log(`🎬 Will use ${(processedFile?.pages?.length || 0) > 0 ? 'PROCESSED' : 'FALLBACK'} pages`);
|
|
||||||
|
|
||||||
// Convert processed pages to PageEditor format or create placeholders from metadata
|
|
||||||
let pages: PDFPage[] = [];
|
let pages: PDFPage[] = [];
|
||||||
|
let totalPageCount = 0;
|
||||||
|
|
||||||
if (processedFile?.pages && processedFile.pages.length > 0) {
|
activeFileIds.forEach((fileId, fileIndex) => {
|
||||||
// Use fully processed pages with thumbnails
|
const fileRecord = selectors.getFileRecord(fileId);
|
||||||
pages = processedFile.pages.map((page, index) => {
|
if (!fileRecord) {
|
||||||
const pageId = `${primaryFileId}-page-${index + 1}`;
|
console.warn(`🎬 PageEditor: No record found for file ${fileId}`);
|
||||||
// Try multiple sources for thumbnails in order of preference:
|
return;
|
||||||
// 1. Processed data thumbnail
|
}
|
||||||
// 2. Cached thumbnail from previous generation
|
|
||||||
// 3. For page 1: FileRecord's thumbnailUrl (from FileProcessingService)
|
const processedFile = fileRecord.processedFile;
|
||||||
let thumbnail = page.thumbnail || null;
|
console.log(`🎬 PageEditor: Processing file ${fileIndex + 1}/${activeFileIds.length} (${fileRecord.name})`);
|
||||||
const cachedThumbnail = getThumbnailFromCache(pageId);
|
console.log(`🎬 ProcessedFile exists:`, !!processedFile);
|
||||||
if (!thumbnail && cachedThumbnail) {
|
console.log(`🎬 ProcessedFile pages:`, processedFile?.pages?.length || 0);
|
||||||
thumbnail = cachedThumbnail;
|
console.log(`🎬 ProcessedFile totalPages:`, processedFile?.totalPages || 'unknown');
|
||||||
console.log(`📸 PageEditor: Using cached thumbnail for page ${index + 1} (${pageId})`);
|
|
||||||
}
|
let filePages: PDFPage[] = [];
|
||||||
if (!thumbnail && index === 0) {
|
|
||||||
// For page 1, use the thumbnail from FileProcessingService
|
if (processedFile?.pages && processedFile.pages.length > 0) {
|
||||||
thumbnail = primaryFileRecord.thumbnailUrl || null;
|
// Use fully processed pages with thumbnails
|
||||||
if (thumbnail) {
|
filePages = processedFile.pages.map((page, pageIndex) => {
|
||||||
addThumbnailToCache(pageId, thumbnail);
|
const pageId = `${fileId}-page-${pageIndex + 1}`;
|
||||||
console.log(`📸 PageEditor: Using FileProcessingService thumbnail for page 1 (${pageId})`);
|
const globalPageNumber = totalPageCount + pageIndex + 1;
|
||||||
|
|
||||||
|
// Try multiple sources for thumbnails in order of preference:
|
||||||
|
// 1. Processed data thumbnail
|
||||||
|
// 2. Cached thumbnail from previous generation
|
||||||
|
// 3. For page 1: FileRecord's thumbnailUrl (from FileProcessingService)
|
||||||
|
let thumbnail = page.thumbnail || null;
|
||||||
|
const cachedThumbnail = getThumbnailFromCache(pageId);
|
||||||
|
if (!thumbnail && cachedThumbnail) {
|
||||||
|
thumbnail = cachedThumbnail;
|
||||||
|
console.log(`📸 PageEditor: Using cached thumbnail for ${fileRecord.name} page ${pageIndex + 1} (${pageId})`);
|
||||||
}
|
}
|
||||||
}
|
if (!thumbnail && pageIndex === 0) {
|
||||||
|
// For page 1 of each file, use the thumbnail from FileProcessingService
|
||||||
return {
|
thumbnail = fileRecord.thumbnailUrl || null;
|
||||||
id: pageId,
|
if (thumbnail) {
|
||||||
pageNumber: index + 1,
|
addThumbnailToCache(pageId, thumbnail);
|
||||||
thumbnail,
|
console.log(`📸 PageEditor: Using FileProcessingService thumbnail for ${fileRecord.name} page 1 (${pageId})`);
|
||||||
rotation: page.rotation || 0,
|
}
|
||||||
selected: false,
|
|
||||||
splitBefore: page.splitBefore || false,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else if (processedFile?.totalPages && processedFile.totalPages > 0) {
|
|
||||||
// Create placeholder pages from metadata while thumbnails are being generated
|
|
||||||
console.log(`🎬 PageEditor: Creating ${processedFile.totalPages} placeholder pages from metadata`);
|
|
||||||
pages = Array.from({ length: processedFile.totalPages }, (_, index) => {
|
|
||||||
const pageId = `${primaryFileId}-page-${index + 1}`;
|
|
||||||
|
|
||||||
// Check for existing cached thumbnail
|
|
||||||
let thumbnail = getThumbnailFromCache(pageId) || null;
|
|
||||||
|
|
||||||
// For page 1, try to use the FileRecord thumbnail
|
|
||||||
if (!thumbnail && index === 0) {
|
|
||||||
thumbnail = primaryFileRecord.thumbnailUrl || null;
|
|
||||||
if (thumbnail) {
|
|
||||||
addThumbnailToCache(pageId, thumbnail);
|
|
||||||
console.log(`📸 PageEditor: Using FileProcessingService thumbnail for placeholder page 1 (${pageId})`);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: pageId,
|
||||||
|
pageNumber: globalPageNumber,
|
||||||
|
thumbnail,
|
||||||
|
rotation: page.rotation || 0,
|
||||||
|
selected: false,
|
||||||
|
splitBefore: page.splitBefore || false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
totalPageCount += processedFile.pages.length;
|
||||||
|
} else if (processedFile?.totalPages && processedFile.totalPages > 0) {
|
||||||
|
// Create placeholder pages from metadata while thumbnails are being generated
|
||||||
|
console.log(`🎬 PageEditor: Creating ${processedFile.totalPages} placeholder pages for ${fileRecord.name} from metadata`);
|
||||||
|
filePages = Array.from({ length: processedFile.totalPages }, (_, pageIndex) => {
|
||||||
|
const pageId = `${fileId}-page-${pageIndex + 1}`;
|
||||||
|
const globalPageNumber = totalPageCount + pageIndex + 1;
|
||||||
|
|
||||||
|
// Check for existing cached thumbnail
|
||||||
|
let thumbnail = getThumbnailFromCache(pageId) || null;
|
||||||
|
|
||||||
|
// For page 1 of each file, try to use the FileRecord thumbnail
|
||||||
|
if (!thumbnail && pageIndex === 0) {
|
||||||
|
thumbnail = fileRecord.thumbnailUrl || null;
|
||||||
|
if (thumbnail) {
|
||||||
|
addThumbnailToCache(pageId, thumbnail);
|
||||||
|
console.log(`📸 PageEditor: Using FileProcessingService thumbnail for ${fileRecord.name} placeholder page 1 (${pageId})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: pageId,
|
||||||
|
pageNumber: globalPageNumber,
|
||||||
|
thumbnail, // Will be null initially, populated by PageThumbnail components
|
||||||
|
rotation: 0,
|
||||||
|
selected: false,
|
||||||
|
splitBefore: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
totalPageCount += processedFile.totalPages;
|
||||||
|
} else {
|
||||||
|
// Ultimate fallback - single page while we wait for metadata
|
||||||
|
const pageId = `${fileId}-page-1`;
|
||||||
|
const globalPageNumber = totalPageCount + 1;
|
||||||
|
filePages = [{
|
||||||
id: pageId,
|
id: pageId,
|
||||||
pageNumber: index + 1,
|
pageNumber: globalPageNumber,
|
||||||
thumbnail, // Will be null initially, populated by PageThumbnail components
|
thumbnail: getThumbnailFromCache(pageId) || fileRecord.thumbnailUrl || null,
|
||||||
rotation: 0,
|
rotation: 0,
|
||||||
selected: false,
|
selected: false,
|
||||||
splitBefore: false,
|
splitBefore: false,
|
||||||
};
|
}];
|
||||||
});
|
|
||||||
} else {
|
totalPageCount += 1;
|
||||||
// Ultimate fallback - single page while we wait for metadata
|
}
|
||||||
pages = [{
|
|
||||||
id: `${primaryFileId}-page-1`,
|
pages.push(...filePages);
|
||||||
pageNumber: 1,
|
});
|
||||||
thumbnail: getThumbnailFromCache(`${primaryFileId}-page-1`) || primaryFileRecord.thumbnailUrl || null,
|
|
||||||
rotation: 0,
|
console.log(`🎬 PageEditor: Created merged document with ${pages.length} total pages from ${activeFileIds.length} files`);
|
||||||
selected: false,
|
|
||||||
splitBefore: false,
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create document with determined pages
|
// Create document with determined pages
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: activeFileIds.length === 1 ? (primaryFileId ?? 'unknown') : `merged:${filesSignature}`,
|
id: activeFileIds.length === 1 ? (primaryFileId ?? 'unknown') : `merged:${filesSignature}`,
|
||||||
name,
|
name,
|
||||||
@ -220,7 +241,7 @@ const PageEditor = ({
|
|||||||
totalPages: pages.length,
|
totalPages: pages.length,
|
||||||
destroy: () => {} // Optional cleanup function
|
destroy: () => {} // Optional cleanup function
|
||||||
};
|
};
|
||||||
}, [filesSignature, primaryFileId, primaryFileRecord]);
|
}, [filesSignature, activeFileIds, selectors, getThumbnailFromCache, addThumbnailToCache]);
|
||||||
|
|
||||||
|
|
||||||
// Display document: Use edited version if exists, otherwise original
|
// Display document: Use edited version if exists, otherwise original
|
||||||
@ -286,74 +307,20 @@ const PageEditor = ({
|
|||||||
|
|
||||||
// Simple cache-first thumbnail generation (no complex detection needed)
|
// Simple cache-first thumbnail generation (no complex detection needed)
|
||||||
|
|
||||||
// Lazy thumbnail generation - only generate when needed, with intelligent batching
|
|
||||||
const generateMissingThumbnails = useCallback(async () => {
|
|
||||||
if (!mergedPdfDocument || !primaryFileId || activeFileIds.length !== 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = selectors.getFile(primaryFileId);
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
const totalPages = mergedPdfDocument.totalPages;
|
|
||||||
if (totalPages <= 1) return; // Only page 1, nothing to generate
|
|
||||||
|
|
||||||
// For very large documents (2000+ pages), be much more conservative
|
|
||||||
const isVeryLargeDocument = totalPages > 2000;
|
|
||||||
|
|
||||||
if (isVeryLargeDocument) {
|
|
||||||
console.log(`📸 PageEditor: Very large document (${totalPages} pages) - using minimal thumbnail generation`);
|
|
||||||
// For very large docs, only generate the next visible batch (pages 2-25) to avoid UI blocking
|
|
||||||
const pageNumbersToGenerate = [];
|
|
||||||
for (let pageNum = 2; pageNum <= Math.min(25, totalPages); pageNum++) {
|
|
||||||
const pageId = `${primaryFileId}-page-${pageNum}`;
|
|
||||||
if (!getThumbnailFromCache(pageId)) {
|
|
||||||
pageNumbersToGenerate.push(pageNum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageNumbersToGenerate.length > 0) {
|
|
||||||
console.log(`📸 PageEditor: Generating initial batch for large doc: pages [${pageNumbersToGenerate.join(', ')}]`);
|
|
||||||
await generateThumbnailBatch(file, primaryFileId, pageNumbersToGenerate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule remaining thumbnails with delay to avoid blocking
|
|
||||||
setTimeout(() => {
|
|
||||||
generateRemainingThumbnailsLazily(file, primaryFileId, totalPages, 26);
|
|
||||||
}, 2000); // 2 second delay before starting background generation
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For smaller documents, check which pages 2+ need thumbnails
|
|
||||||
const pageNumbersToGenerate = [];
|
|
||||||
for (let pageNum = 2; pageNum <= totalPages; pageNum++) {
|
|
||||||
const pageId = `${primaryFileId}-page-${pageNum}`;
|
|
||||||
if (!getThumbnailFromCache(pageId)) {
|
|
||||||
pageNumbersToGenerate.push(pageNum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageNumbersToGenerate.length === 0) {
|
|
||||||
console.log(`📸 PageEditor: All pages 2+ already cached, skipping generation`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`📸 PageEditor: Generating thumbnails for pages: [${pageNumbersToGenerate.slice(0, 5).join(', ')}${pageNumbersToGenerate.length > 5 ? '...' : ''}]`);
|
|
||||||
await generateThumbnailBatch(file, primaryFileId, pageNumbersToGenerate);
|
|
||||||
}, [mergedPdfDocument, primaryFileId, activeFileIds, selectors]);
|
|
||||||
|
|
||||||
// Helper function to generate thumbnails in batches
|
// Helper function to generate thumbnails in batches
|
||||||
const generateThumbnailBatch = useCallback(async (file: File, fileId: string, pageNumbers: number[]) => {
|
const generateThumbnailBatch = useCallback(async (file: File, fileId: string, pageNumbers: number[]) => {
|
||||||
|
console.log(`📸 PageEditor: Starting thumbnail batch for ${file.name}, pages: [${pageNumbers.join(', ')}]`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Load PDF array buffer for Web Workers
|
// Load PDF array buffer for Web Workers
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
console.log(`📸 PageEditor: Loaded array buffer for ${file.name} (${arrayBuffer.byteLength} bytes)`);
|
||||||
|
|
||||||
// Calculate quality scale based on file size
|
// Calculate quality scale based on file size
|
||||||
const scale = calculateScaleFromFileSize(selectors.getFileRecord(fileId)?.size || 0);
|
const scale = calculateScaleFromFileSize(selectors.getFileRecord(fileId)?.size || 0);
|
||||||
|
|
||||||
// Start parallel thumbnail generation WITHOUT blocking the main thread
|
// Start parallel thumbnail generation WITHOUT blocking the main thread
|
||||||
await generateThumbnails(
|
const results = await generateThumbnails(
|
||||||
fileId, // Add fileId as first parameter
|
fileId, // Add fileId as first parameter
|
||||||
arrayBuffer,
|
arrayBuffer,
|
||||||
pageNumbers,
|
pageNumbers,
|
||||||
@ -365,12 +332,15 @@ const PageEditor = ({
|
|||||||
},
|
},
|
||||||
// Progress callback for thumbnail updates
|
// Progress callback for thumbnail updates
|
||||||
(progress: { completed: number; total: number; thumbnails: Array<{ pageNumber: number; thumbnail: string }> }) => {
|
(progress: { completed: number; total: number; thumbnails: Array<{ pageNumber: number; thumbnail: string }> }) => {
|
||||||
|
console.log(`📸 PageEditor: Progress update - ${progress.completed}/${progress.total} completed, ${progress.thumbnails.length} new thumbnails`);
|
||||||
|
|
||||||
// Batch process thumbnails to reduce main thread work
|
// Batch process thumbnails to reduce main thread work
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
progress.thumbnails.forEach(({ pageNumber, thumbnail }: { pageNumber: number; thumbnail: string }) => {
|
progress.thumbnails.forEach(({ pageNumber, thumbnail }: { pageNumber: number; thumbnail: string }) => {
|
||||||
// Use stable fileId for cache key
|
// Use stable fileId for cache key
|
||||||
const pageId = `${fileId}-page-${pageNumber}`;
|
const pageId = `${fileId}-page-${pageNumber}`;
|
||||||
addThumbnailToCache(pageId, thumbnail);
|
addThumbnailToCache(pageId, thumbnail);
|
||||||
|
console.log(`📸 PageEditor: Cached thumbnail for ${pageId}`);
|
||||||
|
|
||||||
// Don't update context state - thumbnails stay in cache only
|
// Don't update context state - thumbnails stay in cache only
|
||||||
// This eliminates per-page context rerenders
|
// This eliminates per-page context rerenders
|
||||||
@ -380,9 +350,9 @@ const PageEditor = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Removed verbose logging - only log errors
|
console.log(`📸 PageEditor: Thumbnail batch completed for ${file.name}. Generated ${results.length} thumbnails`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('PageEditor: Thumbnail generation failed:', error);
|
console.error(`PageEditor: Thumbnail generation failed for ${file.name}:`, error);
|
||||||
}
|
}
|
||||||
}, [generateThumbnails, addThumbnailToCache, selectors]);
|
}, [generateThumbnails, addThumbnailToCache, selectors]);
|
||||||
|
|
||||||
@ -415,6 +385,80 @@ const PageEditor = ({
|
|||||||
console.log(`📸 PageEditor: Background thumbnail generation completed for ${totalPages} pages`);
|
console.log(`📸 PageEditor: Background thumbnail generation completed for ${totalPages} pages`);
|
||||||
}, [getThumbnailFromCache, generateThumbnailBatch]);
|
}, [getThumbnailFromCache, generateThumbnailBatch]);
|
||||||
|
|
||||||
|
// Lazy thumbnail generation - only generate when needed, with intelligent batching for all files
|
||||||
|
const generateMissingThumbnails = useCallback(async () => {
|
||||||
|
if (!mergedPdfDocument || activeFileIds.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalPages = mergedPdfDocument.totalPages;
|
||||||
|
if (totalPages <= activeFileIds.length) return; // Only page 1 per file, nothing to generate
|
||||||
|
|
||||||
|
// Set a high worker limit for multi-file processing
|
||||||
|
// Each file may need multiple PDF document instances for parallel page processing
|
||||||
|
const neededWorkers = Math.min(activeFileIds.length * 3, 12); // Allow 3 workers per file, cap at 12
|
||||||
|
pdfWorkerManager.setMaxWorkers(neededWorkers);
|
||||||
|
console.log(`📸 PageEditor: Set worker limit to ${neededWorkers} for ${activeFileIds.length} files`);
|
||||||
|
|
||||||
|
// For very large documents (2000+ pages), be much more conservative
|
||||||
|
const isVeryLargeDocument = totalPages > 2000;
|
||||||
|
|
||||||
|
console.log(`📸 PageEditor: Generating thumbnails for ${activeFileIds.length} files with ${totalPages} total pages`);
|
||||||
|
|
||||||
|
// Process files strictly sequentially to avoid PDF document contention
|
||||||
|
// Each file will use its own PDF document instance from the cache
|
||||||
|
for (const fileId of activeFileIds) {
|
||||||
|
const file = selectors.getFile(fileId);
|
||||||
|
const fileRecord = selectors.getFileRecord(fileId);
|
||||||
|
|
||||||
|
if (!file || !fileRecord?.processedFile) continue;
|
||||||
|
|
||||||
|
const fileTotalPages = fileRecord.processedFile.totalPages || fileRecord.processedFile.pages?.length || 1;
|
||||||
|
if (fileTotalPages <= 1) continue; // Only page 1 for this file, skip
|
||||||
|
|
||||||
|
if (isVeryLargeDocument) {
|
||||||
|
console.log(`📸 PageEditor: Very large document (${totalPages} pages) - using minimal thumbnail generation for ${fileRecord.name}`);
|
||||||
|
// For very large docs, only generate the next visible batch (pages 2-25) per file to avoid UI blocking
|
||||||
|
const pageNumbersToGenerate = [];
|
||||||
|
for (let pageNum = 2; pageNum <= Math.min(25, fileTotalPages); pageNum++) {
|
||||||
|
const pageId = `${fileId}-page-${pageNum}`;
|
||||||
|
if (!getThumbnailFromCache(pageId)) {
|
||||||
|
pageNumbersToGenerate.push(pageNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageNumbersToGenerate.length > 0) {
|
||||||
|
console.log(`📸 PageEditor: Generating initial batch for ${fileRecord.name}: pages [${pageNumbersToGenerate.join(', ')}]`);
|
||||||
|
await generateThumbnailBatch(file, fileId, pageNumbersToGenerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule remaining thumbnails with delay to avoid blocking
|
||||||
|
setTimeout(() => {
|
||||||
|
generateRemainingThumbnailsLazily(file, fileId, fileTotalPages, 26);
|
||||||
|
}, 2000); // 2 second delay before starting background generation
|
||||||
|
} else {
|
||||||
|
// For smaller documents, check which pages 2+ need thumbnails for this file
|
||||||
|
const pageNumbersToGenerate = [];
|
||||||
|
for (let pageNum = 2; pageNum <= fileTotalPages; pageNum++) {
|
||||||
|
const pageId = `${fileId}-page-${pageNum}`;
|
||||||
|
if (!getThumbnailFromCache(pageId)) {
|
||||||
|
pageNumbersToGenerate.push(pageNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageNumbersToGenerate.length > 0) {
|
||||||
|
console.log(`📸 PageEditor: Generating thumbnails for ${fileRecord.name}: [${pageNumbersToGenerate.slice(0, 5).join(', ')}${pageNumbersToGenerate.length > 5 ? '...' : ''}]`);
|
||||||
|
await generateThumbnailBatch(file, fileId, pageNumbersToGenerate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay between files to ensure proper sequential processing and worker cleanup
|
||||||
|
if (activeFileIds.length > 1) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500)); // Increased delay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [mergedPdfDocument, activeFileIds, selectors, getThumbnailFromCache, generateThumbnailBatch, generateRemainingThumbnailsLazily]);
|
||||||
|
|
||||||
// Simple useEffect - just generate missing thumbnails when document is ready
|
// Simple useEffect - just generate missing thumbnails when document is ready
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mergedPdfDocument && mergedPdfDocument.totalPages > 1) {
|
if (mergedPdfDocument && mergedPdfDocument.totalPages > 1) {
|
||||||
@ -437,7 +481,8 @@ const PageEditor = ({
|
|||||||
enhancedPDFProcessingService.emergencyCleanup();
|
enhancedPDFProcessingService.emergencyCleanup();
|
||||||
fileProcessingService.emergencyCleanup();
|
fileProcessingService.emergencyCleanup();
|
||||||
pdfProcessingService.clearAll();
|
pdfProcessingService.clearAll();
|
||||||
// Final emergency cleanup of all workers
|
// Reset worker limit to default and cleanup
|
||||||
|
pdfWorkerManager.setMaxWorkers(3); // Reset to conservative default
|
||||||
pdfWorkerManager.emergencyCleanup();
|
pdfWorkerManager.emergencyCleanup();
|
||||||
};
|
};
|
||||||
}, [stopGeneration, destroyThumbnails]);
|
}, [stopGeneration, destroyThumbnails]);
|
||||||
|
@ -111,6 +111,43 @@ const PageThumbnail = React.memo(({
|
|||||||
};
|
};
|
||||||
}, [page.id, originalFile, requestThumbnail, getThumbnailFromCache]); // Removed thumbnailUrl to prevent loops
|
}, [page.id, originalFile, requestThumbnail, getThumbnailFromCache]); // Removed thumbnailUrl to prevent loops
|
||||||
|
|
||||||
|
// Poll cache for thumbnails that might be generated by other processes (like PageEditor batch generation)
|
||||||
|
useEffect(() => {
|
||||||
|
if (thumbnailUrl) {
|
||||||
|
return; // Already have a thumbnail
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
const pollCache = () => {
|
||||||
|
if (cancelled) return;
|
||||||
|
|
||||||
|
const cachedThumbnail = getThumbnailFromCache(page.id);
|
||||||
|
if (cachedThumbnail) {
|
||||||
|
console.log(`📸 PageThumbnail: Found cached thumbnail for page ${page.pageNumber} via polling`);
|
||||||
|
setThumbnailUrl(cachedThumbnail);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue polling every 1 second for up to 30 seconds
|
||||||
|
setTimeout(pollCache, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start polling after a short delay
|
||||||
|
const pollTimer = setTimeout(pollCache, 500);
|
||||||
|
|
||||||
|
// Stop polling after 30 seconds to avoid infinite polling
|
||||||
|
const stopTimer = setTimeout(() => {
|
||||||
|
cancelled = true;
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
clearTimeout(pollTimer);
|
||||||
|
clearTimeout(stopTimer);
|
||||||
|
};
|
||||||
|
}, [page.id, thumbnailUrl, getThumbnailFromCache]);
|
||||||
|
|
||||||
|
|
||||||
const pageElementRef = useCallback((element: HTMLDivElement | null) => {
|
const pageElementRef = useCallback((element: HTMLDivElement | null) => {
|
||||||
if (element) {
|
if (element) {
|
||||||
|
@ -194,7 +194,7 @@ class PDFWorkerManager {
|
|||||||
* Set maximum concurrent workers
|
* Set maximum concurrent workers
|
||||||
*/
|
*/
|
||||||
setMaxWorkers(max: number): void {
|
setMaxWorkers(max: number): void {
|
||||||
this.maxWorkers = Math.max(1, Math.min(max, 10)); // Between 1-10 workers
|
this.maxWorkers = Math.max(1, Math.min(max, 15)); // Between 1-15 workers for multi-file support
|
||||||
console.log(`🏭 Max workers set to ${this.maxWorkers}`);
|
console.log(`🏭 Max workers set to ${this.maxWorkers}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user