diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index aaa985d46..0a03fd01a 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -1925,6 +1925,11 @@ "noToolsFound": "No tools found", "allTools": "ALL TOOLS", "quickAccess": "QUICK ACCESS", + "categories": { + "standardTools": "Standard Tools", + "advancedTools": "Advanced Tools", + "recommendedTools": "Recommended Tools" + }, "subcategories": { "signing": "Signing", "documentSecurity": "Document Security", diff --git a/frontend/src/components/layout/Workbench.tsx b/frontend/src/components/layout/Workbench.tsx index fc41d2480..39c5166f7 100644 --- a/frontend/src/components/layout/Workbench.tsx +++ b/frontend/src/components/layout/Workbench.tsx @@ -14,6 +14,7 @@ import PageEditorControls from '../pageEditor/PageEditorControls'; import Viewer from '../viewer/Viewer'; import ToolRenderer from '../tools/ToolRenderer'; import LandingPage from '../shared/LandingPage'; +import { ToolId } from '../../data/toolsTaxonomy'; // No props needed - component uses contexts directly export default function Workbench() { @@ -42,15 +43,15 @@ export default function Workbench() { const handlePreviewClose = () => { setPreviewFile(null); const previousMode = sessionStorage.getItem('previousMode'); - if (previousMode === 'split') { + if (previousMode === ToolId.SPLIT_PDF) { // Use context's handleToolSelect which coordinates tool selection and view changes - handleToolSelect('split'); + handleToolSelect(ToolId.SPLIT_PDF); sessionStorage.removeItem('previousMode'); - } else if (previousMode === 'compress') { - handleToolSelect('compress'); + } else if (previousMode === ToolId.COMPRESS) { + handleToolSelect(ToolId.COMPRESS); sessionStorage.removeItem('previousMode'); - } else if (previousMode === 'convert') { - handleToolSelect('convert'); + } else if (previousMode === ToolId.CONVERT) { + handleToolSelect(ToolId.CONVERT); sessionStorage.removeItem('previousMode'); } else { setCurrentView('fileEditor'); diff --git a/frontend/src/components/shared/QuickAccessBar.tsx b/frontend/src/components/shared/QuickAccessBar.tsx index bc041a923..2ec161a00 100644 --- a/frontend/src/components/shared/QuickAccessBar.tsx +++ b/frontend/src/components/shared/QuickAccessBar.tsx @@ -13,11 +13,12 @@ import { ButtonConfig } from '../../types/sidebar'; import './quickAccessBar/QuickAccessBar.css'; import AllToolsNavButton from './AllToolsNavButton'; import ActiveToolButton from "./quickAccessBar/ActiveToolButton"; -import { - isNavButtonActive, - getNavButtonStyle, +import { + isNavButtonActive, + getNavButtonStyle, getActiveNavButton, } from './quickAccessBar/QuickAccessBar'; +import { ToolId } from "../../data/toolsTaxonomy"; const QuickAccessBar = forwardRef(({ }, ref) => { @@ -39,23 +40,23 @@ const QuickAccessBar = forwardRef(({ openFilesModal(); }; - + const buttonConfigs: ButtonConfig[] = [ { - id: 'read', + id: ToolId.READ, name: t("quickAccess.read", "Read"), icon: , size: 'lg', isRound: false, type: 'navigation', onClick: () => { - setActiveButton('read'); + setActiveButton(ToolId.READ); handleBackToTools(); handleReaderToggle(); } }, { - id: 'sign', + id: ToolId.SIGN, name: t("quickAccess.sign", "Sign"), icon: @@ -66,11 +67,11 @@ const QuickAccessBar = forwardRef(({ type: 'navigation', onClick: () => { setActiveButton('sign'); - handleToolSelect('sign'); + handleToolSelect(ToolId.SIGN); } }, { - id: 'automate', + id: ToolId.AUTOMATE, name: t("quickAccess.automate", "Automate"), icon: @@ -80,8 +81,8 @@ const QuickAccessBar = forwardRef(({ isRound: false, type: 'navigation', onClick: () => { - setActiveButton('automate'); - handleToolSelect('automate'); + setActiveButton(ToolId.AUTOMATE); + handleToolSelect(ToolId.AUTOMATE); } }, { @@ -226,4 +227,4 @@ const QuickAccessBar = forwardRef(({ ); }); -export default QuickAccessBar; \ No newline at end of file +export default QuickAccessBar; diff --git a/frontend/src/components/tools/SearchResults.tsx b/frontend/src/components/tools/SearchResults.tsx index 949bd2f64..63a746b49 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, ToolId, ToolRegistryEntry } from '../../data/toolsTaxonomy'; import ToolButton from './toolPicker/ToolButton'; import { useTranslation } from 'react-i18next'; import { useToolSections } from '../../hooks/useToolSections'; @@ -8,8 +8,8 @@ import SubcategoryHeader from './shared/SubcategoryHeader'; import NoToolsFound from './shared/NoToolsFound'; interface SearchResultsProps { - filteredTools: [string, ToolRegistryEntry][]; - onSelect: (id: string) => void; + filteredTools: [ToolId, ToolRegistryEntry][]; + onSelect: (id: ToolId) => void; } const SearchResults: React.FC = ({ filteredTools, onSelect }) => { @@ -23,8 +23,8 @@ const SearchResults: React.FC = ({ filteredTools, onSelect } return ( {searchGroups.map(group => ( - - + + {group.tools.map(({ id, tool }) => ( void; - filteredTools: [string, ToolRegistryEntry][]; + selectedToolKey: ToolId | null; + onSelect: (id: ToolId) => void; + filteredTools: [ToolId, ToolRegistryEntry][]; isSearching?: boolean; } // Helper function to render tool buttons for a subcategory const renderToolButtons = ( - subcategory: any, - selectedToolKey: string | null, - onSelect: (id: string) => void, + t: TFunction, + subcategory: SubcategoryGroup, + selectedToolKey: ToolId | null, + onSelect: (id: ToolId) => void, showSubcategoryHeader: boolean = true ) => ( - + {showSubcategoryHeader && ( - + )} - {subcategory.tools.map(({ id, tool }: { id: string; tool: any }) => ( + {subcategory.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] ); @@ -120,7 +122,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)) )} ) : ( @@ -164,8 +166,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) )} @@ -210,8 +212,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 b1f91fc39..da0418571 100644 --- a/frontend/src/components/tools/shared/SuggestedToolsSection.tsx +++ b/frontend/src/components/tools/shared/SuggestedToolsSection.tsx @@ -21,7 +21,7 @@ export function SuggestedToolsSection(): React.ReactElement { const IconComponent = tool.icon; return ( void; + onSelect: (id: ToolId) => void; } const ToolButton: React.FC = ({ id, tool, isSelected, onSelect }) => { - const handleClick = (id: string) => { + const handleClick = (id: ToolId) => { 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/contexts/ToolWorkflowContext.tsx b/frontend/src/contexts/ToolWorkflowContext.tsx index 4ca66e61c..60023b911 100644 --- a/frontend/src/contexts/ToolWorkflowContext.tsx +++ b/frontend/src/contexts/ToolWorkflowContext.tsx @@ -6,7 +6,7 @@ import React, { createContext, useContext, useReducer, useCallback, useMemo } from 'react'; import { useToolManagement } from '../hooks/useToolManagement'; import { PageEditorFunctions } from '../types/pageEditor'; -import { ToolRegistryEntry } from '../data/toolsTaxonomy'; +import { ToolId, ToolRegistryEntry } from '../data/toolsTaxonomy'; import { useToolWorkflowUrlSync } from '../hooks/useUrlSync'; // State interface @@ -69,10 +69,10 @@ function toolWorkflowReducer(state: ToolWorkflowState, action: ToolWorkflowActio // Context value interface interface ToolWorkflowContextValue extends ToolWorkflowState { // Tool management (from hook) - selectedToolKey: string | null; + selectedToolKey: ToolId | null; selectedTool: ToolRegistryEntry | null; toolRegistry: any; // From useToolManagement - + // UI Actions setSidebarsVisible: (visible: boolean) => void; setLeftPanelView: (view: 'toolPicker' | 'toolContent') => void; @@ -82,16 +82,16 @@ interface ToolWorkflowContextValue extends ToolWorkflowState { setSearchQuery: (query: string) => void; // Tool Actions - selectTool: (toolId: string) => void; + selectTool: (toolId: ToolId) => void; clearToolSelection: () => void; // Workflow Actions (compound actions) - handleToolSelect: (toolId: string) => void; + handleToolSelect: (toolId: ToolId) => void; handleBackToTools: () => void; handleReaderToggle: () => void; // Computed values - filteredTools: [string, ToolRegistryEntry][]; // Filtered by search + filteredTools: [ToolId, ToolRegistryEntry][]; // Filtered by search isPanelVisible: boolean; } @@ -144,9 +144,9 @@ export function ToolWorkflowProvider({ children, onViewChange, enableUrlSync = t }, []); // Workflow actions (compound actions that coordinate multiple state changes) - const handleToolSelect = useCallback((toolId: string) => { + const handleToolSelect = useCallback((toolId: ToolId) => { // Special-case: if tool is a dedicated reader tool, enter reader mode and do not go to toolContent - if (toolId === 'read' || toolId === 'view-pdf') { + if (toolId === ToolId.READ) { setReaderMode(true); setLeftPanelView('toolPicker'); clearToolSelection(); @@ -175,7 +175,7 @@ export function ToolWorkflowProvider({ children, onViewChange, enableUrlSync = t // Filter tools based on search query const filteredTools = useMemo(() => { if (!toolRegistry) return []; - return Object.entries(toolRegistry).filter(([_, { name }]) => + return (Object.entries(toolRegistry) as [ToolId, ToolRegistryEntry][]).filter(([_, { name }]) => name.toLowerCase().includes(state.searchQuery.toLowerCase()) ); }, [toolRegistry, state.searchQuery]); @@ -230,4 +230,4 @@ export function useToolWorkflow(): ToolWorkflowContextValue { throw new Error('useToolWorkflow must be used within a ToolWorkflowProvider'); } return context; -} \ No newline at end of file +} diff --git a/frontend/src/data/toolsTaxonomy.ts b/frontend/src/data/toolsTaxonomy.ts index a76752b87..90a29c6e6 100644 --- a/frontend/src/data/toolsTaxonomy.ts +++ b/frontend/src/data/toolsTaxonomy.ts @@ -2,102 +2,160 @@ import { type TFunction } from 'i18next'; import React from 'react'; import { BaseToolProps } from '../types/tool'; +export enum ToolId { + CERT_SIGN = 'certSign', + SIGN = 'sign', + ADD_PASSWORD = 'addPassword', + WATERMARK = 'watermark', + ADD_STAMP = 'add-stamp', + SANITIZE = 'sanitize', + FLATTEN = 'flatten', + UNLOCK_PDF_FORMS = 'unlock-pdf-forms', + MANAGE_CERTIFICATES = 'manage-certificates', + CHANGE_PERMISSIONS = 'change-permissions', + GET_ALL_INFO_ON_PDF = 'get-all-info-on-pdf', + VALIDATE_PDF_SIGNATURE = 'validate-pdf-signature', + READ = 'read', + CHANGE_METADATA = 'change-metadata', + CROP_PDF = 'cropPdf', + ROTATE = 'rotate', + SPLIT_PDF = 'splitPdf', + REORGANIZE_PAGES = 'reorganize-pages', + ADJUST_PAGE_SIZE_SCALE = 'adjust-page-size-scale', + ADD_PAGE_NUMBERS = 'addPageNumbers', + MULTI_PAGE_LAYOUT = 'multi-page-layout', + SINGLE_LARGE_PAGE = 'single-large-page', + ADD_ATTACHMENTS = 'add-attachments', + EXTRACT_PAGES = 'extractPages', + EXTRACT_IMAGES = 'extract-images', + REMOVE_PAGES = 'removePages', + REMOVE_BLANK_PAGES = 'remove-blank-pages', + REMOVE_ANNOTATIONS = 'remove-annotations', + REMOVE_IMAGE = 'remove-image', + REMOVE_PASSWORD = 'remove-password', + REMOVE_CERTIFICATE_SIGN = 'remove-certificate-sign', + AUTOMATE = 'automate', + AUTO_RENAME_PDF_FILE = 'auto-rename-pdf-file', + AUTO_SPLIT_PAGES = 'auto-split-pages', + AUTO_SPLIT_BY_SIZE_COUNT = 'auto-split-by-size-count', + ADJUST_CONTRAST = 'adjustContrast', + REPAIR = 'repair', + DETECT_SPLIT_SCANNED_PHOTOS = 'detect-split-scanned-photos', + OVERLAY_PDFS = 'overlay-pdfs', + REPLACE_AND_INVERT_COLOR = 'replace-and-invert-color', + ADD_IMAGE = 'add-image', + EDIT_TABLE_OF_CONTENTS = 'edit-table-of-contents', + SCANNER_EFFECT = 'scanner-effect', + SHOW_JAVASCRIPT = 'show-javascript', + DEV_API = 'dev-api', + DEV_FOLDER_SCANNING = 'dev-folder-scanning', + DEV_SSO_GUIDE = 'dev-sso-guide', + DEV_AIRGAPPED = 'dev-airgapped', + COMPARE = 'compare', + COMPRESS = 'compress', + CONVERT = 'convert', + MERGE_PDFS = 'mergePdfs', + MULTI_TOOL = 'multi-tool', + OCR = 'ocr', + REDACT = 'redact' +}; + 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 = { - icon: React.ReactNode; - name: string; - 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; - maxFiles?: number; - supportedFormats?: string[]; - endpoints?: string[]; - link?: string; - type?: string; + icon: React.ReactNode; + name: string; + component: React.ComponentType | null; + view: 'sign' | 'security' | 'format' | 'extract' | 'view' | 'merge' | 'pageEditor' | 'convert' | 'redact' | 'split' | 'convert' | 'remove' | 'compress' | 'external'; + description: string; + categoryId: ToolCategoryId; + subcategoryId: SubcategoryId; + maxFiles?: number; + supportedFormats?: string[]; + endpoints?: string[]; + link?: string; + type?: string; } -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 f27fbd5c7..011549569 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useTranslation } from 'react-i18next'; import SplitPdfPanel from "../tools/Split"; import CompressPdfPanel from "../tools/Compress"; @@ -8,7 +7,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, ToolId, ToolRegistry } from './toolsTaxonomy'; import AddWatermark from '../tools/AddWatermark'; import Repair from '../tools/Repair'; import SingleLargePage from '../tools/SingleLargePage'; @@ -24,319 +23,319 @@ export function useFlatToolRegistry(): ToolRegistry { const allTools: ToolRegistry = { // Signing - "certSign": { + [ToolId.CERT_SIGN]: { icon: workspace_premium, name: t("home.certSign.title", "Sign with Certificate"), 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": { + [ToolId.SIGN]: { icon: signature, name: t("home.sign.title", "Sign"), 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 }, // Document Security - "addPassword": { + [ToolId.ADD_PASSWORD]: { icon: password, name: t("home.addPassword.title", "Add Password"), 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"] }, - "watermark": { + [ToolId.WATERMARK]: { icon: branding_watermark, name: t("home.watermark.title", "Add Watermark"), component: AddWatermark, 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"] }, - "add-stamp": { + [ToolId.ADD_STAMP]: { icon: approval, name: t("home.AddStampRequest.title", "Add Stamp to PDF"), 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": { + [ToolId.SANITIZE]: { icon: cleaning_services, name: t("home.sanitize.title", "Sanitize"), 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"] }, - "flatten": { + [ToolId.FLATTEN]: { icon: layers_clear, name: t("home.flatten.title", "Flatten"), 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": { + [ToolId.UNLOCK_PDF_FORMS]: { icon: preview_off, name: t("home.unlockPDFForms.title", "Unlock PDF Forms"), 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"] }, - "manage-certificates": { + [ToolId.MANAGE_CERTIFICATES]: { icon: license, name: t("home.manageCertificates.title", "Manage Certificates"), 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": { + [ToolId.CHANGE_PERMISSIONS]: { icon: lock, name: t("home.changePermissions.title", "Change Permissions"), 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"] }, // Verification - "get-all-info-on-pdf": { + [ToolId.GET_ALL_INFO_ON_PDF]: { icon: fact_check, name: t("home.getPdfInfo.title", "Get ALL Info on PDF"), 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": { + [ToolId.VALIDATE_PDF_SIGNATURE]: { icon: verified, name: t("home.validateSignature.title", "Validate PDF Signature"), 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 }, // Document Review - "read": { + [ToolId.READ]: { icon: article, name: t("home.read.title", "Read"), 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": { + [ToolId.CHANGE_METADATA]: { icon: assignment, name: t("home.changeMetadata.title", "Change Metadata"), 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 - "cropPdf": { + [ToolId.CROP_PDF]: { icon: crop, name: t("home.crop.title", "Crop PDF"), 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": { + [ToolId.ROTATE]: { icon: rotate_right, name: t("home.rotate.title", "Rotate"), 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": { + [ToolId.SPLIT_PDF]: { icon: content_cut, name: t("home.split.title", "Split"), 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 }, - "reorganize-pages": { + [ToolId.REORGANIZE_PAGES]: { icon: move_down, name: t("home.reorganizePages.title", "Reorganize Pages"), 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": { + [ToolId.ADJUST_PAGE_SIZE_SCALE]: { icon: crop_free, name: t("home.scalePages.title", "Adjust page size/scale"), 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": { + [ToolId.ADD_PAGE_NUMBERS]: { icon: 123, name: t("home.addPageNumbers.title", "Add Page Numbers"), 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": { + [ToolId.MULTI_PAGE_LAYOUT]: { icon: dashboard, name: t("home.pageLayout.title", "Multi-Page Layout"), 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": { + [ToolId.SINGLE_LARGE_PAGE]: { icon: looks_one, name: t("home.pdfToSinglePage.title", "PDF to Single Large Page"), 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"] }, - "add-attachments": { + [ToolId.ADD_ATTACHMENTS]: { icon: attachment, name: t("home.attachments.title", "Add Attachments"), 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, }, // Extraction - "extractPages": { + [ToolId.EXTRACT_PAGES]: { icon: upload, name: t("home.extractPages.title", "Extract Pages"), 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": { + [ToolId.EXTRACT_IMAGES]: { icon: filter, name: t("home.extractImages.title", "Extract Images"), 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 }, // Removal - "removePages": { + [ToolId.REMOVE_PAGES]: { icon: delete, name: t("home.removePages.title", "Remove Pages"), 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": { + [ToolId.REMOVE_BLANK_PAGES]: { icon: scan_delete, name: t("home.removeBlanks.title", "Remove Blank Pages"), 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": { + [ToolId.REMOVE_ANNOTATIONS]: { icon: thread_unread, name: t("home.removeAnnotations.title", "Remove Annotations"), 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": { + [ToolId.REMOVE_IMAGE]: { icon: remove_selection, name: t("home.removeImagePdf.title", "Remove Image"), 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": { + [ToolId.REMOVE_PASSWORD]: { icon: lock_open_right, name: t("home.removePassword.title", "Remove Password"), 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, }, - "remove-certificate-sign": { + [ToolId.REMOVE_CERTIFICATE_SIGN]: { icon: remove_moderator, name: t("home.removeCertSign.title", "Remove Certificate Sign"), 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"] }, @@ -344,203 +343,203 @@ export function useFlatToolRegistry(): ToolRegistry { // Automation - "automate": { + [ToolId.AUTOMATE]: { icon: automation, name: t("home.automate.title", "Automate"), component: null, 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 }, - "auto-rename-pdf-file": { + [ToolId.AUTO_RENAME_PDF_FILE]: { icon: match_word, name: t("home.auto-rename.title", "Auto Rename PDF File"), 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": { + [ToolId.AUTO_SPLIT_PAGES]: { icon: split_scene_right, name: t("home.autoSplitPDF.title", "Auto Split Pages"), 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": { + [ToolId.AUTO_SPLIT_BY_SIZE_COUNT]: { icon: content_cut, name: t("home.autoSizeSplitPDF.title", "Auto Split by Size/Count"), 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 }, // Advanced Formatting - "adjustContrast": { + [ToolId.ADJUST_CONTRAST]: { icon: palette, name: t("home.adjustContrast.title", "Adjust Colors/Contrast"), 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": { + [ToolId.REPAIR]: { icon: build, name: t("home.repair.title", "Repair"), 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"] }, - "detect-split-scanned-photos": { + [ToolId.DETECT_SPLIT_SCANNED_PHOTOS]: { icon: scanner, name: t("home.ScannerImageSplit.title", "Detect & Split Scanned Photos"), 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": { + [ToolId.OVERLAY_PDFS]: { icon: layers, name: t("home.overlay-pdfs.title", "Overlay PDFs"), 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": { + [ToolId.REPLACE_AND_INVERT_COLOR]: { icon: format_color_fill, name: t("home.replaceColorPdf.title", "Replace & Invert Color"), 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": { + [ToolId.ADD_IMAGE]: { icon: image, name: t("home.addImage.title", "Add Image"), 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": { + [ToolId.EDIT_TABLE_OF_CONTENTS]: { icon: bookmark_add, name: t("home.editTableOfContents.title", "Edit Table of Contents"), 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": { + [ToolId.SCANNER_EFFECT]: { icon: scanner, name: t("home.fakeScan.title", "Scanner Effect"), 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 }, // Developer Tools - "show-javascript": { + [ToolId.SHOW_JAVASCRIPT]: { icon: javascript, name: t("home.showJS.title", "Show JavaScript"), 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": { + [ToolId.DEV_API]: { icon: open_in_new, name: t("home.devApi.title", "API"), 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": { + [ToolId.DEV_FOLDER_SCANNING]: { icon: open_in_new, name: t("home.devFolderScanning.title", "Automated Folder Scanning"), 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": { + [ToolId.DEV_SSO_GUIDE]: { icon: open_in_new, name: t("home.devSsoGuide.title", "SSO Guide"), 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": { + [ToolId.DEV_AIRGAPPED]: { icon: open_in_new, name: t("home.devAirgapped.title", "Air-gapped Setup"), 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" }, // Recommended Tools - "compare": { + [ToolId.COMPARE]: { icon: compare, name: t("home.compare.title", "Compare"), 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": { + [ToolId.COMPRESS]: { icon: zoom_in_map, name: t("home.compress.title", "Compress"), 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 }, - "convert": { + [ToolId.CONVERT]: { icon: sync_alt, name: t("home.convert.title", "Convert"), 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", @@ -577,51 +576,51 @@ export function useFlatToolRegistry(): ToolRegistry { "dbf", "fods", "vsd", "vor", "vor3", "vor4", "uop", "pct", "ps", "pdf" ] }, - "mergePdfs": { + [ToolId.MERGE_PDFS]: { icon: library_add, name: t("home.merge.title", "Merge"), 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": { + [ToolId.MULTI_TOOL]: { icon: dashboard_customize, name: t("home.multiTool.title", "Multi-Tool"), 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": { + [ToolId.OCR]: { icon: quick_reference_all, name: t("home.ocr.title", "OCR"), 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 }, - "redact": { + [ToolId.REDACT]: { icon: visibility_off, name: t("home.redact.title", "Redact"), 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 }, }; if (showPlaceholderTools) { return allTools; } else { - const filteredTools = Object.keys(allTools) + const filteredTools = (Object.keys(allTools) as ToolId[]) .filter(key => allTools[key].component !== null || allTools[key].link) .reduce((obj, key) => { obj[key] = allTools[key]; diff --git a/frontend/src/hooks/useSuggestedTools.ts b/frontend/src/hooks/useSuggestedTools.ts index ae6d77035..8b8809a7d 100644 --- a/frontend/src/hooks/useSuggestedTools.ts +++ b/frontend/src/hooks/useSuggestedTools.ts @@ -7,9 +7,10 @@ import SwapHorizIcon from '@mui/icons-material/SwapHoriz'; import CleaningServicesIcon from '@mui/icons-material/CleaningServices'; import CropIcon from '@mui/icons-material/Crop'; import TextFieldsIcon from '@mui/icons-material/TextFields'; +import { ToolId } from '../data/toolsTaxonomy'; export interface SuggestedTool { - name: string; + id: ToolId; title: string; icon: React.ComponentType; navigate: () => void; @@ -17,27 +18,27 @@ export interface SuggestedTool { const ALL_SUGGESTED_TOOLS: Omit[] = [ { - name: 'compress', + id: ToolId.COMPRESS, title: 'Compress', icon: CompressIcon }, { - name: 'convert', + id: ToolId.CONVERT, title: 'Convert', icon: SwapHorizIcon }, { - name: 'sanitize', + id: ToolId.SANITIZE, title: 'Sanitize', icon: CleaningServicesIcon }, { - name: 'split', + id: ToolId.SPLIT_PDF, title: 'Split', icon: CropIcon }, { - name: 'ocr', + id: ToolId.OCR, title: 'OCR', icon: TextFieldsIcon } @@ -48,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: () => handleToolSelect(tool.name) + navigate: () => handleToolSelect(tool.id) })); }, [selectedToolKey, handleToolSelect]); -} \ No newline at end of file +} diff --git a/frontend/src/hooks/useToolManagement.tsx b/frontend/src/hooks/useToolManagement.tsx index e991677a9..5c3359792 100644 --- a/frontend/src/hooks/useToolManagement.tsx +++ b/frontend/src/hooks/useToolManagement.tsx @@ -1,15 +1,15 @@ import React, { useState, useCallback, useMemo, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useFlatToolRegistry } from "../data/useTranslatedToolRegistry"; -import { getAllEndpoints, type ToolRegistryEntry } from "../data/toolsTaxonomy"; +import { getAllEndpoints, ToolId, type ToolRegistryEntry } from "../data/toolsTaxonomy"; import { useMultipleEndpointsEnabled } from "./useEndpointConfig"; interface ToolManagementResult { - selectedToolKey: string | null; + selectedToolKey: ToolId | null; selectedTool: ToolRegistryEntry | null; toolSelectedFileIds: string[]; toolRegistry: Record; - selectTool: (toolKey: string) => void; + selectTool: (toolKey: ToolId) => void; clearToolSelection: () => void; setToolSelectedFileIds: (fileIds: string[]) => void; } @@ -17,7 +17,7 @@ interface ToolManagementResult { export const useToolManagement = (): ToolManagementResult => { const { t } = useTranslation(); - const [selectedToolKey, setSelectedToolKey] = useState(null); + const [selectedToolKey, setSelectedToolKey] = useState(null); const [toolSelectedFileIds, setToolSelectedFileIds] = useState([]); // Build endpoints list from registry entries with fallback to legacy mapping @@ -35,7 +35,7 @@ export const useToolManagement = (): ToolManagementResult => { const allEndpoints = useMemo(() => getAllEndpoints(baseRegistry), [baseRegistry]); const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabled(allEndpoints); - const isToolAvailable = useCallback((toolKey: string): boolean => { + const isToolAvailable = useCallback((toolKey: ToolId): boolean => { if (endpointsLoading) return true; const endpoints = baseRegistry[toolKey]?.endpoints || []; return endpoints.length === 0 || endpoints.some((endpoint: string) => endpointStatus[endpoint] === true); @@ -43,7 +43,7 @@ export const useToolManagement = (): ToolManagementResult => { const toolRegistry: Record = useMemo(() => { const availableToolRegistry: Record = {}; - Object.keys(baseRegistry).forEach(toolKey => { + (Object.keys(baseRegistry) as ToolId[]).forEach(toolKey => { if (isToolAvailable(toolKey)) { const baseTool = baseRegistry[toolKey as keyof typeof baseRegistry]; availableToolRegistry[toolKey] = { @@ -58,7 +58,7 @@ export const useToolManagement = (): ToolManagementResult => { useEffect(() => { if (!endpointsLoading && selectedToolKey && !toolRegistry[selectedToolKey]) { - const firstAvailableTool = Object.keys(toolRegistry)[0]; + const firstAvailableTool = (Object.keys(toolRegistry) as ToolId[])[0]; if (firstAvailableTool) { setSelectedToolKey(firstAvailableTool); } else { @@ -67,7 +67,7 @@ export const useToolManagement = (): ToolManagementResult => { } }, [endpointsLoading, selectedToolKey, toolRegistry]); - const selectTool = useCallback((toolKey: string) => { + const selectTool = useCallback((toolKey: ToolId) => { setSelectedToolKey(toolKey); }, []); diff --git a/frontend/src/hooks/useToolSections.ts b/frontend/src/hooks/useToolSections.ts index 4c1a0c05d..b3d862799 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, ToolId, ToolRegistryEntry } from '../data/toolsTaxonomy'; import { useTranslation } from 'react-i18next'; +type SubcategoryIdMap = { + [subcategoryId in SubcategoryId]: Array<{ id: 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: ToolId; + tool: ToolRegistryEntry; + }[]; +}; + +export type ToolSectionKey = 'quick' | 'all'; + +export interface ToolSection { + key: ToolSectionKey; + title: string; + subcategories: SubcategoryGroup[]; +}; + +export function useToolSections(filteredTools: [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 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 };