Skip to content

Commit 701cab8

Browse files
committed
fixing(websocket): optimize WebSocket connections in CanvasSheet component & Learning
1 parent 318496f commit 701cab8

File tree

12 files changed

+2296
-282
lines changed

12 files changed

+2296
-282
lines changed

apps/collabydraw/app/(canvas)/canvas/[roomName]/page.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import client from '@repo/db/client';
22
import { notFound, redirect } from 'next/navigation';
33
import { getServerSession } from 'next-auth';
44
import { authOptions } from '@/utils/auth';
5-
import { CanvasSheet } from '@/components/canvas/CanvasSheet';
5+
// import RoomCanvas from '@/components/canvas/RoomCanvas';
6+
import CanvasSheet from '@/components/canvas/CanvasSheet';
67

78
export default async function CanvasPage({ params }: { params: Promise<{ roomName: string }> }) {
89
const resolvedParams = params instanceof Promise ? await params : params;
@@ -31,5 +32,12 @@ export default async function CanvasPage({ params }: { params: Promise<{ roomNam
3132
userName={user.name || 'User-' + user.id}
3233
token={session.accessToken}
3334
/>
35+
// <RoomCanvas
36+
// roomId={room.id.toString()}
37+
// roomName={room.slug}
38+
// userId={user.id}
39+
// userName={user.name || 'User-' + user.id}
40+
// token={session.accessToken}
41+
// />
3442
)
3543
}
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
"use client"
2+
3+
import { Game } from "@/draw/Game";
4+
import { BgFill, canvasBgLight, StrokeEdge, StrokeFill, StrokeStyle, StrokeWidth, ToolType } from "@/types/canvas";
5+
import React, { useCallback, useEffect, useRef, useState } from "react";
6+
import { Scale } from "../Scale";
7+
import { MobileNavbar } from "../mobile-navbar";
8+
import { useTheme } from "next-themes";
9+
import { MainMenuStack } from "../MainMenuStack";
10+
import { ToolMenuStack } from "../ToolMenuStack";
11+
import SidebarTriggerButton from "../SidebarTriggerButton";
12+
import { useMediaQuery } from "@/hooks/useMediaQuery";
13+
import Toolbar from "../Toolbar";
14+
import ScreenLoading from "../ScreenLoading";
15+
import CollaborationStart from "../CollaborationStartBtn";
16+
import { cn } from "@/lib/utils";
17+
import { RoomParticipants } from "@repo/common/types";
18+
19+
// const WS_URL = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:8080';
20+
21+
export default function CanvasSheet({ roomName, roomId, userId, userName, socket, isConnected }: {
22+
roomName: string; roomId: string; userId: string; userName: string; token: string; socket: WebSocket | null; isConnected: boolean;
23+
}) {
24+
const { theme } = useTheme()
25+
const canvasRef = useRef<HTMLCanvasElement>(null);
26+
// const [game, setGame] = useState<Game>();
27+
const [scale, setScale] = useState<number>(1);
28+
const [activeTool, setActiveTool] = useState<ToolType>("grab");
29+
const [strokeFill, setStrokeFill] = useState<StrokeFill>("#f08c00");
30+
const [strokeWidth, setStrokeWidth] = useState<StrokeWidth>(1);
31+
const [bgFill, setBgFill] = useState<BgFill>("#00000000");
32+
const [strokeEdge, setStrokeEdge] = useState<StrokeEdge>("round");
33+
const [strokeStyle, setStrokeStyle] = useState<StrokeStyle>("solid");
34+
const [grabbing, setGrabbing] = useState(false);
35+
const paramsRef = useRef({ roomId, roomName, userId, userName });
36+
const activeToolRef = useRef(activeTool);
37+
const strokeFillRef = useRef(strokeFill);
38+
const strokeWidthRef = useRef(strokeWidth);
39+
const strokeEdgeRef = useRef(strokeEdge);
40+
const strokeStyleRef = useRef(strokeStyle);
41+
const bgFillRef = useRef(bgFill);
42+
const [sidebarOpen, setSidebarOpen] = useState(false);
43+
const [canvasColor, setCanvasColor] = useState<string>(canvasBgLight[0]);
44+
const canvasColorRef = useRef(canvasColor);
45+
const { matches, isLoading } = useMediaQuery('md');
46+
const [participants, setParticipants] = useState<RoomParticipants[]>([]);
47+
const gameRef = useRef<Game>();
48+
const [isSocketReady, setIsSocketReady] = useState(false);
49+
const connectionRef = useRef<number>(0);
50+
51+
useEffect(() => {
52+
if (!socket) {
53+
setIsSocketReady(false);
54+
return;
55+
}
56+
57+
const currentConnection = ++connectionRef.current;
58+
let timeout: NodeJS.Timeout;
59+
60+
const handleOpen = () => {
61+
if (currentConnection !== connectionRef.current) return;
62+
console.log('Socket ready in CanvasSheet');
63+
setIsSocketReady(true);
64+
};
65+
66+
if (socket.readyState === WebSocket.OPEN) {
67+
// Add slight delay to ensure parent state updates
68+
timeout = setTimeout(handleOpen, 50);
69+
} else {
70+
socket.addEventListener('open', handleOpen);
71+
}
72+
73+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
74+
let connectionRefValue = connectionRef.current;
75+
76+
return () => {
77+
connectionRefValue++;
78+
clearTimeout(timeout);
79+
socket.removeEventListener('open', handleOpen);
80+
setIsSocketReady(false);
81+
};
82+
}, [socket, isConnected, setIsSocketReady]); // Add isConnected to dependencies
83+
84+
useEffect(() => {
85+
console.log('E2')
86+
paramsRef.current = { roomId, roomName, userId, userName };
87+
}, [roomId, roomName, userId, userName]);
88+
89+
useEffect(() => {
90+
console.log('E3')
91+
setCanvasColor(canvasBgLight[0]);
92+
}, [theme])
93+
94+
useEffect(() => {
95+
console.log('E4')
96+
const handleResize = () => {
97+
if (canvasRef.current && gameRef.current) {
98+
const canvas = canvasRef.current;
99+
canvas.width = window.innerWidth;
100+
canvas.height = window.innerHeight;
101+
gameRef.current.handleResize(window.innerWidth, window.innerHeight);
102+
}
103+
};
104+
handleResize();
105+
window.addEventListener('resize', handleResize);
106+
return () => window.removeEventListener('resize', handleResize);
107+
}, [gameRef]);
108+
109+
useEffect(() => {
110+
console.log('E6')
111+
strokeEdgeRef.current = strokeEdge;
112+
gameRef?.current?.setStrokeEdge(strokeEdge);
113+
}, [strokeEdge, gameRef]);
114+
115+
useEffect(() => {
116+
console.log('E7')
117+
strokeStyleRef.current = strokeStyle;
118+
gameRef?.current?.setStrokeStyle(strokeStyle);
119+
}, [strokeStyle, gameRef]);
120+
121+
useEffect(() => {
122+
console.log('E8')
123+
activeToolRef.current = activeTool;
124+
gameRef?.current?.setTool(activeTool);
125+
}, [activeTool, gameRef]);
126+
127+
useEffect(() => {
128+
console.log('E9')
129+
strokeWidthRef.current = strokeWidth;
130+
gameRef?.current?.setStrokeWidth(strokeWidth);
131+
}, [strokeWidth, gameRef]);
132+
133+
useEffect(() => {
134+
console.log('E10')
135+
strokeFillRef.current = strokeFill;
136+
gameRef?.current?.setStrokeFill(strokeFill);
137+
}, [strokeFill, gameRef]);
138+
139+
useEffect(() => {
140+
console.log('E11')
141+
bgFillRef.current = bgFill;
142+
gameRef?.current?.setBgFill(bgFill);
143+
}, [bgFill, gameRef]);
144+
145+
useEffect(() => {
146+
console.log('E12')
147+
if (gameRef.current && canvasColorRef.current !== canvasColor) {
148+
canvasColorRef.current = canvasColor;
149+
gameRef.current.setCanvasBgColor(canvasColor);
150+
}
151+
}, [canvasColor, gameRef]);
152+
153+
useEffect(() => {
154+
console.log('E13')
155+
if (gameRef.current) {
156+
gameRef.current.setScale(scale);
157+
}
158+
}, [scale, gameRef]);
159+
160+
useEffect(() => {
161+
console.log('E14')
162+
const handleKeyDown = (e: KeyboardEvent) => {
163+
switch (e.key) {
164+
case "1":
165+
setActiveTool("grab");
166+
break;
167+
case "2":
168+
setActiveTool("rectangle");
169+
break;
170+
case "3":
171+
setActiveTool("ellipse");
172+
break;
173+
case "4":
174+
setActiveTool("diamond");
175+
break;
176+
case "5":
177+
setActiveTool("line");
178+
break;
179+
case "6":
180+
setActiveTool("pen");
181+
break;
182+
case "7":
183+
setActiveTool("eraser");
184+
break;
185+
default:
186+
break;
187+
}
188+
};
189+
document.addEventListener("keydown", handleKeyDown);
190+
return () => {
191+
document.removeEventListener("keydown", handleKeyDown);
192+
};
193+
}, []);
194+
195+
useEffect(() => {
196+
console.log('E16')
197+
if (!isSocketReady || !socket || !canvasRef.current) return;
198+
console.log('Initializing game with valid socket');
199+
const game = new Game(
200+
canvasRef.current,
201+
paramsRef.current.roomId,
202+
canvasColorRef.current,
203+
paramsRef.current.roomName,
204+
(newScale) => setScale(newScale),
205+
false,
206+
socket,
207+
(newParticipants) => setParticipants(newParticipants)
208+
);
209+
210+
gameRef.current = game;
211+
// setGame(game);
212+
213+
canvasRef.current.width = window.innerWidth;
214+
canvasRef.current.height = window.innerHeight;
215+
216+
return () => {
217+
gameRef.current?.destroy();
218+
gameRef.current = undefined;
219+
};
220+
}, [isSocketReady, socket]);
221+
222+
useEffect(() => {
223+
console.log('E17')
224+
if (activeTool === "grab") {
225+
const handleGrab = () => {
226+
setGrabbing((prev) => !prev)
227+
}
228+
229+
document.addEventListener("mousedown", handleGrab)
230+
document.addEventListener("mouseup", handleGrab)
231+
232+
return () => {
233+
document.removeEventListener("mousedown", handleGrab)
234+
document.removeEventListener("mouseup", handleGrab)
235+
}
236+
}
237+
}, [activeTool]);
238+
239+
const toggleSidebar = useCallback(() => {
240+
console.log('E19')
241+
setSidebarOpen(prev => !prev);
242+
}, []);
243+
244+
// useEffect(() => {
245+
// if (!socket) {
246+
// setIsSocketReady(false);
247+
// return;
248+
// }
249+
250+
// const handleOpen = () => setIsSocketReady(true);
251+
252+
// if (socket.readyState === WebSocket.OPEN) {
253+
// handleOpen();
254+
// } else {
255+
// socket.addEventListener('open', handleOpen);
256+
// }
257+
258+
// console.log('isSocketReady = ', isSocketReady)
259+
260+
// return () => {
261+
// socket.removeEventListener('open', handleOpen);
262+
// };
263+
// }, [isSocketReady, socket]);
264+
265+
// useEffect(() => {
266+
// console.log('socket in CanvasSheet = ', socket)
267+
// }, [socket])
268+
269+
useEffect(() => {
270+
console.log('participants = ', participants)
271+
}, [participants])
272+
273+
if (isLoading) {
274+
return <ScreenLoading />
275+
}
276+
277+
if (!socket || !isSocketReady) {
278+
return <ScreenLoading content={isConnected ? "Finalizing connection..." : "Connecting..."} />;
279+
}
280+
281+
return (
282+
<div className={cn("collabydraw h-screen overflow-hidden",
283+
activeTool === "eraser"
284+
? "cursor-[url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAOBJREFUOE9jZKAyYKSyeQzDwMD////7MDAw6EGD5hIjI+MWfMGE08sggz5+/Dj71q1bHPv27eMFGeLk5PRZTU3tBz8/fyoug7EaCDLs58+fa0NDQ9k2b96M4iBfX1+G1atX/2JnZw/GZihWAz98+PA8NjZWAt0wmMkgQxcvXvxCQEBAEt37GAaCXHf69OnFZmZmAvjC6tSpUx9MTU1j0V2JzcCqzs7OpoqKCmZ8BnZ0dPwtLy+vY2RkbENWRxcDqetlkPOpGikgA6mebGCGUi1hI8ca1bIeucXaMCi+SPU6AHRTjhWg+vuGAAAAAElFTkSuQmCC')_10_10,auto]"
285+
: activeTool === "grab" && !sidebarOpen
286+
? grabbing ? "cursor-grabbing" : "cursor-grab"
287+
: "cursor-crosshair")}>
288+
<div className="App_Menu App_Menu_Top fixed z-[4] top-4 right-4 left-4 flex justify-center items-center md:grid md:grid-cols-[1fr_auto_1fr] md:gap-8 md:items-start">
289+
{matches && (
290+
<div className="Main_Menu_Stack Sidebar_Trigger_Button md:grid md:gap-[calc(.25rem*6)] grid-cols-[auto] grid-flow-row grid-rows auto-rows-min justify-self-start">
291+
<div className="relative">
292+
<SidebarTriggerButton onClick={toggleSidebar} />
293+
294+
{sidebarOpen && (
295+
<MainMenuStack
296+
isOpen={sidebarOpen}
297+
onClose={() => setSidebarOpen(false)}
298+
canvasColor={canvasColor}
299+
setCanvasColor={setCanvasColor}
300+
roomName={roomName}
301+
/>
302+
)}
303+
</div>
304+
305+
<ToolMenuStack activeTool={activeTool}
306+
strokeFill={strokeFill}
307+
setStrokeFill={setStrokeFill}
308+
strokeWidth={strokeWidth}
309+
setStrokeWidth={setStrokeWidth}
310+
bgFill={bgFill}
311+
setBgFill={setBgFill}
312+
strokeEdge={strokeEdge}
313+
setStrokeEdge={setStrokeEdge}
314+
strokeStyle={strokeStyle}
315+
setStrokeStyle={setStrokeStyle}
316+
/>
317+
</div>
318+
)}
319+
<Toolbar
320+
selectedTool={activeTool}
321+
onToolSelect={setActiveTool}
322+
/>
323+
{matches && (
324+
<CollaborationStart participants={participants} slug={roomName} />
325+
)}
326+
</div>
327+
328+
{matches && (
329+
<Scale scale={scale} setScale={setScale} />
330+
)}
331+
332+
{!matches && (
333+
<MobileNavbar
334+
sidebarOpen={sidebarOpen}
335+
setSidebarOpen={setSidebarOpen}
336+
canvasColor={canvasColor}
337+
setCanvasColor={setCanvasColor}
338+
scale={scale}
339+
setScale={setScale}
340+
341+
activeTool={activeTool}
342+
strokeFill={strokeFill}
343+
setStrokeFill={setStrokeFill}
344+
strokeWidth={strokeWidth}
345+
setStrokeWidth={setStrokeWidth}
346+
bgFill={bgFill}
347+
setBgFill={setBgFill}
348+
349+
strokeEdge={strokeEdge}
350+
setStrokeEdge={setStrokeEdge}
351+
strokeStyle={strokeStyle}
352+
setStrokeStyle={setStrokeStyle}
353+
354+
roomName={roomName}
355+
/>
356+
)}
357+
<canvas className={cn("collabydraw collabydraw-canvas", theme === 'dark' ? 'collabydraw-canvas-dark' : '')} ref={canvasRef} />
358+
</div >
359+
)
360+
};

0 commit comments

Comments
 (0)