diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json
index 081f746ee..08995c2e0 100644
--- a/frontend/public/locales/en-GB/translation.json
+++ b/frontend/public/locales/en-GB/translation.json
@@ -363,6 +363,10 @@
"title": "Add image",
"desc": "Adds a image onto a set location on the PDF"
},
+ "attachments": {
+ "title": "Add Attachments",
+ "desc": "Add or remove embedded files (attachments) to/from a PDF"
+ },
"watermark": {
"title": "Add Watermark",
"desc": "Add a custom watermark to your PDF document."
@@ -578,6 +582,34 @@
"replaceColorPdf": {
"title": "Advanced Colour options",
"desc": "Replace colour for text and background in PDF and invert full colour of pdf to reduce file size"
+ },
+ "EMLToPDF": {
+ "title": "Email to PDF",
+ "desc": "Converts email (EML) files to PDF format including headers, body, and inline images"
+ },
+ "fakeScan": {
+ "title": "Fake Scan",
+ "desc": "Create a PDF that looks like it was scanned"
+ },
+ "editTableOfContents": {
+ "title": "Edit Table of Contents",
+ "desc": "Add or edit bookmarks and table of contents in PDF documents"
+ },
+ "automate": {
+ "title": "Automate",
+ "desc": "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
+ },
+ "manageCertificates": {
+ "title": "Manage Certificates",
+ "desc": "Import, export, or delete digital certificate files used for signing PDFs."
+ },
+ "read": {
+ "title": "Read",
+ "desc": "View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."
+ },
+ "reorganizePages": {
+ "title": "Reorganize Pages",
+ "desc": "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
}
},
"viewPdf": {
@@ -691,6 +723,15 @@
"upload": "Add image",
"submit": "Add image"
},
+ "attachments": {
+ "tags": "attachments,add,remove,embed,file",
+ "title": "Add Attachments",
+ "header": "Add Attachments",
+ "add": "Add Attachment",
+ "remove": "Remove Attachment",
+ "embed": "Embed Attachment",
+ "submit": "Add Attachments"
+ },
"watermark": {
"tags": "Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo",
"title": "Add Watermark",
@@ -1526,7 +1567,7 @@
"title": "How we use Cookies",
"description": {
"1": "We use cookies and other technologies to make Stirling PDF work better for you—helping us improve our tools and keep building features you'll love.",
- "2": "If you’d rather not, clicking 'No Thanks' will only enable the essential cookies needed to keep things running smoothly."
+ "2": "If you'd rather not, clicking 'No Thanks' will only enable the essential cookies needed to keep things running smoothly."
},
"acceptAllBtn": "Okay",
"acceptNecessaryBtn": "No Thanks",
@@ -1550,7 +1591,7 @@
"1": "Strictly Necessary Cookies",
"2": "Always Enabled"
},
- "description": "These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they can’t be turned off."
+ "description": "These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they can't be turned off."
},
"analytics": {
"title": "Analytics",
diff --git a/frontend/src/components/shared/QuickAccessBar.tsx b/frontend/src/components/shared/QuickAccessBar.tsx
index 22a49617e..710dc5b4a 100644
--- a/frontend/src/components/shared/QuickAccessBar.tsx
+++ b/frontend/src/components/shared/QuickAccessBar.tsx
@@ -144,7 +144,10 @@ const QuickAccessBar = ({
{
id: 'automate',
name: 'Automate',
- icon: ,
+ icon:
+
+ automation
+ ,
tooltip: 'Automate workflows',
size: 'lg',
isRound: false,
@@ -214,6 +217,9 @@ const QuickAccessBar = ({
return (
{/* Fixed header outside scrollable area */}
diff --git a/frontend/src/components/tools/ToolPicker.css b/frontend/src/components/tools/ToolPicker.css
new file mode 100644
index 000000000..dbd49f4f6
--- /dev/null
+++ b/frontend/src/components/tools/ToolPicker.css
@@ -0,0 +1,27 @@
+.tool-picker-scrollable {
+ overflow-y: auto !important;
+ overflow-x: hidden !important;
+ scrollbar-width: thin;
+ scrollbar-color: var(--mantine-color-gray-4) transparent;
+}
+
+.tool-picker-scrollable::-webkit-scrollbar {
+ width: 6px;
+}
+
+.tool-picker-scrollable::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.tool-picker-scrollable::-webkit-scrollbar-thumb {
+ background-color: var(--mantine-color-gray-4);
+ border-radius: 3px;
+}
+
+.tool-picker-scrollable::-webkit-scrollbar-thumb:hover {
+ background-color: var(--mantine-color-gray-5);
+}
+
+.search-input {
+ margin: 1rem;
+}
\ No newline at end of file
diff --git a/frontend/src/components/tools/ToolPicker.tsx b/frontend/src/components/tools/ToolPicker.tsx
index c22a0f60f..4e75fb73c 100644
--- a/frontend/src/components/tools/ToolPicker.tsx
+++ b/frontend/src/components/tools/ToolPicker.tsx
@@ -1,7 +1,21 @@
-import React, { useState } from "react";
-import { Box, Text, Stack, Button, TextInput, Group } from "@mantine/core";
+import React, { useState, useMemo } from "react";
+import { Box, Text, Stack, Button, TextInput, Group, Tooltip, Collapse, ActionIcon } from "@mantine/core";
import { useTranslation } from "react-i18next";
-import { ToolRegistry } from "../../types/tool";
+import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
+import ChevronRightIcon from "@mui/icons-material/ChevronRight";
+import SearchIcon from "@mui/icons-material/Search";
+import { baseToolRegistry } from "../../data/toolRegistry";
+import "./ToolPicker.css";
+
+type Tool = {
+ icon: React.ReactNode;
+ name: string;
+ description: string;
+};
+
+type ToolRegistry = {
+ [id: string]: Tool;
+};
interface ToolPickerProps {
selectedToolKey: string | null;
@@ -9,45 +23,218 @@ interface ToolPickerProps {
toolRegistry: ToolRegistry;
}
+interface GroupedTools {
+ [category: string]: {
+ [subcategory: string]: Array<{ id: string; tool: Tool }>;
+ };
+}
+
const ToolPicker = ({ selectedToolKey, onSelect, toolRegistry }: ToolPickerProps) => {
const { t } = useTranslation();
const [search, setSearch] = useState("");
+ const [expandedCategories, setExpandedCategories] = useState
>(new Set());
- const filteredTools = Object.entries(toolRegistry).filter(([_, { name }]) =>
- name.toLowerCase().includes(search.toLowerCase())
+ // Group tools by category and subcategory in a single pass - O(n)
+ const groupedTools = useMemo(() => {
+ const grouped: GroupedTools = {};
+
+ Object.entries(toolRegistry).forEach(([id, tool]) => {
+ // Get category and subcategory from the base registry
+ const baseTool = baseToolRegistry[id as keyof typeof baseToolRegistry];
+ const category = baseTool?.category || "Other";
+ const subcategory = baseTool?.subcategory || "General";
+
+ if (!grouped[category]) {
+ grouped[category] = {};
+ }
+ if (!grouped[category][subcategory]) {
+ grouped[category][subcategory] = [];
+ }
+
+ grouped[category][subcategory].push({ id, tool });
+ });
+
+ return grouped;
+ }, [toolRegistry]);
+
+ // Sort categories in custom order and subcategories alphabetically - O(c * s * log(s))
+ const sortedCategories = useMemo(() => {
+ const categoryOrder = ['RECOMMENDED TOOLS', 'STANDARD TOOLS', 'ADVANCED TOOLS'];
+
+ return Object.entries(groupedTools)
+ .map(([category, subcategories]) => ({
+ category,
+ subcategories: Object.entries(subcategories)
+ .sort(([a], [b]) => a.localeCompare(b)) // Sort subcategories alphabetically
+ .map(([subcategory, tools]) => ({
+ subcategory,
+ tools: tools.sort((a, b) => a.tool.name.localeCompare(b.tool.name)) // Sort tools alphabetically
+ }))
+ }))
+ .sort((a, b) => {
+ const aIndex = categoryOrder.indexOf(a.category.toUpperCase());
+ const bIndex = categoryOrder.indexOf(b.category.toUpperCase());
+ return aIndex - bIndex;
+ });
+ }, [groupedTools, t]);
+
+ // Filter tools based on search - O(n)
+ const filteredCategories = useMemo(() => {
+ if (!search.trim()) return sortedCategories;
+
+ return sortedCategories.map(({ category, subcategories }) => ({
+ category,
+ subcategories: subcategories.map(({ subcategory, tools }) => ({
+ subcategory,
+ tools: tools.filter(({ tool }) =>
+ tool.name.toLowerCase().includes(search.toLowerCase()) ||
+ tool.description.toLowerCase().includes(search.toLowerCase())
+ )
+ })).filter(({ tools }) => tools.length > 0)
+ })).filter(({ subcategories }) => subcategories.length > 0);
+ }, [sortedCategories, search, t]);
+
+ const toggleCategory = (category: string) => {
+ setExpandedCategories(prev => {
+ const newSet = new Set(prev);
+ if (newSet.has(category)) {
+ newSet.delete(category);
+ } else {
+ newSet.add(category);
+ }
+ return newSet;
+ });
+ };
+
+ const renderToolButton = (id: string, tool: Tool, index: number) => (
+
+ onSelect(id)}
+ size="md"
+ radius="md"
+ leftSection={tool.icon}
+ fullWidth
+ justify="flex-start"
+ style={{ borderRadius: '0' }}
+ >
+
+ {index + 1}.
+
+ {tool.name}
+
+
);
return (
-
- setSearch(e.currentTarget.value)}
- mb="md"
- autoComplete="off"
- />
-
- {filteredTools.length === 0 ? (
-
- {t("toolPicker.noToolsFound", "No tools found")}
-
- ) : (
- filteredTools.map(([id, { icon, name }]) => (
- onSelect(id)}
- size="md"
- radius="md"
- leftSection={icon}
- fullWidth
- justify="flex-start"
- >
- {name}
-
- ))
- )}
-
+
+ setSearch(e.currentTarget.value)}
+ autoComplete="off"
+ className="search-input rounded-lg"
+ leftSection={ }
+ />
+
+
+ {filteredCategories.length === 0 ? (
+
+ {t("toolPicker.noToolsFound", "No tools found")}
+
+ ) : (
+ filteredCategories.map(({ category, subcategories }) => (
+
+ {/* Category Header */}
+ toggleCategory(category)}
+ rightSection={
+
+
+
+ }
+ fullWidth
+ justify="space-between"
+ style={{
+ fontWeight: 'bold',
+ backgroundColor: 'var(--bg-toolbar)',
+ marginBottom: '0',
+ borderTop: '1px solid var(--border-default)',
+ borderBottom: '1px solid var(--border-default)',
+ borderRadius: '0',
+ padding: '0.75rem 1rem',
+ color: 'var(--text-primary)'
+ }}
+ >
+ {category.toUpperCase()}
+
+
+ {/* Subcategories */}
+
+
+ {subcategories.map(({ subcategory, tools }) => (
+
+ {/* Subcategory Header (only show if there are multiple subcategories) */}
+ {subcategories.length > 1 && (
+
+ {subcategory}
+
+ )}
+
+ {/* Tools in this subcategory */}
+
+ {tools.map(({ id, tool }, index) =>
+ renderToolButton(id, tool, index)
+ )}
+
+
+ ))}
+
+
+
+ ))
+ )}
+
+
);
};
diff --git a/frontend/src/data/toolRegistry.tsx b/frontend/src/data/toolRegistry.tsx
new file mode 100644
index 000000000..b24fd89bc
--- /dev/null
+++ b/frontend/src/data/toolRegistry.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import SplitPdfPanel from "../tools/Split";
+import CompressPdfPanel from "../tools/Compress";
+import MergePdfPanel from "../tools/Merge";
+
+export type ToolRegistryEntry = {
+ icon: React.ReactNode;
+ name: string;
+ component: React.ComponentType | null;
+ view: string;
+ description: string;
+ category: string;
+ subcategory: string | null;
+};
+
+export type ToolRegistry = {
+ [key: string]: ToolRegistryEntry;
+};
+
+export const baseToolRegistry: ToolRegistry = {
+ "add-attachments": { icon: attachment , name: "home.attachments.title", component: null, view: "format", description: "home.attachments.desc", category: "Standard Tools", subcategory: "Page Formatting" },
+ "add-image": { icon: image , name: "home.addImage.title", component: null, view: "format", description: "home.addImage.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
+ "add-page-numbers": { icon: 123 , name: "home.add-page-numbers.title", component: null, view: "format", description: "home.add-page-numbers.desc", category: "Standard Tools", subcategory: "Page Formatting" },
+ "add-password": { icon: password , name: "home.addPassword.title", component: null, view: "security", description: "home.addPassword.desc", category: "Standard Tools", subcategory: "Document Security" },
+ "add-stamp": { icon: approval , name: "home.AddStampRequest.title", component: null, view: "format", description: "home.AddStampRequest.desc", category: "Standard Tools", subcategory: "Document Security" },
+ "add-watermark": { icon: branding_watermark , name: "home.watermark.title", component: null, view: "format", description: "home.watermark.desc", category: "Standard Tools", subcategory: "Document Security" },
+ "adjust-colors-contrast": { icon: palette , name: "home.adjust-contrast.title", component: null, view: "format", description: "home.adjust-contrast.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
+ "adjust-page-size-scale": { icon: crop_free , name: "home.scalePages.title", component: null, view: "format", description: "home.scalePages.desc", category: "Standard Tools", subcategory: "Page Formatting" },
+ "auto-rename-pdf-file": { icon: match_word , name: "home.auto-rename.title", component: null, view: "format", description: "home.auto-rename.desc", category: "Advanced Tools", subcategory: "Automation" },
+ "auto-split-by-size-count": { icon: content_cut , name: "home.autoSizeSplitPDF.title", component: null, view: "format", description: "home.autoSizeSplitPDF.desc", category: "Advanced Tools", subcategory: "Automation" },
+ "auto-split-pages": { icon: split_scene_right , name: "home.autoSplitPDF.title", component: null, view: "format", description: "home.autoSplitPDF.desc", category: "Advanced Tools", subcategory: "Automation" },
+ "automate": { icon: automation , name: "home.automate.title", component: null, view: "format", description: "home.automate.desc", category: "Advanced Tools", subcategory: "Automation" },
+ "certSign": { icon: workspace_premium , name: "home.certSign.title", component: null, view: "sign", description: "home.certSign.desc", category: "Standard Tools", subcategory: "Signing" },
+ "change-metadata": { icon: assignment , name: "home.changeMetadata.title", component: null, view: "format", description: "home.changeMetadata.desc", category: "Standard Tools", subcategory: "Document Review" },
+ "change-permissions": { icon: admin_panel_settings , name: "home.permissions.title", component: null, view: "security", description: "home.permissions.desc", category: "Standard Tools", subcategory: "Document Review" },
+ "compare": { icon: compare , name: "home.compare.title", component: null, view: "format", description: "home.compare.desc", category: "Recommended Tools", subcategory: null },
+ "compressPdfs": { icon: zoom_in_map , name: "home.compressPdfs.title", component: CompressPdfPanel, view: "compress", description: "home.compressPdfs.desc", category: "Recommended Tools", subcategory: null },
+ "convert": { icon: sync_alt , name: "home.fileToPDF.title", component: null, view: "convert", description: "home.fileToPDF.desc", category: "Recommended Tools", subcategory: null },
+ "cropPdf": { icon: crop , name: "home.crop.title", component: null, view: "format", description: "home.crop.desc", category: "Standard Tools", subcategory: "Page Formatting" },
+ "detect-split-scanned-photos": { icon: scanner , name: "home.ScannerImageSplit.title", component: null, view: "format", description: "home.ScannerImageSplit.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
+ "edit-table-of-contents": { icon: bookmark_add , name: "home.editTableOfContents.title", component: null, view: "format", description: "home.editTableOfContents.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
+ "extract-images": { icon: filter , name: "home.extractImages.title", component: null, view: "extract", description: "home.extractImages.desc", category: "Standard Tools", subcategory: "Extraction" },
+ "extract-pages": { icon: upload , name: "home.extractPage.title", component: null, view: "extract", description: "home.extractPage.desc", category: "Standard Tools", subcategory: "Extraction" },
+ "flatten": { icon: layers_clear , name: "home.flatten.title", component: null, view: "format", description: "home.flatten.desc", category: "Standard Tools", subcategory: "Document Security" },
+ "get-all-info-on-pdf": { icon: fact_check , name: "home.getPdfInfo.title", component: null, view: "extract", description: "home.getPdfInfo.desc", category: "Standard Tools", subcategory: "Verification" },
+ "manage-certificates": { icon: license , name: "home.manageCertificates.title", component: null, view: "security", description: "home.manageCertificates.desc", category: "Standard Tools", subcategory: "Document Security" },
+ "mergePdfs": { icon: library_add , name: "home.merge.title", component: MergePdfPanel, view: "merge", description: "home.merge.desc", category: "Recommended Tools", subcategory: null },
+ "multi-page-layout": { icon: dashboard , name: "home.pageLayout.title", component: null, view: "format", description: "home.pageLayout.desc", category: "Standard Tools", subcategory: "Page Formatting" },
+ "multi-tool": { icon: dashboard_customize , name: "home.multiTool.title", component: null, view: "pageEditor", description: "home.multiTool.desc", category: "Recommended Tools", subcategory: null },
+ "ocr": { icon: quick_reference_all , name: "home.ocr.title", component: null, view: "convert", description: "home.ocr.desc", category: "Recommended Tools", subcategory: null },
+ "overlay-pdfs": { icon: layers , name: "home.overlay-pdfs.title", component: null, view: "format", description: "home.overlay-pdfs.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
+ "read": { icon: article , name: "home.read.title", component: null, view: "view", description: "home.read.desc", category: "Standard Tools", subcategory: "Document Review" },
+ "redact": { icon: visibility_off , name: "home.redact.title", component: null, view: "redact", description: "home.redact.desc", category: "Recommended Tools", subcategory: null },
+ "remove": { icon: delete , name: "home.removePages.title", component: null, view: "remove", description: "home.removePages.desc", category: "Standard Tools", subcategory: "Removal" },
+ "remove-annotations": { icon: thread_unread , name: "home.removeAnnotations.title", component: null, view: "remove", description: "home.removeAnnotations.desc", category: "Standard Tools", subcategory: "Removal" },
+ "remove-blank-pages": { icon: scan_delete , name: "home.removeBlanks.title", component: null, view: "remove", description: "home.removeBlanks.desc", category: "Standard Tools", subcategory: "Removal" },
+ "remove-certificate-sign": { icon: remove_moderator , name: "home.removeCertSign.title", component: null, view: "security", description: "home.removeCertSign.desc", category: "Standard Tools", subcategory: "Removal" },
+ "remove-image": { icon: remove_selection , name: "home.removeImagePdf.title", component: null, view: "format", description: "home.removeImagePdf.desc", category: "Standard Tools", subcategory: "Removal" },
+ "remove-password": { icon: lock_open_right , name: "home.removePassword.title", component: null, view: "security", description: "home.removePassword.desc", category: "Standard Tools", subcategory: "Removal" },
+ "repair": { icon: build , name: "home.repair.title", component: null, view: "format", description: "home.repair.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
+ "replace-and-invert-color": { icon: format_color_fill , name: "home.replaceColorPdf.title", component: null, view: "format", description: "home.replaceColorPdf.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
+ "reorganize-pages": { icon: move_down , name: "home.reorganizePages.title", component: null, view: "pageEditor", description: "home.reorganizePages.desc", category: "Standard Tools", subcategory: "Page Formatting" },
+ "rotate": { icon: rotate_right , name: "home.rotate.title", component: null, view: "format", description: "home.rotate.desc", category: "Standard Tools", subcategory: "Page Formatting" },
+ "sanitize": { icon: sanitizer , name: "home.sanitizePdf.title", component: null, view: "security", description: "home.sanitizePdf.desc", category: "Standard Tools", subcategory: "Document Security" },
+ "scanner-effect": { icon: scanner , name: "home.fakeScan.title", component: null, view: "format", description: "home.fakeScan.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
+ "show-javascript": { icon: javascript , name: "home.showJS.title", component: null, view: "extract", description: "home.showJS.desc", category: "Advanced Tools", subcategory: "Developer Tools" },
+ "sign": { icon: signature , name: "home.sign.title", component: null, view: "sign", description: "home.sign.desc", category: "Standard Tools", subcategory: "Signing" },
+ "single-large-page": { icon: looks_one , name: "home.PdfToSinglePage.title", component: null, view: "format", description: "home.PdfToSinglePage.desc", category: "Standard Tools", subcategory: "Page Formatting" },
+ "split": { icon: content_cut , name: "home.split.title", component: null, view: "format", description: "home.split.desc", category: "Standard Tools", subcategory: "Page Formatting" },
+ "split-by-chapters": { icon: collections_bookmark , name: "home.splitPdfByChapters.title", component: null, view: "format", description: "home.splitPdfByChapters.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
+ "split-by-sections": { icon: grid_on , name: "home.split-by-sections.title", component: null, view: "format", description: "home.split-by-sections.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
+ "splitPdf": { icon: content_cut , name: "home.split.title", component: SplitPdfPanel, view: "split", description: "home.split.desc", category: "Standard Tools", subcategory: "Page Formatting" },
+ "unlock-pdf-forms": { icon: preview_off , name: "home.unlockPDFForms.title", component: null, view: "security", description: "home.unlockPDFForms.desc", category: "Standard Tools", subcategory: "Document Security" },
+ "validate-pdf-signature": { icon: verified , name: "home.validateSignature.title", component: null, view: "security", description: "home.validateSignature.desc", category: "Standard Tools", subcategory: "Verification" },
+ "view-pdf": { icon: article , name: "home.viewPdf.title", component: null, view: "view", description: "home.viewPdf.desc", category: "Recommended Tools", subcategory: null
+ }
+};
+
+export const toolEndpoints: Record = {
+ split: ["split-pages", "split-pdf-by-sections", "split-by-size-or-count", "split-pdf-by-chapters"],
+ compressPdfs: ["compress-pdf"],
+ merge: ["merge-pdfs"],
+ // Add more endpoint mappings as needed
+};
\ No newline at end of file
diff --git a/frontend/src/hooks/useToolManagement.tsx b/frontend/src/hooks/useToolManagement.tsx
index 7ada59024..ec2630267 100644
--- a/frontend/src/hooks/useToolManagement.tsx
+++ b/frontend/src/hooks/useToolManagement.tsx
@@ -1,38 +1,11 @@
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
-import ContentCutIcon from "@mui/icons-material/ContentCut";
-import ZoomInMapIcon from "@mui/icons-material/ZoomInMap";
import { useMultipleEndpointsEnabled } from "./useEndpointConfig";
-import { Tool, ToolDefinition, BaseToolProps, ToolRegistry } from "../types/tool";
-
-
-// Add entry here with maxFiles, endpoints, and lazy component
-const toolDefinitions: Record = {
- split: {
- id: "split",
- icon: ,
- component: React.lazy(() => import("../tools/Split")),
- maxFiles: 1,
- category: "manipulation",
- description: "Split PDF files into smaller parts",
- endpoints: ["split-pages", "split-pdf-by-sections", "split-by-size-or-count", "split-pdf-by-chapters"]
- },
- compress: {
- id: "compress",
- icon: ,
- component: React.lazy(() => import("../tools/Compress")),
- maxFiles: -1,
- category: "optimization",
- description: "Reduce PDF file size",
- endpoints: ["compress-pdf"]
- },
-
-};
-
+import { baseToolRegistry, toolEndpoints, type ToolRegistry, type ToolRegistryEntry } from "../data/toolRegistry";
interface ToolManagementResult {
selectedToolKey: string | null;
- selectedTool: Tool | null;
+ selectedTool: ToolRegistryEntry | null;
toolSelectedFileIds: string[];
toolRegistry: ToolRegistry;
selectTool: (toolKey: string) => void;
@@ -47,30 +20,30 @@ export const useToolManagement = (): ToolManagementResult => {
const [toolSelectedFileIds, setToolSelectedFileIds] = useState([]);
const allEndpoints = Array.from(new Set(
- Object.values(toolDefinitions).flatMap(tool => tool.endpoints || [])
+ Object.values(toolEndpoints).flat() as string[]
));
const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabled(allEndpoints);
const isToolAvailable = useCallback((toolKey: string): boolean => {
if (endpointsLoading) return true;
- const tool = toolDefinitions[toolKey];
- if (!tool?.endpoints) return true;
- return tool.endpoints.some(endpoint => endpointStatus[endpoint] === true);
+ const endpoints = toolEndpoints[toolKey] || [];
+ return endpoints.length === 0 || endpoints.some((endpoint: string) => endpointStatus[endpoint] === true);
}, [endpointsLoading, endpointStatus]);
const toolRegistry: ToolRegistry = useMemo(() => {
- const availableTools: ToolRegistry = {};
- Object.keys(toolDefinitions).forEach(toolKey => {
+ const availableToolRegistry: ToolRegistry = {};
+ Object.keys(baseToolRegistry).forEach(toolKey => {
if (isToolAvailable(toolKey)) {
- const toolDef = toolDefinitions[toolKey];
- availableTools[toolKey] = {
- ...toolDef,
- name: t(`home.${toolKey}.title`, toolKey.charAt(0).toUpperCase() + toolKey.slice(1))
+ const baseTool = baseToolRegistry[toolKey as keyof typeof baseToolRegistry];
+ availableToolRegistry[toolKey] = {
+ ...baseTool,
+ name: t(baseTool.name),
+ description: t(baseTool.description)
};
}
});
- return availableTools;
- }, [t, isToolAvailable]);
+ return availableToolRegistry;
+ }, [isToolAvailable, t]);
useEffect(() => {
if (!endpointsLoading && selectedToolKey && !toolRegistry[selectedToolKey]) {
diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx
index d24c58b44..3a60071ef 100644
--- a/frontend/src/pages/HomePage.tsx
+++ b/frontend/src/pages/HomePage.tsx
@@ -104,10 +104,10 @@ function HomePageContent() {
{/* Left: Tool Picker or Selected Tool Panel */}
{/* Back button */}
-
+
{/* Tool title */}
-
+
{selectedTool?.name}
{/* Tool content */}
-