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

This commit is contained in:
2024-02-07 17:33:17 -07:00
parent 75420f4427
commit c6f51ab9b6
22 changed files with 509 additions and 544 deletions
Generated
+37 -8
View File
@@ -198,6 +198,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "castaway"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
dependencies = [
"rustversion",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.0.83"
@@ -244,6 +253,19 @@ dependencies = [
"windows-sys 0.48.0", "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]] [[package]]
name = "confy" name = "confy"
version = "0.6.0" version = "0.6.0"
@@ -861,7 +883,7 @@ dependencies = [
[[package]] [[package]]
name = "managarr" name = "managarr"
version = "0.0.31" version = "0.0.32"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"backtrace", "backtrace",
@@ -1199,12 +1221,13 @@ dependencies = [
[[package]] [[package]]
name = "ratatui" name = "ratatui"
version = "0.25.0" version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5659e52e4ba6e07b2dad9f1158f578ef84a73762625ddb51536019f34d180eb" checksum = "154b85ef15a5d1719bcaa193c3c81fe645cd120c156874cd660fe49fd21d1373"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.4.2",
"cassowary", "cassowary",
"compact_str",
"crossterm", "crossterm",
"indoc", "indoc",
"itertools", "itertools",
@@ -1582,19 +1605,25 @@ dependencies = [
] ]
[[package]] [[package]]
name = "strum" name = "static_assertions"
version = "0.25.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"strum_macros", "strum_macros",
] ]
[[package]] [[package]]
name = "strum_macros" name = "strum_macros"
version = "0.25.3" version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "managarr" name = "managarr"
version = "0.0.31" version = "0.0.32"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"] authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "A TUI to manage your Servarrs" description = "A TUI to manage your Servarrs"
keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui"] keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui"]
@@ -28,11 +28,11 @@ reqwest = { version = "0.11.14", features = ["json"] }
serde_yaml = "0.9.16" serde_yaml = "0.9.16"
serde_json = "1.0.91" serde_json = "1.0.91"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
strum = {version = "0.25.0", features = ["derive"] } strum = {version = "0.26.1", features = ["derive"] }
strum_macros = "0.25.0" strum_macros = "0.26.1"
tokio = { version = "1.29.0", features = ["full"] } tokio = { version = "1.29.0", features = ["full"] }
tokio-util = "0.7.8" 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" urlencoding = "2.1.2"
[dev-dependencies] [dev-dependencies]
+54 -74
View File
@@ -1,9 +1,9 @@
use std::iter; use std::iter;
use std::rc::Rc; use std::rc::Rc;
use ratatui::layout::{Alignment, Constraint, Rect}; use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::Modifier; use ratatui::style::{Modifier, Style, Stylize};
use ratatui::text::{Line, Span, Text}; use ratatui::text::{Line, Text};
use ratatui::widgets::Paragraph; use ratatui::widgets::Paragraph;
use ratatui::widgets::Row; use ratatui::widgets::Row;
use ratatui::widgets::Table; use ratatui::widgets::Table;
@@ -15,16 +15,16 @@ use ratatui::Frame;
use crate::app::App; use crate::app::App;
use crate::models::{HorizontallyScrollableText, Route, StatefulList, StatefulTable, TabState}; use crate::models::{HorizontallyScrollableText, Route, StatefulList, StatefulTable, TabState};
use crate::ui::radarr_ui::RadarrUi; use crate::ui::radarr_ui::RadarrUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
background_block, borderless_block, centered_rect, horizontal_chunks, background_block, borderless_block, centered_rect, horizontal_chunks,
horizontal_chunks_with_margin, layout_block, layout_block_top_border, layout_button_paragraph, horizontal_chunks_with_margin, layout_block, layout_block_top_border, layout_button_paragraph,
layout_button_paragraph_borderless, layout_paragraph_borderless, logo_block, show_cursor, layout_button_paragraph_borderless, layout_paragraph_borderless, logo_block, show_cursor,
style_block_highlight, style_default, style_default_bold, style_failure, style_help, style_block_highlight, title_block, title_block_centered, vertical_chunks_with_margin,
style_highlight, style_primary, style_secondary, style_system_function, title_block,
title_block_centered, vertical_chunks_with_margin,
}; };
mod radarr_ui; mod radarr_ui;
mod styles;
mod utils; mod utils;
static HIGHLIGHT_SYMBOL: &str = "=> "; static HIGHLIGHT_SYMBOL: &str = "=> ";
@@ -37,59 +37,53 @@ pub trait DrawUi {
pub fn ui(f: &mut Frame<'_>, app: &mut App<'_>) { pub fn ui(f: &mut Frame<'_>, app: &mut App<'_>) {
f.render_widget(background_block(), f.size()); f.render_widget(background_block(), f.size());
let main_chunks = if !app.error.text.is_empty() { let [header, context, table] = if !app.error.text.is_empty() {
let chunks = vertical_chunks_with_margin( let [header, error, context, table] = Layout::vertical([
vec![ Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(10),
Constraint::Length(10), Constraint::Fill(1),
Constraint::Length(0), ])
], .margin(1)
f.size(), .areas(f.size());
1,
);
draw_error(f, app, chunks[1]); draw_error(f, app, error);
Rc::new([chunks[0], chunks[2], chunks[3]]) [header, context, table]
} else { } else {
vertical_chunks_with_margin( Layout::vertical([
vec![ Constraint::Length(3),
Constraint::Length(3), Constraint::Length(10),
Constraint::Length(10), Constraint::Fill(1),
Constraint::Length(0), ])
], .margin(1)
f.size(), .areas(f.size())
1,
)
}; };
draw_header_row(f, app, main_chunks[0]); draw_header_row(f, app, header);
if RadarrUi::accepts(*app.get_current_route()) { if RadarrUi::accepts(*app.get_current_route()) {
RadarrUi::draw_context_row(f, app, main_chunks[1]); RadarrUi::draw_context_row(f, app, context);
RadarrUi::draw(f, app, main_chunks[2]); RadarrUi::draw(f, app, table);
} }
} }
fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let chunks = let chunks =
horizontal_chunks_with_margin(vec![Constraint::Length(75), Constraint::Min(0)], area, 1); 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 let titles = app
.server_tabs .server_tabs
.tabs .tabs
.iter() .iter()
.map(|tab| Line::from(Span::styled(tab.title, style_default_bold()))) .map(|tab| Line::from(tab.title.bold()));
.collect();
let tabs = Tabs::new(titles) let tabs = Tabs::new(titles)
.block(logo_block()) .block(logo_block())
.highlight_style(style_secondary()) .highlight_style(Style::new().secondary())
.select(app.server_tabs.index); .select(app.server_tabs.index);
let help = Paragraph::new(help_text) let help = Paragraph::new(help_text)
.block(borderless_block()) .block(borderless_block())
.style(style_help())
.alignment(Alignment::Right); .alignment(Alignment::Right);
f.render_widget(tabs, area); 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) { fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let block = let block = title_block("Error | <esc> to close").failure().bold();
title_block("Error | <esc> to close").style(style_failure().add_modifier(Modifier::BOLD));
app.error.scroll_left_or_reset( app.error.scroll_left_or_reset(
area.width as usize, 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, app.tick_count % app.ticks_until_scroll == 0,
); );
let mut text = Text::from(app.error.to_string()); let paragraph = Paragraph::new(Text::from(app.error.to_string()).failure())
text.patch_style(style_failure());
let paragraph = Paragraph::new(text)
.block(block) .block(block)
.wrap(Wrap { trim: true }) .wrap(Wrap { trim: true })
.style(style_primary()); .primary();
f.render_widget(paragraph, area); 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); f.render_widget(background_block(), prompt_area);
let error_message = Paragraph::new(Text::from(message)) let error_message = Paragraph::new(Text::from(message))
.block(title_block_centered("Error").style(style_failure())) .block(title_block_centered("Error").failure())
.style(style_failure().add_modifier(Modifier::BOLD)) .failure()
.bold()
.wrap(Wrap { trim: false }) .wrap(Wrap { trim: false })
.alignment(Alignment::Center); .alignment(Alignment::Center);
@@ -262,24 +253,21 @@ fn draw_tabs<'a>(
let chunks = let chunks =
vertical_chunks_with_margin(vec![Constraint::Length(2), Constraint::Min(0)], area, 1); vertical_chunks_with_margin(vec![Constraint::Length(2), Constraint::Min(0)], area, 1);
let horizontal_chunks = horizontal_chunks_with_margin( let horizontal_chunks = horizontal_chunks_with_margin(
vec![Constraint::Percentage(10), Constraint::Min(0)], vec![Constraint::Percentage(10), Constraint::Fill(1)],
area, area,
1, 1,
); );
let block = title_block(title); 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 let titles = tab_state
.tabs .tabs
.iter() .iter()
.map(|tab_route| Line::from(Span::styled(tab_route.title, style_default_bold()))) .map(|tab_route| Line::from(tab_route.title.bold()));
.collect();
let tabs = Tabs::new(titles) let tabs = Tabs::new(titles)
.block(block) .block(block)
.highlight_style(style_secondary()) .highlight_style(Style::new().secondary())
.select(tab_state.index); .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()) .block(borderless_block())
.alignment(Alignment::Right); .alignment(Alignment::Right);
@@ -369,15 +357,13 @@ fn draw_table_contents<'a, T, F>(
{ {
let rows = content.items.iter().map(row_mapper); let rows = content.items.iter().map(row_mapper);
let headers = Row::new(table_headers) let headers = Row::new(table_headers).default().bold().bottom_margin(0);
.style(style_default_bold())
.bottom_margin(0);
let mut table = Table::new(rows, &constraints).header(headers).block(block); let mut table = Table::new(rows, &constraints).header(headers).block(block);
if highlight { if highlight {
table = table table = table
.highlight_style(style_highlight()) .highlight_style(Style::new().highlight())
.highlight_symbol(HIGHLIGHT_SYMBOL); .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) { pub fn loading(f: &mut Frame<'_>, block: Block<'_>, area: Rect, is_loading: bool) {
if is_loading { if is_loading {
let text = "\n\n Loading ...\n\n".to_owned(); let paragraph = Paragraph::new(Text::from("\n\n Loading ...\n\n"))
let mut text = Text::from(text); .system_function()
text.patch_style(style_system_function());
let paragraph = Paragraph::new(text)
.style(style_system_function())
.block(block); .block(block);
f.render_widget(paragraph, area); f.render_widget(paragraph, area);
} else { } else {
@@ -538,7 +520,7 @@ pub fn draw_checkbox_with_label(
let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: "))) let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: ")))
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Right) .alignment(Alignment::Right)
.style(style_primary()); .primary();
f.render_widget(label_paragraph, horizontal_chunks[0]); 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}: "))) let description_paragraph = Paragraph::new(Text::from(format!("\n{description}: ")))
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Right) .alignment(Alignment::Right)
.style(style_primary()); .primary();
f.render_widget(description_paragraph, horizontal_chunks[0]); f.render_widget(description_paragraph, horizontal_chunks[0]);
@@ -614,7 +596,7 @@ pub fn draw_selectable_list<'a, T>(
let items: Vec<ListItem<'_>> = content.items.iter().map(item_mapper).collect(); let items: Vec<ListItem<'_>> = content.items.iter().map(item_mapper).collect();
let list = List::new(items) let list = List::new(items)
.block(layout_block()) .block(layout_block())
.highlight_style(style_highlight()); .highlight_style(Style::new().highlight());
f.render_stateful_widget(list, area, &mut content.state); 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); let mut list = List::new(items).block(block);
if is_popup { 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); 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<St
let chunks = let chunks =
vertical_chunks_with_margin(vec![Constraint::Min(0), Constraint::Length(2)], area, 1); vertical_chunks_with_margin(vec![Constraint::Min(0), Constraint::Length(2)], area, 1);
let mut help_test = Text::from(format!(" {help_string}")); let help_paragraph = Paragraph::new(Text::from(format!(" {help_string}").help()))
help_test.patch_style(style_help());
let help_paragraph = Paragraph::new(help_test)
.block(layout_block_top_border()) .block(layout_block_top_border())
.alignment(Alignment::Left); .alignment(Alignment::Left);
@@ -697,12 +677,12 @@ pub fn draw_text_box(f: &mut Frame<'_>, text_box_props: TextBoxProps<'_>) {
cursor_after_string, cursor_after_string,
} = text_box_props; } = text_box_props;
let (block, style) = if let Some(title) = block_title { let (block, style) = if let Some(title) = block_title {
(title_block_centered(title), style_default()) (title_block_centered(title), Style::new().default())
} else { } else {
( (
layout_block(), layout_block(),
if should_show_cursor { if should_show_cursor {
style_default() Style::new().default()
} else { } else {
style_block_highlight(is_selected) 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}: "))) let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: ")))
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Right) .alignment(Alignment::Right)
.style(style_primary()); .primary();
f.render_widget(label_paragraph, horizontal_chunks[0]); f.render_widget(label_paragraph, horizontal_chunks[0]);
@@ -801,7 +781,7 @@ pub fn draw_input_box_popup(
); );
let help = Paragraph::new("<esc> cancel") let help = Paragraph::new("<esc> cancel")
.style(style_help()) .help()
.alignment(Alignment::Center) .alignment(Alignment::Center)
.block(borderless_block()); .block(borderless_block());
f.render_widget(help, chunks[1]); 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) { pub fn draw_error_message_popup(f: &mut Frame<'_>, error_message_area: Rect, error_msg: &str) {
let input = Paragraph::new(error_msg) let input = Paragraph::new(error_msg)
.style(style_failure()) .failure()
.alignment(Alignment::Center) .alignment(Alignment::Center)
.block(layout_block()); .block(layout_block());
@@ -1,5 +1,6 @@
use ratatui::layout::{Alignment, Constraint, Rect}; 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::widgets::{Cell, Paragraph, Row, Wrap};
use ratatui::Frame; use ratatui::Frame;
@@ -12,10 +13,10 @@ use crate::models::servarr_data::radarr::radarr_data::{
}; };
use crate::models::Route; use crate::models::Route;
use crate::ui::radarr_ui::collections::draw_collections; use crate::ui::radarr_ui::collections::draw_collections;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, get_width_from_percentage, layout_block_top_border_with_title, borderless_block, get_width_from_percentage, layout_block_top_border_with_title, title_block,
line_info_primary, style_default, style_help, style_primary, title_block, title_style, title_style, vertical_chunks_with_margin,
vertical_chunks_with_margin,
}; };
use crate::ui::{draw_large_popup_over, draw_small_popup_over, draw_table, DrawUi, TableProps}; use crate::ui::{draw_large_popup_over, draw_small_popup_over, draw_table, DrawUi, TableProps};
use crate::utils::convert_runtime; use crate::utils::convert_runtime;
@@ -98,11 +99,13 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, content_are
.current_selection() .current_selection()
.clone() .clone()
}; };
let mut help_text = Text::from(format!( let help_text = Text::from(
"<↑↓> scroll table | {}", format!(
build_context_clue_string(&COLLECTION_DETAILS_CONTEXT_CLUES) "<↑↓> scroll table | {}",
)); build_context_clue_string(&COLLECTION_DETAILS_CONTEXT_CLUES)
help_text.patch_style(style_help()); )
.help(),
);
let monitored = if collection_selection.monitored { let monitored = if collection_selection.monitored {
"Yes" "Yes"
} else { } 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 minimum_availability = collection_selection.minimum_availability.to_display_str();
let collection_description = Text::from(vec![ let collection_description = Text::from(vec![
line_info_primary( Line::from(vec![
"Overview: ".to_owned(), "Overview ".primary().bold(),
collection_selection.overview.clone().unwrap_or_default(), collection_selection
), .overview
line_info_primary( .clone()
"Root Folder Path: ".to_owned(), .unwrap_or_default()
.default(),
]),
Line::from(vec![
"Root Folder Path: ".primary().bold(),
collection_selection collection_selection
.root_folder_path .root_folder_path
.clone() .clone()
.unwrap_or_default(), .unwrap_or_default()
), .default(),
line_info_primary("Quality Profile: ".to_owned(), quality_profile), ]),
line_info_primary( Line::from(vec![
"Minimum Availability: ".to_owned(), "Quality Profile: ".primary().bold(),
minimum_availability.to_owned(), quality_profile.default(),
), ]),
line_info_primary("Monitored: ".to_owned(), monitored.to_owned()), Line::from(vec![
line_info_primary("Search on Add: ".to_owned(), search_on_add.to_owned()), "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) 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(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")), Cell::from(movie.genres.join(", ")),
]) ])
.style(style_primary()) .primary()
}, },
app.is_loading, app.is_loading,
true, true,
@@ -246,7 +260,7 @@ fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect)
content_area, content_area,
1, 1,
); );
let mut overview = Text::from( let overview = Text::from(
app app
.data .data
.radarr_data .radarr_data
@@ -254,10 +268,9 @@ fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect)
.current_selection() .current_selection()
.clone() .clone()
.overview, .overview,
); )
overview.patch_style(style_default()); .default();
let mut help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES)); let help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES).help());
help_text.patch_style(style_help());
let paragraph = Paragraph::new(overview) let paragraph = Paragraph::new(overview)
.block(borderless_block()) .block(borderless_block())
+3 -3
View File
@@ -10,11 +10,12 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, COLLEC
use crate::models::Route; use crate::models::Route;
use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi; use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi;
use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi; 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::{ use crate::ui::{
draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box,
draw_prompt_popup_over, draw_table, DrawUi, TableProps, draw_prompt_popup_over, draw_table, DrawUi, TableProps,
}; };
use crate::ui::styles::ManagarrStyle;
mod collection_details_ui; mod collection_details_ui;
#[cfg(test)] #[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(search_on_add),
Cell::from(monitored), Cell::from(monitored),
]) ]).primary()
.style(style_primary())
}, },
app.is_loading, app.is_loading,
true, true,
+3 -3
View File
@@ -6,8 +6,9 @@ use crate::app::App;
use crate::models::radarr_models::DownloadRecord; use crate::models::radarr_models::DownloadRecord;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
use crate::models::{HorizontallyScrollableText, Route}; 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::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps};
use crate::ui::styles::ManagarrStyle;
use crate::utils::convert_to_gb; use crate::utils::convert_to_gb;
#[cfg(test)] #[cfg(test)]
@@ -119,8 +120,7 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
), ),
Cell::from(indexer.to_owned()), Cell::from(indexer.to_owned()),
Cell::from(download_client.to_owned()), Cell::from(download_client.to_owned()),
]) ]).primary()
.style(style_primary())
}, },
app.is_loading, app.is_loading,
true, true,
+13 -15
View File
@@ -10,7 +10,7 @@ use crate::ui::{
draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading, draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading,
DrawUi, LabeledTextBoxProps, DrawUi, LabeledTextBoxProps,
}; };
use ratatui::layout::{Constraint, Rect}; use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::Frame; use ratatui::Frame;
use std::iter; use std::iter;
@@ -66,16 +66,14 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R
1, 1,
); );
let left_chunks = vertical_chunks( let [name, rss, auto_search, interactive_search, _] = Layout::vertical([
vec![ Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Min(0),
Constraint::Min(0), ])
], .areas(split_chunks[0]);
split_chunks[0],
);
let right_chunks = vertical_chunks( let right_chunks = vertical_chunks(
vec![ vec![
Constraint::Length(3), 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( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: left_chunks[0], area: name,
label: "Name", label: "Name",
text: &edit_indexer_modal.name.text, text: &edit_indexer_modal.name.text,
offset: *edit_indexer_modal.name.offset.borrow(), 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( draw_checkbox_with_label(
f, f,
left_chunks[1], rss,
"Enable RSS", "Enable RSS",
edit_indexer_modal.enable_rss.unwrap_or_default(), edit_indexer_modal.enable_rss.unwrap_or_default(),
selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableRss, selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableRss,
); );
draw_checkbox_with_label( draw_checkbox_with_label(
f, f,
left_chunks[2], auto_search,
"Enable Automatic Search", "Enable Automatic Search",
edit_indexer_modal edit_indexer_modal
.enable_automatic_search .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( draw_checkbox_with_label(
f, f,
left_chunks[3], interactive_search,
"Enable Interactive Search", "Enable Interactive Search",
edit_indexer_modal edit_indexer_modal
.enable_interactive_search .enable_interactive_search
+8 -16
View File
@@ -10,7 +10,8 @@ use crate::models::Route;
use crate::ui::radarr_ui::indexers::edit_indexer_ui::EditIndexerUi; 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::indexer_settings_ui::IndexerSettingsUi;
use crate::ui::radarr_ui::indexers::test_all_indexers_ui::TestAllIndexersUi; 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}; use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps};
mod edit_indexer_ui; mod edit_indexer_ui;
@@ -103,24 +104,15 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
} = indexer; } = indexer;
let bool_to_text = |flag: bool| { let bool_to_text = |flag: bool| {
if flag { 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 rss = bool_to_text(*enable_rss);
let mut rss = Text::from(rss_text); let automatic_search = bool_to_text(*enable_automatic_search);
rss.patch_style(rss_style); let interactive_search = bool_to_text(*enable_interactive_search);
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 tags: String = tags let tags: String = tags
.iter() .iter()
.map(|tag_id| { .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(priority.to_string()),
Cell::from(tags), Cell::from(tags),
]) ])
.style(style_primary()) .primary()
}, },
app.is_loading, app.is_loading,
true, true,
@@ -4,9 +4,8 @@ use crate::models::servarr_data::radarr::modals::IndexerTestResultModalItem;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::Route; use crate::models::Route;
use crate::ui::radarr_ui::indexers::draw_indexers; use crate::ui::radarr_ui::indexers::draw_indexers;
use crate::ui::utils::{ use crate::ui::styles::ManagarrStyle;
borderless_block, get_width_from_percentage, style_failure, style_success, title_block, use crate::ui::utils::{borderless_block, get_width_from_percentage, title_block};
};
use crate::ui::{ use crate::ui::{
draw_help_and_get_content_rect, draw_large_popup_over, draw_table, DrawUi, TableProps, 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, app.tick_count % app.ticks_until_scroll == 0,
); );
let pass_fail = if result.is_valid { "" } else { "" }; let pass_fail = if result.is_valid { "" } else { "" };
let row_style = if result.is_valid { let row = Row::new(vec![
style_success()
} else {
style_failure()
};
Row::new(vec![
Cell::from(result.name.to_owned()), Cell::from(result.name.to_owned()),
Cell::from(pass_fail.to_owned()), Cell::from(pass_fail.to_owned()),
Cell::from(result.validation_failures.to_string()), Cell::from(result.validation_failures.to_string()),
]) ]);
.style(row_style)
if result.is_valid {
row.success()
} else {
row.failure()
}
}, },
app.is_loading, app.is_loading,
true, true,
+6 -9
View File
@@ -11,10 +11,10 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ADD_MO
use crate::models::Route; use crate::models::Route;
use crate::ui::radarr_ui::collections::{draw_collection_details, draw_collections}; use crate::ui::radarr_ui::collections::{draw_collection_details, draw_collections};
use crate::ui::radarr_ui::library::draw_library; use crate::ui::radarr_ui::library::draw_library;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, get_width_from_percentage, horizontal_chunks, layout_block, borderless_block, get_width_from_percentage, horizontal_chunks, layout_block,
layout_paragraph_borderless, style_help, style_primary, title_block_centered, layout_paragraph_borderless, title_block_centered, vertical_chunks_with_margin,
vertical_chunks_with_margin,
}; };
use crate::ui::{ use crate::ui::{
draw_button, draw_drop_down_menu_button, draw_drop_down_popup, draw_error_popup, 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]); f.render_widget(layout_block(), chunks[1]);
let mut help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES)); let help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES).help());
help_text.patch_style(style_help());
let help_paragraph = Paragraph::new(help_text) let help_paragraph = Paragraph::new(help_text)
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Center); .alignment(Alignment::Center);
@@ -167,10 +166,8 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
| ActiveRadarrBlock::AddMovieSelectRootFolder | ActiveRadarrBlock::AddMovieSelectRootFolder
| ActiveRadarrBlock::AddMovieAlreadyInLibrary | ActiveRadarrBlock::AddMovieAlreadyInLibrary
| ActiveRadarrBlock::AddMovieTagsInput => { | ActiveRadarrBlock::AddMovieTagsInput => {
let mut help_text = Text::from(build_context_clue_string( let help_text =
&ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, Text::from(build_context_clue_string(&ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES).help());
));
help_text.patch_style(style_help());
let help_paragraph = Paragraph::new(help_text) let help_paragraph = Paragraph::new(help_text)
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Center); .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(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")), Cell::from(movie.genres.join(", ")),
]) ])
.style(style_primary()) .primary()
}, },
is_loading, is_loading,
true, true,
+17 -14
View File
@@ -6,7 +6,7 @@ use crate::app::App;
use crate::models::radarr_models::Movie; use crate::models::radarr_models::Movie;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, LIBRARY_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, LIBRARY_BLOCKS};
use crate::models::Route; 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::add_movie_ui::AddMovieUi;
use crate::ui::radarr_ui::library::delete_movie_ui::DeleteMovieUi; use crate::ui::radarr_ui::library::delete_movie_ui::DeleteMovieUi;
use crate::ui::radarr_ui::library::edit_movie_ui::EditMovieUi; 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::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");
Row::new(vec![ decorate_with_row_style(
Cell::from(movie.title.to_string()), downloads_vec,
Cell::from(movie.year.to_string()), movie,
Cell::from(movie.studio.to_string()), Row::new(vec![
Cell::from(format!("{hours}h {minutes}m")), Cell::from(movie.title.to_string()),
Cell::from(certification), Cell::from(movie.year.to_string()),
Cell::from(movie.original_language.name.to_owned()), Cell::from(movie.studio.to_string()),
Cell::from(format!("{file_size:.2} GB")), Cell::from(format!("{hours}h {minutes}m")),
Cell::from(quality_profile), Cell::from(certification),
Cell::from(monitored.to_owned()), Cell::from(movie.original_language.name.to_owned()),
Cell::from(tags), Cell::from(format!("{file_size:.2} GB")),
]) Cell::from(quality_profile),
.style(determine_row_style(downloads_vec, movie)) Cell::from(monitored.to_owned()),
Cell::from(tags),
]),
)
}, },
app.is_loading, app.is_loading,
true, true,
+34 -38
View File
@@ -1,7 +1,7 @@
use std::iter; use std::iter;
use ratatui::layout::{Alignment, Constraint, Rect}; use ratatui::layout::{Alignment, Constraint, Rect};
use ratatui::style::{Modifier, Style}; use ratatui::style::{Style, Stylize};
use ratatui::text::{Line, Span, Text}; use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{Cell, ListItem, Paragraph, Row, Wrap}; use ratatui::widgets::{Cell, ListItem, Paragraph, Row, Wrap};
use ratatui::Frame; 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::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS};
use crate::models::Route; use crate::models::Route;
use crate::ui::radarr_ui::library::draw_library; use crate::ui::radarr_ui::library::draw_library;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, get_width_from_percentage, layout_block_bottom_border, layout_block_top_border, 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, vertical_chunks,
style_success, style_warning, vertical_chunks,
}; };
use crate::ui::{ use crate::ui::{
draw_drop_down_popup, draw_large_popup_over, draw_prompt_box, draw_prompt_box_with_content, 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, 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 = 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 = 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 = 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 file_details = Text::from(file_info);
let audio_details = Text::from(audio_details); let audio_details = Text::from(audio_details);
@@ -214,19 +208,22 @@ fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, content_area: Rect) {
.unwrap() .unwrap()
.split(": ") .split(": ")
.collect::<Vec<&str>>()[1]; .collect::<Vec<&str>>()[1];
let mut text = Text::from( let text = Text::from(
movie_details movie_details
.items .items
.iter() .iter()
.map(|line| { .map(|line| {
let split = line.split(':').collect::<Vec<&str>>(); let split = line.split(':').collect::<Vec<&str>>();
let title = format!("{}:", split[0]); 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::<Vec<Line<'_>>>(), .collect::<Vec<Line<'_>>>(),
); );
text.patch_style(determine_style_from_download_status(download_status));
let paragraph = Paragraph::new(text) let paragraph = Paragraph::new(text)
.block(block) .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(quality.quality.name.to_owned()),
Cell::from(date.to_string()), Cell::from(date.to_string()),
]) ])
.style(style_success()) .success()
}, },
app.is_loading, app.is_loading,
true, 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(person_name.to_owned()),
Cell::from(character.clone().unwrap_or_default()), Cell::from(character.clone().unwrap_or_default()),
]) ])
.style(style_success()) .success()
}, },
app.is_loading, app.is_loading,
true, 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(job.clone().unwrap_or_default()),
Cell::from(department.clone().unwrap_or_default()), Cell::from(department.clone().unwrap_or_default()),
]) ])
.style(style_success()) .success()
}, },
app.is_loading, app.is_loading,
true, 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 size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" }; let rejected_str = if *rejected { "" } else { "" };
let peers = if seeders.is_none() || leechers.is_none() { let peers = if seeders.is_none() || leechers.is_none() {
Text::default() Text::from("")
} else { } else {
let seeders = seeders.clone().unwrap().as_u64().unwrap(); let seeders = seeders.clone().unwrap().as_u64().unwrap();
let leechers = leechers.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() { 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(language),
Cell::from(quality), Cell::from(quality),
]) ])
.style(style_primary()) .primary()
}, },
app.is_loading || is_empty, app.is_loading || is_empty,
true, true,
@@ -567,16 +566,13 @@ fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp
}; };
if current_selection.rejected { if current_selection.rejected {
let mut lines_vec = vec![Line::from(vec![Span::styled( let mut lines_vec = vec![Line::from("Rejection reasons: ".primary().bold())];
"Rejection reasons: ",
style_primary().add_modifier(Modifier::BOLD),
)])];
let mut rejections_spans = current_selection let mut rejections_spans = current_selection
.rejections .rejections
.clone() .clone()
.unwrap_or_default() .unwrap_or_default()
.iter() .iter()
.map(|item| Line::from(vec![Span::styled(format!("{item}"), style_primary())])) .map(|item| Line::from(format!("{item}").primary().bold()))
.collect::<Vec<Line<'_>>>(); .collect::<Vec<Line<'_>>>();
lines_vec.append(&mut rejections_spans); 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 { match download_status {
"Downloaded" => style_success(), "Downloaded" => Style::new().success(),
"Awaiting Import" => style_awaiting_import(), "Awaiting Import" => Style::new().awaiting_import(),
"Downloading" => style_warning(), "Downloading" => Style::new().warning(),
"Missing" => style_failure(), "Missing" => Style::new().failure(),
_ => style_success(), _ => 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 { if seeders == 0 {
style_failure() text.failure()
} else if seeders < leechers { } else if seeders < leechers {
style_warning() text.warning()
} else { } else {
style_success() text.success()
} }
} }
+30 -30
View File
@@ -1,10 +1,10 @@
use std::iter; use std::iter;
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use ratatui::layout::{Alignment, Constraint, Rect}; use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Color, Style}; use ratatui::prelude::Stylize;
use ratatui::text::Text; use ratatui::text::Text;
use ratatui::widgets::Paragraph; use ratatui::widgets::{Paragraph, Row};
use ratatui::Frame; use ratatui::Frame;
use crate::app::App; 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::library::LibraryUi;
use crate::ui::radarr_ui::root_folders::RootFoldersUi; use crate::ui::radarr_ui::root_folders::RootFoldersUi;
use crate::ui::radarr_ui::system::SystemUi; use crate::ui::radarr_ui::system::SystemUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, horizontal_chunks, layout_block, line_gauge_with_label, line_gauge_with_title, borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
style_awaiting_import, style_bold, style_default, style_failure, style_success, vertical_chunks_with_margin,
style_unmonitored, style_warning, title_block, vertical_chunks_with_margin,
}; };
use crate::ui::DrawUi; use crate::ui::DrawUi;
use crate::utils::convert_to_gb; 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) { 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( let [stats, downloads] =
vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).areas(main);
chunks[0],
);
draw_stats_context(f, app, context_chunks[0]); draw_stats_context(f, app, stats);
draw_downloads_context(f, app, context_chunks[1]); draw_downloads_context(f, app, downloads);
draw_radarr_logo(f, chunks[1]); 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 app.data.radarr_data.version
))) )))
.block(borderless_block()) .block(borderless_block())
.style(style_bold()); .bold();
let uptime = Utc::now() - start_time.to_owned(); let uptime = Utc::now() - start_time.to_owned();
let days = uptime.num_days(); let days = uptime.num_days();
@@ -122,12 +120,10 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
width = 2 width = 2
))) )))
.block(borderless_block()) .block(borderless_block())
.style(style_bold()); .bold();
let storage = let storage = Paragraph::new(Text::from("Storage:")).block(borderless_block().bold());
Paragraph::new(Text::from("Storage:")).block(borderless_block().style(style_bold())); let folders = Paragraph::new(Text::from("Root Folders:")).block(borderless_block().bold());
let folders =
Paragraph::new(Text::from("Root Folders:")).block(borderless_block().style(style_bold()));
f.render_widget(version_paragraph, chunks[0]); f.render_widget(version_paragraph, chunks[0]);
f.render_widget(uptime_paragraph, chunks[1]); 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 space: f64 = convert_to_gb(*free_space);
let root_folder_space = Paragraph::new(format!("{path}: {space:.2} GB free")) let root_folder_space = Paragraph::new(format!("{path}: {space:.2} GB free"))
.block(borderless_block()) .block(borderless_block())
.style(style_default()); .default();
f.render_widget(root_folder_space, chunks[i + disk_space_vec.len() + 4]) 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 !movie.has_file {
if let Some(download) = downloads_vec if let Some(download) = downloads_vec
.iter() .iter()
.find(|&download| download.movie_id == movie.id) .find(|&download| download.movie_id == movie.id)
{ {
if download.status == "downloading" { if download.status == "downloading" {
return style_warning(); return row.warning();
} }
if download.status == "completed" { if download.status == "completed" {
return style_awaiting_import(); return row.awaiting_import();
} }
} }
return style_failure(); return row.failure();
} }
if !movie.monitored { if !movie.monitored {
style_unmonitored() row.unmonitored()
} else { } else {
style_success() row.success()
} }
} }
fn draw_radarr_logo(f: &mut Frame<'_>, area: Rect) { fn draw_radarr_logo(f: &mut Frame<'_>, area: Rect) {
let mut logo_text = Text::from(RADARR_LOGO); let logo_text = Text::from(RADARR_LOGO);
logo_text.patch_style(Style::default().fg(Color::LightYellow));
let logo = Paragraph::new(logo_text) let logo = Paragraph::new(logo_text)
.block(layout_block()) .light_yellow()
.block(layout_block().default())
.alignment(Alignment::Center); .alignment(Alignment::Center);
f.render_widget(logo, area); f.render_widget(logo, area);
} }
+12 -10
View File
@@ -1,19 +1,21 @@
use crate::ui::utils::{style_default, style_failure, style_secondary}; use ratatui::style::{Style, Stylize};
use ratatui::style::{Color, Modifier, Style}; use ratatui::widgets::ListItem;
use crate::ui::styles::ManagarrStyle;
#[cfg(test)] #[cfg(test)]
#[path = "radarr_ui_utils_tests.rs"] #[path = "radarr_ui_utils_tests.rs"]
mod radarr_ui_utils_tests; 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() { match level.to_lowercase().as_str() {
"trace" => Style::default().fg(Color::Gray), "trace" => list_item.gray(),
"debug" => Style::default().fg(Color::Blue), "debug" => list_item.blue(),
"info" => style_default(), "info" => list_item.style(Style::new().default()),
"warn" => style_secondary(), "warn" => list_item.style(Style::new().secondary()),
"error" => style_failure(), "error" => list_item.style(Style::new().failure()),
"fatal" => style_failure().add_modifier(Modifier::BOLD), "fatal" => list_item.style(Style::new().failure().bold()),
_ => style_default(), _ => list_item.style(Style::new().default()),
} }
} }
+30 -12
View File
@@ -2,32 +2,50 @@
mod tests { mod tests {
use super::super::*; use super::super::*;
use pretty_assertions::assert_str_eq; use pretty_assertions::assert_str_eq;
use ratatui::prelude::Text;
use ratatui::text::Span;
#[test] #[test]
fn test_determine_log_style_by_level() { fn test_determine_log_style_by_level() {
let list_item = ListItem::new(Text::from(Span::raw("test")));
assert_eq!( assert_eq!(
determine_log_style_by_level("trace"), style_log_list_item(list_item.clone(), "trace".to_string()),
Style::default().fg(Color::Gray) list_item.clone().gray()
); );
assert_eq!( assert_eq!(
determine_log_style_by_level("debug"), style_log_list_item(list_item.clone(), "debug".to_string()),
Style::default().fg(Color::Blue) 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!( assert_eq!(
determine_log_style_by_level("fatal"), style_log_list_item(list_item.clone(), "info".to_string()),
style_failure().add_modifier(Modifier::BOLD) 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] #[test]
fn test_determine_log_style_by_level_case_insensitive() { fn test_determine_log_style_by_level_case_insensitive() {
let list_item = ListItem::new(Text::from(Span::raw("test")));
assert_eq!( assert_eq!(
determine_log_style_by_level("TrAcE"), style_log_list_item(list_item.clone(), "TrAcE".to_string()),
Style::default().fg(Color::Gray) list_item.gray()
); );
} }
+3 -3
View File
@@ -6,11 +6,12 @@ use crate::app::App;
use crate::models::radarr_models::RootFolder; use crate::models::radarr_models::RootFolder;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS};
use crate::models::Route; 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::{ use crate::ui::{
draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table,
DrawUi, TableProps, DrawUi, TableProps,
}; };
use crate::ui::styles::ManagarrStyle;
use crate::utils::convert_to_gb; use crate::utils::convert_to_gb;
#[cfg(test)] #[cfg(test)]
@@ -94,8 +95,7 @@ fn draw_root_folders(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.len() .len()
.to_string(), .to_string(),
), ),
]) ]).primary()
.style(style_primary())
}, },
app.is_loading, app.is_loading,
true, true,
+19 -19
View File
@@ -13,11 +13,10 @@ use ratatui::{
use crate::app::App; use crate::app::App;
use crate::models::radarr_models::Task; use crate::models::radarr_models::Task;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::ui::radarr_ui::radarr_ui_utils::{ use crate::ui::radarr_ui::radarr_ui_utils::{convert_to_minutes_hours_days, style_log_list_item};
convert_to_minutes_hours_days, determine_log_style_by_level,
};
use crate::ui::radarr_ui::system::system_details_ui::SystemDetailsUi; 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::ui::{draw_table, ListProps, TableProps};
use crate::{ use crate::{
models::Route, 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.last_duration),
Cell::from(task_props.next_execution), Cell::from(task_props.next_execution),
]) ])
.style(style_primary()) .primary()
}, },
app.is_loading, app.is_loading,
false, 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(started_string),
Cell::from(duration.to_owned()), Cell::from(duration.to_owned()),
]) ])
.style(style_primary()) .primary()
}, },
app.is_loading, app.is_loading,
false, false,
@@ -190,10 +189,9 @@ fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
area, area,
|log| { |log| {
let log_line = log.to_string(); let log_line = log.to_string();
let level = log_line.split('|').collect::<Vec<&str>>()[1]; let level = log_line.split('|').collect::<Vec<&str>>()[1].to_string();
let style = determine_log_style_by_level(level);
ListItem::new(Text::from(Span::raw(log_line))).style(style) style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level)
}, },
ListProps { ListProps {
content: &mut app.data.radarr_data.logs, 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) { fn draw_help(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let mut help_text = Text::from(format!( let help_text = Text::from(
" {}", format!(
app " {}",
.data app
.radarr_data .data
.main_tabs .radarr_data
.get_active_tab_contextual_help() .main_tabs
.unwrap() .get_active_tab_contextual_help()
)); .unwrap()
help_text.patch_style(style_help()); )
.help(),
);
let help_paragraph = Paragraph::new(help_text) let help_paragraph = Paragraph::new(help_text)
.block(layout_block_top_border()) .block(layout_block_top_border())
.alignment(Alignment::Left); .alignment(Alignment::Left);
+6 -6
View File
@@ -8,12 +8,13 @@ use crate::app::radarr::radarr_context_clues::SYSTEM_TASKS_CONTEXT_CLUES;
use crate::app::App; use crate::app::App;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::models::Route; 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::{ use crate::ui::radarr_ui::system::{
draw_queued_events, draw_system_ui_layout, extract_task_props, TASK_TABLE_CONSTRAINTS, draw_queued_events, draw_system_ui_layout, extract_task_props, TASK_TABLE_CONSTRAINTS,
TASK_TABLE_HEADERS, 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::{ use crate::ui::{
draw_help_and_get_content_rect, draw_large_popup_over, draw_list_box, draw_medium_popup_over, 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, 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, area,
|log| { |log| {
let log_line = log.to_string(); let log_line = log.to_string();
let level = log.text.split('|').collect::<Vec<&str>>()[1]; let level = log.text.split('|').collect::<Vec<&str>>()[1].to_string();
let style = determine_log_style_by_level(level);
ListItem::new(Text::from(Span::raw(log_line))).style(style) style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level)
}, },
ListProps { ListProps {
content: &mut app.data.radarr_data.log_details, 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.last_duration),
Cell::from(task_props.next_execution), Cell::from(task_props.next_execution),
]) ])
.style(style_primary()) .primary()
}, },
app.is_loading, app.is_loading,
true, true,
+81
View File
@@ -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<Item = T>,
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()
}
}
+73
View File
@@ -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(), <Style as Default>::default())
}
#[test]
fn test_style_awaiting_import() {
assert_eq!(
Style::new().awaiting_import(),
Style::new().fg(COLOR_ORANGE)
);
}
#[test]
fn test_style_default() {
assert_eq!(Style::new().default(), Style::new().white());
}
#[test]
fn test_style_failure() {
assert_eq!(Style::new().failure(), Style::new().red());
}
#[test]
fn test_style_help() {
assert_eq!(Style::new().help(), Style::new().light_blue());
}
#[test]
fn test_style_highlight() {
assert_eq!(
Style::new().highlight(),
Style::new().add_modifier(Modifier::REVERSED)
);
}
#[test]
fn test_style_primary() {
assert_eq!(Style::new().primary(), Style::new().cyan());
}
#[test]
fn test_style_secondary() {
assert_eq!(Style::new().secondary(), Style::new().yellow());
}
#[test]
fn test_style_success() {
assert_eq!(Style::new().success(), Style::new().green());
}
#[test]
fn test_style_system_function() {
assert_eq!(Style::new().system_function(), Style::new().yellow());
}
#[test]
fn test_style_unmonitored() {
assert_eq!(Style::new().unmonitored(), Style::new().white());
}
#[test]
fn test_style_warning() {
assert_eq!(Style::new().warning(), Style::new().magenta());
}
}
+19 -105
View File
@@ -1,19 +1,12 @@
use crate::ui::styles::ManagarrStyle;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Modifier, Style}; use ratatui::style::{Color, Style, Stylize};
use ratatui::text::{Line, Span, Text}; use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{Block, BorderType, Borders, LineGauge, Paragraph, Wrap}; use ratatui::widgets::{Block, BorderType, Borders, LineGauge, Paragraph, Wrap};
use ratatui::{symbols, Frame}; use ratatui::{symbols, Frame};
use std::rc::Rc; use std::rc::Rc;
pub const COLOR_TEAL: Color = Color::Rgb(35, 50, 55); pub const COLOR_TEAL: Color = Color::Rgb(35, 50, 55);
pub const COLOR_CYAN: Color = Color::Cyan;
pub const COLOR_LIGHT_BLUE: Color = Color::LightBlue;
pub const COLOR_YELLOW: Color = Color::Yellow;
pub const COLOR_GREEN: Color = Color::Green;
pub const COLOR_RED: Color = Color::Red;
pub const COLOR_ORANGE: Color = Color::Rgb(255, 170, 66);
pub const COLOR_WHITE: Color = Color::White;
pub const COLOR_MAGENTA: Color = Color::Magenta;
#[cfg(test)] #[cfg(test)]
#[path = "utils_tests.rs"] #[path = "utils_tests.rs"]
@@ -60,11 +53,11 @@ fn layout_with_constraints(constraints: Vec<Constraint>) -> Layout {
} }
pub fn background_block<'a>() -> Block<'a> { pub fn background_block<'a>() -> Block<'a> {
Block::default().style(Style::default().bg(COLOR_TEAL).fg(COLOR_WHITE)) Block::new().white().bg(COLOR_TEAL)
} }
pub fn layout_block<'a>() -> Block<'a> { pub fn layout_block<'a>() -> Block<'a> {
Block::default() Block::new()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
} }
@@ -78,11 +71,11 @@ pub fn layout_block_top_border_with_title(title_span: Span<'_>) -> Block<'_> {
} }
pub fn layout_block_top_border<'a>() -> Block<'a> { pub fn layout_block_top_border<'a>() -> Block<'a> {
Block::default().borders(Borders::TOP) Block::new().borders(Borders::TOP)
} }
pub fn layout_block_bottom_border<'a>() -> Block<'a> { pub fn layout_block_bottom_border<'a>() -> Block<'a> {
Block::default().borders(Borders::BOTTOM) Block::new().borders(Borders::BOTTOM)
} }
pub fn layout_button_paragraph( pub fn layout_button_paragraph(
@@ -110,102 +103,26 @@ pub fn layout_button_paragraph_borderless(
pub fn layout_paragraph_borderless(string: &str) -> Paragraph<'_> { pub fn layout_paragraph_borderless(string: &str) -> Paragraph<'_> {
Paragraph::new(Text::from(string)) Paragraph::new(Text::from(string))
.block(borderless_block()) .block(borderless_block())
.style(style_primary().add_modifier(Modifier::BOLD)) .primary()
.bold()
.wrap(Wrap { trim: false }) .wrap(Wrap { trim: false })
.alignment(Alignment::Center) .alignment(Alignment::Center)
} }
pub fn borderless_block<'a>() -> Block<'a> { pub fn borderless_block<'a>() -> Block<'a> {
Block::default() Block::new()
}
pub fn line_info_with_style<'a>(
title: String,
content: String,
title_style: Style,
content_style: Style,
) -> Line<'a> {
Line::from(vec![
Span::styled(title, title_style),
Span::styled(content, content_style),
])
}
pub fn line_info_default<'a>(title: String, content: String) -> Line<'a> {
line_info_with_style(title, content, style_bold(), style_default())
}
pub fn line_info_primary<'a>(title: String, content: String) -> Line<'a> {
line_info_with_style(
title,
content,
style_primary().add_modifier(Modifier::BOLD),
style_default(),
)
}
pub fn style_bold() -> Style {
Style::default().add_modifier(Modifier::BOLD)
}
pub fn style_highlight() -> Style {
Style::default().add_modifier(Modifier::REVERSED)
}
pub fn style_default() -> Style {
Style::default().fg(COLOR_WHITE)
}
pub fn style_default_bold() -> Style {
style_default().add_modifier(Modifier::BOLD)
}
pub fn style_primary() -> Style {
Style::default().fg(COLOR_CYAN)
}
pub fn style_secondary() -> Style {
Style::default().fg(COLOR_YELLOW)
}
pub fn style_system_function() -> Style {
Style::default().fg(COLOR_YELLOW)
}
pub fn style_unmonitored() -> Style {
Style::default().fg(COLOR_WHITE)
}
pub fn style_success() -> Style {
Style::default().fg(COLOR_GREEN)
}
pub fn style_warning() -> Style {
Style::default().fg(COLOR_MAGENTA)
}
pub fn style_failure() -> Style {
Style::default().fg(COLOR_RED)
}
pub fn style_awaiting_import() -> Style {
Style::default().fg(COLOR_ORANGE)
}
pub fn style_help() -> Style {
Style::default().fg(COLOR_LIGHT_BLUE)
} }
pub fn style_block_highlight(is_selected: bool) -> Style { pub fn style_block_highlight(is_selected: bool) -> Style {
if is_selected { if is_selected {
style_system_function().add_modifier(Modifier::BOLD) Style::new().system_function().bold()
} else { } else {
style_default_bold() Style::new().default().bold()
} }
} }
pub fn title_style(title: &str) -> Span<'_> { pub fn title_style(title: &str) -> Span<'_> {
Span::styled(format!(" {title} "), style_bold()) format!(" {title} ").bold()
} }
pub fn title_block(title: &str) -> Block<'_> { pub fn title_block(title: &str) -> Block<'_> {
@@ -219,10 +136,7 @@ pub fn title_block_centered(title: &str) -> Block<'_> {
pub fn logo_block<'a>() -> Block<'a> { pub fn logo_block<'a>() -> Block<'a> {
layout_block().title(Span::styled( layout_block().title(Span::styled(
" Managarr - A Servarr management TUI ", " Managarr - A Servarr management TUI ",
Style::default() Style::new().magenta().bold().italic(),
.fg(Color::Magenta)
.add_modifier(Modifier::BOLD)
.add_modifier(Modifier::ITALIC),
)) ))
} }
@@ -250,18 +164,18 @@ pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
} }
pub fn line_gauge_with_title(title: &str, ratio: f64) -> LineGauge<'_> { pub fn line_gauge_with_title(title: &str, ratio: f64) -> LineGauge<'_> {
LineGauge::default() LineGauge::new()
.block(Block::default().title(title)) .block(Block::new().title(title))
.gauge_style(Style::default().fg(COLOR_CYAN)) .gauge_style(Style::new().cyan())
.line_set(symbols::line::THICK) .line_set(symbols::line::THICK)
.ratio(ratio) .ratio(ratio)
.label(Line::from(format!("{:.0}%", ratio * 100.0))) .label(Line::from(format!("{:.0}%", ratio * 100.0)))
} }
pub fn line_gauge_with_label(title: &str, ratio: f64) -> LineGauge<'_> { pub fn line_gauge_with_label(title: &str, ratio: f64) -> LineGauge<'_> {
LineGauge::default() LineGauge::new()
.block(Block::default()) .block(Block::new())
.gauge_style(Style::default().fg(COLOR_CYAN)) .gauge_style(Style::new().cyan())
.line_set(symbols::line::THICK) .line_set(symbols::line::THICK)
.ratio(ratio) .ratio(ratio)
.label(Line::from(format!("{title}: {:.0}%", ratio * 100.0))) .label(Line::from(format!("{title}: {:.0}%", ratio * 100.0)))
+4 -133
View File
@@ -3,17 +3,14 @@ mod test {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Modifier, Style}; use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span}; use ratatui::text::Span;
use ratatui::widgets::{Block, BorderType, Borders}; use ratatui::widgets::{Block, BorderType, Borders};
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, centered_rect, get_width_from_percentage, horizontal_chunks, borderless_block, centered_rect, get_width_from_percentage, horizontal_chunks,
horizontal_chunks_with_margin, layout_block, layout_block_bottom_border, horizontal_chunks_with_margin, layout_block, layout_block_bottom_border,
layout_block_top_border, layout_block_top_border_with_title, layout_block_with_title, layout_block_top_border, layout_block_top_border_with_title, layout_block_with_title,
layout_with_constraints, line_info_default, line_info_primary, line_info_with_style, layout_with_constraints, logo_block, style_block_highlight, title_block, title_block_centered,
logo_block, style_block_highlight, style_bold, style_default, style_default_bold,
style_failure, style_help, style_highlight, style_primary, style_secondary, style_success,
style_system_function, style_unmonitored, style_warning, title_block, title_block_centered,
title_style, vertical_chunks, vertical_chunks_with_margin, title_style, vertical_chunks, vertical_chunks_with_margin,
}; };
@@ -176,132 +173,6 @@ mod test {
assert_eq!(borderless_block(), Block::default()); assert_eq!(borderless_block(), Block::default());
} }
#[test]
fn test_line_info_with_style() {
let first_style = Style::default()
.fg(Color::DarkGray)
.add_modifier(Modifier::BOLD);
let second_style = Style::default()
.fg(Color::LightYellow)
.add_modifier(Modifier::ITALIC);
let expected_lines = Line::from(vec![
Span::styled("title".to_owned(), first_style),
Span::styled("content".to_owned(), second_style),
]);
assert_eq!(
line_info_with_style(
"title".to_owned(),
"content".to_owned(),
first_style,
second_style
),
expected_lines
);
}
#[test]
fn test_line_info_default() {
let expected_line = Line::from(vec![
Span::styled(
"title".to_owned(),
Style::default().add_modifier(Modifier::BOLD),
),
Span::styled("content".to_owned(), Style::default().fg(Color::White)),
]);
assert_eq!(
line_info_default("title".to_owned(), "content".to_owned()),
expected_line
);
}
#[test]
fn test_line_info_primary() {
let expected_line = Line::from(vec![
Span::styled(
"title".to_owned(),
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
),
Span::styled("content".to_owned(), Style::default().fg(Color::White)),
]);
assert_eq!(
line_info_primary("title".to_owned(), "content".to_owned()),
expected_line
);
}
#[test]
fn test_style_bold() {
assert_eq!(style_bold(), Style::default().add_modifier(Modifier::BOLD));
}
#[test]
fn test_style_highlight() {
assert_eq!(
style_highlight(),
Style::default().add_modifier(Modifier::REVERSED)
);
}
#[test]
fn test_style_unmonitored() {
assert_eq!(style_unmonitored(), Style::default().fg(Color::White));
}
#[test]
fn test_style_default() {
assert_eq!(style_default(), Style::default().fg(Color::White));
}
#[test]
fn test_style_default_bold() {
assert_eq!(
style_default_bold(),
Style::default()
.fg(Color::White)
.add_modifier(Modifier::BOLD)
);
}
#[test]
fn test_style_primary() {
assert_eq!(style_primary(), Style::default().fg(Color::Cyan));
}
#[test]
fn test_style_secondary() {
assert_eq!(style_secondary(), Style::default().fg(Color::Yellow));
}
#[test]
fn test_style_system_function() {
assert_eq!(style_system_function(), Style::default().fg(Color::Yellow));
}
#[test]
fn test_style_success() {
assert_eq!(style_success(), Style::default().fg(Color::Green));
}
#[test]
fn test_style_warning() {
assert_eq!(style_warning(), Style::default().fg(Color::Magenta));
}
#[test]
fn test_style_failure() {
assert_eq!(style_failure(), Style::default().fg(Color::Red));
}
#[test]
fn test_style_help() {
assert_eq!(style_help(), Style::default().fg(Color::LightBlue));
}
#[test] #[test]
fn test_style_button_highlight_selected() { fn test_style_button_highlight_selected() {
let expected_style = Style::default() let expected_style = Style::default()
@@ -390,9 +261,9 @@ mod test {
x: 0, x: 0,
y: 0, y: 0,
width: 100, width: 100,
height: 10 height: 10,
}, },
30 30,
), ),
30 30
); );