From cd8693b9669576adb47df3d0ca32ba5ef387ffb9 Mon Sep 17 00:00:00 2001 From: Vinoth Pandian Date: Sat, 17 Aug 2024 16:11:21 -0400 Subject: [PATCH] Add custom paths feature Fixes #143 Add support for custom path generator in `ReactSketchCanvas`. * Add `getSvgPathFromPoints` prop to `ReactSketchCanvas` component to accept a callback function for custom path generation. * Pass `getSvgPathFromPoints` prop to `Canvas` component. * Add `getSvgPathFromPoints` prop to `Canvas` component to accept a callback function for custom path generation. * Pass `getSvgPathFromPoints` prop to `SvgPath` component. * Modify `SvgPath` component to use the custom path generator if provided. * Update documentation in `README.md` to include the new `getSvgPathFromPoints` prop and its usage. * Add tests in `export.spec.tsx` to verify the functionality of the custom path generator. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/vinothpandian/react-sketch-canvas/issues/143?shareId=XXXX-XXXX-XXXX-XXXX). --- packages/react-sketch-canvas/README.md | 1 + .../react-sketch-canvas/src/Canvas/index.tsx | 9 ++++-- .../react-sketch-canvas/src/Paths/index.tsx | 27 ++++++++--------- .../src/ReactSketchCanvas/index.tsx | 2 ++ packages/tests/src/actions/export.spec.tsx | 29 +++++++++++++++++++ 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/packages/react-sketch-canvas/README.md b/packages/react-sketch-canvas/README.md index b680fb7..769f4cc 100644 --- a/packages/react-sketch-canvas/README.md +++ b/packages/react-sketch-canvas/README.md @@ -128,6 +128,7 @@ const Canvas = class extends React.Component { | svgStyle | PropTypes.object | {} | Add CSS styling as CSS-in-JS object for the SVG | | withTimestamp | PropTypes.bool | false | Add timestamp to individual strokes for measuring sketching time | | readOnly | PropTypes.bool | false | Disable drawing on the canvas (undo/redo, clear & reset will still work.) | +| getSvgPathFromPoints | PropTypes.func | undefined | Custom path generator callback that receives the current path as an array and returns an SVG path | Set SVG background using CSS [background][css-bg] value diff --git a/packages/react-sketch-canvas/src/Canvas/index.tsx b/packages/react-sketch-canvas/src/Canvas/index.tsx index 6dbaf4b..a3cbdca 100644 --- a/packages/react-sketch-canvas/src/Canvas/index.tsx +++ b/packages/react-sketch-canvas/src/Canvas/index.tsx @@ -70,6 +70,7 @@ export const Canvas = React.forwardRef((props, ref) => { svgStyle = {}, withViewBox = false, readOnly = false, + getSvgPathFromPoints = undefined, } = props; const canvasRef = React.useRef(null); @@ -263,7 +264,7 @@ export const Canvas = React.forwardRef((props, ref) => { })); /* Add event listener to Mouse up and Touch up to -release drawing even when point goes out of canvas */ + release drawing even when point goes out of canvas */ React.useEffect(() => { document.addEventListener("pointerup", handlePointerUp); return () => { @@ -408,7 +409,11 @@ release drawing even when point goes out of canvas */ key={`${id}__stroke-group-${i}`} mask={`${eraserPaths[i] && `url(#${id}__eraser-mask-${i})`}`} > - + ))} diff --git a/packages/react-sketch-canvas/src/Paths/index.tsx b/packages/react-sketch-canvas/src/Paths/index.tsx index 558be4a..22748f3 100644 --- a/packages/react-sketch-canvas/src/Paths/index.tsx +++ b/packages/react-sketch-canvas/src/Paths/index.tsx @@ -11,6 +11,7 @@ type ControlPoints = { type PathProps = { id: string; paths: CanvasPath[]; + getSvgPathFromPoints?: (points: Point[]) => string; }; export const line = (pointA: Point, pointB: Point) => { @@ -78,27 +79,21 @@ export const bezierCommand = (point: Point, i: number, a: Point[]): string => { }; export type SvgPathProps = { - // List of points to create the stroke paths: Point[]; - // Unique ID id: string; - // Width of the stroke strokeWidth: number; - // Color of the stroke strokeColor: string; - // Bezier command to smoothen the line command?: (point: Point, i: number, a: Point[]) => string; + getSvgPathFromPoints?: (points: Point[]) => string; }; -/** - * Generate SVG Path tag from the given points - */ export function SvgPath({ paths, id, strokeWidth, strokeColor, command = bezierCommand, + getSvgPathFromPoints, }: SvgPathProps): JSX.Element { if (paths.length === 1) { const { x, y } = paths[0]; @@ -118,11 +113,13 @@ export function SvgPath({ ); } - const d = paths.reduce( - (acc, point, i, a) => - i === 0 ? `M ${point.x},${point.y}` : `${acc} ${command(point, i, a)}`, - "", - ); + const d = getSvgPathFromPoints + ? getSvgPathFromPoints(paths) + : paths.reduce( + (acc, point, i, a) => + i === 0 ? `M ${point.x},${point.y}` : `${acc} ${command(point, i, a)}`, + "", + ); return ( {paths.map((path: CanvasPath, index: number) => ( ))} diff --git a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx index 6095e34..348aa3f 100644 --- a/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx +++ b/packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx @@ -45,6 +45,7 @@ export const ReactSketchCanvas = React.forwardRef< withTimestamp = false, withViewBox = false, readOnly = false, + getSvgPathFromPoints = undefined, } = props; const svgCanvas = React.createRef(); @@ -251,6 +252,7 @@ export const ReactSketchCanvas = React.forwardRef< onPointerUp={handlePointerUp} withViewBox={withViewBox} readOnly={readOnly} + getSvgPathFromPoints={getSvgPathFromPoints} /> ); }); diff --git a/packages/tests/src/actions/export.spec.tsx b/packages/tests/src/actions/export.spec.tsx index 71b88ce..fa3b8d5 100644 --- a/packages/tests/src/actions/export.spec.tsx +++ b/packages/tests/src/actions/export.spec.tsx @@ -431,3 +431,32 @@ test.describe("export SVG", () => { }); }); }); + +test.describe("custom path generator", () => { + test("should use custom path generator if provided", async ({ mount }) => { + let svg: string | undefined; + const handleExportSVG = (exportedSvg: string | undefined) => { + svg = exportedSvg; + }; + + const customPathGenerator = (points: { x: number; y: number }[]) => + `M${points.map((p) => `${p.x},${p.y}`).join(" ")}`; + + const { canvas, exportSVGButton } = await mountCanvasForExport({ + mount, + handleExportSVG, + getSvgPathFromPoints: customPathGenerator, + }); + + const { firstStrokePathId } = getCanvasIds(canvasId); + + await exportSVGButton.click(); + expect(svg).not.toContain(firstStrokePathId.slice(1)); + + await drawSquares(canvas); + + await exportSVGButton.click(); + expect(svg).toContain(firstStrokePathId.slice(1)); + expect(svg).toContain("M"); + }); +});