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

This commit is contained in:
2023-08-08 10:50:06 -06:00
parent 9142d5ab3e
commit a564710aee
7 changed files with 437 additions and 21 deletions
+2
View File
@@ -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,
+189 -1
View File
@@ -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)]
+5
View File
@@ -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 {
+48 -7
View File
@@ -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::<Test>;
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;
+126 -6
View File
@@ -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::<AddRootFolderBody, Value>(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();
+3 -5
View File
@@ -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<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect)
}
fn draw_error<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
let block = borderless_block()
.title("Error | <esc> to close")
.style(style_failure())
.borders(Borders::ALL);
let block =
title_block("Error | <esc> to close").style(style_failure().add_modifier(Modifier::BOLD));
app.error.scroll_left_or_reset(
area.width as usize,
+64 -2
View File
@@ -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<B: Backend>(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<B: Backend>(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<B: Backend>(f: &mut Frame<'_, B>, app: &mut App,
);
}
fn draw_delete_root_folder_prompt<B: Backend>(
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<B: Backend>(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("<esc> 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<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
let chunks =
vertical_chunks_with_margin(vec![Constraint::Length(3), Constraint::Min(0)], area, 1);