mirror of
https://github.com/documenso/documenso.git
synced 2026-08-25 15:52:29 +10:00
fix: allow zooming and dragging uploaded signatures
This commit is contained in:
@@ -1,46 +1,18 @@
|
||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
||||
import { SIGNATURE_CANVAS_DPI, SIGNATURE_MIN_COVERAGE_THRESHOLD } from '@documenso/lib/constants/signatures';
|
||||
import { SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
|
||||
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { Undo2 } from 'lucide-react';
|
||||
import type { StrokeOptions } from 'perfect-freehand';
|
||||
import { getStroke } from 'perfect-freehand';
|
||||
import type { MouseEvent, PointerEvent, RefObject, TouchEvent } from 'react';
|
||||
import type { MouseEvent, PointerEvent, TouchEvent } from 'react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { getSvgPathFromStroke } from './helper';
|
||||
import { checkSignatureValidity, getSvgPathFromStroke } from './helper';
|
||||
import { Point } from './point';
|
||||
import { SignaturePadColorPicker } from './signature-pad-color-picker';
|
||||
|
||||
const checkSignatureValidity = (element: RefObject<HTMLCanvasElement>) => {
|
||||
if (!element.current) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ctx = element.current.getContext('2d');
|
||||
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, element.current.width, element.current.height);
|
||||
const data = imageData.data;
|
||||
let filledPixels = 0;
|
||||
const totalPixels = data.length / 4;
|
||||
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
if (data[i + 3] > 0) {
|
||||
filledPixels++;
|
||||
}
|
||||
}
|
||||
|
||||
const filledPercentage = filledPixels / totalPixels;
|
||||
const isValid = filledPercentage > SIGNATURE_MIN_COVERAGE_THRESHOLD;
|
||||
|
||||
return isValid;
|
||||
};
|
||||
|
||||
export type SignaturePadDrawProps = {
|
||||
className?: string;
|
||||
value: string;
|
||||
@@ -48,6 +20,8 @@ export type SignaturePadDrawProps = {
|
||||
};
|
||||
|
||||
export const SignaturePadDraw = ({ className, value, onChange, ...props }: SignaturePadDrawProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const $el = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const $imageData = useRef<ImageData | null>(null);
|
||||
@@ -276,9 +250,19 @@ export const SignaturePadDraw = ({ className, value, onChange, ...props }: Signa
|
||||
{...props}
|
||||
/>
|
||||
|
||||
<SignaturePadColorPicker selectedColor={selectedColor} setSelectedColor={setSelectedColor} />
|
||||
<SignaturePadColorPicker
|
||||
className={cn('transition-opacity duration-100', {
|
||||
'pointer-events-none opacity-0': isPressed,
|
||||
})}
|
||||
selectedColor={selectedColor}
|
||||
setSelectedColor={setSelectedColor}
|
||||
/>
|
||||
|
||||
<div className="absolute right-3 bottom-3 flex gap-2">
|
||||
<div
|
||||
className={cn('absolute right-3 bottom-3 flex gap-2 transition-opacity duration-100', {
|
||||
'pointer-events-none opacity-0': isPressed,
|
||||
})}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full p-0 text-[0.688rem] text-muted-foreground/60 ring-offset-background hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
@@ -289,7 +273,11 @@ export const SignaturePadDraw = ({ className, value, onChange, ...props }: Signa
|
||||
</div>
|
||||
|
||||
{isSignatureValid === false && (
|
||||
<div className="absolute bottom-4 left-4 flex gap-2">
|
||||
<div
|
||||
className={cn('absolute bottom-4 left-4 flex gap-2 transition-opacity duration-100', {
|
||||
'pointer-events-none opacity-0': isPressed,
|
||||
})}
|
||||
>
|
||||
<span className="text-destructive text-xs">
|
||||
<Trans>Signature is too small</Trans>
|
||||
</span>
|
||||
@@ -297,10 +285,14 @@ export const SignaturePadDraw = ({ className, value, onChange, ...props }: Signa
|
||||
)}
|
||||
|
||||
{isSignatureValid && lines.length > 0 && (
|
||||
<div className="absolute bottom-4 left-4 flex gap-2">
|
||||
<div
|
||||
className={cn('absolute bottom-4 left-4 flex gap-2 transition-opacity duration-100', {
|
||||
'pointer-events-none opacity-0': isPressed,
|
||||
})}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
title="undo"
|
||||
title={t`Undo`}
|
||||
className="rounded-full p-0 text-[0.688rem] text-muted-foreground/60 ring-offset-background hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onClick={onUndoClick}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user