From 9477788709127ffd5611caed0fa7ee191326ce00 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 10 Aug 2024 13:31:52 +0900 Subject: feat(frontend): partially implement watch page --- frontend/app/components/GolfWatchApp.client.tsx | 66 +++++++--- .../GolfWatchApps/GolfWatchAppGaming.tsx | 137 +++++++++++++++++---- .../GolfWatchApps/GolfWatchAppStarting.tsx | 6 +- .../GolfWatchApps/GolfWatchAppWaiting.tsx | 8 +- 4 files changed, 171 insertions(+), 46 deletions(-) (limited to 'frontend/app/components') diff --git a/frontend/app/components/GolfWatchApp.client.tsx b/frontend/app/components/GolfWatchApp.client.tsx index 829f709..355f7e3 100644 --- a/frontend/app/components/GolfWatchApp.client.tsx +++ b/frontend/app/components/GolfWatchApp.client.tsx @@ -3,7 +3,9 @@ import useWebSocket, { ReadyState } from "react-use-websocket"; import type { components } from "../.server/api/schema"; import GolfWatchAppConnecting from "./GolfWatchApps/GolfWatchAppConnecting"; import GolfWatchAppFinished from "./GolfWatchApps/GolfWatchAppFinished"; -import GolfWatchAppGaming from "./GolfWatchApps/GolfWatchAppGaming"; +import GolfWatchAppGaming, { + PlayerInfo, +} from "./GolfWatchApps/GolfWatchAppGaming"; import GolfWatchAppStarting from "./GolfWatchApps/GolfWatchAppStarting"; import GolfWatchAppWaiting from "./GolfWatchApps/GolfWatchAppWaiting"; @@ -34,12 +36,12 @@ export default function GolfWatchApp({ const [startedAt, setStartedAt] = useState(null); - const [timeLeftSeconds, setTimeLeftSeconds] = useState(null); + const [leftTimeSeconds, setLeftTimeSeconds] = useState(null); useEffect(() => { if (gameState === "starting" && startedAt !== null) { const timer1 = setInterval(() => { - setTimeLeftSeconds((prev) => { + setLeftTimeSeconds((prev) => { if (prev === null) { return null; } @@ -68,10 +70,23 @@ export default function GolfWatchApp({ } }, [gameState, startedAt, game.duration_seconds]); - const [scoreA, setScoreA] = useState(null); - const [scoreB, setScoreB] = useState(null); - const [codeA, setCodeA] = useState(""); - const [codeB, setCodeB] = useState(""); + const playerA = game.players[0]; + const playerB = game.players[1]; + + const [playerInfoA, setPlayerInfoA] = useState({ + displayName: playerA?.display_name ?? null, + iconPath: playerA?.icon_path ?? null, + score: null, + code: "", + submissionResult: undefined, + }); + const [playerInfoB, setPlayerInfoB] = useState({ + displayName: playerB?.display_name ?? null, + iconPath: playerB?.icon_path ?? null, + score: null, + code: "", + submissionResult: undefined, + }); if (readyState === ReadyState.UNINSTANTIATED) { throw new Error("WebSocket is not connected"); @@ -96,38 +111,51 @@ export default function GolfWatchApp({ const { start_at } = lastJsonMessage.data; setStartedAt(start_at); const nowSec = Math.floor(Date.now() / 1000); - setTimeLeftSeconds(start_at - nowSec); + setLeftTimeSeconds(start_at - nowSec); setGameState("starting"); } } else if (lastJsonMessage.type === "watcher:s2c:code") { const { player_id, code } = lastJsonMessage.data; - setCodeA(code); - } else if (lastJsonMessage.type === "watcher:s2c:execresult") { - const { score } = lastJsonMessage.data; - if (score !== null && (scoreA === null || score < scoreA)) { - setScoreA(score); + if (player_id === playerA?.user_id) { + setPlayerInfoA((prev) => ({ ...prev, code })); + } else if (player_id === playerB?.user_id) { + setPlayerInfoB((prev) => ({ ...prev, code })); + } else { + throw new Error("Unknown player_id"); } + } else if (lastJsonMessage.type === "watcher:s2c:execresult") { + // const { score } = lastJsonMessage.data; + // if (score !== null && (scoreA === null || score < scoreA)) { + // setScoreA(score); + // } } } else { setGameState("waiting"); } } - }, [lastJsonMessage, readyState, gameState, scoreA]); + }, [ + lastJsonMessage, + readyState, + gameState, + playerInfoA, + playerInfoB, + playerA?.user_id, + playerB?.user_id, + ]); if (gameState === "connecting") { return ; } else if (gameState === "waiting") { return ; } else if (gameState === "starting") { - return ; + return ; } else if (gameState === "gaming") { return ( ); } else if (gameState === "finished") { diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx index 22277f8..470a00c 100644 --- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx +++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx @@ -1,41 +1,130 @@ type Props = { problem: string; - codeA: string; - scoreA: number | null; - codeB: string; - scoreB: number | null; + playerInfoA: PlayerInfo; + playerInfoB: PlayerInfo; + leftTimeSeconds: number; +}; + +export type PlayerInfo = { + displayName: string | null; + iconPath: string | null; + score: number | null; + code: string | null; + submissionResult?: SubmissionResult; +}; + +type SubmissionResult = { + status: string; + nextScore: number; + executionResults: ExecutionResult[]; +}; + +type ExecutionResult = { + status: string; + label: string; + output: string; }; export default function GolfWatchAppGaming({ problem, - codeA, - scoreA, - codeB, - scoreB, + playerInfoA, + playerInfoB, + leftTimeSeconds, }: Props) { + const leftTime = (() => { + const m = Math.floor(leftTimeSeconds / 60); + const s = leftTimeSeconds % 60; + return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; + })(); + const scoreRatio = (() => { + const scoreA = playerInfoA.score ?? 0; + const scoreB = playerInfoB.score ?? 0; + const totalScore = scoreA + scoreB; + return totalScore === 0 ? 50 : (scoreA / totalScore) * 100; + })(); + return ( -
-
-
{problem}
+
+
+
+ {playerInfoA.displayName} +
+
{leftTime}
+
+ {playerInfoB.displayName} +
-
-
-
{scoreA}
-
-
-							{codeA}
-						
+
+
+ {playerInfoA.score ?? "-"} +
+
+
+
+
+ {playerInfoB.score ?? "-"} +
+
+
+
+
+						{playerInfoA.code}
+					
+
+
+
+ {playerInfoA.submissionResult?.status}( + {playerInfoA.submissionResult?.nextScore}) +
+
+
    + {playerInfoA.submissionResult?.executionResults.map( + (result, idx) => ( +
  1. +
    +
    + {result.status} {result.label} +
    +
    {result.output}
    +
    +
  2. + ), + )} +
-
-
{scoreB}
-
-
-							{codeB}
-						
+
+
+						{playerInfoB.code}
+					
+
+
+
+ {playerInfoB.submissionResult?.status}( + {playerInfoB.submissionResult?.nextScore}) +
+
+
    + {playerInfoB.submissionResult?.executionResults.map( + (result, idx) => ( +
  1. +
    +
    + {result.status} {result.label} +
    +
    {result.output}
    +
    +
  2. + ), + )} +
+
{problem}
); } diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppStarting.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppStarting.tsx index ef72cec..8282fb4 100644 --- a/frontend/app/components/GolfWatchApps/GolfWatchAppStarting.tsx +++ b/frontend/app/components/GolfWatchApps/GolfWatchAppStarting.tsx @@ -1,8 +1,10 @@ type Props = { - timeLeft: number; + leftTimeSeconds: number; }; -export default function GolfWatchAppStarting({ timeLeft }: Props) { +export default function GolfWatchAppStarting({ + leftTimeSeconds: timeLeft, +}: Props) { return (
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx index d58ec19..17ef2b9 100644 --- a/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx +++ b/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx @@ -1,3 +1,9 @@ export default function GolfWatchAppWaiting() { - return
Waiting...
; + return ( +
+
+

Waiting...

+
+
+ ); } -- cgit v1.2.3-70-g09d2