mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-01-14 20:11:17 +01:00
change requests
This commit is contained in:
parent
3d30286b42
commit
f5771d827f
@ -32,7 +32,8 @@ export default function RightRail() {
|
||||
const topButtons = useMemo(() => buttons.filter(b => (b.section || 'top') === 'top' && (b.visible ?? true)), [buttons]);
|
||||
|
||||
// Access PageEditor functions for page-editor-specific actions
|
||||
const { pageEditorFunctions } = useToolWorkflow();
|
||||
const { pageEditorFunctions, toolPanelMode, leftPanelView } = useToolWorkflow();
|
||||
const disableForFullscreen = toolPanelMode === 'fullscreen' && leftPanelView === 'toolPicker';
|
||||
|
||||
// CSV input state for page selection
|
||||
const [csvInput, setCsvInput] = useState<string>("");
|
||||
@ -183,13 +184,13 @@ export default function RightRail() {
|
||||
<>
|
||||
<div className="right-rail-section">
|
||||
{topButtons.map(btn => (
|
||||
<Tooltip key={btn.id} content={btn.tooltip} position="left" offset={12} arrow>
|
||||
<Tooltip key={btn.id} content={btn.tooltip} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
className="right-rail-icon"
|
||||
onClick={() => actions[btn.id]?.()}
|
||||
disabled={btn.disabled || allButtonsDisabled}
|
||||
disabled={btn.disabled || allButtonsDisabled || disableForFullscreen}
|
||||
>
|
||||
{btn.icon}
|
||||
</ActionIcon>
|
||||
@ -207,7 +208,7 @@ export default function RightRail() {
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1rem' }}>
|
||||
{/* Search */}
|
||||
<Tooltip content={t('rightRail.search', 'Search PDF')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.search', 'Search PDF')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<Popover position="left" withArrow shadow="md" offset={8}>
|
||||
<Popover.Target>
|
||||
<div style={{ display: 'inline-flex' }}>
|
||||
@ -215,7 +216,7 @@ export default function RightRail() {
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
className="right-rail-icon"
|
||||
disabled={currentView !== 'viewer' || allButtonsDisabled}
|
||||
disabled={currentView !== 'viewer' || allButtonsDisabled || disableForFullscreen}
|
||||
aria-label={typeof t === 'function' ? t('rightRail.search', 'Search PDF') : 'Search PDF'}
|
||||
>
|
||||
<LocalIcon icon="search" width="1.5rem" height="1.5rem" />
|
||||
@ -235,7 +236,7 @@ export default function RightRail() {
|
||||
|
||||
|
||||
{/* Pan Mode */}
|
||||
<Tooltip content={t('rightRail.panMode', 'Pan Mode')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.panMode', 'Pan Mode')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant={isPanning ? "filled" : "subtle"}
|
||||
color={isPanning ? "blue" : undefined}
|
||||
@ -245,14 +246,14 @@ export default function RightRail() {
|
||||
viewerContext?.panActions.togglePan();
|
||||
setIsPanning(!isPanning);
|
||||
}}
|
||||
disabled={currentView !== 'viewer' || allButtonsDisabled}
|
||||
disabled={currentView !== 'viewer' || allButtonsDisabled || disableForFullscreen}
|
||||
>
|
||||
<LocalIcon icon="pan-tool-rounded" width="1.5rem" height="1.5rem" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
{/* Rotate Left */}
|
||||
<Tooltip content={t('rightRail.rotateLeft', 'Rotate Left')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.rotateLeft', 'Rotate Left')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
@ -260,14 +261,14 @@ export default function RightRail() {
|
||||
onClick={() => {
|
||||
viewerContext?.rotationActions.rotateBackward();
|
||||
}}
|
||||
disabled={currentView !== 'viewer' || allButtonsDisabled}
|
||||
disabled={currentView !== 'viewer' || allButtonsDisabled || disableForFullscreen}
|
||||
>
|
||||
<LocalIcon icon="rotate-left" width="1.5rem" height="1.5rem" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
{/* Rotate Right */}
|
||||
<Tooltip content={t('rightRail.rotateRight', 'Rotate Right')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.rotateRight', 'Rotate Right')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
@ -275,14 +276,14 @@ export default function RightRail() {
|
||||
onClick={() => {
|
||||
viewerContext?.rotationActions.rotateForward();
|
||||
}}
|
||||
disabled={currentView !== 'viewer' || allButtonsDisabled}
|
||||
disabled={currentView !== 'viewer' || allButtonsDisabled || disableForFullscreen}
|
||||
>
|
||||
<LocalIcon icon="rotate-right" width="1.5rem" height="1.5rem" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
{/* Sidebar Toggle */}
|
||||
<Tooltip content={t('rightRail.toggleSidebar', 'Toggle Sidebar')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.toggleSidebar', 'Toggle Sidebar')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
@ -290,7 +291,7 @@ export default function RightRail() {
|
||||
onClick={() => {
|
||||
viewerContext?.toggleThumbnailSidebar();
|
||||
}}
|
||||
disabled={currentView !== 'viewer' || allButtonsDisabled}
|
||||
disabled={currentView !== 'viewer' || allButtonsDisabled || disableForFullscreen}
|
||||
>
|
||||
<LocalIcon icon="view-list" width="1.5rem" height="1.5rem" />
|
||||
</ActionIcon>
|
||||
@ -309,14 +310,14 @@ export default function RightRail() {
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '1rem' }}>
|
||||
{/* Select All Button */}
|
||||
<Tooltip content={t('rightRail.selectAll', 'Select All')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.selectAll', 'Select All')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<div>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
className="right-rail-icon"
|
||||
onClick={handleSelectAll}
|
||||
disabled={currentView === 'viewer' || totalItems === 0 || selectedCount === totalItems || allButtonsDisabled}
|
||||
disabled={currentView === 'viewer' || totalItems === 0 || selectedCount === totalItems || allButtonsDisabled || disableForFullscreen}
|
||||
>
|
||||
<LocalIcon icon="select-all" width="1.5rem" height="1.5rem" />
|
||||
</ActionIcon>
|
||||
@ -324,14 +325,14 @@ export default function RightRail() {
|
||||
</Tooltip>
|
||||
|
||||
{/* Deselect All Button */}
|
||||
<Tooltip content={t('rightRail.deselectAll', 'Deselect All')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.deselectAll', 'Deselect All')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<div>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
className="right-rail-icon"
|
||||
onClick={handleDeselectAll}
|
||||
disabled={currentView === 'viewer' || selectedCount === 0 || allButtonsDisabled}
|
||||
disabled={currentView === 'viewer' || selectedCount === 0 || allButtonsDisabled || disableForFullscreen}
|
||||
>
|
||||
<LocalIcon icon="crop-square-outline" width="1.5rem" height="1.5rem" />
|
||||
</ActionIcon>
|
||||
@ -340,7 +341,7 @@ export default function RightRail() {
|
||||
|
||||
{/* Select by Numbers - page editor only, with animated presence */}
|
||||
{pageControlsMounted && (
|
||||
<Tooltip content={t('rightRail.selectByNumber', 'Select by Page Numbers')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.selectByNumber', 'Select by Page Numbers')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
|
||||
<div className={`right-rail-fade ${pageControlsVisible ? 'enter' : 'exit'}`} aria-hidden={!pageControlsVisible}>
|
||||
<Popover position="left" withArrow shadow="md" offset={8}>
|
||||
@ -350,7 +351,7 @@ export default function RightRail() {
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
className="right-rail-icon"
|
||||
disabled={!pageControlsVisible || totalItems === 0 || allButtonsDisabled}
|
||||
disabled={!pageControlsVisible || totalItems === 0 || allButtonsDisabled || disableForFullscreen}
|
||||
aria-label={typeof t === 'function' ? t('rightRail.selectByNumber', 'Select by Page Numbers') : 'Select by Page Numbers'}
|
||||
>
|
||||
<LocalIcon icon="pin-end" width="1.5rem" height="1.5rem" />
|
||||
@ -377,7 +378,7 @@ export default function RightRail() {
|
||||
|
||||
{/* Delete Selected Pages - page editor only, with animated presence */}
|
||||
{pageControlsMounted && (
|
||||
<Tooltip content={t('rightRail.deleteSelected', 'Delete Selected Pages')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.deleteSelected', 'Delete Selected Pages')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
|
||||
<div className={`right-rail-fade ${pageControlsVisible ? 'enter' : 'exit'}`} aria-hidden={!pageControlsVisible}>
|
||||
<div style={{ display: 'inline-flex' }}>
|
||||
@ -386,7 +387,7 @@ export default function RightRail() {
|
||||
radius="md"
|
||||
className="right-rail-icon"
|
||||
onClick={() => { pageEditorFunctions?.handleDelete?.(); }}
|
||||
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || allButtonsDisabled}
|
||||
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || allButtonsDisabled || disableForFullscreen}
|
||||
aria-label={typeof t === 'function' ? t('rightRail.deleteSelected', 'Delete Selected Pages') : 'Delete Selected Pages'}
|
||||
>
|
||||
<LocalIcon icon="delete-outline-rounded" width="1.5rem" height="1.5rem" />
|
||||
@ -399,7 +400,7 @@ export default function RightRail() {
|
||||
|
||||
{/* Export Selected Pages - page editor only */}
|
||||
{pageControlsMounted && (
|
||||
<Tooltip content={t('rightRail.exportSelected', 'Export Selected Pages')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.exportSelected', 'Export Selected Pages')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<div className={`right-rail-fade ${pageControlsVisible ? 'enter' : 'exit'}`} aria-hidden={!pageControlsVisible}>
|
||||
<div style={{ display: 'inline-flex' }}>
|
||||
<ActionIcon
|
||||
@ -407,7 +408,7 @@ export default function RightRail() {
|
||||
radius="md"
|
||||
className="right-rail-icon"
|
||||
onClick={() => { pageEditorFunctions?.onExportSelected?.(); }}
|
||||
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || pageEditorFunctions?.exportLoading || allButtonsDisabled}
|
||||
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || pageEditorFunctions?.exportLoading || allButtonsDisabled || disableForFullscreen}
|
||||
aria-label={typeof t === 'function' ? t('rightRail.exportSelected', 'Export Selected Pages') : 'Export Selected Pages'}
|
||||
>
|
||||
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />
|
||||
@ -418,7 +419,7 @@ export default function RightRail() {
|
||||
)}
|
||||
|
||||
{/* Close (File Editor: Close Selected | Page Editor: Close PDF) */}
|
||||
<Tooltip content={currentView === 'pageEditor' ? t('rightRail.closePdf', 'Close PDF') : t('rightRail.closeSelected', 'Close Selected Files')} position="left" offset={12} arrow>
|
||||
<Tooltip content={currentView === 'pageEditor' ? t('rightRail.closePdf', 'Close PDF') : t('rightRail.closeSelected', 'Close Selected Files')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<div>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
@ -429,7 +430,7 @@ export default function RightRail() {
|
||||
currentView === 'viewer' ||
|
||||
(currentView === 'fileEditor' && selectedCount === 0) ||
|
||||
(currentView === 'pageEditor' && (activeFiles.length === 0 || !pageEditorFunctions?.closePdf)) ||
|
||||
allButtonsDisabled
|
||||
allButtonsDisabled || disableForFullscreen
|
||||
}
|
||||
>
|
||||
<LocalIcon icon="close-rounded" width="1.5rem" height="1.5rem" />
|
||||
@ -465,7 +466,7 @@ export default function RightRail() {
|
||||
currentView === 'pageEditor'
|
||||
? t('rightRail.exportAll', 'Export PDF')
|
||||
: (selectedCount > 0 ? t('rightRail.downloadSelected', 'Download Selected Files') : t('rightRail.downloadAll', 'Download All'))
|
||||
} position="left" offset={12} arrow>
|
||||
} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<div>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
@ -473,7 +474,7 @@ export default function RightRail() {
|
||||
className="right-rail-icon"
|
||||
onClick={handleExportAll}
|
||||
disabled={
|
||||
currentView === 'viewer' ? !exportState?.canExport : totalItems === 0 || allButtonsDisabled
|
||||
disableForFullscreen || (currentView === 'viewer' ? !exportState?.canExport : totalItems === 0 || allButtonsDisabled)
|
||||
}
|
||||
>
|
||||
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />
|
||||
|
||||
@ -10,6 +10,7 @@ import { useFileState, useFileContext } from '../../../contexts/FileContext';
|
||||
import { generateThumbnailWithMetadata } from '../../../utils/thumbnailUtils';
|
||||
import { createProcessedFile } from '../../../contexts/file/fileActions';
|
||||
import { createStirlingFile, createNewStirlingFileStub } from '../../../types/fileContext';
|
||||
import { useToolWorkflow } from '../../../contexts/ToolWorkflowContext';
|
||||
|
||||
interface ViewerAnnotationControlsProps {
|
||||
currentView: string;
|
||||
@ -17,6 +18,8 @@ interface ViewerAnnotationControlsProps {
|
||||
|
||||
export default function ViewerAnnotationControls({ currentView }: ViewerAnnotationControlsProps) {
|
||||
const { t } = useTranslation();
|
||||
const { toolPanelMode, leftPanelView } = useToolWorkflow();
|
||||
const disableForFullscreen = toolPanelMode === 'fullscreen' && leftPanelView === 'toolPicker';
|
||||
const [selectedColor, setSelectedColor] = useState('#000000');
|
||||
const [isColorPickerOpen, setIsColorPickerOpen] = useState(false);
|
||||
const [isHoverColorPickerOpen, setIsHoverColorPickerOpen] = useState(false);
|
||||
@ -42,7 +45,7 @@ export default function ViewerAnnotationControls({ currentView }: ViewerAnnotati
|
||||
return (
|
||||
<>
|
||||
{/* Annotation Visibility Toggle */}
|
||||
<Tooltip content={t('rightRail.toggleAnnotations', 'Toggle Annotations Visibility')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.toggleAnnotations', 'Toggle Annotations Visibility')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
@ -50,7 +53,7 @@ export default function ViewerAnnotationControls({ currentView }: ViewerAnnotati
|
||||
onClick={() => {
|
||||
viewerContext?.toggleAnnotationsVisibility();
|
||||
}}
|
||||
disabled={currentView !== 'viewer' || viewerContext?.isAnnotationMode}
|
||||
disabled={currentView !== 'viewer' || viewerContext?.isAnnotationMode || disableForFullscreen}
|
||||
>
|
||||
<LocalIcon
|
||||
icon={viewerContext?.isAnnotationsVisible ? "visibility" : "visibility-off-rounded"}
|
||||
@ -94,7 +97,7 @@ export default function ViewerAnnotationControls({ currentView }: ViewerAnnotati
|
||||
}
|
||||
}
|
||||
}}
|
||||
disabled={currentView !== 'viewer'}
|
||||
disabled={currentView !== 'viewer' || disableForFullscreen}
|
||||
aria-label="Drawing mode active"
|
||||
>
|
||||
<LocalIcon icon="edit" width="1.5rem" height="1.5rem" />
|
||||
@ -119,7 +122,7 @@ export default function ViewerAnnotationControls({ currentView }: ViewerAnnotati
|
||||
</div>
|
||||
) : (
|
||||
// When inactive: Show "Draw" tooltip
|
||||
<Tooltip content={t('rightRail.draw', 'Draw')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.draw', 'Draw')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
@ -136,7 +139,7 @@ export default function ViewerAnnotationControls({ currentView }: ViewerAnnotati
|
||||
}
|
||||
}
|
||||
}}
|
||||
disabled={currentView !== 'viewer'}
|
||||
disabled={currentView !== 'viewer' || disableForFullscreen}
|
||||
aria-label={typeof t === 'function' ? t('rightRail.draw', 'Draw') : 'Draw'}
|
||||
>
|
||||
<LocalIcon icon="edit" width="1.5rem" height="1.5rem" />
|
||||
@ -145,7 +148,7 @@ export default function ViewerAnnotationControls({ currentView }: ViewerAnnotati
|
||||
)}
|
||||
|
||||
{/* Save PDF with Annotations */}
|
||||
<Tooltip content={t('rightRail.save', 'Save')} position="left" offset={12} arrow>
|
||||
<Tooltip content={t('rightRail.save', 'Save')} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="md"
|
||||
@ -193,7 +196,7 @@ export default function ViewerAnnotationControls({ currentView }: ViewerAnnotati
|
||||
}
|
||||
}
|
||||
}}
|
||||
disabled={currentView !== 'viewer'}
|
||||
disabled={currentView !== 'viewer' || disableForFullscreen}
|
||||
>
|
||||
<LocalIcon icon="save" width="1.5rem" height="1.5rem" />
|
||||
</ActionIcon>
|
||||
|
||||
@ -11,7 +11,7 @@ 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 ThumbUpRoundedIcon from '@mui/icons-material/ThumbUpRounded';
|
||||
import Badge from '../shared/Badge';
|
||||
import './ToolPanel.css';
|
||||
|
||||
@ -34,22 +34,12 @@ const FullscreenToolList = ({
|
||||
}: FullscreenToolListProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { hotkeys } = useHotkeys();
|
||||
const { toolRegistry, recentTools, favoriteTools, toggleFavorite, isFavorite, fullscreenToolSettings } = useToolWorkflow();
|
||||
const { toolRegistry, 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
|
||||
@ -60,8 +50,16 @@ const FullscreenToolList = ({
|
||||
.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 quickSection = useMemo(() => sections.find(section => section.key === 'quick'), [sections]);
|
||||
const recommendedItems = useMemo(() => {
|
||||
if (!quickSection) return [] as Array<{ id: string, tool: ToolRegistryEntry }>;
|
||||
const items: Array<{ id: string, tool: ToolRegistryEntry }> = [];
|
||||
quickSection.subcategories.forEach(sc => sc.tools.forEach(t => items.push(t)));
|
||||
return items.slice(0, 5);
|
||||
}, [quickSection]);
|
||||
|
||||
// Show recommended/favorites section only when not searching
|
||||
const showRecentFavorites = searchQuery.trim().length === 0 && ((recommendedItems.length > 0) || favoriteToolItems.length > 0);
|
||||
|
||||
const subcategoryGroups = useMemo(() => {
|
||||
if (searchQuery.trim().length > 0) {
|
||||
@ -88,16 +86,10 @@ const FullscreenToolList = ({
|
||||
|
||||
const getItemClasses = (isDetailed: boolean) => {
|
||||
const base = isDetailed ? 'tool-panel__fullscreen-item--detailed' : '';
|
||||
const border = fullscreenToolSettings.toolItemBorder === 'hidden' ? 'tool-panel__fullscreen-item--no-border' : '';
|
||||
const hover = `tool-panel__fullscreen-item--hover-${fullscreenToolSettings.hoverIntensity}`;
|
||||
return [base, border, hover].filter(Boolean).join(' ');
|
||||
return base;
|
||||
};
|
||||
|
||||
const getIconBackground = (categoryColor: string, isDetailed: boolean) => {
|
||||
if (fullscreenToolSettings.iconBackground === 'none' || fullscreenToolSettings.iconBackground === 'hover') {
|
||||
return 'transparent';
|
||||
}
|
||||
|
||||
const baseColor = isDetailed ? 'var(--fullscreen-bg-icon-detailed)' : 'var(--fullscreen-bg-icon-compact)';
|
||||
const blend1 = isDetailed ? '18%' : '15%';
|
||||
const blend2 = isDetailed ? '8%' : '6%';
|
||||
@ -109,12 +101,6 @@ const FullscreenToolList = ({
|
||||
};
|
||||
|
||||
const getIconStyle = () => {
|
||||
if (fullscreenToolSettings.iconColorScheme === 'monochrome') {
|
||||
return { filter: 'grayscale(1) opacity(0.8)' };
|
||||
}
|
||||
if (fullscreenToolSettings.iconColorScheme === 'vibrant') {
|
||||
return { filter: 'saturate(1.5) brightness(1.1)' };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
@ -157,16 +143,7 @@ const FullscreenToolList = ({
|
||||
// Detailed view
|
||||
if (showDescriptions) {
|
||||
const iconBg = getIconBackground(categoryColor, true);
|
||||
const iconClasses = fullscreenToolSettings.iconBackground === 'hover'
|
||||
? 'tool-panel__fullscreen-icon tool-panel__fullscreen-icon--hover-bg'
|
||||
: 'tool-panel__fullscreen-icon';
|
||||
|
||||
const hoverBgDetailed = fullscreenToolSettings.iconBackground === 'hover'
|
||||
? `linear-gradient(135deg,
|
||||
color-mix(in srgb, ${categoryColor} 18%, var(--fullscreen-bg-icon-detailed)),
|
||||
color-mix(in srgb, ${categoryColor} 8%, var(--fullscreen-bg-icon-detailed))
|
||||
)`
|
||||
: undefined;
|
||||
const iconClasses = 'tool-panel__fullscreen-icon';
|
||||
|
||||
return (
|
||||
<button
|
||||
@ -176,9 +153,6 @@ const FullscreenToolList = ({
|
||||
onClick={handleClick}
|
||||
aria-disabled={isDisabled}
|
||||
disabled={isDisabled}
|
||||
style={{
|
||||
['--fullscreen-icon-hover-bg' as any]: hoverBgDetailed,
|
||||
}}
|
||||
>
|
||||
{tool.icon ? (
|
||||
<span
|
||||
@ -235,16 +209,7 @@ const FullscreenToolList = ({
|
||||
|
||||
// Compact view
|
||||
const iconBg = getIconBackground(categoryColor, false);
|
||||
const iconClasses = fullscreenToolSettings.iconBackground === 'hover'
|
||||
? 'tool-panel__fullscreen-list-icon tool-panel__fullscreen-list-icon--hover-bg'
|
||||
: 'tool-panel__fullscreen-list-icon';
|
||||
|
||||
const hoverBgCompact = fullscreenToolSettings.iconBackground === 'hover'
|
||||
? `linear-gradient(135deg,
|
||||
color-mix(in srgb, ${categoryColor} 15%, var(--fullscreen-bg-icon-compact)),
|
||||
color-mix(in srgb, ${categoryColor} 6%, var(--fullscreen-bg-icon-compact))
|
||||
)`
|
||||
: undefined;
|
||||
const iconClasses = 'tool-panel__fullscreen-list-icon';
|
||||
|
||||
const compactButton = (
|
||||
<button
|
||||
@ -254,9 +219,6 @@ const FullscreenToolList = ({
|
||||
onClick={handleClick}
|
||||
aria-disabled={isDisabled}
|
||||
disabled={isDisabled}
|
||||
style={{
|
||||
['--fullscreen-icon-hover-bg' as any]: hoverBgCompact,
|
||||
}}
|
||||
>
|
||||
{tool.icon ? (
|
||||
<span
|
||||
@ -299,19 +261,23 @@ const FullscreenToolList = ({
|
||||
</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>
|
||||
);
|
||||
const tooltipContent = isDisabled
|
||||
? (
|
||||
<span><strong>{t('toolPanel.fullscreen.comingSoon', 'Coming soon:')}</strong> {tool.description}</span>
|
||||
)
|
||||
: (
|
||||
<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
|
||||
@ -332,14 +298,18 @@ const FullscreenToolList = ({
|
||||
{showRecentFavorites && (
|
||||
<>
|
||||
{favoriteToolItems.length > 0 && (
|
||||
<section className="tool-panel__fullscreen-group tool-panel__fullscreen-group--special">
|
||||
<section
|
||||
className="tool-panel__fullscreen-group tool-panel__fullscreen-group--special"
|
||||
style={{
|
||||
borderColor: 'var(--fullscreen-border-favorites)',
|
||||
}}
|
||||
>
|
||||
<header className="tool-panel__fullscreen-section-header">
|
||||
<div className="tool-panel__fullscreen-section-title">
|
||||
<span
|
||||
className="tool-panel__fullscreen-section-icon"
|
||||
style={{
|
||||
color: fullscreenToolSettings.headerIconColor === 'colored' ? '#FFC107' : 'var(--mantine-color-dimmed)',
|
||||
...getIconStyle(),
|
||||
color: 'var(--special-color-favorites)',
|
||||
}}
|
||||
aria-hidden
|
||||
>
|
||||
@ -351,8 +321,8 @@ const FullscreenToolList = ({
|
||||
</div>
|
||||
<Badge
|
||||
size="sm"
|
||||
variant={fullscreenToolSettings.headerBadgeColor === 'colored' ? 'colored' : 'default'}
|
||||
color={fullscreenToolSettings.headerBadgeColor === 'colored' ? '#FFC107' : undefined}
|
||||
variant="colored"
|
||||
color="var(--special-color-favorites)"
|
||||
>
|
||||
{favoriteToolItems.length}
|
||||
</Badge>
|
||||
@ -369,39 +339,43 @@ const FullscreenToolList = ({
|
||||
</section>
|
||||
)}
|
||||
|
||||
{recentToolItems.length > 0 && (
|
||||
<section className="tool-panel__fullscreen-group tool-panel__fullscreen-group--special">
|
||||
{recommendedItems.length > 0 && (
|
||||
<section
|
||||
className="tool-panel__fullscreen-group tool-panel__fullscreen-group--special"
|
||||
style={{
|
||||
borderColor: 'var(--fullscreen-border-recommended)',
|
||||
}}
|
||||
>
|
||||
<header className="tool-panel__fullscreen-section-header">
|
||||
<div className="tool-panel__fullscreen-section-title">
|
||||
<span
|
||||
className="tool-panel__fullscreen-section-icon"
|
||||
style={{
|
||||
color: fullscreenToolSettings.headerIconColor === 'colored' ? '#1BB1D4' : 'var(--mantine-color-dimmed)',
|
||||
...getIconStyle(),
|
||||
color: 'var(--special-color-recommended)',
|
||||
}}
|
||||
aria-hidden
|
||||
>
|
||||
<HistoryRoundedIcon />
|
||||
<ThumbUpRoundedIcon />
|
||||
</span>
|
||||
<Text size="sm" fw={600} tt="uppercase" lts={0.5} c="dimmed">
|
||||
{t('toolPanel.fullscreen.recent', 'Recently used')}
|
||||
{t('toolPanel.fullscreen.recommended', 'Recommended')}
|
||||
</Text>
|
||||
</div>
|
||||
<Badge
|
||||
size="sm"
|
||||
variant={fullscreenToolSettings.headerBadgeColor === 'colored' ? 'colored' : 'default'}
|
||||
color={fullscreenToolSettings.headerBadgeColor === 'colored' ? '#1BB1D4' : undefined}
|
||||
variant="colored"
|
||||
color="var(--special-color-recommended)"
|
||||
>
|
||||
{recentToolItems.length}
|
||||
{recommendedItems.length}
|
||||
</Badge>
|
||||
</header>
|
||||
{showDescriptions ? (
|
||||
<div className="tool-panel__fullscreen-grid tool-panel__fullscreen-grid--detailed">
|
||||
{recentToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
|
||||
{recommendedItems.map((item: any) => renderToolItem(item.id, item.tool))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="tool-panel__fullscreen-list">
|
||||
{recentToolItems.map((item: any) => renderToolItem(item.id, item.tool))}
|
||||
{recommendedItems.map((item: any) => renderToolItem(item.id, item.tool))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
@ -425,8 +399,7 @@ const FullscreenToolList = ({
|
||||
<span
|
||||
className="tool-panel__fullscreen-section-icon"
|
||||
style={{
|
||||
color: fullscreenToolSettings.sectionTitleColor === 'colored' ? categoryColor : 'var(--mantine-color-dimmed)',
|
||||
...getIconStyle(),
|
||||
color: categoryColor,
|
||||
}}
|
||||
aria-hidden
|
||||
>
|
||||
@ -438,17 +411,16 @@ const FullscreenToolList = ({
|
||||
tt="uppercase"
|
||||
lts={0.5}
|
||||
style={{
|
||||
color: fullscreenToolSettings.sectionTitleColor === 'colored' ? categoryColor : undefined,
|
||||
color: categoryColor,
|
||||
}}
|
||||
c={fullscreenToolSettings.sectionTitleColor === 'neutral' ? 'dimmed' : undefined}
|
||||
>
|
||||
{getSubcategoryLabel(t, subcategoryId)}
|
||||
</Text>
|
||||
</div>
|
||||
<Badge
|
||||
size="sm"
|
||||
variant={fullscreenToolSettings.sectionTitleColor === 'colored' ? 'colored' : 'default'}
|
||||
color={fullscreenToolSettings.sectionTitleColor === 'colored' ? categoryColor : undefined}
|
||||
variant="colored"
|
||||
color={categoryColor}
|
||||
>
|
||||
{tools.length}
|
||||
</Badge>
|
||||
|
||||
@ -1,204 +0,0 @@
|
||||
import { ActionIcon, Drawer, Radio, SegmentedControl, Stack, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import TuneRoundedIcon from '@mui/icons-material/TuneRounded';
|
||||
import { useState } from 'react';
|
||||
|
||||
export interface FullscreenToolStyleSettings {
|
||||
iconBackground: 'none' | 'hover' | 'always';
|
||||
iconColorScheme: 'colored' | 'vibrant' | 'monochrome';
|
||||
sectionTitleColor: 'colored' | 'neutral';
|
||||
headerIconColor: 'colored' | 'monochrome';
|
||||
headerBadgeColor: 'colored' | 'neutral';
|
||||
toolItemBorder: 'visible' | 'hidden';
|
||||
hoverIntensity: 'subtle' | 'moderate' | 'prominent';
|
||||
}
|
||||
|
||||
export const defaultFullscreenToolSettings: FullscreenToolStyleSettings = {
|
||||
iconBackground: 'always',
|
||||
iconColorScheme: 'colored',
|
||||
sectionTitleColor: 'colored',
|
||||
headerIconColor: 'colored',
|
||||
headerBadgeColor: 'colored',
|
||||
toolItemBorder: 'visible',
|
||||
hoverIntensity: 'moderate',
|
||||
};
|
||||
|
||||
interface FullscreenToolSettingsProps {
|
||||
settings: FullscreenToolStyleSettings;
|
||||
onChange: (settings: FullscreenToolStyleSettings) => void;
|
||||
}
|
||||
|
||||
const FullscreenToolSettings = ({ settings, onChange }: FullscreenToolSettingsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
const updateSetting = <K extends keyof FullscreenToolStyleSettings>(
|
||||
key: K,
|
||||
value: FullscreenToolStyleSettings[K]
|
||||
) => {
|
||||
onChange({ ...settings, [key]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
radius="xl"
|
||||
size="md"
|
||||
onClick={() => setOpened(true)}
|
||||
aria-label={t('toolPanel.fullscreen.settings.title', 'Customize appearance')}
|
||||
style={{ color: 'var(--right-rail-icon)' }}
|
||||
>
|
||||
<TuneRoundedIcon fontSize="small" />
|
||||
</ActionIcon>
|
||||
|
||||
<Drawer
|
||||
opened={opened}
|
||||
onClose={() => setOpened(false)}
|
||||
title={t('toolPanel.fullscreen.settings.title', 'Customize appearance')}
|
||||
position="right"
|
||||
size="md"
|
||||
styles={{
|
||||
root: { zIndex: 1300 },
|
||||
overlay: { zIndex: 1300 },
|
||||
inner: { zIndex: 1300 },
|
||||
}}
|
||||
>
|
||||
<Stack gap="xl">
|
||||
<div>
|
||||
<Text size="sm" fw={600} mb="xs">
|
||||
{t('toolPanel.fullscreen.settings.iconBackground.label', 'Tool icon background')}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mb="sm">
|
||||
{t('toolPanel.fullscreen.settings.iconBackground.description', 'When to show colored backgrounds behind tool icons')}
|
||||
</Text>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={settings.iconBackground}
|
||||
onChange={(value) => updateSetting('iconBackground', value as any)}
|
||||
data={[
|
||||
{ label: t('toolPanel.fullscreen.settings.iconBackground.none', 'None'), value: 'none' },
|
||||
{ label: t('toolPanel.fullscreen.settings.iconBackground.hover', 'On hover'), value: 'hover' },
|
||||
{ label: t('toolPanel.fullscreen.settings.iconBackground.always', 'Always'), value: 'always' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={600} mb="xs">
|
||||
{t('toolPanel.fullscreen.settings.iconColor.label', 'Tool icon color')}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mb="sm">
|
||||
{t('toolPanel.fullscreen.settings.iconColor.description', 'Color scheme for tool icons')}
|
||||
</Text>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={settings.iconColorScheme}
|
||||
onChange={(value) => updateSetting('iconColorScheme', value as any)}
|
||||
data={[
|
||||
{ label: t('toolPanel.fullscreen.settings.iconColor.colored', 'Colored'), value: 'colored' },
|
||||
{ label: t('toolPanel.fullscreen.settings.iconColor.vibrant', 'Vibrant'), value: 'vibrant' },
|
||||
{ label: t('toolPanel.fullscreen.settings.iconColor.monochrome', 'Monochrome'), value: 'monochrome' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={600} mb="xs">
|
||||
{t('toolPanel.fullscreen.settings.sectionTitle.label', 'Section titles')}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mb="sm">
|
||||
{t('toolPanel.fullscreen.settings.sectionTitle.description', 'Color for category section titles')}
|
||||
</Text>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={settings.sectionTitleColor}
|
||||
onChange={(value) => updateSetting('sectionTitleColor', value as any)}
|
||||
data={[
|
||||
{ label: t('toolPanel.fullscreen.settings.sectionTitle.colored', 'Colored'), value: 'colored' },
|
||||
{ label: t('toolPanel.fullscreen.settings.sectionTitle.neutral', 'Neutral'), value: 'neutral' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={600} mb="xs">
|
||||
{t('toolPanel.fullscreen.settings.headerIcon.label', 'Section header icons')}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mb="sm">
|
||||
{t('toolPanel.fullscreen.settings.headerIcon.description', 'Color for Favorites/Recent icons')}
|
||||
</Text>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={settings.headerIconColor}
|
||||
onChange={(value) => updateSetting('headerIconColor', value as any)}
|
||||
data={[
|
||||
{ label: t('toolPanel.fullscreen.settings.headerIcon.colored', 'Colored'), value: 'colored' },
|
||||
{ label: t('toolPanel.fullscreen.settings.headerIcon.monochrome', 'Monochrome'), value: 'monochrome' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={600} mb="xs">
|
||||
{t('toolPanel.fullscreen.settings.headerBadge.label', 'Section header badges')}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mb="sm">
|
||||
{t('toolPanel.fullscreen.settings.headerBadge.description', 'Color for count badges in section headers')}
|
||||
</Text>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={settings.headerBadgeColor}
|
||||
onChange={(value) => updateSetting('headerBadgeColor', value as any)}
|
||||
data={[
|
||||
{ label: t('toolPanel.fullscreen.settings.headerBadge.colored', 'Colored'), value: 'colored' },
|
||||
{ label: t('toolPanel.fullscreen.settings.headerBadge.neutral', 'Neutral'), value: 'neutral' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={600} mb="xs">
|
||||
{t('toolPanel.fullscreen.settings.border.label', 'Tool item borders')}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mb="sm">
|
||||
{t('toolPanel.fullscreen.settings.border.description', 'Show borders around tool items')}
|
||||
</Text>
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={settings.toolItemBorder}
|
||||
onChange={(value) => updateSetting('toolItemBorder', value as any)}
|
||||
data={[
|
||||
{ label: t('toolPanel.fullscreen.settings.border.visible', 'Visible'), value: 'visible' },
|
||||
{ label: t('toolPanel.fullscreen.settings.border.hidden', 'Hidden'), value: 'hidden' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={600} mb="xs">
|
||||
{t('toolPanel.fullscreen.settings.hover.label', 'Hover effect intensity')}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" mb="sm">
|
||||
{t('toolPanel.fullscreen.settings.hover.description', 'How prominent the hover effect should be')}
|
||||
</Text>
|
||||
<Radio.Group
|
||||
value={settings.hoverIntensity}
|
||||
onChange={(value) => updateSetting('hoverIntensity', value as any)}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Radio value="subtle" label={t('toolPanel.fullscreen.settings.hover.subtle', 'Subtle')} />
|
||||
<Radio value="moderate" label={t('toolPanel.fullscreen.settings.hover.moderate', 'Moderate')} />
|
||||
<Radio value="prominent" label={t('toolPanel.fullscreen.settings.hover.prominent', 'Prominent')} />
|
||||
</Stack>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
</Stack>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FullscreenToolSettings;
|
||||
|
||||
|
||||
@ -4,11 +4,9 @@ import ViewSidebarRoundedIcon from '@mui/icons-material/ViewSidebarRounded';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ToolSearch from './toolPicker/ToolSearch';
|
||||
import FullscreenToolList from './FullscreenToolList';
|
||||
import FullscreenToolSettings from './FullscreenToolSettings';
|
||||
import { ToolRegistryEntry } from '../../data/toolsTaxonomy';
|
||||
import { ToolId } from '../../types/toolId';
|
||||
import { useFocusTrap } from '../../hooks/tools/useFocusTrap';
|
||||
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
||||
import { BASE_PATH } from '../../constants/app';
|
||||
import './ToolPanel.css';
|
||||
|
||||
@ -48,7 +46,6 @@ const FullscreenToolSurface = ({
|
||||
}: FullscreenToolSurfaceProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const { fullscreenToolSettings, setFullscreenToolSettings } = useToolWorkflow();
|
||||
const [isExiting, setIsExiting] = useState(false);
|
||||
const surfaceRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -104,10 +101,6 @@ const FullscreenToolSurface = ({
|
||||
<img src={brandTextSrc} alt={brandAltText} className="tool-panel__fullscreen-brand-text" />
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
|
||||
<FullscreenToolSettings
|
||||
settings={fullscreenToolSettings}
|
||||
onChange={setFullscreenToolSettings}
|
||||
/>
|
||||
<Tooltip label={toggleLabel} position="bottom" withArrow>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
|
||||
@ -15,6 +15,8 @@
|
||||
--fullscreen-border-subtle-75: color-mix(in srgb, var(--border-subtle) 75%, transparent);
|
||||
--fullscreen-border-subtle-70: color-mix(in srgb, var(--border-subtle) 70%, transparent);
|
||||
--fullscreen-border-subtle-65: color-mix(in srgb, var(--border-subtle) 65%, transparent);
|
||||
--fullscreen-border-favorites: color-mix(in srgb, var(--special-color-favorites) 25%, var(--border-subtle));
|
||||
--fullscreen-border-recommended: color-mix(in srgb, var(--special-color-recommended) 25%, var(--border-subtle));
|
||||
--fullscreen-shadow-primary: color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.55)) 25%, transparent);
|
||||
--fullscreen-shadow-secondary: color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.35)) 30%, transparent);
|
||||
--fullscreen-shadow-group: color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.45)) 18%, transparent);
|
||||
@ -25,10 +27,6 @@
|
||||
--fullscreen-accent-list-border: color-mix(in srgb, var(--text-primary) 20%, var(--border-subtle));
|
||||
--fullscreen-text-icon: color-mix(in srgb, var(--text-primary) 90%, var(--text-muted));
|
||||
--fullscreen-text-icon-compact: color-mix(in srgb, var(--text-primary) 88%, var(--text-muted));
|
||||
--fullscreen-hover-subtle: color-mix(in srgb, var(--text-primary) 5%, var(--bg-toolbar));
|
||||
--fullscreen-hover: color-mix(in srgb, var(--text-primary) 8%, var(--bg-toolbar));
|
||||
--fullscreen-hover-prominent: color-mix(in srgb, var(--text-primary) 12%, var(--bg-toolbar));
|
||||
--fullscreen-icon-hover-bg: color-mix(in srgb, var(--text-primary) 10%, var(--bg-muted));
|
||||
}
|
||||
|
||||
.tool-panel {
|
||||
@ -234,6 +232,8 @@
|
||||
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
/* Allow flex children to shrink without forcing horizontal overflow */
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tool-panel__fullscreen-item:focus-visible {
|
||||
@ -297,12 +297,7 @@
|
||||
}
|
||||
|
||||
.tool-panel__fullscreen-group--special {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
color-mix(in srgb, var(--fullscreen-bg-group) 95%, var(--text-primary) 5%),
|
||||
var(--fullscreen-bg-group)
|
||||
);
|
||||
border-width: 1.5px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.tool-panel__fullscreen-icon {
|
||||
@ -353,6 +348,9 @@
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
text-align: left;
|
||||
/* Prevent long content (names, descriptions, shortcuts) from overflowing */
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tool-panel__fullscreen-name {
|
||||
@ -363,6 +361,15 @@
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* Ensure long words or unbroken strings wrap instead of overflowing */
|
||||
.tool-panel__fullscreen-name,
|
||||
.tool-panel__fullscreen-description,
|
||||
.tool-panel__fullscreen-match {
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.tool-panel__fullscreen-match {
|
||||
font-style: italic;
|
||||
}
|
||||
@ -372,6 +379,9 @@
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.2rem;
|
||||
/* Allow hotkey chips to wrap on small widths to avoid overflow */
|
||||
flex-wrap: wrap;
|
||||
row-gap: 0.25rem;
|
||||
}
|
||||
|
||||
.tool-panel__fullscreen-list {
|
||||
@ -531,42 +541,5 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Dynamic settings support */
|
||||
|
||||
/* No border variant */
|
||||
.tool-panel__fullscreen-item--no-border,
|
||||
.tool-panel__fullscreen-list-item--no-border {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* Hover intensity variants */
|
||||
.tool-panel__fullscreen-item--hover-subtle:hover,
|
||||
.tool-panel__fullscreen-list-item--hover-subtle:hover {
|
||||
background: var(--fullscreen-hover-subtle);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.tool-panel__fullscreen-item--hover-moderate:hover,
|
||||
.tool-panel__fullscreen-list-item--hover-moderate:hover {
|
||||
background: var(--fullscreen-hover);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.tool-panel__fullscreen-item--hover-prominent:hover,
|
||||
.tool-panel__fullscreen-list-item--hover-prominent:hover {
|
||||
background: var(--fullscreen-hover-prominent);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Hover-only icon background */
|
||||
.tool-panel__fullscreen-icon--hover-bg,
|
||||
.tool-panel__fullscreen-list-icon--hover-bg {
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.tool-panel__fullscreen-item:hover .tool-panel__fullscreen-icon--hover-bg,
|
||||
.tool-panel__fullscreen-list-item:hover .tool-panel__fullscreen-list-icon--hover-bg {
|
||||
background: var(--fullscreen-icon-hover-bg) !important;
|
||||
}
|
||||
|
||||
|
||||
@ -66,7 +66,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
||||
}, []);
|
||||
|
||||
const { sections: visibleSections } = useToolSections(filteredTools);
|
||||
const { favoriteTools, recentTools, toolRegistry } = useToolWorkflow();
|
||||
const { favoriteTools, toolRegistry } = useToolWorkflow();
|
||||
|
||||
const favoriteToolItems = useMemo(() => {
|
||||
return favoriteTools
|
||||
@ -79,24 +79,19 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
||||
.filter((item: any) => item && (item.tool.component || item.tool.link || item.id === 'read' || item.id === 'multiTool')) as Array<{ id: string; tool: ToolRegistryEntry }>;
|
||||
}, [favoriteTools, toolRegistry]);
|
||||
|
||||
const recentToolItems = useMemo(() => {
|
||||
return recentTools
|
||||
.map((toolId) => {
|
||||
const tool = (toolRegistry as any)[toolId as ToolId] as ToolRegistryEntry | undefined;
|
||||
return tool ? { id: toolId as string, tool } : null;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.slice(0, 5) as Array<{ id: string; tool: ToolRegistryEntry }>; // cap to 5
|
||||
}, [recentTools, toolRegistry]);
|
||||
|
||||
const recommendedCount = useMemo(() => {
|
||||
return favoriteToolItems.length + recentToolItems.length;
|
||||
}, [favoriteToolItems.length, recentToolItems.length]);
|
||||
|
||||
const quickSection = useMemo(
|
||||
() => visibleSections.find(s => s.key === 'quick'),
|
||||
[visibleSections]
|
||||
);
|
||||
|
||||
const recommendedItems = useMemo(() => {
|
||||
if (!quickSection) return [] as Array<{ id: string; tool: ToolRegistryEntry }>;
|
||||
const items: Array<{ id: string; tool: ToolRegistryEntry }> = [];
|
||||
quickSection.subcategories.forEach((sc: any) => sc.tools.forEach((toolEntry: any) => items.push(toolEntry)));
|
||||
return items.slice(0, 5);
|
||||
}, [quickSection]);
|
||||
|
||||
const recommendedCount = useMemo(() => favoriteToolItems.length + recommendedItems.length, [favoriteToolItems.length, recommendedItems.length]);
|
||||
const allSection = useMemo(
|
||||
() => visibleSections.find(s => s.key === 'all'),
|
||||
[visibleSections]
|
||||
@ -172,7 +167,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
||||
}}
|
||||
onClick={() => scrollTo(quickAccessRef)}
|
||||
>
|
||||
<span style={{ fontSize: "1rem" }}>{t("toolPicker.recommended", "RECOMMENDED")}</span>
|
||||
<span style={{ fontSize: "1rem" }}>{t("toolPicker.quickAccess", "QUICK ACCESS")}</span>
|
||||
<Badge>
|
||||
{recommendedCount}
|
||||
</Badge>
|
||||
@ -196,13 +191,13 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
{recentToolItems.length > 0 && (
|
||||
{recommendedItems.length > 0 && (
|
||||
<Box w="100%">
|
||||
<SubcategoryHeader label={t('toolPanel.fullscreen.recent', 'Recently used')} />
|
||||
<SubcategoryHeader label={t('toolPanel.fullscreen.recommended', 'Recommended')} />
|
||||
<div>
|
||||
{recentToolItems.map(({ id, tool }) => (
|
||||
{recommendedItems.map(({ id, tool }) => (
|
||||
<ToolButton
|
||||
key={`recent-${id}`}
|
||||
key={`rec-${id}`}
|
||||
id={id}
|
||||
tool={tool}
|
||||
isSelected={selectedToolKey === id}
|
||||
@ -212,7 +207,6 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
{/* Temporarily hide the rest of Recommended tools; show only Favourites and Recently used */}
|
||||
</Stack>
|
||||
</Box>
|
||||
</>
|
||||
@ -247,7 +241,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
||||
|
||||
<Box ref={allToolsRef} w="100%">
|
||||
<Stack p="sm" gap="xs">
|
||||
{allSection?.subcategories.map(sc =>
|
||||
{allSection?.subcategories.map((sc: any) =>
|
||||
renderToolButtons(t, sc, selectedToolKey, onSelect, true, false)
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@ -9,20 +9,16 @@ import { PageEditorFunctions } from '../types/pageEditor';
|
||||
import { ToolRegistryEntry, ToolRegistry } from '../data/toolsTaxonomy';
|
||||
import { useNavigationActions, useNavigationState } from './NavigationContext';
|
||||
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';
|
||||
import { FullscreenToolStyleSettings } from '../components/tools/FullscreenToolSettings';
|
||||
import {
|
||||
ToolWorkflowState,
|
||||
TOOL_PANEL_MODE_STORAGE_KEY,
|
||||
FULLSCREEN_TOOL_SETTINGS_STORAGE_KEY,
|
||||
createInitialState,
|
||||
toolWorkflowReducer,
|
||||
ToolPanelMode,
|
||||
} from './toolWorkflow/state';
|
||||
import { usePreferences } from '../contexts/PreferencesContext';
|
||||
|
||||
// State interface
|
||||
// Types and reducer/state moved to './toolWorkflow/state'
|
||||
@ -40,7 +36,6 @@ interface ToolWorkflowContextValue extends ToolWorkflowState {
|
||||
setLeftPanelView: (view: 'toolPicker' | 'toolContent' | 'hidden') => void;
|
||||
setReaderMode: (mode: boolean) => void;
|
||||
setToolPanelMode: (mode: ToolPanelMode) => void;
|
||||
setFullscreenToolSettings: (settings: FullscreenToolStyleSettings) => void;
|
||||
setPreviewFile: (file: File | null) => void;
|
||||
setPageEditorFunctions: (functions: PageEditorFunctions | null) => void;
|
||||
setSearchQuery: (query: string) => void;
|
||||
@ -80,7 +75,6 @@ interface ToolWorkflowProviderProps {
|
||||
|
||||
export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
||||
const [state, dispatch] = useReducer(toolWorkflowReducer, undefined, createInitialState);
|
||||
const { preferences } = usePreferences();
|
||||
|
||||
// Store reset functions for tools
|
||||
const [toolResetFunctions, setToolResetFunctions] = React.useState<Record<string, () => void>>({});
|
||||
@ -129,10 +123,6 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
||||
}, []);
|
||||
|
||||
|
||||
const setFullscreenToolSettings = useCallback((settings: FullscreenToolStyleSettings) => {
|
||||
dispatch({ type: 'SET_FULLSCREEN_TOOL_SETTINGS', payload: settings });
|
||||
}, []);
|
||||
|
||||
const setPreviewFile = useCallback((file: File | null) => {
|
||||
dispatch({ type: 'SET_PREVIEW_FILE', payload: file });
|
||||
if (file) {
|
||||
@ -148,7 +138,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
||||
dispatch({ type: 'SET_SEARCH_QUERY', payload: query });
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
@ -156,24 +146,6 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
||||
window.localStorage.setItem(TOOL_PANEL_MODE_STORAGE_KEY, state.toolPanelMode);
|
||||
}, [state.toolPanelMode]);
|
||||
|
||||
// Initialize tool panel mode from user preferences if no explicit localStorage preference exists yet
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
const stored = window.localStorage.getItem(TOOL_PANEL_MODE_STORAGE_KEY);
|
||||
if (stored === null && preferences?.defaultToolPanelMode && state.toolPanelMode !== preferences.defaultToolPanelMode) {
|
||||
dispatch({ type: 'SET_TOOL_PANEL_MODE', payload: preferences.defaultToolPanelMode });
|
||||
}
|
||||
}, [preferences?.defaultToolPanelMode]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const serialized = JSON.stringify(state.fullscreenToolSettings);
|
||||
window.localStorage.setItem(FULLSCREEN_TOOL_SETTINGS_STORAGE_KEY, serialized);
|
||||
}, [state.fullscreenToolSettings]);
|
||||
|
||||
// Tool reset methods
|
||||
const registerToolReset = useCallback((toolId: string, resetFunction: () => void) => {
|
||||
setToolResetFunctions(prev => ({ ...prev, [toolId]: resetFunction }));
|
||||
@ -253,15 +225,6 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
||||
[state.sidebarsVisible, state.readerMode, state.leftPanelView]
|
||||
);
|
||||
|
||||
// URL sync for proper tool navigation
|
||||
useNavigationUrlSync(
|
||||
navigationState.selectedTool,
|
||||
handleToolSelect,
|
||||
handleBackToTools,
|
||||
toolRegistry as ToolRegistry,
|
||||
true
|
||||
);
|
||||
|
||||
// Properly memoized context value
|
||||
const contextValue = useMemo((): ToolWorkflowContextValue => ({
|
||||
// State
|
||||
@ -276,7 +239,6 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
||||
setLeftPanelView,
|
||||
setReaderMode,
|
||||
setToolPanelMode,
|
||||
setFullscreenToolSettings,
|
||||
setPreviewFile,
|
||||
setPageEditorFunctions,
|
||||
setSearchQuery,
|
||||
@ -313,7 +275,6 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
||||
setLeftPanelView,
|
||||
setReaderMode,
|
||||
setToolPanelMode,
|
||||
setFullscreenToolSettings,
|
||||
setPreviewFile,
|
||||
setPageEditorFunctions,
|
||||
setSearchQuery,
|
||||
@ -343,7 +304,6 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
||||
export function useToolWorkflow(): ToolWorkflowContextValue {
|
||||
const context = useContext(ToolWorkflowContext);
|
||||
if (!context) {
|
||||
|
||||
console.error('ToolWorkflowContext not found. Current stack:', new Error().stack);
|
||||
throw new Error('useToolWorkflow must be used within a ToolWorkflowProvider');
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { FullscreenToolStyleSettings, defaultFullscreenToolSettings } from '../../components/tools/FullscreenToolSettings';
|
||||
import { PageEditorFunctions } from '../../types/pageEditor';
|
||||
|
||||
// State & Modes
|
||||
@ -10,9 +9,7 @@ export interface ToolWorkflowState {
|
||||
leftPanelView: 'toolPicker' | 'toolContent' | 'hidden';
|
||||
readerMode: boolean;
|
||||
toolPanelMode: ToolPanelMode;
|
||||
fullscreenToolSettings: FullscreenToolStyleSettings;
|
||||
|
||||
// File/Preview State
|
||||
previewFile: File | null;
|
||||
pageEditorFunctions: PageEditorFunctions | null;
|
||||
|
||||
@ -26,7 +23,6 @@ export type ToolWorkflowAction =
|
||||
| { type: 'SET_LEFT_PANEL_VIEW'; payload: 'toolPicker' | 'toolContent' | 'hidden' }
|
||||
| { type: 'SET_READER_MODE'; payload: boolean }
|
||||
| { type: 'SET_TOOL_PANEL_MODE'; payload: ToolPanelMode }
|
||||
| { type: 'SET_FULLSCREEN_TOOL_SETTINGS'; payload: FullscreenToolStyleSettings }
|
||||
| { type: 'SET_PREVIEW_FILE'; payload: File | null }
|
||||
| { type: 'SET_PAGE_EDITOR_FUNCTIONS'; payload: PageEditorFunctions | null }
|
||||
| { type: 'SET_SEARCH_QUERY'; payload: string }
|
||||
@ -34,7 +30,6 @@ export type ToolWorkflowAction =
|
||||
|
||||
// Storage keys
|
||||
export const TOOL_PANEL_MODE_STORAGE_KEY = 'toolPanelModePreference';
|
||||
export const FULLSCREEN_TOOL_SETTINGS_STORAGE_KEY = 'fullscreenToolStyleSettings';
|
||||
|
||||
export const getStoredToolPanelMode = (): ToolPanelMode => {
|
||||
if (typeof window === 'undefined') {
|
||||
@ -49,24 +44,7 @@ export const getStoredToolPanelMode = (): ToolPanelMode => {
|
||||
return 'sidebar';
|
||||
};
|
||||
|
||||
export const getStoredFullscreenToolSettings = (): FullscreenToolStyleSettings => {
|
||||
if (typeof window === 'undefined') {
|
||||
return defaultFullscreenToolSettings;
|
||||
}
|
||||
|
||||
try {
|
||||
const storedNew = window.localStorage.getItem(FULLSCREEN_TOOL_SETTINGS_STORAGE_KEY);
|
||||
if (storedNew) {
|
||||
return { ...defaultFullscreenToolSettings, ...JSON.parse(storedNew) };
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse fullscreen tool settings:', e);
|
||||
}
|
||||
|
||||
return defaultFullscreenToolSettings;
|
||||
};
|
||||
|
||||
export const baseState: Omit<ToolWorkflowState, 'toolPanelMode' | 'fullscreenToolSettings'> = {
|
||||
export const baseState: Omit<ToolWorkflowState, 'toolPanelMode'> = {
|
||||
sidebarsVisible: true,
|
||||
leftPanelView: 'toolPicker',
|
||||
readerMode: false,
|
||||
@ -78,7 +56,6 @@ export const baseState: Omit<ToolWorkflowState, 'toolPanelMode' | 'fullscreenToo
|
||||
export const createInitialState = (): ToolWorkflowState => ({
|
||||
...baseState,
|
||||
toolPanelMode: getStoredToolPanelMode(),
|
||||
fullscreenToolSettings: getStoredFullscreenToolSettings(),
|
||||
});
|
||||
|
||||
export function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkflowAction): ToolWorkflowState {
|
||||
@ -91,8 +68,6 @@ export function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkfl
|
||||
return { ...state, readerMode: action.payload };
|
||||
case 'SET_TOOL_PANEL_MODE':
|
||||
return { ...state, toolPanelMode: action.payload };
|
||||
case 'SET_FULLSCREEN_TOOL_SETTINGS':
|
||||
return { ...state, fullscreenToolSettings: action.payload };
|
||||
case 'SET_PREVIEW_FILE':
|
||||
return { ...state, previewFile: action.payload };
|
||||
case 'SET_PAGE_EDITOR_FUNCTIONS':
|
||||
@ -103,7 +78,6 @@ export function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkfl
|
||||
return {
|
||||
...baseState,
|
||||
toolPanelMode: state.toolPanelMode,
|
||||
fullscreenToolSettings: state.fullscreenToolSettings,
|
||||
searchQuery: state.searchQuery,
|
||||
};
|
||||
default:
|
||||
|
||||
@ -60,6 +60,10 @@
|
||||
--category-color-automation: #ec4899; /* Pink for automation tools */
|
||||
--category-color-developer: #6b7280; /* Gray for developer tools */
|
||||
--category-color-default: #6b7280; /* Default gray */
|
||||
|
||||
/* Special section colors - consistent across light and dark modes */
|
||||
--special-color-favorites: #FFC107; /* Yellow/gold for favorites */
|
||||
--special-color-recommended: #1BB1D4; /* Cyan for recommended */
|
||||
--color-yellow-500: #eab308;
|
||||
--color-yellow-600: #ca8a04;
|
||||
--color-yellow-700: #a16207;
|
||||
@ -301,6 +305,10 @@
|
||||
--category-color-developer: #6b7280; /* Gray for developer tools */
|
||||
--category-color-default: #6b7280; /* Default gray */
|
||||
|
||||
/* Special section colors - same as light mode for consistency */
|
||||
--special-color-favorites: #FFC107; /* Yellow/gold for favorites */
|
||||
--special-color-recommended: #1BB1D4; /* Cyan for recommended */
|
||||
|
||||
/* Success (green) - dark */
|
||||
--color-green-50: #052e16;
|
||||
--color-green-100: #064e3b;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user