Added full support for managing the blocklist
@@ -863,7 +863,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "managarr"
|
||||
version = "0.0.33"
|
||||
version = "0.0.34"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"backtrace",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "managarr"
|
||||
version = "0.0.33"
|
||||
version = "0.0.34"
|
||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||
description = "A TUI to manage your Servarrs"
|
||||
keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui"]
|
||||
|
||||
@@ -36,7 +36,7 @@ pleasant as possible!
|
||||
|
||||
### Radarr
|
||||
|
||||
- [x] View your library, downloads, collections
|
||||
- [x] View your library, downloads, collections, and blocklist
|
||||
- [x] View details of a specific movie including description, history, downloaded file info, or the credits
|
||||
- [x] View details of any collection and the movies in them
|
||||
- [x] Search your library or collections
|
||||
@@ -48,6 +48,7 @@ pleasant as possible!
|
||||
- [x] Edit your movies, collections, and indexers
|
||||
- [x] Manage your tags
|
||||
- [x] Manage your root folders
|
||||
- [x] Manage your blocklist
|
||||
- [ ] Manage your quality profiles
|
||||
- [ ] Manage your quality definitions
|
||||
- [x] View and browse logs, tasks, events queues, and updates
|
||||
@@ -144,6 +145,8 @@ tautulli:
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Dependencies
|
||||
* [ratatui](https://github.com/tui-rs-revival/ratatui)
|
||||
|
||||
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 363 KiB After Width: | Height: | Size: 374 KiB |
|
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 382 KiB |
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 176 KiB |
@@ -15,6 +15,7 @@ generate_keybindings! {
|
||||
left,
|
||||
right,
|
||||
backspace,
|
||||
clear,
|
||||
search,
|
||||
settings,
|
||||
filter,
|
||||
@@ -67,6 +68,10 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
|
||||
key: Key::Backspace,
|
||||
desc: "backspace",
|
||||
},
|
||||
clear: KeyBinding {
|
||||
key: Key::Char('c'),
|
||||
desc: "clear",
|
||||
},
|
||||
search: KeyBinding {
|
||||
key: Key::Char('s'),
|
||||
desc: "search",
|
||||
|
||||
@@ -13,6 +13,7 @@ mod test {
|
||||
#[case(DEFAULT_KEYBINDINGS.left, Key::Left, "left")]
|
||||
#[case(DEFAULT_KEYBINDINGS.right, Key::Right, "right")]
|
||||
#[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, "backspace")]
|
||||
#[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), "clear")]
|
||||
#[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), "search")]
|
||||
#[case(DEFAULT_KEYBINDINGS.settings, Key::Char('s'), "settings")]
|
||||
#[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), "filter")]
|
||||
|
||||
@@ -11,6 +11,11 @@ mod radarr_tests;
|
||||
impl<'a> App<'a> {
|
||||
pub(super) async fn dispatch_by_radarr_block(&mut self, active_radarr_block: &ActiveRadarrBlock) {
|
||||
match active_radarr_block {
|
||||
ActiveRadarrBlock::Blocklist => {
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetBlocklist.into())
|
||||
.await;
|
||||
}
|
||||
ActiveRadarrBlock::Collections => {
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetCollections.into())
|
||||
|
||||
@@ -21,14 +21,6 @@ pub static LIBRARY_CONTEXT_CLUES: [ContextClue; 10] = [
|
||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
||||
];
|
||||
|
||||
pub static DOWNLOADS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||
];
|
||||
|
||||
pub static COLLECTIONS_CONTEXT_CLUES: [ContextClue; 8] = [
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||
@@ -43,6 +35,25 @@ pub static COLLECTIONS_CONTEXT_CLUES: [ContextClue; 8] = [
|
||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
||||
];
|
||||
|
||||
pub static DOWNLOADS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||
];
|
||||
|
||||
pub static BLOCKLIST_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||
(DEFAULT_KEYBINDINGS.clear, "clear blocklist"),
|
||||
];
|
||||
|
||||
pub static ROOT_FOLDERS_CONTEXT_CLUES: [ContextClue; 3] = [
|
||||
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||
@@ -52,8 +63,7 @@ pub static ROOT_FOLDERS_CONTEXT_CLUES: [ContextClue; 3] = [
|
||||
),
|
||||
];
|
||||
|
||||
pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
||||
pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||
(DEFAULT_KEYBINDINGS.submit, "edit indexer"),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.settings,
|
||||
|
||||
@@ -4,7 +4,7 @@ mod tests {
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::radarr::radarr_context_clues::{
|
||||
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES,
|
||||
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES,
|
||||
COLLECTION_DETAILS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||
LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||
MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
||||
@@ -67,22 +67,6 @@ mod tests {
|
||||
assert_eq!(library_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downloads_context_clues() {
|
||||
let mut downloads_context_clues_iter = DOWNLOADS_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
|
||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
||||
assert_eq!(downloads_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collections_context_clues() {
|
||||
let mut collections_context_clues = COLLECTIONS_CONTEXT_CLUES.iter();
|
||||
@@ -129,6 +113,53 @@ mod tests {
|
||||
assert_eq!(collections_context_clues.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downloads_context_clues() {
|
||||
let mut downloads_context_clues_iter = DOWNLOADS_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
|
||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
||||
assert_eq!(downloads_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_context_clues() {
|
||||
let mut blocklist_context_clues_iter = BLOCKLIST_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
|
||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
||||
|
||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "details");
|
||||
|
||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
||||
|
||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.clear);
|
||||
assert_str_eq!(*description, "clear blocklist");
|
||||
assert_eq!(blocklist_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root_folders_context_clues() {
|
||||
let mut root_folders_context_clues_iter = ROOT_FOLDERS_CONTEXT_CLUES.iter();
|
||||
@@ -156,11 +187,6 @@ mod tests {
|
||||
|
||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc);
|
||||
|
||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "edit indexer");
|
||||
|
||||
|
||||
@@ -12,6 +12,23 @@ mod tests {
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::network::NetworkEvent;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dispatch_by_blocklist_block() {
|
||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||
|
||||
app
|
||||
.dispatch_by_radarr_block(&ActiveRadarrBlock::Blocklist)
|
||||
.await;
|
||||
|
||||
assert!(app.is_loading);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetBlocklist.into()
|
||||
);
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dispatch_by_collections_block() {
|
||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||
|
||||
@@ -0,0 +1,707 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::blocklist::{blocklist_sorting_options, BlocklistHandler};
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::models::radarr_models::{BlocklistItem, Language, Movie, Quality, QualityWrapper};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use chrono::DateTime;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use std::cmp::Ordering;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
mod test_handle_scroll_up_and_down {
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::models::radarr_models::BlocklistItem;
|
||||
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
|
||||
|
||||
use super::*;
|
||||
|
||||
test_iterable_scroll!(
|
||||
test_blocklist_scroll,
|
||||
BlocklistHandler,
|
||||
blocklist,
|
||||
simple_stateful_iterable_vec!(BlocklistItem, String, source_title),
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
None,
|
||||
source_title,
|
||||
to_string
|
||||
);
|
||||
|
||||
#[rstest]
|
||||
fn test_blocklist_sort_scroll(
|
||||
#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key,
|
||||
) {
|
||||
let blocklist_field_vec = sort_options();
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.blocklist.sorting(sort_options());
|
||||
|
||||
if key == Key::Up {
|
||||
for i in (0..blocklist_field_vec.len()).rev() {
|
||||
BlocklistHandler::with(
|
||||
&key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.sort
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_selection(),
|
||||
&blocklist_field_vec[i]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for i in 0..blocklist_field_vec.len() {
|
||||
BlocklistHandler::with(
|
||||
&key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.sort
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_selection(),
|
||||
&blocklist_field_vec[(i + 1) % blocklist_field_vec.len()]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_home_end {
|
||||
use super::*;
|
||||
use crate::models::radarr_models::BlocklistItem;
|
||||
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
|
||||
test_iterable_home_and_end!(
|
||||
test_blocklist_home_and_end,
|
||||
BlocklistHandler,
|
||||
blocklist,
|
||||
extended_stateful_iterable_vec!(BlocklistItem, String, source_title),
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
None,
|
||||
source_title,
|
||||
to_string
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_sort_home_end() {
|
||||
let blocklist_field_vec = sort_options();
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.blocklist.sorting(sort_options());
|
||||
|
||||
BlocklistHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.end.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.sort
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_selection(),
|
||||
&blocklist_field_vec[blocklist_field_vec.len() - 1]
|
||||
);
|
||||
|
||||
BlocklistHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.home.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.sort
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_selection(),
|
||||
&blocklist_field_vec[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_delete {
|
||||
use super::*;
|
||||
use crate::assert_delete_prompt;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
|
||||
|
||||
#[test]
|
||||
fn test_delete_blocklist_item_prompt() {
|
||||
assert_delete_prompt!(
|
||||
BlocklistHandler,
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_left_right_action {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_tab_left() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(3);
|
||||
|
||||
BlocklistHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.left.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::Blocklist,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
&ActiveRadarrBlock::Downloads.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Downloads.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_tab_right() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(3);
|
||||
|
||||
BlocklistHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.right.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::Blocklist,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
&ActiveRadarrBlock::RootFolders.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::RootFolders.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_blocklist_left_right_prompt_toggle(
|
||||
#[values(
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt,
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt
|
||||
)]
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
|
||||
) {
|
||||
let mut app = App::default();
|
||||
|
||||
BlocklistHandler::with(&key, &mut app, &active_radarr_block, &None).handle();
|
||||
|
||||
assert!(app.data.radarr_data.prompt_confirm);
|
||||
|
||||
BlocklistHandler::with(&key, &mut app, &active_radarr_block, &None).handle();
|
||||
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_submit {
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt,
|
||||
RadarrEvent::DeleteBlocklistItem
|
||||
)]
|
||||
#[case(
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt,
|
||||
RadarrEvent::ClearBlocklist
|
||||
)]
|
||||
fn test_blocklist_prompt_confirm_submit(
|
||||
#[case] base_route: ActiveRadarrBlock,
|
||||
#[case] prompt_block: ActiveRadarrBlock,
|
||||
#[case] expected_action: RadarrEvent,
|
||||
) {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.prompt_confirm = true;
|
||||
app.push_navigation_stack(base_route.into());
|
||||
app.push_navigation_stack(prompt_block.into());
|
||||
|
||||
BlocklistHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle();
|
||||
|
||||
assert!(app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(expected_action)
|
||||
);
|
||||
assert_eq!(app.get_current_route(), &base_route.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_blocklist_prompt_decline_submit(
|
||||
#[values(
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt,
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt
|
||||
)]
|
||||
prompt_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||
app.push_navigation_stack(prompt_block.into());
|
||||
|
||||
BlocklistHandler::with(&SUBMIT_KEY, &mut app, &prompt_block, &None).handle();
|
||||
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Blocklist.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_sort_prompt_submit() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.blocklist.sort_asc = true;
|
||||
app.data.radarr_data.blocklist.sorting(sort_options());
|
||||
app.data.radarr_data.blocklist.set_items(blocklist_vec());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
|
||||
|
||||
let mut expected_vec = blocklist_vec();
|
||||
expected_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
expected_vec.reverse();
|
||||
|
||||
BlocklistHandler::with(
|
||||
&SUBMIT_KEY,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Blocklist.into()
|
||||
);
|
||||
assert_eq!(app.data.radarr_data.blocklist.items, expected_vec);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_esc {
|
||||
use super::*;
|
||||
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt
|
||||
)]
|
||||
#[case(
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt
|
||||
)]
|
||||
fn test_blocklist_prompt_blocks_esc(
|
||||
#[case] base_block: ActiveRadarrBlock,
|
||||
#[case] prompt_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::default();
|
||||
app.push_navigation_stack(base_block.into());
|
||||
app.push_navigation_stack(prompt_block.into());
|
||||
app.data.radarr_data.prompt_confirm = true;
|
||||
|
||||
BlocklistHandler::with(&ESC_KEY, &mut app, &prompt_block, &None).handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), &base_block.into());
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_esc_blocklist_item_details() {
|
||||
let mut app = App::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::BlocklistItemDetails.into());
|
||||
|
||||
BlocklistHandler::with(
|
||||
&ESC_KEY,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::BlocklistItemDetails,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Blocklist.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_sort_prompt_block_esc() {
|
||||
let mut app = App::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
|
||||
|
||||
BlocklistHandler::with(
|
||||
&ESC_KEY,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::BlocklistSortPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Blocklist.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_esc() {
|
||||
let mut app = App::default();
|
||||
app.error = "test error".to_owned().into();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Blocklist.into());
|
||||
|
||||
DownloadsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::Blocklist, &None).handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Blocklist.into()
|
||||
);
|
||||
assert!(app.error.text.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_key_char {
|
||||
use super::*;
|
||||
use crate::assert_refresh_key;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_refresh_blocklist_key() {
|
||||
assert_refresh_key!(BlocklistHandler, ActiveRadarrBlock::Blocklist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_blocklist_key() {
|
||||
let mut app = App::default();
|
||||
|
||||
BlocklistHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.clear.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::Blocklist,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_key() {
|
||||
let mut app = App::default();
|
||||
|
||||
BlocklistHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.sort.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::Blocklist,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::BlocklistSortPrompt.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.blocklist.sort.as_ref().unwrap().items,
|
||||
blocklist_sorting_options()
|
||||
);
|
||||
assert!(!app.data.radarr_data.blocklist.sort_asc);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_sorting_options_movie_title() {
|
||||
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||
a.movie
|
||||
.title
|
||||
.text
|
||||
.to_lowercase()
|
||||
.cmp(&b.movie.title.text.to_lowercase())
|
||||
};
|
||||
let mut expected_blocklist_vec = blocklist_vec();
|
||||
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = blocklist_sorting_options()[0].clone();
|
||||
let mut sorted_blocklist_vec = blocklist_vec();
|
||||
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||
assert_str_eq!(sort_option.name, "Movie Title");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_sorting_options_source_title() {
|
||||
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||
a.source_title
|
||||
.to_lowercase()
|
||||
.cmp(&b.source_title.to_lowercase())
|
||||
};
|
||||
let mut expected_blocklist_vec = blocklist_vec();
|
||||
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = blocklist_sorting_options()[1].clone();
|
||||
let mut sorted_blocklist_vec = blocklist_vec();
|
||||
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||
assert_str_eq!(sort_option.name, "Source Title");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_sorting_options_languages() {
|
||||
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||
let a_languages = a
|
||||
.languages
|
||||
.iter()
|
||||
.map(|lang| lang.name.to_lowercase())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let b_languages = b
|
||||
.languages
|
||||
.iter()
|
||||
.map(|lang| lang.name.to_lowercase())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
a_languages.cmp(&b_languages)
|
||||
};
|
||||
let mut expected_blocklist_vec = blocklist_vec();
|
||||
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = blocklist_sorting_options()[2].clone();
|
||||
let mut sorted_blocklist_vec = blocklist_vec();
|
||||
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||
assert_str_eq!(sort_option.name, "Languages");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_sorting_options_quality() {
|
||||
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||
a.quality
|
||||
.quality
|
||||
.name
|
||||
.to_lowercase()
|
||||
.cmp(&b.quality.quality.name.to_lowercase())
|
||||
};
|
||||
let mut expected_blocklist_vec = blocklist_vec();
|
||||
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = blocklist_sorting_options()[3].clone();
|
||||
let mut sorted_blocklist_vec = blocklist_vec();
|
||||
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||
assert_str_eq!(sort_option.name, "Quality");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_sorting_options_custom_formats() {
|
||||
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||
let a_custom_formats = a
|
||||
.custom_formats
|
||||
.as_ref()
|
||||
.unwrap_or(&Vec::new())
|
||||
.iter()
|
||||
.map(|lang| lang.name.to_lowercase())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let b_custom_formats = b
|
||||
.custom_formats
|
||||
.as_ref()
|
||||
.unwrap_or(&Vec::new())
|
||||
.iter()
|
||||
.map(|lang| lang.name.to_lowercase())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
a_custom_formats.cmp(&b_custom_formats)
|
||||
};
|
||||
let mut expected_blocklist_vec = blocklist_vec();
|
||||
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = blocklist_sorting_options()[4].clone();
|
||||
let mut sorted_blocklist_vec = blocklist_vec();
|
||||
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||
assert_str_eq!(sort_option.name, "Formats");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_sorting_options_date() {
|
||||
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering =
|
||||
|a, b| a.date.cmp(&b.date);
|
||||
let mut expected_blocklist_vec = blocklist_vec();
|
||||
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = blocklist_sorting_options()[5].clone();
|
||||
let mut sorted_blocklist_vec = blocklist_vec();
|
||||
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||
assert_str_eq!(sort_option.name, "Date");
|
||||
}
|
||||
|
||||
fn blocklist_vec() -> Vec<BlocklistItem> {
|
||||
vec![
|
||||
BlocklistItem {
|
||||
id: 3,
|
||||
source_title: "test 1".to_owned(),
|
||||
languages: vec![Language {
|
||||
name: "telgu".to_owned(),
|
||||
}],
|
||||
quality: QualityWrapper {
|
||||
quality: Quality {
|
||||
name: "HD - 1080p".to_owned(),
|
||||
},
|
||||
},
|
||||
custom_formats: Some(vec![Language {
|
||||
name: "nikki".to_owned(),
|
||||
}]),
|
||||
date: DateTime::from(DateTime::parse_from_rfc3339("2024-01-10T07:28:45Z").unwrap()),
|
||||
movie: Movie {
|
||||
title: "test 3".into(),
|
||||
..Movie::default()
|
||||
},
|
||||
..BlocklistItem::default()
|
||||
},
|
||||
BlocklistItem {
|
||||
id: 2,
|
||||
source_title: "test 2".to_owned(),
|
||||
languages: vec![Language {
|
||||
name: "chinese".to_owned(),
|
||||
}],
|
||||
quality: QualityWrapper {
|
||||
quality: Quality {
|
||||
name: "SD - 720p".to_owned(),
|
||||
},
|
||||
},
|
||||
custom_formats: Some(vec![
|
||||
Language {
|
||||
name: "alex".to_owned(),
|
||||
},
|
||||
Language {
|
||||
name: "English".to_owned(),
|
||||
},
|
||||
]),
|
||||
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
|
||||
movie: Movie {
|
||||
title: "test 2".into(),
|
||||
..Movie::default()
|
||||
},
|
||||
..BlocklistItem::default()
|
||||
},
|
||||
BlocklistItem {
|
||||
id: 1,
|
||||
source_title: "test 3".to_owned(),
|
||||
languages: vec![Language {
|
||||
name: "english".to_owned(),
|
||||
}],
|
||||
quality: QualityWrapper {
|
||||
quality: Quality {
|
||||
name: "HD - 1080p".to_owned(),
|
||||
},
|
||||
},
|
||||
custom_formats: Some(vec![Language {
|
||||
name: "English".to_owned(),
|
||||
}]),
|
||||
date: DateTime::from(DateTime::parse_from_rfc3339("2024-03-10T07:28:45Z").unwrap()),
|
||||
movie: Movie {
|
||||
title: "test 1".into(),
|
||||
..Movie::default()
|
||||
},
|
||||
..BlocklistItem::default()
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn sort_options() -> Vec<SortOption<BlocklistItem>> {
|
||||
vec![SortOption {
|
||||
name: "Test 1",
|
||||
cmp_fn: Some(|a, b| {
|
||||
b.source_title
|
||||
.to_lowercase()
|
||||
.cmp(&a.source_title.to_lowercase())
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_handler_accepts() {
|
||||
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
|
||||
if BLOCKLIST_BLOCKS.contains(&active_radarr_block) {
|
||||
assert!(BlocklistHandler::accepts(&active_radarr_block));
|
||||
} else {
|
||||
assert!(!BlocklistHandler::accepts(&active_radarr_block));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::radarr_models::BlocklistItem;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::models::Scrollable;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "blocklist_handler_tests.rs"]
|
||||
mod blocklist_handler_tests;
|
||||
|
||||
pub(super) struct BlocklistHandler<'a, 'b> {
|
||||
key: &'a Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_radarr_block: &'a ActiveRadarrBlock,
|
||||
_context: &'a Option<ActiveRadarrBlock>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a, 'b> {
|
||||
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
|
||||
BLOCKLIST_BLOCKS.contains(active_block)
|
||||
}
|
||||
|
||||
fn with(
|
||||
key: &'a Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_block: &'a ActiveRadarrBlock,
|
||||
context: &'a Option<ActiveRadarrBlock>,
|
||||
) -> Self {
|
||||
BlocklistHandler {
|
||||
key,
|
||||
app,
|
||||
active_radarr_block: active_block,
|
||||
_context: context,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key(&self) -> &Key {
|
||||
self.key
|
||||
}
|
||||
|
||||
fn handle_scroll_up(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_up(),
|
||||
ActiveRadarrBlock::BlocklistSortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_up(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_scroll_down(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_down(),
|
||||
ActiveRadarrBlock::BlocklistSortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_down(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_home(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_to_top(),
|
||||
ActiveRadarrBlock::BlocklistSortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_to_top(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_end(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Blocklist => self.app.data.radarr_data.blocklist.scroll_to_bottom(),
|
||||
ActiveRadarrBlock::BlocklistSortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_to_bottom(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_delete(&mut self) {
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::Blocklist {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::DeleteBlocklistItemPrompt.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_left_right_action(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Blocklist => handle_change_tab_left_right_keys(self.app, self.key),
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt
|
||||
| ActiveRadarrBlock::BlocklistClearAllItemsPrompt => handle_prompt_toggle(self.app, self.key),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_submit(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
|
||||
if self.app.data.radarr_data.prompt_confirm {
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem);
|
||||
}
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
|
||||
if self.app.data.radarr_data.prompt_confirm {
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::ClearBlocklist);
|
||||
}
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveRadarrBlock::BlocklistSortPrompt => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.items
|
||||
.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
self.app.data.radarr_data.blocklist.apply_sorting();
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveRadarrBlock::Blocklist => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::BlocklistItemDetails.into());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_esc(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt
|
||||
| ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.data.radarr_data.prompt_confirm = false;
|
||||
}
|
||||
ActiveRadarrBlock::BlocklistItemDetails | ActiveRadarrBlock::BlocklistSortPrompt => {
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
_ => handle_clear_errors(self.app),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
let key = self.key;
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::Blocklist {
|
||||
match self.key {
|
||||
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ if *key == DEFAULT_KEYBINDINGS.clear.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into());
|
||||
}
|
||||
_ if *key == DEFAULT_KEYBINDINGS.sort.key => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.sorting(blocklist_sorting_options());
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn blocklist_sorting_options() -> Vec<SortOption<BlocklistItem>> {
|
||||
vec![
|
||||
SortOption {
|
||||
name: "Movie Title",
|
||||
cmp_fn: Some(|a, b| {
|
||||
a.movie
|
||||
.title
|
||||
.text
|
||||
.to_lowercase()
|
||||
.cmp(&b.movie.title.text.to_lowercase())
|
||||
}),
|
||||
},
|
||||
SortOption {
|
||||
name: "Source Title",
|
||||
cmp_fn: Some(|a, b| {
|
||||
a.source_title
|
||||
.to_lowercase()
|
||||
.cmp(&b.source_title.to_lowercase())
|
||||
}),
|
||||
},
|
||||
SortOption {
|
||||
name: "Languages",
|
||||
cmp_fn: Some(|a, b| {
|
||||
let a_languages = a
|
||||
.languages
|
||||
.iter()
|
||||
.map(|lang| lang.name.to_lowercase())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let b_languages = b
|
||||
.languages
|
||||
.iter()
|
||||
.map(|lang| lang.name.to_lowercase())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
a_languages.cmp(&b_languages)
|
||||
}),
|
||||
},
|
||||
SortOption {
|
||||
name: "Quality",
|
||||
cmp_fn: Some(|a, b| {
|
||||
a.quality
|
||||
.quality
|
||||
.name
|
||||
.to_lowercase()
|
||||
.cmp(&b.quality.quality.name.to_lowercase())
|
||||
}),
|
||||
},
|
||||
SortOption {
|
||||
name: "Formats",
|
||||
cmp_fn: Some(|a, b| {
|
||||
let a_custom_formats = a
|
||||
.custom_formats
|
||||
.as_ref()
|
||||
.unwrap_or(&Vec::new())
|
||||
.iter()
|
||||
.map(|lang| lang.name.to_lowercase())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let b_custom_formats = b
|
||||
.custom_formats
|
||||
.as_ref()
|
||||
.unwrap_or(&Vec::new())
|
||||
.iter()
|
||||
.map(|lang| lang.name.to_lowercase())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
a_custom_formats.cmp(&b_custom_formats)
|
||||
}),
|
||||
},
|
||||
SortOption {
|
||||
name: "Date",
|
||||
cmp_fn: Some(|a, b| a.date.cmp(&b.date)),
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -267,7 +267,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_collections_tab_left() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(2);
|
||||
app.data.radarr_data.main_tabs.set_index(1);
|
||||
|
||||
CollectionsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.left.key,
|
||||
@@ -279,18 +279,15 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
&ActiveRadarrBlock::Downloads.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Downloads.into()
|
||||
&ActiveRadarrBlock::Movies.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collections_tab_right() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(2);
|
||||
app.data.radarr_data.main_tabs.set_index(1);
|
||||
|
||||
CollectionsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.right.key,
|
||||
@@ -302,11 +299,11 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
&ActiveRadarrBlock::RootFolders.into()
|
||||
&ActiveRadarrBlock::Downloads.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::RootFolders.into()
|
||||
&ActiveRadarrBlock::Downloads.into()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -832,6 +829,26 @@ mod tests {
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collections_sort_prompt_block_esc() {
|
||||
let mut app = App::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::CollectionsSortPrompt.into());
|
||||
|
||||
CollectionsHandler::with(
|
||||
&ESC_KEY,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::CollectionsSortPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_esc() {
|
||||
let mut app = App::default();
|
||||
|
||||
@@ -287,6 +287,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.data.radarr_data.prompt_confirm = false;
|
||||
}
|
||||
ActiveRadarrBlock::CollectionsSortPrompt => {
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
_ => {
|
||||
self.app.data.radarr_data.collections.reset_search();
|
||||
self.app.data.radarr_data.collections.reset_filter();
|
||||
|
||||
@@ -74,7 +74,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_downloads_tab_left() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(1);
|
||||
app.data.radarr_data.main_tabs.set_index(2);
|
||||
|
||||
DownloadsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.left.key,
|
||||
@@ -84,26 +84,6 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
&ActiveRadarrBlock::Movies.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downloads_tab_right() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(1);
|
||||
|
||||
DownloadsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.right.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::Downloads,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
&ActiveRadarrBlock::Collections.into()
|
||||
@@ -114,6 +94,29 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downloads_tab_right() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(2);
|
||||
|
||||
DownloadsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.right.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::Downloads,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
&ActiveRadarrBlock::Blocklist.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Blocklist.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_downloads_left_right_prompt_toggle(
|
||||
#[values(
|
||||
|
||||
@@ -78,7 +78,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_indexers_tab_left() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(4);
|
||||
app.data.radarr_data.main_tabs.set_index(5);
|
||||
|
||||
IndexersHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.left.key,
|
||||
@@ -101,7 +101,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_indexers_tab_right() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(4);
|
||||
app.data.radarr_data.main_tabs.set_index(5);
|
||||
|
||||
IndexersHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.right.key,
|
||||
|
||||
@@ -312,11 +312,11 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
&ActiveRadarrBlock::Downloads.into()
|
||||
&ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Downloads.into()
|
||||
&ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -776,6 +776,23 @@ mod tests {
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sort_prompt_block_esc() {
|
||||
let mut app = App::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::MoviesSortPrompt.into());
|
||||
|
||||
LibraryHandler::with(
|
||||
&ESC_KEY,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::MoviesSortPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_esc() {
|
||||
let mut app = App::default();
|
||||
|
||||
@@ -298,6 +298,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.data.radarr_data.prompt_confirm = false;
|
||||
}
|
||||
ActiveRadarrBlock::MoviesSortPrompt => {
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
_ => {
|
||||
self.app.data.radarr_data.movies.reset_search();
|
||||
self.app.data.radarr_data.movies.reset_filter();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::handlers::radarr_handlers::blocklist::BlocklistHandler;
|
||||
use crate::handlers::radarr_handlers::collections::CollectionsHandler;
|
||||
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
|
||||
use crate::handlers::radarr_handlers::indexers::IndexersHandler;
|
||||
@@ -9,6 +10,7 @@ use crate::handlers::KeyEventHandler;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::{App, Key};
|
||||
|
||||
mod blocklist;
|
||||
mod collections;
|
||||
mod downloads;
|
||||
mod indexers;
|
||||
@@ -54,6 +56,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
|
||||
RootFoldersHandler::with(self.key, self.app, self.active_radarr_block, self.context)
|
||||
.handle()
|
||||
}
|
||||
_ if BlocklistHandler::accepts(self.active_radarr_block) => {
|
||||
BlocklistHandler::with(self.key, self.app, self.active_radarr_block, self.context).handle()
|
||||
}
|
||||
_ => self.handle_key_event(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,13 @@ mod tests {
|
||||
use crate::test_handler_delegation;
|
||||
|
||||
#[rstest]
|
||||
#[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Downloads)]
|
||||
#[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Collections)]
|
||||
#[case(2, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::RootFolders)]
|
||||
#[case(3, ActiveRadarrBlock::Collections, ActiveRadarrBlock::Indexers)]
|
||||
#[case(4, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)]
|
||||
#[case(5, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)]
|
||||
#[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Collections)]
|
||||
#[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Downloads)]
|
||||
#[case(2, ActiveRadarrBlock::Collections, ActiveRadarrBlock::Blocklist)]
|
||||
#[case(3, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::RootFolders)]
|
||||
#[case(4, ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Indexers)]
|
||||
#[case(5, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)]
|
||||
#[case(6, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)]
|
||||
fn test_radarr_handler_change_tab_left_right_keys(
|
||||
#[case] index: usize,
|
||||
#[case] left_block: ActiveRadarrBlock,
|
||||
@@ -68,6 +69,7 @@ mod tests {
|
||||
fn test_delegates_library_blocks_to_library_handler(
|
||||
#[values(
|
||||
ActiveRadarrBlock::Movies,
|
||||
ActiveRadarrBlock::MoviesSortPrompt,
|
||||
ActiveRadarrBlock::SearchMovie,
|
||||
ActiveRadarrBlock::SearchMovieError,
|
||||
ActiveRadarrBlock::FilterMovies,
|
||||
@@ -112,6 +114,7 @@ mod tests {
|
||||
#[values(
|
||||
ActiveRadarrBlock::Collections,
|
||||
ActiveRadarrBlock::SearchCollection,
|
||||
ActiveRadarrBlock::CollectionsSortPrompt,
|
||||
ActiveRadarrBlock::SearchCollectionError,
|
||||
ActiveRadarrBlock::FilterCollections,
|
||||
ActiveRadarrBlock::FilterCollectionsError,
|
||||
@@ -189,6 +192,24 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_delegates_blocklist_blocks_to_blocklist_handler(
|
||||
#[values(
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
ActiveRadarrBlock::BlocklistItemDetails,
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt,
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt,
|
||||
ActiveRadarrBlock::BlocklistSortPrompt
|
||||
)]
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
) {
|
||||
test_handler_delegation!(
|
||||
RadarrHandler,
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
active_radarr_block
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_radarr_handler_accepts() {
|
||||
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
|
||||
|
||||
@@ -123,7 +123,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_root_folders_tab_left() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(3);
|
||||
app.data.radarr_data.main_tabs.set_index(4);
|
||||
|
||||
RootFoldersHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.left.key,
|
||||
@@ -135,18 +135,18 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
&ActiveRadarrBlock::Collections.into()
|
||||
&ActiveRadarrBlock::Blocklist.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Collections.into()
|
||||
&ActiveRadarrBlock::Blocklist.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root_folders_tab_right() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(3);
|
||||
app.data.radarr_data.main_tabs.set_index(4);
|
||||
|
||||
RootFoldersHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.right.key,
|
||||
|
||||
@@ -422,11 +422,6 @@ mod tests {
|
||||
let mut app = App::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::System.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into());
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.queued_events
|
||||
.set_items(vec![QueueEvent::default()]);
|
||||
|
||||
SystemDetailsHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::SystemUpdates, &None)
|
||||
.handle();
|
||||
|
||||
@@ -22,7 +22,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_system_tab_left() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(5);
|
||||
app.data.radarr_data.main_tabs.set_index(6);
|
||||
|
||||
SystemHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.left.key,
|
||||
@@ -42,7 +42,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_system_tab_right() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.main_tabs.set_index(5);
|
||||
app.data.radarr_data.main_tabs.set_index(6);
|
||||
|
||||
SystemHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.right.key,
|
||||
|
||||
@@ -54,6 +54,29 @@ pub struct AddRootFolderBody {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BlocklistResponse {
|
||||
pub records: Vec<BlocklistItem>,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlocklistItem {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub movie_id: i64,
|
||||
pub source_title: String,
|
||||
pub languages: Vec<Language>,
|
||||
pub quality: QualityWrapper,
|
||||
pub custom_formats: Option<Vec<Language>>,
|
||||
pub date: DateTime<Utc>,
|
||||
pub protocol: String,
|
||||
pub indexer: String,
|
||||
pub message: String,
|
||||
pub movie: Movie,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Derivative, Default, Clone, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Collection {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::app::context_clues::build_context_clue_string;
|
||||
use crate::app::radarr::radarr_context_clues::{
|
||||
COLLECTIONS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||
LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||
BLOCKLIST_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES,
|
||||
INDEXERS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||
MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
||||
SYSTEM_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::models::radarr_models::{
|
||||
AddMovieSearchResult, Collection, CollectionMovie, DiskSpace, DownloadRecord, Indexer,
|
||||
IndexerSettings, Movie, QueueEvent, RootFolder, Task,
|
||||
AddMovieSearchResult, BlocklistItem, Collection, CollectionMovie, DiskSpace, DownloadRecord,
|
||||
Indexer, IndexerSettings, Movie, QueueEvent, RootFolder, Task,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::modals::{
|
||||
AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem,
|
||||
@@ -40,6 +40,7 @@ pub struct RadarrData<'a> {
|
||||
pub selected_block: BlockSelectionState<'a, ActiveRadarrBlock>,
|
||||
pub downloads: StatefulTable<DownloadRecord>,
|
||||
pub indexers: StatefulTable<Indexer>,
|
||||
pub blocklist: StatefulTable<BlocklistItem>,
|
||||
pub quality_profile_map: BiMap<i64, String>,
|
||||
pub tags_map: BiMap<i64, String>,
|
||||
pub collections: StatefulTable<Collection>,
|
||||
@@ -91,6 +92,7 @@ impl<'a> Default for RadarrData<'a> {
|
||||
selected_block: BlockSelectionState::default(),
|
||||
downloads: StatefulTable::default(),
|
||||
indexers: StatefulTable::default(),
|
||||
blocklist: StatefulTable::default(),
|
||||
quality_profile_map: BiMap::default(),
|
||||
tags_map: BiMap::default(),
|
||||
collections: StatefulTable::default(),
|
||||
@@ -122,6 +124,12 @@ impl<'a> Default for RadarrData<'a> {
|
||||
help: String::new(),
|
||||
contextual_help: Some(build_context_clue_string(&LIBRARY_CONTEXT_CLUES)),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Collections",
|
||||
route: ActiveRadarrBlock::Collections.into(),
|
||||
help: String::new(),
|
||||
contextual_help: Some(build_context_clue_string(&COLLECTIONS_CONTEXT_CLUES)),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Downloads",
|
||||
route: ActiveRadarrBlock::Downloads.into(),
|
||||
@@ -129,10 +137,10 @@ impl<'a> Default for RadarrData<'a> {
|
||||
contextual_help: Some(build_context_clue_string(&DOWNLOADS_CONTEXT_CLUES)),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Collections",
|
||||
route: ActiveRadarrBlock::Collections.into(),
|
||||
title: "Blocklist",
|
||||
route: ActiveRadarrBlock::Blocklist.into(),
|
||||
help: String::new(),
|
||||
contextual_help: Some(build_context_clue_string(&COLLECTIONS_CONTEXT_CLUES)),
|
||||
contextual_help: Some(build_context_clue_string(&BLOCKLIST_CONTEXT_CLUES)),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Root Folders",
|
||||
@@ -213,11 +221,16 @@ pub enum ActiveRadarrBlock {
|
||||
AddMovieEmptySearchResults,
|
||||
AddRootFolderPrompt,
|
||||
AutomaticallySearchMoviePrompt,
|
||||
Blocklist,
|
||||
BlocklistClearAllItemsPrompt,
|
||||
BlocklistItemDetails,
|
||||
BlocklistSortPrompt,
|
||||
Collections,
|
||||
CollectionsSortPrompt,
|
||||
CollectionDetails,
|
||||
Cast,
|
||||
Crew,
|
||||
DeleteBlocklistItemPrompt,
|
||||
DeleteDownloadPrompt,
|
||||
DeleteIndexerPrompt,
|
||||
DeleteMoviePrompt,
|
||||
@@ -323,6 +336,13 @@ pub static ROOT_FOLDERS_BLOCKS: [ActiveRadarrBlock; 3] = [
|
||||
ActiveRadarrBlock::AddRootFolderPrompt,
|
||||
ActiveRadarrBlock::DeleteRootFolderPrompt,
|
||||
];
|
||||
pub static BLOCKLIST_BLOCKS: [ActiveRadarrBlock; 5] = [
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
ActiveRadarrBlock::BlocklistItemDetails,
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt,
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt,
|
||||
ActiveRadarrBlock::BlocklistSortPrompt,
|
||||
];
|
||||
pub static ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 10] = [
|
||||
ActiveRadarrBlock::AddMovieSearchInput,
|
||||
ActiveRadarrBlock::AddMovieSearchResults,
|
||||
|
||||
@@ -6,8 +6,8 @@ mod tests {
|
||||
|
||||
use crate::app::context_clues::build_context_clue_string;
|
||||
use crate::app::radarr::radarr_context_clues::{
|
||||
COLLECTIONS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||
LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||
BLOCKLIST_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES,
|
||||
INDEXERS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||
MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
||||
SYSTEM_CONTEXT_CLUES,
|
||||
};
|
||||
@@ -64,6 +64,7 @@ mod tests {
|
||||
assert_eq!(radarr_data.selected_block, BlockSelectionState::default());
|
||||
assert!(radarr_data.downloads.items.is_empty());
|
||||
assert!(radarr_data.indexers.items.is_empty());
|
||||
assert!(radarr_data.blocklist.items.is_empty());
|
||||
assert!(radarr_data.quality_profile_map.is_empty());
|
||||
assert!(radarr_data.tags_map.is_empty());
|
||||
assert!(radarr_data.collections.items.is_empty());
|
||||
@@ -89,7 +90,7 @@ mod tests {
|
||||
assert!(!radarr_data.delete_movie_files);
|
||||
assert!(!radarr_data.add_list_exclusion);
|
||||
|
||||
assert_eq!(radarr_data.main_tabs.tabs.len(), 6);
|
||||
assert_eq!(radarr_data.main_tabs.tabs.len(), 7);
|
||||
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[0].title, "Library");
|
||||
assert_eq!(
|
||||
@@ -102,58 +103,69 @@ mod tests {
|
||||
Some(build_context_clue_string(&LIBRARY_CONTEXT_CLUES))
|
||||
);
|
||||
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[1].title, "Downloads");
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[1].title, "Collections");
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[1].route,
|
||||
ActiveRadarrBlock::Downloads.into()
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert!(radarr_data.main_tabs.tabs[1].help.is_empty());
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[1].contextual_help,
|
||||
Some(build_context_clue_string(&DOWNLOADS_CONTEXT_CLUES))
|
||||
Some(build_context_clue_string(&COLLECTIONS_CONTEXT_CLUES))
|
||||
);
|
||||
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[2].title, "Collections");
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[2].title, "Downloads");
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[2].route,
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
ActiveRadarrBlock::Downloads.into()
|
||||
);
|
||||
assert!(radarr_data.main_tabs.tabs[2].help.is_empty());
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[2].contextual_help,
|
||||
Some(build_context_clue_string(&COLLECTIONS_CONTEXT_CLUES))
|
||||
Some(build_context_clue_string(&DOWNLOADS_CONTEXT_CLUES))
|
||||
);
|
||||
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[3].title, "Root Folders");
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[3].title, "Blocklist");
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[3].route,
|
||||
ActiveRadarrBlock::RootFolders.into()
|
||||
ActiveRadarrBlock::Blocklist.into()
|
||||
);
|
||||
assert!(radarr_data.main_tabs.tabs[3].help.is_empty());
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[3].contextual_help,
|
||||
Some(build_context_clue_string(&ROOT_FOLDERS_CONTEXT_CLUES))
|
||||
Some(build_context_clue_string(&BLOCKLIST_CONTEXT_CLUES))
|
||||
);
|
||||
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[4].title, "Indexers");
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[4].title, "Root Folders");
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[4].route,
|
||||
ActiveRadarrBlock::Indexers.into()
|
||||
ActiveRadarrBlock::RootFolders.into()
|
||||
);
|
||||
assert!(radarr_data.main_tabs.tabs[4].help.is_empty());
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[4].contextual_help,
|
||||
Some(build_context_clue_string(&INDEXERS_CONTEXT_CLUES))
|
||||
Some(build_context_clue_string(&ROOT_FOLDERS_CONTEXT_CLUES))
|
||||
);
|
||||
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[5].title, "System");
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[5].title, "Indexers");
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[5].route,
|
||||
ActiveRadarrBlock::System.into()
|
||||
ActiveRadarrBlock::Indexers.into()
|
||||
);
|
||||
assert!(radarr_data.main_tabs.tabs[5].help.is_empty());
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[5].contextual_help,
|
||||
Some(build_context_clue_string(&INDEXERS_CONTEXT_CLUES))
|
||||
);
|
||||
|
||||
assert_str_eq!(radarr_data.main_tabs.tabs[6].title, "System");
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[6].route,
|
||||
ActiveRadarrBlock::System.into()
|
||||
);
|
||||
assert!(radarr_data.main_tabs.tabs[6].help.is_empty());
|
||||
assert_eq!(
|
||||
radarr_data.main_tabs.tabs[6].contextual_help,
|
||||
Some(build_context_clue_string(&SYSTEM_CONTEXT_CLUES))
|
||||
);
|
||||
|
||||
@@ -246,10 +258,10 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS, COLLECTIONS_BLOCKS,
|
||||
COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS, DELETE_MOVIE_SELECTION_BLOCKS,
|
||||
DOWNLOADS_BLOCKS, EDIT_COLLECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS,
|
||||
EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_BLOCKS,
|
||||
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS, BLOCKLIST_BLOCKS,
|
||||
COLLECTIONS_BLOCKS, COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS,
|
||||
DELETE_MOVIE_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_COLLECTION_BLOCKS,
|
||||
EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_BLOCKS,
|
||||
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, EDIT_MOVIE_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS,
|
||||
INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, LIBRARY_BLOCKS,
|
||||
MOVIE_DETAILS_BLOCKS, ROOT_FOLDERS_BLOCKS, SYSTEM_DETAILS_BLOCKS,
|
||||
@@ -296,6 +308,16 @@ mod tests {
|
||||
assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveRadarrBlock::DeleteRootFolderPrompt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_blocks_contents() {
|
||||
assert_eq!(BLOCKLIST_BLOCKS.len(), 5);
|
||||
assert!(BLOCKLIST_BLOCKS.contains(&ActiveRadarrBlock::Blocklist));
|
||||
assert!(BLOCKLIST_BLOCKS.contains(&ActiveRadarrBlock::BlocklistItemDetails));
|
||||
assert!(BLOCKLIST_BLOCKS.contains(&ActiveRadarrBlock::DeleteBlocklistItemPrompt));
|
||||
assert!(BLOCKLIST_BLOCKS.contains(&ActiveRadarrBlock::BlocklistClearAllItemsPrompt));
|
||||
assert!(BLOCKLIST_BLOCKS.contains(&ActiveRadarrBlock::BlocklistSortPrompt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_movie_blocks_contents() {
|
||||
assert_eq!(ADD_MOVIE_BLOCKS.len(), 10);
|
||||
|
||||
@@ -143,7 +143,11 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
.put(uri)
|
||||
.json(&body.unwrap_or_default())
|
||||
.header("X-Api-Key", api_token),
|
||||
RequestMethod::Delete => self.client.delete(uri).header("X-Api-Key", api_token),
|
||||
RequestMethod::Delete => self
|
||||
.client
|
||||
.delete(uri)
|
||||
.json(&body.unwrap_or_default())
|
||||
.header("X-Api-Key", api_token),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ use urlencoding::encode;
|
||||
|
||||
use crate::app::RadarrConfig;
|
||||
use crate::models::radarr_models::{
|
||||
AddMovieBody, AddMovieSearchResult, AddOptions, AddRootFolderBody, Collection, CollectionMovie,
|
||||
CommandBody, Credit, CreditType, DiskSpace, DownloadRecord, DownloadsResponse, Indexer,
|
||||
IndexerSettings, IndexerTestResult, LogResponse, Movie, MovieCommandBody, MovieHistoryItem,
|
||||
QualityProfile, QueueEvent, Release, ReleaseDownloadBody, RootFolder, SystemStatus, Tag, Task,
|
||||
Update,
|
||||
AddMovieBody, AddMovieSearchResult, AddOptions, AddRootFolderBody, BlocklistResponse, Collection,
|
||||
CollectionMovie, CommandBody, Credit, CreditType, DiskSpace, DownloadRecord, DownloadsResponse,
|
||||
Indexer, IndexerSettings, IndexerTestResult, LogResponse, Movie, MovieCommandBody,
|
||||
MovieHistoryItem, QualityProfile, QueueEvent, Release, ReleaseDownloadBody, RootFolder,
|
||||
SystemStatus, Tag, Task, Update,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::modals::{
|
||||
AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem,
|
||||
@@ -33,6 +33,8 @@ mod radarr_network_tests;
|
||||
pub enum RadarrEvent {
|
||||
AddMovie,
|
||||
AddRootFolder,
|
||||
ClearBlocklist,
|
||||
DeleteBlocklistItem,
|
||||
DeleteDownload,
|
||||
DeleteIndexer,
|
||||
DeleteMovie,
|
||||
@@ -42,6 +44,7 @@ pub enum RadarrEvent {
|
||||
EditCollection,
|
||||
EditIndexer,
|
||||
EditMovie,
|
||||
GetBlocklist,
|
||||
GetCollections,
|
||||
GetDownloads,
|
||||
GetIndexers,
|
||||
@@ -75,6 +78,9 @@ pub enum RadarrEvent {
|
||||
impl RadarrEvent {
|
||||
const fn resource(self) -> &'static str {
|
||||
match self {
|
||||
RadarrEvent::ClearBlocklist => "/blocklist/bulk",
|
||||
RadarrEvent::DeleteBlocklistItem => "/blocklist",
|
||||
RadarrEvent::GetBlocklist => "/blocklist?page=1&pageSize=10000",
|
||||
RadarrEvent::GetCollections | RadarrEvent::EditCollection => "/collection",
|
||||
RadarrEvent::GetDownloads | RadarrEvent::DeleteDownload => "/queue",
|
||||
RadarrEvent::GetIndexers | RadarrEvent::EditIndexer | RadarrEvent::DeleteIndexer => {
|
||||
@@ -125,6 +131,8 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
match radarr_event {
|
||||
RadarrEvent::AddMovie => self.add_movie().await,
|
||||
RadarrEvent::AddRootFolder => self.add_root_folder().await,
|
||||
RadarrEvent::ClearBlocklist => self.clear_blocklist().await,
|
||||
RadarrEvent::DeleteBlocklistItem => self.delete_blocklist_item().await,
|
||||
RadarrEvent::DeleteDownload => self.delete_download().await,
|
||||
RadarrEvent::DeleteIndexer => self.delete_indexer().await,
|
||||
RadarrEvent::DeleteMovie => self.delete_movie().await,
|
||||
@@ -134,6 +142,7 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
RadarrEvent::EditCollection => self.edit_collection().await,
|
||||
RadarrEvent::EditIndexer => self.edit_indexer().await,
|
||||
RadarrEvent::EditMovie => self.edit_movie().await,
|
||||
RadarrEvent::GetBlocklist => self.get_blocklist().await,
|
||||
RadarrEvent::GetCollections => self.get_collections().await,
|
||||
RadarrEvent::GetDownloads => self.get_downloads().await,
|
||||
RadarrEvent::GetIndexers => self.get_indexers().await,
|
||||
@@ -319,6 +328,64 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn clear_blocklist(&mut self) {
|
||||
info!("Clearing Radarr blocklist");
|
||||
|
||||
let ids = self
|
||||
.app
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| item.id)
|
||||
.collect::<Vec<i64>>();
|
||||
|
||||
let request_props = self
|
||||
.radarr_request_props_from(
|
||||
RadarrEvent::ClearBlocklist.resource(),
|
||||
RequestMethod::Delete,
|
||||
Some(json!({"ids": ids})),
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<Value, ()>(request_props, |_, _| ())
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn delete_blocklist_item(&mut self) {
|
||||
let blocklist_item_id = self
|
||||
.app
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.current_selection()
|
||||
.id;
|
||||
|
||||
info!("Deleting Radarr blocklist item for item with id: {blocklist_item_id}");
|
||||
|
||||
let request_props = self
|
||||
.radarr_request_props_from(
|
||||
format!(
|
||||
"{}/{blocklist_item_id}",
|
||||
RadarrEvent::DeleteBlocklistItem.resource()
|
||||
)
|
||||
.as_str(),
|
||||
RequestMethod::Delete,
|
||||
None::<()>,
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<(), ()>(request_props, |_, _| ())
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn delete_download(&mut self) {
|
||||
let download_id = self
|
||||
.app
|
||||
@@ -794,6 +861,32 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn get_blocklist(&mut self) {
|
||||
info!("Fetching blocklist");
|
||||
|
||||
let request_props = self
|
||||
.radarr_request_props_from(
|
||||
RadarrEvent::GetBlocklist.resource(),
|
||||
RequestMethod::Get,
|
||||
None::<()>,
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<(), BlocklistResponse>(request_props, |blocklist_resp, mut app| {
|
||||
if !matches!(
|
||||
app.get_current_route(),
|
||||
Route::Radarr(ActiveRadarrBlock::BlocklistSortPrompt, _)
|
||||
) {
|
||||
let mut blocklist_vec = blocklist_resp.records;
|
||||
blocklist_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
app.data.radarr_data.blocklist.set_items(blocklist_vec);
|
||||
app.data.radarr_data.blocklist.apply_sorting_toggle(false);
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn get_collections(&mut self) {
|
||||
info!("Fetching Radarr collections");
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ mod test {
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::models::radarr_models::{
|
||||
CollectionMovie, IndexerField, Language, MediaInfo, MinimumAvailability, Monitor, MovieFile,
|
||||
Quality, QualityWrapper, Rating, RatingsList,
|
||||
BlocklistItem, CollectionMovie, IndexerField, Language, MediaInfo, MinimumAvailability,
|
||||
Monitor, MovieFile, Quality, QualityWrapper, Rating, RatingsList,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::stateful_table::SortOption;
|
||||
@@ -186,6 +186,9 @@ mod test {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(RadarrEvent::ClearBlocklist, "/blocklist/bulk")]
|
||||
#[case(RadarrEvent::DeleteBlocklistItem, "/blocklist")]
|
||||
#[case(RadarrEvent::GetBlocklist, "/blocklist?page=1&pageSize=10000")]
|
||||
#[case(RadarrEvent::GetLogs, "/log")]
|
||||
#[case(RadarrEvent::SearchNewMovie, "/movie/lookup")]
|
||||
#[case(RadarrEvent::GetMovieCredits, "/credit")]
|
||||
@@ -1302,6 +1305,271 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_blocklist_event(#[values(true, false)] use_custom_sorting: bool) {
|
||||
let blocklist_json = json!({"records": [{
|
||||
"id": 123,
|
||||
"movieId": 1007,
|
||||
"sourceTitle": "z movie",
|
||||
"languages": [{"name": "English"}],
|
||||
"quality": {"quality": {"name": "HD - 1080p"}},
|
||||
"customFormats": [{"name": "English"}],
|
||||
"date": "2024-02-10T07:28:45Z",
|
||||
"protocol": "usenet",
|
||||
"indexer": "DrunkenSlug (Prowlarr)",
|
||||
"message": "test message",
|
||||
"movie": {
|
||||
"id": 1007,
|
||||
"title": "z movie",
|
||||
"tmdbId": 1234,
|
||||
"originalLanguage": {"name": "English"},
|
||||
"sizeOnDisk": 3543348019i64,
|
||||
"status": "Downloaded",
|
||||
"overview": "Blah blah blah",
|
||||
"path": "/nfs/movies",
|
||||
"studio": "21st Century Alex",
|
||||
"genres": ["cool", "family", "fun"],
|
||||
"year": 2023,
|
||||
"monitored": true,
|
||||
"hasFile": true,
|
||||
"runtime": 120,
|
||||
"qualityProfileId": 2222,
|
||||
"minimumAvailability": "announced",
|
||||
"certification": "R",
|
||||
"tags": [1],
|
||||
"ratings": {
|
||||
"imdb": {"value": 9.9},
|
||||
"tmdb": {"value": 9.9},
|
||||
"rottenTomatoes": {"value": 9.9}
|
||||
},
|
||||
},
|
||||
}, {
|
||||
"id": 456,
|
||||
"movieId": 2001,
|
||||
"sourceTitle": "A Movie",
|
||||
"languages": [{"name": "English"}],
|
||||
"quality": {"quality": {"name": "HD - 1080p"}},
|
||||
"customFormats": [{"name": "English"}],
|
||||
"date": "2024-02-10T07:28:45Z",
|
||||
"protocol": "usenet",
|
||||
"indexer": "DrunkenSlug (Prowlarr)",
|
||||
"message": "test message",
|
||||
"movie": {
|
||||
"id": 2001,
|
||||
"title": "A Movie",
|
||||
"tmdbId": 1234,
|
||||
"originalLanguage": {"name": "English"},
|
||||
"sizeOnDisk": 3543348019i64,
|
||||
"status": "Downloaded",
|
||||
"overview": "Blah blah blah",
|
||||
"path": "/nfs/movies",
|
||||
"studio": "21st Century Alex",
|
||||
"genres": ["cool", "family", "fun"],
|
||||
"year": 2023,
|
||||
"monitored": true,
|
||||
"hasFile": true,
|
||||
"runtime": 120,
|
||||
"qualityProfileId": 2222,
|
||||
"minimumAvailability": "announced",
|
||||
"certification": "R",
|
||||
"tags": [1],
|
||||
"ratings": {
|
||||
"imdb": {"value": 9.9},
|
||||
"tmdb": {"value": 9.9},
|
||||
"rottenTomatoes": {"value": 9.9}
|
||||
},
|
||||
},
|
||||
}]});
|
||||
let mut expected_blocklist = vec![
|
||||
BlocklistItem {
|
||||
id: 123,
|
||||
movie_id: 1007,
|
||||
source_title: "z movie".into(),
|
||||
movie: Movie {
|
||||
id: 1007,
|
||||
title: "z movie".into(),
|
||||
movie_file: None,
|
||||
collection: None,
|
||||
..movie()
|
||||
},
|
||||
..blocklist_item()
|
||||
},
|
||||
BlocklistItem {
|
||||
id: 456,
|
||||
movie_id: 2001,
|
||||
source_title: "A Movie".into(),
|
||||
movie: Movie {
|
||||
id: 2001,
|
||||
title: "A Movie".into(),
|
||||
movie_file: None,
|
||||
collection: None,
|
||||
..movie()
|
||||
},
|
||||
..blocklist_item()
|
||||
},
|
||||
];
|
||||
let (async_server, app_arc, _server) = mock_radarr_api(
|
||||
RequestMethod::Get,
|
||||
None,
|
||||
Some(blocklist_json),
|
||||
None,
|
||||
RadarrEvent::GetBlocklist.resource(),
|
||||
)
|
||||
.await;
|
||||
app_arc.lock().await.data.radarr_data.blocklist.sort_asc = true;
|
||||
if use_custom_sorting {
|
||||
let cmp_fn = |a: &BlocklistItem, b: &BlocklistItem| {
|
||||
a.source_title
|
||||
.to_lowercase()
|
||||
.cmp(&b.source_title.to_lowercase())
|
||||
};
|
||||
expected_blocklist.sort_by(cmp_fn);
|
||||
|
||||
let blocklist_sort_option = SortOption {
|
||||
name: "Source Title",
|
||||
cmp_fn: Some(cmp_fn),
|
||||
};
|
||||
app_arc
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.sorting(vec![blocklist_sort_option]);
|
||||
}
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network.handle_radarr_event(RadarrEvent::GetBlocklist).await;
|
||||
|
||||
async_server.assert_async().await;
|
||||
assert_eq!(
|
||||
app_arc.lock().await.data.radarr_data.blocklist.items,
|
||||
expected_blocklist
|
||||
);
|
||||
assert!(app_arc.lock().await.data.radarr_data.blocklist.sort_asc);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_blocklist_event_no_op_when_user_is_selecting_sort_options() {
|
||||
let blocklist_json = json!({"records": [{
|
||||
"id": 123,
|
||||
"movieId": 1007,
|
||||
"sourceTitle": "z movie",
|
||||
"languages": [{"name": "English"}],
|
||||
"quality": {"quality": {"name": "HD - 1080p"}},
|
||||
"customFormats": [{"name": "English"}],
|
||||
"date": "2024-02-10T07:28:45Z",
|
||||
"protocol": "usenet",
|
||||
"indexer": "DrunkenSlug (Prowlarr)",
|
||||
"message": "test message",
|
||||
"movie": {
|
||||
"id": 1007,
|
||||
"title": "z movie",
|
||||
"tmdbId": 1234,
|
||||
"originalLanguage": {"name": "English"},
|
||||
"sizeOnDisk": 3543348019i64,
|
||||
"status": "Downloaded",
|
||||
"overview": "Blah blah blah",
|
||||
"path": "/nfs/movies",
|
||||
"studio": "21st Century Alex",
|
||||
"genres": ["cool", "family", "fun"],
|
||||
"year": 2023,
|
||||
"monitored": true,
|
||||
"hasFile": true,
|
||||
"runtime": 120,
|
||||
"qualityProfileId": 2222,
|
||||
"minimumAvailability": "announced",
|
||||
"certification": "R",
|
||||
"tags": [1],
|
||||
"ratings": {
|
||||
"imdb": {"value": 9.9},
|
||||
"tmdb": {"value": 9.9},
|
||||
"rottenTomatoes": {"value": 9.9}
|
||||
},
|
||||
},
|
||||
}, {
|
||||
"id": 456,
|
||||
"movieId": 2001,
|
||||
"sourceTitle": "A Movie",
|
||||
"languages": [{"name": "English"}],
|
||||
"quality": {"quality": {"name": "HD - 1080p"}},
|
||||
"customFormats": [{"name": "English"}],
|
||||
"date": "2024-02-10T07:28:45Z",
|
||||
"protocol": "usenet",
|
||||
"indexer": "DrunkenSlug (Prowlarr)",
|
||||
"message": "test message",
|
||||
"movie": {
|
||||
"id": 2001,
|
||||
"title": "A Movie",
|
||||
"tmdbId": 1234,
|
||||
"originalLanguage": {"name": "English"},
|
||||
"sizeOnDisk": 3543348019i64,
|
||||
"status": "Downloaded",
|
||||
"overview": "Blah blah blah",
|
||||
"path": "/nfs/movies",
|
||||
"studio": "21st Century Alex",
|
||||
"genres": ["cool", "family", "fun"],
|
||||
"year": 2023,
|
||||
"monitored": true,
|
||||
"hasFile": true,
|
||||
"runtime": 120,
|
||||
"qualityProfileId": 2222,
|
||||
"minimumAvailability": "announced",
|
||||
"certification": "R",
|
||||
"tags": [1],
|
||||
"ratings": {
|
||||
"imdb": {"value": 9.9},
|
||||
"tmdb": {"value": 9.9},
|
||||
"rottenTomatoes": {"value": 9.9}
|
||||
},
|
||||
},
|
||||
}]});
|
||||
let (async_server, app_arc, _server) = mock_radarr_api(
|
||||
RequestMethod::Get,
|
||||
None,
|
||||
Some(blocklist_json),
|
||||
None,
|
||||
RadarrEvent::GetBlocklist.resource(),
|
||||
)
|
||||
.await;
|
||||
app_arc.lock().await.data.radarr_data.blocklist.sort_asc = true;
|
||||
app_arc
|
||||
.lock()
|
||||
.await
|
||||
.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
|
||||
let cmp_fn = |a: &BlocklistItem, b: &BlocklistItem| {
|
||||
a.source_title
|
||||
.to_lowercase()
|
||||
.cmp(&b.source_title.to_lowercase())
|
||||
};
|
||||
let blocklist_sort_option = SortOption {
|
||||
name: "Source Title",
|
||||
cmp_fn: Some(cmp_fn),
|
||||
};
|
||||
app_arc
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.sorting(vec![blocklist_sort_option]);
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network.handle_radarr_event(RadarrEvent::GetBlocklist).await;
|
||||
|
||||
async_server.assert_async().await;
|
||||
assert!(app_arc
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.items
|
||||
.is_empty());
|
||||
assert!(app_arc.lock().await.data.radarr_data.blocklist.sort_asc);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_collections_event(#[values(true, false)] use_custom_sorting: bool) {
|
||||
@@ -2154,6 +2422,68 @@ mod test {
|
||||
assert!(!app_arc.lock().await.data.radarr_data.add_list_exclusion);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_clear_blocklist_event() {
|
||||
let blocklist_items = vec![
|
||||
BlocklistItem {
|
||||
id: 1,
|
||||
..blocklist_item()
|
||||
},
|
||||
BlocklistItem {
|
||||
id: 2,
|
||||
..blocklist_item()
|
||||
},
|
||||
BlocklistItem {
|
||||
id: 3,
|
||||
..blocklist_item()
|
||||
},
|
||||
];
|
||||
let expected_request_json = json!({ "ids": [1, 2, 3]});
|
||||
let (async_server, app_arc, _server) = mock_radarr_api(
|
||||
RequestMethod::Delete,
|
||||
Some(expected_request_json),
|
||||
None,
|
||||
None,
|
||||
RadarrEvent::ClearBlocklist.resource(),
|
||||
)
|
||||
.await;
|
||||
app_arc
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.set_items(blocklist_items);
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network
|
||||
.handle_radarr_event(RadarrEvent::ClearBlocklist)
|
||||
.await;
|
||||
|
||||
async_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_delete_blocklist_item_event() {
|
||||
let resource = format!("{}/1", RadarrEvent::DeleteBlocklistItem.resource());
|
||||
let (async_server, app_arc, _server) =
|
||||
mock_radarr_api(RequestMethod::Delete, None, None, None, &resource).await;
|
||||
app_arc
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.set_items(vec![blocklist_item()]);
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network
|
||||
.handle_radarr_event(RadarrEvent::DeleteBlocklistItem)
|
||||
.await;
|
||||
|
||||
async_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_delete_download_event() {
|
||||
let resource = format!("{}/1", RadarrEvent::DeleteDownload.resource());
|
||||
@@ -3311,6 +3641,22 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
fn blocklist_item() -> BlocklistItem {
|
||||
BlocklistItem {
|
||||
id: 1,
|
||||
movie_id: 1,
|
||||
source_title: "z movie".to_owned(),
|
||||
languages: vec![language()],
|
||||
quality: quality_wrapper(),
|
||||
custom_formats: Some(vec![language()]),
|
||||
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
|
||||
protocol: "usenet".to_owned(),
|
||||
indexer: "DrunkenSlug (Prowlarr)".to_owned(),
|
||||
message: "test message".to_owned(),
|
||||
movie: movie(),
|
||||
}
|
||||
}
|
||||
|
||||
fn collection() -> Collection {
|
||||
Collection {
|
||||
id: 123,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||
use crate::ui::radarr_ui::blocklist::BlocklistUi;
|
||||
use crate::ui::DrawUi;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_ui_accepts() {
|
||||
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
|
||||
if BLOCKLIST_BLOCKS.contains(&active_radarr_block) {
|
||||
assert!(BlocklistUi::accepts(active_radarr_block.into()));
|
||||
} else {
|
||||
assert!(!BlocklistUi::accepts(active_radarr_block.into()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
use crate::app::App;
|
||||
use crate::models::radarr_models::BlocklistItem;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||
use crate::models::Route;
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
|
||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||
use crate::ui::widgets::message::Message;
|
||||
use crate::ui::widgets::popup::{Popup, Size};
|
||||
use crate::ui::DrawUi;
|
||||
use ratatui::layout::{Alignment, Constraint, Rect};
|
||||
use ratatui::style::{Style, Stylize};
|
||||
use ratatui::text::{Line, Text};
|
||||
use ratatui::widgets::{Cell, Row};
|
||||
use ratatui::Frame;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "blocklist_ui_tests.rs"]
|
||||
mod blocklist_ui_tests;
|
||||
|
||||
pub(super) struct BlocklistUi;
|
||||
|
||||
impl DrawUi for BlocklistUi {
|
||||
fn accepts(route: Route) -> bool {
|
||||
if let Route::Radarr(active_radarr_block, _) = route {
|
||||
return BLOCKLIST_BLOCKS.contains(&active_radarr_block);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||
match active_radarr_block {
|
||||
ActiveRadarrBlock::Blocklist | ActiveRadarrBlock::BlocklistSortPrompt => {
|
||||
draw_blocklist_table(f, app, area)
|
||||
}
|
||||
ActiveRadarrBlock::BlocklistItemDetails => {
|
||||
draw_blocklist_table(f, app, area);
|
||||
draw_blocklist_item_details_popup(f, app);
|
||||
}
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
|
||||
let prompt = format!(
|
||||
"Do you want to remove this item from your blocklist: \n{}?",
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.blocklist
|
||||
.current_selection()
|
||||
.source_title
|
||||
);
|
||||
let confirmation_prompt = ConfirmationPrompt::new()
|
||||
.title("Remove Item from Blocklist")
|
||||
.prompt(&prompt)
|
||||
.yes_no_value(app.data.radarr_data.prompt_confirm);
|
||||
|
||||
draw_blocklist_table(f, app, area);
|
||||
f.render_widget(Popup::new(confirmation_prompt).size(Size::Prompt), f.size());
|
||||
}
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
|
||||
let confirmation_prompt = ConfirmationPrompt::new()
|
||||
.title("Clear Blocklist")
|
||||
.prompt("Do you want to clear your blocklist?")
|
||||
.yes_no_value(app.data.radarr_data.prompt_confirm);
|
||||
|
||||
draw_blocklist_table(f, app, area);
|
||||
f.render_widget(
|
||||
Popup::new(confirmation_prompt).size(Size::SmallPrompt),
|
||||
f.size(),
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_blocklist_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||
let current_selection = if app.data.radarr_data.blocklist.items.is_empty() {
|
||||
BlocklistItem::default()
|
||||
} else {
|
||||
app.data.radarr_data.blocklist.current_selection().clone()
|
||||
};
|
||||
let blocklist_table_footer = app
|
||||
.data
|
||||
.radarr_data
|
||||
.main_tabs
|
||||
.get_active_tab_contextual_help();
|
||||
|
||||
let blocklist_row_mapping = |blocklist_item: &BlocklistItem| {
|
||||
let BlocklistItem {
|
||||
source_title,
|
||||
languages,
|
||||
quality,
|
||||
custom_formats,
|
||||
date,
|
||||
movie,
|
||||
..
|
||||
} = blocklist_item;
|
||||
|
||||
movie.title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 20),
|
||||
current_selection == *blocklist_item,
|
||||
app.tick_count % app.ticks_until_scroll == 0,
|
||||
);
|
||||
|
||||
let languages_string = languages
|
||||
.iter()
|
||||
.map(|lang| lang.name.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let custom_formats_string = if let Some(formats) = custom_formats.as_ref() {
|
||||
formats
|
||||
.iter()
|
||||
.map(|cf| cf.name.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
|
||||
Row::new(vec![
|
||||
Cell::from(movie.title.to_string()),
|
||||
Cell::from(source_title.to_owned()),
|
||||
Cell::from(languages_string),
|
||||
Cell::from(quality.quality.name.to_owned()),
|
||||
Cell::from(custom_formats_string),
|
||||
Cell::from(date.to_string()),
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let blocklist_table = ManagarrTable::new(
|
||||
Some(&mut app.data.radarr_data.blocklist),
|
||||
blocklist_row_mapping,
|
||||
)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.footer(blocklist_table_footer)
|
||||
.sorting(active_radarr_block == ActiveRadarrBlock::BlocklistSortPrompt)
|
||||
.headers([
|
||||
"Movie Title",
|
||||
"Source Title",
|
||||
"Languages",
|
||||
"Quality",
|
||||
"Formats",
|
||||
"Date",
|
||||
])
|
||||
.constraints([
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Percentage(35),
|
||||
Constraint::Percentage(10),
|
||||
Constraint::Percentage(10),
|
||||
Constraint::Percentage(10),
|
||||
Constraint::Percentage(15),
|
||||
]);
|
||||
|
||||
f.render_widget(blocklist_table, area);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_blocklist_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let current_selection = if app.data.radarr_data.blocklist.items.is_empty() {
|
||||
BlocklistItem::default()
|
||||
} else {
|
||||
app.data.radarr_data.blocklist.current_selection().clone()
|
||||
};
|
||||
let BlocklistItem {
|
||||
source_title,
|
||||
protocol,
|
||||
indexer,
|
||||
message,
|
||||
..
|
||||
} = current_selection;
|
||||
let text = Text::from(vec![
|
||||
Line::from(vec![
|
||||
"Name: ".bold().secondary(),
|
||||
source_title.to_owned().secondary(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
"Protocol: ".bold().secondary(),
|
||||
protocol.to_owned().secondary(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
"Indexer: ".bold().secondary(),
|
||||
indexer.to_owned().secondary(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
"Message: ".bold().secondary(),
|
||||
message.to_owned().secondary(),
|
||||
]),
|
||||
]);
|
||||
|
||||
let message = Message::new(text)
|
||||
.title("Details")
|
||||
.style(Style::new().secondary())
|
||||
.alignment(Alignment::Left);
|
||||
|
||||
f.render_widget(Popup::new(message).size(Size::NarrowMessage), f.size());
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie, RootFolder}
|
||||
use crate::models::servarr_data::radarr::radarr_data::RadarrData;
|
||||
use crate::models::Route;
|
||||
use crate::ui::draw_tabs;
|
||||
use crate::ui::radarr_ui::blocklist::BlocklistUi;
|
||||
use crate::ui::radarr_ui::collections::CollectionsUi;
|
||||
use crate::ui::radarr_ui::downloads::DownloadsUi;
|
||||
use crate::ui::radarr_ui::indexers::IndexersUi;
|
||||
@@ -27,6 +28,7 @@ use crate::ui::widgets::loading_block::LoadingBlock;
|
||||
use crate::ui::DrawUi;
|
||||
use crate::utils::convert_to_gb;
|
||||
|
||||
mod blocklist;
|
||||
mod collections;
|
||||
mod downloads;
|
||||
mod indexers;
|
||||
@@ -57,6 +59,7 @@ impl DrawUi for RadarrUi {
|
||||
_ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_area),
|
||||
_ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_area),
|
||||
_ if SystemUi::accepts(route) => SystemUi::draw(f, app, content_area),
|
||||
_ if BlocklistUi::accepts(route) => BlocklistUi::draw(f, app, content_area),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ pub struct Message<'a> {
|
||||
text: Text<'a>,
|
||||
title: &'a str,
|
||||
style: Style,
|
||||
alignment: Alignment,
|
||||
}
|
||||
|
||||
impl<'a> Message<'a> {
|
||||
@@ -25,6 +26,7 @@ impl<'a> Message<'a> {
|
||||
text: message.into(),
|
||||
title: "Error",
|
||||
style: Style::new().failure().bold(),
|
||||
alignment: Alignment::Center,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +40,15 @@ impl<'a> Message<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alignment(mut self, alignment: Alignment) -> Self {
|
||||
self.alignment = alignment;
|
||||
self
|
||||
}
|
||||
|
||||
fn render_message(self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new(self.text)
|
||||
.style(self.style)
|
||||
.alignment(Alignment::Center)
|
||||
.alignment(self.alignment)
|
||||
.block(title_block_centered(self.title).style(self.style))
|
||||
.wrap(Wrap { trim: true })
|
||||
.render(area, buf);
|
||||
|
||||
@@ -3,6 +3,7 @@ mod tests {
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::widgets::message::Message;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use ratatui::layout::Alignment;
|
||||
use ratatui::style::{Style, Stylize};
|
||||
use ratatui::text::Text;
|
||||
|
||||
@@ -15,6 +16,7 @@ mod tests {
|
||||
assert_eq!(message.text, Text::from(test_message));
|
||||
assert_str_eq!(message.title, "Error");
|
||||
assert_eq!(message.style, Style::new().failure().bold());
|
||||
assert_eq!(message.alignment, Alignment::Center);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -27,6 +29,7 @@ mod tests {
|
||||
assert_str_eq!(message.title, title);
|
||||
assert_eq!(message.text, Text::from(test_message));
|
||||
assert_eq!(message.style, Style::new().failure().bold());
|
||||
assert_eq!(message.alignment, Alignment::Center);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -39,5 +42,18 @@ mod tests {
|
||||
assert_eq!(message.style, style);
|
||||
assert_eq!(message.text, Text::from(test_message));
|
||||
assert_str_eq!(message.title, "Error");
|
||||
assert_eq!(message.alignment, Alignment::Center);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_alignment() {
|
||||
let test_message = "This is a message";
|
||||
|
||||
let message = Message::new(test_message).alignment(Alignment::Left);
|
||||
|
||||
assert_eq!(message.alignment, Alignment::Left);
|
||||
assert_eq!(message.text, Text::from(test_message));
|
||||
assert_str_eq!(message.title, "Error");
|
||||
assert_eq!(message.style, Style::new().failure().bold());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ use ratatui::widgets::{Block, Clear, Paragraph, Widget};
|
||||
mod popup_tests;
|
||||
|
||||
pub enum Size {
|
||||
SmallPrompt,
|
||||
Prompt,
|
||||
LargePrompt,
|
||||
Message,
|
||||
NarrowMessage,
|
||||
LargeMessage,
|
||||
InputBox,
|
||||
Dropdown,
|
||||
@@ -24,9 +26,11 @@ pub enum Size {
|
||||
impl Size {
|
||||
pub fn to_percent(&self) -> (u16, u16) {
|
||||
match self {
|
||||
Size::SmallPrompt => (20, 20),
|
||||
Size::Prompt => (35, 35),
|
||||
Size::LargePrompt => (70, 45),
|
||||
Size::Message => (25, 8),
|
||||
Size::NarrowMessage => (50, 20),
|
||||
Size::LargeMessage => (25, 25),
|
||||
Size::InputBox => (30, 13),
|
||||
Size::Dropdown => (20, 30),
|
||||
|
||||
@@ -6,9 +6,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_dimensions_to_percent() {
|
||||
assert_eq!(Size::SmallPrompt.to_percent(), (20, 20));
|
||||
assert_eq!(Size::Prompt.to_percent(), (35, 35));
|
||||
assert_eq!(Size::LargePrompt.to_percent(), (70, 45));
|
||||
assert_eq!(Size::Message.to_percent(), (25, 8));
|
||||
assert_eq!(Size::NarrowMessage.to_percent(), (50, 20));
|
||||
assert_eq!(Size::LargeMessage.to_percent(), (25, 25));
|
||||
assert_eq!(Size::InputBox.to_percent(), (30, 13));
|
||||
assert_eq!(Size::Dropdown.to_percent(), (20, 30));
|
||||
|
||||