Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8a06f3601 | |||
| 50d4ddfa28 | |||
| 368f7505ff |
+1
-1
@@ -273,10 +273,10 @@ impl App<'_> {
|
||||
config: Some(ServarrConfig::default()),
|
||||
},
|
||||
]),
|
||||
|
||||
..App::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_default_fully_populated() -> Self {
|
||||
App {
|
||||
data: Data {
|
||||
|
||||
+8
-8
@@ -3,38 +3,38 @@
|
||||
extern crate assertables;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser};
|
||||
use clap::{CommandFactory, Parser, crate_authors, crate_description, crate_name, crate_version};
|
||||
use clap_complete::generate;
|
||||
use crossterm::execute;
|
||||
use crossterm::terminal::{
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
|
||||
};
|
||||
use log::{debug, error, warn};
|
||||
use network::NetworkTrait;
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
use ratatui::Terminal;
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
use reqwest::Client;
|
||||
use std::panic::PanicHookInfo;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Instant;
|
||||
use std::{io, panic, process};
|
||||
use tokio::select;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use utils::{
|
||||
build_network_client, load_config, start_cli_no_spinner, start_cli_with_spinner, tail_logs,
|
||||
};
|
||||
|
||||
use crate::app::{log_and_print_error, App};
|
||||
use crate::app::{App, log_and_print_error};
|
||||
use crate::cli::Command;
|
||||
use crate::event::input_event::{Events, InputEvent};
|
||||
use crate::event::Key;
|
||||
use crate::event::input_event::{Events, InputEvent};
|
||||
use crate::network::{Network, NetworkEvent};
|
||||
use crate::ui::theme::{Theme, ThemeDefinitionsWrapper};
|
||||
use crate::ui::{ui, THEME};
|
||||
use crate::ui::{THEME, ui};
|
||||
use crate::utils::load_theme_config;
|
||||
|
||||
mod app;
|
||||
|
||||
+9
-12
@@ -165,18 +165,15 @@ pub fn draw_help_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let keymapping_table = ManagarrTable::new(
|
||||
Some(app.keymapping_table.as_mut().unwrap()),
|
||||
keymap_row_mapping,
|
||||
)
|
||||
.block(title_block("Keybindings"))
|
||||
.loading(app.is_loading)
|
||||
.headers(["Key", "Alt Key", "Description"])
|
||||
.constraints([
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
]);
|
||||
let keymapping_table =
|
||||
ManagarrTable::new(app, |app| app.keymapping_table.as_mut(), keymap_row_mapping)
|
||||
.block(title_block("Keybindings"))
|
||||
.headers(["Key", "Alt Key", "Description"])
|
||||
.constraints([
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
]);
|
||||
f.render_widget(Clear, table_area);
|
||||
f.render_widget(background_block(), table_area);
|
||||
f.render_widget(keymapping_table, table_area);
|
||||
|
||||
@@ -81,6 +81,8 @@ fn draw_blocklist_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
} else {
|
||||
app.data.radarr_data.blocklist.current_selection().clone()
|
||||
};
|
||||
let ui_scroll_tick_count = app.ui_scroll_tick_count;
|
||||
let is_sorting = active_radarr_block == ActiveRadarrBlock::BlocklistSortPrompt;
|
||||
|
||||
let blocklist_row_mapping = |blocklist_item: &BlocklistItem| {
|
||||
let BlocklistItem {
|
||||
@@ -96,7 +98,7 @@ fn draw_blocklist_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
movie.title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 20),
|
||||
current_selection == *blocklist_item,
|
||||
app.ui_scroll_tick_count == 0,
|
||||
ui_scroll_tick_count == 0,
|
||||
);
|
||||
|
||||
let languages_string = languages
|
||||
@@ -125,12 +127,12 @@ fn draw_blocklist_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
.primary()
|
||||
};
|
||||
let blocklist_table = ManagarrTable::new(
|
||||
Some(&mut app.data.radarr_data.blocklist),
|
||||
app,
|
||||
|app| Some(&mut app.data.radarr_data.blocklist),
|
||||
blocklist_row_mapping,
|
||||
)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.sorting(active_radarr_block == ActiveRadarrBlock::BlocklistSortPrompt)
|
||||
.sorting(is_sorting)
|
||||
.headers([
|
||||
"Movie Title",
|
||||
"Source Title",
|
||||
|
||||
@@ -56,7 +56,8 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
|
||||
Layout::vertical([Constraint::Percentage(25), Constraint::Fill(0)])
|
||||
.margin(1)
|
||||
.areas(area);
|
||||
let collection_selection = app.data.radarr_data.collections.current_selection();
|
||||
|
||||
let collection_selection = app.data.radarr_data.collections.current_selection().clone();
|
||||
let quality_profile = app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -74,15 +75,19 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
|
||||
.current_selection()
|
||||
.clone()
|
||||
};
|
||||
let movie_row_mapper = |movie: &CollectionMovie| {
|
||||
let in_library = if app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.items
|
||||
.iter()
|
||||
.any(|mov| mov.tmdb_id == movie.tmdb_id)
|
||||
{
|
||||
|
||||
let ui_scroll_tick_count = app.ui_scroll_tick_count;
|
||||
let movie_tmdb_ids: Vec<_> = app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.items
|
||||
.iter()
|
||||
.map(|mov| mov.tmdb_id)
|
||||
.collect();
|
||||
|
||||
let movie_row_mapper = move |movie: &CollectionMovie| {
|
||||
let in_library = if movie_tmdb_ids.contains(&movie.tmdb_id) {
|
||||
"✔"
|
||||
} else {
|
||||
""
|
||||
@@ -90,7 +95,7 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
|
||||
movie.title.scroll_left_or_reset(
|
||||
get_width_from_percentage(table_area, 20),
|
||||
current_selection == *movie,
|
||||
app.ui_scroll_tick_count == 0,
|
||||
ui_scroll_tick_count == 0,
|
||||
);
|
||||
let (hours, minutes) = convert_runtime(movie.runtime);
|
||||
let imdb_rating = movie
|
||||
@@ -179,11 +184,11 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
|
||||
.block(borderless_block())
|
||||
.wrap(Wrap { trim: false });
|
||||
let movies_table = ManagarrTable::new(
|
||||
Some(&mut app.data.radarr_data.collection_movies),
|
||||
app,
|
||||
|app| Some(&mut app.data.radarr_data.collection_movies),
|
||||
movie_row_mapper,
|
||||
)
|
||||
.block(layout_block_top_border_with_title(title_style("Movies")))
|
||||
.loading(app.is_loading)
|
||||
.headers([
|
||||
"✔",
|
||||
"Title",
|
||||
|
||||
@@ -63,14 +63,25 @@ pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
|
||||
} else {
|
||||
Collection::default()
|
||||
};
|
||||
let quality_profile_map = &app.data.radarr_data.quality_profile_map;
|
||||
let content = Some(&mut app.data.radarr_data.collections);
|
||||
let quality_profile_map = app.data.radarr_data.quality_profile_map.clone();
|
||||
let ui_scroll_tick_count = app.ui_scroll_tick_count;
|
||||
let is_loading = app.is_loading
|
||||
|| app.data.radarr_data.movies.is_empty()
|
||||
|| app.data.radarr_data.quality_profile_map.is_empty();
|
||||
let is_sorting = active_radarr_block == ActiveRadarrBlock::CollectionsSortPrompt;
|
||||
let is_searching = active_radarr_block == ActiveRadarrBlock::SearchCollection;
|
||||
let search_produced_empty_results =
|
||||
active_radarr_block == ActiveRadarrBlock::SearchCollectionError;
|
||||
let is_filtering = active_radarr_block == ActiveRadarrBlock::FilterCollections;
|
||||
let filter_produced_empty_results =
|
||||
active_radarr_block == ActiveRadarrBlock::FilterCollectionsError;
|
||||
|
||||
let collection_row_mapping = |collection: &Collection| {
|
||||
let number_of_movies = collection.movies.as_ref().unwrap_or(&Vec::new()).len();
|
||||
collection.title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 25),
|
||||
*collection == current_selection,
|
||||
app.ui_scroll_tick_count == 0,
|
||||
ui_scroll_tick_count == 0,
|
||||
);
|
||||
let monitored = if collection.monitored { "🏷" } else { "" };
|
||||
let search_on_add = if collection.search_on_add {
|
||||
@@ -100,38 +111,34 @@ pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let collections_table = ManagarrTable::new(content, collection_row_mapping)
|
||||
.loading(
|
||||
app.is_loading
|
||||
|| app.data.radarr_data.movies.is_empty()
|
||||
|| app.data.radarr_data.quality_profile_map.is_empty(),
|
||||
)
|
||||
.block(layout_block_top_border())
|
||||
.sorting(active_radarr_block == ActiveRadarrBlock::CollectionsSortPrompt)
|
||||
.searching(active_radarr_block == ActiveRadarrBlock::SearchCollection)
|
||||
.search_produced_empty_results(
|
||||
active_radarr_block == ActiveRadarrBlock::SearchCollectionError,
|
||||
)
|
||||
.filtering(active_radarr_block == ActiveRadarrBlock::FilterCollections)
|
||||
.filter_produced_empty_results(
|
||||
active_radarr_block == ActiveRadarrBlock::FilterCollectionsError,
|
||||
)
|
||||
.headers([
|
||||
"Collection",
|
||||
"Number of Movies",
|
||||
"Root Folder Path",
|
||||
"Quality Profile",
|
||||
"Search on Add",
|
||||
"Monitored",
|
||||
])
|
||||
.constraints([
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(15),
|
||||
Constraint::Percentage(15),
|
||||
Constraint::Percentage(15),
|
||||
Constraint::Percentage(15),
|
||||
Constraint::Percentage(15),
|
||||
]);
|
||||
let collections_table = ManagarrTable::new(
|
||||
app,
|
||||
|app| Some(&mut app.data.radarr_data.collections),
|
||||
collection_row_mapping,
|
||||
)
|
||||
.loading(is_loading)
|
||||
.block(layout_block_top_border())
|
||||
.sorting(is_sorting)
|
||||
.searching(is_searching)
|
||||
.search_produced_empty_results(search_produced_empty_results)
|
||||
.filtering(is_filtering)
|
||||
.filter_produced_empty_results(filter_produced_empty_results)
|
||||
.headers([
|
||||
"Collection",
|
||||
"Number of Movies",
|
||||
"Root Folder Path",
|
||||
"Quality Profile",
|
||||
"Search on Add",
|
||||
"Monitored",
|
||||
])
|
||||
.constraints([
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(15),
|
||||
Constraint::Percentage(15),
|
||||
Constraint::Percentage(15),
|
||||
Constraint::Percentage(15),
|
||||
Constraint::Percentage(15),
|
||||
]);
|
||||
|
||||
if [
|
||||
ActiveRadarrBlock::SearchCollection,
|
||||
|
||||
@@ -71,8 +71,9 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
} else {
|
||||
app.data.radarr_data.downloads.current_selection().clone()
|
||||
};
|
||||
let ui_scroll_tick_count = app.ui_scroll_tick_count;
|
||||
|
||||
let downloads_row_mapping = |download_record: &DownloadRecord| {
|
||||
let downloads_row_mapping = move |download_record: &DownloadRecord| {
|
||||
let DownloadRecord {
|
||||
title,
|
||||
size,
|
||||
@@ -87,7 +88,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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,11 +115,11 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
.primary()
|
||||
};
|
||||
let downloads_table = ManagarrTable::new(
|
||||
Some(&mut app.data.radarr_data.downloads),
|
||||
app,
|
||||
|app| Some(&mut app.data.radarr_data.downloads),
|
||||
downloads_row_mapping,
|
||||
)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.headers([
|
||||
"Title",
|
||||
"Percent Complete",
|
||||
|
||||
@@ -111,7 +111,9 @@ impl DrawUi for IndexersUi {
|
||||
}
|
||||
|
||||
fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let indexers_row_mapping = |indexer: &'_ Indexer| {
|
||||
let tags_map = app.data.radarr_data.tags_map.clone();
|
||||
|
||||
let indexers_row_mapping = move |indexer: &'_ Indexer| {
|
||||
let Indexer {
|
||||
name,
|
||||
enable_rss,
|
||||
@@ -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
|
||||
.radarr_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.radarr_data.indexers),
|
||||
app,
|
||||
|app| Some(&mut app.data.radarr_data.indexers),
|
||||
indexers_row_mapping,
|
||||
)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.headers([
|
||||
"Indexer",
|
||||
"RSS",
|
||||
|
||||
@@ -33,6 +33,7 @@ impl DrawUi for TestAllIndexersUi {
|
||||
fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let is_loading = app.is_loading || app.data.radarr_data.indexer_test_all_results.is_none();
|
||||
let block = title_block("Test All Indexers");
|
||||
let ui_scroll_tick_count = app.ui_scroll_tick_count;
|
||||
|
||||
let current_selection =
|
||||
if let Some(test_all_results) = app.data.radarr_data.indexer_test_all_results.as_ref() {
|
||||
@@ -45,7 +46,7 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are
|
||||
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![
|
||||
@@ -62,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.radarr_data.indexer_test_all_results.as_mut(),
|
||||
app,
|
||||
|app| app.data.radarr_data.indexer_test_all_results.as_mut(),
|
||||
test_results_row_mapping,
|
||||
)
|
||||
.loading(is_loading)
|
||||
|
||||
@@ -80,13 +80,14 @@ fn draw_add_movie_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
|
||||
.radarr_data
|
||||
.add_movie_search
|
||||
.as_ref()
|
||||
.expect("add_movie_search must be populated")
|
||||
.text;
|
||||
.text
|
||||
.clone();
|
||||
let offset = app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -95,6 +96,16 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
.expect("add_movie_search must be populated")
|
||||
.offset
|
||||
.load(Ordering::SeqCst);
|
||||
let ui_scroll_tick_count = app.ui_scroll_tick_count;
|
||||
let library_tmdb_ids: Vec<i64> = app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.items
|
||||
.iter()
|
||||
.map(|m| m.tmdb_id)
|
||||
.collect();
|
||||
|
||||
let search_results_row_mapping = |movie: &AddMovieSearchResult| {
|
||||
let (hours, minutes) = convert_runtime(movie.runtime);
|
||||
let imdb_rating = movie
|
||||
@@ -123,14 +134,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
} else {
|
||||
format!("{rotten_tomatoes_rating}%")
|
||||
};
|
||||
let in_library = if app
|
||||
.data
|
||||
.radarr_data
|
||||
.movies
|
||||
.items
|
||||
.iter()
|
||||
.any(|mov| mov.tmdb_id == movie.tmdb_id)
|
||||
{
|
||||
let in_library = if library_tmdb_ids.contains(&movie.tmdb_id) {
|
||||
"✔"
|
||||
} else {
|
||||
""
|
||||
@@ -139,7 +143,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
movie.title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 27),
|
||||
*movie == current_selection,
|
||||
app.ui_scroll_tick_count == 0,
|
||||
ui_scroll_tick_count == 0,
|
||||
);
|
||||
|
||||
Row::new(vec![
|
||||
@@ -157,7 +161,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
if let Route::Radarr(active_radarr_block, _) = app.get_current_route() {
|
||||
match active_radarr_block {
|
||||
ActiveRadarrBlock::AddMovieSearchInput => {
|
||||
let search_box = InputBox::new(block_content)
|
||||
let search_box = InputBox::new(&block_content)
|
||||
.offset(offset)
|
||||
.block(title_block_centered("Add Movie"));
|
||||
|
||||
@@ -181,7 +185,8 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
| ActiveRadarrBlock::AddMovieAlreadyInLibrary
|
||||
| ActiveRadarrBlock::AddMovieTagsInput => {
|
||||
let search_results_table = ManagarrTable::new(
|
||||
app.data.radarr_data.add_searched_movies.as_mut(),
|
||||
app,
|
||||
|app| app.data.radarr_data.add_searched_movies.as_mut(),
|
||||
search_results_row_mapping,
|
||||
)
|
||||
.loading(is_loading)
|
||||
@@ -212,7 +217,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
}
|
||||
|
||||
f.render_widget(
|
||||
InputBox::new(block_content)
|
||||
InputBox::new(&block_content)
|
||||
.offset(offset)
|
||||
.block(title_block_centered("Add Movie")),
|
||||
search_box_area,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use ratatui::Frame;
|
||||
use ratatui::layout::{Constraint, Rect};
|
||||
use ratatui::widgets::{Cell, Row};
|
||||
use ratatui::Frame;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::models::Route;
|
||||
use crate::models::radarr_models::Movie;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, LIBRARY_BLOCKS};
|
||||
use crate::models::Route;
|
||||
use crate::ui::DrawUi;
|
||||
use crate::ui::radarr_ui::decorate_with_row_style;
|
||||
use crate::ui::radarr_ui::library::add_movie_ui::AddMovieUi;
|
||||
use crate::ui::radarr_ui::library::delete_movie_ui::DeleteMovieUi;
|
||||
@@ -15,7 +16,6 @@ use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
|
||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||
use crate::ui::widgets::popup::{Popup, Size};
|
||||
use crate::ui::DrawUi;
|
||||
use crate::utils::{convert_runtime, convert_to_gb};
|
||||
|
||||
mod add_movie_ui;
|
||||
@@ -83,16 +83,21 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
} else {
|
||||
Movie::default()
|
||||
};
|
||||
let quality_profile_map = &app.data.radarr_data.quality_profile_map;
|
||||
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 quality_profile_map = app.data.radarr_data.quality_profile_map.clone();
|
||||
let tags_map = app.data.radarr_data.tags_map.clone();
|
||||
let downloads_vec = app.data.radarr_data.downloads.items.clone();
|
||||
let ui_scroll_tick_count = app.ui_scroll_tick_count;
|
||||
let is_sorting = active_radarr_block == ActiveRadarrBlock::MoviesSortPrompt;
|
||||
let is_searching = active_radarr_block == ActiveRadarrBlock::SearchMovie;
|
||||
let search_produced_empty_results = active_radarr_block == ActiveRadarrBlock::SearchMovieError;
|
||||
let is_filtering = active_radarr_block == ActiveRadarrBlock::FilterMovies;
|
||||
let filter_produced_empty_results = active_radarr_block == ActiveRadarrBlock::FilterMoviesError;
|
||||
|
||||
let library_table_row_mapping = |movie: &Movie| {
|
||||
movie.title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 27),
|
||||
*movie == current_selection,
|
||||
app.ui_scroll_tick_count == 0,
|
||||
ui_scroll_tick_count == 0,
|
||||
);
|
||||
let monitored = if movie.monitored { "🏷" } else { "" };
|
||||
let studio = movie.studio.clone().unwrap_or_default();
|
||||
@@ -114,7 +119,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
.join(", ");
|
||||
|
||||
decorate_with_row_style(
|
||||
downloads_vec,
|
||||
&downloads_vec,
|
||||
movie,
|
||||
Row::new(vec![
|
||||
Cell::from(movie.title.to_string()),
|
||||
@@ -130,38 +135,41 @@ 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)
|
||||
.sorting(active_radarr_block == ActiveRadarrBlock::MoviesSortPrompt)
|
||||
.searching(active_radarr_block == ActiveRadarrBlock::SearchMovie)
|
||||
.search_produced_empty_results(active_radarr_block == ActiveRadarrBlock::SearchMovieError)
|
||||
.filtering(active_radarr_block == ActiveRadarrBlock::FilterMovies)
|
||||
.filter_produced_empty_results(active_radarr_block == ActiveRadarrBlock::FilterMoviesError)
|
||||
.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),
|
||||
]);
|
||||
let library_table = ManagarrTable::new(
|
||||
app,
|
||||
|app| Some(&mut app.data.radarr_data.movies),
|
||||
library_table_row_mapping,
|
||||
)
|
||||
.block(layout_block_top_border())
|
||||
.sorting(is_sorting)
|
||||
.searching(is_searching)
|
||||
.search_produced_empty_results(search_produced_empty_results)
|
||||
.filtering(is_filtering)
|
||||
.filter_produced_empty_results(filter_produced_empty_results)
|
||||
.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),
|
||||
]);
|
||||
|
||||
if [
|
||||
ActiveRadarrBlock::SearchMovie,
|
||||
|
||||
@@ -10,7 +10,7 @@ use serde_json::Number;
|
||||
use crate::app::App;
|
||||
use crate::models::Route;
|
||||
use crate::models::radarr_models::{Credit, MovieHistoryItem, RadarrRelease};
|
||||
use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
|
||||
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS};
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::utils::{
|
||||
@@ -225,135 +225,138 @@ fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
|
||||
}
|
||||
|
||||
fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
if let Some(movie_details_modal) = app.data.radarr_data.movie_details_modal.as_mut() {
|
||||
let current_selection = if movie_details_modal.movie_history.items.is_empty() {
|
||||
let current_selection = if let Some(modal) = app.data.radarr_data.movie_details_modal.as_ref() {
|
||||
if modal.movie_history.items.is_empty() {
|
||||
MovieHistoryItem::default()
|
||||
} else {
|
||||
movie_details_modal
|
||||
.movie_history
|
||||
.current_selection()
|
||||
.clone()
|
||||
};
|
||||
let history_row_mapping = |movie_history_item: &MovieHistoryItem| {
|
||||
let MovieHistoryItem {
|
||||
source_title,
|
||||
quality,
|
||||
languages,
|
||||
date,
|
||||
event_type,
|
||||
} = movie_history_item;
|
||||
modal.movie_history.current_selection().clone()
|
||||
}
|
||||
} else {
|
||||
MovieHistoryItem::default()
|
||||
};
|
||||
let ui_scroll_tick_count = app.ui_scroll_tick_count;
|
||||
|
||||
movie_history_item.source_title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 34),
|
||||
current_selection == *movie_history_item,
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
let history_row_mapping = |movie_history_item: &MovieHistoryItem| {
|
||||
let MovieHistoryItem {
|
||||
source_title,
|
||||
quality,
|
||||
languages,
|
||||
date,
|
||||
event_type,
|
||||
} = movie_history_item;
|
||||
|
||||
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()),
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let history_table = ManagarrTable::new(
|
||||
Some(&mut movie_details_modal.movie_history),
|
||||
history_row_mapping,
|
||||
)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.headers(["Source Title", "Event Type", "Languages", "Quality", "Date"])
|
||||
.constraints([
|
||||
Constraint::Percentage(34),
|
||||
Constraint::Percentage(17),
|
||||
Constraint::Percentage(14),
|
||||
Constraint::Percentage(14),
|
||||
Constraint::Percentage(21),
|
||||
]);
|
||||
movie_history_item.source_title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 34),
|
||||
current_selection == *movie_history_item,
|
||||
ui_scroll_tick_count == 0,
|
||||
);
|
||||
|
||||
f.render_widget(history_table, area);
|
||||
}
|
||||
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()),
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let history_table = ManagarrTable::new(
|
||||
app,
|
||||
|app| {
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details_modal
|
||||
.as_mut()
|
||||
.map(|m| &mut m.movie_history)
|
||||
},
|
||||
history_row_mapping,
|
||||
)
|
||||
.block(layout_block_top_border())
|
||||
.headers(["Source Title", "Event Type", "Languages", "Quality", "Date"])
|
||||
.constraints([
|
||||
Constraint::Percentage(34),
|
||||
Constraint::Percentage(17),
|
||||
Constraint::Percentage(14),
|
||||
Constraint::Percentage(14),
|
||||
Constraint::Percentage(21),
|
||||
]);
|
||||
|
||||
f.render_widget(history_table, area);
|
||||
}
|
||||
|
||||
fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
match app.data.radarr_data.movie_details_modal.as_mut() {
|
||||
Some(movie_details_modal) if !app.is_loading => {
|
||||
let cast_row_mapping = |cast_member: &Credit| {
|
||||
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()),
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let content = Some(&mut movie_details_modal.movie_cast);
|
||||
let cast_table = ManagarrTable::new(content, cast_row_mapping)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading)
|
||||
.headers(["Cast Member", "Character"])
|
||||
.constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
||||
Row::new(vec![
|
||||
Cell::from(person_name.to_owned()),
|
||||
Cell::from(character.clone().unwrap_or_default()),
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let cast_table = ManagarrTable::new(
|
||||
app,
|
||||
|app| {
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details_modal
|
||||
.as_mut()
|
||||
.map(|m| &mut m.movie_cast)
|
||||
},
|
||||
cast_row_mapping,
|
||||
)
|
||||
.block(layout_block_top_border())
|
||||
.headers(["Cast Member", "Character"])
|
||||
.constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
||||
|
||||
f.render_widget(cast_table, area);
|
||||
}
|
||||
_ => f.render_widget(
|
||||
LoadingBlock::new(
|
||||
app.is_loading || app.data.radarr_data.movie_details_modal.is_none(),
|
||||
layout_block_top_border(),
|
||||
),
|
||||
area,
|
||||
),
|
||||
}
|
||||
f.render_widget(cast_table, area);
|
||||
}
|
||||
|
||||
fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
match app.data.radarr_data.movie_details_modal.as_mut() {
|
||||
Some(movie_details_modal) if !app.is_loading => {
|
||||
let crew_row_mapping = |crew_member: &Credit| {
|
||||
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()),
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let content = Some(&mut movie_details_modal.movie_crew);
|
||||
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));
|
||||
Row::new(vec![
|
||||
Cell::from(person_name.to_owned()),
|
||||
Cell::from(job.clone().unwrap_or_default()),
|
||||
Cell::from(department.clone().unwrap_or_default()),
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let crew_table = ManagarrTable::new(
|
||||
app,
|
||||
|app| {
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details_modal
|
||||
.as_mut()
|
||||
.map(|m| &mut m.movie_crew)
|
||||
},
|
||||
crew_row_mapping,
|
||||
)
|
||||
.block(layout_block_top_border())
|
||||
.headers(["Crew Member", "Job", "Department"])
|
||||
.constraints(iter::repeat_n(Constraint::Ratio(1, 3), 3));
|
||||
|
||||
f.render_widget(crew_table, area);
|
||||
}
|
||||
|
||||
_ => f.render_widget(
|
||||
LoadingBlock::new(
|
||||
app.is_loading || app.data.radarr_data.movie_details_modal.is_none(),
|
||||
layout_block_top_border(),
|
||||
),
|
||||
area,
|
||||
),
|
||||
}
|
||||
f.render_widget(crew_table, area);
|
||||
}
|
||||
|
||||
fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
@@ -369,16 +372,9 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
_ => (RadarrRelease::default(), true),
|
||||
};
|
||||
let current_route = app.get_current_route();
|
||||
let mut default_movie_details_modal = MovieDetailsModal::default();
|
||||
let content = Some(
|
||||
&mut app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details_modal
|
||||
.as_mut()
|
||||
.unwrap_or(&mut default_movie_details_modal)
|
||||
.movie_releases,
|
||||
);
|
||||
let ui_scroll_tick_count = app.ui_scroll_tick_count;
|
||||
let is_sorting = active_radarr_block == ActiveRadarrBlock::ManualSearchSortPrompt;
|
||||
|
||||
let releases_row_mapping = |release: &RadarrRelease| {
|
||||
let RadarrRelease {
|
||||
protocol,
|
||||
@@ -398,7 +394,7 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
get_width_from_percentage(area, 30),
|
||||
current_selection == *release
|
||||
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
ui_scroll_tick_count == 0,
|
||||
);
|
||||
let size = convert_to_gb(*size);
|
||||
let rejected_str = if *rejected { "⛔" } else { "" };
|
||||
@@ -443,24 +439,35 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let releases_table = ManagarrTable::new(content, releases_row_mapping)
|
||||
.block(layout_block_top_border())
|
||||
.loading(app.is_loading || is_empty)
|
||||
.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 releases_table = ManagarrTable::new(
|
||||
app,
|
||||
|app| {
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details_modal
|
||||
.as_mut()
|
||||
.map(|m| &mut m.movie_releases)
|
||||
},
|
||||
releases_row_mapping,
|
||||
)
|
||||
.block(layout_block_top_border())
|
||||
.loading(is_empty)
|
||||
.sorting(is_sorting)
|
||||
.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(releases_table, area);
|
||||
}
|
||||
|
||||
@@ -83,11 +83,11 @@ fn draw_root_folders(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
};
|
||||
|
||||
let root_folders_table = ManagarrTable::new(
|
||||
Some(&mut app.data.radarr_data.root_folders),
|
||||
app,
|
||||
|app| Some(&mut app.data.radarr_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),
|
||||
|
||||
@@ -96,12 +96,15 @@ fn draw_tasks(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let tasks_table = ManagarrTable::new(Some(&mut app.data.radarr_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.radarr_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);
|
||||
}
|
||||
@@ -144,11 +147,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.radarr_data.queued_events),
|
||||
app,
|
||||
|app| Some(&mut app.data.radarr_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([
|
||||
|
||||
@@ -93,11 +93,14 @@ fn draw_tasks_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
])
|
||||
.primary()
|
||||
};
|
||||
let tasks_table = ManagarrTable::new(Some(&mut app.data.radarr_data.tasks), tasks_row_mapping)
|
||||
.loading(app.is_loading)
|
||||
.margin(1)
|
||||
.headers(TASK_TABLE_HEADERS)
|
||||
.constraints(TASK_TABLE_CONSTRAINTS);
|
||||
let tasks_table = ManagarrTable::new(
|
||||
app,
|
||||
|app| Some(&mut app.data.radarr_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);
|
||||
|
||||
+3
-3
@@ -17,9 +17,9 @@ expression: output
|
||||
╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯
|
||||
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Library │ Collections │ Downloads │ Blocklist │ Root Folders │ Indexers │ System │
|
||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ Title ▼ Year Studio Runtime Rating Language Size Quality Profile Monitored Tags │
|
||||
│=> Test 2023 21st Century Alex 2h 0m R English 3.30 GB HD - 1080p 🏷 alex │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
|
||||
+31
-31
@@ -17,37 +17,37 @@ expression: output
|
||||
╰───────────────────────────────────│ f filter │─────────────────╯╰──────────────────╯
|
||||
╭ Movies ─────────────────────────│ ctrl-r refresh │─────────────────────────────────────╮
|
||||
│ Library │ Collections │ Downloads │ u update all │ │
|
||||
│───────────────────────────────────│ enter details │─────────────────────────────────────│
|
||||
│ Title ▼ │ esc cancel filter │ofile Monitored Tags │
|
||||
│=> Test │ ↑ k scroll up │ 🏷 alex │
|
||||
│ │ ↓ j scroll down │ │
|
||||
│ │ ← h previous tab │ │
|
||||
│ │ → l next tab │ │
|
||||
│ │ pgUp ctrl-u page up │ │
|
||||
│ │ pgDown ctrl-d page down │ │
|
||||
│ │ tab next servarr │ │
|
||||
│ │ shift-tab previous servarr │ │
|
||||
│ │ q quit │ │
|
||||
│ │ ? show/hide keybindings │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
|
||||
+3
-3
@@ -20,9 +20,9 @@ expression: output
|
||||
╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯
|
||||
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Library │ Collections │ Downloads │ Blocklist │ Root Folders │ Indexers │ System │
|
||||
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ Title ▼ Year Studio Runtime Rating Language Size Quality Profile Monitored Tags │
|
||||
│=> Test 2023 21st Century Alex 2h 0m R English 3.30 GB HD - 1080p 🏷 alex │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -7,7 +7,7 @@ use ratatui::prelude::Text;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::widgets::{Block, Paragraph, Widget};
|
||||
use tachyonfx::pattern::SweepPattern;
|
||||
use tachyonfx::{fx, Interpolation};
|
||||
use tachyonfx::fx;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "loading_block_tests.rs"]
|
||||
@@ -40,15 +40,20 @@ impl<'a, 'b> LoadingBlock<'a, 'b> {
|
||||
self.block.render(area, buf);
|
||||
}
|
||||
if let Some(app) = self.app
|
||||
&& !app.has_active_effect {
|
||||
let color = Style::new().failure().fg.expect("primary fg color is unset");
|
||||
let fx =
|
||||
fx::repeating(fx::paint_fg(color, 1000)
|
||||
.with_pattern(SweepPattern::left_to_right(10))
|
||||
.with_area(area));
|
||||
app.effects.add_effect(fx);
|
||||
app.has_active_effect = true;
|
||||
}
|
||||
&& !app.has_active_effect
|
||||
{
|
||||
let color = Style::new()
|
||||
.failure()
|
||||
.fg
|
||||
.expect("primary fg color is unset");
|
||||
let fx = fx::repeating(
|
||||
fx::paint_fg(color, 1000)
|
||||
.with_pattern(SweepPattern::left_to_right(10))
|
||||
.with_area(area),
|
||||
);
|
||||
app.effects.add_effect(fx);
|
||||
app.has_active_effect = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::input_box_popup::InputBoxPopup;
|
||||
use super::message::Message;
|
||||
use super::popup::Size;
|
||||
use crate::app::App;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::ui::HIGHLIGHT_SYMBOL;
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
@@ -16,23 +17,28 @@ use ratatui::prelude::{Style, Stylize, Text};
|
||||
use ratatui::widgets::{Block, ListItem, Row, StatefulWidget, Table, Widget, WidgetRef};
|
||||
use std::fmt::Debug;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tachyonfx::{Interpolation, fx};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "managarr_table_tests.rs"]
|
||||
mod managarr_table_tests;
|
||||
|
||||
#[derive(Setters)]
|
||||
pub struct ManagarrTable<'a, T, F>
|
||||
pub struct ManagarrTable<'a, 'b, T, F, G>
|
||||
where
|
||||
F: Fn(&T) -> Row<'a>,
|
||||
T: Clone + PartialEq + Eq + Debug,
|
||||
G: for<'c> FnMut(&'c mut App<'b>) -> Option<&'c mut StatefulTable<T>>,
|
||||
{
|
||||
#[setters(strip_option)]
|
||||
content: Option<&'a mut StatefulTable<T>>,
|
||||
#[setters(skip)]
|
||||
app: &'a mut App<'b>,
|
||||
#[setters(skip)]
|
||||
content_accessor: G,
|
||||
#[setters(skip)]
|
||||
table_headers: Vec<String>,
|
||||
#[setters(skip)]
|
||||
constraints: Vec<Constraint>,
|
||||
#[setters(skip)]
|
||||
row_mapper: F,
|
||||
block: Block<'a>,
|
||||
margin: u16,
|
||||
@@ -51,46 +57,69 @@ where
|
||||
search_box_offset: usize,
|
||||
filter_box_content_length: usize,
|
||||
filter_box_offset: usize,
|
||||
#[setters(skip)]
|
||||
sort_header_info: Option<(usize, bool)>,
|
||||
}
|
||||
|
||||
impl<'a, T, F> ManagarrTable<'a, T, F>
|
||||
impl<'a, 'b, T, F, G> ManagarrTable<'a, 'b, T, F, G>
|
||||
where
|
||||
F: Fn(&T) -> Row<'a>,
|
||||
T: Clone + PartialEq + Eq + Debug,
|
||||
G: for<'c> FnMut(&'c mut App<'b>) -> Option<&'c mut StatefulTable<T>>,
|
||||
{
|
||||
pub fn new(content: Option<&'a mut StatefulTable<T>>, row_mapper: F) -> Self {
|
||||
let mut managarr_table = Self {
|
||||
content: None,
|
||||
pub fn new(app: &'a mut App<'b>, mut content_accessor: G, row_mapper: F) -> Self {
|
||||
let is_loading = app.is_loading;
|
||||
|
||||
// Extract values from content in a scoped block so the borrow ends
|
||||
let (
|
||||
search_box_content_length,
|
||||
search_box_offset,
|
||||
filter_box_content_length,
|
||||
filter_box_offset,
|
||||
sort_header_info,
|
||||
) = {
|
||||
if let Some(content) = content_accessor(&mut *app) {
|
||||
let (scl, sbo) = if let Some(search) = content.search.as_ref() {
|
||||
(search.text.len(), search.offset.load(Ordering::SeqCst))
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
let (fcl, fbo) = if let Some(filter) = content.filter.as_ref() {
|
||||
(filter.text.len(), filter.offset.load(Ordering::SeqCst))
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
let sort_info = content.sort.as_ref().map(|sort_list| {
|
||||
let idx = sort_list.state.selected().unwrap_or(0);
|
||||
(idx, content.sort_asc)
|
||||
});
|
||||
(scl, sbo, fcl, fbo, sort_info)
|
||||
} else {
|
||||
(0, 0, 0, 0, None)
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
app,
|
||||
content_accessor,
|
||||
table_headers: Vec::new(),
|
||||
constraints: Vec::new(),
|
||||
row_mapper,
|
||||
block: borderless_block(),
|
||||
margin: 0,
|
||||
is_loading: false,
|
||||
is_loading,
|
||||
highlight_rows: true,
|
||||
is_sorting: false,
|
||||
is_searching: false,
|
||||
search_produced_empty_results: false,
|
||||
is_filtering: false,
|
||||
filter_produced_empty_results: false,
|
||||
search_box_content_length: 0,
|
||||
search_box_offset: 0,
|
||||
filter_box_content_length: 0,
|
||||
filter_box_offset: 0,
|
||||
};
|
||||
|
||||
if let Some(content) = content.as_ref() {
|
||||
if let Some(search) = content.search.as_ref() {
|
||||
managarr_table.search_box_content_length = search.text.len();
|
||||
managarr_table.search_box_offset = search.offset.load(Ordering::SeqCst);
|
||||
} else if let Some(filter) = content.filter.as_ref() {
|
||||
managarr_table.filter_box_content_length = filter.text.len();
|
||||
managarr_table.filter_box_offset = filter.offset.load(Ordering::SeqCst);
|
||||
}
|
||||
search_box_content_length,
|
||||
search_box_offset,
|
||||
filter_box_content_length,
|
||||
filter_box_offset,
|
||||
sort_header_info,
|
||||
}
|
||||
|
||||
managarr_table.content = content;
|
||||
managarr_table
|
||||
}
|
||||
|
||||
pub fn headers<I>(mut self, headers: I) -> Self
|
||||
@@ -111,9 +140,9 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
fn render_table(self, area: Rect, buf: &mut Buffer) {
|
||||
fn render_table(mut self, area: Rect, buf: &mut Buffer) {
|
||||
let table_headers = self.parse_headers();
|
||||
let table_area = {
|
||||
let content_area = {
|
||||
let [content_area, _] = Layout::vertical([Constraint::Fill(1), Constraint::Fill(0)])
|
||||
.margin(self.margin)
|
||||
.areas(area);
|
||||
@@ -122,87 +151,98 @@ where
|
||||
};
|
||||
let loading_block = LoadingBlock::new(self.is_loading, self.block.clone());
|
||||
|
||||
if let Some(content) = self.content
|
||||
&& !self.is_loading
|
||||
// Render table content in a scoped block so the borrow ends before we access effects
|
||||
{
|
||||
let (table_contents, table_state) = if content.filtered_items.is_some() {
|
||||
(
|
||||
content.filtered_items.as_ref().unwrap(),
|
||||
content.filtered_state.as_mut().unwrap(),
|
||||
)
|
||||
} else {
|
||||
(&content.items, &mut content.state)
|
||||
};
|
||||
if !table_contents.is_empty() {
|
||||
let rows = table_contents.iter().map(&self.row_mapper);
|
||||
if let Some(content) = (self.content_accessor)(&mut *self.app)
|
||||
&& !self.is_loading
|
||||
{
|
||||
let (table_contents, table_state) = if content.filtered_items.is_some() {
|
||||
(
|
||||
content.filtered_items.as_ref().unwrap(),
|
||||
content.filtered_state.as_mut().unwrap(),
|
||||
)
|
||||
} else {
|
||||
(&content.items, &mut content.state)
|
||||
};
|
||||
if !table_contents.is_empty() {
|
||||
let rows = table_contents.iter().map(&self.row_mapper);
|
||||
|
||||
let headers = Row::new(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)
|
||||
.block(self.block);
|
||||
let mut table = Table::new(rows, &self.constraints)
|
||||
.header(headers)
|
||||
.block(self.block.clone());
|
||||
|
||||
if self.highlight_rows {
|
||||
table = table
|
||||
.row_highlight_style(Style::new().highlight())
|
||||
.highlight_symbol(HIGHLIGHT_SYMBOL);
|
||||
}
|
||||
if self.highlight_rows {
|
||||
table = table
|
||||
.row_highlight_style(Style::new().highlight())
|
||||
.highlight_symbol(HIGHLIGHT_SYMBOL);
|
||||
}
|
||||
|
||||
StatefulWidget::render(table, table_area, buf, table_state);
|
||||
StatefulWidget::render(table, content_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)
|
||||
.dimensions(20, 50)
|
||||
.render(table_area, buf);
|
||||
}
|
||||
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)
|
||||
.dimensions(20, 50)
|
||||
.render(content_area, buf);
|
||||
}
|
||||
|
||||
if self.is_searching {
|
||||
let box_content = &content.search.as_ref().unwrap();
|
||||
InputBoxPopup::new(&box_content.text)
|
||||
.offset(box_content.offset.load(Ordering::SeqCst))
|
||||
.block(title_block_centered("Search"))
|
||||
.render_ref(table_area, buf);
|
||||
}
|
||||
if self.is_searching {
|
||||
let box_content = &content.search.as_ref().unwrap();
|
||||
InputBoxPopup::new(&box_content.text)
|
||||
.offset(box_content.offset.load(Ordering::SeqCst))
|
||||
.block(title_block_centered("Search"))
|
||||
.render_ref(content_area, buf);
|
||||
}
|
||||
|
||||
if self.is_filtering {
|
||||
let box_content = &content.filter.as_ref().unwrap();
|
||||
InputBoxPopup::new(&box_content.text)
|
||||
.offset(box_content.offset.load(Ordering::SeqCst))
|
||||
.block(title_block_centered("Filter"))
|
||||
.render_ref(table_area, buf);
|
||||
}
|
||||
if self.is_filtering {
|
||||
let box_content = &content.filter.as_ref().unwrap();
|
||||
InputBoxPopup::new(&box_content.text)
|
||||
.offset(box_content.offset.load(Ordering::SeqCst))
|
||||
.block(title_block_centered("Filter"))
|
||||
.render_ref(content_area, buf);
|
||||
}
|
||||
|
||||
if self.search_produced_empty_results {
|
||||
Popup::new(Message::new("No items found matching search"))
|
||||
.size(Size::Message)
|
||||
.render(table_area, buf);
|
||||
}
|
||||
if self.search_produced_empty_results {
|
||||
Popup::new(Message::new("No items found matching search"))
|
||||
.size(Size::Message)
|
||||
.render(content_area, buf);
|
||||
}
|
||||
|
||||
if self.filter_produced_empty_results {
|
||||
Popup::new(Message::new("The given filter produced empty results"))
|
||||
.size(Size::Message)
|
||||
.render(table_area, buf);
|
||||
if self.filter_produced_empty_results {
|
||||
Popup::new(Message::new("The given filter produced empty results"))
|
||||
.size(Size::Message)
|
||||
.render(content_area, buf);
|
||||
}
|
||||
} else {
|
||||
loading_block.render(content_area, buf);
|
||||
}
|
||||
} else {
|
||||
loading_block.render(table_area, buf);
|
||||
loading_block.render(content_area, buf);
|
||||
}
|
||||
} else {
|
||||
loading_block.render(table_area, buf);
|
||||
}
|
||||
|
||||
// Now the content borrow has ended, we can access app for effects
|
||||
if !self.app.has_active_effect {
|
||||
let timer = (100, Interpolation::Linear);
|
||||
let fx = fx::coalesce(timer).with_area(content_area);
|
||||
self.app.effects.add_effect(fx);
|
||||
self.app.has_active_effect = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_headers(&self) -> Vec<Text<'a>> {
|
||||
if let Some(ref content) = self.content
|
||||
&& let Some(ref sort_list) = content.sort
|
||||
if let Some((idx, sort_asc)) = self.sort_header_info
|
||||
&& !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);
|
||||
let direction = if sort_asc { " ▲" } else { " ▼" };
|
||||
if idx < new_headers.len() {
|
||||
new_headers[idx].push_str(direction);
|
||||
}
|
||||
|
||||
return new_headers.into_iter().map(Text::from).collect();
|
||||
}
|
||||
@@ -238,10 +278,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, F> Widget for ManagarrTable<'a, T, F>
|
||||
impl<'a, 'b, T, F, G> Widget for ManagarrTable<'a, 'b, T, F, G>
|
||||
where
|
||||
F: Fn(&T) -> Row<'a>,
|
||||
T: Clone + PartialEq + Eq + Debug,
|
||||
G: for<'c> FnMut(&'c mut App<'b>) -> Option<&'c mut StatefulTable<T>>,
|
||||
{
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_table(area, buf);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::App;
|
||||
use crate::models::stateful_list::StatefulList;
|
||||
use crate::models::stateful_table::{SortOption, StatefulTable};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::models::{HorizontallyScrollableText, Scrollable};
|
||||
use crate::ui::utils::borderless_block;
|
||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||
@@ -14,15 +15,19 @@ mod tests {
|
||||
#[test]
|
||||
fn test_managarr_table_new() {
|
||||
let items = vec!["item1", "item2", "item3"];
|
||||
let mut stateful_table = StatefulTable::default();
|
||||
stateful_table.set_items(items.clone());
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data.movies.set_items(items.clone().into_iter().map(|s| {
|
||||
let mut movie = crate::models::radarr_models::Movie::default();
|
||||
movie.title = crate::models::HorizontallyScrollableText::from(s);
|
||||
movie
|
||||
}).collect());
|
||||
|
||||
let managarr_table =
|
||||
ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)]));
|
||||
let managarr_table = ManagarrTable::new(
|
||||
&mut app,
|
||||
|app| Some(&mut app.data.radarr_data.movies),
|
||||
|movie: &crate::models::radarr_models::Movie| Row::new(vec![Cell::new(movie.title.to_string())]),
|
||||
);
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert_eq!(managarr_table.content.unwrap().items, items);
|
||||
assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")]));
|
||||
assert_eq!(managarr_table.table_headers, Vec::<String>::new());
|
||||
assert_eq!(managarr_table.constraints, Vec::new());
|
||||
assert_eq!(managarr_table.block, borderless_block());
|
||||
@@ -43,20 +48,24 @@ mod tests {
|
||||
#[test]
|
||||
fn test_managarr_table_new_search_box_populated() {
|
||||
let items = vec!["item1", "item2", "item3"];
|
||||
let mut stateful_table = StatefulTable::default();
|
||||
stateful_table.set_items(items.clone());
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data.movies.set_items(items.clone().into_iter().map(|s| {
|
||||
let mut movie = crate::models::radarr_models::Movie::default();
|
||||
movie.title = crate::models::HorizontallyScrollableText::from(s);
|
||||
movie
|
||||
}).collect());
|
||||
let horizontally_scrollable_test = HorizontallyScrollableText {
|
||||
text: "test".to_owned(),
|
||||
offset: AtomicUsize::new(3),
|
||||
};
|
||||
stateful_table.search = Some(horizontally_scrollable_test);
|
||||
app.data.radarr_data.movies.search = Some(horizontally_scrollable_test);
|
||||
|
||||
let managarr_table =
|
||||
ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)]));
|
||||
let managarr_table = ManagarrTable::new(
|
||||
&mut app,
|
||||
|app| Some(&mut app.data.radarr_data.movies),
|
||||
|movie: &crate::models::radarr_models::Movie| Row::new(vec![Cell::new(movie.title.to_string())]),
|
||||
);
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert_eq!(managarr_table.content.unwrap().items, items);
|
||||
assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")]));
|
||||
assert_eq!(managarr_table.table_headers, Vec::<String>::new());
|
||||
assert_eq!(managarr_table.constraints, Vec::new());
|
||||
assert_eq!(managarr_table.block, borderless_block());
|
||||
@@ -77,20 +86,24 @@ mod tests {
|
||||
#[test]
|
||||
fn test_managarr_table_new_filter_box_populated() {
|
||||
let items = vec!["item1", "item2", "item3"];
|
||||
let mut stateful_table = StatefulTable::default();
|
||||
stateful_table.set_items(items.clone());
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data.movies.set_items(items.clone().into_iter().map(|s| {
|
||||
let mut movie = crate::models::radarr_models::Movie::default();
|
||||
movie.title = crate::models::HorizontallyScrollableText::from(s);
|
||||
movie
|
||||
}).collect());
|
||||
let horizontally_scrollable_test = HorizontallyScrollableText {
|
||||
text: "test".to_owned(),
|
||||
offset: AtomicUsize::new(3),
|
||||
};
|
||||
stateful_table.filter = Some(horizontally_scrollable_test);
|
||||
app.data.radarr_data.movies.filter = Some(horizontally_scrollable_test);
|
||||
|
||||
let managarr_table =
|
||||
ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)]));
|
||||
let managarr_table = ManagarrTable::new(
|
||||
&mut app,
|
||||
|app| Some(&mut app.data.radarr_data.movies),
|
||||
|movie: &crate::models::radarr_models::Movie| Row::new(vec![Cell::new(movie.title.to_string())]),
|
||||
);
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert_eq!(managarr_table.content.unwrap().items, items);
|
||||
assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")]));
|
||||
assert_eq!(managarr_table.table_headers, Vec::<String>::new());
|
||||
assert_eq!(managarr_table.constraints, Vec::new());
|
||||
assert_eq!(managarr_table.block, borderless_block());
|
||||
@@ -111,18 +124,22 @@ mod tests {
|
||||
#[test]
|
||||
fn test_managarr_table_headers() {
|
||||
let items = vec!["item1", "item2", "item3"];
|
||||
let mut stateful_table = StatefulTable::default();
|
||||
stateful_table.set_items(items.clone());
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data.movies.set_items(items.clone().into_iter().map(|s| {
|
||||
let mut movie = crate::models::radarr_models::Movie::default();
|
||||
movie.title = crate::models::HorizontallyScrollableText::from(s);
|
||||
movie
|
||||
}).collect());
|
||||
let headers = ["column 1", "column 2"];
|
||||
|
||||
let managarr_table =
|
||||
ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)]))
|
||||
.headers(headers);
|
||||
let managarr_table = ManagarrTable::new(
|
||||
&mut app,
|
||||
|app| Some(&mut app.data.radarr_data.movies),
|
||||
|movie: &crate::models::radarr_models::Movie| Row::new(vec![Cell::new(movie.title.to_string())]),
|
||||
)
|
||||
.headers(headers);
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert_eq!(managarr_table.table_headers, headers);
|
||||
assert_eq!(managarr_table.content.unwrap().items, items);
|
||||
assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")]));
|
||||
assert_eq!(managarr_table.constraints, Vec::new());
|
||||
assert_eq!(managarr_table.block, borderless_block());
|
||||
assert_eq!(managarr_table.margin, 0);
|
||||
@@ -142,18 +159,22 @@ mod tests {
|
||||
#[test]
|
||||
fn test_managarr_table_constraints() {
|
||||
let items = vec!["item1", "item2", "item3"];
|
||||
let mut stateful_table = StatefulTable::default();
|
||||
stateful_table.set_items(items.clone());
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data.movies.set_items(items.clone().into_iter().map(|s| {
|
||||
let mut movie = crate::models::radarr_models::Movie::default();
|
||||
movie.title = crate::models::HorizontallyScrollableText::from(s);
|
||||
movie
|
||||
}).collect());
|
||||
let constraints = [Constraint::Length(1), Constraint::Fill(1)];
|
||||
|
||||
let managarr_table =
|
||||
ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)]))
|
||||
.constraints(constraints);
|
||||
let managarr_table = ManagarrTable::new(
|
||||
&mut app,
|
||||
|app| Some(&mut app.data.radarr_data.movies),
|
||||
|movie: &crate::models::radarr_models::Movie| Row::new(vec![Cell::new(movie.title.to_string())]),
|
||||
)
|
||||
.constraints(constraints);
|
||||
|
||||
let row_mapper = managarr_table.row_mapper;
|
||||
assert_eq!(managarr_table.constraints, constraints);
|
||||
assert_eq!(managarr_table.content.unwrap().items, items);
|
||||
assert_eq!(row_mapper(&"item1"), Row::new(vec![Cell::new("item1")]));
|
||||
assert_eq!(managarr_table.table_headers, Vec::<String>::new());
|
||||
assert_eq!(managarr_table.block, borderless_block());
|
||||
assert_eq!(managarr_table.margin, 0);
|
||||
@@ -185,14 +206,21 @@ mod tests {
|
||||
},
|
||||
]);
|
||||
sort_list.scroll_down();
|
||||
let mut stateful_table = StatefulTable::default();
|
||||
stateful_table.set_items(items.clone());
|
||||
stateful_table.sort = Some(sort_list);
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data.movies.set_items(items.clone().into_iter().map(|s| {
|
||||
let mut movie = crate::models::radarr_models::Movie::default();
|
||||
movie.title = crate::models::HorizontallyScrollableText::from(s);
|
||||
movie
|
||||
}).collect());
|
||||
app.data.radarr_data.movies.sort = Some(sort_list);
|
||||
let headers = ["column 1", "column 2"];
|
||||
|
||||
let managarr_table =
|
||||
ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)]))
|
||||
.headers(headers);
|
||||
let managarr_table = ManagarrTable::new(
|
||||
&mut app,
|
||||
|app| Some(&mut app.data.radarr_data.movies),
|
||||
|movie: &crate::models::radarr_models::Movie| Row::new(vec![Cell::new(movie.title.to_string())]),
|
||||
)
|
||||
.headers(headers);
|
||||
|
||||
assert_eq!(
|
||||
managarr_table.parse_headers(),
|
||||
@@ -215,15 +243,22 @@ mod tests {
|
||||
},
|
||||
]);
|
||||
sort_list.scroll_down();
|
||||
let mut stateful_table = StatefulTable::default();
|
||||
stateful_table.set_items(items.clone());
|
||||
stateful_table.sort = Some(sort_list);
|
||||
stateful_table.sort_asc = true;
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data.movies.set_items(items.clone().into_iter().map(|s| {
|
||||
let mut movie = crate::models::radarr_models::Movie::default();
|
||||
movie.title = crate::models::HorizontallyScrollableText::from(s);
|
||||
movie
|
||||
}).collect());
|
||||
app.data.radarr_data.movies.sort = Some(sort_list);
|
||||
app.data.radarr_data.movies.sort_asc = true;
|
||||
let headers = ["column 1", "column 2"];
|
||||
|
||||
let managarr_table =
|
||||
ManagarrTable::new(Some(&mut stateful_table), |&s| Row::new(vec![Cell::new(s)]))
|
||||
.headers(headers);
|
||||
let managarr_table = ManagarrTable::new(
|
||||
&mut app,
|
||||
|app| Some(&mut app.data.radarr_data.movies),
|
||||
|movie: &crate::models::radarr_models::Movie| Row::new(vec![Cell::new(movie.title.to_string())]),
|
||||
)
|
||||
.headers(headers);
|
||||
|
||||
assert_eq!(
|
||||
managarr_table.parse_headers(),
|
||||
|
||||
+9
-10
@@ -4,7 +4,7 @@ use derive_setters::Setters;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::widgets::{Block, Clear, Widget};
|
||||
use tachyonfx::{fx, Interpolation};
|
||||
use tachyonfx::{Interpolation, fx};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "popup_tests.rs"]
|
||||
@@ -78,7 +78,7 @@ where
|
||||
percent_y: 0,
|
||||
margin: 0,
|
||||
block: None,
|
||||
app: None
|
||||
app: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,14 +119,13 @@ where
|
||||
|
||||
self.widget.render(content_area, buf);
|
||||
if let Some(app) = self.app
|
||||
&& !app.has_active_effect {
|
||||
let timer = (100, Interpolation::Linear);
|
||||
let fx =
|
||||
fx::coalesce(timer)
|
||||
.with_area(content_area);
|
||||
app.effects.add_effect(fx);
|
||||
app.has_active_effect = true;
|
||||
}
|
||||
&& !app.has_active_effect
|
||||
{
|
||||
let timer = (100, Interpolation::Linear);
|
||||
let fx = fx::coalesce(timer).with_area(content_area);
|
||||
app.effects.add_effect(fx);
|
||||
app.has_active_effect = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user