Added other tools

This commit is contained in:
Connor Yoh 2025-08-21 18:08:47 +01:00
parent 85caad5f5c
commit def603a367
14 changed files with 386 additions and 191 deletions

View File

@ -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: <K extends keyof AddWatermarkParameters>(key: K, value: AddWatermarkParameters[K]) => void;
disabled?: boolean;
}
const AddWatermarkSingleStepSettings = ({ parameters, onParameterChange, disabled = false }: AddWatermarkSingleStepSettingsProps) => {
return (
<Stack gap="lg">
{/* Watermark Type Selection */}
<WatermarkTypeSettings
watermarkType={parameters.watermarkType}
onWatermarkTypeChange={(type) => onParameterChange("watermarkType", type)}
disabled={disabled}
/>
{/* Conditional settings based on watermark type */}
{parameters.watermarkType === "text" && (
<>
<WatermarkWording
parameters={parameters}
onParameterChange={onParameterChange}
disabled={disabled}
/>
<WatermarkTextStyle
parameters={parameters}
onParameterChange={onParameterChange}
disabled={disabled}
/>
</>
)}
{parameters.watermarkType === "image" && (
<WatermarkImageFile
parameters={parameters}
onParameterChange={onParameterChange}
disabled={disabled}
/>
)}
{/* Formatting settings for both text and image */}
{parameters.watermarkType && (
<WatermarkFormatting
parameters={parameters}
onParameterChange={onParameterChange}
disabled={disabled}
/>
)}
</Stack>
);
};
export default AddWatermarkSingleStepSettings;

View File

