mirror of
https://github.com/Xevion/smart-rgb.git
synced 2025-12-15 04:13:21 -06:00
Update source files
This commit is contained in:
211
frontend/pages/index/+Page.tsx
Normal file
211
frontend/pages/index/+Page.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
import { useState, useEffect, lazy, Suspense } from "react";
|
||||
import "./+Page.css";
|
||||
import { Attacks } from "@/shared/components/Attacks";
|
||||
import { AttackControls } from "@/shared/components/AttackControls";
|
||||
import { Leaderboard } from "@/shared/components/Leaderboard";
|
||||
import { MenuScreen } from "@/shared/components/MenuScreen";
|
||||
import { GameMenu } from "@/shared/components/GameMenu";
|
||||
import { SpawnPhaseOverlay } from "@/shared/components/SpawnPhaseOverlay";
|
||||
import { GameCanvas } from "@/shared/components/GameCanvas";
|
||||
import { useGameAPI } from "@/shared/api/GameAPIContext";
|
||||
import { useAnalytics } from "@/shared/analytics";
|
||||
import type { GameOutcome, LeaderboardSnapshot } from "@/shared/api/types";
|
||||
import type { GameRenderer } from "@/shared/render/GameRenderer";
|
||||
|
||||
// Lazy load conditional components that aren't needed immediately
|
||||
const GameEndOverlay = lazy(() => import("@/shared/components/GameEndOverlay"));
|
||||
const AlphaWarningModal = lazy(() => import("@/shared/components/AlphaWarningModal"));
|
||||
|
||||
function App() {
|
||||
const [showMenu, setShowMenu] = useState(true);
|
||||
const [gameStarted, setGameStarted] = useState(false);
|
||||
const [gameOutcome, setGameOutcome] = useState<GameOutcome | null>(null);
|
||||
const [spawnPhaseActive, setSpawnPhaseActive] = useState(false);
|
||||
const [spawnCountdown, setSpawnCountdown] = useState<{
|
||||
startedAtMs: number;
|
||||
durationSecs: number;
|
||||
} | null>(null);
|
||||
const [initialGameState, setInitialGameState] = useState<any | null>(null);
|
||||
const [initialLeaderboard, setInitialLeaderboard] = useState<LeaderboardSnapshot | null>(null);
|
||||
const [renderer, setRenderer] = useState<GameRenderer | null>(null);
|
||||
const [highlightedNation, setHighlightedNation] = useState<number | null>(null);
|
||||
const api = useGameAPI();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
// Prefetch conditional components after initial render
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
// Trigger chunk downloads without rendering
|
||||
import("@/shared/components/GameEndOverlay");
|
||||
import("@/shared/components/AlphaWarningModal");
|
||||
}, 2000);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// Track app started on mount
|
||||
useEffect(() => {
|
||||
if (!analytics) return;
|
||||
analytics.track("app_started", {
|
||||
platform: __DESKTOP__ ? "desktop" : "browser",
|
||||
});
|
||||
}, [analytics]);
|
||||
|
||||
// Check for existing game state on mount (for reload recovery)
|
||||
useEffect(() => {
|
||||
if (!api || typeof api.getGameState !== "function") return;
|
||||
|
||||
api.getGameState().then((state) => {
|
||||
// Only recover if we actually have render data (indicates a running game)
|
||||
if (state && state.render_init) {
|
||||
console.log("Recovered game state after reload:", state);
|
||||
setInitialGameState(state.render_init);
|
||||
setInitialLeaderboard(state.leaderboard_snapshot);
|
||||
setGameStarted(true);
|
||||
setShowMenu(false);
|
||||
}
|
||||
});
|
||||
}, [api]);
|
||||
|
||||
// Subscribe to spawn phase events
|
||||
useEffect(() => {
|
||||
if (!api) return;
|
||||
|
||||
const unsubUpdate = api.onSpawnPhaseUpdate((update) => {
|
||||
setSpawnPhaseActive(true);
|
||||
setSpawnCountdown(update.countdown);
|
||||
});
|
||||
|
||||
const unsubEnd = api.onSpawnPhaseEnded(() => {
|
||||
setSpawnPhaseActive(false);
|
||||
setSpawnCountdown(null);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubUpdate();
|
||||
unsubEnd();
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
// Subscribe to game end events
|
||||
useEffect(() => {
|
||||
if (!gameStarted || !api) return;
|
||||
|
||||
const unsubscribe = api.onGameEnded((outcome) => {
|
||||
console.log("Game outcome received:", outcome);
|
||||
setGameOutcome(outcome);
|
||||
setSpawnPhaseActive(false); // Hide spawn overlay on game end
|
||||
// Track game ended
|
||||
if (analytics) {
|
||||
analytics.track("game_ended", {
|
||||
outcome: outcome.toString().toLowerCase(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return () => unsubscribe();
|
||||
}, [gameStarted, api, analytics]);
|
||||
|
||||
// Track renderer initialization with GPU info
|
||||
useEffect(() => {
|
||||
if (renderer && analytics) {
|
||||
const rendererInfo = renderer.getRendererInfo();
|
||||
analytics.track("renderer_initialized", rendererInfo);
|
||||
}
|
||||
}, [renderer, analytics]);
|
||||
|
||||
// Sync highlighted nation with renderer
|
||||
useEffect(() => {
|
||||
if (renderer) {
|
||||
renderer.setHighlightedNation(highlightedNation);
|
||||
}
|
||||
}, [highlightedNation, renderer]);
|
||||
|
||||
const handleStartSingleplayer = () => {
|
||||
setShowMenu(false);
|
||||
setGameStarted(true);
|
||||
// Start the game in the backend
|
||||
if (api) {
|
||||
api.startGame();
|
||||
}
|
||||
// Track game started
|
||||
if (analytics) {
|
||||
analytics.track("game_started", {
|
||||
mode: "singleplayer",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleExit = async () => {
|
||||
if (__DESKTOP__) {
|
||||
const { invoke } = await import("@tauri-apps/api/core");
|
||||
await invoke("request_exit").catch((err) => {
|
||||
console.error("Failed to request exit:", err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Game Canvas - always rendered at root, hidden under menu initially */}
|
||||
<GameCanvas
|
||||
className="game-canvas"
|
||||
initialState={initialGameState}
|
||||
onRendererReady={setRenderer}
|
||||
onNationHover={setHighlightedNation}
|
||||
/>
|
||||
|
||||
{/* Menu Screen - covers everything when visible */}
|
||||
<MenuScreen onStartSingleplayer={handleStartSingleplayer} onExit={handleExit} isVisible={showMenu} />
|
||||
|
||||
{/* Game UI - only visible when game is started */}
|
||||
{gameStarted && (
|
||||
<div className="game-ui">
|
||||
{/* Spawn Phase Overlay */}
|
||||
<SpawnPhaseOverlay isVisible={spawnPhaseActive} countdown={spawnCountdown} />
|
||||
|
||||
<Leaderboard
|
||||
initialSnapshot={initialLeaderboard}
|
||||
highlightedNation={highlightedNation}
|
||||
onNationHover={setHighlightedNation}
|
||||
/>
|
||||
<AttackControls>
|
||||
<Attacks onNationHover={setHighlightedNation} />
|
||||
</AttackControls>
|
||||
<GameMenu
|
||||
onExit={() => {
|
||||
if (api) {
|
||||
api.quitGame();
|
||||
}
|
||||
setGameOutcome(null);
|
||||
setGameStarted(false);
|
||||
setShowMenu(true);
|
||||
}}
|
||||
onSettings={() => {
|
||||
// TODO: Implement settings
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Game End Overlay */}
|
||||
{gameOutcome && (
|
||||
<Suspense fallback={null}>
|
||||
<GameEndOverlay
|
||||
outcome={gameOutcome}
|
||||
onSpectate={() => setGameOutcome(null)}
|
||||
onExit={() => {
|
||||
if (api) {
|
||||
api.quitGame();
|
||||
}
|
||||
setGameOutcome(null);
|
||||
setGameStarted(false);
|
||||
setShowMenu(true);
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user