Skip to content

Commit

Permalink
Merge pull request #62 from apsinghdev/feat/implement-collaboration
Browse files Browse the repository at this point in the history
feat: implement the functionality of the collaboration workflow
  • Loading branch information
apsinghdev authored Sep 4, 2024
2 parents 07fdbbe + a6e5820 commit 23581e6
Show file tree
Hide file tree
Showing 14 changed files with 280 additions and 94 deletions.
28 changes: 21 additions & 7 deletions api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,40 @@ app.get('/test', (res) => {
})

io.on('connection', (socket) => {
console.log('user connected socket')

socket.on('joinRoom', ({room_id}) => {
socket.join(room_id);
console.log(`User ${socket.id} joined room ${room_id}`);
})

socket.on('draw', (data)=>{
socket.broadcast.emit('draw', data);
const room = data.room_id;
socket.to(room).emit('draw', data);
})

socket.on('clear', () => {
io.emit('clear');
socket.on('clear', (data) => {
const room = data.room_id;
socket.to(room).emit('clear');
})

socket.on('open-text-editor', data => {
socket.broadcast.emit("open-text-editor", data);
const room = data.room_id;
socket.to(room).emit("open-text-editor", data);
})

socket.on('close-text-editor', data => {
socket.broadcast.emit("close-text-editor", data);
const room = data.room_id;
socket.to(room).emit("close-text-editor", data);
})

socket.on("text-updated", (data) => {
socket.broadcast.emit("text-updated", data);
const room = data.room_id;
socket.to(room).emit("text-updated", data);
});

socket.on("disconnect", () => {
console.log(`${socket.id} disconnected`);
})
})

server.listen(PORT, ()=>{
Expand Down
1 change: 0 additions & 1 deletion client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<script src="./src/socket.js"></script>
</body>
</html>
14 changes: 14 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"react-icons": "^5.2.0",
"recoil": "^0.7.7",
"socket.io-client": "^4.7.4",
"uuid": "^10.0.0",
"y-socket.io": "^1.1.3",
"yjs": "^13.6.15"
},
Expand Down
140 changes: 112 additions & 28 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useRef, useState } from "react";
import { io } from "socket.io-client";
import "./App.css";
import socket from "./socket";

import InfoMsg from "./components/InfoMsg";
import Sidebar from "./components/Sidebar";
import Canvas from "./components/Canvas";
import Menu from "./components/Menu";
import EraserCursor from "./components/EraserCursor";
import TextEditor from "./components/TextEditor";
import { useRecoilValue, useRecoilState } from "recoil";
import { useRecoilValue, useRecoilState, useSetRecoilState } from "recoil";
import {
eraserState,
cursorPosition,
canvasColors,
canvasState,
showMenuState,
showTextEditor,
collaborationStarted,
showMsg,
roomIdAtom,
messageTxtAtom
} from "./atoms";
import { useSocket } from "./Context";

socket.connect();
const PORT = "http://localhost:8000";

function App() {
const [showMenu, setShowMenu] = useRecoilState(showMenuState);
Expand All @@ -32,7 +37,13 @@ function App() {
const canvasColor = useRecoilValue(canvasColors);
const [currentCanvas, setCanvas] = useRecoilState(canvasState);
const textEditor = useRecoilValue(showTextEditor);

const setTextEditor = useSetRecoilState(showTextEditor);
const hasCollaborationStarted = useRecoilValue(collaborationStarted);
const setCollaborationFlag = useSetRecoilState(collaborationStarted);
const [showMessage, setShowMsg] = useRecoilState(showMsg);
const [roomId, setRoomId] = useRecoilState(roomIdAtom);
const { socket, setSocket } = useSocket();
const [ messageText, setMessageText ] = useRecoilState(messageTxtAtom);

function toggleMenu() {
setShowMenu(!showMenu);
Expand Down Expand Up @@ -76,7 +87,9 @@ function App() {
const endX = e.clientX - canvas.getBoundingClientRect().left;
const endY = e.clientY - canvas.getBoundingClientRect().top;
drawLine(startX, startY, endX, endY, penColor);
socket.emit("draw", { startX, startY, endX, endY, penColor, lineWidth });
if (hasCollaborationStarted && socket) {
socket.emit("draw", { startX, startY, endX, endY, penColor, lineWidth, room_id: roomId });
}
setStartX(endX);
setStartY(endY);
}
Expand Down Expand Up @@ -104,29 +117,31 @@ function App() {
canvas.removeEventListener("mousedown", handleMousedown);
canvas.removeEventListener("mouseup", handleMouseup);
};
}, [penColor, eraserMode, position, ctx, isDrawing, startX, startY]);
}, [socket, penColor, eraserMode, position, ctx, isDrawing, startX, startY]);

useEffect(() => {
socket.on("draw", (data) => {
drawLine(
data.startX,
data.startY,
data.endX,
data.endY,
data.penColor,
data.lineWidth
);
});

socket.on("clear", () => {
clearRect();
});

return () => {
socket.off("draw");
socket.off("clear");
};
}, [socket, ctx]);
if (hasCollaborationStarted && socket) {
socket.on("draw", (data) => {
drawLine(
data.startX,
data.startY,
data.endX,
data.endY,
data.penColor,
data.lineWidth
);
});

socket.on("clear", () => {
clearRect();
});

return () => {
socket.off("draw");
socket.off("clear");
};
}
}, [socket, ctx, hasCollaborationStarted]);

