From 89b2cb7d3017faf52af04ebd4c8127b95479910f Mon Sep 17 00:00:00 2001 From: EthanHealy01 Date: Wed, 8 Oct 2025 01:24:41 +0100 Subject: [PATCH] add favorites to sidebar --- frontend/src/components/tools/ToolPanel.css | 6 +-- frontend/src/components/tools/ToolPicker.tsx | 41 +++++++++++++++++-- .../tools/toolPicker/ToolButton.tsx | 37 +++++++++++++++-- .../tools/toolPicker/ToolPicker.css | 18 ++++++++ 4 files changed, 90 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/tools/ToolPanel.css b/frontend/src/components/tools/ToolPanel.css index 1a7bb11d0..9a3e6be28 100644 --- a/frontend/src/components/tools/ToolPanel.css +++ b/frontend/src/components/tools/ToolPanel.css @@ -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; } diff --git a/frontend/src/components/tools/ToolPicker.tsx b/frontend/src/components/tools/ToolPicker.tsx index 7893b50bd..30fc6bd84 100644 --- a/frontend/src/components/tools/ToolPicker.tsx +++ b/frontend/src/components/tools/ToolPicker.tsx @@ -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)} > - {t("toolPicker.quickAccess", "QUICK ACCESS")} + {t("toolPicker.recommended", "RECOMMENDED")} {quickSection?.subcategories.reduce((acc, sc) => acc + sc.tools.length, 0)} @@ -150,9 +166,26 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa - {quickSection?.subcategories.map(sc => - renderToolButtons(t, sc, selectedToolKey, onSelect, false, false) - )} + {favoriteToolItems.length > 0 && ( + + +
+ {favoriteToolItems.map(({ id, tool }) => ( + + ))} +
+
+ )} + + {quickSection?.subcategories.map(sc => + renderToolButtons(t, sc, selectedToolKey, onSelect, false, false) + )}
diff --git a/frontend/src/components/tools/toolPicker/ToolButton.tsx b/frontend/src/components/tools/toolPicker/ToolButton.tsx index 00f8d47ab..2eb3eddfe 100644 --- a/frontend/src/components/tools/toolPicker/ToolButton.tsx +++ b/frontend/src/components/tools/toolPicker/ToolButton.tsx @@ -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 = ({ 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 = ({ id, tool, isSelected, onSelect, ); + const star = !isUnavailable ? ( + { + 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 ? ( + + ) : ( + + )} + + ) : null; + return ( - - {buttonElement} - +
+ {star} + + {buttonElement} + +
); }; diff --git a/frontend/src/components/tools/toolPicker/ToolPicker.css b/frontend/src/components/tools/toolPicker/ToolPicker.css index 427230a75..9f1068482 100644 --- a/frontend/src/components/tools/toolPicker/ToolPicker.css +++ b/frontend/src/components/tools/toolPicker/ToolPicker.css @@ -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;