@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
22import { Toolbar } from "./Toolbar" ;
33import { ColorPicker } from "./ColorPicker" ;
44import { StrokeControl } from "./StrokeControl" ;
5+ import { Cursor } from "./Cursor" ;
56import { toast } from "sonner" ;
67import { io } from "socket.io-client" ;
78import tinycolor from "tinycolor2" ;
@@ -28,10 +29,15 @@ export const Canvas = () => {
2829
2930 // --- Collaboration State ---
3031 const [ roomId , setRoomId ] = useState ( "" ) ;
32+ const [ username , setUsername ] = useState ( "" ) ;
3133 const [ joined , setJoined ] = useState ( false ) ;
3234 const [ socket , setSocket ] = useState ( null ) ;
3335 const [ isModalOpen , setIsModalOpen ] = useState ( false ) ;
3436
37+ // --- Cursor Tracking State ---
38+ const [ otherCursors , setOtherCursors ] = useState ( new Map ( ) ) ; // userId -> { x, y, username, color }
39+ const cursorColors = useRef ( new Map ( ) ) ; // userId -> color
40+
3541 const [ isLoggedIn , setIsLoggedIn ] = useState ( ! ! localStorage . getItem ( "token" ) ) ;
3642
3743 const handleLogout = async ( ) => {
@@ -115,12 +121,34 @@ export const Canvas = () => {
115121 canvasImage . current = canvas . toDataURL ( ) ;
116122 } ;
117123
124+ // Generate a random color for a user
125+ const getColorForUser = ( userId ) => {
126+ if ( ! cursorColors . current . has ( userId ) ) {
127+ const colors = [
128+ "#FF6B6B" , "#4ECDC4" , "#45B7D1" , "#FFA07A" ,
129+ "#98D8C8" , "#F7DC6F" , "#BB8FCE" , "#85C1E2" ,
130+ "#F8B739" , "#52D3AA" , "#E74C3C" , "#3498DB"
131+ ] ;
132+ const color = colors [ Math . floor ( Math . random ( ) * colors . length ) ] ;
133+ cursorColors . current . set ( userId , color ) ;
134+ }
135+ return cursorColors . current . get ( userId ) ;
136+ } ;
137+
118138 useEffect ( ( ) => {
119139 const s = io ( "http://localhost:3000" ) ;
120140 setSocket ( s ) ;
121- s . on ( "connect" , ( ) => console . log ( "Connected to server:" , s . id ) ) ;
141+
142+ s . on ( "connect" , ( ) => {
143+ console . log ( "✅ Connected to server:" , s . id ) ;
144+ } ) ;
145+
146+ s . on ( "disconnect" , ( ) => {
147+ console . log ( "❌ Disconnected from server" ) ;
148+ } ) ;
149+
122150 s . on ( "draw" , ( { x, y, color, width, type, tool } ) => {
123- if ( ! joined ) return ;
151+ console . log ( "📥 Received draw event:" , { x , y , type , tool } ) ;
124152 const ctx = canvasRef . current ?. getContext ( "2d" ) ;
125153 if ( ! ctx ) return ;
126154
@@ -139,8 +167,80 @@ export const Canvas = () => {
139167 ctx . restore ( ) ; // Restore to default transform
140168 saveCanvasState ( ) ; // Save state after remote draw
141169 } ) ;
142- return ( ) => s . disconnect ( ) ;
143- } , [ joined , scale , offset ] ) ; // Add scale/offset dependencies
170+
171+ // Handle cursor movements from other users
172+ s . on ( "cursor-move" , ( { userId, x, y } ) => {
173+ console . log ( "🖱️ Received cursor from:" , userId , "at" , x , y ) ;
174+ setOtherCursors ( ( prev ) => {
175+ const updated = new Map ( prev ) ;
176+ const existing = updated . get ( userId ) || { } ;
177+ const newCursor = {
178+ x,
179+ y,
180+ username : existing . username || `User-${ userId . slice ( 0 , 4 ) } ` ,
181+ color : getColorForUser ( userId )
182+ } ;
183+ console . log ( "📌 Setting cursor:" , userId , newCursor ) ;
184+ updated . set ( userId , newCursor ) ;
185+ console . log ( "🗺️ Total cursors:" , updated . size ) ;
186+ return updated ;
187+ } ) ;
188+ } ) ;
189+
190+ // Handle new user joining
191+ s . on ( "user-joined" , ( { userId, username } ) => {
192+ console . log ( "👤 User joined:" , username , "(ID:" , userId , ")" ) ;
193+ setOtherCursors ( ( prev ) => {
194+ const updated = new Map ( prev ) ;
195+ updated . set ( userId , {
196+ x : 0 ,
197+ y : 0 ,
198+ username,
199+ color : getColorForUser ( userId )
200+ } ) ;
201+ console . log ( "🗺️ Total cursors after join:" , updated . size ) ;
202+ return updated ;
203+ } ) ;
204+ toast . info ( `${ username } joined the room` ) ;
205+ } ) ;
206+
207+ // Handle existing users when joining
208+ s . on ( "existing-users" , ( users ) => {
209+ console . log ( "👥 Existing users:" , users ) ;
210+ setOtherCursors ( ( prev ) => {
211+ const updated = new Map ( prev ) ;
212+ users . forEach ( ( { userId, username } ) => {
213+ updated . set ( userId , {
214+ x : 0 ,
215+ y : 0 ,
216+ username,
217+ color : getColorForUser ( userId )
218+ } ) ;
219+ } ) ;
220+ return updated ;
221+ } ) ;
222+ } ) ;
223+
224+ // Handle user leaving
225+ s . on ( "user-left" , ( { userId } ) => {
226+ console . log ( "👋 User left:" , userId ) ;
227+ setOtherCursors ( ( prev ) => {
228+ const updated = new Map ( prev ) ;
229+ const user = updated . get ( userId ) ;
230+ updated . delete ( userId ) ;
231+ cursorColors . current . delete ( userId ) ;
232+ if ( user ) {
233+ toast . info ( `${ user . username } left the room` ) ;
234+ }
235+ return updated ;
236+ } ) ;
237+ } ) ;
238+
239+ return ( ) => {
240+ console . log ( "🔌 Disconnecting socket..." ) ;
241+ s . disconnect ( ) ;
242+ } ;
243+ } , [ ] ) ; // Remove dependencies to prevent socket recreation!
144244
145245 useEffect ( ( ) => {
146246 const canvas = canvasRef . current ;
@@ -527,7 +627,9 @@ export const Canvas = () => {
527627 // --- Collaboration Handlers (Unchanged) ---
528628 const handleJoinRoom = ( ) => {
529629 if ( ! roomId . trim ( ) || ! socket ) return ;
530- socket . emit ( "join-room" , roomId . trim ( ) ) ;
630+ const displayName = username . trim ( ) || `User-${ socket . id ?. slice ( 0 , 4 ) } ` ;
631+ console . log ( "🚨 Joining room:" , roomId . trim ( ) , "as" , displayName ) ;
632+ socket . emit ( "join-room" , roomId . trim ( ) , displayName ) ;
531633 setJoined ( true ) ;
532634 setIsModalOpen ( false ) ;
533635 toast . success ( `Collaborative mode active - joined room: ${ roomId } ` ) ;
@@ -542,6 +644,8 @@ export const Canvas = () => {
542644 if ( socket ) {
543645 socket . emit ( "leave-room" , roomId ) ;
544646 setJoined ( false ) ;
647+ setOtherCursors ( new Map ( ) ) ; // Clear all cursors
648+ cursorColors . current . clear ( ) ; // Clear color mappings
545649 toast . success ( `Left room: ${ roomId } ` ) ;
546650 }
547651 } ;
@@ -641,17 +745,45 @@ export const Canvas = () => {
641745 onFocus = { ( ) => setIsCanvasFocused ( true ) }
642746 onBlur = { ( ) => setIsCanvasFocused ( false ) }
643747 onMouseDown = { startDrawing }
644- onMouseMove = { draw }
748+ onMouseMove = { ( e ) => {
749+ // Send cursor position to other users when in a room
750+ if ( joined && socket ) {
751+ const { x, y } = getWorldPoint ( e ) ;
752+ socket . emit ( "cursor-move" , { roomId, x, y } ) ;
753+ }
754+ draw ( e ) ;
755+ } }
645756 onMouseUp = { stopDrawing }
646757 onMouseLeave = { stopDrawing }
647758 onWheel = { handleWheel } // Added wheel handler
648759 className = { `${ getCursor ( ) } focus:outline-2 focus:outline-primary` } // Dynamic cursor
649760 />
761+
762+ { /* --- Render Other Users' Cursors --- */ }
763+ { joined && Array . from ( otherCursors . entries ( ) ) . map ( ( [ userId , cursor ] ) => {
764+ console . log ( "🎯 Rendering cursor for:" , userId , cursor ) ;
765+ return (
766+ < Cursor
767+ key = { userId }
768+ x = { cursor . x * scale + offset . x }
769+ y = { cursor . y * scale + offset . y }
770+ username = { cursor . username }
771+ color = { cursor . color }
772+ />
773+ ) ;
774+ } ) }
650775
651776 { /* --- Modal and Info (Unchanged) --- */ }
652777 { isModalOpen && (
653778 < div className = "fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white/90 border border-gray-400 rounded-xl shadow-xl p-6 z-50 flex flex-col items-center gap-3" >
654779 < h2 className = "text-xl font-semibold" > Join a Room</ h2 >
780+ < input
781+ type = "text"
782+ placeholder = "Enter Your Name"
783+ value = { username }
784+ onChange = { ( e ) => setUsername ( e . target . value ) }
785+ className = "border border-gray-400 rounded-md px-4 py-2 w-64 text-center"
786+ />
655787 < input
656788 type = "text"
657789 placeholder = "Enter Room ID"
0 commit comments