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! { -