feat: implement notification system and backoff strategy for rTorrent polling
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
use crate::components::layout::sidebar::Sidebar;
|
||||
use crate::components::layout::statusbar::StatusBar;
|
||||
use crate::components::layout::toolbar::Toolbar;
|
||||
use crate::components::toast::ToastContainer;
|
||||
use crate::components::torrent::table::TorrentTable;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
use crate::components::layout::sidebar::Sidebar;
|
||||
use crate::components::layout::toolbar::Toolbar;
|
||||
use crate::components::layout::statusbar::StatusBar;
|
||||
use crate::components::torrent::table::TorrentTable;
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
@@ -12,7 +13,7 @@ pub fn App() -> impl IntoView {
|
||||
view! {
|
||||
<div class="drawer lg:drawer-open h-screen w-full" style="height: 100dvh;">
|
||||
<input id="my-drawer" type="checkbox" class="drawer-toggle" />
|
||||
|
||||
|
||||
<div class="drawer-content flex flex-col h-full overflow-hidden bg-base-100 text-base-content text-sm select-none">
|
||||
// Toolbar at the top
|
||||
<Toolbar />
|
||||
@@ -25,7 +26,7 @@ pub fn App() -> impl IntoView {
|
||||
</Routes>
|
||||
</Router>
|
||||
</main>
|
||||
|
||||
|
||||
// Status Bar at the bottom
|
||||
<StatusBar />
|
||||
</div>
|
||||
@@ -36,6 +37,8 @@ pub fn App() -> impl IntoView {
|
||||
<Sidebar />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ToastContainer />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod modal;
|
||||
pub mod context_menu;
|
||||
pub mod layout;
|
||||
pub mod modal;
|
||||
pub mod toast;
|
||||
pub mod torrent;
|
||||
|
||||
35
frontend/src/components/toast.rs
Normal file
35
frontend/src/components/toast.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use leptos::*;
|
||||
use shared::NotificationLevel;
|
||||
|
||||
#[component]
|
||||
pub fn ToastContainer() -> impl IntoView {
|
||||
let store = use_context::<crate::store::TorrentStore>().expect("store not provided");
|
||||
let notifications = store.notifications;
|
||||
|
||||
view! {
|
||||
<div class="toast toast-end toast-bottom z-[9999]">
|
||||
{move || notifications.get().into_iter().map(|item| {
|
||||
let alert_class = match item.notification.level {
|
||||
NotificationLevel::Info => "alert-info",
|
||||
NotificationLevel::Success => "alert-success",
|
||||
NotificationLevel::Warning => "alert-warning",
|
||||
NotificationLevel::Error => "alert-error",
|
||||
};
|
||||
|
||||
let icon = match item.notification.level {
|
||||
NotificationLevel::Info => view! { <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> },
|
||||
NotificationLevel::Success => view! { <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg> },
|
||||
NotificationLevel::Warning => view! { <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg> },
|
||||
NotificationLevel::Error => view! { <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg> },
|
||||
};
|
||||
|
||||
view! {
|
||||
<div class={format!("alert {} shadow-lg transition-all duration-300 animate-in slide-in-from-bottom-5 fade-in", alert_class)}>
|
||||
{icon}
|
||||
<span>{item.notification.message}</span>
|
||||
</div>
|
||||
}
|
||||
}).collect::<Vec<_>>()}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
use futures::StreamExt;
|
||||
use gloo_net::eventsource::futures::EventSource;
|
||||
use leptos::*;
|
||||
use shared::{AppEvent, GlobalStats, Torrent};
|
||||
use shared::{AppEvent, GlobalStats, SystemNotification, Torrent};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct NotificationItem {
|
||||
pub id: u64,
|
||||
pub notification: SystemNotification,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum FilterStatus {
|
||||
@@ -36,6 +42,7 @@ pub struct TorrentStore {
|
||||
pub filter: RwSignal<FilterStatus>,
|
||||
pub search_query: RwSignal<String>,
|
||||
pub global_stats: RwSignal<GlobalStats>,
|
||||
pub notifications: RwSignal<Vec<NotificationItem>>,
|
||||
}
|
||||
|
||||
pub fn provide_torrent_store() {
|
||||
@@ -43,12 +50,14 @@ pub fn provide_torrent_store() {
|
||||
let filter = create_rw_signal(FilterStatus::All);
|
||||
let search_query = create_rw_signal(String::new());
|
||||
let global_stats = create_rw_signal(GlobalStats::default());
|
||||
let notifications = create_rw_signal(Vec::<NotificationItem>::new());
|
||||
|
||||
let store = TorrentStore {
|
||||
torrents,
|
||||
filter,
|
||||
search_query,
|
||||
global_stats,
|
||||
notifications,
|
||||
};
|
||||
provide_context(store);
|
||||
|
||||
@@ -105,6 +114,25 @@ pub fn provide_torrent_store() {
|
||||
AppEvent::Stats(stats) => {
|
||||
global_stats.set(stats);
|
||||
}
|
||||
AppEvent::Notification(n) => {
|
||||
let id = js_sys::Date::now() as u64;
|
||||
let item = NotificationItem {
|
||||
id,
|
||||
notification: n,
|
||||
};
|
||||
notifications.update(|list| list.push(item));
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
let notifications = notifications;
|
||||
let _ = set_timeout(
|
||||
move || {
|
||||
notifications.update(|list| {
|
||||
list.retain(|i| i.id != id);
|
||||
});
|
||||
},
|
||||
std::time::Duration::from_secs(5),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user