diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 729d648f6..63937bc74 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -1927,18 +1927,23 @@ "noToolsFound": "No tools found", "allTools": "ALL TOOLS", "quickAccess": "QUICK ACCESS", + "categories": { + "standardTools": "Standard Tools", + "advancedTools": "Advanced Tools", + "recommendedTools": "Recommended Tools" + }, "subcategories": { - "Signing": "Signing", - "Document Security": "Document Security", - "Verification": "Verification", - "Document Review": "Document Review", - "Page Formatting": "Page Formatting", - "Extraction": "Extraction", - "Removal": "Removal", - "Automation": "Automation", - "General": "General", - "Advanced Formatting": "Advanced Formatting", - "Developer Tools": "Developer Tools" + "signing": "Signing", + "documentSecurity": "Document Security", + "verification": "Verification", + "documentReview": "Document Review", + "pageFormatting": "Page Formatting", + "extraction": "Extraction", + "removal": "Removal", + "automation": "Automation", + "general": "General", + "advancedFormatting": "Advanced Formatting", + "developerTools": "Developer Tools" } }, "quickAccess": { diff --git a/frontend/src/components/tools/SearchResults.tsx b/frontend/src/components/tools/SearchResults.tsx index 949bd2f64..6ecb64b7e 100644 --- a/frontend/src/components/tools/SearchResults.tsx +++ b/frontend/src/components/tools/SearchResults.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import { Box, Stack, Text } from '@mantine/core'; -import { ToolRegistryEntry } from '../../data/toolsTaxonomy'; +import { getSubcategoryLabel, ToolRegistryEntry } from '../../data/toolsTaxonomy'; import ToolButton from './toolPicker/ToolButton'; import { useTranslation } from 'react-i18next'; import { useToolSections } from '../../hooks/useToolSections'; @@ -23,8 +23,8 @@ const SearchResults: React.FC = ({ filteredTools, onSelect } return ( {searchGroups.map(group => ( - - + + {group.tools.map(({ id, tool }) => ( visibleSections.find(s => (s as any).key === 'quick'), + () => visibleSections.find(s => s.key === 'quick'), [visibleSections] ); const allSection = useMemo( - () => visibleSections.find(s => (s as any).key === 'all'), + () => visibleSections.find(s => s.key === 'all'), [visibleSections] ); @@ -95,7 +94,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa {searchGroups.length === 0 ? ( ) : ( - searchGroups.map(group => renderToolButtons(group, selectedToolKey, onSelect)) + searchGroups.map(group => renderToolButtons(t, group, selectedToolKey, onSelect)) )} ) : ( @@ -139,8 +138,8 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa - {quickSection?.subcategories.map(sc => - renderToolButtons(sc, selectedToolKey, onSelect, false) + {quickSection?.subcategories.map(sc => + renderToolButtons(t, sc, selectedToolKey, onSelect, false) )} @@ -185,8 +184,8 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa - {allSection?.subcategories.map(sc => - renderToolButtons(sc, selectedToolKey, onSelect, true) + {allSection?.subcategories.map(sc => + renderToolButtons(t, sc, selectedToolKey, onSelect, true) )} diff --git a/frontend/src/components/tools/shared/SuggestedToolsSection.tsx b/frontend/src/components/tools/shared/SuggestedToolsSection.tsx index 1055ed1f7..fca3b5e56 100644 --- a/frontend/src/components/tools/shared/SuggestedToolsSection.tsx +++ b/frontend/src/components/tools/shared/SuggestedToolsSection.tsx @@ -22,7 +22,7 @@ export function SuggestedToolsSection(): React.ReactElement { const IconComponent = tool.icon; return ( void, + t: TFunction, + subcategory: SubcategoryGroup, + selectedToolKey: String | null, + onSelect: (id: String) => void, showSubcategoryHeader: boolean = true ) => ( - + {showSubcategoryHeader && ( - + )} - {subcategory.tools.map(({ id, tool }: { id: string; tool: any }) => ( + {subcategory.tools.map(({ id, tool }) => ( -); \ No newline at end of file +); diff --git a/frontend/src/components/tools/toolPicker/ToolButton.tsx b/frontend/src/components/tools/toolPicker/ToolButton.tsx index af668a1fa..66bd9489e 100644 --- a/frontend/src/components/tools/toolPicker/ToolButton.tsx +++ b/frontend/src/components/tools/toolPicker/ToolButton.tsx @@ -14,9 +14,9 @@ interface ToolButtonProps { const ToolButton: React.FC = ({ id, tool, isSelected, onSelect }) => { const handleClick = (id: string) => { if (tool.link) { - // Open external link in new tab + // Open external link in new tab window.open(tool.link, '_blank', 'noopener,noreferrer'); - return; + return; } // Normal tool selection onSelect(id); @@ -47,4 +47,4 @@ const ToolButton: React.FC = ({ id, tool, isSelected, onSelect ); }; -export default ToolButton; \ No newline at end of file +export default ToolButton; diff --git a/frontend/src/data/toolsTaxonomy.ts b/frontend/src/data/toolsTaxonomy.ts index ed7cee091..0892b8b6c 100644 --- a/frontend/src/data/toolsTaxonomy.ts +++ b/frontend/src/data/toolsTaxonomy.ts @@ -5,23 +5,23 @@ import { BaseToolProps } from '../types/tool'; import { BaseParameters } from '../types/parameters'; export enum SubcategoryId { - SIGNING = 'signing', - DOCUMENT_SECURITY = 'documentSecurity', - VERIFICATION = 'verification', - DOCUMENT_REVIEW = 'documentReview', - PAGE_FORMATTING = 'pageFormatting', - EXTRACTION = 'extraction', - REMOVAL = 'removal', - AUTOMATION = 'automation', - GENERAL = 'general', - ADVANCED_FORMATTING = 'advancedFormatting', - DEVELOPER_TOOLS = 'developerTools' + SIGNING = 'signing', + DOCUMENT_SECURITY = 'documentSecurity', + VERIFICATION = 'verification', + DOCUMENT_REVIEW = 'documentReview', + PAGE_FORMATTING = 'pageFormatting', + EXTRACTION = 'extraction', + REMOVAL = 'removal', + AUTOMATION = 'automation', + GENERAL = 'general', + ADVANCED_FORMATTING = 'advancedFormatting', + DEVELOPER_TOOLS = 'developerTools' } -export enum ToolCategory { - STANDARD_TOOLS = 'Standard Tools', - ADVANCED_TOOLS = 'Advanced Tools', - RECOMMENDED_TOOLS = 'Recommended Tools' +export enum ToolCategoryId { + STANDARD_TOOLS = 'standardTools', + ADVANCED_TOOLS = 'advancedTools', + RECOMMENDED_TOOLS = 'recommendedTools' } export type ToolRegistryEntry = { @@ -30,8 +30,8 @@ export type ToolRegistryEntry = { component: React.ComponentType | null; view: 'sign' | 'security' | 'format' | 'extract' | 'view' | 'merge' | 'pageEditor' | 'convert' | 'redact' | 'split' | 'convert' | 'remove' | 'compress' | 'external'; description: string; - category: ToolCategory; - subcategory: SubcategoryId; + categoryId: ToolCategoryId; + subcategoryId: SubcategoryId; maxFiles?: number; supportedFormats?: string[]; endpoints?: string[]; @@ -43,67 +43,67 @@ export type ToolRegistryEntry = { settingsComponent?: React.ComponentType; } -export type ToolRegistry = Record; +export type ToolRegistry = Record; export const SUBCATEGORY_ORDER: SubcategoryId[] = [ - SubcategoryId.SIGNING, - SubcategoryId.DOCUMENT_SECURITY, - SubcategoryId.VERIFICATION, - SubcategoryId.DOCUMENT_REVIEW, - SubcategoryId.PAGE_FORMATTING, - SubcategoryId.EXTRACTION, - SubcategoryId.REMOVAL, - SubcategoryId.AUTOMATION, - SubcategoryId.GENERAL, - SubcategoryId.ADVANCED_FORMATTING, - SubcategoryId.DEVELOPER_TOOLS, + SubcategoryId.SIGNING, + SubcategoryId.DOCUMENT_SECURITY, + SubcategoryId.VERIFICATION, + SubcategoryId.DOCUMENT_REVIEW, + SubcategoryId.PAGE_FORMATTING, + SubcategoryId.EXTRACTION, + SubcategoryId.REMOVAL, + SubcategoryId.AUTOMATION, + SubcategoryId.GENERAL, + SubcategoryId.ADVANCED_FORMATTING, + SubcategoryId.DEVELOPER_TOOLS, ]; export const SUBCATEGORY_COLOR_MAP: Record = { - [SubcategoryId.SIGNING]: '#FF7892', - [SubcategoryId.DOCUMENT_SECURITY]: '#FF7892', - [SubcategoryId.VERIFICATION]: '#1BB1D4', - [SubcategoryId.DOCUMENT_REVIEW]: '#48BD54', - [SubcategoryId.PAGE_FORMATTING]: '#7882FF', - [SubcategoryId.EXTRACTION]: '#1BB1D4', - [SubcategoryId.REMOVAL]: '#7882FF', - [SubcategoryId.AUTOMATION]: '#69DC95', - [SubcategoryId.GENERAL]: '#69DC95', - [SubcategoryId.ADVANCED_FORMATTING]: '#F55454', - [SubcategoryId.DEVELOPER_TOOLS]: '#F55454', + [SubcategoryId.SIGNING]: '#FF7892', + [SubcategoryId.DOCUMENT_SECURITY]: '#FF7892', + [SubcategoryId.VERIFICATION]: '#1BB1D4', + [SubcategoryId.DOCUMENT_REVIEW]: '#48BD54', + [SubcategoryId.PAGE_FORMATTING]: '#7882FF', + [SubcategoryId.EXTRACTION]: '#1BB1D4', + [SubcategoryId.REMOVAL]: '#7882FF', + [SubcategoryId.AUTOMATION]: '#69DC95', + [SubcategoryId.GENERAL]: '#69DC95', + [SubcategoryId.ADVANCED_FORMATTING]: '#F55454', + [SubcategoryId.DEVELOPER_TOOLS]: '#F55454', }; -export const getSubcategoryColor = (subcategory: SubcategoryId): string => SUBCATEGORY_COLOR_MAP[subcategory] || '#7882FF'; - +export const getCategoryLabel = (t: TFunction, id: ToolCategoryId): string => t(`toolPicker.categories.${id}`, id); export const getSubcategoryLabel = (t: TFunction, id: SubcategoryId): string => t(`toolPicker.subcategories.${id}`, id); +export const getSubcategoryColor = (subcategory: SubcategoryId): string => SUBCATEGORY_COLOR_MAP[subcategory] || '#7882FF'; export const getAllEndpoints = (registry: ToolRegistry): string[] => { - const lists: string[][] = []; - Object.values(registry).forEach(entry => { - if (entry.endpoints && entry.endpoints.length > 0) { - lists.push(entry.endpoints); - } - }); - return Array.from(new Set(lists.flat())); + const lists: string[][] = []; + Object.values(registry).forEach(entry => { + if (entry.endpoints && entry.endpoints.length > 0) { + lists.push(entry.endpoints); + } + }); + return Array.from(new Set(lists.flat())); }; export const getConversionEndpoints = (extensionToEndpoint: Record>): string[] => { - const endpoints = new Set(); - Object.values(extensionToEndpoint).forEach(toEndpoints => { - Object.values(toEndpoints).forEach(endpoint => { - endpoints.add(endpoint); - }); - }); - return Array.from(endpoints); + const endpoints = new Set(); + Object.values(extensionToEndpoint).forEach(toEndpoints => { + Object.values(toEndpoints).forEach(endpoint => { + endpoints.add(endpoint); + }); + }); + return Array.from(endpoints); }; export const getAllApplicationEndpoints = ( - registry: ToolRegistry, - extensionToEndpoint?: Record> + registry: ToolRegistry, + extensionToEndpoint?: Record> ): string[] => { - const toolEp = getAllEndpoints(registry); - const convEp = extensionToEndpoint ? getConversionEndpoints(extensionToEndpoint) : []; - return Array.from(new Set([...toolEp, ...convEp])); + const toolEp = getAllEndpoints(registry); + const convEp = extensionToEndpoint ? getConversionEndpoints(extensionToEndpoint) : []; + return Array.from(new Set([...toolEp, ...convEp])); }; diff --git a/frontend/src/data/useTranslatedToolRegistry.tsx b/frontend/src/data/useTranslatedToolRegistry.tsx index 587b66d8f..3c3b5e89b 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -8,7 +8,7 @@ import Sanitize from '../tools/Sanitize'; import AddPassword from '../tools/AddPassword'; import ChangePermissions from '../tools/ChangePermissions'; import RemovePassword from '../tools/RemovePassword'; -import { SubcategoryId, ToolCategory, ToolRegistry } from './toolsTaxonomy'; +import { SubcategoryId, ToolCategoryId, ToolRegistry } from './toolsTaxonomy'; import AddWatermark from '../tools/AddWatermark'; import Repair from '../tools/Repair'; import SingleLargePage from '../tools/SingleLargePage'; @@ -55,8 +55,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "sign", description: t("home.certSign.desc", "Signs a PDF with a Certificate/Key (PEM/P12)"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.SIGNING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.SIGNING }, "sign": { icon: signature, @@ -64,8 +64,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "sign", description: t("home.sign.desc", "Adds signature to PDF by drawing, text or image"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.SIGNING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.SIGNING }, @@ -77,8 +77,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: AddPassword, view: "security", description: t("home.addPassword.desc", "Add password protection and restrictions to PDF files"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY, maxFiles: -1, endpoints: ["add-password"], operationConfig: addPasswordOperationConfig, @@ -91,8 +91,8 @@ export function useFlatToolRegistry(): ToolRegistry { view: "format", maxFiles: -1, description: t("home.watermark.desc", "Add a custom watermark to your PDF document."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY, endpoints: ["add-watermark"], operationConfig: addWatermarkOperationConfig, settingsComponent: AddWatermarkSingleStepSettings @@ -103,8 +103,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.AddStampRequest.desc", "Add text or add image stamps at set locations"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY }, "sanitize": { icon: cleaning_services, @@ -112,8 +112,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: Sanitize, view: "security", maxFiles: -1, - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY, description: t("home.sanitize.desc", "Remove potentially harmful elements from PDF files"), endpoints: ["sanitize-pdf"], operationConfig: sanitizeOperationConfig, @@ -125,8 +125,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.flatten.desc", "Remove all interactive elements and forms from a PDF"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY }, "unlock-pdf-forms": { icon: preview_off, @@ -134,8 +134,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: UnlockPdfForms, view: "security", description: t("home.unlockPDFForms.desc", "Remove read-only property of form fields in a PDF document."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY, maxFiles: -1, endpoints: ["unlock-pdf-forms"], operationConfig: unlockPdfFormsOperationConfig, @@ -147,8 +147,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "security", description: t("home.manageCertificates.desc", "Import, export, or delete digital certificate files used for signing PDFs."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY }, "change-permissions": { icon: lock, @@ -156,8 +156,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: ChangePermissions, view: "security", description: t("home.changePermissions.desc", "Change document restrictions and permissions"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY, maxFiles: -1, endpoints: ["add-password"], operationConfig: changePermissionsOperationConfig, @@ -171,8 +171,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "extract", description: t("home.getPdfInfo.desc", "Grabs any and all information possible on PDFs"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.VERIFICATION + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.VERIFICATION }, "validate-pdf-signature": { icon: verified, @@ -180,8 +180,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "security", description: t("home.validateSignature.desc", "Verify digital signatures and certificates in PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.VERIFICATION + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.VERIFICATION }, @@ -193,8 +193,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "view", description: t("home.read.desc", "View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_REVIEW + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_REVIEW }, "change-metadata": { icon: assignment, @@ -202,8 +202,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.changeMetadata.desc", "Change/Remove/Add metadata from a PDF document"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_REVIEW + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_REVIEW }, // Page Formatting @@ -213,8 +213,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.crop.desc", "Crop a PDF to reduce its size (maintains text!)"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "rotate": { icon: rotate_right, @@ -222,8 +222,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.rotate.desc", "Easily rotate your PDFs."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "splitPdf": { icon: content_cut, @@ -231,8 +231,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: SplitPdfPanel, view: "split", description: t("home.split.desc", "Split PDFs into multiple documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING, operationConfig: splitOperationConfig, settingsComponent: SplitSettings }, @@ -242,8 +242,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "pageEditor", description: t("home.reorganizePages.desc", "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "adjust-page-size-scale": { icon: crop_free, @@ -251,8 +251,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.scalePages.desc", "Change the size/scale of a page and/or its contents."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "addPageNumbers": { icon: 123, @@ -260,8 +260,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.addPageNumbers.desc", "Add Page numbers throughout a document in a set location"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "multi-page-layout": { icon: dashboard, @@ -269,8 +269,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.pageLayout.desc", "Merge multiple pages of a PDF document into a single page"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "single-large-page": { icon: looks_one, @@ -278,8 +278,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: SingleLargePage, view: "format", description: t("home.pdfToSinglePage.desc", "Merges all PDF pages into one large single page"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING, maxFiles: -1, endpoints: ["pdf-to-single-page"], operationConfig: singleLargePageOperationConfig @@ -290,8 +290,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.attachments.desc", "Add or remove embedded files (attachments) to/from a PDF"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING, }, @@ -303,8 +303,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "extract", description: t("home.extractPages.desc", "Extract specific pages from a PDF document"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.EXTRACTION + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.EXTRACTION }, "extract-images": { icon: filter, @@ -312,8 +312,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "extract", description: t("home.extractImages.desc", "Extract images from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.EXTRACTION + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.EXTRACTION }, @@ -325,8 +325,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "remove", description: t("home.removePages.desc", "Remove specific pages from a PDF document"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL }, "remove-blank-pages": { icon: scan_delete, @@ -334,8 +334,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "remove", description: t("home.removeBlanks.desc", "Remove blank pages from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL }, "remove-annotations": { icon: thread_unread, @@ -343,8 +343,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "remove", description: t("home.removeAnnotations.desc", "Remove annotations and comments from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL }, "remove-image": { icon: remove_selection, @@ -352,8 +352,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.removeImagePdf.desc", "Remove images from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL }, "remove-password": { icon: lock_open_right, @@ -361,8 +361,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: RemovePassword, view: "security", description: t("home.removePassword.desc", "Remove password protection from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL, endpoints: ["remove-password"], maxFiles: -1, operationConfig: removePasswordOperationConfig, @@ -374,8 +374,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: RemoveCertificateSign, view: "security", description: t("home.removeCertSign.desc", "Remove digital signature from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL, maxFiles: -1, endpoints: ["remove-certificate-sign"], operationConfig: removeCertificateSignOperationConfig @@ -390,8 +390,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: React.lazy(() => import('../tools/Automate')), view: "format", description: t("home.automate.desc", "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.AUTOMATION, + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.AUTOMATION, maxFiles: -1, endpoints: ["handleData"] }, @@ -401,8 +401,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.auto-rename.desc", "Automatically rename PDF files based on their content"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.AUTOMATION + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.AUTOMATION }, "auto-split-pages": { icon: split_scene_right, @@ -410,8 +410,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.autoSplitPDF.desc", "Automatically split PDF pages based on content detection"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.AUTOMATION + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.AUTOMATION }, "auto-split-by-size-count": { icon: content_cut, @@ -419,8 +419,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.autoSizeSplitPDF.desc", "Automatically split PDFs by file size or page count"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.AUTOMATION + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.AUTOMATION }, @@ -432,8 +432,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.adjustContrast.desc", "Adjust colors and contrast of PDF documents"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "repair": { icon: build, @@ -441,8 +441,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: Repair, view: "format", description: t("home.repair.desc", "Repair corrupted or damaged PDF files"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING, + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING, maxFiles: -1, endpoints: ["repair"], operationConfig: repairOperationConfig, @@ -454,8 +454,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.ScannerImageSplit.desc", "Detect and split scanned photos into separate pages"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "overlay-pdfs": { icon: layers, @@ -463,8 +463,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.overlay-pdfs.desc", "Overlay one PDF on top of another"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "replace-and-invert-color": { icon: format_color_fill, @@ -472,8 +472,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.replaceColorPdf.desc", "Replace or invert colors in PDF documents"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "add-image": { icon: image, @@ -481,8 +481,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.addImage.desc", "Add images to PDF documents"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "edit-table-of-contents": { icon: bookmark_add, @@ -490,8 +490,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.editTableOfContents.desc", "Add or edit bookmarks and table of contents in PDF documents"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "scanner-effect": { icon: scanner, @@ -499,8 +499,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.fakeScan.desc", "Create a PDF that looks like it was scanned"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, @@ -512,8 +512,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "extract", description: t("home.showJS.desc", "Extract and display JavaScript code from PDF documents"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.DEVELOPER_TOOLS + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.DEVELOPER_TOOLS }, "dev-api": { icon: open_in_new, @@ -521,8 +521,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "external", description: t("home.devApi.desc", "Link to API documentation"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.DEVELOPER_TOOLS, + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.DEVELOPER_TOOLS, link: "https://stirlingpdf.io/swagger-ui/5.21.0/index.html" }, "dev-folder-scanning": { @@ -531,8 +531,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "external", description: t("home.devFolderScanning.desc", "Link to automated folder scanning guide"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.DEVELOPER_TOOLS, + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.DEVELOPER_TOOLS, link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Folder%20Scanning/" }, "dev-sso-guide": { @@ -541,8 +541,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "external", description: t("home.devSsoGuide.desc", "Link to SSO guide"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.DEVELOPER_TOOLS, + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.DEVELOPER_TOOLS, link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Single%20Sign-On%20Configuration", }, "dev-airgapped": { @@ -551,8 +551,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "external", description: t("home.devAirgapped.desc", "Link to air-gapped setup guide"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.DEVELOPER_TOOLS, + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.DEVELOPER_TOOLS, link: "https://docs.stirlingpdf.com/Pro/#activation" }, @@ -564,8 +564,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.compare.desc", "Compare two PDF documents and highlight differences"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL }, "compress": { icon: zoom_in_map, @@ -573,8 +573,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: CompressPdfPanel, view: "compress", description: t("home.compress.desc", "Compress PDFs to reduce their file size."), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL, + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, maxFiles: -1, operationConfig: compressOperationConfig, settingsComponent: CompressSettings @@ -585,8 +585,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: ConvertPanel, view: "convert", description: t("home.convert.desc", "Convert files to and from PDF format"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL, + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, maxFiles: -1, endpoints: [ "pdf-to-img", @@ -631,8 +631,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "merge", description: t("home.merge.desc", "Merge multiple PDFs into a single document"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL, + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, maxFiles: -1 }, "multi-tool": { @@ -641,8 +641,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "pageEditor", description: t("home.multiTool.desc", "Use multiple tools on a single PDF document"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL, + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, maxFiles: -1 }, "ocr": { @@ -651,8 +651,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: OCRPanel, view: "convert", description: t("home.ocr.desc", "Extract text from scanned PDFs using Optical Character Recognition"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL, + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, maxFiles: -1, operationConfig: ocrOperationConfig, settingsComponent: OCRSettings @@ -663,8 +663,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "redact", description: t("home.redact.desc", "Permanently remove sensitive information from PDF documents"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL }, }; diff --git a/frontend/src/hooks/useSuggestedTools.ts b/frontend/src/hooks/useSuggestedTools.ts index d9e218451..f179a744f 100644 --- a/frontend/src/hooks/useSuggestedTools.ts +++ b/frontend/src/hooks/useSuggestedTools.ts @@ -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; navigate: () => void; @@ -17,27 +17,27 @@ export interface SuggestedTool { const ALL_SUGGESTED_TOOLS: Omit[] = [ { - 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 } @@ -49,12 +49,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: () => actions.handleToolSelect(tool.name) + navigate: () => actions.handleToolSelect(tool.id) })); }, [selectedToolKey, actions]); } diff --git a/frontend/src/hooks/useToolSections.ts b/frontend/src/hooks/useToolSections.ts index 4c1a0c05d..41762a8e1 100644 --- a/frontend/src/hooks/useToolSections.ts +++ b/frontend/src/hooks/useToolSections.ts @@ -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> = {}; - const all: Record> = {}; + 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>) => + 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> = {}; - const seen = new Set(); + const searchGroups: SubcategoryGroup[] = useMemo(() => { + const subMap = {} as SubcategoryIdMap; + const seen = new Set(); 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 };