V2 Repair tool (#4234)

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## 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.
This commit is contained in:
Anthony Stirling 2025-08-19 18:00:50 +01:00 committed by GitHub
parent 8f32082145
commit f1cb8ce5b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 234 additions and 5 deletions

View File

@ -1188,7 +1188,18 @@
"tags": "fix,restore,correction,recover",
"title": "Repair",
"header": "Repair PDFs",
"submit": "Repair"
"submit": "Repair",
"description": "This tool will attempt to repair corrupted or damaged PDF files. No additional settings are required.",
"filenamePrefix": "repaired",
"files": {
"placeholder": "Select a PDF file in the main view to get started"
},
"error": {
"failed": "An error occurred whilst repairing the PDF."
},
"results": {
"title": "Repair Results"
}
},
"removeBlanks": {
"tags": "cleanup,streamline,non-content,organize",

View File

@ -915,7 +915,18 @@
"tags": "fix,restore,correction,recover",
"title": "Repair",
"header": "Repair PDFs",
"submit": "Repair"
"submit": "Repair",
"description": "This tool will attempt to repair corrupted or damaged PDF files. No additional settings are required.",
"filenamePrefix": "repaired",
"files": {
"placeholder": "Select a PDF file in the main view to get started"
},
"error": {
"failed": "An error occurred while repairing the PDF."
},
"results": {
"title": "Repair Results"
}
},
"removeBlanks": {
"tags": "cleanup,streamline,non-content,organize",

View File

@ -0,0 +1,27 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { RepairParameters } from '../../../hooks/tools/repair/useRepairParameters';
interface RepairSettingsProps {
parameters: RepairParameters;
onParameterChange: <K extends keyof RepairParameters>(parameter: K, value: RepairParameters[K]) => void;
disabled?: boolean;
}
const RepairSettings: React.FC<RepairSettingsProps> = ({
parameters,
onParameterChange,
disabled = false
}) => {
const { t } = useTranslation();
return (
<div className="repair-settings">
<p className="text-muted">
{t('repair.description', 'This tool will attempt to repair corrupted or damaged PDF files. No additional settings are required.')}
</p>
</div>
);
};
export default RepairSettings;

View File

@ -10,6 +10,7 @@ import ChangePermissions from '../tools/ChangePermissions';
import RemovePassword from '../tools/RemovePassword';
import { SubcategoryId, ToolCategory, ToolRegistry } from './toolsTaxonomy';
import AddWatermark from '../tools/AddWatermark';
import Repair from '../tools/Repair';
// Hook to get the translated tool registry
export function useFlatToolRegistry(): ToolRegistry {
@ -384,11 +385,13 @@ export function useFlatToolRegistry(): ToolRegistry {
"repair": {
icon: <span className="material-symbols-rounded">build</span>,
name: t("home.repair.title", "Repair"),
component: null,
component: Repair,
view: "format",
description: t("home.repair.desc", "Repair corrupted or damaged PDF files"),
category: ToolCategory.ADVANCED_TOOLS,
subcategory: SubcategoryId.ADVANCED_FORMATTING
subcategory: SubcategoryId.ADVANCED_FORMATTING,
maxFiles: -1,
endpoints: ["repair"]
},
"detect-split-scanned-photos": {
icon: <span className="material-symbols-rounded">scanner</span>,

View File

@ -0,0 +1,23 @@
import { useTranslation } from 'react-i18next';
import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { RepairParameters } from './useRepairParameters';
export const useRepairOperation = () => {
const { t } = useTranslation();
const buildFormData = (parameters: RepairParameters, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
return formData;
};
return useToolOperation<RepairParameters>({
operationType: 'repair',
endpoint: '/api/v1/misc/repair',
buildFormData,
filePrefix: t('repair.filenamePrefix', 'repaired') + '_',
multiFileEndpoint: false,
getErrorMessage: createStandardErrorHandler(t('repair.error.failed', 'An error occurred while repairing the PDF.'))
});
};

View File

@ -0,0 +1,20 @@
import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface RepairParameters extends BaseParameters {
// Extends BaseParameters - ready for future parameter additions if needed
}
export const defaultParameters: RepairParameters = {
// No parameters needed
};
export type RepairParametersHook = BaseParametersHook<RepairParameters>;
export const useRepairParameters = (): RepairParametersHook => {
return useBaseParameters({
defaultParameters,
endpointName: 'repair',
// validateFn: optional custom validation if needed in future
});
};

View File

@ -0,0 +1,46 @@
import { useState, useCallback } from 'react';
export interface BaseParametersHook<T> {
parameters: T;
updateParameter: <K extends keyof T>(parameter: K, value: T[K]) => void;
resetParameters: () => void;
validateParameters: () => boolean;
getEndpointName: () => string;
}
export interface BaseParametersConfig<T> {
defaultParameters: T;
endpointName: string;
validateFn?: (params: T) => boolean;
}
export function useBaseParameters<T>(config: BaseParametersConfig<T>): BaseParametersHook<T> {
const [parameters, setParameters] = useState<T>(config.defaultParameters);
const updateParameter = useCallback(<K extends keyof T>(parameter: K, value: T[K]) => {
setParameters(prev => ({
...prev,
[parameter]: value,
}));
}, []);
const resetParameters = useCallback(() => {
setParameters(config.defaultParameters);
}, [config.defaultParameters]);
const validateParameters = useCallback(() => {
return config.validateFn ? config.validateFn(parameters) : true;
}, [parameters, config.validateFn]);
const getEndpointName = useCallback(() => {
return config.endpointName;
}, [config.endpointName]);
return {
parameters,
updateParameter,
resetParameters,
validateParameters,
getEndpointName,
};
}

View File

@ -0,0 +1,80 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileContext } from "../contexts/FileContext";
import { useToolFileSelection } from "../contexts/FileSelectionContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { useRepairParameters } from "../hooks/tools/repair/useRepairParameters";
import { useRepairOperation } from "../hooks/tools/repair/useRepairOperation";
import { BaseToolProps } from "../types/tool";
const Repair = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation();
const { setCurrentMode } = useFileContext();
const { selectedFiles } = useToolFileSelection();
const repairParams = useRepairParameters();
const repairOperation = useRepairOperation();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(repairParams.getEndpointName());
useEffect(() => {
repairOperation.resetResults();
onPreviewFile?.(null);
}, [repairParams.parameters]);
const handleRepair = async () => {
try {
await repairOperation.executeOperation(repairParams.parameters, selectedFiles);
if (repairOperation.files && onComplete) {
onComplete(repairOperation.files);
}
} catch (error) {
if (onError) {
onError(error instanceof Error ? error.message : t("repair.error.failed", "Repair operation failed"));
}
}
};
const handleThumbnailClick = (file: File) => {
onPreviewFile?.(file);
sessionStorage.setItem("previousMode", "repair");
setCurrentMode("viewer");
};
const handleSettingsReset = () => {
repairOperation.resetResults();
onPreviewFile?.(null);
setCurrentMode("repair");
};
const hasFiles = selectedFiles.length > 0;
const hasResults = repairOperation.files.length > 0 || repairOperation.downloadUrl !== null;
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasFiles || hasResults,
placeholder: t("repair.files.placeholder", "Select a PDF file in the main view to get started"),
},
steps: [],
executeButton: {
text: t("repair.submit", "Repair PDF"),
isVisible: !hasResults,
loadingText: t("loading"),
onClick: handleRepair,
disabled: !repairParams.validateParameters() || !hasFiles || !endpointEnabled,
},
review: {
isVisible: hasResults,
operation: repairOperation,
title: t("repair.results.title", "Repair Results"),
onFileClick: handleThumbnailClick,
},
});
};
export default Repair;

View File

@ -18,7 +18,8 @@ export type ModeType =
| 'addPassword'
| 'changePermissions'
| 'watermark'
| 'removePassword';
| 'removePassword'
| 'repair';
export type ViewType = 'viewer' | 'pageEditor' | 'fileEditor';

View File

@ -0,0 +1,7 @@
// Base parameter interfaces for reusable patterns
export interface BaseParameters {
// Base interface that all tool parameters should extend
// Provides a foundation for adding common properties across all tools
// Examples of future additions: userId, sessionId, commonFlags, etc.
}