JoinLobbyModal for public and private lobbies (#3097)

## Description:

Replaced the src/client/JoinPrivateLobbyModal.ts with a new
src/client/JoinLobbyModal.ts which handles both public + private
lobbies.

<img width="771" height="714" alt="image"
src="https://github.com/user-attachments/assets/7ac55d91-3f0c-4f99-b960-cea9e617538d"
/>

also made a "connecting" to the lobby 
<img width="772" height="708" alt="image"
src="https://github.com/user-attachments/assets/a2812462-c5f4-459a-b63a-49d93bb2a6a2"
/>


It also needed to be updated to address the issue with the modal using
both polling + websockets

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

w.o.n
This commit is contained in:
Ryan
2026-02-02 21:02:20 -08:00
committed by GitHub
parent d04f90ec4a
commit 2baaebfef3
14 changed files with 1201 additions and 839 deletions
+42 -16
View File
@@ -1,7 +1,12 @@
import version from "resources/version.txt?raw";
import { UserMeResponse } from "../core/ApiSchemas";
import { EventBus } from "../core/EventBus";
import { GAME_ID_REGEX, GameRecord, GameStartInfo } from "../core/Schemas";
import {
GAME_ID_REGEX,
GameInfo,
GameRecord,
GameStartInfo,
} from "../core/Schemas";
import { GameEnv } from "../core/configuration/Config";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { GameType } from "../core/game/Game";
@@ -22,7 +27,7 @@ import "./GoogleAdElement";
import { GutterAds } from "./GutterAds";
import { HelpModal } from "./HelpModal";
import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
import { JoinLobbyModal } from "./JoinLobbyModal";
import "./LangSelector";
import { LangSelector } from "./LangSelector";
import { initLayout } from "./Layout";
@@ -33,7 +38,7 @@ import { initNavigation } from "./Navigation";
import "./NewsModal";
import "./PatternInput";
import "./PublicLobby";
import { PublicLobby } from "./PublicLobby";
import { PublicLobby, ShowPublicLobbyModalEvent } from "./PublicLobby";
import { SinglePlayerModal } from "./SinglePlayerModal";
import { TerritoryPatternsModal } from "./TerritoryPatternsModal";
import { TokenLoginModal } from "./TokenLoginModal";
@@ -203,6 +208,7 @@ declare global {
// Extend the global interfaces to include your custom events
interface DocumentEventMap {
"join-lobby": CustomEvent<JoinLobbyEvent>;
"show-public-lobby-modal": CustomEvent<ShowPublicLobbyModalEvent>;
"kick-player": CustomEvent;
"join-changed": CustomEvent;
}
@@ -216,6 +222,8 @@ export interface JoinLobbyEvent {
gameStartInfo?: GameStartInfo;
// GameRecord exists when replaying an archived game.
gameRecord?: GameRecord;
source?: "public" | "private" | "host" | "matchmaking" | "singleplayer";
publicLobbyInfo?: GameInfo;
}
class Client {
@@ -228,7 +236,7 @@ class Client {
private flagInput: FlagInput | null = null;
private hostModal: HostPrivateLobbyModal;
private joinModal: JoinPrivateLobbyModal;
private joinModal: JoinLobbyModal;
private publicLobby: PublicLobby;
private userSettings: UserSettings = new UserSettings();
private patternsModal: TerritoryPatternsModal;
@@ -302,6 +310,10 @@ class Client {
this.gutterAds = gutterAds;
document.addEventListener("join-lobby", this.handleJoinLobby.bind(this));
document.addEventListener(
"show-public-lobby-modal",
this.handleShowPublicLobbyModal.bind(this),
);
document.addEventListener("leave-lobby", this.handleLeaveLobby.bind(this));
document.addEventListener("kick-player", this.handleKickPlayer.bind(this));
document.addEventListener(
@@ -504,7 +516,6 @@ class Client {
hostLobbyButton.addEventListener("click", () => {
if (this.usernameInput?.isValid()) {
window.showPage?.("page-host-lobby");
this.publicLobby.leaveLobby();
} else {
window.dispatchEvent(
new CustomEvent("show-message", {
@@ -519,10 +530,12 @@ class Client {
});
this.joinModal = document.querySelector(
"join-private-lobby-modal",
) as JoinPrivateLobbyModal;
if (!this.joinModal || !(this.joinModal instanceof JoinPrivateLobbyModal)) {
console.warn("Join private lobby modal element not found");
"join-lobby-modal",
) as JoinLobbyModal;
if (!this.joinModal || !(this.joinModal instanceof JoinLobbyModal)) {
console.warn("Join lobby modal element not found");
} else {
this.joinModal.eventBus = this.eventBus;
}
const joinPrivateLobbyButton = document.getElementById(
"join-private-lobby-button",
@@ -531,7 +544,7 @@ class Client {
throw new Error("Missing join-private-lobby-button");
joinPrivateLobbyButton.addEventListener("click", () => {
if (this.usernameInput?.isValid()) {
window.showPage?.("page-join-private-lobby");
window.showPage?.("page-join-lobby");
} else {
window.dispatchEvent(
new CustomEvent("show-message", {
@@ -631,7 +644,7 @@ class Client {
private async handleUrl() {
// Wait for modal custom elements to be defined
await Promise.all([
customElements.whenDefined("join-private-lobby-modal"),
customElements.whenDefined("join-lobby-modal"),
customElements.whenDefined("host-lobby-modal"),
]);
@@ -644,7 +657,7 @@ class Client {
// Wait 2 seconds to ensure all elements are actually loaded,
// On low end-chromebooks the join modal was not registered in time.
await new Promise((resolve) => setTimeout(resolve, 2000));
window.showPage?.("page-join-private-lobby");
window.showPage?.("page-join-lobby");
this.joinModal?.open(lobbyId);
console.log(`CrazyGames: joining lobby ${lobbyId} from invite param`);
return;
@@ -733,7 +746,7 @@ class Client {
const lobbyId =
pathMatch && GAME_ID_REGEX.test(pathMatch[1]) ? pathMatch[1] : null;
if (lobbyId) {
window.showPage?.("page-join-private-lobby");
window.showPage?.("page-join-lobby");
this.joinModal.open(lobbyId);
console.log(`joining lobby ${lobbyId}`);
return;
@@ -759,7 +772,10 @@ class Client {
document.body.classList.remove("in-game");
}
const config = await getServerConfigFromClient();
this.updateJoinUrlForShare(lobby.gameID, config);
// Only update URL immediately for private lobbies, not public ones
if (!lobby.publicLobbyInfo && lobby.source !== "public") {
this.updateJoinUrlForShare(lobby.gameID, config);
}
const pattern = this.userSettings.getSelectedPatternName(
await fetchCosmetics(),
@@ -796,10 +812,10 @@ class Client {
document
.getElementById("username-validation-error")
?.classList.add("hidden");
this.joinModal?.closeWithoutLeaving();
[
"single-player-modal",
"host-lobby-modal",
"join-private-lobby-modal",
"game-starting-modal",
"game-top-bar",
"help-modal",
@@ -884,6 +900,17 @@ class Client {
}
}
private handleShowPublicLobbyModal(
event: CustomEvent<ShowPublicLobbyModalEvent>,
) {
const { lobby } = event.detail;
console.log(`Opening JoinLobbyModal for public lobby ${lobby.gameID}`);
// Open the join lobby modal page and pass the lobby info
window.showPage?.("page-join-lobby");
this.joinModal?.open(lobby.gameID, lobby);
}
private async handleLeaveLobby(/* event: CustomEvent */) {
if (this.gameStop === null) {
return;
@@ -902,7 +929,6 @@ class Client {
document.body.classList.remove("in-game");
crazyGamesSDK.gameplayStop();
this.publicLobby.leaveLobby();
}
private handleKickPlayer(event: CustomEvent) {