import { LitElement, html } from "lit"; import { customElement, state } from "lit/decorators.js"; import { Difficulty, GameMapType, GameType } from "../core/game/Game"; import { consolex } from "../core/Consolex"; import { getMapsImage } from "./utilities/Maps"; import { GameID, GameInfo } from "../core/Schemas"; @customElement("public-lobby") export class PublicLobby extends LitElement { @state() private lobbies: GameInfo[] = []; @state() public isLobbyHighlighted: boolean = false; @state() private isButtonDebounced: boolean = false; private lobbiesInterval: number | null = null; private currLobby: GameInfo = null; private debounceDelay: number = 750; private lobbyIDToStart = new Map(); createRenderRoot() { return this; } connectedCallback() { super.connectedCallback(); this.fetchAndUpdateLobbies(); this.lobbiesInterval = window.setInterval( () => this.fetchAndUpdateLobbies(), 1000, ); } disconnectedCallback() { super.disconnectedCallback(); if (this.lobbiesInterval !== null) { clearInterval(this.lobbiesInterval); this.lobbiesInterval = null; } } private async fetchAndUpdateLobbies(): Promise { try { this.lobbies = await this.fetchLobbies(); this.lobbies.forEach((l) => { // Store the start time on first fetch because endpoint is cached, causing // the time to appear irregular. if (!this.lobbyIDToStart.has(l.gameID)) { this.lobbyIDToStart.set(l.gameID, l.msUntilStart + Date.now()); } }); } catch (error) { consolex.error("Error fetching lobbies:", error); } } async fetchLobbies(): Promise { try { const response = await fetch(`/api/public_lobbies`); 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; } } public stop() { if (this.lobbiesInterval !== null) { this.isLobbyHighlighted = false; clearInterval(this.lobbiesInterval); this.lobbiesInterval = null; } } render() { if (this.lobbies.length === 0) return html``; const lobby = this.lobbies[0]; if (!lobby?.gameConfig) { return; } const timeRemaining = Math.max( 0, Math.floor((this.lobbyIDToStart.get(lobby.gameID) - Date.now()) / 1000), ); // Format time to show minutes and seconds const minutes = Math.floor(timeRemaining / 60); const seconds = timeRemaining % 60; const timeDisplay = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`; const playersRemainingBeforeMax = lobby.gameConfig.maxPlayers - lobby.numClients; return html` `; } leaveLobby() { this.isLobbyHighlighted = false; this.currLobby = null; } private lobbyClicked(lobby: GameInfo) { if (this.isButtonDebounced) { return; } // Set debounce state this.isButtonDebounced = true; // Reset debounce after delay setTimeout(() => { this.isButtonDebounced = false; }, this.debounceDelay); if (this.currLobby == null) { this.isLobbyHighlighted = true; this.currLobby = lobby; this.dispatchEvent( new CustomEvent("join-lobby", { detail: lobby, bubbles: true, composed: true, }), ); } else { this.dispatchEvent( new CustomEvent("leave-lobby", { detail: { lobby: this.currLobby }, bubbles: true, composed: true, }), ); this.leaveLobby(); } } }