Merge branch 'develop' into fix-key-event-handling

This commit is contained in:
Alex Clarke
2025-04-07 11:58:06 -06:00
committed by GitHub
152 changed files with 3309 additions and 666 deletions
Generated
+10
View File
@@ -1391,6 +1391,7 @@ dependencies = [
"tokio",
"tokio-util",
"urlencoding",
"validate_theme_derive",
"veil",
]
@@ -2780,6 +2781,15 @@ dependencies = [
"getrandom 0.3.1",
]
[[package]]
name = "validate_theme_derive"
version = "0.1.0"
dependencies = [
"log",
"quote",
"syn 2.0.99",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
+2 -1
View File
@@ -14,7 +14,7 @@ rust-version = "1.85.0"
exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"]
[workspace]
members = ["proc_macros/enum_display_style_derive"]
members = ["proc_macros/enum_display_style_derive", "proc_macros/validate_theme_derive"]
[dependencies]
anyhow = "1.0.68"
@@ -63,6 +63,7 @@ deunicode = "1.6.0"
paste = "1.0.15"
openssl = { version = "0.10.70", features = ["vendored"] }
veil = "0.2.0"
validate_theme_derive = { path = "proc_macros/validate_theme_derive" }
enum_display_style_derive = { path = "proc_macros/enum_display_style_derive" }
[dev-dependencies]
+15 -1
View File
@@ -206,6 +206,16 @@ Key:
- [ ] Support for Tautulli
### Themes
Managarr ships with a few themes out of the box. Here's a few examples:
![default](themes/default/manual_episode_search.png)
![dracula](themes/dracula/manual_episode_search.png)
![watermelon-dark](themes/watermelon-dark/manual_episode_search.png)
You can also create your own custom themes as well. To learn more about what themes are built-in to Managarr and how
to create your own custom themes, check out the [Themes README](themes/README.md).
### The Managarr CLI
Managarr can be used in one of two ways: As a TUI, or as a CLI for managing your Servarrs.
@@ -218,7 +228,7 @@ To see all available commands, simply run `managarr --help`:
```shell
$ managarr --help
managarr 0.5.0
managarr 0.5.1
Alex Clarke <alex.j.tusa@gmail.com>
A TUI and CLI to manage your Servarrs
@@ -235,6 +245,8 @@ Commands:
Options:
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
--config-file <CONFIG_FILE> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
--themes-file <THEMES_FILE> The Managarr themes file to use [env: MANAGARR_THEMES_FILE=]
--theme <THEME> The name of the Managarr theme to use [env: MANAGARR_THEME=]
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify the name of the instance configuration that you want to use.
This is useful when you have multiple instances of the same Servarr defined in your config file.
By default, if left empty, the first configured Servarr instance listed in the config file will be used.
@@ -315,6 +327,7 @@ managarr --config-file /path/to/config.yml
### Example Configuration:
```yaml
theme: default
radarr:
- host: 192.168.0.78
port: 7878
@@ -357,6 +370,7 @@ tautulli:
### Example Multi-Instance Configuration:
```yaml
theme: default
radarr:
- host: 192.168.0.78 # No name specified, so this instance's name will default to 'Radarr 1'
port: 7878
@@ -4,7 +4,8 @@ use crate::macro_models::DisplayStyleArgs;
use darling::FromVariant;
use quote::quote;
use syn::{Data, DeriveInput, parse_macro_input};
/// Derive macro for the EnumDisplayStyle trait.
/// Derive macro for generating a `to_display_str` method for an enum.
///
/// # Example
///
@@ -15,13 +16,12 @@ use syn::{Data, DeriveInput, parse_macro_input};
///
/// #[derive(EnumDisplayStyle)]
/// enum Weekend {
/// Saturday,
/// Sunday,
/// Saturday,
/// Sunday,
/// }
///
/// assert_eq!(Weekend::Saturday.to_display_str(), "Saturday");
/// assert_eq!(Weekend::Sunday.to_display_str(), "Sunday");
///
/// ```
///
/// Using custom values for the display style:
@@ -31,10 +31,10 @@ use syn::{Data, DeriveInput, parse_macro_input};
///
/// #[derive(EnumDisplayStyle)]
/// enum MonitorStatus {
/// #[display_style(name = "Monitor Transactions")]
/// Active,
/// #[display_style(name = "Don't Monitor Transactions")]
/// None,
/// #[display_style(name = "Monitor Transactions")]
/// Active,
/// #[display_style(name = "Don't Monitor Transactions")]
/// None,
/// }
///
/// assert_eq!(MonitorStatus::Active.to_display_str(), "Monitor Transactions");
@@ -0,0 +1,14 @@
[package]
name = "validate_theme_derive"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
quote = "1.0.39"
syn = "2.0.99"
[dev-dependencies]
log = "0.4.17"
@@ -0,0 +1,106 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields, parse_macro_input};
/// Derive macro for generating a `validate` method for a Theme struct.
/// The `validate` method ensures that all values with the `validate` attribute are not `None`.
/// Otherwise, an error message it output to both the log file and stdout and the program exits.
///
/// # Example
///
/// Valid themes pass through the program transitively without any messages being output.
///
/// ```
/// use validate_theme_derive::ValidateTheme;
///
/// #[derive(ValidateTheme, Default)]
/// struct Theme {
/// pub name: String,
/// #[validate]
/// pub good: Option<Style>,
/// #[validate]
/// pub bad: Option<Style>,
/// pub ugly: Option<Style>,
/// }
///
/// struct Style {
/// color: String,
/// }
///
/// let theme = Theme {
/// good: Some(Style { color: "Green".to_owned() }),
/// bad: Some(Style { color: "Red".to_owned() }),
/// ..Theme::default()
/// };
///
/// // Since only `good` and `bad` have the `validate` attribute, the `validate` method will only check those fields.
/// theme.validate();
/// // Since both `good` and `bad` have values, the program will not exit and no message is output.
/// ```
///
/// Invalid themes will output an error message to both the log file and stdout and the program will exit.
///
/// ```should_panic
/// use validate_theme_derive::ValidateTheme;
///
/// #[derive(ValidateTheme, Default)]
/// struct Theme {
/// pub name: String,
/// #[validate]
/// pub good: Option<Style>,
/// #[validate]
/// pub bad: Option<Style>,
/// pub ugly: Option<Style>,
/// }
///
/// struct Style {
/// color: String,
/// }
///
/// let theme = Theme {
/// bad: Some(Style { color: "Red".to_owned() }),
/// ..Theme::default()
/// };
///
/// // Since `good` has the `validate` attribute and since `good` is `None`, the `validate` method will output an error message and exit the program.
/// theme.validate();
/// ```
#[proc_macro_derive(ValidateTheme, attributes(validate))]
pub fn derive_validate_theme(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
let mut validation_checks = Vec::new();
if let Data::Struct(data_struct) = &input.data {
if let Fields::Named(fields) = &data_struct.fields {
for field in &fields.named {
let field_name = &field.ident;
let has_validate_attr = field
.attrs
.iter()
.any(|attr| attr.path().is_ident("validate"));
if has_validate_attr {
validation_checks.push(quote! {
if self.#field_name.is_none() {
log::error!("{} is missing a color value.", stringify!(#field_name));
eprintln!("{} is missing a color value.", stringify!(#field_name));
std::process::exit(1);
}
})
}
}
}
}
quote! {
impl #struct_name {
pub fn validate(&self) {
#(#validation_checks)*
}
}
}
.into()
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 220 KiB

+3 -2
View File
@@ -31,6 +31,7 @@ mod tests {
};
let sonarr_config_2 = ServarrConfig::default();
let config = AppConfig {
theme: None,
radarr: Some(vec![radarr_config_1.clone(), radarr_config_2.clone()]),
sonarr: Some(vec![sonarr_config_1.clone(), sonarr_config_2.clone()]),
};
@@ -97,7 +98,7 @@ mod tests {
assert!(!app.is_loading);
assert!(!app.is_routing);
assert!(!app.should_refresh);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app.cli_mode);
}
@@ -117,7 +118,7 @@ mod tests {
assert!(!app.is_loading);
assert!(!app.is_routing);
assert!(!app.should_refresh);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app.cli_mode);
}
+61 -1
View File
@@ -44,128 +44,188 @@ generate_keybindings! {
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct KeyBinding {
pub key: Key,
pub alt: Option<Key>,
pub desc: &'static str,
}
pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
add: KeyBinding {
key: Key::Char('a'),
alt: None,
desc: "add",
},
up: KeyBinding {
key: Key::Up,
alt: Some(Key::Char('k')),
desc: "up",
},
down: KeyBinding {
key: Key::Down,
alt: Some(Key::Char('j')),
desc: "down",
},
left: KeyBinding {
key: Key::Left,
alt: Some(Key::Char('h')),
desc: "left",
},
right: KeyBinding {
key: Key::Right,
alt: Some(Key::Char('l')),
desc: "right",
},
backspace: KeyBinding {
key: Key::Backspace,
alt: Some(Key::Ctrl('h')),
desc: "backspace",
},
next_servarr: KeyBinding {
key: Key::Tab,
alt: None,
desc: "next servarr",
},
previous_servarr: KeyBinding {
key: Key::BackTab,
alt: None,
desc: "previous servarr",
},
clear: KeyBinding {
key: Key::Char('c'),
alt: None,
desc: "clear",
},
auto_search: KeyBinding {
key: Key::Char('S'),
alt: None,
desc: "auto search",
},
search: KeyBinding {
key: Key::Char('s'),
alt: None,
desc: "search",
},
settings: KeyBinding {
key: Key::Char('S'),
alt: None,
desc: "settings",
},
filter: KeyBinding {
key: Key::Char('f'),
alt: None,
desc: "filter",
},
sort: KeyBinding {
key: Key::Char('o'),
alt: None,
desc: "sort",
},
edit: KeyBinding {
key: Key::Char('e'),
alt: None,
desc: "edit",
},
events: KeyBinding {
key: Key::Char('e'),
alt: None,
desc: "events",
},
logs: KeyBinding {
key: Key::Char('l'),
key: Key::Char('L'),
alt: None,
desc: "logs",
},
tasks: KeyBinding {
key: Key::Char('t'),
alt: None,
desc: "tasks",
},
test: KeyBinding {
key: Key::Char('t'),
alt: None,
desc: "test",
},
test_all: KeyBinding {
key: Key::Char('T'),
alt: None,
desc: "test all",
},
toggle_monitoring: KeyBinding {
key: Key::Char('m'),
alt: None,
desc: "toggle monitoring",
},
refresh: KeyBinding {
key: Key::Ctrl('r'),
alt: None,
desc: "refresh",
},
update: KeyBinding {
key: Key::Char('u'),
alt: None,
desc: "update",
},
home: KeyBinding {
key: Key::Home,
alt: None,
desc: "home",
},
end: KeyBinding {
key: Key::End,
alt: None,
desc: "end",
},
delete: KeyBinding {
key: Key::Delete,
alt: None,
desc: "delete",
},
submit: KeyBinding {
key: Key::Enter,
alt: None,
desc: "submit",
},
confirm: KeyBinding {
key: Key::Ctrl('s'),
alt: None,
desc: "submit",
},
quit: KeyBinding {
key: Key::Char('q'),
alt: None,
desc: "quit",
},
esc: KeyBinding {
key: Key::Esc,
alt: None,
desc: "close",
},
};
#[macro_export]
macro_rules! matches_key {
($binding:ident, $key:expr) => {
$crate::app::key_binding::DEFAULT_KEYBINDINGS.$binding.key == $key
|| ($crate::app::key_binding::DEFAULT_KEYBINDINGS
.$binding
.alt
.is_some()
&& $crate::app::key_binding::DEFAULT_KEYBINDINGS
.$binding
.alt
.unwrap()
== $key)
};
($binding:ident, $key:expr, $ignore_special_keys:expr) => {
$crate::app::key_binding::DEFAULT_KEYBINDINGS.$binding.key == $key
|| !$ignore_special_keys
&& ($crate::app::key_binding::DEFAULT_KEYBINDINGS
.$binding
.alt
.is_some()
&& $crate::app::key_binding::DEFAULT_KEYBINDINGS
.$binding
.alt
.unwrap()
== $key)
};
}
+71 -30
View File
@@ -5,44 +5,85 @@ mod test {
use crate::app::key_binding::{KeyBinding, DEFAULT_KEYBINDINGS};
use crate::event::Key;
use crate::matches_key;
#[rstest]
#[case(DEFAULT_KEYBINDINGS.add, Key::Char('a'), "add")]
#[case(DEFAULT_KEYBINDINGS.up, Key::Up, "up")]
#[case(DEFAULT_KEYBINDINGS.down, Key::Down, "down")]
#[case(DEFAULT_KEYBINDINGS.left, Key::Left, "left")]
#[case(DEFAULT_KEYBINDINGS.right, Key::Right, "right")]
#[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, "backspace")]
#[case(DEFAULT_KEYBINDINGS.next_servarr, Key::Tab, "next servarr")]
#[case(DEFAULT_KEYBINDINGS.previous_servarr, Key::BackTab, "previous servarr")]
#[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), "clear")]
#[case(DEFAULT_KEYBINDINGS.auto_search, Key::Char('S'), "auto search")]
#[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), "search")]
#[case(DEFAULT_KEYBINDINGS.settings, Key::Char('S'), "settings")]
#[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), "filter")]
#[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), "sort")]
#[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), "edit")]
#[case(DEFAULT_KEYBINDINGS.events, Key::Char('e'), "events")]
#[case(DEFAULT_KEYBINDINGS.logs, Key::Char('l'), "logs")]
#[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), "tasks")]
#[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")]
#[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), "test all")]
#[case(DEFAULT_KEYBINDINGS.toggle_monitoring, Key::Char('m'), "toggle monitoring")]
#[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), "refresh")]
#[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), "update")]
#[case(DEFAULT_KEYBINDINGS.home, Key::Home, "home")]
#[case(DEFAULT_KEYBINDINGS.end, Key::End, "end")]
#[case(DEFAULT_KEYBINDINGS.delete, Key::Delete, "delete")]
#[case(DEFAULT_KEYBINDINGS.submit, Key::Enter, "submit")]
#[case(DEFAULT_KEYBINDINGS.confirm, Key::Ctrl('s'), "submit")]
#[case(DEFAULT_KEYBINDINGS.quit, Key::Char('q'), "quit")]
#[case(DEFAULT_KEYBINDINGS.esc, Key::Esc, "close")]
#[case(DEFAULT_KEYBINDINGS.add, Key::Char('a'), None, "add")]
#[case(DEFAULT_KEYBINDINGS.up, Key::Up, Some(Key::Char('k')), "up")]
#[case(DEFAULT_KEYBINDINGS.down, Key::Down, Some(Key::Char('j')), "down")]
#[case(DEFAULT_KEYBINDINGS.left, Key::Left, Some(Key::Char('h')), "left")]
#[case(DEFAULT_KEYBINDINGS.right, Key::Right, Some(Key::Char('l')), "right")]
#[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, Some(Key::Ctrl('h')), "backspace")]
#[case(DEFAULT_KEYBINDINGS.next_servarr, Key::Tab, None, "next servarr")]
#[case(DEFAULT_KEYBINDINGS.previous_servarr, Key::BackTab, None, "previous servarr")]
#[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), None, "clear")]
#[case(DEFAULT_KEYBINDINGS.auto_search, Key::Char('S'), None, "auto search")]
#[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), None, "search")]
#[case(DEFAULT_KEYBINDINGS.settings, Key::Char('S'), None, "settings")]
#[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), None, "filter")]
#[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), None, "sort")]
#[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), None, "edit")]
#[case(DEFAULT_KEYBINDINGS.events, Key::Char('e'), None, "events")]
#[case(DEFAULT_KEYBINDINGS.logs, Key::Char('L'), None, "logs")]
#[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), None, "tasks")]
#[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), None, "test")]
#[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), None, "test all")]
#[case(DEFAULT_KEYBINDINGS.toggle_monitoring, Key::Char('m'), None, "toggle monitoring")]
#[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), None, "refresh")]
#[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), None, "update")]
#[case(DEFAULT_KEYBINDINGS.home, Key::Home, None, "home")]
#[case(DEFAULT_KEYBINDINGS.end, Key::End, None, "end")]
#[case(DEFAULT_KEYBINDINGS.delete, Key::Delete, None, "delete")]
#[case(DEFAULT_KEYBINDINGS.submit, Key::Enter, None, "submit")]
#[case(DEFAULT_KEYBINDINGS.confirm, Key::Ctrl('s'), None, "submit")]
#[case(DEFAULT_KEYBINDINGS.quit, Key::Char('q'), None, "quit")]
#[case(DEFAULT_KEYBINDINGS.esc, Key::Esc, None, "close")]
fn test_default_key_bindings_and_descriptions(
#[case] key_binding: KeyBinding,
#[case] expected_key: Key,
#[case] expected_alt_key: Option<Key>,
#[case] expected_desc: &str,
) {
assert_eq!(key_binding.key, expected_key);
assert_eq!(key_binding.alt, expected_alt_key);
assert_str_eq!(key_binding.desc, expected_desc);
}
#[test]
fn test_matches_key_macro() {
let key = Key::Char('t');
assert!(matches_key!(test, key));
assert!(!matches_key!(test, Key::Char('T')));
}
#[test]
fn test_matches_key_macro_with_alt_keybinding() {
let alt_key = Key::Char('k');
let key = Key::Up;
assert!(matches_key!(up, key));
assert!(matches_key!(up, alt_key));
assert!(!matches_key!(up, Key::Char('t')));
}
#[test]
fn test_matches_key_macro_with_alt_keybinding_uses_alt_key_when_ignore_special_keys_is_false() {
let alt_key = Key::Char('k');
let key = Key::Up;
assert!(matches_key!(up, key, false));
assert!(matches_key!(up, alt_key, false));
assert!(!matches_key!(up, Key::Char('t'), false));
}
#[test]
fn test_matches_key_macro_with_alt_keybinding_ignores_alt_key_when_ignore_special_keys_is_true() {
let alt_key = Key::Char('k');
let key = Key::Up;
assert!(matches_key!(up, key, true));
assert!(!matches_key!(up, alt_key, true));
assert!(!matches_key!(up, Key::Char('t'), true));
}
}
+3 -2
View File
@@ -39,7 +39,7 @@ pub struct App<'a> {
pub is_routing: bool,
pub is_loading: bool,
pub should_refresh: bool,
pub should_ignore_quit_key: bool,
pub ignore_special_keys_for_textbox_input: bool,
pub cli_mode: bool,
pub data: Data<'a>,
}
@@ -224,7 +224,7 @@ impl Default for App<'_> {
is_loading: false,
is_routing: false,
should_refresh: false,
should_ignore_quit_key: false,
ignore_special_keys_for_textbox_input: false,
cli_mode: false,
data: Data::default(),
}
@@ -270,6 +270,7 @@ pub struct Data<'a> {
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct AppConfig {
pub theme: Option<String>,
pub radarr: Option<Vec<ServarrConfig>>,
pub sonarr: Option<Vec<ServarrConfig>>,
}
+24 -23
View File
@@ -1,9 +1,9 @@
use radarr_handlers::RadarrHandler;
use sonarr_handlers::SonarrHandler;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::matches_key;
use crate::models::{HorizontallyScrollableText, Route};
mod radarr_handlers;
@@ -22,40 +22,42 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
fn handle_key_event(&mut self) {
let key = self.get_key();
match key {
_ if key == DEFAULT_KEYBINDINGS.up.key => {
_ if matches_key!(up, key, self.ignore_special_keys()) => {
if self.is_ready() {
self.handle_scroll_up();
}
}
_ if key == DEFAULT_KEYBINDINGS.down.key => {
_ if matches_key!(down, key, self.ignore_special_keys()) => {
if self.is_ready() {
self.handle_scroll_down();
}
}
_ if key == DEFAULT_KEYBINDINGS.home.key => {
_ if matches_key!(home, key) => {
if self.is_ready() {
self.handle_home();
}
}
_ if key == DEFAULT_KEYBINDINGS.end.key => {
_ if matches_key!(end, key) => {
if self.is_ready() {
self.handle_end();
}
}
_ if key == DEFAULT_KEYBINDINGS.delete.key => {
_ if matches_key!(delete, key) => {
if self.is_ready() {
self.handle_delete();
}
}
_ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => {
_ if matches_key!(left, key, self.ignore_special_keys())
|| matches_key!(right, key, self.ignore_special_keys()) =>
{
self.handle_left_right_action()
}
_ if key == DEFAULT_KEYBINDINGS.submit.key => {
_ if matches_key!(submit, key) => {
if self.is_ready() {
self.handle_submit();
}
}
_ if key == DEFAULT_KEYBINDINGS.esc.key => self.handle_esc(),
_ if matches_key!(esc, key) => self.handle_esc(),
_ => {
if self.is_ready() {
self.handle_char_key_event();
@@ -71,6 +73,7 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
fn accepts(active_block: T) -> bool;
fn new(key: Key, app: &'a mut App<'b>, active_block: T, context: Option<T>) -> Self;
fn get_key(&self) -> Key;
fn ignore_special_keys(&self) -> bool;
fn is_ready(&self) -> bool;
fn handle_scroll_up(&mut self);
fn handle_scroll_down(&mut self);
@@ -84,12 +87,12 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
}
pub fn handle_events(key: Key, app: &mut App<'_>) {
if key == DEFAULT_KEYBINDINGS.next_servarr.key {
if matches_key!(next_servarr, key) {
app.reset();
app.server_tabs.next();
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
app.cancellation_token.cancel();
} else if key == DEFAULT_KEYBINDINGS.previous_servarr.key {
} else if matches_key!(previous_servarr, key) {
app.reset();
app.server_tabs.previous();
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
@@ -115,17 +118,15 @@ fn handle_clear_errors(app: &mut App<'_>) {
fn handle_prompt_toggle(app: &mut App<'_>, key: Key) {
match key {
_ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => {
match app.get_current_route() {
Route::Radarr(_, _) => {
app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm
}
Route::Sonarr(_, _) => {
app.data.sonarr_data.prompt_confirm = !app.data.sonarr_data.prompt_confirm
}
_ => (),
_ if matches_key!(left, key) || matches_key!(right, key) => match app.get_current_route() {
Route::Radarr(_, _) => {
app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm
}
}
Route::Sonarr(_, _) => {
app.data.sonarr_data.prompt_confirm = !app.data.sonarr_data.prompt_confirm
}
_ => (),
},
_ => (),
}
}
@@ -149,7 +150,7 @@ macro_rules! handle_text_box_left_right_keys {
macro_rules! handle_text_box_keys {
($self:expr, $key:expr, $input:expr) => {
match $self.key {
_ if $key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.backspace.key => {
_ if $crate::matches_key!(backspace, $key) => {
$input.pop();
}
Key::Char(character) => {
@@ -165,7 +166,7 @@ macro_rules! handle_prompt_left_right_keys {
($self:expr, $confirm_prompt:expr, $data:ident) => {
if $self.app.data.$data.selected_block.get_active_block() == $confirm_prompt {
handle_prompt_toggle($self.app, $self.key);
} else if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.left.key {
} else if $crate::matches_key!(left, $self.key) {
$self.app.data.$data.selected_block.left();
} else {
$self.app.data.$data.selected_block.right();
@@ -4,6 +4,7 @@ mod tests {
use chrono::DateTime;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -541,6 +542,25 @@ mod tests {
})
}
#[rstest]
fn test_blocklist_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = BlocklistHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_blocklist_item_id() {
let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
@@ -9,6 +7,7 @@ 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"]
@@ -51,6 +50,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
BLOCKLIST_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -143,10 +146,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
let key = self.key;
match self.active_radarr_block {
ActiveRadarrBlock::Blocklist => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ if key == DEFAULT_KEYBINDINGS.clear.key => {
_ if matches_key!(clear, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into());
@@ -154,7 +157,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
_ => (),
},
ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem(
self.extract_blocklist_item_id(),
@@ -164,7 +167,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
}
}
ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::ClearBlocklist);
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::CollectionMovie;
@@ -11,6 +9,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
};
use crate::models::stateful_table::StatefulTable;
use crate::models::BlockSelectionState;
use crate::{handle_table_events, matches_key};
#[cfg(test)]
#[path = "collection_details_handler_tests.rs"]
@@ -46,6 +45,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
COLLECTION_DETAILS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -130,7 +133,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
fn handle_char_key_event(&mut self) {
if self.active_radarr_block == ActiveRadarrBlock::CollectionDetails
&& self.key == DEFAULT_KEYBINDINGS.edit.key
&& matches_key!(edit, self.key)
{
self.app.push_navigation_stack(
(
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_str_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -278,6 +279,25 @@ mod tests {
});
}
#[rstest]
fn test_collection_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = CollectionDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_collection_details_handler_not_ready_when_loading() {
let mut app = App::test_default();
@@ -589,6 +589,25 @@ mod tests {
});
}
#[rstest]
fn test_collections_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = CollectionsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_collections_handler_not_ready_when_loading() {
let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -7,7 +6,7 @@ use crate::models::servarr_data::radarr::modals::EditCollectionModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS};
use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
#[cfg(test)]
#[path = "edit_collection_handler_tests.rs"]
@@ -69,6 +68,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
EDIT_COLLECTION_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -262,7 +265,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
)
.into(),
);
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
ActiveRadarrBlock::EditCollectionToggleMonitored => {
self
@@ -311,7 +314,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
| ActiveRadarrBlock::EditCollectionSelectQualityProfile => self.app.pop_navigation_stack(),
ActiveRadarrBlock::EditCollectionRootFolderPathInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => (),
}
@@ -321,7 +324,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
match self.active_radarr_block {
ActiveRadarrBlock::EditCollectionRootFolderPathInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveRadarrBlock::EditCollectionPrompt => {
self.app.pop_navigation_stack();
@@ -354,7 +357,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
ActiveRadarrBlock::EditCollectionPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::EditCollectionConfirmPrompt
&& key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, key)
{
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(
@@ -2,6 +2,7 @@
mod tests {
use bimap::BiMap;
use pretty_assertions::assert_str_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -457,7 +458,7 @@ mod tests {
#[test]
fn test_edit_collection_root_folder_path_input_submit() {
let mut app = App::test_default();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal {
path: "Test Path".into(),
..EditCollectionModal::default()
@@ -473,7 +474,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.radarr_data
@@ -759,7 +760,7 @@ mod tests {
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
if selected_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput {
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
}
}
@@ -791,7 +792,7 @@ mod tests {
);
if active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput {
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
}
}
}
@@ -811,7 +812,7 @@ mod tests {
let mut app = App::test_default();
app.data.radarr_data = create_test_radarr_data();
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into());
@@ -823,7 +824,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.get_current_route(),
ActiveRadarrBlock::EditCollectionPrompt.into()
@@ -926,7 +927,7 @@ mod tests {
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
EditCollectionHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::EditCollectionRootFolderPathInput,
None,
@@ -942,7 +943,7 @@ mod tests {
.unwrap()
.path
.text,
"h"
"a"
);
}
@@ -1019,6 +1020,25 @@ mod tests {
});
}
#[rstest]
fn test_edit_collection_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EditCollectionHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_build_edit_collection_params() {
let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
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;
@@ -14,6 +12,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
use crate::models::stateful_table::SortOption;
use crate::models::BlockSelectionState;
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
mod collection_details_handler;
mod edit_collection_handler;
@@ -73,6 +72,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|| COLLECTIONS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -145,7 +148,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
let key = self.key;
match self.active_radarr_block {
ActiveRadarrBlock::Collections => match self.key {
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
_ if matches_key!(edit, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
@@ -154,18 +157,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
self.app.data.radarr_data.selected_block =
BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
}
_ if key == DEFAULT_KEYBINDINGS.update.key => {
_ if matches_key!(update, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
}
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ => (),
},
ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -387,6 +388,25 @@ mod tests {
})
}
#[rstest]
fn test_downloads_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = DownloadsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_download_id() {
let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::DownloadRecord;
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"]
@@ -47,6 +46,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
DOWNLOADS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -130,18 +133,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
let key = self.key;
match self.active_radarr_block {
ActiveRadarrBlock::Downloads => match self.key {
_ if key == DEFAULT_KEYBINDINGS.update.key => {
_ if matches_key!(update, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into());
}
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ => (),
},
ActiveRadarrBlock::DeleteDownloadPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteDownload(self.extract_download_id()));
@@ -150,7 +153,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
}
}
ActiveRadarrBlock::UpdateDownloadsPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads);
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -6,7 +5,9 @@ use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams;
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys};
use crate::{
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
};
#[cfg(test)]
#[path = "edit_indexer_handler_tests.rs"]
@@ -65,6 +66,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
EDIT_INDEXER_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -356,7 +361,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
| ActiveRadarrBlock::EditIndexerSeedRatioInput
| ActiveRadarrBlock::EditIndexerTagsInput => {
self.app.push_navigation_stack(selected_block.into());
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
ActiveRadarrBlock::EditIndexerPriorityInput => self
.app
@@ -402,7 +407,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
| ActiveRadarrBlock::EditIndexerSeedRatioInput
| ActiveRadarrBlock::EditIndexerTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveRadarrBlock::EditIndexerPriorityInput => self.app.pop_navigation_stack(),
_ => (),
@@ -423,7 +428,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
| ActiveRadarrBlock::EditIndexerPriorityInput
| ActiveRadarrBlock::EditIndexerTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => self.app.pop_navigation_stack(),
}
@@ -504,7 +509,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
ActiveRadarrBlock::EditIndexerPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::EditIndexerConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, self.key)
{
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action =
@@ -10,6 +10,7 @@ mod tests {
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams;
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
mod test_handle_scroll_up_and_down {
@@ -1002,7 +1003,7 @@ mod tests {
.handle();
assert_eq!(app.get_current_route(), block.into());
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
}
#[test]
@@ -1027,7 +1028,7 @@ mod tests {
app.get_current_route(),
ActiveRadarrBlock::EditIndexerPriorityInput.into()
);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
}
#[test]
@@ -1193,7 +1194,7 @@ mod tests {
fn test_edit_indexer_name_input_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
name: "Test".into(),
..EditIndexerModal::default()
@@ -1209,7 +1210,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.radarr_data
@@ -1229,7 +1230,7 @@ mod tests {
fn test_edit_indexer_url_input_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
url: "Test".into(),
..EditIndexerModal::default()
@@ -1245,7 +1246,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.radarr_data
@@ -1265,7 +1266,7 @@ mod tests {
fn test_edit_indexer_api_key_input_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
api_key: "Test".into(),
..EditIndexerModal::default()
@@ -1281,7 +1282,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.radarr_data
@@ -1301,7 +1302,7 @@ mod tests {
fn test_edit_indexer_seed_ratio_input_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
seed_ratio: "Test".into(),
..EditIndexerModal::default()
@@ -1317,7 +1318,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.radarr_data
@@ -1337,7 +1338,7 @@ mod tests {
fn test_edit_indexer_tags_input_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
tags: "Test".into(),
..EditIndexerModal::default()
@@ -1353,7 +1354,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.radarr_data
@@ -1417,12 +1418,12 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(active_radarr_block.into());
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
EditIndexerHandler::new(ESC_KEY, &mut app, active_radarr_block, None).handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.data.radarr_data.edit_indexer_modal,
Some(EditIndexerModal::default())
@@ -1597,7 +1598,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::EditIndexerNameInput,
None,
@@ -1613,7 +1614,7 @@ mod tests {
.unwrap()
.name
.text,
"h"
"a"
);
}
@@ -1624,7 +1625,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::EditIndexerUrlInput,
None,
@@ -1640,7 +1641,7 @@ mod tests {
.unwrap()
.url
.text,
"h"
"a"
);
}
@@ -1651,7 +1652,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::EditIndexerApiKeyInput,
None,
@@ -1667,7 +1668,7 @@ mod tests {
.unwrap()
.api_key
.text,
"h"
"a"
);
}
@@ -1678,7 +1679,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::EditIndexerSeedRatioInput,
None,
@@ -1694,7 +1695,7 @@ mod tests {
.unwrap()
.seed_ratio
.text,
"h"
"a"
);
}
@@ -1705,7 +1706,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::EditIndexerTagsInput,
None,
@@ -1721,7 +1722,7 @@ mod tests {
.unwrap()
.tags
.text,
"h"
"a"
);
}
@@ -1793,6 +1794,25 @@ mod tests {
})
}
#[rstest]
fn test_edit_indexer_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EditIndexerHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_build_edit_indexer_params() {
let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -7,7 +6,9 @@ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS,
};
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys};
use crate::{
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
};
#[cfg(test)]
#[path = "edit_indexer_settings_handler_tests.rs"]
@@ -37,6 +38,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
INDEXER_SETTINGS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -207,7 +212,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
self.app.push_navigation_stack(
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into(),
);
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags => {
let indexer_settings = self.app.data.radarr_data.indexer_settings.as_mut().unwrap();
@@ -224,7 +229,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
}
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveRadarrBlock::IndexerSettingsMinimumAgeInput
| ActiveRadarrBlock::IndexerSettingsRetentionInput
@@ -244,7 +249,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
}
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => self.app.pop_navigation_stack(),
}
@@ -269,7 +274,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
ActiveRadarrBlock::AllIndexerSettingsPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::IndexerSettingsConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, self.key)
{
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -602,7 +603,7 @@ mod tests {
app.get_current_route(),
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into()
);
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
}
#[test]
@@ -716,7 +717,7 @@ mod tests {
#[test]
fn test_edit_indexer_settings_whitelisted_subtitle_tags_input_submit() {
let mut app = App::test_default();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.indexer_settings = Some(IndexerSettings {
whitelisted_hardcoded_subs: "Test tags".into(),
..IndexerSettings::default()
@@ -734,7 +735,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.radarr_data
@@ -814,7 +815,7 @@ mod tests {
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into(),
);
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
IndexerSettingsHandler::new(
ESC_KEY,
@@ -825,7 +826,7 @@ mod tests {
.handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.data.radarr_data.indexer_settings,
Some(IndexerSettings::default())
@@ -907,7 +908,7 @@ mod tests {
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
None,
@@ -923,7 +924,7 @@ mod tests {
.unwrap()
.whitelisted_hardcoded_subs
.text,
"h"
"a"
);
}
@@ -970,6 +971,25 @@ mod tests {
})
}
#[rstest]
fn test_indexer_settings_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = IndexerSettingsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_build_edit_indexer_settings_body() {
let mut app = App::test_default();
@@ -633,6 +633,25 @@ mod tests {
})
}
#[rstest]
fn test_indexers_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = IndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_indexer_id() {
let mut app = App::test_default();
+10 -7
View File
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
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;
@@ -15,6 +13,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
use crate::models::servarr_models::Indexer;
use crate::models::BlockSelectionState;
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
mod edit_indexer_handler;
mod edit_indexer_settings_handler;
@@ -70,6 +69,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
|| INDEXERS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -169,20 +172,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
let key = self.key;
match self.active_radarr_block {
ActiveRadarrBlock::Indexers => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ if key == DEFAULT_KEYBINDINGS.test.key => {
_ if matches_key!(test, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::TestIndexer.into());
}
_ if key == DEFAULT_KEYBINDINGS.test_all.key => {
_ if matches_key!(test_all, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into());
}
_ if key == DEFAULT_KEYBINDINGS.settings.key => {
_ if matches_key!(settings, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
@@ -192,7 +195,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
_ => (),
},
ActiveRadarrBlock::DeleteIndexerPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteIndexer(self.extract_indexer_id()));
@@ -48,6 +48,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandl
active_block == ActiveRadarrBlock::TestAllIndexers
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -7,6 +7,7 @@ mod tests {
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::stateful_table::StatefulTable;
use rstest::rstest;
use strum::IntoEnumIterator;
mod test_handle_esc {
@@ -48,6 +49,25 @@ mod tests {
});
}
#[rstest]
fn test_test_all_indexers_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = TestAllIndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_test_all_indexers_handler_is_not_ready_when_loading() {
let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::{
@@ -11,7 +10,9 @@ use crate::models::servarr_data::radarr::radarr_data::{
use crate::models::stateful_table::StatefulTable;
use crate::models::{BlockSelectionState, Scrollable};
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, App, Key};
use crate::{
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key, App, Key,
};
#[cfg(test)]
#[path = "add_movie_handler_tests.rs"]
@@ -132,6 +133,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
ADD_MOVIE_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -404,7 +409,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
self
.app
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into());
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ if self.active_radarr_block == ActiveRadarrBlock::AddMovieSearchResults
&& self.app.data.radarr_data.add_searched_movies.is_some() =>
@@ -468,7 +473,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
)
.into(),
);
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
_ => (),
}
@@ -479,7 +484,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
| ActiveRadarrBlock::AddMovieSelectRootFolder => self.app.pop_navigation_stack(),
ActiveRadarrBlock::AddMovieTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => (),
}
@@ -490,12 +495,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
ActiveRadarrBlock::AddMovieSearchInput => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.add_movie_search = None;
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveRadarrBlock::AddMovieSearchResults | ActiveRadarrBlock::AddMovieEmptySearchResults => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.add_searched_movies = None;
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
ActiveRadarrBlock::AddMoviePrompt => {
self.app.pop_navigation_stack();
@@ -509,7 +514,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
| ActiveRadarrBlock::AddMovieSelectRootFolder => self.app.pop_navigation_stack(),
ActiveRadarrBlock::AddMovieTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => (),
}
@@ -542,7 +547,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
ActiveRadarrBlock::AddMoviePrompt => {
if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::AddMovieConfirmPrompt
&& key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, key)
{
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action =
@@ -782,7 +782,7 @@ mod tests {
#[test]
fn test_add_movie_search_input_submit() {
let mut app = App::test_default();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.add_movie_search = Some("test".into());
AddMovieHandler::new(
@@ -793,7 +793,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.get_current_route(),
ActiveRadarrBlock::AddMovieSearchResults.into()
@@ -805,7 +805,7 @@ mod tests {
let mut app = App::test_default();
app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default());
app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
AddMovieHandler::new(
SUBMIT_KEY,
@@ -815,7 +815,7 @@ mod tests {
)
.handle();
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.get_current_route(),
ActiveRadarrBlock::AddMovieSearchInput.into()
@@ -1093,7 +1093,7 @@ mod tests {
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
if selected_block == ActiveRadarrBlock::AddMovieTagsInput {
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
}
}
@@ -1126,7 +1126,7 @@ mod tests {
);
if active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput {
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
}
}
}
@@ -1149,7 +1149,7 @@ mod tests {
let mut app = App::test_default();
app.is_loading = is_ready;
app.data.radarr_data = create_test_radarr_data();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
AddMovieHandler::new(
@@ -1160,7 +1160,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert_eq!(app.data.radarr_data.add_movie_search, None);
}
@@ -1169,7 +1169,7 @@ mod tests {
fn test_add_movie_input_esc() {
let mut app = App::test_default();
app.data.radarr_data = create_test_radarr_data();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into());
app.push_navigation_stack(ActiveRadarrBlock::AddMovieTagsInput.into());
@@ -1181,7 +1181,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.get_current_route(),
ActiveRadarrBlock::AddMoviePrompt.into()
@@ -1213,7 +1213,7 @@ mod tests {
ActiveRadarrBlock::AddMovieSearchInput.into()
);
assert!(app.data.radarr_data.add_searched_movies.is_none());
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
}
#[test]
@@ -1259,7 +1259,7 @@ mod tests {
fn test_add_movie_tags_input_esc() {
let mut app = App::test_default();
app.data.radarr_data = create_test_radarr_data();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into());
app.push_navigation_stack(ActiveRadarrBlock::AddMovieTagsInput.into());
@@ -1271,7 +1271,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.get_current_route(),
ActiveRadarrBlock::AddMoviePrompt.into()
@@ -1395,7 +1395,7 @@ mod tests {
app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default());
AddMovieHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::AddMovieSearchInput,
None,
@@ -1404,7 +1404,7 @@ mod tests {
assert_str_eq!(
app.data.radarr_data.add_movie_search.as_ref().unwrap().text,
"h"
"a"
);
}
@@ -1414,7 +1414,7 @@ mod tests {
app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default());
AddMovieHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::AddMovieTagsInput,
None,
@@ -1430,7 +1430,7 @@ mod tests {
.unwrap()
.tags
.text,
"h"
"a"
);
}
@@ -1523,6 +1523,25 @@ mod tests {
});
}
#[rstest]
fn test_add_movie_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = AddMovieHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_add_movie_search_no_panic_on_none_search_result() {
let mut app = App::test_default();
@@ -1,7 +1,7 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::matches_key;
use crate::models::radarr_models::DeleteMovieParams;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
use crate::network::radarr_network::RadarrEvent;
@@ -37,6 +37,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
DELETE_MOVIE_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -122,7 +126,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
if self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt
&& self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::DeleteMovieConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, self.key)
{
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -313,6 +314,25 @@ mod tests {
});
}
#[rstest]
fn test_delete_movie_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = DeleteMovieHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_build_delete_movie_params() {
let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -7,7 +6,7 @@ use crate::models::servarr_data::radarr::modals::EditMovieModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS};
use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
#[cfg(test)]
#[path = "edit_movie_handler_tests.rs"]
@@ -68,6 +67,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
EDIT_MOVIE_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -290,7 +293,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
)
.into(),
);
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
ActiveRadarrBlock::EditMovieToggleMonitored => {
self
@@ -319,7 +322,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
| ActiveRadarrBlock::EditMovieSelectQualityProfile => self.app.pop_navigation_stack(),
ActiveRadarrBlock::EditMoviePathInput | ActiveRadarrBlock::EditMovieTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => (),
}
@@ -329,7 +332,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
match self.active_radarr_block {
ActiveRadarrBlock::EditMovieTagsInput | ActiveRadarrBlock::EditMoviePathInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveRadarrBlock::EditMoviePrompt => {
self.app.pop_navigation_stack();
@@ -376,7 +379,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
ActiveRadarrBlock::EditMoviePrompt => {
if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::EditMovieConfirmPrompt
&& key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, key)
{
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action =
@@ -2,6 +2,7 @@
mod tests {
use bimap::BiMap;
use pretty_assertions::assert_str_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -548,7 +549,7 @@ mod tests {
#[test]
fn test_edit_movie_path_input_submit() {
let mut app = App::test_default();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal {
path: "Test Path".into(),
..EditMovieModal::default()
@@ -564,7 +565,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.radarr_data
@@ -583,7 +584,7 @@ mod tests {
#[test]
fn test_edit_movie_tags_input_submit() {
let mut app = App::test_default();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal {
tags: "Test Tags".into(),
..EditMovieModal::default()
@@ -599,7 +600,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.radarr_data
@@ -813,7 +814,7 @@ mod tests {
if selected_block == ActiveRadarrBlock::EditMoviePathInput
|| selected_block == ActiveRadarrBlock::EditMovieTagsInput
{
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
}
}
@@ -851,7 +852,7 @@ mod tests {
.into()
);
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
}
#[rstest]
@@ -885,7 +886,7 @@ mod tests {
if active_radarr_block == ActiveRadarrBlock::EditMoviePathInput
|| active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput
{
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
}
}
}
@@ -911,13 +912,13 @@ mod tests {
) {
let mut app = App::test_default();
app.data.radarr_data = create_test_radarr_data();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into());
app.push_navigation_stack(active_radarr_block.into());
EditMovieHandler::new(ESC_KEY, &mut app, active_radarr_block, None).handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.get_current_route(),
ActiveRadarrBlock::EditMoviePrompt.into()
@@ -1033,7 +1034,7 @@ mod tests {
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default());
EditMovieHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::EditMoviePathInput,
None,
@@ -1049,7 +1050,7 @@ mod tests {
.unwrap()
.path
.text,
"h"
"a"
);
}
@@ -1059,7 +1060,7 @@ mod tests {
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default());
EditMovieHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::EditMovieTagsInput,
None,
@@ -1075,7 +1076,7 @@ mod tests {
.unwrap()
.tags
.text,
"h"
"a"
);
}
@@ -1148,6 +1149,25 @@ mod tests {
});
}
#[rstest]
fn test_edit_movie_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EditMovieHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_build_edit_movie_params() {
let mut app = App::test_default();
@@ -331,7 +331,7 @@ mod tests {
app.get_current_route(),
ActiveRadarrBlock::AddMovieSearchInput.into()
);
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.add_movie_search.is_some());
}
@@ -355,7 +355,7 @@ mod tests {
.handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.add_movie_search.is_none());
}
@@ -777,6 +777,25 @@ mod tests {
});
}
#[rstest]
fn test_library_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = LibraryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_library_handler_not_ready_when_loading() {
let mut app = App::test_default();
+11 -8
View File
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
@@ -8,7 +7,6 @@ use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHand
use crate::handlers::radarr_handlers::library::movie_details_handler::MovieDetailsHandler;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::models::radarr_models::Movie;
use crate::models::servarr_data::radarr::radarr_data::{
@@ -17,6 +15,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
use crate::models::stateful_table::SortOption;
use crate::models::{BlockSelectionState, HorizontallyScrollableText};
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
mod add_movie_handler;
mod delete_movie_handler;
@@ -81,6 +80,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|| LIBRARY_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -161,7 +164,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
let key = self.key;
match self.active_radarr_block {
ActiveRadarrBlock::Movies => match self.key {
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
_ if matches_key!(edit, key) => {
self.app.push_navigation_stack(
(
ActiveRadarrBlock::EditMoviePrompt,
@@ -173,25 +176,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
self.app.data.radarr_data.selected_block =
BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS);
}
_ if key == DEFAULT_KEYBINDINGS.add.key => {
_ if matches_key!(add, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
self.app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
_ if key == DEFAULT_KEYBINDINGS.update.key => {
_ if matches_key!(update, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into());
}
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ => (),
},
ActiveRadarrBlock::UpdateAllMoviesPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies);
@@ -1,9 +1,7 @@
use serde_json::Number;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::{
@@ -16,6 +14,7 @@ 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"]
@@ -136,6 +135,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
MOVIE_DETAILS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -245,13 +248,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
| ActiveRadarrBlock::Cast
| ActiveRadarrBlock::Crew
| ActiveRadarrBlock::ManualSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.left.key => {
_ if matches_key!(left, self.key) => {
self.app.data.radarr_data.movie_info_tabs.previous();
self.app.pop_and_push_navigation_stack(
self.app.data.radarr_data.movie_info_tabs.get_active_route(),
);
}
_ if self.key == DEFAULT_KEYBINDINGS.right.key => {
_ if matches_key!(right, self.key) => {
self.app.data.radarr_data.movie_info_tabs.next();
self.app.pop_and_push_navigation_stack(
self.app.data.radarr_data.movie_info_tabs.get_active_route(),
@@ -332,12 +335,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
| ActiveRadarrBlock::Cast
| ActiveRadarrBlock::Crew
| ActiveRadarrBlock::ManualSearch => match self.key {
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => {
_ if matches_key!(auto_search, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AutomaticallySearchMoviePrompt.into());
}
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
_ if matches_key!(edit, key) => {
self.app.push_navigation_stack(
(
ActiveRadarrBlock::EditMoviePrompt,
@@ -349,35 +352,33 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
self.app.data.radarr_data.selected_block =
BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS);
}
_ if key == DEFAULT_KEYBINDINGS.update.key => {
_ if matches_key!(update, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::UpdateAndScanPrompt.into());
}
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self
.app
.pop_and_push_navigation_stack(self.active_radarr_block.into());
}
_ => (),
},
ActiveRadarrBlock::AutomaticallySearchMoviePrompt
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
ActiveRadarrBlock::AutomaticallySearchMoviePrompt if matches_key!(confirm, key) => {
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::TriggerAutomaticSearch(self.extract_movie_id()));
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::UpdateAndScanPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
ActiveRadarrBlock::UpdateAndScanPrompt if matches_key!(confirm, key) => {
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::UpdateAndScan(self.extract_movie_id()));
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::ManualSearchConfirmPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
ActiveRadarrBlock::ManualSearchConfirmPrompt if matches_key!(confirm, key) => {
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DownloadRelease(
self.build_radarr_release_download_body(),
@@ -1042,6 +1042,25 @@ mod tests {
});
}
#[rstest]
fn test_movie_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = MovieDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[rstest]
fn test_movie_details_handler_is_not_ready_when_loading(
#[values(
+9 -6
View File
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::radarr_handlers::blocklist::BlocklistHandler;
use crate::handlers::radarr_handlers::collections::CollectionsHandler;
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
@@ -8,7 +7,7 @@ use crate::handlers::radarr_handlers::root_folders::RootFoldersHandler;
use crate::handlers::radarr_handlers::system::SystemHandler;
use crate::handlers::KeyEventHandler;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::{App, Key};
use crate::{matches_key, App, Key};
mod blocklist;
mod collections;
@@ -65,6 +64,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
true
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -109,11 +112,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) {
let key_ref = key;
match key_ref {
_ if key == DEFAULT_KEYBINDINGS.left.key => {
_ if matches_key!(left, key, app.ignore_special_keys_for_textbox_input) => {
app.data.radarr_data.main_tabs.previous();
app.pop_and_push_navigation_stack(app.data.radarr_data.main_tabs.get_active_route());
}
_ if key == DEFAULT_KEYBINDINGS.right.key => {
_ if matches_key!(right, key, app.ignore_special_keys_for_textbox_input) => {
app.data.radarr_data.main_tabs.next();
app.pop_and_push_navigation_stack(app.data.radarr_data.main_tabs.get_active_route());
}
@@ -139,7 +142,7 @@ macro_rules! search_table {
};
$app.data.radarr_data.is_searching = false;
$app.should_ignore_quit_key = false;
$app.ignore_special_keys_for_textbox_input = false;
if search_index.is_some() {
$app.pop_navigation_stack();
@@ -166,7 +169,7 @@ macro_rules! search_table {
};
$app.data.radarr_data.is_searching = false;
$app.should_ignore_quit_key = false;
$app.ignore_special_keys_for_textbox_input = false;
if search_index.is_some() {
$app.pop_navigation_stack();
@@ -275,7 +275,7 @@ pub(in crate::handlers::radarr_handlers) mod utils {
audio_stream_count: 1,
video_bit_depth: 10,
video_bitrate: 0,
video_codec: "x265".to_owned(),
video_codec: Some("x265".to_owned()),
video_fps: Number::from_f64(23.976).unwrap(),
resolution: "1920x804".to_owned(),
run_time: "2:00:00".to_owned(),
@@ -46,6 +46,76 @@ mod tests {
assert_eq!(app.get_current_route(), right_block.into());
}
#[rstest]
#[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Collections)]
#[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Downloads)]
#[case(2, ActiveRadarrBlock::Collections, ActiveRadarrBlock::Blocklist)]
#[case(3, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::RootFolders)]
#[case(4, ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Indexers)]
#[case(5, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)]
#[case(6, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)]
fn test_radarr_handler_change_tab_left_right_keys_alt_navigation(
#[case] index: usize,
#[case] left_block: ActiveRadarrBlock,
#[case] right_block: ActiveRadarrBlock,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = false;
app.data.radarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
left_block.into()
);
assert_eq!(app.get_current_route(), left_block.into());
app.data.radarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
right_block.into()
);
assert_eq!(app.get_current_route(), right_block.into());
}
#[rstest]
#[case(0, ActiveRadarrBlock::Movies)]
#[case(1, ActiveRadarrBlock::Collections)]
#[case(2, ActiveRadarrBlock::Downloads)]
#[case(3, ActiveRadarrBlock::Blocklist)]
#[case(4, ActiveRadarrBlock::RootFolders)]
#[case(5, ActiveRadarrBlock::Indexers)]
#[case(6, ActiveRadarrBlock::System)]
fn test_radarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
#[case] index: usize,
#[case] block: ActiveRadarrBlock,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(block.into());
app.data.radarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
block.into()
);
assert_eq!(app.get_current_route(), block.into());
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
block.into()
);
assert_eq!(app.get_current_route(), block.into());
}
#[rstest]
fn test_delegates_system_blocks_to_system_handler(
#[values(
@@ -217,6 +287,25 @@ mod tests {
})
}
#[rstest]
fn test_radarr_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = RadarrHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::Movies,
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_radarr_handler_is_ready() {
let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
@@ -8,7 +7,9 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_F
use crate::models::servarr_models::{AddRootFolderBody, RootFolder};
use crate::models::HorizontallyScrollableText;
use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys};
use crate::{
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
};
#[cfg(test)]
#[path = "root_folders_handler_tests.rs"]
@@ -68,6 +69,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
ROOT_FOLDERS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -168,7 +173,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
self.build_add_root_folder_body(),
));
self.app.data.radarr_data.prompt_confirm = true;
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
self.app.pop_navigation_stack();
}
_ => (),
@@ -181,7 +186,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
self.app.pop_navigation_stack();
self.app.data.radarr_data.edit_root_folder = None;
self.app.data.radarr_data.prompt_confirm = false;
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveRadarrBlock::DeleteRootFolderPrompt => {
self.app.pop_navigation_stack();
@@ -195,15 +200,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
let key = self.key;
match self.active_radarr_block {
ActiveRadarrBlock::RootFolders => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ if key == DEFAULT_KEYBINDINGS.add.key => {
_ if matches_key!(add, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
self.app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
_ => (),
},
@@ -215,7 +220,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
)
}
ActiveRadarrBlock::DeleteRootFolderPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteRootFolder(self.extract_root_folder_id()));
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -262,7 +263,7 @@ mod tests {
.set_items(vec![RootFolder::default()]);
app.data.radarr_data.edit_root_folder = Some("Test".into());
app.data.radarr_data.prompt_confirm = true;
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
@@ -275,7 +276,7 @@ mod tests {
.handle();
assert!(app.data.radarr_data.prompt_confirm);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::AddRootFolder(expected_add_root_folder_body))
@@ -291,7 +292,7 @@ mod tests {
let mut app = App::test_default();
app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
app.data.radarr_data.prompt_confirm = false;
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
@@ -304,7 +305,7 @@ mod tests {
.handle();
assert!(!app.data.radarr_data.prompt_confirm);
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.prompt_confirm_action.is_none());
assert_eq!(
app.get_current_route(),
@@ -406,7 +407,7 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
app.data.radarr_data.edit_root_folder = Some("/nfs/test".into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
RootFoldersHandler::new(
ESC_KEY,
@@ -423,7 +424,7 @@ mod tests {
assert!(app.data.radarr_data.edit_root_folder.is_none());
assert!(!app.data.radarr_data.prompt_confirm);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
}
#[rstest]
@@ -472,7 +473,7 @@ mod tests {
app.get_current_route(),
ActiveRadarrBlock::AddRootFolderPrompt.into()
);
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.edit_root_folder.is_some());
}
@@ -499,7 +500,7 @@ mod tests {
app.get_current_route(),
ActiveRadarrBlock::RootFolders.into()
);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.edit_root_folder.is_none());
}
@@ -589,7 +590,7 @@ mod tests {
app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
RootFoldersHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::AddRootFolderPrompt,
None,
@@ -598,7 +599,7 @@ mod tests {
assert_str_eq!(
app.data.radarr_data.edit_root_folder.as_ref().unwrap().text,
"h"
"a"
);
}
@@ -644,6 +645,25 @@ mod tests {
})
}
#[rstest]
fn test_root_folders_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = RootFoldersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_build_add_root_folder_body() {
let mut app = App::test_default();
+10 -6
View File
@@ -1,9 +1,9 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler;
use crate::handlers::{handle_clear_errors, KeyEventHandler};
use crate::matches_key;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::Scrollable;
@@ -35,6 +35,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b
SystemDetailsHandler::accepts(active_block) || active_block == ActiveRadarrBlock::System
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -86,15 +90,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b
if self.active_radarr_block == ActiveRadarrBlock::System {
let key = self.key;
match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ if key == DEFAULT_KEYBINDINGS.events.key => {
_ if matches_key!(events, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemQueuedEvents.into());
}
_ if key == DEFAULT_KEYBINDINGS.logs.key => {
_ if matches_key!(logs, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemLogs.into());
@@ -106,12 +110,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b
.set_items(self.app.data.radarr_data.logs.items.to_vec());
self.app.data.radarr_data.log_details.scroll_to_bottom();
}
_ if key == DEFAULT_KEYBINDINGS.tasks.key => {
_ if matches_key!(tasks, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into());
}
_ if key == DEFAULT_KEYBINDINGS.update.key => {
_ if matches_key!(update, key) => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into());
@@ -1,7 +1,7 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::matches_key;
use crate::models::radarr_models::RadarrTaskName;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::models::stateful_list::StatefulList;
@@ -36,6 +36,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
SYSTEM_DETAILS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -114,7 +118,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
match self.active_radarr_block {
ActiveRadarrBlock::SystemLogs => match self.key {
_ if key == DEFAULT_KEYBINDINGS.left.key => {
_ if matches_key!(left, key) => {
self
.app
.data
@@ -124,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
.iter()
.for_each(|log| log.scroll_right());
}
_ if key == DEFAULT_KEYBINDINGS.right.key => {
_ if matches_key!(right, key) => {
self
.app
.data
@@ -178,14 +182,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
}
fn handle_char_key_event(&mut self) {
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_radarr_block)
&& self.key == DEFAULT_KEYBINDINGS.refresh.key
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_radarr_block) && matches_key!(refresh, self.key)
{
self.app.should_refresh = true;
}
if self.active_radarr_block == ActiveRadarrBlock::SystemTaskStartConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, self.key)
{
self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -938,6 +939,25 @@ mod tests {
})
}
#[rstest]
fn test_system_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SystemDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_task_name() {
let mut app = App::test_default();
@@ -450,6 +450,25 @@ mod tests {
})
}
#[rstest]
fn test_system_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SystemHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_system_handler_is_not_ready_when_loading() {
let mut app = App::test_default();
@@ -4,6 +4,7 @@ mod tests {
use chrono::DateTime;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -513,6 +514,25 @@ mod tests {
})
}
#[rstest]
fn test_blocklist_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = BlocklistHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_blocklist_item_id() {
let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
@@ -9,6 +7,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, BLOCKL
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"]
@@ -51,6 +50,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
BLOCKLIST_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -143,10 +146,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
let key = self.key;
match self.active_sonarr_block {
ActiveSonarrBlock::Blocklist => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ if key == DEFAULT_KEYBINDINGS.clear.key => {
_ if matches_key!(clear, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::BlocklistClearAllItemsPrompt.into());
@@ -154,7 +157,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
_ => (),
},
ActiveSonarrBlock::DeleteBlocklistItemPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteBlocklistItem(
self.extract_blocklist_item_id(),
@@ -164,7 +167,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
}
}
ActiveSonarrBlock::BlocklistClearAllItemsPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::ClearBlocklist);
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -389,6 +390,25 @@ mod tests {
})
}
#[rstest]
fn test_downloads_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = DownloadsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_download_id() {
let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
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"]
@@ -47,6 +46,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
DOWNLOADS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -130,18 +133,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
let key = self.key;
match self.active_sonarr_block {
ActiveSonarrBlock::Downloads => match self.key {
_ if key == DEFAULT_KEYBINDINGS.update.key => {
_ if matches_key!(update, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::UpdateDownloadsPrompt.into());
}
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ => (),
},
ActiveSonarrBlock::DeleteDownloadPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action =
Some(SonarrEvent::DeleteDownload(self.extract_download_id()));
@@ -150,7 +153,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
}
}
ActiveSonarrBlock::UpdateDownloadsPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateDownloads);
@@ -4,6 +4,7 @@ mod tests {
use chrono::DateTime;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -306,6 +307,25 @@ mod tests {
})
}
#[rstest]
fn test_history_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = HistoryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_history_handler_not_ready_when_loading() {
let mut app = App::test_default();
+6 -3
View File
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, KeyEventHandler};
@@ -9,6 +7,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, HISTOR
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"]
@@ -52,6 +51,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, '
HISTORY_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -110,7 +113,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, '
let key = self.key;
if self.active_sonarr_block == ActiveSonarrBlock::History {
match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ => (),
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -6,7 +5,9 @@ use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams;
use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys};
use crate::{
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
};
#[cfg(test)]
#[path = "edit_indexer_handler_tests.rs"]
@@ -64,6 +65,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
EDIT_INDEXER_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -355,7 +360,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
| ActiveSonarrBlock::EditIndexerSeedRatioInput
| ActiveSonarrBlock::EditIndexerTagsInput => {
self.app.push_navigation_stack(selected_block.into());
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
ActiveSonarrBlock::EditIndexerPriorityInput => self
.app
@@ -401,7 +406,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
| ActiveSonarrBlock::EditIndexerSeedRatioInput
| ActiveSonarrBlock::EditIndexerTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveSonarrBlock::EditIndexerPriorityInput => self.app.pop_navigation_stack(),
_ => (),
@@ -422,7 +427,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
| ActiveSonarrBlock::EditIndexerPriorityInput
| ActiveSonarrBlock::EditIndexerTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => self.app.pop_navigation_stack(),
}
@@ -503,7 +508,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
ActiveSonarrBlock::EditIndexerPrompt => {
if self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::EditIndexerConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, self.key)
{
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action =
@@ -10,6 +10,7 @@ mod tests {
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams;
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
mod test_handle_scroll_up_and_down {
@@ -1002,7 +1003,7 @@ mod tests {
.handle();
assert_eq!(app.get_current_route(), block.into());
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
}
#[test]
@@ -1027,7 +1028,7 @@ mod tests {
app.get_current_route(),
ActiveSonarrBlock::EditIndexerPriorityInput.into()
);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
}
#[test]
@@ -1193,7 +1194,7 @@ mod tests {
fn test_edit_indexer_name_input_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal {
name: "Test".into(),
..EditIndexerModal::default()
@@ -1209,7 +1210,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.sonarr_data
@@ -1229,7 +1230,7 @@ mod tests {
fn test_edit_indexer_url_input_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal {
url: "Test".into(),
..EditIndexerModal::default()
@@ -1245,7 +1246,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.sonarr_data
@@ -1265,7 +1266,7 @@ mod tests {
fn test_edit_indexer_api_key_input_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal {
api_key: "Test".into(),
..EditIndexerModal::default()
@@ -1281,7 +1282,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.sonarr_data
@@ -1301,7 +1302,7 @@ mod tests {
fn test_edit_indexer_seed_ratio_input_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal {
seed_ratio: "Test".into(),
..EditIndexerModal::default()
@@ -1317,7 +1318,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.sonarr_data
@@ -1337,7 +1338,7 @@ mod tests {
fn test_edit_indexer_tags_input_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal {
tags: "Test".into(),
..EditIndexerModal::default()
@@ -1353,7 +1354,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.sonarr_data
@@ -1417,12 +1418,12 @@ mod tests {
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.push_navigation_stack(active_sonarr_block.into());
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
EditIndexerHandler::new(ESC_KEY, &mut app, active_sonarr_block, None).handle();
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.data.sonarr_data.edit_indexer_modal,
Some(EditIndexerModal::default())
@@ -1597,7 +1598,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveSonarrBlock::EditIndexerNameInput,
None,
@@ -1613,7 +1614,7 @@ mod tests {
.unwrap()
.name
.text,
"h"
"a"
);
}
@@ -1624,7 +1625,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveSonarrBlock::EditIndexerUrlInput,
None,
@@ -1640,7 +1641,7 @@ mod tests {
.unwrap()
.url
.text,
"h"
"a"
);
}
@@ -1651,7 +1652,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveSonarrBlock::EditIndexerApiKeyInput,
None,
@@ -1667,7 +1668,7 @@ mod tests {
.unwrap()
.api_key
.text,
"h"
"a"
);
}
@@ -1678,7 +1679,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveSonarrBlock::EditIndexerSeedRatioInput,
None,
@@ -1694,7 +1695,7 @@ mod tests {
.unwrap()
.seed_ratio
.text,
"h"
"a"
);
}
@@ -1705,7 +1706,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveSonarrBlock::EditIndexerTagsInput,
None,
@@ -1721,7 +1722,7 @@ mod tests {
.unwrap()
.tags
.text,
"h"
"a"
);
}
@@ -1793,6 +1794,25 @@ mod tests {
})
}
#[rstest]
fn test_edit_indexer_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EditIndexerHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_build_edit_indexer_params() {
let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_prompt_left_right_keys;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS,
};
use crate::models::sonarr_models::IndexerSettings;
use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_prompt_left_right_keys, matches_key};
#[cfg(test)]
#[path = "edit_indexer_settings_handler_tests.rs"]
@@ -37,6 +36,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl
INDEXER_SETTINGS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -183,7 +186,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl
if self.active_sonarr_block == ActiveSonarrBlock::AllIndexerSettingsPrompt
&& self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::IndexerSettingsConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, self.key)
{
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditAllIndexerSettings(
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -521,6 +522,25 @@ mod tests {
})
}
#[rstest]
fn test_indexer_settings_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = IndexerSettingsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_build_edit_indexer_settings_params() {
let mut app = App::test_default();
@@ -643,6 +643,25 @@ mod tests {
})
}
#[rstest]
fn test_indexers_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = IndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_indexer_id() {
let mut app = App::test_default();
+10 -7
View File
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
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;
@@ -15,6 +13,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{
use crate::models::servarr_models::Indexer;
use crate::models::BlockSelectionState;
use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
mod edit_indexer_handler;
mod edit_indexer_settings_handler;
@@ -70,6 +69,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
|| INDEXERS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -168,20 +171,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
let key = self.key;
match self.active_sonarr_block {
ActiveSonarrBlock::Indexers => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ if key == DEFAULT_KEYBINDINGS.test.key => {
_ if matches_key!(test, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::TestIndexer.into());
}
_ if key == DEFAULT_KEYBINDINGS.test_all.key => {
_ if matches_key!(test_all, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::TestAllIndexers.into());
}
_ if key == DEFAULT_KEYBINDINGS.settings.key => {
_ if matches_key!(settings, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into());
@@ -191,7 +194,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
_ => (),
},
ActiveSonarrBlock::DeleteIndexerPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action =
Some(SonarrEvent::DeleteIndexer(self.extract_indexer_id()));
@@ -48,6 +48,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for TestAllIndexersHandl
active_block == ActiveSonarrBlock::TestAllIndexers
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -7,6 +7,7 @@ mod tests {
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::stateful_table::StatefulTable;
use rstest::rstest;
use strum::IntoEnumIterator;
mod test_handle_esc {
@@ -48,6 +49,25 @@ mod tests {
});
}
#[rstest]
fn test_test_all_indexers_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = TestAllIndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_test_all_indexers_handler_is_not_ready_when_loading() {
let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::modals::AddSeriesModal;
@@ -9,7 +8,9 @@ use crate::models::sonarr_models::{AddSeriesBody, AddSeriesOptions, AddSeriesSea
use crate::models::stateful_table::StatefulTable;
use crate::models::{BlockSelectionState, Scrollable};
use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, App, Key};
use crate::{
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key, App, Key,
};
#[cfg(test)]
#[path = "add_series_handler_tests.rs"]
@@ -126,6 +127,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
ADD_SERIES_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -440,7 +445,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
self
.app
.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into());
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ if self.active_sonarr_block == ActiveSonarrBlock::AddSeriesSearchResults
&& self.app.data.sonarr_data.add_searched_series.is_some() =>
@@ -509,7 +514,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
.get_active_block()
.into(),
);
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
ActiveSonarrBlock::AddSeriesToggleUseSeasonFolder => {
self
@@ -538,7 +543,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
| ActiveSonarrBlock::AddSeriesSelectRootFolder => self.app.pop_navigation_stack(),
ActiveSonarrBlock::AddSeriesTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => (),
}
@@ -549,13 +554,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
ActiveSonarrBlock::AddSeriesSearchInput => {
self.app.pop_navigation_stack();
self.app.data.sonarr_data.add_series_search = None;
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveSonarrBlock::AddSeriesSearchResults
| ActiveSonarrBlock::AddSeriesEmptySearchResults => {
self.app.pop_navigation_stack();
self.app.data.sonarr_data.add_searched_series = None;
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
ActiveSonarrBlock::AddSeriesPrompt => {
self.app.pop_navigation_stack();
@@ -570,7 +575,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
| ActiveSonarrBlock::AddSeriesSelectRootFolder => self.app.pop_navigation_stack(),
ActiveSonarrBlock::AddSeriesTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => (),
}
@@ -609,7 +614,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
ActiveSonarrBlock::AddSeriesPrompt => {
if self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::AddSeriesConfirmPrompt
&& key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, key)
{
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action =
@@ -2,6 +2,7 @@
mod tests {
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -912,7 +913,7 @@ mod tests {
fn test_add_series_search_input_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.add_series_search = Some("test".into());
AddSeriesHandler::new(
@@ -923,7 +924,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.get_current_route(),
ActiveSonarrBlock::AddSeriesSearchResults.into()
@@ -936,7 +937,7 @@ mod tests {
app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
AddSeriesHandler::new(
SUBMIT_KEY,
@@ -946,7 +947,7 @@ mod tests {
)
.handle();
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.get_current_route(),
ActiveSonarrBlock::AddSeriesSearchInput.into()
@@ -1230,7 +1231,7 @@ mod tests {
assert_eq!(app.data.sonarr_data.prompt_confirm_action, None);
if selected_block == ActiveSonarrBlock::AddSeriesTagsInput {
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
}
}
@@ -1259,7 +1260,7 @@ mod tests {
);
if active_sonarr_block == ActiveSonarrBlock::AddSeriesTagsInput {
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
}
}
@@ -1336,7 +1337,7 @@ mod tests {
let mut app = App::test_default();
app.is_loading = is_ready;
app.data.sonarr_data = create_test_sonarr_data();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into());
@@ -1348,7 +1349,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into());
assert_eq!(app.data.sonarr_data.add_series_search, None);
}
@@ -1357,7 +1358,7 @@ mod tests {
fn test_add_series_input_esc() {
let mut app = App::test_default();
app.data.sonarr_data = create_test_sonarr_data();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesTagsInput.into());
@@ -1370,7 +1371,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.get_current_route(),
ActiveSonarrBlock::AddSeriesPrompt.into()
@@ -1403,7 +1404,7 @@ mod tests {
ActiveSonarrBlock::AddSeriesSearchInput.into()
);
assert!(app.data.sonarr_data.add_searched_series.is_none());
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
}
#[test]
@@ -1451,7 +1452,7 @@ mod tests {
fn test_add_series_tags_input_esc() {
let mut app = App::test_default();
app.data.sonarr_data = create_test_sonarr_data();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesTagsInput.into());
@@ -1464,7 +1465,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.get_current_route(),
ActiveSonarrBlock::AddSeriesPrompt.into()
@@ -1570,7 +1571,7 @@ mod tests {
app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
AddSeriesHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveSonarrBlock::AddSeriesSearchInput,
None,
@@ -1585,7 +1586,7 @@ mod tests {
.as_ref()
.unwrap()
.text,
"h"
"a"
);
}
@@ -1596,7 +1597,7 @@ mod tests {
app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default());
AddSeriesHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveSonarrBlock::AddSeriesTagsInput,
None,
@@ -1612,7 +1613,7 @@ mod tests {
.unwrap()
.tags
.text,
"h"
"a"
);
}
@@ -1714,6 +1715,25 @@ mod tests {
});
}
#[rstest]
fn test_add_series_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = AddSeriesHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_add_series_search_no_panic_on_none_search_result() {
let mut app = App::test_default();
@@ -1,9 +1,10 @@
use crate::models::sonarr_models::DeleteSeriesParams;
use crate::network::sonarr_network::SonarrEvent;
use crate::{
app::{key_binding::DEFAULT_KEYBINDINGS, App},
app::App,
event::Key,
handlers::{handle_prompt_toggle, KeyEventHandler},
matches_key,
models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS},
};
@@ -38,6 +39,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler<
DELETE_SERIES_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -123,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler<
if self.active_sonarr_block == ActiveSonarrBlock::DeleteSeriesPrompt
&& self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::DeleteSeriesConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, self.key)
{
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -320,6 +321,25 @@ mod tests {
});
}
#[rstest]
fn test_delete_series_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = DeleteSeriesHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_build_delete_series_params() {
let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -7,7 +6,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_S
use crate::models::sonarr_models::EditSeriesParams;
use crate::models::Scrollable;
use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
#[cfg(test)]
#[path = "edit_series_handler_tests.rs"]
@@ -83,6 +82,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
EDIT_SERIES_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -342,7 +345,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
)
.into(),
);
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
ActiveSonarrBlock::EditSeriesToggleMonitored => {
self
@@ -392,7 +395,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
| ActiveSonarrBlock::EditSeriesSelectLanguageProfile => self.app.pop_navigation_stack(),
ActiveSonarrBlock::EditSeriesPathInput | ActiveSonarrBlock::EditSeriesTagsInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => (),
}
@@ -402,7 +405,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
match self.active_sonarr_block {
ActiveSonarrBlock::EditSeriesTagsInput | ActiveSonarrBlock::EditSeriesPathInput => {
self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveSonarrBlock::EditSeriesPrompt => {
self.app.pop_navigation_stack();
@@ -450,7 +453,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
ActiveSonarrBlock::EditSeriesPrompt => {
if self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::EditSeriesConfirmPrompt
&& key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, key)
{
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action =
@@ -2,6 +2,7 @@
mod tests {
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -677,7 +678,7 @@ mod tests {
#[test]
fn test_edit_series_path_input_submit() {
let mut app = App::test_default();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal {
path: "Test Path".into(),
..EditSeriesModal::default()
@@ -694,7 +695,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.sonarr_data
@@ -713,7 +714,7 @@ mod tests {
#[test]
fn test_edit_series_tags_input_submit() {
let mut app = App::test_default();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal {
tags: "Test Tags".into(),
..EditSeriesModal::default()
@@ -730,7 +731,7 @@ mod tests {
)
.handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app
.data
.sonarr_data
@@ -1010,7 +1011,7 @@ mod tests {
if selected_block == ActiveSonarrBlock::EditSeriesPathInput
|| selected_block == ActiveSonarrBlock::EditSeriesTagsInput
{
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
}
}
@@ -1049,7 +1050,7 @@ mod tests {
.into()
);
assert_eq!(app.data.sonarr_data.prompt_confirm_action, None);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
}
#[rstest]
@@ -1085,7 +1086,7 @@ mod tests {
if active_sonarr_block == ActiveSonarrBlock::EditSeriesPathInput
|| active_sonarr_block == ActiveSonarrBlock::EditSeriesTagsInput
{
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
}
}
}
@@ -1111,14 +1112,14 @@ mod tests {
) {
let mut app = App::test_default();
app.data.sonarr_data = create_test_sonarr_data();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::EditSeriesPrompt.into());
app.push_navigation_stack(active_sonarr_block.into());
EditSeriesHandler::new(ESC_KEY, &mut app, active_sonarr_block, None).handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.get_current_route(),
ActiveSonarrBlock::EditSeriesPrompt.into()
@@ -1243,7 +1244,7 @@ mod tests {
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default());
EditSeriesHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveSonarrBlock::EditSeriesPathInput,
None,
@@ -1259,7 +1260,7 @@ mod tests {
.unwrap()
.path
.text,
"h"
"a"
);
}
@@ -1270,7 +1271,7 @@ mod tests {
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default());
EditSeriesHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveSonarrBlock::EditSeriesTagsInput,
None,
@@ -1286,7 +1287,7 @@ mod tests {
.unwrap()
.tags
.text,
"h"
"a"
);
}
@@ -1368,6 +1369,25 @@ mod tests {
});
}
#[rstest]
fn test_edit_series_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EditSeriesHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_build_edit_series_params() {
let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::library::season_details_handler::releases_sorting_options;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS};
use crate::models::sonarr_models::{SonarrHistoryItem, SonarrRelease, SonarrReleaseDownloadBody};
use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)]
#[path = "episode_details_handler_tests.rs"]
@@ -88,6 +87,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
EPISODE_DETAILS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -142,7 +145,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
| ActiveSonarrBlock::EpisodeHistory
| ActiveSonarrBlock::EpisodeFile
| ActiveSonarrBlock::ManualEpisodeSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.left.key => {
_ if matches_key!(left, self.key) => {
self
.app
.data
@@ -170,7 +173,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
.get_active_route(),
);
}
_ if self.key == DEFAULT_KEYBINDINGS.right.key => {
_ if matches_key!(right, self.key) => {
self
.app
.data
@@ -306,21 +309,19 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
| ActiveSonarrBlock::EpisodeHistory
| ActiveSonarrBlock::EpisodeFile
| ActiveSonarrBlock::ManualEpisodeSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, self.key) => {
self
.app
.pop_and_push_navigation_stack(self.active_sonarr_block.into());
}
_ if self.key == DEFAULT_KEYBINDINGS.auto_search.key => {
_ if matches_key!(auto_search, self.key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchEpisodePrompt.into());
}
_ => (),
},
ActiveSonarrBlock::AutomaticallySearchEpisodePrompt
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
ActiveSonarrBlock::AutomaticallySearchEpisodePrompt if matches_key!(confirm, key) => {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::TriggerAutomaticEpisodeSearch(self.extract_episode_id()),
@@ -328,9 +329,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
self.app.pop_navigation_stack();
}
ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt if matches_key!(confirm, key) => {
if self.app.data.sonarr_data.prompt_confirm {
let SonarrRelease {
guid, indexer_id, ..
@@ -625,6 +625,25 @@ mod tests {
});
}
#[rstest]
fn test_episode_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EpisodeDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_episode_id() {
let mut app = App::test_default();
@@ -316,7 +316,7 @@ mod tests {
app.get_current_route(),
ActiveSonarrBlock::AddSeriesSearchInput.into()
);
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.sonarr_data.add_series_search.is_some());
}
@@ -340,7 +340,7 @@ mod tests {
.handle();
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.sonarr_data.add_series_search.is_none());
}
@@ -827,6 +827,25 @@ mod tests {
});
}
#[rstest]
fn test_library_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = LibraryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_library_handler_not_ready_when_loading() {
let mut app = App::test_default();
+11 -7
View File
@@ -8,6 +8,7 @@ use crate::{
event::Key,
handle_table_events,
handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler},
matches_key,
models::{
servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, DELETE_SERIES_SELECTION_BLOCKS, EDIT_SERIES_SELECTION_BLOCKS,
@@ -21,7 +22,6 @@ use crate::{
};
use super::handle_change_tab_left_right_keys;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
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;
@@ -102,6 +102,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
|| LIBRARY_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -182,7 +186,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
let key = self.key;
match self.active_sonarr_block {
ActiveSonarrBlock::Series => match self.key {
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
_ if matches_key!(edit, key) => {
self.app.push_navigation_stack(
(
ActiveSonarrBlock::EditSeriesPrompt,
@@ -194,25 +198,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
self.app.data.sonarr_data.selected_block =
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
}
_ if key == DEFAULT_KEYBINDINGS.add.key => {
_ if matches_key!(add, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into());
self.app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
_ if key == DEFAULT_KEYBINDINGS.update.key => {
_ if matches_key!(update, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::UpdateAllSeriesPrompt.into());
}
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ => (),
},
ActiveSonarrBlock::UpdateAllSeriesPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateAllSeries);
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::history::history_sorting_options;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -12,6 +10,7 @@ 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)]
@@ -140,6 +139,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
SEASON_DETAILS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -193,7 +196,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
ActiveSonarrBlock::SeasonDetails
| ActiveSonarrBlock::SeasonHistory
| ActiveSonarrBlock::ManualSeasonSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.left.key => {
_ if matches_key!(left, self.key) => {
self
.app
.data
@@ -215,7 +218,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
.get_active_route(),
);
}
_ if self.key == DEFAULT_KEYBINDINGS.right.key => {
_ if matches_key!(right, self.key) => {
self
.app
.data
@@ -378,7 +381,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
fn handle_char_key_event(&mut self) {
let key = self.key;
match self.active_sonarr_block {
ActiveSonarrBlock::SeasonDetails if self.key == DEFAULT_KEYBINDINGS.toggle_monitoring.key => {
ActiveSonarrBlock::SeasonDetails if matches_key!(toggle_monitoring, self.key) => {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::ToggleEpisodeMonitoring(self.extract_episode_id()),
@@ -391,21 +394,19 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
ActiveSonarrBlock::SeasonDetails
| ActiveSonarrBlock::SeasonHistory
| ActiveSonarrBlock::ManualSeasonSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, self.key) => {
self
.app
.pop_and_push_navigation_stack(self.active_sonarr_block.into());
}
_ if self.key == DEFAULT_KEYBINDINGS.auto_search.key => {
_ if matches_key!(auto_search, self.key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeasonPrompt.into());
}
_ => (),
},
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt if matches_key!(confirm, key) => {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::TriggerAutomaticSeasonSearch(self.extract_series_id_season_number_tuple()),
@@ -413,7 +414,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
self.app.pop_navigation_stack();
}
ActiveSonarrBlock::DeleteEpisodeFilePrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
ActiveSonarrBlock::DeleteEpisodeFilePrompt if matches_key!(confirm, key) => {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteEpisodeFile(
self.extract_episode_file_id(),
@@ -421,9 +422,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
self.app.pop_navigation_stack();
}
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt if matches_key!(confirm, key) => {
self.app.data.sonarr_data.prompt_confirm = true;
let SonarrRelease {
guid, indexer_id, ..
@@ -789,6 +789,25 @@ mod tests {
});
}
#[rstest]
fn test_season_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SeasonDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_episode_file_id() {
let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::history::history_sorting_options;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -11,6 +9,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{
use crate::models::sonarr_models::{Season, SonarrHistoryItem};
use crate::models::BlockSelectionState;
use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)]
#[path = "series_details_handler_tests.rs"]
@@ -91,6 +90,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
SERIES_DETAILS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -130,7 +133,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
fn handle_left_right_action(&mut self) {
match self.active_sonarr_block {
ActiveSonarrBlock::SeriesDetails | ActiveSonarrBlock::SeriesHistory => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.left.key => {
_ if matches_key!(left, self.key) => {
self.app.data.sonarr_data.series_info_tabs.previous();
self.app.pop_and_push_navigation_stack(
self
@@ -141,7 +144,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
.get_active_route(),
);
}
_ if self.key == DEFAULT_KEYBINDINGS.right.key => {
_ if matches_key!(right, self.key) => {
self.app.data.sonarr_data.series_info_tabs.next();
self.app.pop_and_push_navigation_stack(
self
@@ -250,20 +253,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
let key = self.key;
match self.active_sonarr_block {
ActiveSonarrBlock::SeriesDetails => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => self
_ if matches_key!(refresh, key) => self
.app
.pop_and_push_navigation_stack(self.active_sonarr_block.into()),
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => {
_ if matches_key!(auto_search, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into());
}
_ if key == DEFAULT_KEYBINDINGS.update.key => {
_ if matches_key!(update, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into());
}
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
_ if matches_key!(edit, key) => {
self.app.push_navigation_stack(
(
ActiveSonarrBlock::EditSeriesPrompt,
@@ -275,7 +278,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
self.app.data.sonarr_data.selected_block =
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
}
_ if key == DEFAULT_KEYBINDINGS.toggle_monitoring.key => {
_ if matches_key!(toggle_monitoring, key) => {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::ToggleSeasonMonitoring(self.extract_series_id_season_number_tuple()),
@@ -288,15 +291,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
_ => (),
},
ActiveSonarrBlock::SeriesHistory => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => self
_ if matches_key!(refresh, key) => self
.app
.pop_and_push_navigation_stack(self.active_sonarr_block.into()),
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => {
_ if matches_key!(auto_search, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into());
}
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
_ if matches_key!(edit, key) => {
self.app.push_navigation_stack(
(
ActiveSonarrBlock::EditSeriesPrompt,
@@ -308,7 +311,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
self.app.data.sonarr_data.selected_block =
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
}
_ if key == DEFAULT_KEYBINDINGS.update.key => {
_ if matches_key!(update, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into());
@@ -316,7 +319,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
_ => (),
},
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::TriggerAutomaticSeriesSearch(self.extract_series_id()),
@@ -611,6 +611,25 @@ mod tests {
});
}
#[rstest]
fn test_series_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SeriesDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_series_id_season_number_tuple() {
let mut app = App::test_default();
+7 -5
View File
@@ -7,9 +7,7 @@ use root_folders::RootFoldersHandler;
use system::SystemHandler;
use crate::{
app::{key_binding::DEFAULT_KEYBINDINGS, App},
event::Key,
models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
app::App, event::Key, matches_key, models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
};
use super::KeyEventHandler;
@@ -69,6 +67,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b
true
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -113,11 +115,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b
pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) {
let key_ref = key;
match key_ref {
_ if key == DEFAULT_KEYBINDINGS.left.key => {
_ if matches_key!(left, key, app.ignore_special_keys_for_textbox_input) => {
app.data.sonarr_data.main_tabs.previous();
app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route());
}
_ if key == DEFAULT_KEYBINDINGS.right.key => {
_ if matches_key!(right, key, app.ignore_special_keys_for_textbox_input) => {
app.data.sonarr_data.main_tabs.next();
app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route());
}
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
@@ -8,7 +7,9 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ROOT_F
use crate::models::servarr_models::{AddRootFolderBody, RootFolder};
use crate::models::HorizontallyScrollableText;
use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys};
use crate::{
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
};
#[cfg(test)]
#[path = "root_folders_handler_tests.rs"]
@@ -66,6 +67,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
ROOT_FOLDERS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -166,7 +171,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
self.build_add_root_folder_body(),
));
self.app.data.sonarr_data.prompt_confirm = true;
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
self.app.pop_navigation_stack();
}
_ => (),
@@ -179,7 +184,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
self.app.pop_navigation_stack();
self.app.data.sonarr_data.edit_root_folder = None;
self.app.data.sonarr_data.prompt_confirm = false;
self.app.should_ignore_quit_key = false;
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveSonarrBlock::DeleteRootFolderPrompt => {
self.app.pop_navigation_stack();
@@ -193,15 +198,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
let key = self.key;
match self.active_sonarr_block {
ActiveSonarrBlock::RootFolders => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ if key == DEFAULT_KEYBINDINGS.add.key => {
_ if matches_key!(add, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into());
self.app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true;
self.app.ignore_special_keys_for_textbox_input = true;
}
_ => (),
},
@@ -213,7 +218,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
)
}
ActiveSonarrBlock::DeleteRootFolderPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key {
if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action =
Some(SonarrEvent::DeleteRootFolder(self.extract_root_folder_id()));
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -269,7 +270,7 @@ mod tests {
.set_items(vec![RootFolder::default()]);
app.data.sonarr_data.edit_root_folder = Some("Test".into());
app.data.sonarr_data.prompt_confirm = true;
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into());
@@ -282,7 +283,7 @@ mod tests {
.handle();
assert!(app.data.sonarr_data.prompt_confirm);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::AddRootFolder(expected_add_root_folder_body))
@@ -299,7 +300,7 @@ mod tests {
let mut app = App::test_default();
app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
app.data.sonarr_data.prompt_confirm = false;
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into());
@@ -312,7 +313,7 @@ mod tests {
.handle();
assert!(!app.data.sonarr_data.prompt_confirm);
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.sonarr_data.prompt_confirm_action.is_none());
assert_eq!(
app.get_current_route(),
@@ -414,7 +415,7 @@ mod tests {
app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into());
app.data.sonarr_data.edit_root_folder = Some("/nfs/test".into());
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
RootFoldersHandler::new(
ESC_KEY,
@@ -431,7 +432,7 @@ mod tests {
assert!(app.data.sonarr_data.edit_root_folder.is_none());
assert!(!app.data.sonarr_data.prompt_confirm);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
}
#[rstest]
@@ -481,7 +482,7 @@ mod tests {
app.get_current_route(),
ActiveSonarrBlock::AddRootFolderPrompt.into()
);
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.sonarr_data.edit_root_folder.is_some());
}
@@ -508,7 +509,7 @@ mod tests {
app.get_current_route(),
ActiveSonarrBlock::RootFolders.into()
);
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.sonarr_data.edit_root_folder.is_none());
}
@@ -600,7 +601,7 @@ mod tests {
app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
RootFoldersHandler::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveSonarrBlock::AddRootFolderPrompt,
None,
@@ -609,7 +610,7 @@ mod tests {
assert_str_eq!(
app.data.sonarr_data.edit_root_folder.as_ref().unwrap().text,
"h"
"a"
);
}
@@ -655,6 +656,25 @@ mod tests {
})
}
#[rstest]
fn test_root_folders_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = RootFoldersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_root_folder_id() {
let mut app = App::test_default();
@@ -297,7 +297,7 @@ pub(in crate::handlers::sonarr_handlers) mod utils {
audio_stream_count: 1,
video_bit_depth: 10,
video_bitrate: 0,
video_codec: "x265".to_owned(),
video_codec: Some("x265".to_owned()),
video_fps: Number::from_f64(23.976).unwrap(),
resolution: "1920x1080".to_owned(),
run_time: "23:51".to_owned(),
@@ -327,7 +327,7 @@ pub(in crate::handlers::sonarr_handlers) mod utils {
title: None,
season_number: 1,
monitored: true,
statistics: season_statistics(),
statistics: Some(season_statistics()),
}
}
@@ -9,6 +9,7 @@ mod tests {
use crate::test_handler_delegation;
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
#[rstest]
#[case(0, ActiveSonarrBlock::System, ActiveSonarrBlock::Downloads)]
@@ -45,6 +46,77 @@ mod tests {
assert_eq!(app.get_current_route(), right_block.into());
}
#[rstest]
#[case(0, ActiveSonarrBlock::System, ActiveSonarrBlock::Downloads)]
#[case(1, ActiveSonarrBlock::Series, ActiveSonarrBlock::Blocklist)]
#[case(2, ActiveSonarrBlock::Downloads, ActiveSonarrBlock::History)]
#[case(3, ActiveSonarrBlock::Blocklist, ActiveSonarrBlock::RootFolders)]
#[case(4, ActiveSonarrBlock::History, ActiveSonarrBlock::Indexers)]
#[case(5, ActiveSonarrBlock::RootFolders, ActiveSonarrBlock::System)]
#[case(6, ActiveSonarrBlock::Indexers, ActiveSonarrBlock::Series)]
fn test_sonarr_handler_change_tab_left_right_keys_alt_navigation(
#[case] index: usize,
#[case] left_block: ActiveSonarrBlock,
#[case] right_block: ActiveSonarrBlock,
) {
let mut app = App::test_default();
app.data.sonarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
assert_eq!(
app.data.sonarr_data.main_tabs.get_active_route(),
left_block.into()
);
assert_eq!(app.get_current_route(), left_block.into());
app.data.sonarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
assert_eq!(
app.data.sonarr_data.main_tabs.get_active_route(),
right_block.into()
);
assert_eq!(app.get_current_route(), right_block.into());
}
#[rstest]
#[case(0, ActiveSonarrBlock::Series)]
#[case(1, ActiveSonarrBlock::Downloads)]
#[case(2, ActiveSonarrBlock::Blocklist)]
#[case(3, ActiveSonarrBlock::History)]
#[case(4, ActiveSonarrBlock::RootFolders)]
#[case(5, ActiveSonarrBlock::Indexers)]
#[case(6, ActiveSonarrBlock::System)]
fn test_sonarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
#[case] index: usize,
#[case] block: ActiveSonarrBlock,
) {
let mut app = App::test_default();
app.push_navigation_stack(block.into());
app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
assert_eq!(
app.data.sonarr_data.main_tabs.get_active_route(),
block.into()
);
assert_eq!(app.get_current_route(), block.into());
app.data.sonarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
assert_eq!(
app.data.sonarr_data.main_tabs.get_active_route(),
block.into()
);
assert_eq!(app.get_current_route(), block.into());
}
#[rstest]
fn test_delegates_library_blocks_to_library_handler(
#[values(
@@ -59,10 +131,10 @@ mod tests {
ActiveSonarrBlock::AddSeriesSelectRootFolder,
ActiveSonarrBlock::AddSeriesSelectSeriesType,
ActiveSonarrBlock::AddSeriesTagsInput,
// ActiveSonarrBlock::AutomaticallySearchEpisodePrompt,
// ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
// ActiveSonarrBlock::AutomaticallySearchSeriesPrompt,
// ActiveSonarrBlock::DeleteEpisodeFilePrompt,
ActiveSonarrBlock::AutomaticallySearchEpisodePrompt,
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt,
ActiveSonarrBlock::DeleteEpisodeFilePrompt,
ActiveSonarrBlock::DeleteSeriesPrompt,
ActiveSonarrBlock::EditSeriesPrompt,
ActiveSonarrBlock::EditSeriesPathInput,
@@ -70,39 +142,36 @@ mod tests {
ActiveSonarrBlock::EditSeriesSelectQualityProfile,
ActiveSonarrBlock::EditSeriesSelectLanguageProfile,
ActiveSonarrBlock::EditSeriesTagsInput,
// ActiveSonarrBlock::EpisodeDetails,
// ActiveSonarrBlock::EpisodeFile,
// ActiveSonarrBlock::EpisodeHistory,
// ActiveSonarrBlock::EpisodesSortPrompt,
// ActiveSonarrBlock::FilterEpisodes,
// ActiveSonarrBlock::FilterEpisodesError,
ActiveSonarrBlock::EpisodeDetails,
ActiveSonarrBlock::EpisodeFile,
ActiveSonarrBlock::EpisodeHistory,
ActiveSonarrBlock::FilterSeries,
ActiveSonarrBlock::FilterSeriesError,
// ActiveSonarrBlock::FilterSeriesHistory,
// ActiveSonarrBlock::FilterSeriesHistoryError,
// ActiveSonarrBlock::ManualEpisodeSearch,
// ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt,
// ActiveSonarrBlock::ManualEpisodeSearchSortPrompt,
// ActiveSonarrBlock::ManualSeasonSearch,
// ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
// ActiveSonarrBlock::ManualSeasonSearchSortPrompt,
// ActiveSonarrBlock::SearchEpisodes,
// ActiveSonarrBlock::SearchEpisodesError,
// ActiveSonarrBlock::SearchSeason,
// ActiveSonarrBlock::SearchSeasonError,
ActiveSonarrBlock::FilterSeriesHistory,
ActiveSonarrBlock::FilterSeriesHistoryError,
ActiveSonarrBlock::ManualEpisodeSearch,
ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt,
ActiveSonarrBlock::ManualEpisodeSearchSortPrompt,
ActiveSonarrBlock::ManualSeasonSearch,
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
ActiveSonarrBlock::ManualSeasonSearchSortPrompt,
ActiveSonarrBlock::SearchEpisodes,
ActiveSonarrBlock::SearchEpisodesError,
ActiveSonarrBlock::SearchSeason,
ActiveSonarrBlock::SearchSeasonError,
ActiveSonarrBlock::SearchSeries,
ActiveSonarrBlock::SearchSeriesError,
// ActiveSonarrBlock::SearchSeriesHistory,
// ActiveSonarrBlock::SearchSeriesHistoryError,
// ActiveSonarrBlock::SeasonDetails,
ActiveSonarrBlock::SearchSeriesHistory,
ActiveSonarrBlock::SearchSeriesHistoryError,
ActiveSonarrBlock::SeasonDetails,
ActiveSonarrBlock::Series,
// ActiveSonarrBlock::SeriesDetails,
// ActiveSonarrBlock::SeriesHistory,
// ActiveSonarrBlock::SeriesHistorySortPrompt,
ActiveSonarrBlock::SeriesDetails,
ActiveSonarrBlock::SeriesHistory,
ActiveSonarrBlock::SeriesHistorySortPrompt,
ActiveSonarrBlock::SeriesSortPrompt,
ActiveSonarrBlock::UpdateAllSeriesPrompt,
// ActiveSonarrBlock::UpdateAndScanSeriesPrompt
// ActiveSonarrBlock::SeriesHistoryDetails,
ActiveSonarrBlock::UpdateAndScanSeriesPrompt,
ActiveSonarrBlock::SeriesHistoryDetails
)]
active_sonarr_block: ActiveSonarrBlock,
) {
@@ -222,4 +291,45 @@ mod tests {
active_sonarr_block
);
}
#[test]
fn test_sonarr_handler_accepts() {
ActiveSonarrBlock::iter().for_each(|active_sonarr_block| {
assert!(SonarrHandler::accepts(active_sonarr_block));
})
}
#[rstest]
fn test_sonarr_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SonarrHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_sonarr_handler_is_ready() {
let mut app = App::test_default();
app.is_loading = true;
let handler = SonarrHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert!(handler.is_ready());
}
}
+10 -6
View File
@@ -1,9 +1,9 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::sonarr_handlers::system::system_details_handler::SystemDetailsHandler;
use crate::handlers::{handle_clear_errors, KeyEventHandler};
use crate::matches_key;
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::Scrollable;
@@ -35,6 +35,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b
SystemDetailsHandler::accepts(active_block) || active_block == ActiveSonarrBlock::System
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -86,15 +90,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b
if self.active_sonarr_block == ActiveSonarrBlock::System {
let key = self.key;
match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ if key == DEFAULT_KEYBINDINGS.events.key => {
_ if matches_key!(events, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::SystemQueuedEvents.into());
}
_ if key == DEFAULT_KEYBINDINGS.logs.key => {
_ if matches_key!(logs, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::SystemLogs.into());
@@ -106,12 +110,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b
.set_items(self.app.data.sonarr_data.logs.items.to_vec());
self.app.data.sonarr_data.log_details.scroll_to_bottom();
}
_ if key == DEFAULT_KEYBINDINGS.tasks.key => {
_ if matches_key!(tasks, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into());
}
_ if key == DEFAULT_KEYBINDINGS.update.key => {
_ if matches_key!(update, key) => {
self
.app
.push_navigation_stack(ActiveSonarrBlock::SystemUpdates.into());
@@ -1,7 +1,7 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::matches_key;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::models::sonarr_models::SonarrTaskName;
use crate::models::stateful_list::StatefulList;
@@ -36,6 +36,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
SYSTEM_DETAILS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -114,7 +118,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
match self.active_sonarr_block {
ActiveSonarrBlock::SystemLogs => match self.key {
_ if key == DEFAULT_KEYBINDINGS.left.key => {
_ if matches_key!(left, key) => {
self
.app
.data
@@ -124,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
.iter()
.for_each(|log| log.scroll_right());
}
_ if key == DEFAULT_KEYBINDINGS.right.key => {
_ if matches_key!(right, key) => {
self
.app
.data
@@ -178,14 +182,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
}
fn handle_char_key_event(&mut self) {
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_sonarr_block)
&& self.key == DEFAULT_KEYBINDINGS.refresh.key
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_sonarr_block) && matches_key!(refresh, self.key)
{
self.app.should_refresh = true;
}
if self.active_sonarr_block == ActiveSonarrBlock::SystemTaskStartConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
&& matches_key!(confirm, self.key)
{
self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)]
mod tests {
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -960,6 +961,25 @@ mod tests {
})
}
#[rstest]
fn test_system_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SystemDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_task_name() {
let mut app = App::test_default();
@@ -456,6 +456,25 @@ mod tests {
})
}
#[rstest]
fn test_system_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SystemHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_system_handler_is_not_ready_when_loading() {
let mut app = App::test_default();
+17 -17
View File
@@ -42,17 +42,17 @@ macro_rules! handle_table_events {
fn [<handle_ $name _table_events>](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool {
if $self.is_ready() {
match $self.key {
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.up.key => $self.[<handle_ $name _table_scroll_up>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.down.key => $self.[<handle_ $name _table_scroll_down>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.home.key => $self.[<handle_ $name _table_home>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.end.key => $self.[<handle_ $name _table_end>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.left.key
|| $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.right.key =>
_ if $crate::matches_key!(up, $self.key, $self.ignore_special_keys()) => $self.[<handle_ $name _table_scroll_up>](config),
_ if $crate::matches_key!(down, $self.key, $self.ignore_special_keys()) => $self.[<handle_ $name _table_scroll_down>](config),
_ if $crate::matches_key!(home, $self.key) => $self.[<handle_ $name _table_home>](config),
_ if $crate::matches_key!(end, $self.key) => $self.[<handle_ $name _table_end>](config),
_ if $crate::matches_key!(left, $self.key, $self.ignore_special_keys())
|| $crate::matches_key!(right, $self.key, $self.ignore_special_keys()) =>
{
$self.[<handle_ $name _table_left_right>](config)
}
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.submit.key => $self.[<handle_ $name _table_submit>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.esc.key => $self.[<handle_ $name _table_esc>](config),
_ if $crate::matches_key!(submit, $self.key) => $self.[<handle_ $name _table_submit>](config),
_ if $crate::matches_key!(esc, $self.key) => $self.[<handle_ $name _table_esc>](config),
_ if config.searching_block.is_some()
&& $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() =>
{
@@ -63,11 +63,11 @@ macro_rules! handle_table_events {
{
$self.[<handle_ $name _table_filter_box_input>]()
}
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.filter.key
_ if $crate::matches_key!(filter, $self.key)
&& config.filtering_block.is_some() => $self.[<handle_ $name _table_filter_key>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.search.key
_ if $crate::matches_key!(search, $self.key)
&& config.searching_block.is_some() => $self.[<handle_ $name _table_search_key>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.sort.key
_ if $crate::matches_key!(sort, $self.key)
&& config.sorting_block.is_some() => $self.[<handle_ $name _table_sort_key>](config),
_ => false,
}
@@ -244,7 +244,7 @@ macro_rules! handle_table_events {
&& $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() =>
{
$self.app.pop_navigation_stack();
$self.app.should_ignore_quit_key = false;
$self.app.ignore_special_keys_for_textbox_input = false;
if $table.search.is_some() {
let search_field_fn = config
@@ -267,7 +267,7 @@ macro_rules! handle_table_events {
&& $self.app.get_current_route() == *config.filtering_block.as_ref().unwrap() =>
{
$self.app.pop_navigation_stack();
$self.app.should_ignore_quit_key = false;
$self.app.ignore_special_keys_for_textbox_input = false;
if $table.filter.is_some() {
let filter_field_fn = config
@@ -305,7 +305,7 @@ macro_rules! handle_table_events {
{
$self.app.pop_navigation_stack();
$table.reset_search();
$self.app.should_ignore_quit_key = false;
$self.app.ignore_special_keys_for_textbox_input = false;
true
}
_ if (config.filtering_block.is_some()
@@ -315,7 +315,7 @@ macro_rules! handle_table_events {
{
$self.app.pop_navigation_stack();
$table.reset_filter();
$self.app.should_ignore_quit_key = false;
$self.app.ignore_special_keys_for_textbox_input = false;
true
}
_ if config.table_block == $self.app.get_current_route()
@@ -335,7 +335,7 @@ macro_rules! handle_table_events {
.push_navigation_stack(config.filtering_block.expect("Filtering block is undefined").into());
$table.reset_filter();
$table.filter = Some($crate::models::HorizontallyScrollableText::default());
$self.app.should_ignore_quit_key = true;
$self.app.ignore_special_keys_for_textbox_input = true;
true
} else {
@@ -349,7 +349,7 @@ macro_rules! handle_table_events {
.app
.push_navigation_stack(config.searching_block.expect("Searching block is undefined"));
$table.search = Some($crate::models::HorizontallyScrollableText::default());
$self.app.should_ignore_quit_key = true;
$self.app.ignore_special_keys_for_textbox_input = true;
true
} else {
+22 -18
View File
@@ -48,6 +48,10 @@ mod tests {
true
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
@@ -648,7 +652,7 @@ mod tests {
TableHandlerUnit::new(SUBMIT_KEY, &mut app, ActiveRadarrBlock::FilterMovies, None).handle();
assert!(app.data.radarr_data.movies.filtered_items.is_some());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(
app
.data
@@ -684,7 +688,7 @@ mod tests {
TableHandlerUnit::new(SUBMIT_KEY, &mut app, ActiveRadarrBlock::FilterMovies, None).handle();
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.movies.filtered_items.is_none());
assert_eq!(
app.get_current_route(),
@@ -735,7 +739,7 @@ mod tests {
active_radarr_block: ActiveRadarrBlock,
) {
let mut app = App::test_default();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(active_radarr_block.into());
app.data.radarr_data = create_test_radarr_data();
@@ -744,7 +748,7 @@ mod tests {
TableHandlerUnit::new(ESC_KEY, &mut app, active_radarr_block, None).handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.data.radarr_data.movies.search, None);
}
@@ -754,7 +758,7 @@ mod tests {
active_radarr_block: ActiveRadarrBlock,
) {
let mut app = App::test_default();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(active_radarr_block.into());
app.data.radarr_data = create_test_radarr_data();
@@ -773,7 +777,7 @@ mod tests {
TableHandlerUnit::new(ESC_KEY, &mut app, active_radarr_block, None).handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
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);
@@ -820,7 +824,7 @@ mod tests {
app.get_current_route(),
ActiveRadarrBlock::SearchMovie.into()
);
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.data.radarr_data.movies.search,
Some(HorizontallyScrollableText::default())
@@ -847,7 +851,7 @@ mod tests {
.handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.data.radarr_data.movies.search, None);
}
@@ -869,7 +873,7 @@ mod tests {
.handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.data.radarr_data.movies.search, None);
}
@@ -894,7 +898,7 @@ mod tests {
app.get_current_route(),
ActiveRadarrBlock::FilterMovies.into()
);
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.movies.filter.is_some());
}
@@ -918,14 +922,14 @@ mod tests {
.handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.movies.filter.is_none());
}
#[test]
fn test_filter_table_key_resets_previous_filter() {
let mut app = App::test_default();
app.should_ignore_quit_key = true;
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.data.radarr_data = create_test_radarr_data();
app
@@ -947,7 +951,7 @@ mod tests {
app.get_current_route(),
ActiveRadarrBlock::FilterMovies.into()
);
assert!(app.should_ignore_quit_key);
assert!(app.ignore_special_keys_for_textbox_input);
assert_eq!(
app.data.radarr_data.movies.filter,
Some(HorizontallyScrollableText::default())
@@ -974,7 +978,7 @@ mod tests {
.handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key);
assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.data.radarr_data.movies.filter, None);
}
@@ -1040,7 +1044,7 @@ mod tests {
app.data.radarr_data.movies.search = Some(HorizontallyScrollableText::default());
TableHandlerUnit::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::SearchMovie,
None,
@@ -1049,7 +1053,7 @@ mod tests {
assert_str_eq!(
app.data.radarr_data.movies.search.as_ref().unwrap().text,
"h"
"a"
);
}
@@ -1065,7 +1069,7 @@ mod tests {
app.data.radarr_data.movies.filter = Some(HorizontallyScrollableText::default());
TableHandlerUnit::new(
Key::Char('h'),
Key::Char('a'),
&mut app,
ActiveRadarrBlock::FilterMovies,
None,
@@ -1074,7 +1078,7 @@ mod tests {
assert_str_eq!(
app.data.radarr_data.movies.filter.as_ref().unwrap().text,
"h"
"a"
);
}
+64 -11
View File
@@ -1,10 +1,4 @@
use anyhow::Result;
use std::panic::PanicHookInfo;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{io, panic, process};
use clap::{crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser};
use clap_complete::generate;
use crossterm::execute;
@@ -16,6 +10,11 @@ use network::NetworkTrait;
use ratatui::backend::CrosstermBackend;
use ratatui::Terminal;
use reqwest::Client;
use std::panic::PanicHookInfo;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{io, panic, process};
use tokio::select;
use tokio::sync::mpsc::Receiver;
use tokio::sync::{mpsc, Mutex};
@@ -24,12 +23,14 @@ use utils::{
build_network_client, load_config, start_cli_no_spinner, start_cli_with_spinner, tail_logs,
};
use crate::app::App;
use crate::app::{log_and_print_error, App};
use crate::cli::Command;
use crate::event::input_event::{Events, InputEvent};
use crate::event::Key;
use crate::network::{Network, NetworkEvent};
use crate::ui::ui;
use crate::ui::theme::{Theme, ThemeDefinitionsWrapper};
use crate::ui::{ui, THEME};
use crate::utils::load_theme_config;
mod app;
mod cli;
@@ -74,6 +75,22 @@ struct Cli {
help = "The Managarr configuration file to use"
)]
config_file: Option<PathBuf>,
#[arg(
long,
global = true,
value_parser,
env = "MANAGARR_THEMES_FILE",
help = "The Managarr themes file to use"
)]
themes_file: Option<PathBuf>,
#[arg(
long,
global = true,
value_parser,
env = "MANAGARR_THEME",
help = "The name of the Managarr theme to use"
)]
theme: Option<String>,
#[arg(
long,
global = true,
@@ -98,10 +115,12 @@ async fn main() -> Result<()> {
} else {
confy::load("managarr", "config")?
};
let theme_name = config.theme.clone();
let spinner_disabled = args.disable_spinner;
debug!("Managarr loaded using config: {config:?}");
config.validate();
config.post_process_initialization();
let reqwest_client = build_network_client(&config);
let (sync_network_tx, sync_network_rx) = mpsc::channel(500);
let cancellation_token = CancellationToken::new();
@@ -140,7 +159,12 @@ async fn main() -> Result<()> {
std::thread::spawn(move || {
start_networking(sync_network_rx, &app_nw, cancellation_token, reqwest_client)
});
start_ui(&app).await?;
start_ui(
&app,
&args.themes_file,
args.theme.unwrap_or(theme_name.unwrap_or_default()),
)
.await?;
}
}
@@ -174,7 +198,36 @@ async fn start_networking(
}
}
async fn start_ui(app: &Arc<Mutex<App<'_>>>) -> Result<()> {
async fn start_ui(
app: &Arc<Mutex<App<'_>>>,
themes_file_arg: &Option<PathBuf>,
theme_name: String,
) -> Result<()> {
let theme_definitions_wrapper = if let Some(ref theme_file) = themes_file_arg {
load_theme_config(theme_file.to_str().expect("Invalid theme file specified"))?
} else {
confy::load("managarr", "themes").unwrap_or_else(|_| ThemeDefinitionsWrapper::default())
};
let theme = if !theme_name.is_empty() {
let theme_definition = theme_definitions_wrapper
.theme_definitions
.iter()
.find(|t| t.name == theme_name);
if theme_definition.is_none() {
log_and_print_error(format!("The specified theme was not found: {theme_name}"));
process::exit(1);
}
theme_definition.unwrap().theme
} else {
debug!("No theme specified, using default theme");
Theme::default()
};
debug!("Managarr loaded using theme: {theme:?}");
theme.validate();
THEME.set(theme);
let mut stdout = io::stdout();
enable_raw_mode()?;
@@ -193,7 +246,7 @@ async fn start_ui(app: &Arc<Mutex<App<'_>>>) -> Result<()> {
match input_events.next()? {
InputEvent::KeyEvent(key) => {
if key == Key::Char('q') && !app.should_ignore_quit_key {
if key == Key::Char('q') && !app.ignore_special_keys_for_textbox_input {
break;
}
+1 -1
View File
@@ -249,7 +249,7 @@ pub struct MediaInfo {
pub video_bit_depth: i64,
#[serde(deserialize_with = "super::from_i64")]
pub video_bitrate: i64,
pub video_codec: String,
pub video_codec: Option<String>,
#[derivative(Default(value = "Number::from(0)"))]
pub video_fps: Number,
pub resolution: String,
+2 -2
View File
@@ -253,7 +253,7 @@ pub struct MediaInfo {
pub video_bit_depth: i64,
#[serde(deserialize_with = "super::from_i64")]
pub video_bitrate: i64,
pub video_codec: String,
pub video_codec: Option<String>,
#[derivative(Default(value = "Number::from(0)"))]
pub video_fps: Number,
pub resolution: String,
@@ -288,7 +288,7 @@ pub struct Season {
#[serde(deserialize_with = "super::from_i64")]
pub season_number: i64,
pub monitored: bool,
pub statistics: SeasonStatistics,
pub statistics: Option<SeasonStatistics>,
}
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
+1 -1
View File
@@ -1300,7 +1300,7 @@ impl Network<'_, '_> {
Runtime: {}",
media_info.video_bit_depth,
media_info.video_bitrate,
media_info.video_codec,
media_info.video_codec.unwrap_or_default(),
media_info.video_fps.as_f64().unwrap(),
media_info.resolution,
media_info.scan_type,
+1 -1
View File
@@ -4008,7 +4008,7 @@ mod test {
audio_stream_count: 1,
video_bit_depth: 10,
video_bitrate: 0,
video_codec: "x265".to_owned(),
video_codec: Some("x265".to_owned()),
video_fps: Number::from_f64(23.976).unwrap(),
resolution: "1920x804".to_owned(),
run_time: "2:00:00".to_owned(),
+1 -1
View File
@@ -1408,7 +1408,7 @@ impl Network<'_, '_> {
Subtitles: {}",
media_info.video_bit_depth,
media_info.video_bitrate,
media_info.video_codec,
media_info.video_codec.unwrap_or_default(),
media_info.video_fps.as_f64().unwrap(),
media_info.resolution,
media_info.scan_type,
+2 -2
View File
@@ -5670,7 +5670,7 @@ mod test {
audio_stream_count: 1,
video_bit_depth: 10,
video_bitrate: 0,
video_codec: "x265".to_owned(),
video_codec: Some("x265".to_owned()),
video_fps: Number::from_f64(23.976).unwrap(),
resolution: "1920x1080".to_owned(),
run_time: "23:51".to_owned(),
@@ -5700,7 +5700,7 @@ mod test {
title: None,
season_number: 1,
monitored: true,
statistics: season_statistics(),
statistics: Some(season_statistics()),
}
}
+148
View File
@@ -0,0 +1,148 @@
use crate::ui::theme::{Background, Style, Theme, ThemeDefinition};
use ratatui::style::Color;
use std::str::FromStr;
#[cfg(test)]
#[path = "builtin_themes_tests.rs"]
mod builtin_themes_tests;
pub(in crate::ui) fn watermelon_dark_theme() -> Theme {
Theme {
background: Some(Background {
enabled: Some(false),
color: Some(Color::from_str("#233237").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#00FF00").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#80ffbf").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff19d9").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#8c19ff").unwrap()),
}),
..Theme::default()
}
}
pub(in crate::ui) fn dracula_theme() -> Theme {
Theme {
background: Some(Background {
enabled: Some(true),
color: Some(Color::from_str("#232326").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
downloading: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff5555").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ffb86c").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
unmonitored_missing: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
help: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
success: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
warning: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
unreleased: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
..Theme::default()
}
}
pub(in crate::ui) fn eldritch_theme() -> Theme {
Theme {
background: Some(Background {
enabled: Some(true),
color: Some(Color::from_str("#212337").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#ebfafa").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#37f499").unwrap()),
}),
downloading: Some(Style {
color: Some(Color::from_str("#f7c67f").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#f16c75").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#f7c67f").unwrap()),
}),
unmonitored_missing: Some(Style {
color: Some(Color::from_str("#7081d0").unwrap()),
}),
help: Some(Style {
color: Some(Color::from_str("#7081d0").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#f265b5").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#04d1f9").unwrap()),
}),
success: Some(Style {
color: Some(Color::from_str("#37f499").unwrap()),
}),
warning: Some(Style {
color: Some(Color::from_str("#f1fc79").unwrap()),
}),
unreleased: Some(Style {
color: Some(Color::from_str("#ebfafa").unwrap()),
}),
..Theme::default()
}
}
pub fn get_builtin_themes() -> Vec<ThemeDefinition> {
vec![
ThemeDefinition {
name: "default".to_owned(),
theme: Theme::default(),
},
ThemeDefinition {
name: "watermelon-dark".to_owned(),
theme: watermelon_dark_theme(),
},
ThemeDefinition {
name: "dracula".to_owned(),
theme: dracula_theme(),
},
ThemeDefinition {
name: "eldritch".to_owned(),
theme: eldritch_theme(),
},
]
}
+143
View File
@@ -0,0 +1,143 @@
#[cfg(test)]
mod test {
use crate::ui::builtin_themes::get_builtin_themes;
use crate::ui::theme::{Background, Style, Theme, ThemeDefinition};
use pretty_assertions::assert_eq;
use ratatui::prelude::Color;
use std::str::FromStr;
#[test]
fn test_builtin_themes() {
let watermelon_dark = Theme {
background: Some(Background {
enabled: Some(false),
color: Some(Color::from_str("#233237").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#00FF00").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#80ffbf").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff19d9").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#8c19ff").unwrap()),
}),
..Theme::default()
};
let dracula = Theme {
background: Some(Background {
enabled: Some(true),
color: Some(Color::from_str("#232326").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
downloading: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff5555").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ffb86c").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
unmonitored_missing: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
help: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
success: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
warning: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
unreleased: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
..Theme::default()
};
let eldritch = Theme {
background: Some(Background {
enabled: Some(true),
color: Some(Color::from_str("#212337").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#ebfafa").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#37f499").unwrap()),
}),
downloading: Some(Style {
color: Some(Color::from_str("#f7c67f").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#f16c75").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#f7c67f").unwrap()),
}),
unmonitored_missing: Some(Style {
color: Some(Color::from_str("#7081d0").unwrap()),
}),
help: Some(Style {
color: Some(Color::from_str("#7081d0").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#f265b5").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#04d1f9").unwrap()),
}),
success: Some(Style {
color: Some(Color::from_str("#37f499").unwrap()),
}),
warning: Some(Style {
color: Some(Color::from_str("#f1fc79").unwrap()),
}),
unreleased: Some(Style {
color: Some(Color::from_str("#ebfafa").unwrap()),
}),
..Theme::default()
};
let expected_themes = vec![
ThemeDefinition {
name: "default".to_owned(),
theme: Theme::default(),
},
ThemeDefinition {
name: "watermelon-dark".to_owned(),
theme: watermelon_dark,
},
ThemeDefinition {
name: "dracula".to_owned(),
theme: dracula,
},
ThemeDefinition {
name: "eldritch".to_owned(),
theme: eldritch,
},
];
assert_eq!(expected_themes, get_builtin_themes());
}
}
+12 -2
View File
@@ -1,3 +1,4 @@
use std::cell::Cell;
use std::sync::atomic::Ordering;
use ratatui::layout::{Constraint, Flex, Layout, Rect};
@@ -15,19 +16,26 @@ use crate::app::App;
use crate::models::{HorizontallyScrollableText, Route, TabState};
use crate::ui::radarr_ui::RadarrUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::theme::Theme;
use crate::ui::utils::{
background_block, borderless_block, centered_rect, logo_block, title_block, title_block_centered,
unstyled_title_block,
};
use crate::ui::widgets::input_box::InputBox;
use crate::ui::widgets::popup::Size;
mod builtin_themes;
mod radarr_ui;
mod sonarr_ui;
mod styles;
pub mod theme;
mod utils;
mod widgets;
static HIGHLIGHT_SYMBOL: &str = "=> ";
thread_local! {
pub static THEME: Cell<Theme> = Cell::new(Theme::default());
}
pub trait DrawUi {
fn accepts(route: Route) -> bool;
@@ -100,7 +108,9 @@ fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
}
fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let block = title_block("Error | <esc> to close").failure().bold();
let block = unstyled_title_block("Error | <esc> to close")
.failure()
.bold();
app.error.scroll_left_or_reset(
area.width as usize,
@@ -130,7 +140,7 @@ pub fn draw_popup(
fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -> Rect {
if title.is_empty() {
f.render_widget(layout_block(), area);
f.render_widget(layout_block().default(), area);
} else {
f.render_widget(title_block(title), area);
}

Some files were not shown because too many files have changed in this diff Show More