mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-26 17:52:59 +02:00
Pop up moving away from page editor
This commit is contained in:
parent
b1cec32ef9
commit
f70e45cee4
@ -1,6 +1,7 @@
|
|||||||
import { useState, useCallback, useRef, useEffect } from "react";
|
import { useState, useCallback, useRef, useEffect } from "react";
|
||||||
import { Text, Center, Box, LoadingOverlay, Stack } from "@mantine/core";
|
import { Text, Center, Box, LoadingOverlay, Stack } from "@mantine/core";
|
||||||
import { useFileState, useFileActions } from "../../contexts/FileContext";
|
import { useFileState, useFileActions } from "../../contexts/FileContext";
|
||||||
|
import { useNavigationGuard } from "../../contexts/NavigationContext";
|
||||||
import { PDFDocument, PageEditorFunctions } from "../../types/pageEditor";
|
import { PDFDocument, PageEditorFunctions } from "../../types/pageEditor";
|
||||||
import { pdfExportService } from "../../services/pdfExportService";
|
import { pdfExportService } from "../../services/pdfExportService";
|
||||||
import { documentManipulationService } from "../../services/documentManipulationService";
|
import { documentManipulationService } from "../../services/documentManipulationService";
|
||||||
@ -36,6 +37,9 @@ const PageEditor = ({
|
|||||||
const { state, selectors } = useFileState();
|
const { state, selectors } = useFileState();
|
||||||
const { actions } = useFileActions();
|
const { actions } = useFileActions();
|
||||||
|
|
||||||
|
// Navigation guard for unsaved changes
|
||||||
|
const { setHasUnsavedChanges } = useNavigationGuard();
|
||||||
|
|
||||||
// Prefer IDs + selectors to avoid array identity churn
|
// Prefer IDs + selectors to avoid array identity churn
|
||||||
const activeFileIds = state.files.ids;
|
const activeFileIds = state.files.ids;
|
||||||
|
|
||||||
@ -82,6 +86,12 @@ const PageEditor = ({
|
|||||||
updateUndoRedoState();
|
updateUndoRedoState();
|
||||||
}, [updateUndoRedoState]);
|
}, [updateUndoRedoState]);
|
||||||
|
|
||||||
|
// Wrapper for executeCommand to track unsaved changes
|
||||||
|
const executeCommandWithTracking = useCallback((command: any) => {
|
||||||
|
undoManagerRef.current.executeCommand(command);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
}, [setHasUnsavedChanges]);
|
||||||
|
|
||||||
// Watch for container size changes to update split line positions
|
// Watch for container size changes to update split line positions
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = gridContainerRef.current;
|
const container = gridContainerRef.current;
|
||||||
@ -138,17 +148,16 @@ const PageEditor = ({
|
|||||||
// DOM-first command handlers
|
// DOM-first command handlers
|
||||||
const handleRotatePages = useCallback((pageIds: string[], rotation: number) => {
|
const handleRotatePages = useCallback((pageIds: string[], rotation: number) => {
|
||||||
const bulkRotateCommand = new BulkRotateCommand(pageIds, rotation);
|
const bulkRotateCommand = new BulkRotateCommand(pageIds, rotation);
|
||||||
undoManagerRef.current.executeCommand(bulkRotateCommand);
|
executeCommandWithTracking(bulkRotateCommand);
|
||||||
}, []);
|
}, [executeCommandWithTracking]);
|
||||||
|
|
||||||
// Command factory functions for PageThumbnail
|
// Command factory functions for PageThumbnail
|
||||||
const createRotateCommand = useCallback((pageIds: string[], rotation: number) => ({
|
const createRotateCommand = useCallback((pageIds: string[], rotation: number) => ({
|
||||||
execute: () => {
|
execute: () => {
|
||||||
const bulkRotateCommand = new BulkRotateCommand(pageIds, rotation);
|
const bulkRotateCommand = new BulkRotateCommand(pageIds, rotation);
|
||||||
|
executeCommandWithTracking(bulkRotateCommand);
|
||||||
undoManagerRef.current.executeCommand(bulkRotateCommand);
|
|
||||||
}
|
}
|
||||||
}), []);
|
}), [executeCommandWithTracking]);
|
||||||
|
|
||||||
const createDeleteCommand = useCallback((pageIds: string[]) => ({
|
const createDeleteCommand = useCallback((pageIds: string[]) => ({
|
||||||
execute: () => {
|
execute: () => {
|
||||||
@ -174,10 +183,10 @@ const PageEditor = ({
|
|||||||
() => getPageNumbersFromIds(selectedPageIds),
|
() => getPageNumbersFromIds(selectedPageIds),
|
||||||
closePdf
|
closePdf
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(deleteCommand);
|
executeCommandWithTracking(deleteCommand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}), [displayDocument, splitPositions, selectedPageIds, getPageNumbersFromIds]);
|
}), [displayDocument, splitPositions, selectedPageIds, getPageNumbersFromIds, executeCommandWithTracking]);
|
||||||
|
|
||||||
const createSplitCommand = useCallback((position: number) => ({
|
const createSplitCommand = useCallback((position: number) => ({
|
||||||
execute: () => {
|
execute: () => {
|
||||||
@ -186,9 +195,9 @@ const PageEditor = ({
|
|||||||
() => splitPositions,
|
() => splitPositions,
|
||||||
setSplitPositions
|
setSplitPositions
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(splitCommand);
|
executeCommandWithTracking(splitCommand);
|
||||||
}
|
}
|
||||||
}), [splitPositions]);
|
}), [splitPositions, executeCommandWithTracking]);
|
||||||
|
|
||||||
// Command executor for PageThumbnail
|
// Command executor for PageThumbnail
|
||||||
const executeCommand = useCallback((command: any) => {
|
const executeCommand = useCallback((command: any) => {
|
||||||
@ -232,8 +241,8 @@ const PageEditor = ({
|
|||||||
() => selectedPageNumbers,
|
() => selectedPageNumbers,
|
||||||
closePdf
|
closePdf
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(deleteCommand);
|
executeCommandWithTracking(deleteCommand);
|
||||||
}, [selectedPageIds, displayDocument, splitPositions, getPageNumbersFromIds, getPageIdsFromNumbers]);
|
}, [selectedPageIds, displayDocument, splitPositions, getPageNumbersFromIds, getPageIdsFromNumbers, executeCommandWithTracking]);
|
||||||
|
|
||||||
const handleDeletePage = useCallback((pageNumber: number) => {
|
const handleDeletePage = useCallback((pageNumber: number) => {
|
||||||
if (!displayDocument) return;
|
if (!displayDocument) return;
|
||||||
@ -251,8 +260,8 @@ const PageEditor = ({
|
|||||||
() => getPageNumbersFromIds(selectedPageIds),
|
() => getPageNumbersFromIds(selectedPageIds),
|
||||||
closePdf
|
closePdf
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(deleteCommand);
|
executeCommandWithTracking(deleteCommand);
|
||||||
}, [displayDocument, splitPositions, selectedPageIds, getPageNumbersFromIds]);
|
}, [displayDocument, splitPositions, selectedPageIds, getPageNumbersFromIds, executeCommandWithTracking]);
|
||||||
|
|
||||||
const handleSplit = useCallback(() => {
|
const handleSplit = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageIds.length === 0) return;
|
if (!displayDocument || selectedPageIds.length === 0) return;
|
||||||
@ -298,8 +307,8 @@ const PageEditor = ({
|
|||||||
: `Add ${selectedPositions.length - existingSplitsCount} split(s)`
|
: `Add ${selectedPositions.length - existingSplitsCount} split(s)`
|
||||||
};
|
};
|
||||||
|
|
||||||
undoManagerRef.current.executeCommand(smartSplitCommand);
|
executeCommandWithTracking(smartSplitCommand);
|
||||||
}, [selectedPageIds, displayDocument, splitPositions, setSplitPositions, getPageNumbersFromIds]);
|
}, [selectedPageIds, displayDocument, splitPositions, setSplitPositions, getPageNumbersFromIds, executeCommandWithTracking]);
|
||||||
|
|
||||||
const handleSplitAll = useCallback(() => {
|
const handleSplitAll = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageIds.length === 0) return;
|
if (!displayDocument || selectedPageIds.length === 0) return;
|
||||||
@ -344,8 +353,8 @@ const PageEditor = ({
|
|||||||
: `Add ${selectedPositions.length - existingSplitsCount} split(s)`
|
: `Add ${selectedPositions.length - existingSplitsCount} split(s)`
|
||||||
};
|
};
|
||||||
|
|
||||||
undoManagerRef.current.executeCommand(smartSplitCommand);
|
executeCommandWithTracking(smartSplitCommand);
|
||||||
}, [selectedPageIds, displayDocument, splitPositions, setSplitPositions, getPageNumbersFromIds]);
|
}, [selectedPageIds, displayDocument, splitPositions, setSplitPositions, getPageNumbersFromIds, executeCommandWithTracking]);
|
||||||
|
|
||||||
const handlePageBreak = useCallback(() => {
|
const handlePageBreak = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageIds.length === 0) return;
|
if (!displayDocument || selectedPageIds.length === 0) return;
|
||||||
@ -358,8 +367,8 @@ const PageEditor = ({
|
|||||||
() => displayDocument,
|
() => displayDocument,
|
||||||
setEditedDocument
|
setEditedDocument
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(pageBreakCommand);
|
executeCommandWithTracking(pageBreakCommand);
|
||||||
}, [selectedPageIds, displayDocument, getPageNumbersFromIds]);
|
}, [selectedPageIds, displayDocument, getPageNumbersFromIds, executeCommandWithTracking]);
|
||||||
|
|
||||||
const handlePageBreakAll = useCallback(() => {
|
const handlePageBreakAll = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageIds.length === 0) return;
|
if (!displayDocument || selectedPageIds.length === 0) return;
|
||||||
@ -372,8 +381,8 @@ const PageEditor = ({
|
|||||||
() => displayDocument,
|
() => displayDocument,
|
||||||
setEditedDocument
|
setEditedDocument
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(pageBreakCommand);
|
executeCommandWithTracking(pageBreakCommand);
|
||||||
}, [selectedPageIds, displayDocument, getPageNumbersFromIds]);
|
}, [selectedPageIds, displayDocument, getPageNumbersFromIds, executeCommandWithTracking]);
|
||||||
|
|
||||||
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;
|
||||||
@ -416,8 +425,8 @@ const PageEditor = ({
|
|||||||
() => displayDocument,
|
() => displayDocument,
|
||||||
setEditedDocument
|
setEditedDocument
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(reorderCommand);
|
executeCommandWithTracking(reorderCommand);
|
||||||
}, [displayDocument, getPageNumbersFromIds]);
|
}, [displayDocument, getPageNumbersFromIds, executeCommandWithTracking]);
|
||||||
|
|
||||||
// Helper function to collect source files for multi-file export
|
// Helper function to collect source files for multi-file export
|
||||||
const getSourceFiles = useCallback((): Map<FileId, File> | null => {
|
const getSourceFiles = useCallback((): Map<FileId, File> | null => {
|
||||||
@ -499,13 +508,14 @@ const PageEditor = ({
|
|||||||
|
|
||||||
// Step 4: Download the result
|
// Step 4: Download the result
|
||||||
pdfExportService.downloadFile(result.blob, result.filename);
|
pdfExportService.downloadFile(result.blob, result.filename);
|
||||||
|
setHasUnsavedChanges(false); // Clear unsaved changes after successful export
|
||||||
|
|
||||||
setExportLoading(false);
|
setExportLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Export failed:', error);
|
console.error('Export failed:', error);
|
||||||
setExportLoading(false);
|
setExportLoading(false);
|
||||||
}
|
}
|
||||||
}, [displayDocument, selectedPageIds, mergedPdfDocument, splitPositions, getSourceFiles, getExportFilename]);
|
}, [displayDocument, selectedPageIds, mergedPdfDocument, splitPositions, getSourceFiles, getExportFilename, setHasUnsavedChanges]);
|
||||||
|
|
||||||
const onExportAll = useCallback(async () => {
|
const onExportAll = useCallback(async () => {
|
||||||
if (!displayDocument) return;
|
if (!displayDocument) return;
|
||||||
@ -552,6 +562,7 @@ const PageEditor = ({
|
|||||||
const zipFilename = baseExportFilename.replace(/\.pdf$/i, '.zip');
|
const zipFilename = baseExportFilename.replace(/\.pdf$/i, '.zip');
|
||||||
|
|
||||||
pdfExportService.downloadFile(zipBlob, zipFilename);
|
pdfExportService.downloadFile(zipBlob, zipFilename);
|
||||||
|
setHasUnsavedChanges(false); // Clear unsaved changes after successful export
|
||||||
} else {
|
} else {
|
||||||
// Single document - regular export
|
// Single document - regular export
|
||||||
const sourceFiles = getSourceFiles();
|
const sourceFiles = getSourceFiles();
|
||||||
@ -570,6 +581,7 @@ const PageEditor = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
pdfExportService.downloadFile(result.blob, result.filename);
|
pdfExportService.downloadFile(result.blob, result.filename);
|
||||||
|
setHasUnsavedChanges(false); // Clear unsaved changes after successful export
|
||||||
}
|
}
|
||||||
|
|
||||||
setExportLoading(false);
|
setExportLoading(false);
|
||||||
@ -577,7 +589,7 @@ const PageEditor = ({
|
|||||||
console.error('Export failed:', error);
|
console.error('Export failed:', error);
|
||||||
setExportLoading(false);
|
setExportLoading(false);
|
||||||
}
|
}
|
||||||
}, [displayDocument, mergedPdfDocument, splitPositions, getSourceFiles, getExportFilename]);
|
}, [displayDocument, mergedPdfDocument, splitPositions, getSourceFiles, getExportFilename, setHasUnsavedChanges]);
|
||||||
|
|
||||||
// Apply DOM changes to document state using dedicated service
|
// Apply DOM changes to document state using dedicated service
|
||||||
const applyChanges = useCallback(() => {
|
const applyChanges = useCallback(() => {
|
||||||
@ -779,7 +791,14 @@ const PageEditor = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
<NavigationWarningModal />
|
<NavigationWarningModal
|
||||||
|
onApplyAndContinue={async () => {
|
||||||
|
applyChanges();
|
||||||
|
}}
|
||||||
|
onExportAndContinue={async () => {
|
||||||
|
await onExportAll();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -109,16 +109,34 @@ export const NavigationProvider: React.FC<{
|
|||||||
|
|
||||||
const actions: NavigationContextActions = {
|
const actions: NavigationContextActions = {
|
||||||
setWorkbench: useCallback((workbench: WorkbenchType) => {
|
setWorkbench: useCallback((workbench: WorkbenchType) => {
|
||||||
|
// If we're leaving pageEditor workbench and have unsaved changes, request navigation
|
||||||
|
if (state.workbench === 'pageEditor' && workbench !== 'pageEditor' && state.hasUnsavedChanges) {
|
||||||
|
const performWorkbenchChange = () => {
|
||||||
dispatch({ type: 'SET_WORKBENCH', payload: { workbench } });
|
dispatch({ type: 'SET_WORKBENCH', payload: { workbench } });
|
||||||
}, []),
|
};
|
||||||
|
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn: performWorkbenchChange } });
|
||||||
|
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: true } });
|
||||||
|
} else {
|
||||||
|
dispatch({ type: 'SET_WORKBENCH', payload: { workbench } });
|
||||||
|
}
|
||||||
|
}, [state.workbench, state.hasUnsavedChanges]),
|
||||||
|
|
||||||
setSelectedTool: useCallback((toolId: ToolId | null) => {
|
setSelectedTool: useCallback((toolId: ToolId | null) => {
|
||||||
dispatch({ type: 'SET_SELECTED_TOOL', payload: { toolId } });
|
dispatch({ type: 'SET_SELECTED_TOOL', payload: { toolId } });
|
||||||
}, []),
|
}, []),
|
||||||
|
|
||||||
setToolAndWorkbench: useCallback((toolId: ToolId | null, workbench: WorkbenchType) => {
|
setToolAndWorkbench: useCallback((toolId: ToolId | null, workbench: WorkbenchType) => {
|
||||||
|
// If we're leaving pageEditor workbench and have unsaved changes, request navigation
|
||||||
|
if (state.workbench === 'pageEditor' && workbench !== 'pageEditor' && state.hasUnsavedChanges) {
|
||||||
|
const performWorkbenchChange = () => {
|
||||||
dispatch({ type: 'SET_TOOL_AND_WORKBENCH', payload: { toolId, workbench } });
|
dispatch({ type: 'SET_TOOL_AND_WORKBENCH', payload: { toolId, workbench } });
|
||||||
}, []),
|
};
|
||||||
|
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn: performWorkbenchChange } });
|
||||||
|
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: true } });
|
||||||
|
} else {
|
||||||
|
dispatch({ type: 'SET_TOOL_AND_WORKBENCH', payload: { toolId, workbench } });
|
||||||
|
}
|
||||||
|
}, [state.workbench, state.hasUnsavedChanges]),
|
||||||
|
|
||||||
setHasUnsavedChanges: useCallback((hasChanges: boolean) => {
|
setHasUnsavedChanges: useCallback((hasChanges: boolean) => {
|
||||||
dispatch({ type: 'SET_UNSAVED_CHANGES', payload: { hasChanges } });
|
dispatch({ type: 'SET_UNSAVED_CHANGES', payload: { hasChanges } });
|
||||||
|
@ -216,7 +216,6 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
|||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
setLeftPanelView('toolContent');
|
setLeftPanelView('toolContent');
|
||||||
setReaderMode(false); // Disable read mode when selecting tools
|
setReaderMode(false); // Disable read mode when selecting tools
|
||||||
|
|
||||||
}, [actions, getSelectedTool, setLeftPanelView, setReaderMode, setSearchQuery]);
|
}, [actions, getSelectedTool, setLeftPanelView, setReaderMode, setSearchQuery]);
|
||||||
|
|
||||||
const handleBackToTools = useCallback(() => {
|
const handleBackToTools = useCallback(() => {
|
||||||
|
Loading…
Reference in New Issue
Block a user