From 5abed23cf29e3947468786cb90a1e299a6239f98 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Thu, 5 Dec 2024 19:07:03 -0700 Subject: [PATCH] refactor(ui): all table search and filter functionality is now available directly through the ManagarrTable widget to make life easier moving forward --- Cargo.toml | 5 +- src/ui/mod.rs | 7 +- src/ui/radarr_ui/collections/mod.rs | 78 ++--- src/ui/radarr_ui/library/mod.rs | 70 ++--- src/ui/sonarr_ui/history/mod.rs | 265 +++-------------- src/ui/sonarr_ui/library/library_ui_tests.rs | 10 +- src/ui/widgets/input_box.rs | 14 +- src/ui/widgets/input_box_popup.rs | 60 ++++ src/ui/widgets/input_box_popup_tests.rs | 34 +++ src/ui/widgets/managarr_table.rs | 123 +++++++- src/ui/widgets/managarr_table_tests.rs | 283 ++++++++++++++++++- src/ui/widgets/mod.rs | 1 + src/ui/widgets/popup.rs | 4 +- src/ui/widgets/popup_tests.rs | 1 + 14 files changed, 609 insertions(+), 346 deletions(-) create mode 100644 src/ui/widgets/input_box_popup.rs create mode 100644 src/ui/widgets/input_box_popup_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 7886fd5..937f273 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,10 @@ strum = { version = "0.26.3", features = ["derive"] } strum_macros = "0.26.4" tokio = { version = "1.36.0", features = ["full"] } tokio-util = "0.7.8" -ratatui = { version = "0.29.0", features = ["all-widgets"] } +ratatui = { version = "0.29.0", features = [ + "all-widgets", + "unstable-widget-ref", +] } urlencoding = "2.1.2" clap = { version = "4.5.20", features = ["derive", "cargo", "env"] } clap_complete = "4.5.33" diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 61bc4a4..a91ae29 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -9,6 +9,7 @@ use ratatui::widgets::Tabs; use ratatui::widgets::Wrap; use ratatui::Frame; use sonarr_ui::SonarrUi; +use utils::layout_block; use crate::app::App; use crate::models::{HorizontallyScrollableText, Route, TabState}; @@ -161,7 +162,11 @@ pub fn draw_popup_over_ui( } fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -> Rect { - f.render_widget(title_block(title), area); + if title.is_empty() { + f.render_widget(layout_block(), area); + } else { + f.render_widget(title_block(title), area); + } let [header_area, content_area] = Layout::vertical([Constraint::Length(1), Constraint::Fill(0)]) .margin(1) diff --git a/src/ui/radarr_ui/collections/mod.rs b/src/ui/radarr_ui/collections/mod.rs index 8b033a3..f86b51f 100644 --- a/src/ui/radarr_ui/collections/mod.rs +++ b/src/ui/radarr_ui/collections/mod.rs @@ -14,9 +14,8 @@ 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::{draw_input_box_popup, draw_popup_over, DrawUi}; +use crate::ui::DrawUi; mod collection_details_ui; #[cfg(test)] @@ -40,40 +39,12 @@ impl DrawUi for CollectionsUi { fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let route = app.get_current_route(); let mut collections_ui_matcher = |active_radarr_block| match active_radarr_block { - ActiveRadarrBlock::Collections | ActiveRadarrBlock::CollectionsSortPrompt => { - draw_collections(f, app, area) - } - ActiveRadarrBlock::SearchCollection => draw_popup_over( - f, - app, - area, - draw_collections, - draw_collection_search_box, - Size::InputBox, - ), - ActiveRadarrBlock::SearchCollectionError => { - let popup = Popup::new(Message::new("Collection not found!")).size(Size::Message); - - draw_collections(f, app, area); - f.render_widget(popup, f.area()); - } - ActiveRadarrBlock::FilterCollections => draw_popup_over( - f, - app, - area, - draw_collections, - draw_filter_collections_box, - Size::InputBox, - ), - ActiveRadarrBlock::FilterCollectionsError => { - let popup = Popup::new(Message::new( - "No collections found matching the given filter!", - )) - .size(Size::Message); - - draw_collections(f, app, area); - f.render_widget(popup, f.area()); - } + ActiveRadarrBlock::Collections + | ActiveRadarrBlock::CollectionsSortPrompt + | ActiveRadarrBlock::SearchCollection + | ActiveRadarrBlock::SearchCollectionError + | ActiveRadarrBlock::FilterCollections + | ActiveRadarrBlock::FilterCollectionsError => draw_collections(f, app, area), ActiveRadarrBlock::UpdateAllCollectionsPrompt => { let confirmation_prompt = ConfirmationPrompt::new() .title("Update All Collections") @@ -156,6 +127,14 @@ pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) .footer(collections_table_footer) .block(layout_block_top_border()) .sorting(active_radarr_block == ActiveRadarrBlock::CollectionsSortPrompt) + .searching(active_radarr_block == ActiveRadarrBlock::SearchCollection) + .search_produced_empty_results( + active_radarr_block == ActiveRadarrBlock::SearchCollectionError, + ) + .filtering(active_radarr_block == ActiveRadarrBlock::FilterCollections) + .filter_produced_empty_results( + active_radarr_block == ActiveRadarrBlock::FilterCollectionsError, + ) .headers([ "Collection", "Number of Movies", @@ -173,24 +152,15 @@ pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) Constraint::Percentage(15), ]); + if [ + ActiveRadarrBlock::SearchCollection, + ActiveRadarrBlock::FilterCollections, + ] + .contains(&active_radarr_block) + { + collections_table.show_cursor(f, area); + } + f.render_widget(collections_table, area); } } - -fn draw_collection_search_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - draw_input_box_popup( - f, - area, - "Search", - app.data.radarr_data.collections.search.as_ref().unwrap(), - ); -} - -fn draw_filter_collections_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - draw_input_box_popup( - f, - area, - "Filter", - app.data.radarr_data.collections.filter.as_ref().unwrap(), - ) -} diff --git a/src/ui/radarr_ui/library/mod.rs b/src/ui/radarr_ui/library/mod.rs index a343b44..760f31b 100644 --- a/src/ui/radarr_ui/library/mod.rs +++ b/src/ui/radarr_ui/library/mod.rs @@ -14,9 +14,8 @@ use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi; 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::{draw_input_box_popup, draw_popup_over, DrawUi}; +use crate::ui::DrawUi; use crate::utils::{convert_runtime, convert_to_gb}; mod add_movie_ui; @@ -47,36 +46,12 @@ impl DrawUi for LibraryUi { let route = app.get_current_route(); let mut library_ui_matchers = |active_radarr_block: ActiveRadarrBlock| match active_radarr_block { - ActiveRadarrBlock::Movies | ActiveRadarrBlock::MoviesSortPrompt => draw_library(f, app, area), - ActiveRadarrBlock::SearchMovie => draw_popup_over( - f, - app, - area, - draw_library, - draw_movie_search_box, - Size::InputBox, - ), - ActiveRadarrBlock::SearchMovieError => { - let popup = Popup::new(Message::new("Movie not found!")).size(Size::Message); - - draw_library(f, app, area); - f.render_widget(popup, f.area()); - } - ActiveRadarrBlock::FilterMovies => draw_popup_over( - f, - app, - area, - draw_library, - draw_filter_movies_box, - Size::InputBox, - ), - ActiveRadarrBlock::FilterMoviesError => { - let popup = Popup::new(Message::new("No movies found matching the given filter!")) - .size(Size::Message); - - draw_library(f, app, area); - f.render_widget(popup, f.area()); - } + ActiveRadarrBlock::Movies + | ActiveRadarrBlock::MoviesSortPrompt + | ActiveRadarrBlock::SearchMovie + | ActiveRadarrBlock::SearchMovieError + | ActiveRadarrBlock::FilterMovies + | ActiveRadarrBlock::FilterMoviesError => draw_library(f, app, area), ActiveRadarrBlock::UpdateAllMoviesPrompt => { let confirmation_prompt = ConfirmationPrompt::new() .title("Update All Movies") @@ -174,6 +149,10 @@ pub(super) fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { .loading(app.is_loading) .footer(help_footer) .sorting(active_radarr_block == ActiveRadarrBlock::MoviesSortPrompt) + .searching(active_radarr_block == ActiveRadarrBlock::SearchMovie) + .search_produced_empty_results(active_radarr_block == ActiveRadarrBlock::SearchMovieError) + .filtering(active_radarr_block == ActiveRadarrBlock::FilterMovies) + .filter_produced_empty_results(active_radarr_block == ActiveRadarrBlock::FilterMoviesError) .headers([ "Title", "Year", @@ -199,24 +178,15 @@ pub(super) fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { Constraint::Percentage(12), ]); + if [ + ActiveRadarrBlock::SearchMovie, + ActiveRadarrBlock::FilterMovies, + ] + .contains(&active_radarr_block) + { + library_table.show_cursor(f, area); + } + f.render_widget(library_table, area); } } - -fn draw_movie_search_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - draw_input_box_popup( - f, - area, - "Search", - app.data.radarr_data.movies.search.as_ref().unwrap(), - ); -} - -fn draw_filter_movies_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - draw_input_box_popup( - f, - area, - "Filter", - app.data.radarr_data.movies.filter.as_ref().unwrap(), - ) -} diff --git a/src/ui/sonarr_ui/history/mod.rs b/src/ui/sonarr_ui/history/mod.rs index cec5580..70fe8a2 100644 --- a/src/ui/sonarr_ui/history/mod.rs +++ b/src/ui/sonarr_ui/history/mod.rs @@ -1,19 +1,27 @@ use crate::app::App; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, HISTORY_BLOCKS}; -use crate::models::sonarr_models::{SonarrHistoryData, SonarrHistoryEventType, SonarrHistoryItem}; +use crate::models::sonarr_models::{SonarrHistoryEventType, SonarrHistoryItem}; 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::managarr_table::ManagarrTable; use crate::ui::widgets::message::Message; use crate::ui::widgets::popup::{Popup, Size}; -use crate::ui::{draw_input_box_popup, draw_popup_over, DrawUi}; +use crate::ui::DrawUi; use ratatui::layout::{Alignment, Constraint, Rect}; -use ratatui::style::{Style, Stylize}; -use ratatui::text::{Line, Text}; +use ratatui::style::Style; +use ratatui::text::Text; use ratatui::widgets::{Cell, Row}; use ratatui::Frame; +use super::sonarr_ui_utils::{ + create_download_failed_history_event_details, + create_download_folder_imported_history_event_details, + create_episode_file_deleted_history_event_details, + create_episode_file_renamed_history_event_details, create_grabbed_history_event_details, + create_no_data_history_event_details, +}; + #[cfg(test)] #[path = "history_ui_tests.rs"] mod history_ui_tests; @@ -32,40 +40,12 @@ impl DrawUi for HistoryUi { 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::History | ActiveSonarrBlock::HistorySortPrompt => { - draw_history_table(f, app, area) - } - ActiveSonarrBlock::SearchHistory => draw_popup_over( - f, - app, - area, - draw_history_table, - draw_history_search_box, - Size::InputBox, - ), - ActiveSonarrBlock::SearchHistoryError => { - let popup = Popup::new(Message::new("History item not found!")).size(Size::Message); - - draw_history_table(f, app, area); - f.render_widget(popup, f.area()); - } - ActiveSonarrBlock::FilterHistory => draw_popup_over( - f, - app, - area, - draw_history_table, - draw_filter_history_box, - Size::InputBox, - ), - ActiveSonarrBlock::FilterHistoryError => { - let popup = Popup::new(Message::new( - "No history items found matching the given filter!", - )) - .size(Size::Message); - - draw_history_table(f, app, area); - f.render_widget(popup, f.area()); - } + ActiveSonarrBlock::History + | ActiveSonarrBlock::HistorySortPrompt + | ActiveSonarrBlock::SearchHistory + | ActiveSonarrBlock::SearchHistoryError + | ActiveSonarrBlock::FilterHistory + | ActiveSonarrBlock::FilterHistoryError => draw_history_table(f, app, area), ActiveSonarrBlock::HistoryItemDetails => { draw_history_table(f, app, area); draw_history_item_details_popup(f, app); @@ -120,6 +100,10 @@ fn draw_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { .loading(app.is_loading) .footer(history_table_footer) .sorting(active_sonarr_block == ActiveSonarrBlock::HistorySortPrompt) + .searching(active_sonarr_block == ActiveSonarrBlock::SearchHistory) + .search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchHistoryError) + .filtering(active_sonarr_block == ActiveSonarrBlock::FilterHistory) + .filter_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::FilterHistoryError) .headers(["Source Title", "Event Type", "Language", "Quality", "Date"]) .constraints([ Constraint::Percentage(40), @@ -129,6 +113,15 @@ fn draw_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { Constraint::Percentage(20), ]); + if [ + ActiveSonarrBlock::SearchHistory, + ActiveSonarrBlock::FilterHistory, + ] + .contains(&active_sonarr_block) + { + history_table.show_cursor(f, area); + } + f.render_widget(history_table, area); } } @@ -141,18 +134,20 @@ fn draw_history_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) { }; let line_vec = match current_selection.event_type { - SonarrHistoryEventType::Unknown => create_unknown_event_vec(current_selection), + SonarrHistoryEventType::Grabbed => create_grabbed_history_event_details(current_selection), SonarrHistoryEventType::DownloadFolderImported => { - create_download_folder_imported_event_vec(current_selection) + create_download_folder_imported_history_event_details(current_selection) + } + SonarrHistoryEventType::DownloadFailed => { + create_download_failed_history_event_details(current_selection) } - SonarrHistoryEventType::DownloadFailed => create_download_failed_event_vec(current_selection), SonarrHistoryEventType::EpisodeFileDeleted => { - create_episode_file_deleted_event_vec(current_selection) + create_episode_file_deleted_history_event_details(current_selection) } SonarrHistoryEventType::EpisodeFileRenamed => { - create_episode_file_renamed_event_vec(current_selection) + create_episode_file_renamed_history_event_details(current_selection) } - _ => create_no_data_event_vec(current_selection), + _ => create_no_data_history_event_details(current_selection), }; let text = Text::from(line_vec); @@ -163,185 +158,3 @@ fn draw_history_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) { f.render_widget(Popup::new(message).size(Size::NarrowMessage), f.area()); } - -fn create_unknown_event_vec(history_item: SonarrHistoryItem) -> Vec> { - let SonarrHistoryItem { - source_title, data, .. - } = history_item; - let SonarrHistoryData { - indexer, - release_group, - series_match_type, - nzb_info_url, - download_client_name, - age, - published_date, - .. - } = data; - - vec![ - Line::from(vec![ - "Source Title: ".bold().secondary(), - source_title.text.secondary(), - ]), - Line::from(vec![ - "Indexer: ".bold().secondary(), - indexer.unwrap_or_default().secondary(), - ]), - Line::from(vec![ - "Release Group: ".bold().secondary(), - release_group.unwrap_or_default().secondary(), - ]), - Line::from(vec![ - "Series Match Type: ".bold().secondary(), - series_match_type.unwrap_or_default().secondary(), - ]), - Line::from(vec![ - "NZB Info URL: ".bold().secondary(), - nzb_info_url.unwrap_or_default().secondary(), - ]), - Line::from(vec![ - "Download Client Name: ".bold().secondary(), - download_client_name.unwrap_or_default().secondary(), - ]), - Line::from(vec![ - "Age: ".bold().secondary(), - format!("{} days", age.unwrap_or("0".to_owned())).secondary(), - ]), - Line::from(vec![ - "Published Date: ".bold().secondary(), - published_date.unwrap_or_default().to_string().secondary(), - ]), - ] -} - -fn create_download_folder_imported_event_vec( - history_item: SonarrHistoryItem, -) -> Vec> { - let SonarrHistoryItem { - source_title, data, .. - } = history_item; - let SonarrHistoryData { - dropped_path, - imported_path, - .. - } = data; - - vec![ - Line::from(vec![ - "Source Title: ".bold().secondary(), - source_title.text.secondary(), - ]), - Line::from(vec![ - "Dropped Path: ".bold().secondary(), - dropped_path.unwrap_or_default().secondary(), - ]), - Line::from(vec![ - "Imported Path: ".bold().secondary(), - imported_path.unwrap_or_default().secondary(), - ]), - ] -} - -fn create_download_failed_event_vec(history_item: SonarrHistoryItem) -> Vec> { - let SonarrHistoryItem { - source_title, data, .. - } = history_item; - let SonarrHistoryData { message, .. } = data; - - vec![ - Line::from(vec![ - "Source Title: ".bold().secondary(), - source_title.text.secondary(), - ]), - Line::from(vec![ - "Message: ".bold().secondary(), - message.unwrap_or_default().secondary(), - ]), - ] -} - -fn create_episode_file_deleted_event_vec(history_item: SonarrHistoryItem) -> Vec> { - let SonarrHistoryItem { - source_title, data, .. - } = history_item; - let SonarrHistoryData { reason, .. } = data; - - vec![ - Line::from(vec![ - "Source Title: ".bold().secondary(), - source_title.text.secondary(), - ]), - Line::from(vec![ - "Reason: ".bold().secondary(), - reason.unwrap_or_default().secondary(), - ]), - ] -} - -fn create_episode_file_renamed_event_vec(history_item: SonarrHistoryItem) -> Vec> { - let SonarrHistoryItem { - source_title, data, .. - } = history_item; - let SonarrHistoryData { - source_path, - source_relative_path, - path, - relative_path, - .. - } = data; - - vec![ - Line::from(vec![ - "Source Title: ".bold().secondary(), - source_title.text.secondary(), - ]), - Line::from(vec![ - "Source Path: ".bold().secondary(), - source_path.unwrap_or_default().secondary(), - ]), - Line::from(vec![ - "Source Relative Path: ".bold().secondary(), - source_relative_path.unwrap_or_default().secondary(), - ]), - Line::from(vec![ - "Destination Path: ".bold().secondary(), - path.unwrap_or_default().secondary(), - ]), - Line::from(vec![ - "Destination Relative Path: ".bold().secondary(), - relative_path.unwrap_or_default().secondary(), - ]), - ] -} - -fn create_no_data_event_vec(history_item: SonarrHistoryItem) -> Vec> { - let SonarrHistoryItem { source_title, .. } = history_item; - - vec![ - Line::from(vec![ - "Source Title: ".bold().secondary(), - source_title.text.secondary(), - ]), - Line::from(vec![String::new().secondary()]), - Line::from(vec!["No additional data available".bold().secondary()]), - ] -} - -fn draw_history_search_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - draw_input_box_popup( - f, - area, - "Search", - app.data.sonarr_data.history.search.as_ref().unwrap(), - ); -} - -fn draw_filter_history_box(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - draw_input_box_popup( - f, - area, - "Filter", - app.data.sonarr_data.history.filter.as_ref().unwrap(), - ) -} diff --git a/src/ui/sonarr_ui/library/library_ui_tests.rs b/src/ui/sonarr_ui/library/library_ui_tests.rs index 417590a..7c0c075 100644 --- a/src/ui/sonarr_ui/library/library_ui_tests.rs +++ b/src/ui/sonarr_ui/library/library_ui_tests.rs @@ -2,6 +2,7 @@ mod tests { use crate::models::servarr_data::sonarr::sonarr_data::{ ActiveSonarrBlock, ADD_SERIES_BLOCKS, DELETE_SERIES_BLOCKS, EDIT_SERIES_BLOCKS, + SERIES_DETAILS_BLOCKS, }; use crate::models::{ servarr_data::sonarr::sonarr_data::LIBRARY_BLOCKS, sonarr_models::SeriesStatus, @@ -26,12 +27,13 @@ mod tests { library_ui_blocks.extend(ADD_SERIES_BLOCKS); library_ui_blocks.extend(DELETE_SERIES_BLOCKS); library_ui_blocks.extend(EDIT_SERIES_BLOCKS); + library_ui_blocks.extend(SERIES_DETAILS_BLOCKS); - ActiveSonarrBlock::iter().for_each(|active_radarr_block| { - if library_ui_blocks.contains(&active_radarr_block) { - assert!(LibraryUi::accepts(active_radarr_block.into())); + ActiveSonarrBlock::iter().for_each(|active_sonarr_block| { + if library_ui_blocks.contains(&active_sonarr_block) { + assert!(LibraryUi::accepts(active_sonarr_block.into())); } else { - assert!(!LibraryUi::accepts(active_radarr_block.into())); + assert!(!LibraryUi::accepts(active_sonarr_block.into())); } }); } diff --git a/src/ui/widgets/input_box.rs b/src/ui/widgets/input_box.rs index fa0f87c..96577b1 100644 --- a/src/ui/widgets/input_box.rs +++ b/src/ui/widgets/input_box.rs @@ -2,7 +2,7 @@ use ratatui::buffer::Buffer; use ratatui::layout::{Constraint, Layout, Position, Rect}; use ratatui::prelude::Text; use ratatui::style::{Style, Styled, Stylize}; -use ratatui::widgets::{Block, Paragraph, Widget}; +use ratatui::widgets::{Block, Paragraph, Widget, WidgetRef}; use ratatui::Frame; use crate::ui::styles::ManagarrStyle; @@ -12,6 +12,8 @@ use crate::ui::utils::{borderless_block, layout_block}; #[path = "input_box_tests.rs"] mod input_box_tests; +#[derive(Default)] +#[cfg_attr(test, derive(Debug, PartialEq))] pub struct InputBox<'a> { content: &'a str, offset: usize, @@ -96,7 +98,7 @@ impl<'a> InputBox<'a> { } } - fn render_input_box(self, area: Rect, buf: &mut Buffer) { + 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() @@ -106,7 +108,7 @@ impl<'a> InputBox<'a> { let input_box_paragraph = Paragraph::new(Text::from(self.content)) .style(style) - .block(self.block); + .block(self.block.clone()); if let Some(label) = self.label { let [label_area, text_box_area] = @@ -133,6 +135,12 @@ impl<'a> Widget for InputBox<'a> { } } +impl<'a> WidgetRef for InputBox<'a> { + fn render_ref(&self, area: Rect, buf: &mut Buffer) { + self.render_input_box(area, buf); + } +} + impl<'a> Styled for InputBox<'a> { type Item = InputBox<'a>; diff --git a/src/ui/widgets/input_box_popup.rs b/src/ui/widgets/input_box_popup.rs new file mode 100644 index 0000000..8e92a38 --- /dev/null +++ b/src/ui/widgets/input_box_popup.rs @@ -0,0 +1,60 @@ +use crate::ui::styles::ManagarrStyle; +use crate::ui::utils::{background_block, borderless_block, centered_rect}; +use ratatui::buffer::Buffer; +use ratatui::layout::{Constraint, Layout, Rect}; +use ratatui::widgets::{Block, Clear, Paragraph, Widget, WidgetRef}; + +use super::input_box::InputBox; + +#[cfg(test)] +#[path = "input_box_popup_tests.rs"] +mod input_box_popup_tests; + +pub struct InputBoxPopup<'a> { + input_box: InputBox<'a>, +} + +impl<'a> InputBoxPopup<'a> { + pub fn new(content: &'a str) -> Self { + Self { + input_box: InputBox::new(content), + } + } + + pub fn block(mut self, block: Block<'a>) -> InputBoxPopup<'a> { + self.input_box = self.input_box.block(block); + self + } + + pub fn offset(mut self, offset: usize) -> InputBoxPopup<'a> { + self.input_box = self.input_box.offset(offset); + self + } + + fn render_popup(&self, area: Rect, buf: &mut Buffer) { + let popup_area = Rect { + height: 6, + ..centered_rect(30, 20, area) + }; + Clear.render(popup_area, buf); + background_block().render(popup_area, buf); + + let [text_box_area, help_area] = + Layout::vertical([Constraint::Length(3), Constraint::Length(1)]) + .margin(1) + .areas(popup_area); + self.input_box.render_ref(text_box_area, buf); + + let help = Paragraph::new(" cancel") + .help() + .centered() + .block(borderless_block()); + help.render(help_area, buf); + } +} + +impl<'a> WidgetRef for InputBoxPopup<'a> { + fn render_ref(&self, area: Rect, buf: &mut Buffer) { + self.render_popup(area, buf); + } +} diff --git a/src/ui/widgets/input_box_popup_tests.rs b/src/ui/widgets/input_box_popup_tests.rs new file mode 100644 index 0000000..962a3b9 --- /dev/null +++ b/src/ui/widgets/input_box_popup_tests.rs @@ -0,0 +1,34 @@ +#[cfg(test)] +mod tests { + use crate::ui::utils::layout_block; + use crate::ui::widgets::input_box::InputBox; + use crate::ui::widgets::input_box_popup::InputBoxPopup; + use pretty_assertions::assert_eq; + + #[test] + fn test_input_box_popup_new() { + let expected_input_box = InputBox::new("test"); + + let input_box_popup = InputBoxPopup::new("test"); + + assert_eq!(input_box_popup.input_box, expected_input_box); + } + + #[test] + fn test_input_box_popup_block() { + let expected_input_box = InputBox::new("test").block(layout_block().title("title")); + + let input_box_popup = InputBoxPopup::new("test").block(layout_block().title("title")); + + assert_eq!(input_box_popup.input_box, expected_input_box); + } + + #[test] + fn test_input_box_popup_offset() { + let expected_input_box = InputBox::new("test").offset(5); + + let input_box_popup = InputBoxPopup::new("test").offset(5); + + assert_eq!(input_box_popup.input_box, expected_input_box); + } +} diff --git a/src/ui/widgets/managarr_table.rs b/src/ui/widgets/managarr_table.rs index eb9b5c9..b11df86 100644 --- a/src/ui/widgets/managarr_table.rs +++ b/src/ui/widgets/managarr_table.rs @@ -1,15 +1,21 @@ use crate::models::stateful_table::StatefulTable; use crate::ui::styles::ManagarrStyle; -use crate::ui::utils::layout_block_top_border; +use crate::ui::utils::{centered_rect, layout_block_top_border, title_block_centered}; use crate::ui::widgets::loading_block::LoadingBlock; use crate::ui::widgets::popup::Popup; use crate::ui::widgets::selectable_list::SelectableList; use crate::ui::HIGHLIGHT_SYMBOL; use ratatui::buffer::Buffer; -use ratatui::layout::{Alignment, Constraint, Layout, Rect}; +use ratatui::layout::{Alignment, Constraint, Layout, Position, Rect}; use ratatui::prelude::{Style, Stylize, Text}; -use ratatui::widgets::{Block, ListItem, Paragraph, Row, StatefulWidget, Table, Widget}; +use ratatui::widgets::{Block, ListItem, Paragraph, Row, StatefulWidget, Table, Widget, WidgetRef}; +use ratatui::Frame; use std::fmt::Debug; +use std::sync::atomic::Ordering; + +use super::input_box_popup::InputBoxPopup; +use super::message::Message; +use super::popup::Size; #[cfg(test)] #[path = "managarr_table_tests.rs"] @@ -31,6 +37,14 @@ where is_loading: bool, highlight_rows: bool, is_sorting: bool, + is_searching: bool, + search_produced_empty_results: bool, + is_filtering: bool, + filter_produced_empty_results: bool, + search_box_content_length: usize, + search_box_offset: usize, + filter_box_content_length: usize, + filter_box_offset: usize, } impl<'a, T, F> ManagarrTable<'a, T, F> @@ -39,8 +53,8 @@ where T: Clone + PartialEq + Eq + Debug, { pub fn new(content: Option<&'a mut StatefulTable>, row_mapper: F) -> Self { - Self { - content, + let mut managarr_table = Self { + content: None, table_headers: Vec::new(), constraints: Vec::new(), row_mapper, @@ -51,7 +65,28 @@ where is_loading: false, highlight_rows: true, is_sorting: false, + is_searching: false, + search_produced_empty_results: false, + is_filtering: false, + filter_produced_empty_results: false, + search_box_content_length: 0, + search_box_offset: 0, + filter_box_content_length: 0, + filter_box_offset: 0, + }; + + if let Some(content) = content.as_ref() { + if let Some(search) = content.search.as_ref() { + managarr_table.search_box_content_length = search.text.len(); + managarr_table.search_box_offset = search.offset.load(Ordering::SeqCst); + } else if let Some(filter) = content.filter.as_ref() { + managarr_table.filter_box_content_length = filter.text.len(); + managarr_table.filter_box_offset = filter.offset.load(Ordering::SeqCst); + } } + + managarr_table.content = content; + managarr_table } pub fn headers(mut self, headers: I) -> Self @@ -107,6 +142,26 @@ where self } + pub fn searching(mut self, is_searching: bool) -> Self { + self.is_searching = is_searching; + self + } + + pub fn search_produced_empty_results(mut self, no_search_results: bool) -> Self { + self.search_produced_empty_results = no_search_results; + self + } + + pub fn filtering(mut self, is_filtering: bool) -> Self { + self.is_filtering = is_filtering; + self + } + + pub fn filter_produced_empty_results(mut self, no_filter_results: bool) -> Self { + self.filter_produced_empty_results = no_filter_results; + self + } + fn render_table(self, area: Rect, buf: &mut Buffer) { let table_headers = self.parse_headers(); let table_area = if let Some(ref footer) = self.footer { @@ -160,6 +215,34 @@ where .dimensions(20, 50) .render(table_area, buf); } + + if self.is_searching { + let box_content = &content.search.as_ref().unwrap(); + InputBoxPopup::new(&box_content.text) + .offset(box_content.offset.load(Ordering::SeqCst)) + .block(title_block_centered("Search")) + .render_ref(table_area, buf); + } + + if self.is_filtering { + let box_content = &content.filter.as_ref().unwrap(); + InputBoxPopup::new(&box_content.text) + .offset(box_content.offset.load(Ordering::SeqCst)) + .block(title_block_centered("Filter")) + .render_ref(table_area, buf); + } + + if self.search_produced_empty_results { + Popup::new(Message::new("No items found matching search")) + .size(Size::Message) + .render(table_area, buf); + } + + if self.filter_produced_empty_results { + Popup::new(Message::new("The given filter produced empty results")) + .size(Size::Message) + .render(table_area, buf); + } } else { loading_block.render(table_area, buf); } @@ -189,6 +272,36 @@ where .map(Text::from) .collect() } + + pub fn show_cursor(&self, f: &mut Frame<'_>, area: Rect) { + let mut draw_cursor = |length: usize, offset: usize| { + let table_area = if self.footer.is_some() { + let [content_area, _] = Layout::vertical([Constraint::Fill(0), Constraint::Length(2)]) + .margin(self.margin) + .areas(area); + content_area + } else { + area + }; + let popup_area = Rect { + height: 7, + ..centered_rect(30, 20, table_area) + }; + let [text_box_area, _] = Layout::vertical([Constraint::Length(3), Constraint::Length(1)]) + .margin(1) + .areas(popup_area); + f.set_cursor_position(Position { + x: text_box_area.x + (length - offset) as u16 + 1, + y: text_box_area.y + 1, + }); + }; + + if self.is_searching { + draw_cursor(self.search_box_content_length, self.search_box_offset); + } else if self.is_filtering { + draw_cursor(self.filter_box_content_length, self.filter_box_offset); + } + } } impl<'a, T, F> Widget for ManagarrTable<'a, T, F> diff --git a/src/ui/widgets/managarr_table_tests.rs b/src/ui/widgets/managarr_table_tests.rs index 16c9951..3e43edf 100644 --- a/src/ui/widgets/managarr_table_tests.rs +++ b/src/ui/widgets/managarr_table_tests.rs @@ -2,13 +2,14 @@ mod tests { use crate::models::stateful_list::StatefulList; use crate::models::stateful_table::{SortOption, StatefulTable}; - use crate::models::Scrollable; + use crate::models::{HorizontallyScrollableText, Scrollable}; use crate::ui::utils::layout_block; use crate::ui::widgets::managarr_table::ManagarrTable; use pretty_assertions::assert_eq; use ratatui::layout::{Alignment, Constraint}; use ratatui::text::Text; use ratatui::widgets::{Block, Cell, Row}; + use std::sync::atomic::AtomicUsize; #[test] fn test_managarr_table_new() { @@ -31,6 +32,86 @@ mod tests { assert!(!managarr_table.is_loading); assert!(managarr_table.highlight_rows); assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); + } + + #[test] + fn test_managarr_table_new_search_box_populated() { + let items = vec!["item1", "item2", "item3"]; + let mut stateful_table = StatefulTable::default(); + stateful_table.set_items(items.clone()); + let horizontally_scrollable_test = HorizontallyScrollableText { + text: "test".to_owned(), + offset: AtomicUsize::new(3), + }; + stateful_table.search = Some(horizontally_scrollable_test); + + let managarr_table = + ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)])); + + let row_mapper = managarr_table.row_mapper; + assert_eq!(managarr_table.content.unwrap().items, items); + assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")])); + assert_eq!(managarr_table.table_headers, Vec::::new()); + assert_eq!(managarr_table.constraints, Vec::new()); + assert_eq!(managarr_table.footer, None); + assert_eq!(managarr_table.footer_alignment, Alignment::Left); + assert_eq!(managarr_table.block, Block::new()); + assert_eq!(managarr_table.margin, 0); + assert!(!managarr_table.is_loading); + assert!(managarr_table.highlight_rows); + assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 4); + assert_eq!(managarr_table.search_box_offset, 3); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); + } + + #[test] + fn test_managarr_table_new_filter_box_populated() { + let items = vec!["item1", "item2", "item3"]; + let mut stateful_table = StatefulTable::default(); + stateful_table.set_items(items.clone()); + let horizontally_scrollable_test = HorizontallyScrollableText { + text: "test".to_owned(), + offset: AtomicUsize::new(3), + }; + stateful_table.filter = Some(horizontally_scrollable_test); + + let managarr_table = + ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)])); + + let row_mapper = managarr_table.row_mapper; + assert_eq!(managarr_table.content.unwrap().items, items); + assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")])); + assert_eq!(managarr_table.table_headers, Vec::::new()); + assert_eq!(managarr_table.constraints, Vec::new()); + assert_eq!(managarr_table.footer, None); + assert_eq!(managarr_table.footer_alignment, Alignment::Left); + assert_eq!(managarr_table.block, Block::new()); + assert_eq!(managarr_table.margin, 0); + assert!(!managarr_table.is_loading); + assert!(managarr_table.highlight_rows); + assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 4); + assert_eq!(managarr_table.filter_box_offset, 3); } #[test] @@ -56,6 +137,14 @@ mod tests { assert!(!managarr_table.is_loading); assert!(managarr_table.highlight_rows); assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); } #[test] @@ -81,6 +170,14 @@ mod tests { assert!(!managarr_table.is_loading); assert!(managarr_table.highlight_rows); assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); } #[test] @@ -106,6 +203,14 @@ mod tests { assert!(!managarr_table.is_loading); assert!(managarr_table.highlight_rows); assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); } #[test] @@ -130,6 +235,14 @@ mod tests { assert!(!managarr_table.is_loading); assert!(managarr_table.highlight_rows); assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); } #[test] @@ -154,6 +267,14 @@ mod tests { assert!(!managarr_table.is_loading); assert!(managarr_table.highlight_rows); assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); } #[test] @@ -177,6 +298,14 @@ mod tests { assert!(!managarr_table.is_loading); assert!(managarr_table.highlight_rows); assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); } #[test] @@ -201,6 +330,14 @@ mod tests { assert_eq!(managarr_table.margin, 0); assert!(managarr_table.highlight_rows); assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); } #[test] @@ -225,6 +362,14 @@ mod tests { assert_eq!(managarr_table.margin, 0); assert!(!managarr_table.is_loading); assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); } #[test] @@ -249,6 +394,142 @@ mod tests { assert_eq!(managarr_table.margin, 0); assert!(!managarr_table.is_loading); assert!(managarr_table.highlight_rows); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); + } + + #[test] + fn test_managarr_table_is_searching() { + let items = vec!["item1", "item2", "item3"]; + let mut stateful_table = StatefulTable::default(); + stateful_table.set_items(items.clone()); + + let managarr_table = + ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)])) + .searching(true); + + let row_mapper = managarr_table.row_mapper; + assert!(managarr_table.is_searching); + assert_eq!(managarr_table.content.unwrap().items, items); + assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")])); + assert_eq!(managarr_table.table_headers, Vec::::new()); + assert_eq!(managarr_table.constraints, Vec::new()); + assert_eq!(managarr_table.footer, None); + assert_eq!(managarr_table.footer_alignment, Alignment::Left); + assert_eq!(managarr_table.block, Block::new()); + assert_eq!(managarr_table.margin, 0); + assert!(!managarr_table.is_loading); + assert!(managarr_table.highlight_rows); + assert!(!managarr_table.is_sorting); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); + } + + #[test] + fn test_managarr_table_search_produced_empty_results() { + let items = vec!["item1", "item2", "item3"]; + let mut stateful_table = StatefulTable::default(); + stateful_table.set_items(items.clone()); + + let managarr_table = + ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)])) + .search_produced_empty_results(true); + + let row_mapper = managarr_table.row_mapper; + assert!(managarr_table.search_produced_empty_results); + assert_eq!(managarr_table.content.unwrap().items, items); + assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")])); + assert_eq!(managarr_table.table_headers, Vec::::new()); + assert_eq!(managarr_table.constraints, Vec::new()); + assert_eq!(managarr_table.footer, None); + assert_eq!(managarr_table.footer_alignment, Alignment::Left); + assert_eq!(managarr_table.block, Block::new()); + assert_eq!(managarr_table.margin, 0); + assert!(!managarr_table.is_loading); + assert!(managarr_table.highlight_rows); + assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.is_filtering); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); + } + + #[test] + fn test_managarr_table_is_filtering() { + let items = vec!["item1", "item2", "item3"]; + let mut stateful_table = StatefulTable::default(); + stateful_table.set_items(items.clone()); + + let managarr_table = + ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)])) + .filtering(true); + + let row_mapper = managarr_table.row_mapper; + assert!(managarr_table.is_filtering); + assert_eq!(managarr_table.content.unwrap().items, items); + assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")])); + assert_eq!(managarr_table.table_headers, Vec::::new()); + assert_eq!(managarr_table.constraints, Vec::new()); + assert_eq!(managarr_table.footer, None); + assert_eq!(managarr_table.footer_alignment, Alignment::Left); + assert_eq!(managarr_table.block, Block::new()); + assert_eq!(managarr_table.margin, 0); + assert!(!managarr_table.is_loading); + assert!(managarr_table.highlight_rows); + assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); + } + + #[test] + fn test_managarr_table_filter_produced_empty_results() { + let items = vec!["item1", "item2", "item3"]; + let mut stateful_table = StatefulTable::default(); + stateful_table.set_items(items.clone()); + + let managarr_table = + ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)])) + .filter_produced_empty_results(true); + + let row_mapper = managarr_table.row_mapper; + assert!(managarr_table.filter_produced_empty_results); + assert_eq!(managarr_table.content.unwrap().items, items); + assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")])); + assert_eq!(managarr_table.table_headers, Vec::::new()); + assert_eq!(managarr_table.constraints, Vec::new()); + assert_eq!(managarr_table.footer, None); + assert_eq!(managarr_table.footer_alignment, Alignment::Left); + assert_eq!(managarr_table.block, Block::new()); + assert_eq!(managarr_table.margin, 0); + assert!(!managarr_table.is_loading); + assert!(managarr_table.highlight_rows); + assert!(!managarr_table.is_sorting); + assert!(!managarr_table.is_searching); + assert!(!managarr_table.search_produced_empty_results); + assert!(!managarr_table.is_filtering); + assert_eq!(managarr_table.search_box_content_length, 0); + assert_eq!(managarr_table.search_box_offset, 0); + assert_eq!(managarr_table.filter_box_content_length, 0); + assert_eq!(managarr_table.filter_box_offset, 0); } #[test] diff --git a/src/ui/widgets/mod.rs b/src/ui/widgets/mod.rs index 8b279a5..1e1c38a 100644 --- a/src/ui/widgets/mod.rs +++ b/src/ui/widgets/mod.rs @@ -7,3 +7,4 @@ pub(super) mod managarr_table; pub(super) mod message; pub(super) mod popup; pub(super) mod selectable_list; +mod input_box_popup; diff --git a/src/ui/widgets/popup.rs b/src/ui/widgets/popup.rs index 81c00f1..02b5083 100644 --- a/src/ui/widgets/popup.rs +++ b/src/ui/widgets/popup.rs @@ -22,6 +22,7 @@ pub enum Size { Small, Medium, Large, + XXLarge, Long, } @@ -40,6 +41,7 @@ impl Size { Size::Small => (40, 40), Size::Medium => (60, 60), Size::Large => (75, 75), + Size::XXLarge => (90, 90), Size::Long => (65, 75), } } @@ -118,6 +120,6 @@ impl<'a, T: Widget> Popup<'a, T> { impl<'a, T: Widget> Widget for Popup<'a, T> { fn render(self, area: Rect, buf: &mut Buffer) { - self.render_popup(area, buf); + self.render_popup(area, buf); } } diff --git a/src/ui/widgets/popup_tests.rs b/src/ui/widgets/popup_tests.rs index 2098ed0..a7bce95 100644 --- a/src/ui/widgets/popup_tests.rs +++ b/src/ui/widgets/popup_tests.rs @@ -18,6 +18,7 @@ mod tests { assert_eq!(Size::Small.to_percent(), (40, 40)); assert_eq!(Size::Medium.to_percent(), (60, 60)); assert_eq!(Size::Large.to_percent(), (75, 75)); + assert_eq!(Size::XXLarge.to_percent(), (90, 90)); assert_eq!(Size::Long.to_percent(), (65, 75)); }