Merge remote-tracking branch 'origin/V2' into feature/v2/automate

This commit is contained in:
Connor Yoh 2025-08-21 12:03:17 +01:00
commit 018d1e57ff
22 changed files with 416 additions and 594 deletions

View File

@ -366,14 +366,6 @@
"title": "Convert", "title": "Convert",
"desc": "Convert files between different formats" "desc": "Convert files between different formats"
}, },
"imageToPDF": {
"title": "Image to PDF",
"desc": "Convert a image (PNG, JPEG, GIF) to PDF."
},
"pdfToImage": {
"title": "PDF to Image",
"desc": "Convert a PDF to a image. (PNG, JPEG, GIF)"
},
"pdfOrganiser": { "pdfOrganiser": {
"title": "Organise", "title": "Organise",
"desc": "Remove/Rearrange pages in any order" "desc": "Remove/Rearrange pages in any order"
@ -390,14 +382,6 @@
"title": "Add Watermark", "title": "Add Watermark",
"desc": "Add a custom watermark to your PDF document." "desc": "Add a custom watermark to your PDF document."
}, },
"permissions": {
"title": "Change Permissions",
"desc": "Change the permissions of your PDF document"
},
"pageRemover": {
"title": "Remove",
"desc": "Delete unwanted pages from your PDF document."
},
"removePassword": { "removePassword": {
"title": "Remove Password", "title": "Remove Password",
"desc": "Remove password protection from your PDF document." "desc": "Remove password protection from your PDF document."
@ -414,10 +398,6 @@
"title": "Change Metadata", "title": "Change Metadata",
"desc": "Change/Remove/Add metadata from a PDF document" "desc": "Change/Remove/Add metadata from a PDF document"
}, },
"fileToPDF": {
"title": "Convert file to PDF",
"desc": "Convert nearly any file to PDF (DOCX, PNG, XLS, PPT, TXT and more)"
},
"ocr": { "ocr": {
"title": "OCR / Cleanup scans", "title": "OCR / Cleanup scans",
"desc": "Cleanup scans and detects text from images within a PDF and re-adds it as text." "desc": "Cleanup scans and detects text from images within a PDF and re-adds it as text."
@ -426,30 +406,6 @@
"title": "Extract Images", "title": "Extract Images",
"desc": "Extracts all images from a PDF and saves them to zip" "desc": "Extracts all images from a PDF and saves them to zip"
}, },
"pdfToPDFA": {
"title": "PDF to PDF/A",
"desc": "Convert PDF to PDF/A for long-term storage"
},
"PDFToWord": {
"title": "PDF to Word",
"desc": "Convert PDF to Word formats (DOC, DOCX and ODT)"
},
"PDFToPresentation": {
"title": "PDF to Presentation",
"desc": "Convert PDF to Presentation formats (PPT, PPTX and ODP)"
},
"PDFToText": {
"title": "PDF to RTF (Text)",
"desc": "Convert PDF to Text or RTF format"
},
"PDFToHTML": {
"title": "PDF to HTML",
"desc": "Convert PDF to HTML format"
},
"PDFToXML": {
"title": "PDF to XML",
"desc": "Convert PDF to XML format"
},
"ScannerImageSplit": { "ScannerImageSplit": {
"title": "Detect/Split Scanned photos", "title": "Detect/Split Scanned photos",
"desc": "Splits multiple photos from within a photo/PDF" "desc": "Splits multiple photos from within a photo/PDF"
@ -518,34 +474,14 @@
"title": "Auto Split Pages", "title": "Auto Split Pages",
"desc": "Auto Split Scanned PDF with physical scanned page splitter QR Code" "desc": "Auto Split Scanned PDF with physical scanned page splitter QR Code"
}, },
"sanitizePdf": { "sanitize": {
"title": "Sanitise", "title": "Sanitise",
"desc": "Remove scripts and other elements from PDF files" "desc": "Remove potentially harmful elements from PDF files"
},
"URLToPDF": {
"title": "URL/Website To PDF",
"desc": "Converts any http(s)URL to PDF"
},
"HTMLToPDF": {
"title": "HTML to PDF",
"desc": "Converts any HTML file or zip to PDF"
},
"MarkdownToPDF": {
"title": "Markdown to PDF",
"desc": "Converts any Markdown file to PDF"
},
"PDFToMarkdown": {
"title": "PDF to Markdown",
"desc": "Converts any PDF to Markdown"
}, },
"getPdfInfo": { "getPdfInfo": {
"title": "Get ALL Info on PDF", "title": "Get ALL Info on PDF",
"desc": "Grabs any and all information possible on PDFs" "desc": "Grabs any and all information possible on PDFs"
}, },
"pageExtracter": {
"title": "Extract page(s)",
"desc": "Extracts select pages from PDF"
},
"pdfToSinglePage": { "pdfToSinglePage": {
"title": "PDF to Single Large Page", "title": "PDF to Single Large Page",
"desc": "Merges all PDF pages into one large single page" "desc": "Merges all PDF pages into one large single page"
@ -562,14 +498,6 @@
"title": "Manual Redaction", "title": "Manual Redaction",
"desc": "Redacts a PDF based on selected text, drawn shapes and/or selected page(s)" "desc": "Redacts a PDF based on selected text, drawn shapes and/or selected page(s)"
}, },
"PDFToCSV": {
"title": "PDF to CSV",
"desc": "Extracts Tables from a PDF converting it to CSV"
},
"split-by-size-or-count": {
"title": "Auto Split by Size/Count",
"desc": "Split a single PDF into multiple documents based on size, page count, or document count"
},
"overlay-pdfs": { "overlay-pdfs": {
"title": "Overlay PDFs", "title": "Overlay PDFs",
"desc": "Overlays PDFs on-top of another PDF" "desc": "Overlays PDFs on-top of another PDF"
@ -625,6 +553,54 @@
"reorganizePages": { "reorganizePages": {
"title": "Reorganize Pages", "title": "Reorganize Pages",
"desc": "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control." "desc": "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
},
"extractPages": {
"title": "Extract Pages",
"desc": "Extract specific pages from a PDF document"
},
"removePages": {
"title": "Remove Pages",
"desc": "Remove specific pages from a PDF document"
},
"removeImagePdf": {
"title": "Remove Image",
"desc": "Remove images from PDF documents"
},
"autoSizeSplitPDF": {
"title": "Auto Split by Size/Count",
"desc": "Automatically split PDFs by file size or page count"
},
"adjust-contrast": {
"title": "Adjust Colours/Contrast",
"desc": "Adjust colours and contrast of PDF documents"
},
"replaceColorPdf": {
"title": "Replace & Invert Colour",
"desc": "Replace or invert colours in PDF documents"
},
"devApi": {
"title": "API",
"desc": "Link to API documentation"
},
"devFolderScanning": {
"title": "Automated Folder Scanning",
"desc": "Link to automated folder scanning guide"
},
"devSsoGuide": {
"title": "SSO Guide",
"desc": "Link to SSO guide"
},
"devAirgapped": {
"title": "Air-gapped Setup",
"desc": "Link to air-gapped setup guide"
},
"addPassword": {
"title": "Add Password",
"desc": "Add password protection and restrictions to PDF files"
},
"changePermissions": {
"title": "Change Permissions",
"desc": "Change document restrictions and permissions"
} }
}, },
"viewPdf": { "viewPdf": {

View File

@ -1,7 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Button, Stack, Text, NumberInput, Select, Divider } from "@mantine/core"; import { Button, Stack, Text, NumberInput, Select, Divider } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { CompressParameters } from "../../../hooks/tools/compress/useCompressOperation"; import { CompressParameters } from "../../../hooks/tools/compress/useCompressParameters";
interface CompressSettingsProps { interface CompressSettingsProps {
parameters: CompressParameters; parameters: CompressParameters;

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Stack, Text, Checkbox } from '@mantine/core'; import { Stack, Text, Checkbox } from '@mantine/core';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { OCRParameters } from './OCRSettings'; import { OCRParameters } from '../../../hooks/tools/ocr/useOCRParameters';
export interface AdvancedOCRParameters { export interface AdvancedOCRParameters {
advancedOptions: string[]; advancedOptions: string[];

View File

@ -2,13 +2,7 @@ import React from 'react';
import { Stack, Select, Text, Divider } from '@mantine/core'; import { Stack, Select, Text, Divider } from '@mantine/core';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import LanguagePicker from './LanguagePicker'; import LanguagePicker from './LanguagePicker';
import { OCRParameters } from '../../../hooks/tools/ocr/useOCRParameters';
export interface OCRParameters {
languages: string[];
ocrType: string;
ocrRenderType: string;
additionalOptions: string[];
}
interface OCRSettingsProps { interface OCRSettingsProps {
parameters: OCRParameters; parameters: OCRParameters;

View File

@ -1,17 +1,3 @@
export interface AddWatermarkParameters {
watermarkType?: 'text' | 'image';
watermarkText: string;
watermarkImage?: File;
fontSize: number; // Used for both text size and image size
rotation: number;
opacity: number;
widthSpacer: number;
heightSpacer: number;
alphabet: string;
customColor: string;
convertPDFToImage: boolean;
}
export interface AlphabetOption { export interface AlphabetOption {
value: string; value: string;
label: string; label: string;
@ -25,16 +11,3 @@ export const alphabetOptions: AlphabetOption[] = [
{ value: "chinese", label: "简体中文" }, { value: "chinese", label: "简体中文" },
{ value: "thai", label: "ไทย" }, { value: "thai", label: "ไทย" },
]; ];
export const defaultWatermarkParameters: AddWatermarkParameters = {
watermarkType: undefined,
watermarkText: '',
fontSize: 12,
rotation: 0,
opacity: 50,
widthSpacer: 50,
heightSpacer: 50,
alphabet: 'roman',
customColor: '#d3d3d3',
convertPDFToImage: false
};

View File

@ -1,6 +1,7 @@
import { type TFunction } from 'i18next'; import { type TFunction } from 'i18next';
import React from 'react'; import React from 'react';
import { ToolOperationHook } from '../hooks/tools/shared/useToolOperation'; import { ToolOperationHook } from '../hooks/tools/shared/useToolOperation';
import { BaseToolProps } from '../types/tool';
export enum SubcategoryId { export enum SubcategoryId {
SIGNING = 'signing', SIGNING = 'signing',
@ -25,7 +26,7 @@ export enum ToolCategory {
export type ToolRegistryEntry = { export type ToolRegistryEntry = {
icon: React.ReactNode; icon: React.ReactNode;
name: string; name: string;
component: React.ComponentType<any> | null; component: React.ComponentType<BaseToolProps> | null;
view: 'sign' | 'security' | 'format' | 'extract' | 'view' | 'merge' | 'pageEditor' | 'convert' | 'redact' | 'split' | 'convert' | 'remove' | 'compress' | 'external'; view: 'sign' | 'security' | 'format' | 'extract' | 'view' | 'merge' | 'pageEditor' | 'convert' | 'redact' | 'split' | 'convert' | 'remove' | 'compress' | 'external';
description: string; description: string;
category: ToolCategory; category: ToolCategory;

View File

@ -15,13 +15,13 @@ import SingleLargePage from '../tools/SingleLargePage';
import UnlockPdfForms from '../tools/UnlockPdfForms'; import UnlockPdfForms from '../tools/UnlockPdfForms';
import RemoveCertificateSign from '../tools/RemoveCertificateSign'; import RemoveCertificateSign from '../tools/RemoveCertificateSign';
const showPlaceholderTools = false; // For development purposes. Allows seeing the full list of tools, even if they're unimplemented
// Hook to get the translated tool registry // Hook to get the translated tool registry
export function useFlatToolRegistry(): ToolRegistry { export function useFlatToolRegistry(): ToolRegistry {
const { t } = useTranslation(); const { t } = useTranslation();
return useMemo(() => ({ const allTools: ToolRegistry = {
// Signing // Signing
"certSign": { "certSign": {
@ -57,7 +57,7 @@ export function useFlatToolRegistry(): ToolRegistry {
maxFiles: -1, maxFiles: -1,
endpoints: ["add-password"] endpoints: ["add-password"]
}, },
"add-watermark": { "watermark": {
icon: <span className="material-symbols-rounded">branding_watermark</span>, icon: <span className="material-symbols-rounded">branding_watermark</span>,
name: t("home.watermark.title", "Add Watermark"), name: t("home.watermark.title", "Add Watermark"),
component: AddWatermark, component: AddWatermark,
@ -217,12 +217,12 @@ export function useFlatToolRegistry(): ToolRegistry {
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.PAGE_FORMATTING subcategory: SubcategoryId.PAGE_FORMATTING
}, },
"add-page-numbers": { "addPageNumbers": {
icon: <span className="material-symbols-rounded">123</span>, icon: <span className="material-symbols-rounded">123</span>,
name: t("home.add-page-numbers.title", "Add Page Numbers"), name: t("home.addPageNumbers.title", "Add Page Numbers"),
component: null, component: null,
view: "format", view: "format",
description: t("home.add-page-numbers.desc", "Add Page numbers throughout a document in a set location"), description: t("home.addPageNumbers.desc", "Add Page numbers throughout a document in a set location"),
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.PAGE_FORMATTING subcategory: SubcategoryId.PAGE_FORMATTING
}, },
@ -237,10 +237,10 @@ export function useFlatToolRegistry(): ToolRegistry {
}, },
"single-large-page": { "single-large-page": {
icon: <span className="material-symbols-rounded">looks_one</span>, icon: <span className="material-symbols-rounded">looks_one</span>,
name: t("home.PdfToSinglePage.title", "PDF to Single Large Page"), name: t("home.pdfToSinglePage.title", "PDF to Single Large Page"),
component: SingleLargePage, component: SingleLargePage,
view: "format", view: "format",
description: t("home.PdfToSinglePage.desc", "Merges all PDF pages into one large single page"), description: t("home.pdfToSinglePage.desc", "Merges all PDF pages into one large single page"),
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.PAGE_FORMATTING, subcategory: SubcategoryId.PAGE_FORMATTING,
maxFiles: -1, maxFiles: -1,
@ -259,12 +259,12 @@ export function useFlatToolRegistry(): ToolRegistry {
// Extraction // Extraction
"extract-pages": { "extractPages": {
icon: <span className="material-symbols-rounded">upload</span>, icon: <span className="material-symbols-rounded">upload</span>,
name: t("home.extractPage.title", "Extract Pages"), name: t("home.extractPages.title", "Extract Pages"),
component: null, component: null,
view: "extract", view: "extract",
description: t("home.extractPage.desc", "Extract specific pages from a PDF document"), description: t("home.extractPages.desc", "Extract specific pages from a PDF document"),
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.EXTRACTION subcategory: SubcategoryId.EXTRACTION
}, },
@ -281,7 +281,7 @@ export function useFlatToolRegistry(): ToolRegistry {
// Removal // Removal
"remove": { "removePages": {
icon: <span className="material-symbols-rounded">delete</span>, icon: <span className="material-symbols-rounded">delete</span>,
name: t("home.removePages.title", "Remove Pages"), name: t("home.removePages.title", "Remove Pages"),
component: null, component: null,
@ -331,10 +331,10 @@ export function useFlatToolRegistry(): ToolRegistry {
}, },
"remove-certificate-sign": { "remove-certificate-sign": {
icon: <span className="material-symbols-rounded">remove_moderator</span>, icon: <span className="material-symbols-rounded">remove_moderator</span>,
name: t("home.removeCertSign.title", "Remove Certificate Signatures"), name: t("home.removeCertSign.title", "Remove Certificate Sign"),
component: RemoveCertificateSign, component: RemoveCertificateSign,
view: "security", view: "security",
description: t("home.removeCertSign.desc", "Remove digital signatures from PDF documents"), description: t("home.removeCertSign.desc", "Remove digital signature from PDF documents"),
category: ToolCategory.STANDARD_TOOLS, category: ToolCategory.STANDARD_TOOLS,
subcategory: SubcategoryId.REMOVAL, subcategory: SubcategoryId.REMOVAL,
maxFiles: -1, maxFiles: -1,
@ -386,12 +386,12 @@ export function useFlatToolRegistry(): ToolRegistry {
// Advanced Formatting // Advanced Formatting
"adjust-colors-contrast": { "adjustContrast": {
icon: <span className="material-symbols-rounded">palette</span>, icon: <span className="material-symbols-rounded">palette</span>,
name: t("home.adjust-contrast.title", "Adjust Colors/Contrast"), name: t("home.adjustContrast.title", "Adjust Colors/Contrast"),
component: null, component: null,
view: "format", view: "format",
description: t("home.adjust-contrast.desc", "Adjust colors and contrast of PDF documents"), description: t("home.adjustContrast.desc", "Adjust colors and contrast of PDF documents"),
category: ToolCategory.ADVANCED_TOOLS, category: ToolCategory.ADVANCED_TOOLS,
subcategory: SubcategoryId.ADVANCED_FORMATTING subcategory: SubcategoryId.ADVANCED_FORMATTING
}, },
@ -525,22 +525,22 @@ export function useFlatToolRegistry(): ToolRegistry {
category: ToolCategory.RECOMMENDED_TOOLS, category: ToolCategory.RECOMMENDED_TOOLS,
subcategory: SubcategoryId.GENERAL subcategory: SubcategoryId.GENERAL
}, },
"compressPdfs": { "compress": {
icon: <span className="material-symbols-rounded">zoom_in_map</span>, icon: <span className="material-symbols-rounded">zoom_in_map</span>,
name: t("home.compressPdfs.title", "Compress"), name: t("home.compress.title", "Compress"),
component: CompressPdfPanel, component: CompressPdfPanel,
view: "compress", view: "compress",
description: t("home.compressPdfs.desc", "Compress PDFs to reduce their file size."), description: t("home.compress.desc", "Compress PDFs to reduce their file size."),
category: ToolCategory.RECOMMENDED_TOOLS, category: ToolCategory.RECOMMENDED_TOOLS,
subcategory: SubcategoryId.GENERAL, subcategory: SubcategoryId.GENERAL,
maxFiles: -1 maxFiles: -1
}, },
"convert": { "convert": {
icon: <span className="material-symbols-rounded">sync_alt</span>, icon: <span className="material-symbols-rounded">sync_alt</span>,
name: t("home.fileToPDF.title", "Convert"), name: t("home.convert.title", "Convert"),
component: ConvertPanel, component: ConvertPanel,
view: "convert", view: "convert",
description: t("home.fileToPDF.desc", "Convert files to and from PDF format"), description: t("home.convert.desc", "Convert files to and from PDF format"),
category: ToolCategory.RECOMMENDED_TOOLS, category: ToolCategory.RECOMMENDED_TOOLS,
subcategory: SubcategoryId.GENERAL, subcategory: SubcategoryId.GENERAL,
maxFiles: -1, maxFiles: -1,
@ -618,5 +618,17 @@ export function useFlatToolRegistry(): ToolRegistry {
category: ToolCategory.RECOMMENDED_TOOLS, category: ToolCategory.RECOMMENDED_TOOLS,
subcategory: SubcategoryId.GENERAL subcategory: SubcategoryId.GENERAL
}, },
}), [t]); };
if (showPlaceholderTools) {
return allTools;
} else {
const filteredTools = Object.keys(allTools)
.filter(key => allTools[key].component !== null || allTools[key].link)
.reduce((obj, key) => {
obj[key] = allTools[key];
return obj;
}, {} as ToolRegistry);
return filteredTools;
}
} }

View File

@ -1,7 +1,8 @@
import { useState } from 'react';
import { ChangePermissionsParameters, ChangePermissionsParametersHook, useChangePermissionsParameters } from '../changePermissions/useChangePermissionsParameters'; import { ChangePermissionsParameters, ChangePermissionsParametersHook, useChangePermissionsParameters } from '../changePermissions/useChangePermissionsParameters';
import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface AddPasswordParameters { export interface AddPasswordParameters extends BaseParameters {
password: string; password: string;
ownerPassword: string; ownerPassword: string;
keyLength: number; keyLength: number;
@ -11,14 +12,9 @@ export interface AddPasswordFullParameters extends AddPasswordParameters {
permissions: ChangePermissionsParameters; permissions: ChangePermissionsParameters;
} }
export interface AddPasswordParametersHook { export interface AddPasswordParametersHook extends BaseParametersHook<AddPasswordParameters> {
fullParameters: AddPasswordFullParameters; fullParameters: AddPasswordFullParameters;
parameters: AddPasswordParameters;
permissions: ChangePermissionsParametersHook; permissions: ChangePermissionsParametersHook;
updateParameter: <K extends keyof AddPasswordParameters>(parameter: K, value: AddPasswordParameters[K]) => void;
resetParameters: () => void;
validateParameters: () => boolean;
getEndpointName: () => string;
} }
export const defaultParameters: AddPasswordParameters = { export const defaultParameters: AddPasswordParameters = {
@ -28,42 +24,31 @@ export const defaultParameters: AddPasswordParameters = {
}; };
export const useAddPasswordParameters = (): AddPasswordParametersHook => { export const useAddPasswordParameters = (): AddPasswordParametersHook => {
const [parameters, setParameters] = useState<AddPasswordParameters>(defaultParameters);
const permissions = useChangePermissionsParameters(); const permissions = useChangePermissionsParameters();
const baseHook = useBaseParameters({
defaultParameters,
endpointName: 'add-password',
validateFn: () => {
// No required parameters for Add Password. Defer to permissions validation.
return permissions.validateParameters();
},
});
const fullParameters: AddPasswordFullParameters = { const fullParameters: AddPasswordFullParameters = {
...parameters, ...baseHook.parameters,
permissions: permissions.parameters, permissions: permissions.parameters,
}; };
const updateParameter = <K extends keyof AddPasswordParameters>(parameter: K, value: AddPasswordParameters[K]) => {
setParameters(prev => ({
...prev,
[parameter]: value,
})
);
};
const resetParameters = () => { const resetParameters = () => {
setParameters(defaultParameters); baseHook.resetParameters();
permissions.resetParameters(); permissions.resetParameters();
}; };
const validateParameters = () => {
// No required parameters for Add Password. Defer to permissions validation.
return permissions.validateParameters();
};
const getEndpointName = () => {
return 'add-password';
};
return { return {
...baseHook,
fullParameters, fullParameters,
parameters,
permissions, permissions,
updateParameter,
resetParameters, resetParameters,
validateParameters,
getEndpointName,
}; };
}; };

View File

@ -1,35 +1,48 @@
import { useState, useCallback } from 'react'; import { BaseParameters } from '../../../types/parameters';
import { defaultWatermarkParameters, AddWatermarkParameters } from '../../../constants/addWatermarkConstants'; import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export const useAddWatermarkParameters = () => { export interface AddWatermarkParameters extends BaseParameters {
const [parameters, setParameters] = useState<AddWatermarkParameters>(defaultWatermarkParameters); watermarkType?: 'text' | 'image';
watermarkText: string;
watermarkImage?: File;
fontSize: number; // Used for both text size and image size
rotation: number;
opacity: number;
widthSpacer: number;
heightSpacer: number;
alphabet: string;
customColor: string;
convertPDFToImage: boolean;
}
const updateParameter = useCallback(<K extends keyof AddWatermarkParameters>( export const defaultParameters: AddWatermarkParameters = {
key: K, watermarkType: undefined,
value: AddWatermarkParameters[K] watermarkText: '',
) => { fontSize: 12,
setParameters(prev => ({ ...prev, [key]: value })); rotation: 0,
}, []); opacity: 50,
widthSpacer: 50,
const resetParameters = useCallback(() => { heightSpacer: 50,
setParameters(defaultWatermarkParameters); alphabet: 'roman',
}, []); customColor: '#d3d3d3',
convertPDFToImage: false
const validateParameters = useCallback((): boolean => { };
if (!parameters.watermarkType) {
return false; export type AddWatermarkParametersHook = BaseParametersHook<AddWatermarkParameters>;
}
if (parameters.watermarkType === 'text') { export const useAddWatermarkParameters = (): AddWatermarkParametersHook => {
return parameters.watermarkText.trim().length > 0; return useBaseParameters({
} else { defaultParameters: defaultParameters,
return parameters.watermarkImage !== undefined; endpointName: 'add-watermark',
} validateFn: (params): boolean => {
}, [parameters]); if (!params.watermarkType) {
return false;
return { }
parameters, if (params.watermarkType === 'text') {
updateParameter, return params.watermarkText.trim().length > 0;
resetParameters, } else {
validateParameters return params.watermarkImage !== undefined;
}; }
},
});
}; };

View File

@ -1,6 +1,7 @@
import { useState } from 'react'; import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface ChangePermissionsParameters { export interface ChangePermissionsParameters extends BaseParameters {
preventAssembly: boolean; preventAssembly: boolean;
preventExtractContent: boolean; preventExtractContent: boolean;
preventExtractForAccessibility: boolean; preventExtractForAccessibility: boolean;
@ -11,14 +12,6 @@ export interface ChangePermissionsParameters {
preventPrintingFaithful: boolean; preventPrintingFaithful: boolean;
} }
export interface ChangePermissionsParametersHook {
parameters: ChangePermissionsParameters;
updateParameter: (parameter: keyof ChangePermissionsParameters, value: boolean) => void;
resetParameters: () => void;
validateParameters: () => boolean;
getEndpointName: () => string;
}
export const defaultParameters: ChangePermissionsParameters = { export const defaultParameters: ChangePermissionsParameters = {
preventAssembly: false, preventAssembly: false,
preventExtractContent: false, preventExtractContent: false,
@ -30,35 +23,11 @@ export const defaultParameters: ChangePermissionsParameters = {
preventPrintingFaithful: false, preventPrintingFaithful: false,
}; };
export type ChangePermissionsParametersHook = BaseParametersHook<ChangePermissionsParameters>;
export const useChangePermissionsParameters = (): ChangePermissionsParametersHook => { export const useChangePermissionsParameters = (): ChangePermissionsParametersHook => {
const [parameters, setParameters] = useState<ChangePermissionsParameters>(defaultParameters); return useBaseParameters({
defaultParameters,
const updateParameter = <K extends keyof ChangePermissionsParameters>(parameter: K, value: ChangePermissionsParameters[K]) => { endpointName: 'add-password', // Change Permissions is a fake endpoint for the Add Password tool
setParameters(prev => ({ });
...prev,
[parameter]: value,
})
);
};
const resetParameters = () => {
setParameters(defaultParameters);
};
const validateParameters = () => {
// Always valid - any combination of permissions is allowed
return true;
};
const getEndpointName = () => {
return 'add-password'; // Change Permissions is a fake endpoint for the Add Password tool
};
return {
parameters,
updateParameter,
resetParameters,
validateParameters,
getEndpointName,
};
}; };

View File

@ -1,15 +1,7 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useToolOperation, ToolOperationConfig } from '../shared/useToolOperation'; import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { CompressParameters } from './useCompressParameters';
export interface CompressParameters {
compressionLevel: number;
grayscale: boolean;
expectedSize: string;
compressionMethod: 'quality' | 'filesize';
fileSizeValue: string;
fileSizeUnit: 'KB' | 'MB';
}
const buildFormData = (parameters: CompressParameters, file: File): FormData => { const buildFormData = (parameters: CompressParameters, file: File): FormData => {
const formData = new FormData(); const formData = new FormData();

View File

@ -1,15 +1,16 @@
import { useState } from 'react'; import { BaseParameters } from '../../../types/parameters';
import { CompressParameters } from './useCompressOperation'; import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface CompressParametersHook { export interface CompressParameters extends BaseParameters {
parameters: CompressParameters; compressionLevel: number;
updateParameter: (parameter: keyof CompressParameters, value: string | boolean | number) => void; grayscale: boolean;
resetParameters: () => void; expectedSize: string;
validateParameters: () => boolean; compressionMethod: 'quality' | 'filesize';
getEndpointName: () => string; fileSizeValue: string;
fileSizeUnit: 'KB' | 'MB';
} }
export const initialParameters: CompressParameters = { const defaultParameters: CompressParameters = {
compressionLevel: 5, compressionLevel: 5,
grayscale: false, grayscale: false,
expectedSize: '', expectedSize: '',
@ -18,32 +19,15 @@ export const initialParameters: CompressParameters = {
fileSizeUnit: 'MB', fileSizeUnit: 'MB',
}; };
export type CompressParametersHook = BaseParametersHook<CompressParameters>;
export const useCompressParameters = (): CompressParametersHook => { export const useCompressParameters = (): CompressParametersHook => {
const [parameters, setParameters] = useState<CompressParameters>(initialParameters); return useBaseParameters({
defaultParameters,
const updateParameter = (parameter: keyof CompressParameters, value: string | boolean | number) => { endpointName: 'compress-pdf',
setParameters(prev => ({ ...prev, [parameter]: value })); validateFn: (params) => {
}; // For compression, we only need to validate that compression level is within range
return params.compressionLevel >= 1 && params.compressionLevel <= 9;
const resetParameters = () => { },
setParameters(initialParameters); });
};
const validateParameters = () => {
// For compression, we only need to validate that compression level is within range
// and that at least one file is selected (at least, I think that's all we need to do here)
return parameters.compressionLevel >= 1 && parameters.compressionLevel <= 9;
};
const getEndpointName = () => {
return 'compress-pdf';
};
return {
parameters,
updateParameter,
resetParameters,
validateParameters,
getEndpointName,
};
}; };

View File

@ -5,6 +5,7 @@
import { describe, test, expect } from 'vitest'; import { describe, test, expect } from 'vitest';
import { renderHook, act } from '@testing-library/react'; import { renderHook, act } from '@testing-library/react';
import { useConvertParameters } from './useConvertParameters'; import { useConvertParameters } from './useConvertParameters';
import { FIT_OPTIONS } from '../../../constants/convertConstants';
describe('useConvertParameters', () => { describe('useConvertParameters', () => {
@ -44,13 +45,19 @@ describe('useConvertParameters', () => {
result.current.updateParameter('imageOptions', { result.current.updateParameter('imageOptions', {
colorType: 'grayscale', colorType: 'grayscale',
dpi: 150, dpi: 150,
singleOrMultiple: 'single' singleOrMultiple: 'single',
fitOption: FIT_OPTIONS.FILL_PAGE,
autoRotate: false,
combineImages: false,
}); });
}); });
expect(result.current.parameters.imageOptions.colorType).toBe('grayscale'); expect(result.current.parameters.imageOptions.colorType).toBe('grayscale');
expect(result.current.parameters.imageOptions.dpi).toBe(150); expect(result.current.parameters.imageOptions.dpi).toBe(150);
expect(result.current.parameters.imageOptions.singleOrMultiple).toBe('single'); expect(result.current.parameters.imageOptions.singleOrMultiple).toBe('single');
expect(result.current.parameters.imageOptions.fitOption).toBe(FIT_OPTIONS.FILL_PAGE);
expect(result.current.parameters.imageOptions.autoRotate).toBe(false);
expect(result.current.parameters.imageOptions.combineImages).toBe(false);
}); });
test('should update nested HTML options', () => { test('should update nested HTML options', () => {

View File

@ -1,4 +1,3 @@
import { useState, useEffect } from 'react';
import { import {
COLOR_TYPES, COLOR_TYPES,
OUTPUT_OPTIONS, OUTPUT_OPTIONS,
@ -11,8 +10,10 @@ import {
} from '../../../constants/convertConstants'; } from '../../../constants/convertConstants';
import { getEndpointName as getEndpointNameUtil, getEndpointUrl, isImageFormat, isWebFormat } from '../../../utils/convertUtils'; import { getEndpointName as getEndpointNameUtil, getEndpointUrl, isImageFormat, isWebFormat } from '../../../utils/convertUtils';
import { detectFileExtension as detectFileExtensionUtil } from '../../../utils/fileUtils'; import { detectFileExtension as detectFileExtensionUtil } from '../../../utils/fileUtils';
import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface ConvertParameters { export interface ConvertParameters extends BaseParameters {
fromExtension: string; fromExtension: string;
toExtension: string; toExtension: string;
imageOptions: { imageOptions: {
@ -39,18 +40,13 @@ export interface ConvertParameters {
smartDetectionType: 'mixed' | 'images' | 'web' | 'none'; smartDetectionType: 'mixed' | 'images' | 'web' | 'none';
} }
export interface ConvertParametersHook { export interface ConvertParametersHook extends BaseParametersHook<ConvertParameters> {
parameters: ConvertParameters;
updateParameter: (parameter: keyof ConvertParameters, value: any) => void;
resetParameters: () => void;
validateParameters: () => boolean;
getEndpointName: () => string;
getEndpoint: () => string; getEndpoint: () => string;
getAvailableToExtensions: (fromExtension: string) => Array<{value: string, label: string, group: string}>; getAvailableToExtensions: (fromExtension: string) => Array<{value: string, label: string, group: string}>;
analyzeFileTypes: (files: Array<{name: string}>) => void; analyzeFileTypes: (files: Array<{name: string}>) => void;
} }
const initialParameters: ConvertParameters = { const defaultParameters: ConvertParameters = {
fromExtension: '', fromExtension: '',
toExtension: '', toExtension: '',
imageOptions: { imageOptions: {
@ -77,66 +73,62 @@ const initialParameters: ConvertParameters = {
smartDetectionType: 'none', smartDetectionType: 'none',
}; };
export const useConvertParameters = (): ConvertParametersHook => { const validateParameters = (params: ConvertParameters): boolean => {
const [parameters, setParameters] = useState<ConvertParameters>(initialParameters); const { fromExtension, toExtension } = params;
const updateParameter = (parameter: keyof ConvertParameters, value: any) => { if (!fromExtension || !toExtension) return false;
setParameters(prev => ({ ...prev, [parameter]: value }));
};
const resetParameters = () => { // Handle dynamic format identifiers (file-<extension>)
setParameters(initialParameters); let supportedToExtensions: string[] = [];
}; if (fromExtension.startsWith('file-')) {
// Dynamic format - use 'any' conversion options
supportedToExtensions = CONVERSION_MATRIX['any'] || [];
} else {
// Regular format - check conversion matrix
supportedToExtensions = CONVERSION_MATRIX[fromExtension] || [];
}
const validateParameters = () => { if (!supportedToExtensions.includes(toExtension)) {
const { fromExtension, toExtension } = parameters; return false;
}
if (!fromExtension || !toExtension) return false; return true;
};
// Handle dynamic format identifiers (file-<extension>) const getEndpointName = (params: ConvertParameters): string => {
let supportedToExtensions: string[] = []; const { fromExtension, toExtension, isSmartDetection, smartDetectionType } = params;
if (fromExtension.startsWith('file-')) {
// Dynamic format - use 'any' conversion options
supportedToExtensions = CONVERSION_MATRIX['any'] || [];
} else {
// Regular format - check conversion matrix
supportedToExtensions = CONVERSION_MATRIX[fromExtension] || [];
}
if (!supportedToExtensions.includes(toExtension)) { if (isSmartDetection) {
return false; if (smartDetectionType === 'mixed') {
} // Mixed file types -> PDF using file-to-pdf endpoint
return true;
};
const getEndpointName = () => {
const { fromExtension, toExtension, isSmartDetection, smartDetectionType } = parameters;
if (isSmartDetection) {
if (smartDetectionType === 'mixed') {
// Mixed file types -> PDF using file-to-pdf endpoint
return 'file-to-pdf';
} else if (smartDetectionType === 'images') {
// All images -> PDF using img-to-pdf endpoint
return 'img-to-pdf';
} else if (smartDetectionType === 'web') {
// All web files -> PDF using html-to-pdf endpoint
return 'html-to-pdf';
}
}
// Handle dynamic format identifiers (file-<extension>)
if (fromExtension.startsWith('file-')) {
// Dynamic format - use file-to-pdf endpoint
return 'file-to-pdf'; return 'file-to-pdf';
} else if (smartDetectionType === 'images') {
// All images -> PDF using img-to-pdf endpoint
return 'img-to-pdf';
} else if (smartDetectionType === 'web') {
// All web files -> PDF using html-to-pdf endpoint
return 'html-to-pdf';
} }
}
return getEndpointNameUtil(fromExtension, toExtension); // Handle dynamic format identifiers (file-<extension>)
}; if (fromExtension.startsWith('file-')) {
// Dynamic format - use file-to-pdf endpoint
return 'file-to-pdf';
}
return getEndpointNameUtil(fromExtension, toExtension);
};
export const useConvertParameters = (): ConvertParametersHook => {
const baseHook = useBaseParameters({
defaultParameters,
endpointName: getEndpointName,
validateFn: validateParameters,
});
const getEndpoint = () => { const getEndpoint = () => {
const { fromExtension, toExtension, isSmartDetection, smartDetectionType } = parameters; const { fromExtension, toExtension, isSmartDetection, smartDetectionType } = baseHook.parameters;
if (isSmartDetection) { if (isSmartDetection) {
if (smartDetectionType === 'mixed') { if (smartDetectionType === 'mixed') {
@ -189,7 +181,7 @@ export const useConvertParameters = (): ConvertParametersHook => {
const analyzeFileTypes = (files: Array<{name: string}>) => { const analyzeFileTypes = (files: Array<{name: string}>) => {
if (files.length === 0) { if (files.length === 0) {
// No files - only reset smart detection, keep user's format choices // No files - only reset smart detection, keep user's format choices
setParameters(prev => ({ baseHook.setParameters(prev => ({
...prev, ...prev,
isSmartDetection: false, isSmartDetection: false,
smartDetectionType: 'none' smartDetectionType: 'none'
@ -215,7 +207,7 @@ export const useConvertParameters = (): ConvertParametersHook => {
availableTargets = CONVERSION_MATRIX['any'] || []; availableTargets = CONVERSION_MATRIX['any'] || [];
} }
setParameters(prev => { baseHook.setParameters(prev => {
// Check if current toExtension is still valid for the new fromExtension // Check if current toExtension is still valid for the new fromExtension
const currentToExt = prev.toExtension; const currentToExt = prev.toExtension;
const isCurrentToExtValid = availableTargets.includes(currentToExt); const isCurrentToExtValid = availableTargets.includes(currentToExt);
@ -256,7 +248,7 @@ export const useConvertParameters = (): ConvertParametersHook => {
availableTargets = CONVERSION_MATRIX['any'] || []; availableTargets = CONVERSION_MATRIX['any'] || [];
} }
setParameters(prev => { baseHook.setParameters(prev => {
// Check if current toExtension is still valid for the new fromExtension // Check if current toExtension is still valid for the new fromExtension
const currentToExt = prev.toExtension; const currentToExt = prev.toExtension;
const isCurrentToExtValid = availableTargets.includes(currentToExt); const isCurrentToExtValid = availableTargets.includes(currentToExt);
@ -285,7 +277,7 @@ export const useConvertParameters = (): ConvertParametersHook => {
if (allImages) { if (allImages) {
// All files are images - use image-to-pdf conversion // All files are images - use image-to-pdf conversion
setParameters(prev => ({ baseHook.setParameters(prev => ({
...prev, ...prev,
isSmartDetection: true, isSmartDetection: true,
smartDetectionType: 'images', smartDetectionType: 'images',
@ -294,7 +286,7 @@ export const useConvertParameters = (): ConvertParametersHook => {
})); }));
} else if (allWeb) { } else if (allWeb) {
// All files are web files - use html-to-pdf conversion // All files are web files - use html-to-pdf conversion
setParameters(prev => ({ baseHook.setParameters(prev => ({
...prev, ...prev,
isSmartDetection: true, isSmartDetection: true,
smartDetectionType: 'web', smartDetectionType: 'web',
@ -303,7 +295,7 @@ export const useConvertParameters = (): ConvertParametersHook => {
})); }));
} else { } else {
// Mixed non-image types - use file-to-pdf conversion // Mixed non-image types - use file-to-pdf conversion
setParameters(prev => ({ baseHook.setParameters(prev => ({
...prev, ...prev,
isSmartDetection: true, isSmartDetection: true,
smartDetectionType: 'mixed', smartDetectionType: 'mixed',
@ -315,11 +307,7 @@ export const useConvertParameters = (): ConvertParametersHook => {
}; };
return { return {
parameters, ...baseHook,
updateParameter,
resetParameters,
validateParameters,
getEndpointName,
getEndpoint, getEndpoint,
getAvailableToExtensions, getAvailableToExtensions,
analyzeFileTypes, analyzeFileTypes,

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 '../../../components/tools/ocr/OCRSettings'; import { OCRParameters } 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';

View File

@ -1,13 +1,15 @@
import { useState } from 'react'; import { BaseParameters } from '../../../types/parameters';
import { OCRParameters } from '../../../components/tools/ocr/OCRSettings'; import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface OCRParametersHook { export interface OCRParameters extends BaseParameters {
parameters: OCRParameters; languages: string[];
updateParameter: (key: keyof OCRParameters, value: any) => void; ocrType: string;
resetParameters: () => void; ocrRenderType: string;
validateParameters: () => boolean; additionalOptions: string[];
} }
export type OCRParametersHook = BaseParametersHook<OCRParameters>;
const defaultParameters: OCRParameters = { const defaultParameters: OCRParameters = {
languages: [], languages: [],
ocrType: 'skip-text', ocrType: 'skip-text',
@ -16,28 +18,12 @@ const defaultParameters: OCRParameters = {
}; };
export const useOCRParameters = (): OCRParametersHook => { export const useOCRParameters = (): OCRParametersHook => {
const [parameters, setParameters] = useState<OCRParameters>(defaultParameters); return useBaseParameters({
defaultParameters,
const updateParameter = (key: keyof OCRParameters, value: any) => { endpointName: 'ocr-pdf',
setParameters(prev => ({ validateFn: (params) => {
...prev, // At minimum, we need at least one language selected
[key]: value return params.languages.length > 0;
})); },
}; });
const resetParameters = () => {
setParameters(defaultParameters);
};
const validateParameters = () => {
// At minimum, we need at least one language selected
return parameters.languages.length > 0;
};
return {
parameters,
updateParameter,
resetParameters,
validateParameters,
};
}; };

View File

@ -1,49 +1,22 @@
import { useState } from 'react'; import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface RemovePasswordParameters { export interface RemovePasswordParameters extends BaseParameters {
password: string; password: string;
} }
export interface RemovePasswordParametersHook { export type RemovePasswordParametersHook = BaseParametersHook<RemovePasswordParameters>;
parameters: RemovePasswordParameters;
updateParameter: <K extends keyof RemovePasswordParameters>(parameter: K, value: RemovePasswordParameters[K]) => void;
resetParameters: () => void;
validateParameters: () => boolean;
getEndpointName: () => string;
}
export const defaultParameters: RemovePasswordParameters = { export const defaultParameters: RemovePasswordParameters = {
password: '', password: '',
}; };
export const useRemovePasswordParameters = (): RemovePasswordParametersHook => { export const useRemovePasswordParameters = (): RemovePasswordParametersHook => {
const [parameters, setParameters] = useState<RemovePasswordParameters>(defaultParameters); return useBaseParameters({
defaultParameters,
const updateParameter = <K extends keyof RemovePasswordParameters>(parameter: K, value: RemovePasswordParameters[K]) => { endpointName: 'remove-password',
setParameters(prev => ({ validateFn: (params) => {
...prev, return params.password !== '';
[parameter]: value, },
}) });
);
};
const resetParameters = () => {
setParameters(defaultParameters);
};
const validateParameters = () => {
return parameters.password !== '';
};
const getEndpointName = () => {
return 'remove-password';
};
return {
parameters,
updateParameter,
resetParameters,
validateParameters,
getEndpointName,
};
}; };

View File

@ -1,6 +1,7 @@
import { useState, useCallback } from 'react'; import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface SanitizeParameters { export interface SanitizeParameters extends BaseParameters {
removeJavaScript: boolean; removeJavaScript: boolean;
removeEmbeddedFiles: boolean; removeEmbeddedFiles: boolean;
removeXMPMetadata: boolean; removeXMPMetadata: boolean;
@ -18,36 +19,14 @@ export const defaultParameters: SanitizeParameters = {
removeFonts: false, removeFonts: false,
}; };
export const useSanitizeParameters = () => { export type SanitizeParametersHook = BaseParametersHook<SanitizeParameters>;
const [parameters, setParameters] = useState<SanitizeParameters>(defaultParameters);
const updateParameter = useCallback(<K extends keyof SanitizeParameters>( export const useSanitizeParameters = (): SanitizeParametersHook => {
key: K, return useBaseParameters({
value: SanitizeParameters[K] defaultParameters,
) => { endpointName: 'sanitize-pdf',
setParameters(prev => ({ validateFn: (params) => {
...prev, return Object.values(params).some(value => value === true);
[key]: value },
})); });
}, []);
const resetParameters = useCallback(() => {
setParameters(defaultParameters);
}, []);
const validateParameters = useCallback(() => {
return Object.values(parameters).some(value => value === true);
}, [parameters]);
const getEndpointName = () => {
return 'sanitize-pdf'
};
return {
parameters,
updateParameter,
resetParameters,
validateParameters,
getEndpointName,
};
}; };

View File

@ -1,7 +1,8 @@
import { useState, useCallback } from 'react'; import { useState, useCallback, Dispatch, SetStateAction } from 'react';
export interface BaseParametersHook<T> { export interface BaseParametersHook<T> {
parameters: T; parameters: T;
setParameters: Dispatch<SetStateAction<T>>;
updateParameter: <K extends keyof T>(parameter: K, value: T[K]) => void; updateParameter: <K extends keyof T>(parameter: K, value: T[K]) => void;
resetParameters: () => void; resetParameters: () => void;
validateParameters: () => boolean; validateParameters: () => boolean;
@ -10,7 +11,7 @@ export interface BaseParametersHook<T> {
export interface BaseParametersConfig<T> { export interface BaseParametersConfig<T> {
defaultParameters: T; defaultParameters: T;
endpointName: string; endpointName: string | ((params: T) => string);
validateFn?: (params: T) => boolean; validateFn?: (params: T) => boolean;
} }
@ -32,12 +33,21 @@ export function useBaseParameters<T>(config: BaseParametersConfig<T>): BaseParam
return config.validateFn ? config.validateFn(parameters) : true; return config.validateFn ? config.validateFn(parameters) : true;
}, [parameters, config.validateFn]); }, [parameters, config.validateFn]);
const getEndpointName = useCallback(() => { const endpointName = config.endpointName;
return config.endpointName; let getEndpointName: () => string;
}, [config.endpointName]); if (typeof endpointName === "string") {
getEndpointName = useCallback(() => {
return endpointName;
}, []);
} else {
getEndpointName = useCallback(() => {
return endpointName(parameters);
}, [parameters]);
}
return { return {
parameters, parameters,
setParameters,
updateParameter, updateParameter,
resetParameters, resetParameters,
validateParameters, validateParameters,

View File

@ -1,7 +1,8 @@
import { useState } from 'react';
import { SPLIT_MODES, SPLIT_TYPES, ENDPOINTS, type SplitMode, SplitType } from '../../../constants/splitConstants'; import { SPLIT_MODES, SPLIT_TYPES, ENDPOINTS, type SplitMode, SplitType } from '../../../constants/splitConstants';
import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface SplitParameters { export interface SplitParameters extends BaseParameters {
mode: SplitMode | ''; mode: SplitMode | '';
pages: string; pages: string;
hDiv: string; hDiv: string;
@ -14,15 +15,9 @@ export interface SplitParameters {
allowDuplicates: boolean; allowDuplicates: boolean;
} }
export interface SplitParametersHook { export type SplitParametersHook = BaseParametersHook<SplitParameters>;
parameters: SplitParameters;
updateParameter: (parameter: keyof SplitParameters, value: string | boolean) => void;
resetParameters: () => void;
validateParameters: () => boolean;
getEndpointName: () => string;
}
const initialParameters: SplitParameters = { const defaultParameters: SplitParameters = {
mode: '', mode: '',
pages: '', pages: '',
hDiv: '2', hDiv: '2',
@ -36,43 +31,27 @@ const initialParameters: SplitParameters = {
}; };
export const useSplitParameters = (): SplitParametersHook => { export const useSplitParameters = (): SplitParametersHook => {
const [parameters, setParameters] = useState<SplitParameters>(initialParameters); return useBaseParameters({
defaultParameters,
endpointName: (params) => {
if (!params.mode) return ENDPOINTS[SPLIT_MODES.BY_PAGES];
return ENDPOINTS[params.mode as SplitMode];
},
validateFn: (params) => {
if (!params.mode) return false;
const updateParameter = (parameter: keyof SplitParameters, value: string | boolean) => { switch (params.mode) {
setParameters(prev => ({ ...prev, [parameter]: value })); case SPLIT_MODES.BY_PAGES:
}; return params.pages.trim() !== "";
case SPLIT_MODES.BY_SECTIONS:
const resetParameters = () => { return params.hDiv !== "" && params.vDiv !== "";
setParameters(initialParameters); case SPLIT_MODES.BY_SIZE_OR_COUNT:
}; return params.splitValue.trim() !== "";
case SPLIT_MODES.BY_CHAPTERS:
const validateParameters = () => { return params.bookmarkLevel !== "";
if (!parameters.mode) return false; default:
return false;
switch (parameters.mode) { }
case SPLIT_MODES.BY_PAGES: },
return parameters.pages.trim() !== ""; });
case SPLIT_MODES.BY_SECTIONS:
return parameters.hDiv !== "" && parameters.vDiv !== "";
case SPLIT_MODES.BY_SIZE_OR_COUNT:
return parameters.splitValue.trim() !== "";
case SPLIT_MODES.BY_CHAPTERS:
return parameters.bookmarkLevel !== "";
default:
return false;
}
};
const getEndpointName = () => {
if (!parameters.mode) return ENDPOINTS[SPLIT_MODES.BY_PAGES];
return ENDPOINTS[parameters.mode as SplitMode];
};
return {
parameters,
updateParameter,
resetParameters,
validateParameters,
getEndpointName,
};
}; };

View File

@ -48,8 +48,8 @@ export const useToolManagement = (): ToolManagementResult => {
const baseTool = baseRegistry[toolKey as keyof typeof baseRegistry]; const baseTool = baseRegistry[toolKey as keyof typeof baseRegistry];
availableToolRegistry[toolKey] = { availableToolRegistry[toolKey] = {
...baseTool, ...baseTool,
name: t(baseTool.name), name: baseTool.name,
description: t(baseTool.description) description: baseTool.description,
}; };
} }
}); });

View File

@ -13,6 +13,7 @@ import { I18nextProvider } from 'react-i18next';
import i18n from '../../i18n/config'; import i18n from '../../i18n/config';
import axios from 'axios'; import axios from 'axios';
import { detectFileExtension } from '../../utils/fileUtils'; import { detectFileExtension } from '../../utils/fileUtils';
import { FIT_OPTIONS } from '../../constants/convertConstants';
// Mock axios // Mock axios
vi.mock('axios'); vi.mock('axios');
@ -403,7 +404,7 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
colorType: 'grayscale', colorType: 'grayscale',
dpi: 150, dpi: 150,
singleOrMultiple: 'single', singleOrMultiple: 'single',
fitOption: 'fitToPage', fitOption: FIT_OPTIONS.FIT_PAGE,
autoRotate: false, autoRotate: false,
combineImages: true combineImages: true
}); });
@ -417,7 +418,7 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
}); });
const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData; const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
expect(formData.get('fitOption')).toBe('fitToPage'); expect(formData.get('fitOption')).toBe(FIT_OPTIONS.FIT_PAGE);
expect(formData.get('colorType')).toBe('grayscale'); expect(formData.get('colorType')).toBe('grayscale');
expect(formData.get('autoRotate')).toBe('false'); expect(formData.get('autoRotate')).toBe('false');
}); });