import { LitElement, html } from "lit"; import { customElement, state } from "lit/decorators.js"; import { translateText } from "../client/Utils"; import { consolex } from "../core/Consolex"; import { GameMode } from "../core/game/Game"; import { GameID, GameInfo } from "../core/Schemas"; import { generateID } from "../core/Util"; import { JoinLobbyEvent } from "./Main"; import { getMapsImage } from "./utilities/Maps"; @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 = 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)) { const msUntilStart = l.msUntilStart ?? 0; this.lobbyIDToStart.set(l.gameID, 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 start = this.lobbyIDToStart.get(lobby.gameID) ?? 0; const timeRemaining = Math.max(0, Math.floor((start - 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 teamCount = lobby.gameConfig.gameMode === GameMode.Team ? lobby.gameConfig.playerTeams || 0 : null; 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: { gameID: lobby.gameID, clientID: generateID(), } as JoinLobbyEvent, bubbles: true, composed: true, }), ); } else { this.dispatchEvent( new CustomEvent("leave-lobby", { detail: { lobby: this.currLobby }, bubbles: true, composed: true, }), ); this.leaveLobby(); } } }