Files
OpenFrontIO/src/client/Main.ts
T
Mittanicz 166ef92970 Create base components button, modal .. (#331)
Create base components with shared styles, as start of make ui better.
For now shoul look same but underhood new copoments are used.

This should be first PR that handle this and many more comes. I am in
rush due conflict with other ppl, but should work as i tested.

Testing again and look at structure

Main goal i have global css not scope in component die loading times and
size of elements. (Modal due nature of lit and shadow dom is exception,
maybe later find better way).

Documenting the components will happen later as base components
establish their usage.
2025-03-24 10:34:27 -07:00

245 lines
7.6 KiB
TypeScript

import { ClientGameRunner, joinLobby } from "./ClientGameRunner";
import favicon from "../../resources/images/Favicon.svg";
import "./PublicLobby";
import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
import "./UsernameInput";
import "./styles.css";
import { UsernameInput } from "./UsernameInput";
import { SinglePlayerModal } from "./SinglePlayerModal";
import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
import { GameStartingModal } from "./gameStartingModal";
import { generateID } from "../core/Util";
import { generateCryptoRandomUUID } from "./Utils";
import { consolex } from "../core/Consolex";
import "./FlagInput";
import { FlagInput } from "./FlagInput";
import page from "page";
import { PublicLobby } from "./PublicLobby";
import { UserSettings } from "../core/game/UserSettings";
import "./DarkModeButton";
import { DarkModeButton } from "./DarkModeButton";
import "./GoogleAdElement";
import { HelpModal } from "./HelpModal";
import { GameType } from "../core/game/Game";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import GoogleAdElement from "./GoogleAdElement";
import { GameConfig, GameInfo, GameRecord } from "../core/Schemas";
export interface JoinLobbyEvent {
// Multiplayer games only have gameID, gameConfig is not known until game starts.
gameID: string;
// GameConfig only exists when playing a singleplayer game.
gameConfig?: GameConfig;
// GameRecord exists when replaying an archived game.
gameRecord?: GameRecord;
}
class Client {
private gameStop: () => void;
private usernameInput: UsernameInput | null = null;
private flagInput: FlagInput | null = null;
private darkModeButton: DarkModeButton | null = null;
private joinModal: JoinPrivateLobbyModal;
private publicLobby: PublicLobby;
private googleAds: NodeListOf<GoogleAdElement>;
private userSettings: UserSettings = new UserSettings();
constructor() {}
initialize(): void {
this.flagInput = document.querySelector("flag-input") as FlagInput;
if (!this.flagInput) {
consolex.warn("Flag input element not found");
}
this.darkModeButton = document.querySelector(
"dark-mode-button",
) as DarkModeButton;
if (!this.darkModeButton) {
consolex.warn("Dark mode button element not found");
}
this.usernameInput = document.querySelector(
"username-input",
) as UsernameInput;
if (!this.usernameInput) {
consolex.warn("Username input element not found");
}
this.publicLobby = document.querySelector("public-lobby") as PublicLobby;
this.googleAds = document.querySelectorAll(
"google-ad",
) as NodeListOf<GoogleAdElement>;
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));
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 hlpModal = document.querySelector("help-modal") as HelpModal;
hlpModal instanceof HelpModal;
document.getElementById("help-button").addEventListener("click", () => {
hlpModal.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.publicLobby.leaveLobby();
}
});
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();
}
});
if (this.userSettings.darkMode()) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
page("/join/:lobbyId", (ctx) => {
if (ctx.init && sessionStorage.getItem("inLobby")) {
// On page reload, go back home
page.redirect("/");
return;
}
const lobbyId = ctx.params.lobbyId;
this.joinModal.open(lobbyId);
consolex.log(`joining lobby ${lobbyId}`);
});
page();
}
private async handleJoinLobby(event: CustomEvent) {
const lobby = event.detail as JoinLobbyEvent;
consolex.log(`joining lobby ${lobby.gameID}`);
if (this.gameStop != null) {
consolex.log("joining lobby, stopping existing game");
this.gameStop();
}
const config = await getServerConfigFromClient();
this.gameStop = joinLobby(
{
serverConfig: config,
flag: (): string =>
this.flagInput.getCurrentFlag() == "xx"
? ""
: this.flagInput.getCurrentFlag(),
playerName: (): string => this.usernameInput.getCurrentUsername(),
gameID: lobby.gameID,
persistentID: getPersistentIDFromCookie(),
playerID: generateID(),
clientID: generateID(),
gameConfig: lobby.gameConfig ?? lobby.gameRecord?.gameConfig,
gameRecord: lobby.gameRecord,
},
() => {
this.joinModal.close();
this.publicLobby.stop();
document.querySelectorAll(".ad").forEach((ad) => {
(ad as HTMLElement).style.display = "none";
});
// show when the game loads
const startingModal = document.querySelector(
"game-starting-modal",
) as GameStartingModal;
startingModal instanceof GameStartingModal;
startingModal.show();
if (event.detail.gameConfig?.gameType != GameType.Singleplayer) {
window.history.pushState({}, "", `/join/${lobby.gameID}`);
sessionStorage.setItem("inLobby", "true");
}
},
);
}
private async handleLeaveLobby(event: CustomEvent) {
if (this.gameStop == null) {
return;
}
consolex.log("leaving lobby, cancelling game");
this.gameStop();
this.gameStop = null;
this.publicLobby.leaveLobby();
}
}
// Initialize the client when the DOM is loaded
document.addEventListener("DOMContentLoaded", () => {
new Client().initialize();
});
function setFavicon(): void {
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";
// Try to get existing cookie
const cookies = document.cookie.split(";");
for (const 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(";");
return newID;
}