This commit is contained in:
Connor Yoh 2025-08-15 17:37:14 +01:00
parent 457dcbed9e
commit a17326ab27
12 changed files with 132 additions and 344 deletions

View File

@ -0,0 +1,45 @@
import React, { useRef } from "react";
import { FileButton, Button } from "@mantine/core";
import { useTranslation } from "react-i18next";
interface FileUploadButtonProps {
file?: File;
onChange: (file: File | null) => void;
accept?: string;
disabled?: boolean;
placeholder?: string;
variant?: "outline" | "filled" | "light" | "default" | "subtle" | "gradient";
fullWidth?: boolean;
}
const FileUploadButton = ({
file,
onChange,
accept = "*/*",
disabled = false,
placeholder,
variant = "outline",
fullWidth = true
}: FileUploadButtonProps) => {
const { t } = useTranslation();
const resetRef = useRef<() => void>(null);
const defaultPlaceholder = t('common.chooseFile', 'Choose File');
return (
<FileButton
resetRef={resetRef}
onChange={onChange}
accept={accept}
disabled={disabled}
>
{(props) => (
<Button {...props} variant={variant} fullWidth={fullWidth}>
{file ? file.name : (placeholder || defaultPlaceholder)}
</Button>
)}
</FileButton>
);
};
export default FileUploadButton;

View File

@ -1,174 +0,0 @@
import React, { useRef } from "react";
import { Button, Stack, Text, NumberInput, Select, TextInput, FileButton } from "@mantine/core";
import { useTranslation } from "react-i18next";
interface AddWatermarkParameters {
watermarkType: 'text' | 'image';
watermarkText: string;
watermarkImage?: File;
fontSize: number;
rotation: number;
opacity: number;
widthSpacer: number;
heightSpacer: number;
position: string;
overrideX?: number;
overrideY?: number;
}
interface AddWatermarkSettingsProps {
parameters: AddWatermarkParameters;
onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
disabled?: boolean;
}
const AddWatermarkSettings = ({ parameters, onParameterChange, disabled = false }: AddWatermarkSettingsProps) => {
const { t } = useTranslation();
const resetRef = useRef<() => void>(null);
const positionOptions = [
{ value: 'topLeft', label: 'Top Left' },
{ value: 'topCenter', label: 'Top Center' },
{ value: 'topRight', label: 'Top Right' },
{ value: 'centerLeft', label: 'Center Left' },
{ value: 'center', label: 'Center' },
{ value: 'centerRight', label: 'Center Right' },
{ value: 'bottomLeft', label: 'Bottom Left' },
{ value: 'bottomCenter', label: 'Bottom Center' },
{ value: 'bottomRight', label: 'Bottom Right' }
];
return (
<Stack gap="md">
{/* Watermark Type Selection */}
<Stack gap="sm">
<Text size="sm" fw={500}>Watermark Type</Text>
<div style={{ display: 'flex', gap: '4px' }}>
<Button
variant={parameters.watermarkType === 'text' ? 'filled' : 'outline'}
color={parameters.watermarkType === 'text' ? 'blue' : 'gray'}
onClick={() => onParameterChange('watermarkType', 'text')}
disabled={disabled}
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
>
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
Text
</div>
</Button>
<Button
variant={parameters.watermarkType === 'image' ? 'filled' : 'outline'}
color={parameters.watermarkType === 'image' ? 'blue' : 'gray'}
onClick={() => onParameterChange('watermarkType', 'image')}
disabled={disabled}
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
>
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
Image
</div>
</Button>
</div>
</Stack>
{/* Text Watermark Settings */}
{parameters.watermarkType === 'text' && (
<Stack gap="sm">
<Text size="sm" fw={500}>Watermark Text</Text>
<TextInput
placeholder="Enter watermark text"
value={parameters.watermarkText}
onChange={(e) => onParameterChange('watermarkText', e.target.value)}
disabled={disabled}
/>
<Text size="sm" fw={500}>Font Size</Text>
<NumberInput
value={parameters.fontSize}
onChange={(value) => onParameterChange('fontSize', value || 12)}
min={8}
max={72}
disabled={disabled}
/>
</Stack>
)}
{/* Image Watermark Settings */}
{parameters.watermarkType === 'image' && (
<Stack gap="sm">
<Text size="sm" fw={500}>Watermark Image</Text>
<FileButton
resetRef={resetRef}
onChange={(file) => onParameterChange('watermarkImage', file)}
accept="image/*"
disabled={disabled}
>
{(props) => (
<Button {...props} variant="outline" fullWidth>
{parameters.watermarkImage ? parameters.watermarkImage.name : 'Choose Image'}
</Button>
)}
</FileButton>
{parameters.watermarkImage && (
<Text size="xs" c="dimmed">
Selected: {parameters.watermarkImage.name}
</Text>
)}
</Stack>
)}
{/* Position Settings */}
<Stack gap="sm">
<Text size="sm" fw={500}>Position</Text>
<Select
value={parameters.position}
onChange={(value) => value && onParameterChange('position', value)}
data={positionOptions}
disabled={disabled}
/>
</Stack>
{/* Appearance Settings */}
<Stack gap="sm">
<Text size="sm" fw={500}>Rotation (degrees)</Text>
<NumberInput
value={parameters.rotation}
onChange={(value) => onParameterChange('rotation', value || 0)}
min={-360}
max={360}
disabled={disabled}
/>
<Text size="sm" fw={500}>Opacity (%)</Text>
<NumberInput
value={parameters.opacity}
onChange={(value) => onParameterChange('opacity', value || 50)}
min={0}
max={100}
disabled={disabled}
/>
</Stack>
{/* Spacing Settings */}
<Stack gap="sm">
<Text size="sm" fw={500}>Width Spacing</Text>
<NumberInput
value={parameters.widthSpacer}
onChange={(value) => onParameterChange('widthSpacer', value || 50)}
min={0}
max={200}
disabled={disabled}
/>
<Text size="sm" fw={500}>Height Spacing</Text>
<NumberInput
value={parameters.heightSpacer}
onChange={(value) => onParameterChange('heightSpacer', value || 50)}
min={0}
max={200}
disabled={disabled}
/>
</Stack>
</Stack>
);
};
export default AddWatermarkSettings;

