Stirling-PDF/frontend/src/contexts/ToolWorkflowContext.tsx
2025-08-15 16:26:06 +01:00

232 lines
7.3 KiB
TypeScript

/**
* ToolWorkflowContext - Manages tool selection, UI state, and workflow coordination
* Eliminates prop drilling with a single, simple context
*/
import React, { createContext, useContext, useReducer, useCallback, useMemo } from 'react';
import { useToolManagement } from '../hooks/useToolManagement';
import { type ToolRegistryEntry } from '../data/toolRegistry';
import { PageEditorFunctions } from '../types/pageEditor';
// State interface
interface ToolWorkflowState {
// UI State
sidebarsVisible: boolean;
leftPanelView: 'toolPicker' | 'toolContent';
readerMode: boolean;
// File/Preview State
previewFile: File | null;
pageEditorFunctions: PageEditorFunctions | null;
// Search State
searchQuery: string;
}
// Actions
type ToolWorkflowAction =
| { type: 'SET_SIDEBARS_VISIBLE'; payload: boolean }
| { type: 'SET_LEFT_PANEL_VIEW'; payload: 'toolPicker' | 'toolContent' }
| { type: 'SET_READER_MODE'; payload: boolean }
| { type: 'SET_PREVIEW_FILE'; payload: File | null }
| { type: 'SET_PAGE_EDITOR_FUNCTIONS'; payload: PageEditorFunctions | null }
| { type: 'SET_SEARCH_QUERY'; payload: string }
| { type: 'RESET_UI_STATE' };
// Initial state
const initialState: ToolWorkflowState = {
sidebarsVisible: true,
leftPanelView: 'toolPicker',
readerMode: false,
previewFile: null,
pageEditorFunctions: null,
searchQuery: '',
};
// Reducer
function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkflowAction): ToolWorkflowState {
switch (action.type) {
case 'SET_SIDEBARS_VISIBLE':
return { ...state, sidebarsVisible: action.payload };
case 'SET_LEFT_PANEL_VIEW':
return { ...state, leftPanelView: action.payload };
case 'SET_READER_MODE':
return { ...state, readerMode: action.payload };
case 'SET_PREVIEW_FILE':
return { ...state, previewFile: action.payload };
case 'SET_PAGE_EDITOR_FUNCTIONS':
return { ...state, pageEditorFunctions: action.payload };
case 'SET_SEARCH_QUERY':
return { ...state, searchQuery: action.payload };
case 'RESET_UI_STATE':
return { ...initialState, searchQuery: state.searchQuery }; // Preserve search
default:
return state;
}
}
// Context value interface
interface ToolWorkflowContextValue extends ToolWorkflowState {
// Tool management (from hook)
selectedToolKey: string | null;
selectedTool: ToolRegistryEntry | null;
toolRegistry: Record<string, ToolRegistryEntry>; // From useToolManagement
// UI Actions
setSidebarsVisible: (visible: boolean) => void;
setLeftPanelView: (view: 'toolPicker' | 'toolContent') => void;
setReaderMode: (mode: boolean) => void;
setPreviewFile: (file: File | null) => void;
setPageEditorFunctions: (functions: PageEditorFunctions | null) => void;
setSearchQuery: (query: string) => void;
// Tool Actions
selectTool: (toolId: string) => void;
clearToolSelection: () => void;
// Workflow Actions (compound actions)
handleToolSelect: (toolId: string) => void;
handleBackToTools: () => void;
handleReaderToggle: () => void;
// Computed values
filteredTools: [string, ToolRegistryEntry][]; // Filtered by search
isPanelVisible: boolean;
}
const ToolWorkflowContext = createContext<ToolWorkflowContextValue | undefined>(undefined);
// Provider component
interface ToolWorkflowProviderProps {
children: React.ReactNode;
/** Handler for view changes (passed from parent) */
onViewChange?: (view: string) => void;
}
export function ToolWorkflowProvider({ children, onViewChange }: ToolWorkflowProviderProps) {
const [state, dispatch] = useReducer(toolWorkflowReducer, initialState);
// Tool management hook
const {
selectedToolKey,
selectedTool,
toolRegistry,
selectTool,
clearToolSelection,
} = useToolManagement();
// UI Action creators
const setSidebarsVisible = useCallback((visible: boolean) => {
dispatch({ type: 'SET_SIDEBARS_VISIBLE', payload: visible });
}, []);
const setLeftPanelView = useCallback((view: 'toolPicker' | 'toolContent') => {
dispatch({ type: 'SET_LEFT_PANEL_VIEW', payload: view });
}, []);
const setReaderMode = useCallback((mode: boolean) => {
dispatch({ type: 'SET_READER_MODE', payload: mode });
}, []);
const setPreviewFile = useCallback((file: File | null) => {
dispatch({ type: 'SET_PREVIEW_FILE', payload: file });
}, []);
const setPageEditorFunctions = useCallback((functions: PageEditorFunctions | null) => {
dispatch({ type: 'SET_PAGE_EDITOR_FUNCTIONS', payload: functions });
}, []);
const setSearchQuery = useCallback((query: string) => {
dispatch({ type: 'SET_SEARCH_QUERY', payload: query });
}, []);
// Workflow actions (compound actions that coordinate multiple state changes)
const handleToolSelect = useCallback((toolId: string) => {
// Special-case: if tool is a dedicated reader tool, enter reader mode and do not go to toolContent
if (toolId === 'read' || toolId === 'view-pdf') {
setReaderMode(true);
setLeftPanelView('toolPicker');
clearToolSelection();
setSearchQuery('');
return;
}
selectTool(toolId);
onViewChange?.('fileEditor');
setLeftPanelView('toolContent');
setReaderMode(false);
// Clear search so the tool content becomes visible immediately
setSearchQuery('');
}, [selectTool, onViewChange, setLeftPanelView, setReaderMode, setSearchQuery, clearToolSelection]);
const handleBackToTools = useCallback(() => {
setLeftPanelView('toolPicker');
setReaderMode(false);
clearToolSelection();
}, [setLeftPanelView, setReaderMode, clearToolSelection]);
const handleReaderToggle = useCallback(() => {
setReaderMode(true);
}, [setReaderMode]);
// Filter tools based on search query
const filteredTools = useMemo(() => {
if (!toolRegistry) return [];
return Object.entries(toolRegistry).filter(([_, { name }]) =>
name.toLowerCase().includes(state.searchQuery.toLowerCase())
);
}, [toolRegistry, state.searchQuery]);
const isPanelVisible = useMemo(() =>
state.sidebarsVisible && !state.readerMode,
[state.sidebarsVisible, state.readerMode]
);
// Simple context value with basic memoization
const contextValue = useMemo((): ToolWorkflowContextValue => ({
// State
...state,
selectedToolKey,
selectedTool,
toolRegistry,
// Actions
setSidebarsVisible,
setLeftPanelView,
setReaderMode,
setPreviewFile,
setPageEditorFunctions,
setSearchQuery,
selectTool,
clearToolSelection,
// Workflow Actions
handleToolSelect,
handleBackToTools,
handleReaderToggle,
// Computed
filteredTools,
isPanelVisible,
}), [state, selectedToolKey, selectedTool, toolRegistry, filteredTools, isPanelVisible]);
return (
<ToolWorkflowContext.Provider value={contextValue}>
{children}
</ToolWorkflowContext.Provider>
);
}
// Custom hook to use the context
export function useToolWorkflow(): ToolWorkflowContextValue {
const context = useContext(ToolWorkflowContext);
if (!context) {
throw new Error('useToolWorkflow must be used within a ToolWorkflowProvider');
}
return context;
}
// Convenience exports for specific use cases (optional - components can use useToolWorkflow directly)
export const useToolSelection = useToolWorkflow;
export const useToolPanelState = useToolWorkflow;
export const useWorkbenchState = useToolWorkflow;