This commit is contained in:
Connor Yoh 2025-08-15 16:53:53 +01:00
parent 9f8a9e45b1
commit 0bd050396c
10 changed files with 550 additions and 157 deletions

View File

@ -1766,11 +1766,10 @@
"rotation": "Rotation (degrees)", "rotation": "Rotation (degrees)",
"opacity": "Opacity (%)", "opacity": "Opacity (%)",
"spacing": { "spacing": {
"width": "Width Spacing", "horizontal": "Horizontal Spacing",
"height": "Height Spacing" "vertical": "Vertical Spacing"
}, },
"convertToImage": "Convert result to image-based PDF", "convertToImage": "Flatten PDF pages to images"
"convertToImageDesc": "Creates a PDF with images instead of text (more secure but larger file size)"
}, },
"alphabet": { "alphabet": {
"roman": "Roman/Latin", "roman": "Roman/Latin",
@ -1782,10 +1781,10 @@
}, },
"steps": { "steps": {
"type": "Watermark Type", "type": "Watermark Type",
"textContent": "Text Content", "wording": "Wording",
"imageContent": "Image Content", "textStyle": "Style",
"style": "Style & Position", "file": "Watermark File",
"advanced": "Advanced Options" "formatting": "Formatting"
}, },
"results": { "results": {
"title": "Watermark Results" "title": "Watermark Results"
@ -1854,6 +1853,91 @@
"bullet3": "Higher values create more spread out patterns" "bullet3": "Higher values create more spread out patterns"
} }
}, },
"wording": {
"header": {
"title": "Text Content"
},
"text": {
"title": "Watermark Text",
"text": "Enter the text that will appear as your watermark across the document.",
"bullet1": "Keep it concise for better readability",
"bullet2": "Common examples: 'CONFIDENTIAL', 'DRAFT', company name",
"bullet3": "Supports all Unicode characters"
},
"fontSize": {
"title": "Font Size",
"text": "Control the size of your watermark text (8-72 points).",
"bullet1": "Smaller sizes (8-14pt) for subtle watermarks",
"bullet2": "Medium sizes (16-24pt) for standard visibility",
"bullet3": "Large sizes (30-72pt) for prominent watermarks"
}
},
"textStyle": {
"header": {
"title": "Text Style"
},
"language": {
"title": "Language Support",
"text": "Choose the appropriate language setting to ensure proper font rendering.",
"bullet1": "Roman/Latin for Western languages",
"bullet2": "Arabic for Arabic script",
"bullet3": "Japanese, Korean, Chinese for Asian languages",
"bullet4": "Thai for Thai script"
},
"color": {
"title": "Color Selection",
"text": "Choose a color that provides good contrast with your document content.",
"bullet1": "Light gray (#d3d3d3) for subtle watermarks",
"bullet2": "Black or dark colors for high contrast",
"bullet3": "Custom colors for branding purposes"
}
},
"file": {
"header": {
"title": "Image Upload"
},
"upload": {
"title": "Image Selection",
"text": "Upload an image file to use as your watermark.",
"bullet1": "Supports common formats: PNG, JPG, GIF, BMP",
"bullet2": "PNG with transparency works best",
"bullet3": "Higher resolution images maintain quality better"
},
"recommendations": {
"title": "Best Practices",
"text": "Tips for optimal image watermark results.",
"bullet1": "Use logos or stamps with transparent backgrounds",
"bullet2": "Simple designs work better than complex images",
"bullet3": "Consider the final document size when choosing resolution"
}
},
"formatting": {
"header": {
"title": "Formatting & Layout"
},
"appearance": {
"title": "Appearance Settings",
"text": "Control how your watermark looks and blends with the document.",
"bullet1": "Rotation: -360° to 360° for angled watermarks",
"bullet2": "Opacity: 0-100% for transparency control",
"bullet3": "Lower opacity creates subtle watermarks"
},
"spacing": {
"title": "Spacing Control",
"text": "Adjust the spacing between repeated watermarks across the page.",
"bullet1": "Horizontal spacing: Distance between watermarks left to right",
"bullet2": "Vertical spacing: Distance between watermarks top to bottom",
"bullet3": "Higher values create more spread out patterns"
},
"security": {
"title": "Security Option",
"text": "Flatten PDF pages to images for enhanced security.",
"bullet1": "Prevents text selection and copying",
"bullet2": "Makes watermarks harder to remove",
"bullet3": "Results in larger file sizes",
"bullet4": "Best for sensitive or copyrighted content"
}
},
"advanced": { "advanced": {
"header": { "header": {
"title": "Advanced Options" "title": "Advanced Options"

View File

@ -1,93 +0,0 @@
import React, { useRef } from "react";
import { Stack, Text, TextInput, FileButton, Button, NumberInput, Select, ColorInput } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { AddWatermarkParameters } from "./types";
interface WatermarkContentSettingsProps {
parameters: AddWatermarkParameters;
onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
disabled?: boolean;
}
const WatermarkContentSettings = ({ parameters, onParameterChange, disabled = false }: WatermarkContentSettingsProps) => {
const { t } = useTranslation();
const resetRef = useRef<() => void>(null);
const alphabetOptions = [
{ value: 'roman', label: t('watermark.alphabet.roman', 'Roman/Latin') },
{ value: 'arabic', label: t('watermark.alphabet.arabic', 'Arabic') },
{ value: 'japanese', label: t('watermark.alphabet.japanese', 'Japanese') },
{ value: 'korean', label: t('watermark.alphabet.korean', 'Korean') },
{ value: 'chinese', label: t('watermark.alphabet.chinese', 'Chinese') },
{ value: 'thai', label: t('watermark.alphabet.thai', 'Thai') }
];
return (
<Stack gap="md">
{/* Text Watermark Settings */}
{parameters.watermarkType === 'text' && (
<Stack gap="sm">
<Text size="sm" fw={500}>{t('watermark.settings.text.label', 'Watermark Text')}</Text>
<TextInput
placeholder={t('watermark.settings.text.placeholder', 'Enter watermark text')}
value={parameters.watermarkText}
onChange={(e) => onParameterChange('watermarkText', e.target.value)}
disabled={disabled}
/>
<Text size="sm" fw={500}>{t('watermark.settings.fontSize', 'Font Size')}</Text>
<NumberInput
value={parameters.fontSize}
onChange={(value) => onParameterChange('fontSize', value || 12)}
min={8}
max={72}
disabled={disabled}
/>
<Text size="sm" fw={500}>{t('watermark.settings.alphabet', 'Font/Language')}</Text>
<Select
value={parameters.alphabet}
onChange={(value) => value && onParameterChange('alphabet', value)}
data={alphabetOptions}
disabled={disabled}
/>
<Text size="sm" fw={500}>{t('watermark.settings.color', 'Watermark Color')}</Text>
<ColorInput
value={parameters.customColor}
onChange={(value) => onParameterChange('customColor', value)}
disabled={disabled}
format="hex"
swatches={['#d3d3d3', '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff']}
/>
</Stack>
)}
{/* Image Watermark Settings */}
{parameters.watermarkType === 'image' && (
<Stack gap="sm">
<Text size="sm" fw={500}>{t('watermark.settings.image.label', '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 : t('watermark.settings.image.choose', 'Choose Image')}
</Button>
)}
</FileButton>
{parameters.watermarkImage && (
<Text size="xs" c="dimmed">
{t('watermark.settings.image.selected', 'Selected: {{filename}}', { filename: parameters.watermarkImage.name })}
</Text>
)}
</Stack>
)}
</Stack>
);
};
export default WatermarkContentSettings;

View File

@ -0,0 +1,34 @@
import React, { useRef } from "react";
import { Stack, Text, FileButton, Button } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { AddWatermarkParameters } from "./types";
interface WatermarkFileProps {
parameters: AddWatermarkParameters;
onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
disabled?: boolean;
}
const WatermarkFile = ({ parameters, onParameterChange, disabled = false }: WatermarkFileProps) => {
const { t } = useTranslation();
const resetRef = useRef<() => void>(null);
return (
<Stack gap="sm">
<FileButton
resetRef={resetRef}
onChange={(file) => onParameterChange('watermarkImage', file)}
accept="image/*"
disabled={disabled}
>
{(props) => (
<Button {...props} variant="outline" fullWidth>
{parameters.watermarkImage ? parameters.watermarkImage.name : t('watermark.settings.image.choose', 'Choose Image')}
</Button>
)}
</FileButton>
</Stack>
);
};
export default WatermarkFile;

View File

@ -0,0 +1,73 @@
import React from "react";
import { Stack, Checkbox, Group } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { AddWatermarkParameters } from "./types";
import NumberInputWithUnit from "../shared/NumberInputWithUnit";
interface WatermarkFormattingProps {
parameters: AddWatermarkParameters;
onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
disabled?: boolean;
}
const WatermarkFormatting = ({ parameters, onParameterChange, disabled = false }: WatermarkFormattingProps) => {
const { t } = useTranslation();
return (
<Stack gap="md">
{/* Position & Appearance - 2 per row */}
<Group grow align="flex-start">
<NumberInputWithUnit
label={t('watermark.settings.rotation', 'Rotation')}
value={parameters.rotation}
onChange={(value) => onParameterChange('rotation', typeof value === 'number' ? value : 0)}
unit="°"
min={-360}
max={360}
disabled={disabled}
/>
<NumberInputWithUnit
label={t('watermark.settings.opacity', 'Opacity')}
value={parameters.opacity}
onChange={(value) => onParameterChange('opacity', typeof value === 'number' ? value : 50)}
unit="%"
min={0}
max={100}
disabled={disabled}
/>
</Group>
{/* Spacing - 2 per row */}
<Group grow align="flex-start">
<NumberInputWithUnit
label={t('watermark.settings.spacing.horizontal', 'Horizontal Spacing')}
value={parameters.widthSpacer}
onChange={(value) => onParameterChange('widthSpacer', typeof value === 'number' ? value : 50)}
unit="px"
min={0}
max={200}
disabled={disabled}
/>
<NumberInputWithUnit
label={t('watermark.settings.spacing.vertical', 'Vertical Spacing')}
value={parameters.heightSpacer}
onChange={(value) => onParameterChange('heightSpacer', typeof value === 'number' ? value : 50)}
unit="px"
min={0}
max={200}
disabled={disabled}
/>
</Group>
{/* Advanced Options */}
<Checkbox
label={t('watermark.settings.convertToImage', 'Flatten PDF pages to images')}
checked={parameters.convertPDFToImage}
onChange={(event) => onParameterChange('convertPDFToImage', event.currentTarget.checked)}
disabled={disabled}
/>
</Stack>
);
};
export default WatermarkFormatting;

View File

@ -0,0 +1,65 @@
import React from "react";
import { Stack, Text, Select, ColorInput, NumberInput, Group } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { AddWatermarkParameters } from "./types";
interface WatermarkTextStyleProps {
parameters: AddWatermarkParameters;
onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
disabled?: boolean;
}
const WatermarkTextStyle = ({ parameters, onParameterChange, disabled = false }: WatermarkTextStyleProps) => {
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 (
<Stack gap="sm">
<Group align="flex-start">
<Stack gap="xs" style={{ flex: 1 }}>
<Text size="xs" fw={500}>
{t("watermark.settings.color", "Colour")}
</Text>
<ColorInput
value={parameters.customColor}
onChange={(value) => onParameterChange("customColor", value)}
disabled={disabled}
format="hex"
/>
</Stack>
<Stack gap="xs" style={{ flex: 0.5 }}>
<Text size="xs" fw={500}>
{t("watermark.settings.fontSize", "Size")}
</Text>
<NumberInput
value={parameters.fontSize}
onChange={(value) => onParameterChange("fontSize", value || 12)}
min={8}
max={72}
disabled={disabled}
/>
</Stack>
</Group>
<Text size="xs" fw={500}>
{t("watermark.settings.alphabet", "Alphabet")}
</Text>
<Select
value={parameters.alphabet}
onChange={(value) => value && onParameterChange("alphabet", value)}
data={alphabetOptions}
disabled={disabled}
/>
</Stack>
);
};
export default WatermarkTextStyle;

View File

@ -13,7 +13,6 @@ const WatermarkTypeSettings = ({ watermarkType, onWatermarkTypeChange, disabled
return ( return (
<Stack gap="sm"> <Stack gap="sm">
<Text size="sm" fw={500}>{t('watermark.settings.type', 'Watermark Type')}</Text>
<div style={{ display: 'flex', gap: '4px' }}> <div style={{ display: 'flex', gap: '4px' }}>
<Button <Button
variant={watermarkType === 'text' ? 'filled' : 'outline'} variant={watermarkType === 'text' ? 'filled' : 'outline'}
@ -42,4 +41,4 @@ const WatermarkTypeSettings = ({ watermarkType, onWatermarkTypeChange, disabled
); );
}; };
export default WatermarkTypeSettings; export default WatermarkTypeSettings;

View File

@ -0,0 +1,27 @@
import React from "react";
import { Stack, Text, TextInput } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { AddWatermarkParameters } from "./types";
interface WatermarkWordingProps {
parameters: AddWatermarkParameters;
onParameterChange: (key: keyof AddWatermarkParameters, value: any) => void;
disabled?: boolean;
}
const WatermarkWording = ({ parameters, onParameterChange, disabled = false }: WatermarkWordingProps) => {
const { t } = useTranslation();
return (
<Stack gap="sm">
<TextInput
placeholder={t('watermark.settings.text.placeholder', 'Enter watermark text')}
value={parameters.watermarkText}
onChange={(e) => onParameterChange('watermarkText', e.target.value)}
disabled={disabled}
/>
</Stack>
);
};
export default WatermarkWording;

View File

@ -0,0 +1,50 @@
import React from "react";
import { Stack, Text, NumberInput } from "@mantine/core";
interface NumberInputWithUnitProps {
label: string;
value: number;
onChange: (value: number | string) => void;
unit: string;
min?: number;
max?: number;
disabled?: boolean;
}
const NumberInputWithUnit = ({
label,
value,
onChange,
unit,
min,
max,
disabled = false
}: NumberInputWithUnitProps) => {
return (
<Stack gap="xs" style={{ flex: 1 }}>
<Text size="xs" fw={500} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{label}
</Text>
<NumberInput
value={value}
onChange={onChange}
min={min}
max={max}
disabled={disabled}
rightSection={
<Text size="sm" c="dimmed" pr="sm">
{unit}
</Text>
}
rightSectionWidth={unit.length * 8 + 20} // Dynamic width based on unit length
styles={{
rightSection: {
pointerEvents: 'none',
}
}}
/>
</Stack>
);
};
export default NumberInputWithUnit;

View File

@ -97,27 +97,132 @@ export const useWatermarkStyleTips = (): TooltipContent => {
}; };
}; };
export const useWatermarkAdvancedTips = (): TooltipContent => { export const useWatermarkWordingTips = (): TooltipContent => {
const { t } = useTranslation(); const { t } = useTranslation();
return { return {
header: { header: {
title: t("watermark.tooltip.advanced.header.title", "Advanced Options") title: t("watermark.tooltip.wording.header.title", "Text Content")
}, },
tips: [ tips: [
{ {
title: t("watermark.tooltip.advanced.conversion.title", "PDF to Image Conversion"), title: t("watermark.tooltip.wording.text.title", "Watermark Text"),
description: t("watermark.tooltip.advanced.conversion.text", "Convert the final PDF to an image-based format for enhanced security."), description: t("watermark.tooltip.wording.text.text", "Enter the text that will appear as your watermark across the document."),
bullets: [ bullets: [
t("watermark.tooltip.advanced.conversion.bullet1", "Prevents text selection and copying"), t("watermark.tooltip.wording.text.bullet1", "Keep it concise for better readability"),
t("watermark.tooltip.advanced.conversion.bullet2", "Makes watermarks harder to remove"), t("watermark.tooltip.wording.text.bullet2", "Common examples: 'CONFIDENTIAL', 'DRAFT', company name"),
t("watermark.tooltip.advanced.conversion.bullet3", "Results in larger file sizes"), t("watermark.tooltip.wording.text.bullet3", "Supports all Unicode characters")
t("watermark.tooltip.advanced.conversion.bullet4", "Best for sensitive or copyrighted content")
] ]
}, },
{ {
title: t("watermark.tooltip.advanced.security.title", "Security Considerations"), title: t("watermark.tooltip.wording.fontSize.title", "Font Size"),
description: t("watermark.tooltip.advanced.security.text", "Image-based PDFs provide additional protection against unauthorized editing and content extraction.") description: t("watermark.tooltip.wording.fontSize.text", "Control the size of your watermark text (8-72 points)."),
bullets: [
t("watermark.tooltip.wording.fontSize.bullet1", "Smaller sizes (8-14pt) for subtle watermarks"),
t("watermark.tooltip.wording.fontSize.bullet2", "Medium sizes (16-24pt) for standard visibility"),
t("watermark.tooltip.wording.fontSize.bullet3", "Large sizes (30-72pt) for prominent watermarks")
]
}
]
};
};
export const useWatermarkTextStyleTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("watermark.tooltip.textStyle.header.title", "Text Style")
},
tips: [
{
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"),
description: t("watermark.tooltip.textStyle.color.text", "Choose a color that provides good contrast with your document content."),
bullets: [
t("watermark.tooltip.textStyle.color.bullet1", "Light gray (#d3d3d3) for subtle watermarks"),
t("watermark.tooltip.textStyle.color.bullet2", "Black or dark colors for high contrast"),
t("watermark.tooltip.textStyle.color.bullet3", "Custom colors for branding purposes")
]
}
]
};
};
export const useWatermarkFileTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("watermark.tooltip.file.header.title", "Image Upload")
},
tips: [
{
title: t("watermark.tooltip.file.upload.title", "Image Selection"),
description: t("watermark.tooltip.file.upload.text", "Upload an image file to use as your watermark."),
bullets: [
t("watermark.tooltip.file.upload.bullet1", "Supports common formats: PNG, JPG, GIF, BMP"),
t("watermark.tooltip.file.upload.bullet2", "PNG with transparency works best"),
t("watermark.tooltip.file.upload.bullet3", "Higher resolution images maintain quality better")
]
},
{
title: t("watermark.tooltip.file.recommendations.title", "Best Practices"),
description: t("watermark.tooltip.file.recommendations.text", "Tips for optimal image watermark results."),
bullets: [
t("watermark.tooltip.file.recommendations.bullet1", "Use logos or stamps with transparent backgrounds"),
t("watermark.tooltip.file.recommendations.bullet2", "Simple designs work better than complex images"),
t("watermark.tooltip.file.recommendations.bullet3", "Consider the final document size when choosing resolution")
]
}
]
};
};
export const useWatermarkFormattingTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("watermark.tooltip.formatting.header.title", "Formatting & Layout")
},
tips: [
{
title: t("watermark.tooltip.formatting.appearance.title", "Appearance Settings"),
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"),
description: t("watermark.tooltip.formatting.security.text", "Convert the final PDF to an image-based format for enhanced security."),
bullets: [
t("watermark.tooltip.formatting.security.bullet1", "Prevents text selection and copying"),
t("watermark.tooltip.formatting.security.bullet2", "Makes watermarks harder to remove"),
t("watermark.tooltip.formatting.security.bullet3", "Results in larger file sizes"),
t("watermark.tooltip.formatting.security.bullet4", "Best for sensitive or copyrighted content")
]
} }
] ]
}; };