View File

@ -1,29 +0,0 @@
import React from "react";
import { Stack, Checkbox } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { AddWatermarkParameters } from "./types";
interface WatermarkAdvancedSettingsProps {
parameters: AddWatermarkParameters;
onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
disabled?: boolean;
}
const WatermarkAdvancedSettings = ({ parameters, onParameterChange, disabled = false }: WatermarkAdvancedSettingsProps) => {
const { t } = useTranslation();
return (
<Stack gap="md">
{/* Output Options */}
<Checkbox
label={t('watermark.settings.convertToImage', 'Convert result to image-based PDF')}
description={t('watermark.settings.convertToImageDesc', 'Creates a PDF with images instead of text (more secure but larger file size)')}
checked={parameters.convertPDFToImage}
onChange={(event) => onParameterChange('convertPDFToImage', event.currentTarget.checked)}
disabled={disabled}
/>
</Stack>
);
};
export default WatermarkAdvancedSettings;

View File

@ -1,7 +1,8 @@
import React, { useRef } from "react"; import React from "react";
import { Stack, Text, FileButton, Button } from "@mantine/core"; import { Stack } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { AddWatermarkParameters } from "./types"; import { AddWatermarkParameters } from "../../../hooks/tools/addWatermark/useAddWatermarkParameters";
import FileUploadButton from "../../shared/FileUploadButton";
interface WatermarkFileProps { interface WatermarkFileProps {
parameters: AddWatermarkParameters; parameters: AddWatermarkParameters;
@ -11,22 +12,16 @@ interface WatermarkFileProps {
const WatermarkFile = ({ parameters, onParameterChange, disabled = false }: WatermarkFileProps) => { const WatermarkFile = ({ parameters, onParameterChange, disabled = false }: WatermarkFileProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const resetRef = useRef<() => void>(null);
return ( return (
<Stack gap="sm"> <Stack gap="sm">
<FileButton <FileUploadButton
resetRef={resetRef} file={parameters.watermarkImage}
onChange={(file) => onParameterChange('watermarkImage', file)} onChange={(file) => onParameterChange('watermarkImage', file)}
accept="image/*" accept="image/*"
disabled={disabled} disabled={disabled}
> placeholder={t('watermark.settings.image.choose', 'Choose Image')}
{(props) => ( />
<Button {...props} variant="outline" fullWidth>
{parameters.watermarkImage ? parameters.watermarkImage.name : t('watermark.settings.image.choose', 'Choose Image')}
</Button>
)}
</FileButton>
</Stack> </Stack>
); );
}; };

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { Stack, Checkbox, Group } from "@mantine/core"; import { Stack, Checkbox, Group } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { AddWatermarkParameters } from "./types"; import { AddWatermarkParameters } from "../../../hooks/tools/addWatermark/useAddWatermarkParameters";
import NumberInputWithUnit from "../shared/NumberInputWithUnit"; import NumberInputWithUnit from "../shared/NumberInputWithUnit";
interface WatermarkFormattingProps { interface WatermarkFormattingProps {

View File

@ -1,7 +1,8 @@
import React from "react"; import React from "react";
import { Stack, Text, Select, ColorInput, NumberInput, Group } from "@mantine/core"; import { Stack, Text, Select, ColorInput, NumberInput, Group } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { AddWatermarkParameters } from "./types"; import { AddWatermarkParameters } from "../../../hooks/tools/addWatermark/useAddWatermarkParameters";
import { alphabetOptions } from "../../../constants/addWatermarkConstants";
interface WatermarkTextStyleProps { interface WatermarkTextStyleProps {
parameters: AddWatermarkParameters; parameters: AddWatermarkParameters;
@ -12,14 +13,6 @@ interface WatermarkTextStyleProps {
const WatermarkTextStyle = ({ parameters, onParameterChange, disabled = false }: WatermarkTextStyleProps) => { const WatermarkTextStyle = ({ parameters, onParameterChange, disabled = false }: WatermarkTextStyleProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const alphabetOptions = [
{ value: "roman", label: "Roman" },
{ value: "arabic", label: "العربية" },
{ value: "japanese", label: "日本語" },
{ value: "korean", label: "한국어" },
{ value: "chinese", label: "简体中文" },
{ value: "thai", label: "ไทย" },
];
return ( return (
<Stack gap="sm"> <Stack gap="sm">

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { Stack, Text, TextInput } from "@mantine/core"; import { Stack, Text, TextInput } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { AddWatermarkParameters } from "./types"; import { AddWatermarkParameters } from "../../../hooks/tools/addWatermark/useAddWatermarkParameters";
interface WatermarkWordingProps { interface WatermarkWordingProps {
parameters: AddWatermarkParameters; parameters: AddWatermarkParameters;

View File

@ -1,13 +0,0 @@
export interface AddWatermarkParameters {
watermarkType?: 'text' | 'image';
watermarkText: string;
watermarkImage?: File;
fontSize: number;
rotation: number;
opacity: number;
widthSpacer: number;
heightSpacer: number;
alphabet: string;
customColor: string;
convertPDFToImage: boolean;
}

View File

@ -1,5 +1,43 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips'; import { TooltipContent, TooltipTip } from '../../types/tips';
// Shared tooltip content to reduce duplication
const useSharedWatermarkContent = () => {
const { t } = useTranslation();
const languageSupportTip: TooltipTip = {
title: t("watermark.tooltip.language.title", "Language Support"),
description: t("watermark.tooltip.language.text", "Choose the appropriate language setting to ensure proper font rendering for your text."),
bullets: [
t("watermark.tooltip.language.bullet1", "Roman/Latin for Western languages"),
t("watermark.tooltip.language.bullet2", "Arabic for Arabic script"),
t("watermark.tooltip.language.bullet3", "Japanese, Korean, Chinese for Asian languages"),
t("watermark.tooltip.language.bullet4", "Thai for Thai script")
]
};
const appearanceTip: TooltipTip = {
title: t("watermark.tooltip.appearance.title", "Appearance Settings"),
description: t("watermark.tooltip.appearance.text", "Control how your watermark looks and blends with the document."),
bullets: [
t("watermark.tooltip.appearance.bullet1", "Rotation: -360° to 360° for angled watermarks"),
t("watermark.tooltip.appearance.bullet2", "Opacity: 0-100% for transparency control"),
t("watermark.tooltip.appearance.bullet3", "Lower opacity creates subtle watermarks")
]
};
const spacingTip: TooltipTip = {
title: t("watermark.tooltip.spacing.title", "Spacing Control"),
description: t("watermark.tooltip.spacing.text", "Adjust the spacing between repeated watermarks across the page."),
bullets: [
t("watermark.tooltip.spacing.bullet1", "Width spacing: Horizontal distance between watermarks"),
t("watermark.tooltip.spacing.bullet2", "Height spacing: Vertical distance between watermarks"),
t("watermark.tooltip.spacing.bullet3", "Higher values create more spread out patterns")
]
};
return { languageSupportTip, appearanceTip, spacingTip };
};
export const useWatermarkTypeTips = (): TooltipContent => { export const useWatermarkTypeTips = (): TooltipContent => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -35,67 +73,7 @@ export const useWatermarkTypeTips = (): TooltipContent => {
}; };
}; };
export const useWatermarkContentTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("watermark.tooltip.content.header.title", "Content Configuration")
},
tips: [
{
title: t("watermark.tooltip.content.text.title", "Text Settings"),
description: t("watermark.tooltip.content.text.text", "Configure your text watermark appearance and language support."),
bullets: [
t("watermark.tooltip.content.text.bullet1", "Enter your watermark text"),
t("watermark.tooltip.content.text.bullet2", "Adjust font size (8-72pt)"),
t("watermark.tooltip.content.text.bullet3", "Select language/script support"),
t("watermark.tooltip.content.text.bullet4", "Choose custom colors")
]
},
{
title: t("watermark.tooltip.content.language.title", "Language Support"),
description: t("watermark.tooltip.content.language.text", "Choose the appropriate language setting to ensure proper font rendering for your text."),
bullets: [
t("watermark.tooltip.content.language.bullet1", "Roman/Latin for Western languages"),
t("watermark.tooltip.content.language.bullet2", "Arabic for Arabic script"),
t("watermark.tooltip.content.language.bullet3", "Japanese, Korean, Chinese for Asian languages"),
t("watermark.tooltip.content.language.bullet4", "Thai for Thai script")
]
}
]
};
};
export const useWatermarkStyleTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("watermark.tooltip.style.header.title", "Style & Positioning")
},
tips: [
{
title: t("watermark.tooltip.style.appearance.title", "Appearance Settings"),
description: t("watermark.tooltip.style.appearance.text", "Control how your watermark looks and blends with the document."),
bullets: [
t("watermark.tooltip.style.appearance.bullet1", "Rotation: -360° to 360° for angled watermarks"),
t("watermark.tooltip.style.appearance.bullet2", "Opacity: 0-100% for transparency control"),
t("watermark.tooltip.style.appearance.bullet3", "Lower opacity creates subtle watermarks")
]
},
{
title: t("watermark.tooltip.style.spacing.title", "Spacing Control"),
description: t("watermark.tooltip.style.spacing.text", "Adjust the spacing between repeated watermarks across the page."),
bullets: [
t("watermark.tooltip.style.spacing.bullet1", "Width spacing: Horizontal distance between watermarks"),
t("watermark.tooltip.style.spacing.bullet2", "Height spacing: Vertical distance between watermarks"),
t("watermark.tooltip.style.spacing.bullet3", "Higher values create more spread out patterns")
]
}
]
};
};
export const useWatermarkWordingTips = (): TooltipContent => { export const useWatermarkWordingTips = (): TooltipContent => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -129,22 +107,14 @@ export const useWatermarkWordingTips = (): TooltipContent => {
export const useWatermarkTextStyleTips = (): TooltipContent => { export const useWatermarkTextStyleTips = (): TooltipContent => {
const { t } = useTranslation(); const { t } = useTranslation();
const { languageSupportTip } = useSharedWatermarkContent();
return { return {
header: { header: {
title: t("watermark.tooltip.textStyle.header.title", "Text Style") title: t("watermark.tooltip.textStyle.header.title", "Text Style")
}, },
tips: [ tips: [
{ languageSupportTip,
title: t("watermark.tooltip.textStyle.language.title", "Language Support"),
description: t("watermark.tooltip.textStyle.language.text", "Choose the appropriate language setting to ensure proper font rendering."),
bullets: [
t("watermark.tooltip.textStyle.language.bullet1", "Roman/Latin for Western languages"),
t("watermark.tooltip.textStyle.language.bullet2", "Arabic for Arabic script"),
t("watermark.tooltip.textStyle.language.bullet3", "Japanese, Korean, Chinese for Asian languages"),
t("watermark.tooltip.textStyle.language.bullet4", "Thai for Thai script")
]
},
{ {
title: t("watermark.tooltip.textStyle.color.title", "Color Selection"), title: t("watermark.tooltip.textStyle.color.title", "Color Selection"),
description: t("watermark.tooltip.textStyle.color.text", "Choose a color that provides good contrast with your document content."), description: t("watermark.tooltip.textStyle.color.text", "Choose a color that provides good contrast with your document content."),
@ -190,30 +160,15 @@ export const useWatermarkFileTips = (): TooltipContent => {
export const useWatermarkFormattingTips = (): TooltipContent => { export const useWatermarkFormattingTips = (): TooltipContent => {
const { t } = useTranslation(); const { t } = useTranslation();
const { appearanceTip, spacingTip } = useSharedWatermarkContent();
return { return {
header: { header: {
title: t("watermark.tooltip.formatting.header.title", "Formatting & Layout") title: t("watermark.tooltip.formatting.header.title", "Formatting & Layout")
}, },
tips: [ tips: [
{ appearanceTip,
title: t("watermark.tooltip.formatting.appearance.title", "Appearance Settings"), spacingTip,
description: t("watermark.tooltip.formatting.appearance.text", "Control how your watermark looks and blends with the document."),
bullets: [
t("watermark.tooltip.formatting.appearance.bullet1", "Rotation: -360° to 360° for angled watermarks"),
t("watermark.tooltip.formatting.appearance.bullet2", "Opacity: 0-100% for transparency control"),
t("watermark.tooltip.formatting.appearance.bullet3", "Lower opacity creates subtle watermarks")
]
},
{
title: t("watermark.tooltip.formatting.spacing.title", "Spacing Control"),
description: t("watermark.tooltip.formatting.spacing.text", "Adjust the spacing between repeated watermarks across the page."),
bullets: [
t("watermark.tooltip.formatting.spacing.bullet1", "Width spacing: Horizontal distance between watermarks"),
t("watermark.tooltip.formatting.spacing.bullet2", "Height spacing: Vertical distance between watermarks"),
t("watermark.tooltip.formatting.spacing.bullet3", "Higher values create more spread out patterns")
]
},
{ {
title: t("watermark.tooltip.formatting.security.title", "Security Option"), title: t("watermark.tooltip.formatting.security.title", "Security Option"),
description: t("watermark.tooltip.formatting.security.text", "Convert the final PDF to an image-based format for enhanced security."), description: t("watermark.tooltip.formatting.security.text", "Convert the final PDF to an image-based format for enhanced security."),

View File

@ -0,0 +1,28 @@
import { AddWatermarkParameters } from "../hooks/tools/addWatermark/useAddWatermarkParameters";
export interface AlphabetOption {
value: string;
label: string;
}
export const alphabetOptions: AlphabetOption[] = [
{ value: "roman", label: "Roman" },
{ value: "arabic", label: "العربية" },
{ value: "japanese", label: "日本語" },
{ value: "korean", label: "한국어" },
{ value: "chinese", label: "简体中文" },
{ value: "thai", label: "ไทย" },
];
export const defaultWatermarkParameters: AddWatermarkParameters = {
watermarkType: undefined,
watermarkText: '',
fontSize: 12,
rotation: 0,
opacity: 50,
widthSpacer: 50,
heightSpacer: 50,
alphabet: 'roman',
customColor: '#d3d3d3',
convertPDFToImage: false
};

View File

@ -1,4 +1,5 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { defaultWatermarkParameters } from '../../../constants/addWatermarkConstants';
export interface AddWatermarkParameters { export interface AddWatermarkParameters {
watermarkType?: 'text' | 'image'; watermarkType?: 'text' | 'image';
@ -14,21 +15,9 @@ export interface AddWatermarkParameters {
convertPDFToImage: boolean; convertPDFToImage: boolean;
} }
const defaultParameters: AddWatermarkParameters = {
watermarkType: undefined,
watermarkText: '',
fontSize: 12,
rotation: 0,
opacity: 50,
widthSpacer: 50,
heightSpacer: 50,
alphabet: 'roman',
customColor: '#d3d3d3',
convertPDFToImage: false
};
export const useAddWatermarkParameters = () => { export const useAddWatermarkParameters = () => {
const [parameters, setParameters] = useState<AddWatermarkParameters>(defaultParameters); const [parameters, setParameters] = useState<AddWatermarkParameters>(defaultWatermarkParameters);
const updateParameter = useCallback(<K extends keyof AddWatermarkParameters>( const updateParameter = useCallback(<K extends keyof AddWatermarkParameters>(
key: K, key: K,
@ -38,7 +27,7 @@ export const useAddWatermarkParameters = () => {
}, []); }, []);
const resetParameters = useCallback(() => { const resetParameters = useCallback(() => {
setParameters(defaultParameters); setParameters(defaultWatermarkParameters);
}, []); }, []);
const validateParameters = useCallback((): boolean => { const validateParameters = useCallback((): boolean => {

View File

@ -22,7 +22,6 @@ import {
useWatermarkFormattingTips, useWatermarkFormattingTips,
} from "../components/tooltips/useWatermarkTips"; } from "../components/tooltips/useWatermarkTips";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps } from "../types/tool";
import { isVisible } from "@testing-library/user-event/dist/utils";
const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();