Added multi tool and read to tool list.

multitool top of quick access
Page editor always avaialble
This commit is contained in:
Connor Yoh 2025-09-26 15:28:05 +01:00
parent 0bdc6466ca
commit b1cec32ef9
11 changed files with 78 additions and 64 deletions

View File

@ -149,7 +149,6 @@ export default function Workbench() {
<TopControls
currentView={currentView}
setCurrentView={setCurrentView}
selectedToolKey={selectedToolId}
/>
{/* Dismiss All Errors Button */}

View File

@ -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: (
<div style={viewOptionStyle as React.CSSProperties}>
@ -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<WorkbenchType | null>(null);
const isToolSelected = selectedToolKey !== null;
const handleViewChange = useCallback((view: string) => {
if (!isValidWorkbench(view)) {
return;
@ -122,7 +118,7 @@ const TopControls = ({
<div className="absolute left-0 w-full top-0 z-[100] pointer-events-none">
<div className="flex justify-center mt-[0.5rem]">
<SegmentedControl
data={createViewOptions(currentView, switchingTo, isToolSelected)}
data={createViewOptions(currentView, switchingTo)}
value={currentView}
onChange={handleViewChange}
color="blue"

View File

@ -33,8 +33,11 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ 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

View File

@ -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,

View File

@ -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() {
<div className="flex-1 flex flex-col overflow-y-auto">
<SearchResults
filteredTools={filteredTools}
onSelect={handleToolSelect}
onSelect={(id) => handleToolSelect(id as ToolId)}
searchQuery={searchQuery}
/>
</div>
@ -80,7 +81,7 @@ export default function ToolPanel() {
<div className="flex-1 flex flex-col overflow-auto">
<ToolPicker
selectedToolKey={selectedToolKey}
onSelect={handleToolSelect}
onSelect={(id) => handleToolSelect(id as ToolId)}
filteredTools={filteredTools}
isSearching={Boolean(searchQuery && searchQuery.trim().length > 0)}
/>

View File

@ -17,7 +17,8 @@ interface ToolButtonProps {
}
const ToolButton: React.FC<ToolButtonProps> = ({ 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) => {

View File

@ -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

View File

@ -178,8 +178,32 @@ export function useFlatToolRegistry(): ToolRegistry {
return useMemo(() => {
const allTools: ToolRegistry = {
// Recommended Tools in order
multiTool: {
icon: <LocalIcon icon="dashboard-customize-rounded" width="1.5rem" height="1.5rem" />,
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: <LocalIcon icon="library-add-rounded" width="1.5rem" height="1.5rem" />,
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: <LocalIcon icon="workspace-premium-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.certSign.title", "Certificate Sign"),
@ -812,30 +836,7 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: ConvertSettings,
synonyms: getSynonyms(t, "convert")
},
merge: {
icon: <LocalIcon icon="library-add-rounded" width="1.5rem" height="1.5rem" />,
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: <LocalIcon icon="dashboard-customize-rounded" width="1.5rem" height="1.5rem" />,
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: <LocalIcon icon="quick-reference-all-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.ocr.title", "OCR"),

View File

@ -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 };
}
}

View File

@ -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);
});
}

View File

@ -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