mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-24 21:29:25 +00:00
231 lines
7.2 KiB
TypeScript
231 lines
7.2 KiB
TypeScript
![]() |
import React, { createContext, useContext, useReducer, useCallback } from 'react';
|
||
|
import { useNavigationUrlSync } from '../hooks/useUrlSync';
|
||
|
|
||
|
/**
|
||
|
* NavigationContext - Complete navigation management system
|
||
|
*
|
||
|
* Handles navigation modes, navigation guards for unsaved changes,
|
||
|
* and breadcrumb/history navigation. Separated from FileContext to
|
||
|
* maintain clear separation of concerns.
|
||
|
*/
|
||
|
|
||
|
// Navigation mode types - complete list to match fileContext.ts
|
||
|
export type ModeType =
|
||
|
| 'viewer'
|
||
|
| 'pageEditor'
|
||
|
| 'fileEditor'
|
||
|
| 'merge'
|
||
|
| 'split'
|
||
|
| 'compress'
|
||
|
| 'ocr'
|
||
|
| 'convert'
|
||
|
| 'sanitize'
|
||
|
| 'addPassword'
|
||
|
| 'changePermissions'
|
||
|
| 'addWatermark'
|
||
|
| 'removePassword'
|
||
|
| 'single-large-page'
|
||
|
| 'repair'
|
||
|
| 'unlockPdfForms'
|
||
|
| 'removeCertificateSign';
|
||
|
|
||
|
// Navigation state
|
||
|
interface NavigationState {
|
||
|
currentMode: ModeType;
|
||
|
hasUnsavedChanges: boolean;
|
||
|
pendingNavigation: (() => void) | null;
|
||
|
showNavigationWarning: boolean;
|
||
|
}
|
||
|
|
||
|
// Navigation actions
|
||
|
type NavigationAction =
|
||
|
| { type: 'SET_MODE'; payload: { mode: ModeType } }
|
||
|
| { type: 'SET_UNSAVED_CHANGES'; payload: { hasChanges: boolean } }
|
||
|
| { type: 'SET_PENDING_NAVIGATION'; payload: { navigationFn: (() => void) | null } }
|
||
|
| { type: 'SHOW_NAVIGATION_WARNING'; payload: { show: boolean } };
|
||
|
|
||
|
// Navigation reducer
|
||
|
const navigationReducer = (state: NavigationState, action: NavigationAction): NavigationState => {
|
||
|
switch (action.type) {
|
||
|
case 'SET_MODE':
|
||
|
return { ...state, currentMode: action.payload.mode };
|
||
|
|
||
|
case 'SET_UNSAVED_CHANGES':
|
||
|
return { ...state, hasUnsavedChanges: action.payload.hasChanges };
|
||
|
|
||
|
case 'SET_PENDING_NAVIGATION':
|
||
|
return { ...state, pendingNavigation: action.payload.navigationFn };
|
||
|
|
||
|
case 'SHOW_NAVIGATION_WARNING':
|
||
|
return { ...state, showNavigationWarning: action.payload.show };
|
||
|
|
||
|
default:
|
||
|
return state;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Initial state
|
||
|
const initialState: NavigationState = {
|
||
|
currentMode: 'pageEditor',
|
||
|
hasUnsavedChanges: false,
|
||
|
pendingNavigation: null,
|
||
|
showNavigationWarning: false
|
||
|
};
|
||
|
|
||
|
// Navigation context actions interface
|
||
|
export interface NavigationContextActions {
|
||
|
setMode: (mode: ModeType) => void;
|
||
|
setHasUnsavedChanges: (hasChanges: boolean) => void;
|
||
|
showNavigationWarning: (show: boolean) => void;
|
||
|
requestNavigation: (navigationFn: () => void) => void;
|
||
|
confirmNavigation: () => void;
|
||
|
cancelNavigation: () => void;
|
||
|
}
|
||
|
|
||
|
// Split context values
|
||
|
export interface NavigationContextStateValue {
|
||
|
currentMode: ModeType;
|
||
|
hasUnsavedChanges: boolean;
|
||
|
pendingNavigation: (() => void) | null;
|
||
|
showNavigationWarning: boolean;
|
||
|
}
|
||
|
|
||
|
export interface NavigationContextActionsValue {
|
||
|
actions: NavigationContextActions;
|
||
|
}
|
||
|
|
||
|
// Create contexts
|
||
|
const NavigationStateContext = createContext<NavigationContextStateValue | undefined>(undefined);
|
||
|
const NavigationActionsContext = createContext<NavigationContextActionsValue | undefined>(undefined);
|
||
|
|
||
|
// Provider component
|
||
|
export const NavigationProvider: React.FC<{
|
||
|
children: React.ReactNode;
|
||
|
enableUrlSync?: boolean;
|
||
|
}> = ({ children, enableUrlSync = true }) => {
|
||
|
const [state, dispatch] = useReducer(navigationReducer, initialState);
|
||
|
|
||
|
const actions: NavigationContextActions = {
|
||
|
setMode: useCallback((mode: ModeType) => {
|
||
|
dispatch({ type: 'SET_MODE', payload: { mode } });
|
||
|
}, []),
|
||
|
|
||
|
setHasUnsavedChanges: useCallback((hasChanges: boolean) => {
|
||
|
dispatch({ type: 'SET_UNSAVED_CHANGES', payload: { hasChanges } });
|
||
|
}, []),
|
||
|
|
||
|
showNavigationWarning: useCallback((show: boolean) => {
|
||
|
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show } });
|
||
|
}, []),
|
||
|
|
||
|
requestNavigation: useCallback((navigationFn: () => void) => {
|
||
|
// If no unsaved changes, navigate immediately
|
||
|
if (!state.hasUnsavedChanges) {
|
||
|
navigationFn();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Otherwise, store the navigation and show warning
|
||
|
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn } });
|
||
|
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: true } });
|
||
|
}, [state.hasUnsavedChanges]),
|
||
|
|
||
|
confirmNavigation: useCallback(() => {
|
||
|
// Execute pending navigation
|
||
|
if (state.pendingNavigation) {
|
||
|
state.pendingNavigation();
|
||
|
}
|
||
|
|
||
|
// Clear navigation state
|
||
|
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn: null } });
|
||
|
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: false } });
|
||
|
}, [state.pendingNavigation]),
|
||
|
|
||
|
cancelNavigation: useCallback(() => {
|
||
|
// Clear navigation without executing
|
||
|
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn: null } });
|
||
|
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: false } });
|
||
|
}, [])
|
||
|
};
|
||
|
|
||
|
const stateValue: NavigationContextStateValue = {
|
||
|
currentMode: state.currentMode,
|
||
|
hasUnsavedChanges: state.hasUnsavedChanges,
|
||
|
pendingNavigation: state.pendingNavigation,
|
||
|
showNavigationWarning: state.showNavigationWarning
|
||
|
};
|
||
|
|
||
|
const actionsValue: NavigationContextActionsValue = {
|
||
|
actions
|
||
|
};
|
||
|
|
||
|
// Enable URL synchronization
|
||
|
useNavigationUrlSync(state.currentMode, actions.setMode, enableUrlSync);
|
||
|
|
||
|
return (
|
||
|
<NavigationStateContext.Provider value={stateValue}>
|
||
|
<NavigationActionsContext.Provider value={actionsValue}>
|
||
|
{children}
|
||
|
</NavigationActionsContext.Provider>
|
||
|
</NavigationStateContext.Provider>
|
||
|
);
|
||
|
};
|
||
|
|
||
|
// Navigation hooks
|
||
|
export const useNavigationState = () => {
|
||
|
const context = useContext(NavigationStateContext);
|
||
|
if (context === undefined) {
|
||
|
throw new Error('useNavigationState must be used within NavigationProvider');
|
||
|
}
|
||
|
return context;
|
||
|
};
|
||
|
|
||
|
export const useNavigationActions = () => {
|
||
|
const context = useContext(NavigationActionsContext);
|
||
|
if (context === undefined) {
|
||
|
throw new Error('useNavigationActions must be used within NavigationProvider');
|
||
|
}
|
||
|
return context;
|
||
|
};
|
||
|
|
||
|
// Combined hook for convenience
|
||
|
export const useNavigation = () => {
|
||
|
const state = useNavigationState();
|
||
|
const { actions } = useNavigationActions();
|
||
|
return { ...state, ...actions };
|
||
|
};
|
||
|
|
||
|
// Navigation guard hook (equivalent to old useFileNavigation)
|
||
|
export const useNavigationGuard = () => {
|
||
|
const state = useNavigationState();
|
||
|
const { actions } = useNavigationActions();
|
||
|
|
||
|
return {
|
||
|
pendingNavigation: state.pendingNavigation,
|
||
|
showNavigationWarning: state.showNavigationWarning,
|
||
|
hasUnsavedChanges: state.hasUnsavedChanges,
|
||
|
requestNavigation: actions.requestNavigation,
|
||
|
confirmNavigation: actions.confirmNavigation,
|
||
|
cancelNavigation: actions.cancelNavigation,
|
||
|
setHasUnsavedChanges: actions.setHasUnsavedChanges,
|
||
|
setShowNavigationWarning: actions.showNavigationWarning
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// Utility functions for mode handling
|
||
|
export const isValidMode = (mode: string): mode is ModeType => {
|
||
|
const validModes: ModeType[] = [
|
||
|
'viewer', 'pageEditor', 'fileEditor', 'merge', 'split',
|
||
|
'compress', 'ocr', 'convert', 'addPassword', 'changePermissions', 'sanitize'
|
||
|
];
|
||
|
return validModes.includes(mode as ModeType);
|
||
|
};
|
||
|
|
||
|
export const getDefaultMode = (): ModeType => 'pageEditor';
|
||
|
|
||
|
// TODO: This will be expanded for URL-based routing system
|
||
|
// - URL parsing utilities
|
||
|
// - Route definitions
|
||
|
// - Navigation hooks with URL sync
|
||
|
// - History management
|
||
|
// - Breadcrumb restoration from URL params
|