testing
This commit is contained in:
@@ -7,12 +7,13 @@ use crate::models::Route;
|
||||
#[path = "lidarr_context_clues_tests.rs"]
|
||||
mod lidarr_context_clues_tests;
|
||||
|
||||
pub static ARTISTS_CONTEXT_CLUES: [ContextClue; 8] = [
|
||||
pub static ARTISTS_CONTEXT_CLUES: [ContextClue; 9] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
||||
|
||||
@@ -24,6 +24,10 @@ mod tests {
|
||||
artists_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
artists_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
artists_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{ArgAction, ArgGroup, Subcommand};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command, mutex_flags_or_option},
|
||||
models::lidarr_models::{EditArtistParams, NewItemMonitorType},
|
||||
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||
};
|
||||
|
||||
use super::LidarrCommand;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_command_handler_tests.rs"]
|
||||
mod edit_command_handler_tests;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||
pub enum LidarrEditCommand {
|
||||
#[command(
|
||||
about = "Edit preferences for the specified artist",
|
||||
group(
|
||||
ArgGroup::new("edit_artist")
|
||||
.args([
|
||||
"enable_monitoring",
|
||||
"disable_monitoring",
|
||||
"monitor_new_items",
|
||||
"quality_profile_id",
|
||||
"metadata_profile_id",
|
||||
"root_folder_path",
|
||||
"tag",
|
||||
"clear_tags"
|
||||
]).required(true)
|
||||
.multiple(true))
|
||||
)]
|
||||
Artist {
|
||||
#[arg(
|
||||
long,
|
||||
help = "The ID of the artist whose settings you want to edit",
|
||||
required = true
|
||||
)]
|
||||
artist_id: i64,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Enable monitoring of this artist in Lidarr so Lidarr will automatically download releases from this artist if they are available",
|
||||
conflicts_with = "disable_monitoring"
|
||||
)]
|
||||
enable_monitoring: bool,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Disable monitoring of this artist so Lidarr does not automatically download releases from this artist if they are available",
|
||||
conflicts_with = "enable_monitoring"
|
||||
)]
|
||||
disable_monitoring: bool,
|
||||
#[arg(
|
||||
long,
|
||||
help = "How Lidarr should monitor new albums from this artist",
|
||||
value_enum
|
||||
)]
|
||||
monitor_new_items: Option<NewItemMonitorType>,
|
||||
#[arg(long, help = "The ID of the quality profile to use for this artist")]
|
||||
quality_profile_id: Option<i64>,
|
||||
#[arg(long, help = "The ID of the metadata profile to use for this artist")]
|
||||
metadata_profile_id: Option<i64>,
|
||||
#[arg(
|
||||
long,
|
||||
help = "The root folder path where all artist data and metadata should live"
|
||||
)]
|
||||
root_folder_path: Option<String>,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Tag IDs to tag this artist with",
|
||||
value_parser,
|
||||
action = ArgAction::Append,
|
||||
conflicts_with = "clear_tags"
|
||||
)]
|
||||
tag: Option<Vec<i64>>,
|
||||
#[arg(long, help = "Clear all tags on this artist", conflicts_with = "tag")]
|
||||
clear_tags: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<LidarrEditCommand> for Command {
|
||||
fn from(value: LidarrEditCommand) -> Self {
|
||||
Command::Lidarr(LidarrCommand::Edit(value))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct LidarrEditCommandHandler<'a, 'b> {
|
||||
_app: &'a Arc<Mutex<App<'b>>>,
|
||||
command: LidarrEditCommand,
|
||||
network: &'a mut dyn NetworkTrait,
|
||||
}
|
||||
|
||||
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrEditCommand> for LidarrEditCommandHandler<'a, 'b> {
|
||||
fn with(
|
||||
_app: &'a Arc<Mutex<App<'b>>>,
|
||||
command: LidarrEditCommand,
|
||||
network: &'a mut dyn NetworkTrait,
|
||||
) -> Self {
|
||||
LidarrEditCommandHandler {
|
||||
_app,
|
||||
command,
|
||||
network,
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle(self) -> Result<String> {
|
||||
let result = match self.command {
|
||||
LidarrEditCommand::Artist {
|
||||
artist_id,
|
||||
enable_monitoring,
|
||||
disable_monitoring,
|
||||
monitor_new_items,
|
||||
quality_profile_id,
|
||||
metadata_profile_id,
|
||||
root_folder_path,
|
||||
tag,
|
||||
clear_tags,
|
||||
} => {
|
||||
let monitored_value = mutex_flags_or_option(enable_monitoring, disable_monitoring);
|
||||
let edit_artist_params = EditArtistParams {
|
||||
artist_id,
|
||||
monitored: monitored_value,
|
||||
monitor_new_items,
|
||||
quality_profile_id,
|
||||
metadata_profile_id,
|
||||
root_folder_path,
|
||||
tags: tag,
|
||||
tag_input_string: None,
|
||||
clear_tags,
|
||||
};
|
||||
|
||||
self
|
||||
.network
|
||||
.handle_network_event(LidarrEvent::EditArtist(edit_artist_params).into())
|
||||
.await?;
|
||||
"Artist Updated".to_owned()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cli::{
|
||||
Command,
|
||||
lidarr::{LidarrCommand, edit_command_handler::LidarrEditCommand},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_edit_command_from() {
|
||||
let command = LidarrEditCommand::Artist {
|
||||
artist_id: 1,
|
||||
enable_monitoring: false,
|
||||
disable_monitoring: false,
|
||||
monitor_new_items: None,
|
||||
quality_profile_id: None,
|
||||
metadata_profile_id: None,
|
||||
root_folder_path: None,
|
||||
tag: None,
|
||||
clear_tags: false,
|
||||
};
|
||||
|
||||
let result = Command::from(command.clone());
|
||||
|
||||
assert_eq!(result, Command::Lidarr(LidarrCommand::Edit(command)));
|
||||
}
|
||||
|
||||
mod cli {
|
||||
use crate::{Cli, models::lidarr_models::NewItemMonitorType};
|
||||
|
||||
use super::*;
|
||||
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "edit", "artist"]);
|
||||
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_with_artist_id_still_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"lidarr",
|
||||
"edit",
|
||||
"artist",
|
||||
"--artist-id",
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_monitoring_flags_conflict() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"lidarr",
|
||||
"edit",
|
||||
"artist",
|
||||
"--artist-id",
|
||||
"1",
|
||||
"--enable-monitoring",
|
||||
"--disable-monitoring",
|
||||
]);
|
||||
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_tag_flags_conflict() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"lidarr",
|
||||
"edit",
|
||||
"artist",
|
||||
"--artist-id",
|
||||
"1",
|
||||
"--tag",
|
||||
"1",
|
||||
"--clear-tags",
|
||||
]);
|
||||
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_artist_assert_argument_flags_require_args(
|
||||
#[values(
|
||||
"--monitor-new-items",
|
||||
"--quality-profile-id",
|
||||
"--metadata-profile-id",
|
||||
"--root-folder-path",
|
||||
"--tag"
|
||||
)]
|
||||
flag: &str,
|
||||
) {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"lidarr",
|
||||
"edit",
|
||||
"artist",
|
||||
"--artist-id",
|
||||
"1",
|
||||
flag,
|
||||
]);
|
||||
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_monitor_new_items_validation() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"lidarr",
|
||||
"edit",
|
||||
"artist",
|
||||
"--artist-id",
|
||||
"1",
|
||||
"--monitor-new-items",
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_only_requires_at_least_one_argument_plus_artist_id() {
|
||||
let expected_args = LidarrEditCommand::Artist {
|
||||
artist_id: 1,
|
||||
enable_monitoring: false,
|
||||
disable_monitoring: false,
|
||||
monitor_new_items: None,
|
||||
quality_profile_id: None,
|
||||
metadata_profile_id: None,
|
||||
root_folder_path: Some("/nfs/test".to_owned()),
|
||||
tag: None,
|
||||
clear_tags: false,
|
||||
};
|
||||
|
||||
let result = Cli::try_parse_from([
|
||||
"managarr",
|
||||
"lidarr",
|
||||
"edit",
|
||||
"artist",
|
||||
"--artist-id",
|
||||
"1",
|
||||
"--root-folder-path",
|
||||
"/nfs/test",
|
||||
]);
|
||||
|
||||
assert_ok!(&result);
|
||||
|
||||
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_tag_argument_is_repeatable() {
|
||||
let expected_args = LidarrEditCommand::Artist {
|
||||
artist_id: 1,
|
||||
enable_monitoring: false,
|
||||
disable_monitoring: false,
|
||||
monitor_new_items: None,
|
||||
quality_profile_id: None,
|
||||
metadata_profile_id: None,
|
||||
root_folder_path: None,
|
||||
tag: Some(vec![1, 2]),
|
||||
clear_tags: false,
|
||||
};
|
||||
|
||||
let result = Cli::try_parse_from([
|
||||
"managarr",
|
||||
"lidarr",
|
||||
"edit",
|
||||
"artist",
|
||||
"--artist-id",
|
||||
"1",
|
||||
"--tag",
|
||||
"1",
|
||||
"--tag",
|
||||
"2",
|
||||
]);
|
||||
|
||||
assert_ok!(&result);
|
||||
|
||||
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_all_arguments_defined() {
|
||||
let expected_args = LidarrEditCommand::Artist {
|
||||
artist_id: 1,
|
||||
enable_monitoring: true,
|
||||
disable_monitoring: false,
|
||||
monitor_new_items: Some(NewItemMonitorType::New),
|
||||
quality_profile_id: Some(1),
|
||||
metadata_profile_id: Some(1),
|
||||
root_folder_path: Some("/nfs/test".to_owned()),
|
||||
tag: Some(vec![1, 2]),
|
||||
clear_tags: false,
|
||||
};
|
||||
|
||||
let result = Cli::try_parse_from([
|
||||
"managarr",
|
||||
"lidarr",
|
||||
"edit",
|
||||
"artist",
|
||||
"--artist-id",
|
||||
"1",
|
||||
"--enable-monitoring",
|
||||
"--monitor-new-items",
|
||||
"new",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
"--metadata-profile-id",
|
||||
"1",
|
||||
"--root-folder-path",
|
||||
"/nfs/test",
|
||||
"--tag",
|
||||
"1",
|
||||
"--tag",
|
||||
"2",
|
||||
]);
|
||||
|
||||
assert_ok!(&result);
|
||||
|
||||
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
mod handler {
|
||||
use std::sync::Arc;
|
||||
|
||||
use mockall::predicate::eq;
|
||||
use serde_json::json;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
CliCommandHandler,
|
||||
lidarr::edit_command_handler::{LidarrEditCommand, LidarrEditCommandHandler},
|
||||
},
|
||||
models::{
|
||||
Serdeable,
|
||||
lidarr_models::{EditArtistParams, LidarrSerdeable, NewItemMonitorType},
|
||||
},
|
||||
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_artist_command() {
|
||||
let expected_edit_artist_params = EditArtistParams {
|
||||
artist_id: 1,
|
||||
monitored: Some(true),
|
||||
monitor_new_items: Some(NewItemMonitorType::New),
|
||||
quality_profile_id: Some(1),
|
||||
metadata_profile_id: Some(1),
|
||||
root_folder_path: Some("/nfs/test".to_owned()),
|
||||
tags: Some(vec![1, 2]),
|
||||
tag_input_string: None,
|
||||
clear_tags: false,
|
||||
};
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
.expect_handle_network_event()
|
||||
.with(eq::<NetworkEvent>(
|
||||
LidarrEvent::EditArtist(expected_edit_artist_params).into(),
|
||||
))
|
||||
.times(1)
|
||||
.returning(|_| {
|
||||
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||
json!({"testResponse": "response"}),
|
||||
)))
|
||||
});
|
||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||
let edit_artist_command = LidarrEditCommand::Artist {
|
||||
artist_id: 1,
|
||||
enable_monitoring: true,
|
||||
disable_monitoring: false,
|
||||
monitor_new_items: Some(NewItemMonitorType::New),
|
||||
quality_profile_id: Some(1),
|
||||
metadata_profile_id: Some(1),
|
||||
root_folder_path: Some("/nfs/test".to_owned()),
|
||||
tag: Some(vec![1, 2]),
|
||||
clear_tags: false,
|
||||
};
|
||||
|
||||
let result = LidarrEditCommandHandler::with(&app_arc, edit_artist_command, &mut mock_network)
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_artist_command_handles_disable_monitoring_flag_properly() {
|
||||
let expected_edit_artist_params = EditArtistParams {
|
||||
artist_id: 1,
|
||||
monitored: Some(false),
|
||||
monitor_new_items: Some(NewItemMonitorType::None),
|
||||
quality_profile_id: Some(1),
|
||||
metadata_profile_id: Some(1),
|
||||
root_folder_path: Some("/nfs/test".to_owned()),
|
||||
tags: Some(vec![1, 2]),
|
||||
tag_input_string: None,
|
||||
clear_tags: false,
|
||||
};
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
.expect_handle_network_event()
|
||||
.with(eq::<NetworkEvent>(
|
||||
LidarrEvent::EditArtist(expected_edit_artist_params).into(),
|
||||
))
|
||||
.times(1)
|
||||
.returning(|_| {
|
||||
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||
json!({"testResponse": "response"}),
|
||||
)))
|
||||
});
|
||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||
let edit_artist_command = LidarrEditCommand::Artist {
|
||||
artist_id: 1,
|
||||
enable_monitoring: false,
|
||||
disable_monitoring: true,
|
||||
monitor_new_items: Some(NewItemMonitorType::None),
|
||||
quality_profile_id: Some(1),
|
||||
metadata_profile_id: Some(1),
|
||||
root_folder_path: Some("/nfs/test".to_owned()),
|
||||
tag: Some(vec![1, 2]),
|
||||
clear_tags: false,
|
||||
};
|
||||
|
||||
let result = LidarrEditCommandHandler::with(&app_arc, edit_artist_command, &mut mock_network)
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_artist_command_no_monitoring_boolean_flags_returns_none_value() {
|
||||
let expected_edit_artist_params = EditArtistParams {
|
||||
artist_id: 1,
|
||||
monitored: None,
|
||||
monitor_new_items: Some(NewItemMonitorType::All),
|
||||
quality_profile_id: Some(1),
|
||||
metadata_profile_id: Some(1),
|
||||
root_folder_path: Some("/nfs/test".to_owned()),
|
||||
tags: Some(vec![1, 2]),
|
||||
tag_input_string: None,
|
||||
clear_tags: false,
|
||||
};
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
.expect_handle_network_event()
|
||||
.with(eq::<NetworkEvent>(
|
||||
LidarrEvent::EditArtist(expected_edit_artist_params).into(),
|
||||
))
|
||||
.times(1)
|
||||
.returning(|_| {
|
||||
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||
json!({"testResponse": "response"}),
|
||||
)))
|
||||
});
|
||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||
let edit_artist_command = LidarrEditCommand::Artist {
|
||||
artist_id: 1,
|
||||
enable_monitoring: false,
|
||||
disable_monitoring: false,
|
||||
monitor_new_items: Some(NewItemMonitorType::All),
|
||||
quality_profile_id: Some(1),
|
||||
metadata_profile_id: Some(1),
|
||||
root_folder_path: Some("/nfs/test".to_owned()),
|
||||
tag: Some(vec![1, 2]),
|
||||
clear_tags: false,
|
||||
};
|
||||
|
||||
let result = LidarrEditCommandHandler::with(&app_arc, edit_artist_command, &mut mock_network)
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use clap::{Subcommand, arg};
|
||||
use delete_command_handler::{LidarrDeleteCommand, LidarrDeleteCommandHandler};
|
||||
use edit_command_handler::{LidarrEditCommand, LidarrEditCommandHandler};
|
||||
use get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler};
|
||||
use list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
|
||||
use refresh_command_handler::{LidarrRefreshCommand, LidarrRefreshCommandHandler};
|
||||
@@ -14,6 +15,7 @@ use crate::{app::App, network::NetworkTrait};
|
||||
use super::{CliCommandHandler, Command};
|
||||
|
||||
mod delete_command_handler;
|
||||
mod edit_command_handler;
|
||||
mod get_command_handler;
|
||||
mod list_command_handler;
|
||||
mod refresh_command_handler;
|
||||
@@ -29,6 +31,11 @@ pub enum LidarrCommand {
|
||||
about = "Commands to delete resources from your Lidarr instance"
|
||||
)]
|
||||
Delete(LidarrDeleteCommand),
|
||||
#[command(
|
||||
subcommand,
|
||||
about = "Commands to edit resources in your Lidarr instance"
|
||||
)]
|
||||
Edit(LidarrEditCommand),
|
||||
#[command(
|
||||
subcommand,
|
||||
about = "Commands to fetch details of the resources in your Lidarr instance"
|
||||
@@ -89,6 +96,11 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, '
|
||||
.handle()
|
||||
.await?
|
||||
}
|
||||
LidarrCommand::Edit(edit_command) => {
|
||||
LidarrEditCommandHandler::with(self.app, edit_command, self.network)
|
||||
.handle()
|
||||
.await?
|
||||
}
|
||||
LidarrCommand::Get(get_command) => {
|
||||
LidarrGetCommandHandler::with(self.app, get_command, self.network)
|
||||
.handle()
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::ActiveKeybindingBlock;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -75,7 +76,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveKeybindingBlock> for KeybindingHandle
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::{
|
||||
matches_key,
|
||||
models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DELETE_ARTIST_BLOCKS},
|
||||
};
|
||||
use crate::models::Route;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "delete_artist_handler_tests.rs"]
|
||||
@@ -143,7 +144,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for DeleteArtistHandler<
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,455 @@
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||
use crate::models::{Route, Scrollable};
|
||||
use crate::models::lidarr_models::EditArtistParams;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_ARTIST_BLOCKS};
|
||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_artist_handler_tests.rs"]
|
||||
mod edit_artist_handler_tests;
|
||||
|
||||
pub(in crate::handlers::lidarr_handlers) struct EditArtistHandler<'a, 'b> {
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_lidarr_block: ActiveLidarrBlock,
|
||||
context: Option<ActiveLidarrBlock>,
|
||||
}
|
||||
|
||||
impl EditArtistHandler<'_, '_> {
|
||||
fn build_edit_artist_params(&mut self) -> EditArtistParams {
|
||||
let edit_artist_modal = self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.take()
|
||||
.expect("EditArtistModal is None");
|
||||
let artist_id = self.app.data.lidarr_data.artists.current_selection().id;
|
||||
let tags = edit_artist_modal.tags.text;
|
||||
|
||||
let EditArtistModal {
|
||||
monitored,
|
||||
path,
|
||||
monitor_list,
|
||||
quality_profile_list,
|
||||
metadata_profile_list,
|
||||
..
|
||||
} = edit_artist_modal;
|
||||
let quality_profile = quality_profile_list.current_selection();
|
||||
let quality_profile_id = *self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.quality_profile_map
|
||||
.iter()
|
||||
.filter(|(_, value)| *value == quality_profile)
|
||||
.map(|(key, _)| key)
|
||||
.next()
|
||||
.unwrap();
|
||||
let metadata_profile = metadata_profile_list.current_selection();
|
||||
let metadata_profile_id = *self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.metadata_profile_map
|
||||
.iter()
|
||||
.filter(|(_, value)| *value == metadata_profile)
|
||||
.map(|(key, _)| key)
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
EditArtistParams {
|
||||
artist_id,
|
||||
monitored,
|
||||
monitor_new_items: Some(*monitor_list.current_selection()),
|
||||
quality_profile_id: Some(quality_profile_id),
|
||||
metadata_profile_id: Some(metadata_profile_id),
|
||||
root_folder_path: Some(path.text),
|
||||
tag_input_string: Some(tags),
|
||||
..EditArtistParams::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for EditArtistHandler<'a, 'b> {
|
||||
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||
EDIT_ARTIST_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
self.app.ignore_special_keys_for_textbox_input
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_block: ActiveLidarrBlock,
|
||||
context: Option<ActiveLidarrBlock>,
|
||||
) -> EditArtistHandler<'a, 'b> {
|
||||
EditArtistHandler {
|
||||
key,
|
||||
app,
|
||||
active_lidarr_block: active_block,
|
||||
context,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key(&self) -> Key {
|
||||
self.key
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
!self.app.is_loading && self.app.data.lidarr_data.edit_artist_modal.is_some()
|
||||
}
|
||||
|
||||
fn handle_scroll_up(&mut self) {
|
||||
match self.active_lidarr_block {
|
||||
ActiveLidarrBlock::EditArtistSelectMonitorNewItems => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.monitor_list
|
||||
.scroll_up(),
|
||||
ActiveLidarrBlock::EditArtistSelectQualityProfile => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.quality_profile_list
|
||||
.scroll_up(),
|
||||
ActiveLidarrBlock::EditArtistSelectMetadataProfile => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.metadata_profile_list
|
||||
.scroll_up(),
|
||||
ActiveLidarrBlock::EditArtistPrompt => self.app.data.lidarr_data.selected_block.up(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_scroll_down(&mut self) {
|
||||
match self.active_lidarr_block {
|
||||
ActiveLidarrBlock::EditArtistSelectMonitorNewItems => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.monitor_list
|
||||
.scroll_down(),
|
||||
ActiveLidarrBlock::EditArtistSelectQualityProfile => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.quality_profile_list
|
||||
.scroll_down(),
|
||||
ActiveLidarrBlock::EditArtistSelectMetadataProfile => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.metadata_profile_list
|
||||
.scroll_down(),
|
||||
ActiveLidarrBlock::EditArtistPrompt => self.app.data.lidarr_data.selected_block.down(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_home(&mut self) {
|
||||
match self.active_lidarr_block {
|
||||
ActiveLidarrBlock::EditArtistSelectMonitorNewItems => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.monitor_list
|
||||
.scroll_to_top(),
|
||||
ActiveLidarrBlock::EditArtistSelectQualityProfile => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.quality_profile_list
|
||||
.scroll_to_top(),
|
||||
ActiveLidarrBlock::EditArtistSelectMetadataProfile => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.metadata_profile_list
|
||||
.scroll_to_top(),
|
||||
ActiveLidarrBlock::EditArtistPathInput => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.path
|
||||
.scroll_home(),
|
||||
ActiveLidarrBlock::EditArtistTagsInput => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.tags
|
||||
.scroll_home(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_end(&mut self) {
|
||||
match self.active_lidarr_block {
|
||||
ActiveLidarrBlock::EditArtistSelectMonitorNewItems => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.monitor_list
|
||||
.scroll_to_bottom(),
|
||||
ActiveLidarrBlock::EditArtistSelectQualityProfile => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.quality_profile_list
|
||||
.scroll_to_bottom(),
|
||||
ActiveLidarrBlock::EditArtistSelectMetadataProfile => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.metadata_profile_list
|
||||
.scroll_to_bottom(),
|
||||
ActiveLidarrBlock::EditArtistPathInput => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.path
|
||||
.reset_offset(),
|
||||
ActiveLidarrBlock::EditArtistTagsInput => self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.tags
|
||||
.reset_offset(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_delete(&mut self) {}
|
||||
|
||||
fn handle_left_right_action(&mut self) {
|
||||
match self.active_lidarr_block {
|
||||
ActiveLidarrBlock::EditArtistPrompt => handle_prompt_toggle(self.app, self.key),
|
||||
ActiveLidarrBlock::EditArtistPathInput => {
|
||||
handle_text_box_left_right_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.path
|
||||
)
|
||||
}
|
||||
ActiveLidarrBlock::EditArtistTagsInput => {
|
||||
handle_text_box_left_right_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.tags
|
||||
)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_submit(&mut self) {
|
||||
match self.active_lidarr_block {
|
||||
ActiveLidarrBlock::EditArtistPrompt => {
|
||||
match self.app.data.lidarr_data.selected_block.get_active_block() {
|
||||
ActiveLidarrBlock::EditArtistConfirmPrompt => {
|
||||
if self.app.data.lidarr_data.prompt_confirm {
|
||||
self.app.data.lidarr_data.prompt_confirm_action =
|
||||
Some(LidarrEvent::EditArtist(self.build_edit_artist_params()));
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveLidarrBlock::EditArtistSelectMonitorNewItems
|
||||
| ActiveLidarrBlock::EditArtistSelectQualityProfile
|
||||
| ActiveLidarrBlock::EditArtistSelectMetadataProfile => self.app.push_navigation_stack(
|
||||
(
|
||||
self.app.data.lidarr_data.selected_block.get_active_block(),
|
||||
self.context,
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
ActiveLidarrBlock::EditArtistPathInput | ActiveLidarrBlock::EditArtistTagsInput => {
|
||||
self.app.push_navigation_stack(
|
||||
(
|
||||
self.app.data.lidarr_data.selected_block.get_active_block(),
|
||||
self.context,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
self.app.ignore_special_keys_for_textbox_input = true;
|
||||
}
|
||||
ActiveLidarrBlock::EditArtistToggleMonitored => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.monitored = Some(
|
||||
!self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.monitored
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
ActiveLidarrBlock::EditArtistSelectMonitorNewItems
|
||||
| ActiveLidarrBlock::EditArtistSelectQualityProfile
|
||||
| ActiveLidarrBlock::EditArtistSelectMetadataProfile => self.app.pop_navigation_stack(),
|
||||
ActiveLidarrBlock::EditArtistPathInput | ActiveLidarrBlock::EditArtistTagsInput => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.ignore_special_keys_for_textbox_input = false;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_esc(&mut self) {
|
||||
match self.active_lidarr_block {
|
||||
ActiveLidarrBlock::EditArtistTagsInput | ActiveLidarrBlock::EditArtistPathInput => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.ignore_special_keys_for_textbox_input = false;
|
||||
}
|
||||
ActiveLidarrBlock::EditArtistPrompt => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.data.lidarr_data.edit_artist_modal = None;
|
||||
self.app.data.lidarr_data.prompt_confirm = false;
|
||||
}
|
||||
ActiveLidarrBlock::EditArtistSelectMonitorNewItems
|
||||
| ActiveLidarrBlock::EditArtistSelectQualityProfile
|
||||
| ActiveLidarrBlock::EditArtistSelectMetadataProfile => self.app.pop_navigation_stack(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
let key = self.key;
|
||||
match self.active_lidarr_block {
|
||||
ActiveLidarrBlock::EditArtistPathInput => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.path
|
||||
)
|
||||
}
|
||||
ActiveLidarrBlock::EditArtistTagsInput => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.tags
|
||||
)
|
||||
}
|
||||
ActiveLidarrBlock::EditArtistPrompt => {
|
||||
if self.app.data.lidarr_data.selected_block.get_active_block()
|
||||
== ActiveLidarrBlock::EditArtistConfirmPrompt
|
||||
&& matches_key!(confirm, key)
|
||||
{
|
||||
self.app.data.lidarr_data.prompt_confirm = true;
|
||||
self.app.data.lidarr_data.prompt_confirm_action =
|
||||
Some(LidarrEvent::EditArtist(self.build_edit_artist_params()));
|
||||
self.app.should_refresh = true;
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn app_mut(&mut self) -> &mut App<'b> {
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::lidarr_handlers::library::edit_artist_handler::EditArtistHandler;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_ARTIST_BLOCKS};
|
||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||
|
||||
mod test_handle_scroll_up_and_down {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::EDIT_ARTIST_SELECTION_BLOCKS;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_artist_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) {
|
||||
let mut app = App::test_default();
|
||||
app.data.lidarr_data.edit_artist_modal = Some(EditArtistModal::default());
|
||||
app.data.lidarr_data.selected_block = BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS);
|
||||
app.data.lidarr_data.selected_block.down();
|
||||
|
||||
EditArtistHandler::new(key, &mut app, ActiveLidarrBlock::EditArtistPrompt, None).handle();
|
||||
|
||||
if key == Key::Up {
|
||||
assert_eq!(
|
||||
app.data.lidarr_data.selected_block.get_active_block(),
|
||||
ActiveLidarrBlock::EditArtistToggleMonitored
|
||||
);
|
||||
} else {
|
||||
assert_eq!(
|
||||
app.data.lidarr_data.selected_block.get_active_block(),
|
||||
ActiveLidarrBlock::EditArtistSelectQualityProfile
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_artist_prompt_scroll_no_op_when_not_ready(#[values(Key::Up, Key::Down)] key: Key) {
|
||||
let mut app = App::test_default();
|
||||
app.is_loading = true;
|
||||
app.data.lidarr_data.edit_artist_modal = Some(EditArtistModal::default());
|
||||
app.data.lidarr_data.selected_block = BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS);
|
||||
app.data.lidarr_data.selected_block.down();
|
||||
|
||||
EditArtistHandler::new(key, &mut app, ActiveLidarrBlock::EditArtistPrompt, None).handle();
|
||||
|
||||
assert_eq!(
|
||||
app.data.lidarr_data.selected_block.get_active_block(),
|
||||
ActiveLidarrBlock::EditArtistSelectMonitorNewItems
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_left_right_action {
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) {
|
||||
let mut app = App::test_default();
|
||||
app.data.lidarr_data.edit_artist_modal = Some(EditArtistModal::default());
|
||||
app.push_navigation_stack(ActiveLidarrBlock::EditArtistPrompt.into());
|
||||
|
||||
EditArtistHandler::new(key, &mut app, ActiveLidarrBlock::EditArtistPrompt, None).handle();
|
||||
|
||||
assert!(app.data.lidarr_data.prompt_confirm);
|
||||
|
||||
EditArtistHandler::new(key, &mut app, ActiveLidarrBlock::EditArtistPrompt, None).handle();
|
||||
|
||||
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_submit {
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::EDIT_ARTIST_SELECTION_BLOCKS;
|
||||
|
||||
use super::*;
|
||||
use crate::assert_navigation_popped;
|
||||
|
||||
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_prompt_prompt_decline_submit() {
|
||||
let mut app = App::test_default();
|
||||
app.data.lidarr_data.edit_artist_modal = Some(EditArtistModal::default());
|
||||
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||
app.push_navigation_stack(ActiveLidarrBlock::EditArtistPrompt.into());
|
||||
app.data.lidarr_data.selected_block = BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS);
|
||||
// Navigate to the confirm prompt (last selection block)
|
||||
for _ in 0..EDIT_ARTIST_SELECTION_BLOCKS.len() - 1 {
|
||||
app.data.lidarr_data.selected_block.down();
|
||||
}
|
||||
|
||||
EditArtistHandler::new(
|
||||
SUBMIT_KEY,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::EditArtistPrompt,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(app.data.lidarr_data.prompt_confirm_action.is_none());
|
||||
assert_navigation_popped!(&app, ActiveLidarrBlock::Artists.into());
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_esc {
|
||||
use super::*;
|
||||
use crate::assert_navigation_popped;
|
||||
|
||||
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_prompt_esc() {
|
||||
let mut app = App::test_default();
|
||||
app.data.lidarr_data.edit_artist_modal = Some(EditArtistModal::default());
|
||||
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||
app.push_navigation_stack(ActiveLidarrBlock::EditArtistPrompt.into());
|
||||
app.data.lidarr_data.prompt_confirm = true;
|
||||
|
||||
EditArtistHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::EditArtistPrompt, None).handle();
|
||||
|
||||
assert_navigation_popped!(&app, ActiveLidarrBlock::Artists.into());
|
||||
assert!(app.data.lidarr_data.edit_artist_modal.is_none());
|
||||
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_select_blocks_esc() {
|
||||
let mut app = App::test_default();
|
||||
app.data.lidarr_data.edit_artist_modal = Some(EditArtistModal::default());
|
||||
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||
app.push_navigation_stack(ActiveLidarrBlock::EditArtistPrompt.into());
|
||||
app.push_navigation_stack(ActiveLidarrBlock::EditArtistSelectQualityProfile.into());
|
||||
|
||||
EditArtistHandler::new(
|
||||
ESC_KEY,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::EditArtistSelectQualityProfile,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_navigation_popped!(&app, ActiveLidarrBlock::EditArtistPrompt.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_handler_accepts() {
|
||||
let mut edit_artist_handler_blocks = Vec::new();
|
||||
for block in ActiveLidarrBlock::iter() {
|
||||
if EditArtistHandler::accepts(block) {
|
||||
edit_artist_handler_blocks.push(block);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(edit_artist_handler_blocks, EDIT_ARTIST_BLOCKS.to_vec());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_handler_is_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
app.is_loading = true;
|
||||
app.data.lidarr_data.edit_artist_modal = Some(EditArtistModal::default());
|
||||
|
||||
let handler = EditArtistHandler::new(
|
||||
Key::Esc,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::EditArtistPrompt,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!handler.is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_handler_is_not_ready_when_edit_artist_modal_is_none() {
|
||||
let mut app = App::test_default();
|
||||
app.data.lidarr_data.edit_artist_modal = None;
|
||||
|
||||
let handler = EditArtistHandler::new(
|
||||
Key::Esc,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::EditArtistPrompt,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!handler.is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_handler_is_ready_when_not_loading_and_modal_is_some() {
|
||||
let mut app = App::test_default();
|
||||
app.data.lidarr_data.edit_artist_modal = Some(EditArtistModal::default());
|
||||
|
||||
let handler = EditArtistHandler::new(
|
||||
Key::Esc,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::EditArtistPrompt,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(handler.is_ready());
|
||||
}
|
||||
}
|
||||
@@ -11,19 +11,24 @@ mod tests {
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::lidarr_handlers::library::{LibraryHandler, artists_sorting_options};
|
||||
use crate::models::lidarr_models::{Artist, ArtistStatistics, ArtistStatus};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, LIBRARY_BLOCKS};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, LIBRARY_BLOCKS};
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
use crate::{assert_modal_absent, assert_navigation_popped, assert_navigation_pushed};
|
||||
|
||||
#[test]
|
||||
fn test_library_handler_accepts() {
|
||||
for lidarr_block in ActiveLidarrBlock::iter() {
|
||||
if LIBRARY_BLOCKS.contains(&lidarr_block) {
|
||||
let mut library_handler_blocks = Vec::new();
|
||||
library_handler_blocks.extend(LIBRARY_BLOCKS);
|
||||
library_handler_blocks.extend(DELETE_ARTIST_BLOCKS);
|
||||
library_handler_blocks.extend(EDIT_ARTIST_BLOCKS);
|
||||
|
||||
ActiveLidarrBlock::iter().for_each(|lidarr_block| {
|
||||
if library_handler_blocks.contains(&lidarr_block) {
|
||||
assert!(LibraryHandler::accepts(lidarr_block));
|
||||
} else {
|
||||
assert!(!LibraryHandler::accepts(lidarr_block));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -7,8 +7,10 @@ use crate::{
|
||||
BlockSelectionState,
|
||||
lidarr_models::Artist,
|
||||
servarr_data::lidarr::lidarr_data::{
|
||||
ActiveLidarrBlock, DELETE_ARTIST_SELECTION_BLOCKS, LIBRARY_BLOCKS,
|
||||
ActiveLidarrBlock, DELETE_ARTIST_SELECTION_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS,
|
||||
LIBRARY_BLOCKS,
|
||||
},
|
||||
servarr_data::lidarr::modals::EditArtistModal,
|
||||
stateful_table::SortOption,
|
||||
},
|
||||
network::lidarr_network::LidarrEvent,
|
||||
@@ -18,8 +20,11 @@ use super::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
|
||||
mod delete_artist_handler;
|
||||
mod edit_artist_handler;
|
||||
|
||||
pub(in crate::handlers::lidarr_handlers) use delete_artist_handler::DeleteArtistHandler;
|
||||
pub(in crate::handlers::lidarr_handlers) use edit_artist_handler::EditArtistHandler;
|
||||
use crate::models::Route;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "library_handler_tests.rs"]
|
||||
@@ -29,7 +34,7 @@ pub(super) struct LibraryHandler<'a, 'b> {
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_lidarr_block: ActiveLidarrBlock,
|
||||
_context: Option<ActiveLidarrBlock>,
|
||||
context: Option<ActiveLidarrBlock>,
|
||||
}
|
||||
|
||||
impl LibraryHandler<'_, '_> {
|
||||
@@ -55,12 +60,23 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, '
|
||||
|app| &mut app.data.lidarr_data.artists,
|
||||
artists_table_handling_config,
|
||||
) {
|
||||
self.handle_key_event();
|
||||
match self.active_lidarr_block {
|
||||
_ if DeleteArtistHandler::accepts(self.active_lidarr_block) => {
|
||||
DeleteArtistHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||
.handle();
|
||||
}
|
||||
_ if EditArtistHandler::accepts(self.active_lidarr_block) => {
|
||||
EditArtistHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||
}
|
||||
_ => self.handle_key_event(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||
LIBRARY_BLOCKS.contains(&active_block)
|
||||
DeleteArtistHandler::accepts(active_block)
|
||||
|| EditArtistHandler::accepts(active_block)
|
||||
|| LIBRARY_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
@@ -77,7 +93,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, '
|
||||
key,
|
||||
app,
|
||||
active_lidarr_block: active_block,
|
||||
_context: context,
|
||||
context,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +167,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, '
|
||||
.app
|
||||
.pop_and_push_navigation_stack(self.active_lidarr_block.into());
|
||||
}
|
||||
_ if matches_key!(edit, key) => {
|
||||
self.app.data.lidarr_data.edit_artist_modal =
|
||||
Some((&self.app.data.lidarr_data).into());
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveLidarrBlock::EditArtistPrompt.into());
|
||||
self.app.data.lidarr_data.selected_block =
|
||||
BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS);
|
||||
}
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
@@ -177,7 +202,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, '
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,47 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::lidarr_handlers::LidarrHandler;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||
|
||||
#[rstest]
|
||||
fn test_lidarr_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 = LidarrHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
handler.ignore_special_keys(),
|
||||
ignore_special_keys_for_textbox_input
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_handler_is_ready() {
|
||||
let mut app = App::test_default();
|
||||
app.is_loading = true;
|
||||
|
||||
let handler = LidarrHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(handler.is_ready());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_handler_accepts() {
|
||||
for lidarr_block in ActiveLidarrBlock::iter() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use library::{DeleteArtistHandler, LibraryHandler};
|
||||
use library::LibraryHandler;
|
||||
|
||||
use crate::{
|
||||
app::App, event::Key, matches_key, models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
||||
};
|
||||
|
||||
use crate::models::Route;
|
||||
use super::KeyEventHandler;
|
||||
|
||||
mod library;
|
||||
@@ -22,10 +22,6 @@ pub(super) struct LidarrHandler<'a, 'b> {
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LidarrHandler<'a, 'b> {
|
||||
fn handle(&mut self) {
|
||||
match self.active_lidarr_block {
|
||||
_ if DeleteArtistHandler::accepts(self.active_lidarr_block) => {
|
||||
DeleteArtistHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||
.handle();
|
||||
}
|
||||
_ if LibraryHandler::accepts(self.active_lidarr_block) => {
|
||||
LibraryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||
}
|
||||
@@ -85,7 +81,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LidarrHandler<'a, 'b
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::radarr_models::BlocklistItem;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
@@ -178,7 +179,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::event::Key;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::matches_key;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::{BlockSelectionState, Route};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ADD_MOVIE_SELECTION_BLOCKS, ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS,
|
||||
EDIT_COLLECTION_SELECTION_BLOCKS,
|
||||
@@ -148,7 +148,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||
use crate::models::Scrollable;
|
||||
use crate::models::{Route, Scrollable};
|
||||
use crate::models::radarr_models::EditCollectionParams;
|
||||
use crate::models::servarr_data::radarr::modals::EditCollectionModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS};
|
||||
@@ -376,7 +376,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::{BlockSelectionState, Route};
|
||||
use crate::models::radarr_models::Collection;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, COLLECTIONS_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS,
|
||||
@@ -179,7 +179,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
@@ -164,7 +165,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{
|
||||
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
|
||||
};
|
||||
use crate::models::Route;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_indexer_handler_tests.rs"]
|
||||
@@ -527,7 +528,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{
|
||||
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
|
||||
};
|
||||
use crate::models::Route;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_indexer_settings_handler_tests.rs"]
|
||||
@@ -293,7 +294,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::handlers::radarr_handlers::indexers::test_all_indexers_handler::TestA
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::{BlockSelectionState, Route};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
|
||||
INDEXER_SETTINGS_SELECTION_BLOCKS, INDEXERS_BLOCKS,
|
||||
@@ -212,7 +212,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -101,7 +102,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandl
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::models::servarr_data::radarr::modals::AddMovieModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS, ActiveRadarrBlock,
|
||||
};
|
||||
use crate::models::{BlockSelectionState, Scrollable};
|
||||
use crate::models::{BlockSelectionState, Route, Scrollable};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{App, Key, handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
|
||||
|
||||
@@ -558,7 +558,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::event::Key;
|
||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::radarr_models::DeleteMovieParams;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
@@ -141,7 +142,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||
use crate::models::Scrollable;
|
||||
use crate::models::{Route, Scrollable};
|
||||
use crate::models::radarr_models::EditMovieParams;
|
||||
use crate::models::servarr_data::radarr::modals::EditMovieModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS};
|
||||
@@ -397,7 +397,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
|
||||
};
|
||||
use crate::models::servarr_models::Language;
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::models::{BlockSelectionState, Scrollable};
|
||||
use crate::models::{BlockSelectionState, Route, Scrollable};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -379,7 +379,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::handlers::radarr_handlers::root_folders::RootFoldersHandler;
|
||||
use crate::handlers::radarr_handlers::system::SystemHandler;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::{App, Key, matches_key};
|
||||
use crate::models::Route;
|
||||
|
||||
mod blocklist;
|
||||
mod collections;
|
||||
@@ -112,7 +113,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use crate::models::{HorizontallyScrollableText, Route};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS};
|
||||
use crate::models::servarr_models::AddRootFolderBody;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
@@ -231,7 +231,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler;
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors};
|
||||
use crate::matches_key;
|
||||
use crate::models::Scrollable;
|
||||
use crate::models::{Route, Scrollable};
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
|
||||
mod system_details_handler;
|
||||
@@ -129,7 +129,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::Scrollable;
|
||||
use crate::models::{Route, Scrollable};
|
||||
use crate::models::radarr_models::RadarrTaskName;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
|
||||
use crate::models::stateful_list::StatefulList;
|
||||
@@ -201,7 +201,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, BLOCKLIST_BLOCKS};
|
||||
use crate::models::sonarr_models::BlocklistItem;
|
||||
use crate::models::stateful_table::SortOption;
|
||||
@@ -178,7 +179,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DOWNLOADS_BLOCKS};
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
|
||||
@@ -164,7 +165,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors};
|
||||
use crate::matches_key;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, HISTORY_BLOCKS};
|
||||
use crate::models::servarr_models::Language;
|
||||
use crate::models::sonarr_models::SonarrHistoryItem;
|
||||
@@ -121,7 +122,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, '
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{
|
||||
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
|
||||
};
|
||||
use crate::models::Route;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_indexer_handler_tests.rs"]
|
||||
@@ -526,7 +527,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
use crate::models::sonarr_models::IndexerSettings;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{handle_prompt_left_right_keys, matches_key};
|
||||
use crate::models::Route;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_indexer_settings_handler_tests.rs"]
|
||||
@@ -202,7 +203,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::handlers::sonarr_handlers::indexers::test_all_indexers_handler::TestA
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::{BlockSelectionState, Route};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
ActiveSonarrBlock, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
|
||||
INDEXER_SETTINGS_SELECTION_BLOCKS, INDEXERS_BLOCKS,
|
||||
@@ -211,7 +211,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -101,7 +102,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for TestAllIndexersHandl
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
ADD_SERIES_BLOCKS, ADD_SERIES_SELECTION_BLOCKS, ActiveSonarrBlock,
|
||||
};
|
||||
use crate::models::sonarr_models::{AddSeriesBody, AddSeriesOptions, AddSeriesSearchResult};
|
||||
use crate::models::{BlockSelectionState, Scrollable};
|
||||
use crate::models::{BlockSelectionState, Route, Scrollable};
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{App, Key, handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
|
||||
|
||||
@@ -625,7 +625,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::{
|
||||
matches_key,
|
||||
models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS},
|
||||
};
|
||||
use crate::models::Route;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "delete_series_handler_tests.rs"]
|
||||
@@ -143,7 +144,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler<
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||
use crate::models::Scrollable;
|
||||
use crate::models::{Route, Scrollable};
|
||||
use crate::models::servarr_data::sonarr::modals::EditSeriesModal;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_SERIES_BLOCKS};
|
||||
use crate::models::sonarr_models::EditSeriesParams;
|
||||
@@ -471,7 +471,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::handlers::sonarr_handlers::library::season_details_handler::releases_
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS};
|
||||
use crate::models::sonarr_models::{SonarrRelease, SonarrReleaseDownloadBody};
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
@@ -370,7 +371,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ use crate::handlers::sonarr_handlers::library::episode_details_handler::EpisodeD
|
||||
use crate::handlers::sonarr_handlers::library::season_details_handler::SeasonDetailsHandler;
|
||||
use crate::handlers::sonarr_handlers::library::series_details_handler::SeriesDetailsHandler;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::models::Route;
|
||||
|
||||
mod add_series_handler;
|
||||
mod delete_series_handler;
|
||||
@@ -245,7 +246,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::models::sonarr_models::{
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use serde_json::Number;
|
||||
use crate::models::Route;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "season_details_handler_tests.rs"]
|
||||
@@ -458,7 +459,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::handlers::sonarr_handlers::history::history_sorting_options;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::{BlockSelectionState, Route};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
ActiveSonarrBlock, EDIT_SERIES_SELECTION_BLOCKS, SERIES_DETAILS_BLOCKS,
|
||||
};
|
||||
@@ -342,7 +342,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use system::SystemHandler;
|
||||
use crate::{
|
||||
app::App, event::Key, matches_key, models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
|
||||
};
|
||||
|
||||
use crate::models::Route;
|
||||
use super::KeyEventHandler;
|
||||
|
||||
mod blocklist;
|
||||
@@ -115,7 +115,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::event::Key;
|
||||
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use crate::models::{HorizontallyScrollableText, Route};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ROOT_FOLDERS_BLOCKS};
|
||||
use crate::models::servarr_models::AddRootFolderBody;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
@@ -229,7 +229,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::sonarr_handlers::system::system_details_handler::SystemDetailsHandler;
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors};
|
||||
use crate::matches_key;
|
||||
use crate::models::Scrollable;
|
||||
use crate::models::{Route, Scrollable};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||
|
||||
mod system_details_handler;
|
||||
@@ -129,7 +129,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::Scrollable;
|
||||
use crate::models::{Route, Scrollable};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS};
|
||||
use crate::models::sonarr_models::SonarrTaskName;
|
||||
use crate::models::stateful_list::StatefulList;
|
||||
@@ -201,7 +201,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ mod tests {
|
||||
use crate::models::servarr_models::Language;
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use rstest::rstest;
|
||||
use crate::models::Route;
|
||||
|
||||
struct TableHandlerUnit<'a, 'b> {
|
||||
key: Key,
|
||||
@@ -98,7 +99,7 @@ mod tests {
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
fn current_route(&self) -> Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ impl From<(&i64, &String)> for MetadataProfile {
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
clap::ValueEnum,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
@@ -205,6 +206,21 @@ pub struct DeleteArtistParams {
|
||||
pub add_import_list_exclusion: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditArtistParams {
|
||||
pub artist_id: i64,
|
||||
pub monitored: Option<bool>,
|
||||
pub monitor_new_items: Option<NewItemMonitorType>,
|
||||
pub quality_profile_id: Option<i64>,
|
||||
pub metadata_profile_id: Option<i64>,
|
||||
pub root_folder_path: Option<String>,
|
||||
pub tags: Option<Vec<i64>>,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub tag_input_string: Option<String>,
|
||||
pub clear_tags: bool,
|
||||
}
|
||||
|
||||
impl From<LidarrSerdeable> for Serdeable {
|
||||
fn from(value: LidarrSerdeable) -> Serdeable {
|
||||
Serdeable::Lidarr(value)
|
||||
@@ -223,6 +239,7 @@ serde_enum_from!(
|
||||
RootFolders(Vec<RootFolder>),
|
||||
SecurityConfig(SecurityConfig),
|
||||
SystemStatus(SystemStatus),
|
||||
Tag(Tag),
|
||||
Tags(Vec<Tag>),
|
||||
Value(Value),
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use serde_json::Number;
|
||||
|
||||
use crate::app::lidarr::lidarr_context_clues::ARTISTS_CONTEXT_CLUES;
|
||||
use crate::models::{
|
||||
BlockSelectionState, Route, TabRoute, TabState,
|
||||
@@ -8,9 +10,17 @@ use crate::models::{
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
use bimap::BiMap;
|
||||
use chrono::{DateTime, Utc};
|
||||
use strum::EnumIter;
|
||||
use strum::{EnumIter};
|
||||
use super::modals::EditArtistModal;
|
||||
#[cfg(test)]
|
||||
use strum::{Display, EnumString};
|
||||
use {
|
||||
strum::{Display, EnumString, IntoEnumIterator},
|
||||
crate::models::lidarr_models::NewItemMonitorType,
|
||||
crate::models::stateful_table::SortOption,
|
||||
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::quality_profile_map,
|
||||
crate::network::servarr_test_utils::diskspace,
|
||||
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{download_record, metadata_profile, metadata_profile_map, quality_profile, root_folder, tags_map},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "lidarr_data_tests.rs"]
|
||||
@@ -22,6 +32,7 @@ pub struct LidarrData<'a> {
|
||||
pub delete_artist_files: bool,
|
||||
pub disk_space_vec: Vec<DiskSpace>,
|
||||
pub downloads: StatefulTable<DownloadRecord>,
|
||||
pub edit_artist_modal: Option<EditArtistModal>,
|
||||
pub main_tabs: TabState,
|
||||
pub metadata_profile_map: BiMap<i64, String>,
|
||||
pub prompt_confirm: bool,
|
||||
@@ -39,6 +50,31 @@ impl LidarrData<'_> {
|
||||
self.delete_artist_files = false;
|
||||
self.add_import_list_exclusion = false;
|
||||
}
|
||||
|
||||
pub fn tag_ids_to_display(&self, tag_ids: &[Number]) -> String {
|
||||
tag_ids
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
let id = id.as_i64()?;
|
||||
self.tags_map.get_by_left(&id).cloned()
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
pub fn sorted_quality_profile_names(&self) -> Vec<String> {
|
||||
let mut quality_profile_names: Vec<String> =
|
||||
self.quality_profile_map.right_values().cloned().collect();
|
||||
quality_profile_names.sort();
|
||||
quality_profile_names
|
||||
}
|
||||
|
||||
pub fn sorted_metadata_profile_names(&self) -> Vec<String> {
|
||||
let mut metadata_profile_names: Vec<String> =
|
||||
self.metadata_profile_map.right_values().cloned().collect();
|
||||
metadata_profile_names.sort();
|
||||
metadata_profile_names
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for LidarrData<'a> {
|
||||
@@ -49,6 +85,7 @@ impl<'a> Default for LidarrData<'a> {
|
||||
delete_artist_files: false,
|
||||
disk_space_vec: Vec::new(),
|
||||
downloads: StatefulTable::default(),
|
||||
edit_artist_modal: None,
|
||||
metadata_profile_map: BiMap::new(),
|
||||
prompt_confirm: false,
|
||||
prompt_confirm_action: None,
|
||||
@@ -71,11 +108,25 @@ impl<'a> Default for LidarrData<'a> {
|
||||
#[cfg(test)]
|
||||
impl LidarrData<'_> {
|
||||
pub fn test_default_fully_populated() -> Self {
|
||||
use crate::models::lidarr_models::{Artist, DownloadRecord};
|
||||
use crate::models::servarr_models::{DiskSpace, RootFolder};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
let mut edit_artist_modal = EditArtistModal {
|
||||
monitored: Some(true),
|
||||
path: "/nfs/music".into(),
|
||||
tags: "alex".into(),
|
||||
..EditArtistModal::default()
|
||||
};
|
||||
edit_artist_modal.monitor_list.set_items(NewItemMonitorType::iter().collect());
|
||||
edit_artist_modal.quality_profile_list.set_items(vec![quality_profile().name]);
|
||||
edit_artist_modal.metadata_profile_list.set_items(vec![metadata_profile().name]);
|
||||
|
||||
let mut lidarr_data = LidarrData::default();
|
||||
let mut lidarr_data = LidarrData {
|
||||
delete_artist_files: true,
|
||||
disk_space_vec: vec![diskspace()],
|
||||
quality_profile_map: quality_profile_map(),
|
||||
metadata_profile_map: metadata_profile_map(),
|
||||
edit_artist_modal: Some(edit_artist_modal),
|
||||
tags_map: tags_map(),
|
||||
..LidarrData::default()
|
||||
};
|
||||
lidarr_data.artists.set_items(vec![Artist::default()]);
|
||||
lidarr_data.artists.sorting(vec![SortOption {
|
||||
name: "Name",
|
||||
@@ -83,19 +134,12 @@ impl LidarrData<'_> {
|
||||
}]);
|
||||
lidarr_data.artists.search = Some("artist search".into());
|
||||
lidarr_data.artists.filter = Some("artist filter".into());
|
||||
lidarr_data.quality_profile_map = BiMap::from_iter([(1i64, "Lossless".to_owned())]);
|
||||
lidarr_data.metadata_profile_map = BiMap::from_iter([(1i64, "Standard".to_owned())]);
|
||||
lidarr_data.tags_map = BiMap::from_iter([(1i64, "usenet".to_owned())]);
|
||||
lidarr_data.disk_space_vec = vec![DiskSpace {
|
||||
free_space: 50000000000,
|
||||
total_space: 100000000000,
|
||||
}];
|
||||
lidarr_data
|
||||
.downloads
|
||||
.set_items(vec![DownloadRecord::default()]);
|
||||
.set_items(vec![download_record()]);
|
||||
lidarr_data
|
||||
.root_folders
|
||||
.set_items(vec![RootFolder::default()]);
|
||||
.set_items(vec![root_folder()]);
|
||||
lidarr_data.version = "1.0.0".to_owned();
|
||||
|
||||
lidarr_data
|
||||
@@ -112,6 +156,14 @@ pub enum ActiveLidarrBlock {
|
||||
DeleteArtistConfirmPrompt,
|
||||
DeleteArtistToggleDeleteFile,
|
||||
DeleteArtistToggleAddListExclusion,
|
||||
EditArtistPrompt,
|
||||
EditArtistConfirmPrompt,
|
||||
EditArtistPathInput,
|
||||
EditArtistSelectMetadataProfile,
|
||||
EditArtistSelectMonitorNewItems,
|
||||
EditArtistSelectQualityProfile,
|
||||
EditArtistTagsInput,
|
||||
EditArtistToggleMonitored,
|
||||
FilterArtists,
|
||||
FilterArtistsError,
|
||||
SearchArtists,
|
||||
@@ -142,6 +194,27 @@ pub const DELETE_ARTIST_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[
|
||||
&[ActiveLidarrBlock::DeleteArtistConfirmPrompt],
|
||||
];
|
||||
|
||||
pub static EDIT_ARTIST_BLOCKS: [ActiveLidarrBlock; 8] = [
|
||||
ActiveLidarrBlock::EditArtistPrompt,
|
||||
ActiveLidarrBlock::EditArtistConfirmPrompt,
|
||||
ActiveLidarrBlock::EditArtistPathInput,
|
||||
ActiveLidarrBlock::EditArtistSelectMetadataProfile,
|
||||
ActiveLidarrBlock::EditArtistSelectMonitorNewItems,
|
||||
ActiveLidarrBlock::EditArtistSelectQualityProfile,
|
||||
ActiveLidarrBlock::EditArtistTagsInput,
|
||||
ActiveLidarrBlock::EditArtistToggleMonitored,
|
||||
];
|
||||
|
||||
pub const EDIT_ARTIST_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[
|
||||
&[ActiveLidarrBlock::EditArtistToggleMonitored],
|
||||
&[ActiveLidarrBlock::EditArtistSelectMonitorNewItems],
|
||||
&[ActiveLidarrBlock::EditArtistSelectQualityProfile],
|
||||
&[ActiveLidarrBlock::EditArtistSelectMetadataProfile],
|
||||
&[ActiveLidarrBlock::EditArtistPathInput],
|
||||
&[ActiveLidarrBlock::EditArtistTagsInput],
|
||||
&[ActiveLidarrBlock::EditArtistConfirmPrompt],
|
||||
];
|
||||
|
||||
impl From<ActiveLidarrBlock> for Route {
|
||||
fn from(active_lidarr_block: ActiveLidarrBlock) -> Route {
|
||||
Route::Lidarr(active_lidarr_block, None)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bimap::BiMap;
|
||||
use crate::app::lidarr::lidarr_context_clues::ARTISTS_CONTEXT_CLUES;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||
DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS,
|
||||
};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS};
|
||||
use crate::models::{
|
||||
BlockSelectionState, Route,
|
||||
servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, LIBRARY_BLOCKS, LidarrData},
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use serde_json::Number;
|
||||
|
||||
#[test]
|
||||
fn test_from_active_lidarr_block_to_route() {
|
||||
@@ -41,6 +41,50 @@ mod tests {
|
||||
assert!(!lidarr_data.add_import_list_exclusion);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tag_ids_to_display() {
|
||||
let mut tags_map = BiMap::new();
|
||||
tags_map.insert(3, "test 3".to_owned());
|
||||
tags_map.insert(2, "test 2".to_owned());
|
||||
tags_map.insert(1, "test 1".to_owned());
|
||||
let lidarr_data = LidarrData {
|
||||
tags_map,
|
||||
..LidarrData::default()
|
||||
};
|
||||
|
||||
assert_str_eq!(lidarr_data.tag_ids_to_display(&[Number::from(1), Number::from(2)]), "test 1, test 2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sorted_quality_profile_names() {
|
||||
let mut quality_profile_map = BiMap::new();
|
||||
quality_profile_map.insert(3, "test 3".to_owned());
|
||||
quality_profile_map.insert(2, "test 2".to_owned());
|
||||
quality_profile_map.insert(1, "test 1".to_owned());
|
||||
let lidarr_data = LidarrData {
|
||||
quality_profile_map,
|
||||
..LidarrData::default()
|
||||
};
|
||||
let expected_quality_profile_vec = vec!["test 1".to_owned(), "test 2".to_owned(), "test 3".to_owned()];
|
||||
|
||||
assert_iter_eq!(lidarr_data.sorted_quality_profile_names(), expected_quality_profile_vec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sorted_metadata_profile_names() {
|
||||
let mut metadata_profile_map = BiMap::new();
|
||||
metadata_profile_map.insert(3, "test 3".to_owned());
|
||||
metadata_profile_map.insert(2, "test 2".to_owned());
|
||||
metadata_profile_map.insert(1, "test 1".to_owned());
|
||||
let lidarr_data = LidarrData {
|
||||
metadata_profile_map,
|
||||
..LidarrData::default()
|
||||
};
|
||||
let expected_metadata_profile_vec = vec!["test 1".to_owned(), "test 2".to_owned(), "test 3".to_owned()];
|
||||
|
||||
assert_iter_eq!(lidarr_data.sorted_metadata_profile_names(), expected_metadata_profile_vec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_data_default() {
|
||||
let lidarr_data = LidarrData::default();
|
||||
@@ -50,6 +94,7 @@ mod tests {
|
||||
assert!(!lidarr_data.delete_artist_files);
|
||||
assert_is_empty!(lidarr_data.disk_space_vec);
|
||||
assert_is_empty!(lidarr_data.downloads);
|
||||
assert_none!(lidarr_data.edit_artist_modal);
|
||||
assert_is_empty!(lidarr_data.metadata_profile_map);
|
||||
assert!(!lidarr_data.prompt_confirm);
|
||||
assert_none!(lidarr_data.prompt_confirm_action);
|
||||
@@ -113,4 +158,31 @@ mod tests {
|
||||
);
|
||||
assert_none!(delete_artist_block_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_blocks() {
|
||||
assert_eq!(EDIT_ARTIST_BLOCKS.len(), 8);
|
||||
assert!(EDIT_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::EditArtistPrompt));
|
||||
assert!(EDIT_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::EditArtistConfirmPrompt));
|
||||
assert!(EDIT_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::EditArtistPathInput));
|
||||
assert!(EDIT_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::EditArtistSelectMetadataProfile));
|
||||
assert!(EDIT_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::EditArtistSelectMonitorNewItems));
|
||||
assert!(EDIT_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::EditArtistSelectQualityProfile));
|
||||
assert!(EDIT_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::EditArtistTagsInput));
|
||||
assert!(EDIT_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::EditArtistToggleMonitored));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_selection_blocks_ordering() {
|
||||
let mut edit_artist_block_iter = EDIT_ARTIST_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(edit_artist_block_iter.next().unwrap(), &[ActiveLidarrBlock::EditArtistToggleMonitored]);
|
||||
assert_eq!(edit_artist_block_iter.next().unwrap(), &[ActiveLidarrBlock::EditArtistSelectMonitorNewItems]);
|
||||
assert_eq!(edit_artist_block_iter.next().unwrap(), &[ActiveLidarrBlock::EditArtistSelectQualityProfile]);
|
||||
assert_eq!(edit_artist_block_iter.next().unwrap(), &[ActiveLidarrBlock::EditArtistSelectMetadataProfile]);
|
||||
assert_eq!(edit_artist_block_iter.next().unwrap(), &[ActiveLidarrBlock::EditArtistPathInput]);
|
||||
assert_eq!(edit_artist_block_iter.next().unwrap(), &[ActiveLidarrBlock::EditArtistTagsInput]);
|
||||
assert_eq!(edit_artist_block_iter.next().unwrap(), &[ActiveLidarrBlock::EditArtistConfirmPrompt]);
|
||||
assert_none!(edit_artist_block_iter.next());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod lidarr_data;
|
||||
pub mod modals;
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use super::lidarr_data::LidarrData;
|
||||
use crate::models::{
|
||||
HorizontallyScrollableText, lidarr_models::NewItemMonitorType, stateful_list::StatefulList,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "modals_tests.rs"]
|
||||
mod modals_tests;
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub struct EditArtistModal {
|
||||
pub monitor_list: StatefulList<NewItemMonitorType>,
|
||||
pub quality_profile_list: StatefulList<String>,
|
||||
pub metadata_profile_list: StatefulList<String>,
|
||||
pub monitored: Option<bool>,
|
||||
pub path: HorizontallyScrollableText,
|
||||
pub tags: HorizontallyScrollableText,
|
||||
}
|
||||
|
||||
impl From<&LidarrData<'_>> for EditArtistModal {
|
||||
fn from(lidarr_data: &LidarrData<'_>) -> EditArtistModal {
|
||||
let mut edit_artist_modal = EditArtistModal::default();
|
||||
let artist = lidarr_data.artists.current_selection();
|
||||
|
||||
edit_artist_modal
|
||||
.monitor_list
|
||||
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
|
||||
edit_artist_modal.path = artist.path.clone().into();
|
||||
edit_artist_modal.tags = lidarr_data.tag_ids_to_display(&artist.tags).into();
|
||||
edit_artist_modal.monitored = Some(artist.monitored);
|
||||
|
||||
let monitor_index = edit_artist_modal
|
||||
.monitor_list
|
||||
.items
|
||||
.iter()
|
||||
.position(|m| *m == artist.monitor_new_items);
|
||||
edit_artist_modal.monitor_list.state.select(monitor_index);
|
||||
|
||||
edit_artist_modal
|
||||
.quality_profile_list
|
||||
.set_items(lidarr_data.sorted_quality_profile_names());
|
||||
let quality_profile_name = lidarr_data
|
||||
.quality_profile_map
|
||||
.get_by_left(&artist.quality_profile_id)
|
||||
.unwrap();
|
||||
let quality_profile_index = edit_artist_modal
|
||||
.quality_profile_list
|
||||
.items
|
||||
.iter()
|
||||
.position(|profile| profile == quality_profile_name);
|
||||
edit_artist_modal
|
||||
.quality_profile_list
|
||||
.state
|
||||
.select(quality_profile_index);
|
||||
|
||||
edit_artist_modal
|
||||
.metadata_profile_list
|
||||
.set_items(lidarr_data.sorted_metadata_profile_names());
|
||||
let metadata_profile_name = lidarr_data
|
||||
.metadata_profile_map
|
||||
.get_by_left(&artist.metadata_profile_id)
|
||||
.unwrap();
|
||||
let metadata_profile_index = edit_artist_modal
|
||||
.metadata_profile_list
|
||||
.items
|
||||
.iter()
|
||||
.position(|profile| profile == metadata_profile_name);
|
||||
edit_artist_modal
|
||||
.metadata_profile_list
|
||||
.state
|
||||
.select(metadata_profile_index);
|
||||
|
||||
edit_artist_modal
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
|
||||
use crate::models::lidarr_models::{Artist, NewItemMonitorType};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::LidarrData;
|
||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_modal_from_lidarr_data() {
|
||||
let mut lidarr_data = LidarrData {
|
||||
quality_profile_map: BiMap::from_iter([(1i64, "HD - 1080p".to_owned()), (2i64, "Any".to_owned())]),
|
||||
metadata_profile_map: BiMap::from_iter([(1i64, "Standard".to_owned()), (2i64, "None".to_owned())]),
|
||||
tags_map: BiMap::from_iter([(1i64, "usenet".to_owned())]),
|
||||
..LidarrData::default()
|
||||
};
|
||||
let artist = Artist {
|
||||
id: 1,
|
||||
monitored: true,
|
||||
monitor_new_items: NewItemMonitorType::All,
|
||||
quality_profile_id: 1,
|
||||
metadata_profile_id: 1,
|
||||
path: "/nfs/music/test_artist".to_owned(),
|
||||
tags: vec![serde_json::Number::from(1)],
|
||||
..Artist::default()
|
||||
};
|
||||
lidarr_data.artists.set_items(vec![artist]);
|
||||
|
||||
let edit_artist_modal = EditArtistModal::from(&lidarr_data);
|
||||
|
||||
assert_eq!(edit_artist_modal.monitored, Some(true));
|
||||
assert_eq!(
|
||||
*edit_artist_modal.monitor_list.current_selection(),
|
||||
NewItemMonitorType::All
|
||||
);
|
||||
assert_str_eq!(
|
||||
edit_artist_modal.quality_profile_list.current_selection(),
|
||||
"HD - 1080p"
|
||||
);
|
||||
assert_str_eq!(
|
||||
edit_artist_modal.metadata_profile_list.current_selection(),
|
||||
"Standard"
|
||||
);
|
||||
assert_str_eq!(edit_artist_modal.path.text, "/nfs/music/test_artist");
|
||||
assert_str_eq!(edit_artist_modal.tags.text, "usenet");
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod radarr_data_tests {
|
||||
use bimap::BiMap;
|
||||
use chrono::{DateTime, Utc};
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
|
||||
use serde_json::Number;
|
||||
use crate::app::context_clues::{
|
||||
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||
@@ -61,6 +62,35 @@ mod tests {
|
||||
assert_movie_info_tabs_reset!(radarr_data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tag_ids_to_display() {
|
||||
let mut tags_map = BiMap::new();
|
||||
tags_map.insert(3, "test 3".to_owned());
|
||||
tags_map.insert(2, "test 2".to_owned());
|
||||
tags_map.insert(1, "test 1".to_owned());
|
||||
let radarr_data = RadarrData {
|
||||
tags_map,
|
||||
..RadarrData::default()
|
||||
};
|
||||
|
||||
assert_str_eq!(radarr_data.tag_ids_to_display(&[Number::from(1), Number::from(2)]), "test 1, test 2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sorted_quality_profile_names() {
|
||||
let mut quality_profile_map = BiMap::new();
|
||||
quality_profile_map.insert(3, "test 3".to_owned());
|
||||
quality_profile_map.insert(2, "test 2".to_owned());
|
||||
quality_profile_map.insert(1, "test 1".to_owned());
|
||||
let radarr_data = RadarrData {
|
||||
quality_profile_map,
|
||||
..RadarrData::default()
|
||||
};
|
||||
let expected_quality_profile_vec = vec!["test 1".to_owned(), "test 2".to_owned(), "test 3".to_owned()];
|
||||
|
||||
assert_iter_eq!(radarr_data.sorted_quality_profile_names(), expected_quality_profile_vec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_radarr_data_defaults() {
|
||||
let radarr_data = RadarrData::default();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod sonarr_data_tests {
|
||||
use bimap::BiMap;
|
||||
use chrono::{DateTime, Utc};
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
|
||||
use serde_json::Number;
|
||||
use crate::app::sonarr::sonarr_context_clues::SERIES_HISTORY_CONTEXT_CLUES;
|
||||
use crate::models::sonarr_models::{Season, SonarrHistoryItem};
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
@@ -77,6 +78,50 @@ mod tests {
|
||||
assert_eq!(sonarr_data.series_info_tabs.index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tag_ids_to_display() {
|
||||
let mut tags_map = BiMap::new();
|
||||
tags_map.insert(3, "test 3".to_owned());
|
||||
tags_map.insert(2, "test 2".to_owned());
|
||||
tags_map.insert(1, "test 1".to_owned());
|
||||
let sonarr_data = SonarrData {
|
||||
tags_map,
|
||||
..SonarrData::default()
|
||||
};
|
||||
|
||||
assert_str_eq!(sonarr_data.tag_ids_to_display(&[Number::from(1), Number::from(2)]), "test 1, test 2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sorted_quality_profile_names() {
|
||||
let mut quality_profile_map = BiMap::new();
|
||||
quality_profile_map.insert(3, "test 3".to_owned());
|
||||
quality_profile_map.insert(2, "test 2".to_owned());
|
||||
quality_profile_map.insert(1, "test 1".to_owned());
|
||||
let sonarr_data = SonarrData {
|
||||
quality_profile_map,
|
||||
..SonarrData::default()
|
||||
};
|
||||
let expected_quality_profile_vec = vec!["test 1".to_owned(), "test 2".to_owned(), "test 3".to_owned()];
|
||||
|
||||
assert_iter_eq!(sonarr_data.sorted_quality_profile_names(), expected_quality_profile_vec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sorted_language_profile_names() {
|
||||
let mut language_profiles_map = BiMap::new();
|
||||
language_profiles_map.insert(3, "test 3".to_owned());
|
||||
language_profiles_map.insert(2, "test 2".to_owned());
|
||||
language_profiles_map.insert(1, "test 1".to_owned());
|
||||
let sonarr_data = SonarrData {
|
||||
language_profiles_map,
|
||||
..SonarrData::default()
|
||||
};
|
||||
let expected_language_profiles_vec = vec!["test 1".to_owned(), "test 2".to_owned(), "test 3".to_owned()];
|
||||
|
||||
assert_iter_eq!(sonarr_data.sorted_language_profile_names(), expected_language_profiles_vec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sonarr_data_defaults() {
|
||||
let sonarr_data = SonarrData::default();
|
||||
|
||||
@@ -3,7 +3,7 @@ use log::{debug, info, warn};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::models::Route;
|
||||
use crate::models::lidarr_models::{Artist, DeleteArtistParams};
|
||||
use crate::models::lidarr_models::{Artist, DeleteArtistParams, EditArtistParams};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||
use crate::models::servarr_models::CommandBody;
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
@@ -168,4 +168,119 @@ impl Network<'_, '_> {
|
||||
.handle_request::<CommandBody, Value>(request_props, |_, _| ())
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::network::lidarr_network) async fn edit_artist(
|
||||
&mut self,
|
||||
mut edit_artist_params: EditArtistParams,
|
||||
) -> Result<()> {
|
||||
info!("Editing Lidarr artist");
|
||||
if let Some(tag_input_str) = edit_artist_params.tag_input_string.as_ref() {
|
||||
let tag_ids_vec = self.extract_and_add_lidarr_tag_ids_vec(tag_input_str).await;
|
||||
edit_artist_params.tags = Some(tag_ids_vec);
|
||||
}
|
||||
let artist_id = edit_artist_params.artist_id;
|
||||
let detail_event = LidarrEvent::GetArtistDetails(artist_id);
|
||||
let event = LidarrEvent::EditArtist(EditArtistParams::default());
|
||||
info!("Fetching artist details for artist with ID: {artist_id}");
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(
|
||||
detail_event,
|
||||
RequestMethod::Get,
|
||||
None::<()>,
|
||||
Some(format!("/{artist_id}")),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut response = String::new();
|
||||
|
||||
self
|
||||
.handle_request::<(), Value>(request_props, |detailed_artist_body, _| {
|
||||
response = detailed_artist_body.to_string()
|
||||
})
|
||||
.await?;
|
||||
|
||||
info!("Constructing edit artist body");
|
||||
|
||||
let mut detailed_artist_body: Value = serde_json::from_str(&response)?;
|
||||
let (
|
||||
monitored,
|
||||
monitor_new_items,
|
||||
quality_profile_id,
|
||||
metadata_profile_id,
|
||||
root_folder_path,
|
||||
tags,
|
||||
) = {
|
||||
let monitored = edit_artist_params.monitored.unwrap_or(
|
||||
detailed_artist_body["monitored"]
|
||||
.as_bool()
|
||||
.expect("Unable to deserialize 'monitored'"),
|
||||
);
|
||||
let monitor_new_items = edit_artist_params.monitor_new_items.unwrap_or_else(|| {
|
||||
serde_json::from_value(detailed_artist_body["monitorNewItems"].clone())
|
||||
.expect("Unable to deserialize 'monitorNewItems'")
|
||||
});
|
||||
let quality_profile_id = edit_artist_params.quality_profile_id.unwrap_or_else(|| {
|
||||
detailed_artist_body["qualityProfileId"]
|
||||
.as_i64()
|
||||
.expect("Unable to deserialize 'qualityProfileId'")
|
||||
});
|
||||
let metadata_profile_id = edit_artist_params.metadata_profile_id.unwrap_or_else(|| {
|
||||
detailed_artist_body["metadataProfileId"]
|
||||
.as_i64()
|
||||
.expect("Unable to deserialize 'metadataProfileId'")
|
||||
});
|
||||
let root_folder_path = edit_artist_params.root_folder_path.unwrap_or_else(|| {
|
||||
detailed_artist_body["path"]
|
||||
.as_str()
|
||||
.expect("Unable to deserialize 'path'")
|
||||
.to_owned()
|
||||
});
|
||||
let tags = if edit_artist_params.clear_tags {
|
||||
vec![]
|
||||
} else {
|
||||
edit_artist_params.tags.unwrap_or(
|
||||
detailed_artist_body["tags"]
|
||||
.as_array()
|
||||
.expect("Unable to deserialize 'tags'")
|
||||
.iter()
|
||||
.map(|item| item.as_i64().expect("Unable to deserialize tag ID"))
|
||||
.collect(),
|
||||
)
|
||||
};
|
||||
|
||||
(
|
||||
monitored,
|
||||
monitor_new_items,
|
||||
quality_profile_id,
|
||||
metadata_profile_id,
|
||||
root_folder_path,
|
||||
tags,
|
||||
)
|
||||
};
|
||||
|
||||
*detailed_artist_body.get_mut("monitored").unwrap() = json!(monitored);
|
||||
*detailed_artist_body.get_mut("monitorNewItems").unwrap() = json!(monitor_new_items);
|
||||
*detailed_artist_body.get_mut("qualityProfileId").unwrap() = json!(quality_profile_id);
|
||||
*detailed_artist_body.get_mut("metadataProfileId").unwrap() = json!(metadata_profile_id);
|
||||
*detailed_artist_body.get_mut("path").unwrap() = json!(root_folder_path);
|
||||
*detailed_artist_body.get_mut("tags").unwrap() = json!(tags);
|
||||
|
||||
debug!("Edit artist body: {detailed_artist_body:?}");
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(
|
||||
event,
|
||||
RequestMethod::Put,
|
||||
Some(detailed_artist_body),
|
||||
Some(format!("/{artist_id}")),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<Value, ()>(request_props, |_, _| ())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#[cfg(test)]
|
||||
#[allow(dead_code)] // TODO: maybe remove?
|
||||
pub mod test_utils {
|
||||
use crate::models::lidarr_models::{Artist, ArtistStatistics, ArtistStatus, DownloadRecord, DownloadStatus, DownloadsResponse, Member, MetadataProfile, NewItemMonitorType, Ratings, SystemStatus};
|
||||
use crate::models::servarr_models::{QualityProfile, RootFolder, Tag};
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use bimap::BiMap;
|
||||
use chrono::DateTime;
|
||||
use serde_json::Number;
|
||||
|
||||
pub fn member() -> Member {
|
||||
Member {
|
||||
name: Some("alex".to_owned()),
|
||||
instrument: Some("piano".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ratings() -> Ratings {
|
||||
Ratings {
|
||||
votes: 15,
|
||||
value: 8.4
|
||||
}
|
||||
}
|
||||
|
||||
pub fn artist_statistics() -> ArtistStatistics {
|
||||
ArtistStatistics {
|
||||
album_count: 1,
|
||||
track_file_count: 15,
|
||||
track_count: 15,
|
||||
total_track_count: 15,
|
||||
size_on_disk: 12345,
|
||||
percent_of_tracks: 99.9
|
||||
}
|
||||
}
|
||||
|
||||
pub fn artist() -> Artist {
|
||||
Artist {
|
||||
id: 1,
|
||||
artist_name: "Alex".into(),
|
||||
foreign_artist_id: "test-foreign-id".to_owned(),
|
||||
status: ArtistStatus::Continuing,
|
||||
overview: Some("some interesting description of the artist".to_owned()),
|
||||
artist_type: Some("Person".to_owned()),
|
||||
disambiguation: Some("American pianist".to_owned()),
|
||||
members: Some(vec![member()]),
|
||||
path: "/nfs/music/test-artist".to_owned(),
|
||||
quality_profile_id: quality_profile().id,
|
||||
metadata_profile_id: metadata_profile().id,
|
||||
monitored: true,
|
||||
monitor_new_items: NewItemMonitorType::All,
|
||||
genres: vec!["soundtrack".to_owned()],
|
||||
tags: vec![Number::from(tag().id)],
|
||||
added: DateTime::from(DateTime::parse_from_rfc3339("2023-01-01T00:00:00Z").unwrap()),
|
||||
ratings: Some(ratings()),
|
||||
statistics: Some(artist_statistics())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn quality_profile() -> QualityProfile {
|
||||
QualityProfile {
|
||||
id: 1,
|
||||
name: "Lossless".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn quality_profile_map() -> BiMap<i64, String> {
|
||||
let quality_profile = quality_profile();
|
||||
BiMap::from_iter(vec![(quality_profile.id, quality_profile.name)])
|
||||
}
|
||||
|
||||
pub fn metadata_profile() -> MetadataProfile {
|
||||
MetadataProfile {
|
||||
id: 1,
|
||||
name: "Standard".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metadata_profile_map() -> BiMap<i64, String> {
|
||||
let metadata_profile = metadata_profile();
|
||||
BiMap::from_iter(vec![(metadata_profile.id, metadata_profile.name)])
|
||||
}
|
||||
|
||||
pub fn tag() -> Tag {
|
||||
Tag {
|
||||
id: 1,
|
||||
label: "alex".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tags_map() -> BiMap<i64, String> {
|
||||
let tag = tag();
|
||||
BiMap::from_iter(vec![(tag.id, tag.label)])
|
||||
}
|
||||
|
||||
pub fn download_record() -> DownloadRecord {
|
||||
DownloadRecord {
|
||||
title: "Test download title".to_owned(),
|
||||
status: DownloadStatus::Downloading,
|
||||
id: 1,
|
||||
album_id: Some(Number::from(1i64)),
|
||||
artist_id: Some(Number::from(1i64)),
|
||||
size: 3543348019f64,
|
||||
sizeleft: 1771674009f64,
|
||||
output_path: Some(HorizontallyScrollableText::from("/nfs/music/alex/album")),
|
||||
indexer: "kickass torrents".to_owned(),
|
||||
download_client: Some("transmission".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn downloads_response() -> DownloadsResponse {
|
||||
DownloadsResponse {
|
||||
records: vec![download_record()]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_status() -> SystemStatus {
|
||||
SystemStatus {
|
||||
version: "1.0".to_owned(),
|
||||
start_time: DateTime::from(DateTime::parse_from_rfc3339("2023-01-01T00:00:00Z").unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_folder() -> RootFolder {
|
||||
RootFolder {
|
||||
id: 1,
|
||||
path: "/nfs".to_owned(),
|
||||
accessible: true,
|
||||
free_space: 219902325555200,
|
||||
unmapped_folders: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ use anyhow::Result;
|
||||
use log::info;
|
||||
|
||||
use super::{NetworkEvent, NetworkResource};
|
||||
use crate::models::lidarr_models::{DeleteArtistParams, LidarrSerdeable, MetadataProfile};
|
||||
use crate::models::lidarr_models::{
|
||||
DeleteArtistParams, EditArtistParams, LidarrSerdeable, MetadataProfile,
|
||||
};
|
||||
use crate::models::servarr_models::{QualityProfile, Tag};
|
||||
use crate::network::{Network, RequestMethod};
|
||||
|
||||
@@ -15,9 +17,15 @@ mod system;
|
||||
#[path = "lidarr_network_tests.rs"]
|
||||
mod lidarr_network_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "lidarr_network_test_utils.rs"]
|
||||
pub mod lidarr_network_test_utils;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum LidarrEvent {
|
||||
AddTag(String),
|
||||
DeleteArtist(DeleteArtistParams),
|
||||
EditArtist(EditArtistParams),
|
||||
GetArtistDetails(i64),
|
||||
GetDiskSpace,
|
||||
GetDownloads(u64),
|
||||
@@ -37,7 +45,9 @@ pub enum LidarrEvent {
|
||||
impl NetworkResource for LidarrEvent {
|
||||
fn resource(&self) -> &'static str {
|
||||
match &self {
|
||||
LidarrEvent::AddTag(_) | LidarrEvent::GetTags => "/tag",
|
||||
LidarrEvent::DeleteArtist(_)
|
||||
| LidarrEvent::EditArtist(_)
|
||||
| LidarrEvent::GetArtistDetails(_)
|
||||
| LidarrEvent::ListArtists
|
||||
| LidarrEvent::ToggleArtistMonitoring(_) => "/artist",
|
||||
@@ -49,7 +59,6 @@ impl NetworkResource for LidarrEvent {
|
||||
LidarrEvent::GetQualityProfiles => "/qualityprofile",
|
||||
LidarrEvent::GetRootFolders => "/rootfolder",
|
||||
LidarrEvent::GetStatus => "/system/status",
|
||||
LidarrEvent::GetTags => "/tag",
|
||||
LidarrEvent::HealthCheck => "/health",
|
||||
}
|
||||
}
|
||||
@@ -67,6 +76,7 @@ impl Network<'_, '_> {
|
||||
lidarr_event: LidarrEvent,
|
||||
) -> Result<LidarrSerdeable> {
|
||||
match lidarr_event {
|
||||
LidarrEvent::AddTag(tag) => self.add_lidarr_tag(tag).await.map(LidarrSerdeable::from),
|
||||
LidarrEvent::DeleteArtist(params) => {
|
||||
self.delete_artist(params).await.map(LidarrSerdeable::from)
|
||||
}
|
||||
@@ -111,6 +121,7 @@ impl Network<'_, '_> {
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
LidarrEvent::UpdateAllArtists => self.update_all_artists().await.map(LidarrSerdeable::from),
|
||||
LidarrEvent::EditArtist(params) => self.edit_artist(params).await.map(LidarrSerdeable::from),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,4 +191,61 @@ impl Network<'_, '_> {
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn add_lidarr_tag(&mut self, tag: String) -> Result<Tag> {
|
||||
info!("Adding a new Lidarr tag");
|
||||
let event = LidarrEvent::AddTag(String::new());
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(
|
||||
event,
|
||||
RequestMethod::Post,
|
||||
Some(serde_json::json!({ "label": tag })),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<serde_json::Value, Tag>(request_props, |tag, mut app| {
|
||||
app.data.lidarr_data.tags_map.insert(tag.id, tag.label);
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::network::lidarr_network) async fn extract_and_add_lidarr_tag_ids_vec(
|
||||
&mut self,
|
||||
edit_tags: &str,
|
||||
) -> Vec<i64> {
|
||||
let missing_tags_vec = {
|
||||
let tags_map = &self.app.lock().await.data.lidarr_data.tags_map;
|
||||
edit_tags
|
||||
.split(',')
|
||||
.filter(|&tag| {
|
||||
!tag.is_empty() && tags_map.get_by_right(tag.to_lowercase().trim()).is_none()
|
||||
})
|
||||
.collect::<Vec<&str>>()
|
||||
};
|
||||
|
||||
for tag in missing_tags_vec {
|
||||
self
|
||||
.add_lidarr_tag(tag.trim().to_owned())
|
||||
.await
|
||||
.expect("Unable to add tag");
|
||||
}
|
||||
|
||||
let app = self.app.lock().await;
|
||||
edit_tags
|
||||
.split(',')
|
||||
.filter(|tag| !tag.is_empty())
|
||||
.map(|tag| {
|
||||
*app
|
||||
.data
|
||||
.lidarr_data
|
||||
.tags_map
|
||||
.get_by_right(tag.to_lowercase().trim())
|
||||
.unwrap()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,6 +284,7 @@ pub mod test_utils {
|
||||
subtitles: Some("English".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn quality() -> Quality {
|
||||
Quality {
|
||||
name: "Bluray-1080p".to_owned(),
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use ratatui::Frame;
|
||||
use ratatui::layout::{Constraint, Rect};
|
||||
use ratatui::prelude::Layout;
|
||||
use ratatui::widgets::ListItem;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_ARTIST_BLOCKS};
|
||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||
use crate::render_selectable_input_box;
|
||||
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::{title_block_centered};
|
||||
use crate::ui::widgets::button::Button;
|
||||
use crate::ui::widgets::checkbox::Checkbox;
|
||||
use crate::ui::widgets::input_box::InputBox;
|
||||
use crate::ui::widgets::popup::{Popup, Size};
|
||||
use crate::ui::widgets::selectable_list::SelectableList;
|
||||
use crate::ui::{DrawUi, draw_popup};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_artist_ui_tests.rs"]
|
||||
mod edit_artist_ui_tests;
|
||||
|
||||
pub(super) struct EditArtistUi;
|
||||
|
||||
impl DrawUi for EditArtistUi {
|
||||
fn accepts(route: Route) -> bool {
|
||||
let Route::Lidarr(active_lidarr_block, _) = route else {
|
||||
return false;
|
||||
};
|
||||
EDIT_ARTIST_BLOCKS.contains(&active_lidarr_block)
|
||||
}
|
||||
|
||||
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
|
||||
if let Route::Lidarr(active_lidarr_block, _context_option) = app.get_current_route() {
|
||||
let draw_edit_artist_prompt = |f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| {
|
||||
draw_edit_artist_confirmation_prompt(f, app, prompt_area);
|
||||
|
||||
match active_lidarr_block {
|
||||
ActiveLidarrBlock::EditArtistSelectMonitorNewItems => {
|
||||
draw_edit_artist_select_monitor_new_items_popup(f, app);
|
||||
}
|
||||
ActiveLidarrBlock::EditArtistSelectQualityProfile => {
|
||||
draw_edit_artist_select_quality_profile_popup(f, app);
|
||||
}
|
||||
ActiveLidarrBlock::EditArtistSelectMetadataProfile => {
|
||||
draw_edit_artist_select_metadata_profile_popup(f, app);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
};
|
||||
|
||||
draw_popup(f, app, draw_edit_artist_prompt, Size::Long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_edit_artist_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let artist_name = app
|
||||
.data
|
||||
.lidarr_data
|
||||
.artists
|
||||
.current_selection()
|
||||
.artist_name
|
||||
.text
|
||||
.clone();
|
||||
let title = format!("Edit - {artist_name}");
|
||||
f.render_widget(title_block_centered(&title), area);
|
||||
|
||||
let yes_no_value = app.data.lidarr_data.prompt_confirm;
|
||||
let selected_block = app.data.lidarr_data.selected_block.get_active_block();
|
||||
let highlight_yes_no = selected_block == ActiveLidarrBlock::EditArtistConfirmPrompt;
|
||||
let EditArtistModal {
|
||||
monitor_list,
|
||||
quality_profile_list,
|
||||
metadata_profile_list,
|
||||
monitored,
|
||||
path,
|
||||
tags,
|
||||
} = app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_ref()
|
||||
.expect("edit_artist_modal must exist in this context");
|
||||
let selected_monitor_new_items = monitor_list.current_selection();
|
||||
let selected_quality_profile = quality_profile_list.current_selection();
|
||||
let selected_metadata_profile = metadata_profile_list.current_selection();
|
||||
|
||||
let [
|
||||
_,
|
||||
monitored_area,
|
||||
monitor_new_items_area,
|
||||
quality_profile_area,
|
||||
metadata_profile_area,
|
||||
path_area,
|
||||
tags_area,
|
||||
_,
|
||||
buttons_area,
|
||||
] = Layout::vertical([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.margin(1)
|
||||
.areas(area);
|
||||
let [save_area, cancel_area] =
|
||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.areas(buttons_area);
|
||||
|
||||
let monitored_checkbox = Checkbox::new("Monitored")
|
||||
.checked(monitored.unwrap_or_default())
|
||||
.highlighted(selected_block == ActiveLidarrBlock::EditArtistToggleMonitored);
|
||||
let monitor_new_items_drop_down_button = Button::new()
|
||||
.title(selected_monitor_new_items.to_display_str())
|
||||
.label("Monitor New Albums")
|
||||
.icon("▼")
|
||||
.selected(selected_block == ActiveLidarrBlock::EditArtistSelectMonitorNewItems);
|
||||
let quality_profile_drop_down_button = Button::new()
|
||||
.title(selected_quality_profile)
|
||||
.label("Quality Profile")
|
||||
.icon("▼")
|
||||
.selected(selected_block == ActiveLidarrBlock::EditArtistSelectQualityProfile);
|
||||
let metadata_profile_drop_down_button = Button::new()
|
||||
.title(selected_metadata_profile)
|
||||
.label("Metadata Profile")
|
||||
.icon("▼")
|
||||
.selected(selected_block == ActiveLidarrBlock::EditArtistSelectMetadataProfile);
|
||||
|
||||
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
|
||||
let path_input_box = InputBox::new(&path.text)
|
||||
.offset(path.offset.load(Ordering::SeqCst))
|
||||
.label("Path")
|
||||
.highlighted(selected_block == ActiveLidarrBlock::EditArtistPathInput)
|
||||
.selected(active_lidarr_block == ActiveLidarrBlock::EditArtistPathInput);
|
||||
let tags_input_box = InputBox::new(&tags.text)
|
||||
.offset(tags.offset.load(Ordering::SeqCst))
|
||||
.label("Tags")
|
||||
.highlighted(selected_block == ActiveLidarrBlock::EditArtistTagsInput)
|
||||
.selected(active_lidarr_block == ActiveLidarrBlock::EditArtistTagsInput);
|
||||
|
||||
match active_lidarr_block {
|
||||
ActiveLidarrBlock::EditArtistPathInput => path_input_box.show_cursor(f, path_area),
|
||||
ActiveLidarrBlock::EditArtistTagsInput => tags_input_box.show_cursor(f, tags_area),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
render_selectable_input_box!(path_input_box, f, path_area);
|
||||
render_selectable_input_box!(tags_input_box, f, tags_area);
|
||||
}
|
||||
|
||||
let save_button = Button::new()
|
||||
.title("Save")
|
||||
.selected(yes_no_value && highlight_yes_no);
|
||||
let cancel_button = Button::new()
|
||||
.title("Cancel")
|
||||
.selected(!yes_no_value && highlight_yes_no);
|
||||
|
||||
f.render_widget(monitored_checkbox, monitored_area);
|
||||
f.render_widget(monitor_new_items_drop_down_button, monitor_new_items_area);
|
||||
f.render_widget(quality_profile_drop_down_button, quality_profile_area);
|
||||
f.render_widget(metadata_profile_drop_down_button, metadata_profile_area);
|
||||
f.render_widget(save_button, save_area);
|
||||
f.render_widget(cancel_button, cancel_area);
|
||||
}
|
||||
|
||||
fn draw_edit_artist_select_monitor_new_items_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let monitor_list = SelectableList::new(
|
||||
&mut app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.expect("edit_artist_modal must exist in this context")
|
||||
.monitor_list,
|
||||
|monitor_type| ListItem::new(monitor_type.to_display_str().to_owned()),
|
||||
);
|
||||
let popup = Popup::new(monitor_list).size(Size::Dropdown);
|
||||
|
||||
f.render_widget(popup, f.area());
|
||||
}
|
||||
|
||||
fn draw_edit_artist_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let quality_profile_list = SelectableList::new(
|
||||
&mut app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.expect("edit_artist_modal must exist in this context")
|
||||
.quality_profile_list,
|
||||
|quality_profile| ListItem::new(quality_profile.clone()),
|
||||
);
|
||||
let popup = Popup::new(quality_profile_list).size(Size::Dropdown);
|
||||
|
||||
f.render_widget(popup, f.area());
|
||||
}
|
||||
|
||||
fn draw_edit_artist_select_metadata_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let metadata_profile_list = SelectableList::new(
|
||||
&mut app
|
||||
.data
|
||||
.lidarr_data
|
||||
.edit_artist_modal
|
||||
.as_mut()
|
||||
.expect("edit_artist_modal must exist in this context")
|
||||
.metadata_profile_list,
|
||||
|metadata_profile| ListItem::new(metadata_profile.clone()),
|
||||
);
|
||||
let popup = Popup::new(metadata_profile_list).size(Size::Dropdown);
|
||||
|
||||
f.render_widget(popup, f.area());
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_ARTIST_BLOCKS};
|
||||
use crate::ui::DrawUi;
|
||||
use crate::ui::lidarr_ui::library::edit_artist_ui::EditArtistUi;
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_ui_accepts() {
|
||||
let mut edit_artist_ui_blocks = Vec::new();
|
||||
for block in ActiveLidarrBlock::iter() {
|
||||
if EditArtistUi::accepts(Route::Lidarr(block, None)) {
|
||||
edit_artist_ui_blocks.push(block);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(edit_artist_ui_blocks, EDIT_ARTIST_BLOCKS.to_vec());
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ mod tests {
|
||||
|
||||
use crate::models::lidarr_models::{Artist, ArtistStatistics, ArtistStatus};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||
ActiveLidarrBlock, DELETE_ARTIST_BLOCKS, LIBRARY_BLOCKS,
|
||||
ActiveLidarrBlock, DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, LIBRARY_BLOCKS,
|
||||
};
|
||||
use crate::ui::DrawUi;
|
||||
use crate::ui::lidarr_ui::library::{LibraryUi, decorate_artist_row_with_style};
|
||||
@@ -17,6 +17,8 @@ mod tests {
|
||||
let mut library_ui_blocks = Vec::new();
|
||||
library_ui_blocks.extend(LIBRARY_BLOCKS);
|
||||
library_ui_blocks.extend(DELETE_ARTIST_BLOCKS);
|
||||
library_ui_blocks.extend(EDIT_ARTIST_BLOCKS);
|
||||
|
||||
for active_lidarr_block in ActiveLidarrBlock::iter() {
|
||||
if library_ui_blocks.contains(&active_lidarr_block) {
|
||||
assert!(LibraryUi::accepts(active_lidarr_block.into()));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use delete_artist_ui::DeleteArtistUi;
|
||||
use edit_artist_ui::EditArtistUi;
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Constraint, Rect},
|
||||
@@ -26,6 +27,7 @@ use crate::{
|
||||
};
|
||||
|
||||
mod delete_artist_ui;
|
||||
mod edit_artist_ui;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "library_ui_tests.rs"]
|
||||
@@ -36,7 +38,9 @@ pub(super) struct LibraryUi;
|
||||
impl DrawUi for LibraryUi {
|
||||
fn accepts(route: Route) -> bool {
|
||||
if let Route::Lidarr(active_lidarr_block, _) = route {
|
||||
return DeleteArtistUi::accepts(route) || LIBRARY_BLOCKS.contains(&active_lidarr_block);
|
||||
return DeleteArtistUi::accepts(route)
|
||||
|| EditArtistUi::accepts(route)
|
||||
|| LIBRARY_BLOCKS.contains(&active_lidarr_block);
|
||||
}
|
||||
|
||||
false
|
||||
@@ -48,6 +52,7 @@ impl DrawUi for LibraryUi {
|
||||
|
||||
match route {
|
||||
_ if DeleteArtistUi::accepts(route) => DeleteArtistUi::draw(f, app, area),
|
||||
_ if EditArtistUi::accepts(route) => EditArtistUi::draw(f, app, area),
|
||||
Route::Lidarr(ActiveLidarrBlock::UpdateAllArtistsPrompt, _) => {
|
||||
let confirmation_prompt = ConfirmationPrompt::new()
|
||||
.title("Update All Artists")
|
||||
|
||||
Reference in New Issue
Block a user