Refactored the filter and search logic to follow the established modal logic and added some refactored functions to the UI module as well to clean up the UI code too

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