diff --git a/frontend/public/reviewLecture.json b/frontend/public/reviewLecture.json index 77f95e3..584a652 100644 --- a/frontend/public/reviewLecture.json +++ b/frontend/public/reviewLecture.json @@ -24,7 +24,7 @@ "text": "타자석에 위치해 있습니다." }, { - "start": 57840, + "start": 157840, "text": "가 제기되고 있습니다. 기존 15m의 안전벨트를 20m까지 높이는 공사를 진행하고 있지만 근본적인 해결책이 되기 어렵습니다." } ] diff --git a/frontend/src/assets/svgs/progressPlay.svg b/frontend/src/assets/svgs/progressPlay.svg index a4d1996..3978c15 100644 --- a/frontend/src/assets/svgs/progressPlay.svg +++ b/frontend/src/assets/svgs/progressPlay.svg @@ -1,3 +1,3 @@ - - + + diff --git a/frontend/src/components/LogContainer/LogContainer.tsx b/frontend/src/components/LogContainer/LogContainer.tsx index 3f9efdc..bf6e27d 100644 --- a/frontend/src/components/LogContainer/LogContainer.tsx +++ b/frontend/src/components/LogContainer/LogContainer.tsx @@ -1,16 +1,21 @@ import SendMessage from "@/assets/svgs/sendMessage.svg?react"; import participantSocketRefState from "@/stores/stateParticipantSocketRef"; -import { useEffect, useRef, useState } from "react"; -import { useRecoilValue } from "recoil"; +import { CSSProperties, useEffect, useLayoutEffect, useRef, useState } from "react"; +import { useRecoilState, useRecoilValue } from "recoil"; import { useLocation } from "react-router-dom"; import { convertMsToTimeString } from "@/utils/convertMsToTimeString"; import axios from "axios"; +import progressMsTimeState from "@/stores/stateProgressMsTime"; +import convertTimeStringToMS from "@/utils/converTimeStringToMS"; interface LogItemInterface { key?: string; title: string; contents: string; + className?: string; + onClick?: any; + style?: CSSProperties; } interface LogContainerInterface { @@ -18,9 +23,13 @@ interface LogContainerInterface { className: string; } -const LogItem = ({ title, contents }: LogItemInterface) => { +const LogItem = ({ title, contents, className, onClick, style }: LogItemInterface) => { return ( -
  • +
  • {title}

    {contents}

  • @@ -33,7 +42,10 @@ const LogContainer = ({ type, className }: LogContainerInterface) => { const [scriptList, setScriptList] = useState>([]); const messageInputRef = useRef(null); const logContainerRef = useRef(null); + const [progressMsTime, setProgressMsTime] = useRecoilState(progressMsTimeState); const socket = useRecoilValue(participantSocketRefState); + const [hilightedItemIndex, setHilightedItemIndex] = useState(0); + const roomid = new URLSearchParams(useLocation().search).get("roomid") || "999999"; if (type === "prompt") { @@ -46,6 +58,20 @@ const LogContainer = ({ type, className }: LogContainerInterface) => { console.log("프롬프트에 표시할 스크립트 로딩 실패", error); }); }, []); + + useLayoutEffect(() => { + let currentIndexOfPrompt = + scriptList.findIndex((value) => { + const startTime = Math.floor(+value.start / 1000) * 1000; + + return startTime > progressMsTime; + }) - 1; + const lastStartTime = +scriptList[scriptList.length - 1]?.start; + if (Math.floor(lastStartTime / 1000) * 1000 <= progressMsTime) { + currentIndexOfPrompt = scriptList.length - 1; + } else if (currentIndexOfPrompt < 0) setHilightedItemIndex(0); + setHilightedItemIndex(currentIndexOfPrompt); + }, [progressMsTime]); } else { useEffect(() => { if (!logContainerRef.current) return; @@ -75,7 +101,7 @@ const LogContainer = ({ type, className }: LogContainerInterface) => { // 추후 사용자의 닉네임을 가져와야한다. setQuestionList([...questionList, { title: "닉네임", contents: messageContents }]); - socket.emit("ask", { + socket?.emit("ask", { type: "question", roomId: roomid, content: messageContents @@ -104,9 +130,23 @@ const LogContainer = ({ type, className }: LogContainerInterface) => { )} {type === "prompt" && ( -
      +
        {scriptList.map(({ start, text }, index) => { - return ; + return ( + { + const currentTarget = event.currentTarget as HTMLLIElement; + if (!currentTarget.children[0].textContent) return; + convertTimeStringToMS(currentTarget.children[0].textContent); + setProgressMsTime(convertTimeStringToMS(currentTarget.children[0].textContent)); + }} + /> + ); })}
      )} diff --git a/frontend/src/pages/Review/Review.tsx b/frontend/src/pages/Review/Review.tsx index e8a2882..4ad650f 100644 --- a/frontend/src/pages/Review/Review.tsx +++ b/frontend/src/pages/Review/Review.tsx @@ -1,31 +1,60 @@ -import { useSetRecoilState, useRecoilValue } from "recoil"; +import { useRecoilValue } from "recoil"; import { useEffect, useRef } from "react"; +import { fabric } from "fabric"; import CloseIcon from "@/assets/svgs/close.svg?react"; import ScriptIcon from "@/assets/svgs/whiteboard/script.svg?react"; import isQuestionLogOpenState from "@/stores/stateIsQuestionLogOpen"; -import videoRefState from "../Test/components/stateVideoRef"; import LogToggleButton from "@/components/Button/LogToggleButton"; import LogContainer from "@/components/LogContainer/LogContainer"; import Header from "@/components/Header/Header"; import ProgressBar from "./components/ProgressBar"; +// 추후 해당 다시보기의 전체 플레이 타임을 받아올 수 있어야 할 것 같습니다. +const TOTAL_MS_TIME_OF_REVIEW = 200000; + const Review = () => { - const setVideoRef = useSetRecoilState(videoRefState); - const videoRef = useRef(null); + const canvasRef = useRef(null); + const canvasContainerRef = useRef(null); const isQuestionLogOpen = useRecoilValue(isQuestionLogOpenState); useEffect(() => { - setVideoRef(videoRef); + if (!canvasContainerRef.current || !canvasRef.current) return; + + const canvasContainer = canvasContainerRef.current; + // 캔버스 생성 + const newCanvas = new fabric.Canvas(canvasRef.current, { + width: canvasContainer.offsetWidth, + height: canvasContainer.offsetHeight, + selection: false + }); + newCanvas.backgroundColor = "lightgray"; + newCanvas.defaultCursor = "default"; + + var text = new fabric.Text("화이트보드를 불러오고 있습니다", { + fontSize: 18, + textAlign: "center", + originX: "center", + originY: "center", + left: canvasContainer.offsetWidth / 2, + top: canvasContainer.offsetHeight / 2, + selectable: false + }); + newCanvas.add(text); + + // 언마운트 시 캔버스 정리 + return () => { + newCanvas.dispose(); + }; }, []); return ( <>
      -
      - +
      + { {isQuestionLogOpen ? : } - +
      ); diff --git a/frontend/src/pages/Review/components/ProgressBar.tsx b/frontend/src/pages/Review/components/ProgressBar.tsx index f25c78e..8c3bb96 100644 --- a/frontend/src/pages/Review/components/ProgressBar.tsx +++ b/frontend/src/pages/Review/components/ProgressBar.tsx @@ -1,12 +1,68 @@ import PlayIcon from "@/assets/svgs/progressPlay.svg?react"; import PauseIcon from "@/assets/svgs/progressPause.svg?react"; -import { useState } from "react"; -const ProgressBar = ({ className }: { className: string }) => { +import { useRecoilState } from "recoil"; +import { useEffect, useRef, useState } from "react"; +import { convertMsToTimeString } from "@/utils/convertMsToTimeString"; + +import progressMsTimeState from "@/stores/stateProgressMsTime"; + +const getPercentOfProgress = (progressTime: number, totalTime: number) => { + const percent = (progressTime / totalTime) * 100; + let result; + + if (percent < 0) { + result = 0; + } else if (percent > 100) { + result = 100; + } else { + result = percent; + } + + return result.toFixed(1) + "%"; +}; + +const ProgressBar = ({ className, totalTime }: { className: string; totalTime: number }) => { const [isPlaying, setIsPlaying] = useState(false); + const [progressMsTime, setProgressMsTime] = useRecoilState(progressMsTimeState); + const timerRef = useRef(); + const lastUpdatedTime = useRef(); + const UPDATE_INTERVAL_MS = 150; + + const handleProgressBarMouseDown = (event: React.MouseEvent) => { + const { left, width } = event.currentTarget.getBoundingClientRect(); + const mouseClickedX = event.clientX; + const percent = (mouseClickedX - left) / width; + setProgressMsTime(Math.round(totalTime * percent)); + }; + + useEffect(() => { + if (isPlaying) { + lastUpdatedTime.current = new Date().getTime(); + + timerRef.current = setInterval(() => { + const dateNow = new Date().getTime(); + const diffTime = dateNow - lastUpdatedTime.current; + setProgressMsTime((progressMsTime) => progressMsTime + diffTime); + + lastUpdatedTime.current = dateNow; + }, UPDATE_INTERVAL_MS); + } else { + clearInterval(timerRef.current); + } + }, [isPlaying]); + + useEffect(() => { + if (progressMsTime >= totalTime) { + setProgressMsTime(totalTime); + setIsPlaying(false); + clearInterval(timerRef.current); + } + }, [progressMsTime]); + return (
      -
      -
      +
      { + handleProgressBarMouseDown(event); + }} + > +
      +
      +
      - 00:00:00 + {convertMsToTimeString(progressMsTime)}
      ); }; diff --git a/frontend/src/stores/stateProgressMsTime.ts b/frontend/src/stores/stateProgressMsTime.ts new file mode 100644 index 0000000..5e4d468 --- /dev/null +++ b/frontend/src/stores/stateProgressMsTime.ts @@ -0,0 +1,8 @@ +import { atom } from "recoil"; + +const progressMsTimeState = atom({ + key: "progressMsTimeState", + default: 0 +}); + +export default progressMsTimeState; diff --git a/frontend/src/utils/converTimeStringToMS.ts b/frontend/src/utils/converTimeStringToMS.ts new file mode 100644 index 0000000..2abdc8c --- /dev/null +++ b/frontend/src/utils/converTimeStringToMS.ts @@ -0,0 +1,12 @@ +const MS_OF_SECOND = 1000; +const MS_OF_MINUTE = 60 * MS_OF_SECOND; +const MS_OF_HOUR = 60 * MS_OF_MINUTE; + +const convertTimeStringToMS = (timeString: string) => { + const [hour, minute, second] = timeString.split(":"); + const result = parseInt(hour) * MS_OF_HOUR + parseInt(minute) * MS_OF_MINUTE + parseInt(second) * MS_OF_SECOND; + + return result; +}; + +export default convertTimeStringToMS; diff --git a/frontend/src/utils/convertMsToTimeString.ts b/frontend/src/utils/convertMsToTimeString.ts index 014b750..bb018ff 100644 --- a/frontend/src/utils/convertMsToTimeString.ts +++ b/frontend/src/utils/convertMsToTimeString.ts @@ -2,8 +2,8 @@ const MS_OF_SECOND = 1000; const SECOND_OF_HOUR = 3600; const MINUTE_OF_HOUR = 60; -export const convertMsToTimeString = (ms: string) => { - let msNumber = parseInt(ms); +export const convertMsToTimeString = (ms: string | number) => { + let msNumber = typeof ms === "string" ? parseInt(ms) : ms; let seconds = Math.floor(msNumber / MS_OF_SECOND); let hours = Math.floor(seconds / SECOND_OF_HOUR); seconds = seconds % 3600;