feat(ui): Sonarr support for the series details popup
This commit is contained in:
@@ -196,7 +196,13 @@ impl<'a> App<'a> {
|
||||
.current_selection()
|
||||
.clone()
|
||||
.seasons
|
||||
.unwrap_or_default();
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|mut season| {
|
||||
season.title = Some(format!("Season {}", season.season_number));
|
||||
season
|
||||
})
|
||||
.collect();
|
||||
self.data.sonarr_data.seasons.set_items(seasons);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,24 @@ pub static HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
||||
];
|
||||
|
||||
pub static SERIES_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
|
||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
|
||||
];
|
||||
|
||||
pub static SEASON_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
|
||||
@@ -9,7 +9,7 @@ mod tests {
|
||||
HISTORY_CONTEXT_CLUES, MANUAL_EPISODE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||
MANUAL_EPISODE_SEARCH_CONTEXT_CLUES, MANUAL_SEASON_SEARCH_CONTEXT_CLUES,
|
||||
SEASON_DETAILS_CONTEXT_CLUES, SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES,
|
||||
SYSTEM_TASKS_CONTEXT_CLUES,
|
||||
SERIES_HISTORY_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -86,6 +86,57 @@ mod tests {
|
||||
assert_eq!(series_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_series_history_context_clues() {
|
||||
let mut series_history_context_clues_iter = SERIES_HISTORY_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
|
||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
||||
|
||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "details");
|
||||
|
||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
||||
|
||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
|
||||
|
||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
|
||||
|
||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
||||
|
||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc);
|
||||
|
||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
||||
assert_str_eq!(*description, "cancel filter/close");
|
||||
assert_eq!(series_history_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_context_clues() {
|
||||
let mut history_context_clues_iter = HISTORY_CONTEXT_CLUES.iter();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod sonarr_tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
@@ -572,6 +572,13 @@ mod tests {
|
||||
app.populate_seasons_table().await;
|
||||
|
||||
assert!(!app.data.sonarr_data.seasons.items.is_empty());
|
||||
assert_str_eq!(
|
||||
app.data.sonarr_data.seasons.items[0]
|
||||
.title
|
||||
.as_ref()
|
||||
.unwrap(),
|
||||
"Season 0"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -585,6 +592,13 @@ mod tests {
|
||||
app.populate_seasons_table().await;
|
||||
|
||||
assert!(!app.data.sonarr_data.seasons.items.is_empty());
|
||||
assert_str_eq!(
|
||||
app.data.sonarr_data.seasons.items[0]
|
||||
.title
|
||||
.as_ref()
|
||||
.unwrap(),
|
||||
"Season 0"
|
||||
);
|
||||
}
|
||||
|
||||
fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver<NetworkEvent>) {
|
||||
|
||||
@@ -323,6 +323,11 @@ mod test_utils {
|
||||
macro_rules! test_handler_delegation {
|
||||
($handler:ident, $base:expr, $active_block:expr) => {
|
||||
let mut app = App::default();
|
||||
let mut series_history = $crate::models::stateful_table::StatefulTable::default();
|
||||
series_history.set_items(vec![
|
||||
$crate::models::sonarr_models::SonarrHistoryItem::default(),
|
||||
]);
|
||||
app.data.sonarr_data.series_history = Some(series_history);
|
||||
app.push_navigation_stack($base.into());
|
||||
app.push_navigation_stack($active_block.into());
|
||||
|
||||
|
||||
@@ -457,7 +457,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
self
|
||||
.app
|
||||
.pop_and_push_navigation_stack((self.active_radarr_block).into());
|
||||
.pop_and_push_navigation_stack(self.active_radarr_block.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.sort.key => {
|
||||
self
|
||||
|
||||
@@ -1785,6 +1785,7 @@ mod tests {
|
||||
let mut app = App::default();
|
||||
app.is_loading = true;
|
||||
app.push_navigation_stack(active_radarr_block.into());
|
||||
app.is_routing = false;
|
||||
app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal {
|
||||
movie_details: ScrollableText::with_string("test".to_owned()),
|
||||
..MovieDetailsModal::default()
|
||||
@@ -1799,7 +1800,7 @@ mod tests {
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), active_radarr_block.into());
|
||||
assert!(app.is_routing);
|
||||
assert!(!app.is_routing);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
|
||||
@@ -312,7 +312,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, '
|
||||
}
|
||||
}
|
||||
|
||||
fn history_sorting_options() -> Vec<SortOption<SonarrHistoryItem>> {
|
||||
pub(in crate::handlers::sonarr_handlers) fn history_sorting_options() -> Vec<SortOption<SonarrHistoryItem>> {
|
||||
vec![
|
||||
SortOption {
|
||||
name: "Source Title",
|
||||
|
||||
@@ -11,9 +11,7 @@ mod tests {
|
||||
use crate::event::Key;
|
||||
use crate::handlers::sonarr_handlers::library::{series_sorting_options, LibraryHandler};
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
ActiveSonarrBlock, ADD_SERIES_BLOCKS, DELETE_SERIES_BLOCKS, EDIT_SERIES_BLOCKS, LIBRARY_BLOCKS,
|
||||
};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ADD_SERIES_BLOCKS, DELETE_SERIES_BLOCKS, EDIT_SERIES_BLOCKS, LIBRARY_BLOCKS, SERIES_DETAILS_BLOCKS};
|
||||
use crate::models::sonarr_models::{Series, SeriesStatus, SeriesType};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
@@ -615,6 +613,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_search_series_submit() {
|
||||
let mut app = App::default();
|
||||
app.should_ignore_quit_key = true;
|
||||
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
|
||||
app.push_navigation_stack(ActiveSonarrBlock::SearchSeries.into());
|
||||
app
|
||||
@@ -629,6 +628,7 @@ mod tests {
|
||||
|
||||
LibraryHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::SearchSeries, None).handle();
|
||||
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert_str_eq!(
|
||||
app.data.sonarr_data.series.current_selection().title.text,
|
||||
"Test 2"
|
||||
@@ -639,6 +639,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_search_series_submit_error_on_no_search_hits() {
|
||||
let mut app = App::default();
|
||||
app.should_ignore_quit_key = true;
|
||||
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
|
||||
app.push_navigation_stack(ActiveSonarrBlock::SearchSeries.into());
|
||||
app
|
||||
@@ -653,6 +654,7 @@ mod tests {
|
||||
|
||||
LibraryHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::SearchSeries, None).handle();
|
||||
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert_str_eq!(
|
||||
app.data.sonarr_data.series.current_selection().title.text,
|
||||
"Test 1"
|
||||
@@ -666,6 +668,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_search_filtered_series_submit() {
|
||||
let mut app = App::default();
|
||||
app.should_ignore_quit_key = true;
|
||||
app
|
||||
.data
|
||||
.sonarr_data
|
||||
@@ -685,6 +688,7 @@ mod tests {
|
||||
|
||||
LibraryHandler::with(SUBMIT_KEY, &mut app, ActiveSonarrBlock::SearchSeries, None).handle();
|
||||
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert_str_eq!(
|
||||
app.data.sonarr_data.series.current_selection().title.text,
|
||||
"Test 2"
|
||||
@@ -695,6 +699,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_filter_series_submit() {
|
||||
let mut app = App::default();
|
||||
app.should_ignore_quit_key = true;
|
||||
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
|
||||
app.push_navigation_stack(ActiveSonarrBlock::FilterSeries.into());
|
||||
app
|
||||
@@ -732,6 +737,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_filter_series_submit_error_on_no_filter_matches() {
|
||||
let mut app = App::default();
|
||||
app.should_ignore_quit_key = true;
|
||||
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
|
||||
app.push_navigation_stack(ActiveSonarrBlock::FilterSeries.into());
|
||||
app
|
||||
@@ -946,7 +952,6 @@ mod tests {
|
||||
}
|
||||
|
||||
mod test_handle_key_char {
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use serde_json::Number;
|
||||
use strum::IntoEnumIterator;
|
||||
@@ -1465,27 +1470,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// #[rstest]
|
||||
// fn test_delegates_series_details_blocks_to_series_details_handler(
|
||||
// #[values(
|
||||
// ActiveSonarrBlock::SeriesDetails,
|
||||
// ActiveSonarrBlock::SeriesHistory,
|
||||
// ActiveSonarrBlock::FileInfo,
|
||||
// ActiveSonarrBlock::Cast,
|
||||
// ActiveSonarrBlock::Crew,
|
||||
// ActiveSonarrBlock::AutomaticallySearchSeriesPrompt,
|
||||
// ActiveSonarrBlock::UpdateAndScanPrompt,
|
||||
// ActiveSonarrBlock::ManualSearch,
|
||||
// ActiveSonarrBlock::ManualSearchConfirmPrompt
|
||||
// )]
|
||||
// active_sonarr_block: ActiveSonarrBlock,
|
||||
// ) {
|
||||
// test_handler_delegation!(
|
||||
// LibraryHandler,
|
||||
// ActiveSonarrBlock::Series,
|
||||
// active_sonarr_block
|
||||
// );
|
||||
// }
|
||||
#[rstest]
|
||||
fn test_delegates_series_details_blocks_to_series_details_handler(
|
||||
#[values(
|
||||
ActiveSonarrBlock::SeriesDetails,
|
||||
ActiveSonarrBlock::SeriesHistory,
|
||||
ActiveSonarrBlock::SearchSeason,
|
||||
ActiveSonarrBlock::SearchSeasonError,
|
||||
ActiveSonarrBlock::UpdateAndScanSeriesPrompt,
|
||||
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt,
|
||||
ActiveSonarrBlock::SearchSeriesHistory,
|
||||
ActiveSonarrBlock::SearchSeriesHistoryError,
|
||||
ActiveSonarrBlock::FilterSeriesHistory,
|
||||
ActiveSonarrBlock::FilterSeriesHistoryError,
|
||||
ActiveSonarrBlock::SeriesHistorySortPrompt,
|
||||
ActiveSonarrBlock::SeriesHistoryDetails
|
||||
)]
|
||||
active_sonarr_block: ActiveSonarrBlock,
|
||||
) {
|
||||
test_handler_delegation!(
|
||||
LibraryHandler,
|
||||
ActiveSonarrBlock::Series,
|
||||
active_sonarr_block
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_delegates_edit_series_blocks_to_edit_series_handler(
|
||||
@@ -1711,6 +1719,7 @@ mod tests {
|
||||
library_handler_blocks.extend(ADD_SERIES_BLOCKS);
|
||||
library_handler_blocks.extend(DELETE_SERIES_BLOCKS);
|
||||
library_handler_blocks.extend(EDIT_SERIES_BLOCKS);
|
||||
library_handler_blocks.extend(SERIES_DETAILS_BLOCKS);
|
||||
|
||||
ActiveSonarrBlock::iter().for_each(|active_sonarr_block| {
|
||||
if library_handler_blocks.contains(&active_sonarr_block) {
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::{
|
||||
|
||||
use super::handle_change_tab_left_right_keys;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::handlers::sonarr_handlers::library::series_details_handler::SeriesDetailsHandler;
|
||||
|
||||
mod add_series_handler;
|
||||
mod delete_series_handler;
|
||||
@@ -29,6 +30,7 @@ mod delete_series_handler;
|
||||
#[cfg(test)]
|
||||
#[path = "library_handler_tests.rs"]
|
||||
mod library_handler_tests;
|
||||
mod series_details_handler;
|
||||
|
||||
pub(super) struct LibraryHandler<'a, 'b> {
|
||||
key: Key,
|
||||
@@ -51,6 +53,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
|
||||
EditSeriesHandler::with(self.key, self.app, self.active_sonarr_block, self.context)
|
||||
.handle();
|
||||
}
|
||||
_ if SeriesDetailsHandler::accepts(self.active_sonarr_block) => {
|
||||
SeriesDetailsHandler::with(self.key, self.app, self.active_sonarr_block, self.context)
|
||||
.handle();
|
||||
}
|
||||
_ => self.handle_key_event(),
|
||||
}
|
||||
}
|
||||
@@ -59,6 +65,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
|
||||
AddSeriesHandler::accepts(active_block)
|
||||
|| DeleteSeriesHandler::accepts(active_block)
|
||||
|| EditSeriesHandler::accepts(active_block)
|
||||
|| SeriesDetailsHandler::accepts(active_block)
|
||||
|| LIBRARY_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,610 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_text_box_keys;
|
||||
use crate::handlers::sonarr_handlers::history::history_sorting_options;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
ActiveSonarrBlock, EDIT_SERIES_SELECTION_BLOCKS, SERIES_DETAILS_BLOCKS,
|
||||
};
|
||||
use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable};
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "series_details_handler_tests.rs"]
|
||||
mod series_details_handler_tests;
|
||||
|
||||
pub(super) struct SeriesDetailsHandler<'a, 'b> {
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_sonarr_block: ActiveSonarrBlock,
|
||||
_context: Option<ActiveSonarrBlock>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler<'a, 'b> {
|
||||
fn accepts(active_block: ActiveSonarrBlock) -> bool {
|
||||
SERIES_DETAILS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn with(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_block: ActiveSonarrBlock,
|
||||
_context: Option<ActiveSonarrBlock>,
|
||||
) -> SeriesDetailsHandler<'a, 'b> {
|
||||
SeriesDetailsHandler {
|
||||
key,
|
||||
app,
|
||||
active_sonarr_block: active_block,
|
||||
_context,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key(&self) -> Key {
|
||||
self.key
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
if self.active_sonarr_block == ActiveSonarrBlock::SeriesHistory {
|
||||
!self.app.is_loading && self.app.data.sonarr_data.series_history.is_some()
|
||||
} else {
|
||||
!self.app.is_loading
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_scroll_up(&mut self) {
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SeriesDetails => self.app.data.sonarr_data.seasons.scroll_up(),
|
||||
ActiveSonarrBlock::SeriesHistory => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_up(),
|
||||
ActiveSonarrBlock::SeriesHistorySortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_up(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_scroll_down(&mut self) {
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SeriesDetails => self.app.data.sonarr_data.seasons.scroll_down(),
|
||||
ActiveSonarrBlock::SeriesHistory => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_down(),
|
||||
ActiveSonarrBlock::SeriesHistorySortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_down(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_home(&mut self) {
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SeriesDetails => self.app.data.sonarr_data.seasons.scroll_to_top(),
|
||||
ActiveSonarrBlock::SearchSeason => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.seasons
|
||||
.search
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_home(),
|
||||
ActiveSonarrBlock::SearchSeriesHistory => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.search
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_home(),
|
||||
ActiveSonarrBlock::FilterSeriesHistory => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.filter
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_home(),
|
||||
ActiveSonarrBlock::SeriesHistory => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_to_top(),
|
||||
ActiveSonarrBlock::SeriesHistorySortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_to_top(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_end(&mut self) {
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SeriesDetails => self.app.data.sonarr_data.seasons.scroll_to_bottom(),
|
||||
ActiveSonarrBlock::SearchSeason => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.seasons
|
||||
.search
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.reset_offset(),
|
||||
ActiveSonarrBlock::SearchSeriesHistory => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.search
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.reset_offset(),
|
||||
ActiveSonarrBlock::FilterSeriesHistory => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.filter
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.reset_offset(),
|
||||
ActiveSonarrBlock::SeriesHistory => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_to_bottom(),
|
||||
ActiveSonarrBlock::SeriesHistorySortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_to_bottom(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_delete(&mut self) {}
|
||||
|
||||
fn handle_left_right_action(&mut self) {
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SeriesDetails | ActiveSonarrBlock::SeriesHistory => match self.key {
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.left.key => {
|
||||
self.app.data.sonarr_data.series_info_tabs.previous();
|
||||
self.app.pop_and_push_navigation_stack(
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_info_tabs
|
||||
.get_active_route(),
|
||||
);
|
||||
}
|
||||
_ if self.key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
self.app.data.sonarr_data.series_info_tabs.next();
|
||||
self.app.pop_and_push_navigation_stack(
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_info_tabs
|
||||
.get_active_route(),
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveSonarrBlock::UpdateAndScanSeriesPrompt
|
||||
| ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => {
|
||||
handle_prompt_toggle(self.app, self.key)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_submit(&mut self) {
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SeriesDetails if !self.app.data.sonarr_data.seasons.is_empty() => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
|
||||
}
|
||||
ActiveSonarrBlock::SeriesHistory
|
||||
if !self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_ref()
|
||||
.expect("Series history should be Some")
|
||||
.is_empty() =>
|
||||
{
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::SeriesHistoryDetails.into());
|
||||
}
|
||||
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => {
|
||||
if self.app.data.sonarr_data.prompt_confirm {
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
Some(SonarrEvent::TriggerAutomaticSeriesSearch(None));
|
||||
}
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveSonarrBlock::UpdateAndScanSeriesPrompt => {
|
||||
if self.app.data.sonarr_data.prompt_confirm {
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
Some(SonarrEvent::UpdateAndScanSeries(None));
|
||||
}
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveSonarrBlock::SearchSeason => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
|
||||
if self.app.data.sonarr_data.seasons.search.is_some() {
|
||||
let has_match = self.app.data.sonarr_data.seasons.apply_search(|season| {
|
||||
season
|
||||
.title
|
||||
.as_ref()
|
||||
.expect("Season was not populated with title in handlers")
|
||||
});
|
||||
|
||||
if !has_match {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::SearchSeasonError.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
ActiveSonarrBlock::SearchSeriesHistory => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
|
||||
if self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.search
|
||||
.is_some()
|
||||
{
|
||||
let has_match = self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.apply_search(|history_item| &history_item.source_title.text);
|
||||
|
||||
if !has_match {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::SearchSeriesHistoryError.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
ActiveSonarrBlock::FilterSeriesHistory => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
|
||||
if self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.filter
|
||||
.is_some()
|
||||
{
|
||||
let has_matches = self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.apply_filter(|history_item| &history_item.source_title.text);
|
||||
|
||||
if !has_matches {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::FilterSeriesHistoryError.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
ActiveSonarrBlock::SeriesHistorySortPrompt => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.items
|
||||
.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.apply_sorting();
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_esc(&mut self) {
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SearchSeason | ActiveSonarrBlock::SearchSeasonError => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.data.sonarr_data.seasons.reset_search();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
}
|
||||
ActiveSonarrBlock::SearchSeriesHistory | ActiveSonarrBlock::SearchSeriesHistoryError => {
|
||||
self.app.pop_navigation_stack();
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.reset_search();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
}
|
||||
ActiveSonarrBlock::FilterSeriesHistory | ActiveSonarrBlock::FilterSeriesHistoryError => {
|
||||
self.app.pop_navigation_stack();
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.reset_filter();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
}
|
||||
ActiveSonarrBlock::UpdateAndScanSeriesPrompt
|
||||
| ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.data.sonarr_data.prompt_confirm = false;
|
||||
}
|
||||
ActiveSonarrBlock::SeriesHistoryDetails | ActiveSonarrBlock::SeriesHistorySortPrompt => {
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveSonarrBlock::SeriesHistory => {
|
||||
if self.app.data.sonarr_data.series_history.as_ref().expect("Series history is not populated").filtered_items.is_some() {
|
||||
self.app.data.sonarr_data.series_history.as_mut().expect("Series history is not populated").reset_filter();
|
||||
} else {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.data.sonarr_data.reset_series_info_tabs();
|
||||
}
|
||||
}
|
||||
ActiveSonarrBlock::SeriesDetails => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.data.sonarr_data.reset_series_info_tabs();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
let key = self.key;
|
||||
match self.active_sonarr_block {
|
||||
ActiveSonarrBlock::SeriesDetails => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => self
|
||||
.app
|
||||
.pop_and_push_navigation_stack(self.active_sonarr_block.into()),
|
||||
_ if key == DEFAULT_KEYBINDINGS.search.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::SearchSeason.into());
|
||||
self.app.data.sonarr_data.seasons.search = Some(HorizontallyScrollableText::default());
|
||||
self.app.should_ignore_quit_key = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
|
||||
self.app.push_navigation_stack(
|
||||
(
|
||||
ActiveSonarrBlock::EditSeriesPrompt,
|
||||
Some(self.active_sonarr_block),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
self.app.data.sonarr_data.edit_series_modal = Some((&self.app.data.sonarr_data).into());
|
||||
self.app.data.sonarr_data.selected_block =
|
||||
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveSonarrBlock::SeriesHistory => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => self
|
||||
.app
|
||||
.pop_and_push_navigation_stack(self.active_sonarr_block.into()),
|
||||
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.search.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::SearchSeriesHistory.into());
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.expect("Series history should be populated")
|
||||
.search = Some(HorizontallyScrollableText::default());
|
||||
self.app.should_ignore_quit_key = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.filter.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::FilterSeriesHistory.into());
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.expect("Series history should be populated")
|
||||
.reset_filter();
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.expect("Series history should be populated")
|
||||
.filter = Some(HorizontallyScrollableText::default());
|
||||
self.app.should_ignore_quit_key = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.sort.key => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_history
|
||||
.as_mut()
|
||||
.expect("Series history should be populated")
|
||||
.sorting(history_sorting_options());
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::SeriesHistorySortPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
|
||||
self.app.push_navigation_stack(
|
||||
(
|
||||
ActiveSonarrBlock::EditSeriesPrompt,
|
||||
Some(self.active_sonarr_block),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
self.app.data.sonarr_data.edit_series_modal = Some((&self.app.data.sonarr_data).into());
|
||||
self.app.data.sonarr_data.selected_block =
|
||||
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveSonarrBlock::SearchSeason => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
key,
|
||||
self.app.data.sonarr_data.seasons.search.as_mut().unwrap()
|
||||
)
|
||||
}
|
||||
ActiveSonarrBlock::SearchSeriesHistory => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
key,
|
||||
self.app.data.sonarr_data.series_history.as_mut().expect("Series history should be populated").search.as_mut().unwrap()
|
||||
)
|
||||
}
|
||||
ActiveSonarrBlock::FilterSeriesHistory => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
key,
|
||||
self.app.data.sonarr_data.series_history.as_mut().expect("Series history should be populated").filter.as_mut().unwrap()
|
||||
)
|
||||
}
|
||||
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.sonarr_data.prompt_confirm = true;
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
Some(SonarrEvent::TriggerAutomaticSeriesSearch(None));
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
ActiveSonarrBlock::UpdateAndScanSeriesPrompt => {
|
||||
if self.app.data.sonarr_data.prompt_confirm {
|
||||
self.app.data.sonarr_data.prompt_confirm_action =
|
||||
Some(SonarrEvent::UpdateAndScanSeries(None));
|
||||
}
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,15 +7,15 @@ mod utils {
|
||||
($handler:ident, $block:expr, $context:expr) => {
|
||||
let mut app = App::default();
|
||||
let mut sonarr_data = SonarrData {
|
||||
quality_profile_map: BiMap::from_iter([
|
||||
quality_profile_map: bimap::BiMap::from_iter([
|
||||
(2222, "HD - 1080p".to_owned()),
|
||||
(1111, "Any".to_owned()),
|
||||
]),
|
||||
language_profiles_map: BiMap::from_iter([
|
||||
language_profiles_map: bimap::BiMap::from_iter([
|
||||
(2222, "English".to_owned()),
|
||||
(1111, "Any".to_owned()),
|
||||
]),
|
||||
tags_map: BiMap::from_iter([(1, "test".to_owned())]),
|
||||
tags_map: bimap::BiMap::from_iter([(1, "test".to_owned())]),
|
||||
..create_test_sonarr_data()
|
||||
};
|
||||
sonarr_data.series.set_items(vec![Series {
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::{
|
||||
},
|
||||
sonarr::sonarr_context_clues::{
|
||||
HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES,
|
||||
SERIES_HISTORY_CONTEXT_CLUES,
|
||||
},
|
||||
},
|
||||
models::{
|
||||
@@ -80,6 +81,12 @@ impl<'a> SonarrData<'a> {
|
||||
self.delete_series_files = false;
|
||||
self.add_list_exclusion = false;
|
||||
}
|
||||
|
||||
pub fn reset_series_info_tabs(&mut self) {
|
||||
self.series_history = None;
|
||||
self.seasons = StatefulTable::default();
|
||||
self.series_info_tabs.index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for SonarrData<'a> {
|
||||
@@ -174,7 +181,7 @@ impl<'a> Default for SonarrData<'a> {
|
||||
title: "History",
|
||||
route: ActiveSonarrBlock::SeriesHistory.into(),
|
||||
help: String::new(),
|
||||
contextual_help: Some(build_context_clue_string(&HISTORY_CONTEXT_CLUES)),
|
||||
contextual_help: Some(build_context_clue_string(&SERIES_HISTORY_CONTEXT_CLUES)),
|
||||
},
|
||||
]),
|
||||
}
|
||||
@@ -304,12 +311,13 @@ pub static LIBRARY_BLOCKS: [ActiveSonarrBlock; 7] = [
|
||||
ActiveSonarrBlock::UpdateAllSeriesPrompt,
|
||||
];
|
||||
|
||||
pub static SERIES_DETAILS_BLOCKS: [ActiveSonarrBlock; 11] = [
|
||||
pub static SERIES_DETAILS_BLOCKS: [ActiveSonarrBlock; 12] = [
|
||||
ActiveSonarrBlock::SeriesDetails,
|
||||
ActiveSonarrBlock::SeriesHistory,
|
||||
ActiveSonarrBlock::SearchSeason,
|
||||
ActiveSonarrBlock::SearchSeasonError,
|
||||
ActiveSonarrBlock::UpdateAndScanSeriesPrompt,
|
||||
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt,
|
||||
ActiveSonarrBlock::SearchSeriesHistory,
|
||||
ActiveSonarrBlock::SearchSeriesHistoryError,
|
||||
ActiveSonarrBlock::FilterSeriesHistory,
|
||||
|
||||
@@ -4,6 +4,9 @@ mod tests {
|
||||
use chrono::{DateTime, Utc};
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
|
||||
use crate::app::sonarr::sonarr_context_clues::SERIES_HISTORY_CONTEXT_CLUES;
|
||||
use crate::models::sonarr_models::{Season, SonarrHistoryItem};
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::{
|
||||
app::{
|
||||
context_clues::{
|
||||
@@ -56,6 +59,24 @@ mod tests {
|
||||
assert!(!sonarr_data.add_list_exclusion);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reset_series_info_tabs() {
|
||||
let mut series_history = StatefulTable::default();
|
||||
series_history.set_items(vec![SonarrHistoryItem::default()]);
|
||||
let mut sonarr_data = SonarrData {
|
||||
series_history: Some(series_history),
|
||||
..SonarrData::default()
|
||||
};
|
||||
sonarr_data.seasons.set_items(vec![Season::default()]);
|
||||
sonarr_data.series_info_tabs.index = 1;
|
||||
|
||||
sonarr_data.reset_series_info_tabs();
|
||||
|
||||
assert!(sonarr_data.series_history.is_none());
|
||||
assert!(sonarr_data.seasons.is_empty());
|
||||
assert_eq!(sonarr_data.series_info_tabs.index, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sonarr_data_defaults() {
|
||||
let sonarr_data = SonarrData::default();
|
||||
@@ -195,7 +216,7 @@ mod tests {
|
||||
assert!(sonarr_data.series_info_tabs.tabs[1].help.is_empty());
|
||||
assert_eq!(
|
||||
sonarr_data.series_info_tabs.tabs[1].contextual_help,
|
||||
Some(build_context_clue_string(&HISTORY_CONTEXT_CLUES))
|
||||
Some(build_context_clue_string(&SERIES_HISTORY_CONTEXT_CLUES))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -570,12 +591,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_series_details_blocks_contents() {
|
||||
assert_eq!(SERIES_DETAILS_BLOCKS.len(), 11);
|
||||
assert_eq!(SERIES_DETAILS_BLOCKS.len(), 12);
|
||||
assert!(SERIES_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SeriesDetails));
|
||||
assert!(SERIES_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SeriesHistory));
|
||||
assert!(SERIES_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeason));
|
||||
assert!(SERIES_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeasonError));
|
||||
assert!(SERIES_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::UpdateAndScanSeriesPrompt));
|
||||
assert!(SERIES_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::AutomaticallySearchSeriesPrompt));
|
||||
assert!(SERIES_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeriesHistory));
|
||||
assert!(SERIES_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::SearchSeriesHistoryError));
|
||||
assert!(SERIES_DETAILS_BLOCKS.contains(&ActiveSonarrBlock::FilterSeriesHistory));
|
||||
|
||||
@@ -32,6 +32,8 @@ pub mod utils {
|
||||
|
||||
let mut seasons = StatefulTable::default();
|
||||
seasons.set_items(vec![Season::default()]);
|
||||
let mut series_history = StatefulTable::default();
|
||||
series_history.set_items(vec![SonarrHistoryItem::default()]);
|
||||
|
||||
let mut sonarr_data = SonarrData {
|
||||
delete_series_files: true,
|
||||
@@ -39,6 +41,7 @@ pub mod utils {
|
||||
add_series_search: Some("test search".into()),
|
||||
edit_root_folder: Some("test path".into()),
|
||||
seasons,
|
||||
series_history: Some(series_history),
|
||||
season_details_modal: Some(season_details_modal),
|
||||
add_searched_series: Some(StatefulTable::default()),
|
||||
..SonarrData::default()
|
||||
|
||||
@@ -239,6 +239,7 @@ impl Eq for Rating {}
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Season {
|
||||
pub title: Option<String>,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub season_number: i64,
|
||||
pub monitored: bool,
|
||||
|
||||
@@ -3,7 +3,7 @@ mod test {
|
||||
use std::sync::Arc;
|
||||
|
||||
use bimap::BiMap;
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::DateTime;
|
||||
use indoc::formatdoc;
|
||||
use mockito::{Matcher, Server};
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
@@ -5237,8 +5237,7 @@ mod test {
|
||||
)
|
||||
.await;
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
let date_time = DateTime::from(DateTime::parse_from_rfc3339("2023-02-25T20:16:43Z").unwrap())
|
||||
as DateTime<Utc>;
|
||||
let date_time = DateTime::from(DateTime::parse_from_rfc3339("2023-02-25T20:16:43Z").unwrap());
|
||||
|
||||
if let SonarrSerdeable::SystemStatus(status) = network
|
||||
.handle_sonarr_event(SonarrEvent::GetStatus)
|
||||
@@ -6874,6 +6873,7 @@ mod test {
|
||||
|
||||
fn season() -> Season {
|
||||
Season {
|
||||
title: None,
|
||||
season_number: 1,
|
||||
monitored: true,
|
||||
statistics: season_statistics(),
|
||||
|
||||
@@ -60,7 +60,6 @@ impl DrawUi for MovieDetailsUi {
|
||||
.prompt(&prompt)
|
||||
.yes_no_value(app.data.radarr_data.prompt_confirm);
|
||||
|
||||
draw_movie_info(f, app, content_area);
|
||||
f.render_widget(
|
||||
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
|
||||
f.area(),
|
||||
|
||||
@@ -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")),
|
||||
]);
|
||||
|
||||
@@ -14,6 +14,7 @@ where
|
||||
#[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;
|
||||
@@ -44,6 +45,10 @@ where
|
||||
self.fg(COLOR_ORANGE)
|
||||
}
|
||||
|
||||
fn indeterminate(self) -> T {
|
||||
self.fg(COLOR_ORANGE)
|
||||
}
|
||||
|
||||
fn default(self) -> T {
|
||||
self.white()
|
||||
}
|
||||
|
||||
@@ -18,6 +18,14 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_indeterminate() {
|
||||
assert_eq!(
|
||||
Style::new().indeterminate(),
|
||||
Style::new().fg(COLOR_ORANGE)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_default() {
|
||||
assert_eq!(Style::new().default(), Style::new().white());
|
||||
|
||||
@@ -1,50 +1,26 @@
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::{layout_block, style_block_highlight};
|
||||
use derive_setters::Setters;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
||||
use ratatui::prelude::{Style, Text, Widget};
|
||||
use ratatui::style::Styled;
|
||||
use ratatui::widgets::Paragraph;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "button_tests.rs"]
|
||||
mod button_tests;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Setters)]
|
||||
pub struct Button<'a> {
|
||||
title: &'a str,
|
||||
#[setters(strip_option)]
|
||||
label: Option<&'a str>,
|
||||
#[setters(strip_option)]
|
||||
icon: Option<&'a str>,
|
||||
#[setters(into)]
|
||||
style: Style,
|
||||
#[setters(rename = "selected")]
|
||||
is_selected: bool,
|
||||
}
|
||||
|
||||
impl<'a> Button<'a> {
|
||||
pub fn title(mut self, title: &'a str) -> Button<'a> {
|
||||
self.title = title;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label(mut self, label: &'a str) -> Button<'a> {
|
||||
self.label = Some(label);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn icon(mut self, icon: &'a str) -> Button<'a> {
|
||||
self.icon = Some(icon);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> Button<'a> {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn selected(mut self, is_selected: bool) -> Button<'a> {
|
||||
self.is_selected = is_selected;
|
||||
self
|
||||
}
|
||||
|
||||
fn render_button_with_icon(self, area: Rect, buf: &mut Buffer) {
|
||||
let [title_area, icon_area] = Layout::horizontal([
|
||||
Constraint::Length(self.title.len() as u16),
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ui::widgets::button::Button;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use ratatui::style::{Style, Stylize};
|
||||
|
||||
#[test]
|
||||
fn test_title() {
|
||||
let button = Button::default().title("Title");
|
||||
|
||||
assert_str_eq!(button.title, "Title");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_label() {
|
||||
let button = Button::default().label("Label");
|
||||
|
||||
assert_eq!(button.label, Some("Label"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_icon() {
|
||||
let button = Button::default().icon("Icon");
|
||||
|
||||
assert_eq!(button.icon, Some("Icon"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style() {
|
||||
let button = Button::default().style(Style::new().bold());
|
||||
|
||||
assert_eq!(button.style, Style::new().bold());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_selected() {
|
||||
let button = Button::default().selected(true);
|
||||
|
||||
assert!(button.is_selected);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use derive_setters::Setters;
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::{borderless_block, layout_block, style_block_highlight};
|
||||
use ratatui::buffer::Buffer;
|
||||
@@ -6,14 +7,13 @@ use ratatui::prelude::Text;
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::widgets::{Paragraph, Widget};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "checkbox_tests.rs"]
|
||||
mod checkbox_tests;
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
#[derive(PartialEq, Debug, Copy, Clone, Setters)]
|
||||
pub struct Checkbox<'a> {
|
||||
#[setters(skip)]
|
||||
label: &'a str,
|
||||
#[setters(rename = "checked")]
|
||||
is_checked: bool,
|
||||
#[setters(rename = "highlighted")]
|
||||
is_highlighted: bool,
|
||||
}
|
||||
|
||||
@@ -26,16 +26,6 @@ impl<'a> Checkbox<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn checked(mut self, is_checked: bool) -> Checkbox<'a> {
|
||||
self.is_checked = is_checked;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn highlighted(mut self, is_selected: bool) -> Checkbox<'a> {
|
||||
self.is_highlighted = is_selected;
|
||||
self
|
||||
}
|
||||
|
||||
fn render_checkbox(self, area: Rect, buf: &mut Buffer) {
|
||||
let check = if self.is_checked { "✔" } else { "" };
|
||||
let [label_area, checkbox_area] =
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ui::widgets::checkbox::Checkbox;
|
||||
use pretty_assertions::assert_str_eq;
|
||||
|
||||
#[test]
|
||||
fn test_checkbox_new() {
|
||||
let checkbox = Checkbox::new("test");
|
||||
|
||||
assert_str_eq!(checkbox.label, "test");
|
||||
assert!(!checkbox.is_checked);
|
||||
assert!(!checkbox.is_highlighted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_checkbox_checked() {
|
||||
let checkbox = Checkbox::new("test").checked(true);
|
||||
|
||||
assert_str_eq!(checkbox.label, "test");
|
||||
assert!(checkbox.is_checked);
|
||||
assert!(!checkbox.is_highlighted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_checkbox_highlighted() {
|
||||
let checkbox = Checkbox::new("test").highlighted(true);
|
||||
|
||||
assert_str_eq!(checkbox.label, "test");
|
||||
assert!(!checkbox.is_checked);
|
||||
assert!(checkbox.is_highlighted);
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,19 @@ use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::{Paragraph, Widget};
|
||||
use std::iter;
|
||||
use derive_setters::Setters;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "confirmation_prompt_tests.rs"]
|
||||
mod confirmation_prompt_tests;
|
||||
|
||||
#[derive(Setters)]
|
||||
pub struct ConfirmationPrompt<'a> {
|
||||
title: &'a str,
|
||||
prompt: &'a str,
|
||||
#[setters(strip_option)]
|
||||
content: Option<Paragraph<'a>>,
|
||||
#[setters(strip_option)]
|
||||
checkboxes: Option<Vec<Checkbox<'a>>>,
|
||||
yes_no_value: bool,
|
||||
yes_no_highlighted: bool,
|
||||
@@ -34,36 +38,6 @@ impl<'a> ConfirmationPrompt<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(mut self, title: &'a str) -> Self {
|
||||
self.title = title;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn prompt(mut self, prompt: &'a str) -> Self {
|
||||
self.prompt = prompt;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn content(mut self, content: Paragraph<'a>) -> Self {
|
||||
self.content = Some(content);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn checkboxes(mut self, checkboxes: Vec<Checkbox<'a>>) -> Self {
|
||||
self.checkboxes = Some(checkboxes);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn yes_no_value(mut self, yes_highlighted: bool) -> Self {
|
||||
self.yes_no_value = yes_highlighted;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn yes_no_highlighted(mut self, yes_highlighted: bool) -> Self {
|
||||
self.yes_no_highlighted = yes_highlighted;
|
||||
self
|
||||
}
|
||||
|
||||
fn render_confirmation_prompt_with_checkboxes(self, area: Rect, buf: &mut Buffer) {
|
||||
title_block_centered(self.title).render(area, buf);
|
||||
let help_text =
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ui::widgets::checkbox::Checkbox;
|
||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use ratatui::widgets::Paragraph;
|
||||
|
||||
#[test]
|
||||
fn test_confirmation_prompt_new() {
|
||||
@@ -16,78 +14,4 @@ mod tests {
|
||||
assert!(!confirmation_prompt.yes_no_value);
|
||||
assert!(confirmation_prompt.yes_no_highlighted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_confirmation_prompt_title() {
|
||||
let confirmation_prompt = ConfirmationPrompt::new().title("title");
|
||||
|
||||
assert_str_eq!(confirmation_prompt.title, "title");
|
||||
assert_str_eq!(confirmation_prompt.prompt, "");
|
||||
assert_eq!(confirmation_prompt.content, None);
|
||||
assert_eq!(confirmation_prompt.checkboxes, None);
|
||||
assert!(!confirmation_prompt.yes_no_value);
|
||||
assert!(confirmation_prompt.yes_no_highlighted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_confirmation_prompt_prompt() {
|
||||
let confirmation_prompt = ConfirmationPrompt::new().prompt("prompt");
|
||||
|
||||
assert_str_eq!(confirmation_prompt.prompt, "prompt");
|
||||
assert_str_eq!(confirmation_prompt.title, "");
|
||||
assert_eq!(confirmation_prompt.content, None);
|
||||
assert_eq!(confirmation_prompt.checkboxes, None);
|
||||
assert!(!confirmation_prompt.yes_no_value);
|
||||
assert!(confirmation_prompt.yes_no_highlighted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_confirmation_prompt_content() {
|
||||
let content = Paragraph::new("content");
|
||||
let confirmation_prompt = ConfirmationPrompt::new().content(content.clone());
|
||||
|
||||
assert_eq!(confirmation_prompt.content, Some(content));
|
||||
assert_str_eq!(confirmation_prompt.title, "");
|
||||
assert_str_eq!(confirmation_prompt.prompt, "");
|
||||
assert_eq!(confirmation_prompt.checkboxes, None);
|
||||
assert!(!confirmation_prompt.yes_no_value);
|
||||
assert!(confirmation_prompt.yes_no_highlighted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_confirmation_prompt_checkboxes() {
|
||||
let checkboxes = vec![Checkbox::new("test").highlighted(true).checked(false)];
|
||||
let confirmation_prompt = ConfirmationPrompt::new().checkboxes(checkboxes.clone());
|
||||
|
||||
assert_eq!(confirmation_prompt.checkboxes, Some(checkboxes));
|
||||
assert_str_eq!(confirmation_prompt.title, "");
|
||||
assert_str_eq!(confirmation_prompt.prompt, "");
|
||||
assert_eq!(confirmation_prompt.content, None);
|
||||
assert!(!confirmation_prompt.yes_no_value);
|
||||
assert!(confirmation_prompt.yes_no_highlighted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_confirmation_prompt_yes_no_value() {
|
||||
let confirmation_prompt = ConfirmationPrompt::new().yes_no_value(true);
|
||||
|
||||
assert!(confirmation_prompt.yes_no_value);
|
||||
assert_str_eq!(confirmation_prompt.title, "");
|
||||
assert_str_eq!(confirmation_prompt.prompt, "");
|
||||
assert_eq!(confirmation_prompt.content, None);
|
||||
assert_eq!(confirmation_prompt.checkboxes, None);
|
||||
assert!(confirmation_prompt.yes_no_highlighted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_confirmation_prompt_yes_no_highlighted() {
|
||||
let confirmation_prompt = ConfirmationPrompt::new().yes_no_highlighted(false);
|
||||
|
||||
assert!(!confirmation_prompt.yes_no_highlighted);
|
||||
assert_str_eq!(confirmation_prompt.title, "");
|
||||
assert_str_eq!(confirmation_prompt.prompt, "");
|
||||
assert_eq!(confirmation_prompt.content, None);
|
||||
assert_eq!(confirmation_prompt.checkboxes, None);
|
||||
assert!(!confirmation_prompt.yes_no_value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use derive_setters::Setters;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Layout, Position, Rect};
|
||||
use ratatui::prelude::Text;
|
||||
@@ -12,16 +13,20 @@ use crate::ui::utils::{borderless_block, layout_block};
|
||||
#[path = "input_box_tests.rs"]
|
||||
mod input_box_tests;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Setters)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub struct InputBox<'a> {
|
||||
content: &'a str,
|
||||
offset: usize,
|
||||
#[setters(into)]
|
||||
style: Style,
|
||||
block: Block<'a>,
|
||||
#[setters(strip_option)]
|
||||
label: Option<&'a str>,
|
||||
cursor_after_string: bool,
|
||||
#[setters(rename = "highlighted", strip_option)]
|
||||
is_highlighted: Option<bool>,
|
||||
#[setters(rename = "selected", strip_option)]
|
||||
is_selected: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -39,41 +44,6 @@ impl<'a> InputBox<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style<S: Into<Style>>(mut self, style: S) -> InputBox<'a> {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn block(mut self, block: Block<'a>) -> InputBox<'a> {
|
||||
self.block = block;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label(mut self, label: &'a str) -> InputBox<'a> {
|
||||
self.label = Some(label);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn offset(mut self, offset: usize) -> InputBox<'a> {
|
||||
self.offset = offset;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cursor_after_string(mut self, cursor_after_string: bool) -> InputBox<'a> {
|
||||
self.cursor_after_string = cursor_after_string;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn highlighted(mut self, is_highlighted: bool) -> InputBox<'a> {
|
||||
self.is_highlighted = Some(is_highlighted);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn selected(mut self, is_selected: bool) -> InputBox<'a> {
|
||||
self.is_selected = Some(is_selected);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_selected(&self) -> bool {
|
||||
self.is_selected.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -20,104 +20,6 @@ mod tests {
|
||||
assert_eq!(input_box.is_selected, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_input_box_style() {
|
||||
let input_box = InputBox::new("test").style(Style::new().highlight());
|
||||
|
||||
assert_eq!(input_box.style, Style::new().highlight());
|
||||
assert_str_eq!(input_box.content, "test");
|
||||
assert_eq!(input_box.offset, 0);
|
||||
assert_eq!(input_box.block, layout_block());
|
||||
assert_eq!(input_box.label, None);
|
||||
assert!(input_box.cursor_after_string);
|
||||
assert_eq!(input_box.is_highlighted, None);
|
||||
assert_eq!(input_box.is_selected, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_input_box_block() {
|
||||
let input_box = InputBox::new("test").block(layout_block().title("title"));
|
||||
|
||||
assert_eq!(input_box.block, layout_block().title("title"));
|
||||
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.label, None);
|
||||
assert!(input_box.cursor_after_string);
|
||||
assert_eq!(input_box.is_highlighted, None);
|
||||
assert_eq!(input_box.is_selected, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_input_box_label() {
|
||||
let input_box = InputBox::new("test").label("label");
|
||||
|
||||
assert_str_eq!(input_box.label.unwrap(), "label");
|
||||
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.block, layout_block());
|
||||
assert!(input_box.cursor_after_string);
|
||||
assert_eq!(input_box.is_highlighted, None);
|
||||
assert_eq!(input_box.is_selected, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_input_box_offset() {
|
||||
let input_box = InputBox::new("test").offset(1);
|
||||
|
||||
assert_eq!(input_box.offset, 1);
|
||||
assert_str_eq!(input_box.content, "test");
|
||||
assert_eq!(input_box.style, Style::new().default());
|
||||
assert_eq!(input_box.block, layout_block());
|
||||
assert_eq!(input_box.label, None);
|
||||
assert!(input_box.cursor_after_string);
|
||||
assert_eq!(input_box.is_highlighted, None);
|
||||
assert_eq!(input_box.is_selected, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_input_box_cursor_after_string() {
|
||||
let input_box = InputBox::new("test").cursor_after_string(false);
|
||||
|
||||
assert!(!input_box.cursor_after_string);
|
||||
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.block, layout_block());
|
||||
assert_eq!(input_box.label, None);
|
||||
assert_eq!(input_box.is_highlighted, None);
|
||||
assert_eq!(input_box.is_selected, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_input_box_highlighted() {
|
||||
let input_box = InputBox::new("test").highlighted(true);
|
||||
|
||||
assert_eq!(input_box.is_highlighted, Some(true));
|
||||
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.block, layout_block());
|
||||
assert_eq!(input_box.label, None);
|
||||
assert!(input_box.cursor_after_string);
|
||||
assert_eq!(input_box.is_selected, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_input_box_selected() {
|
||||
let input_box = InputBox::new("test").selected(true);
|
||||
|
||||
assert_eq!(input_box.is_selected, Some(true));
|
||||
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.block, layout_block());
|
||||
assert_eq!(input_box.label, None);
|
||||
assert!(input_box.cursor_after_string);
|
||||
assert_eq!(input_box.is_highlighted, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_input_box_is_selected() {
|
||||
let input_box = InputBox::new("test").selected(true);
|
||||
|
||||
@@ -12,7 +12,7 @@ use ratatui::widgets::{Block, ListItem, Paragraph, Row, StatefulWidget, Table, W
|
||||
use ratatui::Frame;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use derive_setters::Setters;
|
||||
use super::input_box_popup::InputBoxPopup;
|
||||
use super::message::Message;
|
||||
use super::popup::Size;
|
||||
@@ -21,24 +21,32 @@ use super::popup::Size;
|
||||
#[path = "managarr_table_tests.rs"]
|
||||
mod managarr_table_tests;
|
||||
|
||||
#[derive(Setters)]
|
||||
pub struct ManagarrTable<'a, T, F>
|
||||
where
|
||||
F: Fn(&T) -> Row<'a>,
|
||||
T: Clone + PartialEq + Eq + Debug,
|
||||
{
|
||||
#[setters(strip_option)]
|
||||
content: Option<&'a mut StatefulTable<T>>,
|
||||
#[setters(skip)]
|
||||
table_headers: Vec<String>,
|
||||
#[setters(skip)]
|
||||
constraints: Vec<Constraint>,
|
||||
row_mapper: F,
|
||||
footer: Option<String>,
|
||||
footer_alignment: Alignment,
|
||||
block: Block<'a>,
|
||||
margin: u16,
|
||||
#[setters(rename = "loading")]
|
||||
is_loading: bool,
|
||||
highlight_rows: bool,
|
||||
#[setters(rename = "sorting")]
|
||||
is_sorting: bool,
|
||||
#[setters(rename = "searching")]
|
||||
is_searching: bool,
|
||||
search_produced_empty_results: bool,
|
||||
#[setters(rename = "filtering")]
|
||||
is_filtering: bool,
|
||||
filter_produced_empty_results: bool,
|
||||
search_box_content_length: usize,
|
||||
@@ -107,61 +115,6 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
pub fn footer(mut self, footer: Option<String>) -> Self {
|
||||
self.footer = footer;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn footer_alignment(mut self, alignment: Alignment) -> Self {
|
||||
self.footer_alignment = alignment;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn block(mut self, block: Block<'a>) -> Self {
|
||||
self.block = block;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn margin(mut self, margin: u16) -> Self {
|
||||
self.margin = margin;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn loading(mut self, is_loading: bool) -> Self {
|
||||
self.is_loading = is_loading;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn highlight_rows(mut self, highlight_rows: bool) -> Self {
|
||||
self.highlight_rows = highlight_rows;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn sorting(mut self, is_sorting: bool) -> Self {
|
||||
self.is_sorting = is_sorting;
|
||||
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 {
|
||||
|
||||
@@ -3,7 +3,6 @@ mod tests {
|
||||
use crate::models::stateful_list::StatefulList;
|
||||
use crate::models::stateful_table::{SortOption, StatefulTable};
|
||||
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};
|
||||
@@ -180,358 +179,6 @@ mod tests {
|
||||
assert_eq!(managarr_table.filter_box_offset, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_managarr_table_footer() {
|
||||
let items = vec!["item1", "item2", "item3"];
|
||||
let mut stateful_table = StatefulTable::default();
|
||||
stateful_table.set_items(items.clone());
|
||||
let footer = "footer".to_owned();
|
||||
|
||||
let managarr_table =
|
||||
ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)]))
|
||||
.footer(Some(footer.clone()));
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert_eq!(managarr_table.footer, Some(footer));
|
||||
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::<String>::new());
|
||||
assert_eq!(managarr_table.constraints, Vec::new());
|
||||
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, 0);
|
||||
assert_eq!(managarr_table.filter_box_offset, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_managarr_table_footer_alignment() {
|
||||
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)]))
|
||||
.footer_alignment(Alignment::Center);
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert_eq!(managarr_table.footer_alignment, Alignment::Center);
|
||||
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::<String>::new());
|
||||
assert_eq!(managarr_table.constraints, Vec::new());
|
||||
assert_eq!(managarr_table.footer, None);
|
||||
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, 0);
|
||||
assert_eq!(managarr_table.filter_box_offset, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_managarr_table_block() {
|
||||
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)]))
|
||||
.block(layout_block());
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert_eq!(managarr_table.block, layout_block());
|
||||
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::<String>::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.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, 0);
|
||||
assert_eq!(managarr_table.filter_box_offset, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_managarr_table_margin() {
|
||||
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)])).margin(1);
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert_eq!(managarr_table.margin, 1);
|
||||
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::<String>::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!(!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_loading() {
|
||||
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)]))
|
||||
.loading(true);
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert!(managarr_table.is_loading);
|
||||
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::<String>::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.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_highlight_rows() {
|
||||
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)]))
|
||||
.highlight_rows(false);
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert!(!managarr_table.highlight_rows);
|
||||
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::<String>::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.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_sorting() {
|
||||
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)]))
|
||||
.sorting(true);
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert!(managarr_table.is_sorting);
|
||||
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::<String>::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_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::<String>::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::<String>::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::<String>::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::<String>::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]
|
||||
fn test_managarr_table_parse_headers() {
|
||||
let items = vec!["item1", "item2", "item3"];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
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};
|
||||
@@ -10,6 +11,7 @@ use ratatui::widgets::{Paragraph, Widget, Wrap};
|
||||
#[path = "message_tests.rs"]
|
||||
mod message_tests;
|
||||
|
||||
#[derive(Setters)]
|
||||
pub struct Message<'a> {
|
||||
text: Text<'a>,
|
||||
title: &'a str,
|
||||
@@ -30,21 +32,6 @@ impl<'a> Message<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(mut self, title: &'a str) -> Self {
|
||||
self.title = title;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn style(mut self, style: Style) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alignment(mut self, alignment: Alignment) -> Self {
|
||||
self.alignment = alignment;
|
||||
self
|
||||
}
|
||||
|
||||
fn render_message(self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new(self.text)
|
||||
.style(self.style)
|
||||
|
||||
@@ -18,42 +18,4 @@ mod tests {
|
||||
assert_eq!(message.style, Style::new().failure().bold());
|
||||
assert_eq!(message.alignment, Alignment::Center);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_title() {
|
||||
let test_message = "This is a message";
|
||||
let title = "Success";
|
||||
|
||||
let message = Message::new(test_message).title(title);
|
||||
|
||||
assert_str_eq!(message.title, title);
|
||||
assert_eq!(message.text, Text::from(test_message));
|
||||
assert_eq!(message.style, Style::new().failure().bold());
|
||||
assert_eq!(message.alignment, Alignment::Center);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_style() {
|
||||
let test_message = "This is a message";
|
||||
let style = Style::new().success().bold();
|
||||
|
||||
let message = Message::new(test_message).style(style);
|
||||
|
||||
assert_eq!(message.style, style);
|
||||
assert_eq!(message.text, Text::from(test_message));
|
||||
assert_str_eq!(message.title, "Error");
|
||||
assert_eq!(message.alignment, Alignment::Center);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_alignment() {
|
||||
let test_message = "This is a message";
|
||||
|
||||
let message = Message::new(test_message).alignment(Alignment::Left);
|
||||
|
||||
assert_eq!(message.alignment, Alignment::Left);
|
||||
assert_eq!(message.text, Text::from(test_message));
|
||||
assert_str_eq!(message.title, "Error");
|
||||
assert_eq!(message.style, Style::new().failure().bold());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user