Skip to content

Commit 65e96de

Browse files
authored
Merge pull request #33 from apsinghdev/Feat/implement-eraser-functionality
Feat/implement eraser functionality
2 parents 49073be + 6a37932 commit 65e96de

File tree

9 files changed

+167
-52
lines changed

9 files changed

+167
-52
lines changed

client/package-lock.json

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"react": "^18.2.0",
1414
"react-dom": "^18.2.0",
1515
"react-icons": "^5.2.0",
16+
"recoil": "^0.7.7",
1617
"socket.io-client": "^4.7.4"
1718
},
1819
"devDependencies": {

client/src/App.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@
77
height: 100%;
88
display: flex;
99
width: 100%;
10+
position: relative;
1011
}
1112

13+
.eraser-cursor{
14+
pointer-events: none;
15+
z-index: 1000;
16+
transform: translate(-50%, -50%);
17+
}
1218

1319

1420

client/src/App.jsx

Lines changed: 68 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable react-hooks/exhaustive-deps */
12
import { useEffect, useRef, useState } from "react";
23
import "./App.css";
34
import { io } from "socket.io-client";
@@ -7,60 +8,76 @@ const socket = io("http://localhost:8000");
78
import Sidebar from "./components/Sidebar";
89
import Canvas from "./components/Canvas";
910
import Menu from "./components/Menu";
11+
import EraserCursor from "./components/EraserCursor";
12+
import { useRecoilValue, useRecoilState } from "recoil";
13+
import { eraserState, cursorPosition } from "./atoms";
1014

