From a0cac07e09231efcd3ecb668298564e9b2546de8 Mon Sep 17 00:00:00 2001 From: Tang Bo Kuan Date: Wed, 8 Nov 2023 02:54:07 +0800 Subject: [PATCH 1/6] Add stopwatch tsx component --- frontend/components/CollabPage/CollabPage.tsx | 2 + .../CollabPage/Stopwatch/Stopwatch.tsx | 109 ++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 frontend/components/CollabPage/Stopwatch/Stopwatch.tsx diff --git a/frontend/components/CollabPage/CollabPage.tsx b/frontend/components/CollabPage/CollabPage.tsx index a187875..75988b6 100644 --- a/frontend/components/CollabPage/CollabPage.tsx +++ b/frontend/components/CollabPage/CollabPage.tsx @@ -23,6 +23,7 @@ import RejectEndSessionSnackBar from "./RejectEndSessionSnackBar"; import EndingSessionBackdrop from "./EndingSessionBackDrop"; import { enqueueSnackbar } from "notistack"; import { messageHandler } from "@/utils/handlers"; +import Stopwatch from "./Stopwatch/Stopwatch"; const CollabPage = () => { const { userId, language, roomId, cancelMatching, questions } = @@ -309,6 +310,7 @@ const CollabPage = () => { + {isEndingSession && } ); diff --git a/frontend/components/CollabPage/Stopwatch/Stopwatch.tsx b/frontend/components/CollabPage/Stopwatch/Stopwatch.tsx new file mode 100644 index 0000000..f453375 --- /dev/null +++ b/frontend/components/CollabPage/Stopwatch/Stopwatch.tsx @@ -0,0 +1,109 @@ +// Source: https://github.com/jinderbrar/Stopwatch-using-ReactJS-and-Material-UI/blob/main/src/components/Stopwatch.js +import React, { useState, useRef, useEffect } from "react"; +import { + Box, + Typography, + IconButton, + Grid, + Tooltip +} from "@mui/material"; +import PlayCircleFilledIcon from "@mui/icons-material/PlayCircleFilled"; +import PauseCircleFilledIcon from "@mui/icons-material/PauseCircleFilled"; +import RestoreIcon from "@mui/icons-material/Restore"; + +const Stopwatch = () => { + const [time, setTime] = useState(0.0); + const [isActive, setIsActive] = useState(false); + const intervalRef = useRef(null); + + const formatTime = () => { + const sec = `${Math.floor(time) % 60}`.padStart(2, "0"); + const min = `${Math.floor(time / 60) % 60}`.padStart(2, "0"); + const hour = `${Math.floor(time / 3600)}`.padStart(2, "0"); + return ( + <> + {[hour, min, sec].join(":")} + + {["hr", "min", "sec"].map((unit) => ( + + {unit} + + ))} + + + ); + }; + + const handlePlayPause = () => { + setIsActive(!isActive); + }; + const handleReset = () => { + setTime(0); + setIsActive(false); + }; + + useEffect(() => { + if (isActive) { + intervalRef.current = setInterval(() => setTime((prevTime) => prevTime + 0.1), 100); + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; + } + }, [isActive]); + + return ( + <> + + {formatTime()} + + + + + + ); +}; + +type ControlButtonsProps = { + args: { + time: number; + isActive: boolean; + handlePlayPause: () => void; + handleReset: () => void; + }; +}; + +const ControlButtons: React.FC = ({ + args: { time, isActive, handlePlayPause, handleReset } +}) => { + return ( + <> + {/* play or pause stopwatch */} + + handlePlayPause()}> + {isActive ? ( + + ) : ( + + )} + + + {/* reset stopwatch */} + + handleReset()}> + + + + + ); +}; + +export default Stopwatch; From df63b61252ea2b19068c77ecaed9d12c169fc049 Mon Sep 17 00:00:00 2001 From: Tang Bo Kuan Date: Wed, 8 Nov 2023 04:13:03 +0800 Subject: [PATCH 2/6] Stash changes --- collaboration-service/src/index.ts | 18 +++++ frontend/components/CollabPage/CollabPage.tsx | 43 +++++++++++- .../CollabPage/FabComponent/FabMenu.tsx | 70 +++++++++++++++++++ .../{Stopwatch => FabComponent}/Stopwatch.tsx | 43 ++++++++++-- 4 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 frontend/components/CollabPage/FabComponent/FabMenu.tsx rename frontend/components/CollabPage/{Stopwatch => FabComponent}/Stopwatch.tsx (79%) diff --git a/collaboration-service/src/index.ts b/collaboration-service/src/index.ts index cfeda70..94e729b 100644 --- a/collaboration-service/src/index.ts +++ b/collaboration-service/src/index.ts @@ -192,6 +192,24 @@ io.on("connection", (socket: Socket) => { .emit("runCodeDone", results); }); + extendedSocket.on("stopwatch_start_request", () => { + io.sockets + .in(extendedSocket.roomId) + .emit("start_stopwatch"); + }); + + extendedSocket.on("stopwatch_stop_request", () => { + io.sockets + .in(extendedSocket.roomId) + .emit("stop_stopwatch"); + }); + + extendedSocket.on("stopwatch_reset_request", () => { + io.sockets + .in(extendedSocket.roomId) + .emit("reset_stopwatch"); + }); + // Handle socket disconnection extendedSocket.on("disconnect", (reason) => { console.log( diff --git a/frontend/components/CollabPage/CollabPage.tsx b/frontend/components/CollabPage/CollabPage.tsx index 75988b6..af46193 100644 --- a/frontend/components/CollabPage/CollabPage.tsx +++ b/frontend/components/CollabPage/CollabPage.tsx @@ -23,7 +23,8 @@ import RejectEndSessionSnackBar from "./RejectEndSessionSnackBar"; import EndingSessionBackdrop from "./EndingSessionBackDrop"; import { enqueueSnackbar } from "notistack"; import { messageHandler } from "@/utils/handlers"; -import Stopwatch from "./Stopwatch/Stopwatch"; +import Stopwatch from "./FabComponent/Stopwatch"; +import FabMenu from "./FabComponent/FabMenu"; const CollabPage = () => { const { userId, language, roomId, cancelMatching, questions } = @@ -48,6 +49,30 @@ const CollabPage = () => { useState(false); const [iHaveAcceptedEndSession, setIHaveAcceptedEndSession] = useState(false); + // Stopwatch stuff + const [isRunning, setIsRunning] = useState(false); + const [isReset, setIsReset] = useState(false); + + const sendStartRequest = () => { + socket?.emit("stopwatch_start_request"); + }; + + const sendStopRequest = () => { + socket?.emit("stopwatch_stop_request"); + }; + + const sendResetRequest = () => { + socket?.emit("stopwatch_reset_request"); + }; + + const stopwatchProps = { + isRunning: isRunning, + isReset: isReset, + setIsReset: setIsReset, + sendStartRequest: sendStartRequest, + sendStopRequest: sendStopRequest, + sendResetRequest: sendResetRequest, + }; const toggleInterviewerView = () => { setShowInterviewerView(!showInterviewerView); @@ -205,6 +230,19 @@ const CollabPage = () => { messageHandler("End session request rejected", "warning"); }); + + socket.on("start_stopwatch", () => { + setIsRunning(true); + }); + + socket.on("stop_stopwatch", () => { + setIsRunning(false); + }); + + socket.on("reset_stopwatch", () => { + setIsReset(true); + setIsRunning(false); + }); return () => { socket.disconnect(); @@ -310,7 +348,8 @@ const CollabPage = () => { - + {/* */} + {isEndingSession && } ); diff --git a/frontend/components/CollabPage/FabComponent/FabMenu.tsx b/frontend/components/CollabPage/FabComponent/FabMenu.tsx new file mode 100644 index 0000000..a0eb695 --- /dev/null +++ b/frontend/components/CollabPage/FabComponent/FabMenu.tsx @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { Fab, Menu, MenuItem } from '@mui/material'; +import MenuIcon from '@mui/icons-material/Menu'; +import { StopwatchProps } from "./Stopwatch"; +import Stopwatch from './Stopwatch'; +import VideoAudioChat from '../VideoComm'; + +interface FabMenuProps { + stopwatchProps: StopwatchProps; + username1: string | null; + username2: string; +} + +const FabMenu = () => { + const [anchorEl, setAnchorEl] = React.useState(null); + const [stopwatchOpen, setStopwatchOpen] = React.useState(false); + const [videoOpen, setVideoOpen] = React.useState(false); + const open = Boolean(anchorEl); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClickVideo = () => { + setAnchorEl(null); + }; + + const handleClickStopwatch = () => { + setAnchorEl(null); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + return ( +
+ + + + + Video + Stopwatch + +
+ ); +}; + +export default FabMenu; diff --git a/frontend/components/CollabPage/Stopwatch/Stopwatch.tsx b/frontend/components/CollabPage/FabComponent/Stopwatch.tsx similarity index 79% rename from frontend/components/CollabPage/Stopwatch/Stopwatch.tsx rename to frontend/components/CollabPage/FabComponent/Stopwatch.tsx index f453375..60c198b 100644 --- a/frontend/components/CollabPage/Stopwatch/Stopwatch.tsx +++ b/frontend/components/CollabPage/FabComponent/Stopwatch.tsx @@ -10,8 +10,24 @@ import { import PlayCircleFilledIcon from "@mui/icons-material/PlayCircleFilled"; import PauseCircleFilledIcon from "@mui/icons-material/PauseCircleFilled"; import RestoreIcon from "@mui/icons-material/Restore"; +import { send } from "process"; -const Stopwatch = () => { +export interface StopwatchProps { + isRunning: boolean; + isReset: boolean; + setIsReset: (isReset: boolean) => void; + sendStartRequest: () => void; + sendStopRequest: () => void; + sendResetRequest: () => void; +} + +const Stopwatch = ({ + isRunning, + isReset, + setIsReset, + sendStartRequest, + sendStopRequest, + sendResetRequest }: StopwatchProps) => { const [time, setTime] = useState(0.0); const [isActive, setIsActive] = useState(false); const intervalRef = useRef(null); @@ -35,13 +51,32 @@ const Stopwatch = () => { }; const handlePlayPause = () => { - setIsActive(!isActive); + if (isActive) { + sendStopRequest(); + } else { + sendStartRequest(); + } }; const handleReset = () => { - setTime(0); - setIsActive(false); + sendResetRequest(); }; + useEffect(() => { + if (isRunning) { + setIsActive(true); + } else { + setIsActive(false); + } + + }, [isRunning]); + + useEffect(() => { + if (isReset) { + setIsReset(false); + setTime(0); + } + }, [isReset]); + useEffect(() => { if (isActive) { intervalRef.current = setInterval(() => setTime((prevTime) => prevTime + 0.1), 100); From f5bb859b58d193f8d0d90710b5c1b4d5e589bafd Mon Sep 17 00:00:00 2001 From: Tang Bo Kuan Date: Wed, 8 Nov 2023 05:09:33 +0800 Subject: [PATCH 3/6] Add timer --- frontend/components/CollabPage/CollabPage.tsx | 2 + .../CollabPage/FabComponent/FabMenu.tsx | 13 ++-- .../CollabPage/FabComponent/Stopwatch.tsx | 70 ++++++++++++++----- 3 files changed, 64 insertions(+), 21 deletions(-) diff --git a/frontend/components/CollabPage/CollabPage.tsx b/frontend/components/CollabPage/CollabPage.tsx index 8aac96e..19e012b 100644 --- a/frontend/components/CollabPage/CollabPage.tsx +++ b/frontend/components/CollabPage/CollabPage.tsx @@ -73,6 +73,8 @@ const CollabPage = () => { sendStartRequest: sendStartRequest, sendStopRequest: sendStopRequest, sendResetRequest: sendResetRequest, + setIsOpen: (x: boolean) => {}, // will be filled up by FabMenu + isOpen: false, // will be filled up by FabMenu }; const toggleInterviewerView = () => { diff --git a/frontend/components/CollabPage/FabComponent/FabMenu.tsx b/frontend/components/CollabPage/FabComponent/FabMenu.tsx index 7f4b3ca..11030bd 100644 --- a/frontend/components/CollabPage/FabComponent/FabMenu.tsx +++ b/frontend/components/CollabPage/FabComponent/FabMenu.tsx @@ -17,10 +17,6 @@ const FabMenu = ({ stopwatchProps, username1, username2 }: FabMenuProps) => { const open = Boolean(anchorEl); const [callActive, setCallActive] = useState(true); - const toggleVideo = () => { - setCallActive(!callActive); - }; - const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; @@ -31,6 +27,7 @@ const FabMenu = ({ stopwatchProps, username1, username2 }: FabMenuProps) => { }; const handleClickStopwatch = () => { + setStopwatchOpen(true); setAnchorEl(null); }; @@ -38,6 +35,13 @@ const FabMenu = ({ stopwatchProps, username1, username2 }: FabMenuProps) => { setAnchorEl(null); }; + const handleCloseStopwatch = () => { + setStopwatchOpen(false); + }; + + stopwatchProps.setIsOpen = setStopwatchOpen; + stopwatchProps.isOpen = stopwatchOpen; + return (
{ callActive={callActive} setCallActive={setCallActive} /> +
); }; diff --git a/frontend/components/CollabPage/FabComponent/Stopwatch.tsx b/frontend/components/CollabPage/FabComponent/Stopwatch.tsx index 60c198b..69f7aa5 100644 --- a/frontend/components/CollabPage/FabComponent/Stopwatch.tsx +++ b/frontend/components/CollabPage/FabComponent/Stopwatch.tsx @@ -5,12 +5,13 @@ import { Typography, IconButton, Grid, - Tooltip + Tooltip, } from "@mui/material"; +import Draggable from "react-draggable"; import PlayCircleFilledIcon from "@mui/icons-material/PlayCircleFilled"; import PauseCircleFilledIcon from "@mui/icons-material/PauseCircleFilled"; +import CloseIcon from "@mui/icons-material/Close"; import RestoreIcon from "@mui/icons-material/Restore"; -import { send } from "process"; export interface StopwatchProps { isRunning: boolean; @@ -19,6 +20,8 @@ export interface StopwatchProps { sendStartRequest: () => void; sendStopRequest: () => void; sendResetRequest: () => void; + setIsOpen: (isOpen: boolean) => void; + isOpen: boolean; } const Stopwatch = ({ @@ -27,7 +30,9 @@ const Stopwatch = ({ setIsReset, sendStartRequest, sendStopRequest, - sendResetRequest }: StopwatchProps) => { + sendResetRequest, + setIsOpen, + isOpen }: StopwatchProps) => { const [time, setTime] = useState(0.0); const [isActive, setIsActive] = useState(false); const intervalRef = useRef(null); @@ -61,6 +66,10 @@ const Stopwatch = ({ sendResetRequest(); }; + const onClose = () => { + setIsOpen(false); + }; + useEffect(() => { if (isRunning) { setIsActive(true); @@ -89,21 +98,48 @@ const Stopwatch = ({ }, [isActive]); return ( - <> - - {formatTime()} - - + + + {/* Close button at the top right corner */} + - - - + > + + + + + + {formatTime()} + + + + + + + ); }; From 2ff284266e186b848d6b9d58ef22a2f4d5a9ab41 Mon Sep 17 00:00:00 2001 From: Tang Bo Kuan Date: Wed, 8 Nov 2023 05:24:47 +0800 Subject: [PATCH 4/6] Change close to minimize --- frontend/components/CollabPage/FabComponent/Stopwatch.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/CollabPage/FabComponent/Stopwatch.tsx b/frontend/components/CollabPage/FabComponent/Stopwatch.tsx index 69f7aa5..5f529e4 100644 --- a/frontend/components/CollabPage/FabComponent/Stopwatch.tsx +++ b/frontend/components/CollabPage/FabComponent/Stopwatch.tsx @@ -10,7 +10,7 @@ import { import Draggable from "react-draggable"; import PlayCircleFilledIcon from "@mui/icons-material/PlayCircleFilled"; import PauseCircleFilledIcon from "@mui/icons-material/PauseCircleFilled"; -import CloseIcon from "@mui/icons-material/Close"; +import MinimizeIcon from '@mui/icons-material/Minimize'; import RestoreIcon from "@mui/icons-material/Restore"; export interface StopwatchProps { @@ -121,7 +121,7 @@ const Stopwatch = ({ }} > - + From b8a4ce738767d220bf0a431a3b3eaf3a4f1a8d65 Mon Sep 17 00:00:00 2001 From: Tang Bo Kuan Date: Wed, 8 Nov 2023 12:37:13 +0800 Subject: [PATCH 5/6] Resolve conflicts --- frontend/components/CollabPage/CollabPage.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frontend/components/CollabPage/CollabPage.tsx b/frontend/components/CollabPage/CollabPage.tsx index 4452c23..836fe29 100644 --- a/frontend/components/CollabPage/CollabPage.tsx +++ b/frontend/components/CollabPage/CollabPage.tsx @@ -38,11 +38,6 @@ const CollabPage = () => { useState(false); const [showInterviewerView, setShowInterviewerView] = useState(false); const [showDialog, setShowDialog] = useState(true); -<<<<<<< HEAD - const [snackBarIsOpen, setSnackBarIsOpen] = useState(false); -======= - const [callActive, setCallActive] = useState(true); ->>>>>>> 2e67647073b954455cc0e106e26ca745fd1cab07 const user1socket = roomId.split("*-*")[0]; const user2socket = roomId.split("*-*")[1]; const [isEndingSession, setIsEndingSession] = useState(false); // If this is true, end session procedure starts (see useEffect) From 9348d141db39a56937a08e665c1147b08e158874 Mon Sep 17 00:00:00 2001 From: Tang Bo Kuan Date: Wed, 8 Nov 2023 12:50:56 +0800 Subject: [PATCH 6/6] Merge changes and resolve conflicts --- .../CollabPage/FabComponent/FabMenu.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/frontend/components/CollabPage/FabComponent/FabMenu.tsx b/frontend/components/CollabPage/FabComponent/FabMenu.tsx index 11030bd..105b977 100644 --- a/frontend/components/CollabPage/FabComponent/FabMenu.tsx +++ b/frontend/components/CollabPage/FabComponent/FabMenu.tsx @@ -1,9 +1,10 @@ -import React, { useState } from 'react'; -import { Fab, Menu, MenuItem } from '@mui/material'; -import MenuIcon from '@mui/icons-material/Menu'; +import React, { useState } from "react"; +import { Fab, Menu, MenuItem } from "@mui/material"; +import MenuIcon from "@mui/icons-material/Menu"; import { StopwatchProps } from "./Stopwatch"; -import Stopwatch from './Stopwatch'; -import VideoAudioChat from '../VideoComm'; +import Stopwatch from "./Stopwatch"; +import dynamic from "next/dynamic"; +const VideoAudioChat = dynamic(() => import("../VideoComm"), { ssr: false }); interface FabMenuProps { stopwatchProps: StopwatchProps; @@ -61,12 +62,12 @@ const FabMenu = ({ stopwatchProps, username1, username2 }: FabMenuProps) => { open={open} onClose={handleClose} anchorOrigin={{ - vertical: 'top', - horizontal: 'right', + vertical: "top", + horizontal: "right", }} transformOrigin={{ - vertical: 'top', - horizontal: 'left', + vertical: "top", + horizontal: "left", }} > Video