Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 한글 질문 중첩 전송 문제 해결 #211 #213

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions frontend/public/reviewLecture.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{
"start": 1480,
"text": "울산 중구의 야구장입니다. 울산 전국 체전을 앞두고 관할 지자체가 73억 원을 투입해 만든 공인규격 야구장입니다."
},
{
"start": 9825,
"text": "그런데 지난해 10월 전국체전 고등부 경기 당시 파울공이 야구장 밖으로 날아가 차량을 파손시키는 사고가 발"
},
{
"start": 20770,
"text": "비슷한 사고가 4차례나 더 있었습니다. 앞서 시범 경기 기간에도 파월 공으로 인한 차량 파손 사고가 30여 차례나 발생했습니다."
},
{
"start": 32930,
"text": "당초 이 야구장은 생활체육인을 위한 시설로 설계됐다가 전국 체전을 앞두고 공인규격 야구장으로 바뀌었습니다."
},
{
"start": 40485,
"text": "그런데 타자석 위치를 도로와 인접한 곳으로 잡았습니다. 보통 공인규격의 야구장은 안전사고를 막기 위해 도로와 먼 곳에"
},
{
"start": 49790,
"text": "타자석에 위치해 있습니다."
},
{
"start": 57840,
"text": "가 제기되고 있습니다. 기존 15m의 안전벨트를 20m까지 높이는 공사를 진행하고 있지만 근본적인 해결책이 되기 어렵습니다."
}
]
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Login from "./pages/Login/Login";
import Test from "./pages/Test/Test";
import Instructor from "./pages/Instructor/Instructor";
import Participant from "./pages/Participant/Participant";
import Review from "./pages/Review/Review";

