Refactored the ErrorMessage widget into a generic Message widget for enhanced reuse. Added support for testing a single indexer at a time.

This commit is contained in:
2024-02-15 11:11:10 -07:00
parent b49bfaa9c1
commit a360c83431
23 changed files with 418 additions and 78 deletions
+5
View File
@@ -23,6 +23,7 @@ generate_keybindings! {
logs, logs,
tasks, tasks,
test, test,
test_all,
refresh, refresh,
update, update,
events, events,
@@ -102,6 +103,10 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
key: Key::Char('t'), key: Key::Char('t'),
desc: "test", desc: "test",
}, },
test_all: KeyBinding {
key: Key::Char('T'),
desc: "test all",
},
refresh: KeyBinding { refresh: KeyBinding {
key: Key::Ctrl('r'), key: Key::Ctrl('r'),
desc: "refresh", desc: "refresh",
+1
View File
@@ -22,6 +22,7 @@ mod test {
#[case(DEFAULT_KEYBINDINGS.logs, Key::Char('l'), "logs")] #[case(DEFAULT_KEYBINDINGS.logs, Key::Char('l'), "logs")]
#[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), "tasks")] #[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), "tasks")]
#[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")] #[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")]
#[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), "test all")]
#[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), "refresh")] #[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), "refresh")]
#[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), "update")] #[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), "update")]
#[case(DEFAULT_KEYBINDINGS.home, Key::Home, "home")] #[case(DEFAULT_KEYBINDINGS.home, Key::Home, "home")]
+5
View File
@@ -49,6 +49,11 @@ impl<'a> App<'a> {
.dispatch_network_event(RadarrEvent::GetIndexerSettings.into()) .dispatch_network_event(RadarrEvent::GetIndexerSettings.into())
.await; .await;
} }
ActiveRadarrBlock::TestIndexer => {
self
.dispatch_network_event(RadarrEvent::TestIndexer.into())
.await;
}
ActiveRadarrBlock::TestAllIndexers => { ActiveRadarrBlock::TestAllIndexers => {
self self
.dispatch_network_event(RadarrEvent::TestAllIndexers.into()) .dispatch_network_event(RadarrEvent::TestAllIndexers.into())
+3 -2
View File
@@ -52,7 +52,7 @@ pub static ROOT_FOLDERS_CONTEXT_CLUES: [ContextClue; 3] = [
), ),
]; ];
pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 6] = [ pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 7] = [
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc), (DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
(DEFAULT_KEYBINDINGS.submit, "edit indexer"), (DEFAULT_KEYBINDINGS.submit, "edit indexer"),
( (
@@ -60,7 +60,8 @@ pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 6] = [
DEFAULT_KEYBINDINGS.settings.desc, DEFAULT_KEYBINDINGS.settings.desc,
), ),
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc), (DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
(DEFAULT_KEYBINDINGS.test, "test all indexers"), (DEFAULT_KEYBINDINGS.test, "test indexer"),
(DEFAULT_KEYBINDINGS.test_all, "test all indexers"),
( (
DEFAULT_KEYBINDINGS.refresh, DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc, DEFAULT_KEYBINDINGS.refresh.desc,
@@ -177,6 +177,11 @@ mod tests {
let (key_binding, description) = indexers_context_clues_iter.next().unwrap(); let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test); assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test);
assert_str_eq!(*description, "test indexer");
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test_all);
assert_str_eq!(*description, "test all indexers"); assert_str_eq!(*description, "test all indexers");
let (key_binding, description) = indexers_context_clues_iter.next().unwrap(); let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
+16
View File
@@ -161,6 +161,22 @@ mod tests {
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
} }
#[tokio::test]
async fn test_dispatch_by_test_indexer_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_radarr_block(&ActiveRadarrBlock::TestIndexer)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::TestIndexer.into()
);
assert_eq!(app.tick_count, 0);
}
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_test_all_indexers_block() { async fn test_dispatch_by_test_all_indexers_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); let (mut app, mut sync_network_rx) = construct_app_unit();
@@ -312,6 +312,19 @@ mod tests {
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
} }
#[test]
fn test_test_indexer_esc() {
let mut app = App::default();
app.data.radarr_data.indexer_test_error = Some("test result".to_owned());
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::TestIndexer.into());
IndexersHandler::with(&ESC_KEY, &mut app, &ActiveRadarrBlock::TestIndexer, &None).handle();
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
assert_eq!(app.data.radarr_data.indexer_test_error, None);
}
#[test] #[test]
fn test_default_esc() { fn test_default_esc() {
let mut app = App::default(); let mut app = App::default();
@@ -391,6 +404,24 @@ mod tests {
) )
.handle(); .handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::TestIndexer.into()
);
}
#[test]
fn test_test_all_key() {
let mut app = App::default();
IndexersHandler::with(
&DEFAULT_KEYBINDINGS.test_all.key,
&mut app,
&ActiveRadarrBlock::Indexers,
&None,
)
.handle();
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
&ActiveRadarrBlock::TestAllIndexers.into() &ActiveRadarrBlock::TestAllIndexers.into()
@@ -152,6 +152,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.radarr_data.prompt_confirm = false; self.app.data.radarr_data.prompt_confirm = false;
} }
ActiveRadarrBlock::TestIndexer => {
self.app.pop_navigation_stack();
self.app.data.radarr_data.indexer_test_error = None;
}
_ => handle_clear_errors(self.app), _ => handle_clear_errors(self.app),
} }
} }
@@ -169,6 +173,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if *key == DEFAULT_KEYBINDINGS.test.key => { _ if *key == DEFAULT_KEYBINDINGS.test.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::TestIndexer.into());
}
_ if *key == DEFAULT_KEYBINDINGS.test_all.key => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into()); .push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into());
@@ -59,6 +59,7 @@ pub struct RadarrData<'a> {
pub edit_indexer_modal: Option<EditIndexerModal>, pub edit_indexer_modal: Option<EditIndexerModal>,
pub edit_root_folder: Option<HorizontallyScrollableText>, pub edit_root_folder: Option<HorizontallyScrollableText>,
pub indexer_settings: Option<IndexerSettings>, pub indexer_settings: Option<IndexerSettings>,
pub indexer_test_error: Option<String>,
pub indexer_test_all_results: Option<StatefulTable<IndexerTestResultModalItem>>, pub indexer_test_all_results: Option<StatefulTable<IndexerTestResultModalItem>>,
pub movie_details_modal: Option<MovieDetailsModal>, pub movie_details_modal: Option<MovieDetailsModal>,
pub prompt_confirm: bool, pub prompt_confirm: bool,
@@ -107,6 +108,7 @@ impl<'a> Default for RadarrData<'a> {
edit_indexer_modal: None, edit_indexer_modal: None,
edit_root_folder: None, edit_root_folder: None,
indexer_settings: None, indexer_settings: None,
indexer_test_error: None,
indexer_test_all_results: None, indexer_test_all_results: None,
movie_details_modal: None, movie_details_modal: None,
prompt_confirm: false, prompt_confirm: false,
@@ -279,6 +281,7 @@ pub enum ActiveRadarrBlock {
SystemTasks, SystemTasks,
SystemTaskStartConfirmPrompt, SystemTaskStartConfirmPrompt,
SystemUpdates, SystemUpdates,
TestIndexer,
TestAllIndexers, TestAllIndexers,
UpdateAndScanPrompt, UpdateAndScanPrompt,
UpdateAllCollectionsPrompt, UpdateAllCollectionsPrompt,
@@ -309,10 +312,11 @@ pub static COLLECTIONS_BLOCKS: [ActiveRadarrBlock; 7] = [
ActiveRadarrBlock::FilterCollectionsError, ActiveRadarrBlock::FilterCollectionsError,
ActiveRadarrBlock::UpdateAllCollectionsPrompt, ActiveRadarrBlock::UpdateAllCollectionsPrompt,
]; ];
pub static INDEXERS_BLOCKS: [ActiveRadarrBlock; 3] = [ pub static INDEXERS_BLOCKS: [ActiveRadarrBlock; 4] = [
ActiveRadarrBlock::AddIndexer, ActiveRadarrBlock::AddIndexer,
ActiveRadarrBlock::DeleteIndexerPrompt, ActiveRadarrBlock::DeleteIndexerPrompt,
ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Indexers,
ActiveRadarrBlock::TestIndexer,
]; ];
pub static ROOT_FOLDERS_BLOCKS: [ActiveRadarrBlock; 3] = [ pub static ROOT_FOLDERS_BLOCKS: [ActiveRadarrBlock; 3] = [
ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::RootFolders,
@@ -81,6 +81,7 @@ mod tests {
assert!(radarr_data.edit_root_folder.is_none()); assert!(radarr_data.edit_root_folder.is_none());
assert!(radarr_data.edit_indexer_modal.is_none()); assert!(radarr_data.edit_indexer_modal.is_none());
assert!(radarr_data.indexer_settings.is_none()); assert!(radarr_data.indexer_settings.is_none());
assert!(radarr_data.indexer_test_error.is_none());
assert!(radarr_data.indexer_test_all_results.is_none()); assert!(radarr_data.indexer_test_all_results.is_none());
assert!(radarr_data.movie_details_modal.is_none()); assert!(radarr_data.movie_details_modal.is_none());
assert!(radarr_data.prompt_confirm_action.is_none()); assert!(radarr_data.prompt_confirm_action.is_none());
@@ -280,10 +281,11 @@ mod tests {
#[test] #[test]
fn test_indexers_blocks_contents() { fn test_indexers_blocks_contents() {
assert_eq!(INDEXERS_BLOCKS.len(), 3); assert_eq!(INDEXERS_BLOCKS.len(), 4);
assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::AddIndexer)); assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::AddIndexer));
assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::DeleteIndexerPrompt)); assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::DeleteIndexerPrompt));
assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::Indexers)); assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::Indexers));
assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::TestIndexer));
} }
#[test] #[test]
+58
View File
@@ -63,6 +63,7 @@ pub enum RadarrEvent {
HealthCheck, HealthCheck,
SearchNewMovie, SearchNewMovie,
StartTask, StartTask,
TestIndexer,
TestAllIndexers, TestAllIndexers,
TriggerAutomaticSearch, TriggerAutomaticSearch,
UpdateAllMovies, UpdateAllMovies,
@@ -99,6 +100,7 @@ impl RadarrEvent {
RadarrEvent::GetTags => "/tag", RadarrEvent::GetTags => "/tag",
RadarrEvent::GetTasks => "/system/task", RadarrEvent::GetTasks => "/system/task",
RadarrEvent::GetUpdates => "/update", RadarrEvent::GetUpdates => "/update",
RadarrEvent::TestIndexer => "/indexer/test",
RadarrEvent::TestAllIndexers => "/indexer/testall", RadarrEvent::TestAllIndexers => "/indexer/testall",
RadarrEvent::StartTask RadarrEvent::StartTask
| RadarrEvent::GetQueuedEvents | RadarrEvent::GetQueuedEvents
@@ -153,6 +155,7 @@ impl<'a, 'b> Network<'a, 'b> {
RadarrEvent::HealthCheck => self.get_healthcheck().await, RadarrEvent::HealthCheck => self.get_healthcheck().await,
RadarrEvent::SearchNewMovie => self.search_movie().await, RadarrEvent::SearchNewMovie => self.search_movie().await,
RadarrEvent::StartTask => self.start_task().await, RadarrEvent::StartTask => self.start_task().await,
RadarrEvent::TestIndexer => self.test_indexer().await,
RadarrEvent::TestAllIndexers => self.test_all_indexers().await, RadarrEvent::TestAllIndexers => self.test_all_indexers().await,
RadarrEvent::TriggerAutomaticSearch => self.trigger_automatic_search().await, RadarrEvent::TriggerAutomaticSearch => self.trigger_automatic_search().await,
RadarrEvent::UpdateAllMovies => self.update_all_movies().await, RadarrEvent::UpdateAllMovies => self.update_all_movies().await,
@@ -1524,6 +1527,61 @@ impl<'a, 'b> Network<'a, 'b> {
.await; .await;
} }
async fn test_indexer(&mut self) {
let id = self
.app
.lock()
.await
.data
.radarr_data
.indexers
.current_selection()
.id;
info!("Testing Radarr indexer with ID: {id}");
info!("Fetching indexer details for indexer with ID: {id}");
let request_props = self
.radarr_request_props_from(
format!("{}/{id}", RadarrEvent::GetIndexers.resource()).as_str(),
RequestMethod::Get,
None::<()>,
)
.await;
let mut test_body: Value = Value::default();
self
.handle_request::<(), Value>(request_props, |detailed_indexer_body, _| {
test_body = detailed_indexer_body;
})
.await;
info!("Testing indexer");
let mut request_props = self
.radarr_request_props_from(
RadarrEvent::TestIndexer.resource(),
RequestMethod::Post,
Some(test_body),
)
.await;
request_props.ignore_status_code = true;
self
.handle_request::<Value, Value>(request_props, |test_results, mut app| {
if test_results.as_object().is_none() {
app.data.radarr_data.indexer_test_error = Some(
test_results.as_array().unwrap()[0]
.get("errorMessage")
.unwrap()
.to_string(),
);
};
})
.await;
}
async fn test_all_indexers(&mut self) { async fn test_all_indexers(&mut self) {
info!("Testing all indexers"); info!("Testing all indexers");
+134
View File
@@ -196,6 +196,7 @@ mod test {
#[case(RadarrEvent::GetTags, "/tag")] #[case(RadarrEvent::GetTags, "/tag")]
#[case(RadarrEvent::GetTasks, "/system/task")] #[case(RadarrEvent::GetTasks, "/system/task")]
#[case(RadarrEvent::GetUpdates, "/update")] #[case(RadarrEvent::GetUpdates, "/update")]
#[case(RadarrEvent::TestIndexer, "/indexer/test")]
#[case(RadarrEvent::TestAllIndexers, "/indexer/testall")] #[case(RadarrEvent::TestAllIndexers, "/indexer/testall")]
#[case(RadarrEvent::HealthCheck, "/health")] #[case(RadarrEvent::HealthCheck, "/health")]
fn test_resource(#[case] event: RadarrEvent, #[case] expected_uri: String) { fn test_resource(#[case] event: RadarrEvent, #[case] expected_uri: String) {
@@ -686,6 +687,139 @@ mod test {
); );
} }
#[tokio::test]
async fn test_handle_test_indexer_event_error() {
let indexer_details_json = json!({
"enableRss": true,
"enableAutomaticSearch": true,
"enableInteractiveSearch": true,
"name": "Test Indexer",
"fields": [
{
"name": "baseUrl",
"value": "https://test.com",
},
{
"name": "apiKey",
"value": "",
},
{
"name": "seedCriteria.seedRatio",
"value": "1.2",
},
],
"tags": [1],
"id": 1
});
let response_json = json!([
{
"isWarning": false,
"propertyName": "",
"errorMessage": "test failure",
"severity": "error"
}]);
let resource = format!("{}/1", RadarrEvent::GetIndexers.resource());
let (async_details_server, app_arc, mut server) = mock_radarr_api(
RequestMethod::Get,
None,
Some(indexer_details_json.clone()),
None,
&resource,
)
.await;
let async_test_server = server
.mock(
"POST",
format!("/api/v3{}", RadarrEvent::TestIndexer.resource()).as_str(),
)
.with_status(400)
.match_header("X-Api-Key", "test1234")
.match_body(Matcher::Json(indexer_details_json))
.with_body(response_json.to_string())
.create_async()
.await;
app_arc
.lock()
.await
.data
.radarr_data
.indexers
.set_items(vec![indexer()]);
let mut network = Network::new(&app_arc, CancellationToken::new());
network.handle_radarr_event(RadarrEvent::TestIndexer).await;
async_details_server.assert_async().await;
async_test_server.assert_async().await;
assert_eq!(
app_arc.lock().await.data.radarr_data.indexer_test_error,
Some("\"test failure\"".to_owned())
);
}
#[tokio::test]
async fn test_handle_test_indexer_event_success() {
let indexer_details_json = json!({
"enableRss": true,
"enableAutomaticSearch": true,
"enableInteractiveSearch": true,
"name": "Test Indexer",
"fields": [
{
"name": "baseUrl",
"value": "https://test.com",
},
{
"name": "apiKey",
"value": "",
},
{
"name": "seedCriteria.seedRatio",
"value": "1.2",
},
],
"tags": [1],
"id": 1
});
let resource = format!("{}/1", RadarrEvent::GetIndexers.resource());
let (async_details_server, app_arc, mut server) = mock_radarr_api(
RequestMethod::Get,
None,
Some(indexer_details_json.clone()),
None,
&resource,
)
.await;
let async_test_server = server
.mock(
"POST",
format!("/api/v3{}", RadarrEvent::TestIndexer.resource()).as_str(),
)
.with_status(200)
.match_header("X-Api-Key", "test1234")
.match_body(Matcher::Json(indexer_details_json))
.with_body("{}")
.create_async()
.await;
app_arc
.lock()
.await
.data
.radarr_data
.indexers
.set_items(vec![indexer()]);
let mut network = Network::new(&app_arc, CancellationToken::new());
network.handle_radarr_event(RadarrEvent::TestIndexer).await;
async_details_server.assert_async().await;
async_test_server.assert_async().await;
assert_eq!(
app_arc.lock().await.data.radarr_data.indexer_test_error,
None
);
}
#[tokio::test] #[tokio::test]
async fn test_handle_test_all_indexers_event() { async fn test_handle_test_all_indexers_event() {
let indexers = vec![ let indexers = vec![
+4 -4
View File
@@ -13,8 +13,8 @@ use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt; use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
use crate::ui::widgets::error_message::ErrorMessage;
use crate::ui::widgets::managarr_table::ManagarrTable; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::message::Message;
use crate::ui::widgets::popup::{Popup, Size}; use crate::ui::widgets::popup::{Popup, Size};
use crate::ui::{draw_input_box_popup, draw_popup_over, DrawUi}; use crate::ui::{draw_input_box_popup, draw_popup_over, DrawUi};
@@ -52,7 +52,7 @@ impl DrawUi for CollectionsUi {
Size::InputBox, Size::InputBox,
), ),
ActiveRadarrBlock::SearchCollectionError => { ActiveRadarrBlock::SearchCollectionError => {
let popup = Popup::new(ErrorMessage::new("Collection not found!")).size(Size::Error); let popup = Popup::new(Message::new("Collection not found!")).size(Size::Message);
draw_collections(f, app, area); draw_collections(f, app, area);
f.render_widget(popup, f.size()); f.render_widget(popup, f.size());
@@ -66,10 +66,10 @@ impl DrawUi for CollectionsUi {
Size::InputBox, Size::InputBox,
), ),
ActiveRadarrBlock::FilterCollectionsError => { ActiveRadarrBlock::FilterCollectionsError => {
let popup = Popup::new(ErrorMessage::new( let popup = Popup::new(Message::new(
"No collections found matching the given filter!", "No collections found matching the given filter!",
)) ))
.size(Size::Error); .size(Size::Message);
draw_collections(f, app, area); draw_collections(f, app, area);
f.render_widget(popup, f.size()); f.render_widget(popup, f.size());
+26 -1
View File
@@ -1,4 +1,5 @@
use ratatui::layout::{Constraint, Rect}; use ratatui::layout::{Constraint, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::text::Text; use ratatui::text::Text;
use ratatui::widgets::{Cell, Row}; use ratatui::widgets::{Cell, Row};
use ratatui::Frame; use ratatui::Frame;
@@ -11,9 +12,11 @@ use crate::ui::radarr_ui::indexers::edit_indexer_ui::EditIndexerUi;
use crate::ui::radarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi; use crate::ui::radarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi;
use crate::ui::radarr_ui::indexers::test_all_indexers_ui::TestAllIndexersUi; use crate::ui::radarr_ui::indexers::test_all_indexers_ui::TestAllIndexersUi;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::layout_block_top_border; use crate::ui::utils::{layout_block_top_border, title_block};
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt; use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::widgets::managarr_table::ManagarrTable; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::message::Message;
use crate::ui::widgets::popup::{Popup, Size}; use crate::ui::widgets::popup::{Popup, Size};
use crate::ui::DrawUi; use crate::ui::DrawUi;
@@ -43,6 +46,28 @@ impl DrawUi for IndexersUi {
let route = *app.get_current_route(); let route = *app.get_current_route();
let mut indexers_matchers = |active_radarr_block| match active_radarr_block { let mut indexers_matchers = |active_radarr_block| match active_radarr_block {
ActiveRadarrBlock::Indexers => draw_indexers(f, app, area), ActiveRadarrBlock::Indexers => draw_indexers(f, app, area),
ActiveRadarrBlock::TestIndexer => {
draw_indexers(f, app, area);
if app.is_loading {
let loading_popup = Popup::new(LoadingBlock::new(
app.is_loading,
title_block("Testing Indexer"),
))
.size(Size::LargeMessage);
f.render_widget(loading_popup, f.size());
} else {
let popup = if let Some(result) = app.data.radarr_data.indexer_test_error.as_ref() {
Popup::new(Message::new(result.clone())).size(Size::LargeMessage)
} else {
let message = Message::new("Indexer test succeeded!")
.title("Success")
.style(Style::new().success().bold());
Popup::new(message).size(Size::Message)
};
f.render_widget(popup, f.size());
}
}
ActiveRadarrBlock::DeleteIndexerPrompt => { ActiveRadarrBlock::DeleteIndexerPrompt => {
let prompt = format!( let prompt = format!(
"Do you really want to delete this indexer: \n{}?", "Do you really want to delete this indexer: \n{}?",
+4 -5
View File
@@ -17,9 +17,9 @@ use crate::ui::utils::{
title_block_centered, title_block_centered,
}; };
use crate::ui::widgets::button::Button; use crate::ui::widgets::button::Button;
use crate::ui::widgets::error_message::ErrorMessage;
use crate::ui::widgets::input_box::InputBox; use crate::ui::widgets::input_box::InputBox;
use crate::ui::widgets::managarr_table::ManagarrTable; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::message::Message;
use crate::ui::widgets::popup::{Popup, Size}; use crate::ui::widgets::popup::{Popup, Size};
use crate::ui::widgets::selectable_list::SelectableList; use crate::ui::widgets::selectable_list::SelectableList;
use crate::ui::{draw_popup_over, DrawUi}; use crate::ui::{draw_popup_over, DrawUi};
@@ -79,8 +79,7 @@ impl DrawUi for AddMovieUi {
ActiveRadarrBlock::AddMovieAlreadyInLibrary => { ActiveRadarrBlock::AddMovieAlreadyInLibrary => {
draw_add_movie_search(f, app, area); draw_add_movie_search(f, app, area);
f.render_widget( f.render_widget(
Popup::new(ErrorMessage::new("This film is already in your library")) Popup::new(Message::new("This film is already in your library")).size(Size::Message),
.size(Size::Error),
f.size(), f.size(),
); );
} }
@@ -220,8 +219,8 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let help_paragraph = Paragraph::new(help_text) let help_paragraph = Paragraph::new(help_text)
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Center); .alignment(Alignment::Center);
let error_message = ErrorMessage::new("No movies found matching your query!"); let error_message = Message::new("No movies found matching your query!");
let error_message_popup = Popup::new(error_message).size(Size::Error); let error_message_popup = Popup::new(error_message).size(Size::Message);
f.render_widget(layout_block(), results_area); f.render_widget(layout_block(), results_area);
f.render_widget(error_message_popup, f.size()); f.render_widget(error_message_popup, f.size());
+4 -6
View File
@@ -13,8 +13,8 @@ use crate::ui::radarr_ui::library::edit_movie_ui::EditMovieUi;
use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi; use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi;
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt; use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
use crate::ui::widgets::error_message::ErrorMessage;
use crate::ui::widgets::managarr_table::ManagarrTable; use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::message::Message;
use crate::ui::widgets::popup::{Popup, Size}; use crate::ui::widgets::popup::{Popup, Size};
use crate::ui::{draw_input_box_popup, draw_popup_over, DrawUi}; use crate::ui::{draw_input_box_popup, draw_popup_over, DrawUi};
use crate::utils::{convert_runtime, convert_to_gb}; use crate::utils::{convert_runtime, convert_to_gb};
@@ -57,7 +57,7 @@ impl DrawUi for LibraryUi {
Size::InputBox, Size::InputBox,
), ),
ActiveRadarrBlock::SearchMovieError => { ActiveRadarrBlock::SearchMovieError => {
let popup = Popup::new(ErrorMessage::new("Movie not found!")).size(Size::Error); let popup = Popup::new(Message::new("Movie not found!")).size(Size::Message);
draw_library(f, app, area); draw_library(f, app, area);
f.render_widget(popup, f.size()); f.render_widget(popup, f.size());
@@ -71,10 +71,8 @@ impl DrawUi for LibraryUi {
Size::InputBox, Size::InputBox,
), ),
ActiveRadarrBlock::FilterMoviesError => { ActiveRadarrBlock::FilterMoviesError => {
let popup = Popup::new(ErrorMessage::new( let popup = Popup::new(Message::new("No movies found matching the given filter!"))
"No movies found matching the given filter!", .size(Size::Message);
))
.size(Size::Error);
draw_library(f, app, area); draw_library(f, app, area);
f.render_widget(popup, f.size()); f.render_widget(popup, f.size());
-40
View File
@@ -1,40 +0,0 @@
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::title_block_centered;
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect};
use ratatui::style::Stylize;
use ratatui::text::Text;
use ratatui::widgets::{Paragraph, Widget};
#[cfg(test)]
#[path = "error_message_tests.rs"]
mod error_message_tests;
pub struct ErrorMessage<'a> {
text: Text<'a>,
}
impl<'a> ErrorMessage<'a> {
pub fn new<T>(message: T) -> Self
where
T: Into<Text<'a>>,
{
ErrorMessage {
text: message.into(),
}
}
fn render_error_message(self, area: Rect, buf: &mut Buffer) {
Paragraph::new(self.text)
.failure()
.alignment(Alignment::Center)
.block(title_block_centered("Error").failure().bold())
.render(area, buf);
}
}
impl<'a> Widget for ErrorMessage<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_error_message(area, buf);
}
}
-14
View File
@@ -1,14 +0,0 @@
#[cfg(test)]
mod tests {
use crate::ui::widgets::error_message::ErrorMessage;
use pretty_assertions::assert_eq;
use ratatui::text::Text;
#[test]
fn test_error_message_new() {
let message = "This is an error message";
let error_message = ErrorMessage::new(message);
assert_eq!(error_message.text, Text::from(message));
}
}
+55
View File
@@ -0,0 +1,55 @@
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::title_block_centered;
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::text::Text;
use ratatui::widgets::{Paragraph, Widget, Wrap};
#[cfg(test)]
#[path = "message_tests.rs"]
mod message_tests;
pub struct Message<'a> {
text: Text<'a>,
title: &'a str,
style: Style,
}
impl<'a> Message<'a> {
pub fn new<T>(message: T) -> Self
where
T: Into<Text<'a>>,
{
Message {
text: message.into(),
title: "Error",
style: Style::new().failure().bold(),
}
}
pub fn title(mut self, title: &'a str) -> Self {
self.title = title;
self
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
fn render_message(self, area: Rect, buf: &mut Buffer) {
Paragraph::new(self.text)
.style(self.style)
.alignment(Alignment::Center)
.block(title_block_centered(self.title).style(self.style))
.wrap(Wrap { trim: true })
.render(area, buf);
}
}
impl<'a> Widget for Message<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.render_message(area, buf);
}
}
+43
View File
@@ -0,0 +1,43 @@
#[cfg(test)]
mod tests {
use crate::ui::styles::ManagarrStyle;
use crate::ui::widgets::message::Message;
use pretty_assertions::{assert_eq, assert_str_eq};
use ratatui::style::{Style, Stylize};
use ratatui::text::Text;
#[test]
fn test_error_message_new() {
let test_message = "This is a message";
let message = Message::new(test_message);
assert_eq!(message.text, Text::from(test_message));
assert_str_eq!(message.title, "Error");
assert_eq!(message.style, Style::new().failure().bold());
}
#[test]
fn test_message_title() {
let test_message = "This is a message";
let title = "Success";
let message = Message::new(test_message).title(title);
assert_str_eq!(message.title, title);
assert_eq!(message.text, Text::from(test_message));
assert_eq!(message.style, Style::new().failure().bold());
}
#[test]
fn test_message_style() {
let test_message = "This is a message";
let style = Style::new().success().bold();
let message = Message::new(test_message).style(style);
assert_eq!(message.style, style);
assert_eq!(message.text, Text::from(test_message));
assert_str_eq!(message.title, "Error");
}
}
+1 -1
View File
@@ -1,9 +1,9 @@
pub(super) mod button; pub(super) mod button;
pub(super) mod checkbox; pub(super) mod checkbox;
pub(super) mod confirmation_prompt; pub(super) mod confirmation_prompt;
pub(super) mod error_message;
pub(super) mod input_box; pub(super) mod input_box;
pub(super) mod loading_block; pub(super) mod loading_block;
pub(super) mod managarr_table; pub(super) mod managarr_table;
pub(super) mod message;
pub(super) mod popup; pub(super) mod popup;
pub(super) mod selectable_list; pub(super) mod selectable_list;
+4 -2
View File
@@ -12,7 +12,8 @@ mod popup_tests;
pub enum Size { pub enum Size {
Prompt, Prompt,
LargePrompt, LargePrompt,
Error, Message,
LargeMessage,
InputBox, InputBox,
Dropdown, Dropdown,
Small, Small,
@@ -25,7 +26,8 @@ impl Size {
match self { match self {
Size::Prompt => (35, 35), Size::Prompt => (35, 35),
Size::LargePrompt => (70, 45), Size::LargePrompt => (70, 45),
Size::Error => (25, 8), Size::Message => (25, 8),
Size::LargeMessage => (25, 25),
Size::InputBox => (30, 13), Size::InputBox => (30, 13),
Size::Dropdown => (20, 30), Size::Dropdown => (20, 30),
Size::Small => (40, 40), Size::Small => (40, 40),
+2 -1
View File
@@ -8,7 +8,8 @@ mod tests {
fn test_dimensions_to_percent() { fn test_dimensions_to_percent() {
assert_eq!(Size::Prompt.to_percent(), (35, 35)); assert_eq!(Size::Prompt.to_percent(), (35, 35));
assert_eq!(Size::LargePrompt.to_percent(), (70, 45)); assert_eq!(Size::LargePrompt.to_percent(), (70, 45));
assert_eq!(Size::Error.to_percent(), (25, 8)); assert_eq!(Size::Message.to_percent(), (25, 8));
assert_eq!(Size::LargeMessage.to_percent(), (25, 25));
assert_eq!(Size::InputBox.to_percent(), (30, 13)); assert_eq!(Size::InputBox.to_percent(), (30, 13));
assert_eq!(Size::Dropdown.to_percent(), (20, 30)); assert_eq!(Size::Dropdown.to_percent(), (20, 30));
assert_eq!(Size::Small.to_percent(), (40, 40)); assert_eq!(Size::Small.to_percent(), (40, 40));