From f35b119c0d688d69a7354c4ff198db4385f178f7 Mon Sep 17 00:00:00 2001 From: spinline Date: Wed, 11 Feb 2026 20:02:58 +0300 Subject: [PATCH] fix: replace leptos-shadcn-toast with custom implementation to fix WASM panic --- frontend/Cargo.toml | 2 +- frontend/src/app.rs | 8 +-- frontend/src/components/mod.rs | 1 + frontend/src/components/toast.rs | 111 +++++++++++++++++++++++++++++++ frontend/src/store.rs | 13 ++-- 5 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 frontend/src/components/toast.rs diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 2092e69..8331bb9 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -50,7 +50,7 @@ leptos-shadcn-scroll-area = "0.8" leptos-shadcn-dialog = "0.8" leptos-shadcn-label = "0.8" leptos-shadcn-alert = "0.8" -leptos-shadcn-toast = "0.8" + leptos-shadcn-dropdown-menu = "0.8" leptos-shadcn-tooltip = "0.8" leptos-shadcn-skeleton = "0.8" \ No newline at end of file diff --git a/frontend/src/app.rs b/frontend/src/app.rs index 88148bc..74cc82f 100644 --- a/frontend/src/app.rs +++ b/frontend/src/app.rs @@ -8,20 +8,20 @@ use leptos::task::spawn_local; use leptos_router::components::{Router, Routes, Route}; use leptos_router::hooks::use_navigate; use leptos_shadcn_skeleton::Skeleton; -use leptos_shadcn_toast::SonnerProvider; +use crate::components::toast::Toaster; #[component] pub fn App() -> impl IntoView { view! { - - - + + } } #[component] fn InnerApp() -> impl IntoView { crate::store::provide_torrent_store(); + crate::components::toast::provide_toast_context(); let store = use_context::(); let is_loading = signal(true); diff --git a/frontend/src/components/mod.rs b/frontend/src/components/mod.rs index fccaf48..2cb44a6 100644 --- a/frontend/src/components/mod.rs +++ b/frontend/src/components/mod.rs @@ -2,3 +2,4 @@ pub mod context_menu; pub mod layout; pub mod torrent; pub mod auth; +pub mod toast; diff --git a/frontend/src/components/toast.rs b/frontend/src/components/toast.rs new file mode 100644 index 0000000..b81b0e9 --- /dev/null +++ b/frontend/src/components/toast.rs @@ -0,0 +1,111 @@ +use leptos::prelude::*; +use std::collections::HashMap; +use uuid::Uuid; +use shared::NotificationLevel; + +#[derive(Clone, Debug, PartialEq)] +pub struct Toast { + pub id: String, + pub message: String, + pub level: NotificationLevel, + pub visible: RwSignal, +} + +#[derive(Clone, Copy)] +pub struct ToastContext { + pub toasts: RwSignal>, +} + +impl ToastContext { + pub fn add(&self, message: impl Into, level: NotificationLevel) { + let id = Uuid::new_v4().to_string(); + let message = message.into(); + let toast = Toast { + id: id.clone(), + message, + level, + visible: RwSignal::new(true), + }; + + self.toasts.update(|m| { + m.insert(id.clone(), toast); + }); + + // Auto remove after 5 seconds + let toasts = self.toasts; + let id_clone = id.clone(); + leptos::task::spawn_local(async move { + gloo_timers::future::TimeoutFuture::new(5000).await; + toasts.update(|m| { + if let Some(t) = m.get(&id_clone) { + t.visible.set(false); + } + }); + // Wait for animation + gloo_timers::future::TimeoutFuture::new(300).await; + toasts.update(|m| { + m.remove(&id_clone); + }); + }); + } +} + +pub fn provide_toast_context() { + let toasts = RwSignal::new(HashMap::new()); + provide_context(ToastContext { toasts }); +} + +#[component] +pub fn Toaster() -> impl IntoView { + let context = expect_context::(); + + view! { +
+ {move || { + context.toasts.get().into_values().map(|toast| { + view! { } + }).collect::>() + }} +
+ } +} + +#[component] +fn ToastItem(toast: Toast) -> impl IntoView { + let (visible, set_visible) = (toast.visible, toast.visible.write_only()); + + let base_classes = "pointer-events-auto relative w-full rounded-lg border p-4 shadow-lg transition-all duration-300 ease-in-out"; + let color_classes = match toast.level { + NotificationLevel::Success => "bg-green-50 text-green-900 border-green-200 dark:bg-green-900 dark:text-green-100 dark:border-green-800", + NotificationLevel::Error => "bg-red-50 text-red-900 border-red-200 dark:bg-red-900 dark:text-red-100 dark:border-red-800", + NotificationLevel::Warning => "bg-yellow-50 text-yellow-900 border-yellow-200 dark:bg-yellow-900 dark:text-yellow-100 dark:border-yellow-800", + NotificationLevel::Info => "bg-blue-50 text-blue-900 border-blue-200 dark:bg-blue-900 dark:text-blue-100 dark:border-blue-800", + }; + + view! { + + } +} diff --git a/frontend/src/store.rs b/frontend/src/store.rs index e2bfb1c..f73c456 100644 --- a/frontend/src/store.rs +++ b/frontend/src/store.rs @@ -7,15 +7,18 @@ use std::collections::HashMap; use struct_patch::traits::Patch; use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; +use crate::components::toast::ToastContext; + pub fn show_toast(level: NotificationLevel, message: impl Into) { let msg = message.into(); gloo_console::log!("TOAST CALL:", &msg, format!("{:?}", level)); log::info!("Displaying toast: [{:?}] {}", level, msg); - match level { - NotificationLevel::Info => { leptos_shadcn_toast::toast::info(&msg).show(); }, - NotificationLevel::Success => { leptos_shadcn_toast::toast::success(&msg).show(); }, - NotificationLevel::Warning => { leptos_shadcn_toast::toast::warning(&msg).show(); }, - NotificationLevel::Error => { leptos_shadcn_toast::toast::error(&msg).show(); }, + + if let Some(context) = use_context::() { + context.add(msg, level); + } else { + log::error!("ToastContext not found!"); + gloo_console::error!("ToastContext not found!"); } }