From a564710aeeda840f36cea76c442de087dc091ff2 Mon Sep 17 00:00:00 2001 From: Dark-Alex-17 Date: Tue, 8 Aug 2023 10:50:06 -0600 Subject: [PATCH] Added the ability to add and delete root folders, and added a body to the error message logs and dialog box if a body is returned from the Servarr --- src/app/radarr.rs | 2 + src/handlers/radarr_handlers/mod.rs | 190 +++++++++++++++++++++++++++- src/models/radarr_models.rs | 5 + src/network/mod.rs | 55 +++++++- src/network/radarr_network.rs | 132 ++++++++++++++++++- src/ui/mod.rs | 8 +- src/ui/radarr_ui/mod.rs | 66 +++++++++- 7 files changed, 437 insertions(+), 21 deletions(-) diff --git a/src/app/radarr.rs b/src/app/radarr.rs index ecade02..b68b8e1 100644 --- a/src/app/radarr.rs +++ b/src/app/radarr.rs @@ -336,6 +336,7 @@ pub enum ActiveRadarrBlock { AddMovieConfirmPrompt, AddMovieTagsInput, AddMovieEmptySearchResults, + AddRootFolderPrompt, AutomaticallySearchMoviePrompt, Collections, CollectionDetails, @@ -343,6 +344,7 @@ pub enum ActiveRadarrBlock { Crew, DeleteMoviePrompt, DeleteDownloadPrompt, + DeleteRootFolderPrompt, Downloads, EditCollectionPrompt, EditCollectionConfirmPrompt, diff --git a/src/handlers/radarr_handlers/mod.rs b/src/handlers/radarr_handlers/mod.rs index bb176db..105bbf6 100644 --- a/src/handlers/radarr_handlers/mod.rs +++ b/src/handlers/radarr_handlers/mod.rs @@ -9,7 +9,7 @@ use crate::handlers::radarr_handlers::edit_collection_handler::EditCollectionHan use crate::handlers::radarr_handlers::edit_movie_handler::EditMovieHandler; use crate::handlers::radarr_handlers::movie_details_handler::MovieDetailsHandler; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; -use crate::models::Scrollable; +use crate::models::{HorizontallyScrollableText, Scrollable}; use crate::network::radarr_network::RadarrEvent; use crate::utils::strip_non_alphanumeric_characters; use crate::{handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; @@ -164,6 +164,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => { self.app.data.radarr_data.filter.scroll_home() } + ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.scroll_home(), _ => (), } } @@ -204,6 +205,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => { self.app.data.radarr_data.filter.reset_offset() } + ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.reset_offset(), _ => (), } } @@ -216,6 +218,9 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { ActiveRadarrBlock::Downloads => self .app .push_navigation_stack(ActiveRadarrBlock::DeleteDownloadPrompt.into()), + ActiveRadarrBlock::RootFolders => self + .app + .push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()), _ => (), } } @@ -242,9 +247,13 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { }, ActiveRadarrBlock::DeleteMoviePrompt | ActiveRadarrBlock::DeleteDownloadPrompt + | ActiveRadarrBlock::DeleteRootFolderPrompt | ActiveRadarrBlock::UpdateAllMoviesPrompt | ActiveRadarrBlock::UpdateAllCollectionsPrompt | ActiveRadarrBlock::UpdateDownloadsPrompt => handle_prompt_toggle(self.app, self.key), + ActiveRadarrBlock::AddRootFolderPrompt => { + handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.edit_path) + } ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => { handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search) } @@ -364,6 +373,13 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { self.app.pop_navigation_stack(); } + ActiveRadarrBlock::DeleteRootFolderPrompt => { + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteRootFolder); + } + + self.app.pop_navigation_stack(); + } ActiveRadarrBlock::UpdateAllMoviesPrompt => { if self.app.data.radarr_data.prompt_confirm { self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies); @@ -385,6 +401,12 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { self.app.pop_navigation_stack(); } + ActiveRadarrBlock::AddRootFolderPrompt => { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddRootFolder); + self.app.data.radarr_data.prompt_confirm = true; + self.app.should_ignore_quit_key = false; + self.app.pop_navigation_stack(); + } _ => (), } } @@ -401,8 +423,15 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { self.app.data.radarr_data.reset_search(); self.app.should_ignore_quit_key = false; } + ActiveRadarrBlock::AddRootFolderPrompt => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.edit_path = HorizontallyScrollableText::default(); + self.app.data.radarr_data.prompt_confirm = false; + self.app.should_ignore_quit_key = false; + } ActiveRadarrBlock::DeleteMoviePrompt | ActiveRadarrBlock::DeleteDownloadPrompt + | ActiveRadarrBlock::DeleteRootFolderPrompt | ActiveRadarrBlock::UpdateAllMoviesPrompt | ActiveRadarrBlock::UpdateAllCollectionsPrompt | ActiveRadarrBlock::UpdateDownloadsPrompt => { @@ -514,8 +543,17 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { _ if *key == DEFAULT_KEYBINDINGS.refresh.key => { self.app.should_refresh = true; } + _ if *key == DEFAULT_KEYBINDINGS.add.key => { + self + .app + .push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); + self.app.should_ignore_quit_key = true; + } _ => (), }, + ActiveRadarrBlock::AddRootFolderPrompt => { + handle_text_box_keys!(self, key, self.app.data.radarr_data.edit_path) + } _ if SEARCH_BLOCKS.contains(self.active_radarr_block) => { handle_text_box_keys!(self, key, self.app.data.radarr_data.search) } @@ -869,6 +907,15 @@ mod tests { path ); + #[test] + fn test_add_root_folder_prompt_home_end_keys() { + test_text_box_home_end_keys!( + RadarrHandler, + ActiveRadarrBlock::AddRootFolderPrompt, + edit_path + ); + } + #[rstest] fn test_search_boxes_home_end_keys( #[values(ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchCollection)] @@ -916,6 +963,24 @@ mod tests { &ActiveRadarrBlock::DeleteDownloadPrompt.into() ); } + + #[test] + fn test_root_folder_delete() { + let mut app = App::default(); + + RadarrHandler::with( + &DELETE_KEY, + &mut app, + &ActiveRadarrBlock::RootFolders, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::DeleteRootFolderPrompt.into() + ); + } } mod test_handle_left_right_action { @@ -987,6 +1052,7 @@ mod tests { #[values( ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteDownloadPrompt, + ActiveRadarrBlock::DeleteRootFolderPrompt, ActiveRadarrBlock::UpdateAllMoviesPrompt, ActiveRadarrBlock::UpdateAllCollectionsPrompt, ActiveRadarrBlock::UpdateDownloadsPrompt @@ -1005,6 +1071,15 @@ mod tests { assert!(!app.data.radarr_data.prompt_confirm); } + #[test] + fn test_add_root_folder_prompt_left_right_keys() { + test_text_box_left_right_keys!( + RadarrHandler, + ActiveRadarrBlock::AddRootFolderPrompt, + edit_path + ); + } + #[rstest] fn test_search_boxes_left_right_keys( #[values(ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchCollection)] @@ -1240,6 +1315,34 @@ mod tests { ); } + #[test] + fn test_add_root_folder_prompt_confirm_submit() { + let mut app = App::default(); + app.data.radarr_data.prompt_confirm = true; + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); + app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); + + RadarrHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(RadarrEvent::AddRootFolder) + ); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + } + #[rstest] #[case( ActiveRadarrBlock::Movies, @@ -1251,6 +1354,11 @@ mod tests { ActiveRadarrBlock::DeleteDownloadPrompt, RadarrEvent::DeleteDownload )] + #[case( + ActiveRadarrBlock::RootFolders, + ActiveRadarrBlock::DeleteRootFolderPrompt, + RadarrEvent::DeleteRootFolder + )] #[case( ActiveRadarrBlock::Movies, ActiveRadarrBlock::UpdateAllMoviesPrompt, @@ -1365,6 +1473,10 @@ mod tests { #[rstest] #[case(ActiveRadarrBlock::Movies, ActiveRadarrBlock::DeleteMoviePrompt)] #[case(ActiveRadarrBlock::Movies, ActiveRadarrBlock::UpdateAllMoviesPrompt)] + #[case( + ActiveRadarrBlock::RootFolders, + ActiveRadarrBlock::DeleteRootFolderPrompt + )] #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt)] #[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::UpdateDownloadsPrompt)] #[case( @@ -1386,6 +1498,32 @@ mod tests { assert!(!app.data.radarr_data.prompt_confirm); } + #[test] + fn test_add_root_folder_prompt_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); + app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); + app.data.radarr_data.edit_path = HorizontallyScrollableText::from("/nfs/test".to_owned()); + app.should_ignore_quit_key = true; + + RadarrHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::RootFolders.into() + ); + + assert!(app.data.radarr_data.edit_path.text.is_empty()); + assert!(!app.data.radarr_data.prompt_confirm); + assert!(!app.should_ignore_quit_key); + } + #[test] fn test_default_esc() { let mut app = App::default(); @@ -1485,6 +1623,25 @@ mod tests { assert!(app.should_ignore_quit_key); } + #[test] + fn test_root_folder_add() { + let mut app = App::default(); + + RadarrHandler::with( + &DEFAULT_KEYBINDINGS.add.key, + &mut app, + &ActiveRadarrBlock::RootFolders, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddRootFolderPrompt.into() + ); + assert!(app.should_ignore_quit_key); + } + #[test] fn test_movie_edit_key() { test_edit_movie_key!( @@ -1552,6 +1709,22 @@ mod tests { assert!(app.should_refresh); } + #[test] + fn test_add_root_folder_prompt_backspace_key() { + let mut app = App::default(); + app.data.radarr_data.edit_path = "/nfs/test".to_owned().into(); + + RadarrHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_path.text, "/nfs/tes"); + } + #[rstest] fn test_search_boxes_backspace_key( #[values(ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchCollection)] @@ -1590,6 +1763,21 @@ mod tests { assert_str_eq!(app.data.radarr_data.filter.text, "Tes"); } + #[test] + fn test_add_root_folder_prompt_char_key() { + let mut app = App::default(); + + RadarrHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert_str_eq!(app.data.radarr_data.edit_path.text, "h"); + } + #[rstest] fn test_search_boxes_char_key( #[values(ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchCollection)] diff --git a/src/models/radarr_models.rs b/src/models/radarr_models.rs index 83d228f..0676632 100644 --- a/src/models/radarr_models.rs +++ b/src/models/radarr_models.rs @@ -324,6 +324,11 @@ pub struct AddMovieSearchResult { pub ratings: RatingsList, } +#[derive(Default, Serialize, Debug)] +pub struct AddRootFolderBody { + pub path: String, +} + #[derive(Default, Derivative, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct MovieCommandBody { diff --git a/src/network/mod.rs b/src/network/mod.rs index 359018f..c0f7cb8 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use anyhow::anyhow; use log::{debug, error}; +use regex::Regex; use reqwest::{Client, RequestBuilder}; use serde::de::DeserializeOwned; use serde::Serialize; @@ -71,13 +72,21 @@ impl<'a> Network<'a> { RequestMethod::Delete | RequestMethod::Put => (), } } else { + let status = response.status(); + let whitespace_regex = Regex::new(r"\s+").unwrap(); + let response_body = response.text().await.unwrap_or_default(); + let error_body = whitespace_regex + .replace_all(&response_body.replace('\n', " "), " ") + .to_string(); + error!( - "Request failed. Received {} response code", - response.status() + "Request failed. Received {} response code with body: {}", + status, response_body ); self.app.lock().await.handle_error(anyhow!( - "Request failed. Received {} response code", - response.status() + "Request failed. Received {} response code with body: {}", + status, + error_body )); } } @@ -337,7 +346,31 @@ mod tests { async_server.assert_async().await; assert_str_eq!( app_arc.lock().await.error.text, - "Request failed. Received 404 Not Found response code" + r#"Request failed. Received 404 Not Found response code with body: { "value": "Test" }"# + ); + } + + #[tokio::test] + async fn test_handle_request_non_success_code_empty_response_body() { + let (async_server, app_arc, server) = mock_api(RequestMethod::Post, 404, false).await; + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_request::<(), Test>( + RequestProps { + uri: format!("{}/test", server.url()), + method: RequestMethod::Post, + body: None, + api_token: "test1234".to_owned(), + }, + |response, mut app| app.error = HorizontallyScrollableText::from(response.value), + ) + .await; + + async_server.assert_async().await; + assert_str_eq!( + app_arc.lock().await.error.text, + r#"Request failed. Received 404 Not Found response code with body: "# ); } @@ -360,7 +393,11 @@ mod tests { let mut body = None::; if request_method == RequestMethod::Post { - async_server = async_server.with_body(r#"{ "value": "Test" }"#); + async_server = async_server.with_body( + r#"{ + "value": "Test" + }"#, + ); body = Some(Test { value: "Test".to_owned(), }); @@ -402,7 +439,11 @@ mod tests { .with_status(response_status); if has_response_body { - async_server = async_server.with_body(r#"{ "value": "Test" }"#); + async_server = async_server.with_body( + r#"{ + "value": "Test" + }"#, + ); } async_server = async_server.create_async().await; diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index ef28abf..0f42c35 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -9,9 +9,10 @@ use urlencoding::encode; use crate::app::radarr::ActiveRadarrBlock; use crate::app::RadarrConfig; use crate::models::radarr_models::{ - AddMovieBody, AddMovieSearchResult, AddOptions, Collection, CollectionMovie, CommandBody, Credit, - CreditType, DiskSpace, DownloadRecord, DownloadsResponse, Movie, MovieCommandBody, - MovieHistoryItem, QualityProfile, Release, ReleaseDownloadBody, RootFolder, SystemStatus, Tag, + AddMovieBody, AddMovieSearchResult, AddOptions, AddRootFolderBody, Collection, CollectionMovie, + CommandBody, Credit, CreditType, DiskSpace, DownloadRecord, DownloadsResponse, Movie, + MovieCommandBody, MovieHistoryItem, QualityProfile, Release, ReleaseDownloadBody, RootFolder, + SystemStatus, Tag, }; use crate::models::{Route, ScrollableText}; use crate::network::{Network, NetworkEvent, RequestMethod, RequestProps}; @@ -20,8 +21,10 @@ use crate::utils::{convert_runtime, convert_to_gb}; #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum RadarrEvent { AddMovie, + AddRootFolder, DeleteDownload, DeleteMovie, + DeleteRootFolder, DownloadRelease, EditMovie, EditCollection, @@ -62,7 +65,9 @@ impl RadarrEvent { RadarrEvent::GetOverview => "/diskspace", RadarrEvent::GetQualityProfiles => "/qualityprofile", RadarrEvent::GetReleases | RadarrEvent::DownloadRelease => "/release", - RadarrEvent::GetRootFolders => "/rootfolder", + RadarrEvent::AddRootFolder | RadarrEvent::GetRootFolders | RadarrEvent::DeleteRootFolder => { + "/rootfolder" + } RadarrEvent::GetStatus => "/system/status", RadarrEvent::GetTags => "/tag", RadarrEvent::TriggerAutomaticSearch @@ -85,8 +90,10 @@ impl<'a> Network<'a> { pub async fn handle_radarr_event(&self, radarr_event: RadarrEvent) { match radarr_event { RadarrEvent::AddMovie => self.add_movie().await, + RadarrEvent::AddRootFolder => self.add_root_folder().await, RadarrEvent::DeleteMovie => self.delete_movie().await, RadarrEvent::DeleteDownload => self.delete_download().await, + RadarrEvent::DeleteRootFolder => self.delete_root_folder().await, RadarrEvent::DownloadRelease => self.download_release().await, RadarrEvent::EditMovie => self.edit_movie().await, RadarrEvent::EditCollection => self.edit_collection().await, @@ -726,6 +733,42 @@ impl<'a> Network<'a> { .await; } + async fn delete_root_folder(&self) { + let root_folder_id = self + .app + .lock() + .await + .data + .radarr_data + .root_folders + .current_selection() + .id + .as_u64() + .unwrap(); + + info!( + "Deleting Radarr root folder for folder with id: {}", + root_folder_id + ); + + let request_props = self + .radarr_request_props_from( + format!( + "{}/{}", + RadarrEvent::DeleteRootFolder.resource(), + root_folder_id + ) + .as_str(), + RequestMethod::Delete, + None::<()>, + ) + .await; + + self + .handle_request::<(), ()>(request_props, |_, _| ()) + .await; + } + async fn add_movie(&self) { info!("Adding new movie to Radarr"); let body = { @@ -804,6 +847,27 @@ impl<'a> Network<'a> { .await; } + async fn add_root_folder(&self) { + info!("Adding new root folder to Radarr"); + let body = AddRootFolderBody { + path: self.app.lock().await.data.radarr_data.edit_path.drain(), + }; + + debug!("Add root folder body: {:?}", body); + + let request_props = self + .radarr_request_props_from( + RadarrEvent::AddRootFolder.resource(), + RequestMethod::Post, + Some(body), + ) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await; + } + async fn edit_movie(&self) { info!("Editing Radarr movie"); @@ -2178,9 +2242,34 @@ mod test { async_server.assert_async().await; } + #[tokio::test] + async fn test_handle_delete_root_folder_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Delete, + None, + None, + format!("{}/1", RadarrEvent::DeleteRootFolder.resource()).as_str(), + ) + .await; + app_arc + .lock() + .await + .data + .radarr_data + .root_folders + .set_items(vec![root_folder()]); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::DeleteRootFolder) + .await; + + async_server.assert_async().await; + } + #[rstest] #[tokio::test] - async fn test_handle_add_movie_event(#[values(true, false)] collection_details_context: bool) { + async fn test_handle_add_movie_event(#[values(true, false)] movie_details_context: bool) { let (async_server, app_arc, _server) = mock_radarr_api( RequestMethod::Post, Some(json!({ @@ -2239,7 +2328,7 @@ mod test { .radarr_data .minimum_availability_list .set_items(Vec::from_iter(MinimumAvailability::iter())); - if collection_details_context { + if movie_details_context { app .data .radarr_data @@ -2261,6 +2350,37 @@ mod test { async_server.assert_async().await; } + #[tokio::test] + async fn test_handle_add_root_folder_event() { + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Post, + Some(json!({ + "path": "/nfs/test" + })), + None, + RadarrEvent::AddRootFolder.resource(), + ) + .await; + + app_arc.lock().await.data.radarr_data.edit_path = + HorizontallyScrollableText::from("/nfs/test".to_owned()); + let network = Network::new(reqwest::Client::new(), &app_arc); + + network + .handle_radarr_event(RadarrEvent::AddRootFolder) + .await; + + async_server.assert_async().await; + assert!(app_arc + .lock() + .await + .data + .radarr_data + .edit_path + .text + .is_empty()); + } + #[tokio::test] async fn test_handle_edit_movie_event() { let mut expected_body: Value = serde_json::from_str(MOVIE_JSON).unwrap(); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 595a1a6..98ca4ba 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -6,7 +6,7 @@ use tui::widgets::Paragraph; use tui::widgets::Row; use tui::widgets::Table; use tui::widgets::Tabs; -use tui::widgets::{Block, Borders, Wrap}; +use tui::widgets::{Block, Wrap}; use tui::widgets::{Clear, List, ListItem}; use tui::Frame; @@ -87,10 +87,8 @@ fn draw_header_row(f: &mut Frame<'_, B>, app: &mut App, area: Rect) } fn draw_error(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { - let block = borderless_block() - .title("Error | to close") - .style(style_failure()) - .borders(Borders::ALL); + let block = + title_block("Error | to close").style(style_failure().add_modifier(Modifier::BOLD)); app.error.scroll_left_or_reset( area.width as usize, diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index bc710ee..ed755e1 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -25,8 +25,9 @@ use crate::ui::radarr_ui::movie_details_ui::draw_movie_info_popup; use crate::ui::utils::{ borderless_block, get_width_from_percentage, horizontal_chunks, layout_block, layout_block_top_border, line_gauge_with_label, line_gauge_with_title, show_cursor, - style_awaiting_import, style_bold, style_default, style_failure, style_primary, style_success, - style_unmonitored, style_warning, title_block, title_block_centered, vertical_chunks_with_margin, + style_awaiting_import, style_bold, style_default, style_failure, style_help, style_primary, + style_success, style_unmonitored, style_warning, title_block, title_block_centered, + vertical_chunks_with_margin, }; use crate::ui::{ draw_drop_down_list, draw_large_popup_over, draw_medium_popup_over, draw_popup, draw_popup_over, @@ -72,6 +73,15 @@ pub(super) fn draw_radarr_ui(f: &mut Frame<'_, B>, app: &mut App, ar ), ActiveRadarrBlock::Downloads => draw_downloads(f, app, content_rect), ActiveRadarrBlock::RootFolders => draw_root_folders(f, app, content_rect), + ActiveRadarrBlock::AddRootFolderPrompt => draw_popup_over( + f, + app, + content_rect, + draw_root_folders, + draw_add_root_folder_prompt_box, + 30, + 15, + ), ActiveRadarrBlock::Collections => draw_collections(f, app, content_rect), _ if MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block) => { draw_large_popup_over(f, app, content_rect, draw_library, draw_movie_info_popup) @@ -150,6 +160,13 @@ pub(super) fn draw_radarr_ui(f: &mut Frame<'_, B>, app: &mut App, ar draw_downloads, draw_delete_download_prompt, ), + ActiveRadarrBlock::DeleteRootFolderPrompt => draw_prompt_popup_over( + f, + app, + content_rect, + draw_root_folders, + draw_delete_root_folder_prompt, + ), ActiveRadarrBlock::UpdateDownloadsPrompt => draw_prompt_popup_over( f, app, @@ -363,6 +380,51 @@ fn draw_delete_download_prompt(f: &mut Frame<'_, B>, app: &mut App, ); } +fn draw_delete_root_folder_prompt( + f: &mut Frame<'_, B>, + app: &mut App, + prompt_area: Rect, +) { + draw_prompt_box( + f, + prompt_area, + "Delete Root Folder", + format!( + "Do you really want to delete this root folder: {}?", + app.data.radarr_data.root_folders.current_selection().path + ) + .as_str(), + &app.data.radarr_data.prompt_confirm, + ); +} + +fn draw_add_root_folder_prompt_box(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { + let chunks = vertical_chunks_with_margin( + vec![ + Constraint::Length(3), + Constraint::Length(1), + Constraint::Min(0), + ], + area, + 1, + ); + let block_title = "Add Root Folder"; + let offset = *app.data.radarr_data.edit_path.offset.borrow(); + let block_content = &app.data.radarr_data.edit_path.text; + + let input = Paragraph::new(block_content.as_str()) + .style(style_default()) + .block(title_block_centered(block_title)); + let help = Paragraph::new(" cancel") + .style(style_help()) + .alignment(Alignment::Center) + .block(borderless_block()); + show_cursor(f, chunks[0], offset, block_content); + + f.render_widget(input, chunks[0]); + f.render_widget(help, chunks[1]); +} + fn draw_search_box(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { let chunks = vertical_chunks_with_margin(vec![Constraint::Length(3), Constraint::Min(0)], area, 1);