Mostly added Add movie functionality. Removed calendar functions for now. Want to add the ability to modify settings and quality profiles first
This commit is contained in:
@@ -23,3 +23,4 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
strum = { version = "0.24.1", features = ["derive"] }
|
strum = { version = "0.24.1", features = ["derive"] }
|
||||||
tokio = { version = "1.24.1", features = ["full"] }
|
tokio = { version = "1.24.1", features = ["full"] }
|
||||||
tui = "0.19.0"
|
tui = "0.19.0"
|
||||||
|
urlencoding = "2.1.2"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ macro_rules! generate_keybindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generate_keybindings! {
|
generate_keybindings! {
|
||||||
|
add,
|
||||||
up,
|
up,
|
||||||
down,
|
down,
|
||||||
left,
|
left,
|
||||||
@@ -30,6 +31,10 @@ pub struct KeyBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
|
pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
|
||||||
|
add: KeyBinding {
|
||||||
|
key: Key::Char('a'),
|
||||||
|
desc: "Add",
|
||||||
|
},
|
||||||
up: KeyBinding {
|
up: KeyBinding {
|
||||||
key: Key::Up,
|
key: Key::Up,
|
||||||
desc: "Scroll up",
|
desc: "Scroll up",
|
||||||
|
|||||||
+4
-1
@@ -30,6 +30,7 @@ pub struct App {
|
|||||||
pub is_routing: bool,
|
pub is_routing: bool,
|
||||||
pub is_loading: bool,
|
pub is_loading: bool,
|
||||||
pub should_refresh: bool,
|
pub should_refresh: bool,
|
||||||
|
pub should_ignore_quit_key: bool,
|
||||||
pub config: AppConfig,
|
pub config: AppConfig,
|
||||||
pub data: Data,
|
pub data: Data,
|
||||||
}
|
}
|
||||||
@@ -119,7 +120,8 @@ impl Default for App {
|
|||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Radarr".to_owned(),
|
title: "Radarr".to_owned(),
|
||||||
route: ActiveRadarrBlock::Movies.into(),
|
route: ActiveRadarrBlock::Movies.into(),
|
||||||
help: "<tab> change servarr | <?> help | <q> quit ".to_owned(),
|
help: "<↑↓> scroll | ←→ change tab | <tab> change servarr | <?> help | <q> quit "
|
||||||
|
.to_owned(),
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Sonarr".to_owned(),
|
title: "Sonarr".to_owned(),
|
||||||
@@ -136,6 +138,7 @@ impl Default for App {
|
|||||||
is_loading: false,
|
is_loading: false,
|
||||||
is_routing: false,
|
is_routing: false,
|
||||||
should_refresh: false,
|
should_refresh: false,
|
||||||
|
should_ignore_quit_key: false,
|
||||||
config: AppConfig::default(),
|
config: AppConfig::default(),
|
||||||
data: Data::default(),
|
data: Data::default(),
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-12
@@ -5,10 +5,11 @@ use chrono::{DateTime, Utc};
|
|||||||
use strum::EnumIter;
|
use strum::EnumIter;
|
||||||
|
|
||||||
use crate::app::{App, Route};
|
use crate::app::{App, Route};
|
||||||
use crate::models::{ScrollableText, StatefulMatrix, StatefulTable, TabRoute, TabState};
|
|
||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
Collection, CollectionMovie, Credit, DiskSpace, DownloadRecord, Movie, MovieHistoryItem,
|
AddMovieSearchResult, Collection, CollectionMovie, Credit, DiskSpace, DownloadRecord, Movie,
|
||||||
|
MovieHistoryItem,
|
||||||
};
|
};
|
||||||
|
use crate::models::{ScrollableText, StatefulTable, TabRoute, TabState};
|
||||||
use crate::network::radarr_network::RadarrEvent;
|
use crate::network::radarr_network::RadarrEvent;
|
||||||
|
|
||||||
pub struct RadarrData {
|
pub struct RadarrData {
|
||||||
@@ -17,6 +18,7 @@ pub struct RadarrData {
|
|||||||
pub start_time: DateTime<Utc>,
|
pub start_time: DateTime<Utc>,
|
||||||
pub movies: StatefulTable<Movie>,
|
pub movies: StatefulTable<Movie>,
|
||||||
pub filtered_movies: StatefulTable<Movie>,
|
pub filtered_movies: StatefulTable<Movie>,
|
||||||
|
pub add_searched_movies: StatefulTable<AddMovieSearchResult>,
|
||||||
pub downloads: StatefulTable<DownloadRecord>,
|
pub downloads: StatefulTable<DownloadRecord>,
|
||||||
pub quality_profile_map: HashMap<u64, String>,
|
pub quality_profile_map: HashMap<u64, String>,
|
||||||
pub movie_details: ScrollableText,
|
pub movie_details: ScrollableText,
|
||||||
@@ -29,7 +31,6 @@ pub struct RadarrData {
|
|||||||
pub collections: StatefulTable<Collection>,
|
pub collections: StatefulTable<Collection>,
|
||||||
pub filtered_collections: StatefulTable<Collection>,
|
pub filtered_collections: StatefulTable<Collection>,
|
||||||
pub collection_movies: StatefulTable<CollectionMovie>,
|
pub collection_movies: StatefulTable<CollectionMovie>,
|
||||||
pub calendar: StatefulMatrix<>
|
|
||||||
pub main_tabs: TabState,
|
pub main_tabs: TabState,
|
||||||
pub movie_info_tabs: TabState,
|
pub movie_info_tabs: TabState,
|
||||||
pub search: String,
|
pub search: String,
|
||||||
@@ -49,6 +50,7 @@ impl RadarrData {
|
|||||||
self.filter = String::default();
|
self.filter = String::default();
|
||||||
self.filtered_movies = StatefulTable::default();
|
self.filtered_movies = StatefulTable::default();
|
||||||
self.filtered_collections = StatefulTable::default();
|
self.filtered_collections = StatefulTable::default();
|
||||||
|
self.add_searched_movies = StatefulTable::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_movie_info_tabs(&mut self) {
|
pub fn reset_movie_info_tabs(&mut self) {
|
||||||
@@ -74,6 +76,7 @@ impl Default for RadarrData {
|
|||||||
version: String::default(),
|
version: String::default(),
|
||||||
start_time: DateTime::default(),
|
start_time: DateTime::default(),
|
||||||
movies: StatefulTable::default(),
|
movies: StatefulTable::default(),
|
||||||
|
add_searched_movies: StatefulTable::default(),
|
||||||
filtered_movies: StatefulTable::default(),
|
filtered_movies: StatefulTable::default(),
|
||||||
downloads: StatefulTable::default(),
|
downloads: StatefulTable::default(),
|
||||||
quality_profile_map: HashMap::default(),
|
quality_profile_map: HashMap::default(),
|
||||||
@@ -95,25 +98,20 @@ impl Default for RadarrData {
|
|||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Library".to_owned(),
|
title: "Library".to_owned(),
|
||||||
route: ActiveRadarrBlock::Movies.into(),
|
route: ActiveRadarrBlock::Movies.into(),
|
||||||
help: "<↑↓> scroll | <s> search | <f> filter | <enter> details | <esc> cancel filter | <del> delete | ←→ change tab "
|
help: "<a> add | <s> search | <f> filter | <enter> details | <esc> cancel filter | <del> delete "
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Downloads".to_owned(),
|
title: "Downloads".to_owned(),
|
||||||
route: ActiveRadarrBlock::Downloads.into(),
|
route: ActiveRadarrBlock::Downloads.into(),
|
||||||
help: "<↑↓> scroll | ←→ change tab ".to_owned(),
|
help: String::default(),
|
||||||
},
|
},
|
||||||
TabRoute {
|
TabRoute {
|
||||||
title: "Collections".to_owned(),
|
title: "Collections".to_owned(),
|
||||||
route: ActiveRadarrBlock::Collections.into(),
|
route: ActiveRadarrBlock::Collections.into(),
|
||||||
help: "<↑↓> scroll | <s> search | <f> filter | <enter> details | <esc> cancel filter | ←→ change tab "
|
help: "<s> search | <f> filter | <enter> details | <esc> cancel filter "
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
},
|
},
|
||||||
TabRoute {
|
|
||||||
title: "Calendar".to_owned(),
|
|
||||||
route: ActiveRadarrBlock::Calendar.into(),
|
|
||||||
help: "<↑↓> scroll | <enter> details | ←→ change tab ".to_owned()
|
|
||||||
}
|
|
||||||
]),
|
]),
|
||||||
movie_info_tabs: TabState::new(vec![
|
movie_info_tabs: TabState::new(vec![
|
||||||
TabRoute {
|
TabRoute {
|
||||||
@@ -148,7 +146,9 @@ impl Default for RadarrData {
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, EnumIter)]
|
#[derive(Clone, PartialEq, Eq, Debug, EnumIter)]
|
||||||
pub enum ActiveRadarrBlock {
|
pub enum ActiveRadarrBlock {
|
||||||
AddMovie,
|
AddMovieSearchInput,
|
||||||
|
AddMovieSearchResults,
|
||||||
|
AddMoviePrompt,
|
||||||
Calendar,
|
Calendar,
|
||||||
Collections,
|
Collections,
|
||||||
CollectionDetails,
|
CollectionDetails,
|
||||||
@@ -209,6 +209,12 @@ impl App {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ActiveRadarrBlock::AddMovieSearchResults => {
|
||||||
|
self.is_loading = true;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(RadarrEvent::SearchNewMovie.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::FileInfo => {
|
ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::FileInfo => {
|
||||||
self.is_loading = true;
|
self.is_loading = true;
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::app::radarr::ActiveRadarrBlock;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::models::{Scrollable, StatefulTable};
|
||||||
|
use crate::{App, Key};
|
||||||
|
|
||||||
|
pub(super) struct AddMovieHandler<'a> {
|
||||||
|
key: &'a Key,
|
||||||
|
app: &'a mut App,
|
||||||
|
active_radarr_block: &'a ActiveRadarrBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for AddMovieHandler<'a> {
|
||||||
|
fn with(
|
||||||
|
key: &'a Key,
|
||||||
|
app: &'a mut App,
|
||||||
|
active_block: &'a ActiveRadarrBlock,
|
||||||
|
) -> AddMovieHandler<'a> {
|
||||||
|
AddMovieHandler {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_radarr_block: active_block,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self) -> &Key {
|
||||||
|
self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_up(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::AddMovieSearchResults => {
|
||||||
|
self.app.data.radarr_data.add_searched_movies.scroll_up()
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_down(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::AddMovieSearchResults => {
|
||||||
|
self.app.data.radarr_data.add_searched_movies.scroll_down()
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_home(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::AddMovieSearchResults => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.add_searched_movies
|
||||||
|
.scroll_to_top(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_end(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::AddMovieSearchResults => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.add_searched_movies
|
||||||
|
.scroll_to_bottom(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_delete(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
if *self.active_radarr_block == ActiveRadarrBlock::AddMoviePrompt {
|
||||||
|
match self.key {
|
||||||
|
_ if *self.key == DEFAULT_KEYBINDINGS.left.key
|
||||||
|
|| *self.key == DEFAULT_KEYBINDINGS.right.key =>
|
||||||
|
{
|
||||||
|
self.app.data.radarr_data.prompt_confirm = !self.app.data.radarr_data.prompt_confirm;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::AddMovieSearchInput => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into());
|
||||||
|
self.app.should_ignore_quit_key = false;
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::AddMovieSearchResults => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::AddMovieSearchInput => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.radarr_data.reset_search();
|
||||||
|
self.app.should_ignore_quit_key = false;
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::AddMovieSearchResults => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.radarr_data.add_searched_movies = StatefulTable::default();
|
||||||
|
self.app.should_ignore_quit_key = true;
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::AddMoviePrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.radarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
let key = self.key;
|
||||||
|
match self.active_radarr_block {
|
||||||
|
ActiveRadarrBlock::AddMovieSearchInput => 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);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
use crate::app::radarr::ActiveRadarrBlock;
|
use crate::app::radarr::ActiveRadarrBlock;
|
||||||
|
use crate::handlers::radarr_handlers::add_movie_handler::AddMovieHandler;
|
||||||
use crate::handlers::radarr_handlers::collection_details_handler::CollectionDetailsHandler;
|
use crate::handlers::radarr_handlers::collection_details_handler::CollectionDetailsHandler;
|
||||||
use crate::handlers::radarr_handlers::movie_details_handler::MovieDetailsHandler;
|
use crate::handlers::radarr_handlers::movie_details_handler::MovieDetailsHandler;
|
||||||
use crate::handlers::{handle_clear_errors, KeyEventHandler};
|
use crate::handlers::{handle_clear_errors, KeyEventHandler};
|
||||||
@@ -7,6 +8,7 @@ use crate::models::Scrollable;
|
|||||||
use crate::utils::strip_non_alphanumeric_characters;
|
use crate::utils::strip_non_alphanumeric_characters;
|
||||||
use crate::{App, Key};
|
use crate::{App, Key};
|
||||||
|
|
||||||
|
mod add_movie_handler;
|
||||||
mod collection_details_handler;
|
mod collection_details_handler;
|
||||||
mod movie_details_handler;
|
mod movie_details_handler;
|
||||||
|
|
||||||
@@ -29,6 +31,11 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> {
|
|||||||
ActiveRadarrBlock::CollectionDetails | ActiveRadarrBlock::ViewMovieOverview => {
|
ActiveRadarrBlock::CollectionDetails | ActiveRadarrBlock::ViewMovieOverview => {
|
||||||
CollectionDetailsHandler::with(self.key, self.app, self.active_radarr_block).handle()
|
CollectionDetailsHandler::with(self.key, self.app, self.active_radarr_block).handle()
|
||||||
}
|
}
|
||||||
|
ActiveRadarrBlock::AddMovieSearchInput
|
||||||
|
| ActiveRadarrBlock::AddMovieSearchResults
|
||||||
|
| ActiveRadarrBlock::AddMoviePrompt => {
|
||||||
|
AddMovieHandler::with(self.key, self.app, self.active_radarr_block).handle()
|
||||||
|
}
|
||||||
_ => self.handle_key_event(),
|
_ => self.handle_key_event(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,6 +307,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> {
|
|||||||
| ActiveRadarrBlock::FilterCollections => {
|
| ActiveRadarrBlock::FilterCollections => {
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
self.app.data.radarr_data.reset_search();
|
self.app.data.radarr_data.reset_search();
|
||||||
|
self.app.should_ignore_quit_key = false;
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::DeleteMoviePrompt => {
|
ActiveRadarrBlock::DeleteMoviePrompt => {
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
@@ -321,12 +329,20 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> {
|
|||||||
.app
|
.app
|
||||||
.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into());
|
.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into());
|
||||||
self.app.data.radarr_data.is_searching = true;
|
self.app.data.radarr_data.is_searching = true;
|
||||||
|
self.app.should_ignore_quit_key = true;
|
||||||
}
|
}
|
||||||
_ if *key == DEFAULT_KEYBINDINGS.filter.key => {
|
_ if *key == DEFAULT_KEYBINDINGS.filter.key => {
|
||||||
self
|
self
|
||||||
.app
|
.app
|
||||||
.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into());
|
.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into());
|
||||||
self.app.data.radarr_data.is_searching = true;
|
self.app.data.radarr_data.is_searching = true;
|
||||||
|
self.app.should_ignore_quit_key = true;
|
||||||
|
}
|
||||||
|
_ if *key == DEFAULT_KEYBINDINGS.add.key => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
|
||||||
|
self.app.should_ignore_quit_key = true;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
@@ -336,12 +352,14 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> {
|
|||||||
.app
|
.app
|
||||||
.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into());
|
.push_navigation_stack(ActiveRadarrBlock::SearchCollection.into());
|
||||||
self.app.data.radarr_data.is_searching = true;
|
self.app.data.radarr_data.is_searching = true;
|
||||||
|
self.app.should_ignore_quit_key = true;
|
||||||
}
|
}
|
||||||
_ if *key == DEFAULT_KEYBINDINGS.filter.key => {
|
_ if *key == DEFAULT_KEYBINDINGS.filter.key => {
|
||||||
self
|
self
|
||||||
.app
|
.app
|
||||||
.push_navigation_stack(ActiveRadarrBlock::FilterCollections.into());
|
.push_navigation_stack(ActiveRadarrBlock::FilterCollections.into());
|
||||||
self.app.data.radarr_data.is_searching = true;
|
self.app.data.radarr_data.is_searching = true;
|
||||||
|
self.app.should_ignore_quit_key = true;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
@@ -386,6 +404,7 @@ impl RadarrHandler<'_> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.app.data.radarr_data.is_searching = false;
|
self.app.data.radarr_data.is_searching = false;
|
||||||
|
self.app.should_ignore_quit_key = false;
|
||||||
|
|
||||||
if collection_index.is_some() {
|
if collection_index.is_some() {
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
@@ -415,6 +434,7 @@ impl RadarrHandler<'_> {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
self.app.data.radarr_data.is_searching = false;
|
self.app.data.radarr_data.is_searching = false;
|
||||||
|
self.app.should_ignore_quit_key = false;
|
||||||
|
|
||||||
if !filter_matches.is_empty() {
|
if !filter_matches.is_empty() {
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
|
|||||||
+1
-1
@@ -83,7 +83,7 @@ async fn start_ui(app: &Arc<Mutex<App>>) -> Result<()> {
|
|||||||
|
|
||||||
match input_events.next()? {
|
match input_events.next()? {
|
||||||
InputEvent::KeyEvent(key) => {
|
InputEvent::KeyEvent(key) => {
|
||||||
if key == Key::Char('q') {
|
if key == Key::Char('q') && !app.should_ignore_quit_key {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,61 +113,6 @@ impl<T> Scrollable for StatefulTable<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct StatefulMatrix<T> {
|
|
||||||
pub selection: (usize, usize),
|
|
||||||
pub items: Vec<Vec<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Scrollable for StatefulMatrix<T> {
|
|
||||||
fn scroll_down(&mut self) {
|
|
||||||
if self.selection.0 >= self.items.len() - 1 {
|
|
||||||
self.selection.0 = 0;
|
|
||||||
} else {
|
|
||||||
self.selection.0 += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scroll_up(&mut self) {
|
|
||||||
if self.selection.0 == 0 {
|
|
||||||
self.selection.0 = self.items.len() - 1;
|
|
||||||
} else {
|
|
||||||
self.selection.0 -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scroll_to_top(&mut self) {
|
|
||||||
self.selection.0 = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scroll_to_bottom(&mut self) {
|
|
||||||
self.selection.0 = self.items.len() - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> StatefulMatrix<T> {
|
|
||||||
pub fn current_selection(&self) -> &T {
|
|
||||||
let (x, y) = self.selection;
|
|
||||||
&self.items[x][y]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scroll_left(&mut self) {
|
|
||||||
if self.selection.1 == 0 {
|
|
||||||
self.selection.1 = self.items[0].len() - 1;
|
|
||||||
} else {
|
|
||||||
self.selection.1 -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scroll_right(&mut self) {
|
|
||||||
if self.selection.1 >= self.items[0].len() - 1 {
|
|
||||||
self.selection.1 = 0;
|
|
||||||
} else {
|
|
||||||
self.selection.1 += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ScrollableText {
|
pub struct ScrollableText {
|
||||||
pub items: Vec<String>,
|
pub items: Vec<String>,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use derivative::Derivative;
|
use derivative::Derivative;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Number;
|
use serde_json::Number;
|
||||||
|
|
||||||
use crate::models::HorizontallyScrollableText;
|
use crate::models::HorizontallyScrollableText;
|
||||||
@@ -199,3 +199,39 @@ pub struct Credit {
|
|||||||
#[serde(rename(deserialize = "type"))]
|
#[serde(rename(deserialize = "type"))]
|
||||||
pub credit_type: CreditType,
|
pub credit_type: CreditType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct AddMovieBody {
|
||||||
|
pub tmdb_id: Number,
|
||||||
|
pub title: String,
|
||||||
|
pub root_folder_path: String,
|
||||||
|
pub quality_profile_id: Number,
|
||||||
|
pub minimum_availability: String,
|
||||||
|
pub monitored: bool,
|
||||||
|
pub add_options: AddOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct AddOptions {
|
||||||
|
pub search_for_movie: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Derivative, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[derivative(Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct AddMovieSearchResult {
|
||||||
|
#[derivative(Default(value = "Number::from(0)"))]
|
||||||
|
pub tmdb_id: Number,
|
||||||
|
pub title: String,
|
||||||
|
pub original_language: Language,
|
||||||
|
pub status: String,
|
||||||
|
pub overview: String,
|
||||||
|
pub genres: Vec<String>,
|
||||||
|
#[derivative(Default(value = "Number::from(0)"))]
|
||||||
|
pub year: Number,
|
||||||
|
#[derivative(Default(value = "Number::from(0)"))]
|
||||||
|
pub runtime: Number,
|
||||||
|
pub ratings: RatingsList,
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ use log::{debug, error};
|
|||||||
use reqwest::{RequestBuilder, StatusCode};
|
use reqwest::{RequestBuilder, StatusCode};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use tokio::sync::MutexGuard;
|
use tokio::sync::MutexGuard;
|
||||||
|
use urlencoding::encode;
|
||||||
|
|
||||||
use crate::app::{App, RadarrConfig};
|
use crate::app::{App, RadarrConfig};
|
||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
Collection, Credit, CreditType, DiskSpace, DownloadsResponse, Movie, MovieHistoryItem,
|
AddMovieSearchResult, Collection, Credit, CreditType, DiskSpace, DownloadsResponse, Movie,
|
||||||
QualityProfile, SystemStatus,
|
MovieHistoryItem, QualityProfile, SystemStatus,
|
||||||
};
|
};
|
||||||
use crate::models::ScrollableText;
|
use crate::models::ScrollableText;
|
||||||
use crate::network::utils::get_movie_status;
|
use crate::network::utils::get_movie_status;
|
||||||
@@ -27,6 +28,7 @@ pub enum RadarrEvent {
|
|||||||
GetOverview,
|
GetOverview,
|
||||||
GetQualityProfiles,
|
GetQualityProfiles,
|
||||||
GetStatus,
|
GetStatus,
|
||||||
|
SearchNewMovie,
|
||||||
HealthCheck,
|
HealthCheck,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ impl RadarrEvent {
|
|||||||
RadarrEvent::GetCollections => "/collection",
|
RadarrEvent::GetCollections => "/collection",
|
||||||
RadarrEvent::GetDownloads => "/queue",
|
RadarrEvent::GetDownloads => "/queue",
|
||||||
RadarrEvent::GetMovies | RadarrEvent::GetMovieDetails | RadarrEvent::DeleteMovie => "/movie",
|
RadarrEvent::GetMovies | RadarrEvent::GetMovieDetails | RadarrEvent::DeleteMovie => "/movie",
|
||||||
|
RadarrEvent::SearchNewMovie => "/movie/lookup",
|
||||||
RadarrEvent::GetMovieCredits => "/credit",
|
RadarrEvent::GetMovieCredits => "/credit",
|
||||||
RadarrEvent::GetMovieHistory => "/history/movie",
|
RadarrEvent::GetMovieHistory => "/history/movie",
|
||||||
RadarrEvent::GetOverview => "/diskspace",
|
RadarrEvent::GetOverview => "/diskspace",
|
||||||
@@ -122,6 +125,11 @@ impl<'a> Network<'a> {
|
|||||||
.get_quality_profiles(RadarrEvent::GetQualityProfiles.resource().to_owned())
|
.get_quality_profiles(RadarrEvent::GetQualityProfiles.resource().to_owned())
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
RadarrEvent::SearchNewMovie => {
|
||||||
|
self
|
||||||
|
.search_movie(RadarrEvent::SearchNewMovie.resource().to_owned())
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,13 +150,13 @@ impl<'a> Network<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_diskspace(&self, resource: String) {
|
async fn get_diskspace(&self, resource: String) {
|
||||||
type RequestType = Vec<DiskSpace>;
|
type ResponseType = Vec<DiskSpace>;
|
||||||
self
|
self
|
||||||
.handle_request::<RequestType>(
|
.handle_request::<ResponseType>(
|
||||||
RequestProps {
|
RequestProps {
|
||||||
resource,
|
resource,
|
||||||
method: RequestMethod::GET,
|
method: RequestMethod::GET,
|
||||||
body: None::<RequestType>,
|
body: None::<ResponseType>,
|
||||||
},
|
},
|
||||||
|disk_space_vec, mut app| {
|
|disk_space_vec, mut app| {
|
||||||
app.data.radarr_data.disk_space_vec = disk_space_vec;
|
app.data.radarr_data.disk_space_vec = disk_space_vec;
|
||||||
@@ -174,19 +182,44 @@ impl<'a> Network<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_movies(&self, resource: String) {
|
async fn get_movies(&self, resource: String) {
|
||||||
type RequestType = Vec<Movie>;
|
type ResponseType = Vec<Movie>;
|
||||||
self
|
self
|
||||||
.handle_request::<RequestType>(
|
.handle_request::<ResponseType>(
|
||||||
RequestProps {
|
RequestProps {
|
||||||
resource,
|
resource,
|
||||||
method: RequestMethod::GET,
|
method: RequestMethod::GET,
|
||||||
body: None::<RequestType>,
|
body: None::<ResponseType>,
|
||||||
},
|
},
|
||||||
|movie_vec, mut app| app.data.radarr_data.movies.set_items(movie_vec),
|
|movie_vec, mut app| app.data.radarr_data.movies.set_items(movie_vec),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn search_movie(&self, resource: String) {
|
||||||
|
type ResponseType = Vec<AddMovieSearchResult>;
|
||||||
|
let search_string = self.app.lock().await.data.radarr_data.search.clone();
|
||||||
|
debug!(
|
||||||
|
"Searching for movie: {:?}",
|
||||||
|
format!("{}?term={}", resource, encode(search_string.as_str()))
|
||||||
|
);
|
||||||
|
self
|
||||||
|
.handle_request::<ResponseType>(
|
||||||
|
RequestProps {
|
||||||
|
resource: format!("{}?term={}", resource, encode(&search_string)),
|
||||||
|
method: RequestMethod::GET,
|
||||||
|
body: None::<ResponseType>,
|
||||||
|
},
|
||||||
|
|movie_vec, mut app| {
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.add_searched_movies
|
||||||
|
.set_items(movie_vec)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_movie_details(&self, resource: String) {
|
async fn get_movie_details(&self, resource: String) {
|
||||||
let movie_id = self.extract_movie_id().await;
|
let movie_id = self.extract_movie_id().await;
|
||||||
self
|
self
|
||||||
@@ -338,13 +371,13 @@ impl<'a> Network<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_movie_history(&self, resource: String) {
|
async fn get_movie_history(&self, resource: String) {
|
||||||
type RequestType = Vec<MovieHistoryItem>;
|
type ResponseType = Vec<MovieHistoryItem>;
|
||||||
self
|
self
|
||||||
.handle_request::<RequestType>(
|
.handle_request::<ResponseType>(
|
||||||
RequestProps {
|
RequestProps {
|
||||||
resource: self.append_movie_id_param(&resource).await,
|
resource: self.append_movie_id_param(&resource).await,
|
||||||
method: RequestMethod::GET,
|
method: RequestMethod::GET,
|
||||||
body: None::<RequestType>,
|
body: None::<ResponseType>,
|
||||||
},
|
},
|
||||||
|movie_history_vec, mut app| {
|
|movie_history_vec, mut app| {
|
||||||
let mut reversed_movie_history_vec = movie_history_vec.to_vec();
|
let mut reversed_movie_history_vec = movie_history_vec.to_vec();
|
||||||
@@ -360,13 +393,13 @@ impl<'a> Network<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_collections(&self, resource: String) {
|
async fn get_collections(&self, resource: String) {
|
||||||
type RequestType = Vec<Collection>;
|
type ResponseType = Vec<Collection>;
|
||||||
self
|
self
|
||||||
.handle_request::<RequestType>(
|
.handle_request::<ResponseType>(
|
||||||
RequestProps {
|
RequestProps {
|
||||||
resource,
|
resource,
|
||||||
method: RequestMethod::GET,
|
method: RequestMethod::GET,
|
||||||
body: None::<RequestType>,
|
body: None::<ResponseType>,
|
||||||
},
|
},
|
||||||
|collections_vec, mut app| {
|
|collections_vec, mut app| {
|
||||||
app.data.radarr_data.collections.set_items(collections_vec);
|
app.data.radarr_data.collections.set_items(collections_vec);
|
||||||
@@ -395,13 +428,13 @@ impl<'a> Network<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_quality_profiles(&self, resource: String) {
|
async fn get_quality_profiles(&self, resource: String) {
|
||||||
type RequestType = Vec<QualityProfile>;
|
type ResponseType = Vec<QualityProfile>;
|
||||||
self
|
self
|
||||||
.handle_request::<RequestType>(
|
.handle_request::<ResponseType>(
|
||||||
RequestProps {
|
RequestProps {
|
||||||
resource,
|
resource,
|
||||||
method: RequestMethod::GET,
|
method: RequestMethod::GET,
|
||||||
body: None::<RequestType>,
|
body: None::<ResponseType>,
|
||||||
},
|
},
|
||||||
|quality_profiles, mut app| {
|
|quality_profiles, mut app| {
|
||||||
app.data.radarr_data.quality_profile_map = quality_profiles
|
app.data.radarr_data.quality_profile_map = quality_profiles
|
||||||
@@ -414,13 +447,13 @@ impl<'a> Network<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_credits(&self, resource: String) {
|
async fn get_credits(&self, resource: String) {
|
||||||
type RequestType = Vec<Credit>;
|
type ResponseType = Vec<Credit>;
|
||||||
self
|
self
|
||||||
.handle_request::<RequestType>(
|
.handle_request::<ResponseType>(
|
||||||
RequestProps {
|
RequestProps {
|
||||||
resource: self.append_movie_id_param(&resource).await,
|
resource: self.append_movie_id_param(&resource).await,
|
||||||
method: RequestMethod::GET,
|
method: RequestMethod::GET,
|
||||||
body: None::<RequestType>,
|
body: None::<ResponseType>,
|
||||||
},
|
},
|
||||||
|credit_vec, mut app| {
|
|credit_vec, mut app| {
|
||||||
let cast_vec: Vec<Credit> = credit_vec
|
let cast_vec: Vec<Credit> = credit_vec
|
||||||
|
|||||||
+2
-5
@@ -16,7 +16,7 @@ use crate::ui::utils::{
|
|||||||
borderless_block, centered_rect, horizontal_chunks, horizontal_chunks_with_margin, layout_block,
|
borderless_block, centered_rect, horizontal_chunks, horizontal_chunks_with_margin, layout_block,
|
||||||
layout_block_top_border, logo_block, style_default_bold, style_failure, style_help,
|
layout_block_top_border, logo_block, style_default_bold, style_failure, style_help,
|
||||||
style_highlight, style_primary, style_secondary, style_system_function, title_block,
|
style_highlight, style_primary, style_secondary, style_system_function, title_block,
|
||||||
vertical_chunks_with_margin,
|
title_block_centered, vertical_chunks_with_margin,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod radarr_ui;
|
mod radarr_ui;
|
||||||
@@ -259,10 +259,7 @@ pub fn draw_prompt_box<B: Backend>(
|
|||||||
prompt: &str,
|
prompt: &str,
|
||||||
yes_no_value: &bool,
|
yes_no_value: &bool,
|
||||||
) {
|
) {
|
||||||
f.render_widget(
|
f.render_widget(title_block_centered(title), prompt_area);
|
||||||
title_block(title).title_alignment(Alignment::Center),
|
|
||||||
prompt_area,
|
|
||||||
);
|
|
||||||
|
|
||||||
let chunks = vertical_chunks_with_margin(
|
let chunks = vertical_chunks_with_margin(
|
||||||
vec![
|
vec![
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
use tui::backend::Backend;
|
||||||
|
use tui::layout::{Alignment, Constraint, Rect};
|
||||||
|
use tui::text::Text;
|
||||||
|
use tui::widgets::{Cell, Paragraph, Row};
|
||||||
|
use tui::Frame;
|
||||||
|
|
||||||
|
use crate::app::radarr::ActiveRadarrBlock;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::ui::utils::{
|
||||||
|
borderless_block, layout_block, show_cursor, style_default, style_help, style_primary,
|
||||||
|
title_block_centered, vertical_chunks_with_margin,
|
||||||
|
};
|
||||||
|
use crate::ui::{draw_medium_popup_over, draw_prompt_box, draw_table, TableProps};
|
||||||
|
use crate::utils::convert_runtime;
|
||||||
|
use crate::App;
|
||||||
|
|
||||||
|
pub(super) fn draw_add_movie_search_popup<B: Backend>(
|
||||||
|
f: &mut Frame<'_, B>,
|
||||||
|
app: &mut App,
|
||||||
|
area: Rect,
|
||||||
|
) {
|
||||||
|
if let Route::Radarr(active_radarr_block) = app.get_current_route().clone() {
|
||||||
|
match active_radarr_block {
|
||||||
|
ActiveRadarrBlock::AddMovieSearchInput | ActiveRadarrBlock::AddMovieSearchResults => {
|
||||||
|
draw_add_movie_search(f, app, area);
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::AddMoviePrompt => {
|
||||||
|
draw_medium_popup_over(
|
||||||
|
f,
|
||||||
|
app,
|
||||||
|
area,
|
||||||
|
draw_add_movie_search,
|
||||||
|
draw_add_movie_confirmation_prompt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_add_movie_search<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect) {
|
||||||
|
let chunks = vertical_chunks_with_margin(
|
||||||
|
vec![
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Min(0),
|
||||||
|
Constraint::Length(3),
|
||||||
|
],
|
||||||
|
area,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
let block_content = app.data.radarr_data.search.as_str();
|
||||||
|
|
||||||
|
let search_paragraph = Paragraph::new(Text::from(block_content))
|
||||||
|
.style(style_default())
|
||||||
|
.block(title_block_centered(" Add Movie "));
|
||||||
|
|
||||||
|
if let Route::Radarr(active_radarr_block) = app.get_current_route().clone() {
|
||||||
|
match active_radarr_block {
|
||||||
|
ActiveRadarrBlock::AddMovieSearchInput => {
|
||||||
|
show_cursor(f, chunks[0], block_content);
|
||||||
|
f.render_widget(layout_block(), chunks[1]);
|
||||||
|
|
||||||
|
let mut help_text = Text::from("<esc> close");
|
||||||
|
help_text.patch_style(style_help());
|
||||||
|
let help_paragraph = Paragraph::new(help_text)
|
||||||
|
.block(borderless_block())
|
||||||
|
.alignment(Alignment::Center);
|
||||||
|
f.render_widget(help_paragraph, chunks[2]);
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::AddMovieSearchResults | ActiveRadarrBlock::AddMoviePrompt => {
|
||||||
|
let mut help_text = Text::from("<esc> edit search");
|
||||||
|
help_text.patch_style(style_help());
|
||||||
|
let help_paragraph = Paragraph::new(help_text)
|
||||||
|
.block(borderless_block())
|
||||||
|
.alignment(Alignment::Center);
|
||||||
|
f.render_widget(help_paragraph, chunks[2]);
|
||||||
|
|
||||||
|
draw_table(
|
||||||
|
f,
|
||||||
|
chunks[1],
|
||||||
|
layout_block(),
|
||||||
|
TableProps {
|
||||||
|
content: &mut app.data.radarr_data.add_searched_movies,
|
||||||
|
table_headers: vec![
|
||||||
|
"Title",
|
||||||
|
"Year",
|
||||||
|
"Runtime",
|
||||||
|
"IMDB Rating",
|
||||||
|
"Rotten Tomatoes Rating",
|
||||||
|
"Genres",
|
||||||
|
],
|
||||||
|
constraints: vec![
|
||||||
|
Constraint::Percentage(20),
|
||||||
|
Constraint::Percentage(8),
|
||||||
|
Constraint::Percentage(10),
|
||||||
|
Constraint::Percentage(10),
|
||||||
|
Constraint::Percentage(18),
|
||||||
|
Constraint::Percentage(30),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|movie| {
|
||||||
|
let (hours, minutes) = convert_runtime(movie.runtime.as_u64().unwrap());
|
||||||
|
let imdb_rating = movie
|
||||||
|
.ratings
|
||||||
|
.imdb
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.value
|
||||||
|
.as_f64()
|
||||||
|
.unwrap();
|
||||||
|
let rotten_tomatoes_rating = movie
|
||||||
|
.ratings
|
||||||
|
.rotten_tomatoes
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.value
|
||||||
|
.as_u64()
|
||||||
|
.unwrap();
|
||||||
|
let imdb_rating = if imdb_rating == 0.0 {
|
||||||
|
String::default()
|
||||||
|
} else {
|
||||||
|
format!("{:.1}", imdb_rating)
|
||||||
|
};
|
||||||
|
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
|
||||||
|
String::default()
|
||||||
|
} else {
|
||||||
|
format!("{}%", rotten_tomatoes_rating)
|
||||||
|
};
|
||||||
|
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(movie.title.to_owned()),
|
||||||
|
Cell::from(movie.year.as_u64().unwrap().to_string()),
|
||||||
|
Cell::from(format!("{}h {}m", hours, minutes)),
|
||||||
|
Cell::from(imdb_rating),
|
||||||
|
Cell::from(rotten_tomatoes_rating),
|
||||||
|
Cell::from(movie.genres.join(", ")),
|
||||||
|
])
|
||||||
|
.style(style_primary())
|
||||||
|
},
|
||||||
|
app.is_loading,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f.render_widget(search_paragraph, chunks[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_add_movie_confirmation_prompt<B: Backend>(
|
||||||
|
f: &mut Frame<'_, B>,
|
||||||
|
app: &mut App,
|
||||||
|
prompt_area: Rect,
|
||||||
|
) {
|
||||||
|
draw_prompt_box(
|
||||||
|
f,
|
||||||
|
prompt_area,
|
||||||
|
" Confirm Add Movie? ",
|
||||||
|
format!(
|
||||||
|
"{}:\n\n{}",
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.add_searched_movies
|
||||||
|
.current_selection()
|
||||||
|
.title,
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.add_searched_movies
|
||||||
|
.current_selection()
|
||||||
|
.overview
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&app.data.radarr_data.prompt_confirm,
|
||||||
|
);
|
||||||
|
}
|
||||||
+13
-2
@@ -14,12 +14,13 @@ use crate::app::App;
|
|||||||
use crate::logos::RADARR_LOGO;
|
use crate::logos::RADARR_LOGO;
|
||||||
use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie};
|
use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie};
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
|
use crate::ui::radarr_ui::add_movie_ui::draw_add_movie_search_popup;
|
||||||
use crate::ui::radarr_ui::collection_details_ui::draw_collection_details_popup;
|
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::radarr_ui::movie_details_ui::draw_movie_info;
|
||||||
use crate::ui::utils::{
|
use crate::ui::utils::{
|
||||||
borderless_block, horizontal_chunks, layout_block, layout_block_top_border,
|
borderless_block, horizontal_chunks, layout_block, layout_block_top_border,
|
||||||
line_gauge_with_label, line_gauge_with_title, show_cursor, style_bold, style_default,
|
line_gauge_with_label, line_gauge_with_title, show_cursor, style_bold, style_default,
|
||||||
style_failure, style_primary, style_success, style_warning, title_block,
|
style_failure, style_primary, style_success, style_warning, title_block, title_block_centered,
|
||||||
vertical_chunks_with_margin,
|
vertical_chunks_with_margin,
|
||||||
};
|
};
|
||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
@@ -28,6 +29,7 @@ use crate::ui::{
|
|||||||
};
|
};
|
||||||
use crate::utils::{convert_runtime, convert_to_gb};
|
use crate::utils::{convert_runtime, convert_to_gb};
|
||||||
|
|
||||||
|
mod add_movie_ui;
|
||||||
mod collection_details_ui;
|
mod collection_details_ui;
|
||||||
mod movie_details_ui;
|
mod movie_details_ui;
|
||||||
|
|
||||||
@@ -60,6 +62,15 @@ pub(super) fn draw_radarr_ui<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, ar
|
|||||||
| ActiveRadarrBlock::Crew => {
|
| ActiveRadarrBlock::Crew => {
|
||||||
draw_large_popup_over(f, app, content_rect, draw_library, draw_movie_info)
|
draw_large_popup_over(f, app, content_rect, draw_library, draw_movie_info)
|
||||||
}
|
}
|
||||||
|
ActiveRadarrBlock::AddMovieSearchInput
|
||||||
|
| ActiveRadarrBlock::AddMovieSearchResults
|
||||||
|
| ActiveRadarrBlock::AddMoviePrompt => draw_large_popup_over(
|
||||||
|
f,
|
||||||
|
app,
|
||||||
|
content_rect,
|
||||||
|
draw_library,
|
||||||
|
draw_add_movie_search_popup,
|
||||||
|
),
|
||||||
ActiveRadarrBlock::CollectionDetails | ActiveRadarrBlock::ViewMovieOverview => {
|
ActiveRadarrBlock::CollectionDetails | ActiveRadarrBlock::ViewMovieOverview => {
|
||||||
draw_large_popup_over(
|
draw_large_popup_over(
|
||||||
f,
|
f,
|
||||||
@@ -200,7 +211,7 @@ fn draw_search_box<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, area: Rect)
|
|||||||
|
|
||||||
let input = Paragraph::new(block_content)
|
let input = Paragraph::new(block_content)
|
||||||
.style(style_default())
|
.style(style_default())
|
||||||
.block(title_block(block_title));
|
.block(title_block_centered(block_title));
|
||||||
show_cursor(f, chunks[0], block_content);
|
show_cursor(f, chunks[0], block_content);
|
||||||
|
|
||||||
f.render_widget(input, chunks[0]);
|
f.render_widget(input, chunks[0]);
|
||||||
|
|||||||
+5
-1
@@ -1,5 +1,5 @@
|
|||||||
use tui::backend::Backend;
|
use tui::backend::Backend;
|
||||||
use tui::layout::{Constraint, Direction, Layout, Rect};
|
use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
|
||||||
use tui::style::{Color, Modifier, Style};
|
use tui::style::{Color, Modifier, Style};
|
||||||
use tui::text::{Span, Spans};
|
use tui::text::{Span, Spans};
|
||||||
use tui::widgets::{Block, Borders, LineGauge};
|
use tui::widgets::{Block, Borders, LineGauge};
|
||||||
@@ -152,6 +152,10 @@ pub fn title_block(title: &str) -> Block<'_> {
|
|||||||
layout_block_with_title(title_style(title))
|
layout_block_with_title(title_style(title))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn title_block_centered(title: &str) -> Block<'_> {
|
||||||
|
title_block(title).title_alignment(Alignment::Center)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn logo_block<'a>() -> Block<'a> {
|
pub fn logo_block<'a>() -> Block<'a> {
|
||||||
layout_block().title(Span::styled(
|
layout_block().title(Span::styled(
|
||||||
" Managarr - A Servarr management TUI ",
|
" Managarr - A Servarr management TUI ",
|
||||||
|
|||||||
Reference in New Issue
Block a user