diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 17bafc6f4..c737b85a5 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -619,8 +619,7 @@ "title": "Auto Split by Size/Count", "desc": "Automatically split PDFs by file size or page count" }, - "replaceColorPdf": { - "tags": "color,replace,invert", + "replaceColor": { "title": "Replace & Invert Colour", "desc": "Replace or invert colours in PDF documents" }, @@ -2533,25 +2532,48 @@ }, "selectCustomCert": "Custom Certificate File X.509 (Optional)" }, - "replace-color": { - "title": "Advanced Colour options", - "header": "Replace-Invert Colour PDF", - "selectText": { - "1": "Replace or Invert colour Options", - "2": "Default(Default high contrast colours)", - "3": "Custom(Customised colours)", - "4": "Full-Invert(Invert all colours)", - "5": "High contrast colour options", - "6": "white text on black background", - "7": "Black text on white background", - "8": "Yellow text on black background", - "9": "Green text on black background", - "10": "Choose text Colour", - "11": "Choose background Colour" + "replaceColor": { + "labels": { + "settings": "Settings", + "colourOperation": "Colour operation" }, - "submit": "Replace" + "options": { + "highContrast": "High contrast", + "invertAll": "Invert all colours", + "custom": "Custom" + }, + "tooltip": { + "header": { + "title": "Replace & Invert Colour Settings Overview" + }, + "description": { + "title": "Description", + "text": "Transform PDF colours to improve readability and accessibility. Choose from high contrast presets, invert all colours, or create custom colour schemes." + }, + "highContrast": { + "title": "High Contrast", + "text": "Apply predefined high contrast colour combinations designed for better readability and accessibility compliance.", + "bullet1": "White text on black background - Classic dark mode", + "bullet2": "Black text on white background - Standard high contrast", + "bullet3": "Yellow text on black background - High visibility option", + "bullet4": "Green text on black background - Alternative high contrast" + }, + "invertAll": { + "title": "Invert All Colours", + "text": "Completely invert all colours in the PDF, creating a negative-like effect. Useful for creating dark mode versions of documents or reducing eye strain in low-light conditions." + }, + "custom": { + "title": "Custom Colours", + "text": "Define your own text and background colours using the colour pickers. Perfect for creating branded documents or specific accessibility requirements.", + "bullet1": "Text colour - Choose the colour for text elements", + "bullet2": "Background colour - Set the background colour for the document" + } + }, + "error": { + "failed": "An error occurred while processing the colour replacement." + } }, - "replaceColorPdf": { + "replaceColor": { "tags": "Replace Colour,Page operations,Back end,server side" }, "login": { diff --git a/frontend/src/components/tools/replaceColor/ReplaceColorSettings.tsx b/frontend/src/components/tools/replaceColor/ReplaceColorSettings.tsx new file mode 100644 index 000000000..8630d1811 --- /dev/null +++ b/frontend/src/components/tools/replaceColor/ReplaceColorSettings.tsx @@ -0,0 +1,108 @@ +import React from "react"; +import { Stack, Text, Select, ColorInput } from "@mantine/core"; +import { useTranslation } from "react-i18next"; +import { ReplaceColorParameters } from "../../../hooks/tools/replaceColor/useReplaceColorParameters"; + +interface ReplaceColorSettingsProps { + parameters: ReplaceColorParameters; + onParameterChange: (key: K, value: ReplaceColorParameters[K]) => void; + disabled?: boolean; +} + +const ReplaceColorSettings = ({ parameters, onParameterChange, disabled = false }: ReplaceColorSettingsProps) => { + const { t } = useTranslation(); + + const replaceAndInvertOptions = [ + { + value: 'HIGH_CONTRAST_COLOR', + label: t('replaceColor.options.highContrast', 'High contrast') + }, + { + value: 'FULL_INVERSION', + label: t('replaceColor.options.invertAll', 'Invert all colours') + }, + { + value: 'CUSTOM_COLOR', + label: t('replaceColor.options.custom', 'Custom') + } + ]; + + const highContrastOptions = [ + { + value: 'WHITE_TEXT_ON_BLACK', + label: t('replace-color.selectText.6', 'White text on black background') + }, + { + value: 'BLACK_TEXT_ON_WHITE', + label: t('replace-color.selectText.7', 'Black text on white background') + }, + { + value: 'YELLOW_TEXT_ON_BLACK', + label: t('replace-color.selectText.8', 'Yellow text on black background') + }, + { + value: 'GREEN_TEXT_ON_BLACK', + label: t('replace-color.selectText.9', 'Green text on black background') + } + ]; + + return ( + + + + {t('replaceColor.labels.colourOperation', 'Colour operation')} + + value && onParameterChange('highContrastColorCombination', value as ReplaceColorParameters['highContrastColorCombination'])} + data={highContrastOptions} + disabled={disabled} + /> + + )} + + {parameters.replaceAndInvertOption === 'CUSTOM_COLOR' && ( + <> + + + {t('replace-color.selectText.10', 'Choose text Color')} + + onParameterChange('textColor', value)} + format="hex" + disabled={disabled} + /> + + + + + {t('replace-color.selectText.11', 'Choose background Color')} + + onParameterChange('backGroundColor', value)} + format="hex" + disabled={disabled} + /> + + + )} + + ); +}; + +export default ReplaceColorSettings; \ No newline at end of file diff --git a/frontend/src/components/tooltips/useReplaceColorTips.ts b/frontend/src/components/tooltips/useReplaceColorTips.ts new file mode 100644 index 000000000..58cc5f9d5 --- /dev/null +++ b/frontend/src/components/tooltips/useReplaceColorTips.ts @@ -0,0 +1,40 @@ +import { useTranslation } from 'react-i18next'; +import { TooltipContent } from '../../types/tips'; + +export const useReplaceColorTips = (): TooltipContent => { + const { t } = useTranslation(); + + return { + header: { + title: t("replaceColor.tooltip.header.title", "Replace & Invert Colour Settings Overview") + }, + tips: [ + { + title: t("replaceColor.tooltip.description.title", "Description"), + description: t("replaceColor.tooltip.description.text", "Transform PDF colours to improve readability and accessibility. Choose from high contrast presets, invert all colours, or create custom colour schemes.") + }, + { + title: t("replaceColor.tooltip.highContrast.title", "High Contrast"), + description: t("replaceColor.tooltip.highContrast.text", "Apply predefined high contrast colour combinations designed for better readability and accessibility compliance."), + bullets: [ + t("replaceColor.tooltip.highContrast.bullet1", "White text on black background - Classic dark mode"), + t("replaceColor.tooltip.highContrast.bullet2", "Black text on white background - Standard high contrast"), + t("replaceColor.tooltip.highContrast.bullet3", "Yellow text on black background - High visibility option"), + t("replaceColor.tooltip.highContrast.bullet4", "Green text on black background - Alternative high contrast") + ] + }, + { + title: t("replaceColor.tooltip.invertAll.title", "Invert All Colours"), + description: t("replaceColor.tooltip.invertAll.text", "Completely invert all colours in the PDF, creating a negative-like effect. Useful for creating dark mode versions of documents or reducing eye strain in low-light conditions.") + }, + { + title: t("replaceColor.tooltip.custom.title", "Custom Colours"), + description: t("replaceColor.tooltip.custom.text", "Define your own text and background colours using the colour pickers. Perfect for creating branded documents or specific accessibility requirements."), + bullets: [ + t("replaceColor.tooltip.custom.bullet1", "Text colour - Choose the colour for text elements"), + t("replaceColor.tooltip.custom.bullet2", "Background colour - Set the background colour for the document") + ] + } + ] + }; +}; \ No newline at end of file diff --git a/frontend/src/data/useTranslatedToolRegistry.tsx b/frontend/src/data/useTranslatedToolRegistry.tsx index 5cd9970b3..f2bc2b197 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -50,6 +50,7 @@ import { redactOperationConfig } from "../hooks/tools/redact/useRedactOperation" import { rotateOperationConfig } from "../hooks/tools/rotate/useRotateOperation"; import { changeMetadataOperationConfig } from "../hooks/tools/changeMetadata/useChangeMetadataOperation"; import { cropOperationConfig } from "../hooks/tools/crop/useCropOperation"; +import { replaceColorOperationConfig } from "../hooks/tools/replaceColor/useReplaceColorOperation"; import CompressSettings from "../components/tools/compress/CompressSettings"; import SplitSettings from "../components/tools/split/SplitSettings"; import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings"; @@ -68,6 +69,7 @@ import RedactSingleStepSettings from "../components/tools/redact/RedactSingleSte import RotateSettings from "../components/tools/rotate/RotateSettings"; import Redact from "../tools/Redact"; import AdjustPageScale from "../tools/AdjustPageScale"; +import ReplaceColor from "../tools/ReplaceColor"; import ScannerImageSplit from "../tools/ScannerImageSplit"; import { ToolId } from "../types/toolId"; import MergeSettings from '../components/tools/merge/MergeSettings'; @@ -77,6 +79,7 @@ import AdjustPageScaleSettings from "../components/tools/adjustPageScale/AdjustP import ScannerImageSplitSettings from "../components/tools/scannerImageSplit/ScannerImageSplitSettings"; import ChangeMetadataSingleStep from "../components/tools/changeMetadata/ChangeMetadataSingleStep"; import CropSettings from "../components/tools/crop/CropSettings"; +import ReplaceColorSettings from "../components/tools/replaceColor/ReplaceColorSettings"; const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI @@ -646,14 +649,18 @@ export function useFlatToolRegistry(): ToolRegistry { subcategoryId: SubcategoryId.ADVANCED_FORMATTING, synonyms: getSynonyms(t, "overlayPdfs"), }, - replaceColorPdf: { + replaceColor: { icon: , - name: t("home.replaceColorPdf.title", "Replace & Invert Color"), - component: null, - description: t("home.replaceColorPdf.desc", "Replace or invert colors in PDF documents"), + name: t("home.replaceColor.title", "Replace & Invert Color"), + component: ReplaceColor, + description: t("home.replaceColor.desc", "Replace or invert colors in PDF documents"), categoryId: ToolCategoryId.ADVANCED_TOOLS, subcategoryId: SubcategoryId.ADVANCED_FORMATTING, - synonyms: getSynonyms(t, "replaceColorPdf"), + maxFiles: -1, + endpoints: ["replace-invert-pdf"], + operationConfig: replaceColorOperationConfig, + settingsComponent: ReplaceColorSettings, + synonyms: getSynonyms(t, "replaceColor"), }, addImage: { icon: , diff --git a/frontend/src/hooks/tools/replaceColor/useReplaceColorOperation.ts b/frontend/src/hooks/tools/replaceColor/useReplaceColorOperation.ts new file mode 100644 index 000000000..e2a101a1c --- /dev/null +++ b/frontend/src/hooks/tools/replaceColor/useReplaceColorOperation.ts @@ -0,0 +1,38 @@ +import { useTranslation } from 'react-i18next'; +import { ToolType, useToolOperation } from '../shared/useToolOperation'; +import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; +import { ReplaceColorParameters, defaultParameters } from './useReplaceColorParameters'; + +export const buildReplaceColorFormData = (parameters: ReplaceColorParameters, file: File): FormData => { + const formData = new FormData(); + formData.append('fileInput', file); + + formData.append('replaceAndInvertOption', parameters.replaceAndInvertOption); + + if (parameters.replaceAndInvertOption === 'HIGH_CONTRAST_COLOR') { + formData.append('highContrastColorCombination', parameters.highContrastColorCombination); + } else if (parameters.replaceAndInvertOption === 'CUSTOM_COLOR') { + formData.append('textColor', parameters.textColor); + formData.append('backGroundColor', parameters.backGroundColor); + } + + return formData; +}; + +export const replaceColorOperationConfig = { + toolType: ToolType.singleFile, + buildFormData: buildReplaceColorFormData, + operationType: 'replaceColor', + endpoint: '/api/v1/misc/replace-invert-pdf', + multiFileEndpoint: false, + defaultParameters, +} as const; + +export const useReplaceColorOperation = () => { + const { t } = useTranslation(); + + return useToolOperation({ + ...replaceColorOperationConfig, + getErrorMessage: createStandardErrorHandler(t('replaceColor.error.failed', 'An error occurred while processing the color replacement.')) + }); +}; \ No newline at end of file diff --git a/frontend/src/hooks/tools/replaceColor/useReplaceColorParameters.ts b/frontend/src/hooks/tools/replaceColor/useReplaceColorParameters.ts new file mode 100644 index 000000000..2b269aa1a --- /dev/null +++ b/frontend/src/hooks/tools/replaceColor/useReplaceColorParameters.ts @@ -0,0 +1,29 @@ +import { BaseParameters } from '../../../types/parameters'; +import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters'; + +export interface ReplaceColorParameters extends BaseParameters { + replaceAndInvertOption: 'HIGH_CONTRAST_COLOR' | 'CUSTOM_COLOR' | 'FULL_INVERSION'; + highContrastColorCombination: 'WHITE_TEXT_ON_BLACK' | 'BLACK_TEXT_ON_WHITE' | 'YELLOW_TEXT_ON_BLACK' | 'GREEN_TEXT_ON_BLACK'; + textColor: string; + backGroundColor: string; +} + +export const defaultParameters: ReplaceColorParameters = { + replaceAndInvertOption: 'HIGH_CONTRAST_COLOR', + highContrastColorCombination: 'WHITE_TEXT_ON_BLACK', + textColor: '#000000', + backGroundColor: '#ffffff', +}; + +export type ReplaceColorParametersHook = BaseParametersHook; + +export const useReplaceColorParameters = (): ReplaceColorParametersHook => { + return useBaseParameters({ + defaultParameters, + endpointName: 'replace-invert-pdf', + validateFn: () => { + // All parameters are always valid as they have defaults + return true; + }, + }); +}; \ No newline at end of file diff --git a/frontend/src/tools/ReplaceColor.tsx b/frontend/src/tools/ReplaceColor.tsx new file mode 100644 index 000000000..5486fc591 --- /dev/null +++ b/frontend/src/tools/ReplaceColor.tsx @@ -0,0 +1,58 @@ +import { useTranslation } from "react-i18next"; +import { createToolFlow } from "../components/tools/shared/createToolFlow"; +import ReplaceColorSettings from "../components/tools/replaceColor/ReplaceColorSettings"; +import { useReplaceColorParameters } from "../hooks/tools/replaceColor/useReplaceColorParameters"; +import { useReplaceColorOperation } from "../hooks/tools/replaceColor/useReplaceColorOperation"; +import { useBaseTool } from "../hooks/tools/shared/useBaseTool"; +import { BaseToolProps, ToolComponent } from "../types/tool"; +import { useReplaceColorTips } from "../components/tooltips/useReplaceColorTips"; + +const ReplaceColor = (props: BaseToolProps) => { + const { t } = useTranslation(); + const replaceColorTips = useReplaceColorTips(); + + const base = useBaseTool( + 'replaceColor', + useReplaceColorParameters, + useReplaceColorOperation, + props + ); + + return createToolFlow({ + files: { + selectedFiles: base.selectedFiles, + isCollapsed: base.hasResults, + }, + steps: [ + { + title: t("replaceColor.labels.settings", "Settings"), + isCollapsed: base.settingsCollapsed, + onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined, + tooltip: replaceColorTips, + content: ( + + ), + }, + ], + executeButton: { + text: t("replace-color.submit", "Replace"), + isVisible: !base.hasResults, + loadingText: t("loading"), + onClick: base.handleExecute, + disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled, + }, + review: { + isVisible: base.hasResults, + operation: base.operation, + title: t("replace-color.title", "Replace-Invert-Color"), + onFileClick: base.handleThumbnailClick, + onUndo: base.handleUndo, + }, + }); +}; + +export default ReplaceColor as ToolComponent; \ No newline at end of file diff --git a/frontend/src/types/toolId.ts b/frontend/src/types/toolId.ts index 49bdb6523..bdc614c6f 100644 --- a/frontend/src/types/toolId.ts +++ b/frontend/src/types/toolId.ts @@ -49,7 +49,7 @@ export const TOOL_IDS = [ 'validateSignature', 'read', 'automate', - 'replaceColorPdf', + 'replaceColor', 'showJS', 'devApi', 'devFolderScanning', diff --git a/frontend/src/utils/urlMapping.ts b/frontend/src/utils/urlMapping.ts index 016591ab3..409eaea3f 100644 --- a/frontend/src/utils/urlMapping.ts +++ b/frontend/src/utils/urlMapping.ts @@ -76,7 +76,7 @@ export const URL_TO_TOOL_MAP: Record = { '/extract-images': 'extractImages', '/adjust-contrast': 'adjustContrast', '/fake-scan': 'fakeScan', - '/replace-color-pdf': 'replaceColorPdf', + '/replace-color-pdf': 'replaceColor', // Metadata and info '/change-metadata': 'changeMetadata', @@ -116,7 +116,7 @@ export const URL_TO_TOOL_MAP: Record = { '/view-pdf': 'read', '/get-info-on-pdf': 'getPdfInfo', '/remove-image-pdf': 'removeImage', - '/replace-and-invert-color-pdf': 'replaceColorPdf', + '/replace-and-invert-color-pdf': 'replaceColor', '/pipeline': 'automate', '/extract-image-scans': 'scannerImageSplit', '/show-javascript': 'showJS',