feat(ui): Sonarr support for the series details popup
This commit is contained in:
@@ -82,7 +82,7 @@ impl DrawUi for EditSeriesUi {
|
||||
}
|
||||
_ if SERIES_DETAILS_BLOCKS.contains(&context) => {
|
||||
draw_popup_over_ui::<SeriesDetailsUi>(f, app, area, draw_library, Size::Large);
|
||||
draw_popup(f, app, draw_edit_series_prompt, Size::Medium);
|
||||
draw_popup(f, app, draw_edit_series_prompt, Size::Long);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ mod tests {
|
||||
use crate::ui::DrawUi;
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::widgets::{Cell, Row};
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::models::sonarr_models::{Season, SeasonStatistics};
|
||||
use crate::{
|
||||
models::sonarr_models::{Series, SeriesStatistics},
|
||||
models::sonarr_models::Series,
|
||||
ui::sonarr_ui::library::decorate_series_row_with_style,
|
||||
};
|
||||
|
||||
@@ -38,45 +38,193 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(SeriesStatus::Ended, None, RowStyle::Missing)]
|
||||
#[case(SeriesStatus::Ended, Some(59.0), RowStyle::Missing)]
|
||||
#[case(SeriesStatus::Ended, Some(100.0), RowStyle::Downloaded)]
|
||||
#[case(SeriesStatus::Continuing, None, RowStyle::Missing)]
|
||||
#[case(SeriesStatus::Continuing, Some(59.0), RowStyle::Missing)]
|
||||
#[case(SeriesStatus::Continuing, Some(100.0), RowStyle::Unreleased)]
|
||||
#[case(SeriesStatus::Upcoming, None, RowStyle::Unreleased)]
|
||||
#[case(SeriesStatus::Deleted, None, RowStyle::Missing)]
|
||||
fn test_decorate_series_row_with_style(
|
||||
#[case] series_status: SeriesStatus,
|
||||
#[case] percent_of_episodes: Option<f64>,
|
||||
#[case] expected_row_style: RowStyle,
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_downloaded_when_ended_and_all_monitored_episodes_are_present(
|
||||
) {
|
||||
let mut series = Series {
|
||||
status: series_status,
|
||||
let seasons = vec![
|
||||
Season {
|
||||
monitored: false,
|
||||
statistics: SeasonStatistics {
|
||||
episode_count: 1,
|
||||
total_episode_count: 3,
|
||||
..SeasonStatistics::default()
|
||||
},
|
||||
..Season::default()
|
||||
},
|
||||
Season {
|
||||
monitored: true,
|
||||
statistics: SeasonStatistics {
|
||||
episode_count: 3,
|
||||
total_episode_count: 3,
|
||||
..SeasonStatistics::default()
|
||||
},
|
||||
..Season::default()
|
||||
},
|
||||
];
|
||||
let series = Series {
|
||||
status: SeriesStatus::Ended,
|
||||
seasons: Some(seasons),
|
||||
..Series::default()
|
||||
};
|
||||
if let Some(percentage) = percent_of_episodes {
|
||||
series.statistics = Some(SeriesStatistics {
|
||||
percent_of_episodes: percentage,
|
||||
..SeriesStatistics::default()
|
||||
});
|
||||
}
|
||||
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_series_row_with_style(&series, row.clone());
|
||||
|
||||
match expected_row_style {
|
||||
RowStyle::Downloaded => assert_eq!(style, row.downloaded()),
|
||||
RowStyle::Missing => assert_eq!(style, row.missing()),
|
||||
RowStyle::Unreleased => assert_eq!(style, row.unreleased()),
|
||||
}
|
||||
assert_eq!(style, row.downloaded());
|
||||
}
|
||||
|
||||
enum RowStyle {
|
||||
Downloaded,
|
||||
Missing,
|
||||
Unreleased,
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_missing_when_ended_and_episodes_are_missing() {
|
||||
let seasons = vec![
|
||||
Season {
|
||||
monitored: true,
|
||||
statistics: SeasonStatistics {
|
||||
episode_count: 1,
|
||||
total_episode_count: 3,
|
||||
..SeasonStatistics::default()
|
||||
},
|
||||
..Season::default()
|
||||
},
|
||||
Season {
|
||||
monitored: true,
|
||||
statistics: SeasonStatistics {
|
||||
episode_count: 3,
|
||||
total_episode_count: 3,
|
||||
..SeasonStatistics::default()
|
||||
},
|
||||
..Season::default()
|
||||
},
|
||||
];
|
||||
let series = Series {
|
||||
status: SeriesStatus::Ended,
|
||||
seasons: Some(seasons),
|
||||
..Series::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_series_row_with_style(&series, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_indeterminate_when_ended_and_seasons_is_empty() {
|
||||
let series = Series {
|
||||
status: SeriesStatus::Ended,
|
||||
..Series::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_series_row_with_style(&series, row.clone());
|
||||
|
||||
assert_eq!(style, row.indeterminate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_unreleased_when_continuing_and_all_monitored_episodes_are_present(
|
||||
) {
|
||||
let seasons = vec![
|
||||
Season {
|
||||
monitored: false,
|
||||
statistics: SeasonStatistics {
|
||||
episode_count: 1,
|
||||
total_episode_count: 3,
|
||||
..SeasonStatistics::default()
|
||||
},
|
||||
..Season::default()
|
||||
},
|
||||
Season {
|
||||
monitored: true,
|
||||
statistics: SeasonStatistics {
|
||||
episode_count: 3,
|
||||
total_episode_count: 3,
|
||||
..SeasonStatistics::default()
|
||||
},
|
||||
..Season::default()
|
||||
},
|
||||
];
|
||||
let series = Series {
|
||||
status: SeriesStatus::Continuing,
|
||||
seasons: Some(seasons),
|
||||
..Series::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_series_row_with_style(&series, row.clone());
|
||||
|
||||
assert_eq!(style, row.unreleased());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_missing_when_continuing_and_episodes_are_missing() {
|
||||
let seasons = vec![
|
||||
Season {
|
||||
monitored: true,
|
||||
statistics: SeasonStatistics {
|
||||
episode_count: 1,
|
||||
total_episode_count: 3,
|
||||
..SeasonStatistics::default()
|
||||
},
|
||||
..Season::default()
|
||||
},
|
||||
Season {
|
||||
monitored: true,
|
||||
statistics: SeasonStatistics {
|
||||
episode_count: 3,
|
||||
total_episode_count: 3,
|
||||
..SeasonStatistics::default()
|
||||
},
|
||||
..Season::default()
|
||||
},
|
||||
];
|
||||
let series = Series {
|
||||
status: SeriesStatus::Continuing,
|
||||
seasons: Some(seasons),
|
||||
..Series::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_series_row_with_style(&series, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_indeterminate_when_continuing_and_seasons_is_empty() {
|
||||
let series = Series {
|
||||
status: SeriesStatus::Continuing,
|
||||
..Series::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_series_row_with_style(&series, row.clone());
|
||||
|
||||
assert_eq!(style, row.indeterminate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_unreleased_when_upcoming() {
|
||||
let series = Series {
|
||||
status: SeriesStatus::Upcoming,
|
||||
..Series::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_series_row_with_style(&series, row.clone());
|
||||
|
||||
assert_eq!(style, row.unreleased());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_row_with_style_defaults_to_indeterminate() {
|
||||
let series = Series {
|
||||
status: SeriesStatus::Deleted,
|
||||
..Series::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_series_row_with_style(&series, row.clone());
|
||||
|
||||
assert_eq!(style, row.indeterminate());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,35 +61,6 @@ impl DrawUi for LibraryUi {
|
||||
| ActiveSonarrBlock::SearchSeriesError
|
||||
| ActiveSonarrBlock::FilterSeries
|
||||
| ActiveSonarrBlock::FilterSeriesError => draw_library(f, app, area),
|
||||
// ActiveSonarrBlock::SearchSeries => draw_popup_over(
|
||||
// f,
|
||||
// app,
|
||||
// area,
|
||||
// draw_library,
|
||||
// draw_library_search_box,
|
||||
// Size::InputBox,
|
||||
// ),
|
||||
// ActiveSonarrBlock::SearchSeriesError => {
|
||||
// let popup = Popup::new(Message::new("Series not found!")).size(Size::Message);
|
||||
|
||||
// draw_library(f, app, area);
|
||||
// f.render_widget(popup, f.area());
|
||||
// }
|
||||
// ActiveSonarrBlock::FilterSeries => draw_popup_over(
|
||||
// f,
|
||||
// app,
|
||||
// area,
|
||||
// draw_library,
|
||||
// draw_filter_series_box,
|
||||
// Size::InputBox,
|
||||
// ),
|
||||
// ActiveSonarrBlock::FilterSeriesError => {
|
||||
// let popup = Popup::new(Message::new("No series found matching the given filter!"))
|
||||
// .size(Size::Message);
|
||||
|
||||
// draw_library(f, app, area);
|
||||
// f.render_widget(popup, f.area());
|
||||
// }
|
||||
ActiveSonarrBlock::UpdateAllSeriesPrompt => {
|
||||
let confirmation_prompt = ConfirmationPrompt::new()
|
||||
.title("Update All Series")
|
||||
@@ -234,24 +205,36 @@ pub(super) fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
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();
|
||||
if let Some(ref seasons) = series.seasons {
|
||||
return if seasons
|
||||
.iter()
|
||||
.filter(|season| season.monitored)
|
||||
.all(|season| season.statistics.episode_count == season.statistics.total_episode_count)
|
||||
{
|
||||
row.downloaded()
|
||||
} else {
|
||||
row.missing()
|
||||
}
|
||||
}
|
||||
|
||||
row.missing()
|
||||
}
|
||||
|
||||
row.indeterminate()
|
||||
}
|
||||
SeriesStatus::Continuing => {
|
||||
if let Some(ref stats) = series.statistics {
|
||||
if stats.percent_of_episodes == 100.0 {
|
||||
return row.unreleased();
|
||||
}
|
||||
if let Some(ref seasons) = series.seasons {
|
||||
return if seasons
|
||||
.iter()
|
||||
.filter(|season| season.monitored)
|
||||
.all(|season| season.statistics.episode_count == season.statistics.total_episode_count)
|
||||
{
|
||||
row.unreleased()
|
||||
} else {
|
||||
row.missing()
|
||||
};
|
||||
}
|
||||
|
||||
row.missing()
|
||||
row.indeterminate()
|
||||
}
|
||||
SeriesStatus::Upcoming => row.unreleased(),
|
||||
_ => row.missing(),
|
||||
_ => row.indeterminate(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use deunicode::deunicode;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::style::{Style, Stylize};
|
||||
use ratatui::text::{Line, Text};
|
||||
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
||||
use ratatui::Frame;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SERIES_DETAILS_BLOCKS};
|
||||
@@ -67,6 +69,20 @@ impl DrawUi for SeriesDetailsUi {
|
||||
draw_series_details(f, app, content_area);
|
||||
|
||||
match active_sonarr_block {
|
||||
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => {
|
||||
let prompt = format!(
|
||||
"Do you want to trigger an automatic search of your indexers for all monitored episode(s) for the series: {}", app.data.sonarr_data.series.current_selection().title
|
||||
);
|
||||
let confirmation_prompt = ConfirmationPrompt::new()
|
||||
.title("Automatic Series Search")
|
||||
.prompt(&prompt)
|
||||
.yes_no_value(app.data.sonarr_data.prompt_confirm);
|
||||
|
||||
f.render_widget(
|
||||
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
|
||||
f.area(),
|
||||
);
|
||||
}
|
||||
ActiveSonarrBlock::UpdateAndScanSeriesPrompt => {
|
||||
let prompt = format!(
|
||||
"Do you want to trigger an update and disk scan for the series: {}?",
|
||||
@@ -83,14 +99,7 @@ impl DrawUi for SeriesDetailsUi {
|
||||
);
|
||||
}
|
||||
ActiveSonarrBlock::SeriesHistoryDetails => {
|
||||
draw_popup_over(
|
||||
f,
|
||||
app,
|
||||
popup_area,
|
||||
draw_series_history_table,
|
||||
draw_history_item_details_popup,
|
||||
Size::Small,
|
||||
);
|
||||
draw_history_item_details_popup(f, app, popup_area);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
@@ -129,19 +138,25 @@ pub fn draw_series_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
|
||||
.get_by_left(¤t_selection.language_profile_id)
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
let overview = Regex::new(r"[\r\n\t]")
|
||||
.unwrap()
|
||||
.replace_all(
|
||||
&deunicode(
|
||||
current_selection
|
||||
.overview
|
||||
.as_ref()
|
||||
.unwrap_or(&String::new()),
|
||||
),
|
||||
"",
|
||||
)
|
||||
.to_string();
|
||||
|
||||
let mut series_description = vec![
|
||||
Line::from(vec![
|
||||
"Title: ".primary().bold(),
|
||||
current_selection.title.text.clone().primary().bold(),
|
||||
]),
|
||||
Line::from(vec![
|
||||
"Overview: ".primary().bold(),
|
||||
current_selection
|
||||
.overview
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.default(),
|
||||
]),
|
||||
Line::from(vec!["Overview: ".primary().bold(), overview.default()]),
|
||||
Line::from(vec![
|
||||
"Network: ".primary().bold(),
|
||||
current_selection
|
||||
@@ -194,7 +209,7 @@ pub fn draw_series_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
|
||||
|
||||
let description_paragraph = Paragraph::new(series_description)
|
||||
.block(borderless_block())
|
||||
.wrap(Wrap { trim: false });
|
||||
.wrap(Wrap { trim: true });
|
||||
f.render_widget(description_paragraph, area);
|
||||
}
|
||||
|
||||
@@ -220,9 +235,10 @@ fn draw_seasons_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
.get_active_tab_contextual_help();
|
||||
let season_row_mapping = |season: &Season| {
|
||||
let Season {
|
||||
season_number,
|
||||
title,
|
||||
monitored,
|
||||
statistics,
|
||||
..
|
||||
} = season;
|
||||
let SeasonStatistics {
|
||||
episode_count,
|
||||
@@ -235,7 +251,7 @@ fn draw_seasons_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
|
||||
let row = Row::new(vec![
|
||||
Cell::from(season_monitored.to_owned()),
|
||||
Cell::from(format!("Season {}", season_number)),
|
||||
Cell::from(title.clone().unwrap()),
|
||||
Cell::from(format!("{}/{}", episode_count, total_episode_count)),
|
||||
Cell::from(format!("{size:.2} GB")),
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user