Skip to content

Commit

Permalink
fix(canvas): rework canvas hook
Browse files Browse the repository at this point in the history
  • Loading branch information
Tonours committed Feb 8, 2024
1 parent c7d034d commit b51b1cc
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 57 deletions.
24 changes: 21 additions & 3 deletions app/components/ui/Toolbar/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,41 @@ export const Toolbar = ({ className, children }: ToolbarProps) => {
export type ToolbarItemProps = {
children: React.ReactNode;
className?: string;
selected?: boolean;
onClick?: (e: React.MouseEvent<HTMLElement>) => 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<HTMLElement>) => {
if (onClick) {
e.preventDefault();
onClick(e);
}
};

const Component = as;

return (
<button onClick={onClick} className={classNames} data-testid="toolbar-item">
<Component
onClick={handleOnClick}
className={classNames}
data-testid="toolbar-item"
>
{children}
</button>
</Component>
);
};

Expand Down
120 changes: 69 additions & 51 deletions app/hooks/useCanvasDrawing.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,70 @@
import { useEffect, useRef, useCallback } from 'react';
import { useRef, useState, useCallback, useEffect } from 'react';

type Line = { x: number; y: number }[];

export const useCanvasDrawing = (
parentRef?: React.RefObject<HTMLDivElement>
) => {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const isDrawingRef = useRef(false);
const isDrawingRef = useRef<boolean>(false);
const [lineWidth, setLineWidth] = useState<number>(6);
const [lineColor, setLineColor] = useState<string>('#000000');
const linesRef = useRef<Line[]>([]);

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();
Expand All @@ -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;
Expand All @@ -83,26 +87,40 @@ export const useCanvasDrawing = (

const stopDrawing = () => {
isDrawingRef.current = false;
redrawLines(); // Redessine pour s'assurer que tout est cohérent
};

canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
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;
};
43 changes: 40 additions & 3 deletions app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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<HTMLDivElement | null>(null);
const { canvasRef, clearCanvas } = useCanvasDrawing(parentRef);
const {
canvasRef,
lineColor,
lineWidth,
clearCanvas,
setLineWidth,
setLineColor,
} = useCanvasDrawing(parentRef);
const modifiers = [
snapBottomToCursor,
(e: Parameters<Modifier>[0]) =>
Expand All @@ -22,12 +32,39 @@ export default function Index() {
parentRef?.current?.getBoundingClientRect() as ClientRect | null,
}),
];

return (
<MainLayout>
<div ref={parentRef} className="bg-gray-50 w-full h-full relative">
<DndContext modifiers={modifiers}>
<Toolbar>
<Toolbar.Item className="col-span-2" onClick={() => clearCanvas()}>
{TOOLBAR_LINE_WIDTH.map((width) => (
<Toolbar.Item
key={width}
as="button"
selected={lineWidth === width}
onClick={() => setLineWidth(width)}
>
<IconCircle className={`w-[${width}px] h-[${width}px]`} />
</Toolbar.Item>
))}

<Toolbar.Item className="col-span-2">
<input
type="color"
className="w-full h-8 cursor-pointer"
value={lineColor}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setLineColor(e.target.value);
}}
/>
</Toolbar.Item>
<hr className="col-span-2 my-1 border-gray-200" />
<Toolbar.Item
as="button"
className="col-span-2 mt-auto"
onClick={clearCanvas}
>
<IconTrash className="w-3 h-[auto]" />
</Toolbar.Item>
</Toolbar>
Expand Down

0 comments on commit b51b1cc

Please sign in to comment.