From 7690ce7345449257ea266feeea77c1aff85962e5 Mon Sep 17 00:00:00 2001 From: micielski <73398428+micielski@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:07:29 +0200 Subject: [PATCH] feat: sorting (#100) --- rm-config/defaults/config.toml | 8 +- rm-config/src/keymap/actions/general.rs | 6 + rm-config/src/main_config/icons.rs | 14 ++ rm-config/src/main_config/torrents_tab.rs | 10 ++ rm-main/src/tui/tabs/torrents/mod.rs | 72 +++++++- .../src/tui/tabs/torrents/popups/details.rs | 10 +- .../tui/tabs/torrents/rustmission_torrent.rs | 149 ++++++++++------- .../src/tui/tabs/torrents/table_manager.rs | 157 ++++++++++++++++-- rm-main/src/tui/tabs/torrents/task_manager.rs | 11 ++ .../src/tui/tabs/torrents/tasks/default.rs | 4 +- rm-main/src/tui/tabs/torrents/tasks/mod.rs | 2 + rm-main/src/tui/tabs/torrents/tasks/sort.rs | 46 +++++ rm-shared/src/action.rs | 2 + 13 files changed, 414 insertions(+), 77 deletions(-) create mode 100644 rm-main/src/tui/tabs/torrents/tasks/sort.rs diff --git a/rm-config/defaults/config.toml b/rm-config/defaults/config.toml index 994849e..ce3d39b 100644 --- a/rm-config/defaults/config.toml +++ b/rm-config/defaults/config.toml @@ -34,6 +34,11 @@ free_space_refresh = 10 # SmallStatus, Category, CategoryIcon headers = ["Name", "SizeWhenDone", "Progress", "Eta", "DownloadRate", "UploadRate"] +# Default header to sort by: +default_sort = "AddedDate" +# Reverse the default sort? +default_sort_reverse = true + # Whether to insert category icon into name as declared in categories.toml. # An alternative to inserting category's icon into torrent's name is adding a # CategoryIcon header into your headers. @@ -68,4 +73,5 @@ category_icon_insert_into_name = true # provider_disabled = "⛔" # "󰪎" # provider_category_general = "[G]" # "" # provider_category_anime = "[A]" # "󰎁" - +# sort_ascending = "↓" # "󰒼" +# sort_descending = "↑" # "󰒽"" diff --git a/rm-config/src/keymap/actions/general.rs b/rm-config/src/keymap/actions/general.rs index 35cb54a..c99cd84 100644 --- a/rm-config/src/keymap/actions/general.rs +++ b/rm-config/src/keymap/actions/general.rs @@ -23,6 +23,8 @@ pub enum GeneralAction { GoToBeginning, GoToEnd, XdgOpen, + MoveToColumnLeft, + MoveToColumnRight, } impl UserAction for GeneralAction { @@ -46,6 +48,8 @@ impl UserAction for GeneralAction { GeneralAction::GoToBeginning => "scroll to the beginning", GeneralAction::GoToEnd => "scroll to the end", GeneralAction::XdgOpen => "open with xdg-open", + GeneralAction::MoveToColumnRight => "move to right column", + GeneralAction::MoveToColumnLeft => "move to left column", } } } @@ -71,6 +75,8 @@ impl From for Action { GeneralAction::GoToBeginning => Action::Home, GeneralAction::GoToEnd => Action::End, GeneralAction::XdgOpen => Action::XdgOpen, + GeneralAction::MoveToColumnLeft => Action::MoveToColumnLeft, + GeneralAction::MoveToColumnRight => Action::MoveToColumnRight, } } } diff --git a/rm-config/src/main_config/icons.rs b/rm-config/src/main_config/icons.rs index 97d1abf..39716ce 100644 --- a/rm-config/src/main_config/icons.rs +++ b/rm-config/src/main_config/icons.rs @@ -46,6 +46,10 @@ pub struct Icons { pub provider_category_general: String, #[serde(default = "default_provider_category_anime")] pub provider_category_anime: String, + #[serde(default = "default_sort_ascending")] + pub sort_ascending: String, + #[serde(default = "default_sort_descending")] + pub sort_descending: String, } impl Default for Icons { @@ -73,6 +77,8 @@ impl Default for Icons { provider_disabled: default_provider_disabled(), provider_category_general: default_provider_category_general(), provider_category_anime: default_provider_category_anime(), + sort_ascending: default_sort_ascending(), + sort_descending: default_sort_descending(), } } } @@ -163,3 +169,11 @@ fn default_provider_category_general() -> String { fn default_provider_category_anime() -> String { "󰎁".into() } + +fn default_sort_ascending() -> String { + "󰒼".into() +} + +fn default_sort_descending() -> String { + "󰒽".into() +} diff --git a/rm-config/src/main_config/torrents_tab.rs b/rm-config/src/main_config/torrents_tab.rs index d82a9fe..75ae87b 100644 --- a/rm-config/src/main_config/torrents_tab.rs +++ b/rm-config/src/main_config/torrents_tab.rs @@ -5,6 +5,10 @@ use serde::Deserialize; pub struct TorrentsTab { #[serde(default = "default_headers")] pub headers: Vec
, + #[serde(default = "default_sort")] + pub default_sort: Header, + #[serde(default = "default_true")] + pub default_sort_reverse: bool, #[serde(default = "default_true")] pub category_icon_insert_into_name: bool, } @@ -13,6 +17,10 @@ fn default_true() -> bool { true } +fn default_sort() -> Header { + Header::AddedDate +} + fn default_headers() -> Vec
{ vec![ Header::Name, @@ -28,6 +36,8 @@ impl Default for TorrentsTab { fn default() -> Self { Self { headers: default_headers(), + default_sort: default_sort(), + default_sort_reverse: default_true(), category_icon_insert_into_name: default_true(), } } diff --git a/rm-main/src/tui/tabs/torrents/mod.rs b/rm-main/src/tui/tabs/torrents/mod.rs index bca22a2..28f6659 100644 --- a/rm-main/src/tui/tabs/torrents/mod.rs +++ b/rm-main/src/tui/tabs/torrents/mod.rs @@ -12,7 +12,7 @@ use crate::tui::components::{Component, ComponentAction}; use popups::details::DetailsPopup; use popups::stats::StatisticsPopup; use ratatui::prelude::*; -use ratatui::widgets::{Row, Table}; +use ratatui::widgets::{Cell, Row, Table}; use rm_config::CONFIG; use rm_shared::status_task::StatusTask; use rustmission_torrent::RustmissionTorrent; @@ -76,6 +76,35 @@ impl Component for TorrentsTab { return ComponentAction::Nothing; } + if self.table_manager.sorting_is_being_selected { + match action { + A::Close => { + self.table_manager.leave_sorting(); + self.task_manager.default(); + self.ctx.send_action(Action::Render); + } + A::MoveToColumnLeft => { + self.table_manager.move_to_column_left(); + self.ctx.send_action(Action::Render); + } + A::MoveToColumnRight => { + self.table_manager.move_to_column_right(); + self.ctx.send_action(Action::Render); + } + A::Down | A::Up => { + self.table_manager.reverse_sort(); + self.ctx.send_action(Action::Render); + } + A::Confirm => { + self.table_manager.apply_sort(); + self.task_manager.default(); + self.ctx.send_action(Action::Render); + } + _ => (), + } + return ComponentAction::Nothing; + } + if action.is_quit() { self.ctx.send_action(Action::HardQuit); } @@ -122,6 +151,11 @@ impl Component for TorrentsTab { } } A::XdgOpen => self.xdg_open_current_torrent(), + A::MoveToColumnLeft | A::MoveToColumnRight => { + self.table_manager.enter_sorting_selection(); + self.task_manager.sort(); + self.ctx.send_action(Action::Render); + } other => { self.task_manager.handle_actions(other); } @@ -196,11 +230,45 @@ impl TorrentsTab { .fg(CONFIG.general.accent_color); let rows = self.table_manager.rows(); + + let mut text_headers = self + .table_manager + .headers() + .iter() + .map(|h| h.header_name()) + .collect::>(); + + let sorted_header_name; + if let Some(sort_header) = self.table_manager.sort_header { + let icon = if self.table_manager.sort_reverse { + &CONFIG.icons.sort_descending + } else { + &CONFIG.icons.sort_ascending + }; + + sorted_header_name = format!("{icon} {}", text_headers[sort_header]); + text_headers[sort_header] = sorted_header_name.as_str(); + } + + let mut headers = text_headers + .iter() + .cloned() + .map(Cell::from) + .collect::>(); + + if let Some(sort_header) = self.table_manager.sort_header { + if self.table_manager.sorting_is_being_selected { + headers[sort_header] = headers[sort_header] + .clone() + .style(Style::default().fg(CONFIG.general.accent_color)); + } + } + let table_widget = { let table = Table::new(rows, &self.table_manager.widths).highlight_style(highlight_table_style); if !CONFIG.general.headers_hide { - table.header(Row::new(self.table_manager.headers().iter().cloned())) + table.header(Row::new(headers)) } else { table } diff --git a/rm-main/src/tui/tabs/torrents/popups/details.rs b/rm-main/src/tui/tabs/torrents/popups/details.rs index d63285b..df65e3f 100644 --- a/rm-main/src/tui/tabs/torrents/popups/details.rs +++ b/rm-main/src/tui/tabs/torrents/popups/details.rs @@ -10,7 +10,7 @@ use crate::tui::{ app, components::{keybinding_style, popup_close_button_highlight, Component, ComponentAction}, main_window::centered_rect, - tabs::torrents::rustmission_torrent::RustmissionTorrent, + tabs::torrents::rustmission_torrent::{CategoryType, RustmissionTorrent}, }; pub struct DetailsPopup { @@ -140,12 +140,12 @@ impl Component for DetailsPopup { lines.push(Line::from(format!("Error: {error}")).red()); } - if let Some(category) = &self.torrent.categories.first() { + if let Some(category) = &self.torrent.category { let mut category_line = Line::from("Category: "); - let mut category_span = Span::raw(category.as_str()); + let mut category_span = Span::raw(category.name()); - if let Some(config_category) = CONFIG.categories.map.get(*category) { - category_span = category_span.set_style(Style::default().fg(config_category.color)) + if let CategoryType::Config(category) = category { + category_span = category_span.set_style(Style::default().fg(category.color)) } category_line.push_span(category_span); diff --git a/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs b/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs index 60b2ab4..9656213 100644 --- a/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs +++ b/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs @@ -4,7 +4,7 @@ use ratatui::{ text::{Line, Span}, widgets::{Cell, Row}, }; -use rm_config::CONFIG; +use rm_config::{categories::Category, CONFIG}; use rm_shared::{ header::Header, utils::{bytes_to_human_format, seconds_to_human_format}, @@ -14,10 +14,10 @@ use transmission_rpc::types::{ErrorType, Id, Torrent, TorrentStatus}; #[derive(Clone)] pub struct RustmissionTorrent { pub torrent_name: String, - pub size_when_done: String, - pub progress: String, - pub eta_secs: String, - pub download_speed: String, + pub size_when_done: i64, + pub progress: f32, + pub eta_secs: i64, + pub download_speed: i64, pub upload_speed: String, pub uploaded_ever: String, pub upload_ratio: String, @@ -28,10 +28,25 @@ pub struct RustmissionTorrent { pub activity_date: NaiveDateTime, pub added_date: NaiveDateTime, pub peers_connected: i64, - pub categories: Vec, + pub category: Option, pub error: Option, } +#[derive(Clone)] +pub enum CategoryType { + Plain(String), + Config(Category), +} + +impl CategoryType { + pub fn name(&self) -> &str { + match self { + CategoryType::Plain(name) => name, + CategoryType::Config(config) => &config.name, + } + } +} + impl RustmissionTorrent { pub fn to_row(&self, headers: &[Header]) -> ratatui::widgets::Row { headers @@ -42,6 +57,32 @@ impl RustmissionTorrent { .height(if self.error.is_some() { 2 } else { 1 }) } + pub fn progress(&self) -> String { + match self.progress { + done if done == 1f32 => String::default(), + percent => format!("{:.2}%", percent * 100f32), + } + } + + pub fn eta_secs(&self) -> String { + match self.eta_secs { + -2 => "∞".to_string(), + -1 => String::default(), + eta_secs => seconds_to_human_format(eta_secs), + } + } + + pub fn download_speed(&self) -> String { + match self.download_speed { + 0 => String::default(), + down => bytes_to_human_format(down), + } + } + + pub fn size_when_done(&self) -> String { + bytes_to_human_format(self.size_when_done) + } + pub fn to_row_with_higlighted_indices( &self, highlighted_indices: &Vec, @@ -133,11 +174,7 @@ impl RustmissionTorrent { } fn category_icon_span(&self) -> Span { - if let Some(category) = self - .categories - .first() - .and_then(|category| CONFIG.categories.map.get(category)) - { + if let Some(CategoryType::Config(category)) = &self.category { Span::styled( format!("{} ", category.icon), Style::default().fg(category.color), @@ -149,17 +186,15 @@ impl RustmissionTorrent { fn torrent_name_with_category_icon(&self) -> Line<'_> { let mut line = Line::default(); - if let Some(category) = self - .categories - .first() - .and_then(|category| CONFIG.categories.map.get(category)) - { + + if let Some(CategoryType::Config(category)) = &self.category { line.push_span(Span::styled( category.icon.as_str(), Style::default().fg(category.color), )); line.push_span(Span::raw(" ")); } + line.push_span(self.torrent_name.as_str()); line } @@ -168,17 +203,23 @@ impl RustmissionTorrent { match header { Header::Name => { if let Some(error) = &self.error { - Cell::from(format!("{}\n{error}", self.torrent_name)) + Cell::from(vec![ + Line::from(self.torrent_name.as_str()), + Line::from(Span::styled( + format!("\n{error}"), + Style::default().red().dim().italic(), + )), + ]) } else if CONFIG.torrents_tab.category_icon_insert_into_name { Cell::from(self.torrent_name_with_category_icon()) } else { Cell::from(self.torrent_name.as_str()) } } - Header::SizeWhenDone => Cell::from(self.size_when_done.as_str()), - Header::Progress => Cell::from(self.progress.as_str()), - Header::Eta => Cell::from(self.eta_secs.as_str()), - Header::DownloadRate => Cell::from(download_speed_format(&self.download_speed)), + Header::SizeWhenDone => Cell::from(self.size_when_done()), + Header::Progress => Cell::from(self.progress()), + Header::Eta => Cell::from(self.eta_secs()), + Header::DownloadRate => Cell::from(download_speed_format(&self.download_speed())), Header::UploadRate => Cell::from(upload_speed_format(&self.upload_speed)), Header::DownloadDir => Cell::from(self.download_dir.as_str()), Header::Padding => Cell::from(""), @@ -212,26 +253,25 @@ impl RustmissionTorrent { } } } - Header::Category => match self.categories.first() { - Some(category) => { - if let Some(config_category) = CONFIG.categories.map.get(category) { - Cell::from(category.as_str()).fg(config_category.color) - } else { - Cell::from(category.as_str()) + Header::Category => { + if let Some(category) = &self.category { + match category { + CategoryType::Plain(name) => Cell::from(name.as_str()), + CategoryType::Config(category) => { + Cell::from(category.name.as_str()).fg(category.color) + } } + } else { + Cell::default() } - None => Cell::default(), - }, - Header::CategoryIcon => match self.categories.first() { - Some(category) => { - if let Some(config_category) = CONFIG.categories.map.get(category) { - Cell::from(config_category.icon.as_str()).fg(config_category.color) - } else { - Cell::default() - } + } + Header::CategoryIcon => { + if let Some(CategoryType::Config(category)) = &self.category { + Cell::from(category.icon.as_str()).fg(category.color) + } else { + Cell::default() } - None => Cell::default(), - }, + } } } @@ -241,7 +281,7 @@ impl RustmissionTorrent { pub fn update_status(&mut self, new_status: TorrentStatus) { if self.error.is_some() { - self.style = Style::default().red().italic(); + self.style = Style::default().red(); } else if new_status == TorrentStatus::Stopped { self.style = Style::default().dark_gray().italic(); } else { @@ -258,23 +298,13 @@ impl From for RustmissionTorrent { let torrent_name = t.name.clone().expect("name requested"); - let size_when_done = bytes_to_human_format(t.size_when_done.expect("field requested")); + let size_when_done = t.size_when_done.expect("field requested"); - let progress = match t.percent_done.expect("field requested") { - done if done == 1f32 => String::default(), - percent => format!("{:.2}%", percent * 100f32), - }; + let progress = t.percent_done.expect("field requested"); - let eta_secs = match t.eta.expect("field requested") { - -2 => "∞".to_string(), - -1 => String::default(), - eta_secs => seconds_to_human_format(eta_secs), - }; + let eta_secs = t.eta.expect("field requested"); - let download_speed = match t.rate_download.expect("field requested") { - 0 => String::default(), - down => bytes_to_human_format(down), - }; + let download_speed = t.rate_download.expect("field requested"); let upload_speed = match t.rate_upload.expect("field requested") { 0 => String::default(), @@ -318,7 +348,7 @@ impl From for RustmissionTorrent { let style = { if error.is_some() { - Style::default().red().italic() + Style::default().red() } else { match status { TorrentStatus::Stopped => Style::default().dark_gray().italic(), @@ -327,7 +357,14 @@ impl From for RustmissionTorrent { } }; - let categories = t.labels.unwrap(); + let category = if let Some(category) = t.labels.unwrap().first() { + match CONFIG.categories.map.get(category) { + Some(category) => Some(CategoryType::Config(category.clone())), + None => Some(CategoryType::Plain(category.to_string())), + } + } else { + None + }; Self { torrent_name, @@ -345,7 +382,7 @@ impl From for RustmissionTorrent { activity_date, added_date, peers_connected, - categories, + category, error, } } diff --git a/rm-main/src/tui/tabs/torrents/table_manager.rs b/rm-main/src/tui/tabs/torrents/table_manager.rs index 49aef84..8ab735e 100644 --- a/rm-main/src/tui/tabs/torrents/table_manager.rs +++ b/rm-main/src/tui/tabs/torrents/table_manager.rs @@ -2,7 +2,7 @@ use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; use ratatui::{prelude::*, widgets::Row}; use rm_config::CONFIG; use rm_shared::header::Header; -use std::collections::HashMap; +use std::{cmp::Ordering, collections::HashMap}; use crate::tui::components::GenericTable; @@ -13,7 +13,9 @@ pub struct TableManager { pub widths: Vec, pub filter: Option, pub torrents_displaying_no: u16, - headers: Vec<&'static str>, + pub sort_header: Option, + pub sort_reverse: bool, + pub sorting_is_being_selected: bool, } pub struct Filter { @@ -26,17 +28,147 @@ impl TableManager { pub fn new() -> Self { let table = GenericTable::new(vec![]); let widths = Self::default_widths(&CONFIG.torrents_tab.headers); - let mut headers = vec![]; - for header in &CONFIG.torrents_tab.headers { - headers.push(header.header_name()); - } Self { table, widths, filter: None, torrents_displaying_no: 0, - headers, + sort_header: None, + sort_reverse: false, + sorting_is_being_selected: false, + } + } + + pub fn enter_sorting_selection(&mut self) { + self.sorting_is_being_selected = true; + if self.sort_header.is_none() { + self.sort_header = Some(0); + self.sort(); + } + } + + pub fn reverse_sort(&mut self) { + self.sort_reverse = !self.sort_reverse; + self.sort(); + } + + pub fn leave_sorting(&mut self) { + self.sorting_is_being_selected = false; + self.sort_header = None; + self.sort(); + } + + pub fn apply_sort(&mut self) { + self.sorting_is_being_selected = false; + } + + pub fn move_to_column_left(&mut self) { + if let Some(selected) = self.sort_header { + if selected == 0 { + self.sort_header = Some(self.headers().len() - 1); + self.sort(); + } else { + self.sort_header = Some(selected - 1); + self.sort(); + } + } else { + self.sort_header = Some(0); + self.sort(); + } + } + + pub fn move_to_column_right(&mut self) { + if let Some(selected) = self.sort_header { + let headers_count = self.headers().len(); + if selected < headers_count.saturating_sub(1) { + self.sort_header = Some(selected + 1); + self.sort(); + } else { + self.sort_header = Some(0); + self.sort(); + } + } + } + + pub fn sort(&mut self) { + let sort_by = self + .sort_header + .and_then(|idx| Some(CONFIG.torrents_tab.headers[idx])) + .unwrap_or(CONFIG.torrents_tab.default_sort); + + match sort_by { + Header::Id => todo!(), + Header::Name => self.table.items.sort_by(|x, y| { + x.torrent_name + .to_lowercase() + .cmp(&y.torrent_name.to_lowercase()) + }), + Header::SizeWhenDone => self + .table + .items + .sort_by(|x, y| x.size_when_done.cmp(&y.size_when_done)), + Header::Progress => self.table.items.sort_unstable_by(|x, y| { + x.progress + .partial_cmp(&y.progress) + .unwrap_or(Ordering::Equal) + }), + Header::Eta => self.table.items.sort_by(|x, y| x.eta_secs.cmp(&y.eta_secs)), + Header::DownloadRate => self + .table + .items + .sort_by(|x, y| x.download_speed.cmp(&y.download_speed)), + Header::UploadRate => self + .table + .items + .sort_by(|x, y| x.upload_speed.cmp(&y.upload_speed)), + Header::DownloadDir => self + .table + .items + .sort_by(|x, y| x.download_dir.cmp(&y.download_dir)), + Header::Padding => (), + Header::UploadRatio => self + .table + .items + .sort_by(|x, y| x.upload_ratio.cmp(&y.upload_ratio)), + Header::UploadedEver => self + .table + .items + .sort_by(|x, y| x.uploaded_ever.cmp(&y.uploaded_ever)), + Header::ActivityDate => self + .table + .items + .sort_by(|x, y| x.activity_date.cmp(&y.activity_date)), + Header::AddedDate => self + .table + .items + .sort_by(|x, y| x.added_date.cmp(&y.added_date)), + Header::PeersConnected => self + .table + .items + .sort_by(|x, y| x.peers_connected.cmp(&y.peers_connected)), + Header::SmallStatus => (), + Header::Category => self.table.items.sort_by(|x, y| { + x.category + .as_ref() + .and_then(|cat| { + Some( + cat.name().cmp( + y.category + .as_ref() + .and_then(|cat| Some(cat.name())) + .unwrap_or_default(), + ), + ) + }) + .unwrap_or(Ordering::Less) + }), + Header::CategoryIcon => (), + } + if self.sort_reverse { + self.table.items.reverse(); + } else if self.sort_header.is_none() && CONFIG.torrents_tab.default_sort_reverse { + self.table.items.reverse(); } } @@ -73,8 +205,8 @@ impl TableManager { } } - pub const fn headers(&self) -> &Vec<&'static str> { - &self.headers + pub fn headers(&self) -> &Vec
{ + &CONFIG.torrents_tab.headers } pub fn current_torrent(&mut self) -> Option<&mut RustmissionTorrent> { @@ -97,6 +229,7 @@ impl TableManager { self.table.set_items(rows); self.widths = self.header_widths(&self.table.items); self.update_rows_number(); + self.sort(); } pub fn set_filter(&mut self, filter: String) { @@ -164,7 +297,7 @@ impl TableManager { } for row in rows { - if !row.download_speed.is_empty() { + if !row.download_speed().is_empty() { map.entry(&Header::DownloadRate) .and_modify(|c| *c = Header::DownloadRate.default_constraint()); } @@ -172,12 +305,12 @@ impl TableManager { map.entry(&Header::UploadRate) .and_modify(|c| *c = Header::UploadRate.default_constraint()); } - if !row.progress.is_empty() { + if !row.progress().is_empty() { map.entry(&Header::Progress) .and_modify(|c| *c = Header::Progress.default_constraint()); } - if !row.eta_secs.is_empty() { + if !row.eta_secs().is_empty() { map.entry(&Header::Eta) .and_modify(|c| *c = Header::Eta.default_constraint()); } diff --git a/rm-main/src/tui/tabs/torrents/task_manager.rs b/rm-main/src/tui/tabs/torrents/task_manager.rs index dbeadb0..daf6554 100644 --- a/rm-main/src/tui/tabs/torrents/task_manager.rs +++ b/rm-main/src/tui/tabs/torrents/task_manager.rs @@ -39,6 +39,7 @@ pub enum CurrentTask { ChangeCategory(tasks::ChangeCategory), Default(tasks::Default), Status(tasks::Status), + Sort(tasks::Sort), } impl CurrentTask { @@ -84,6 +85,7 @@ impl Component for TaskManager { } } CurrentTask::Default(_) => (), + CurrentTask::Sort(_) => (), }; ComponentAction::Nothing } @@ -116,6 +118,7 @@ impl Component for TaskManager { CurrentTask::Default(default_bar) => default_bar.render(f, rect), CurrentTask::Status(status_bar) => status_bar.render(f, rect), CurrentTask::ChangeCategory(category_bar) => category_bar.render(f, rect), + CurrentTask::Sort(sort_bar) => sort_bar.render(f, rect), } } @@ -162,6 +165,14 @@ impl TaskManager { self.ctx.send_update_action(UpdateAction::SwitchToInputMode); } + pub fn default(&mut self) { + self.current_task = CurrentTask::Default(tasks::Default::new()); + } + + pub fn sort(&mut self) { + self.current_task = CurrentTask::Sort(tasks::Sort::new()); + } + fn success_task(&mut self, task: StatusTask) { self.current_task = CurrentTask::Status(tasks::Status::new( self.ctx.clone(), diff --git a/rm-main/src/tui/tabs/torrents/tasks/default.rs b/rm-main/src/tui/tabs/torrents/tasks/default.rs index f54e891..99598dd 100644 --- a/rm-main/src/tui/tabs/torrents/tasks/default.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/default.rs @@ -13,7 +13,7 @@ impl Default { } impl Component for Default { - fn render(&mut self, f: &mut ratatui::Frame<'_>, rect: Rect) { + fn render(&mut self, f: &mut Frame<'_>, rect: Rect) { let mut line = Line::default(); let mut line_is_empty = true; @@ -27,6 +27,8 @@ impl Component for Default { if let Some(keys) = CONFIG.keybindings.get_keys_for_action(Action::Confirm) { if !line_is_empty { line.push_span(Span::raw(" | ")); + } else { + line.push_span(Span::raw(format!("{} ", CONFIG.icons.help))); } line.push_span(Span::styled(keys, keybinding_style())); line.push_span(Span::raw(" - details")); diff --git a/rm-main/src/tui/tabs/torrents/tasks/mod.rs b/rm-main/src/tui/tabs/torrents/tasks/mod.rs index 5c27c0f..b7fec47 100644 --- a/rm-main/src/tui/tabs/torrents/tasks/mod.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/mod.rs @@ -4,6 +4,7 @@ mod default; mod delete_torrent; mod filter; mod move_torrent; +mod sort; mod status; pub use add_magnet::AddMagnet; @@ -12,4 +13,5 @@ pub use default::Default; pub use delete_torrent::Delete; pub use filter::Filter; pub use move_torrent::Move; +pub use sort::Sort; pub use status::{CurrentTaskState, Status}; diff --git a/rm-main/src/tui/tabs/torrents/tasks/sort.rs b/rm-main/src/tui/tabs/torrents/tasks/sort.rs new file mode 100644 index 0000000..68bc261 --- /dev/null +++ b/rm-main/src/tui/tabs/torrents/tasks/sort.rs @@ -0,0 +1,46 @@ +use rm_config::CONFIG; +use rm_shared::action::Action; + +use ratatui::prelude::*; + +use crate::tui::components::{keybinding_style, Component}; + +pub struct Sort {} + +impl Sort { + pub const fn new() -> Self { + Self {} + } +} + +impl Component for Sort { + fn render(&mut self, f: &mut Frame<'_>, rect: Rect) { + let mut line = Line::default(); + let mut line_is_empty = true; + + if let Some(keys) = CONFIG.keybindings.get_keys_for_action(Action::Close) { + line_is_empty = false; + line.push_span(Span::styled(keys, keybinding_style())); + line.push_span(Span::raw(" - reset & exit")); + } + + if let Some(keys) = CONFIG.keybindings.get_keys_for_action(Action::Confirm) { + if !line_is_empty { + line.push_span(Span::raw(" | ")); + } + line_is_empty = false; + line.push_span(Span::styled(keys, keybinding_style())); + line.push_span(Span::raw(" - apply")); + } + + if let Some(keys) = CONFIG.keybindings.get_keys_for_action(Action::Down) { + if !line_is_empty { + line.push_span(Span::raw(" | ")); + } + line.push_span(Span::styled(keys, keybinding_style())); + line.push_span(Span::raw(" - reverse")); + } + + f.render_widget(line, rect); + } +} diff --git a/rm-shared/src/action.rs b/rm-shared/src/action.rs index 411af35..338a589 100644 --- a/rm-shared/src/action.rs +++ b/rm-shared/src/action.rs @@ -31,6 +31,8 @@ pub enum Action { ChangeTab(u8), XdgOpen, Input(KeyEvent), + MoveToColumnLeft, + MoveToColumnRight, // Torrents Tab ShowStats, ShowFiles,