diff --git a/src/client/Main.ts b/src/client/Main.ts index 1f11f5811..cfacc3c14 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -1,10 +1,10 @@ import { GameRunner, joinLobby } from "./GameRunner"; -import backgroundImage from '../../resources/images/TerrainMapFrontPage.png'; -import favicon from '../../resources/images/Favicon.svg'; +import backgroundImage from "../../resources/images/TerrainMapFrontPage.png"; +import favicon from "../../resources/images/Favicon.svg"; -import './PublicLobby'; -import './UsernameInput'; -import './styles.css'; +import "./PublicLobby"; +import "./UsernameInput"; +import "./styles.css"; import { UsernameInput } from "./UsernameInput"; import { SinglePlayerModal } from "./SinglePlayerModal"; import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal"; @@ -12,132 +12,152 @@ import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal"; import { generateID } from "../core/Util"; import { generateCryptoRandomUUID } from "./Utils"; import { consolex } from "../core/Consolex"; +import { PublicLobby } from "./PublicLobby"; class Client { - private gameStop: () => void + private gameStop: () => void; - private usernameInput: UsernameInput | null = null; + private usernameInput: UsernameInput | null = null; - private joinModal: JoinPrivateLobbyModal - constructor() { + private joinModal: JoinPrivateLobbyModal; + private publicLobby: PublicLobby; + constructor() {} + + initialize(): void { + this.usernameInput = document.querySelector( + "username-input" + ) as UsernameInput; + if (!this.usernameInput) { + consolex.warn("Username input element not found"); } + window.addEventListener("beforeunload", (event) => { + consolex.log("Browser is closing"); + if (this.gameStop != null) { + this.gameStop(); + } + }); - initialize(): void { - this.usernameInput = document.querySelector('username-input') as UsernameInput; - if (!this.usernameInput) { - consolex.warn('Username input element not found'); + setFavicon(); + document.addEventListener("join-lobby", this.handleJoinLobby.bind(this)); + document.addEventListener("leave-lobby", this.handleLeaveLobby.bind(this)); + document.addEventListener( + "single-player", + this.handleSinglePlayer.bind(this) + ); + + const spModal = document.querySelector( + "single-player-modal" + ) as SinglePlayerModal; + spModal instanceof SinglePlayerModal; + document.getElementById("single-player").addEventListener("click", () => { + if (this.usernameInput.isValid()) { + spModal.open(); + } + }); + + this.publicLobby = document.querySelector("public-lobby") as PublicLobby; + + const hostModal = document.querySelector( + "host-lobby-modal" + ) as HostPrivateLobbyModal; + hostModal instanceof HostPrivateLobbyModal; + document + .getElementById("host-lobby-button") + .addEventListener("click", () => { + if (this.usernameInput.isValid()) { + hostModal.open(); } - window.addEventListener('beforeunload', (event) => { - consolex.log('Browser is closing'); - if (this.gameStop != null) { - this.gameStop() - } - }); + }); - setFavicon() - document.addEventListener('join-lobby', this.handleJoinLobby.bind(this)); - document.addEventListener('leave-lobby', this.handleLeaveLobby.bind(this)); - document.addEventListener('single-player', this.handleSinglePlayer.bind(this)); - - - const spModal = document.querySelector('single-player-modal') as SinglePlayerModal; - spModal instanceof SinglePlayerModal - document.getElementById('single-player').addEventListener('click', () => { - if (this.usernameInput.isValid()) { - spModal.open(); - } - }) - - const hostModal = document.querySelector('host-lobby-modal') as HostPrivateLobbyModal; - hostModal instanceof HostPrivateLobbyModal - document.getElementById('host-lobby-button').addEventListener('click', () => { - if (this.usernameInput.isValid()) { - hostModal.open(); - } - }) - - this.joinModal = document.querySelector('join-private-lobby-modal') as JoinPrivateLobbyModal; - this.joinModal instanceof JoinPrivateLobbyModal - document.getElementById('join-private-lobby-button').addEventListener('click', () => { - if (this.usernameInput.isValid()) { - this.joinModal.open(); - } - }) - } - - private async handleJoinLobby(event: CustomEvent) { - const lobby = event.detail.lobby - consolex.log(`joining lobby ${lobby.id}`) - if (this.gameStop != null) { - consolex.log('joining lobby, stopping existing game') - this.gameStop() + this.joinModal = document.querySelector( + "join-private-lobby-modal" + ) as JoinPrivateLobbyModal; + this.joinModal instanceof JoinPrivateLobbyModal; + document + .getElementById("join-private-lobby-button") + .addEventListener("click", () => { + if (this.usernameInput.isValid()) { + this.joinModal.open(); } - this.gameStop = joinLobby( - { - gameType: event.detail.gameType, - playerName: (): string => this.usernameInput.getCurrentUsername(), - gameID: lobby.id, - persistentID: getPersistentIDFromCookie(), - playerID: generateID(), - clientID: generateID(), - map: event.detail.map, - difficulty: event.detail.difficulty, - }, - () => this.joinModal.close() - ); - } + }); + } - private async handleLeaveLobby(event: CustomEvent) { - if (this.gameStop == null) { - return - } - consolex.log('leaving lobby, cancelling game') - this.gameStop() - this.gameStop = null + private async handleJoinLobby(event: CustomEvent) { + const lobby = event.detail.lobby; + consolex.log(`joining lobby ${lobby.id}`); + if (this.gameStop != null) { + consolex.log("joining lobby, stopping existing game"); + this.gameStop(); } + this.gameStop = joinLobby( + { + gameType: event.detail.gameType, + playerName: (): string => this.usernameInput.getCurrentUsername(), + gameID: lobby.id, + persistentID: getPersistentIDFromCookie(), + playerID: generateID(), + clientID: generateID(), + map: event.detail.map, + difficulty: event.detail.difficulty, + }, + () => { + this.joinModal.close(); + this.publicLobby.stop(); + } + ); + } - private async handleSinglePlayer(event: CustomEvent) { - alert('coming soon') + private async handleLeaveLobby(event: CustomEvent) { + if (this.gameStop == null) { + return; } + consolex.log("leaving lobby, cancelling game"); + this.gameStop(); + this.gameStop = null; + } + + private async handleSinglePlayer(event: CustomEvent) { + alert("coming soon"); + } } // Initialize the client when the DOM is loaded -document.addEventListener('DOMContentLoaded', () => { - new Client().initialize(); +document.addEventListener("DOMContentLoaded", () => { + new Client().initialize(); }); document.body.style.backgroundImage = `url(${backgroundImage})`; function setFavicon(): void { - const link = document.createElement('link'); - link.type = 'image/x-icon'; - link.rel = 'shortcut icon'; - link.href = favicon; - document.head.appendChild(link); + const link = document.createElement("link"); + link.type = "image/x-icon"; + link.rel = "shortcut icon"; + link.href = favicon; + document.head.appendChild(link); } // WARNING: DO NOT EXPOSE THIS ID export function getPersistentIDFromCookie(): string { - const COOKIE_NAME = 'player_persistent_id' + const COOKIE_NAME = "player_persistent_id"; - // Try to get existing cookie - const cookies = document.cookie.split(';') - for (let cookie of cookies) { - const [cookieName, cookieValue] = cookie.split('=').map(c => c.trim()) - if (cookieName === COOKIE_NAME) { - return cookieValue - } + // Try to get existing cookie + const cookies = document.cookie.split(";"); + for (let cookie of cookies) { + const [cookieName, cookieValue] = cookie.split("=").map((c) => c.trim()); + if (cookieName === COOKIE_NAME) { + return cookieValue; } + } - // If no cookie exists, create new ID and set cookie - const newID = generateCryptoRandomUUID() - document.cookie = [ - `${COOKIE_NAME}=${newID}`, - `max-age=${5 * 365 * 24 * 60 * 60}`, // 5 years - 'path=/', - 'SameSite=Strict', - 'Secure' - ].join(';') + // If no cookie exists, create new ID and set cookie + const newID = generateCryptoRandomUUID(); + document.cookie = [ + `${COOKIE_NAME}=${newID}`, + `max-age=${5 * 365 * 24 * 60 * 60}`, // 5 years + "path=/", + "SameSite=Strict", + "Secure", + ].join(";"); - return newID -} \ No newline at end of file + return newID; +} diff --git a/src/client/PublicLobby.ts b/src/client/PublicLobby.ts index b799070b6..364cd70e3 100644 --- a/src/client/PublicLobby.ts +++ b/src/client/PublicLobby.ts @@ -1,18 +1,18 @@ -import { LitElement, html, css } from 'lit'; -import { customElement, state } from 'lit/decorators.js'; +import { LitElement, html, css } from "lit"; +import { customElement, state } from "lit/decorators.js"; import { Lobby } from "../core/Schemas"; -import { Difficulty, GameMap, GameType } from '../core/game/Game'; -import { consolex } from '../core/Consolex'; +import { Difficulty, GameMap, GameType } from "../core/game/Game"; +import { consolex } from "../core/Consolex"; -@customElement('public-lobby') +@customElement("public-lobby") export class PublicLobby extends LitElement { - @state() private lobbies: Lobby[] = []; - @state() private isLobbyHighlighted: boolean = false; - private lobbiesInterval: number | null = null; + @state() private lobbies: Lobby[] = []; + @state() private isLobbyHighlighted: boolean = false; + private lobbiesInterval: number | null = null; - private currLobby: Lobby = null + private currLobby: Lobby = null; - static styles = css` + static styles = css` /* Add your styles here, based on your existing CSS */ .lobby-button { display: flex; @@ -22,7 +22,7 @@ export class PublicLobby extends LitElement { width: 100%; max-width: 20rem; margin: 0 auto; - padding: .5rem 1rem; + padding: 0.5rem 1rem; background-color: #2563eb; color: white; font-weight: bold; @@ -42,90 +42,107 @@ export class PublicLobby extends LitElement { background-color: #15803d; } - .lobby-name { font-size: 1.2rem; } - .lobby-timer { font-size: 1rem; } - .player-count { font-size: 1rem; } + .lobby-name { + font-size: 1.2rem; + } + .lobby-timer { + font-size: 1rem; + } + .player-count { + font-size: 1rem; + } `; - connectedCallback() { - super.connectedCallback(); - this.fetchAndUpdateLobbies(); // Fetch immediately on start - this.lobbiesInterval = window.setInterval(() => this.fetchAndUpdateLobbies(), 1000); + connectedCallback() { + super.connectedCallback(); + this.fetchAndUpdateLobbies(); // Fetch immediately on start + this.lobbiesInterval = window.setInterval( + () => this.fetchAndUpdateLobbies(), + 1000 + ); + } + + disconnectedCallback() { + super.disconnectedCallback(); + this.stop(); + } + + public stop() { + if (this.lobbiesInterval !== null) { + clearInterval(this.lobbiesInterval); + this.lobbiesInterval = null; + } + } + + private async fetchAndUpdateLobbies(): Promise { + try { + const lobbies = await this.fetchLobbies(); + this.lobbies = lobbies; + } catch (error) { + consolex.error("Error fetching and updating lobbies:", error); + } + } + + async fetchLobbies(): Promise { + const url = "/lobbies"; + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + return data.lobbies; + } catch (error) { + consolex.error("Error fetching lobbies:", error); + throw error; + } + } + + render() { + if (this.lobbies.length === 0) { + return html``; } - disconnectedCallback() { - super.disconnectedCallback(); - if (this.lobbiesInterval !== null) { - clearInterval(this.lobbiesInterval); - this.lobbiesInterval = null; - } - } + const lobby = this.lobbies[0]; + const timeRemaining = Math.max(0, Math.floor(lobby.msUntilStart / 1000)); - private async fetchAndUpdateLobbies(): Promise { - try { - const lobbies = await this.fetchLobbies(); - this.lobbies = lobbies; - } catch (error) { - consolex.error('Error fetching and updating lobbies:', error); - } - } - - async fetchLobbies(): Promise { - const url = '/lobbies'; - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - return data.lobbies; - } catch (error) { - consolex.error('Error fetching lobbies:', error); - throw error; - } - } - - render() { - if (this.lobbies.length === 0) { - return html``; - } - - const lobby = this.lobbies[0]; - const timeRemaining = Math.max(0, Math.floor(lobby.msUntilStart / 1000)); - - return html` + return html` `; - } + } - private lobbyClicked(lobby: Lobby) { - this.isLobbyHighlighted = !this.isLobbyHighlighted; - if (this.currLobby == null) { - this.currLobby = lobby - this.dispatchEvent(new CustomEvent('join-lobby', { - detail: { - lobby: lobby, - gameType: GameType.Public, - map: GameMap.World, - difficulty: Difficulty.Medium, - }, - bubbles: true, - composed: true - })); - } else { - this.dispatchEvent(new CustomEvent('leave-lobby', { - detail: { lobby: this.currLobby }, - bubbles: true, - composed: true - })); - this.currLobby = null - } + private lobbyClicked(lobby: Lobby) { + this.isLobbyHighlighted = !this.isLobbyHighlighted; + if (this.currLobby == null) { + this.currLobby = lobby; + this.dispatchEvent( + new CustomEvent("join-lobby", { + detail: { + lobby: lobby, + gameType: GameType.Public, + map: GameMap.World, + difficulty: Difficulty.Medium, + }, + bubbles: true, + composed: true, + }) + ); + } else { + this.dispatchEvent( + new CustomEvent("leave-lobby", { + detail: { lobby: this.currLobby }, + bubbles: true, + composed: true, + }) + ); + this.currLobby = null; } -} \ No newline at end of file + } +}