From ca31b4018fd2281f012a31461310e420bd8ff4c4 Mon Sep 17 00:00:00 2001 From: spinline Date: Tue, 10 Feb 2026 23:45:21 +0300 Subject: [PATCH] feat: leptos-shadcn-tabs ile torrent detay paneli eklendi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cargo.toml: leptos-shadcn-tabs ve leptos-shadcn-scroll-area eklendi - store.rs: selected_torrent sinyali eklendi (seçili torrent hash'i) - detail.rs: General, Transfer, Files, Peers tab'lı detay paneli oluşturuldu - table.rs: StoredValue ile satır tıklama ve seçili satır highlight - app.rs: TorrentDetail paneli TorrentTable altına entegre edildi --- Cargo.lock | 32 +++++ frontend/Cargo.toml | 4 +- frontend/src/app.rs | 8 +- frontend/src/components/torrent/detail.rs | 156 ++++++++++++++++++++++ frontend/src/components/torrent/mod.rs | 1 + frontend/src/components/torrent/table.rs | 30 ++++- frontend/src/store.rs | 4 +- 7 files changed, 231 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/torrent/detail.rs diff --git a/Cargo.lock b/Cargo.lock index 4afd32b..bc36dd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1267,8 +1267,10 @@ dependencies = [ "leptos-shadcn-context-menu", "leptos-shadcn-input", "leptos-shadcn-progress", + "leptos-shadcn-scroll-area", "leptos-shadcn-separator", "leptos-shadcn-sheet", + "leptos-shadcn-tabs", "leptos-use", "leptos_router", "log", @@ -2263,6 +2265,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "leptos-shadcn-scroll-area" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef3d7bdcae4919ad495529ec2a5974036fb0b959580df310f36b2fd33f90860c" +dependencies = [ + "leptos", + "leptos-node-ref", + "leptos-shadcn-signal-management", + "leptos-struct-component", + "leptos-style", + "tailwind_fuse", + "web-sys", +] + [[package]] name = "leptos-shadcn-separator" version = "0.8.1" @@ -2307,6 +2324,21 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "leptos-shadcn-tabs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f817c834e70a8359933b7b274564313be64105370611af96f05508541b661b" +dependencies = [ + "leptos", + "leptos-node-ref", + "leptos-shadcn-signal-management", + "leptos-struct-component", + "leptos-style", + "tailwind_fuse", + "web-sys", +] + [[package]] name = "leptos-struct-component" version = "0.2.0" diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 1b6d51a..ed0b321 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -43,4 +43,6 @@ leptos-shadcn-context-menu = "0.8" leptos-shadcn-separator = "0.8" leptos-shadcn-progress = "0.8" leptos-shadcn-avatar = "0.8" -leptos-shadcn-sheet = "0.8" \ No newline at end of file +leptos-shadcn-sheet = "0.8" +leptos-shadcn-tabs = "0.8" +leptos-shadcn-scroll-area = "0.8" \ No newline at end of file diff --git a/frontend/src/app.rs b/frontend/src/app.rs index d54ed6f..2120287 100644 --- a/frontend/src/app.rs +++ b/frontend/src/app.rs @@ -1,6 +1,7 @@ use crate::components::layout::protected::Protected; use crate::components::toast::ToastContainer; use crate::components::torrent::table::TorrentTable; +use crate::components::torrent::detail::TorrentDetail; use crate::components::auth::login::Login; use crate::components::auth::setup::Setup; use leptos::prelude::*; @@ -124,7 +125,12 @@ pub fn App() -> impl IntoView { }.into_any()> - +
+
+ +
+ +
diff --git a/frontend/src/components/torrent/detail.rs b/frontend/src/components/torrent/detail.rs new file mode 100644 index 0000000..615ce8a --- /dev/null +++ b/frontend/src/components/torrent/detail.rs @@ -0,0 +1,156 @@ +use leptos::prelude::*; +use leptos_shadcn_tabs::{Tabs, TabsList, TabsTrigger, TabsContent}; + +fn format_bytes(bytes: i64) -> String { + const UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"]; + if bytes < 1024 { return format!("{} B", bytes); } + let i = (bytes as f64).log2().div_euclid(10.0) as usize; + format!("{:.1} {}", (bytes as f64) / 1024_f64.powi(i as i32), UNITS[i]) +} + +fn format_speed(bytes_per_sec: i64) -> String { + if bytes_per_sec == 0 { return "0 B/s".to_string(); } + format!("{}/s", format_bytes(bytes_per_sec)) +} + +fn format_date(timestamp: i64) -> String { + if timestamp <= 0 { return "N/A".to_string(); } + let dt = chrono::DateTime::from_timestamp(timestamp, 0); + match dt { Some(dt) => dt.format("%d/%m/%Y %H:%M").to_string(), None => "N/A".to_string() } +} + +fn format_duration(seconds: i64) -> String { + if seconds <= 0 { return "∞".to_string(); } + let days = seconds / 86400; + let hours = (seconds % 86400) / 3600; + let minutes = (seconds % 3600) / 60; + let secs = seconds % 60; + if days > 0 { format!("{}d {}h", days, hours) } + else if hours > 0 { format!("{}h {}m", hours, minutes) } + else if minutes > 0 { format!("{}m {}s", minutes, secs) } + else { format!("{}s", secs) } +} + +#[component] +pub fn TorrentDetail() -> impl IntoView { + let store = use_context::().expect("store not provided"); + + let torrent = Memo::new(move |_| { + let hash = store.selected_torrent.get()?; + store.torrents.with(|map| map.get(&hash).cloned()) + }); + + let close = move |_| { + store.selected_torrent.set(None); + }; + + view! { + + {move || { + let t = torrent.get().unwrap(); + let name = t.name.clone(); + let status_color = match t.status { + shared::TorrentStatus::Seeding => "text-green-500", + shared::TorrentStatus::Downloading => "text-blue-500", + shared::TorrentStatus::Paused => "text-yellow-500", + shared::TorrentStatus::Error => "text-red-500", + _ => "text-muted-foreground", + }; + let status_text = format!("{:?}", t.status); + + view! { +
+ // Header +
+
+

{name}

+ {status_text} +
+ +
+ + // Tabs + +
+ + "General" + "Transfer" + "Files" + "Peers" + +
+ + +
+ + + + + + + +
+
+ + +
+ + + + + + +
+
+ + +
+ + + + "File list will be available when file API is connected." +
+
+ + +
+ + + + "Peer list will be available when peer API is connected." +
+
+
+
+ } + }} +
+ } +} + +#[component] +fn DetailItem( + #[prop(into)] label: String, + #[prop(into)] value: String, +) -> impl IntoView { + let title = value.clone(); + view! { +
+ {label} + {value} +
+ } +} diff --git a/frontend/src/components/torrent/mod.rs b/frontend/src/components/torrent/mod.rs index 438f1c3..50f6a93 100644 --- a/frontend/src/components/torrent/mod.rs +++ b/frontend/src/components/torrent/mod.rs @@ -1,2 +1,3 @@ pub mod table; pub mod add_torrent; +pub mod detail; diff --git a/frontend/src/components/torrent/table.rs b/frontend/src/components/torrent/table.rs index ea33924..ee03db7 100644 --- a/frontend/src/components/torrent/table.rs +++ b/frontend/src/components/torrent/table.rs @@ -210,6 +210,8 @@ fn TorrentRow( let h = hash.clone(); let torrent = Memo::new(move |_| store.torrents.with(|map| map.get(&h).cloned())); + let stored_hash = StoredValue::new(hash.clone()); + view! { { @@ -219,7 +221,18 @@ fn TorrentRow( let status_color = match t.status { shared::TorrentStatus::Seeding => "text-green-500", shared::TorrentStatus::Downloading => "text-blue-500", shared::TorrentStatus::Paused => "text-yellow-500", shared::TorrentStatus::Error => "text-red-500", _ => "text-muted-foreground" }; view! { -
+
{t_name.clone()}
{format_bytes(t.size)}
@@ -251,6 +264,8 @@ fn TorrentCard( let h = hash.clone(); let torrent = Memo::new(move |_| store.torrents.with(|map| map.get(&h).cloned())); + let stored_hash = StoredValue::new(hash.clone()); + view! { { @@ -260,6 +275,18 @@ fn TorrentCard( let status_badge_class = match t.status { shared::TorrentStatus::Seeding => "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 border-green-200 dark:border-green-800", shared::TorrentStatus::Downloading => "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 border-blue-200 dark:border-blue-800", shared::TorrentStatus::Paused => "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800", shared::TorrentStatus::Error => "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 border-red-200 dark:border-red-800", _ => "bg-muted text-muted-foreground" }; view! { +
@@ -285,6 +312,7 @@ fn TorrentCard(
+
} } } diff --git a/frontend/src/store.rs b/frontend/src/store.rs index 44796fc..e0fa3f0 100644 --- a/frontend/src/store.rs +++ b/frontend/src/store.rs @@ -69,6 +69,7 @@ pub struct TorrentStore { pub global_stats: RwSignal, pub notifications: RwSignal>, pub user: RwSignal>, + pub selected_torrent: RwSignal>, } pub fn provide_torrent_store() { @@ -78,10 +79,11 @@ pub fn provide_torrent_store() { let global_stats = RwSignal::new(GlobalStats::default()); let notifications = RwSignal::new(Vec::::new()); let user = RwSignal::new(Option::::None); + let selected_torrent = RwSignal::new(Option::::None); let show_browser_notification = crate::utils::notification::use_app_notification(); - let store = TorrentStore { torrents, filter, search_query, global_stats, notifications, user }; + let store = TorrentStore { torrents, filter, search_query, global_stats, notifications, user, selected_torrent }; provide_context(store); let notifications_for_sse = notifications;