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