refactored managarr table initializer so a mutable app reference can be passed

This commit is contained in:
2026-01-05 09:49:03 -07:00
parent 50d4ddfa28
commit c8a06f3601
35 changed files with 955 additions and 775 deletions
+4 -3
View File
@@ -103,13 +103,14 @@ fn draw_blocklist_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
])
.primary()
};
let is_sorting = active_sonarr_block == ActiveSonarrBlock::BlocklistSortPrompt;
let blocklist_table = ManagarrTable::new(
Some(&mut app.data.sonarr_data.blocklist),
app,
|app| Some(&mut app.data.sonarr_data.blocklist),
blocklist_row_mapping,
)
.block(layout_block_top_border())
.loading(app.is_loading)
.sorting(active_sonarr_block == ActiveSonarrBlock::BlocklistSortPrompt)
.sorting(is_sorting)
.headers([
"Series Title",
"Source Title",
+4 -3
View File
@@ -72,6 +72,7 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
} else {
app.data.sonarr_data.downloads.current_selection().clone()
};
let ui_scroll_tick_count = app.ui_scroll_tick_count;
let downloads_row_mapping = |download_record: &DownloadRecord| {
let DownloadRecord {
@@ -88,7 +89,7 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
output_path.as_ref().unwrap().scroll_left_or_reset(
get_width_from_percentage(area, 18),
current_selection == *download_record,
app.ui_scroll_tick_count == 0,
ui_scroll_tick_count == 0,
);
}
@@ -120,11 +121,11 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.primary()
};
let downloads_table = ManagarrTable::new(
Some(&mut app.data.sonarr_data.downloads),
app,
|app| Some(&mut app.data.sonarr_data.downloads),
downloads_row_mapping,
)
.block(layout_block_top_border())
.loading(app.is_loading)
.headers([
"Title",
"Percent Complete",
+25 -21
View File
@@ -1,19 +1,19 @@
use crate::app::App;
use crate::models::Route;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, HISTORY_BLOCKS};
use crate::models::servarr_models::Language;
use crate::models::sonarr_models::{SonarrHistoryEventType, SonarrHistoryItem};
use crate::models::Route;
use crate::ui::DrawUi;
use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::message::Message;
use crate::ui::widgets::popup::{Popup, Size};
use crate::ui::DrawUi;
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Rect};
use ratatui::style::Style;
use ratatui::text::Text;
use ratatui::widgets::{Cell, Row};
use ratatui::Frame;
use super::sonarr_ui_utils::{
create_download_failed_history_event_details,
@@ -55,6 +55,8 @@ fn draw_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
} else {
app.data.sonarr_data.history.current_selection().clone()
};
let ui_scroll_tick_count = app.ui_scroll_tick_count;
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
let history_row_mapping = |history_item: &SonarrHistoryItem| {
let SonarrHistoryItem {
@@ -69,7 +71,7 @@ fn draw_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40),
current_selection == *history_item,
app.ui_scroll_tick_count == 0,
ui_scroll_tick_count == 0,
);
Row::new(vec![
@@ -93,23 +95,25 @@ fn draw_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
])
.primary()
};
let history_table =
ManagarrTable::new(Some(&mut app.data.sonarr_data.history), history_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.sorting(active_sonarr_block == ActiveSonarrBlock::HistorySortPrompt)
.searching(active_sonarr_block == ActiveSonarrBlock::SearchHistory)
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchHistoryError)
.filtering(active_sonarr_block == ActiveSonarrBlock::FilterHistory)
.filter_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::FilterHistoryError)
.headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
.constraints([
Constraint::Percentage(40),
Constraint::Percentage(15),
Constraint::Percentage(12),
Constraint::Percentage(13),
Constraint::Percentage(20),
]);
let history_table = ManagarrTable::new(
app,
|app| Some(&mut app.data.sonarr_data.history),
history_row_mapping,
)
.block(layout_block_top_border())
.sorting(active_sonarr_block == ActiveSonarrBlock::HistorySortPrompt)
.searching(active_sonarr_block == ActiveSonarrBlock::SearchHistory)
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchHistoryError)
.filtering(active_sonarr_block == ActiveSonarrBlock::FilterHistory)
.filter_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::FilterHistoryError)
.headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
.constraints([
Constraint::Percentage(40),
Constraint::Percentage(15),
Constraint::Percentage(12),
Constraint::Percentage(13),
Constraint::Percentage(20),
]);
if [
ActiveSonarrBlock::SearchHistory,
+5 -6
View File
@@ -111,6 +111,8 @@ impl DrawUi for IndexersUi {
}
fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let tags_map = app.data.sonarr_data.tags_map.clone();
let indexers_row_mapping = |indexer: &'_ Indexer| {
let Indexer {
name,
@@ -136,10 +138,7 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let tags: String = tags
.iter()
.map(|tag_id| {
app
.data
.sonarr_data
.tags_map
tags_map
.get_by_left(&tag_id.as_i64().unwrap())
.unwrap_or(&empty_tag)
.clone()
@@ -158,11 +157,11 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.primary()
};
let indexers_table = ManagarrTable::new(
Some(&mut app.data.sonarr_data.indexers),
app,
|app| Some(&mut app.data.sonarr_data.indexers),
indexers_row_mapping,
)
.block(layout_block_top_border())
.loading(app.is_loading)
.headers([
"Indexer",
"RSS",
@@ -39,12 +39,14 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are
} else {
IndexerTestResultModalItem::default()
};
let ui_scroll_tick_count = app.ui_scroll_tick_count;
f.render_widget(title_block("Test All Indexers"), area);
let test_results_row_mapping = |result: &IndexerTestResultModalItem| {
result.validation_failures.scroll_left_or_reset(
get_width_from_percentage(area, 86),
*result == current_selection,
app.ui_scroll_tick_count == 0,
ui_scroll_tick_count == 0,
);
let pass_fail = if result.is_valid { "" } else { "" };
let row = Row::new(vec![
@@ -61,7 +63,8 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are
};
let indexers_test_results_table = ManagarrTable::new(
app.data.sonarr_data.indexer_test_all_results.as_mut(),
app,
|app| app.data.sonarr_data.indexer_test_all_results.as_mut(),
test_results_row_mapping,
)
.loading(is_loading)
+26 -14
View File
@@ -75,13 +75,14 @@ fn draw_add_series_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
Layout::vertical([Constraint::Length(3), Constraint::Fill(0)])
.margin(1)
.areas(area);
let block_content = &app
let block_content = app
.data
.sonarr_data
.add_series_search
.as_ref()
.expect("add_series_search must be populated")
.text;
.text
.clone();
let offset = app
.data
.sonarr_data
@@ -90,6 +91,16 @@ fn draw_add_series_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.expect("add_series_search must be populated")
.offset
.load(Ordering::SeqCst);
let series_tvdb_ids: Vec<_> = app
.data
.sonarr_data
.series
.items
.iter()
.map(|s| s.tvdb_id)
.collect();
let ui_scroll_tick_count = app.ui_scroll_tick_count;
let search_results_row_mapping = |series: &AddSeriesSearchResult| {
let rating = series.ratings.clone().unwrap_or_default().value;
let series_rating = if rating == 0.0 {
@@ -97,14 +108,7 @@ fn draw_add_series_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
} else {
format!("{rating:.1}")
};
let in_library = if app
.data
.sonarr_data
.series
.items
.iter()
.any(|mov| mov.tvdb_id == series.tvdb_id)
{
let in_library = if series_tvdb_ids.contains(&series.tvdb_id) {
""
} else {
""
@@ -119,7 +123,7 @@ fn draw_add_series_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
series.title.scroll_left_or_reset(
get_width_from_percentage(area, 27),
*series == current_selection,
app.ui_scroll_tick_count == 0,
ui_scroll_tick_count == 0,
);
Row::new(vec![
@@ -137,7 +141,7 @@ fn draw_add_series_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
match active_sonarr_block {
ActiveSonarrBlock::AddSeriesSearchInput => {
let search_box = InputBox::new(block_content)
let search_box = InputBox::new(&block_content)
.offset(offset)
.block(title_block_centered("Add Series"));
@@ -162,7 +166,8 @@ fn draw_add_series_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
| ActiveSonarrBlock::AddSeriesAlreadyInLibrary
| ActiveSonarrBlock::AddSeriesTagsInput => {
let search_results_table = ManagarrTable::new(
app.data.sonarr_data.add_searched_series.as_mut(),
app,
|app| app.data.sonarr_data.add_searched_series.as_mut(),
search_results_row_mapping,
)
.loading(is_loading)
@@ -181,13 +186,20 @@ fn draw_add_series_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
]);
f.render_widget(search_results_table, results_area);
f.render_widget(
InputBox::new(&block_content)
.offset(offset)
.block(title_block_centered("Add Series")),
search_box_area,
);
return;
}
_ => (),
}
}
f.render_widget(
InputBox::new(block_content)
InputBox::new(&block_content)
.offset(offset)
.block(title_block_centered("Add Series")),
search_box_area,
+47 -36
View File
@@ -267,6 +267,7 @@ fn draw_episode_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
.current_selection()
.clone()
};
let ui_scroll_tick_count = app.ui_scroll_tick_count;
let history_row_mapping = |history_item: &SonarrHistoryItem| {
let SonarrHistoryItem {
@@ -281,7 +282,7 @@ fn draw_episode_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40),
current_selection == *history_item,
app.ui_scroll_tick_count == 0,
ui_scroll_tick_count == 0,
);
Row::new(vec![
@@ -305,28 +306,33 @@ fn draw_episode_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
])
.primary()
};
let mut episode_history_table = &mut app
.data
.sonarr_data
.season_details_modal
.as_mut()
.expect("season_details_modal must exist in this context")
.episode_details_modal
.as_mut()
.expect("episode_details_modal must exist in this context")
.episode_history;
let history_table =
ManagarrTable::new(Some(&mut episode_history_table), history_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
.constraints([
Constraint::Percentage(40),
Constraint::Percentage(15),
Constraint::Percentage(12),
Constraint::Percentage(13),
Constraint::Percentage(20),
]);
let history_table = ManagarrTable::new(
app,
|app| {
Some(
&mut app
.data
.sonarr_data
.season_details_modal
.as_mut()
.expect("season_details_modal must exist in this context")
.episode_details_modal
.as_mut()
.expect("episode_details_modal must exist in this context")
.episode_history,
)
},
history_row_mapping,
)
.block(layout_block_top_border())
.headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
.constraints([
Constraint::Percentage(40),
Constraint::Percentage(15),
Constraint::Percentage(12),
Constraint::Percentage(13),
Constraint::Percentage(20),
]);
f.render_widget(history_table, area);
}
@@ -409,6 +415,7 @@ fn draw_episode_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
episode_details_modal.episode_releases.is_empty(),
)
};
let ui_scroll_tick_count = app.ui_scroll_tick_count;
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
let episode_release_row_mapping = |release: &SonarrRelease| {
@@ -431,7 +438,7 @@ fn draw_episode_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
get_width_from_percentage(area, 30),
current_selection == *release
&& active_sonarr_block != ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt,
app.ui_scroll_tick_count == 0,
ui_scroll_tick_count == 0,
);
let size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" };
@@ -480,22 +487,26 @@ fn draw_episode_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
])
.primary()
};
let mut episode_release_table = &mut app
.data
.sonarr_data
.season_details_modal
.as_mut()
.expect("season_details_modal must exist in this context")
.episode_details_modal
.as_mut()
.expect("episode_details_modal must exist in this context")
.episode_releases;
let release_table = ManagarrTable::new(
Some(&mut episode_release_table),
app,
|app| {
Some(
&mut app
.data
.sonarr_data
.season_details_modal
.as_mut()
.expect("season_details_modal must exist in this context")
.episode_details_modal
.as_mut()
.expect("episode_details_modal must exist in this context")
.episode_releases,
)
},
episode_release_row_mapping,
)
.block(layout_block_top_border())
.loading(app.is_loading || is_empty)
.loading(is_empty)
.sorting(active_sonarr_block == ActiveSonarrBlock::ManualEpisodeSearchSortPrompt)
.headers([
"Source", "Age", "", "Title", "Indexer", "Size", "Peers", "Language", "Quality",
+45 -42
View File
@@ -2,9 +2,9 @@ use add_series_ui::AddSeriesUi;
use delete_series_ui::DeleteSeriesUi;
use edit_series_ui::EditSeriesUi;
use ratatui::{
Frame,
layout::{Constraint, Rect},
widgets::{Cell, Row},
Frame,
};
use series_details_ui::SeriesDetailsUi;
@@ -16,15 +16,15 @@ use crate::utils::convert_to_gb;
use crate::{
app::App,
models::{
Route,
servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, LIBRARY_BLOCKS},
sonarr_models::{Series, SeriesStatus},
Route,
},
ui::{
DrawUi,
styles::ManagarrStyle,
utils::{get_width_from_percentage, layout_block_top_border},
widgets::managarr_table::ManagarrTable,
DrawUi,
},
};
@@ -86,16 +86,16 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
} else {
Series::default()
};
let quality_profile_map = &app.data.sonarr_data.quality_profile_map;
let language_profile_map = &app.data.sonarr_data.language_profiles_map;
let tags_map = &app.data.sonarr_data.tags_map;
let content = Some(&mut app.data.sonarr_data.series);
let quality_profile_map = app.data.sonarr_data.quality_profile_map.clone();
let language_profile_map = app.data.sonarr_data.language_profiles_map.clone();
let tags_map = app.data.sonarr_data.tags_map.clone();
let ui_scroll_tick_count = app.ui_scroll_tick_count;
let series_table_row_mapping = |series: &Series| {
series.title.scroll_left_or_reset(
get_width_from_percentage(area, 23),
*series == current_selection,
app.ui_scroll_tick_count == 0,
ui_scroll_tick_count == 0,
);
let monitored = if series.monitored { "🏷" } else { "" };
let certification = series.certification.clone().unwrap_or_default();
@@ -139,40 +139,43 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
]),
)
};
let series_table = ManagarrTable::new(content, series_table_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.sorting(active_sonarr_block == ActiveSonarrBlock::SeriesSortPrompt)
.searching(active_sonarr_block == ActiveSonarrBlock::SearchSeries)
.filtering(active_sonarr_block == ActiveSonarrBlock::FilterSeries)
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchSeriesError)
.filter_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::FilterSeriesError)
.headers([
"Title",
"Year",
"Network",
"Status",
"Rating",
"Type",
"Quality Profile",
"Language",
"Size",
"Monitored",
"Tags",
])
.constraints([
Constraint::Percentage(20),
Constraint::Percentage(4),
Constraint::Percentage(14),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(11),
Constraint::Percentage(8),
Constraint::Percentage(7),
Constraint::Percentage(6),
Constraint::Percentage(12),
]);
let series_table = ManagarrTable::new(
app,
|app| Some(&mut app.data.sonarr_data.series),
series_table_row_mapping,
)
.block(layout_block_top_border())
.sorting(active_sonarr_block == ActiveSonarrBlock::SeriesSortPrompt)
.searching(active_sonarr_block == ActiveSonarrBlock::SearchSeries)
.filtering(active_sonarr_block == ActiveSonarrBlock::FilterSeries)
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchSeriesError)
.filter_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::FilterSeriesError)
.headers([
"Title",
"Year",
"Network",
"Status",
"Rating",
"Type",
"Quality Profile",
"Language",
"Size",
"Monitored",
"Tags",
])
.constraints([
Constraint::Percentage(20),
Constraint::Percentage(4),
Constraint::Percentage(14),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(6),
Constraint::Percentage(11),
Constraint::Percentage(8),
Constraint::Percentage(7),
Constraint::Percentage(6),
Constraint::Percentage(12),
]);
if [
ActiveSonarrBlock::SearchSeries,
+104 -87
View File
@@ -162,16 +162,7 @@ fn draw_episodes_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.episode_files
.items
.clone();
let content = Some(
&mut app
.data
.sonarr_data
.season_details_modal
.as_mut()
.expect("Season details modal is unpopulated")
.episodes,
);
let downloads_vec = &app.data.sonarr_data.downloads.items;
let downloads_vec = app.data.sonarr_data.downloads.items.clone();
let episode_row_mapping = |episode: &Episode| {
let Episode {
@@ -202,7 +193,7 @@ fn draw_episodes_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
};
decorate_with_row_style(
downloads_vec,
&downloads_vec,
episode,
Row::new(vec![
Cell::from(episode_monitored.to_owned()),
@@ -215,27 +206,40 @@ fn draw_episodes_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
)
};
let is_searching = active_sonarr_block == ActiveSonarrBlock::SearchEpisodes;
let season_table = ManagarrTable::new(content, episode_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.searching(is_searching)
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchEpisodesError)
.headers([
"🏷",
"#",
"Title",
"Air Date",
"Size on Disk",
"Quality Profile",
])
.constraints([
Constraint::Percentage(4),
Constraint::Percentage(4),
Constraint::Percentage(50),
Constraint::Percentage(19),
Constraint::Percentage(10),
Constraint::Percentage(12),
]);
let season_table = ManagarrTable::new(
app,
|app| {
Some(
&mut app
.data
.sonarr_data
.season_details_modal
.as_mut()
.expect("Season details modal is unpopulated")
.episodes,
)
},
episode_row_mapping,
)
.block(layout_block_top_border())
.searching(is_searching)
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchEpisodesError)
.headers([
"🏷",
"#",
"Title",
"Air Date",
"Size on Disk",
"Quality Profile",
])
.constraints([
Constraint::Percentage(4),
Constraint::Percentage(4),
Constraint::Percentage(50),
Constraint::Percentage(19),
Constraint::Percentage(10),
Constraint::Percentage(12),
]);
if is_searching {
season_table.show_cursor(f, area);
@@ -256,6 +260,7 @@ fn draw_season_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.current_selection()
.clone()
};
let ui_scroll_tick_count = app.ui_scroll_tick_count;
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
let history_row_mapping = |history_item: &SonarrHistoryItem| {
@@ -271,7 +276,7 @@ fn draw_season_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40),
current_selection == *history_item,
app.ui_scroll_tick_count == 0,
ui_scroll_tick_count == 0,
);
Row::new(vec![
@@ -295,34 +300,39 @@ fn draw_season_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
])
.primary()
};
let mut season_history_table = &mut app
.data
.sonarr_data
.season_details_modal
.as_mut()
.expect("season_details_modal must exist in this context")
.season_history;
let history_table =
ManagarrTable::new(Some(&mut season_history_table), history_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.sorting(active_sonarr_block == ActiveSonarrBlock::SeasonHistorySortPrompt)
.searching(active_sonarr_block == ActiveSonarrBlock::SearchSeasonHistory)
.search_produced_empty_results(
active_sonarr_block == ActiveSonarrBlock::SearchSeasonHistoryError,
let history_table = ManagarrTable::new(
app,
|app| {
Some(
&mut app
.data
.sonarr_data
.season_details_modal
.as_mut()
.expect("season_details_modal must exist in this context")
.season_history,
)
.filtering(active_sonarr_block == ActiveSonarrBlock::FilterSeasonHistory)
.filter_produced_empty_results(
active_sonarr_block == ActiveSonarrBlock::FilterSeasonHistoryError,
)
.headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
.constraints([
Constraint::Percentage(40),
Constraint::Percentage(15),
Constraint::Percentage(12),
Constraint::Percentage(13),
Constraint::Percentage(20),
]);
},
history_row_mapping,
)
.block(layout_block_top_border())
.sorting(active_sonarr_block == ActiveSonarrBlock::SeasonHistorySortPrompt)
.searching(active_sonarr_block == ActiveSonarrBlock::SearchSeasonHistory)
.search_produced_empty_results(
active_sonarr_block == ActiveSonarrBlock::SearchSeasonHistoryError,
)
.filtering(active_sonarr_block == ActiveSonarrBlock::FilterSeasonHistory)
.filter_produced_empty_results(
active_sonarr_block == ActiveSonarrBlock::FilterSeasonHistoryError,
)
.headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
.constraints([
Constraint::Percentage(40),
Constraint::Percentage(15),
Constraint::Percentage(12),
Constraint::Percentage(13),
Constraint::Percentage(20),
]);
if [
ActiveSonarrBlock::SearchSeriesHistory,
@@ -360,6 +370,7 @@ fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
season_details_modal.season_releases.is_empty(),
)
};
let ui_scroll_tick_count = app.ui_scroll_tick_count;
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
let season_release_row_mapping = |release: &SonarrRelease| {
@@ -382,7 +393,7 @@ fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
get_width_from_percentage(area, 30),
current_selection == *release
&& active_sonarr_block != ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
app.ui_scroll_tick_count == 0,
ui_scroll_tick_count == 0,
);
let size = convert_to_gb(*size);
let rejected_str = if *rejected { "" } else { "" };
@@ -431,32 +442,38 @@ fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
])
.primary()
};
let mut season_release_table = &mut app
.data
.sonarr_data
.season_details_modal
.as_mut()
.expect("season_details_modal must exist in this context")
.season_releases;
let release_table =
ManagarrTable::new(Some(&mut season_release_table), season_release_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading || is_empty)
.sorting(active_sonarr_block == ActiveSonarrBlock::ManualSeasonSearchSortPrompt)
.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 release_table = ManagarrTable::new(
app,
|app| {
Some(
&mut app
.data
.sonarr_data
.season_details_modal
.as_mut()
.expect("season_details_modal must exist in this context")
.season_releases,
)
},
season_release_row_mapping,
)
.block(layout_block_top_border())
.loading(is_empty)
.sorting(active_sonarr_block == ActiveSonarrBlock::ManualSeasonSearchSortPrompt)
.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),
]);
f.render_widget(release_table, area);
}
+40 -41
View File
@@ -228,7 +228,6 @@ pub fn draw_series_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
fn draw_seasons_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
let content = Some(&mut app.data.sonarr_data.seasons);
let season_row_mapping = |season: &Season| {
let Season {
title,
@@ -271,18 +270,21 @@ fn draw_seasons_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
}
};
let is_searching = active_sonarr_block == ActiveSonarrBlock::SearchSeason;
let season_table = ManagarrTable::new(content, season_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.searching(is_searching)
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchSeasonError)
.headers(["Monitored", "Season", "Episode Count", "Size on Disk"])
.constraints([
Constraint::Percentage(6),
Constraint::Ratio(1, 3),
Constraint::Ratio(1, 3),
Constraint::Ratio(1, 3),
]);
let season_table = ManagarrTable::new(
app,
|app| Some(&mut app.data.sonarr_data.seasons),
season_row_mapping,
)
.block(layout_block_top_border())
.searching(is_searching)
.search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchSeasonError)
.headers(["Monitored", "Season", "Episode Count", "Size on Disk"])
.constraints([
Constraint::Percentage(6),
Constraint::Ratio(1, 3),
Constraint::Ratio(1, 3),
Constraint::Ratio(1, 3),
]);
if is_searching {
season_table.show_cursor(f, area);
@@ -300,6 +302,7 @@ fn draw_series_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
} else {
series_history.current_selection().clone()
};
let ui_scroll_tick_count = app.ui_scroll_tick_count;
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
let history_row_mapping = |history_item: &SonarrHistoryItem| {
@@ -315,7 +318,7 @@ fn draw_series_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40),
current_selection == *history_item,
app.ui_scroll_tick_count == 0,
ui_scroll_tick_count == 0,
);
Row::new(vec![
@@ -339,33 +342,29 @@ fn draw_series_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
])
.primary()
};
let mut series_history_table = app
.data
.sonarr_data
.series_history
.as_mut()
.expect("series_history must be populated");
let history_table =
ManagarrTable::new(Some(&mut series_history_table), history_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.sorting(active_sonarr_block == ActiveSonarrBlock::SeriesHistorySortPrompt)
.searching(active_sonarr_block == ActiveSonarrBlock::SearchSeriesHistory)
.search_produced_empty_results(
active_sonarr_block == ActiveSonarrBlock::SearchSeriesHistoryError,
)
.filtering(active_sonarr_block == ActiveSonarrBlock::FilterSeriesHistory)
.filter_produced_empty_results(
active_sonarr_block == ActiveSonarrBlock::FilterSeriesHistoryError,
)
.headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
.constraints([
Constraint::Percentage(40),
Constraint::Percentage(15),
Constraint::Percentage(12),
Constraint::Percentage(13),
Constraint::Percentage(20),
]);
let history_table = ManagarrTable::new(
app,
|app| app.data.sonarr_data.series_history.as_mut(),
history_row_mapping,
)
.block(layout_block_top_border())
.sorting(active_sonarr_block == ActiveSonarrBlock::SeriesHistorySortPrompt)
.searching(active_sonarr_block == ActiveSonarrBlock::SearchSeriesHistory)
.search_produced_empty_results(
active_sonarr_block == ActiveSonarrBlock::SearchSeriesHistoryError,
)
.filtering(active_sonarr_block == ActiveSonarrBlock::FilterSeriesHistory)
.filter_produced_empty_results(
active_sonarr_block == ActiveSonarrBlock::FilterSeriesHistoryError,
)
.headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
.constraints([
Constraint::Percentage(40),
Constraint::Percentage(15),
Constraint::Percentage(12),
Constraint::Percentage(13),
Constraint::Percentage(20),
]);
if [
ActiveSonarrBlock::SearchSeriesHistory,
+2 -2
View File
@@ -84,11 +84,11 @@ fn draw_root_folders(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
};
let root_folders_table = ManagarrTable::new(
Some(&mut app.data.sonarr_data.root_folders),
app,
|app| Some(&mut app.data.sonarr_data.root_folders),
root_folders_row_mapping,
)
.block(layout_block_top_border())
.loading(app.is_loading)
.headers(["Path", "Free Space", "Unmapped Folders"])
.constraints([
Constraint::Ratio(3, 5),
+11 -8
View File
@@ -89,12 +89,15 @@ fn draw_tasks(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
])
.primary()
};
let tasks_table = ManagarrTable::new(Some(&mut app.data.sonarr_data.tasks), tasks_row_mapping)
.block(title_block("Tasks"))
.loading(app.is_loading)
.highlight_rows(false)
.headers(TASK_TABLE_HEADERS)
.constraints(TASK_TABLE_CONSTRAINTS);
let tasks_table = ManagarrTable::new(
app,
|app| Some(&mut app.data.sonarr_data.tasks),
tasks_row_mapping,
)
.block(title_block("Tasks"))
.highlight_rows(false)
.headers(TASK_TABLE_HEADERS)
.constraints(TASK_TABLE_CONSTRAINTS);
f.render_widget(tasks_table, area);
}
@@ -137,11 +140,11 @@ pub(super) fn draw_queued_events(f: &mut Frame<'_>, app: &mut App<'_>, area: Rec
.primary()
};
let events_table = ManagarrTable::new(
Some(&mut app.data.sonarr_data.queued_events),
app,
|app| Some(&mut app.data.sonarr_data.queued_events),
events_row_mapping,
)
.block(title_block("Queued Events"))
.loading(app.is_loading)
.highlight_rows(false)
.headers(["Trigger", "Status", "Name", "Queued", "Started", "Duration"])
.constraints([
+19 -11
View File
@@ -92,27 +92,35 @@ fn draw_tasks_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
])
.primary()
};
let tasks_table = ManagarrTable::new(Some(&mut app.data.sonarr_data.tasks), tasks_row_mapping)
.loading(app.is_loading)
.margin(1)
.headers(TASK_TABLE_HEADERS)
.constraints(TASK_TABLE_CONSTRAINTS);
let current_route = app.get_current_route();
let prompt_confirm = app.data.sonarr_data.prompt_confirm;
let task_name = if app.data.sonarr_data.tasks.items.is_empty() {
String::new()
} else {
app.data.sonarr_data.tasks.current_selection().name.clone()
};
let tasks_table = ManagarrTable::new(
app,
|app| Some(&mut app.data.sonarr_data.tasks),
tasks_row_mapping,
)
.margin(1)
.headers(TASK_TABLE_HEADERS)
.constraints(TASK_TABLE_CONSTRAINTS);
f.render_widget(title_block("Tasks"), area);
f.render_widget(tasks_table, area);
if matches!(
app.get_current_route(),
current_route,
Route::Sonarr(ActiveSonarrBlock::SystemTaskStartConfirmPrompt, _)
) {
let prompt = format!(
"Do you want to manually start this task: {}?",
app.data.sonarr_data.tasks.current_selection().name
);
let prompt = format!("Do you want to manually start this task: {}?", task_name);
let confirmation_prompt = ConfirmationPrompt::new()
.title("Start Task")
.prompt(&prompt)
.yes_no_value(app.data.sonarr_data.prompt_confirm);
.yes_no_value(prompt_confirm);
f.render_widget(
Popup::new(confirmation_prompt).size(Size::MediumPrompt),