build: Upgraded to Ratatui v0.30.0 and fixed a new security vulnerability [#13]

This commit is contained in:
2026-01-07 17:15:54 -07:00
parent 243de47cae
commit f0ed71b436
43 changed files with 1532 additions and 828 deletions
Generated
+1037 -397
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -42,7 +42,7 @@ strum = { version = "0.26.3", features = ["derive"] }
strum_macros = "0.26.4"
tokio = { version = "1.44.2", features = ["full"] }
tokio-util = "0.7.8"
ratatui = { version = "0.29.0", features = [
ratatui = { version = "0.30.0", features = [
"all-widgets",
"unstable-widget-ref",
] }
@@ -59,7 +59,7 @@ ctrlc = "3.4.5"
colored = "3.0.0"
async-trait = "0.1.83"
dirs-next = "2.0.0"
managarr-tree-widget = "0.24.0"
managarr-tree-widget = "0.25.0"
indicatif = "0.17.9"
derive_setters = "0.1.6"
deunicode = "1.6.0"
+3 -3
View File
@@ -109,14 +109,14 @@ fn draw_add_artist_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.block(title_block_centered("Add Artist"));
search_box.show_cursor(f, search_box_area);
f.render_widget(layout_block().default(), results_area);
f.render_widget(layout_block().default_color(), results_area);
f.render_widget(search_box, search_box_area);
}
ActiveLidarrBlock::AddArtistEmptySearchResults => {
let error_message = Message::new("No artists found matching your query!");
let error_message_popup = Popup::new(error_message).size(Size::Message);
f.render_widget(layout_block().default(), results_area);
f.render_widget(layout_block().default_color(), results_area);
f.render_widget(error_message_popup, f.area());
}
ActiveLidarrBlock::AddArtistSearchResults => {
@@ -125,7 +125,7 @@ fn draw_add_artist_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
search_results_row_mapping,
)
.loading(is_loading)
.block(layout_block().default())
.block(layout_block().default_color())
.headers(["", "Name", "Type", "Status", "Rating", "Genres"])
.constraints([
Constraint::Percentage(3),
+5 -6
View File
@@ -11,7 +11,6 @@ use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_A
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
use crate::render_selectable_input_box;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::title_block_centered;
use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox;
@@ -120,17 +119,17 @@ fn draw_edit_artist_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, ar
let monitored_checkbox = Checkbox::new("Monitored")
.checked(monitored.unwrap_or_default())
.highlighted(selected_block == ActiveLidarrBlock::EditArtistToggleMonitored);
let monitor_new_items_drop_down_button = Button::new()
let monitor_new_items_drop_down_button = Button::default()
.title(selected_monitor_new_items.to_display_str())
.label("Monitor New Albums")
.icon("")
.selected(selected_block == ActiveLidarrBlock::EditArtistSelectMonitorNewItems);
let quality_profile_drop_down_button = Button::new()
let quality_profile_drop_down_button = Button::default()
.title(selected_quality_profile)
.label("Quality Profile")
.icon("")
.selected(selected_block == ActiveLidarrBlock::EditArtistSelectQualityProfile);
let metadata_profile_drop_down_button = Button::new()
let metadata_profile_drop_down_button = Button::default()
.title(selected_metadata_profile)
.label("Metadata Profile")
.icon("")
@@ -158,10 +157,10 @@ fn draw_edit_artist_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, ar
render_selectable_input_box!(tags_input_box, f, tags_area);
}
let save_button = Button::new()
let save_button = Button::default()
.title("Save")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::new()
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
+2 -2
View File
@@ -154,7 +154,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())
.default();
.default_color();
f.render_widget(
root_folder_space,
@@ -205,7 +205,7 @@ fn draw_lidarr_logo(f: &mut Frame<'_>, area: Rect) {
let logo_text = Text::from(LIDARR_LOGO);
let logo = Paragraph::new(logo_text)
.light_green()
.block(layout_block().default())
.block(layout_block().default_color())
.centered();
f.render_widget(logo, area);
}
+5 -5
View File
@@ -4,7 +4,7 @@ use std::sync::atomic::Ordering;
use lidarr_ui::LidarrUi;
use ratatui::Frame;
use ratatui::layout::{Constraint, Flex, Layout, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::style::Stylize;
use ratatui::text::{Line, Text};
use ratatui::widgets::Paragraph;
use ratatui::widgets::Tabs;
@@ -17,7 +17,7 @@ use crate::app::App;
use crate::models::servarr_models::KeybindingItem;
use crate::models::{HorizontallyScrollableText, Route, TabState};
use crate::ui::radarr_ui::RadarrUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::{ManagarrStyle, secondary_style};
use crate::ui::theme::Theme;
use crate::ui::utils::{
background_block, borderless_block, centered_rect, logo_block, title_block, title_block_centered,
@@ -116,7 +116,7 @@ fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.map(|tab| Line::from(tab.title.clone().bold()));
let tabs = Tabs::new(titles)
.block(borderless_block())
.highlight_style(Style::new().secondary())
.highlight_style(secondary_style())
.select(app.server_tabs.index);
let help = Paragraph::new(help_text)
.block(borderless_block())
@@ -185,7 +185,7 @@ pub fn draw_help_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -> Rect {
if title.is_empty() {
f.render_widget(layout_block().default(), area);
f.render_widget(layout_block().default_color(), area);
} else {
f.render_widget(title_block(title), area);
}
@@ -200,7 +200,7 @@ fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -
.map(|tab_route| Line::from(tab_route.title.clone().bold()));
let tabs = Tabs::new(titles)
.block(borderless_block())
.highlight_style(Style::new().secondary())
.highlight_style(secondary_style())
.select(tab_state.index);
f.render_widget(tabs, header_area);
+3 -3
View File
@@ -3,7 +3,7 @@ use crate::models::Route;
use crate::models::radarr_models::BlocklistItem;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
use crate::ui::DrawUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::{ManagarrStyle, secondary_style};
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
use crate::ui::widgets::managarr_table::ManagarrTable;
@@ -11,7 +11,7 @@ use crate::ui::widgets::message::Message;
use crate::ui::widgets::popup::{Popup, Size};
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::style::Stylize;
use ratatui::text::{Line, Text};
use ratatui::widgets::{Cell, Row};
@@ -186,7 +186,7 @@ fn draw_blocklist_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
let message = Message::new(text)
.title("Details")
.style(Style::new().secondary())
.style(secondary_style())
.alignment(Alignment::Left);
f.render_widget(Popup::new(message).size(Size::NarrowMessage), f.area());
@@ -150,7 +150,7 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
.overview
.clone()
.unwrap_or_default()
.default(),
.default_color(),
]),
Line::from(vec![
"Root Folder Path: ".primary().bold(),
@@ -158,20 +158,23 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
.root_folder_path
.clone()
.unwrap_or_default()
.default(),
.default_color(),
]),
Line::from(vec![
"Quality Profile: ".primary().bold(),
quality_profile.default(),
quality_profile.default_color(),
]),
Line::from(vec![
"Minimum Availability: ".primary().bold(),
minimum_availability.default(),
minimum_availability.default_color(),
]),
Line::from(vec![
"Monitored: ".primary().bold(),
monitored.default_color(),
]),
Line::from(vec!["Monitored: ".primary().bold(), monitored.default()]),
Line::from(vec![
"Search on Add: ".primary().bold(),
search_on_add.default(),
search_on_add.default_color(),
]),
]);
@@ -225,7 +228,7 @@ fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.clone()
.overview,
)
.default();
.default_color();
let paragraph = Paragraph::new(overview)
.block(borderless_block())
@@ -11,7 +11,6 @@ use crate::models::servarr_data::radarr::radarr_data::{
};
use crate::render_selectable_input_box;
use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox;
@@ -129,12 +128,12 @@ fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>
let monitored_checkbox = Checkbox::new("Monitored")
.highlighted(selected_block == ActiveRadarrBlock::EditCollectionToggleMonitored)
.checked(monitored.unwrap_or_default());
let min_availability_drop_down_button = Button::new()
let min_availability_drop_down_button = Button::default()
.title(selected_minimum_availability.to_display_str())
.label("Minimum Availability")
.icon("")
.selected(selected_block == ActiveRadarrBlock::EditCollectionSelectMinimumAvailability);
let quality_profile_drop_down_button = Button::new()
let quality_profile_drop_down_button = Button::default()
.title(selected_quality_profile)
.label("Quality Profile")
.icon("")
@@ -152,10 +151,10 @@ fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>
let search_on_add_checkbox = Checkbox::new("Search on Add")
.highlighted(selected_block == ActiveRadarrBlock::EditCollectionToggleSearchOnAdd)
.checked(search_on_add.unwrap_or_default());
let save_button = Button::new()
let save_button = Button::default()
.title("Save")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::new()
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
+2 -3
View File
@@ -4,7 +4,6 @@ use crate::app::App;
use crate::models::Route;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::render_selectable_input_box;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::title_block_centered;
use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox;
@@ -152,10 +151,10 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.flex(Flex::Center)
.areas(buttons_area);
let save_button = Button::new()
let save_button = Button::default()
.title("Save")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::new()
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
@@ -9,7 +9,6 @@ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS,
};
use crate::render_selectable_input_box;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::title_block_centered;
use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox;
@@ -154,10 +153,10 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area:
.flex(Flex::Center)
.areas(buttons_area);
let save_button = Button::new()
let save_button = Button::default()
.title("Save")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::new()
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::ui::styles::success_style;
use ratatui::Frame;
use ratatui::layout::{Constraint, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::text::Text;
use ratatui::widgets::{Cell, Row};
@@ -73,7 +73,7 @@ impl DrawUi for IndexersUi {
} else {
let message = Message::new("Indexer test succeeded!")
.title("Success")
.style(Style::new().success().bold());
.style(success_style().bold());
Popup::new(message).size(Size::Message)
}
};
+9 -9
View File
@@ -164,14 +164,14 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.block(title_block_centered("Add Movie"));
search_box.show_cursor(f, search_box_area);
f.render_widget(layout_block().default(), results_area);
f.render_widget(layout_block().default_color(), results_area);
f.render_widget(search_box, search_box_area);
}
ActiveRadarrBlock::AddMovieEmptySearchResults => {
let error_message = Message::new("No movies found matching your query!");
let error_message_popup = Popup::new(error_message).size(Size::Message);
f.render_widget(layout_block().default(), results_area);
f.render_widget(layout_block().default_color(), results_area);
f.render_widget(error_message_popup, f.area());
}
ActiveRadarrBlock::AddMovieSearchResults
@@ -187,7 +187,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
search_results_row_mapping,
)
.loading(is_loading)
.block(layout_block().default())
.block(layout_block().default_color())
.headers([
"",
"Title",
@@ -338,22 +338,22 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
.areas(buttons_area);
let root_folder_drop_down_button = Button::new()
let root_folder_drop_down_button = Button::default()
.title(&selected_root_folder.path)
.label("Root Folder")
.icon("")
.selected(selected_block == ActiveRadarrBlock::AddMovieSelectRootFolder);
let monitor_drop_down_button = Button::new()
let monitor_drop_down_button = Button::default()
.title(selected_monitor.to_display_str())
.label("Monitor")
.icon("")
.selected(selected_block == ActiveRadarrBlock::AddMovieSelectMonitor);
let min_availability_drop_down_button = Button::new()
let min_availability_drop_down_button = Button::default()
.title(selected_minimum_availability.to_display_str())
.label("Minimum Availability")
.icon("")
.selected(selected_block == ActiveRadarrBlock::AddMovieSelectMinimumAvailability);
let quality_profile_drop_down_button = Button::new()
let quality_profile_drop_down_button = Button::default()
.title(selected_quality_profile)
.label("Quality Profile")
.icon("")
@@ -373,10 +373,10 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
render_selectable_input_box!(tags_input_box, f, tags_area);
}
let add_button = Button::new()
let add_button = Button::default()
.title("Add")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::new()
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
+4 -5
View File
@@ -14,7 +14,6 @@ use crate::models::servarr_data::radarr::radarr_data::{
use crate::render_selectable_input_box;
use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox;
@@ -125,12 +124,12 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, are
let monitored_checkbox = Checkbox::new("Monitored")
.checked(monitored.unwrap_or_default())
.highlighted(selected_block == ActiveRadarrBlock::EditMovieToggleMonitored);
let min_availability_drop_down_button = Button::new()
let min_availability_drop_down_button = Button::default()
.title(selected_minimum_availability.to_display_str())
.label("Minimum Availability")
.icon("")
.selected(selected_block == ActiveRadarrBlock::EditMovieSelectMinimumAvailability);
let quality_profile_drop_down_button = Button::new()
let quality_profile_drop_down_button = Button::default()
.title(selected_quality_profile)
.label("Quality Profile")
.icon("")
@@ -158,10 +157,10 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, are
render_selectable_input_box!(tags_input_box, f, tags_area);
}
let save_button = Button::new()
let save_button = Button::default()
.title("Save")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::new()
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
+11 -7
View File
@@ -1,3 +1,7 @@
use crate::ui::styles::{
awaiting_import_style, downloaded_style, downloading_style, missing_style,
unmonitored_missing_style, unreleased_style,
};
use std::iter;
use ratatui::Frame;
@@ -529,12 +533,12 @@ fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>) {
fn style_from_download_status(download_status: &str, is_monitored: bool, status: String) -> Style {
match download_status {
"Downloaded" => Style::new().downloaded(),
"Awaiting Import" => Style::new().awaiting_import(),
"Downloading" => Style::new().downloading(),
_ if !is_monitored && download_status == "Missing" => Style::new().unmonitored_missing(),
_ if status != "released" && download_status == "Missing" => Style::new().unreleased(),
"Missing" => Style::new().missing(),
_ => Style::new().downloaded(),
"Downloaded" => downloaded_style(),
"Awaiting Import" => awaiting_import_style(),
"Downloading" => downloading_style(),
_ if !is_monitored && download_status == "Missing" => unmonitored_missing_style(),
_ if status != "released" && download_status == "Missing" => unreleased_style(),
"Missing" => missing_style(),
_ => downloaded_style(),
}
}
@@ -11,7 +11,10 @@ mod tests {
use crate::ui::radarr_ui::library::movie_details_ui::{
MovieDetailsUi, style_from_download_status,
};
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::{
awaiting_import_style, downloaded_style, downloading_style, missing_style,
unmonitored_missing_style,
};
use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app};
#[test]
@@ -26,13 +29,13 @@ mod tests {
}
#[rstest]
#[case("Downloading", true, "", Style::new().downloading())]
#[case("Downloaded", true, "", Style::new().downloaded())]
#[case("Awaiting Import", true, "", Style::new().awaiting_import())]
#[case("Missing", false, "", Style::new().unmonitored_missing())]
#[case("Missing", false, "", Style::new().unmonitored_missing())]
#[case("Missing", true, "released", Style::new().missing())]
#[case("", true, "", Style::new().downloaded())]
#[case("Downloading", true, "", downloading_style())]
#[case("Downloaded", true, "", downloaded_style())]
#[case("Awaiting Import", true, "", awaiting_import_style())]
#[case("Missing", false, "", unmonitored_missing_style())]
#[case("Missing", false, "", unmonitored_missing_style())]
#[case("Missing", true, "released", missing_style())]
#[case("", true, "", downloaded_style())]
fn test_style_from_download_status(
#[case] download_status: &str,
#[case] is_monitored: bool,
+2 -2
View File
@@ -161,7 +161,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())
.default();
.default_color();
f.render_widget(
root_folder_space,
@@ -249,7 +249,7 @@ fn draw_radarr_logo(f: &mut Frame<'_>, area: Rect) {
let logo_text = Text::from(RADARR_LOGO);
let logo = Paragraph::new(logo_text)
.light_yellow()
.block(layout_block().default())
.block(layout_block().default_color())
.centered();
f.render_widget(logo, area);
}
+2 -2
View File
@@ -1,3 +1,4 @@
use crate::ui::styles::default_style;
use std::ops::Sub;
#[cfg(test)]
@@ -5,7 +6,6 @@ use crate::ui::ui_test_utils::test_utils::Utc;
#[cfg(not(test))]
use chrono::Utc;
use ratatui::layout::Layout;
use ratatui::style::Style;
use ratatui::text::{Span, Text};
use ratatui::widgets::{Cell, Row};
use ratatui::{
@@ -178,7 +178,7 @@ fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level)
})
.block(block)
.highlight_style(Style::new().default());
.highlight_style(default_style());
f.render_widget(logs_box, area);
}
+3 -3
View File
@@ -3,7 +3,7 @@ use crate::models::Route;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, BLOCKLIST_BLOCKS};
use crate::models::sonarr_models::BlocklistItem;
use crate::ui::DrawUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::{ManagarrStyle, secondary_style};
use crate::ui::utils::layout_block_top_border;
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
use crate::ui::widgets::managarr_table::ManagarrTable;
@@ -11,7 +11,7 @@ use crate::ui::widgets::message::Message;
use crate::ui::widgets::popup::{Popup, Size};
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::style::Stylize;
use ratatui::text::{Line, Text};
use ratatui::widgets::{Cell, Row};
@@ -163,7 +163,7 @@ fn draw_blocklist_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
let message = Message::new(text)
.title("Details")
.style(Style::new().secondary())
.style(secondary_style())
.alignment(Alignment::Left);
f.render_widget(Popup::new(message).size(Size::NarrowMessage), f.area());
+2 -3
View File
@@ -4,14 +4,13 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, HISTOR
use crate::models::servarr_models::Language;
use crate::models::sonarr_models::{SonarrHistoryEventType, SonarrHistoryItem};
use crate::ui::DrawUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::{ManagarrStyle, secondary_style};
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::message::Message;
use crate::ui::widgets::popup::{Popup, Size};
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Rect};
use ratatui::style::Style;
use ratatui::text::Text;
use ratatui::widgets::{Cell, Row};
@@ -151,7 +150,7 @@ fn draw_history_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
let message = Message::new(text)
.title("Details")
.style(Style::new().secondary())
.style(secondary_style())
.alignment(Alignment::Left);
f.render_widget(Popup::new(message).size(Size::NarrowMessage), f.area());
+2 -3
View File
@@ -4,7 +4,6 @@ use crate::app::App;
use crate::models::Route;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS};
use crate::render_selectable_input_box;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::title_block_centered;
use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox;
@@ -151,10 +150,10 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.flex(Flex::Center)
.areas(buttons_area);
let save_button = Button::new()
let save_button = Button::default()
.title("Save")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::new()
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
@@ -7,7 +7,6 @@ use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS,
};
use crate::render_selectable_input_box;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::title_block_centered;
use crate::ui::widgets::button::Button;
use crate::ui::widgets::input_box::InputBox;
@@ -103,10 +102,10 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area:
.flex(Flex::Center)
.areas(buttons_area);
let save_button = Button::new()
let save_button = Button::default()
.title("Save")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::new()
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::ui::styles::success_style;
use ratatui::Frame;
use ratatui::layout::{Constraint, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::text::Text;
use ratatui::widgets::{Cell, Row};
@@ -73,7 +73,7 @@ impl DrawUi for IndexersUi {
} else {
let message = Message::new("Indexer test succeeded!")
.title("Success")
.style(Style::new().success().bold());
.style(success_style().bold());
Popup::new(message).size(Size::Message)
}
};
+10 -10
View File
@@ -144,14 +144,14 @@ fn draw_add_series_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.block(title_block_centered("Add Series"));
search_box.show_cursor(f, search_box_area);
f.render_widget(layout_block().default(), results_area);
f.render_widget(layout_block().default_color(), results_area);
f.render_widget(search_box, search_box_area);
}
ActiveSonarrBlock::AddSeriesEmptySearchResults => {
let error_message = Message::new("No series found matching your query!");
let error_message_popup = Popup::new(error_message).size(Size::Message);
f.render_widget(layout_block().default(), results_area);
f.render_widget(layout_block().default_color(), results_area);
f.render_widget(error_message_popup, f.area());
}
ActiveSonarrBlock::AddSeriesSearchResults
@@ -168,7 +168,7 @@ fn draw_add_series_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
search_results_row_mapping,
)
.loading(is_loading)
.block(layout_block().default())
.block(layout_block().default_color())
.headers([
"", "Title", "Year", "Network", "Seasons", "Rating", "Genres",
])
@@ -314,27 +314,27 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let use_season_folder_checkbox = Checkbox::new("Season Folder")
.checked(*use_season_folder)
.highlighted(selected_block == ActiveSonarrBlock::AddSeriesToggleUseSeasonFolder);
let root_folder_drop_down_button = Button::new()
let root_folder_drop_down_button = Button::default()
.title(&selected_root_folder.path)
.label("Root Folder")
.icon("")
.selected(selected_block == ActiveSonarrBlock::AddSeriesSelectRootFolder);
let monitor_drop_down_button = Button::new()
let monitor_drop_down_button = Button::default()
.title(selected_monitor.to_display_str())
.label("Monitor")
.icon("")
.selected(selected_block == ActiveSonarrBlock::AddSeriesSelectMonitor);
let series_type_drop_down_button = Button::new()
let series_type_drop_down_button = Button::default()
.title(selected_series_type.to_display_str())
.label("Series Type")
.icon("")
.selected(selected_block == ActiveSonarrBlock::AddSeriesSelectSeriesType);
let quality_profile_drop_down_button = Button::new()
let quality_profile_drop_down_button = Button::default()
.title(selected_quality_profile)
.label("Quality Profile")
.icon("")
.selected(selected_block == ActiveSonarrBlock::AddSeriesSelectQualityProfile);
let language_profile_drop_down_button = Button::new()
let language_profile_drop_down_button = Button::default()
.title(selected_language_profile)
.label("Language Profile")
.icon("")
@@ -356,10 +356,10 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
render_selectable_input_box!(tags_input_box, f, tags_area);
}
let add_button = Button::new()
let add_button = Button::default()
.title("Add")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::new()
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
+5 -6
View File
@@ -13,7 +13,6 @@ use crate::models::servarr_data::sonarr::sonarr_data::{
};
use crate::render_selectable_input_box;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox;
@@ -145,17 +144,17 @@ fn draw_edit_series_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, ar
let season_folder_checkbox = Checkbox::new("Season Folder")
.checked(use_season_folders.unwrap_or_default())
.highlighted(selected_block == ActiveSonarrBlock::EditSeriesToggleSeasonFolder);
let series_type_drop_down_button = Button::new()
let series_type_drop_down_button = Button::default()
.title(selected_series_type.to_display_str())
.label("Series Type")
.icon("")
.selected(selected_block == ActiveSonarrBlock::EditSeriesSelectSeriesType);
let quality_profile_drop_down_button = Button::new()
let quality_profile_drop_down_button = Button::default()
.title(selected_quality_profile)
.label("Quality Profile")
.icon("")
.selected(selected_block == ActiveSonarrBlock::EditSeriesSelectQualityProfile);
let language_profile_drop_down_button = Button::new()
let language_profile_drop_down_button = Button::default()
.title(selected_language_profile)
.label("Language Profile")
.icon("")
@@ -183,10 +182,10 @@ fn draw_edit_series_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, ar
render_selectable_input_box!(tags_input_box, f, tags_area);
}
let save_button = Button::new()
let save_button = Button::default()
.title("Save")
.selected(yes_no_value && highlight_yes_no);
let cancel_button = Button::new()
let cancel_button = Button::default()
.title("Cancel")
.selected(!yes_no_value && highlight_yes_no);
+12 -8
View File
@@ -13,6 +13,10 @@ use crate::ui::sonarr_ui::sonarr_ui_utils::{
create_no_data_history_event_details,
};
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::{
awaiting_import_style, downloaded_style, downloading_style, missing_style, secondary_style,
unmonitored_missing_style, unmonitored_style, unreleased_style,
};
use crate::ui::utils::{
borderless_block, decorate_peer_style, get_width_from_percentage, layout_block_bottom_border,
layout_block_top_border,
@@ -388,7 +392,7 @@ fn draw_history_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: R
let message = Message::new(text)
.title("Details")
.style(Style::new().secondary())
.style(secondary_style())
.alignment(Alignment::Left);
f.render_widget(Popup::new(message).size(Size::NarrowMessage), area);
@@ -602,29 +606,29 @@ fn style_from_status(download: Option<&DownloadRecord>, episode: &Episode) -> St
if !episode.has_file {
if let Some(download) = download {
if download.status == DownloadStatus::Downloading {
return Style::new().downloading();
return downloading_style();
}
if download.status == DownloadStatus::Completed {
return Style::new().awaiting_import();
return awaiting_import_style();
}
}
if !episode.monitored {
return Style::new().unmonitored_missing();
return unmonitored_missing_style();
}
if let Some(air_date) = episode.air_date_utc.as_ref()
&& air_date > &Utc::now()
{
return Style::new().unreleased();
return unreleased_style();
}
return Style::new().missing();
return missing_style();
}
if !episode.monitored {
Style::new().unmonitored()
unmonitored_style()
} else {
Style::new().downloaded()
downloaded_style()
}
}
@@ -13,6 +13,7 @@ use crate::ui::sonarr_ui::sonarr_ui_utils::{
create_no_data_history_event_details,
};
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::secondary_style;
use crate::ui::utils::{
borderless_block, decorate_peer_style, get_width_from_percentage, layout_block_top_border,
};
@@ -26,7 +27,7 @@ use crate::utils::convert_to_gb;
use chrono::Utc;
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Rect};
use ratatui::prelude::{Line, Style, Stylize, Text};
use ratatui::prelude::{Line, Stylize, Text};
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
use serde_json::Number;
@@ -567,7 +568,7 @@ fn draw_history_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: R
let message = Message::new(text)
.title("Details")
.style(Style::new().secondary())
.style(secondary_style())
.alignment(Alignment::Left);
f.render_widget(Popup::new(message).size(Size::NarrowMessage), area);
+21 -14
View File
@@ -1,8 +1,9 @@
use crate::ui::styles::secondary_style;
use chrono::Utc;
use deunicode::deunicode;
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::style::Stylize;
use ratatui::text::{Line, Text};
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
use regex::Regex;
@@ -157,54 +158,60 @@ fn draw_series_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
"Title: ".primary().bold(),
current_selection.title.text.clone().primary().bold(),
]),
Line::from(vec!["Overview: ".primary().bold(), overview.default()]),
Line::from(vec![
"Overview: ".primary().bold(),
overview.default_color(),
]),
Line::from(vec![
"Network: ".primary().bold(),
current_selection
.network
.clone()
.unwrap_or_default()
.default(),
.default_color(),
]),
Line::from(vec![
"Status: ".primary().bold(),
current_selection.status.to_display_str().default(),
current_selection.status.to_display_str().default_color(),
]),
Line::from(vec![
"Genres: ".primary().bold(),
current_selection.genres.join(", ").default(),
current_selection.genres.join(", ").default_color(),
]),
Line::from(vec![
"Rating: ".primary().bold(),
format!("{}%", (current_selection.ratings.value * 10.0) as i32).default(),
format!("{}%", (current_selection.ratings.value * 10.0) as i32).default_color(),
]),
Line::from(vec![
"Year: ".primary().bold(),
current_selection.year.to_string().default(),
current_selection.year.to_string().default_color(),
]),
Line::from(vec![
"Runtime: ".primary().bold(),
format!("{} minutes", current_selection.runtime).default(),
format!("{} minutes", current_selection.runtime).default_color(),
]),
Line::from(vec![
"Path: ".primary().bold(),
current_selection.path.clone().default(),
current_selection.path.clone().default_color(),
]),
Line::from(vec![
"Quality Profile: ".primary().bold(),
quality_profile.default(),
quality_profile.default_color(),
]),
Line::from(vec![
"Language Profile: ".primary().bold(),
language_profile.default(),
language_profile.default_color(),
]),
Line::from(vec![
"Monitored: ".primary().bold(),
monitored.default_color(),
]),
Line::from(vec!["Monitored: ".primary().bold(), monitored.default()]),
];
if let Some(stats) = current_selection.statistics.as_ref() {
let size = convert_to_gb(stats.size_on_disk);
series_description.extend(vec![Line::from(vec![
"Size on Disk: ".primary().bold(),
format!("{size:.2} GB").default(),
format!("{size:.2} GB").default_color(),
])]);
}
@@ -421,7 +428,7 @@ fn draw_history_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: R
let message = Message::new(text)
.title("Details")
.style(Style::new().secondary())
.style(secondary_style())
.alignment(Alignment::Left);
f.render_widget(Popup::new(message).size(Size::NarrowMessage), area);
+2 -2
View File
@@ -173,7 +173,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())
.default();
.default_color();
f.render_widget(
root_folder_space,
@@ -224,7 +224,7 @@ fn draw_sonarr_logo(f: &mut Frame<'_>, area: Rect) {
let logo_text = Text::from(SONARR_LOGO);
let logo = Paragraph::new(logo_text)
.light_cyan()
.block(layout_block().default())
.block(layout_block().default_color())
.centered();
f.render_widget(logo, area);
}
+2 -2
View File
@@ -1,3 +1,4 @@
use crate::ui::styles::default_style;
use std::ops::Sub;
#[cfg(test)]
@@ -5,7 +6,6 @@ use crate::ui::ui_test_utils::test_utils::Utc;
#[cfg(not(test))]
use chrono::Utc;
use ratatui::layout::Layout;
use ratatui::style::Style;
use ratatui::text::{Span, Text};
use ratatui::widgets::{Cell, Row};
use ratatui::{
@@ -171,7 +171,7 @@ fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level)
})
.block(block)
.highlight_style(Style::new().default());
.highlight_style(default_style());
f.render_widget(logs_box, area);
}
+263 -213
View File
@@ -1,253 +1,303 @@
use crate::ui::THEME;
use ratatui::style::{Styled, Stylize};
use ratatui::style::{Style, Styled};
#[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 indeterminate(self) -> T;
fn default(self) -> T;
fn downloaded(self) -> T;
fn downloading(self) -> T;
fn failure(self) -> T;
fn help(self) -> T;
fn highlight(self) -> T;
fn missing(self) -> T;
fn primary(self) -> T;
fn secondary(self) -> T;
fn success(self) -> T;
fn system_function(self) -> T;
fn unmonitored(self) -> T;
fn unmonitored_missing(self) -> T;
fn unreleased(self) -> T;
fn warning(self) -> T;
pub fn awaiting_import_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.awaiting_import
.expect("awaiting_import style must be defined in theme")
.color
.expect("awaiting_import color must be defined"),
)
})
}
impl<T, U> ManagarrStyle<'_, T> for U
where
U: Styled<Item = T>,
T: Default,
{
fn new() -> T {
T::default()
pub fn indeterminate_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.indeterminate
.expect("indeterminate style must be defined in theme")
.color
.expect("indeterminate color must be defined"),
)
})
}
pub fn default_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.default
.expect("default style must be defined in theme")
.color
.expect("default color must be defined"),
)
})
}
pub fn downloaded_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.downloaded
.expect("downloaded style must be defined in theme")
.color
.expect("downloaded color must be defined"),
)
})
}
pub fn downloading_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.downloading
.expect("downloading style must be defined in theme")
.color
.expect("downloading color must be defined"),
)
})
}
pub fn failure_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.failure
.expect("failure style must be defined in theme")
.color
.expect("failure color must be defined"),
)
})
}
pub fn help_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.help
.expect("help style must be defined in theme")
.color
.expect("help color must be defined"),
)
})
}
pub fn highlight_style() -> Style {
Style::new().reversed()
}
pub fn missing_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.missing
.expect("missing style must be defined in theme")
.color
.expect("missing color must be defined"),
)
})
}
pub fn primary_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.primary
.expect("primary style must be defined in theme")
.color
.expect("primary color must be defined"),
)
})
}
pub fn secondary_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.secondary
.expect("secondary style must be defined in theme")
.color
.expect("secondary color must be defined"),
)
})
}
pub fn success_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.success
.expect("success style must be defined in theme")
.color
.expect("success color must be defined"),
)
})
}
pub fn system_function_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.system_function
.expect("system_function style must be defined in theme")
.color
.expect("system_function color must be defined"),
)
})
}
pub fn unmonitored_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.unmonitored
.expect("unmonitored style must be defined in theme")
.color
.expect("unmonitored color must be defined"),
)
})
}
pub fn unmonitored_missing_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.unmonitored_missing
.expect("unmonitored_missing style must be defined in theme")
.color
.expect("unmonitored_missing color must be defined"),
)
})
}
pub fn unreleased_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.unreleased
.expect("unreleased style must be defined in theme")
.color
.expect("unreleased color must be defined"),
)
})
}
pub fn warning_style() -> Style {
THEME.with(|theme| {
Style::new().fg(
theme
.get()
.warning
.expect("warning style must be defined in theme")
.color
.expect("warning color must be defined"),
)
})
}
pub trait ManagarrStyle: Styled {
fn awaiting_import(self) -> Self::Item;
fn indeterminate(self) -> Self::Item;
fn default_color(self) -> Self::Item;
fn downloaded(self) -> Self::Item;
fn downloading(self) -> Self::Item;
fn failure(self) -> Self::Item;
fn help(self) -> Self::Item;
fn missing(self) -> Self::Item;
fn primary(self) -> Self::Item;
fn secondary(self) -> Self::Item;
fn success(self) -> Self::Item;
fn system_function(self) -> Self::Item;
fn unmonitored(self) -> Self::Item;
fn unmonitored_missing(self) -> Self::Item;
fn unreleased(self) -> Self::Item;
fn warning(self) -> Self::Item;
}
impl<T: Styled> ManagarrStyle for T {
fn awaiting_import(self) -> Self::Item {
self.set_style(awaiting_import_style())
}
fn awaiting_import(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.awaiting_import
.expect("awaiting_import style must be defined in theme")
.color
.expect("awaiting_import color must be defined"),
)
})
fn indeterminate(self) -> Self::Item {
self.set_style(indeterminate_style())
}
fn indeterminate(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.indeterminate
.expect("indeterminate style must be defined in theme")
.color
.expect("indeterminate color must be defined"),
)
})
fn default_color(self) -> Self::Item {
self.set_style(default_style())
}
fn default(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.default
.expect("default style must be defined in theme")
.color
.expect("default color must be defined"),
)
})
fn downloaded(self) -> Self::Item {
self.set_style(downloaded_style())
}
fn downloaded(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.downloaded
.expect("downloaded style must be defined in theme")
.color
.expect("downloaded color must be defined"),
)
})
fn downloading(self) -> Self::Item {
self.set_style(downloading_style())
}
fn downloading(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.downloading
.expect("downloading style must be defined in theme")
.color
.expect("downloading color must be defined"),
)
})
fn failure(self) -> Self::Item {
self.set_style(failure_style())
}
fn failure(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.failure
.expect("failure style must be defined in theme")
.color
.expect("failure color must be defined"),
)
})
fn help(self) -> Self::Item {
self.set_style(help_style())
}
fn help(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.help
.expect("help style must be defined in theme")
.color
.expect("help color must be defined"),
)
})
fn missing(self) -> Self::Item {
self.set_style(missing_style())
}
fn highlight(self) -> T {
self.reversed()
fn primary(self) -> Self::Item {
self.set_style(primary_style())
}
fn missing(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.missing
.expect("missing style must be defined in theme")
.color
.expect("missing color must be defined"),
)
})
fn secondary(self) -> Self::Item {
self.set_style(secondary_style())
}
fn primary(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.primary
.expect("primary style must be defined in theme")
.color
.expect("primary color must be defined"),
)
})
fn success(self) -> Self::Item {
self.set_style(success_style())
}
fn secondary(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.secondary
.expect("secondary style must be defined in theme")
.color
.expect("secondary color must be defined"),
)
})
fn system_function(self) -> Self::Item {
self.set_style(system_function_style())
}
fn success(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.success
.expect("success style must be defined in theme")
.color
.expect("success color must be defined"),
)
})
fn unmonitored(self) -> Self::Item {
self.set_style(unmonitored_style())
}
fn system_function(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.system_function
.expect("system_function style must be defined in theme")
.color
.expect("system_function color must be defined"),
)
})
fn unmonitored_missing(self) -> Self::Item {
self.set_style(unmonitored_missing_style())
}
fn unmonitored(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.unmonitored
.expect("unmonitored style must be defined in theme")
.color
.expect("unmonitored color must be defined"),
)
})
fn unreleased(self) -> Self::Item {
self.set_style(unreleased_style())
}
fn unmonitored_missing(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.unmonitored_missing
.expect("unmonitored_missing style must be defined in theme")
.color
.expect("unmonitored_missing color must be defined"),
)
})
}
fn unreleased(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.unreleased
.expect("unreleased style must be defined in theme")
.color
.expect("unreleased color must be defined"),
)
})
}
fn warning(self) -> T {
THEME.with(|theme| {
self.fg(
theme
.get()
.warning
.expect("warning style must be defined in theme")
.color
.expect("warning color must be defined"),
)
})
fn warning(self) -> Self::Item {
self.set_style(warning_style())
}
}
+24 -24
View File
@@ -1,19 +1,19 @@
#[cfg(test)]
mod test {
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::{
awaiting_import_style, default_style, downloaded_style, downloading_style, failure_style,
help_style, highlight_style, indeterminate_style, missing_style, primary_style,
secondary_style, success_style, system_function_style, unmonitored_missing_style,
unmonitored_style, unreleased_style, warning_style,
};
use pretty_assertions::assert_eq;
use ratatui::prelude::Modifier;
use ratatui::style::{Color, Style, Stylize};
#[test]
fn test_new() {
assert_eq!(Style::new(), <Style as Default>::default())
}
use ratatui::style::{Color, Style};
#[test]
fn test_style_awaiting_import() {
assert_eq!(
Style::new().awaiting_import(),
awaiting_import_style(),
Style::new().fg(Color::Rgb(255, 170, 66))
);
}
@@ -21,86 +21,86 @@ mod test {
#[test]
fn test_style_indeterminate() {
assert_eq!(
Style::new().indeterminate(),
indeterminate_style(),
Style::new().fg(Color::Rgb(255, 170, 66))
);
}
#[test]
fn test_style_default() {
assert_eq!(Style::new().default(), Style::new().white());
assert_eq!(default_style(), Style::new().white());
}
#[test]
fn test_style_downloaded() {
assert_eq!(Style::new().downloaded(), Style::new().green());
assert_eq!(downloaded_style(), Style::new().green());
}
#[test]
fn test_style_downloading() {
assert_eq!(Style::new().downloading(), Style::new().magenta());
assert_eq!(downloading_style(), Style::new().magenta());
}
#[test]
fn test_style_failure() {
assert_eq!(Style::new().failure(), Style::new().red());
assert_eq!(failure_style(), Style::new().red());
}
#[test]
fn test_style_help() {
assert_eq!(Style::new().help(), Style::new().light_blue());
assert_eq!(help_style(), Style::new().light_blue());
}
#[test]
fn test_style_highlight() {
assert_eq!(
Style::new().highlight(),
highlight_style(),
Style::new().add_modifier(Modifier::REVERSED)
);
}
#[test]
fn test_style_missing() {
assert_eq!(Style::new().missing(), Style::new().red());
assert_eq!(missing_style(), Style::new().red());
}
#[test]
fn test_style_primary() {
assert_eq!(Style::new().primary(), Style::new().cyan());
assert_eq!(primary_style(), Style::new().cyan());
}
#[test]
fn test_style_secondary() {
assert_eq!(Style::new().secondary(), Style::new().yellow());
assert_eq!(secondary_style(), Style::new().yellow());
}
#[test]
fn test_style_success() {
assert_eq!(Style::new().success(), Style::new().green());
assert_eq!(success_style(), Style::new().green());
}
#[test]
fn test_style_system_function() {
assert_eq!(Style::new().system_function(), Style::new().yellow());
assert_eq!(system_function_style(), Style::new().yellow());
}
#[test]
fn test_style_unmonitored() {
assert_eq!(Style::new().unmonitored(), Style::new().gray());
assert_eq!(unmonitored_style(), Style::new().gray());
}
#[test]
fn test_style_unmonitored_missing() {
assert_eq!(Style::new().unmonitored_missing(), Style::new().yellow());
assert_eq!(unmonitored_missing_style(), Style::new().yellow());
}
#[test]
fn test_style_unreleased() {
assert_eq!(Style::new().unreleased(), Style::new().light_cyan());
assert_eq!(unreleased_style(), Style::new().light_cyan());
}
#[test]
fn test_style_warning() {
assert_eq!(Style::new().warning(), Style::new().magenta());
assert_eq!(warning_style(), Style::new().magenta());
}
}
+22 -20
View File
@@ -1,8 +1,10 @@
use crate::ui::THEME;
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::{
ManagarrStyle, default_style, failure_style, primary_style, secondary_style,
system_function_style,
};
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::symbols;
use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{Block, BorderType, Borders, LineGauge, ListItem, Paragraph, Wrap};
@@ -37,11 +39,11 @@ pub fn layout_block_top_border_with_title(title_span: Span<'_>) -> Block<'_> {
}
pub fn layout_block_top_border<'a>() -> Block<'a> {
Block::new().borders(Borders::TOP).default()
Block::new().borders(Borders::TOP).default_color()
}
pub fn layout_block_bottom_border<'a>() -> Block<'a> {
Block::new().borders(Borders::BOTTOM).default()
Block::new().borders(Borders::BOTTOM).default_color()
}
pub fn layout_paragraph_borderless(string: &str) -> Paragraph<'_> {
@@ -54,14 +56,14 @@ pub fn layout_paragraph_borderless(string: &str) -> Paragraph<'_> {
}
pub fn borderless_block<'a>() -> Block<'a> {
Block::new().default()
Block::new().default_color()
}
pub fn style_block_highlight(is_selected: bool) -> Style {
if is_selected {
Style::new().system_function().bold()
system_function_style().bold()
} else {
Style::new().default().bold()
default_style().bold()
}
}
@@ -74,7 +76,7 @@ pub fn unstyled_title_block(title: &str) -> Block<'_> {
}
pub fn title_block(title: &str) -> Block<'_> {
unstyled_title_block(title).default()
unstyled_title_block(title).default_color()
}
pub fn title_block_centered(title: &str) -> Block<'_> {
@@ -82,7 +84,7 @@ pub fn title_block_centered(title: &str) -> Block<'_> {
}
pub fn logo_block<'a>() -> Block<'a> {
layout_block().default().title(Span::styled(
layout_block().default_color().title(Span::styled(
" Managarr - A Servarr management TUI ",
Style::new().magenta().bold().italic(),
))
@@ -107,19 +109,19 @@ pub fn centered_rect(percent_x: u16, percent_y: u16, area: Rect) -> Rect {
}
pub fn line_gauge_with_title(title: &str, ratio: f64) -> LineGauge<'_> {
LineGauge::new()
LineGauge::default()
.block(Block::new().title(title))
.filled_style(Style::new().primary())
.line_set(symbols::line::THICK)
.filled_style(primary_style())
.filled_symbol("")
.ratio(ratio)
.label(Line::from(format!("{:.0}%", ratio * 100.0)))
}
pub fn line_gauge_with_label(title: &str, ratio: f64) -> LineGauge<'_> {
LineGauge::new()
LineGauge::default()
.block(Block::new())
.filled_style(Style::new().primary())
.line_set(symbols::line::THICK)
.filled_style(primary_style())
.filled_symbol("")
.ratio(ratio)
.label(Line::from(format!("{title}: {:.0}%", ratio * 100.0)))
}
@@ -132,11 +134,11 @@ pub(super) fn style_log_list_item(list_item: ListItem<'_>, level: String) -> Lis
match level.to_lowercase().as_str() {
"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()),
"info" => list_item.style(default_style()),
"warn" => list_item.style(secondary_style()),
"error" => list_item.style(failure_style()),
"fatal" => list_item.style(failure_style().bold()),
_ => list_item.style(default_style()),
}
}
+13 -14
View File
@@ -1,6 +1,6 @@
#[cfg(test)]
mod test {
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::{ManagarrStyle, default_style, failure_style, secondary_style};
use crate::ui::utils::{
borderless_block, centered_rect, convert_to_minutes_hours_days, decorate_peer_style,
get_width_from_percentage, layout_block, layout_block_bottom_border, layout_block_top_border,
@@ -49,7 +49,7 @@ mod test {
.add_modifier(Modifier::BOLD),
);
let expected_block = Block::new()
.default()
.default_color()
.borders(Borders::TOP)
.title(title_span.clone());
@@ -63,7 +63,7 @@ mod test {
fn test_layout_block_top_border() {
assert_eq!(
layout_block_top_border(),
Block::new().borders(Borders::TOP).default()
Block::new().borders(Borders::TOP).default_color()
);
}
@@ -71,13 +71,13 @@ mod test {
fn test_layout_block_bottom_border() {
assert_eq!(
layout_block_bottom_border(),
Block::new().borders(Borders::BOTTOM).default()
Block::new().borders(Borders::BOTTOM).default_color()
);
}
#[test]
fn test_borderless_block() {
assert_eq!(borderless_block(), Block::new().default());
assert_eq!(borderless_block(), Block::new().default_color());
}
#[test]
@@ -117,7 +117,7 @@ mod test {
#[test]
fn test_title_block() {
let expected_block = Block::new()
.default()
.default_color()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.title(Span::styled(
@@ -131,7 +131,7 @@ mod test {
#[test]
fn test_title_block_centered() {
let expected_block = Block::new()
.default()
.default_color()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.title(Span::styled(
@@ -146,7 +146,7 @@ mod test {
#[test]
fn test_logo_block() {
let expected_block = Block::new()
.default()
.default_color()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.title(Span::styled(
@@ -190,7 +190,6 @@ mod test {
#[test]
fn test_determine_log_style_by_level() {
use crate::ui::styles::ManagarrStyle;
let list_item = ListItem::new(Text::from(Span::raw("test")));
assert_eq!(
@@ -203,23 +202,23 @@ mod test {
);
assert_eq!(
style_log_list_item(list_item.clone(), "info".to_string()),
list_item.clone().style(Style::new().default())
list_item.clone().style(default_style())
);
assert_eq!(
style_log_list_item(list_item.clone(), "warn".to_string()),
list_item.clone().style(Style::new().secondary())
list_item.clone().style(secondary_style())
);
assert_eq!(
style_log_list_item(list_item.clone(), "error".to_string()),
list_item.clone().style(Style::new().failure())
list_item.clone().style(failure_style())
);
assert_eq!(
style_log_list_item(list_item.clone(), "fatal".to_string()),
list_item.clone().style(Style::new().failure().bold())
list_item.clone().style(failure_style().bold())
);
assert_eq!(
style_log_list_item(list_item.clone(), "".to_string()),
list_item.style(Style::new().default())
list_item.style(default_style())
);
}
-1
View File
@@ -4,7 +4,6 @@ use derive_setters::Setters;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::prelude::Text;
use ratatui::style::Stylize;
use ratatui::widgets::{Paragraph, Widget};
#[derive(PartialEq, Debug, Copy, Clone, Setters)]
+4 -5
View File
@@ -1,4 +1,3 @@
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
use crate::ui::widgets::button::Button;
use crate::ui::widgets::checkbox::Checkbox;
@@ -63,11 +62,11 @@ impl ConfirmationPrompt<'_> {
checkbox.render(chunks[i + 1], buf);
});
Button::new()
Button::default()
.title("Yes")
.selected(self.yes_no_value && self.yes_no_highlighted)
.render(yes_area, buf);
Button::new()
Button::default()
.title("No")
.selected(!self.yes_no_value && self.yes_no_highlighted)
.render(no_area, buf);
@@ -109,11 +108,11 @@ impl ConfirmationPrompt<'_> {
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
.areas(buttons_area);
Button::new()
Button::default()
.title("Yes")
.selected(self.yes_no_value)
.render(yes_area, buf);
Button::new()
Button::default()
.title("No")
.selected(!self.yes_no_value)
.render(no_area, buf);
+4 -3
View File
@@ -1,9 +1,10 @@
use crate::ui::styles::{default_style, system_function_style};
use derive_setters::Setters;
use ratatui::Frame;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Layout, Position, Rect};
use ratatui::prelude::Text;
use ratatui::style::{Style, Styled, Stylize};
use ratatui::style::{Style, Styled};
use ratatui::widgets::{Block, Paragraph, Widget, WidgetRef};
use crate::ui::styles::ManagarrStyle;
@@ -35,7 +36,7 @@ impl<'a> InputBox<'a> {
InputBox {
content,
offset: 0,
style: Style::new().default(),
style: default_style(),
block: layout_block(),
label: None,
cursor_after_string: true,
@@ -71,7 +72,7 @@ impl<'a> InputBox<'a> {
fn render_input_box(&self, area: Rect, buf: &mut Buffer) {
let style =
if matches!(self.is_highlighted, Some(true)) && matches!(self.is_selected, Some(false)) {
Style::new().system_function().bold()
system_function_style().bold()
} else {
self.style
};
+2 -3
View File
@@ -1,10 +1,9 @@
#[cfg(test)]
mod tests {
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::default_style;
use crate::ui::utils::layout_block;
use crate::ui::widgets::input_box::InputBox;
use pretty_assertions::{assert_eq, assert_str_eq};
use ratatui::style::Style;
#[test]
fn test_input_box_new() {
@@ -12,7 +11,7 @@ mod tests {
assert_str_eq!(input_box.content, "test");
assert_eq!(input_box.offset, 0);
assert_eq!(input_box.style, Style::new().default());
assert_eq!(input_box.style, default_style());
assert_eq!(input_box.block, layout_block());
assert_eq!(input_box.label, None);
assert!(input_box.cursor_after_string);
+7 -4
View File
@@ -3,7 +3,7 @@ use super::message::Message;
use super::popup::Size;
use crate::models::stateful_table::StatefulTable;
use crate::ui::HIGHLIGHT_SYMBOL;
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::{ManagarrStyle, highlight_style};
use crate::ui::utils::{borderless_block, centered_rect, title_block_centered};
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::widgets::popup::Popup;
@@ -12,7 +12,7 @@ use derive_setters::Setters;
use ratatui::Frame;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Layout, Position, Rect};
use ratatui::prelude::{Style, Stylize, Text};
use ratatui::prelude::{Stylize, Text};
use ratatui::widgets::{Block, ListItem, Row, StatefulWidget, Table, Widget, WidgetRef};
use std::fmt::Debug;
use std::sync::atomic::Ordering;
@@ -136,7 +136,10 @@ where
if !table_contents.is_empty() {
let rows = table_contents.iter().map(&self.row_mapper);
let headers = Row::new(table_headers).default().bold().bottom_margin(0);
let headers = Row::new(table_headers)
.default_color()
.bold()
.bottom_margin(0);
let mut table = Table::new(rows, &self.constraints)
.header(headers)
@@ -144,7 +147,7 @@ where
if self.highlight_rows {
table = table
.row_highlight_style(Style::new().highlight())
.row_highlight_style(highlight_style())
.highlight_symbol(HIGHLIGHT_SYMBOL);
}
+3 -3
View File
@@ -1,9 +1,9 @@
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::failure_style;
use crate::ui::utils::title_block_centered;
use derive_setters::Setters;
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::style::Style;
use ratatui::text::Text;
use ratatui::widgets::{Paragraph, Widget, Wrap};
@@ -27,7 +27,7 @@ impl<'a> Message<'a> {
Message {
text: message.into(),
title: "Error",
style: Style::new().failure().bold(),
style: failure_style().bold(),
alignment: Alignment::Center,
}
}
+2 -3
View File
@@ -1,10 +1,9 @@
#[cfg(test)]
mod tests {
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::failure_style;
use crate::ui::widgets::message::Message;
use pretty_assertions::{assert_eq, assert_str_eq};
use ratatui::layout::Alignment;
use ratatui::style::{Style, Stylize};
use ratatui::text::Text;
#[test]
@@ -15,7 +14,7 @@ mod tests {
assert_eq!(message.text, Text::from(test_message));
assert_str_eq!(message.title, "Error");
assert_eq!(message.style, Style::new().failure().bold());
assert_eq!(message.style, failure_style().bold());
assert_eq!(message.alignment, Alignment::Center);
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
use crate::models::stateful_list::StatefulList;
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::highlight_style;
use crate::ui::utils::layout_block;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
@@ -29,7 +29,7 @@ where
Self {
content,
row_mapper,
highlight_style: Style::new().highlight(),
highlight_style: highlight_style(),
block: layout_block(),
}
}
+4 -4
View File
@@ -1,11 +1,11 @@
#[cfg(test)]
mod tests {
use crate::models::stateful_list::StatefulList;
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::highlight_style;
use crate::ui::utils::{layout_block, title_block};
use crate::ui::widgets::selectable_list::SelectableList;
use pretty_assertions::assert_eq;
use ratatui::style::{Style, Stylize};
use ratatui::style::Style;
use ratatui::widgets::ListItem;
#[test]
@@ -20,7 +20,7 @@ mod tests {
let row_mapper = selectable_list.row_mapper;
assert_eq!(selectable_list.content.items, items);
assert_eq!(row_mapper(&"test"), ListItem::new("test"));
assert_eq!(selectable_list.highlight_style, Style::new().highlight());
assert_eq!(selectable_list.highlight_style, highlight_style());
assert_eq!(selectable_list.block, layout_block());
}
@@ -55,6 +55,6 @@ mod tests {
assert_eq!(selectable_list.block, title_block("test"));
assert_eq!(selectable_list.content.items, items);
assert_eq!(row_mapper(&"test"), ListItem::new("test"));
assert_eq!(selectable_list.highlight_style, Style::new().highlight());
assert_eq!(selectable_list.highlight_style, highlight_style());
}
}