Merge pull request #11 from Dark-Alex-17/rc/v0.2.0-address-community-comments
Rc/v0.2.0 address community comments
This commit is contained in:
Generated
+1
-1
@@ -1148,7 +1148,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "managarr"
|
||||
version = "0.1.5"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "managarr"
|
||||
version = "0.1.5"
|
||||
version = "0.2.0"
|
||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||
description = "A TUI and CLI to manage your Servarrs"
|
||||
keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui"]
|
||||
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
|
||||
documentation = "https://github.com/Dark-Alex-17/managarr"
|
||||
repository = "https://github.com/Dark-Alex-17/managarr"
|
||||
homepage = "https://github.com/Dark-Alex-17/managarr"
|
||||
@@ -38,7 +38,7 @@ tokio = { version = "1.36.0", features = ["full"] }
|
||||
tokio-util = "0.7.8"
|
||||
ratatui = { version = "0.28.0", features = ["all-widgets"] }
|
||||
urlencoding = "2.1.2"
|
||||
clap = { version = "4.5.20", features = ["derive", "cargo"] }
|
||||
clap = { version = "4.5.20", features = ["derive", "cargo", "env"] }
|
||||
clap_complete = "4.5.33"
|
||||
itertools = "0.13.0"
|
||||
ctrlc = "3.4.5"
|
||||
|
||||
@@ -113,7 +113,7 @@ To see all available commands, simply run `managarr --help`:
|
||||
|
||||
```shell
|
||||
$ managarr --help
|
||||
managarr 0.1.3
|
||||
managarr 0.1.5
|
||||
Alex Clarke <alex.j.tusa@gmail.com>
|
||||
|
||||
A TUI and CLI to manage your Servarrs
|
||||
@@ -127,7 +127,6 @@ Commands:
|
||||
|
||||
Options:
|
||||
--config <CONFIG> The Managarr configuration file to use
|
||||
--disable-terminal-size-checks Disable the terminal size checks
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
@@ -159,7 +158,6 @@ Commands:
|
||||
|
||||
Options:
|
||||
--config <CONFIG> The Managarr configuration file to use
|
||||
--disable-terminal-size-checks Disable the terminal size checks
|
||||
-h, --help Print help
|
||||
```
|
||||
|
||||
@@ -244,6 +242,13 @@ tautulli:
|
||||
use_ssl: false
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
Managarr supports using environment variables on startup so you don't have to always specify certain flags:
|
||||
|
||||
| Variable | Description | Equivalent Flag |
|
||||
| --------------------------------------- | -------------------------------- | -------------------------------- |
|
||||
| `MANAGARR_CONFIG_FILE` | Set the path to the config file | `--config` |
|
||||
|
||||
## Track My Progress for the Beta release (With Sonarr Support!)
|
||||
Progress for the beta release can be followed on my [Wekan Board](https://wekan.alexjclarke.com/b/dHoGjBb44MHM9HSv4/managarr)
|
||||
with all items tagged `Beta`.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
coverage:
|
||||
range: "80..100"
|
||||
|
||||
ignore:
|
||||
- "**/*_tests.rs"
|
||||
- "src/ui"
|
||||
@@ -33,6 +33,7 @@ generate_keybindings! {
|
||||
tab,
|
||||
delete,
|
||||
submit,
|
||||
confirm,
|
||||
quit,
|
||||
esc
|
||||
}
|
||||
@@ -140,6 +141,10 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
|
||||
key: Key::Enter,
|
||||
desc: "submit",
|
||||
},
|
||||
confirm: KeyBinding {
|
||||
key: Key::Ctrl('s'),
|
||||
desc: "submit",
|
||||
},
|
||||
quit: KeyBinding {
|
||||
key: Key::Char('q'),
|
||||
desc: "quit",
|
||||
|
||||
@@ -31,6 +31,7 @@ mod test {
|
||||
#[case(DEFAULT_KEYBINDINGS.tab, Key::Tab, "tab")]
|
||||
#[case(DEFAULT_KEYBINDINGS.delete, Key::Delete, "delete")]
|
||||
#[case(DEFAULT_KEYBINDINGS.submit, Key::Enter, "submit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.confirm, Key::Ctrl('s'), "submit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.quit, Key::Char('q'), "quit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.esc, Key::Esc, "close")]
|
||||
fn test_default_key_bindings_and_descriptions(
|
||||
|
||||
@@ -120,6 +120,11 @@ pub static ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||
(DEFAULT_KEYBINDINGS.esc, "edit search"),
|
||||
];
|
||||
|
||||
pub static CONFIRMATION_PROMPT_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||
(DEFAULT_KEYBINDINGS.confirm, "submit"),
|
||||
(DEFAULT_KEYBINDINGS.esc, "cancel"),
|
||||
];
|
||||
|
||||
pub static SYSTEM_TASKS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||
(DEFAULT_KEYBINDINGS.submit, "start task"),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
|
||||
@@ -5,8 +5,8 @@ mod tests {
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::radarr::radarr_context_clues::{
|
||||
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES,
|
||||
COLLECTION_DETAILS_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||
LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||
COLLECTION_DETAILS_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES,
|
||||
INDEXERS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES,
|
||||
MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
||||
SYSTEM_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES,
|
||||
};
|
||||
@@ -349,6 +349,22 @@ mod tests {
|
||||
assert_eq!(add_movie_search_results_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_confirmation_prompt_context_clues() {
|
||||
let mut confirmation_prompt_context_clues_iter = CONFIRMATION_PROMPT_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.confirm);
|
||||
assert_str_eq!(*description, "submit");
|
||||
|
||||
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
||||
assert_str_eq!(*description, "cancel");
|
||||
assert_eq!(confirmation_prompt_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_tasks_context_clues() {
|
||||
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
|
||||
|
||||
@@ -584,6 +584,9 @@ mod tests {
|
||||
|
||||
mod test_handle_key_char {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -716,6 +719,43 @@ mod tests {
|
||||
assert!(app.data.radarr_data.blocklist.sort.is_none());
|
||||
assert!(!app.data.radarr_data.blocklist.sort_asc);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt,
|
||||
RadarrEvent::DeleteBlocklistItem(None)
|
||||
)]
|
||||
#[case(
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt,
|
||||
RadarrEvent::ClearBlocklist
|
||||
)]
|
||||
fn test_blocklist_prompt_confirm(
|
||||
#[case] base_route: ActiveRadarrBlock,
|
||||
#[case] prompt_block: ActiveRadarrBlock,
|
||||
#[case] expected_action: RadarrEvent,
|
||||
) {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.blocklist.set_items(blocklist_vec());
|
||||
app.push_navigation_stack(base_route.into());
|
||||
app.push_navigation_stack(prompt_block.into());
|
||||
|
||||
BlocklistHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&prompt_block,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(expected_action)
|
||||
);
|
||||
assert_eq!(app.get_current_route(), &base_route.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -182,8 +182,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
let key = self.key;
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::Blocklist {
|
||||
match self.key {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Blocklist => match self.key {
|
||||
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
@@ -204,8 +204,26 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
.push_navigation_stack(ActiveRadarrBlock::BlocklistSortPrompt.into());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
|
||||
if *key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::DeleteBlocklistItem(None));
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
|
||||
if *key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::ClearBlocklist);
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1040,6 +1040,7 @@ mod tests {
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
RadarrData, EDIT_COLLECTION_SELECTION_BLOCKS,
|
||||
};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::test_edit_collection_key;
|
||||
|
||||
use super::*;
|
||||
@@ -1509,6 +1510,36 @@ mod tests {
|
||||
assert!(app.data.radarr_data.collections.sort.is_none());
|
||||
assert!(!app.data.radarr_data.collections.sort_asc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_all_collections_prompt_confirm_confirm() {
|
||||
let mut app = App::default();
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.collections
|
||||
.set_items(vec![Collection::default()]);
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
|
||||
|
||||
CollectionsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::UpdateAllCollectionsPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::UpdateCollections)
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
|
||||
@@ -291,7 +291,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
let key = self.key;
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::EditCollectionRootFolderPathInput {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::EditCollectionRootFolderPathInput => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
key,
|
||||
@@ -305,5 +306,19 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
||||
.path
|
||||
)
|
||||
}
|
||||
ActiveRadarrBlock::EditCollectionPrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== &ActiveRadarrBlock::EditCollectionConfirmPrompt
|
||||
&& *key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(None));
|
||||
self.app.should_refresh = true;
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -871,7 +871,15 @@ mod tests {
|
||||
|
||||
mod test_handle_key_char {
|
||||
use super::*;
|
||||
use crate::models::servarr_data::radarr::modals::EditCollectionModal;
|
||||
use crate::{
|
||||
models::{
|
||||
servarr_data::radarr::{
|
||||
modals::EditCollectionModal, radarr_data::EDIT_COLLECTION_SELECTION_BLOCKS,
|
||||
},
|
||||
BlockSelectionState,
|
||||
},
|
||||
network::radarr_network::RadarrEvent,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_edit_collection_root_folder_path_input_backspace() {
|
||||
@@ -927,6 +935,39 @@ mod tests {
|
||||
"h"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_collection_confirm_prompt_prompt_confirmation_confirm() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Collections.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
|
||||
app.data.radarr_data.selected_block =
|
||||
BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS);
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.selected_block
|
||||
.set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1);
|
||||
|
||||
EditCollectionHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::EditCollectionPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::EditCollection(None))
|
||||
);
|
||||
assert!(app.should_refresh);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -385,6 +385,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
|
||||
if *key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +349,9 @@ mod tests {
|
||||
|
||||
mod test_handle_key_char {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -450,6 +453,47 @@ mod tests {
|
||||
);
|
||||
assert!(!app.should_refresh);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
ActiveRadarrBlock::Downloads,
|
||||
ActiveRadarrBlock::DeleteDownloadPrompt,
|
||||
RadarrEvent::DeleteDownload(None)
|
||||
)]
|
||||
#[case(
|
||||
ActiveRadarrBlock::Downloads,
|
||||
ActiveRadarrBlock::UpdateDownloadsPrompt,
|
||||
RadarrEvent::UpdateDownloads
|
||||
)]
|
||||
fn test_downloads_prompt_confirm_submit(
|
||||
#[case] base_route: ActiveRadarrBlock,
|
||||
#[case] prompt_block: ActiveRadarrBlock,
|
||||
#[case] expected_action: RadarrEvent,
|
||||
) {
|
||||
let mut app = App::default();
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.downloads
|
||||
.set_items(vec![DownloadRecord::default()]);
|
||||
app.push_navigation_stack(base_route.into());
|
||||
app.push_navigation_stack(prompt_block.into());
|
||||
|
||||
DownloadsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&prompt_block,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(expected_action)
|
||||
);
|
||||
assert_eq!(app.get_current_route(), &base_route.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -119,8 +119,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
let key = self.key;
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::Downloads {
|
||||
match self.key {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Downloads => match self.key {
|
||||
_ if *key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
self
|
||||
.app
|
||||
@@ -130,7 +130,24 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
ActiveRadarrBlock::DeleteDownloadPrompt => {
|
||||
if *key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteDownload(None));
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::UpdateDownloadsPrompt => {
|
||||
if *key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads);
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,6 +429,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
|
||||
.tags
|
||||
);
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerPrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== &ActiveRadarrBlock::EditIndexerConfirmPrompt
|
||||
&& *self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditIndexer(None));
|
||||
self.app.should_refresh = true;
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1282,6 +1282,9 @@ mod tests {
|
||||
mod test_handle_key_char {
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::radarr::modals::EditIndexerModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use pretty_assertions::assert_str_eq;
|
||||
|
||||
use super::*;
|
||||
@@ -1560,6 +1563,37 @@ mod tests {
|
||||
"h"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_prompt_prompt_confirmation_confirm() {
|
||||
let mut app = App::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::EditIndexerPrompt.into());
|
||||
app.data.radarr_data.selected_block =
|
||||
BlockSelectionState::new(&EDIT_INDEXER_TORRENT_SELECTION_BLOCKS);
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.selected_block
|
||||
.set_index(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1);
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::EditIndexerPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
|
||||
assert!(app.data.radarr_data.edit_indexer_modal.is_some());
|
||||
assert!(app.should_refresh);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::EditIndexer(None))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -241,7 +241,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
|
||||
}
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
self.key,
|
||||
@@ -255,5 +256,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
|
||||
.whitelisted_hardcoded_subs
|
||||
)
|
||||
}
|
||||
ActiveRadarrBlock::AllIndexerSettingsPrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== &ActiveRadarrBlock::IndexerSettingsConfirmPrompt
|
||||
&& *self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::EditAllIndexerSettings(None));
|
||||
self.app.should_refresh = true;
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -851,7 +851,13 @@ mod tests {
|
||||
mod test_handle_key_char {
|
||||
use pretty_assertions::assert_str_eq;
|
||||
|
||||
use crate::models::radarr_models::IndexerSettings;
|
||||
use crate::{
|
||||
models::{
|
||||
radarr_models::IndexerSettings,
|
||||
servarr_data::radarr::radarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, BlockSelectionState,
|
||||
},
|
||||
network::radarr_network::RadarrEvent,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -909,6 +915,37 @@ mod tests {
|
||||
"h"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_settings_prompt_prompt_confirmation_confirm() {
|
||||
let mut app = App::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
|
||||
app.data.radarr_data.selected_block =
|
||||
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS);
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.selected_block
|
||||
.set_index(INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1);
|
||||
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
|
||||
|
||||
IndexerSettingsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::AllIndexerSettingsPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::EditAllIndexerSettings(None))
|
||||
);
|
||||
assert!(app.data.radarr_data.indexer_settings.is_some());
|
||||
assert!(app.should_refresh);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -464,7 +464,10 @@ mod tests {
|
||||
mod test_handle_key_char {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::models::servarr_data::radarr::radarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS;
|
||||
use crate::{
|
||||
models::servarr_data::radarr::radarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS,
|
||||
network::radarr_network::RadarrEvent,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -696,6 +699,33 @@ mod tests {
|
||||
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_indexer_prompt_confirm() {
|
||||
let mut app = App::default();
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.indexers
|
||||
.set_items(vec![Indexer::default()]);
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into());
|
||||
|
||||
IndexersHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::DeleteIndexerPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::DeleteIndexer(None))
|
||||
);
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
|
||||
@@ -166,8 +166,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
let key = self.key;
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::Indexers {
|
||||
match self.key {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Indexers => match self.key {
|
||||
_ if *key == DEFAULT_KEYBINDINGS.add.key => {
|
||||
self
|
||||
.app
|
||||
@@ -194,7 +194,16 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
|
||||
BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
ActiveRadarrBlock::DeleteIndexerPrompt => {
|
||||
if *key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer(None));
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,6 +461,16 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
|
||||
.tags
|
||||
)
|
||||
}
|
||||
ActiveRadarrBlock::AddMoviePrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== &ActiveRadarrBlock::AddMovieConfirmPrompt
|
||||
&& *key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddMovie(None));
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1494,7 +1494,13 @@ mod tests {
|
||||
|
||||
mod test_handle_key_char {
|
||||
use super::*;
|
||||
use crate::models::servarr_data::radarr::modals::AddMovieModal;
|
||||
use crate::{
|
||||
models::{
|
||||
servarr_data::radarr::{modals::AddMovieModal, radarr_data::ADD_MOVIE_SELECTION_BLOCKS},
|
||||
BlockSelectionState,
|
||||
},
|
||||
network::radarr_network::RadarrEvent,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_add_movie_search_input_backspace() {
|
||||
@@ -1588,6 +1594,35 @@ mod tests {
|
||||
"h"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_movie_confirm_prompt_prompt_confirmation_confirm() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into());
|
||||
app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS);
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.selected_block
|
||||
.set_index(ADD_MOVIE_SELECTION_BLOCKS.len() - 1);
|
||||
|
||||
AddMovieHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::AddMoviePrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::AddMovie(None))
|
||||
);
|
||||
assert!(app.data.radarr_data.add_movie_modal.is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
@@ -100,5 +101,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_char_key_event(&mut self) {}
|
||||
fn handle_char_key_event(&mut self) {
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::DeleteMoviePrompt
|
||||
&& self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== &ActiveRadarrBlock::DeleteMovieConfirmPrompt
|
||||
&& *self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteMovie(None));
|
||||
self.app.should_refresh = true;
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +250,51 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_key_char {
|
||||
use crate::{
|
||||
models::{
|
||||
servarr_data::radarr::radarr_data::DELETE_MOVIE_SELECTION_BLOCKS, BlockSelectionState,
|
||||
},
|
||||
network::radarr_network::RadarrEvent,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_delete_movie_confirm_prompt_prompt_confirm() {
|
||||
let mut app = App::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into());
|
||||
app.data.radarr_data.delete_movie_files = true;
|
||||
app.data.radarr_data.add_list_exclusion = true;
|
||||
app.data.radarr_data.selected_block =
|
||||
BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS);
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.selected_block
|
||||
.set_index(DELETE_MOVIE_SELECTION_BLOCKS.len() - 1);
|
||||
|
||||
DeleteMovieHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::DeleteMoviePrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::DeleteMovie(None))
|
||||
);
|
||||
assert!(app.should_refresh);
|
||||
assert!(app.data.radarr_data.prompt_confirm);
|
||||
assert!(app.data.radarr_data.delete_movie_files);
|
||||
assert!(app.data.radarr_data.add_list_exclusion);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_movie_handler_accepts() {
|
||||
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
|
||||
|
||||
@@ -327,6 +327,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
|
||||
.tags
|
||||
)
|
||||
}
|
||||
ActiveRadarrBlock::EditMoviePrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== &ActiveRadarrBlock::EditMovieConfirmPrompt
|
||||
&& *key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditMovie(None));
|
||||
self.app.should_refresh = true;
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,7 +940,16 @@ mod tests {
|
||||
|
||||
mod test_handle_key_char {
|
||||
use super::*;
|
||||
use crate::models::servarr_data::radarr::modals::EditMovieModal;
|
||||
use crate::{
|
||||
models::{
|
||||
servarr_data::radarr::{
|
||||
modals::EditMovieModal,
|
||||
radarr_data::{EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS},
|
||||
},
|
||||
BlockSelectionState,
|
||||
},
|
||||
network::radarr_network::RadarrEvent,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_edit_movie_path_input_backspace() {
|
||||
@@ -1051,6 +1060,36 @@ mod tests {
|
||||
"h"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_movie_confirm_prompt_prompt_confirm() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into());
|
||||
app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS);
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.selected_block
|
||||
.set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1);
|
||||
|
||||
EditMovieHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::EditMoviePrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::EditMovie(None))
|
||||
);
|
||||
assert!(app.data.radarr_data.edit_movie_modal.is_some());
|
||||
assert!(app.should_refresh);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -996,6 +996,7 @@ mod tests {
|
||||
RadarrData, EDIT_MOVIE_SELECTION_BLOCKS,
|
||||
};
|
||||
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::test_edit_movie_key;
|
||||
|
||||
use super::*;
|
||||
@@ -1452,6 +1453,33 @@ mod tests {
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||
assert!(app.data.radarr_data.movies.sort.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_all_movies_prompt_confirm() {
|
||||
let mut app = App::default();
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.set_items(vec![Movie::default()]);
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into());
|
||||
|
||||
LibraryHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::UpdateAllMoviesPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::UpdateAllMovies)
|
||||
);
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
|
||||
@@ -386,6 +386,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|
||||
self.app.data.radarr_data.movies.filter.as_mut().unwrap()
|
||||
)
|
||||
}
|
||||
ActiveRadarrBlock::UpdateAllMoviesPrompt => {
|
||||
if *key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies);
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,6 +464,32 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::AutomaticallySearchMoviePrompt => {
|
||||
if *key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::TriggerAutomaticSearch(None));
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::UpdateAndScanPrompt => {
|
||||
if *key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAndScan(None));
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::ManualSearchConfirmPrompt => {
|
||||
if *key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::DownloadRelease(None));
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1468,6 +1468,7 @@ mod tests {
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
RadarrData, EDIT_MOVIE_SELECTION_BLOCKS,
|
||||
};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::test_edit_movie_key;
|
||||
|
||||
use super::*;
|
||||
@@ -1780,6 +1781,50 @@ mod tests {
|
||||
assert_eq!(app.get_current_route(), &active_radarr_block.into());
|
||||
assert!(app.is_routing);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
ActiveRadarrBlock::AutomaticallySearchMoviePrompt,
|
||||
RadarrEvent::TriggerAutomaticSearch(None)
|
||||
)]
|
||||
#[case(
|
||||
ActiveRadarrBlock::UpdateAndScanPrompt,
|
||||
RadarrEvent::UpdateAndScan(None)
|
||||
)]
|
||||
#[case(
|
||||
ActiveRadarrBlock::ManualSearchConfirmPrompt,
|
||||
RadarrEvent::DownloadRelease(None)
|
||||
)]
|
||||
fn test_movie_info_prompt_confirm(
|
||||
#[case] prompt_block: ActiveRadarrBlock,
|
||||
#[case] expected_action: RadarrEvent,
|
||||
) {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal {
|
||||
movie_details: ScrollableText::with_string("test".to_owned()),
|
||||
..MovieDetailsModal::default()
|
||||
});
|
||||
app.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into());
|
||||
app.push_navigation_stack(prompt_block.into());
|
||||
|
||||
MovieDetailsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&prompt_block,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::MovieDetails.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(expected_action)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -180,6 +180,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
|
||||
self.app.data.radarr_data.edit_root_folder.as_mut().unwrap()
|
||||
)
|
||||
}
|
||||
ActiveRadarrBlock::DeleteRootFolderPrompt => {
|
||||
if *key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::DeleteRootFolder(None));
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,6 +554,8 @@ mod tests {
|
||||
mod test_handle_key_char {
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -706,6 +708,36 @@ mod tests {
|
||||
"h"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_root_folder_prompt_confirm() {
|
||||
let mut app = App::default();
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.root_folders
|
||||
.set_items(vec![RootFolder::default()]);
|
||||
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into());
|
||||
|
||||
RootFoldersHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::DeleteRootFolderPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::DeleteRootFolder(None))
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::RootFolders.into()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -168,5 +168,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
|
||||
{
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::SystemTaskStartConfirmPrompt
|
||||
&& *self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::StartTask(None));
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -858,6 +858,8 @@ mod tests {
|
||||
mod test_handle_key_char {
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
@@ -912,6 +914,32 @@ mod tests {
|
||||
assert_eq!(app.get_current_route(), &active_radarr_block.into());
|
||||
assert!(!app.should_refresh);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_tasks_start_task_prompt_confirm() {
|
||||
let mut app = App::default();
|
||||
app.data.radarr_data.updates = ScrollableText::with_string("Test".to_owned());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::SystemTaskStartConfirmPrompt.into());
|
||||
|
||||
SystemDetailsHandler::with(
|
||||
&DEFAULT_KEYBINDINGS.confirm.key,
|
||||
&mut app,
|
||||
&ActiveRadarrBlock::SystemTaskStartConfirmPrompt,
|
||||
&None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::StartTask(None))
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::SystemTasks.into()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+6
-23
@@ -18,7 +18,7 @@ use clap_complete::generate;
|
||||
use colored::Colorize;
|
||||
use crossterm::execute;
|
||||
use crossterm::terminal::{
|
||||
disable_raw_mode, enable_raw_mode, size, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
};
|
||||
use log::error;
|
||||
use network::NetworkTrait;
|
||||
@@ -46,9 +46,6 @@ mod network;
|
||||
mod ui;
|
||||
mod utils;
|
||||
|
||||
static MIN_TERM_WIDTH: u16 = 205;
|
||||
static MIN_TERM_HEIGHT: u16 = 40;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
name = crate_name!(),
|
||||
@@ -71,11 +68,10 @@ struct Cli {
|
||||
long,
|
||||
global = true,
|
||||
value_parser,
|
||||
env = "MANAGARR_CONFIG_FILE",
|
||||
help = "The Managarr configuration file to use"
|
||||
)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long, global = true, help = "Disable the terminal size checks")]
|
||||
disable_terminal_size_checks: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -131,7 +127,7 @@ async fn main() -> Result<()> {
|
||||
std::thread::spawn(move || {
|
||||
start_networking(sync_network_rx, &app_nw, cancellation_token, reqwest_client)
|
||||
});
|
||||
start_ui(&app, !args.disable_terminal_size_checks).await?;
|
||||
start_ui(&app).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,20 +150,7 @@ async fn start_networking(
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_ui(app: &Arc<Mutex<App<'_>>>, check_terminal_size: bool) -> Result<()> {
|
||||
if check_terminal_size {
|
||||
let (width, height) = size()?;
|
||||
if width < MIN_TERM_WIDTH || height < MIN_TERM_HEIGHT {
|
||||
return Err(anyhow!(
|
||||
"Terminal too small. Minimum size required: {}x{}; current terminal size: {}x{}",
|
||||
MIN_TERM_WIDTH,
|
||||
MIN_TERM_HEIGHT,
|
||||
width,
|
||||
height
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_ui(app: &Arc<Mutex<App<'_>>>) -> Result<()> {
|
||||
let mut stdout = io::stdout();
|
||||
enable_raw_mode()?;
|
||||
|
||||
@@ -247,7 +230,7 @@ fn build_network_client(config: &AppConfig) -> Client {
|
||||
let mut client_builder = Client::builder();
|
||||
|
||||
if config.radarr.use_ssl {
|
||||
let cert = add_cert_to_builder(config.radarr.ssl_cert_path.clone(), "Radarr");
|
||||
let cert = create_cert(config.radarr.ssl_cert_path.clone(), "Radarr");
|
||||
client_builder = client_builder.add_root_certificate(cert);
|
||||
}
|
||||
|
||||
@@ -261,7 +244,7 @@ fn build_network_client(config: &AppConfig) -> Client {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_cert_to_builder(cert_path: Option<String>, servarr_name: &str) -> Certificate {
|
||||
fn create_cert(cert_path: Option<String>, servarr_name: &str) -> Certificate {
|
||||
let err = |error: String| {
|
||||
error!("{}", error);
|
||||
eprintln!("error: {}", error.red());
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use ratatui::layout::{Alignment, Constraint, Flex, Layout, Rect};
|
||||
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
||||
use ratatui::style::{Style, Stylize};
|
||||
use ratatui::text::{Line, Text};
|
||||
use ratatui::widgets::Clear;
|
||||
@@ -83,7 +83,7 @@ fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
.select(app.server_tabs.index);
|
||||
let help = Paragraph::new(help_text)
|
||||
.block(borderless_block())
|
||||
.alignment(Alignment::Right);
|
||||
.right_aligned();
|
||||
|
||||
f.render_widget(tabs, tabs_area);
|
||||
f.render_widget(help, help_area);
|
||||
@@ -170,7 +170,7 @@ fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -
|
||||
.select(tab_state.index);
|
||||
let help = Paragraph::new(Text::from(tab_state.get_active_tab_help().help()))
|
||||
.block(borderless_block())
|
||||
.alignment(Alignment::Right);
|
||||
.right_aligned();
|
||||
|
||||
f.render_widget(tabs, tabs_area);
|
||||
f.render_widget(help, help_area);
|
||||
@@ -197,7 +197,7 @@ pub fn draw_input_box_popup(
|
||||
|
||||
let help = Paragraph::new("<esc> cancel")
|
||||
.help()
|
||||
.alignment(Alignment::Center)
|
||||
.centered()
|
||||
.block(borderless_block());
|
||||
f.render_widget(help, help_area);
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
.wrap(Wrap { trim: false });
|
||||
let help_paragraph = Paragraph::new(help_text)
|
||||
.block(borderless_block())
|
||||
.alignment(Alignment::Center);
|
||||
.centered();
|
||||
|
||||
f.render_widget(paragraph, paragraph_area);
|
||||
f.render_widget(help_paragraph, help_area);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::widgets::ListItem;
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::{ListItem, Paragraph};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::context_clues::build_context_clue_string;
|
||||
use crate::app::radarr::radarr_context_clues::CONFIRMATION_PROMPT_CONTEXT_CLUES;
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::radarr::modals::EditCollectionModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
@@ -110,7 +113,7 @@ fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>
|
||||
let selected_minimum_availability = minimum_availability_list.current_selection();
|
||||
let selected_quality_profile = quality_profile_list.current_selection();
|
||||
|
||||
let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, root_folder_area, search_on_add_area, _, buttons_area] =
|
||||
let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, root_folder_area, search_on_add_area, _, buttons_area, help_area] =
|
||||
Layout::vertical([
|
||||
Constraint::Length(6),
|
||||
Constraint::Length(3),
|
||||
@@ -118,8 +121,9 @@ fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Fill(0),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.margin(1)
|
||||
.areas(area);
|
||||
@@ -127,6 +131,8 @@ fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>
|
||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.areas(buttons_area);
|
||||
|
||||
let help_text = Text::from(build_context_clue_string(&CONFIRMATION_PROMPT_CONTEXT_CLUES).help());
|
||||
let help_paragraph = Paragraph::new(help_text).centered();
|
||||
let prompt_paragraph = layout_paragraph_borderless(&collection_overview);
|
||||
let monitored_checkbox = Checkbox::new("Monitored")
|
||||
.highlighted(selected_block == &ActiveRadarrBlock::EditCollectionToggleMonitored)
|
||||
@@ -169,6 +175,7 @@ fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>
|
||||
f.render_widget(search_on_add_checkbox, search_on_add_area);
|
||||
f.render_widget(save_button, save_area);
|
||||
f.render_widget(cancel_button, cancel_area);
|
||||
f.render_widget(help_paragraph, help_area);
|
||||
}
|
||||
|
||||
fn draw_edit_collection_select_minimum_availability_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::app::context_clues::build_context_clue_string;
|
||||
use crate::app::radarr::radarr_context_clues::CONFIRMATION_PROMPT_CONTEXT_CLUES;
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
|
||||
use crate::models::Route;
|
||||
@@ -14,6 +16,8 @@ use crate::ui::widgets::loading_block::LoadingBlock;
|
||||
use crate::ui::widgets::popup::Size;
|
||||
use crate::ui::{draw_popup_over, DrawUi};
|
||||
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::Frame;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -50,32 +54,36 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let highlight_yes_no = selected_block == &ActiveRadarrBlock::EditIndexerConfirmPrompt;
|
||||
let edit_indexer_modal_option = &app.data.radarr_data.edit_indexer_modal;
|
||||
let protocol = &app.data.radarr_data.indexers.current_selection().protocol;
|
||||
let help_text = Text::from(build_context_clue_string(&CONFIRMATION_PROMPT_CONTEXT_CLUES).help());
|
||||
let help_paragraph = Paragraph::new(help_text).centered();
|
||||
|
||||
if edit_indexer_modal_option.is_some() {
|
||||
let edit_indexer_modal = edit_indexer_modal_option.as_ref().unwrap();
|
||||
|
||||
let [settings_area, buttons_area] =
|
||||
Layout::vertical([Constraint::Fill(0), Constraint::Length(3)])
|
||||
let [settings_area, _, buttons_area, help_area] = Layout::vertical([
|
||||
Constraint::Length(15),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.margin(1)
|
||||
.areas(area);
|
||||
let [left_side_area, right_side_area] =
|
||||
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
|
||||
.margin(1)
|
||||
.areas(settings_area);
|
||||
let [name_area, rss_area, auto_search_area, interactive_search_area, _] = Layout::vertical([
|
||||
let [name_area, rss_area, auto_search_area, interactive_search_area] = Layout::vertical([
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Fill(0),
|
||||
])
|
||||
.areas(left_side_area);
|
||||
let [url_area, api_key_area, seed_ratio_area, tags_area, _] = Layout::vertical([
|
||||
let [url_area, api_key_area, seed_ratio_area, tags_area] = Layout::vertical([
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Fill(0),
|
||||
])
|
||||
.areas(right_side_area);
|
||||
|
||||
@@ -161,6 +169,7 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
f.render_widget(interactive_search_checkbox, interactive_search_area);
|
||||
f.render_widget(save_button, save_area);
|
||||
f.render_widget(cancel_button, cancel_area);
|
||||
f.render_widget(help_paragraph, help_area);
|
||||
}
|
||||
} else {
|
||||
f.render_widget(LoadingBlock::new(app.is_loading, block), area);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::context_clues::build_context_clue_string;
|
||||
use crate::app::radarr::radarr_context_clues::CONFIRMATION_PROMPT_CONTEXT_CLUES;
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS,
|
||||
@@ -52,33 +56,37 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area:
|
||||
let selected_block = app.data.radarr_data.selected_block.get_active_block();
|
||||
let highlight_yes_no = selected_block == &ActiveRadarrBlock::IndexerSettingsConfirmPrompt;
|
||||
let indexer_settings_option = &app.data.radarr_data.indexer_settings;
|
||||
let help_text = Text::from(build_context_clue_string(&CONFIRMATION_PROMPT_CONTEXT_CLUES).help());
|
||||
let help_paragraph = Paragraph::new(help_text).centered();
|
||||
|
||||
if indexer_settings_option.is_some() {
|
||||
let indexer_settings = indexer_settings_option.as_ref().unwrap();
|
||||
|
||||
let [settings_area, buttons_area] =
|
||||
Layout::vertical([Constraint::Fill(0), Constraint::Length(3)])
|
||||
let [settings_area, _, buttons_area, help_area] = Layout::vertical([
|
||||
Constraint::Length(15),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.margin(1)
|
||||
.areas(area);
|
||||
let [left_side_area, right_side_area] =
|
||||
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
|
||||
.margin(1)
|
||||
.areas(settings_area);
|
||||
let [min_age_area, retention_area, max_size_area, prefer_flags_area, _] = Layout::vertical([
|
||||
let [min_age_area, retention_area, max_size_area, prefer_flags_area] = Layout::vertical([
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Fill(0),
|
||||
])
|
||||
.areas(left_side_area);
|
||||
let [availability_delay_area, rss_sync_interval_area, whitelisted_sub_tags_area, allow_hardcoded_subs_area, _] =
|
||||
let [availability_delay_area, rss_sync_interval_area, whitelisted_sub_tags_area, allow_hardcoded_subs_area] =
|
||||
Layout::vertical([
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Fill(0),
|
||||
])
|
||||
.areas(right_side_area);
|
||||
|
||||
@@ -162,6 +170,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area:
|
||||
f.render_widget(allow_hardcoded_subs_checkbox, allow_hardcoded_subs_area);
|
||||
f.render_widget(save_button, save_area);
|
||||
f.render_widget(cancel_button, cancel_area);
|
||||
f.render_widget(help_paragraph, help_area);
|
||||
} else {
|
||||
f.render_widget(LoadingBlock::new(app.is_loading, block), area);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::{Cell, ListItem, Paragraph, Row};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::context_clues::{build_context_clue_string, BARE_POPUP_CONTEXT_CLUES};
|
||||
use crate::app::radarr::radarr_context_clues::ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES;
|
||||
use crate::app::radarr::radarr_context_clues::{
|
||||
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::models::radarr_models::AddMovieSearchResult;
|
||||
use crate::models::servarr_data::radarr::modals::AddMovieModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS};
|
||||
@@ -209,7 +211,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES).help());
|
||||
let help_paragraph = Paragraph::new(help_text)
|
||||
.block(borderless_block())
|
||||
.alignment(Alignment::Center);
|
||||
.centered();
|
||||
|
||||
search_box.show_cursor(f, search_box_area);
|
||||
f.render_widget(layout_block(), results_area);
|
||||
@@ -220,7 +222,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES).help());
|
||||
let help_paragraph = Paragraph::new(help_text)
|
||||
.block(borderless_block())
|
||||
.alignment(Alignment::Center);
|
||||
.centered();
|
||||
let error_message = Message::new("No movies found matching your query!");
|
||||
let error_message_popup = Popup::new(error_message).size(Size::Message);
|
||||
|
||||
@@ -240,7 +242,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
Text::from(build_context_clue_string(&ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES).help());
|
||||
let help_paragraph = Paragraph::new(help_text)
|
||||
.block(borderless_block())
|
||||
.alignment(Alignment::Center);
|
||||
.centered();
|
||||
let search_results_table = ManagarrTable::new(
|
||||
app.data.radarr_data.add_searched_movies.as_mut(),
|
||||
search_results_row_mapping,
|
||||
@@ -369,7 +371,7 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
|
||||
f.render_widget(title_block_centered(&title), area);
|
||||
|
||||
let [paragraph_area, root_folder_area, monitor_area, min_availability_area, quality_profile_area, tags_area, _, buttons_area] =
|
||||
let [paragraph_area, root_folder_area, monitor_area, min_availability_area, quality_profile_area, tags_area, _, buttons_area, help_area] =
|
||||
Layout::vertical([
|
||||
Constraint::Length(6),
|
||||
Constraint::Length(3),
|
||||
@@ -377,14 +379,18 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Fill(0),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.margin(1)
|
||||
.areas(area);
|
||||
|
||||
let prompt_paragraph = layout_paragraph_borderless(&prompt);
|
||||
let help_text = Text::from(build_context_clue_string(&CONFIRMATION_PROMPT_CONTEXT_CLUES).help());
|
||||
let help_paragraph = Paragraph::new(help_text).centered();
|
||||
f.render_widget(prompt_paragraph, paragraph_area);
|
||||
f.render_widget(help_paragraph, help_area);
|
||||
|
||||
let [add_area, cancel_area] =
|
||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
|
||||
@@ -2,9 +2,12 @@ use std::sync::atomic::Ordering;
|
||||
|
||||
use ratatui::layout::{Constraint, Rect};
|
||||
use ratatui::prelude::Layout;
|
||||
use ratatui::widgets::ListItem;
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::{ListItem, Paragraph};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::context_clues::build_context_clue_string;
|
||||
use crate::app::radarr::radarr_context_clues::CONFIRMATION_PROMPT_CONTEXT_CLUES;
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::radarr::modals::EditMovieModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
@@ -113,7 +116,7 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, are
|
||||
let selected_minimum_availability = minimum_availability_list.current_selection();
|
||||
let selected_quality_profile = quality_profile_list.current_selection();
|
||||
|
||||
let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, path_area, tags_area, _, buttons_area] =
|
||||
let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, path_area, tags_area, _, buttons_area, help_area] =
|
||||
Layout::vertical([
|
||||
Constraint::Length(6),
|
||||
Constraint::Length(3),
|
||||
@@ -121,8 +124,9 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, are
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Fill(0),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.margin(1)
|
||||
.areas(area);
|
||||
@@ -130,6 +134,8 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, are
|
||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.areas(buttons_area);
|
||||
|
||||
let help_text = Text::from(build_context_clue_string(&CONFIRMATION_PROMPT_CONTEXT_CLUES).help());
|
||||
let help_paragraph = Paragraph::new(help_text).centered();
|
||||
let prompt_paragraph = layout_paragraph_borderless(&movie_overview);
|
||||
let monitored_checkbox = Checkbox::new("Monitored")
|
||||
.checked(monitored.unwrap_or_default())
|
||||
@@ -181,6 +187,7 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, are
|
||||
f.render_widget(quality_profile_drop_down_button, quality_profile_area);
|
||||
f.render_widget(save_button, save_area);
|
||||
f.render_widget(cancel_button, cancel_area);
|
||||
f.render_widget(help_paragraph, help_area);
|
||||
}
|
||||
|
||||
fn draw_edit_movie_select_minimum_availability_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::iter;
|
||||
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Style, Stylize};
|
||||
use ratatui::text::{Line, Span, Text};
|
||||
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
||||
@@ -510,7 +510,7 @@ fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let content_paragraph = Paragraph::new(lines_vec)
|
||||
.block(borderless_block())
|
||||
.wrap(Wrap { trim: false })
|
||||
.alignment(Alignment::Left);
|
||||
.left_aligned();
|
||||
let confirmation_prompt = ConfirmationPrompt::new()
|
||||
.title(title)
|
||||
.prompt(&prompt)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::iter;
|
||||
|
||||
use chrono::{Duration, Utc};
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::prelude::Stylize;
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::{Paragraph, Row};
|
||||
@@ -192,7 +192,11 @@ fn draw_downloads_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
||||
size,
|
||||
..
|
||||
} = &downloads_vec[i];
|
||||
let percent = 1f64 - (*sizeleft as f64 / *size as f64);
|
||||
let percent = if *size == 0 {
|
||||
0.0
|
||||
} else {
|
||||
1f64 - (*sizeleft as f64 / *size as f64)
|
||||
};
|
||||
let download_gauge = line_gauge_with_title(title, percent);
|
||||
|
||||
f.render_widget(download_gauge, download_item_areas[i]);
|
||||
@@ -244,6 +248,6 @@ fn draw_radarr_logo(f: &mut Frame<'_>, area: Rect) {
|
||||
let logo = Paragraph::new(logo_text)
|
||||
.light_yellow()
|
||||
.block(layout_block().default())
|
||||
.alignment(Alignment::Center);
|
||||
.centered();
|
||||
f.render_widget(logo, area);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::ops::Sub;
|
||||
|
||||
use chrono::Utc;
|
||||
use ratatui::layout::{Alignment, Layout};
|
||||
use ratatui::layout::Layout;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::text::{Span, Text};
|
||||
use ratatui::widgets::{Cell, Paragraph, Row};
|
||||
@@ -204,7 +204,7 @@ fn draw_help(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
);
|
||||
let help_paragraph = Paragraph::new(help_text)
|
||||
.block(layout_block_top_border())
|
||||
.alignment(Alignment::Left);
|
||||
.left_aligned();
|
||||
|
||||
f.render_widget(help_paragraph, area);
|
||||
}
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ pub fn layout_paragraph_borderless(string: &str) -> Paragraph<'_> {
|
||||
.primary()
|
||||
.bold()
|
||||
.wrap(Wrap { trim: false })
|
||||
.alignment(Alignment::Center)
|
||||
.centered()
|
||||
}
|
||||
|
||||
pub fn borderless_block<'a>() -> Block<'a> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::{layout_block, style_block_highlight};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Alignment, Constraint, Flex, Layout, Rect};
|
||||
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
||||
use ratatui::prelude::{Style, Text, Widget};
|
||||
use ratatui::style::Styled;
|
||||
use ratatui::widgets::Paragraph;
|
||||
@@ -58,11 +58,11 @@ impl<'a> Button<'a> {
|
||||
if let Some(icon) = self.icon {
|
||||
layout_block().style(style).render(area, buf);
|
||||
Paragraph::new(Text::from(self.title))
|
||||
.alignment(Alignment::Left)
|
||||
.left_aligned()
|
||||
.style(style)
|
||||
.render(title_area, buf);
|
||||
Paragraph::new(Text::from(format!("{icon} ")))
|
||||
.alignment(Alignment::Right)
|
||||
.right_aligned()
|
||||
.style(style)
|
||||
.render(icon_area, buf);
|
||||
}
|
||||
@@ -72,7 +72,7 @@ impl<'a> Button<'a> {
|
||||
let [label_area, button_area] =
|
||||
Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
|
||||
let label_paragraph = Paragraph::new(Text::from(format!("\n{}: ", self.label.unwrap())))
|
||||
.alignment(Alignment::Right)
|
||||
.right_aligned()
|
||||
.primary();
|
||||
|
||||
if self.icon.is_some() {
|
||||
@@ -87,7 +87,7 @@ impl<'a> Button<'a> {
|
||||
fn render_button(self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new(Text::from(self.title))
|
||||
.block(layout_block())
|
||||
.alignment(Alignment::Center)
|
||||
.centered()
|
||||
.style(style_block_highlight(self.is_selected))
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::{borderless_block, layout_block, style_block_highlight};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::prelude::Text;
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::widgets::{Paragraph, Widget};
|
||||
@@ -47,13 +47,13 @@ impl<'a> Checkbox<'a> {
|
||||
|
||||
Paragraph::new(Text::from(format!("\n{}: ", self.label)))
|
||||
.block(borderless_block())
|
||||
.alignment(Alignment::Right)
|
||||
.right_aligned()
|
||||
.primary()
|
||||
.render(label_area, buf);
|
||||
|
||||
Paragraph::new(Text::from(check))
|
||||
.block(layout_block())
|
||||
.alignment(Alignment::Center)
|
||||
.centered()
|
||||
.style(style_block_highlight(self.is_highlighted).bold())
|
||||
.render(checkbox_box_area, buf);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use crate::app::context_clues::build_context_clue_string;
|
||||
use crate::app::radarr::radarr_context_clues::CONFIRMATION_PROMPT_CONTEXT_CLUES;
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
|
||||
use crate::ui::widgets::button::Button;
|
||||
use crate::ui::widgets::checkbox::Checkbox;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::{Paragraph, Widget};
|
||||
use std::iter;
|
||||
|
||||
@@ -64,12 +67,16 @@ impl<'a> ConfirmationPrompt<'a> {
|
||||
|
||||
fn render_confirmation_prompt_with_checkboxes(self, area: Rect, buf: &mut Buffer) {
|
||||
title_block_centered(self.title).render(area, buf);
|
||||
let help_text =
|
||||
Text::from(build_context_clue_string(&CONFIRMATION_PROMPT_CONTEXT_CLUES).help());
|
||||
let help_paragraph = Paragraph::new(help_text).centered();
|
||||
|
||||
if let Some(checkboxes) = self.checkboxes {
|
||||
let mut constraints = vec![
|
||||
Constraint::Length(4),
|
||||
Constraint::Fill(0),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(1),
|
||||
];
|
||||
constraints.splice(
|
||||
1..1,
|
||||
@@ -81,6 +88,7 @@ impl<'a> ConfirmationPrompt<'a> {
|
||||
.areas(chunks[checkboxes.len() + 2]);
|
||||
|
||||
layout_paragraph_borderless(self.prompt).render(chunks[0], buf);
|
||||
help_paragraph.render(chunks[checkboxes.len() + 3], buf);
|
||||
|
||||
checkboxes
|
||||
.into_iter()
|
||||
@@ -102,27 +110,38 @@ impl<'a> ConfirmationPrompt<'a> {
|
||||
|
||||
fn render_confirmation_prompt(self, area: Rect, buf: &mut Buffer) {
|
||||
title_block_centered(self.title).render(area, buf);
|
||||
let help_text =
|
||||
Text::from(build_context_clue_string(&CONFIRMATION_PROMPT_CONTEXT_CLUES).help());
|
||||
let help_paragraph = Paragraph::new(help_text).centered();
|
||||
|
||||
let [prompt_area, buttons_area] = if let Some(content_paragraph) = self.content {
|
||||
let [prompt_area, content_area, _, buttons_area] = Layout::vertical([
|
||||
let [prompt_area, content_area, _, buttons_area, help_area] = Layout::vertical([
|
||||
Constraint::Length(4),
|
||||
Constraint::Length(7),
|
||||
Constraint::Fill(0),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.margin(1)
|
||||
.areas(area);
|
||||
|
||||
content_paragraph.render(content_area, buf);
|
||||
help_paragraph.render(help_area, buf);
|
||||
|
||||
[prompt_area, buttons_area]
|
||||
} else {
|
||||
let [prompt_area, buttons_area] =
|
||||
Layout::vertical([Constraint::Percentage(72), Constraint::Length(3)])
|
||||
let [prompt_area, buttons_area, _, help_area] = Layout::vertical([
|
||||
Constraint::Percentage(72),
|
||||
Constraint::Length(3),
|
||||
Constraint::Fill(0),
|
||||
Constraint::Min(1),
|
||||
])
|
||||
.margin(1)
|
||||
.flex(Flex::SpaceBetween)
|
||||
.areas(area);
|
||||
|
||||
help_paragraph.render(help_area, buf);
|
||||
|
||||
[prompt_area, buttons_area]
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Position, Rect};
|
||||
use ratatui::layout::{Constraint, Layout, Position, Rect};
|
||||
use ratatui::prelude::Text;
|
||||
use ratatui::style::{Style, Styled, Stylize};
|
||||
use ratatui::widgets::{Block, Paragraph, Widget};
|
||||
@@ -114,7 +114,7 @@ impl<'a> InputBox<'a> {
|
||||
|
||||
Paragraph::new(Text::from(format!("\n{label}: ")))
|
||||
.block(borderless_block())
|
||||
.alignment(Alignment::Right)
|
||||
.right_aligned()
|
||||
.primary()
|
||||
.render(label_area, buf);
|
||||
input_box_paragraph.render(text_box_area, buf);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::{background_block, centered_rect, layout_block_top_border};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::prelude::Text;
|
||||
use ratatui::widgets::{Block, Clear, Paragraph, Widget};
|
||||
|
||||
@@ -27,7 +27,7 @@ impl Size {
|
||||
pub fn to_percent(&self) -> (u16, u16) {
|
||||
match self {
|
||||
Size::SmallPrompt => (20, 20),
|
||||
Size::Prompt => (35, 35),
|
||||
Size::Prompt => (37, 37),
|
||||
Size::LargePrompt => (70, 45),
|
||||
Size::Message => (25, 8),
|
||||
Size::NarrowMessage => (50, 20),
|
||||
@@ -100,7 +100,7 @@ impl<'a, T: Widget> Popup<'a, T> {
|
||||
|
||||
Paragraph::new(Text::from(format!(" {footer}").help()))
|
||||
.block(layout_block_top_border())
|
||||
.alignment(Alignment::Left)
|
||||
.left_aligned()
|
||||
.render(help_footer_area, buf);
|
||||
|
||||
content_area
|
||||
|
||||
@@ -7,7 +7,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_dimensions_to_percent() {
|
||||
assert_eq!(Size::SmallPrompt.to_percent(), (20, 20));
|
||||
assert_eq!(Size::Prompt.to_percent(), (35, 35));
|
||||
assert_eq!(Size::Prompt.to_percent(), (37, 37));
|
||||
assert_eq!(Size::LargePrompt.to_percent(), (70, 45));
|
||||
assert_eq!(Size::Message.to_percent(), (25, 8));
|
||||
assert_eq!(Size::NarrowMessage.to_percent(), (50, 20));
|
||||
|
||||
Reference in New Issue
Block a user