@ -17,9 +17,27 @@ import RemoveCertificateSign from '../tools/RemoveCertificateSign';
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';
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 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';
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 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."), description: t("home.watermark.desc", "Add a custom watermark to your PDF document."),
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.DOCUMENT_SECURITY, subcategory: SubcategoryId.DOCUMENT_SECURITY,
endpoints: ["add-watermark"] endpoints: ["add-watermark"],
operationConfig: addWatermarkOperationConfig,
settingsComponent: AddWatermarkSingleStepSettings
}, },
"add-stamp": { "add-stamp": {
icon: <span className="material-symbols-rounded">approval</span>, icon: <span className="material-symbols-rounded">approval</span>,
@ -95,7 +115,9 @@ export function useFlatToolRegistry(): ToolRegistry {
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.DOCUMENT_SECURITY, subcategory: SubcategoryId.DOCUMENT_SECURITY,
description: t("home.sanitize.desc", "Remove potentially harmful elements from PDF files"), description: t("home.sanitize.desc", "Remove potentially harmful elements from PDF files"),
endpoints: ["sanitize-pdf"] endpoints: ["sanitize-pdf"],
operationConfig: sanitizeOperationConfig,
settingsComponent: SanitizeSettings
}, },
"flatten": { "flatten": {
icon: <span className="material-symbols-rounded">layers_clear</span>, icon: <span className="material-symbols-rounded">layers_clear</span>,
@ -115,7 +137,9 @@ export function useFlatToolRegistry(): ToolRegistry {
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.DOCUMENT_SECURITY, subcategory: SubcategoryId.DOCUMENT_SECURITY,
maxFiles: -1, maxFiles: -1,
endpoints: ["unlock-pdf-forms"] endpoints: ["unlock-pdf-forms"],
operationConfig: unlockPdfFormsOperationConfig,
settingsComponent: UnlockPdfFormsSettings
}, },
"manage-certificates": { "manage-certificates": {
icon: <span className="material-symbols-rounded">license</span>, icon: <span className="material-symbols-rounded">license</span>,
@ -135,7 +159,9 @@ export function useFlatToolRegistry(): ToolRegistry {
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.DOCUMENT_SECURITY, subcategory: SubcategoryId.DOCUMENT_SECURITY,
maxFiles: -1, maxFiles: -1,
endpoints: ["add-password"] endpoints: ["add-password"],
operationConfig: changePermissionsOperationConfig,
settingsComponent: ChangePermissionsSettings
}, },
// Verification // Verification
@ -255,7 +281,8 @@ export function useFlatToolRegistry(): ToolRegistry {
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.PAGE_FORMATTING, subcategory: SubcategoryId.PAGE_FORMATTING,
maxFiles: -1, maxFiles: -1,
endpoints: ["pdf-to-single-page"] endpoints: ["pdf-to-single-page"],
operationConfig: singleLargePageOperationConfig
}, },
"add-attachments": { "add-attachments": {
icon: <span className="material-symbols-rounded">attachment</span>, icon: <span className="material-symbols-rounded">attachment</span>,
@ -338,7 +365,8 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategory: SubcategoryId.REMOVAL, subcategory: SubcategoryId.REMOVAL,
endpoints: ["remove-password"], endpoints: ["remove-password"],
maxFiles: -1, maxFiles: -1,
operationConfig: removePasswordOperationConfig,
settingsComponent: RemovePasswordSettings
}, },
"remove-certificate-sign": { "remove-certificate-sign": {
icon: <span className="material-symbols-rounded">remove_moderator</span>, icon: <span className="material-symbols-rounded">remove_moderator</span>,
@ -349,7 +377,8 @@ export function useFlatToolRegistry(): ToolRegistry {
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.REMOVAL, subcategory: SubcategoryId.REMOVAL,
maxFiles: -1, maxFiles: -1,
endpoints: ["remove-certificate-sign"] endpoints: ["remove-certificate-sign"],
operationConfig: removeCertificateSignOperationConfig
}, },
@ -415,7 +444,9 @@ export function useFlatToolRegistry(): ToolRegistry {
category: ToolCategory.ADVANCED_TOOLS, category: ToolCategory.ADVANCED_TOOLS,
subcategory: SubcategoryId.ADVANCED_FORMATTING, subcategory: SubcategoryId.ADVANCED_FORMATTING,
maxFiles: -1, maxFiles: -1,
endpoints: ["repair"] endpoints: ["repair"],
operationConfig: repairOperationConfig,
settingsComponent: RepairSettings
}, },
"detect-split-scanned-photos": { "detect-split-scanned-photos": {
icon: <span className="material-symbols-rounded">scanner</span>, icon: <span className="material-symbols-rounded">scanner</span>,
@ -590,7 +621,8 @@ export function useFlatToolRegistry(): ToolRegistry {
"zip", "zip",
// Other // Other
"dbf", "fods", "vsd", "vor", "vor3", "vor4", "uop", "pct", "ps", "pdf" "dbf", "fods", "vsd", "vor", "vor3", "vor4", "uop", "pct", "ps", "pdf"
] ],
operationConfig: convertOperationConfig
}, },
"mergePdfs": { "mergePdfs": {
icon: <span className="material-symbols-rounded">library_add</span>, icon: <span className="material-symbols-rounded">library_add</span>,
@ -620,7 +652,9 @@ export function useFlatToolRegistry(): ToolRegistry {
description: t("home.ocr.desc", "Extract text from scanned PDFs using Optical Character Recognition"), description: t("home.ocr.desc", "Extract text from scanned PDFs using Optical Character Recognition"),
category: ToolCategory.RECOMMENDED_TOOLS, category: ToolCategory.RECOMMENDED_TOOLS,
subcategory: SubcategoryId.GENERAL, subcategory: SubcategoryId.GENERAL,
maxFiles: -1 maxFiles: -1,
operationConfig: ocrOperationConfig,
settingsComponent: OCRSettings
}, },
"redact": { "redact": {
icon: <span className="material-symbols-rounded">visibility_off</span>, icon: <span className="material-symbols-rounded">visibility_off</span>,

View File

@ -1,9 +1,10 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useToolOperation } from '../shared/useToolOperation'; import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; 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(); const formData = new FormData();
formData.append("fileInput", file); formData.append("fileInput", file);
@ -32,15 +33,22 @@ const buildFormData = (parameters: AddWatermarkParameters, file: File): FormData
return 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 = () => { export const useAddWatermarkOperation = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return useToolOperation<AddWatermarkParameters>({ return useToolOperation<AddWatermarkParameters>({
operationType: 'watermark', ...addWatermarkOperationConfig,
endpoint: '/api/v1/security/add-watermark',
buildFormData,
filePrefix: t('watermark.filenamePrefix', 'watermarked') + '_', 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.')) getErrorMessage: createStandardErrorHandler(t('watermark.error.failed', 'An error occurred while adding watermark to the PDF.'))
}); });
}; };

View File

@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useToolOperation } from '../shared/useToolOperation'; import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import type { ChangePermissionsParameters } from './useChangePermissionsParameters'; import { ChangePermissionsParameters, defaultParameters } from './useChangePermissionsParameters';
export const getFormData = ((parameters: ChangePermissionsParameters) => export const getFormData = ((parameters: ChangePermissionsParameters) =>
Object.entries(parameters).map(([key, value]) => Object.entries(parameters).map(([key, value]) =>
@ -9,10 +9,8 @@ export const getFormData = ((parameters: ChangePermissionsParameters) =>
) as string[][] ) as string[][]
); );
export const useChangePermissionsOperation = () => { // Static function that can be used by both the hook and automation executor
const { t } = useTranslation(); export const buildChangePermissionsFormData = (parameters: ChangePermissionsParameters, file: File): FormData => {
const buildFormData = (parameters: ChangePermissionsParameters, file: File): FormData => {
const formData = new FormData(); const formData = new FormData();
formData.append("fileInput", file); formData.append("fileInput", file);
@ -22,14 +20,23 @@ export const useChangePermissionsOperation = () => {
}); });
return formData; return formData;
}; };
return useToolOperation({ // Static configuration object
operationType: 'changePermissions', export const changePermissionsOperationConfig = {
operationType: 'change-permissions',
endpoint: '/api/v1/security/add-password', // Change Permissions is a fake endpoint for the Add Password tool endpoint: '/api/v1/security/add-password', // Change Permissions is a fake endpoint for the Add Password tool
buildFormData, buildFormData: buildChangePermissionsFormData,
filePrefix: 'permissions_', filePrefix: 'permissions_',
multiFileEndpoint: false, multiFileEndpoint: false,
defaultParameters,
} as const;
export const useChangePermissionsOperation = () => {
const { t } = useTranslation();
return useToolOperation({
...changePermissionsOperationConfig,
getErrorMessage: createStandardErrorHandler( getErrorMessage: createStandardErrorHandler(
t('changePermissions.error.failed', 'An error occurred while changing PDF permissions.') t('changePermissions.error.failed', 'An error occurred while changing PDF permissions.')
) )

View File

@ -1,13 +1,14 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import axios from 'axios'; import axios from 'axios';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ConvertParameters } from './useConvertParameters'; import { ConvertParameters, defaultParameters } from './useConvertParameters';
import { detectFileExtension } from '../../../utils/fileUtils'; import { detectFileExtension } from '../../../utils/fileUtils';
import { createFileFromApiResponse } from '../../../utils/fileResponseUtils'; import { createFileFromApiResponse } from '../../../utils/fileResponseUtils';
import { useToolOperation, ToolOperationConfig } from '../shared/useToolOperation'; import { useToolOperation, ToolOperationConfig } from '../shared/useToolOperation';
import { getEndpointUrl, isImageFormat, isWebFormat } from '../../../utils/convertUtils'; 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[], selectedFiles: File[],
parameters: ConvertParameters parameters: ConvertParameters
): boolean => { ): 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(); const formData = new FormData();
selectedFiles.forEach(file => { selectedFiles.forEach(file => {
@ -69,7 +71,8 @@ const buildFormData = (parameters: ConvertParameters, selectedFiles: File[]): Fo
return formData; return formData;
}; };
const createFileFromResponse = ( // Static function that can be used by both the hook and automation executor
export const createFileFromResponse = (
responseData: any, responseData: any,
headers: any, headers: any,
originalFileName: string, originalFileName: string,
@ -81,19 +84,16 @@ const createFileFromResponse = (
return createFileFromApiResponse(responseData, headers, fallbackFilename); return createFileFromApiResponse(responseData, headers, fallbackFilename);
}; };
export const useConvertOperation = () => { // Static processor that can be used by both the hook and automation executor
const { t } = useTranslation(); export const convertProcessor = async (
const customConvertProcessor = useCallback(async (
parameters: ConvertParameters, parameters: ConvertParameters,
selectedFiles: File[] selectedFiles: File[]
): Promise<File[]> => { ): Promise<File[]> => {
const processedFiles: File[] = []; const processedFiles: File[] = [];
const endpoint = getEndpointUrl(parameters.fromExtension, parameters.toExtension); const endpoint = getEndpointUrl(parameters.fromExtension, parameters.toExtension);
if (!endpoint) { if (!endpoint) {
throw new Error(t('errorNotSupported', 'Unsupported conversion format')); throw new Error('Unsupported conversion format');
} }
// Convert-specific routing logic: decide batch vs individual processing // Convert-specific routing logic: decide batch vs individual processing
@ -101,7 +101,7 @@ export const useConvertOperation = () => {
// Individual processing for complex cases (PDF→image, smart detection, etc.) // Individual processing for complex cases (PDF→image, smart detection, etc.)
for (const file of selectedFiles) { for (const file of selectedFiles) {
try { try {
const formData = buildFormData(parameters, [file]); const formData = buildConvertFormData(parameters, [file]);
const response = await axios.post(endpoint, formData, { responseType: 'blob' }); const response = await axios.post(endpoint, formData, { responseType: 'blob' });
const convertedFile = createFileFromResponse(response.data, response.headers, file.name, parameters.toExtension); const convertedFile = createFileFromResponse(response.data, response.headers, file.name, parameters.toExtension);
@ -113,7 +113,7 @@ export const useConvertOperation = () => {
} }
} else { } else {
// Batch processing for simple cases (image→PDF combine) // Batch processing for simple cases (image→PDF combine)
const formData = buildFormData(parameters, selectedFiles); const formData = buildConvertFormData(parameters, selectedFiles);
const response = await axios.post(endpoint, formData, { responseType: 'blob' }); const response = await axios.post(endpoint, formData, { responseType: 'blob' });
const baseFilename = selectedFiles.length === 1 const baseFilename = selectedFiles.length === 1
@ -122,18 +122,34 @@ export const useConvertOperation = () => {
const convertedFile = createFileFromResponse(response.data, response.headers, baseFilename, parameters.toExtension); const convertedFile = createFileFromResponse(response.data, response.headers, baseFilename, parameters.toExtension);
processedFiles.push(convertedFile); processedFiles.push(convertedFile);
} }
return processedFiles; return processedFiles;
}, [t]); };
return useToolOperation<ConvertParameters>({ // Static configuration object
export const convertOperationConfig = {
operationType: 'convert', operationType: 'convert',
endpoint: '', // Not used with customProcessor but required endpoint: '', // Not used with customProcessor but required
buildFormData, // Not used with customProcessor but required buildFormData: buildConvertFormData, // Not used with customProcessor but required
filePrefix: 'converted_', filePrefix: 'converted_',
customProcessor: customConvertProcessor, // Convert handles its own routing customProcessor: convertProcessor,
defaultParameters,
} as const;
export const useConvertOperation = () => {
const { t } = useTranslation();
const customConvertProcessor = useCallback(async (
parameters: ConvertParameters,
selectedFiles: File[]
): Promise<File[]> => {
return convertProcessor(parameters, selectedFiles);
}, []);
return useToolOperation<ConvertParameters>({
...convertOperationConfig,
customProcessor: customConvertProcessor, // Use instance-specific processor for translation support
getErrorMessage: (error) => { getErrorMessage: (error) => {
if (error.response?.data && typeof error.response.data === 'string') { if (error.response?.data && typeof error.response.data === 'string') {
return error.response.data; return error.response.data;

View File

@ -46,7 +46,7 @@ export interface ConvertParametersHook extends BaseParametersHook<ConvertParamet
analyzeFileTypes: (files: Array<{name: string}>) => void; analyzeFileTypes: (files: Array<{name: string}>) => void;
} }
const defaultParameters: ConvertParameters = { export const defaultParameters: ConvertParameters = {
fromExtension: '', fromExtension: '',
toExtension: '', toExtension: '',
imageOptions: { imageOptions: {

View File

@ -1,6 +1,6 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { OCRParameters } from './useOCRParameters'; import { OCRParameters, defaultParameters } from './useOCRParameters';
import { useToolOperation, ToolOperationConfig } from '../shared/useToolOperation'; import { useToolOperation, ToolOperationConfig } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { useToolResources } from '../shared/useToolResources'; import { useToolResources } from '../shared/useToolResources';
@ -37,7 +37,8 @@ function stripExt(name: string): string {
return i > 0 ? name.slice(0, i) : name; 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(); const formData = new FormData();
formData.append('fileInput', file); formData.append('fileInput', file);
parameters.languages.forEach((lang) => formData.append('languages', lang)); parameters.languages.forEach((lang) => formData.append('languages', lang));
@ -51,12 +52,8 @@ const buildFormData = (parameters: OCRParameters, file: File): FormData => {
return formData; return formData;
}; };
export const useOCROperation = () => { // Static response handler for OCR - can be used by automation executor
const { t } = useTranslation(); export const ocrResponseHandler = async (blob: Blob, originalFiles: File[], extractZipFiles: (blob: Blob) => Promise<{ success: boolean; extractedFiles: File[]; errors: string[] }>): Promise<File[]> => {
const { extractZipFiles } = useToolResources();
// OCR-specific parsing: ZIP (sidecar) vs PDF vs HTML error
const responseHandler = useCallback(async (blob: Blob, originalFiles: File[]): Promise<File[]> => {
const headBuf = await blob.slice(0, 8).arrayBuffer(); const headBuf = await blob.slice(0, 8).arrayBuffer();
const head = new TextDecoder().decode(new Uint8Array(headBuf)); const head = new TextDecoder().decode(new Uint8Array(headBuf));
@ -64,8 +61,8 @@ export const useOCROperation = () => {
if (head.startsWith('PK')) { if (head.startsWith('PK')) {
const base = stripExt(originalFiles[0].name); const base = stripExt(originalFiles[0].name);
try { try {
const extracted = await extractZipFiles(blob); const result = await extractZipFiles(blob);
if (extracted.length > 0) return extracted; if (result.success && result.extractedFiles.length > 0) return result.extractedFiles;
} catch { /* ignore and try local extractor */ } } catch { /* ignore and try local extractor */ }
try { try {
const local = await extractZipFile(blob); // local fallback const local = await extractZipFile(blob); // local fallback
@ -85,7 +82,7 @@ export const useOCROperation = () => {
const title = const title =
text.match(/<title[^>]*>([^<]+)<\/title>/i)?.[1] || text.match(/<title[^>]*>([^<]+)<\/title>/i)?.[1] ||
text.match(/<h1[^>]*>([^<]+)<\/h1>/i)?.[1] || text.match(/<h1[^>]*>([^<]+)<\/h1>/i)?.[1] ||
t('ocr.error.unknown', 'Unknown error'); 'Unknown error';
throw new Error(`OCR service error: ${title}`); throw new Error(`OCR service error: ${title}`);
} }
throw new Error(`Response is not a valid PDF. Header: "${head}"`); throw new Error(`Response is not a valid PDF. Header: "${head}"`);
@ -93,15 +90,30 @@ export const useOCROperation = () => {
const base = stripExt(originalFiles[0].name); const base = stripExt(originalFiles[0].name);
return [new File([blob], `ocr_${base}.pdf`, { type: 'application/pdf' })]; return [new File([blob], `ocr_${base}.pdf`, { type: 'application/pdf' })];
}, [t, extractZipFiles]); };
const ocrConfig: ToolOperationConfig<OCRParameters> = { // Static configuration object (without t function dependencies)
export const ocrOperationConfig = {
operationType: 'ocr', operationType: 'ocr',
endpoint: '/api/v1/misc/ocr-pdf', endpoint: '/api/v1/misc/ocr-pdf',
buildFormData, buildFormData: buildOCRFormData,
filePrefix: 'ocr_', filePrefix: 'ocr_',
multiFileEndpoint: false, // Process files individually multiFileEndpoint: false,
responseHandler, // use shared flow 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<File[]> => {
return ocrResponseHandler(blob, originalFiles, extractZipFiles);
}, [extractZipFiles]);
const ocrConfig: ToolOperationConfig<OCRParameters> = {
...ocrOperationConfig,
responseHandler,
getErrorMessage: (error) => getErrorMessage: (error) =>
error.message?.includes('OCR tools') && error.message?.includes('not installed') 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.' ? '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.'

View File

@ -10,7 +10,7 @@ export interface OCRParameters extends BaseParameters {
export type OCRParametersHook = BaseParametersHook<OCRParameters>; export type OCRParametersHook = BaseParametersHook<OCRParameters>;
const defaultParameters: OCRParameters = { export const defaultParameters: OCRParameters = {
languages: [], languages: [],
ocrType: 'skip-text', ocrType: 'skip-text',
ocrRenderType: 'hocr', ocrRenderType: 'hocr',

View File

@ -1,23 +1,31 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useToolOperation } from '../shared/useToolOperation'; import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; 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 = () => { export const useRemoveCertificateSignOperation = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const buildFormData = (parameters: RemoveCertificateSignParameters, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
return formData;
};
return useToolOperation<RemoveCertificateSignParameters>({ return useToolOperation<RemoveCertificateSignParameters>({
operationType: 'removeCertificateSign', ...removeCertificateSignOperationConfig,
endpoint: '/api/v1/security/remove-cert-sign',
buildFormData,
filePrefix: t('removeCertSign.filenamePrefix', 'unsigned') + '_', filePrefix: t('removeCertSign.filenamePrefix', 'unsigned') + '_',
multiFileEndpoint: false,
getErrorMessage: createStandardErrorHandler(t('removeCertSign.error.failed', 'An error occurred while removing certificate signatures.')) getErrorMessage: createStandardErrorHandler(t('removeCertSign.error.failed', 'An error occurred while removing certificate signatures.'))
}); });
}; };

View File

@ -1,24 +1,32 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useToolOperation } from '../shared/useToolOperation'; import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { RemovePasswordParameters } from './useRemovePasswordParameters'; import { RemovePasswordParameters, defaultParameters } from './useRemovePasswordParameters';
export const useRemovePasswordOperation = () => { // Static function that can be used by both the hook and automation executor
const { t } = useTranslation(); export const buildRemovePasswordFormData = (parameters: RemovePasswordParameters, file: File): FormData => {
const buildFormData = (parameters: RemovePasswordParameters, file: File): FormData => {
const formData = new FormData(); const formData = new FormData();
formData.append("fileInput", file); formData.append("fileInput", file);
formData.append("password", parameters.password); formData.append("password", parameters.password);
return formData; return formData;
}; };
return useToolOperation<RemovePasswordParameters>({ // Static configuration object
export const removePasswordOperationConfig = {
operationType: 'removePassword', operationType: 'removePassword',
endpoint: '/api/v1/security/remove-password', endpoint: '/api/v1/security/remove-password',
buildFormData, buildFormData: buildRemovePasswordFormData,
filePrefix: t('removePassword.filenamePrefix', 'decrypted') + '_', filePrefix: 'decrypted_', // Will be overridden in hook with translation
multiFileEndpoint: false, multiFileEndpoint: false,
defaultParameters,
} as const;
export const useRemovePasswordOperation = () => {
const { t } = useTranslation();
return useToolOperation<RemovePasswordParameters>({
...removePasswordOperationConfig,
filePrefix: t('removePassword.filenamePrefix', 'decrypted') + '_',
getErrorMessage: createStandardErrorHandler(t('removePassword.error.failed', 'An error occurred while removing the password from the PDF.')) getErrorMessage: createStandardErrorHandler(t('removePassword.error.failed', 'An error occurred while removing the password from the PDF.'))
}); });
}; };

View File

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

View File

@ -1,9 +1,10 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useToolOperation } from '../shared/useToolOperation'; import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; 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(); const formData = new FormData();
formData.append('fileInput', file); formData.append('fileInput', file);
@ -18,15 +19,22 @@ const buildFormData = (parameters: SanitizeParameters, file: File): FormData =>
return 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 = () => { export const useSanitizeOperation = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return useToolOperation<SanitizeParameters>({ return useToolOperation<SanitizeParameters>({
operationType: 'sanitize', ...sanitizeOperationConfig,
endpoint: '/api/v1/security/sanitize-pdf',
buildFormData,
filePrefix: t('sanitize.filenamePrefix', 'sanitized') + '_', 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.')) getErrorMessage: createStandardErrorHandler(t('sanitize.error.failed', 'An error occurred while sanitising the PDF.'))
}); });
}; };

View File

@ -1,23 +1,31 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useToolOperation } from '../shared/useToolOperation'; import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; 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 = () => { export const useSingleLargePageOperation = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const buildFormData = (parameters: SingleLargePageParameters, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
return formData;
};
return useToolOperation<SingleLargePageParameters>({ return useToolOperation<SingleLargePageParameters>({
operationType: 'singleLargePage', ...singleLargePageOperationConfig,
endpoint: '/api/v1/general/pdf-to-single-page',
buildFormData,
filePrefix: t('pdfToSinglePage.filenamePrefix', 'single_page') + '_', filePrefix: t('pdfToSinglePage.filenamePrefix', 'single_page') + '_',
multiFileEndpoint: false,
getErrorMessage: createStandardErrorHandler(t('pdfToSinglePage.error.failed', 'An error occurred while converting to single page.')) getErrorMessage: createStandardErrorHandler(t('pdfToSinglePage.error.failed', 'An error occurred while converting to single page.'))
}); });
}; };

View File

@ -1,23 +1,31 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useToolOperation } from '../shared/useToolOperation'; import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; 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 = () => { export const useUnlockPdfFormsOperation = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const buildFormData = (parameters: UnlockPdfFormsParameters, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
return formData;
};
return useToolOperation<UnlockPdfFormsParameters>({ return useToolOperation<UnlockPdfFormsParameters>({
operationType: 'unlockPdfForms', ...unlockPdfFormsOperationConfig,
endpoint: '/api/v1/misc/unlock-pdf-forms',
buildFormData,
filePrefix: t('unlockPDFForms.filenamePrefix', 'unlocked_forms') + '_', filePrefix: t('unlockPDFForms.filenamePrefix', 'unlocked_forms') + '_',
multiFileEndpoint: false,
getErrorMessage: createStandardErrorHandler(t('unlockPDFForms.error.failed', 'An error occurred while unlocking PDF forms.')) getErrorMessage: createStandardErrorHandler(t('unlockPDFForms.error.failed', 'An error occurred while unlocking PDF forms.'))
}); });
}; };