From f40305f4ad875316037ee44d2cb4ea0532e58a81 Mon Sep 17 00:00:00 2001 From: patrick simonian Date: Thu, 21 May 2026 14:04:19 -0700 Subject: [PATCH 1/4] Issue #3930 add a dynamic html title --- resources/lang/en.json | 2 ++ resources/lang/es.json | 2 ++ src/client/Main.ts | 43 +++++++++++++++++++++++++-- src/client/PageTitleManager.ts | 8 +++++ tests/client/PageTitleManager.test.ts | 13 ++++++++ 5 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 src/client/PageTitleManager.ts create mode 100644 tests/client/PageTitleManager.test.ts diff --git a/resources/lang/en.json b/resources/lang/en.json index 80df945c68..e72fbfcb6c 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -31,6 +31,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..3311225917 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"; @@ -61,12 +62,16 @@ 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"; +import { setTitle } from "./PageTitleManager"; import "./components/DesktopNavBar"; import "./components/Footer"; @@ -326,7 +331,7 @@ class Client { `url(${assetUrl("fonts/OpenFront.ttf")})`, ); document.fonts.add(openFrontFont); - openFrontFont.load().catch(() => {}); + openFrontFont.load().catch(() => { }); const versionElements = document.querySelectorAll( "#game-version, .game-version-display", @@ -498,7 +503,7 @@ class Client { // Authorized console.log( `Your player ID is ${userMeResponse.player.publicId}\n` + - "Sharing this ID will allow others to view your game history and stats.", + "Sharing this ID will allow others to view your game history and stats.", ); } }; @@ -855,6 +860,37 @@ class Client { this.lobbyHandle = newLobbyHandle; + const onLobbyInfo = (event: LobbyInfoEvent) => { + + const lobby = event.lobby; + 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), + })) + 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"); document.getElementById("settings-button")?.classList.add("hidden"); @@ -944,6 +980,8 @@ class Client { // Store current URL for popstate confirmation this.currentUrl = window.location.href; + + setTitle(translateText("main.title_game_in_progress")); }); } @@ -967,6 +1005,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..d27046c9c6 --- /dev/null +++ b/src/client/PageTitleManager.ts @@ -0,0 +1,8 @@ + +/** + * 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..db7f1bb6c1 --- /dev/null +++ b/tests/client/PageTitleManager.test.ts @@ -0,0 +1,13 @@ +import { describe, it, expect, beforeEach } 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"); + }); +}); From 3538dd1d4578982f4ce0a5e234a085a7c1bbee98 Mon Sep 17 00:00:00 2001 From: patrick simonian Date: Thu, 21 May 2026 14:36:43 -0700 Subject: [PATCH 2/4] Add code rabbit feedback for lobby timer --- src/client/Main.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/client/Main.ts b/src/client/Main.ts index 3311225917..38d0383538 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -859,10 +859,20 @@ 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(), @@ -879,7 +889,7 @@ class Client { setTitle(translateText("main.title_starting", { time: renderDuration(seconds), })) - setTimeout(updateTitle, 1000); + lobbyTitleTimer = setTimeout(updateTitle, 1000); } else { setTitle(translateText("main.title_game_in_progress")); } From ec7ec1c89efa51ba261d236537e96f123ee6e31b Mon Sep 17 00:00:00 2001 From: Patrick Simonian Date: Thu, 21 May 2026 14:41:30 -0700 Subject: [PATCH 3/4] Update src/client/Main.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/client/Main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/Main.ts b/src/client/Main.ts index 38d0383538..17e8a00c76 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -888,7 +888,7 @@ class Client { if (seconds > 0) { setTitle(translateText("main.title_starting", { time: renderDuration(seconds), - })) + })); lobbyTitleTimer = setTimeout(updateTitle, 1000); } else { setTitle(translateText("main.title_game_in_progress")); From 6fb25a2eccba24fe50632ae7bea8a40d6d8311b3 Mon Sep 17 00:00:00 2001 From: patrick simonian Date: Fri, 22 May 2026 13:39:39 -0700 Subject: [PATCH 4/4] fix linting --- src/client/Main.ts | 14 ++++++++------ src/client/PageTitleManager.ts | 1 - tests/client/PageTitleManager.test.ts | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/client/Main.ts b/src/client/Main.ts index 17e8a00c76..f1815ecd7b 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -47,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"; @@ -71,7 +72,6 @@ import { translateText, } from "./Utils"; import { installSafariPinchZoomBlocker } from "./utilities/DisableSafariPinchZoom"; -import { setTitle } from "./PageTitleManager"; import "./components/DesktopNavBar"; import "./components/Footer"; @@ -331,7 +331,7 @@ class Client { `url(${assetUrl("fonts/OpenFront.ttf")})`, ); document.fonts.add(openFrontFont); - openFrontFont.load().catch(() => { }); + openFrontFont.load().catch(() => {}); const versionElements = document.querySelectorAll( "#game-version, .game-version-display", @@ -503,7 +503,7 @@ class Client { // Authorized console.log( `Your player ID is ${userMeResponse.player.publicId}\n` + - "Sharing this ID will allow others to view your game history and stats.", + "Sharing this ID will allow others to view your game history and stats.", ); } }; @@ -886,9 +886,11 @@ class Client { serverTimeOffset, ); if (seconds > 0) { - setTitle(translateText("main.title_starting", { - time: renderDuration(seconds), - })); + setTitle( + translateText("main.title_starting", { + time: renderDuration(seconds), + }), + ); lobbyTitleTimer = setTimeout(updateTitle, 1000); } else { setTitle(translateText("main.title_game_in_progress")); diff --git a/src/client/PageTitleManager.ts b/src/client/PageTitleManager.ts index d27046c9c6..ee362f2805 100644 --- a/src/client/PageTitleManager.ts +++ b/src/client/PageTitleManager.ts @@ -1,4 +1,3 @@ - /** * Directly sets the document title. * The caller is responsible for string concatenation and localization if needed. diff --git a/tests/client/PageTitleManager.test.ts b/tests/client/PageTitleManager.test.ts index db7f1bb6c1..37800cd46f 100644 --- a/tests/client/PageTitleManager.test.ts +++ b/tests/client/PageTitleManager.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeEach } from "vitest"; +import { beforeEach, describe, expect, it } from "vitest"; import { setTitle } from "../../src/client/PageTitleManager"; describe("PageTitleManager", () => {