Added horizontally scrollable text

This commit is contained in:
2023-08-08 10:50:04 -06:00
parent 44db47f8ee
commit 43e35da49f
9 changed files with 313 additions and 76 deletions
+11 -36
View File
@@ -7,9 +7,8 @@ use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::Sender;
use tokio::time::Instant;
use crate::app::models::{TabRoute, TabState};
use crate::app::models::{HorizontallyScrollableText, TabRoute, TabState};
use crate::app::radarr::{ActiveRadarrBlock, RadarrData};
use crate::network::radarr_network::RadarrEvent;
use crate::network::NetworkEvent;
pub(crate) mod key_binding;
@@ -34,7 +33,7 @@ pub struct App {
navigation_stack: Vec<Route>,
network_tx: Option<Sender<NetworkEvent>>,
pub server_tabs: TabState,
pub error: String,
pub error: HorizontallyScrollableText,
pub client: Client,
pub title: &'static str,
pub tick_until_poll: u64,
@@ -72,47 +71,23 @@ impl App {
pub fn reset(&mut self) {
self.reset_tick_count();
self.error = String::default();
self.error = HorizontallyScrollableText::default();
self.data = Data::default();
}
pub fn handle_error(&mut self, error: anyhow::Error) {
if self.error.is_empty() {
self.error = format!("{} ", error);
if self.error.text.is_empty() {
self.error = HorizontallyScrollableText::new(error.to_string());
}
}
fn scroll_error_horizontally(&mut self) {
let first_char = self.error.chars().next().unwrap();
self.error = format!("{}{}", &self.error[1..], first_char);
}
pub async fn on_tick(&mut self, is_first_render: bool) {
if !self.error.is_empty() {
self.scroll_error_horizontally();
}
if self.tick_count % self.tick_until_poll == 0 || self.is_routing {
match self.get_current_route() {
Route::Radarr(active_radarr_block) => {
let active_block = active_radarr_block.clone();
if is_first_render {
self.dispatch(RadarrEvent::GetQualityProfiles.into()).await;
self.dispatch(RadarrEvent::GetOverview.into()).await;
self.dispatch(RadarrEvent::GetStatus.into()).await;
self.dispatch_by_radarr_block(active_block.clone()).await;
}
if self.is_routing
|| self
.network_tick_frequency
.checked_sub(self.last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0))
.is_zero()
{
self.dispatch_by_radarr_block(active_block).await;
}
self
.radarr_on_tick(active_radarr_block.clone(), is_first_render)
.await;
}
_ => (),
}
@@ -150,7 +125,7 @@ impl Default for App {
App {
navigation_stack: vec![DEFAULT_ROUTE],
network_tx: None,
error: String::default(),
error: HorizontallyScrollableText::default(),
server_tabs: TabState::new(vec![
TabRoute {
title: "Radarr".to_owned(),
@@ -163,9 +138,9 @@ impl Default for App {
]),
client: Client::new(),
title: "Managarr",
tick_until_poll: 20,
tick_until_poll: 50,
tick_count: 0,
network_tick_frequency: Duration::from_secs(10),
network_tick_frequency: Duration::from_secs(20),
last_tick: Instant::now(),
is_loading: false,
is_routing: false,
+50 -1
View File
@@ -1,3 +1,7 @@
use std::cell::RefCell;
use std::fmt::{Display, Formatter};
use serde::Deserialize;
use tui::widgets::TableState;
use crate::app::Route;
@@ -21,7 +25,7 @@ impl<T> Default for StatefulTable<T> {
}
}
impl<T> StatefulTable<T> {
impl<T: Clone> StatefulTable<T> {
pub fn set_items(&mut self, items: Vec<T>) {
let items_len = items.len();
self.items = items;
@@ -42,6 +46,10 @@ impl<T> StatefulTable<T> {
pub fn current_selection(&self) -> &T {
&self.items[self.state.selected().unwrap_or(0)]
}
pub fn current_selection_clone(&self) -> T {
self.items[self.state.selected().unwrap_or(0)].clone()
}
}
impl<T> Scrollable for StatefulTable<T> {
@@ -108,6 +116,47 @@ impl Scrollable for ScrollableText {
}
}
#[derive(Default, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(from = "String")]
pub struct HorizontallyScrollableText {
pub text: String,
pub offset: RefCell<usize>,
}
impl From<String> for HorizontallyScrollableText {
fn from(input: String) -> HorizontallyScrollableText {
HorizontallyScrollableText::new(input)
}
}
impl Display for HorizontallyScrollableText {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if *self.offset.borrow() == 0 {
write!(f, "{}", self.text)
} else {
write!(f, "{}", &self.text[*self.offset.borrow()..])
}
}
}
impl HorizontallyScrollableText {
pub fn new(input: String) -> HorizontallyScrollableText {
HorizontallyScrollableText {
text: format!("{} ", input),
offset: RefCell::new(0),
}
}
pub fn scroll_text(&self) {
let new_offset = *self.offset.borrow() + 1;
*self.offset.borrow_mut() = new_offset % self.text.len();
}
pub fn reset_offset(&self) {
*self.offset.borrow_mut() = 0;
}
}
#[derive(Clone)]
pub struct TabRoute {
pub title: String,
+53 -5
View File
@@ -1,4 +1,5 @@
use std::collections::HashMap;
use std::time::Duration;
use chrono::{DateTime, Utc};
use strum::EnumIter;
@@ -6,7 +7,7 @@ use strum::EnumIter;
use crate::app::models::{ScrollableText, StatefulTable, TabRoute, TabState};
use crate::app::App;
use crate::network::radarr_network::{
DiskSpace, DownloadRecord, Movie, MovieHistoryItem, RadarrEvent,
Credit, DiskSpace, DownloadRecord, Movie, MovieHistoryItem, RadarrEvent,
};
pub struct RadarrData {
@@ -18,14 +19,18 @@ pub struct RadarrData {
pub quality_profile_map: HashMap<u64, String>,
pub movie_details: ScrollableText,
pub movie_history: StatefulTable<MovieHistoryItem>,
pub movie_cast: StatefulTable<Credit>,
pub movie_crew: StatefulTable<Credit>,
pub main_tabs: TabState,
pub movie_info_tabs: TabState,
}
impl RadarrData {
pub fn reset_movie_info_tab(&mut self) {
pub fn reset_movie_info_tabs(&mut self) {
self.movie_details = ScrollableText::default();
self.movie_history = StatefulTable::default();
self.movie_cast = StatefulTable::default();
self.movie_crew = StatefulTable::default();
self.movie_info_tabs.index = 0;
}
@@ -45,6 +50,8 @@ impl Default for RadarrData {
quality_profile_map: HashMap::default(),
movie_details: ScrollableText::default(),
movie_history: StatefulTable::default(),
movie_cast: StatefulTable::default(),
movie_crew: StatefulTable::default(),
main_tabs: TabState::new(vec![
TabRoute {
title: "Library".to_owned(),
@@ -64,6 +71,14 @@ impl Default for RadarrData {
title: "History".to_owned(),
route: ActiveRadarrBlock::MovieHistory.into(),
},
TabRoute {
title: "Cast".to_owned(),
route: ActiveRadarrBlock::Cast.into(),
},
TabRoute {
title: "Crew".to_owned(),
route: ActiveRadarrBlock::Crew.into(),
},
]),
}
}
@@ -74,6 +89,8 @@ pub enum ActiveRadarrBlock {
AddMovie,
Calendar,
Collections,
Cast,
Crew,
Events,
Logs,
Movies,
@@ -86,7 +103,7 @@ pub enum ActiveRadarrBlock {
}
impl App {
pub(super) async fn dispatch_by_radarr_block(&mut self, active_radarr_block: ActiveRadarrBlock) {
pub(super) async fn dispatch_by_radarr_block(&mut self, active_radarr_block: &ActiveRadarrBlock) {
match active_radarr_block {
ActiveRadarrBlock::Downloads => self.dispatch(RadarrEvent::GetDownloads.into()).await,
ActiveRadarrBlock::Movies => {
@@ -95,15 +112,46 @@ impl App {
}
ActiveRadarrBlock::MovieDetails => {
self.is_loading = true;
self.dispatch(RadarrEvent::GetMovieDetails.into()).await
self.dispatch(RadarrEvent::GetMovieDetails.into()).await;
}
ActiveRadarrBlock::MovieHistory => {
self.is_loading = true;
self.dispatch(RadarrEvent::GetMovieHistory.into()).await
self.dispatch(RadarrEvent::GetMovieHistory.into()).await;
}
ActiveRadarrBlock::Cast | ActiveRadarrBlock::Crew => {
if self.data.radarr_data.movie_cast.items.is_empty()
|| self.data.radarr_data.movie_crew.items.is_empty()
{
self.is_loading = true;
self.dispatch(RadarrEvent::GetMovieCredits.into()).await;
}
}
_ => (),
}
self.reset_tick_count();
}
pub(super) async fn radarr_on_tick(
&mut self,
active_radarr_block: ActiveRadarrBlock,
is_first_render: bool,
) {
if is_first_render {
self.dispatch(RadarrEvent::GetQualityProfiles.into()).await;
self.dispatch(RadarrEvent::GetOverview.into()).await;
self.dispatch(RadarrEvent::GetStatus.into()).await;
self.dispatch_by_radarr_block(&active_radarr_block).await;
}
if self.is_routing
|| self
.network_tick_frequency
.checked_sub(self.last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0))
.is_zero()
{
self.dispatch_by_radarr_block(&active_radarr_block).await;
}
}
}