diff --git a/README.md b/README.md index e374705..3219aaa 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ tautulli: - [x] View details of a specific movie including description, history, downloaded file info, or the credits - [x] View details of any collection and the movies in them - [x] Search your library or collections -- [ ] Add movies to Radarr +- [ ] Add or delete movies - [ ] Manage your quality profiles - [ ] Modify your Radarr settings diff --git a/src/app/key_binding.rs b/src/app/key_binding.rs index 2d90ed3..5be0e26 100644 --- a/src/app/key_binding.rs +++ b/src/app/key_binding.rs @@ -9,18 +9,19 @@ macro_rules! generate_keybindings { } generate_keybindings! { - up, - down, - left, - right, - backspace, - search, - filter, - home, - end, - submit, - quit, - esc + up, + down, + left, + right, + backspace, + search, + filter, + home, + end, + delete, + submit, + quit, + esc } pub struct KeyBinding { @@ -65,6 +66,10 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings { key: Key::End, desc: "End", }, + delete: KeyBinding { + key: Key::Delete, + desc: "Delete selected item", + }, submit: KeyBinding { key: Key::Enter, desc: "Select", diff --git a/src/app/mod.rs b/src/app/mod.rs index 25d4414..c22a57e 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -29,6 +29,7 @@ pub struct App { pub network_tick_frequency: Duration, pub is_routing: bool, pub is_loading: bool, + pub should_refresh: bool, pub config: AppConfig, pub data: Data, } @@ -69,7 +70,7 @@ impl App { } pub async fn on_tick(&mut self, is_first_render: bool) { - if self.tick_count % self.tick_until_poll == 0 || self.is_routing { + if self.tick_count % self.tick_until_poll == 0 || self.is_routing || self.should_refresh { match self.get_current_route() { Route::Radarr(active_radarr_block) => { self @@ -80,6 +81,7 @@ impl App { } self.is_routing = false; + self.should_refresh = false; } self.tick_count += 1; @@ -133,6 +135,7 @@ impl Default for App { last_tick: Instant::now(), is_loading: false, is_routing: false, + should_refresh: false, config: AppConfig::default(), data: Data::default(), } diff --git a/src/app/radarr.rs b/src/app/radarr.rs index d299b50..8387eff 100644 --- a/src/app/radarr.rs +++ b/src/app/radarr.rs @@ -33,6 +33,7 @@ pub struct RadarrData { pub movie_info_tabs: TabState, pub search: String, pub filter: String, + pub prompt_confirm: bool, pub is_searching: bool, } @@ -88,6 +89,7 @@ impl Default for RadarrData { search: String::default(), filter: String::default(), is_searching: false, + prompt_confirm: false, main_tabs: TabState::new(vec![ TabRoute { title: "Library".to_owned(), @@ -146,6 +148,7 @@ pub enum ActiveRadarrBlock { CollectionDetails, Cast, Crew, + DeleteMoviePrompt, FileInfo, FilterCollections, FilterMovies, @@ -192,6 +195,13 @@ impl App { self .dispatch_network_event(RadarrEvent::GetDownloads.into()) .await; + + if self.data.radarr_data.prompt_confirm { + self.data.radarr_data.prompt_confirm = false; + self + .dispatch_network_event(RadarrEvent::DeleteMovie.into()) + .await; + } } ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::FileInfo => { self.is_loading = true; diff --git a/src/event/key.rs b/src/event/key.rs index 84403fc..78ae188 100644 --- a/src/event/key.rs +++ b/src/event/key.rs @@ -14,6 +14,7 @@ pub enum Key { Backspace, Home, End, + Delete, Char(char), Unknown, } @@ -57,6 +58,10 @@ impl From for Key { code: KeyCode::End, .. } => Key::End, + KeyEvent { + code: KeyCode::Delete, + .. + } => Key::Delete, KeyEvent { code: KeyCode::Enter, .. diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 36e3956..24dc484 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -15,8 +15,9 @@ pub trait KeyEventHandler<'a, T: Into> { _ if *key == DEFAULT_KEYBINDINGS.down.key => self.handle_scroll_down(), _ if *key == DEFAULT_KEYBINDINGS.home.key => self.handle_home(), _ if *key == DEFAULT_KEYBINDINGS.end.key => self.handle_end(), + _ if *key == DEFAULT_KEYBINDINGS.delete.key => self.handle_delete(), _ if *key == DEFAULT_KEYBINDINGS.left.key || *key == DEFAULT_KEYBINDINGS.right.key => { - self.handle_tab_action() + self.handle_left_right_action() } _ if *key == DEFAULT_KEYBINDINGS.submit.key => self.handle_submit(), _ if *key == DEFAULT_KEYBINDINGS.esc.key => self.handle_esc(), @@ -34,7 +35,8 @@ pub trait KeyEventHandler<'a, T: Into> { fn handle_scroll_down(&mut self); fn handle_home(&mut self); fn handle_end(&mut self); - fn handle_tab_action(&mut self); + fn handle_delete(&mut self); + fn handle_left_right_action(&mut self); fn handle_submit(&mut self); fn handle_esc(&mut self); fn handle_char_key_event(&mut self); diff --git a/src/handlers/radarr_handlers/collection_details_handler.rs b/src/handlers/radarr_handlers/collection_details_handler.rs index 3210deb..ac153d0 100644 --- a/src/handlers/radarr_handlers/collection_details_handler.rs +++ b/src/handlers/radarr_handlers/collection_details_handler.rs @@ -56,7 +56,9 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for CollectionDetailsHandler<'a> } } - fn handle_tab_action(&mut self) {} + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) {} fn handle_submit(&mut self) { if ActiveRadarrBlock::CollectionDetails == *self.active_radarr_block { diff --git a/src/handlers/radarr_handlers/mod.rs b/src/handlers/radarr_handlers/mod.rs index c900f88..76fd56f 100644 --- a/src/handlers/radarr_handlers/mod.rs +++ b/src/handlers/radarr_handlers/mod.rs @@ -171,7 +171,15 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { } } - fn handle_tab_action(&mut self) { + fn handle_delete(&mut self) { + if *self.active_radarr_block == ActiveRadarrBlock::Movies { + self + .app + .push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); + } + } + + fn handle_left_right_action(&mut self) { match self.active_radarr_block { ActiveRadarrBlock::Movies | ActiveRadarrBlock::Downloads | ActiveRadarrBlock::Collections => { match self.key { @@ -202,6 +210,14 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { _ => (), } } + ActiveRadarrBlock::DeleteMoviePrompt => match self.key { + _ if *self.key == DEFAULT_KEYBINDINGS.left.key + || *self.key == DEFAULT_KEYBINDINGS.right.key => + { + self.app.data.radarr_data.prompt_confirm = !self.app.data.radarr_data.prompt_confirm; + } + _ => (), + }, _ => (), } } @@ -268,6 +284,10 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { .set_items(filtered_collections); } } + ActiveRadarrBlock::DeleteMoviePrompt => { + self.app.should_refresh = self.app.data.radarr_data.prompt_confirm; + self.app.pop_navigation_stack(); + } _ => (), } } @@ -281,6 +301,10 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { self.app.pop_navigation_stack(); self.app.data.radarr_data.reset_search(); } + ActiveRadarrBlock::DeleteMoviePrompt => { + self.app.pop_navigation_stack(); + self.app.data.radarr_data.prompt_confirm = false; + } _ => { self.app.data.radarr_data.reset_search(); handle_clear_errors(self.app); diff --git a/src/handlers/radarr_handlers/movie_details_handler.rs b/src/handlers/radarr_handlers/movie_details_handler.rs index 0a01d9e..902de40 100644 --- a/src/handlers/radarr_handlers/movie_details_handler.rs +++ b/src/handlers/radarr_handlers/movie_details_handler.rs @@ -68,7 +68,9 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for MovieDetailsHandler<'a> { } } - fn handle_tab_action(&mut self) { + fn handle_delete(&mut self) {} + + fn handle_left_right_action(&mut self) { match self.active_radarr_block { ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::MovieHistory diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 8c33c64..83ffa23 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -1,7 +1,7 @@ use anyhow::anyhow; use indoc::formatdoc; use log::{debug, error}; -use reqwest::RequestBuilder; +use reqwest::{RequestBuilder, StatusCode}; use serde::de::DeserializeOwned; use tokio::sync::MutexGuard; @@ -17,6 +17,7 @@ use crate::utils::{convert_runtime, convert_to_gb}; #[derive(Debug, Eq, PartialEq)] pub enum RadarrEvent { + DeleteMovie, GetCollections, GetDownloads, GetMovies, @@ -29,12 +30,24 @@ pub enum RadarrEvent { HealthCheck, } +#[derive(Clone)] +enum RequestMethod { + GET, + DELETE, +} + +struct RequestProps { + pub resource: String, + pub method: RequestMethod, + pub body: Option, +} + impl RadarrEvent { const fn resource(self) -> &'static str { match self { RadarrEvent::GetCollections => "/collection", RadarrEvent::GetDownloads => "/queue", - RadarrEvent::GetMovies | RadarrEvent::GetMovieDetails => "/movie", + RadarrEvent::GetMovies | RadarrEvent::GetMovieDetails | RadarrEvent::DeleteMovie => "/movie", RadarrEvent::GetMovieCredits => "/credit", RadarrEvent::GetMovieHistory => "/history/movie", RadarrEvent::GetOverview => "/diskspace", @@ -56,86 +69,133 @@ impl<'a> Network<'a> { match radarr_event { RadarrEvent::GetCollections => { self - .get_collections(RadarrEvent::GetCollections.resource()) + .get_collections(RadarrEvent::GetCollections.resource().to_owned()) .await } RadarrEvent::HealthCheck => { self - .get_healthcheck(RadarrEvent::HealthCheck.resource()) + .get_healthcheck(RadarrEvent::HealthCheck.resource().to_owned()) .await } RadarrEvent::GetOverview => { self - .get_diskspace(RadarrEvent::GetOverview.resource()) + .get_diskspace(RadarrEvent::GetOverview.resource().to_owned()) + .await + } + RadarrEvent::GetStatus => { + self + .get_status(RadarrEvent::GetStatus.resource().to_owned()) + .await + } + RadarrEvent::GetMovies => { + self + .get_movies(RadarrEvent::GetMovies.resource().to_owned()) + .await + } + RadarrEvent::DeleteMovie => { + self + .delete_movie(RadarrEvent::DeleteMovie.resource().to_owned()) .await } - RadarrEvent::GetStatus => self.get_status(RadarrEvent::GetStatus.resource()).await, - RadarrEvent::GetMovies => self.get_movies(RadarrEvent::GetMovies.resource()).await, RadarrEvent::GetMovieCredits => { self - .get_credits(RadarrEvent::GetMovieCredits.resource()) + .get_credits(RadarrEvent::GetMovieCredits.resource().to_owned()) .await } RadarrEvent::GetMovieDetails => { self - .get_movie_details(RadarrEvent::GetMovieDetails.resource()) + .get_movie_details(RadarrEvent::GetMovieDetails.resource().to_owned()) .await } RadarrEvent::GetMovieHistory => { self - .get_movie_history(RadarrEvent::GetMovieHistory.resource()) + .get_movie_history(RadarrEvent::GetMovieHistory.resource().to_owned()) .await } RadarrEvent::GetDownloads => { self - .get_downloads(RadarrEvent::GetDownloads.resource()) + .get_downloads(RadarrEvent::GetDownloads.resource().to_owned()) .await } RadarrEvent::GetQualityProfiles => { self - .get_quality_profiles(RadarrEvent::GetQualityProfiles.resource()) + .get_quality_profiles(RadarrEvent::GetQualityProfiles.resource().to_owned()) .await } } } - async fn get_healthcheck(&self, resource: &str) { - if let Err(e) = self.call_radarr_api(resource).await.send().await { + async fn get_healthcheck(&self, resource: String) { + if let Err(e) = self + .call_radarr_api::<()>(RequestProps { + resource, + method: RequestMethod::GET, + body: None::<()>, + }) + .await + .send() + .await + { error!("Healthcheck failed. {:?}", e); self.app.lock().await.handle_error(anyhow!(e)); } } - async fn get_diskspace(&self, resource: &str) { + async fn get_diskspace(&self, resource: String) { + type RequestType = Vec; self - .handle_get_request::>(resource, |disk_space_vec, mut app| { - app.data.radarr_data.disk_space_vec = disk_space_vec; - }) + .handle_request::( + RequestProps { + resource, + method: RequestMethod::GET, + body: None::, + }, + |disk_space_vec, mut app| { + app.data.radarr_data.disk_space_vec = disk_space_vec; + }, + ) .await; } - async fn get_status(&self, resource: &str) { + async fn get_status(&self, resource: String) { self - .handle_get_request::(resource, |system_status, mut app| { - app.data.radarr_data.version = system_status.version; - app.data.radarr_data.start_time = system_status.start_time; - }) + .handle_request::( + RequestProps { + resource, + method: RequestMethod::GET, + body: None::, + }, + |system_status, mut app| { + app.data.radarr_data.version = system_status.version; + app.data.radarr_data.start_time = system_status.start_time; + }, + ) .await; } - async fn get_movies(&self, resource: &str) { + async fn get_movies(&self, resource: String) { + type RequestType = Vec; self - .handle_get_request::>(resource, |movie_vec, mut app| { - app.data.radarr_data.movies.set_items(movie_vec) - }) + .handle_request::( + RequestProps { + resource, + method: RequestMethod::GET, + body: None::, + }, + |movie_vec, mut app| app.data.radarr_data.movies.set_items(movie_vec), + ) .await; } - async fn get_movie_details(&self, resource: &str) { + async fn get_movie_details(&self, resource: String) { let movie_id = self.extract_movie_id().await; self - .handle_get_request::( - format!("{}/{}", resource, movie_id).as_str(), + .handle_request::( + RequestProps { + resource: format!("{}/{}", resource, movie_id), + method: RequestMethod::GET, + body: None::, + }, |movie_response, mut app| { let Movie { id, @@ -277,10 +337,15 @@ impl<'a> Network<'a> { .await; } - async fn get_movie_history(&self, resource: &str) { + async fn get_movie_history(&self, resource: String) { + type RequestType = Vec; self - .handle_get_request::>( - self.append_movie_id_param(resource).await.as_str(), + .handle_request::( + RequestProps { + resource: self.append_movie_id_param(&resource).await, + method: RequestMethod::GET, + body: None::, + }, |movie_history_vec, mut app| { let mut reversed_movie_history_vec = movie_history_vec.to_vec(); reversed_movie_history_vec.reverse(); @@ -294,41 +359,69 @@ impl<'a> Network<'a> { .await; } - async fn get_collections(&self, resource: &str) { + async fn get_collections(&self, resource: String) { + type RequestType = Vec; self - .handle_get_request::>(resource, |collections_vec, mut app| { - app.data.radarr_data.collections.set_items(collections_vec); - }) + .handle_request::( + RequestProps { + resource, + method: RequestMethod::GET, + body: None::, + }, + |collections_vec, mut app| { + app.data.radarr_data.collections.set_items(collections_vec); + }, + ) .await; } - async fn get_downloads(&self, resource: &str) { + async fn get_downloads(&self, resource: String) { self - .handle_get_request::(resource, |queue_response, mut app| { - app - .data - .radarr_data - .downloads - .set_items(queue_response.records); - }) + .handle_request::( + RequestProps { + resource, + method: RequestMethod::GET, + body: None::, + }, + |queue_response, mut app| { + app + .data + .radarr_data + .downloads + .set_items(queue_response.records); + }, + ) .await } - async fn get_quality_profiles(&self, resource: &str) { + async fn get_quality_profiles(&self, resource: String) { + type RequestType = Vec; self - .handle_get_request::>(resource, |quality_profiles, mut app| { - app.data.radarr_data.quality_profile_map = quality_profiles - .iter() - .map(|profile| (profile.id.as_u64().unwrap(), profile.name.clone())) - .collect(); - }) + .handle_request::( + RequestProps { + resource, + method: RequestMethod::GET, + body: None::, + }, + |quality_profiles, mut app| { + app.data.radarr_data.quality_profile_map = quality_profiles + .iter() + .map(|profile| (profile.id.as_u64().unwrap(), profile.name.clone())) + .collect(); + }, + ) .await; } - async fn get_credits(&self, resource: &str) { + async fn get_credits(&self, resource: String) { + type RequestType = Vec; self - .handle_get_request::>( - self.append_movie_id_param(resource).await.as_str(), + .handle_request::( + RequestProps { + resource: self.append_movie_id_param(&resource).await, + method: RequestMethod::GET, + body: None::, + }, |credit_vec, mut app| { let cast_vec: Vec = credit_vec .iter() @@ -348,7 +441,26 @@ impl<'a> Network<'a> { .await; } - async fn call_radarr_api(&self, resource: &str) -> RequestBuilder { + async fn delete_movie(&self, resource: String) { + let movie_id = self.extract_movie_id().await; + self + .handle_request::<()>( + RequestProps { + resource: format!("{}/{}", resource, movie_id), + method: RequestMethod::DELETE, + body: None::<()>, + }, + |_, _| (), + ) + .await; + } + + async fn call_radarr_api(&self, request_props: RequestProps) -> RequestBuilder { + let RequestProps { + resource, + method, + body, + } = request_props; debug!("Creating RequestBuilder for resource: {:?}", resource); let app = self.app.lock().await; let RadarrConfig { @@ -356,38 +468,53 @@ impl<'a> Network<'a> { port, api_token, } = &app.config.radarr; + let uri = format!( + "http://{}:{}/api/v3{}", + host, + port.unwrap_or(7878), + resource + ); - app - .client - .get(format!( - "http://{}:{}/api/v3{}", - host, - port.unwrap_or(7878), - resource - )) - .header("X-Api-Key", api_token) + match method { + RequestMethod::GET => app.client.get(uri).header("X-Api-Key", api_token), + RequestMethod::DELETE => app.client.delete(uri).header("X-Api-Key", api_token), + } } - async fn handle_get_request( + async fn handle_request( &self, - resource: &str, + request_props: RequestProps, mut app_update_fn: impl FnMut(T, MutexGuard), ) where T: DeserializeOwned, { - match self.call_radarr_api(resource).await.send().await { - Ok(response) => match utils::parse_response::(response).await { - Ok(value) => { - 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!(e)); + let method = request_props.method.clone(); + match self.call_radarr_api(request_props).await.send().await { + Ok(response) => match method { + RequestMethod::GET => match utils::parse_response::(response).await { + Ok(value) => { + 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!(e)); + } + }, + RequestMethod::DELETE => { + if response.status() != StatusCode::OK { + error!( + "Received the following code for delete operation: {:?}", + response.status() + ); + self.app.lock().await.handle_error(anyhow!( + "Received a non 200 OK response for delete operation" + )); + } } }, Err(e) => { - error!("Failed to fetch resource. {:?}", e); + error!("Failed to send request. {:?}", e); self.app.lock().await.handle_error(anyhow!(e)); } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 341941e..61338ed 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,8 +1,6 @@ -use std::iter::Map; -use std::slice::Iter; - use tui::backend::Backend; use tui::layout::{Alignment, Constraint, Rect}; +use tui::style::{Modifier, Style}; use tui::text::{Span, Spans, Text}; use tui::widgets::Clear; use tui::widgets::Paragraph; @@ -15,9 +13,10 @@ use tui::Frame; use crate::app::App; use crate::models::{Route, StatefulTable, TabState}; use crate::ui::utils::{ - borderless_block, centered_rect, horizontal_chunks_with_margin, layout_block_top_border, - logo_block, style_default_bold, style_failure, style_help, style_highlight, style_primary, - style_secondary, style_system_function, title_block, vertical_chunks_with_margin, + borderless_block, centered_rect, horizontal_chunks, horizontal_chunks_with_margin, layout_block, + layout_block_top_border, logo_block, style_default_bold, style_failure, style_help, + style_highlight, style_primary, style_secondary, style_system_function, title_block, + vertical_chunks_with_margin, }; mod radarr_ui; @@ -252,3 +251,56 @@ pub fn loading(f: &mut Frame<'_, B>, block: Block<'_>, area: Rect, i f.render_widget(block, area) } } + +pub fn draw_prompt_box( + f: &mut Frame<'_, B>, + prompt_area: Rect, + title: &str, + prompt: &str, + yes_no_value: &bool, +) { + f.render_widget( + title_block(title).title_alignment(Alignment::Center), + prompt_area, + ); + + let chunks = vertical_chunks_with_margin( + vec![ + Constraint::Percentage(72), + Constraint::Min(0), + Constraint::Length(3), + ], + prompt_area, + 1, + ); + + let prompt_paragraph = Paragraph::new(Text::from(prompt)) + .block(borderless_block()) + .style(style_primary().add_modifier(Modifier::BOLD)) + .wrap(Wrap { trim: false }) + .alignment(Alignment::Center); + f.render_widget(prompt_paragraph, chunks[0]); + + let horizontal_chunks = horizontal_chunks( + vec![Constraint::Percentage(50), Constraint::Percentage(50)], + chunks[2], + ); + + draw_button(f, horizontal_chunks[0], "Yes", *yes_no_value); + draw_button(f, horizontal_chunks[1], "No", !*yes_no_value); +} + +pub fn draw_button(f: &mut Frame<'_, B>, area: Rect, label: &str, is_selected: bool) { + let style = if is_selected { + style_system_function().add_modifier(Modifier::BOLD) + } else { + style_default_bold() + }; + + let label_paragraph = Paragraph::new(Text::from(label)) + .block(layout_block()) + .alignment(Alignment::Center) + .style(style); + + f.render_widget(label_paragraph, area); +} diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index a0efe17..821cc3f 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -23,7 +23,8 @@ use crate::ui::utils::{ vertical_chunks_with_margin, }; use crate::ui::{ - draw_large_popup_over, draw_popup_over, draw_table, draw_tabs, loading, TableProps, + draw_large_popup_over, draw_popup_over, draw_prompt_box, draw_table, draw_tabs, loading, + TableProps, }; use crate::utils::{convert_runtime, convert_to_gb}; @@ -68,6 +69,15 @@ pub(super) fn draw_radarr_ui(f: &mut Frame<'_, B>, app: &mut App, ar draw_collection_details_popup, ) } + ActiveRadarrBlock::DeleteMoviePrompt => draw_popup_over( + f, + app, + content_rect, + draw_library, + draw_delete_movie_prompt, + 30, + 30, + ), _ => (), } } @@ -141,6 +151,20 @@ fn draw_library(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { ); } +fn draw_delete_movie_prompt(f: &mut Frame<'_, B>, app: &mut App, prompt_area: Rect) { + draw_prompt_box( + f, + prompt_area, + " Confirm Delete Movie? ", + format!( + "Do you really want to delete {}?", + app.data.radarr_data.movies.current_selection().title + ) + .as_str(), + &app.data.radarr_data.prompt_confirm, + ); +} + fn draw_search_box(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { let chunks = vertical_chunks_with_margin(vec![Constraint::Length(3)], area, 1); if !app.data.radarr_data.is_searching { diff --git a/src/ui/utils.rs b/src/ui/utils.rs index 97a10f0..a7d5655 100644 --- a/src/ui/utils.rs +++ b/src/ui/utils.rs @@ -153,7 +153,7 @@ pub fn title_block(title: &str) -> Block<'_> { } pub fn logo_block<'a>() -> Block<'a> { - Block::default().borders(Borders::ALL).title(Span::styled( + layout_block().title(Span::styled( " Managarr - A Servarr management TUI ", Style::default() .fg(Color::Magenta)