function clearRect() {
if (ctx) {
Expand All @@ -136,7 +151,10 @@ function App() {

function clearOnClick() {
clearRect();
socket.emit("clear");
if (hasCollaborationStarted && socket) {
const data = {room_id: roomId};
socket.emit("clear", data);
}
}

function addStroke(e) {
Expand All @@ -158,6 +176,71 @@ function App() {
}
}

const closeMsg = () => {
setMessageText(null);
setShowMsg(false);
}

useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const roomID = urlParams.get("roomID");
const RETRIES = 5;
const DELAY_DURATION = 2000;
let attempts = 0;

const closeConnection = (socket) => {
if (attempts >= RETRIES) {
socket.disconnect();
setCollaborationFlag(false);
console.log("Max socket calls exceeded. Please try connecting again.")
}
}

if (roomID) {
const collaborationLink = window.location.href;
setRoomId(roomID);
setCollaborationFlag(true);
const newSocket = io(PORT);

try {
newSocket.on("connect", () => {
console.log("connected");
try {
newSocket.emit("joinRoom", { room_id: roomID });
console.log(`joined room ${roomID}`);
setSocket(newSocket);
setMessageText(`Collaboration Link : ${collaborationLink}`);
setShowMsg(true);
} catch (error) {
console.log("Can't join the room", error);
}
});

newSocket.on("connect_error", (error) => {
attempts++;
console.log("Failed to connect to the socket server. Retrying...")
setTimeout(() => closeConnection(newSocket), DELAY_DURATION);
})

} catch (error) {
console.log("Can't connect", error);
}
}
}, []);

// Hook to listen the events emitted by the server
useEffect(() => {
if (hasCollaborationStarted && socket) {
socket.on("open-text-editor", () => {
setTextEditor(true);
});

return () => {
socket.off("open-text-editor");
};
}
}, [showTextEditor, socket, hasCollaborationStarted]);

return (
<div id="container">
<Sidebar
Expand All @@ -172,6 +255,7 @@ function App() {
{eraserMode && <EraserCursor></EraserCursor>}
{showMenu && <Menu></Menu>}
{textEditor && <TextEditor></TextEditor>}
{showMessage && <InfoMsg message={messageText} clickHandler={closeMsg}></InfoMsg>}
</div>
);
}
Expand Down
20 changes: 20 additions & 0 deletions client/src/Context.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { createContext, useContext, useState } from 'react';

// Create a Context for the socket
const SocketContext = createContext(null);

// Create a custom hook to use the SocketContext
export const useSocket = () => {
return useContext(SocketContext);
};

// Create a provider component
export const SocketProvider = ({ children }) => {
const [socket, setSocket] = useState(null);

return (
<SocketContext.Provider value={{ socket, setSocket }}>
{children}
</SocketContext.Provider>
);
};
22 changes: 21 additions & 1 deletion client/src/atoms.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,24 @@ export const showTextEditor = atom({
export const textEditorInput = atom({
key: "textEditorInput",
default: ''
})
})

export const collaborationStarted = atom({
key: "collaboraionstarted",
default: false
})

export const showMsg = atom({
key: "showMsg",
default: false
})

export const roomIdAtom = atom({
key: "roomIdAtom",
default: null
})

export const messageTxtAtom = atom({
key: "msgTxtAtom",
default: null
});
10 changes: 10 additions & 0 deletions client/src/components/InfoMsg.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const InfoMsg = (props) => {
return (
<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" >
<h6 className="font-sans text-emerald-300 cursor-pointer justify-center flex absolute top-0 right-2 mb-1" onClick={props.clickHandler}>×</h6>
<h6 className="font-sans text-white justify-center flex">{props.message}</h6>
</div>
)
}

export default InfoMsg;
Loading

0 comments on commit 23581e6

Please sign in to comment.