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)",
"opacity": "Opacity (%)",
"spacing": {
"width": "Width Spacing",
"height": "Height Spacing"
"horizontal": "Horizontal Spacing",
"vertical": "Vertical Spacing"
},
"convertToImage": "Convert result to image-based PDF",
"convertToImageDesc": "Creates a PDF with images instead of text (more secure but larger file size)"
"convertToImage": "Flatten PDF pages to images"
},
"alphabet": {
"roman": "Roman/Latin",
@ -1782,10 +1781,10 @@
},
"steps": {
"type": "Watermark Type",
"textContent": "Text Content",
"imageContent": "Image Content",
"style": "Style & Position",
"advanced": "Advanced Options"
"wording": "Wording",
"textStyle": "Style",
"file": "Watermark File",
"formatting": "Formatting"
},
"results": {
"title": "Watermark Results"
@ -1854,6 +1853,91 @@
"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": {
"header": {
"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 (
<Stack gap="sm">
<Text size="sm" fw={500}>{t('watermark.settings.type', 'Watermark Type')}</Text>
<div style={{ display: 'flex', gap: '4px' }}>
<Button
variant={watermarkType === 'text' ? 'filled' : 'outline'}

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();
return {
header: {
title: t("watermark.tooltip.advanced.header.title", "Advanced Options")
title: t("watermark.tooltip.wording.header.title", "Text Content")
},
tips: [
{
title: t("watermark.tooltip.advanced.conversion.title", "PDF to Image Conversion"),
description: t("watermark.tooltip.advanced.conversion.text", "Convert the final PDF to an image-based format for enhanced security."),
title: t("watermark.tooltip.wording.text.title", "Watermark Text"),
description: t("watermark.tooltip.wording.text.text", "Enter the text that will appear as your watermark across the document."),
bullets: [
t("watermark.tooltip.advanced.conversion.bullet1", "Prevents text selection and copying"),
t("watermark.tooltip.advanced.conversion.bullet2", "Makes watermarks harder to remove"),
t("watermark.tooltip.advanced.conversion.bullet3", "Results in larger file sizes"),
t("watermark.tooltip.advanced.conversion.bullet4", "Best for sensitive or copyrighted content")
t("watermark.tooltip.wording.text.bullet1", "Keep it concise for better readability"),
t("watermark.tooltip.wording.text.bullet2", "Common examples: 'CONFIDENTIAL', 'DRAFT', company name"),
t("watermark.tooltip.wording.text.bullet3", "Supports all Unicode characters")
]
},
{
title: t("watermark.tooltip.advanced.security.title", "Security Considerations"),
description: t("watermark.tooltip.advanced.security.text", "Image-based PDFs provide additional protection against unauthorized editing and content extraction.")
title: t("watermark.tooltip.wording.fontSize.title", "Font Size"),
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 WatermarkTypeSettings from "../components/tools/addWatermark/WatermarkTypeSettings";
import WatermarkContentSettings from "../components/tools/addWatermark/WatermarkContentSettings";
import WatermarkStyleSettings from "../components/tools/addWatermark/WatermarkStyleSettings";
import WatermarkAdvancedSettings from "../components/tools/addWatermark/WatermarkAdvancedSettings";
import WatermarkWording from "../components/tools/addWatermark/WatermarkWording";
import WatermarkTextStyle from "../components/tools/addWatermark/WatermarkTextStyle";
import WatermarkFile from "../components/tools/addWatermark/WatermarkFile";
import WatermarkFormatting from "../components/tools/addWatermark/WatermarkFormatting";
import { useAddWatermarkParameters } from "../hooks/tools/addWatermark/useAddWatermarkParameters";
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";
const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
@ -28,9 +29,10 @@ const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =>
const watermarkParams = useAddWatermarkParameters();
const watermarkOperation = useAddWatermarkOperation();
const watermarkTypeTips = useWatermarkTypeTips();
const watermarkContentTips = useWatermarkContentTips();
const watermarkStyleTips = useWatermarkStyleTips();
const watermarkAdvancedTips = useWatermarkAdvancedTips();
const watermarkWordingTips = useWatermarkWordingTips();
const watermarkTextStyleTips = useWatermarkTextStyleTips();
const watermarkFileTips = useWatermarkFileTips();
const watermarkFormattingTips = useWatermarkFormattingTips();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("add-watermark");
@ -78,24 +80,22 @@ const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =>
// Step completion logic
const typeStepCompleted = hasFiles && !!watermarkParams.parameters.watermarkType;
const contentStepCompleted = typeStepCompleted && (
(watermarkParams.parameters.watermarkType === 'text' && watermarkParams.parameters.watermarkText.trim().length > 0) ||
(watermarkParams.parameters.watermarkType === 'image' && watermarkParams.parameters.watermarkImage !== undefined)
);
const textWordingCompleted = typeStepCompleted &&
watermarkParams.parameters.watermarkType === 'text' &&
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
const styleCollapsed = collapsedStyle || hasResults;
const advancedCollapsed = collapsedAdvanced || hasResults;
// Dynamic step structure based on watermark type
const getSteps = () => {
const steps = [];
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasFiles || hasResults,
},
steps: [
{
// Step 1: Watermark Type (always visible after files)
if (hasFiles) {
steps.push({
title: t("watermark.steps.type", "Watermark Type"),
isCollapsed: settingsCollapsed? true : collapsedType,
isCollapsed: hasResults ? true : collapsedType,
onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedType(!collapsedType),
tooltip: watermarkTypeTips,
content: (
@ -105,48 +105,97 @@ const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =>
disabled={endpointLoading}
/>
),
},
{
title: watermarkParams.parameters.watermarkType === 'text'
? t("watermark.steps.textContent", "Text Content")
: t("watermark.steps.imageContent", "Image Content"),
isCollapsed: settingsCollapsed? true : contentStepCompleted,
tooltip: watermarkContentTips,
});
}
// Text watermark path
if (watermarkParams.parameters.watermarkType === 'text') {
// Step 2: Wording
steps.push({
title: t("watermark.steps.wording", "Wording"),
isCollapsed: hasResults,
tooltip: watermarkWordingTips,
content: (
<WatermarkContentSettings
<WatermarkWording
parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter}
disabled={endpointLoading}
/>
),
},
{
title: t("watermark.steps.style", "Style & Position"),
isCollapsed: settingsCollapsed? true : styleCollapsed,
});
// Step 3: Style
steps.push({
title: t("watermark.steps.textStyle", "Style"),
isCollapsed: hasResults ? true : collapsedStyle,
onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedStyle(!collapsedStyle),
tooltip: watermarkStyleTips,
tooltip: watermarkTextStyleTips,
content: (
<WatermarkStyleSettings
<WatermarkTextStyle
parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter}
disabled={endpointLoading}
/>
),
},
{
title: t("watermark.steps.advanced", "Advanced Options"),
isCollapsed: settingsCollapsed? true : advancedCollapsed,
});
// Step 4: Formatting
steps.push({
title: t("watermark.steps.formatting", "Formatting"),
isCollapsed: hasResults ? true : collapsedAdvanced,
onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedAdvanced(!collapsedAdvanced),
tooltip: watermarkAdvancedTips,
tooltip: watermarkFormattingTips,
content: (
<WatermarkAdvancedSettings
<WatermarkFormatting
parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter}
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: {
text: t("watermark.submit", "Add Watermark"),
isVisible: !hasResults,