diff --git a/src/ui/sonarr_ui/blocklist/blocklist_ui_tests.rs b/src/ui/sonarr_ui/blocklist/blocklist_ui_tests.rs new file mode 100644 index 0000000..009adc0 --- /dev/null +++ b/src/ui/sonarr_ui/blocklist/blocklist_ui_tests.rs @@ -0,0 +1,18 @@ +#[cfg(test)] +mod tests { + use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, BLOCKLIST_BLOCKS}; + use crate::ui::sonarr_ui::blocklist::BlocklistUi; + use crate::ui::DrawUi; + use strum::IntoEnumIterator; + + #[test] + fn test_blocklist_ui_accepts() { + ActiveSonarrBlock::iter().for_each(|active_sonarr_block| { + if BLOCKLIST_BLOCKS.contains(&active_sonarr_block) { + assert!(BlocklistUi::accepts(active_sonarr_block.into())); + } else { + assert!(!BlocklistUi::accepts(active_sonarr_block.into())); + } + }); + } +} diff --git a/src/ui/sonarr_ui/blocklist/mod.rs b/src/ui/sonarr_ui/blocklist/mod.rs new file mode 100644 index 0000000..2cf068e --- /dev/null +++ b/src/ui/sonarr_ui/blocklist/mod.rs @@ -0,0 +1,178 @@ +use crate::app::App; +use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, BLOCKLIST_BLOCKS}; +use crate::models::sonarr_models::BlocklistItem; +use crate::models::Route; +use crate::ui::styles::ManagarrStyle; +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; +use crate::ui::widgets::message::Message; +use crate::ui::widgets::popup::{Popup, Size}; +use crate::ui::DrawUi; +use ratatui::layout::{Alignment, Constraint, Rect}; +use ratatui::style::{Style, Stylize}; +use ratatui::text::{Line, Text}; +use ratatui::widgets::{Cell, Row}; +use ratatui::Frame; + +#[cfg(test)] +#[path = "blocklist_ui_tests.rs"] +mod blocklist_ui_tests; + +pub(super) struct BlocklistUi; + +impl DrawUi for BlocklistUi { + fn accepts(route: Route) -> bool { + if let Route::Sonarr(active_sonarr_block, _) = route { + return BLOCKLIST_BLOCKS.contains(&active_sonarr_block); + } + + false + } + + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { + if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() { + match active_sonarr_block { + ActiveSonarrBlock::Blocklist | ActiveSonarrBlock::BlocklistSortPrompt => { + draw_blocklist_table(f, app, area) + } + ActiveSonarrBlock::BlocklistItemDetails => { + draw_blocklist_table(f, app, area); + draw_blocklist_item_details_popup(f, app); + } + ActiveSonarrBlock::DeleteBlocklistItemPrompt => { + let prompt = format!( + "Do you want to remove this item from your blocklist: \n{}?", + app + .data + .sonarr_data + .blocklist + .current_selection() + .source_title + ); + let confirmation_prompt = ConfirmationPrompt::new() + .title("Remove Item from Blocklist") + .prompt(&prompt) + .yes_no_value(app.data.sonarr_data.prompt_confirm); + + draw_blocklist_table(f, app, area); + f.render_widget(Popup::new(confirmation_prompt).size(Size::Prompt), f.area()); + } + ActiveSonarrBlock::BlocklistClearAllItemsPrompt => { + let confirmation_prompt = ConfirmationPrompt::new() + .title("Clear Blocklist") + .prompt("Do you want to clear your blocklist?") + .yes_no_value(app.data.sonarr_data.prompt_confirm); + + draw_blocklist_table(f, app, area); + f.render_widget( + Popup::new(confirmation_prompt).size(Size::SmallPrompt), + f.area(), + ); + } + _ => (), + } + } + } +} + +fn draw_blocklist_table(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.blocklist.items.is_empty() { + BlocklistItem::default() + } else { + app.data.sonarr_data.blocklist.current_selection().clone() + }; + let blocklist_table_footer = app + .data + .sonarr_data + .main_tabs + .get_active_tab_contextual_help(); + + let blocklist_row_mapping = |blocklist_item: &BlocklistItem| { + let BlocklistItem { + source_title, + series_title, + language, + quality, + date, + .. + } = blocklist_item; + + let title = series_title.as_ref().unwrap_or(&String::new()).to_owned(); + + Row::new(vec![ + Cell::from(title), + Cell::from(source_title.to_owned()), + Cell::from(language.name.to_owned()), + Cell::from(quality.quality.name.to_owned()), + Cell::from(date.to_string()), + ]) + .primary() + }; + let blocklist_table = ManagarrTable::new( + Some(&mut app.data.sonarr_data.blocklist), + blocklist_row_mapping, + ) + .block(layout_block_top_border()) + .loading(app.is_loading) + .footer(blocklist_table_footer) + .sorting(active_sonarr_block == ActiveSonarrBlock::BlocklistSortPrompt) + .headers([ + "Series Title", + "Source Title", + "Languages", + "Quality", + "Date", + ]) + .constraints([ + Constraint::Percentage(25), + Constraint::Percentage(40), + Constraint::Percentage(10), + Constraint::Percentage(10), + Constraint::Percentage(15), + ]); + + f.render_widget(blocklist_table, area); + } +} + +fn draw_blocklist_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) { + let current_selection = if app.data.sonarr_data.blocklist.items.is_empty() { + BlocklistItem::default() + } else { + app.data.sonarr_data.blocklist.current_selection().clone() + }; + let BlocklistItem { + source_title, + protocol, + indexer, + message, + .. + } = current_selection; + let text = Text::from(vec![ + Line::from(vec![ + "Name: ".bold().secondary(), + source_title.to_owned().secondary(), + ]), + Line::from(vec![ + "Protocol: ".bold().secondary(), + protocol.to_owned().secondary(), + ]), + Line::from(vec![ + "Indexer: ".bold().secondary(), + indexer.to_owned().secondary(), + ]), + Line::from(vec![ + "Message: ".bold().secondary(), + message.to_owned().secondary(), + ]), + ]); + + let message = Message::new(text) + .title("Details") + .style(Style::new().secondary()) + .alignment(Alignment::Left); + + f.render_widget(Popup::new(message).size(Size::NarrowMessage), f.area()); +} diff --git a/src/ui/sonarr_ui/mod.rs b/src/ui/sonarr_ui/mod.rs index b10f4aa..7fac977 100644 --- a/src/ui/sonarr_ui/mod.rs +++ b/src/ui/sonarr_ui/mod.rs @@ -1,5 +1,6 @@ use std::{cmp, iter}; +use blocklist::BlocklistUi; use chrono::{Duration, Utc}; use downloads::DownloadsUi; use library::LibraryUi; @@ -33,6 +34,7 @@ use super::{ DrawUi, }; +mod blocklist; mod downloads; mod library; @@ -54,6 +56,7 @@ impl DrawUi for SonarrUi { match route { _ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area), _ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_area), + _ if BlocklistUi::accepts(route) => BlocklistUi::draw(f, app, content_area), _ => (), } }