mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
Add ability to enable or disable allowed signature types: - Drawn - Typed - Uploaded **Tabbed style signature dialog**  **Document settings**  **Team preferences**  - 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 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' }}
|
|
/>
|
|
);
|
|
};
|