Partially working filtering logic
This commit is contained in:
@@ -15,6 +15,7 @@ generate_keybindings! {
|
||||
right,
|
||||
backspace,
|
||||
search,
|
||||
filter,
|
||||
submit,
|
||||
quit,
|
||||
esc
|
||||
@@ -50,6 +51,10 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
|
||||
key: Key::Char('s'),
|
||||
desc: "Search",
|
||||
},
|
||||
filter: KeyBinding {
|
||||
key: Key::Char('f'),
|
||||
desc: "Filter",
|
||||
},
|
||||
submit: KeyBinding {
|
||||
key: Key::Enter,
|
||||
desc: "Select",
|
||||
|
||||
+18
-6
@@ -30,6 +30,7 @@ pub struct RadarrData {
|
||||
pub main_tabs: TabState,
|
||||
pub movie_info_tabs: TabState,
|
||||
pub search: String,
|
||||
pub filter: String,
|
||||
pub is_searching: bool,
|
||||
}
|
||||
|
||||
@@ -38,6 +39,12 @@ impl RadarrData {
|
||||
self.collection_movies = StatefulTable::default();
|
||||
}
|
||||
|
||||
pub fn reset_search(&mut self) {
|
||||
self.is_searching = false;
|
||||
self.search = String::default();
|
||||
self.filter = String::default();
|
||||
}
|
||||
|
||||
pub fn reset_movie_info_tabs(&mut self) {
|
||||
self.file_details = String::default();
|
||||
self.audio_details = String::default();
|
||||
@@ -73,23 +80,25 @@ impl Default for RadarrData {
|
||||
collections: StatefulTable::default(),
|
||||
collection_movies: StatefulTable::default(),
|
||||
search: String::default(),
|
||||
filter: String::default(),
|
||||
is_searching: false,
|
||||
main_tabs: TabState::new(vec![
|
||||
TabRoute {
|
||||
title: "Library".to_owned(),
|
||||
route: ActiveRadarrBlock::Movies.into(),
|
||||
help: "<↑↓> scroll table | <s> search | <enter> movie details | ←→ change tab "
|
||||
help: "<↑↓> scroll | <s> search | <f> filter | <enter> details | ←→ change tab "
|
||||
.to_owned(),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Downloads".to_owned(),
|
||||
route: ActiveRadarrBlock::Downloads.into(),
|
||||
help: "<↑↓> scroll table | ←→ change tab ".to_owned(),
|
||||
help: "<↑↓> scroll | ←→ change tab ".to_owned(),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Collections".to_owned(),
|
||||
route: ActiveRadarrBlock::Collections.into(),
|
||||
help: "<↑↓> scroll table | <enter> collection details | ←→ change tab ".to_owned(),
|
||||
help: "<↑↓> scroll | <s> search | <f> filter | <enter> details | ←→ change tab "
|
||||
.to_owned(),
|
||||
},
|
||||
]),
|
||||
movie_info_tabs: TabState::new(vec![
|
||||
@@ -101,7 +110,7 @@ impl Default for RadarrData {
|
||||
TabRoute {
|
||||
title: "History".to_owned(),
|
||||
route: ActiveRadarrBlock::MovieHistory.into(),
|
||||
help: "<↑↓> scroll table | ←→ change tab | <esc> close ".to_owned(),
|
||||
help: "<↑↓> scroll | ←→ change tab | <esc> close ".to_owned(),
|
||||
},
|
||||
TabRoute {
|
||||
title: "File".to_owned(),
|
||||
@@ -111,12 +120,12 @@ impl Default for RadarrData {
|
||||
TabRoute {
|
||||
title: "Cast".to_owned(),
|
||||
route: ActiveRadarrBlock::Cast.into(),
|
||||
help: "<↑↓> scroll table | ←→ change tab | <esc> close ".to_owned(),
|
||||
help: "<↑↓> scroll | ←→ change tab | <esc> close ".to_owned(),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Crew".to_owned(),
|
||||
route: ActiveRadarrBlock::Crew.into(),
|
||||
help: "<↑↓> scroll table | ←→ change tab | <esc> close ".to_owned(),
|
||||
help: "<↑↓> scroll | ←→ change tab | <esc> close ".to_owned(),
|
||||
},
|
||||
]),
|
||||
}
|
||||
@@ -132,11 +141,14 @@ pub enum ActiveRadarrBlock {
|
||||
Cast,
|
||||
Crew,
|
||||
FileInfo,
|
||||
FilterCollections,
|
||||
FilterMovies,
|
||||
Movies,
|
||||
MovieDetails,
|
||||
MovieHistory,
|
||||
Downloads,
|
||||
SearchMovie,
|
||||
SearchCollection,
|
||||
SortOptions,
|
||||
ViewMovieOverview,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
use regex::Regex;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::radarr::ActiveRadarrBlock;
|
||||
use crate::handlers::radarr_handlers::collection_details_handler::CollectionDetailsHandler;
|
||||
use crate::handlers::radarr_handlers::movie_details_handler::MovieDetailsHandler;
|
||||
use crate::handlers::{handle_clear_errors, KeyEventHandler};
|
||||
use crate::models::radarr_models::Movie;
|
||||
use crate::models::Scrollable;
|
||||
use crate::utils::strip_non_alphanumeric_characters;
|
||||
use crate::{App, Key};
|
||||
|
||||
mod collection_details_handler;
|
||||
@@ -50,11 +54,39 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> {
|
||||
|
||||
fn handle_scroll_up(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Collections => self.app.data.radarr_data.collections.scroll_up(),
|
||||
ActiveRadarrBlock::Collections => {
|
||||
if !self.app.data.radarr_data.filter.is_empty() {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.collections
|
||||
.scroll_up_with_filter(|&collection| {
|
||||
strip_non_alphanumeric_characters(&collection.title)
|
||||
.starts_with(&self.app.data.radarr_data.filter)
|
||||
});
|
||||
} else {
|
||||
self.app.data.radarr_data.collections.scroll_up()
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::CollectionDetails => {
|
||||
self.app.data.radarr_data.collection_movies.scroll_up()
|
||||
}
|
||||
ActiveRadarrBlock::Movies => self.app.data.radarr_data.movies.scroll_up(),
|
||||
ActiveRadarrBlock::Movies => {
|
||||
if !self.app.data.radarr_data.filter.is_empty() {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.scroll_up_with_filter(|&movie| {
|
||||
strip_non_alphanumeric_characters(&movie.title)
|
||||
.starts_with(&self.app.data.radarr_data.filter)
|
||||
});
|
||||
} else {
|
||||
self.app.data.radarr_data.movies.scroll_up()
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_up(),
|
||||
_ => (),
|
||||
}
|
||||
@@ -62,11 +94,39 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> {
|
||||
|
||||
fn handle_scroll_down(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Collections => self.app.data.radarr_data.collections.scroll_down(),
|
||||
ActiveRadarrBlock::Collections => {
|
||||
if !self.app.data.radarr_data.filter.is_empty() {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.collections
|
||||
.scroll_down_with_filter(|&collection| {
|
||||
strip_non_alphanumeric_characters(&collection.title)
|
||||
.starts_with(&self.app.data.radarr_data.filter)
|
||||
});
|
||||
} else {
|
||||
self.app.data.radarr_data.collections.scroll_down()
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::CollectionDetails => {
|
||||
self.app.data.radarr_data.collection_movies.scroll_down()
|
||||
}
|
||||
ActiveRadarrBlock::Movies => self.app.data.radarr_data.movies.scroll_down(),
|
||||
ActiveRadarrBlock::Movies => {
|
||||
if !self.app.data.radarr_data.filter.is_empty() {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.scroll_down_with_filter(|&movie| {
|
||||
strip_non_alphanumeric_characters(&movie.title)
|
||||
.starts_with(&self.app.data.radarr_data.filter)
|
||||
});
|
||||
} else {
|
||||
self.app.data.radarr_data.movies.scroll_down()
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::Downloads => self.app.data.radarr_data.downloads.scroll_down(),
|
||||
_ => (),
|
||||
}
|
||||
@@ -131,7 +191,9 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> {
|
||||
.movies
|
||||
.items
|
||||
.iter()
|
||||
.position(|movie| movie.title.to_lowercase() == search_string);
|
||||
.position(|movie| {
|
||||
strip_non_alphanumeric_characters(&movie.title).starts_with(&search_string)
|
||||
});
|
||||
|
||||
self.app.data.radarr_data.is_searching = false;
|
||||
self.app.data.radarr_data.movies.select_index(movie_index);
|
||||
@@ -140,41 +202,155 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> {
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::SearchCollection => {
|
||||
let search_string = self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.search
|
||||
.drain(..)
|
||||
.collect::<String>()
|
||||
.to_lowercase();
|
||||
let collection_index =
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.collections
|
||||
.items
|
||||
.iter()
|
||||
.position(|collection| {
|
||||
strip_non_alphanumeric_characters(&collection.title).starts_with(&search_string)
|
||||
});
|
||||
|
||||
self.app.data.radarr_data.is_searching = false;
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.collections
|
||||
.select_index(collection_index);
|
||||
|
||||
if collection_index.is_some() {
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::FilterMovies => {
|
||||
self.app.data.radarr_data.filter =
|
||||
strip_non_alphanumeric_characters(&self.app.data.radarr_data.filter);
|
||||
let filter_string = &self.app.data.radarr_data.filter;
|
||||
let filter_matches = self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.items
|
||||
.iter()
|
||||
.filter(|&movie| {
|
||||
strip_non_alphanumeric_characters(&movie.title).starts_with(filter_string)
|
||||
})
|
||||
.count();
|
||||
|
||||
self.app.data.radarr_data.is_searching = false;
|
||||
|
||||
if filter_matches > 0 {
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::FilterCollections => {
|
||||
self.app.data.radarr_data.filter =
|
||||
strip_non_alphanumeric_characters(&self.app.data.radarr_data.filter);
|
||||
let filter_string = &self.app.data.radarr_data.filter;
|
||||
let filter_matches = self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.collections
|
||||
.items
|
||||
.iter()
|
||||
.filter(|&collection| {
|
||||
strip_non_alphanumeric_characters(&collection.title).starts_with(filter_string)
|
||||
})
|
||||
.count();
|
||||
|
||||
self.app.data.radarr_data.is_searching = false;
|
||||
|
||||
if filter_matches > 0 {
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_esc(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::SearchMovie => {
|
||||
ActiveRadarrBlock::SearchMovie
|
||||
| ActiveRadarrBlock::SearchCollection
|
||||
| ActiveRadarrBlock::FilterMovies
|
||||
| ActiveRadarrBlock::FilterCollections => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.data.radarr_data.is_searching = false;
|
||||
self.app.data.radarr_data.search = String::default();
|
||||
self.app.data.radarr_data.reset_search();
|
||||
}
|
||||
_ => {
|
||||
self.app.data.radarr_data.reset_search();
|
||||
handle_clear_errors(self.app);
|
||||
}
|
||||
_ => handle_clear_errors(self.app),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
let key = self.key;
|
||||
match *self.active_radarr_block {
|
||||
ActiveRadarrBlock::Movies => match key {
|
||||
ActiveRadarrBlock::Movies => match self.key {
|
||||
_ if *key == DEFAULT_KEYBINDINGS.search.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into());
|
||||
self.app.data.radarr_data.is_searching = true;
|
||||
}
|
||||
_ if *key == DEFAULT_KEYBINDINGS.filter.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into());
|
||||
self.app.data.radarr_data.is_searching = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::SearchMovie => match key {
|
||||
ActiveRadarrBlock::Collections => match self.key {
|
||||
_ if *key == DEFAULT_KEYBINDINGS.search.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into());
|
||||
self.app.data.radarr_data.is_searching = true;
|
||||
}
|
||||
_ if *key == DEFAULT_KEYBINDINGS.filter.key => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::FilterCollections.into());
|
||||
self.app.data.radarr_data.is_searching = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => match self.key {
|
||||
_ if *key == DEFAULT_KEYBINDINGS.backspace.key => {
|
||||
self.app.data.radarr_data.search.pop();
|
||||
}
|
||||
Key::Char(character) => self.app.data.radarr_data.search.push(*character),
|
||||
Key::Char(character) => {
|
||||
self.app.data.radarr_data.search.push(*character);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => {}
|
||||
ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => match self.key {
|
||||
_ if *key == DEFAULT_KEYBINDINGS.backspace.key => {
|
||||
self.app.data.radarr_data.filter.pop();
|
||||
}
|
||||
Key::Char(character) => {
|
||||
self.app.data.radarr_data.filter.push(*character);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+50
-1
@@ -40,7 +40,7 @@ impl<T> Default for StatefulTable<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> StatefulTable<T> {
|
||||
impl<T: Clone + PartialEq + Eq> StatefulTable<T> {
|
||||
pub fn set_items(&mut self, items: Vec<T>) {
|
||||
let items_len = items.len();
|
||||
self.items = items;
|
||||
@@ -69,6 +69,55 @@ impl<T: Clone> StatefulTable<T> {
|
||||
pub fn select_index(&mut self, index: Option<usize>) {
|
||||
self.state.select(index);
|
||||
}
|
||||
|
||||
pub fn scroll_up_with_filter<F>(&mut self, filter: F)
|
||||
where
|
||||
F: FnMut(&&T) -> bool,
|
||||
{
|
||||
let filtered_list: Vec<&T> = self.items.iter().filter(filter).collect();
|
||||
|
||||
let element_position = filtered_list
|
||||
.iter()
|
||||
.position(|&item| item == self.current_selection())
|
||||
.unwrap();
|
||||
|
||||
if element_position == 0 {
|
||||
let selected_index = self
|
||||
.items
|
||||
.iter()
|
||||
.position(|item| item == filtered_list[filtered_list.len()]);
|
||||
self.select_index(selected_index);
|
||||
} else {
|
||||
let selected_index = self
|
||||
.items
|
||||
.iter()
|
||||
.position(|item| item == filtered_list[element_position - 1]);
|
||||
self.select_index(selected_index)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_down_with_filter<F>(&mut self, filter: F)
|
||||
where
|
||||
F: FnMut(&&T) -> bool,
|
||||
{
|
||||
let filtered_list: Vec<&T> = self.items.iter().filter(filter).collect();
|
||||
|
||||
let element_position = filtered_list
|
||||
.iter()
|
||||
.position(|&item| item == self.current_selection())
|
||||
.unwrap();
|
||||
|
||||
if element_position + 1 > filtered_list.len() {
|
||||
let selected_index = self.items.iter().position(|item| item == filtered_list[0]);
|
||||
self.select_index(selected_index);
|
||||
} else {
|
||||
let selected_index = self
|
||||
.items
|
||||
.iter()
|
||||
.position(|item| item == filtered_list[element_position + 1]);
|
||||
self.select_index(selected_index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Scrollable for StatefulTable<T> {
|
||||
|
||||
@@ -19,7 +19,7 @@ pub struct SystemStatus {
|
||||
pub start_time: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Deserialize, Debug, Clone)]
|
||||
#[derive(Derivative, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Movie {
|
||||
@@ -48,7 +48,7 @@ pub struct Movie {
|
||||
pub collection: Option<Collection>,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Deserialize, Debug, Clone)]
|
||||
#[derive(Derivative, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CollectionMovie {
|
||||
@@ -62,7 +62,7 @@ pub struct CollectionMovie {
|
||||
pub ratings: RatingsList,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Derivative, Clone, Debug)]
|
||||
#[derive(Deserialize, Derivative, Clone, Debug, PartialEq, Eq)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Collection {
|
||||
@@ -75,7 +75,7 @@ pub struct Collection {
|
||||
pub movies: Option<Vec<CollectionMovie>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Derivative, Debug, Clone)]
|
||||
#[derive(Deserialize, Derivative, Debug, Clone, PartialEq, Eq)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MovieFile {
|
||||
@@ -85,7 +85,7 @@ pub struct MovieFile {
|
||||
pub media_info: MediaInfo,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Derivative, Debug, Clone)]
|
||||
#[derive(Deserialize, Derivative, Debug, Clone, PartialEq, Eq)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MediaInfo {
|
||||
@@ -109,7 +109,7 @@ pub struct MediaInfo {
|
||||
pub scan_type: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Debug, Clone)]
|
||||
#[derive(Default, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RatingsList {
|
||||
pub imdb: Option<Rating>,
|
||||
@@ -117,7 +117,7 @@ pub struct RatingsList {
|
||||
pub rotten_tomatoes: Option<Rating>,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Deserialize, Debug, Clone)]
|
||||
#[derive(Derivative, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[derivative(Default)]
|
||||
pub struct Rating {
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
@@ -189,7 +189,7 @@ pub enum CreditType {
|
||||
Crew,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Credit {
|
||||
pub person_name: String,
|
||||
|
||||
+7
-2
@@ -1,3 +1,6 @@
|
||||
use std::iter::Map;
|
||||
use std::slice::Iter;
|
||||
|
||||
use tui::backend::Backend;
|
||||
use tui::layout::{Alignment, Constraint, Rect};
|
||||
use tui::text::{Span, Spans, Text};
|
||||
@@ -198,16 +201,18 @@ pub struct TableProps<'a, T> {
|
||||
pub constraints: Vec<Constraint>,
|
||||
}
|
||||
|
||||
fn draw_table<'a, B, T, F>(
|
||||
fn draw_table<'a, B, T, F, S>(
|
||||
f: &mut Frame<'_, B>,
|
||||
content_area: Rect,
|
||||
block: Block,
|
||||
table_props: TableProps<'a, T>,
|
||||
row_mapper: F,
|
||||
filter_fn: S,
|
||||
is_loading: bool,
|
||||
) where
|
||||
B: Backend,
|
||||
F: Fn(&T) -> Row<'a>,
|
||||
S: FnMut(&&T) -> bool,
|
||||
{
|
||||
let TableProps {
|
||||
content,
|
||||
@@ -216,7 +221,7 @@ fn draw_table<'a, B, T, F>(
|
||||
} = table_props;
|
||||
|
||||
if !content.items.is_empty() {
|
||||
let rows = content.items.iter().map(row_mapper);
|
||||
let rows = content.items.iter().filter(filter_fn).map(row_mapper);
|
||||
|
||||
let headers = Row::new(table_headers)
|
||||
.style(style_default_bold())
|
||||
|
||||
@@ -150,6 +150,7 @@ fn draw_collection_details<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, cont
|
||||
])
|
||||
.style(style_primary())
|
||||
},
|
||||
|_| true,
|
||||
app.is_loading,
|
||||
);
|
||||
}
|
||||
|
||||
+65
-16
@@ -2,6 +2,7 @@ use std::iter;
|
||||
use std::ops::Sub;
|
||||
|
||||
use chrono::{Duration, Utc};
|
||||
use regex::Regex;
|
||||
use tui::backend::Backend;
|
||||
use tui::layout::{Alignment, Constraint, Rect};
|
||||
use tui::style::{Color, Style};
|
||||
@@ -12,14 +13,15 @@ use tui::Frame;
|
||||
use crate::app::radarr::{ActiveRadarrBlock, RadarrData};
|
||||
use crate::app::App;
|
||||
use crate::logos::RADARR_LOGO;
|
||||
use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie};
|
||||
use crate::models::radarr_models::{Collection, DiskSpace, DownloadRecord, Movie};
|
||||
use crate::models::Route;
|
||||
use crate::ui::radarr_ui::collection_details_ui::draw_collection_details_popup;
|
||||
use crate::ui::radarr_ui::movie_details_ui::draw_movie_info;
|
||||
use crate::ui::utils::{
|
||||
borderless_block, horizontal_chunks, layout_block_top_border, line_gauge_with_label,
|
||||
line_gauge_with_title, style_bold, style_default, style_failure, style_primary, style_success,
|
||||
style_warning, title_block, vertical_chunks_with_margin,
|
||||
borderless_block, horizontal_chunks, layout_block, layout_block_top_border,
|
||||
line_gauge_with_label, line_gauge_with_title, show_cursor, style_bold, style_default,
|
||||
style_failure, style_primary, style_success, style_warning, title_block,
|
||||
vertical_chunks_with_margin,
|
||||
};
|
||||
use crate::ui::{
|
||||
draw_large_popup_over, draw_popup_over, draw_table, draw_tabs, loading, TableProps,
|
||||
@@ -35,9 +37,20 @@ pub(super) fn draw_radarr_ui<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, ar
|
||||
if let Route::Radarr(active_radarr_block) = app.get_current_route().clone() {
|
||||
match active_radarr_block {
|
||||
ActiveRadarrBlock::Movies => draw_library(f, app, content_rect),
|
||||
ActiveRadarrBlock::SearchMovie => {
|
||||
ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::FilterMovies => {
|
||||
draw_popup_over(f, app, content_rect, draw_library, draw_search_box, 30, 10)
|
||||
}
|
||||
ActiveRadarrBlock::SearchCollection | ActiveRadarrBlock::FilterCollections => {
|
||||
draw_popup_over(
|
||||
f,
|
||||
app,
|
||||
content_rect,
|
||||
draw_collections,
|
||||
draw_search_box,
|
||||
30,
|
||||
10,
|
||||
)
|
||||
}
|
||||
ActiveRadarrBlock::Downloads => draw_downloads(f, app, content_rect),
|
||||
ActiveRadarrBlock::Collections => draw_collections(f, app, content_rect),
|
||||
ActiveRadarrBlock::MovieDetails
|
||||
@@ -71,6 +84,16 @@ pub(super) fn draw_radarr_context_row<B: Backend>(f: &mut Frame<'_, B>, app: &Ap
|
||||
fn draw_library<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
|
||||
let quality_profile_map = &app.data.radarr_data.quality_profile_map;
|
||||
let downloads_vec = &app.data.radarr_data.downloads.items;
|
||||
let filter_fn: Box<dyn FnMut(&&Movie) -> bool> = if !app.data.radarr_data.filter.is_empty() {
|
||||
Box::new(|&movie| {
|
||||
Regex::new(r"[^a-zA-Z0-9\s]")
|
||||
.unwrap()
|
||||
.replace_all(&movie.title.to_lowercase(), "")
|
||||
.starts_with(&app.data.radarr_data.filter)
|
||||
})
|
||||
} else {
|
||||
Box::new(|_| true)
|
||||
};
|
||||
|
||||
draw_table(
|
||||
f,
|
||||
@@ -118,6 +141,7 @@ fn draw_library<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
|
||||
])
|
||||
.style(determine_row_style(downloads_vec, movie))
|
||||
},
|
||||
filter_fn,
|
||||
app.is_loading,
|
||||
);
|
||||
}
|
||||
@@ -125,23 +149,36 @@ fn draw_library<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
|
||||
fn draw_search_box<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
|
||||
let chunks = vertical_chunks_with_margin(vec![Constraint::Length(3)], area, 1);
|
||||
if !app.data.radarr_data.is_searching {
|
||||
let input = Paragraph::new("Movie not found!")
|
||||
let error_msg = match app.get_current_route() {
|
||||
Route::Radarr(active_radarr_block) => match active_radarr_block {
|
||||
ActiveRadarrBlock::SearchMovie => "Movie not found!",
|
||||
ActiveRadarrBlock::SearchCollection => "Collection not found!",
|
||||
ActiveRadarrBlock::FilterMovies => "No movies found matching filter!",
|
||||
ActiveRadarrBlock::FilterCollections => "No collections found matching filter!",
|
||||
_ => "",
|
||||
},
|
||||
_ => "",
|
||||
};
|
||||
|
||||
let input = Paragraph::new(error_msg)
|
||||
.style(style_failure())
|
||||
.block(title_block("Search"));
|
||||
f.set_cursor(
|
||||
chunks[0].x + app.data.radarr_data.search.len() as u16 + 1,
|
||||
chunks[0].y + 1,
|
||||
);
|
||||
.block(layout_block());
|
||||
|
||||
f.render_widget(input, chunks[0]);
|
||||
} else {
|
||||
let block_title = match app.get_current_route() {
|
||||
Route::Radarr(active_radarr_block) => match active_radarr_block {
|
||||
ActiveRadarrBlock::SearchMovie | ActiveRadarrBlock::SearchCollection => "Search",
|
||||
ActiveRadarrBlock::FilterMovies | ActiveRadarrBlock::FilterCollections => "Filter",
|
||||
_ => "",
|
||||
},
|
||||
_ => "",
|
||||
};
|
||||
|
||||
let input = Paragraph::new(app.data.radarr_data.search.as_ref())
|
||||
.style(style_default())
|
||||
.block(title_block("Search"));
|
||||
f.set_cursor(
|
||||
chunks[0].x + app.data.radarr_data.search.len() as u16 + 1,
|
||||
chunks[0].y + 1,
|
||||
);
|
||||
.block(title_block(block_title));
|
||||
show_cursor(f, chunks[0], &app.data.radarr_data.search);
|
||||
|
||||
f.render_widget(input, chunks[0]);
|
||||
}
|
||||
@@ -238,12 +275,23 @@ fn draw_downloads<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
|
||||
])
|
||||
.style(style_primary())
|
||||
},
|
||||
|_| true,
|
||||
app.is_loading,
|
||||
);
|
||||
}
|
||||
|
||||
fn draw_collections<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
|
||||
let quality_profile_map = &app.data.radarr_data.quality_profile_map;
|
||||
let filter_fn: Box<dyn FnMut(&&Collection) -> bool> = if !app.data.radarr_data.filter.is_empty() {
|
||||
Box::new(|&collection| {
|
||||
Regex::new(r"[^a-zA-Z0-9\s]")
|
||||
.unwrap()
|
||||
.replace_all(&collection.title.to_lowercase(), "")
|
||||
.starts_with(&app.data.radarr_data.filter)
|
||||
})
|
||||
} else {
|
||||
Box::new(|_| true)
|
||||
};
|
||||
draw_table(
|
||||
f,
|
||||
area,
|
||||
@@ -276,6 +324,7 @@ fn draw_collections<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect)
|
||||
])
|
||||
.style(style_primary())
|
||||
},
|
||||
filter_fn,
|
||||
app.is_loading,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -196,6 +196,7 @@ fn draw_movie_history<B: Backend>(
|
||||
])
|
||||
.style(style_success())
|
||||
},
|
||||
|_| true,
|
||||
app.is_loading,
|
||||
);
|
||||
}
|
||||
@@ -228,6 +229,7 @@ fn draw_movie_cast<B: Backend>(
|
||||
])
|
||||
.style(style_success())
|
||||
},
|
||||
|_| true,
|
||||
app.is_loading,
|
||||
)
|
||||
}
|
||||
@@ -262,6 +264,7 @@ fn draw_movie_crew<B: Backend>(
|
||||
])
|
||||
.style(style_success())
|
||||
},
|
||||
|_| true,
|
||||
app.is_loading,
|
||||
);
|
||||
}
|
||||
|
||||
+6
-1
@@ -1,8 +1,9 @@
|
||||
use tui::backend::Backend;
|
||||
use tui::layout::{Constraint, Direction, Layout, Rect};
|
||||
use tui::style::{Color, Modifier, Style};
|
||||
use tui::symbols;
|
||||
use tui::text::{Span, Spans};
|
||||
use tui::widgets::{Block, Borders, LineGauge};
|
||||
use tui::{symbols, Frame};
|
||||
|
||||
pub fn horizontal_chunks(constraints: Vec<Constraint>, size: Rect) -> Vec<Rect> {
|
||||
Layout::default()
|
||||
@@ -204,3 +205,7 @@ pub fn line_gauge_with_label(title: &str, ratio: f64) -> LineGauge {
|
||||
.ratio(ratio)
|
||||
.label(Spans::from(format!("{}: {:.0}%", title, ratio * 100.0)))
|
||||
}
|
||||
|
||||
pub fn show_cursor<B: Backend>(f: &mut Frame<'_, B>, area: Rect, string: &String) {
|
||||
f.set_cursor(area.x + string.len() as u16 + 1, area.y + 1);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use log::LevelFilter;
|
||||
use log4rs::append::file::FileAppender;
|
||||
use log4rs::config::{Appender, Root};
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
use regex::Regex;
|
||||
|
||||
pub fn init_logging_config() -> log4rs::Config {
|
||||
let file_path = "/tmp/managarr.log";
|
||||
@@ -30,3 +31,10 @@ pub fn convert_runtime(runtime: u64) -> (u64, u64) {
|
||||
|
||||
(hours, minutes)
|
||||
}
|
||||
|
||||
pub fn strip_non_alphanumeric_characters(input: &str) -> String {
|
||||
Regex::new(r"[^a-zA-Z0-9\s]")
|
||||
.unwrap()
|
||||
.replace_all(&input.to_lowercase(), "")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user