feat(ui): Initial UI support for switching to Sonarr tabs

This commit is contained in:
2024-11-29 15:58:19 -07:00
parent 4d1b0fe301
commit 08f190fc6e
13 changed files with 537 additions and 19 deletions
+179
View File
@@ -0,0 +1,179 @@
use ratatui::{
layout::{Constraint, Rect},
widgets::{Cell, Row},
Frame,
};
use crate::{
app::App,
models::{
servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SERIES_BLOCKS},
sonarr_models::{Series, SeriesStatus},
EnumDisplayStyle, Route,
},
ui::{
styles::ManagarrStyle,
utils::{get_width_from_percentage, layout_block_top_border},
widgets::managarr_table::ManagarrTable,
DrawUi,
},
utils::convert_runtime,
};
#[cfg(test)]
#[path = "library_ui_tests.rs"]
mod library_ui_tests;
pub(super) struct LibraryUi;
impl DrawUi for LibraryUi {
fn accepts(route: Route) -> bool {
if let Route::Sonarr(active_sonarr_block, _) = route {
return SERIES_BLOCKS.contains(&active_sonarr_block);
}
false
}
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let route = app.get_current_route();
let mut series_ui_matchers = |active_sonarr_block: ActiveSonarrBlock| match active_sonarr_block
{
ActiveSonarrBlock::Series | ActiveSonarrBlock::SeriesSortPrompt => draw_series(f, app, area),
_ => (),
};
match route {
Route::Sonarr(active_sonarr_block, _) if SERIES_BLOCKS.contains(&active_sonarr_block) => {
series_ui_matchers(active_sonarr_block)
}
_ => (),
}
}
}
pub(super) fn draw_series(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
let current_selection = if !app.data.sonarr_data.series.items.is_empty() {
app.data.sonarr_data.series.current_selection().clone()
} else {
Series::default()
};
let quality_profile_map = &app.data.sonarr_data.quality_profile_map;
let language_profile_map = &app.data.sonarr_data.language_profiles_map;
let tags_map = &app.data.sonarr_data.tags_map;
let content = Some(&mut app.data.sonarr_data.series);
let help_footer = app
.data
.sonarr_data
.main_tabs
.get_active_tab_contextual_help();
let series_table_row_mapping = |series: &Series| {
series.title.scroll_left_or_reset(
get_width_from_percentage(area, 27),
*series == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
let monitored = if series.monitored { "🏷" } else { "" };
let (hours, minutes) = convert_runtime(series.runtime);
let certification = series.certification.clone().unwrap_or_default();
let network = series.network.clone().unwrap_or_default();
let quality_profile = quality_profile_map
.get_by_left(&series.quality_profile_id)
.unwrap()
.to_owned();
let language_profile = language_profile_map
.get_by_left(&series.language_profile_id)
.unwrap()
.to_owned();
let tags = if !series.tags.is_empty() {
series
.tags
.iter()
.map(|tag_id| {
tags_map
.get_by_left(&tag_id.as_i64().unwrap())
.unwrap()
.clone()
})
.collect::<Vec<String>>()
.join(", ")
} else {
String::new()
};
decorate_series_row_with_style(
series,
Row::new(vec![
Cell::from(series.title.to_string()),
Cell::from(series.year.to_string()),
Cell::from(network),
Cell::from(format!("{hours}h {minutes}m")),
Cell::from(certification),
Cell::from(series.series_type.to_display_str()),
Cell::from(quality_profile),
Cell::from(language_profile),
Cell::from(monitored.to_owned()),
Cell::from(tags),
]),
)
};
let series_table = ManagarrTable::new(content, series_table_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.footer(help_footer)
.sorting(active_sonarr_block == ActiveSonarrBlock::SeriesSortPrompt)
.headers([
"Title",
"Year",
"Network",
"Runtime",
"Rating",
"Type",
"Quality Profile",
"Language Profile",
"Monitored",
"Tags",
])
.constraints([
Constraint::Percentage(27),
Constraint::Percentage(4),
Constraint::Percentage(10),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(13),
Constraint::Percentage(10),
Constraint::Percentage(6),
Constraint::Percentage(12),
]);
f.render_widget(series_table, area);
}
}
fn decorate_series_row_with_style<'a>(series: &Series, row: Row<'a>) -> Row<'a> {
match series.status {
SeriesStatus::Ended => {
if let Some(ref stats) = series.statistics {
if stats.percent_of_episodes == 100.0 {
return row.downloaded();
}
}
row.missing()
}
SeriesStatus::Continuing => {
if let Some(ref stats) = series.statistics {
if stats.percent_of_episodes == 100.0 {
return row.unreleased();
}
}
row.missing()
}
SeriesStatus::Upcoming => row.unreleased(),
_ => row.missing(),
}
}