From 0eefc734f52ade39d4680564944934596af7aa18 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 5 Oct 2025 23:00:44 +0100 Subject: [PATCH] colour and fixes --- .../main/resources/messages_en_GB.properties | 4 + .../src/components/tools/LegacyToolList.tsx | 474 ++++++++++++------ frontend/src/components/tools/ToolPanel.css | 170 ++++++- frontend/src/components/tools/ToolPanel.tsx | 2 +- frontend/src/contexts/ToolWorkflowContext.tsx | 40 +- frontend/src/data/toolsTaxonomy.ts | 62 ++- frontend/src/hooks/tools/useToolHistory.ts | 92 ++++ 7 files changed, 654 insertions(+), 190 deletions(-) create mode 100644 frontend/src/hooks/tools/useToolHistory.ts diff --git a/app/core/src/main/resources/messages_en_GB.properties b/app/core/src/main/resources/messages_en_GB.properties index 1d19d5465..71d876806 100644 --- a/app/core/src/main/resources/messages_en_GB.properties +++ b/app/core/src/main/resources/messages_en_GB.properties @@ -1977,3 +1977,7 @@ viewer.nextPage=Next Page viewer.pageNavigation=Page Navigation viewer.currentPage=Current Page viewer.totalPages=Total Pages +toolPanel.legacy.favorites=Favourites +toolPanel.legacy.recent=Recently used +toolPanel.legacy.favorite=Add to favourites +toolPanel.legacy.unfavorite=Remove from favourites diff --git a/frontend/src/components/tools/LegacyToolList.tsx b/frontend/src/components/tools/LegacyToolList.tsx index c4e2c16da..64bde4291 100644 --- a/frontend/src/components/tools/LegacyToolList.tsx +++ b/frontend/src/components/tools/LegacyToolList.tsx @@ -1,12 +1,17 @@ import React, { useMemo } from 'react'; -import { Badge, Text } from '@mantine/core'; +import { ActionIcon, Badge, Text } from '@mantine/core'; import { Tooltip } from '../shared/Tooltip'; import { useTranslation } from 'react-i18next'; -import { ToolRegistryEntry } from '../../data/toolsTaxonomy'; +import { ToolRegistryEntry, getSubcategoryLabel, getSubcategoryColor, getSubcategoryIcon } from '../../data/toolsTaxonomy'; import { ToolId } from '../../types/toolId'; import { useToolSections } from '../../hooks/useToolSections'; -import { getSubcategoryLabel } from '../../data/toolsTaxonomy'; import NoToolsFound from './shared/NoToolsFound'; +import { useHotkeys } from '../../contexts/HotkeyContext'; +import HotkeyDisplay from '../hotkeys/HotkeyDisplay'; +import { useToolWorkflow } from '../../contexts/ToolWorkflowContext'; +import StarRoundedIcon from '@mui/icons-material/StarRounded'; +import StarBorderRoundedIcon from '@mui/icons-material/StarBorderRounded'; +import HistoryRoundedIcon from '@mui/icons-material/HistoryRounded'; import './ToolPanel.css'; interface LegacyToolListProps { @@ -27,11 +32,36 @@ const LegacyToolList = ({ onSelect, }: LegacyToolListProps) => { const { t } = useTranslation(); + const { hotkeys } = useHotkeys(); + const { toolRegistry, recentTools, favoriteTools, toggleFavorite, isFavorite } = useToolWorkflow(); const { sections, searchGroups } = useToolSections(filteredTools, searchQuery); const tooltipPortalTarget = typeof document !== 'undefined' ? document.body : undefined; + // Prepare recent and favorite tool items + const recentToolItems = useMemo(() => { + return recentTools + .map((toolId) => { + const tool = toolRegistry[toolId]; + return tool ? { id: toolId, tool } : null; + }) + .filter(Boolean) + .slice(0, 6); // Show max 6 recent tools + }, [recentTools, toolRegistry]); + + const favoriteToolItems = useMemo(() => { + return favoriteTools + .map((toolId) => { + const tool = toolRegistry[toolId]; + return tool ? { id: toolId, tool } : null; + }) + .filter(Boolean); + }, [favoriteTools, toolRegistry]); + + // Show recent/favorites section only when not searching + const showRecentFavorites = searchQuery.trim().length === 0 && (recentToolItems.length > 0 || favoriteToolItems.length > 0); + const subcategoryGroups = useMemo(() => { if (searchQuery.trim().length > 0) { return searchGroups; @@ -40,7 +70,7 @@ const LegacyToolList = ({ return allSection ? allSection.subcategories : []; }, [searchGroups, sections, searchQuery]); - if (subcategoryGroups.length === 0) { + if (subcategoryGroups.length === 0 && !showRecentFavorites) { return (
@@ -55,158 +85,296 @@ const LegacyToolList = ({ ? 'tool-panel__legacy-groups tool-panel__legacy-groups--detailed' : 'tool-panel__legacy-groups tool-panel__legacy-groups--compact'; + // Helper function to render a tool item + const renderToolItem = (id: string, tool: ToolRegistryEntry) => { + const matchedText = matchedTextMap.get(id); + const isSelected = selectedToolKey === id; + const isDisabled = !tool.component && !tool.link && id !== 'read' && id !== 'multiTool'; + const binding = hotkeys[id]; + const isFav = isFavorite(id as ToolId); + const categoryColor = getSubcategoryColor(tool.subcategoryId); + + let iconNode: React.ReactNode = null; + if (React.isValidElement<{ style?: React.CSSProperties }>(tool.icon)) { + const element = tool.icon as React.ReactElement<{ style?: React.CSSProperties }>; + iconNode = React.cloneElement(element, { + style: { + ...(element.props.style || {}), + fontSize: showDescriptions ? '1.75rem' : '1.5rem', + }, + }); + } else { + iconNode = tool.icon; + } + + const handleClick = () => { + if (isDisabled) return; + if (tool.link) { + window.open(tool.link, '_blank', 'noopener,noreferrer'); + return; + } + onSelect(id as ToolId); + }; + + const handleStarClick = (e: React.MouseEvent) => { + e.stopPropagation(); + toggleFavorite(id as ToolId); + }; + + // Detailed view + if (showDescriptions) { + return ( + + ); + } + + // Compact view + const compactButton = ( + + ); + + const tooltipContent = ( +
+ {tool.description} + {binding && ( +
+ + {t('settings.hotkeys.shortcut', 'Shortcut')} + + +
+ )} +
+ ); + + return ( + + {compactButton} + + ); + }; + return (
- {subcategoryGroups.map(({ subcategoryId, tools }) => ( -
-
- - {getSubcategoryLabel(t, subcategoryId)} - - - {tools.length} - -
- - {showDescriptions ? ( -
- {tools.map(({ id, tool }) => { - const matchedText = matchedTextMap.get(id); - const isSelected = selectedToolKey === id; - const isDisabled = !tool.component && !tool.link && id !== 'read' && id !== 'multiTool'; - - let iconNode: React.ReactNode = null; - if (React.isValidElement<{ style?: React.CSSProperties }>(tool.icon)) { - const element = tool.icon as React.ReactElement<{ style?: React.CSSProperties }>; - iconNode = React.cloneElement(element, { - style: { - ...(element.props.style || {}), - fontSize: '1.75rem', - }, - }); - } else { - iconNode = tool.icon; - } - - const handleClick = () => { - if (isDisabled) return; - if (tool.link) { - window.open(tool.link, '_blank', 'noopener,noreferrer'); - return; - } - onSelect(id as ToolId); - }; - - return ( - - ); - })} -
- ) : ( -
- {tools.map(({ id, tool }) => { - const matchedText = matchedTextMap.get(id); - const isSelected = selectedToolKey === id; - const isDisabled = !tool.component && !tool.link && id !== 'read' && id !== 'multiTool'; - - let iconNode: React.ReactNode = null; - if (React.isValidElement<{ style?: React.CSSProperties }>(tool.icon)) { - const element = tool.icon as React.ReactElement<{ style?: React.CSSProperties }>; - iconNode = React.cloneElement(element, { - style: { - ...(element.props.style || {}), - fontSize: '1.5rem', - }, - }); - } else { - iconNode = tool.icon; - } - - const handleClick = () => { - if (isDisabled) return; - if (tool.link) { - window.open(tool.link, '_blank', 'noopener,noreferrer'); - return; - } - onSelect(id as ToolId); - }; - - const baseButton = ( - - ); - - if (showDescriptions || !tool.description) { - return React.cloneElement(baseButton, { key: id }); - } - - return ( - - {baseButton} - - ); - })} -
+ {showRecentFavorites && ( + <> + {favoriteToolItems.length > 0 && ( +
+
+
+ + + + + {t('toolPanel.legacy.favorites', 'Favourites')} + +
+ + {favoriteToolItems.length} + +
+ {showDescriptions ? ( +
+ {favoriteToolItems.map((item: any) => renderToolItem(item.id, item.tool))} +
+ ) : ( +
+ {favoriteToolItems.map((item: any) => renderToolItem(item.id, item.tool))} +
+ )} +
)} -
- ))} + + {recentToolItems.length > 0 && ( +
+
+
+ + + + + {t('toolPanel.legacy.recent', 'Recently used')} + +
+ + {recentToolItems.length} + +
+ {showDescriptions ? ( +
+ {recentToolItems.map((item: any) => renderToolItem(item.id, item.tool))} +
+ ) : ( +
+ {recentToolItems.map((item: any) => renderToolItem(item.id, item.tool))} +
+ )} +
+ )} + + )} + + {subcategoryGroups.map(({ subcategoryId, tools }) => { + const categoryColor = getSubcategoryColor(subcategoryId); + + return ( +
+
+
+ + {getSubcategoryIcon(subcategoryId)} + + + {getSubcategoryLabel(t, subcategoryId)} + +
+ + {tools.length} + +
+ + {showDescriptions ? ( +
+ {tools.map(({ id, tool }) => renderToolItem(id, tool))} +
+ ) : ( +
+ {tools.map(({ id, tool }) => renderToolItem(id, tool))} +
+ )} +
+ ); + })}
); }; diff --git a/frontend/src/components/tools/ToolPanel.css b/frontend/src/components/tools/ToolPanel.css index 19536c361..17dba0406 100644 --- a/frontend/src/components/tools/ToolPanel.css +++ b/frontend/src/components/tools/ToolPanel.css @@ -2,9 +2,9 @@ .tool-panel__legacy-surface-inner { --legacy-bg-surface-1: color-mix(in srgb, var(--bg-toolbar) 96%, transparent); --legacy-bg-surface-2: color-mix(in srgb, var(--bg-background) 90%, transparent); - --legacy-bg-header: color-mix(in srgb, var(--bg-toolbar) 86%, transparent); - --legacy-bg-controls-1: color-mix(in srgb, var(--bg-toolbar) 84%, transparent); - --legacy-bg-controls-2: color-mix(in srgb, var(--bg-background) 72%, transparent); + --legacy-bg-header: var(--bg-toolbar); + --legacy-bg-controls-1: var(--bg-toolbar); + --legacy-bg-controls-2: color-mix(in srgb, var(--bg-toolbar) 95%, var(--bg-background)); --legacy-bg-body-1: color-mix(in srgb, var(--bg-background) 86%, transparent); --legacy-bg-body-2: color-mix(in srgb, var(--bg-toolbar) 78%, transparent); --legacy-bg-group: color-mix(in srgb, var(--bg-toolbar) 82%, transparent); @@ -87,7 +87,7 @@ height: 100%; display: flex; flex-direction: column; - border-radius: 0 1.25rem 1.25rem 0; + border-radius: 0; background: linear-gradient( 140deg, @@ -111,34 +111,36 @@ .tool-panel__legacy-header { display: flex; justify-content: space-between; - align-items: flex-start; + align-items: center; gap: 1rem; - padding: 1.25rem 1.75rem; - border-bottom: 1px solid var(--legacy-border-subtle-70); - background: linear-gradient( - 180deg, - var(--legacy-bg-header), - transparent 85% - ); + padding: 0.75rem 1.75rem; + border-bottom: 1px solid var(--border-subtle); + background: var(--bg-toolbar); } -.tool-panel__legacy-heading { +.tool-panel__legacy-brand { display: flex; - flex-direction: column; - gap: 0.3rem; + align-items: center; + gap: 0.625rem; +} + +.tool-panel__legacy-brand-icon { + height: 1.75rem; + width: auto; +} + +.tool-panel__legacy-brand-text { + height: 1.25rem; + width: auto; } .tool-panel__legacy-controls { display: flex; align-items: center; gap: 1rem; - padding: 1rem 1.75rem; - border-bottom: 1px solid var(--legacy-border-subtle-70); - background: linear-gradient( - 180deg, - var(--legacy-bg-controls-1), - var(--legacy-bg-controls-2) - ); + padding: 0.75rem 1.75rem; + border-bottom: 1px solid var(--tool-panel-search-border-bottom); + background: var(--tool-panel-search-bg); } .tool-panel__legacy-controls .search-input-container { @@ -188,10 +190,24 @@ .tool-panel__legacy-section-header { display: flex; align-items: center; + justify-content: space-between; gap: 0.5rem; padding: 0.1rem 0.15rem 0.35rem; } +.tool-panel__legacy-section-title { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.tool-panel__legacy-section-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 1.1rem; +} + .tool-panel__legacy-grid { display: grid; gap: 0.75rem; @@ -244,6 +260,51 @@ min-height: 7.5rem; } +.tool-panel__legacy-item--with-star { + position: relative; +} + +.tool-panel__legacy-star { + position: absolute; + top: 0.5rem; + right: 0.5rem; + opacity: 0; + transition: opacity 0.2s ease; + z-index: 2; +} + +.tool-panel__legacy-item:hover .tool-panel__legacy-star, +.tool-panel__legacy-star:focus { + opacity: 1; +} + +.tool-panel__legacy-list-item--with-star { + position: relative; +} + +.tool-panel__legacy-star-compact { + position: absolute; + top: 0.35rem; + right: 0.35rem; + opacity: 0; + transition: opacity 0.2s ease; + z-index: 2; +} + +.tool-panel__legacy-list-item:hover .tool-panel__legacy-star-compact, +.tool-panel__legacy-star-compact:focus { + opacity: 1; +} + +.tool-panel__legacy-group--special { + background: linear-gradient( + 135deg, + color-mix(in srgb, var(--legacy-bg-group) 95%, var(--text-primary) 5%), + var(--legacy-bg-group) + ); + border-width: 1.5px; +} + .tool-panel__legacy-icon { display: flex; align-items: center; @@ -254,10 +315,37 @@ background: var(--legacy-bg-icon-detailed); color: var(--legacy-text-icon); flex-shrink: 0; + transition: transform 0.2s ease, box-shadow 0.2s ease; + position: relative; + overflow: hidden; +} + +.tool-panel__legacy-icon::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, + color-mix(in srgb, var(--text-primary) 12%, transparent), + color-mix(in srgb, var(--text-primary) 4%, transparent) + ); + opacity: 0; + transition: opacity 0.2s ease; + border-radius: 0.75rem; +} + +.tool-panel__legacy-item:hover:not([aria-disabled="true"]) .tool-panel__legacy-icon { + transform: scale(1.08); + box-shadow: 0 4px 12px color-mix(in srgb, var(--text-primary) 15%, transparent); +} + +.tool-panel__legacy-item:hover:not([aria-disabled="true"]) .tool-panel__legacy-icon::before { + opacity: 1; } .tool-panel__legacy-icon svg { font-size: 1.65rem; + position: relative; + z-index: 1; } .tool-panel__legacy-body { @@ -279,6 +367,13 @@ font-style: italic; } +.tool-panel__legacy-shortcut { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 0.2rem; +} + .tool-panel__legacy-list { display: flex; flex-direction: column; @@ -298,6 +393,7 @@ border: 1px solid transparent; width: 100%; box-sizing: border-box; + position: relative; } .tool-panel__legacy-list-item[aria-disabled="true"], @@ -326,6 +422,36 @@ background: var(--legacy-bg-icon-compact); color: var(--legacy-text-icon-compact); flex-shrink: 0; + transition: transform 0.2s ease, box-shadow 0.2s ease; + position: relative; + overflow: hidden; +} + +.tool-panel__legacy-list-icon::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, + color-mix(in srgb, var(--text-primary) 10%, transparent), + color-mix(in srgb, var(--text-primary) 3%, transparent) + ); + opacity: 0; + transition: opacity 0.2s ease; + border-radius: 0.6rem; +} + +.tool-panel__legacy-list-item:hover:not([aria-disabled="true"]) .tool-panel__legacy-list-icon { + transform: scale(1.06); + box-shadow: 0 2px 8px color-mix(in srgb, var(--text-primary) 12%, transparent); +} + +.tool-panel__legacy-list-item:hover:not([aria-disabled="true"]) .tool-panel__legacy-list-icon::before { + opacity: 1; +} + +.tool-panel__legacy-list-icon svg { + position: relative; + z-index: 1; } .tool-panel__legacy-list-body { diff --git a/frontend/src/components/tools/ToolPanel.tsx b/frontend/src/components/tools/ToolPanel.tsx index 75c1462ef..bd7b11cd3 100644 --- a/frontend/src/components/tools/ToolPanel.tsx +++ b/frontend/src/components/tools/ToolPanel.tsx @@ -46,7 +46,7 @@ export default function ToolPanel() { const legacyExpanded = isLegacyMode && leftPanelView === 'toolPicker' && !isMobile; // Use custom hooks for state management - const [showLegacyDescriptions, setShowLegacyDescriptions] = useLocalStorageState('legacyToolDescriptions', true); + const [showLegacyDescriptions, setShowLegacyDescriptions] = useLocalStorageState('legacyToolDescriptions', false); const legacyGeometry = useToolPanelGeometry({ enabled: legacyExpanded, toolPanelRef, diff --git a/frontend/src/contexts/ToolWorkflowContext.tsx b/frontend/src/contexts/ToolWorkflowContext.tsx index 076330eda..6d8801326 100644 --- a/frontend/src/contexts/ToolWorkflowContext.tsx +++ b/frontend/src/contexts/ToolWorkflowContext.tsx @@ -12,6 +12,7 @@ import { ToolId, isValidToolId } from '../types/toolId'; import { useNavigationUrlSync } from '../hooks/useUrlSync'; import { getDefaultWorkbench } from '../types/workbench'; import { filterToolRegistryByQuery } from '../utils/toolSearch'; +import { useToolHistory } from '../hooks/tools/useToolHistory'; // State interface type ToolPanelMode = 'sidebar' | 'legacy'; @@ -134,6 +135,13 @@ interface ToolWorkflowContextValue extends ToolWorkflowState { // Computed values filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>; // Filtered by search isPanelVisible: boolean; + + // Tool History + recentTools: ToolId[]; + favoriteTools: ToolId[]; + addToRecent: (toolId: ToolId) => void; + toggleFavorite: (toolId: ToolId) => void; + isFavorite: (toolId: ToolId) => boolean; } const ToolWorkflowContext = createContext(undefined); @@ -159,6 +167,15 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) { getSelectedTool, } = useToolManagement(); + // Tool history hook + const { + recentTools, + favoriteTools, + addToRecent, + toggleFavorite, + isFavorite, + } = useToolHistory(); + // Get selected tool from navigation context const selectedTool = getSelectedTool(navigationState.selectedTool); @@ -224,19 +241,24 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) { // Workflow actions (compound actions that coordinate multiple state changes) const handleToolSelect = useCallback((toolId: ToolId) => { + // Track tool usage in recent history + addToRecent(toolId); + // Handle read tool selection - should behave exactly like QuickAccessBar read button if (toolId === 'read') { setReaderMode(true); actions.setSelectedTool('read'); actions.setWorkbench('viewer'); setSearchQuery(''); + setToolPanelMode('sidebar'); // Close legacy mode when switching to reader + setLeftPanelView('toolPicker'); // Show tool picker when navigating back to tools return; } - // Handle multiTool selection - enable page editor workbench and hide left panel + // Handle multiTool selection - enable page editor workbench if (toolId === 'multiTool') { setReaderMode(false); - setLeftPanelView('hidden'); + setLeftPanelView('toolPicker'); // Show tool picker when navigating back to tools in mobile actions.setSelectedTool('multiTool'); actions.setWorkbench('pageEditor'); setSearchQuery(''); @@ -259,7 +281,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) { setSearchQuery(''); setLeftPanelView('toolContent'); setReaderMode(false); // Disable read mode when selecting tools - }, [actions, getSelectedTool, setLeftPanelView, setReaderMode, setSearchQuery]); + }, [actions, getSelectedTool, setLeftPanelView, setReaderMode, setSearchQuery, addToRecent]); const handleBackToTools = useCallback(() => { setLeftPanelView('toolPicker'); @@ -324,6 +346,13 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) { // Computed filteredTools, isPanelVisible, + + // Tool History + recentTools, + favoriteTools, + addToRecent, + toggleFavorite, + isFavorite, }), [ state, navigationState.selectedTool, @@ -345,6 +374,11 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) { handleReaderToggle, filteredTools, isPanelVisible, + recentTools, + favoriteTools, + addToRecent, + toggleFavorite, + isFavorite, ]); return ( diff --git a/frontend/src/data/toolsTaxonomy.ts b/frontend/src/data/toolsTaxonomy.ts index 79890dc6d..7033046f4 100644 --- a/frontend/src/data/toolsTaxonomy.ts +++ b/frontend/src/data/toolsTaxonomy.ts @@ -4,6 +4,17 @@ import { ToolOperationConfig } from '../hooks/tools/shared/useToolOperation'; import { BaseToolProps } from '../types/tool'; import { WorkbenchType } from '../types/workbench'; import { ToolId } from '../types/toolId'; +import DrawRoundedIcon from '@mui/icons-material/DrawRounded'; +import SecurityRoundedIcon from '@mui/icons-material/SecurityRounded'; +import VerifiedUserRoundedIcon from '@mui/icons-material/VerifiedUserRounded'; +import RateReviewRoundedIcon from '@mui/icons-material/RateReviewRounded'; +import ViewAgendaRoundedIcon from '@mui/icons-material/ViewAgendaRounded'; +import FileDownloadRoundedIcon from '@mui/icons-material/FileDownloadRounded'; +import DeleteSweepRoundedIcon from '@mui/icons-material/DeleteSweepRounded'; +import SmartToyRoundedIcon from '@mui/icons-material/SmartToyRounded'; +import BuildRoundedIcon from '@mui/icons-material/BuildRounded'; +import TuneRoundedIcon from '@mui/icons-material/TuneRounded'; +import CodeRoundedIcon from '@mui/icons-material/CodeRounded'; export enum SubcategoryId { SIGNING = 'signing', @@ -66,17 +77,46 @@ export const SUBCATEGORY_ORDER: SubcategoryId[] = [ ]; export const SUBCATEGORY_COLOR_MAP: Record = { - [SubcategoryId.SIGNING]: '#FF7892', - [SubcategoryId.DOCUMENT_SECURITY]: '#FF7892', - [SubcategoryId.VERIFICATION]: '#1BB1D4', - [SubcategoryId.DOCUMENT_REVIEW]: '#48BD54', - [SubcategoryId.PAGE_FORMATTING]: '#7882FF', - [SubcategoryId.EXTRACTION]: '#1BB1D4', - [SubcategoryId.REMOVAL]: '#7882FF', - [SubcategoryId.AUTOMATION]: '#69DC95', - [SubcategoryId.GENERAL]: '#69DC95', - [SubcategoryId.ADVANCED_FORMATTING]: '#F55454', - [SubcategoryId.DEVELOPER_TOOLS]: '#F55454', + [SubcategoryId.SIGNING]: '#E91E63', // Rose Pink + [SubcategoryId.DOCUMENT_SECURITY]: '#D32F2F', // Deep Red + [SubcategoryId.VERIFICATION]: '#1976D2', // Medium Blue + [SubcategoryId.DOCUMENT_REVIEW]: '#388E3C', // Forest Green + [SubcategoryId.PAGE_FORMATTING]: '#5E35B1', // Deep Purple + [SubcategoryId.EXTRACTION]: '#F57C00', // Amber/Gold + [SubcategoryId.REMOVAL]: '#E64A19', // Rust Orange + [SubcategoryId.AUTOMATION]: '#00897B', // Teal + [SubcategoryId.GENERAL]: '#689F38', // Olive Green + [SubcategoryId.ADVANCED_FORMATTING]: '#8E24AA', // Deep Magenta + [SubcategoryId.DEVELOPER_TOOLS]: '#455A64', // Slate Grey +}; + +export const getSubcategoryIcon = (subcategory: SubcategoryId): React.ReactNode => { + switch (subcategory) { + case SubcategoryId.SIGNING: + return React.createElement(DrawRoundedIcon); + case SubcategoryId.DOCUMENT_SECURITY: + return React.createElement(SecurityRoundedIcon); + case SubcategoryId.VERIFICATION: + return React.createElement(VerifiedUserRoundedIcon); + case SubcategoryId.DOCUMENT_REVIEW: + return React.createElement(RateReviewRoundedIcon); + case SubcategoryId.PAGE_FORMATTING: + return React.createElement(ViewAgendaRoundedIcon); + case SubcategoryId.EXTRACTION: + return React.createElement(FileDownloadRoundedIcon); + case SubcategoryId.REMOVAL: + return React.createElement(DeleteSweepRoundedIcon); + case SubcategoryId.AUTOMATION: + return React.createElement(SmartToyRoundedIcon); + case SubcategoryId.GENERAL: + return React.createElement(BuildRoundedIcon); + case SubcategoryId.ADVANCED_FORMATTING: + return React.createElement(TuneRoundedIcon); + case SubcategoryId.DEVELOPER_TOOLS: + return React.createElement(CodeRoundedIcon); + default: + return React.createElement(BuildRoundedIcon); + } }; export const getCategoryLabel = (t: TFunction, id: ToolCategoryId): string => t(`toolPicker.categories.${id}`, id); diff --git a/frontend/src/hooks/tools/useToolHistory.ts b/frontend/src/hooks/tools/useToolHistory.ts new file mode 100644 index 000000000..8bae5b324 --- /dev/null +++ b/frontend/src/hooks/tools/useToolHistory.ts @@ -0,0 +1,92 @@ +import { useState, useEffect, useCallback } from 'react'; +import { ToolId } from '../../types/toolId'; + +const RECENT_TOOLS_KEY = 'stirlingpdf.recentTools'; +const FAVORITE_TOOLS_KEY = 'stirlingpdf.favoriteTools'; +const MAX_RECENT_TOOLS = 10; + +interface ToolHistoryData { + recentTools: ToolId[]; + favoriteTools: ToolId[]; +} + +export function useToolHistory() { + const [recentTools, setRecentTools] = useState([]); + const [favoriteTools, setFavoriteTools] = useState([]); + + // Load from localStorage on mount + useEffect(() => { + if (typeof window === 'undefined') { + return; + } + + const recentStr = window.localStorage.getItem(RECENT_TOOLS_KEY); + const favoritesStr = window.localStorage.getItem(FAVORITE_TOOLS_KEY); + + if (recentStr) { + try { + const recent = JSON.parse(recentStr) as ToolId[]; + setRecentTools(recent); + } catch { + // Ignore parse errors + } + } + + if (favoritesStr) { + try { + const favorites = JSON.parse(favoritesStr) as ToolId[]; + setFavoriteTools(favorites); + } catch { + // Ignore parse errors + } + } + }, []); + + // Add a tool to recent history + const addToRecent = useCallback((toolId: ToolId) => { + if (typeof window === 'undefined') { + return; + } + + setRecentTools((prev) => { + // Remove if already exists + const filtered = prev.filter((id) => id !== toolId); + // Add to front + const updated = [toolId, ...filtered].slice(0, MAX_RECENT_TOOLS); + window.localStorage.setItem(RECENT_TOOLS_KEY, JSON.stringify(updated)); + return updated; + }); + }, []); + + // Toggle favorite status + const toggleFavorite = useCallback((toolId: ToolId) => { + if (typeof window === 'undefined') { + return; + } + + setFavoriteTools((prev) => { + const isFavorite = prev.includes(toolId); + const updated = isFavorite + ? prev.filter((id) => id !== toolId) + : [...prev, toolId]; + window.localStorage.setItem(FAVORITE_TOOLS_KEY, JSON.stringify(updated)); + return updated; + }); + }, []); + + // Check if a tool is favorited + const isFavorite = useCallback( + (toolId: ToolId): boolean => { + return favoriteTools.includes(toolId); + }, + [favoriteTools] + ); + + return { + recentTools, + favoriteTools, + addToRecent, + toggleFavorite, + isFavorite, + }; +}