mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 06:09:23 +00:00
Fully working with performant thumbnail generation
This commit is contained in:
parent
e1c30edddb
commit
62f92da0fe
@ -16,8 +16,7 @@ import { enhancedPDFProcessingService } from "../../services/enhancedPDFProcessi
|
||||
import { fileProcessingService } from "../../services/fileProcessingService";
|
||||
import { pdfProcessingService } from "../../services/pdfProcessingService";
|
||||
import { pdfWorkerManager } from "../../services/pdfWorkerManager";
|
||||
import { useThumbnailGeneration } from "../../hooks/useThumbnailGeneration";
|
||||
import { calculateScaleFromFileSize } from "../../utils/thumbnailUtils";
|
||||
// Thumbnail generation is now handled by individual PageThumbnail components
|
||||
import { fileStorage } from "../../services/fileStorage";
|
||||
import { indexedDBManager, DATABASE_CONFIGS } from "../../services/indexedDBManager";
|
||||
import './PageEditor.module.css';
|
||||
@ -47,9 +46,12 @@ class RotatePageCommand extends DOMCommand {
|
||||
if (pageElement) {
|
||||
const img = pageElement.querySelector('img');
|
||||
if (img) {
|
||||
const currentRotation = parseInt(img.style.rotate?.replace(/[^\d-]/g, '') || '0');
|
||||
// Extract current rotation from transform property to match the animated CSS
|
||||
const currentTransform = img.style.transform || '';
|
||||
const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/);
|
||||
const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0;
|
||||
const newRotation = currentRotation + this.degrees;
|
||||
img.style.rotate = `${newRotation}deg`;
|
||||
img.style.transform = `rotate(${newRotation}deg)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -60,9 +62,12 @@ class RotatePageCommand extends DOMCommand {
|
||||
if (pageElement) {
|
||||
const img = pageElement.querySelector('img');
|
||||
if (img) {
|
||||
const currentRotation = parseInt(img.style.rotate?.replace(/[^\d-]/g, '') || '0');
|
||||
// Extract current rotation from transform property
|
||||
const currentTransform = img.style.transform || '';
|
||||
const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/);
|
||||
const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0;
|
||||
const previousRotation = currentRotation - this.degrees;
|
||||
img.style.rotate = `${previousRotation}deg`;
|
||||
img.style.transform = `rotate(${previousRotation}deg)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -297,14 +302,18 @@ class BulkRotateCommand extends DOMCommand {
|
||||
if (img) {
|
||||
// Store original rotation for undo (only on first execution)
|
||||
if (!this.originalRotations.has(pageId)) {
|
||||
const currentRotation = parseInt(img.style.rotate?.replace(/[^\d-]/g, '') || '0');
|
||||
const currentTransform = img.style.transform || '';
|
||||
const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/);
|
||||
const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0;
|
||||
this.originalRotations.set(pageId, currentRotation);
|
||||
}
|
||||
|
||||
// Apply rotation
|
||||
const currentRotation = parseInt(img.style.rotate?.replace(/[^\d-]/g, '') || '0');
|
||||
// Apply rotation using transform to trigger CSS animation
|
||||
const currentTransform = img.style.transform || '';
|
||||
const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/);
|
||||
const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0;
|
||||
const newRotation = currentRotation + this.degrees;
|
||||
img.style.rotate = `${newRotation}deg`;
|
||||
img.style.transform = `rotate(${newRotation}deg)`;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -316,7 +325,7 @@ class BulkRotateCommand extends DOMCommand {
|
||||
if (pageElement) {
|
||||
const img = pageElement.querySelector('img');
|
||||
if (img && this.originalRotations.has(pageId)) {
|
||||
img.style.rotate = `${this.originalRotations.get(pageId)}deg`;
|
||||
img.style.transform = `rotate(${this.originalRotations.get(pageId)}deg)`;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -476,50 +485,7 @@ const PageEditor = ({
|
||||
// DOM-first undo manager (replaces the old React state undo system)
|
||||
const undoManagerRef = useRef(new UndoManager());
|
||||
|
||||
// Thumbnail generation (opt-in for visual tools) - MUST be before mergedPdfDocument
|
||||
const {
|
||||
generateThumbnails,
|
||||
addThumbnailToCache,
|
||||
getThumbnailFromCache,
|
||||
stopGeneration,
|
||||
destroyThumbnails
|
||||
} = useThumbnailGeneration();
|
||||
|
||||
// Helper function to generate thumbnails in batches
|
||||
const generateThumbnailBatch = useCallback(async (file: File, fileId: string, pageNumbers: number[]) => {
|
||||
console.log(`📸 PageEditor: Starting thumbnail batch for ${file.name}, pages: [${pageNumbers.join(', ')}]`);
|
||||
|
||||
try {
|
||||
// Load PDF array buffer for Web Workers
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
|
||||
// Calculate quality scale based on file size
|
||||
const scale = calculateScaleFromFileSize(selectors.getFileRecord(fileId)?.size || 0);
|
||||
|
||||
// Start parallel thumbnail generation
|
||||
const results = await generateThumbnails(
|
||||
fileId,
|
||||
arrayBuffer,
|
||||
pageNumbers,
|
||||
{
|
||||
scale,
|
||||
parallelBatches: Math.min(4, pageNumbers.length),
|
||||
}
|
||||
);
|
||||
|
||||
// Cache all generated thumbnails
|
||||
results.forEach(({ pageNumber, thumbnail }) => {
|
||||
if (thumbnail) {
|
||||
const pageId = `${fileId}-${pageNumber}`;
|
||||
addThumbnailToCache(pageId, thumbnail);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`📸 PageEditor: Thumbnail batch completed for ${file.name}. Generated ${results.length} thumbnails`);
|
||||
} catch (error) {
|
||||
console.error(`PageEditor: Thumbnail generation failed for ${file.name}:`, error);
|
||||
}
|
||||
}, [generateThumbnails, addThumbnailToCache, selectors]);
|
||||
// Thumbnail generation is now handled on-demand by individual PageThumbnail components using modern services
|
||||
|
||||
|
||||
// Get primary file record outside useMemo to track processedFile changes
|
||||
@ -617,52 +583,13 @@ const PageEditor = ({
|
||||
return mergedDoc;
|
||||
}, [activeFileIds, primaryFileId, primaryFileRecord, processedFilePages, processedFileTotalPages, selectors, filesSignature]);
|
||||
|
||||
// Generate missing thumbnails for all loaded files
|
||||
const generateMissingThumbnails = useCallback(async () => {
|
||||
if (!mergedPdfDocument || activeFileIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
// Large document detection for smart loading
|
||||
const isVeryLargeDocument = useMemo(() => {
|
||||
return mergedPdfDocument ? mergedPdfDocument.totalPages > 2000 : false;
|
||||
}, [mergedPdfDocument?.totalPages]);
|
||||
|
||||
console.log(`📸 PageEditor: Generating thumbnails for ${activeFileIds.length} files with ${mergedPdfDocument.totalPages} total pages`);
|
||||
|
||||
// Process files sequentially to avoid PDF document contention
|
||||
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;
|
||||
if (!fileTotalPages) continue;
|
||||
|
||||
// Find missing thumbnails for this file
|
||||
const pageNumbersToGenerate: number[] = [];
|
||||
for (let pageNum = 1; pageNum <= fileTotalPages; pageNum++) {
|
||||
const pageId = `${fileId}-${pageNum}`;
|
||||
if (!getThumbnailFromCache(pageId)) {
|
||||
pageNumbersToGenerate.push(pageNum);
|
||||
}
|
||||
}
|
||||
|
||||
if (pageNumbersToGenerate.length > 0) {
|
||||
console.log(`📸 PageEditor: Generating thumbnails for ${fileRecord.name}: pages [${pageNumbersToGenerate.join(', ')}]`);
|
||||
await generateThumbnailBatch(file, fileId, pageNumbersToGenerate);
|
||||
}
|
||||
|
||||
// Small delay between files to ensure proper sequential processing
|
||||
if (activeFileIds.length > 1) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
}
|
||||
}, [mergedPdfDocument, activeFileIds, selectors, getThumbnailFromCache, generateThumbnailBatch]);
|
||||
|
||||
// Generate missing thumbnails when document is ready
|
||||
useEffect(() => {
|
||||
if (mergedPdfDocument && mergedPdfDocument.totalPages > 0) {
|
||||
console.log(`📸 PageEditor: Document ready with ${mergedPdfDocument.totalPages} pages, checking for missing thumbnails`);
|
||||
generateMissingThumbnails();
|
||||
}
|
||||
}, [mergedPdfDocument, generateMissingThumbnails]);
|
||||
// Thumbnails are now generated on-demand by PageThumbnail components
|
||||
// No bulk generation needed - modern thumbnail service handles this efficiently
|
||||
|
||||
// Selection and UI state management
|
||||
const [selectionMode, setSelectionMode] = useState(false);
|
||||
|
@ -72,7 +72,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
||||
const [isMouseDown, setIsMouseDown] = useState(false);
|
||||
const [mouseStartPos, setMouseStartPos] = useState<{x: number, y: number} | null>(null);
|
||||
const dragElementRef = useRef<HTMLDivElement>(null);
|
||||
const { getThumbnailFromCache } = useThumbnailGeneration();
|
||||
const { getThumbnailFromCache, requestThumbnail } = useThumbnailGeneration();
|
||||
|
||||
// Update thumbnail URL when page prop changes
|
||||
useEffect(() => {
|
||||
@ -81,22 +81,43 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
||||
}
|
||||
}, [page.thumbnail, page.id]);
|
||||
|
||||
// Poll for cached thumbnails as they're generated
|
||||
// Request thumbnail on-demand using modern service
|
||||
useEffect(() => {
|
||||
const checkThumbnail = () => {
|
||||
const cachedThumbnail = getThumbnailFromCache(page.id);
|
||||
if (cachedThumbnail && cachedThumbnail !== thumbnailUrl) {
|
||||
setThumbnailUrl(cachedThumbnail);
|
||||
}
|
||||
let isCancelled = false;
|
||||
|
||||
// If we already have a thumbnail, use it
|
||||
if (page.thumbnail) {
|
||||
setThumbnailUrl(page.thumbnail);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
const cachedThumbnail = getThumbnailFromCache(page.id);
|
||||
if (cachedThumbnail) {
|
||||
setThumbnailUrl(cachedThumbnail);
|
||||
return;
|
||||
}
|
||||
|
||||
// Request thumbnail generation if we have the original file
|
||||
if (originalFile) {
|
||||
// Extract page number from page.id (format: fileId-pageNumber)
|
||||
const pageNumber = parseInt(page.id.split('-').pop() || '1');
|
||||
|
||||
requestThumbnail(page.id, originalFile, pageNumber)
|
||||
.then(thumbnail => {
|
||||
if (!isCancelled && thumbnail) {
|
||||
setThumbnailUrl(thumbnail);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.warn(`Failed to generate thumbnail for ${page.id}:`, error);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
|
||||
// Check immediately
|
||||
checkThumbnail();
|
||||
|
||||
// Poll every 500ms for new thumbnails
|
||||
const pollInterval = setInterval(checkThumbnail, 500);
|
||||
return () => clearInterval(pollInterval);
|
||||
}, [page.id, getThumbnailFromCache, thumbnailUrl]);
|
||||
}, [page.id, page.thumbnail, originalFile, getThumbnailFromCache, requestThumbnail]);
|
||||
|
||||
const pageElementRef = useCallback((element: HTMLDivElement | null) => {
|
||||
if (element) {
|
||||
@ -270,13 +291,11 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
||||
top: 8,
|
||||
right: 8,
|
||||
zIndex: 10,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
border: '1px solid #ccc',
|
||||
backgroundColor: 'white',
|
||||
borderRadius: '4px',
|
||||
padding: '4px',
|
||||
padding: '2px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
pointerEvents: 'auto',
|
||||
cursor: 'pointer'
|
||||
pointerEvents: 'auto'
|
||||
}}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onMouseUp={(e) => e.stopPropagation()}
|
||||
@ -369,6 +388,9 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onMouseUp={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Tooltip label="Move Left">
|
||||
<ActionIcon
|
||||
|
Loading…
x
Reference in New Issue
Block a user