Refactored table filtering and searching so that they are now relative to the table being filtered/searched on. Also created two new widgets for error messages and popups to make life easier moving forward. Going to refactor table sorting into StatefulTable's as well so all tables can be searched, filtered, and sorted moving forwards.

This commit is contained in:
2024-02-11 19:02:18 -07:00
parent 5973f4d685
commit adda82f7f3
38 changed files with 1561 additions and 1165 deletions
+8 -17
View File
@@ -182,23 +182,14 @@ impl<'a> App<'a> {
} }
async fn populate_movie_collection_table(&mut self) { async fn populate_movie_collection_table(&mut self) {
let collection_movies = let collection_movies = self
if let Some(filtered_collections) = self.data.radarr_data.filtered_collections.as_ref() { .data
filtered_collections .radarr_data
.current_selection() .collections
.clone() .current_selection()
.movies .clone()
.unwrap_or_default() .movies
} else { .unwrap_or_default();
self
.data
.radarr_data
.collections
.current_selection()
.clone()
.movies
.unwrap_or_default()
};
self self
.data .data
.radarr_data .radarr_data
+8 -7
View File
@@ -8,7 +8,6 @@ mod tests {
use crate::app::App; use crate::app::App;
use crate::models::radarr_models::{Collection, CollectionMovie, Credit, Release}; use crate::models::radarr_models::{Collection, CollectionMovie, Credit, Release};
use crate::models::servarr_data::radarr::modals::MovieDetailsModal; use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
use crate::models::StatefulTable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::network::NetworkEvent; use crate::network::NetworkEvent;
@@ -666,12 +665,14 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_populate_movie_collection_table_filtered() { async fn test_populate_movie_collection_table_filtered() {
let mut app = App::default(); let mut app = App::default();
let mut filtered_collections = StatefulTable::default(); app
filtered_collections.set_items(vec![Collection { .data
movies: Some(vec![CollectionMovie::default()]), .radarr_data
..Collection::default() .collections
}]); .set_filtered_items(vec![Collection {
app.data.radarr_data.filtered_collections = Some(filtered_collections); movies: Some(vec![CollectionMovie::default()]),
..Collection::default()
}]);
app.populate_movie_collection_table().await; app.populate_movie_collection_table().await;
@@ -19,7 +19,6 @@ mod tests {
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
use rstest::rstest; use rstest::rstest;
use crate::models::StatefulTable;
use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
use super::*; use super::*;
@@ -34,55 +33,11 @@ mod tests {
title, title,
to_string 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 { mod test_handle_home_end {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use crate::models::StatefulTable;
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
use super::*; use super::*;
@@ -98,63 +53,10 @@ mod tests {
to_string 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] #[test]
fn test_collection_search_box_home_end_keys() { fn test_collection_search_box_home_end_keys() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.search = Some("Test".into()); app.data.radarr_data.collections.search = Some("Test".into());
CollectionsHandler::with( CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.home.key, &DEFAULT_KEYBINDINGS.home.key,
@@ -168,6 +70,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.collections
.search .search
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -188,6 +91,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.collections
.search .search
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -200,7 +104,7 @@ mod tests {
#[test] #[test]
fn test_collection_filter_box_home_end_keys() { fn test_collection_filter_box_home_end_keys() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.filter = Some("Test".into()); app.data.radarr_data.collections.filter = Some("Test".into());
CollectionsHandler::with( CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.home.key, &DEFAULT_KEYBINDINGS.home.key,
@@ -214,6 +118,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.collections
.filter .filter
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -234,6 +139,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.collections
.filter .filter
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -326,7 +232,7 @@ mod tests {
#[test] #[test]
fn test_collection_search_box_left_right_keys() { fn test_collection_search_box_left_right_keys() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.search = Some("Test".into()); app.data.radarr_data.collections.search = Some("Test".into());
CollectionsHandler::with( CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.left.key, &DEFAULT_KEYBINDINGS.left.key,
@@ -340,6 +246,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.collections
.search .search
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -360,6 +267,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.collections
.search .search
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -372,7 +280,7 @@ mod tests {
#[test] #[test]
fn test_collection_filter_box_left_right_keys() { fn test_collection_filter_box_left_right_keys() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.filter = Some("Test".into()); app.data.radarr_data.collections.filter = Some("Test".into());
CollectionsHandler::with( CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.left.key, &DEFAULT_KEYBINDINGS.left.key,
@@ -386,6 +294,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.collections
.filter .filter
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -406,6 +315,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.collections
.filter .filter
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -417,7 +327,6 @@ mod tests {
} }
mod test_handle_submit { mod test_handle_submit {
use crate::models::StatefulTable;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
@@ -457,7 +366,7 @@ mod tests {
Collection, Collection,
HorizontallyScrollableText HorizontallyScrollableText
)); ));
app.data.radarr_data.search = Some("Test 2".into()); app.data.radarr_data.collections.search = Some("Test 2".into());
CollectionsHandler::with( CollectionsHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -496,7 +405,7 @@ mod tests {
Collection, Collection,
HorizontallyScrollableText HorizontallyScrollableText
)); ));
app.data.radarr_data.search = Some("Test 5".into()); app.data.radarr_data.collections.search = Some("Test 5".into());
CollectionsHandler::with( CollectionsHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -527,13 +436,15 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
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(ActiveRadarrBlock::SearchCollection.into());
let mut filtered_collections = StatefulTable::default(); app
filtered_collections.set_items(extended_stateful_iterable_vec!( .data
Collection, .radarr_data
HorizontallyScrollableText .collections
)); .set_items(extended_stateful_iterable_vec!(
app.data.radarr_data.filtered_collections = Some(filtered_collections); Collection,
app.data.radarr_data.search = Some("Test 2".into()); HorizontallyScrollableText
));
app.data.radarr_data.collections.search = Some("Test 2".into());
CollectionsHandler::with( CollectionsHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -547,9 +458,7 @@ mod tests {
app app
.data .data
.radarr_data .radarr_data
.filtered_collections .collections
.as_ref()
.unwrap()
.current_selection() .current_selection()
.title .title
.text, .text,
@@ -574,7 +483,7 @@ mod tests {
Collection, Collection,
HorizontallyScrollableText HorizontallyScrollableText
)); ));
app.data.radarr_data.filter = Some("Test".into()); app.data.radarr_data.collections.filter = Some("Test".into());
CollectionsHandler::with( CollectionsHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -584,15 +493,16 @@ mod tests {
) )
.handle(); .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!( assert_eq!(
app app
.data .data
.radarr_data .radarr_data
.filtered_collections .collections
.filtered_items
.as_ref() .as_ref()
.unwrap() .unwrap()
.items
.len(), .len(),
3 3
); );
@@ -600,9 +510,7 @@ mod tests {
app app
.data .data
.radarr_data .radarr_data
.filtered_collections .collections
.as_ref()
.unwrap()
.current_selection() .current_selection()
.title .title
.text, .text,
@@ -627,7 +535,7 @@ mod tests {
Collection, Collection,
HorizontallyScrollableText HorizontallyScrollableText
)); ));
app.data.radarr_data.filter = Some("Test 5".into()); app.data.radarr_data.collections.filter = Some("Test 5".into());
CollectionsHandler::with( CollectionsHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -637,7 +545,8 @@ mod tests {
) )
.handle(); .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!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::FilterCollectionsError.into() &ActiveRadarrBlock::FilterCollectionsError.into()
@@ -695,9 +604,10 @@ mod tests {
mod test_handle_esc { mod test_handle_esc {
use pretty_assertions::assert_eq; 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::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::*; use super::*;
@@ -716,6 +626,7 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(active_radarr_block.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();
app.data.radarr_data.collections.search = Some("Test".into());
CollectionsHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); CollectionsHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle();
@@ -724,7 +635,7 @@ mod tests {
&ActiveRadarrBlock::Collections.into() &ActiveRadarrBlock::Collections.into()
); );
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert_search_reset!(app.data.radarr_data); assert_eq!(app.data.radarr_data.collections.search, None);
} }
#[rstest] #[rstest]
@@ -740,6 +651,12 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
app.push_navigation_stack(active_radarr_block.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();
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(); CollectionsHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle();
@@ -748,7 +665,9 @@ mod tests {
&ActiveRadarrBlock::Collections.into() &ActiveRadarrBlock::Collections.into()
); );
assert!(!app.should_ignore_quit_key); 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] #[test]
@@ -780,6 +699,13 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
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 = 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(); CollectionsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Collections, &None).handle();
@@ -788,8 +714,10 @@ mod tests {
&ActiveRadarrBlock::Collections.into() &ActiveRadarrBlock::Collections.into()
); );
assert!(app.error.text.is_empty()); assert!(app.error.text.is_empty());
assert_search_reset!(app.data.radarr_data); assert_eq!(app.data.radarr_data.collections.search, None);
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);
} }
} }
@@ -805,6 +733,7 @@ mod tests {
RadarrData, EDIT_COLLECTION_SELECTION_BLOCKS, RadarrData, EDIT_COLLECTION_SELECTION_BLOCKS,
}; };
use crate::models::StatefulTable;
use crate::{assert_refresh_key, test_edit_collection_key}; use crate::{assert_refresh_key, test_edit_collection_key};
use super::*; use super::*;
@@ -825,9 +754,11 @@ mod tests {
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::SearchCollection.into() &ActiveRadarrBlock::SearchCollection.into()
); );
assert!(app.data.radarr_data.is_searching);
assert!(app.should_ignore_quit_key); 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] #[test]
@@ -846,9 +777,8 @@ mod tests {
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::FilterCollections.into() &ActiveRadarrBlock::FilterCollections.into()
); );
assert!(app.data.radarr_data.is_filtering);
assert!(app.should_ignore_quit_key); assert!(app.should_ignore_quit_key);
assert!(app.data.radarr_data.filter.is_some()); assert!(app.data.radarr_data.collections.filter.is_some());
} }
#[test] #[test]
@@ -857,6 +787,8 @@ mod tests {
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.data.radarr_data = create_test_radarr_data(); 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( CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.filter.key, &DEFAULT_KEYBINDINGS.filter.key,
@@ -870,10 +802,13 @@ mod tests {
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::FilterCollections.into() &ActiveRadarrBlock::FilterCollections.into()
); );
assert!(app.data.radarr_data.is_filtering);
assert!(app.should_ignore_quit_key); assert!(app.should_ignore_quit_key);
assert!(app.data.radarr_data.filter.is_some()); assert_eq!(
assert!(app.data.radarr_data.filtered_collections.is_none()); 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] #[test]
@@ -911,7 +846,7 @@ mod tests {
#[test] #[test]
fn test_search_collections_box_backspace_key() { fn test_search_collections_box_backspace_key() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.search = Some("Test".into()); app.data.radarr_data.collections.search = Some("Test".into());
CollectionsHandler::with( CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, &DEFAULT_KEYBINDINGS.backspace.key,
@@ -921,13 +856,24 @@ mod tests {
) )
.handle(); .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] #[test]
fn test_filter_collections_box_backspace_key() { fn test_filter_collections_box_backspace_key() {
let mut app = App::default(); 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( CollectionsHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, &DEFAULT_KEYBINDINGS.backspace.key,
@@ -937,13 +883,23 @@ mod tests {
) )
.handle(); .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] #[test]
fn test_search_collections_box_char_key() { fn test_search_collections_box_char_key() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); app.data.radarr_data.collections.search = Some(HorizontallyScrollableText::default());
CollectionsHandler::with( CollectionsHandler::with(
&Key::Char('h'), &Key::Char('h'),
@@ -953,13 +909,24 @@ mod tests {
) )
.handle(); .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] #[test]
fn test_filter_collections_box_char_key() { fn test_filter_collections_box_char_key() {
let mut app = App::default(); 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( CollectionsHandler::with(
&Key::Char('h'), &Key::Char('h'),
@@ -969,7 +936,17 @@ mod tests {
) )
.handle(); .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"
);
} }
} }
+84 -61
View File
@@ -8,10 +8,9 @@ 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, StatefulTable}; use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::utils::strip_non_search_characters; use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
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;
@@ -68,38 +67,24 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
fn handle_scroll_up(&mut self) { fn handle_scroll_up(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Collections { if self.active_radarr_block == &ActiveRadarrBlock::Collections {
if let Some(filtered_collections) = self.app.data.radarr_data.filtered_collections.as_mut() { self.app.data.radarr_data.collections.scroll_up()
filtered_collections.scroll_up();
} else {
self.app.data.radarr_data.collections.scroll_up()
}
} }
} }
fn handle_scroll_down(&mut self) { fn handle_scroll_down(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Collections { if self.active_radarr_block == &ActiveRadarrBlock::Collections {
if let Some(filtered_collections) = self.app.data.radarr_data.filtered_collections.as_mut() { self.app.data.radarr_data.collections.scroll_down()
filtered_collections.scroll_down();
} else {
self.app.data.radarr_data.collections.scroll_down()
}
} }
} }
fn handle_home(&mut self) { fn handle_home(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Collections => { ActiveRadarrBlock::Collections => self.app.data.radarr_data.collections.scroll_to_top(),
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::SearchCollection => self ActiveRadarrBlock::SearchCollection => self
.app .app
.data .data
.radarr_data .radarr_data
.collections
.search .search
.as_mut() .as_mut()
.unwrap() .unwrap()
@@ -108,6 +93,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
.app .app
.data .data
.radarr_data .radarr_data
.collections
.filter .filter
.as_mut() .as_mut()
.unwrap() .unwrap()
@@ -118,18 +104,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
fn handle_end(&mut self) { fn handle_end(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Collections => { ActiveRadarrBlock::Collections => self.app.data.radarr_data.collections.scroll_to_bottom(),
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::SearchCollection => self ActiveRadarrBlock::SearchCollection => self
.app .app
.data .data
.radarr_data .radarr_data
.collections
.search .search
.as_mut() .as_mut()
.unwrap() .unwrap()
@@ -138,6 +118,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
.app .app
.data .data
.radarr_data .radarr_data
.collections
.filter .filter
.as_mut() .as_mut()
.unwrap() .unwrap()
@@ -156,14 +137,28 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
handle_text_box_left_right_keys!( handle_text_box_left_right_keys!(
self, self,
self.key, self.key,
self.app.data.radarr_data.search.as_mut().unwrap() self
.app
.data
.radarr_data
.collections
.search
.as_mut()
.unwrap()
) )
} }
ActiveRadarrBlock::FilterCollections => { ActiveRadarrBlock::FilterCollections => {
handle_text_box_left_right_keys!( handle_text_box_left_right_keys!(
self, self,
self.key, 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 .app
.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()), .push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()),
ActiveRadarrBlock::SearchCollection => { ActiveRadarrBlock::SearchCollection => {
if self.app.data.radarr_data.filtered_collections.is_some() { self.app.pop_navigation_stack();
search_table!( self.app.should_ignore_quit_key = false;
self.app,
filtered_collections, if self.app.data.radarr_data.collections.search.is_some() {
ActiveRadarrBlock::SearchCollectionError, let has_match = self
true .app
); .data
} else { .radarr_data
search_table!( .collections
self.app, .apply_search(|collection| &collection.title.text);
collections,
ActiveRadarrBlock::SearchCollectionError if !has_match {
); self
.app
.push_navigation_stack(ActiveRadarrBlock::SearchCollectionError.into());
}
} }
} }
ActiveRadarrBlock::FilterCollections => { ActiveRadarrBlock::FilterCollections => {
filter_table!( self.app.pop_navigation_stack();
self.app, self.app.should_ignore_quit_key = false;
collections,
filtered_collections, if self.app.data.radarr_data.collections.filter.is_some() {
ActiveRadarrBlock::FilterCollectionsError 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 => { ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
if self.app.data.radarr_data.prompt_confirm { 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 { match self.active_radarr_block {
ActiveRadarrBlock::FilterCollections | ActiveRadarrBlock::FilterCollectionsError => { 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.collections.reset_filter();
self.app.should_ignore_quit_key = false; self.app.should_ignore_quit_key = false;
} }
ActiveRadarrBlock::SearchCollection | ActiveRadarrBlock::SearchCollectionError => { 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.collections.reset_search();
self.app.should_ignore_quit_key = false; self.app.should_ignore_quit_key = false;
} }
ActiveRadarrBlock::UpdateAllCollectionsPrompt => { 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.prompt_confirm = false;
} }
_ => { _ => {
self.app.data.radarr_data.reset_search(); self.app.data.radarr_data.collections.reset_search();
self.app.data.radarr_data.reset_filter(); self.app.data.radarr_data.collections.reset_filter();
handle_clear_errors(self.app); handle_clear_errors(self.app);
} }
} }
@@ -242,17 +251,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); .push_navigation_stack(ActiveRadarrBlock::SearchCollection.into());
self.app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.collections.search =
self.app.data.radarr_data.is_searching = true; Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true; self.app.should_ignore_quit_key = true;
} }
_ if *key == DEFAULT_KEYBINDINGS.filter.key => { _ if *key == DEFAULT_KEYBINDINGS.filter.key => {
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.collections.reset_filter();
self.app.data.radarr_data.filter = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.collections.filter =
self.app.data.radarr_data.is_filtering = true; Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true; self.app.should_ignore_quit_key = true;
} }
_ if *key == DEFAULT_KEYBINDINGS.edit.key => { _ if *key == DEFAULT_KEYBINDINGS.edit.key => {
@@ -282,14 +291,28 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
handle_text_box_keys!( handle_text_box_keys!(
self, self,
key, key,
self.app.data.radarr_data.search.as_mut().unwrap() self
.app
.data
.radarr_data
.collections
.search
.as_mut()
.unwrap()
) )
} }
ActiveRadarrBlock::FilterCollections => { ActiveRadarrBlock::FilterCollections => {
handle_text_box_keys!( handle_text_box_keys!(
self, self,
key, key,
self.app.data.radarr_data.filter.as_mut().unwrap() self
.app
.data
.radarr_data
.collections
.filter
.as_mut()
.unwrap()
) )
} }
_ => (), _ => (),
@@ -193,7 +193,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
.app .app
.data .data
.radarr_data .radarr_data
.search .add_movie_search
.as_mut() .as_mut()
.unwrap() .unwrap()
.scroll_home(), .scroll_home(),
@@ -260,7 +260,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
.app .app
.data .data
.radarr_data .radarr_data
.search .add_movie_search
.as_mut() .as_mut()
.unwrap() .unwrap()
.reset_offset(), .reset_offset(),
@@ -286,7 +286,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
handle_text_box_left_right_keys!( handle_text_box_left_right_keys!(
self, self,
self.key, self.key,
self.app.data.radarr_data.search.as_mut().unwrap() self.app.data.radarr_data.add_movie_search.as_mut().unwrap()
) )
} }
ActiveRadarrBlock::AddMovieTagsInput => { ActiveRadarrBlock::AddMovieTagsInput => {
@@ -314,7 +314,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
.app .app
.data .data
.radarr_data .radarr_data
.search .add_movie_search
.as_mut() .as_mut()
.unwrap() .unwrap()
.text .text
@@ -407,7 +407,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::AddMovieSearchInput => { ActiveRadarrBlock::AddMovieSearchInput => {
self.app.pop_navigation_stack(); 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; self.app.should_ignore_quit_key = false;
} }
ActiveRadarrBlock::AddMovieSearchResults | ActiveRadarrBlock::AddMovieEmptySearchResults => { ActiveRadarrBlock::AddMovieSearchResults | ActiveRadarrBlock::AddMovieEmptySearchResults => {
@@ -440,7 +440,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
handle_text_box_keys!( handle_text_box_keys!(
self, self,
key, key,
self.app.data.radarr_data.search.as_mut().unwrap() self.app.data.radarr_data.add_movie_search.as_mut().unwrap()
) )
} }
ActiveRadarrBlock::AddMovieTagsInput => { ActiveRadarrBlock::AddMovieTagsInput => {
@@ -632,7 +632,7 @@ mod tests {
#[test] #[test]
fn test_add_movie_search_input_home_end_keys() { fn test_add_movie_search_input_home_end_keys() {
let mut app = App::default(); 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( AddMovieHandler::with(
&DEFAULT_KEYBINDINGS.home.key, &DEFAULT_KEYBINDINGS.home.key,
@@ -646,7 +646,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.search .add_movie_search
.as_ref() .as_ref()
.unwrap() .unwrap()
.offset .offset
@@ -666,7 +666,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.search .add_movie_search
.as_ref() .as_ref()
.unwrap() .unwrap()
.offset .offset
@@ -749,7 +749,7 @@ mod tests {
#[test] #[test]
fn test_add_movie_search_input_left_right_keys() { fn test_add_movie_search_input_left_right_keys() {
let mut app = App::default(); 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( AddMovieHandler::with(
&DEFAULT_KEYBINDINGS.left.key, &DEFAULT_KEYBINDINGS.left.key,
@@ -763,7 +763,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.search .add_movie_search
.as_ref() .as_ref()
.unwrap() .unwrap()
.offset .offset
@@ -783,7 +783,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.search .add_movie_search
.as_ref() .as_ref()
.unwrap() .unwrap()
.offset .offset
@@ -863,7 +863,7 @@ mod tests {
fn test_add_movie_search_input_submit() { fn test_add_movie_search_input_submit() {
let mut app = App::default(); let mut app = App::default();
app.should_ignore_quit_key = true; 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( AddMovieHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -883,7 +883,7 @@ mod tests {
#[test] #[test]
fn test_add_movie_search_input_submit_noop_on_empty_search() { fn test_add_movie_search_input_submit_noop_on_empty_search() {
let mut app = App::default(); 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.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
app.should_ignore_quit_key = true; 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::modals::AddMovieModal;
use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data; use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data;
use crate::models::StatefulTable; use crate::models::StatefulTable;
use crate::{assert_search_reset, simple_stateful_iterable_vec}; use crate::simple_stateful_iterable_vec;
use super::*; use super::*;
@@ -1170,7 +1170,7 @@ mod tests {
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); 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] #[test]
@@ -1344,7 +1344,7 @@ mod tests {
#[test] #[test]
fn test_add_movie_search_input_backspace() { fn test_add_movie_search_input_backspace() {
let mut app = App::default(); 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( AddMovieHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, &DEFAULT_KEYBINDINGS.backspace.key,
@@ -1354,7 +1354,10 @@ mod tests {
) )
.handle(); .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] #[test]
@@ -1389,7 +1392,7 @@ mod tests {
#[test] #[test]
fn test_add_movie_search_input_char_key() { fn test_add_movie_search_input_char_key() {
let mut app = App::default(); 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( AddMovieHandler::with(
&Key::Char('h'), &Key::Char('h'),
@@ -1399,7 +1402,10 @@ mod tests {
) )
.handle(); .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] #[test]
@@ -18,7 +18,6 @@ mod tests {
use crate::test_handler_delegation; use crate::test_handler_delegation;
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
use crate::models::StatefulTable;
use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
use super::*; use super::*;
@@ -33,55 +32,11 @@ mod tests {
title, title,
to_string 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 { mod test_handle_home_end {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use crate::models::StatefulTable;
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
use super::*; use super::*;
@@ -97,63 +52,10 @@ mod tests {
to_string 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] #[test]
fn test_movie_search_box_home_end_keys() { fn test_movie_search_box_home_end_keys() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.search = Some("Test".into()); app.data.radarr_data.movies.search = Some("Test".into());
LibraryHandler::with( LibraryHandler::with(
&DEFAULT_KEYBINDINGS.home.key, &DEFAULT_KEYBINDINGS.home.key,
@@ -167,6 +69,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.movies
.search .search
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -187,6 +90,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.movies
.search .search
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -199,7 +103,7 @@ mod tests {
#[test] #[test]
fn test_movie_filter_box_home_end_keys() { fn test_movie_filter_box_home_end_keys() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.filter = Some("Test".into()); app.data.radarr_data.movies.filter = Some("Test".into());
LibraryHandler::with( LibraryHandler::with(
&DEFAULT_KEYBINDINGS.home.key, &DEFAULT_KEYBINDINGS.home.key,
@@ -213,6 +117,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.movies
.filter .filter
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -233,6 +138,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.movies
.filter .filter
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -349,7 +255,7 @@ mod tests {
#[test] #[test]
fn test_movie_search_box_left_right_keys() { fn test_movie_search_box_left_right_keys() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.search = Some("Test".into()); app.data.radarr_data.movies.search = Some("Test".into());
LibraryHandler::with( LibraryHandler::with(
&DEFAULT_KEYBINDINGS.left.key, &DEFAULT_KEYBINDINGS.left.key,
@@ -363,6 +269,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.movies
.search .search
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -383,6 +290,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.movies
.search .search
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -395,7 +303,7 @@ mod tests {
#[test] #[test]
fn test_movie_filter_box_left_right_keys() { fn test_movie_filter_box_left_right_keys() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.filter = Some("Test".into()); app.data.radarr_data.movies.filter = Some("Test".into());
LibraryHandler::with( LibraryHandler::with(
&DEFAULT_KEYBINDINGS.left.key, &DEFAULT_KEYBINDINGS.left.key,
@@ -409,6 +317,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.movies
.filter .filter
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -429,6 +338,7 @@ mod tests {
*app *app
.data .data
.radarr_data .radarr_data
.movies
.filter .filter
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -443,7 +353,6 @@ mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use crate::extended_stateful_iterable_vec; use crate::extended_stateful_iterable_vec;
use crate::models::StatefulTable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use super::*; use super::*;
@@ -475,7 +384,7 @@ mod tests {
Movie, Movie,
HorizontallyScrollableText HorizontallyScrollableText
)); ));
app.data.radarr_data.search = Some("Test 2".into()); app.data.radarr_data.movies.search = Some("Test 2".into());
LibraryHandler::with( LibraryHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -505,7 +414,7 @@ mod tests {
Movie, Movie,
HorizontallyScrollableText HorizontallyScrollableText
)); ));
app.data.radarr_data.search = Some("Test 5".into()); app.data.radarr_data.movies.search = Some("Test 5".into());
LibraryHandler::with( LibraryHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -530,13 +439,15 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
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(ActiveRadarrBlock::SearchMovie.into());
let mut filtered_movies = StatefulTable::default(); app
filtered_movies.set_items(extended_stateful_iterable_vec!( .data
Movie, .radarr_data
HorizontallyScrollableText .movies
)); .set_filtered_items(extended_stateful_iterable_vec!(
app.data.radarr_data.filtered_movies = Some(filtered_movies); Movie,
app.data.radarr_data.search = Some("Test 2".into()); HorizontallyScrollableText
));
app.data.radarr_data.movies.search = Some("Test 2".into());
LibraryHandler::with( LibraryHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -547,15 +458,7 @@ mod tests {
.handle(); .handle();
assert_str_eq!( assert_str_eq!(
app app.data.radarr_data.movies.current_selection().title.text,
.data
.radarr_data
.filtered_movies
.as_ref()
.unwrap()
.current_selection()
.title
.text,
"Test 2" "Test 2"
); );
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
@@ -574,7 +477,7 @@ mod tests {
Movie, Movie,
HorizontallyScrollableText HorizontallyScrollableText
)); ));
app.data.radarr_data.filter = Some("Test".into()); app.data.radarr_data.movies.filter = Some("Test".into());
LibraryHandler::with( LibraryHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -584,28 +487,21 @@ mod tests {
) )
.handle(); .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!( assert_eq!(
app app
.data .data
.radarr_data .radarr_data
.filtered_movies .movies
.filtered_items
.as_ref() .as_ref()
.unwrap() .unwrap()
.items
.len(), .len(),
3 3
); );
assert_str_eq!( assert_str_eq!(
app app.data.radarr_data.movies.current_selection().title.text,
.data
.radarr_data
.filtered_movies
.as_ref()
.unwrap()
.current_selection()
.title
.text,
"Test 1" "Test 1"
); );
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
@@ -624,7 +520,7 @@ mod tests {
Movie, Movie,
HorizontallyScrollableText HorizontallyScrollableText
)); ));
app.data.radarr_data.filter = Some("Test 5".into()); app.data.radarr_data.movies.filter = Some("Test 5".into());
LibraryHandler::with( LibraryHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -634,7 +530,8 @@ mod tests {
) )
.handle(); .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!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::FilterMoviesError.into() &ActiveRadarrBlock::FilterMoviesError.into()
@@ -686,9 +583,10 @@ mod tests {
mod test_handle_esc { mod test_handle_esc {
use pretty_assertions::assert_eq; 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::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::*; use super::*;
@@ -704,12 +602,13 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(active_radarr_block.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();
app.data.radarr_data.movies.search = Some("Test".into());
LibraryHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &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_eq!(app.data.radarr_data.movies.search, None);
} }
#[rstest] #[rstest]
@@ -722,12 +621,20 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(active_radarr_block.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();
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(); 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_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] #[test]
@@ -756,13 +663,22 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
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 = 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(); LibraryHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Movies, &None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
assert!(app.error.text.is_empty()); assert!(app.error.text.is_empty());
assert_search_reset!(app.data.radarr_data); assert_eq!(app.data.radarr_data.movies.search, None);
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);
} }
} }
@@ -778,6 +694,7 @@ mod tests {
RadarrData, EDIT_MOVIE_SELECTION_BLOCKS, RadarrData, EDIT_MOVIE_SELECTION_BLOCKS,
}; };
use crate::models::StatefulTable;
use crate::{assert_refresh_key, test_edit_movie_key}; use crate::{assert_refresh_key, test_edit_movie_key};
use super::*; use super::*;
@@ -798,14 +715,17 @@ mod tests {
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::SearchMovie.into() &ActiveRadarrBlock::SearchMovie.into()
); );
assert!(app.data.radarr_data.is_searching);
assert!(app.should_ignore_quit_key); 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] #[test]
fn test_filter_movies_key() { fn test_filter_movies_key() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.movies = StatefulTable::default();
LibraryHandler::with( LibraryHandler::with(
&DEFAULT_KEYBINDINGS.filter.key, &DEFAULT_KEYBINDINGS.filter.key,
@@ -819,9 +739,8 @@ mod tests {
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::FilterMovies.into() &ActiveRadarrBlock::FilterMovies.into()
); );
assert!(app.data.radarr_data.is_filtering);
assert!(app.should_ignore_quit_key); assert!(app.should_ignore_quit_key);
assert!(app.data.radarr_data.filter.is_some()); assert!(app.data.radarr_data.movies.filter.is_some());
} }
#[test] #[test]
@@ -830,6 +749,8 @@ mod tests {
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.data.radarr_data = create_test_radarr_data(); 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( LibraryHandler::with(
&DEFAULT_KEYBINDINGS.filter.key, &DEFAULT_KEYBINDINGS.filter.key,
@@ -843,10 +764,13 @@ mod tests {
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::FilterMovies.into() &ActiveRadarrBlock::FilterMovies.into()
); );
assert!(app.data.radarr_data.is_filtering);
assert!(app.should_ignore_quit_key); assert!(app.should_ignore_quit_key);
assert!(app.data.radarr_data.filter.is_some()); assert_eq!(
assert!(app.data.radarr_data.filtered_movies.is_none()); 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] #[test]
@@ -866,7 +790,7 @@ mod tests {
&ActiveRadarrBlock::AddMovieSearchInput.into() &ActiveRadarrBlock::AddMovieSearchInput.into()
); );
assert!(app.should_ignore_quit_key); 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] #[test]
@@ -904,7 +828,7 @@ mod tests {
#[test] #[test]
fn test_search_movies_box_backspace_key() { fn test_search_movies_box_backspace_key() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.search = Some("Test".into()); app.data.radarr_data.movies.search = Some("Test".into());
LibraryHandler::with( LibraryHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, &DEFAULT_KEYBINDINGS.backspace.key,
@@ -914,13 +838,17 @@ mod tests {
) )
.handle(); .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] #[test]
fn test_filter_movies_box_backspace_key() { fn test_filter_movies_box_backspace_key() {
let mut app = App::default(); 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( LibraryHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, &DEFAULT_KEYBINDINGS.backspace.key,
@@ -930,13 +858,16 @@ mod tests {
) )
.handle(); .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] #[test]
fn test_search_movies_box_char_key() { fn test_search_movies_box_char_key() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); app.data.radarr_data.movies.search = Some(HorizontallyScrollableText::default());
LibraryHandler::with( LibraryHandler::with(
&Key::Char('h'), &Key::Char('h'),
@@ -946,13 +877,17 @@ mod tests {
) )
.handle(); .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] #[test]
fn test_filter_movies_box_char_key() { fn test_filter_movies_box_char_key() {
let mut app = App::default(); 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( LibraryHandler::with(
&Key::Char('h'), &Key::Char('h'),
@@ -962,7 +897,10 @@ mod tests {
) )
.handle(); .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"
);
} }
} }
+55 -56
View File
@@ -11,10 +11,9 @@ 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, StatefulTable}; use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::utils::strip_non_search_characters; use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
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;
@@ -81,38 +80,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
fn handle_scroll_up(&mut self) { fn handle_scroll_up(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Movies { if self.active_radarr_block == &ActiveRadarrBlock::Movies {
if let Some(filtered_movies) = self.app.data.radarr_data.filtered_movies.as_mut() { self.app.data.radarr_data.movies.scroll_up()
filtered_movies.scroll_up();
} else {
self.app.data.radarr_data.movies.scroll_up()
}
} }
} }
fn handle_scroll_down(&mut self) { fn handle_scroll_down(&mut self) {
if self.active_radarr_block == &ActiveRadarrBlock::Movies { if self.active_radarr_block == &ActiveRadarrBlock::Movies {
if let Some(filtered_movies) = self.app.data.radarr_data.filtered_movies.as_mut() { self.app.data.radarr_data.movies.scroll_down()
filtered_movies.scroll_down();
} else {
self.app.data.radarr_data.movies.scroll_down()
}
} }
} }
fn handle_home(&mut self) { fn handle_home(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Movies => { ActiveRadarrBlock::Movies => self.app.data.radarr_data.movies.scroll_to_top(),
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::SearchMovie => { ActiveRadarrBlock::SearchMovie => {
self self
.app .app
.data .data
.radarr_data .radarr_data
.movies
.search .search
.as_mut() .as_mut()
.unwrap() .unwrap()
@@ -123,6 +109,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
.app .app
.data .data
.radarr_data .radarr_data
.movies
.filter .filter
.as_mut() .as_mut()
.unwrap() .unwrap()
@@ -134,17 +121,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
fn handle_end(&mut self) { fn handle_end(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Movies => { ActiveRadarrBlock::Movies => self.app.data.radarr_data.movies.scroll_to_bottom(),
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::SearchMovie => self ActiveRadarrBlock::SearchMovie => self
.app .app
.data .data
.radarr_data .radarr_data
.movies
.search .search
.as_mut() .as_mut()
.unwrap() .unwrap()
@@ -153,6 +135,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
.app .app
.data .data
.radarr_data .radarr_data
.movies
.filter .filter
.as_mut() .as_mut()
.unwrap() .unwrap()
@@ -179,14 +162,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
handle_text_box_left_right_keys!( handle_text_box_left_right_keys!(
self, self,
self.key, self.key,
self.app.data.radarr_data.search.as_mut().unwrap() self.app.data.radarr_data.movies.search.as_mut().unwrap()
) )
} }
ActiveRadarrBlock::FilterMovies => { ActiveRadarrBlock::FilterMovies => {
handle_text_box_left_right_keys!( handle_text_box_left_right_keys!(
self, self,
self.key, 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 .app
.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()), .push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()),
ActiveRadarrBlock::SearchMovie => { ActiveRadarrBlock::SearchMovie => {
if self.app.data.radarr_data.filtered_movies.is_some() { self.app.pop_navigation_stack();
search_table!( self.app.should_ignore_quit_key = false;
self.app,
filtered_movies, if self.app.data.radarr_data.movies.search.is_some() {
ActiveRadarrBlock::SearchMovieError, let has_match = self
true .app
); .data
} else { .radarr_data
search_table!(self.app, movies, ActiveRadarrBlock::SearchMovieError); .movies
.apply_search(|movie| &movie.title.text);
if !has_match {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SearchMovieError.into());
}
} }
} }
ActiveRadarrBlock::FilterMovies => { ActiveRadarrBlock::FilterMovies => {
filter_table!( self.app.pop_navigation_stack();
self.app, self.app.should_ignore_quit_key = false;
movies,
filtered_movies, if self.app.data.radarr_data.movies.filter.is_some() {
ActiveRadarrBlock::FilterMoviesError 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 => { ActiveRadarrBlock::UpdateAllMoviesPrompt => {
if self.app.data.radarr_data.prompt_confirm { 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 { match self.active_radarr_block {
ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterMoviesError => { 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.movies.reset_filter();
self.app.should_ignore_quit_key = false; self.app.should_ignore_quit_key = false;
} }
ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchMovieError => { 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.movies.reset_search();
self.app.should_ignore_quit_key = false; self.app.should_ignore_quit_key = false;
} }
ActiveRadarrBlock::UpdateAllMoviesPrompt => { 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.prompt_confirm = false;
} }
_ => { _ => {
self.app.data.radarr_data.reset_search(); self.app.data.radarr_data.movies.reset_search();
self.app.data.radarr_data.reset_filter(); self.app.data.radarr_data.movies.reset_filter();
handle_clear_errors(self.app); handle_clear_errors(self.app);
} }
} }
@@ -261,17 +262,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); .push_navigation_stack(ActiveRadarrBlock::SearchMovie.into());
self.app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.movies.search = Some(HorizontallyScrollableText::default());
self.app.data.radarr_data.is_searching = true;
self.app.should_ignore_quit_key = true; self.app.should_ignore_quit_key = true;
} }
_ if *key == DEFAULT_KEYBINDINGS.filter.key => { _ if *key == DEFAULT_KEYBINDINGS.filter.key => {
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.movies.reset_filter();
self.app.data.radarr_data.filter = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.movies.filter = Some(HorizontallyScrollableText::default());
self.app.data.radarr_data.is_filtering = true;
self.app.should_ignore_quit_key = true; self.app.should_ignore_quit_key = true;
} }
_ if *key == DEFAULT_KEYBINDINGS.edit.key => { _ if *key == DEFAULT_KEYBINDINGS.edit.key => {
@@ -290,7 +289,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); .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; self.app.should_ignore_quit_key = true;
} }
_ if *key == DEFAULT_KEYBINDINGS.update.key => { _ if *key == DEFAULT_KEYBINDINGS.update.key => {
@@ -307,14 +306,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
handle_text_box_keys!( handle_text_box_keys!(
self, self,
key, key,
self.app.data.radarr_data.search.as_mut().unwrap() self.app.data.radarr_data.movies.search.as_mut().unwrap()
) )
} }
ActiveRadarrBlock::FilterMovies => { ActiveRadarrBlock::FilterMovies => {
handle_text_box_keys!( handle_text_box_keys!(
self, self,
key, key,
self.app.data.radarr_data.filter.as_mut().unwrap() self.app.data.radarr_data.movies.filter.as_mut().unwrap()
) )
} }
_ => (), _ => (),
-41
View File
@@ -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);
}
};
}
@@ -11,7 +11,6 @@ mod utils {
(1111, "Any".to_owned()), (1111, "Any".to_owned()),
]), ]),
tags_map: BiMap::from_iter([(1, "test".to_owned())]), tags_map: BiMap::from_iter([(1, "test".to_owned())]),
filtered_movies: None,
..create_test_radarr_data() ..create_test_radarr_data()
}; };
radarr_data.movies.set_items(vec![Movie { radarr_data.movies.set_items(vec![Movie {
@@ -126,7 +125,6 @@ mod utils {
(2222, "HD - 1080p".to_owned()), (2222, "HD - 1080p".to_owned()),
(1111, "Any".to_owned()), (1111, "Any".to_owned()),
]), ]),
filtered_collections: None,
..create_test_radarr_data() ..create_test_radarr_data()
}; };
radarr_data.collections.set_items(vec![Collection { radarr_data.collections.set_items(vec![Collection {
@@ -1,6 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -8,249 +8,8 @@ mod tests {
use crate::app::App; use crate::app::App;
use crate::handlers::radarr_handlers::{handle_change_tab_left_right_keys, RadarrHandler}; use crate::handlers::radarr_handlers::{handle_change_tab_left_right_keys, RadarrHandler};
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
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, StatefulTable}; use crate::test_handler_delegation;
use crate::utils::strip_non_search_characters;
use crate::{
extended_stateful_iterable_vec, filter_table, search_table, test_handler_delegation,
};
#[test]
fn test_search_table_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());
}
#[rstest] #[rstest]
#[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Downloads)] #[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Downloads)]
@@ -375,9 +134,6 @@ mod tests {
#[rstest] #[rstest]
fn test_delegates_indexers_blocks_to_indexers_handler( fn test_delegates_indexers_blocks_to_indexers_handler(
// Add these once implemented:
// ActiveRadarrBlock::AddIndexer,
// ActiveRadarrBlock::EditIndexer,
#[values( #[values(
ActiveRadarrBlock::DeleteIndexerPrompt, ActiveRadarrBlock::DeleteIndexerPrompt,
ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
+196 -35
View File
@@ -3,47 +3,51 @@ use std::fmt::{Debug, Display, Formatter};
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use ratatui::widgets::{ListState, TableState}; use ratatui::widgets::{ListState, TableState};
use regex::Regex;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Number; use serde_json::Number;
pub mod radarr_models; pub mod radarr_models;
pub mod servarr_data; 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<ActiveRadarrBlock>),
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 { macro_rules! stateful_iterable {
($name:ident, $state:ty) => { ($name:ident, $state:ty) => {
#[derive(Default)] #[derive(Default)]
pub struct $name<T> { pub struct $name<T> {
pub state: $state, pub state: $state,
pub items: Vec<T>, pub items: Vec<T>,
pub filter: Option<HorizontallyScrollableText>,
pub search: Option<HorizontallyScrollableText>,
pub filtered_items: Option<Vec<T>>,
pub filtered_state: Option<$state>,
} }
impl<T> Scrollable for $name<T> { impl<T> Scrollable for $name<T> {
fn scroll_down(&mut self) { 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() { if self.items.is_empty() {
return; return;
} }
@@ -63,6 +67,30 @@ macro_rules! stateful_iterable {
} }
fn scroll_up(&mut self) { 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() { if self.items.is_empty() {
return; return;
} }
@@ -82,6 +110,15 @@ macro_rules! stateful_iterable {
} }
fn scroll_to_top(&mut self) { 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() { if self.items.is_empty() {
return; return;
} }
@@ -90,6 +127,19 @@ macro_rules! stateful_iterable {
} }
fn scroll_to_bottom(&mut self) { 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() { if self.items.is_empty() {
return; return;
} }
@@ -98,6 +148,7 @@ macro_rules! stateful_iterable {
} }
} }
#[allow(dead_code)]
impl<T> $name<T> impl<T> $name<T>
where where
T: Clone + PartialEq + Eq + Debug, T: Clone + PartialEq + Eq + Debug,
@@ -119,25 +170,128 @@ macro_rules! stateful_iterable {
} }
} }
pub fn set_filtered_items(&mut self, filtered_items: Vec<T>) {
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<usize>) {
if let Some(filtered_state) = &mut self.filtered_state {
filtered_state.select(index);
} else {
self.state.select(index);
}
}
pub fn current_selection(&self) -> &T { 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<ActiveRadarrBlock>),
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!(StatefulList, ListState);
stateful_iterable!(StatefulTable, TableState); stateful_iterable!(StatefulTable, TableState);
impl<T> StatefulTable<T>
where
T: Clone + PartialEq + Eq + Debug,
{
pub fn select_index(&mut self, index: Option<usize>) {
self.state.select(index);
}
}
#[derive(Default)] #[derive(Default)]
pub struct ScrollableText { pub struct ScrollableText {
pub items: Vec<String>, pub items: Vec<String>,
@@ -429,3 +583,10 @@ where
"Unable to convert Number to i64: {num:?}" "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()
}
+754 -8
View File
@@ -3,14 +3,15 @@ mod tests {
use std::cell::RefCell; use std::cell::RefCell;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use ratatui::widgets::{ListState, TableState};
use serde::de::value::Error as ValueError; use serde::de::value::Error as ValueError;
use serde::de::value::F64Deserializer; use serde::de::value::F64Deserializer;
use serde::de::value::I64Deserializer; use serde::de::value::I64Deserializer;
use serde::de::IntoDeserializer; use serde::de::IntoDeserializer;
use serde_json::to_string; use serde_json::to_string;
use crate::models::from_i64;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::{from_i64, strip_non_search_characters};
use crate::models::{ use crate::models::{
BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, StatefulList, BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, StatefulList,
StatefulTable, TabRoute, TabState, StatefulTable, TabRoute, TabState,
@@ -46,6 +47,59 @@ mod tests {
stateful_table.scroll_to_bottom(); stateful_table.scroll_to_bottom();
} }
#[test]
fn test_stateful_table_filtered_scrolling_on_empty_table_performs_no_op() {
let mut filtered_stateful_table: StatefulTable<String> = 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] #[test]
fn test_stateful_table_scroll() { fn test_stateful_table_scroll() {
let mut stateful_table = create_test_stateful_table(); let mut stateful_table = create_test_stateful_table();
@@ -77,6 +131,86 @@ mod tests {
assert_eq!(stateful_table.state.selected(), Some(0)); 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] #[test]
fn test_stateful_table_set_items() { fn test_stateful_table_set_items() {
let items_vec = vec!["Test 1", "Test 2", "Test 3"]; let items_vec = vec!["Test 1", "Test 2", "Test 3"];
@@ -97,6 +231,27 @@ mod tests {
assert_eq!(stateful_table.state.selected(), Some(2)); 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] #[test]
fn test_stateful_table_current_selection() { fn test_stateful_table_current_selection() {
let mut stateful_table = create_test_stateful_table(); 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]); 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] #[test]
fn test_stateful_table_select_index() { fn test_stateful_table_select_index() {
let mut stateful_table = create_test_stateful_table(); let mut stateful_table = create_test_stateful_table();
@@ -123,6 +299,42 @@ mod tests {
assert_eq!(stateful_table.state.selected(), None); 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] #[test]
fn test_stateful_table_scroll_up() { fn test_stateful_table_scroll_up() {
let mut stateful_table = create_test_stateful_table(); let mut stateful_table = create_test_stateful_table();
@@ -139,7 +351,150 @@ mod tests {
} }
#[test] #[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<String> = StatefulList::default(); let mut stateful_list: StatefulList<String> = StatefulList::default();
assert_eq!(stateful_list.state.selected(), None); assert_eq!(stateful_list.state.selected(), None);
@@ -159,6 +514,59 @@ mod tests {
stateful_list.scroll_to_bottom(); stateful_list.scroll_to_bottom();
} }
#[test]
fn test_filtered_stateful_list_scrolling_on_empty_list_performs_no_op() {
let mut filtered_stateful_list: StatefulList<String> = 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] #[test]
fn test_stateful_list_scroll() { fn test_stateful_list_scroll() {
let mut stateful_list = create_test_stateful_list(); let mut stateful_list = create_test_stateful_list();
@@ -190,6 +598,86 @@ mod tests {
assert_eq!(stateful_list.state.selected(), Some(0)); 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] #[test]
fn test_stateful_list_set_items() { fn test_stateful_list_set_items() {
let items_vec = vec!["Test 1", "Test 2", "Test 3"]; let items_vec = vec!["Test 1", "Test 2", "Test 3"];
@@ -210,6 +698,27 @@ mod tests {
assert_eq!(stateful_list.state.selected(), Some(2)); 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] #[test]
fn test_stateful_list_current_selection() { fn test_stateful_list_current_selection() {
let mut stateful_list = create_test_stateful_list(); 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]); 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] #[test]
fn test_stateful_list_scroll_up() { 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] #[test]
@@ -771,10 +1495,32 @@ mod tests {
stateful_table 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> { fn create_test_stateful_list() -> StatefulList<&'static str> {
let mut stateful_list = StatefulList::default(); let mut stateful_list = StatefulList::default();
stateful_list.set_items(vec!["Test 1", "Test 2"]); stateful_list.set_items(vec!["Test 1", "Test 2"]);
stateful_list 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()
)
}
} }
+2 -10
View File
@@ -133,11 +133,7 @@ impl From<&RadarrData<'_>> for EditMovieModal {
minimum_availability, minimum_availability,
quality_profile_id, quality_profile_id,
.. ..
} = if let Some(filtered_movies) = radarr_data.filtered_movies.as_ref() { } = radarr_data.movies.current_selection();
filtered_movies.current_selection()
} else {
radarr_data.movies.current_selection()
};
edit_movie_modal edit_movie_modal
.minimum_availability_list .minimum_availability_list
@@ -249,11 +245,7 @@ impl From<&RadarrData<'_>> for EditCollectionModal {
minimum_availability, minimum_availability,
quality_profile_id, quality_profile_id,
.. ..
} = if let Some(filtered_collections) = radarr_data.filtered_collections.as_ref() { } = radarr_data.collections.current_selection();
filtered_collections.current_selection()
} else {
radarr_data.collections.current_selection()
};
edit_collection_modal.path = root_folder_path.clone().unwrap_or_default().into(); edit_collection_modal.path = root_folder_path.clone().unwrap_or_default().into();
edit_collection_modal.monitored = Some(*monitored); edit_collection_modal.monitored = Some(*monitored);
@@ -117,7 +117,7 @@ mod test {
(1111, "Any".to_owned()), (1111, "Any".to_owned()),
]), ]),
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".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() ..create_test_radarr_data()
}; };
let movie = Movie { let movie = Movie {
@@ -130,9 +130,7 @@ mod test {
}; };
if test_filtered_movies { if test_filtered_movies {
let mut filtered_movies = StatefulTable::default(); radarr_data.movies.set_filtered_items(vec![movie]);
filtered_movies.set_items(vec![movie]);
radarr_data.filtered_movies = Some(filtered_movies);
} else { } else {
radarr_data.movies.set_items(vec![movie]); radarr_data.movies.set_items(vec![movie]);
} }
@@ -209,7 +207,7 @@ mod test {
(2222, "HD - 1080p".to_owned()), (2222, "HD - 1080p".to_owned()),
(1111, "Any".to_owned()), (1111, "Any".to_owned()),
]), ]),
filtered_collections: None, collections: StatefulTable::default(),
..create_test_radarr_data() ..create_test_radarr_data()
}; };
let collection = Collection { let collection = Collection {
@@ -222,9 +220,7 @@ mod test {
}; };
if test_filtered_collections { if test_filtered_collections {
let mut filtered_collections = StatefulTable::default(); radarr_data.collections.set_filtered_items(vec![collection]);
filtered_collections.set_items(vec![collection]);
radarr_data.filtered_collections = Some(filtered_collections);
} else { } else {
radarr_data.collections.set_items(vec![collection]); radarr_data.collections.set_items(vec![collection]);
} }
+2 -41
View File
@@ -50,16 +50,13 @@ pub struct RadarrData<'a> {
pub updates: ScrollableText, pub updates: ScrollableText,
pub main_tabs: TabState, pub main_tabs: TabState,
pub movie_info_tabs: TabState, pub movie_info_tabs: TabState,
pub search: Option<HorizontallyScrollableText>, pub add_movie_search: Option<HorizontallyScrollableText>,
pub filter: Option<HorizontallyScrollableText>,
pub add_movie_modal: Option<AddMovieModal>, pub add_movie_modal: Option<AddMovieModal>,
pub add_searched_movies: Option<StatefulTable<AddMovieSearchResult>>, pub add_searched_movies: Option<StatefulTable<AddMovieSearchResult>>,
pub edit_movie_modal: Option<EditMovieModal>, pub edit_movie_modal: Option<EditMovieModal>,
pub edit_collection_modal: Option<EditCollectionModal>, pub edit_collection_modal: Option<EditCollectionModal>,
pub edit_indexer_modal: Option<EditIndexerModal>, pub edit_indexer_modal: Option<EditIndexerModal>,
pub edit_root_folder: Option<HorizontallyScrollableText>, pub edit_root_folder: Option<HorizontallyScrollableText>,
pub filtered_collections: Option<StatefulTable<Collection>>,
pub filtered_movies: Option<StatefulTable<Movie>>,
pub indexer_settings: Option<IndexerSettings>, pub indexer_settings: Option<IndexerSettings>,
pub indexer_test_all_results: Option<StatefulTable<IndexerTestResultModalItem>>, pub indexer_test_all_results: Option<StatefulTable<IndexerTestResultModalItem>>,
pub movie_details_modal: Option<MovieDetailsModal>, pub movie_details_modal: Option<MovieDetailsModal>,
@@ -67,8 +64,6 @@ pub struct RadarrData<'a> {
pub prompt_confirm_action: Option<RadarrEvent>, pub prompt_confirm_action: Option<RadarrEvent>,
pub delete_movie_files: bool, pub delete_movie_files: bool,
pub add_list_exclusion: bool, pub add_list_exclusion: bool,
pub is_searching: bool,
pub is_filtering: bool,
} }
impl<'a> RadarrData<'a> { impl<'a> RadarrData<'a> {
@@ -77,22 +72,6 @@ impl<'a> RadarrData<'a> {
self.add_list_exclusion = false; 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) { pub fn reset_movie_info_tabs(&mut self) {
self.movie_details_modal = None; self.movie_details_modal = None;
self.movie_info_tabs.index = 0; self.movie_info_tabs.index = 0;
@@ -119,21 +98,16 @@ impl<'a> Default for RadarrData<'a> {
tasks: StatefulTable::default(), tasks: StatefulTable::default(),
queued_events: StatefulTable::default(), queued_events: StatefulTable::default(),
updates: ScrollableText::default(), updates: ScrollableText::default(),
search: None, add_movie_search: None,
filter: None,
add_movie_modal: None, add_movie_modal: None,
add_searched_movies: None, add_searched_movies: None,
edit_movie_modal: None, edit_movie_modal: None,
edit_collection_modal: None, edit_collection_modal: None,
edit_indexer_modal: None, edit_indexer_modal: None,
edit_root_folder: None, edit_root_folder: None,
filtered_collections: None,
filtered_movies: None,
indexer_settings: None, indexer_settings: None,
indexer_test_all_results: None, indexer_test_all_results: None,
movie_details_modal: None, movie_details_modal: None,
is_searching: false,
is_filtering: false,
prompt_confirm: false, prompt_confirm: false,
prompt_confirm_action: None, prompt_confirm_action: None,
delete_movie_files: false, delete_movie_files: false,
@@ -505,16 +479,3 @@ impl From<(ActiveRadarrBlock, Option<ActiveRadarrBlock>)> for Route {
Route::Radarr(value.0, value.1) 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,
}
@@ -16,8 +16,8 @@ mod tests {
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
use crate::models::Route; use crate::models::Route;
use crate::assert_movie_info_tabs_reset;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::{assert_filter_reset, assert_movie_info_tabs_reset, assert_search_reset};
#[test] #[test]
fn test_from_tuple_to_route_with_context() { fn test_from_tuple_to_route_with_context() {
@@ -43,24 +43,6 @@ mod tests {
assert!(!radarr_data.add_list_exclusion); 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] #[test]
fn test_reset_movie_info_tabs() { fn test_reset_movie_info_tabs() {
let mut radarr_data = utils::create_test_radarr_data(); 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.tasks.items.is_empty());
assert!(radarr_data.queued_events.items.is_empty()); assert!(radarr_data.queued_events.items.is_empty());
assert!(radarr_data.updates.get_text().is_empty()); assert!(radarr_data.updates.get_text().is_empty());
assert!(radarr_data.search.is_none()); assert!(radarr_data.add_movie_search.is_none());
assert!(radarr_data.filter.is_none());
assert!(radarr_data.add_movie_modal.is_none()); assert!(radarr_data.add_movie_modal.is_none());
assert!(radarr_data.add_searched_movies.is_none()); assert!(radarr_data.add_searched_movies.is_none());
assert!(radarr_data.edit_movie_modal.is_none()); assert!(radarr_data.edit_movie_modal.is_none());
assert!(radarr_data.edit_collection_modal.is_none()); assert!(radarr_data.edit_collection_modal.is_none());
assert!(radarr_data.edit_root_folder.is_none()); assert!(radarr_data.edit_root_folder.is_none());
assert!(radarr_data.edit_indexer_modal.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_settings.is_none());
assert!(radarr_data.indexer_test_all_results.is_none()); assert!(radarr_data.indexer_test_all_results.is_none());
assert!(radarr_data.movie_details_modal.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_action.is_none());
assert!(!radarr_data.prompt_confirm); assert!(!radarr_data.prompt_confirm);
assert!(!radarr_data.delete_movie_files); assert!(!radarr_data.delete_movie_files);
@@ -1,8 +1,7 @@
#[cfg(test)] #[cfg(test)]
pub mod utils { pub mod utils {
use crate::models::radarr_models::{ use crate::models::radarr_models::{
AddMovieSearchResult, Collection, CollectionMovie, Credit, Movie, MovieHistoryItem, Release, AddMovieSearchResult, CollectionMovie, Credit, MovieHistoryItem, Release, ReleaseField,
ReleaseField,
}; };
use crate::models::servarr_data::radarr::modals::MovieDetailsModal; use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
use crate::models::servarr_data::radarr::radarr_data::RadarrData; use crate::models::servarr_data::radarr::radarr_data::RadarrData;
@@ -31,30 +30,15 @@ pub mod utils {
movie_details_modal.sort_ascending = Some(true); movie_details_modal.sort_ascending = Some(true);
let mut radarr_data = RadarrData { let mut radarr_data = RadarrData {
is_searching: true,
is_filtering: true,
delete_movie_files: true, delete_movie_files: true,
add_list_exclusion: true, add_list_exclusion: true,
search: Some("test search".into()), add_movie_search: Some("test search".into()),
filter: Some("test filter".into()),
edit_root_folder: Some("test path".into()), edit_root_folder: Some("test path".into()),
movie_details_modal: Some(movie_details_modal), movie_details_modal: Some(movie_details_modal),
filtered_movies: Some(StatefulTable::default()),
filtered_collections: Some(StatefulTable::default()),
add_searched_movies: Some(StatefulTable::default()), add_searched_movies: Some(StatefulTable::default()),
..RadarrData::default() ..RadarrData::default()
}; };
radarr_data.movie_info_tabs.index = 1; 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 radarr_data
.add_searched_movies .add_searched_movies
.as_mut() .as_mut()
@@ -70,28 +54,6 @@ pub mod utils {
radarr_data 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_export]
macro_rules! assert_movie_info_tabs_reset { macro_rules! assert_movie_info_tabs_reset {
($radarr_data:expr) => { ($radarr_data:expr) => {
+9 -52
View File
@@ -1433,7 +1433,7 @@ impl<'a, 'b> Network<'a, 'b> {
.await .await
.data .data
.radarr_data .radarr_data
.search .add_movie_search
.clone() .clone()
.ok_or(anyhow!("Encountered a race condition")); .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) { async fn extract_movie_id(&mut self) -> (i64, i64) {
let app = self.app.lock().await; let app = self.app.lock().await;
if app.data.radarr_data.filtered_movies.is_some() { (
( app.data.radarr_data.movies.current_selection().id,
app app.data.radarr_data.movies.current_selection().tmdb_id,
.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,
)
}
} }
async fn extract_collection_id(&mut self) -> i64 { async fn extract_collection_id(&mut self) -> i64 {
if self self
.app .app
.lock() .lock()
.await .await
.data .data
.radarr_data .radarr_data
.filtered_collections .collections
.is_some() .current_selection()
{ .id
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
}
} }
async fn append_movie_id_param(&mut self, resource: &str) -> String { async fn append_movie_id_param(&mut self, resource: &str) -> String {
+6 -6
View File
@@ -454,7 +454,7 @@ mod test {
&resource, &resource,
) )
.await; .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()); let mut network = Network::new(&app_arc, CancellationToken::new());
network network
@@ -520,7 +520,7 @@ mod test {
); );
let (async_server, app_arc, _server) = let (async_server, app_arc, _server) =
mock_radarr_api(RequestMethod::Get, None, Some(json!([])), None, &resource).await; mock_radarr_api(RequestMethod::Get, None, Some(json!([])), None, &resource).await;
app_arc.lock().await.data.radarr_data.search = Some("test term".into()); app_arc.lock().await.data.radarr_data.add_movie_search = Some("test term".into());
let mut network = Network::new(&app_arc, CancellationToken::new()); let mut network = Network::new(&app_arc, CancellationToken::new());
network network
@@ -2639,12 +2639,12 @@ mod test {
async fn test_extract_movie_id_filtered_movies() { async fn test_extract_movie_id_filtered_movies() {
let app_arc = Arc::new(Mutex::new(App::default())); let app_arc = Arc::new(Mutex::new(App::default()));
let mut filtered_movies = StatefulTable::default(); let mut filtered_movies = StatefulTable::default();
filtered_movies.set_items(vec![Movie { filtered_movies.set_filtered_items(vec![Movie {
id: 1, id: 1,
tmdb_id: 2, tmdb_id: 2,
..Movie::default() ..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()); let mut network = Network::new(&app_arc, CancellationToken::new());
assert_eq!(network.extract_movie_id().await, (1, 2)); assert_eq!(network.extract_movie_id().await, (1, 2));
@@ -2672,11 +2672,11 @@ mod test {
async fn test_extract_collection_id_filtered_collection() { async fn test_extract_collection_id_filtered_collection() {
let app_arc = Arc::new(Mutex::new(App::default())); let app_arc = Arc::new(Mutex::new(App::default()));
let mut filtered_collections = StatefulTable::default(); let mut filtered_collections = StatefulTable::default();
filtered_collections.set_items(vec![Collection { filtered_collections.set_filtered_items(vec![Collection {
id: 1, id: 1,
..Collection::default() ..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()); let mut network = Network::new(&app_arc, CancellationToken::new());
assert_eq!(network.extract_collection_id().await, 1); assert_eq!(network.extract_collection_id().await, 1);
-35
View File
@@ -220,32 +220,6 @@ pub fn draw_drop_down_popup(
draw_popup_over(f, app, area, background_fn, drop_down_fn, 20, 30); 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 { fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -> Rect {
f.render_widget(title_block(title), area); f.render_widget(title_block(title), area);
@@ -485,12 +459,3 @@ pub fn draw_input_box_popup(
.block(borderless_block()); .block(borderless_block());
f.render_widget(help, help_area); 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);
}
@@ -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)]) Layout::vertical([Constraint::Percentage(25), Constraint::Fill(0)])
.margin(1) .margin(1)
.areas(area); .areas(area);
let collection_selection = let collection_selection = app.data.radarr_data.collections.current_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 quality_profile = app let quality_profile = app
.data .data
.radarr_data .radarr_data
@@ -89,36 +89,22 @@ impl DrawUi for EditCollectionUi {
} }
fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let (collection_title, collection_overview) = let collection_title = app
if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() { .data
( .radarr_data
filtered_collections.current_selection().title.text.clone(), .collections
filtered_collections .current_selection()
.current_selection() .title
.overview .text
.clone() .clone();
.unwrap_or_default(), let collection_overview = app
) .data
} else { .radarr_data
( .collections
app .current_selection()
.data .overview
.radarr_data .clone()
.collections .unwrap_or_default();
.current_selection()
.title
.text
.clone(),
app
.data
.radarr_data
.collections
.current_selection()
.overview
.clone()
.unwrap_or_default(),
)
};
let title = format!("Edit - {collection_title}"); let title = format!("Edit - {collection_title}");
let yes_no_value = app.data.radarr_data.prompt_confirm; let yes_no_value = app.data.radarr_data.prompt_confirm;
let selected_block = app.data.radarr_data.selected_block.get_active_block(); let selected_block = app.data.radarr_data.selected_block.get_active_block();
+15 -18
View File
@@ -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::radarr_ui::collections::edit_collection_ui::EditCollectionUi;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
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::widgets::error_message::ErrorMessage;
use crate::ui::widgets::managarr_table::ManagarrTable; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::{ use crate::ui::{
draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, DrawUi,
draw_prompt_popup_over, DrawUi,
}; };
mod collection_details_ui; 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) { pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let current_selection = let current_selection = if !app.data.radarr_data.collections.items.is_empty() {
if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() { app.data.radarr_data.collections.current_selection().clone()
filtered_collections.current_selection().clone() } else {
} else if !app.data.radarr_data.collections.items.is_empty() { Collection::default()
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 quality_profile_map = &app.data.radarr_data.quality_profile_map;
let content = Some(&mut app.data.radarr_data.collections);
let collections_table_footer = app let collections_table_footer = app
.data .data
.radarr_data .radarr_data
@@ -187,7 +181,7 @@ fn draw_collection_search_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
f, f,
area, area,
"Search", "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, f,
area, area,
"Filter", "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) { 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) { 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,
);
} }
+19 -12
View File
@@ -17,11 +17,12 @@ use crate::ui::utils::{
title_block_centered, title_block_centered,
}; };
use crate::ui::widgets::button::Button; use crate::ui::widgets::button::Button;
use crate::ui::widgets::error_message::ErrorMessage;
use crate::ui::widgets::input_box::InputBox; use crate::ui::widgets::input_box::InputBox;
use crate::ui::widgets::managarr_table::ManagarrTable; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::popup::Popup;
use crate::ui::{ use crate::ui::{
draw_drop_down_popup, draw_error_popup, draw_error_popup_over, draw_large_popup_over, draw_drop_down_popup, draw_large_popup_over, draw_medium_popup_over, draw_selectable_list, DrawUi,
draw_medium_popup_over, draw_selectable_list, DrawUi,
}; };
use crate::utils::convert_runtime; use crate::utils::convert_runtime;
use crate::{render_selectable_input_box, App}; 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); draw_medium_popup_over(f, app, area, draw_add_movie_search, draw_confirmation_popup);
} }
} }
ActiveRadarrBlock::AddMovieAlreadyInLibrary => draw_error_popup_over( ActiveRadarrBlock::AddMovieAlreadyInLibrary => {
f, draw_add_movie_search(f, app, area);
app, f.render_widget(
area, Popup::new(
"This film is already in your library", ErrorMessage::new("This film is already in your library"),
draw_add_movie_search, 25,
), 8,
),
f.size(),
);
}
_ => (), _ => (),
}; };
@@ -108,11 +113,11 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
]) ])
.margin(1) .margin(1)
.areas(area); .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 let offset = *app
.data .data
.radarr_data .radarr_data
.search .add_movie_search
.as_ref() .as_ref()
.unwrap() .unwrap()
.offset .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) let help_paragraph = Paragraph::new(help_text)
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Center); .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); 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); f.render_widget(help_paragraph, help_area);
} }
ActiveRadarrBlock::AddMovieSearchResults ActiveRadarrBlock::AddMovieSearchResults
+17 -27
View File
@@ -19,8 +19,8 @@ use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox; use crate::ui::widgets::checkbox::Checkbox;
use crate::ui::widgets::input_box::InputBox; use crate::ui::widgets::input_box::InputBox;
use crate::ui::{ use crate::ui::{
draw_drop_down_popup, draw_large_popup_over_background_fn_with_ui, draw_drop_down_popup, draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over,
draw_medium_popup_over, draw_popup, draw_selectable_list, DrawUi, draw_popup, draw_selectable_list, DrawUi,
}; };
#[cfg(test)] #[cfg(test)]
@@ -91,31 +91,21 @@ impl DrawUi for EditMovieUi {
} }
fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let (movie_title, movie_overview) = let movie_title = app
if let Some(filtered_movies) = app.data.radarr_data.filtered_movies.as_ref() { .data
( .radarr_data
filtered_movies.current_selection().title.text.clone(), .movies
filtered_movies.current_selection().overview.clone(), .current_selection()
) .title
} else { .text
( .clone();
app let movie_overview = app
.data .data
.radarr_data .radarr_data
.movies .movies
.current_selection() .current_selection()
.title .overview
.text .clone();
.clone(),
app
.data
.radarr_data
.movies
.current_selection()
.overview
.clone(),
)
};
let title = format!("Edit - {movie_title}"); let title = format!("Edit - {movie_title}");
let yes_no_value = app.data.radarr_data.prompt_confirm; let yes_no_value = app.data.radarr_data.prompt_confirm;
let selected_block = app.data.radarr_data.selected_block.get_active_block(); let selected_block = app.data.radarr_data.selected_block.get_active_block();
+15 -18
View File
@@ -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::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::utils::{get_width_from_percentage, layout_block_top_border}; 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::widgets::managarr_table::ManagarrTable;
use crate::ui::{ use crate::ui::{
draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, DrawUi,
draw_prompt_popup_over, DrawUi,
}; };
use crate::utils::{convert_runtime, convert_to_gb}; 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) { pub(super) fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let current_selection = let current_selection = if !app.data.radarr_data.movies.items.is_empty() {
if let Some(filtered_movies) = app.data.radarr_data.filtered_movies.as_ref() { app.data.radarr_data.movies.current_selection().clone()
filtered_movies.current_selection().clone() } else {
} else if !app.data.radarr_data.movies.items.is_empty() { Movie::default()
app.data.radarr_data.movies.current_selection().clone() };
} else {
Movie::default()
};
let quality_profile_map = &app.data.radarr_data.quality_profile_map; let quality_profile_map = &app.data.radarr_data.quality_profile_map;
let tags_map = &app.data.radarr_data.tags_map; let tags_map = &app.data.radarr_data.tags_map;
let downloads_vec = &app.data.radarr_data.downloads.items; let downloads_vec = &app.data.radarr_data.downloads.items;
let content = match app.data.radarr_data.filtered_movies.as_mut() { let content = Some(&mut app.data.radarr_data.movies);
Some(filtered_movies) if !app.data.radarr_data.is_filtering => Some(filtered_movies),
_ => Some(&mut app.data.radarr_data.movies),
};
let help_footer = app let help_footer = app
.data .data
.radarr_data .radarr_data
@@ -203,7 +197,7 @@ fn draw_movie_search_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
f, f,
area, area,
"Search", "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, f,
area, area,
"Filter", "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) { 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) { 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,
);
} }
@@ -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) { 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 tasks_popup_table = |f: &mut Frame<'_>, app: &mut App<'_>, area: Rect| {
let help_footer = Some(build_context_clue_string(&SYSTEM_TASKS_CONTEXT_CLUES)); 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 tasks_row_mapping = |task: &Task| {
let task_props = extract_task_props(task); let task_props = extract_task_props(task);
+3 -3
View File
@@ -40,7 +40,7 @@ impl<'a> Button<'a> {
self 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([ let [title_area, icon_area] = Layout::horizontal([
Constraint::Length(self.title.len() as u16), Constraint::Length(self.title.len() as u16),
Constraint::Percentage(25), 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] = let [label_area, button_area] =
Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area); Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
let label_paragraph = Paragraph::new(Text::from(format!("\n{}: ", self.label.unwrap()))) 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)) Paragraph::new(Text::from(self.title))
.block(layout_block()) .block(layout_block())
.alignment(Alignment::Center) .alignment(Alignment::Center)
+1 -1
View File
@@ -31,7 +31,7 @@ impl<'a> Checkbox<'a> {
self 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 check = if self.is_checked { "" } else { "" };
let [label_area, checkbox_area] = let [label_area, checkbox_area] =
Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area); Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
+36
View File
@@ -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<T>(message: T) -> Self
where
T: Into<Text<'a>>,
{
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);
}
}
+4 -4
View File
@@ -47,12 +47,12 @@ impl<'a> InputBox<'a> {
self.label = Some(label); self.label = Some(label);
self self
} }
pub fn offset(mut self, offset: usize) -> InputBox<'a> { pub fn offset(mut self, offset: usize) -> InputBox<'a> {
self.offset = offset; self.offset = offset;
self self
} }
pub fn cursor_after_string(mut self, cursor_after_string: bool) -> InputBox<'a> { pub fn cursor_after_string(mut self, cursor_after_string: bool) -> InputBox<'a> {
self.cursor_after_string = cursor_after_string; self.cursor_after_string = cursor_after_string;
self 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 = let style =
if matches!(self.is_highlighted, Some(true)) && matches!(self.is_selected, Some(false)) { if matches!(self.is_highlighted, Some(true)) && matches!(self.is_selected, Some(false)) {
Style::new().system_function().bold() Style::new().system_function().bold()
@@ -99,7 +99,7 @@ impl<'a> InputBox<'a> {
let input_box_paragraph = Paragraph::new(Text::from(self.content)) let input_box_paragraph = Paragraph::new(Text::from(self.content))
.style(style) .style(style)
.block(self.block.clone()); .block(self.block);
if let Some(label) = self.label { if let Some(label) = self.label {
let [label_area, text_box_area] = let [label_area, text_box_area] =
+3 -3
View File
@@ -14,14 +14,14 @@ impl<'a> LoadingBlock<'a> {
Self { is_loading, block } 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 { if self.is_loading {
Paragraph::new(Text::from("\n\n Loading ...\n\n")) Paragraph::new(Text::from("\n\n Loading ...\n\n"))
.system_function() .system_function()
.block(self.block.clone()) .block(self.block)
.render(area, buf); .render(area, buf);
} else { } else {
self.block.clone().render(area, buf); self.block.render(area, buf);
} }
} }
} }
+18 -10
View File
@@ -86,12 +86,12 @@ where
self self
} }
pub fn highlight_rows(mut self, hightlight_rows: bool) -> Self { pub fn highlight_rows(mut self, highlight_rows: bool) -> Self {
self.highlight_rows = hightlight_rows; self.highlight_rows = highlight_rows;
self 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 table_area = if let Some(ref footer) = self.footer {
let [content_area, footer_area] = let [content_area, footer_area] =
Layout::vertical([Constraint::Fill(0), Constraint::Length(2)]) Layout::vertical([Constraint::Fill(0), Constraint::Length(2)])
@@ -109,18 +109,26 @@ where
}; };
let loading_block = LoadingBlock::new(self.is_loading, self.block.clone()); let loading_block = LoadingBlock::new(self.is_loading, self.block.clone());
if let Some(ref mut content) = self.content { if let Some(content) = self.content {
if !content.items.is_empty() { let (table_contents, table_state) = if content.filtered_items.is_some() {
let rows = content.items.iter().map(&self.row_mapper); (
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() .default()
.bold() .bold()
.bottom_margin(0); .bottom_margin(0);
let mut table = Table::new(rows, &self.constraints) let mut table = Table::new(rows, &self.constraints)
.header(headers) .header(headers)
.block(self.block.clone()); .block(self.block);
if self.highlight_rows { if self.highlight_rows {
table = table table = table
@@ -128,7 +136,7 @@ where
.highlight_symbol(HIGHLIGHT_SYMBOL); .highlight_symbol(HIGHLIGHT_SYMBOL);
} }
StatefulWidget::render(table, table_area, buf, &mut content.state); StatefulWidget::render(table, table_area, buf, table_state);
} else { } else {
loading_block.render(table_area, buf); loading_block.render(table_area, buf);
} }
@@ -142,7 +150,7 @@ impl<'a, T, F> Widget for ManagarrTable<'a, T, F>
where where
F: Fn(&T) -> Row<'a>, 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); self.render_table(area, buf);
} }
} }
+2
View File
@@ -1,5 +1,7 @@
pub(super) mod button; pub(super) mod button;
pub(super) mod checkbox; pub(super) mod checkbox;
pub(super) mod error_message;
pub(super) mod input_box; pub(super) mod input_box;
pub(super) mod loading_block; pub(super) mod loading_block;
pub(super) mod managarr_table; pub(super) mod managarr_table;
pub(super) mod popup;
+34
View File
@@ -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<T: Widget> {
widget: T,
percent_x: u16,
percent_y: u16,
}
impl<T: Widget> Popup<T> {
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<T: Widget> Widget for Popup<T> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_popup(area, buf);
}
}
-8
View File
@@ -2,7 +2,6 @@ use log::LevelFilter;
use log4rs::append::file::FileAppender; use log4rs::append::file::FileAppender;
use log4rs::config::{Appender, Root}; use log4rs::config::{Appender, Root};
use log4rs::encode::pattern::PatternEncoder; use log4rs::encode::pattern::PatternEncoder;
use regex::Regex;
#[cfg(test)] #[cfg(test)]
#[path = "utils_tests.rs"] #[path = "utils_tests.rs"]
@@ -37,10 +36,3 @@ pub fn convert_runtime(runtime: i64) -> (i64, i64) {
(hours, minutes) (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()
}
+1 -9
View File
@@ -2,7 +2,7 @@
mod tests { mod tests {
use pretty_assertions::assert_eq; 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] #[test]
fn test_convert_to_gb() { fn test_convert_to_gb() {
@@ -17,12 +17,4 @@ mod tests {
assert_eq!(hours, 2); assert_eq!(hours, 2);
assert_eq!(minutes, 34); 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()
)
}
} }