From fa30123f987b7d806d973d77125a27e8f661ad9e Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 14:55:34 +0900 Subject: [PATCH 01/16] =?UTF-8?q?feat:=20commons/loading=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8,=20utils=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Common/Loading.tsx | 35 +++++++++++++++++++ .../Submission/SubmissionResult.tsx | 5 ++- frontend/src/pages/ContestPage.tsx | 4 ++- frontend/src/utils/date/index.ts | 13 +++++++ 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/Common/Loading.tsx diff --git a/frontend/src/components/Common/Loading.tsx b/frontend/src/components/Common/Loading.tsx new file mode 100644 index 0000000..cb74d11 --- /dev/null +++ b/frontend/src/components/Common/Loading.tsx @@ -0,0 +1,35 @@ +interface Props { + size: string; + color: string; +} + +export default function Loading({ size, color }: Props) { + return ( + + + + + + ); +} diff --git a/frontend/src/components/Submission/SubmissionResult.tsx b/frontend/src/components/Submission/SubmissionResult.tsx index a6d9fe6..7625471 100644 --- a/frontend/src/components/Submission/SubmissionResult.tsx +++ b/frontend/src/components/Submission/SubmissionResult.tsx @@ -5,11 +5,13 @@ import { useEffect, useState } from 'react'; import { range } from '@/utils/array'; import type { Socket } from '@/utils/socket'; +import ConnectHeader from './ConnectHeader'; import Score from './Score'; import { type Message, type ScoreResult, SUBMIT_STATE, type SubmitState } from './types'; interface Props { socket: Socket; + endsAt: string; } type SubmitResult = { @@ -18,7 +20,7 @@ type SubmitResult = { score?: ScoreResult; }; -export function SubmissionResult({ socket }: Props) { +export function SubmissionResult({ socket, endsAt }: Props) { const [scoreResults, setScoreResults] = useState([]); const [submissionMessage, setSubmissionMessage] = useState(''); @@ -71,6 +73,7 @@ export function SubmissionResult({ socket }: Props) { return ( <>
+

{submissionMessage}

{scoreResults.map(({ score, submitState, testcaseId }) => ( diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index 2e3bb3c..b75cd1e 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -89,6 +89,8 @@ export default function ContestPage() { submitSolution(form); } + const { endsAt } = competition; + return (
@@ -117,7 +119,7 @@ export default function ContestPage() {
- +
diff --git a/frontend/src/utils/date/index.ts b/frontend/src/utils/date/index.ts index 367eb7a..7f9e09e 100644 --- a/frontend/src/utils/date/index.ts +++ b/frontend/src/utils/date/index.ts @@ -1,5 +1,8 @@ const ONE_SEC_BY_MS = 1_000; const ONE_MIN_BY_MS = 60 * ONE_SEC_BY_MS; +const ONE_MIN_BY_SEC = 60; +const ONE_HOUR_BY_MIN = 60; +const ONE_HOUR_BY_SEC = ONE_HOUR_BY_MIN * ONE_MIN_BY_SEC; export function toLocalDate(date: Date) { const localTimeOffset = date.getTimezoneOffset() * ONE_MIN_BY_MS; @@ -15,3 +18,13 @@ export const formatDate = (date: Date, form: string) => { return ''; }; + +export const formatTimeFromMiliSeconds = (ms: number) => { + const sec = Math.floor(ms / ONE_SEC_BY_MS); + // 시간(초)을 'hh:mm:ss' 형식으로 변환 + const hours = Math.floor(sec / ONE_HOUR_BY_SEC); + const minutes = Math.floor((sec % ONE_HOUR_BY_SEC) / ONE_MIN_BY_SEC); + const seconds = sec % ONE_MIN_BY_SEC; + + return [hours, minutes, seconds].map((time) => String(time).padStart(2, '0')).join(':'); +}; From 8c69e740bcec4650ee42c0a1da6d89026e5f0211 Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 14:58:13 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat:=20=EC=97=B0=EA=B2=B0=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20componenet=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Submission/ConnectHeader.tsx | 58 +++++++++++++++++++ .../src/hooks/competition/useConnectHeader.ts | 54 +++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 frontend/src/components/Submission/ConnectHeader.tsx create mode 100644 frontend/src/hooks/competition/useConnectHeader.ts diff --git a/frontend/src/components/Submission/ConnectHeader.tsx b/frontend/src/components/Submission/ConnectHeader.tsx new file mode 100644 index 0000000..c02f4c6 --- /dev/null +++ b/frontend/src/components/Submission/ConnectHeader.tsx @@ -0,0 +1,58 @@ +import { css } from '@style/css'; + +import Loading from '@/components/Common/Loading'; +import useConnectHeader from '@/hooks/competition/useConnectHeader'; +import { formatTimeFromMiliSeconds } from '@/utils/date'; +import type { Socket } from '@/utils/socket'; + +interface Props { + socket: Socket; + isConnected: boolean; + endsAt: string; +} + +export default function ConnectHeader(props: Props) { + let { isConnected, socket, endsAt } = props; + // api 연결이 X endsAt 대신 임시로 만들어놓은 것. + endsAt = '2023-11-28T12:10:10.000Z'; + const { remainTime } = useConnectHeader({ socket, endsAt }); + + return ( +
+
+ {isConnected && remainTime !== -1 ? ( + {formatTimeFromMiliSeconds(remainTime)} + ) : ( +
+ 연결 중... + +
+ )} +
+
+ ); +} + +const headerStyle = css({ + position: 'relative;', +}); + +const disConnectedStyle = css({ + color: 'darkred', +}); + +const loadingBoxStyle = css({ + display: 'flex', + gap: '1rem', +}); + +const positionRightStyle = css({ + display: 'flex', + position: 'absolute;', + right: '0px', +}); + +const timeStyle = css({ + color: 'lightgray', + fontWeight: 'bold', +}); diff --git a/frontend/src/hooks/competition/useConnectHeader.ts b/frontend/src/hooks/competition/useConnectHeader.ts new file mode 100644 index 0000000..3ff1fae --- /dev/null +++ b/frontend/src/hooks/competition/useConnectHeader.ts @@ -0,0 +1,54 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; + +import { Socket } from 'socket.io-client'; + +interface UseConnectHeader { + socket: Socket; + endsAt: string; +} + +let timerIntervalId: NodeJS.Timeout; + +export default function useConnectHeader({ socket, endsAt }: UseConnectHeader) { + const endTime = useMemo(() => new Date(endsAt).getTime(), [endsAt]); + const [remainTime, setRemainTime] = useState(-1); + useEffect(() => { + console.log('타이머 실행'); + // 웹 소켓 대신 사용. + mockWebSocket(); + if (!socket.hasListeners('ping')) { + socket.on('ping', handlePingMessage); + } + }, [socket]); + + const handlePingMessage = useCallback((time: Date | string) => { + clearInterval(timerIntervalId); + time = typeof time === 'string' ? new Date(time) : time; + const remainSec = endTime - time.getTime(); + setRemainTime(remainSec); + timerIntervalId = setInterval(() => { + console.log('1초마다 실행'); + setRemainTime((prev) => prev - 1000); + }, 1000); + }, []); + + // 웹 소켓 대신 사용. + // 웹 소켓 연결 후 삭제 예정 + const mockWebSocket = useCallback(() => { + const delayFactor = 2; + setInterval(() => { + console.log('ping 5초( + 네트워크 지연) 마다 실행'); + const serverTime = new Date(); + handlePingMessage(serverTime); + }, 5000 + Math.random() * delayFactor); + }, []); + + useEffect(() => { + // TODO time 0이면 대시보드로 이동하는 로직 + // 해당 PR에서 해결할 문제는 아니라 PASS + if (remainTime === 0) { + // 나가는 로직 + } + }, [remainTime]); + return { remainTime }; +} From f464989137409b0d5a51240155ebe3cbda8742eb Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 15:07:05 +0900 Subject: [PATCH 03/16] =?UTF-8?q?refactor:=20ConnectHeader=20->=20Timer?= =?UTF-8?q?=EB=A1=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Submission/SubmissionResult.tsx | 4 ++-- .../Submission/{ConnectHeader.tsx => Timer.tsx} | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) rename frontend/src/components/Submission/{ConnectHeader.tsx => Timer.tsx} (84%) diff --git a/frontend/src/components/Submission/SubmissionResult.tsx b/frontend/src/components/Submission/SubmissionResult.tsx index 7625471..1fc0edf 100644 --- a/frontend/src/components/Submission/SubmissionResult.tsx +++ b/frontend/src/components/Submission/SubmissionResult.tsx @@ -5,8 +5,8 @@ import { useEffect, useState } from 'react'; import { range } from '@/utils/array'; import type { Socket } from '@/utils/socket'; -import ConnectHeader from './ConnectHeader'; import Score from './Score'; +import Timer from './Timer'; import { type Message, type ScoreResult, SUBMIT_STATE, type SubmitState } from './types'; interface Props { @@ -73,7 +73,7 @@ export function SubmissionResult({ socket, endsAt }: Props) { return ( <>
- +

{submissionMessage}

{scoreResults.map(({ score, submitState, testcaseId }) => ( diff --git a/frontend/src/components/Submission/ConnectHeader.tsx b/frontend/src/components/Submission/Timer.tsx similarity index 84% rename from frontend/src/components/Submission/ConnectHeader.tsx rename to frontend/src/components/Submission/Timer.tsx index c02f4c6..1bc31af 100644 --- a/frontend/src/components/Submission/ConnectHeader.tsx +++ b/frontend/src/components/Submission/Timer.tsx @@ -11,17 +11,17 @@ interface Props { endsAt: string; } -export default function ConnectHeader(props: Props) { +export default function Time(props: Props) { let { isConnected, socket, endsAt } = props; // api 연결이 X endsAt 대신 임시로 만들어놓은 것. endsAt = '2023-11-28T12:10:10.000Z'; const { remainTime } = useConnectHeader({ socket, endsAt }); return ( -
+
{isConnected && remainTime !== -1 ? ( - {formatTimeFromMiliSeconds(remainTime)} + {formatTimeFromMiliSeconds(remainTime)} ) : (
연결 중... @@ -33,7 +33,7 @@ export default function ConnectHeader(props: Props) { ); } -const headerStyle = css({ +const wrapperStyle = css({ position: 'relative;', }); @@ -52,7 +52,7 @@ const positionRightStyle = css({ right: '0px', }); -const timeStyle = css({ +const timeTextStyle = css({ color: 'lightgray', fontWeight: 'bold', }); From d32691dbe5b9d80463f6fba1a8cdcd33086cb5d0 Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 15:30:51 +0900 Subject: [PATCH 04/16] =?UTF-8?q?refactor:=20hooks=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD,=20delay=202000=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Submission/Timer.tsx | 4 ++-- .../hooks/competition/{useConnectHeader.ts => useTimer.ts} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename frontend/src/hooks/competition/{useConnectHeader.ts => useTimer.ts} (98%) diff --git a/frontend/src/components/Submission/Timer.tsx b/frontend/src/components/Submission/Timer.tsx index 1bc31af..7692cb1 100644 --- a/frontend/src/components/Submission/Timer.tsx +++ b/frontend/src/components/Submission/Timer.tsx @@ -1,7 +1,7 @@ import { css } from '@style/css'; import Loading from '@/components/Common/Loading'; -import useConnectHeader from '@/hooks/competition/useConnectHeader'; +import useTimer from '@/hooks/competition/useTimer'; import { formatTimeFromMiliSeconds } from '@/utils/date'; import type { Socket } from '@/utils/socket'; @@ -15,7 +15,7 @@ export default function Time(props: Props) { let { isConnected, socket, endsAt } = props; // api 연결이 X endsAt 대신 임시로 만들어놓은 것. endsAt = '2023-11-28T12:10:10.000Z'; - const { remainTime } = useConnectHeader({ socket, endsAt }); + const { remainTime } = useTimer({ socket, endsAt }); return (
diff --git a/frontend/src/hooks/competition/useConnectHeader.ts b/frontend/src/hooks/competition/useTimer.ts similarity index 98% rename from frontend/src/hooks/competition/useConnectHeader.ts rename to frontend/src/hooks/competition/useTimer.ts index 3ff1fae..de54700 100644 --- a/frontend/src/hooks/competition/useConnectHeader.ts +++ b/frontend/src/hooks/competition/useTimer.ts @@ -35,7 +35,7 @@ export default function useConnectHeader({ socket, endsAt }: UseConnectHeader) { // 웹 소켓 대신 사용. // 웹 소켓 연결 후 삭제 예정 const mockWebSocket = useCallback(() => { - const delayFactor = 2; + const delayFactor = 2000; setInterval(() => { console.log('ping 5초( + 네트워크 지연) 마다 실행'); const serverTime = new Date(); From 15c789ae956225a2be7e04bf4104c0bb91d03c57 Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 15:47:10 +0900 Subject: [PATCH 05/16] =?UTF-8?q?chore:=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Submission/Timer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Submission/Timer.tsx b/frontend/src/components/Submission/Timer.tsx index 7692cb1..4bc36d5 100644 --- a/frontend/src/components/Submission/Timer.tsx +++ b/frontend/src/components/Submission/Timer.tsx @@ -34,7 +34,7 @@ export default function Time(props: Props) { } const wrapperStyle = css({ - position: 'relative;', + position: 'relative', }); const disConnectedStyle = css({ @@ -48,7 +48,7 @@ const loadingBoxStyle = css({ const positionRightStyle = css({ display: 'flex', - position: 'absolute;', + position: 'absolute', right: '0px', }); From 133e5fc4f82d35585c30dccc0879a5bcdd496d5e Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 17:13:15 +0900 Subject: [PATCH 06/16] =?UTF-8?q?refactor:=20useTimer=20=EC=9D=B8=EC=88=98?= =?UTF-8?q?=EB=A1=9C=20Date=EA=B0=9D=EC=B2=B4=20=EB=B0=9B=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/competition/useTimer.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/hooks/competition/useTimer.ts b/frontend/src/hooks/competition/useTimer.ts index de54700..d6eb987 100644 --- a/frontend/src/hooks/competition/useTimer.ts +++ b/frontend/src/hooks/competition/useTimer.ts @@ -1,16 +1,15 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Socket } from 'socket.io-client'; -interface UseConnectHeader { +interface UseTimer { socket: Socket; - endsAt: string; + endsAt: Date; } -let timerIntervalId: NodeJS.Timeout; - -export default function useConnectHeader({ socket, endsAt }: UseConnectHeader) { - const endTime = useMemo(() => new Date(endsAt).getTime(), [endsAt]); +export default function useTimer({ socket, endsAt }: UseTimer) { + const timerIntervalId = useRef(null); + const endTime = useMemo(() => endsAt.getTime(), [endsAt]); const [remainTime, setRemainTime] = useState(-1); useEffect(() => { console.log('타이머 실행'); @@ -22,11 +21,12 @@ export default function useConnectHeader({ socket, endsAt }: UseConnectHeader) { }, [socket]); const handlePingMessage = useCallback((time: Date | string) => { - clearInterval(timerIntervalId); + if (timerIntervalId.current) clearInterval(timerIntervalId.current); + time = typeof time === 'string' ? new Date(time) : time; const remainSec = endTime - time.getTime(); setRemainTime(remainSec); - timerIntervalId = setInterval(() => { + timerIntervalId.current = setInterval(() => { console.log('1초마다 실행'); setRemainTime((prev) => prev - 1000); }, 1000); From 3357515215ec10b404dc26aed61964b509089e13 Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 17:15:03 +0900 Subject: [PATCH 07/16] =?UTF-8?q?feat:=20Timer=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=B0=B0=EC=B9=98=20=EB=B3=80=EA=B2=BD,?= =?UTF-8?q?=20Connection=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Submission/Connection.tsx | 16 ++++++++++++++ .../Submission/SubmissionResult.tsx | 8 +++---- frontend/src/components/Submission/Timer.tsx | 21 +++++++------------ .../src/hooks/competition/useCompetition.ts | 4 ++++ frontend/src/pages/ContestPage.tsx | 14 ++++++++++--- 5 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 frontend/src/components/Submission/Connection.tsx diff --git a/frontend/src/components/Submission/Connection.tsx b/frontend/src/components/Submission/Connection.tsx new file mode 100644 index 0000000..237a748 --- /dev/null +++ b/frontend/src/components/Submission/Connection.tsx @@ -0,0 +1,16 @@ +import { css } from '@style/css'; + +import Loading from '@/components/Common/Loading'; + +export default function Connection() { + return ( +
+ 연결 중... + +
+ ); +} +const rowStyle = css({ + display: 'flex', + gap: '0.5rem', +}); diff --git a/frontend/src/components/Submission/SubmissionResult.tsx b/frontend/src/components/Submission/SubmissionResult.tsx index 1fc0edf..7e45abc 100644 --- a/frontend/src/components/Submission/SubmissionResult.tsx +++ b/frontend/src/components/Submission/SubmissionResult.tsx @@ -2,16 +2,16 @@ import { css } from '@style/css'; import { useEffect, useState } from 'react'; +import Connection from '@/components/Submission/Connection'; import { range } from '@/utils/array'; import type { Socket } from '@/utils/socket'; import Score from './Score'; -import Timer from './Timer'; import { type Message, type ScoreResult, SUBMIT_STATE, type SubmitState } from './types'; interface Props { socket: Socket; - endsAt: string; + isConnected: boolean; } type SubmitResult = { @@ -20,7 +20,7 @@ type SubmitResult = { score?: ScoreResult; }; -export function SubmissionResult({ socket, endsAt }: Props) { +export function SubmissionResult({ socket, isConnected }: Props) { const [scoreResults, setScoreResults] = useState([]); const [submissionMessage, setSubmissionMessage] = useState(''); @@ -73,7 +73,7 @@ export function SubmissionResult({ socket, endsAt }: Props) { return ( <>
- + {!isConnected && }

{submissionMessage}

{scoreResults.map(({ score, submitState, testcaseId }) => ( diff --git a/frontend/src/components/Submission/Timer.tsx b/frontend/src/components/Submission/Timer.tsx index 4bc36d5..c9c75a8 100644 --- a/frontend/src/components/Submission/Timer.tsx +++ b/frontend/src/components/Submission/Timer.tsx @@ -7,19 +7,19 @@ import type { Socket } from '@/utils/socket'; interface Props { socket: Socket; - isConnected: boolean; - endsAt: string; + endsAt: Date; + isConnected?: boolean; } -export default function Time(props: Props) { - let { isConnected, socket, endsAt } = props; +export default function Timer(props: Props) { + let { socket, endsAt, isConnected } = props; // api 연결이 X endsAt 대신 임시로 만들어놓은 것. - endsAt = '2023-11-28T12:10:10.000Z'; + endsAt = new Date('2023-11-28T13:10:10.000Z'); const { remainTime } = useTimer({ socket, endsAt }); return (
-
+
{isConnected && remainTime !== -1 ? ( {formatTimeFromMiliSeconds(remainTime)} ) : ( @@ -34,7 +34,8 @@ export default function Time(props: Props) { } const wrapperStyle = css({ - position: 'relative', + display: 'flex', + alignItems: 'center', }); const disConnectedStyle = css({ @@ -46,12 +47,6 @@ const loadingBoxStyle = css({ gap: '1rem', }); -const positionRightStyle = css({ - display: 'flex', - position: 'absolute', - right: '0px', -}); - const timeTextStyle = css({ color: 'lightgray', fontWeight: 'bold', diff --git a/frontend/src/hooks/competition/useCompetition.ts b/frontend/src/hooks/competition/useCompetition.ts index dd19dd9..a4204b8 100644 --- a/frontend/src/hooks/competition/useCompetition.ts +++ b/frontend/src/hooks/competition/useCompetition.ts @@ -25,6 +25,7 @@ const notFoundCompetition: CompetitionInfo = { export const useCompetition = (competitionId: number) => { const [competition, setCompetition] = useState(notFoundCompetition); + const [isConnected, setIsConnected] = useState(false); const socket = useRef( createSocketInstance('/competitions', { @@ -38,10 +39,12 @@ export const useCompetition = (competitionId: number) => { const handleConnect = () => { console.log('connected!'); + setIsConnected(true); }; const handleDisconnect = () => { console.log('disconnected!'); + setIsConnected(false); }; useEffect(() => { @@ -77,5 +80,6 @@ export const useCompetition = (competitionId: number) => { socket, competition, submitSolution, + isConnected, }; }; diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index b75cd1e..3f14d4e 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -9,6 +9,7 @@ import ProblemViewer from '@/components/Problem/ProblemViewer'; import { SimulationInputList } from '@/components/Simulation/SimulationInputList'; import { SimulationResultList } from '@/components/Simulation/SimulationResultList'; import { SubmissionResult } from '@/components/Submission'; +import Timer from '@/components/Submission/Timer'; import { SITE } from '@/constants'; import type { SubmissionForm } from '@/hooks/competition'; import { useCompetition } from '@/hooks/competition'; @@ -34,7 +35,7 @@ export default function ContestPage() { cancelSimulation, } = useSimulations(); - const { socket, competition, submitSolution } = useCompetition(competitionId); + const { socket, competition, submitSolution, isConnected } = useCompetition(competitionId); const { problemList } = useCompetitionProblemList(competitionId); const currentProblem = useMemo(() => { @@ -95,8 +96,9 @@ export default function ContestPage() {
-
+
{problem.title} +
@@ -119,7 +121,7 @@ export default function ContestPage() {
- +
@@ -150,3 +152,9 @@ const problemTitleStyle = css({ const execButtonStyle = css({ color: 'black', }); + +const rowStyle = css({ + display: 'flex', + border: '1px solid orange', + justifyContent: 'space-between', +}); From c8c458c85744df74895a8f0cab0ef87f446c5edb Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 17:19:22 +0900 Subject: [PATCH 08/16] =?UTF-8?q?chore:=20=EC=97=B0=EA=B2=B0=20=EB=81=8A?= =?UTF-8?q?=EA=B2=BC=EC=9D=84=20=EB=95=8C=20=EB=B3=B4=EC=97=AC=EC=A4=84=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=9D=B4=EB=A6=84=20dis?= =?UTF-8?q?connection=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Submission/{Connection.tsx => Disconnection.tsx} | 0 frontend/src/components/Submission/SubmissionResult.tsx | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename frontend/src/components/Submission/{Connection.tsx => Disconnection.tsx} (100%) diff --git a/frontend/src/components/Submission/Connection.tsx b/frontend/src/components/Submission/Disconnection.tsx similarity index 100% rename from frontend/src/components/Submission/Connection.tsx rename to frontend/src/components/Submission/Disconnection.tsx diff --git a/frontend/src/components/Submission/SubmissionResult.tsx b/frontend/src/components/Submission/SubmissionResult.tsx index 7e45abc..d1722ba 100644 --- a/frontend/src/components/Submission/SubmissionResult.tsx +++ b/frontend/src/components/Submission/SubmissionResult.tsx @@ -2,7 +2,7 @@ import { css } from '@style/css'; import { useEffect, useState } from 'react'; -import Connection from '@/components/Submission/Connection'; +import Disconnection from '@/components/Submission/Disconnection'; import { range } from '@/utils/array'; import type { Socket } from '@/utils/socket'; @@ -73,7 +73,7 @@ export function SubmissionResult({ socket, isConnected }: Props) { return ( <>
- {!isConnected && } + {!isConnected && }

{submissionMessage}

{scoreResults.map(({ score, submitState, testcaseId }) => ( From 399a9d7e29e3e2adbb890a5c4bed63873f7c93c8 Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 17:25:40 +0900 Subject: [PATCH 09/16] =?UTF-8?q?refactor:=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Submission/Timer.tsx | 6 +++--- frontend/src/hooks/competition/useTimer.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/Submission/Timer.tsx b/frontend/src/components/Submission/Timer.tsx index c9c75a8..3ebae4b 100644 --- a/frontend/src/components/Submission/Timer.tsx +++ b/frontend/src/components/Submission/Timer.tsx @@ -15,13 +15,13 @@ export default function Timer(props: Props) { let { socket, endsAt, isConnected } = props; // api 연결이 X endsAt 대신 임시로 만들어놓은 것. endsAt = new Date('2023-11-28T13:10:10.000Z'); - const { remainTime } = useTimer({ socket, endsAt }); + const { remainMiliSeconds } = useTimer({ socket, endsAt }); return (
- {isConnected && remainTime !== -1 ? ( - {formatTimeFromMiliSeconds(remainTime)} + {isConnected && remainMiliSeconds !== -1 ? ( + {formatTimeFromMiliSeconds(remainMiliSeconds)} ) : (
연결 중... diff --git a/frontend/src/hooks/competition/useTimer.ts b/frontend/src/hooks/competition/useTimer.ts index d6eb987..5b67cb1 100644 --- a/frontend/src/hooks/competition/useTimer.ts +++ b/frontend/src/hooks/competition/useTimer.ts @@ -10,7 +10,7 @@ interface UseTimer { export default function useTimer({ socket, endsAt }: UseTimer) { const timerIntervalId = useRef(null); const endTime = useMemo(() => endsAt.getTime(), [endsAt]); - const [remainTime, setRemainTime] = useState(-1); + const [remainMiliSeconds, setRemainMiliSeconds] = useState(-1); useEffect(() => { console.log('타이머 실행'); // 웹 소켓 대신 사용. @@ -24,11 +24,11 @@ export default function useTimer({ socket, endsAt }: UseTimer) { if (timerIntervalId.current) clearInterval(timerIntervalId.current); time = typeof time === 'string' ? new Date(time) : time; - const remainSec = endTime - time.getTime(); - setRemainTime(remainSec); + const remainMiliSec = endTime - time.getTime(); + setRemainMiliSeconds(remainMiliSec); timerIntervalId.current = setInterval(() => { console.log('1초마다 실행'); - setRemainTime((prev) => prev - 1000); + setRemainMiliSeconds((prev) => prev - 1000); }, 1000); }, []); @@ -46,9 +46,9 @@ export default function useTimer({ socket, endsAt }: UseTimer) { useEffect(() => { // TODO time 0이면 대시보드로 이동하는 로직 // 해당 PR에서 해결할 문제는 아니라 PASS - if (remainTime === 0) { + if (Math.floor(remainMiliSeconds / 1000) <= 0) { // 나가는 로직 } - }, [remainTime]); - return { remainTime }; + }, [remainMiliSeconds]); + return { remainMiliSeconds }; } From 0cc86272ecd4ee2b4ade02879b9d04753bef9a2e Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 17:50:10 +0900 Subject: [PATCH 10/16] =?UTF-8?q?refactor:=20component=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD,=20import=20type=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Submission/Connecting.tsx | 24 +++++++++++++++++++ .../components/Submission/Disconnection.tsx | 16 ------------- .../Submission/SubmissionResult.tsx | 4 ++-- frontend/src/hooks/competition/useTimer.ts | 2 +- 4 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 frontend/src/components/Submission/Connecting.tsx delete mode 100644 frontend/src/components/Submission/Disconnection.tsx diff --git a/frontend/src/components/Submission/Connecting.tsx b/frontend/src/components/Submission/Connecting.tsx new file mode 100644 index 0000000..ceadde1 --- /dev/null +++ b/frontend/src/components/Submission/Connecting.tsx @@ -0,0 +1,24 @@ +import { css } from '@style/css'; + +import Loading from '@/components/Common/Loading'; + +interface Props { + isConnected: boolean; +} + +export default function Connecting(props: Props) { + return ( + <> + {props.isConnected && ( +
+ 연결 중... + +
+ )} + + ); +} +const rowStyle = css({ + display: 'flex', + gap: '0.5rem', +}); diff --git a/frontend/src/components/Submission/Disconnection.tsx b/frontend/src/components/Submission/Disconnection.tsx deleted file mode 100644 index 237a748..0000000 --- a/frontend/src/components/Submission/Disconnection.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { css } from '@style/css'; - -import Loading from '@/components/Common/Loading'; - -export default function Connection() { - return ( -
- 연결 중... - -
- ); -} -const rowStyle = css({ - display: 'flex', - gap: '0.5rem', -}); diff --git a/frontend/src/components/Submission/SubmissionResult.tsx b/frontend/src/components/Submission/SubmissionResult.tsx index d1722ba..2d6341e 100644 --- a/frontend/src/components/Submission/SubmissionResult.tsx +++ b/frontend/src/components/Submission/SubmissionResult.tsx @@ -2,7 +2,7 @@ import { css } from '@style/css'; import { useEffect, useState } from 'react'; -import Disconnection from '@/components/Submission/Disconnection'; +import Connecting from '@/components/Submission/Connecting'; import { range } from '@/utils/array'; import type { Socket } from '@/utils/socket'; @@ -73,7 +73,7 @@ export function SubmissionResult({ socket, isConnected }: Props) { return ( <>
- {!isConnected && } +

{submissionMessage}

{scoreResults.map(({ score, submitState, testcaseId }) => ( diff --git a/frontend/src/hooks/competition/useTimer.ts b/frontend/src/hooks/competition/useTimer.ts index 5b67cb1..4f26c32 100644 --- a/frontend/src/hooks/competition/useTimer.ts +++ b/frontend/src/hooks/competition/useTimer.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { Socket } from 'socket.io-client'; +import { Socket } from '@/utils/socket'; interface UseTimer { socket: Socket; From 00bd4fb1b0c1dfaa9ec4b1d8d1722b2f1676577b Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 19:51:09 +0900 Subject: [PATCH 11/16] =?UTF-8?q?refactor:=20useTimer=20=ED=9B=85=EC=97=90?= =?UTF-8?q?=200=EC=B4=88=EA=B0=80=20=EB=90=98=EB=A9=B4=20=EC=BD=9C?= =?UTF-8?q?=EB=B0=B1=ED=95=A8=EC=88=98=20=EC=8B=A4=ED=96=89=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/competition/useTimer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/hooks/competition/useTimer.ts b/frontend/src/hooks/competition/useTimer.ts index 4f26c32..fba4d63 100644 --- a/frontend/src/hooks/competition/useTimer.ts +++ b/frontend/src/hooks/competition/useTimer.ts @@ -5,9 +5,10 @@ import { Socket } from '@/utils/socket'; interface UseTimer { socket: Socket; endsAt: Date; + onTimeoutHandler?: () => void; } -export default function useTimer({ socket, endsAt }: UseTimer) { +export default function useTimer({ socket, endsAt, onTimeoutHandler }: UseTimer) { const timerIntervalId = useRef(null); const endTime = useMemo(() => endsAt.getTime(), [endsAt]); const [remainMiliSeconds, setRemainMiliSeconds] = useState(-1); @@ -47,6 +48,7 @@ export default function useTimer({ socket, endsAt }: UseTimer) { // TODO time 0이면 대시보드로 이동하는 로직 // 해당 PR에서 해결할 문제는 아니라 PASS if (Math.floor(remainMiliSeconds / 1000) <= 0) { + if (typeof onTimeoutHandler === 'function') onTimeoutHandler(); // 나가는 로직 } }, [remainMiliSeconds]); From be43eeb5c80cc744fe8b130a49996b1fa0602364 Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 19:58:45 +0900 Subject: [PATCH 12/16] =?UTF-8?q?chore:=20Timer=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=BA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/{Submission => Common}/Timer.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename frontend/src/components/{Submission => Common}/Timer.tsx (84%) diff --git a/frontend/src/components/Submission/Timer.tsx b/frontend/src/components/Common/Timer.tsx similarity index 84% rename from frontend/src/components/Submission/Timer.tsx rename to frontend/src/components/Common/Timer.tsx index 3ebae4b..d236400 100644 --- a/frontend/src/components/Submission/Timer.tsx +++ b/frontend/src/components/Common/Timer.tsx @@ -2,7 +2,7 @@ import { css } from '@style/css'; import Loading from '@/components/Common/Loading'; import useTimer from '@/hooks/competition/useTimer'; -import { formatTimeFromMiliSeconds } from '@/utils/date'; +import { formatMilliSecond } from '@/utils/date'; import type { Socket } from '@/utils/socket'; interface Props { @@ -14,14 +14,14 @@ interface Props { export default function Timer(props: Props) { let { socket, endsAt, isConnected } = props; // api 연결이 X endsAt 대신 임시로 만들어놓은 것. - endsAt = new Date('2023-11-28T13:10:10.000Z'); + endsAt = new Date('2023-11-29T13:10:10.000Z'); const { remainMiliSeconds } = useTimer({ socket, endsAt }); return (
{isConnected && remainMiliSeconds !== -1 ? ( - {formatTimeFromMiliSeconds(remainMiliSeconds)} + {formatMilliSecond(remainMiliSeconds, 'hh:mm:ss')} ) : (
연결 중... From 55b58b51a7f7903e449cf16b434fc4eaa023030f Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 19:59:41 +0900 Subject: [PATCH 13/16] =?UTF-8?q?refactor:=20utils/date=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Submission/Connecting.tsx | 2 +- frontend/src/pages/ContestPage.tsx | 3 +-- frontend/src/utils/date/index.ts | 15 +++++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Submission/Connecting.tsx b/frontend/src/components/Submission/Connecting.tsx index ceadde1..5530327 100644 --- a/frontend/src/components/Submission/Connecting.tsx +++ b/frontend/src/components/Submission/Connecting.tsx @@ -9,7 +9,7 @@ interface Props { export default function Connecting(props: Props) { return ( <> - {props.isConnected && ( + {!props.isConnected && (
연결 중... diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index 3f14d4e..2055833 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -3,13 +3,13 @@ import { css } from '@style/css'; import { useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; +import Timer from '@/components/Common/Timer'; import ContestBreadCrumb from '@/components/Contest/ContestBreadCrumb'; import Editor from '@/components/Editor/Editor'; import ProblemViewer from '@/components/Problem/ProblemViewer'; import { SimulationInputList } from '@/components/Simulation/SimulationInputList'; import { SimulationResultList } from '@/components/Simulation/SimulationResultList'; import { SubmissionResult } from '@/components/Submission'; -import Timer from '@/components/Submission/Timer'; import { SITE } from '@/constants'; import type { SubmissionForm } from '@/hooks/competition'; import { useCompetition } from '@/hooks/competition'; @@ -155,6 +155,5 @@ const execButtonStyle = css({ const rowStyle = css({ display: 'flex', - border: '1px solid orange', justifyContent: 'space-between', }); diff --git a/frontend/src/utils/date/index.ts b/frontend/src/utils/date/index.ts index 7f9e09e..96ed403 100644 --- a/frontend/src/utils/date/index.ts +++ b/frontend/src/utils/date/index.ts @@ -19,12 +19,15 @@ export const formatDate = (date: Date, form: string) => { return ''; }; -export const formatTimeFromMiliSeconds = (ms: number) => { +export const formatMilliSecond = (ms: number, form: string) => { const sec = Math.floor(ms / ONE_SEC_BY_MS); - // 시간(초)을 'hh:mm:ss' 형식으로 변환 - const hours = Math.floor(sec / ONE_HOUR_BY_SEC); - const minutes = Math.floor((sec % ONE_HOUR_BY_SEC) / ONE_MIN_BY_SEC); - const seconds = sec % ONE_MIN_BY_SEC; - return [hours, minutes, seconds].map((time) => String(time).padStart(2, '0')).join(':'); + if (form === 'hh:mm:ss') { + // 시간(초)을 'hh:mm:ss' 형식으로 변환 + const hours = Math.floor(sec / ONE_HOUR_BY_SEC); + const minutes = Math.floor((sec % ONE_HOUR_BY_SEC) / ONE_MIN_BY_SEC); + const seconds = sec % ONE_MIN_BY_SEC; + return [hours, minutes, seconds].map((time) => String(time).padStart(2, '0')).join(':'); + } + return ''; }; From bb7244812a85d3d43d05dd3747e883bd5aa79a2a Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 20:15:53 +0900 Subject: [PATCH 14/16] =?UTF-8?q?refactor:=20Timer=20=ED=95=98=EB=82=98?= =?UTF-8?q?=EC=9D=98=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=EB=A1=A4=20?= =?UTF-8?q?=EB=B9=BC=EC=84=9C=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/{Common/Timer.tsx => Timer/index.tsx} | 2 +- frontend/src/hooks/{competition => timer}/useTimer.ts | 0 frontend/src/pages/ContestPage.tsx | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename frontend/src/components/{Common/Timer.tsx => Timer/index.tsx} (96%) rename frontend/src/hooks/{competition => timer}/useTimer.ts (100%) diff --git a/frontend/src/components/Common/Timer.tsx b/frontend/src/components/Timer/index.tsx similarity index 96% rename from frontend/src/components/Common/Timer.tsx rename to frontend/src/components/Timer/index.tsx index d236400..c800bb4 100644 --- a/frontend/src/components/Common/Timer.tsx +++ b/frontend/src/components/Timer/index.tsx @@ -1,7 +1,7 @@ import { css } from '@style/css'; import Loading from '@/components/Common/Loading'; -import useTimer from '@/hooks/competition/useTimer'; +import useTimer from '@/hooks/timer/useTimer'; import { formatMilliSecond } from '@/utils/date'; import type { Socket } from '@/utils/socket'; diff --git a/frontend/src/hooks/competition/useTimer.ts b/frontend/src/hooks/timer/useTimer.ts similarity index 100% rename from frontend/src/hooks/competition/useTimer.ts rename to frontend/src/hooks/timer/useTimer.ts diff --git a/frontend/src/pages/ContestPage.tsx b/frontend/src/pages/ContestPage.tsx index 2055833..538c05a 100644 --- a/frontend/src/pages/ContestPage.tsx +++ b/frontend/src/pages/ContestPage.tsx @@ -3,13 +3,13 @@ import { css } from '@style/css'; import { useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; -import Timer from '@/components/Common/Timer'; import ContestBreadCrumb from '@/components/Contest/ContestBreadCrumb'; import Editor from '@/components/Editor/Editor'; import ProblemViewer from '@/components/Problem/ProblemViewer'; import { SimulationInputList } from '@/components/Simulation/SimulationInputList'; import { SimulationResultList } from '@/components/Simulation/SimulationResultList'; import { SubmissionResult } from '@/components/Submission'; +import Timer from '@/components/Timer'; import { SITE } from '@/constants'; import type { SubmissionForm } from '@/hooks/competition'; import { useCompetition } from '@/hooks/competition'; From 20abf252c8fab10c4d6bdbe7e93a83a610a031ef Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 20:31:04 +0900 Subject: [PATCH 15/16] =?UTF-8?q?refactor:=20=EB=B6=84=EA=B8=B0=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Submission/Connecting.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/Submission/Connecting.tsx b/frontend/src/components/Submission/Connecting.tsx index 5530327..3ba2fdc 100644 --- a/frontend/src/components/Submission/Connecting.tsx +++ b/frontend/src/components/Submission/Connecting.tsx @@ -7,15 +7,13 @@ interface Props { } export default function Connecting(props: Props) { + if (!props.isConnected) return null; + return ( - <> - {!props.isConnected && ( -
- 연결 중... - -
- )} - +
+ 연결 중... + +
); } const rowStyle = css({ From 2311b1c15f06d11fd09d451327f790194b71b1a4 Mon Sep 17 00:00:00 2001 From: youseock Date: Tue, 28 Nov 2023 20:37:41 +0900 Subject: [PATCH 16/16] =?UTF-8?q?refactor:=20=EB=B6=84=EB=B6=84=EA=B8=B0?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Timer/index.tsx | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Timer/index.tsx b/frontend/src/components/Timer/index.tsx index c800bb4..ff15dfe 100644 --- a/frontend/src/components/Timer/index.tsx +++ b/frontend/src/components/Timer/index.tsx @@ -17,17 +17,24 @@ export default function Timer(props: Props) { endsAt = new Date('2023-11-29T13:10:10.000Z'); const { remainMiliSeconds } = useTimer({ socket, endsAt }); + if (isConnected && remainMiliSeconds !== -1) { + // 연결도 되어있고, 서버 시간도 도착해서 count down을 시작할 수 있을 때 + return ( +
+
+ {formatMilliSecond(remainMiliSeconds, 'hh:mm:ss')} +
+
+ ); + } + return (
- {isConnected && remainMiliSeconds !== -1 ? ( - {formatMilliSecond(remainMiliSeconds, 'hh:mm:ss')} - ) : ( -
- 연결 중... - -
- )} +
+ 연결 중... + +
); @@ -38,7 +45,7 @@ const wrapperStyle = css({ alignItems: 'center', }); -const disConnectedStyle = css({ +const disconnectedStyle = css({ color: 'darkred', });