Stirling-PDF/frontend/src/hooks/tools/convert/useConvertOperation.ts
2025-08-13 11:37:58 +01:00

165 lines
7.2 KiB
TypeScript

import { useCallback } from 'react';
import axios from 'axios';
import { useTranslation } from 'react-i18next';
import { ConvertParameters } from './useConvertParameters';
import { detectFileExtension } from '../../../utils/fileUtils';
import { createFileFromApiResponse } from '../../../utils/fileResponseUtils';
import { useToolOperation, ToolOperationConfig } from '../shared/useToolOperation';
import { getEndpointUrl, isImageFormat, isWebFormat } from '../../../utils/convertUtils';
const shouldProcessFilesSeparately = (
selectedFiles: File[],
parameters: ConvertParameters
): boolean => {
return selectedFiles.length > 1 && (
// Image to PDF with combineImages = false
((isImageFormat(parameters.fromExtension) || parameters.fromExtension === 'image') &&
parameters.toExtension === 'pdf' && !parameters.imageOptions.combineImages) ||
// PDF to image conversions (each PDF should generate its own image file)
(parameters.fromExtension === 'pdf' && isImageFormat(parameters.toExtension)) ||
// PDF to PDF/A conversions (each PDF should be processed separately)
(parameters.fromExtension === 'pdf' && parameters.toExtension === 'pdfa') ||
// Web files to PDF conversions (each web file should generate its own PDF)
((isWebFormat(parameters.fromExtension) || parameters.fromExtension === 'web') &&
parameters.toExtension === 'pdf') ||
// Web files smart detection
(parameters.isSmartDetection && parameters.smartDetectionType === 'web') ||
// Mixed file types (smart detection)
(parameters.isSmartDetection && parameters.smartDetectionType === 'mixed')
);
};
const buildFormData = (parameters: ConvertParameters, selectedFiles: File[]): FormData => {
const formData = new FormData();
selectedFiles.forEach(file => {
formData.append("fileInput", file);
});
const { fromExtension, toExtension, imageOptions, htmlOptions, emailOptions, pdfaOptions } = parameters;
if (isImageFormat(toExtension)) {
formData.append("imageFormat", toExtension);
formData.append("colorType", imageOptions.colorType);
formData.append("dpi", imageOptions.dpi.toString());
formData.append("singleOrMultiple", imageOptions.singleOrMultiple);
} else if (fromExtension === 'pdf' && ['docx', 'odt'].includes(toExtension)) {
formData.append("outputFormat", toExtension);
} else if (fromExtension === 'pdf' && ['pptx', 'odp'].includes(toExtension)) {
formData.append("outputFormat", toExtension);
} else if (fromExtension === 'pdf' && ['txt', 'rtf'].includes(toExtension)) {
formData.append("outputFormat", toExtension);
} else if ((isImageFormat(fromExtension) || fromExtension === 'image') && toExtension === 'pdf') {
formData.append("fitOption", imageOptions.fitOption);
formData.append("colorType", imageOptions.colorType);
formData.append("autoRotate", imageOptions.autoRotate.toString());
} else if ((fromExtension === 'html' || fromExtension === 'zip') && toExtension === 'pdf') {
formData.append("zoom", htmlOptions.zoomLevel.toString());
} else if (fromExtension === 'eml' && toExtension === 'pdf') {
formData.append("includeAttachments", emailOptions.includeAttachments.toString());
formData.append("maxAttachmentSizeMB", emailOptions.maxAttachmentSizeMB.toString());
formData.append("downloadHtml", emailOptions.downloadHtml.toString());
formData.append("includeAllRecipients", emailOptions.includeAllRecipients.toString());
} else if (fromExtension === 'pdf' && toExtension === 'pdfa') {
formData.append("outputFormat", pdfaOptions.outputFormat);
} else if (fromExtension === 'pdf' && toExtension === 'csv') {
formData.append("pageNumbers", "all");
}
return formData;
};
const createFileFromResponse = (
responseData: any,
headers: any,
originalFileName: string,
targetExtension: string
): File => {
const originalName = originalFileName.split('.')[0];
const fallbackFilename = `${originalName}_converted.${targetExtension}`;
return createFileFromApiResponse(responseData, headers, fallbackFilename);
};
export const useConvertOperation = () => {
const { t } = useTranslation();
const customConvertProcessor = useCallback(async (
parameters: ConvertParameters,
selectedFiles: File[]
): Promise<File[]> => {
const processedFiles: File[] = [];
const endpoint = getEndpointUrl(parameters.fromExtension, parameters.toExtension);
if (!endpoint) {
throw new Error(t('errorNotSupported', 'Unsupported conversion format'));
}
// Convert-specific routing logic: decide batch vs individual processing
if (shouldProcessFilesSeparately(selectedFiles, parameters)) {
// Individual processing for complex cases (PDF→image, smart detection, etc.)
const failedFiles: { file: string; error: string }[] = [];
for (const file of selectedFiles) {
try {
const formData = buildFormData(parameters, [file]);
const response = await axios.post(endpoint, formData, { responseType: 'blob' });
const convertedFile = createFileFromResponse(response.data, response.headers, file.name, parameters.toExtension);
processedFiles.push(convertedFile);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
failedFiles.push({ file: file.name, error: errorMessage });
}
}
// If some files failed but others succeeded, throw detailed error
if (failedFiles.length > 0) {
if (processedFiles.length === 0) {
// All files failed
const errorDetails = failedFiles.map(f => `${f.file}: ${f.error}`).join(', ');
throw new Error(`All files failed to convert: ${errorDetails}`);
} else {
// Partial failure - log warning but continue with successful files
const failedNames = failedFiles.map(f => `${f.file} (${f.error})`).join(', ');
console.warn(`Some files failed to convert: ${failedNames}. Successfully converted ${processedFiles.length} files.`);
}
}
} else {
// Batch processing for simple cases (image→PDF combine)
const formData = buildFormData(parameters, selectedFiles);
const response = await axios.post(endpoint, formData, { responseType: 'blob' });
const baseFilename = selectedFiles.length === 1
? selectedFiles[0].name
: 'converted_files';
const convertedFile = createFileFromResponse(response.data, response.headers, baseFilename, parameters.toExtension);
processedFiles.push(convertedFile);
}
return processedFiles;
}, [t]);
return useToolOperation<ConvertParameters>({
operationType: 'convert',
endpoint: '', // Not used with customProcessor but required
buildFormData, // Not used with customProcessor but required
filePrefix: 'converted_',
customProcessor: customConvertProcessor, // Convert handles its own routing
validateParams: (params) => {
return { valid: true };
},
getErrorMessage: (error) => {
if (error.response?.data && typeof error.response.data === 'string') {
return error.response.data;
}
if (error.message) {
return error.message;
}
return t("convert.errorConversion", "An error occurred while converting the file.");
}
});
};