Skip to content

Commit 23581e6

Browse files
authored
Merge pull request #62 from apsinghdev/feat/implement-collaboration
feat: implement the functionality of the collaboration workflow
2 parents 07fdbbe + a6e5820 commit 23581e6

File tree

14 files changed

+280
-94
lines changed

14 files changed

+280
-94
lines changed

api/index.js

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,40 @@ app.get('/test', (res) => {
2727
})
2828

2929
io.on('connection', (socket) => {
30-
console.log('user connected socket')
30+
31+
socket.on('joinRoom', ({room_id}) => {
32+
socket.join(room_id);
33+
console.log(`User ${socket.id} joined room ${room_id}`);
34+
})
35+
3136
socket.on('draw', (data)=>{
32-
socket.broadcast.emit('draw', data);
37+
const room = data.room_id;
38+
socket.to(room).emit('draw', data);
3339
})
3440

35-
socket.on('clear', () => {
36-
io.emit('clear');
41+
socket.on('clear', (data) => {
42+
const room = data.room_id;
43+
socket.to(room).emit('clear');
3744
})
3845

3946
socket.on('open-text-editor', data => {
40-
socket.broadcast.emit("open-text-editor", data);
47+
const room = data.room_id;
48+
socket.to(room).emit("open-text-editor", data);
4149
})
4250

4351
socket.on('close-text-editor', data => {
44-
socket.broadcast.emit("close-text-editor", data);
52+
const room = data.room_id;
53+
socket.to(room).emit("close-text-editor", data);
4554
})
4655

4756
socket.on("text-updated", (data) => {
48-
socket.broadcast.emit("text-updated", data);
57+
const room = data.room_id;
58+
socket.to(room).emit("text-updated", data);
4959
});
60+
61+
socket.on("disconnect", () => {
62+
console.log(`${socket.id} disconnected`);
63+
})
5064
})
5165

5266
server.listen(PORT, ()=>{

client/index.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,5 @@
99
<body>
1010
<div id="root"></div>
1111
<script type="module" src="/src/main.jsx"></script>
12-
<script src="./src/socket.js"></script>
1312
</body>
1413
</html>

client/package-lock.json

Lines changed: 14 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
@@ -16,6 +16,7 @@
1616
"react-icons": "^5.2.0",
1717
"recoil": "^0.7.7",
1818
"socket.io-client": "^4.7.4",
19+
"uuid": "^10.0.0",
1920
"y-socket.io": "^1.1.3",
2021
"yjs": "^13.6.15"
2122
},

client/src/App.jsx

Lines changed: 112 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
/* eslint-disable react-hooks/exhaustive-deps */
22
import { useEffect, useRef, useState } from "react";
3+
import { io } from "socket.io-client";
34
import "./App.css";
4-
import socket from "./socket";
5-
5+
import InfoMsg from "./components/InfoMsg";
66
import Sidebar from "./components/Sidebar";
77
import Canvas from "./components/Canvas";
88
import Menu from "./components/Menu";
99
import EraserCursor from "./components/EraserCursor";
1010
import TextEditor from "./components/TextEditor";
11-
import { useRecoilValue, useRecoilState } from "recoil";
11+
import { useRecoilValue, useRecoilState, useSetRecoilState } from "recoil";
1212
import {
1313
eraserState,
1414
cursorPosition,
1515
canvasColors,
1616
canvasState,
1717
showMenuState,
1818
showTextEditor,
19+
collaborationStarted,
20+
showMsg,
21+
roomIdAtom,
22+
messageTxtAtom
1923
} from "./atoms";
24+
import { useSocket } from "./Context";
2025

21-
socket.connect();
26+
const PORT = "http://localhost:8000";
2227

2328
function App() {
2429
const [showMenu, setShowMenu] = useRecoilState(showMenuState);
@@ -32,7 +37,13 @@ function App() {
3237
const canvasColor = useRecoilValue(canvasColors);
3338
const [currentCanvas, setCanvas] = useRecoilState(canvasState);
3439
const textEditor = useRecoilValue(showTextEditor);
35-
40+
const setTextEditor = useSetRecoilState(showTextEditor);
41+
const hasCollaborationStarted = useRecoilValue(collaborationStarted);
42+
const setCollaborationFlag = useSetRecoilState(collaborationStarted);
43+
const [showMessage, setShowMsg] = useRecoilState(showMsg);
44+
const [roomId, setRoomId] = useRecoilState(roomIdAtom);
45+
const { socket, setSocket } = useSocket();
46+
const [ messageText, setMessageText ] = useRecoilState(messageTxtAtom);
3647

3748
function toggleMenu() {
3849
setShowMenu(!showMenu);
@@ -76,7 +87,9 @@ function App() {
7687
const endX = e.clientX - canvas.getBoundingClientRect().left;
7788
const endY = e.clientY - canvas.getBoundingClientRect().top;
7889
drawLine(startX, startY, endX, endY, penColor);
79-
socket.emit("draw", { startX, startY, endX, endY, penColor, lineWidth });
90+
if (hasCollaborationStarted && socket) {
91+
socket.emit("draw", { startX, startY, endX, endY, penColor, lineWidth, room_id: roomId });
92+
}
8093
setStartX(endX);
8194
setStartY(endY);
8295
}
@@ -104,29 +117,31 @@ function App() {
104117
canvas.removeEventListener("mousedown", handleMousedown);
105118
canvas.removeEventListener("mouseup", handleMouseup);
106119
};
107-
}, [penColor, eraserMode, position, ctx, isDrawing, startX, startY]);
120+
}, [socket, penColor, eraserMode, position, ctx, isDrawing, startX, startY]);
108121

109122
useEffect(() => {
110-
socket.on("draw", (data) => {
111-
drawLine(
112-
data.startX,
113-
data.startY,
114-
data.endX,
115-
data.endY,
116-
data.penColor,
117-
data.lineWidth
118-
);
119-
});
120-
121-
socket.on("clear", () => {
122-
clearRect();
123-
});
124-
125-
return () => {
126-
socket.off("draw");
127-
socket.off("clear");
128-
};
129-
}, [socket, ctx]);
123+
if (hasCollaborationStarted && socket) {
124+
socket.on("draw", (data) => {
125+
drawLine(
126+
data.startX,
127+
data.startY,
128+
data.endX,
129+
data.endY,
130+
data.penColor,
131+
data.lineWidth
132+
);
133+
});
134+
135+
socket.on("clear", () => {
136+
clearRect();
137+
});
138+
139+
return () => {
140+
socket.off("draw");
141+
socket.off("clear");
142+
};
143+
}
144+
}, [socket, ctx, hasCollaborationStarted]);
130145

