V2 Replace Google Fonts icons with locally bundled Iconify icons (#4283)

# Description of Changes

This PR refactors the frontend icon system to remove reliance on
@mui/icons-material and the Google Material Symbols webfont.

🔄 Changes

Introduced a new LocalIcon component powered by Iconify.
Added scripts/generate-icons.js to:
Scan the codebase for used icons.
Extract only required Material Symbols from
@iconify-json/material-symbols.
Generate a minimized JSON bundle and TypeScript types.
Updated .gitignore to exclude generated icon files.
Replaced all <span className="material-symbols-rounded"> and MUI icon
imports with <LocalIcon> usage.
Removed material-symbols CSS import and related font dependency.
Updated tsconfig.json to support JSON imports.
Added prebuild/predev hooks to auto-generate the icons.

 Benefits

No more 5MB+ Google webfont download → reduces initial page load size.
Smaller install footprint → no giant @mui/icons-material dependency.
Only ships the icons we actually use, cutting bundle size further.
Type-safe icons via auto-generated MaterialSymbolIcon union type.

Note most MUI not included in this update since they are low priority
due to small SVG sizing (don't grab whole bundle)


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: a <a>
This commit is contained in:
Anthony Stirling
2025-08-25 16:07:55 +01:00
committed by GitHub
parent 55ebf9ebd0
commit 73deece29e
19 changed files with 535 additions and 155 deletions

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Container, Text, Button, Checkbox, Group, useMantineColorScheme } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import AddIcon from '@mui/icons-material/Add';
import LocalIcon from './LocalIcon';
import { useTranslation } from 'react-i18next';
import { useFileHandler } from '../../hooks/useFileHandler';
import { useFilesModalContext } from '../../contexts/FilesModalContext';
@@ -138,7 +138,7 @@ const LandingPage = () => {
onClick={handleOpenFilesModal}
onMouseEnter={() => setIsUploadHover(false)}
>
<AddIcon className="text-[var(--accent-interactive)]" />
<LocalIcon icon="add" width="1.5rem" height="1.5rem" className="text-[var(--accent-interactive)]" />
{!isUploadHover && (
<span>
{t('landing.addFiles', 'Add Files')}
@@ -165,7 +165,7 @@ const LandingPage = () => {
onClick={handleNativeUploadClick}
onMouseEnter={() => setIsUploadHover(true)}
>
<span className="material-symbols-rounded" style={{ fontSize: '1.25rem', color: 'var(--accent-interactive)' }}>upload</span>
<LocalIcon icon="upload" width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} />
{isUploadHover && (
<span style={{ marginLeft: '.5rem' }}>
{t('landing.uploadFromComputer', 'Upload from computer')}

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { Menu, Button, ScrollArea, ActionIcon } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { supportedLanguages } from '../../i18n';
import LanguageIcon from '@mui/icons-material/Language';
import LocalIcon from './LocalIcon';
import styles from './LanguageSelector.module.css';
interface LanguageSelectorProps {
@@ -105,13 +105,13 @@ const LanguageSelector = ({ position = 'bottom-start', offset = 8, compact = fal
}
}}
>
<span className="material-symbols-rounded">language</span>
<LocalIcon icon="language" width="1.5rem" height="1.5rem" />
</ActionIcon>
) : (
<Button
variant="subtle"
size="sm"
leftSection={<LanguageIcon style={{ fontSize: 18 }} />}
leftSection={<LocalIcon icon="language" width="1.5rem" height="1.5rem" />}
styles={{
root: {
border: 'none',

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { addCollection, Icon } from '@iconify/react';
import iconSet from '../../assets/material-symbols-icons.json';
// Load icons synchronously at import time - guaranteed to be ready on first render
let iconsLoaded = false;
let localIconCount = 0;
try {
if (iconSet) {
addCollection(iconSet);
iconsLoaded = true;
localIconCount = Object.keys(iconSet.icons || {}).length;
console.info(`✅ Local icons loaded: ${localIconCount} icons (${Math.round(JSON.stringify(iconSet).length / 1024)}KB)`);
}
} catch (error) {
console.info(' Local icons not available - using CDN fallback');
}
interface LocalIconProps {
icon: string;
width?: string | number;
height?: string | number;
style?: React.CSSProperties;
className?: string;
}
/**
* LocalIcon component that uses our locally bundled Material Symbols icons
* instead of loading from CDN
*/
export const LocalIcon: React.FC<LocalIconProps> = ({ icon, ...props }) => {
// Convert our icon naming convention to the local collection format
const iconName = icon.startsWith('material-symbols:')
? icon
: `material-symbols:${icon}`;
// Development logging (only in dev mode)
if (process.env.NODE_ENV === 'development') {
const logKey = `icon-${iconName}`;
if (!sessionStorage.getItem(logKey)) {
const source = iconsLoaded ? 'local' : 'CDN';
console.debug(`🎯 Icon: ${iconName} (${source})`);
sessionStorage.setItem(logKey, 'logged');
}
}
// Always render the icon - Iconify will use local if available, CDN if not
return <Icon icon={iconName} {...props} />;
};
export default LocalIcon;

View File

@@ -1,9 +1,7 @@
import React, { useState, useRef, forwardRef, useEffect } from "react";
import { ActionIcon, Stack, Divider } from "@mantine/core";
import { useTranslation } from 'react-i18next';
import MenuBookIcon from "@mui/icons-material/MenuBookRounded";
import SettingsIcon from "@mui/icons-material/SettingsRounded";
import FolderIcon from "@mui/icons-material/FolderRounded";
import LocalIcon from './LocalIcon';
import { useRainbowThemeContext } from "./RainbowThemeProvider";
import AppConfigModal from './AppConfigModal';
import { useIsOverflowing } from '../../hooks/useIsOverflowing';
@@ -44,7 +42,7 @@ const QuickAccessBar = forwardRef<HTMLDivElement>(({
{
id: 'read',
name: t("quickAccess.read", "Read"),
icon: <MenuBookIcon sx={{ fontSize: "1.5rem" }} />,
icon: <LocalIcon icon="menu-book-rounded" width="1.5rem" height="1.5rem" />,
size: 'lg',
isRound: false,
type: 'navigation',
@@ -54,29 +52,23 @@ const QuickAccessBar = forwardRef<HTMLDivElement>(({
handleReaderToggle();
}
},
// TODO: Add sign tool
// {
// id: 'sign',
// name: t("quickAccess.sign", "Sign"),
// icon:
// <span className="material-symbols-rounded font-size-20">
// signature
// </span>,
// size: 'lg',
// isRound: false,
// type: 'navigation',
// onClick: () => {
// setActiveButton('sign');
// handleToolSelect('sign');
// }
// },
// TODO: Add sign
//{
// id: 'sign',
// name: t("quickAccess.sign", "Sign"),
// icon: <LocalIcon icon="signature-rounded" width="1.25rem" height="1.25rem" />,
// size: 'lg',
// isRound: false,
// type: 'navigation',
// onClick: () => {
// setActiveButton('sign');
// handleToolSelect('sign');
// }
//},
{
id: 'automate',
name: t("quickAccess.automate", "Automate"),
icon:
<span className="material-symbols-rounded font-size-20">
automation
</span>,
icon: <LocalIcon icon="automation-outline" width="1.25rem" height="1.25rem" />,
size: 'lg',
isRound: false,
type: 'navigation',
@@ -88,28 +80,26 @@ const QuickAccessBar = forwardRef<HTMLDivElement>(({
{
id: 'files',
name: t("quickAccess.files", "Files"),
icon: <FolderIcon sx={{ fontSize: "1.25rem" }} />,
icon: <LocalIcon icon="folder-rounded" width="1.25rem" height="1.25rem" />,
isRound: true,
size: 'lg',
type: 'modal',
onClick: handleFilesButtonClick
},
{
id: 'activity',
name: t("quickAccess.activity", "Activity"),
icon:
<span className="material-symbols-rounded font-size-20">
vital_signs
</span>,
isRound: true,
size: 'lg',
type: 'navigation',
onClick: () => setActiveButton('activity')
},
//TODO: Activity
//{
// id: 'activity',
// name: t("quickAccess.activity", "Activity"),
// icon: <LocalIcon icon="vital-signs-rounded" width="1.25rem" height="1.25rem" />,
// isRound: true,
// size: 'lg',
// type: 'navigation',
// onClick: () => setActiveButton('activity')
//},
{
id: 'config',
name: t("quickAccess.config", "Config"),
icon: <SettingsIcon sx={{ fontSize: "1rem" }} />,
icon: <LocalIcon icon="settings-rounded" width="1.25rem" height="1.25rem" />,
size: 'lg',
type: 'modal',
onClick: () => {
@@ -180,8 +170,8 @@ const QuickAccessBar = forwardRef<HTMLDivElement>(({
</div>
{/* Add divider after Automate button (index 2) */}
{index === 2 && (
{/* Add divider after Automate button (index 1) and Files button (index 2) */}
{index === 1 && (
<Divider
size="xs"
className="content-divider"

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useState, useEffect, useMemo } from 'react';
import { ActionIcon, Divider, Popover } from '@mantine/core';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import LocalIcon from './LocalIcon';
import './rightRail/RightRail.css';
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
import { useRightRail } from '../../contexts/RightRailContext';
@@ -234,9 +234,7 @@ export default function RightRail() {
onClick={handleSelectAll}
disabled={currentView === 'viewer' || totalItems === 0 || selectedCount === totalItems}
>
<span className="material-symbols-rounded">
select_all
</span>
<LocalIcon icon="select-all" width="1.5rem" height="1.5rem" />
</ActionIcon>
</div>
</Tooltip>
@@ -251,9 +249,7 @@ export default function RightRail() {
onClick={handleDeselectAll}
disabled={currentView === 'viewer' || selectedCount === 0}
>
<span className="material-symbols-rounded">
crop_square
</span>
<LocalIcon icon="crop-square-outline" width="1.5rem" height="1.5rem" />
</ActionIcon>
</div>
</Tooltip>
@@ -273,9 +269,7 @@ export default function RightRail() {
disabled={!pageControlsVisible || totalItems === 0}
aria-label={typeof t === 'function' ? t('rightRail.selectByNumber', 'Select by Page Numbers') : 'Select by Page Numbers'}
>
<span className="material-symbols-rounded">
pin_end
</span>
<LocalIcon icon="pin-end" width="1.5rem" height="1.5rem" />
</ActionIcon>
</div>
</Popover.Target>
@@ -309,7 +303,7 @@ export default function RightRail() {
disabled={!pageControlsVisible || (Array.isArray(selectedPageNumbers) ? selectedPageNumbers.length === 0 : true)}
aria-label={typeof t === 'function' ? t('rightRail.deleteSelected', 'Delete Selected Pages') : 'Delete Selected Pages'}
>
<span className="material-symbols-rounded">delete</span>
<LocalIcon icon="delete-outline-rounded" width="1.5rem" height="1.5rem" />
</ActionIcon>
</div>
</div>
@@ -331,7 +325,7 @@ export default function RightRail() {
(currentView === 'pageEditor' && (activeFiles.length === 0 || !pageEditorFunctions?.closePdf))
}
>
<CloseRoundedIcon />
<LocalIcon icon="close-rounded" width="1.5rem" height="1.5rem" />
</ActionIcon>
</div>
</Tooltip>
@@ -349,7 +343,7 @@ export default function RightRail() {
className="right-rail-icon"
onClick={toggleTheme}
>
<span className="material-symbols-rounded">contrast</span>
<LocalIcon icon="contrast" width="1.5rem" height="1.5rem" />
</ActionIcon>
</Tooltip>
@@ -368,9 +362,7 @@ export default function RightRail() {
onClick={handleExportAll}
disabled={currentView === 'viewer' || totalItems === 0}
>
<span className="material-symbols-rounded">
download
</span>
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />
</ActionIcon>
</div>
</Tooltip>

View File

@@ -1,5 +1,6 @@
import React, { forwardRef } from 'react';
import { useMantineColorScheme } from '@mantine/core';
import LocalIcon from './LocalIcon';
import styles from './textInput/TextInput.module.css';
/**
@@ -96,7 +97,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(({
style={{ color: colorScheme === 'dark' ? '#FFFFFF' : '#6B7382' }}
aria-label="Clear input"
>
<span className="material-symbols-rounded">close</span>
<LocalIcon icon="close-rounded" width="1.25rem" height="1.25rem" />
</button>
)}
</div>

View File

@@ -1,5 +1,6 @@
import React, { useState, useRef, useEffect } from 'react';
import { createPortal } from 'react-dom';
import LocalIcon from './LocalIcon';
import { isClickOutside, addEventListenerWithCleanup } from '../../utils/genericUtils';
import { useTooltipPosition } from '../../hooks/useTooltipPosition';
import { TooltipTip } from '../../types/tips';
@@ -171,9 +172,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
}}
title="Close tooltip"
>
<span className="material-symbols-rounded">
close
</span>
<LocalIcon icon="close-rounded" width="1.25rem" height="1.25rem" />
</button>
)}
{arrow && getArrowClass() && (

View File

@@ -1,7 +1,6 @@
import React, { createContext, useContext, useMemo, useRef } from 'react';
import { Text, Stack, Box, Flex, Divider } from '@mantine/core';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import LocalIcon from '../../shared/LocalIcon';
import { Tooltip } from '../../shared/Tooltip';
import { TooltipTip } from '../../../types/tips';
import { createFilesToolStep, FilesToolStepProps } from './FilesToolStep';
@@ -54,9 +53,7 @@ const renderTooltipTitle = (
<Text fw={500} size="lg">
{title}
</Text>
<span className="material-symbols-rounded" style={{ fontSize: '1.2rem', color: 'var(--icon-files-color)' }}>
gpp_maybe
</span>
<LocalIcon icon="gpp-maybe-outline-rounded" width="1.25rem" height="1.25rem" style={{ color: 'var(--icon-files-color)' }} />
</Flex>
</Tooltip>
);
@@ -125,14 +122,12 @@ const ToolStep = ({
</Flex>
{isCollapsed ? (
<ChevronRightIcon style={{
fontSize: '1.2rem',
<LocalIcon icon="chevron-right-rounded" width="1.2rem" height="1.2rem" style={{
color: 'var(--mantine-color-dimmed)',
opacity: onCollapsedClick ? 1 : 0.5
}} />
) : (
<ExpandMoreIcon style={{
fontSize: '1.2rem',
<LocalIcon icon="expand-more-rounded" width="1.2rem" height="1.2rem" style={{
color: 'var(--mantine-color-dimmed)',
opacity: onCollapsedClick ? 1 : 0.5
}} />

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { Flex, Text, Divider } from '@mantine/core';
import LocalIcon from '../../shared/LocalIcon';
import { Tooltip } from '../../shared/Tooltip';
export interface ToolWorkflowTitleProps {
@@ -29,9 +30,7 @@ export function ToolWorkflowTitle({ title, tooltip }: ToolWorkflowTitleProps) {
<Text fw={500} size="xl" p="md">
{title}
</Text>
<span className="material-symbols-rounded" style={{ fontSize: '1.2rem', color: 'var(--icon-files-color)' }}>
gpp_maybe
</span>
<LocalIcon icon="gpp-maybe-outline-rounded" width="1.25rem" height="1.25rem" style={{ color: 'var(--icon-files-color)' }} />
</Flex>
</Tooltip>
</Flex>

View File

@@ -1,6 +1,7 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import { Stack, Button, Text } from "@mantine/core";
import { useTranslation } from "react-i18next";
import LocalIcon from '../../shared/LocalIcon';
import { ToolRegistryEntry } from "../../../data/toolsTaxonomy";
import { TextInput } from "../../shared/TextInput";
import './ToolPicker.css';
@@ -74,7 +75,7 @@ const ToolSearch = ({
value={value}
onChange={handleSearchChange}
placeholder={placeholder || t("toolPicker.searchPlaceholder", "Search tools...")}
icon={hideIcon ? undefined : <span className="material-symbols-rounded">search</span>}
icon={hideIcon ? undefined : <LocalIcon icon="search-rounded" width="1.5rem" height="1.5rem" />}
autoComplete="off"
/>

View File

@@ -1,4 +1,5 @@
import React, { useMemo } from 'react';
import LocalIcon from '../components/shared/LocalIcon';
import { useTranslation } from 'react-i18next';
import SplitPdfPanel from "../tools/Split";
import CompressPdfPanel from "../tools/Compress";
@@ -50,7 +51,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Signing
"certSign": {
icon: <span className="material-symbols-rounded">workspace_premium</span>,
icon: <LocalIcon icon="workspace-premium-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.certSign.title", "Sign with Certificate"),
component: null,
view: "sign",
@@ -59,7 +60,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.SIGNING
},
"sign": {
icon: <span className="material-symbols-rounded">signature</span>,
icon: <LocalIcon icon="signature-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.sign.title", "Sign"),
component: null,
view: "sign",
@@ -72,7 +73,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Document Security
"addPassword": {
icon: <span className="material-symbols-rounded">password</span>,
icon: <LocalIcon icon="password-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.addPassword.title", "Add Password"),
component: AddPassword,
view: "security",
@@ -85,7 +86,7 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: AddPasswordSettings
},
"watermark": {
icon: <span className="material-symbols-rounded">branding_watermark</span>,
icon: <LocalIcon icon="branding-watermark-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.watermark.title", "Add Watermark"),
component: AddWatermark,
view: "format",
@@ -98,7 +99,7 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: AddWatermarkSingleStepSettings
},
"add-stamp": {
icon: <span className="material-symbols-rounded">approval</span>,
icon: <LocalIcon icon="approval-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.AddStampRequest.title", "Add Stamp to PDF"),
component: null,
view: "format",
@@ -107,7 +108,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.DOCUMENT_SECURITY
},
"sanitize": {
icon: <span className="material-symbols-rounded">cleaning_services</span>,
icon: <LocalIcon icon="cleaning-services-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.sanitize.title", "Sanitize"),
component: Sanitize,
view: "security",
@@ -120,7 +121,7 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: SanitizeSettings
},
"flatten": {
icon: <span className="material-symbols-rounded">layers_clear</span>,
icon: <LocalIcon icon="layers-clear-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.flatten.title", "Flatten"),
component: null,
view: "format",
@@ -129,7 +130,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.DOCUMENT_SECURITY
},
"unlock-pdf-forms": {
icon: <span className="material-symbols-rounded">preview_off</span>,
icon: <LocalIcon icon="preview-off-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.unlockPDFForms.title", "Unlock PDF Forms"),
component: UnlockPdfForms,
view: "security",
@@ -142,7 +143,7 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: UnlockPdfFormsSettings
},
"manage-certificates": {
icon: <span className="material-symbols-rounded">license</span>,
icon: <LocalIcon icon="license-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.manageCertificates.title", "Manage Certificates"),
component: null,
view: "security",
@@ -151,7 +152,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.DOCUMENT_SECURITY
},
"change-permissions": {
icon: <span className="material-symbols-rounded">lock</span>,
icon: <LocalIcon icon="lock-outline" width="1.5rem" height="1.5rem" />,
name: t("home.changePermissions.title", "Change Permissions"),
component: ChangePermissions,
view: "security",
@@ -166,7 +167,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Verification
"get-all-info-on-pdf": {
icon: <span className="material-symbols-rounded">fact_check</span>,
icon: <LocalIcon icon="fact-check-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.getPdfInfo.title", "Get ALL Info on PDF"),
component: null,
view: "extract",
@@ -175,7 +176,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.VERIFICATION
},
"validate-pdf-signature": {
icon: <span className="material-symbols-rounded">verified</span>,
icon: <LocalIcon icon="verified-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.validateSignature.title", "Validate PDF Signature"),
component: null,
view: "security",
@@ -188,7 +189,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Document Review
"read": {
icon: <span className="material-symbols-rounded">article</span>,
icon: <LocalIcon icon="article-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.read.title", "Read"),
component: null,
view: "view",
@@ -197,7 +198,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.DOCUMENT_REVIEW
},
"change-metadata": {
icon: <span className="material-symbols-rounded">assignment</span>,
icon: <LocalIcon icon="assignment-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.changeMetadata.title", "Change Metadata"),
component: null,
view: "format",
@@ -208,7 +209,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Page Formatting
"cropPdf": {
icon: <span className="material-symbols-rounded">crop</span>,
icon: <LocalIcon icon="crop-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.crop.title", "Crop PDF"),
component: null,
view: "format",
@@ -217,7 +218,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.PAGE_FORMATTING
},
"rotate": {
icon: <span className="material-symbols-rounded">rotate_right</span>,
icon: <LocalIcon icon="rotate-right-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.rotate.title", "Rotate"),
component: null,
view: "format",
@@ -226,7 +227,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.PAGE_FORMATTING
},
"splitPdf": {
icon: <span className="material-symbols-rounded">content_cut</span>,
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.split.title", "Split"),
component: SplitPdfPanel,
view: "split",
@@ -237,7 +238,7 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: SplitSettings
},
"reorganize-pages": {
icon: <span className="material-symbols-rounded">move_down</span>,
icon: <LocalIcon icon="move-down-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.reorganizePages.title", "Reorganize Pages"),
component: null,
view: "pageEditor",
@@ -246,7 +247,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.PAGE_FORMATTING
},
"adjust-page-size-scale": {
icon: <span className="material-symbols-rounded">crop_free</span>,
icon: <LocalIcon icon="crop-free-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.scalePages.title", "Adjust page size/scale"),
component: null,
view: "format",
@@ -255,7 +256,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.PAGE_FORMATTING
},
"addPageNumbers": {
icon: <span className="material-symbols-rounded">123</span>,
icon: <LocalIcon icon="123-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.addPageNumbers.title", "Add Page Numbers"),
component: null,
view: "format",
@@ -264,7 +265,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.PAGE_FORMATTING
},
"multi-page-layout": {
icon: <span className="material-symbols-rounded">dashboard</span>,
icon: <LocalIcon icon="dashboard-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.pageLayout.title", "Multi-Page Layout"),
component: null,
view: "format",
@@ -273,7 +274,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.PAGE_FORMATTING
},
"single-large-page": {
icon: <span className="material-symbols-rounded">looks_one</span>,
icon: <LocalIcon icon="looks-one-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.pdfToSinglePage.title", "PDF to Single Large Page"),
component: SingleLargePage,
view: "format",
@@ -285,7 +286,7 @@ export function useFlatToolRegistry(): ToolRegistry {
operationConfig: singleLargePageOperationConfig
},
"add-attachments": {
icon: <span className="material-symbols-rounded">attachment</span>,
icon: <LocalIcon icon="attachment-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.attachments.title", "Add Attachments"),
component: null,
view: "format",
@@ -298,7 +299,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Extraction
"extractPages": {
icon: <span className="material-symbols-rounded">upload</span>,
icon: <LocalIcon icon="upload-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.extractPages.title", "Extract Pages"),
component: null,
view: "extract",
@@ -307,7 +308,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.EXTRACTION
},
"extract-images": {
icon: <span className="material-symbols-rounded">filter</span>,
icon: <LocalIcon icon="filter-alt" width="1.5rem" height="1.5rem" />,
name: t("home.extractImages.title", "Extract Images"),
component: null,
view: "extract",
@@ -320,7 +321,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Removal
"removePages": {
icon: <span className="material-symbols-rounded">delete</span>,
icon: <LocalIcon icon="delete-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removePages.title", "Remove Pages"),
component: null,
view: "remove",
@@ -329,7 +330,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.REMOVAL
},
"remove-blank-pages": {
icon: <span className="material-symbols-rounded">scan_delete</span>,
icon: <LocalIcon icon="scan-delete-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removeBlanks.title", "Remove Blank Pages"),
component: null,
view: "remove",
@@ -338,7 +339,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.REMOVAL
},
"remove-annotations": {
icon: <span className="material-symbols-rounded">thread_unread</span>,
icon: <LocalIcon icon="thread-unread-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removeAnnotations.title", "Remove Annotations"),
component: null,
view: "remove",
@@ -347,7 +348,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.REMOVAL
},
"remove-image": {
icon: <span className="material-symbols-rounded">remove_selection</span>,
icon: <LocalIcon icon="remove-selection-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removeImagePdf.title", "Remove Image"),
component: null,
view: "format",
@@ -356,7 +357,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.REMOVAL
},
"remove-password": {
icon: <span className="material-symbols-rounded">lock_open_right</span>,
icon: <LocalIcon icon="lock-open-right-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removePassword.title", "Remove Password"),
component: RemovePassword,
view: "security",
@@ -369,7 +370,7 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: RemovePasswordSettings
},
"remove-certificate-sign": {
icon: <span className="material-symbols-rounded">remove_moderator</span>,
icon: <LocalIcon icon="remove-moderator-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removeCertSign.title", "Remove Certificate Sign"),
component: RemoveCertificateSign,
view: "security",
@@ -385,7 +386,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Automation
"automate": {
icon: <span className="material-symbols-rounded">automation</span>,
icon: <LocalIcon icon="automation-outline" width="1.5rem" height="1.5rem" />,
name: t("home.automate.title", "Automate"),
component: React.lazy(() => import('../tools/Automate')),
view: "format",
@@ -396,7 +397,7 @@ export function useFlatToolRegistry(): ToolRegistry {
endpoints: ["handleData"]
},
"auto-rename-pdf-file": {
icon: <span className="material-symbols-rounded">match_word</span>,
icon: <LocalIcon icon="match-word-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.auto-rename.title", "Auto Rename PDF File"),
component: null,
view: "format",
@@ -405,7 +406,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.AUTOMATION
},
"auto-split-pages": {
icon: <span className="material-symbols-rounded">split_scene_right</span>,
icon: <LocalIcon icon="split-scene-right-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.autoSplitPDF.title", "Auto Split Pages"),
component: null,
view: "format",
@@ -414,7 +415,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.AUTOMATION
},
"auto-split-by-size-count": {
icon: <span className="material-symbols-rounded">content_cut</span>,
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.autoSizeSplitPDF.title", "Auto Split by Size/Count"),
component: null,
view: "format",
@@ -427,7 +428,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Advanced Formatting
"adjustContrast": {
icon: <span className="material-symbols-rounded">palette</span>,
icon: <LocalIcon icon="palette" width="1.5rem" height="1.5rem" />,
name: t("home.adjustContrast.title", "Adjust Colors/Contrast"),
component: null,
view: "format",
@@ -436,7 +437,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
},
"repair": {
icon: <span className="material-symbols-rounded">build</span>,
icon: <LocalIcon icon="build-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.repair.title", "Repair"),
component: Repair,
view: "format",
@@ -449,7 +450,7 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: RepairSettings
},
"detect-split-scanned-photos": {
icon: <span className="material-symbols-rounded">scanner</span>,
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.ScannerImageSplit.title", "Detect & Split Scanned Photos"),
component: null,
view: "format",
@@ -458,7 +459,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
},
"overlay-pdfs": {
icon: <span className="material-symbols-rounded">layers</span>,
icon: <LocalIcon icon="layers-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.overlay-pdfs.title", "Overlay PDFs"),
component: null,
view: "format",
@@ -467,7 +468,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
},
"replace-and-invert-color": {
icon: <span className="material-symbols-rounded">format_color_fill</span>,
icon: <LocalIcon icon="format-color-fill-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.replaceColorPdf.title", "Replace & Invert Color"),
component: null,
view: "format",
@@ -476,7 +477,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
},
"add-image": {
icon: <span className="material-symbols-rounded">image</span>,
icon: <LocalIcon icon="image-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.addImage.title", "Add Image"),
component: null,
view: "format",
@@ -485,7 +486,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
},
"edit-table-of-contents": {
icon: <span className="material-symbols-rounded">bookmark_add</span>,
icon: <LocalIcon icon="bookmark-add-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.editTableOfContents.title", "Edit Table of Contents"),
component: null,
view: "format",
@@ -494,7 +495,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
},
"scanner-effect": {
icon: <span className="material-symbols-rounded">scanner</span>,
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.fakeScan.title", "Scanner Effect"),
component: null,
view: "format",
@@ -507,7 +508,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Developer Tools
"show-javascript": {
icon: <span className="material-symbols-rounded">javascript</span>,
icon: <LocalIcon icon="javascript-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.showJS.title", "Show JavaScript"),
component: null,
view: "extract",
@@ -516,7 +517,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.DEVELOPER_TOOLS
},
"dev-api": {
icon: <span className="material-symbols-rounded" style={{ color: '#2F7BF6' }}>open_in_new</span>,
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: '#2F7BF6' }} />,
name: t("home.devApi.title", "API"),
component: null,
view: "external",
@@ -526,7 +527,7 @@ export function useFlatToolRegistry(): ToolRegistry {
link: "https://stirlingpdf.io/swagger-ui/5.21.0/index.html"
},
"dev-folder-scanning": {
icon: <span className="material-symbols-rounded" style={{ color: '#2F7BF6' }}>open_in_new</span>,
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: '#2F7BF6' }} />,
name: t("home.devFolderScanning.title", "Automated Folder Scanning"),
component: null,
view: "external",
@@ -536,7 +537,7 @@ export function useFlatToolRegistry(): ToolRegistry {
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Folder%20Scanning/"
},
"dev-sso-guide": {
icon: <span className="material-symbols-rounded" style={{ color: '#2F7BF6' }}>open_in_new</span>,
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: '#2F7BF6' }} />,
name: t("home.devSsoGuide.title", "SSO Guide"),
component: null,
view: "external",
@@ -546,7 +547,7 @@ export function useFlatToolRegistry(): ToolRegistry {
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Single%20Sign-On%20Configuration",
},
"dev-airgapped": {
icon: <span className="material-symbols-rounded" style={{ color: '#2F7BF6' }}>open_in_new</span>,
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: '#2F7BF6' }} />,
name: t("home.devAirgapped.title", "Air-gapped Setup"),
component: null,
view: "external",
@@ -559,7 +560,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Recommended Tools
"compare": {
icon: <span className="material-symbols-rounded">compare</span>,
icon: <LocalIcon icon="compare-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.compare.title", "Compare"),
component: null,
view: "format",
@@ -568,7 +569,7 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.GENERAL
},
"compress": {
icon: <span className="material-symbols-rounded">zoom_in_map</span>,
icon: <LocalIcon icon="zoom-in-map-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.compress.title", "Compress"),
component: CompressPdfPanel,
view: "compress",
@@ -580,7 +581,7 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: CompressSettings
},
"convert": {
icon: <span className="material-symbols-rounded">sync_alt</span>,
icon: <LocalIcon icon="sync-alt-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.convert.title", "Convert"),
component: ConvertPanel,
view: "convert",
@@ -626,7 +627,7 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: ConvertSettings
},
"mergePdfs": {
icon: <span className="material-symbols-rounded">library_add</span>,
icon: <LocalIcon icon="library-add-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.merge.title", "Merge"),
component: null,
view: "merge",
@@ -636,7 +637,7 @@ export function useFlatToolRegistry(): ToolRegistry {
maxFiles: -1
},
"multi-tool": {
icon: <span className="material-symbols-rounded">dashboard_customize</span>,
icon: <LocalIcon icon="dashboard-customize-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.multiTool.title", "Multi-Tool"),
component: null,
view: "pageEditor",
@@ -646,7 +647,7 @@ export function useFlatToolRegistry(): ToolRegistry {
maxFiles: -1
},
"ocr": {
icon: <span className="material-symbols-rounded">quick_reference_all</span>,
icon: <LocalIcon icon="quick-reference-all-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.ocr.title", "OCR"),
component: OCRPanel,
view: "convert",
@@ -658,7 +659,7 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: OCRSettings
},
"redact": {
icon: <span className="material-symbols-rounded">visibility_off</span>,
icon: <LocalIcon icon="visibility-off-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.redact.title", "Redact"),
component: null,
view: "redact",

View File

@@ -4,4 +4,15 @@ declare module "../components/PageEditor";
declare module "../components/Viewer";
declare module "*.js";
declare module '*.module.css';
declare module 'pdfjs-dist';
declare module 'pdfjs-dist';
// Auto-generated icon set JSON import
declare module '../assets/material-symbols-icons.json' {
const value: {
prefix: string;
icons: Record<string, any>;
width?: number;
height?: number;
};
export default value;
}

View File

@@ -1,11 +1,15 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import StarIcon from '@mui/icons-material/Star';
import CompressIcon from '@mui/icons-material/Compress';
import SecurityIcon from '@mui/icons-material/Security';
import TextFieldsIcon from '@mui/icons-material/TextFields';
import React from 'react';
import LocalIcon from '../../../components/shared/LocalIcon';
import { SuggestedAutomation } from '../../../types/automation';
// Create icon components
const CompressIcon = () => React.createElement(LocalIcon, { icon: 'compress', width: '1.5rem', height: '1.5rem' });
const TextFieldsIcon = () => React.createElement(LocalIcon, { icon: 'text-fields', width: '1.5rem', height: '1.5rem' });
const SecurityIcon = () => React.createElement(LocalIcon, { icon: 'security', width: '1.5rem', height: '1.5rem' });
const StarIcon = () => React.createElement(LocalIcon, { icon: 'star', width: '1.5rem', height: '1.5rem' });
export function useSuggestedAutomations(): SuggestedAutomation[] {
const { t } = useTranslation();

View File

@@ -1,9 +1,3 @@
@import 'material-symbols/rounded.css';
.material-symbols-rounded {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',