From a2b6ed9cd67f1406a6656bce9b3d51b55378ac1e Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Thu, 22 Aug 2024 03:57:05 +0900
Subject: feat(frontend): jotai for play app

---
 frontend/app/routes/golf.$gameId.play.tsx | 97 +++++++++++++++++++++++++++++--
 1 file changed, 91 insertions(+), 6 deletions(-)

(limited to 'frontend/app/routes/golf.$gameId.play.tsx')

diff --git a/frontend/app/routes/golf.$gameId.play.tsx b/frontend/app/routes/golf.$gameId.play.tsx
index ea1b8fd..a2860dd 100644
--- a/frontend/app/routes/golf.$gameId.play.tsx
+++ b/frontend/app/routes/golf.$gameId.play.tsx
@@ -1,10 +1,17 @@
 import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
-import { useLoaderData } from "@remix-run/react";
-import { ClientOnly } from "remix-utils/client-only";
+import { ClientLoaderFunctionArgs, 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,
+	setCurrentTimestampAtom,
+	setDurationSecondsAtom,
+	submitResultAtom,
+} from "../states/play";
+import { PlayerState } from "../types/PlayerState";
 
 export const meta: MetaFunction<typeof loader> = ({ data }) => [
 	{
@@ -25,19 +32,97 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
 	};
 
 	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: "",
+			})),
+		},
+	};
+
 	return {
 		game,
 		player: user,
 		sockToken,
+		playerState,
 	};
 }
 
+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 } = useLoaderData<typeof loader>();
+	const { game, player, sockToken, playerState } =
+		useLoaderData<typeof loader>();
+
+	useHydrateAtoms([
+		[setCurrentTimestampAtom, undefined],
+		[setDurationSecondsAtom, game.duration_seconds],
+		[scoreAtom, playerState.score],
+		[submitResultAtom, playerState.submitResult],
+	]);
 
 	return (
-		<ClientOnly fallback={<GolfPlayAppConnecting />}>
-			{() => <GolfPlayApp game={game} player={player} sockToken={sockToken} />}
-		</ClientOnly>
+		<GolfPlayApp
+			game={game}
+			player={player}
+			initialCode={playerState.code}
+			sockToken={sockToken}
+		/>
 	);
 }
-- 
cgit v1.2.3-70-g09d2

