mirror of
https://github.com/documenso/documenso.git
synced 2025-11-17 18:21:32 +10:00
Add ability to enable or disable allowed signature types: - Drawn - Typed - Uploaded **Tabbed style signature dialog**  **Document settings**  **Team preferences**  ## Changes Made - Add multiselect to select allowed signatures in document and templates settings tab - Add multiselect to select allowed signatures in teams preferences - Removed "Enable typed signatures" from document/template edit page - Refactored signature pad to use tabs instead of an all in one signature pad ## Testing Performed Added E2E tests to check settings are applied correctly for documents and templates
129 lines
3.3 KiB
TypeScript
129 lines
3.3 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
|
|
import { SIGNATURE_CANVAS_DPI, isBase64Image } from '@documenso/lib/constants/signatures';
|
|
|
|
import { cn } from '../../lib/utils';
|
|
|
|
export type SignatureRenderProps = {
|
|
className?: string;
|
|
value: string;
|
|
};
|
|
|
|
/**
|
|
* Renders a typed, uploaded or drawn signature.
|
|
*/
|
|
export const SignatureRender = ({ className, value }: SignatureRenderProps) => {
|
|
const $el = useRef<HTMLCanvasElement>(null);
|
|
const $imageData = useRef<ImageData | null>(null);
|
|
|
|
const renderTypedSignature = () => {
|
|
if (!$el.current) {
|
|
return;
|
|
}
|
|
|
|
const ctx = $el.current.getContext('2d');
|
|
|
|
if (!ctx) {
|
|
return;
|
|
}
|
|
|
|
ctx.clearRect(0, 0, $el.current.width, $el.current.height);
|
|
|
|
const canvasWidth = $el.current.width;
|
|
const canvasHeight = $el.current.height;
|
|
const fontFamily = 'Caveat';
|
|
|
|
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
// ctx.fillStyle = selectedColor; // Todo: Color not implemented...
|
|
|
|
// Calculate the desired width (25ch)
|
|
const desiredWidth = canvasWidth * 0.85; // 85% of canvas width
|
|
|
|
// Start with a base font size
|
|
let fontSize = 18;
|
|
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
|
|
// Measure 10 characters and calculate scale factor
|
|
const characterWidth = ctx.measureText('m'.repeat(10)).width;
|
|
const scaleFactor = desiredWidth / characterWidth;
|
|
|
|
// Apply scale factor to font size
|
|
fontSize = fontSize * scaleFactor;
|
|
|
|
// Adjust font size if it exceeds canvas width
|
|
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
|
|
const textWidth = ctx.measureText(value).width;
|
|
|
|
if (textWidth > desiredWidth) {
|
|
fontSize = fontSize * (desiredWidth / textWidth);
|
|
}
|
|
|
|
// Set final font and render text
|
|
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
ctx.fillText(value, canvasWidth / 2, canvasHeight / 2);
|
|
};
|
|
|
|
const renderImageSignature = () => {
|
|
if (!$el.current || typeof value !== 'string') {
|
|
return;
|
|
}
|
|
|
|
const ctx = $el.current.getContext('2d');
|
|
|
|
if (!ctx) {
|
|
return;
|
|
}
|
|
|
|
ctx.clearRect(0, 0, $el.current.width, $el.current.height);
|
|
|
|
const { width, height } = $el.current;
|
|
|
|
const img = new Image();
|
|
|
|
img.onload = () => {
|
|
// Calculate the scaled dimensions while maintaining aspect ratio
|
|
const scale = Math.min(width / img.width, height / img.height);
|
|
const scaledWidth = img.width * scale;
|
|
const scaledHeight = img.height * scale;
|
|
|
|
// Calculate center position
|
|
const x = (width - scaledWidth) / 2;
|
|
const y = (height - scaledHeight) / 2;
|
|
|
|
ctx?.drawImage(img, x, y, scaledWidth, scaledHeight);
|
|
|
|
const defaultImageData = ctx?.getImageData(0, 0, width, height) || null;
|
|
|
|
$imageData.current = defaultImageData;
|
|
};
|
|
|
|
img.src = value;
|
|
};
|
|
|
|
useEffect(() => {
|
|
if ($el.current) {
|
|
$el.current.width = $el.current.clientWidth * SIGNATURE_CANVAS_DPI;
|
|
$el.current.height = $el.current.clientHeight * SIGNATURE_CANVAS_DPI;
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (isBase64Image(value)) {
|
|
renderImageSignature();
|
|
} else {
|
|
renderTypedSignature();
|
|
}
|
|
}, [value]);
|
|
|
|
return (
|
|
<canvas
|
|
ref={$el}
|
|
className={cn('h-full w-full dark:hue-rotate-180 dark:invert', className)}
|
|
style={{ touchAction: 'none' }}
|
|
/>
|
|
);
|
|
};
|