diff --git a/frontend/src/components/layout/statusbar.rs b/frontend/src/components/layout/statusbar.rs
index 369ba09..296eaeb 100644
--- a/frontend/src/components/layout/statusbar.rs
+++ b/frontend/src/components/layout/statusbar.rs
@@ -1,5 +1,4 @@
use leptos::*;
-use wasm_bindgen::JsCast;
#[component]
pub fn StatusBar() -> impl IntoView {
@@ -34,10 +33,10 @@ pub fn StatusBar() -> impl IntoView {
base.to_string()
}
}>
-
@@ -49,8 +48,8 @@ pub fn StatusBar() -> impl IntoView {
// Backdrop to close on outside click
// iOS Safari requires cursor:pointer inline style for click events on div elements
- impl IntoView {
{
let themes = vec![
- "light", "dark", "cupcake", "dracula", "cyberpunk",
- "emerald", "luxury", "nord", "sunset", "winter",
+ "light", "dark", "cupcake", "dracula", "cyberpunk",
+ "emerald", "luxury", "nord", "sunset", "winter",
"night", "synthwave", "retro", "forest"
];
themes.into_iter().map(|theme| {
view! {
- impl IntoView {
}
-
+
diff --git a/frontend/src/components/torrent/table.rs b/frontend/src/components/torrent/table.rs
index 3abe579..1ee9147 100644
--- a/frontend/src/components/torrent/table.rs
+++ b/frontend/src/components/torrent/table.rs
@@ -2,16 +2,17 @@ use leptos::*;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
-
-
-
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])
+ format!(
+ "{:.1} {}",
+ (bytes as f64) / 1024_f64.powi(i as i32),
+ UNITS[i]
+ )
}
fn format_speed(bytes_per_sec: i64) -> String {
@@ -41,32 +42,47 @@ enum SortDirection {
#[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 || {
- let mut torrents = store.torrents.get().into_iter().filter(|t| {
- let filter = store.filter.get();
- let search = store.search_query.get().to_lowercase();
-
- let matches_filter = match filter {
- crate::store::FilterStatus::All => true,
- crate::store::FilterStatus::Downloading => t.status == shared::TorrentStatus::Downloading,
- crate::store::FilterStatus::Seeding => t.status == shared::TorrentStatus::Seeding,
- crate::store::FilterStatus::Completed => t.status == shared::TorrentStatus::Seeding || t.status == shared::TorrentStatus::Paused, // Approximate
- crate::store::FilterStatus::Inactive => t.status == shared::TorrentStatus::Paused || t.status == shared::TorrentStatus::Error,
- _ => true
- };
+ let mut torrents = store
+ .torrents
+ .get()
+ .into_iter()
+ .filter(|t| {
+ let filter = store.filter.get();
+ let search = store.search_query.get().to_lowercase();
- let matches_search = if search.is_empty() {
- true
- } else {
- t.name.to_lowercase().contains(&search)
- };
+ let matches_filter = match filter {
+ crate::store::FilterStatus::All => true,
+ crate::store::FilterStatus::Downloading => {
+ t.status == shared::TorrentStatus::Downloading
+ }
+ crate::store::FilterStatus::Seeding => {
+ t.status == shared::TorrentStatus::Seeding
+ }
+ crate::store::FilterStatus::Completed => {
+ t.status == shared::TorrentStatus::Seeding
+ || t.status == shared::TorrentStatus::Paused
+ } // Approximate
+ crate::store::FilterStatus::Inactive => {
+ t.status == shared::TorrentStatus::Paused
+ || t.status == shared::TorrentStatus::Error
+ }
+ _ => true,
+ };
- matches_filter && matches_search
- }).collect::>();
+ let matches_search = if search.is_empty() {
+ true
+ } else {
+ t.name.to_lowercase().contains(&search)
+ };
+
+ matches_filter && matches_search
+ })
+ .collect::>();
torrents.sort_by(|a, b| {
let col = sort_col.get();
@@ -74,32 +90,41 @@ pub fn TorrentTable() -> impl IntoView {
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::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)
+ // 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 }
+ 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,
+ sort_dir.update(|d| {
+ *d = match d {
+ SortDirection::Ascending => SortDirection::Descending,
+ SortDirection::Descending => SortDirection::Ascending,
+ }
});
} else {
sort_col.set(col);
@@ -110,11 +135,16 @@ pub fn TorrentTable() -> impl IntoView {
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(),
+ SortDirection::Ascending => {
+ view! { "▲" }.into_view()
+ }
+ SortDirection::Descending => {
+ view! { "▼" }.into_view()
+ }
}
} else {
- view!{ "▲" }.into_view()
+ view! { "▲" }
+ .into_view()
}
};
@@ -134,29 +164,34 @@ pub fn TorrentTable() -> impl IntoView {
set_menu_visible.set(false); // Close menu immediately
spawn_local(async move {
- let action_req = if action == "delete_with_data" { "delete_with_data" } else { &action };
-
+ let action_req = if action == "delete_with_data" {
+ "delete_with_data"
+ } else {
+ &action
+ };
+
let req_body = shared::TorrentActionRequest {
hash: hash.clone(),
action: action_req.to_string(),
};
- let client = gloo_net::http::Request::post("/api/torrents/action")
- .json(&req_body);
+ let client = gloo_net::http::Request::post("/api/torrents/action").json(&req_body);
match client {
- Ok(req) => {
- match req.send().await {
- Ok(resp) => {
- if !resp.ok() {
- logging::error!("Failed to execute action: {} {}", resp.status(), resp.status_text());
- } else {
- logging::log!("Action {} executed successfully", action);
- }
+ Ok(req) => match req.send().await {
+ Ok(resp) => {
+ if !resp.ok() {
+ logging::error!(
+ "Failed to execute action: {} {}",
+ resp.status(),
+ resp.status_text()
+ );
+ } else {
+ logging::log!("Action {} executed successfully", action);
}
- Err(e) => logging::error!("Network error executing action: {}", e),
}
- }
+ Err(e) => logging::error!("Network error executing action: {}", e),
+ },
Err(e) => logging::error!("Failed to serialize request: {}", e),
}
});
@@ -202,17 +237,17 @@ pub fn TorrentTable() -> impl IntoView {
shared::TorrentStatus::Error => "text-error",
_ => "text-base-content/50"
};
- let t_hash = t.hash.clone();
+ let t_hash = t.hash.clone();
let t_hash_click = t.hash.clone();
-
+
let is_selected_fn = move || {
selected_hash.get() == Some(t_hash.clone())
};
view! {
- impl IntoView {
};
let t_hash = t.hash.clone();
// We don't need t_hash_click separately if we use t_hash, but existing pattern uses clones
- let t_hash_click = t.hash.clone();
-
- let is_selected_fn = move || {
- selected_hash.get() == Some(t_hash.clone())
- };
+ let t_hash_click = t.hash.clone();
+
+
// Long press logic
let (timer_id, set_timer_id) = create_signal(Option::::None);
let t_hash_long = t.hash.clone();
-
+
let clear_timer = move || {
if let Some(id) = timer_id.get_untracked() {
window().clear_timeout_with_handle(id);
@@ -282,38 +315,38 @@ pub fn TorrentTable() -> impl IntoView {
let handle_touchstart = {
let t_hash = t_hash_long.clone();
move |e: web_sys::TouchEvent| {
- // Don't prevent default immediately, or we can't scroll.
+ // Don't prevent default immediately, or we can't scroll.
// But for long press, we might need to if we want to stop iOS menu.
// -webkit-touch-callout: none (in CSS) handles the iOS menu suppression usually.
-
+
clear_timer();
-
+
if let Some(touch) = e.touches().get(0) {
let x = touch.client_x();
let y = touch.client_y();
let hash = t_hash.clone();
-
+
let closure = Closure::wrap(Box::new(move || {
set_menu_position.set((x, y));
set_selected_hash.set(Some(hash.clone()));
set_menu_visible.set(true);
-
+
// Haptic feedback if available
let navigator = window().navigator();
let _ = navigator.vibrate_with_duration(50);
}) as Box);
-
+
let id = window()
.set_timeout_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
600 // 600ms long press
)
.unwrap_or(0);
-
+
closure.forget(); // Leak memory? effectively yes, but for a simplified timeout it's "okay" in this context or we need to store the closure key.
- // In a real app we might want to store the closure to drop it, but `set_timeout` takes a function pointer effectively.
+ // In a real app we might want to store the closure to drop it, but `set_timeout` takes a function pointer effectively.
// Actually, `closure.forget()` is standard for one-off callbacks that the JS side consumes.
-
+
set_timer_id.set(Some(id));
}
}
@@ -329,7 +362,7 @@ pub fn TorrentTable() -> impl IntoView {
};
view! {
- impl IntoView {
{status_str}
-
+
{format_bytes(t.size)}
@@ -383,7 +416,7 @@ pub fn TorrentTable() -> impl IntoView {
}
}).collect::>()}
-
+