parking this to work on OCR, I added all the icons, tool names / descriptions and changed the styling of the all tools bar

This commit is contained in:
EthanHealy01 2025-07-28 14:41:33 +01:00
parent eaaaf5c06c
commit 6f7a157340
8 changed files with 407 additions and 89 deletions

View File

@ -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 youd 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 cant 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",

View File

@ -144,7 +144,10 @@ const QuickAccessBar = ({
{
id: 'automate',
name: 'Automate',
icon: <AutoAwesomeIcon sx={{ fontSize: "1.5rem" }} />,
icon:
<span className="material-symbols-rounded font-size-20">
automation
</span>,
tooltip: 'Automate workflows',
size: 'lg',
isRound: false,
@ -214,6 +217,9 @@ const QuickAccessBar = ({
return (
<div
className={`h-screen flex flex-col w-20 quick-access-bar-main ${isRainbowMode ? 'rainbow-mode' : ''}`}
style={{
borderRight: '1px solid var(--border-default)'
}}
>
{/* Fixed header outside scrollable area */}
<div className="quick-access-header">

View File

@ -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;
}

View File

@ -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<Set<string>>(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) => (
<Tooltip
key={id}
label={tool.description}
position="right"
withArrow
openDelay={500}
>
<Button
variant={selectedToolKey === id ? "filled" : "subtle"}
onClick={() => onSelect(id)}
size="md"
radius="md"
leftSection={tool.icon}
fullWidth
justify="flex-start"
style={{ borderRadius: '0' }}
>
<span style={{ marginRight: '8px', opacity: 0.6, fontSize: '0.8em' }}>
{index + 1}.
</span>
{tool.name}
</Button>
</Tooltip>
);
return (
<Box >
<TextInput
placeholder={t("toolPicker.searchPlaceholder", "Search tools...")}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
mb="md"
autoComplete="off"
/>
<Stack align="flex-start">
{filteredTools.length === 0 ? (
<Text c="dimmed" size="sm">
{t("toolPicker.noToolsFound", "No tools found")}
</Text>
) : (
filteredTools.map(([id, { icon, name }]) => (
<Button
key={id}
variant={selectedToolKey === id ? "filled" : "subtle"}
onClick={() => onSelect(id)}
size="md"
radius="md"
leftSection={icon}
fullWidth
justify="flex-start"
>
{name}
</Button>
))
)}
</Stack>
<Box style={{
height: '100%',
display: 'flex',
flexDirection: 'column',
backgroundColor: 'var(--bg-toolbar)',
padding: '0'
}}>
<TextInput
placeholder={t("toolPicker.searchPlaceholder", "Search tools...")}
value={search}
radius="md"
onChange={(e) => setSearch(e.currentTarget.value)}
autoComplete="off"
className="search-input rounded-lg"
leftSection={<SearchIcon sx={{ fontSize: 16, color: 'var(--search-text)' }} />}
/>
<Box
className="tool-picker-scrollable"
style={{
flex: 1,
overflowY: 'auto',
overflowX: 'hidden',
minHeight: 0,
maxHeight: 'calc(100vh - 200px)'
}}
>
<Stack align="flex-start" gap="xs">
{filteredCategories.length === 0 ? (
<Text c="dimmed" size="sm">
{t("toolPicker.noToolsFound", "No tools found")}
</Text>
) : (
filteredCategories.map(({ category, subcategories }) => (
<Box key={category} style={{ width: '100%' }}>
{/* Category Header */}
<Button
variant="subtle"
onClick={() => toggleCategory(category)}
rightSection={
<div style={{
transition: 'transform 0.2s ease',
transform: expandedCategories.has(category) ? 'rotate(90deg)' : 'rotate(0deg)'
}}>
<ChevronRightIcon sx={{ fontSize: 16, color: 'var(--text-primary)' }} />
</div>
}
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()}
</Button>
{/* Subcategories */}
<Collapse in={expandedCategories.has(category)}>
<Stack gap="xs" style={{ paddingLeft: '1rem', paddingRight: '1rem' }}>
{subcategories.map(({ subcategory, tools }) => (
<Box key={subcategory}>
{/* Subcategory Header (only show if there are multiple subcategories) */}
{subcategories.length > 1 && (
<Text
size="sm"
fw={500}
style={{
marginBottom: '4px',
textTransform: 'uppercase',
fontSize: '0.75rem',
borderBottom: '1px solid var(--border-default)',
paddingBottom: '0.5rem',
marginLeft: '1rem',
marginRight: '1rem',
color: 'var(--text-secondary)'
}}
>
{subcategory}
</Text>
)}
{/* Tools in this subcategory */}
<Stack gap="xs">
{tools.map(({ id, tool }, index) =>
renderToolButton(id, tool, index)
)}
</Stack>
</Box>
))}
</Stack>
</Collapse>
</Box>
))
)}
</Stack>
</Box>
</Box>
);
};

View File

@ -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<any> | null;
view: string;
description: string;
category: string;
subcategory: string | null;
};
export type ToolRegistry = {
[key: string]: ToolRegistryEntry;
};
export const baseToolRegistry: ToolRegistry = {
"add-attachments": { icon: <span className="material-symbols-rounded">attachment</span>, name: "home.attachments.title", component: null, view: "format", description: "home.attachments.desc", category: "Standard Tools", subcategory: "Page Formatting" },
"add-image": { icon: <span className="material-symbols-rounded">image</span>, name: "home.addImage.title", component: null, view: "format", description: "home.addImage.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
"add-page-numbers": { icon: <span className="material-symbols-rounded">123</span>, 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: <span className="material-symbols-rounded">password</span>, name: "home.addPassword.title", component: null, view: "security", description: "home.addPassword.desc", category: "Standard Tools", subcategory: "Document Security" },
"add-stamp": { icon: <span className="material-symbols-rounded">approval</span>, name: "home.AddStampRequest.title", component: null, view: "format", description: "home.AddStampRequest.desc", category: "Standard Tools", subcategory: "Document Security" },
"add-watermark": { icon: <span className="material-symbols-rounded">branding_watermark</span>, name: "home.watermark.title", component: null, view: "format", description: "home.watermark.desc", category: "Standard Tools", subcategory: "Document Security" },
"adjust-colors-contrast": { icon: <span className="material-symbols-rounded">palette</span>, 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: <span className="material-symbols-rounded">crop_free</span>, name: "home.scalePages.title", component: null, view: "format", description: "home.scalePages.desc", category: "Standard Tools", subcategory: "Page Formatting" },
"auto-rename-pdf-file": { icon: <span className="material-symbols-rounded">match_word</span>, 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: <span className="material-symbols-rounded">content_cut</span>, name: "home.autoSizeSplitPDF.title", component: null, view: "format", description: "home.autoSizeSplitPDF.desc", category: "Advanced Tools", subcategory: "Automation" },
"auto-split-pages": { icon: <span className="material-symbols-rounded">split_scene_right</span>, name: "home.autoSplitPDF.title", component: null, view: "format", description: "home.autoSplitPDF.desc", category: "Advanced Tools", subcategory: "Automation" },
"automate": { icon: <span className="material-symbols-rounded">automation</span>, name: "home.automate.title", component: null, view: "format", description: "home.automate.desc", category: "Advanced Tools", subcategory: "Automation" },
"certSign": { icon: <span className="material-symbols-rounded">workspace_premium</span>, name: "home.certSign.title", component: null, view: "sign", description: "home.certSign.desc", category: "Standard Tools", subcategory: "Signing" },
"change-metadata": { icon: <span className="material-symbols-rounded">assignment</span>, name: "home.changeMetadata.title", component: null, view: "format", description: "home.changeMetadata.desc", category: "Standard Tools", subcategory: "Document Review" },
"change-permissions": { icon: <span className="material-symbols-rounded">admin_panel_settings</span>, name: "home.permissions.title", component: null, view: "security", description: "home.permissions.desc", category: "Standard Tools", subcategory: "Document Review" },
"compare": { icon: <span className="material-symbols-rounded">compare</span>, name: "home.compare.title", component: null, view: "format", description: "home.compare.desc", category: "Recommended Tools", subcategory: null },
"compressPdfs": { icon: <span className="material-symbols-rounded">zoom_in_map</span>, name: "home.compressPdfs.title", component: CompressPdfPanel, view: "compress", description: "home.compressPdfs.desc", category: "Recommended Tools", subcategory: null },
"convert": { icon: <span className="material-symbols-rounded">sync_alt</span>, name: "home.fileToPDF.title", component: null, view: "convert", description: "home.fileToPDF.desc", category: "Recommended Tools", subcategory: null },
"cropPdf": { icon: <span className="material-symbols-rounded">crop</span>, name: "home.crop.title", component: null, view: "format", description: "home.crop.desc", category: "Standard Tools", subcategory: "Page Formatting" },
"detect-split-scanned-photos": { icon: <span className="material-symbols-rounded">scanner</span>, name: "home.ScannerImageSplit.title", component: null, view: "format", description: "home.ScannerImageSplit.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
"edit-table-of-contents": { icon: <span className="material-symbols-rounded">bookmark_add</span>, name: "home.editTableOfContents.title", component: null, view: "format", description: "home.editTableOfContents.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
"extract-images": { icon: <span className="material-symbols-rounded">filter</span>, name: "home.extractImages.title", component: null, view: "extract", description: "home.extractImages.desc", category: "Standard Tools", subcategory: "Extraction" },
"extract-pages": { icon: <span className="material-symbols-rounded">upload</span>, name: "home.extractPage.title", component: null, view: "extract", description: "home.extractPage.desc", category: "Standard Tools", subcategory: "Extraction" },
"flatten": { icon: <span className="material-symbols-rounded">layers_clear</span>, name: "home.flatten.title", component: null, view: "format", description: "home.flatten.desc", category: "Standard Tools", subcategory: "Document Security" },
"get-all-info-on-pdf": { icon: <span className="material-symbols-rounded">fact_check</span>, name: "home.getPdfInfo.title", component: null, view: "extract", description: "home.getPdfInfo.desc", category: "Standard Tools", subcategory: "Verification" },
"manage-certificates": { icon: <span className="material-symbols-rounded">license</span>, name: "home.manageCertificates.title", component: null, view: "security", description: "home.manageCertificates.desc", category: "Standard Tools", subcategory: "Document Security" },
"mergePdfs": { icon: <span className="material-symbols-rounded">library_add</span>, name: "home.merge.title", component: MergePdfPanel, view: "merge", description: "home.merge.desc", category: "Recommended Tools", subcategory: null },
"multi-page-layout": { icon: <span className="material-symbols-rounded">dashboard</span>, name: "home.pageLayout.title", component: null, view: "format", description: "home.pageLayout.desc", category: "Standard Tools", subcategory: "Page Formatting" },
"multi-tool": { icon: <span className="material-symbols-rounded">dashboard_customize</span>, name: "home.multiTool.title", component: null, view: "pageEditor", description: "home.multiTool.desc", category: "Recommended Tools", subcategory: null },
"ocr": { icon: <span className="material-symbols-rounded">quick_reference_all</span>, name: "home.ocr.title", component: null, view: "convert", description: "home.ocr.desc", category: "Recommended Tools", subcategory: null },
"overlay-pdfs": { icon: <span className="material-symbols-rounded">layers</span>, name: "home.overlay-pdfs.title", component: null, view: "format", description: "home.overlay-pdfs.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
"read": { icon: <span className="material-symbols-rounded">article</span>, name: "home.read.title", component: null, view: "view", description: "home.read.desc", category: "Standard Tools", subcategory: "Document Review" },
"redact": { icon: <span className="material-symbols-rounded">visibility_off</span>, name: "home.redact.title", component: null, view: "redact", description: "home.redact.desc", category: "Recommended Tools", subcategory: null },
"remove": { icon: <span className="material-symbols-rounded">delete</span>, name: "home.removePages.title", component: null, view: "remove", description: "home.removePages.desc", category: "Standard Tools", subcategory: "Removal" },
"remove-annotations": { icon: <span className="material-symbols-rounded">thread_unread</span>, name: "home.removeAnnotations.title", component: null, view: "remove", description: "home.removeAnnotations.desc", category: "Standard Tools", subcategory: "Removal" },
"remove-blank-pages": { icon: <span className="material-symbols-rounded">scan_delete</span>, name: "home.removeBlanks.title", component: null, view: "remove", description: "home.removeBlanks.desc", category: "Standard Tools", subcategory: "Removal" },
"remove-certificate-sign": { icon: <span className="material-symbols-rounded">remove_moderator</span>, name: "home.removeCertSign.title", component: null, view: "security", description: "home.removeCertSign.desc", category: "Standard Tools", subcategory: "Removal" },
"remove-image": { icon: <span className="material-symbols-rounded">remove_selection</span>, name: "home.removeImagePdf.title", component: null, view: "format", description: "home.removeImagePdf.desc", category: "Standard Tools", subcategory: "Removal" },
"remove-password": { icon: <span className="material-symbols-rounded">lock_open_right</span>, name: "home.removePassword.title", component: null, view: "security", description: "home.removePassword.desc", category: "Standard Tools", subcategory: "Removal" },
"repair": { icon: <span className="material-symbols-rounded">build</span>, name: "home.repair.title", component: null, view: "format", description: "home.repair.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
"replace-and-invert-color": { icon: <span className="material-symbols-rounded">format_color_fill</span>, name: "home.replaceColorPdf.title", component: null, view: "format", description: "home.replaceColorPdf.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
"reorganize-pages": { icon: <span className="material-symbols-rounded">move_down</span>, name: "home.reorganizePages.title", component: null, view: "pageEditor", description: "home.reorganizePages.desc", category: "Standard Tools", subcategory: "Page Formatting" },
"rotate": { icon: <span className="material-symbols-rounded">rotate_right</span>, name: "home.rotate.title", component: null, view: "format", description: "home.rotate.desc", category: "Standard Tools", subcategory: "Page Formatting" },
"sanitize": { icon: <span className="material-symbols-rounded">sanitizer</span>, name: "home.sanitizePdf.title", component: null, view: "security", description: "home.sanitizePdf.desc", category: "Standard Tools", subcategory: "Document Security" },
"scanner-effect": { icon: <span className="material-symbols-rounded">scanner</span>, name: "home.fakeScan.title", component: null, view: "format", description: "home.fakeScan.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
"show-javascript": { icon: <span className="material-symbols-rounded">javascript</span>, name: "home.showJS.title", component: null, view: "extract", description: "home.showJS.desc", category: "Advanced Tools", subcategory: "Developer Tools" },
"sign": { icon: <span className="material-symbols-rounded">signature</span>, name: "home.sign.title", component: null, view: "sign", description: "home.sign.desc", category: "Standard Tools", subcategory: "Signing" },
"single-large-page": { icon: <span className="material-symbols-rounded">looks_one</span>, name: "home.PdfToSinglePage.title", component: null, view: "format", description: "home.PdfToSinglePage.desc", category: "Standard Tools", subcategory: "Page Formatting" },
"split": { icon: <span className="material-symbols-rounded">content_cut</span>, name: "home.split.title", component: null, view: "format", description: "home.split.desc", category: "Standard Tools", subcategory: "Page Formatting" },
"split-by-chapters": { icon: <span className="material-symbols-rounded">collections_bookmark</span>, name: "home.splitPdfByChapters.title", component: null, view: "format", description: "home.splitPdfByChapters.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
"split-by-sections": { icon: <span className="material-symbols-rounded">grid_on</span>, name: "home.split-by-sections.title", component: null, view: "format", description: "home.split-by-sections.desc", category: "Advanced Tools", subcategory: "Advanced Formatting" },
"splitPdf": { icon: <span className="material-symbols-rounded">content_cut</span>, name: "home.split.title", component: SplitPdfPanel, view: "split", description: "home.split.desc", category: "Standard Tools", subcategory: "Page Formatting" },
"unlock-pdf-forms": { icon: <span className="material-symbols-rounded">preview_off</span>, name: "home.unlockPDFForms.title", component: null, view: "security", description: "home.unlockPDFForms.desc", category: "Standard Tools", subcategory: "Document Security" },
"validate-pdf-signature": { icon: <span className="material-symbols-rounded">verified</span>, name: "home.validateSignature.title", component: null, view: "security", description: "home.validateSignature.desc", category: "Standard Tools", subcategory: "Verification" },
"view-pdf": { icon: <span className="material-symbols-rounded">article</span>, name: "home.viewPdf.title", component: null, view: "view", description: "home.viewPdf.desc", category: "Recommended Tools", subcategory: null
}
};
export const toolEndpoints: Record<string, string[]> = {
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
};

View File

@ -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<string, ToolDefinition> = {
split: {
id: "split",
icon: <ContentCutIcon />,
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: <ZoomInMapIcon />,
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<string[]>([]);
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]) {

View File

@ -104,10 +104,10 @@ function HomePageContent() {
{/* Left: Tool Picker or Selected Tool Panel */}
<div
className={`h-screen flex flex-col overflow-hidden bg-[var(--bg-toolbar)] border-r border-[var(--border-subtle)] transition-all duration-300 ease-out ${isRainbowMode ? rainbowStyles.rainbowPaper : ''}`}
className={`h-screen flex flex-col overflow-hidden border-r border-[var(--border-subtle)] transition-all duration-300 ease-out ${isRainbowMode ? rainbowStyles.rainbowPaper : ''}`}
style={{
width: sidebarsVisible && !readerMode ? '14vw' : '0',
padding: sidebarsVisible && !readerMode ? '0.5rem' : '0'
width: sidebarsVisible && !readerMode ? '280px' : '0',
backgroundColor: 'var(--bg-toolbar)'
}}
>
<div
@ -132,7 +132,7 @@ function HomePageContent() {
// Selected Tool Content View
<div className="flex-1 flex flex-col">
{/* Back button */}
<div className="mb-4">
<div className="mb-4" style={{ padding: '0 1rem' }}>
<Button
variant="subtle"
size="sm"
@ -144,12 +144,12 @@ function HomePageContent() {
</div>
{/* Tool title */}
<div className="mb-4" style={{ marginLeft: '0.5rem' }}>
<div className="mb-4" style={{ marginLeft: '1rem' }}>
<h2 className="text-lg font-semibold">{selectedTool?.name}</h2>
</div>
{/* Tool content */}
<div className="flex-1 min-h-0">
<div className="flex-1 min-h-0" style={{ padding: '0 1rem' }}>
<ToolRenderer
selectedToolKey={selectedToolKey}
onPreviewFile={setPreviewFile}

View File

@ -73,12 +73,12 @@
--bg-raised: #f9fafb;
--bg-muted: #f3f4f6;
--bg-background: #f9fafb;
--bg-toolbar: #ffffff;
--bg-toolbar: #FFFFFF;
--text-primary: #111827;
--text-secondary: #4b5563;
--text-muted: #6b7280;
--border-subtle: #e5e7eb;
--border-default: #d1d5db;
--border-default: #E2E8F0;
--border-strong: #9ca3af;
--hover-bg: #f9fafb;
--active-bg: #f3f4f6;
@ -143,12 +143,12 @@
--bg-raised: #1F2329;
--bg-muted: #1F2329;
--bg-background: #2A2F36;
--bg-toolbar: #272A2E;
--bg-toolbar: #1F2329;
--text-primary: #f9fafb;
--text-secondary: #d1d5db;
--text-muted: #9ca3af;
--border-subtle: #2A2F36;
--border-default: #374151;
--border-default: #3A4047;
--border-strong: #4b5563;
--hover-bg: #374151;
--active-bg: #4b5563;