mirror of
https://github.com/HexCardGames/HexDeck.git
synced 2025-09-05 11:18:38 +02:00
feat: add game store for lobby overlay/player management
This commit is contained in:
@ -55,6 +55,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- TODO dedicated rejoinRoom and rejoinSession loading states & error messages -->
|
||||||
<div class="w-full max-w-md">
|
<div class="w-full max-w-md">
|
||||||
{#if $rejoinRoomSessionData?.sessionToken}
|
{#if $rejoinRoomSessionData?.sessionToken}
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
|
@ -21,9 +21,13 @@
|
|||||||
UserX,
|
UserX,
|
||||||
Play,
|
Play,
|
||||||
TextCursorInput,
|
TextCursorInput,
|
||||||
|
|
||||||
|
Gamepad2
|
||||||
|
|
||||||
} from "lucide-svelte";
|
} from "lucide-svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import { GameState, sessionStore } from "../../stores/sessionStore";
|
import { GameState, sessionStore } from "../../stores/sessionStore";
|
||||||
|
import { toggleLobbyOverlay } from '../../stores/gameStore';
|
||||||
|
|
||||||
let copied = false;
|
let copied = false;
|
||||||
let showLeaveModal = false;
|
let showLeaveModal = false;
|
||||||
@ -160,16 +164,27 @@
|
|||||||
<span>{$_("lobby.leave_game")}</span>
|
<span>{$_("lobby.leave_game")}</span>
|
||||||
</Button>
|
</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 -->
|
<!-- Game Status -->
|
||||||
<div class="text-center p-6 w-full">
|
<div class="text-center p-6 w-full">
|
||||||
<span
|
<span>{$_("game_status.game_status")}
|
||||||
>{$_("game_status.game_status", {
|
<Badge color="dark">
|
||||||
values: {
|
{$_(`game_status.${GameState[$sessionStore.gameState].toLowerCase()}`)}
|
||||||
game_status: $_(
|
</Badge>
|
||||||
`game_status.${GameState[$sessionStore.gameState].toLowerCase()}`,
|
</span
|
||||||
),
|
|
||||||
},
|
|
||||||
})}</span
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -181,6 +196,8 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Copy Join Code Button -->
|
<!-- Copy Join Code Button -->
|
||||||
|
<!-- TODO add Streamer mode (hide room code) here -->
|
||||||
|
{#if sessionStore.getState().gameState === GameState.Lobby}
|
||||||
<Button
|
<Button
|
||||||
id="b1"
|
id="b1"
|
||||||
type="button"
|
type="button"
|
||||||
@ -234,6 +251,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- Start game button -->
|
<!-- Start game button -->
|
||||||
{#if sessionStore.getPlayerPermissions().isHost && sessionStore.getState().gameState === GameState.Lobby}
|
{#if sessionStore.getPlayerPermissions().isHost && sessionStore.getState().gameState === GameState.Lobby}
|
||||||
@ -265,7 +283,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Players Table -->
|
<!-- Players Table -->
|
||||||
<Table striped hoverable>
|
<Table striped hoverable noborder class="mb-16">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableHeadCell class="cursor-pointer flex items-center">
|
<TableHeadCell class="cursor-pointer flex items-center">
|
||||||
{$_("lobby.player_name")}
|
{$_("lobby.player_name")}
|
||||||
@ -275,7 +293,7 @@
|
|||||||
|
|
||||||
<TableBody tableBodyClass="divide-y">
|
<TableBody tableBodyClass="divide-y">
|
||||||
{#each filteredPlayers() as player}
|
{#each filteredPlayers() as player}
|
||||||
<TableBodyRow>
|
<TableBodyRow class="!bg-black/2 hover:!bg-black/4 dark:!bg-white/20 dark:hover:!bg-white/30">
|
||||||
<TableBodyCell>
|
<TableBodyCell>
|
||||||
{player.Username}
|
{player.Username}
|
||||||
{#if sessionStore.isCurrentPlayer(player.PlayerId)}
|
{#if sessionStore.isCurrentPlayer(player.PlayerId)}
|
||||||
|
@ -69,14 +69,16 @@
|
|||||||
"player_name": "Player Name",
|
"player_name": "Player Name",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
"you": "You"
|
"you": "You",
|
||||||
|
"player": "Player",
|
||||||
|
"return_to_game": "Return to game"
|
||||||
},
|
},
|
||||||
"player_status": {
|
"player_status": {
|
||||||
"connected": "Connected",
|
"connected": "Connected",
|
||||||
"disconnected": "Disconnected"
|
"disconnected": "Disconnected"
|
||||||
},
|
},
|
||||||
"game_status": {
|
"game_status": {
|
||||||
"game_status": "Game status: {game_status}",
|
"game_status": "Game status:",
|
||||||
"lobby": "Lobby",
|
"lobby": "Lobby",
|
||||||
"running": "Running",
|
"running": "Running",
|
||||||
"ended": "Ended"
|
"ended": "Ended"
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
<script lang="ts">
|
<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 Lobby from "../components/Game/Lobby.svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { GameState, sessionStore } from "../stores/sessionStore";
|
import { GameState, sessionStore } from "../stores/sessionStore";
|
||||||
import { Spinner } from "flowbite-svelte";
|
import { Button, Spinner, Tooltip } from "flowbite-svelte";
|
||||||
import { SvelteDate } from "svelte/reactivity";
|
import { SvelteDate } from "svelte/reactivity";
|
||||||
import { requestJoinRoom } from "../stores/roomStore";
|
import { requestJoinRoom } from "../stores/roomStore";
|
||||||
|
import gameStore, { toggleLobbyOverlay } from "../stores/gameStore";
|
||||||
|
import { UsersRound } from "lucide-svelte";
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// TODO: check if already connected to room, currently its overwriting the session
|
// TODO: check if already connected to room, currently its overwriting the session
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const joinParam = params.get('join');
|
const joinParam = params.get("join");
|
||||||
|
|
||||||
if(joinParam) {
|
if (joinParam) {
|
||||||
await requestJoinRoom(joinParam);
|
await requestJoinRoom(joinParam);
|
||||||
// Maybe show message instead redirecting to / if the join was unsuccessful
|
// Maybe show message instead redirecting to / if the join was unsuccessful
|
||||||
}
|
}
|
||||||
@ -21,7 +25,7 @@
|
|||||||
console.warn("No sessionData found! Go back home.");
|
console.warn("No sessionData found! Go back home.");
|
||||||
window.history.replaceState({}, "", "/");
|
window.history.replaceState({}, "", "/");
|
||||||
}
|
}
|
||||||
sessionStore.connect()
|
sessionStore.connect();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -35,23 +39,40 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid items-center text-center md:items-start">
|
<div class="grid items-center text-center md:items-start">
|
||||||
<span class="text-2xl font-medium">
|
<span class="text-2xl font-medium">
|
||||||
{$_('game_screen.loading')}
|
{$_("game_screen.loading")}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-medium text-sky-500">{$sessionStore.players?.find((player) => player.PlayerId == $sessionStore.userId)?.Username}</span>
|
<span class="font-medium text-sky-500">
|
||||||
<span
|
{$sessionStore.players?.find(
|
||||||
class="flex gap-2 font-medium text-gray-600 dark:text-gray-400"
|
(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>{new SvelteDate().toLocaleString()}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if $sessionStore.gameState == GameState.Lobby}
|
{:else}
|
||||||
|
{#if $sessionStore.gameState == GameState.Lobby}
|
||||||
<div>
|
<div>
|
||||||
|
<!-- Lobby and player list -->
|
||||||
<Lobby />
|
<Lobby />
|
||||||
</div>
|
</div>
|
||||||
{:else if $sessionStore.gameState == GameState.Running}
|
{/if}
|
||||||
<div>Running Game</div>
|
|
||||||
{:else if $sessionStore.gameState == GameState.Ended}
|
{#if $sessionStore.gameState == GameState.Running}
|
||||||
<div>Game Ended</div>
|
<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}
|
{/if}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import { theme, toggleTheme } from "../stores/theme";
|
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 { Tooltip, Button } from "flowbite-svelte";
|
||||||
import options from "../stores/pageoptions";
|
import options from "../stores/pageoptions";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
import gameStore, { toggleLobbyOverlay } from "../stores/gameStore";
|
||||||
|
import { GameState, sessionStore } from "../stores/sessionStore";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="Header">
|
<header class="Header">
|
||||||
@ -16,7 +18,8 @@
|
|||||||
<h1 class="text-3xl">{$_("page_name")}</h1>
|
<h1 class="text-3xl">{$_("page_name")}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="middle-header-group header-group"></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
|
<Button
|
||||||
on:click={toggleTheme}
|
on:click={toggleTheme}
|
||||||
class="!p-2 mt-2 rounded-full focus:bg-primary-700 hover:bg-primary-600 focus:ring-0"
|
class="!p-2 mt-2 rounded-full focus:bg-primary-700 hover:bg-primary-600 focus:ring-0"
|
||||||
@ -30,24 +33,66 @@
|
|||||||
<SunMoon size="2rem" />
|
<SunMoon size="2rem" />
|
||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
<Tooltip type='auto'>
|
<Tooltip type="auto">
|
||||||
{$_("header.theme_btn.tooltip", { values: { current_theme: $_(`header.theme_btn.${$theme}`)}})}
|
{$_("header.theme_btn.tooltip", {
|
||||||
|
values: { current_theme: $_(`header.theme_btn.${$theme}`) },
|
||||||
|
})}
|
||||||
</Tooltip>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="page-slot">
|
<div class="main-container">
|
||||||
|
<div class="page-slot">
|
||||||
<slot />
|
<slot />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.main-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
padding-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.page-slot {
|
.page-slot {
|
||||||
margin-top: 90px;
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Header {
|
.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;
|
opacity: 1;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
@ -69,6 +114,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Header-content {
|
.Header-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
import options from "../stores/pageoptions";
|
import options from "../stores/pageoptions";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="overflow-auto size-full pb-18">
|
||||||
<div class="flex justify-center mb-8">
|
<div class="flex justify-center mb-8">
|
||||||
<div>
|
<div>
|
||||||
<!-- Top Title container -->
|
<!-- Top Title container -->
|
||||||
<div class="flex items-center space-x-2 my-6">
|
<div class="flex items-center space-x-2 my-6">
|
||||||
@ -26,24 +26,44 @@
|
|||||||
<!-- Join or create rooms -->
|
<!-- Join or create rooms -->
|
||||||
<ConnectRoom />
|
<ConnectRoom />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-8">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-8">
|
||||||
|
|
||||||
<!-- Open source container -->
|
<!-- 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">
|
<div
|
||||||
<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>
|
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"
|
||||||
<h4 class="text-xl font-semibold">{$_("landing_page.open_source_container.title")}</h4>
|
>
|
||||||
|
<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>
|
<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">
|
<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")}
|
{$_("landing_page.open_source_container.github")}
|
||||||
</GradientButton>
|
</GradientButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- stats container -->
|
<!-- stats container -->
|
||||||
<StatsContainer />
|
<StatsContainer />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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