From b51b1ccd6d1507d883ba4ce008a924501f1d9993 Mon Sep 17 00:00:00 2001 From: Anthony Guimard Date: Thu, 8 Feb 2024 01:16:12 +0100 Subject: [PATCH] fix(canvas): rework canvas hook --- app/components/ui/Toolbar/Toolbar.tsx | 24 +++++- app/hooks/useCanvasDrawing.tsx | 120 +++++++++++++++----------- app/routes/_index.tsx | 43 ++++++++- 3 files changed, 130 insertions(+), 57 deletions(-) diff --git a/app/components/ui/Toolbar/Toolbar.tsx b/app/components/ui/Toolbar/Toolbar.tsx index 8988594..bbbd58f 100755 --- a/app/components/ui/Toolbar/Toolbar.tsx +++ b/app/components/ui/Toolbar/Toolbar.tsx @@ -59,23 +59,41 @@ export const Toolbar = ({ className, children }: ToolbarProps) => { export type ToolbarItemProps = { children: React.ReactNode; className?: string; + selected?: boolean; onClick?: (e: React.MouseEvent) => void; + as?: 'button' | 'div'; }; export const ToolbarItem = ({ children, className, + selected = false, onClick, + as = 'div', }: ToolbarItemProps) => { const classNames = clsx( 'w-full flex items-center justify-center p-2 bg-gray-100 text-gray-700 hover:text-gray-900 hover:bg-gray-200 rounded-sm duration-150', - className + className, + { 'bg-gray-300': selected } ); + const handleOnClick = (e: React.MouseEvent) => { + if (onClick) { + e.preventDefault(); + onClick(e); + } + }; + + const Component = as; + return ( - + ); }; diff --git a/app/hooks/useCanvasDrawing.tsx b/app/hooks/useCanvasDrawing.tsx index 5483c08..7d21c01 100644 --- a/app/hooks/useCanvasDrawing.tsx +++ b/app/hooks/useCanvasDrawing.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useCallback } from 'react'; +import { useRef, useState, useCallback, useEffect } from 'react'; type Line = { x: number; y: number }[]; @@ -6,63 +6,65 @@ export const useCanvasDrawing = ( parentRef?: React.RefObject ) => { const canvasRef = useRef(null); - const isDrawingRef = useRef(false); + const isDrawingRef = useRef(false); + const [lineWidth, setLineWidth] = useState(6); + const [lineColor, setLineColor] = useState('#000000'); const linesRef = useRef([]); - const clearCanvas = () => { - linesRef.current = []; - redrawLines(); - }; - - const drawLine = useCallback((line: Line) => { + const updateDrawingProperties = useCallback( + (ctx: CanvasRenderingContext2D) => { + ctx.lineWidth = lineWidth; + ctx.strokeStyle = lineColor; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + }, + [lineWidth, lineColor] + ); + + const clearCanvas = useCallback(() => { const canvas = canvasRef.current; - if (!canvas) { - return; - } - const ctx = canvas.getContext('2d'); - - if (!ctx || line.length < 2) { + const ctx = canvas?.getContext('2d'); + if (!canvas || !ctx) { return; } - const [start, ...rest] = line; - ctx.beginPath(); - ctx.moveTo(start.x, start.y); - rest.forEach((point) => { - ctx.lineTo(point.x, point.y); - }); - ctx.stroke(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + linesRef.current = []; }, []); - const redrawLines = useCallback(() => { + const drawLine = useCallback( + (line: Line) => { + const canvas = canvasRef.current; + const ctx = canvas?.getContext('2d'); + if (!canvas || !ctx || line.length < 2) { + return; + } + + updateDrawingProperties(ctx); + + ctx.beginPath(); + line.forEach((point, index) => { + if (index === 0) { + ctx.moveTo(point.x, point.y); + } else { + ctx.lineTo(point.x, point.y); + } + }); + ctx.stroke(); + }, + [updateDrawingProperties] + ); + + useEffect(() => { const canvas = canvasRef.current; if (!canvas) { return; } const ctx = canvas.getContext('2d'); - if (!canvas || !ctx) { + if (!ctx) { return; } - ctx.clearRect(0, 0, canvas.width, canvas.height); - linesRef.current.forEach(drawLine); - }, [drawLine]); - - // Gestion du redimensionnement avec debounce pour optimiser les performances - const resizeCanvas = useCallback(() => { - const canvas = canvasRef.current; - if (!canvas || !parentRef?.current) return; - - canvas.width = parentRef.current.clientWidth; - canvas.height = parentRef.current.clientHeight; - redrawLines(); - }, [redrawLines, parentRef]); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - - // Ajout des écouteurs d'événements const startDrawing = (e: MouseEvent) => { isDrawingRef.current = true; const rect = canvas.getBoundingClientRect(); @@ -72,7 +74,9 @@ export const useCanvasDrawing = ( }; const draw = (e: MouseEvent) => { - if (!isDrawingRef.current) return; + if (!isDrawingRef.current) { + return; + } const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; @@ -83,7 +87,6 @@ export const useCanvasDrawing = ( const stopDrawing = () => { isDrawingRef.current = false; - redrawLines(); // Redessine pour s'assurer que tout est cohérent }; canvas.addEventListener('mousedown', startDrawing); @@ -91,18 +94,33 @@ export const useCanvasDrawing = ( canvas.addEventListener('mouseup', stopDrawing); canvas.addEventListener('mouseleave', stopDrawing); - window.addEventListener('resize', resizeCanvas); - resizeCanvas(); // Applique initialement la taille du canvas - - // Nettoyage des écouteurs d'événements return () => { canvas.removeEventListener('mousedown', startDrawing); canvas.removeEventListener('mousemove', draw); canvas.removeEventListener('mouseup', stopDrawing); canvas.removeEventListener('mouseleave', stopDrawing); - window.removeEventListener('resize', resizeCanvas); }; - }, [resizeCanvas, drawLine, redrawLines]); + }, [drawLine]); + + useEffect(() => { + const resizeCanvas = () => { + const canvas = canvasRef.current; + const parent = parentRef?.current; + if (canvas && parent) { + canvas.width = parent.clientWidth; + canvas.height = parent.clientHeight; + } + }; - return { canvasRef, clearCanvas }; + resizeCanvas(); + }, [parentRef]); + + return { + canvasRef, + clearCanvas, + lineColor, + lineWidth, + setLineWidth, + setLineColor, + } as const; }; diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index b8ae2d8..040d406 100755 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -1,4 +1,4 @@ -import { useRef } from 'react'; +import { ChangeEvent, useRef } from 'react'; import { ClientRect, DndContext, Modifier } from '@dnd-kit/core'; import { restrictToParentElement } from '@dnd-kit/modifiers'; @@ -8,11 +8,21 @@ import { WhiteBoard } from '~/components/ui/WhiteBoard'; import { snapBottomToCursor } from '~/utils/dndkit'; import IconTrash from '~/icons/icon-trash.svg?react'; +import IconCircle from '~/icons/icon-circle.svg?react'; import { useCanvasDrawing } from '~/hooks/useCanvasDrawing'; +const TOOLBAR_LINE_WIDTH = [6, 10]; + export default function Index() { const parentRef = useRef(null); - const { canvasRef, clearCanvas } = useCanvasDrawing(parentRef); + const { + canvasRef, + lineColor, + lineWidth, + clearCanvas, + setLineWidth, + setLineColor, + } = useCanvasDrawing(parentRef); const modifiers = [ snapBottomToCursor, (e: Parameters[0]) => @@ -22,12 +32,39 @@ export default function Index() { parentRef?.current?.getBoundingClientRect() as ClientRect | null, }), ]; + return (
- clearCanvas()}> + {TOOLBAR_LINE_WIDTH.map((width) => ( + setLineWidth(width)} + > + + + ))} + + + ) => { + setLineColor(e.target.value); + }} + /> + +
+