From c46d26ae02bd397ad36165e5f9806063626a0cfe Mon Sep 17 00:00:00 2001
From: Henri Bourcereau
Date: Sat, 25 Apr 2026 21:51:16 +0200
Subject: [PATCH] fix: show login name in game
---
clients/web/assets/style.css | 2 +-
clients/web/locales/en.json | 1 +
clients/web/locales/fr.json | 1 +
clients/web/src/app.rs | 93 +++++++++++++++++++--------------
clients/web/src/game/session.rs | 17 +++++-
clients/web/src/main.rs | 7 ++-
clients/web/src/nav.rs | 7 +--
7 files changed, 81 insertions(+), 47 deletions(-)
diff --git a/clients/web/assets/style.css b/clients/web/assets/style.css
index b2d89a4..8c7630e 100644
--- a/clients/web/assets/style.css
+++ b/clients/web/assets/style.css
@@ -290,7 +290,7 @@ a:hover { text-decoration: underline; }
/* ── Game overlay (full-screen, covers portal during play) ───────── */
.game-overlay {
position: fixed;
- inset: 0;
+ inset: 54px 0 0 0;
background: #8a7050;
background-image:
radial-gradient(ellipse at 20% 10%, rgba(80,48,16,0.35) 0%, transparent 60%),
diff --git a/clients/web/locales/en.json b/clients/web/locales/en.json
index 5d5005d..5cbb36e 100644
--- a/clients/web/locales/en.json
+++ b/clients/web/locales/en.json
@@ -58,6 +58,7 @@
"hint_move": "Click a highlighted field to move a checker",
"hint_hold_or_go": "Hold to keep points — Go to reset the setting",
"hint_continue": "Click Continue when ready",
+ "anonymous_name": "Anonymous",
"login_failed": "Invalid username or password.",
"sign_in": "Sign in",
"sign_out": "Sign out",
diff --git a/clients/web/locales/fr.json b/clients/web/locales/fr.json
index 8cef7df..da12cc8 100644
--- a/clients/web/locales/fr.json
+++ b/clients/web/locales/fr.json
@@ -58,6 +58,7 @@
"hint_move": "Cliquez un champ surligné pour déplacer",
"hint_hold_or_go": "Tenir pour garder les points — S'en aller pour repartir",
"hint_continue": "Cliquez Continuer quand vous êtes prêt",
+ "anonymous_name": "Anonyme",
"login_failed": "Identifiant ou mot de passe incorrect.",
"sign_in": "Se connecter",
"sign_out": "Se déconnecter",
diff --git a/clients/web/src/app.rs b/clients/web/src/app.rs
index 8d604b6..b9ce4aa 100644
--- a/clients/web/src/app.rs
+++ b/clients/web/src/app.rs
@@ -4,6 +4,7 @@ use gloo_storage::{LocalStorage, Storage};
use leptos::prelude::*;
use leptos::task::spawn_local;
use leptos_router::components::{Route, Router, Routes};
+use leptos_router::hooks::use_location;
use leptos_router::path;
use serde::{Deserialize, Serialize};
@@ -11,15 +12,15 @@ use backbone_lib::session::{ConnectError, GameSession, RoomConfig, RoomRole, Ses
use backbone_lib::traits::ViewStateUpdate;
use crate::api;
+use crate::i18n::*;
use crate::game::components::{ConnectingScreen, GameScreen};
use crate::game::session::{
- compute_last_moves, push_or_show, run_local_bot_game,
+ compute_last_moves, patch_player_name, push_or_show, run_local_bot_game,
};
use crate::game::trictrac::backend::TrictracBackend;
use crate::game::trictrac::types::{
GameDelta, PlayerAction, ScoredEvent, SerStage, ViewState,
};
-use crate::i18n::I18nContextProvider;
use crate::nav::SiteNav;
use crate::portal::{account::AccountPage, game_detail::GameDetailPage, lobby::LobbyPage, profile::ProfilePage};
use trictrac_store::CheckerMove;
@@ -128,6 +129,7 @@ async fn submit_game_result(room_code: String, game_state: ViewState) {
#[component]
pub fn App() -> impl IntoView {
+ let i18n = use_i18n();
let stored = load_session();
let initial_screen = if stored.is_some() {
Screen::Connecting
@@ -225,8 +227,10 @@ pub fn App() -> impl IntoView {
};
if remote_config.is_none() {
+ let player_name = auth_username.get_untracked()
+ .unwrap_or_else(|| t_string!(i18n, anonymous_name).to_string());
loop {
- let restart = run_local_bot_game(screen, &mut cmd_rx, pending).await;
+ let restart = run_local_bot_game(screen, &mut cmd_rx, pending, player_name.clone()).await;
if !restart {
break;
}
@@ -266,7 +270,9 @@ pub fn App() -> impl IntoView {
let is_host = session.is_host;
let player_id = session.player_id;
let reconnect_token = session.reconnect_token;
- let mut vs = ViewState::default_with_names("Blancs", "Noirs");
+ let my_name = auth_username.get_untracked()
+ .unwrap_or_else(|| t_string!(i18n, anonymous_name).to_string());
+ let mut vs = ViewState::default_with_names("", "");
let mut result_submitted = false;
loop {
@@ -290,6 +296,7 @@ pub fn App() -> impl IntoView {
ViewStateUpdate::Full(state) => vs = state,
ViewStateUpdate::Incremental(delta) => vs.apply_delta(&delta),
}
+ patch_player_name(&mut vs, player_id, &my_name);
if is_host && !result_submitted && vs.stage == SerStage::Ended {
result_submitted = true;
@@ -343,42 +350,52 @@ pub fn App() -> impl IntoView {
});
view! {
-
-
- // Nav: hidden while game overlay is active
-
+
+
- // Portal pages — always mounted for router stability
-
- "Page not found."
}>
-
-
-
-
-
-
+
+ "Page not found." }>
+
+
+
+
+
+
- // Game overlay: fixed, covers portal during play
- {move || {
- let q = pending.get();
- let front = q.front().cloned();
- if let Some(state) = front {
- return view! {
-
- }.into_any();
- }
- match screen.get() {
- Screen::Playing(state) => view! {
-
- }.into_any(),
- Screen::Connecting => view! {
-
- }.into_any(),
- _ => view! { }.into_any(),
- }
- }}
-
-
+
+
+ }
+}
+
+/// Renders the full-screen game overlay, but only when the current route is "/".
+/// This lets the user navigate to profile/account pages while a game is running.
+#[component]
+fn GameOverlay(
+ pending: RwSignal>,
+ screen: RwSignal,
+) -> impl IntoView {
+ let location = use_location();
+
+ move || {
+ if location.pathname.get() != "/" {
+ return view! { }.into_any();
+ }
+ let q = pending.get();
+ let front = q.front().cloned();
+ if let Some(state) = front {
+ return view! {
+
+ }.into_any();
+ }
+ match screen.get() {
+ Screen::Playing(state) => view! {
+
+ }.into_any(),
+ Screen::Connecting => view! {
+
+ }.into_any(),
+ _ => view! { }.into_any(),
+ }
}
}
diff --git a/clients/web/src/game/session.rs b/clients/web/src/game/session.rs
index e648d01..df603ec 100644
--- a/clients/web/src/game/session.rs
+++ b/clients/web/src/game/session.rs
@@ -18,12 +18,13 @@ pub async fn run_local_bot_game(
screen: RwSignal,
cmd_rx: &mut mpsc::UnboundedReceiver,
pending: RwSignal>,
+ player_name: String,
) -> bool {
let mut backend = TrictracBackend::new(0);
backend.player_arrival(0);
backend.player_arrival(1);
- let mut vs = ViewState::default_with_names("You", "Bot");
+ let mut vs = ViewState::default_with_names(&player_name, "Bot");
for cmd in backend.drain_commands() {
match cmd {
BackendCommand::ResetViewState => {
@@ -35,6 +36,7 @@ pub async fn run_local_bot_game(
_ => {}
}
}
+ patch_bot_names(&mut vs, &player_name);
screen.set(Screen::Playing(GameUiState {
view_state: vs.clone(),
player_id: 0,
@@ -58,6 +60,7 @@ pub async fn run_local_bot_game(
vs.apply_delta(&delta);
}
}
+ patch_bot_names(&mut vs, &player_name);
let scored = compute_scored_event(&prev_vs, &vs, 0);
let opp_scored = compute_scored_event(&prev_vs, &vs, 1);
screen.set(Screen::Playing(GameUiState {
@@ -86,6 +89,7 @@ pub async fn run_local_bot_game(
if let BackendCommand::Delta(delta) = cmd {
let delta_prev_vs = vs.clone();
vs.apply_delta(&delta);
+ patch_bot_names(&mut vs, &player_name);
push_or_show(
&delta_prev_vs,
GameUiState {
@@ -110,6 +114,17 @@ pub async fn run_local_bot_game(
}
}
+/// Patches the player names in a ViewState after a backend delta (bot game: slot 0 = human, 1 = Bot).
+pub fn patch_bot_names(vs: &mut ViewState, player_name: &str) {
+ vs.scores[0].name = player_name.to_string();
+ vs.scores[1].name = "Bot".to_string();
+}
+
+/// Patches the local player's name in a ViewState after a backend delta (multiplayer).
+pub fn patch_player_name(vs: &mut ViewState, player_id: u16, name: &str) {
+ vs.scores[player_id as usize].name = name.to_string();
+}
+
/// Returns the checker moves to animate when the board changed between two ViewStates.
pub fn compute_last_moves(
prev: &ViewState,
diff --git a/clients/web/src/main.rs b/clients/web/src/main.rs
index bfd8adf..5d5fbb4 100644
--- a/clients/web/src/main.rs
+++ b/clients/web/src/main.rs
@@ -7,8 +7,13 @@ mod nav;
mod portal;
use app::App;
+use i18n::I18nContextProvider;
use leptos::prelude::*;
fn main() {
- mount_to_body(|| view! { })
+ mount_to_body(|| view! {
+
+
+
+ })
}
diff --git a/clients/web/src/nav.rs b/clients/web/src/nav.rs
index c5b59fd..10ecc36 100644
--- a/clients/web/src/nav.rs
+++ b/clients/web/src/nav.rs
@@ -3,19 +3,14 @@ use leptos::task::spawn_local;
use leptos_router::components::A;
use crate::api;
-use crate::app::Screen;
use crate::i18n::*;
#[component]
pub fn SiteNav() -> impl IntoView {
let i18n = use_i18n();
- let screen = use_context::>().expect("Screen context not found");
let auth_username =
use_context::>>().expect("auth_username context not found");
- let is_game_active =
- move || !matches!(screen.get(), Screen::Login { .. });
-
let logout = move |_| {
spawn_local(async move {
let _ = api::post_logout().await;
@@ -24,7 +19,7 @@ pub fn SiteNav() -> impl IntoView {
};
view! {
-
+
"Trictrac"