mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Basic tool creation
This commit is contained in:
parent
129e4d00e9
commit
85b612bf22
@ -1737,5 +1737,48 @@
|
|||||||
"text": "To make these permissions unchangeable, use the Add Password tool to set an owner password."
|
"text": "To make these permissions unchangeable, use the Add Password tool to set an owner password."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"watermark": {
|
||||||
|
"completed": "Watermark added",
|
||||||
|
"submit": "Add Watermark",
|
||||||
|
"filenamePrefix": "watermarked",
|
||||||
|
"error": {
|
||||||
|
"failed": "An error occurred while adding watermark to the PDF."
|
||||||
|
},
|
||||||
|
"watermarkType": {
|
||||||
|
"text": "Text",
|
||||||
|
"image": "Image"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"type": "Watermark Type",
|
||||||
|
"text": {
|
||||||
|
"label": "Watermark Text",
|
||||||
|
"placeholder": "Enter watermark text"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"label": "Watermark Image",
|
||||||
|
"choose": "Choose Image",
|
||||||
|
"selected": "Selected: {{filename}}"
|
||||||
|
},
|
||||||
|
"fontSize": "Font Size",
|
||||||
|
"position": "Position",
|
||||||
|
"rotation": "Rotation (degrees)",
|
||||||
|
"opacity": "Opacity (%)",
|
||||||
|
"spacing": {
|
||||||
|
"width": "Width Spacing",
|
||||||
|
"height": "Height Spacing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"positions": {
|
||||||
|
"topLeft": "Top Left",
|
||||||
|
"topCenter": "Top Center",
|
||||||
|
"topRight": "Top Right",
|
||||||
|
"centerLeft": "Center Left",
|
||||||
|
"center": "Center",
|
||||||
|
"centerRight": "Center Right",
|
||||||
|
"bottomLeft": "Bottom Left",
|
||||||
|
"bottomCenter": "Bottom Center",
|
||||||
|
"bottomRight": "Bottom Right"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,174 @@
|
|||||||
|
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;
|
@ -0,0 +1,44 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useToolOperation } from '../shared/useToolOperation';
|
||||||
|
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||||
|
import { AddWatermarkParameters } from './useAddWatermarkParameters';
|
||||||
|
|
||||||
|
const buildFormData = (parameters: AddWatermarkParameters, file: File): FormData => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("fileInput", file);
|
||||||
|
|
||||||
|
if (parameters.watermarkType === 'text') {
|
||||||
|
formData.append("watermarkText", parameters.watermarkText);
|
||||||
|
} else if (parameters.watermarkImage) {
|
||||||
|
formData.append("watermarkImage", parameters.watermarkImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.append("fontSize", parameters.fontSize.toString());
|
||||||
|
formData.append("rotation", parameters.rotation.toString());
|
||||||
|
formData.append("opacity", (parameters.opacity / 100).toString()); // Convert percentage to decimal
|
||||||
|
formData.append("widthSpacer", parameters.widthSpacer.toString());
|
||||||
|
formData.append("heightSpacer", parameters.heightSpacer.toString());
|
||||||
|
formData.append("position", parameters.position);
|
||||||
|
|
||||||
|
if (parameters.overrideX !== undefined) {
|
||||||
|
formData.append("overrideX", parameters.overrideX.toString());
|
||||||
|
}
|
||||||
|
if (parameters.overrideY !== undefined) {
|
||||||
|
formData.append("overrideY", parameters.overrideY.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAddWatermarkOperation = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return useToolOperation<AddWatermarkParameters>({
|
||||||
|
operationType: 'watermark',
|
||||||
|
endpoint: '/api/v1/security/add-watermark',
|
||||||
|
buildFormData,
|
||||||
|
filePrefix: t('watermark.filenamePrefix', 'watermarked') + '_',
|
||||||
|
multiFileEndpoint: false, // Individual API calls per file
|
||||||
|
getErrorMessage: createStandardErrorHandler(t('watermark.error.failed', 'An error occurred while adding watermark to the PDF.'))
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,56 @@
|
|||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultParameters: AddWatermarkParameters = {
|
||||||
|
watermarkType: 'text',
|
||||||
|
watermarkText: '',
|
||||||
|
fontSize: 12,
|
||||||
|
rotation: 0,
|
||||||
|
opacity: 50,
|
||||||
|
widthSpacer: 50,
|
||||||
|
heightSpacer: 50,
|
||||||
|
position: 'center'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAddWatermarkParameters = () => {
|
||||||
|
const [parameters, setParameters] = useState<AddWatermarkParameters>(defaultParameters);
|
||||||
|
|
||||||
|
const updateParameter = useCallback(<K extends keyof AddWatermarkParameters>(
|
||||||
|
key: K,
|
||||||
|
value: AddWatermarkParameters[K]
|
||||||
|
) => {
|
||||||
|
setParameters(prev => ({ ...prev, [key]: value }));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resetParameters = useCallback(() => {
|
||||||
|
setParameters(defaultParameters);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validateParameters = useCallback((): boolean => {
|
||||||
|
if (parameters.watermarkType === 'text') {
|
||||||
|
return parameters.watermarkText.trim().length > 0;
|
||||||
|
} else {
|
||||||
|
return parameters.watermarkImage !== undefined;
|
||||||
|
}
|
||||||
|
}, [parameters]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
parameters,
|
||||||
|
updateParameter,
|
||||||
|
resetParameters,
|
||||||
|
validateParameters
|
||||||
|
};
|
||||||
|
};
|
@ -6,6 +6,7 @@ import SwapHorizIcon from "@mui/icons-material/SwapHoriz";
|
|||||||
import ApiIcon from "@mui/icons-material/Api";
|
import ApiIcon from "@mui/icons-material/Api";
|
||||||
import CleaningServicesIcon from "@mui/icons-material/CleaningServices";
|
import CleaningServicesIcon from "@mui/icons-material/CleaningServices";
|
||||||
import LockIcon from "@mui/icons-material/Lock";
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
|
import BrandingWatermarkIcon from "@mui/icons-material/BrandingWatermark";
|
||||||
import { useMultipleEndpointsEnabled } from "./useEndpointConfig";
|
import { useMultipleEndpointsEnabled } from "./useEndpointConfig";
|
||||||
import { Tool, ToolDefinition, BaseToolProps, ToolRegistry } from "../types/tool";
|
import { Tool, ToolDefinition, BaseToolProps, ToolRegistry } from "../types/tool";
|
||||||
|
|
||||||
@ -104,6 +105,15 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
|||||||
description: "Change document restrictions and permissions",
|
description: "Change document restrictions and permissions",
|
||||||
endpoints: ["add-password"]
|
endpoints: ["add-password"]
|
||||||
},
|
},
|
||||||
|
watermark: {
|
||||||
|
id: "watermark",
|
||||||
|
icon: <BrandingWatermarkIcon />,
|
||||||
|
component: React.lazy(() => import("../tools/AddWatermark")),
|
||||||
|
maxFiles: -1,
|
||||||
|
category: "security",
|
||||||
|
description: "Add text or image watermarks to PDF files",
|
||||||
|
endpoints: ["add-watermark"]
|
||||||
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
167
frontend/src/tools/AddWatermark.tsx
Normal file
167
frontend/src/tools/AddWatermark.tsx
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import React, { useEffect, useMemo } from "react";
|
||||||
|
import { Button, Stack, Text } from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import DownloadIcon from "@mui/icons-material/Download";
|
||||||
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
|
import { useFileContext } from "../contexts/FileContext";
|
||||||
|
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
||||||
|
|
||||||
|
import ToolStep, { ToolStepContainer } from "../components/tools/shared/ToolStep";
|
||||||
|
import OperationButton from "../components/tools/shared/OperationButton";
|
||||||
|
import ErrorNotification from "../components/tools/shared/ErrorNotification";
|
||||||
|
import FileStatusIndicator from "../components/tools/shared/FileStatusIndicator";
|
||||||
|
import ResultsPreview from "../components/tools/shared/ResultsPreview";
|
||||||
|
|
||||||
|
import AddWatermarkSettings from "../components/tools/addWatermark/AddWatermarkSettings";
|
||||||
|
|
||||||
|
import { useAddWatermarkParameters } from "../hooks/tools/addWatermark/useAddWatermarkParameters";
|
||||||
|
import { useAddWatermarkOperation } from "../hooks/tools/addWatermark/useAddWatermarkOperation";
|
||||||
|
import { BaseToolProps } from "../types/tool";
|
||||||
|
|
||||||
|
const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { setCurrentMode } = useFileContext();
|
||||||
|
const { selectedFiles } = useToolFileSelection();
|
||||||
|
|
||||||
|
const watermarkParams = useAddWatermarkParameters();
|
||||||
|
const watermarkOperation = useAddWatermarkOperation();
|
||||||
|
|
||||||
|
// Endpoint validation
|
||||||
|
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("add-watermark");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
watermarkOperation.resetResults();
|
||||||
|
onPreviewFile?.(null);
|
||||||
|
}, [watermarkParams.parameters, selectedFiles]);
|
||||||
|
|
||||||
|
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 : 'Add watermark operation failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleThumbnailClick = (file: File) => {
|
||||||
|
onPreviewFile?.(file);
|
||||||
|
sessionStorage.setItem('previousMode', 'watermark');
|
||||||
|
setCurrentMode('viewer');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSettingsReset = () => {
|
||||||
|
watermarkOperation.resetResults();
|
||||||
|
onPreviewFile?.(null);
|
||||||
|
setCurrentMode('watermark');
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasFiles = selectedFiles.length > 0;
|
||||||
|
const hasResults = watermarkOperation.files.length > 0 || watermarkOperation.downloadUrl !== null;
|
||||||
|
const filesCollapsed = hasFiles;
|
||||||
|
const settingsCollapsed = hasResults;
|
||||||
|
|
||||||
|
const previewResults = useMemo(() =>
|
||||||
|
watermarkOperation.files?.map((file, index) => ({
|
||||||
|
file,
|
||||||
|
thumbnail: watermarkOperation.thumbnails[index]
|
||||||
|
})) || [],
|
||||||
|
[watermarkOperation.files, watermarkOperation.thumbnails]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolStepContainer>
|
||||||
|
<Stack gap="sm" h="100%" p="sm" style={{ overflow: 'auto' }}>
|
||||||
|
{/* Files Step */}
|
||||||
|
<ToolStep
|
||||||
|
title="Files"
|
||||||
|
isVisible={true}
|
||||||
|
isCollapsed={filesCollapsed}
|
||||||
|
isCompleted={filesCollapsed}
|
||||||
|
completedMessage={hasFiles ?
|
||||||
|
selectedFiles.length === 1
|
||||||
|
? `Selected: ${selectedFiles[0].name}`
|
||||||
|
: `Selected: ${selectedFiles.length} files`
|
||||||
|
: undefined}
|
||||||
|
>
|
||||||
|
<FileStatusIndicator
|
||||||
|
selectedFiles={selectedFiles}
|
||||||
|
placeholder="Select a PDF file in the main view to get started"
|
||||||
|
/>
|
||||||
|
</ToolStep>
|
||||||
|
|
||||||
|
{/* Settings Step */}
|
||||||
|
<ToolStep
|
||||||
|
title="Settings"
|
||||||
|
isVisible={hasFiles}
|
||||||
|
isCollapsed={settingsCollapsed}
|
||||||
|
isCompleted={settingsCollapsed}
|
||||||
|
onCollapsedClick={settingsCollapsed ? handleSettingsReset : undefined}
|
||||||
|
completedMessage={settingsCollapsed ? "Watermark added" : undefined}
|
||||||
|
>
|
||||||
|
<Stack gap="sm">
|
||||||
|
<AddWatermarkSettings
|
||||||
|
parameters={watermarkParams.parameters}
|
||||||
|
onParameterChange={watermarkParams.updateParameter}
|
||||||
|
disabled={endpointLoading}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<OperationButton
|
||||||
|
onClick={handleAddWatermark}
|
||||||
|
isLoading={watermarkOperation.isLoading}
|
||||||
|
disabled={!watermarkParams.validateParameters() || !hasFiles || !endpointEnabled}
|
||||||
|
loadingText={t("loading")}
|
||||||
|
submitText="Add Watermark and Review"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</ToolStep>
|
||||||
|
|
||||||
|
{/* Results Step */}
|
||||||
|
<ToolStep
|
||||||
|
title="Results"
|
||||||
|
isVisible={hasResults}
|
||||||
|
>
|
||||||
|
<Stack gap="sm">
|
||||||
|
{watermarkOperation.status && (
|
||||||
|
<Text size="sm" c="dimmed">{watermarkOperation.status}</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ErrorNotification
|
||||||
|
error={watermarkOperation.errorMessage}
|
||||||
|
onClose={watermarkOperation.clearError}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{watermarkOperation.downloadUrl && (
|
||||||
|
<Button
|
||||||
|
component="a"
|
||||||
|
href={watermarkOperation.downloadUrl}
|
||||||
|
download={watermarkOperation.downloadFilename}
|
||||||
|
leftSection={<DownloadIcon />}
|
||||||
|
color="green"
|
||||||
|
fullWidth
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
{t("download", "Download")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ResultsPreview
|
||||||
|
files={previewResults}
|
||||||
|
onFileClick={handleThumbnailClick}
|
||||||
|
isGeneratingThumbnails={watermarkOperation.isGeneratingThumbnails}
|
||||||
|
title="Watermark Results"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</ToolStep>
|
||||||
|
</Stack>
|
||||||
|
</ToolStepContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddWatermark;
|
@ -16,7 +16,8 @@ export type ModeType =
|
|||||||
| 'convert'
|
| 'convert'
|
||||||
| 'sanitize'
|
| 'sanitize'
|
||||||
| 'addPassword'
|
| 'addPassword'
|
||||||
| 'changePermissions';
|
| 'changePermissions'
|
||||||
|
| 'watermark';
|
||||||
|
|
||||||
export type ViewType = 'viewer' | 'pageEditor' | 'fileEditor';
|
export type ViewType = 'viewer' | 'pageEditor' | 'fileEditor';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user