import { RecoilRoot } from "recoil";
import Example from "./pages/Example/Example";
Expand All @@ -28,6 +29,7 @@ const App = () => {
<Route path="/test" element={<Test />} />
<Route path="/instructor" element={<Instructor />} />
<Route path="/participant" element={<Participant />} />
<Route path="/review" element={<Review />} />
<Route path="/lecture-end" element={<LectureEnd />} />
<Route path="/example" element={<Example />} />
<Route path="/error" element={<ErrorPage />} />
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/assets/svgs/pause.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions frontend/src/assets/svgs/play.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frontend/src/assets/svgs/whiteboard/script.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import CloseIcon from "@/assets/svgs/close.svg?react";
import QuestionIcon from "@/assets/svgs/whiteboard/question.svg?react";
import isQuestionLogOpenState from "@/stores/stateIsQuestionLogOpen";

import { useRecoilState } from "recoil";

const QuestionLogButton = ({ className }: { className?: string }) => {
const LogToggleButton = ({ className, children }: { className?: string; children: React.ReactNode }) => {
const [isQuestionLogOpen, setIsQuestionLogOpen] = useRecoilState(isQuestionLogOpenState);

return (
Expand All @@ -12,16 +11,14 @@ const QuestionLogButton = ({ className }: { className?: string }) => {
className={`${className} w-12 h-12 flex justify-center items-center rounded-xl mb-3 ${
isQuestionLogOpen ? "transparent" : "bg-grayscale-lightgray shadow-md"
}`}
aria-label="질문 리스트"
aria-label="채팅, 프롬프트"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍👍👍👍

aria-pressed={isQuestionLogOpen}
onClick={() => {
setIsQuestionLogOpen(!isQuestionLogOpen);
}}
>
<span className={`semibold-20 + ${isQuestionLogOpen ? "text-white" : "text-grayscale-black"}`}>
{isQuestionLogOpen ? <CloseIcon /> : <QuestionIcon fill="black" />}
</span>
{children}
</button>
);
};
export default QuestionLogButton;
export default LogToggleButton;
14 changes: 13 additions & 1 deletion frontend/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import HeaderSettingButton from "./components/HeaderSettingButton";
import HeaderCodeCopyButton from "./components/HeaderCodeCopyButton";

interface HeaderProps {
type: "login" | "main" | "instructor" | "participant";
type: "login" | "main" | "instructor" | "participant" | "review";
}

const Header = ({ type }: HeaderProps) => {
Expand All @@ -26,6 +26,7 @@ const Header = ({ type }: HeaderProps) => {
<HeaderCodeCopyButton lectureCode="000000" />
</>
)}
{type === "review" && <HeaderLogo type="lecture" />}
</div>

<div className="flex items-center gap-4 semibold-20">
Expand Down Expand Up @@ -58,6 +59,17 @@ const Header = ({ type }: HeaderProps) => {
<HeaderProfileButton isProfileClicked={isProfileClicked} setIsProfileClicked={setIsProfileClicked} />
</>
)}
{type === "review" && (
<>
<HeaderParticipantControls />
<HeaderSettingButton
isSettingClicked={isSettingClicked}
setIsSettingClicked={setIsSettingClicked}
type={type}
/>
<HeaderProfileButton isProfileClicked={isProfileClicked} setIsProfileClicked={setIsProfileClicked} />
</>
)}
</div>
</header>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import HeaderParticipantSettingModal from "./HeaderParticipantSettingModal";
interface HeaderSettingButtonProps {
isSettingClicked: boolean;
setIsSettingClicked: React.Dispatch<React.SetStateAction<boolean>>;
type: "login" | "main" | "instructor" | "participant";
type: "login" | "main" | "instructor" | "participant" | "review";
}

const HeaderSettingButton = ({ isSettingClicked, setIsSettingClicked, type }: HeaderSettingButtonProps) => {
Expand All @@ -22,7 +22,7 @@ const HeaderSettingButton = ({ isSettingClicked, setIsSettingClicked, type }: He
{type === "instructor" && (
<HeaderInstructorSettingModal isSettingClicked={isSettingClicked} setIsSettingClicked={setIsSettingClicked} />
)}
{type === "participant" && (
{(type === "participant" || type === "review") && (
<HeaderParticipantSettingModal isSettingClicked={isSettingClicked} setIsSettingClicked={setIsSettingClicked} />
)}
</>
Expand Down
68 changes: 52 additions & 16 deletions frontend/src/components/LogContainer/LogContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,65 @@
import SendMessage from "@/assets/svgs/sendMessage.svg?react";
import participantSocketRefState from "@/stores/stateParticipantSocketRef";

import { ChangeEvent, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useRecoilValue } from "recoil";
import { useLocation } from "react-router-dom";
import axios from "axios";

interface LogItemInterface {
key?: string;
title: string;
contents: string;
}

interface LogContainerInterface {
type: "question" | "prompt";
className: string;
}

const LogItem = ({ title, contents }: LogItemInterface) => {
return (
<li className="h-21 p-4 border mt-4 first-of-type:mt-0 bg-grayscale-white border-grayscale-lightgray rounded-lg">
<li className="h-21 p-4 border mt-4 mb-2 first-of-type:mt-0 bg-grayscale-white border-grayscale-lightgray rounded-lg">
<p className="semibold-16">{title}</p>
<p className="mt-2 medium-12 text-grayscale-darkgray">{contents}</p>
</li>
);
};

interface LogContainerInterface {
type: "question" | "prompt";
className: string;
function convertMsToTimeString(ms: string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 function도 재활용될 수 있다면, util로 빼보는건 어떨까 싶어요! 나중에 개선 과정에서 같이 논의해봐요!

let msNumber = parseInt(ms);
let seconds = Math.floor(msNumber / 1000);
let hours = Math.floor(seconds / 3600);
seconds = seconds % 3600;
Comment on lines +31 to +33
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

가능하다면 magic number 따로 선언해주시면 좋을 것 같습니다!


let minutes = Math.floor(seconds / 60);
seconds = seconds % 60;

return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds
.toString()
.padStart(2, "0")}`;
}

const LogContainer = ({ type, className }: LogContainerInterface) => {
const [isInputEmpty, setIsInputEmpty] = useState<boolean>(true);
const [questionList, setQuestionList] = useState<Array<{ title: string; contents: string }>>([]);
const [scriptList, setScriptList] = useState<Array<{ start: string; text: string }>>([]);
const messageInputRef = useRef<HTMLInputElement | null>(null);
const socket = useRecoilValue(participantSocketRefState);
const roomid = new URLSearchParams(useLocation().search).get("roomid") || "999999";

const handleInputChange = ({ target }: ChangeEvent<HTMLInputElement>) => {
useEffect(() => {
axios("./reviewLecture.json")
.then(({ data }) => {
// @ts-ignore
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

흑흑... ts 미지원으로 고생 많으십니다... 이 부분도 다음주에 같이 해결해봐요!

setScriptList(data);
})
.catch((error) => {
console.log("프롬프트에 표시할 스크립트 로딩 실패", error);
});
}, []);

const handleInputChange = () => {
if (!messageInputRef.current) return;
const inputValue = messageInputRef.current.value;
setIsInputEmpty(inputValue.replace(/\n|\s/g, "").length === 0);
Expand Down Expand Up @@ -70,18 +97,27 @@ const LogContainer = ({ type, className }: LogContainerInterface) => {

return (
<section
className={`flex flex-col ${className} w-72 h-[calc(100vh-12rem)] bg-grayscale-white border border-grayscale-lightgray rounded-xl`}
className={`flex flex-col pb-4 ${className} w-72 h-[calc(100vh-12rem)] bg-grayscale-white border border-grayscale-lightgray rounded-xl`}
>
<h2 className="mt-2 h-14 semibold-18 leading-10 p-4 flex items-center">
{type === "question" ? "질문하기" : "강의 프롬프트"}
</h2>
<ul className="px-4 flex-grow overflow-y-auto ">
{questionList.map(({ title, contents }, index) => {
return <LogItem key={`k-${index}`} title={title} contents={contents} />;
})}
</ul>
{type === "question" && (
<div className="flex justify-between p-4">
<ul className="px-4 flex-grow overflow-y-auto ">
{questionList.map(({ title, contents }, index) => {
return <LogItem key={`k-${index}`} title={title} contents={contents} />;
})}
</ul>
)}
{type === "prompt" && (
<ul className="px-4 flex-grow overflow-y-auto ">
{scriptList.map(({ start, text }, index) => {
return <LogItem key={`k-${index}`} title={convertMsToTimeString(start)} contents={text} />;
})}
</ul>
)}
{type === "question" && (
<div className="flex justify-between px-4 pt-4">
<label htmlFor="question-input" className="a11y-hidden">
질문 입력 창
</label>
Expand All @@ -91,10 +127,10 @@ const LogContainer = ({ type, className }: LogContainerInterface) => {
className="w-52 medium-12 border border-boarlog rounded-lg px-3 py-2"
placeholder="질문을 입력해 주세요."
ref={messageInputRef}
onChange={(event) => {
handleInputChange(event);
onChange={() => {
handleInputChange();
}}
onKeyDown={(event) => {
onKeyUp={(event) => {
handleInputEnter(event);
}}
/>
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/pages/Participant/Participant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Header from "@/components/Header/Header";
import participantCavasInstanceState from "@/stores/stateParticipantCanvasInstance";
import QuestionLogButton from "./components/QuestionLogButton";
import LogContainer from "@/components/LogContainer/LogContainer";

import isQuestionLogOpenState from "@/stores/stateIsQuestionLogOpen";

const Participant = () => {
Expand Down Expand Up @@ -54,7 +55,9 @@ const Participant = () => {
type="question"
className={`absolute top-2.5 right-2.5 ${isQuestionLogOpen ? "block" : "hidden"}`}
/>
<QuestionLogButton className="absolute top-2.5 right-2.5" />
<LogToggleButton className="absolute top-2.5 right-2.5">
{isQuestionLogOpen ? <CloseIcon /> : <QuestionIcon fill="black" />}
</LogToggleButton>
</section>
</>
);
Expand Down
42 changes: 42 additions & 0 deletions frontend/src/pages/Review/Review.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useSetRecoilState, useRecoilValue } from "recoil";
import { useEffect, useRef } from "react";

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 Review = () => {
const setVideoRef = useSetRecoilState(videoRefState);
const videoRef = useRef<HTMLVideoElement>(null);
const isQuestionLogOpen = useRecoilValue(isQuestionLogOpenState);

useEffect(() => {
setVideoRef(videoRef);
}, []);

return (
<>
<Header type="review" />
<section className="relative">
<video className="w-[100vw] h-[calc(100vh-5rem)]" autoPlay muted ref={videoRef}></video>
<LogContainer
type="prompt"
className={`absolute top-2.5 right-2.5 ${isQuestionLogOpen ? "block" : "hidden"}`}
/>
<LogToggleButton className="absolute top-2.5 right-2.5">
{isQuestionLogOpen ? <CloseIcon /> : <ScriptIcon fill="black" />}
</LogToggleButton>
<ProgressBar className="absolute bottom-2.5 left-1/2 -translate-x-1/2" />
</section>
</>
);
};

export default Review;
28 changes: 28 additions & 0 deletions frontend/src/pages/Review/components/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import PlayIcon from "@/assets/svgs/play.svg?react";
import PauseIcon from "@/assets/svgs/pause.svg?react";
import { useState } from "react";

const ProgressBar = ({ className }: { className: string }) => {
const [isPlaying, setIsPlaying] = useState(false);
return (
<div
className={`${className} flex gap-4 justify-between items-center w-[70vw] h-12 min-w-[400px] rounded-lg border border-grayscale-lightgray shadow-md p-4`}
>
<button
type="button"
className="medium-12 w-8 p-2"
onClick={() => {
setIsPlaying(!isPlaying);
}}
>
{isPlaying ? <PlayIcon /> : <PauseIcon />}
</button>
<div className="relative grow h-1 bg-grayscale-lightgray">
<div className="absolute top-0 left-0 h-1 w-[50%] bg-boarlog-100"></div>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

50%라면 w-1/2로 작성할 수 있어요!

</div>
<span className="medium-12">00:00:00</span>
</div>
);
};

export default ProgressBar;