Stirling-PDF/ADDING_TOOLS.md
Ludy 80cba55459
refactor: remove legacy Thymeleaf web UI controllers and templates (#5406)
# Description of Changes

This pull request completes the removal of Thymeleaf template engine
support and documentation from the Stirling-PDF codebase and developer
guides, reflecting the project's full migration to a React-based
frontend. It removes all references to Thymeleaf in code, configuration,
and documentation, and updates guides to focus exclusively on the React
SPA architecture. This streamlines the codebase and clarifies the
development workflow for contributors.

**Codebase cleanup: Thymeleaf removal**
- Removed all commented-out Thymeleaf dependencies and related
configuration beans from `build.gradle` and `AppConfig.java`
(`app/common/build.gradle`,
`app/common/src/main/java/stirling/software/common/configuration/AppConfig.java`)
[[1]](diffhunk://#diff-2a1a21726f33b05d16451237c68d6df91a5f4a58419d839715f3f1538a9a14aeL32)
[[2]](diffhunk://#diff-70792df9a0ab5675ded888c9eb8e2815c780d7b39f4bda8cf2da51d1b336899aL67-L76).
- Fully commented out (as a precursor to future deletion) the
`FileFallbackTemplateResolver` and `InputStreamTemplateResource`
classes, which were only used for Thymeleaf template resolution
(`app/common/src/main/java/stirling/software/common/configuration/FileFallbackTemplateResolver.java`,
`app/common/src/main/java/stirling/software/common/model/InputStreamTemplateResource.java`)
[[1]](diffhunk://#diff-e2bc7614074316b972355cb7dda47b98f75b00eb6b2ca4f143a680ab2803dcd8L1-L49)
[[2]](diffhunk://#diff-ab10ee12d8de8fb77759e931170373d388bde04bad6d0e42a0ab674355ef7ef3L1-L40).

**Documentation updates: React-only focus**
- Removed all instructions and references to migrating or developing
with Thymeleaf templates in `DeveloperGuide.md` and `ADDING_TOOLS.md`,
including detailed Thymeleaf usage examples, migration steps, and
translation key usage in templates
[[1]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL5-R5)
[[2]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL41-L43)
[[3]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL103-L105)
[[4]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL157)
[[5]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL312)
[[6]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL404-R396)
[[7]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL451-L505)
[[8]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL530-R467)
[[9]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL585-L669)
[[10]](diffhunk://#diff-ccd22fcbec8148152c8c77b85fbfe2633a6707b5ad50c2ef88fa87e2c47ea88fL699-L709)
[[11]](diffhunk://#diff-e2f8148ea620602b7761e8ee24afeac1c577476630528e210fe0b22e950016ddL3-R3)
[[12]](diffhunk://#diff-e2f8148ea620602b7761e8ee24afeac1c577476630528e210fe0b22e950016ddL267-R267).
- Updated architecture descriptions in `CLAUDE.md` to reflect that the
frontend is now exclusively a React SPA and that Thymeleaf templates
have been fully replaced
[[1]](diffhunk://#diff-6ebdb617a8104a7756d0cf36578ab01103dc9f07e4dc6feb751296b9c402faf7L131-R132)
[[2]](diffhunk://#diff-6ebdb617a8104a7756d0cf36578ab01103dc9f07e4dc6feb751296b9c402faf7L143-L144).

**Labeler configuration update**
- Removed labeler rules for files related to the old Thymeleaf-based web
controllers and UI directories, as these are now obsolete
(`.github/labeler-config-srvaroa.yml`).

These changes ensure the codebase and documentation are consistent with
the new React-only frontend approach, reducing maintenance overhead and
potential confusion for contributors.

---

## 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)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### 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.
2026-01-21 21:58:29 +00:00

9.6 KiB

Adding New React Tools to Stirling PDF

This guide covers how to add new PDF tools to the React frontend.

Overview

When adding tools, follow this systematic approach using the established patterns and architecture.

1. Create Tool Structure

Create these files in the correct directories:

frontend/src/hooks/tools/[toolName]/
  ├── use[ToolName]Parameters.ts     # Parameter definitions and validation
  └── use[ToolName]Operation.ts      # Tool operation logic using useToolOperation

frontend/src/components/tools/[toolName]/
  └── [ToolName]Settings.tsx         # Settings UI component (if needed)

frontend/src/tools/
  └── [ToolName].tsx                 # Main tool component

2. Implementation Pattern

Use useBaseTool for simplified hook management. This is the recommended approach for all new tools:

Parameters Hook (use[ToolName]Parameters.ts):

import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';

export interface [ToolName]Parameters extends BaseParameters {
  // Define your tool-specific parameters here
  someOption: boolean;
}

export const defaultParameters: [ToolName]Parameters = {
  someOption: false,
};

export const use[ToolName]Parameters = (): BaseParametersHook<[ToolName]Parameters> => {
  return useBaseParameters({
    defaultParameters,
    endpointName: 'your-endpoint-name',
    validateFn: (params) => true, // Add validation logic
  });
};

Operation Hook (use[ToolName]Operation.ts):

import { useTranslation } from 'react-i18next';
import { ToolType, useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';

export const build[ToolName]FormData = (parameters: [ToolName]Parameters, file: File): FormData => {
  const formData = new FormData();
  formData.append('fileInput', file);
  // Add parameters to formData
  return formData;
};

export const [toolName]OperationConfig = {
  toolType: ToolType.singleFile, // or ToolType.multiFile (buildFormData's file parameter will need to be updated)
  buildFormData: build[ToolName]FormData,
  operationType: '[toolName]',
  endpoint: '/api/v1/category/endpoint-name',
  filePrefix: 'processed_', // Will be overridden with translation
  defaultParameters,
} as const;

export const use[ToolName]Operation = () => {
  const { t } = useTranslation();
  return useToolOperation({
    ...[toolName]OperationConfig,
    filePrefix: t('[toolName].filenamePrefix', 'processed') + '_',
    getErrorMessage: createStandardErrorHandler(t('[toolName].error.failed', 'Operation failed'))
  });
};

Main Component ([ToolName].tsx):

import { useTranslation } from "react-i18next";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { use[ToolName]Parameters } from "../hooks/tools/[toolName]/use[ToolName]Parameters";
import { use[ToolName]Operation } from "../hooks/tools/[toolName]/use[ToolName]Operation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";

const [ToolName] = (props: BaseToolProps) => {
  const { t } = useTranslation();
  const base = useBaseTool('[toolName]', use[ToolName]Parameters, use[ToolName]Operation, props);

  return createToolFlow({
    files: {
      selectedFiles: base.selectedFiles,
      isCollapsed: base.hasResults,
      placeholder: t("[toolName].files.placeholder", "Select files to get started"),
    },
    steps: [
      // Add settings steps if needed
    ],
    executeButton: {
      text: t("[toolName].submit", "Process"),
      isVisible: !base.hasResults,
      loadingText: t("loading"),
      onClick: base.handleExecute,
      disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
    },
    review: {
      isVisible: base.hasResults,
      operation: base.operation,
      title: t("[toolName].results.title", "Results"),
      onFileClick: base.handleThumbnailClick,
      onUndo: base.handleUndo,
    },
  });
};

[ToolName].tool = () => use[ToolName]Operation;
export default [ToolName] as ToolComponent;

Note: Some existing tools (like AddPassword, Compress) use a legacy pattern with manual hook management. Always use the Modern Pattern above for new tools - it's cleaner, more maintainable, and includes automation support.

3. Register Tool in System

Update these files to register your new tool:

Tool Registry (frontend/src/data/useTranslatedToolRegistry.tsx):

  1. Add imports at the top:
import [ToolName] from "../tools/[ToolName]";
import { [toolName]OperationConfig } from "../hooks/tools/[toolName]/use[ToolName]Operation";
import [ToolName]Settings from "../components/tools/[toolName]/[ToolName]Settings";
  1. Add tool entry in the allTools object:
[toolName]: {
  icon: <LocalIcon icon="your-icon-name" width="1.5rem" height="1.5rem" />,
  name: t("home.[toolName].title", "Tool Name"),
  component: [ToolName],
  description: t("home.[toolName].desc", "Tool description"),
  categoryId: ToolCategoryId.STANDARD_TOOLS, // or appropriate category
  subcategoryId: SubcategoryId.APPROPRIATE_SUBCATEGORY,
  maxFiles: -1, // or specific number
  endpoints: ["endpoint-name"],
  operationConfig: [toolName]OperationConfig,
  settingsComponent: [ToolName]Settings, // if settings exist
},

Create user-friendly tooltips to help non-technical users understand your tool. Use simple, clear language - avoid technical jargon:

Tooltip Hook (frontend/src/components/tooltips/use[ToolName]Tips.ts):

import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips';

export const use[ToolName]Tips = (): TooltipContent => {
  const { t } = useTranslation();

  return {
    header: {
      title: t("[toolName].tooltip.header.title", "Tool Overview")
    },
    tips: [
      {
        title: t("[toolName].tooltip.description.title", "What does this tool do?"),
        description: t("[toolName].tooltip.description.text", "Simple explanation in everyday language that non-technical users can understand."),
        bullets: [
          t("[toolName].tooltip.description.bullet1", "Easy-to-understand benefit 1"),
          t("[toolName].tooltip.description.bullet2", "Easy-to-understand benefit 2")
        ]
      }
      // Add more tip sections as needed
    ]
  };
};

Add tooltip to your main component:

import { use[ToolName]Tips } from "../components/tooltips/use[ToolName]Tips";

const [ToolName] = (props: BaseToolProps) => {
  const tips = use[ToolName]Tips();

  // In your steps array:
  steps: [
    {
      title: t("[toolName].steps.settings", "Settings"),
      tooltip: tips, // Add this line
      content: <[ToolName]Settings ... />
    }
  ]

5. Add Translations

Update translation files. Important: Only update en-GB files - other languages are handled separately.

File to update: frontend/public/locales/en-GB/translation.toml

Required Translation Keys:

{
  "home": {
    "[toolName]": {
      "title": "Tool Name",
      "desc": "Tool description"
    }
  },
  "[toolName]": {
    "title": "Tool Name",
    "submit": "Process",
    "filenamePrefix": "processed",
    "files": {
      "placeholder": "Select files to get started"
    },
    "steps": {
      "settings": "Settings"
    },
    "options": {
      "title": "Tool Options",
      "someOption": "Option Label",
      "someOption.desc": "Option description",
      "note": "General information about the tool."
    },
    "results": {
      "title": "Results"
    },
    "error": {
      "failed": "Operation failed"
    },
    "tooltip": {
      "header": {
        "title": "Tool Overview"
      },
      "description": {
        "title": "What does this tool do?",
        "text": "Simple explanation in everyday language",
        "bullet1": "Easy-to-understand benefit 1",
        "bullet2": "Easy-to-understand benefit 2"
      }
    }
  }
}

Translation Notes:

  • Only update en-GB/translation.toml - other locale files are managed separately
  • Use descriptive keys that match your component's t() calls
  • Include tooltip translations if you created tooltip hooks
  • Add options.* keys if your tool has settings with descriptions

Tooltip Writing Guidelines:

  • Use simple, everyday language - avoid technical terms like "converts interactive elements"
  • Focus on benefits - explain what the user gains, not how it works internally
  • Use concrete examples - "text boxes become regular text" vs "form fields are flattened"
  • Answer user questions - "What does this do?", "When should I use this?", "What's this option for?"
  • Keep descriptions concise - 1-2 sentences maximum per section
  • Use bullet points for multiple benefits or features

6. Testing Your Tool

  • Verify tool appears in UI with correct icon and description
  • Test with various file sizes and types
  • Confirm translations work
  • Check error handling
  • Test undo functionality
  • Verify results display correctly

Tool Development Patterns

Three Tool Patterns:

Pattern 1: Single-File Tools (Individual processing)

  • Backend processes one file per API call
  • Set multiFileEndpoint: false
  • Examples: Compress, Rotate

Pattern 2: Multi-File Tools (Batch processing)

  • Backend accepts MultipartFile[] arrays in single API call
  • Set multiFileEndpoint: true
  • Examples: Split, Merge, Overlay

Pattern 3: Complex Tools (Custom processing)

  • Tools with complex routing logic or non-standard processing
  • Provide customProcessor for full control
  • Examples: Convert, OCR