remove the 'convenience exports' from ToolWorkflowContext and standardise toolButton for tool picker. + Other general improvements

This commit is contained in:
EthanHealy01 2025-08-15 19:34:18 +01:00
parent f2b5a4a1f8
commit 681f0abfd8
12 changed files with 144 additions and 124 deletions

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Box } from '@mantine/core'; import { Box } from '@mantine/core';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider'; import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
import { useWorkbenchState, useToolSelection } from '../../contexts/ToolWorkflowContext'; import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
import { useFileHandler } from '../../hooks/useFileHandler'; import { useFileHandler } from '../../hooks/useFileHandler';
import { useFileContext } from '../../contexts/FileContext'; import { useFileContext } from '../../contexts/FileContext';
@ -28,9 +28,9 @@ export default function Workbench() {
setPreviewFile, setPreviewFile,
setPageEditorFunctions, setPageEditorFunctions,
setSidebarsVisible setSidebarsVisible
} = useWorkbenchState(); } = useToolWorkflow();
const { selectedToolKey, selectedTool, handleToolSelect } = useToolSelection(); const { selectedToolKey, selectedTool, handleToolSelect } = useToolWorkflow();
const { addToActiveFiles } = useFileHandler(); const { addToActiveFiles } = useFileHandler();
const handlePreviewClose = () => { const handlePreviewClose = () => {

View File

@ -5,6 +5,7 @@ import ToolButton from './toolPicker/ToolButton';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useToolSections } from '../../hooks/useToolSections'; import { useToolSections } from '../../hooks/useToolSections';
import SubcategoryHeader from './shared/SubcategoryHeader'; import SubcategoryHeader from './shared/SubcategoryHeader';
import NoToolsFound from './shared/NoToolsFound';
interface SearchResultsProps { interface SearchResultsProps {
filteredTools: [string, ToolRegistryEntry][]; filteredTools: [string, ToolRegistryEntry][];
@ -16,11 +17,7 @@ const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect }
const { searchGroups } = useToolSections(filteredTools); const { searchGroups } = useToolSections(filteredTools);
if (searchGroups.length === 0) { if (searchGroups.length === 0) {
return ( return <NoToolsFound />;
<Text c="dimmed" size="sm" p="sm">
{t('toolPicker.noToolsFound', 'No tools found')}
</Text>
);
} }
return ( return (

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider'; import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
import { useToolPanelState, useToolSelection, useWorkbenchState } from '../../contexts/ToolWorkflowContext'; import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
import ToolPicker from './ToolPicker'; import ToolPicker from './ToolPicker';
import SearchResults from './SearchResults'; import SearchResults from './SearchResults';
import ToolRenderer from './ToolRenderer'; import ToolRenderer from './ToolRenderer';
@ -27,10 +27,10 @@ export default function ToolPanel() {
toolRegistry, toolRegistry,
setSearchQuery, setSearchQuery,
handleBackToTools handleBackToTools
} = useToolPanelState(); } = useToolWorkflow();
const { selectedToolKey, handleToolSelect } = useToolSelection(); const { selectedToolKey, handleToolSelect } = useToolWorkflow();
const { setPreviewFile } = useWorkbenchState(); const { setPreviewFile } = useToolWorkflow();
return ( return (
<div <div

View File

@ -6,6 +6,7 @@ import ToolButton from "./toolPicker/ToolButton";
import "./toolPicker/ToolPicker.css"; import "./toolPicker/ToolPicker.css";
import { useToolSections } from "../../hooks/useToolSections"; import { useToolSections } from "../../hooks/useToolSections";
import SubcategoryHeader from "./shared/SubcategoryHeader"; import SubcategoryHeader from "./shared/SubcategoryHeader";
import NoToolsFound from "./shared/NoToolsFound";
interface ToolPickerProps { interface ToolPickerProps {
selectedToolKey: string | null; selectedToolKey: string | null;
@ -14,6 +15,31 @@ interface ToolPickerProps {
isSearching?: boolean; isSearching?: boolean;
} }
// Helper function to render tool buttons for a subcategory
const renderToolButtons = (
subcategory: any,
selectedToolKey: string | null,
onSelect: (id: string) => void,
showSubcategoryHeader: boolean = true
) => (
<Box key={subcategory.subcategory} w="100%">
{showSubcategoryHeader && (
<SubcategoryHeader label={subcategory.subcategory} />
)}
<Stack gap="xs">
{subcategory.tools.map(({ id, tool }: { id: string; tool: any }) => (
<ToolButton
key={id}
id={id}
tool={tool}
isSelected={selectedToolKey === id}
onSelect={onSelect}
/>
))}
</Stack>
</Box>
);
const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = false }: ToolPickerProps) => { const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = false }: ToolPickerProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [quickHeaderHeight, setQuickHeaderHeight] = useState(0); const [quickHeaderHeight, setQuickHeaderHeight] = useState(0);
@ -92,26 +118,9 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
{isSearching ? ( {isSearching ? (
<Stack p="sm" gap="xs"> <Stack p="sm" gap="xs">
{searchGroups.length === 0 ? ( {searchGroups.length === 0 ? (
<Text c="dimmed" size="sm" p="sm"> <NoToolsFound />
{t("toolPicker.noToolsFound", "No tools found")}
</Text>
) : ( ) : (
searchGroups.map(group => ( searchGroups.map(group => renderToolButtons(group, selectedToolKey, onSelect))
<Box key={group.subcategory} w="100%">
<SubcategoryHeader label={group.subcategory} />
<Stack gap="xs">
{group.tools.map(({ id, tool }) => (
<ToolButton
key={id}
id={id}
tool={tool}
isSelected={selectedToolKey === id}
onSelect={onSelect}
/>
))}
</Stack>
</Box>
))
)} )}
</Stack> </Stack>
) : ( ) : (
@ -124,8 +133,8 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
position: "sticky", position: "sticky",
top: 0, top: 0,
zIndex: 2, zIndex: 2,
borderTop: `1px solid var(--tool-header-border)`, borderTop: `0.0625rem solid var(--tool-header-border)`,
borderBottom: `1px solid var(--tool-header-border)`, borderBottom: `0.0625rem solid var(--tool-header-border)`,
marginBottom: -1, marginBottom: -1,
padding: "0.5rem 1rem", padding: "0.5rem 1rem",
fontWeight: 700, fontWeight: 700,
@ -143,9 +152,9 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
style={{ style={{
background: "var(--tool-header-badge-bg)", background: "var(--tool-header-badge-bg)",
color: "var(--tool-header-badge-text)", color: "var(--tool-header-badge-text)",
borderRadius: 8, borderRadius: ".5rem",
padding: "2px 8px", padding: "0.125rem 0.5rem",
fontSize: 12, fontSize: ".75rem",
fontWeight: 700 fontWeight: 700
}} }}
> >
@ -155,24 +164,9 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
<Box ref={quickAccessRef} w="100%"> <Box ref={quickAccessRef} w="100%">
<Stack p="sm" gap="xs"> <Stack p="sm" gap="xs">
{quickSection?.subcategories.map(sc => ( {quickSection?.subcategories.map(sc =>
<Box key={sc.subcategory} w="100%"> renderToolButtons(sc, selectedToolKey, onSelect, quickSection?.subcategories.length === 1)
{quickSection?.subcategories.length > 1 && ( )}
<SubcategoryHeader label={sc.subcategory} />
)}
<Stack gap="xs">
{sc.tools.map(({ id, tool }) => (
<ToolButton
key={id}
id={id}
tool={tool}
isSelected={selectedToolKey === id}
onSelect={onSelect}
/>
))}
</Stack>
</Box>
))}
</Stack> </Stack>
</Box> </Box>
</> </>
@ -186,8 +180,8 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
position: "sticky", position: "sticky",
top: quickSection ? quickHeaderHeight - 1: 0, top: quickSection ? quickHeaderHeight - 1: 0,
zIndex: 2, zIndex: 2,
borderTop: `1px solid var(--tool-header-border)`, borderTop: `0.0625rem solid var(--tool-header-border)`,
borderBottom: `1px solid var(--tool-header-border)`, borderBottom: `0.0625rem solid var(--tool-header-border)`,
padding: "0.5rem 1rem", padding: "0.5rem 1rem",
fontWeight: 700, fontWeight: 700,
background: "var(--tool-header-bg)", background: "var(--tool-header-bg)",
@ -204,9 +198,9 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
style={{ style={{
background: "var(--tool-header-badge-bg)", background: "var(--tool-header-badge-bg)",
color: "var(--tool-header-badge-text)", color: "var(--tool-header-badge-text)",
borderRadius: 8, borderRadius: ".5rem",
padding: "2px 8px", padding: "0.125rem 0.5rem",
fontSize: 12, fontSize: ".75rem",
fontWeight: 700 fontWeight: 700
}} }}
> >
@ -216,34 +210,15 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
<Box ref={allToolsRef} w="100%"> <Box ref={allToolsRef} w="100%">
<Stack p="sm" gap="xs"> <Stack p="sm" gap="xs">
{allSection?.subcategories.map(sc => ( {allSection?.subcategories.map(sc =>
<Box key={sc.subcategory} w="100%"> renderToolButtons(sc, selectedToolKey, onSelect, allSection?.subcategories.length === 1)
{allSection?.subcategories.length > 1 && ( )}
<SubcategoryHeader label={sc.subcategory} />
)}
<Stack gap="xs">
{sc.tools.map(({ id, tool }) => (
<ToolButton
key={id}
id={id}
tool={tool}
isSelected={selectedToolKey === id}
onSelect={onSelect}
/>
))}
</Stack>
</Box>
))}
</Stack> </Stack>
</Box> </Box>
</> </>
)} )}
{!quickSection && !allSection && ( {!quickSection && !allSection && <NoToolsFound />}
<Text c="dimmed" size="sm" p="sm">
{t("toolPicker.noToolsFound", "No tools found")}
</Text>
)}
{/* bottom spacer to allow scrolling past the last row */} {/* bottom spacer to allow scrolling past the last row */}
<div aria-hidden style={{ height: 200 }} /> <div aria-hidden style={{ height: 200 }} />

View File

@ -4,6 +4,7 @@ import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useMultipleEndpointsEnabled } from "../../../hooks/useEndpointConfig"; import { useMultipleEndpointsEnabled } from "../../../hooks/useEndpointConfig";
import { isImageFormat, isWebFormat } from "../../../utils/convertUtils"; import { isImageFormat, isWebFormat } from "../../../utils/convertUtils";
import { getConversionEndpoints } from "../../../data/toolRegistry";
import { useFileSelectionActions } from "../../../contexts/FileSelectionContext"; import { useFileSelectionActions } from "../../../contexts/FileSelectionContext";
import { useFileContext } from "../../../contexts/FileContext"; import { useFileContext } from "../../../contexts/FileContext";
import { detectFileExtension } from "../../../utils/fileUtils"; import { detectFileExtension } from "../../../utils/fileUtils";
@ -43,15 +44,7 @@ const ConvertSettings = ({
const { setSelectedFiles } = useFileSelectionActions(); const { setSelectedFiles } = useFileSelectionActions();
const { activeFiles, setSelectedFiles: setContextSelectedFiles } = useFileContext(); const { activeFiles, setSelectedFiles: setContextSelectedFiles } = useFileContext();
const allEndpoints = useMemo(() => { const allEndpoints = useMemo(() => getConversionEndpoints(EXTENSION_TO_ENDPOINT), []);
const endpoints = new Set<string>();
Object.values(EXTENSION_TO_ENDPOINT).forEach(toEndpoints => {
Object.values(toEndpoints).forEach(endpoint => {
endpoints.add(endpoint);
});
});
return Array.from(endpoints);
}, []);
const { endpointStatus } = useMultipleEndpointsEnabled(allEndpoints); const { endpointStatus } = useMultipleEndpointsEnabled(allEndpoints);

View File

@ -0,0 +1,15 @@
import React from 'react';
import { Text } from '@mantine/core';
import { useTranslation } from 'react-i18next';
const NoToolsFound: React.FC = () => {
const { t } = useTranslation();
return (
<Text c="dimmed" size="sm" p="sm">
{t("toolPicker.noToolsFound", "No tools found")}
</Text>
);
};
export default NoToolsFound;

View File

@ -6,7 +6,7 @@
} }
.tool-picker-scrollable::-webkit-scrollbar { .tool-picker-scrollable::-webkit-scrollbar {
width: 6px; width: 0.375rem;
} }
.tool-picker-scrollable::-webkit-scrollbar-track { .tool-picker-scrollable::-webkit-scrollbar-track {
@ -15,7 +15,7 @@
.tool-picker-scrollable::-webkit-scrollbar-thumb { .tool-picker-scrollable::-webkit-scrollbar-thumb {
background-color: var(--mantine-color-gray-4); background-color: var(--mantine-color-gray-4);
border-radius: 3px; border-radius: 0.1875rem;
} }
.tool-picker-scrollable::-webkit-scrollbar-thumb:hover { .tool-picker-scrollable::-webkit-scrollbar-thumb:hover {

View File

@ -225,8 +225,3 @@ export function useToolWorkflow(): ToolWorkflowContextValue {
} }
return context; return context;
} }
// Convenience exports for specific use cases (optional - components can use useToolWorkflow directly)
export const useToolSelection = useToolWorkflow;
export const useToolPanelState = useToolWorkflow;
export const useWorkbenchState = useToolWorkflow;

View File

@ -12,7 +12,7 @@ export type ToolRegistryEntry = {
view: string; view: string;
description: string; description: string;
category: string; category: string;
subcategory: string | null; subcategory: string;
// Optional custom props for tools // Optional custom props for tools
maxFiles?: number; maxFiles?: number;
@ -77,8 +77,7 @@ export const SUBCATEGORY_COLOR_MAP: Record<string, string> = {
'Developer Tools': '#F55454', 'Developer Tools': '#F55454',
}; };
export const getSubcategoryColor = (subcategory?: string | null): string => { export const getSubcategoryColor = (subcategory: string): string => {
if (!subcategory) return '#7882FF';
return SUBCATEGORY_COLOR_MAP[subcategory] || '#7882FF'; return SUBCATEGORY_COLOR_MAP[subcategory] || '#7882FF';
}; };
@ -575,7 +574,7 @@ export function useFlatToolRegistry(): ToolRegistry {
view: "format", view: "format",
description: t("home.compare.desc", "Compare two PDF documents and highlight differences"), description: t("home.compare.desc", "Compare two PDF documents and highlight differences"),
category: "Recommended Tools", category: "Recommended Tools",
subcategory: null subcategory: "General"
}, },
"compressPdfs": { "compressPdfs": {
icon: <span className="material-symbols-rounded">zoom_in_map</span>, icon: <span className="material-symbols-rounded">zoom_in_map</span>,
@ -584,7 +583,7 @@ export function useFlatToolRegistry(): ToolRegistry {
view: "compress", view: "compress",
description: t("home.compressPdfs.desc", "Compress PDFs to reduce their file size."), description: t("home.compressPdfs.desc", "Compress PDFs to reduce their file size."),
category: "Recommended Tools", category: "Recommended Tools",
subcategory: null, subcategory: "General",
maxFiles: -1 maxFiles: -1
}, },
"convert": { "convert": {
@ -594,7 +593,7 @@ export function useFlatToolRegistry(): ToolRegistry {
view: "convert", view: "convert",
description: t("home.fileToPDF.desc", "Convert files to and from PDF format"), description: t("home.fileToPDF.desc", "Convert files to and from PDF format"),
category: "Recommended Tools", category: "Recommended Tools",
subcategory: null, subcategory: "General",
maxFiles: -1, maxFiles: -1,
endpoints: [ endpoints: [
"pdf-to-img", "pdf-to-img",
@ -638,7 +637,7 @@ export function useFlatToolRegistry(): ToolRegistry {
view: "merge", view: "merge",
description: t("home.merge.desc", "Merge multiple PDFs into a single document"), description: t("home.merge.desc", "Merge multiple PDFs into a single document"),
category: "Recommended Tools", category: "Recommended Tools",
subcategory: null, subcategory: "General",
maxFiles: -1 maxFiles: -1
}, },
"multi-tool": { "multi-tool": {
@ -648,7 +647,7 @@ export function useFlatToolRegistry(): ToolRegistry {
view: "pageEditor", view: "pageEditor",
description: t("home.multiTool.desc", "Use multiple tools on a single PDF document"), description: t("home.multiTool.desc", "Use multiple tools on a single PDF document"),
category: "Recommended Tools", category: "Recommended Tools",
subcategory: null, subcategory: "General",
maxFiles: -1 maxFiles: -1
}, },
"ocr": { "ocr": {
@ -658,7 +657,7 @@ export function useFlatToolRegistry(): ToolRegistry {
view: "convert", view: "convert",
description: t("home.ocr.desc", "Extract text from scanned PDFs using Optical Character Recognition"), description: t("home.ocr.desc", "Extract text from scanned PDFs using Optical Character Recognition"),
category: "Recommended Tools", category: "Recommended Tools",
subcategory: null, subcategory: "General",
maxFiles: -1 maxFiles: -1
}, },
"redact": { "redact": {
@ -668,7 +667,7 @@ export function useFlatToolRegistry(): ToolRegistry {
view: "redact", view: "redact",
description: t("home.redact.desc", "Permanently remove sensitive information from PDF documents"), description: t("home.redact.desc", "Permanently remove sensitive information from PDF documents"),
category: "Recommended Tools", category: "Recommended Tools",
subcategory: null subcategory: "General"
}, },
}; };
} }
@ -682,3 +681,56 @@ export const toolEndpoints: Record<string, string[]> = {
merge: ["merge-pdfs"], merge: ["merge-pdfs"],
// Add more endpoint mappings as needed // Add more endpoint mappings as needed
}; };
/**
* Get all endpoints from both registry entries and legacy toolEndpoints mapping
* This consolidates endpoint discovery logic in one place
*/
export const getAllEndpoints = (registry: ToolRegistry): string[] => {
const lists: string[][] = [];
// Get endpoints from registry entries
Object.values(registry).forEach(entry => {
if (entry.endpoints && entry.endpoints.length > 0) {
lists.push(entry.endpoints);
}
});
// Get endpoints from legacy toolEndpoints mapping
Object.entries(toolEndpoints).forEach(([key, list]) => {
// Only add if not already covered by registry entries
if (!registry[key]?.endpoints) {
lists.push(list);
}
});
return Array.from(new Set(lists.flat()));
};
/**
* Get all endpoints from a conversion matrix (like EXTENSION_TO_ENDPOINT)
* This is useful for convert-specific endpoint discovery
*/
export const getConversionEndpoints = (extensionToEndpoint: Record<string, Record<string, string>>): string[] => {
const endpoints = new Set<string>();
Object.values(extensionToEndpoint).forEach(toEndpoints => {
Object.values(toEndpoints).forEach(endpoint => {
endpoints.add(endpoint);
});
});
return Array.from(endpoints);
};
/**
* Get all endpoints from both tool registry and conversion matrix
* This provides comprehensive endpoint coverage for the entire application
*/
export const getAllApplicationEndpoints = (
registry: ToolRegistry,
extensionToEndpoint?: Record<string, Record<string, string>>
): string[] => {
const toolEndpoints = getAllEndpoints(registry);
const conversionEndpoints = extensionToEndpoint ? getConversionEndpoints(extensionToEndpoint) : [];
return Array.from(new Set([...toolEndpoints, ...conversionEndpoints]));
};

View File

@ -1,6 +1,6 @@
import React, { useState, useCallback, useMemo, useEffect } from 'react'; import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useFlatToolRegistry, toolEndpoints, type ToolRegistryEntry } from "../data/toolRegistry"; import { useFlatToolRegistry, toolEndpoints, getAllEndpoints, type ToolRegistryEntry } from "../data/toolRegistry";
import { useMultipleEndpointsEnabled } from "./useEndpointConfig"; import { useMultipleEndpointsEnabled } from "./useEndpointConfig";
interface ToolManagementResult { interface ToolManagementResult {
@ -20,7 +20,7 @@ export const useToolManagement = (): ToolManagementResult => {
const [toolSelectedFileIds, setToolSelectedFileIds] = useState<string[]>([]); const [toolSelectedFileIds, setToolSelectedFileIds] = useState<string[]>([]);
// Build endpoints list from registry entries with fallback to legacy mapping // Build endpoints list from registry entries with fallback to legacy mapping
const baseRegistry = useFlatToolRegistry(); const baseRegistry = useFlatToolRegistry();
const registryDerivedEndpoints = useMemo(() => { const registryDerivedEndpoints = useMemo(() => {
const endpointsByTool: Record<string, string[]> = {}; const endpointsByTool: Record<string, string[]> = {};
Object.entries(baseRegistry).forEach(([key, entry]) => { Object.entries(baseRegistry).forEach(([key, entry]) => {
@ -31,14 +31,7 @@ export const useToolManagement = (): ToolManagementResult => {
return endpointsByTool; return endpointsByTool;
}, [baseRegistry]); }, [baseRegistry]);
const allEndpoints = useMemo(() => { const allEndpoints = useMemo(() => getAllEndpoints(baseRegistry), [baseRegistry]);
const lists: string[][] = [];
Object.values(registryDerivedEndpoints).forEach(list => lists.push(list));
Object.entries(toolEndpoints).forEach(([key, list]) => {
if (!registryDerivedEndpoints[key]) lists.push(list);
});
return Array.from(new Set(lists.flat()));
}, [registryDerivedEndpoints]);
const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabled(allEndpoints); const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabled(allEndpoints);
const isToolAvailable = useCallback((toolKey: string): boolean => { const isToolAvailable = useCallback((toolKey: string): boolean => {

View File

@ -14,8 +14,8 @@ export function useToolSections(filteredTools: [string, ToolRegistryEntry][]) {
const groupedTools = useMemo(() => { const groupedTools = useMemo(() => {
const grouped: GroupedTools = {}; const grouped: GroupedTools = {};
filteredTools.forEach(([id, tool]) => { filteredTools.forEach(([id, tool]) => {
const category = tool?.category || 'OTHER'; const category = tool.category;
const subcategory = tool?.subcategory || 'General'; const subcategory = tool.subcategory;
if (!grouped[category]) grouped[category] = {}; if (!grouped[category]) grouped[category] = {};
if (!grouped[category][subcategory]) grouped[category][subcategory] = []; if (!grouped[category][subcategory]) grouped[category][subcategory] = [];
grouped[category][subcategory].push({ id, tool }); grouped[category][subcategory].push({ id, tool });
@ -72,7 +72,7 @@ export function useToolSections(filteredTools: [string, ToolRegistryEntry][]) {
filteredTools.forEach(([id, tool]) => { filteredTools.forEach(([id, tool]) => {
if (seen.has(id)) return; if (seen.has(id)) return;
seen.add(id); seen.add(id);
const sub = tool?.subcategory || 'General'; const sub = tool.subcategory;
if (!subMap[sub]) subMap[sub] = []; if (!subMap[sub]) subMap[sub] = [];
subMap[sub].push({ id, tool }); subMap[sub].push({ id, tool });
}); });

View File

@ -1,7 +1,7 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useFileContext } from "../contexts/FileContext"; import { useFileContext } from "../contexts/FileContext";
import { FileSelectionProvider, useFileSelection } from "../contexts/FileSelectionContext"; import { FileSelectionProvider, useFileSelection } from "../contexts/FileSelectionContext";
import { ToolWorkflowProvider, useToolSelection } from "../contexts/ToolWorkflowContext"; import { ToolWorkflowProvider, useToolWorkflow } from "../contexts/ToolWorkflowContext";
import { Group } from "@mantine/core"; import { Group } from "@mantine/core";
import { SidebarProvider, useSidebarContext } from "../contexts/SidebarContext"; import { SidebarProvider, useSidebarContext } from "../contexts/SidebarContext";
@ -20,7 +20,7 @@ function HomePageContent() {
const { setMaxFiles, setIsToolMode, setSelectedFiles } = useFileSelection(); const { setMaxFiles, setIsToolMode, setSelectedFiles } = useFileSelection();
const { selectedTool } = useToolSelection(); const { selectedTool } = useToolWorkflow();
// Update file selection context when tool changes // Update file selection context when tool changes
useEffect(() => { useEffect(() => {