diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 7d01af4f5..13d686f88 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -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", diff --git a/frontend/public/locales/en-US/translation.json b/frontend/public/locales/en-US/translation.json index af7188944..eb5d2e04e 100644 --- a/frontend/public/locales/en-US/translation.json +++ b/frontend/public/locales/en-US/translation.json @@ -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", diff --git a/frontend/src/components/tools/repair/RepairSettings.tsx b/frontend/src/components/tools/repair/RepairSettings.tsx new file mode 100644 index 000000000..15078defb --- /dev/null +++ b/frontend/src/components/tools/repair/RepairSettings.tsx @@ -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: (parameter: K, value: RepairParameters[K]) => void; + disabled?: boolean; +} + +const RepairSettings: React.FC = ({ + parameters, + onParameterChange, + disabled = false +}) => { + const { t } = useTranslation(); + + return ( +
+

+ {t('repair.description', 'This tool will attempt to repair corrupted or damaged PDF files. No additional settings are required.')} +

+
+ ); +}; + +export default RepairSettings; \ No newline at end of file diff --git a/frontend/src/data/useTranslatedToolRegistry.tsx b/frontend/src/data/useTranslatedToolRegistry.tsx index 5f6460217..1a53cf651 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -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: build, 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: scanner, diff --git a/frontend/src/hooks/tools/repair/useRepairOperation.ts b/frontend/src/hooks/tools/repair/useRepairOperation.ts new file mode 100644 index 000000000..b547bbd8f --- /dev/null +++ b/frontend/src/hooks/tools/repair/useRepairOperation.ts @@ -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({ + 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.')) + }); +}; \ No newline at end of file diff --git a/frontend/src/hooks/tools/repair/useRepairParameters.ts b/frontend/src/hooks/tools/repair/useRepairParameters.ts new file mode 100644 index 000000000..5c924de93 --- /dev/null +++ b/frontend/src/hooks/tools/repair/useRepairParameters.ts @@ -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; + +export const useRepairParameters = (): RepairParametersHook => { + return useBaseParameters({ + defaultParameters, + endpointName: 'repair', + // validateFn: optional custom validation if needed in future + }); +}; \ No newline at end of file diff --git a/frontend/src/hooks/tools/shared/useBaseParameters.ts b/frontend/src/hooks/tools/shared/useBaseParameters.ts new file mode 100644 index 000000000..af244e6f9 --- /dev/null +++ b/frontend/src/hooks/tools/shared/useBaseParameters.ts @@ -0,0 +1,46 @@ +import { useState, useCallback } from 'react'; + +export interface BaseParametersHook { + parameters: T; + updateParameter: (parameter: K, value: T[K]) => void; + resetParameters: () => void; + validateParameters: () => boolean; + getEndpointName: () => string; +} + +export interface BaseParametersConfig { + defaultParameters: T; + endpointName: string; + validateFn?: (params: T) => boolean; +} + +export function useBaseParameters(config: BaseParametersConfig): BaseParametersHook { + const [parameters, setParameters] = useState(config.defaultParameters); + + const updateParameter = useCallback((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, + }; +} \ No newline at end of file diff --git a/frontend/src/tools/Repair.tsx b/frontend/src/tools/Repair.tsx new file mode 100644 index 000000000..a3b63f591 --- /dev/null +++ b/frontend/src/tools/Repair.tsx @@ -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; \ No newline at end of file diff --git a/frontend/src/types/fileContext.ts b/frontend/src/types/fileContext.ts index 0b5552c4f..1c513b523 100644 --- a/frontend/src/types/fileContext.ts +++ b/frontend/src/types/fileContext.ts @@ -18,7 +18,8 @@ export type ModeType = | 'addPassword' | 'changePermissions' | 'watermark' - | 'removePassword'; + | 'removePassword' + | 'repair'; export type ViewType = 'viewer' | 'pageEditor' | 'fileEditor'; diff --git a/frontend/src/types/parameters.ts b/frontend/src/types/parameters.ts new file mode 100644 index 000000000..6f8856a8b --- /dev/null +++ b/frontend/src/types/parameters.ts @@ -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. +} \ No newline at end of file