From e7995a02560cf750764a4e80d488748711b14485 Mon Sep 17 00:00:00 2001 From: Reece Date: Mon, 16 Jun 2025 19:57:50 +0100 Subject: [PATCH] Page editor updates --- frontend/src/components/PageEditor.tsx | 236 +++++++++++++++---------- 1 file changed, 140 insertions(+), 96 deletions(-) diff --git a/frontend/src/components/PageEditor.tsx b/frontend/src/components/PageEditor.tsx index a34b088f1..de00d6b00 100644 --- a/frontend/src/components/PageEditor.tsx +++ b/frontend/src/components/PageEditor.tsx @@ -20,6 +20,8 @@ import ConstructionIcon from "@mui/icons-material/Construction"; import EventListIcon from "@mui/icons-material/EventList"; import DeselectIcon from "@mui/icons-material/Deselect"; import SelectAllIcon from "@mui/icons-material/SelectAll"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; +import ArrowForwardIcon from "@mui/icons-material/ArrowForward"; import { usePDFProcessor } from "../hooks/usePDFProcessor"; import { PDFDocument, PDFPage } from "../types/pageEditor"; import { fileStorage } from "../services/fileStorage"; @@ -62,6 +64,7 @@ const PageEditor: React.FC = ({ const [exportLoading, setExportLoading] = useState(false); const [showExportModal, setShowExportModal] = useState(false); const [exportPreview, setExportPreview] = useState<{pageCount: number; splitCount: number; estimatedSize: string} | null>(null); + const [movingPage, setMovingPage] = useState(null); const fileInputRef = useRef<() => void>(null); // Undo/Redo system @@ -163,13 +166,13 @@ const PageEditor: React.FC = ({ const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); - + if (!draggedPage) return; - + // Get the element under the mouse cursor const elementUnderCursor = document.elementFromPoint(e.clientX, e.clientY); if (!elementUnderCursor) return; - + // Find the closest page container const pageContainer = elementUnderCursor.closest('[data-page-id]'); if (pageContainer) { @@ -179,14 +182,14 @@ const PageEditor: React.FC = ({ return; } } - + // Check if over the end zone const endZone = elementUnderCursor.closest('[data-drop-zone="end"]'); if (endZone) { setDropTarget('end'); return; } - + // If not over any valid drop target, clear it setDropTarget(null); }, [draggedPage]); @@ -345,33 +348,28 @@ const PageEditor: React.FC = ({ - - {error && ( - setError(null)}> - {error} - - )} + - files[0] && handleFileUpload(files[0])} - accept={["application/pdf"]} - multiple={false} - h="60vh" - style={{ minHeight: 400 }} - > -
- - - - Drop a PDF file here or click to upload - - - Supports PDF files only - - -
-
-
+ files[0] && handleFileUpload(files[0])} + accept={["application/pdf"]} + multiple={false} + h="60vh" + style={{ minHeight: 400 }} + > +
+ + + + Drop a PDF file here or click to upload + + + Supports PDF files only + + +
+
+
); } @@ -392,6 +390,14 @@ const PageEditor: React.FC = ({ .page-container:hover { transform: scale(1.02); } + .page-move-animation { + transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); + } + .page-moving { + z-index: 10; + transform: scale(1.05); + box-shadow: 0 10px 30px rgba(0,0,0,0.3); + } @keyframes pulse { 0%, 100% { opacity: 1; @@ -489,30 +495,15 @@ const PageEditor: React.FC = ({ {page.splitBefore && index > 0 && (
- -
+ /> )}
= ({ !rounded-lg cursor-grab select-none - w-[15rem] - h-[15rem] + w-[20rem] + h-[20rem] flex items-center justify-center flex-shrink-0 shadow-sm hover:shadow-md transition-all relative + page-move-animation ${selectedPages.includes(page.id) ? 'ring-2 ring-blue-500 bg-blue-50' : 'bg-white hover:bg-gray-50'} ${draggedPage === page.id ? 'opacity-50 scale-95' : ''} + ${movingPage === page.id ? 'page-moving' : ''} `} style={{ transform: (() => { if (!draggedPage || page.id === draggedPage) return 'translateX(0)'; - + if (dropTarget === page.id) { return 'translateX(20px)'; // Move slightly right to indicate drop position } @@ -606,6 +599,62 @@ const PageEditor: React.FC = ({ whiteSpace: 'nowrap' }} > + + { + e.stopPropagation(); + if (index > 0 && !movingPage) { + setMovingPage(page.id); + setTimeout(() => { + const command = new ReorderPageCommand( + pdfDocument, + setPdfDocument, + page.id, + index - 1 + ); + executeCommand(command); + setTimeout(() => setMovingPage(null), 100); + setStatus(`Moved page ${page.pageNumber} left`); + }, 50); + } + }} + > + + + + + + { + e.stopPropagation(); + if (index < pdfDocument.pages.length - 1 && !movingPage) { + setMovingPage(page.id); + setTimeout(() => { + const command = new ReorderPageCommand( + pdfDocument, + setPdfDocument, + page.id, + index + 1 + ); + executeCommand(command); + setTimeout(() => setMovingPage(null), 100); + setStatus(`Moved page ${page.pageNumber} right`); + }, 50); + } + }} + > + + + + = ({ - - { - e.stopPropagation(); - const command = new ToggleSplitCommand( - pdfDocument, - setPdfDocument, - [page.id] - ); - executeCommand(command); - setStatus(`Split marker toggled for page ${page.pageNumber}`); - }} - > - - - + {index > 0 && ( + + { + e.stopPropagation(); + const command = new ToggleSplitCommand( + pdfDocument, + setPdfDocument, + [page.id] + ); + executeCommand(command); + setStatus(`Split marker toggled for page ${page.pageNumber}`); + }} + > + + + + )} = ({
))} - + {/* Landing zone at the end */} -
handleDrop(e, 'end')} - > - - Drop here to
move to end -
+
+
handleDrop(e, 'end')} + > + + Drop here to
move to end +
+