mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Consistently refer to IDs via types for categories, subcategories and tools. Also fixes related issues with subcategory name rendering
This commit is contained in:
parent
ec3cac484d
commit
c2feebc6a5
@ -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",
|
||||
|
@ -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');
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
getNavButtonStyle,
|
||||
getActiveNavButton,
|
||||
} from './quickAccessBar/QuickAccessBar';
|
||||
import { ToolId } from "../../data/toolsTaxonomy";
|
||||
|
||||
const QuickAccessBar = forwardRef<HTMLDivElement>(({
|
||||
}, ref) => {
|
||||
@ -42,20 +43,20 @@ const QuickAccessBar = forwardRef<HTMLDivElement>(({
|
||||
|
||||
const buttonConfigs: ButtonConfig[] = [
|
||||
{
|
||||
id: 'read',
|
||||
id: ToolId.READ,
|
||||
name: t("quickAccess.read", "Read"),
|
||||
icon: <MenuBookIcon sx={{ fontSize: "1.5rem" }} />,
|
||||
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:
|
||||
<span className="material-symbols-rounded font-size-20">
|
||||
@ -66,11 +67,11 @@ const QuickAccessBar = forwardRef<HTMLDivElement>(({
|
||||
type: 'navigation',
|
||||
onClick: () => {
|
||||
setActiveButton('sign');
|
||||
handleToolSelect('sign');
|
||||
handleToolSelect(ToolId.SIGN);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'automate',
|
||||
id: ToolId.AUTOMATE,
|
||||
name: t("quickAccess.automate", "Automate"),
|
||||
icon:
|
||||
<span className="material-symbols-rounded font-size-20">
|
||||
@ -80,8 +81,8 @@ const QuickAccessBar = forwardRef<HTMLDivElement>(({
|
||||
isRound: false,
|
||||
type: 'navigation',
|
||||
onClick: () => {
|
||||
setActiveButton('automate');
|
||||
handleToolSelect('automate');
|
||||
setActiveButton(ToolId.AUTOMATE);
|
||||
handleToolSelect(ToolId.AUTOMATE);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -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<SearchResultsProps> = ({ filteredTools, onSelect }) => {
|
||||
@ -23,8 +23,8 @@ const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect }
|
||||
return (
|
||||
<Stack p="sm" gap="xs">
|
||||
{searchGroups.map(group => (
|
||||
<Box key={group.subcategory} w="100%">
|
||||
<SubcategoryHeader label={t(`toolPicker.subcategories.${group.subcategory}`, group.subcategory)} />
|
||||
<Box key={group.subcategoryId} w="100%">
|
||||
<SubcategoryHeader label={getSubcategoryLabel(t, group.subcategoryId)} />
|
||||
<Stack gap="xs">
|
||||
{group.tools.map(({ id, tool }) => (
|
||||
<ToolButton
|
||||
|
@ -1,33 +1,35 @@
|
||||
import React, { useMemo, useRef, useLayoutEffect, useState } from "react";
|
||||
import { Box, Text, Stack } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ToolRegistryEntry } from "../../data/toolsTaxonomy";
|
||||
import { getSubcategoryLabel, ToolId, ToolRegistryEntry } from "../../data/toolsTaxonomy";
|
||||
import ToolButton from "./toolPicker/ToolButton";
|
||||
import "./toolPicker/ToolPicker.css";
|
||||
import { useToolSections } from "../../hooks/useToolSections";
|
||||
import { SubcategoryGroup, useToolSections } from "../../hooks/useToolSections";
|
||||
import SubcategoryHeader from "./shared/SubcategoryHeader";
|
||||
import NoToolsFound from "./shared/NoToolsFound";
|
||||
import { TFunction } from "i18next";
|
||||
|
||||
interface ToolPickerProps {
|
||||
selectedToolKey: string | null;
|
||||
onSelect: (id: string) => 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
|
||||
) => (
|
||||
<Box key={subcategory.subcategory} w="100%">
|
||||
<Box key={subcategory.subcategoryId} w="100%">
|
||||
{showSubcategoryHeader && (
|
||||
<SubcategoryHeader label={subcategory.subcategory} />
|
||||
<SubcategoryHeader label={getSubcategoryLabel(t, subcategory.subcategoryId)} />
|
||||
)}
|
||||
<Stack gap="xs">
|
||||
{subcategory.tools.map(({ id, tool }: { id: string; tool: any }) => (
|
||||
{subcategory.tools.map(({ id, tool }) => (
|
||||
<ToolButton
|
||||
key={id}
|
||||
id={id}
|
||||
@ -69,11 +71,11 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
||||
const { sections: visibleSections } = useToolSections(filteredTools);
|
||||
|
||||
const quickSection = useMemo(
|
||||
() => 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 ? (
|
||||
<NoToolsFound />
|
||||
) : (
|
||||
searchGroups.map(group => renderToolButtons(group, selectedToolKey, onSelect))
|
||||
searchGroups.map(group => renderToolButtons(t, group, selectedToolKey, onSelect))
|
||||
)}
|
||||
</Stack>
|
||||
) : (
|
||||
@ -165,7 +167,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
||||
<Box ref={quickAccessRef} w="100%">
|
||||
<Stack p="sm" gap="xs">
|
||||
{quickSection?.subcategories.map(sc =>
|
||||
renderToolButtons(sc, selectedToolKey, onSelect, false)
|
||||
renderToolButtons(t, sc, selectedToolKey, onSelect, false)
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
@ -211,7 +213,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa
|
||||
<Box ref={allToolsRef} w="100%">
|
||||
<Stack p="sm" gap="xs">
|
||||
{allSection?.subcategories.map(sc =>
|
||||
renderToolButtons(sc, selectedToolKey, onSelect, true)
|
||||
renderToolButtons(t, sc, selectedToolKey, onSelect, true)
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
@ -21,7 +21,7 @@ export function SuggestedToolsSection(): React.ReactElement {
|
||||
const IconComponent = tool.icon;
|
||||
return (
|
||||
<Card
|
||||
key={tool.name}
|
||||
key={tool.id}
|
||||
p="sm"
|
||||
withBorder
|
||||
style={{ cursor: 'pointer' }}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import React from "react";
|
||||
import { Button } from "@mantine/core";
|
||||
import { Tooltip } from "../../shared/Tooltip";
|
||||
import { ToolRegistryEntry } from "../../../data/toolsTaxonomy";
|
||||
import { ToolId, ToolRegistryEntry } from "../../../data/toolsTaxonomy";
|
||||
import FitText from "../../shared/FitText";
|
||||
|
||||
interface ToolButtonProps {
|
||||
id: string;
|
||||
id: ToolId;
|
||||
tool: ToolRegistryEntry;
|
||||
isSelected: boolean;
|
||||
onSelect: (id: string) => void;
|
||||
onSelect: (id: ToolId) => void;
|
||||
}
|
||||
|
||||
const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect }) => {
|
||||
const handleClick = (id: string) => {
|
||||
const handleClick = (id: ToolId) => {
|
||||
if (tool.link) {
|
||||
// Open external link in new tab
|
||||
window.open(tool.link, '_blank', 'noopener,noreferrer');
|
||||
|
@ -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,7 +69,7 @@ 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
|
||||
|
||||
@ -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]);
|
||||
|
@ -2,6 +2,64 @@ 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',
|
||||
@ -16,10 +74,10 @@ export enum SubcategoryId {
|
||||
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 = {
|
||||
@ -28,8 +86,8 @@ export type ToolRegistryEntry = {
|
||||
component: React.ComponentType<BaseToolProps> | 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[];
|
||||
@ -37,7 +95,7 @@ export type ToolRegistryEntry = {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export type ToolRegistry = Record<string, ToolRegistryEntry>;
|
||||
export type ToolRegistry = Record<ToolId, ToolRegistryEntry>;
|
||||
|
||||
export const SUBCATEGORY_ORDER: SubcategoryId[] = [
|
||||
SubcategoryId.SIGNING,
|
||||
@ -67,9 +125,9 @@ export const SUBCATEGORY_COLOR_MAP: Record<SubcategoryId, string> = {
|
||||
[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';
|
||||
|
||||
|
||||
|
||||
|
@ -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: <span className="material-symbols-rounded">workspace_premium</span>,
|
||||
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: <span className="material-symbols-rounded">signature</span>,
|
||||
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: <span className="material-symbols-rounded">password</span>,
|
||||
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: <span className="material-symbols-rounded">branding_watermark</span>,
|
||||
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: <span className="material-symbols-rounded">approval</span>,
|
||||
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: <span className="material-symbols-rounded">cleaning_services</span>,
|
||||
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: <span className="material-symbols-rounded">layers_clear</span>,
|
||||
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: <span className="material-symbols-rounded">preview_off</span>,
|
||||
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: <span className="material-symbols-rounded">license</span>,
|
||||
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: <span className="material-symbols-rounded">lock</span>,
|
||||
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: <span className="material-symbols-rounded">fact_check</span>,
|
||||
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: <span className="material-symbols-rounded">verified</span>,
|
||||
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: <span className="material-symbols-rounded">article</span>,
|
||||
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: <span className="material-symbols-rounded">assignment</span>,
|
||||
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: <span className="material-symbols-rounded">crop</span>,
|
||||
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: <span className="material-symbols-rounded">rotate_right</span>,
|
||||
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: <span className="material-symbols-rounded">content_cut</span>,
|
||||
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: <span className="material-symbols-rounded">move_down</span>,
|
||||
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: <span className="material-symbols-rounded">crop_free</span>,
|
||||
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: <span className="material-symbols-rounded">123</span>,
|
||||
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: <span className="material-symbols-rounded">dashboard</span>,
|
||||
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: <span className="material-symbols-rounded">looks_one</span>,
|
||||
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: <span className="material-symbols-rounded">attachment</span>,
|
||||
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: <span className="material-symbols-rounded">upload</span>,
|
||||
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: <span className="material-symbols-rounded">filter</span>,
|
||||
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: <span className="material-symbols-rounded">delete</span>,
|
||||
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: <span className="material-symbols-rounded">scan_delete</span>,
|
||||
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: <span className="material-symbols-rounded">thread_unread</span>,
|
||||
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: <span className="material-symbols-rounded">remove_selection</span>,
|
||||
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: <span className="material-symbols-rounded">lock_open_right</span>,
|
||||
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: <span className="material-symbols-rounded">remove_moderator</span>,
|
||||
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: <span className="material-symbols-rounded">automation</span>,
|
||||
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: <span className="material-symbols-rounded">match_word</span>,
|
||||
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: <span className="material-symbols-rounded">split_scene_right</span>,
|
||||
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: <span className="material-symbols-rounded">content_cut</span>,
|
||||
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: <span className="material-symbols-rounded">palette</span>,
|
||||
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: <span className="material-symbols-rounded">build</span>,
|
||||
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: <span className="material-symbols-rounded">scanner</span>,
|
||||
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: <span className="material-symbols-rounded">layers</span>,
|
||||
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: <span className="material-symbols-rounded">format_color_fill</span>,
|
||||
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: <span className="material-symbols-rounded">image</span>,
|
||||
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: <span className="material-symbols-rounded">bookmark_add</span>,
|
||||
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: <span className="material-symbols-rounded">scanner</span>,
|
||||
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: <span className="material-symbols-rounded">javascript</span>,
|
||||
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: <span className="material-symbols-rounded" style={{ color: '#2F7BF6' }}>open_in_new</span>,
|
||||
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: <span className="material-symbols-rounded" style={{ color: '#2F7BF6' }}>open_in_new</span>,
|
||||
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: <span className="material-symbols-rounded" style={{ color: '#2F7BF6' }}>open_in_new</span>,
|
||||
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: <span className="material-symbols-rounded" style={{ color: '#2F7BF6' }}>open_in_new</span>,
|
||||
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: <span className="material-symbols-rounded">compare</span>,
|
||||
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: <span className="material-symbols-rounded">zoom_in_map</span>,
|
||||
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: <span className="material-symbols-rounded">sync_alt</span>,
|
||||
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: <span className="material-symbols-rounded">library_add</span>,
|
||||
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: <span className="material-symbols-rounded">dashboard_customize</span>,
|
||||
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: <span className="material-symbols-rounded">quick_reference_all</span>,
|
||||
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: <span className="material-symbols-rounded">visibility_off</span>,
|
||||
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];
|
||||
|
@ -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<any>;
|
||||
navigate: () => void;
|
||||
@ -17,27 +18,27 @@ export interface SuggestedTool {
|
||||
|
||||
const ALL_SUGGESTED_TOOLS: Omit<SuggestedTool, 'navigate'>[] = [
|
||||
{
|
||||
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]);
|
||||
}
|
@ -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<string, ToolRegistryEntry>;
|
||||
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<string | null>(null);
|
||||
const [selectedToolKey, setSelectedToolKey] = useState<ToolId | null>(null);
|
||||
const [toolSelectedFileIds, setToolSelectedFileIds] = useState<string[]>([]);
|
||||
|
||||
// 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<string, ToolRegistryEntry> = useMemo(() => {
|
||||
const availableToolRegistry: Record<string, ToolRegistryEntry> = {};
|
||||
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);
|
||||
}, []);
|
||||
|
||||
|
@ -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<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
|
||||
const all: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
|
||||
const quick = {} as SubcategoryIdMap;
|
||||
const all = {} as SubcategoryIdMap;
|
||||
|
||||
Object.entries(groupedTools).forEach(([origCat, subs]) => {
|
||||
const upperCat = origCat.toUpperCase();
|
||||
Object.entries(groupedTools).forEach(([c, subs]) => {
|
||||
const categoryId = c as ToolCategoryId;
|
||||
|
||||
Object.entries(subs).forEach(([sub, tools]) => {
|
||||
if (!all[sub]) all[sub] = [];
|
||||
all[sub].push(...tools);
|
||||
Object.entries(subs).forEach(([s, tools]) => {
|
||||
const subcategoryId = s as SubcategoryId;
|
||||
if (!all[subcategoryId]) all[subcategoryId] = [];
|
||||
all[subcategoryId].push(...tools);
|
||||
});
|
||||
|
||||
if (upperCat === ToolCategory.RECOMMENDED_TOOLS.toUpperCase()) {
|
||||
Object.entries(subs).forEach(([sub, tools]) => {
|
||||
if (!quick[sub]) quick[sub] = [];
|
||||
quick[sub].push(...tools);
|
||||
if (categoryId === ToolCategoryId.RECOMMENDED_TOOLS) {
|
||||
Object.entries(subs).forEach(([s, tools]) => {
|
||||
const subcategoryId = s as SubcategoryId;
|
||||
if (!quick[subcategoryId]) quick[subcategoryId] = [];
|
||||
quick[subcategoryId].push(...tools);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const sortSubs = (obj: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>>) =>
|
||||
const sortSubs = (obj: SubcategoryIdMap) =>
|
||||
Object.entries(obj)
|
||||
.sort(([a], [b]) => {
|
||||
const ai = getOrderIndex(a);
|
||||
const bi = getOrderIndex(b);
|
||||
const aId = a as SubcategoryId;
|
||||
const bId = b as SubcategoryId;
|
||||
const ai = getOrderIndex(aId);
|
||||
const bi = getOrderIndex(bId);
|
||||
if (ai !== bi) return ai - bi;
|
||||
return a.localeCompare(b);
|
||||
return aId.localeCompare(bId);
|
||||
})
|
||||
.map(([subcategory, tools]) => ({ subcategory, tools }));
|
||||
.map(([subcategoryId, tools]) => ({ subcategoryId, tools } as SubcategoryGroup));
|
||||
|
||||
const built = [
|
||||
const built: ToolSection[] = [
|
||||
{ key: 'quick', title: t('toolPicker.quickAccess', 'QUICK ACCESS'), subcategories: sortSubs(quick) },
|
||||
{ key: 'all', title: t('toolPicker.allTools', 'ALL TOOLS'), subcategories: sortSubs(all) }
|
||||
];
|
||||
@ -67,19 +89,20 @@ export function useToolSections(filteredTools: [string, ToolRegistryEntry][]) {
|
||||
return built.filter(section => section.subcategories.some(sc => sc.tools.length > 0));
|
||||
}, [groupedTools]);
|
||||
|
||||
const searchGroups = useMemo(() => {
|
||||
const subMap: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
|
||||
const seen = new Set<string>();
|
||||
const searchGroups: SubcategoryGroup[] = useMemo(() => {
|
||||
const subMap = {} as SubcategoryIdMap;
|
||||
const seen = new Set<ToolId>();
|
||||
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 };
|
||||
|
Loading…
x
Reference in New Issue
Block a user