mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01:00
V2 results flow (#4196)
Better tool flow for reusability Pinning Styling of tool flow consumption of files after tooling --------- Co-authored-by: Connor Yoh <connor@stirlingpdf.com> Co-authored-by: James Brunton <jbrunton96@gmail.com>
This commit is contained in:
@@ -35,6 +35,7 @@ const initialViewerConfig: ViewerConfig = {
|
||||
const initialState: FileContextState = {
|
||||
activeFiles: [],
|
||||
processedFiles: new Map(),
|
||||
pinnedFiles: new Set(),
|
||||
currentMode: 'pageEditor',
|
||||
currentView: 'fileEditor', // Legacy field
|
||||
currentTool: null, // Legacy field
|
||||
@@ -77,6 +78,9 @@ type FileContextAction =
|
||||
| { type: 'SET_UNSAVED_CHANGES'; payload: boolean }
|
||||
| { type: 'SET_PENDING_NAVIGATION'; payload: (() => void) | null }
|
||||
| { type: 'SHOW_NAVIGATION_WARNING'; payload: boolean }
|
||||
| { type: 'PIN_FILE'; payload: File }
|
||||
| { type: 'UNPIN_FILE'; payload: File }
|
||||
| { type: 'CONSUME_FILES'; payload: { inputFiles: File[]; outputFiles: File[] } }
|
||||
| { type: 'RESET_CONTEXT' }
|
||||
| { type: 'LOAD_STATE'; payload: Partial<FileContextState> };
|
||||
|
||||
@@ -317,6 +321,43 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
|
||||
showNavigationWarning: action.payload
|
||||
};
|
||||
|
||||
case 'PIN_FILE':
|
||||
return {
|
||||
...state,
|
||||
pinnedFiles: new Set([...state.pinnedFiles, action.payload])
|
||||
};
|
||||
|
||||
case 'UNPIN_FILE':
|
||||
const newPinnedFiles = new Set(state.pinnedFiles);
|
||||
newPinnedFiles.delete(action.payload);
|
||||
return {
|
||||
...state,
|
||||
pinnedFiles: newPinnedFiles
|
||||
};
|
||||
|
||||
case 'CONSUME_FILES': {
|
||||
const { inputFiles, outputFiles } = action.payload;
|
||||
const unpinnedInputFiles = inputFiles.filter(file => !state.pinnedFiles.has(file));
|
||||
|
||||
// Remove unpinned input files and add output files
|
||||
const newActiveFiles = [
|
||||
...state.activeFiles.filter(file => !unpinnedInputFiles.includes(file)),
|
||||
...outputFiles
|
||||
];
|
||||
|
||||
// Update processed files map - remove consumed files, keep pinned ones
|
||||
const newProcessedFiles = new Map(state.processedFiles);
|
||||
unpinnedInputFiles.forEach(file => {
|
||||
newProcessedFiles.delete(file);
|
||||
});
|
||||
|
||||
return {
|
||||
...state,
|
||||
activeFiles: newActiveFiles,
|
||||
processedFiles: newProcessedFiles
|
||||
};
|
||||
}
|
||||
|
||||
case 'RESET_CONTEXT':
|
||||
return {
|
||||
...initialState
|
||||
@@ -560,6 +601,46 @@ export function FileContextProvider({
|
||||
dispatch({ type: 'CLEAR_SELECTIONS' });
|
||||
}, [cleanupAllFiles]);
|
||||
|
||||
// File pinning functions
|
||||
const pinFile = useCallback((file: File) => {
|
||||
dispatch({ type: 'PIN_FILE', payload: file });
|
||||
}, []);
|
||||
|
||||
const unpinFile = useCallback((file: File) => {
|
||||
dispatch({ type: 'UNPIN_FILE', payload: file });
|
||||
}, []);
|
||||
|
||||
const isFilePinned = useCallback((file: File): boolean => {
|
||||
return state.pinnedFiles.has(file);
|
||||
}, [state.pinnedFiles]);
|
||||
|
||||
// File consumption function
|
||||
const consumeFiles = useCallback(async (inputFiles: File[], outputFiles: File[]): Promise<void> => {
|
||||
dispatch({ type: 'CONSUME_FILES', payload: { inputFiles, outputFiles } });
|
||||
|
||||
// Store new output files if persistence is enabled
|
||||
if (enablePersistence) {
|
||||
for (const file of outputFiles) {
|
||||
try {
|
||||
const fileId = getFileId(file);
|
||||
if (!fileId) {
|
||||
try {
|
||||
const thumbnail = await (thumbnailGenerationService as any).generateThumbnail(file);
|
||||
const storedFile = await fileStorage.storeFile(file, thumbnail);
|
||||
Object.defineProperty(file, 'id', { value: storedFile.id, writable: false });
|
||||
} catch (thumbnailError) {
|
||||
console.warn('Failed to generate thumbnail, storing without:', thumbnailError);
|
||||
const storedFile = await fileStorage.storeFile(file);
|
||||
Object.defineProperty(file, 'id', { value: storedFile.id, writable: false });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to store output file:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [enablePersistence, state.pinnedFiles]);
|
||||
|
||||
// Navigation guard system functions
|
||||
const setHasUnsavedChanges = useCallback((hasChanges: boolean) => {
|
||||
dispatch({ type: 'SET_UNSAVED_CHANGES', payload: hasChanges });
|
||||
@@ -783,6 +864,10 @@ export function FileContextProvider({
|
||||
removeFiles,
|
||||
replaceFile,
|
||||
clearAllFiles,
|
||||
pinFile,
|
||||
unpinFile,
|
||||
isFilePinned,
|
||||
consumeFiles,
|
||||
setCurrentMode,
|
||||
setCurrentView,
|
||||
setCurrentTool,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
||||
import React, { createContext, useContext, useState, useCallback, useEffect, ReactNode } from 'react';
|
||||
import {
|
||||
MaxFiles,
|
||||
FileSelectionContextValue
|
||||
} from '../types/tool';
|
||||
import { useFileContext } from './FileContext';
|
||||
|
||||
interface FileSelectionProviderProps {
|
||||
children: ReactNode;
|
||||
@@ -11,10 +12,23 @@ interface FileSelectionProviderProps {
|
||||
const FileSelectionContext = createContext<FileSelectionContextValue | undefined>(undefined);
|
||||
|
||||
export function FileSelectionProvider({ children }: FileSelectionProviderProps) {
|
||||
const { activeFiles } = useFileContext();
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const [maxFiles, setMaxFiles] = useState<MaxFiles>(-1);
|
||||
const [isToolMode, setIsToolMode] = useState<boolean>(false);
|
||||
|
||||
// Sync selected files with active files - remove any selected files that are no longer active
|
||||
useEffect(() => {
|
||||
if (selectedFiles.length > 0) {
|
||||
const activeFileSet = new Set(activeFiles);
|
||||
const validSelectedFiles = selectedFiles.filter(file => activeFileSet.has(file));
|
||||
|
||||
if (validSelectedFiles.length !== selectedFiles.length) {
|
||||
setSelectedFiles(validSelectedFiles);
|
||||
}
|
||||
}
|
||||
}, [activeFiles, selectedFiles]);
|
||||
|
||||
const clearSelection = useCallback(() => {
|
||||
setSelectedFiles([]);
|
||||
}, []);
|
||||
|
||||
@@ -15,11 +15,11 @@ interface ToolWorkflowState {
|
||||
sidebarsVisible: boolean;
|
||||
leftPanelView: 'toolPicker' | 'toolContent';
|
||||
readerMode: boolean;
|
||||
|
||||
|
||||
// File/Preview State
|
||||
previewFile: File | null;
|
||||
pageEditorFunctions: PageEditorFunctions | null;
|
||||
|
||||
|
||||
// Search State
|
||||
searchQuery: string;
|
||||
}
|
||||
@@ -72,7 +72,7 @@ interface ToolWorkflowContextValue extends ToolWorkflowState {
|
||||
selectedToolKey: string | null;
|
||||
selectedTool: Tool | null;
|
||||
toolRegistry: any; // From useToolManagement
|
||||
|
||||
|
||||
// UI Actions
|
||||
setSidebarsVisible: (visible: boolean) => void;
|
||||
setLeftPanelView: (view: 'toolPicker' | 'toolContent') => void;
|
||||
@@ -80,16 +80,16 @@ interface ToolWorkflowContextValue extends ToolWorkflowState {
|
||||
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, any][]; // Filtered by search
|
||||
isPanelVisible: boolean;
|
||||
@@ -106,7 +106,7 @@ interface ToolWorkflowProviderProps {
|
||||
|
||||
export function ToolWorkflowProvider({ children, onViewChange }: ToolWorkflowProviderProps) {
|
||||
const [state, dispatch] = useReducer(toolWorkflowReducer, initialState);
|
||||
|
||||
|
||||
// Tool management hook
|
||||
const {
|
||||
selectedToolKey,
|
||||
@@ -181,7 +181,7 @@ export function ToolWorkflowProvider({ children, onViewChange }: ToolWorkflowPro
|
||||
);
|
||||
}, [toolRegistry, state.searchQuery]);
|
||||
|
||||
const isPanelVisible = useMemo(() =>
|
||||
const isPanelVisible = useMemo(() =>
|
||||
state.sidebarsVisible && !state.readerMode,
|
||||
[state.sidebarsVisible, state.readerMode]
|
||||
);
|
||||
@@ -193,7 +193,7 @@ export function ToolWorkflowProvider({ children, onViewChange }: ToolWorkflowPro
|
||||
selectedToolKey,
|
||||
selectedTool,
|
||||
toolRegistry,
|
||||
|
||||
|
||||
// Actions
|
||||
setSidebarsVisible,
|
||||
setLeftPanelView,
|
||||
@@ -203,12 +203,12 @@ export function ToolWorkflowProvider({ children, onViewChange }: ToolWorkflowPro
|
||||
setSearchQuery,
|
||||
selectTool,
|
||||
clearToolSelection,
|
||||
|
||||
|
||||
// Workflow Actions
|
||||
handleToolSelect,
|
||||
handleBackToTools,
|
||||
handleReaderToggle,
|
||||
|
||||
|
||||
// Computed
|
||||
filteredTools,
|
||||
isPanelVisible,
|
||||
@@ -232,5 +232,5 @@ export function useToolWorkflow(): ToolWorkflowContextValue {
|
||||
|
||||
// Convenience exports for specific use cases (optional - components can use useToolWorkflow directly)
|
||||
export const useToolSelection = useToolWorkflow;
|
||||
export const useToolPanelState = useToolWorkflow;
|
||||
export const useWorkbenchState = useToolWorkflow;
|
||||
export const useToolPanelState = useToolWorkflow;
|
||||
export const useWorkbenchState = useToolWorkflow;
|
||||
|
||||
Reference in New Issue
Block a user