Added sorting to the main library table
This commit is contained in:
@@ -5,12 +5,13 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
#[path = "radarr_context_clues_tests.rs"]
|
||||
mod radarr_context_clues_tests;
|
||||
|
||||
pub static LIBRARY_CONTEXT_CLUES: [ContextClue; 9] = [
|
||||
pub static LIBRARY_CONTEXT_CLUES: [ContextClue; 10] = [
|
||||
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
||||
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
|
||||
@@ -42,6 +42,11 @@ mod tests {
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_str_eq;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
use std::cmp::Ordering;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::library::LibraryHandler;
|
||||
use crate::handlers::radarr_handlers::library::{movies_sorting_options, LibraryHandler};
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::models::radarr_models::Movie;
|
||||
use crate::models::radarr_models::{Language, Movie};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, DELETE_MOVIE_BLOCKS, EDIT_MOVIE_BLOCKS, LIBRARY_BLOCKS,
|
||||
MOVIE_DETAILS_BLOCKS,
|
||||
};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use crate::test_handler_delegation;
|
||||
|
||||
mod test_handle_scroll_up_and_down {
|
||||
use crate::{simple_stateful_iterable_vec, test_iterable_scroll};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -32,6 +35,51 @@ mod tests {
|
||||
title,
|
||||
to_string
|
||||
);
|
||||
|
||||
#[rstest]
|
||||
fn test_movies_sort_scroll(
|
||||
#[values(DEFAULT_KEYBINDINGS.up.key, DEFAULT_KEYBINDINGS.down.key)] key: Key,
|
||||
) {
|
||||
let movie_field_vec = sort_options();
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.movies.sorting(sort_options());
|
||||
|
||||
if key == Key::Up {
|
||||
for i in (0..movie_field_vec.len()).rev() {
|
||||
LibraryHandler::with(&key, &mut app, &ActiveRadarrBlock::MoviesSortPrompt, &None)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.sort
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_selection(),
|
||||
&movie_field_vec[i]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for i in 0..movie_field_vec.len() {
|
||||
LibraryHandler::with(&key, &mut app, &ActiveRadarrBlock::MoviesSortPrompt, &None)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.sort
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_selection(),
|
||||
&movie_field_vec[(i + 1) % movie_field_vec.len()]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_home_end {
|
||||
@@ -147,6 +195,53 @@ mod tests {
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sort_home_end() {
|
||||
let movie_field_vec = sort_options();
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.movies.sorting(sort_options());
|
||||
|
||||
LibraryHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.end.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::MoviesSortPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.sort
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_selection(),
|
||||
&movie_field_vec[movie_field_vec.len() - 1]
|
||||
);
|
||||
|
||||
LibraryHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.home.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::MoviesSortPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.sort
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_selection(),
|
||||
&movie_field_vec[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_delete {
|
||||
@@ -579,6 +674,30 @@ mod tests {
|
||||
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sort_prompt_submit() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.movies.sort_asc = true;
|
||||
app.data.radarr_data.movies.sorting(sort_options());
|
||||
app.data.radarr_data.movies.set_items(movies_vec());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::MoviesSortPrompt.into());
|
||||
|
||||
let mut expected_vec = movies_vec();
|
||||
expected_vec.reverse();
|
||||
|
||||
LibraryHandler::with(
|
||||
&SUBMIT_KEY,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::MoviesSortPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||
assert_eq!(app.data.radarr_data.movies.items, expected_vec);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_esc {
|
||||
@@ -902,6 +1021,29 @@ mod tests {
|
||||
"h"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_key() {
|
||||
let mut app = App::default();
|
||||
|
||||
LibraryHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.sort.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::Movies,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::MoviesSortPrompt.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.movies.sort.as_ref().unwrap().items,
|
||||
movies_sorting_options()
|
||||
);
|
||||
assert!(!app.data.radarr_data.movies.sort_asc);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -975,6 +1117,247 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sorting_options_title() {
|
||||
let expected_cmp_fn: fn(&Movie, &Movie) -> Ordering = |a, b| {
|
||||
a.title
|
||||
.text
|
||||
.to_lowercase()
|
||||
.cmp(&b.title.text.to_lowercase())
|
||||
};
|
||||
let mut expected_movies_vec = movies_vec();
|
||||
expected_movies_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = movies_sorting_options()[0].clone();
|
||||
let mut sorted_movies_vec = movies_vec();
|
||||
sorted_movies_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_movies_vec, expected_movies_vec);
|
||||
assert_str_eq!(sort_option.name, "Title");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sorting_options_year() {
|
||||
let expected_cmp_fn: fn(&Movie, &Movie) -> Ordering = |a, b| a.year.cmp(&b.year);
|
||||
let mut expected_movies_vec = movies_vec();
|
||||
expected_movies_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = movies_sorting_options()[1].clone();
|
||||
let mut sorted_movies_vec = movies_vec();
|
||||
sorted_movies_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_movies_vec, expected_movies_vec);
|
||||
assert_str_eq!(sort_option.name, "Year");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sorting_options_studio() {
|
||||
let expected_cmp_fn: fn(&Movie, &Movie) -> Ordering =
|
||||
|a, b| a.studio.to_lowercase().cmp(&b.studio.to_lowercase());
|
||||
let mut expected_movies_vec = movies_vec();
|
||||
expected_movies_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = movies_sorting_options()[2].clone();
|
||||
let mut sorted_movies_vec = movies_vec();
|
||||
sorted_movies_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_movies_vec, expected_movies_vec);
|
||||
assert_str_eq!(sort_option.name, "Studio");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sorting_options_runtime() {
|
||||
let expected_cmp_fn: fn(&Movie, &Movie) -> Ordering = |a, b| a.runtime.cmp(&b.runtime);
|
||||
let mut expected_movies_vec = movies_vec();
|
||||
expected_movies_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = movies_sorting_options()[3].clone();
|
||||
let mut sorted_movies_vec = movies_vec();
|
||||
sorted_movies_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_movies_vec, expected_movies_vec);
|
||||
assert_str_eq!(sort_option.name, "Runtime");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sorting_options_rating() {
|
||||
let expected_cmp_fn: fn(&Movie, &Movie) -> Ordering = |a, b| {
|
||||
a.certification
|
||||
.as_ref()
|
||||
.unwrap_or(&String::new())
|
||||
.to_lowercase()
|
||||
.cmp(
|
||||
&b.certification
|
||||
.as_ref()
|
||||
.unwrap_or(&String::new())
|
||||
.to_lowercase(),
|
||||
)
|
||||
};
|
||||
let mut expected_movies_vec = movies_vec();
|
||||
expected_movies_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = movies_sorting_options()[4].clone();
|
||||
let mut sorted_movies_vec = movies_vec();
|
||||
sorted_movies_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_movies_vec, expected_movies_vec);
|
||||
assert_str_eq!(sort_option.name, "Rating");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sorting_options_language() {
|
||||
let expected_cmp_fn: fn(&Movie, &Movie) -> Ordering = |a, b| {
|
||||
a.original_language
|
||||
.name
|
||||
.to_lowercase()
|
||||
.cmp(&b.original_language.name.to_lowercase())
|
||||
};
|
||||
let mut expected_movies_vec = movies_vec();
|
||||
expected_movies_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = movies_sorting_options()[5].clone();
|
||||
let mut sorted_movies_vec = movies_vec();
|
||||
sorted_movies_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_movies_vec, expected_movies_vec);
|
||||
assert_str_eq!(sort_option.name, "Language");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sorting_options_size() {
|
||||
let expected_cmp_fn: fn(&Movie, &Movie) -> Ordering =
|
||||
|a, b| a.size_on_disk.cmp(&b.size_on_disk);
|
||||
let mut expected_movies_vec = movies_vec();
|
||||
expected_movies_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = movies_sorting_options()[6].clone();
|
||||
let mut sorted_movies_vec = movies_vec();
|
||||
sorted_movies_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_movies_vec, expected_movies_vec);
|
||||
assert_str_eq!(sort_option.name, "Size");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sorting_options_quality() {
|
||||
let expected_cmp_fn: fn(&Movie, &Movie) -> Ordering =
|
||||
|a, b| a.quality_profile_id.cmp(&b.quality_profile_id);
|
||||
let mut expected_movies_vec = movies_vec();
|
||||
expected_movies_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = movies_sorting_options()[7].clone();
|
||||
let mut sorted_movies_vec = movies_vec();
|
||||
sorted_movies_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_movies_vec, expected_movies_vec);
|
||||
assert_str_eq!(sort_option.name, "Quality");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sorting_options_monitored() {
|
||||
let expected_cmp_fn: fn(&Movie, &Movie) -> Ordering = |a, b| a.monitored.cmp(&b.monitored);
|
||||
let mut expected_movies_vec = movies_vec();
|
||||
expected_movies_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = movies_sorting_options()[8].clone();
|
||||
let mut sorted_movies_vec = movies_vec();
|
||||
sorted_movies_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_movies_vec, expected_movies_vec);
|
||||
assert_str_eq!(sort_option.name, "Monitored");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movies_sorting_options_tags() {
|
||||
let expected_cmp_fn: fn(&Movie, &Movie) -> Ordering = |a, b| {
|
||||
let a_str = a
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag| tag.as_i64().unwrap().to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
let b_str = b
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag| tag.as_i64().unwrap().to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
a_str.cmp(&b_str)
|
||||
};
|
||||
let mut expected_movies_vec = movies_vec();
|
||||
expected_movies_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
let sort_option = movies_sorting_options()[9].clone();
|
||||
let mut sorted_movies_vec = movies_vec();
|
||||
sorted_movies_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||
|
||||
assert_eq!(sorted_movies_vec, expected_movies_vec);
|
||||
assert_str_eq!(sort_option.name, "Tags");
|
||||
}
|
||||
|
||||
fn movies_vec() -> Vec<Movie> {
|
||||
vec![
|
||||
Movie {
|
||||
title: "test 1".into(),
|
||||
original_language: Language {
|
||||
name: "English".to_owned(),
|
||||
},
|
||||
size_on_disk: 1024,
|
||||
studio: "Studio 1".to_owned(),
|
||||
year: 2024,
|
||||
monitored: false,
|
||||
runtime: 12.into(),
|
||||
quality_profile_id: 1,
|
||||
certification: Some("PG-13".to_owned()),
|
||||
tags: vec![1.into(), 2.into()],
|
||||
..Movie::default()
|
||||
},
|
||||
Movie {
|
||||
title: "test 2".into(),
|
||||
original_language: Language {
|
||||
name: "Chinese".to_owned(),
|
||||
},
|
||||
size_on_disk: 2048,
|
||||
studio: "Studio 2".to_owned(),
|
||||
year: 1998,
|
||||
monitored: false,
|
||||
runtime: 60.into(),
|
||||
quality_profile_id: 2,
|
||||
certification: Some("R".to_owned()),
|
||||
tags: vec![1.into(), 3.into()],
|
||||
..Movie::default()
|
||||
},
|
||||
Movie {
|
||||
title: "test 3".into(),
|
||||
original_language: Language {
|
||||
name: "Japanese".to_owned(),
|
||||
},
|
||||
size_on_disk: 512,
|
||||
studio: "studio 3".to_owned(),
|
||||
year: 1954,
|
||||
monitored: true,
|
||||
runtime: 120.into(),
|
||||
quality_profile_id: 3,
|
||||
certification: Some("G".to_owned()),
|
||||
tags: vec![2.into(), 3.into()],
|
||||
..Movie::default()
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn sort_options() -> Vec<SortOption<Movie>> {
|
||||
vec![SortOption {
|
||||
name: "Test 1",
|
||||
cmp_fn: Some(|a, b| {
|
||||
a.title
|
||||
.text
|
||||
.to_lowercase()
|
||||
.cmp(&b.title.text.to_lowercase())
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_library_handler_accepts() {
|
||||
let mut library_handler_blocks = Vec::new();
|
||||
|
||||
@@ -8,9 +8,11 @@ use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHand
|
||||
use crate::handlers::radarr_handlers::library::movie_details_handler::MovieDetailsHandler;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||
|
||||
use crate::models::radarr_models::Movie;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, DELETE_MOVIE_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, LIBRARY_BLOCKS,
|
||||
};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
@@ -79,14 +81,34 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
||||
}
|
||||
|
||||
fn handle_scroll_up(&mut self) {
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::Movies {
|
||||
self.app.data.radarr_data.movies.scroll_up()
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Movies => self.app.data.radarr_data.movies.scroll_up(),
|
||||
ActiveRadarrBlock::MoviesSortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_up(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_scroll_down(&mut self) {
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::Movies {
|
||||
self.app.data.radarr_data.movies.scroll_down()
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Movies => self.app.data.radarr_data.movies.scroll_down(),
|
||||
ActiveRadarrBlock::MoviesSortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_down(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +137,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
||||
.unwrap()
|
||||
.scroll_home();
|
||||
}
|
||||
ActiveRadarrBlock::MoviesSortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_to_top(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -140,6 +171,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.reset_offset(),
|
||||
ActiveRadarrBlock::MoviesSortPrompt => self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.sort
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.scroll_to_bottom(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -226,6 +266,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveRadarrBlock::MoviesSortPrompt => {
|
||||
self.app.data.radarr_data.movies.apply_sorting();
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -300,6 +345,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
||||
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ if *key == DEFAULT_KEYBINDINGS.sort.key => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.sorting(movies_sorting_options());
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::MoviesSortPrompt.into());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::SearchMovie => {
|
||||
@@ -320,3 +376,84 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn movies_sorting_options() -> Vec<SortOption<Movie>> {
|
||||
vec![
|
||||
SortOption {
|
||||
name: "Title",
|
||||
cmp_fn: Some(|a, b| {
|
||||
a.title
|
||||
.text
|
||||
.to_lowercase()
|
||||
.cmp(&b.title.text.to_lowercase())
|
||||
}),
|
||||
},
|
||||
SortOption {
|
||||
name: "Year",
|
||||
cmp_fn: Some(|a, b| a.year.cmp(&b.year)),
|
||||
},
|
||||
SortOption {
|
||||
name: "Studio",
|
||||
cmp_fn: Some(|a, b| a.studio.to_lowercase().cmp(&b.studio.to_lowercase())),
|
||||
},
|
||||
SortOption {
|
||||
name: "Runtime",
|
||||
cmp_fn: Some(|a, b| a.runtime.cmp(&b.runtime)),
|
||||
},
|
||||
SortOption {
|
||||
name: "Rating",
|
||||
cmp_fn: Some(|a, b| {
|
||||
a.certification
|
||||
.as_ref()
|
||||
.unwrap_or(&String::new())
|
||||
.to_lowercase()
|
||||
.cmp(
|
||||
&b.certification
|
||||
.as_ref()
|
||||
.unwrap_or(&String::new())
|
||||
.to_lowercase(),
|
||||
)
|
||||
}),
|
||||
},
|
||||
SortOption {
|
||||
name: "Language",
|
||||
cmp_fn: Some(|a, b| {
|
||||
a.original_language
|
||||
.name
|
||||
.to_lowercase()
|
||||
.cmp(&b.original_language.name.to_lowercase())
|
||||
}),
|
||||
},
|
||||
SortOption {
|
||||
name: "Size",
|
||||
cmp_fn: Some(|a, b| a.size_on_disk.cmp(&b.size_on_disk)),
|
||||
},
|
||||
SortOption {
|
||||
name: "Quality",
|
||||
cmp_fn: Some(|a, b| a.quality_profile_id.cmp(&b.quality_profile_id)),
|
||||
},
|
||||
SortOption {
|
||||
name: "Monitored",
|
||||
cmp_fn: Some(|a, b| a.monitored.cmp(&b.monitored)),
|
||||
},
|
||||
SortOption {
|
||||
name: "Tags",
|
||||
cmp_fn: Some(|a, b| {
|
||||
let a_str = a
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag| tag.as_i64().unwrap().to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
let b_str = b
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag| tag.as_i64().unwrap().to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
a_str.cmp(&b_str)
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -469,11 +469,16 @@ fn releases_sorting_options() -> Vec<SortOption<Release>> {
|
||||
},
|
||||
SortOption {
|
||||
name: "Title",
|
||||
cmp_fn: Some(|a, b| a.title.text.cmp(&b.title.text)),
|
||||
cmp_fn: Some(|a, b| {
|
||||
a.title
|
||||
.text
|
||||
.to_lowercase()
|
||||
.cmp(&b.title.text.to_lowercase())
|
||||
}),
|
||||
},
|
||||
SortOption {
|
||||
name: "Indexer",
|
||||
cmp_fn: Some(|a, b| a.indexer.cmp(&b.indexer)),
|
||||
cmp_fn: Some(|a, b| a.indexer.to_lowercase().cmp(&b.indexer.to_lowercase())),
|
||||
},
|
||||
SortOption {
|
||||
name: "Size",
|
||||
|
||||
@@ -1108,8 +1108,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_releases_sorting_options_title() {
|
||||
let expected_cmp_fn: fn(&Release, &Release) -> Ordering =
|
||||
|a, b| a.title.text.cmp(&b.title.text);
|
||||
let expected_cmp_fn: fn(&Release, &Release) -> Ordering = |a, b| {
|
||||
a.title
|
||||
.text
|
||||
.to_lowercase()
|
||||
.cmp(&b.title.text.to_lowercase())
|
||||
};
|
||||
let mut expected_releases_vec = release_vec();
|
||||
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
@@ -1123,7 +1127,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_releases_sorting_options_indexer() {
|
||||
let expected_cmp_fn: fn(&Release, &Release) -> Ordering = |a, b| a.indexer.cmp(&b.indexer);
|
||||
let expected_cmp_fn: fn(&Release, &Release) -> Ordering =
|
||||
|a, b| a.indexer.to_lowercase().cmp(&b.indexer.to_lowercase());
|
||||
let mut expected_releases_vec = release_vec();
|
||||
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||
|
||||
@@ -1237,8 +1242,8 @@ mod tests {
|
||||
let release_b = Release {
|
||||
protocol: "Protocol B".to_owned(),
|
||||
age: 2,
|
||||
title: HorizontallyScrollableText::from("Title B"),
|
||||
indexer: "Indexer B".to_owned(),
|
||||
title: HorizontallyScrollableText::from("title B"),
|
||||
indexer: "indexer B".to_owned(),
|
||||
size: 2,
|
||||
rejected: false,
|
||||
seeders: Some(Number::from(2)),
|
||||
|
||||
@@ -270,6 +270,7 @@ pub enum ActiveRadarrBlock {
|
||||
MovieHistory,
|
||||
#[default]
|
||||
Movies,
|
||||
MoviesSortPrompt,
|
||||
RootFolders,
|
||||
System,
|
||||
SystemLogs,
|
||||
@@ -289,8 +290,9 @@ pub enum ActiveRadarrBlock {
|
||||
ViewMovieOverview,
|
||||
}
|
||||
|
||||
pub static LIBRARY_BLOCKS: [ActiveRadarrBlock; 6] = [
|
||||
pub static LIBRARY_BLOCKS: [ActiveRadarrBlock; 7] = [
|
||||
ActiveRadarrBlock::Movies,
|
||||
ActiveRadarrBlock::MoviesSortPrompt,
|
||||
ActiveRadarrBlock::SearchMovie,
|
||||
ActiveRadarrBlock::SearchMovieError,
|
||||
ActiveRadarrBlock::FilterMovies,
|
||||
|
||||
@@ -256,8 +256,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_library_blocks_contents() {
|
||||
assert_eq!(LIBRARY_BLOCKS.len(), 6);
|
||||
assert_eq!(LIBRARY_BLOCKS.len(), 7);
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveRadarrBlock::Movies));
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveRadarrBlock::MoviesSortPrompt));
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveRadarrBlock::SearchMovie));
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveRadarrBlock::SearchMovieError));
|
||||
assert!(LIBRARY_BLOCKS.contains(&ActiveRadarrBlock::FilterMovies));
|
||||
|
||||
@@ -218,8 +218,14 @@ where
|
||||
}
|
||||
|
||||
pub fn apply_sorting(&mut self) {
|
||||
self.apply_sorting_toggle(true);
|
||||
}
|
||||
|
||||
pub fn apply_sorting_toggle(&mut self, toggle_dir: bool) {
|
||||
if let Some(sort_options) = &mut self.sort {
|
||||
self.sort_asc = !self.sort_asc;
|
||||
if toggle_dir {
|
||||
self.sort_asc = !self.sort_asc;
|
||||
}
|
||||
let selected_sort_option = sort_options.current_selection();
|
||||
let mut items = self.filtered_items.as_ref().unwrap_or(&self.items).clone();
|
||||
if let Some(cmp_fn) = selected_sort_option.cmp_fn {
|
||||
|
||||
@@ -268,18 +268,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stateful_table_apply_sorting_no_op_no_sort_options() {
|
||||
fn test_stateful_table_apply_sorting_toggle_no_op_no_sort_options() {
|
||||
let mut stateful_table = create_test_stateful_table();
|
||||
let expected_items = stateful_table.items.clone();
|
||||
|
||||
stateful_table.apply_sorting();
|
||||
stateful_table.apply_sorting_toggle(true);
|
||||
|
||||
assert_eq!(stateful_table.items, expected_items);
|
||||
assert!(!stateful_table.sort_asc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stateful_table_apply_sorting_no_op_no_cmp_fn() {
|
||||
fn test_stateful_table_apply_sorting_toggle_no_op_no_cmp_fn() {
|
||||
let mut stateful_table = create_test_stateful_table();
|
||||
stateful_table.sorting(vec![SortOption {
|
||||
name: "Test 1",
|
||||
@@ -287,14 +287,14 @@ mod tests {
|
||||
}]);
|
||||
let expected_items = stateful_table.items.clone();
|
||||
|
||||
stateful_table.apply_sorting();
|
||||
stateful_table.apply_sorting_toggle(true);
|
||||
|
||||
assert_eq!(stateful_table.items, expected_items);
|
||||
assert!(stateful_table.sort_asc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filtered_stateful_table_apply_sorting_no_op_no_cmp_fn() {
|
||||
fn test_filtered_stateful_table_apply_sorting_toggle_no_op_no_cmp_fn() {
|
||||
let mut filtered_stateful_table = create_test_filtered_stateful_table();
|
||||
filtered_stateful_table.sorting(vec![SortOption {
|
||||
name: "Test 1",
|
||||
@@ -306,7 +306,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
filtered_stateful_table.apply_sorting();
|
||||
filtered_stateful_table.apply_sorting_toggle(true);
|
||||
|
||||
assert_eq!(
|
||||
*filtered_stateful_table.filtered_items.as_ref().unwrap(),
|
||||
@@ -316,7 +316,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stateful_table_apply_sorting() {
|
||||
fn test_stateful_table_apply_sorting_toggles_direction() {
|
||||
let mut stateful_table = create_test_stateful_table();
|
||||
stateful_table.sorting(vec![SortOption {
|
||||
name: "Test 1",
|
||||
@@ -325,12 +325,12 @@ mod tests {
|
||||
let mut expected_items = stateful_table.items.clone();
|
||||
expected_items.sort();
|
||||
|
||||
stateful_table.apply_sorting();
|
||||
stateful_table.apply_sorting_toggle(true);
|
||||
|
||||
assert_eq!(stateful_table.items, expected_items);
|
||||
assert!(stateful_table.sort_asc);
|
||||
|
||||
stateful_table.apply_sorting();
|
||||
stateful_table.apply_sorting_toggle(true);
|
||||
|
||||
expected_items.reverse();
|
||||
assert_eq!(stateful_table.items, expected_items);
|
||||
@@ -338,7 +338,46 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filtered_stateful_table_apply_sorting() {
|
||||
fn test_stateful_table_apply_sorting_toggle() {
|
||||
let mut stateful_table = create_test_stateful_table();
|
||||
stateful_table.sorting(vec![SortOption {
|
||||
name: "Test 1",
|
||||
cmp_fn: Some(|a, b| a.cmp(b)),
|
||||
}]);
|
||||
let mut expected_items = stateful_table.items.clone();
|
||||
expected_items.sort();
|
||||
|
||||
stateful_table.apply_sorting_toggle(true);
|
||||
|
||||
assert_eq!(stateful_table.items, expected_items);
|
||||
assert!(stateful_table.sort_asc);
|
||||
|
||||
stateful_table.apply_sorting_toggle(true);
|
||||
|
||||
expected_items.reverse();
|
||||
assert_eq!(stateful_table.items, expected_items);
|
||||
assert!(!stateful_table.sort_asc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stateful_table_apply_sorting_toggle_false_doesnt_toggle_direction() {
|
||||
let mut stateful_table = create_test_stateful_table();
|
||||
stateful_table.sorting(vec![SortOption {
|
||||
name: "Test 1",
|
||||
cmp_fn: Some(|a, b| a.cmp(b)),
|
||||
}]);
|
||||
let mut expected_items = stateful_table.items.clone();
|
||||
expected_items.sort();
|
||||
expected_items.reverse();
|
||||
|
||||
stateful_table.apply_sorting_toggle(false);
|
||||
|
||||
assert_eq!(stateful_table.items, expected_items);
|
||||
assert!(!stateful_table.sort_asc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filtered_stateful_table_apply_sorting_toggle() {
|
||||
let mut filtered_stateful_table = create_test_filtered_stateful_table();
|
||||
filtered_stateful_table.sorting(vec![SortOption {
|
||||
name: "Test 1",
|
||||
@@ -351,7 +390,7 @@ mod tests {
|
||||
.clone();
|
||||
expected_items.sort();
|
||||
|
||||
filtered_stateful_table.apply_sorting();
|
||||
filtered_stateful_table.apply_sorting_toggle(true);
|
||||
|
||||
assert_eq!(
|
||||
*filtered_stateful_table.filtered_items.as_ref().unwrap(),
|
||||
@@ -359,7 +398,7 @@ mod tests {
|
||||
);
|
||||
assert!(filtered_stateful_table.sort_asc);
|
||||
|
||||
filtered_stateful_table.apply_sorting();
|
||||
filtered_stateful_table.apply_sorting_toggle(true);
|
||||
|
||||
expected_items.reverse();
|
||||
assert_eq!(
|
||||
|
||||
@@ -1191,7 +1191,8 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
|
||||
self
|
||||
.handle_request::<(), Vec<Movie>>(request_props, |movie_vec, mut app| {
|
||||
app.data.radarr_data.movies.set_items(movie_vec)
|
||||
app.data.radarr_data.movies.set_items(movie_vec);
|
||||
app.data.radarr_data.movies.apply_sorting_toggle(false);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -301,6 +301,7 @@ mod test {
|
||||
RadarrEvent::GetMovies.resource(),
|
||||
)
|
||||
.await;
|
||||
app_arc.lock().await.data.radarr_data.movies.sort_asc = true;
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network.handle_radarr_event(RadarrEvent::GetMovies).await;
|
||||
@@ -310,6 +311,7 @@ mod test {
|
||||
app_arc.lock().await.data.radarr_data.movies.items,
|
||||
vec![movie()]
|
||||
);
|
||||
assert!(app_arc.lock().await.data.radarr_data.movies.sort_asc);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -46,7 +46,7 @@ impl DrawUi for LibraryUi {
|
||||
let route = *app.get_current_route();
|
||||
let mut library_ui_matchers = |active_radarr_block: ActiveRadarrBlock| match active_radarr_block
|
||||
{
|
||||
ActiveRadarrBlock::Movies => draw_library(f, app, area),
|
||||
ActiveRadarrBlock::Movies | ActiveRadarrBlock::MoviesSortPrompt => draw_library(f, app, area),
|
||||
ActiveRadarrBlock::SearchMovie => draw_popup_over(
|
||||
f,
|
||||
app,
|
||||
@@ -101,94 +101,97 @@ impl DrawUi for LibraryUi {
|
||||
}
|
||||
|
||||
pub(super) fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let current_selection = if !app.data.radarr_data.movies.items.is_empty() {
|
||||
app.data.radarr_data.movies.current_selection().clone()
|
||||
} else {
|
||||
Movie::default()
|
||||
};
|
||||
let quality_profile_map = &app.data.radarr_data.quality_profile_map;
|
||||
let tags_map = &app.data.radarr_data.tags_map;
|
||||
let downloads_vec = &app.data.radarr_data.downloads.items;
|
||||
let content = Some(&mut app.data.radarr_data.movies);
|
||||
let help_footer = app
|
||||
.data
|
||||
.radarr_data
|
||||
.main_tabs
|
||||
.get_active_tab_contextual_help();
|
||||
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||
let current_selection = if !app.data.radarr_data.movies.items.is_empty() {
|
||||
app.data.radarr_data.movies.current_selection().clone()
|
||||
} else {
|
||||
Movie::default()
|
||||
};
|
||||
let quality_profile_map = &app.data.radarr_data.quality_profile_map;
|
||||
let tags_map = &app.data.radarr_data.tags_map;
|
||||
let downloads_vec = &app.data.radarr_data.downloads.items;
|
||||
let content = Some(&mut app.data.radarr_data.movies);
|
||||
let help_footer = app
|
||||
.data
|
||||
.radarr_data
|
||||
.main_tabs
|
||||
.get_active_tab_contextual_help();
|
||||
|
||||
let library_table_row_mapping = |movie: &Movie| {
|
||||
movie.title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 27),
|
||||
*movie == current_selection,
|
||||
app.tick_count % app.ticks_until_scroll == 0,
|
||||
);
|
||||
let monitored = if movie.monitored { "🏷" } else { "" };
|
||||
let (hours, minutes) = convert_runtime(movie.runtime);
|
||||
let file_size: f64 = convert_to_gb(movie.size_on_disk);
|
||||
let certification = movie.certification.clone().unwrap_or_default();
|
||||
let quality_profile = quality_profile_map
|
||||
.get_by_left(&movie.quality_profile_id)
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
let tags = movie
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag_id| {
|
||||
tags_map
|
||||
.get_by_left(&tag_id.as_i64().unwrap())
|
||||
.unwrap()
|
||||
.clone()
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let library_table_row_mapping = |movie: &Movie| {
|
||||
movie.title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 27),
|
||||
*movie == current_selection,
|
||||
app.tick_count % app.ticks_until_scroll == 0,
|
||||
);
|
||||
let monitored = if movie.monitored { "🏷" } else { "" };
|
||||
let (hours, minutes) = convert_runtime(movie.runtime);
|
||||
let file_size: f64 = convert_to_gb(movie.size_on_disk);
|
||||
let certification = movie.certification.clone().unwrap_or_default();
|
||||
let quality_profile = quality_profile_map
|
||||
.get_by_left(&movie.quality_profile_id)
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
let tags = movie
|
||||
.tags
|
||||
.iter()
|
||||
.map(|tag_id| {
|
||||
tags_map
|
||||
.get_by_left(&tag_id.as_i64().unwrap())
|
||||
.unwrap()
|
||||
.clone()
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
decorate_with_row_style(
|
||||
downloads_vec,
|
||||
movie,
|
||||
Row::new(vec![
|
||||
Cell::from(movie.title.to_string()),
|
||||
Cell::from(movie.year.to_string()),
|
||||
Cell::from(movie.studio.to_string()),
|
||||
Cell::from(format!("{hours}h {minutes}m")),
|
||||
Cell::from(certification),
|
||||
Cell::from(movie.original_language.name.to_owned()),
|
||||
Cell::from(format!("{file_size:.2} GB")),
|
||||
Cell::from(quality_profile),
|
||||
Cell::from(monitored.to_owned()),
|
||||
Cell::from(tags),
|
||||
]),
|
||||
)
|
||||
};
|
||||
let library_table = ManagarrTable::new(content, library_table_row_mapping)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.footer(help_footer)
|
||||
.headers([
|
||||
"Title",
|
||||
"Year",
|
||||
"Studio",
|
||||
"Runtime",
|
||||
"Rating",
|
||||
"Language",
|
||||
"Size",
|
||||
"Quality Profile",
|
||||
"Monitored",
|
||||
"Tags",
|
||||
])
|
||||
.constraints([
|
||||
Constraint::Percentage(27),
|
||||
Constraint::Percentage(4),
|
||||
Constraint::Percentage(17),
|
||||
Constraint::Percentage(6),
|
||||
Constraint::Percentage(6),
|
||||
Constraint::Percentage(6),
|
||||
Constraint::Percentage(6),
|
||||
Constraint::Percentage(10),
|
||||
Constraint::Percentage(6),
|
||||
Constraint::Percentage(12),
|
||||
]);
|
||||
decorate_with_row_style(
|
||||
downloads_vec,
|
||||
movie,
|
||||
Row::new(vec![
|
||||
Cell::from(movie.title.to_string()),
|
||||
Cell::from(movie.year.to_string()),
|
||||
Cell::from(movie.studio.to_string()),
|
||||
Cell::from(format!("{hours}h {minutes}m")),
|
||||
Cell::from(certification),
|
||||
Cell::from(movie.original_language.name.to_owned()),
|
||||
Cell::from(format!("{file_size:.2} GB")),
|
||||
Cell::from(quality_profile),
|
||||
Cell::from(monitored.to_owned()),
|
||||
Cell::from(tags),
|
||||
]),
|
||||
)
|
||||
};
|
||||
let library_table = ManagarrTable::new(content, library_table_row_mapping)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.footer(help_footer)
|
||||
.sorting(active_radarr_block == ActiveRadarrBlock::MoviesSortPrompt)
|
||||
.headers([
|
||||
"Title",
|
||||
"Year",
|
||||
"Studio",
|
||||
"Runtime",
|
||||
"Rating",
|
||||
"Language",
|
||||
"Size",
|
||||
"Quality Profile",
|
||||
"Monitored",
|
||||
"Tags",
|
||||
])
|
||||
.constraints([
|
||||
Constraint::Percentage(27),
|
||||
Constraint::Percentage(4),
|
||||
Constraint::Percentage(17),
|
||||
Constraint::Percentage(6),
|
||||
Constraint::Percentage(6),
|
||||
Constraint::Percentage(6),
|
||||
Constraint::Percentage(6),
|
||||
Constraint::Percentage(10),
|
||||
Constraint::Percentage(6),
|
||||
Constraint::Percentage(12),
|
||||
]);
|
||||
|
||||
f.render_widget(library_table, area);
|
||||
f.render_widget(library_table, area);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_update_all_movies_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
|
||||
Reference in New Issue
Block a user