diff --git a/resources/lang/en.json b/resources/lang/en.json index b48625c30a..33e83257cc 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -46,6 +46,8 @@ }, "main": { "title": "OpenFront (ALPHA)", + "title_starting": "OpenFront (ALPHA) - Starting {time}", + "title_game_in_progress": "OpenFront (ALPHA) - Game in progress", "login_discord": "Login with Discord", "sign_in": "Sign in", "discord_avatar_alt": "Discord profile avatar", diff --git a/resources/lang/es.json b/resources/lang/es.json index 93c877be5f..331ffe0669 100644 --- a/resources/lang/es.json +++ b/resources/lang/es.json @@ -7,6 +7,8 @@ }, "main": { "title": "OpenFront (ALPHA)", + "title_starting": "OpenFront (ALPHA) - Comenzando en {time}", + "title_game_in_progress": "OpenFront (ALPHA) - Partida en progreso", "join_discord": "¡Únete al Discord!", "login_discord": "Iniciar sesión con Discord", "logged_in": "¡Sesión iniciada!", diff --git a/src/client/Main.ts b/src/client/Main.ts index bd73b5f46c..f1815ecd7b 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -8,6 +8,7 @@ import { GameInfo, GameRecord, GameStartInfo, + LobbyInfoEvent, PublicGameInfo, } from "../core/Schemas"; import { GameEnv } from "../core/configuration/Config"; @@ -46,6 +47,7 @@ import { MatchmakingModal } from "./Matchmaking"; import { modalRouter } from "./ModalRouter"; import { initNavigation } from "./Navigation"; import "./NewsModal"; +import { setTitle } from "./PageTitleManager"; import "./PatternInput"; import "./SinglePlayerModal"; import { StoreModal } from "./Store"; @@ -61,9 +63,12 @@ import { UserSettingModal } from "./UserSettingModal"; import "./UsernameInput"; import { genAnonUsername, UsernameInput } from "./UsernameInput"; import { + calculateServerTimeOffset, getDiscordAvatarUrl, + getSecondsUntilServerTimestamp, incrementGamesPlayed, isInIframe, + renderDuration, translateText, } from "./Utils"; import { installSafariPinchZoomBlocker } from "./utilities/DisableSafariPinchZoom"; @@ -854,6 +859,49 @@ class Client { } this.lobbyHandle = newLobbyHandle; + let lobbyTitleTimer: ReturnType | null = null; + const onLobbyInfo = (event: LobbyInfoEvent) => { + if (this.lobbyHandle !== newLobbyHandle) { + if (lobbyTitleTimer !== null) { + clearTimeout(lobbyTitleTimer); + lobbyTitleTimer = null; + } + return; + } + const lobby = event.lobby; + if (lobbyTitleTimer !== null) { + clearTimeout(lobbyTitleTimer); + lobbyTitleTimer = null; + } + if (lobby.startsAt) { + const serverTimeOffset = calculateServerTimeOffset( + lobby.serverTime ?? Date.now(), + ); + const updateTitle = () => { + if (this.lobbyHandle !== newLobbyHandle) { + return; + } + const seconds = getSecondsUntilServerTimestamp( + lobby.startsAt!, + serverTimeOffset, + ); + if (seconds > 0) { + setTitle( + translateText("main.title_starting", { + time: renderDuration(seconds), + }), + ); + lobbyTitleTimer = setTimeout(updateTitle, 1000); + } else { + setTitle(translateText("main.title_game_in_progress")); + } + }; + updateTitle(); + } else { + setTitle(translateText("main.title_game_in_progress")); + } + }; + this.eventBus.on(LobbyInfoEvent, onLobbyInfo); this.lobbyHandle.prestart.then(() => { console.log("Closing modals"); @@ -944,6 +992,8 @@ class Client { // Store current URL for popstate confirmation this.currentUrl = window.location.href; + + setTitle(translateText("main.title_game_in_progress")); }); } @@ -967,6 +1017,7 @@ class Client { this.lobbyHandle.stop(true); this.lobbyHandle = null; this.currentUrl = null; + setTitle(translateText("main.title")); try { history.replaceState(null, "", "/"); diff --git a/src/client/PageTitleManager.ts b/src/client/PageTitleManager.ts new file mode 100644 index 0000000000..ee362f2805 --- /dev/null +++ b/src/client/PageTitleManager.ts @@ -0,0 +1,7 @@ +/** + * Directly sets the document title. + * The caller is responsible for string concatenation and localization if needed. + */ +export function setTitle(title: string): void { + document.title = title; +} diff --git a/tests/client/PageTitleManager.test.ts b/tests/client/PageTitleManager.test.ts new file mode 100644 index 0000000000..37800cd46f --- /dev/null +++ b/tests/client/PageTitleManager.test.ts @@ -0,0 +1,13 @@ +import { beforeEach, describe, expect, it } from "vitest"; +import { setTitle } from "../../src/client/PageTitleManager"; + +describe("PageTitleManager", () => { + beforeEach(() => { + document.title = ""; + }); + + it("should set a custom title", () => { + setTitle("Custom Title"); + expect(document.title).toBe("Custom Title"); + }); +});