diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index 107627240..682b9631a 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -11,6 +11,7 @@ import { useState, } from 'react'; +import { Undo2 } from 'lucide-react'; import { StrokeOptions, getStroke } from 'perfect-freehand'; import { cn } from '@documenso/ui/lib/utils'; @@ -35,7 +36,8 @@ export const SignaturePad = ({ const $el = useRef(null); const [isPressed, setIsPressed] = useState(false); - const [points, setPoints] = useState([]); + const [lines, setLines] = useState([]); + const [currentLine, setCurrentLine] = useState([]); const perfectFreehandOptions = useMemo(() => { const size = $el.current ? Math.min($el.current.height, $el.current.width) * 0.03 : 10; @@ -60,26 +62,7 @@ export const SignaturePad = ({ const point = Point.fromEvent(event, DPI, $el.current); - const newPoints = [...points, point]; - - setPoints(newPoints); - - if ($el.current) { - const ctx = $el.current.getContext('2d'); - - if (ctx) { - ctx.save(); - - ctx.imageSmoothingEnabled = true; - ctx.imageSmoothingQuality = 'high'; - - const pathData = new Path2D( - getSvgPathFromStroke(getStroke(newPoints, perfectFreehandOptions)), - ); - - ctx.fill(pathData); - } - } + setCurrentLine([point]); }; const onMouseMove = (event: MouseEvent | PointerEvent | TouchEvent) => { @@ -93,31 +76,36 @@ export const SignaturePad = ({ const point = Point.fromEvent(event, DPI, $el.current); - if (point.distanceTo(points[points.length - 1]) > 5) { - const newPoints = [...points, point]; - - setPoints(newPoints); + if (point.distanceTo(currentLine[currentLine.length - 1]) > 5) { + setCurrentLine([...currentLine, point]); + // Update the canvas here to draw the lines if ($el.current) { const ctx = $el.current.getContext('2d'); if (ctx) { ctx.restore(); - ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; - const pathData = new Path2D( - getSvgPathFromStroke(getStroke(points, perfectFreehandOptions)), - ); + lines.forEach((line) => { + const pathData = new Path2D( + getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)), + ); + ctx.fill(pathData); + }); + + const pathData = new Path2D( + getSvgPathFromStroke(getStroke([...currentLine, point], perfectFreehandOptions)), + ); ctx.fill(pathData); } } } }; - const onMouseUp = (event: MouseEvent | PointerEvent | TouchEvent, addPoint = true) => { + const onMouseUp = (event: MouseEvent | PointerEvent | TouchEvent, addLine = true) => { if (event.cancelable) { event.preventDefault(); } @@ -126,15 +114,16 @@ export const SignaturePad = ({ const point = Point.fromEvent(event, DPI, $el.current); - const newPoints = [...points]; + const newLines = [...lines]; - if (addPoint) { - newPoints.push(point); - - setPoints(newPoints); + if (addLine && currentLine.length > 0) { + newLines.push([...currentLine, point]); + setCurrentLine([]); } - if ($el.current && newPoints.length > 0) { + setLines(newLines); + + if ($el.current && newLines.length > 0) { const ctx = $el.current.getContext('2d'); if (ctx) { @@ -143,19 +132,18 @@ export const SignaturePad = ({ ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; - const pathData = new Path2D( - getSvgPathFromStroke(getStroke(newPoints, perfectFreehandOptions)), - ); + newLines.forEach((line) => { + const pathData = new Path2D( + getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)), + ); + ctx.fill(pathData); + }); - ctx.fill(pathData); + onChange?.($el.current.toDataURL()); ctx.save(); } - - onChange?.($el.current.toDataURL()); } - - setPoints([]); }; const onMouseEnter = (event: MouseEvent | PointerEvent | TouchEvent) => { @@ -185,7 +173,29 @@ export const SignaturePad = ({ onChange?.(null); - setPoints([]); + setLines([]); + setCurrentLine([]); + }; + + const onUndoClick = () => { + if (lines.length === 0) { + return; + } + + const newLines = [...lines]; + newLines.pop(); // Remove the last line + setLines(newLines); + + // Clear the canvas + if ($el.current) { + const ctx = $el.current.getContext('2d'); + ctx?.clearRect(0, 0, $el.current.width, $el.current.height); + + newLines.forEach((line) => { + const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions))); + ctx?.fill(pathData); + }); + } }; useEffect(() => { @@ -225,15 +235,29 @@ export const SignaturePad = ({ {...props} /> -
+
+ + {lines.length > 0 && ( +
+ +
+ )}
); };