mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
Fix rotate failing on large documents (#5917)
This commit is contained in:
@@ -9,44 +9,62 @@ export abstract class DOMCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class RotatePageCommand extends DOMCommand {
|
export class RotatePageCommand extends DOMCommand {
|
||||||
|
private originalRotation: number | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private pageId: string,
|
private pageId: string,
|
||||||
private degrees: number
|
private degrees: number,
|
||||||
|
private getCurrentDocument: () => PDFDocument | null,
|
||||||
|
private setDocument: (doc: PDFDocument) => void
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(): void {
|
execute(): void {
|
||||||
|
const currentDoc = this.getCurrentDocument();
|
||||||
|
if (!currentDoc) return;
|
||||||
|
|
||||||
|
const page = currentDoc.pages.find(p => p.id === this.pageId);
|
||||||
|
if (!page) return;
|
||||||
|
|
||||||
|
if (this.originalRotation === null) {
|
||||||
|
this.originalRotation = page.rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRotation = ((page.rotation + this.degrees) % 360 + 360) % 360;
|
||||||
|
|
||||||
|
this.setDocument({
|
||||||
|
...currentDoc,
|
||||||
|
pages: currentDoc.pages.map(p =>
|
||||||
|
p.id === this.pageId ? { ...p, rotation: newRotation } : p
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also update DOM immediately for CSS animation on currently-mounted pages
|
||||||
const pageElement = document.querySelector(`[data-page-id="${this.pageId}"]`);
|
const pageElement = document.querySelector(`[data-page-id="${this.pageId}"]`);
|
||||||
if (pageElement) {
|
if (pageElement) {
|
||||||
const img = pageElement.querySelector('img');
|
const img = pageElement.querySelector('img');
|
||||||
if (img) {
|
if (img) img.style.transform = `rotate(${newRotation}deg)`;
|
||||||
const currentTransform = img.style.transform || '';
|
|
||||||
const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/);
|
|
||||||
const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0;
|
|
||||||
let newRotation = currentRotation + this.degrees;
|
|
||||||
|
|
||||||
newRotation = ((newRotation % 360) + 360) % 360;
|
|
||||||
|
|
||||||
img.style.transform = `rotate(${newRotation}deg)`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
undo(): void {
|
undo(): void {
|
||||||
|
if (this.originalRotation === null) return;
|
||||||
|
|
||||||
|
const currentDoc = this.getCurrentDocument();
|
||||||
|
if (!currentDoc) return;
|
||||||
|
|
||||||
|
this.setDocument({
|
||||||
|
...currentDoc,
|
||||||
|
pages: currentDoc.pages.map(p =>
|
||||||
|
p.id === this.pageId ? { ...p, rotation: this.originalRotation! } : p
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
const pageElement = document.querySelector(`[data-page-id="${this.pageId}"]`);
|
const pageElement = document.querySelector(`[data-page-id="${this.pageId}"]`);
|
||||||
if (pageElement) {
|
if (pageElement) {
|
||||||
const img = pageElement.querySelector('img');
|
const img = pageElement.querySelector('img');
|
||||||
if (img) {
|
if (img) img.style.transform = `rotate(${this.originalRotation}deg)`;
|
||||||
const currentTransform = img.style.transform || '';
|
|
||||||
const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/);
|
|
||||||
const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0;
|
|
||||||
let previousRotation = currentRotation - this.degrees;
|
|
||||||
|
|
||||||
previousRotation = ((previousRotation % 360) + 360) % 360;
|
|
||||||
|
|
||||||
img.style.transform = `rotate(${previousRotation}deg)`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,38 +329,67 @@ export class BulkRotateCommand extends DOMCommand {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private pageIds: string[],
|
private pageIds: string[],
|
||||||
private degrees: number
|
private degrees: number,
|
||||||
|
private getCurrentDocument: () => PDFDocument | null,
|
||||||
|
private setDocument: (doc: PDFDocument) => void
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(): void {
|
execute(): void {
|
||||||
this.pageIds.forEach(pageId => {
|
const currentDoc = this.getCurrentDocument();
|
||||||
|
if (!currentDoc) return;
|
||||||
|
|
||||||
|
const pageIdSet = new Set(this.pageIds);
|
||||||
|
|
||||||
|
// Store original rotations from state for reliable undo (only on first execution)
|
||||||
|
if (this.originalRotations.size === 0) {
|
||||||
|
for (const page of currentDoc.pages) {
|
||||||
|
if (pageIdSet.has(page.id)) {
|
||||||
|
this.originalRotations.set(page.id, page.rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update state so rotation survives virtualizer unmount/remount
|
||||||
|
// Build a map of id → new rotation for O(1) DOM lookups below
|
||||||
|
const newRotationById = new Map<string, number>();
|
||||||
|
const updatedPages = currentDoc.pages.map(page => {
|
||||||
|
if (pageIdSet.has(page.id)) {
|
||||||
|
const newRotation = ((page.rotation + this.degrees) % 360 + 360) % 360;
|
||||||
|
newRotationById.set(page.id, newRotation);
|
||||||
|
return { ...page, rotation: newRotation };
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setDocument({ ...currentDoc, pages: updatedPages });
|
||||||
|
|
||||||
|
// Mirror to DOM immediately for CSS animation on currently-mounted pages
|
||||||
|
for (const pageId of this.pageIds) {
|
||||||
const pageElement = document.querySelector(`[data-page-id="${pageId}"]`);
|
const pageElement = document.querySelector(`[data-page-id="${pageId}"]`);
|
||||||
if (pageElement) {
|
if (pageElement) {
|
||||||
const img = pageElement.querySelector('img');
|
const img = pageElement.querySelector('img');
|
||||||
if (img) {
|
if (img) img.style.transform = `rotate(${newRotationById.get(pageId)}deg)`;
|
||||||
// Store original rotation for undo (only on first execution)
|
|
||||||
if (!this.originalRotations.has(pageId)) {
|
|
||||||
const currentTransform = img.style.transform || '';
|
|
||||||
const rotateMatch = currentTransform.match(/rotate\(([^)]+)\)/);
|
|
||||||
const currentRotation = rotateMatch ? parseInt(rotateMatch[1]) : 0;
|
|
||||||
this.originalRotations.set(pageId, currentRotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.transform = `rotate(${newRotation}deg)`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
undo(): void {
|
undo(): void {
|
||||||
this.pageIds.forEach(pageId => {
|
const currentDoc = this.getCurrentDocument();
|
||||||
|
if (!currentDoc) return;
|
||||||
|
|
||||||
|
// Restore original rotations in state
|
||||||
|
const updatedPages = currentDoc.pages.map(page =>
|
||||||
|
this.originalRotations.has(page.id)
|
||||||
|
? { ...page, rotation: this.originalRotations.get(page.id)! }
|
||||||
|
: page
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setDocument({ ...currentDoc, pages: updatedPages });
|
||||||
|
|
||||||
|
// Mirror to DOM immediately for CSS animation on currently-mounted pages
|
||||||
|
for (const pageId of this.pageIds) {
|
||||||
const pageElement = document.querySelector(`[data-page-id="${pageId}"]`);
|
const pageElement = document.querySelector(`[data-page-id="${pageId}"]`);
|
||||||
if (pageElement) {
|
if (pageElement) {
|
||||||
const img = pageElement.querySelector('img');
|
const img = pageElement.querySelector('img');
|
||||||
@@ -350,7 +397,7 @@ export class BulkRotateCommand extends DOMCommand {
|
|||||||
img.style.transform = `rotate(${this.originalRotations.get(pageId)}deg)`;
|
img.style.transform = `rotate(${this.originalRotations.get(pageId)}deg)`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get description(): string {
|
get description(): string {
|
||||||
|
|||||||
@@ -66,20 +66,20 @@ export const usePageEditorCommands = ({
|
|||||||
|
|
||||||
const handleRotatePages = useCallback(
|
const handleRotatePages = useCallback(
|
||||||
(pageIds: string[], rotation: number) => {
|
(pageIds: string[], rotation: number) => {
|
||||||
const bulkRotateCommand = new BulkRotateCommand(pageIds, rotation);
|
const bulkRotateCommand = new BulkRotateCommand(pageIds, rotation, getEditedDocument, setEditedDocument);
|
||||||
executeCommandWithTracking(bulkRotateCommand);
|
executeCommandWithTracking(bulkRotateCommand);
|
||||||
},
|
},
|
||||||
[executeCommandWithTracking]
|
[executeCommandWithTracking, getEditedDocument, setEditedDocument]
|
||||||
);
|
);
|
||||||
|
|
||||||
const createRotateCommand = useCallback(
|
const createRotateCommand = useCallback(
|
||||||
(pageIds: string[], rotation: number) => ({
|
(pageIds: string[], rotation: number) => ({
|
||||||
execute: () => {
|
execute: () => {
|
||||||
const bulkRotateCommand = new BulkRotateCommand(pageIds, rotation);
|
const bulkRotateCommand = new BulkRotateCommand(pageIds, rotation, getEditedDocument, setEditedDocument);
|
||||||
executeCommandWithTracking(bulkRotateCommand);
|
executeCommandWithTracking(bulkRotateCommand);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[executeCommandWithTracking]
|
[executeCommandWithTracking, getEditedDocument, setEditedDocument]
|
||||||
);
|
);
|
||||||
|
|
||||||
const createDeleteCommand = useCallback(
|
const createDeleteCommand = useCallback(
|
||||||
|
|||||||
Reference in New Issue
Block a user