diff --git a/src/app/radarr.rs b/src/app/radarr.rs index 2b4551c..c0e0a43 100644 --- a/src/app/radarr.rs +++ b/src/app/radarr.rs @@ -405,7 +405,7 @@ pub enum ActiveRadarrBlock { ViewMovieOverview, } -pub const ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 10] = [ +pub static ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 10] = [ ActiveRadarrBlock::AddMovieSearchInput, ActiveRadarrBlock::AddMovieSearchResults, ActiveRadarrBlock::AddMovieEmptySearchResults, @@ -417,7 +417,7 @@ pub const ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 10] = [ ActiveRadarrBlock::AddMovieAlreadyInLibrary, ActiveRadarrBlock::AddMovieTagsInput, ]; -pub const ADD_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ +pub static ADD_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ ActiveRadarrBlock::AddMovieSelectRootFolder, ActiveRadarrBlock::AddMovieSelectMonitor, ActiveRadarrBlock::AddMovieSelectMinimumAvailability, @@ -425,7 +425,7 @@ pub const ADD_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ ActiveRadarrBlock::AddMovieTagsInput, ActiveRadarrBlock::AddMovieConfirmPrompt, ]; -pub const EDIT_COLLECTION_BLOCKS: [ActiveRadarrBlock; 7] = [ +pub static EDIT_COLLECTION_BLOCKS: [ActiveRadarrBlock; 7] = [ ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionConfirmPrompt, ActiveRadarrBlock::EditCollectionRootFolderPathInput, @@ -434,7 +434,7 @@ pub const EDIT_COLLECTION_BLOCKS: [ActiveRadarrBlock; 7] = [ ActiveRadarrBlock::EditCollectionToggleSearchOnAdd, ActiveRadarrBlock::EditCollectionToggleMonitored, ]; -pub const EDIT_COLLECTION_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ +pub static EDIT_COLLECTION_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ ActiveRadarrBlock::EditCollectionToggleMonitored, ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, ActiveRadarrBlock::EditCollectionSelectQualityProfile, @@ -442,7 +442,7 @@ pub const EDIT_COLLECTION_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ ActiveRadarrBlock::EditCollectionToggleSearchOnAdd, ActiveRadarrBlock::EditCollectionConfirmPrompt, ]; -pub const EDIT_MOVIE_BLOCKS: [ActiveRadarrBlock; 7] = [ +pub static EDIT_MOVIE_BLOCKS: [ActiveRadarrBlock; 7] = [ ActiveRadarrBlock::EditMoviePrompt, ActiveRadarrBlock::EditMovieConfirmPrompt, ActiveRadarrBlock::EditMoviePathInput, @@ -451,7 +451,7 @@ pub const EDIT_MOVIE_BLOCKS: [ActiveRadarrBlock; 7] = [ ActiveRadarrBlock::EditMovieTagsInput, ActiveRadarrBlock::EditMovieToggleMonitored, ]; -pub const EDIT_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ +pub static EDIT_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ ActiveRadarrBlock::EditMovieToggleMonitored, ActiveRadarrBlock::EditMovieSelectMinimumAvailability, ActiveRadarrBlock::EditMovieSelectQualityProfile, @@ -459,7 +459,7 @@ pub const EDIT_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ ActiveRadarrBlock::EditMovieTagsInput, ActiveRadarrBlock::EditMovieConfirmPrompt, ]; -pub const MOVIE_DETAILS_BLOCKS: [ActiveRadarrBlock; 10] = [ +pub static MOVIE_DETAILS_BLOCKS: [ActiveRadarrBlock; 10] = [ ActiveRadarrBlock::MovieDetails, ActiveRadarrBlock::MovieHistory, ActiveRadarrBlock::FileInfo, @@ -471,25 +471,25 @@ pub const MOVIE_DETAILS_BLOCKS: [ActiveRadarrBlock; 10] = [ ActiveRadarrBlock::ManualSearchSortPrompt, ActiveRadarrBlock::ManualSearchConfirmPrompt, ]; -pub const COLLECTION_DETAILS_BLOCKS: [ActiveRadarrBlock; 2] = [ +pub static COLLECTION_DETAILS_BLOCKS: [ActiveRadarrBlock; 2] = [ ActiveRadarrBlock::CollectionDetails, ActiveRadarrBlock::ViewMovieOverview, ]; -pub const SEARCH_BLOCKS: [ActiveRadarrBlock; 2] = [ +pub static SEARCH_BLOCKS: [ActiveRadarrBlock; 2] = [ ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchCollection, ]; -pub const FILTER_BLOCKS: [ActiveRadarrBlock; 2] = [ +pub static FILTER_BLOCKS: [ActiveRadarrBlock; 2] = [ ActiveRadarrBlock::FilterMovies, ActiveRadarrBlock::FilterCollections, ]; -pub const DELETE_MOVIE_BLOCKS: [ActiveRadarrBlock; 4] = [ +pub static DELETE_MOVIE_BLOCKS: [ActiveRadarrBlock; 4] = [ ActiveRadarrBlock::DeleteMoviePrompt, ActiveRadarrBlock::DeleteMovieConfirmPrompt, ActiveRadarrBlock::DeleteMovieToggleDeleteFile, ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, ]; -pub const DELETE_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 3] = [ +pub static DELETE_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 3] = [ ActiveRadarrBlock::DeleteMovieToggleDeleteFile, ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, ActiveRadarrBlock::DeleteMovieConfirmPrompt, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 4697232..3544cf9 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -15,6 +15,7 @@ use tui::Frame; use crate::app::App; use crate::models::{Route, StatefulList, StatefulTable, TabState}; +use crate::ui::radarr_ui::RadarrUi; use crate::ui::utils::{ background_block, borderless_block, centered_rect, horizontal_chunks, horizontal_chunks_with_margin, layout_block, layout_block_top_border, layout_button_paragraph, @@ -29,6 +30,11 @@ mod utils; static HIGHLIGHT_SYMBOL: &str = "=> "; +pub trait DrawUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect); + fn draw_context_row(_f: &mut Frame<'_, B>, _app: &App<'_>, _area: Rect) {} +} + pub fn ui(f: &mut Frame<'_, B>, app: &mut App<'_>) { f.render_widget(background_block(), f.size()); let main_chunks = if !app.error.text.is_empty() { @@ -59,9 +65,10 @@ pub fn ui(f: &mut Frame<'_, B>, app: &mut App<'_>) { }; draw_header_row(f, app, main_chunks[0]); - draw_context_row(f, app, main_chunks[1]); - if let Route::Radarr(_, _) = app.get_current_route() { - radarr_ui::draw_radarr_ui(f, app, main_chunks[2]); + + if let Route::Radarr(_, _) = *app.get_current_route() { + RadarrUi::draw_context_row(f, app, main_chunks[1]); + RadarrUi::draw(f, app, main_chunks[2]); } } @@ -113,7 +120,7 @@ fn draw_error(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { pub fn draw_popup( f: &mut Frame<'_, B>, app: &mut App<'_>, - popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), percent_x: u16, percent_y: u16, ) { @@ -123,12 +130,24 @@ pub fn draw_popup( popup_fn(f, app, popup_area); } +pub fn draw_popup_ui( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + percent_x: u16, + percent_y: u16, +) { + let popup_area = centered_rect(percent_x, percent_y, f.size()); + f.render_widget(Clear, popup_area); + f.render_widget(background_block(), popup_area); + T::draw(f, app, popup_area); +} + pub fn draw_popup_over( f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), percent_x: u16, percent_y: u16, ) { @@ -137,12 +156,25 @@ pub fn draw_popup_over( draw_popup(f, app, popup_fn, percent_x, percent_y); } +pub fn draw_popup_over_ui( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + area: Rect, + background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + percent_x: u16, + percent_y: u16, +) { + background_fn(f, app, area); + + draw_popup_ui::(f, app, percent_x, percent_y); +} + pub fn draw_prompt_popup_over( f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 35, 35); } @@ -151,8 +183,8 @@ pub fn draw_small_popup_over( f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 40, 40); } @@ -161,8 +193,8 @@ pub fn draw_medium_popup_over( f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 60, 60); } @@ -171,28 +203,31 @@ pub fn draw_large_popup_over( f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 75, 75); } +pub fn draw_large_popup_over_ui( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + area: Rect, + background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), +) { + draw_popup_over_ui::(f, app, area, background_fn, 75, 75); +} + pub fn draw_drop_down_popup( f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), - drop_down_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + background_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + drop_down_fn: impl Fn(&mut Frame<'_, B>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, drop_down_fn, 20, 30); } -fn draw_context_row(f: &mut Frame<'_, B>, app: &App<'_>, area: Rect) { - if let Route::Radarr(_, _) = app.get_current_route() { - radarr_ui::draw_radarr_context_row(f, app, area) - } -} - pub fn draw_error_popup_over( f: &mut Frame<'_, B>, app: &mut App<'_>, diff --git a/src/ui/radarr_ui/add_movie_ui.rs b/src/ui/radarr_ui/add_movie_ui.rs index 4fa21ba..cd1f798 100644 --- a/src/ui/radarr_ui/add_movie_ui.rs +++ b/src/ui/radarr_ui/add_movie_ui.rs @@ -4,10 +4,12 @@ use tui::text::Text; use tui::widgets::{Cell, ListItem, Paragraph, Row}; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS}; use crate::models::radarr_models::AddMovieSearchResult; use crate::models::Route; use crate::ui::radarr_ui::collection_details_ui::draw_collection_details; +use crate::ui::radarr_ui::collections_ui::draw_collections; +use crate::ui::radarr_ui::library_ui::draw_library; use crate::ui::radarr_ui::{ draw_select_minimum_availability_popup, draw_select_quality_profile_popup, draw_select_root_folder_popup, @@ -19,50 +21,74 @@ use crate::ui::utils::{ }; use crate::ui::{ draw_button, draw_drop_down_list, draw_drop_down_menu_button, draw_drop_down_popup, - draw_error_popup, draw_error_popup_over, draw_medium_popup_over, draw_table, draw_text_box, - draw_text_box_with_label, TableProps, + draw_error_popup, draw_error_popup_over, draw_large_popup_over, draw_medium_popup_over, + draw_table, draw_text_box, draw_text_box_with_label, DrawUi, TableProps, }; use crate::utils::convert_runtime; use crate::App; -pub(super) fn draw_add_movie_search_popup( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - area: Rect, -) { - if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { - match active_radarr_block { - ActiveRadarrBlock::AddMovieSearchInput - | ActiveRadarrBlock::AddMovieSearchResults - | ActiveRadarrBlock::AddMovieEmptySearchResults => { - draw_add_movie_search(f, app, area); - } - ActiveRadarrBlock::AddMoviePrompt - | ActiveRadarrBlock::AddMovieSelectMonitor - | ActiveRadarrBlock::AddMovieSelectMinimumAvailability - | ActiveRadarrBlock::AddMovieSelectQualityProfile - | ActiveRadarrBlock::AddMovieSelectRootFolder - | ActiveRadarrBlock::AddMovieTagsInput => { - if context_option.is_some() { - draw_medium_popup_over( +pub(super) struct AddMoviesUi {} + +impl DrawUi for AddMoviesUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { + let draw_add_movie_search_popup = + |f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect| match active_radarr_block { + ActiveRadarrBlock::AddMovieSearchInput + | ActiveRadarrBlock::AddMovieSearchResults + | ActiveRadarrBlock::AddMovieEmptySearchResults => { + draw_add_movie_search(f, app, area); + } + ActiveRadarrBlock::AddMoviePrompt + | ActiveRadarrBlock::AddMovieSelectMonitor + | ActiveRadarrBlock::AddMovieSelectMinimumAvailability + | ActiveRadarrBlock::AddMovieSelectQualityProfile + | ActiveRadarrBlock::AddMovieSelectRootFolder + | ActiveRadarrBlock::AddMovieTagsInput => { + if context_option.is_some() { + draw_medium_popup_over( + f, + app, + area, + draw_collection_details, + draw_confirmation_popup, + ); + } else { + draw_medium_popup_over(f, app, area, draw_add_movie_search, draw_confirmation_popup); + } + } + ActiveRadarrBlock::AddMovieAlreadyInLibrary => draw_error_popup_over( f, app, area, - draw_collection_details, - draw_confirmation_popup, - ); - } else { - draw_medium_popup_over(f, app, area, draw_add_movie_search, draw_confirmation_popup); + "This film is already in your library", + draw_add_movie_search, + ), + _ => (), + }; + + match active_radarr_block { + _ if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) => { + if context_option.is_some() { + draw_large_popup_over( + f, + app, + content_rect, + draw_collections, + draw_add_movie_search_popup, + ) + } else { + draw_large_popup_over( + f, + app, + content_rect, + draw_library, + draw_add_movie_search_popup, + ) + } } + _ => (), } - ActiveRadarrBlock::AddMovieAlreadyInLibrary => draw_error_popup_over( - f, - app, - area, - "This film is already in your library", - draw_add_movie_search, - ), - _ => (), } } } diff --git a/src/ui/radarr_ui/collection_details_ui.rs b/src/ui/radarr_ui/collection_details_ui.rs index 12c9a83..ca3c14f 100644 --- a/src/ui/radarr_ui/collection_details_ui.rs +++ b/src/ui/radarr_ui/collection_details_ui.rs @@ -8,32 +8,44 @@ use crate::app::radarr::ActiveRadarrBlock; use crate::app::App; use crate::models::radarr_models::CollectionMovie; use crate::models::Route; +use crate::ui::radarr_ui::collections_ui::draw_collections; use crate::ui::utils::{ borderless_block, get_width_from_percentage, layout_block_top_border_with_title, spans_info_primary, style_default, style_help, style_primary, title_block, title_style, vertical_chunks_with_margin, }; -use crate::ui::{draw_small_popup_over, draw_table, TableProps}; +use crate::ui::{draw_large_popup_over, draw_small_popup_over, draw_table, DrawUi, TableProps}; use crate::utils::convert_runtime; -pub(super) fn draw_collection_details_popup( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - content_area: Rect, -) { - if let Route::Radarr(active_radarr_block, context_option) = app.get_current_route() { - match context_option.as_ref().unwrap_or(active_radarr_block) { - ActiveRadarrBlock::ViewMovieOverview => { - draw_small_popup_over( - f, - app, - content_area, - draw_collection_details, - draw_movie_overview, - ); - } - ActiveRadarrBlock::CollectionDetails => draw_collection_details(f, app, content_area), - _ => (), +pub(super) struct CollectionDetailsUi {} + +impl DrawUi for CollectionDetailsUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { + let draw_collection_details_popup = + |f: &mut Frame<'_, B>, app: &mut App<'_>, popup_area: Rect| match context_option + .unwrap_or(active_radarr_block) + { + ActiveRadarrBlock::ViewMovieOverview => { + draw_small_popup_over( + f, + app, + popup_area, + draw_collection_details, + draw_movie_overview, + ); + } + ActiveRadarrBlock::CollectionDetails => draw_collection_details(f, app, popup_area), + _ => (), + }; + + draw_large_popup_over( + f, + app, + content_rect, + draw_collections, + draw_collection_details_popup, + ); } } } diff --git a/src/ui/radarr_ui/collections_ui.rs b/src/ui/radarr_ui/collections_ui.rs new file mode 100644 index 0000000..f0f0af9 --- /dev/null +++ b/src/ui/radarr_ui/collections_ui.rs @@ -0,0 +1,148 @@ +use tui::backend::Backend; +use tui::layout::{Constraint, Rect}; +use tui::widgets::{Cell, Row}; +use tui::Frame; + +use crate::app::radarr::ActiveRadarrBlock; +use crate::app::App; +use crate::models::radarr_models::Collection; +use crate::models::Route; +use crate::ui::radarr_ui::{draw_filter_box, draw_search_box}; +use crate::ui::utils::{get_width_from_percentage, layout_block_top_border, style_primary}; +use crate::ui::{ + draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps, +}; + +pub(super) struct CollectionsUi {} + +impl DrawUi for CollectionsUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { + match active_radarr_block { + ActiveRadarrBlock::Collections => draw_collections(f, app, content_rect), + ActiveRadarrBlock::SearchCollection => draw_popup_over( + f, + app, + content_rect, + draw_collections, + draw_search_box, + 30, + 11, + ), + ActiveRadarrBlock::FilterCollections => draw_popup_over( + f, + app, + content_rect, + draw_collections, + draw_filter_box, + 30, + 11, + ), + ActiveRadarrBlock::UpdateAllCollectionsPrompt => draw_prompt_popup_over( + f, + app, + content_rect, + draw_collections, + draw_update_all_collections_prompt, + ), + _ => (), + } + } + } +} + +pub(super) fn draw_collections(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + let current_selection = if !app.data.radarr_data.filtered_collections.items.is_empty() { + app + .data + .radarr_data + .filtered_collections + .current_selection() + .clone() + } else if !app.data.radarr_data.collections.items.is_empty() { + app.data.radarr_data.collections.current_selection().clone() + } else { + Collection::default() + }; + let quality_profile_map = &app.data.radarr_data.quality_profile_map; + let content = if !app.data.radarr_data.filtered_collections.items.is_empty() + && !app.data.radarr_data.is_filtering + { + &mut app.data.radarr_data.filtered_collections + } else { + &mut app.data.radarr_data.collections + }; + draw_table( + f, + area, + layout_block_top_border(), + TableProps { + content, + table_headers: vec![ + "Collection", + "Number of Movies", + "Root Folder Path", + "Quality Profile", + "Search on Add", + "Monitored", + ], + constraints: vec![ + Constraint::Percentage(25), + Constraint::Percentage(15), + Constraint::Percentage(15), + Constraint::Percentage(15), + Constraint::Percentage(15), + Constraint::Percentage(15), + ], + help: app + .data + .radarr_data + .main_tabs + .get_active_tab_contextual_help(), + }, + |collection| { + let number_of_movies = collection.movies.clone().unwrap_or_default().len(); + collection.title.scroll_left_or_reset( + get_width_from_percentage(area, 25), + *collection == current_selection, + app.tick_count % app.ticks_until_scroll == 0, + ); + let monitored = if collection.monitored { "🏷" } else { "" }; + let search_on_add = if collection.search_on_add { + "Yes" + } else { + "No" + }; + + Row::new(vec![ + Cell::from(collection.title.to_string()), + Cell::from(number_of_movies.to_string()), + Cell::from(collection.root_folder_path.clone().unwrap_or_default()), + Cell::from( + quality_profile_map + .get_by_left(&collection.quality_profile_id.as_u64().unwrap()) + .unwrap() + .to_owned(), + ), + Cell::from(search_on_add), + Cell::from(monitored), + ]) + .style(style_primary()) + }, + app.is_loading, + ); +} + +fn draw_update_all_collections_prompt( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + prompt_area: Rect, +) { + draw_prompt_box( + f, + prompt_area, + "Update All Collections", + "Do you want to update all of your collections?", + app.data.radarr_data.prompt_confirm, + ); +} diff --git a/src/ui/radarr_ui/delete_movie_ui.rs b/src/ui/radarr_ui/delete_movie_ui.rs index 8aa0854..5f57eb2 100644 --- a/src/ui/radarr_ui/delete_movie_ui.rs +++ b/src/ui/radarr_ui/delete_movie_ui.rs @@ -5,41 +5,47 @@ use tui::Frame; use crate::app::radarr::ActiveRadarrBlock; use crate::app::App; use crate::models::Route; -use crate::ui::draw_prompt_box_with_checkboxes; +use crate::ui::radarr_ui::library_ui::draw_library; +use crate::ui::{draw_prompt_box_with_checkboxes, draw_prompt_popup_over, DrawUi}; -pub(super) fn draw_delete_movie_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { - if matches!( - *app.get_current_route(), - Route::Radarr(ActiveRadarrBlock::DeleteMoviePrompt, _) - ) { - let selected_block = app.data.radarr_data.selected_block.get_active_block(); - draw_prompt_box_with_checkboxes( - f, - prompt_area, - "Delete Movie", - format!( - "Do you really want to delete: {}?", - app.data.radarr_data.movies.current_selection().title - ) - .as_str(), - vec![ - ( - "Delete Movie Files", - app.data.radarr_data.delete_movie_files, - selected_block == &ActiveRadarrBlock::DeleteMovieToggleDeleteFile, - ), - ( - "Add List Exclusion", - app.data.radarr_data.add_list_exclusion, - selected_block == &ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, - ), - ], - selected_block == &ActiveRadarrBlock::DeleteMovieConfirmPrompt, - app.data.radarr_data.prompt_confirm, - ) +pub(super) struct DeleteMovieUi {} + +impl DrawUi for DeleteMovieUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + if matches!( + *app.get_current_route(), + Route::Radarr(ActiveRadarrBlock::DeleteMoviePrompt, _) + ) { + let draw_delete_movie_prompt = + |f: &mut Frame<'_, B>, app: &mut App<'_>, prompt_area: Rect| { + let selected_block = app.data.radarr_data.selected_block.get_active_block(); + draw_prompt_box_with_checkboxes( + f, + prompt_area, + "Delete Movie", + format!( + "Do you really want to delete: {}?", + app.data.radarr_data.movies.current_selection().title + ) + .as_str(), + vec![ + ( + "Delete Movie Files", + app.data.radarr_data.delete_movie_files, + selected_block == &ActiveRadarrBlock::DeleteMovieToggleDeleteFile, + ), + ( + "Add List Exclusion", + app.data.radarr_data.add_list_exclusion, + selected_block == &ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, + ), + ], + selected_block == &ActiveRadarrBlock::DeleteMovieConfirmPrompt, + app.data.radarr_data.prompt_confirm, + ) + }; + + draw_prompt_popup_over(f, app, content_rect, draw_library, draw_delete_movie_prompt); + } } } diff --git a/src/ui/radarr_ui/downloads_ui.rs b/src/ui/radarr_ui/downloads_ui.rs new file mode 100644 index 0000000..0115e89 --- /dev/null +++ b/src/ui/radarr_ui/downloads_ui.rs @@ -0,0 +1,147 @@ +use tui::backend::Backend; +use tui::layout::{Constraint, Rect}; +use tui::widgets::{Cell, Row}; +use tui::Frame; + +use crate::app::radarr::ActiveRadarrBlock; +use crate::app::App; +use crate::models::radarr_models::DownloadRecord; +use crate::models::{HorizontallyScrollableText, Route}; +use crate::ui::utils::{get_width_from_percentage, layout_block_top_border, style_primary}; +use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps}; +use crate::utils::convert_to_gb; + +pub(super) struct DownloadsUi {} + +impl DrawUi for DownloadsUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { + match active_radarr_block { + ActiveRadarrBlock::Downloads => draw_downloads(f, app, content_rect), + ActiveRadarrBlock::DeleteDownloadPrompt => draw_prompt_popup_over( + f, + app, + content_rect, + draw_downloads, + draw_delete_download_prompt, + ), + ActiveRadarrBlock::UpdateDownloadsPrompt => draw_prompt_popup_over( + f, + app, + content_rect, + draw_downloads, + draw_update_downloads_prompt, + ), + _ => (), + } + } + } +} + +fn draw_downloads(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + let current_selection = if app.data.radarr_data.downloads.items.is_empty() { + DownloadRecord::default() + } else { + app.data.radarr_data.downloads.current_selection().clone() + }; + + draw_table( + f, + area, + layout_block_top_border(), + TableProps { + content: &mut app.data.radarr_data.downloads, + table_headers: vec![ + "Title", + "Percent Complete", + "Size", + "Output Path", + "Indexer", + "Download Client", + ], + constraints: vec![ + Constraint::Percentage(30), + Constraint::Percentage(11), + Constraint::Percentage(11), + Constraint::Percentage(18), + Constraint::Percentage(17), + Constraint::Percentage(13), + ], + help: app + .data + .radarr_data + .main_tabs + .get_active_tab_contextual_help(), + }, + |download_record| { + let DownloadRecord { + title, + size, + sizeleft, + download_client, + indexer, + output_path, + .. + } = download_record; + + if matches!(output_path, Some(_)) { + output_path.as_ref().unwrap().scroll_left_or_reset( + get_width_from_percentage(area, 18), + current_selection == *download_record, + app.tick_count % app.ticks_until_scroll == 0, + ); + } + + let percent = 1f64 - (sizeleft.as_f64().unwrap() / size.as_f64().unwrap()); + let file_size: f64 = convert_to_gb(size.as_u64().unwrap()); + + Row::new(vec![ + Cell::from(title.to_owned()), + Cell::from(format!("{:.0}%", percent * 100.0)), + Cell::from(format!("{:.2} GB", file_size)), + Cell::from( + output_path + .as_ref() + .unwrap_or(&HorizontallyScrollableText::default()) + .to_string(), + ), + Cell::from(indexer.to_owned()), + Cell::from(download_client.to_owned()), + ]) + .style(style_primary()) + }, + app.is_loading, + ); +} + +fn draw_delete_download_prompt( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + prompt_area: Rect, +) { + draw_prompt_box( + f, + prompt_area, + "Cancel Download", + format!( + "Do you really want to delete this download: {}?", + app.data.radarr_data.downloads.current_selection().title + ) + .as_str(), + app.data.radarr_data.prompt_confirm, + ); +} + +fn draw_update_downloads_prompt( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + prompt_area: Rect, +) { + draw_prompt_box( + f, + prompt_area, + "Update Downloads", + "Do you want to update your downloads?", + app.data.radarr_data.prompt_confirm, + ); +} diff --git a/src/ui/radarr_ui/edit_collection_ui.rs b/src/ui/radarr_ui/edit_collection_ui.rs index 9fe2977..24c26eb 100644 --- a/src/ui/radarr_ui/edit_collection_ui.rs +++ b/src/ui/radarr_ui/edit_collection_ui.rs @@ -2,9 +2,11 @@ use tui::backend::Backend; use tui::layout::{Constraint, Rect}; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS}; use crate::app::App; use crate::models::Route; +use crate::ui::radarr_ui::collection_details_ui::CollectionDetailsUi; +use crate::ui::radarr_ui::collections_ui::draw_collections; use crate::ui::radarr_ui::{ draw_select_minimum_availability_popup, draw_select_quality_profile_popup, }; @@ -13,41 +15,64 @@ use crate::ui::utils::{ }; use crate::ui::{ draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup, - draw_text_box_with_label, + draw_large_popup_over_ui, draw_medium_popup_over, draw_popup, draw_text_box_with_label, DrawUi, }; -pub(super) fn draw_edit_collection_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { - if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { - match active_radarr_block { - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability => { - draw_drop_down_popup( - f, - app, - prompt_area, - draw_edit_collection_confirmation_prompt, - draw_select_minimum_availability_popup, - ); +pub(super) struct EditCollectionUi {} + +impl DrawUi for EditCollectionUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { + let draw_edit_collection_prompt = + |f: &mut Frame<'_, B>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block { + ActiveRadarrBlock::EditCollectionSelectMinimumAvailability => { + draw_drop_down_popup( + f, + app, + prompt_area, + draw_edit_collection_confirmation_prompt, + draw_select_minimum_availability_popup, + ); + } + ActiveRadarrBlock::EditCollectionSelectQualityProfile => { + draw_drop_down_popup( + f, + app, + prompt_area, + draw_edit_collection_confirmation_prompt, + draw_select_quality_profile_popup, + ); + } + ActiveRadarrBlock::EditCollectionPrompt + | ActiveRadarrBlock::EditCollectionToggleMonitored + | ActiveRadarrBlock::EditCollectionRootFolderPathInput + | ActiveRadarrBlock::EditCollectionToggleSearchOnAdd => { + draw_edit_collection_confirmation_prompt(f, app, prompt_area) + } + _ => (), + }; + + if let Some(context) = context_option { + match context { + ActiveRadarrBlock::Collections => draw_medium_popup_over( + f, + app, + content_rect, + draw_collections, + draw_edit_collection_prompt, + ), + _ if COLLECTION_DETAILS_BLOCKS.contains(&context) => { + draw_large_popup_over_ui::( + f, + app, + content_rect, + draw_collections, + ); + draw_popup(f, app, draw_edit_collection_prompt, 60, 60); + } + _ => (), + } } - ActiveRadarrBlock::EditCollectionSelectQualityProfile => { - draw_drop_down_popup( - f, - app, - prompt_area, - draw_edit_collection_confirmation_prompt, - draw_select_quality_profile_popup, - ); - } - ActiveRadarrBlock::EditCollectionPrompt - | ActiveRadarrBlock::EditCollectionToggleMonitored - | ActiveRadarrBlock::EditCollectionRootFolderPathInput - | ActiveRadarrBlock::EditCollectionToggleSearchOnAdd => { - draw_edit_collection_confirmation_prompt(f, app, prompt_area) - } - _ => (), } } } diff --git a/src/ui/radarr_ui/edit_movie_ui.rs b/src/ui/radarr_ui/edit_movie_ui.rs index b906448..2db9424 100644 --- a/src/ui/radarr_ui/edit_movie_ui.rs +++ b/src/ui/radarr_ui/edit_movie_ui.rs @@ -2,9 +2,11 @@ use tui::backend::Backend; use tui::layout::{Constraint, Rect}; use tui::Frame; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS}; use crate::app::App; use crate::models::Route; +use crate::ui::radarr_ui::library_ui::draw_library; +use crate::ui::radarr_ui::movie_details_ui::MovieDetailsUi; use crate::ui::radarr_ui::{ draw_select_minimum_availability_popup, draw_select_quality_profile_popup, }; @@ -13,41 +15,55 @@ use crate::ui::utils::{ }; use crate::ui::{ draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup, - draw_text_box_with_label, + draw_large_popup_over_ui, draw_medium_popup_over, draw_popup, draw_text_box_with_label, DrawUi, }; -pub(super) fn draw_edit_movie_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { - if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { - match active_radarr_block { - ActiveRadarrBlock::EditMovieSelectMinimumAvailability => { - draw_drop_down_popup( - f, - app, - prompt_area, - draw_edit_movie_confirmation_prompt, - draw_select_minimum_availability_popup, - ); +pub(super) struct EditMovieUi {} + +impl DrawUi for EditMovieUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { + let draw_edit_movie_prompt = + |f: &mut Frame<'_, B>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block { + ActiveRadarrBlock::EditMovieSelectMinimumAvailability => { + draw_drop_down_popup( + f, + app, + prompt_area, + draw_edit_movie_confirmation_prompt, + draw_select_minimum_availability_popup, + ); + } + ActiveRadarrBlock::EditMovieSelectQualityProfile => { + draw_drop_down_popup( + f, + app, + prompt_area, + draw_edit_movie_confirmation_prompt, + draw_select_quality_profile_popup, + ); + } + ActiveRadarrBlock::EditMoviePrompt + | ActiveRadarrBlock::EditMovieToggleMonitored + | ActiveRadarrBlock::EditMoviePathInput + | ActiveRadarrBlock::EditMovieTagsInput => { + draw_edit_movie_confirmation_prompt(f, app, prompt_area) + } + _ => (), + }; + + if let Some(context) = context_option { + match context { + ActiveRadarrBlock::Movies => { + draw_medium_popup_over(f, app, content_rect, draw_library, draw_edit_movie_prompt); + } + _ if MOVIE_DETAILS_BLOCKS.contains(&context) => { + draw_large_popup_over_ui::(f, app, content_rect, draw_library); + draw_popup(f, app, draw_edit_movie_prompt, 60, 60); + } + _ => (), + } } - ActiveRadarrBlock::EditMovieSelectQualityProfile => { - draw_drop_down_popup( - f, - app, - prompt_area, - draw_edit_movie_confirmation_prompt, - draw_select_quality_profile_popup, - ); - } - ActiveRadarrBlock::EditMoviePrompt - | ActiveRadarrBlock::EditMovieToggleMonitored - | ActiveRadarrBlock::EditMoviePathInput - | ActiveRadarrBlock::EditMovieTagsInput => { - draw_edit_movie_confirmation_prompt(f, app, prompt_area) - } - _ => (), } } } diff --git a/src/ui/radarr_ui/library_ui.rs b/src/ui/radarr_ui/library_ui.rs new file mode 100644 index 0000000..930a4ea --- /dev/null +++ b/src/ui/radarr_ui/library_ui.rs @@ -0,0 +1,159 @@ +use tui::backend::Backend; +use tui::layout::{Constraint, Rect}; +use tui::widgets::{Cell, Row}; +use tui::Frame; + +use crate::app::radarr::ActiveRadarrBlock; +use crate::app::App; +use crate::models::radarr_models::Movie; +use crate::models::Route; +use crate::ui::radarr_ui::{determine_row_style, draw_filter_box, draw_search_box}; +use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; +use crate::ui::{ + draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps, +}; +use crate::utils::{convert_runtime, convert_to_gb}; + +pub(super) struct LibraryUi {} + +impl DrawUi for LibraryUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + if let Route::Radarr(active_radarr_block, _) = app.get_current_route() { + match active_radarr_block { + ActiveRadarrBlock::Movies => draw_library(f, app, content_rect), + ActiveRadarrBlock::SearchMovie => { + draw_popup_over(f, app, content_rect, draw_library, draw_search_box, 30, 11) + } + ActiveRadarrBlock::FilterMovies => { + draw_popup_over(f, app, content_rect, draw_library, draw_filter_box, 30, 11) + } + ActiveRadarrBlock::UpdateAllMoviesPrompt => draw_prompt_popup_over( + f, + app, + content_rect, + draw_library, + draw_update_all_movies_prompt, + ), + _ => (), + } + } + } +} + +pub(super) fn draw_library(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + let current_selection = if !app.data.radarr_data.filtered_movies.items.is_empty() { + app + .data + .radarr_data + .filtered_movies + .current_selection() + .clone() + } else if !app.data.radarr_data.movies.items.is_empty() { + app.data.radarr_data.movies.current_selection().clone() + } else { + Movie::default() + }; + 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 content = if !app.data.radarr_data.filtered_movies.items.is_empty() + && !app.data.radarr_data.is_filtering + { + &mut app.data.radarr_data.filtered_movies + } else { + &mut app.data.radarr_data.movies + }; + + draw_table( + f, + area, + layout_block_top_border(), + TableProps { + content, + table_headers: vec![ + "Title", + "Year", + "Studio", + "Runtime", + "Rating", + "Language", + "Size", + "Quality Profile", + "Monitored", + "Tags", + ], + constraints: vec![ + Constraint::Percentage(27), + Constraint::Percentage(4), + Constraint::Percentage(17), + Constraint::Percentage(6), + Constraint::Percentage(6), + Constraint::Percentage(6), + Constraint::Percentage(6), + Constraint::Percentage(10), + Constraint::Percentage(6), + Constraint::Percentage(12), + ], + help: app + .data + .radarr_data + .main_tabs + .get_active_tab_contextual_help(), + }, + |movie| { + movie.title.scroll_left_or_reset( + get_width_from_percentage(area, 27), + *movie == current_selection, + app.tick_count % app.ticks_until_scroll == 0, + ); + let monitored = if movie.monitored { "🏷" } else { "" }; + 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 certification = movie.certification.clone().unwrap_or_else(|| "".to_owned()); + let quality_profile = quality_profile_map + .get_by_left(&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::>() + .join(", "); + + Row::new(vec![ + Cell::from(movie.title.to_string()), + Cell::from(movie.year.to_string()), + Cell::from(movie.studio.to_string()), + Cell::from(format!("{}h {}m", hours, minutes)), + Cell::from(certification), + Cell::from(movie.original_language.name.to_owned()), + Cell::from(format!("{:.2} GB", file_size)), + Cell::from(quality_profile), + Cell::from(monitored.to_owned()), + Cell::from(tags), + ]) + .style(determine_row_style(downloads_vec, movie)) + }, + app.is_loading, + ); +} + +fn draw_update_all_movies_prompt( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + prompt_area: Rect, +) { + draw_prompt_box( + f, + prompt_area, + "Update All Movies", + "Do you want to update info and scan your disks for all of your movies?", + app.data.radarr_data.prompt_confirm, + ); +} diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index 529bacf..718ffc4 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -6,7 +6,8 @@ use tui::backend::Backend; use tui::layout::{Alignment, Constraint, Rect}; use tui::style::{Color, Style}; use tui::text::Text; -use tui::widgets::{Cell, ListItem, Paragraph, Row}; +use tui::widgets::ListItem; +use tui::widgets::Paragraph; use tui::Frame; use crate::app::radarr::{ @@ -15,503 +16,97 @@ use crate::app::radarr::{ }; use crate::app::App; use crate::logos::RADARR_LOGO; -use crate::models::radarr_models::{Collection, DiskSpace, DownloadRecord, Movie, RootFolder}; -use crate::models::{HorizontallyScrollableText, Route}; -use crate::ui::radarr_ui::add_movie_ui::draw_add_movie_search_popup; -use crate::ui::radarr_ui::collection_details_ui::draw_collection_details_popup; -use crate::ui::radarr_ui::delete_movie_ui::draw_delete_movie_prompt; -use crate::ui::radarr_ui::edit_collection_ui::draw_edit_collection_prompt; -use crate::ui::radarr_ui::edit_movie_ui::draw_edit_movie_prompt; -use crate::ui::radarr_ui::movie_details_ui::draw_movie_info_popup; +use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie, RootFolder}; +use crate::models::Route; +use crate::ui::draw_drop_down_list; +use crate::ui::draw_tabs; +use crate::ui::loading; +use crate::ui::radarr_ui::{ + add_movie_ui::AddMoviesUi, collection_details_ui::CollectionDetailsUi, + collections_ui::CollectionsUi, delete_movie_ui::DeleteMovieUi, downloads_ui::DownloadsUi, + edit_collection_ui::EditCollectionUi, edit_movie_ui::EditMovieUi, library_ui::LibraryUi, + movie_details_ui::MovieDetailsUi, root_folders_ui::RootFoldersUi, +}; 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_help, style_primary, - style_success, style_unmonitored, style_warning, title_block, title_block_centered, - vertical_chunks_with_margin, + borderless_block, horizontal_chunks, layout_block, line_gauge_with_label, line_gauge_with_title, + show_cursor, style_awaiting_import, style_bold, style_default, style_failure, 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, - draw_prompt_box, draw_prompt_popup_over, draw_table, draw_tabs, loading, TableProps, -}; -use crate::utils::{convert_runtime, convert_to_gb}; +use crate::ui::DrawUi; +use crate::utils::convert_to_gb; mod add_movie_ui; mod collection_details_ui; +mod collections_ui; mod delete_movie_ui; +mod downloads_ui; mod edit_collection_ui; mod edit_movie_ui; +mod library_ui; mod movie_details_ui; +mod root_folders_ui; -pub(super) fn draw_radarr_ui(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { - let (content_rect, _) = draw_tabs(f, area, "Movies", &app.data.radarr_data.main_tabs); +pub(super) struct RadarrUi {} - if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { - match active_radarr_block { - ActiveRadarrBlock::Movies => draw_library(f, app, content_rect), - ActiveRadarrBlock::SearchMovie => { - draw_popup_over(f, app, content_rect, draw_library, draw_search_box, 30, 11) - } - ActiveRadarrBlock::FilterMovies => { - draw_popup_over(f, app, content_rect, draw_library, draw_filter_box, 30, 11) - } - ActiveRadarrBlock::SearchCollection => draw_popup_over( - f, - app, - content_rect, - draw_collections, - draw_search_box, - 30, - 11, - ), - ActiveRadarrBlock::FilterCollections => draw_popup_over( - f, - app, - content_rect, - draw_collections, - draw_filter_box, - 30, - 11, - ), - 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) - } - _ if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) => { - if let Route::Radarr(_, Some(_)) = app.get_current_route() { - draw_large_popup_over( - f, - app, - content_rect, - draw_collections, - draw_add_movie_search_popup, - ) - } else { - draw_large_popup_over( - f, - app, - content_rect, - draw_library, - draw_add_movie_search_popup, - ) +impl DrawUi for RadarrUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + let (content_rect, _) = draw_tabs(f, area, "Movies", &app.data.radarr_data.main_tabs); + + if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { + match active_radarr_block { + ActiveRadarrBlock::Movies + | ActiveRadarrBlock::SearchMovie + | ActiveRadarrBlock::FilterMovies + | ActiveRadarrBlock::UpdateAllMoviesPrompt => LibraryUi::draw(f, app, content_rect), + ActiveRadarrBlock::Collections + | ActiveRadarrBlock::SearchCollection + | ActiveRadarrBlock::FilterCollections + | ActiveRadarrBlock::UpdateAllCollectionsPrompt => { + CollectionsUi::draw(f, app, content_rect) } - } - _ if COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block) => draw_large_popup_over( - f, - app, - content_rect, - draw_collections, - draw_collection_details_popup, - ), - _ if EDIT_MOVIE_BLOCKS.contains(&active_radarr_block) => { - if let Some(context) = context_option { - match context { - ActiveRadarrBlock::Movies => { - draw_medium_popup_over(f, app, content_rect, draw_library, draw_edit_movie_prompt) - } - _ if MOVIE_DETAILS_BLOCKS.contains(&context) => { - draw_large_popup_over(f, app, content_rect, draw_library, draw_movie_info_popup); - draw_popup(f, app, draw_edit_movie_prompt, 60, 60); - } - _ => (), - } + ActiveRadarrBlock::Downloads + | ActiveRadarrBlock::DeleteDownloadPrompt + | ActiveRadarrBlock::UpdateDownloadsPrompt => DownloadsUi::draw(f, app, content_rect), + ActiveRadarrBlock::RootFolders + | ActiveRadarrBlock::AddRootFolderPrompt + | ActiveRadarrBlock::DeleteRootFolderPrompt => RootFoldersUi::draw(f, app, content_rect), + _ if MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block) => { + MovieDetailsUi::draw(f, app, content_rect) } - } - _ if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block) => { - if let Some(context) = context_option { - match context { - ActiveRadarrBlock::Collections => draw_medium_popup_over( - f, - app, - content_rect, - draw_collections, - draw_edit_collection_prompt, - ), - _ if COLLECTION_DETAILS_BLOCKS.contains(&context) => { - draw_large_popup_over( - f, - app, - content_rect, - draw_collections, - draw_collection_details_popup, - ); - draw_popup(f, app, draw_edit_collection_prompt, 60, 60); - } - _ => (), - } + _ if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) => { + AddMoviesUi::draw(f, app, content_rect) } + _ if COLLECTION_DETAILS_BLOCKS.contains(&active_radarr_block) => { + CollectionDetailsUi::draw(f, app, content_rect) + } + _ if EDIT_MOVIE_BLOCKS.contains(&active_radarr_block) => { + EditMovieUi::draw(f, app, content_rect) + } + _ if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block) => { + EditCollectionUi::draw(f, app, content_rect) + } + _ if DELETE_MOVIE_BLOCKS.contains(&active_radarr_block) => { + DeleteMovieUi::draw(f, app, content_rect) + } + _ => (), } - _ if DELETE_MOVIE_BLOCKS.contains(&active_radarr_block) => { - draw_prompt_popup_over(f, app, content_rect, draw_library, draw_delete_movie_prompt) - } - ActiveRadarrBlock::DeleteDownloadPrompt => draw_prompt_popup_over( - f, - app, - content_rect, - 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, - content_rect, - draw_downloads, - draw_update_downloads_prompt, - ), - ActiveRadarrBlock::UpdateAllMoviesPrompt => draw_prompt_popup_over( - f, - app, - content_rect, - draw_library, - draw_update_all_movies_prompt, - ), - ActiveRadarrBlock::UpdateAllCollectionsPrompt => draw_prompt_popup_over( - f, - app, - content_rect, - draw_collections, - draw_update_all_collections_prompt, - ), - _ => (), } } -} -pub(super) fn draw_radarr_context_row(f: &mut Frame<'_, B>, app: &App<'_>, area: Rect) { - let chunks = horizontal_chunks(vec![Constraint::Min(0), Constraint::Length(20)], area); + fn draw_context_row(f: &mut Frame<'_, B>, app: &App<'_>, area: Rect) { + let chunks = horizontal_chunks(vec![Constraint::Min(0), Constraint::Length(20)], area); - let context_chunks = horizontal_chunks( - vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], - chunks[0], - ); + let context_chunks = horizontal_chunks( + vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], + chunks[0], + ); - draw_stats_context(f, app, context_chunks[0]); - draw_downloads_context(f, app, context_chunks[1]); - draw_radarr_logo(f, chunks[1]); -} - -fn draw_library(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { - let current_selection = if !app.data.radarr_data.filtered_movies.items.is_empty() { - app - .data - .radarr_data - .filtered_movies - .current_selection() - .clone() - } else if !app.data.radarr_data.movies.items.is_empty() { - app.data.radarr_data.movies.current_selection().clone() - } else { - Movie::default() - }; - 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 content = if !app.data.radarr_data.filtered_movies.items.is_empty() - && !app.data.radarr_data.is_filtering - { - &mut app.data.radarr_data.filtered_movies - } else { - &mut app.data.radarr_data.movies - }; - - draw_table( - f, - area, - layout_block_top_border(), - TableProps { - content, - table_headers: vec![ - "Title", - "Year", - "Studio", - "Runtime", - "Rating", - "Language", - "Size", - "Quality Profile", - "Monitored", - "Tags", - ], - constraints: vec![ - Constraint::Percentage(27), - Constraint::Percentage(4), - Constraint::Percentage(17), - Constraint::Percentage(6), - Constraint::Percentage(6), - Constraint::Percentage(6), - Constraint::Percentage(6), - Constraint::Percentage(10), - Constraint::Percentage(6), - Constraint::Percentage(12), - ], - help: app - .data - .radarr_data - .main_tabs - .get_active_tab_contextual_help(), - }, - |movie| { - movie.title.scroll_left_or_reset( - get_width_from_percentage(area, 27), - *movie == current_selection, - app.tick_count % app.ticks_until_scroll == 0, - ); - let monitored = if movie.monitored { "🏷" } else { "" }; - 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 certification = movie.certification.clone().unwrap_or_else(|| "".to_owned()); - let quality_profile = quality_profile_map - .get_by_left(&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::>() - .join(", "); - - Row::new(vec![ - Cell::from(movie.title.to_string()), - Cell::from(movie.year.to_string()), - Cell::from(movie.studio.to_string()), - Cell::from(format!("{}h {}m", hours, minutes)), - Cell::from(certification), - Cell::from(movie.original_language.name.to_owned()), - Cell::from(format!("{:.2} GB", file_size)), - Cell::from(quality_profile), - Cell::from(monitored.to_owned()), - Cell::from(tags), - ]) - .style(determine_row_style(downloads_vec, movie)) - }, - app.is_loading, - ); -} - -fn draw_update_all_movies_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { - draw_prompt_box( - f, - prompt_area, - "Update All Movies", - "Do you want to update info and scan your disks for all of your movies?", - app.data.radarr_data.prompt_confirm, - ); -} - -fn draw_update_downloads_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { - draw_prompt_box( - f, - prompt_area, - "Update Downloads", - "Do you want to update your downloads?", - app.data.radarr_data.prompt_confirm, - ); -} - -fn draw_update_all_collections_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { - draw_prompt_box( - f, - prompt_area, - "Update All Collections", - "Do you want to update all of your collections?", - app.data.radarr_data.prompt_confirm, - ); -} - -fn draw_delete_download_prompt( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - prompt_area: Rect, -) { - draw_prompt_box( - f, - prompt_area, - "Cancel Download", - format!( - "Do you really want to delete this download: {}?", - app.data.radarr_data.downloads.current_selection().title - ) - .as_str(), - app.data.radarr_data.prompt_confirm, - ); -} - -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); - if !app.data.radarr_data.is_searching { - let error_msg = match app.get_current_route() { - Route::Radarr(active_radarr_block, _) => match active_radarr_block { - ActiveRadarrBlock::SearchMovie => "Movie not found!", - ActiveRadarrBlock::SearchCollection => "Collection not found!", - _ => "", - }, - _ => "", - }; - - let input = Paragraph::new(error_msg) - .style(style_failure()) - .block(layout_block()); - - f.render_widget(input, chunks[0]); - } else { - 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 { - _ if SEARCH_BLOCKS.contains(active_radarr_block) => ( - "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.as_str()) - .style(style_default()) - .block(title_block_centered(block_title)); - show_cursor(f, chunks[0], offset, block_content); - - f.render_widget(input, chunks[0]); + draw_stats_context(f, app, context_chunks[0]); + draw_downloads_context(f, app, context_chunks[1]); + draw_radarr_logo(f, chunks[1]); } } -fn draw_filter_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); - if !app.data.radarr_data.is_filtering { - let error_msg = match app.get_current_route() { - Route::Radarr(active_radarr_block, _) => match active_radarr_block { - ActiveRadarrBlock::FilterMovies => "No movies found matching filter!", - ActiveRadarrBlock::FilterCollections => "No collections found matching filter!", - _ => "", - }, - _ => "", - }; - - let input = Paragraph::new(error_msg) - .style(style_failure()) - .block(layout_block()); - - f.render_widget(input, chunks[0]); - } else { - 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 { - _ if FILTER_BLOCKS.contains(active_radarr_block) => ( - "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.as_str()) - .style(style_default()) - .block(title_block_centered(block_title)); - show_cursor(f, chunks[0], offset, block_content); - - f.render_widget(input, chunks[0]); - } -} - -fn draw_radarr_logo(f: &mut Frame<'_, B>, area: Rect) { - let mut logo_text = Text::from(RADARR_LOGO); - logo_text.patch_style(Style::default().fg(Color::LightYellow)); - let logo = Paragraph::new(logo_text) - .block(layout_block()) - .alignment(Alignment::Center); - f.render_widget(logo, area); -} - fn draw_stats_context(f: &mut Frame<'_, B>, app: &App<'_>, area: Rect) { let block = title_block("Stats"); @@ -640,210 +235,6 @@ fn draw_downloads_context(f: &mut Frame<'_, B>, app: &App<'_>, area: } } -fn draw_downloads(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { - let current_selection = if app.data.radarr_data.downloads.items.is_empty() { - DownloadRecord::default() - } else { - app.data.radarr_data.downloads.current_selection().clone() - }; - - draw_table( - f, - area, - layout_block_top_border(), - TableProps { - content: &mut app.data.radarr_data.downloads, - table_headers: vec![ - "Title", - "Percent Complete", - "Size", - "Output Path", - "Indexer", - "Download Client", - ], - constraints: vec![ - Constraint::Percentage(30), - Constraint::Percentage(11), - Constraint::Percentage(11), - Constraint::Percentage(18), - Constraint::Percentage(17), - Constraint::Percentage(13), - ], - help: app - .data - .radarr_data - .main_tabs - .get_active_tab_contextual_help(), - }, - |download_record| { - let DownloadRecord { - title, - size, - sizeleft, - download_client, - indexer, - output_path, - .. - } = download_record; - - if matches!(output_path, Some(_)) { - output_path.as_ref().unwrap().scroll_left_or_reset( - get_width_from_percentage(area, 18), - current_selection == *download_record, - app.tick_count % app.ticks_until_scroll == 0, - ); - } - - let percent = 1f64 - (sizeleft.as_f64().unwrap() / size.as_f64().unwrap()); - let file_size: f64 = convert_to_gb(size.as_u64().unwrap()); - - Row::new(vec![ - Cell::from(title.to_owned()), - Cell::from(format!("{:.0}%", percent * 100.0)), - Cell::from(format!("{:.2} GB", file_size)), - Cell::from( - output_path - .as_ref() - .unwrap_or(&HorizontallyScrollableText::default()) - .to_string(), - ), - Cell::from(indexer.to_owned()), - Cell::from(download_client.to_owned()), - ]) - .style(style_primary()) - }, - app.is_loading, - ); -} - -fn draw_collections(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { - let current_selection = if !app.data.radarr_data.filtered_collections.items.is_empty() { - app - .data - .radarr_data - .filtered_collections - .current_selection() - .clone() - } else if !app.data.radarr_data.collections.items.is_empty() { - app.data.radarr_data.collections.current_selection().clone() - } else { - Collection::default() - }; - let quality_profile_map = &app.data.radarr_data.quality_profile_map; - let content = if !app.data.radarr_data.filtered_collections.items.is_empty() - && !app.data.radarr_data.is_filtering - { - &mut app.data.radarr_data.filtered_collections - } else { - &mut app.data.radarr_data.collections - }; - draw_table( - f, - area, - layout_block_top_border(), - TableProps { - content, - table_headers: vec![ - "Collection", - "Number of Movies", - "Root Folder Path", - "Quality Profile", - "Search on Add", - "Monitored", - ], - constraints: vec![ - Constraint::Percentage(25), - Constraint::Percentage(15), - Constraint::Percentage(15), - Constraint::Percentage(15), - Constraint::Percentage(15), - Constraint::Percentage(15), - ], - help: app - .data - .radarr_data - .main_tabs - .get_active_tab_contextual_help(), - }, - |collection| { - let number_of_movies = collection.movies.clone().unwrap_or_default().len(); - collection.title.scroll_left_or_reset( - get_width_from_percentage(area, 25), - *collection == current_selection, - app.tick_count % app.ticks_until_scroll == 0, - ); - let monitored = if collection.monitored { "🏷" } else { "" }; - let search_on_add = if collection.search_on_add { - "Yes" - } else { - "No" - }; - - Row::new(vec![ - Cell::from(collection.title.to_string()), - Cell::from(number_of_movies.to_string()), - Cell::from(collection.root_folder_path.clone().unwrap_or_default()), - Cell::from( - quality_profile_map - .get_by_left(&collection.quality_profile_id.as_u64().unwrap()) - .unwrap() - .to_owned(), - ), - Cell::from(search_on_add), - Cell::from(monitored), - ]) - .style(style_primary()) - }, - app.is_loading, - ); -} - -fn draw_root_folders(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { - draw_table( - f, - area, - layout_block_top_border(), - TableProps { - content: &mut app.data.radarr_data.root_folders, - table_headers: vec!["Path", "Free Space", "Unmapped Folders"], - constraints: vec![ - Constraint::Percentage(60), - Constraint::Percentage(20), - Constraint::Percentage(20), - ], - help: app - .data - .radarr_data - .main_tabs - .get_active_tab_contextual_help(), - }, - |root_folders| { - let RootFolder { - path, - free_space, - unmapped_folders, - .. - } = root_folders; - - let space: f64 = convert_to_gb(free_space.as_u64().unwrap()); - - Row::new(vec![ - Cell::from(path.to_owned()), - Cell::from(format!("{:.2} GB", space)), - Cell::from( - unmapped_folders - .as_ref() - .unwrap_or(&Vec::new()) - .len() - .to_string(), - ), - ]) - .style(style_primary()) - }, - app.is_loading, - ); -} - fn determine_row_style(downloads_vec: &[DownloadRecord], movie: &Movie) -> Style { if !movie.has_file { if let Some(download) = downloads_vec @@ -907,3 +298,94 @@ fn draw_select_root_folder_popup( |root_folder| ListItem::new(root_folder.path.to_owned()), ); } + +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); + if !app.data.radarr_data.is_searching { + let error_msg = match app.get_current_route() { + Route::Radarr(active_radarr_block, _) => match active_radarr_block { + ActiveRadarrBlock::SearchMovie => "Movie not found!", + ActiveRadarrBlock::SearchCollection => "Collection not found!", + _ => "", + }, + _ => "", + }; + + let input = Paragraph::new(error_msg) + .style(style_failure()) + .block(layout_block()); + + f.render_widget(input, chunks[0]); + } else { + 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 { + _ if SEARCH_BLOCKS.contains(active_radarr_block) => ( + "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.as_str()) + .style(style_default()) + .block(title_block_centered(block_title)); + show_cursor(f, chunks[0], offset, block_content); + + f.render_widget(input, chunks[0]); + } +} + +fn draw_filter_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); + if !app.data.radarr_data.is_filtering { + let error_msg = match app.get_current_route() { + Route::Radarr(active_radarr_block, _) => match active_radarr_block { + ActiveRadarrBlock::FilterMovies => "No movies found matching filter!", + ActiveRadarrBlock::FilterCollections => "No collections found matching filter!", + _ => "", + }, + _ => "", + }; + + let input = Paragraph::new(error_msg) + .style(style_failure()) + .block(layout_block()); + + f.render_widget(input, chunks[0]); + } else { + 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 { + _ if FILTER_BLOCKS.contains(active_radarr_block) => ( + "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.as_str()) + .style(style_default()) + .block(title_block_centered(block_title)); + show_cursor(f, chunks[0], offset, block_content); + + f.render_widget(input, chunks[0]); + } +} + +fn draw_radarr_logo(f: &mut Frame<'_, B>, area: Rect) { + let mut logo_text = Text::from(RADARR_LOGO); + logo_text.patch_style(Style::default().fg(Color::LightYellow)); + let logo = Paragraph::new(logo_text) + .block(layout_block()) + .alignment(Alignment::Center); + f.render_widget(logo, area); +} diff --git a/src/ui/radarr_ui/movie_details_ui.rs b/src/ui/radarr_ui/movie_details_ui.rs index 6e437de..8fa14f5 100644 --- a/src/ui/radarr_ui/movie_details_ui.rs +++ b/src/ui/radarr_ui/movie_details_ui.rs @@ -11,62 +11,73 @@ use crate::app::radarr::ActiveRadarrBlock; use crate::app::App; use crate::models::radarr_models::{Credit, MovieHistoryItem, Release, ReleaseField}; use crate::models::Route; +use crate::ui::radarr_ui::library_ui::draw_library; use crate::ui::utils::{ borderless_block, get_width_from_percentage, layout_block_bottom_border, layout_block_top_border, spans_info_default, style_awaiting_import, style_bold, style_default, style_failure, style_primary, style_success, style_warning, vertical_chunks, }; use crate::ui::{ - draw_drop_down_list, draw_drop_down_popup, draw_prompt_box, draw_prompt_box_with_content, - draw_prompt_popup_over, draw_small_popup_over, draw_table, draw_tabs, loading, TableProps, + draw_drop_down_list, draw_drop_down_popup, draw_large_popup_over, draw_prompt_box, + draw_prompt_box_with_content, draw_prompt_popup_over, draw_small_popup_over, draw_table, + draw_tabs, loading, DrawUi, TableProps, }; use crate::utils::convert_to_gb; -pub(super) fn draw_movie_info_popup( - f: &mut Frame<'_, B>, - app: &mut App<'_>, - area: Rect, -) { - let (content_area, _) = draw_tabs(f, area, "Movie Info", &app.data.radarr_data.movie_info_tabs); +pub(super) struct MovieDetailsUi {} - if let Route::Radarr(active_radarr_block, context_option) = app.get_current_route() { - match context_option.as_ref().unwrap_or(active_radarr_block) { - ActiveRadarrBlock::AutomaticallySearchMoviePrompt => draw_prompt_popup_over( - f, - app, - content_area, - draw_movie_info, - draw_search_movie_prompt, - ), - ActiveRadarrBlock::UpdateAndScanPrompt => draw_prompt_popup_over( - f, - app, - content_area, - draw_movie_info, - draw_update_and_scan_prompt, - ), - ActiveRadarrBlock::ManualSearchSortPrompt => draw_drop_down_popup( - f, - app, - content_area, - draw_movie_info, - |f, app, content_area| { - draw_drop_down_list( +impl DrawUi for MovieDetailsUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { + let draw_movie_info_popup = |f: &mut Frame<'_, B>, app: &mut App<'_>, popup_area: Rect| { + let (content_area, _) = draw_tabs( + f, + popup_area, + "Movie Info", + &app.data.radarr_data.movie_info_tabs, + ); + + match context_option.unwrap_or(active_radarr_block) { + ActiveRadarrBlock::AutomaticallySearchMoviePrompt => draw_prompt_popup_over( f, + app, content_area, - &mut app.data.radarr_data.movie_releases_sort, - |sort_option| ListItem::new(sort_option.to_string()), - ) - }, - ), - ActiveRadarrBlock::ManualSearchConfirmPrompt => draw_small_popup_over( - f, - app, - content_area, - draw_movie_info, - draw_manual_search_confirm_prompt, - ), - _ => draw_movie_info(f, app, content_area), + draw_movie_info, + draw_search_movie_prompt, + ), + ActiveRadarrBlock::UpdateAndScanPrompt => draw_prompt_popup_over( + f, + app, + content_area, + draw_movie_info, + draw_update_and_scan_prompt, + ), + ActiveRadarrBlock::ManualSearchSortPrompt => draw_drop_down_popup( + f, + app, + content_area, + draw_movie_info, + |f, app, content_area| { + draw_drop_down_list( + f, + content_area, + &mut app.data.radarr_data.movie_releases_sort, + |sort_option| ListItem::new(sort_option.to_string()), + ) + }, + ), + ActiveRadarrBlock::ManualSearchConfirmPrompt => draw_small_popup_over( + f, + app, + content_area, + draw_movie_info, + draw_manual_search_confirm_prompt, + ), + _ => draw_movie_info(f, app, content_area), + } + }; + + draw_large_popup_over(f, app, content_rect, draw_library, draw_movie_info_popup); } } } diff --git a/src/ui/radarr_ui/root_folders_ui.rs b/src/ui/radarr_ui/root_folders_ui.rs new file mode 100644 index 0000000..02baeb4 --- /dev/null +++ b/src/ui/radarr_ui/root_folders_ui.rs @@ -0,0 +1,141 @@ +use tui::backend::Backend; +use tui::layout::{Alignment, Constraint, Rect}; +use tui::widgets::{Cell, Paragraph, Row}; +use tui::Frame; + +use crate::app::radarr::ActiveRadarrBlock; +use crate::app::App; +use crate::models::radarr_models::RootFolder; +use crate::models::Route; +use crate::ui::utils::{ + borderless_block, layout_block_top_border, show_cursor, style_default, style_help, style_primary, + title_block_centered, vertical_chunks_with_margin, +}; +use crate::ui::{ + draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps, +}; +use crate::utils::convert_to_gb; + +pub(super) struct RootFoldersUi {} + +impl DrawUi for RootFoldersUi { + fn draw(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) { + if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { + match active_radarr_block { + 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::DeleteRootFolderPrompt => draw_prompt_popup_over( + f, + app, + content_rect, + draw_root_folders, + draw_delete_root_folder_prompt, + ), + _ => (), + } + } + } +} + +fn draw_root_folders(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { + draw_table( + f, + area, + layout_block_top_border(), + TableProps { + content: &mut app.data.radarr_data.root_folders, + table_headers: vec!["Path", "Free Space", "Unmapped Folders"], + constraints: vec![ + Constraint::Percentage(60), + Constraint::Percentage(20), + Constraint::Percentage(20), + ], + help: app + .data + .radarr_data + .main_tabs + .get_active_tab_contextual_help(), + }, + |root_folders| { + let RootFolder { + path, + free_space, + unmapped_folders, + .. + } = root_folders; + + let space: f64 = convert_to_gb(free_space.as_u64().unwrap()); + + Row::new(vec![ + Cell::from(path.to_owned()), + Cell::from(format!("{:.2} GB", space)), + Cell::from( + unmapped_folders + .as_ref() + .unwrap_or(&Vec::new()) + .len() + .to_string(), + ), + ]) + .style(style_primary()) + }, + app.is_loading, + ); +} + +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_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, + ); +}