diff --git a/src/app/radarr/mod.rs b/src/app/radarr/mod.rs index 344bd58..1daa1af 100644 --- a/src/app/radarr/mod.rs +++ b/src/app/radarr/mod.rs @@ -182,23 +182,14 @@ impl<'a> App<'a> { } async fn populate_movie_collection_table(&mut self) { - let collection_movies = - if let Some(filtered_collections) = self.data.radarr_data.filtered_collections.as_ref() { - filtered_collections - .current_selection() - .clone() - .movies - .unwrap_or_default() - } else { - self - .data - .radarr_data - .collections - .current_selection() - .clone() - .movies - .unwrap_or_default() - }; + let collection_movies = self + .data + .radarr_data + .collections + .current_selection() + .clone() + .movies + .unwrap_or_default(); self .data .radarr_data diff --git a/src/app/radarr/radarr_tests.rs b/src/app/radarr/radarr_tests.rs index 95c06f8..8aa8431 100644 --- a/src/app/radarr/radarr_tests.rs +++ b/src/app/radarr/radarr_tests.rs @@ -8,7 +8,6 @@ mod tests { use crate::app::App; use crate::models::radarr_models::{Collection, CollectionMovie, Credit, Release}; use crate::models::servarr_data::radarr::modals::MovieDetailsModal; - use crate::models::StatefulTable; use crate::network::radarr_network::RadarrEvent; use crate::network::NetworkEvent; @@ -666,12 +665,14 @@ mod tests { #[tokio::test] async fn test_populate_movie_collection_table_filtered() { let mut app = App::default(); - let mut filtered_collections = StatefulTable::default(); - filtered_collections.set_items(vec![Collection { - movies: Some(vec![CollectionMovie::default()]), - ..Collection::default() - }]); - app.data.radarr_data.filtered_collections = Some(filtered_collections); + app + .data + .radarr_data + .collections + .set_filtered_items(vec![Collection { + movies: Some(vec![CollectionMovie::default()]), + ..Collection::default() + }]); app.populate_movie_collection_table().await; diff --git a/src/handlers/radarr_handlers/collections/collections_handler_tests.rs b/src/handlers/radarr_handlers/collections/collections_handler_tests.rs index b2b6ed7..bb192d5 100644 --- a/src/handlers/radarr_handlers/collections/collections_handler_tests.rs +++ b/src/handlers/radarr_handlers/collections/collections_handler_tests.rs @@ -19,7 +19,6 @@ mod tests { mod test_handle_scroll_up_and_down { use rstest::rstest; - use crate::models::StatefulTable; use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; use super::*; @@ -34,55 +33,11 @@ mod tests { title, to_string ); - - #[rstest] - fn test_filtered_collections_scroll( - #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, - ) { - let mut app = App::default(); - let mut filtered_collections = StatefulTable::default(); - filtered_collections.set_items(simple_stateful_iterable_vec!( - Collection, - HorizontallyScrollableText - )); - app.data.radarr_data.filtered_collections = Some(filtered_collections); - - CollectionsHandler::with(&key, &mut app, &ActiveRadarrBlock::Collections, &None).handle(); - - assert_str_eq!( - app - .data - .radarr_data - .filtered_collections - .as_ref() - .unwrap() - .current_selection() - .title - .to_string(), - "Test 2" - ); - - CollectionsHandler::with(&key, &mut app, &ActiveRadarrBlock::Collections, &None).handle(); - - assert_str_eq!( - app - .data - .radarr_data - .filtered_collections - .as_ref() - .unwrap() - .current_selection() - .title - .to_string(), - "Test 1" - ); - } } mod test_handle_home_end { use pretty_assertions::assert_eq; - use crate::models::StatefulTable; use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use super::*; @@ -98,63 +53,10 @@ mod tests { to_string ); - #[test] - fn test_filtered_collections_home_end() { - let mut app = App::default(); - let mut filtered_collections = StatefulTable::default(); - filtered_collections.set_items(extended_stateful_iterable_vec!( - Collection, - HorizontallyScrollableText - )); - app.data.radarr_data.filtered_collections = Some(filtered_collections); - - CollectionsHandler::with( - &DEFAULT_KEYBINDINGS.end.key, - &mut app, - &ActiveRadarrBlock::Collections, - &None, - ) - .handle(); - - assert_str_eq!( - app - .data - .radarr_data - .filtered_collections - .as_ref() - .unwrap() - .current_selection() - .title - .to_string(), - "Test 3" - ); - - CollectionsHandler::with( - &DEFAULT_KEYBINDINGS.home.key, - &mut app, - &ActiveRadarrBlock::Collections, - &None, - ) - .handle(); - - assert_str_eq!( - app - .data - .radarr_data - .filtered_collections - .as_ref() - .unwrap() - .current_selection() - .title - .to_string(), - "Test 1" - ); - } - #[test] fn test_collection_search_box_home_end_keys() { let mut app = App::default(); - app.data.radarr_data.search = Some("Test".into()); + app.data.radarr_data.collections.search = Some("Test".into()); CollectionsHandler::with( &DEFAULT_KEYBINDINGS.home.key, @@ -168,6 +70,7 @@ mod tests { *app .data .radarr_data + .collections .search .as_ref() .unwrap() @@ -188,6 +91,7 @@ mod tests { *app .data .radarr_data + .collections .search .as_ref() .unwrap() @@ -200,7 +104,7 @@ mod tests { #[test] fn test_collection_filter_box_home_end_keys() { let mut app = App::default(); - app.data.radarr_data.filter = Some("Test".into()); + app.data.radarr_data.collections.filter = Some("Test".into()); CollectionsHandler::with( &DEFAULT_KEYBINDINGS.home.key, @@ -214,6 +118,7 @@ mod tests { *app .data .radarr_data + .collections .filter .as_ref() .unwrap() @@ -234,6 +139,7 @@ mod tests { *app .data .radarr_data + .collections .filter .as_ref() .unwrap() @@ -326,7 +232,7 @@ mod tests { #[test] fn test_collection_search_box_left_right_keys() { let mut app = App::default(); - app.data.radarr_data.search = Some("Test".into()); + app.data.radarr_data.collections.search = Some("Test".into()); CollectionsHandler::with( &DEFAULT_KEYBINDINGS.left.key, @@ -340,6 +246,7 @@ mod tests { *app .data .radarr_data + .collections .search .as_ref() .unwrap() @@ -360,6 +267,7 @@ mod tests { *app .data .radarr_data + .collections .search .as_ref() .unwrap() @@ -372,7 +280,7 @@ mod tests { #[test] fn test_collection_filter_box_left_right_keys() { let mut app = App::default(); - app.data.radarr_data.filter = Some("Test".into()); + app.data.radarr_data.collections.filter = Some("Test".into()); CollectionsHandler::with( &DEFAULT_KEYBINDINGS.left.key, @@ -386,6 +294,7 @@ mod tests { *app .data .radarr_data + .collections .filter .as_ref() .unwrap() @@ -406,6 +315,7 @@ mod tests { *app .data .radarr_data + .collections .filter .as_ref() .unwrap() @@ -417,7 +327,6 @@ mod tests { } mod test_handle_submit { - use crate::models::StatefulTable; use pretty_assertions::assert_eq; use crate::network::radarr_network::RadarrEvent; @@ -457,7 +366,7 @@ mod tests { Collection, HorizontallyScrollableText )); - app.data.radarr_data.search = Some("Test 2".into()); + app.data.radarr_data.collections.search = Some("Test 2".into()); CollectionsHandler::with( &SUBMIT_KEY, @@ -496,7 +405,7 @@ mod tests { Collection, HorizontallyScrollableText )); - app.data.radarr_data.search = Some("Test 5".into()); + app.data.radarr_data.collections.search = Some("Test 5".into()); CollectionsHandler::with( &SUBMIT_KEY, @@ -527,13 +436,15 @@ mod tests { let mut app = App::default(); app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); - let mut filtered_collections = StatefulTable::default(); - filtered_collections.set_items(extended_stateful_iterable_vec!( - Collection, - HorizontallyScrollableText - )); - app.data.radarr_data.filtered_collections = Some(filtered_collections); - app.data.radarr_data.search = Some("Test 2".into()); + app + .data + .radarr_data + .collections + .set_items(extended_stateful_iterable_vec!( + Collection, + HorizontallyScrollableText + )); + app.data.radarr_data.collections.search = Some("Test 2".into()); CollectionsHandler::with( &SUBMIT_KEY, @@ -547,9 +458,7 @@ mod tests { app .data .radarr_data - .filtered_collections - .as_ref() - .unwrap() + .collections .current_selection() .title .text, @@ -574,7 +483,7 @@ mod tests { Collection, HorizontallyScrollableText )); - app.data.radarr_data.filter = Some("Test".into()); + app.data.radarr_data.collections.filter = Some("Test".into()); CollectionsHandler::with( &SUBMIT_KEY, @@ -584,15 +493,16 @@ mod tests { ) .handle(); - assert!(app.data.radarr_data.filtered_collections.is_some()); + assert!(!app.should_ignore_quit_key); + assert!(app.data.radarr_data.collections.filtered_items.is_some()); assert_eq!( app .data .radarr_data - .filtered_collections + .collections + .filtered_items .as_ref() .unwrap() - .items .len(), 3 ); @@ -600,9 +510,7 @@ mod tests { app .data .radarr_data - .filtered_collections - .as_ref() - .unwrap() + .collections .current_selection() .title .text, @@ -627,7 +535,7 @@ mod tests { Collection, HorizontallyScrollableText )); - app.data.radarr_data.filter = Some("Test 5".into()); + app.data.radarr_data.collections.filter = Some("Test 5".into()); CollectionsHandler::with( &SUBMIT_KEY, @@ -637,7 +545,8 @@ mod tests { ) .handle(); - assert!(app.data.radarr_data.filtered_collections.is_none()); + assert!(!app.should_ignore_quit_key); + assert!(app.data.radarr_data.collections.filtered_items.is_none()); assert_eq!( app.get_current_route(), &ActiveRadarrBlock::FilterCollectionsError.into() @@ -695,9 +604,10 @@ mod tests { mod test_handle_esc { use pretty_assertions::assert_eq; + use ratatui::widgets::TableState; use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data; - use crate::{assert_filter_reset, assert_search_reset}; + use crate::models::StatefulTable; use super::*; @@ -716,6 +626,7 @@ mod tests { app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(active_radarr_block.into()); app.data.radarr_data = create_test_radarr_data(); + app.data.radarr_data.collections.search = Some("Test".into()); CollectionsHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); @@ -724,7 +635,7 @@ mod tests { &ActiveRadarrBlock::Collections.into() ); assert!(!app.should_ignore_quit_key); - assert_search_reset!(app.data.radarr_data); + assert_eq!(app.data.radarr_data.collections.search, None); } #[rstest] @@ -740,6 +651,12 @@ mod tests { app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(active_radarr_block.into()); app.data.radarr_data = create_test_radarr_data(); + app.data.radarr_data.collections = StatefulTable { + filter: Some("Test".into()), + filtered_items: Some(Vec::new()), + filtered_state: Some(TableState::default()), + ..StatefulTable::default() + }; CollectionsHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); @@ -748,7 +665,9 @@ mod tests { &ActiveRadarrBlock::Collections.into() ); assert!(!app.should_ignore_quit_key); - assert_filter_reset!(app.data.radarr_data); + assert_eq!(app.data.radarr_data.collections.filter, None); + assert_eq!(app.data.radarr_data.collections.filtered_items, None); + assert_eq!(app.data.radarr_data.collections.filtered_state, None); } #[test] @@ -780,6 +699,13 @@ mod tests { app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.data.radarr_data = create_test_radarr_data(); + app.data.radarr_data.collections = StatefulTable { + search: Some("Test".into()), + filter: Some("Test".into()), + filtered_items: Some(Vec::new()), + filtered_state: Some(TableState::default()), + ..StatefulTable::default() + }; CollectionsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Collections, &None).handle(); @@ -788,8 +714,10 @@ mod tests { &ActiveRadarrBlock::Collections.into() ); assert!(app.error.text.is_empty()); - assert_search_reset!(app.data.radarr_data); - assert_filter_reset!(app.data.radarr_data); + assert_eq!(app.data.radarr_data.collections.search, None); + assert_eq!(app.data.radarr_data.collections.filter, None); + assert_eq!(app.data.radarr_data.collections.filtered_items, None); + assert_eq!(app.data.radarr_data.collections.filtered_state, None); } } @@ -805,6 +733,7 @@ mod tests { RadarrData, EDIT_COLLECTION_SELECTION_BLOCKS, }; + use crate::models::StatefulTable; use crate::{assert_refresh_key, test_edit_collection_key}; use super::*; @@ -825,9 +754,11 @@ mod tests { app.get_current_route(), &ActiveRadarrBlock::SearchCollection.into() ); - assert!(app.data.radarr_data.is_searching); assert!(app.should_ignore_quit_key); - assert!(app.data.radarr_data.search.is_some()); + assert_eq!( + app.data.radarr_data.collections.search, + Some(HorizontallyScrollableText::default()) + ); } #[test] @@ -846,9 +777,8 @@ mod tests { 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.collections.filter.is_some()); } #[test] @@ -857,6 +787,8 @@ mod tests { app.should_ignore_quit_key = true; app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.data.radarr_data = create_test_radarr_data(); + app.data.radarr_data.collections = StatefulTable::default(); + app.data.radarr_data.collections.filter = Some("Test".into()); CollectionsHandler::with( &DEFAULT_KEYBINDINGS.filter.key, @@ -870,10 +802,13 @@ mod tests { 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.is_none()); + assert_eq!( + app.data.radarr_data.collections.filter, + Some(HorizontallyScrollableText::default()) + ); + assert!(app.data.radarr_data.collections.filtered_items.is_none()); + assert!(app.data.radarr_data.collections.filtered_state.is_none()); } #[test] @@ -911,7 +846,7 @@ mod tests { #[test] fn test_search_collections_box_backspace_key() { let mut app = App::default(); - app.data.radarr_data.search = Some("Test".into()); + app.data.radarr_data.collections.search = Some("Test".into()); CollectionsHandler::with( &DEFAULT_KEYBINDINGS.backspace.key, @@ -921,13 +856,24 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "Tes"); + assert_str_eq!( + app + .data + .radarr_data + .collections + .search + .as_ref() + .unwrap() + .text, + "Tes" + ); } #[test] fn test_filter_collections_box_backspace_key() { let mut app = App::default(); - app.data.radarr_data.filter = Some("Test".into()); + app.data.radarr_data.collections = StatefulTable::default(); + app.data.radarr_data.collections.filter = Some("Test".into()); CollectionsHandler::with( &DEFAULT_KEYBINDINGS.backspace.key, @@ -937,13 +883,23 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.filter.as_ref().unwrap().text, "Tes"); + assert_str_eq!( + app + .data + .radarr_data + .collections + .filter + .as_ref() + .unwrap() + .text, + "Tes" + ); } #[test] fn test_search_collections_box_char_key() { let mut app = App::default(); - app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); + app.data.radarr_data.collections.search = Some(HorizontallyScrollableText::default()); CollectionsHandler::with( &Key::Char('h'), @@ -953,13 +909,24 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "h"); + assert_str_eq!( + app + .data + .radarr_data + .collections + .search + .as_ref() + .unwrap() + .text, + "h" + ); } #[test] fn test_filter_collections_box_char_key() { let mut app = App::default(); - app.data.radarr_data.filter = Some(HorizontallyScrollableText::default()); + app.data.radarr_data.collections = StatefulTable::default(); + app.data.radarr_data.collections.filter = Some(HorizontallyScrollableText::default()); CollectionsHandler::with( &Key::Char('h'), @@ -969,7 +936,17 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.filter.as_ref().unwrap().text, "h"); + assert_str_eq!( + app + .data + .radarr_data + .collections + .filter + .as_ref() + .unwrap() + .text, + "h" + ); } } diff --git a/src/handlers/radarr_handlers/collections/mod.rs b/src/handlers/radarr_handlers/collections/mod.rs index a9d598a..06c852c 100644 --- a/src/handlers/radarr_handlers/collections/mod.rs +++ b/src/handlers/radarr_handlers/collections/mod.rs @@ -8,10 +8,9 @@ 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, StatefulTable}; +use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable}; use crate::network::radarr_network::RadarrEvent; -use crate::utils::strip_non_search_characters; -use crate::{filter_table, handle_text_box_keys, handle_text_box_left_right_keys, search_table}; +use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; mod collection_details_handler; mod edit_collection_handler; @@ -68,38 +67,24 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' fn handle_scroll_up(&mut self) { if self.active_radarr_block == &ActiveRadarrBlock::Collections { - if let Some(filtered_collections) = self.app.data.radarr_data.filtered_collections.as_mut() { - filtered_collections.scroll_up(); - } else { - self.app.data.radarr_data.collections.scroll_up() - } + self.app.data.radarr_data.collections.scroll_up() } } fn handle_scroll_down(&mut self) { if self.active_radarr_block == &ActiveRadarrBlock::Collections { - if let Some(filtered_collections) = self.app.data.radarr_data.filtered_collections.as_mut() { - filtered_collections.scroll_down(); - } else { - self.app.data.radarr_data.collections.scroll_down() - } + self.app.data.radarr_data.collections.scroll_down() } } fn handle_home(&mut self) { match self.active_radarr_block { - ActiveRadarrBlock::Collections => { - if let Some(filtered_collections) = self.app.data.radarr_data.filtered_collections.as_mut() - { - filtered_collections.scroll_to_top(); - } else { - self.app.data.radarr_data.collections.scroll_to_top() - } - } + ActiveRadarrBlock::Collections => self.app.data.radarr_data.collections.scroll_to_top(), ActiveRadarrBlock::SearchCollection => self .app .data .radarr_data + .collections .search .as_mut() .unwrap() @@ -108,6 +93,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' .app .data .radarr_data + .collections .filter .as_mut() .unwrap() @@ -118,18 +104,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' fn handle_end(&mut self) { match self.active_radarr_block { - ActiveRadarrBlock::Collections => { - if let Some(filtered_collections) = self.app.data.radarr_data.filtered_collections.as_mut() - { - filtered_collections.scroll_to_bottom(); - } else { - self.app.data.radarr_data.collections.scroll_to_bottom() - } - } + ActiveRadarrBlock::Collections => self.app.data.radarr_data.collections.scroll_to_bottom(), ActiveRadarrBlock::SearchCollection => self .app .data .radarr_data + .collections .search .as_mut() .unwrap() @@ -138,6 +118,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' .app .data .radarr_data + .collections .filter .as_mut() .unwrap() @@ -156,14 +137,28 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' handle_text_box_left_right_keys!( self, self.key, - self.app.data.radarr_data.search.as_mut().unwrap() + self + .app + .data + .radarr_data + .collections + .search + .as_mut() + .unwrap() ) } ActiveRadarrBlock::FilterCollections => { handle_text_box_left_right_keys!( self, self.key, - self.app.data.radarr_data.filter.as_mut().unwrap() + self + .app + .data + .radarr_data + .collections + .filter + .as_mut() + .unwrap() ) } _ => (), @@ -176,28 +171,42 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' .app .push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()), ActiveRadarrBlock::SearchCollection => { - if self.app.data.radarr_data.filtered_collections.is_some() { - search_table!( - self.app, - filtered_collections, - ActiveRadarrBlock::SearchCollectionError, - true - ); - } else { - search_table!( - self.app, - collections, - ActiveRadarrBlock::SearchCollectionError - ); + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + + if self.app.data.radarr_data.collections.search.is_some() { + let has_match = self + .app + .data + .radarr_data + .collections + .apply_search(|collection| &collection.title.text); + + if !has_match { + self + .app + .push_navigation_stack(ActiveRadarrBlock::SearchCollectionError.into()); + } } } ActiveRadarrBlock::FilterCollections => { - filter_table!( - self.app, - collections, - filtered_collections, - ActiveRadarrBlock::FilterCollectionsError - ); + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + + if self.app.data.radarr_data.collections.filter.is_some() { + let has_matches = self + .app + .data + .radarr_data + .collections + .apply_filter(|collection| &collection.title.text); + + if !has_matches { + self + .app + .push_navigation_stack(ActiveRadarrBlock::FilterCollectionsError.into()); + } + } } ActiveRadarrBlock::UpdateAllCollectionsPrompt => { if self.app.data.radarr_data.prompt_confirm { @@ -214,12 +223,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' match self.active_radarr_block { ActiveRadarrBlock::FilterCollections | ActiveRadarrBlock::FilterCollectionsError => { self.app.pop_navigation_stack(); - self.app.data.radarr_data.reset_filter(); + self.app.data.radarr_data.collections.reset_filter(); self.app.should_ignore_quit_key = false; } ActiveRadarrBlock::SearchCollection | ActiveRadarrBlock::SearchCollectionError => { self.app.pop_navigation_stack(); - self.app.data.radarr_data.reset_search(); + self.app.data.radarr_data.collections.reset_search(); self.app.should_ignore_quit_key = false; } ActiveRadarrBlock::UpdateAllCollectionsPrompt => { @@ -227,8 +236,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' self.app.data.radarr_data.prompt_confirm = false; } _ => { - self.app.data.radarr_data.reset_search(); - self.app.data.radarr_data.reset_filter(); + self.app.data.radarr_data.collections.reset_search(); + self.app.data.radarr_data.collections.reset_filter(); handle_clear_errors(self.app); } } @@ -242,17 +251,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' self .app .push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); - self.app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); - self.app.data.radarr_data.is_searching = true; + self.app.data.radarr_data.collections.search = + Some(HorizontallyScrollableText::default()); self.app.should_ignore_quit_key = true; } _ if *key == DEFAULT_KEYBINDINGS.filter.key => { 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.data.radarr_data.collections.reset_filter(); + self.app.data.radarr_data.collections.filter = + Some(HorizontallyScrollableText::default()); self.app.should_ignore_quit_key = true; } _ if *key == DEFAULT_KEYBINDINGS.edit.key => { @@ -282,14 +291,28 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' handle_text_box_keys!( self, key, - self.app.data.radarr_data.search.as_mut().unwrap() + self + .app + .data + .radarr_data + .collections + .search + .as_mut() + .unwrap() ) } ActiveRadarrBlock::FilterCollections => { handle_text_box_keys!( self, key, - self.app.data.radarr_data.filter.as_mut().unwrap() + self + .app + .data + .radarr_data + .collections + .filter + .as_mut() + .unwrap() ) } _ => (), diff --git a/src/handlers/radarr_handlers/library/add_movie_handler.rs b/src/handlers/radarr_handlers/library/add_movie_handler.rs index b9f4b92..ca85ddd 100644 --- a/src/handlers/radarr_handlers/library/add_movie_handler.rs +++ b/src/handlers/radarr_handlers/library/add_movie_handler.rs @@ -193,7 +193,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, .app .data .radarr_data - .search + .add_movie_search .as_mut() .unwrap() .scroll_home(), @@ -260,7 +260,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, .app .data .radarr_data - .search + .add_movie_search .as_mut() .unwrap() .reset_offset(), @@ -286,7 +286,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, handle_text_box_left_right_keys!( self, self.key, - self.app.data.radarr_data.search.as_mut().unwrap() + self.app.data.radarr_data.add_movie_search.as_mut().unwrap() ) } ActiveRadarrBlock::AddMovieTagsInput => { @@ -314,7 +314,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, .app .data .radarr_data - .search + .add_movie_search .as_mut() .unwrap() .text @@ -407,7 +407,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, match self.active_radarr_block { ActiveRadarrBlock::AddMovieSearchInput => { self.app.pop_navigation_stack(); - self.app.data.radarr_data.reset_search(); + self.app.data.radarr_data.add_movie_search = None; self.app.should_ignore_quit_key = false; } ActiveRadarrBlock::AddMovieSearchResults | ActiveRadarrBlock::AddMovieEmptySearchResults => { @@ -440,7 +440,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, handle_text_box_keys!( self, key, - self.app.data.radarr_data.search.as_mut().unwrap() + self.app.data.radarr_data.add_movie_search.as_mut().unwrap() ) } ActiveRadarrBlock::AddMovieTagsInput => { diff --git a/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs b/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs index 9ef7779..391f68b 100644 --- a/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs @@ -632,7 +632,7 @@ mod tests { #[test] fn test_add_movie_search_input_home_end_keys() { let mut app = App::default(); - app.data.radarr_data.search = Some("Test".into()); + app.data.radarr_data.add_movie_search = Some("Test".into()); AddMovieHandler::with( &DEFAULT_KEYBINDINGS.home.key, @@ -646,7 +646,7 @@ mod tests { *app .data .radarr_data - .search + .add_movie_search .as_ref() .unwrap() .offset @@ -666,7 +666,7 @@ mod tests { *app .data .radarr_data - .search + .add_movie_search .as_ref() .unwrap() .offset @@ -749,7 +749,7 @@ mod tests { #[test] fn test_add_movie_search_input_left_right_keys() { let mut app = App::default(); - app.data.radarr_data.search = Some("Test".into()); + app.data.radarr_data.add_movie_search = Some("Test".into()); AddMovieHandler::with( &DEFAULT_KEYBINDINGS.left.key, @@ -763,7 +763,7 @@ mod tests { *app .data .radarr_data - .search + .add_movie_search .as_ref() .unwrap() .offset @@ -783,7 +783,7 @@ mod tests { *app .data .radarr_data - .search + .add_movie_search .as_ref() .unwrap() .offset @@ -863,7 +863,7 @@ mod tests { fn test_add_movie_search_input_submit() { let mut app = App::default(); app.should_ignore_quit_key = true; - app.data.radarr_data.search = Some("test".into()); + app.data.radarr_data.add_movie_search = Some("test".into()); AddMovieHandler::with( &SUBMIT_KEY, @@ -883,7 +883,7 @@ mod tests { #[test] fn test_add_movie_search_input_submit_noop_on_empty_search() { let mut app = App::default(); - app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); + app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default()); app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); app.should_ignore_quit_key = true; @@ -1147,7 +1147,7 @@ mod tests { use crate::models::servarr_data::radarr::modals::AddMovieModal; use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data; use crate::models::StatefulTable; - use crate::{assert_search_reset, simple_stateful_iterable_vec}; + use crate::simple_stateful_iterable_vec; use super::*; @@ -1170,7 +1170,7 @@ mod tests { assert!(!app.should_ignore_quit_key); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); - assert_search_reset!(app.data.radarr_data); + assert_eq!(app.data.radarr_data.add_movie_search, None); } #[test] @@ -1344,7 +1344,7 @@ mod tests { #[test] fn test_add_movie_search_input_backspace() { let mut app = App::default(); - app.data.radarr_data.search = Some("Test".into()); + app.data.radarr_data.add_movie_search = Some("Test".into()); AddMovieHandler::with( &DEFAULT_KEYBINDINGS.backspace.key, @@ -1354,7 +1354,10 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "Tes"); + assert_str_eq!( + app.data.radarr_data.add_movie_search.as_ref().unwrap().text, + "Tes" + ); } #[test] @@ -1389,7 +1392,7 @@ mod tests { #[test] fn test_add_movie_search_input_char_key() { let mut app = App::default(); - app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); + app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default()); AddMovieHandler::with( &Key::Char('h'), @@ -1399,7 +1402,10 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "h"); + assert_str_eq!( + app.data.radarr_data.add_movie_search.as_ref().unwrap().text, + "h" + ); } #[test] diff --git a/src/handlers/radarr_handlers/library/library_handler_tests.rs b/src/handlers/radarr_handlers/library/library_handler_tests.rs index e02cc46..246612b 100644 --- a/src/handlers/radarr_handlers/library/library_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/library_handler_tests.rs @@ -18,7 +18,6 @@ mod tests { use crate::test_handler_delegation; mod test_handle_scroll_up_and_down { - use crate::models::StatefulTable; use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; use super::*; @@ -33,55 +32,11 @@ mod tests { title, to_string ); - - #[rstest] - fn test_filtered_movies_scroll( - #[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key, - ) { - let mut app = App::default(); - let mut filtered_movies = StatefulTable::default(); - filtered_movies.set_items(simple_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.filtered_movies = Some(filtered_movies); - - LibraryHandler::with(&key, &mut app, &ActiveRadarrBlock::Movies, &None).handle(); - - assert_str_eq!( - app - .data - .radarr_data - .filtered_movies - .as_ref() - .unwrap() - .current_selection() - .title - .to_string(), - "Test 2" - ); - - LibraryHandler::with(&key, &mut app, &ActiveRadarrBlock::Movies, &None).handle(); - - assert_str_eq!( - app - .data - .radarr_data - .filtered_movies - .as_ref() - .unwrap() - .current_selection() - .title - .to_string(), - "Test 1" - ); - } } mod test_handle_home_end { use pretty_assertions::assert_eq; - use crate::models::StatefulTable; use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use super::*; @@ -97,63 +52,10 @@ mod tests { to_string ); - #[test] - fn test_filtered_movies_home_end() { - let mut app = App::default(); - let mut filtered_movies = StatefulTable::default(); - filtered_movies.set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.filtered_movies = Some(filtered_movies); - - LibraryHandler::with( - &DEFAULT_KEYBINDINGS.end.key, - &mut app, - &ActiveRadarrBlock::Movies, - &None, - ) - .handle(); - - assert_str_eq!( - app - .data - .radarr_data - .filtered_movies - .as_ref() - .unwrap() - .current_selection() - .title - .to_string(), - "Test 3" - ); - - LibraryHandler::with( - &DEFAULT_KEYBINDINGS.home.key, - &mut app, - &ActiveRadarrBlock::Movies, - &None, - ) - .handle(); - - assert_str_eq!( - app - .data - .radarr_data - .filtered_movies - .as_ref() - .unwrap() - .current_selection() - .title - .to_string(), - "Test 1" - ); - } - #[test] fn test_movie_search_box_home_end_keys() { let mut app = App::default(); - app.data.radarr_data.search = Some("Test".into()); + app.data.radarr_data.movies.search = Some("Test".into()); LibraryHandler::with( &DEFAULT_KEYBINDINGS.home.key, @@ -167,6 +69,7 @@ mod tests { *app .data .radarr_data + .movies .search .as_ref() .unwrap() @@ -187,6 +90,7 @@ mod tests { *app .data .radarr_data + .movies .search .as_ref() .unwrap() @@ -199,7 +103,7 @@ mod tests { #[test] fn test_movie_filter_box_home_end_keys() { let mut app = App::default(); - app.data.radarr_data.filter = Some("Test".into()); + app.data.radarr_data.movies.filter = Some("Test".into()); LibraryHandler::with( &DEFAULT_KEYBINDINGS.home.key, @@ -213,6 +117,7 @@ mod tests { *app .data .radarr_data + .movies .filter .as_ref() .unwrap() @@ -233,6 +138,7 @@ mod tests { *app .data .radarr_data + .movies .filter .as_ref() .unwrap() @@ -349,7 +255,7 @@ mod tests { #[test] fn test_movie_search_box_left_right_keys() { let mut app = App::default(); - app.data.radarr_data.search = Some("Test".into()); + app.data.radarr_data.movies.search = Some("Test".into()); LibraryHandler::with( &DEFAULT_KEYBINDINGS.left.key, @@ -363,6 +269,7 @@ mod tests { *app .data .radarr_data + .movies .search .as_ref() .unwrap() @@ -383,6 +290,7 @@ mod tests { *app .data .radarr_data + .movies .search .as_ref() .unwrap() @@ -395,7 +303,7 @@ mod tests { #[test] fn test_movie_filter_box_left_right_keys() { let mut app = App::default(); - app.data.radarr_data.filter = Some("Test".into()); + app.data.radarr_data.movies.filter = Some("Test".into()); LibraryHandler::with( &DEFAULT_KEYBINDINGS.left.key, @@ -409,6 +317,7 @@ mod tests { *app .data .radarr_data + .movies .filter .as_ref() .unwrap() @@ -429,6 +338,7 @@ mod tests { *app .data .radarr_data + .movies .filter .as_ref() .unwrap() @@ -443,7 +353,6 @@ mod tests { use pretty_assertions::assert_eq; use crate::extended_stateful_iterable_vec; - use crate::models::StatefulTable; use crate::network::radarr_network::RadarrEvent; use super::*; @@ -475,7 +384,7 @@ mod tests { Movie, HorizontallyScrollableText )); - app.data.radarr_data.search = Some("Test 2".into()); + app.data.radarr_data.movies.search = Some("Test 2".into()); LibraryHandler::with( &SUBMIT_KEY, @@ -505,7 +414,7 @@ mod tests { Movie, HorizontallyScrollableText )); - app.data.radarr_data.search = Some("Test 5".into()); + app.data.radarr_data.movies.search = Some("Test 5".into()); LibraryHandler::with( &SUBMIT_KEY, @@ -530,13 +439,15 @@ mod tests { let mut app = App::default(); app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); - let mut filtered_movies = StatefulTable::default(); - filtered_movies.set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.filtered_movies = Some(filtered_movies); - app.data.radarr_data.search = Some("Test 2".into()); + app + .data + .radarr_data + .movies + .set_filtered_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.movies.search = Some("Test 2".into()); LibraryHandler::with( &SUBMIT_KEY, @@ -547,15 +458,7 @@ mod tests { .handle(); assert_str_eq!( - app - .data - .radarr_data - .filtered_movies - .as_ref() - .unwrap() - .current_selection() - .title - .text, + app.data.radarr_data.movies.current_selection().title.text, "Test 2" ); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); @@ -574,7 +477,7 @@ mod tests { Movie, HorizontallyScrollableText )); - app.data.radarr_data.filter = Some("Test".into()); + app.data.radarr_data.movies.filter = Some("Test".into()); LibraryHandler::with( &SUBMIT_KEY, @@ -584,28 +487,21 @@ mod tests { ) .handle(); - assert!(app.data.radarr_data.filtered_movies.is_some()); + assert!(app.data.radarr_data.movies.filtered_items.is_some()); + assert!(!app.should_ignore_quit_key); assert_eq!( app .data .radarr_data - .filtered_movies + .movies + .filtered_items .as_ref() .unwrap() - .items .len(), 3 ); assert_str_eq!( - app - .data - .radarr_data - .filtered_movies - .as_ref() - .unwrap() - .current_selection() - .title - .text, + app.data.radarr_data.movies.current_selection().title.text, "Test 1" ); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); @@ -624,7 +520,7 @@ mod tests { Movie, HorizontallyScrollableText )); - app.data.radarr_data.filter = Some("Test 5".into()); + app.data.radarr_data.movies.filter = Some("Test 5".into()); LibraryHandler::with( &SUBMIT_KEY, @@ -634,7 +530,8 @@ mod tests { ) .handle(); - assert!(app.data.radarr_data.filtered_movies.is_none()); + assert!(!app.should_ignore_quit_key); + assert!(app.data.radarr_data.movies.filtered_items.is_none()); assert_eq!( app.get_current_route(), &ActiveRadarrBlock::FilterMoviesError.into() @@ -686,9 +583,10 @@ mod tests { mod test_handle_esc { use pretty_assertions::assert_eq; + use ratatui::widgets::TableState; use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data; - use crate::{assert_filter_reset, assert_search_reset}; + use crate::models::StatefulTable; use super::*; @@ -704,12 +602,13 @@ mod tests { app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(active_radarr_block.into()); app.data.radarr_data = create_test_radarr_data(); + app.data.radarr_data.movies.search = Some("Test".into()); 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); + assert_eq!(app.data.radarr_data.movies.search, None); } #[rstest] @@ -722,12 +621,20 @@ mod tests { app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(active_radarr_block.into()); app.data.radarr_data = create_test_radarr_data(); + app.data.radarr_data.movies = StatefulTable { + filter: Some("Test".into()), + filtered_items: Some(Vec::new()), + filtered_state: Some(TableState::default()), + ..StatefulTable::default() + }; 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_filter_reset!(app.data.radarr_data); + assert_eq!(app.data.radarr_data.movies.filter, None); + assert_eq!(app.data.radarr_data.movies.filtered_items, None); + assert_eq!(app.data.radarr_data.movies.filtered_state, None); } #[test] @@ -756,13 +663,22 @@ mod tests { app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.data.radarr_data = create_test_radarr_data(); + app.data.radarr_data.movies = StatefulTable { + search: Some("Test".into()), + filter: Some("Test".into()), + filtered_items: Some(Vec::new()), + filtered_state: Some(TableState::default()), + ..StatefulTable::default() + }; LibraryHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Movies, &None).handle(); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert!(app.error.text.is_empty()); - assert_search_reset!(app.data.radarr_data); - assert_filter_reset!(app.data.radarr_data); + assert_eq!(app.data.radarr_data.movies.search, None); + assert_eq!(app.data.radarr_data.movies.filter, None); + assert_eq!(app.data.radarr_data.movies.filtered_items, None); + assert_eq!(app.data.radarr_data.movies.filtered_state, None); } } @@ -778,6 +694,7 @@ mod tests { RadarrData, EDIT_MOVIE_SELECTION_BLOCKS, }; + use crate::models::StatefulTable; use crate::{assert_refresh_key, test_edit_movie_key}; use super::*; @@ -798,14 +715,17 @@ mod tests { app.get_current_route(), &ActiveRadarrBlock::SearchMovie.into() ); - assert!(app.data.radarr_data.is_searching); assert!(app.should_ignore_quit_key); - assert!(app.data.radarr_data.search.is_some()); + assert_eq!( + app.data.radarr_data.movies.search, + Some(HorizontallyScrollableText::default()) + ); } #[test] fn test_filter_movies_key() { let mut app = App::default(); + app.data.radarr_data.movies = StatefulTable::default(); LibraryHandler::with( &DEFAULT_KEYBINDINGS.filter.key, @@ -819,9 +739,8 @@ mod tests { 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.movies.filter.is_some()); } #[test] @@ -830,6 +749,8 @@ mod tests { app.should_ignore_quit_key = true; app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.data.radarr_data = create_test_radarr_data(); + app.data.radarr_data.movies = StatefulTable::default(); + app.data.radarr_data.movies.filter = Some("Test".into()); LibraryHandler::with( &DEFAULT_KEYBINDINGS.filter.key, @@ -843,10 +764,13 @@ mod tests { 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.is_none()); + assert_eq!( + app.data.radarr_data.movies.filter, + Some(HorizontallyScrollableText::default()) + ); + assert!(app.data.radarr_data.movies.filtered_items.is_none()); + assert!(app.data.radarr_data.movies.filtered_state.is_none()); } #[test] @@ -866,7 +790,7 @@ mod tests { &ActiveRadarrBlock::AddMovieSearchInput.into() ); assert!(app.should_ignore_quit_key); - assert!(app.data.radarr_data.search.is_some()); + assert!(app.data.radarr_data.add_movie_search.is_some()); } #[test] @@ -904,7 +828,7 @@ mod tests { #[test] fn test_search_movies_box_backspace_key() { let mut app = App::default(); - app.data.radarr_data.search = Some("Test".into()); + app.data.radarr_data.movies.search = Some("Test".into()); LibraryHandler::with( &DEFAULT_KEYBINDINGS.backspace.key, @@ -914,13 +838,17 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "Tes"); + assert_str_eq!( + app.data.radarr_data.movies.search.as_ref().unwrap().text, + "Tes" + ); } #[test] fn test_filter_movies_box_backspace_key() { let mut app = App::default(); - app.data.radarr_data.filter = Some("Test".into()); + app.data.radarr_data.movies = StatefulTable::default(); + app.data.radarr_data.movies.filter = Some("Test".into()); LibraryHandler::with( &DEFAULT_KEYBINDINGS.backspace.key, @@ -930,13 +858,16 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.filter.as_ref().unwrap().text, "Tes"); + assert_str_eq!( + app.data.radarr_data.movies.filter.as_ref().unwrap().text, + "Tes" + ); } #[test] fn test_search_movies_box_char_key() { let mut app = App::default(); - app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); + app.data.radarr_data.movies.search = Some(HorizontallyScrollableText::default()); LibraryHandler::with( &Key::Char('h'), @@ -946,13 +877,17 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "h"); + assert_str_eq!( + app.data.radarr_data.movies.search.as_ref().unwrap().text, + "h" + ); } #[test] fn test_filter_movies_box_char_key() { let mut app = App::default(); - app.data.radarr_data.filter = Some(HorizontallyScrollableText::default()); + app.data.radarr_data.movies = StatefulTable::default(); + app.data.radarr_data.movies.filter = Some(HorizontallyScrollableText::default()); LibraryHandler::with( &Key::Char('h'), @@ -962,7 +897,10 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.filter.as_ref().unwrap().text, "h"); + assert_str_eq!( + app.data.radarr_data.movies.filter.as_ref().unwrap().text, + "h" + ); } } diff --git a/src/handlers/radarr_handlers/library/mod.rs b/src/handlers/radarr_handlers/library/mod.rs index b406763..1689287 100644 --- a/src/handlers/radarr_handlers/library/mod.rs +++ b/src/handlers/radarr_handlers/library/mod.rs @@ -11,10 +11,9 @@ 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, StatefulTable}; +use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable}; use crate::network::radarr_network::RadarrEvent; -use crate::utils::strip_non_search_characters; -use crate::{filter_table, handle_text_box_keys, handle_text_box_left_right_keys, search_table}; +use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; mod add_movie_handler; mod delete_movie_handler; @@ -81,38 +80,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' fn handle_scroll_up(&mut self) { if self.active_radarr_block == &ActiveRadarrBlock::Movies { - if let Some(filtered_movies) = self.app.data.radarr_data.filtered_movies.as_mut() { - filtered_movies.scroll_up(); - } else { - self.app.data.radarr_data.movies.scroll_up() - } + self.app.data.radarr_data.movies.scroll_up() } } fn handle_scroll_down(&mut self) { if self.active_radarr_block == &ActiveRadarrBlock::Movies { - if let Some(filtered_movies) = self.app.data.radarr_data.filtered_movies.as_mut() { - filtered_movies.scroll_down(); - } else { - self.app.data.radarr_data.movies.scroll_down() - } + self.app.data.radarr_data.movies.scroll_down() } } fn handle_home(&mut self) { match self.active_radarr_block { - ActiveRadarrBlock::Movies => { - if let Some(filtered_movies) = self.app.data.radarr_data.filtered_movies.as_mut() { - filtered_movies.scroll_to_top(); - } else { - self.app.data.radarr_data.movies.scroll_to_top() - } - } + ActiveRadarrBlock::Movies => self.app.data.radarr_data.movies.scroll_to_top(), ActiveRadarrBlock::SearchMovie => { self .app .data .radarr_data + .movies .search .as_mut() .unwrap() @@ -123,6 +109,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' .app .data .radarr_data + .movies .filter .as_mut() .unwrap() @@ -134,17 +121,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' fn handle_end(&mut self) { match self.active_radarr_block { - ActiveRadarrBlock::Movies => { - if let Some(filtered_movies) = self.app.data.radarr_data.filtered_movies.as_mut() { - filtered_movies.scroll_to_bottom(); - } else { - self.app.data.radarr_data.movies.scroll_to_bottom() - } - } + ActiveRadarrBlock::Movies => self.app.data.radarr_data.movies.scroll_to_bottom(), ActiveRadarrBlock::SearchMovie => self .app .data .radarr_data + .movies .search .as_mut() .unwrap() @@ -153,6 +135,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' .app .data .radarr_data + .movies .filter .as_mut() .unwrap() @@ -179,14 +162,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' handle_text_box_left_right_keys!( self, self.key, - self.app.data.radarr_data.search.as_mut().unwrap() + self.app.data.radarr_data.movies.search.as_mut().unwrap() ) } ActiveRadarrBlock::FilterMovies => { handle_text_box_left_right_keys!( self, self.key, - self.app.data.radarr_data.filter.as_mut().unwrap() + self.app.data.radarr_data.movies.filter.as_mut().unwrap() ) } _ => (), @@ -199,24 +182,42 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' .app .push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()), ActiveRadarrBlock::SearchMovie => { - if self.app.data.radarr_data.filtered_movies.is_some() { - search_table!( - self.app, - filtered_movies, - ActiveRadarrBlock::SearchMovieError, - true - ); - } else { - search_table!(self.app, movies, ActiveRadarrBlock::SearchMovieError); + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + + if self.app.data.radarr_data.movies.search.is_some() { + let has_match = self + .app + .data + .radarr_data + .movies + .apply_search(|movie| &movie.title.text); + + if !has_match { + self + .app + .push_navigation_stack(ActiveRadarrBlock::SearchMovieError.into()); + } } } ActiveRadarrBlock::FilterMovies => { - filter_table!( - self.app, - movies, - filtered_movies, - ActiveRadarrBlock::FilterMoviesError - ); + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + + if self.app.data.radarr_data.movies.filter.is_some() { + let has_matches = self + .app + .data + .radarr_data + .movies + .apply_filter(|movie| &movie.title.text); + + if !has_matches { + self + .app + .push_navigation_stack(ActiveRadarrBlock::FilterMoviesError.into()); + } + } } ActiveRadarrBlock::UpdateAllMoviesPrompt => { if self.app.data.radarr_data.prompt_confirm { @@ -233,12 +234,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' match self.active_radarr_block { ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterMoviesError => { self.app.pop_navigation_stack(); - self.app.data.radarr_data.reset_filter(); + self.app.data.radarr_data.movies.reset_filter(); self.app.should_ignore_quit_key = false; } ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchMovieError => { self.app.pop_navigation_stack(); - self.app.data.radarr_data.reset_search(); + self.app.data.radarr_data.movies.reset_search(); self.app.should_ignore_quit_key = false; } ActiveRadarrBlock::UpdateAllMoviesPrompt => { @@ -246,8 +247,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' self.app.data.radarr_data.prompt_confirm = false; } _ => { - self.app.data.radarr_data.reset_search(); - self.app.data.radarr_data.reset_filter(); + self.app.data.radarr_data.movies.reset_search(); + self.app.data.radarr_data.movies.reset_filter(); handle_clear_errors(self.app); } } @@ -261,17 +262,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' self .app .push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); - self.app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); - self.app.data.radarr_data.is_searching = true; + self.app.data.radarr_data.movies.search = Some(HorizontallyScrollableText::default()); self.app.should_ignore_quit_key = true; } _ if *key == DEFAULT_KEYBINDINGS.filter.key => { 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.data.radarr_data.movies.reset_filter(); + self.app.data.radarr_data.movies.filter = Some(HorizontallyScrollableText::default()); self.app.should_ignore_quit_key = true; } _ if *key == DEFAULT_KEYBINDINGS.edit.key => { @@ -290,7 +289,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' self .app .push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); - self.app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); + self.app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default()); self.app.should_ignore_quit_key = true; } _ if *key == DEFAULT_KEYBINDINGS.update.key => { @@ -307,14 +306,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' handle_text_box_keys!( self, key, - self.app.data.radarr_data.search.as_mut().unwrap() + self.app.data.radarr_data.movies.search.as_mut().unwrap() ) } ActiveRadarrBlock::FilterMovies => { handle_text_box_keys!( self, key, - self.app.data.radarr_data.filter.as_mut().unwrap() + self.app.data.radarr_data.movies.filter.as_mut().unwrap() ) } _ => (), diff --git a/src/handlers/radarr_handlers/mod.rs b/src/handlers/radarr_handlers/mod.rs index 71d7e1d..19d05ce 100644 --- a/src/handlers/radarr_handlers/mod.rs +++ b/src/handlers/radarr_handlers/mod.rs @@ -179,44 +179,3 @@ macro_rules! search_table { } }; } - -#[macro_export] -macro_rules! filter_table { - ($app:expr, $source_table_ref:ident, $filter_table_ref:ident, $error_block:expr) => { - let empty_filter = match $app.data.radarr_data.filter.as_ref() { - Some(filter) if filter.text.is_empty() => true, - _ => false, - }; - let filter_matches = match $app.data.radarr_data.filter.as_ref() { - Some(filter) if !filter.text.is_empty() => { - let scrubbed_filter = strip_non_search_characters(&filter.text.clone()); - - $app - .data - .radarr_data - .$source_table_ref - .items - .iter() - .filter(|item| strip_non_search_characters(&item.title.text).contains(&scrubbed_filter)) - .cloned() - .collect() - } - _ => 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(); - let mut filter_table = StatefulTable::default(); - filter_table.set_items(filter_matches); - $app.data.radarr_data.$filter_table_ref = Some(filter_table); - } - }; -} diff --git a/src/handlers/radarr_handlers/radarr_handler_test_utils.rs b/src/handlers/radarr_handlers/radarr_handler_test_utils.rs index a71a3ae..88d6795 100644 --- a/src/handlers/radarr_handlers/radarr_handler_test_utils.rs +++ b/src/handlers/radarr_handlers/radarr_handler_test_utils.rs @@ -11,7 +11,6 @@ mod utils { (1111, "Any".to_owned()), ]), tags_map: BiMap::from_iter([(1, "test".to_owned())]), - filtered_movies: None, ..create_test_radarr_data() }; radarr_data.movies.set_items(vec![Movie { @@ -126,7 +125,6 @@ mod utils { (2222, "HD - 1080p".to_owned()), (1111, "Any".to_owned()), ]), - filtered_collections: None, ..create_test_radarr_data() }; radarr_data.collections.set_items(vec![Collection { diff --git a/src/handlers/radarr_handlers/radarr_handler_tests.rs b/src/handlers/radarr_handlers/radarr_handler_tests.rs index c500356..430c133 100644 --- a/src/handlers/radarr_handlers/radarr_handler_tests.rs +++ b/src/handlers/radarr_handlers/radarr_handler_tests.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use pretty_assertions::{assert_eq, assert_str_eq}; + use pretty_assertions::assert_eq; use rstest::rstest; use strum::IntoEnumIterator; @@ -8,249 +8,8 @@ mod tests { use crate::app::App; 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, StatefulTable}; - 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_macro() { - 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 2".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 2" - ); - 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_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 - .movies - .set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.search = Some("Test 5".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::SearchMovieError.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_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 - .movies - .set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - app.data.radarr_data.filter = Some("Test 2".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.is_some()); - assert_eq!( - app - .data - .radarr_data - .filtered_movies - .as_ref() - .unwrap() - .items - .len(), - 1 - ); - assert_str_eq!( - app.data.radarr_data.filtered_movies.as_ref().unwrap().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); - assert!(app.data.radarr_data.filter.is_none()); - } - - #[test] - fn test_filter_table_macro_reset_and_pop_on_empty_filter() { - 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("".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.is_none()); - assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.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_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.is_none()); - 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 - .movies - .set_items(extended_stateful_iterable_vec!( - Movie, - HorizontallyScrollableText - )); - 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.is_none()); - 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()); - } + use crate::test_handler_delegation; #[rstest] #[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Downloads)] @@ -375,9 +134,6 @@ mod tests { #[rstest] fn test_delegates_indexers_blocks_to_indexers_handler( - // Add these once implemented: - // ActiveRadarrBlock::AddIndexer, - // ActiveRadarrBlock::EditIndexer, #[values( ActiveRadarrBlock::DeleteIndexerPrompt, ActiveRadarrBlock::Indexers, diff --git a/src/models/mod.rs b/src/models/mod.rs index 0ec09d5..811554d 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -3,47 +3,51 @@ use std::fmt::{Debug, Display, Formatter}; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use ratatui::widgets::{ListState, TableState}; +use regex::Regex; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Number; pub mod radarr_models; pub mod servarr_data; -#[cfg(test)] -#[path = "model_tests.rs"] -mod model_tests; - -// Allowing dead code for now since we'll eventually be implementing additional Servarr support and we'll need it then -#[allow(dead_code)] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Route { - Radarr(ActiveRadarrBlock, Option), - Sonarr, - Readarr, - Lidarr, - Whisparr, - Bazarr, - Prowlarr, - Tautulli, -} - -pub trait Scrollable { - fn scroll_down(&mut self); - fn scroll_up(&mut self); - fn scroll_to_top(&mut self); - fn scroll_to_bottom(&mut self); -} - macro_rules! stateful_iterable { ($name:ident, $state:ty) => { #[derive(Default)] pub struct $name { pub state: $state, pub items: Vec, + pub filter: Option, + pub search: Option, + pub filtered_items: Option>, + pub filtered_state: Option<$state>, } impl Scrollable for $name { fn scroll_down(&mut self) { + if let Some(filtered_items) = self.filtered_items.as_ref() { + if filtered_items.is_empty() { + return; + } + + let selected_row = match self.filtered_state.as_ref().unwrap().selected() { + Some(i) => { + if i >= filtered_items.len() - 1 { + 0 + } else { + i + 1 + } + } + None => 0, + }; + + self + .filtered_state + .as_mut() + .unwrap() + .select(Some(selected_row)); + return; + } + if self.items.is_empty() { return; } @@ -63,6 +67,30 @@ macro_rules! stateful_iterable { } fn scroll_up(&mut self) { + if let Some(filtered_items) = self.filtered_items.as_ref() { + if filtered_items.is_empty() { + return; + } + + let selected_row = match self.filtered_state.as_ref().unwrap().selected() { + Some(i) => { + if i == 0 { + filtered_items.len() - 1 + } else { + i - 1 + } + } + None => 0, + }; + + self + .filtered_state + .as_mut() + .unwrap() + .select(Some(selected_row)); + return; + } + if self.items.is_empty() { return; } @@ -82,6 +110,15 @@ macro_rules! stateful_iterable { } fn scroll_to_top(&mut self) { + if let Some(filtered_items) = self.filtered_items.as_ref() { + if filtered_items.is_empty() { + return; + } + + self.filtered_state.as_mut().unwrap().select(Some(0)); + return; + } + if self.items.is_empty() { return; } @@ -90,6 +127,19 @@ macro_rules! stateful_iterable { } fn scroll_to_bottom(&mut self) { + if let Some(filtered_items) = self.filtered_items.as_ref() { + if filtered_items.is_empty() { + return; + } + + self + .filtered_state + .as_mut() + .unwrap() + .select(Some(filtered_items.len() - 1)); + return; + } + if self.items.is_empty() { return; } @@ -98,6 +148,7 @@ macro_rules! stateful_iterable { } } + #[allow(dead_code)] impl $name where T: Clone + PartialEq + Eq + Debug, @@ -119,25 +170,128 @@ macro_rules! stateful_iterable { } } + pub fn set_filtered_items(&mut self, filtered_items: Vec) { + self.filtered_items = Some(filtered_items); + let mut filtered_state: $state = Default::default(); + filtered_state.select(Some(0)); + self.filtered_state = Some(filtered_state); + } + + pub fn select_index(&mut self, index: Option) { + if let Some(filtered_state) = &mut self.filtered_state { + filtered_state.select(index); + } else { + self.state.select(index); + } + } + pub fn current_selection(&self) -> &T { - &self.items[self.state.selected().unwrap_or(0)] + if let Some(filtered_items) = &self.filtered_items { + &filtered_items[self + .filtered_state + .as_ref() + .unwrap() + .selected() + .unwrap_or(0)] + } else { + &self.items[self.state.selected().unwrap_or(0)] + } + } + + pub fn apply_filter(&mut self, filter_field: fn(&T) -> &str) -> bool { + let filter_matches = match self.filter { + Some(ref filter) if !filter.text.is_empty() => { + let scrubbed_filter = strip_non_search_characters(&filter.text.clone()); + + self + .items + .iter() + .filter(|item| { + strip_non_search_characters(filter_field(&item)).contains(&scrubbed_filter) + }) + .cloned() + .collect() + } + _ => Vec::new(), + }; + + self.filter = None; + + if filter_matches.is_empty() { + return false; + } + + self.set_filtered_items(filter_matches); + return true; + } + + pub fn reset_filter(&mut self) { + self.filter = None; + self.filtered_items = None; + self.filtered_state = None; + } + + pub fn apply_search(&mut self, search_field: fn(&T) -> &str) -> bool { + let search_index = if let Some(search) = self.search.as_ref() { + let search_string = search.text.clone().to_lowercase(); + + self + .filtered_items + .as_ref() + .unwrap_or(&self.items) + .iter() + .position(|item| { + strip_non_search_characters(search_field(&item)).contains(&search_string) + }) + } else { + None + }; + + self.search = None; + + if search_index.is_none() { + return false; + } + + self.select_index(search_index); + return true; + } + + pub fn reset_search(&mut self) { + self.search = None; } } }; } +#[cfg(test)] +#[path = "model_tests.rs"] +mod model_tests; + +// Allowing dead code for now since we'll eventually be implementing additional Servarr support, and we'll need it then +#[allow(dead_code)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Route { + Radarr(ActiveRadarrBlock, Option), + Sonarr, + Readarr, + Lidarr, + Whisparr, + Bazarr, + Prowlarr, + Tautulli, +} + +pub trait Scrollable { + fn scroll_down(&mut self); + fn scroll_up(&mut self); + fn scroll_to_top(&mut self); + fn scroll_to_bottom(&mut self); +} + stateful_iterable!(StatefulList, ListState); stateful_iterable!(StatefulTable, TableState); -impl StatefulTable -where - T: Clone + PartialEq + Eq + Debug, -{ - pub fn select_index(&mut self, index: Option) { - self.state.select(index); - } -} - #[derive(Default)] pub struct ScrollableText { pub items: Vec, @@ -429,3 +583,10 @@ where "Unable to convert Number to i64: {num:?}" ))) } + +pub fn strip_non_search_characters(input: &str) -> String { + Regex::new(r"[^a-zA-Z0-9.,/'\-:\s]") + .unwrap() + .replace_all(&input.to_lowercase(), "") + .to_string() +} diff --git a/src/models/model_tests.rs b/src/models/model_tests.rs index 77538a5..dcaaf2d 100644 --- a/src/models/model_tests.rs +++ b/src/models/model_tests.rs @@ -3,14 +3,15 @@ mod tests { use std::cell::RefCell; use pretty_assertions::{assert_eq, assert_str_eq}; + use ratatui::widgets::{ListState, TableState}; use serde::de::value::Error as ValueError; use serde::de::value::F64Deserializer; use serde::de::value::I64Deserializer; use serde::de::IntoDeserializer; use serde_json::to_string; - use crate::models::from_i64; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; + use crate::models::{from_i64, strip_non_search_characters}; use crate::models::{ BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, StatefulList, StatefulTable, TabRoute, TabState, @@ -46,6 +47,59 @@ mod tests { stateful_table.scroll_to_bottom(); } + #[test] + fn test_stateful_table_filtered_scrolling_on_empty_table_performs_no_op() { + let mut filtered_stateful_table: StatefulTable = StatefulTable { + filtered_items: Some(Vec::new()), + filtered_state: Some(TableState::default()), + ..StatefulTable::default() + }; + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + None + ); + + filtered_stateful_table.scroll_up(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + None + ); + + filtered_stateful_table.scroll_down(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + None + ); + + filtered_stateful_table.scroll_to_top(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + None + ); + + filtered_stateful_table.scroll_to_bottom(); + } + #[test] fn test_stateful_table_scroll() { let mut stateful_table = create_test_stateful_table(); @@ -77,6 +131,86 @@ mod tests { assert_eq!(stateful_table.state.selected(), Some(0)); } + #[test] + fn test_stateful_table_filtered_items_scroll() { + let mut filtered_stateful_table = create_test_filtered_stateful_table(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + + filtered_stateful_table.scroll_down(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(1) + ); + + filtered_stateful_table.scroll_down(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + + filtered_stateful_table.scroll_up(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(1) + ); + + filtered_stateful_table.scroll_up(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + + filtered_stateful_table.scroll_to_bottom(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(1) + ); + + filtered_stateful_table.scroll_to_top(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + } + #[test] fn test_stateful_table_set_items() { let items_vec = vec!["Test 1", "Test 2", "Test 3"]; @@ -97,6 +231,27 @@ mod tests { assert_eq!(stateful_table.state.selected(), Some(2)); } + #[test] + fn test_stateful_table_set_filtered_items() { + let filtered_items_vec = vec!["Test 1", "Test 2", "Test 3"]; + let mut filtered_stateful_table: StatefulTable<&str> = StatefulTable::default(); + + filtered_stateful_table.set_filtered_items(filtered_items_vec.clone()); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + assert_eq!( + filtered_stateful_table.filtered_items, + Some(filtered_items_vec.clone()) + ); + } + #[test] fn test_stateful_table_current_selection() { let mut stateful_table = create_test_stateful_table(); @@ -108,6 +263,27 @@ mod tests { assert_str_eq!(stateful_table.current_selection(), &stateful_table.items[1]); } + #[test] + fn test_filtered_stateful_table_current_selection() { + let mut filtered_stateful_table = create_test_filtered_stateful_table(); + + assert_str_eq!( + filtered_stateful_table.current_selection(), + &filtered_stateful_table.filtered_items.as_ref().unwrap()[0] + ); + + filtered_stateful_table + .filtered_state + .as_mut() + .unwrap() + .select(Some(1)); + + assert_str_eq!( + filtered_stateful_table.current_selection(), + &filtered_stateful_table.filtered_items.as_ref().unwrap()[1] + ); + } + #[test] fn test_stateful_table_select_index() { let mut stateful_table = create_test_stateful_table(); @@ -123,6 +299,42 @@ mod tests { assert_eq!(stateful_table.state.selected(), None); } + #[test] + fn test_filtered_stateful_table_select_index() { + let mut filtered_stateful_table = create_test_filtered_stateful_table(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + + filtered_stateful_table.select_index(Some(1)); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(1) + ); + + filtered_stateful_table.select_index(None); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + None + ); + } + #[test] fn test_stateful_table_scroll_up() { let mut stateful_table = create_test_stateful_table(); @@ -139,7 +351,150 @@ mod tests { } #[test] - fn test_stateful_list_scrolling_on_empty_table_performs_no_op() { + fn test_filtered_stateful_table_scroll_up() { + let mut filtered_stateful_table = create_test_filtered_stateful_table(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + + filtered_stateful_table.scroll_up(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(1) + ); + + filtered_stateful_table.scroll_up(); + + assert_eq!( + filtered_stateful_table + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + } + + #[test] + fn test_stateful_table_apply_filter() { + let mut stateful_table: StatefulTable<&str> = StatefulTable::default(); + stateful_table.set_items(vec!["this", "is", "a", "test"]); + stateful_table.filter = Some("i".into()); + let expected_items = vec!["this", "is"]; + let mut expected_state = TableState::default(); + expected_state.select(Some(0)); + + let has_matches = stateful_table.apply_filter(|&item| item); + + assert_eq!(stateful_table.filter, None); + assert_eq!(stateful_table.filtered_items, Some(expected_items)); + assert_eq!(stateful_table.filtered_state, Some(expected_state)); + assert!(has_matches); + } + + #[test] + fn test_stateful_table_apply_filter_no_matches() { + let mut stateful_table: StatefulTable<&str> = StatefulTable::default(); + stateful_table.set_items(vec!["this", "is", "a", "test"]); + stateful_table.filter = Some("z".into()); + + let has_matches = stateful_table.apply_filter(|&item| item); + + assert_eq!(stateful_table.filter, None); + assert_eq!(stateful_table.filtered_items, None); + assert_eq!(stateful_table.filtered_state, None); + assert!(!has_matches); + } + + #[test] + fn test_stateful_table_reset_filter() { + let mut stateful_table = create_test_filtered_stateful_table(); + stateful_table.reset_filter(); + + assert_eq!(stateful_table.filter, None); + assert_eq!(stateful_table.filtered_items, None); + assert_eq!(stateful_table.filtered_state, None); + } + + #[test] + fn test_stateful_table_apply_search() { + let mut stateful_table: StatefulTable<&str> = StatefulTable::default(); + stateful_table.set_items(vec!["this", "is", "a", "test"]); + stateful_table.search = Some("test".into()); + let mut expected_state = TableState::default(); + expected_state.select(Some(3)); + + let has_match = stateful_table.apply_search(|&item| item); + + assert_eq!(stateful_table.search, None); + assert_eq!(stateful_table.state, expected_state); + assert!(has_match); + } + + #[test] + fn test_stateful_table_apply_search_no_match() { + let mut stateful_table: StatefulTable<&str> = StatefulTable::default(); + stateful_table.set_items(vec!["this", "is", "a", "test"]); + stateful_table.search = Some("shi-mon-a!".into()); + + let has_match = stateful_table.apply_search(|&item| item); + + assert_eq!(stateful_table.search, None); + assert!(!has_match); + } + + #[test] + fn test_filtered_stateful_table_apply_search() { + let mut stateful_table: StatefulTable<&str> = StatefulTable::default(); + stateful_table.set_filtered_items(vec!["this", "is", "a", "test"]); + stateful_table.search = Some("test".into()); + let mut expected_state = TableState::default(); + expected_state.select(Some(3)); + + let has_match = stateful_table.apply_search(|&item| item); + + assert_eq!(stateful_table.search, None); + assert_eq!(stateful_table.filtered_state, Some(expected_state)); + assert!(has_match); + } + + #[test] + fn test_filtered_stateful_table_apply_search_no_match() { + let mut stateful_table: StatefulTable<&str> = StatefulTable::default(); + stateful_table.set_filtered_items(vec!["this", "is", "a", "test"]); + stateful_table.search = Some("shi-mon-a!".into()); + let mut expected_state = TableState::default(); + expected_state.select(Some(0)); + + let has_match = stateful_table.apply_search(|&item| item); + + assert_eq!(stateful_table.search, None); + assert_eq!(stateful_table.filtered_state, Some(expected_state)); + assert!(!has_match); + } + + #[test] + fn test_stateful_table_reset_search() { + let mut stateful_table = create_test_stateful_table(); + stateful_table.search = Some("test".into()); + stateful_table.reset_search(); + + assert_eq!(stateful_table.search, None); + } + + #[test] + fn test_stateful_list_scrolling_on_empty_list_performs_no_op() { let mut stateful_list: StatefulList = StatefulList::default(); assert_eq!(stateful_list.state.selected(), None); @@ -159,6 +514,59 @@ mod tests { stateful_list.scroll_to_bottom(); } + #[test] + fn test_filtered_stateful_list_scrolling_on_empty_list_performs_no_op() { + let mut filtered_stateful_list: StatefulList = StatefulList { + filtered_items: Some(Vec::new()), + filtered_state: Some(ListState::default()), + ..StatefulList::default() + }; + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + None + ); + + filtered_stateful_list.scroll_up(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + None + ); + + filtered_stateful_list.scroll_down(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + None + ); + + filtered_stateful_list.scroll_to_top(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + None + ); + + filtered_stateful_list.scroll_to_bottom(); + } + #[test] fn test_stateful_list_scroll() { let mut stateful_list = create_test_stateful_list(); @@ -190,6 +598,86 @@ mod tests { assert_eq!(stateful_list.state.selected(), Some(0)); } + #[test] + fn test_filtered_stateful_list_scroll() { + let mut filtered_stateful_list = create_test_filtered_stateful_list(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + + filtered_stateful_list.scroll_down(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(1) + ); + + filtered_stateful_list.scroll_down(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + + filtered_stateful_list.scroll_up(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(1) + ); + + filtered_stateful_list.scroll_up(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + + filtered_stateful_list.scroll_to_bottom(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(1) + ); + + filtered_stateful_list.scroll_to_top(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + } + #[test] fn test_stateful_list_set_items() { let items_vec = vec!["Test 1", "Test 2", "Test 3"]; @@ -210,6 +698,27 @@ mod tests { assert_eq!(stateful_list.state.selected(), Some(2)); } + #[test] + fn test_stateful_list_set_filtered_items() { + let filtered_items_vec = vec!["Test 1", "Test 2", "Test 3"]; + let mut filtered_stateful_list: StatefulList<&str> = StatefulList::default(); + + filtered_stateful_list.set_filtered_items(filtered_items_vec.clone()); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + assert_eq!( + filtered_stateful_list.filtered_items, + Some(filtered_items_vec.clone()) + ); + } + #[test] fn test_stateful_list_current_selection() { let mut stateful_list = create_test_stateful_list(); @@ -221,19 +730,234 @@ mod tests { assert_str_eq!(stateful_list.current_selection(), &stateful_list.items[1]); } + #[test] + fn test_filtered_stateful_list_current_selection() { + let mut filtered_stateful_list = create_test_filtered_stateful_list(); + + assert_str_eq!( + filtered_stateful_list.current_selection(), + &filtered_stateful_list.filtered_items.as_ref().unwrap()[0] + ); + + filtered_stateful_list + .filtered_state + .as_mut() + .unwrap() + .select(Some(1)); + + assert_str_eq!( + filtered_stateful_list.current_selection(), + &filtered_stateful_list.filtered_items.as_ref().unwrap()[1] + ); + } + + #[test] + fn test_stateful_list_select_index() { + let mut stateful_list = create_test_stateful_list(); + + assert_eq!(stateful_list.state.selected(), Some(0)); + + stateful_list.select_index(Some(1)); + + assert_eq!(stateful_list.state.selected(), Some(1)); + + stateful_list.select_index(None); + + assert_eq!(stateful_list.state.selected(), None); + } + + #[test] + fn test_filtered_stateful_list_select_index() { + let mut filtered_stateful_list = create_test_filtered_stateful_list(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + + filtered_stateful_list.select_index(Some(1)); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(1) + ); + + filtered_stateful_list.select_index(None); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + None + ); + } + #[test] fn test_stateful_list_scroll_up() { - let mut stateful_table = create_test_stateful_table(); + let mut stateful_list = create_test_stateful_list(); - assert_eq!(stateful_table.state.selected(), Some(0)); + assert_eq!(stateful_list.state.selected(), Some(0)); - stateful_table.scroll_up(); + stateful_list.scroll_up(); - assert_eq!(stateful_table.state.selected(), Some(1)); + assert_eq!(stateful_list.state.selected(), Some(1)); - stateful_table.scroll_up(); + stateful_list.scroll_up(); - assert_eq!(stateful_table.state.selected(), Some(0)); + assert_eq!(stateful_list.state.selected(), Some(0)); + } + + #[test] + fn test_filtered_stateful_list_scroll_up() { + let mut filtered_stateful_list = create_test_filtered_stateful_list(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + + filtered_stateful_list.scroll_up(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(1) + ); + + filtered_stateful_list.scroll_up(); + + assert_eq!( + filtered_stateful_list + .filtered_state + .as_ref() + .unwrap() + .selected(), + Some(0) + ); + } + + #[test] + fn test_stateful_list_apply_filter() { + let mut stateful_list: StatefulList<&str> = StatefulList::default(); + stateful_list.set_items(vec!["this", "is", "a", "test"]); + stateful_list.filter = Some("i".into()); + let expected_items = vec!["this", "is"]; + let mut expected_state = ListState::default(); + expected_state.select(Some(0)); + + let has_matches = stateful_list.apply_filter(|&item| item); + + assert_eq!(stateful_list.filter, None); + assert_eq!(stateful_list.filtered_items, Some(expected_items)); + assert_eq!(stateful_list.filtered_state, Some(expected_state)); + assert!(has_matches); + } + + #[test] + fn test_stateful_list_apply_filter_no_matches() { + let mut stateful_list: StatefulList<&str> = StatefulList::default(); + stateful_list.set_items(vec!["this", "is", "a", "test"]); + stateful_list.filter = Some("z".into()); + + let has_matches = stateful_list.apply_filter(|&item| item); + + assert_eq!(stateful_list.filter, None); + assert_eq!(stateful_list.filtered_items, None); + assert_eq!(stateful_list.filtered_state, None); + assert!(!has_matches); + } + + #[test] + fn test_stateful_list_reset_filter() { + let mut stateful_list = create_test_filtered_stateful_list(); + stateful_list.reset_filter(); + + assert_eq!(stateful_list.filter, None); + assert_eq!(stateful_list.filtered_items, None); + assert_eq!(stateful_list.filtered_state, None); + } + + #[test] + fn test_stateful_list_apply_search() { + let mut stateful_list: StatefulList<&str> = StatefulList::default(); + stateful_list.set_items(vec!["this", "is", "a", "test"]); + stateful_list.search = Some("test".into()); + let mut expected_state = ListState::default(); + expected_state.select(Some(3)); + + let has_match = stateful_list.apply_search(|&item| item); + + assert_eq!(stateful_list.search, None); + assert_eq!(stateful_list.state, expected_state); + assert!(has_match); + } + + #[test] + fn test_stateful_list_apply_search_no_match() { + let mut stateful_list: StatefulList<&str> = StatefulList::default(); + stateful_list.set_items(vec!["this", "is", "a", "test"]); + stateful_list.search = Some("shi-mon-a!".into()); + + let has_match = stateful_list.apply_search(|&item| item); + + assert_eq!(stateful_list.search, None); + assert!(!has_match); + } + + #[test] + fn test_filtered_stateful_list_apply_search() { + let mut stateful_list: StatefulList<&str> = StatefulList::default(); + stateful_list.set_filtered_items(vec!["this", "is", "a", "test"]); + stateful_list.search = Some("test".into()); + let mut expected_state = ListState::default(); + expected_state.select(Some(3)); + + let has_match = stateful_list.apply_search(|&item| item); + + assert_eq!(stateful_list.search, None); + assert_eq!(stateful_list.filtered_state, Some(expected_state)); + assert!(has_match); + } + + #[test] + fn test_filtered_stateful_list_apply_search_no_match() { + let mut stateful_list: StatefulList<&str> = StatefulList::default(); + stateful_list.set_filtered_items(vec!["this", "is", "a", "test"]); + stateful_list.search = Some("shi-mon-a!".into()); + let mut expected_state = ListState::default(); + expected_state.select(Some(0)); + + let has_match = stateful_list.apply_search(|&item| item); + + assert_eq!(stateful_list.search, None); + assert_eq!(stateful_list.filtered_state, Some(expected_state)); + assert!(!has_match); + } + + #[test] + fn test_stateful_list_reset_search() { + let mut stateful_list = create_test_stateful_list(); + stateful_list.search = Some("test".into()); + stateful_list.reset_search(); + + assert_eq!(stateful_list.search, None); } #[test] @@ -771,10 +1495,32 @@ mod tests { stateful_table } + fn create_test_filtered_stateful_table() -> StatefulTable<&'static str> { + let mut stateful_table = StatefulTable::default(); + stateful_table.set_filtered_items(vec!["Test 1", "Test 2"]); + + stateful_table + } + fn create_test_stateful_list() -> StatefulList<&'static str> { let mut stateful_list = StatefulList::default(); stateful_list.set_items(vec!["Test 1", "Test 2"]); stateful_list } + + fn create_test_filtered_stateful_list() -> StatefulList<&'static str> { + let mut stateful_list = StatefulList::default(); + stateful_list.set_filtered_items(vec!["Test 1", "Test 2"]); + + stateful_list + } + + #[test] + fn test_strip_non_alphanumeric_characters() { + assert_eq!( + strip_non_search_characters("Te$t S7r!ng::'~-@_`,(.)/*}^&%#+="), + "tet s7rng::'-,./".to_owned() + ) + } } diff --git a/src/models/servarr_data/radarr/modals.rs b/src/models/servarr_data/radarr/modals.rs index a093d84..fb44826 100644 --- a/src/models/servarr_data/radarr/modals.rs +++ b/src/models/servarr_data/radarr/modals.rs @@ -133,11 +133,7 @@ impl From<&RadarrData<'_>> for EditMovieModal { minimum_availability, quality_profile_id, .. - } = if let Some(filtered_movies) = radarr_data.filtered_movies.as_ref() { - filtered_movies.current_selection() - } else { - radarr_data.movies.current_selection() - }; + } = radarr_data.movies.current_selection(); edit_movie_modal .minimum_availability_list @@ -249,11 +245,7 @@ impl From<&RadarrData<'_>> for EditCollectionModal { minimum_availability, quality_profile_id, .. - } = if let Some(filtered_collections) = radarr_data.filtered_collections.as_ref() { - filtered_collections.current_selection() - } else { - radarr_data.collections.current_selection() - }; + } = radarr_data.collections.current_selection(); edit_collection_modal.path = root_folder_path.clone().unwrap_or_default().into(); edit_collection_modal.monitored = Some(*monitored); diff --git a/src/models/servarr_data/radarr/modals_tests.rs b/src/models/servarr_data/radarr/modals_tests.rs index 26861aa..03dc309 100644 --- a/src/models/servarr_data/radarr/modals_tests.rs +++ b/src/models/servarr_data/radarr/modals_tests.rs @@ -117,7 +117,7 @@ mod test { (1111, "Any".to_owned()), ]), tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]), - filtered_movies: None, + movies: StatefulTable::default(), ..create_test_radarr_data() }; let movie = Movie { @@ -130,9 +130,7 @@ mod test { }; if test_filtered_movies { - let mut filtered_movies = StatefulTable::default(); - filtered_movies.set_items(vec![movie]); - radarr_data.filtered_movies = Some(filtered_movies); + radarr_data.movies.set_filtered_items(vec![movie]); } else { radarr_data.movies.set_items(vec![movie]); } @@ -209,7 +207,7 @@ mod test { (2222, "HD - 1080p".to_owned()), (1111, "Any".to_owned()), ]), - filtered_collections: None, + collections: StatefulTable::default(), ..create_test_radarr_data() }; let collection = Collection { @@ -222,9 +220,7 @@ mod test { }; if test_filtered_collections { - let mut filtered_collections = StatefulTable::default(); - filtered_collections.set_items(vec![collection]); - radarr_data.filtered_collections = Some(filtered_collections); + radarr_data.collections.set_filtered_items(vec![collection]); } else { radarr_data.collections.set_items(vec![collection]); } diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index f33f56b..592d249 100644 --- a/src/models/servarr_data/radarr/radarr_data.rs +++ b/src/models/servarr_data/radarr/radarr_data.rs @@ -50,16 +50,13 @@ pub struct RadarrData<'a> { pub updates: ScrollableText, pub main_tabs: TabState, pub movie_info_tabs: TabState, - pub search: Option, - pub filter: Option, + pub add_movie_search: Option, pub add_movie_modal: Option, pub add_searched_movies: Option>, pub edit_movie_modal: Option, pub edit_collection_modal: Option, pub edit_indexer_modal: Option, pub edit_root_folder: Option, - pub filtered_collections: Option>, - pub filtered_movies: Option>, pub indexer_settings: Option, pub indexer_test_all_results: Option>, pub movie_details_modal: Option, @@ -67,8 +64,6 @@ pub struct RadarrData<'a> { pub prompt_confirm_action: Option, pub delete_movie_files: bool, pub add_list_exclusion: bool, - pub is_searching: bool, - pub is_filtering: bool, } impl<'a> RadarrData<'a> { @@ -77,22 +72,6 @@ impl<'a> RadarrData<'a> { self.add_list_exclusion = false; } - pub fn reset_search(&mut self) { - self.is_searching = false; - self.search = None; - self.filter = None; - self.filtered_movies = None; - self.filtered_collections = None; - self.add_searched_movies = None; - } - - pub fn reset_filter(&mut self) { - self.is_filtering = false; - self.filter = None; - self.filtered_movies = None; - self.filtered_collections = None; - } - pub fn reset_movie_info_tabs(&mut self) { self.movie_details_modal = None; self.movie_info_tabs.index = 0; @@ -119,21 +98,16 @@ impl<'a> Default for RadarrData<'a> { tasks: StatefulTable::default(), queued_events: StatefulTable::default(), updates: ScrollableText::default(), - search: None, - filter: None, + add_movie_search: None, add_movie_modal: None, add_searched_movies: None, edit_movie_modal: None, edit_collection_modal: None, edit_indexer_modal: None, edit_root_folder: None, - filtered_collections: None, - filtered_movies: None, indexer_settings: None, indexer_test_all_results: None, movie_details_modal: None, - is_searching: false, - is_filtering: false, prompt_confirm: false, prompt_confirm_action: None, delete_movie_files: false, @@ -505,16 +479,3 @@ impl From<(ActiveRadarrBlock, Option)> for Route { Route::Radarr(value.0, value.1) } } - -#[allow(dead_code)] // Returning to this work tomorrow -pub struct EditIndexerSettings { - pub allow_hardcoded_subs: bool, - pub availability_delay: HorizontallyScrollableText, - pub id: HorizontallyScrollableText, - pub maximum_size: HorizontallyScrollableText, - pub minimum_age: HorizontallyScrollableText, - pub prefer_indexer_flags: bool, - pub retention: HorizontallyScrollableText, - pub rss_sync_interval: HorizontallyScrollableText, - pub whitelisted_hardcoded_subs: HorizontallyScrollableText, -} diff --git a/src/models/servarr_data/radarr/radarr_data_tests.rs b/src/models/servarr_data/radarr/radarr_data_tests.rs index 5269296..6f33cde 100644 --- a/src/models/servarr_data/radarr/radarr_data_tests.rs +++ b/src/models/servarr_data/radarr/radarr_data_tests.rs @@ -16,8 +16,8 @@ mod tests { use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData}; use crate::models::Route; + use crate::assert_movie_info_tabs_reset; use crate::models::BlockSelectionState; - use crate::{assert_filter_reset, assert_movie_info_tabs_reset, assert_search_reset}; #[test] fn test_from_tuple_to_route_with_context() { @@ -43,24 +43,6 @@ mod tests { assert!(!radarr_data.add_list_exclusion); } - #[test] - fn test_reset_search() { - let mut radarr_data = utils::create_test_radarr_data(); - - radarr_data.reset_search(); - - assert_search_reset!(radarr_data); - } - - #[test] - fn test_reset_filter() { - let mut radarr_data = utils::create_test_radarr_data(); - - radarr_data.reset_filter(); - - assert_filter_reset!(radarr_data); - } - #[test] fn test_reset_movie_info_tabs() { let mut radarr_data = utils::create_test_radarr_data(); @@ -91,21 +73,16 @@ mod tests { assert!(radarr_data.tasks.items.is_empty()); assert!(radarr_data.queued_events.items.is_empty()); assert!(radarr_data.updates.get_text().is_empty()); - assert!(radarr_data.search.is_none()); - assert!(radarr_data.filter.is_none()); + assert!(radarr_data.add_movie_search.is_none()); assert!(radarr_data.add_movie_modal.is_none()); assert!(radarr_data.add_searched_movies.is_none()); assert!(radarr_data.edit_movie_modal.is_none()); assert!(radarr_data.edit_collection_modal.is_none()); assert!(radarr_data.edit_root_folder.is_none()); assert!(radarr_data.edit_indexer_modal.is_none()); - 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); assert!(radarr_data.prompt_confirm_action.is_none()); assert!(!radarr_data.prompt_confirm); assert!(!radarr_data.delete_movie_files); diff --git a/src/models/servarr_data/radarr/radarr_test_utils.rs b/src/models/servarr_data/radarr/radarr_test_utils.rs index 585bd28..c555308 100644 --- a/src/models/servarr_data/radarr/radarr_test_utils.rs +++ b/src/models/servarr_data/radarr/radarr_test_utils.rs @@ -1,8 +1,7 @@ #[cfg(test)] pub mod utils { use crate::models::radarr_models::{ - AddMovieSearchResult, Collection, CollectionMovie, Credit, Movie, MovieHistoryItem, Release, - ReleaseField, + AddMovieSearchResult, CollectionMovie, Credit, MovieHistoryItem, Release, ReleaseField, }; use crate::models::servarr_data::radarr::modals::MovieDetailsModal; use crate::models::servarr_data::radarr::radarr_data::RadarrData; @@ -31,30 +30,15 @@ pub mod utils { movie_details_modal.sort_ascending = Some(true); let mut radarr_data = RadarrData { - is_searching: true, - is_filtering: true, delete_movie_files: true, add_list_exclusion: true, - search: Some("test search".into()), - filter: Some("test filter".into()), + add_movie_search: Some("test search".into()), edit_root_folder: Some("test path".into()), movie_details_modal: Some(movie_details_modal), - filtered_movies: Some(StatefulTable::default()), - filtered_collections: Some(StatefulTable::default()), add_searched_movies: Some(StatefulTable::default()), ..RadarrData::default() }; radarr_data.movie_info_tabs.index = 1; - radarr_data - .filtered_movies - .as_mut() - .unwrap() - .set_items(vec![Movie::default()]); - radarr_data - .filtered_collections - .as_mut() - .unwrap() - .set_items(vec![Collection::default()]); radarr_data .add_searched_movies .as_mut() @@ -70,28 +54,6 @@ pub mod utils { radarr_data } - #[macro_export] - macro_rules! assert_search_reset { - ($radarr_data:expr) => { - assert!(!$radarr_data.is_searching); - assert!($radarr_data.search.is_none()); - assert!($radarr_data.filter.is_none()); - assert!($radarr_data.filtered_movies.is_none()); - assert!($radarr_data.filtered_collections.is_none()); - assert!($radarr_data.add_searched_movies.is_none()); - }; - } - - #[macro_export] - macro_rules! assert_filter_reset { - ($radarr_data:expr) => { - assert!(!$radarr_data.is_filtering); - assert!($radarr_data.filter.is_none()); - assert!($radarr_data.filtered_movies.is_none()); - assert!($radarr_data.filtered_collections.is_none()); - }; - } - #[macro_export] macro_rules! assert_movie_info_tabs_reset { ($radarr_data:expr) => { diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 9cef674..224cae3 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -1433,7 +1433,7 @@ impl<'a, 'b> Network<'a, 'b> { .await .data .radarr_data - .search + .add_movie_search .clone() .ok_or(anyhow!("Encountered a race condition")); @@ -1711,65 +1711,22 @@ impl<'a, 'b> Network<'a, 'b> { async fn extract_movie_id(&mut self) -> (i64, i64) { let app = self.app.lock().await; - if app.data.radarr_data.filtered_movies.is_some() { - ( - app - .data - .radarr_data - .filtered_movies - .as_ref() - .unwrap() - .current_selection() - .id, - app - .data - .radarr_data - .filtered_movies - .as_ref() - .unwrap() - .current_selection() - .tmdb_id, - ) - } else { - ( - app.data.radarr_data.movies.current_selection().id, - app.data.radarr_data.movies.current_selection().tmdb_id, - ) - } + ( + app.data.radarr_data.movies.current_selection().id, + app.data.radarr_data.movies.current_selection().tmdb_id, + ) } async fn extract_collection_id(&mut self) -> i64 { - if self + self .app .lock() .await .data .radarr_data - .filtered_collections - .is_some() - { - self - .app - .lock() - .await - .data - .radarr_data - .filtered_collections - .as_ref() - .unwrap() - .current_selection() - .id - } else { - self - .app - .lock() - .await - .data - .radarr_data - .collections - .current_selection() - .id - } + .collections + .current_selection() + .id } async fn append_movie_id_param(&mut self, resource: &str) -> String { diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index 91c955c..3163496 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -454,7 +454,7 @@ mod test { &resource, ) .await; - app_arc.lock().await.data.radarr_data.search = Some("test term".into()); + app_arc.lock().await.data.radarr_data.add_movie_search = Some("test term".into()); let mut network = Network::new(&app_arc, CancellationToken::new()); network @@ -520,7 +520,7 @@ mod test { ); let (async_server, app_arc, _server) = mock_radarr_api(RequestMethod::Get, None, Some(json!([])), None, &resource).await; - app_arc.lock().await.data.radarr_data.search = Some("test term".into()); + app_arc.lock().await.data.radarr_data.add_movie_search = Some("test term".into()); let mut network = Network::new(&app_arc, CancellationToken::new()); network @@ -2639,12 +2639,12 @@ mod test { async fn test_extract_movie_id_filtered_movies() { let app_arc = Arc::new(Mutex::new(App::default())); let mut filtered_movies = StatefulTable::default(); - filtered_movies.set_items(vec![Movie { + filtered_movies.set_filtered_items(vec![Movie { id: 1, tmdb_id: 2, ..Movie::default() }]); - app_arc.lock().await.data.radarr_data.filtered_movies = Some(filtered_movies); + app_arc.lock().await.data.radarr_data.movies = filtered_movies; let mut network = Network::new(&app_arc, CancellationToken::new()); assert_eq!(network.extract_movie_id().await, (1, 2)); @@ -2672,11 +2672,11 @@ mod test { async fn test_extract_collection_id_filtered_collection() { let app_arc = Arc::new(Mutex::new(App::default())); let mut filtered_collections = StatefulTable::default(); - filtered_collections.set_items(vec![Collection { + filtered_collections.set_filtered_items(vec![Collection { id: 1, ..Collection::default() }]); - app_arc.lock().await.data.radarr_data.filtered_collections = Some(filtered_collections); + app_arc.lock().await.data.radarr_data.collections = filtered_collections; let mut network = Network::new(&app_arc, CancellationToken::new()); assert_eq!(network.extract_collection_id().await, 1); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 6d39e0e..2b5e304 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -220,32 +220,6 @@ pub fn draw_drop_down_popup( draw_popup_over(f, app, area, background_fn, drop_down_fn, 20, 30); } -pub fn draw_error_popup_over( - f: &mut Frame<'_>, - app: &mut App<'_>, - area: Rect, - message: &str, - 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<'_>, message: &str) { - let prompt_area = centered_rect(25, 8, f.size()); - f.render_widget(Clear, prompt_area); - f.render_widget(background_block(), prompt_area); - - let error_message = Paragraph::new(Text::from(message)) - .block(title_block_centered("Error").failure()) - .failure() - .bold() - .wrap(Wrap { trim: false }) - .alignment(Alignment::Center); - - f.render_widget(error_message, prompt_area); -} - fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -> Rect { f.render_widget(title_block(title), area); @@ -485,12 +459,3 @@ pub fn draw_input_box_popup( .block(borderless_block()); f.render_widget(help, help_area); } - -pub fn draw_error_message_popup(f: &mut Frame<'_>, area: Rect, error_msg: &str) { - let input = Paragraph::new(error_msg) - .failure() - .alignment(Alignment::Center) - .block(layout_block()); - - f.render_widget(input, area); -} diff --git a/src/ui/radarr_ui/collections/collection_details_ui.rs b/src/ui/radarr_ui/collections/collection_details_ui.rs index 9343d8f..623fcf4 100644 --- a/src/ui/radarr_ui/collections/collection_details_ui.rs +++ b/src/ui/radarr_ui/collections/collection_details_ui.rs @@ -72,12 +72,7 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) Layout::vertical([Constraint::Percentage(25), Constraint::Fill(0)]) .margin(1) .areas(area); - let collection_selection = - if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() { - filtered_collections.current_selection() - } else { - app.data.radarr_data.collections.current_selection() - }; + let collection_selection = app.data.radarr_data.collections.current_selection(); let quality_profile = app .data .radarr_data diff --git a/src/ui/radarr_ui/collections/edit_collection_ui.rs b/src/ui/radarr_ui/collections/edit_collection_ui.rs index 8c888ed..116ae55 100644 --- a/src/ui/radarr_ui/collections/edit_collection_ui.rs +++ b/src/ui/radarr_ui/collections/edit_collection_ui.rs @@ -89,36 +89,22 @@ impl DrawUi for EditCollectionUi { } fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - let (collection_title, collection_overview) = - if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() { - ( - filtered_collections.current_selection().title.text.clone(), - filtered_collections - .current_selection() - .overview - .clone() - .unwrap_or_default(), - ) - } else { - ( - app - .data - .radarr_data - .collections - .current_selection() - .title - .text - .clone(), - app - .data - .radarr_data - .collections - .current_selection() - .overview - .clone() - .unwrap_or_default(), - ) - }; + let collection_title = app + .data + .radarr_data + .collections + .current_selection() + .title + .text + .clone(); + let collection_overview = app + .data + .radarr_data + .collections + .current_selection() + .overview + .clone() + .unwrap_or_default(); let title = format!("Edit - {collection_title}"); 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/collections/mod.rs b/src/ui/radarr_ui/collections/mod.rs index 0f4fc77..244a449 100644 --- a/src/ui/radarr_ui/collections/mod.rs +++ b/src/ui/radarr_ui/collections/mod.rs @@ -12,10 +12,10 @@ use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsU use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi; use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; +use crate::ui::widgets::error_message::ErrorMessage; use crate::ui::widgets::managarr_table::ManagarrTable; use crate::ui::{ - draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, - draw_prompt_popup_over, DrawUi, + draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, DrawUi, }; mod collection_details_ui; @@ -101,19 +101,13 @@ impl DrawUi for CollectionsUi { } 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() - } else if !app.data.radarr_data.collections.items.is_empty() { - app.data.radarr_data.collections.current_selection().clone() - } else { - Collection::default() - }; - let quality_profile_map = &app.data.radarr_data.quality_profile_map; - let content = match app.data.radarr_data.filtered_collections.as_mut() { - Some(filtered_collections) if !app.data.radarr_data.is_filtering => Some(filtered_collections), - _ => Some(&mut app.data.radarr_data.collections), + let current_selection = if !app.data.radarr_data.collections.items.is_empty() { + app.data.radarr_data.collections.current_selection().clone() + } else { + Collection::default() }; + let quality_profile_map = &app.data.radarr_data.quality_profile_map; + let content = Some(&mut app.data.radarr_data.collections); let collections_table_footer = app .data .radarr_data @@ -187,7 +181,7 @@ fn draw_collection_search_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) f, area, "Search", - app.data.radarr_data.search.as_ref().unwrap(), + app.data.radarr_data.collections.search.as_ref().unwrap(), ); } @@ -196,14 +190,17 @@ fn draw_filter_collections_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) f, area, "Filter", - app.data.radarr_data.filter.as_ref().unwrap(), + app.data.radarr_data.collections.filter.as_ref().unwrap(), ) } fn draw_search_collection_error_box(f: &mut Frame<'_>, _: &mut App<'_>, area: Rect) { - draw_error_message_popup(f, area, "Collection not found!"); + f.render_widget(ErrorMessage::new("Collection not found!"), area); } 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!"); + f.render_widget( + ErrorMessage::new("No collections found matching the given filter!"), + area, + ); } diff --git a/src/ui/radarr_ui/library/add_movie_ui.rs b/src/ui/radarr_ui/library/add_movie_ui.rs index a063a43..5d470ea 100644 --- a/src/ui/radarr_ui/library/add_movie_ui.rs +++ b/src/ui/radarr_ui/library/add_movie_ui.rs @@ -17,11 +17,12 @@ use crate::ui::utils::{ title_block_centered, }; use crate::ui::widgets::button::Button; +use crate::ui::widgets::error_message::ErrorMessage; use crate::ui::widgets::input_box::InputBox; use crate::ui::widgets::managarr_table::ManagarrTable; +use crate::ui::widgets::popup::Popup; use crate::ui::{ - draw_drop_down_popup, draw_error_popup, draw_error_popup_over, draw_large_popup_over, - draw_medium_popup_over, draw_selectable_list, DrawUi, + draw_drop_down_popup, draw_large_popup_over, draw_medium_popup_over, draw_selectable_list, DrawUi, }; use crate::utils::convert_runtime; use crate::{render_selectable_input_box, App}; @@ -68,13 +69,17 @@ impl DrawUi for AddMovieUi { draw_medium_popup_over(f, app, area, draw_add_movie_search, draw_confirmation_popup); } } - ActiveRadarrBlock::AddMovieAlreadyInLibrary => draw_error_popup_over( - f, - app, - area, - "This film is already in your library", - draw_add_movie_search, - ), + ActiveRadarrBlock::AddMovieAlreadyInLibrary => { + draw_add_movie_search(f, app, area); + f.render_widget( + Popup::new( + ErrorMessage::new("This film is already in your library"), + 25, + 8, + ), + f.size(), + ); + } _ => (), }; @@ -108,11 +113,11 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { ]) .margin(1) .areas(area); - let block_content = &app.data.radarr_data.search.as_ref().unwrap().text; + let block_content = &app.data.radarr_data.add_movie_search.as_ref().unwrap().text; let offset = *app .data .radarr_data - .search + .add_movie_search .as_ref() .unwrap() .offset @@ -197,9 +202,11 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let help_paragraph = Paragraph::new(help_text) .block(borderless_block()) .alignment(Alignment::Center); + let error_message = ErrorMessage::new("No movies found matching your query!"); + let error_message_popup = Popup::new(error_message, 25, 8); f.render_widget(layout_block(), results_area); - draw_error_popup(f, "No movies found matching your query!"); + f.render_widget(error_message_popup, f.size()); f.render_widget(help_paragraph, help_area); } ActiveRadarrBlock::AddMovieSearchResults diff --git a/src/ui/radarr_ui/library/edit_movie_ui.rs b/src/ui/radarr_ui/library/edit_movie_ui.rs index 7cfc458..c77d667 100644 --- a/src/ui/radarr_ui/library/edit_movie_ui.rs +++ b/src/ui/radarr_ui/library/edit_movie_ui.rs @@ -19,8 +19,8 @@ use crate::ui::widgets::button::Button; use crate::ui::widgets::checkbox::Checkbox; use crate::ui::widgets::input_box::InputBox; use crate::ui::{ - draw_drop_down_popup, draw_large_popup_over_background_fn_with_ui, - draw_medium_popup_over, draw_popup, draw_selectable_list, DrawUi, + draw_drop_down_popup, draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, + draw_popup, draw_selectable_list, DrawUi, }; #[cfg(test)] @@ -91,31 +91,21 @@ impl DrawUi for EditMovieUi { } fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - let (movie_title, movie_overview) = - if let Some(filtered_movies) = app.data.radarr_data.filtered_movies.as_ref() { - ( - filtered_movies.current_selection().title.text.clone(), - filtered_movies.current_selection().overview.clone(), - ) - } else { - ( - app - .data - .radarr_data - .movies - .current_selection() - .title - .text - .clone(), - app - .data - .radarr_data - .movies - .current_selection() - .overview - .clone(), - ) - }; + let movie_title = app + .data + .radarr_data + .movies + .current_selection() + .title + .text + .clone(); + let movie_overview = app + .data + .radarr_data + .movies + .current_selection() + .overview + .clone(); let title = format!("Edit - {movie_title}"); 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/library/mod.rs b/src/ui/radarr_ui/library/mod.rs index 708368f..1b4e838 100644 --- a/src/ui/radarr_ui/library/mod.rs +++ b/src/ui/radarr_ui/library/mod.rs @@ -12,10 +12,10 @@ 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::utils::{get_width_from_percentage, layout_block_top_border}; +use crate::ui::widgets::error_message::ErrorMessage; use crate::ui::widgets::managarr_table::ManagarrTable; use crate::ui::{ - draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, - draw_prompt_popup_over, DrawUi, + draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, DrawUi, }; use crate::utils::{convert_runtime, convert_to_gb}; @@ -92,21 +92,15 @@ impl DrawUi for LibraryUi { } 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() - } else if !app.data.radarr_data.movies.items.is_empty() { - app.data.radarr_data.movies.current_selection().clone() - } else { - Movie::default() - }; + let current_selection = if !app.data.radarr_data.movies.items.is_empty() { + app.data.radarr_data.movies.current_selection().clone() + } else { + Movie::default() + }; let quality_profile_map = &app.data.radarr_data.quality_profile_map; let tags_map = &app.data.radarr_data.tags_map; let downloads_vec = &app.data.radarr_data.downloads.items; - let content = match app.data.radarr_data.filtered_movies.as_mut() { - Some(filtered_movies) if !app.data.radarr_data.is_filtering => Some(filtered_movies), - _ => Some(&mut app.data.radarr_data.movies), - }; + let content = Some(&mut app.data.radarr_data.movies); let help_footer = app .data .radarr_data @@ -203,7 +197,7 @@ fn draw_movie_search_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { f, area, "Search", - app.data.radarr_data.search.as_ref().unwrap(), + app.data.radarr_data.movies.search.as_ref().unwrap(), ); } @@ -212,14 +206,17 @@ fn draw_filter_movies_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { f, area, "Filter", - app.data.radarr_data.filter.as_ref().unwrap(), + app.data.radarr_data.movies.filter.as_ref().unwrap(), ) } fn draw_search_movie_error_box(f: &mut Frame<'_>, _: &mut App<'_>, area: Rect) { - draw_error_message_popup(f, area, "Movie not found!"); + f.render_widget(ErrorMessage::new("Movie not found!"), area); } 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!"); + f.render_widget( + ErrorMessage::new("No movies found matching the given filter!"), + area, + ); } diff --git a/src/ui/radarr_ui/system/system_details_ui.rs b/src/ui/radarr_ui/system/system_details_ui.rs index 980bb35..0f1d013 100644 --- a/src/ui/radarr_ui/system/system_details_ui.rs +++ b/src/ui/radarr_ui/system/system_details_ui.rs @@ -85,11 +85,6 @@ fn draw_logs_popup(f: &mut Frame<'_>, 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| { let help_footer = Some(build_context_clue_string(&SYSTEM_TASKS_CONTEXT_CLUES)); - // let context_area = draw_help_footer_and_get_content_area( - // f, - // area, - // help_footer, - // ); let tasks_row_mapping = |task: &Task| { let task_props = extract_task_props(task); diff --git a/src/ui/widgets/button.rs b/src/ui/widgets/button.rs index f029706..024cb6d 100644 --- a/src/ui/widgets/button.rs +++ b/src/ui/widgets/button.rs @@ -40,7 +40,7 @@ impl<'a> Button<'a> { self } - fn render_button_with_icon(&self, area: Rect, buf: &mut Buffer) { + fn render_button_with_icon(self, area: Rect, buf: &mut Buffer) { let [title_area, icon_area] = Layout::horizontal([ Constraint::Length(self.title.len() as u16), Constraint::Percentage(25), @@ -63,7 +63,7 @@ impl<'a> Button<'a> { } } - fn render_labeled_button(&self, area: Rect, buf: &mut Buffer) { + fn render_labeled_button(self, area: Rect, buf: &mut Buffer) { let [label_area, button_area] = Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area); let label_paragraph = Paragraph::new(Text::from(format!("\n{}: ", self.label.unwrap()))) @@ -79,7 +79,7 @@ impl<'a> Button<'a> { } } - fn render_button(&self, area: Rect, buf: &mut Buffer) { + fn render_button(self, area: Rect, buf: &mut Buffer) { Paragraph::new(Text::from(self.title)) .block(layout_block()) .alignment(Alignment::Center) diff --git a/src/ui/widgets/checkbox.rs b/src/ui/widgets/checkbox.rs index 7747b7c..5fa280a 100644 --- a/src/ui/widgets/checkbox.rs +++ b/src/ui/widgets/checkbox.rs @@ -31,7 +31,7 @@ impl<'a> Checkbox<'a> { self } - fn render_checkbox(&self, area: Rect, buf: &mut Buffer) { + fn render_checkbox(self, area: Rect, buf: &mut Buffer) { let check = if self.is_checked { "✔" } else { "" }; let [label_area, checkbox_area] = Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area); diff --git a/src/ui/widgets/error_message.rs b/src/ui/widgets/error_message.rs new file mode 100644 index 0000000..c631a0b --- /dev/null +++ b/src/ui/widgets/error_message.rs @@ -0,0 +1,36 @@ +use crate::ui::styles::ManagarrStyle; +use crate::ui::utils::title_block_centered; +use ratatui::buffer::Buffer; +use ratatui::layout::{Alignment, Rect}; +use ratatui::style::Stylize; +use ratatui::text::Text; +use ratatui::widgets::{Paragraph, Widget}; + +pub struct ErrorMessage<'a> { + text: Text<'a>, +} + +impl<'a> ErrorMessage<'a> { + pub fn new(message: T) -> Self + where + T: Into>, + { + ErrorMessage { + text: message.into(), + } + } + + fn render_error_message(self, area: Rect, buf: &mut Buffer) { + Paragraph::new(self.text) + .failure() + .alignment(Alignment::Center) + .block(title_block_centered("Error").failure().bold()) + .render(area, buf); + } +} + +impl<'a> Widget for ErrorMessage<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + self.render_error_message(area, buf); + } +} diff --git a/src/ui/widgets/input_box.rs b/src/ui/widgets/input_box.rs index 8ce702e..e7c2e80 100644 --- a/src/ui/widgets/input_box.rs +++ b/src/ui/widgets/input_box.rs @@ -47,12 +47,12 @@ impl<'a> InputBox<'a> { self.label = Some(label); self } - + pub fn offset(mut self, offset: usize) -> InputBox<'a> { self.offset = offset; self } - + pub fn cursor_after_string(mut self, cursor_after_string: bool) -> InputBox<'a> { self.cursor_after_string = cursor_after_string; self @@ -89,7 +89,7 @@ impl<'a> InputBox<'a> { } } - fn render_input_box(&self, area: Rect, buf: &mut Buffer) { + fn render_input_box(self, area: Rect, buf: &mut Buffer) { let style = if matches!(self.is_highlighted, Some(true)) && matches!(self.is_selected, Some(false)) { Style::new().system_function().bold() @@ -99,7 +99,7 @@ impl<'a> InputBox<'a> { let input_box_paragraph = Paragraph::new(Text::from(self.content)) .style(style) - .block(self.block.clone()); + .block(self.block); if let Some(label) = self.label { let [label_area, text_box_area] = diff --git a/src/ui/widgets/loading_block.rs b/src/ui/widgets/loading_block.rs index 7dfb5c4..41df126 100644 --- a/src/ui/widgets/loading_block.rs +++ b/src/ui/widgets/loading_block.rs @@ -14,14 +14,14 @@ impl<'a> LoadingBlock<'a> { Self { is_loading, block } } - fn render_loading_block(&self, area: Rect, buf: &mut Buffer) { + fn render_loading_block(self, area: Rect, buf: &mut Buffer) { if self.is_loading { Paragraph::new(Text::from("\n\n Loading ...\n\n")) .system_function() - .block(self.block.clone()) + .block(self.block) .render(area, buf); } else { - self.block.clone().render(area, buf); + self.block.render(area, buf); } } } diff --git a/src/ui/widgets/managarr_table.rs b/src/ui/widgets/managarr_table.rs index e7c8f69..5a56286 100644 --- a/src/ui/widgets/managarr_table.rs +++ b/src/ui/widgets/managarr_table.rs @@ -86,12 +86,12 @@ where self } - pub fn highlight_rows(mut self, hightlight_rows: bool) -> Self { - self.highlight_rows = hightlight_rows; + pub fn highlight_rows(mut self, highlight_rows: bool) -> Self { + self.highlight_rows = highlight_rows; self } - fn render_table(&mut self, area: Rect, buf: &mut Buffer) { + fn render_table(self, area: Rect, buf: &mut Buffer) { let table_area = if let Some(ref footer) = self.footer { let [content_area, footer_area] = Layout::vertical([Constraint::Fill(0), Constraint::Length(2)]) @@ -109,18 +109,26 @@ where }; let loading_block = LoadingBlock::new(self.is_loading, self.block.clone()); - if let Some(ref mut content) = self.content { - if !content.items.is_empty() { - let rows = content.items.iter().map(&self.row_mapper); + if let Some(content) = self.content { + let (table_contents, table_state) = if content.filtered_items.is_some() { + ( + content.filtered_items.as_ref().unwrap(), + content.filtered_state.as_mut().unwrap(), + ) + } else { + (&content.items, &mut content.state) + }; + if !table_contents.is_empty() { + let rows = table_contents.iter().map(&self.row_mapper); - let headers = Row::new(self.table_headers.clone()) + let headers = Row::new(self.table_headers) .default() .bold() .bottom_margin(0); let mut table = Table::new(rows, &self.constraints) .header(headers) - .block(self.block.clone()); + .block(self.block); if self.highlight_rows { table = table @@ -128,7 +136,7 @@ where .highlight_symbol(HIGHLIGHT_SYMBOL); } - StatefulWidget::render(table, table_area, buf, &mut content.state); + StatefulWidget::render(table, table_area, buf, table_state); } else { loading_block.render(table_area, buf); } @@ -142,7 +150,7 @@ impl<'a, T, F> Widget for ManagarrTable<'a, T, F> where F: Fn(&T) -> Row<'a>, { - fn render(mut self, area: Rect, buf: &mut Buffer) { + fn render(self, area: Rect, buf: &mut Buffer) { self.render_table(area, buf); } } diff --git a/src/ui/widgets/mod.rs b/src/ui/widgets/mod.rs index 19659b2..7e85c31 100644 --- a/src/ui/widgets/mod.rs +++ b/src/ui/widgets/mod.rs @@ -1,5 +1,7 @@ pub(super) mod button; pub(super) mod checkbox; +pub(super) mod error_message; pub(super) mod input_box; pub(super) mod loading_block; pub(super) mod managarr_table; +pub(super) mod popup; diff --git a/src/ui/widgets/popup.rs b/src/ui/widgets/popup.rs new file mode 100644 index 0000000..74a30d4 --- /dev/null +++ b/src/ui/widgets/popup.rs @@ -0,0 +1,34 @@ +use crate::ui::utils::{background_block, centered_rect}; +use ratatui::buffer::Buffer; +use ratatui::layout::Rect; +use ratatui::widgets::{Clear, Widget}; + +pub struct Popup { + widget: T, + percent_x: u16, + percent_y: u16, +} + +impl Popup { + pub fn new(widget: T, percent_x: u16, percent_y: u16) -> Self { + Self { + widget, + percent_x, + percent_y, + } + } + + fn render_popup(self, area: Rect, buf: &mut Buffer) { + let popup_area = centered_rect(self.percent_x, self.percent_y, area); + + Clear.render(popup_area, buf); + background_block().render(popup_area, buf); + self.widget.render(popup_area, buf); + } +} + +impl Widget for Popup { + fn render(self, area: Rect, buf: &mut Buffer) { + self.render_popup(area, buf); + } +} diff --git a/src/utils.rs b/src/utils.rs index 8c7a774..6e046c2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,7 +2,6 @@ use log::LevelFilter; use log4rs::append::file::FileAppender; use log4rs::config::{Appender, Root}; use log4rs::encode::pattern::PatternEncoder; -use regex::Regex; #[cfg(test)] #[path = "utils_tests.rs"] @@ -37,10 +36,3 @@ pub fn convert_runtime(runtime: i64) -> (i64, i64) { (hours, minutes) } - -pub fn strip_non_search_characters(input: &str) -> String { - Regex::new(r"[^a-zA-Z0-9.,/'\-:\s]") - .unwrap() - .replace_all(&input.to_lowercase(), "") - .to_string() -} diff --git a/src/utils_tests.rs b/src/utils_tests.rs index 64bc321..6c74458 100644 --- a/src/utils_tests.rs +++ b/src/utils_tests.rs @@ -2,7 +2,7 @@ mod tests { use pretty_assertions::assert_eq; - use crate::utils::{convert_runtime, convert_to_gb, strip_non_search_characters}; + use crate::utils::{convert_runtime, convert_to_gb}; #[test] fn test_convert_to_gb() { @@ -17,12 +17,4 @@ mod tests { assert_eq!(hours, 2); assert_eq!(minutes, 34); } - - #[test] - fn test_strip_non_alphanumeric_characters() { - assert_eq!( - strip_non_search_characters("Te$t S7r!ng::'~-@_`,(.)/*}^&%#+="), - "tet s7rng::'-,./".to_owned() - ) - } }