add favorites to sidebar

This commit is contained in:
EthanHealy01 2025-10-08 01:24:41 +01:00
parent 2960a23a63
commit 89b2cb7d30
4 changed files with 90 additions and 12 deletions

View File

@ -275,8 +275,7 @@
z-index: 2;
}
.tool-panel__fullscreen-item:hover .tool-panel__fullscreen-star,
.tool-panel__fullscreen-star:focus {
.tool-panel__fullscreen-item:hover .tool-panel__fullscreen-star {
opacity: 1;
}
@ -293,8 +292,7 @@
z-index: 2;
}
.tool-panel__fullscreen-list-item:hover .tool-panel__fullscreen-star-compact,
.tool-panel__fullscreen-star-compact:focus {
.tool-panel__fullscreen-list-item:hover .tool-panel__fullscreen-star-compact {
opacity: 1;
}

View File

@ -7,6 +7,10 @@ import { useToolSections } from "../../hooks/useToolSections";
import NoToolsFound from "./shared/NoToolsFound";
import { renderToolButtons } from "./shared/renderToolButtons";
import Badge from "../shared/Badge";
import SubcategoryHeader from "./shared/SubcategoryHeader";
import ToolButton from "./toolPicker/ToolButton";
import { useToolWorkflow } from "../../contexts/ToolWorkflowContext";
import { ToolId } from "../../types/toolId";
interface ToolPickerProps {
selectedToolKey: string | null;
@ -62,6 +66,18 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
}, []);
const { sections: visibleSections } = useToolSections(filteredTools);
const { favoriteTools, toolRegistry } = useToolWorkflow();
const favoriteToolItems = useMemo(() => {
return favoriteTools
.map((toolId) => {
const tool = (toolRegistry as any)[toolId as ToolId] as ToolRegistryEntry | undefined;
return tool ? { id: toolId as string, tool } : null;
})
.filter(Boolean)
// Only include ready tools (component or link) and navigational exceptions
.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 quickSection = useMemo(
() => visibleSections.find(s => s.key === 'quick'),
@ -142,7 +158,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
}}
onClick={() => scrollTo(quickAccessRef)}
>
<span style={{ fontSize: "1rem" }}>{t("toolPicker.quickAccess", "QUICK ACCESS")}</span>
<span style={{ fontSize: "1rem" }}>{t("toolPicker.recommended", "RECOMMENDED")}</span>
<Badge>
{quickSection?.subcategories.reduce((acc, sc) => acc + sc.tools.length, 0)}
</Badge>
@ -150,9 +166,26 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
<Box ref={quickAccessRef} w="100%" my="sm">
<Stack p="sm" gap="xs">
{quickSection?.subcategories.map(sc =>
renderToolButtons(t, sc, selectedToolKey, onSelect, false, false)
)}
{favoriteToolItems.length > 0 && (
<Box w="100%">
<SubcategoryHeader label={t('toolPanel.fullscreen.favorites', 'Favourites')} mt={0} />
<div>
{favoriteToolItems.map(({ id, tool }) => (
<ToolButton
key={`fav-${id}`}
id={id}
tool={tool}
isSelected={selectedToolKey === id}
onSelect={onSelect}
/>
))}
</div>
</Box>
)}
<SubcategoryHeader label={t('toolPanel.recommendedTools', 'Recommended Tools')} />
{quickSection?.subcategories.map(sc =>
renderToolButtons(t, sc, selectedToolKey, onSelect, false, false)
)}
</Stack>
</Box>
</>

View File

@ -1,5 +1,5 @@
import React from "react";
import { Button } from "@mantine/core";
import { ActionIcon, Button } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { Tooltip } from "../../shared/Tooltip";
import { ToolIcon } from "../../shared/ToolIcon";
@ -9,6 +9,10 @@ import { handleUnlessSpecialClick } from "../../../utils/clickHandlers";
import FitText from "../../shared/FitText";
import { useHotkeys } from "../../../contexts/HotkeyContext";
import HotkeyDisplay from "../../hotkeys/HotkeyDisplay";
import StarRoundedIcon from '@mui/icons-material/StarRounded';
import StarBorderRoundedIcon from '@mui/icons-material/StarBorderRounded';
import { useToolWorkflow } from "../../../contexts/ToolWorkflowContext";
import { ToolId } from "../../../types/toolId";
interface ToolButtonProps {
id: string;
@ -27,6 +31,8 @@ const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect,
const { hotkeys } = useHotkeys();
const binding = hotkeys[id];
const { getToolNavigation } = useToolNavigation();
const { isFavorite, toggleFavorite } = useToolWorkflow();
const fav = isFavorite(id as ToolId);
const handleClick = (id: string) => {
if (isUnavailable) return;
@ -163,10 +169,33 @@ const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect,
</Button>
);
const star = !isUnavailable ? (
<ActionIcon
variant="subtle"
radius="xl"
size="xs"
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
toggleFavorite(id as ToolId);
}}
className="tool-button-star"
aria-label={fav ? t('toolPanel.fullscreen.unfavorite', 'Remove from favourites') : t('toolPanel.fullscreen.favorite', 'Add to favourites')}
>
{fav ? (
<StarRoundedIcon fontSize="inherit" style={{ color: '#FFC107', fontSize: '1rem' }} />
) : (
<StarBorderRoundedIcon fontSize="inherit" style={{ fontSize: '1rem' }} />
)}
</ActionIcon>
) : null;
return (
<Tooltip content={tooltipContent} position="right" arrow={true} delay={500}>
{buttonElement}
</Tooltip>
<div className="tool-button-container">
{star}
<Tooltip content={tooltipContent} position="right" arrow={true} delay={500}>
{buttonElement}
</Tooltip>
</div>
);
};

View File

@ -71,6 +71,24 @@
line-height: 1;
}
/* Container to enable hover-only favourite star overlay */
.tool-button-container {
position: relative;
}
.tool-button-star {
position: absolute;
top: 0.35rem;
right: 0.35rem;
opacity: 0;
transition: opacity 0.2s ease;
z-index: 1; /* lower than sticky section headers */
}
.tool-button-container:hover .tool-button-star {
opacity: 1;
}
.search-input-container {
margin-top: 0.5rem;
margin-bottom: 0.5rem;