From c6f51ab9b6185993d909c45fa978782920de941f Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 7 Feb 2024 17:33:17 -0700 Subject: [PATCH] Upgraded to Ratatui 0.26 and did a partial refactor to clean up the UI module. Created the ManagarrStyle trait to make it cleaner to use consistent styles across the project. Still need to update the layouts to be consistent with the newer and nicer format. That's a tomorrow problem --- Cargo.lock | 45 +++++- Cargo.toml | 8 +- src/ui/mod.rs | 128 +++++++--------- .../collections/collection_details_ui.rs | 73 ++++++---- src/ui/radarr_ui/collections/mod.rs | 6 +- src/ui/radarr_ui/downloads/mod.rs | 6 +- src/ui/radarr_ui/indexers/edit_indexer_ui.rs | 28 ++-- src/ui/radarr_ui/indexers/mod.rs | 24 +-- .../indexers/test_all_indexers_ui.rs | 22 ++- src/ui/radarr_ui/library/add_movie_ui.rs | 15 +- src/ui/radarr_ui/library/mod.rs | 31 ++-- src/ui/radarr_ui/library/movie_details_ui.rs | 72 +++++---- src/ui/radarr_ui/mod.rs | 60 ++++---- src/ui/radarr_ui/radarr_ui_utils.rs | 22 +-- src/ui/radarr_ui/radarr_ui_utils_tests.rs | 42 ++++-- src/ui/radarr_ui/root_folders/mod.rs | 6 +- src/ui/radarr_ui/system/mod.rs | 38 ++--- src/ui/radarr_ui/system/system_details_ui.rs | 12 +- src/ui/styles.rs | 81 +++++++++++ src/ui/styles_tests.rs | 73 ++++++++++ src/ui/utils.rs | 124 +++------------- src/ui/utils_tests.rs | 137 +----------------- 22 files changed, 509 insertions(+), 544 deletions(-) create mode 100644 src/ui/styles.rs create mode 100644 src/ui/styles_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 30fe6e7..ecf371a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,6 +198,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.0.83" @@ -244,6 +253,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "confy" version = "0.6.0" @@ -861,7 +883,7 @@ dependencies = [ [[package]] name = "managarr" -version = "0.0.31" +version = "0.0.32" dependencies = [ "anyhow", "backtrace", @@ -1199,12 +1221,13 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5659e52e4ba6e07b2dad9f1158f578ef84a73762625ddb51536019f34d180eb" +checksum = "154b85ef15a5d1719bcaa193c3c81fe645cd120c156874cd660fe49fd21d1373" dependencies = [ "bitflags 2.4.2", "cassowary", + "compact_str", "crossterm", "indoc", "itertools", @@ -1582,19 +1605,25 @@ dependencies = [ ] [[package]] -name = "strum" -version = "0.25.0" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.25.3" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" dependencies = [ "heck", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 5ece488..4dcc184 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "managarr" -version = "0.0.31" +version = "0.0.32" authors = ["Alex Clarke "] description = "A TUI to manage your Servarrs" keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui"] @@ -28,11 +28,11 @@ reqwest = { version = "0.11.14", features = ["json"] } serde_yaml = "0.9.16" serde_json = "1.0.91" serde = { version = "1.0", features = ["derive"] } -strum = {version = "0.25.0", features = ["derive"] } -strum_macros = "0.25.0" +strum = {version = "0.26.1", features = ["derive"] } +strum_macros = "0.26.1" tokio = { version = "1.29.0", features = ["full"] } tokio-util = "0.7.8" -ratatui = { version = "0.25.0", features = ["all-widgets"] } +ratatui = { version = "0.26.0", features = ["all-widgets"] } urlencoding = "2.1.2" [dev-dependencies] diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1c8965e..a57904c 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,9 +1,9 @@ use std::iter; use std::rc::Rc; -use ratatui::layout::{Alignment, Constraint, Rect}; -use ratatui::style::Modifier; -use ratatui::text::{Line, Span, Text}; +use ratatui::layout::{Alignment, Constraint, Layout, Rect}; +use ratatui::style::{Modifier, Style, Stylize}; +use ratatui::text::{Line, Text}; use ratatui::widgets::Paragraph; use ratatui::widgets::Row; use ratatui::widgets::Table; @@ -15,16 +15,16 @@ use ratatui::Frame; use crate::app::App; use crate::models::{HorizontallyScrollableText, Route, StatefulList, StatefulTable, TabState}; use crate::ui::radarr_ui::RadarrUi; +use crate::ui::styles::ManagarrStyle; 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, layout_button_paragraph_borderless, layout_paragraph_borderless, logo_block, show_cursor, - style_block_highlight, style_default, style_default_bold, style_failure, style_help, - style_highlight, style_primary, style_secondary, style_system_function, title_block, - title_block_centered, vertical_chunks_with_margin, + style_block_highlight, title_block, title_block_centered, vertical_chunks_with_margin, }; mod radarr_ui; +mod styles; mod utils; static HIGHLIGHT_SYMBOL: &str = "=> "; @@ -37,59 +37,53 @@ pub trait DrawUi { pub fn ui(f: &mut Frame<'_>, app: &mut App<'_>) { f.render_widget(background_block(), f.size()); - let main_chunks = if !app.error.text.is_empty() { - let chunks = vertical_chunks_with_margin( - vec![ - Constraint::Length(3), - Constraint::Length(3), - Constraint::Length(10), - Constraint::Length(0), - ], - f.size(), - 1, - ); + let [header, context, table] = if !app.error.text.is_empty() { + let [header, error, context, table] = Layout::vertical([ + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(10), + Constraint::Fill(1), + ]) + .margin(1) + .areas(f.size()); - draw_error(f, app, chunks[1]); + draw_error(f, app, error); - Rc::new([chunks[0], chunks[2], chunks[3]]) + [header, context, table] } else { - vertical_chunks_with_margin( - vec![ - Constraint::Length(3), - Constraint::Length(10), - Constraint::Length(0), - ], - f.size(), - 1, - ) + Layout::vertical([ + Constraint::Length(3), + Constraint::Length(10), + Constraint::Fill(1), + ]) + .margin(1) + .areas(f.size()) }; - draw_header_row(f, app, main_chunks[0]); + draw_header_row(f, app, header); if RadarrUi::accepts(*app.get_current_route()) { - RadarrUi::draw_context_row(f, app, main_chunks[1]); - RadarrUi::draw(f, app, main_chunks[2]); + RadarrUi::draw_context_row(f, app, context); + RadarrUi::draw(f, app, table); } } fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let chunks = horizontal_chunks_with_margin(vec![Constraint::Length(75), Constraint::Min(0)], area, 1); - let help_text = Text::from(app.server_tabs.get_active_tab_help()); + let help_text = Text::from(app.server_tabs.get_active_tab_help().help()); let titles = app .server_tabs .tabs .iter() - .map(|tab| Line::from(Span::styled(tab.title, style_default_bold()))) - .collect(); + .map(|tab| Line::from(tab.title.bold())); let tabs = Tabs::new(titles) .block(logo_block()) - .highlight_style(style_secondary()) + .highlight_style(Style::new().secondary()) .select(app.server_tabs.index); let help = Paragraph::new(help_text) .block(borderless_block()) - .style(style_help()) .alignment(Alignment::Right); f.render_widget(tabs, area); @@ -97,8 +91,7 @@ fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { } fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - let block = - title_block("Error | to close").style(style_failure().add_modifier(Modifier::BOLD)); + let block = title_block("Error | to close").failure().bold(); app.error.scroll_left_or_reset( area.width as usize, @@ -106,13 +99,10 @@ fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { app.tick_count % app.ticks_until_scroll == 0, ); - let mut text = Text::from(app.error.to_string()); - text.patch_style(style_failure()); - - let paragraph = Paragraph::new(text) + let paragraph = Paragraph::new(Text::from(app.error.to_string()).failure()) .block(block) .wrap(Wrap { trim: true }) - .style(style_primary()); + .primary(); f.render_widget(paragraph, area); } @@ -245,8 +235,9 @@ pub fn draw_error_popup(f: &mut Frame<'_>, message: &str) { f.render_widget(background_block(), prompt_area); let error_message = Paragraph::new(Text::from(message)) - .block(title_block_centered("Error").style(style_failure())) - .style(style_failure().add_modifier(Modifier::BOLD)) + .block(title_block_centered("Error").failure()) + .failure() + .bold() .wrap(Wrap { trim: false }) .alignment(Alignment::Center); @@ -262,24 +253,21 @@ fn draw_tabs<'a>( let chunks = vertical_chunks_with_margin(vec![Constraint::Length(2), Constraint::Min(0)], area, 1); let horizontal_chunks = horizontal_chunks_with_margin( - vec![Constraint::Percentage(10), Constraint::Min(0)], + vec![Constraint::Percentage(10), Constraint::Fill(1)], area, 1, ); let block = title_block(title); - let mut help_text = Text::from(tab_state.get_active_tab_help()); - help_text.patch_style(style_help()); let titles = tab_state .tabs .iter() - .map(|tab_route| Line::from(Span::styled(tab_route.title, style_default_bold()))) - .collect(); + .map(|tab_route| Line::from(tab_route.title.bold())); let tabs = Tabs::new(titles) .block(block) - .highlight_style(style_secondary()) + .highlight_style(Style::new().secondary()) .select(tab_state.index); - let help = Paragraph::new(help_text) + let help = Paragraph::new(Text::from(tab_state.get_active_tab_help().help())) .block(borderless_block()) .alignment(Alignment::Right); @@ -369,15 +357,13 @@ fn draw_table_contents<'a, T, F>( { let rows = content.items.iter().map(row_mapper); - let headers = Row::new(table_headers) - .style(style_default_bold()) - .bottom_margin(0); + let headers = Row::new(table_headers).default().bold().bottom_margin(0); let mut table = Table::new(rows, &constraints).header(headers).block(block); if highlight { table = table - .highlight_style(style_highlight()) + .highlight_style(Style::new().highlight()) .highlight_symbol(HIGHLIGHT_SYMBOL); } @@ -386,12 +372,8 @@ fn draw_table_contents<'a, T, F>( pub fn loading(f: &mut Frame<'_>, block: Block<'_>, area: Rect, is_loading: bool) { if is_loading { - let text = "\n\n Loading ...\n\n".to_owned(); - let mut text = Text::from(text); - text.patch_style(style_system_function()); - - let paragraph = Paragraph::new(text) - .style(style_system_function()) + let paragraph = Paragraph::new(Text::from("\n\n Loading ...\n\n")) + .system_function() .block(block); f.render_widget(paragraph, area); } else { @@ -538,7 +520,7 @@ pub fn draw_checkbox_with_label( let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: "))) .block(borderless_block()) .alignment(Alignment::Right) - .style(style_primary()); + .primary(); f.render_widget(label_paragraph, horizontal_chunks[0]); @@ -598,7 +580,7 @@ pub fn draw_drop_down_menu_button( let description_paragraph = Paragraph::new(Text::from(format!("\n{description}: "))) .block(borderless_block()) .alignment(Alignment::Right) - .style(style_primary()); + .primary(); f.render_widget(description_paragraph, horizontal_chunks[0]); @@ -614,7 +596,7 @@ pub fn draw_selectable_list<'a, T>( let items: Vec> = content.items.iter().map(item_mapper).collect(); let list = List::new(items) .block(layout_block()) - .highlight_style(style_highlight()); + .highlight_style(Style::new().highlight()); f.render_stateful_widget(list, area, &mut content.state); } @@ -648,7 +630,7 @@ pub fn draw_list_box<'a, T>( let mut list = List::new(items).block(block); if is_popup { - list = list.highlight_style(style_highlight()); + list = list.highlight_style(Style::new().highlight()); } f.render_stateful_widget(list, content_area, &mut content.state); @@ -662,9 +644,7 @@ fn draw_help_and_get_content_rect(f: &mut Frame<'_>, area: Rect, help: Option, text_box_props: TextBoxProps<'_>) { cursor_after_string, } = text_box_props; let (block, style) = if let Some(title) = block_title { - (title_block_centered(title), style_default()) + (title_block_centered(title), Style::new().default()) } else { ( layout_block(), if should_show_cursor { - style_default() + Style::new().default() } else { style_block_highlight(is_selected) }, @@ -753,7 +733,7 @@ pub fn draw_text_box_with_label( let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: "))) .block(borderless_block()) .alignment(Alignment::Right) - .style(style_primary()); + .primary(); f.render_widget(label_paragraph, horizontal_chunks[0]); @@ -801,7 +781,7 @@ pub fn draw_input_box_popup( ); let help = Paragraph::new(" cancel") - .style(style_help()) + .help() .alignment(Alignment::Center) .block(borderless_block()); f.render_widget(help, chunks[1]); @@ -809,7 +789,7 @@ pub fn draw_input_box_popup( pub fn draw_error_message_popup(f: &mut Frame<'_>, error_message_area: Rect, error_msg: &str) { let input = Paragraph::new(error_msg) - .style(style_failure()) + .failure() .alignment(Alignment::Center) .block(layout_block()); diff --git a/src/ui/radarr_ui/collections/collection_details_ui.rs b/src/ui/radarr_ui/collections/collection_details_ui.rs index 74a3c70..46a2f0a 100644 --- a/src/ui/radarr_ui/collections/collection_details_ui.rs +++ b/src/ui/radarr_ui/collections/collection_details_ui.rs @@ -1,5 +1,6 @@ use ratatui::layout::{Alignment, Constraint, Rect}; -use ratatui::text::Text; +use ratatui::style::Stylize; +use ratatui::text::{Line, Text}; use ratatui::widgets::{Cell, Paragraph, Row, Wrap}; use ratatui::Frame; @@ -12,10 +13,10 @@ use crate::models::servarr_data::radarr::radarr_data::{ }; use crate::models::Route; use crate::ui::radarr_ui::collections::draw_collections; +use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{ - borderless_block, get_width_from_percentage, layout_block_top_border_with_title, - line_info_primary, style_default, style_help, style_primary, title_block, title_style, - vertical_chunks_with_margin, + borderless_block, get_width_from_percentage, layout_block_top_border_with_title, title_block, + title_style, vertical_chunks_with_margin, }; use crate::ui::{draw_large_popup_over, draw_small_popup_over, draw_table, DrawUi, TableProps}; use crate::utils::convert_runtime; @@ -98,11 +99,13 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, content_are .current_selection() .clone() }; - let mut help_text = Text::from(format!( - "<↑↓> scroll table | {}", - build_context_clue_string(&COLLECTION_DETAILS_CONTEXT_CLUES) - )); - help_text.patch_style(style_help()); + let help_text = Text::from( + format!( + "<↑↓> scroll table | {}", + build_context_clue_string(&COLLECTION_DETAILS_CONTEXT_CLUES) + ) + .help(), + ); let monitored = if collection_selection.monitored { "Yes" } else { @@ -116,24 +119,35 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, content_are let minimum_availability = collection_selection.minimum_availability.to_display_str(); let collection_description = Text::from(vec![ - line_info_primary( - "Overview: ".to_owned(), - collection_selection.overview.clone().unwrap_or_default(), - ), - line_info_primary( - "Root Folder Path: ".to_owned(), + Line::from(vec![ + "Overview ".primary().bold(), + collection_selection + .overview + .clone() + .unwrap_or_default() + .default(), + ]), + Line::from(vec![ + "Root Folder Path: ".primary().bold(), collection_selection .root_folder_path .clone() - .unwrap_or_default(), - ), - line_info_primary("Quality Profile: ".to_owned(), quality_profile), - line_info_primary( - "Minimum Availability: ".to_owned(), - minimum_availability.to_owned(), - ), - line_info_primary("Monitored: ".to_owned(), monitored.to_owned()), - line_info_primary("Search on Add: ".to_owned(), search_on_add.to_owned()), + .unwrap_or_default() + .default(), + ]), + Line::from(vec![ + "Quality Profile: ".primary().bold(), + quality_profile.default(), + ]), + Line::from(vec![ + "Minimum Availability: ".primary().bold(), + minimum_availability.default(), + ]), + Line::from(vec!["Monitored: ".primary().bold(), monitored.default()]), + Line::from(vec![ + "Search on Add: ".primary().bold(), + search_on_add.default(), + ]), ]); let description_paragraph = Paragraph::new(collection_description) @@ -230,7 +244,7 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, content_are Cell::from(rotten_tomatoes_rating), Cell::from(movie.genres.join(", ")), ]) - .style(style_primary()) + .primary() }, app.is_loading, true, @@ -246,7 +260,7 @@ fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) content_area, 1, ); - let mut overview = Text::from( + let overview = Text::from( app .data .radarr_data @@ -254,10 +268,9 @@ fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) .current_selection() .clone() .overview, - ); - overview.patch_style(style_default()); - let mut help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES)); - help_text.patch_style(style_help()); + ) + .default(); + let help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES).help()); let paragraph = Paragraph::new(overview) .block(borderless_block()) diff --git a/src/ui/radarr_ui/collections/mod.rs b/src/ui/radarr_ui/collections/mod.rs index 519afc5..875197c 100644 --- a/src/ui/radarr_ui/collections/mod.rs +++ b/src/ui/radarr_ui/collections/mod.rs @@ -10,11 +10,12 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, COLLEC use crate::models::Route; use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi; use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi; -use crate::ui::utils::{get_width_from_percentage, layout_block_top_border, style_primary}; +use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; use crate::ui::{ draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps, }; +use crate::ui::styles::ManagarrStyle; mod collection_details_ui; #[cfg(test)] @@ -167,8 +168,7 @@ pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) ), Cell::from(search_on_add), Cell::from(monitored), - ]) - .style(style_primary()) + ]).primary() }, app.is_loading, true, diff --git a/src/ui/radarr_ui/downloads/mod.rs b/src/ui/radarr_ui/downloads/mod.rs index 43e61ed..a298af1 100644 --- a/src/ui/radarr_ui/downloads/mod.rs +++ b/src/ui/radarr_ui/downloads/mod.rs @@ -6,8 +6,9 @@ use crate::app::App; use crate::models::radarr_models::DownloadRecord; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; use crate::models::{HorizontallyScrollableText, Route}; -use crate::ui::utils::{get_width_from_percentage, layout_block_top_border, style_primary}; +use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps}; +use crate::ui::styles::ManagarrStyle; use crate::utils::convert_to_gb; #[cfg(test)] @@ -119,8 +120,7 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { ), Cell::from(indexer.to_owned()), Cell::from(download_client.to_owned()), - ]) - .style(style_primary()) + ]).primary() }, app.is_loading, true, diff --git a/src/ui/radarr_ui/indexers/edit_indexer_ui.rs b/src/ui/radarr_ui/indexers/edit_indexer_ui.rs index 460a2ee..81a4c91 100644 --- a/src/ui/radarr_ui/indexers/edit_indexer_ui.rs +++ b/src/ui/radarr_ui/indexers/edit_indexer_ui.rs @@ -10,7 +10,7 @@ use crate::ui::{ draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading, DrawUi, LabeledTextBoxProps, }; -use ratatui::layout::{Constraint, Rect}; +use ratatui::layout::{Constraint, Layout, Rect}; use ratatui::Frame; use std::iter; @@ -66,16 +66,14 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R 1, ); - let left_chunks = vertical_chunks( - vec![ - Constraint::Length(3), - Constraint::Length(3), - Constraint::Length(3), - Constraint::Length(3), - Constraint::Min(0), - ], - split_chunks[0], - ); + let [name, rss, auto_search, interactive_search, _] = Layout::vertical([ + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Min(0), + ]) + .areas(split_chunks[0]); let right_chunks = vertical_chunks( vec![ Constraint::Length(3), @@ -91,7 +89,7 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R draw_text_box_with_label( f, LabeledTextBoxProps { - area: left_chunks[0], + area: name, label: "Name", text: &edit_indexer_modal.name.text, offset: *edit_indexer_modal.name.offset.borrow(), @@ -166,14 +164,14 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R draw_checkbox_with_label( f, - left_chunks[1], + rss, "Enable RSS", edit_indexer_modal.enable_rss.unwrap_or_default(), selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableRss, ); draw_checkbox_with_label( f, - left_chunks[2], + auto_search, "Enable Automatic Search", edit_indexer_modal .enable_automatic_search @@ -182,7 +180,7 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R ); draw_checkbox_with_label( f, - left_chunks[3], + interactive_search, "Enable Interactive Search", edit_indexer_modal .enable_interactive_search diff --git a/src/ui/radarr_ui/indexers/mod.rs b/src/ui/radarr_ui/indexers/mod.rs index 1d5c660..defcbd7 100644 --- a/src/ui/radarr_ui/indexers/mod.rs +++ b/src/ui/radarr_ui/indexers/mod.rs @@ -10,7 +10,8 @@ use crate::models::Route; use crate::ui::radarr_ui::indexers::edit_indexer_ui::EditIndexerUi; use crate::ui::radarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi; use crate::ui::radarr_ui::indexers::test_all_indexers_ui::TestAllIndexersUi; -use crate::ui::utils::{layout_block_top_border, style_failure, style_primary, style_success}; +use crate::ui::styles::ManagarrStyle; +use crate::ui::utils::layout_block_top_border; use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps}; mod edit_indexer_ui; @@ -103,24 +104,15 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { } = indexer; let bool_to_text = |flag: bool| { if flag { - return ("Enabled", style_success()); + return Text::from("Enabled").success(); } - ("Disabled", style_failure()) + Text::from("Disabled").failure() }; - let (rss_text, rss_style) = bool_to_text(*enable_rss); - let mut rss = Text::from(rss_text); - rss.patch_style(rss_style); - - let (auto_search_text, auto_search_style) = bool_to_text(*enable_automatic_search); - let mut automatic_search = Text::from(auto_search_text); - automatic_search.patch_style(auto_search_style); - - let (interactive_search_text, interactive_search_style) = - bool_to_text(*enable_interactive_search); - let mut interactive_search = Text::from(interactive_search_text); - interactive_search.patch_style(interactive_search_style); + let rss = bool_to_text(*enable_rss); + let automatic_search = bool_to_text(*enable_automatic_search); + let interactive_search = bool_to_text(*enable_interactive_search); let tags: String = tags .iter() .map(|tag_id| { @@ -143,7 +135,7 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { Cell::from(priority.to_string()), Cell::from(tags), ]) - .style(style_primary()) + .primary() }, app.is_loading, true, diff --git a/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs b/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs index f7d3649..85efab6 100644 --- a/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs +++ b/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs @@ -4,9 +4,8 @@ use crate::models::servarr_data::radarr::modals::IndexerTestResultModalItem; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::Route; use crate::ui::radarr_ui::indexers::draw_indexers; -use crate::ui::utils::{ - borderless_block, get_width_from_percentage, style_failure, style_success, title_block, -}; +use crate::ui::styles::ManagarrStyle; +use crate::ui::utils::{borderless_block, get_width_from_percentage, title_block}; use crate::ui::{ draw_help_and_get_content_rect, draw_large_popup_over, draw_table, DrawUi, TableProps, }; @@ -76,18 +75,17 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are app.tick_count % app.ticks_until_scroll == 0, ); let pass_fail = if result.is_valid { "✔" } else { "❌" }; - let row_style = if result.is_valid { - style_success() - } else { - style_failure() - }; - - Row::new(vec![ + let row = Row::new(vec![ Cell::from(result.name.to_owned()), Cell::from(pass_fail.to_owned()), Cell::from(result.validation_failures.to_string()), - ]) - .style(row_style) + ]); + + if result.is_valid { + row.success() + } else { + row.failure() + } }, app.is_loading, true, diff --git a/src/ui/radarr_ui/library/add_movie_ui.rs b/src/ui/radarr_ui/library/add_movie_ui.rs index c239eca..d0ad6db 100644 --- a/src/ui/radarr_ui/library/add_movie_ui.rs +++ b/src/ui/radarr_ui/library/add_movie_ui.rs @@ -11,10 +11,10 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ADD_MO use crate::models::Route; use crate::ui::radarr_ui::collections::{draw_collection_details, draw_collections}; use crate::ui::radarr_ui::library::draw_library; +use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{ borderless_block, get_width_from_percentage, horizontal_chunks, layout_block, - layout_paragraph_borderless, style_help, style_primary, title_block_centered, - vertical_chunks_with_margin, + layout_paragraph_borderless, title_block_centered, vertical_chunks_with_margin, }; use crate::ui::{ draw_button, draw_drop_down_menu_button, draw_drop_down_popup, draw_error_popup, @@ -148,8 +148,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { ); f.render_widget(layout_block(), chunks[1]); - let mut help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES)); - help_text.patch_style(style_help()); + let help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES).help()); let help_paragraph = Paragraph::new(help_text) .block(borderless_block()) .alignment(Alignment::Center); @@ -167,10 +166,8 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { | ActiveRadarrBlock::AddMovieSelectRootFolder | ActiveRadarrBlock::AddMovieAlreadyInLibrary | ActiveRadarrBlock::AddMovieTagsInput => { - let mut help_text = Text::from(build_context_clue_string( - &ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, - )); - help_text.patch_style(style_help()); + let help_text = + Text::from(build_context_clue_string(&ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES).help()); let help_paragraph = Paragraph::new(help_text) .block(borderless_block()) .alignment(Alignment::Center); @@ -259,7 +256,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { Cell::from(rotten_tomatoes_rating), Cell::from(movie.genres.join(", ")), ]) - .style(style_primary()) + .primary() }, is_loading, true, diff --git a/src/ui/radarr_ui/library/mod.rs b/src/ui/radarr_ui/library/mod.rs index f4f2147..4db6da5 100644 --- a/src/ui/radarr_ui/library/mod.rs +++ b/src/ui/radarr_ui/library/mod.rs @@ -6,7 +6,7 @@ use crate::app::App; use crate::models::radarr_models::Movie; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, LIBRARY_BLOCKS}; use crate::models::Route; -use crate::ui::radarr_ui::determine_row_style; +use crate::ui::radarr_ui::decorate_with_row_style; use crate::ui::radarr_ui::library::add_movie_ui::AddMovieUi; use crate::ui::radarr_ui::library::delete_movie_ui::DeleteMovieUi; use crate::ui::radarr_ui::library::edit_movie_ui::EditMovieUi; @@ -186,19 +186,22 @@ pub(super) fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { .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!("{hours}h {minutes}m")), - Cell::from(certification), - Cell::from(movie.original_language.name.to_owned()), - Cell::from(format!("{file_size:.2} GB")), - Cell::from(quality_profile), - Cell::from(monitored.to_owned()), - Cell::from(tags), - ]) - .style(determine_row_style(downloads_vec, movie)) + decorate_with_row_style( + downloads_vec, + movie, + Row::new(vec![ + Cell::from(movie.title.to_string()), + Cell::from(movie.year.to_string()), + Cell::from(movie.studio.to_string()), + Cell::from(format!("{hours}h {minutes}m")), + Cell::from(certification), + Cell::from(movie.original_language.name.to_owned()), + Cell::from(format!("{file_size:.2} GB")), + Cell::from(quality_profile), + Cell::from(monitored.to_owned()), + Cell::from(tags), + ]), + ) }, app.is_loading, true, diff --git a/src/ui/radarr_ui/library/movie_details_ui.rs b/src/ui/radarr_ui/library/movie_details_ui.rs index 2cfd55f..fe03739 100644 --- a/src/ui/radarr_ui/library/movie_details_ui.rs +++ b/src/ui/radarr_ui/library/movie_details_ui.rs @@ -1,7 +1,7 @@ use std::iter; use ratatui::layout::{Alignment, Constraint, Rect}; -use ratatui::style::{Modifier, Style}; +use ratatui::style::{Style, Stylize}; use ratatui::text::{Line, Span, Text}; use ratatui::widgets::{Cell, ListItem, Paragraph, Row, Wrap}; use ratatui::Frame; @@ -11,10 +11,10 @@ use crate::models::radarr_models::{Credit, MovieHistoryItem, Release, ReleaseFie use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS}; use crate::models::Route; use crate::ui::radarr_ui::library::draw_library; +use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{ borderless_block, get_width_from_percentage, layout_block_bottom_border, layout_block_top_border, - line_info_default, style_awaiting_import, style_bold, style_failure, style_primary, - style_success, style_warning, vertical_chunks, + vertical_chunks, }; use crate::ui::{ draw_drop_down_popup, draw_large_popup_over, draw_prompt_box, draw_prompt_box_with_content, @@ -162,19 +162,13 @@ fn draw_file_info(f: &mut Frame<'_>, app: &App<'_>, content_area: Rect) { ], content_area, ); - let mut file_details_title = Text::from("File Details"); - let mut audio_details_title = Text::from("Audio Details"); - let mut video_details_title = Text::from("Video Details"); - file_details_title.patch_style(style_bold()); - audio_details_title.patch_style(style_bold()); - video_details_title.patch_style(style_bold()); let file_details_title_paragraph = - Paragraph::new(file_details_title).block(layout_block_top_border()); + Paragraph::new("File Details".bold()).block(layout_block_top_border()); let audio_details_title_paragraph = - Paragraph::new(audio_details_title).block(borderless_block()); + Paragraph::new("Audio Details".bold()).block(borderless_block()); let video_details_title_paragraph = - Paragraph::new(video_details_title).block(borderless_block()); + Paragraph::new("Video Details".bold()).block(borderless_block()); let file_details = Text::from(file_info); let audio_details = Text::from(audio_details); @@ -214,19 +208,22 @@ fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, content_area: Rect) { .unwrap() .split(": ") .collect::>()[1]; - let mut text = Text::from( + let text = Text::from( movie_details .items .iter() .map(|line| { let split = line.split(':').collect::>(); let title = format!("{}:", split[0]); + let style = style_from_download_status(download_status); - line_info_default(title, split[1..].join(":")) + Line::from(vec![ + title.bold().style(style), + Span::styled(split[1..].join(":"), style), + ]) }) .collect::>>(), ); - text.patch_style(determine_style_from_download_status(download_status)); let paragraph = Paragraph::new(text) .block(block) @@ -304,7 +301,7 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) Cell::from(quality.quality.name.to_owned()), Cell::from(date.to_string()), ]) - .style(style_success()) + .success() }, app.is_loading, true, @@ -347,7 +344,7 @@ fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { Cell::from(person_name.to_owned()), Cell::from(character.clone().unwrap_or_default()), ]) - .style(style_success()) + .success() }, app.is_loading, true, @@ -391,7 +388,7 @@ fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { Cell::from(job.clone().unwrap_or_default()), Cell::from(department.clone().unwrap_or_default()), ]) - .style(style_success()) + .success() }, app.is_loading, true, @@ -505,14 +502,16 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) let size = convert_to_gb(*size); let rejected_str = if *rejected { "⛔" } else { "" }; let peers = if seeders.is_none() || leechers.is_none() { - Text::default() + Text::from("") } else { let seeders = seeders.clone().unwrap().as_u64().unwrap(); let leechers = leechers.clone().unwrap().as_u64().unwrap(); - let mut text = Text::from(format!("{seeders} / {leechers}")); - text.patch_style(determine_peer_style(seeders, leechers)); - text + decorate_peer_style( + seeders, + leechers, + Text::from(format!("{seeders} / {leechers}")), + ) }; let language = if languages.is_some() { @@ -533,7 +532,7 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) Cell::from(language), Cell::from(quality), ]) - .style(style_primary()) + .primary() }, app.is_loading || is_empty, true, @@ -567,16 +566,13 @@ fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp }; if current_selection.rejected { - let mut lines_vec = vec![Line::from(vec![Span::styled( - "Rejection reasons: ", - style_primary().add_modifier(Modifier::BOLD), - )])]; + let mut lines_vec = vec![Line::from("Rejection reasons: ".primary().bold())]; let mut rejections_spans = current_selection .rejections .clone() .unwrap_or_default() .iter() - .map(|item| Line::from(vec![Span::styled(format!("• {item}"), style_primary())])) + .map(|item| Line::from(format!("• {item}").primary().bold())) .collect::>>(); lines_vec.append(&mut rejections_spans); @@ -604,22 +600,22 @@ fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp } } -fn determine_style_from_download_status(download_status: &str) -> Style { +fn style_from_download_status(download_status: &str) -> Style { match download_status { - "Downloaded" => style_success(), - "Awaiting Import" => style_awaiting_import(), - "Downloading" => style_warning(), - "Missing" => style_failure(), - _ => style_success(), + "Downloaded" => Style::new().success(), + "Awaiting Import" => Style::new().awaiting_import(), + "Downloading" => Style::new().warning(), + "Missing" => Style::new().failure(), + _ => Style::new().success(), } } -fn determine_peer_style(seeders: u64, leechers: u64) -> Style { +fn decorate_peer_style(seeders: u64, leechers: u64, text: Text<'_>) -> Text<'_> { if seeders == 0 { - style_failure() + text.failure() } else if seeders < leechers { - style_warning() + text.warning() } else { - style_success() + text.success() } } diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index cfa77c9..a44fdf8 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -1,10 +1,10 @@ use std::iter; use chrono::{Duration, Utc}; -use ratatui::layout::{Alignment, Constraint, Rect}; -use ratatui::style::{Color, Style}; +use ratatui::layout::{Alignment, Constraint, Layout, Rect}; +use ratatui::prelude::Stylize; use ratatui::text::Text; -use ratatui::widgets::Paragraph; +use ratatui::widgets::{Paragraph, Row}; use ratatui::Frame; use crate::app::App; @@ -20,10 +20,10 @@ use crate::ui::radarr_ui::indexers::IndexersUi; use crate::ui::radarr_ui::library::LibraryUi; use crate::ui::radarr_ui::root_folders::RootFoldersUi; use crate::ui::radarr_ui::system::SystemUi; +use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{ - borderless_block, horizontal_chunks, layout_block, line_gauge_with_label, line_gauge_with_title, - style_awaiting_import, style_bold, style_default, style_failure, style_success, - style_unmonitored, style_warning, title_block, vertical_chunks_with_margin, + borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block, + vertical_chunks_with_margin, }; use crate::ui::DrawUi; use crate::utils::convert_to_gb; @@ -63,16 +63,14 @@ impl DrawUi for RadarrUi { } fn draw_context_row(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { - let chunks = horizontal_chunks(vec![Constraint::Min(0), Constraint::Length(20)], area); + let [main, logo] = Layout::horizontal([Constraint::Min(0), Constraint::Length(20)]).areas(area); - let context_chunks = horizontal_chunks( - vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], - chunks[0], - ); + let [stats, downloads] = + Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).areas(main); - draw_stats_context(f, app, context_chunks[0]); - draw_downloads_context(f, app, context_chunks[1]); - draw_radarr_logo(f, chunks[1]); + draw_stats_context(f, app, stats); + draw_downloads_context(f, app, downloads); + draw_radarr_logo(f, logo); } } @@ -107,7 +105,7 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { app.data.radarr_data.version ))) .block(borderless_block()) - .style(style_bold()); + .bold(); let uptime = Utc::now() - start_time.to_owned(); let days = uptime.num_days(); @@ -122,12 +120,10 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { width = 2 ))) .block(borderless_block()) - .style(style_bold()); + .bold(); - let storage = - Paragraph::new(Text::from("Storage:")).block(borderless_block().style(style_bold())); - let folders = - Paragraph::new(Text::from("Root Folders:")).block(borderless_block().style(style_bold())); + let storage = Paragraph::new(Text::from("Storage:")).block(borderless_block().bold()); + let folders = Paragraph::new(Text::from("Root Folders:")).block(borderless_block().bold()); f.render_widget(version_paragraph, chunks[0]); f.render_widget(uptime_paragraph, chunks[1]); @@ -159,7 +155,7 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { let space: f64 = convert_to_gb(*free_space); let root_folder_space = Paragraph::new(format!("{path}: {space:.2} GB free")) .block(borderless_block()) - .style(style_default()); + .default(); f.render_widget(root_folder_space, chunks[i + disk_space_vec.len() + 4]) } @@ -198,36 +194,40 @@ fn draw_downloads_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { } } -fn determine_row_style(downloads_vec: &[DownloadRecord], movie: &Movie) -> Style { +fn decorate_with_row_style<'a>( + downloads_vec: &[DownloadRecord], + movie: &Movie, + row: Row<'a>, +) -> Row<'a> { if !movie.has_file { if let Some(download) = downloads_vec .iter() .find(|&download| download.movie_id == movie.id) { if download.status == "downloading" { - return style_warning(); + return row.warning(); } if download.status == "completed" { - return style_awaiting_import(); + return row.awaiting_import(); } } - return style_failure(); + return row.failure(); } if !movie.monitored { - style_unmonitored() + row.unmonitored() } else { - style_success() + row.success() } } fn draw_radarr_logo(f: &mut Frame<'_>, area: Rect) { - let mut logo_text = Text::from(RADARR_LOGO); - logo_text.patch_style(Style::default().fg(Color::LightYellow)); + let logo_text = Text::from(RADARR_LOGO); let logo = Paragraph::new(logo_text) - .block(layout_block()) + .light_yellow() + .block(layout_block().default()) .alignment(Alignment::Center); f.render_widget(logo, area); } diff --git a/src/ui/radarr_ui/radarr_ui_utils.rs b/src/ui/radarr_ui/radarr_ui_utils.rs index 17de238..9cbadeb 100644 --- a/src/ui/radarr_ui/radarr_ui_utils.rs +++ b/src/ui/radarr_ui/radarr_ui_utils.rs @@ -1,19 +1,21 @@ -use crate::ui::utils::{style_default, style_failure, style_secondary}; -use ratatui::style::{Color, Modifier, Style}; +use ratatui::style::{Style, Stylize}; +use ratatui::widgets::ListItem; + +use crate::ui::styles::ManagarrStyle; #[cfg(test)] #[path = "radarr_ui_utils_tests.rs"] mod radarr_ui_utils_tests; -pub(super) fn determine_log_style_by_level(level: &str) -> Style { +pub(super) fn style_log_list_item(list_item: ListItem<'_>, level: String) -> ListItem<'_> { match level.to_lowercase().as_str() { - "trace" => Style::default().fg(Color::Gray), - "debug" => Style::default().fg(Color::Blue), - "info" => style_default(), - "warn" => style_secondary(), - "error" => style_failure(), - "fatal" => style_failure().add_modifier(Modifier::BOLD), - _ => style_default(), + "trace" => list_item.gray(), + "debug" => list_item.blue(), + "info" => list_item.style(Style::new().default()), + "warn" => list_item.style(Style::new().secondary()), + "error" => list_item.style(Style::new().failure()), + "fatal" => list_item.style(Style::new().failure().bold()), + _ => list_item.style(Style::new().default()), } } diff --git a/src/ui/radarr_ui/radarr_ui_utils_tests.rs b/src/ui/radarr_ui/radarr_ui_utils_tests.rs index 279905d..ddcecf8 100644 --- a/src/ui/radarr_ui/radarr_ui_utils_tests.rs +++ b/src/ui/radarr_ui/radarr_ui_utils_tests.rs @@ -2,32 +2,50 @@ mod tests { use super::super::*; use pretty_assertions::assert_str_eq; + use ratatui::prelude::Text; + use ratatui::text::Span; #[test] fn test_determine_log_style_by_level() { + let list_item = ListItem::new(Text::from(Span::raw("test"))); + assert_eq!( - determine_log_style_by_level("trace"), - Style::default().fg(Color::Gray) + style_log_list_item(list_item.clone(), "trace".to_string()), + list_item.clone().gray() ); assert_eq!( - determine_log_style_by_level("debug"), - Style::default().fg(Color::Blue) + style_log_list_item(list_item.clone(), "debug".to_string()), + list_item.clone().blue() ); - assert_eq!(determine_log_style_by_level("info"), style_default()); - assert_eq!(determine_log_style_by_level("warn"), style_secondary()); - assert_eq!(determine_log_style_by_level("error"), style_failure()); assert_eq!( - determine_log_style_by_level("fatal"), - style_failure().add_modifier(Modifier::BOLD) + style_log_list_item(list_item.clone(), "info".to_string()), + list_item.clone().style(Style::new().default()) + ); + assert_eq!( + style_log_list_item(list_item.clone(), "warn".to_string()), + list_item.clone().style(Style::new().secondary()) + ); + assert_eq!( + style_log_list_item(list_item.clone(), "error".to_string()), + list_item.clone().style(Style::new().failure()) + ); + assert_eq!( + style_log_list_item(list_item.clone(), "fatal".to_string()), + list_item.clone().style(Style::new().failure().bold()) + ); + assert_eq!( + style_log_list_item(list_item.clone(), "".to_string()), + list_item.style(Style::new().default()) ); - assert_eq!(determine_log_style_by_level(""), style_default()); } #[test] fn test_determine_log_style_by_level_case_insensitive() { + let list_item = ListItem::new(Text::from(Span::raw("test"))); + assert_eq!( - determine_log_style_by_level("TrAcE"), - Style::default().fg(Color::Gray) + style_log_list_item(list_item.clone(), "TrAcE".to_string()), + list_item.gray() ); } diff --git a/src/ui/radarr_ui/root_folders/mod.rs b/src/ui/radarr_ui/root_folders/mod.rs index 47c1cdf..906cfd1 100644 --- a/src/ui/radarr_ui/root_folders/mod.rs +++ b/src/ui/radarr_ui/root_folders/mod.rs @@ -6,11 +6,12 @@ use crate::app::App; use crate::models::radarr_models::RootFolder; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; use crate::models::Route; -use crate::ui::utils::{layout_block_top_border, style_primary}; +use crate::ui::utils::{layout_block_top_border}; use crate::ui::{ draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps, }; +use crate::ui::styles::ManagarrStyle; use crate::utils::convert_to_gb; #[cfg(test)] @@ -94,8 +95,7 @@ fn draw_root_folders(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { .len() .to_string(), ), - ]) - .style(style_primary()) + ]).primary() }, app.is_loading, true, diff --git a/src/ui/radarr_ui/system/mod.rs b/src/ui/radarr_ui/system/mod.rs index 523dc28..2a9c860 100644 --- a/src/ui/radarr_ui/system/mod.rs +++ b/src/ui/radarr_ui/system/mod.rs @@ -13,11 +13,10 @@ use ratatui::{ use crate::app::App; use crate::models::radarr_models::Task; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; -use crate::ui::radarr_ui::radarr_ui_utils::{ - convert_to_minutes_hours_days, determine_log_style_by_level, -}; +use crate::ui::radarr_ui::radarr_ui_utils::{convert_to_minutes_hours_days, style_log_list_item}; use crate::ui::radarr_ui::system::system_details_ui::SystemDetailsUi; -use crate::ui::utils::{layout_block_top_border, style_help, style_primary}; +use crate::ui::styles::ManagarrStyle; +use crate::ui::utils::layout_block_top_border; use crate::ui::{draw_table, ListProps, TableProps}; use crate::{ models::Route, @@ -117,7 +116,7 @@ fn draw_tasks(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { Cell::from(task_props.last_duration), Cell::from(task_props.next_execution), ]) - .style(style_primary()) + .primary() }, app.is_loading, false, @@ -177,7 +176,7 @@ pub(super) fn draw_queued_events(f: &mut Frame<'_>, app: &mut App<'_>, area: Rec Cell::from(started_string), Cell::from(duration.to_owned()), ]) - .style(style_primary()) + .primary() }, app.is_loading, false, @@ -190,10 +189,9 @@ fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { area, |log| { let log_line = log.to_string(); - let level = log_line.split('|').collect::>()[1]; - let style = determine_log_style_by_level(level); + let level = log_line.split('|').collect::>()[1].to_string(); - ListItem::new(Text::from(Span::raw(log_line))).style(style) + style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level) }, ListProps { content: &mut app.data.radarr_data.logs, @@ -206,16 +204,18 @@ fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { } fn draw_help(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - let mut help_text = Text::from(format!( - " {}", - app - .data - .radarr_data - .main_tabs - .get_active_tab_contextual_help() - .unwrap() - )); - help_text.patch_style(style_help()); + let help_text = Text::from( + format!( + " {}", + app + .data + .radarr_data + .main_tabs + .get_active_tab_contextual_help() + .unwrap() + ) + .help(), + ); let help_paragraph = Paragraph::new(help_text) .block(layout_block_top_border()) .alignment(Alignment::Left); diff --git a/src/ui/radarr_ui/system/system_details_ui.rs b/src/ui/radarr_ui/system/system_details_ui.rs index 82d6b34..a8454be 100644 --- a/src/ui/radarr_ui/system/system_details_ui.rs +++ b/src/ui/radarr_ui/system/system_details_ui.rs @@ -8,12 +8,13 @@ use crate::app::radarr::radarr_context_clues::SYSTEM_TASKS_CONTEXT_CLUES; use crate::app::App; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS}; use crate::models::Route; -use crate::ui::radarr_ui::radarr_ui_utils::determine_log_style_by_level; +use crate::ui::radarr_ui::radarr_ui_utils::style_log_list_item; use crate::ui::radarr_ui::system::{ draw_queued_events, draw_system_ui_layout, extract_task_props, TASK_TABLE_CONSTRAINTS, TASK_TABLE_HEADERS, }; -use crate::ui::utils::{borderless_block, style_primary, title_block}; +use crate::ui::styles::ManagarrStyle; +use crate::ui::utils::{borderless_block, title_block}; use crate::ui::{ draw_help_and_get_content_rect, draw_large_popup_over, draw_list_box, draw_medium_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, loading, DrawUi, ListProps, TableProps, @@ -75,10 +76,9 @@ fn draw_logs_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { area, |log| { let log_line = log.to_string(); - let level = log.text.split('|').collect::>()[1]; - let style = determine_log_style_by_level(level); + let level = log.text.split('|').collect::>()[1].to_string(); - ListItem::new(Text::from(Span::raw(log_line))).style(style) + style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level) }, ListProps { content: &mut app.data.radarr_data.log_details, @@ -124,7 +124,7 @@ fn draw_tasks_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { Cell::from(task_props.last_duration), Cell::from(task_props.next_execution), ]) - .style(style_primary()) + .primary() }, app.is_loading, true, diff --git a/src/ui/styles.rs b/src/ui/styles.rs new file mode 100644 index 0000000..4452fd8 --- /dev/null +++ b/src/ui/styles.rs @@ -0,0 +1,81 @@ +use ratatui::prelude::{Color, Styled}; +use ratatui::style::Stylize; + +pub const COLOR_ORANGE: Color = Color::Rgb(255, 170, 66); + +#[cfg(test)] +#[path = "styles_tests.rs"] +mod styles_tests; + +pub trait ManagarrStyle<'a, T>: Stylize<'a, T> +where + T: Default, +{ + #[allow(clippy::new_ret_no_self)] + fn new() -> T; + fn awaiting_import(self) -> T; + fn default(self) -> T; + fn failure(self) -> T; + fn help(self) -> T; + fn highlight(self) -> T; + fn primary(self) -> T; + fn secondary(self) -> T; + fn success(self) -> T; + fn system_function(self) -> T; + fn unmonitored(self) -> T; + fn warning(self) -> T; +} + +impl<'a, T, U> ManagarrStyle<'a, T> for U +where + U: Styled, + T: Default, +{ + fn new() -> T { + T::default() + } + + fn awaiting_import(self) -> T { + self.fg(COLOR_ORANGE) + } + + fn default(self) -> T { + self.white() + } + + fn failure(self) -> T { + self.red() + } + + fn help(self) -> T { + self.light_blue() + } + + fn highlight(self) -> T { + self.reversed() + } + + fn primary(self) -> T { + self.cyan() + } + + fn secondary(self) -> T { + self.yellow() + } + + fn success(self) -> T { + self.green() + } + + fn system_function(self) -> T { + self.yellow() + } + + fn unmonitored(self) -> T { + self.white() + } + + fn warning(self) -> T { + self.magenta() + } +} diff --git a/src/ui/styles_tests.rs b/src/ui/styles_tests.rs new file mode 100644 index 0000000..4507c49 --- /dev/null +++ b/src/ui/styles_tests.rs @@ -0,0 +1,73 @@ +#[cfg(test)] +mod test { + use crate::ui::styles::{ManagarrStyle, COLOR_ORANGE}; + use pretty_assertions::assert_eq; + use ratatui::prelude::Modifier; + use ratatui::style::{Style, Stylize}; + + #[test] + fn test_new() { + assert_eq!(Style::new(),