Auto-dismiss error toasts after 10 seconds

This commit is contained in:
2026-09-18 09:18:17 +08:00
parent 294a93912d
commit f7fa8e78e5
6 changed files with 29 additions and 4 deletions
+1
View File
@@ -1,2 +1,3 @@
pub mod card; pub mod card;
pub mod summary; pub mod summary;
pub mod toast;
+20
View File
@@ -0,0 +1,20 @@
//! Auto-dismissing error toast.
use gloo_timers::callback::Timeout;
use sycamore::prelude::*;
const TOAST_MS: u32 = 10_000;
/// Renders the error from `error` as a toast; hides it after `TOAST_MS`.
/// A new error replaces the message and restarts the timer.
pub fn toast(error: Signal<Option<String>>) -> View {
create_effect(move || {
if error.get_clone().is_some() {
// Held until cleanup; dropped (cancelled) when the effect re-runs.
let timeout = Timeout::new(TOAST_MS, move || error.set(None));
on_cleanup(move || drop(timeout));
}
});
view! {
(move || error.get_clone().map(|e| view! { div(class="toast") { (e) } }))
}
}
+2 -1
View File
@@ -3,6 +3,7 @@ use sycamore::prelude::*;
use crate::components::card::{card_back, card_img}; use crate::components::card::{card_back, card_img};
use crate::components::summary::{hand_summary_modal, summary_rows}; use crate::components::summary::{hand_summary_modal, summary_rows};
use crate::components::toast::toast;
use crate::model::{card_label, GameView, MoveView, PlayerView, Scores, ServerMessage}; use crate::model::{card_label, GameView, MoveView, PlayerView, Scores, ServerMessage};
use crate::ws::{self, GameSocket}; use crate::ws::{self, GameSocket};
@@ -123,7 +124,7 @@ pub fn GamePage(id: String) -> View {
view! { view! {
div(class="game-page") { div(class="game-page") {
(move || error.get_clone().map(|e| view! { div(class="toast") { (e) } })) (toast(error))
(move || match game.get_clone() { (move || match game.get_clone() {
None => { None => {
let status = if closed.get() { let status = if closed.get() {
+2 -1
View File
@@ -3,6 +3,7 @@ use wasm_bindgen_futures::spawn_local;
use sycamore::prelude::*; use sycamore::prelude::*;
use crate::api; use crate::api;
use crate::components::toast::toast;
use crate::model::MatchesPage; use crate::model::MatchesPage;
#[component] #[component]
@@ -45,7 +46,7 @@ pub fn HistoryPage() -> View {
a(href="/leaderboard") { "Leaderboard" } a(href="/leaderboard") { "Leaderboard" }
} }
h1 { "My matches" } h1 { "My matches" }
(move || error.get_clone().map(|e| view! { div(class="toast") { (e) } })) (toast(error))
(move || match page.get_clone() { (move || match page.get_clone() {
None => view! { p(class="status") { "Loading…" } }, None => view! { p(class="status") { "Loading…" } },
Some(_) if rows.get_clone().is_empty() => view! { Some(_) if rows.get_clone().is_empty() => view! {
+2 -1
View File
@@ -3,6 +3,7 @@ use wasm_bindgen_futures::spawn_local;
use sycamore::prelude::*; use sycamore::prelude::*;
use crate::api; use crate::api;
use crate::components::toast::toast;
use crate::model::LeaderboardPage; use crate::model::LeaderboardPage;
#[component] #[component]
@@ -24,7 +25,7 @@ pub fn LeaderboardPage() -> View {
a(href="/history") { "My matches" } a(href="/history") { "My matches" }
} }
h1 { "Leaderboard" } h1 { "Leaderboard" }
(move || error.get_clone().map(|e| view! { div(class="toast") { (e) } })) (toast(error))
(move || match page.get_clone() { (move || match page.get_clone() {
None => view! { p(class="status") { "Loading…" } }, None => view! { p(class="status") { "Loading…" } },
Some(p) => { Some(p) => {
+2 -1
View File
@@ -4,6 +4,7 @@ use sycamore::prelude::*;
use sycamore_router::navigate; use sycamore_router::navigate;
use crate::api; use crate::api;
use crate::components::toast::toast;
use crate::model::{GameTypeInfo, User}; use crate::model::{GameTypeInfo, User};
/// Used when the game-types fetch fails: match creation must still work. /// Used when the game-types fetch fails: match creation must still work.
@@ -70,7 +71,7 @@ pub fn LobbyPage() -> View {
view! { view! {
div(class="lobby") { div(class="lobby") {
h1 { "Scopone scientifico" } h1 { "Scopone scientifico" }
(move || error.get_clone().map(|e| view! { div(class="toast") { (e) } })) (toast(error))
(move || match user.get_clone() { (move || match user.get_clone() {
None => view! { p(class="status") { "Loading…" } }, None => view! { p(class="status") { "Loading…" } },
Some(None) => view! { Some(None) => view! {