Mostly completed tags implementation; still need to add the UI option for the Add Movie popup, and I still need to fix the REALLY FAST horizontal scrolling issue (I'm thinking just %2 everything to slow it down). Oh, and also need to convert the quality profile Hashmap into a BiMap

This commit is contained in:
2023-08-08 10:50:05 -06:00
parent f92042fb21
commit 207b8a8c80
21 changed files with 948 additions and 344 deletions
+1
View File
@@ -13,6 +13,7 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.68" anyhow = "1.0.68"
backtrace = "0.3.67" backtrace = "0.3.67"
bimap = "0.6.3"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.0.30", features = ["help", "usage", "error-context", "derive"] } clap = { version = "4.0.30", features = ["help", "usage", "error-context", "derive"] }
confy = { version = "0.5.1", default_features = false, features = ["yaml_conf"] } confy = { version = "0.5.1", default_features = false, features = ["yaml_conf"] }
+7 -3
View File
@@ -1,7 +1,7 @@
use std::time::Duration; use std::time::Duration;
use anyhow::anyhow; use anyhow::anyhow;
use log::error; use log::{debug, error};
use reqwest::Client; use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
@@ -21,6 +21,7 @@ pub struct App {
network_tx: Option<Sender<NetworkEvent>>, network_tx: Option<Sender<NetworkEvent>>,
pub server_tabs: TabState, pub server_tabs: TabState,
pub error: HorizontallyScrollableText, pub error: HorizontallyScrollableText,
pub response: String,
pub client: Client, pub client: Client,
pub title: &'static str, pub title: &'static str,
pub tick_until_poll: u64, pub tick_until_poll: u64,
@@ -45,6 +46,8 @@ impl App {
} }
pub async fn dispatch_network_event(&mut self, action: NetworkEvent) { pub async fn dispatch_network_event(&mut self, action: NetworkEvent) {
debug!("Dispatching network event: {:?}", action);
if let Some(network_tx) = &self.network_tx { if let Some(network_tx) = &self.network_tx {
if let Err(e) = network_tx.send(action).await { if let Err(e) = network_tx.send(action).await {
self.is_loading = false; self.is_loading = false;
@@ -115,6 +118,7 @@ impl Default for App {
navigation_stack: vec![DEFAULT_ROUTE], navigation_stack: vec![DEFAULT_ROUTE],
network_tx: None, network_tx: None,
error: HorizontallyScrollableText::default(), error: HorizontallyScrollableText::default(),
response: String::default(),
server_tabs: TabState::new(vec![ server_tabs: TabState::new(vec![
TabRoute { TabRoute {
title: "Radarr".to_owned(), title: "Radarr".to_owned(),
@@ -260,11 +264,11 @@ mod tests {
app.handle_error(anyhow!(test_string)); app.handle_error(anyhow!(test_string));
assert_eq!(app.error.stationary_style(), test_string); assert_eq!(app.error.text, test_string);
app.handle_error(anyhow!("Testing a different error")); app.handle_error(anyhow!("Testing a different error"));
assert_eq!(app.error.stationary_style(), test_string); assert_eq!(app.error.text, test_string);
} }
#[tokio::test] #[tokio::test]
+91 -38
View File
@@ -1,6 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
use bimap::BiMap;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -9,7 +10,9 @@ use crate::models::radarr_models::{
AddMovieSearchResult, Collection, CollectionMovie, Credit, DiskSpace, DownloadRecord, AddMovieSearchResult, Collection, CollectionMovie, Credit, DiskSpace, DownloadRecord,
MinimumAvailability, Monitor, Movie, MovieHistoryItem, Release, ReleaseField, RootFolder, MinimumAvailability, Monitor, Movie, MovieHistoryItem, Release, ReleaseField, RootFolder,
}; };
use crate::models::{ScrollableText, StatefulList, StatefulTable, TabRoute, TabState}; use crate::models::{
HorizontallyScrollableText, ScrollableText, StatefulList, StatefulTable, TabRoute, TabState,
};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
pub struct RadarrData { pub struct RadarrData {
@@ -26,6 +29,7 @@ pub struct RadarrData {
pub selected_block: ActiveRadarrBlock, pub selected_block: ActiveRadarrBlock,
pub downloads: StatefulTable<DownloadRecord>, pub downloads: StatefulTable<DownloadRecord>,
pub quality_profile_map: HashMap<u64, String>, pub quality_profile_map: HashMap<u64, String>,
pub tags_map: BiMap<u64, String>,
pub movie_details: ScrollableText, pub movie_details: ScrollableText,
pub file_details: String, pub file_details: String,
pub audio_details: String, pub audio_details: String,
@@ -41,10 +45,10 @@ pub struct RadarrData {
pub prompt_confirm_action: Option<RadarrEvent>, pub prompt_confirm_action: Option<RadarrEvent>,
pub main_tabs: TabState, pub main_tabs: TabState,
pub movie_info_tabs: TabState, pub movie_info_tabs: TabState,
pub search: String, pub search: HorizontallyScrollableText,
pub filter: String, pub filter: HorizontallyScrollableText,
pub edit_path: String, pub edit_path: HorizontallyScrollableText,
pub edit_tags: String, pub edit_tags: HorizontallyScrollableText,
pub edit_monitored: Option<bool>, pub edit_monitored: Option<bool>,
pub sort_ascending: Option<bool>, pub sort_ascending: Option<bool>,
pub prompt_confirm: bool, pub prompt_confirm: bool,
@@ -59,8 +63,8 @@ impl RadarrData {
pub fn reset_search(&mut self) { pub fn reset_search(&mut self) {
self.is_searching = false; self.is_searching = false;
self.search = String::default(); self.search = HorizontallyScrollableText::default();
self.filter = String::default(); self.filter = HorizontallyScrollableText::default();
self.filtered_movies = StatefulTable::default(); self.filtered_movies = StatefulTable::default();
self.filtered_collections = StatefulTable::default(); self.filtered_collections = StatefulTable::default();
self.add_searched_movies = StatefulTable::default(); self.add_searched_movies = StatefulTable::default();
@@ -68,15 +72,15 @@ impl RadarrData {
pub fn reset_filter(&mut self) { pub fn reset_filter(&mut self) {
self.is_filtering = false; self.is_filtering = false;
self.filter = String::default(); self.filter = HorizontallyScrollableText::default();
self.filtered_movies = StatefulTable::default(); self.filtered_movies = StatefulTable::default();
self.filtered_collections = StatefulTable::default(); self.filtered_collections = StatefulTable::default();
} }
pub fn reset_edit_movie(&mut self) { pub fn reset_edit_movie(&mut self) {
self.edit_monitored = None; self.edit_monitored = None;
self.edit_path = String::default(); self.edit_path = HorizontallyScrollableText::default();
self.edit_tags = String::default(); self.edit_tags = HorizontallyScrollableText::default();
self.reset_movie_preferences_selections(); self.reset_movie_preferences_selections();
} }
@@ -119,24 +123,37 @@ impl RadarrData {
self.populate_movie_preferences_lists(); self.populate_movie_preferences_lists();
let Movie { let Movie {
path, path,
tags,
monitored, monitored,
minimum_availability, minimum_availability,
quality_profile_id, quality_profile_id,
.. ..
} = if self.filtered_movies.items.is_empty() { } = if self.filtered_movies.items.is_empty() {
self.movies.current_selection_clone() self.movies.current_selection()
} else { } else {
self.filtered_movies.current_selection_clone() self.filtered_movies.current_selection()
}; };
self.edit_path = path; self.edit_path = path.clone().into();
self.edit_monitored = Some(monitored); self.edit_tags = tags
.iter()
.map(|tag_id| {
self
.tags_map
.get_by_left(&tag_id.as_u64().unwrap())
.unwrap()
.clone()
})
.collect::<Vec<String>>()
.join(", ")
.into();
self.edit_monitored = Some(*monitored);
let minimum_availability_index = self let minimum_availability_index = self
.movie_minimum_availability_list .movie_minimum_availability_list
.items .items
.iter() .iter()
.position(|&ma| ma == minimum_availability); .position(|ma| ma == minimum_availability);
self self
.movie_minimum_availability_list .movie_minimum_availability_list
.state .state
@@ -174,6 +191,7 @@ impl Default for RadarrData {
filtered_movies: StatefulTable::default(), filtered_movies: StatefulTable::default(),
downloads: StatefulTable::default(), downloads: StatefulTable::default(),
quality_profile_map: HashMap::default(), quality_profile_map: HashMap::default(),
tags_map: BiMap::default(),
file_details: String::default(), file_details: String::default(),
audio_details: String::default(), audio_details: String::default(),
video_details: String::default(), video_details: String::default(),
@@ -187,10 +205,10 @@ impl Default for RadarrData {
filtered_collections: StatefulTable::default(), filtered_collections: StatefulTable::default(),
collection_movies: StatefulTable::default(), collection_movies: StatefulTable::default(),
prompt_confirm_action: None, prompt_confirm_action: None,
search: String::default(), search: HorizontallyScrollableText::default(),
filter: String::default(), filter: HorizontallyScrollableText::default(),
edit_path: String::default(), edit_path: HorizontallyScrollableText::default(),
edit_tags: String::default(), edit_tags: HorizontallyScrollableText::default(),
edit_monitored: None, edit_monitored: None,
sort_ascending: None, sort_ascending: None,
is_searching: false, is_searching: false,
@@ -519,6 +537,9 @@ impl App {
self self
.dispatch_network_event(RadarrEvent::GetQualityProfiles.into()) .dispatch_network_event(RadarrEvent::GetQualityProfiles.into())
.await; .await;
self
.dispatch_network_event(RadarrEvent::GetTags.into())
.await;
self self
.dispatch_network_event(RadarrEvent::GetRootFolders.into()) .dispatch_network_event(RadarrEvent::GetRootFolders.into())
.await; .await;
@@ -538,6 +559,12 @@ impl App {
.unwrap_or_else(|| Duration::from_secs(0)) .unwrap_or_else(|| Duration::from_secs(0))
.is_zero() .is_zero()
{ {
self
.dispatch_network_event(RadarrEvent::GetQualityProfiles.into())
.await;
self
.dispatch_network_event(RadarrEvent::GetTags.into())
.await;
self.dispatch_by_radarr_block(&active_radarr_block).await; self.dispatch_by_radarr_block(&active_radarr_block).await;
} }
} }
@@ -548,7 +575,8 @@ impl App {
.data .data
.radarr_data .radarr_data
.filtered_collections .filtered_collections
.current_selection_clone() .current_selection()
.clone()
.movies .movies
.unwrap_or_default() .unwrap_or_default()
} else { } else {
@@ -556,7 +584,8 @@ impl App {
.data .data
.radarr_data .radarr_data
.collections .collections
.current_selection_clone() .current_selection()
.clone()
.movies .movies
.unwrap_or_default() .unwrap_or_default()
}; };
@@ -582,10 +611,10 @@ pub mod radarr_test_utils {
let mut radarr_data = RadarrData { let mut radarr_data = RadarrData {
is_searching: true, is_searching: true,
is_filtering: true, is_filtering: true,
search: "test search".to_owned(), search: "test search".to_owned().into(),
filter: "test filter".to_owned(), filter: "test filter".to_owned().into(),
edit_path: "test path".to_owned(), edit_path: "test path".to_owned().into(),
edit_tags: "test tag".to_owned(), edit_tags: "usenet, test".to_owned().into(),
edit_monitored: Some(true), edit_monitored: Some(true),
file_details: "test file details".to_owned(), file_details: "test file details".to_owned(),
audio_details: "test audio details".to_owned(), audio_details: "test audio details".to_owned(),
@@ -642,8 +671,8 @@ pub mod radarr_test_utils {
macro_rules! assert_search_reset { macro_rules! assert_search_reset {
($radarr_data:expr) => { ($radarr_data:expr) => {
assert!(!$radarr_data.is_searching); assert!(!$radarr_data.is_searching);
assert!($radarr_data.search.is_empty()); assert!($radarr_data.search.text.is_empty());
assert!($radarr_data.filter.is_empty()); assert!($radarr_data.filter.text.is_empty());
assert!($radarr_data.filtered_movies.items.is_empty()); assert!($radarr_data.filtered_movies.items.is_empty());
assert!($radarr_data.filtered_collections.items.is_empty()); assert!($radarr_data.filtered_collections.items.is_empty());
assert!($radarr_data.add_searched_movies.items.is_empty()); assert!($radarr_data.add_searched_movies.items.is_empty());
@@ -654,8 +683,8 @@ pub mod radarr_test_utils {
macro_rules! assert_edit_movie_reset { macro_rules! assert_edit_movie_reset {
($radarr_data:expr) => { ($radarr_data:expr) => {
assert!($radarr_data.edit_monitored.is_none()); assert!($radarr_data.edit_monitored.is_none());
assert!($radarr_data.edit_path.is_empty()); assert!($radarr_data.edit_path.text.is_empty());
assert!($radarr_data.edit_tags.is_empty()); assert!($radarr_data.edit_tags.text.is_empty());
}; };
} }
@@ -663,7 +692,7 @@ pub mod radarr_test_utils {
macro_rules! assert_filter_reset { macro_rules! assert_filter_reset {
($radarr_data:expr) => { ($radarr_data:expr) => {
assert!(!$radarr_data.is_filtering); assert!(!$radarr_data.is_filtering);
assert!($radarr_data.filter.is_empty()); assert!($radarr_data.filter.text.is_empty());
assert!($radarr_data.filtered_movies.items.is_empty()); assert!($radarr_data.filtered_movies.items.is_empty());
assert!($radarr_data.filtered_collections.items.is_empty()); assert!($radarr_data.filtered_collections.items.is_empty());
}; };
@@ -704,7 +733,8 @@ mod tests {
mod radarr_data_tests { mod radarr_data_tests {
use std::collections::HashMap; use std::collections::HashMap;
use pretty_assertions::assert_eq; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest; use rstest::rstest;
use serde_json::Number; use serde_json::Number;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -712,7 +742,7 @@ mod tests {
use crate::app::radarr::radarr_test_utils::create_test_radarr_data; use crate::app::radarr::radarr_test_utils::create_test_radarr_data;
use crate::app::radarr::{ActiveRadarrBlock, RadarrData}; use crate::app::radarr::{ActiveRadarrBlock, RadarrData};
use crate::models::radarr_models::{MinimumAvailability, Monitor, Movie}; use crate::models::radarr_models::{MinimumAvailability, Monitor, Movie};
use crate::models::{Route, StatefulTable}; use crate::models::{HorizontallyScrollableText, Route, StatefulTable};
#[test] #[test]
fn test_from_tuple_to_route_with_context() { fn test_from_tuple_to_route_with_context() {
@@ -768,8 +798,8 @@ mod tests {
fn test_reset_edit_movie() { fn test_reset_edit_movie() {
let mut radarr_data = RadarrData { let mut radarr_data = RadarrData {
edit_monitored: Some(true), edit_monitored: Some(true),
edit_path: "test path".to_owned(), edit_path: "test path".to_owned().into(),
edit_tags: "test tag".to_owned(), edit_tags: "test tag".to_owned().into(),
..RadarrData::default() ..RadarrData::default()
}; };
@@ -816,13 +846,14 @@ mod tests {
#[rstest] #[rstest]
fn test_populate_edit_movie_fields(#[values(true, false)] test_filtered_movies: bool) { fn test_populate_edit_movie_fields(#[values(true, false)] test_filtered_movies: bool) {
let mut radarr_data = RadarrData { let mut radarr_data = RadarrData {
edit_path: String::default(), edit_path: HorizontallyScrollableText::default(),
edit_tags: String::default(), edit_tags: HorizontallyScrollableText::default(),
edit_monitored: None, edit_monitored: None,
quality_profile_map: HashMap::from([ quality_profile_map: HashMap::from([
(2222, "HD - 1080p".to_owned()), (2222, "HD - 1080p".to_owned()),
(1111, "Any".to_owned()), (1111, "Any".to_owned()),
]), ]),
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]),
filtered_movies: StatefulTable::default(), filtered_movies: StatefulTable::default(),
..create_test_radarr_data() ..create_test_radarr_data()
}; };
@@ -831,6 +862,7 @@ mod tests {
monitored: true, monitored: true,
quality_profile_id: Number::from(2222), quality_profile_id: Number::from(2222),
minimum_availability: MinimumAvailability::Released, minimum_availability: MinimumAvailability::Released,
tags: vec![Number::from(1), Number::from(2)],
..Movie::default() ..Movie::default()
}; };
@@ -856,11 +888,12 @@ mod tests {
radarr_data.movie_quality_profile_list.items, radarr_data.movie_quality_profile_list.items,
vec!["Any".to_owned(), "HD - 1080p".to_owned()] vec!["Any".to_owned(), "HD - 1080p".to_owned()]
); );
assert_eq!( assert_str_eq!(
radarr_data.movie_quality_profile_list.current_selection(), radarr_data.movie_quality_profile_list.current_selection(),
"HD - 1080p" "HD - 1080p"
); );
assert_eq!(radarr_data.edit_path, "/nfs/movies/Test".to_owned()); assert_str_eq!(radarr_data.edit_path.text, "/nfs/movies/Test");
assert_str_eq!(radarr_data.edit_tags.text, "usenet, test");
assert_eq!(radarr_data.edit_monitored, Some(true)); assert_eq!(radarr_data.edit_monitored, Some(true));
} }
} }
@@ -1325,6 +1358,10 @@ mod tests {
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetQualityProfiles.into() RadarrEvent::GetQualityProfiles.into()
); );
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetTags.into()
);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetRootFolders.into() RadarrEvent::GetRootFolders.into()
@@ -1365,6 +1402,14 @@ mod tests {
.radarr_on_tick(ActiveRadarrBlock::Downloads, false) .radarr_on_tick(ActiveRadarrBlock::Downloads, false)
.await; .await;
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetQualityProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetTags.into()
);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetDownloads.into() RadarrEvent::GetDownloads.into()
@@ -1382,6 +1427,14 @@ mod tests {
.radarr_on_tick(ActiveRadarrBlock::Downloads, false) .radarr_on_tick(ActiveRadarrBlock::Downloads, false)
.await; .await;
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetQualityProfiles.into()
);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetTags.into()
);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetDownloads.into() RadarrEvent::GetDownloads.into()
+1 -1
View File
@@ -20,7 +20,7 @@ pub struct Events {
impl Events { impl Events {
pub fn new() -> Self { pub fn new() -> Self {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let tick_rate: Duration = Duration::from_millis(250); let tick_rate: Duration = Duration::from_millis(50);
thread::spawn(move || { thread::spawn(move || {
let mut last_tick = Instant::now(); let mut last_tick = Instant::now();
+89
View File
@@ -65,6 +65,21 @@ fn handle_prompt_toggle(app: &mut App, key: &Key) {
} }
} }
#[macro_export]
macro_rules! handle_text_box_left_right_keys {
($self:expr, $key:expr, $input:expr) => {
match $self.key {
_ if *$key == DEFAULT_KEYBINDINGS.left.key => {
$input.scroll_left();
}
_ if *$key == DEFAULT_KEYBINDINGS.right.key => {
$input.scroll_right();
}
_ => (),
}
};
}
#[macro_export] #[macro_export]
macro_rules! handle_text_box_keys { macro_rules! handle_text_box_keys {
($self:expr, $key:expr, $input:expr) => { ($self:expr, $key:expr, $input:expr) => {
@@ -221,6 +236,27 @@ mod test_utils {
); );
} }
}; };
($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => {
#[rstest]
fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) {
let mut app = App::default();
app.data.radarr_data.$data_ref.set_items($items);
$handler::with(&key, &mut app, &$block, &$context).handle();
assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field,
"Test 2"
);
$handler::with(&key, &mut app, &$block, &$context).handle();
assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field,
"Test 1"
);
}
};
($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => { ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => {
#[rstest] #[rstest]
fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) { fn $func(#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key) {
@@ -338,6 +374,27 @@ mod test_utils {
); );
} }
}; };
($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident) => {
#[test]
fn $func() {
let mut app = App::default();
app.data.radarr_data.$data_ref.set_items($items);
$handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &$context).handle();
assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field,
"Test 3"
);
$handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &$context).handle();
assert_str_eq!(
app.data.radarr_data.$data_ref.current_selection().$field,
"Test 1"
);
}
};
($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => { ($func:ident, $handler:ident, $data_ref:ident, $items:expr, $block:expr, $context:expr, $field:ident, $conversion_fn:ident) => {
#[test] #[test]
fn $func() { fn $func() {
@@ -403,6 +460,38 @@ mod test_utils {
}; };
} }
#[macro_export]
macro_rules! test_text_box_home_end_keys {
($handler:ident, $block:expr, $field:ident) => {
let mut app = App::default();
app.data.radarr_data.$field = "Test".to_owned().into();
$handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &None).handle();
assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 4);
$handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &None).handle();
assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 0);
};
}
#[macro_export]
macro_rules! test_text_box_left_right_keys {
($handler:ident, $block:expr, $field:ident) => {
let mut app = App::default();
app.data.radarr_data.$field = "Test".to_owned().into();
$handler::with(&DEFAULT_KEYBINDINGS.left.key, &mut app, &$block, &None).handle();
assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 1);
$handler::with(&DEFAULT_KEYBINDINGS.right.key, &mut app, &$block, &None).handle();
assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 0);
};
}
#[macro_export] #[macro_export]
macro_rules! test_handler_delegation { macro_rules! test_handler_delegation {
($base:expr, $active_block:expr) => { ($base:expr, $active_block:expr) => {
@@ -3,7 +3,7 @@ use crate::app::radarr::ActiveRadarrBlock;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::{Scrollable, StatefulTable}; use crate::models::{Scrollable, StatefulTable};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, App, Key}; use crate::{handle_text_box_keys, handle_text_box_left_right_keys, App, Key};
pub(super) struct AddMovieHandler<'a> { pub(super) struct AddMovieHandler<'a> {
key: &'a Key, key: &'a Key,
@@ -119,6 +119,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for AddMovieHandler<'a> {
.radarr_data .radarr_data
.movie_quality_profile_list .movie_quality_profile_list
.scroll_to_top(), .scroll_to_top(),
ActiveRadarrBlock::AddMovieSearchInput => self.app.data.radarr_data.search.scroll_home(),
_ => (), _ => (),
} }
} }
@@ -149,6 +150,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for AddMovieHandler<'a> {
.radarr_data .radarr_data
.movie_quality_profile_list .movie_quality_profile_list
.scroll_to_bottom(), .scroll_to_bottom(),
ActiveRadarrBlock::AddMovieSearchInput => self.app.data.radarr_data.search.reset_offset(),
_ => (), _ => (),
} }
} }
@@ -156,8 +158,12 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for AddMovieHandler<'a> {
fn handle_delete(&mut self) {} fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) { fn handle_left_right_action(&mut self) {
if let ActiveRadarrBlock::AddMoviePrompt = self.active_radarr_block { match self.active_radarr_block {
handle_prompt_toggle(self.app, self.key) ActiveRadarrBlock::AddMoviePrompt => handle_prompt_toggle(self.app, self.key),
ActiveRadarrBlock::AddMovieSearchInput => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search)
}
_ => (),
} }
} }
@@ -298,7 +304,7 @@ mod tests {
ActiveRadarrBlock::AddMovieSearchResults, ActiveRadarrBlock::AddMovieSearchResults,
None, None,
title, title,
stationary_style to_string
); );
test_enum_scroll!( test_enum_scroll!(
@@ -349,10 +355,12 @@ mod tests {
} }
mod test_handle_home_end { mod test_handle_home_end {
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::{ use crate::{
extended_stateful_iterable_vec, test_enum_home_and_end, test_iterable_home_and_end, extended_stateful_iterable_vec, test_enum_home_and_end, test_iterable_home_and_end,
test_text_box_home_end_keys,
}; };
use super::*; use super::*;
@@ -365,7 +373,7 @@ mod tests {
ActiveRadarrBlock::AddMovieSearchResults, ActiveRadarrBlock::AddMovieSearchResults,
None, None,
title, title,
stationary_style to_string
); );
test_enum_home_and_end!( test_enum_home_and_end!(
@@ -393,11 +401,22 @@ mod tests {
ActiveRadarrBlock::AddMovieSelectQualityProfile, ActiveRadarrBlock::AddMovieSelectQualityProfile,
None None
); );
#[test]
fn test_add_movie_search_input_home_end_keys() {
test_text_box_home_end_keys!(
AddMovieHandler,
ActiveRadarrBlock::AddMovieSearchInput,
search
);
}
} }
mod test_handle_left_right_action { mod test_handle_left_right_action {
use rstest::rstest; use rstest::rstest;
use crate::test_text_box_left_right_keys;
use super::*; use super::*;
#[rstest] #[rstest]
@@ -412,6 +431,15 @@ mod tests {
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
#[test]
fn test_add_movie_search_input_left_right_keys() {
test_text_box_left_right_keys!(
AddMovieHandler,
ActiveRadarrBlock::AddMovieSearchInput,
search
);
}
} }
mod test_handle_submit { mod test_handle_submit {
@@ -805,7 +833,7 @@ mod tests {
#[test] #[test]
fn test_add_movie_search_input_backspace() { fn test_add_movie_search_input_backspace() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.search = "Test".to_owned(); app.data.radarr_data.search = "Test".to_owned().into();
AddMovieHandler::with( AddMovieHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, &DEFAULT_KEYBINDINGS.backspace.key,
@@ -815,7 +843,7 @@ mod tests {
) )
.handle(); .handle();
assert_str_eq!(app.data.radarr_data.search, "Tes"); assert_str_eq!(app.data.radarr_data.search.text, "Tes");
} }
#[test] #[test]
@@ -830,7 +858,7 @@ mod tests {
) )
.handle(); .handle();
assert_str_eq!(app.data.radarr_data.search, "h"); assert_str_eq!(app.data.radarr_data.search.text, "h");
} }
} }
} }
@@ -141,7 +141,7 @@ mod tests {
ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::CollectionDetails,
None, None,
title, title,
stationary_style to_string
); );
} }
@@ -158,7 +158,7 @@ mod tests {
ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::CollectionDetails,
None, None,
title, title,
stationary_style to_string
); );
} }
@@ -2,10 +2,10 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::ActiveRadarrBlock; use crate::app::radarr::ActiveRadarrBlock;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_text_box_keys;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
pub(super) struct EditMovieHandler<'a> { pub(super) struct EditMovieHandler<'a> {
key: &'a Key, key: &'a Key,
@@ -100,6 +100,8 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditMovieHandler<'a> {
.radarr_data .radarr_data
.movie_quality_profile_list .movie_quality_profile_list
.scroll_to_top(), .scroll_to_top(),
ActiveRadarrBlock::EditMoviePathInput => self.app.data.radarr_data.edit_path.scroll_home(),
ActiveRadarrBlock::EditMovieTagsInput => self.app.data.radarr_data.edit_tags.scroll_home(),
_ => (), _ => (),
} }
} }
@@ -118,6 +120,8 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditMovieHandler<'a> {
.radarr_data .radarr_data
.movie_quality_profile_list .movie_quality_profile_list
.scroll_to_bottom(), .scroll_to_bottom(),
ActiveRadarrBlock::EditMoviePathInput => self.app.data.radarr_data.edit_path.reset_offset(),
ActiveRadarrBlock::EditMovieTagsInput => self.app.data.radarr_data.edit_tags.reset_offset(),
_ => (), _ => (),
} }
} }
@@ -125,8 +129,15 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditMovieHandler<'a> {
fn handle_delete(&mut self) {} fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) { fn handle_left_right_action(&mut self) {
if let ActiveRadarrBlock::EditMoviePrompt = self.active_radarr_block { match self.active_radarr_block {
handle_prompt_toggle(self.app, self.key) ActiveRadarrBlock::EditMoviePrompt => handle_prompt_toggle(self.app, self.key),
ActiveRadarrBlock::EditMoviePathInput => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.edit_path)
}
ActiveRadarrBlock::EditMovieTagsInput => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.edit_tags)
}
_ => (),
} }
} }
@@ -262,7 +273,7 @@ mod tests {
mod test_handle_home_end { mod test_handle_home_end {
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::{test_enum_home_and_end, test_iterable_home_and_end}; use crate::{test_enum_home_and_end, test_iterable_home_and_end, test_text_box_home_end_keys};
use super::*; use super::*;
@@ -282,11 +293,31 @@ mod tests {
ActiveRadarrBlock::EditMovieSelectQualityProfile, ActiveRadarrBlock::EditMovieSelectQualityProfile,
None None
); );
#[test]
fn test_edit_movie_path_input_home_end_keys() {
test_text_box_home_end_keys!(
EditMovieHandler,
ActiveRadarrBlock::EditMoviePathInput,
edit_path
);
}
#[test]
fn test_edit_movie_tags_input_home_end_keys() {
test_text_box_home_end_keys!(
EditMovieHandler,
ActiveRadarrBlock::EditMovieTagsInput,
edit_tags
);
}
} }
mod test_handle_left_right_action { mod test_handle_left_right_action {
use rstest::rstest; use rstest::rstest;
use crate::{test_text_box_home_end_keys, test_text_box_left_right_keys};
use super::*; use super::*;
#[rstest] #[rstest]
@@ -301,6 +332,24 @@ mod tests {
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
#[test]
fn test_edit_movie_path_input_left_right_keys() {
test_text_box_left_right_keys!(
EditMovieHandler,
ActiveRadarrBlock::EditMoviePathInput,
edit_path
);
}
#[test]
fn test_edit_movie_tags_input_left_right_keys() {
test_text_box_left_right_keys!(
EditMovieHandler,
ActiveRadarrBlock::EditMovieTagsInput,
edit_tags
);
}
} }
mod test_handle_submit { mod test_handle_submit {
@@ -321,7 +370,7 @@ mod tests {
fn test_edit_movie_path_input_submit() { fn test_edit_movie_path_input_submit() {
let mut app = App::default(); let mut app = App::default();
app.should_ignore_quit_key = true; app.should_ignore_quit_key = true;
app.data.radarr_data.edit_path = "Test Path".to_owned(); app.data.radarr_data.edit_path = "Test Path".to_owned().into();
app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into());
app.push_navigation_stack(ActiveRadarrBlock::EditMoviePathInput.into()); app.push_navigation_stack(ActiveRadarrBlock::EditMoviePathInput.into());
@@ -334,7 +383,7 @@ mod tests {
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert!(!app.data.radarr_data.edit_path.is_empty()); assert!(!app.data.radarr_data.edit_path.text.is_empty());
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::EditMoviePrompt.into() &ActiveRadarrBlock::EditMoviePrompt.into()
@@ -345,7 +394,7 @@ mod tests {
fn test_edit_movie_tags_input_submit() { fn test_edit_movie_tags_input_submit() {
let mut app = App::default(); let mut app = App::default();
app.should_ignore_quit_key = true; app.should_ignore_quit_key = true;
app.data.radarr_data.edit_tags = "Test Tags".to_owned(); app.data.radarr_data.edit_tags = "Test Tags".to_owned().into();
app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into());
app.push_navigation_stack(ActiveRadarrBlock::EditMoviePathInput.into()); app.push_navigation_stack(ActiveRadarrBlock::EditMoviePathInput.into());
@@ -358,7 +407,7 @@ mod tests {
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert!(!app.data.radarr_data.edit_tags.is_empty()); assert!(!app.data.radarr_data.edit_tags.text.is_empty());
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::EditMoviePrompt.into() &ActiveRadarrBlock::EditMoviePrompt.into()
@@ -599,7 +648,7 @@ mod tests {
#[test] #[test]
fn test_edit_movie_path_input_backspace() { fn test_edit_movie_path_input_backspace() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.edit_path = "Test".to_owned(); app.data.radarr_data.edit_path = "Test".to_owned().into();
EditMovieHandler::with( EditMovieHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, &DEFAULT_KEYBINDINGS.backspace.key,
@@ -609,13 +658,13 @@ mod tests {
) )
.handle(); .handle();
assert_str_eq!(app.data.radarr_data.edit_path, "Tes"); assert_str_eq!(app.data.radarr_data.edit_path.text, "Tes");
} }
#[test] #[test]
fn test_edit_movie_tags_input_backspace() { fn test_edit_movie_tags_input_backspace() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.edit_tags = "Test".to_owned(); app.data.radarr_data.edit_tags = "Test".to_owned().into();
EditMovieHandler::with( EditMovieHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, &DEFAULT_KEYBINDINGS.backspace.key,
@@ -625,7 +674,7 @@ mod tests {
) )
.handle(); .handle();
assert_str_eq!(app.data.radarr_data.edit_tags, "Tes"); assert_str_eq!(app.data.radarr_data.edit_tags.text, "Tes");
} }
#[test] #[test]
@@ -640,7 +689,7 @@ mod tests {
) )
.handle(); .handle();
assert_str_eq!(app.data.radarr_data.edit_path, "h"); assert_str_eq!(app.data.radarr_data.edit_path.text, "h");
} }
#[test] #[test]
@@ -655,7 +704,7 @@ mod tests {
) )
.handle(); .handle();
assert_str_eq!(app.data.radarr_data.edit_tags, "h"); assert_str_eq!(app.data.radarr_data.edit_tags.text, "h");
} }
} }
} }
+91 -48
View File
@@ -11,7 +11,7 @@ use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::utils::strip_non_alphanumeric_characters; use crate::utils::strip_non_alphanumeric_characters;
use crate::{handle_text_box_keys, App, Key}; use crate::{handle_text_box_keys, handle_text_box_left_right_keys, App, Key};
mod add_movie_handler; mod add_movie_handler;
mod collection_details_handler; mod collection_details_handler;
@@ -149,6 +149,12 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> {
} }
} }
ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_to_top(), ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_to_top(),
ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => {
self.app.data.radarr_data.search.scroll_home()
}
ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => {
self.app.data.radarr_data.filter.scroll_home()
}
_ => (), _ => (),
} }
} }
@@ -182,6 +188,12 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> {
} }
} }
ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_to_bottom(), ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_to_bottom(),
ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => {
self.app.data.radarr_data.search.reset_offset()
}
ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => {
self.app.data.radarr_data.filter.reset_offset()
}
_ => (), _ => (),
} }
} }
@@ -222,6 +234,12 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> {
| ActiveRadarrBlock::RefreshAllMoviesPrompt | ActiveRadarrBlock::RefreshAllMoviesPrompt
| ActiveRadarrBlock::RefreshAllCollectionsPrompt | ActiveRadarrBlock::RefreshAllCollectionsPrompt
| ActiveRadarrBlock::RefreshDownloadsPrompt => handle_prompt_toggle(self.app, self.key), | ActiveRadarrBlock::RefreshDownloadsPrompt => handle_prompt_toggle(self.app, self.key),
ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search)
}
ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => {
handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.filter)
}
_ => (), _ => (),
} }
} }
@@ -476,14 +494,7 @@ impl RadarrHandler<'_> {
where where
F: Fn(&T) -> &str, F: Fn(&T) -> &str,
{ {
let search_string = self let search_string = self.app.data.radarr_data.search.drain().to_lowercase();
.app
.data
.radarr_data
.search
.drain(..)
.collect::<String>()
.to_lowercase();
let search_index = rows.iter().position(|item| { let search_index = rows.iter().position(|item| {
strip_non_alphanumeric_characters(field_selection_fn(item)).contains(&search_string) strip_non_alphanumeric_characters(field_selection_fn(item)).contains(&search_string)
}); });
@@ -503,15 +514,7 @@ impl RadarrHandler<'_> {
F: Fn(&T) -> &str, F: Fn(&T) -> &str,
T: Clone, T: Clone,
{ {
let filter = strip_non_alphanumeric_characters( let filter = strip_non_alphanumeric_characters(&self.app.data.radarr_data.filter.drain());
&self
.app
.data
.radarr_data
.filter
.drain(..)
.collect::<String>(),
);
let filter_matches: Vec<T> = rows let filter_matches: Vec<T> = rows
.iter() .iter()
.filter(|&item| strip_non_alphanumeric_characters(field_selection_fn(item)).contains(&filter)) .filter(|&item| strip_non_alphanumeric_characters(field_selection_fn(item)).contains(&filter))
@@ -537,21 +540,23 @@ mod radarr_handler_test_utils {
($handler:ident, $block:expr, $context:expr) => { ($handler:ident, $block:expr, $context:expr) => {
let mut app = App::default(); let mut app = App::default();
let mut radarr_data = RadarrData { let mut radarr_data = RadarrData {
edit_path: String::default(), edit_path: HorizontallyScrollableText::default(),
edit_tags: String::default(), edit_tags: HorizontallyScrollableText::default(),
edit_monitored: None, edit_monitored: None,
quality_profile_map: HashMap::from([ quality_profile_map: HashMap::from([
(2222, "HD - 1080p".to_owned()), (2222, "HD - 1080p".to_owned()),
(1111, "Any".to_owned()), (1111, "Any".to_owned()),
]), ]),
tags_map: BiMap::from_iter([(1, "test".to_owned())]),
filtered_movies: StatefulTable::default(), filtered_movies: StatefulTable::default(),
..create_test_radarr_data() ..create_test_radarr_data()
}; };
radarr_data.movies.set_items(vec![Movie { radarr_data.movies.set_items(vec![Movie {
path: "/nfs/movies/Test".to_owned(), path: "/nfs/movies/Test".to_owned().into(),
monitored: true, monitored: true,
quality_profile_id: Number::from(2222), quality_profile_id: Number::from(2222),
minimum_availability: MinimumAvailability::Released, minimum_availability: MinimumAvailability::Released,
tags: vec![Number::from(1)],
..Movie::default() ..Movie::default()
}]); }]);
app.data.radarr_data = radarr_data; app.data.radarr_data = radarr_data;
@@ -582,7 +587,7 @@ mod radarr_handler_test_utils {
app.data.radarr_data.movie_quality_profile_list.items, app.data.radarr_data.movie_quality_profile_list.items,
vec!["Any".to_owned(), "HD - 1080p".to_owned()] vec!["Any".to_owned(), "HD - 1080p".to_owned()]
); );
assert_eq!( assert_str_eq!(
app app
.data .data
.radarr_data .radarr_data
@@ -590,10 +595,8 @@ mod radarr_handler_test_utils {
.current_selection(), .current_selection(),
"HD - 1080p" "HD - 1080p"
); );
assert_eq!( assert_str_eq!(app.data.radarr_data.edit_path.text, "/nfs/movies/Test");
app.data.radarr_data.edit_path, assert_str_eq!(app.data.radarr_data.edit_tags.text, "test");
"/nfs/movies/Test".to_owned()
);
assert_eq!(app.data.radarr_data.edit_monitored, Some(true)); assert_eq!(app.data.radarr_data.edit_monitored, Some(true));
}; };
} }
@@ -673,8 +676,12 @@ mod tests {
} }
mod test_handle_home_end { mod test_handle_home_end {
use pretty_assertions::assert_eq;
use crate::models::radarr_models::DownloadRecord; use crate::models::radarr_models::DownloadRecord;
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use crate::{
extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys,
};
use super::*; use super::*;
@@ -727,6 +734,22 @@ mod tests {
None, None,
title title
); );
#[rstest]
fn test_search_boxes_home_end_keys(
#[values(ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchCollection)]
active_radarr_block: ActiveRadarrBlock,
) {
test_text_box_home_end_keys!(RadarrHandler, active_radarr_block, search);
}
#[rstest]
fn test_filter_boxes_home_end_keys(
#[values(ActiveRadarrBlock::FilterMovies, ActiveRadarrBlock::FilterCollections)]
active_radarr_block: ActiveRadarrBlock,
) {
test_text_box_home_end_keys!(RadarrHandler, active_radarr_block, filter);
}
} }
mod test_handle_delete { mod test_handle_delete {
@@ -765,6 +788,8 @@ mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use crate::test_text_box_left_right_keys;
use super::*; use super::*;
#[rstest] #[rstest]
@@ -843,6 +868,22 @@ mod tests {
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
#[rstest]
fn test_search_boxes_left_right_keys(
#[values(ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchCollection)]
active_radarr_block: ActiveRadarrBlock,
) {
test_text_box_left_right_keys!(RadarrHandler, active_radarr_block, search);
}
#[rstest]
fn test_filter_boxes_left_right_keys(
#[values(ActiveRadarrBlock::FilterMovies, ActiveRadarrBlock::FilterCollections)]
active_radarr_block: ActiveRadarrBlock,
) {
test_text_box_left_right_keys!(RadarrHandler, active_radarr_block, filter);
}
} }
mod test_handle_submit { mod test_handle_submit {
@@ -877,7 +918,7 @@ mod tests {
.radarr_data .radarr_data
.movies .movies
.set_items(extended_stateful_iterable_vec!(Movie)); .set_items(extended_stateful_iterable_vec!(Movie));
app.data.radarr_data.search = "Test 2".to_owned(); app.data.radarr_data.search = "Test 2".to_owned().into();
RadarrHandler::with( RadarrHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -901,7 +942,7 @@ mod tests {
.radarr_data .radarr_data
.filtered_movies .filtered_movies
.set_items(extended_stateful_iterable_vec!(Movie)); .set_items(extended_stateful_iterable_vec!(Movie));
app.data.radarr_data.search = "Test 2".to_owned(); app.data.radarr_data.search = "Test 2".to_owned().into();
RadarrHandler::with( RadarrHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -930,7 +971,7 @@ mod tests {
.radarr_data .radarr_data
.collections .collections
.set_items(extended_stateful_iterable_vec!(Collection)); .set_items(extended_stateful_iterable_vec!(Collection));
app.data.radarr_data.search = "Test 2".to_owned(); app.data.radarr_data.search = "Test 2".to_owned().into();
RadarrHandler::with( RadarrHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -954,7 +995,7 @@ mod tests {
.radarr_data .radarr_data
.filtered_collections .filtered_collections
.set_items(extended_stateful_iterable_vec!(Collection)); .set_items(extended_stateful_iterable_vec!(Collection));
app.data.radarr_data.search = "Test 2".to_owned(); app.data.radarr_data.search = "Test 2".to_owned().into();
RadarrHandler::with( RadarrHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -983,7 +1024,7 @@ mod tests {
.radarr_data .radarr_data
.movies .movies
.set_items(extended_stateful_iterable_vec!(Movie)); .set_items(extended_stateful_iterable_vec!(Movie));
app.data.radarr_data.filter = "Test".to_owned(); app.data.radarr_data.filter = "Test".to_owned().into();
RadarrHandler::with( RadarrHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -1013,7 +1054,7 @@ mod tests {
.radarr_data .radarr_data
.collections .collections
.set_items(extended_stateful_iterable_vec!(Collection)); .set_items(extended_stateful_iterable_vec!(Collection));
app.data.radarr_data.filter = "Test".to_owned(); app.data.radarr_data.filter = "Test".to_owned().into();
RadarrHandler::with( RadarrHandler::with(
&SUBMIT_KEY, &SUBMIT_KEY,
@@ -1210,7 +1251,8 @@ mod tests {
mod test_handle_key_char { mod test_handle_key_char {
use std::collections::HashMap; use std::collections::HashMap;
use pretty_assertions::assert_eq; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest; use rstest::rstest;
use serde_json::Number; use serde_json::Number;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -1219,6 +1261,7 @@ mod tests {
use crate::app::radarr::radarr_test_utils::create_test_radarr_data; use crate::app::radarr::radarr_test_utils::create_test_radarr_data;
use crate::app::radarr::RadarrData; use crate::app::radarr::RadarrData;
use crate::models::radarr_models::MinimumAvailability; use crate::models::radarr_models::MinimumAvailability;
use crate::models::HorizontallyScrollableText;
use crate::models::StatefulTable; use crate::models::StatefulTable;
use super::*; use super::*;
@@ -1328,7 +1371,7 @@ mod tests {
active_radarr_block: ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
) { ) {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.search = "Test".to_owned(); app.data.radarr_data.search = "Test".to_owned().into();
RadarrHandler::with( RadarrHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, &DEFAULT_KEYBINDINGS.backspace.key,
@@ -1338,7 +1381,7 @@ mod tests {
) )
.handle(); .handle();
assert_str_eq!(app.data.radarr_data.search, "Tes"); assert_str_eq!(app.data.radarr_data.search.text, "Tes");
} }
#[rstest] #[rstest]
@@ -1347,7 +1390,7 @@ mod tests {
active_radarr_block: ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
) { ) {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.filter = "Test".to_owned(); app.data.radarr_data.filter = "Test".to_owned().into();
RadarrHandler::with( RadarrHandler::with(
&DEFAULT_KEYBINDINGS.backspace.key, &DEFAULT_KEYBINDINGS.backspace.key,
@@ -1357,7 +1400,7 @@ mod tests {
) )
.handle(); .handle();
assert_str_eq!(app.data.radarr_data.filter, "Tes"); assert_str_eq!(app.data.radarr_data.filter.text, "Tes");
} }
#[rstest] #[rstest]
@@ -1369,7 +1412,7 @@ mod tests {
RadarrHandler::with(&Key::Char('h'), &mut app, &active_radarr_block, &None).handle(); RadarrHandler::with(&Key::Char('h'), &mut app, &active_radarr_block, &None).handle();
assert_str_eq!(app.data.radarr_data.search, "h"); assert_str_eq!(app.data.radarr_data.search.text, "h");
} }
#[rstest] #[rstest]
@@ -1381,7 +1424,7 @@ mod tests {
RadarrHandler::with(&Key::Char('h'), &mut app, &active_radarr_block, &None).handle(); RadarrHandler::with(&Key::Char('h'), &mut app, &active_radarr_block, &None).handle();
assert_str_eq!(app.data.radarr_data.filter, "h"); assert_str_eq!(app.data.radarr_data.filter.text, "h");
} }
} }
@@ -1393,7 +1436,7 @@ mod tests {
.radarr_data .radarr_data
.movies .movies
.set_items(extended_stateful_iterable_vec!(Movie)); .set_items(extended_stateful_iterable_vec!(Movie));
app.data.radarr_data.search = "Test 2".to_owned(); app.data.radarr_data.search = "Test 2".to_owned().into();
app.data.radarr_data.is_searching = true; app.data.radarr_data.is_searching = true;
app.should_ignore_quit_key = true; app.should_ignore_quit_key = true;
app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into());
@@ -1412,7 +1455,7 @@ mod tests {
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
assert!(!app.data.radarr_data.is_searching); assert!(!app.data.radarr_data.is_searching);
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert!(app.data.radarr_data.search.is_empty()); assert!(app.data.radarr_data.search.text.is_empty());
} }
#[test] #[test]
@@ -1423,7 +1466,7 @@ mod tests {
.radarr_data .radarr_data
.movies .movies
.set_items(extended_stateful_iterable_vec!(Movie)); .set_items(extended_stateful_iterable_vec!(Movie));
app.data.radarr_data.search = "Test 5".to_owned(); app.data.radarr_data.search = "Test 5".to_owned().into();
app.data.radarr_data.is_searching = true; app.data.radarr_data.is_searching = true;
app.should_ignore_quit_key = true; app.should_ignore_quit_key = true;
app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into());
@@ -1445,7 +1488,7 @@ mod tests {
); );
assert!(!app.data.radarr_data.is_searching); assert!(!app.data.radarr_data.is_searching);
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert!(app.data.radarr_data.search.is_empty()); assert!(app.data.radarr_data.search.text.is_empty());
} }
#[test] #[test]
@@ -1456,7 +1499,7 @@ mod tests {
.radarr_data .radarr_data
.movies .movies
.set_items(extended_stateful_iterable_vec!(Movie)); .set_items(extended_stateful_iterable_vec!(Movie));
app.data.radarr_data.filter = "Test 2".to_owned(); app.data.radarr_data.filter = "Test 2".to_owned().into();
app.data.radarr_data.is_searching = true; app.data.radarr_data.is_searching = true;
app.should_ignore_quit_key = true; app.should_ignore_quit_key = true;
app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into());
@@ -1476,7 +1519,7 @@ mod tests {
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
assert!(!app.data.radarr_data.is_filtering); assert!(!app.data.radarr_data.is_filtering);
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert!(app.data.radarr_data.filter.is_empty()); assert!(app.data.radarr_data.filter.text.is_empty());
} }
#[test] #[test]
@@ -1487,7 +1530,7 @@ mod tests {
.radarr_data .radarr_data
.movies .movies
.set_items(extended_stateful_iterable_vec!(Movie)); .set_items(extended_stateful_iterable_vec!(Movie));
app.data.radarr_data.filter = "Test 5".to_owned(); app.data.radarr_data.filter = "Test 5".to_owned().into();
app.data.radarr_data.is_filtering = true; app.data.radarr_data.is_filtering = true;
app.should_ignore_quit_key = true; app.should_ignore_quit_key = true;
app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into());
@@ -1509,7 +1552,7 @@ mod tests {
); );
assert!(!app.data.radarr_data.is_searching); assert!(!app.data.radarr_data.is_searching);
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert!(app.data.radarr_data.filter.is_empty()); assert!(app.data.radarr_data.filter.text.is_empty());
} }
#[rstest] #[rstest]
@@ -271,12 +271,7 @@ fn sort_releases_by_selected_field(
ReleaseField::Source => |release_a, release_b| release_a.protocol.cmp(&release_b.protocol), ReleaseField::Source => |release_a, release_b| release_a.protocol.cmp(&release_b.protocol),
ReleaseField::Age => |release_a, release_b| release_a.age.as_u64().cmp(&release_b.age.as_u64()), ReleaseField::Age => |release_a, release_b| release_a.age.as_u64().cmp(&release_b.age.as_u64()),
ReleaseField::Rejected => |release_a, release_b| release_a.rejected.cmp(&release_b.rejected), ReleaseField::Rejected => |release_a, release_b| release_a.rejected.cmp(&release_b.rejected),
ReleaseField::Title => |release_a, release_b| { ReleaseField::Title => |release_a, release_b| release_a.title.text.cmp(&release_b.title.text),
release_a
.title
.stationary_style()
.cmp(&release_b.title.stationary_style())
},
ReleaseField::Indexer => |release_a, release_b| release_a.indexer.cmp(&release_b.indexer), ReleaseField::Indexer => |release_a, release_b| release_a.indexer.cmp(&release_b.indexer),
ReleaseField::Size => |release_a, release_b| { ReleaseField::Size => |release_a, release_b| {
release_a release_a
@@ -394,7 +389,7 @@ mod tests {
ActiveRadarrBlock::MovieHistory, ActiveRadarrBlock::MovieHistory,
None, None,
source_title, source_title,
stationary_style to_string
); );
test_iterable_scroll!( test_iterable_scroll!(
@@ -427,7 +422,7 @@ mod tests {
ActiveRadarrBlock::ManualSearch, ActiveRadarrBlock::ManualSearch,
None, None,
title, title,
stationary_style to_string
); );
test_enum_scroll!( test_enum_scroll!(
@@ -484,7 +479,7 @@ mod tests {
ActiveRadarrBlock::MovieHistory, ActiveRadarrBlock::MovieHistory,
None, None,
source_title, source_title,
stationary_style to_string
); );
test_iterable_home_and_end!( test_iterable_home_and_end!(
@@ -517,7 +512,7 @@ mod tests {
ActiveRadarrBlock::ManualSearch, ActiveRadarrBlock::ManualSearch,
None, None,
title, title,
stationary_style to_string
); );
test_enum_home_and_end!( test_enum_home_and_end!(
@@ -717,11 +712,13 @@ mod tests {
} }
mod test_handle_esc { mod test_handle_esc {
use pretty_assertions::assert_eq; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest; use rstest::rstest;
use crate::app::radarr::radarr_test_utils::create_test_radarr_data; use crate::app::radarr::radarr_test_utils::create_test_radarr_data;
use crate::assert_movie_info_tabs_reset; use crate::assert_movie_info_tabs_reset;
use crate::models::HorizontallyScrollableText;
use super::*; use super::*;
@@ -775,7 +772,8 @@ mod tests {
mod test_handle_key_char { mod test_handle_key_char {
use std::collections::HashMap; use std::collections::HashMap;
use pretty_assertions::assert_eq; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest; use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -783,6 +781,7 @@ mod tests {
use crate::app::radarr::RadarrData; use crate::app::radarr::RadarrData;
use crate::handlers::radarr_handlers::RadarrHandler; use crate::handlers::radarr_handlers::RadarrHandler;
use crate::models::radarr_models::{MinimumAvailability, Movie}; use crate::models::radarr_models::{MinimumAvailability, Movie};
use crate::models::HorizontallyScrollableText;
use crate::models::StatefulTable; use crate::models::StatefulTable;
use crate::test_edit_movie_key; use crate::test_edit_movie_key;
+142 -56
View File
@@ -101,10 +101,6 @@ macro_rules! stateful_iterable {
pub fn current_selection(&self) -> &T { pub fn current_selection(&self) -> &T {
&self.items[self.state.selected().unwrap_or(0)] &self.items[self.state.selected().unwrap_or(0)]
} }
pub fn current_selection_clone(&self) -> T {
self.items[self.state.selected().unwrap_or(0)].clone()
}
} }
}; };
} }
@@ -169,8 +165,8 @@ pub struct HorizontallyScrollableText {
} }
impl From<String> for HorizontallyScrollableText { impl From<String> for HorizontallyScrollableText {
fn from(input: String) -> HorizontallyScrollableText { fn from(text: String) -> HorizontallyScrollableText {
HorizontallyScrollableText::new(input) HorizontallyScrollableText::new(text)
} }
} }
@@ -193,32 +189,60 @@ impl Display for HorizontallyScrollableText {
} }
impl HorizontallyScrollableText { impl HorizontallyScrollableText {
pub fn new(input: String) -> HorizontallyScrollableText { pub fn new(text: String) -> HorizontallyScrollableText {
HorizontallyScrollableText { HorizontallyScrollableText {
text: format!("{} ", input), text,
offset: RefCell::new(0), offset: RefCell::new(0),
} }
} }
pub fn scroll_text(&self) { pub fn scroll_left(&self) {
let new_offset = *self.offset.borrow() + 1; if *self.offset.borrow() < self.text.len() {
*self.offset.borrow_mut() = new_offset % self.text.len(); let new_offset = *self.offset.borrow() + 1;
*self.offset.borrow_mut() = new_offset;
}
}
pub fn scroll_right(&self) {
if *self.offset.borrow() > 0 {
let new_offset = *self.offset.borrow() - 1;
*self.offset.borrow_mut() = new_offset;
}
}
pub fn scroll_home(&self) {
*self.offset.borrow_mut() = self.text.len();
} }
pub fn reset_offset(&self) { pub fn reset_offset(&self) {
*self.offset.borrow_mut() = 0; *self.offset.borrow_mut() = 0;
} }
pub fn scroll_or_reset(&self, width: usize, is_current_selection: bool) { pub fn scroll_left_or_reset(&self, width: usize, is_current_selection: bool) {
if is_current_selection && self.text.len().saturating_sub(4) > width { if is_current_selection && self.text.len() >= width && *self.offset.borrow() < self.text.len() {
self.scroll_text(); self.scroll_left();
} else { } else {
self.reset_offset(); self.reset_offset();
} }
} }
pub fn stationary_style(&self) -> String { pub fn drain(&mut self) -> String {
self.text.clone().trim().to_owned() self.reset_offset();
self.text.drain(..).collect()
}
pub fn pop(&mut self) {
if *self.offset.borrow() < self.text.len() {
self
.text
.remove(self.text.len() - *self.offset.borrow() - 1);
}
}
pub fn push(&mut self, character: char) {
self
.text
.insert(self.text.len() - *self.offset.borrow(), character);
} }
} }
@@ -345,23 +369,6 @@ mod tests {
assert_str_eq!(stateful_table.current_selection(), &stateful_table.items[1]); assert_str_eq!(stateful_table.current_selection(), &stateful_table.items[1]);
} }
#[test]
fn test_stateful_table_current_selection_clone() {
let mut stateful_table = create_test_stateful_table();
assert_str_eq!(
stateful_table.current_selection_clone(),
stateful_table.items[0]
);
stateful_table.state.select(Some(1));
assert_str_eq!(
stateful_table.current_selection_clone(),
stateful_table.items[1]
);
}
#[test] #[test]
fn test_stateful_table_select_index() { fn test_stateful_table_select_index() {
let mut stateful_table = create_test_stateful_table(); let mut stateful_table = create_test_stateful_table();
@@ -444,10 +451,7 @@ mod tests {
let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned());
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
assert_str_eq!( assert_str_eq!(horizontally_scrollable_text.text, test_text);
horizontally_scrollable_text.text,
format!("{} ", test_text)
);
} }
#[test] #[test]
@@ -455,10 +459,7 @@ mod tests {
let test_text = "Test string"; let test_text = "Test string";
let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned());
assert_str_eq!( assert_str_eq!(horizontally_scrollable_text.to_string(), test_text);
horizontally_scrollable_text.to_string(),
format!("{} ", test_text)
);
let horizontally_scrollable_text = HorizontallyScrollableText { let horizontally_scrollable_text = HorizontallyScrollableText {
text: test_text.to_owned(), text: test_text.to_owned(),
@@ -481,29 +482,60 @@ mod tests {
let horizontally_scrollable_text = HorizontallyScrollableText::new(test_text.to_owned()); let horizontally_scrollable_text = HorizontallyScrollableText::new(test_text.to_owned());
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
assert_str_eq!( assert_str_eq!(horizontally_scrollable_text.text, test_text);
horizontally_scrollable_text.text,
format!("{} ", test_text)
);
} }
#[test] #[test]
fn test_horizontally_scrollable_text_scroll_text() { fn test_horizontally_scrollable_text_scroll_text_left() {
let horizontally_scrollable_text = HorizontallyScrollableText::from("Test string".to_owned()); let horizontally_scrollable_text = HorizontallyScrollableText::from("Test string".to_owned());
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
for i in 1..horizontally_scrollable_text.text.len() { for i in 1..horizontally_scrollable_text.text.len() - 1 {
horizontally_scrollable_text.scroll_text(); horizontally_scrollable_text.scroll_left();
assert_eq!(*horizontally_scrollable_text.offset.borrow(), i); assert_eq!(*horizontally_scrollable_text.offset.borrow(), i);
} }
horizontally_scrollable_text.scroll_text(); horizontally_scrollable_text.scroll_left();
assert_eq!(
*horizontally_scrollable_text.offset.borrow(),
horizontally_scrollable_text.text.len() - 1
);
}
#[test]
fn test_horizontally_scrollable_text_scroll_text_right() {
let horizontally_scrollable_text = HorizontallyScrollableText::from("Test string".to_owned());
*horizontally_scrollable_text.offset.borrow_mut() = horizontally_scrollable_text.text.len();
for i in 1..horizontally_scrollable_text.text.len() {
horizontally_scrollable_text.scroll_right();
assert_eq!(
*horizontally_scrollable_text.offset.borrow(),
horizontally_scrollable_text.text.len() - i
);
}
horizontally_scrollable_text.scroll_right();
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
} }
#[test]
fn test_horizontally_scrollable_text_scroll_home() {
let horizontally_scrollable_text = HorizontallyScrollableText::from("Test string".to_owned());
horizontally_scrollable_text.scroll_home();
assert_eq!(
*horizontally_scrollable_text.offset.borrow(),
horizontally_scrollable_text.text.len()
);
}
#[test] #[test]
fn test_horizontally_scrollable_text_reset_offset() { fn test_horizontally_scrollable_text_reset_offset() {
let horizontally_scrollable_text = HorizontallyScrollableText { let horizontally_scrollable_text = HorizontallyScrollableText {
@@ -522,29 +554,83 @@ mod tests {
let test_text = "Test string"; let test_text = "Test string";
let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned());
horizontally_scrollable_text.scroll_or_reset(width, true); horizontally_scrollable_text.scroll_left_or_reset(width, true);
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1); assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1);
horizontally_scrollable_text.scroll_or_reset(width, false); horizontally_scrollable_text.scroll_left_or_reset(width, false);
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
horizontally_scrollable_text.scroll_or_reset(width, true); horizontally_scrollable_text.scroll_left_or_reset(width, true);
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1); assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1);
horizontally_scrollable_text.scroll_or_reset(test_text.len(), false); horizontally_scrollable_text.scroll_left_or_reset(test_text.len(), false);
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
} }
#[test] #[test]
fn test_horizontally_scrollable_text_stationary_style() { fn test_horizontally_scrollable_text_drain() {
let test_text = "Test string"; let test_text = "Test string";
let horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned()); let mut horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned());
assert_eq!(horizontally_scrollable_text.stationary_style(), test_text); assert_str_eq!(horizontally_scrollable_text.drain(), test_text);
assert!(horizontally_scrollable_text.text.is_empty());
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
}
#[test]
fn test_horizontally_scrollable_text_pop() {
let test_text = "Test string";
let mut horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned());
horizontally_scrollable_text.pop();
assert_str_eq!(horizontally_scrollable_text.text, "Test strin");
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
horizontally_scrollable_text.scroll_left();
horizontally_scrollable_text.pop();
assert_str_eq!(horizontally_scrollable_text.text, "Test strn");
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1);
horizontally_scrollable_text.scroll_right();
horizontally_scrollable_text.scroll_right();
horizontally_scrollable_text.pop();
assert_str_eq!(horizontally_scrollable_text.text, "Test str");
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
horizontally_scrollable_text.scroll_home();
horizontally_scrollable_text.pop();
assert_str_eq!(horizontally_scrollable_text.text, "Test str");
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 8);
}
#[test]
fn test_horizontally_scrollable_text_push() {
let test_text = "Test string";
let mut horizontally_scrollable_text = HorizontallyScrollableText::from(test_text.to_owned());
horizontally_scrollable_text.push('h');
assert_str_eq!(horizontally_scrollable_text.text, "Test stringh");
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
horizontally_scrollable_text.scroll_left();
horizontally_scrollable_text.push('l');
assert_str_eq!(horizontally_scrollable_text.text, "Test stringlh");
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 1);
horizontally_scrollable_text.scroll_right();
horizontally_scrollable_text.scroll_right();
horizontally_scrollable_text.push('0');
assert_str_eq!(horizontally_scrollable_text.text, "Test stringlh0");
assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0);
} }
#[test] #[test]
+10 -1
View File
@@ -57,6 +57,7 @@ pub struct Movie {
pub quality_profile_id: Number, pub quality_profile_id: Number,
pub minimum_availability: MinimumAvailability, pub minimum_availability: MinimumAvailability,
pub certification: Option<String>, pub certification: Option<String>,
pub tags: Vec<Number>,
pub ratings: RatingsList, pub ratings: RatingsList,
pub movie_file: Option<MovieFile>, pub movie_file: Option<MovieFile>,
pub collection: Option<Collection>, pub collection: Option<Collection>,
@@ -169,13 +170,20 @@ pub struct DownloadRecord {
#[derive(Derivative, Deserialize, Debug)] #[derive(Derivative, Deserialize, Debug)]
#[derivative(Default)] #[derivative(Default)]
#[serde(rename_all = "camelCase")]
pub struct QualityProfile { pub struct QualityProfile {
#[derivative(Default(value = "Number::from(0)"))] #[derivative(Default(value = "Number::from(0)"))]
pub id: Number, pub id: Number,
pub name: String, pub name: String,
} }
#[derive(Derivative, Deserialize, Debug)]
#[derivative(Default)]
pub struct Tag {
#[derivative(Default(value = "Number::from(0)"))]
pub id: Number,
pub label: String,
}
#[derive(Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[derive(Deserialize, Default, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MovieHistoryItem { pub struct MovieHistoryItem {
@@ -265,6 +273,7 @@ pub struct AddMovieBody {
pub quality_profile_id: u64, pub quality_profile_id: u64,
pub minimum_availability: String, pub minimum_availability: String,
pub monitored: bool, pub monitored: bool,
pub tags: Vec<u64>,
pub add_options: AddOptions, pub add_options: AddOptions,
} }
+29 -25
View File
@@ -22,7 +22,6 @@ pub enum NetworkEvent {
pub struct Network<'a> { pub struct Network<'a> {
pub client: Client, pub client: Client,
pub app: &'a Arc<Mutex<App>>, pub app: &'a Arc<Mutex<App>>,
} }
@@ -40,12 +39,12 @@ impl<'a> Network<'a> {
app.is_loading = false; app.is_loading = false;
} }
pub async fn handle_request<T, R>( pub async fn handle_request<B, R>(
&self, &self,
request_props: RequestProps<T>, request_props: RequestProps<B>,
mut app_update_fn: impl FnMut(R, MutexGuard<'_, App>), mut app_update_fn: impl FnMut(R, MutexGuard<'_, App>),
) where ) where
T: Serialize + Default + Debug, B: Serialize + Default + Debug,
R: DeserializeOwned, R: DeserializeOwned,
{ {
let method = request_props.method; let method = request_props.method;
@@ -53,21 +52,23 @@ impl<'a> Network<'a> {
Ok(response) => { Ok(response) => {
if response.status().is_success() { if response.status().is_success() {
match method { match method {
RequestMethod::Get => match utils::parse_response::<R>(response).await { RequestMethod::Get | RequestMethod::Post => {
Ok(value) => { match utils::parse_response::<R>(response).await {
let app = self.app.lock().await; Ok(value) => {
app_update_fn(value, app); let app = self.app.lock().await;
app_update_fn(value, app);
}
Err(e) => {
error!("Failed to parse response! {:?}", e);
self
.app
.lock()
.await
.handle_error(anyhow!("Failed to parse response! {:?}", e));
}
} }
Err(e) => { }
error!("Failed to parse response! {:?}", e); RequestMethod::Delete | RequestMethod::Put => (),
self
.app
.lock()
.await
.handle_error(anyhow!("Failed to parse response! {:?}", e));
}
},
RequestMethod::Delete | RequestMethod::Post | RequestMethod::Put => (),
} }
} else { } else {
error!( error!(
@@ -224,16 +225,19 @@ mod tests {
async_server.assert_async().await; async_server.assert_async().await;
} }
#[rstest]
#[tokio::test] #[tokio::test]
async fn test_handle_request_get() { async fn test_handle_request_with_response_body(
let (async_server, app_arc, server) = mock_api(RequestMethod::Get, 200, true).await; #[values(RequestMethod::Get, RequestMethod::Post)] request_method: RequestMethod,
) {
let (async_server, app_arc, server) = mock_api(request_method, 200, true).await;
let network = Network::new(reqwest::Client::new(), &app_arc); let network = Network::new(reqwest::Client::new(), &app_arc);
network network
.handle_request::<(), Test>( .handle_request::<(), Test>(
RequestProps { RequestProps {
uri: format!("{}/test", server.url()), uri: format!("{}/test", server.url()),
method: RequestMethod::Get, method: request_method,
body: None, body: None,
api_token: "test1234".to_owned(), api_token: "test1234".to_owned(),
}, },
@@ -242,7 +246,7 @@ mod tests {
.await; .await;
async_server.assert_async().await; async_server.assert_async().await;
assert_str_eq!(app_arc.lock().await.error.stationary_style(), "Test"); assert_str_eq!(app_arc.lock().await.error.text, "Test");
} }
#[tokio::test] #[tokio::test]
@@ -275,7 +279,7 @@ mod tests {
.lock() .lock()
.await .await
.error .error
.stationary_style() .text
.starts_with("Failed to parse response!")); .starts_with("Failed to parse response!"));
} }
@@ -300,7 +304,7 @@ mod tests {
.lock() .lock()
.await .await
.error .error
.stationary_style() .text
.starts_with("Failed to send request.")); .starts_with("Failed to send request."));
} }
@@ -332,7 +336,7 @@ mod tests {
async_server.assert_async().await; async_server.assert_async().await;
assert_str_eq!( assert_str_eq!(
app_arc.lock().await.error.stationary_style(), app_arc.lock().await.error.text,
"Request failed. Received 404 Not Found response code" "Request failed. Received 404 Not Found response code"
); );
} }
+277 -85
View File
@@ -11,7 +11,7 @@ use crate::app::RadarrConfig;
use crate::models::radarr_models::{ use crate::models::radarr_models::{
AddMovieBody, AddMovieSearchResult, AddOptions, Collection, CollectionMovie, CommandBody, Credit, AddMovieBody, AddMovieSearchResult, AddOptions, Collection, CollectionMovie, CommandBody, Credit,
CreditType, DiskSpace, DownloadRecord, DownloadsResponse, Movie, MovieCommandBody, CreditType, DiskSpace, DownloadRecord, DownloadsResponse, Movie, MovieCommandBody,
MovieHistoryItem, QualityProfile, Release, ReleaseDownloadBody, RootFolder, SystemStatus, MovieHistoryItem, QualityProfile, Release, ReleaseDownloadBody, RootFolder, SystemStatus, Tag,
}; };
use crate::models::{Route, ScrollableText}; use crate::models::{Route, ScrollableText};
use crate::network::{Network, NetworkEvent, RequestMethod, RequestProps}; use crate::network::{Network, NetworkEvent, RequestMethod, RequestProps};
@@ -35,6 +35,7 @@ pub enum RadarrEvent {
GetReleases, GetReleases,
GetRootFolders, GetRootFolders,
GetStatus, GetStatus,
GetTags,
HealthCheck, HealthCheck,
RefreshAndScan, RefreshAndScan,
RefreshCollections, RefreshCollections,
@@ -62,6 +63,7 @@ impl RadarrEvent {
RadarrEvent::GetReleases | RadarrEvent::DownloadRelease => "/release", RadarrEvent::GetReleases | RadarrEvent::DownloadRelease => "/release",
RadarrEvent::GetRootFolders => "/rootfolder", RadarrEvent::GetRootFolders => "/rootfolder",
RadarrEvent::GetStatus => "/system/status", RadarrEvent::GetStatus => "/system/status",
RadarrEvent::GetTags => "/tag",
RadarrEvent::TriggerAutomaticSearch RadarrEvent::TriggerAutomaticSearch
| RadarrEvent::RefreshAndScan | RadarrEvent::RefreshAndScan
| RadarrEvent::UpdateAllMovies | RadarrEvent::UpdateAllMovies
@@ -97,6 +99,7 @@ impl<'a> Network<'a> {
RadarrEvent::GetReleases => self.get_releases().await, RadarrEvent::GetReleases => self.get_releases().await,
RadarrEvent::GetRootFolders => self.get_root_folders().await, RadarrEvent::GetRootFolders => self.get_root_folders().await,
RadarrEvent::GetStatus => self.get_status().await, RadarrEvent::GetStatus => self.get_status().await,
RadarrEvent::GetTags => self.get_tags().await,
RadarrEvent::HealthCheck => self.get_healthcheck().await, RadarrEvent::HealthCheck => self.get_healthcheck().await,
RadarrEvent::RefreshAndScan => self.refresh_and_scan().await, RadarrEvent::RefreshAndScan => self.refresh_and_scan().await,
RadarrEvent::RefreshCollections => self.refresh_collections().await, RadarrEvent::RefreshCollections => self.refresh_collections().await,
@@ -205,13 +208,13 @@ impl<'a> Network<'a> {
async fn search_movie(&self) { async fn search_movie(&self) {
info!("Searching for specific Radarr movie"); info!("Searching for specific Radarr movie");
let search_string = self.app.lock().await.data.radarr_data.search.clone(); let search_string = &self.app.lock().await.data.radarr_data.search.text;
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
format!( format!(
"{}?term={}", "{}?term={}",
RadarrEvent::SearchNewMovie.resource(), RadarrEvent::SearchNewMovie.resource(),
encode(&search_string) encode(search_string)
) )
.as_str(), .as_str(),
RequestMethod::Get, RequestMethod::Get,
@@ -567,13 +570,56 @@ impl<'a> Network<'a> {
self self
.handle_request::<(), Vec<QualityProfile>>(request_props, |quality_profiles, mut app| { .handle_request::<(), Vec<QualityProfile>>(request_props, |quality_profiles, mut app| {
app.data.radarr_data.quality_profile_map = quality_profiles app.data.radarr_data.quality_profile_map = quality_profiles
.iter() .into_iter()
.map(|profile| (profile.id.as_u64().unwrap(), profile.name.clone())) .map(|profile| (profile.id.as_u64().unwrap(), profile.name))
.collect(); .collect();
}) })
.await; .await;
} }
async fn get_tags(&self) {
info!("Fetching Radarr tags");
let request_props = self
.radarr_request_props_from(
RadarrEvent::GetTags.resource(),
RequestMethod::Get,
None::<()>,
)
.await;
self
.handle_request::<(), Vec<Tag>>(request_props, |tags_vec, mut app| {
app.data.radarr_data.tags_map = tags_vec
.into_iter()
.map(|tag| (tag.id.as_u64().unwrap(), tag.label))
.collect();
})
.await;
}
async fn add_tag(&self, tag: String) {
info!("Adding a new Radarr tag");
let request_props = self
.radarr_request_props_from(
RadarrEvent::GetTags.resource(),
RequestMethod::Post,
Some(json!({ "label": tag })),
)
.await;
self
.handle_request::<Value, Tag>(request_props, |tag, mut app| {
app
.data
.radarr_data
.tags_map
.insert(tag.id.as_u64().unwrap(), tag.label);
})
.await;
}
async fn get_root_folders(&self) { async fn get_root_folders(&self) {
info!("Fetching Radarr root folders"); info!("Fetching Radarr root folders");
@@ -677,34 +723,26 @@ impl<'a> Network<'a> {
async fn add_movie(&self) { async fn add_movie(&self) {
info!("Adding new movie to Radarr"); info!("Adding new movie to Radarr");
let body = { let body = {
let quality_profile_id = self.extract_quality_profile_id().await;
let tag_ids_vec = self.extract_and_add_tag_ids_vec().await;
let app = self.app.lock().await; let app = self.app.lock().await;
let root_folders = app.data.radarr_data.root_folders.to_vec(); let root_folders = app.data.radarr_data.root_folders.to_vec();
let (tmdb_id, title) = if let Route::Radarr(active_radarr_block, _) = app.get_current_route() let (tmdb_id, title) = if let Route::Radarr(active_radarr_block, _) = app.get_current_route()
{ {
if *active_radarr_block == ActiveRadarrBlock::CollectionDetails { if *active_radarr_block == ActiveRadarrBlock::CollectionDetails {
let CollectionMovie { tmdb_id, title, .. } = app let CollectionMovie { tmdb_id, title, .. } =
.data app.data.radarr_data.collection_movies.current_selection();
.radarr_data (tmdb_id, title.text.clone())
.collection_movies
.current_selection_clone();
(tmdb_id, title.stationary_style())
} else { } else {
let AddMovieSearchResult { tmdb_id, title, .. } = app let AddMovieSearchResult { tmdb_id, title, .. } =
.data app.data.radarr_data.add_searched_movies.current_selection();
.radarr_data (tmdb_id, title.text.clone())
.add_searched_movies
.current_selection_clone();
(tmdb_id, title.stationary_style())
} }
} else { } else {
let AddMovieSearchResult { tmdb_id, title, .. } = app let AddMovieSearchResult { tmdb_id, title, .. } =
.data app.data.radarr_data.add_searched_movies.current_selection();
.radarr_data (tmdb_id, title.text.clone())
.add_searched_movies
.current_selection_clone();
(tmdb_id, title.stationary_style())
}; };
let quality_profile_map = app.data.radarr_data.quality_profile_map.clone();
let RootFolder { path, .. } = root_folders let RootFolder { path, .. } = root_folders
.iter() .iter()
@@ -729,17 +767,6 @@ impl<'a> Network<'a> {
.movie_minimum_availability_list .movie_minimum_availability_list
.current_selection() .current_selection()
.to_string(); .to_string();
let quality_profile = app
.data
.radarr_data
.movie_quality_profile_list
.current_selection_clone();
let quality_profile_id = quality_profile_map
.iter()
.filter(|(_, value)| **value == quality_profile)
.map(|(key, _)| key)
.next()
.unwrap();
AddMovieBody { AddMovieBody {
tmdb_id: tmdb_id.as_u64().unwrap(), tmdb_id: tmdb_id.as_u64().unwrap(),
@@ -747,7 +774,8 @@ impl<'a> Network<'a> {
root_folder_path: path.to_owned(), root_folder_path: path.to_owned(),
minimum_availability, minimum_availability,
monitored: true, monitored: true,
quality_profile_id: *quality_profile_id, quality_profile_id,
tags: tag_ids_vec,
add_options: AddOptions { add_options: AddOptions {
monitor, monitor,
search_for_movie: true, search_for_movie: true,
@@ -785,22 +813,20 @@ impl<'a> Network<'a> {
self self
.handle_request::<(), Value>(request_props, |detailed_movie_body, mut app| { .handle_request::<(), Value>(request_props, |detailed_movie_body, mut app| {
app.data.radarr_data.movie_details = app.response = detailed_movie_body.to_string()
ScrollableText::with_string(detailed_movie_body.to_string())
}) })
.await; .await;
info!("Constructing edit movie body"); info!("Constructing edit movie body");
let body = { let body = {
let quality_profile_id = self.extract_quality_profile_id().await;
let tag_ids_vec = self.extract_and_add_tag_ids_vec().await;
let mut app = self.app.lock().await; let mut app = self.app.lock().await;
let mut detailed_movie_body: Value = let mut detailed_movie_body: Value = serde_json::from_str(&app.response).unwrap();
serde_json::from_str(&app.data.radarr_data.movie_details.get_text()).unwrap(); app.response = String::default();
app.data.radarr_data.movie_details = ScrollableText::default();
let quality_profile_map = app.data.radarr_data.quality_profile_map.clone(); let path: String = app.data.radarr_data.edit_path.drain();
let path: String = app.data.radarr_data.edit_path.drain(..).collect();
let _tags: String = app.data.radarr_data.edit_tags.drain(..).collect();
let monitored = app.data.radarr_data.edit_monitored.unwrap_or_default(); let monitored = app.data.radarr_data.edit_monitored.unwrap_or_default();
let minimum_availability = app let minimum_availability = app
@@ -809,22 +835,12 @@ impl<'a> Network<'a> {
.movie_minimum_availability_list .movie_minimum_availability_list
.current_selection() .current_selection()
.to_string(); .to_string();
let quality_profile = app
.data
.radarr_data
.movie_quality_profile_list
.current_selection_clone();
let quality_profile_id = quality_profile_map
.iter()
.filter(|(_, value)| **value == quality_profile)
.map(|(key, _)| key)
.next()
.unwrap();
*detailed_movie_body.get_mut("monitored").unwrap() = json!(monitored); *detailed_movie_body.get_mut("monitored").unwrap() = json!(monitored);
*detailed_movie_body.get_mut("minimumAvailability").unwrap() = json!(minimum_availability); *detailed_movie_body.get_mut("minimumAvailability").unwrap() = json!(minimum_availability);
*detailed_movie_body.get_mut("qualityProfileId").unwrap() = json!(quality_profile_id); *detailed_movie_body.get_mut("qualityProfileId").unwrap() = json!(quality_profile_id);
*detailed_movie_body.get_mut("path").unwrap() = json!(path); *detailed_movie_body.get_mut("path").unwrap() = json!(path);
*detailed_movie_body.get_mut("tags").unwrap() = json!(tag_ids_vec);
detailed_movie_body detailed_movie_body
}; };
@@ -845,25 +861,21 @@ impl<'a> Network<'a> {
} }
async fn download_release(&self) { async fn download_release(&self) {
let Release { let (guid, title, indexer_id) = {
guid, let app = self.app.lock().await;
title, let Release {
indexer_id, guid,
.. title,
} = self indexer_id,
.app ..
.lock() } = app.data.radarr_data.movie_releases.current_selection();
.await
.data (guid.clone(), title.clone(), indexer_id.as_u64().unwrap())
.radarr_data };
.movie_releases
.current_selection_clone();
info!("Downloading release: {}", title); info!("Downloading release: {}", title);
let download_release_body = ReleaseDownloadBody { let download_release_body = ReleaseDownloadBody { guid, indexer_id };
guid,
indexer_id: indexer_id.as_u64().unwrap(),
};
let request_props = self let request_props = self
.radarr_request_props_from( .radarr_request_props_from(
@@ -878,6 +890,65 @@ impl<'a> Network<'a> {
.await; .await;
} }
async fn extract_quality_profile_id(&self) -> u64 {
let app = self.app.lock().await;
let quality_profile = app
.data
.radarr_data
.movie_quality_profile_list
.current_selection();
*app
.data
.radarr_data
.quality_profile_map
.iter()
.filter(|(_, value)| *value == quality_profile)
.map(|(key, _)| key)
.next()
.unwrap()
}
async fn extract_and_add_tag_ids_vec(&self) -> Vec<u64> {
let tags_map = self.app.lock().await.data.radarr_data.tags_map.clone();
let edit_tags = &self
.app
.lock()
.await
.data
.radarr_data
.edit_tags
.text
.clone();
let missing_tags_vec = edit_tags
.split(',')
.into_iter()
.filter(|&tag| !tag.is_empty() && tags_map.get_by_right(tag.trim()).is_none())
.collect::<Vec<&str>>();
for tag in missing_tags_vec {
self.add_tag(tag.trim().to_owned()).await;
}
let app = self.app.lock().await;
app
.data
.radarr_data
.edit_tags
.text
.split(',')
.into_iter()
.filter(|tag| !tag.is_empty())
.map(|tag| {
*app
.data
.radarr_data
.tags_map
.get_by_right(tag.trim())
.unwrap()
})
.collect()
}
async fn extract_movie_id(&self) -> u64 { async fn extract_movie_id(&self) -> u64 {
if !self if !self
.app .app
@@ -898,7 +969,6 @@ impl<'a> Network<'a> {
.filtered_movies .filtered_movies
.current_selection() .current_selection()
.id .id
.clone()
.as_u64() .as_u64()
.unwrap() .unwrap()
} else { } else {
@@ -911,7 +981,6 @@ impl<'a> Network<'a> {
.movies .movies
.current_selection() .current_selection()
.id .id
.clone()
.as_u64() .as_u64()
.unwrap() .unwrap()
} }
@@ -972,6 +1041,7 @@ mod test {
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use bimap::BiMap;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use mockito::{Matcher, Mock, Server, ServerGuard}; use mockito::{Matcher, Mock, Server, ServerGuard};
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
@@ -1010,6 +1080,7 @@ mod test {
"qualityProfileId": 2222, "qualityProfileId": 2222,
"minimumAvailability": "announced", "minimumAvailability": "announced",
"certification": "R", "certification": "R",
"tags": [1],
"ratings": { "ratings": {
"imdb": { "imdb": {
"value": 9.9 "value": 9.9
@@ -1324,7 +1395,7 @@ mod test {
.as_str(), .as_str(),
) )
.await; .await;
app_arc.lock().await.data.radarr_data.search = "test term".to_owned(); app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into();
let network = Network::new(reqwest::Client::new(), &app_arc); let network = Network::new(reqwest::Client::new(), &app_arc);
network network
@@ -1565,6 +1636,7 @@ mod test {
"runtime": 120, "runtime": 120,
"tmdbId": 1234, "tmdbId": 1234,
"qualityProfileId": 2222, "qualityProfileId": 2222,
"tags": [1],
"minimumAvailability": "released", "minimumAvailability": "released",
"ratings": {} "ratings": {}
}); });
@@ -1782,6 +1854,56 @@ mod test {
); );
} }
#[tokio::test]
async fn test_handle_get_tags_event() {
let tags_json = json!([{
"id": 2222,
"label": "usenet"
}]);
let (async_server, app_arc, _server) = mock_radarr_api(
RequestMethod::Get,
None,
Some(tags_json),
RadarrEvent::GetTags.resource(),
)
.await;
let network = Network::new(reqwest::Client::new(), &app_arc);
network.handle_radarr_event(RadarrEvent::GetTags).await;
async_server.assert_async().await;
assert_eq!(
app_arc.lock().await.data.radarr_data.tags_map,
BiMap::from_iter([(2222u64, "usenet".to_owned())])
);
}
#[tokio::test]
async fn test_add_tag() {
let (async_server, app_arc, _server) = mock_radarr_api(
RequestMethod::Post,
Some(json!({ "label": "testing" })),
Some(json!({ "id": 3, "label": "testing" })),
RadarrEvent::GetTags.resource(),
)
.await;
app_arc.lock().await.data.radarr_data.tags_map =
BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]);
let network = Network::new(reqwest::Client::new(), &app_arc);
network.add_tag("testing".to_owned()).await;
async_server.assert_async().await;
assert_eq!(
app_arc.lock().await.data.radarr_data.tags_map,
BiMap::from_iter([
(1, "usenet".to_owned()),
(2, "test".to_owned()),
(3, "testing".to_owned())
])
);
}
#[tokio::test] #[tokio::test]
async fn test_handle_get_root_folders_event() { async fn test_handle_get_root_folders_event() {
let root_folder_json = json!([{ let root_folder_json = json!([{
@@ -1915,6 +2037,7 @@ mod test {
"minimumAvailability": "announced", "minimumAvailability": "announced",
"monitored": true, "monitored": true,
"qualityProfileId": 2222, "qualityProfileId": 2222,
"tags": [1, 2],
"addOptions": { "addOptions": {
"monitor": "movieOnly", "monitor": "movieOnly",
"searchForMovie": true "searchForMovie": true
@@ -1940,6 +2063,9 @@ mod test {
}, },
]; ];
app.data.radarr_data.quality_profile_map = HashMap::from([(2222, "HD - 1080p".to_owned())]); app.data.radarr_data.quality_profile_map = HashMap::from([(2222, "HD - 1080p".to_owned())]);
app.data.radarr_data.tags_map =
BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]);
app.data.radarr_data.edit_tags = "usenet, testing".to_owned().into();
app app
.data .data
.radarr_data .radarr_data
@@ -1984,6 +2110,7 @@ mod test {
*expected_body.get_mut("minimumAvailability").unwrap() = json!("announced"); *expected_body.get_mut("minimumAvailability").unwrap() = json!("announced");
*expected_body.get_mut("qualityProfileId").unwrap() = json!(1111); *expected_body.get_mut("qualityProfileId").unwrap() = json!(1111);
*expected_body.get_mut("path").unwrap() = json!("/nfs/Test Path"); *expected_body.get_mut("path").unwrap() = json!("/nfs/Test Path");
*expected_body.get_mut("tags").unwrap() = json!([1, 2]);
let (async_details_server, app_arc, mut server) = mock_radarr_api( let (async_details_server, app_arc, mut server) = mock_radarr_api(
RequestMethod::Get, RequestMethod::Get,
@@ -2004,8 +2131,10 @@ mod test {
.await; .await;
{ {
let mut app = app_arc.lock().await; let mut app = app_arc.lock().await;
app.data.radarr_data.edit_tags = "test tag".to_owned(); app.data.radarr_data.tags_map =
app.data.radarr_data.edit_path = "/nfs/Test Path".to_owned(); BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]);
app.data.radarr_data.edit_tags = "usenet, testing".to_owned().into();
app.data.radarr_data.edit_path = "/nfs/Test Path".to_owned().into();
app.data.radarr_data.edit_monitored = Some(false); app.data.radarr_data.edit_monitored = Some(false);
app app
.data .data
@@ -2033,8 +2162,7 @@ mod test {
{ {
let app = app_arc.lock().await; let app = app_arc.lock().await;
assert!(app.data.radarr_data.edit_path.is_empty()); assert!(app.data.radarr_data.edit_path.text.is_empty());
assert!(app.data.radarr_data.edit_tags.is_empty());
assert!(app.data.radarr_data.movie_details.items.is_empty()); assert!(app.data.radarr_data.movie_details.items.is_empty());
} }
} }
@@ -2067,9 +2195,74 @@ mod test {
async_server.assert_async().await; async_server.assert_async().await;
} }
#[tokio::test]
async fn test_extract_quality_profile_id() {
let app_arc = Arc::new(Mutex::new(App::default()));
{
let mut app = app_arc.lock().await;
app
.data
.radarr_data
.movie_quality_profile_list
.set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]);
app.data.radarr_data.quality_profile_map =
HashMap::from_iter([(1, "Any".to_owned()), (2, "HD - 1080p".to_owned())]);
}
let network = Network::new(reqwest::Client::new(), &app_arc);
assert_eq!(network.extract_quality_profile_id().await, 1);
}
#[tokio::test]
async fn test_extract_and_add_tag_ids_vec() {
let app_arc = Arc::new(Mutex::new(App::default()));
{
let mut app = app_arc.lock().await;
app.data.radarr_data.edit_tags = " test,hi ,, usenet ".to_owned().into();
app.data.radarr_data.tags_map = BiMap::from_iter([
(1, "usenet".to_owned()),
(2, "test".to_owned()),
(3, "hi".to_owned()),
]);
}
let network = Network::new(reqwest::Client::new(), &app_arc);
assert_eq!(network.extract_and_add_tag_ids_vec().await, vec![2, 3, 1]);
}
#[tokio::test]
async fn test_extract_and_add_tag_ids_vec_add_missing_tags_first() {
let (async_server, app_arc, _server) = mock_radarr_api(
RequestMethod::Post,
Some(json!({ "label": "testing" })),
Some(json!({ "id": 3, "label": "testing" })),
RadarrEvent::GetTags.resource(),
)
.await;
{
let mut app = app_arc.lock().await;
app.data.radarr_data.edit_tags = "usenet, test, testing".to_owned().into();
app.data.radarr_data.tags_map =
BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]);
}
let network = Network::new(reqwest::Client::new(), &app_arc);
let tag_ids_vec = network.extract_and_add_tag_ids_vec().await;
async_server.assert_async().await;
assert_eq!(tag_ids_vec, vec![1, 2, 3]);
assert_eq!(
app_arc.lock().await.data.radarr_data.tags_map,
BiMap::from_iter([
(1, "usenet".to_owned()),
(2, "test".to_owned()),
(3, "testing".to_owned())
])
);
}
#[tokio::test] #[tokio::test]
async fn test_extract_movie_id() { async fn test_extract_movie_id() {
let id = Number::from(1);
let app_arc = Arc::new(Mutex::new(App::default())); let app_arc = Arc::new(Mutex::new(App::default()));
app_arc app_arc
.lock() .lock()
@@ -2078,7 +2271,7 @@ mod test {
.radarr_data .radarr_data
.movies .movies
.set_items(vec![Movie { .set_items(vec![Movie {
id: id.clone(), id: Number::from(1),
..Movie::default() ..Movie::default()
}]); }]);
let network = Network::new(reqwest::Client::new(), &app_arc); let network = Network::new(reqwest::Client::new(), &app_arc);
@@ -2088,7 +2281,6 @@ mod test {
#[tokio::test] #[tokio::test]
async fn test_extract_movie_id_filtered_movies() { async fn test_extract_movie_id_filtered_movies() {
let id = Number::from(1);
let app_arc = Arc::new(Mutex::new(App::default())); let app_arc = Arc::new(Mutex::new(App::default()));
app_arc app_arc
.lock() .lock()
@@ -2097,7 +2289,7 @@ mod test {
.radarr_data .radarr_data
.filtered_movies .filtered_movies
.set_items(vec![Movie { .set_items(vec![Movie {
id: id.clone(), id: Number::from(1),
..Movie::default() ..Movie::default()
}]); }]);
let network = Network::new(reqwest::Client::new(), &app_arc); let network = Network::new(reqwest::Client::new(), &app_arc);
@@ -2107,7 +2299,6 @@ mod test {
#[tokio::test] #[tokio::test]
async fn test_append_movie_id_param() { async fn test_append_movie_id_param() {
let id = Number::from(1);
let app_arc = Arc::new(Mutex::new(App::default())); let app_arc = Arc::new(Mutex::new(App::default()));
app_arc app_arc
.lock() .lock()
@@ -2116,7 +2307,7 @@ mod test {
.radarr_data .radarr_data
.movies .movies
.set_items(vec![Movie { .set_items(vec![Movie {
id: id.clone(), id: Number::from(1),
..Movie::default() ..Movie::default()
}]); }]);
let network = Network::new(reqwest::Client::new(), &app_arc); let network = Network::new(reqwest::Client::new(), &app_arc);
@@ -2342,6 +2533,7 @@ mod test {
quality_profile_id: Number::from(2222), quality_profile_id: Number::from(2222),
minimum_availability: MinimumAvailability::Announced, minimum_availability: MinimumAvailability::Announced,
certification: Some("R".to_owned()), certification: Some("R".to_owned()),
tags: vec![Number::from(1)],
ratings: ratings_list(), ratings: ratings_list(),
movie_file: Some(movie_file()), movie_file: Some(movie_file()),
collection: Some(collection()), collection: Some(collection()),
+5 -2
View File
@@ -93,7 +93,7 @@ fn draw_error<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
.borders(Borders::ALL); .borders(Borders::ALL);
if app.error.text.len() > area.width as usize { if app.error.text.len() > area.width as usize {
app.error.scroll_text(); app.error.scroll_left();
} }
let mut text = Text::from(app.error.to_string()); let mut text = Text::from(app.error.to_string());
@@ -497,6 +497,7 @@ pub fn draw_text_box<B: Backend>(
text_box_area: Rect, text_box_area: Rect,
block_title: Option<&str>, block_title: Option<&str>,
block_content: &str, block_content: &str,
offset: usize,
should_show_cursor: bool, should_show_cursor: bool,
is_selected: bool, is_selected: bool,
) { ) {
@@ -518,7 +519,7 @@ pub fn draw_text_box<B: Backend>(
f.render_widget(search_paragraph, text_box_area); f.render_widget(search_paragraph, text_box_area);
if should_show_cursor { if should_show_cursor {
show_cursor(f, text_box_area, block_content); show_cursor(f, text_box_area, offset, block_content);
} }
} }
@@ -527,6 +528,7 @@ pub fn draw_text_box_with_label<B: Backend>(
area: Rect, area: Rect,
label: &str, label: &str,
text: &str, text: &str,
offset: usize,
is_selected: bool, is_selected: bool,
should_show_cursor: bool, should_show_cursor: bool,
) { ) {
@@ -547,6 +549,7 @@ pub fn draw_text_box_with_label<B: Backend>(
horizontal_chunks[1], horizontal_chunks[1],
None, None,
text, text,
offset,
should_show_cursor, should_show_cursor,
is_selected, is_selected,
); );
+27 -9
View File
@@ -70,7 +70,8 @@ fn draw_add_movie_search<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area:
.data .data
.radarr_data .radarr_data
.add_searched_movies .add_searched_movies
.current_selection_clone() .current_selection()
.clone()
}; };
let chunks = vertical_chunks_with_margin( let chunks = vertical_chunks_with_margin(
@@ -82,12 +83,21 @@ fn draw_add_movie_search<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area:
area, area,
1, 1,
); );
let block_content = app.data.radarr_data.search.as_str(); let block_content = &app.data.radarr_data.search.text;
let offset = *app.data.radarr_data.search.offset.borrow();
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block { match active_radarr_block {
ActiveRadarrBlock::AddMovieSearchInput => { ActiveRadarrBlock::AddMovieSearchInput => {
draw_text_box(f, chunks[0], Some("Add Movie"), block_content, true, false); draw_text_box(
f,
chunks[0],
Some("Add Movie"),
block_content,
offset,
true,
false,
);
f.render_widget(layout_block(), chunks[1]); f.render_widget(layout_block(), chunks[1]);
let mut help_text = Text::from("<esc> close"); let mut help_text = Text::from("<esc> close");
@@ -184,7 +194,7 @@ fn draw_add_movie_search<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area:
"" ""
}; };
movie.title.scroll_or_reset( movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27), get_width_from_percentage(area, 27),
*movie == current_selection, *movie == current_selection,
); );
@@ -208,7 +218,15 @@ fn draw_add_movie_search<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area:
} }
} }
draw_text_box(f, chunks[0], Some("Add Movie"), block_content, false, false); draw_text_box(
f,
chunks[0],
Some("Add Movie"),
block_content,
offset,
false,
false,
);
} }
fn draw_confirmation_popup<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, prompt_area: Rect) { fn draw_confirmation_popup<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, prompt_area: Rect) {
@@ -260,13 +278,13 @@ fn draw_confirmation_prompt<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, pro
let title = "Add Movie"; let title = "Add Movie";
let (movie_title, movie_overview) = if let Route::Radarr(_, Some(_)) = app.get_current_route() { let (movie_title, movie_overview) = if let Route::Radarr(_, Some(_)) = app.get_current_route() {
( (
app &app
.data .data
.radarr_data .radarr_data
.collection_movies .collection_movies
.current_selection() .current_selection()
.title .title
.stationary_style(), .text,
app app
.data .data
.radarr_data .radarr_data
@@ -277,13 +295,13 @@ fn draw_confirmation_prompt<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, pro
) )
} else { } else {
( (
app &app
.data .data
.radarr_data .radarr_data
.add_searched_movies .add_searched_movies
.current_selection() .current_selection()
.title .title
.stationary_style(), .text,
app app
.data .data
.radarr_data .radarr_data
+5 -3
View File
@@ -75,7 +75,8 @@ pub(super) fn draw_collection_details<B: Backend>(
.data .data
.radarr_data .radarr_data
.collection_movies .collection_movies
.current_selection_clone() .current_selection()
.clone()
}; };
let mut help_text = let mut help_text =
Text::from("<↑↓> scroll table | <enter> show overview/add movie | <esc> close"); Text::from("<↑↓> scroll table | <enter> show overview/add movie | <esc> close");
@@ -151,7 +152,7 @@ pub(super) fn draw_collection_details<B: Backend>(
} else { } else {
"" ""
}; };
movie.title.scroll_or_reset( movie.title.scroll_left_or_reset(
get_width_from_percentage(chunks[1], 20), get_width_from_percentage(chunks[1], 20),
current_selection == *movie, current_selection == *movie,
); );
@@ -212,7 +213,8 @@ fn draw_movie_overview<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, content_
.data .data
.radarr_data .radarr_data
.collection_movies .collection_movies
.current_selection_clone() .current_selection()
.clone()
.overview, .overview,
); );
overview.patch_style(style_default()); overview.patch_style(style_default());
+4 -2
View File
@@ -140,7 +140,8 @@ fn draw_edit_confirmation_prompt<B: Backend>(
f, f,
chunks[4], chunks[4],
"Path", "Path",
&app.data.radarr_data.edit_path, &app.data.radarr_data.edit_path.text,
*app.data.radarr_data.edit_path.offset.borrow(),
*selected_block == ActiveRadarrBlock::EditMoviePathInput, *selected_block == ActiveRadarrBlock::EditMoviePathInput,
active_radarr_block == ActiveRadarrBlock::EditMoviePathInput, active_radarr_block == ActiveRadarrBlock::EditMoviePathInput,
); );
@@ -148,7 +149,8 @@ fn draw_edit_confirmation_prompt<B: Backend>(
f, f,
chunks[5], chunks[5],
"Tags", "Tags",
&app.data.radarr_data.edit_tags, &app.data.radarr_data.edit_tags.text,
*app.data.radarr_data.edit_tags.offset.borrow(),
*selected_block == ActiveRadarrBlock::EditMovieTagsInput, *selected_block == ActiveRadarrBlock::EditMovieTagsInput,
active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput, active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput,
); );
+42 -26
View File
@@ -158,6 +158,7 @@ pub(super) fn draw_radarr_context_row<B: Backend>(f: &mut Frame<'_, B>, app: &Ap
fn draw_library<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { fn draw_library<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
let quality_profile_map = &app.data.radarr_data.quality_profile_map; let quality_profile_map = &app.data.radarr_data.quality_profile_map;
let tags_map = &app.data.radarr_data.tags_map;
let downloads_vec = &app.data.radarr_data.downloads.items; let downloads_vec = &app.data.radarr_data.downloads.items;
let content = if !app.data.radarr_data.filtered_movies.items.is_empty() let content = if !app.data.radarr_data.filtered_movies.items.is_empty()
&& !app.data.radarr_data.is_filtering && !app.data.radarr_data.is_filtering
@@ -208,7 +209,21 @@ fn draw_library<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
let (hours, minutes) = convert_runtime(movie.runtime.as_u64().unwrap()); let (hours, minutes) = convert_runtime(movie.runtime.as_u64().unwrap());
let file_size: f64 = convert_to_gb(movie.size_on_disk.as_u64().unwrap()); let file_size: f64 = convert_to_gb(movie.size_on_disk.as_u64().unwrap());
let certification = movie.certification.clone().unwrap_or_else(|| "".to_owned()); let certification = movie.certification.clone().unwrap_or_else(|| "".to_owned());
let tags = ""; let quality_profile = quality_profile_map
.get(&movie.quality_profile_id.as_u64().unwrap())
.unwrap()
.to_owned();
let tags = movie
.tags
.iter()
.map(|tag_id| {
tags_map
.get_by_left(&tag_id.as_u64().unwrap())
.unwrap()
.clone()
})
.collect::<Vec<String>>()
.join(", ");
Row::new(vec![ Row::new(vec![
Cell::from(movie.title.to_owned()), Cell::from(movie.title.to_owned()),
@@ -218,14 +233,9 @@ fn draw_library<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
Cell::from(certification), Cell::from(certification),
Cell::from(movie.original_language.name.to_owned()), Cell::from(movie.original_language.name.to_owned()),
Cell::from(format!("{:.2} GB", file_size)), Cell::from(format!("{:.2} GB", file_size)),
Cell::from( Cell::from(quality_profile),
quality_profile_map
.get(&movie.quality_profile_id.as_u64().unwrap())
.unwrap()
.to_owned(),
),
Cell::from(monitored.to_owned()), Cell::from(monitored.to_owned()),
Cell::from(tags.to_owned()), Cell::from(tags),
]) ])
.style(determine_row_style(downloads_vec, movie)) .style(determine_row_style(downloads_vec, movie))
}, },
@@ -322,20 +332,23 @@ fn draw_search_box<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect)
f.render_widget(input, chunks[0]); f.render_widget(input, chunks[0]);
} else { } else {
let (block_title, block_content) = match app.get_current_route() { let default_content = String::default();
let (block_title, offset, block_content) = match app.get_current_route() {
Route::Radarr(active_radarr_block, _) => match active_radarr_block { Route::Radarr(active_radarr_block, _) => match active_radarr_block {
_ if SEARCH_BLOCKS.contains(active_radarr_block) => { _ if SEARCH_BLOCKS.contains(active_radarr_block) => (
("Search", app.data.radarr_data.search.as_str()) "Search",
} *app.data.radarr_data.search.offset.borrow(),
_ => ("", ""), &app.data.radarr_data.search.text,
),
_ => ("", 0, &default_content),
}, },
_ => ("", ""), _ => ("", 0, &default_content),
}; };
let input = Paragraph::new(block_content) let input = Paragraph::new(block_content.as_str())
.style(style_default()) .style(style_default())
.block(title_block_centered(block_title)); .block(title_block_centered(block_title));
show_cursor(f, chunks[0], block_content); show_cursor(f, chunks[0], offset, block_content);
f.render_widget(input, chunks[0]); f.render_widget(input, chunks[0]);
} }
@@ -360,20 +373,23 @@ fn draw_filter_box<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect)
f.render_widget(input, chunks[0]); f.render_widget(input, chunks[0]);
} else { } else {
let (block_title, block_content) = match app.get_current_route() { let default_content = String::default();
let (block_title, offset, block_content) = match app.get_current_route() {
Route::Radarr(active_radarr_block, _) => match active_radarr_block { Route::Radarr(active_radarr_block, _) => match active_radarr_block {
_ if FILTER_BLOCKS.contains(active_radarr_block) => { _ if FILTER_BLOCKS.contains(active_radarr_block) => (
("Filter", app.data.radarr_data.filter.as_str()) "Filter",
} *app.data.radarr_data.filter.offset.borrow(),
_ => ("", ""), &app.data.radarr_data.filter.text,
),
_ => ("", 0, &default_content),
}, },
_ => ("", ""), _ => ("", 0, &default_content),
}; };
let input = Paragraph::new(block_content) let input = Paragraph::new(block_content.as_str())
.style(style_default()) .style(style_default())
.block(title_block_centered(block_title)); .block(title_block_centered(block_title));
show_cursor(f, chunks[0], block_content); show_cursor(f, chunks[0], offset, block_content);
f.render_widget(input, chunks[0]); f.render_widget(input, chunks[0]);
} }
@@ -413,7 +429,7 @@ fn draw_downloads<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
let current_selection = if app.data.radarr_data.downloads.items.is_empty() { let current_selection = if app.data.radarr_data.downloads.items.is_empty() {
DownloadRecord::default() DownloadRecord::default()
} else { } else {
app.data.radarr_data.downloads.current_selection_clone() app.data.radarr_data.downloads.current_selection().clone()
}; };
draw_table( draw_table(
@@ -456,7 +472,7 @@ fn draw_downloads<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
} = download_record; } = download_record;
let path = output_path.clone().unwrap_or_default(); let path = output_path.clone().unwrap_or_default();
path.scroll_or_reset( path.scroll_left_or_reset(
get_width_from_percentage(area, 18), get_width_from_percentage(area, 18),
current_selection == *download_record, current_selection == *download_record,
); );
+13 -7
View File
@@ -113,7 +113,7 @@ fn draw_refresh_and_scan_prompt<B: Backend>(
"Refresh and Scan", "Refresh and Scan",
format!( format!(
"Do you want to trigger a refresh and disk scan for the movie: {}?", "Do you want to trigger a refresh and disk scan for the movie: {}?",
app.data.radarr_data.movies.current_selection_clone().title app.data.radarr_data.movies.current_selection().title
) )
.as_str(), .as_str(),
&app.data.radarr_data.prompt_confirm, &app.data.radarr_data.prompt_confirm,
@@ -223,7 +223,12 @@ fn draw_movie_history<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, content_a
let current_selection = if app.data.radarr_data.movie_history.items.is_empty() { let current_selection = if app.data.radarr_data.movie_history.items.is_empty() {
MovieHistoryItem::default() MovieHistoryItem::default()
} else { } else {
app.data.radarr_data.movie_history.current_selection_clone() app
.data
.radarr_data
.movie_history
.current_selection()
.clone()
}; };
let block = layout_block_top_border(); let block = layout_block_top_border();
@@ -263,7 +268,7 @@ fn draw_movie_history<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, content_a
event_type, event_type,
} = movie_history_item; } = movie_history_item;
movie_history_item.source_title.scroll_or_reset( movie_history_item.source_title.scroll_left_or_reset(
get_width_from_percentage(content_area, 34), get_width_from_percentage(content_area, 34),
current_selection == *movie_history_item, current_selection == *movie_history_item,
); );
@@ -362,7 +367,8 @@ fn draw_movie_releases<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, content_
.data .data
.radarr_data .radarr_data
.movie_releases .movie_releases
.current_selection_clone() .current_selection()
.clone()
}; };
let current_route = *app.get_current_route(); let current_route = *app.get_current_route();
let mut table_headers_vec = vec![ let mut table_headers_vec = vec![
@@ -432,7 +438,7 @@ fn draw_movie_releases<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, content_
.. ..
} = release; } = release;
let age = format!("{} days", age.as_u64().unwrap_or(0)); let age = format!("{} days", age.as_u64().unwrap_or(0));
title.scroll_or_reset( title.scroll_left_or_reset(
get_width_from_percentage(content_area, 30), get_width_from_percentage(content_area, 30),
current_selection == *release current_selection == *release
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(), && current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
@@ -488,12 +494,12 @@ fn draw_manual_search_confirm_prompt<B: Backend>(
let prompt = if current_selection.rejected { let prompt = if current_selection.rejected {
format!( format!(
"Do you really want to download the rejected release: {}?", "Do you really want to download the rejected release: {}?",
current_selection.title.stationary_style() &current_selection.title.text
) )
} else { } else {
format!( format!(
"Do you want to download the release: {}?", "Do you want to download the release: {}?",
current_selection.title.stationary_style() &current_selection.title.text
) )
}; };
+2 -2
View File
@@ -266,8 +266,8 @@ pub fn line_gauge_with_label(title: &str, ratio: f64) -> LineGauge<'_> {
.label(Spans::from(format!("{}: {:.0}%", title, ratio * 100.0))) .label(Spans::from(format!("{}: {:.0}%", title, ratio * 100.0)))
} }
pub fn show_cursor<B: Backend>(f: &mut Frame<'_, B>, area: Rect, string: &str) { pub fn show_cursor<B: Backend>(f: &mut Frame<'_, B>, area: Rect, offset: usize, string: &str) {
f.set_cursor(area.x + string.len() as u16 + 1, area.y + 1); f.set_cursor(area.x + (string.len() - offset) as u16 + 1, area.y + 1);
} }
pub fn get_width_from_percentage(area: Rect, percentage: u16) -> usize { pub fn get_width_from_percentage(area: Rect, percentage: u16) -> usize {