mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
13
packages/lib/client-only/hooks/use-effect-once.ts
Normal file
13
packages/lib/client-only/hooks/use-effect-once.ts
Normal file
@ -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, []);
|
||||||
|
};
|
||||||
@ -7,6 +7,8 @@ import { Undo2 } from 'lucide-react';
|
|||||||
import type { StrokeOptions } from 'perfect-freehand';
|
import type { StrokeOptions } from 'perfect-freehand';
|
||||||
import { getStroke } 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 { cn } from '../../lib/utils';
|
||||||
import { getSvgPathFromStroke } from './helper';
|
import { getSvgPathFromStroke } from './helper';
|
||||||
import { Point } from './point';
|
import { Point } from './point';
|
||||||
@ -28,6 +30,7 @@ export const SignaturePad = ({
|
|||||||
...props
|
...props
|
||||||
}: SignaturePadProps) => {
|
}: SignaturePadProps) => {
|
||||||
const $el = useRef<HTMLCanvasElement>(null);
|
const $el = useRef<HTMLCanvasElement>(null);
|
||||||
|
const $imageData = useRef<ImageData | null>(null);
|
||||||
|
|
||||||
const [isPressed, setIsPressed] = useState(false);
|
const [isPressed, setIsPressed] = useState(false);
|
||||||
const [lines, setLines] = useState<Point[][]>([]);
|
const [lines, setLines] = useState<Point[][]>([]);
|
||||||
@ -134,7 +137,6 @@ export const SignaturePad = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
onChange?.($el.current.toDataURL());
|
onChange?.($el.current.toDataURL());
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,6 +165,7 @@ export const SignaturePad = ({
|
|||||||
const ctx = $el.current.getContext('2d');
|
const ctx = $el.current.getContext('2d');
|
||||||
|
|
||||||
ctx?.clearRect(0, 0, $el.current.width, $el.current.height);
|
ctx?.clearRect(0, 0, $el.current.width, $el.current.height);
|
||||||
|
$imageData.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange?.(null);
|
onChange?.(null);
|
||||||
@ -176,19 +179,25 @@ export const SignaturePad = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newLines = [...lines];
|
const newLines = lines.slice(0, -1);
|
||||||
newLines.pop(); // Remove the last line
|
|
||||||
setLines(newLines);
|
setLines(newLines);
|
||||||
|
|
||||||
// Clear the canvas
|
// Clear the canvas
|
||||||
if ($el.current) {
|
if ($el.current) {
|
||||||
const ctx = $el.current.getContext('2d');
|
const ctx = $el.current.getContext('2d');
|
||||||
ctx?.clearRect(0, 0, $el.current.width, $el.current.height);
|
const { width, height } = $el.current;
|
||||||
|
ctx?.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
if (typeof defaultValue === 'string' && $imageData.current) {
|
||||||
|
ctx?.putImageData($imageData.current, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
newLines.forEach((line) => {
|
newLines.forEach((line) => {
|
||||||
const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)));
|
const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)));
|
||||||
ctx?.fill(pathData);
|
ctx?.fill(pathData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onChange?.($el.current.toDataURL());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -199,7 +208,7 @@ export const SignaturePad = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
unsafe_useEffectOnce(() => {
|
||||||
if ($el.current && typeof defaultValue === 'string') {
|
if ($el.current && typeof defaultValue === 'string') {
|
||||||
const ctx = $el.current.getContext('2d');
|
const ctx = $el.current.getContext('2d');
|
||||||
|
|
||||||
@ -209,11 +218,15 @@ export const SignaturePad = ({
|
|||||||
|
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
ctx?.drawImage(img, 0, 0, Math.min(width, img.width), Math.min(height, img.height));
|
ctx?.drawImage(img, 0, 0, Math.min(width, img.width), Math.min(height, img.height));
|
||||||
|
|
||||||
|
const defaultImageData = ctx?.getImageData(0, 0, width, height) || null;
|
||||||
|
|
||||||
|
$imageData.current = defaultImageData;
|
||||||
};
|
};
|
||||||
|
|
||||||
img.src = defaultValue;
|
img.src = defaultValue;
|
||||||
}
|
}
|
||||||
}, [defaultValue]);
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user