diff --git a/frontend/src/commands/pageCommands.ts b/frontend/src/commands/pageCommands.ts deleted file mode 100644 index 92a9c9a73..000000000 --- a/frontend/src/commands/pageCommands.ts +++ /dev/null @@ -1,335 +0,0 @@ -import { Command, CommandSequence } from '../hooks/useUndoRedo'; -import { PDFDocument, PDFPage } from '../types/pageEditor'; - -// Base class for page operations -abstract class PageCommand implements Command { - protected pdfDocument: PDFDocument; - protected setPdfDocument: (doc: PDFDocument) => void; - protected previousState: PDFDocument; - - constructor( - pdfDocument: PDFDocument, - setPdfDocument: (doc: PDFDocument) => void - ) { - this.pdfDocument = pdfDocument; - this.setPdfDocument = setPdfDocument; - this.previousState = JSON.parse(JSON.stringify(pdfDocument)); // Deep clone - } - - abstract execute(): void; - abstract description: string; - - undo(): void { - this.setPdfDocument(this.previousState); - } -} - -// Rotate pages command -export class RotatePagesCommand extends PageCommand { - private pageIds: string[]; - private rotation: number; - - constructor( - pdfDocument: PDFDocument, - setPdfDocument: (doc: PDFDocument) => void, - pageIds: string[], - rotation: number - ) { - super(pdfDocument, setPdfDocument); - this.pageIds = pageIds; - this.rotation = rotation; - } - - execute(): void { - const updatedPages = this.pdfDocument.pages.map(page => { - if (this.pageIds.includes(page.id)) { - return { ...page, rotation: page.rotation + this.rotation }; - } - return page; - }); - - this.setPdfDocument({ - ...this.pdfDocument, - pages: updatedPages, - totalPages: updatedPages.length - }); - } - - get description(): string { - const direction = this.rotation > 0 ? 'right' : 'left'; - return `Rotate ${this.pageIds.length} page(s) ${direction}`; - } -} - -// Delete pages command -export class DeletePagesCommand extends PageCommand { - private pageIds: string[]; - private deletedPages: PDFPage[]; - private deletedPositions: Map; - - constructor( - pdfDocument: PDFDocument, - setPdfDocument: (doc: PDFDocument) => void, - pageIds: string[] - ) { - super(pdfDocument, setPdfDocument); - this.pageIds = pageIds; - this.deletedPages = []; - this.deletedPositions = new Map(); - } - - execute(): void { - // Store deleted pages and their positions for undo - this.deletedPages = this.pdfDocument.pages.filter(page => - this.pageIds.includes(page.id) - ); - - this.deletedPages.forEach(page => { - const index = this.pdfDocument.pages.findIndex(p => p.id === page.id); - this.deletedPositions.set(page.id, index); - }); - - const updatedPages = this.pdfDocument.pages - .filter(page => !this.pageIds.includes(page.id)) - .map((page, index) => ({ ...page, pageNumber: index + 1 })); - - this.setPdfDocument({ - ...this.pdfDocument, - pages: updatedPages, - totalPages: updatedPages.length - }); - } - - undo(): void { - // Simply restore to the previous state (before deletion) - this.setPdfDocument(this.previousState); - } - - get description(): string { - return `Delete ${this.pageIds.length} page(s)`; - } -} - -// Move pages command -export class MovePagesCommand extends PageCommand { - private pageIds: string[]; - private targetIndex: number; - private originalIndices: Map; - - constructor( - pdfDocument: PDFDocument, - setPdfDocument: (doc: PDFDocument) => void, - pageIds: string[], - targetIndex: number - ) { - super(pdfDocument, setPdfDocument); - this.pageIds = pageIds; - this.targetIndex = targetIndex; - this.originalIndices = new Map(); - } - - execute(): void { - // Store original positions - this.pageIds.forEach(pageId => { - const index = this.pdfDocument.pages.findIndex(p => p.id === pageId); - this.originalIndices.set(pageId, index); - }); - - let newPages = [...this.pdfDocument.pages]; - const pagesToMove = this.pageIds - .map(id => this.pdfDocument.pages.find(p => p.id === id)) - .filter((page): page is PDFPage => page !== undefined); - - // Remove pages to move - newPages = newPages.filter(page => !this.pageIds.includes(page.id)); - - // Insert pages at target position - newPages.splice(this.targetIndex, 0, ...pagesToMove); - - // Update page numbers - newPages = newPages.map((page, index) => ({ - ...page, - pageNumber: index + 1 - })); - - this.setPdfDocument({ - ...this.pdfDocument, - pages: newPages, - totalPages: newPages.length - }); - } - - get description(): string { - return `Move ${this.pageIds.length} page(s)`; - } -} - -// Reorder single page command (for drag-and-drop) -export class ReorderPageCommand extends PageCommand { - private pageId: string; - private targetIndex: number; - private originalIndex: number; - - constructor( - pdfDocument: PDFDocument, - setPdfDocument: (doc: PDFDocument) => void, - pageId: string, - targetIndex: number - ) { - super(pdfDocument, setPdfDocument); - this.pageId = pageId; - this.targetIndex = targetIndex; - this.originalIndex = pdfDocument.pages.findIndex(p => p.id === pageId); - } - - execute(): void { - const newPages = [...this.pdfDocument.pages]; - const [movedPage] = newPages.splice(this.originalIndex, 1); - newPages.splice(this.targetIndex, 0, movedPage); - - // Update page numbers - const updatedPages = newPages.map((page, index) => ({ - ...page, - pageNumber: index + 1 - })); - - this.setPdfDocument({ - ...this.pdfDocument, - pages: updatedPages, - totalPages: updatedPages.length - }); - } - - get description(): string { - return `Reorder page ${this.originalIndex + 1} to position ${this.targetIndex + 1}`; - } -} - -// Toggle split markers command -export class ToggleSplitCommand extends PageCommand { - private pageIds: string[]; - private previousSplitStates: Map; - - constructor( - pdfDocument: PDFDocument, - setPdfDocument: (doc: PDFDocument) => void, - pageIds: string[] - ) { - super(pdfDocument, setPdfDocument); - this.pageIds = pageIds; - this.previousSplitStates = new Map(); - } - - execute(): void { - // Store previous split states - this.pageIds.forEach(pageId => { - const page = this.pdfDocument.pages.find(p => p.id === pageId); - if (page) { - this.previousSplitStates.set(pageId, !!page.splitBefore); - } - }); - - const updatedPages = this.pdfDocument.pages.map(page => { - if (this.pageIds.includes(page.id)) { - return { ...page, splitBefore: !page.splitBefore }; - } - return page; - }); - - this.setPdfDocument({ - ...this.pdfDocument, - pages: updatedPages, - totalPages: updatedPages.length - }); - } - - undo(): void { - const updatedPages = this.pdfDocument.pages.map(page => { - if (this.pageIds.includes(page.id)) { - const previousState = this.previousSplitStates.get(page.id); - return { ...page, splitBefore: previousState }; - } - return page; - }); - - this.setPdfDocument({ - ...this.pdfDocument, - pages: updatedPages, - totalPages: updatedPages.length - }); - } - - get description(): string { - return `Toggle split markers for ${this.pageIds.length} page(s)`; - } -} - -// Add pages command (for inserting new files) -export class AddPagesCommand extends PageCommand { - private newPages: PDFPage[]; - private insertIndex: number; - - constructor( - pdfDocument: PDFDocument, - setPdfDocument: (doc: PDFDocument) => void, - newPages: PDFPage[], - insertIndex: number = -1 // -1 means append to end - ) { - super(pdfDocument, setPdfDocument); - this.newPages = newPages; - this.insertIndex = insertIndex === -1 ? pdfDocument.pages.length : insertIndex; - } - - execute(): void { - const newPagesArray = [...this.pdfDocument.pages]; - newPagesArray.splice(this.insertIndex, 0, ...this.newPages); - - // Update page numbers for all pages - const updatedPages = newPagesArray.map((page, index) => ({ - ...page, - pageNumber: index + 1 - })); - - this.setPdfDocument({ - ...this.pdfDocument, - pages: updatedPages, - totalPages: updatedPages.length - }); - } - - undo(): void { - const updatedPages = this.pdfDocument.pages - .filter(page => !this.newPages.some(newPage => newPage.id === page.id)) - .map((page, index) => ({ ...page, pageNumber: index + 1 })); - - this.setPdfDocument({ - ...this.pdfDocument, - pages: updatedPages, - totalPages: updatedPages.length - }); - } - - get description(): string { - return `Add ${this.newPages.length} page(s)`; - } -} - -// Command sequence for bulk operations -export class PageCommandSequence implements CommandSequence { - commands: Command[]; - description: string; - - constructor(commands: Command[], description?: string) { - this.commands = commands; - this.description = description || `Execute ${commands.length} operations`; - } - - execute(): void { - this.commands.forEach(command => command.execute()); - } - - undo(): void { - // Undo in reverse order - [...this.commands].reverse().forEach(command => command.undo()); - } -} \ No newline at end of file diff --git a/frontend/src/components/pageEditor/DragDropGrid.tsx b/frontend/src/components/pageEditor/DragDropGrid.tsx index 5829d0375..995778129 100644 --- a/frontend/src/components/pageEditor/DragDropGrid.tsx +++ b/frontend/src/components/pageEditor/DragDropGrid.tsx @@ -6,7 +6,7 @@ import styles from './PageEditor.module.css'; interface DragDropItem { id: string; - splitBefore?: boolean; + splitAfter?: boolean; } interface DragDropGridProps { @@ -128,14 +128,13 @@ const DragDropGrid = ({ justifyContent: 'flex-start', height: '100%', alignItems: 'center', + position: 'relative' }} > {rowItems.map((item, itemIndex) => { const actualIndex = startIndex + itemIndex; return ( - {/* Split marker */} - {renderSplitMarker && item.splitBefore && actualIndex > 0 && renderSplitMarker(item, actualIndex)} {/* Item */} {renderItem(item, actualIndex, itemRefs)} diff --git a/frontend/src/components/pageEditor/PageEditor.tsx b/frontend/src/components/pageEditor/PageEditor.tsx index ea4f82e0b..abb578458 100644 --- a/frontend/src/components/pageEditor/PageEditor.tsx +++ b/frontend/src/components/pageEditor/PageEditor.tsx @@ -365,9 +365,16 @@ const PageEditor = ({ const [isAnimating, setIsAnimating] = useState(false); const [csvInput, setCsvInput] = useState(''); + // Position-based split tracking (replaces page-based splitAfter) + const [splitPositions, setSplitPositions] = useState>(new Set()); + + // Grid container ref for positioning split indicators + const gridContainerRef = useRef(null); + // Export state const [exportLoading, setExportLoading] = useState(false); + // DOM-first command handlers const handleRotatePages = useCallback((pageIds: string[], rotation: number) => { pageIds.forEach(pageId => { @@ -445,30 +452,22 @@ const PageEditor = ({ } class ToggleSplitCommand { - constructor(public pageIds: string[]) {} + constructor(public position: number) {} execute() { if (!displayDocument) return; - console.log('Toggle split:', this.pageIds); + console.log('Toggle split at position:', this.position); - // Create new pages array with toggled split markers - const newPages = displayDocument.pages.map(page => { - if (this.pageIds.includes(page.id)) { - return { - ...page, - splitAfter: !page.splitAfter - }; + // Toggle the split position in the splitPositions set + setSplitPositions(prev => { + const newPositions = new Set(prev); + if (newPositions.has(this.position)) { + newPositions.delete(this.position); + } else { + newPositions.add(this.position); } - return page; + return newPositions; }); - - // Update the document with new split markers - const updatedDocument: PDFDocument = { - ...displayDocument, - pages: newPages, - }; - - setEditedDocument(updatedDocument); } } @@ -482,6 +481,7 @@ const PageEditor = ({ // Interface functions for parent component const displayDocument = editedDocument || mergedPdfDocument; + const handleUndo = useCallback(() => { undoManagerRef.current.undo(); }, []); @@ -510,18 +510,17 @@ const PageEditor = ({ const handleSplit = useCallback(() => { if (!displayDocument || selectedPageNumbers.length === 0) return; - console.log('Toggle split markers at selected pages:', selectedPageNumbers); + console.log('Toggle split markers at selected page positions:', selectedPageNumbers); - // Get page IDs for selected pages - const selectedPageIds = selectedPageNumbers.map(pageNum => { - const page = displayDocument.pages.find(p => p.pageNumber === pageNum); - return page?.id || ''; - }).filter(id => id); - - if (selectedPageIds.length > 0) { - const command = new ToggleSplitCommand(selectedPageIds); - command.execute(); - } + // Convert page numbers to positions (0-based indices) + selectedPageNumbers.forEach(pageNum => { + const pageIndex = displayDocument.pages.findIndex(p => p.pageNumber === pageNum); + if (pageIndex !== -1 && pageIndex < displayDocument.pages.length - 1) { + // Only allow splits before the last page + const command = new ToggleSplitCommand(pageIndex); + command.execute(); + } + }); }, [selectedPageNumbers, displayDocument]); const handleReorderPages = useCallback((sourcePageNumber: number, targetIndex: number, selectedPages?: number[]) => { @@ -592,7 +591,8 @@ const PageEditor = ({ console.log('Applying DOM changes before export...'); const processedDocuments = documentManipulationService.applyDOMChangesToDocument( mergedPdfDocument || displayDocument, // Original order - displayDocument // Current display order (includes reordering) + displayDocument, // Current display order (includes reordering) + splitPositions // Position-based splits ); // For selected pages export, we work with the first document (or single document) @@ -620,7 +620,7 @@ const PageEditor = ({ console.error('Export failed:', error); setExportLoading(false); } - }, [displayDocument, selectedPageNumbers, mergedPdfDocument]); + }, [displayDocument, selectedPageNumbers, mergedPdfDocument, splitPositions]); const onExportAll = useCallback(async () => { if (!displayDocument) return; @@ -631,7 +631,8 @@ const PageEditor = ({ console.log('Applying DOM changes before export...'); const processedDocuments = documentManipulationService.applyDOMChangesToDocument( mergedPdfDocument || displayDocument, // Original order - displayDocument // Current display order (includes reordering) + displayDocument, // Current display order (includes reordering) + splitPositions // Position-based splits ); // Step 2: Check if we have multiple documents (splits) or single document @@ -676,7 +677,7 @@ const PageEditor = ({ console.error('Export failed:', error); setExportLoading(false); } - }, [displayDocument, mergedPdfDocument]); + }, [displayDocument, mergedPdfDocument, splitPositions]); // Apply DOM changes to document state using dedicated service const applyChanges = useCallback(() => { @@ -685,7 +686,8 @@ const PageEditor = ({ // Pass current display document (which includes reordering) to get both reordering AND DOM changes const processedDocuments = documentManipulationService.applyDOMChangesToDocument( mergedPdfDocument || displayDocument, // Original order - displayDocument // Current display order (includes reordering) + displayDocument, // Current display order (includes reordering) + splitPositions // Position-based splits ); // For apply changes, we only set the first document if it's an array (splits shouldn't affect document state) @@ -693,7 +695,7 @@ const PageEditor = ({ setEditedDocument(documentToSet); console.log('Changes applied to document'); - }, [displayDocument, mergedPdfDocument]); + }, [displayDocument, mergedPdfDocument, splitPositions]); const closePdf = useCallback(() => { @@ -767,7 +769,7 @@ const PageEditor = ({ )} {displayDocument && ( - + {/* File name and basic controls */} )} + {/* Split Lines Overlay */} +
+ {Array.from(splitPositions).map((position) => { + // Calculate the split line position based on grid layout + const ITEM_WIDTH = 320; // 20rem + const ITEM_HEIGHT = 340; // 20rem + gap + const ITEM_GAP = 24; // 1.5rem + const ITEMS_PER_ROW = 4; // Default, could be dynamic + + const row = Math.floor(position / ITEMS_PER_ROW); + const col = position % ITEMS_PER_ROW; + + // Position after the current item + const leftPosition = (col + 1) * (ITEM_WIDTH + ITEM_GAP) - ITEM_GAP / 2; + const topPosition = row * ITEM_HEIGHT + 100; // Offset for header controls + + return ( +
+ ); + })} +
+ {/* Pages Grid */} )} /> + )} + ); diff --git a/frontend/src/components/pageEditor/PageThumbnail.tsx b/frontend/src/components/pageEditor/PageThumbnail.tsx index 1ad353dbf..06657e9a6 100644 --- a/frontend/src/components/pageEditor/PageThumbnail.tsx +++ b/frontend/src/components/pageEditor/PageThumbnail.tsx @@ -39,6 +39,7 @@ interface PageThumbnailProps { ToggleSplitCommand: any; pdfDocument: PDFDocument; setPdfDocument: (doc: PDFDocument) => void; + splitPositions: Set; } const PageThumbnail: React.FC = ({ @@ -62,6 +63,7 @@ const PageThumbnail: React.FC = ({ ToggleSplitCommand, pdfDocument, setPdfDocument, + splitPositions, }: PageThumbnailProps) => { const [thumbnailUrl, setThumbnailUrl] = useState(page.thumbnail); const [isDragging, setIsDragging] = useState(false); @@ -195,13 +197,14 @@ const PageThumbnail: React.FC = ({ const handleSplit = useCallback((e: React.MouseEvent) => { e.stopPropagation(); - // Create a command to toggle split marker - const command = new ToggleSplitCommand([page.id]); + // Create a command to toggle split at this position + const command = new ToggleSplitCommand(index); onExecuteCommand(command); - const action = page.splitAfter ? 'removed' : 'added'; - onSetStatus(`Split marker ${action} after page ${page.pageNumber}`); - }, [page.pageNumber, page.id, page.splitAfter, onExecuteCommand, onSetStatus, ToggleSplitCommand]); + const hasSplit = splitPositions.has(index); + const action = hasSplit ? 'removed' : 'added'; + onSetStatus(`Split marker ${action} after position ${index + 1}`); + }, [index, splitPositions, onExecuteCommand, onSetStatus, ToggleSplitCommand]); return (
= ({
- {/* Split indicator - shows where document will be split */} - {page.splitAfter && ( -
- )}
); }; diff --git a/frontend/src/services/documentManipulationService.ts b/frontend/src/services/documentManipulationService.ts index 166e0cbe3..9d3a64be6 100644 --- a/frontend/src/services/documentManipulationService.ts +++ b/frontend/src/services/documentManipulationService.ts @@ -9,17 +9,26 @@ export class DocumentManipulationService { * Apply all DOM changes (rotations, splits, reordering) to document state * Returns single document or multiple documents if splits are present */ - applyDOMChangesToDocument(pdfDocument: PDFDocument, currentDisplayOrder?: PDFDocument): PDFDocument | PDFDocument[] { + applyDOMChangesToDocument(pdfDocument: PDFDocument, currentDisplayOrder?: PDFDocument, splitPositions?: Set): 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, split markers) - const updatedPages = baseDocument.pages.map(page => this.applyPageChanges(page)); + // Apply DOM changes to each page (rotation only now, splits are position-based) + let updatedPages = baseDocument.pages.map(page => this.applyPageChanges(page)); + + // Convert position-based splits to page-based splits for export + if (splitPositions && splitPositions.size > 0) { + updatedPages = updatedPages.map((page, index) => ({ + ...page, + splitAfter: splitPositions.has(index) + })); + } // Create final document with reordered pages and applied changes const finalDocument = { @@ -28,7 +37,7 @@ export class DocumentManipulationService { }; // Check for splits and return multiple documents if needed - if (this.hasSplitMarkers(finalDocument)) { + if (splitPositions && splitPositions.size > 0) { return this.createSplitDocuments(finalDocument); }