diff --git a/Cargo.toml b/Cargo.toml index 706caa2..90dd92b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "managarr" -version = "0.0.26" +version = "0.0.27" authors = ["Alex Clarke "] description = "A TUI for managing *arr servers" keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui", "terminal"] diff --git a/src/handlers/radarr_handlers/collections/collections_handler_tests.rs b/src/handlers/radarr_handlers/collections/collections_handler_tests.rs index 2e475b6..bd4c800 100644 --- a/src/handlers/radarr_handlers/collections/collections_handler_tests.rs +++ b/src/handlers/radarr_handlers/collections/collections_handler_tests.rs @@ -370,6 +370,8 @@ mod tests { #[test] fn test_search_collections_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); app .data .radarr_data @@ -398,11 +400,56 @@ mod tests { .text, "Test 2" ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + } + + #[test] + fn test_search_collections_submit_error_on_no_search_hits() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); + app + .data + .radarr_data + .collections + .set_items(extended_stateful_iterable_vec!( + Collection, + HorizontallyScrollableText + )); + app.data.radarr_data.search = Some("Test 5".into()); + + CollectionsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::SearchCollection, + &None, + ) + .handle(); + + assert_str_eq!( + app + .data + .radarr_data + .collections + .current_selection() + .title + .text, + "Test 1" + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::SearchCollectionError.into() + ); } #[test] fn test_search_filtered_collections_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); app .data .radarr_data @@ -431,11 +478,17 @@ mod tests { .text, "Test 2" ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); } #[test] fn test_filter_collections_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::FilterCollections.into()); app .data .radarr_data @@ -465,6 +518,40 @@ mod tests { .text, "Test 1" ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::Collections.into() + ); + } + + #[test] + fn test_filter_collections_submit_error_on_no_filter_matches() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.push_navigation_stack(ActiveRadarrBlock::FilterCollections.into()); + app + .data + .radarr_data + .collections + .set_items(extended_stateful_iterable_vec!( + Collection, + HorizontallyScrollableText + )); + app.data.radarr_data.filter = Some("Test 5".into()); + + CollectionsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::FilterCollections, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.filtered_collections.items.is_empty()); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::FilterCollectionsError.into() + ); } #[test] @@ -526,21 +613,21 @@ mod tests { const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; - #[test] - fn test_search_collection_block_esc() { + #[rstest] + fn test_search_collection_block_esc( + #[values( + ActiveRadarrBlock::SearchCollection, + ActiveRadarrBlock::SearchCollectionError + )] + active_radarr_block: ActiveRadarrBlock, + ) { let mut app = App::default(); app.should_ignore_quit_key = true; app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); - app.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); + app.push_navigation_stack(active_radarr_block.into()); app.data.radarr_data = create_test_radarr_data(); - CollectionsHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::SearchCollection, - &None, - ) - .handle(); + CollectionsHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); assert_eq!( app.get_current_route(), @@ -550,21 +637,21 @@ mod tests { assert_search_reset!(app.data.radarr_data); } - #[test] - fn test_filter_collections_block_esc() { + #[rstest] + fn test_filter_collections_block_esc( + #[values( + ActiveRadarrBlock::FilterCollections, + ActiveRadarrBlock::FilterCollectionsError + )] + active_radarr_block: ActiveRadarrBlock, + ) { let mut app = App::default(); app.should_ignore_quit_key = true; app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); - app.push_navigation_stack(ActiveRadarrBlock::FilterCollections.into()); + app.push_navigation_stack(active_radarr_block.into()); app.data.radarr_data = create_test_radarr_data(); - CollectionsHandler::with( - &ESC_KEY, - &mut app, - &ActiveRadarrBlock::FilterCollections, - &None, - ) - .handle(); + CollectionsHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); assert_eq!( app.get_current_route(), @@ -675,6 +762,31 @@ mod tests { assert!(app.data.radarr_data.filter.is_some()); } + #[test] + fn test_filter_collections_key_resets_previous_filter() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); + app.data.radarr_data = create_test_radarr_data(); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.filter.key, + &mut app, + &ActiveRadarrBlock::Collections, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::FilterCollections.into() + ); + assert!(app.data.radarr_data.is_filtering); + assert!(app.should_ignore_quit_key); + assert!(app.data.radarr_data.filter.is_some()); + assert!(app.data.radarr_data.filtered_collections.items.is_empty()); + } + #[test] fn test_collection_edit_key() { test_edit_collection_key!( diff --git a/src/handlers/radarr_handlers/collections/mod.rs b/src/handlers/radarr_handlers/collections/mod.rs index 54ce297..92410f1 100644 --- a/src/handlers/radarr_handlers/collections/mod.rs +++ b/src/handlers/radarr_handlers/collections/mod.rs @@ -3,16 +3,15 @@ use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler; use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler; -use crate::handlers::radarr_handlers::{ - filter_table, handle_change_tab_left_right_keys, search_table, -}; +use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, COLLECTIONS_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS, }; use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable}; use crate::network::radarr_network::RadarrEvent; -use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; +use crate::utils::strip_non_search_characters; +use crate::{filter_table, handle_text_box_keys, handle_text_box_left_right_keys, search_table}; mod collection_details_handler; mod edit_collection_handler; @@ -221,46 +220,26 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' .items .is_empty() { - let selected_index = search_table( + search_table!( self.app, - &self.app.data.radarr_data.collections.items.clone(), - |collection| &collection.title.text, + collections, + ActiveRadarrBlock::SearchCollectionError ); - self - .app - .data - .radarr_data - .collections - .select_index(selected_index); } else { - let selected_index = search_table( + search_table!( self.app, - &self.app.data.radarr_data.filtered_collections.items.clone(), - |collection| &collection.title.text, + filtered_collections, + ActiveRadarrBlock::SearchCollectionError ); - self - .app - .data - .radarr_data - .filtered_collections - .select_index(selected_index); } } ActiveRadarrBlock::FilterCollections => { - let filtered_collections = filter_table( + filter_table!( self.app, - &self.app.data.radarr_data.collections.items.clone(), - |collection| &collection.title.text, + collections, + filtered_collections, + ActiveRadarrBlock::FilterCollectionsError ); - - if !filtered_collections.is_empty() { - self - .app - .data - .radarr_data - .filtered_collections - .set_items(filtered_collections); - } } ActiveRadarrBlock::UpdateAllCollectionsPrompt => { if self.app.data.radarr_data.prompt_confirm { @@ -275,12 +254,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' fn handle_esc(&mut self) { match self.active_radarr_block { - ActiveRadarrBlock::FilterCollections => { + ActiveRadarrBlock::FilterCollections | ActiveRadarrBlock::FilterCollectionsError => { self.app.pop_navigation_stack(); self.app.data.radarr_data.reset_filter(); self.app.should_ignore_quit_key = false; } - ActiveRadarrBlock::SearchCollection => { + ActiveRadarrBlock::SearchCollection | ActiveRadarrBlock::SearchCollectionError => { self.app.pop_navigation_stack(); self.app.data.radarr_data.reset_search(); self.app.should_ignore_quit_key = false; @@ -313,6 +292,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' self .app .push_navigation_stack(ActiveRadarrBlock::FilterCollections.into()); + self.app.data.radarr_data.reset_filter(); self.app.data.radarr_data.filter = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.is_filtering = true; self.app.should_ignore_quit_key = true; diff --git a/src/handlers/radarr_handlers/library/library_handler_tests.rs b/src/handlers/radarr_handlers/library/library_handler_tests.rs index 76f82c3..8fda17e 100644 --- a/src/handlers/radarr_handlers/library/library_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/library_handler_tests.rs @@ -388,6 +388,8 @@ mod tests { #[test] fn test_search_movie_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); app .data .radarr_data @@ -410,11 +412,47 @@ mod tests { app.data.radarr_data.movies.current_selection().title.text, "Test 2" ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + } + + #[test] + fn test_search_movie_submit_error_on_no_search_hits() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.search = Some("Test 5".into()); + + LibraryHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::SearchMovie, + &None, + ) + .handle(); + + assert_str_eq!( + app.data.radarr_data.movies.current_selection().title.text, + "Test 1" + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::SearchMovieError.into() + ); } #[test] fn test_search_filtered_movies_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); app .data .radarr_data @@ -443,11 +481,14 @@ mod tests { .text, "Test 2" ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); } #[test] fn test_filter_movies_submit() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); app .data .radarr_data @@ -477,6 +518,37 @@ mod tests { .text, "Test 1" ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + } + + #[test] + fn test_filter_movies_submit_error_on_no_filter_matches() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.filter = Some("Test 5".into()); + + LibraryHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::FilterMovies, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.filtered_movies.items.is_empty()); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::FilterMoviesError.into() + ); } #[test] @@ -532,30 +604,36 @@ mod tests { const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; - #[test] - fn test_search_movie_block_esc() { + #[rstest] + fn test_search_movie_block_esc( + #[values(ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchMovieError)] + active_radarr_block: ActiveRadarrBlock, + ) { let mut app = App::default(); app.should_ignore_quit_key = true; app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); - app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); + app.push_navigation_stack(active_radarr_block.into()); app.data.radarr_data = create_test_radarr_data(); - LibraryHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::SearchMovie, &None).handle(); + LibraryHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert!(!app.should_ignore_quit_key); assert_search_reset!(app.data.radarr_data); } - #[test] - fn test_filter_movies_block_esc() { + #[rstest] + fn test_filter_movies_block_esc( + #[values(ActiveRadarrBlock::FilterMovies, ActiveRadarrBlock::FilterMoviesError)] + active_radarr_block: ActiveRadarrBlock, + ) { let mut app = App::default(); app.should_ignore_quit_key = true; app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); - app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); + app.push_navigation_stack(active_radarr_block.into()); app.data.radarr_data = create_test_radarr_data(); - LibraryHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::FilterMovies, &None).handle(); + LibraryHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert!(!app.should_ignore_quit_key); @@ -657,6 +735,31 @@ mod tests { assert!(app.data.radarr_data.filter.is_some()); } + #[test] + fn test_filter_movies_key_resets_previous_filter() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.data.radarr_data = create_test_radarr_data(); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.filter.key, + &mut app, + &ActiveRadarrBlock::Movies, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::FilterMovies.into() + ); + assert!(app.data.radarr_data.is_filtering); + assert!(app.should_ignore_quit_key); + assert!(app.data.radarr_data.filter.is_some()); + assert!(app.data.radarr_data.filtered_movies.items.is_empty()); + } + #[test] fn test_movie_add_key() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/library/mod.rs b/src/handlers/radarr_handlers/library/mod.rs index 185ca33..5d6c164 100644 --- a/src/handlers/radarr_handlers/library/mod.rs +++ b/src/handlers/radarr_handlers/library/mod.rs @@ -1,20 +1,20 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; +use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::library::add_movie_handler::AddMovieHandler; use crate::handlers::radarr_handlers::library::delete_movie_handler::DeleteMovieHandler; use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHandler; use crate::handlers::radarr_handlers::library::movie_details_handler::MovieDetailsHandler; -use crate::handlers::radarr_handlers::{ - filter_table, handle_change_tab_left_right_keys, search_table, -}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; + use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, DELETE_MOVIE_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, LIBRARY_BLOCKS, }; use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable}; use crate::network::radarr_network::RadarrEvent; -use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; +use crate::utils::strip_non_search_characters; +use crate::{filter_table, handle_text_box_keys, handle_text_box_left_right_keys, search_table}; mod add_movie_handler; mod delete_movie_handler; @@ -200,46 +200,22 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' .push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()), ActiveRadarrBlock::SearchMovie => { if self.app.data.radarr_data.filtered_movies.items.is_empty() { - let selected_index = search_table( - self.app, - &self.app.data.radarr_data.movies.items.clone(), - |movie| &movie.title.text, - ); - self - .app - .data - .radarr_data - .movies - .select_index(selected_index); + search_table!(self.app, movies, ActiveRadarrBlock::SearchMovieError); } else { - let selected_index = search_table( + search_table!( self.app, - &self.app.data.radarr_data.filtered_movies.items.clone(), - |movie| &movie.title.text, + filtered_movies, + ActiveRadarrBlock::SearchMovieError ); - self - .app - .data - .radarr_data - .filtered_movies - .select_index(selected_index); - }; + } } ActiveRadarrBlock::FilterMovies => { - let filtered_movies = filter_table( + filter_table!( self.app, - &self.app.data.radarr_data.movies.items.clone(), - |movie| &movie.title.text, + movies, + filtered_movies, + ActiveRadarrBlock::FilterMoviesError ); - - if !filtered_movies.is_empty() { - self - .app - .data - .radarr_data - .filtered_movies - .set_items(filtered_movies); - } } ActiveRadarrBlock::UpdateAllMoviesPrompt => { if self.app.data.radarr_data.prompt_confirm { @@ -254,12 +230,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' fn handle_esc(&mut self) { match self.active_radarr_block { - ActiveRadarrBlock::FilterMovies => { + ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterMoviesError => { self.app.pop_navigation_stack(); self.app.data.radarr_data.reset_filter(); self.app.should_ignore_quit_key = false; } - ActiveRadarrBlock::SearchMovie => { + ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchMovieError => { self.app.pop_navigation_stack(); self.app.data.radarr_data.reset_search(); self.app.should_ignore_quit_key = false; @@ -292,6 +268,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' self .app .push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); + self.app.data.radarr_data.reset_filter(); self.app.data.radarr_data.filter = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.is_filtering = true; self.app.should_ignore_quit_key = true; diff --git a/src/handlers/radarr_handlers/mod.rs b/src/handlers/radarr_handlers/mod.rs index 4cf1f90..7e7c2f5 100644 --- a/src/handlers/radarr_handlers/mod.rs +++ b/src/handlers/radarr_handlers/mod.rs @@ -7,7 +7,6 @@ use crate::handlers::radarr_handlers::root_folders::RootFoldersHandler; use crate::handlers::radarr_handlers::system::SystemHandler; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; -use crate::utils::strip_non_search_characters; use crate::{App, Key}; mod collections; @@ -100,87 +99,6 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b fn handle_char_key_event(&mut self) {} } -pub fn search_table(app: &mut App<'_>, rows: &[T], field_selection_fn: F) -> Option -where - F: Fn(&T) -> &str, -{ - let search_index = if app.data.radarr_data.search.is_some() { - let search_string = app - .data - .radarr_data - .search - .as_ref() - .unwrap() - .text - .clone() - .to_lowercase(); - - app.data.radarr_data.search = None; - - rows.iter().position(|item| { - strip_non_search_characters(field_selection_fn(item)).contains(&search_string) - }) - } else { - None - }; - - app.data.radarr_data.is_searching = false; - app.should_ignore_quit_key = false; - - if search_index.is_some() { - app.pop_navigation_stack(); - } - - search_index -} - -pub fn filter_table(app: &mut App<'_>, rows: &[T], field_selection_fn: F) -> Vec -where - F: Fn(&T) -> &str, - T: Clone, -{ - let empty_filter = app.data.radarr_data.filter.is_some() - && app - .data - .radarr_data - .filter - .as_ref() - .unwrap() - .text - .is_empty(); - let filter_matches = if app.data.radarr_data.filter.is_some() - && !app - .data - .radarr_data - .filter - .as_ref() - .unwrap() - .text - .is_empty() - { - let filter = - strip_non_search_characters(&app.data.radarr_data.filter.as_ref().unwrap().text.clone()); - - rows - .iter() - .filter(|&item| strip_non_search_characters(field_selection_fn(item)).contains(&filter)) - .cloned() - .collect() - } else { - Vec::new() - }; - - app.data.radarr_data.filter = None; - app.data.radarr_data.is_filtering = false; - app.should_ignore_quit_key = false; - - if !filter_matches.is_empty() || empty_filter { - app.pop_navigation_stack(); - } - - filter_matches -} - pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: &Key) { let key_ref = key; match key_ref { @@ -195,3 +113,99 @@ pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: &Key) { _ => (), } } + +#[macro_export] +macro_rules! search_table { + ($app:expr, $data_ref:ident, $error_block:expr) => { + let search_index = if $app.data.radarr_data.search.is_some() { + let search_string = $app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .text + .clone() + .to_lowercase(); + + $app.data.radarr_data.search = None; + + $app + .data + .radarr_data + .$data_ref + .items + .iter() + .position(|item| strip_non_search_characters(&item.title.text).contains(&search_string)) + } else { + None + }; + + $app.data.radarr_data.is_searching = false; + $app.should_ignore_quit_key = false; + + if search_index.is_some() { + $app.pop_navigation_stack(); + $app.data.radarr_data.$data_ref.select_index(search_index); + } else { + $app.pop_and_push_navigation_stack($error_block.into()); + } + }; +} + +#[macro_export] +macro_rules! filter_table { + ($app:expr, $source_table_ref:ident, $filter_table_ref:ident, $error_block:expr) => { + let empty_filter = $app.data.radarr_data.filter.is_some() + && $app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .text + .is_empty(); + let filter_matches = if $app.data.radarr_data.filter.is_some() + && !$app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .text + .is_empty() + { + let filter = + strip_non_search_characters(&$app.data.radarr_data.filter.as_ref().unwrap().text.clone()); + + $app + .data + .radarr_data + .$source_table_ref + .items + .iter() + .filter(|item| strip_non_search_characters(&item.title.text).contains(&filter)) + .cloned() + .collect() + } else { + Vec::new() + }; + + $app.data.radarr_data.filter = None; + $app.data.radarr_data.is_filtering = false; + $app.should_ignore_quit_key = false; + + if filter_matches.is_empty() && !empty_filter { + $app.pop_and_push_navigation_stack($error_block.into()); + } else if empty_filter { + $app.pop_navigation_stack(); + } else { + $app.pop_navigation_stack(); + $app + .data + .radarr_data + .$filter_table_ref + .set_items(filter_matches); + } + }; +} diff --git a/src/handlers/radarr_handlers/radarr_handler_tests.rs b/src/handlers/radarr_handlers/radarr_handler_tests.rs index 2a13194..37c4a17 100644 --- a/src/handlers/radarr_handlers/radarr_handler_tests.rs +++ b/src/handlers/radarr_handlers/radarr_handler_tests.rs @@ -6,18 +6,21 @@ mod tests { use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; - use crate::handlers::radarr_handlers::{ - filter_table, handle_change_tab_left_right_keys, search_table, RadarrHandler, - }; + use crate::handlers::radarr_handlers::{handle_change_tab_left_right_keys, RadarrHandler}; use crate::handlers::KeyEventHandler; use crate::models::radarr_models::Movie; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::HorizontallyScrollableText; - use crate::{extended_stateful_iterable_vec, test_handler_delegation}; + use crate::utils::strip_non_search_characters; + use crate::{ + extended_stateful_iterable_vec, filter_table, search_table, test_handler_delegation, + }; #[test] - fn test_search_table() { + fn test_search_table_macro() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); app .data .radarr_data @@ -29,13 +32,13 @@ mod tests { app.data.radarr_data.search = Some("Test 2".into()); app.data.radarr_data.is_searching = true; app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); - let movies = &app.data.radarr_data.movies.items.clone(); + search_table!(app, movies, ActiveRadarrBlock::SearchMovieError); - let index = search_table(&mut app, movies, |movie| &movie.title.text); - - assert_eq!(index, Some(1)); + assert_str_eq!( + app.data.radarr_data.movies.current_selection().title.text, + "Test 2" + ); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert!(!app.data.radarr_data.is_searching); assert!(!app.should_ignore_quit_key); @@ -43,8 +46,39 @@ mod tests { } #[test] - fn test_search_table_no_search_hits() { + fn test_search_table_macro_empty_search() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.search = Some("".into()); + app.data.radarr_data.is_searching = true; + app.should_ignore_quit_key = true; + + search_table!(app, movies, ActiveRadarrBlock::SearchMovieError); + + assert_str_eq!( + app.data.radarr_data.movies.current_selection().title.text, + "Test 1" + ); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert!(!app.data.radarr_data.is_searching); + assert!(!app.should_ignore_quit_key); + assert!(app.data.radarr_data.search.is_none()); + } + + #[test] + fn test_search_table_macro_error_on_no_search_hits() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); app .data .radarr_data @@ -56,16 +90,16 @@ mod tests { app.data.radarr_data.search = Some("Test 5".into()); app.data.radarr_data.is_searching = true; app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); - let movies = &app.data.radarr_data.movies.items.clone(); + search_table!(app, movies, ActiveRadarrBlock::SearchMovieError); - let index = search_table(&mut app, movies, |movie| &movie.title.text); - - assert_eq!(index, None); + assert_str_eq!( + app.data.radarr_data.movies.current_selection().title.text, + "Test 1" + ); assert_eq!( app.get_current_route(), - &ActiveRadarrBlock::SearchMovie.into() + &ActiveRadarrBlock::SearchMovieError.into() ); assert!(!app.data.radarr_data.is_searching); assert!(!app.should_ignore_quit_key); @@ -73,8 +107,10 @@ mod tests { } #[test] - fn test_filter_table() { + fn test_filter_table_macro() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); app .data .radarr_data @@ -86,14 +122,19 @@ mod tests { app.data.radarr_data.filter = Some("Test 2".into()); app.data.radarr_data.is_filtering = true; app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); - let movies = &app.data.radarr_data.movies.items.clone(); + filter_table!( + app, + movies, + filtered_movies, + ActiveRadarrBlock::FilterMoviesError + ); - let filter_matches = filter_table(&mut app, movies, |movie| &movie.title.text); - - assert_eq!(filter_matches.len(), 1); - assert_str_eq!(filter_matches[0].title.text, "Test 2"); + assert_eq!(app.data.radarr_data.filtered_movies.items.len(), 1); + assert_str_eq!( + app.data.radarr_data.filtered_movies.items[0].title.text, + "Test 2" + ); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert!(!app.data.radarr_data.is_filtering); assert!(!app.should_ignore_quit_key); @@ -101,38 +142,10 @@ mod tests { } #[test] - fn test_filter_table_no_filter_matches() { + fn test_filter_table_macro_reset_and_pop_on_empty_filter() { let mut app = App::default(); - app - .data - .radarr_data - .movies - .set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.filter = Some("Test 5".into()); - app.data.radarr_data.is_filtering = true; - app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); - - let movies = &app.data.radarr_data.movies.items.clone(); - - let filter_matches = filter_table(&mut app, movies, |movie| &movie.title.text); - - assert!(filter_matches.is_empty()); - assert_eq!( - app.get_current_route(), - &ActiveRadarrBlock::FilterMovies.into() - ); - assert!(!app.data.radarr_data.is_filtering); - assert!(!app.should_ignore_quit_key); - assert!(app.data.radarr_data.filter.is_none()); - } - - #[test] - fn test_filter_table_reset_and_pop_navigation_on_empty_filter() { - let mut app = App::default(); app .data .radarr_data @@ -144,13 +157,15 @@ mod tests { app.data.radarr_data.filter = Some("".into()); app.data.radarr_data.is_filtering = true; app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); - let movies = &app.data.radarr_data.movies.items.clone(); + filter_table!( + app, + movies, + filtered_movies, + ActiveRadarrBlock::FilterMoviesError + ); - let filter_matches = filter_table(&mut app, movies, |movie| &movie.title.text); - - assert!(filter_matches.is_empty()); + assert!(app.data.radarr_data.filtered_movies.items.is_empty()); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert!(!app.data.radarr_data.is_filtering); assert!(!app.should_ignore_quit_key); @@ -158,8 +173,44 @@ mod tests { } #[test] - fn test_filter_table_noop_on_none_filter() { + fn test_filter_table_error_on_no_filter_matches() { let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.filter = Some("Test 5".into()); + app.data.radarr_data.is_filtering = true; + app.should_ignore_quit_key = true; + + filter_table!( + app, + movies, + filtered_movies, + ActiveRadarrBlock::FilterMoviesError + ); + + assert!(app.data.radarr_data.filtered_movies.items.is_empty()); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::FilterMoviesError.into() + ); + assert!(!app.data.radarr_data.is_filtering); + assert!(!app.should_ignore_quit_key); + assert!(app.data.radarr_data.filter.is_none()); + } + + #[test] + fn test_filter_table_macro_error_on_none_filter() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); app .data .radarr_data @@ -170,16 +221,18 @@ mod tests { )); app.data.radarr_data.is_filtering = true; app.should_ignore_quit_key = true; - app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); - let movies = &app.data.radarr_data.movies.items.clone(); + filter_table!( + app, + movies, + filtered_movies, + ActiveRadarrBlock::FilterMoviesError + ); - let filter_matches = filter_table(&mut app, movies, |movie| &movie.title.text); - - assert!(filter_matches.is_empty()); + assert!(app.data.radarr_data.filtered_movies.items.is_empty()); assert_eq!( app.get_current_route(), - &ActiveRadarrBlock::FilterMovies.into() + &ActiveRadarrBlock::FilterMoviesError.into() ); assert!(!app.data.radarr_data.is_filtering); assert!(!app.should_ignore_quit_key); @@ -244,7 +297,9 @@ mod tests { #[values( ActiveRadarrBlock::Movies, ActiveRadarrBlock::SearchMovie, + ActiveRadarrBlock::SearchMovieError, ActiveRadarrBlock::FilterMovies, + ActiveRadarrBlock::FilterMoviesError, ActiveRadarrBlock::UpdateAllMoviesPrompt, ActiveRadarrBlock::AddMovieSearchInput, ActiveRadarrBlock::AddMovieSearchResults, @@ -285,7 +340,9 @@ mod tests { #[values( ActiveRadarrBlock::Collections, ActiveRadarrBlock::SearchCollection, + ActiveRadarrBlock::SearchCollectionError, ActiveRadarrBlock::FilterCollections, + ActiveRadarrBlock::FilterCollectionsError, ActiveRadarrBlock::UpdateAllCollectionsPrompt, ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::ViewMovieOverview, diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index 6901563..0b944a5 100644 --- a/src/models/servarr_data/radarr/radarr_data.rs +++ b/src/models/servarr_data/radarr/radarr_data.rs @@ -287,7 +287,9 @@ pub enum ActiveRadarrBlock { EditMovieToggleMonitored, FileInfo, FilterCollections, + FilterCollectionsError, FilterMovies, + FilterMoviesError, Indexers, IndexerSettingsPrompt, IndexerSettingsAvailabilityDelayInput, @@ -317,21 +319,27 @@ pub enum ActiveRadarrBlock { UpdateAllCollectionsPrompt, UpdateAllMoviesPrompt, UpdateDownloadsPrompt, - SearchMovie, SearchCollection, + SearchCollectionError, + SearchMovie, + SearchMovieError, ViewMovieOverview, } -pub static LIBRARY_BLOCKS: [ActiveRadarrBlock; 4] = [ +pub static LIBRARY_BLOCKS: [ActiveRadarrBlock; 6] = [ ActiveRadarrBlock::Movies, ActiveRadarrBlock::SearchMovie, + ActiveRadarrBlock::SearchMovieError, ActiveRadarrBlock::FilterMovies, + ActiveRadarrBlock::FilterMoviesError, ActiveRadarrBlock::UpdateAllMoviesPrompt, ]; -pub static COLLECTIONS_BLOCKS: [ActiveRadarrBlock; 4] = [ +pub static COLLECTIONS_BLOCKS: [ActiveRadarrBlock; 6] = [ ActiveRadarrBlock::Collections, ActiveRadarrBlock::SearchCollection, + ActiveRadarrBlock::SearchCollectionError, ActiveRadarrBlock::FilterCollections, + ActiveRadarrBlock::FilterCollectionsError, ActiveRadarrBlock::UpdateAllCollectionsPrompt, ]; pub static INDEXERS_BLOCKS: [ActiveRadarrBlock; 4] = [ @@ -420,14 +428,6 @@ pub static COLLECTION_DETAILS_BLOCKS: [ActiveRadarrBlock; 2] = [ ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::ViewMovieOverview, ]; -pub static SEARCH_BLOCKS: [ActiveRadarrBlock; 2] = [ - ActiveRadarrBlock::SearchMovie, - ActiveRadarrBlock::SearchCollection, -]; -pub static FILTER_BLOCKS: [ActiveRadarrBlock; 2] = [ - ActiveRadarrBlock::FilterMovies, - ActiveRadarrBlock::FilterCollections, -]; pub static DELETE_MOVIE_BLOCKS: [ActiveRadarrBlock; 4] = [ ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteMovieConfirmPrompt, diff --git a/src/models/servarr_data/radarr/radarr_data_tests.rs b/src/models/servarr_data/radarr/radarr_data_tests.rs index dd3fe3d..c08d12a 100644 --- a/src/models/servarr_data/radarr/radarr_data_tests.rs +++ b/src/models/servarr_data/radarr/radarr_data_tests.rs @@ -1,7 +1,6 @@ #[cfg(test)] mod tests { mod radarr_data_tests { - use chrono::{DateTime, Utc}; use pretty_assertions::{assert_eq, assert_str_eq}; @@ -279,26 +278,30 @@ mod tests { ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS, COLLECTIONS_BLOCKS, COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS, DELETE_MOVIE_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_COLLECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS, - EDIT_MOVIE_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, FILTER_BLOCKS, INDEXERS_BLOCKS, - INDEXER_SETTINGS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, LIBRARY_BLOCKS, - MOVIE_DETAILS_BLOCKS, ROOT_FOLDERS_BLOCKS, SEARCH_BLOCKS, SYSTEM_DETAILS_BLOCKS, + EDIT_MOVIE_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, + INDEXER_SETTINGS_SELECTION_BLOCKS, LIBRARY_BLOCKS, MOVIE_DETAILS_BLOCKS, ROOT_FOLDERS_BLOCKS, + SYSTEM_DETAILS_BLOCKS, }; #[test] fn test_library_blocks_contents() { - assert_eq!(LIBRARY_BLOCKS.len(), 4); + assert_eq!(LIBRARY_BLOCKS.len(), 6); assert!(LIBRARY_BLOCKS.contains(&ActiveRadarrBlock::Movies)); assert!(LIBRARY_BLOCKS.contains(&ActiveRadarrBlock::SearchMovie)); + assert!(LIBRARY_BLOCKS.contains(&ActiveRadarrBlock::SearchMovieError)); assert!(LIBRARY_BLOCKS.contains(&ActiveRadarrBlock::FilterMovies)); + assert!(LIBRARY_BLOCKS.contains(&ActiveRadarrBlock::FilterMoviesError)); assert!(LIBRARY_BLOCKS.contains(&ActiveRadarrBlock::UpdateAllMoviesPrompt)); } #[test] fn test_collections_blocks_contents() { - assert_eq!(COLLECTIONS_BLOCKS.len(), 4); + assert_eq!(COLLECTIONS_BLOCKS.len(), 6); assert!(COLLECTIONS_BLOCKS.contains(&ActiveRadarrBlock::Collections)); assert!(COLLECTIONS_BLOCKS.contains(&ActiveRadarrBlock::SearchCollection)); + assert!(COLLECTIONS_BLOCKS.contains(&ActiveRadarrBlock::SearchCollectionError)); assert!(COLLECTIONS_BLOCKS.contains(&ActiveRadarrBlock::FilterCollections)); + assert!(COLLECTIONS_BLOCKS.contains(&ActiveRadarrBlock::FilterCollectionsError)); assert!(COLLECTIONS_BLOCKS.contains(&ActiveRadarrBlock::UpdateAllCollectionsPrompt)); } @@ -393,20 +396,6 @@ mod tests { assert!(COLLECTION_DETAILS_BLOCKS.contains(&ActiveRadarrBlock::ViewMovieOverview)); } - #[test] - fn test_search_blocks_contents() { - assert_eq!(SEARCH_BLOCKS.len(), 2); - assert!(SEARCH_BLOCKS.contains(&ActiveRadarrBlock::SearchMovie)); - assert!(SEARCH_BLOCKS.contains(&ActiveRadarrBlock::SearchCollection)); - } - - #[test] - fn test_filter_blocks_contents() { - assert_eq!(FILTER_BLOCKS.len(), 2); - assert!(FILTER_BLOCKS.contains(&ActiveRadarrBlock::FilterMovies)); - assert!(FILTER_BLOCKS.contains(&ActiveRadarrBlock::FilterCollections)); - } - #[test] fn test_delete_movie_blocks_contents() { assert_eq!(DELETE_MOVIE_BLOCKS.len(), 4); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index c6f0c6d..4a3f41d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -14,7 +14,7 @@ use tui::widgets::{Clear, List, ListItem}; use tui::Frame; use crate::app::App; -use crate::models::{Route, StatefulList, StatefulTable, TabState}; +use crate::models::{HorizontallyScrollableText, Route, StatefulList, StatefulTable, TabState}; use crate::ui::radarr_ui::RadarrUi; use crate::ui::utils::{ background_block, borderless_block, centered_rect, horizontal_chunks, @@ -659,8 +659,8 @@ pub fn draw_text_box( should_show_cursor: bool, is_selected: bool, ) { - let (block, style) = if let Some(..) = block_title { - (title_block_centered(block_title.unwrap()), style_default()) + let (block, style) = if let Some(title) = block_title { + (title_block_centered(title), style_default()) } else { ( layout_block(), @@ -671,10 +671,10 @@ pub fn draw_text_box( }, ) }; - let search_paragraph = Paragraph::new(Text::from(block_content)) + let paragraph = Paragraph::new(Text::from(block_content)) .style(style) .block(block); - f.render_widget(search_paragraph, text_box_area); + f.render_widget(paragraph, text_box_area); if should_show_cursor { show_cursor(f, text_box_area, offset, block_content); @@ -716,3 +716,49 @@ pub fn draw_text_box_with_label( is_selected, ); } + +pub fn draw_input_box_popup( + f: &mut Frame<'_, B>, + input_box_area: Rect, + box_title: &str, + box_content: &HorizontallyScrollableText, +) { + let chunks = vertical_chunks_with_margin( + vec![ + Constraint::Length(3), + Constraint::Length(1), + Constraint::Min(0), + ], + input_box_area, + 1, + ); + + draw_text_box( + f, + chunks[0], + Some(box_title), + &box_content.text, + *box_content.offset.borrow(), + true, + false, + ); + + let help = Paragraph::new(" cancel") + .style(style_help()) + .alignment(Alignment::Center) + .block(borderless_block()); + f.render_widget(help, chunks[1]); +} + +pub fn draw_error_message_popup( + f: &mut Frame<'_, B>, + error_message_area: Rect, + error_msg: &str, +) { + let input = Paragraph::new(error_msg) + .style(style_failure()) + .alignment(Alignment::Center) + .block(layout_block()); + + f.render_widget(input, error_message_area); +} diff --git a/src/ui/radarr_ui/collections/mod.rs b/src/ui/radarr_ui/collections/mod.rs index 43a5baf..58218c1 100644 --- a/src/ui/radarr_ui/collections/mod.rs +++ b/src/ui/radarr_ui/collections/mod.rs @@ -11,10 +11,10 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, COLLEC use crate::models::Route; use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi; use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi; -use crate::ui::radarr_ui::{draw_filter_box, draw_search_box}; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border, style_primary}; use crate::ui::{ - draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps, + draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, + draw_prompt_popup_over, draw_table, DrawUi, TableProps, }; mod collection_details_ui; @@ -45,19 +45,37 @@ impl DrawUi for CollectionsUi { app, content_rect, draw_collections, - draw_search_box, + draw_collection_search_box, 30, 13, ), + ActiveRadarrBlock::SearchCollectionError => draw_popup_over( + f, + app, + content_rect, + draw_collections, + draw_search_collection_error_box, + 30, + 8, + ), ActiveRadarrBlock::FilterCollections => draw_popup_over( f, app, content_rect, draw_collections, - draw_filter_box, + draw_filter_collections_box, 30, 13, ), + ActiveRadarrBlock::FilterCollectionsError => draw_popup_over( + f, + app, + content_rect, + draw_collections, + draw_filter_collections_error_box, + 30, + 8, + ), ActiveRadarrBlock::UpdateAllCollectionsPrompt => draw_prompt_popup_over( f, app, @@ -177,3 +195,33 @@ fn draw_update_all_collections_prompt( app.data.radarr_data.prompt_confirm, ); } + +fn draw_collection_search_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + draw_input_box_popup( + f, + area, + "Search", + app.data.radarr_data.search.as_ref().unwrap(), + ); +} + +fn draw_filter_collections_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + draw_input_box_popup( + f, + area, + "Filter", + app.data.radarr_data.filter.as_ref().unwrap(), + ) +} + +fn draw_search_collection_error_box(f: &mut Frame<'_, B>, _: &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, +) { + draw_error_message_popup(f, area, "No collections found matching the given filter!"); +} diff --git a/src/ui/radarr_ui/indexers/indexer_settings_ui.rs b/src/ui/radarr_ui/indexers/indexer_settings_ui.rs index b9a37c3..e87a388 100644 --- a/src/ui/radarr_ui/indexers/indexer_settings_ui.rs +++ b/src/ui/radarr_ui/indexers/indexer_settings_ui.rs @@ -168,7 +168,6 @@ fn draw_edit_indexer_settings_prompt( let button_chunks = horizontal_chunks( iter::repeat(Constraint::Ratio(1, 4)).take(4).collect(), - // vec![Constraint::Percentage(50), Constraint::Percentage(50)], chunks[1], ); diff --git a/src/ui/radarr_ui/library/mod.rs b/src/ui/radarr_ui/library/mod.rs index 1199d32..054b18e 100644 --- a/src/ui/radarr_ui/library/mod.rs +++ b/src/ui/radarr_ui/library/mod.rs @@ -7,14 +7,15 @@ use crate::app::App; use crate::models::radarr_models::Movie; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, LIBRARY_BLOCKS}; use crate::models::Route; +use crate::ui::radarr_ui::determine_row_style; use crate::ui::radarr_ui::library::add_movie_ui::AddMovieUi; use crate::ui::radarr_ui::library::delete_movie_ui::DeleteMovieUi; use crate::ui::radarr_ui::library::edit_movie_ui::EditMovieUi; use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi; -use crate::ui::radarr_ui::{determine_row_style, draw_filter_box, draw_search_box}; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; use crate::ui::{ - draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps, + draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, + draw_prompt_popup_over, draw_table, DrawUi, TableProps, }; use crate::utils::{convert_runtime, convert_to_gb}; @@ -47,12 +48,42 @@ impl DrawUi for LibraryUi { let mut library_ui_matchers = |active_radarr_block: ActiveRadarrBlock| match active_radarr_block { ActiveRadarrBlock::Movies => draw_library(f, app, content_rect), - ActiveRadarrBlock::SearchMovie => { - draw_popup_over(f, app, content_rect, draw_library, draw_search_box, 30, 13) - } - ActiveRadarrBlock::FilterMovies => { - draw_popup_over(f, app, content_rect, draw_library, draw_filter_box, 30, 13) - } + ActiveRadarrBlock::SearchMovie => draw_popup_over( + f, + app, + content_rect, + draw_library, + draw_movie_search_box, + 30, + 13, + ), + ActiveRadarrBlock::SearchMovieError => draw_popup_over( + f, + app, + content_rect, + draw_library, + draw_search_movie_error_box, + 30, + 8, + ), + ActiveRadarrBlock::FilterMovies => draw_popup_over( + f, + app, + content_rect, + draw_library, + draw_filter_movies_box, + 30, + 13, + ), + ActiveRadarrBlock::FilterMoviesError => draw_popup_over( + f, + app, + content_rect, + draw_library, + draw_filter_movies_error_box, + 30, + 8, + ), ActiveRadarrBlock::UpdateAllMoviesPrompt => draw_prompt_popup_over( f, app, @@ -194,3 +225,29 @@ fn draw_update_all_movies_prompt( app.data.radarr_data.prompt_confirm, ); } + +fn draw_movie_search_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + draw_input_box_popup( + f, + area, + "Search", + app.data.radarr_data.search.as_ref().unwrap(), + ); +} + +fn draw_filter_movies_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + draw_input_box_popup( + f, + area, + "Filter", + app.data.radarr_data.filter.as_ref().unwrap(), + ) +} + +fn draw_search_movie_error_box(f: &mut Frame<'_, B>, _: &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) { + draw_error_message_popup(f, area, "No movies found matching the given filter!"); +} diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index cf85680..2ba672d 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -11,9 +11,7 @@ use tui::Frame; use crate::app::App; use crate::logos::RADARR_LOGO; use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie, RootFolder}; -use crate::models::servarr_data::radarr::radarr_data::{ - ActiveRadarrBlock, RadarrData, FILTER_BLOCKS, SEARCH_BLOCKS, -}; +use crate::models::servarr_data::radarr::radarr_data::RadarrData; use crate::models::Route; use crate::ui::draw_tabs; use crate::ui::loading; @@ -25,9 +23,8 @@ use crate::ui::radarr_ui::root_folders::RootFoldersUi; use crate::ui::radarr_ui::system::SystemUi; use crate::ui::utils::{ borderless_block, horizontal_chunks, layout_block, line_gauge_with_label, line_gauge_with_title, - show_cursor, style_awaiting_import, style_bold, style_default, style_failure, style_help, - style_success, style_unmonitored, style_warning, title_block, title_block_centered, - vertical_chunks_with_margin, + style_awaiting_import, style_bold, style_default, style_failure, style_success, + style_unmonitored, style_warning, title_block, vertical_chunks_with_margin, }; use crate::ui::DrawUi; use crate::utils::convert_to_gb; @@ -231,126 +228,6 @@ fn determine_row_style(downloads_vec: &[DownloadRecord], movie: &Movie) -> Style } } -fn draw_search_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { - let chunks = vertical_chunks_with_margin( - vec![ - Constraint::Length(3), - Constraint::Length(1), - Constraint::Min(0), - ], - area, - 1, - ); - if !app.data.radarr_data.is_searching { - let error_msg = match app.get_current_route() { - Route::Radarr(active_radarr_block, _) => match active_radarr_block { - ActiveRadarrBlock::SearchMovie => "Movie not found!", - ActiveRadarrBlock::SearchCollection => "Collection not found!", - _ => "", - }, - _ => "", - }; - - let input = Paragraph::new(error_msg) - .style(style_failure()) - .block(layout_block()); - - f.render_widget(input, chunks[0]); - } else { - let default_content = String::default(); - let (block_title, offset, block_content) = match app.get_current_route() { - Route::Radarr(active_radarr_block, _) => match active_radarr_block { - _ if SEARCH_BLOCKS.contains(active_radarr_block) => ( - "Search", - *app - .data - .radarr_data - .search - .as_ref() - .unwrap() - .offset - .borrow(), - &app.data.radarr_data.search.as_ref().unwrap().text, - ), - _ => ("", 0, &default_content), - }, - _ => ("", 0, &default_content), - }; - - let input = Paragraph::new(block_content.as_str()) - .style(style_default()) - .block(title_block_centered(block_title)); - let help = Paragraph::new(" cancel") - .style(style_help()) - .alignment(Alignment::Center) - .block(borderless_block()); - show_cursor(f, chunks[0], offset, block_content); - - f.render_widget(input, chunks[0]); - f.render_widget(help, chunks[1]); - } -} - -fn draw_filter_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { - let chunks = vertical_chunks_with_margin( - vec![ - Constraint::Length(3), - Constraint::Length(1), - Constraint::Min(0), - ], - area, - 1, - ); - if !app.data.radarr_data.is_filtering { - let error_msg = match app.get_current_route() { - Route::Radarr(active_radarr_block, _) => match active_radarr_block { - ActiveRadarrBlock::FilterMovies => "No movies found matching filter!", - ActiveRadarrBlock::FilterCollections => "No collections found matching filter!", - _ => "", - }, - _ => "", - }; - - let input = Paragraph::new(error_msg) - .style(style_failure()) - .block(layout_block()); - - f.render_widget(input, chunks[0]); - } else { - let default_content = String::default(); - let (block_title, offset, block_content) = match app.get_current_route() { - Route::Radarr(active_radarr_block, _) => match active_radarr_block { - _ if FILTER_BLOCKS.contains(active_radarr_block) => ( - "Filter", - *app - .data - .radarr_data - .filter - .as_ref() - .unwrap() - .offset - .borrow(), - &app.data.radarr_data.filter.as_ref().unwrap().text, - ), - _ => ("", 0, &default_content), - }, - _ => ("", 0, &default_content), - }; - - let input = Paragraph::new(block_content.as_str()) - .style(style_default()) - .block(title_block_centered(block_title)); - let help = Paragraph::new(" cancel") - .style(style_help()) - .alignment(Alignment::Center) - .block(borderless_block()); - show_cursor(f, chunks[0], offset, block_content); - - f.render_widget(input, chunks[0]); - f.render_widget(help, chunks[1]); - } -} - fn draw_radarr_logo(f: &mut Frame<'_, B>, area: Rect) { let mut logo_text = Text::from(RADARR_LOGO); logo_text.patch_style(Style::default().fg(Color::LightYellow)); diff --git a/src/ui/radarr_ui/root_folders/mod.rs b/src/ui/radarr_ui/root_folders/mod.rs index 9cd5acc..a5dbdc8 100644 --- a/src/ui/radarr_ui/root_folders/mod.rs +++ b/src/ui/radarr_ui/root_folders/mod.rs @@ -1,18 +1,16 @@ use tui::backend::Backend; -use tui::layout::{Alignment, Constraint, Rect}; -use tui::widgets::{Cell, Paragraph, Row}; +use tui::layout::{Constraint, Rect}; +use tui::widgets::{Cell, Row}; use tui::Frame; use crate::app::App; use crate::models::radarr_models::RootFolder; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; use crate::models::Route; -use crate::ui::utils::{ - borderless_block, layout_block_top_border, show_cursor, style_default, style_help, style_primary, - title_block_centered, vertical_chunks_with_margin, -}; +use crate::ui::utils::{layout_block_top_border, style_primary}; use crate::ui::{ - draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps, + draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, + DrawUi, TableProps, }; use crate::utils::convert_to_gb; @@ -109,37 +107,12 @@ fn draw_add_root_folder_prompt_box( app: &mut App<'_>, area: Rect, ) { - let chunks = vertical_chunks_with_margin( - vec![ - Constraint::Length(3), - Constraint::Length(1), - Constraint::Min(0), - ], + draw_input_box_popup( + f, area, - 1, + "Add Root Folder", + app.data.radarr_data.edit_root_folder.as_ref().unwrap(), ); - let block_title = "Add Root Folder"; - let offset = *app - .data - .radarr_data - .edit_root_folder - .as_ref() - .unwrap() - .offset - .borrow(); - let block_content = &app.data.radarr_data.edit_root_folder.as_ref().unwrap().text; - - let input = Paragraph::new(block_content.as_str()) - .style(style_default()) - .block(title_block_centered(block_title)); - let help = Paragraph::new(" cancel") - .style(style_help()) - .alignment(Alignment::Center) - .block(borderless_block()); - show_cursor(f, chunks[0], offset, block_content); - - f.render_widget(input, chunks[0]); - f.render_widget(help, chunks[1]); } fn draw_delete_root_folder_prompt(