mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-18 20:04:17 +01:00
colour and fixes
This commit is contained in:
parent
ac03c49abb
commit
0eefc734f5
@ -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
|
||||
|
||||
@ -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 (
|
||||
<div className="tool-panel__legacy-empty">
|
||||
<NoToolsFound />
|
||||
@ -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 (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
className={`tool-panel__legacy-item tool-panel__legacy-item--detailed ${isSelected ? 'tool-panel__legacy-item--selected' : ''} tool-panel__legacy-item--with-star`}
|
||||
onClick={handleClick}
|
||||
aria-disabled={isDisabled}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{tool.icon ? (
|
||||
<span
|
||||
className="tool-panel__legacy-icon"
|
||||
aria-hidden
|
||||
style={{
|
||||
background: `linear-gradient(135deg,
|
||||
color-mix(in srgb, ${categoryColor} 18%, var(--legacy-bg-icon-detailed)),
|
||||
color-mix(in srgb, ${categoryColor} 8%, var(--legacy-bg-icon-detailed))
|
||||
)`,
|
||||
color: categoryColor
|
||||
}}
|
||||
>
|
||||
{iconNode}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="tool-panel__legacy-body">
|
||||
<Text fw={600} size="sm" className="tool-panel__legacy-name">
|
||||
{tool.name}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" className="tool-panel__legacy-description">
|
||||
{tool.description}
|
||||
</Text>
|
||||
{binding && (
|
||||
<div className="tool-panel__legacy-shortcut">
|
||||
<span style={{ color: 'var(--mantine-color-dimmed)', fontSize: '0.75rem' }}>
|
||||
{t('settings.hotkeys.shortcut', 'Shortcut')}
|
||||
</span>
|
||||
<HotkeyDisplay binding={binding} size="sm" />
|
||||
</div>
|
||||
)}
|
||||
{matchedText && (
|
||||
<Text size="xs" c="dimmed" className="tool-panel__legacy-match">
|
||||
{t('toolPanel.legacy.matchedSynonym', 'Matches "{{text}}"', { text: matchedText })}
|
||||
</Text>
|
||||
)}
|
||||
</span>
|
||||
{!isDisabled && (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="xl"
|
||||
size="sm"
|
||||
onClick={handleStarClick}
|
||||
className="tool-panel__legacy-star"
|
||||
aria-label={isFav ? t('toolPanel.legacy.unfavorite', 'Remove from favourites') : t('toolPanel.legacy.favorite', 'Add to favourites')}
|
||||
>
|
||||
{isFav ? (
|
||||
<StarRoundedIcon fontSize="small" style={{ color: '#FFC107' }} />
|
||||
) : (
|
||||
<StarBorderRoundedIcon fontSize="small" />
|
||||
)}
|
||||
</ActionIcon>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Compact view
|
||||
const compactButton = (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
className={`tool-panel__legacy-list-item ${isSelected ? 'tool-panel__legacy-list-item--selected' : ''} ${!isDisabled ? 'tool-panel__legacy-list-item--with-star' : ''}`}
|
||||
onClick={handleClick}
|
||||
aria-disabled={isDisabled}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{tool.icon ? (
|
||||
<span
|
||||
className="tool-panel__legacy-list-icon"
|
||||
aria-hidden
|
||||
style={{
|
||||
background: `linear-gradient(135deg,
|
||||
color-mix(in srgb, ${categoryColor} 15%, var(--legacy-bg-icon-compact)),
|
||||
color-mix(in srgb, ${categoryColor} 6%, var(--legacy-bg-icon-compact))
|
||||
)`,
|
||||
color: categoryColor
|
||||
}}
|
||||
>
|
||||
{iconNode}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="tool-panel__legacy-list-body">
|
||||
<Text fw={600} size="sm" className="tool-panel__legacy-name">
|
||||
{tool.name}
|
||||
</Text>
|
||||
{matchedText && (
|
||||
<Text size="xs" c="dimmed" className="tool-panel__legacy-match">
|
||||
{t('toolPanel.legacy.matchedSynonym', 'Matches "{{text}}"', { text: matchedText})}
|
||||
</Text>
|
||||
)}
|
||||
</span>
|
||||
{!isDisabled && (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="xl"
|
||||
size="xs"
|
||||
onClick={handleStarClick}
|
||||
className="tool-panel__legacy-star-compact"
|
||||
aria-label={isFav ? t('toolPanel.legacy.unfavorite', 'Remove from favourites') : t('toolPanel.legacy.favorite', 'Add to favourites')}
|
||||
>
|
||||
{isFav ? (
|
||||
<StarRoundedIcon fontSize="inherit" style={{ color: '#FFC107', fontSize: '1rem' }} />
|
||||
) : (
|
||||
<StarBorderRoundedIcon fontSize="inherit" style={{ fontSize: '1rem' }} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
const tooltipContent = (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.35rem' }}>
|
||||
<span>{tool.description}</span>
|
||||
{binding && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.75rem' }}>
|
||||
<span style={{ color: 'var(--mantine-color-dimmed)', fontWeight: 500 }}>
|
||||
{t('settings.hotkeys.shortcut', 'Shortcut')}
|
||||
</span>
|
||||
<HotkeyDisplay binding={binding} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={id}
|
||||
content={tooltipContent}
|
||||
position="top"
|
||||
portalTarget={tooltipPortalTarget}
|
||||
arrow
|
||||
delay={80}
|
||||
>
|
||||
{compactButton}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={containerClass}>
|
||||
{subcategoryGroups.map(({ subcategoryId, tools }) => (
|
||||
<section
|
||||
key={subcategoryId}
|
||||
className={`tool-panel__legacy-group ${showDescriptions ? 'tool-panel__legacy-group--detailed' : 'tool-panel__legacy-group--compact'}`}
|
||||
>
|
||||
<header className="tool-panel__legacy-section-header">
|
||||
<Text size="sm" fw={600} tt="uppercase" lts={0.5} c="dimmed">
|
||||
{getSubcategoryLabel(t, subcategoryId)}
|
||||
</Text>
|
||||
<Badge size="sm" variant="light" color="gray">
|
||||
{tools.length}
|
||||
</Badge>
|
||||
</header>
|
||||
|
||||
{showDescriptions ? (
|
||||
<div className="tool-panel__legacy-grid tool-panel__legacy-grid--detailed">
|
||||
{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 (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
className={`tool-panel__legacy-item tool-panel__legacy-item--detailed ${isSelected ? 'tool-panel__legacy-item--selected' : ''}`}
|
||||
onClick={handleClick}
|
||||
aria-disabled={isDisabled}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{tool.icon ? (
|
||||
<span className="tool-panel__legacy-icon" aria-hidden>
|
||||
{iconNode}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="tool-panel__legacy-body">
|
||||
<Text fw={600} size="sm" className="tool-panel__legacy-name">
|
||||
{tool.name}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" className="tool-panel__legacy-description">
|
||||
{tool.description}
|
||||
</Text>
|
||||
{matchedText && (
|
||||
<Text size="xs" c="dimmed" className="tool-panel__legacy-match">
|
||||
{t('toolPanel.legacy.matchedSynonym', 'Matches "{{text}}"', { text: matchedText })}
|
||||
</Text>
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="tool-panel__legacy-list">
|
||||
{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 = (
|
||||
<button
|
||||
type="button"
|
||||
className={`tool-panel__legacy-list-item ${isSelected ? 'tool-panel__legacy-list-item--selected' : ''}`}
|
||||
onClick={handleClick}
|
||||
aria-disabled={isDisabled}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{tool.icon ? (
|
||||
<span className="tool-panel__legacy-list-icon" aria-hidden>
|
||||
{iconNode}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="tool-panel__legacy-list-body">
|
||||
<Text fw={600} size="sm" className="tool-panel__legacy-name">
|
||||
{tool.name}
|
||||
</Text>
|
||||
{matchedText && (
|
||||
<Text size="xs" c="dimmed" className="tool-panel__legacy-match">
|
||||
{t('toolPanel.legacy.matchedSynonym', 'Matches "{{text}}"', { text: matchedText })}
|
||||
</Text>
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
if (showDescriptions || !tool.description) {
|
||||
return React.cloneElement(baseButton, { key: id });
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={id}
|
||||
content={tool.description}
|
||||
position="top"
|
||||
portalTarget={tooltipPortalTarget}
|
||||
arrow
|
||||
delay={80}
|
||||
>
|
||||
{baseButton}
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{showRecentFavorites && (
|
||||
<>
|
||||
{favoriteToolItems.length > 0 && (
|
||||
<section className="tool-panel__legacy-group tool-panel__legacy-group--special">
|
||||
<header className="tool-panel__legacy-section-header">
|
||||
<div className="tool-panel__legacy-section-title">
|
||||
<span className="tool-panel__legacy-section-icon" style={{ color: '#FFC107' }} aria-hidden>
|
||||
<StarRoundedIcon />
|
||||
</span>
|
||||
<Text size="sm" fw={600} tt="uppercase" lts={0.5} c="dimmed">
|
||||
{t('toolPanel.legacy.favorites', 'Favourites')}
|
||||
</Text>
|
||||
</div>
|
||||
<Badge size="sm" variant="light" color="yellow">
|
||||
{favoriteToolItems.length}
|
||||
</Badge>
|
||||
</header>
|
||||
{showDescriptions ? (
|
||||
<div className="tool-panel__legacy-grid tool-panel__legacy-grid--detailed">
|
||||
{favoriteToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="tool-panel__legacy-list">
|
||||
{favoriteToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
</section>
|
||||
))}
|
||||
|
||||
{recentToolItems.length > 0 && (
|
||||
<section className="tool-panel__legacy-group tool-panel__legacy-group--special">
|
||||
<header className="tool-panel__legacy-section-header">
|
||||
<div className="tool-panel__legacy-section-title">
|
||||
<span className="tool-panel__legacy-section-icon" style={{ color: '#1BB1D4' }} aria-hidden>
|
||||
<HistoryRoundedIcon />
|
||||
</span>
|
||||
<Text size="sm" fw={600} tt="uppercase" lts={0.5} c="dimmed">
|
||||
{t('toolPanel.legacy.recent', 'Recently used')}
|
||||
</Text>
|
||||
</div>
|
||||
<Badge size="sm" variant="light" color="cyan">
|
||||
{recentToolItems.length}
|
||||
</Badge>
|
||||
</header>
|
||||
{showDescriptions ? (
|
||||
<div className="tool-panel__legacy-grid tool-panel__legacy-grid--detailed">
|
||||
{recentToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="tool-panel__legacy-list">
|
||||
{recentToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{subcategoryGroups.map(({ subcategoryId, tools }) => {
|
||||
const categoryColor = getSubcategoryColor(subcategoryId);
|
||||
|
||||
return (
|
||||
<section
|
||||
key={subcategoryId}
|
||||
className={`tool-panel__legacy-group ${showDescriptions ? 'tool-panel__legacy-group--detailed' : 'tool-panel__legacy-group--compact'}`}
|
||||
style={{
|
||||
borderColor: `color-mix(in srgb, ${categoryColor} 25%, var(--legacy-border-subtle-65))`,
|
||||
}}
|
||||
>
|
||||
<header className="tool-panel__legacy-section-header">
|
||||
<div className="tool-panel__legacy-section-title">
|
||||
<span
|
||||
className="tool-panel__legacy-section-icon"
|
||||
style={{ color: categoryColor }}
|
||||
aria-hidden
|
||||
>
|
||||
{getSubcategoryIcon(subcategoryId)}
|
||||
</span>
|
||||
<Text size="sm" fw={600} tt="uppercase" lts={0.5} style={{ color: categoryColor }}>
|
||||
{getSubcategoryLabel(t, subcategoryId)}
|
||||
</Text>
|
||||
</div>
|
||||
<Badge size="sm" variant="light" style={{
|
||||
backgroundColor: `color-mix(in srgb, ${categoryColor} 15%, transparent)`,
|
||||
color: categoryColor,
|
||||
borderColor: `color-mix(in srgb, ${categoryColor} 30%, transparent)`
|
||||
}}>
|
||||
{tools.length}
|
||||
</Badge>
|
||||
</header>
|
||||
|
||||
{showDescriptions ? (
|
||||
<div className="tool-panel__legacy-grid tool-panel__legacy-grid--detailed">
|
||||
{tools.map(({ id, tool }) => renderToolItem(id, tool))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="tool-panel__legacy-list">
|
||||
{tools.map(({ id, tool }) => renderToolItem(id, tool))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<ToolWorkflowContextValue | undefined>(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 (
|
||||
|
||||
@ -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, string> = {
|
||||
[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);
|
||||
|
||||
92
frontend/src/hooks/tools/useToolHistory.ts
Normal file
92
frontend/src/hooks/tools/useToolHistory.ts
Normal file
@ -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<ToolId[]>([]);
|
||||
const [favoriteTools, setFavoriteTools] = useState<ToolId[]>([]);
|
||||
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user