mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-28 07:09:23 +00:00
Feature/v2/page editor selection persistance (#4306)
Fixed page editor selection persistance Fixed delete sleected Fixed display issue on some tools on reset settings call --------- Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
This commit is contained in:
parent
47ccb6a6ed
commit
68d59fd377
@ -117,7 +117,8 @@ export default function Workbench() {
|
|||||||
onExportAll={pageEditorFunctions.onExportAll}
|
onExportAll={pageEditorFunctions.onExportAll}
|
||||||
exportLoading={pageEditorFunctions.exportLoading}
|
exportLoading={pageEditorFunctions.exportLoading}
|
||||||
selectionMode={pageEditorFunctions.selectionMode}
|
selectionMode={pageEditorFunctions.selectionMode}
|
||||||
selectedPages={pageEditorFunctions.selectedPages}
|
selectedPageIds={pageEditorFunctions.selectedPageIds}
|
||||||
|
displayDocument={pageEditorFunctions.displayDocument}
|
||||||
splitPositions={pageEditorFunctions.splitPositions}
|
splitPositions={pageEditorFunctions.splitPositions}
|
||||||
totalPages={pageEditorFunctions.totalPages}
|
totalPages={pageEditorFunctions.totalPages}
|
||||||
/>
|
/>
|
||||||
|
@ -4,14 +4,16 @@ import { Group, TextInput, Button, Text } from '@mantine/core';
|
|||||||
interface BulkSelectionPanelProps {
|
interface BulkSelectionPanelProps {
|
||||||
csvInput: string;
|
csvInput: string;
|
||||||
setCsvInput: (value: string) => void;
|
setCsvInput: (value: string) => void;
|
||||||
selectedPages: number[];
|
selectedPageIds: string[];
|
||||||
|
displayDocument?: { pages: { id: string; pageNumber: number }[] };
|
||||||
onUpdatePagesFromCSV: () => void;
|
onUpdatePagesFromCSV: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BulkSelectionPanel = ({
|
const BulkSelectionPanel = ({
|
||||||
csvInput,
|
csvInput,
|
||||||
setCsvInput,
|
setCsvInput,
|
||||||
selectedPages,
|
selectedPageIds,
|
||||||
|
displayDocument,
|
||||||
onUpdatePagesFromCSV,
|
onUpdatePagesFromCSV,
|
||||||
}: BulkSelectionPanelProps) => {
|
}: BulkSelectionPanelProps) => {
|
||||||
return (
|
return (
|
||||||
@ -30,9 +32,12 @@ const BulkSelectionPanel = ({
|
|||||||
Apply
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
{selectedPages.length > 0 && (
|
{selectedPageIds.length > 0 && (
|
||||||
<Text size="sm" c="dimmed" mt="sm">
|
<Text size="sm" c="dimmed" mt="sm">
|
||||||
Selected: {selectedPages.length} pages
|
Selected: {selectedPageIds.length} pages ({displayDocument ? selectedPageIds.map(id => {
|
||||||
|
const page = displayDocument.pages.find(p => p.id === id);
|
||||||
|
return page?.pageNumber || 0;
|
||||||
|
}).filter(n => n > 0).join(', ') : ''})
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -12,10 +12,10 @@ interface DragDropItem {
|
|||||||
|
|
||||||
interface DragDropGridProps<T extends DragDropItem> {
|
interface DragDropGridProps<T extends DragDropItem> {
|
||||||
items: T[];
|
items: T[];
|
||||||
selectedItems: number[];
|
selectedItems: string[];
|
||||||
selectionMode: boolean;
|
selectionMode: boolean;
|
||||||
isAnimating: boolean;
|
isAnimating: boolean;
|
||||||
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPages?: number[]) => void;
|
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => void;
|
||||||
renderItem: (item: T, index: number, refs: React.MutableRefObject<Map<string, HTMLDivElement>>) => React.ReactNode;
|
renderItem: (item: T, index: number, refs: React.MutableRefObject<Map<string, HTMLDivElement>>) => React.ReactNode;
|
||||||
renderSplitMarker?: (item: T, index: number) => React.ReactNode;
|
renderSplitMarker?: (item: T, index: number) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useFileState, useFileActions, useCurrentFile, useFileSelection } from "../../contexts/FileContext";
|
import { useFileState, useFileActions, useCurrentFile, useFileSelection } from "../../contexts/FileContext";
|
||||||
import { ModeType } from "../../contexts/NavigationContext";
|
import { ModeType } from "../../contexts/NavigationContext";
|
||||||
import { PDFDocument, PDFPage } from "../../types/pageEditor";
|
import { PDFDocument, PDFPage, PageEditorFunctions } from "../../types/pageEditor";
|
||||||
import { ProcessedFile as EnhancedProcessedFile } from "../../types/processing";
|
import { ProcessedFile as EnhancedProcessedFile } from "../../types/processing";
|
||||||
import { pdfExportService } from "../../services/pdfExportService";
|
import { pdfExportService } from "../../services/pdfExportService";
|
||||||
import { documentManipulationService } from "../../services/documentManipulationService";
|
import { documentManipulationService } from "../../services/documentManipulationService";
|
||||||
@ -36,31 +36,7 @@ import { usePageDocument } from './hooks/usePageDocument';
|
|||||||
import { usePageEditorState } from './hooks/usePageEditorState';
|
import { usePageEditorState } from './hooks/usePageEditorState';
|
||||||
|
|
||||||
export interface PageEditorProps {
|
export interface PageEditorProps {
|
||||||
onFunctionsReady?: (functions: {
|
onFunctionsReady?: (functions: PageEditorFunctions) => void;
|
||||||
handleUndo: () => void;
|
|
||||||
handleRedo: () => void;
|
|
||||||
canUndo: boolean;
|
|
||||||
canRedo: boolean;
|
|
||||||
handleRotate: (direction: 'left' | 'right') => void;
|
|
||||||
handleDelete: () => void;
|
|
||||||
handleSplit: () => void;
|
|
||||||
handleSplitAll: () => void;
|
|
||||||
handlePageBreak: () => void;
|
|
||||||
handlePageBreakAll: () => void;
|
|
||||||
handleSelectAll: () => void;
|
|
||||||
handleDeselectAll: () => void;
|
|
||||||
handleSetSelectedPages: (pageNumbers: number[]) => void;
|
|
||||||
showExportPreview: (selectedOnly: boolean) => void;
|
|
||||||
onExportSelected: () => void;
|
|
||||||
onExportAll: () => void;
|
|
||||||
applyChanges: () => void;
|
|
||||||
exportLoading: boolean;
|
|
||||||
selectionMode: boolean;
|
|
||||||
selectedPages: number[];
|
|
||||||
splitPositions: Set<number>;
|
|
||||||
totalPages: number;
|
|
||||||
closePdf: () => void;
|
|
||||||
}) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PageEditor = ({
|
const PageEditor = ({
|
||||||
@ -100,8 +76,8 @@ const PageEditor = ({
|
|||||||
|
|
||||||
// UI state management
|
// UI state management
|
||||||
const {
|
const {
|
||||||
selectionMode, selectedPageNumbers, movingPage, isAnimating, splitPositions, exportLoading,
|
selectionMode, selectedPageIds, movingPage, isAnimating, splitPositions, exportLoading,
|
||||||
setSelectionMode, setSelectedPageNumbers, setMovingPage, setIsAnimating, setSplitPositions, setExportLoading,
|
setSelectionMode, setSelectedPageIds, setMovingPage, setIsAnimating, setSplitPositions, setExportLoading,
|
||||||
togglePage, toggleSelectAll, animateReorder
|
togglePage, toggleSelectAll, animateReorder
|
||||||
} = usePageEditorState();
|
} = usePageEditorState();
|
||||||
|
|
||||||
@ -152,17 +128,40 @@ const PageEditor = ({
|
|||||||
|
|
||||||
// Interface functions for parent component
|
// Interface functions for parent component
|
||||||
const displayDocument = editedDocument || mergedPdfDocument;
|
const displayDocument = editedDocument || mergedPdfDocument;
|
||||||
|
|
||||||
|
// Utility functions to convert between page IDs and page numbers
|
||||||
|
const getPageNumbersFromIds = useCallback((pageIds: string[]): number[] => {
|
||||||
|
if (!displayDocument) return [];
|
||||||
|
return pageIds.map(id => {
|
||||||
|
const page = displayDocument.pages.find(p => p.id === id);
|
||||||
|
return page?.pageNumber || 0;
|
||||||
|
}).filter(num => num > 0);
|
||||||
|
}, [displayDocument]);
|
||||||
|
|
||||||
|
const getPageIdsFromNumbers = useCallback((pageNumbers: number[]): string[] => {
|
||||||
|
if (!displayDocument) return [];
|
||||||
|
return pageNumbers.map(num => {
|
||||||
|
const page = displayDocument.pages.find(p => p.pageNumber === num);
|
||||||
|
return page?.id || '';
|
||||||
|
}).filter(id => id !== '');
|
||||||
|
}, [displayDocument]);
|
||||||
|
|
||||||
|
// Convert selectedPageIds to numbers for components that still need numbers
|
||||||
|
const selectedPageNumbers = useMemo(() =>
|
||||||
|
getPageNumbersFromIds(selectedPageIds),
|
||||||
|
[selectedPageIds, getPageNumbersFromIds]
|
||||||
|
);
|
||||||
|
|
||||||
// Select all pages by default when document initially loads
|
// Select all pages by default when document initially loads
|
||||||
const hasInitializedSelection = useRef(false);
|
const hasInitializedSelection = useRef(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (displayDocument && displayDocument.pages.length > 0 && !hasInitializedSelection.current) {
|
if (displayDocument && displayDocument.pages.length > 0 && !hasInitializedSelection.current) {
|
||||||
const allPageNumbers = Array.from({ length: displayDocument.pages.length }, (_, i) => i + 1);
|
const allPageIds = displayDocument.pages.map(p => p.id);
|
||||||
setSelectedPageNumbers(allPageNumbers);
|
setSelectedPageIds(allPageIds);
|
||||||
setSelectionMode(true);
|
setSelectionMode(true);
|
||||||
hasInitializedSelection.current = true;
|
hasInitializedSelection.current = true;
|
||||||
}
|
}
|
||||||
}, [displayDocument, setSelectedPageNumbers, setSelectionMode]);
|
}, [displayDocument, setSelectedPageIds, setSelectionMode]);
|
||||||
|
|
||||||
// DOM-first command handlers
|
// DOM-first command handlers
|
||||||
const handleRotatePages = useCallback((pageIds: string[], rotation: number) => {
|
const handleRotatePages = useCallback((pageIds: string[], rotation: number) => {
|
||||||
@ -192,15 +191,18 @@ const PageEditor = ({
|
|||||||
pagesToDelete,
|
pagesToDelete,
|
||||||
() => displayDocument,
|
() => displayDocument,
|
||||||
setEditedDocument,
|
setEditedDocument,
|
||||||
setSelectedPageNumbers,
|
(pageNumbers: number[]) => {
|
||||||
|
const pageIds = getPageIdsFromNumbers(pageNumbers);
|
||||||
|
setSelectedPageIds(pageIds);
|
||||||
|
},
|
||||||
() => splitPositions,
|
() => splitPositions,
|
||||||
setSplitPositions,
|
setSplitPositions,
|
||||||
() => selectedPageNumbers
|
() => getPageNumbersFromIds(selectedPageIds)
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(deleteCommand);
|
undoManagerRef.current.executeCommand(deleteCommand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}), [displayDocument, splitPositions, selectedPageNumbers]);
|
}), [displayDocument, splitPositions, selectedPageIds, getPageNumbersFromIds]);
|
||||||
|
|
||||||
const createSplitCommand = useCallback((position: number) => ({
|
const createSplitCommand = useCallback((position: number) => ({
|
||||||
execute: () => {
|
execute: () => {
|
||||||
@ -230,30 +232,32 @@ const PageEditor = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleRotate = useCallback((direction: 'left' | 'right') => {
|
const handleRotate = useCallback((direction: 'left' | 'right') => {
|
||||||
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
if (!displayDocument || selectedPageIds.length === 0) return;
|
||||||
const rotation = direction === 'left' ? -90 : 90;
|
const rotation = direction === 'left' ? -90 : 90;
|
||||||
const pagesToRotate = selectedPageNumbers.map(pageNum => {
|
|
||||||
const page = displayDocument.pages.find(p => p.pageNumber === pageNum);
|
handleRotatePages(selectedPageIds, rotation);
|
||||||
return page?.id || '';
|
}, [displayDocument, selectedPageIds, handleRotatePages]);
|
||||||
}).filter(id => id);
|
|
||||||
|
|
||||||
handleRotatePages(pagesToRotate, rotation);
|
|
||||||
}, [displayDocument, selectedPageNumbers, handleRotatePages]);
|
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
if (!displayDocument || selectedPageIds.length === 0) return;
|
||||||
|
|
||||||
|
// Convert selected page IDs to page numbers for the command
|
||||||
|
const selectedPageNumbers = getPageNumbersFromIds(selectedPageIds);
|
||||||
|
|
||||||
const deleteCommand = new DeletePagesCommand(
|
const deleteCommand = new DeletePagesCommand(
|
||||||
selectedPageNumbers,
|
selectedPageNumbers,
|
||||||
() => displayDocument,
|
() => displayDocument,
|
||||||
setEditedDocument,
|
setEditedDocument,
|
||||||
setSelectedPageNumbers,
|
(pageNumbers: number[]) => {
|
||||||
|
const pageIds = getPageIdsFromNumbers(pageNumbers);
|
||||||
|
setSelectedPageIds(pageIds);
|
||||||
|
},
|
||||||
() => splitPositions,
|
() => splitPositions,
|
||||||
setSplitPositions,
|
setSplitPositions,
|
||||||
() => selectedPageNumbers
|
() => selectedPageNumbers
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(deleteCommand);
|
undoManagerRef.current.executeCommand(deleteCommand);
|
||||||
}, [selectedPageNumbers, displayDocument, splitPositions]);
|
}, [selectedPageIds, displayDocument, splitPositions, getPageNumbersFromIds, getPageIdsFromNumbers]);
|
||||||
|
|
||||||
const handleDeletePage = useCallback((pageNumber: number) => {
|
const handleDeletePage = useCallback((pageNumber: number) => {
|
||||||
if (!displayDocument) return;
|
if (!displayDocument) return;
|
||||||
@ -262,18 +266,22 @@ const PageEditor = ({
|
|||||||
[pageNumber],
|
[pageNumber],
|
||||||
() => displayDocument,
|
() => displayDocument,
|
||||||
setEditedDocument,
|
setEditedDocument,
|
||||||
setSelectedPageNumbers,
|
(pageNumbers: number[]) => {
|
||||||
|
const pageIds = getPageIdsFromNumbers(pageNumbers);
|
||||||
|
setSelectedPageIds(pageIds);
|
||||||
|
},
|
||||||
() => splitPositions,
|
() => splitPositions,
|
||||||
setSplitPositions,
|
setSplitPositions,
|
||||||
() => selectedPageNumbers
|
() => getPageNumbersFromIds(selectedPageIds)
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(deleteCommand);
|
undoManagerRef.current.executeCommand(deleteCommand);
|
||||||
}, [displayDocument, splitPositions, selectedPageNumbers]);
|
}, [displayDocument, splitPositions, selectedPageIds, getPageNumbersFromIds]);
|
||||||
|
|
||||||
const handleSplit = useCallback(() => {
|
const handleSplit = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
if (!displayDocument || selectedPageIds.length === 0) return;
|
||||||
|
|
||||||
// Convert selected page numbers to split positions (0-based indices)
|
// Convert selected page IDs to page numbers, then to split positions (0-based indices)
|
||||||
|
const selectedPageNumbers = getPageNumbersFromIds(selectedPageIds);
|
||||||
const selectedPositions: number[] = [];
|
const selectedPositions: number[] = [];
|
||||||
selectedPageNumbers.forEach(pageNum => {
|
selectedPageNumbers.forEach(pageNum => {
|
||||||
const pageIndex = displayDocument.pages.findIndex(p => p.pageNumber === pageNum);
|
const pageIndex = displayDocument.pages.findIndex(p => p.pageNumber === pageNum);
|
||||||
@ -314,12 +322,13 @@ const PageEditor = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
undoManagerRef.current.executeCommand(smartSplitCommand);
|
undoManagerRef.current.executeCommand(smartSplitCommand);
|
||||||
}, [selectedPageNumbers, displayDocument, splitPositions, setSplitPositions]);
|
}, [selectedPageIds, displayDocument, splitPositions, setSplitPositions, getPageNumbersFromIds]);
|
||||||
|
|
||||||
const handleSplitAll = useCallback(() => {
|
const handleSplitAll = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
if (!displayDocument || selectedPageIds.length === 0) return;
|
||||||
|
|
||||||
// Convert selected page numbers to split positions (0-based indices)
|
// Convert selected page IDs to page numbers, then to split positions (0-based indices)
|
||||||
|
const selectedPageNumbers = getPageNumbersFromIds(selectedPageIds);
|
||||||
const selectedPositions: number[] = [];
|
const selectedPositions: number[] = [];
|
||||||
selectedPageNumbers.forEach(pageNum => {
|
selectedPageNumbers.forEach(pageNum => {
|
||||||
const pageIndex = displayDocument.pages.findIndex(p => p.pageNumber === pageNum);
|
const pageIndex = displayDocument.pages.findIndex(p => p.pageNumber === pageNum);
|
||||||
@ -359,31 +368,35 @@ const PageEditor = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
undoManagerRef.current.executeCommand(smartSplitCommand);
|
undoManagerRef.current.executeCommand(smartSplitCommand);
|
||||||
}, [selectedPageNumbers, displayDocument, splitPositions, setSplitPositions]);
|
}, [selectedPageIds, displayDocument, splitPositions, setSplitPositions, getPageNumbersFromIds]);
|
||||||
|
|
||||||
const handlePageBreak = useCallback(() => {
|
const handlePageBreak = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
if (!displayDocument || selectedPageIds.length === 0) return;
|
||||||
|
|
||||||
|
// Convert selected page IDs to page numbers for the command
|
||||||
|
const selectedPageNumbers = getPageNumbersFromIds(selectedPageIds);
|
||||||
|
|
||||||
const pageBreakCommand = new PageBreakCommand(
|
const pageBreakCommand = new PageBreakCommand(
|
||||||
selectedPageNumbers,
|
selectedPageNumbers,
|
||||||
() => displayDocument,
|
() => displayDocument,
|
||||||
setEditedDocument,
|
setEditedDocument
|
||||||
setSelectedPageNumbers
|
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(pageBreakCommand);
|
undoManagerRef.current.executeCommand(pageBreakCommand);
|
||||||
}, [selectedPageNumbers, displayDocument]);
|
}, [selectedPageIds, displayDocument, getPageNumbersFromIds]);
|
||||||
|
|
||||||
const handlePageBreakAll = useCallback(() => {
|
const handlePageBreakAll = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
if (!displayDocument || selectedPageIds.length === 0) return;
|
||||||
|
|
||||||
|
// Convert selected page IDs to page numbers for the command
|
||||||
|
const selectedPageNumbers = getPageNumbersFromIds(selectedPageIds);
|
||||||
|
|
||||||
const pageBreakCommand = new PageBreakCommand(
|
const pageBreakCommand = new PageBreakCommand(
|
||||||
selectedPageNumbers,
|
selectedPageNumbers,
|
||||||
() => displayDocument,
|
() => displayDocument,
|
||||||
setEditedDocument,
|
setEditedDocument
|
||||||
setSelectedPageNumbers
|
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(pageBreakCommand);
|
undoManagerRef.current.executeCommand(pageBreakCommand);
|
||||||
}, [selectedPageNumbers, displayDocument]);
|
}, [selectedPageIds, displayDocument, getPageNumbersFromIds]);
|
||||||
|
|
||||||
const handleInsertFiles = useCallback(async (files: File[], insertAfterPage: number) => {
|
const handleInsertFiles = useCallback(async (files: File[], insertAfterPage: number) => {
|
||||||
if (!displayDocument || files.length === 0) return;
|
if (!displayDocument || files.length === 0) return;
|
||||||
@ -400,21 +413,25 @@ const PageEditor = ({
|
|||||||
|
|
||||||
const handleSelectAll = useCallback(() => {
|
const handleSelectAll = useCallback(() => {
|
||||||
if (!displayDocument) return;
|
if (!displayDocument) return;
|
||||||
const allPageNumbers = Array.from({ length: displayDocument.pages.length }, (_, i) => i + 1);
|
const allPageIds = displayDocument.pages.map(p => p.id);
|
||||||
setSelectedPageNumbers(allPageNumbers);
|
toggleSelectAll(allPageIds);
|
||||||
}, [displayDocument]);
|
}, [displayDocument, toggleSelectAll]);
|
||||||
|
|
||||||
const handleDeselectAll = useCallback(() => {
|
const handleDeselectAll = useCallback(() => {
|
||||||
setSelectedPageNumbers([]);
|
setSelectedPageIds([]);
|
||||||
}, []);
|
}, [setSelectedPageIds]);
|
||||||
|
|
||||||
const handleSetSelectedPages = useCallback((pageNumbers: number[]) => {
|
const handleSetSelectedPages = useCallback((pageNumbers: number[]) => {
|
||||||
setSelectedPageNumbers(pageNumbers);
|
const pageIds = getPageIdsFromNumbers(pageNumbers);
|
||||||
}, []);
|
setSelectedPageIds(pageIds);
|
||||||
|
}, [getPageIdsFromNumbers, setSelectedPageIds]);
|
||||||
|
|
||||||
const handleReorderPages = useCallback((sourcePageNumber: number, targetIndex: number, selectedPages?: number[]) => {
|
const handleReorderPages = useCallback((sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => {
|
||||||
if (!displayDocument) return;
|
if (!displayDocument) return;
|
||||||
|
|
||||||
|
// Convert selectedPageIds to page numbers for the reorder command
|
||||||
|
const selectedPages = selectedPageIds ? getPageNumbersFromIds(selectedPageIds) : undefined;
|
||||||
|
|
||||||
const reorderCommand = new ReorderPagesCommand(
|
const reorderCommand = new ReorderPagesCommand(
|
||||||
sourcePageNumber,
|
sourcePageNumber,
|
||||||
targetIndex,
|
targetIndex,
|
||||||
@ -423,7 +440,7 @@ const PageEditor = ({
|
|||||||
setEditedDocument
|
setEditedDocument
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(reorderCommand);
|
undoManagerRef.current.executeCommand(reorderCommand);
|
||||||
}, [displayDocument]);
|
}, [displayDocument, getPageNumbersFromIds]);
|
||||||
|
|
||||||
// Helper function to collect source files for multi-file export
|
// Helper function to collect source files for multi-file export
|
||||||
const getSourceFiles = useCallback((): Map<string, File> | null => {
|
const getSourceFiles = useCallback((): Map<string, File> | null => {
|
||||||
@ -466,7 +483,7 @@ const PageEditor = ({
|
|||||||
}, [activeFileIds, selectors, displayDocument]);
|
}, [activeFileIds, selectors, displayDocument]);
|
||||||
|
|
||||||
const onExportSelected = useCallback(async () => {
|
const onExportSelected = useCallback(async () => {
|
||||||
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
if (!displayDocument || selectedPageIds.length === 0) return;
|
||||||
|
|
||||||
setExportLoading(true);
|
setExportLoading(true);
|
||||||
try {
|
try {
|
||||||
@ -480,11 +497,11 @@ const PageEditor = ({
|
|||||||
// For selected pages export, we work with the first document (or single document)
|
// For selected pages export, we work with the first document (or single document)
|
||||||
const documentWithDOMState = Array.isArray(processedDocuments) ? processedDocuments[0] : processedDocuments;
|
const documentWithDOMState = Array.isArray(processedDocuments) ? processedDocuments[0] : processedDocuments;
|
||||||
|
|
||||||
// Step 2: Convert selected page numbers to page IDs from the document with DOM state
|
// Step 2: Use the already selected page IDs
|
||||||
const selectedPageIds = selectedPageNumbers.map(pageNum => {
|
// Filter to only include IDs that exist in the document with DOM state
|
||||||
const page = documentWithDOMState.pages.find(p => p.pageNumber === pageNum);
|
const validSelectedPageIds = selectedPageIds.filter(pageId =>
|
||||||
return page?.id || '';
|
documentWithDOMState.pages.some(p => p.id === pageId)
|
||||||
}).filter(id => id);
|
);
|
||||||
|
|
||||||
// Step 3: Export with pdfExportService
|
// Step 3: Export with pdfExportService
|
||||||
|
|
||||||
@ -494,12 +511,12 @@ const PageEditor = ({
|
|||||||
? await pdfExportService.exportPDFMultiFile(
|
? await pdfExportService.exportPDFMultiFile(
|
||||||
documentWithDOMState,
|
documentWithDOMState,
|
||||||
sourceFiles,
|
sourceFiles,
|
||||||
selectedPageIds,
|
validSelectedPageIds,
|
||||||
{ selectedOnly: true, filename: exportFilename }
|
{ selectedOnly: true, filename: exportFilename }
|
||||||
)
|
)
|
||||||
: await pdfExportService.exportPDF(
|
: await pdfExportService.exportPDF(
|
||||||
documentWithDOMState,
|
documentWithDOMState,
|
||||||
selectedPageIds,
|
validSelectedPageIds,
|
||||||
{ selectedOnly: true, filename: exportFilename }
|
{ selectedOnly: true, filename: exportFilename }
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -511,7 +528,7 @@ const PageEditor = ({
|
|||||||
console.error('Export failed:', error);
|
console.error('Export failed:', error);
|
||||||
setExportLoading(false);
|
setExportLoading(false);
|
||||||
}
|
}
|
||||||
}, [displayDocument, selectedPageNumbers, mergedPdfDocument, splitPositions, getSourceFiles, getExportFilename]);
|
}, [displayDocument, selectedPageIds, mergedPdfDocument, splitPositions, getSourceFiles, getExportFilename]);
|
||||||
|
|
||||||
const onExportAll = useCallback(async () => {
|
const onExportAll = useCallback(async () => {
|
||||||
if (!displayDocument) return;
|
if (!displayDocument) return;
|
||||||
@ -606,7 +623,7 @@ const PageEditor = ({
|
|||||||
const closePdf = useCallback(() => {
|
const closePdf = useCallback(() => {
|
||||||
actions.clearAllFiles();
|
actions.clearAllFiles();
|
||||||
undoManagerRef.current.clear();
|
undoManagerRef.current.clear();
|
||||||
setSelectedPageNumbers([]);
|
setSelectedPageIds([]);
|
||||||
setSelectionMode(false);
|
setSelectionMode(false);
|
||||||
}, [actions]);
|
}, [actions]);
|
||||||
|
|
||||||
@ -646,7 +663,8 @@ const PageEditor = ({
|
|||||||
applyChanges,
|
applyChanges,
|
||||||
exportLoading,
|
exportLoading,
|
||||||
selectionMode,
|
selectionMode,
|
||||||
selectedPages: selectedPageNumbers,
|
selectedPageIds,
|
||||||
|
displayDocument: displayDocument || undefined,
|
||||||
splitPositions,
|
splitPositions,
|
||||||
totalPages: displayDocument?.pages.length || 0,
|
totalPages: displayDocument?.pages.length || 0,
|
||||||
closePdf,
|
closePdf,
|
||||||
@ -655,7 +673,7 @@ const PageEditor = ({
|
|||||||
}, [
|
}, [
|
||||||
onFunctionsReady, handleUndo, handleRedo, canUndo, canRedo, handleRotate, handleDelete, handleSplit, handleSplitAll,
|
onFunctionsReady, handleUndo, handleRedo, canUndo, canRedo, handleRotate, handleDelete, handleSplit, handleSplitAll,
|
||||||
handlePageBreak, handlePageBreakAll, handleSelectAll, handleDeselectAll, handleSetSelectedPages, handleExportPreview, onExportSelected, onExportAll, applyChanges, exportLoading,
|
handlePageBreak, handlePageBreakAll, handleSelectAll, handleDeselectAll, handleSetSelectedPages, handleExportPreview, onExportSelected, onExportAll, applyChanges, exportLoading,
|
||||||
selectionMode, selectedPageNumbers, splitPositions, displayDocument?.pages.length, closePdf
|
selectionMode, selectedPageIds, splitPositions, displayDocument?.pages.length, closePdf
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Display all pages - use edited or original document
|
// Display all pages - use edited or original document
|
||||||
@ -745,7 +763,7 @@ const PageEditor = ({
|
|||||||
{/* Pages Grid */}
|
{/* Pages Grid */}
|
||||||
<DragDropGrid
|
<DragDropGrid
|
||||||
items={displayedPages}
|
items={displayedPages}
|
||||||
selectedItems={selectedPageNumbers}
|
selectedItems={selectedPageIds}
|
||||||
selectionMode={selectionMode}
|
selectionMode={selectionMode}
|
||||||
isAnimating={isAnimating}
|
isAnimating={isAnimating}
|
||||||
onReorderPages={handleReorderPages}
|
onReorderPages={handleReorderPages}
|
||||||
@ -756,7 +774,7 @@ const PageEditor = ({
|
|||||||
index={index}
|
index={index}
|
||||||
totalPages={displayDocument.pages.length}
|
totalPages={displayDocument.pages.length}
|
||||||
originalFile={(page as any).originalFileId ? selectors.getFile((page as any).originalFileId) : undefined}
|
originalFile={(page as any).originalFileId ? selectors.getFile((page as any).originalFileId) : undefined}
|
||||||
selectedPages={selectedPageNumbers}
|
selectedPageIds={selectedPageIds}
|
||||||
selectionMode={selectionMode}
|
selectionMode={selectionMode}
|
||||||
movingPage={movingPage}
|
movingPage={movingPage}
|
||||||
isAnimating={isAnimating}
|
isAnimating={isAnimating}
|
||||||
|
@ -37,7 +37,8 @@ interface PageEditorControlsProps {
|
|||||||
|
|
||||||
// Selection state
|
// Selection state
|
||||||
selectionMode: boolean;
|
selectionMode: boolean;
|
||||||
selectedPages: number[];
|
selectedPageIds: string[];
|
||||||
|
displayDocument?: { pages: { id: string; pageNumber: number }[] };
|
||||||
|
|
||||||
// Split state (for tooltip logic)
|
// Split state (for tooltip logic)
|
||||||
splitPositions?: Set<number>;
|
splitPositions?: Set<number>;
|
||||||
@ -59,18 +60,23 @@ const PageEditorControls = ({
|
|||||||
onExportAll,
|
onExportAll,
|
||||||
exportLoading,
|
exportLoading,
|
||||||
selectionMode,
|
selectionMode,
|
||||||
selectedPages,
|
selectedPageIds,
|
||||||
|
displayDocument,
|
||||||
splitPositions,
|
splitPositions,
|
||||||
totalPages
|
totalPages
|
||||||
}: PageEditorControlsProps) => {
|
}: PageEditorControlsProps) => {
|
||||||
// Calculate split tooltip text using smart toggle logic
|
// Calculate split tooltip text using smart toggle logic
|
||||||
const getSplitTooltip = () => {
|
const getSplitTooltip = () => {
|
||||||
if (!splitPositions || !totalPages || selectedPages.length === 0) {
|
if (!splitPositions || !totalPages || selectedPageIds.length === 0) {
|
||||||
return "Split Selected";
|
return "Split Selected";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert selected pages to split positions (same logic as handleSplit)
|
// Convert selected pages to split positions (same logic as handleSplit)
|
||||||
const selectedSplitPositions = selectedPages.map(pageNum => pageNum - 1).filter(pos => pos < totalPages - 1);
|
const selectedPageNumbers = displayDocument ? selectedPageIds.map(id => {
|
||||||
|
const page = displayDocument.pages.find(p => p.id === id);
|
||||||
|
return page?.pageNumber || 0;
|
||||||
|
}).filter(num => num > 0) : [];
|
||||||
|
const selectedSplitPositions = selectedPageNumbers.map(pageNum => pageNum - 1).filter(pos => pos < totalPages - 1);
|
||||||
|
|
||||||
if (selectedSplitPositions.length === 0) {
|
if (selectedSplitPositions.length === 0) {
|
||||||
return "Split Selected";
|
return "Split Selected";
|
||||||
@ -97,8 +103,8 @@ const PageEditorControls = ({
|
|||||||
|
|
||||||
// Calculate page break tooltip text
|
// Calculate page break tooltip text
|
||||||
const getPageBreakTooltip = () => {
|
const getPageBreakTooltip = () => {
|
||||||
return selectedPages.length > 0
|
return selectedPageIds.length > 0
|
||||||
? `Insert ${selectedPages.length} Page Break${selectedPages.length > 1 ? 's' : ''}`
|
? `Insert ${selectedPageIds.length} Page Break${selectedPageIds.length > 1 ? 's' : ''}`
|
||||||
: "Insert Page Breaks";
|
: "Insert Page Breaks";
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -157,7 +163,7 @@ const PageEditorControls = ({
|
|||||||
<Tooltip label="Rotate Selected Left">
|
<Tooltip label="Rotate Selected Left">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => onRotate('left')}
|
onClick={() => onRotate('left')}
|
||||||
disabled={selectedPages.length === 0}
|
disabled={selectedPageIds.length === 0}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||||
radius="md"
|
radius="md"
|
||||||
@ -169,7 +175,7 @@ const PageEditorControls = ({
|
|||||||
<Tooltip label="Rotate Selected Right">
|
<Tooltip label="Rotate Selected Right">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => onRotate('right')}
|
onClick={() => onRotate('right')}
|
||||||
disabled={selectedPages.length === 0}
|
disabled={selectedPageIds.length === 0}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||||
radius="md"
|
radius="md"
|
||||||
@ -181,7 +187,7 @@ const PageEditorControls = ({
|
|||||||
<Tooltip label="Delete Selected">
|
<Tooltip label="Delete Selected">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
disabled={selectedPages.length === 0}
|
disabled={selectedPageIds.length === 0}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||||
radius="md"
|
radius="md"
|
||||||
@ -193,7 +199,7 @@ const PageEditorControls = ({
|
|||||||
<Tooltip label={getSplitTooltip()}>
|
<Tooltip label={getSplitTooltip()}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={onSplit}
|
onClick={onSplit}
|
||||||
disabled={selectedPages.length === 0}
|
disabled={selectedPageIds.length === 0}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||||
radius="md"
|
radius="md"
|
||||||
@ -205,7 +211,7 @@ const PageEditorControls = ({
|
|||||||
<Tooltip label={getPageBreakTooltip()}>
|
<Tooltip label={getPageBreakTooltip()}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={onPageBreak}
|
onClick={onPageBreak}
|
||||||
disabled={selectedPages.length === 0}
|
disabled={selectedPageIds.length === 0}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||||
radius="md"
|
radius="md"
|
||||||
|
@ -19,13 +19,13 @@ interface PageThumbnailProps {
|
|||||||
index: number;
|
index: number;
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
originalFile?: File;
|
originalFile?: File;
|
||||||
selectedPages: number[];
|
selectedPageIds: string[];
|
||||||
selectionMode: boolean;
|
selectionMode: boolean;
|
||||||
movingPage: number | null;
|
movingPage: number | null;
|
||||||
isAnimating: boolean;
|
isAnimating: boolean;
|
||||||
pageRefs: React.MutableRefObject<Map<string, HTMLDivElement>>;
|
pageRefs: React.MutableRefObject<Map<string, HTMLDivElement>>;
|
||||||
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPages?: number[]) => void;
|
onReorderPages: (sourcePageNumber: number, targetIndex: number, selectedPageIds?: string[]) => void;
|
||||||
onTogglePage: (pageNumber: number) => void;
|
onTogglePage: (pageId: string) => void;
|
||||||
onAnimateReorder: () => void;
|
onAnimateReorder: () => void;
|
||||||
onExecuteCommand: (command: { execute: () => void }) => void;
|
onExecuteCommand: (command: { execute: () => void }) => void;
|
||||||
onSetStatus: (status: string) => void;
|
onSetStatus: (status: string) => void;
|
||||||
@ -45,7 +45,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
|||||||
index,
|
index,
|
||||||
totalPages,
|
totalPages,
|
||||||
originalFile,
|
originalFile,
|
||||||
selectedPages,
|
selectedPageIds,
|
||||||
selectionMode,
|
selectionMode,
|
||||||
movingPage,
|
movingPage,
|
||||||
isAnimating,
|
isAnimating,
|
||||||
@ -139,7 +139,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
|||||||
getInitialData: () => ({
|
getInitialData: () => ({
|
||||||
pageNumber: page.pageNumber,
|
pageNumber: page.pageNumber,
|
||||||
pageId: page.id,
|
pageId: page.id,
|
||||||
selectedPages: [page.pageNumber]
|
selectedPageIds: [page.id]
|
||||||
}),
|
}),
|
||||||
onDragStart: () => {
|
onDragStart: () => {
|
||||||
setIsDragging(true);
|
setIsDragging(true);
|
||||||
@ -185,7 +185,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
|||||||
(dragElementRef.current as any).__dragCleanup();
|
(dragElementRef.current as any).__dragCleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [page.id, page.pageNumber, pageRefs, selectionMode, selectedPages, pdfDocument.pages, onReorderPages]);
|
}, [page.id, page.pageNumber, pageRefs, selectionMode, selectedPageIds, pdfDocument.pages, onReorderPages]);
|
||||||
|
|
||||||
// DOM command handlers
|
// DOM command handlers
|
||||||
const handleRotateLeft = useCallback((e: React.MouseEvent) => {
|
const handleRotateLeft = useCallback((e: React.MouseEvent) => {
|
||||||
@ -263,12 +263,12 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
|||||||
|
|
||||||
// If mouse moved less than 5 pixels, consider it a click (not a drag)
|
// If mouse moved less than 5 pixels, consider it a click (not a drag)
|
||||||
if (distance < 5 && !isDragging) {
|
if (distance < 5 && !isDragging) {
|
||||||
onTogglePage(page.pageNumber);
|
onTogglePage(page.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsMouseDown(false);
|
setIsMouseDown(false);
|
||||||
setMouseStartPos(null);
|
setMouseStartPos(null);
|
||||||
}, [isMouseDown, mouseStartPos, isDragging, page.pageNumber, onTogglePage]);
|
}, [isMouseDown, mouseStartPos, isDragging, page.id, onTogglePage]);
|
||||||
|
|
||||||
const handleMouseLeave = useCallback(() => {
|
const handleMouseLeave = useCallback(() => {
|
||||||
setIsMouseDown(false);
|
setIsMouseDown(false);
|
||||||
@ -323,7 +323,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
|||||||
}}
|
}}
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onTogglePage(page.pageNumber);
|
onTogglePage(page.id);
|
||||||
}}
|
}}
|
||||||
onMouseUp={(e) => e.stopPropagation()}
|
onMouseUp={(e) => e.stopPropagation()}
|
||||||
onDragStart={(e) => {
|
onDragStart={(e) => {
|
||||||
@ -332,7 +332,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={Array.isArray(selectedPages) ? selectedPages.includes(page.pageNumber) : false}
|
checked={Array.isArray(selectedPageIds) ? selectedPageIds.includes(page.id) : false}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
// Selection is handled by container mouseDown
|
// Selection is handled by container mouseDown
|
||||||
}}
|
}}
|
||||||
|
@ -403,8 +403,7 @@ export class PageBreakCommand extends DOMCommand {
|
|||||||
constructor(
|
constructor(
|
||||||
private selectedPageNumbers: number[],
|
private selectedPageNumbers: number[],
|
||||||
private getCurrentDocument: () => PDFDocument | null,
|
private getCurrentDocument: () => PDFDocument | null,
|
||||||
private setDocument: (doc: PDFDocument) => void,
|
private setDocument: (doc: PDFDocument) => void
|
||||||
private setSelectedPages: (pages: number[]) => void
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -455,19 +454,7 @@ export class PageBreakCommand extends DOMCommand {
|
|||||||
|
|
||||||
this.setDocument(updatedDocument);
|
this.setDocument(updatedDocument);
|
||||||
|
|
||||||
// Maintain existing selection by mapping original selected pages to their new positions
|
// No need to maintain selection - page IDs remain stable, so selection persists automatically
|
||||||
const updatedSelection: number[] = [];
|
|
||||||
this.selectedPageNumbers.forEach(originalPageNum => {
|
|
||||||
// Find the original page by matching the page ID from the original document
|
|
||||||
const originalPage = this.originalDocument?.pages[originalPageNum - 1];
|
|
||||||
if (originalPage) {
|
|
||||||
const foundPage = newPages.find(page => page.id === originalPage.id && !page.isBlankPage);
|
|
||||||
if (foundPage) {
|
|
||||||
updatedSelection.push(foundPage.pageNumber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.setSelectedPages(updatedSelection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
undo(): void {
|
undo(): void {
|
||||||
|
@ -3,7 +3,7 @@ import { useState, useCallback } from 'react';
|
|||||||
export interface PageEditorState {
|
export interface PageEditorState {
|
||||||
// Selection state
|
// Selection state
|
||||||
selectionMode: boolean;
|
selectionMode: boolean;
|
||||||
selectedPageNumbers: number[];
|
selectedPageIds: string[];
|
||||||
|
|
||||||
// Animation state
|
// Animation state
|
||||||
movingPage: number | null;
|
movingPage: number | null;
|
||||||
@ -17,15 +17,15 @@ export interface PageEditorState {
|
|||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
setSelectionMode: (mode: boolean) => void;
|
setSelectionMode: (mode: boolean) => void;
|
||||||
setSelectedPageNumbers: (pages: number[]) => void;
|
setSelectedPageIds: (pages: string[]) => void;
|
||||||
setMovingPage: (pageNumber: number | null) => void;
|
setMovingPage: (pageNumber: number | null) => void;
|
||||||
setIsAnimating: (animating: boolean) => void;
|
setIsAnimating: (animating: boolean) => void;
|
||||||
setSplitPositions: (positions: Set<number>) => void;
|
setSplitPositions: (positions: Set<number>) => void;
|
||||||
setExportLoading: (loading: boolean) => void;
|
setExportLoading: (loading: boolean) => void;
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
togglePage: (pageNumber: number) => void;
|
togglePage: (pageId: string) => void;
|
||||||
toggleSelectAll: (totalPages: number) => void;
|
toggleSelectAll: (allPageIds: string[]) => void;
|
||||||
animateReorder: () => void;
|
animateReorder: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ export interface PageEditorState {
|
|||||||
export function usePageEditorState(): PageEditorState {
|
export function usePageEditorState(): PageEditorState {
|
||||||
// Selection state
|
// Selection state
|
||||||
const [selectionMode, setSelectionMode] = useState(false);
|
const [selectionMode, setSelectionMode] = useState(false);
|
||||||
const [selectedPageNumbers, setSelectedPageNumbers] = useState<number[]>([]);
|
const [selectedPageIds, setSelectedPageIds] = useState<string[]>([]);
|
||||||
|
|
||||||
// Animation state
|
// Animation state
|
||||||
const [movingPage, setMovingPage] = useState<number | null>(null);
|
const [movingPage, setMovingPage] = useState<number | null>(null);
|
||||||
@ -49,20 +49,19 @@ export function usePageEditorState(): PageEditorState {
|
|||||||
const [exportLoading, setExportLoading] = useState(false);
|
const [exportLoading, setExportLoading] = useState(false);
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
const togglePage = useCallback((pageNumber: number) => {
|
const togglePage = useCallback((pageId: string) => {
|
||||||
setSelectedPageNumbers(prev =>
|
setSelectedPageIds(prev =>
|
||||||
prev.includes(pageNumber)
|
prev.includes(pageId)
|
||||||
? prev.filter(n => n !== pageNumber)
|
? prev.filter(id => id !== pageId)
|
||||||
: [...prev, pageNumber]
|
: [...prev, pageId]
|
||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleSelectAll = useCallback((totalPages: number) => {
|
const toggleSelectAll = useCallback((allPageIds: string[]) => {
|
||||||
if (!totalPages) return;
|
if (!allPageIds.length) return;
|
||||||
|
|
||||||
const allPageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);
|
setSelectedPageIds(prev =>
|
||||||
setSelectedPageNumbers(prev =>
|
prev.length === allPageIds.length ? [] : allPageIds
|
||||||
prev.length === allPageNumbers.length ? [] : allPageNumbers
|
|
||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -74,7 +73,7 @@ export function usePageEditorState(): PageEditorState {
|
|||||||
return {
|
return {
|
||||||
// State
|
// State
|
||||||
selectionMode,
|
selectionMode,
|
||||||
selectedPageNumbers,
|
selectedPageIds,
|
||||||
movingPage,
|
movingPage,
|
||||||
isAnimating,
|
isAnimating,
|
||||||
splitPositions,
|
splitPositions,
|
||||||
@ -82,7 +81,7 @@ export function usePageEditorState(): PageEditorState {
|
|||||||
|
|
||||||
// Setters
|
// Setters
|
||||||
setSelectionMode,
|
setSelectionMode,
|
||||||
setSelectedPageNumbers,
|
setSelectedPageIds,
|
||||||
setMovingPage,
|
setMovingPage,
|
||||||
setIsAnimating,
|
setIsAnimating,
|
||||||
setSplitPositions,
|
setSplitPositions,
|
||||||
|
@ -47,7 +47,7 @@ export default function RightRail() {
|
|||||||
if (currentView === 'pageEditor') {
|
if (currentView === 'pageEditor') {
|
||||||
// Use PageEditor's own state
|
// Use PageEditor's own state
|
||||||
const totalItems = pageEditorFunctions?.totalPages || 0;
|
const totalItems = pageEditorFunctions?.totalPages || 0;
|
||||||
const selectedCount = pageEditorFunctions?.selectedPages?.length || 0;
|
const selectedCount = pageEditorFunctions?.selectedPageIds?.length || 0;
|
||||||
return { totalItems, selectedCount };
|
return { totalItems, selectedCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,12 +147,15 @@ export default function RightRail() {
|
|||||||
|
|
||||||
// Sync csvInput with PageEditor's selected pages
|
// Sync csvInput with PageEditor's selected pages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sortedPageNumbers = Array.isArray(pageEditorFunctions?.selectedPages)
|
const sortedPageNumbers = Array.isArray(pageEditorFunctions?.selectedPageIds) && pageEditorFunctions.displayDocument
|
||||||
? [...pageEditorFunctions.selectedPages].sort((a, b) => a - b)
|
? pageEditorFunctions.selectedPageIds.map(id => {
|
||||||
|
const page = pageEditorFunctions.displayDocument!.pages.find(p => p.id === id);
|
||||||
|
return page?.pageNumber || 0;
|
||||||
|
}).filter(num => num > 0).sort((a, b) => a - b)
|
||||||
: [];
|
: [];
|
||||||
const newCsvInput = sortedPageNumbers.join(', ');
|
const newCsvInput = sortedPageNumbers.join(', ');
|
||||||
setCsvInput(newCsvInput);
|
setCsvInput(newCsvInput);
|
||||||
}, [pageEditorFunctions?.selectedPages]);
|
}, [pageEditorFunctions?.selectedPageIds]);
|
||||||
|
|
||||||
// Clear CSV input when files change (use stable signature to avoid ref churn)
|
// Clear CSV input when files change (use stable signature to avoid ref churn)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -262,7 +265,8 @@ export default function RightRail() {
|
|||||||
<BulkSelectionPanel
|
<BulkSelectionPanel
|
||||||
csvInput={csvInput}
|
csvInput={csvInput}
|
||||||
setCsvInput={setCsvInput}
|
setCsvInput={setCsvInput}
|
||||||
selectedPages={Array.isArray(pageEditorFunctions?.selectedPages) ? pageEditorFunctions.selectedPages : []}
|
selectedPageIds={Array.isArray(pageEditorFunctions?.selectedPageIds) ? pageEditorFunctions.selectedPageIds : []}
|
||||||
|
displayDocument={pageEditorFunctions?.displayDocument}
|
||||||
onUpdatePagesFromCSV={updatePagesFromCSV}
|
onUpdatePagesFromCSV={updatePagesFromCSV}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -284,7 +288,7 @@ export default function RightRail() {
|
|||||||
radius="md"
|
radius="md"
|
||||||
className="right-rail-icon"
|
className="right-rail-icon"
|
||||||
onClick={() => { pageEditorFunctions?.handleDelete?.(); }}
|
onClick={() => { pageEditorFunctions?.handleDelete?.(); }}
|
||||||
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPages?.length || 0) === 0}
|
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0}
|
||||||
aria-label={typeof t === 'function' ? t('rightRail.deleteSelected', 'Delete Selected Pages') : 'Delete Selected Pages'}
|
aria-label={typeof t === 'function' ? t('rightRail.deleteSelected', 'Delete Selected Pages') : 'Delete Selected Pages'}
|
||||||
>
|
>
|
||||||
<LocalIcon icon="delete-outline-rounded" width="1.5rem" height="1.5rem" />
|
<LocalIcon icon="delete-outline-rounded" width="1.5rem" height="1.5rem" />
|
||||||
@ -305,7 +309,7 @@ export default function RightRail() {
|
|||||||
radius="md"
|
radius="md"
|
||||||
className="right-rail-icon"
|
className="right-rail-icon"
|
||||||
onClick={() => { pageEditorFunctions?.onExportSelected?.(); }}
|
onClick={() => { pageEditorFunctions?.onExportSelected?.(); }}
|
||||||
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPages?.length || 0) === 0 || pageEditorFunctions?.exportLoading}
|
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || pageEditorFunctions?.exportLoading}
|
||||||
aria-label={typeof t === 'function' ? t('rightRail.exportSelected', 'Export Selected Pages') : 'Export Selected Pages'}
|
aria-label={typeof t === 'function' ? t('rightRail.exportSelected', 'Export Selected Pages') : 'Export Selected Pages'}
|
||||||
>
|
>
|
||||||
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />
|
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />
|
||||||
|
@ -11,8 +11,6 @@ interface RainbowThemeHook {
|
|||||||
deactivateRainbow: () => void;
|
deactivateRainbow: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowRainbowMode = false; // Override to allow/disallow fun
|
|
||||||
|
|
||||||
export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): RainbowThemeHook {
|
export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): RainbowThemeHook {
|
||||||
// Get theme from localStorage or use initial
|
// Get theme from localStorage or use initial
|
||||||
const [themeMode, setThemeMode] = useState<ThemeMode>(() => {
|
const [themeMode, setThemeMode] = useState<ThemeMode>(() => {
|
||||||
@ -37,11 +35,11 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb
|
|||||||
// Save theme to localStorage whenever it changes
|
// Save theme to localStorage whenever it changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem('stirling-theme', themeMode);
|
localStorage.setItem('stirling-theme', themeMode);
|
||||||
|
|
||||||
// Apply rainbow class to body if in rainbow mode
|
// Apply rainbow class to body if in rainbow mode
|
||||||
if (themeMode === 'rainbow') {
|
if (themeMode === 'rainbow') {
|
||||||
document.body.classList.add('rainbow-mode-active');
|
document.body.classList.add('rainbow-mode-active');
|
||||||
|
|
||||||
// Show easter egg notification
|
// Show easter egg notification
|
||||||
showRainbowNotification();
|
showRainbowNotification();
|
||||||
} else {
|
} else {
|
||||||
@ -79,7 +77,7 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
document.body.appendChild(notification);
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
// Auto-remove notification after 3 seconds
|
// Auto-remove notification after 3 seconds
|
||||||
@ -123,7 +121,7 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
document.body.appendChild(notification);
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
// Auto-remove notification after 2 seconds
|
// Auto-remove notification after 2 seconds
|
||||||
@ -146,7 +144,7 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentTime = Date.now();
|
const currentTime = Date.now();
|
||||||
|
|
||||||
// Simple exit from rainbow mode with single click (after cooldown period)
|
// Simple exit from rainbow mode with single click (after cooldown period)
|
||||||
if (themeMode === 'rainbow') {
|
if (themeMode === 'rainbow') {
|
||||||
setThemeMode('light');
|
setThemeMode('light');
|
||||||
@ -154,7 +152,7 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb
|
|||||||
showExitNotification();
|
showExitNotification();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset counter if too much time has passed (2.5 seconds)
|
// Reset counter if too much time has passed (2.5 seconds)
|
||||||
if (currentTime - lastToggleTime.current > 2500) {
|
if (currentTime - lastToggleTime.current > 2500) {
|
||||||
toggleCount.current = 1;
|
toggleCount.current = 1;
|
||||||
@ -164,18 +162,18 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb
|
|||||||
lastToggleTime.current = currentTime;
|
lastToggleTime.current = currentTime;
|
||||||
|
|
||||||
// Easter egg: Activate rainbow mode after 10 rapid toggles
|
// Easter egg: Activate rainbow mode after 10 rapid toggles
|
||||||
if (allowRainbowMode && toggleCount.current >= 10) {
|
if (toggleCount.current >= 10) {
|
||||||
setThemeMode('rainbow');
|
setThemeMode('rainbow');
|
||||||
console.log('🌈 RAINBOW MODE ACTIVATED! 🌈 You found the secret easter egg!');
|
console.log('🌈 RAINBOW MODE ACTIVATED! 🌈 You found the secret easter egg!');
|
||||||
console.log('🌈 Button will be disabled for 3 seconds, then click once to exit!');
|
console.log('🌈 Button will be disabled for 3 seconds, then click once to exit!');
|
||||||
|
|
||||||
// Disable toggle for 3 seconds
|
// Disable toggle for 3 seconds
|
||||||
setIsToggleDisabled(true);
|
setIsToggleDisabled(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsToggleDisabled(false);
|
setIsToggleDisabled(false);
|
||||||
console.log('🌈 Theme toggle re-enabled! Click once to exit rainbow mode.');
|
console.log('🌈 Theme toggle re-enabled! Click once to exit rainbow mode.');
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
// Reset counter
|
// Reset counter
|
||||||
toggleCount.current = 0;
|
toggleCount.current = 0;
|
||||||
return;
|
return;
|
||||||
@ -205,4 +203,4 @@ export function useRainbowTheme(initialTheme: 'light' | 'dark' = 'light'): Rainb
|
|||||||
activateRainbow,
|
activateRainbow,
|
||||||
deactivateRainbow,
|
deactivateRainbow,
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -50,9 +50,9 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
|
|
||||||
const handleSettingsReset = () => {
|
const handleSettingsReset = () => {
|
||||||
compressOperation.resetResults();
|
compressOperation.resetResults();
|
||||||
onPreviewFile?.(null);
|
onPreviewFile?.(null); };
|
||||||
actions.setMode("compress");
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasFiles = selectedFiles.length > 0;
|
const hasFiles = selectedFiles.length > 0;
|
||||||
const hasResults = compressOperation.files.length > 0 || compressOperation.downloadUrl !== null;
|
const hasResults = compressOperation.files.length > 0 || compressOperation.downloadUrl !== null;
|
||||||
|
@ -28,13 +28,14 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
onPreviewFile?.(null);
|
onPreviewFile?.(null);
|
||||||
}, [splitParams.parameters]);
|
}, [splitParams.parameters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Reset results when selected files change (user selected different files)
|
// Reset results when selected files change (user selected different files)
|
||||||
if (selectedFiles.length > 0) {
|
if (selectedFiles.length > 0) {
|
||||||
splitOperation.resetResults();
|
splitOperation.resetResults();
|
||||||
onPreviewFile?.(null);
|
onPreviewFile?.(null);
|
||||||
}
|
}
|
||||||
}, [selectedFiles]);
|
}, [selectedFiles]);
|
||||||
|
|
||||||
const handleSplit = async () => {
|
const handleSplit = async () => {
|
||||||
try {
|
try {
|
||||||
await splitOperation.executeOperation(splitParams.parameters, selectedFiles);
|
await splitOperation.executeOperation(splitParams.parameters, selectedFiles);
|
||||||
@ -56,8 +57,7 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
const handleSettingsReset = () => {
|
const handleSettingsReset = () => {
|
||||||
splitOperation.resetResults();
|
splitOperation.resetResults();
|
||||||
onPreviewFile?.(null);
|
onPreviewFile?.(null);
|
||||||
actions.setMode("split");
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const hasFiles = selectedFiles.length > 0;
|
const hasFiles = selectedFiles.length > 0;
|
||||||
const hasResults = splitOperation.files.length > 0 || splitOperation.downloadUrl !== null;
|
const hasResults = splitOperation.files.length > 0 || splitOperation.downloadUrl !== null;
|
||||||
|
@ -56,11 +56,14 @@ export interface PageEditorFunctions {
|
|||||||
handleSelectAll: () => void;
|
handleSelectAll: () => void;
|
||||||
handleDeselectAll: () => void;
|
handleDeselectAll: () => void;
|
||||||
handleSetSelectedPages: (pageNumbers: number[]) => void;
|
handleSetSelectedPages: (pageNumbers: number[]) => void;
|
||||||
|
showExportPreview: (selectedOnly: boolean) => void;
|
||||||
onExportSelected: () => void;
|
onExportSelected: () => void;
|
||||||
onExportAll: () => void;
|
onExportAll: () => void;
|
||||||
|
applyChanges: () => void;
|
||||||
exportLoading: boolean;
|
exportLoading: boolean;
|
||||||
selectionMode: boolean;
|
selectionMode: boolean;
|
||||||
selectedPages: number[];
|
selectedPageIds: string[];
|
||||||
|
displayDocument?: PDFDocument;
|
||||||
splitPositions: Set<number>;
|
splitPositions: Set<number>;
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user