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 && (
+
+
+ {showDescriptions ? (
+
+ {favoriteToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
+
+ ) : (
+
+ {favoriteToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
+
+ )}
+
)}
-
- ))}
+
+ {recentToolItems.length > 0 && (
+
+
+ {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 (
+
+
+
+ {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,
+ };
+}