Files
tavolo/web/src/main.rs
T
woggioni 03a571a6a5
CI / Build and push docker image (push) Successful in 1m42s
Notify when another player plays a card
Add an opt-in bell to the game HUD: the first click asks for the
browser's notification permission, and the choice is remembered per
device. A play by any other seat then raises a desktop notification
with the player's name, the card and the next player, while the page
is in the background and the viewer is actually seated in the game.

Detect the newest play across the game families: scopone and scopa
report it in last_move, while briscola and briscola chiamata only
expose the trick, which mid-trick or once completed ends with the
card just played. The first snapshot after every (re)connection is a
resync baseline, so reconnects and the initial load never replay old
moves; so are the viewer's own plays and the server's auto-plays.

Deliver through the service worker registration (installed PWAs on
Android reject page-context notifications), falling back to a page
notification when no worker is active, under a per-game tag that
replaces the previous move. sw.js now focuses the game window when a
notification is clicked.
2026-10-15 21:46:00 +08:00

99 lines
3.2 KiB
Rust

//! Application entry point.
mod api;
mod auth;
mod components;
mod model;
mod notify;
mod pages;
mod pwa;
mod ws;
use sycamore::prelude::*;
use sycamore_router::{navigate, HistoryIntegration, Route, Router};
use wasm_bindgen_futures::spawn_local;
use pages::game::GamePage;
use pages::history::HistoryPage;
use pages::leaderboard::LeaderboardPage;
use pages::lobby::LobbyPage;
use pages::login::LoginPage;
#[derive(Route, Clone)]
enum AppRoutes {
#[to("/")]
Lobby,
#[to("/login")]
Login,
#[to("/game/<id>")]
Game { id: String },
#[to("/history")]
History,
#[to("/leaderboard")]
Leaderboard,
#[not_found]
NotFound,
}
/// After the OIDC callback lands the SPA back on the configured
/// post-login redirect, forward the user to the page they were on before
/// being sent to the login page (see [`auth`]) — but only once a session
/// exists, so a cancelled login never bounces into a stale page.
fn restore_page_after_login() {
let Some(path) = auth::saved_return_to() else {
return;
};
spawn_local(async move {
match api::me().await {
Ok(Some(_)) => {
auth::clear_return_to();
if auth::current_path() != path {
navigate(&path);
}
}
// Not logged in: keep the target for the next successful
// login (a reload of the login page must not lose it).
Ok(None) => {}
// The probe failed (offline?): keep the target, too.
Err(_) => {}
}
});
}
fn main() {
console_error_panic_hook::set_once();
sycamore::render(|| {
view! {
Router(
integration=HistoryIntegration::new(),
view=|route: ReadSignal<AppRoutes>| {
view! {
div(class="app") {
(match route.get_clone() {
AppRoutes::Lobby => view! { LobbyPage() },
AppRoutes::Login => view! { LoginPage() },
AppRoutes::Game { id } => view! { GamePage(id=id) },
AppRoutes::History => view! { HistoryPage() },
AppRoutes::Leaderboard => view! { LeaderboardPage() },
AppRoutes::NotFound => view! {
div(class="status-card") {
div(class="card") {
div(class="card-body p-4") {
h1(class="h4 mb-3") { "Page not found" }
a(class="btn btn-warning", href="/") {
"Back to lobby"
}
}
}
}
},
})
}
}
}
)
}
});
pwa::register();
restore_page_after_login();
}