A collection movie's overview renders in a popup that does not scroll, so a long overview overflows with no way to read the rest. The popup now scrolls. The popup owns its own ScrollableText, built from the selected collection movie's overview when it opens and torn down on Esc so a reopened popup starts at the top. Scrolling mutates only that owned state. The background renders the Collection record's overview and the movies table, neither of which carries a scroll offset, so they cannot scroll along with the popup. The popup also gains the loading and absent fallbacks the rest of the tree already has. Give the collection movie fixture an overview distinct from its collection's, and a multi-paragraph one, so the snapshots can tell the two records apart and a scrolled popup is distinguishable from an unscrolled one.
239 lines
7.2 KiB
Rust
239 lines
7.2 KiB
Rust
use crate::app::App;
|
|
use crate::event::Key;
|
|
use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler;
|
|
use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler;
|
|
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
|
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
|
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
|
use crate::matches_key;
|
|
use crate::models::radarr_models::Collection;
|
|
use crate::models::servarr_data::radarr::radarr_data::{
|
|
ActiveRadarrBlock, COLLECTIONS_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS,
|
|
};
|
|
use crate::models::stateful_table::SortOption;
|
|
use crate::models::{BlockSelectionState, Route};
|
|
use crate::network::radarr_network::RadarrEvent;
|
|
|
|
mod collection_details_handler;
|
|
mod edit_collection_handler;
|
|
mod movie_overview_handler;
|
|
|
|
#[cfg(test)]
|
|
#[path = "collections_handler_tests.rs"]
|
|
mod collections_handler_tests;
|
|
|
|
pub(super) struct CollectionsHandler<'a, 'b> {
|
|
key: Key,
|
|
app: &'a mut App<'b>,
|
|
active_radarr_block: ActiveRadarrBlock,
|
|
context: Option<ActiveRadarrBlock>,
|
|
}
|
|
|
|
impl CollectionsHandler<'_, '_> {}
|
|
|
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'a, 'b> {
|
|
fn handle(&mut self) {
|
|
let collections_table_handling_config =
|
|
TableHandlingConfig::new(ActiveRadarrBlock::Collections.into())
|
|
.sorting_block(ActiveRadarrBlock::CollectionsSortPrompt.into())
|
|
.sort_options(collections_sorting_options())
|
|
.searching_block(ActiveRadarrBlock::SearchCollection.into())
|
|
.search_error_block(ActiveRadarrBlock::SearchCollectionError.into())
|
|
.search_field_fn(|collection| &collection.title.text)
|
|
.filtering_block(ActiveRadarrBlock::FilterCollections.into())
|
|
.filter_error_block(ActiveRadarrBlock::FilterCollectionsError.into())
|
|
.filter_field_fn(|collection| &collection.title.text);
|
|
|
|
if !handle_table(
|
|
self,
|
|
|app| &mut app.data.radarr_data.collections,
|
|
collections_table_handling_config,
|
|
) {
|
|
match self.active_radarr_block {
|
|
_ if CollectionDetailsHandler::accepts(self.active_radarr_block) => {
|
|
CollectionDetailsHandler::new(self.key, self.app, self.active_radarr_block, self.context)
|
|
.handle();
|
|
}
|
|
_ if EditCollectionHandler::accepts(self.active_radarr_block) => {
|
|
EditCollectionHandler::new(self.key, self.app, self.active_radarr_block, self.context)
|
|
.handle();
|
|
}
|
|
_ => self.handle_key_event(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn accepts(active_block: ActiveRadarrBlock) -> bool {
|
|
CollectionDetailsHandler::accepts(active_block)
|
|
|| EditCollectionHandler::accepts(active_block)
|
|
|| COLLECTIONS_BLOCKS.contains(&active_block)
|
|
}
|
|
|
|
fn ignore_special_keys(&self) -> bool {
|
|
self.app.ignore_special_keys_for_textbox_input
|
|
}
|
|
|
|
fn new(
|
|
key: Key,
|
|
app: &'a mut App<'b>,
|
|
active_block: ActiveRadarrBlock,
|
|
context: Option<ActiveRadarrBlock>,
|
|
) -> CollectionsHandler<'a, 'b> {
|
|
CollectionsHandler {
|
|
key,
|
|
app,
|
|
active_radarr_block: active_block,
|
|
context,
|
|
}
|
|
}
|
|
|
|
fn get_key(&self) -> Key {
|
|
self.key
|
|
}
|
|
|
|
fn is_ready(&self) -> bool {
|
|
!self.app.is_loading && !self.app.data.radarr_data.collections.is_empty()
|
|
}
|
|
|
|
fn handle_scroll_up(&mut self) {}
|
|
|
|
fn handle_scroll_down(&mut self) {}
|
|
|
|
fn handle_home(&mut self) {}
|
|
|
|
fn handle_end(&mut self) {}
|
|
|
|
fn handle_delete(&mut self) {}
|
|
|
|
fn handle_left_right_action(&mut self) {
|
|
match self.active_radarr_block {
|
|
ActiveRadarrBlock::Collections => handle_change_tab_left_right_keys(self.app, self.key),
|
|
ActiveRadarrBlock::UpdateAllCollectionsPrompt => handle_prompt_toggle(self.app, self.key),
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
fn handle_submit(&mut self) {
|
|
match self.active_radarr_block {
|
|
ActiveRadarrBlock::Collections => self
|
|
.app
|
|
.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()),
|
|
ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
|
|
if self.app.data.radarr_data.prompt_confirm {
|
|
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
|
|
}
|
|
|
|
self.app.pop_navigation_stack();
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
fn handle_esc(&mut self) {
|
|
match self.active_radarr_block {
|
|
ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
|
|
self.app.pop_navigation_stack();
|
|
self.app.data.radarr_data.prompt_confirm = false;
|
|
}
|
|
_ => {
|
|
handle_clear_errors(self.app);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_char_key_event(&mut self) {
|
|
let key = self.key;
|
|
match self.active_radarr_block {
|
|
ActiveRadarrBlock::Collections => match self.key {
|
|
_ if matches_key!(edit, key) => {
|
|
self
|
|
.app
|
|
.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
|
|
self.app.data.radarr_data.edit_collection_modal =
|
|
Some((&self.app.data.radarr_data).into());
|
|
self.app.data.radarr_data.selected_block =
|
|
BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
|
|
}
|
|
_ if matches_key!(update, key) => {
|
|
self
|
|
.app
|
|
.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
|
|
}
|
|
_ if matches_key!(refresh, key) => {
|
|
self.app.should_refresh = true;
|
|
}
|
|
_ => (),
|
|
},
|
|
ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
|
|
if matches_key!(confirm, key) {
|
|
self.app.data.radarr_data.prompt_confirm = true;
|
|
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
|
|
|
|
self.app.pop_navigation_stack();
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
fn app_mut(&mut self) -> &mut App<'b> {
|
|
self.app
|
|
}
|
|
|
|
fn current_route(&self) -> Route {
|
|
self.app.get_current_route()
|
|
}
|
|
}
|
|
|
|
fn collections_sorting_options() -> Vec<SortOption<Collection>> {
|
|
vec![
|
|
SortOption {
|
|
name: "Collection",
|
|
cmp_fn: Some(|a, b| {
|
|
a.title
|
|
.text
|
|
.to_lowercase()
|
|
.cmp(&b.title.text.to_lowercase())
|
|
}),
|
|
},
|
|
SortOption {
|
|
name: "Number of Movies",
|
|
cmp_fn: Some(|a, b| {
|
|
let a_movie_count = a.movies.as_ref().unwrap_or(&Vec::new()).len();
|
|
let b_movie_count = b.movies.as_ref().unwrap_or(&Vec::new()).len();
|
|
|
|
a_movie_count.cmp(&b_movie_count)
|
|
}),
|
|
},
|
|
SortOption {
|
|
name: "Root Folder Path",
|
|
cmp_fn: Some(|a, b| {
|
|
let a_root_folder = a
|
|
.root_folder_path
|
|
.as_ref()
|
|
.unwrap_or(&String::new())
|
|
.to_owned();
|
|
let b_root_folder = b
|
|
.root_folder_path
|
|
.as_ref()
|
|
.unwrap_or(&String::new())
|
|
.to_owned();
|
|
|
|
a_root_folder.cmp(&b_root_folder)
|
|
}),
|
|
},
|
|
SortOption {
|
|
name: "Quality Profile",
|
|
cmp_fn: Some(|a, b| a.quality_profile_id.cmp(&b.quality_profile_id)),
|
|
},
|
|
SortOption {
|
|
name: "Search on Add",
|
|
cmp_fn: Some(|a, b| a.search_on_add.cmp(&b.search_on_add)),
|
|
},
|
|
SortOption {
|
|
name: "Monitored",
|
|
cmp_fn: Some(|a, b| a.monitored.cmp(&b.monitored)),
|
|
},
|
|
]
|
|
}
|