diff --git a/frontend/public/tailwind.css b/frontend/public/tailwind.css index 4f37113..0fe9ed0 100644 --- a/frontend/public/tailwind.css +++ b/frontend/public/tailwind.css @@ -933,6 +933,9 @@ .mb-6 { margin-bottom: calc(var(--spacing) * 6); } + .ml-1 { + margin-left: calc(var(--spacing) * 1); + } .ml-auto { margin-left: auto; } @@ -1403,6 +1406,9 @@ .uppercase { text-transform: uppercase; } + .opacity-0 { + opacity: 0%; + } .opacity-70 { opacity: 70%; } @@ -1514,6 +1520,13 @@ -webkit-user-select: none; user-select: none; } + .group-hover\:opacity-50 { + &:is(:where(.group):hover *) { + @media (hover: hover) { + opacity: 50%; + } + } + } .hover\:bg-accent { &:hover { @media (hover: hover) { @@ -1521,6 +1534,13 @@ } } } + .hover\:bg-base-300 { + &:hover { + @media (hover: hover) { + background-color: var(--color-base-300); + } + } + } .hover\:bg-primary\/90 { &:hover { @media (hover: hover) { diff --git a/frontend/src/components/torrent/table.rs b/frontend/src/components/torrent/table.rs index 55466a9..b6a7466 100644 --- a/frontend/src/components/torrent/table.rs +++ b/frontend/src/components/torrent/table.rs @@ -19,12 +19,32 @@ fn format_speed(bytes_per_sec: i64) -> String { format!("{}/s", format_bytes(bytes_per_sec)) } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum SortColumn { + Name, + Size, + Progress, + Status, + DownSpeed, + UpSpeed, + ETA, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum SortDirection { + Ascending, + Descending, +} + #[component] pub fn TorrentTable() -> impl IntoView { let store = use_context::().expect("store not provided"); + + let sort_col = create_rw_signal(SortColumn::Name); + let sort_dir = create_rw_signal(SortDirection::Ascending); let filtered_torrents = move || { - store.torrents.get().into_iter().filter(|t| { + let mut torrents = store.torrents.get().into_iter().filter(|t| { let filter = store.filter.get(); match filter { crate::store::FilterStatus::All => true, @@ -34,7 +54,56 @@ pub fn TorrentTable() -> impl IntoView { crate::store::FilterStatus::Inactive => t.status == shared::TorrentStatus::Paused || t.status == shared::TorrentStatus::Error, _ => true } - }).collect::>() + }).collect::>(); + + torrents.sort_by(|a, b| { + let col = sort_col.get(); + let dir = sort_dir.get(); + let cmp = match col { + SortColumn::Name => a.name.to_lowercase().cmp(&b.name.to_lowercase()), + SortColumn::Size => a.size.cmp(&b.size), + SortColumn::Progress => a.percent_complete.partial_cmp(&b.percent_complete).unwrap_or(std::cmp::Ordering::Equal), + SortColumn::Status => format!("{:?}", a.status).cmp(&format!("{:?}", b.status)), + SortColumn::DownSpeed => a.down_rate.cmp(&b.down_rate), + SortColumn::UpSpeed => a.up_rate.cmp(&b.up_rate), + SortColumn::ETA => { + // ETA 0 means infinity usually, so we need to handle it. + // But for simple sorting, maybe just treat is numeric? + // Let's treat 0 as MAX for ascending, MIN for descending? Or just as is? + // Usually negative or 0 means unknown/inf. + // Let's handle 0 as very large number for sorting purposes if we want it at the end of ascending + let a_eta = if a.eta <= 0 { i64::MAX } else { a.eta }; + let b_eta = if b.eta <= 0 { i64::MAX } else { b.eta }; + a_eta.cmp(&b_eta) + } + }; + if dir == SortDirection::Descending { cmp.reverse() } else { cmp } + }); + + torrents + }; + + let handle_sort = move |col: SortColumn| { + if sort_col.get() == col { + sort_dir.update(|d| *d = match d { + SortDirection::Ascending => SortDirection::Descending, + SortDirection::Descending => SortDirection::Ascending, + }); + } else { + sort_col.set(col); + sort_dir.set(SortDirection::Ascending); + } + }; + + let sort_arrow = move |col: SortColumn| { + if sort_col.get() == col { + match sort_dir.get() { + SortDirection::Ascending => view!{ "▲" }.into_view(), + SortDirection::Descending => view!{ "▼" }.into_view(), + } + } else { + view!{ "▲" }.into_view() + } }; view! { @@ -47,15 +116,29 @@ pub fn TorrentTable() -> impl IntoView { - "Name" - "Size" - "Progress" - "Status" + +
"Name" {move || sort_arrow(SortColumn::Name)}
+ + +
"Size" {move || sort_arrow(SortColumn::Size)}
+ + +
"Progress" {move || sort_arrow(SortColumn::Progress)}
+ + +
"Status" {move || sort_arrow(SortColumn::Status)}
+ // "Seeds" // Not available in shared::Torrent // "Peers" // Not available in shared::Torrent - "Down Speed" - "Up Speed" - "ETA" + +
"Down Speed" {move || sort_arrow(SortColumn::DownSpeed)}
+ + +
"Up Speed" {move || sort_arrow(SortColumn::UpSpeed)}
+ + +
"ETA" {move || sort_arrow(SortColumn::ETA)}
+