diff --git a/Cargo.lock b/Cargo.lock index 9c5bf37..fb845c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1386,7 +1386,6 @@ dependencies = [ "mockall", "mockito", "openssl", - "paste", "pretty_assertions", "ratatui", "regex", diff --git a/Cargo.toml b/Cargo.toml index 65a5932..f20f503 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,6 @@ managarr-tree-widget = "0.24.0" indicatif = "0.17.9" derive_setters = "0.1.6" deunicode = "1.6.0" -paste = "1.0.15" openssl = { version = "0.10.70", features = ["vendored"] } veil = "0.2.0" validate_theme_derive = "0.1.0" diff --git a/src/handlers/keybinding_handler.rs b/src/handlers/keybinding_handler.rs index f7fb972..b9560a0 100644 --- a/src/handlers/keybinding_handler.rs +++ b/src/handlers/keybinding_handler.rs @@ -1,10 +1,8 @@ use crate::app::App; use crate::event::Key; -use crate::handle_table_events; use crate::handlers::KeyEventHandler; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::models::servarr_data::ActiveKeybindingBlock; -use crate::models::servarr_models::KeybindingItem; #[cfg(test)] #[path = "keybinding_handler_tests.rs"] @@ -15,20 +13,15 @@ pub(super) struct KeybindingHandler<'a, 'b> { app: &'a mut App<'b>, } -impl KeybindingHandler<'_, '_> { - handle_table_events!( - self, - keybindings, - self.app.keymapping_table.as_mut().unwrap(), - KeybindingItem - ); -} - impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveKeybindingBlock> for KeybindingHandler<'a, 'b> { fn handle(&mut self) { let keybinding_table_handling_config = TableHandlingConfig::new(self.app.get_current_route()); - if !self.handle_keybindings_table_events(keybinding_table_handling_config) { + if !handle_table( + self, + |app| app.keymapping_table.as_mut().unwrap(), + keybinding_table_handling_config, + ) { self.handle_key_event(); } } @@ -77,4 +70,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveKeybindingBlock> for KeybindingHandle } fn handle_char_key_event(&mut self) {} + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 5393139..6cb0691 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -93,6 +93,18 @@ pub trait KeyEventHandler<'a, 'b, T: Into + Copy> { fn handle_submit(&mut self); fn handle_esc(&mut self); fn handle_char_key_event(&mut self); + + /// Returns a mutable reference to the application state. + /// + /// This method is used by the trait-based table handler to modify app state during + /// table operations (e.g., navigation stack, loading flags). + fn app_mut(&mut self) -> &mut App<'b>; + + /// Returns the current navigation route. + /// + /// This method is used by the trait-based table handler to determine which screen + /// or mode is currently active, enabling context-aware event handling. + fn current_route(&self) -> Route; } pub fn handle_events(key: Key, app: &mut App<'_>) { diff --git a/src/handlers/radarr_handlers/blocklist/mod.rs b/src/handlers/radarr_handlers/blocklist/mod.rs index 19834f3..9cf43a0 100644 --- a/src/handlers/radarr_handlers/blocklist/mod.rs +++ b/src/handlers/radarr_handlers/blocklist/mod.rs @@ -1,13 +1,13 @@ use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; -use crate::handlers::table_handler::TableHandlingConfig; +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::BlocklistItem; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS}; use crate::models::stateful_table::SortOption; use crate::network::radarr_network::RadarrEvent; -use crate::{handle_table_events, matches_key}; #[cfg(test)] #[path = "blocklist_handler_tests.rs"] @@ -21,13 +21,6 @@ pub(super) struct BlocklistHandler<'a, 'b> { } impl BlocklistHandler<'_, '_> { - handle_table_events!( - self, - blocklist, - self.app.data.radarr_data.blocklist, - BlocklistItem - ); - fn extract_blocklist_item_id(&self) -> i64 { self.app.data.radarr_data.blocklist.current_selection().id } @@ -40,7 +33,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a, .sorting_block(ActiveRadarrBlock::BlocklistSortPrompt.into()) .sort_options(blocklist_sorting_options()); - if !self.handle_blocklist_table_events(blocklist_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.radarr_data.blocklist, + blocklist_table_handling_config, + ) { self.handle_key_event(); } } @@ -176,6 +173,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a, _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } fn blocklist_sorting_options() -> Vec> { diff --git a/src/handlers/radarr_handlers/collections/collection_details_handler.rs b/src/handlers/radarr_handlers/collections/collection_details_handler.rs index 2f8923b..2cb628b 100644 --- a/src/handlers/radarr_handlers/collections/collection_details_handler.rs +++ b/src/handlers/radarr_handlers/collections/collection_details_handler.rs @@ -1,15 +1,14 @@ use crate::app::App; use crate::event::Key; use crate::handlers::KeyEventHandler; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; +use crate::matches_key; use crate::models::BlockSelectionState; -use crate::models::radarr_models::CollectionMovie; use crate::models::servarr_data::radarr::radarr_data::{ ADD_MOVIE_SELECTION_BLOCKS, ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS, }; use crate::models::stateful_table::StatefulTable; -use crate::{handle_table_events, matches_key}; #[cfg(test)] #[path = "collection_details_handler_tests.rs"] @@ -22,21 +21,18 @@ pub(super) struct CollectionDetailsHandler<'a, 'b> { _context: Option, } -impl CollectionDetailsHandler<'_, '_> { - handle_table_events!( - self, - collection_movies, - self.app.data.radarr_data.collection_movies, - CollectionMovie - ); -} +impl CollectionDetailsHandler<'_, '_> {} impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHandler<'a, 'b> { fn handle(&mut self) { let collection_movies_table_handling_config = TableHandlingConfig::new(ActiveRadarrBlock::CollectionDetails.into()); - if !self.handle_collection_movies_table_events(collection_movies_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.radarr_data.collection_movies, + collection_movies_table_handling_config, + ) { self.handle_key_event(); } } @@ -147,4 +143,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS); } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/collections/edit_collection_handler.rs b/src/handlers/radarr_handlers/collections/edit_collection_handler.rs index 60adb5f..dff7e63 100644 --- a/src/handlers/radarr_handlers/collections/edit_collection_handler.rs +++ b/src/handlers/radarr_handlers/collections/edit_collection_handler.rs @@ -371,4 +371,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/collections/mod.rs b/src/handlers/radarr_handlers/collections/mod.rs index ffdaf5c..69e22d1 100644 --- a/src/handlers/radarr_handlers/collections/mod.rs +++ b/src/handlers/radarr_handlers/collections/mod.rs @@ -3,8 +3,9 @@ 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; +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::BlockSelectionState; use crate::models::radarr_models::Collection; use crate::models::servarr_data::radarr::radarr_data::{ @@ -12,7 +13,6 @@ use crate::models::servarr_data::radarr::radarr_data::{ }; use crate::models::stateful_table::SortOption; use crate::network::radarr_network::RadarrEvent; -use crate::{handle_table_events, matches_key}; mod collection_details_handler; mod edit_collection_handler; @@ -28,14 +28,7 @@ pub(super) struct CollectionsHandler<'a, 'b> { context: Option, } -impl CollectionsHandler<'_, '_> { - handle_table_events!( - self, - collections, - self.app.data.radarr_data.collections, - Collection - ); -} +impl CollectionsHandler<'_, '_> {} impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'a, 'b> { fn handle(&mut self) { @@ -50,7 +43,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' .filter_error_block(ActiveRadarrBlock::FilterCollectionsError.into()) .filter_field_fn(|collection| &collection.title.text); - if !self.handle_collections_table_events(collections_table_handling_config) { + 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) @@ -177,6 +174,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } fn collections_sorting_options() -> Vec> { diff --git a/src/handlers/radarr_handlers/downloads/mod.rs b/src/handlers/radarr_handlers/downloads/mod.rs index 3ee8869..3a449d1 100644 --- a/src/handlers/radarr_handlers/downloads/mod.rs +++ b/src/handlers/radarr_handlers/downloads/mod.rs @@ -1,12 +1,11 @@ use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle}; -use crate::models::radarr_models::DownloadRecord; +use crate::matches_key; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; use crate::network::radarr_network::RadarrEvent; -use crate::{handle_table_events, matches_key}; #[cfg(test)] #[path = "downloads_handler_tests.rs"] @@ -20,13 +19,6 @@ pub(super) struct DownloadsHandler<'a, 'b> { } impl DownloadsHandler<'_, '_> { - handle_table_events!( - self, - downloads, - self.app.data.radarr_data.downloads, - DownloadRecord - ); - fn extract_download_id(&self) -> i64 { self.app.data.radarr_data.downloads.current_selection().id } @@ -37,7 +29,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a, let downloads_table_handling_config = TableHandlingConfig::new(ActiveRadarrBlock::Downloads.into()); - if !self.handle_downloads_table_events(downloads_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.radarr_data.downloads, + downloads_table_handling_config, + ) { self.handle_key_event(); } } @@ -163,4 +159,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a, _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs index 4f684d2..33a2383 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs @@ -522,4 +522,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<' _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs index eae3f87..f933187 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs @@ -288,4 +288,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/indexers/mod.rs b/src/handlers/radarr_handlers/indexers/mod.rs index 28fa376..85f5adc 100644 --- a/src/handlers/radarr_handlers/indexers/mod.rs +++ b/src/handlers/radarr_handlers/indexers/mod.rs @@ -4,16 +4,15 @@ use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; use crate::handlers::radarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler; -use crate::handlers::table_handler::TableHandlingConfig; +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::BlockSelectionState; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, INDEXERS_BLOCKS, }; -use crate::models::servarr_models::Indexer; use crate::network::radarr_network::RadarrEvent; -use crate::{handle_table_events, matches_key}; mod edit_indexer_handler; mod edit_indexer_settings_handler; @@ -31,8 +30,6 @@ pub(super) struct IndexersHandler<'a, 'b> { } impl IndexersHandler<'_, '_> { - handle_table_events!(self, indexers, self.app.data.radarr_data.indexers, Indexer); - fn extract_indexer_id(&self) -> i64 { self.app.data.radarr_data.indexers.current_selection().id } @@ -43,7 +40,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, let indexer_table_handling_config = TableHandlingConfig::new(ActiveRadarrBlock::Indexers.into()); - if !self.handle_indexers_table_events(indexer_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.radarr_data.indexers, + indexer_table_handling_config, + ) { match self.active_radarr_block { _ if EditIndexerHandler::accepts(self.active_radarr_block) => { EditIndexerHandler::new(self.key, self.app, self.active_radarr_block, self.context) @@ -206,4 +207,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/indexers/test_all_indexers_handler.rs b/src/handlers/radarr_handlers/indexers/test_all_indexers_handler.rs index c3dd2ba..c8e26d4 100644 --- a/src/handlers/radarr_handlers/indexers/test_all_indexers_handler.rs +++ b/src/handlers/radarr_handlers/indexers/test_all_indexers_handler.rs @@ -1,9 +1,7 @@ use crate::app::App; use crate::event::Key; -use crate::handle_table_events; use crate::handlers::KeyEventHandler; -use crate::handlers::table_handler::TableHandlingConfig; -use crate::models::servarr_data::modals::IndexerTestResultModalItem; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; #[cfg(test)] @@ -17,27 +15,23 @@ pub(super) struct TestAllIndexersHandler<'a, 'b> { _context: Option, } -impl TestAllIndexersHandler<'_, '_> { - handle_table_events!( - self, - indexer_test_all_results, - self - .app - .data - .radarr_data - .indexer_test_all_results - .as_mut() - .unwrap(), - IndexerTestResultModalItem - ); -} +impl TestAllIndexersHandler<'_, '_> {} impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandler<'a, 'b> { fn handle(&mut self) { let test_all_indexers_test_results_table_handler_config = TableHandlingConfig::new(ActiveRadarrBlock::TestAllIndexers.into()); - if !self.handle_indexer_test_all_results_table_events( + if !handle_table( + self, + |app| { + app + .data + .radarr_data + .indexer_test_all_results + .as_mut() + .unwrap() + }, test_all_indexers_test_results_table_handler_config, ) { self.handle_key_event(); @@ -102,4 +96,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandl } fn handle_char_key_event(&mut self) {} + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/library/add_movie_handler.rs b/src/handlers/radarr_handlers/library/add_movie_handler.rs index 2a6b9da..e668304 100644 --- a/src/handlers/radarr_handlers/library/add_movie_handler.rs +++ b/src/handlers/radarr_handlers/library/add_movie_handler.rs @@ -1,4 +1,4 @@ -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::handlers::{KeyEventHandler, handle_prompt_toggle}; use crate::models::radarr_models::{ AddMovieBody, AddMovieOptions, AddMovieSearchResult, CollectionMovie, @@ -9,9 +9,7 @@ use crate::models::servarr_data::radarr::radarr_data::{ }; use crate::models::{BlockSelectionState, Scrollable}; use crate::network::radarr_network::RadarrEvent; -use crate::{ - App, Key, handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key, -}; +use crate::{App, Key, handle_text_box_keys, handle_text_box_left_right_keys, matches_key}; #[cfg(test)] #[path = "add_movie_handler_tests.rs"] @@ -25,19 +23,6 @@ pub(super) struct AddMovieHandler<'a, 'b> { } impl AddMovieHandler<'_, '_> { - handle_table_events!( - self, - add_movie_search_results, - self - .app - .data - .radarr_data - .add_searched_movies - .as_mut() - .expect("add_searched_movies should be initialized"), - AddMovieSearchResult - ); - fn build_add_movie_body(&mut self) -> AddMovieBody { let add_movie_modal = self .app @@ -123,7 +108,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, let add_movie_table_handling_config = TableHandlingConfig::new(ActiveRadarrBlock::AddMovieSearchResults.into()); - if !self.handle_add_movie_search_results_table_events(add_movie_table_handling_config) { + if !handle_table( + self, + |app| { + app + .data + .radarr_data + .add_searched_movies + .as_mut() + .expect("add_searched_movies should be initialized") + }, + add_movie_table_handling_config, + ) { self.handle_key_event(); } } @@ -557,4 +553,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/library/delete_movie_handler.rs b/src/handlers/radarr_handlers/library/delete_movie_handler.rs index 6a38063..561489f 100644 --- a/src/handlers/radarr_handlers/library/delete_movie_handler.rs +++ b/src/handlers/radarr_handlers/library/delete_movie_handler.rs @@ -136,4 +136,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<' self.app.pop_navigation_stack(); } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/library/edit_movie_handler.rs b/src/handlers/radarr_handlers/library/edit_movie_handler.rs index 61739b3..f463d0f 100644 --- a/src/handlers/radarr_handlers/library/edit_movie_handler.rs +++ b/src/handlers/radarr_handlers/library/edit_movie_handler.rs @@ -392,4 +392,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a, _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/library/mod.rs b/src/handlers/radarr_handlers/library/mod.rs index 1834399..7c441ee 100644 --- a/src/handlers/radarr_handlers/library/mod.rs +++ b/src/handlers/radarr_handlers/library/mod.rs @@ -7,15 +7,15 @@ use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHand use crate::handlers::radarr_handlers::library::movie_details_handler::MovieDetailsHandler; use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle}; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; +use crate::matches_key; use crate::models::radarr_models::Movie; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, DELETE_MOVIE_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, LIBRARY_BLOCKS, }; use crate::models::stateful_table::SortOption; -use crate::models::{BlockSelectionState, HorizontallyScrollableText}; +use crate::models::{BlockSelectionState, HorizontallyScrollableText, Route}; use crate::network::radarr_network::RadarrEvent; -use crate::{handle_table_events, matches_key}; mod add_movie_handler; mod delete_movie_handler; @@ -34,7 +34,6 @@ pub(super) struct LibraryHandler<'a, 'b> { } impl LibraryHandler<'_, '_> { - handle_table_events!(self, movies, self.app.data.radarr_data.movies, Movie); fn extract_movie_id(&self) -> i64 { self.app.data.radarr_data.movies.current_selection().id } @@ -52,7 +51,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' .filter_error_block(ActiveRadarrBlock::FilterMoviesError.into()) .filter_field_fn(|movie| &movie.title.text); - if !self.handle_movies_table_events(movie_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.radarr_data.movies, + movie_table_handling_config, + ) { match self.active_radarr_block { _ if AddMovieHandler::accepts(self.active_radarr_block) => { AddMovieHandler::new(self.key, self.app, self.active_radarr_block, self.context).handle(); @@ -215,6 +218,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> Route { + self.app.get_current_route() + } } fn movies_sorting_options() -> Vec> { diff --git a/src/handlers/radarr_handlers/library/movie_details_handler.rs b/src/handlers/radarr_handlers/library/movie_details_handler.rs index e76fdeb..9877b79 100644 --- a/src/handlers/radarr_handlers/library/movie_details_handler.rs +++ b/src/handlers/radarr_handlers/library/movie_details_handler.rs @@ -2,11 +2,10 @@ use serde_json::Number; use crate::app::App; use crate::event::Key; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::handlers::{KeyEventHandler, handle_prompt_toggle}; -use crate::models::radarr_models::{ - Credit, MovieHistoryItem, RadarrRelease, RadarrReleaseDownloadBody, -}; +use crate::matches_key; +use crate::models::radarr_models::{RadarrRelease, RadarrReleaseDownloadBody}; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, EDIT_MOVIE_SELECTION_BLOCKS, MOVIE_DETAILS_BLOCKS, }; @@ -14,7 +13,6 @@ use crate::models::servarr_models::Language; use crate::models::stateful_table::SortOption; use crate::models::{BlockSelectionState, Scrollable}; use crate::network::radarr_network::RadarrEvent; -use crate::{handle_table_events, matches_key}; #[cfg(test)] #[path = "movie_details_handler_tests.rs"] @@ -28,59 +26,6 @@ pub(super) struct MovieDetailsHandler<'a, 'b> { } impl MovieDetailsHandler<'_, '_> { - handle_table_events!( - self, - movie_releases, - self - .app - .data - .radarr_data - .movie_details_modal - .as_mut() - .unwrap() - .movie_releases, - RadarrRelease - ); - handle_table_events!( - self, - movie_history, - self - .app - .data - .radarr_data - .movie_details_modal - .as_mut() - .unwrap() - .movie_history, - MovieHistoryItem - ); - handle_table_events!( - self, - movie_cast, - self - .app - .data - .radarr_data - .movie_details_modal - .as_mut() - .unwrap() - .movie_cast, - Credit - ); - handle_table_events!( - self, - movie_crew, - self - .app - .data - .radarr_data - .movie_details_modal - .as_mut() - .unwrap() - .movie_crew, - Credit - ); - fn build_radarr_release_download_body(&self) -> RadarrReleaseDownloadBody { let movie_id = self.app.data.radarr_data.movies.current_selection().id; let (guid, indexer_id) = { @@ -122,11 +67,55 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler< let movie_cast_table_handling_config = TableHandlingConfig::new(ActiveRadarrBlock::Cast.into()); let movie_crew_table_handling_config = TableHandlingConfig::new(ActiveRadarrBlock::Crew.into()); - if !self.handle_movie_history_table_events(movie_history_table_handling_config) - && !self.handle_movie_releases_table_events(movie_releases_table_handling_config) - && !self.handle_movie_cast_table_events(movie_cast_table_handling_config) - && !self.handle_movie_crew_table_events(movie_crew_table_handling_config) - { + if !handle_table( + self, + |app| { + &mut app + .data + .radarr_data + .movie_details_modal + .as_mut() + .unwrap() + .movie_history + }, + movie_history_table_handling_config, + ) && !handle_table( + self, + |app| { + &mut app + .data + .radarr_data + .movie_details_modal + .as_mut() + .unwrap() + .movie_releases + }, + movie_releases_table_handling_config, + ) && !handle_table( + self, + |app| { + &mut app + .data + .radarr_data + .movie_details_modal + .as_mut() + .unwrap() + .movie_cast + }, + movie_cast_table_handling_config, + ) && !handle_table( + self, + |app| { + &mut app + .data + .radarr_data + .movie_details_modal + .as_mut() + .unwrap() + .movie_crew + }, + movie_crew_table_handling_config, + ) { self.handle_key_event(); } } @@ -385,6 +374,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler< _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } fn releases_sorting_options() -> Vec> { diff --git a/src/handlers/radarr_handlers/mod.rs b/src/handlers/radarr_handlers/mod.rs index 79acc5c..d8f8075 100644 --- a/src/handlers/radarr_handlers/mod.rs +++ b/src/handlers/radarr_handlers/mod.rs @@ -107,6 +107,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b fn handle_esc(&mut self) {} fn handle_char_key_event(&mut self) {} + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) { diff --git a/src/handlers/radarr_handlers/root_folders/mod.rs b/src/handlers/radarr_handlers/root_folders/mod.rs index 8ceb0aa..aa2a64f 100644 --- a/src/handlers/radarr_handlers/root_folders/mod.rs +++ b/src/handlers/radarr_handlers/root_folders/mod.rs @@ -1,15 +1,13 @@ use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle}; use crate::models::HorizontallyScrollableText; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; -use crate::models::servarr_models::{AddRootFolderBody, RootFolder}; +use crate::models::servarr_models::AddRootFolderBody; use crate::network::radarr_network::RadarrEvent; -use crate::{ - handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key, -}; +use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key}; #[cfg(test)] #[path = "root_folders_handler_tests.rs"] @@ -23,13 +21,6 @@ pub(super) struct RootFoldersHandler<'a, 'b> { } impl RootFoldersHandler<'_, '_> { - handle_table_events!( - self, - root_folders, - self.app.data.radarr_data.root_folders, - RootFolder - ); - fn build_add_root_folder_body(&mut self) -> AddRootFolderBody { let edit_root_folder = self .app @@ -60,7 +51,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<' let root_folder_table_handling_config = TableHandlingConfig::new(ActiveRadarrBlock::RootFolders.into()); - if !self.handle_root_folders_table_events(root_folder_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.radarr_data.root_folders, + root_folder_table_handling_config, + ) { self.handle_key_event(); } } @@ -231,4 +226,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<' _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/system/mod.rs b/src/handlers/radarr_handlers/system/mod.rs index f225624..50201b0 100644 --- a/src/handlers/radarr_handlers/system/mod.rs +++ b/src/handlers/radarr_handlers/system/mod.rs @@ -124,4 +124,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b } } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/radarr_handlers/system/system_details_handler.rs b/src/handlers/radarr_handlers/system/system_details_handler.rs index 77b5082..7b13d4e 100644 --- a/src/handlers/radarr_handlers/system/system_details_handler.rs +++ b/src/handlers/radarr_handlers/system/system_details_handler.rs @@ -196,4 +196,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler self.app.pop_navigation_stack(); } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/blocklist/mod.rs b/src/handlers/sonarr_handlers/blocklist/mod.rs index 2e66041..fa08c85 100644 --- a/src/handlers/sonarr_handlers/blocklist/mod.rs +++ b/src/handlers/sonarr_handlers/blocklist/mod.rs @@ -1,13 +1,13 @@ use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; -use crate::handlers::table_handler::TableHandlingConfig; +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::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, BLOCKLIST_BLOCKS}; use crate::models::sonarr_models::BlocklistItem; use crate::models::stateful_table::SortOption; use crate::network::sonarr_network::SonarrEvent; -use crate::{handle_table_events, matches_key}; #[cfg(test)] #[path = "blocklist_handler_tests.rs"] @@ -21,13 +21,6 @@ pub(super) struct BlocklistHandler<'a, 'b> { } impl BlocklistHandler<'_, '_> { - handle_table_events!( - self, - blocklist, - self.app.data.sonarr_data.blocklist, - BlocklistItem - ); - fn extract_blocklist_item_id(&self) -> i64 { self.app.data.sonarr_data.blocklist.current_selection().id } @@ -40,7 +33,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a, .sorting_block(ActiveSonarrBlock::BlocklistSortPrompt.into()) .sort_options(blocklist_sorting_options()); - if !self.handle_blocklist_table_events(blocklist_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.sonarr_data.blocklist, + blocklist_table_handling_config, + ) { self.handle_key_event(); } } @@ -176,6 +173,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a, _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } fn blocklist_sorting_options() -> Vec> { diff --git a/src/handlers/sonarr_handlers/downloads/mod.rs b/src/handlers/sonarr_handlers/downloads/mod.rs index 5229e59..7a9819a 100644 --- a/src/handlers/sonarr_handlers/downloads/mod.rs +++ b/src/handlers/sonarr_handlers/downloads/mod.rs @@ -1,12 +1,11 @@ use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; -use crate::handlers::table_handler::TableHandlingConfig; +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::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DOWNLOADS_BLOCKS}; -use crate::models::sonarr_models::DownloadRecord; use crate::network::sonarr_network::SonarrEvent; -use crate::{handle_table_events, matches_key}; #[cfg(test)] #[path = "downloads_handler_tests.rs"] @@ -20,13 +19,6 @@ pub(super) struct DownloadsHandler<'a, 'b> { } impl DownloadsHandler<'_, '_> { - handle_table_events!( - self, - downloads, - self.app.data.sonarr_data.downloads, - DownloadRecord - ); - fn extract_download_id(&self) -> i64 { self.app.data.sonarr_data.downloads.current_selection().id } @@ -37,7 +29,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a, let download_table_handling_config = TableHandlingConfig::new(ActiveSonarrBlock::Downloads.into()); - if !self.handle_downloads_table_events(download_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.sonarr_data.downloads, + download_table_handling_config, + ) { self.handle_key_event(); } } @@ -163,4 +159,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a, _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/history/mod.rs b/src/handlers/sonarr_handlers/history/mod.rs index 91942a0..58563d1 100644 --- a/src/handlers/sonarr_handlers/history/mod.rs +++ b/src/handlers/sonarr_handlers/history/mod.rs @@ -1,13 +1,13 @@ use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::handlers::{KeyEventHandler, handle_clear_errors}; +use crate::matches_key; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, HISTORY_BLOCKS}; use crate::models::servarr_models::Language; use crate::models::sonarr_models::SonarrHistoryItem; use crate::models::stateful_table::SortOption; -use crate::{handle_table_events, matches_key}; #[cfg(test)] #[path = "history_handler_tests.rs"] @@ -20,14 +20,7 @@ pub(super) struct HistoryHandler<'a, 'b> { _context: Option, } -impl HistoryHandler<'_, '_> { - handle_table_events!( - self, - history, - self.app.data.sonarr_data.history, - SonarrHistoryItem - ); -} +impl HistoryHandler<'_, '_> {} impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, 'b> { fn handle(&mut self) { @@ -41,7 +34,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, ' .filter_error_block(ActiveSonarrBlock::FilterHistoryError.into()) .filter_field_fn(|history| &history.source_title.text); - if !self.handle_history_table_events(history_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.sonarr_data.history, + history_table_handling_config, + ) { self.handle_key_event(); } } @@ -119,6 +116,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, ' } } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } pub(in crate::handlers::sonarr_handlers) fn history_sorting_options() diff --git a/src/handlers/sonarr_handlers/indexers/edit_indexer_handler.rs b/src/handlers/sonarr_handlers/indexers/edit_indexer_handler.rs index 551ef61..c253008 100644 --- a/src/handlers/sonarr_handlers/indexers/edit_indexer_handler.rs +++ b/src/handlers/sonarr_handlers/indexers/edit_indexer_handler.rs @@ -521,4 +521,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<' _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler.rs b/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler.rs index 4b31164..8da2449 100644 --- a/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler.rs +++ b/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler.rs @@ -197,4 +197,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl self.app.pop_navigation_stack(); } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/indexers/mod.rs b/src/handlers/sonarr_handlers/indexers/mod.rs index dbb7713..cc33890 100644 --- a/src/handlers/sonarr_handlers/indexers/mod.rs +++ b/src/handlers/sonarr_handlers/indexers/mod.rs @@ -4,16 +4,15 @@ use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::sonarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; use crate::handlers::sonarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; use crate::handlers::sonarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler; -use crate::handlers::table_handler::TableHandlingConfig; +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::BlockSelectionState; use crate::models::servarr_data::sonarr::sonarr_data::{ ActiveSonarrBlock, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, INDEXERS_BLOCKS, }; -use crate::models::servarr_models::Indexer; use crate::network::sonarr_network::SonarrEvent; -use crate::{handle_table_events, matches_key}; mod edit_indexer_handler; mod edit_indexer_settings_handler; @@ -31,8 +30,6 @@ pub(super) struct IndexersHandler<'a, 'b> { } impl IndexersHandler<'_, '_> { - handle_table_events!(self, indexers, self.app.data.sonarr_data.indexers, Indexer); - fn extract_indexer_id(&self) -> i64 { self.app.data.sonarr_data.indexers.current_selection().id } @@ -43,7 +40,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a, let indexers_table_handling_config = TableHandlingConfig::new(ActiveSonarrBlock::Indexers.into()); - if !self.handle_indexers_table_events(indexers_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.sonarr_data.indexers, + indexers_table_handling_config, + ) { match self.active_sonarr_block { _ if EditIndexerHandler::accepts(self.active_sonarr_block) => { EditIndexerHandler::new(self.key, self.app, self.active_sonarr_block, self.context) @@ -205,4 +206,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a, _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/indexers/test_all_indexers_handler.rs b/src/handlers/sonarr_handlers/indexers/test_all_indexers_handler.rs index 1047772..d0e3d57 100644 --- a/src/handlers/sonarr_handlers/indexers/test_all_indexers_handler.rs +++ b/src/handlers/sonarr_handlers/indexers/test_all_indexers_handler.rs @@ -1,9 +1,7 @@ use crate::app::App; use crate::event::Key; -use crate::handle_table_events; use crate::handlers::KeyEventHandler; -use crate::handlers::table_handler::TableHandlingConfig; -use crate::models::servarr_data::modals::IndexerTestResultModalItem; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; #[cfg(test)] @@ -17,29 +15,25 @@ pub(super) struct TestAllIndexersHandler<'a, 'b> { _context: Option, } -impl TestAllIndexersHandler<'_, '_> { - handle_table_events!( - self, - indexer_test_all_results, - self - .app - .data - .sonarr_data - .indexer_test_all_results - .as_mut() - .unwrap(), - IndexerTestResultModalItem - ); -} +impl TestAllIndexersHandler<'_, '_> {} impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for TestAllIndexersHandler<'a, 'b> { fn handle(&mut self) { let indexer_test_all_results_table_handling_config = TableHandlingConfig::new(ActiveSonarrBlock::TestAllIndexers.into()); - if !self - .handle_indexer_test_all_results_table_events(indexer_test_all_results_table_handling_config) - { + if !handle_table( + self, + |app| { + app + .data + .sonarr_data + .indexer_test_all_results + .as_mut() + .unwrap() + }, + indexer_test_all_results_table_handling_config, + ) { self.handle_key_event(); } } @@ -102,4 +96,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for TestAllIndexersHandl } fn handle_char_key_event(&mut self) {} + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/library/add_series_handler.rs b/src/handlers/sonarr_handlers/library/add_series_handler.rs index d8ac096..e6f8162 100644 --- a/src/handlers/sonarr_handlers/library/add_series_handler.rs +++ b/src/handlers/sonarr_handlers/library/add_series_handler.rs @@ -1,4 +1,4 @@ -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::handlers::{KeyEventHandler, handle_prompt_toggle}; use crate::models::servarr_data::sonarr::modals::AddSeriesModal; use crate::models::servarr_data::sonarr::sonarr_data::{ @@ -7,9 +7,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ use crate::models::sonarr_models::{AddSeriesBody, AddSeriesOptions, AddSeriesSearchResult}; use crate::models::{BlockSelectionState, Scrollable}; use crate::network::sonarr_network::SonarrEvent; -use crate::{ - App, Key, handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key, -}; +use crate::{App, Key, handle_text_box_keys, handle_text_box_left_right_keys, matches_key}; #[cfg(test)] #[path = "add_series_handler_tests.rs"] @@ -23,19 +21,6 @@ pub(super) struct AddSeriesHandler<'a, 'b> { } impl AddSeriesHandler<'_, '_> { - handle_table_events!( - self, - add_searched_series, - self - .app - .data - .sonarr_data - .add_searched_series - .as_mut() - .expect("add_searched_series should be initialized"), - AddSeriesSearchResult - ); - fn build_add_series_body(&mut self) -> AddSeriesBody { let add_series_modal = self .app @@ -117,7 +102,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a, let add_series_table_handling_config = TableHandlingConfig::new(ActiveSonarrBlock::AddSeriesSearchResults.into()); - if !self.handle_add_searched_series_table_events(add_series_table_handling_config) { + if !handle_table( + self, + |app| { + app + .data + .sonarr_data + .add_searched_series + .as_mut() + .expect("add_searched_series should be initialized") + }, + add_series_table_handling_config, + ) { self.handle_key_event(); } } @@ -624,4 +620,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a, _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/library/delete_series_handler.rs b/src/handlers/sonarr_handlers/library/delete_series_handler.rs index 869a855..97fde80 100644 --- a/src/handlers/sonarr_handlers/library/delete_series_handler.rs +++ b/src/handlers/sonarr_handlers/library/delete_series_handler.rs @@ -138,4 +138,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler< self.app.pop_navigation_stack(); } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/library/edit_series_handler.rs b/src/handlers/sonarr_handlers/library/edit_series_handler.rs index c780e6a..a57224e 100644 --- a/src/handlers/sonarr_handlers/library/edit_series_handler.rs +++ b/src/handlers/sonarr_handlers/library/edit_series_handler.rs @@ -466,4 +466,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/library/episode_details_handler.rs b/src/handlers/sonarr_handlers/library/episode_details_handler.rs index a754419..c007778 100644 --- a/src/handlers/sonarr_handlers/library/episode_details_handler.rs +++ b/src/handlers/sonarr_handlers/library/episode_details_handler.rs @@ -1,12 +1,12 @@ use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::library::season_details_handler::releases_sorting_options; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::handlers::{KeyEventHandler, handle_prompt_toggle}; +use crate::matches_key; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS}; -use crate::models::sonarr_models::{SonarrHistoryItem, SonarrRelease, SonarrReleaseDownloadBody}; +use crate::models::sonarr_models::{SonarrRelease, SonarrReleaseDownloadBody}; use crate::network::sonarr_network::SonarrEvent; -use crate::{handle_table_events, matches_key}; #[cfg(test)] #[path = "episode_details_handler_tests.rs"] @@ -20,39 +20,6 @@ pub(super) struct EpisodeDetailsHandler<'a, 'b> { } impl EpisodeDetailsHandler<'_, '_> { - handle_table_events!( - self, - episode_history, - self - .app - .data - .sonarr_data - .season_details_modal - .as_mut() - .expect("Season details modal is undefined") - .episode_details_modal - .as_mut() - .expect("Episode details modal is undefined") - .episode_history, - SonarrHistoryItem - ); - handle_table_events!( - self, - episode_releases, - self - .app - .data - .sonarr_data - .season_details_modal - .as_mut() - .expect("Season details modal is undefined") - .episode_details_modal - .as_mut() - .expect("Episode details modal is undefined") - .episode_releases, - SonarrRelease - ); - fn extract_episode_id(&self) -> i64 { self .app @@ -76,9 +43,37 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle .sorting_block(ActiveSonarrBlock::ManualEpisodeSearchSortPrompt.into()) .sort_options(releases_sorting_options()); - if !self.handle_episode_history_table_events(episode_history_table_handling_config) - && !self.handle_episode_releases_table_events(episode_releases_table_handling_config) - { + if !handle_table( + self, + |app| { + &mut app + .data + .sonarr_data + .season_details_modal + .as_mut() + .expect("Season details modal is undefined") + .episode_details_modal + .as_mut() + .expect("Episode details modal is undefined") + .episode_history + }, + episode_history_table_handling_config, + ) && !handle_table( + self, + |app| { + &mut app + .data + .sonarr_data + .season_details_modal + .as_mut() + .expect("Season details modal is undefined") + .episode_details_modal + .as_mut() + .expect("Episode details modal is undefined") + .episode_releases + }, + episode_releases_table_handling_config, + ) { self.handle_key_event(); } } @@ -370,4 +365,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/library/mod.rs b/src/handlers/sonarr_handlers/library/mod.rs index 90b5c31..dc60579 100644 --- a/src/handlers/sonarr_handlers/library/mod.rs +++ b/src/handlers/sonarr_handlers/library/mod.rs @@ -6,7 +6,6 @@ use edit_series_handler::EditSeriesHandler; use crate::{ app::App, event::Key, - handle_table_events, handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle}, matches_key, models::{ @@ -25,7 +24,7 @@ use super::handle_change_tab_left_right_keys; use crate::handlers::sonarr_handlers::library::episode_details_handler::EpisodeDetailsHandler; use crate::handlers::sonarr_handlers::library::season_details_handler::SeasonDetailsHandler; use crate::handlers::sonarr_handlers::library::series_details_handler::SeriesDetailsHandler; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; mod add_series_handler; mod delete_series_handler; @@ -45,7 +44,6 @@ pub(super) struct LibraryHandler<'a, 'b> { } impl LibraryHandler<'_, '_> { - handle_table_events!(self, series, self.app.data.sonarr_data.series, Series); fn extract_series_id(&self) -> i64 { self.app.data.sonarr_data.series.current_selection().id } @@ -63,7 +61,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, ' .filter_error_block(ActiveSonarrBlock::FilterSeriesError.into()) .filter_field_fn(|series| &series.title.text); - if !self.handle_series_table_events(series_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.sonarr_data.series, + series_table_handling_config, + ) { match self.active_sonarr_block { _ if AddSeriesHandler::accepts(self.active_sonarr_block) => { AddSeriesHandler::new(self.key, self.app, self.active_sonarr_block, self.context) @@ -238,6 +240,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, ' _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } fn series_sorting_options() -> Vec> { diff --git a/src/handlers/sonarr_handlers/library/season_details_handler.rs b/src/handlers/sonarr_handlers/library/season_details_handler.rs index 4ae7fd0..84aeb53 100644 --- a/src/handlers/sonarr_handlers/library/season_details_handler.rs +++ b/src/handlers/sonarr_handlers/library/season_details_handler.rs @@ -1,8 +1,9 @@ use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::history::history_sorting_options; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::handlers::{KeyEventHandler, handle_prompt_toggle}; +use crate::matches_key; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SEASON_DETAILS_BLOCKS}; use crate::models::servarr_models::Language; use crate::models::sonarr_models::{ @@ -10,7 +11,6 @@ use crate::models::sonarr_models::{ }; use crate::models::stateful_table::SortOption; use crate::network::sonarr_network::SonarrEvent; -use crate::{handle_table_events, matches_key}; use serde_json::Number; #[cfg(test)] @@ -25,46 +25,6 @@ pub(super) struct SeasonDetailsHandler<'a, 'b> { } impl SeasonDetailsHandler<'_, '_> { - handle_table_events!( - self, - episodes, - self - .app - .data - .sonarr_data - .season_details_modal - .as_mut() - .expect("Season details modal is undefined") - .episodes, - Episode - ); - handle_table_events!( - self, - season_history, - self - .app - .data - .sonarr_data - .season_details_modal - .as_mut() - .expect("Season details modal is undefined") - .season_history, - SonarrHistoryItem - ); - handle_table_events!( - self, - season_releases, - self - .app - .data - .sonarr_data - .season_details_modal - .as_mut() - .expect("Season details modal is undefined") - .season_releases, - SonarrRelease - ); - fn extract_episode_file_id(&self) -> i64 { self .app @@ -126,10 +86,43 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler .sorting_block(ActiveSonarrBlock::ManualSeasonSearchSortPrompt.into()) .sort_options(releases_sorting_options()); - if !self.handle_episodes_table_events(episodes_table_handling_config) - && !self.handle_season_history_table_events(season_history_table_handling_config) - && !self.handle_season_releases_table_events(season_releases_table_handling_config) - { + if !handle_table( + self, + |app| { + &mut app + .data + .sonarr_data + .season_details_modal + .as_mut() + .expect("Season details modal is undefined") + .episodes + }, + episodes_table_handling_config, + ) && !handle_table( + self, + |app| { + &mut app + .data + .sonarr_data + .season_details_modal + .as_mut() + .expect("Season details modal is undefined") + .season_history + }, + season_history_table_handling_config, + ) && !handle_table( + self, + |app| { + &mut app + .data + .sonarr_data + .season_details_modal + .as_mut() + .expect("Season details modal is undefined") + .season_releases + }, + season_releases_table_handling_config, + ) { self.handle_key_event(); } } @@ -460,6 +453,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } pub(in crate::handlers::sonarr_handlers::library) fn releases_sorting_options() diff --git a/src/handlers/sonarr_handlers/library/series_details_handler.rs b/src/handlers/sonarr_handlers/library/series_details_handler.rs index 0b6771b..ee60e3e 100644 --- a/src/handlers/sonarr_handlers/library/series_details_handler.rs +++ b/src/handlers/sonarr_handlers/library/series_details_handler.rs @@ -1,15 +1,15 @@ use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::history::history_sorting_options; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::handlers::{KeyEventHandler, handle_prompt_toggle}; +use crate::matches_key; use crate::models::BlockSelectionState; use crate::models::servarr_data::sonarr::sonarr_data::{ ActiveSonarrBlock, EDIT_SERIES_SELECTION_BLOCKS, SERIES_DETAILS_BLOCKS, }; use crate::models::sonarr_models::{Season, SonarrHistoryItem}; use crate::network::sonarr_network::SonarrEvent; -use crate::{handle_table_events, matches_key}; #[cfg(test)] #[path = "series_details_handler_tests.rs"] @@ -23,20 +23,6 @@ pub(super) struct SeriesDetailsHandler<'a, 'b> { } impl SeriesDetailsHandler<'_, '_> { - handle_table_events!(self, season, self.app.data.sonarr_data.seasons, Season); - handle_table_events!( - self, - series_history, - self - .app - .data - .sonarr_data - .series_history - .as_mut() - .expect("Series history is undefined"), - SonarrHistoryItem - ); - fn extract_series_id_season_number_tuple(&self) -> (i64, i64) { let series_id = self.app.data.sonarr_data.series.current_selection().id; let season_number = self @@ -78,9 +64,22 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler .filter_error_block(ActiveSonarrBlock::FilterSeriesHistoryError.into()) .filter_field_fn(|history_item: &SonarrHistoryItem| &history_item.source_title.text); - if !self.handle_season_table_events(season_table_handling_config) - && !self.handle_series_history_table_events(series_history_table_handling_config) - { + if !handle_table( + self, + |app| &mut app.data.sonarr_data.seasons, + season_table_handling_config, + ) && !handle_table( + self, + |app| { + app + .data + .sonarr_data + .series_history + .as_mut() + .expect("Series history is undefined") + }, + series_history_table_handling_config, + ) { self.handle_key_event(); } } @@ -338,4 +337,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/mod.rs b/src/handlers/sonarr_handlers/mod.rs index 2a05c69..033392e 100644 --- a/src/handlers/sonarr_handlers/mod.rs +++ b/src/handlers/sonarr_handlers/mod.rs @@ -110,6 +110,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b fn handle_esc(&mut self) {} fn handle_char_key_event(&mut self) {} + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) { diff --git a/src/handlers/sonarr_handlers/root_folders/mod.rs b/src/handlers/sonarr_handlers/root_folders/mod.rs index e98af49..04da09c 100644 --- a/src/handlers/sonarr_handlers/root_folders/mod.rs +++ b/src/handlers/sonarr_handlers/root_folders/mod.rs @@ -1,15 +1,13 @@ use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; -use crate::handlers::table_handler::TableHandlingConfig; +use crate::handlers::table_handler::{TableHandlingConfig, handle_table}; use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle}; use crate::models::HorizontallyScrollableText; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ROOT_FOLDERS_BLOCKS}; -use crate::models::servarr_models::{AddRootFolderBody, RootFolder}; +use crate::models::servarr_models::AddRootFolderBody; use crate::network::sonarr_network::SonarrEvent; -use crate::{ - handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key, -}; +use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key}; #[cfg(test)] #[path = "root_folders_handler_tests.rs"] @@ -23,13 +21,6 @@ pub(super) struct RootFoldersHandler<'a, 'b> { } impl RootFoldersHandler<'_, '_> { - handle_table_events!( - self, - root_folders, - self.app.data.sonarr_data.root_folders, - RootFolder - ); - fn build_add_root_folder_body(&mut self) -> AddRootFolderBody { let root_folder = self .app @@ -58,7 +49,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<' let root_folders_table_handling_config = TableHandlingConfig::new(ActiveSonarrBlock::RootFolders.into()); - if !self.handle_root_folders_table_events(root_folders_table_handling_config) { + if !handle_table( + self, + |app| &mut app.data.sonarr_data.root_folders, + root_folders_table_handling_config, + ) { self.handle_key_event(); } } @@ -229,4 +224,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<' _ => (), } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/system/mod.rs b/src/handlers/sonarr_handlers/system/mod.rs index ad1bd7a..9c70ac4 100644 --- a/src/handlers/sonarr_handlers/system/mod.rs +++ b/src/handlers/sonarr_handlers/system/mod.rs @@ -124,4 +124,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b } } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/sonarr_handlers/system/system_details_handler.rs b/src/handlers/sonarr_handlers/system/system_details_handler.rs index 9352088..57879e2 100644 --- a/src/handlers/sonarr_handlers/system/system_details_handler.rs +++ b/src/handlers/sonarr_handlers/system/system_details_handler.rs @@ -196,4 +196,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler self.app.pop_navigation_stack(); } } + + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } diff --git a/src/handlers/table_handler.rs b/src/handlers/table_handler.rs index c46320e..028d6fb 100644 --- a/src/handlers/table_handler.rs +++ b/src/handlers/table_handler.rs @@ -1,7 +1,12 @@ -use crate::models::Route; -use crate::models::stateful_table::SortOption; +use crate::app::App; +use crate::app::key_binding::DEFAULT_KEYBINDINGS; +use crate::event::Key; +use crate::matches_key; +use crate::models::stateful_table::{SortOption, StatefulTable}; +use crate::models::{HorizontallyScrollableText, Paginated, Route, Scrollable}; use derive_setters::Setters; use std::fmt::Debug; +use std::marker::PhantomData; #[cfg(test)] #[path = "table_handler_tests.rs"] @@ -32,370 +37,6 @@ where pub table_block: Route, } -#[macro_export] -macro_rules! handle_table_events { - ($self:expr, $name:ty, $table:expr, $row:ident) => { - paste::paste! { - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - if $self.is_ready() { - match $self.key { - _ if $crate::matches_key!(up, $self.key, $self.ignore_special_keys()) => $self.[](config), - _ if $crate::matches_key!(down, $self.key, $self.ignore_special_keys()) => $self.[](config), - _ if $crate::matches_key!(pg_up, $self.key, $self.ignore_special_keys()) => $self.[](config), - _ if $crate::matches_key!(pg_down, $self.key, $self.ignore_special_keys()) => $self.[](config), - _ if $crate::matches_key!(home, $self.key) => $self.[](config), - _ if $crate::matches_key!(end, $self.key) => $self.[](config), - _ if $crate::matches_key!(left, $self.key, $self.ignore_special_keys()) - || $crate::matches_key!(right, $self.key, $self.ignore_special_keys()) => - { - $self.[](config) - } - _ if $crate::matches_key!(submit, $self.key) => $self.[](config), - _ if $crate::matches_key!(esc, $self.key) => $self.[](config), - _ if config.searching_block.is_some() - && $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() => - { - $self.[]() - } - _ if config.filtering_block.is_some() - && $self.app.get_current_route() == *config.filtering_block.as_ref().unwrap() => - { - $self.[]() - } - _ if $crate::matches_key!(filter, $self.key) - && config.filtering_block.is_some() => $self.[](config), - _ if $crate::matches_key!(search, $self.key) - && config.searching_block.is_some() => $self.[](config), - _ if $crate::matches_key!(sort, $self.key) - && config.sorting_block.is_some() => $self.[](config), - _ => false, - } - } else { - false - } - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - use $crate::models::Scrollable; - - match $self.app.get_current_route() { - _ if config.table_block == $self.app.get_current_route() => { - $table.scroll_up(); - true - } - _ if config.sorting_block.is_some() - && $self.app.get_current_route() == *config.sorting_block.as_ref().unwrap() => - { - if let Some(ref mut sort) = $table.sort { - sort.scroll_up(); - } - true - } - _ => false, - } - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - use $crate::models::Scrollable; - - match $self.app.get_current_route() { - _ if config.table_block == $self.app.get_current_route() => { - $table.scroll_down(); - true - } - _ if config.sorting_block.is_some() - && $self.app.get_current_route() == *config.sorting_block.as_ref().unwrap() => - { - if let Some(ref mut sort) = $table.sort { - sort - .scroll_down(); - } - true - } - _ => false, - } - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - use $crate::models::Paginated; - - if config.table_block == $self.app.get_current_route() { - $table.page_up(); - true - } else { - false - } - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - use $crate::models::Paginated; - - if config.table_block == $self.app.get_current_route() { - $table.page_down(); - true - } else { - false - } - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - use $crate::models::Scrollable; - - match $self.app.get_current_route() { - _ if config.table_block == $self.app.get_current_route() => { - $table.scroll_to_top(); - true - } - _ if config.sorting_block.is_some() - && $self.app.get_current_route() == *config.sorting_block.as_ref().unwrap() => - { - if let Some(ref mut sort) = $table.sort { - sort.scroll_to_top(); - } - true - } - _ if config.searching_block.is_some() - && $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() => - { - if let Some(ref mut search) = $table.search { - search.scroll_home(); - } - true - } - _ if config.filtering_block.is_some() - && $self.app.get_current_route() == *config.filtering_block.as_ref().unwrap() => - { - if let Some(ref mut filter) = $table.filter { - filter.scroll_home(); - } - true - } - _ => false, - } - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - use $crate::models::Scrollable; - - match $self.app.get_current_route() { - _ if config.table_block == $self.app.get_current_route() => { - $table.scroll_to_bottom(); - true - } - _ if config.sorting_block.is_some() - && $self.app.get_current_route() == *config.sorting_block.as_ref().unwrap() => - { - if let Some(ref mut sort) = $table.sort { - sort.scroll_to_bottom(); - } - true - } - _ if config.searching_block.is_some() - && $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() => - { - if let Some(ref mut search) = $table.search { - search.reset_offset(); - } - true - } - _ if config.filtering_block.is_some() - && $self.app.get_current_route() == *config.filtering_block.as_ref().unwrap() => - { - if let Some(ref mut filter) = $table.filter { - filter.reset_offset(); - } - true - } - _ => false, - } - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - match $self.app.get_current_route() { - _ if config.searching_block.is_some() - && $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() => - { - if let Some(ref mut search) = $table.search { - $crate::handle_text_box_left_right_keys!($self, $self.key, search); - } - true - } - _ if config.filtering_block.is_some() - && $self.app.get_current_route() == *config.filtering_block.as_ref().unwrap() => - { - if let Some(ref mut filter) = $table.filter { - $crate::handle_text_box_left_right_keys!($self, $self.key, filter); - } - true - } - _ => false, - } - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - match $self.app.get_current_route() { - _ if config.sorting_block.is_some() - && $self.app.get_current_route() == *config.sorting_block.as_ref().unwrap() => - { - $table.apply_sorting(); - $self.app.pop_navigation_stack(); - true - } - _ if config.searching_block.is_some() - && $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() => - { - $self.app.pop_navigation_stack(); - $self.app.ignore_special_keys_for_textbox_input = false; - - if $table.search.is_some() { - let search_field_fn = config - .search_field_fn - .expect("Search field function is required"); - let has_match = $table.apply_search(search_field_fn); - - if !has_match { - $self.app.push_navigation_stack( - config - .search_error_block - .expect("Search error block is undefined"), - ); - } - } - - true - } - _ if config.filtering_block.is_some() - && $self.app.get_current_route() == *config.filtering_block.as_ref().unwrap() => - { - $self.app.pop_navigation_stack(); - $self.app.ignore_special_keys_for_textbox_input = false; - - if $table.filter.is_some() { - let filter_field_fn = config - .filter_field_fn - .expect("Search field function is required"); - let has_match = $table.apply_filter(filter_field_fn); - - if !has_match { - $self.app.push_navigation_stack( - config - .filter_error_block - .expect("Search error block is undefined"), - ); - } - } - - true - } - _ => false, - } - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - match $self.app.get_current_route() { - _ if config.sorting_block.is_some() - && $self.app.get_current_route() == *config.sorting_block.as_ref().unwrap() => - { - $self.app.pop_navigation_stack(); - true - } - _ if (config.searching_block.is_some() - && $self.app.get_current_route() == *config.searching_block.as_ref().unwrap()) - || (config.search_error_block.is_some() - && $self.app.get_current_route() == *config.search_error_block.as_ref().unwrap()) => - { - $self.app.pop_navigation_stack(); - $table.reset_search(); - $self.app.ignore_special_keys_for_textbox_input = false; - true - } - _ if (config.filtering_block.is_some() - && $self.app.get_current_route() == *config.filtering_block.as_ref().unwrap()) - || (config.filter_error_block.is_some() - && $self.app.get_current_route() == *config.filter_error_block.as_ref().unwrap()) => - { - $self.app.pop_navigation_stack(); - $table.reset_filter(); - $self.app.ignore_special_keys_for_textbox_input = false; - true - } - _ if config.table_block == $self.app.get_current_route() - && $table.filtered_items.is_some() => - { - $table.reset_filter(); - true - } - _ => false, - } - } - - fn [](&mut $self) -> bool { - let Some(ref mut search) = $table.search else { - return false; - }; - - $crate::handle_text_box_keys!($self, $self.key, search); - true - } - - fn [](&mut $self) -> bool { - let Some(ref mut filter) = $table.filter else { - return false; - }; - - $crate::handle_text_box_keys!($self, $self.key, filter); - true - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - if $self.app.get_current_route() != config.table_block { - return false; - } - - let Some(ref filtering_block) = config.filtering_block else { - return false; - }; - - let filter = $crate::models::HorizontallyScrollableText::default(); - $table.filter = Some(filter); - $self.app.push_navigation_stack(*filtering_block); - $self.app.ignore_special_keys_for_textbox_input = true; - true - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - if $self.app.get_current_route() != config.table_block { - return false; - } - - let Some(ref searching_block) = config.searching_block else { - return false; - }; - - let search = $crate::models::HorizontallyScrollableText::default(); - $table.search = Some(search); - $self.app.push_navigation_stack(*searching_block); - $self.app.ignore_special_keys_for_textbox_input = true; - true - } - - fn [](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { - if $self.app.get_current_route() != config.table_block { - return false; - } - - let (Some(ref sorting_block), Some(sort_options)) = (config.sorting_block, config.sort_options.as_ref()) else { - return false; - }; - - $table.sorting(sort_options.clone()); - $self.app.push_navigation_stack(*sorting_block); - true - } - } - }; -} - impl TableHandlingConfig where T: Clone + PartialEq + Eq + Debug + Default, @@ -414,3 +55,631 @@ where } } } + +pub trait TableEventHandler<'b, T> +where + T: Clone + PartialEq + Eq + Debug + Default, +{ + /// Returns a mutable reference to the table being managed. + fn table_mut(&mut self) -> &mut StatefulTable; + + /// Returns the configuration for this table's event handling. + fn config(&self) -> &TableHandlingConfig; + + /// Returns whether the handler is ready to process events. + /// + /// Typically, checks if data is loaded and the table is not empty. + fn is_ready(&self) -> bool; + + /// Returns the current navigation route. + fn current_route(&self) -> Route; + + /// Returns whether special keys should be ignored for textbox input. + fn ignore_special_keys(&self) -> bool; + + /// Returns a mutable reference to the application state. + fn app_mut(&mut self) -> &mut App<'b>; + + /// Returns the current key event being processed. + fn key(&self) -> Key; + + /// Main entry point for table event handling. + /// + /// Returns `true` if the event was handled by table logic, `false` otherwise. + /// When `false` is returned, the caller should delegate to other handlers. + fn handle_table_events(&mut self) -> bool { + if !self.is_ready() { + return false; + } + + let key = self.key(); + let config = self.config(); + let current_route = self.current_route(); + let ignore_special = self.ignore_special_keys(); + + match key { + _ if matches_key!(up, key, ignore_special) => self.handle_scroll_up(), + _ if matches_key!(down, key, ignore_special) => self.handle_scroll_down(), + _ if matches_key!(pg_up, key, ignore_special) => self.handle_page_up(), + _ if matches_key!(pg_down, key, ignore_special) => self.handle_page_down(), + _ if matches_key!(home, key) => self.handle_home(), + _ if matches_key!(end, key) => self.handle_end(), + _ if matches_key!(left, key, ignore_special) || matches_key!(right, key, ignore_special) => { + self.handle_left_right() + } + _ if matches_key!(submit, key) => self.handle_submit(), + _ if matches_key!(esc, key) => self.handle_esc(), + _ if config.searching_block.is_some() + && current_route == *config.searching_block.as_ref().unwrap() => + { + self.handle_search_box_input() + } + _ if config.filtering_block.is_some() + && current_route == *config.filtering_block.as_ref().unwrap() => + { + self.handle_filter_box_input() + } + _ if matches_key!(filter, key) && config.filtering_block.is_some() => { + self.handle_filter_key() + } + _ if matches_key!(search, key) && config.searching_block.is_some() => { + self.handle_search_key() + } + _ if matches_key!(sort, key) && config.sorting_block.is_some() => self.handle_sort_key(), + _ => false, + } + } + + fn handle_scroll_up(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + + match current_route { + _ if config.table_block == current_route => { + self.table_mut().scroll_up(); + true + } + _ if config.sorting_block.is_some() + && current_route == *config.sorting_block.as_ref().unwrap() => + { + if let Some(ref mut sort) = self.table_mut().sort { + sort.scroll_up(); + } + true + } + _ => false, + } + } + + fn handle_scroll_down(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + + match current_route { + _ if config.table_block == current_route => { + self.table_mut().scroll_down(); + true + } + _ if config.sorting_block.is_some() + && current_route == *config.sorting_block.as_ref().unwrap() => + { + if let Some(ref mut sort) = self.table_mut().sort { + sort.scroll_down(); + } + true + } + _ => false, + } + } + + fn handle_page_up(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + + if config.table_block == current_route { + self.table_mut().page_up(); + true + } else { + false + } + } + + fn handle_page_down(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + + if config.table_block == current_route { + self.table_mut().page_down(); + true + } else { + false + } + } + + fn handle_home(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + + match current_route { + _ if config.table_block == current_route => { + self.table_mut().scroll_to_top(); + true + } + _ if config.sorting_block.is_some() + && current_route == *config.sorting_block.as_ref().unwrap() => + { + if let Some(ref mut sort) = self.table_mut().sort { + sort.scroll_to_top(); + } + true + } + _ if config.searching_block.is_some() + && current_route == *config.searching_block.as_ref().unwrap() => + { + if let Some(ref mut search) = self.table_mut().search { + search.scroll_home(); + } + true + } + _ if config.filtering_block.is_some() + && current_route == *config.filtering_block.as_ref().unwrap() => + { + if let Some(ref mut filter) = self.table_mut().filter { + filter.scroll_home(); + } + true + } + _ => false, + } + } + + fn handle_end(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + + match current_route { + _ if config.table_block == current_route => { + self.table_mut().scroll_to_bottom(); + true + } + _ if config.sorting_block.is_some() + && current_route == *config.sorting_block.as_ref().unwrap() => + { + if let Some(ref mut sort) = self.table_mut().sort { + sort.scroll_to_bottom(); + } + true + } + _ if config.searching_block.is_some() + && current_route == *config.searching_block.as_ref().unwrap() => + { + if let Some(ref mut search) = self.table_mut().search { + search.reset_offset(); + } + true + } + _ if config.filtering_block.is_some() + && current_route == *config.filtering_block.as_ref().unwrap() => + { + if let Some(ref mut filter) = self.table_mut().filter { + filter.reset_offset(); + } + true + } + _ => false, + } + } + + fn handle_left_right(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + let key = self.key(); + + match current_route { + _ if config.searching_block.is_some() + && current_route == *config.searching_block.as_ref().unwrap() => + { + if let Some(ref mut search) = self.table_mut().search { + if key == DEFAULT_KEYBINDINGS.left.key { + search.scroll_left(); + } else if key == DEFAULT_KEYBINDINGS.right.key { + search.scroll_right(); + } + } + true + } + _ if config.filtering_block.is_some() + && current_route == *config.filtering_block.as_ref().unwrap() => + { + if let Some(ref mut filter) = self.table_mut().filter { + if key == DEFAULT_KEYBINDINGS.left.key { + filter.scroll_left(); + } else if key == DEFAULT_KEYBINDINGS.right.key { + filter.scroll_right(); + } + } + true + } + _ => false, + } + } + + fn handle_submit(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + + let sorting_block = config.sorting_block; + let searching_block = config.searching_block; + let search_field_fn = config.search_field_fn; + let search_error_block = config.search_error_block; + let filtering_block = config.filtering_block; + let filter_field_fn = config.filter_field_fn; + let filter_error_block = config.filter_error_block; + + match current_route { + _ if sorting_block.is_some() && current_route == *sorting_block.as_ref().unwrap() => { + self.table_mut().apply_sorting(); + self.app_mut().pop_navigation_stack(); + true + } + _ if searching_block.is_some() && current_route == *searching_block.as_ref().unwrap() => { + let app = self.app_mut(); + app.pop_navigation_stack(); + app.ignore_special_keys_for_textbox_input = false; + + if self.table_mut().search.is_some() { + let search_fn = search_field_fn.expect("Search field function is required"); + let has_match = self.table_mut().apply_search(search_fn); + + if !has_match { + self + .app_mut() + .push_navigation_stack(search_error_block.expect("Search error block is undefined")); + } + } + + true + } + _ if filtering_block.is_some() && current_route == *filtering_block.as_ref().unwrap() => { + let app = self.app_mut(); + app.pop_navigation_stack(); + app.ignore_special_keys_for_textbox_input = false; + + if self.table_mut().filter.is_some() { + let filter_fn = filter_field_fn.expect("Filter field function is required"); + let has_match = self.table_mut().apply_filter(filter_fn); + + if !has_match { + self + .app_mut() + .push_navigation_stack(filter_error_block.expect("Filter error block is undefined")); + } + } + + true + } + _ => false, + } + } + + fn handle_esc(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + + let sorting_block = config.sorting_block; + let searching_block = config.searching_block; + let search_error_block = config.search_error_block; + let filtering_block = config.filtering_block; + let filter_error_block = config.filter_error_block; + let table_block = config.table_block; + + match current_route { + _ if sorting_block.is_some() && current_route == *sorting_block.as_ref().unwrap() => { + self.app_mut().pop_navigation_stack(); + true + } + _ if (searching_block.is_some() && current_route == *searching_block.as_ref().unwrap()) + || (search_error_block.is_some() + && current_route == *search_error_block.as_ref().unwrap()) => + { + self.app_mut().pop_navigation_stack(); + self.table_mut().reset_search(); + self.app_mut().ignore_special_keys_for_textbox_input = false; + true + } + _ if (filtering_block.is_some() && current_route == *filtering_block.as_ref().unwrap()) + || (filter_error_block.is_some() + && current_route == *filter_error_block.as_ref().unwrap()) => + { + self.app_mut().pop_navigation_stack(); + self.table_mut().reset_filter(); + self.app_mut().ignore_special_keys_for_textbox_input = false; + true + } + _ if table_block == current_route && self.table_mut().filtered_items.is_some() => { + self.table_mut().reset_filter(); + true + } + _ => false, + } + } + + fn handle_search_box_input(&mut self) -> bool { + let key = self.key(); + let Some(ref mut search) = self.table_mut().search else { + return false; + }; + + match key { + _ if matches_key!(backspace, key) => { + search.pop(); + } + Key::Char(character) => { + search.push(character); + } + _ => (), + } + true + } + + fn handle_filter_box_input(&mut self) -> bool { + let key = self.key(); + let Some(ref mut filter) = self.table_mut().filter else { + return false; + }; + + match key { + _ if matches_key!(backspace, key) => { + filter.pop(); + } + Key::Char(character) => { + filter.push(character); + } + _ => (), + } + true + } + + fn handle_filter_key(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + + if current_route != config.table_block { + return false; + } + + let Some(filtering_block) = config.filtering_block else { + return false; + }; + + let filter = HorizontallyScrollableText::default(); + self.table_mut().filter = Some(filter); + let app = self.app_mut(); + app.push_navigation_stack(filtering_block); + app.ignore_special_keys_for_textbox_input = true; + true + } + + fn handle_search_key(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + + if current_route != config.table_block { + return false; + } + + let Some(searching_block) = config.searching_block else { + return false; + }; + + let search = HorizontallyScrollableText::default(); + self.table_mut().search = Some(search); + let app = self.app_mut(); + app.push_navigation_stack(searching_block); + app.ignore_special_keys_for_textbox_input = true; + true + } + + fn handle_sort_key(&mut self) -> bool { + let config = self.config(); + let current_route = self.current_route(); + + if current_route != config.table_block { + return false; + } + + let (Some(sorting_block), Some(sort_options)) = + (config.sorting_block, config.sort_options.as_ref()) + else { + return false; + }; + + let sort_options = sort_options.clone(); + self.table_mut().sorting(sort_options); + self.app_mut().push_navigation_stack(sorting_block); + true + } +} + +/// Adapter struct that implements `TableEventHandler` for any `KeyEventHandler`. +/// +/// This struct enables table handling for existing handlers via composition rather than +/// inheritance. It wraps a handler reference and uses a closure to access the table, +/// allowing flexible access patterns (direct, optional, nested). +/// +/// # Type Parameters +/// +/// - `'a`, `'b`: Lifetimes from the handler +/// - `'handler`: Lifetime of the handler reference +/// - `T`: The table row type +/// - `H`: The handler type that implements `KeyEventHandler` +/// - `R`: The route type that converts into `Route` +/// +/// # Usage +/// +/// This struct is typically created by the `handle_table` helper function and should +/// not be constructed directly: +/// +/// ```rust,ignore +/// if !handle_table(self, |h| &mut h.app.data.my_table, config) { +/// self.handle_key_event(); +/// } +/// ``` +pub struct TableHandlerAdapter<'handler, 'a, 'b, T, H, R, F> +where + T: Clone + PartialEq + Eq + Debug + Default, + H: crate::handlers::KeyEventHandler<'a, 'b, R>, + R: Into + Copy, + F: for<'c> FnMut(&'c mut App<'b>) -> &'c mut StatefulTable, +{ + handler: &'handler mut H, + table_accessor: F, + config: TableHandlingConfig, + _phantom: PhantomData<(&'a (), &'b (), R)>, +} + +impl<'handler, 'a, 'b, T, H, R, F> TableHandlerAdapter<'handler, 'a, 'b, T, H, R, F> +where + T: Clone + PartialEq + Eq + Debug + Default, + H: crate::handlers::KeyEventHandler<'a, 'b, R>, + R: Into + Copy, + F: for<'c> FnMut(&'c mut App<'b>) -> &'c mut StatefulTable, +{ + fn new(handler: &'handler mut H, table_accessor: F, config: TableHandlingConfig) -> Self { + Self { + handler, + table_accessor, + config, + _phantom: PhantomData, + } + } +} + +impl<'handler, 'a, 'b, T, H, R, F> TableEventHandler<'b, T> + for TableHandlerAdapter<'handler, 'a, 'b, T, H, R, F> +where + T: Clone + PartialEq + Eq + Debug + Default, + H: crate::handlers::KeyEventHandler<'a, 'b, R>, + R: Into + Copy, + F: for<'c> FnMut(&'c mut App<'b>) -> &'c mut StatefulTable, +{ + fn table_mut(&mut self) -> &mut StatefulTable { + (self.table_accessor)(self.handler.app_mut()) + } + + fn config(&self) -> &TableHandlingConfig { + &self.config + } + + fn is_ready(&self) -> bool { + self.handler.is_ready() + } + + fn current_route(&self) -> Route { + self.handler.current_route() + } + + fn ignore_special_keys(&self) -> bool { + self.handler.ignore_special_keys() + } + + fn app_mut(&mut self) -> &mut App<'b> { + self.handler.app_mut() + } + + fn key(&self) -> Key { + self.handler.get_key() + } +} + +/// Helper function for ergonomic table event handling. +/// +/// This is the primary entry point for using trait-based table handling. It creates +/// a `TableHandlerAdapter`, calls `handle_table_events()`, and returns the result. +/// +/// # Parameters +/// +/// - `handler`: Mutable reference to a handler implementing `KeyEventHandler` +/// - `table_accessor`: Closure that extracts the table from the handler +/// - `config`: Table handling configuration +/// +/// # Returns +/// +/// `true` if the event was handled by table logic, `false` otherwise. +/// When `false` is returned, the caller should delegate to other event handlers. +/// +/// # Examples +/// +/// ## Single Table Handler +/// +/// ```rust,ignore +/// use crate::handlers::table_handler::{handle_table, TableHandlingConfig}; +/// +/// impl KeyEventHandler for LibraryHandler { +/// fn handle(&mut self) { +/// let config = TableHandlingConfig::new(ActiveBlock::Movies.into()) +/// .sorting_block(ActiveBlock::MoviesSortPrompt.into()) +/// .sort_options(movies_sorting_options()) +/// .searching_block(ActiveBlock::SearchMovie.into()) +/// .search_field_fn(|movie| &movie.title.text) +/// .filtering_block(ActiveBlock::FilterMovies.into()) +/// .filter_field_fn(|movie| &movie.title.text); +/// +/// if !handle_table(self, |h| &mut h.app.data.radarr_data.movies, config) { +/// // Event not handled by table, delegate to other handlers +/// match self.active_block { +/// _ if SubHandler::accepts(self.active_block) => { +/// SubHandler::new(self.key, self.app, self.active_block, self.context).handle(); +/// } +/// _ => self.handle_key_event(), +/// } +/// } +/// } +/// } +/// ``` +/// +/// ## Multiple Tables Handler +/// +/// ```rust,ignore +/// fn handle(&mut self) { +/// let releases_config = TableHandlingConfig::new(ActiveBlock::Releases.into()); +/// let history_config = TableHandlingConfig::new(ActiveBlock::History.into()); +/// +/// // Short-circuit evaluation: try each table in sequence +/// if !handle_table(self, |h| &mut h.app.data.movie_releases, releases_config) +/// && !handle_table(self, |h| &mut h.app.data.movie_history, history_config) +/// { +/// self.handle_key_event(); +/// } +/// } +/// ``` +/// +/// ## Optional Table Access +/// +/// ```rust,ignore +/// fn handle(&mut self) { +/// let config = TableHandlingConfig::new(ActiveBlock::SearchResults.into()); +/// +/// if !handle_table( +/// self, +/// |h| h.app.data.add_searched_movies.as_mut().expect("modal should be initialized"), +/// config, +/// ) { +/// self.handle_key_event(); +/// } +/// } +/// ``` +pub fn handle_table<'a, 'b, T, H, R, F>( + handler: &mut H, + table_accessor: F, + config: TableHandlingConfig, +) -> bool +where + T: Clone + PartialEq + Eq + Debug + Default, + H: crate::handlers::KeyEventHandler<'a, 'b, R>, + R: Into + Copy, + F: for<'c> FnMut(&'c mut App<'b>) -> &'c mut StatefulTable, +{ + let mut adapter = TableHandlerAdapter::new(handler, table_accessor, config); + adapter.handle_table_events() +} diff --git a/src/handlers/table_handler_tests.rs b/src/handlers/table_handler_tests.rs index e419dcd..7e55eaa 100644 --- a/src/handlers/table_handler_tests.rs +++ b/src/handlers/table_handler_tests.rs @@ -3,9 +3,9 @@ mod tests { use crate::app::App; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::event::Key; - use crate::handle_table_events; use crate::handlers::KeyEventHandler; use crate::handlers::table_handler::TableHandlingConfig; + use crate::handlers::table_handler::handle_table; use crate::models::radarr_models::Movie; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_models::Language; @@ -33,13 +33,13 @@ mod tests { let minimal_movie_table_handling_config = TableHandlingConfig::new(ActiveRadarrBlock::Movies.into()); - match self.active_radarr_block { - ActiveRadarrBlock::MovieDetails => { - self.handle_movies_table_events(minimal_movie_table_handling_config); - } - _ => { - self.handle_movies_table_events(movie_table_handling_config); - } + let config = match self.active_radarr_block { + ActiveRadarrBlock::MovieDetails => minimal_movie_table_handling_config, + _ => movie_table_handling_config, + }; + + if !handle_table(self, |app| &mut app.data.radarr_data.movies, config) { + self.handle_key_event(); } } @@ -90,10 +90,14 @@ mod tests { fn handle_esc(&mut self) {} fn handle_char_key_event(&mut self) {} - } - impl TableHandlerUnit<'_, '_> { - handle_table_events!(self, movies, self.app.data.radarr_data.movies, Movie); + fn app_mut(&mut self) -> &mut App<'b> { + self.app + } + + fn current_route(&self) -> crate::models::Route { + self.app.get_current_route() + } } mod test_handle_scroll_up_and_down { @@ -198,6 +202,86 @@ mod tests { } } } + + #[test] + fn test_table_sort_scroll_up_with_sort_some() { + let movie_field_vec = sort_options(); + let mut app = App::test_default(); + app.data.radarr_data.movies.sorting(sort_options()); + app.push_navigation_stack(ActiveRadarrBlock::MoviesSortPrompt.into()); + + assert_eq!( + app + .data + .radarr_data + .movies + .sort + .as_ref() + .unwrap() + .current_selection(), + &movie_field_vec[0] + ); + + TableHandlerUnit::new( + DEFAULT_KEYBINDINGS.up.key, + &mut app, + ActiveRadarrBlock::MoviesSortPrompt, + None, + ) + .handle(); + + assert_eq!( + app + .data + .radarr_data + .movies + .sort + .as_ref() + .unwrap() + .current_selection(), + &movie_field_vec[movie_field_vec.len() - 1] + ); + } + + #[test] + fn test_table_sort_scroll_down_with_sort_some() { + let movie_field_vec = sort_options(); + let mut app = App::test_default(); + app.data.radarr_data.movies.sorting(sort_options()); + app.push_navigation_stack(ActiveRadarrBlock::MoviesSortPrompt.into()); + + assert_eq!( + app + .data + .radarr_data + .movies + .sort + .as_ref() + .unwrap() + .current_selection(), + &movie_field_vec[0] + ); + + TableHandlerUnit::new( + DEFAULT_KEYBINDINGS.down.key, + &mut app, + ActiveRadarrBlock::MoviesSortPrompt, + None, + ) + .handle(); + + assert_eq!( + app + .data + .radarr_data + .movies + .sort + .as_ref() + .unwrap() + .current_selection(), + &movie_field_vec[0] + ); + } } mod test_handle_home_end { @@ -426,6 +510,70 @@ mod tests { &movie_field_vec[0] ); } + + #[test] + fn test_table_sort_home_with_sort_some() { + let movie_field_vec = sort_options(); + let mut app = App::test_default(); + app.data.radarr_data.movies.sorting(sort_options()); + app.push_navigation_stack(ActiveRadarrBlock::MoviesSortPrompt.into()); + + TableHandlerUnit::new( + DEFAULT_KEYBINDINGS.down.key, + &mut app, + ActiveRadarrBlock::MoviesSortPrompt, + None, + ) + .handle(); + + TableHandlerUnit::new( + DEFAULT_KEYBINDINGS.home.key, + &mut app, + ActiveRadarrBlock::MoviesSortPrompt, + None, + ) + .handle(); + + assert_eq!( + app + .data + .radarr_data + .movies + .sort + .as_ref() + .unwrap() + .current_selection(), + &movie_field_vec[0] + ); + } + + #[test] + fn test_table_sort_end_with_sort_some() { + let movie_field_vec = sort_options(); + let mut app = App::test_default(); + app.data.radarr_data.movies.sorting(sort_options()); + app.push_navigation_stack(ActiveRadarrBlock::MoviesSortPrompt.into()); + + TableHandlerUnit::new( + DEFAULT_KEYBINDINGS.end.key, + &mut app, + ActiveRadarrBlock::MoviesSortPrompt, + None, + ) + .handle(); + + assert_eq!( + app + .data + .radarr_data + .movies + .sort + .as_ref() + .unwrap() + .current_selection(), + &movie_field_vec[movie_field_vec.len() - 1] + ); + } } mod test_handle_pagination_scroll { @@ -835,6 +983,30 @@ mod tests { assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); } + + #[test] + fn test_table_block_esc_with_filter_applied() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.data.radarr_data.movies = StatefulTable { + filter: Some("Test".into()), + filtered_items: Some(Vec::new()), + filtered_state: Some(TableState::default()), + ..StatefulTable::default() + }; + app + .data + .radarr_data + .movies + .set_items(vec![Movie::default()]); + + TableHandlerUnit::new(ESC_KEY, &mut app, ActiveRadarrBlock::Movies, None).handle(); + + assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); + 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); + } } mod test_handle_key_char {