Refactored table sorting into the ManagarrTable widget and StatefulTable so any and all tables created can support sorting with minimal UI changes and thus only need to focus on the handlers. I'm going to continue this effort tomorrow and look at what other widgets can be created to simplify things. Most likely, prompt boxes.
This commit is contained in:
+5
-29
@@ -10,11 +10,12 @@ use ratatui::widgets::{Clear, List, ListItem};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::models::{HorizontallyScrollableText, Route, StatefulList, TabState};
|
||||
use crate::models::stateful_list::StatefulList;
|
||||
use crate::models::{HorizontallyScrollableText, Route, TabState};
|
||||
use crate::ui::radarr_ui::RadarrUi;
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::{
|
||||
background_block, borderless_block, centered_rect, layout_block, layout_block_top_border,
|
||||
background_block, borderless_block, centered_rect, layout_block_top_border,
|
||||
layout_paragraph_borderless, logo_block, title_block, title_block_centered,
|
||||
};
|
||||
use crate::ui::widgets::button::Button;
|
||||
@@ -210,25 +211,14 @@ pub fn draw_large_popup_over_background_fn_with_ui<T: DrawUi>(
|
||||
draw_popup_over_ui::<T>(f, app, area, background_fn, 75, 75);
|
||||
}
|
||||
|
||||
pub fn draw_drop_down_popup(
|
||||
f: &mut Frame<'_>,
|
||||
app: &mut App<'_>,
|
||||
area: Rect,
|
||||
background_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect),
|
||||
drop_down_fn: impl Fn(&mut Frame<'_>, &mut App<'_>, Rect),
|
||||
) {
|
||||
draw_popup_over(f, app, area, background_fn, drop_down_fn, 20, 30);
|
||||
}
|
||||
|
||||
fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -> Rect {
|
||||
f.render_widget(title_block(title), area);
|
||||
|
||||
let [header_area, content_area] = Layout::vertical([Constraint::Length(1), Constraint::Fill(0)])
|
||||
.margin(1)
|
||||
.areas(area);
|
||||
let [tabs_area, help_area] = Layout::horizontal([Constraint::Min(25), Constraint::Min(35)])
|
||||
.flex(Flex::SpaceBetween)
|
||||
.areas(header_area);
|
||||
let [tabs_area, help_area] =
|
||||
Layout::horizontal([Constraint::Percentage(45), Constraint::Fill(0)]).areas(header_area);
|
||||
|
||||
let titles = tab_state
|
||||
.tabs
|
||||
@@ -361,20 +351,6 @@ pub fn draw_prompt_box_with_checkboxes(
|
||||
f.render_widget(no_button, no_area);
|
||||
}
|
||||
|
||||
pub fn draw_selectable_list<'a, T>(
|
||||
f: &mut Frame<'_>,
|
||||
area: Rect,
|
||||
content: &'a mut StatefulList<T>,
|
||||
item_mapper: impl Fn(&T) -> ListItem<'a>,
|
||||
) {
|
||||
let items: Vec<ListItem<'_>> = content.items.iter().map(item_mapper).collect();
|
||||
let list = List::new(items)
|
||||
.block(layout_block())
|
||||
.highlight_style(Style::new().highlight());
|
||||
|
||||
f.render_stateful_widget(list, area, &mut content.state);
|
||||
}
|
||||
|
||||
pub fn draw_list_box<'a, T>(
|
||||
f: &mut Frame<'_>,
|
||||
area: Rect,
|
||||
|
||||
@@ -16,9 +16,10 @@ use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
|
||||
use crate::ui::widgets::button::Button;
|
||||
use crate::ui::widgets::checkbox::Checkbox;
|
||||
use crate::ui::widgets::input_box::InputBox;
|
||||
use crate::ui::widgets::popup::Popup;
|
||||
use crate::ui::widgets::selectable_list::SelectableList;
|
||||
use crate::ui::{
|
||||
draw_drop_down_popup, draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over,
|
||||
draw_popup, draw_selectable_list, DrawUi,
|
||||
draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup, DrawUi,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -41,22 +42,12 @@ impl DrawUi for EditCollectionUi {
|
||||
let draw_edit_collection_prompt =
|
||||
|f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block {
|
||||
ActiveRadarrBlock::EditCollectionSelectMinimumAvailability => {
|
||||
draw_drop_down_popup(
|
||||
f,
|
||||
app,
|
||||
prompt_area,
|
||||
draw_edit_collection_confirmation_prompt,
|
||||
draw_edit_collection_select_minimum_availability_popup,
|
||||
);
|
||||
draw_edit_collection_confirmation_prompt(f, app, prompt_area);
|
||||
draw_edit_collection_select_minimum_availability_popup(f, app);
|
||||
}
|
||||
ActiveRadarrBlock::EditCollectionSelectQualityProfile => {
|
||||
draw_drop_down_popup(
|
||||
f,
|
||||
app,
|
||||
prompt_area,
|
||||
draw_edit_collection_confirmation_prompt,
|
||||
draw_edit_collection_select_quality_profile_popup,
|
||||
);
|
||||
draw_edit_collection_confirmation_prompt(f, app, prompt_area);
|
||||
draw_edit_collection_select_quality_profile_popup(f, app);
|
||||
}
|
||||
ActiveRadarrBlock::EditCollectionPrompt
|
||||
| ActiveRadarrBlock::EditCollectionToggleMonitored
|
||||
@@ -180,14 +171,8 @@ fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>
|
||||
f.render_widget(cancel_button, cancel_area);
|
||||
}
|
||||
|
||||
fn draw_edit_collection_select_minimum_availability_popup(
|
||||
f: &mut Frame<'_>,
|
||||
app: &mut App<'_>,
|
||||
area: Rect,
|
||||
) {
|
||||
draw_selectable_list(
|
||||
f,
|
||||
area,
|
||||
fn draw_edit_collection_select_minimum_availability_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let min_availability_list = SelectableList::new(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -197,16 +182,16 @@ fn draw_edit_collection_select_minimum_availability_popup(
|
||||
.minimum_availability_list,
|
||||
|minimum_availability| ListItem::new(minimum_availability.to_display_str().to_owned()),
|
||||
);
|
||||
let popup = Popup::new(min_availability_list, 20, 30);
|
||||
|
||||
f.render_widget(popup, f.size());
|
||||
}
|
||||
|
||||
fn draw_edit_collection_select_quality_profile_popup(
|
||||
f: &mut Frame<'_>,
|
||||
app: &mut App<'_>,
|
||||
area: Rect,
|
||||
) {
|
||||
draw_selectable_list(
|
||||
f,
|
||||
area,
|
||||
let quality_profile_list = SelectableList::new(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -216,4 +201,7 @@ fn draw_edit_collection_select_quality_profile_popup(
|
||||
.quality_profile_list,
|
||||
|quality_profile| ListItem::new(quality_profile.clone()),
|
||||
);
|
||||
let popup = Popup::new(quality_profile_list, 20, 30);
|
||||
|
||||
f.render_widget(popup, f.size());
|
||||
}
|
||||
|
||||
@@ -21,9 +21,8 @@ use crate::ui::widgets::error_message::ErrorMessage;
|
||||
use crate::ui::widgets::input_box::InputBox;
|
||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||
use crate::ui::widgets::popup::Popup;
|
||||
use crate::ui::{
|
||||
draw_drop_down_popup, draw_large_popup_over, draw_medium_popup_over, draw_selectable_list, DrawUi,
|
||||
};
|
||||
use crate::ui::widgets::selectable_list::SelectableList;
|
||||
use crate::ui::{draw_large_popup_over, draw_medium_popup_over, DrawUi};
|
||||
use crate::utils::convert_runtime;
|
||||
use crate::{render_selectable_input_box, App};
|
||||
|
||||
@@ -266,40 +265,20 @@ fn draw_confirmation_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||
match active_radarr_block {
|
||||
ActiveRadarrBlock::AddMovieSelectMonitor => {
|
||||
draw_drop_down_popup(
|
||||
f,
|
||||
app,
|
||||
area,
|
||||
draw_confirmation_prompt,
|
||||
draw_add_movie_select_monitor_popup,
|
||||
);
|
||||
draw_confirmation_prompt(f, app, area);
|
||||
draw_add_movie_select_monitor_popup(f, app);
|
||||
}
|
||||
ActiveRadarrBlock::AddMovieSelectMinimumAvailability => {
|
||||
draw_drop_down_popup(
|
||||
f,
|
||||
app,
|
||||
area,
|
||||
draw_confirmation_prompt,
|
||||
draw_add_movie_select_minimum_availability_popup,
|
||||
);
|
||||
draw_confirmation_prompt(f, app, area);
|
||||
draw_add_movie_select_minimum_availability_popup(f, app);
|
||||
}
|
||||
ActiveRadarrBlock::AddMovieSelectQualityProfile => {
|
||||
draw_drop_down_popup(
|
||||
f,
|
||||
app,
|
||||
area,
|
||||
draw_confirmation_prompt,
|
||||
draw_add_movie_select_quality_profile_popup,
|
||||
);
|
||||
draw_confirmation_prompt(f, app, area);
|
||||
draw_add_movie_select_quality_profile_popup(f, app);
|
||||
}
|
||||
ActiveRadarrBlock::AddMovieSelectRootFolder => {
|
||||
draw_drop_down_popup(
|
||||
f,
|
||||
app,
|
||||
area,
|
||||
draw_confirmation_prompt,
|
||||
draw_add_movie_select_root_folder_popup,
|
||||
);
|
||||
draw_confirmation_prompt(f, app, area);
|
||||
draw_add_movie_select_root_folder_popup(f, app);
|
||||
}
|
||||
ActiveRadarrBlock::AddMoviePrompt | ActiveRadarrBlock::AddMovieTagsInput => {
|
||||
draw_confirmation_prompt(f, app, area)
|
||||
@@ -437,10 +416,8 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
f.render_widget(cancel_button, cancel_area);
|
||||
}
|
||||
|
||||
fn draw_add_movie_select_monitor_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
draw_selectable_list(
|
||||
f,
|
||||
area,
|
||||
fn draw_add_movie_select_monitor_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let monitor_list = SelectableList::new(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -450,16 +427,13 @@ fn draw_add_movie_select_monitor_popup(f: &mut Frame<'_>, app: &mut App<'_>, are
|
||||
.monitor_list,
|
||||
|monitor| ListItem::new(monitor.to_display_str().to_owned()),
|
||||
);
|
||||
let popup = Popup::new(monitor_list, 20, 30);
|
||||
|
||||
f.render_widget(popup, f.size());
|
||||
}
|
||||
|
||||
fn draw_add_movie_select_minimum_availability_popup(
|
||||
f: &mut Frame<'_>,
|
||||
app: &mut App<'_>,
|
||||
area: Rect,
|
||||
) {
|
||||
draw_selectable_list(
|
||||
f,
|
||||
area,
|
||||
fn draw_add_movie_select_minimum_availability_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let minimum_availability_list = SelectableList::new(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -469,12 +443,13 @@ fn draw_add_movie_select_minimum_availability_popup(
|
||||
.minimum_availability_list,
|
||||
|minimum_availability| ListItem::new(minimum_availability.to_display_str().to_owned()),
|
||||
);
|
||||
let popup = Popup::new(minimum_availability_list, 20, 30);
|
||||
|
||||
f.render_widget(popup, f.size());
|
||||
}
|
||||
|
||||
fn draw_add_movie_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
draw_selectable_list(
|
||||
f,
|
||||
area,
|
||||
fn draw_add_movie_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let quality_profile_list = SelectableList::new(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -484,12 +459,13 @@ fn draw_add_movie_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<
|
||||
.quality_profile_list,
|
||||
|quality_profile| ListItem::new(quality_profile.clone()),
|
||||
);
|
||||
let popup = Popup::new(quality_profile_list, 20, 30);
|
||||
|
||||
f.render_widget(popup, f.size());
|
||||
}
|
||||
|
||||
fn draw_add_movie_select_root_folder_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
draw_selectable_list(
|
||||
f,
|
||||
area,
|
||||
fn draw_add_movie_select_root_folder_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let root_folder_list = SelectableList::new(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -499,4 +475,7 @@ fn draw_add_movie_select_root_folder_popup(f: &mut Frame<'_>, app: &mut App<'_>,
|
||||
.root_folder_list,
|
||||
|root_folder| ListItem::new(root_folder.path.to_owned()),
|
||||
);
|
||||
let popup = Popup::new(root_folder_list, 20, 30);
|
||||
|
||||
f.render_widget(popup, f.size());
|
||||
}
|
||||
|
||||
@@ -18,9 +18,10 @@ use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
|
||||
use crate::ui::widgets::button::Button;
|
||||
use crate::ui::widgets::checkbox::Checkbox;
|
||||
use crate::ui::widgets::input_box::InputBox;
|
||||
use crate::ui::widgets::popup::Popup;
|
||||
use crate::ui::widgets::selectable_list::SelectableList;
|
||||
use crate::ui::{
|
||||
draw_drop_down_popup, draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over,
|
||||
draw_popup, draw_selectable_list, DrawUi,
|
||||
draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup, DrawUi,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -43,22 +44,12 @@ impl DrawUi for EditMovieUi {
|
||||
let draw_edit_movie_prompt =
|
||||
|f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block {
|
||||
ActiveRadarrBlock::EditMovieSelectMinimumAvailability => {
|
||||
draw_drop_down_popup(
|
||||
f,
|
||||
app,
|
||||
prompt_area,
|
||||
draw_edit_movie_confirmation_prompt,
|
||||
draw_edit_movie_select_minimum_availability_popup,
|
||||
);
|
||||
draw_edit_movie_confirmation_prompt(f, app, prompt_area);
|
||||
draw_edit_movie_select_minimum_availability_popup(f, app);
|
||||
}
|
||||
ActiveRadarrBlock::EditMovieSelectQualityProfile => {
|
||||
draw_drop_down_popup(
|
||||
f,
|
||||
app,
|
||||
prompt_area,
|
||||
draw_edit_movie_confirmation_prompt,
|
||||
draw_edit_movie_select_quality_profile_popup,
|
||||
);
|
||||
draw_edit_movie_confirmation_prompt(f, app, prompt_area);
|
||||
draw_edit_movie_select_quality_profile_popup(f, app);
|
||||
}
|
||||
ActiveRadarrBlock::EditMoviePrompt
|
||||
| ActiveRadarrBlock::EditMovieToggleMonitored
|
||||
@@ -190,14 +181,8 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, are
|
||||
f.render_widget(cancel_button, cancel_area);
|
||||
}
|
||||
|
||||
fn draw_edit_movie_select_minimum_availability_popup(
|
||||
f: &mut Frame<'_>,
|
||||
app: &mut App<'_>,
|
||||
area: Rect,
|
||||
) {
|
||||
draw_selectable_list(
|
||||
f,
|
||||
area,
|
||||
fn draw_edit_movie_select_minimum_availability_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let minimum_availability_list = SelectableList::new(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -207,12 +192,13 @@ fn draw_edit_movie_select_minimum_availability_popup(
|
||||
.minimum_availability_list,
|
||||
|minimum_availability| ListItem::new(minimum_availability.to_display_str().to_owned()),
|
||||
);
|
||||
let popup = Popup::new(minimum_availability_list, 20, 30);
|
||||
|
||||
f.render_widget(popup, f.size());
|
||||
}
|
||||
|
||||
fn draw_edit_movie_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
draw_selectable_list(
|
||||
f,
|
||||
area,
|
||||
fn draw_edit_movie_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
let quality_profile_list = SelectableList::new(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -222,4 +208,7 @@ fn draw_edit_movie_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App
|
||||
.quality_profile_list,
|
||||
|quality_profile| ListItem::new(quality_profile.clone()),
|
||||
);
|
||||
let popup = Popup::new(quality_profile_list, 20, 30);
|
||||
|
||||
f.render_widget(popup, f.size());
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ use std::iter;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::style::{Style, Stylize};
|
||||
use ratatui::text::{Line, Span, Text};
|
||||
use ratatui::widgets::{Cell, ListItem, Paragraph, Row, Wrap};
|
||||
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::models::radarr_models::{Credit, MovieHistoryItem, Release, ReleaseField};
|
||||
use crate::models::radarr_models::{Credit, MovieHistoryItem, Release};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS};
|
||||
use crate::models::Route;
|
||||
use crate::ui::radarr_ui::library::draw_library;
|
||||
@@ -18,8 +18,8 @@ use crate::ui::utils::{
|
||||
use crate::ui::widgets::loading_block::LoadingBlock;
|
||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||
use crate::ui::{
|
||||
draw_drop_down_popup, draw_large_popup_over, draw_prompt_box, draw_prompt_box_with_content,
|
||||
draw_prompt_popup_over, draw_selectable_list, draw_small_popup_over, draw_tabs, DrawUi,
|
||||
draw_large_popup_over, draw_prompt_box, draw_prompt_box_with_content, draw_prompt_popup_over,
|
||||
draw_small_popup_over, draw_tabs, DrawUi,
|
||||
};
|
||||
use crate::utils::convert_to_gb;
|
||||
|
||||
@@ -63,26 +63,6 @@ impl DrawUi for MovieDetailsUi {
|
||||
draw_movie_info,
|
||||
draw_update_and_scan_prompt,
|
||||
),
|
||||
ActiveRadarrBlock::ManualSearchSortPrompt => draw_drop_down_popup(
|
||||
f,
|
||||
app,
|
||||
content_area,
|
||||
draw_movie_info,
|
||||
|f, app, content_area| {
|
||||
draw_selectable_list(
|
||||
f,
|
||||
content_area,
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.movie_releases_sort,
|
||||
|sort_option| ListItem::new(sort_option.to_string()),
|
||||
)
|
||||
},
|
||||
),
|
||||
ActiveRadarrBlock::ManualSearchConfirmPrompt => draw_small_popup_over(
|
||||
f,
|
||||
app,
|
||||
@@ -388,142 +368,110 @@ fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
}
|
||||
|
||||
fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let (current_selection, is_empty, sort_ascending) =
|
||||
match app.data.radarr_data.movie_details_modal.as_ref() {
|
||||
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||
let (current_selection, is_empty) = match app.data.radarr_data.movie_details_modal.as_ref() {
|
||||
Some(movie_details_modal) if !movie_details_modal.movie_releases.items.is_empty() => (
|
||||
movie_details_modal
|
||||
.movie_releases
|
||||
.current_selection()
|
||||
.clone(),
|
||||
movie_details_modal.movie_releases.items.is_empty(),
|
||||
movie_details_modal.sort_ascending,
|
||||
),
|
||||
_ => (Release::default(), true, None),
|
||||
_ => (Release::default(), true),
|
||||
};
|
||||
let current_route = *app.get_current_route();
|
||||
let help_footer = app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_info_tabs
|
||||
.get_active_tab_contextual_help();
|
||||
let mut table_headers_vec = vec![
|
||||
"Source".to_owned(),
|
||||
"Age".to_owned(),
|
||||
"⛔".to_owned(),
|
||||
"Title".to_owned(),
|
||||
"Indexer".to_owned(),
|
||||
"Size".to_owned(),
|
||||
"Peers".to_owned(),
|
||||
"Language".to_owned(),
|
||||
"Quality".to_owned(),
|
||||
];
|
||||
|
||||
if let Some(ascending) = sort_ascending {
|
||||
let direction = if ascending { " ▲" } else { " ▼" };
|
||||
|
||||
match app
|
||||
let current_route = *app.get_current_route();
|
||||
let help_footer = app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.movie_releases_sort
|
||||
.current_selection()
|
||||
{
|
||||
ReleaseField::Source => table_headers_vec[0].push_str(direction),
|
||||
ReleaseField::Age => table_headers_vec[1].push_str(direction),
|
||||
ReleaseField::Rejected => table_headers_vec[2].push_str(direction),
|
||||
ReleaseField::Title => table_headers_vec[3].push_str(direction),
|
||||
ReleaseField::Indexer => table_headers_vec[4].push_str(direction),
|
||||
ReleaseField::Size => table_headers_vec[5].push_str(direction),
|
||||
ReleaseField::Peers => table_headers_vec[6].push_str(direction),
|
||||
ReleaseField::Language => table_headers_vec[7].push_str(direction),
|
||||
ReleaseField::Quality => table_headers_vec[8].push_str(direction),
|
||||
}
|
||||
}
|
||||
let content = Some(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.movie_releases,
|
||||
);
|
||||
let releases_row_mapping = |release: &Release| {
|
||||
let Release {
|
||||
protocol,
|
||||
age,
|
||||
title,
|
||||
indexer,
|
||||
size,
|
||||
rejected,
|
||||
seeders,
|
||||
leechers,
|
||||
languages,
|
||||
quality,
|
||||
..
|
||||
} = release;
|
||||
let age = format!("{age} days");
|
||||
title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 30),
|
||||
current_selection == *release
|
||||
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
|
||||
app.tick_count % app.ticks_until_scroll == 0,
|
||||
.movie_info_tabs
|
||||
.get_active_tab_contextual_help();
|
||||
let content = Some(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.movie_releases,
|
||||
);
|
||||
let size = convert_to_gb(*size);
|
||||
let rejected_str = if *rejected { "⛔" } else { "" };
|
||||
let peers = if seeders.is_none() || leechers.is_none() {
|
||||
Text::from("")
|
||||
} else {
|
||||
let seeders = seeders.clone().unwrap().as_u64().unwrap();
|
||||
let leechers = leechers.clone().unwrap().as_u64().unwrap();
|
||||
|
||||
decorate_peer_style(
|
||||
let releases_row_mapping = |release: &Release| {
|
||||
let Release {
|
||||
protocol,
|
||||
age,
|
||||
title,
|
||||
indexer,
|
||||
size,
|
||||
rejected,
|
||||
seeders,
|
||||
leechers,
|
||||
Text::from(format!("{seeders} / {leechers}")),
|
||||
)
|
||||
languages,
|
||||
quality,
|
||||
..
|
||||
} = release;
|
||||
let age = format!("{age} days");
|
||||
title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 30),
|
||||
current_selection == *release
|
||||
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
|
||||
app.tick_count % app.ticks_until_scroll == 0,
|
||||
);
|
||||
let size = convert_to_gb(*size);
|
||||
let rejected_str = if *rejected { "⛔" } else { "" };
|
||||
let peers = if seeders.is_none() || leechers.is_none() {
|
||||
Text::from("")
|
||||
} else {
|
||||
let seeders = seeders.clone().unwrap().as_u64().unwrap();
|
||||
let leechers = leechers.clone().unwrap().as_u64().unwrap();
|
||||
|
||||
decorate_peer_style(
|
||||
seeders,
|
||||
leechers,
|
||||
Text::from(format!("{seeders} / {leechers}")),
|
||||
)
|
||||
};
|
||||
|
||||
let language = if languages.is_some() {
|
||||
languages.clone().unwrap()[0].name.clone()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let quality = quality.quality.name.clone();
|
||||
|
||||
Row::new(vec![
|
||||
Cell::from(protocol.clone()),
|
||||
Cell::from(age),
|
||||
Cell::from(rejected_str),
|
||||
Cell::from(title.to_string()),
|
||||
Cell::from(indexer.clone()),
|
||||
Cell::from(format!("{size:.1} GB")),
|
||||
Cell::from(peers),
|
||||
Cell::from(language),
|
||||
Cell::from(quality),
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
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",
|
||||
])
|
||||
.constraints([
|
||||
Constraint::Length(9),
|
||||
Constraint::Length(10),
|
||||
Constraint::Length(5),
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Percentage(18),
|
||||
Constraint::Length(12),
|
||||
Constraint::Length(12),
|
||||
Constraint::Percentage(7),
|
||||
Constraint::Percentage(10),
|
||||
]);
|
||||
|
||||
let language = if languages.is_some() {
|
||||
languages.clone().unwrap()[0].name.clone()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let quality = quality.quality.name.clone();
|
||||
|
||||
Row::new(vec![
|
||||
Cell::from(protocol.clone()),
|
||||
Cell::from(age),
|
||||
Cell::from(rejected_str),
|
||||
Cell::from(title.to_string()),
|
||||
Cell::from(indexer.clone()),
|
||||
Cell::from(format!("{size:.1} GB")),
|
||||
Cell::from(peers),
|
||||
Cell::from(language),
|
||||
Cell::from(quality),
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let releases_table = ManagarrTable::new(content, releases_row_mapping)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading || is_empty)
|
||||
.footer(help_footer)
|
||||
.headers(table_headers_vec.iter().map(|s| &**s))
|
||||
.constraints([
|
||||
Constraint::Length(9),
|
||||
Constraint::Length(10),
|
||||
Constraint::Length(5),
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Percentage(18),
|
||||
Constraint::Length(12),
|
||||
Constraint::Length(12),
|
||||
Constraint::Percentage(7),
|
||||
Constraint::Percentage(10),
|
||||
]);
|
||||
|
||||
f.render_widget(releases_table, area);
|
||||
f.render_widget(releases_table, area);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
use crate::models::StatefulTable;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::layout_block_top_border;
|
||||
use crate::ui::widgets::loading_block::LoadingBlock;
|
||||
use crate::ui::widgets::popup::Popup;
|
||||
use crate::ui::widgets::selectable_list::SelectableList;
|
||||
use crate::ui::HIGHLIGHT_SYMBOL;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::prelude::{Style, Stylize, Text};
|
||||
use ratatui::widgets::{Block, Paragraph, Row, StatefulWidget, Table, Widget};
|
||||
use ratatui::widgets::{Block, ListItem, Paragraph, Row, StatefulWidget, Table, Widget};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub struct ManagarrTable<'a, T, F>
|
||||
where
|
||||
F: Fn(&T) -> Row<'a>,
|
||||
T: Clone + PartialEq + Eq + Debug,
|
||||
{
|
||||
content: Option<&'a mut StatefulTable<T>>,
|
||||
table_headers: Vec<Text<'a>>,
|
||||
table_headers: Vec<String>,
|
||||
constraints: Vec<Constraint>,
|
||||
row_mapper: F,
|
||||
footer: Option<String>,
|
||||
@@ -22,11 +26,13 @@ where
|
||||
margin: u16,
|
||||
is_loading: bool,
|
||||
highlight_rows: bool,
|
||||
is_sorting: bool,
|
||||
}
|
||||
|
||||
impl<'a, T, F> ManagarrTable<'a, T, F>
|
||||
where
|
||||
F: Fn(&T) -> Row<'a>,
|
||||
T: Clone + PartialEq + Eq + Debug,
|
||||
{
|
||||
pub fn new(content: Option<&'a mut StatefulTable<T>>, row_mapper: F) -> Self {
|
||||
Self {
|
||||
@@ -40,13 +46,14 @@ where
|
||||
margin: 0,
|
||||
is_loading: false,
|
||||
highlight_rows: true,
|
||||
is_sorting: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn headers<I>(mut self, headers: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Into<Text<'a>>,
|
||||
I::Item: Into<String>,
|
||||
{
|
||||
self.table_headers = headers.into_iter().map(Into::into).collect();
|
||||
self
|
||||
@@ -91,7 +98,13 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
pub fn sorting(mut self, is_sorting: bool) -> Self {
|
||||
self.is_sorting = is_sorting;
|
||||
self
|
||||
}
|
||||
|
||||
fn render_table(self, area: Rect, buf: &mut Buffer) {
|
||||
let table_headers = self.parse_headers();
|
||||
let table_area = if let Some(ref footer) = self.footer {
|
||||
let [content_area, footer_area] =
|
||||
Layout::vertical([Constraint::Fill(0), Constraint::Length(2)])
|
||||
@@ -121,10 +134,7 @@ where
|
||||
if !table_contents.is_empty() {
|
||||
let rows = table_contents.iter().map(&self.row_mapper);
|
||||
|
||||
let headers = Row::new(self.table_headers)
|
||||
.default()
|
||||
.bold()
|
||||
.bottom_margin(0);
|
||||
let headers = Row::new(table_headers).default().bold().bottom_margin(0);
|
||||
|
||||
let mut table = Table::new(rows, &self.constraints)
|
||||
.header(headers)
|
||||
@@ -137,6 +147,13 @@ where
|
||||
}
|
||||
|
||||
StatefulWidget::render(table, table_area, buf, table_state);
|
||||
|
||||
if content.sort.is_some() && self.is_sorting {
|
||||
let selectable_list = SelectableList::new(content.sort.as_mut().unwrap(), |item| {
|
||||
ListItem::new(Text::from(item.name))
|
||||
});
|
||||
Popup::new(selectable_list, 20, 50).render(table_area, buf);
|
||||
}
|
||||
} else {
|
||||
loading_block.render(table_area, buf);
|
||||
}
|
||||
@@ -144,11 +161,34 @@ where
|
||||
loading_block.render(table_area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_headers(&self) -> Vec<Text<'a>> {
|
||||
if let Some(ref content) = self.content {
|
||||
if let Some(ref sort_list) = content.sort {
|
||||
if !self.is_sorting {
|
||||
let mut new_headers = self.table_headers.clone();
|
||||
let idx = sort_list.state.selected().unwrap_or(0);
|
||||
let direction = if content.sort_asc { " ▲" } else { " ▼" };
|
||||
new_headers[idx].push_str(direction);
|
||||
|
||||
return new_headers.into_iter().map(Text::from).collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
.table_headers
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(Text::from)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, F> Widget for ManagarrTable<'a, T, F>
|
||||
where
|
||||
F: Fn(&T) -> Row<'a>,
|
||||
T: Clone + PartialEq + Eq + Debug,
|
||||
{
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_table(area, buf);
|
||||
|
||||
@@ -5,3 +5,4 @@ pub(super) mod input_box;
|
||||
pub(super) mod loading_block;
|
||||
pub(super) mod managarr_table;
|
||||
pub(super) mod popup;
|
||||
pub(super) mod selectable_list;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
use crate::models::stateful_list::StatefulList;
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::layout_block;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::Widget;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::widgets::{List, ListItem, StatefulWidget};
|
||||
|
||||
pub struct SelectableList<'a, T, F>
|
||||
where
|
||||
F: Fn(&T) -> ListItem<'a>,
|
||||
{
|
||||
content: &'a mut StatefulList<T>,
|
||||
row_mapper: F,
|
||||
}
|
||||
|
||||
impl<'a, T, F> SelectableList<'a, T, F>
|
||||
where
|
||||
F: Fn(&T) -> ListItem<'a>,
|
||||
{
|
||||
pub fn new(content: &'a mut StatefulList<T>, row_mapper: F) -> Self {
|
||||
Self {
|
||||
content,
|
||||
row_mapper,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_list(self, area: Rect, buf: &mut Buffer) {
|
||||
let items: Vec<ListItem<'_>> = self.content.items.iter().map(&self.row_mapper).collect();
|
||||
|
||||
let selectable_list = List::new(items)
|
||||
.block(layout_block())
|
||||
.highlight_style(Style::new().highlight());
|
||||
|
||||
StatefulWidget::render(selectable_list, area, buf, &mut self.content.state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, F> Widget for SelectableList<'a, T, F>
|
||||
where
|
||||
F: Fn(&T) -> ListItem<'a>,
|
||||
{
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_list(area, buf);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user