diff --git a/rm-main/src/cli.rs b/rm-main/src/cli.rs deleted file mode 100644 index 7f17a54..0000000 --- a/rm-main/src/cli.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::{fs::File, io::Read}; - -use anyhow::{bail, Result}; -use base64::Engine; -use clap::{Parser, Subcommand}; -use regex::Regex; -use transmission_rpc::types::TorrentAddArgs; - -use crate::transmission; - -#[derive(Parser)] -#[command(version, about)] -pub struct Args { - #[command(subcommand)] - pub command: Option, -} - -#[derive(Subcommand)] -pub enum Commands { - AddTorrent { torrent: String }, - FetchRss { url: String, filter: Option }, -} - -pub async fn handle_command(command: Commands) -> Result<()> { - match command { - Commands::AddTorrent { torrent } => add_torrent(torrent).await?, - Commands::FetchRss { url, filter } => fetch_rss(&url, filter.as_deref()).await?, - } - Ok(()) -} - -async fn add_torrent(torrent: String) -> Result<()> { - let mut transclient = transmission::utils::new_client(); - let args = { - if torrent.starts_with("magnet:") - || torrent.starts_with("http:") - || torrent.starts_with("https:") - { - TorrentAddArgs { - filename: Some(torrent), - ..Default::default() - } - } else if torrent.starts_with("www") { - TorrentAddArgs { - filename: Some(format!("https://{torrent}")), - ..Default::default() - } - } else { - let mut torrent_file = File::open(torrent)?; - let mut buf = vec![]; - torrent_file.read_to_end(&mut buf).unwrap(); - let metainfo = base64::engine::general_purpose::STANDARD.encode(buf); - TorrentAddArgs { - metainfo: Some(metainfo), - ..Default::default() - } - } - }; - - if let Err(e) = transclient.torrent_add(args).await { - eprintln!("error while adding a torrent: {e}"); - if e.to_string().contains("expected value at line") { - eprintln!("Check whether your arguments are valid."); - } - - std::process::exit(1); - }; - Ok(()) -} - -async fn fetch_rss(url: &str, filter: Option<&str>) -> Result<()> { - let mut transclient = transmission::utils::new_client(); - let content = reqwest::get(url).await?.bytes().await?; - let channel = rss::Channel::read_from(&content[..])?; - let re: Option = { - if let Some(filter_str) = filter { - let res = Regex::new(filter_str)?; - Some(res) - } else { - None - } - }; - let items = channel.items().iter().filter_map(|item| { - if let (Some(title), Some(url)) = (item.title(), item.link()) { - if let Some(re) = &re { - if re.is_match(title) { - return Some((title, url)); - } - } else { - return Some((title, url)); - } - } - None - }); - for (title, url) in items { - println!("downloading {title}"); - let args = TorrentAddArgs { - filename: Some(url.to_string()), - ..Default::default() - }; - if let Err(e) = transclient.torrent_add(args).await { - bail!("error while adding a torrent: {e}") - } - } - Ok(()) -} diff --git a/rm-main/src/cli/add_torrent.rs b/rm-main/src/cli/add_torrent.rs new file mode 100644 index 0000000..a6eb416 --- /dev/null +++ b/rm-main/src/cli/add_torrent.rs @@ -0,0 +1,46 @@ +use std::{fs::File, io::Read}; + +use anyhow::Result; +use base64::Engine; +use transmission_rpc::types::TorrentAddArgs; + +use crate::transmission; + +pub(super) async fn add_torrent(torrent: String) -> Result<()> { + let mut transclient = transmission::utils::new_client(); + let args = { + if torrent.starts_with("magnet:") + || torrent.starts_with("http:") + || torrent.starts_with("https:") + { + TorrentAddArgs { + filename: Some(torrent), + ..Default::default() + } + } else if torrent.starts_with("www") { + TorrentAddArgs { + filename: Some(format!("https://{torrent}")), + ..Default::default() + } + } else { + let mut torrent_file = File::open(torrent)?; + let mut buf = vec![]; + torrent_file.read_to_end(&mut buf).unwrap(); + let metainfo = base64::engine::general_purpose::STANDARD.encode(buf); + TorrentAddArgs { + metainfo: Some(metainfo), + ..Default::default() + } + } + }; + + if let Err(e) = transclient.torrent_add(args).await { + eprintln!("error while adding a torrent: {e}"); + if e.to_string().contains("expected value at line") { + eprintln!("Check whether your arguments are valid."); + } + + std::process::exit(1); + }; + Ok(()) +} diff --git a/rm-main/src/cli/fetch_rss.rs b/rm-main/src/cli/fetch_rss.rs new file mode 100644 index 0000000..4122035 --- /dev/null +++ b/rm-main/src/cli/fetch_rss.rs @@ -0,0 +1,42 @@ +use anyhow::{bail, Result}; +use regex::Regex; +use transmission_rpc::types::TorrentAddArgs; + +use crate::transmission; + +pub async fn fetch_rss(url: &str, filter: Option<&str>) -> Result<()> { + let mut transclient = transmission::utils::new_client(); + let content = reqwest::get(url).await?.bytes().await?; + let channel = rss::Channel::read_from(&content[..])?; + let re: Option = { + if let Some(filter_str) = filter { + let res = Regex::new(filter_str)?; + Some(res) + } else { + None + } + }; + let items = channel.items().iter().filter_map(|item| { + if let (Some(title), Some(url)) = (item.title(), item.link()) { + if let Some(re) = &re { + if re.is_match(title) { + return Some((title, url)); + } + } else { + return Some((title, url)); + } + } + None + }); + for (title, url) in items { + println!("downloading {title}"); + let args = TorrentAddArgs { + filename: Some(url.to_string()), + ..Default::default() + }; + if let Err(e) = transclient.torrent_add(args).await { + bail!("error while adding a torrent: {e}") + } + } + Ok(()) +} diff --git a/rm-main/src/cli/mod.rs b/rm-main/src/cli/mod.rs new file mode 100644 index 0000000..cdd3e5e --- /dev/null +++ b/rm-main/src/cli/mod.rs @@ -0,0 +1,29 @@ +mod add_torrent; +mod fetch_rss; + +use anyhow::Result; +use clap::{Parser, Subcommand}; + +use add_torrent::add_torrent; +use fetch_rss::fetch_rss; + +#[derive(Parser)] +#[command(version, about)] +pub struct Args { + #[command(subcommand)] + pub command: Option, +} + +#[derive(Subcommand)] +pub enum Commands { + AddTorrent { torrent: String }, + FetchRss { url: String, filter: Option }, +} + +pub async fn handle_command(command: Commands) -> Result<()> { + match command { + Commands::AddTorrent { torrent } => add_torrent(torrent).await?, + Commands::FetchRss { url, filter } => fetch_rss(&url, filter.as_deref()).await?, + } + Ok(()) +} diff --git a/rm-main/src/main.rs b/rm-main/src/main.rs index e4b60a7..843558b 100644 --- a/rm-main/src/main.rs +++ b/rm-main/src/main.rs @@ -1,13 +1,10 @@ -pub mod app; mod cli; pub mod transmission; -pub mod tui; -mod ui; - -use app::App; +mod tui; use anyhow::Result; use clap::Parser; +use tui::app::App; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { diff --git a/rm-main/src/transmission/fetchers.rs b/rm-main/src/transmission/fetchers.rs index 6840123..1d1ef06 100644 --- a/rm-main/src/transmission/fetchers.rs +++ b/rm-main/src/transmission/fetchers.rs @@ -4,9 +4,10 @@ use rm_config::CONFIG; use tokio::sync::oneshot; use transmission_rpc::types::TorrentGetField; -use crate::app; use rm_shared::action::UpdateAction; +use crate::tui::app; + use super::TorrentAction; pub async fn stats(ctx: app::Ctx) { diff --git a/rm-main/src/app.rs b/rm-main/src/tui/app.rs similarity index 96% rename from rm-main/src/app.rs rename to rm-main/src/tui/app.rs index d44d032..4d4b31a 100644 --- a/rm-main/src/app.rs +++ b/rm-main/src/tui/app.rs @@ -1,22 +1,20 @@ -use crossterm::event::Event; -use crossterm::event::KeyCode; -use crossterm::event::KeyModifiers; -use rm_config::CONFIG; -use rm_shared::action::Action; -use rm_shared::action::UpdateAction; use std::sync::Arc; -use crate::ui::components::tabs::CurrentTab; use crate::{ transmission::{self, TorrentAction}, - tui::Tui, - ui::{components::Component, MainWindow}, + tui::components::Component, }; +use rm_config::CONFIG; +use rm_shared::action::{Action, UpdateAction}; + use anyhow::{Error, Result}; +use crossterm::event::{Event, KeyCode, KeyModifiers}; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use transmission_rpc::{types::SessionGet, TransClient}; +use super::{components::CurrentTab, main_window::MainWindow, terminal::Tui}; + #[derive(Clone)] pub struct Ctx { pub session_info: Arc, diff --git a/rm-main/src/ui/tabs/torrents/input_manager.rs b/rm-main/src/tui/components/input_manager.rs similarity index 76% rename from rm-main/src/ui/tabs/torrents/input_manager.rs rename to rm-main/src/tui/components/input_manager.rs index 3e75f5b..0f1706f 100644 --- a/rm-main/src/ui/tabs/torrents/input_manager.rs +++ b/rm-main/src/tui/components/input_manager.rs @@ -1,11 +1,12 @@ +use crossterm::event::{Event, KeyEvent}; use ratatui::{ prelude::*, widgets::{Clear, Paragraph}, }; use rm_config::CONFIG; -use tui_input::{Input, InputRequest}; +use tui_input::{backend::crossterm::to_input_request, Input, InputResponse}; -use crate::ui::components::Component; +use crate::tui::components::Component; pub struct InputManager { input: Input, @@ -31,8 +32,14 @@ impl InputManager { self.input.to_string() } - pub fn handle(&mut self, req: InputRequest) { - self.input.handle(req); + pub fn handle_key(&mut self, key: KeyEvent) -> InputResponse { + let event = Event::Key(key); + + if let Some(req) = to_input_request(&event) { + self.input.handle(req) + } else { + None + } } } diff --git a/rm-main/src/ui/components/mod.rs b/rm-main/src/tui/components/mod.rs similarity index 79% rename from rm-main/src/ui/components/mod.rs rename to rm-main/src/tui/components/mod.rs index 6e60556..2e15a81 100644 --- a/rm-main/src/ui/components/mod.rs +++ b/rm-main/src/tui/components/mod.rs @@ -1,12 +1,16 @@ -pub mod table; -pub mod tabs; +mod input_manager; +mod table; +mod tabs; + +pub use input_manager::InputManager; +pub use table::GenericTable; +pub use tabs::{CurrentTab, TabComponent}; use ratatui::prelude::*; use ratatui::Frame; use rm_shared::action::Action; use rm_shared::action::UpdateAction; -pub use tabs::TabComponent; #[derive(Clone, Copy, PartialEq, Eq)] pub enum ComponentAction { diff --git a/rm-main/src/ui/components/table.rs b/rm-main/src/tui/components/table.rs similarity index 100% rename from rm-main/src/ui/components/table.rs rename to rm-main/src/tui/components/table.rs diff --git a/rm-main/src/ui/components/tabs.rs b/rm-main/src/tui/components/tabs.rs similarity index 99% rename from rm-main/src/ui/components/tabs.rs rename to rm-main/src/tui/components/tabs.rs index c92d8c8..d3986b4 100644 --- a/rm-main/src/ui/components/tabs.rs +++ b/rm-main/src/tui/components/tabs.rs @@ -1,7 +1,8 @@ -use crate::app; use rm_config::CONFIG; use rm_shared::action::Action; +use crate::tui::app; + use super::{Component, ComponentAction}; use ratatui::{layout::Flex, prelude::*, widgets::Tabs}; diff --git a/rm-main/src/ui/global_popups/error.rs b/rm-main/src/tui/global_popups/error.rs similarity index 97% rename from rm-main/src/ui/global_popups/error.rs rename to rm-main/src/tui/global_popups/error.rs index d992e64..fbd57e7 100644 --- a/rm-main/src/ui/global_popups/error.rs +++ b/rm-main/src/tui/global_popups/error.rs @@ -3,11 +3,12 @@ use ratatui::{ widgets::{Block, Clear, Paragraph, Wrap}, }; -use crate::ui::{ - centered_rect, +use rm_shared::action::Action; + +use crate::tui::{ components::{Component, ComponentAction}, + main_window::centered_rect, }; -use rm_shared::action::Action; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ErrorPopup { diff --git a/rm-main/src/ui/global_popups/help.rs b/rm-main/src/tui/global_popups/help.rs similarity index 95% rename from rm-main/src/ui/global_popups/help.rs rename to rm-main/src/tui/global_popups/help.rs index 32e8ef7..6bd7692 100644 --- a/rm-main/src/ui/global_popups/help.rs +++ b/rm-main/src/tui/global_popups/help.rs @@ -8,19 +8,18 @@ use ratatui::{ }, }; -use crate::{ - app, - ui::{ - centered_rect, - components::{Component, ComponentAction}, - }, -}; use rm_config::{ keymap::{actions::UserAction, Keybinding}, CONFIG, }; use rm_shared::action::Action; +use crate::tui::{ + app, + components::{Component, ComponentAction}, + main_window::centered_rect, +}; + macro_rules! add_line { ($lines:expr, $key:expr, $description:expr) => { $lines.push(Line::from(vec![ @@ -79,14 +78,13 @@ impl HelpPopup { .push(keybinding.keycode_string()); } - for (_, keycodes) in &keys { - let delimiter_len; + for keycodes in keys.values() { let mut keycodes_total_len = 0; - if keycodes.len() >= 2 { - delimiter_len = (keycodes.len() - 1) * 3; + let delimiter_len = if keycodes.len() >= 2 { + (keycodes.len() - 1) * 3 } else { - delimiter_len = 0; - } + 0 + }; for keycode in keycodes { keycodes_total_len += keycode.chars().count(); diff --git a/rm-main/src/ui/global_popups/mod.rs b/rm-main/src/tui/global_popups/mod.rs similarity index 98% rename from rm-main/src/ui/global_popups/mod.rs rename to rm-main/src/tui/global_popups/mod.rs index 1c33b31..c9815ab 100644 --- a/rm-main/src/ui/global_popups/mod.rs +++ b/rm-main/src/tui/global_popups/mod.rs @@ -6,9 +6,10 @@ use ratatui::prelude::*; pub use error::ErrorPopup; pub use help::HelpPopup; -use crate::app; use rm_shared::action::Action; +use crate::tui::app; + use super::components::{Component, ComponentAction}; pub(super) struct GlobalPopupManager { diff --git a/rm-main/src/ui/mod.rs b/rm-main/src/tui/main_window.rs similarity index 77% rename from rm-main/src/ui/mod.rs rename to rm-main/src/tui/main_window.rs index 0b9451c..d3cbbf6 100644 --- a/rm-main/src/ui/mod.rs +++ b/rm-main/src/tui/main_window.rs @@ -1,22 +1,14 @@ -pub mod components; -pub mod global_popups; -pub mod tabs; - -use crate::ui::{global_popups::ErrorPopup, tabs::torrents::TorrentsTab}; - -use components::ComponentAction; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use ratatui::prelude::*; -use tui_input::InputRequest; use rm_shared::action::{Action, UpdateAction}; -use crate::app::{self}; +use crate::tui::components::CurrentTab; -use self::{ - components::{tabs::CurrentTab, Component, TabComponent}, - global_popups::GlobalPopupManager, - tabs::search::SearchTab, +use super::{ + app, + components::{Component, ComponentAction, TabComponent}, + global_popups::{ErrorPopup, GlobalPopupManager}, + tabs::{search::SearchTab, torrents::TorrentsTab}, }; pub struct MainWindow { @@ -99,19 +91,7 @@ impl Component for MainWindow { } } -const fn to_input_request(key_event: KeyEvent) -> Option { - use InputRequest as R; - - match (key_event.code, key_event.modifiers) { - (KeyCode::Backspace, KeyModifiers::ALT) => Some(R::DeletePrevWord), - (KeyCode::Backspace, _) => Some(R::DeletePrevChar), - (KeyCode::Delete, _) => Some(R::DeleteNextChar), - (KeyCode::Char(char), _) => Some(R::InsertChar(char)), - _ => None, - } -} - -fn centered_rect(r: Rect, percent_x: u16, percent_y: u16) -> Rect { +pub fn centered_rect(r: Rect, percent_x: u16, percent_y: u16) -> Rect { let popup_layout = Layout::vertical([ Constraint::Percentage((100 - percent_y) / 2), Constraint::Percentage(percent_y), diff --git a/rm-main/src/tui/mod.rs b/rm-main/src/tui/mod.rs new file mode 100644 index 0000000..f10aad6 --- /dev/null +++ b/rm-main/src/tui/mod.rs @@ -0,0 +1,6 @@ +pub mod app; +mod components; +mod global_popups; +pub mod main_window; +mod tabs; +pub mod terminal; diff --git a/rm-main/src/ui/tabs/mod.rs b/rm-main/src/tui/tabs/mod.rs similarity index 100% rename from rm-main/src/ui/tabs/mod.rs rename to rm-main/src/tui/tabs/mod.rs diff --git a/rm-main/src/ui/tabs/search/bottom_bar.rs b/rm-main/src/tui/tabs/search/bottom_bar.rs similarity index 99% rename from rm-main/src/ui/tabs/search/bottom_bar.rs rename to rm-main/src/tui/tabs/search/bottom_bar.rs index 2b74954..f93ca82 100644 --- a/rm-main/src/ui/tabs/search/bottom_bar.rs +++ b/rm-main/src/tui/tabs/search/bottom_bar.rs @@ -9,7 +9,7 @@ use rm_config::CONFIG; use rm_shared::action::{Action, UpdateAction}; use throbber_widgets_tui::ThrobberState; -use crate::{app, ui::components::Component}; +use crate::tui::{app, components::Component}; use super::{ConfiguredProvider, ProviderState}; diff --git a/rm-main/src/ui/tabs/search/mod.rs b/rm-main/src/tui/tabs/search/mod.rs similarity index 97% rename from rm-main/src/ui/tabs/search/mod.rs rename to rm-main/src/tui/tabs/search/mod.rs index 4f0e43f..a94447b 100644 --- a/rm-main/src/ui/tabs/search/mod.rs +++ b/rm-main/src/tui/tabs/search/mod.rs @@ -4,7 +4,7 @@ mod popups; use std::{borrow::Cow, sync::Arc}; use bottom_bar::BottomBar; -use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::event::{Event, KeyCode, KeyEvent}; use futures::{stream::FuturesUnordered, StreamExt}; use magnetease::{Magnet, MagneteaseErrorKind, WhichProvider}; use popups::{CurrentPopup, PopupManager}; @@ -16,14 +16,13 @@ use ratatui::{ use reqwest::Client; use rm_config::CONFIG; use tokio::sync::mpsc::{self, UnboundedSender}; -use tui_input::Input; +use tui_input::{backend::crossterm::to_input_request, Input}; use crate::{ - app, transmission::TorrentAction, - ui::{ - components::{table::GenericTable, Component, ComponentAction}, - to_input_request, + tui::{ + app, + components::{Component, ComponentAction, GenericTable}, }, }; use rm_shared::{ @@ -57,7 +56,7 @@ impl SearchTab { let mut configured_providers = vec![]; for provider in WhichProvider::all() { - configured_providers.push(ConfiguredProvider::new(provider.clone(), false)); + configured_providers.push(ConfiguredProvider::new(provider, false)); } for configured_provider in &mut configured_providers { @@ -154,7 +153,8 @@ impl SearchTab { .send_update_action(UpdateAction::SwitchToNormalMode); } _ => { - if let Some(req) = to_input_request(input) { + let event = Event::Key(input); + if let Some(req) = to_input_request(&event) { self.input.handle(req); self.ctx.send_action(A::Render); } diff --git a/rm-main/src/ui/tabs/search/popups/mod.rs b/rm-main/src/tui/tabs/search/popups/mod.rs similarity index 94% rename from rm-main/src/ui/tabs/search/popups/mod.rs rename to rm-main/src/tui/tabs/search/popups/mod.rs index 443ff05..c49f544 100644 --- a/rm-main/src/ui/tabs/search/popups/mod.rs +++ b/rm-main/src/tui/tabs/search/popups/mod.rs @@ -5,10 +5,9 @@ use ratatui::prelude::*; use ratatui::Frame; use rm_shared::action::Action; -use crate::{ - app, - ui::components::{Component, ComponentAction}, -}; +use crate::tui::app; +use crate::tui::components::Component; +use crate::tui::components::ComponentAction; use super::ConfiguredProvider; diff --git a/rm-main/src/ui/tabs/search/popups/providers.rs b/rm-main/src/tui/tabs/search/popups/providers.rs similarity index 98% rename from rm-main/src/ui/tabs/search/popups/providers.rs rename to rm-main/src/tui/tabs/search/popups/providers.rs index 3602c64..781c454 100644 --- a/rm-main/src/ui/tabs/search/popups/providers.rs +++ b/rm-main/src/tui/tabs/search/popups/providers.rs @@ -13,9 +13,9 @@ use ratatui::{ use rm_config::CONFIG; use rm_shared::action::Action; -use crate::ui::{ - centered_rect, +use crate::tui::{ components::{Component, ComponentAction}, + main_window::centered_rect, tabs::search::{ConfiguredProvider, ProviderState}, }; diff --git a/rm-main/src/ui/tabs/torrents/bottom_stats.rs b/rm-main/src/tui/tabs/torrents/bottom_stats.rs similarity index 98% rename from rm-main/src/ui/tabs/torrents/bottom_stats.rs rename to rm-main/src/tui/tabs/torrents/bottom_stats.rs index 6597f64..2d9fb76 100644 --- a/rm-main/src/ui/tabs/torrents/bottom_stats.rs +++ b/rm-main/src/tui/tabs/torrents/bottom_stats.rs @@ -8,7 +8,7 @@ use ratatui::{ use rm_shared::utils::bytes_to_human_format; use transmission_rpc::types::{FreeSpace, SessionStats}; -use crate::ui::components::Component; +use crate::tui::components::Component; use super::table_manager::TableManager; diff --git a/rm-main/src/ui/tabs/torrents/mod.rs b/rm-main/src/tui/tabs/torrents/mod.rs similarity index 98% rename from rm-main/src/ui/tabs/torrents/mod.rs rename to rm-main/src/tui/tabs/torrents/mod.rs index 60ee6c1..d64e606 100644 --- a/rm-main/src/ui/tabs/torrents/mod.rs +++ b/rm-main/src/tui/tabs/torrents/mod.rs @@ -1,5 +1,4 @@ mod bottom_stats; -mod input_manager; pub mod popups; pub mod rustmission_torrent; pub mod table_manager; @@ -7,8 +6,10 @@ pub mod task_manager; pub mod tasks; use crate::transmission::TorrentAction; -use crate::ui::tabs::torrents::popups::stats::StatisticsPopup; +use crate::tui::app; +use crate::tui::components::{Component, ComponentAction}; +use popups::stats::StatisticsPopup; use ratatui::prelude::*; use ratatui::widgets::{Row, Table}; use rm_config::CONFIG; @@ -16,8 +17,7 @@ use rm_shared::status_task::StatusTask; use rustmission_torrent::RustmissionTorrent; use transmission_rpc::types::TorrentStatus; -use crate::ui::components::{Component, ComponentAction}; -use crate::{app, transmission}; +use crate::transmission; use rm_shared::action::{Action, ErrorMessage, UpdateAction}; use self::bottom_stats::BottomStats; diff --git a/rm-main/src/ui/tabs/torrents/popups/files.rs b/rm-main/src/tui/tabs/torrents/popups/files.rs similarity index 99% rename from rm-main/src/ui/tabs/torrents/popups/files.rs rename to rm-main/src/tui/tabs/torrents/popups/files.rs index 520d429..7c801c2 100644 --- a/rm-main/src/ui/tabs/torrents/popups/files.rs +++ b/rm-main/src/tui/tabs/torrents/popups/files.rs @@ -14,11 +14,11 @@ use transmission_rpc::types::{Id, Torrent, TorrentSetArgs}; use tui_tree_widget::{Tree, TreeItem, TreeState}; use crate::{ - app, transmission::TorrentAction, - ui::{ - centered_rect, + tui::{ + app, components::{Component, ComponentAction}, + main_window::centered_rect, }, }; use rm_shared::{ diff --git a/rm-main/src/ui/tabs/torrents/popups/mod.rs b/rm-main/src/tui/tabs/torrents/popups/mod.rs similarity index 97% rename from rm-main/src/ui/tabs/torrents/popups/mod.rs rename to rm-main/src/tui/tabs/torrents/popups/mod.rs index cda27f8..e3874d2 100644 --- a/rm-main/src/ui/tabs/torrents/popups/mod.rs +++ b/rm-main/src/tui/tabs/torrents/popups/mod.rs @@ -1,8 +1,9 @@ -use self::{files::FilesPopup, stats::StatisticsPopup}; -use crate::{ +use crate::tui::{ app, - ui::components::{Component, ComponentAction}, + components::{Component, ComponentAction}, }; + +use self::{files::FilesPopup, stats::StatisticsPopup}; use rm_shared::action::{Action, UpdateAction}; use ratatui::prelude::*; diff --git a/rm-main/src/ui/tabs/torrents/popups/stats.rs b/rm-main/src/tui/tabs/torrents/popups/stats.rs similarity index 97% rename from rm-main/src/ui/tabs/torrents/popups/stats.rs rename to rm-main/src/tui/tabs/torrents/popups/stats.rs index f301c74..9f03a47 100644 --- a/rm-main/src/ui/tabs/torrents/popups/stats.rs +++ b/rm-main/src/tui/tabs/torrents/popups/stats.rs @@ -11,11 +11,12 @@ use ratatui::{ use rm_config::CONFIG; use transmission_rpc::types::SessionStats; -use crate::ui::{ - centered_rect, +use rm_shared::{action::Action, utils::bytes_to_human_format}; + +use crate::tui::{ components::{Component, ComponentAction}, + main_window::centered_rect, }; -use rm_shared::{action::Action, utils::bytes_to_human_format}; pub struct StatisticsPopup { stats: Arc, diff --git a/rm-main/src/ui/tabs/torrents/rustmission_torrent.rs b/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs similarity index 100% rename from rm-main/src/ui/tabs/torrents/rustmission_torrent.rs rename to rm-main/src/tui/tabs/torrents/rustmission_torrent.rs diff --git a/rm-main/src/ui/tabs/torrents/table_manager.rs b/rm-main/src/tui/tabs/torrents/table_manager.rs similarity index 99% rename from rm-main/src/ui/tabs/torrents/table_manager.rs rename to rm-main/src/tui/tabs/torrents/table_manager.rs index 72d6a73..74e5b13 100644 --- a/rm-main/src/ui/tabs/torrents/table_manager.rs +++ b/rm-main/src/tui/tabs/torrents/table_manager.rs @@ -4,7 +4,7 @@ use rm_config::CONFIG; use rm_shared::header::Header; use std::collections::HashMap; -use crate::ui::components::table::GenericTable; +use crate::tui::components::GenericTable; use super::rustmission_torrent::RustmissionTorrent; diff --git a/rm-main/src/ui/tabs/torrents/task_manager.rs b/rm-main/src/tui/tabs/torrents/task_manager.rs similarity index 98% rename from rm-main/src/ui/tabs/torrents/task_manager.rs rename to rm-main/src/tui/tabs/torrents/task_manager.rs index 4e4d24f..bc6a7e4 100644 --- a/rm-main/src/ui/tabs/torrents/task_manager.rs +++ b/rm-main/src/tui/tabs/torrents/task_manager.rs @@ -2,15 +2,16 @@ use ratatui::prelude::*; use throbber_widgets_tui::ThrobberState; use tokio::time::Instant; -use crate::{ - app, - ui::components::{Component, ComponentAction}, -}; use rm_shared::{ action::{Action, UpdateAction}, status_task::StatusTask, }; +use crate::tui::{ + app, + components::{Component, ComponentAction}, +}; + use super::{ rustmission_torrent::RustmissionTorrent, table_manager::Filter, diff --git a/rm-main/src/ui/tabs/torrents/tasks/add_magnet.rs b/rm-main/src/tui/tabs/torrents/tasks/add_magnet.rs similarity index 77% rename from rm-main/src/ui/tabs/torrents/tasks/add_magnet.rs rename to rm-main/src/tui/tabs/torrents/tasks/add_magnet.rs index d2d07f0..60da468 100644 --- a/rm-main/src/ui/tabs/torrents/tasks/add_magnet.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/add_magnet.rs @@ -2,12 +2,10 @@ use crossterm::event::{KeyCode, KeyEvent}; use ratatui::prelude::*; use crate::{ - app, transmission::TorrentAction, - ui::{ - components::{Component, ComponentAction}, - tabs::torrents::input_manager::InputManager, - to_input_request, + tui::{ + app, + components::{Component, ComponentAction, InputManager}, }, }; use rm_shared::{ @@ -58,10 +56,8 @@ impl AddMagnetBar { return ComponentAction::Quit; } - if let Some(req) = to_input_request(input) { - self.input_magnet_mgr.handle(req); + if self.input_magnet_mgr.handle_key(input).is_some() { self.ctx.send_action(Action::Render); - return ComponentAction::Nothing; } ComponentAction::Nothing @@ -78,22 +74,15 @@ impl AddMagnetBar { let task = StatusTask::new_add(self.input_magnet_mgr.text()); self.ctx.send_update_action(UpdateAction::TaskSet(task)); - let update_action = UpdateAction::SwitchToNormalMode; - self.ctx.send_update_action(update_action); - - return ComponentAction::Quit; - } - if input.code == KeyCode::Esc { - return ComponentAction::Quit; - } - - if let Some(req) = to_input_request(input) { - self.input_location_mgr.handle(req); + ComponentAction::Quit + } else if input.code == KeyCode::Esc { + ComponentAction::Quit + } else if self.input_location_mgr.handle_key(input).is_some() { self.ctx.send_action(Action::Render); - return ComponentAction::Nothing; + ComponentAction::Nothing + } else { + ComponentAction::Nothing } - - ComponentAction::Nothing } } diff --git a/rm-main/src/ui/tabs/torrents/tasks/default.rs b/rm-main/src/tui/tabs/torrents/tasks/default.rs similarity index 93% rename from rm-main/src/ui/tabs/torrents/tasks/default.rs rename to rm-main/src/tui/tabs/torrents/tasks/default.rs index 03013d4..40126e1 100644 --- a/rm-main/src/ui/tabs/torrents/tasks/default.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/default.rs @@ -1,8 +1,8 @@ -use crate::ui::components::Component; - use ratatui::prelude::*; use rm_config::CONFIG; +use crate::tui::components::Component; + pub struct DefaultBar {} impl DefaultBar { diff --git a/rm-main/src/ui/tabs/torrents/tasks/delete_torrent.rs b/rm-main/src/tui/tabs/torrents/tasks/delete_torrent.rs similarity index 89% rename from rm-main/src/ui/tabs/torrents/tasks/delete_torrent.rs rename to rm-main/src/tui/tabs/torrents/tasks/delete_torrent.rs index 3544820..295f577 100644 --- a/rm-main/src/ui/tabs/torrents/tasks/delete_torrent.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/delete_torrent.rs @@ -2,15 +2,9 @@ use crossterm::event::KeyCode; use ratatui::prelude::*; use transmission_rpc::types::Id; -use crate::{ - app, - transmission::TorrentAction, - ui::{ - components::{Component, ComponentAction}, - tabs::torrents::input_manager::InputManager, - to_input_request, - }, -}; +use crate::transmission::TorrentAction; +use crate::tui::app; +use crate::tui::components::{Component, ComponentAction, InputManager}; use rm_shared::action::{Action, UpdateAction}; use rm_shared::status_task::StatusTask; @@ -85,8 +79,7 @@ impl Component for DeleteBar { } } - if let Some(req) = to_input_request(input) { - self.input_mgr.handle(req); + if self.input_mgr.handle_key(input).is_some() { self.ctx.send_action(Action::Render); } diff --git a/rm-main/src/ui/tabs/torrents/tasks/filter.rs b/rm-main/src/tui/tabs/torrents/tasks/filter.rs similarity index 76% rename from rm-main/src/ui/tabs/torrents/tasks/filter.rs rename to rm-main/src/tui/tabs/torrents/tasks/filter.rs index c0e1848..d0a0bb8 100644 --- a/rm-main/src/ui/tabs/torrents/tasks/filter.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/filter.rs @@ -1,15 +1,13 @@ use crossterm::event::KeyCode; use ratatui::prelude::*; -use crate::{ +use rm_shared::action::{Action, UpdateAction}; + +use crate::tui::{ app, - ui::{ - components::{Component, ComponentAction}, - tabs::torrents::{input_manager::InputManager, table_manager::Filter}, - to_input_request, - }, + components::{Component, ComponentAction, InputManager}, + tabs::torrents::table_manager::Filter, }; -use rm_shared::action::{Action, UpdateAction}; pub struct FilterBar { ctx: app::Ctx, @@ -39,16 +37,14 @@ impl Component for FilterBar { if self.input.text().is_empty() { self.ctx.send_update_action(UpdateAction::SearchFilterClear); } - return ComponentAction::Quit; - } - - if let Some(req) = to_input_request(input) { - self.input.handle(req); + ComponentAction::Quit + } else if self.input.handle_key(input).is_some() { self.ctx .send_update_action(UpdateAction::SearchFilterApply(self.input.text())); + ComponentAction::Nothing + } else { + ComponentAction::Nothing } - - ComponentAction::Nothing } _ => ComponentAction::Nothing, } diff --git a/rm-main/src/ui/tabs/torrents/tasks/mod.rs b/rm-main/src/tui/tabs/torrents/tasks/mod.rs similarity index 100% rename from rm-main/src/ui/tabs/torrents/tasks/mod.rs rename to rm-main/src/tui/tabs/torrents/tasks/mod.rs diff --git a/rm-main/src/ui/tabs/torrents/tasks/move_torrent.rs b/rm-main/src/tui/tabs/torrents/tasks/move_torrent.rs similarity index 79% rename from rm-main/src/ui/tabs/torrents/tasks/move_torrent.rs rename to rm-main/src/tui/tabs/torrents/tasks/move_torrent.rs index e7febce..0495afe 100644 --- a/rm-main/src/ui/tabs/torrents/tasks/move_torrent.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/move_torrent.rs @@ -7,12 +7,10 @@ use rm_shared::{ use transmission_rpc::types::Id; use crate::{ - app, transmission::TorrentAction, - ui::{ - components::{Component, ComponentAction}, - tabs::torrents::input_manager::InputManager, - to_input_request, + tui::{ + app, + components::{Component, ComponentAction, InputManager}, }, }; @@ -44,19 +42,15 @@ impl MoveBar { let task = StatusTask::new_move(new_location); self.ctx.send_update_action(UpdateAction::TaskSet(task)); - return ComponentAction::Quit; - } - - if input.code == KeyCode::Esc { - return ComponentAction::Quit; - } - - if let Some(req) = to_input_request(input) { - self.input_mgr.handle(req); + ComponentAction::Quit + } else if input.code == KeyCode::Esc { + ComponentAction::Quit + } else if self.input_mgr.handle_key(input).is_some() { self.ctx.send_action(Action::Render); + ComponentAction::Nothing + } else { + ComponentAction::Nothing } - - ComponentAction::Nothing } } diff --git a/rm-main/src/ui/tabs/torrents/tasks/status.rs b/rm-main/src/tui/tabs/torrents/tasks/status.rs similarity index 98% rename from rm-main/src/ui/tabs/torrents/tasks/status.rs rename to rm-main/src/tui/tabs/torrents/tasks/status.rs index bee0178..1a124eb 100644 --- a/rm-main/src/ui/tabs/torrents/tasks/status.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/status.rs @@ -1,5 +1,3 @@ -use crate::{app, ui::components::Component}; - use ratatui::{prelude::*, style::Style}; use rm_shared::{ action::{Action, UpdateAction}, @@ -8,6 +6,8 @@ use rm_shared::{ use throbber_widgets_tui::ThrobberState; use tokio::time::{self, Instant}; +use crate::tui::{app, components::Component}; + pub struct StatusBar { task: StatusTask, pub task_status: CurrentTaskState, diff --git a/rm-main/src/tui.rs b/rm-main/src/tui/terminal.rs similarity index 90% rename from rm-main/src/tui.rs rename to rm-main/src/tui/terminal.rs index 6cba203..35c31f7 100644 --- a/rm-main/src/tui.rs +++ b/rm-main/src/tui/terminal.rs @@ -7,7 +7,7 @@ use crossterm::{ terminal::{EnterAlternateScreen, LeaveAlternateScreen}, }; use futures::{FutureExt, StreamExt}; -use ratatui::backend::CrosstermBackend as Backend; +use ratatui::{backend::CrosstermBackend as Backend, Terminal}; use tokio::{ sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, task::JoinHandle, @@ -15,7 +15,7 @@ use tokio::{ use tokio_util::sync::CancellationToken; pub struct Tui { - pub terminal: ratatui::Terminal>, + pub terminal: Terminal>, pub task: JoinHandle>, pub cancellation_token: CancellationToken, pub event_rx: UnboundedReceiver, @@ -24,7 +24,7 @@ pub struct Tui { impl Tui { pub(crate) fn new() -> Result { - let terminal = ratatui::Terminal::new(Backend::new(std::io::stdout()))?; + let terminal = Terminal::new(Backend::new(std::io::stdout()))?; let (event_tx, event_rx) = mpsc::unbounded_channel(); let cancellation_token = CancellationToken::new(); let task = tokio::spawn(async { Ok(()) }); @@ -37,8 +37,31 @@ impl Tui { }) } + fn handle_crossterm_event( + event: Option>, + event_tx: &UnboundedSender, + ) -> Result<()> { + match event { + Some(Ok(Event::Key(key))) => { + if key.kind == KeyEventKind::Press { + event_tx.send(Event::Key(key)).unwrap(); + } + } + Some(Ok(Event::Resize(x, y))) => event_tx.send(Event::Resize(x, y)).unwrap(), + Some(Err(e)) => Err(e)?, + _ => (), + } + Ok(()) + } + + pub(crate) fn enter(&mut self) -> Result<()> { + crossterm::terminal::enable_raw_mode()?; + crossterm::execute!(std::io::stdout(), EnterAlternateScreen, cursor::Hide)?; + self.start()?; + Ok(()) + } + pub fn start(&mut self) -> Result<()> { - self.cancel(); self.cancellation_token = CancellationToken::new(); let cancellation_token = self.cancellation_token.clone(); let event_tx = self.event_tx.clone(); @@ -57,25 +80,8 @@ impl Tui { Ok(()) } - fn handle_crossterm_event( - event: Option>, - event_tx: &UnboundedSender, - ) -> Result<()> { - match event { - Some(Ok(Event::Key(key))) => { - if key.kind == KeyEventKind::Press { - event_tx.send(Event::Key(key)).unwrap(); - } - } - Some(Ok(Event::Resize(x, y))) => event_tx.send(Event::Resize(x, y)).unwrap(), - Some(Err(e)) => Err(e)?, - _ => (), - } - Ok(()) - } - - pub(crate) fn stop(&self) { - self.cancel(); + pub(crate) fn exit(&mut self) -> Result<()> { + self.cancellation_token.cancel(); let mut counter = 0; while !self.task.is_finished() { std::thread::sleep(Duration::from_millis(1)); @@ -87,17 +93,6 @@ impl Tui { break; } } - } - - pub(crate) fn enter(&mut self) -> Result<()> { - crossterm::terminal::enable_raw_mode()?; - crossterm::execute!(std::io::stdout(), EnterAlternateScreen, cursor::Hide)?; - self.start()?; - Ok(()) - } - - pub(crate) fn exit(&mut self) -> Result<()> { - self.stop(); if crossterm::terminal::is_raw_mode_enabled()? { self.terminal.flush()?; crossterm::execute!(std::io::stdout(), LeaveAlternateScreen, cursor::Show)?; @@ -106,10 +101,6 @@ impl Tui { Ok(()) } - pub fn cancel(&self) { - self.cancellation_token.cancel(); - } - pub async fn next(&mut self) -> Option { self.event_rx.recv().await }