Data-drive Add Watermark

This commit is contained in:
James Brunton 2025-09-04 16:57:53 +01:00
parent 6376189a63
commit b9b8e6e4e1
7 changed files with 283 additions and 406 deletions

View File

@ -26,8 +26,8 @@ const AddWatermarkSingleStepSettings = ({ parameters, onParameterChange, disable
<Stack gap="lg">
{/* Watermark Type Selection */}
<WatermarkTypeSettings
watermarkType={parameters.watermarkType}
onWatermarkTypeChange={(type) => onParameterChange("watermarkType", type)}
parameters={parameters}
onParameterChange={onParameterChange}
disabled={disabled}
/>

View File

@ -3,21 +3,21 @@ import { Button, Stack, Text } from "@mantine/core";
import { useTranslation } from "react-i18next";
interface WatermarkTypeSettingsProps {
watermarkType?: 'text' | 'image';
onWatermarkTypeChange: (type: 'text' | 'image') => void;
parameters: { watermarkType?: 'text' | 'image' };
onParameterChange: (key: 'watermarkType', value: 'text' | 'image') => void;
disabled?: boolean;
}
const WatermarkTypeSettings = ({ watermarkType, onWatermarkTypeChange, disabled = false }: WatermarkTypeSettingsProps) => {
const WatermarkTypeSettings = ({ parameters, onParameterChange, disabled = false }: WatermarkTypeSettingsProps) => {
const { t } = useTranslation();
return (
<Stack gap="sm">
<div style={{ display: 'flex', gap: '4px' }}>
<Button
variant={watermarkType === 'text' ? 'filled' : 'outline'}
color={watermarkType === 'text' ? 'blue' : 'var(--text-muted)'}
onClick={() => onWatermarkTypeChange('text')}
variant={parameters.watermarkType === 'text' ? 'filled' : 'outline'}
color={parameters.watermarkType === 'text' ? 'blue' : 'var(--text-muted)'}
onClick={() => onParameterChange('watermarkType', 'text')}
disabled={disabled}
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
>
@ -26,9 +26,9 @@ const WatermarkTypeSettings = ({ watermarkType, onWatermarkTypeChange, disabled
</div>
</Button>
<Button
variant={watermarkType === 'image' ? 'filled' : 'outline'}
color={watermarkType === 'image' ? 'blue' : 'var(--text-muted)'}
onClick={() => onWatermarkTypeChange('image')}
variant={parameters.watermarkType === 'image' ? 'filled' : 'outline'}
color={parameters.watermarkType === 'image' ? 'blue' : 'var(--text-muted)'}
onClick={() => onParameterChange('watermarkType', 'image')}
disabled={disabled}
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
>

View File

@ -1,3 +1,4 @@
import { useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { GenericToolProps } from './toolDefinition';
import { useBaseTool } from '../../../hooks/tools/shared/useBaseTool';
@ -19,8 +20,31 @@ function GenericTool<TParams>(props: GenericToolProps<TParams>) {
props
);
// Get steps (either static array or dynamic function result)
const stepDefinitions = typeof definition.steps === 'function'
? definition.steps(base.params.parameters, base.hasFiles, base.hasResults)
: definition.steps;
// State for individual step collapse - each step manages its own collapse state
const [stepCollapseStates, setStepCollapseStates] = useState<Record<string, boolean>>(() => {
// Initialize collapse states for all steps
const initialStates: Record<string, boolean> = {};
stepDefinitions.forEach((stepDef, index) => {
// First step starts expanded, others start collapsed
initialStates[stepDef.key] = index > 0;
});
return initialStates;
});
const toggleStepCollapse = useCallback((stepKey: string) => {
setStepCollapseStates(prev => ({
...prev,
[stepKey]: !prev[stepKey]
}));
}, []);
// Build steps from definition - filter and map in separate operations for better typing
const visibleSteps = definition.steps.filter((stepDef) => {
const visibleSteps = stepDefinitions.filter((stepDef) => {
const isVisible = typeof stepDef.isVisible === 'function'
? stepDef.isVisible(base.params.parameters, base.hasFiles, base.hasResults)
: stepDef.isVisible ?? true;
@ -29,8 +53,8 @@ function GenericTool<TParams>(props: GenericToolProps<TParams>) {
const steps: MiddleStepConfig[] = visibleSteps.map((stepDef) => ({
title: stepDef.title(t),
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.hasResults ? base.handleSettingsReset : undefined,
isCollapsed: base.hasResults ? true : (stepCollapseStates[stepDef.key] ?? false),
onCollapsedClick: base.hasResults ? base.handleSettingsReset : () => toggleStepCollapse(stepDef.key),
tooltip: stepDef.tooltip?.(t),
content: (
<stepDef.component

View File

@ -74,8 +74,8 @@ export interface ToolDefinition<TParams> {
/** Hook that provides operation execution */
useOperation: () => ToolOperationHook<TParams>;
/** Configuration steps for the tool */
steps: ToolStepDefinition<TParams>[];
/** Configuration steps for the tool - can be static array or dynamic function */
steps: ToolStepDefinition<TParams>[] | ((params: TParams, hasFiles: boolean, hasResults: boolean) => ToolStepDefinition<TParams>[]);
/** Execute button configuration */
executeButton: ToolExecuteButtonDefinition;

View File

@ -1,176 +0,0 @@
import { useTranslation } from 'react-i18next';
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.")
};
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 => {
const { t } = useTranslation();
return {
header: {
title: t("watermark.tooltip.type.header.title", "Watermark Type Selection")
},
tips: [
{
title: t("watermark.tooltip.type.description.title", "Choose Your Watermark"),
description: t("watermark.tooltip.type.description.text", "Select between text or image watermarks based on your needs.")
},
{
title: t("watermark.tooltip.type.text.title", "Text Watermarks"),
description: t("watermark.tooltip.type.text.text", "Perfect for adding copyright notices, company names, or confidentiality labels. Supports multiple languages and custom colors."),
bullets: [
t("watermark.tooltip.type.text.bullet1", "Customizable fonts and languages"),
t("watermark.tooltip.type.text.bullet2", "Adjustable colors and transparency"),
t("watermark.tooltip.type.text.bullet3", "Ideal for legal or branding text")
]
},
{
title: t("watermark.tooltip.type.image.title", "Image Watermarks"),
description: t("watermark.tooltip.type.image.text", "Use logos, stamps, or any image as a watermark. Great for branding and visual identification."),
bullets: [
t("watermark.tooltip.type.image.bullet1", "Upload any image format"),
t("watermark.tooltip.type.image.bullet2", "Maintains image quality"),
t("watermark.tooltip.type.image.bullet3", "Perfect for logos and stamps")
]
}
]
};
};
export const useWatermarkWordingTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("watermark.tooltip.wording.header.title", "Text Content")
},
tips: [
{
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.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", "Emoji characters are not supported and will be filtered out")
]
}
]
};
};
export const useWatermarkTextStyleTips = (): TooltipContent => {
const { t } = useTranslation();
const { languageSupportTip } = useSharedWatermarkContent();
return {
header: {
title: t("watermark.tooltip.textStyle.header.title", "Text Style")
},
tips: [
{
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")
]
},
languageSupportTip
]
};
};
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();
const { appearanceTip, spacingTip } = useSharedWatermarkContent();
return {
header: {
title: t("watermark.tooltip.formatting.header.title", "Formatting & Layout")
},
tips: [
{
title: t("watermark.tooltip.formatting.size.title", "Size Control"),
description: t("watermark.tooltip.formatting.size.text", "Adjust the size of your watermark (text or image)."),
bullets: [
t("watermark.tooltip.formatting.size.bullet1", "Larger sizes create more prominent watermarks")
]
},
appearanceTip,
spacingTip,
{
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

@ -1,220 +1,11 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileSelection } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import WatermarkTypeSettings from "../components/tools/addWatermark/WatermarkTypeSettings";
import WatermarkWording from "../components/tools/addWatermark/WatermarkWording";
import WatermarkTextStyle from "../components/tools/addWatermark/WatermarkTextStyle";
import WatermarkImageFile from "../components/tools/addWatermark/WatermarkImageFile";
import WatermarkFormatting from "../components/tools/addWatermark/WatermarkFormatting";
import { useAddWatermarkParameters } from "../hooks/tools/addWatermark/useAddWatermarkParameters";
import { useAddWatermarkOperation } from "../hooks/tools/addWatermark/useAddWatermarkOperation";
import {
useWatermarkTypeTips,
useWatermarkWordingTips,
useWatermarkTextStyleTips,
useWatermarkFileTips,
useWatermarkFormattingTips,
} from "../components/tooltips/useWatermarkTips";
import { BaseToolProps, ToolComponent } from "../types/tool";
import GenericTool from "../components/tools/shared/GenericTool";
import { addWatermarkDefinition } from "./definitions/addWatermarkDefinition";
const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation();
const { actions } = useNavigationActions();
const { selectedFiles } = useFileSelection();
const [collapsedType, setCollapsedType] = useState(false);
const [collapsedStyle, setCollapsedStyle] = useState(true);
const [collapsedFormatting, setCollapsedFormatting] = useState(true);
const watermarkParams = useAddWatermarkParameters();
const watermarkOperation = useAddWatermarkOperation();
const watermarkTypeTips = useWatermarkTypeTips();
const watermarkWordingTips = useWatermarkWordingTips();
const watermarkTextStyleTips = useWatermarkTextStyleTips();
const watermarkFileTips = useWatermarkFileTips();
const watermarkFormattingTips = useWatermarkFormattingTips();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("add-watermark");
useEffect(() => {
watermarkOperation.resetResults();
onPreviewFile?.(null);
}, [watermarkParams.parameters]);
// Auto-collapse type step after selection
useEffect(() => {
if (watermarkParams.parameters.watermarkType && !collapsedType) {
setCollapsedType(true);
}
}, [watermarkParams.parameters.watermarkType]);
const handleAddWatermark = async () => {
try {
await watermarkOperation.executeOperation(watermarkParams.parameters, selectedFiles);
if (watermarkOperation.files && onComplete) {
onComplete(watermarkOperation.files);
}
} catch (error) {
if (onError) {
onError(error instanceof Error ? error.message : t("watermark.error.failed", "Add watermark operation failed"));
}
}
};
const handleThumbnailClick = (file: File) => {
onPreviewFile?.(file);
sessionStorage.setItem("previousMode", "watermark");
};
const handleSettingsReset = () => {
watermarkOperation.resetResults();
onPreviewFile?.(null);
};
const handleUndo = async () => {
await watermarkOperation.undoOperation();
onPreviewFile?.(null);
};
const hasFiles = selectedFiles.length > 0;
const hasResults = watermarkOperation.files.length > 0 || watermarkOperation.downloadUrl !== null;
// Dynamic step structure based on watermark type
const getSteps = () => {
const steps = [];
steps.push({
title: t("watermark.steps.type", "Watermark Type"),
isCollapsed: hasResults ? true : collapsedType,
isVisible: hasFiles || hasResults,
onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedType(!collapsedType),
tooltip: watermarkTypeTips,
content: (
<WatermarkTypeSettings
watermarkType={watermarkParams.parameters.watermarkType}
onWatermarkTypeChange={(type) => watermarkParams.updateParameter("watermarkType", type)}
disabled={endpointLoading}
/>
),
});
if (hasFiles || hasResults) {
// Text watermark path
if (watermarkParams.parameters.watermarkType === "text") {
// Step 2: Wording
steps.push({
title: t("watermark.steps.wording", "Wording"),
isCollapsed: hasResults,
tooltip: watermarkWordingTips,
content: (
<WatermarkWording
parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter}
disabled={endpointLoading}
/>
),
});
// Step 3: Style
steps.push({
title: t("watermark.steps.textStyle", "Style"),
isCollapsed: hasResults ? true : collapsedStyle,
onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedStyle(!collapsedStyle),
tooltip: watermarkTextStyleTips,
content: (
<WatermarkTextStyle
parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter}
disabled={endpointLoading}
/>
),
});
// Step 4: Formatting
steps.push({
title: t("watermark.steps.formatting", "Formatting"),
isCollapsed: hasResults ? true : collapsedFormatting,
onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedFormatting(!collapsedFormatting),
tooltip: watermarkFormattingTips,
content: (
<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: hasResults,
tooltip: watermarkFileTips,
content: (
<WatermarkImageFile
parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter}
disabled={endpointLoading}
/>
),
});
// Step 3: Formatting
steps.push({
title: t("watermark.steps.formatting", "Formatting"),
isCollapsed: hasResults ? true : collapsedFormatting,
onCollapsedClick: hasResults ? handleSettingsReset : () => setCollapsedFormatting(!collapsedFormatting),
tooltip: watermarkFormattingTips,
content: (
<WatermarkFormatting
parameters={watermarkParams.parameters}
onParameterChange={watermarkParams.updateParameter}
disabled={endpointLoading}
/>
),
});
}
}
return steps;
};
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasResults,
},
steps: getSteps(),
executeButton: {
text: t("watermark.submit", "Add Watermark"),
isVisible: !hasResults,
loadingText: t("loading"),
onClick: handleAddWatermark,
disabled: !watermarkParams.validateParameters() || !hasFiles || !endpointEnabled,
},
review: {
isVisible: hasResults,
operation: watermarkOperation,
title: t("watermark.results.title", "Watermark Results"),
onFileClick: handleThumbnailClick,
onUndo: handleUndo,
},
forceStepNumbers: true,
});
const AddWatermark = (props: BaseToolProps) => {
return <GenericTool definition={addWatermarkDefinition} {...props} />;
};
// Static method to get the operation hook for automation
AddWatermark.tool = () => useAddWatermarkOperation;
AddWatermark.tool = () => addWatermarkDefinition.useOperation;
export default AddWatermark as ToolComponent;

View File

@ -0,0 +1,238 @@
import { ToolDefinition, ToolStepDefinition } from '../../components/tools/shared/toolDefinition';
import { AddWatermarkParameters, useAddWatermarkParameters } from '../../hooks/tools/addWatermark/useAddWatermarkParameters';
import { useAddWatermarkOperation } from '../../hooks/tools/addWatermark/useAddWatermarkOperation';
import WatermarkTypeSettings from '../../components/tools/addWatermark/WatermarkTypeSettings';
import WatermarkWording from '../../components/tools/addWatermark/WatermarkWording';
import WatermarkTextStyle from '../../components/tools/addWatermark/WatermarkTextStyle';
import WatermarkImageFile from '../../components/tools/addWatermark/WatermarkImageFile';
import WatermarkFormatting from '../../components/tools/addWatermark/WatermarkFormatting';
import { TooltipTip } from '../../types/tips';
import { TFunction } from 'i18next';
const languageSupportTip: (t: TFunction) => TooltipTip = (t) => ({
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.")
});
const appearanceTip: (t: TFunction) => TooltipTip = (t) => ({
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: (t: TFunction) => TooltipTip = (t) => ({
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")
]
});
export const addWatermarkDefinition: ToolDefinition<AddWatermarkParameters> = {
id: 'addWatermark',
useParameters: useAddWatermarkParameters,
useOperation: useAddWatermarkOperation,
steps: (params, hasFiles, hasResults) => {
const steps: ToolStepDefinition<AddWatermarkParameters>[] = [];
// Step 1: Watermark Type (always present)
steps.push({
key: 'type',
title: (t) => t("watermark.steps.type", "Watermark Type"),
component: WatermarkTypeSettings,
tooltip: (t) => ({
header: {
title: t("watermark.tooltip.type.header.title", "Watermark Type Selection")
},
tips: [
{
title: t("watermark.tooltip.type.description.title", "Choose Your Watermark"),
description: t("watermark.tooltip.type.description.text", "Select between text or image watermarks based on your needs.")
},
{
title: t("watermark.tooltip.type.text.title", "Text Watermarks"),
description: t("watermark.tooltip.type.text.text", "Perfect for adding copyright notices, company names, or confidentiality labels. Supports multiple languages and custom colors."),
bullets: [
t("watermark.tooltip.type.text.bullet1", "Customizable fonts and languages"),
t("watermark.tooltip.type.text.bullet2", "Adjustable colors and transparency"),
t("watermark.tooltip.type.text.bullet3", "Ideal for legal or branding text")
]
},
{
title: t("watermark.tooltip.type.image.title", "Image Watermarks"),
description: t("watermark.tooltip.type.image.text", "Use logos, stamps, or any image as a watermark. Great for branding and visual identification."),
bullets: [
t("watermark.tooltip.type.image.bullet1", "Upload any image format"),
t("watermark.tooltip.type.image.bullet2", "Maintains image quality"),
t("watermark.tooltip.type.image.bullet3", "Perfect for logos and stamps")
]
}
]
}),
});
// Only show remaining steps if we have files and watermark type is selected
if ((hasFiles || hasResults) && params.watermarkType) {
// Text watermark path
if (params.watermarkType === "text") {
// Step 2: Wording
steps.push({
key: 'wording',
title: (t) => t("watermark.steps.wording", "Wording"),
component: WatermarkWording,
tooltip: (t) => ({
header: {
title: t("watermark.tooltip.wording.header.title", "Text Content")
},
tips: [
{
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.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", "Emoji characters are not supported and will be filtered out")
]
}
]
}),
});
// Step 3: Text Style
steps.push({
key: 'textStyle',
title: (t) => t("watermark.steps.textStyle", "Style"),
component: WatermarkTextStyle,
tooltip: (t) => ({
header: {
title: t("watermark.tooltip.textStyle.header.title", "Text Style")
},
tips: [
{
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")
]
},
languageSupportTip(t),
]
}),
});
// Step 4: Formatting
steps.push({
key: 'formatting',
title: (t) => t("watermark.steps.formatting", "Formatting"),
component: WatermarkFormatting,
tooltip: (t) => ({
header: {
title: t("watermark.tooltip.textStyle.header.title", "Text Style")
},
tips: [
{
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")
]
},
languageSupportTip(t),
]
}),
});
}
// Image watermark path
if (params.watermarkType === "image") {
// Step 2: Watermark File
steps.push({
key: 'imageFile',
title: (t) => t("watermark.steps.file", "Watermark File"),
component: WatermarkImageFile,
tooltip: (t) => ({
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")
]
}
]
}),
});
// Step 3: Formatting
steps.push({
key: 'formatting',
title: (t) => t("watermark.steps.formatting", "Formatting"),
component: WatermarkFormatting,
tooltip: (t) => ({
header: {
title: t("watermark.tooltip.formatting.header.title", "Formatting & Layout"),
},
tips: [
{
title: t("watermark.tooltip.formatting.size.title", "Size Control"),
description: t("watermark.tooltip.formatting.size.text", "Adjust the size of your watermark (text or image)."),
bullets: [
t("watermark.tooltip.formatting.size.bullet1", "Larger sizes create more prominent watermarks"),
],
},
appearanceTip(t),
spacingTip(t),
{
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"),
],
},
],
}),
});
}
}
return steps;
},
executeButton: {
text: (t) => t("watermark.submit", "Add Watermark"),
loadingText: (t) => t("loading"),
},
review: {
title: (t) => t("watermark.results.title", "Watermark Results"),
},
};