diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-03-08 10:32:05 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-03-08 10:39:25 +0900 |
| commit | c889a9ad33374eae03cec5b0358d79016d6fd97e (patch) | |
| tree | 852a080bc1b0e68e5f46f8bca0ff787aafd314e1 /frontend | |
| parent | 8dbdf96e674c1e26d7c98af8d0608f30bc1bf166 (diff) | |
| download | iosdc-japan-2025-albatross-c889a9ad33374eae03cec5b0358d79016d6fd97e.tar.gz iosdc-japan-2025-albatross-c889a9ad33374eae03cec5b0358d79016d6fd97e.tar.zst iosdc-japan-2025-albatross-c889a9ad33374eae03cec5b0358d79016d6fd97e.zip | |
show ranking
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/app/components/GolfWatchApp.tsx | 66 | ||||
| -rw-r--r-- | frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx (renamed from frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx) | 18 | ||||
| -rw-r--r-- | frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx | 102 | ||||
| -rw-r--r-- | frontend/app/components/GolfWatchApps/GolfWatchAppWaiting1v1.tsx (renamed from frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx) | 2 | ||||
| -rw-r--r-- | frontend/app/components/GolfWatchApps/GolfWatchAppWaitingMultiplayer.tsx | 15 | ||||
| -rw-r--r-- | frontend/app/routes/golf.$gameId.watch.tsx | 4 | ||||
| -rw-r--r-- | frontend/app/states/watch.ts | 5 |
7 files changed, 171 insertions, 41 deletions
diff --git a/frontend/app/components/GolfWatchApp.tsx b/frontend/app/components/GolfWatchApp.tsx index fe71932..402884f 100644 --- a/frontend/app/components/GolfWatchApp.tsx +++ b/frontend/app/components/GolfWatchApp.tsx @@ -1,4 +1,4 @@ -import { useAtomValue, useSetAtom } from "jotai"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { useContext, useEffect, useState } from "react"; import { useTimer } from "react-use-precision-timer"; import { @@ -10,14 +10,16 @@ import { import type { components } from "../api/schema"; import { gameStateKindAtom, + rankingAtom, setCurrentTimestampAtom, setGameStartedAtAtom, setLatestGameStatesAtom, - setRankingAtom, } from "../states/watch"; -import GolfWatchAppGaming from "./GolfWatchApps/GolfWatchAppGaming"; +import GolfWatchAppGaming1v1 from "./GolfWatchApps/GolfWatchAppGaming1v1"; +import GolfWatchAppGamingMultiplayer from "./GolfWatchApps/GolfWatchAppGamingMultiplayer"; import GolfWatchAppStarting from "./GolfWatchApps/GolfWatchAppStarting"; -import GolfWatchAppWaiting from "./GolfWatchApps/GolfWatchAppWaiting"; +import GolfWatchAppWaiting1v1 from "./GolfWatchApps/GolfWatchAppWaiting1v1"; +import GolfWatchAppWaitingMultiplayer from "./GolfWatchApps/GolfWatchAppWaitingMultiplayer"; type Game = components["schemas"]["Game"]; @@ -32,23 +34,27 @@ export default function GolfWatchApp({ game }: Props) { const setGameStartedAt = useSetAtom(setGameStartedAtAtom); const setCurrentTimestamp = useSetAtom(setCurrentTimestampAtom); const setLatestGameStates = useSetAtom(setLatestGameStatesAtom); - const setRanking = useSetAtom(setRankingAtom); + const [ranking, setRanking] = useAtom(rankingAtom); useTimer({ delay: 1000, startImmediately: true }, setCurrentTimestamp); - const playerA = game.main_players[0]!; - const playerB = game.main_players[1]!; + const playerA = game.main_players[0]; + const playerB = game.main_players[1]; - const playerProfileA = { - id: playerA.user_id, - displayName: playerA.display_name, - iconPath: playerA.icon_path ?? null, - }; - const playerProfileB = { - id: playerB.user_id, - displayName: playerB.display_name, - iconPath: playerB.icon_path ?? null, - }; + const playerProfileA = playerA + ? { + id: playerA.user_id, + displayName: playerA.display_name, + iconPath: playerA.icon_path ?? null, + } + : null; + const playerProfileB = playerB + ? { + id: playerB.user_id, + displayName: playerB.display_name, + iconPath: playerB.icon_path ?? null, + } + : null; const [isDataPolling, setIsDataPolling] = useState(false); @@ -101,21 +107,31 @@ export default function GolfWatchApp({ game }: Props) { ]); if (gameStateKind === "waiting") { - return ( - <GolfWatchAppWaiting + return game.game_type === "1v1" ? ( + <GolfWatchAppWaiting1v1 gameDisplayName={game.display_name} - playerProfileA={playerProfileA} - playerProfileB={playerProfileB} + playerProfileA={playerProfileA!} + playerProfileB={playerProfileB!} /> + ) : ( + <GolfWatchAppWaitingMultiplayer gameDisplayName={game.display_name} /> ); } else if (gameStateKind === "starting") { return <GolfWatchAppStarting gameDisplayName={game.display_name} />; } else if (gameStateKind === "gaming" || gameStateKind === "finished") { - return ( - <GolfWatchAppGaming + return game.game_type === "1v1" ? ( + <GolfWatchAppGaming1v1 + gameDisplayName={game.display_name} + playerProfileA={playerProfileA!} + playerProfileB={playerProfileB!} + problemTitle={game.problem.title} + problemDescription={game.problem.description} + gameResult={null /* TODO */} + /> + ) : ( + <GolfWatchAppGamingMultiplayer gameDisplayName={game.display_name} - playerProfileA={playerProfileA} - playerProfileB={playerProfileB} + ranking={ranking} problemTitle={game.problem.title} problemDescription={game.problem.description} gameResult={null /* TODO */} diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx index afb8bfe..033186c 100644 --- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx +++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx @@ -19,7 +19,7 @@ type Props = { gameResult: "winA" | "winB" | "draw" | null; }; -export default function GolfWatchAppGaming({ +export default function GolfWatchAppGaming1v1({ gameDisplayName, playerProfileA, playerProfileB, @@ -30,14 +30,14 @@ export default function GolfWatchAppGaming({ const leftTimeSeconds = useAtomValue(gamingLeftTimeSecondsAtom)!; const latestGameStates = useAtomValue(latestGameStatesAtom); - const stateA = latestGameStates[playerProfileA.id]!; - const codeA = stateA.code; - const scoreA = stateA.score; - const statusA = stateA.status; - const stateB = latestGameStates[playerProfileB.id]!; - const codeB = stateB.code; - const scoreB = stateB.score; - const statusB = stateB.status; + const stateA = latestGameStates[playerProfileA.id]; + const codeA = stateA?.code ?? ""; + const scoreA = stateA?.score ?? null; + const statusA = stateA?.status ?? "none"; + const stateB = latestGameStates[playerProfileB.id]; + const codeB = stateB?.code ?? ""; + const scoreB = stateB?.score ?? null; + const statusB = stateB?.status ?? "none"; const leftTime = (() => { const m = Math.floor(leftTimeSeconds / 60); diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx new file mode 100644 index 0000000..b6d2ac3 --- /dev/null +++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx @@ -0,0 +1,102 @@ +import { useAtomValue } from "jotai"; +import type { components } from "../../api/schema"; +import { gamingLeftTimeSecondsAtom } from "../../states/watch"; +import BorderedContainer from "../BorderedContainer"; + +type RankingEntry = components["schemas"]["RankingEntry"]; + +type Props = { + gameDisplayName: string; + ranking: RankingEntry[]; + problemTitle: string; + problemDescription: string; + gameResult: "winA" | "winB" | "draw" | null; +}; + +export default function GolfWatchAppGamingMultiplayer({ + gameDisplayName, + ranking, + problemTitle, + problemDescription, + gameResult, +}: Props) { + const leftTimeSeconds = useAtomValue(gamingLeftTimeSecondsAtom)!; + + const leftTime = (() => { + const m = Math.floor(leftTimeSeconds / 60); + const s = leftTimeSeconds % 60; + return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; + })(); + + const topBg = gameResult + ? gameResult === "winA" + ? "bg-orange-400" + : gameResult === "winB" + ? "bg-purple-400" + : "bg-pink-500" + : "bg-sky-600"; + + return ( + <div className="min-h-screen bg-gray-100 flex flex-col"> + <div className={`text-white ${topBg} grid grid-cols-3 px-4 py-2`}> + <div className="font-bold flex justify-between my-auto"></div> + <div className="font-bold text-center"> + <div className="text-gray-100">{gameDisplayName}</div> + <div className="text-3xl">{leftTime}</div> + </div> + <div className="font-bold flex justify-between my-auto"></div> + </div> + <div className="grow grid grid-cols-2 p-4 gap-4"> + <div className="flex flex-col gap-4"> + <div> + <div className="mb-2 text-center text-xl font-bold"> + {problemTitle} + </div> + <BorderedContainer>{problemDescription}</BorderedContainer> + </div> + </div> + <div> + <table className="min-w-full divide-y divide-gray-200"> + <thead className="bg-gray-50"> + <tr> + <th + scope="col" + className="px-6 py-3 text-left font-medium text-gray-800 uppercase tracking-wider" + > + 順位 + </th> + <th + scope="col" + className="px-6 py-3 text-left font-medium text-gray-800 uppercase tracking-wider" + > + 名前 + </th> + <th + scope="col" + className="px-6 py-3 text-left font-medium text-gray-800 uppercase tracking-wider" + > + スコア + </th> + </tr> + </thead> + <tbody className="bg-white divide-y divide-gray-200"> + {ranking.map((entry, index) => ( + <tr key={entry.player.user_id}> + <td className="px-6 py-4 whitespace-nowrap text-gray-900"> + {index + 1} + </td> + <td className="px-6 py-4 whitespace-nowrap text-gray-900"> + {entry.player.display_name} + </td> + <td className="px-6 py-4 whitespace-nowrap text-gray-900"> + {entry.score} + </td> + </tr> + ))} + </tbody> + </table> + </div> + </div> + </div> + ); +} diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting1v1.tsx index c2a5431..fb315ff 100644 --- a/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx +++ b/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting1v1.tsx @@ -7,7 +7,7 @@ type Props = { playerProfileB: PlayerProfile; }; -export default function GolfWatchAppWaiting({ +export default function GolfWatchAppWaiting1v1({ gameDisplayName, playerProfileA, playerProfileB, diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppWaitingMultiplayer.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppWaitingMultiplayer.tsx new file mode 100644 index 0000000..13bcc10 --- /dev/null +++ b/frontend/app/components/GolfWatchApps/GolfWatchAppWaitingMultiplayer.tsx @@ -0,0 +1,15 @@ +type Props = { + gameDisplayName: string; +}; + +export default function GolfWatchAppWaitingMultiplayer({ + gameDisplayName, +}: Props) { + return ( + <div className="min-h-screen bg-gray-100 flex flex-col font-bold text-center"> + <div className="text-white bg-sky-600 p-10"> + <div className="text-4xl">{gameDisplayName}</div> + </div> + </div> + ); +} diff --git a/frontend/app/routes/golf.$gameId.watch.tsx b/frontend/app/routes/golf.$gameId.watch.tsx index 0c07633..fed06aa 100644 --- a/frontend/app/routes/golf.$gameId.watch.tsx +++ b/frontend/app/routes/golf.$gameId.watch.tsx @@ -10,11 +10,11 @@ import { } from "../api/client"; import GolfWatchApp from "../components/GolfWatchApp"; import { + rankingAtom, setCurrentTimestampAtom, setDurationSecondsAtom, setGameStartedAtAtom, setLatestGameStatesAtom, - setRankingAtom, } from "../states/watch"; export const meta: MetaFunction<typeof loader> = ({ data }) => [ @@ -59,10 +59,10 @@ export default function GolfWatch() { useLoaderData<typeof loader>(); useHydrateAtoms([ + [rankingAtom, ranking], [setCurrentTimestampAtom, undefined], [setDurationSecondsAtom, game.duration_seconds], [setGameStartedAtAtom, game.started_at ?? null], - [setRankingAtom, ranking], [setLatestGameStatesAtom, gameStates], ]); diff --git a/frontend/app/states/watch.ts b/frontend/app/states/watch.ts index ad95e0a..d3cc723 100644 --- a/frontend/app/states/watch.ts +++ b/frontend/app/states/watch.ts @@ -58,10 +58,7 @@ export const gamingLeftTimeSecondsAtom = atom<number | null>((get) => { return Math.min(durationSeconds, Math.max(0, finishedAt - currentTimestamp)); }); -const rankingAtom = atom<RankingEntry[]>([]); -export const setRankingAtom = atom(null, (_, set, value: RankingEntry[]) => { - set(rankingAtom, value); -}); +export const rankingAtom = atom<RankingEntry[]>([]); const rawLatestGameStatesAtom = atom<{ [key: string]: LatestGameState | undefined; |
