feat: Refactor all keybinding tips into a dynamically changing menu that can be invoked via '?' [#32]

This commit is contained in:
Dark-Alex-17 committed 2025-08-12 16:27:34 -06:00
1 parent 1f4870d082
commit 00ab0f27f7
64 files changed
+1627 -903

No files matched your search

+7 -36
View File
@@ -1,14 +1,9 @@
use std::sync::atomic::Ordering;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::text::Text;
use ratatui::widgets::{Cell, ListItem, Paragraph, Row};
use ratatui::widgets::{Cell, ListItem, Row};
use ratatui::Frame;
use crate::app::context_clues::{
build_context_clue_string, BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
};
use crate::app::radarr::radarr_context_clues::ADD_MOVIE_SEARCH_RESULTS_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};
@@ -16,8 +11,7 @@ use crate::models::Route;
use crate::ui::radarr_ui::collections::CollectionsUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{
borderless_block, get_width_from_percentage, layout_block, layout_paragraph_borderless,
title_block_centered,
get_width_from_percentage, layout_block, layout_paragraph_borderless, title_block_centered,
};
use crate::ui::widgets::button::Button;
use crate::ui::widgets::input_box::InputBox;
@@ -83,13 +77,10 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
AddMovieSearchResult::default()
};
let [search_box_area, results_area, help_area] = Layout::vertical([
Constraint::Length(3),
Constraint::Fill(0),
Constraint::Length(3),
])
.margin(1)
.areas(area);
let [search_box_area, results_area] =
Layout::vertical([Constraint::Length(3), Constraint::Fill(0)])
.margin(1)
.areas(area);
let block_content = &app.data.radarr_data.add_movie_search.as_ref().unwrap().text;
let offset = app
.data
@@ -164,27 +155,17 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let search_box = InputBox::new(block_content)
.offset(offset)
.block(title_block_centered("Add Movie"));
let help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES).help());
let help_paragraph = Paragraph::new(help_text)
.block(borderless_block())
.centered();
search_box.show_cursor(f, search_box_area);
f.render_widget(layout_block().default(), results_area);
f.render_widget(search_box, search_box_area);
f.render_widget(help_paragraph, help_area);
}
ActiveRadarrBlock::AddMovieEmptySearchResults => {
let help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES).help());
let help_paragraph = Paragraph::new(help_text)
.block(borderless_block())
.centered();
let error_message = Message::new("No movies found matching your query!");
let error_message_popup = Popup::new(error_message).size(Size::Message);
f.render_widget(layout_block().default(), results_area);
f.render_widget(error_message_popup, f.area());
f.render_widget(help_paragraph, help_area);
}
ActiveRadarrBlock::AddMovieSearchResults
| ActiveRadarrBlock::AddMoviePrompt
@@ -194,11 +175,6 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
| ActiveRadarrBlock::AddMovieSelectRootFolder
| ActiveRadarrBlock::AddMovieAlreadyInLibrary
| ActiveRadarrBlock::AddMovieTagsInput => {
let help_text =
Text::from(build_context_clue_string(&ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES).help());
let help_paragraph = Paragraph::new(help_text)
.block(borderless_block())
.centered();
let search_results_table = ManagarrTable::new(
app.data.radarr_data.add_searched_movies.as_mut(),
search_results_row_mapping,
@@ -225,7 +201,6 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
]);
f.render_widget(search_results_table, results_area);
f.render_widget(help_paragraph, help_area);
}
_ => (),
}
@@ -322,7 +297,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, help_area] =
let [paragraph_area, root_folder_area, monitor_area, min_availability_area, quality_profile_area, tags_area, _, buttons_area] =
Layout::vertical([
Constraint::Length(6),
Constraint::Length(3),
@@ -332,16 +307,12 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Constraint::Length(3),
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 -8
View File
@@ -2,11 +2,9 @@ use std::sync::atomic::Ordering;
use ratatui::layout::{Constraint, Rect};
use ratatui::prelude::Layout;
use ratatui::text::Text;
use ratatui::widgets::{ListItem, Paragraph};
use ratatui::widgets::ListItem;
use ratatui::Frame;
use crate::app::context_clues::{build_context_clue_string, CONFIRMATION_PROMPT_CONTEXT_CLUES};
use crate::app::App;
use crate::models::servarr_data::radarr::modals::EditMovieModal;
use crate::models::servarr_data::radarr::radarr_data::{
@@ -94,7 +92,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, help_area] =
let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, path_area, tags_area, _, buttons_area] =
Layout::vertical([
Constraint::Length(6),
Constraint::Length(3),
@@ -104,7 +102,6 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, are
Constraint::Length(3),
Constraint::Fill(1),
Constraint::Length(3),
Constraint::Length(1),
])
.margin(1)
.areas(area);
@@ -112,8 +109,6 @@ 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())
@@ -164,7 +159,6 @@ 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<'_>) {
-6
View File
@@ -85,11 +85,6 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let tags_map = &app.data.radarr_data.tags_map;
let downloads_vec = &app.data.radarr_data.downloads.items;
let content = Some(&mut app.data.radarr_data.movies);
let help_footer = app
.data
.radarr_data
.main_tabs
.get_active_tab_contextual_help();
let library_table_row_mapping = |movie: &Movie| {
movie.title.scroll_left_or_reset(
@@ -143,7 +138,6 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let library_table = ManagarrTable::new(content, library_table_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.footer(help_footer)
.sorting(active_radarr_block == ActiveRadarrBlock::MoviesSortPrompt)
.searching(active_radarr_block == ActiveRadarrBlock::SearchMovie)
.search_produced_empty_results(active_radarr_block == ActiveRadarrBlock::SearchMovieError)
+1 -25
View File
@@ -259,18 +259,12 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
])
.primary()
};
let help_footer = app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help();
let history_table = ManagarrTable::new(
Some(&mut movie_details_modal.movie_history),
history_row_mapping,
)
.block(layout_block_top_border())
.loading(app.is_loading)
.footer(help_footer)
.headers(["Source Title", "Event Type", "Languages", "Quality", "Date"])
.constraints([
Constraint::Percentage(34),
@@ -301,14 +295,8 @@ fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.primary()
};
let content = Some(&mut movie_details_modal.movie_cast);
let help_footer = app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help();
let cast_table = ManagarrTable::new(content, cast_row_mapping)
.block(layout_block_top_border())
.footer(help_footer)
.loading(app.is_loading)
.headers(["Cast Member", "Character"])
.constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
@@ -344,17 +332,11 @@ fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.primary()
};
let content = Some(&mut movie_details_modal.movie_crew);
let help_footer = app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help();
let crew_table = ManagarrTable::new(content, crew_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.headers(["Crew Member", "Job", "Department"])
.constraints(iter::repeat_n(Constraint::Ratio(1, 3), 3))
.footer(help_footer);
.constraints(iter::repeat_n(Constraint::Ratio(1, 3), 3));
f.render_widget(crew_table, area);
}
@@ -383,11 +365,6 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
};
let current_route = app.get_current_route();
let mut default_movie_details_modal = MovieDetailsModal::default();
let help_footer = app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help();
let content = Some(
&mut app
.data
@@ -464,7 +441,6 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let releases_table = ManagarrTable::new(content, releases_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading || is_empty)
.footer(help_footer)
.sorting(active_radarr_block == ActiveRadarrBlock::ManualSearchSortPrompt)
.headers([
"Source", "Age", "⛔", "Title", "Indexer", "Size", "Peers", "Language", "Quality",