diff --git a/Cargo.lock b/Cargo.lock index 87f493a..0dc63ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -26,6 +38,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -547,6 +565,10 @@ name = "hashbrown" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "heck" @@ -828,9 +850,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "lru" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2994eeba8ed550fd9b47a0b38f0242bc3344e496483c6180b69139cc2fa5d1d7" +dependencies = [ + "hashbrown 0.14.1", +] + [[package]] name = "managarr" -version = "0.0.29" +version = "0.0.30" dependencies = [ "anyhow", "backtrace", @@ -1162,15 +1193,16 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e2e4cd95294a85c3b4446e63ef054eea43e0205b1fd60120c16b74ff7ff96ad" +checksum = "0ebc917cfb527a566c37ecb94c7e3fd098353516fb4eb6bea17015ade0182425" dependencies = [ "bitflags 2.4.0", "cassowary", "crossterm", "indoc", "itertools", + "lru", "paste", "strum", "time", @@ -1909,6 +1941,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "want" version = "0.3.1" @@ -2130,3 +2168,23 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] diff --git a/Cargo.toml b/Cargo.toml index a4d696c..094dc6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "managarr" -version = "0.0.29" +version = "0.0.30" authors = ["Alex Clarke "] description = "A TUI to manage your Servarrs" keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui"] @@ -32,7 +32,7 @@ strum = {version = "0.25.0", features = ["derive"] } strum_macros = "0.25.0" tokio = { version = "1.29.0", features = ["full"] } tokio-util = "0.7.8" -tui = { version = "0.23.0", package = "ratatui", features = ["all-widgets"] } +ratatui = { version = "0.24.0", features = ["all-widgets"] } urlencoding = "2.1.2" [dev-dependencies] diff --git a/src/app/key_binding.rs b/src/app/key_binding.rs index 443f1d3..067cd38 100644 --- a/src/app/key_binding.rs +++ b/src/app/key_binding.rs @@ -22,6 +22,7 @@ generate_keybindings! { edit, logs, tasks, + test, refresh, update, events, @@ -97,6 +98,10 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings { key: Key::Char('t'), desc: "tasks", }, + test: KeyBinding { + key: Key::Char('t'), + desc: "test", + }, refresh: KeyBinding { key: Key::Ctrl('r'), desc: "refresh", diff --git a/src/app/key_binding_tests.rs b/src/app/key_binding_tests.rs index edd99af..3fc4fe2 100644 --- a/src/app/key_binding_tests.rs +++ b/src/app/key_binding_tests.rs @@ -21,6 +21,7 @@ mod test { #[case(DEFAULT_KEYBINDINGS.events, Key::Char('e'), "events")] #[case(DEFAULT_KEYBINDINGS.logs, Key::Char('l'), "logs")] #[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), "tasks")] + #[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")] #[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), "refresh")] #[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), "update")] #[case(DEFAULT_KEYBINDINGS.home, Key::Home, "home")] diff --git a/src/app/radarr/mod.rs b/src/app/radarr/mod.rs index 112e2b4..344bd58 100644 --- a/src/app/radarr/mod.rs +++ b/src/app/radarr/mod.rs @@ -49,6 +49,11 @@ impl<'a> App<'a> { .dispatch_network_event(RadarrEvent::GetIndexerSettings.into()) .await; } + ActiveRadarrBlock::TestAllIndexers => { + self + .dispatch_network_event(RadarrEvent::TestAllIndexers.into()) + .await; + } ActiveRadarrBlock::System => { self .dispatch_network_event(RadarrEvent::GetTasks.into()) diff --git a/src/app/radarr/radarr_context_clues.rs b/src/app/radarr/radarr_context_clues.rs index 7fb7c4e..bdfbd04 100644 --- a/src/app/radarr/radarr_context_clues.rs +++ b/src/app/radarr/radarr_context_clues.rs @@ -50,7 +50,7 @@ pub static ROOT_FOLDERS_CONTEXT_CLUES: [ContextClue; 3] = [ ), ]; -pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 5] = [ +pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 6] = [ (DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc), (DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc), ( @@ -58,6 +58,7 @@ pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 5] = [ DEFAULT_KEYBINDINGS.settings.desc, ), (DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc), + (DEFAULT_KEYBINDINGS.test, "test all indexers"), ( DEFAULT_KEYBINDINGS.refresh, DEFAULT_KEYBINDINGS.refresh.desc, diff --git a/src/app/radarr/radarr_context_clues_tests.rs b/src/app/radarr/radarr_context_clues_tests.rs index b9b3497..bbad3c3 100644 --- a/src/app/radarr/radarr_context_clues_tests.rs +++ b/src/app/radarr/radarr_context_clues_tests.rs @@ -166,6 +166,11 @@ mod tests { let (key_binding, description) = indexers_context_clues_iter.next().unwrap(); + assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test); + assert_str_eq!(*description, "test all indexers"); + + let (key_binding, description) = indexers_context_clues_iter.next().unwrap(); + assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); assert_eq!(indexers_context_clues_iter.next(), None); diff --git a/src/app/radarr/radarr_tests.rs b/src/app/radarr/radarr_tests.rs index 3059203..95c06f8 100644 --- a/src/app/radarr/radarr_tests.rs +++ b/src/app/radarr/radarr_tests.rs @@ -162,6 +162,22 @@ mod tests { assert_eq!(app.tick_count, 0); } + #[tokio::test] + async fn test_dispatch_by_test_all_indexers_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::TestAllIndexers) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::TestAllIndexers.into() + ); + assert_eq!(app.tick_count, 0); + } + #[tokio::test] async fn test_dispatch_by_system_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs index f6857cf..3945cb6 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs @@ -9,7 +9,7 @@ use crate::network::radarr_network::RadarrEvent; use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; #[cfg(test)] -#[path = "./edit_indexer_settings_handler_tests.rs"] +#[path = "edit_indexer_settings_handler_tests.rs"] mod edit_indexer_settings_handler_tests; pub(super) struct IndexerSettingsHandler<'a, 'b> { diff --git a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs index 2fb5f20..acda375 100644 --- a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs @@ -7,7 +7,7 @@ mod tests { use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; - use crate::handlers::radarr_handlers::indexers::IndexersHandler; + use crate::handlers::radarr_handlers::indexers::{IndexersHandler, TestAllIndexersHandler}; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, @@ -302,6 +302,24 @@ mod tests { &INDEXER_SETTINGS_SELECTION_BLOCKS ); } + + #[test] + fn test_test_key() { + let mut app = App::default(); + + IndexersHandler::with( + &DEFAULT_KEYBINDINGS.test.key, + &mut app, + &ActiveRadarrBlock::Indexers, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::TestAllIndexers.into() + ); + } } #[rstest] @@ -327,6 +345,15 @@ mod tests { ); } + #[test] + fn test_delegates_test_all_indexers_block_to_test_all_indexers_handler() { + test_handler_delegation!( + TestAllIndexersHandler, + ActiveRadarrBlock::Indexers, + ActiveRadarrBlock::TestAllIndexers + ); + } + #[test] fn test_indexers_handler_accepts() { let mut indexers_blocks = Vec::new(); diff --git a/src/handlers/radarr_handlers/indexers/mod.rs b/src/handlers/radarr_handlers/indexers/mod.rs index f1bbe4a..6e6a80e 100644 --- a/src/handlers/radarr_handlers/indexers/mod.rs +++ b/src/handlers/radarr_handlers/indexers/mod.rs @@ -3,6 +3,7 @@ use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; +use crate::handlers::radarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, @@ -11,6 +12,7 @@ use crate::models::{BlockSelectionState, Scrollable}; use crate::network::radarr_network::RadarrEvent; mod edit_indexer_settings_handler; +mod test_all_indexers_handler; #[cfg(test)] #[path = "indexers_handler_tests.rs"] @@ -30,6 +32,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, IndexerSettingsHandler::with(self.key, self.app, self.active_radarr_block, self.context) .handle() } + _ if TestAllIndexersHandler::accepts(self.active_radarr_block) => { + TestAllIndexersHandler::with(self.key, self.app, self.active_radarr_block, self.context) + .handle() + } _ => self.handle_key_event(), } } @@ -137,6 +143,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { self.app.should_refresh = true; } + _ if *key == DEFAULT_KEYBINDINGS.test.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into()); + } _ if *key == DEFAULT_KEYBINDINGS.settings.key => { self .app diff --git a/src/handlers/radarr_handlers/indexers/test_all_indexers_handler.rs b/src/handlers/radarr_handlers/indexers/test_all_indexers_handler.rs new file mode 100644 index 0000000..c68347f --- /dev/null +++ b/src/handlers/radarr_handlers/indexers/test_all_indexers_handler.rs @@ -0,0 +1,107 @@ +use crate::app::App; +use crate::event::Key; +use crate::handlers::KeyEventHandler; +use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; +use crate::models::Scrollable; + +#[cfg(test)] +#[path = "test_all_indexers_handler_tests.rs"] +mod test_all_indexers_handler_tests; + +pub(super) struct TestAllIndexersHandler<'a, 'b> { + key: &'a Key, + app: &'a mut App<'b>, + active_radarr_block: &'a ActiveRadarrBlock, + _context: &'a Option, +} + +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandler<'a, 'b> { + fn accepts(active_block: &'a ActiveRadarrBlock) -> bool { + active_block == &ActiveRadarrBlock::TestAllIndexers + } + + fn with( + key: &'a Key, + app: &'a mut App<'b>, + active_block: &'a ActiveRadarrBlock, + _context: &'a Option, + ) -> TestAllIndexersHandler<'a, 'b> { + TestAllIndexersHandler { + key, + app, + active_radarr_block: active_block, + _context, + } + } + + fn get_key(&self) -> &Key { + self.key + } + + fn handle_scroll_up(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::TestAllIndexers { + self + .app + .data + .radarr_data + .indexer_test_all_results + .as_mut() + .unwrap() + .scroll_up() + } + } + + fn handle_scroll_down(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::TestAllIndexers { + self + .app + .data + .radarr_data + .indexer_test_all_results + .as_mut() + .unwrap() + .scroll_down() + } + } + + fn handle_home(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::TestAllIndexers { + self + .app + .data + .radarr_data + .indexer_test_all_results + .as_mut() + .unwrap() + .scroll_to_top() + } + } + + fn handle_end(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::TestAllIndexers { + self + .app + .data + .radarr_data + .indexer_test_all_results + .as_mut() + .unwrap() + .scroll_to_bottom() + } + } + + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) {} + + fn handle_submit(&mut self) {} + + fn handle_esc(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::TestAllIndexers { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.indexer_test_all_results = None; + } + } + + fn handle_char_key_event(&mut self) {} +} diff --git a/src/handlers/radarr_handlers/indexers/test_all_indexers_handler_tests.rs b/src/handlers/radarr_handlers/indexers/test_all_indexers_handler_tests.rs new file mode 100644 index 0000000..5413336 --- /dev/null +++ b/src/handlers/radarr_handlers/indexers/test_all_indexers_handler_tests.rs @@ -0,0 +1,165 @@ +#[cfg(test)] +mod tests { + use crate::app::key_binding::DEFAULT_KEYBINDINGS; + use crate::app::App; + use crate::event::Key; + use crate::handlers::radarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler; + use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; + use strum::IntoEnumIterator; + + mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_str_eq; + use rstest::rstest; + + use crate::models::servarr_data::radarr::modals::IndexerTestResultModalItem; + use crate::models::StatefulTable; + use crate::simple_stateful_iterable_vec; + + use super::*; + + #[rstest] + fn test_test_all_indexers_results_scroll( + #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, + ) { + let mut app = App::default(); + let mut indexer_test_results = StatefulTable::default(); + indexer_test_results.set_items(simple_stateful_iterable_vec!( + IndexerTestResultModalItem, + String, + name + )); + app.data.radarr_data.indexer_test_all_results = Some(indexer_test_results); + + TestAllIndexersHandler::with(&key, &mut app, &ActiveRadarrBlock::TestAllIndexers, &None) + .handle(); + + assert_str_eq!( + app + .data + .radarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 2" + ); + + TestAllIndexersHandler::with(&key, &mut app, &ActiveRadarrBlock::TestAllIndexers, &None) + .handle(); + + assert_str_eq!( + app + .data + .radarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 1" + ); + } + } + + mod test_handle_home_end { + use crate::extended_stateful_iterable_vec; + use crate::models::servarr_data::radarr::modals::IndexerTestResultModalItem; + use crate::models::StatefulTable; + use pretty_assertions::assert_str_eq; + + use super::*; + + #[test] + fn test_test_all_indexers_results_home_end() { + let mut app = App::default(); + let mut indexer_test_results = StatefulTable::default(); + indexer_test_results.set_items(extended_stateful_iterable_vec!( + IndexerTestResultModalItem, + String, + name + )); + app.data.radarr_data.indexer_test_all_results = Some(indexer_test_results); + + TestAllIndexersHandler::with( + &DEFAULT_KEYBINDINGS.end.key, + &mut app, + &ActiveRadarrBlock::TestAllIndexers, + &None, + ) + .handle(); + + assert_str_eq!( + app + .data + .radarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 3" + ); + + TestAllIndexersHandler::with( + &DEFAULT_KEYBINDINGS.home.key, + &mut app, + &ActiveRadarrBlock::TestAllIndexers, + &None, + ) + .handle(); + + assert_str_eq!( + app + .data + .radarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .current_selection() + .name, + "Test 1" + ); + } + } + + mod test_handle_esc { + use pretty_assertions::assert_eq; + + use crate::models::StatefulTable; + + use super::*; + + #[test] + fn test_test_all_indexers_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into()); + app.data.radarr_data.indexer_test_all_results = Some(StatefulTable::default()); + + TestAllIndexersHandler::with( + &DEFAULT_KEYBINDINGS.esc.key, + &mut app, + &ActiveRadarrBlock::TestAllIndexers, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + assert!(!app.data.radarr_data.prompt_confirm); + assert!(app.data.radarr_data.indexer_test_all_results.is_none()); + } + } + + #[test] + fn test_test_all_indexers_handler_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if active_radarr_block == ActiveRadarrBlock::TestAllIndexers { + assert!(TestAllIndexersHandler::accepts(&active_radarr_block)); + } else { + assert!(!TestAllIndexersHandler::accepts(&active_radarr_block)); + } + }); + } +} diff --git a/src/main.rs b/src/main.rs index 8b09cb4..13371dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,11 +9,11 @@ use crossterm::execute; use crossterm::terminal::{ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, }; +use ratatui::backend::CrosstermBackend; +use ratatui::Terminal; use tokio::sync::mpsc::Receiver; use tokio::sync::{mpsc, Mutex}; use tokio_util::sync::CancellationToken; -use tui::backend::CrosstermBackend; -use tui::Terminal; use crate::app::App; use crate::event::input_event::{Events, InputEvent}; diff --git a/src/models/mod.rs b/src/models/mod.rs index 7cd4e66..0ec09d5 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -2,9 +2,9 @@ use std::cell::RefCell; use std::fmt::{Debug, Display, Formatter}; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; +use ratatui::widgets::{ListState, TableState}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Number; -use tui::widgets::{ListState, TableState}; pub mod radarr_models; pub mod servarr_data; diff --git a/src/models/radarr_models.rs b/src/models/radarr_models.rs index a64beed..06ef75e 100644 --- a/src/models/radarr_models.rs +++ b/src/models/radarr_models.rs @@ -211,6 +211,23 @@ pub struct IndexerSettings { pub whitelisted_hardcoded_subs: HorizontallyScrollableText, } +#[derive(Default, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct IndexerTestResult { + #[serde(deserialize_with = "super::from_i64")] + pub id: i64, + pub is_valid: bool, + pub validation_failures: Vec, +} + +#[derive(Default, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct IndexerValidationFailure { + pub property_name: String, + pub error_message: String, + pub severity: String, +} + #[derive(Deserialize, Default, Debug, Clone, PartialEq, Eq, Ord, PartialOrd)] pub struct Language { pub name: String, diff --git a/src/models/servarr_data/radarr/modals.rs b/src/models/servarr_data/radarr/modals.rs index 6169b21..9321c42 100644 --- a/src/models/servarr_data/radarr/modals.rs +++ b/src/models/servarr_data/radarr/modals.rs @@ -208,3 +208,10 @@ impl From<&RadarrData<'_>> for EditCollectionModal { edit_collection_modal } } + +#[derive(Default, Clone, Eq, PartialEq, Debug)] +pub struct IndexerTestResultModalItem { + pub name: String, + pub is_valid: bool, + pub validation_failures: HorizontallyScrollableText, +} diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index ddf9462..64e97d8 100644 --- a/src/models/servarr_data/radarr/radarr_data.rs +++ b/src/models/servarr_data/radarr/radarr_data.rs @@ -10,7 +10,7 @@ use crate::models::radarr_models::{ IndexerSettings, Movie, QueueEvent, RootFolder, Task, }; use crate::models::servarr_data::radarr::modals::{ - AddMovieModal, EditCollectionModal, EditMovieModal, MovieDetailsModal, + AddMovieModal, EditCollectionModal, EditMovieModal, IndexerTestResultModalItem, MovieDetailsModal, }; use crate::models::{ BlockSelectionState, HorizontallyScrollableText, Route, ScrollableText, StatefulList, @@ -59,6 +59,7 @@ pub struct RadarrData<'a> { pub filtered_collections: Option>, pub filtered_movies: Option>, pub indexer_settings: Option, + pub indexer_test_all_results: Option>, pub movie_details_modal: Option, pub prompt_confirm: bool, pub prompt_confirm_action: Option, @@ -126,6 +127,7 @@ impl<'a> Default for RadarrData<'a> { filtered_collections: None, filtered_movies: None, indexer_settings: None, + indexer_test_all_results: None, movie_details_modal: None, is_searching: false, is_filtering: false, @@ -288,6 +290,7 @@ pub enum ActiveRadarrBlock { SystemTasks, SystemTaskStartConfirmPrompt, SystemUpdates, + TestAllIndexers, UpdateAndScanPrompt, UpdateAllCollectionsPrompt, UpdateAllMoviesPrompt, @@ -315,11 +318,12 @@ pub static COLLECTIONS_BLOCKS: [ActiveRadarrBlock; 6] = [ ActiveRadarrBlock::FilterCollectionsError, ActiveRadarrBlock::UpdateAllCollectionsPrompt, ]; -pub static INDEXERS_BLOCKS: [ActiveRadarrBlock; 4] = [ +pub static INDEXERS_BLOCKS: [ActiveRadarrBlock; 5] = [ ActiveRadarrBlock::AddIndexer, ActiveRadarrBlock::EditIndexer, ActiveRadarrBlock::DeleteIndexerPrompt, ActiveRadarrBlock::Indexers, + ActiveRadarrBlock::TestAllIndexers, ]; pub static ROOT_FOLDERS_BLOCKS: [ActiveRadarrBlock; 3] = [ ActiveRadarrBlock::RootFolders, diff --git a/src/models/servarr_data/radarr/radarr_data_tests.rs b/src/models/servarr_data/radarr/radarr_data_tests.rs index aafd751..f568136 100644 --- a/src/models/servarr_data/radarr/radarr_data_tests.rs +++ b/src/models/servarr_data/radarr/radarr_data_tests.rs @@ -101,6 +101,7 @@ mod tests { assert!(radarr_data.filtered_collections.is_none()); assert!(radarr_data.filtered_movies.is_none()); assert!(radarr_data.indexer_settings.is_none()); + assert!(radarr_data.indexer_test_all_results.is_none()); assert!(radarr_data.movie_details_modal.is_none()); assert!(!radarr_data.is_searching); assert!(!radarr_data.is_filtering); @@ -298,11 +299,12 @@ mod tests { #[test] fn test_indexers_blocks_contents() { - assert_eq!(INDEXERS_BLOCKS.len(), 4); + assert_eq!(INDEXERS_BLOCKS.len(), 5); assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::AddIndexer)); assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::EditIndexer)); assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::DeleteIndexerPrompt)); assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::Indexers)); + assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::TestAllIndexers)); } #[test] diff --git a/src/network/mod.rs b/src/network/mod.rs index d98fdd3..12af9b8 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -59,6 +59,7 @@ impl<'a, 'b> Network<'a, 'b> { B: Serialize + Default + Debug, R: DeserializeOwned, { + let ignore_status_code = request_props.ignore_status_code; let method = request_props.method; let request_uri = request_props.uri.clone(); select! { @@ -71,7 +72,7 @@ impl<'a, 'b> Network<'a, 'b> { resp = self.call_api(request_props).await.send() => { match resp { Ok(response) => { - if response.status().is_success() { + if response.status().is_success() || ignore_status_code { match method { RequestMethod::Get | RequestMethod::Post => { match utils::parse_response::(response).await { @@ -125,6 +126,7 @@ impl<'a, 'b> Network<'a, 'b> { method, body, api_token, + .. } = request_props; debug!("Creating RequestBuilder for resource: {uri:?}"); debug!("Sending {method:?} request to {uri} with body {body:?}"); @@ -160,4 +162,5 @@ pub struct RequestProps { pub method: RequestMethod, pub body: Option, pub api_token: String, + pub ignore_status_code: bool, } diff --git a/src/network/network_tests.rs b/src/network/network_tests.rs index 97ee5e1..f3b3e18 100644 --- a/src/network/network_tests.rs +++ b/src/network/network_tests.rs @@ -74,6 +74,7 @@ mod tests { value: "Test".to_owned(), }), api_token: "test1234".to_owned(), + ignore_status_code: false, }, |_, _| (), ) @@ -97,6 +98,7 @@ mod tests { method: request_method, body: None, api_token: "test1234".to_owned(), + ignore_status_code: false, }, |response, mut app| app.error = HorizontallyScrollableText::from(response.value), ) @@ -106,6 +108,32 @@ mod tests { assert_str_eq!(app_arc.lock().await.error.text, "Test"); } + #[rstest] + #[tokio::test] + async fn test_handle_request_with_response_body_ignore_error_code( + #[values(RequestMethod::Get, RequestMethod::Post)] request_method: RequestMethod, + ) { + let (async_server, app_arc, server) = mock_api(request_method, 400, true).await; + let mut network = Network::new(&app_arc, CancellationToken::new()); + let mut test_result = String::default(); + + network + .handle_request::<(), Test>( + RequestProps { + uri: format!("{}/test", server.url()), + method: request_method, + body: None, + api_token: "test1234".to_owned(), + ignore_status_code: true, + }, + |response, _app| test_result = response.value, + ) + .await; + + async_server.assert_async().await; + assert!(app_arc.lock().await.error.text.is_empty()); + } + #[tokio::test] async fn test_handle_request_request_is_cancelled() { let (async_server, _, server) = mock_api(RequestMethod::Get, 200, true).await; @@ -127,6 +155,7 @@ mod tests { method: RequestMethod::Get, body: None, api_token: "test1234".to_owned(), + ignore_status_code: false, }, |_, _| (), ) @@ -157,6 +186,7 @@ mod tests { method: RequestMethod::Get, body: None, api_token: "test1234".to_owned(), + ignore_status_code: false, }, |response, mut app| app.error = HorizontallyScrollableText::from(response.value), ) @@ -183,6 +213,7 @@ mod tests { method: RequestMethod::Get, body: None, api_token: "test1234".to_owned(), + ignore_status_code: false, }, |response, mut app| app.error = HorizontallyScrollableText::from(response.value), ) @@ -217,6 +248,7 @@ mod tests { method: request_method, body: None, api_token: "test1234".to_owned(), + ignore_status_code: false, }, |response, mut app| app.error = HorizontallyScrollableText::from(response.value), ) @@ -241,6 +273,7 @@ mod tests { method: RequestMethod::Post, body: None, api_token: "test1234".to_owned(), + ignore_status_code: false, }, |response, mut app| app.error = HorizontallyScrollableText::from(response.value), ) @@ -292,6 +325,7 @@ mod tests { method: request_method, body, api_token: "test1234".to_owned(), + ignore_status_code: false, }) .await .send() diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index ef112b4..a3b64a5 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -11,11 +11,12 @@ use crate::app::RadarrConfig; use crate::models::radarr_models::{ AddMovieBody, AddMovieSearchResult, AddOptions, AddRootFolderBody, Collection, CollectionMovie, CommandBody, Credit, CreditType, DiskSpace, DownloadRecord, DownloadsResponse, Indexer, - IndexerSettings, LogResponse, Movie, MovieCommandBody, MovieHistoryItem, QualityProfile, - QueueEvent, Release, ReleaseDownloadBody, RootFolder, SystemStatus, Tag, Task, Update, + IndexerSettings, IndexerTestResult, LogResponse, Movie, MovieCommandBody, MovieHistoryItem, + QualityProfile, QueueEvent, Release, ReleaseDownloadBody, RootFolder, SystemStatus, Tag, Task, + Update, }; use crate::models::servarr_data::radarr::modals::{ - AddMovieModal, EditCollectionModal, EditMovieModal, MovieDetailsModal, + AddMovieModal, EditCollectionModal, EditMovieModal, IndexerTestResultModalItem, MovieDetailsModal, }; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::{HorizontallyScrollableText, Route, Scrollable, ScrollableText, StatefulTable}; @@ -58,6 +59,7 @@ pub enum RadarrEvent { HealthCheck, SearchNewMovie, StartTask, + TestAllIndexers, TriggerAutomaticSearch, UpdateAllMovies, UpdateAndScan, @@ -92,6 +94,7 @@ impl RadarrEvent { RadarrEvent::GetTags => "/tag", RadarrEvent::GetTasks => "/system/task", RadarrEvent::GetUpdates => "/update", + RadarrEvent::TestAllIndexers => "/indexer/testall", RadarrEvent::StartTask | RadarrEvent::GetQueuedEvents | RadarrEvent::TriggerAutomaticSearch @@ -143,6 +146,7 @@ impl<'a, 'b> Network<'a, 'b> { RadarrEvent::HealthCheck => self.get_healthcheck().await, RadarrEvent::SearchNewMovie => self.search_movie().await, RadarrEvent::StartTask => self.start_task().await, + RadarrEvent::TestAllIndexers => self.test_all_indexers().await, RadarrEvent::TriggerAutomaticSearch => self.trigger_automatic_search().await, RadarrEvent::UpdateAllMovies => self.update_all_movies().await, RadarrEvent::UpdateAndScan => self.update_and_scan().await, @@ -1320,6 +1324,56 @@ impl<'a, 'b> Network<'a, 'b> { .await; } + async fn test_all_indexers(&mut self) { + info!("Testing all indexers"); + + let mut request_props = self + .radarr_request_props_from( + RadarrEvent::TestAllIndexers.resource(), + RequestMethod::Post, + None, + ) + .await; + request_props.ignore_status_code = true; + + self + .handle_request::<(), Vec>(request_props, |test_results, mut app| { + let mut test_all_indexer_results = StatefulTable::default(); + let indexers = app.data.radarr_data.indexers.items.clone(); + let modal_test_results = test_results + .iter() + .map(|result| { + let name = indexers + .iter() + .filter(|&indexer| indexer.id == result.id) + .map(|indexer| indexer.name.clone()) + .nth(0) + .unwrap_or_default(); + let validation_failures = result + .validation_failures + .iter() + .map(|failure| { + format!( + "Failure for field '{}': {}", + failure.property_name, failure.error_message + ) + }) + .collect::>() + .join(", "); + + IndexerTestResultModalItem { + name: name.unwrap_or_default(), + is_valid: result.is_valid, + validation_failures: validation_failures.into(), + } + }) + .collect(); + test_all_indexer_results.set_items(modal_test_results); + app.data.radarr_data.indexer_test_all_results = Some(test_all_indexer_results); + }) + .await; + } + async fn trigger_automatic_search(&mut self) { let (movie_id, tmdb_id) = self.extract_movie_id().await; info!("Searching indexers for movie with TMDB id {tmdb_id} and with Radarr id: {movie_id}"); @@ -1470,6 +1524,7 @@ impl<'a, 'b> Network<'a, 'b> { method, body, api_token: api_token.to_owned(), + ignore_status_code: false, } } diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index 0aac1f7..8614c06 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -110,6 +110,7 @@ mod test { fn test_resource_movie( #[values( RadarrEvent::AddMovie, + RadarrEvent::EditMovie, RadarrEvent::GetMovies, RadarrEvent::GetMovieDetails, RadarrEvent::DeleteMovie @@ -119,6 +120,40 @@ mod test { assert_str_eq!(event.resource(), "/movie"); } + #[rstest] + fn test_resource_collection( + #[values(RadarrEvent::GetCollections, RadarrEvent::EditCollection)] event: RadarrEvent, + ) { + assert_str_eq!(event.resource(), "/collection"); + } + + #[rstest] + fn test_resource_indexer( + #[values(RadarrEvent::GetIndexers, RadarrEvent::DeleteIndexer)] event: RadarrEvent, + ) { + assert_str_eq!(event.resource(), "/indexer"); + } + + #[rstest] + fn test_resource_indexer_settings( + #[values(RadarrEvent::GetIndexerSettings, RadarrEvent::UpdateIndexerSettings)] + event: RadarrEvent, + ) { + assert_str_eq!(event.resource(), "/config/indexer"); + } + + #[rstest] + fn test_resource_root_folder( + #[values( + RadarrEvent::AddRootFolder, + RadarrEvent::GetRootFolders, + RadarrEvent::DeleteRootFolder + )] + event: RadarrEvent, + ) { + assert_str_eq!(event.resource(), "/rootfolder"); + } + #[rstest] fn test_resource_release( #[values(RadarrEvent::GetReleases, RadarrEvent::DownloadRelease)] event: RadarrEvent, @@ -136,6 +171,8 @@ mod test { #[rstest] fn test_resource_command( #[values( + RadarrEvent::StartTask, + RadarrEvent::GetQueuedEvents, RadarrEvent::TriggerAutomaticSearch, RadarrEvent::UpdateAndScan, RadarrEvent::UpdateAllMovies, @@ -148,34 +185,20 @@ mod test { } #[rstest] - fn test_resource( - #[values( - RadarrEvent::GetCollections, - RadarrEvent::SearchNewMovie, - RadarrEvent::GetMovieCredits, - RadarrEvent::GetMovieHistory, - RadarrEvent::GetOverview, - RadarrEvent::GetQualityProfiles, - RadarrEvent::GetRootFolders, - RadarrEvent::GetStatus, - RadarrEvent::HealthCheck - )] - event: RadarrEvent, - ) { - let expected_resource = match event { - RadarrEvent::GetCollections => "/collection", - RadarrEvent::SearchNewMovie => "/movie/lookup", - RadarrEvent::GetMovieCredits => "/credit", - RadarrEvent::GetMovieHistory => "/history/movie", - RadarrEvent::GetOverview => "/diskspace", - RadarrEvent::GetQualityProfiles => "/qualityprofile", - RadarrEvent::GetRootFolders => "/rootfolder", - RadarrEvent::GetStatus => "/system/status", - RadarrEvent::HealthCheck => "/health", - _ => "", - }; - - assert_str_eq!(event.resource(), expected_resource); + #[case(RadarrEvent::GetLogs, "/log")] + #[case(RadarrEvent::SearchNewMovie, "/movie/lookup")] + #[case(RadarrEvent::GetMovieCredits, "/credit")] + #[case(RadarrEvent::GetMovieHistory, "/history/movie")] + #[case(RadarrEvent::GetOverview, "/diskspace")] + #[case(RadarrEvent::GetQualityProfiles, "/qualityprofile")] + #[case(RadarrEvent::GetStatus, "/system/status")] + #[case(RadarrEvent::GetTags, "/tag")] + #[case(RadarrEvent::GetTasks, "/system/task")] + #[case(RadarrEvent::GetUpdates, "/update")] + #[case(RadarrEvent::TestAllIndexers, "/indexer/testall")] + #[case(RadarrEvent::HealthCheck, "/health")] + fn test_resource(#[case] event: RadarrEvent, #[case] expected_uri: String) { + assert_str_eq!(event.resource(), expected_uri); } #[test] @@ -192,6 +215,7 @@ mod test { RequestMethod::Get, None, None, + None, RadarrEvent::HealthCheck.resource(), ) .await; @@ -217,6 +241,7 @@ mod test { "totalSpace": 4444 } ])), + None, RadarrEvent::GetOverview.resource(), ) .await; @@ -249,6 +274,7 @@ mod test { "version": "v1", "startTime": "2023-02-25T20:16:43Z" })), + None, RadarrEvent::GetStatus.resource(), ) .await; @@ -271,6 +297,7 @@ mod test { RequestMethod::Get, None, Some(serde_json::from_str(format!("[ {MOVIE_JSON} ]").as_str()).unwrap()), + None, RadarrEvent::GetMovies.resource(), ) .await; @@ -303,8 +330,14 @@ mod test { "quality": { "quality": { "name": "HD - 1080p" }} }]); let resource = format!("{}?movieId=1", RadarrEvent::GetReleases.resource()); - let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Get, None, Some(release_json), &resource).await; + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(release_json), + None, + &resource, + ) + .await; app_arc .lock() .await @@ -364,6 +397,7 @@ mod test { RequestMethod::Get, None, Some(add_movie_search_result_json), + None, &resource, ) .await; @@ -404,6 +438,7 @@ mod test { "name": "TestTask" })), None, + None, RadarrEvent::StartTask.resource(), ) .await; @@ -431,7 +466,7 @@ mod test { RadarrEvent::SearchNewMovie.resource() ); let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Get, None, Some(json!([])), &resource).await; + mock_radarr_api(RequestMethod::Get, None, Some(json!([])), None, &resource).await; app_arc.lock().await.data.radarr_data.search = Some("test term".into()); let mut network = Network::new(&app_arc, CancellationToken::new()); @@ -502,6 +537,97 @@ mod test { ); } + #[tokio::test] + async fn test_handle_test_all_indexers_event() { + let indexers = vec![ + Indexer { + id: 1, + name: Some("Test 1".to_owned()), + ..Indexer::default() + }, + Indexer { + id: 2, + name: Some("Test 2".to_owned()), + ..Indexer::default() + }, + ]; + let indexer_test_results_modal_items = vec![ + IndexerTestResultModalItem { + name: "Test 1".to_owned(), + is_valid: true, + validation_failures: HorizontallyScrollableText::default(), + }, + IndexerTestResultModalItem { + name: "Test 2".to_owned(), + is_valid: false, + validation_failures: "Failure for field 'test field 1': test error message, Failure for field 'test field 2': test error message 2".into(), + }, + ]; + let response_json = json!([ + { + "id": 1, + "isValid": true, + "validationFailures": [] + }, + { + "id": 2, + "isValid": false, + "validationFailures": [ + { + "propertyName": "test field 1", + "errorMessage": "test error message", + "severity": "error" + }, + { + "propertyName": "test field 2", + "errorMessage": "test error message 2", + "severity": "error" + }, + ] + }]); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + None, + Some(response_json), + Some(400), + RadarrEvent::TestAllIndexers.resource(), + ) + .await; + app_arc + .lock() + .await + .data + .radarr_data + .indexers + .set_items(indexers); + let mut network = Network::new(&app_arc, CancellationToken::new()); + + network + .handle_radarr_event(RadarrEvent::TestAllIndexers) + .await; + + async_server.assert_async().await; + assert!(app_arc + .lock() + .await + .data + .radarr_data + .indexer_test_all_results + .is_some()); + assert_eq!( + app_arc + .lock() + .await + .data + .radarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .items, + indexer_test_results_modal_items + ); + } + #[tokio::test] async fn test_handle_trigger_automatic_search_event() { let (async_server, app_arc, _server) = mock_radarr_api( @@ -511,6 +637,7 @@ mod test { "movieIds": [ 1 ] })), None, + None, RadarrEvent::TriggerAutomaticSearch.resource(), ) .await; @@ -539,6 +666,7 @@ mod test { "movieIds": [ 1 ] })), None, + None, RadarrEvent::UpdateAndScan.resource(), ) .await; @@ -567,6 +695,7 @@ mod test { "movieIds": [] })), None, + None, RadarrEvent::UpdateAllMovies.resource(), ) .await; @@ -587,6 +716,7 @@ mod test { "name": "RefreshMonitoredDownloads" })), None, + None, RadarrEvent::UpdateDownloads.resource(), ) .await; @@ -616,6 +746,7 @@ mod test { RequestMethod::Put, Some(indexer_settings_json), None, + None, RadarrEvent::UpdateIndexerSettings.resource(), ) .await; @@ -645,6 +776,7 @@ mod test { "name": "RefreshCollections" })), None, + None, RadarrEvent::UpdateCollections.resource(), ) .await; @@ -664,6 +796,7 @@ mod test { RequestMethod::Get, None, Some(serde_json::from_str(MOVIE_JSON).unwrap()), + None, &resource, ) .await; @@ -775,6 +908,7 @@ mod test { RequestMethod::Get, None, Some(movie_json_with_missing_fields), + None, &resource, ) .await; @@ -843,6 +977,7 @@ mod test { RequestMethod::Get, None, Some(movie_history_item_json), + None, &resource, ) .await; @@ -890,6 +1025,7 @@ mod test { RequestMethod::Get, None, Some(movie_history_item_json), + None, &resource, ) .await; @@ -957,6 +1093,7 @@ mod test { RequestMethod::Get, None, Some(collection_json), + None, RadarrEvent::GetCollections.resource(), ) .await; @@ -992,6 +1129,7 @@ mod test { RequestMethod::Get, None, Some(downloads_response_json), + None, RadarrEvent::GetDownloads.resource(), ) .await; @@ -1057,6 +1195,7 @@ mod test { RequestMethod::Get, None, Some(indexers_response_json), + None, RadarrEvent::GetIndexers.resource(), ) .await; @@ -1088,6 +1227,7 @@ mod test { RequestMethod::Get, None, Some(indexer_settings_response_json), + None, RadarrEvent::GetIndexerSettings.resource(), ) .await; @@ -1121,6 +1261,7 @@ mod test { RequestMethod::Get, None, Some(indexer_settings_response_json), + None, RadarrEvent::GetIndexerSettings.resource(), ) .await; @@ -1166,6 +1307,7 @@ mod test { RequestMethod::Get, None, Some(queued_events_json), + None, RadarrEvent::GetQueuedEvents.resource(), ) .await; @@ -1221,6 +1363,7 @@ mod test { } ] })), + None, &resource, ) .await; @@ -1254,6 +1397,7 @@ mod test { RequestMethod::Get, None, Some(quality_profile_json), + None, RadarrEvent::GetQualityProfiles.resource(), ) .await; @@ -1280,6 +1424,7 @@ mod test { RequestMethod::Get, None, Some(tags_json), + None, RadarrEvent::GetTags.resource(), ) .await; @@ -1335,6 +1480,7 @@ mod test { RequestMethod::Get, None, Some(tasks_json), + None, RadarrEvent::GetTasks.resource(), ) .await; @@ -1421,6 +1567,7 @@ mod test { RequestMethod::Get, None, Some(tasks_json), + None, RadarrEvent::GetUpdates.resource(), ) .await; @@ -1441,6 +1588,7 @@ mod test { RequestMethod::Post, Some(json!({ "label": "testing" })), Some(json!({ "id": 3, "label": "testing" })), + None, RadarrEvent::GetTags.resource(), ) .await; @@ -1473,6 +1621,7 @@ mod test { RequestMethod::Get, None, Some(root_folder_json), + None, RadarrEvent::GetRootFolders.resource(), ) .await; @@ -1505,8 +1654,14 @@ mod test { } ]); let resource = format!("{}?movieId=1", RadarrEvent::GetMovieCredits.resource()); - let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Get, None, Some(credits_json), &resource).await; + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(credits_json), + None, + &resource, + ) + .await; app_arc .lock() .await @@ -1536,7 +1691,7 @@ mod test { RadarrEvent::DeleteMovie.resource() ); let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; + mock_radarr_api(RequestMethod::Delete, None, None, None, &resource).await; { let mut app = app_arc.lock().await; app.data.radarr_data.movies.set_items(vec![movie()]); @@ -1556,7 +1711,7 @@ mod test { async fn test_handle_delete_download_event() { let resource = format!("{}/1", RadarrEvent::DeleteDownload.resource()); let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; + mock_radarr_api(RequestMethod::Delete, None, None, None, &resource).await; app_arc .lock() .await @@ -1577,7 +1732,7 @@ mod test { async fn test_handle_delete_indexer_event() { let resource = format!("{}/1", RadarrEvent::DeleteIndexer.resource()); let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; + mock_radarr_api(RequestMethod::Delete, None, None, None, &resource).await; app_arc .lock() .await @@ -1598,7 +1753,7 @@ mod test { async fn test_handle_delete_root_folder_event() { let resource = format!("{}/1", RadarrEvent::DeleteRootFolder.resource()); let (async_server, app_arc, _server) = - mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; + mock_radarr_api(RequestMethod::Delete, None, None, None, &resource).await; app_arc .lock() .await @@ -1634,6 +1789,7 @@ mod test { } })), None, + None, RadarrEvent::AddMovie.resource(), ) .await; @@ -1720,6 +1876,7 @@ mod test { } })), None, + None, RadarrEvent::AddMovie.resource(), ) .await; @@ -1805,6 +1962,7 @@ mod test { "path": "/nfs/test" })), None, + None, RadarrEvent::AddRootFolder.resource(), ) .await; @@ -1840,6 +1998,7 @@ mod test { RequestMethod::Get, None, Some(serde_json::from_str(MOVIE_JSON).unwrap()), + None, &resource, ) .await; @@ -1933,6 +2092,7 @@ mod test { RequestMethod::Get, None, Some(detailed_collection_body), + None, &resource, ) .await; @@ -1992,6 +2152,7 @@ mod test { "movieId": 1 })), None, + None, RadarrEvent::DownloadRelease.resource(), ) .await; @@ -2042,6 +2203,7 @@ mod test { RequestMethod::Post, Some(json!({ "label": "testing" })), Some(json!({ "id": 3, "label": "testing" })), + None, RadarrEvent::GetTags.resource(), ) .await; @@ -2256,15 +2418,22 @@ mod test { method: RequestMethod, request_body: Option, response_body: Option, + response_status: Option, resource: &str, ) -> (Mock, Arc>>, ServerGuard) { + let status = if let Some(status) = response_status { + status + } else { + 200 + }; let mut server = Server::new_async().await; let mut async_server = server .mock( &method.to_string().to_uppercase(), format!("/api/v3{resource}").as_str(), ) - .match_header("X-Api-Key", "test1234"); + .match_header("X-Api-Key", "test1234") + .with_status(status); if let Some(body) = request_body { async_server = async_server.match_body(Matcher::Json(body)); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index aebfabf..be84cd1 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,17 +1,16 @@ use std::iter; use std::rc::Rc; -use tui::backend::Backend; -use tui::layout::{Alignment, Constraint, Rect}; -use tui::style::Modifier; -use tui::text::{Line, Span, Text}; -use tui::widgets::Paragraph; -use tui::widgets::Row; -use tui::widgets::Table; -use tui::widgets::Tabs; -use tui::widgets::{Block, Wrap}; -use tui::widgets::{Clear, List, ListItem}; -use tui::Frame; +use ratatui::layout::{Alignment, Constraint, Rect}; +use ratatui::style::Modifier; +use ratatui::text::{Line, Span, Text}; +use ratatui::widgets::Paragraph; +use ratatui::widgets::Row; +use ratatui::widgets::Table; +use ratatui::widgets::Tabs; +use ratatui::widgets::{Block, Wrap}; +use ratatui::widgets::{Clear, List, ListItem}; +use ratatui::Frame; use crate::app::App; use crate::models::{HorizontallyScrollableText, Route, StatefulList, StatefulTable, TabState}; @@ -32,11 +31,11 @@ static HIGHLIGHT_SYMBOL: &str = "=> "; pub trait DrawUi { fn accepts(route: Route) -> bool; - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect); - fn draw_context_row(_f: &mut Frame<'_, B>, _app: &App<'_>, _area: Rect) {} + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect); + fn draw_context_row(_f: &mut Frame<'_>, _app: &App<'_>, _area: Rect) {} } -pub fn ui(f: &mut Frame<'_, B>, app: &mut App<'_>) { +pub fn ui(f: &mut Frame<'_>, app: &mut App<'_>) { f.render_widget(background_block(), f.size()); let main_chunks = if !app.error.text.is_empty() { let chunks = vertical_chunks_with_margin( @@ -73,7 +72,7 @@ pub fn ui(f: &mut Frame<'_, B>, app: &mut App<'_>) { } } -fn draw_header_row(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let chunks = horizontal_chunks_with_margin(vec![Constraint::Length(75), Constraint::Min(0)], area, 1); let help_text = Text::from(app.server_tabs.get_active_tab_help()); @@ -97,7 +96,7 @@ fn draw_header_row(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Re f.render_widget(help, chunks[1]); } -fn draw_error(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let block = title_block("Error | to close").style(style_failure().add_modifier(Modifier::BOLD)); @@ -118,10 +117,10 @@ fn draw_error(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { f.render_widget(paragraph, area); } -pub fn draw_popup( - f: &mut Frame<'_, B>, +pub fn draw_popup( + f: &mut Frame<'_>, app: &mut App<'_>, - popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), percent_x: u16, percent_y: u16, ) { @@ -131,8 +130,8 @@ pub fn draw_popup( popup_fn(f, app, popup_area); } -pub fn draw_popup_ui( - f: &mut Frame<'_, B>, +pub fn draw_popup_ui( + f: &mut Frame<'_>, app: &mut App<'_>, percent_x: u16, percent_y: u16, @@ -143,12 +142,12 @@ pub fn draw_popup_ui( T::draw(f, app, popup_area); } -pub fn draw_popup_over( - f: &mut Frame<'_, B>, +pub fn draw_popup_over( + f: &mut Frame<'_>, app: &mut App<'_>, area: Rect, - background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), percent_x: u16, percent_y: u16, ) { @@ -157,90 +156,90 @@ pub fn draw_popup_over( draw_popup(f, app, popup_fn, percent_x, percent_y); } -pub fn draw_popup_over_ui( - f: &mut Frame<'_, B>, +pub fn draw_popup_over_ui( + f: &mut Frame<'_>, app: &mut App<'_>, area: Rect, - background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), percent_x: u16, percent_y: u16, ) { background_fn(f, app, area); - draw_popup_ui::(f, app, percent_x, percent_y); + draw_popup_ui::(f, app, percent_x, percent_y); } -pub fn draw_prompt_popup_over( - f: &mut Frame<'_, B>, +pub fn draw_prompt_popup_over( + f: &mut Frame<'_>, app: &mut App<'_>, area: Rect, - background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 35, 35); } -pub fn draw_small_popup_over( - f: &mut Frame<'_, B>, +pub fn draw_small_popup_over( + f: &mut Frame<'_>, app: &mut App<'_>, area: Rect, - background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 40, 40); } -pub fn draw_medium_popup_over( - f: &mut Frame<'_, B>, +pub fn draw_medium_popup_over( + f: &mut Frame<'_>, app: &mut App<'_>, area: Rect, - background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 60, 60); } -pub fn draw_large_popup_over( - f: &mut Frame<'_, B>, +pub fn draw_large_popup_over( + f: &mut Frame<'_>, app: &mut App<'_>, area: Rect, - background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 75, 75); } -pub fn draw_large_popup_over_background_fn_with_ui( - f: &mut Frame<'_, B>, +pub fn draw_large_popup_over_background_fn_with_ui( + f: &mut Frame<'_>, app: &mut App<'_>, area: Rect, - background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), ) { - draw_popup_over_ui::(f, app, area, background_fn, 75, 75); + draw_popup_over_ui::(f, app, area, background_fn, 75, 75); } -pub fn draw_drop_down_popup( - f: &mut Frame<'_, B>, +pub fn draw_drop_down_popup( + f: &mut Frame<'_>, app: &mut App<'_>, area: Rect, - background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - drop_down_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), + drop_down_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, drop_down_fn, 20, 30); } -pub fn draw_error_popup_over( - f: &mut Frame<'_, B>, +pub fn draw_error_popup_over( + f: &mut Frame<'_>, app: &mut App<'_>, area: Rect, message: &str, - background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: fn(&mut Frame<'_>, &mut App<'_>, Rect), ) { background_fn(f, app, area); draw_error_popup(f, message); } -pub fn draw_error_popup(f: &mut Frame<'_, B>, message: &str) { +pub fn draw_error_popup(f: &mut Frame<'_>, message: &str) { let prompt_area = centered_rect(25, 8, f.size()); f.render_widget(Clear, prompt_area); f.render_widget(background_block(), prompt_area); @@ -254,8 +253,8 @@ pub fn draw_error_popup(f: &mut Frame<'_, B>, message: &str) { f.render_widget(error_message, prompt_area); } -fn draw_tabs<'a, B: Backend>( - f: &mut Frame<'_, B>, +fn draw_tabs<'a>( + f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState, @@ -306,8 +305,8 @@ pub struct ListProps<'a, T> { pub help: Option, } -fn draw_table<'a, B, T, F>( - f: &mut Frame<'_, B>, +fn draw_table<'a, T, F>( + f: &mut Frame<'_>, content_area: Rect, block: Block<'_>, table_props: TableProps<'a, T>, @@ -315,7 +314,6 @@ fn draw_table<'a, B, T, F>( is_loading: bool, highlight: bool, ) where - B: Backend, F: Fn(&T) -> Row<'a>, { let TableProps { @@ -357,8 +355,8 @@ fn draw_table<'a, B, T, F>( } #[allow(clippy::too_many_arguments)] -fn draw_table_contents<'a, B, T, F>( - f: &mut Frame<'_, B>, +fn draw_table_contents<'a, T, F>( + f: &mut Frame<'_>, block: Block<'_>, row_mapper: F, highlight: bool, @@ -367,7 +365,6 @@ fn draw_table_contents<'a, B, T, F>( constraints: Vec, content_area: Rect, ) where - B: Backend, F: Fn(&T) -> Row<'a>, { let rows = content.items.iter().map(row_mapper); @@ -389,7 +386,7 @@ fn draw_table_contents<'a, B, T, F>( f.render_stateful_widget(table, content_area, &mut content.state); } -pub fn loading(f: &mut Frame<'_, B>, block: Block<'_>, area: Rect, is_loading: bool) { +pub fn loading(f: &mut Frame<'_>, block: Block<'_>, area: Rect, is_loading: bool) { if is_loading { let text = "\n\n Loading ...\n\n".to_owned(); let mut text = Text::from(text); @@ -404,8 +401,8 @@ pub fn loading(f: &mut Frame<'_, B>, block: Block<'_>, area: Rect, i } } -pub fn draw_prompt_box( - f: &mut Frame<'_, B>, +pub fn draw_prompt_box( + f: &mut Frame<'_>, prompt_area: Rect, title: &str, prompt: &str, @@ -414,8 +411,8 @@ pub fn draw_prompt_box( draw_prompt_box_with_content(f, prompt_area, title, prompt, None, yes_no_value); } -pub fn draw_prompt_box_with_content( - f: &mut Frame<'_, B>, +pub fn draw_prompt_box_with_content( + f: &mut Frame<'_>, prompt_area: Rect, title: &str, prompt: &str, @@ -463,8 +460,8 @@ pub fn draw_prompt_box_with_content( draw_button(f, horizontal_chunks[1], "No", !yes_no_value); } -pub fn draw_prompt_box_with_checkboxes( - f: &mut Frame<'_, B>, +pub fn draw_prompt_box_with_checkboxes( + f: &mut Frame<'_>, prompt_area: Rect, title: &str, prompt: &str, @@ -513,12 +510,7 @@ pub fn draw_prompt_box_with_checkboxes( ); } -pub fn draw_checkbox( - f: &mut Frame<'_, B>, - area: Rect, - is_checked: bool, - is_selected: bool, -) { +pub fn draw_checkbox(f: &mut Frame<'_>, area: Rect, is_checked: bool, is_selected: bool) { let check = if is_checked { "✔" } else { "" }; let label_paragraph = Paragraph::new(Text::from(check)) .block(layout_block()) @@ -529,8 +521,8 @@ pub fn draw_checkbox( f.render_widget(label_paragraph, checkbox_area); } -pub fn draw_checkbox_with_label( - f: &mut Frame<'_, B>, +pub fn draw_checkbox_with_label( + f: &mut Frame<'_>, area: Rect, label: &str, is_checked: bool, @@ -555,14 +547,14 @@ pub fn draw_checkbox_with_label( draw_checkbox(f, horizontal_chunks[1], is_checked, is_selected); } -pub fn draw_button(f: &mut Frame<'_, B>, area: Rect, label: &str, is_selected: bool) { +pub fn draw_button(f: &mut Frame<'_>, area: Rect, label: &str, is_selected: bool) { let label_paragraph = layout_button_paragraph(is_selected, label, Alignment::Center); f.render_widget(label_paragraph, area); } -pub fn draw_button_with_icon( - f: &mut Frame<'_, B>, +pub fn draw_button_with_icon( + f: &mut Frame<'_>, area: Rect, label: &str, icon: &str, @@ -589,8 +581,8 @@ pub fn draw_button_with_icon( f.render_widget(icon_paragraph, horizontal_chunks[1]); } -pub fn draw_drop_down_menu_button( - f: &mut Frame<'_, B>, +pub fn draw_drop_down_menu_button( + f: &mut Frame<'_>, area: Rect, description: &str, selection: &str, @@ -615,8 +607,8 @@ pub fn draw_drop_down_menu_button( draw_button_with_icon(f, horizontal_chunks[1], selection, "▼", is_selected); } -pub fn draw_selectable_list<'a, B: Backend, T>( - f: &mut Frame<'_, B>, +pub fn draw_selectable_list<'a, T>( + f: &mut Frame<'_>, area: Rect, content: &'a mut StatefulList, item_mapper: impl Fn(&T) -> ListItem<'a>, @@ -629,8 +621,8 @@ pub fn draw_selectable_list<'a, B: Backend, T>( f.render_stateful_widget(list, area, &mut content.state); } -pub fn draw_list_box<'a, B: Backend, T>( - f: &mut Frame<'_, B>, +pub fn draw_list_box<'a, T>( + f: &mut Frame<'_>, area: Rect, item_mapper: impl Fn(&T) -> ListItem<'a>, list_props: ListProps<'a, T>, @@ -667,11 +659,7 @@ pub fn draw_list_box<'a, B: Backend, T>( } } -fn draw_help_and_get_content_rect( - f: &mut Frame<'_, B>, - area: Rect, - help: Option, -) -> Rect { +fn draw_help_and_get_content_rect(f: &mut Frame<'_>, area: Rect, help: Option) -> Rect { if let Some(help_string) = help { let chunks = vertical_chunks_with_margin(vec![Constraint::Min(0), Constraint::Length(2)], area, 1); @@ -690,8 +678,8 @@ fn draw_help_and_get_content_rect( } } -pub fn draw_text_box( - f: &mut Frame<'_, B>, +pub fn draw_text_box( + f: &mut Frame<'_>, text_box_area: Rect, block_title: Option<&str>, block_content: &str, @@ -721,8 +709,8 @@ pub fn draw_text_box( } } -pub fn draw_text_box_with_label( - f: &mut Frame<'_, B>, +pub fn draw_text_box_with_label( + f: &mut Frame<'_>, area: Rect, label: &str, text: &str, @@ -757,8 +745,8 @@ pub fn draw_text_box_with_label( ); } -pub fn draw_input_box_popup( - f: &mut Frame<'_, B>, +pub fn draw_input_box_popup( + f: &mut Frame<'_>, input_box_area: Rect, box_title: &str, box_content: &HorizontallyScrollableText, @@ -790,11 +778,7 @@ pub fn draw_input_box_popup( f.render_widget(help, chunks[1]); } -pub fn draw_error_message_popup( - f: &mut Frame<'_, B>, - error_message_area: Rect, - error_msg: &str, -) { +pub fn draw_error_message_popup(f: &mut Frame<'_>, error_message_area: Rect, error_msg: &str) { let input = Paragraph::new(error_msg) .style(style_failure()) .alignment(Alignment::Center) diff --git a/src/ui/radarr_ui/collections/collection_details_ui.rs b/src/ui/radarr_ui/collections/collection_details_ui.rs index 415b1fb..74a3c70 100644 --- a/src/ui/radarr_ui/collections/collection_details_ui.rs +++ b/src/ui/radarr_ui/collections/collection_details_ui.rs @@ -1,8 +1,7 @@ -use tui::backend::Backend; -use tui::layout::{Alignment, Constraint, Rect}; -use tui::text::Text; -use tui::widgets::{Cell, Paragraph, Row, Wrap}; -use tui::Frame; +use ratatui::layout::{Alignment, Constraint, Rect}; +use ratatui::text::Text; +use ratatui::widgets::{Cell, Paragraph, Row, Wrap}; +use ratatui::Frame; use crate::app::context_clues::{build_context_clue_string, BARE_POPUP_CONTEXT_CLUES}; use crate::app::radarr::radarr_context_clues::COLLECTION_DETAILS_CONTEXT_CLUES; @@ -36,10 +35,10 @@ impl DrawUi for CollectionDetailsUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_collection_details_popup = - |f: &mut Frame<'_, B>, app: &mut App<'_>, popup_area: Rect| match context_option + |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| match context_option .unwrap_or(active_radarr_block) { ActiveRadarrBlock::ViewMovieOverview => { @@ -66,11 +65,7 @@ impl DrawUi for CollectionDetailsUi { } } -pub fn draw_collection_details( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - content_area: Rect, -) { +pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { let chunks = vertical_chunks_with_margin( vec![ Constraint::Percentage(25), @@ -242,7 +237,7 @@ pub fn draw_collection_details( ); } -fn draw_movie_overview(f: &mut Frame<'_, B>, app: &mut App<'_>, content_area: Rect) { +fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { let title_block = title_block("Overview"); f.render_widget(title_block, content_area); diff --git a/src/ui/radarr_ui/collections/edit_collection_ui.rs b/src/ui/radarr_ui/collections/edit_collection_ui.rs index 52ec7d1..f7db01f 100644 --- a/src/ui/radarr_ui/collections/edit_collection_ui.rs +++ b/src/ui/radarr_ui/collections/edit_collection_ui.rs @@ -1,7 +1,6 @@ -use tui::backend::Backend; -use tui::layout::{Constraint, Rect}; -use tui::widgets::ListItem; -use tui::Frame; +use ratatui::layout::{Constraint, Rect}; +use ratatui::widgets::ListItem; +use ratatui::Frame; use crate::app::App; use crate::models::servarr_data::radarr::modals::EditCollectionModal; @@ -35,10 +34,10 @@ impl DrawUi for EditCollectionUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_edit_collection_prompt = - |f: &mut Frame<'_, B>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block { + |f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block { ActiveRadarrBlock::EditCollectionSelectMinimumAvailability => { draw_drop_down_popup( f, @@ -76,7 +75,7 @@ impl DrawUi for EditCollectionUi { draw_edit_collection_prompt, ), _ if COLLECTION_DETAILS_BLOCKS.contains(&context) => { - draw_large_popup_over_background_fn_with_ui::( + draw_large_popup_over_background_fn_with_ui::( f, app, content_rect, @@ -91,8 +90,8 @@ impl DrawUi for EditCollectionUi { } } -fn draw_edit_collection_confirmation_prompt( - f: &mut Frame<'_, B>, +fn draw_edit_collection_confirmation_prompt( + f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect, ) { @@ -223,8 +222,8 @@ fn draw_edit_collection_confirmation_prompt( ); } -fn draw_edit_collection_select_minimum_availability_popup( - f: &mut Frame<'_, B>, +fn draw_edit_collection_select_minimum_availability_popup( + f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect, ) { @@ -242,8 +241,8 @@ fn draw_edit_collection_select_minimum_availability_popup( ); } -fn draw_edit_collection_select_quality_profile_popup( - f: &mut Frame<'_, B>, +fn draw_edit_collection_select_quality_profile_popup( + f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect, ) { diff --git a/src/ui/radarr_ui/collections/mod.rs b/src/ui/radarr_ui/collections/mod.rs index 093fcc5..519afc5 100644 --- a/src/ui/radarr_ui/collections/mod.rs +++ b/src/ui/radarr_ui/collections/mod.rs @@ -1,7 +1,6 @@ -use tui::backend::Backend; -use tui::layout::{Constraint, Rect}; -use tui::widgets::{Cell, Row}; -use tui::Frame; +use ratatui::layout::{Constraint, Rect}; +use ratatui::widgets::{Cell, Row}; +use ratatui::Frame; pub(super) use collection_details_ui::draw_collection_details; @@ -36,7 +35,7 @@ impl DrawUi for CollectionsUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { let route = *app.get_current_route(); let mut collections_ui_matcher = |active_radarr_block| match active_radarr_block { ActiveRadarrBlock::Collections => draw_collections(f, app, content_rect), @@ -99,7 +98,7 @@ impl DrawUi for CollectionsUi { } } -pub(super) fn draw_collections(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let current_selection = if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() { filtered_collections.current_selection().clone() @@ -176,11 +175,7 @@ pub(super) fn draw_collections(f: &mut Frame<'_, B>, app: &mut App<' ); } -fn draw_update_all_collections_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_update_all_collections_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { draw_prompt_box( f, prompt_area, @@ -190,7 +185,7 @@ fn draw_update_all_collections_prompt( ); } -fn draw_collection_search_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_collection_search_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_input_box_popup( f, area, @@ -199,7 +194,7 @@ fn draw_collection_search_box(f: &mut Frame<'_, B>, app: &mut App<'_ ); } -fn draw_filter_collections_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_filter_collections_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_input_box_popup( f, area, @@ -208,14 +203,10 @@ fn draw_filter_collections_box(f: &mut Frame<'_, B>, app: &mut App<' ) } -fn draw_search_collection_error_box(f: &mut Frame<'_, B>, _: &mut App<'_>, area: Rect) { +fn draw_search_collection_error_box(f: &mut Frame<'_>, _: &mut App<'_>, area: Rect) { draw_error_message_popup(f, area, "Collection not found!"); } -fn draw_filter_collections_error_box( - f: &mut Frame<'_, B>, - _: &mut App<'_>, - area: Rect, -) { +fn draw_filter_collections_error_box(f: &mut Frame<'_>, _: &mut App<'_>, area: Rect) { draw_error_message_popup(f, area, "No collections found matching the given filter!"); } diff --git a/src/ui/radarr_ui/downloads/mod.rs b/src/ui/radarr_ui/downloads/mod.rs index dccc031..43e61ed 100644 --- a/src/ui/radarr_ui/downloads/mod.rs +++ b/src/ui/radarr_ui/downloads/mod.rs @@ -1,7 +1,6 @@ -use tui::backend::Backend; -use tui::layout::{Constraint, Rect}; -use tui::widgets::{Cell, Row}; -use tui::Frame; +use ratatui::layout::{Constraint, Rect}; +use ratatui::widgets::{Cell, Row}; +use ratatui::Frame; use crate::app::App; use crate::models::radarr_models::DownloadRecord; @@ -26,7 +25,7 @@ impl DrawUi for DownloadsUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { ActiveRadarrBlock::Downloads => draw_downloads(f, app, content_rect), @@ -50,7 +49,7 @@ impl DrawUi for DownloadsUi { } } -fn draw_downloads(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let current_selection = if app.data.radarr_data.downloads.items.is_empty() { DownloadRecord::default() } else { @@ -128,11 +127,7 @@ fn draw_downloads(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rec ); } -fn draw_delete_download_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_delete_download_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { draw_prompt_box( f, prompt_area, @@ -146,11 +141,7 @@ fn draw_delete_download_prompt( ); } -fn draw_update_downloads_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_update_downloads_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { draw_prompt_box( f, prompt_area, diff --git a/src/ui/radarr_ui/indexers/indexer_settings_ui.rs b/src/ui/radarr_ui/indexers/indexer_settings_ui.rs index a18f2c2..eb4b4f7 100644 --- a/src/ui/radarr_ui/indexers/indexer_settings_ui.rs +++ b/src/ui/radarr_ui/indexers/indexer_settings_ui.rs @@ -1,7 +1,6 @@ +use ratatui::layout::{Constraint, Rect}; +use ratatui::Frame; use std::iter; -use tui::backend::Backend; -use tui::layout::{Constraint, Rect}; -use tui::Frame; use crate::app::App; use crate::models::servarr_data::radarr::radarr_data::{ @@ -32,7 +31,7 @@ impl DrawUi for IndexerSettingsUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { draw_popup_over( f, app, @@ -45,11 +44,7 @@ impl DrawUi for IndexerSettingsUi { } } -fn draw_edit_indexer_settings_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { let block = title_block_centered("Configure All Indexer Settings"); let yes_no_value = app.data.radarr_data.prompt_confirm; let selected_block = app.data.radarr_data.selected_block.get_active_block(); diff --git a/src/ui/radarr_ui/indexers/mod.rs b/src/ui/radarr_ui/indexers/mod.rs index b85dbf4..58b4d46 100644 --- a/src/ui/radarr_ui/indexers/mod.rs +++ b/src/ui/radarr_ui/indexers/mod.rs @@ -1,14 +1,14 @@ -use tui::backend::Backend; -use tui::layout::{Constraint, Rect}; -use tui::text::Text; -use tui::widgets::{Cell, Row}; -use tui::Frame; +use ratatui::layout::{Constraint, Rect}; +use ratatui::text::Text; +use ratatui::widgets::{Cell, Row}; +use ratatui::Frame; use crate::app::App; use crate::models::radarr_models::Indexer; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, INDEXERS_BLOCKS}; use crate::models::Route; use crate::ui::radarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi; +use crate::ui::radarr_ui::indexers::test_all_indexers_ui::TestAllIndexersUi; use crate::ui::utils::{layout_block_top_border, style_failure, style_primary, style_success}; use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps}; @@ -17,6 +17,7 @@ mod indexer_settings_ui; #[cfg(test)] #[path = "indexers_ui_tests.rs"] mod indexers_ui_tests; +mod test_all_indexers_ui; pub(super) struct IndexersUi; @@ -29,7 +30,7 @@ impl DrawUi for IndexersUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { let route = *app.get_current_route(); let mut indexers_matchers = |active_radarr_block| match active_radarr_block { ActiveRadarrBlock::Indexers => draw_indexers(f, app, content_rect), @@ -45,6 +46,7 @@ impl DrawUi for IndexersUi { match route { _ if IndexerSettingsUi::accepts(route) => IndexerSettingsUi::draw(f, app, content_rect), + _ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, content_rect), Route::Radarr(active_radarr_block, _) if INDEXERS_BLOCKS.contains(&active_radarr_block) => { indexers_matchers(active_radarr_block) } @@ -53,7 +55,7 @@ impl DrawUi for IndexersUi { } } -fn draw_indexers(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_table( f, area, @@ -125,11 +127,7 @@ fn draw_indexers(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect ) } -fn draw_delete_indexer_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_delete_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { draw_prompt_box( f, prompt_area, diff --git a/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs b/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs new file mode 100644 index 0000000..f7d3649 --- /dev/null +++ b/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs @@ -0,0 +1,95 @@ +use crate::app::context_clues::{build_context_clue_string, BARE_POPUP_CONTEXT_CLUES}; +use crate::app::App; +use crate::models::servarr_data::radarr::modals::IndexerTestResultModalItem; +use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; +use crate::models::Route; +use crate::ui::radarr_ui::indexers::draw_indexers; +use crate::ui::utils::{ + borderless_block, get_width_from_percentage, style_failure, style_success, title_block, +}; +use crate::ui::{ + draw_help_and_get_content_rect, draw_large_popup_over, draw_table, DrawUi, TableProps, +}; +use ratatui::layout::{Constraint, Rect}; +use ratatui::widgets::{Cell, Row}; +use ratatui::Frame; + +#[cfg(test)] +#[path = "test_all_indexers_ui_tests.rs"] +mod test_all_indexers_ui_tests; + +pub(super) struct TestAllIndexersUi; + +impl DrawUi for TestAllIndexersUi { + fn accepts(route: Route) -> bool { + if let Route::Radarr(active_radarr_block, _) = route { + return active_radarr_block == ActiveRadarrBlock::TestAllIndexers; + } + + false + } + + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + draw_large_popup_over( + f, + app, + content_rect, + draw_indexers, + draw_test_all_indexers_test_results, + ); + } +} + +fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { + let current_selection = + if let Some(test_all_results) = app.data.radarr_data.indexer_test_all_results.as_ref() { + test_all_results.current_selection().clone() + } else { + IndexerTestResultModalItem::default() + }; + f.render_widget(title_block("Test All Indexers"), area); + let help = Some(format!( + "<↑↓> scroll | {}", + build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES) + )); + let content_area = draw_help_and_get_content_rect(f, area, help); + + draw_table( + f, + content_area, + borderless_block(), + TableProps { + content: app.data.radarr_data.indexer_test_all_results.as_mut(), + wrapped_content: None, + table_headers: vec!["Indexer", "Pass/Fail", "Failure Messages"], + constraints: vec![ + Constraint::Percentage(20), + Constraint::Percentage(10), + Constraint::Percentage(70), + ], + help: None, + }, + |result| { + result.validation_failures.scroll_left_or_reset( + get_width_from_percentage(area, 86), + *result == current_selection, + app.tick_count % app.ticks_until_scroll == 0, + ); + let pass_fail = if result.is_valid { "✔" } else { "❌" }; + let row_style = if result.is_valid { + style_success() + } else { + style_failure() + }; + + Row::new(vec![ + Cell::from(result.name.to_owned()), + Cell::from(pass_fail.to_owned()), + Cell::from(result.validation_failures.to_string()), + ]) + .style(row_style) + }, + app.is_loading, + true, + ); +} diff --git a/src/ui/radarr_ui/indexers/test_all_indexers_ui_tests.rs b/src/ui/radarr_ui/indexers/test_all_indexers_ui_tests.rs new file mode 100644 index 0000000..66ffea8 --- /dev/null +++ b/src/ui/radarr_ui/indexers/test_all_indexers_ui_tests.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; + use crate::ui::radarr_ui::indexers::test_all_indexers_ui::TestAllIndexersUi; + use crate::ui::DrawUi; + + #[test] + fn test_test_all_indexers_ui_accepts() { + ActiveRadarrBlock::iter().for_each(|active_radarr_block| { + if active_radarr_block == ActiveRadarrBlock::TestAllIndexers { + assert!(TestAllIndexersUi::accepts(active_radarr_block.into())); + } else { + assert!(!TestAllIndexersUi::accepts(active_radarr_block.into())); + } + }); + } +} diff --git a/src/ui/radarr_ui/library/add_movie_ui.rs b/src/ui/radarr_ui/library/add_movie_ui.rs index 95b065e..9d0bc0b 100644 --- a/src/ui/radarr_ui/library/add_movie_ui.rs +++ b/src/ui/radarr_ui/library/add_movie_ui.rs @@ -1,8 +1,7 @@ -use tui::backend::Backend; -use tui::layout::{Alignment, Constraint, Rect}; -use tui::text::Text; -use tui::widgets::{Cell, ListItem, Paragraph, Row}; -use tui::Frame; +use ratatui::layout::{Alignment, Constraint, Rect}; +use ratatui::text::Text; +use ratatui::widgets::{Cell, ListItem, Paragraph, Row}; +use ratatui::Frame; use crate::app::context_clues::{build_context_clue_string, BARE_POPUP_CONTEXT_CLUES}; use crate::app::radarr::radarr_context_clues::ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES; @@ -40,10 +39,10 @@ impl DrawUi for AddMovieUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_add_movie_search_popup = - |f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect| match active_radarr_block { + |f: &mut Frame<'_>, app: &mut App<'_>, area: Rect| match active_radarr_block { ActiveRadarrBlock::AddMovieSearchInput | ActiveRadarrBlock::AddMovieSearchResults | ActiveRadarrBlock::AddMovieEmptySearchResults => { @@ -103,7 +102,7 @@ impl DrawUi for AddMovieUi { } } -fn draw_add_movie_search(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let is_loading = app.is_loading || app.data.radarr_data.add_searched_movies.is_none(); let current_selection = if let Some(add_searched_movies) = app.data.radarr_data.add_searched_movies.as_ref() { @@ -277,7 +276,7 @@ fn draw_add_movie_search(f: &mut Frame<'_, B>, app: &mut App<'_>, ar ); } -fn draw_confirmation_popup(f: &mut Frame<'_, B>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_confirmation_popup(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { ActiveRadarrBlock::AddMovieSelectMonitor => { @@ -324,11 +323,7 @@ fn draw_confirmation_popup(f: &mut Frame<'_, B>, app: &mut App<'_>, } } -fn draw_confirmation_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { let (movie_title, movie_overview) = if let Route::Radarr(_, Some(_)) = app.get_current_route() { ( &app @@ -469,11 +464,7 @@ fn draw_confirmation_prompt( ); } -fn draw_add_movie_select_monitor_popup( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - popup_area: Rect, -) { +fn draw_add_movie_select_monitor_popup(f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect) { draw_selectable_list( f, popup_area, @@ -488,8 +479,8 @@ fn draw_add_movie_select_monitor_popup( ); } -fn draw_add_movie_select_minimum_availability_popup( - f: &mut Frame<'_, B>, +fn draw_add_movie_select_minimum_availability_popup( + f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect, ) { @@ -507,8 +498,8 @@ fn draw_add_movie_select_minimum_availability_popup( ); } -fn draw_add_movie_select_quality_profile_popup( - f: &mut Frame<'_, B>, +fn draw_add_movie_select_quality_profile_popup( + f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect, ) { @@ -526,11 +517,7 @@ fn draw_add_movie_select_quality_profile_popup( ); } -fn draw_add_movie_select_root_folder_popup( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - popup_area: Rect, -) { +fn draw_add_movie_select_root_folder_popup(f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect) { draw_selectable_list( f, popup_area, diff --git a/src/ui/radarr_ui/library/delete_movie_ui.rs b/src/ui/radarr_ui/library/delete_movie_ui.rs index 3ce00a9..cf46bfd 100644 --- a/src/ui/radarr_ui/library/delete_movie_ui.rs +++ b/src/ui/radarr_ui/library/delete_movie_ui.rs @@ -1,6 +1,5 @@ -use tui::backend::Backend; -use tui::layout::Rect; -use tui::Frame; +use ratatui::layout::Rect; +use ratatui::Frame; use crate::app::App; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; @@ -23,39 +22,38 @@ impl DrawUi for DeleteMovieUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { if matches!( *app.get_current_route(), Route::Radarr(ActiveRadarrBlock::DeleteMoviePrompt, _) ) { - let draw_delete_movie_prompt = - |f: &mut Frame<'_, B>, app: &mut App<'_>, prompt_area: Rect| { - let selected_block = app.data.radarr_data.selected_block.get_active_block(); - draw_prompt_box_with_checkboxes( - f, - prompt_area, - "Delete Movie", - format!( - "Do you really want to delete: \n{}?", - app.data.radarr_data.movies.current_selection().title.text - ) - .as_str(), - vec![ - ( - "Delete Movie Files", - app.data.radarr_data.delete_movie_files, - selected_block == &ActiveRadarrBlock::DeleteMovieToggleDeleteFile, - ), - ( - "Add List Exclusion", - app.data.radarr_data.add_list_exclusion, - selected_block == &ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, - ), - ], - selected_block == &ActiveRadarrBlock::DeleteMovieConfirmPrompt, - app.data.radarr_data.prompt_confirm, + let draw_delete_movie_prompt = |f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| { + let selected_block = app.data.radarr_data.selected_block.get_active_block(); + draw_prompt_box_with_checkboxes( + f, + prompt_area, + "Delete Movie", + format!( + "Do you really want to delete: \n{}?", + app.data.radarr_data.movies.current_selection().title.text ) - }; + .as_str(), + vec![ + ( + "Delete Movie Files", + app.data.radarr_data.delete_movie_files, + selected_block == &ActiveRadarrBlock::DeleteMovieToggleDeleteFile, + ), + ( + "Add List Exclusion", + app.data.radarr_data.add_list_exclusion, + selected_block == &ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, + ), + ], + selected_block == &ActiveRadarrBlock::DeleteMovieConfirmPrompt, + app.data.radarr_data.prompt_confirm, + ) + }; draw_prompt_popup_over(f, app, content_rect, draw_library, draw_delete_movie_prompt); } diff --git a/src/ui/radarr_ui/library/edit_movie_ui.rs b/src/ui/radarr_ui/library/edit_movie_ui.rs index f1473fc..66ed4a0 100644 --- a/src/ui/radarr_ui/library/edit_movie_ui.rs +++ b/src/ui/radarr_ui/library/edit_movie_ui.rs @@ -1,7 +1,6 @@ -use tui::backend::Backend; -use tui::layout::{Constraint, Rect}; -use tui::widgets::ListItem; -use tui::Frame; +use ratatui::layout::{Constraint, Rect}; +use ratatui::widgets::ListItem; +use ratatui::Frame; use crate::app::App; use crate::models::servarr_data::radarr::modals::EditMovieModal; @@ -36,10 +35,10 @@ impl DrawUi for EditMovieUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_edit_movie_prompt = - |f: &mut Frame<'_, B>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block { + |f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block { ActiveRadarrBlock::EditMovieSelectMinimumAvailability => { draw_drop_down_popup( f, @@ -73,7 +72,7 @@ impl DrawUi for EditMovieUi { draw_medium_popup_over(f, app, content_rect, draw_library, draw_edit_movie_prompt); } _ if MOVIE_DETAILS_BLOCKS.contains(&context) => { - draw_large_popup_over_background_fn_with_ui::( + draw_large_popup_over_background_fn_with_ui::( f, app, content_rect, @@ -88,11 +87,7 @@ impl DrawUi for EditMovieUi { } } -fn draw_edit_movie_confirmation_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { let (movie_title, movie_overview) = if let Some(filtered_movies) = app.data.radarr_data.filtered_movies.as_ref() { ( @@ -216,8 +211,8 @@ fn draw_edit_movie_confirmation_prompt( ); } -fn draw_edit_movie_select_minimum_availability_popup( - f: &mut Frame<'_, B>, +fn draw_edit_movie_select_minimum_availability_popup( + f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect, ) { @@ -235,8 +230,8 @@ fn draw_edit_movie_select_minimum_availability_popup( ); } -fn draw_edit_movie_select_quality_profile_popup( - f: &mut Frame<'_, B>, +fn draw_edit_movie_select_quality_profile_popup( + f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect, ) { diff --git a/src/ui/radarr_ui/library/mod.rs b/src/ui/radarr_ui/library/mod.rs index 22598e1..f4f2147 100644 --- a/src/ui/radarr_ui/library/mod.rs +++ b/src/ui/radarr_ui/library/mod.rs @@ -1,7 +1,6 @@ -use tui::backend::Backend; -use tui::layout::{Constraint, Rect}; -use tui::widgets::{Cell, Row}; -use tui::Frame; +use ratatui::layout::{Constraint, Rect}; +use ratatui::widgets::{Cell, Row}; +use ratatui::Frame; use crate::app::App; use crate::models::radarr_models::Movie; @@ -43,7 +42,7 @@ impl DrawUi for LibraryUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { let route = *app.get_current_route(); let mut library_ui_matchers = |active_radarr_block: ActiveRadarrBlock| match active_radarr_block { @@ -107,7 +106,7 @@ impl DrawUi for LibraryUi { } } -pub(super) fn draw_library(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +pub(super) fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let current_selection = if let Some(filtered_movies) = app.data.radarr_data.filtered_movies.as_ref() { filtered_movies.current_selection().clone() @@ -206,11 +205,7 @@ pub(super) fn draw_library(f: &mut Frame<'_, B>, app: &mut App<'_>, ); } -fn draw_update_all_movies_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_update_all_movies_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { draw_prompt_box( f, prompt_area, @@ -220,7 +215,7 @@ fn draw_update_all_movies_prompt( ); } -fn draw_movie_search_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_movie_search_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_input_box_popup( f, area, @@ -229,7 +224,7 @@ fn draw_movie_search_box(f: &mut Frame<'_, B>, app: &mut App<'_>, ar ); } -fn draw_filter_movies_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_filter_movies_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_input_box_popup( f, area, @@ -238,10 +233,10 @@ fn draw_filter_movies_box(f: &mut Frame<'_, B>, app: &mut App<'_>, a ) } -fn draw_search_movie_error_box(f: &mut Frame<'_, B>, _: &mut App<'_>, area: Rect) { +fn draw_search_movie_error_box(f: &mut Frame<'_>, _: &mut App<'_>, area: Rect) { draw_error_message_popup(f, area, "Movie not found!"); } -fn draw_filter_movies_error_box(f: &mut Frame<'_, B>, _: &mut App<'_>, area: Rect) { +fn draw_filter_movies_error_box(f: &mut Frame<'_>, _: &mut App<'_>, area: Rect) { draw_error_message_popup(f, area, "No movies found matching the given filter!"); } diff --git a/src/ui/radarr_ui/library/movie_details_ui.rs b/src/ui/radarr_ui/library/movie_details_ui.rs index df65e0d..2cfd55f 100644 --- a/src/ui/radarr_ui/library/movie_details_ui.rs +++ b/src/ui/radarr_ui/library/movie_details_ui.rs @@ -1,11 +1,10 @@ use std::iter; -use tui::backend::Backend; -use tui::layout::{Alignment, Constraint, Rect}; -use tui::style::{Modifier, Style}; -use tui::text::{Line, Span, Text}; -use tui::widgets::{Cell, ListItem, Paragraph, Row, Wrap}; -use tui::Frame; +use ratatui::layout::{Alignment, Constraint, Rect}; +use ratatui::style::{Modifier, Style}; +use ratatui::text::{Line, Span, Text}; +use ratatui::widgets::{Cell, ListItem, Paragraph, Row, Wrap}; +use ratatui::Frame; use crate::app::App; use crate::models::radarr_models::{Credit, MovieHistoryItem, Release, ReleaseField}; @@ -39,9 +38,9 @@ impl DrawUi for MovieDetailsUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { - let draw_movie_info_popup = |f: &mut Frame<'_, B>, app: &mut App<'_>, popup_area: Rect| { + let draw_movie_info_popup = |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| { let (content_area, _) = draw_tabs( f, popup_area, @@ -100,7 +99,7 @@ impl DrawUi for MovieDetailsUi { } } -fn draw_movie_info(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_movie_info(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if let Route::Radarr(active_radarr_block, _) = app.data.radarr_data.movie_info_tabs.get_active_route() { @@ -116,11 +115,7 @@ fn draw_movie_info(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Re } } -fn draw_search_movie_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_search_movie_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { draw_prompt_box( f, prompt_area, @@ -134,11 +129,7 @@ fn draw_search_movie_prompt( ); } -fn draw_update_and_scan_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_update_and_scan_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { draw_prompt_box( f, prompt_area, @@ -152,7 +143,7 @@ fn draw_update_and_scan_prompt( ); } -fn draw_file_info(f: &mut Frame<'_, B>, app: &App<'_>, content_area: Rect) { +fn draw_file_info(f: &mut Frame<'_>, app: &App<'_>, content_area: Rect) { match app.data.radarr_data.movie_details_modal.as_ref() { Some(movie_details_modal) if !movie_details_modal.file_details.is_empty() && !app.is_loading => @@ -210,7 +201,7 @@ fn draw_file_info(f: &mut Frame<'_, B>, app: &App<'_>, content_area: } } -fn draw_movie_details(f: &mut Frame<'_, B>, app: &App<'_>, content_area: Rect) { +fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, content_area: Rect) { let block = layout_block_top_border(); match app.data.radarr_data.movie_details_modal.as_ref() { @@ -253,7 +244,7 @@ fn draw_movie_details(f: &mut Frame<'_, B>, app: &App<'_>, content_a } } -fn draw_movie_history(f: &mut Frame<'_, B>, app: &mut App<'_>, content_area: Rect) { +fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { if let Some(movie_details_modal) = app.data.radarr_data.movie_details_modal.as_mut() { let current_selection = if movie_details_modal.movie_history.items.is_empty() { MovieHistoryItem::default() @@ -321,7 +312,7 @@ fn draw_movie_history(f: &mut Frame<'_, B>, app: &mut App<'_>, conte } } -fn draw_movie_cast(f: &mut Frame<'_, B>, app: &mut App<'_>, content_area: Rect) { +fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { draw_table( f, content_area, @@ -363,7 +354,7 @@ fn draw_movie_cast(f: &mut Frame<'_, B>, app: &mut App<'_>, content_ ); } -fn draw_movie_crew(f: &mut Frame<'_, B>, app: &mut App<'_>, content_area: Rect) { +fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { draw_table( f, content_area, @@ -407,7 +398,7 @@ fn draw_movie_crew(f: &mut Frame<'_, B>, app: &mut App<'_>, content_ ); } -fn draw_movie_releases(f: &mut Frame<'_, B>, app: &mut App<'_>, content_area: Rect) { +fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { let (current_selection, is_empty, sort_ascending) = match app.data.radarr_data.movie_details_modal.as_ref() { Some(movie_details_modal) if !movie_details_modal.movie_releases.items.is_empty() => ( @@ -549,11 +540,7 @@ fn draw_movie_releases(f: &mut Frame<'_, B>, app: &mut App<'_>, cont ); } -fn draw_manual_search_confirm_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { let current_selection = app .data .radarr_data diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index 0de6756..cfa77c9 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -1,12 +1,11 @@ use std::iter; use chrono::{Duration, Utc}; -use tui::backend::Backend; -use tui::layout::{Alignment, Constraint, Rect}; -use tui::style::{Color, Style}; -use tui::text::Text; -use tui::widgets::Paragraph; -use tui::Frame; +use ratatui::layout::{Alignment, Constraint, Rect}; +use ratatui::style::{Color, Style}; +use ratatui::text::Text; +use ratatui::widgets::Paragraph; +use ratatui::Frame; use crate::app::App; use crate::logos::RADARR_LOGO; @@ -48,7 +47,7 @@ impl DrawUi for RadarrUi { matches!(route, Route::Radarr(_, _)) } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let (content_rect, _) = draw_tabs(f, area, "Movies", &app.data.radarr_data.main_tabs); let route = *app.get_current_route(); @@ -63,7 +62,7 @@ impl DrawUi for RadarrUi { } } - fn draw_context_row(f: &mut Frame<'_, B>, app: &App<'_>, area: Rect) { + fn draw_context_row(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { let chunks = horizontal_chunks(vec![Constraint::Min(0), Constraint::Length(20)], area); let context_chunks = horizontal_chunks( @@ -77,7 +76,7 @@ impl DrawUi for RadarrUi { } } -fn draw_stats_context(f: &mut Frame<'_, B>, app: &App<'_>, area: Rect) { +fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { let block = title_block("Stats"); if !app.data.radarr_data.version.is_empty() { @@ -169,7 +168,7 @@ fn draw_stats_context(f: &mut Frame<'_, B>, app: &App<'_>, area: Rec } } -fn draw_downloads_context(f: &mut Frame<'_, B>, app: &App<'_>, area: Rect) { +fn draw_downloads_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { let block = title_block("Downloads"); let downloads_vec = &app.data.radarr_data.downloads.items; @@ -224,7 +223,7 @@ fn determine_row_style(downloads_vec: &[DownloadRecord], movie: &Movie) -> Style } } -fn draw_radarr_logo(f: &mut Frame<'_, B>, area: Rect) { +fn draw_radarr_logo(f: &mut Frame<'_>, area: Rect) { let mut logo_text = Text::from(RADARR_LOGO); logo_text.patch_style(Style::default().fg(Color::LightYellow)); let logo = Paragraph::new(logo_text) diff --git a/src/ui/radarr_ui/radarr_ui_utils.rs b/src/ui/radarr_ui/radarr_ui_utils.rs index 7bc8fa7..17de238 100644 --- a/src/ui/radarr_ui/radarr_ui_utils.rs +++ b/src/ui/radarr_ui/radarr_ui_utils.rs @@ -1,5 +1,5 @@ use crate::ui::utils::{style_default, style_failure, style_secondary}; -use tui::style::{Color, Modifier, Style}; +use ratatui::style::{Color, Modifier, Style}; #[cfg(test)] #[path = "radarr_ui_utils_tests.rs"] diff --git a/src/ui/radarr_ui/root_folders/mod.rs b/src/ui/radarr_ui/root_folders/mod.rs index b4eae24..47c1cdf 100644 --- a/src/ui/radarr_ui/root_folders/mod.rs +++ b/src/ui/radarr_ui/root_folders/mod.rs @@ -1,7 +1,6 @@ -use tui::backend::Backend; -use tui::layout::{Constraint, Rect}; -use tui::widgets::{Cell, Row}; -use tui::Frame; +use ratatui::layout::{Constraint, Rect}; +use ratatui::widgets::{Cell, Row}; +use ratatui::Frame; use crate::app::App; use crate::models::radarr_models::RootFolder; @@ -29,7 +28,7 @@ impl DrawUi for RootFoldersUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { ActiveRadarrBlock::RootFolders => draw_root_folders(f, app, content_rect), @@ -55,7 +54,7 @@ impl DrawUi for RootFoldersUi { } } -fn draw_root_folders(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_root_folders(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_table( f, area, @@ -103,11 +102,7 @@ fn draw_root_folders(f: &mut Frame<'_, B>, app: &mut App<'_>, area: ); } -fn draw_add_root_folder_prompt_box( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - area: Rect, -) { +fn draw_add_root_folder_prompt_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_input_box_popup( f, area, @@ -116,11 +111,7 @@ fn draw_add_root_folder_prompt_box( ); } -fn draw_delete_root_folder_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_delete_root_folder_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { draw_prompt_box( f, prompt_area, diff --git a/src/ui/radarr_ui/system/mod.rs b/src/ui/radarr_ui/system/mod.rs index 4e7a626..523dc28 100644 --- a/src/ui/radarr_ui/system/mod.rs +++ b/src/ui/radarr_ui/system/mod.rs @@ -1,11 +1,10 @@ use std::ops::Sub; use chrono::Utc; -use tui::layout::Alignment; -use tui::text::{Span, Text}; -use tui::widgets::{Cell, Paragraph, Row}; -use tui::{ - backend::Backend, +use ratatui::layout::Alignment; +use ratatui::text::{Span, Text}; +use ratatui::widgets::{Cell, Paragraph, Row}; +use ratatui::{ layout::{Constraint, Rect}, widgets::ListItem, Frame, @@ -62,7 +61,7 @@ impl DrawUi for SystemUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { let route = *app.get_current_route(); match route { @@ -75,11 +74,7 @@ impl DrawUi for SystemUi { } } -pub(super) fn draw_system_ui_layout( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - area: Rect, -) { +pub(super) fn draw_system_ui_layout(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let vertical_chunks = vertical_chunks( vec![ Constraint::Ratio(1, 2), @@ -100,7 +95,7 @@ pub(super) fn draw_system_ui_layout( draw_help(f, app, vertical_chunks[2]); } -fn draw_tasks(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_tasks(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_table( f, area, @@ -129,7 +124,7 @@ fn draw_tasks(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { ); } -pub(super) fn draw_queued_events(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +pub(super) fn draw_queued_events(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_table( f, area, @@ -189,7 +184,7 @@ pub(super) fn draw_queued_events(f: &mut Frame<'_, B>, app: &mut App ); } -fn draw_logs(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_list_box( f, area, @@ -210,7 +205,7 @@ fn draw_logs(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { ); } -fn draw_help(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_help(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let mut help_text = Text::from(format!( " {}", app diff --git a/src/ui/radarr_ui/system/system_details_ui.rs b/src/ui/radarr_ui/system/system_details_ui.rs index 561fdf6..82d6b34 100644 --- a/src/ui/radarr_ui/system/system_details_ui.rs +++ b/src/ui/radarr_ui/system/system_details_ui.rs @@ -1,8 +1,7 @@ -use tui::backend::Backend; -use tui::layout::Rect; -use tui::text::{Span, Text}; -use tui::widgets::{Cell, ListItem, Paragraph, Row}; -use tui::Frame; +use ratatui::layout::Rect; +use ratatui::text::{Span, Text}; +use ratatui::widgets::{Cell, ListItem, Paragraph, Row}; +use ratatui::Frame; use crate::app::context_clues::{build_context_clue_string, BARE_POPUP_CONTEXT_CLUES}; use crate::app::radarr::radarr_context_clues::SYSTEM_TASKS_CONTEXT_CLUES; @@ -35,7 +34,7 @@ impl DrawUi for SystemDetailsUi { false } - fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { ActiveRadarrBlock::SystemLogs => { @@ -70,7 +69,7 @@ impl DrawUi for SystemDetailsUi { } } -fn draw_logs_popup(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_logs_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_list_box( f, area, @@ -94,8 +93,8 @@ fn draw_logs_popup(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Re ); } -fn draw_tasks_popup(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { - let tasks_popup_table = |f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect| { +fn draw_tasks_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { + let tasks_popup_table = |f: &mut Frame<'_>, app: &mut App<'_>, area: Rect| { f.render_widget(title_block("Tasks"), area); let context_area = draw_help_and_get_content_rect( @@ -142,7 +141,7 @@ fn draw_tasks_popup(f: &mut Frame<'_, B>, app: &mut App<'_>, area: R } } -fn draw_start_task_prompt(f: &mut Frame<'_, B>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_start_task_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { draw_prompt_box( f, prompt_area, @@ -156,7 +155,7 @@ fn draw_start_task_prompt(f: &mut Frame<'_, B>, app: &mut App<'_>, p ); } -fn draw_updates_popup(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { +fn draw_updates_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { f.render_widget(title_block("Updates"), area); let content_rect = draw_help_and_get_content_rect( diff --git a/src/ui/utils.rs b/src/ui/utils.rs index 410cb51..f3a5d56 100644 --- a/src/ui/utils.rs +++ b/src/ui/utils.rs @@ -1,10 +1,9 @@ +use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; +use ratatui::style::{Color, Modifier, Style}; +use ratatui::text::{Line, Span, Text}; +use ratatui::widgets::{Block, BorderType, Borders, LineGauge, Paragraph, Wrap}; +use ratatui::{symbols, Frame}; use std::rc::Rc; -use tui::backend::Backend; -use tui::layout::{Alignment, Constraint, Direction, Layout, Rect}; -use tui::style::{Color, Modifier, Style}; -use tui::text::{Line, Span, Text}; -use tui::widgets::{Block, BorderType, Borders, LineGauge, Paragraph, Wrap}; -use tui::{symbols, Frame}; pub const COLOR_TEAL: Color = Color::Rgb(35, 50, 55); pub const COLOR_CYAN: Color = Color::Cyan; @@ -268,7 +267,7 @@ pub fn line_gauge_with_label(title: &str, ratio: f64) -> LineGauge<'_> { .label(Line::from(format!("{title}: {:.0}%", ratio * 100.0))) } -pub fn show_cursor(f: &mut Frame<'_, B>, area: Rect, offset: usize, string: &str) { +pub fn show_cursor(f: &mut Frame<'_>, area: Rect, offset: usize, string: &str) { f.set_cursor(area.x + (string.len() - offset) as u16 + 1, area.y + 1); } diff --git a/src/ui/utils_tests.rs b/src/ui/utils_tests.rs index f44385a..b309b44 100644 --- a/src/ui/utils_tests.rs +++ b/src/ui/utils_tests.rs @@ -1,10 +1,10 @@ #[cfg(test)] mod test { use pretty_assertions::assert_eq; - use tui::layout::{Alignment, Constraint, Direction, Layout, Rect}; - use tui::style::{Color, Modifier, Style}; - use tui::text::{Line, Span}; - use tui::widgets::{Block, BorderType, Borders}; + use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; + use ratatui::style::{Color, Modifier, Style}; + use ratatui::text::{Line, Span}; + use ratatui::widgets::{Block, BorderType, Borders}; use crate::ui::utils::{ borderless_block, centered_rect, get_width_from_percentage, horizontal_chunks,