Added the ability to edit existing indexers with basic options, added a tags column to the indexers table, and fixed a bug in the counter fields that displayed the cursor next to the integer instead of on it to make understanding the counter easier. Also upgraded to confy v0.60.0 and rust version to 1.75

This commit is contained in:
2024-01-19 15:45:41 -07:00
parent 3d249cc51c
commit 2ec4472efc
29 changed files with 3513 additions and 362 deletions
@@ -16,7 +16,7 @@ use crate::ui::utils::{
use crate::ui::{
draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup,
draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup,
draw_selectable_list, draw_text_box_with_label, DrawUi,
draw_selectable_list, draw_text_box_with_label, DrawUi, LabeledTextBoxProps,
};
#[cfg(test)]
@@ -191,12 +191,16 @@ fn draw_edit_collection_confirmation_prompt(
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
draw_text_box_with_label(
f,
chunks[4],
"Root Folder",
&path.text,
*path.offset.borrow(),
selected_block == &ActiveRadarrBlock::EditCollectionRootFolderPathInput,
active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput,
LabeledTextBoxProps {
area: chunks[4],
label: "Root Folder",
text: &path.text,
offset: *path.offset.borrow(),
is_selected: selected_block == &ActiveRadarrBlock::EditCollectionRootFolderPathInput,
should_show_cursor: active_radarr_block
== ActiveRadarrBlock::EditCollectionRootFolderPathInput,
cursor_after_string: true,
},
);
}
@@ -0,0 +1,214 @@
use crate::app::App;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::Route;
use crate::ui::radarr_ui::indexers::draw_indexers;
use crate::ui::utils::{
horizontal_chunks, horizontal_chunks_with_margin, title_block_centered, vertical_chunks,
vertical_chunks_with_margin,
};
use crate::ui::{
draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading,
DrawUi, LabeledTextBoxProps,
};
use ratatui::layout::{Constraint, Rect};
use ratatui::Frame;
use std::iter;
#[cfg(test)]
#[path = "edit_indexer_ui_tests.rs"]
mod edit_indexer_ui_tests;
pub(super) struct EditIndexerUi;
impl DrawUi for EditIndexerUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return EDIT_INDEXER_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) {
draw_popup_over(
f,
app,
content_rect,
draw_indexers,
draw_edit_indexer_prompt,
70,
45,
);
}
}
fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) {
let block = title_block_centered("Edit Indexer");
let yes_no_value = app.data.radarr_data.prompt_confirm;
let selected_block = app.data.radarr_data.selected_block.get_active_block();
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;
if edit_indexer_modal_option.is_some() {
let edit_indexer_modal = edit_indexer_modal_option.as_ref().unwrap();
f.render_widget(block, prompt_area);
let chunks = vertical_chunks_with_margin(
vec![Constraint::Min(0), Constraint::Length(3)],
prompt_area,
1,
);
let split_chunks = horizontal_chunks_with_margin(
vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)],
chunks[0],
1,
);
let left_chunks = vertical_chunks(
vec![
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Min(0),
],
split_chunks[0],
);
let right_chunks = vertical_chunks(
vec![
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Min(0),
],
split_chunks[1],
);
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
draw_text_box_with_label(
f,
LabeledTextBoxProps {
area: left_chunks[0],
label: "Name",
text: &edit_indexer_modal.name.text,
offset: *edit_indexer_modal.name.offset.borrow(),
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerNameInput,
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerNameInput,
cursor_after_string: true,
},
);
draw_text_box_with_label(
f,
LabeledTextBoxProps {
area: right_chunks[0],
label: "URL",
text: &edit_indexer_modal.url.text,
offset: *edit_indexer_modal.url.offset.borrow(),
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerUrlInput,
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerUrlInput,
cursor_after_string: true,
},
);
draw_text_box_with_label(
f,
LabeledTextBoxProps {
area: right_chunks[1],
label: "API Key",
text: &edit_indexer_modal.api_key.text,
offset: *edit_indexer_modal.api_key.offset.borrow(),
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerApiKeyInput,
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerApiKeyInput,
cursor_after_string: true,
},
);
if protocol == "torrent" {
draw_text_box_with_label(
f,
LabeledTextBoxProps {
area: right_chunks[2],
label: "Seed Ratio",
text: &edit_indexer_modal.seed_ratio.text,
offset: *edit_indexer_modal.seed_ratio.offset.borrow(),
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerSeedRatioInput,
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerSeedRatioInput,
cursor_after_string: true,
},
);
draw_text_box_with_label(
f,
LabeledTextBoxProps {
area: right_chunks[3],
label: "Tags",
text: &edit_indexer_modal.tags.text,
offset: *edit_indexer_modal.tags.offset.borrow(),
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerTagsInput,
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerTagsInput,
cursor_after_string: true,
},
);
} else {
draw_text_box_with_label(
f,
LabeledTextBoxProps {
area: right_chunks[2],
label: "Tags",
text: &edit_indexer_modal.tags.text,
offset: *edit_indexer_modal.tags.offset.borrow(),
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerTagsInput,
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerTagsInput,
cursor_after_string: true,
},
);
}
draw_checkbox_with_label(
f,
left_chunks[1],
"Enable RSS",
edit_indexer_modal.enable_rss.unwrap_or_default(),
selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableRss,
);
draw_checkbox_with_label(
f,
left_chunks[2],
"Enable Automatic Search",
edit_indexer_modal
.enable_automatic_search
.unwrap_or_default(),
selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch,
);
draw_checkbox_with_label(
f,
left_chunks[3],
"Enable Interactive Search",
edit_indexer_modal
.enable_interactive_search
.unwrap_or_default(),
selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch,
);
let button_chunks = horizontal_chunks(
iter::repeat(Constraint::Ratio(1, 4)).take(4).collect(),
chunks[1],
);
draw_button(
f,
button_chunks[1],
"Save",
yes_no_value && highlight_yes_no,
);
draw_button(
f,
button_chunks[2],
"Cancel",
!yes_no_value && highlight_yes_no,
);
}
} else {
loading(f, block, prompt_area, app.is_loading);
}
}
@@ -0,0 +1,18 @@
#[cfg(test)]
mod tests {
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::ui::radarr_ui::indexers::edit_indexer_ui::EditIndexerUi;
use crate::ui::DrawUi;
use strum::IntoEnumIterator;
#[test]
fn test_edit_indexer_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if EDIT_INDEXER_BLOCKS.contains(&active_radarr_block) {
assert!(EditIndexerUi::accepts(active_radarr_block.into()));
} else {
assert!(!EditIndexerUi::accepts(active_radarr_block.into()));
}
});
}
}
@@ -13,7 +13,8 @@ use crate::ui::utils::{
vertical_chunks_with_margin,
};
use crate::ui::{
draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading, DrawUi,
draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading,
DrawUi, LabeledTextBoxProps,
};
#[cfg(test)]
@@ -91,57 +92,82 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
draw_text_box_with_label(
f,
left_chunks[0],
"Minimum Age (minutes) ▴▾",
&indexer_settings.minimum_age.to_string(),
0,
selected_block == &ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
active_radarr_block == ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
LabeledTextBoxProps {
area: left_chunks[0],
label: "Minimum Age (minutes) ▴▾",
text: &indexer_settings.minimum_age.to_string(),
offset: 0,
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
should_show_cursor: active_radarr_block
== ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
cursor_after_string: false,
},
);
draw_text_box_with_label(
f,
left_chunks[1],
"Retention (days) ▴▾",
&indexer_settings.retention.to_string(),
0,
selected_block == &ActiveRadarrBlock::IndexerSettingsRetentionInput,
active_radarr_block == ActiveRadarrBlock::IndexerSettingsRetentionInput,
LabeledTextBoxProps {
area: left_chunks[1],
label: "Retention (days) ▴▾",
text: &indexer_settings.retention.to_string(),
offset: 0,
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsRetentionInput,
should_show_cursor: active_radarr_block
== ActiveRadarrBlock::IndexerSettingsRetentionInput,
cursor_after_string: false,
},
);
draw_text_box_with_label(
f,
left_chunks[2],
"Maximum Size (MB) ▴▾",
&indexer_settings.maximum_size.to_string(),
0,
selected_block == &ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
active_radarr_block == ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
LabeledTextBoxProps {
area: left_chunks[2],
label: "Maximum Size (MB) ▴▾",
text: &indexer_settings.maximum_size.to_string(),
offset: 0,
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
should_show_cursor: active_radarr_block
== ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
cursor_after_string: false,
},
);
draw_text_box_with_label(
f,
right_chunks[0],
"Availability Delay (days) ▴▾",
&indexer_settings.availability_delay.to_string(),
0,
selected_block == &ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
active_radarr_block == ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
LabeledTextBoxProps {
area: right_chunks[0],
label: "Availability Delay (days) ▴▾",
text: &indexer_settings.availability_delay.to_string(),
offset: 0,
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
should_show_cursor: active_radarr_block
== ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
cursor_after_string: false,
},
);
draw_text_box_with_label(
f,
right_chunks[1],
"RSS Sync Interval (minutes) ▴▾",
&indexer_settings.rss_sync_interval.to_string(),
0,
selected_block == &ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
active_radarr_block == ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
LabeledTextBoxProps {
area: right_chunks[1],
label: "RSS Sync Interval (minutes) ▴▾",
text: &indexer_settings.rss_sync_interval.to_string(),
offset: 0,
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
should_show_cursor: active_radarr_block
== ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
cursor_after_string: false,
},
);
draw_text_box_with_label(
f,
right_chunks[2],
"Whitelisted Subtitle Tags",
&indexer_settings.whitelisted_hardcoded_subs.text,
*indexer_settings.whitelisted_hardcoded_subs.offset.borrow(),
selected_block == &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
active_radarr_block == ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
LabeledTextBoxProps {
area: right_chunks[2],
label: "Whitelisted Subtitle Tags",
text: &indexer_settings.whitelisted_hardcoded_subs.text,
offset: *indexer_settings.whitelisted_hardcoded_subs.offset.borrow(),
is_selected: selected_block
== &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
should_show_cursor: active_radarr_block
== ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
cursor_after_string: true,
},
);
}
@@ -3,7 +3,7 @@ mod tests {
use strum::IntoEnumIterator;
use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS,
ActiveRadarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS,
};
use crate::ui::radarr_ui::indexers::IndexersUi;
use crate::ui::DrawUi;
@@ -13,6 +13,8 @@ mod tests {
let mut indexers_blocks = Vec::new();
indexers_blocks.extend(INDEXERS_BLOCKS);
indexers_blocks.extend(INDEXER_SETTINGS_BLOCKS);
indexers_blocks.extend(EDIT_INDEXER_BLOCKS);
indexers_blocks.push(ActiveRadarrBlock::TestAllIndexers);
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if indexers_blocks.contains(&active_radarr_block) {
+30 -7
View File
@@ -7,24 +7,29 @@ use crate::app::App;
use crate::models::radarr_models::Indexer;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, INDEXERS_BLOCKS};
use crate::models::Route;
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::test_all_indexers_ui::TestAllIndexersUi;
use crate::ui::utils::{layout_block_top_border, style_failure, style_primary, style_success};
use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps};
mod edit_indexer_ui;
mod indexer_settings_ui;
mod test_all_indexers_ui;
#[cfg(test)]
#[path = "indexers_ui_tests.rs"]
mod indexers_ui_tests;
mod test_all_indexers_ui;
pub(super) struct IndexersUi;
impl DrawUi for IndexersUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return IndexerSettingsUi::accepts(route) || INDEXERS_BLOCKS.contains(&active_radarr_block);
return EditIndexerUi::accepts(route)
|| IndexerSettingsUi::accepts(route)
|| TestAllIndexersUi::accepts(route)
|| INDEXERS_BLOCKS.contains(&active_radarr_block);
}
false
@@ -45,6 +50,7 @@ impl DrawUi for IndexersUi {
};
match route {
_ if EditIndexerUi::accepts(route) => EditIndexerUi::draw(f, app, content_rect),
_ if IndexerSettingsUi::accepts(route) => IndexerSettingsUi::draw(f, app, content_rect),
_ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, content_rect),
Route::Radarr(active_radarr_block, _) if INDEXERS_BLOCKS.contains(&active_radarr_block) => {
@@ -69,13 +75,15 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
"Automatic Search",
"Interactive Search",
"Priority",
"Tags",
],
constraints: vec![
Constraint::Ratio(1, 5),
Constraint::Ratio(1, 5),
Constraint::Ratio(1, 5),
Constraint::Ratio(1, 5),
Constraint::Ratio(1, 5),
Constraint::Percentage(25),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(13),
Constraint::Percentage(23),
],
help: app
.data
@@ -90,6 +98,7 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
enable_automatic_search,
enable_interactive_search,
priority,
tags,
..
} = indexer;
let bool_to_text = |flag: bool| {
@@ -112,6 +121,19 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
bool_to_text(*enable_interactive_search);
let mut interactive_search = Text::from(interactive_search_text);
interactive_search.patch_style(interactive_search_style);
let tags: String = tags
.iter()
.map(|tag_id| {
app
.data
.radarr_data
.tags_map
.get_by_left(&tag_id.as_i64().unwrap())
.unwrap()
.clone()
})
.collect::<Vec<String>>()
.join(", ");
Row::new(vec![
Cell::from(name.clone().unwrap_or_default()),
@@ -119,6 +141,7 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Cell::from(automatic_search),
Cell::from(interactive_search),
Cell::from(priority.to_string()),
Cell::from(tags),
])
.style(style_primary())
},
+29 -19
View File
@@ -19,7 +19,8 @@ use crate::ui::utils::{
use crate::ui::{
draw_button, draw_drop_down_menu_button, draw_drop_down_popup, draw_error_popup,
draw_error_popup_over, draw_large_popup_over, draw_medium_popup_over, draw_selectable_list,
draw_table, draw_text_box, draw_text_box_with_label, DrawUi, TableProps,
draw_table, draw_text_box, draw_text_box_with_label, DrawUi, LabeledTextBoxProps, TableProps,
TextBoxProps,
};
use crate::utils::convert_runtime;
use crate::App;
@@ -135,12 +136,15 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
ActiveRadarrBlock::AddMovieSearchInput => {
draw_text_box(
f,
chunks[0],
Some("Add Movie"),
block_content,
offset,
true,
false,
TextBoxProps {
text_box_area: chunks[0],
block_title: Some("Add Movie"),
block_content,
offset,
should_show_cursor: true,
is_selected: false,
cursor_after_string: true,
},
);
f.render_widget(layout_block(), chunks[1]);
@@ -267,12 +271,15 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_text_box(
f,
chunks[0],
Some("Add Movie"),
block_content,
offset,
false,
false,
TextBoxProps {
text_box_area: chunks[0],
block_title: Some("Add Movie"),
block_content,
offset,
should_show_cursor: false,
is_selected: false,
cursor_after_string: true,
},
);
}
@@ -441,12 +448,15 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
draw_text_box_with_label(
f,
chunks[5],
"Tags",
&tags.text,
*tags.offset.borrow(),
selected_block == &ActiveRadarrBlock::AddMovieTagsInput,
active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput,
LabeledTextBoxProps {
area: chunks[5],
label: "Tags",
text: &tags.text,
offset: *tags.offset.borrow(),
is_selected: selected_block == &ActiveRadarrBlock::AddMovieTagsInput,
should_show_cursor: active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput,
cursor_after_string: true,
},
);
}
+19 -13
View File
@@ -17,7 +17,7 @@ use crate::ui::utils::{
use crate::ui::{
draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup,
draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup,
draw_selectable_list, draw_text_box_with_label, DrawUi,
draw_selectable_list, draw_text_box_with_label, DrawUi, LabeledTextBoxProps,
};
#[cfg(test)]
@@ -179,21 +179,27 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, pro
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
draw_text_box_with_label(
f,
chunks[4],
"Path",
&path.text,
*path.offset.borrow(),
selected_block == &ActiveRadarrBlock::EditMoviePathInput,
active_radarr_block == ActiveRadarrBlock::EditMoviePathInput,
LabeledTextBoxProps {
area: chunks[4],
label: "Path",
text: &path.text,
offset: *path.offset.borrow(),
is_selected: selected_block == &ActiveRadarrBlock::EditMoviePathInput,
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditMoviePathInput,
cursor_after_string: true,
},
);
draw_text_box_with_label(
f,
chunks[5],
"Tags",
&tags.text,
*tags.offset.borrow(),
selected_block == &ActiveRadarrBlock::EditMovieTagsInput,
active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput,
LabeledTextBoxProps {
area: chunks[5],
label: "Tags",
text: &tags.text,
offset: *tags.offset.borrow(),
is_selected: selected_block == &ActiveRadarrBlock::EditMovieTagsInput,
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput,
cursor_after_string: true,
},
);
}