mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Restore URl for tools
This commit is contained in:
parent
83dee1c0b5
commit
511bdee7db
@ -1,4 +1,5 @@
|
||||
import React, { createContext, useContext, useReducer, useCallback } from 'react';
|
||||
import { useNavigationUrlSync } from '../hooks/useUrlSync';
|
||||
|
||||
/**
|
||||
* NavigationContext - Complete navigation management system
|
||||
@ -92,7 +93,10 @@ const NavigationStateContext = createContext<NavigationContextStateValue | undef
|
||||
const NavigationActionsContext = createContext<NavigationContextActionsValue | undefined>(undefined);
|
||||
|
||||
// Provider component
|
||||
export const NavigationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
export const NavigationProvider: React.FC<{
|
||||
children: React.ReactNode;
|
||||
enableUrlSync?: boolean;
|
||||
}> = ({ children, enableUrlSync = true }) => {
|
||||
const [state, dispatch] = useReducer(navigationReducer, initialState);
|
||||
|
||||
const actions: NavigationContextActions = {
|
||||
@ -149,6 +153,9 @@ export const NavigationProvider: React.FC<{ children: React.ReactNode }> = ({ ch
|
||||
actions
|
||||
};
|
||||
|
||||
// Enable URL synchronization
|
||||
useNavigationUrlSync(state.currentMode, actions.setMode, enableUrlSync);
|
||||
|
||||
return (
|
||||
<NavigationStateContext.Provider value={stateValue}>
|
||||
<NavigationActionsContext.Provider value={actionsValue}>
|
||||
|
@ -7,6 +7,7 @@ import React, { createContext, useContext, useReducer, useCallback, useMemo } fr
|
||||
import { useToolManagement } from '../hooks/useToolManagement';
|
||||
import { PageEditorFunctions } from '../types/pageEditor';
|
||||
import { ToolRegistryEntry } from '../data/toolsTaxonomy';
|
||||
import { useToolWorkflowUrlSync } from '../hooks/useUrlSync';
|
||||
|
||||
// State interface
|
||||
interface ToolWorkflowState {
|
||||
@ -101,9 +102,11 @@ interface ToolWorkflowProviderProps {
|
||||
children: React.ReactNode;
|
||||
/** Handler for view changes (passed from parent) */
|
||||
onViewChange?: (view: string) => void;
|
||||
/** Enable URL synchronization for tool selection */
|
||||
enableUrlSync?: boolean;
|
||||
}
|
||||
|
||||
export function ToolWorkflowProvider({ children, onViewChange }: ToolWorkflowProviderProps) {
|
||||
export function ToolWorkflowProvider({ children, onViewChange, enableUrlSync = true }: ToolWorkflowProviderProps) {
|
||||
const [state, dispatch] = useReducer(toolWorkflowReducer, initialState);
|
||||
|
||||
// Tool management hook
|
||||
@ -182,6 +185,9 @@ export function ToolWorkflowProvider({ children, onViewChange }: ToolWorkflowPro
|
||||
[state.sidebarsVisible, state.readerMode]
|
||||
);
|
||||
|
||||
// Enable URL synchronization for tool selection
|
||||
useToolWorkflowUrlSync(selectedToolKey, selectTool, clearToolSelection, enableUrlSync);
|
||||
|
||||
// Simple context value with basic memoization
|
||||
const contextValue = useMemo((): ToolWorkflowContextValue => ({
|
||||
// State
|
||||
|
125
frontend/src/hooks/useUrlSync.ts
Normal file
125
frontend/src/hooks/useUrlSync.ts
Normal file
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* URL synchronization hooks for tool routing
|
||||
*/
|
||||
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { ModeType } from '../contexts/NavigationContext';
|
||||
import { parseToolRoute, updateToolRoute, clearToolRoute } from '../utils/urlRouting';
|
||||
|
||||
/**
|
||||
* Hook to sync navigation mode with URL
|
||||
*/
|
||||
export function useNavigationUrlSync(
|
||||
currentMode: ModeType,
|
||||
setMode: (mode: ModeType) => void,
|
||||
enableSync: boolean = true
|
||||
) {
|
||||
// Initialize mode from URL on mount
|
||||
useEffect(() => {
|
||||
if (!enableSync) return;
|
||||
|
||||
const route = parseToolRoute();
|
||||
if (route.mode !== currentMode) {
|
||||
setMode(route.mode);
|
||||
}
|
||||
}, []); // Only run on mount
|
||||
|
||||
// Update URL when mode changes
|
||||
useEffect(() => {
|
||||
if (!enableSync) return;
|
||||
|
||||
if (currentMode === 'pageEditor') {
|
||||
clearToolRoute();
|
||||
} else {
|
||||
updateToolRoute(currentMode, currentMode);
|
||||
}
|
||||
}, [currentMode, enableSync]);
|
||||
|
||||
// Handle browser back/forward navigation
|
||||
useEffect(() => {
|
||||
if (!enableSync) return;
|
||||
|
||||
const handlePopState = () => {
|
||||
const route = parseToolRoute();
|
||||
if (route.mode !== currentMode) {
|
||||
setMode(route.mode);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
return () => window.removeEventListener('popstate', handlePopState);
|
||||
}, [currentMode, setMode, enableSync]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to sync tool workflow with URL
|
||||
*/
|
||||
export function useToolWorkflowUrlSync(
|
||||
selectedToolKey: string | null,
|
||||
selectTool: (toolKey: string) => void,
|
||||
clearTool: () => void,
|
||||
enableSync: boolean = true
|
||||
) {
|
||||
// Initialize tool from URL on mount
|
||||
useEffect(() => {
|
||||
if (!enableSync) return;
|
||||
|
||||
const route = parseToolRoute();
|
||||
if (route.toolKey && route.toolKey !== selectedToolKey) {
|
||||
selectTool(route.toolKey);
|
||||
} else if (!route.toolKey && selectedToolKey) {
|
||||
clearTool();
|
||||
}
|
||||
}, []); // Only run on mount
|
||||
|
||||
// Update URL when tool changes
|
||||
useEffect(() => {
|
||||
if (!enableSync) return;
|
||||
|
||||
if (selectedToolKey) {
|
||||
const route = parseToolRoute();
|
||||
if (route.toolKey !== selectedToolKey) {
|
||||
updateToolRoute(selectedToolKey as ModeType, selectedToolKey);
|
||||
}
|
||||
}
|
||||
}, [selectedToolKey, enableSync]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get current URL route information
|
||||
*/
|
||||
export function useCurrentRoute() {
|
||||
const getCurrentRoute = useCallback(() => {
|
||||
return parseToolRoute();
|
||||
}, []);
|
||||
|
||||
return getCurrentRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to programmatically navigate to tools
|
||||
*/
|
||||
export function useToolNavigation() {
|
||||
const navigateToTool = useCallback((toolKey: string) => {
|
||||
updateToolRoute(toolKey as ModeType, toolKey);
|
||||
|
||||
// Dispatch a custom event to notify other components
|
||||
window.dispatchEvent(new CustomEvent('toolNavigation', {
|
||||
detail: { toolKey }
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const navigateToHome = useCallback(() => {
|
||||
clearToolRoute();
|
||||
|
||||
// Dispatch a custom event to notify other components
|
||||
window.dispatchEvent(new CustomEvent('toolNavigation', {
|
||||
detail: { toolKey: null }
|
||||
}));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
navigateToTool,
|
||||
navigateToHome
|
||||
};
|
||||
}
|
180
frontend/src/utils/urlRouting.ts
Normal file
180
frontend/src/utils/urlRouting.ts
Normal file
@ -0,0 +1,180 @@
|
||||
/**
|
||||
* URL routing utilities for tool navigation
|
||||
* Provides clean URL routing for the V2 tool system
|
||||
*/
|
||||
|
||||
import { ModeType } from '../contexts/NavigationContext';
|
||||
|
||||
export interface ToolRoute {
|
||||
mode: ModeType;
|
||||
toolKey?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the current URL to extract tool routing information
|
||||
*/
|
||||
export function parseToolRoute(): ToolRoute {
|
||||
const path = window.location.pathname;
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Extract tool from URL path (e.g., /split-pdf -> split)
|
||||
const toolMatch = path.match(/\/([a-zA-Z-]+)(?:-pdf)?$/);
|
||||
if (toolMatch) {
|
||||
const toolKey = toolMatch[1].toLowerCase();
|
||||
|
||||
// Map URL paths to tool keys and modes (excluding internal UI modes)
|
||||
const toolMappings: Record<string, { mode: ModeType; toolKey: string }> = {
|
||||
'split': { mode: 'split', toolKey: 'split' },
|
||||
'merge': { mode: 'merge', toolKey: 'merge' },
|
||||
'compress': { mode: 'compress', toolKey: 'compress' },
|
||||
'convert': { mode: 'convert', toolKey: 'convert' },
|
||||
'add-password': { mode: 'addPassword', toolKey: 'addPassword' },
|
||||
'change-permissions': { mode: 'changePermissions', toolKey: 'changePermissions' },
|
||||
'sanitize': { mode: 'sanitize', toolKey: 'sanitize' },
|
||||
'ocr': { mode: 'ocr', toolKey: 'ocr' }
|
||||
};
|
||||
|
||||
const mapping = toolMappings[toolKey];
|
||||
if (mapping) {
|
||||
return {
|
||||
mode: mapping.mode,
|
||||
toolKey: mapping.toolKey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Check for query parameter fallback (e.g., ?tool=split)
|
||||
const toolParam = searchParams.get('tool');
|
||||
if (toolParam && isValidMode(toolParam)) {
|
||||
return {
|
||||
mode: toolParam as ModeType,
|
||||
toolKey: toolParam
|
||||
};
|
||||
}
|
||||
|
||||
// Default to page editor for home page
|
||||
return {
|
||||
mode: 'pageEditor'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the URL to reflect the current tool selection
|
||||
* Internal UI modes (viewer, fileEditor, pageEditor) don't get URLs
|
||||
*/
|
||||
export function updateToolRoute(mode: ModeType, toolKey?: string): void {
|
||||
const currentPath = window.location.pathname;
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Don't create URLs for internal UI modes
|
||||
if (mode === 'viewer' || mode === 'fileEditor' || mode === 'pageEditor') {
|
||||
// If we're switching to an internal mode, clear any existing tool URL
|
||||
if (currentPath !== '/') {
|
||||
clearToolRoute();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let newPath = '/';
|
||||
|
||||
// Map modes to URL paths (only for actual tools)
|
||||
if (toolKey) {
|
||||
const pathMappings: Record<string, string> = {
|
||||
'split': '/split-pdf',
|
||||
'merge': '/merge-pdf',
|
||||
'compress': '/compress-pdf',
|
||||
'convert': '/convert-pdf',
|
||||
'addPassword': '/add-password-pdf',
|
||||
'changePermissions': '/change-permissions-pdf',
|
||||
'sanitize': '/sanitize-pdf',
|
||||
'ocr': '/ocr-pdf'
|
||||
};
|
||||
|
||||
newPath = pathMappings[toolKey] || `/${toolKey}`;
|
||||
}
|
||||
|
||||
// Remove tool query parameter since we're using path-based routing
|
||||
searchParams.delete('tool');
|
||||
|
||||
// Construct final URL
|
||||
const queryString = searchParams.toString();
|
||||
const fullUrl = newPath + (queryString ? `?${queryString}` : '');
|
||||
|
||||
// Update URL without triggering page reload
|
||||
if (currentPath !== newPath || window.location.search !== (queryString ? `?${queryString}` : '')) {
|
||||
window.history.replaceState(null, '', fullUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear tool routing and return to home page
|
||||
*/
|
||||
export function clearToolRoute(): void {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
searchParams.delete('tool');
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = '/' + (queryString ? `?${queryString}` : '');
|
||||
|
||||
window.history.replaceState(null, '', url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get clean tool name for display purposes
|
||||
*/
|
||||
export function getToolDisplayName(toolKey: string): string {
|
||||
const displayNames: Record<string, string> = {
|
||||
'split': 'Split PDF',
|
||||
'merge': 'Merge PDF',
|
||||
'compress': 'Compress PDF',
|
||||
'convert': 'Convert PDF',
|
||||
'addPassword': 'Add Password',
|
||||
'changePermissions': 'Change Permissions',
|
||||
'sanitize': 'Sanitize PDF',
|
||||
'ocr': 'OCR PDF'
|
||||
};
|
||||
|
||||
return displayNames[toolKey] || toolKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a mode is valid
|
||||
*/
|
||||
function 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate shareable URL for current tool state
|
||||
* Only generates URLs for actual tools, not internal UI modes
|
||||
*/
|
||||
export function generateShareableUrl(mode: ModeType, toolKey?: string): string {
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
// Don't generate URLs for internal UI modes
|
||||
if (mode === 'viewer' || mode === 'fileEditor' || mode === 'pageEditor') {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
if (toolKey) {
|
||||
const pathMappings: Record<string, string> = {
|
||||
'split': '/split-pdf',
|
||||
'merge': '/merge-pdf',
|
||||
'compress': '/compress-pdf',
|
||||
'convert': '/convert-pdf',
|
||||
'addPassword': '/add-password-pdf',
|
||||
'changePermissions': '/change-permissions-pdf',
|
||||
'sanitize': '/sanitize-pdf',
|
||||
'ocr': '/ocr-pdf'
|
||||
};
|
||||
|
||||
const path = pathMappings[toolKey] || `/${toolKey}`;
|
||||
return `${baseUrl}${path}`;
|
||||
}
|
||||
|
||||
return baseUrl;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user