diff --git a/frontend/src/components/tools/convert/ConvertSettings.tsx b/frontend/src/components/tools/convert/ConvertSettings.tsx index 6907a10f5..03c45b019 100644 --- a/frontend/src/components/tools/convert/ConvertSettings.tsx +++ b/frontend/src/components/tools/convert/ConvertSettings.tsx @@ -3,6 +3,7 @@ import { Stack, Text, Group, Divider, UnstyledButton, useMantineTheme, useMantin import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import { useTranslation } from "react-i18next"; import { useMultipleEndpointsEnabled } from "../../../hooks/useEndpointConfig"; +import { isImageFormat } from "../../../utils/convertUtils"; import GroupedFormatDropdown from "./GroupedFormatDropdown"; import ConvertToImageSettings from "./ConvertToImageSettings"; import ConvertFromImageSettings from "./ConvertFromImageSettings"; @@ -54,11 +55,20 @@ const ConvertSettings = ({ // Enhanced FROM options with endpoint availability const enhancedFromOptions = useMemo(() => { - return FROM_FORMAT_OPTIONS.map(option => ({ - ...option, - enabled: true // All "from" formats are generally available for selection - })); - }, []); + return FROM_FORMAT_OPTIONS.map(option => { + // Check if this source format has any available conversions + const availableConversions = getAvailableToExtensions(option.value) || []; + const hasAvailableConversions = availableConversions.some(targetOption => + isConversionAvailable(option.value, targetOption.value) + ); + + + return { + ...option, + enabled: hasAvailableConversions + }; + }); + }, [getAvailableToExtensions, endpointStatus]); // Enhanced TO options with endpoint availability const enhancedToOptions = useMemo(() => { @@ -69,7 +79,7 @@ const ConvertSettings = ({ ...option, enabled: isConversionAvailable(parameters.fromExtension, option.value) })); - }, [parameters.fromExtension, getAvailableToExtensions]); + }, [parameters.fromExtension, getAvailableToExtensions, endpointStatus]); const handleFromExtensionChange = (value: string) => { onParameterChange('fromExtension', value); @@ -149,7 +159,7 @@ const ConvertSettings = ({ {/* Format-specific options */} - {['png', 'jpg'].includes(parameters.toExtension) && ( + {isImageFormat(parameters.toExtension) && ( <> )} + + {/* EML specific options */} + {parameters.fromExtension === 'eml' && parameters.toExtension === 'pdf' && ( + <> + + + {t("convert.emlOptions", "Email Options")}: + + {t("convert.emlNote", "Email attachments and embedded images will be included in the PDF conversion.")} + + + + )} ); }; diff --git a/frontend/src/constants/convertConstants.ts b/frontend/src/constants/convertConstants.ts index 709517532..6aba231fd 100644 --- a/frontend/src/constants/convertConstants.ts +++ b/frontend/src/constants/convertConstants.ts @@ -18,10 +18,14 @@ export const CONVERSION_ENDPOINTS = { 'pdf-office-word': '/api/v1/convert/pdf/word', 'pdf-office-presentation': '/api/v1/convert/pdf/presentation', 'pdf-office-text': '/api/v1/convert/pdf/text', + 'pdf-csv': '/api/v1/convert/pdf/csv', + 'pdf-markdown': '/api/v1/convert/pdf/markdown', 'pdf-html': '/api/v1/convert/pdf/html', 'pdf-xml': '/api/v1/convert/pdf/xml', + 'pdf-pdfa': '/api/v1/convert/pdf/pdfa', 'html-pdf': '/api/v1/convert/html/pdf', - 'markdown-pdf': '/api/v1/convert/markdown/pdf' + 'markdown-pdf': '/api/v1/convert/markdown/pdf', + 'eml-pdf': '/api/v1/convert/eml/pdf' } as const; export const ENDPOINT_NAMES = { @@ -31,10 +35,14 @@ export const ENDPOINT_NAMES = { 'pdf-office-word': 'pdf-to-word', 'pdf-office-presentation': 'pdf-to-presentation', 'pdf-office-text': 'pdf-to-text', + 'pdf-csv': 'pdf-to-csv', + 'pdf-markdown': 'pdf-to-markdown', 'pdf-html': 'pdf-to-html', 'pdf-xml': 'pdf-to-xml', + 'pdf-pdfa': 'pdf-to-pdfa', 'html-pdf': 'html-to-pdf', - 'markdown-pdf': 'markdown-to-pdf' + 'markdown-pdf': 'markdown-to-pdf', + 'eml-pdf': 'eml-to-pdf' } as const; @@ -62,42 +70,53 @@ export const FROM_FORMAT_OPTIONS = [ { value: 'md', label: 'MD', group: 'Text' }, { value: 'txt', label: 'TXT', group: 'Text' }, { value: 'rtf', label: 'RTF', group: 'Text' }, + { value: 'eml', label: 'EML', group: 'Email' }, ]; export const TO_FORMAT_OPTIONS = [ { value: 'pdf', label: 'PDF', group: 'Document' }, + { value: 'pdfa', label: 'PDF/A', group: 'Document' }, { value: 'docx', label: 'DOCX', group: 'Document' }, { value: 'odt', label: 'ODT', group: 'Document' }, + { value: 'csv', label: 'CSV', group: 'Spreadsheet' }, { value: 'pptx', label: 'PPTX', group: 'Presentation' }, { value: 'odp', label: 'ODP', group: 'Presentation' }, { value: 'txt', label: 'TXT', group: 'Text' }, { value: 'rtf', label: 'RTF', group: 'Text' }, + { value: 'md', label: 'MD', group: 'Text' }, { value: 'png', label: 'PNG', group: 'Image' }, { value: 'jpg', label: 'JPG', group: 'Image' }, + { value: 'gif', label: 'GIF', group: 'Image' }, + { value: 'tiff', label: 'TIFF', group: 'Image' }, + { value: 'bmp', label: 'BMP', group: 'Image' }, + { value: 'webp', label: 'WEBP', group: 'Image' }, { value: 'html', label: 'HTML', group: 'Web' }, { value: 'xml', label: 'XML', group: 'Web' }, ]; // Conversion matrix - what each source format can convert to export const CONVERSION_MATRIX: Record = { - 'pdf': ['png', 'jpg', 'docx', 'odt', 'pptx', 'odp', 'txt', 'rtf', 'html', 'xml'], + 'pdf': ['png', 'jpg', 'gif', 'tiff', 'bmp', 'webp', 'docx', 'odt', 'pptx', 'odp', 'csv', 'txt', 'rtf', 'md', 'html', 'xml', 'pdfa'], 'docx': ['pdf'], 'doc': ['pdf'], 'odt': ['pdf'], 'xlsx': ['pdf'], 'xls': ['pdf'], 'ods': ['pdf'], 'pptx': ['pdf'], 'ppt': ['pdf'], 'odp': ['pdf'], 'jpg': ['pdf'], 'jpeg': ['pdf'], 'png': ['pdf'], 'gif': ['pdf'], 'bmp': ['pdf'], 'tiff': ['pdf'], 'webp': ['pdf'], 'html': ['pdf'], 'htm': ['pdf'], 'md': ['pdf'], - 'txt': ['pdf'], 'rtf': ['pdf'] + 'txt': ['pdf'], 'rtf': ['pdf'], + 'eml': ['pdf'] }; // Map extensions to endpoint keys export const EXTENSION_TO_ENDPOINT: Record> = { 'pdf': { - 'png': 'pdf-to-img', 'jpg': 'pdf-to-img', + 'png': 'pdf-to-img', 'jpg': 'pdf-to-img', 'gif': 'pdf-to-img', 'tiff': 'pdf-to-img', 'bmp': 'pdf-to-img', 'webp': 'pdf-to-img', 'docx': 'pdf-to-word', 'odt': 'pdf-to-word', 'pptx': 'pdf-to-presentation', 'odp': 'pdf-to-presentation', - 'txt': 'pdf-to-text', 'rtf': 'pdf-to-text', - 'html': 'pdf-to-html', 'xml': 'pdf-to-xml' + 'csv': 'pdf-to-csv', + 'txt': 'pdf-to-text', 'rtf': 'pdf-to-text', 'md': 'pdf-to-markdown', + 'html': 'pdf-to-html', 'xml': 'pdf-to-xml', + 'pdfa': 'pdf-to-pdfa' }, 'docx': { 'pdf': 'file-to-pdf' }, 'doc': { 'pdf': 'file-to-pdf' }, 'odt': { 'pdf': 'file-to-pdf' }, 'xlsx': { 'pdf': 'file-to-pdf' }, 'xls': { 'pdf': 'file-to-pdf' }, 'ods': { 'pdf': 'file-to-pdf' }, @@ -106,7 +125,8 @@ export const EXTENSION_TO_ENDPOINT: Record> = { 'gif': { 'pdf': 'img-to-pdf' }, 'bmp': { 'pdf': 'img-to-pdf' }, 'tiff': { 'pdf': 'img-to-pdf' }, 'webp': { 'pdf': 'img-to-pdf' }, 'html': { 'pdf': 'html-to-pdf' }, 'htm': { 'pdf': 'html-to-pdf' }, 'md': { 'pdf': 'markdown-to-pdf' }, - 'txt': { 'pdf': 'file-to-pdf' }, 'rtf': { 'pdf': 'file-to-pdf' } + 'txt': { 'pdf': 'file-to-pdf' }, 'rtf': { 'pdf': 'file-to-pdf' }, + 'eml': { 'pdf': 'eml-to-pdf' } }; export type ColorType = typeof COLOR_TYPES[keyof typeof COLOR_TYPES]; diff --git a/frontend/src/hooks/tools/convert/useConvertOperation.ts b/frontend/src/hooks/tools/convert/useConvertOperation.ts index f7b4d20ae..4b639988e 100644 --- a/frontend/src/hooks/tools/convert/useConvertOperation.ts +++ b/frontend/src/hooks/tools/convert/useConvertOperation.ts @@ -11,7 +11,7 @@ import { ENDPOINT_NAMES, EXTENSION_TO_ENDPOINT } from '../../../constants/convertConstants'; -import { getEndpointUrl } from '../../../utils/convertUtils'; +import { getEndpointUrl, isImageFormat } from '../../../utils/convertUtils'; export interface ConvertOperationHook { executeOperation: ( @@ -66,8 +66,8 @@ export const useConvertOperation = (): ConvertOperationHook => { const { fromExtension, toExtension, imageOptions } = parameters; // Add conversion-specific parameters - if (['png', 'jpg'].includes(toExtension)) { - formData.append("imageFormat", toExtension === 'jpg' ? 'jpg' : 'png'); + if (isImageFormat(toExtension)) { + formData.append("imageFormat", toExtension); formData.append("colorType", imageOptions.colorType); formData.append("dpi", imageOptions.dpi.toString()); formData.append("singleOrMultiple", imageOptions.singleOrMultiple); @@ -77,7 +77,7 @@ export const useConvertOperation = (): ConvertOperationHook => { formData.append("outputFormat", toExtension); } else if (fromExtension === 'pdf' && ['txt', 'rtf'].includes(toExtension)) { formData.append("outputFormat", toExtension); - } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'webp'].includes(fromExtension) && toExtension === 'pdf') { + } else if (isImageFormat(fromExtension) && toExtension === 'pdf') { formData.append("fitOption", "fillPage"); formData.append("colorType", imageOptions.colorType); formData.append("autoRotate", "true"); diff --git a/frontend/src/hooks/useToolManagement.tsx b/frontend/src/hooks/useToolManagement.tsx index dc8300aec..9f9a62a9e 100644 --- a/frontend/src/hooks/useToolManagement.tsx +++ b/frontend/src/hooks/useToolManagement.tsx @@ -33,7 +33,7 @@ const toolEndpoints: Record = { split: ["split-pages", "split-pdf-by-sections", "split-by-size-or-count", "split-pdf-by-chapters"], compress: ["compress-pdf"], merge: ["merge-pdfs"], - convert: ["pdf-to-img", "img-to-pdf", "pdf-to-word", "pdf-to-presentation", "pdf-to-text", "pdf-to-html", "pdf-to-xml", "html-to-pdf", "markdown-to-pdf", "file-to-pdf"], + convert: ["pdf-to-img", "img-to-pdf", "pdf-to-word", "pdf-to-presentation", "pdf-to-text", "pdf-to-csv", "pdf-to-markdown", "pdf-to-html", "pdf-to-xml", "pdf-to-pdfa", "html-to-pdf", "markdown-to-pdf", "file-to-pdf", "eml-to-pdf"], }; diff --git a/frontend/src/utils/convertUtils.ts b/frontend/src/utils/convertUtils.ts index deb945a4f..1ba33ef67 100644 --- a/frontend/src/utils/convertUtils.ts +++ b/frontend/src/utils/convertUtils.ts @@ -35,4 +35,11 @@ export const getEndpointUrl = (fromExtension: string, toExtension: string): stri */ export const isConversionSupported = (fromExtension: string, toExtension: string): boolean => { return getEndpointName(fromExtension, toExtension) !== ''; +}; + +/** + * Checks if the given extension is an image format + */ +export const isImageFormat = (extension: string): boolean => { + return ['png', 'jpg', 'jpeg', 'gif', 'tiff', 'bmp', 'webp'].includes(extension.toLowerCase()); }; \ No newline at end of file