mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-22 04:09:22 +00:00
make a hook to manage sections / subcategories in the toolPicker
This commit is contained in:
parent
4de65c1cc0
commit
d64a753b06
@ -1,9 +1,10 @@
|
|||||||
import React, { useMemo, useRef, useLayoutEffect, useState } from "react";
|
import React, { useMemo, useRef, useLayoutEffect, useState } from "react";
|
||||||
import { Box, Text, Stack } from "@mantine/core";
|
import { Box, Text, Stack } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { type ToolRegistryEntry, SUBCATEGORY_ORDER } from "../../data/toolRegistry";
|
import { type ToolRegistryEntry } from "../../data/toolRegistry";
|
||||||
import ToolButton from "./toolPicker/ToolButton";
|
import ToolButton from "./toolPicker/ToolButton";
|
||||||
import "./toolPicker/ToolPicker.css";
|
import "./toolPicker/ToolPicker.css";
|
||||||
|
import { useToolSections } from "../../hooks/useToolSections";
|
||||||
|
|
||||||
interface ToolPickerProps {
|
interface ToolPickerProps {
|
||||||
selectedToolKey: string | null;
|
selectedToolKey: string | null;
|
||||||
@ -43,6 +44,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
|||||||
return () => window.removeEventListener("resize", update);
|
return () => window.removeEventListener("resize", update);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Lightweight inline component to avoid an extra file
|
||||||
const SubcategoryHeader: React.FC<{ label: string; mt?: string | number; mb?: string | number }> = ({ label, mt = "1rem", mb = "0.25rem" }) => (
|
const SubcategoryHeader: React.FC<{ label: string; mt?: string | number; mb?: string | number }> = ({ label, mt = "1rem", mb = "0.25rem" }) => (
|
||||||
<div className="tool-subcategory-row" style={{ marginLeft: "1rem", marginRight: "1rem", marginTop: mt, marginBottom: mb }}>
|
<div className="tool-subcategory-row" style={{ marginLeft: "1rem", marginRight: "1rem", marginTop: mt, marginBottom: mb }}>
|
||||||
<div className="tool-subcategory-row-rule" />
|
<div className="tool-subcategory-row-rule" />
|
||||||
@ -51,68 +53,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const groupedTools = useMemo(() => {
|
const { sections } = useToolSections(filteredTools);
|
||||||
const grouped: GroupedTools = {};
|
|
||||||
filteredTools.forEach(([id, tool]) => {
|
|
||||||
const category = tool?.category || "OTHER";
|
|
||||||
const subcategory = tool?.subcategory || "General";
|
|
||||||
if (!grouped[category]) grouped[category] = {};
|
|
||||||
if (!grouped[category][subcategory]) grouped[category][subcategory] = [];
|
|
||||||
grouped[category][subcategory].push({ id, tool });
|
|
||||||
});
|
|
||||||
return grouped;
|
|
||||||
}, [filteredTools]);
|
|
||||||
|
|
||||||
const sections = useMemo(() => {
|
|
||||||
|
|
||||||
const getOrderIndex = (name: string) => {
|
|
||||||
const idx = SUBCATEGORY_ORDER.indexOf(name);
|
|
||||||
return idx === -1 ? Number.MAX_SAFE_INTEGER : idx;
|
|
||||||
};
|
|
||||||
// Build two buckets: Quick includes only Recommended; All includes all categories (including Recommended)
|
|
||||||
const quick: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
|
|
||||||
const all: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
|
|
||||||
|
|
||||||
Object.entries(groupedTools).forEach(([origCat, subs]) => {
|
|
||||||
const upperCat = origCat.toUpperCase();
|
|
||||||
|
|
||||||
// Always add to ALL
|
|
||||||
Object.entries(subs).forEach(([sub, tools]) => {
|
|
||||||
if (!all[sub]) all[sub] = [];
|
|
||||||
all[sub].push(...tools);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add Recommended to QUICK ACCESS
|
|
||||||
if (upperCat === 'RECOMMENDED TOOLS') {
|
|
||||||
Object.entries(subs).forEach(([sub, tools]) => {
|
|
||||||
if (!quick[sub]) quick[sub] = [];
|
|
||||||
quick[sub].push(...tools);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const sortSubs = (obj: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>>) =>
|
|
||||||
Object.entries(obj)
|
|
||||||
.sort(([a], [b]) => {
|
|
||||||
const ai = getOrderIndex(a);
|
|
||||||
const bi = getOrderIndex(b);
|
|
||||||
if (ai !== bi) return ai - bi;
|
|
||||||
return a.localeCompare(b);
|
|
||||||
})
|
|
||||||
.map(([subcategory, tools]) => ({
|
|
||||||
subcategory,
|
|
||||||
// preserve original insertion order coming from filteredTools
|
|
||||||
tools
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Build sections and filter out any with no tools (avoids empty headers during search)
|
|
||||||
const built = [
|
|
||||||
{ title: "QUICK ACCESS", ref: quickAccessRef, subcategories: sortSubs(quick) },
|
|
||||||
{ title: "ALL TOOLS", ref: allToolsRef, subcategories: sortSubs(all) }
|
|
||||||
];
|
|
||||||
|
|
||||||
return built.filter(section => section.subcategories.some(sc => sc.tools.length > 0));
|
|
||||||
}, [groupedTools]);
|
|
||||||
|
|
||||||
const visibleSections = sections;
|
const visibleSections = sections;
|
||||||
|
|
||||||
@ -141,25 +82,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Build flat list by subcategory for search mode
|
// Build flat list by subcategory for search mode
|
||||||
const searchGroups = useMemo(() => {
|
const { searchGroups } = useToolSections(isSearching ? filteredTools : []);
|
||||||
if (!isSearching) return [] as Array<{ subcategory: string; tools: Array<{ id: string; tool: ToolRegistryEntry }> }>;
|
|
||||||
const subMap: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
|
|
||||||
const seen = new Set<string>();
|
|
||||||
filteredTools.forEach(([id, tool]) => {
|
|
||||||
if (seen.has(id)) return;
|
|
||||||
seen.add(id);
|
|
||||||
const sub = tool?.subcategory || 'General';
|
|
||||||
if (!subMap[sub]) subMap[sub] = [];
|
|
||||||
subMap[sub].push({ id, tool });
|
|
||||||
});
|
|
||||||
return Object.entries(subMap)
|
|
||||||
.sort(([a],[b]) => a.localeCompare(b))
|
|
||||||
.map(([subcategory, tools]) => ({
|
|
||||||
subcategory,
|
|
||||||
// preserve insertion order
|
|
||||||
tools
|
|
||||||
}));
|
|
||||||
}, [isSearching, filteredTools]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
84
frontend/src/hooks/useToolSections.ts
Normal file
84
frontend/src/hooks/useToolSections.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { type ToolRegistryEntry, SUBCATEGORY_ORDER } from '../data/toolRegistry';
|
||||||
|
|
||||||
|
type GroupedTools = {
|
||||||
|
[category: string]: {
|
||||||
|
[subcategory: string]: Array<{ id: string; tool: ToolRegistryEntry }>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useToolSections(filteredTools: [string, ToolRegistryEntry][]) {
|
||||||
|
const groupedTools = useMemo(() => {
|
||||||
|
const grouped: GroupedTools = {};
|
||||||
|
filteredTools.forEach(([id, tool]) => {
|
||||||
|
const category = tool?.category || 'OTHER';
|
||||||
|
const subcategory = tool?.subcategory || 'General';
|
||||||
|
if (!grouped[category]) grouped[category] = {};
|
||||||
|
if (!grouped[category][subcategory]) grouped[category][subcategory] = [];
|
||||||
|
grouped[category][subcategory].push({ id, tool });
|
||||||
|
});
|
||||||
|
return grouped;
|
||||||
|
}, [filteredTools]);
|
||||||
|
|
||||||
|
const sections = useMemo(() => {
|
||||||
|
const getOrderIndex = (name: string) => {
|
||||||
|
const idx = SUBCATEGORY_ORDER.indexOf(name);
|
||||||
|
return idx === -1 ? Number.MAX_SAFE_INTEGER : idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
const quick: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
|
||||||
|
const all: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
|
||||||
|
|
||||||
|
Object.entries(groupedTools).forEach(([origCat, subs]) => {
|
||||||
|
const upperCat = origCat.toUpperCase();
|
||||||
|
|
||||||
|
Object.entries(subs).forEach(([sub, tools]) => {
|
||||||
|
if (!all[sub]) all[sub] = [];
|
||||||
|
all[sub].push(...tools);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (upperCat === 'RECOMMENDED TOOLS') {
|
||||||
|
Object.entries(subs).forEach(([sub, tools]) => {
|
||||||
|
if (!quick[sub]) quick[sub] = [];
|
||||||
|
quick[sub].push(...tools);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortSubs = (obj: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>>) =>
|
||||||
|
Object.entries(obj)
|
||||||
|
.sort(([a], [b]) => {
|
||||||
|
const ai = getOrderIndex(a);
|
||||||
|
const bi = getOrderIndex(b);
|
||||||
|
if (ai !== bi) return ai - bi;
|
||||||
|
return a.localeCompare(b);
|
||||||
|
})
|
||||||
|
.map(([subcategory, tools]) => ({ subcategory, tools }));
|
||||||
|
|
||||||
|
const built = [
|
||||||
|
{ title: 'QUICK ACCESS', subcategories: sortSubs(quick) },
|
||||||
|
{ title: 'ALL TOOLS', subcategories: sortSubs(all) }
|
||||||
|
];
|
||||||
|
|
||||||
|
return built.filter(section => section.subcategories.some(sc => sc.tools.length > 0));
|
||||||
|
}, [groupedTools]);
|
||||||
|
|
||||||
|
const searchGroups = useMemo(() => {
|
||||||
|
const subMap: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
|
||||||
|
const seen = new Set<string>();
|
||||||
|
filteredTools.forEach(([id, tool]) => {
|
||||||
|
if (seen.has(id)) return;
|
||||||
|
seen.add(id);
|
||||||
|
const sub = tool?.subcategory || 'General';
|
||||||
|
if (!subMap[sub]) subMap[sub] = [];
|
||||||
|
subMap[sub].push({ id, tool });
|
||||||
|
});
|
||||||
|
return Object.entries(subMap)
|
||||||
|
.sort(([a], [b]) => a.localeCompare(b))
|
||||||
|
.map(([subcategory, tools]) => ({ subcategory, tools }));
|
||||||
|
}, [filteredTools]);
|
||||||
|
|
||||||
|
return { sections, searchGroups };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user