diff --git a/frontend/src/components/tools/addWatermark/AddWatermarkSingleStepSettings.tsx b/frontend/src/components/tools/addWatermark/AddWatermarkSingleStepSettings.tsx new file mode 100644 index 000000000..59ed48e95 --- /dev/null +++ b/frontend/src/components/tools/addWatermark/AddWatermarkSingleStepSettings.tsx @@ -0,0 +1,70 @@ +/** + * AddWatermarkSingleStepSettings - Used for automation only + * + * This component combines all watermark settings into a single step interface + * for use in the automation system. It includes type selection and all relevant + * settings in one unified component. + */ + +import React from "react"; +import { Stack } from "@mantine/core"; +import { AddWatermarkParameters } from "../../../hooks/tools/addWatermark/useAddWatermarkParameters"; +import WatermarkTypeSettings from "./WatermarkTypeSettings"; +import WatermarkWording from "./WatermarkWording"; +import WatermarkTextStyle from "./WatermarkTextStyle"; +import WatermarkImageFile from "./WatermarkImageFile"; +import WatermarkFormatting from "./WatermarkFormatting"; + +interface AddWatermarkSingleStepSettingsProps { + parameters: AddWatermarkParameters; + onParameterChange: (key: K, value: AddWatermarkParameters[K]) => void; + disabled?: boolean; +} + +const AddWatermarkSingleStepSettings = ({ parameters, onParameterChange, disabled = false }: AddWatermarkSingleStepSettingsProps) => { + return ( + + {/* Watermark Type Selection */} + onParameterChange("watermarkType", type)} + disabled={disabled} + /> + + {/* Conditional settings based on watermark type */} + {parameters.watermarkType === "text" && ( + <> + + + + )} + + {parameters.watermarkType === "image" && ( + + )} + + {/* Formatting settings for both text and image */} + {parameters.watermarkType && ( + + )} + + ); +}; + +export default AddWatermarkSingleStepSettings; \ No newline at end of file diff --git a/frontend/src/data/useTranslatedToolRegistry.tsx b/frontend/src/data/useTranslatedToolRegistry.tsx index 769e2cbd1..c4ec2e7a7 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -17,9 +17,27 @@ import RemoveCertificateSign from '../tools/RemoveCertificateSign'; import { compressOperationConfig } from '../hooks/tools/compress/useCompressOperation'; import { splitOperationConfig } from '../hooks/tools/split/useSplitOperation'; import { addPasswordOperationConfig } from '../hooks/tools/addPassword/useAddPasswordOperation'; +import { removePasswordOperationConfig } from '../hooks/tools/removePassword/useRemovePasswordOperation'; +import { sanitizeOperationConfig } from '../hooks/tools/sanitize/useSanitizeOperation'; +import { repairOperationConfig } from '../hooks/tools/repair/useRepairOperation'; +import { addWatermarkOperationConfig } from '../hooks/tools/addWatermark/useAddWatermarkOperation'; +import { unlockPdfFormsOperationConfig } from '../hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation'; +import { singleLargePageOperationConfig } from '../hooks/tools/singleLargePage/useSingleLargePageOperation'; +import { ocrOperationConfig } from '../hooks/tools/ocr/useOCROperation'; +import { convertOperationConfig } from '../hooks/tools/convert/useConvertOperation'; +import { removeCertificateSignOperationConfig } from '../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation'; +import { changePermissionsOperationConfig } from '../hooks/tools/changePermissions/useChangePermissionsOperation'; import CompressSettings from '../components/tools/compress/CompressSettings'; import SplitSettings from '../components/tools/split/SplitSettings'; import AddPasswordSettings from '../components/tools/addPassword/AddPasswordSettings'; +import RemovePasswordSettings from '../components/tools/removePassword/RemovePasswordSettings'; +import SanitizeSettings from '../components/tools/sanitize/SanitizeSettings'; +import RepairSettings from '../components/tools/repair/RepairSettings'; +import UnlockPdfFormsSettings from '../components/tools/unlockPdfForms/UnlockPdfFormsSettings'; +import AddWatermarkSingleStepSettings from '../components/tools/addWatermark/AddWatermarkSingleStepSettings'; +import OCRSettings from '../components/tools/ocr/OCRSettings'; +import ConvertSettings from '../components/tools/convert/ConvertSettings'; +import ChangePermissionsSettings from '../components/tools/changePermissions/ChangePermissionsSettings'; const showPlaceholderTools = false; // For development purposes. Allows seeing the full list of tools, even if they're unimplemented @@ -75,7 +93,9 @@ export function useFlatToolRegistry(): ToolRegistry { description: t("home.watermark.desc", "Add a custom watermark to your PDF document."), category: ToolCategory.STANDARD_TOOLS, subcategory: SubcategoryId.DOCUMENT_SECURITY, - endpoints: ["add-watermark"] + endpoints: ["add-watermark"], + operationConfig: addWatermarkOperationConfig, + settingsComponent: AddWatermarkSingleStepSettings }, "add-stamp": { icon: approval, @@ -95,7 +115,9 @@ export function useFlatToolRegistry(): ToolRegistry { category: ToolCategory.STANDARD_TOOLS, subcategory: SubcategoryId.DOCUMENT_SECURITY, description: t("home.sanitize.desc", "Remove potentially harmful elements from PDF files"), - endpoints: ["sanitize-pdf"] + endpoints: ["sanitize-pdf"], + operationConfig: sanitizeOperationConfig, + settingsComponent: SanitizeSettings }, "flatten": { icon: layers_clear, @@ -115,7 +137,9 @@ export function useFlatToolRegistry(): ToolRegistry { category: ToolCategory.STANDARD_TOOLS, subcategory: SubcategoryId.DOCUMENT_SECURITY, maxFiles: -1, - endpoints: ["unlock-pdf-forms"] + endpoints: ["unlock-pdf-forms"], + operationConfig: unlockPdfFormsOperationConfig, + settingsComponent: UnlockPdfFormsSettings }, "manage-certificates": { icon: license, @@ -135,7 +159,9 @@ export function useFlatToolRegistry(): ToolRegistry { category: ToolCategory.STANDARD_TOOLS, subcategory: SubcategoryId.DOCUMENT_SECURITY, maxFiles: -1, - endpoints: ["add-password"] + endpoints: ["add-password"], + operationConfig: changePermissionsOperationConfig, + settingsComponent: ChangePermissionsSettings }, // Verification @@ -255,7 +281,8 @@ export function useFlatToolRegistry(): ToolRegistry { category: ToolCategory.STANDARD_TOOLS, subcategory: SubcategoryId.PAGE_FORMATTING, maxFiles: -1, - endpoints: ["pdf-to-single-page"] + endpoints: ["pdf-to-single-page"], + operationConfig: singleLargePageOperationConfig }, "add-attachments": { icon: attachment, @@ -338,7 +365,8 @@ export function useFlatToolRegistry(): ToolRegistry { subcategory: SubcategoryId.REMOVAL, endpoints: ["remove-password"], maxFiles: -1, - + operationConfig: removePasswordOperationConfig, + settingsComponent: RemovePasswordSettings }, "remove-certificate-sign": { icon: remove_moderator, @@ -349,7 +377,8 @@ export function useFlatToolRegistry(): ToolRegistry { category: ToolCategory.STANDARD_TOOLS, subcategory: SubcategoryId.REMOVAL, maxFiles: -1, - endpoints: ["remove-certificate-sign"] + endpoints: ["remove-certificate-sign"], + operationConfig: removeCertificateSignOperationConfig }, @@ -415,7 +444,9 @@ export function useFlatToolRegistry(): ToolRegistry { category: ToolCategory.ADVANCED_TOOLS, subcategory: SubcategoryId.ADVANCED_FORMATTING, maxFiles: -1, - endpoints: ["repair"] + endpoints: ["repair"], + operationConfig: repairOperationConfig, + settingsComponent: RepairSettings }, "detect-split-scanned-photos": { icon: scanner, @@ -590,7 +621,8 @@ export function useFlatToolRegistry(): ToolRegistry { "zip", // Other "dbf", "fods", "vsd", "vor", "vor3", "vor4", "uop", "pct", "ps", "pdf" - ] + ], + operationConfig: convertOperationConfig }, "mergePdfs": { icon: library_add, @@ -620,7 +652,9 @@ export function useFlatToolRegistry(): ToolRegistry { description: t("home.ocr.desc", "Extract text from scanned PDFs using Optical Character Recognition"), category: ToolCategory.RECOMMENDED_TOOLS, subcategory: SubcategoryId.GENERAL, - maxFiles: -1 + maxFiles: -1, + operationConfig: ocrOperationConfig, + settingsComponent: OCRSettings }, "redact": { icon: visibility_off, diff --git a/frontend/src/hooks/tools/addWatermark/useAddWatermarkOperation.ts b/frontend/src/hooks/tools/addWatermark/useAddWatermarkOperation.ts index a5f28bc06..5c07ee6e7 100644 --- a/frontend/src/hooks/tools/addWatermark/useAddWatermarkOperation.ts +++ b/frontend/src/hooks/tools/addWatermark/useAddWatermarkOperation.ts @@ -1,9 +1,10 @@ import { useTranslation } from 'react-i18next'; import { useToolOperation } from '../shared/useToolOperation'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; -import { AddWatermarkParameters } from './useAddWatermarkParameters'; +import { AddWatermarkParameters, defaultParameters } from './useAddWatermarkParameters'; -const buildFormData = (parameters: AddWatermarkParameters, file: File): FormData => { +// Static function that can be used by both the hook and automation executor +export const buildAddWatermarkFormData = (parameters: AddWatermarkParameters, file: File): FormData => { const formData = new FormData(); formData.append("fileInput", file); @@ -32,15 +33,22 @@ const buildFormData = (parameters: AddWatermarkParameters, file: File): FormData return formData; }; +// Static configuration object +export const addWatermarkOperationConfig = { + operationType: 'watermark', + endpoint: '/api/v1/security/add-watermark', + buildFormData: buildAddWatermarkFormData, + filePrefix: 'watermarked_', // Will be overridden in hook with translation + multiFileEndpoint: false, + defaultParameters, +} as const; + export const useAddWatermarkOperation = () => { const { t } = useTranslation(); return useToolOperation({ - operationType: 'watermark', - endpoint: '/api/v1/security/add-watermark', - buildFormData, + ...addWatermarkOperationConfig, filePrefix: t('watermark.filenamePrefix', 'watermarked') + '_', - multiFileEndpoint: false, // Individual API calls per file getErrorMessage: createStandardErrorHandler(t('watermark.error.failed', 'An error occurred while adding watermark to the PDF.')) }); }; \ No newline at end of file diff --git a/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.ts b/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.ts index 664a423ff..ee28d5381 100644 --- a/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.ts +++ b/frontend/src/hooks/tools/changePermissions/useChangePermissionsOperation.ts @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next'; import { useToolOperation } from '../shared/useToolOperation'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; -import type { ChangePermissionsParameters } from './useChangePermissionsParameters'; +import { ChangePermissionsParameters, defaultParameters } from './useChangePermissionsParameters'; export const getFormData = ((parameters: ChangePermissionsParameters) => Object.entries(parameters).map(([key, value]) => @@ -9,27 +9,34 @@ export const getFormData = ((parameters: ChangePermissionsParameters) => ) as string[][] ); +// Static function that can be used by both the hook and automation executor +export const buildChangePermissionsFormData = (parameters: ChangePermissionsParameters, file: File): FormData => { + const formData = new FormData(); + formData.append("fileInput", file); + + // Add all permission parameters + getFormData(parameters).forEach(([key, value]) => { + formData.append(key, value); + }); + + return formData; +}; + +// Static configuration object +export const changePermissionsOperationConfig = { + operationType: 'change-permissions', + endpoint: '/api/v1/security/add-password', // Change Permissions is a fake endpoint for the Add Password tool + buildFormData: buildChangePermissionsFormData, + filePrefix: 'permissions_', + multiFileEndpoint: false, + defaultParameters, +} as const; + export const useChangePermissionsOperation = () => { const { t } = useTranslation(); - const buildFormData = (parameters: ChangePermissionsParameters, file: File): FormData => { - const formData = new FormData(); - formData.append("fileInput", file); - - // Add all permission parameters - getFormData(parameters).forEach(([key, value]) => { - formData.append(key, value); - }); - - return formData; - }; - return useToolOperation({ - operationType: 'changePermissions', - endpoint: '/api/v1/security/add-password', // Change Permissions is a fake endpoint for the Add Password tool - buildFormData, - filePrefix: 'permissions_', - multiFileEndpoint: false, + ...changePermissionsOperationConfig, getErrorMessage: createStandardErrorHandler( t('changePermissions.error.failed', 'An error occurred while changing PDF permissions.') ) diff --git a/frontend/src/hooks/tools/convert/useConvertOperation.ts b/frontend/src/hooks/tools/convert/useConvertOperation.ts index f14302aaa..28b854d4b 100644 --- a/frontend/src/hooks/tools/convert/useConvertOperation.ts +++ b/frontend/src/hooks/tools/convert/useConvertOperation.ts @@ -1,13 +1,14 @@ import { useCallback } from 'react'; import axios from 'axios'; import { useTranslation } from 'react-i18next'; -import { ConvertParameters } from './useConvertParameters'; +import { ConvertParameters, defaultParameters } from './useConvertParameters'; import { detectFileExtension } from '../../../utils/fileUtils'; import { createFileFromApiResponse } from '../../../utils/fileResponseUtils'; import { useToolOperation, ToolOperationConfig } from '../shared/useToolOperation'; import { getEndpointUrl, isImageFormat, isWebFormat } from '../../../utils/convertUtils'; -const shouldProcessFilesSeparately = ( +// Static function that can be used by both the hook and automation executor +export const shouldProcessFilesSeparately = ( selectedFiles: File[], parameters: ConvertParameters ): boolean => { @@ -29,7 +30,8 @@ const shouldProcessFilesSeparately = ( ); }; -const buildFormData = (parameters: ConvertParameters, selectedFiles: File[]): FormData => { +// Static function that can be used by both the hook and automation executor +export const buildConvertFormData = (parameters: ConvertParameters, selectedFiles: File[]): FormData => { const formData = new FormData(); selectedFiles.forEach(file => { @@ -69,7 +71,8 @@ const buildFormData = (parameters: ConvertParameters, selectedFiles: File[]): Fo return formData; }; -const createFileFromResponse = ( +// Static function that can be used by both the hook and automation executor +export const createFileFromResponse = ( responseData: any, headers: any, originalFileName: string, @@ -81,6 +84,59 @@ const createFileFromResponse = ( return createFileFromApiResponse(responseData, headers, fallbackFilename); }; +// Static processor that can be used by both the hook and automation executor +export const convertProcessor = async ( + parameters: ConvertParameters, + selectedFiles: File[] +): Promise => { + const processedFiles: File[] = []; + const endpoint = getEndpointUrl(parameters.fromExtension, parameters.toExtension); + + if (!endpoint) { + throw new Error('Unsupported conversion format'); + } + + // Convert-specific routing logic: decide batch vs individual processing + if (shouldProcessFilesSeparately(selectedFiles, parameters)) { + // Individual processing for complex cases (PDF→image, smart detection, etc.) + for (const file of selectedFiles) { + try { + const formData = buildConvertFormData(parameters, [file]); + const response = await axios.post(endpoint, formData, { responseType: 'blob' }); + + const convertedFile = createFileFromResponse(response.data, response.headers, file.name, parameters.toExtension); + + processedFiles.push(convertedFile); + } catch (error) { + console.warn(`Failed to convert file ${file.name}:`, error); + } + } + } else { + // Batch processing for simple cases (image→PDF combine) + const formData = buildConvertFormData(parameters, selectedFiles); + const response = await axios.post(endpoint, formData, { responseType: 'blob' }); + + const baseFilename = selectedFiles.length === 1 + ? selectedFiles[0].name + : 'converted_files'; + + const convertedFile = createFileFromResponse(response.data, response.headers, baseFilename, parameters.toExtension); + processedFiles.push(convertedFile); + } + + return processedFiles; +}; + +// Static configuration object +export const convertOperationConfig = { + operationType: 'convert', + endpoint: '', // Not used with customProcessor but required + buildFormData: buildConvertFormData, // Not used with customProcessor but required + filePrefix: 'converted_', + customProcessor: convertProcessor, + defaultParameters, +} as const; + export const useConvertOperation = () => { const { t } = useTranslation(); @@ -88,52 +144,12 @@ export const useConvertOperation = () => { parameters: ConvertParameters, selectedFiles: File[] ): Promise => { - - const processedFiles: File[] = []; - const endpoint = getEndpointUrl(parameters.fromExtension, parameters.toExtension); - - if (!endpoint) { - throw new Error(t('errorNotSupported', 'Unsupported conversion format')); - } - - // Convert-specific routing logic: decide batch vs individual processing - if (shouldProcessFilesSeparately(selectedFiles, parameters)) { - // Individual processing for complex cases (PDF→image, smart detection, etc.) - for (const file of selectedFiles) { - try { - const formData = buildFormData(parameters, [file]); - const response = await axios.post(endpoint, formData, { responseType: 'blob' }); - - const convertedFile = createFileFromResponse(response.data, response.headers, file.name, parameters.toExtension); - - processedFiles.push(convertedFile); - } catch (error) { - console.warn(`Failed to convert file ${file.name}:`, error); - } - } - } else { - // Batch processing for simple cases (image→PDF combine) - const formData = buildFormData(parameters, selectedFiles); - const response = await axios.post(endpoint, formData, { responseType: 'blob' }); - - const baseFilename = selectedFiles.length === 1 - ? selectedFiles[0].name - : 'converted_files'; - - const convertedFile = createFileFromResponse(response.data, response.headers, baseFilename, parameters.toExtension); - processedFiles.push(convertedFile); - - } - - return processedFiles; - }, [t]); + return convertProcessor(parameters, selectedFiles); + }, []); return useToolOperation({ - operationType: 'convert', - endpoint: '', // Not used with customProcessor but required - buildFormData, // Not used with customProcessor but required - filePrefix: 'converted_', - customProcessor: customConvertProcessor, // Convert handles its own routing + ...convertOperationConfig, + customProcessor: customConvertProcessor, // Use instance-specific processor for translation support getErrorMessage: (error) => { if (error.response?.data && typeof error.response.data === 'string') { return error.response.data; diff --git a/frontend/src/hooks/tools/convert/useConvertParameters.ts b/frontend/src/hooks/tools/convert/useConvertParameters.ts index 64ba7a38e..a3d7092be 100644 --- a/frontend/src/hooks/tools/convert/useConvertParameters.ts +++ b/frontend/src/hooks/tools/convert/useConvertParameters.ts @@ -46,7 +46,7 @@ export interface ConvertParametersHook extends BaseParametersHook) => void; } -const defaultParameters: ConvertParameters = { +export const defaultParameters: ConvertParameters = { fromExtension: '', toExtension: '', imageOptions: { diff --git a/frontend/src/hooks/tools/ocr/useOCROperation.ts b/frontend/src/hooks/tools/ocr/useOCROperation.ts index 8beeff950..b6f06e31f 100644 --- a/frontend/src/hooks/tools/ocr/useOCROperation.ts +++ b/frontend/src/hooks/tools/ocr/useOCROperation.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { OCRParameters } from './useOCRParameters'; +import { OCRParameters, defaultParameters } from './useOCRParameters'; import { useToolOperation, ToolOperationConfig } from '../shared/useToolOperation'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; import { useToolResources } from '../shared/useToolResources'; @@ -37,7 +37,8 @@ function stripExt(name: string): string { return i > 0 ? name.slice(0, i) : name; } -const buildFormData = (parameters: OCRParameters, file: File): FormData => { +// Static function that can be used by both the hook and automation executor +export const buildOCRFormData = (parameters: OCRParameters, file: File): FormData => { const formData = new FormData(); formData.append('fileInput', file); parameters.languages.forEach((lang) => formData.append('languages', lang)); @@ -51,57 +52,68 @@ const buildFormData = (parameters: OCRParameters, file: File): FormData => { return formData; }; +// Static response handler for OCR - can be used by automation executor +export const ocrResponseHandler = async (blob: Blob, originalFiles: File[], extractZipFiles: (blob: Blob) => Promise<{ success: boolean; extractedFiles: File[]; errors: string[] }>): Promise => { + const headBuf = await blob.slice(0, 8).arrayBuffer(); + const head = new TextDecoder().decode(new Uint8Array(headBuf)); + + // ZIP: sidecar or multi-asset output + if (head.startsWith('PK')) { + const base = stripExt(originalFiles[0].name); + try { + const result = await extractZipFiles(blob); + if (result.success && result.extractedFiles.length > 0) return result.extractedFiles; + } catch { /* ignore and try local extractor */ } + try { + const local = await extractZipFile(blob); // local fallback + if (local.length > 0) return local; + } catch { /* fall through */ } + return [new File([blob], `ocr_${base}.zip`, { type: 'application/zip' })]; + } + + // Not a PDF: surface error details if present + if (!head.startsWith('%PDF')) { + const textBuf = await blob.slice(0, 1024).arrayBuffer(); + const text = new TextDecoder().decode(new Uint8Array(textBuf)); + if (/error|exception|html/i.test(text)) { + if (text.includes('OCR tools') && text.includes('not installed')) { + throw new Error('OCR tools (OCRmyPDF or Tesseract) are not installed on the server. Use the standard or fat Docker image instead of ultra-lite, or install OCR tools manually.'); + } + const title = + text.match(/]*>([^<]+)<\/title>/i)?.[1] || + text.match(/]*>([^<]+)<\/h1>/i)?.[1] || + 'Unknown error'; + throw new Error(`OCR service error: ${title}`); + } + throw new Error(`Response is not a valid PDF. Header: "${head}"`); + } + + const base = stripExt(originalFiles[0].name); + return [new File([blob], `ocr_${base}.pdf`, { type: 'application/pdf' })]; +}; + +// Static configuration object (without t function dependencies) +export const ocrOperationConfig = { + operationType: 'ocr', + endpoint: '/api/v1/misc/ocr-pdf', + buildFormData: buildOCRFormData, + filePrefix: 'ocr_', + multiFileEndpoint: false, + defaultParameters, +} as const; + export const useOCROperation = () => { const { t } = useTranslation(); const { extractZipFiles } = useToolResources(); // OCR-specific parsing: ZIP (sidecar) vs PDF vs HTML error const responseHandler = useCallback(async (blob: Blob, originalFiles: File[]): Promise => { - const headBuf = await blob.slice(0, 8).arrayBuffer(); - const head = new TextDecoder().decode(new Uint8Array(headBuf)); - - // ZIP: sidecar or multi-asset output - if (head.startsWith('PK')) { - const base = stripExt(originalFiles[0].name); - try { - const extracted = await extractZipFiles(blob); - if (extracted.length > 0) return extracted; - } catch { /* ignore and try local extractor */ } - try { - const local = await extractZipFile(blob); // local fallback - if (local.length > 0) return local; - } catch { /* fall through */ } - return [new File([blob], `ocr_${base}.zip`, { type: 'application/zip' })]; - } - - // Not a PDF: surface error details if present - if (!head.startsWith('%PDF')) { - const textBuf = await blob.slice(0, 1024).arrayBuffer(); - const text = new TextDecoder().decode(new Uint8Array(textBuf)); - if (/error|exception|html/i.test(text)) { - if (text.includes('OCR tools') && text.includes('not installed')) { - throw new Error('OCR tools (OCRmyPDF or Tesseract) are not installed on the server. Use the standard or fat Docker image instead of ultra-lite, or install OCR tools manually.'); - } - const title = - text.match(/]*>([^<]+)<\/title>/i)?.[1] || - text.match(/]*>([^<]+)<\/h1>/i)?.[1] || - t('ocr.error.unknown', 'Unknown error'); - throw new Error(`OCR service error: ${title}`); - } - throw new Error(`Response is not a valid PDF. Header: "${head}"`); - } - - const base = stripExt(originalFiles[0].name); - return [new File([blob], `ocr_${base}.pdf`, { type: 'application/pdf' })]; - }, [t, extractZipFiles]); + return ocrResponseHandler(blob, originalFiles, extractZipFiles); + }, [extractZipFiles]); const ocrConfig: ToolOperationConfig = { - operationType: 'ocr', - endpoint: '/api/v1/misc/ocr-pdf', - buildFormData, - filePrefix: 'ocr_', - multiFileEndpoint: false, // Process files individually - responseHandler, // use shared flow + ...ocrOperationConfig, + responseHandler, getErrorMessage: (error) => error.message?.includes('OCR tools') && error.message?.includes('not installed') ? 'OCR tools (OCRmyPDF or Tesseract) are not installed on the server. Use the standard or fat Docker image instead of ultra-lite, or install OCR tools manually.' diff --git a/frontend/src/hooks/tools/ocr/useOCRParameters.ts b/frontend/src/hooks/tools/ocr/useOCRParameters.ts index 0fbda4a33..a3fd546d2 100644 --- a/frontend/src/hooks/tools/ocr/useOCRParameters.ts +++ b/frontend/src/hooks/tools/ocr/useOCRParameters.ts @@ -10,7 +10,7 @@ export interface OCRParameters extends BaseParameters { export type OCRParametersHook = BaseParametersHook; -const defaultParameters: OCRParameters = { +export const defaultParameters: OCRParameters = { languages: [], ocrType: 'skip-text', ocrRenderType: 'hocr', diff --git a/frontend/src/hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation.ts b/frontend/src/hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation.ts index 5987944ec..c466606bc 100644 --- a/frontend/src/hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation.ts +++ b/frontend/src/hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation.ts @@ -1,23 +1,31 @@ import { useTranslation } from 'react-i18next'; import { useToolOperation } from '../shared/useToolOperation'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; -import { RemoveCertificateSignParameters } from './useRemoveCertificateSignParameters'; +import { RemoveCertificateSignParameters, defaultParameters } from './useRemoveCertificateSignParameters'; + +// Static function that can be used by both the hook and automation executor +export const buildRemoveCertificateSignFormData = (parameters: RemoveCertificateSignParameters, file: File): FormData => { + const formData = new FormData(); + formData.append("fileInput", file); + return formData; +}; + +// Static configuration object +export const removeCertificateSignOperationConfig = { + operationType: 'remove-certificate-sign', + endpoint: '/api/v1/security/remove-cert-sign', + buildFormData: buildRemoveCertificateSignFormData, + filePrefix: 'unsigned_', // Will be overridden in hook with translation + multiFileEndpoint: false, + defaultParameters, +} as const; export const useRemoveCertificateSignOperation = () => { const { t } = useTranslation(); - const buildFormData = (parameters: RemoveCertificateSignParameters, file: File): FormData => { - const formData = new FormData(); - formData.append("fileInput", file); - return formData; - }; - return useToolOperation({ - operationType: 'removeCertificateSign', - endpoint: '/api/v1/security/remove-cert-sign', - buildFormData, + ...removeCertificateSignOperationConfig, filePrefix: t('removeCertSign.filenamePrefix', 'unsigned') + '_', - multiFileEndpoint: false, getErrorMessage: createStandardErrorHandler(t('removeCertSign.error.failed', 'An error occurred while removing certificate signatures.')) }); }; \ No newline at end of file diff --git a/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.ts b/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.ts index 37f8fa0db..9d61d3c59 100644 --- a/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.ts +++ b/frontend/src/hooks/tools/removePassword/useRemovePasswordOperation.ts @@ -1,24 +1,32 @@ import { useTranslation } from 'react-i18next'; import { useToolOperation } from '../shared/useToolOperation'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; -import { RemovePasswordParameters } from './useRemovePasswordParameters'; +import { RemovePasswordParameters, defaultParameters } from './useRemovePasswordParameters'; + +// Static function that can be used by both the hook and automation executor +export const buildRemovePasswordFormData = (parameters: RemovePasswordParameters, file: File): FormData => { + const formData = new FormData(); + formData.append("fileInput", file); + formData.append("password", parameters.password); + return formData; +}; + +// Static configuration object +export const removePasswordOperationConfig = { + operationType: 'removePassword', + endpoint: '/api/v1/security/remove-password', + buildFormData: buildRemovePasswordFormData, + filePrefix: 'decrypted_', // Will be overridden in hook with translation + multiFileEndpoint: false, + defaultParameters, +} as const; export const useRemovePasswordOperation = () => { const { t } = useTranslation(); - const buildFormData = (parameters: RemovePasswordParameters, file: File): FormData => { - const formData = new FormData(); - formData.append("fileInput", file); - formData.append("password", parameters.password); - return formData; - }; - return useToolOperation({ - operationType: 'removePassword', - endpoint: '/api/v1/security/remove-password', - buildFormData, + ...removePasswordOperationConfig, filePrefix: t('removePassword.filenamePrefix', 'decrypted') + '_', - multiFileEndpoint: false, getErrorMessage: createStandardErrorHandler(t('removePassword.error.failed', 'An error occurred while removing the password from the PDF.')) }); }; diff --git a/frontend/src/hooks/tools/repair/useRepairOperation.ts b/frontend/src/hooks/tools/repair/useRepairOperation.ts index b547bbd8f..8be4fb13f 100644 --- a/frontend/src/hooks/tools/repair/useRepairOperation.ts +++ b/frontend/src/hooks/tools/repair/useRepairOperation.ts @@ -1,23 +1,31 @@ import { useTranslation } from 'react-i18next'; import { useToolOperation } from '../shared/useToolOperation'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; -import { RepairParameters } from './useRepairParameters'; +import { RepairParameters, defaultParameters } from './useRepairParameters'; + +// Static function that can be used by both the hook and automation executor +export const buildRepairFormData = (parameters: RepairParameters, file: File): FormData => { + const formData = new FormData(); + formData.append("fileInput", file); + return formData; +}; + +// Static configuration object +export const repairOperationConfig = { + operationType: 'repair', + endpoint: '/api/v1/misc/repair', + buildFormData: buildRepairFormData, + filePrefix: 'repaired_', // Will be overridden in hook with translation + multiFileEndpoint: false, + defaultParameters, +} as const; 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, + ...repairOperationConfig, 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/sanitize/useSanitizeOperation.ts b/frontend/src/hooks/tools/sanitize/useSanitizeOperation.ts index c83086121..284b715f8 100644 --- a/frontend/src/hooks/tools/sanitize/useSanitizeOperation.ts +++ b/frontend/src/hooks/tools/sanitize/useSanitizeOperation.ts @@ -1,9 +1,10 @@ import { useTranslation } from 'react-i18next'; import { useToolOperation } from '../shared/useToolOperation'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; -import { SanitizeParameters } from './useSanitizeParameters'; +import { SanitizeParameters, defaultParameters } from './useSanitizeParameters'; -const buildFormData = (parameters: SanitizeParameters, file: File): FormData => { +// Static function that can be used by both the hook and automation executor +export const buildSanitizeFormData = (parameters: SanitizeParameters, file: File): FormData => { const formData = new FormData(); formData.append('fileInput', file); @@ -18,15 +19,22 @@ const buildFormData = (parameters: SanitizeParameters, file: File): FormData => return formData; }; +// Static configuration object +export const sanitizeOperationConfig = { + operationType: 'sanitize', + endpoint: '/api/v1/security/sanitize-pdf', + buildFormData: buildSanitizeFormData, + filePrefix: 'sanitized_', // Will be overridden in hook with translation + multiFileEndpoint: false, + defaultParameters, +} as const; + export const useSanitizeOperation = () => { const { t } = useTranslation(); return useToolOperation({ - operationType: 'sanitize', - endpoint: '/api/v1/security/sanitize-pdf', - buildFormData, + ...sanitizeOperationConfig, filePrefix: t('sanitize.filenamePrefix', 'sanitized') + '_', - multiFileEndpoint: false, // Individual API calls per file getErrorMessage: createStandardErrorHandler(t('sanitize.error.failed', 'An error occurred while sanitising the PDF.')) }); }; diff --git a/frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts b/frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts index e73944864..41cbb6dd3 100644 --- a/frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts +++ b/frontend/src/hooks/tools/singleLargePage/useSingleLargePageOperation.ts @@ -1,23 +1,31 @@ import { useTranslation } from 'react-i18next'; import { useToolOperation } from '../shared/useToolOperation'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; -import { SingleLargePageParameters } from './useSingleLargePageParameters'; +import { SingleLargePageParameters, defaultParameters } from './useSingleLargePageParameters'; + +// Static function that can be used by both the hook and automation executor +export const buildSingleLargePageFormData = (parameters: SingleLargePageParameters, file: File): FormData => { + const formData = new FormData(); + formData.append("fileInput", file); + return formData; +}; + +// Static configuration object +export const singleLargePageOperationConfig = { + operationType: 'single-large-page', + endpoint: '/api/v1/general/pdf-to-single-page', + buildFormData: buildSingleLargePageFormData, + filePrefix: 'single_page_', // Will be overridden in hook with translation + multiFileEndpoint: false, + defaultParameters, +} as const; export const useSingleLargePageOperation = () => { const { t } = useTranslation(); - const buildFormData = (parameters: SingleLargePageParameters, file: File): FormData => { - const formData = new FormData(); - formData.append("fileInput", file); - return formData; - }; - return useToolOperation({ - operationType: 'singleLargePage', - endpoint: '/api/v1/general/pdf-to-single-page', - buildFormData, + ...singleLargePageOperationConfig, filePrefix: t('pdfToSinglePage.filenamePrefix', 'single_page') + '_', - multiFileEndpoint: false, getErrorMessage: createStandardErrorHandler(t('pdfToSinglePage.error.failed', 'An error occurred while converting to single page.')) }); }; \ No newline at end of file diff --git a/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation.ts b/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation.ts index 3b648762b..d6081452f 100644 --- a/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation.ts +++ b/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation.ts @@ -1,23 +1,31 @@ import { useTranslation } from 'react-i18next'; import { useToolOperation } from '../shared/useToolOperation'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; -import { UnlockPdfFormsParameters } from './useUnlockPdfFormsParameters'; +import { UnlockPdfFormsParameters, defaultParameters } from './useUnlockPdfFormsParameters'; + +// Static function that can be used by both the hook and automation executor +export const buildUnlockPdfFormsFormData = (parameters: UnlockPdfFormsParameters, file: File): FormData => { + const formData = new FormData(); + formData.append("fileInput", file); + return formData; +}; + +// Static configuration object +export const unlockPdfFormsOperationConfig = { + operationType: 'unlock-pdf-forms', + endpoint: '/api/v1/misc/unlock-pdf-forms', + buildFormData: buildUnlockPdfFormsFormData, + filePrefix: 'unlocked_forms_', // Will be overridden in hook with translation + multiFileEndpoint: false, + defaultParameters, +} as const; export const useUnlockPdfFormsOperation = () => { const { t } = useTranslation(); - const buildFormData = (parameters: UnlockPdfFormsParameters, file: File): FormData => { - const formData = new FormData(); - formData.append("fileInput", file); - return formData; - }; - return useToolOperation({ - operationType: 'unlockPdfForms', - endpoint: '/api/v1/misc/unlock-pdf-forms', - buildFormData, + ...unlockPdfFormsOperationConfig, filePrefix: t('unlockPDFForms.filenamePrefix', 'unlocked_forms') + '_', - multiFileEndpoint: false, getErrorMessage: createStandardErrorHandler(t('unlockPDFForms.error.failed', 'An error occurred while unlocking PDF forms.')) }); }; \ No newline at end of file