diff --git a/frontend/src/constants/convertConstants.ts b/frontend/src/constants/convertConstants.ts index 49daf6dc5..709517532 100644 --- a/frontend/src/constants/convertConstants.ts +++ b/frontend/src/constants/convertConstants.ts @@ -1,21 +1,3 @@ -export const FROM_FORMATS = { - PDF: 'pdf', - OFFICE: 'office', - IMAGE: 'image', - HTML: 'html', - MARKDOWN: 'markdown', - TEXT: 'text' -} as const; - -export const TO_FORMATS = { - PDF: 'pdf', - IMAGE: 'image', - OFFICE_WORD: 'office-word', - OFFICE_PRESENTATION: 'office-presentation', - OFFICE_TEXT: 'office-text', - HTML: 'html', - XML: 'xml' -} as const; export const COLOR_TYPES = { COLOR: 'color', @@ -28,14 +10,6 @@ export const OUTPUT_OPTIONS = { MULTIPLE: 'multiple' } as const; -export const OFFICE_FORMATS = { - DOCX: 'docx', - ODT: 'odt', - PPTX: 'pptx', - ODP: 'odp', - TXT: 'txt', - RTF: 'rtf' -} as const; export const CONVERSION_ENDPOINTS = { 'office-pdf': '/api/v1/convert/file/pdf', @@ -63,23 +37,6 @@ export const ENDPOINT_NAMES = { 'markdown-pdf': 'markdown-to-pdf' } as const; -export const SUPPORTED_CONVERSIONS: Record = { - [FROM_FORMATS.PDF]: [TO_FORMATS.IMAGE, TO_FORMATS.OFFICE_WORD, TO_FORMATS.OFFICE_PRESENTATION, TO_FORMATS.OFFICE_TEXT, TO_FORMATS.HTML, TO_FORMATS.XML], - [FROM_FORMATS.OFFICE]: [TO_FORMATS.PDF], - [FROM_FORMATS.IMAGE]: [TO_FORMATS.PDF], - [FROM_FORMATS.HTML]: [TO_FORMATS.PDF], - [FROM_FORMATS.MARKDOWN]: [TO_FORMATS.PDF], - [FROM_FORMATS.TEXT]: [TO_FORMATS.PDF] -}; - -export const FILE_EXTENSIONS = { - [FROM_FORMATS.PDF]: ['pdf'], - [FROM_FORMATS.OFFICE]: ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp'], - [FROM_FORMATS.IMAGE]: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'webp'], - [FROM_FORMATS.HTML]: ['html', 'htm'], - [FROM_FORMATS.MARKDOWN]: ['md'], - [FROM_FORMATS.TEXT]: ['txt', 'rtf'] -}; // Grouped file extensions for dropdowns export const FROM_FORMAT_OPTIONS = [ @@ -152,8 +109,5 @@ export const EXTENSION_TO_ENDPOINT: Record> = { 'txt': { 'pdf': 'file-to-pdf' }, 'rtf': { 'pdf': 'file-to-pdf' } }; -export type FromFormat = typeof FROM_FORMATS[keyof typeof FROM_FORMATS]; -export type ToFormat = typeof TO_FORMATS[keyof typeof TO_FORMATS]; export type ColorType = typeof COLOR_TYPES[keyof typeof COLOR_TYPES]; -export type OutputOption = typeof OUTPUT_OPTIONS[keyof typeof OUTPUT_OPTIONS]; -export type OfficeFormat = typeof OFFICE_FORMATS[keyof typeof OFFICE_FORMATS]; \ No newline at end of file +export type OutputOption = typeof OUTPUT_OPTIONS[keyof typeof OUTPUT_OPTIONS]; \ No newline at end of file diff --git a/frontend/src/hooks/tools/convert/useConvertOperation.ts b/frontend/src/hooks/tools/convert/useConvertOperation.ts index 9f907e599..f7b4d20ae 100644 --- a/frontend/src/hooks/tools/convert/useConvertOperation.ts +++ b/frontend/src/hooks/tools/convert/useConvertOperation.ts @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react'; +import { useCallback, useState, useEffect } from 'react'; import axios from 'axios'; import { useTranslation } from 'react-i18next'; import { useFileContext } from '../../../contexts/FileContext'; @@ -11,6 +11,7 @@ import { ENDPOINT_NAMES, EXTENSION_TO_ENDPOINT } from '../../../constants/convertConstants'; +import { getEndpointUrl } from '../../../utils/convertUtils'; export interface ConvertOperationHook { executeOperation: ( @@ -152,22 +153,8 @@ export const useConvertOperation = (): ConvertOperationHook => { const { operation, operationId, fileId } = createOperation(parameters, selectedFiles); const formData = buildFormData(parameters, selectedFiles); - // Get endpoint using constants - const getEndpoint = () => { - const { fromExtension, toExtension } = parameters; - const endpointKey = EXTENSION_TO_ENDPOINT[fromExtension]?.[toExtension]; - if (!endpointKey) return ''; - - // Find the endpoint URL from CONVERSION_ENDPOINTS using the endpoint name - for (const [key, endpoint] of Object.entries(CONVERSION_ENDPOINTS)) { - if (ENDPOINT_NAMES[key as keyof typeof ENDPOINT_NAMES] === endpointKey) { - return endpoint; - } - } - return ''; - }; - - const endpoint = getEndpoint(); + // Get endpoint using utility function + const endpoint = getEndpointUrl(parameters.fromExtension, parameters.toExtension); if (!endpoint) { setErrorMessage(t("convert.errorNotSupported", { from: parameters.fromExtension, to: parameters.toExtension })); return; @@ -211,6 +198,11 @@ export const useConvertOperation = (): ConvertOperationHook => { }, [t, createOperation, buildFormData, recordOperation, markOperationApplied, markOperationFailed, processResults]); const resetResults = useCallback(() => { + // Clean up blob URLs to prevent memory leaks + if (downloadUrl) { + window.URL.revokeObjectURL(downloadUrl); + } + setFiles([]); setThumbnails([]); setIsGeneratingThumbnails(false); @@ -219,12 +211,21 @@ export const useConvertOperation = (): ConvertOperationHook => { setStatus(''); setErrorMessage(null); setIsLoading(false); - }, []); + }, [downloadUrl]); const clearError = useCallback(() => { setErrorMessage(null); }, []); + // Cleanup blob URLs on unmount to prevent memory leaks + useEffect(() => { + return () => { + if (downloadUrl) { + window.URL.revokeObjectURL(downloadUrl); + } + }; + }, [downloadUrl]); + return { executeOperation, diff --git a/frontend/src/hooks/tools/convert/useConvertParameters.ts b/frontend/src/hooks/tools/convert/useConvertParameters.ts index b948131bd..cb8f7420c 100644 --- a/frontend/src/hooks/tools/convert/useConvertParameters.ts +++ b/frontend/src/hooks/tools/convert/useConvertParameters.ts @@ -1,24 +1,13 @@ import { useState } from 'react'; import { - FROM_FORMATS, - TO_FORMATS, COLOR_TYPES, OUTPUT_OPTIONS, - OFFICE_FORMATS, - CONVERSION_ENDPOINTS, - ENDPOINT_NAMES, - SUPPORTED_CONVERSIONS, - FILE_EXTENSIONS, - FROM_FORMAT_OPTIONS, TO_FORMAT_OPTIONS, CONVERSION_MATRIX, - EXTENSION_TO_ENDPOINT, - type FromFormat, - type ToFormat, type ColorType, - type OutputOption, - type OfficeFormat + type OutputOption } from '../../../constants/convertConstants'; +import { getEndpointName as getEndpointNameUtil, getEndpointUrl } from '../../../utils/convertUtils'; export interface ConvertParameters { fromExtension: string; @@ -83,23 +72,12 @@ export const useConvertParameters = (): ConvertParametersHook => { const getEndpointName = () => { const { fromExtension, toExtension } = parameters; - if (!fromExtension || !toExtension) return ''; - - const endpointKey = EXTENSION_TO_ENDPOINT[fromExtension]?.[toExtension]; - return endpointKey || ''; + return getEndpointNameUtil(fromExtension, toExtension); }; const getEndpoint = () => { - const endpointName = getEndpointName(); - if (!endpointName) return ''; - - // Find the endpoint URL from CONVERSION_ENDPOINTS using the endpoint name - for (const [key, endpoint] of Object.entries(CONVERSION_ENDPOINTS)) { - if (ENDPOINT_NAMES[key as keyof typeof ENDPOINT_NAMES] === endpointName) { - return endpoint; - } - } - return ''; + const { fromExtension, toExtension } = parameters; + return getEndpointUrl(fromExtension, toExtension); }; const getAvailableToExtensions = (fromExtension: string) => { diff --git a/frontend/src/utils/convertUtils.ts b/frontend/src/utils/convertUtils.ts new file mode 100644 index 000000000..deb945a4f --- /dev/null +++ b/frontend/src/utils/convertUtils.ts @@ -0,0 +1,38 @@ +import { + CONVERSION_ENDPOINTS, + ENDPOINT_NAMES, + EXTENSION_TO_ENDPOINT +} from '../constants/convertConstants'; + +/** + * Resolves the endpoint name for a given conversion + */ +export const getEndpointName = (fromExtension: string, toExtension: string): string => { + if (!fromExtension || !toExtension) return ''; + + const endpointKey = EXTENSION_TO_ENDPOINT[fromExtension]?.[toExtension]; + return endpointKey || ''; +}; + +/** + * Resolves the full endpoint URL for a given conversion + */ +export const getEndpointUrl = (fromExtension: string, toExtension: string): string => { + const endpointName = getEndpointName(fromExtension, toExtension); + if (!endpointName) return ''; + + // Find the endpoint URL from CONVERSION_ENDPOINTS using the endpoint name + for (const [key, endpoint] of Object.entries(CONVERSION_ENDPOINTS)) { + if (ENDPOINT_NAMES[key as keyof typeof ENDPOINT_NAMES] === endpointName) { + return endpoint; + } + } + return ''; +}; + +/** + * Checks if a conversion is supported + */ +export const isConversionSupported = (fromExtension: string, toExtension: string): boolean => { + return getEndpointName(fromExtension, toExtension) !== ''; +}; \ No newline at end of file