From f5dcab6356b09ce45f99359d0a180961d0d912c1 Mon Sep 17 00:00:00 2001 From: Ian Chege Date: Tue, 23 Apr 2024 14:56:01 +0300 Subject: [PATCH] feat: add eraser feature --- components/board/board-elements/eraser.tsx | 44 ++++++++++++++++++++++ components/board/broadcast/main.tsx | 4 +- components/board/canvas.tsx | 12 ++++++ components/board/layer-preview.tsx | 10 +++++ components/board/toolbar.tsx | 18 ++++++++- lib/utils.ts | 10 ++--- types/canvas.ts | 18 +++++++++ 7 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 components/board/board-elements/eraser.tsx diff --git a/components/board/board-elements/eraser.tsx b/components/board/board-elements/eraser.tsx new file mode 100644 index 0000000..365fb3d --- /dev/null +++ b/components/board/board-elements/eraser.tsx @@ -0,0 +1,44 @@ +import getStroke from "perfect-freehand" + +import { getSvgPathFromStroke } from "@/lib/utils" + +interface PathProps { + x: number + y: number + points: number[][] + fill?: string + onPointerDown?: (e: React.PointerEvent) => void + stroke?: string +} + +export const Eraser = ({ + x, + y, + points, + fill, + onPointerDown, + stroke, +}: PathProps) => { + return ( + + ) +} diff --git a/components/board/broadcast/main.tsx b/components/board/broadcast/main.tsx index 4208847..24a9b94 100644 --- a/components/board/broadcast/main.tsx +++ b/components/board/broadcast/main.tsx @@ -14,7 +14,7 @@ export const Broadcast = () => { useEffect(() => { function onKeyUp(e: KeyboardEvent) { - if (e.key === "7") { + if (e.key === "8") { setState({ mode: CursorMode.Chat, previousMessage: null, @@ -29,7 +29,7 @@ export const Broadcast = () => { window.addEventListener("keyup", onKeyUp) function onKeyDown(e: KeyboardEvent) { - if (e.key === "7") { + if (e.key === "8") { e.preventDefault() } } diff --git a/components/board/canvas.tsx b/components/board/canvas.tsx index 9c9edc1..ca326ac 100644 --- a/components/board/canvas.tsx +++ b/components/board/canvas.tsx @@ -200,6 +200,11 @@ export const Canvas = ({ workspaceId }: ICanvasProps) => { } const id = nanoid() + const newLayer = penPointsToPathLayer(pencilDraft, lastUsedColor) + // If the current mode is Eraser, set the fill color to white (or your canvas background color) + if (canvasState.mode === CanvasMode.Erasing) { + newLayer.fill = "#fff" // white color for erasing + } liveLayers.set( id, new LiveObject(penPointsToPathLayer(pencilDraft, lastUsedColor)), @@ -439,6 +444,13 @@ export const Canvas = ({ workspaceId }: ICanvasProps) => { setCanvasState({ mode: CanvasMode.Pencil }) break } + case "7": { + setCanvasState({ + mode: CanvasMode.Erasing, + layerType: LayerType.Eraser, + }) + break + } case "z": { if (e.ctrlKey || e.metaKey) { if (e.shiftKey) { diff --git a/components/board/layer-preview.tsx b/components/board/layer-preview.tsx index c994f4e..9102982 100644 --- a/components/board/layer-preview.tsx +++ b/components/board/layer-preview.tsx @@ -6,6 +6,7 @@ import { colorToCss } from "@/lib/utils" import { useStorage } from "@/liveblocks.config" import { LayerType } from "@/types/canvas" import { Ellipse, Note, Path, Rectangle, Text } from "./board-elements" +import { Eraser } from "./board-elements/eraser" interface ILayerPreviewProps { id: string @@ -67,6 +68,15 @@ export const LayerPreview = memo( selectionColor={selectionColor} /> ) + case LayerType.Eraser: + return ( + onLayerPointerDown(e, id)} + /> + ) default: return null } diff --git a/components/board/toolbar.tsx b/components/board/toolbar.tsx index 3e24ff5..27c1bab 100644 --- a/components/board/toolbar.tsx +++ b/components/board/toolbar.tsx @@ -1,5 +1,6 @@ import { Circle, + Eraser, MessageSquareText, MousePointer2, Pencil, @@ -117,7 +118,22 @@ export const Toolbar = ({ } isActive={canvasState.mode === CanvasMode.Pencil} /> - + + setCanvasState({ + mode: CanvasMode.Erasing, + layerType: LayerType.Eraser, + }) + } + isActive={ + canvasState.mode === CanvasMode.Erasing && + canvasState.layerType === LayerType.Eraser + } + /> + , a: Point, - b: Point + b: Point, ) { const rect = { x: Math.min(a.x, b.x), @@ -126,7 +126,7 @@ export function getContrastingTextColor(color: Color) { export function penPointsToPathLayer( points: number[][], - color: Color + color: Color, ): PathLayer { if (points.length < 2) { throw new Error("Cannot transform points with less than 2 points") @@ -177,7 +177,7 @@ export function getSvgPathFromStroke(stroke: number[][]) { acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2) return acc }, - ["M", ...stroke[0], "Q"] + ["M", ...stroke[0], "Q"], ) d.push("Z") diff --git a/types/canvas.ts b/types/canvas.ts index 6808a33..f192bd9 100644 --- a/types/canvas.ts +++ b/types/canvas.ts @@ -15,6 +15,7 @@ export enum LayerType { Path, Text, Note, + Eraser, } export type RectangleLayer = { @@ -48,6 +49,17 @@ export type PathLayer = { value?: string } +export type EraserLayer = { + type: LayerType.Eraser + x: number + y: number + height: number + width: number + fill: string + points: number[][] + value?: string +} + export type TextLayer = { type: LayerType.Text x: number @@ -123,6 +135,10 @@ export type CanvasState = | { mode: CanvasMode.Pencil } + | { + mode: CanvasMode.Erasing + layerType: LayerType.Eraser + } | { mode: CanvasMode.Pressing origin: Point @@ -159,6 +175,7 @@ export enum CanvasMode { Inserting, Resizing, Pencil, + Erasing, } export enum CursorMode { @@ -172,5 +189,6 @@ export type Layer = | RectangleLayer | EllipseLayer | PathLayer + | EraserLayer | TextLayer | NoteLayer