Stirling-PDF/frontend/src/hooks/tools/convert/useConvertParameters.ts
James Brunton a6706fcb0c
V2 reduce boilerplate in param hooks (#4246)
# Description of Changes
Extend the base params in all tools param hooks, reducing boilerplate
code.
2025-08-21 07:48:25 +00:00

316 lines
10 KiB
TypeScript

import {
COLOR_TYPES,
OUTPUT_OPTIONS,
FIT_OPTIONS,
TO_FORMAT_OPTIONS,
CONVERSION_MATRIX,
type ColorType,
type OutputOption,
type FitOption
} from '../../../constants/convertConstants';
import { getEndpointName as getEndpointNameUtil, getEndpointUrl, isImageFormat, isWebFormat } from '../../../utils/convertUtils';
import { detectFileExtension as detectFileExtensionUtil } from '../../../utils/fileUtils';
import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface ConvertParameters extends BaseParameters {
fromExtension: string;
toExtension: string;
imageOptions: {
colorType: ColorType;
dpi: number;
singleOrMultiple: OutputOption;
fitOption: FitOption;
autoRotate: boolean;
combineImages: boolean;
};
htmlOptions: {
zoomLevel: number;
};
emailOptions: {
includeAttachments: boolean;
maxAttachmentSizeMB: number;
downloadHtml: boolean;
includeAllRecipients: boolean;
};
pdfaOptions: {
outputFormat: string;
};
isSmartDetection: boolean;
smartDetectionType: 'mixed' | 'images' | 'web' | 'none';
}
export interface ConvertParametersHook extends BaseParametersHook<ConvertParameters> {
getEndpoint: () => string;
getAvailableToExtensions: (fromExtension: string) => Array<{value: string, label: string, group: string}>;
analyzeFileTypes: (files: Array<{name: string}>) => void;
}
const defaultParameters: ConvertParameters = {
fromExtension: '',
toExtension: '',
imageOptions: {
colorType: COLOR_TYPES.COLOR,
dpi: 300,
singleOrMultiple: OUTPUT_OPTIONS.MULTIPLE,
fitOption: FIT_OPTIONS.MAINTAIN_ASPECT,
autoRotate: true,
combineImages: true,
},
htmlOptions: {
zoomLevel: 1.0,
},
emailOptions: {
includeAttachments: true,
maxAttachmentSizeMB: 10,
downloadHtml: false,
includeAllRecipients: false,
},
pdfaOptions: {
outputFormat: 'pdfa-1',
},
isSmartDetection: false,
smartDetectionType: 'none',
};
const validateParameters = (params: ConvertParameters): boolean => {
const { fromExtension, toExtension } = params;
if (!fromExtension || !toExtension) return false;
// Handle dynamic format identifiers (file-<extension>)
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] || [];
}
if (!supportedToExtensions.includes(toExtension)) {
return false;
}
return true;
};
const getEndpointName = (params: ConvertParameters): string => {
const { fromExtension, toExtension, isSmartDetection, smartDetectionType } = params;
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 getEndpointNameUtil(fromExtension, toExtension);
};
export const useConvertParameters = (): ConvertParametersHook => {
const baseHook = useBaseParameters({
defaultParameters,
endpointName: getEndpointName,
validateFn: validateParameters,
});
const getEndpoint = () => {
const { fromExtension, toExtension, isSmartDetection, smartDetectionType } = baseHook.parameters;
if (isSmartDetection) {
if (smartDetectionType === 'mixed') {
// Mixed file types -> PDF using file-to-pdf endpoint
return '/api/v1/convert/file/pdf';
} else if (smartDetectionType === 'images') {
// All images -> PDF using img-to-pdf endpoint
return '/api/v1/convert/img/pdf';
} else if (smartDetectionType === 'web') {
// All web files -> PDF using html-to-pdf endpoint
return '/api/v1/convert/html/pdf';
}
}
// Handle dynamic format identifiers (file-<extension>)
if (fromExtension.startsWith('file-')) {
// Dynamic format - use file-to-pdf endpoint
return '/api/v1/convert/file/pdf';
}
return getEndpointUrl(fromExtension, toExtension);
};
const getAvailableToExtensions = (fromExtension: string) => {
if (!fromExtension) return [];
// Handle dynamic format identifiers (file-<extension>)
if (fromExtension.startsWith('file-')) {
// Dynamic format - use 'any' conversion options (file-to-pdf)
const supportedExtensions = CONVERSION_MATRIX['any'] || [];
return TO_FORMAT_OPTIONS.filter(option =>
supportedExtensions.includes(option.value)
);
}
let supportedExtensions = CONVERSION_MATRIX[fromExtension] || [];
// If no explicit conversion exists, but file-to-pdf might be available,
// fall back to 'any' conversion (which converts unknown files to PDF via file-to-pdf)
if (supportedExtensions.length === 0 && fromExtension !== 'any') {
supportedExtensions = CONVERSION_MATRIX['any'] || [];
}
return TO_FORMAT_OPTIONS.filter(option =>
supportedExtensions.includes(option.value)
);
};
const analyzeFileTypes = (files: Array<{name: string}>) => {
if (files.length === 0) {
// No files - only reset smart detection, keep user's format choices
baseHook.setParameters(prev => ({
...prev,
isSmartDetection: false,
smartDetectionType: 'none'
// Don't reset fromExtension and toExtension - let user keep their choices
}));
return;
}
if (files.length === 1) {
// Single file - use regular detection with smart target selection
const detectedExt = detectFileExtensionUtil(files[0].name);
let fromExt = detectedExt;
let availableTargets = detectedExt ? CONVERSION_MATRIX[detectedExt] || [] : [];
// If no explicit conversion exists for this file type, create a dynamic format entry
// and fall back to 'any' conversion logic for the actual endpoint
if (availableTargets.length === 0 && detectedExt) {
fromExt = `file-${detectedExt}`; // Create dynamic format identifier
availableTargets = CONVERSION_MATRIX['any'] || [];
} else if (availableTargets.length === 0) {
// No extension detected - fall back to 'any'
fromExt = 'any';
availableTargets = CONVERSION_MATRIX['any'] || [];
}
baseHook.setParameters(prev => {
// Check if current toExtension is still valid for the new fromExtension
const currentToExt = prev.toExtension;
const isCurrentToExtValid = availableTargets.includes(currentToExt);
// Auto-select target only if:
// 1. No current target is set, OR
// 2. Current target is invalid for new source type, OR
// 3. There's only one possible target (forced conversion)
let newToExtension = currentToExt;
if (!currentToExt || !isCurrentToExtValid) {
newToExtension = availableTargets.length === 1 ? availableTargets[0] : '';
}
return {
...prev,
isSmartDetection: false,
smartDetectionType: 'none',
fromExtension: fromExt,
toExtension: newToExtension
};
});
return;
}
// Multiple files - analyze file types
const extensions = files.map(file => detectFileExtensionUtil(file.name));
const uniqueExtensions = [...new Set(extensions)];
if (uniqueExtensions.length === 1) {
// All files are the same type - use regular detection with smart target selection
const detectedExt = uniqueExtensions[0];
let fromExt = detectedExt;
let availableTargets = CONVERSION_MATRIX[detectedExt] || [];
// If no explicit conversion exists for this file type, fall back to 'any'
if (availableTargets.length === 0) {
fromExt = 'any';
availableTargets = CONVERSION_MATRIX['any'] || [];
}
baseHook.setParameters(prev => {
// Check if current toExtension is still valid for the new fromExtension
const currentToExt = prev.toExtension;
const isCurrentToExtValid = availableTargets.includes(currentToExt);
// Auto-select target only if:
// 1. No current target is set, OR
// 2. Current target is invalid for new source type, OR
// 3. There's only one possible target (forced conversion)
let newToExtension = currentToExt;
if (!currentToExt || !isCurrentToExtValid) {
newToExtension = availableTargets.length === 1 ? availableTargets[0] : '';
}
return {
...prev,
isSmartDetection: false,
smartDetectionType: 'none',
fromExtension: fromExt,
toExtension: newToExtension
};
});
} else {
// Mixed file types
const allImages = uniqueExtensions.every(ext => isImageFormat(ext));
const allWeb = uniqueExtensions.every(ext => isWebFormat(ext));
if (allImages) {
// All files are images - use image-to-pdf conversion
baseHook.setParameters(prev => ({
...prev,
isSmartDetection: true,
smartDetectionType: 'images',
fromExtension: 'image',
toExtension: 'pdf'
}));
} else if (allWeb) {
// All files are web files - use html-to-pdf conversion
baseHook.setParameters(prev => ({
...prev,
isSmartDetection: true,
smartDetectionType: 'web',
fromExtension: 'html',
toExtension: 'pdf'
}));
} else {
// Mixed non-image types - use file-to-pdf conversion
baseHook.setParameters(prev => ({
...prev,
isSmartDetection: true,
smartDetectionType: 'mixed',
fromExtension: 'any',
toExtension: 'pdf'
}));
}
}
};
return {
...baseHook,
getEndpoint,
getAvailableToExtensions,
analyzeFileTypes,
};
};