mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
V2 Fix subcategory names in All Tools (and search) pane (#4252)
# Description of Changes Because we used string typing for IDs and names, it was really easy to make mistakes where variables named like `subcategory` would be stored as an ID in one file, but then read assuming it's a name in another file. This PR changes the code to consistently use enum cases when referring to IDs of categories, subcategories, and tools (at least in as many places as I can find them, ~I had to add a `ToolId` enum for this work~ I originally added a `ToolId` type for this work, but it caused too many issues when merging with #4222 so I've pulled it back out for now). Making that change made it obvious where we were inconsistently passing IDs and reading them as names etc. allowing me to fix rendering issues in the All Tools pane, where the subcategory IDs were being rendered directly (instead of being translated) or where IDs were being translated into names, but were then being re-translated, causing warnings in the log.
This commit is contained in:
@@ -9,7 +9,7 @@ import CropIcon from '@mui/icons-material/Crop';
|
||||
import TextFieldsIcon from '@mui/icons-material/TextFields';
|
||||
|
||||
export interface SuggestedTool {
|
||||
name: string;
|
||||
id: string /* FIX ME: Should be ToolId */;
|
||||
title: string;
|
||||
icon: React.ComponentType<any>;
|
||||
navigate: () => void;
|
||||
@@ -17,27 +17,27 @@ export interface SuggestedTool {
|
||||
|
||||
const ALL_SUGGESTED_TOOLS: Omit<SuggestedTool, 'navigate'>[] = [
|
||||
{
|
||||
name: 'compress',
|
||||
id: 'compress',
|
||||
title: 'Compress',
|
||||
icon: CompressIcon
|
||||
},
|
||||
{
|
||||
name: 'convert',
|
||||
id: 'convert',
|
||||
title: 'Convert',
|
||||
icon: SwapHorizIcon
|
||||
},
|
||||
{
|
||||
name: 'sanitize',
|
||||
id: 'sanitize',
|
||||
title: 'Sanitize',
|
||||
icon: CleaningServicesIcon
|
||||
},
|
||||
{
|
||||
name: 'split',
|
||||
id: 'split',
|
||||
title: 'Split',
|
||||
icon: CropIcon
|
||||
},
|
||||
{
|
||||
name: 'ocr',
|
||||
id: 'ocr',
|
||||
title: 'OCR',
|
||||
icon: TextFieldsIcon
|
||||
}
|
||||
@@ -48,12 +48,12 @@ export function useSuggestedTools(): SuggestedTool[] {
|
||||
|
||||
return useMemo(() => {
|
||||
// Filter out the current tool
|
||||
const filteredTools = ALL_SUGGESTED_TOOLS.filter(tool => tool.name !== selectedToolKey);
|
||||
|
||||
const filteredTools = ALL_SUGGESTED_TOOLS.filter(tool => tool.id !== selectedToolKey);
|
||||
|
||||
// Add navigation function to each tool
|
||||
return filteredTools.map(tool => ({
|
||||
...tool,
|
||||
navigate: () => handleToolSelect(tool.name)
|
||||
navigate: () => handleToolSelect(tool.id)
|
||||
}));
|
||||
}, [selectedToolKey, handleToolSelect]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ interface ToolManagementResult {
|
||||
export const useToolManagement = (): ToolManagementResult => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [selectedToolKey, setSelectedToolKey] = useState<string | null>(null);
|
||||
const [selectedToolKey, setSelectedToolKey] = useState<string /* FIX ME: Should be ToolId */ | null>(null);
|
||||
const [toolSelectedFileIds, setToolSelectedFileIds] = useState<string[]>([]);
|
||||
|
||||
// Build endpoints list from registry entries with fallback to legacy mapping
|
||||
|
||||
@@ -1,65 +1,87 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { SUBCATEGORY_ORDER, ToolCategory, ToolRegistryEntry } from '../data/toolsTaxonomy';
|
||||
import { SUBCATEGORY_ORDER, SubcategoryId, ToolCategoryId, ToolRegistryEntry } from '../data/toolsTaxonomy';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type SubcategoryIdMap = {
|
||||
[subcategoryId in SubcategoryId]: Array<{ id: string /* FIX ME: Should be ToolId */; tool: ToolRegistryEntry }>;
|
||||
}
|
||||
|
||||
type GroupedTools = {
|
||||
[category: string]: {
|
||||
[subcategory: string]: Array<{ id: string; tool: ToolRegistryEntry }>;
|
||||
};
|
||||
[categoryId in ToolCategoryId]: SubcategoryIdMap;
|
||||
};
|
||||
|
||||
export function useToolSections(filteredTools: [string, ToolRegistryEntry][]) {
|
||||
export interface SubcategoryGroup {
|
||||
subcategoryId: SubcategoryId;
|
||||
tools: {
|
||||
id: string /* FIX ME: Should be ToolId */;
|
||||
tool: ToolRegistryEntry;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type ToolSectionKey = 'quick' | 'all';
|
||||
|
||||
export interface ToolSection {
|
||||
key: ToolSectionKey;
|
||||
title: string;
|
||||
subcategories: SubcategoryGroup[];
|
||||
};
|
||||
|
||||
export function useToolSections(filteredTools: [string /* FIX ME: Should be ToolId */, ToolRegistryEntry][]) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const groupedTools = useMemo(() => {
|
||||
const grouped: GroupedTools = {};
|
||||
const grouped = {} as GroupedTools;
|
||||
filteredTools.forEach(([id, tool]) => {
|
||||
const category = tool.category;
|
||||
const subcategory = tool.subcategory;
|
||||
if (!grouped[category]) grouped[category] = {};
|
||||
if (!grouped[category][subcategory]) grouped[category][subcategory] = [];
|
||||
grouped[category][subcategory].push({ id, tool });
|
||||
const categoryId = tool.categoryId;
|
||||
const subcategoryId = tool.subcategoryId;
|
||||
if (!grouped[categoryId]) grouped[categoryId] = {} as SubcategoryIdMap;
|
||||
if (!grouped[categoryId][subcategoryId]) grouped[categoryId][subcategoryId] = [];
|
||||
grouped[categoryId][subcategoryId].push({ id, tool });
|
||||
});
|
||||
return grouped;
|
||||
}, [filteredTools]);
|
||||
|
||||
const sections = useMemo(() => {
|
||||
const getOrderIndex = (name: string) => {
|
||||
const idx = SUBCATEGORY_ORDER.indexOf(name as any);
|
||||
const sections: ToolSection[] = useMemo(() => {
|
||||
const getOrderIndex = (id: SubcategoryId) => {
|
||||
const idx = SUBCATEGORY_ORDER.indexOf(id);
|
||||
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 }>> = {};
|
||||
const quick = {} as SubcategoryIdMap;
|
||||
const all = {} as SubcategoryIdMap;
|
||||
|
||||
Object.entries(groupedTools).forEach(([origCat, subs]) => {
|
||||
const upperCat = origCat.toUpperCase();
|
||||
Object.entries(groupedTools).forEach(([c, subs]) => {
|
||||
const categoryId = c as ToolCategoryId;
|
||||
|
||||
Object.entries(subs).forEach(([sub, tools]) => {
|
||||
if (!all[sub]) all[sub] = [];
|
||||
all[sub].push(...tools);
|
||||
Object.entries(subs).forEach(([s, tools]) => {
|
||||
const subcategoryId = s as SubcategoryId;
|
||||
if (!all[subcategoryId]) all[subcategoryId] = [];
|
||||
all[subcategoryId].push(...tools);
|
||||
});
|
||||
|
||||
if (upperCat === ToolCategory.RECOMMENDED_TOOLS.toUpperCase()) {
|
||||
Object.entries(subs).forEach(([sub, tools]) => {
|
||||
if (!quick[sub]) quick[sub] = [];
|
||||
quick[sub].push(...tools);
|
||||
if (categoryId === ToolCategoryId.RECOMMENDED_TOOLS) {
|
||||
Object.entries(subs).forEach(([s, tools]) => {
|
||||
const subcategoryId = s as SubcategoryId;
|
||||
if (!quick[subcategoryId]) quick[subcategoryId] = [];
|
||||
quick[subcategoryId].push(...tools);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const sortSubs = (obj: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>>) =>
|
||||
const sortSubs = (obj: SubcategoryIdMap) =>
|
||||
Object.entries(obj)
|
||||
.sort(([a], [b]) => {
|
||||
const ai = getOrderIndex(a);
|
||||
const bi = getOrderIndex(b);
|
||||
const aId = a as SubcategoryId;
|
||||
const bId = b as SubcategoryId;
|
||||
const ai = getOrderIndex(aId);
|
||||
const bi = getOrderIndex(bId);
|
||||
if (ai !== bi) return ai - bi;
|
||||
return a.localeCompare(b);
|
||||
return aId.localeCompare(bId);
|
||||
})
|
||||
.map(([subcategory, tools]) => ({ subcategory, tools }));
|
||||
.map(([subcategoryId, tools]) => ({ subcategoryId, tools } as SubcategoryGroup));
|
||||
|
||||
const built = [
|
||||
const built: ToolSection[] = [
|
||||
{ key: 'quick', title: t('toolPicker.quickAccess', 'QUICK ACCESS'), subcategories: sortSubs(quick) },
|
||||
{ key: 'all', title: t('toolPicker.allTools', 'ALL TOOLS'), subcategories: sortSubs(all) }
|
||||
];
|
||||
@@ -67,19 +89,20 @@ export function useToolSections(filteredTools: [string, ToolRegistryEntry][]) {
|
||||
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>();
|
||||
const searchGroups: SubcategoryGroup[] = useMemo(() => {
|
||||
const subMap = {} as SubcategoryIdMap;
|
||||
const seen = new Set<string /* FIX ME: Should be ToolId */>();
|
||||
filteredTools.forEach(([id, tool]) => {
|
||||
if (seen.has(id)) return;
|
||||
seen.add(id);
|
||||
const sub = tool.subcategory;
|
||||
const toolId = id as string /* FIX ME: Should be ToolId */;
|
||||
if (seen.has(toolId)) return;
|
||||
seen.add(toolId);
|
||||
const sub = tool.subcategoryId;
|
||||
if (!subMap[sub]) subMap[sub] = [];
|
||||
subMap[sub].push({ id, tool });
|
||||
subMap[sub].push({ id: toolId, tool });
|
||||
});
|
||||
return Object.entries(subMap)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([subcategory, tools]) => ({ subcategory, tools }));
|
||||
.map(([subcategoryId, tools]) => ({ subcategoryId, tools } as SubcategoryGroup));
|
||||
}, [filteredTools]);
|
||||
|
||||
return { sections, searchGroups };
|
||||
|
||||
Reference in New Issue
Block a user