From a8c28dc368da06f6f1d71c81236bd1de2fe44296 Mon Sep 17 00:00:00 2001
From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.>
Date: Tue, 2 Sep 2025 22:42:38 +0100
Subject: [PATCH 1/2] init
---
ADDING_TOOLS.md | 308 ++++++++++++++++++
CLAUDE.md | 1 +
.../public/locales/en-GB/translation.json | 43 ++-
.../tools/flatten/FlattenSettings.tsx | 35 ++
.../src/components/tooltips/useFlattenTips.ts | 34 ++
.../src/data/useTranslatedToolRegistry.tsx | 9 +-
.../tools/flatten/useFlattenOperation.ts | 33 ++
.../tools/flatten/useFlattenParameters.ts | 20 ++
frontend/src/tools/Flatten.tsx | 63 ++++
9 files changed, 544 insertions(+), 2 deletions(-)
create mode 100644 ADDING_TOOLS.md
create mode 100644 frontend/src/components/tools/flatten/FlattenSettings.tsx
create mode 100644 frontend/src/components/tooltips/useFlattenTips.ts
create mode 100644 frontend/src/hooks/tools/flatten/useFlattenOperation.ts
create mode 100644 frontend/src/hooks/tools/flatten/useFlattenParameters.ts
create mode 100644 frontend/src/tools/Flatten.tsx
diff --git a/ADDING_TOOLS.md b/ADDING_TOOLS.md
new file mode 100644
index 000000000..8b2aa1d99
--- /dev/null
+++ b/ADDING_TOOLS.md
@@ -0,0 +1,308 @@
+# Adding New React Tools to Stirling PDF
+
+This guide covers how to add new PDF tools to the React frontend, either by migrating existing Thymeleaf templates or creating entirely new tools.
+
+## 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`):
+```typescript
+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`):
+```typescript
+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: 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`):
+```typescript
+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:
+```typescript
+import [ToolName] from "../tools/[ToolName]";
+import { [toolName]OperationConfig } from "../hooks/tools/[toolName]/use[ToolName]Operation";
+import [ToolName]Settings from "../components/tools/[toolName]/[ToolName]Settings";
+```
+
+2. Add tool entry in the `allTools` object:
+```typescript
+[toolName]: {
+ icon: