mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-23 12:06:14 +00:00
Canvas and dosument draw split, drawing improvements
This commit is contained in:
parent
a70472b172
commit
32fed96aa7
@ -1,6 +1,6 @@
|
|||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Stack, TextInput, FileInput, Paper, Group, Button, Text, Alert } from '@mantine/core';
|
import { Stack, TextInput, FileInput, Paper, Group, Button, Text, Alert, Modal, ColorSwatch, Menu, ActionIcon, Slider } from '@mantine/core';
|
||||||
import ButtonSelector from "../../shared/ButtonSelector";
|
import ButtonSelector from "../../shared/ButtonSelector";
|
||||||
import { SignParameters } from "../../../hooks/tools/sign/useSignParameters";
|
import { SignParameters } from "../../../hooks/tools/sign/useSignParameters";
|
||||||
|
|
||||||
@ -11,13 +11,21 @@ interface SignSettingsProps {
|
|||||||
onActivateDrawMode?: () => void;
|
onActivateDrawMode?: () => void;
|
||||||
onActivateSignaturePlacement?: () => void;
|
onActivateSignaturePlacement?: () => void;
|
||||||
onDeactivateSignature?: () => void;
|
onDeactivateSignature?: () => void;
|
||||||
|
onUpdateDrawSettings?: (color: string, size: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SignSettings = ({ parameters, onParameterChange, disabled = false, onActivateDrawMode, onActivateSignaturePlacement, onDeactivateSignature }: SignSettingsProps) => {
|
const SignSettings = ({ parameters, onParameterChange, disabled = false, onActivateDrawMode, onActivateSignaturePlacement, onDeactivateSignature, onUpdateDrawSettings }: SignSettingsProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
const [isDrawing, setIsDrawing] = useState(false);
|
const [isDrawing, setIsDrawing] = useState(false);
|
||||||
const [signatureImage, setSignatureImage] = useState<File | null>(null);
|
const [signatureImage, setSignatureImage] = useState<File | null>(null);
|
||||||
|
const [canvasSignatureData, setCanvasSignatureData] = useState<string | null>(null);
|
||||||
|
const [imageSignatureData, setImageSignatureData] = useState<string | null>(null);
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const modalCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const [isModalDrawing, setIsModalDrawing] = useState(false);
|
||||||
|
const [selectedColor, setSelectedColor] = useState('#000000');
|
||||||
|
const [penSize, setPenSize] = useState(2);
|
||||||
|
|
||||||
// Drawing functions for signature canvas
|
// Drawing functions for signature canvas
|
||||||
const startDrawing = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
const startDrawing = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
||||||
@ -25,11 +33,14 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
|
|
||||||
setIsDrawing(true);
|
setIsDrawing(true);
|
||||||
const rect = canvasRef.current.getBoundingClientRect();
|
const rect = canvasRef.current.getBoundingClientRect();
|
||||||
const x = e.clientX - rect.left;
|
const scaleX = canvasRef.current.width / rect.width;
|
||||||
const y = e.clientY - rect.top;
|
const scaleY = canvasRef.current.height / rect.height;
|
||||||
|
const x = (e.clientX - rect.left) * scaleX;
|
||||||
|
const y = (e.clientY - rect.top) * scaleY;
|
||||||
|
|
||||||
const ctx = canvasRef.current.getContext('2d');
|
const ctx = canvasRef.current.getContext('2d');
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
|
ctx.strokeStyle = selectedColor;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(x, y);
|
ctx.moveTo(x, y);
|
||||||
}
|
}
|
||||||
@ -39,8 +50,10 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
if (!isDrawing || !canvasRef.current || disabled) return;
|
if (!isDrawing || !canvasRef.current || disabled) return;
|
||||||
|
|
||||||
const rect = canvasRef.current.getBoundingClientRect();
|
const rect = canvasRef.current.getBoundingClientRect();
|
||||||
const x = e.clientX - rect.left;
|
const scaleX = canvasRef.current.width / rect.width;
|
||||||
const y = e.clientY - rect.top;
|
const scaleY = canvasRef.current.height / rect.height;
|
||||||
|
const x = (e.clientX - rect.left) * scaleX;
|
||||||
|
const y = (e.clientY - rect.top) * scaleY;
|
||||||
|
|
||||||
const ctx = canvasRef.current.getContext('2d');
|
const ctx = canvasRef.current.getContext('2d');
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
@ -58,7 +71,15 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
if (canvasRef.current) {
|
if (canvasRef.current) {
|
||||||
const dataURL = canvasRef.current.toDataURL('image/png');
|
const dataURL = canvasRef.current.toDataURL('image/png');
|
||||||
console.log('Saving canvas signature data:', dataURL.substring(0, 50) + '...');
|
console.log('Saving canvas signature data:', dataURL.substring(0, 50) + '...');
|
||||||
|
setCanvasSignatureData(dataURL);
|
||||||
onParameterChange('signatureData', dataURL);
|
onParameterChange('signatureData', dataURL);
|
||||||
|
|
||||||
|
// Auto-activate placement mode after drawing
|
||||||
|
setTimeout(() => {
|
||||||
|
if (onActivateSignaturePlacement) {
|
||||||
|
onActivateSignaturePlacement();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -68,10 +89,90 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
const ctx = canvasRef.current.getContext('2d');
|
const ctx = canvasRef.current.getContext('2d');
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
|
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
|
||||||
|
setCanvasSignatureData(null);
|
||||||
onParameterChange('signatureData', undefined);
|
onParameterChange('signatureData', undefined);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Modal canvas drawing functions
|
||||||
|
const startModalDrawing = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
||||||
|
if (!modalCanvasRef.current) return;
|
||||||
|
|
||||||
|
setIsModalDrawing(true);
|
||||||
|
const rect = modalCanvasRef.current.getBoundingClientRect();
|
||||||
|
const scaleX = modalCanvasRef.current.width / rect.width;
|
||||||
|
const scaleY = modalCanvasRef.current.height / rect.height;
|
||||||
|
const x = (e.clientX - rect.left) * scaleX;
|
||||||
|
const y = (e.clientY - rect.top) * scaleY;
|
||||||
|
|
||||||
|
const ctx = modalCanvasRef.current.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
ctx.strokeStyle = selectedColor;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawModal = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
||||||
|
if (!isModalDrawing || !modalCanvasRef.current) return;
|
||||||
|
|
||||||
|
const rect = modalCanvasRef.current.getBoundingClientRect();
|
||||||
|
const scaleX = modalCanvasRef.current.width / rect.width;
|
||||||
|
const scaleY = modalCanvasRef.current.height / rect.height;
|
||||||
|
const x = (e.clientX - rect.left) * scaleX;
|
||||||
|
const y = (e.clientY - rect.top) * scaleY;
|
||||||
|
|
||||||
|
const ctx = modalCanvasRef.current.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopModalDrawing = () => {
|
||||||
|
if (!isModalDrawing) return;
|
||||||
|
setIsModalDrawing(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearModalCanvas = () => {
|
||||||
|
if (!modalCanvasRef.current) return;
|
||||||
|
|
||||||
|
const ctx = modalCanvasRef.current.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
ctx.clearRect(0, 0, modalCanvasRef.current.width, modalCanvasRef.current.height);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveModalSignature = () => {
|
||||||
|
if (!modalCanvasRef.current) return;
|
||||||
|
|
||||||
|
const dataURL = modalCanvasRef.current.toDataURL('image/png');
|
||||||
|
setCanvasSignatureData(dataURL);
|
||||||
|
onParameterChange('signatureData', dataURL);
|
||||||
|
|
||||||
|
// Copy to small canvas for display
|
||||||
|
if (canvasRef.current) {
|
||||||
|
const ctx = canvasRef.current.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
ctx.clearRect(0, 0, canvasRef.current!.width, canvasRef.current!.height);
|
||||||
|
ctx.drawImage(img, 0, 0, canvasRef.current!.width, canvasRef.current!.height);
|
||||||
|
};
|
||||||
|
img.src = dataURL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsModalOpen(false);
|
||||||
|
|
||||||
|
// Auto-activate placement mode after saving modal signature
|
||||||
|
setTimeout(() => {
|
||||||
|
if (onActivateSignaturePlacement) {
|
||||||
|
onActivateSignaturePlacement();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
// Handle signature image upload
|
// Handle signature image upload
|
||||||
const handleSignatureImageChange = (file: File | null) => {
|
const handleSignatureImageChange = (file: File | null) => {
|
||||||
console.log('Image file selected:', file);
|
console.log('Image file selected:', file);
|
||||||
@ -80,7 +181,15 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
if (e.target?.result) {
|
if (e.target?.result) {
|
||||||
console.log('Image loaded, saving to signatureData, length:', (e.target.result as string).length);
|
console.log('Image loaded, saving to signatureData, length:', (e.target.result as string).length);
|
||||||
|
setImageSignatureData(e.target.result as string);
|
||||||
onParameterChange('signatureData', e.target.result as string);
|
onParameterChange('signatureData', e.target.result as string);
|
||||||
|
|
||||||
|
// Auto-activate placement mode after image upload
|
||||||
|
setTimeout(() => {
|
||||||
|
if (onActivateSignaturePlacement) {
|
||||||
|
onActivateSignaturePlacement();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
@ -90,16 +199,77 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
|
|
||||||
// Initialize canvas
|
// Initialize canvas
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (canvasRef.current && parameters.signatureType === 'draw') {
|
if (canvasRef.current && parameters.signatureType === 'canvas') {
|
||||||
const ctx = canvasRef.current.getContext('2d');
|
const ctx = canvasRef.current.getContext('2d');
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
ctx.strokeStyle = '#000000';
|
ctx.strokeStyle = selectedColor;
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
ctx.lineJoin = 'round';
|
ctx.lineJoin = 'round';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [parameters.signatureType]);
|
}, [parameters.signatureType, selectedColor]);
|
||||||
|
|
||||||
|
// Initialize modal canvas when opened
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (modalCanvasRef.current && isModalOpen) {
|
||||||
|
const ctx = modalCanvasRef.current.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
ctx.strokeStyle = selectedColor;
|
||||||
|
ctx.lineWidth = 3;
|
||||||
|
ctx.lineCap = 'round';
|
||||||
|
ctx.lineJoin = 'round';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isModalOpen, selectedColor]);
|
||||||
|
|
||||||
|
// Switch signature data based on mode
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (parameters.signatureType === 'canvas' && canvasSignatureData) {
|
||||||
|
onParameterChange('signatureData', canvasSignatureData);
|
||||||
|
} else if (parameters.signatureType === 'image' && imageSignatureData) {
|
||||||
|
onParameterChange('signatureData', imageSignatureData);
|
||||||
|
}
|
||||||
|
}, [parameters.signatureType, canvasSignatureData, imageSignatureData, onParameterChange]);
|
||||||
|
|
||||||
|
// Initialize draw mode on mount if draw type is selected
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log('SignSettings: Component mounted, initial signatureType:', parameters.signatureType);
|
||||||
|
if (parameters.signatureType === 'draw' && onActivateDrawMode) {
|
||||||
|
console.log('SignSettings: Initial activation of draw mode with delay');
|
||||||
|
// Add a delay to ensure the API bridge is ready
|
||||||
|
setTimeout(() => {
|
||||||
|
onActivateDrawMode();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}, [onActivateDrawMode]); // Only run on mount/when callback changes
|
||||||
|
|
||||||
|
// Auto-activate draw mode when draw type is selected
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log('SignSettings: signatureType changed to:', parameters.signatureType);
|
||||||
|
if (parameters.signatureType === 'draw') {
|
||||||
|
console.log('SignSettings: Activating draw mode, onActivateDrawMode:', !!onActivateDrawMode);
|
||||||
|
if (onActivateDrawMode) {
|
||||||
|
onActivateDrawMode();
|
||||||
|
}
|
||||||
|
} else if (parameters.signatureType !== 'draw') {
|
||||||
|
console.log('SignSettings: Deactivating draw mode, onDeactivateSignature:', !!onDeactivateSignature);
|
||||||
|
if (onDeactivateSignature) {
|
||||||
|
onDeactivateSignature();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [parameters.signatureType, onActivateDrawMode, onDeactivateSignature]);
|
||||||
|
|
||||||
|
// Update draw settings when color or pen size changes
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log('SignSettings: Draw settings changed - color:', selectedColor, 'penSize:', penSize, 'signatureType:', parameters.signatureType);
|
||||||
|
if (parameters.signatureType === 'draw' && onUpdateDrawSettings) {
|
||||||
|
console.log('SignSettings: Calling onUpdateDrawSettings');
|
||||||
|
onUpdateDrawSettings(selectedColor, penSize);
|
||||||
|
} else {
|
||||||
|
console.log('SignSettings: Not calling onUpdateDrawSettings - signatureType not draw or function not available');
|
||||||
|
}
|
||||||
|
}, [selectedColor, penSize, parameters.signatureType, onUpdateDrawSettings]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
@ -110,12 +280,16 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
</Text>
|
</Text>
|
||||||
<ButtonSelector
|
<ButtonSelector
|
||||||
value={parameters.signatureType}
|
value={parameters.signatureType}
|
||||||
onChange={(value) => onParameterChange('signatureType', value as 'image' | 'text' | 'draw')}
|
onChange={(value) => onParameterChange('signatureType', value as 'image' | 'text' | 'draw' | 'canvas')}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
value: 'draw',
|
value: 'draw',
|
||||||
label: t('sign.type.draw', 'Draw'),
|
label: t('sign.type.draw', 'Draw'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'canvas',
|
||||||
|
label: t('sign.type.canvas', 'Canvas'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: 'image',
|
value: 'image',
|
||||||
label: t('sign.type.image', 'Image'),
|
label: t('sign.type.image', 'Image'),
|
||||||
@ -130,20 +304,72 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Signature Creation based on type */}
|
{/* Signature Creation based on type */}
|
||||||
{parameters.signatureType === 'draw' && (
|
{parameters.signatureType === 'canvas' && (
|
||||||
<Paper withBorder p="md">
|
<Paper withBorder p="md" style={{ position: 'relative' }}>
|
||||||
|
<ActionIcon
|
||||||
|
variant="filled"
|
||||||
|
color="blue"
|
||||||
|
size="sm"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
zIndex: 10,
|
||||||
|
}}
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
disabled={disabled}
|
||||||
|
title="Expand Canvas"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</ActionIcon>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text fw={500}>{t('sign.draw.title', 'Draw your signature')}</Text>
|
<Text fw={500}>{t('sign.draw.title', 'Draw your signature')}</Text>
|
||||||
<Button
|
<Group gap="sm">
|
||||||
variant="subtle"
|
<Menu shadow="md" width={200}>
|
||||||
color="red"
|
<Menu.Target>
|
||||||
size="compact-sm"
|
<Button
|
||||||
onClick={clearCanvas}
|
variant="subtle"
|
||||||
disabled={disabled}
|
size="compact-sm"
|
||||||
>
|
disabled={disabled}
|
||||||
{t('sign.draw.clear', 'Clear')}
|
rightSection={
|
||||||
</Button>
|
<ColorSwatch
|
||||||
|
color={selectedColor}
|
||||||
|
size={12}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Color
|
||||||
|
</Button>
|
||||||
|
</Menu.Target>
|
||||||
|
<Menu.Dropdown>
|
||||||
|
<Menu.Label>Select Color</Menu.Label>
|
||||||
|
<Group gap="xs" p="xs">
|
||||||
|
{['#000000', '#0066cc', '#cc0000', '#cc6600', '#009900', '#6600cc'].map((color) => (
|
||||||
|
<ColorSwatch
|
||||||
|
key={color}
|
||||||
|
color={color}
|
||||||
|
size={24}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
border: selectedColor === color ? '2px solid #333' : '1px solid #ddd'
|
||||||
|
}}
|
||||||
|
onClick={() => setSelectedColor(color)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
color="red"
|
||||||
|
size="compact-sm"
|
||||||
|
onClick={clearCanvas}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{t('sign.draw.clear', 'Clear')}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
@ -154,15 +380,13 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
cursor: disabled ? 'default' : 'crosshair',
|
cursor: disabled ? 'default' : 'crosshair',
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
onMouseDown={startDrawing}
|
onMouseDown={startDrawing}
|
||||||
onMouseMove={draw}
|
onMouseMove={draw}
|
||||||
onMouseUp={stopDrawing}
|
onMouseUp={stopDrawing}
|
||||||
onMouseLeave={stopDrawing}
|
onMouseLeave={stopDrawing}
|
||||||
/>
|
/>
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
{t('sign.draw.hint', 'Click and drag to draw your signature')}
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
)}
|
)}
|
||||||
@ -199,63 +423,172 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Direct PDF Drawing */}
|
||||||
|
{parameters.signatureType === 'draw' && (
|
||||||
|
<Paper withBorder p="md">
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fw={500}>Direct PDF Drawing</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Draw signatures and annotations directly on the PDF document. Drawing mode will be activated automatically when you go to the PDF viewer.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* Drawing Controls */}
|
||||||
|
<Group gap="md" align="flex-end">
|
||||||
|
{/* Color Picker */}
|
||||||
|
<div>
|
||||||
|
<Text size="sm" fw={500} mb="xs">Color</Text>
|
||||||
|
<Group gap="xs">
|
||||||
|
{['#000000', '#0066cc', '#cc0000', '#cc6600', '#009900', '#6600cc'].map((color) => (
|
||||||
|
<ColorSwatch
|
||||||
|
key={color}
|
||||||
|
color={color}
|
||||||
|
size={24}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
border: selectedColor === color ? '2px solid #333' : '1px solid #ddd'
|
||||||
|
}}
|
||||||
|
onClick={() => setSelectedColor(color)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pen Size */}
|
||||||
|
<div style={{ flexGrow: 1, maxWidth: '200px' }}>
|
||||||
|
<Text size="sm" fw={500} mb="xs">Pen Size: {penSize}px</Text>
|
||||||
|
<Slider
|
||||||
|
value={penSize}
|
||||||
|
onChange={setPenSize}
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
step={1}
|
||||||
|
marks={[
|
||||||
|
{ value: 1, label: '1' },
|
||||||
|
{ value: 5, label: '5' },
|
||||||
|
{ value: 10, label: '10' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
{/* Instructions for placing signature */}
|
{/* Instructions for placing signature */}
|
||||||
<Alert color="blue" title={t('sign.instructions.title', 'How to add signature')}>
|
{(parameters.signatureType === 'canvas' || parameters.signatureType === 'image' || parameters.signatureType === 'text') && (
|
||||||
<Text size="sm">
|
<Alert color="blue" title={t('sign.instructions.title', 'How to add signature')}>
|
||||||
{parameters.signatureType === 'draw' && t('sign.instructions.draw', 'Draw your signature above, then click "Draw Directly on PDF" to draw live, or "Place Canvas Signature" to place your drawn signature.')}
|
<Text size="sm">
|
||||||
{parameters.signatureType === 'image' && t('sign.instructions.image', 'Upload your signature image above, then click "Activate Image Placement" to place it on the PDF.')}
|
{parameters.signatureType === 'canvas' && 'Draw your signature in the canvas above. Placement mode will activate automatically, or click the buttons below to control placement.'}
|
||||||
{parameters.signatureType === 'text' && t('sign.instructions.text', 'Enter your name above, then click "Activate Text Signature" to place it on the PDF.')}
|
{parameters.signatureType === 'image' && 'Upload your signature image above. Placement mode will activate automatically, or click the buttons below to control placement.'}
|
||||||
</Text>
|
{parameters.signatureType === 'text' && 'Enter your name above. Placement mode will activate automatically, or click the buttons below to control placement.'}
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Group mt="sm" gap="sm">
|
<Group mt="sm" gap="sm">
|
||||||
{/* Universal activation button */}
|
{/* Universal activation button */}
|
||||||
{((parameters.signatureType === 'draw' && parameters.signatureData) ||
|
{((parameters.signatureType === 'canvas' && parameters.signatureData) ||
|
||||||
(parameters.signatureType === 'image' && parameters.signatureData) ||
|
(parameters.signatureType === 'image' && parameters.signatureData) ||
|
||||||
(parameters.signatureType === 'text' && parameters.signerName)) && (
|
(parameters.signatureType === 'text' && parameters.signerName)) && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (onActivateSignaturePlacement) {
|
||||||
|
onActivateSignaturePlacement();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{t('sign.activate', 'Activate Signature Placement')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Universal deactivate button */}
|
||||||
<Button
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (onActivateSignaturePlacement) {
|
if (onDeactivateSignature) {
|
||||||
onActivateSignaturePlacement();
|
onDeactivateSignature();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{t('sign.activate', 'Activate Signature Placement')}
|
{t('sign.deactivate', 'Stop Placing Signatures')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</Group>
|
||||||
|
|
||||||
{/* Draw directly mode for draw type */}
|
</Alert>
|
||||||
{parameters.signatureType === 'draw' && (
|
)}
|
||||||
<Button
|
|
||||||
variant="outline"
|
{/* Modal for larger signature canvas */}
|
||||||
onClick={() => {
|
<Modal
|
||||||
if (onActivateDrawMode) {
|
opened={isModalOpen}
|
||||||
onActivateDrawMode();
|
onClose={() => setIsModalOpen(false)}
|
||||||
}
|
title="Draw Your Signature"
|
||||||
|
size="xl"
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
{/* Color picker */}
|
||||||
|
<Paper withBorder p="sm">
|
||||||
|
<Group gap="sm" align="center">
|
||||||
|
<Text size="sm" fw={500}>Color:</Text>
|
||||||
|
{['#000000', '#0066cc', '#cc0000', '#cc6600', '#009900', '#6600cc'].map((color) => (
|
||||||
|
<ColorSwatch
|
||||||
|
key={color}
|
||||||
|
color={color}
|
||||||
|
size={24}
|
||||||
|
style={{ cursor: 'pointer', border: selectedColor === color ? '2px solid #333' : 'none' }}
|
||||||
|
onClick={() => setSelectedColor(color)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<Paper withBorder p="md">
|
||||||
|
<canvas
|
||||||
|
ref={modalCanvasRef}
|
||||||
|
width={800}
|
||||||
|
height={400}
|
||||||
|
style={{
|
||||||
|
border: '1px solid #ccc',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'crosshair',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '800px',
|
||||||
|
height: 'auto',
|
||||||
}}
|
}}
|
||||||
disabled={disabled}
|
onMouseDown={startModalDrawing}
|
||||||
|
onMouseMove={drawModal}
|
||||||
|
onMouseUp={stopModalDrawing}
|
||||||
|
onMouseLeave={stopModalDrawing}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
color="red"
|
||||||
|
onClick={clearModalCanvas}
|
||||||
>
|
>
|
||||||
{t('sign.activate.draw', 'Draw Directly on PDF')}
|
Clear Canvas
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
<Group gap="sm">
|
||||||
|
<Button
|
||||||
{/* Universal deactivate button */}
|
variant="subtle"
|
||||||
<Button
|
onClick={() => setIsModalOpen(false)}
|
||||||
variant="subtle"
|
>
|
||||||
color="red"
|
Cancel
|
||||||
onClick={() => {
|
</Button>
|
||||||
if (onDeactivateSignature) {
|
<Button
|
||||||
onDeactivateSignature();
|
onClick={saveModalSignature}
|
||||||
}
|
>
|
||||||
}}
|
Save Signature
|
||||||
disabled={disabled}
|
</Button>
|
||||||
>
|
</Group>
|
||||||
{t('sign.deactivate', 'Stop Placing Signatures')}
|
</Group>
|
||||||
</Button>
|
</Stack>
|
||||||
</Group>
|
</Modal>
|
||||||
|
|
||||||
</Alert>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,7 @@ export interface SignatureAPI {
|
|||||||
addTextSignature: (text: string, x: number, y: number, pageIndex: number) => void;
|
addTextSignature: (text: string, x: number, y: number, pageIndex: number) => void;
|
||||||
activateDrawMode: () => void;
|
activateDrawMode: () => void;
|
||||||
activateSignaturePlacementMode: () => void;
|
activateSignaturePlacementMode: () => void;
|
||||||
|
updateDrawSettings: (color: string, size: number) => void;
|
||||||
deactivateTools: () => void;
|
deactivateTools: () => void;
|
||||||
applySignatureFromParameters: (params: SignParameters) => void;
|
applySignatureFromParameters: (params: SignParameters) => void;
|
||||||
}
|
}
|
||||||
@ -63,9 +64,26 @@ export const SignatureAPIBridge = forwardRef<SignatureAPI, SignatureAPIBridgePro
|
|||||||
},
|
},
|
||||||
|
|
||||||
activateDrawMode: () => {
|
activateDrawMode: () => {
|
||||||
if (!annotationApi) return;
|
console.log('SignatureAPIBridge.activateDrawMode called, annotationApi:', !!annotationApi);
|
||||||
|
if (!annotationApi) {
|
||||||
|
console.log('No annotationApi available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Setting active tool to ink');
|
||||||
// Activate the built-in ink tool for drawing
|
// Activate the built-in ink tool for drawing
|
||||||
annotationApi.setActiveTool('ink');
|
annotationApi.setActiveTool('ink');
|
||||||
|
|
||||||
|
// Set default ink tool properties (black color, 2px width)
|
||||||
|
const activeTool = annotationApi.getActiveTool();
|
||||||
|
console.log('Active tool after setting ink:', activeTool);
|
||||||
|
if (activeTool && activeTool.id === 'ink') {
|
||||||
|
console.log('Setting ink tool defaults');
|
||||||
|
annotationApi.setToolDefaults('ink', {
|
||||||
|
color: '#000000',
|
||||||
|
thickness: 2
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
activateSignaturePlacementMode: () => {
|
activateSignaturePlacementMode: () => {
|
||||||
@ -107,6 +125,28 @@ export const SignatureAPIBridge = forwardRef<SignatureAPI, SignatureAPIBridgePro
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateDrawSettings: (color: string, size: number) => {
|
||||||
|
console.log('SignatureAPIBridge.updateDrawSettings called with color:', color, 'size:', size);
|
||||||
|
if (!annotationApi) {
|
||||||
|
console.log('No annotationApi available for updateDrawSettings');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always update ink tool defaults regardless of current tool
|
||||||
|
console.log('Setting ink tool defaults');
|
||||||
|
annotationApi.setToolDefaults('ink', {
|
||||||
|
color: color,
|
||||||
|
thickness: size
|
||||||
|
});
|
||||||
|
|
||||||
|
// If ink tool is currently active, reactivate it to apply settings immediately
|
||||||
|
const activeTool = annotationApi.getActiveTool();
|
||||||
|
console.log('Current active tool:', activeTool);
|
||||||
|
if (activeTool && activeTool.id === 'ink') {
|
||||||
|
console.log('Reactivating ink tool to apply new settings');
|
||||||
|
annotationApi.setActiveTool('ink');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
deactivateTools: () => {
|
deactivateTools: () => {
|
||||||
if (!annotationApi) return;
|
if (!annotationApi) return;
|
||||||
|
@ -17,6 +17,7 @@ interface SignatureActions {
|
|||||||
activateDrawMode: () => void;
|
activateDrawMode: () => void;
|
||||||
deactivateDrawMode: () => void;
|
deactivateDrawMode: () => void;
|
||||||
activateSignaturePlacementMode: () => void;
|
activateSignaturePlacementMode: () => void;
|
||||||
|
updateDrawSettings: (color: string, size: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combined context interface
|
// Combined context interface
|
||||||
@ -60,9 +61,14 @@ export const SignatureProvider: React.FC<{ children: ReactNode }> = ({ children
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const activateDrawMode = useCallback(() => {
|
const activateDrawMode = useCallback(() => {
|
||||||
|
console.log('SignatureContext.activateDrawMode called, apiRef:', !!signatureApiRef.current);
|
||||||
if (signatureApiRef.current) {
|
if (signatureApiRef.current) {
|
||||||
|
console.log('Calling signatureApiRef.current.activateDrawMode()');
|
||||||
signatureApiRef.current.activateDrawMode();
|
signatureApiRef.current.activateDrawMode();
|
||||||
setPlacementMode(true);
|
setPlacementMode(true);
|
||||||
|
console.log('Draw mode activated successfully');
|
||||||
|
} else {
|
||||||
|
console.log('signatureApiRef.current is null - cannot activate draw mode');
|
||||||
}
|
}
|
||||||
}, [setPlacementMode]);
|
}, [setPlacementMode]);
|
||||||
|
|
||||||
@ -84,6 +90,16 @@ export const SignatureProvider: React.FC<{ children: ReactNode }> = ({ children
|
|||||||
}
|
}
|
||||||
}, [state.signatureConfig, setPlacementMode]);
|
}, [state.signatureConfig, setPlacementMode]);
|
||||||
|
|
||||||
|
const updateDrawSettings = useCallback((color: string, size: number) => {
|
||||||
|
console.log('SignatureContext.updateDrawSettings called with color:', color, 'size:', size);
|
||||||
|
console.log('signatureApiRef.current available:', !!signatureApiRef.current);
|
||||||
|
if (signatureApiRef.current) {
|
||||||
|
signatureApiRef.current.updateDrawSettings(color, size);
|
||||||
|
} else {
|
||||||
|
console.log('signatureApiRef.current is null - cannot update draw settings');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
// No auto-activation - all modes use manual buttons
|
// No auto-activation - all modes use manual buttons
|
||||||
|
|
||||||
@ -95,6 +111,7 @@ export const SignatureProvider: React.FC<{ children: ReactNode }> = ({ children
|
|||||||
activateDrawMode,
|
activateDrawMode,
|
||||||
deactivateDrawMode,
|
deactivateDrawMode,
|
||||||
activateSignaturePlacementMode,
|
activateSignaturePlacementMode,
|
||||||
|
updateDrawSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -9,7 +9,7 @@ export interface SignaturePosition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SignParameters {
|
export interface SignParameters {
|
||||||
signatureType: 'image' | 'text' | 'draw';
|
signatureType: 'image' | 'text' | 'draw' | 'canvas';
|
||||||
signatureData?: string; // Base64 encoded image or text content
|
signatureData?: string; // Base64 encoded image or text content
|
||||||
signaturePosition?: SignaturePosition;
|
signaturePosition?: SignaturePosition;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
@ -41,6 +41,11 @@ const validateSignParameters = (parameters: SignParameters): boolean => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For canvas signatures, require signature data
|
||||||
|
if (parameters.signatureType === 'canvas' && !parameters.signatureData) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// For text signatures, require signer name
|
// For text signatures, require signer name
|
||||||
if (parameters.signatureType === 'text' && !parameters.signerName) {
|
if (parameters.signatureType === 'text' && !parameters.signerName) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -12,7 +12,7 @@ import { useSignature } from "../contexts/SignatureContext";
|
|||||||
const Sign = (props: BaseToolProps) => {
|
const Sign = (props: BaseToolProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setWorkbench } = useNavigation();
|
const { setWorkbench } = useNavigation();
|
||||||
const { setSignatureConfig, activateDrawMode, activateSignaturePlacementMode, deactivateDrawMode } = useSignature();
|
const { setSignatureConfig, activateDrawMode, activateSignaturePlacementMode, deactivateDrawMode, updateDrawSettings } = useSignature();
|
||||||
|
|
||||||
// Manual sync function
|
// Manual sync function
|
||||||
const syncSignatureConfig = () => {
|
const syncSignatureConfig = () => {
|
||||||
@ -41,6 +41,16 @@ const Sign = (props: BaseToolProps) => {
|
|||||||
}
|
}
|
||||||
}, [base.selectedFiles.length, setWorkbench]);
|
}, [base.selectedFiles.length, setWorkbench]);
|
||||||
|
|
||||||
|
// Auto-activate draw mode when files are loaded and draw type is selected
|
||||||
|
useEffect(() => {
|
||||||
|
if (base.selectedFiles.length > 0 && base.params.parameters.signatureType === 'draw') {
|
||||||
|
console.log('Sign: Files loaded with draw mode, activating after delay');
|
||||||
|
setTimeout(() => {
|
||||||
|
activateDrawMode();
|
||||||
|
}, 1000); // Give viewer time to initialize
|
||||||
|
}
|
||||||
|
}, [base.selectedFiles.length, base.params.parameters.signatureType, activateDrawMode]);
|
||||||
|
|
||||||
// Sync signature configuration with context
|
// Sync signature configuration with context
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSignatureConfig(base.params.parameters);
|
setSignatureConfig(base.params.parameters);
|
||||||
@ -60,9 +70,10 @@ const Sign = (props: BaseToolProps) => {
|
|||||||
parameters={base.params.parameters}
|
parameters={base.params.parameters}
|
||||||
onParameterChange={base.params.updateParameter}
|
onParameterChange={base.params.updateParameter}
|
||||||
disabled={base.endpointLoading}
|
disabled={base.endpointLoading}
|
||||||
onActivateDrawMode={activateDrawMode}
|
onActivateDrawMode={() => activateDrawMode()}
|
||||||
onActivateSignaturePlacement={handleSignaturePlacement}
|
onActivateSignaturePlacement={handleSignaturePlacement}
|
||||||
onDeactivateSignature={deactivateDrawMode}
|
onDeactivateSignature={deactivateDrawMode}
|
||||||
|
onUpdateDrawSettings={updateDrawSettings}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -79,7 +90,7 @@ const Sign = (props: BaseToolProps) => {
|
|||||||
steps: getSteps(),
|
steps: getSteps(),
|
||||||
executeButton: {
|
executeButton: {
|
||||||
text: t('sign.submit', 'Sign Document'),
|
text: t('sign.submit', 'Sign Document'),
|
||||||
isVisible: base.operation.files.length === 0,
|
isVisible: false, // Hide the execute button - signatures are placed directly
|
||||||
loadingText: t('loading'),
|
loadingText: t('loading'),
|
||||||
onClick: base.handleExecute,
|
onClick: base.handleExecute,
|
||||||
disabled: !base.params.validateParameters() || base.selectedFiles.length === 0 || !base.endpointEnabled,
|
disabled: !base.params.validateParameters() || base.selectedFiles.length === 0 || !base.endpointEnabled,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user