Compare commits
3 Commits
develop
...
ea73f1d3d4
| Author | SHA1 | Date | |
|---|---|---|---|
| ea73f1d3d4 | |||
| d69056e5d6 | |||
| 051f30b097 |
Generated
+120
-8
@@ -41,6 +41,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anpa"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d032745fe46100dbcb28ee6e30f12c4b148786f8889e07cd0a3445eeb54970f"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
@@ -217,6 +223,31 @@ dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bon"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebeb9aaf9329dff6ceb65c689ca3db33dbf15f324909c60e4e5eef5701ce31b1"
|
||||
dependencies = [
|
||||
"bon-macros",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bon-macros"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77e9d642a7e3a318e37c2c9427b5a6a48aa1ad55dcd986f3034ab2239045a645"
|
||||
dependencies = [
|
||||
"darling 0.21.3",
|
||||
"ident_case",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.1"
|
||||
@@ -376,6 +407,20 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
|
||||
dependencies = [
|
||||
"castaway",
|
||||
"cfg-if",
|
||||
"itoa",
|
||||
"rustversion",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "confy"
|
||||
version = "0.6.1"
|
||||
@@ -459,8 +504,18 @@ version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.20.11",
|
||||
"darling_macro 0.20.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
|
||||
dependencies = [
|
||||
"darling_core 0.21.3",
|
||||
"darling_macro 0.21.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -477,13 +532,38 @@ dependencies = [
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.20.11",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
|
||||
dependencies = [
|
||||
"darling_core 0.21.3",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
@@ -536,7 +616,7 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae5c625eda104c228c06ecaf988d1c60e542176bd7a490e60eeda3493244c0c9"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.20.11",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
@@ -664,7 +744,7 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "583f1f514d2754010ff71ed6853068cacbe43cc142cc076aa1b871d9754efc48"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.20.11",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
@@ -673,7 +753,7 @@ dependencies = [
|
||||
name = "enum_display_style_derive"
|
||||
version = "0.6.1"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.20.11",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
@@ -1249,7 +1329,7 @@ version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.20.11",
|
||||
"indoc",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1454,6 +1534,7 @@ dependencies = [
|
||||
"serial_test",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tachyonfx",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"urlencoding",
|
||||
@@ -1477,6 +1558,12 @@ version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "micromath"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@@ -2005,6 +2092,16 @@ dependencies = [
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
@@ -2109,7 +2206,7 @@ checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cassowary",
|
||||
"compact_str",
|
||||
"compact_str 0.8.1",
|
||||
"crossterm",
|
||||
"indoc",
|
||||
"instability",
|
||||
@@ -2704,6 +2801,21 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tachyonfx"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de9d53c5afe979f7de9d5ba856c9829b118d6aca6335750201f1ada46bc198e2"
|
||||
dependencies = [
|
||||
"anpa",
|
||||
"bon",
|
||||
"compact_str 0.9.0",
|
||||
"micromath",
|
||||
"ratatui",
|
||||
"thiserror 2.0.17",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.23.0"
|
||||
|
||||
@@ -67,6 +67,7 @@ openssl = { version = "0.10.70", features = ["vendored"] }
|
||||
veil = "0.2.0"
|
||||
validate_theme_derive = "0.1.0"
|
||||
enum_display_style_derive = "0.1.0"
|
||||
tachyonfx = { version = "0.21.0", features = ["sendable"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.16"
|
||||
|
||||
@@ -80,6 +80,7 @@ mod tests {
|
||||
assert_eq!(app.tick_until_poll, 400);
|
||||
assert_eq!(app.ticks_until_scroll, 4);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
assert_eq!(app.ui_scroll_tick_count, 0);
|
||||
assert!(!app.is_loading);
|
||||
assert!(!app.is_routing);
|
||||
assert!(!app.should_refresh);
|
||||
@@ -240,6 +241,27 @@ mod tests {
|
||||
assert_eq!(app.tick_count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_on_ui_scroll_tick() {
|
||||
let mut app = App {
|
||||
ticks_until_scroll: 1,
|
||||
..App::default()
|
||||
};
|
||||
|
||||
assert_eq!(app.ui_scroll_tick_count, 0);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
|
||||
app.on_ui_scroll_tick();
|
||||
|
||||
assert_eq!(app.ui_scroll_tick_count, 1);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
|
||||
app.on_ui_scroll_tick();
|
||||
|
||||
assert_eq!(app.ui_scroll_tick_count, 0);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_tick_first_render() {
|
||||
let (sync_network_tx, mut sync_network_rx) = mpsc::channel::<NetworkEvent>(500);
|
||||
|
||||
@@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, process};
|
||||
use tachyonfx::{Duration, EffectManager};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use veil::Redact;
|
||||
@@ -39,6 +40,10 @@ pub struct App<'a> {
|
||||
pub tick_until_poll: u64,
|
||||
pub ticks_until_scroll: u64,
|
||||
pub tick_count: u64,
|
||||
pub ui_scroll_tick_count: u64,
|
||||
pub last_tick: Duration,
|
||||
pub effects: EffectManager<()>,
|
||||
pub has_active_effect: bool,
|
||||
pub is_routing: bool,
|
||||
pub is_loading: bool,
|
||||
pub should_refresh: bool,
|
||||
@@ -145,6 +150,14 @@ impl App<'_> {
|
||||
self.tick_count = 0;
|
||||
}
|
||||
|
||||
pub fn on_ui_scroll_tick(&mut self) {
|
||||
if self.ui_scroll_tick_count == self.ticks_until_scroll {
|
||||
self.ui_scroll_tick_count = 0;
|
||||
} else {
|
||||
self.ui_scroll_tick_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn reset(&mut self) {
|
||||
self.reset_tick_count();
|
||||
@@ -180,6 +193,7 @@ impl App<'_> {
|
||||
pub fn push_navigation_stack(&mut self, route: Route) {
|
||||
self.navigation_stack.push(route);
|
||||
self.is_routing = true;
|
||||
self.has_active_effect = false;
|
||||
}
|
||||
|
||||
pub fn pop_navigation_stack(&mut self) {
|
||||
@@ -227,6 +241,10 @@ impl Default for App<'_> {
|
||||
tick_until_poll: 400,
|
||||
ticks_until_scroll: 4,
|
||||
tick_count: 0,
|
||||
ui_scroll_tick_count: 0,
|
||||
last_tick: Duration::ZERO,
|
||||
effects: EffectManager::default(),
|
||||
has_active_effect: false,
|
||||
is_loading: false,
|
||||
is_routing: false,
|
||||
should_refresh: false,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::thread;
|
||||
@@ -49,7 +50,10 @@ impl Events {
|
||||
Events { rx }
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Result<InputEvent<Key>, mpsc::RecvError> {
|
||||
self.rx.recv()
|
||||
pub fn next(&self) -> Result<Option<InputEvent<Key>>> {
|
||||
match self.rx.try_recv() {
|
||||
Ok(event) => Ok(Some(event)),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-11
@@ -3,37 +3,38 @@
|
||||
extern crate assertables;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{CommandFactory, Parser, crate_authors, crate_description, crate_name, crate_version};
|
||||
use clap::{crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser};
|
||||
use clap_complete::generate;
|
||||
use crossterm::execute;
|
||||
use crossterm::terminal::{
|
||||
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
};
|
||||
use log::{debug, error, warn};
|
||||
use network::NetworkTrait;
|
||||
use ratatui::Terminal;
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
use ratatui::Terminal;
|
||||
use reqwest::Client;
|
||||
use std::panic::PanicHookInfo;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use std::{io, panic, process};
|
||||
use tokio::select;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
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::{App, log_and_print_error};
|
||||
use crate::app::{log_and_print_error, App};
|
||||
use crate::cli::Command;
|
||||
use crate::event::Key;
|
||||
use crate::event::input_event::{Events, InputEvent};
|
||||
use crate::event::Key;
|
||||
use crate::network::{Network, NetworkEvent};
|
||||
use crate::ui::theme::{Theme, ThemeDefinitionsWrapper};
|
||||
use crate::ui::{THEME, ui};
|
||||
use crate::ui::{ui, THEME};
|
||||
use crate::utils::load_theme_config;
|
||||
|
||||
mod app;
|
||||
@@ -242,22 +243,25 @@ async fn start_ui(
|
||||
terminal.hide_cursor()?;
|
||||
|
||||
let input_events = Events::new();
|
||||
let mut last_frame_instant = Instant::now();
|
||||
|
||||
loop {
|
||||
let mut app = app.lock().await;
|
||||
app.last_tick = last_frame_instant.elapsed().into();
|
||||
last_frame_instant = Instant::now();
|
||||
|
||||
terminal.draw(|f| ui(f, &mut app))?;
|
||||
|
||||
match input_events.next()? {
|
||||
InputEvent::KeyEvent(key) => {
|
||||
Some(InputEvent::KeyEvent(key)) => {
|
||||
if key == Key::Char('q') && !app.ignore_special_keys_for_textbox_input {
|
||||
break;
|
||||
}
|
||||
|
||||
handlers::handle_events(key, &mut app);
|
||||
}
|
||||
|
||||
InputEvent::Tick => app.on_tick().await,
|
||||
Some(InputEvent::Tick) => app.on_tick().await,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-5
@@ -52,6 +52,7 @@ pub trait DrawUi {
|
||||
}
|
||||
|
||||
pub fn ui(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
app.on_ui_scroll_tick();
|
||||
f.render_widget(background_block(), f.area());
|
||||
let [header_area, context_area, table_area] = if !app.error.text.is_empty() {
|
||||
let [header_area, error_area, context_area, table_area] = Layout::vertical([
|
||||
@@ -91,6 +92,11 @@ pub fn ui(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
if app.keymapping_table.is_some() {
|
||||
draw_help_popup(f, app);
|
||||
}
|
||||
|
||||
let area = f.area();
|
||||
app
|
||||
.effects
|
||||
.process_effects(app.last_tick, f.buffer_mut(), area);
|
||||
}
|
||||
|
||||
fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
@@ -124,11 +130,9 @@ fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
.failure()
|
||||
.bold();
|
||||
|
||||
app.error.scroll_left_or_reset(
|
||||
area.width as usize,
|
||||
true,
|
||||
app.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
);
|
||||
app
|
||||
.error
|
||||
.scroll_left_or_reset(area.width as usize, true, app.ui_scroll_tick_count == 0);
|
||||
|
||||
let paragraph = Paragraph::new(Text::from(app.error.to_string().failure()))
|
||||
.block(block)
|
||||
|
||||
@@ -96,7 +96,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
|
||||
let languages_string = languages
|
||||
|
||||
@@ -90,7 +90,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
let (hours, minutes) = convert_runtime(movie.runtime);
|
||||
let imdb_rating = movie
|
||||
|
||||
@@ -70,7 +70,7 @@ pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect)
|
||||
collection.title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 25),
|
||||
*collection == current_selection,
|
||||
app.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
let monitored = if collection.monitored { "🏷" } else { "" };
|
||||
let search_on_add = if collection.search_on_add {
|
||||
|
||||
@@ -87,7 +87,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
let pass_fail = if result.is_valid { "✔" } else { "❌" };
|
||||
let row = Row::new(vec![
|
||||
|
||||
@@ -139,7 +139,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
|
||||
Row::new(vec![
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
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::ui::DrawUi;
|
||||
use crate::models::Route;
|
||||
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;
|
||||
@@ -16,6 +15,7 @@ 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;
|
||||
@@ -64,7 +64,9 @@ impl DrawUi for LibraryUi {
|
||||
.yes_no_value(app.data.radarr_data.prompt_confirm);
|
||||
|
||||
f.render_widget(
|
||||
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
|
||||
Popup::new(confirmation_prompt)
|
||||
.size(Size::MediumPrompt)
|
||||
.app(app),
|
||||
f.area(),
|
||||
);
|
||||
}
|
||||
@@ -90,7 +92,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
movie.title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 27),
|
||||
*movie == current_selection,
|
||||
app.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
let monitored = if movie.monitored { "🏷" } else { "" };
|
||||
let studio = movie.studio.clone().unwrap_or_default();
|
||||
|
||||
@@ -246,7 +246,7 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
movie_history_item.source_title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 34),
|
||||
current_selection == *movie_history_item,
|
||||
app.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
|
||||
Row::new(vec![
|
||||
@@ -398,7 +398,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
let size = convert_to_gb(*size);
|
||||
let rejected_str = if *rejected { "⛔" } else { "" };
|
||||
|
||||
@@ -88,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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::ui::DrawUi;
|
||||
use crate::models::Route;
|
||||
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 ratatui::Frame;
|
||||
use crate::ui::DrawUi;
|
||||
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,
|
||||
@@ -69,7 +69,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
|
||||
Row::new(vec![
|
||||
@@ -154,5 +154,6 @@ fn draw_history_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||
.style(Style::new().secondary())
|
||||
.alignment(Alignment::Left);
|
||||
|
||||
f.render_widget(Popup::new(message).size(Size::NarrowMessage), f.area());
|
||||
let widget = Popup::new(message).size(Size::NarrowMessage).app(app);
|
||||
f.render_widget(widget, f.area());
|
||||
}
|
||||
|
||||
@@ -44,7 +44,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
let pass_fail = if result.is_valid { "✔" } else { "❌" };
|
||||
let row = Row::new(vec![
|
||||
|
||||
@@ -119,7 +119,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
|
||||
Row::new(vec![
|
||||
|
||||
@@ -281,7 +281,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
|
||||
Row::new(vec![
|
||||
@@ -431,7 +431,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
let size = convert_to_gb(*size);
|
||||
let rejected_str = if *rejected { "⛔" } else { "" };
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -95,7 +95,7 @@ fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
series.title.scroll_left_or_reset(
|
||||
get_width_from_percentage(area, 23),
|
||||
*series == current_selection,
|
||||
app.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
let monitored = if series.monitored { "🏷" } else { "" };
|
||||
let certification = series.certification.clone().unwrap_or_default();
|
||||
|
||||
@@ -271,7 +271,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
|
||||
Row::new(vec![
|
||||
@@ -382,7 +382,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
let size = convert_to_gb(*size);
|
||||
let rejected_str = if *rejected { "⛔" } else { "" };
|
||||
|
||||
@@ -315,7 +315,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.tick_count.is_multiple_of(app.ticks_until_scroll),
|
||||
app.ui_scroll_tick_count == 0,
|
||||
);
|
||||
|
||||
Row::new(vec![
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
use crate::app::App;
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use derive_setters::Setters;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::Text;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::widgets::{Block, Paragraph, Widget};
|
||||
use tachyonfx::pattern::SweepPattern;
|
||||
use tachyonfx::{fx, Interpolation};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "loading_block_tests.rs"]
|
||||
mod loading_block_tests;
|
||||
|
||||
pub struct LoadingBlock<'a> {
|
||||
#[derive(Setters)]
|
||||
pub struct LoadingBlock<'a, 'b> {
|
||||
is_loading: bool,
|
||||
block: Block<'a>,
|
||||
#[setters(strip_option)]
|
||||
app: Option<&'a mut App<'b>>,
|
||||
}
|
||||
|
||||
impl<'a> LoadingBlock<'a> {
|
||||
impl<'a, 'b> LoadingBlock<'a, 'b> {
|
||||
pub fn new(is_loading: bool, block: Block<'a>) -> Self {
|
||||
Self { is_loading, block }
|
||||
Self {
|
||||
is_loading,
|
||||
block,
|
||||
app: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_loading_block(self, area: Rect, buf: &mut Buffer) {
|
||||
@@ -27,10 +39,20 @@ impl<'a> LoadingBlock<'a> {
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for LoadingBlock<'_> {
|
||||
impl Widget for LoadingBlock<'_, '_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_loading_block(area, buf);
|
||||
}
|
||||
|
||||
+26
-13
@@ -1,7 +1,10 @@
|
||||
use crate::app::App;
|
||||
use crate::ui::utils::{background_block, centered_rect};
|
||||
use derive_setters::Setters;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::widgets::{Block, Clear, Widget};
|
||||
use tachyonfx::{fx, Interpolation};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "popup_tests.rs"]
|
||||
@@ -49,15 +52,25 @@ impl Size {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Popup<'a, T: Widget> {
|
||||
#[derive(Setters)]
|
||||
pub struct Popup<'a, 'b, T: Widget> {
|
||||
#[setters(skip)]
|
||||
widget: T,
|
||||
margin: u16,
|
||||
#[setters(skip)]
|
||||
percent_x: u16,
|
||||
#[setters(skip)]
|
||||
percent_y: u16,
|
||||
#[setters(strip_option)]
|
||||
block: Option<Block<'a>>,
|
||||
#[setters(strip_option)]
|
||||
app: Option<&'a mut App<'b>>,
|
||||
}
|
||||
|
||||
impl<'a, T: Widget> Popup<'a, T> {
|
||||
impl<T> Popup<'_, '_, T>
|
||||
where
|
||||
T: Widget,
|
||||
{
|
||||
pub fn new(widget: T) -> Self {
|
||||
Self {
|
||||
widget,
|
||||
@@ -65,6 +78,7 @@ impl<'a, T: Widget> Popup<'a, T> {
|
||||
percent_y: 0,
|
||||
margin: 0,
|
||||
block: None,
|
||||
app: None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,16 +95,6 @@ impl<'a, T: Widget> Popup<'a, T> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn block(mut self, block: Block<'a>) -> Self {
|
||||
self.block = Some(block);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn margin(mut self, margin: u16) -> Self {
|
||||
self.margin = margin;
|
||||
self
|
||||
}
|
||||
|
||||
fn render_popup(self, area: Rect, buf: &mut Buffer) {
|
||||
let mut popup_area = centered_rect(self.percent_x, self.percent_y, area);
|
||||
let height = if popup_area.height < 3 {
|
||||
@@ -114,10 +118,19 @@ impl<'a, T: Widget> Popup<'a, T> {
|
||||
.areas(popup_area);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Widget> Widget for Popup<'_, T> {
|
||||
impl<T: Widget> Widget for Popup<'_, '_, T> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_popup(area, buf);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user