Refactored tables and loading blocks to use the new dedicated widgets for Tables and Loading blocks

This commit is contained in:
2024-02-10 19:23:19 -07:00
parent 68de986c48
commit 51b789fd0f
19 changed files with 1174 additions and 1150 deletions
+92 -90
View File
@@ -18,9 +18,10 @@ use crate::ui::utils::{
};
use crate::ui::widgets::button::Button;
use crate::ui::widgets::input_box::InputBox;
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::{
draw_drop_down_popup, draw_error_popup, draw_error_popup_over, draw_large_popup_over,
draw_medium_popup_over, draw_selectable_list, draw_table, DrawUi, TableProps,
draw_medium_popup_over, draw_selectable_list, DrawUi,
};
use crate::utils::convert_runtime;
use crate::{render_selectable_input_box, App};
@@ -116,6 +117,64 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.unwrap()
.offset
.borrow();
let search_results_row_mapping = |movie: &AddMovieSearchResult| {
let (hours, minutes) = convert_runtime(movie.runtime);
let imdb_rating = movie
.ratings
.imdb
.clone()
.unwrap_or_default()
.value
.as_f64()
.unwrap();
let rotten_tomatoes_rating = movie
.ratings
.rotten_tomatoes
.clone()
.unwrap_or_default()
.value
.as_u64()
.unwrap();
let imdb_rating = if imdb_rating == 0.0 {
String::new()
} else {
format!("{imdb_rating:.1}")
};
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
String::new()
} else {
format!("{rotten_tomatoes_rating}%")
};
let in_library = if app
.data
.radarr_data
.movies
.items
.iter()
.any(|mov| mov.tmdb_id == movie.tmdb_id)
{
""
} else {
""
};
movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27),
*movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
Row::new(vec![
Cell::from(in_library),
Cell::from(movie.title.to_string()),
Cell::from(movie.year.to_string()),
Cell::from(format!("{hours}h {minutes}m")),
Cell::from(imdb_rating),
Cell::from(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")),
])
.primary()
};
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block {
@@ -134,8 +193,14 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
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())
.alignment(Alignment::Center);
f.render_widget(layout_block(), results_area);
draw_error_popup(f, "No movies found matching your query!");
f.render_widget(help_paragraph, help_area);
}
ActiveRadarrBlock::AddMovieSearchResults
| ActiveRadarrBlock::AddMoviePrompt
@@ -150,96 +215,33 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let help_paragraph = Paragraph::new(help_text)
.block(borderless_block())
.alignment(Alignment::Center);
let search_results_table = ManagarrTable::new(
app.data.radarr_data.add_searched_movies.as_mut(),
search_results_row_mapping,
)
.loading(is_loading)
.block(layout_block())
.headers([
"",
"Title",
"Year",
"Runtime",
"IMDB",
"Rotten Tomatoes",
"Genres",
])
.constraints([
Constraint::Percentage(2),
Constraint::Percentage(27),
Constraint::Percentage(8),
Constraint::Percentage(10),
Constraint::Percentage(8),
Constraint::Percentage(14),
Constraint::Percentage(28),
]);
f.render_widget(search_results_table, results_area);
f.render_widget(help_paragraph, help_area);
draw_table(
f,
results_area,
layout_block(),
TableProps {
content: None,
wrapped_content: Some(app.data.radarr_data.add_searched_movies.as_mut()),
table_headers: vec![
"",
"Title",
"Year",
"Runtime",
"IMDB",
"Rotten Tomatoes",
"Genres",
],
constraints: vec![
Constraint::Percentage(2),
Constraint::Percentage(27),
Constraint::Percentage(8),
Constraint::Percentage(10),
Constraint::Percentage(8),
Constraint::Percentage(14),
Constraint::Percentage(28),
],
help: None,
},
|movie| {
let (hours, minutes) = convert_runtime(movie.runtime);
let imdb_rating = movie
.ratings
.imdb
.clone()
.unwrap_or_default()
.value
.as_f64()
.unwrap();
let rotten_tomatoes_rating = movie
.ratings
.rotten_tomatoes
.clone()
.unwrap_or_default()
.value
.as_u64()
.unwrap();
let imdb_rating = if imdb_rating == 0.0 {
String::new()
} else {
format!("{imdb_rating:.1}")
};
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
String::new()
} else {
format!("{rotten_tomatoes_rating}%")
};
let in_library = if app
.data
.radarr_data
.movies
.items
.iter()
.any(|mov| mov.tmdb_id == movie.tmdb_id)
{
""
} else {
""
};
movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27),
*movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
Row::new(vec![
Cell::from(in_library),
Cell::from(movie.title.to_string()),
Cell::from(movie.year.to_string()),
Cell::from(format!("{hours}h {minutes}m")),
Cell::from(imdb_rating),
Cell::from(rotten_tomatoes_rating),
Cell::from(movie.genres.join(", ")),
])
.primary()
},
is_loading,
true,
);
}
_ => (),
}
+79 -83
View File
@@ -12,9 +12,10 @@ use crate::ui::radarr_ui::library::delete_movie_ui::DeleteMovieUi;
use crate::ui::radarr_ui::library::edit_movie_ui::EditMovieUi;
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::widgets::managarr_table::ManagarrTable;
use crate::ui::{
draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box,
draw_prompt_popup_over, draw_table, DrawUi, TableProps,
draw_prompt_popup_over, DrawUi,
};
use crate::utils::{convert_runtime, convert_to_gb};
@@ -106,90 +107,85 @@ pub(super) fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Some(filtered_movies) if !app.data.radarr_data.is_filtering => Some(filtered_movies),
_ => Some(&mut app.data.radarr_data.movies),
};
let help_footer = app
.data
.radarr_data
.main_tabs
.get_active_tab_contextual_help();
draw_table(
f,
area,
layout_block_top_border(),
TableProps {
content,
wrapped_content: None,
table_headers: vec![
"Title",
"Year",
"Studio",
"Runtime",
"Rating",
"Language",
"Size",
"Quality Profile",
"Monitored",
"Tags",
],
constraints: vec![
Constraint::Percentage(27),
Constraint::Percentage(4),
Constraint::Percentage(17),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(10),
Constraint::Percentage(6),
Constraint::Percentage(12),
],
help: app
.data
.radarr_data
.main_tabs
.get_active_tab_contextual_help(),
},
|movie| {
movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27),
*movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
let monitored = if movie.monitored { "🏷" } else { "" };
let (hours, minutes) = convert_runtime(movie.runtime);
let file_size: f64 = convert_to_gb(movie.size_on_disk);
let certification = movie.certification.clone().unwrap_or_default();
let quality_profile = quality_profile_map
.get_by_left(&movie.quality_profile_id)
.unwrap()
.to_owned();
let tags = movie
.tags
.iter()
.map(|tag_id| {
tags_map
.get_by_left(&tag_id.as_i64().unwrap())
.unwrap()
.clone()
})
.collect::<Vec<String>>()
.join(", ");
let library_table_row_mapping = |movie: &Movie| {
movie.title.scroll_left_or_reset(
get_width_from_percentage(area, 27),
*movie == current_selection,
app.tick_count % app.ticks_until_scroll == 0,
);
let monitored = if movie.monitored { "🏷" } else { "" };
let (hours, minutes) = convert_runtime(movie.runtime);
let file_size: f64 = convert_to_gb(movie.size_on_disk);
let certification = movie.certification.clone().unwrap_or_default();
let quality_profile = quality_profile_map
.get_by_left(&movie.quality_profile_id)
.unwrap()
.to_owned();
let tags = movie
.tags
.iter()
.map(|tag_id| {
tags_map
.get_by_left(&tag_id.as_i64().unwrap())
.unwrap()
.clone()
})
.collect::<Vec<String>>()
.join(", ");
decorate_with_row_style(
downloads_vec,
movie,
Row::new(vec![
Cell::from(movie.title.to_string()),
Cell::from(movie.year.to_string()),
Cell::from(movie.studio.to_string()),
Cell::from(format!("{hours}h {minutes}m")),
Cell::from(certification),
Cell::from(movie.original_language.name.to_owned()),
Cell::from(format!("{file_size:.2} GB")),
Cell::from(quality_profile),
Cell::from(monitored.to_owned()),
Cell::from(tags),
]),
)
},
app.is_loading,
true,
);
decorate_with_row_style(
downloads_vec,
movie,
Row::new(vec![
Cell::from(movie.title.to_string()),
Cell::from(movie.year.to_string()),
Cell::from(movie.studio.to_string()),
Cell::from(format!("{hours}h {minutes}m")),
Cell::from(certification),
Cell::from(movie.original_language.name.to_owned()),
Cell::from(format!("{file_size:.2} GB")),
Cell::from(quality_profile),
Cell::from(monitored.to_owned()),
Cell::from(tags),
]),
)
};
let library_table = ManagarrTable::new(content, library_table_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.footer(help_footer)
.headers([
"Title",
"Year",
"Studio",
"Runtime",
"Rating",
"Language",
"Size",
"Quality Profile",
"Monitored",
"Tags",
])
.constraints([
Constraint::Percentage(27),
Constraint::Percentage(4),
Constraint::Percentage(17),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(10),
Constraint::Percentage(6),
Constraint::Percentage(12),
]);
f.render_widget(library_table, area);
}
fn draw_update_all_movies_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
+211 -222
View File
@@ -15,10 +15,11 @@ use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{
borderless_block, get_width_from_percentage, layout_block_bottom_border, layout_block_top_border,
};
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_table, draw_tabs,
loading, DrawUi, TableProps,
draw_prompt_popup_over, draw_selectable_list, draw_small_popup_over, draw_tabs, DrawUi,
};
use crate::utils::convert_to_gb;
@@ -189,7 +190,10 @@ fn draw_file_info(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(video_details_title_paragraph, video_details_title_area);
f.render_widget(video_details_paragraph, video_details_area);
}
_ => loading(f, layout_block_top_border(), area, app.is_loading),
_ => f.render_widget(
LoadingBlock::new(app.is_loading, layout_block_top_border()),
area,
),
}
}
@@ -230,11 +234,12 @@ fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
f.render_widget(paragraph, area);
}
_ => loading(
f,
block,
_ => f.render_widget(
LoadingBlock::new(
app.is_loading || app.data.radarr_data.movie_details_modal.is_none(),
block,
),
area,
app.is_loading || app.data.radarr_data.movie_details_modal.is_none(),
),
}
}
@@ -249,148 +254,137 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.current_selection()
.clone()
};
let history_row_mapping = |movie_history_item: &MovieHistoryItem| {
let MovieHistoryItem {
source_title,
quality,
languages,
date,
event_type,
} = movie_history_item;
draw_table(
f,
area,
layout_block_top_border(),
TableProps {
content: Some(&mut movie_details_modal.movie_history),
wrapped_content: None,
table_headers: vec!["Source Title", "Event Type", "Languages", "Quality", "Date"],
constraints: vec![
Constraint::Percentage(34),
Constraint::Percentage(17),
Constraint::Percentage(14),
Constraint::Percentage(14),
Constraint::Percentage(21),
],
help: app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help(),
},
|movie_history_item| {
let MovieHistoryItem {
source_title,
quality,
languages,
date,
event_type,
} = movie_history_item;
movie_history_item.source_title.scroll_left_or_reset(
get_width_from_percentage(area, 34),
current_selection == *movie_history_item,
app.tick_count % app.ticks_until_scroll == 0,
);
movie_history_item.source_title.scroll_left_or_reset(
get_width_from_percentage(area, 34),
current_selection == *movie_history_item,
app.tick_count % app.ticks_until_scroll == 0,
);
Row::new(vec![
Cell::from(source_title.to_string()),
Cell::from(event_type.to_owned()),
Cell::from(
languages
.iter()
.map(|language| language.name.to_owned())
.collect::<Vec<String>>()
.join(","),
),
Cell::from(quality.quality.name.to_owned()),
Cell::from(date.to_string()),
])
.success()
};
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),
Constraint::Percentage(17),
Constraint::Percentage(14),
Constraint::Percentage(14),
Constraint::Percentage(21),
]);
Row::new(vec![
Cell::from(source_title.to_string()),
Cell::from(event_type.to_owned()),
Cell::from(
languages
.iter()
.map(|language| language.name.to_owned())
.collect::<Vec<String>>()
.join(","),
),
Cell::from(quality.quality.name.to_owned()),
Cell::from(date.to_string()),
])
.success()
},
app.is_loading,
true,
);
f.render_widget(history_table, area);
}
}
fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_table(
f,
area,
layout_block_top_border(),
TableProps {
content: Some(
&mut app
.data
.radarr_data
.movie_details_modal
.as_mut()
.unwrap()
.movie_cast,
),
wrapped_content: None,
constraints: iter::repeat(Constraint::Ratio(1, 2)).take(2).collect(),
table_headers: vec!["Cast Member", "Character"],
help: app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help(),
},
|cast_member| {
let Credit {
person_name,
character,
..
} = cast_member;
let cast_row_mapping = |cast_member: &Credit| {
let Credit {
person_name,
character,
..
} = cast_member;
Row::new(vec![
Cell::from(person_name.to_owned()),
Cell::from(character.clone().unwrap_or_default()),
])
.success()
},
app.is_loading,
true,
Row::new(vec![
Cell::from(person_name.to_owned()),
Cell::from(character.clone().unwrap_or_default()),
])
.success()
};
let content = Some(
&mut app
.data
.radarr_data
.movie_details_modal
.as_mut()
.unwrap()
.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)]);
f.render_widget(cast_table, area);
}
fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_table(
f,
area,
layout_block_top_border(),
TableProps {
content: Some(
&mut app
.data
.radarr_data
.movie_details_modal
.as_mut()
.unwrap()
.movie_crew,
),
wrapped_content: None,
constraints: iter::repeat(Constraint::Ratio(1, 3)).take(3).collect(),
table_headers: vec!["Crew Member", "Job", "Department"],
help: app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help(),
},
|crew_member| {
let Credit {
person_name,
job,
department,
..
} = crew_member;
let crew_row_mapping = |crew_member: &Credit| {
let Credit {
person_name,
job,
department,
..
} = crew_member;
Row::new(vec![
Cell::from(person_name.to_owned()),
Cell::from(job.clone().unwrap_or_default()),
Cell::from(department.clone().unwrap_or_default()),
])
.success()
},
app.is_loading,
true,
Row::new(vec![
Cell::from(person_name.to_owned()),
Cell::from(job.clone().unwrap_or_default()),
Cell::from(department.clone().unwrap_or_default()),
])
.success()
};
let content = Some(
&mut app
.data
.radarr_data
.movie_details_modal
.as_mut()
.unwrap()
.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(Constraint::Ratio(1, 3)).take(3))
.footer(help_footer);
f.render_widget(crew_table, area);
}
fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
@@ -407,6 +401,11 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
_ => (Release::default(), true, None),
};
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(),
@@ -442,99 +441,89 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
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,
);
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();
draw_table(
f,
area,
layout_block_top_border(),
TableProps {
content: Some(
&mut app
.data
.radarr_data
.movie_details_modal
.as_mut()
.unwrap()
.movie_releases,
),
wrapped_content: None,
constraints: vec![
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),
],
table_headers: table_headers_vec.iter().map(|s| &**s).collect(),
help: app
.data
.radarr_data
.movie_info_tabs
.get_active_tab_contextual_help(),
},
|release| {
let Release {
protocol,
age,
title,
indexer,
size,
rejected,
decorate_peer_style(
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();
Text::from(format!("{seeders} / {leechers}")),
)
};
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();
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),
]);
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()
},
app.is_loading || is_empty,
true,
);
f.render_widget(releases_table, area);
}
fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {