Files
vibetorrent/frontend/src/store.rs

209 lines
8.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String>) {
if let Some(store) = use_context::<TorrentStore>() {
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<String>) {
show_toast(NotificationLevel::Success, message);
}
/// Convenience function for error toasts
pub fn toast_error(message: impl Into<String>) {
show_toast(NotificationLevel::Error, message);
}
/// Convenience function for info toasts
pub fn toast_info(message: impl Into<String>) {
show_toast(NotificationLevel::Info, message);
}
/// Convenience function for warning toasts
pub fn toast_warning(message: impl Into<String>) {
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<Vec<Torrent>>,
pub filter: RwSignal<FilterStatus>,
pub search_query: RwSignal<String>,
pub global_stats: RwSignal<GlobalStats>,
pub notifications: RwSignal<Vec<NotificationItem>>,
}
pub fn provide_torrent_store() {
let torrents = create_rw_signal(Vec::<Torrent>::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::<NotificationItem>::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::<AppEvent>(&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),
);
}
}
}
}
}
});
});
}