diff --git a/src/app/key_binding.rs b/src/app/key_binding.rs index 376d027..2d90ed3 100644 --- a/src/app/key_binding.rs +++ b/src/app/key_binding.rs @@ -16,6 +16,8 @@ generate_keybindings! { backspace, search, filter, + home, + end, submit, quit, esc @@ -55,6 +57,14 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings { key: Key::Char('f'), desc: "Filter", }, + home: KeyBinding { + key: Key::Home, + desc: "Home", + }, + end: KeyBinding { + key: Key::End, + desc: "End", + }, submit: KeyBinding { key: Key::Enter, desc: "Select", diff --git a/src/event/key.rs b/src/event/key.rs index ca95a30..84403fc 100644 --- a/src/event/key.rs +++ b/src/event/key.rs @@ -12,6 +12,8 @@ pub enum Key { Enter, Esc, Backspace, + Home, + End, Char(char), Unknown, } @@ -47,6 +49,14 @@ impl From for Key { code: KeyCode::Backspace, .. } => Key::Backspace, + KeyEvent { + code: KeyCode::Home, + .. + } => Key::Home, + KeyEvent { + code: KeyCode::End, + .. + } => Key::End, KeyEvent { code: KeyCode::Enter, .. diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index bad0d41..36e3956 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -13,6 +13,8 @@ pub trait KeyEventHandler<'a, T: Into> { match key { _ if *key == DEFAULT_KEYBINDINGS.up.key => self.handle_scroll_up(), _ if *key == DEFAULT_KEYBINDINGS.down.key => self.handle_scroll_down(), + _ if *key == DEFAULT_KEYBINDINGS.home.key => self.handle_home(), + _ if *key == DEFAULT_KEYBINDINGS.end.key => self.handle_end(), _ if *key == DEFAULT_KEYBINDINGS.left.key || *key == DEFAULT_KEYBINDINGS.right.key => { self.handle_tab_action() } @@ -30,6 +32,8 @@ pub trait KeyEventHandler<'a, T: Into> { fn get_key(&self) -> &Key; fn handle_scroll_up(&mut self); fn handle_scroll_down(&mut self); + fn handle_home(&mut self); + fn handle_end(&mut self); fn handle_tab_action(&mut self); fn handle_submit(&mut self); fn handle_esc(&mut self); diff --git a/src/handlers/radarr_handlers/collection_details_handler.rs b/src/handlers/radarr_handlers/collection_details_handler.rs index 637ed7d..3210deb 100644 --- a/src/handlers/radarr_handlers/collection_details_handler.rs +++ b/src/handlers/radarr_handlers/collection_details_handler.rs @@ -39,6 +39,23 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for CollectionDetailsHandler<'a> } } + fn handle_home(&mut self) { + if ActiveRadarrBlock::CollectionDetails == *self.active_radarr_block { + self.app.data.radarr_data.collection_movies.scroll_to_top(); + } + } + + fn handle_end(&mut self) { + if ActiveRadarrBlock::CollectionDetails == *self.active_radarr_block { + self + .app + .data + .radarr_data + .collection_movies + .scroll_to_bottom(); + } + } + fn handle_tab_action(&mut self) {} fn handle_submit(&mut self) { diff --git a/src/handlers/radarr_handlers/mod.rs b/src/handlers/radarr_handlers/mod.rs index 0708fe5..c900f88 100644 --- a/src/handlers/radarr_handlers/mod.rs +++ b/src/handlers/radarr_handlers/mod.rs @@ -3,7 +3,6 @@ use crate::app::radarr::ActiveRadarrBlock; use crate::handlers::radarr_handlers::collection_details_handler::CollectionDetailsHandler; use crate::handlers::radarr_handlers::movie_details_handler::MovieDetailsHandler; use crate::handlers::{handle_clear_errors, KeyEventHandler}; -use crate::models::radarr_models::{Collection, Movie}; use crate::models::Scrollable; use crate::utils::strip_non_alphanumeric_characters; use crate::{App, Key}; @@ -66,9 +65,6 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { self.app.data.radarr_data.collections.scroll_up() } } - ActiveRadarrBlock::CollectionDetails => { - self.app.data.radarr_data.collection_movies.scroll_up() - } ActiveRadarrBlock::Movies => { if !self.app.data.radarr_data.filtered_movies.items.is_empty() { self.app.data.radarr_data.filtered_movies.scroll_up(); @@ -97,9 +93,6 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { self.app.data.radarr_data.collections.scroll_down() } } - ActiveRadarrBlock::CollectionDetails => { - self.app.data.radarr_data.collection_movies.scroll_down() - } ActiveRadarrBlock::Movies => { if !self.app.data.radarr_data.filtered_movies.items.is_empty() { self.app.data.radarr_data.filtered_movies.scroll_down(); @@ -112,6 +105,72 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { } } + fn handle_home(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Collections => { + if !self + .app + .data + .radarr_data + .filtered_collections + .items + .is_empty() + { + self + .app + .data + .radarr_data + .filtered_collections + .scroll_to_top(); + } else { + self.app.data.radarr_data.collections.scroll_to_top() + } + } + ActiveRadarrBlock::Movies => { + if !self.app.data.radarr_data.filtered_movies.items.is_empty() { + self.app.data.radarr_data.filtered_movies.scroll_to_top(); + } else { + self.app.data.radarr_data.movies.scroll_to_top() + } + } + ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_to_top(), + _ => (), + } + } + + fn handle_end(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::Collections => { + if !self + .app + .data + .radarr_data + .filtered_collections + .items + .is_empty() + { + self + .app + .data + .radarr_data + .filtered_collections + .scroll_to_bottom(); + } else { + self.app.data.radarr_data.collections.scroll_to_bottom() + } + } + ActiveRadarrBlock::Movies => { + if !self.app.data.radarr_data.filtered_movies.items.is_empty() { + self.app.data.radarr_data.filtered_movies.scroll_to_bottom(); + } else { + self.app.data.radarr_data.movies.scroll_to_bottom() + } + } + ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_to_bottom(), + _ => (), + } + } + fn handle_tab_action(&mut self) { match self.active_radarr_block { ActiveRadarrBlock::Movies | ActiveRadarrBlock::Downloads | ActiveRadarrBlock::Collections => { @@ -156,123 +215,57 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { .app .push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()), ActiveRadarrBlock::SearchMovie => { - let search_string = self - .app - .data - .radarr_data - .search - .drain(..) - .collect::() - .to_lowercase(); - let movie_index = self + let selected_index = self + .search_table(&self.app.data.radarr_data.movies.items.clone(), |movie| { + &movie.title + }); + self .app .data .radarr_data .movies - .items - .iter() - .position(|movie| { - strip_non_alphanumeric_characters(&movie.title).contains(&search_string) - }); - - self.app.data.radarr_data.is_searching = false; - self.app.data.radarr_data.movies.select_index(movie_index); - - if movie_index.is_some() { - self.app.pop_navigation_stack(); - } + .select_index(selected_index); } ActiveRadarrBlock::SearchCollection => { - let search_string = self - .app - .data - .radarr_data - .search - .drain(..) - .collect::() - .to_lowercase(); - let collection_index = - self - .app - .data - .radarr_data - .collections - .items - .iter() - .position(|collection| { - strip_non_alphanumeric_characters(&collection.title).contains(&search_string) - }); - - self.app.data.radarr_data.is_searching = false; + let selected_index = self.search_table( + &self.app.data.radarr_data.collections.items.clone(), + |movie| &movie.title, + ); self .app .data .radarr_data .collections - .select_index(collection_index); - - if collection_index.is_some() { - self.app.pop_navigation_stack(); - } + .select_index(selected_index); } ActiveRadarrBlock::FilterMovies => { - let filter = strip_non_alphanumeric_characters( - &self - .app - .data - .radarr_data - .filter - .drain(..) - .collect::(), - ); - let movie_list = self.app.data.radarr_data.movies.items.clone(); - let filter_matches = movie_list - .iter() - .filter(|&movie| strip_non_alphanumeric_characters(&movie.title).contains(&filter)) - .map(|movie| movie.to_owned()) - .collect::>(); + let filtered_movies = self + .filter_table(&self.app.data.radarr_data.movies.items.clone(), |movie| { + &movie.title + }); - self.app.data.radarr_data.is_searching = false; - - if !filter_matches.is_empty() { - self.app.pop_navigation_stack(); + if !filtered_movies.is_empty() { self .app .data .radarr_data .filtered_movies - .set_items(filter_matches); + .set_items(filtered_movies); } } ActiveRadarrBlock::FilterCollections => { - let filter = strip_non_alphanumeric_characters( - &self - .app - .data - .radarr_data - .filter - .drain(..) - .collect::(), + let filtered_collections = self.filter_table( + &self.app.data.radarr_data.collections.items.clone(), + |collection| &collection.title, ); - let collection_list = self.app.data.radarr_data.collections.items.clone(); - let filter_matches = collection_list - .iter() - .filter(|&collection| { - strip_non_alphanumeric_characters(&collection.title).contains(&filter) - }) - .map(|collection| collection.to_owned()) - .collect::>(); - self.app.data.radarr_data.is_searching = false; - - if !filter_matches.is_empty() { - self.app.pop_navigation_stack(); + if !filtered_collections.is_empty() { self .app .data .radarr_data .filtered_collections - .set_items(filter_matches); + .set_items(filtered_collections); } } _ => (), @@ -350,3 +343,59 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { } } } + +impl RadarrHandler<'_> { + fn search_table(&mut self, rows: &[T], field_selection_fn: F) -> Option + where + F: Fn(&T) -> &str, + { + let search_string = self + .app + .data + .radarr_data + .search + .drain(..) + .collect::() + .to_lowercase(); + let collection_index = rows.iter().position(|item| { + strip_non_alphanumeric_characters(field_selection_fn(item)).contains(&search_string) + }); + + self.app.data.radarr_data.is_searching = false; + + if collection_index.is_some() { + self.app.pop_navigation_stack(); + } + + collection_index + } + + fn filter_table(&mut self, rows: &[T], field_selection_fn: F) -> Vec + where + F: Fn(&T) -> &str, + T: Clone, + { + let filter = strip_non_alphanumeric_characters( + &self + .app + .data + .radarr_data + .filter + .drain(..) + .collect::(), + ); + let filter_matches: Vec = rows + .iter() + .filter(|&item| strip_non_alphanumeric_characters(field_selection_fn(item)).contains(&filter)) + .cloned() + .collect(); + + self.app.data.radarr_data.is_searching = false; + + if !filter_matches.is_empty() { + self.app.pop_navigation_stack(); + } + + filter_matches + } +} diff --git a/src/handlers/radarr_handlers/movie_details_handler.rs b/src/handlers/radarr_handlers/movie_details_handler.rs index 384e76d..0a01d9e 100644 --- a/src/handlers/radarr_handlers/movie_details_handler.rs +++ b/src/handlers/radarr_handlers/movie_details_handler.rs @@ -48,6 +48,26 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for MovieDetailsHandler<'a> { } } + fn handle_home(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::MovieDetails => self.app.data.radarr_data.movie_details.scroll_to_top(), + ActiveRadarrBlock::MovieHistory => self.app.data.radarr_data.movie_history.scroll_to_top(), + ActiveRadarrBlock::Cast => self.app.data.radarr_data.movie_cast.scroll_to_top(), + ActiveRadarrBlock::Crew => self.app.data.radarr_data.movie_crew.scroll_to_top(), + _ => (), + } + } + + fn handle_end(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::MovieDetails => self.app.data.radarr_data.movie_details.scroll_to_bottom(), + ActiveRadarrBlock::MovieHistory => self.app.data.radarr_data.movie_history.scroll_to_bottom(), + ActiveRadarrBlock::Cast => self.app.data.radarr_data.movie_cast.scroll_to_bottom(), + ActiveRadarrBlock::Crew => self.app.data.radarr_data.movie_crew.scroll_to_bottom(), + _ => (), + } + } + fn handle_tab_action(&mut self) { match self.active_radarr_block { ActiveRadarrBlock::MovieDetails diff --git a/src/models/mod.rs b/src/models/mod.rs index ee92b52..e520508 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,7 +1,6 @@ use std::cell::RefCell; use std::fmt::{Debug, Display, Formatter}; -use log::debug; use serde::Deserialize; use tui::widgets::TableState; @@ -25,6 +24,8 @@ pub enum Route { 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); } pub struct StatefulTable { @@ -102,6 +103,14 @@ impl Scrollable for StatefulTable { self.state.select(Some(selected_row)); } + + fn scroll_to_top(&mut self) { + self.state.select(Some(0)); + } + + fn scroll_to_bottom(&mut self) { + self.state.select(Some(self.items.len() - 1)); + } } #[derive(Default)] @@ -134,6 +143,14 @@ impl Scrollable for ScrollableText { self.offset -= 1; } } + + fn scroll_to_top(&mut self) { + self.offset = 0; + } + + fn scroll_to_bottom(&mut self) { + self.offset = (self.items.len() - 1) as u16; + } } #[derive(Default, Deserialize, Debug, Clone, PartialEq, Eq)]