Files
HexDeck/frontend/src/views/Game/Lobby.svelte

325 lines
14 KiB
Svelte

<script lang="ts">
import RenamePlayer from "../../components/RenamePlayer.svelte";
import CardDeckShowcase from "../../components/CardDeckShowcase.svelte";
import { Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, TableSearch, Badge, Button, Modal, Popover, Tooltip, Card } from "flowbite-svelte";
import { CircleArrowOutUpLeft, Copy, AlertCircle, UserX, Play, TextCursorInput, Gamepad2 } from "lucide-svelte";
import { _ } from "svelte-i18n";
import { GameState, sessionStore } from "../../stores/sessionStore";
import { toggleLobbyOverlay } from "../../stores/gameStore";
import { CardDecks } from "../../stores/cardDeck";
let copied = false;
let showLeaveModal = false;
$: players = $sessionStore.players;
let searchQuery = "";
let rename_player = "";
let showRenameModal = false;
let kick_player = "";
let showKickModal = false;
let showCardDeckModal = false;
export let maxRotationDeg: number;
export let centerDistancePx: number;
export let cardWidth: number;
export let cardHeight: number;
function filteredPlayers() {
return players.filter((player) => player.Username.toLowerCase().includes(searchQuery.toLowerCase()));
}
function insert(str: string, index: number, value: string) {
return str.slice(0, index) + value + str.slice(index);
}
function copyGameCodeToClipboard() {
navigator.clipboard.writeText(insert($sessionStore.joinCode || "000000", 3, "-")).then(() => {
copied = true;
setTimeout(() => (copied = false), 2000);
});
}
function copyGameLinkToClipboard() {
navigator.clipboard.writeText(`${window.location.origin}/Game?join=${$sessionStore.joinCode}`).then(() => {
copied = true;
setTimeout(() => (copied = false), 2000);
});
}
function leaveRoom() {
sessionStore.leaveRoom();
showLeaveModal = false;
}
</script>
<!-- Modal: Confirm Leave Room -->
<Modal bind:open={showLeaveModal} size="md" backdropClass="fixed inset-0 z-40 bg-gray-900 bg-black/50 dark:bg-black/80 backdrop-opacity-50" autoclose outsideclose>
<div class="text-center">
<AlertCircle class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200" />
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">
{$_("lobby.confirm_leave_message")}
</h3>
<Button on:click={() => (showLeaveModal = false)} color="alternative" class="hover:text-dark hover:bg-gray-100">{$_("lobby.cancel")}</Button>
<Button on:click={leaveRoom} color="red" class="me-2">{$_("lobby.confirm_leave")}</Button>
</div>
</Modal>
<!-- Modal: Rename Player -->
<Modal bind:open={showRenameModal} size="md" backdropClass="fixed inset-0 z-40 bg-gray-900 bg-black/50 dark:bg-black/80 backdrop-opacity-50" autoclose outsideclose>
<div class="text-center">
<TextCursorInput class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200" />
<RenamePlayer playerId={rename_player} />
</div>
</Modal>
<!-- Modal: Confirm Kick Player -->
<Modal bind:open={showKickModal} size="md" backdropClass="fixed inset-0 z-40 bg-gray-900 bg-black/50 dark:bg-black/80 backdrop-opacity-50" autoclose outsideclose>
<div class="text-center">
<UserX class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200" />
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">
{$_("lobby.confirm_kick_player_message", {
values: { player_name: sessionStore.getUser(kick_player)?.Username || "Name not found" },
})}
</h3>
<Button on:click={() => (showLeaveModal = false)} color="alternative" class="hover:text-dark hover:bg-gray-100">{$_("lobby.cancel")}</Button>
<Button
on:click={() => {
sessionStore.kickPlayer(kick_player);
}}
color="red"
class="me-2">{$_("lobby.confirm_kick_player")}</Button
>
</div>
</Modal>
<!-- Modal: Select card deck -->
<Modal bind:open={showCardDeckModal} size="md" backdropClass="fixed inset-0 z-40 bg-gray-900 bg-black/50 dark:bg-black/80 backdrop-opacity-50" style="color: unset;" autoclose outsideclose>
<div class="text-center">
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">
{$_("lobby.card_deck_modal")}
</h3>
<div class="grid justify-center gap-5">
{#each CardDecks as cardDeck}
<Card style="color: unset;">
<span class="mb-[-15px] text-xl">{cardDeck.name}</span>
<div class="flex justify-center">
<CardDeckShowcase cardComponent={cardDeck.cardComponent} cardDeckId={cardDeck.id} {cardHeight} {cardWidth} centerDistancePx={centerDistancePx * 3} {maxRotationDeg} />
</div>
<Button
on:click={() => {
sessionStore.setCardDeck(cardDeck.id);
}}>{$_("lobby.choose_card_deck")}</Button
>
</Card>
{/each}
</div>
<Button on:click={() => (showCardDeckModal = false)} color="alternative" class="mt-5 hover:text-dark hover:bg-gray-100">{$_("lobby.cancel")}</Button>
</div>
</Modal>
<div class="flex">
<!-- Leave Room Button -->
<Button
color="none"
class="m-2 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={() => {
showLeaveModal = true;
}}
>
<CircleArrowOutUpLeft class="mr-2" />
<span>{$_("lobby.leave_game")}</span>
</Button>
<!-- Return to game Button -->
{#if sessionStore.getState().gameState !== GameState.Lobby}
<Button
color="none"
class="m-2 ml-auto 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}
</div>
<!-- Game Status -->
<div class="md:mt-[-65px] text-center p-6 w-full">
<span
>{$_("game_status.game_status")}
<Badge color="dark">
{$_(`game_status.${GameState[$sessionStore.gameState].toLowerCase()}`)}
</Badge>
</span>
</div>
<div class="grid lg:grid-flow-col grid-flow-row justify-center mt-6 mb-2 gap-4">
<!-- Rename (This) Player -->
{#if sessionStore.isConnected()}
<RenamePlayer />
{/if}
<!-- Copy Join Code Button -->
<!-- TODO add Streamer mode (hide room code) here -->
{#if sessionStore.getState().gameState === GameState.Lobby}
<Button
id="b1"
type="button"
class="w-xs mx-auto text-dark bg-primary-200 dark:bg-primary-900 hover:bg-primary-200 dark:hover:bg-primary-900 backdrop-blur-lg border border-black/20 dark:border-white/20 shadow-lg p-4 rounded-2xl flex items-center justify-between transition-all cursor-pointer"
on:click={() => {
copyGameCodeToClipboard();
}}
>
<div class="grid justify-items-start">
<span class="text-sm">{$_("lobby.room_join_code")}</span>
<div class="relative">
<span class="text-xl font-semibold tracking-widest select-none transition-opacity duration-300 opacity-0" class:opacity-100={!copied}
>{insert($sessionStore.joinCode || "000000", 3, "-")}
</span>
<span class="absolute left-0 text-xl font-semibold tracking-widest select-none transition-opacity duration-300 opacity-0" class:opacity-100={copied}>
{$_("lobby.copied")}
</span>
</div>
</div>
<Copy />
</Button>
<Popover class="text-sm max-w-screen font-light z-100" triggeredBy="#b1" placement="bottom">
<div class="grid gap-2">
<Button
on:click={() => {
copyGameCodeToClipboard();
}}
>
{$_("lobby.copy_code")}
</Button>
<Button
on:click={() => {
copyGameLinkToClipboard();
}}
>
{$_("lobby.copy_join_link")}
</Button>
{#if sessionStore.getPlayerPermissions().isHost}
<Button on:click={() => {}}>
{$_("lobby.regenerate_join_code")}
</Button>
{/if}
</div>
</Popover>
{/if}
<!-- Start game button -->
{#if sessionStore.getPlayerPermissions().isHost && sessionStore.getState().gameState === GameState.Lobby}
<Button
class="w-xs mx-auto text-dark bg-green-200 dark:bg-green-900 hover:bg-green-200 dark:hover:bg-green-900 backdrop-blur-lg border border-black/20 dark:border-white/20 shadow-lg p-4 rounded-2xl flex items-center justify-between transition-all cursor-pointer"
on:click={() => {
sessionStore.startGame();
}}
>
<div class="grid justify-items-start">
<div class="relative">
<span class="text-xl font-semibold tracking-widest select-none transition-opacity duration-300">{$_("lobby.start_game")} </span>
</div>
</div>
<Play />
</Button>
{/if}
</div>
{#if players.length > 5}
<!-- Search Bar -->
<TableSearch bind:inputValue={searchQuery} placeholder={$_("lobby.search_player")} />
{/if}
<!-- Card deck selection -->
{#if sessionStore.getState().gameState == GameState.Lobby}
<div class="flex w-full justify-center my-10">
<Card style="color: unset;">
<span class="text-xl">{$_("lobby.selected_card_deck")}</span>
<span class="opacity-80 mb-[-15px]">{CardDecks.find((e) => e.id == $sessionStore.cardDeckId)?.name}</span>
<div class="flex justify-center">
<CardDeckShowcase
cardComponent={CardDecks.find((e) => e.id == $sessionStore.cardDeckId)?.cardComponent}
cardDeckId={CardDecks.find((e) => e.id == $sessionStore.cardDeckId)?.id ?? 0}
{cardHeight}
{cardWidth}
centerDistancePx={centerDistancePx * 3}
{maxRotationDeg}
/>
</div>
{#if sessionStore.getPlayerPermissions().isHost}
<Button on:click={() => (showCardDeckModal = true)}>{$_("lobby.change_card_deck")}</Button>
{/if}
</Card>
</div>
{/if}
<!-- Players Table -->
<Table striped hoverable noborder class="mb-16">
<TableHead>
<TableHeadCell class="cursor-pointer flex items-center">
{$_("lobby.player_name")}
</TableHeadCell>
<TableHeadCell>{$_("lobby.status")}</TableHeadCell>
</TableHead>
<TableBody tableBodyClass="divide-y">
{#each filteredPlayers() as player}
<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)}
<Badge color="purple" class="ml-1">{$_("lobby.you")}</Badge>
{/if}
{#if sessionStore.getPlayerPermissions(player.PlayerId).isHost}
<Badge color="blue" class="ml-1">{$_("lobby.host")}</Badge>
{/if}
</TableBodyCell>
<TableBodyCell>
{#if player.IsConnected}
<Badge color="green">{$_(`player_status.connected`)}</Badge>
{:else}
<Badge color="yellow">{$_(`player_status.disconnected`)}</Badge>
{/if}
</TableBodyCell>
<!-- Can kick and rename player -->
{#if sessionStore.getPlayerPermissions().isHost}
<TableBodyCell>
<!-- kick player -->
<Button
outline={true}
color="alternative"
class="p-2! text-red-800 hover:bg-red-500"
size="lg"
on:click={() => {
showKickModal = true;
kick_player = player.PlayerId;
}}
>
<UserX class="w-7 h-7" />
</Button>
<Tooltip type="auto">{$_("lobby.kick_player")}</Tooltip>
<!-- rename player -->
<Button
outline={true}
color="alternative"
class="p-2! text-blue-800 hover:bg-blue-500"
size="lg"
on:click={() => {
showRenameModal = true;
rename_player = player.PlayerId;
}}
>
<TextCursorInput class="w-7 h-7" />
</Button>
<Tooltip type="auto">{$_("lobby.rename_player")}</Tooltip>
</TableBodyCell>
{/if}
</TableBodyRow>
{/each}
</TableBody>
</Table>