use futures::StreamExt; use gloo_net::eventsource::futures::EventSource; use leptos::*; use shared::{AppEvent, GlobalStats, NotificationLevel, SystemNotification, Torrent}; #[derive(Clone, Debug, PartialEq)] pub struct NotificationItem { pub id: u64, pub notification: SystemNotification, } // ============================================================================ // Toast Helper Functions (Clean Code: Single Responsibility) // ============================================================================ /// Shows a toast notification with the given level and message. /// Auto-removes after 5 seconds. pub fn show_toast(level: NotificationLevel, message: impl Into) { if let Some(store) = use_context::() { let id = js_sys::Date::now() as u64; let notification = SystemNotification { level, message: message.into(), }; let item = NotificationItem { id, notification }; store.notifications.update(|list| list.push(item)); // Auto-remove after 5 seconds let notifications = store.notifications; let _ = set_timeout( move || { notifications.update(|list| list.retain(|i| i.id != id)); }, std::time::Duration::from_secs(5), ); } } /// Convenience function for success toasts pub fn toast_success(message: impl Into) { show_toast(NotificationLevel::Success, message); } /// Convenience function for error toasts pub fn toast_error(message: impl Into) { show_toast(NotificationLevel::Error, message); } /// Convenience function for info toasts pub fn toast_info(message: impl Into) { show_toast(NotificationLevel::Info, message); } /// Convenience function for warning toasts pub fn toast_warning(message: impl Into) { show_toast(NotificationLevel::Warning, message); } // ============================================================================ // Action Message Mapping (Clean Code: DRY Principle) // ============================================================================ /// Maps torrent action strings to user-friendly Turkish messages. /// Returns (success_message, error_message) pub fn get_action_messages(action: &str) -> (&'static str, &'static str) { match action { "start" => ("Torrent başlatıldı", "Torrent başlatılamadı"), "stop" => ("Torrent durduruldu", "Torrent durdurulamadı"), "pause" => ("Torrent duraklatıldı", "Torrent duraklatılamadı"), "delete" => ("Torrent silindi", "Torrent silinemedi"), "delete_with_data" => ("Torrent ve verileri silindi", "Torrent silinemedi"), "recheck" => ("Torrent kontrol ediliyor", "Kontrol başlatılamadı"), _ => ("İşlem tamamlandı", "İşlem başarısız"), } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum FilterStatus { All, Downloading, Seeding, Completed, Paused, Inactive, Active, Error, } impl FilterStatus { pub fn as_str(&self) -> &'static str { match self { FilterStatus::All => "All", FilterStatus::Downloading => "Downloading", FilterStatus::Seeding => "Seeding", FilterStatus::Completed => "Completed", FilterStatus::Paused => "Paused", FilterStatus::Inactive => "Inactive", FilterStatus::Active => "Active", FilterStatus::Error => "Error", } } } #[derive(Clone, Copy, Debug)] pub struct TorrentStore { pub torrents: RwSignal>, pub filter: RwSignal, pub search_query: RwSignal, pub global_stats: RwSignal, pub notifications: RwSignal>, } pub fn provide_torrent_store() { let torrents = create_rw_signal(Vec::::new()); 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::::new()); let store = TorrentStore { torrents, filter, search_query, global_stats, notifications, }; provide_context(store); // Initialize SSE connection create_effect(move |_| { spawn_local(async move { let mut es = EventSource::new("/api/events").unwrap(); let mut stream = es.subscribe("message").unwrap(); while let Some(Ok((_, msg))) = stream.next().await { if let Some(data_str) = msg.data().as_string() { if let Ok(event) = serde_json::from_str::(&data_str) { match event { AppEvent::FullList { torrents: list, .. } => { torrents.set(list); } AppEvent::Update(update) => { torrents.update(|list| { if let Some(t) = list.iter_mut().find(|t| t.hash == update.hash) { if let Some(name) = update.name { t.name = name; } if let Some(size) = update.size { t.size = size; } if let Some(down_rate) = update.down_rate { t.down_rate = down_rate; } if let Some(up_rate) = update.up_rate { t.up_rate = up_rate; } if let Some(percent_complete) = update.percent_complete { t.percent_complete = percent_complete; } if let Some(completed) = update.completed { t.completed = completed; } if let Some(eta) = update.eta { t.eta = eta; } if let Some(status) = update.status { t.status = status; } if let Some(error_message) = update.error_message { t.error_message = error_message; } if let Some(label) = update.label { t.label = Some(label); } } }); } 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), ); } } } } } }); }); }