feat: add game store for lobby overlay/player management

This commit is contained in:
pixii
2025-03-07 09:38:35 +01:00
parent e5f8876464
commit 8fe9519afc
7 changed files with 204 additions and 76 deletions

View File

@ -55,6 +55,7 @@
});
</script>
<!-- TODO dedicated rejoinRoom and rejoinSession loading states & error messages -->
<div class="w-full max-w-md">
{#if $rejoinRoomSessionData?.sessionToken}
<div class="mb-6">

View File

@ -21,9 +21,13 @@
UserX,
Play,
TextCursorInput,
Gamepad2
} from "lucide-svelte";
import { _ } from "svelte-i18n";
import { GameState, sessionStore } from "../../stores/sessionStore";
import { toggleLobbyOverlay } from '../../stores/gameStore';
let copied = false;
let showLeaveModal = false;
@ -160,16 +164,27 @@
<span>{$_("lobby.leave_game")}</span>
</Button>
<!-- Return to game Button -->
{#if sessionStore.getState().gameState !== GameState.Lobby}
<Button
color="none"
class="sm:absolute m-2 right-0 border-2 border-gray-500 dark:border-gray-300 hover:bg-gray-500 dark:hover:bg-gray-300 hover:text-white dark:hover:text-black rounded-full text-gray-500 dark:text-gray-300"
on:click={() => {
toggleLobbyOverlay();
}}
>
<span>{$_("lobby.return_to_game")}</span>
<Gamepad2 class="ml-2" />
</Button>
{/if}
<!-- Game Status -->
<div class="text-center p-6 w-full">
<span
>{$_("game_status.game_status", {
values: {
game_status: $_(
`game_status.${GameState[$sessionStore.gameState].toLowerCase()}`,
),
},
})}</span
<span>{$_("game_status.game_status")}
<Badge color="dark">
{$_(`game_status.${GameState[$sessionStore.gameState].toLowerCase()}`)}
</Badge>
</span
>
</div>
@ -181,6 +196,8 @@
{/if}
<!-- Copy Join Code Button -->
<!-- TODO add Streamer mode (hide room code) here -->
{#if sessionStore.getState().gameState === GameState.Lobby}
<Button
id="b1"
type="button"
@ -234,6 +251,7 @@
{/if}
</div>
</Popover>
{/if}
<!-- Start game button -->
{#if sessionStore.getPlayerPermissions().isHost && sessionStore.getState().gameState === GameState.Lobby}
@ -265,7 +283,7 @@
{/if}
<!-- Players Table -->
<Table striped hoverable>
<Table striped hoverable noborder class="mb-16">
<TableHead>
<TableHeadCell class="cursor-pointer flex items-center">
{$_("lobby.player_name")}
@ -275,7 +293,7 @@
<TableBody tableBodyClass="divide-y">
{#each filteredPlayers() as player}
<TableBodyRow>
<TableBodyRow class="!bg-black/2 hover:!bg-black/4 dark:!bg-white/20 dark:hover:!bg-white/30">
<TableBodyCell>
{player.Username}
{#if sessionStore.isCurrentPlayer(player.PlayerId)}

View File

@ -69,14 +69,16 @@
"player_name": "Player Name",
"status": "Status",
"host": "Host",
"you": "You"
"you": "You",
"player": "Player",
"return_to_game": "Return to game"
},
"player_status": {
"connected": "Connected",
"disconnected": "Disconnected"
},
"game_status": {
"game_status": "Game status: {game_status}",
"game_status": "Game status:",
"lobby": "Lobby",
"running": "Running",
"ended": "Ended"

View File

@ -1,18 +1,22 @@
<script lang="ts">
import EndScreen from "./../components/Game/EndScreen.svelte";
import Main from "./../components/Game/Main.svelte";
import Lobby from "../components/Game/Lobby.svelte";
import { _ } from "svelte-i18n";
import { onMount } from "svelte";
import { GameState, sessionStore } from "../stores/sessionStore";
import { Spinner } from "flowbite-svelte";
import { Button, Spinner, Tooltip } from "flowbite-svelte";
import { SvelteDate } from "svelte/reactivity";
import { requestJoinRoom } from "../stores/roomStore";
import gameStore, { toggleLobbyOverlay } from "../stores/gameStore";
import { UsersRound } from "lucide-svelte";
onMount(async () => {
// TODO: check if already connected to room, currently its overwriting the session
const params = new URLSearchParams(window.location.search);
const joinParam = params.get('join');
const joinParam = params.get("join");
if(joinParam) {
if (joinParam) {
await requestJoinRoom(joinParam);
// Maybe show message instead redirecting to / if the join was unsuccessful
}
@ -21,7 +25,7 @@
console.warn("No sessionData found! Go back home.");
window.history.replaceState({}, "", "/");
}
sessionStore.connect()
sessionStore.connect();
});
</script>
@ -35,23 +39,40 @@
</div>
<div class="grid items-center text-center md:items-start">
<span class="text-2xl font-medium">
{$_('game_screen.loading')}
{$_("game_screen.loading")}
</span>
<span class="font-medium text-sky-500">{$sessionStore.players?.find((player) => player.PlayerId == $sessionStore.userId)?.Username}</span>
<span
class="flex gap-2 font-medium text-gray-600 dark:text-gray-400"
>
<span class="font-medium text-sky-500">
{$sessionStore.players?.find(
(player) => player.PlayerId == $sessionStore.userId,
)?.Username}
</span>
<span class="flex gap-2 font-medium text-gray-600 dark:text-gray-400">
<span>{new SvelteDate().toLocaleString()}</span>
</span>
</div>
</div>
</div>
{:else if $sessionStore.gameState == GameState.Lobby}
<div>
<Lobby />
</div>
{:else if $sessionStore.gameState == GameState.Running}
<div>Running Game</div>
{:else if $sessionStore.gameState == GameState.Ended}
<div>Game Ended</div>
{:else}
{#if $sessionStore.gameState == GameState.Lobby}
<div>
<!-- Lobby and player list -->
<Lobby />
</div>
{/if}
{#if $sessionStore.gameState == GameState.Running}
<div class="size-full">
{#if $gameStore.isLobbyOverlayShown}
<div class="absolute inset-0 z-10 bg-white/30 dark:bg-black/30 backdrop-blur-sm mt-24">
<Lobby />
</div>
{/if}
<!-- Running game -->
<Main />
</div>
{:else if $sessionStore.gameState == GameState.Ended}
<div>
<EndScreen />
</div>
{/if}
{/if}

View File

@ -1,9 +1,11 @@
<script>
import { theme, toggleTheme } from "../stores/theme";
import { Moon, Sun, SunMoon } from "lucide-svelte";
import { Gamepad2, Moon, Sun, SunMoon, UsersRound } from "lucide-svelte";
import { Tooltip, Button } from "flowbite-svelte";
import options from "../stores/pageoptions";
import { _ } from "svelte-i18n";
import gameStore, { toggleLobbyOverlay } from "../stores/gameStore";
import { GameState, sessionStore } from "../stores/sessionStore";
</script>
<header class="Header">
@ -16,7 +18,8 @@
<h1 class="text-3xl">{$_("page_name")}</h1>
</div>
<div class="middle-header-group header-group"></div>
<div class="right-header-group header-group">
<div class="right-header-group header-group gap-2">
<!-- Theme btn -->
<Button
on:click={toggleTheme}
class="!p-2 mt-2 rounded-full focus:bg-primary-700 hover:bg-primary-600 focus:ring-0"
@ -25,29 +28,71 @@
{#if $theme === "dark"}
<Moon size="2rem" />
{:else if $theme === "light"}
<Sun size="2rem" />
<Sun size="2rem" />
{:else if $theme === "system"}
<SunMoon size="2rem" />
<SunMoon size="2rem" />
{/if}
</Button>
<Tooltip type='auto'>
{$_("header.theme_btn.tooltip", { values: { current_theme: $_(`header.theme_btn.${$theme}`)}})}
<Tooltip type="auto">
{$_("header.theme_btn.tooltip", {
values: { current_theme: $_(`header.theme_btn.${$theme}`) },
})}
</Tooltip>
<!-- Player list btn (ingame) -->
{#if $sessionStore.gameState == GameState.Running}
<Button
on:click={() => {
toggleLobbyOverlay();
}}
class="!p-2 mt-2 rounded-full focus:bg-primary-700 hover:bg-primary-600 focus:ring-0"
color="none"
>
{#if $gameStore.isLobbyOverlayShown}
<Gamepad2 size="2rem" />
{:else}
<UsersRound size="2rem" />
{/if}
</Button>
{#if $gameStore.isLobbyOverlayShown}
<Tooltip type="auto">
{$_("lobby.return_to_game")}
</Tooltip>
{:else}
<Tooltip type="auto">
{$_("lobby.player")}
</Tooltip>
{/if}
{/if}
</div>
</div>
</header>
<div class="page-slot">
<slot />
<div class="main-container">
<div class="page-slot">
<slot />
</div>
</div>
<style>
.page-slot {
margin-top: 90px;
.main-container {
display: flex;
flex-direction: column;
height: 100vh;
padding-top: 100px;
}
.page-slot {
flex-grow: 1;
overflow: hidden;
}
.Header {
background: linear-gradient(180deg, var(--default-background-color) 30%, transparent 100%);
background: linear-gradient(
180deg,
var(--default-background-color) 30%,
transparent 100%
);
opacity: 1;
position: fixed;
height: 100px;
@ -69,6 +114,7 @@
width: 100%;
height: 100%;
}
.Header-content {
display: flex;
justify-content: space-between;

View File

@ -7,43 +7,63 @@
import options from "../stores/pageoptions";
</script>
<div class="overflow-auto size-full pb-18">
<div class="flex justify-center mb-8">
<div>
<!-- Top Title container -->
<div class="flex items-center space-x-2 my-6">
<div>
<svelte:component this={options.page_icon} size="5.2rem" />
</div>
<div>
<h2 class="text-4xl">{$_("page_name")}</h2>
<h3 class="text-xl">
{$_("landing_page.sub_title")}
</h3>
</div>
</div>
<div class="flex justify-center mb-8">
<div>
<!-- Top Title container -->
<div class="flex items-center space-x-2 my-6">
<div>
<svelte:component this={options.page_icon} size="5.2rem" />
</div>
<div>
<h2 class="text-4xl">{$_("page_name")}</h2>
<h3 class="text-xl">
{$_("landing_page.sub_title")}
</h3>
</div>
<!-- Join or create rooms -->
<ConnectRoom />
</div>
</div>
<div class="flex justify-center">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-8">
<!-- Open source container -->
<div
class="p-4 bg-primary-50 dark:bg-primary-950 rounded-xl grid content-start justify-items-center w-3xs text-center space-y-2 border-1 border-primary-200 dark:border-primary-800"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="48"
height="48"
viewBox="0 0 24 24"
><path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M15.157 20.136c.211.51.8.757 1.284.492a9.25 9.25 0 1 0-8.882 0c.484.265 1.073.018 1.284-.492l1.358-3.28c.212-.51-.043-1.086-.478-1.426a3.7 3.7 0 1 1 4.554 0c-.435.34-.69.916-.478 1.426z"
/></svg
>
<h4 class="text-xl font-semibold">
{$_("landing_page.open_source_container.title")}
</h4>
<span>{$_("landing_page.open_source_container.content")}</span>
<GradientButton
color="purpleToBlue"
class="opacity-75 focus:opacity-100 focus:ring-0"
href="https://github.com/HexCardGames/HexDeck"
target="_blank"
>
{$_("landing_page.open_source_container.github")}
</GradientButton>
</div>
<!-- Join or create rooms -->
<ConnectRoom />
<!-- stats container -->
<StatsContainer />
</div>
</div>
</div>
<div class="flex justify-center">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-8">
<!-- Open source container -->
<div class="p-4 bg-primary-50 dark:bg-primary-950 rounded-xl grid content-start justify-items-center w-3xs text-center space-y-2 border-1 border-primary-200 dark:border-primary-800">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.157 20.136c.211.51.8.757 1.284.492a9.25 9.25 0 1 0-8.882 0c.484.265 1.073.018 1.284-.492l1.358-3.28c.212-.51-.043-1.086-.478-1.426a3.7 3.7 0 1 1 4.554 0c-.435.34-.69.916-.478 1.426z"/></svg>
<h4 class="text-xl font-semibold">{$_("landing_page.open_source_container.title")}</h4>
<span>{$_("landing_page.open_source_container.content")}</span>
<GradientButton color="purpleToBlue" class="opacity-75 focus:opacity-100 focus:ring-0" href="https://github.com/HexCardGames/HexDeck" target="_blank">
{$_("landing_page.open_source_container.github")}
</GradientButton>
</div>
<!-- stats container -->
<StatsContainer />
</div>
</div>
<Footer />
<Footer />

View File

@ -0,0 +1,20 @@
import { writable } from 'svelte/store';
interface GameState {
isLobbyOverlayShown: boolean;
}
const initialState: GameState = {
isLobbyOverlayShown: false,
};
const gameStore = writable<GameState>(initialState);
export const toggleLobbyOverlay = () => {
gameStore.update(state => ({
...state,
isLobbyOverlayShown: !state.isLobbyOverlayShown,
}));
};
export default gameStore;