From d8aecc4092f236814e3c831d3bbad0c57f5b643d Mon Sep 17 00:00:00 2001 From: apoorv taneja Date: Thu, 25 Jan 2024 13:21:55 +0530 Subject: [PATCH 1/5] fixed undo operation on signature pad --- packages/ui/primitives/signature-pad/signature-pad.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index 80bac0e18..e6f844b52 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -26,7 +26,7 @@ export const SignaturePad = ({ ...props }: SignaturePadProps) => { const $el = useRef(null); - + const defaultImageRef = useRef(null); const [isPressed, setIsPressed] = useState(false); const [lines, setLines] = useState([]); const [currentLine, setCurrentLine] = useState([]); @@ -161,6 +161,7 @@ export const SignaturePad = ({ const ctx = $el.current.getContext('2d'); ctx?.clearRect(0, 0, $el.current.width, $el.current.height); + defaultImageRef.current = null; } onChange?.(null); @@ -181,8 +182,11 @@ export const SignaturePad = ({ // Clear the canvas if ($el.current) { const ctx = $el.current.getContext('2d'); + const { width, height } = $el.current; ctx?.clearRect(0, 0, $el.current.width, $el.current.height); - + if (typeof defaultValue === 'string' && defaultImageRef.current) { + ctx?.putImageData(defaultImageRef.current, 0, 0); + } newLines.forEach((line) => { const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions))); ctx?.fill(pathData); @@ -207,6 +211,8 @@ export const SignaturePad = ({ img.onload = () => { ctx?.drawImage(img, 0, 0, Math.min(width, img.width), Math.min(height, img.height)); + const defaultImageData = ctx?.getImageData(0, 0, width, height) || null; + defaultImageRef.current = defaultImageData; }; img.src = defaultValue; From 142c1c003ebbdb7bf39d18f59f78a6a47e278f2e Mon Sep 17 00:00:00 2001 From: apoorv taneja Date: Fri, 2 Feb 2024 18:16:54 +0530 Subject: [PATCH 2/5] changed useEffect variables --- packages/ui/primitives/signature-pad/signature-pad.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index cb546538c..b52626026 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -219,7 +219,7 @@ export const SignaturePad = ({ img.src = defaultValue; } - }, [defaultValue]); + }, []); return (
Date: Fri, 2 Feb 2024 19:32:39 +0530 Subject: [PATCH 3/5] fixed variable declaration --- packages/ui/primitives/signature-pad/signature-pad.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index b52626026..c38c69fb2 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -185,7 +185,7 @@ export const SignaturePad = ({ if ($el.current) { const ctx = $el.current.getContext('2d'); const { width, height } = $el.current; - ctx?.clearRect(0, 0, $el.current.width, $el.current.height); + ctx?.clearRect(0, 0, width, height); if (typeof defaultValue === 'string' && defaultImageRef.current) { ctx?.putImageData(defaultImageRef.current, 0, 0); } From c970abc871d1b3f961717d42a6d4eed1bc3602df Mon Sep 17 00:00:00 2001 From: apoorv taneja Date: Fri, 2 Feb 2024 20:46:54 +0530 Subject: [PATCH 4/5] added onchange handler --- packages/ui/primitives/signature-pad/signature-pad.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index c38c69fb2..ad6f92e91 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -134,7 +134,6 @@ export const SignaturePad = ({ }); onChange?.($el.current.toDataURL()); - ctx.save(); } } @@ -193,6 +192,7 @@ export const SignaturePad = ({ const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions))); ctx?.fill(pathData); }); + onChange?.($el.current.toDataURL()); } }; From d83769b4104c9fbe4e74625486328712e16c4ca2 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Fri, 16 Feb 2024 11:56:02 +0000 Subject: [PATCH 5/5] chore: use unsafe effect --- .../lib/client-only/hooks/use-effect-once.ts | 13 ++++++++++ .../signature-pad/signature-pad.tsx | 25 ++++++++++++------- 2 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 packages/lib/client-only/hooks/use-effect-once.ts diff --git a/packages/lib/client-only/hooks/use-effect-once.ts b/packages/lib/client-only/hooks/use-effect-once.ts new file mode 100644 index 000000000..dc6d062dd --- /dev/null +++ b/packages/lib/client-only/hooks/use-effect-once.ts @@ -0,0 +1,13 @@ +import type { EffectCallback } from 'react'; +import { useEffect } from 'react'; + +/** + * Dangerously runs an effect "once" by ignoring the depedencies of a given effect. + * + * DANGER: The effect will run twice in concurrent react and development environments. + */ +export const unsafe_useEffectOnce = (callback: EffectCallback) => { + // Intentionally avoiding exhaustive deps and rule of hooks here + // eslint-disable-next-line react-hooks/exhaustive-deps, react-hooks/rules-of-hooks + return useEffect(callback, []); +}; diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index ad6f92e91..8524450fc 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -7,6 +7,8 @@ import { Undo2 } from 'lucide-react'; import type { StrokeOptions } from 'perfect-freehand'; import { getStroke } from 'perfect-freehand'; +import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once'; + import { cn } from '../../lib/utils'; import { getSvgPathFromStroke } from './helper'; import { Point } from './point'; @@ -28,7 +30,8 @@ export const SignaturePad = ({ ...props }: SignaturePadProps) => { const $el = useRef(null); - const defaultImageRef = useRef(null); + const $imageData = useRef(null); + const [isPressed, setIsPressed] = useState(false); const [lines, setLines] = useState([]); const [currentLine, setCurrentLine] = useState([]); @@ -162,7 +165,7 @@ export const SignaturePad = ({ const ctx = $el.current.getContext('2d'); ctx?.clearRect(0, 0, $el.current.width, $el.current.height); - defaultImageRef.current = null; + $imageData.current = null; } onChange?.(null); @@ -176,8 +179,7 @@ export const SignaturePad = ({ return; } - const newLines = [...lines]; - newLines.pop(); // Remove the last line + const newLines = lines.slice(0, -1); setLines(newLines); // Clear the canvas @@ -185,13 +187,16 @@ export const SignaturePad = ({ const ctx = $el.current.getContext('2d'); const { width, height } = $el.current; ctx?.clearRect(0, 0, width, height); - if (typeof defaultValue === 'string' && defaultImageRef.current) { - ctx?.putImageData(defaultImageRef.current, 0, 0); + + if (typeof defaultValue === 'string' && $imageData.current) { + ctx?.putImageData($imageData.current, 0, 0); } + newLines.forEach((line) => { const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions))); ctx?.fill(pathData); }); + onChange?.($el.current.toDataURL()); } }; @@ -203,7 +208,7 @@ export const SignaturePad = ({ } }, []); - useEffect(() => { + unsafe_useEffectOnce(() => { if ($el.current && typeof defaultValue === 'string') { const ctx = $el.current.getContext('2d'); @@ -213,13 +218,15 @@ export const SignaturePad = ({ img.onload = () => { ctx?.drawImage(img, 0, 0, Math.min(width, img.width), Math.min(height, img.height)); + const defaultImageData = ctx?.getImageData(0, 0, width, height) || null; - defaultImageRef.current = defaultImageData; + + $imageData.current = defaultImageData; }; img.src = defaultValue; } - }, []); + }); return (