mirror of
https://github.com/HexCardGames/HexDeck.git
synced 2025-09-04 02:48:39 +02:00
feat: add game store for lobby overlay/player management
This commit is contained in:
@ -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">
|
||||
|
@ -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)}
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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 />
|
||||
|
20
frontend/src/stores/gameStore.ts
Normal file
20
frontend/src/stores/gameStore.ts
Normal 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;
|
Reference in New Issue
Block a user