aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/app/components/GolfWatchApp.tsx66
-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.tsx102
-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.tsx15
-rw-r--r--frontend/app/routes/golf.$gameId.watch.tsx4
-rw-r--r--frontend/app/states/watch.ts5
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;