aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app/routes
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/app/routes')
-rw-r--r--frontend/app/routes/dashboard.tsx19
-rw-r--r--frontend/app/routes/golf.$gameId.play.tsx110
-rw-r--r--frontend/app/routes/golf.$gameId.watch.tsx185
3 files changed, 67 insertions, 247 deletions
diff --git a/frontend/app/routes/dashboard.tsx b/frontend/app/routes/dashboard.tsx
index cf5453c..08461a5 100644
--- a/frontend/app/routes/dashboard.tsx
+++ b/frontend/app/routes/dashboard.tsx
@@ -1,7 +1,7 @@
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { Form, useLoaderData } from "@remix-run/react";
-import { apiGetGames } from "../.server/api/client";
import { ensureUserLoggedIn } from "../.server/auth";
+import { apiGetGames } from "../api/client";
import BorderedContainer from "../components/BorderedContainer";
import NavigateLink from "../components/NavigateLink";
import UserIcon from "../components/UserIcon";
@@ -39,7 +39,7 @@ export default function Dashboard() {
<BorderedContainer>
<div className="px-4">
{games.length === 0 ? (
- <p>エントリーしている試合はありません</p>
+ <p>エントリーできる試合はありません</p>
) : (
<ul className="divide-y">
{games.map((game) => (
@@ -58,15 +58,12 @@ export default function Dashboard() {
</span>
</div>
<span>
- {game.state === "closed" || game.state === "finished" ? (
- <span className="text-lg text-gray-400 bg-gray-200 px-4 py-2 rounded">
- 入室
- </span>
- ) : (
- <NavigateLink to={`/golf/${game.game_id}/play`}>
- 入室
- </NavigateLink>
- )}
+ <NavigateLink to={`/golf/${game.game_id}/play`}>
+ 対戦
+ </NavigateLink>
+ <NavigateLink to={`/golf/${game.game_id}/watch`}>
+ 観戦
+ </NavigateLink>
</span>
</li>
))}
diff --git a/frontend/app/routes/golf.$gameId.play.tsx b/frontend/app/routes/golf.$gameId.play.tsx
index 91a2b8c..e523187 100644
--- a/frontend/app/routes/golf.$gameId.play.tsx
+++ b/frontend/app/routes/golf.$gameId.play.tsx
@@ -1,17 +1,19 @@
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
-import { ClientLoaderFunctionArgs, useLoaderData } from "@remix-run/react";
+import { useLoaderData } from "@remix-run/react";
import { useHydrateAtoms } from "jotai/utils";
-import { apiGetGame, apiGetToken } from "../.server/api/client";
import { ensureUserLoggedIn } from "../.server/auth";
-import GolfPlayApp from "../components/GolfPlayApp.client";
-import GolfPlayAppConnecting from "../components/GolfPlayApps/GolfPlayAppConnecting";
import {
- scoreAtom,
+ ApiAuthTokenContext,
+ apiGetGame,
+ apiGetGamePlayLatestState,
+} from "../api/client";
+import GolfPlayApp from "../components/GolfPlayApp";
+import {
setCurrentTimestampAtom,
setDurationSecondsAtom,
- submitResultAtom,
+ setGameStartedAtAtom,
+ setLatestGameStateAtom,
} from "../states/play";
-import { PlayerState } from "../types/PlayerState";
export const meta: MetaFunction<typeof loader> = ({ data }) => [
{
@@ -24,105 +26,39 @@ export const meta: MetaFunction<typeof loader> = ({ data }) => [
export async function loader({ params, request }: LoaderFunctionArgs) {
const { token, user } = await ensureUserLoggedIn(request);
+ const gameId = Number(params.gameId);
+
const fetchGame = async () => {
- return (await apiGetGame(token, Number(params.gameId))).game;
+ return (await apiGetGame(token, gameId)).game;
};
- const fetchSockToken = async () => {
- return (await apiGetToken(token)).token;
+ const fetchGameState = async () => {
+ return (await apiGetGamePlayLatestState(token, gameId)).state;
};
- const [game, sockToken] = await Promise.all([fetchGame(), fetchSockToken()]);
-
- const playerState: PlayerState = {
- code: "",
- score: null,
- submitResult: {
- status: "waiting_submission",
- execResults: game.exec_steps.map((r) => ({
- testcase_id: r.testcase_id,
- status: "waiting_submission",
- label: r.label,
- stdout: "",
- stderr: "",
- })),
- },
- };
+ const [game, state] = await Promise.all([fetchGame(), fetchGameState()]);
return {
+ apiAuthToken: token,
game,
player: user,
- sockToken,
- playerState,
+ gameState: state,
};
}
-export async function clientLoader({ serverLoader }: ClientLoaderFunctionArgs) {
- const data = await serverLoader<typeof loader>();
- const baseKey = `playerState:${data.game.game_id}:${data.player.user_id}`;
-
- const localCode = (() => {
- const rawValue = window.localStorage.getItem(`${baseKey}:code`);
- if (rawValue === null) {
- return null;
- }
- return rawValue;
- })();
-
- const localScore = (() => {
- const rawValue = window.localStorage.getItem(`${baseKey}:score`);
- if (rawValue === null || rawValue === "") {
- return null;
- }
- return Number(rawValue);
- })();
-
- const localSubmissionResult = (() => {
- const rawValue = window.localStorage.getItem(`${baseKey}:submissionResult`);
- if (rawValue === null) {
- return null;
- }
- const parsed = JSON.parse(rawValue);
- if (typeof parsed !== "object") {
- return null;
- }
- return parsed;
- })();
-
- if (localCode !== null) {
- data.playerState.code = localCode;
- }
- if (localScore !== null) {
- data.playerState.score = localScore;
- }
- if (localSubmissionResult !== null) {
- data.playerState.submitResult = localSubmissionResult;
- }
-
- return data;
-}
-clientLoader.hydrate = true;
-
-export function HydrateFallback() {
- return <GolfPlayAppConnecting />;
-}
-
export default function GolfPlay() {
- const { game, player, sockToken, playerState } =
+ const { apiAuthToken, game, player, gameState } =
useLoaderData<typeof loader>();
useHydrateAtoms([
[setCurrentTimestampAtom, undefined],
[setDurationSecondsAtom, game.duration_seconds],
- [scoreAtom, playerState.score],
- [submitResultAtom, playerState.submitResult],
+ [setGameStartedAtAtom, game.started_at ?? null],
+ [setLatestGameStateAtom, gameState],
]);
return (
- <GolfPlayApp
- game={game}
- player={player}
- initialCode={playerState.code}
- sockToken={sockToken}
- />
+ <ApiAuthTokenContext.Provider value={apiAuthToken}>
+ <GolfPlayApp game={game} player={player} initialCode={gameState.code} />
+ </ApiAuthTokenContext.Provider>
);
}
diff --git a/frontend/app/routes/golf.$gameId.watch.tsx b/frontend/app/routes/golf.$gameId.watch.tsx
index 5a41de5..0c07633 100644
--- a/frontend/app/routes/golf.$gameId.watch.tsx
+++ b/frontend/app/routes/golf.$gameId.watch.tsx
@@ -1,21 +1,21 @@
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
-import { ClientLoaderFunctionArgs, useLoaderData } from "@remix-run/react";
+import { useLoaderData } from "@remix-run/react";
import { useHydrateAtoms } from "jotai/utils";
-import { apiGetGame, apiGetToken } from "../.server/api/client";
import { ensureUserLoggedIn } from "../.server/auth";
-import GolfWatchApp from "../components/GolfWatchApp.client";
-import GolfWatchAppConnecting from "../components/GolfWatchApps/GolfWatchAppConnecting";
import {
- codeAAtom,
- codeBAtom,
- scoreAAtom,
- scoreBAtom,
+ ApiAuthTokenContext,
+ apiGetGame,
+ apiGetGameWatchLatestStates,
+ apiGetGameWatchRanking,
+} from "../api/client";
+import GolfWatchApp from "../components/GolfWatchApp";
+import {
setCurrentTimestampAtom,
setDurationSecondsAtom,
- submitResultAAtom,
- submitResultBAtom,
+ setGameStartedAtAtom,
+ setLatestGameStatesAtom,
+ setRankingAtom,
} from "../states/watch";
-import { PlayerState } from "../types/PlayerState";
export const meta: MetaFunction<typeof loader> = ({ data }) => [
{
@@ -28,160 +28,47 @@ export const meta: MetaFunction<typeof loader> = ({ data }) => [
export async function loader({ params, request }: LoaderFunctionArgs) {
const { token } = await ensureUserLoggedIn(request);
+ const gameId = Number(params.gameId);
+
const fetchGame = async () => {
- return (await apiGetGame(token, Number(params.gameId))).game;
+ return (await apiGetGame(token, gameId)).game;
};
- const fetchSockToken = async () => {
- return (await apiGetToken(token)).token;
+ const fetchRanking = async () => {
+ return (await apiGetGameWatchRanking(token, gameId)).ranking;
};
-
- const [game, sockToken] = await Promise.all([fetchGame(), fetchSockToken()]);
-
- if (game.game_type !== "1v1") {
- throw new Response("Not Found", { status: 404 });
- }
-
- const playerStateA: PlayerState = {
- code: "",
- score: null,
- submitResult: {
- status: "waiting_submission",
- execResults: game.exec_steps.map((r) => ({
- testcase_id: r.testcase_id,
- status: "waiting_submission",
- label: r.label,
- stdout: "",
- stderr: "",
- })),
- },
+ const fetchGameStates = async () => {
+ return (await apiGetGameWatchLatestStates(token, gameId)).states;
};
- const playerStateB = structuredClone(playerStateA);
+
+ const [game, ranking, gameStates] = await Promise.all([
+ fetchGame(),
+ fetchRanking(),
+ fetchGameStates(),
+ ]);
return {
+ apiAuthToken: token,
game,
- sockToken,
- playerStateA,
- playerStateB,
+ ranking,
+ gameStates,
};
}
-export async function clientLoader({ serverLoader }: ClientLoaderFunctionArgs) {
- const data = await serverLoader<typeof loader>();
-
- const playerIdA = data.game.players[0]?.user_id;
- const playerIdB = data.game.players[1]?.user_id;
-
- if (playerIdA !== null) {
- const baseKeyA = `watcherState:${data.game.game_id}:${playerIdA}`;
-
- const localCodeA = (() => {
- const rawValue = window.localStorage.getItem(`${baseKeyA}:code`);
-
- if (rawValue === null) {
- return null;
- }
- return rawValue;
- })();
-
- const localScoreA = (() => {
- const rawValue = window.localStorage.getItem(`${baseKeyA}:score`);
- if (rawValue === null || rawValue === "") {
- return null;
- }
- return Number(rawValue);
- })();
-
- const localSubmissionResultA = (() => {
- const rawValue = window.localStorage.getItem(
- `${baseKeyA}:submissionResult`,
- );
- if (rawValue === null) {
- return null;
- }
- const parsed = JSON.parse(rawValue);
- if (typeof parsed !== "object") {
- return null;
- }
- return parsed;
- })();
-
- if (localCodeA !== null) {
- data.playerStateA.code = localCodeA;
- }
- if (localScoreA !== null) {
- data.playerStateA.score = localScoreA;
- }
- if (localSubmissionResultA !== null) {
- data.playerStateA.submitResult = localSubmissionResultA;
- }
- }
-
- if (playerIdB !== null) {
- const baseKeyB = `watcherState:${data.game.game_id}:${playerIdB}`;
-
- const localCodeB = (() => {
- const rawValue = window.localStorage.getItem(`${baseKeyB}:code`);
- if (rawValue === null) {
- return null;
- }
- return rawValue;
- })();
-
- const localScoreB = (() => {
- const rawValue = window.localStorage.getItem(`${baseKeyB}:score`);
- if (rawValue === null || rawValue === "") {
- return null;
- }
- return Number(rawValue);
- })();
-
- const localSubmissionResultB = (() => {
- const rawValue = window.localStorage.getItem(
- `${baseKeyB}:submissionResult`,
- );
- if (rawValue === null) {
- return null;
- }
- const parsed = JSON.parse(rawValue);
- if (typeof parsed !== "object") {
- return null;
- }
- return parsed;
- })();
-
- if (localCodeB !== null) {
- data.playerStateB.code = localCodeB;
- }
- if (localScoreB !== null) {
- data.playerStateB.score = localScoreB;
- }
- if (localSubmissionResultB !== null) {
- data.playerStateB.submitResult = localSubmissionResultB;
- }
- }
-
- return data;
-}
-clientLoader.hydrate = true;
-
-export function HydrateFallback() {
- return <GolfWatchAppConnecting />;
-}
-
export default function GolfWatch() {
- const { game, sockToken, playerStateA, playerStateB } =
+ const { apiAuthToken, game, ranking, gameStates } =
useLoaderData<typeof loader>();
useHydrateAtoms([
[setCurrentTimestampAtom, undefined],
[setDurationSecondsAtom, game.duration_seconds],
- [codeAAtom, playerStateA.code],
- [codeBAtom, playerStateB.code],
- [scoreAAtom, playerStateA.score],
- [scoreBAtom, playerStateB.score],
- [submitResultAAtom, playerStateA.submitResult],
- [submitResultBAtom, playerStateB.submitResult],
+ [setGameStartedAtAtom, game.started_at ?? null],
+ [setRankingAtom, ranking],
+ [setLatestGameStatesAtom, gameStates],
]);
- return <GolfWatchApp game={game} sockToken={sockToken} />;
+ return (
+ <ApiAuthTokenContext.Provider value={apiAuthToken}>
+ <GolfWatchApp game={game} />
+ </ApiAuthTokenContext.Provider>
+ );
}