diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index a0f7a2666..47d8767dc 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -1107,8 +1107,11 @@ "textRtf": "Text/RTF", "grayscale": "Greyscale", "errorConversion": "An error occurred while converting the file.", - "cbzOptions": "CBZ to PDF Options", + "cbrOptions": "CBR to PDF Options", "optimizeForEbook": "Optimize PDF for ebook readers (uses Ghostscript)", + "cbrOutputOptions": "PDF to CBR Options", + "cbrDpi": "DPI for image rendering", + "cbzOptions": "CBZ to PDF Options", "cbzOutputOptions": "PDF to CBZ Options", "cbzDpi": "DPI for image rendering" }, @@ -2206,7 +2209,7 @@ }, "cta": "Compare", "loading": "Comparing...", - + "summary": { "baseHeading": "Original document", "comparisonHeading": "Edited document", @@ -2262,7 +2265,7 @@ "body": "This comparison is taking longer than usual. You can let it continue or cancel it.", "cancel": "Cancel comparison" }, - + "newLine": "new-line", "complex": { "message": "One or both of the provided documents are large files, accuracy of comparison may be reduced" diff --git a/frontend/src/core/components/tools/convert/ConvertFromCbrSettings.tsx b/frontend/src/core/components/tools/convert/ConvertFromCbrSettings.tsx new file mode 100644 index 000000000..8f1a80946 --- /dev/null +++ b/frontend/src/core/components/tools/convert/ConvertFromCbrSettings.tsx @@ -0,0 +1,36 @@ +import { Stack, Text, Checkbox } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import { ConvertParameters } from "@app/hooks/tools/convert/useConvertParameters"; + +interface ConvertFromCbrSettingsProps { + parameters: ConvertParameters; + onParameterChange: (key: K, value: ConvertParameters[K]) => void; + disabled?: boolean; +} + +const ConvertFromCbrSettings = ({ + parameters, + onParameterChange, + disabled = false +}: ConvertFromCbrSettingsProps) => { + const { t } = useTranslation(); + + return ( + + {t("convert.cbrOptions", "CBR Options")}: + + onParameterChange('cbrOptions', { + ...parameters.cbrOptions, + optimizeForEbook: event.currentTarget.checked + })} + disabled={disabled} + data-testid="optimize-ebook-checkbox" + /> + + ); +}; + +export default ConvertFromCbrSettings; diff --git a/frontend/src/core/components/tools/convert/ConvertSettings.tsx b/frontend/src/core/components/tools/convert/ConvertSettings.tsx index d9727b078..30395ebf3 100644 --- a/frontend/src/core/components/tools/convert/ConvertSettings.tsx +++ b/frontend/src/core/components/tools/convert/ConvertSettings.tsx @@ -16,6 +16,8 @@ import ConvertFromEmailSettings from "@app/components/tools/convert/ConvertFromE import ConvertFromCbzSettings from "@app/components/tools/convert/ConvertFromCbzSettings"; import ConvertToCbzSettings from "@app/components/tools/convert/ConvertToCbzSettings"; import ConvertToPdfaSettings from "@app/components/tools/convert/ConvertToPdfaSettings"; +import ConvertFromCbrSettings from "@app/components/tools/convert/ConvertFromCbrSettings"; +import ConvertToCbrSettings from "@app/components/tools/convert/ConvertToCbrSettings"; import { ConvertParameters } from "@app/hooks/tools/convert/useConvertParameters"; import { FROM_FORMAT_OPTIONS, @@ -120,6 +122,12 @@ const ConvertSettings = ({ onParameterChange('pdfaOptions', { outputFormat: 'pdfa-1', }); + onParameterChange('cbrOptions', { + optimizeForEbook: false, + }); + onParameterChange('pdfToCbrOptions', { + dpi: 150, + }); onParameterChange('cbzOptions', { optimizeForEbook: false, }); @@ -188,6 +196,12 @@ const ConvertSettings = ({ onParameterChange('pdfaOptions', { outputFormat: 'pdfa-1', }); + onParameterChange('cbrOptions', { + optimizeForEbook: false, + }); + onParameterChange('pdfToCbrOptions', { + dpi: 150, + }); onParameterChange('cbzOptions', { optimizeForEbook: false, }); @@ -344,6 +358,30 @@ const ConvertSettings = ({ )} + {/* CBR to PDF options */} + {parameters.fromExtension === 'cbr' && parameters.toExtension === 'pdf' && ( + <> + + + + )} + + {/* PDF to CBR options */} + {parameters.fromExtension === 'pdf' && parameters.toExtension === 'cbr' && ( + <> + + + + )} + ); }; diff --git a/frontend/src/core/components/tools/convert/ConvertToCbrSettings.tsx b/frontend/src/core/components/tools/convert/ConvertToCbrSettings.tsx new file mode 100644 index 000000000..db85eda77 --- /dev/null +++ b/frontend/src/core/components/tools/convert/ConvertToCbrSettings.tsx @@ -0,0 +1,39 @@ +import { Stack, Text, NumberInput } from "@mantine/core"; +import { useTranslation } from "react-i18next"; +import { ConvertParameters } from "@app/hooks/tools/convert/useConvertParameters"; + +interface ConvertToCbrSettingsProps { + parameters: ConvertParameters; + onParameterChange: (key: K, value: ConvertParameters[K]) => void; + disabled?: boolean; +} + +const ConvertToCbrSettings = ({ + parameters, + onParameterChange, + disabled = false +}: ConvertToCbrSettingsProps) => { + const { t } = useTranslation(); + + return ( + + {t("convert.cbrOutputOptions", "PDF to CBR Options")}: + + + typeof val === 'number' && + onParameterChange('pdfToCbrOptions', { ...parameters.pdfToCbrOptions, dpi: val }) + } + min={72} + max={600} + step={50} + disabled={disabled} + /> + + ); +}; + +export default ConvertToCbrSettings; diff --git a/frontend/src/core/constants/convertConstants.ts b/frontend/src/core/constants/convertConstants.ts index 0dc6e3726..73d00677f 100644 --- a/frontend/src/core/constants/convertConstants.ts +++ b/frontend/src/core/constants/convertConstants.ts @@ -33,7 +33,9 @@ export const CONVERSION_ENDPOINTS = { 'pdf-pdfa': '/api/v1/convert/pdf/pdfa', 'html-pdf': '/api/v1/convert/html/pdf', 'markdown-pdf': '/api/v1/convert/markdown/pdf', - 'eml-pdf': '/api/v1/convert/eml/pdf' + 'eml-pdf': '/api/v1/convert/eml/pdf', + 'cbr-pdf': '/api/v1/convert/cbr/pdf', + 'pdf-cbr': '/api/v1/convert/pdf/cbr' } as const; export const ENDPOINT_NAMES = { @@ -52,7 +54,9 @@ export const ENDPOINT_NAMES = { 'pdf-pdfa': 'pdf-to-pdfa', 'html-pdf': 'html-to-pdf', 'markdown-pdf': 'markdown-to-pdf', - 'eml-pdf': 'eml-to-pdf' + 'eml-pdf': 'eml-to-pdf', + 'cbr-pdf': 'cbr-to-pdf', + 'pdf-cbr': 'pdf-to-cbr' } as const; @@ -62,6 +66,7 @@ export const FROM_FORMAT_OPTIONS = [ { value: 'image', label: 'Images', group: 'Multiple Files' }, { value: 'pdf', label: 'PDF', group: 'Document' }, { value: 'cbz', label: 'CBZ', group: 'Archive' }, + { value: 'cbr', label: 'CBR', group: 'Archive' }, { value: 'docx', label: 'DOCX', group: 'Document' }, { value: 'doc', label: 'DOC', group: 'Document' }, { value: 'odt', label: 'ODT', group: 'Document' }, @@ -93,6 +98,7 @@ export const TO_FORMAT_OPTIONS = [ { value: 'docx', label: 'DOCX', group: 'Document' }, { value: 'odt', label: 'ODT', group: 'Document' }, { value: 'cbz', label: 'CBZ', group: 'Archive' }, + { value: 'cbr', label: 'CBR', group: 'Archive' }, { value: 'csv', label: 'CSV', group: 'Spreadsheet' }, { value: 'pptx', label: 'PPTX', group: 'Presentation' }, { value: 'odp', label: 'ODP', group: 'Presentation' }, @@ -113,7 +119,7 @@ export const TO_FORMAT_OPTIONS = [ export const CONVERSION_MATRIX: Record = { 'any': ['pdf'], // Mixed files always convert to PDF 'image': ['pdf'], // Multiple images always convert to PDF - 'pdf': ['png', 'jpg', 'gif', 'tiff', 'bmp', 'webp', 'docx', 'odt', 'pptx', 'odp', 'csv', 'txt', 'rtf', 'md', 'html', 'xml', 'pdfa', 'cbz'], + 'pdf': ['png', 'jpg', 'gif', 'tiff', 'bmp', 'webp', 'docx', 'odt', 'pptx', 'odp', 'csv', 'txt', 'rtf', 'md', 'html', 'xml', 'pdfa', 'cbz', 'cbr'], 'cbz': ['pdf'], 'docx': ['pdf'], 'doc': ['pdf'], 'odt': ['pdf'], 'xlsx': ['pdf'], 'xls': ['pdf'], 'ods': ['pdf'], @@ -123,7 +129,8 @@ export const CONVERSION_MATRIX: Record = { 'zip': ['pdf'], 'md': ['pdf'], 'txt': ['pdf'], 'rtf': ['pdf'], - 'eml': ['pdf'] + 'eml': ['pdf'], + 'cbr': ['pdf'] }; // Map extensions to endpoint keys @@ -138,6 +145,7 @@ export const EXTENSION_TO_ENDPOINT: Record> = { 'txt': 'pdf-to-text', 'rtf': 'pdf-to-text', 'md': 'pdf-to-markdown', 'html': 'pdf-to-html', 'xml': 'pdf-to-xml', 'pdfa': 'pdf-to-pdfa', + 'cbr': 'pdf-to-cbr', 'cbz': 'pdf-to-cbz' }, 'cbz': { 'pdf': 'cbz-to-pdf' }, @@ -150,7 +158,8 @@ export const EXTENSION_TO_ENDPOINT: Record> = { 'zip': { 'pdf': 'html-to-pdf' }, 'md': { 'pdf': 'markdown-to-pdf' }, 'txt': { 'pdf': 'file-to-pdf' }, 'rtf': { 'pdf': 'file-to-pdf' }, - 'eml': { 'pdf': 'eml-to-pdf' } + 'eml': { 'pdf': 'eml-to-pdf' }, + 'cbr': { 'pdf': 'cbr-to-pdf' } }; export type ColorType = typeof COLOR_TYPES[keyof typeof COLOR_TYPES]; diff --git a/frontend/src/core/constants/convertSupportedFornats.ts b/frontend/src/core/constants/convertSupportedFornats.ts index 86138c4e6..f16fef369 100644 --- a/frontend/src/core/constants/convertSupportedFornats.ts +++ b/frontend/src/core/constants/convertSupportedFornats.ts @@ -13,9 +13,7 @@ export const CONVERT_SUPPORTED_FORMATS = [ // Email formats 'eml', // Archive formats - 'zip', 'cbz', + 'zip', 'cbr', 'cbz', // Other 'dbf', 'fods', 'vsd', 'vor', 'vor3', 'vor4', 'uop', 'pct', 'ps', 'pdf', ]; - - diff --git a/frontend/src/core/hooks/tools/convert/useConvertOperation.ts b/frontend/src/core/hooks/tools/convert/useConvertOperation.ts index 9134c9db4..ef4fceebd 100644 --- a/frontend/src/core/hooks/tools/convert/useConvertOperation.ts +++ b/frontend/src/core/hooks/tools/convert/useConvertOperation.ts @@ -21,6 +21,8 @@ export const shouldProcessFilesSeparately = ( (parameters.fromExtension === 'pdf' && parameters.toExtension === 'pdfa') || // PDF to text-like formats should be one output per input (parameters.fromExtension === 'pdf' && ['txt', 'rtf', 'csv'].includes(parameters.toExtension)) || + // PDF to CBR conversions (each PDF should generate its own archive) + (parameters.fromExtension === 'pdf' && parameters.toExtension === 'cbr') || // Web files to PDF conversions (each web file should generate its own PDF) ((isWebFormat(parameters.fromExtension) || parameters.fromExtension === 'web') && parameters.toExtension === 'pdf') || @@ -34,12 +36,12 @@ export const shouldProcessFilesSeparately = ( // Static function that can be used by both the hook and automation executor export const buildConvertFormData = (parameters: ConvertParameters, selectedFiles: File[]): FormData => { const formData = new FormData(); + const { fromExtension, toExtension, imageOptions, htmlOptions, emailOptions, pdfaOptions, cbrOptions, pdfToCbrOptions, cbzOptions, cbzOutputOptions } = parameters; selectedFiles.forEach(file => { formData.append("fileInput", file); }); - const { fromExtension, toExtension, imageOptions, htmlOptions, emailOptions, pdfaOptions, cbzOptions, cbzOutputOptions } = parameters; if (isImageFormat(toExtension)) { formData.append("imageFormat", toExtension); @@ -67,6 +69,10 @@ export const buildConvertFormData = (parameters: ConvertParameters, selectedFile formData.append("outputFormat", pdfaOptions.outputFormat); } else if (fromExtension === 'pdf' && toExtension === 'csv') { formData.append("pageNumbers", "all"); + } else if (fromExtension === 'cbr' && toExtension === 'pdf') { + formData.append("optimizeForEbook", cbrOptions.optimizeForEbook.toString()); + } else if (fromExtension === 'pdf' && toExtension === 'cbr') { + formData.append("dpi", pdfToCbrOptions.dpi.toString()); } else if (fromExtension === 'cbz' && toExtension === 'pdf') { formData.append("optimizeForEbook", (cbzOptions?.optimizeForEbook ?? false).toString()); } else if (fromExtension === 'pdf' && toExtension === 'cbz') { diff --git a/frontend/src/core/hooks/tools/convert/useConvertParameters.ts b/frontend/src/core/hooks/tools/convert/useConvertParameters.ts index eaa05d571..c6ab72cb8 100644 --- a/frontend/src/core/hooks/tools/convert/useConvertParameters.ts +++ b/frontend/src/core/hooks/tools/convert/useConvertParameters.ts @@ -36,6 +36,12 @@ export interface ConvertParameters extends BaseParameters { pdfaOptions: { outputFormat: string; }; + cbrOptions: { + optimizeForEbook: boolean; + }; + pdfToCbrOptions: { + dpi: number; + }; cbzOptions: { optimizeForEbook: boolean; }; @@ -75,6 +81,12 @@ export const defaultParameters: ConvertParameters = { pdfaOptions: { outputFormat: 'pdfa-1', }, + cbrOptions: { + optimizeForEbook: false, + }, + pdfToCbrOptions: { + dpi: 150, + }, cbzOptions: { optimizeForEbook: false, }, diff --git a/frontend/src/core/tests/convert/ConvertIntegration.test.tsx b/frontend/src/core/tests/convert/ConvertIntegration.test.tsx index bb2f0bcf1..73e776c32 100644 --- a/frontend/src/core/tests/convert/ConvertIntegration.test.tsx +++ b/frontend/src/core/tests/convert/ConvertIntegration.test.tsx @@ -152,6 +152,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, @@ -225,6 +231,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, @@ -276,6 +288,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, @@ -336,6 +354,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, @@ -400,6 +424,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, @@ -462,6 +492,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, @@ -520,6 +556,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, @@ -575,6 +617,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, @@ -632,6 +680,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, @@ -686,6 +740,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, @@ -746,6 +806,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, @@ -805,6 +871,12 @@ describe('Convert Tool Integration Tests', () => { pdfaOptions: { outputFormat: '' }, + cbrOptions: { + optimizeForEbook: false + }, + pdfToCbrOptions: { + dpi: 150 + }, cbzOptions: { optimizeForEbook: false }, diff --git a/frontend/src/core/utils/urlMapping.ts b/frontend/src/core/utils/urlMapping.ts index ba6fe1aaa..acdea74d9 100644 --- a/frontend/src/core/utils/urlMapping.ts +++ b/frontend/src/core/utils/urlMapping.ts @@ -28,6 +28,8 @@ export const URL_TO_TOOL_MAP: Record = { '/pdf-to-pdfa': 'convert', '/pdf-to-word': 'convert', '/pdf-to-xml': 'convert', + '/cbr-to-pdf': 'convert', + '/pdf-to-cbr': 'convert', '/cbz-to-pdf': 'convert', '/pdf-to-cbz': 'convert',