Refactored things a bit and added help text support
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use serde::Deserialize;
|
||||
use tui::widgets::TableState;
|
||||
|
||||
use crate::app::radarr::ActiveRadarrBlock;
|
||||
|
||||
pub mod radarr_models;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Route {
|
||||
Radarr(ActiveRadarrBlock),
|
||||
Sonarr,
|
||||
}
|
||||
|
||||
pub trait Scrollable {
|
||||
fn scroll_down(&mut self);
|
||||
fn scroll_up(&mut self);
|
||||
}
|
||||
|
||||
pub struct StatefulTable<T> {
|
||||
pub state: TableState,
|
||||
pub items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for StatefulTable<T> {
|
||||
fn default() -> StatefulTable<T> {
|
||||
StatefulTable {
|
||||
state: TableState::default(),
|
||||
items: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> StatefulTable<T> {
|
||||
pub fn set_items(&mut self, items: Vec<T>) {
|
||||
let items_len = items.len();
|
||||
self.items = items;
|
||||
if !self.items.is_empty() {
|
||||
let selected_row = self.state.selected().map_or(0, |i| {
|
||||
if i > 0 && i < items_len {
|
||||
i
|
||||
} else if i >= items_len {
|
||||
items_len - 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
});
|
||||
self.state.select(Some(selected_row));
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
fn scroll_down(&mut self) {
|
||||
let selected_row = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.items.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
|
||||
self.state.select(Some(selected_row));
|
||||
}
|
||||
|
||||
fn scroll_up(&mut self) {
|
||||
let selected_row = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
self.items.len() - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
|
||||
self.state.select(Some(selected_row));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ScrollableText {
|
||||
pub items: Vec<String>,
|
||||
pub offset: u16,
|
||||
}
|
||||
|
||||
impl ScrollableText {
|
||||
pub fn with_string(item: String) -> ScrollableText {
|
||||
let items: Vec<&str> = item.split('\n').collect();
|
||||
let items: Vec<String> = items.iter().map(|it| it.to_string()).collect();
|
||||
ScrollableText { items, offset: 0 }
|
||||
}
|
||||
|
||||
pub fn get_text(&self) -> String {
|
||||
self.items.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
impl Scrollable for ScrollableText {
|
||||
fn scroll_down(&mut self) {
|
||||
if self.offset < self.items.len() as u16 {
|
||||
self.offset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll_up(&mut self) {
|
||||
if self.offset > 0 {
|
||||
self.offset -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
pub route: Route,
|
||||
pub help: String,
|
||||
}
|
||||
|
||||
pub struct TabState {
|
||||
pub tabs: Vec<TabRoute>,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl TabState {
|
||||
pub fn new(tabs: Vec<TabRoute>) -> TabState {
|
||||
TabState { tabs, index: 0 }
|
||||
}
|
||||
|
||||
pub fn set_index(&mut self, index: usize) -> &TabRoute {
|
||||
self.index = index;
|
||||
&self.tabs[self.index]
|
||||
}
|
||||
|
||||
pub fn get_active_route(&self) -> &Route {
|
||||
&self.tabs[self.index].route
|
||||
}
|
||||
|
||||
pub fn get_active_tab_help(&self) -> String {
|
||||
self.tabs[self.index].help.clone()
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
self.index = (self.index + 1) % self.tabs.len();
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
if self.index > 0 {
|
||||
self.index -= 1;
|
||||
} else {
|
||||
self.index = self.tabs.len() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use derivative::Derivative;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Number;
|
||||
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiskSpace {
|
||||
pub free_space: Number,
|
||||
pub total_space: Number,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SystemStatus {
|
||||
pub version: String,
|
||||
pub start_time: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Deserialize, Debug, Clone)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Movie {
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub id: Number,
|
||||
pub title: String,
|
||||
pub original_language: Language,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub size_on_disk: Number,
|
||||
pub status: String,
|
||||
pub overview: String,
|
||||
pub path: String,
|
||||
pub studio: String,
|
||||
pub genres: Vec<String>,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub year: Number,
|
||||
pub monitored: bool,
|
||||
pub has_file: bool,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub runtime: Number,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub quality_profile_id: Number,
|
||||
pub certification: Option<String>,
|
||||
pub ratings: RatingsList,
|
||||
pub movie_file: Option<MovieFile>,
|
||||
pub collection: Option<Collection>,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Deserialize, Debug, Clone)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CollectionMovie {
|
||||
pub title: String,
|
||||
pub overview: String,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub year: Number,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub runtime: Number,
|
||||
pub genres: Vec<String>,
|
||||
pub ratings: RatingsList,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Derivative, Clone, Debug)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Collection {
|
||||
pub title: String,
|
||||
pub root_folder_path: Option<String>,
|
||||
pub search_on_add: bool,
|
||||
pub overview: Option<String>,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub quality_profile_id: Number,
|
||||
pub movies: Option<Vec<CollectionMovie>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Derivative, Debug, Clone)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MovieFile {
|
||||
pub relative_path: String,
|
||||
pub path: String,
|
||||
pub date_added: DateTime<Utc>,
|
||||
pub media_info: MediaInfo,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Derivative, Debug, Clone)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MediaInfo {
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub audio_bitrate: Number,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub audio_channels: Number,
|
||||
pub audio_codec: Option<String>,
|
||||
pub audio_languages: Option<String>,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub audio_stream_count: Number,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub video_bit_depth: Number,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub video_bitrate: Number,
|
||||
pub video_codec: String,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub video_fps: Number,
|
||||
pub resolution: String,
|
||||
pub run_time: String,
|
||||
pub scan_type: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RatingsList {
|
||||
pub imdb: Option<Rating>,
|
||||
pub tmdb: Option<Rating>,
|
||||
pub rotten_tomatoes: Option<Rating>,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Deserialize, Debug, Clone)]
|
||||
#[derivative(Default)]
|
||||
pub struct Rating {
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub value: Number,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Deserialize, Debug)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadsResponse {
|
||||
pub records: Vec<DownloadRecord>,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadRecord {
|
||||
pub title: String,
|
||||
pub status: String,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub movie_id: Number,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub size: Number,
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub sizeleft: Number,
|
||||
pub output_path: HorizontallyScrollableText,
|
||||
pub indexer: String,
|
||||
pub download_client: String,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Deserialize, Debug)]
|
||||
#[derivative(Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QualityProfile {
|
||||
#[derivative(Default(value = "Number::from(0)"))]
|
||||
pub id: Number,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MovieHistoryItem {
|
||||
pub source_title: HorizontallyScrollableText,
|
||||
pub quality: QualityHistory,
|
||||
pub languages: Vec<Language>,
|
||||
pub date: DateTime<Utc>,
|
||||
pub event_type: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Language {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct QualityHistory {
|
||||
pub quality: Quality,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Quality {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, PartialEq, Eq, Clone, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum CreditType {
|
||||
Cast,
|
||||
Crew,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Credit {
|
||||
pub person_name: String,
|
||||
pub character: Option<String>,
|
||||
pub department: Option<String>,
|
||||
pub job: Option<String>,
|
||||
#[serde(rename(deserialize = "type"))]
|
||||
pub credit_type: CreditType,
|
||||
}
|
||||
Reference in New Issue
Block a user