-
Notifications
You must be signed in to change notification settings - Fork 0
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
Changes from all commits
c9982ca
8edab5b
f1ab05a
9229abe
ba390ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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까지 높이는 공사를 진행하고 있지만 근본적인 해결책이 되기 어렵습니다." | ||
} | ||
] |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
@@ -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> | ||
|
@@ -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); | ||
}} | ||
/> | ||
|
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; |
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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍👍👍👍👍