131146
function clearRect() {
132147
if (ctx) {
@@ -136,7 +151,10 @@ function App() {
136151

137152
function clearOnClick() {
138153
clearRect();
139-
socket.emit("clear");
154+
if (hasCollaborationStarted && socket) {
155+
const data = {room_id: roomId};
156+
socket.emit("clear", data);
157+
}
140158
}
141159

142160
function addStroke(e) {
@@ -158,6 +176,71 @@ function App() {
158176
}
159177
}
160178

179+
const closeMsg = () => {
180+
setMessageText(null);
181+
setShowMsg(false);
182+
}
183+
184+
useEffect(() => {
185+
const urlParams = new URLSearchParams(window.location.search);
186+
const roomID = urlParams.get("roomID");
187+
const RETRIES = 5;
188+
const DELAY_DURATION = 2000;
189+
let attempts = 0;
190+
191+
const closeConnection = (socket) => {
192+
if (attempts >= RETRIES) {
193+
socket.disconnect();
194+
setCollaborationFlag(false);
195+
console.log("Max socket calls exceeded. Please try connecting again.")
196+
}
197+
}
198+
199+
if (roomID) {
200+
const collaborationLink = window.location.href;
201+
setRoomId(roomID);
202+
setCollaborationFlag(true);
203+
const newSocket = io(PORT);
204+
205+
try {
206+
newSocket.on("connect", () => {
207+
console.log("connected");
208+
try {
209+
newSocket.emit("joinRoom", { room_id: roomID });
210+
console.log(`joined room ${roomID}`);
211+
setSocket(newSocket);
212+
setMessageText(`Collaboration Link : ${collaborationLink}`);
213+
setShowMsg(true);
214+
} catch (error) {
215+
console.log("Can't join the room", error);
216+
}
217+
});
218+
219+
newSocket.on("connect_error", (error) => {
220+
attempts++;
221+
console.log("Failed to connect to the socket server. Retrying...")
222+
setTimeout(() => closeConnection(newSocket), DELAY_DURATION);
223+
})
224+
225+
} catch (error) {
226+
console.log("Can't connect", error);
227+
}
228+
}
229+
}, []);
230+
231+
// Hook to listen the events emitted by the server
232+
useEffect(() => {
233+
if (hasCollaborationStarted && socket) {
234+
socket.on("open-text-editor", () => {
235+
setTextEditor(true);
236+
});
237+
238+
return () => {
239+
socket.off("open-text-editor");
240+
};
241+
}
242+
}, [showTextEditor, socket, hasCollaborationStarted]);
243+
161244
return (
162245
<div id="container">
163246
<Sidebar
@@ -172,6 +255,7 @@ function App() {
172255
{eraserMode && <EraserCursor></EraserCursor>}
173256
{showMenu && <Menu></Menu>}
174257
{textEditor && <TextEditor></TextEditor>}
258+
{showMessage && <InfoMsg message={messageText} clickHandler={closeMsg}></InfoMsg>}
175259
</div>
176260
);
177261
}

client/src/Context.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React, { createContext, useContext, useState } from 'react';
2+
3+
// Create a Context for the socket
4+
const SocketContext = createContext(null);
5+
6+
// Create a custom hook to use the SocketContext
7+
export const useSocket = () => {
8+
return useContext(SocketContext);
9+
};
10+
11+
// Create a provider component
12+
export const SocketProvider = ({ children }) => {
13+
const [socket, setSocket] = useState(null);
14+
15+
return (
16+
<SocketContext.Provider value={{ socket, setSocket }}>
17+
{children}
18+
</SocketContext.Provider>
19+
);
20+
};

client/src/atoms.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,24 @@ export const showTextEditor = atom({
3434
export const textEditorInput = atom({
3535
key: "textEditorInput",
3636
default: ''
37-
})
37+
})
38+
39+
export const collaborationStarted = atom({
40+
key: "collaboraionstarted",
41+
default: false
42+
})
43+
44+
export const showMsg = atom({
45+
key: "showMsg",
46+
default: false
47+
})
48+
49+
export const roomIdAtom = atom({
50+
key: "roomIdAtom",
51+
default: null
52+
})
53+
54+
export const messageTxtAtom = atom({
55+
key: "msgTxtAtom",
56+
default: null
57+
});

client/src/components/InfoMsg.jsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const InfoMsg = (props) => {
2+
return (
3+
<div className="fixed top-4 left-1/2 transform -translate-x-1/2 inline-block max-w-lg max-h-40 overflow-auto p-3 m-2 max-w-full break-words rounded-xl bg-gradient-to-r from-slate-900 to-slate-700 absolute rounded-lg shadow-xl content-center animate-slide-in" >
4+
<h6 className="font-sans text-emerald-300 cursor-pointer justify-center flex absolute top-0 right-2 mb-1" onClick={props.clickHandler}>×</h6>
5+
<h6 className="font-sans text-white justify-center flex">{props.message}</h6>
6+
</div>
7+
)
8+
}
9+
10+
export default InfoMsg;

0 commit comments

Comments
 (0)