diff --git a/frontend/src/components/layout/Workbench.tsx b/frontend/src/components/layout/Workbench.tsx index 77a5082a1..8089d1a44 100644 --- a/frontend/src/components/layout/Workbench.tsx +++ b/frontend/src/components/layout/Workbench.tsx @@ -149,7 +149,6 @@ export default function Workbench() { {/* Dismiss All Errors Button */} diff --git a/frontend/src/components/shared/TopControls.tsx b/frontend/src/components/shared/TopControls.tsx index bf42b773f..03eacc801 100644 --- a/frontend/src/components/shared/TopControls.tsx +++ b/frontend/src/components/shared/TopControls.tsx @@ -19,7 +19,7 @@ const viewOptionStyle = { // Build view options showing text always -const createViewOptions = (currentView: WorkbenchType, switchingTo: WorkbenchType | null, isToolSelected: boolean) => { +const createViewOptions = (currentView: WorkbenchType, switchingTo: WorkbenchType | null) => { const viewerOption = { label: (
@@ -75,7 +75,7 @@ const createViewOptions = (currentView: WorkbenchType, switchingTo: WorkbenchTyp // Build options array conditionally return [ viewerOption, - ...(isToolSelected ? [] : [pageEditorOption]), + pageEditorOption, fileEditorOption, ]; }; @@ -83,19 +83,15 @@ const createViewOptions = (currentView: WorkbenchType, switchingTo: WorkbenchTyp interface TopControlsProps { currentView: WorkbenchType; setCurrentView: (view: WorkbenchType) => void; - selectedToolKey?: string | null; } const TopControls = ({ currentView, setCurrentView, - selectedToolKey, -}: TopControlsProps) => { + }: TopControlsProps) => { const { isRainbowMode } = useRainbowThemeContext(); const [switchingTo, setSwitchingTo] = useState(null); - const isToolSelected = selectedToolKey !== null; - const handleViewChange = useCallback((view: string) => { if (!isValidWorkbench(view)) { return; @@ -122,7 +118,7 @@ const TopControls = ({
= ({ setActiveButton }) const { getHomeNavigation } = useSidebarNavigation(); // Determine if the indicator should be visible (do not require selectedTool to be resolved yet) + // Special case: multiTool should always show even when sidebars are hidden const indicatorShouldShow = Boolean( - selectedToolKey && leftPanelView === 'toolContent' && !NAV_IDS.includes(selectedToolKey) + selectedToolKey && + ((leftPanelView === 'toolContent' && !NAV_IDS.includes(selectedToolKey)) || + selectedToolKey === 'multiTool') ); // Local animation and hover state diff --git a/frontend/src/components/shared/quickAccessBar/QuickAccessBar.ts b/frontend/src/components/shared/quickAccessBar/QuickAccessBar.ts index efadc4a72..03e028834 100644 --- a/frontend/src/components/shared/quickAccessBar/QuickAccessBar.ts +++ b/frontend/src/components/shared/quickAccessBar/QuickAccessBar.ts @@ -12,7 +12,7 @@ export const isNavButtonActive = ( isFilesModalOpen: boolean, configModalOpen: boolean, selectedToolKey?: string | null, - leftPanelView?: 'toolPicker' | 'toolContent' + leftPanelView?: 'toolPicker' | 'toolContent' | 'hidden' ): boolean => { const isActiveByLocalState = config.type === 'navigation' && activeButton === config.id; const isActiveByContext = @@ -35,7 +35,7 @@ export const getNavButtonStyle = ( isFilesModalOpen: boolean, configModalOpen: boolean, selectedToolKey?: string | null, - leftPanelView?: 'toolPicker' | 'toolContent' + leftPanelView?: 'toolPicker' | 'toolContent' | 'hidden' ) => { const isActive = isNavButtonActive( config, diff --git a/frontend/src/components/tools/ToolPanel.tsx b/frontend/src/components/tools/ToolPanel.tsx index d3eea3bd9..7f19482bd 100644 --- a/frontend/src/components/tools/ToolPanel.tsx +++ b/frontend/src/components/tools/ToolPanel.tsx @@ -7,6 +7,7 @@ import ToolSearch from './toolPicker/ToolSearch'; import { useSidebarContext } from "../../contexts/SidebarContext"; import rainbowStyles from '../../styles/rainbow.module.css'; import { ScrollArea } from '@mantine/core'; +import { ToolId } from '../../types/toolId'; // No props needed - component uses context @@ -71,7 +72,7 @@ export default function ToolPanel() {
handleToolSelect(id as ToolId)} searchQuery={searchQuery} />
@@ -80,7 +81,7 @@ export default function ToolPanel() {
handleToolSelect(id as ToolId)} filteredTools={filteredTools} isSearching={Boolean(searchQuery && searchQuery.trim().length > 0)} /> diff --git a/frontend/src/components/tools/toolPicker/ToolButton.tsx b/frontend/src/components/tools/toolPicker/ToolButton.tsx index 9f3d60d50..236cbb49f 100644 --- a/frontend/src/components/tools/toolPicker/ToolButton.tsx +++ b/frontend/src/components/tools/toolPicker/ToolButton.tsx @@ -17,7 +17,8 @@ interface ToolButtonProps { } const ToolButton: React.FC = ({ id, tool, isSelected, onSelect, disableNavigation = false, matchedSynonym }) => { - const isUnavailable = !tool.component && !tool.link; + // Special case: read and multiTool are navigational tools that are always available + const isUnavailable = !tool.component && !tool.link && id !== 'read' && id !== 'multiTool'; const { getToolNavigation } = useToolNavigation(); const handleClick = (id: string) => { diff --git a/frontend/src/contexts/ToolWorkflowContext.tsx b/frontend/src/contexts/ToolWorkflowContext.tsx index 106636692..d83c3261c 100644 --- a/frontend/src/contexts/ToolWorkflowContext.tsx +++ b/frontend/src/contexts/ToolWorkflowContext.tsx @@ -17,7 +17,7 @@ import { filterToolRegistryByQuery } from '../utils/toolSearch'; interface ToolWorkflowState { // UI State sidebarsVisible: boolean; - leftPanelView: 'toolPicker' | 'toolContent'; + leftPanelView: 'toolPicker' | 'toolContent' | 'hidden'; readerMode: boolean; // File/Preview State @@ -31,7 +31,7 @@ interface ToolWorkflowState { // Actions type ToolWorkflowAction = | { type: 'SET_SIDEBARS_VISIBLE'; payload: boolean } - | { type: 'SET_LEFT_PANEL_VIEW'; payload: 'toolPicker' | 'toolContent' } + | { type: 'SET_LEFT_PANEL_VIEW'; payload: 'toolPicker' | 'toolContent' | 'hidden' } | { type: 'SET_READER_MODE'; payload: boolean } | { type: 'SET_PREVIEW_FILE'; payload: File | null } | { type: 'SET_PAGE_EDITOR_FUNCTIONS'; payload: PageEditorFunctions | null } @@ -80,7 +80,7 @@ interface ToolWorkflowContextValue extends ToolWorkflowState { // UI Actions setSidebarsVisible: (visible: boolean) => void; - setLeftPanelView: (view: 'toolPicker' | 'toolContent') => void; + setLeftPanelView: (view: 'toolPicker' | 'toolContent' | 'hidden') => void; setReaderMode: (mode: boolean) => void; setPreviewFile: (file: File | null) => void; setPageEditorFunctions: (functions: PageEditorFunctions | null) => void; @@ -96,7 +96,7 @@ interface ToolWorkflowContextValue extends ToolWorkflowState { resetTool: (toolId: string) => void; // Workflow Actions (compound actions) - handleToolSelect: (toolId: string) => void; + handleToolSelect: (toolId: ToolId) => void; handleBackToTools: () => void; handleReaderToggle: () => void; @@ -136,7 +136,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) { dispatch({ type: 'SET_SIDEBARS_VISIBLE', payload: visible }); }, []); - const setLeftPanelView = useCallback((view: 'toolPicker' | 'toolContent') => { + const setLeftPanelView = useCallback((view: 'toolPicker' | 'toolContent' | 'hidden') => { dispatch({ type: 'SET_LEFT_PANEL_VIEW', payload: view }); }, []); @@ -180,7 +180,26 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) { }, []); // Empty dependency array makes this stable // Workflow actions (compound actions that coordinate multiple state changes) - const handleToolSelect = useCallback((toolId: string) => { + const handleToolSelect = useCallback((toolId: ToolId) => { + // Handle read tool selection - should behave exactly like QuickAccessBar read button + if (toolId === 'read') { + setReaderMode(true); + actions.setSelectedTool('read'); + actions.setWorkbench('viewer'); + setSearchQuery(''); + return; + } + + // Handle multiTool selection - enable page editor workbench and hide left panel + if (toolId === 'multiTool') { + setReaderMode(false); + setLeftPanelView('hidden'); + actions.setSelectedTool('multiTool'); + actions.setWorkbench('pageEditor'); + setSearchQuery(''); + return; + } + // Set the selected tool and determine the appropriate workbench const validToolId = isValidToolId(toolId) ? toolId : null; actions.setSelectedTool(validToolId); @@ -195,19 +214,9 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) { // Clear search query when selecting a tool setSearchQuery(''); + setLeftPanelView('toolContent'); + setReaderMode(false); // Disable read mode when selecting tools - // Handle view switching logic - if (toolId === 'allTools' || toolId === 'read' || toolId === 'view-pdf') { - setLeftPanelView('toolPicker'); - if (toolId === 'read' || toolId === 'view-pdf') { - setReaderMode(true); - } else { - setReaderMode(false); - } - } else { - setLeftPanelView('toolContent'); - setReaderMode(false); // Disable read mode when selecting tools - } }, [actions, getSelectedTool, setLeftPanelView, setReaderMode, setSearchQuery]); const handleBackToTools = useCallback(() => { @@ -227,8 +236,8 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) { }, [toolRegistry, state.searchQuery]); const isPanelVisible = useMemo(() => - state.sidebarsVisible && !state.readerMode, - [state.sidebarsVisible, state.readerMode] + state.sidebarsVisible && !state.readerMode && state.leftPanelView !== 'hidden', + [state.sidebarsVisible, state.readerMode, state.leftPanelView] ); // URL sync for proper tool navigation diff --git a/frontend/src/data/useTranslatedToolRegistry.tsx b/frontend/src/data/useTranslatedToolRegistry.tsx index b8b3b1d18..0b1fd5f17 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -178,8 +178,32 @@ export function useFlatToolRegistry(): ToolRegistry { return useMemo(() => { const allTools: ToolRegistry = { + // Recommended Tools in order + multiTool: { + icon: , + name: t("home.multiTool.title", "Multi-Tool"), + component: null, + workbench: "pageEditor", + description: t("home.multiTool.desc", "Use multiple tools on a single PDF document"), + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, + maxFiles: -1, + synonyms: getSynonyms(t, "multiTool"), + }, + merge: { + icon: , + name: t("home.merge.title", "Merge"), + component: Merge, + description: t("home.merge.desc", "Merge multiple PDFs into a single document"), + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, + maxFiles: -1, + endpoints: ["merge-pdfs"], + operationConfig: mergeOperationConfig, + settingsComponent: MergeSettings, + synonyms: getSynonyms(t, "merge") + }, // Signing - certSign: { icon: , name: t("home.certSign.title", "Certificate Sign"), @@ -812,30 +836,7 @@ export function useFlatToolRegistry(): ToolRegistry { settingsComponent: ConvertSettings, synonyms: getSynonyms(t, "convert") }, - merge: { - icon: , - name: t("home.merge.title", "Merge"), - component: Merge, - description: t("home.merge.desc", "Merge multiple PDFs into a single document"), - categoryId: ToolCategoryId.RECOMMENDED_TOOLS, - subcategoryId: SubcategoryId.GENERAL, - maxFiles: -1, - endpoints: ["merge-pdfs"], - operationConfig: mergeOperationConfig, - settingsComponent: MergeSettings, - synonyms: getSynonyms(t, "merge") - }, - multiTool: { - icon: , - name: t("home.multiTool.title", "Multi-Tool"), - component: null, - workbench: "pageEditor", - description: t("home.multiTool.desc", "Use multiple tools on a single PDF document"), - categoryId: ToolCategoryId.RECOMMENDED_TOOLS, - subcategoryId: SubcategoryId.GENERAL, - maxFiles: -1, - synonyms: getSynonyms(t, "multiTool"), - }, + ocr: { icon: , name: t("home.ocr.title", "OCR"), diff --git a/frontend/src/hooks/useToolNavigation.ts b/frontend/src/hooks/useToolNavigation.ts index 704fd5026..5c6fcf47c 100644 --- a/frontend/src/hooks/useToolNavigation.ts +++ b/frontend/src/hooks/useToolNavigation.ts @@ -2,6 +2,7 @@ import { useCallback } from 'react'; import { ToolRegistryEntry, getToolUrlPath } from '../data/toolsTaxonomy'; import { useToolWorkflow } from '../contexts/ToolWorkflowContext'; import { handleUnlessSpecialClick } from '../utils/clickHandlers'; +import { ToolId } from '../types/toolId'; export interface ToolNavigationProps { /** Full URL for the tool (for href attribute) */ @@ -34,7 +35,7 @@ export function useToolNavigation(): { } // Use SPA navigation for internal tools - handleToolSelect(toolId); + handleToolSelect(toolId as ToolId); }); }; @@ -42,4 +43,4 @@ export function useToolNavigation(): { }, [handleToolSelect]); return { getToolNavigation }; -} \ No newline at end of file +} diff --git a/frontend/src/hooks/useToolSections.ts b/frontend/src/hooks/useToolSections.ts index 088a1ba52..5c5be8773 100644 --- a/frontend/src/hooks/useToolSections.ts +++ b/frontend/src/hooks/useToolSections.ts @@ -72,7 +72,10 @@ export function useToolSections( const subcategoryId = s as SubcategoryId; if (!quick[subcategoryId]) quick[subcategoryId] = []; // Only include ready tools (have a component or external link) in Quick Access - const readyTools = tools.filter(({ tool }) => tool.component !== null || !!tool.link); + // Special case: read and multiTool are navigational tools that don't need components + const readyTools = tools.filter(({ tool, id }) => + tool.component !== null || !!tool.link || id === 'read' || id === 'multiTool' + ); quick[subcategoryId].push(...readyTools); }); } diff --git a/frontend/src/hooks/useUrlSync.ts b/frontend/src/hooks/useUrlSync.ts index a90ee4fbe..545285c5e 100644 --- a/frontend/src/hooks/useUrlSync.ts +++ b/frontend/src/hooks/useUrlSync.ts @@ -14,7 +14,7 @@ import { withBasePath } from '../constants/app'; */ export function useNavigationUrlSync( selectedTool: ToolId | null, - handleToolSelect: (toolId: string) => void, + handleToolSelect: (toolId: ToolId) => void, clearToolSelection: () => void, registry: ToolRegistry, enableSync: boolean = true