diff --git a/src/App.tsx b/src/App.tsx index f40362d..779fd17 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -695,6 +695,12 @@ export function App() { setShowPrivacy(true); }, []); + /** Scroll to top whenever the view changes. */ + // eslint-disable-next-line react-hooks/exhaustive-deps -- activeTool and showPrivacy are intentional trigger deps + useEffect(() => { + window.scrollTo(0, 0); + }, [activeTool, showPrivacy]); + /** Metadata for the active tool (memoised to avoid redundant lookups). */ const activeMeta = useMemo( () => (activeTool ? (tools.find((t) => t.id === activeTool) ?? null) : null), diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index e43e5d6..1ec0756 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -102,7 +102,13 @@ export function Layout({ children, onHome, showBack, onPrivacy, badgeAccent }: L
diff --git a/src/tools/DuplicatePage.tsx b/src/tools/DuplicatePage.tsx index 993f5ce..79066b7 100644 --- a/src/tools/DuplicatePage.tsx +++ b/src/tools/DuplicatePage.tsx @@ -8,6 +8,7 @@ import { useCallback, useState } from "react"; import { FileDropZone } from "../components/FileDropZone.tsx"; +import { SortableGrid } from "../components/SortableGrid.tsx"; import { categoryAccent, categoryGlow } from "../config/theme.ts"; import { downloadPdf, formatFileSize } from "../utils/file-helpers.ts"; import { useSortableDrag } from "../hooks/useSortableDrag.ts"; @@ -43,8 +44,7 @@ export default function DuplicatePage() { }, []); // Drag state (desktop + mobile touch) - const { dragIndex, dragOverSlot, setDragIndex, setDragOverSlot, getTouchHandlers } = - useSortableDrag(handleMove); + const drag = useSortableDrag(handleMove); const handleFile = useCallback(async (files: File[]) => { const pdf = files[0]; @@ -69,7 +69,7 @@ export default function DuplicatePage() { }, []); const hasCopies = items.some((it) => it.type === "copy"); - const isDragging = dragIndex !== null; + const isDragging = drag.dragIndex !== null; const handleDuplicatePage = useCallback((pageIndex: number, afterSlot: number) => { setItems((prev) => { @@ -81,9 +81,9 @@ export default function DuplicatePage() { const handleReset = useCallback(() => { setItems((prev) => prev.filter((it) => it.type === "original")); - setDragIndex(null); - setDragOverSlot(null); - }, [setDragIndex, setDragOverSlot]); + drag.setDragIndex(null); + drag.setDragOverSlot(null); + }, [drag]); const handleApply = useCallback(async () => { if (!file || !hasCopies) return; @@ -110,170 +110,6 @@ export default function DuplicatePage() { } }, [file, hasCopies, items]); - const renderItems = () => { - const elements: React.ReactNode[] = []; - - for (let slot = 0; slot <= items.length; slot++) { - // ── Drop zone ── - const isAdjacentToDrag = dragIndex !== null && (slot === dragIndex || slot === dragIndex + 1); - - elements.push( -
diff --git a/src/tools/ReorderPages.tsx b/src/tools/ReorderPages.tsx index 1fb0949..eb40a62 100644 --- a/src/tools/ReorderPages.tsx +++ b/src/tools/ReorderPages.tsx @@ -10,6 +10,7 @@ import { useState, useCallback } from "react"; import { FileDropZone } from "../components/FileDropZone.tsx"; +import { SortableGrid } from "../components/SortableGrid.tsx"; import { categoryAccent, categoryGlow } from "../config/theme.ts"; import { reorderPages } from "../utils/pdf-operations.ts"; import { renderAllThumbnails } from "../utils/pdf-renderer.ts"; @@ -37,8 +38,7 @@ export default function ReorderPages() { }, []); // Drag state (desktop + mobile touch) - const { dragIndex, dragOverSlot, setDragIndex, setDragOverSlot, getTouchHandlers } = - useSortableDrag(handleMove); + const drag = useSortableDrag(handleMove); const handleFile = useCallback(async (files: File[]) => { const pdf = files[0]; @@ -79,133 +79,12 @@ export default function ReorderPages() { const handleReset = useCallback(() => { setOrder(thumbnails.map((_, i) => i)); - setDragIndex(null); - setDragOverSlot(null); - }, [thumbnails, setDragIndex, setDragOverSlot]); + drag.setDragIndex(null); + drag.setDragOverSlot(null); + }, [thumbnails, drag]); const isReordered = order.some((pageIdx, i) => pageIdx !== i); - const isDragging = dragIndex !== null; - - /** - * Build the interleaved layout: [drop_0] [page_0] [drop_1] [page_1] ... [page_N-1] [drop_N] - * - * When dragging, the drop-zone gaps expand and show a vertical bar indicator - * on hover—exactly like AddBlankPage. The source page card gets a highlighted - * ring to show it's selected. Dropping on a gap moves the page to that slot. - */ - const renderItems = () => { - const items: React.ReactNode[] = []; - - for (let slot = 0; slot <= order.length; slot++) { - // ── Drop zone ── - // Don't show drop zones immediately adjacent to the dragged card - // (dropping there would be a no-op). - const isAdjacentToDrag = dragIndex !== null && (slot === dragIndex || slot === dragIndex + 1); - - items.push( -