Feature/v2/navigate save prompt (#4586)

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
This commit is contained in:
Reece Browne
2025-10-02 20:14:35 +01:00
committed by GitHub
parent 8aa6aff53a
commit f9ac1bd62e
15 changed files with 396 additions and 169 deletions
@@ -10,14 +10,8 @@ export class DocumentManipulationService {
* Returns single document or multiple documents if splits are present
*/
applyDOMChangesToDocument(pdfDocument: PDFDocument, currentDisplayOrder?: PDFDocument, splitPositions?: Set<number>): PDFDocument | PDFDocument[] {
console.log('DocumentManipulationService: Applying DOM changes to document');
console.log('Original document page order:', pdfDocument.pages.map(p => p.pageNumber));
console.log('Current display order:', currentDisplayOrder?.pages.map(p => p.pageNumber) || 'none provided');
console.log('Split positions:', splitPositions ? Array.from(splitPositions).sort() : 'none');
// Use current display order (from React state) if provided, otherwise use original order
const baseDocument = currentDisplayOrder || pdfDocument;
console.log('Using page order:', baseDocument.pages.map(p => p.pageNumber));
// Apply DOM changes to each page (rotation only now, splits are position-based)
let updatedPages = baseDocument.pages.map(page => this.applyPageChanges(page));
@@ -57,32 +51,25 @@ export class DocumentManipulationService {
private createSplitDocuments(document: PDFDocument): PDFDocument[] {
const documents: PDFDocument[] = [];
const splitPoints: number[] = [];
// Find split points
document.pages.forEach((page, index) => {
if (page.splitAfter) {
console.log(`Found split marker at page ${page.pageNumber} (index ${index}), adding split point at ${index + 1}`);
splitPoints.push(index + 1);
}
});
// Add end point if not already there
if (splitPoints.length === 0 || splitPoints[splitPoints.length - 1] !== document.pages.length) {
splitPoints.push(document.pages.length);
}
console.log('Final split points:', splitPoints);
console.log('Total pages to split:', document.pages.length);
let startIndex = 0;
let partNumber = 1;
for (const endIndex of splitPoints) {
const segmentPages = document.pages.slice(startIndex, endIndex);
console.log(`Creating split document ${partNumber}: pages ${startIndex}-${endIndex-1} (${segmentPages.length} pages)`);
console.log(`Split document ${partNumber} page numbers:`, segmentPages.map(p => p.pageNumber));
if (segmentPages.length > 0) {
documents.push({
...document,
@@ -93,11 +80,10 @@ export class DocumentManipulationService {
});
partNumber++;
}
startIndex = endIndex;
}
console.log(`Created ${documents.length} split documents`);
return documents;
}
@@ -108,7 +94,6 @@ export class DocumentManipulationService {
// Find the DOM element for this page
const pageElement = document.querySelector(`[data-page-id="${page.id}"]`);
if (!pageElement) {
console.log(`Page ${page.pageNumber}: No DOM element found, keeping original state`);
return page;
}
@@ -116,8 +101,7 @@ export class DocumentManipulationService {
// Apply rotation changes from DOM
updatedPage.rotation = this.getRotationFromDOM(pageElement, page);
return updatedPage;
}
@@ -126,16 +110,21 @@ export class DocumentManipulationService {
*/
private getRotationFromDOM(pageElement: Element, originalPage: PDFPage): number {
const img = pageElement.querySelector('img');
if (img && img.style.transform) {
// Parse rotation from transform property (e.g., "rotate(90deg)" -> 90)
const rotationMatch = img.style.transform.match(/rotate\((-?\d+)deg\)/);
const domRotation = rotationMatch ? parseInt(rotationMatch[1]) : 0;
console.log(`Page ${originalPage.pageNumber}: DOM rotation = ${domRotation}°, original = ${originalPage.rotation}°`);
return domRotation;
if (img) {
const originalRotation = parseInt(img.getAttribute('data-original-rotation') || '0');
const currentTransform = img.style.transform || '';
const rotationMatch = currentTransform.match(/rotate\((-?\d+)deg\)/);
const visualRotation = rotationMatch ? parseInt(rotationMatch[1]) : originalRotation;
const userChange = ((visualRotation - originalRotation) % 360 + 360) % 360;
let finalRotation = (originalPage.rotation + userChange) % 360;
if (finalRotation === 360) finalRotation = 0;
return finalRotation;
}
console.log(`Page ${originalPage.pageNumber}: No DOM rotation found, keeping original = ${originalPage.rotation}°`);
return originalPage.rotation;
}
@@ -200,11 +200,13 @@ export class EnhancedPDFProcessingService {
const page = await pdf.getPage(i);
const thumbnail = await this.renderPageThumbnail(page, config.thumbnailQuality);
const rotation = page.rotate || 0;
pages.push({
id: `${createQuickKey(file)}-page-${i}`,
pageNumber: i,
thumbnail,
rotation: 0,
rotation,
selected: false
});
@@ -254,7 +256,7 @@ export class EnhancedPDFProcessingService {
id: `${createQuickKey(file)}-page-${i}`,
pageNumber: i,
thumbnail,
rotation: 0,
rotation: page.rotate || 0,
selected: false
});
@@ -265,11 +267,15 @@ export class EnhancedPDFProcessingService {
// Create placeholder pages for remaining pages
for (let i = priorityCount + 1; i <= totalPages; i++) {
// Load page just to get rotation
const page = await pdf.getPage(i);
const rotation = page.rotate || 0;
pages.push({
id: `${createQuickKey(file)}-page-${i}`,
pageNumber: i,
thumbnail: null, // Will be loaded lazily
rotation: 0,
rotation,
selected: false
});
}
@@ -316,7 +322,7 @@ export class EnhancedPDFProcessingService {
id: `${createQuickKey(file)}-page-${i}`,
pageNumber: i,
thumbnail,
rotation: 0,
rotation: page.rotate || 0,
selected: false
});
@@ -333,11 +339,15 @@ export class EnhancedPDFProcessingService {
// Create placeholders for remaining pages
for (let i = firstChunkEnd + 1; i <= totalPages; i++) {
// Load page just to get rotation
const page = await pdf.getPage(i);
const rotation = page.rotate || 0;
pages.push({
id: `${createQuickKey(file)}-page-${i}`,
pageNumber: i,
thumbnail: null,
rotation: 0,
rotation,
selected: false
});
}
@@ -367,11 +377,15 @@ export class EnhancedPDFProcessingService {
// Create placeholder pages without thumbnails
const pages: PDFPage[] = [];
for (let i = 1; i <= totalPages; i++) {
// Load page just to get rotation
const page = await pdf.getPage(i);
const rotation = page.rotate || 0;
pages.push({
id: `${createQuickKey(file)}-page-${i}`,
pageNumber: i,
thumbnail: null,
rotation: 0,
rotation,
selected: false
});
}
@@ -390,7 +404,7 @@ export class EnhancedPDFProcessingService {
const scales = { low: 0.2, medium: 0.5, high: 0.8 }; // Reduced low quality for page editor
const scale = scales[quality];
const viewport = page.getViewport({ scale });
const viewport = page.getViewport({ scale, rotation: 0 });
const canvas = document.createElement('canvas');
canvas.width = viewport.width;
canvas.height = viewport.height;
+34
View File
@@ -0,0 +1,34 @@
import { StirlingFile, StirlingFileStub } from '../types/fileContext';
import { createChildStub, generateProcessedFileMetadata } from '../contexts/file/fileActions';
import { createStirlingFile } from '../types/fileContext';
import { ToolId } from '../types/toolId';
/**
* Create StirlingFiles and StirlingFileStubs from exported files
* Used when saving page editor changes to create version history
*/
export async function createStirlingFilesAndStubs(
files: File[],
parentStub: StirlingFileStub,
toolId: ToolId
): Promise<{ stirlingFiles: StirlingFile[], stubs: StirlingFileStub[] }> {
const stirlingFiles: StirlingFile[] = [];
const stubs: StirlingFileStub[] = [];
for (const file of files) {
const processedFileMetadata = await generateProcessedFileMetadata(file);
const childStub = createChildStub(
parentStub,
{ toolId, timestamp: Date.now() },
file,
processedFileMetadata?.thumbnailUrl,
processedFileMetadata
);
const stirlingFile = createStirlingFile(file, childStub.id);
stirlingFiles.push(stirlingFile);
stubs.push(childStub);
}
return { stirlingFiles, stubs };
}
+46
View File
@@ -0,0 +1,46 @@
import { PDFDocument } from '../types/pageEditor';
import { pdfExportService } from './pdfExportService';
import { FileId } from '../types/file';
/**
* Export processed documents to File objects
* Handles both single documents and split documents (multiple PDFs)
*/
export async function exportProcessedDocumentsToFiles(
processedDocuments: PDFDocument | PDFDocument[],
sourceFiles: Map<FileId, File> | null,
exportFilename: string
): Promise<File[]> {
console.log('exportProcessedDocumentsToFiles called with:', {
isArray: Array.isArray(processedDocuments),
numDocs: Array.isArray(processedDocuments) ? processedDocuments.length : 1,
hasSourceFiles: sourceFiles !== null,
sourceFilesSize: sourceFiles?.size
});
if (Array.isArray(processedDocuments)) {
// Multiple documents (splits)
const files: File[] = [];
const baseName = exportFilename.replace(/\.pdf$/i, '');
for (let i = 0; i < processedDocuments.length; i++) {
const doc = processedDocuments[i];
const partFilename = `${baseName}_part_${i + 1}.pdf`;
const result = sourceFiles
? await pdfExportService.exportPDFMultiFile(doc, sourceFiles, [], { selectedOnly: false, filename: partFilename })
: await pdfExportService.exportPDF(doc, [], { selectedOnly: false, filename: partFilename });
files.push(new File([result.blob], result.filename, { type: 'application/pdf' }));
}
return files;
} else {
// Single document
const result = sourceFiles
? await pdfExportService.exportPDFMultiFile(processedDocuments, sourceFiles, [], { selectedOnly: false, filename: exportFilename })
: await pdfExportService.exportPDF(processedDocuments, [], { selectedOnly: false, filename: exportFilename });
return [new File([result.blob], result.filename, { type: 'application/pdf' })];
}
}
+4 -16
View File
@@ -98,10 +98,7 @@ export class PDFExportService {
// Create a blank page
const blankPage = newDoc.addPage(PageSizes.A4);
// Apply rotation if needed
if (page.rotation !== 0) {
blankPage.setRotation(degrees(page.rotation));
}
blankPage.setRotation(degrees(page.rotation));
} else if (page.originalFileId && loadedDocs.has(page.originalFileId)) {
// Get the correct source document for this page
const sourceDoc = loadedDocs.get(page.originalFileId)!;
@@ -111,10 +108,7 @@ export class PDFExportService {
// Copy the page from the correct source document
const [copiedPage] = await newDoc.copyPages(sourceDoc, [sourcePageIndex]);
// Apply rotation
if (page.rotation !== 0) {
copiedPage.setRotation(degrees(page.rotation));
}
copiedPage.setRotation(degrees(page.rotation));
newDoc.addPage(copiedPage);
}
@@ -147,10 +141,7 @@ export class PDFExportService {
// Create a blank page
const blankPage = newDoc.addPage(PageSizes.A4);
// Apply rotation if needed
if (page.rotation !== 0) {
blankPage.setRotation(degrees(page.rotation));
}
blankPage.setRotation(degrees(page.rotation));
} else {
// Get the original page from source document using originalPageNumber
const sourcePageIndex = page.originalPageNumber - 1;
@@ -159,10 +150,7 @@ export class PDFExportService {
// Copy the page
const [copiedPage] = await newDoc.copyPages(sourceDoc, [sourcePageIndex]);
// Apply rotation
if (page.rotation !== 0) {
copiedPage.setRotation(degrees(page.rotation));
}
copiedPage.setRotation(degrees(page.rotation));
newDoc.addPage(copiedPage);
}
@@ -164,7 +164,7 @@ export class ThumbnailGenerationService {
for (const pageNumber of batch) {
try {
const page = await pdf.getPage(pageNumber);
const viewport = page.getViewport({ scale });
const viewport = page.getViewport({ scale, rotation: 0 });
const canvas = document.createElement('canvas');
canvas.width = viewport.width;