View File

@ -7,13 +7,14 @@ import { useToolFileSelection } from "../contexts/FileSelectionContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow"; import { createToolFlow } from "../components/tools/shared/createToolFlow";
import WatermarkTypeSettings from "../components/tools/addWatermark/WatermarkTypeSettings"; import WatermarkTypeSettings from "../components/tools/addWatermark/WatermarkTypeSettings";
import WatermarkContentSettings from "../components/tools/addWatermark/WatermarkContentSettings"; import WatermarkWording from "../components/tools/addWatermark/WatermarkWording";
import WatermarkStyleSettings from "../components/tools/addWatermark/WatermarkStyleSettings"; import WatermarkTextStyle from "../components/tools/addWatermark/WatermarkTextStyle";
import WatermarkAdvancedSettings from "../components/tools/addWatermark/WatermarkAdvancedSettings"; import WatermarkFile from "../components/tools/addWatermark/WatermarkFile";
import WatermarkFormatting from "../components/tools/addWatermark/WatermarkFormatting";
import { useAddWatermarkParameters } from "../hooks/tools/addWatermark/useAddWatermarkParameters"; import { useAddWatermarkParameters } from "../hooks/tools/addWatermark/useAddWatermarkParameters";
import { useAddWatermarkOperation } from "../hooks/tools/addWatermark/useAddWatermarkOperation"; import { useAddWatermarkOperation } from "../hooks/tools/addWatermark/useAddWatermarkOperation";
import { useWatermarkTypeTips, useWatermarkContentTips, useWatermarkStyleTips, useWatermarkAdvancedTips } from "../components/tooltips/useWatermarkTips"; import { useWatermarkTypeTips, useWatermarkWordingTips, useWatermarkTextStyleTips, useWatermarkFileTips, useWatermarkFormattingTips } from "../components/tooltips/useWatermarkTips";
import { BaseToolProps } from "../types/tool"; import { BaseToolProps } from "../types/tool";
const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
@ -28,9 +29,10 @@ const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =>
const watermarkParams = useAddWatermarkParameters(); const watermarkParams = useAddWatermarkParameters();
const watermarkOperation = useAddWatermarkOperation(); const watermarkOperation = useAddWatermarkOperation();
const watermarkTypeTips = useWatermarkTypeTips(); const watermarkTypeTips = useWatermarkTypeTips();
const watermarkContentTips = useWatermarkContentTips(); const watermarkWordingTips = useWatermarkWordingTips();
const watermarkStyleTips = useWatermarkStyleTips(); const watermarkTextStyleTips = useWatermarkTextStyleTips();
const watermarkAdvancedTips = useWatermarkAdvancedTips(); const watermarkFileTips = useWatermarkFileTips();
const watermarkFormattingTips = useWatermarkFormattingTips();
// Endpoint validation // Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("add-watermark"); const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("add-watermark");
@ -78,24 +80,22 @@ const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =>
// Step completion logic // Step completion logic
const typeStepCompleted = hasFiles && !!watermarkParams.parameters.watermarkType; const typeStepCompleted = hasFiles && !!watermarkParams.parameters.watermarkType;
const contentStepCompleted = typeStepCompleted && ( const textWordingCompleted = typeStepCompleted &&
(watermarkParams.parameters.watermarkType === 'text' && watermarkParams.parameters.watermarkText.trim().length > 0) || watermarkParams.parameters.watermarkType === 'text' &&
(watermarkParams.parameters.watermarkType === 'image' && watermarkParams.parameters.watermarkImage !== undefined) watermarkParams.parameters.watermarkText.trim().length > 0;
); const imageFileCompleted = typeStepCompleted &&
watermarkParams.parameters.watermarkType === 'image' &&
watermarkParams.parameters.watermarkImage !== undefined;
// Step visibility logic - all steps always visible once files are selected // Dynamic step structure based on watermark type
const styleCollapsed = collapsedStyle || hasResults; const getSteps = () => {
const advancedCollapsed = collapsedAdvanced || hasResults; const steps = [];
return createToolFlow({ // Step 1: Watermark Type (always visible after files)
files: { if (hasFiles) {
selectedFiles, steps.push({
isCollapsed: hasFiles || hasResults,
},
steps: [
{
title: t("watermark.steps.type", "Watermark Type"), title: t("watermark.steps.type", "Watermark Type"),
isCollapsed: settingsCollapsed? true : collapsedType, isCollapsed: hasResults ? true : collapsedType,
onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedType(!collapsedType), onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedType(!collapsedType),
tooltip: watermarkTypeTips, tooltip: watermarkTypeTips,
content: ( content: (
@ -105,48 +105,97 @@ const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =>
disabled={endpointLoading} disabled={endpointLoading}
/> />
), ),
}, });
{ }
title: watermarkParams.parameters.watermarkType === 'text'
? t("watermark.steps.textContent", "Text Content") // Text watermark path
: t("watermark.steps.imageContent", "Image Content"), if (watermarkParams.parameters.watermarkType === 'text') {
isCollapsed: settingsCollapsed? true : contentStepCompleted, // Step 2: Wording
tooltip: watermarkContentTips, steps.push({
title: t("watermark.steps.wording", "Wording"),
isCollapsed: hasResults,
tooltip: watermarkWordingTips,
content: ( content: (
<WatermarkContentSettings <WatermarkWording
parameters={watermarkParams.parameters} parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter} onParameterChange={watermarkParams.updateParameter}
disabled={endpointLoading} disabled={endpointLoading}
/> />
), ),
}, });
{
title: t("watermark.steps.style", "Style & Position"), // Step 3: Style
isCollapsed: settingsCollapsed? true : styleCollapsed, steps.push({
title: t("watermark.steps.textStyle", "Style"),
isCollapsed: hasResults ? true : collapsedStyle,
onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedStyle(!collapsedStyle), onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedStyle(!collapsedStyle),
tooltip: watermarkStyleTips, tooltip: watermarkTextStyleTips,
content: ( content: (
<WatermarkStyleSettings <WatermarkTextStyle
parameters={watermarkParams.parameters} parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter} onParameterChange={watermarkParams.updateParameter}
disabled={endpointLoading} disabled={endpointLoading}
/> />
), ),
}, });
{
title: t("watermark.steps.advanced", "Advanced Options"), // Step 4: Formatting
isCollapsed: settingsCollapsed? true : advancedCollapsed, steps.push({
title: t("watermark.steps.formatting", "Formatting"),
isCollapsed: hasResults ? true : collapsedAdvanced,
onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedAdvanced(!collapsedAdvanced), onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedAdvanced(!collapsedAdvanced),
tooltip: watermarkAdvancedTips, tooltip: watermarkFormattingTips,
content: ( content: (
<WatermarkAdvancedSettings <WatermarkFormatting
parameters={watermarkParams.parameters} parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter} onParameterChange={watermarkParams.updateParameter}
disabled={endpointLoading} disabled={endpointLoading}
/> />
), ),
}, });
], }
// Image watermark path
if (watermarkParams.parameters.watermarkType === 'image') {
// Step 2: Watermark File
steps.push({
title: t("watermark.steps.file", "Watermark File"),
isCollapsed: false,
tooltip: watermarkFileTips,
content: (
<WatermarkFile
parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter}
disabled={endpointLoading}
/>
),
});
// Step 3: Formatting
steps.push({
title: t("watermark.steps.formatting", "Formatting"),
isCollapsed: hasResults ? true : collapsedAdvanced,
onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedAdvanced(!collapsedAdvanced),
tooltip: watermarkFormattingTips,
content: (
<WatermarkFormatting
parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter}
disabled={endpointLoading}
/>
),
});
}
return steps;
};
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasFiles || hasResults,
},
steps: getSteps(),
executeButton: { executeButton: {
text: t("watermark.submit", "Add Watermark"), text: t("watermark.submit", "Add Watermark"),
isVisible: !hasResults, isVisible: !hasResults,