diff --git a/frontend/src/components/Header/components/HeaderInstructorControls.tsx b/frontend/src/components/Header/components/HeaderInstructorControls.tsx index ed95911..b504ac0 100644 --- a/frontend/src/components/Header/components/HeaderInstructorControls.tsx +++ b/frontend/src/components/Header/components/HeaderInstructorControls.tsx @@ -75,17 +75,16 @@ const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsPr axios .get(`${import.meta.env.VITE_API_SERVER_URL}/lecture?code=${roomid}`) .then((result) => { - //현재 강의 생성이 잘 되지 않아서 보류 - console.log(result.data); - //setTitle(result.data.title); + const lecureTitle = result.data.title; + console.log(lecureTitle); + // 이후 작업 내일 예정 }) .catch(() => { - showToast({ message: "존재하지 않는 강의실입니다.", type: "alert" }); - //navigate("/"); + // showToast({ message: "존재하지 않는 강의실입니다.", type: "alert" }); + // navigate("/"); }); setLectureCode(roomid); window.addEventListener("popstate", handlePopstate); - console.log("Strart"); }, []); useEffect(() => { inputMicVolumeRef.current = inputMicVolume; diff --git a/frontend/src/components/Header/components/HeaderParticipantControls.tsx b/frontend/src/components/Header/components/HeaderParticipantControls.tsx index 12b8b18..9bbed88 100644 --- a/frontend/src/components/Header/components/HeaderParticipantControls.tsx +++ b/frontend/src/components/Header/components/HeaderParticipantControls.tsx @@ -323,7 +323,7 @@ const HeaderParticipantControls = ({ setLectureCode }: HeaderParticipantControls isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} /> - + ); }; diff --git a/frontend/src/components/LogContainer/LogContainer.tsx b/frontend/src/components/LogContainer/LogContainer.tsx index 44ce268..43fecf9 100644 --- a/frontend/src/components/LogContainer/LogContainer.tsx +++ b/frontend/src/components/LogContainer/LogContainer.tsx @@ -2,10 +2,9 @@ import SendMessage from "@/assets/svgs/sendMessage.svg?react"; import participantSocketRefState from "@/stores/stateParticipantSocketRef"; import { CSSProperties, useEffect, useLayoutEffect, useRef, useState } from "react"; -import { useRecoilState, useRecoilValue } from "recoil"; +import { 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"; @@ -21,6 +20,7 @@ interface LogItemInterface { interface LogContainerInterface { type: "question" | "prompt"; className: string; + scriptList?: Array<{ start: string; text: string }>; updateProgressMsTime?: (time: number) => void; } @@ -37,30 +37,20 @@ const LogItem = ({ title, contents, className, onClick, style }: LogItemInterfac ); }; -const LogContainer = ({ type, className, updateProgressMsTime }: LogContainerInterface) => { +const LogContainer = ({ type, className, scriptList, updateProgressMsTime }: LogContainerInterface) => { const [isInputEmpty, setIsInputEmpty] = useState(true); const [questionList, setQuestionList] = useState>([]); - const [scriptList, setScriptList] = useState>([]); const messageInputRef = useRef(null); const logContainerRef = useRef(null); - const [progressMsTime, setProgressMsTime] = useRecoilState(progressMsTimeState); + const progressMsTime = useRecoilValue(progressMsTimeState); const socket = useRecoilValue(participantSocketRefState); const [hilightedItemIndex, setHilightedItemIndex] = useState(0); const roomid = new URLSearchParams(useLocation().search).get("roomid") || "999999"; if (type === "prompt") { - useEffect(() => { - axios("./reviewLecture.json") - .then(({ data }) => { - setScriptList(data); - }) - .catch((error) => { - console.log("프롬프트에 표시할 스크립트 로딩 실패", error); - }); - }, []); - useLayoutEffect(() => { + if (!scriptList) return; let currentIndexOfPrompt = scriptList.findIndex((value) => { const startTime = Math.floor(+value.start / 1000) * 1000; @@ -132,7 +122,7 @@ const LogContainer = ({ type, className, updateProgressMsTime }: LogContainerInt )} {type === "prompt" && (
    - {scriptList.map(({ start, text }, index) => { + {scriptList?.map(({ start, text }, index) => { return ( diff --git a/frontend/src/pages/Review/Review.tsx b/frontend/src/pages/Review/Review.tsx index 88c0c29..a667f12 100644 --- a/frontend/src/pages/Review/Review.tsx +++ b/frontend/src/pages/Review/Review.tsx @@ -1,33 +1,38 @@ import { useRecoilValue, useRecoilState } from "recoil"; import { useState, useEffect, useRef } from "react"; import { fabric } from "fabric"; -import { ICanvasData, loadCanvasData, updateCanvasSize } from "@/utils/fabricCanvasUtil"; -import progressMsTimeState from "@/stores/stateProgressMsTime"; - import axios from "axios"; +import { ICanvasData, loadCanvasData, updateCanvasSize } from "@/utils/fabricCanvasUtil"; + import CloseIcon from "@/assets/svgs/close.svg?react"; import ScriptIcon from "@/assets/svgs/whiteboard/script.svg?react"; +import progressMsTimeState from "@/stores/stateProgressMsTime"; import isQuestionLogOpenState from "@/stores/stateIsQuestionLogOpen"; import LogToggleButton from "@/components/Button/LogToggleButton"; import LogContainer from "@/components/LogContainer/LogContainer"; import Header from "@/components/Header/Header"; import ProgressBar from "./components/ProgressBar"; +import { useToast } from "@/components/Toast/useToast"; const Review = () => { + const [prograssBarState, setPrograssBarState] = useState<"disabled" | "playing" | "paused">("disabled"); const isQuestionLogOpen = useRecoilValue(isQuestionLogOpenState); const [progressMsTime, setProgressMsTime] = useRecoilState(progressMsTimeState); - const loadedDataRef = useRef(); + const showToast = useToast(); + const loadedDataRef = useRef(); + const scriptListRef = useRef>(); const onFrameIdRef = useRef(null); // 마이크 볼륨 측정 타이머 id const canvasContainerRef = useRef(null); const canvasRef = useRef(null); + const localAudioRef = useRef(null); let fabricCanvasRef = useRef(); let canvasCntRef = useRef(0); + let totalTimeRef = useRef(0); - const [prograssBarState, setPrograssBarState] = useState<"disabled" | "playing" | "paused">("disabled"); let startTime = Date.now(); let canvasData: ICanvasData = { canvasJSON: "", @@ -36,8 +41,6 @@ const Review = () => { width: 0, height: 0 }; - // 추후 해당 다시보기의 전체 플레이 타임을 받아올 수 있어야 할 것 같습니다. - let TOTAL_MS_TIME_OF_REVIEW = 200000; useEffect(() => { handleInitCanvas(); @@ -48,7 +51,6 @@ const Review = () => { window.addEventListener("resize", handleResize); }; }, []); - // 윈도우 리사이즈 이벤트 감지 const handleInitCanvas = () => { if (!canvasContainerRef.current || !canvasRef.current) return; @@ -74,15 +76,34 @@ const Review = () => { fabricCanvasRef.current = newCanvas; }; const handleLoadData = () => { + /* + 현재 /lecture/record/:id 에서 불러오는 임시 데이터의 canvasJSON 데이터가 + 실제 canvasJSON 데이터와 다르기 때문에 임시로 더미 데이터를 불러오도록 설정했습니다. + */ axios("./dummyCanvasData.json") .then(({ data }) => { - // 추후 해당 다시보기의 전체 플레이 타임을 받아올 수 있어야 할 것 같습니다. - TOTAL_MS_TIME_OF_REVIEW = 200000; loadedDataRef.current = data; setPrograssBarState("paused"); }) - .catch((error) => { - console.log("화이트보드 데이터 로딩 실패", error); + .catch(() => { + showToast({ message: "강의 데이터를 불러오는 데 실패했습니다.", type: "alert" }); + }); + + // 실제 데이터를 불러오는 코드 + axios + .get(`https://boarlog.shop/lecture/record/6576c9dfccd3e23a8e0fe473`) + .then((result) => { + // console.log(result.data); + // loadedDataRef.current = result.data.; + scriptListRef.current = result.data.subtitles; + localAudioRef.current!.src = result.data.audio_file; + localAudioRef.current!.addEventListener("loadedmetadata", () => { + totalTimeRef.current = localAudioRef.current!.duration * 1000; + }); + //setPrograssBarState("paused"); + }) + .catch(() => { + showToast({ message: "강의 데이터를 불러오는 데 실패했습니다.", type: "alert" }); }); }; const handleResize = () => { @@ -106,8 +127,12 @@ const Review = () => { }); canvasCntRef.current += 1; } - if (elapsedTime < TOTAL_MS_TIME_OF_REVIEW) onFrameIdRef.current = window.requestAnimationFrame(onFrame); - else console.log("다시보기 끝"); + if (elapsedTime < totalTimeRef.current) onFrameIdRef.current = window.requestAnimationFrame(onFrame); + else { + setPrograssBarState("paused"); + setProgressMsTime(0); + canvasCntRef.current = 0; + } }; const play = () => { @@ -123,10 +148,13 @@ const Review = () => { } onFrameIdRef.current = window.requestAnimationFrame(onFrame); + localAudioRef.current!.play(); setPrograssBarState("playing"); }; const pause = () => { if (onFrameIdRef.current) window.cancelAnimationFrame(onFrameIdRef.current); + + localAudioRef.current!.pause(); setPrograssBarState("paused"); }; @@ -144,11 +172,12 @@ const Review = () => { } } - return closestSmallerIndex; + return closestSmallerIndex >= 0 ? closestSmallerIndex : 0; }; // logContainer에서 프롬프트를 클릭하거나 프로그래스 바를 클릭했을 때 진행시간을 조정하는 함수입니다. const updateProgressMsTime = (newProgressMsTime: number) => { + setProgressMsTime(newProgressMsTime); const currentProgressBarState = prograssBarState; pause(); const newCanvasCntRef = findClosest(loadedDataRef.current!, newProgressMsTime); @@ -161,10 +190,12 @@ const Review = () => { canvasCntRef.current = newCanvasCntRef + 1; startTime = Date.now() - newProgressMsTime; + localAudioRef.current!.currentTime = newProgressMsTime / 1000; if (currentProgressBarState === "playing") { onFrameIdRef.current = window.requestAnimationFrame(onFrame); setPrograssBarState("playing"); + localAudioRef.current!.play(); } }; @@ -176,6 +207,7 @@ const Review = () => { @@ -183,13 +215,14 @@ const Review = () => { + ); }; diff --git a/frontend/src/pages/Review/components/ProgressBar.tsx b/frontend/src/pages/Review/components/ProgressBar.tsx index 8d910bc..1b20a87 100644 --- a/frontend/src/pages/Review/components/ProgressBar.tsx +++ b/frontend/src/pages/Review/components/ProgressBar.tsx @@ -1,8 +1,8 @@ import PlayIcon from "@/assets/svgs/progressPlay.svg?react"; import PauseIcon from "@/assets/svgs/progressPause.svg?react"; -import { useRecoilState } from "recoil"; -import { useEffect, useRef, useState } from "react"; +import { useRecoilValue } from "recoil"; +import { useEffect, useRef } from "react"; import { convertMsToTimeString } from "@/utils/convertMsToTimeString"; import progressMsTimeState from "@/stores/stateProgressMsTime"; @@ -39,41 +39,18 @@ const ProgressBar = ({ pause, updateProgressMsTime }: ProgressBarProps) => { - const [isPlaying, setIsPlaying] = useState(false); - const [progressMsTime, setProgressMsTime] = useRecoilState(progressMsTimeState); + const progressMsTime = useRecoilValue(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)); updateProgressMsTime(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) { - prograssBarState = "disabled"; - setProgressMsTime(totalTime); - setIsPlaying(false); clearInterval(timerRef.current); } }, [progressMsTime]); @@ -95,7 +72,7 @@ const ProgressBar = ({ ) : prograssBarState === "playing" ? ( ) : ( - = totalTime && "gray"}`} /> + )}