mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-08 17:51:20 +02:00
init
This commit is contained in:
parent
1a3e8e7ecf
commit
a8c28dc368
308
ADDING_TOOLS.md
Normal file
308
ADDING_TOOLS.md
Normal file
@ -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: <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
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Add Tooltips (Optional but Recommended)
|
||||||
|
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`):
|
||||||
|
```typescript
|
||||||
|
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:**
|
||||||
|
```typescript
|
||||||
|
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.json`
|
||||||
|
|
||||||
|
**Required Translation Keys**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"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.json`** - 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. Migration from Thymeleaf
|
||||||
|
When migrating existing Thymeleaf templates:
|
||||||
|
|
||||||
|
1. **Identify Form Parameters**: Look at the original `<form>` inputs to determine parameter structure
|
||||||
|
2. **Extract Translation Keys**: Find `#{key.name}` references and add them to JSON translations
|
||||||
|
3. **Map API Endpoint**: Note the `th:action` URL for the operation hook
|
||||||
|
4. **Preserve Functionality**: Ensure all original form behavior is replicated
|
||||||
|
|
||||||
|
## 7. 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
|
||||||
|
|
||||||
|
## Benefits of This Architecture:
|
||||||
|
- **No Timeouts**: Operations run until completion (supports 100GB+ files)
|
||||||
|
- **Consistent**: All tools follow same pattern and interface
|
||||||
|
- **Maintainable**: Single responsibility hooks, easy to test and modify
|
||||||
|
- **i18n Ready**: Built-in internationalization support
|
||||||
|
- **Type Safe**: Full TypeScript support with generic interfaces
|
||||||
|
- **Memory Safe**: Automatic resource cleanup and blob URL management
|
@ -208,6 +208,7 @@ return useToolOperation({
|
|||||||
- **Tool Development**: New tools should follow `useToolOperation` hook pattern (see `useCompressOperation.ts`)
|
- **Tool Development**: New tools should follow `useToolOperation` hook pattern (see `useCompressOperation.ts`)
|
||||||
- **Performance Target**: Must handle PDFs up to 100GB+ without browser crashes
|
- **Performance Target**: Must handle PDFs up to 100GB+ without browser crashes
|
||||||
- **Preview System**: Tools can preview results without polluting main file context (see Split tool implementation)
|
- **Preview System**: Tools can preview results without polluting main file context (see Split tool implementation)
|
||||||
|
- **Adding Tools**: See `ADDING_TOOLS.md` for complete guide to creating new PDF tools
|
||||||
|
|
||||||
## Communication Style
|
## Communication Style
|
||||||
- Be direct and to the point
|
- Be direct and to the point
|
||||||
|
@ -1305,7 +1305,48 @@
|
|||||||
"title": "Flatten",
|
"title": "Flatten",
|
||||||
"header": "Flatten PDF",
|
"header": "Flatten PDF",
|
||||||
"flattenOnlyForms": "Flatten only forms",
|
"flattenOnlyForms": "Flatten only forms",
|
||||||
"submit": "Flatten"
|
"submit": "Flatten",
|
||||||
|
"filenamePrefix": "flattened",
|
||||||
|
"files": {
|
||||||
|
"placeholder": "Select a PDF file in the main view to get started"
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"settings": "Settings"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"stepTitle": "Flatten Options",
|
||||||
|
"title": "Flatten Options",
|
||||||
|
"flattenOnlyForms": "Flatten only forms",
|
||||||
|
"flattenOnlyForms.desc": "Only flatten form fields, leaving other interactive elements intact",
|
||||||
|
"note": "Flattening removes interactive elements from the PDF, making them non-editable."
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"title": "Flatten Results"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"failed": "An error occurred while flattening the PDF."
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"header": {
|
||||||
|
"title": "About Flattening PDFs"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"title": "What does flattening do?",
|
||||||
|
"text": "Flattening makes your PDF non-editable by turning fillable forms and buttons into regular text and images. The PDF will look exactly the same, but no one can change or fill in the forms anymore. Perfect for sharing completed forms, creating final documents for records, or ensuring the PDF looks the same everywhere.",
|
||||||
|
"bullet1": "Text boxes become regular text (can't be edited)",
|
||||||
|
"bullet2": "Checkboxes and buttons become pictures",
|
||||||
|
"bullet3": "Great for final versions you don't want changed",
|
||||||
|
"bullet4": "Ensures consistent appearance across all devices"
|
||||||
|
},
|
||||||
|
"formsOnly": {
|
||||||
|
"title": "What does 'Flatten only forms' mean?",
|
||||||
|
"text": "This option only removes the ability to fill in forms, but keeps other features working like clicking links, viewing bookmarks, and reading comments.",
|
||||||
|
"bullet1": "Forms become non-editable",
|
||||||
|
"bullet2": "Links still work when clicked",
|
||||||
|
"bullet3": "Comments and notes remain visible",
|
||||||
|
"bullet4": "Bookmarks still help you navigate"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"repair": {
|
"repair": {
|
||||||
"tags": "fix,restore,correction,recover",
|
"tags": "fix,restore,correction,recover",
|
||||||
|
35
frontend/src/components/tools/flatten/FlattenSettings.tsx
Normal file
35
frontend/src/components/tools/flatten/FlattenSettings.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Stack, Text, Checkbox } from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FlattenParameters } from "../../../hooks/tools/flatten/useFlattenParameters";
|
||||||
|
|
||||||
|
interface FlattenSettingsProps {
|
||||||
|
parameters: FlattenParameters;
|
||||||
|
onParameterChange: (key: keyof FlattenParameters, value: boolean) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FlattenSettings = ({ parameters, onParameterChange, disabled = false }: FlattenSettingsProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Checkbox
|
||||||
|
checked={parameters.flattenOnlyForms}
|
||||||
|
onChange={(event) => onParameterChange('flattenOnlyForms', event.currentTarget.checked)}
|
||||||
|
disabled={disabled}
|
||||||
|
label={
|
||||||
|
<div>
|
||||||
|
<Text size="sm">{t('flatten.options.flattenOnlyForms', 'Flatten only forms')}</Text>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
{t('flatten.options.flattenOnlyForms.desc', 'Only flatten form fields, leaving other interactive elements intact')}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FlattenSettings;
|
34
frontend/src/components/tooltips/useFlattenTips.ts
Normal file
34
frontend/src/components/tooltips/useFlattenTips.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { TooltipContent } from '../../types/tips';
|
||||||
|
|
||||||
|
export const useFlattenTips = (): TooltipContent => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return {
|
||||||
|
header: {
|
||||||
|
title: t("flatten.tooltip.header.title", "About Flattening PDFs")
|
||||||
|
},
|
||||||
|
tips: [
|
||||||
|
{
|
||||||
|
title: t("flatten.tooltip.description.title", "What does flattening do?"),
|
||||||
|
description: t("flatten.tooltip.description.text", "Flattening makes your PDF non-editable by turning fillable forms and buttons into regular text and images. The PDF will look exactly the same, but no one can change or fill in the forms anymore. Perfect for sharing completed forms, creating final documents for records, or ensuring the PDF looks the same everywhere."),
|
||||||
|
bullets: [
|
||||||
|
t("flatten.tooltip.description.bullet1", "Text boxes become regular text (can't be edited)"),
|
||||||
|
t("flatten.tooltip.description.bullet2", "Checkboxes and buttons become pictures"),
|
||||||
|
t("flatten.tooltip.description.bullet3", "Great for final versions you don't want changed"),
|
||||||
|
t("flatten.tooltip.description.bullet4", "Ensures consistent appearance across all devices")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("flatten.tooltip.formsOnly.title", "What does 'Flatten only forms' mean?"),
|
||||||
|
description: t("flatten.tooltip.formsOnly.text", "This option only removes the ability to fill in forms, but keeps other features working like clicking links, viewing bookmarks, and reading comments."),
|
||||||
|
bullets: [
|
||||||
|
t("flatten.tooltip.formsOnly.bullet1", "Forms become non-editable"),
|
||||||
|
t("flatten.tooltip.formsOnly.bullet2", "Links still work when clicked"),
|
||||||
|
t("flatten.tooltip.formsOnly.bullet3", "Comments and notes remain visible"),
|
||||||
|
t("flatten.tooltip.formsOnly.bullet4", "Bookmarks still help you navigate")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
@ -15,6 +15,7 @@ import Repair from "../tools/Repair";
|
|||||||
import SingleLargePage from "../tools/SingleLargePage";
|
import SingleLargePage from "../tools/SingleLargePage";
|
||||||
import UnlockPdfForms from "../tools/UnlockPdfForms";
|
import UnlockPdfForms from "../tools/UnlockPdfForms";
|
||||||
import RemoveCertificateSign from "../tools/RemoveCertificateSign";
|
import RemoveCertificateSign from "../tools/RemoveCertificateSign";
|
||||||
|
import Flatten from "../tools/Flatten";
|
||||||
import { compressOperationConfig } from "../hooks/tools/compress/useCompressOperation";
|
import { compressOperationConfig } from "../hooks/tools/compress/useCompressOperation";
|
||||||
import { splitOperationConfig } from "../hooks/tools/split/useSplitOperation";
|
import { splitOperationConfig } from "../hooks/tools/split/useSplitOperation";
|
||||||
import { addPasswordOperationConfig } from "../hooks/tools/addPassword/useAddPasswordOperation";
|
import { addPasswordOperationConfig } from "../hooks/tools/addPassword/useAddPasswordOperation";
|
||||||
@ -28,6 +29,7 @@ import { ocrOperationConfig } from "../hooks/tools/ocr/useOCROperation";
|
|||||||
import { convertOperationConfig } from "../hooks/tools/convert/useConvertOperation";
|
import { convertOperationConfig } from "../hooks/tools/convert/useConvertOperation";
|
||||||
import { removeCertificateSignOperationConfig } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation";
|
import { removeCertificateSignOperationConfig } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation";
|
||||||
import { changePermissionsOperationConfig } from "../hooks/tools/changePermissions/useChangePermissionsOperation";
|
import { changePermissionsOperationConfig } from "../hooks/tools/changePermissions/useChangePermissionsOperation";
|
||||||
|
import { flattenOperationConfig } from "../hooks/tools/flatten/useFlattenOperation";
|
||||||
import CompressSettings from "../components/tools/compress/CompressSettings";
|
import CompressSettings from "../components/tools/compress/CompressSettings";
|
||||||
import SplitSettings from "../components/tools/split/SplitSettings";
|
import SplitSettings from "../components/tools/split/SplitSettings";
|
||||||
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
|
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
|
||||||
@ -39,6 +41,7 @@ import AddWatermarkSingleStepSettings from "../components/tools/addWatermark/Add
|
|||||||
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
||||||
import ConvertSettings from "../components/tools/convert/ConvertSettings";
|
import ConvertSettings from "../components/tools/convert/ConvertSettings";
|
||||||
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
|
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
|
||||||
|
import FlattenSettings from "../components/tools/flatten/FlattenSettings";
|
||||||
import { ToolId } from "../types/toolId";
|
import { ToolId } from "../types/toolId";
|
||||||
|
|
||||||
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
|
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
|
||||||
@ -198,10 +201,14 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
flatten: {
|
flatten: {
|
||||||
icon: <LocalIcon icon="layers-clear-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="layers-clear-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.flatten.title", "Flatten"),
|
name: t("home.flatten.title", "Flatten"),
|
||||||
component: null,
|
component: Flatten,
|
||||||
description: t("home.flatten.desc", "Remove all interactive elements and forms from a PDF"),
|
description: t("home.flatten.desc", "Remove all interactive elements and forms from a PDF"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
|
maxFiles: -1,
|
||||||
|
endpoints: ["flatten"],
|
||||||
|
operationConfig: flattenOperationConfig,
|
||||||
|
settingsComponent: FlattenSettings,
|
||||||
},
|
},
|
||||||
"unlock-pdf-forms": {
|
"unlock-pdf-forms": {
|
||||||
icon: <LocalIcon icon="preview-off-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="preview-off-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
|
33
frontend/src/hooks/tools/flatten/useFlattenOperation.ts
Normal file
33
frontend/src/hooks/tools/flatten/useFlattenOperation.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ToolType, useToolOperation } from '../shared/useToolOperation';
|
||||||
|
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||||
|
import { FlattenParameters, defaultParameters } from './useFlattenParameters';
|
||||||
|
|
||||||
|
// Static function that can be used by both the hook and automation executor
|
||||||
|
export const buildFlattenFormData = (parameters: FlattenParameters, file: File): FormData => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('fileInput', file);
|
||||||
|
formData.append('flattenOnlyForms', parameters.flattenOnlyForms.toString());
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Static configuration object
|
||||||
|
export const flattenOperationConfig = {
|
||||||
|
toolType: ToolType.singleFile,
|
||||||
|
buildFormData: buildFlattenFormData,
|
||||||
|
operationType: 'flatten',
|
||||||
|
endpoint: '/api/v1/misc/flatten',
|
||||||
|
filePrefix: 'flattened_', // Will be overridden in hook with translation
|
||||||
|
multiFileEndpoint: false,
|
||||||
|
defaultParameters,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const useFlattenOperation = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return useToolOperation<FlattenParameters>({
|
||||||
|
...flattenOperationConfig,
|
||||||
|
filePrefix: t('flatten.filenamePrefix', 'flattened') + '_',
|
||||||
|
getErrorMessage: createStandardErrorHandler(t('flatten.error.failed', 'An error occurred while flattening the PDF.'))
|
||||||
|
});
|
||||||
|
};
|
20
frontend/src/hooks/tools/flatten/useFlattenParameters.ts
Normal file
20
frontend/src/hooks/tools/flatten/useFlattenParameters.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { BaseParameters } from '../../../types/parameters';
|
||||||
|
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
|
||||||
|
|
||||||
|
export interface FlattenParameters extends BaseParameters {
|
||||||
|
flattenOnlyForms: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultParameters: FlattenParameters = {
|
||||||
|
flattenOnlyForms: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FlattenParametersHook = BaseParametersHook<FlattenParameters>;
|
||||||
|
|
||||||
|
export const useFlattenParameters = (): FlattenParametersHook => {
|
||||||
|
return useBaseParameters({
|
||||||
|
defaultParameters,
|
||||||
|
endpointName: 'flatten',
|
||||||
|
validateFn: () => true, // Always valid - no required parameters
|
||||||
|
});
|
||||||
|
};
|
63
frontend/src/tools/Flatten.tsx
Normal file
63
frontend/src/tools/Flatten.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||||
|
import FlattenSettings from "../components/tools/flatten/FlattenSettings";
|
||||||
|
import { useFlattenParameters } from "../hooks/tools/flatten/useFlattenParameters";
|
||||||
|
import { useFlattenOperation } from "../hooks/tools/flatten/useFlattenOperation";
|
||||||
|
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
|
||||||
|
import { useFlattenTips } from "../components/tooltips/useFlattenTips";
|
||||||
|
import { BaseToolProps, ToolComponent } from "../types/tool";
|
||||||
|
|
||||||
|
const Flatten = (props: BaseToolProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const flattenTips = useFlattenTips();
|
||||||
|
|
||||||
|
const base = useBaseTool(
|
||||||
|
'flatten',
|
||||||
|
useFlattenParameters,
|
||||||
|
useFlattenOperation,
|
||||||
|
props
|
||||||
|
);
|
||||||
|
|
||||||
|
return createToolFlow({
|
||||||
|
forceStepNumbers: true,
|
||||||
|
files: {
|
||||||
|
selectedFiles: base.selectedFiles,
|
||||||
|
isCollapsed: base.hasResults,
|
||||||
|
placeholder: t("flatten.files.placeholder", "Select a PDF file in the main view to get started"),
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
title: t("flatten.options.stepTitle", "Flatten Options"),
|
||||||
|
isCollapsed: base.settingsCollapsed,
|
||||||
|
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
|
||||||
|
tooltip: flattenTips,
|
||||||
|
content: (
|
||||||
|
<FlattenSettings
|
||||||
|
parameters={base.params.parameters}
|
||||||
|
onParameterChange={base.params.updateParameter}
|
||||||
|
disabled={base.endpointLoading}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
executeButton: {
|
||||||
|
text: t("flatten.submit", "Flatten PDF"),
|
||||||
|
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("flatten.results.title", "Flatten Results"),
|
||||||
|
onFileClick: base.handleThumbnailClick,
|
||||||
|
onUndo: base.handleUndo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Static method to get the operation hook for automation
|
||||||
|
Flatten.tool = () => useFlattenOperation;
|
||||||
|
|
||||||
|
export default Flatten as ToolComponent;
|
Loading…
Reference in New Issue
Block a user