1115
function App() {
12-
13-
const [showMenu, setShowMenu ] = useState(false);
14-
15-
function toggleMenu(){
16+
const [showMenu, setShowMenu] = useState(false);
17+
const eraserMode = useRecoilValue(eraserState);
18+
const [position, setPosition] = useRecoilState(cursorPosition);
19+
const [ctx, setCtx] = useState(null);
20+
const [startX, setStartX] = useState(0);
21+
const [startY, setStartY] = useState(0);
22+
const [isDrawing, setIsDrawing] = useState(false);
23+
const [penColor, setPenColor] = useState("#000000");
24+
25+
function toggleMenu() {
1626
setShowMenu(!showMenu);
1727
}
1828

1929
const canvasRef = useRef(null);
2030
const sidebarRef = useRef(null);
21-
let color = '#000000'
22-
let ctx;
23-
let canvas;
2431
let lineWidth;
2532

26-
function drawLine(sx, sy, ex, ey, color, lineWidth) {
33+
function drawLine(sx, sy, ex, ey, penColor, lineWidth) {
2734
ctx.moveTo(sx, sy);
2835
ctx.lineTo(ex, ey);
2936
ctx.lineCap = "round";
3037
ctx.lineWidth = lineWidth;
31-
ctx.strokeStyle = color;
38+
ctx.strokeStyle = penColor;
3239
ctx.stroke();
3340
}
3441

35-
useEffect(() => {
36-
canvas = canvasRef.current;
37-
let isDrawing = false;
38-
let startX = 0;
39-
let startY = 0;
40-
ctx = canvas.getContext("2d");
41-
42-
canvas.width = canvas.getBoundingClientRect().width;
43-
canvas.height = canvas.getBoundingClientRect().height;
42+
useEffect(() => {
43+
const canvas = canvasRef.current;
44+
if (canvas) {
45+
const context = canvas.getContext("2d");
46+
canvas.width = canvas.getBoundingClientRect().width;
47+
canvas.height = canvas.getBoundingClientRect().height;
48+
setCtx(context);
49+
}
50+
}, [canvasRef.current]);
4451

52+
useEffect(() => {
53+
if (!ctx) return;
54+
const canvas = canvasRef.current;
4555
function handleMousemove(e) {
56+
// if eraseMode is set the position of the eraser cursor
57+
if (eraserMode) {
58+
setPosition(() => ({ x: e.clientX, y: e.clientY }));
59+
}
60+
4661
if (!isDrawing) return;
4762
const endX = e.clientX - canvas.getBoundingClientRect().left;
4863
const endY = e.clientY - canvas.getBoundingClientRect().top;
49-
drawLine(startX, startY, endX, endY, color );
50-
socket.emit("draw", { startX, startY, endX, endY, color, lineWidth });
51-
startX = endX;
52-
startY = endY;
64+
drawLine(startX, startY, endX, endY, penColor);
65+
socket.emit("draw", { startX, startY, endX, endY, penColor, lineWidth });
66+
setStartX(endX);
67+
setStartY(endY);
5368
}
5469

5570
function handleMousedown(e) {
56-
isDrawing = true;
57-
startX = e.clientX - canvas.getBoundingClientRect().left;
58-
startY = e.clientY - canvas.getBoundingClientRect().top;
71+
setIsDrawing(true);
72+
let X = e.clientX - canvas.getBoundingClientRect().left;
73+
let Y = e.clientY - canvas.getBoundingClientRect().top;
74+
setStartX(X);
75+
setStartY(Y);
5976
}
6077
function handleMouseup() {
61-
isDrawing = false;
62-
startX = 0;
63-
startY = 0;
78+
setIsDrawing(false);
79+
setStartX(0);
80+
setStartY(0);
6481
ctx.beginPath();
6582
}
6683

@@ -73,7 +90,7 @@ function App() {
7390
canvas.removeEventListener("mousedown", handleMousedown);
7491
canvas.removeEventListener("mouseup", handleMouseup);
7592
};
76-
}, [color]);
93+
}, [penColor, eraserMode, position, ctx, isDrawing, startX, startY]);
7794

7895
useEffect(() => {
7996
socket.on("draw", (data) => {
@@ -82,62 +99,66 @@ function App() {
8299
data.startY,
83100
data.endX,
84101
data.endY,
85-
data.color,
102+
data.penColor,
86103
data.lineWidth
87104
);
88105
});
89-
106+
90107
socket.on("clear", () => {
91108
clearRect();
92109
});
93-
94-
return () => {
95-
socket.off("draw");
96-
socket.off("clear");
97-
}
98-
}, []);
110+
111+
return () => {
112+
socket.off("draw");
113+
socket.off("clear");
114+
};
115+
}, [socket, ctx]);
99116

100117
function clearRect() {
101-
ctx.clearRect(0, 0, canvas.width, canvas.height);
118+
if (ctx) {
119+
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
120+
}
102121
}
103122

104-
function clearOnClick(){
123+
function clearOnClick() {
105124
clearRect();
106-
socket.emit('clear')
125+
socket.emit("clear");
107126
}
108127

109128
function addStroke(e) {
110129
if (e.target.id === "penColor") {
111130
const newColor = e.target.value;
112-
color = newColor;
113-
ctx.strokeStyle = newColor;
114-
131+
setPenColor(newColor);
132+
if (ctx) {
133+
ctx.strokeStyle = newColor;
134+
}
115135
}
116136
}
117137

118138
function addLineWidth(e) {
119139
if (e.target.id === "lineWidth") {
120140
lineWidth = e.target.value;
121-
ctx.lineWidth = lineWidth;
141+
if (ctx) {
142+
ctx.lineWidth = lineWidth;
143+
}
122144
}
123145
}
124146

125147
return (
126148
<div id="container">
127149
<Sidebar
128150
addStroke={addStroke}
129-
130151
addLineWidth={addLineWidth}
131152
clearOnClick={clearOnClick}
132153
ref={sidebarRef}
133154
id="clear"
134155
toggleMenu={toggleMenu}
135156
></Sidebar>
136157
<Canvas canvasRef={canvasRef}></Canvas>
158+
{eraserMode && <EraserCursor></EraserCursor>}
137159
{showMenu && <Menu></Menu>}
138160
</div>
139161
);
140162
}
141163

142-
export default App;
143-
164+
export default App;

client/src/assets/eraserSvg.svg

Lines changed: 22 additions & 0 deletions
Loading

client/src/atoms.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { atom} from "recoil";
2+
3+
export const eraserState = atom({
4+
key: "eraser",
5+
default: false
6+
});
7+
8+
export const cursorPosition = atom({
9+
key: "cursor-position",
10+
default: {x: 0, y: 0}
11+
})

client/src/components/Eraser.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import eraserImg from "../assets/eraser.png";
2+
import { useRecoilState } from "recoil";
3+
import { eraserState } from "../atoms";
24

35
function Eraser(){
6+
const [ eraserMode, setEraserMode ] = useRecoilState(eraserState);
7+
8+
function clickHandler(){
9+
setEraserMode(!eraserMode);
10+
}
11+
412
return (
513
<div className="flex p-1 items-center justify-around mt-3">
614
<h1 className="font-sans text-lg antialiased font-semibold text-white">
715
Eraser :
816
</h1>
9-
<img src={eraserImg} className="h-8 w-8 rounded cursor-pointer"></img>
17+
<img src={eraserImg} className="h-8 w-8 rounded cursor-pointer" onClick={clickHandler} ></img>
1018
</div>
1119
);
1220
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import eraserSvg from "../assets/eraserSvg.svg";
2+
import { useRecoilValue } from "recoil";
3+
import { cursorPosition } from "../atoms";
4+
5+
function EraserCursor() {
6+
const position = useRecoilValue(cursorPosition);
7+
8+
return (
9+
<div
10+
className="h-7 w-7 left-36 transform rotate-90 absolute eraser-cursor"
11+
style={{ left: position.x, top: position.y }}
12+
>
13+
<img src={eraserSvg}></img>
14+
</div>
15+
);
16+
}
17+
18+
export default EraserCursor;

client/src/main.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import React from 'react'
22
import ReactDOM from 'react-dom/client'
33
import App from './App.jsx'
44
import './index.css'
5+
import { RecoilRoot } from "recoil"
56

6-
ReactDOM.createRoot(document.getElementById('root')).render(
7+
ReactDOM.createRoot(document.getElementById("root")).render(
78
<React.StrictMode>
8-
<App />
9-
</React.StrictMode>,
10-
)
9+
<RecoilRoot>
10+
<App />
11+
</RecoilRoot>
12+
</React.StrictMode>
13+
);

0 commit comments

Comments
 (0)