feat: run the tui
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
pub use error_stack::{Report, ResultExt};
|
// Removed unused imports Report and ResultExt
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
#[error("An error occurred")]
|
#[error("An error occurred")]
|
||||||
pub struct Error;
|
pub struct Error;
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
pub mod errors;
|
|
||||||
use errors::*;
|
|
||||||
mod api;
|
|
||||||
@@ -2,10 +2,10 @@ mod api;
|
|||||||
mod cli;
|
mod cli;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod tui;
|
mod tui;
|
||||||
use errors::*;
|
|
||||||
|
|
||||||
use crate::api::SonarrClient;
|
use crate::api::SonarrClient;
|
||||||
pub fn main() -> Result<()> {
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// let args = <cli::Cli as clap::Parser>::parse();
|
// let args = <cli::Cli as clap::Parser>::parse();
|
||||||
// match args.cmd {
|
// match args.cmd {
|
||||||
// cli::SubCommand::Add(add) => {
|
// cli::SubCommand::Add(add) => {
|
||||||
@@ -23,6 +23,6 @@ pub fn main() -> Result<()> {
|
|||||||
"https://sonarr.tsuba.darksailor.dev".into(),
|
"https://sonarr.tsuba.darksailor.dev".into(),
|
||||||
"1a47401731bf44ae9787dfcd4bab402f".into(),
|
"1a47401731bf44ae9787dfcd4bab402f".into(),
|
||||||
);
|
);
|
||||||
tui::run_app(client);
|
tui::run_app(client).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
84
src/tui.rs
84
src/tui.rs
@@ -7,10 +7,10 @@ use ratatui::{
|
|||||||
backend::{Backend, CrosstermBackend},
|
backend::{Backend, CrosstermBackend},
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
text::{Line, Span, Text},
|
text::{Line, Span},
|
||||||
widgets::{
|
widgets::{
|
||||||
Block, Borders, Cell, Clear, Gauge, List, ListItem, ListState, Paragraph, Row, Table,
|
Block, Borders, Cell, Clear, List, ListItem, ListState, Paragraph, Row, Table, TableState,
|
||||||
TableState, Tabs, Wrap,
|
Tabs, Wrap,
|
||||||
},
|
},
|
||||||
Frame, Terminal,
|
Frame, Terminal,
|
||||||
};
|
};
|
||||||
@@ -649,8 +649,8 @@ async fn handle_input(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
|
fn ui(f: &mut Frame, app: &App) {
|
||||||
let size = f.size();
|
let size = f.area();
|
||||||
|
|
||||||
// Create main layout
|
// Create main layout
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
@@ -684,7 +684,7 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_header<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
fn render_header(f: &mut Frame, area: Rect, app: &App) {
|
||||||
let tabs = Tabs::new(TabIndex::titles())
|
let tabs = Tabs::new(TabIndex::titles())
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
@@ -702,7 +702,7 @@ fn render_header<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
|||||||
f.render_widget(tabs, area);
|
f.render_widget(tabs, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_series_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
fn render_series_tab(f: &mut Frame, area: Rect, app: &App) {
|
||||||
if app.series.is_empty() {
|
if app.series.is_empty() {
|
||||||
let loading_text = if app.loading {
|
let loading_text = if app.loading {
|
||||||
"Loading series..."
|
"Loading series..."
|
||||||
@@ -745,7 +745,7 @@ fn render_series_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
|||||||
f.render_stateful_widget(list, area, &mut app.series_list_state.clone());
|
f.render_stateful_widget(list, area, &mut app.series_list_state.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_calendar_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
fn render_calendar_tab(f: &mut Frame, area: Rect, app: &App) {
|
||||||
if app.calendar.is_empty() {
|
if app.calendar.is_empty() {
|
||||||
let loading_text = if app.loading {
|
let loading_text = if app.loading {
|
||||||
"Loading calendar..."
|
"Loading calendar..."
|
||||||
@@ -801,7 +801,7 @@ fn render_calendar_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
|||||||
f.render_stateful_widget(list, area, &mut app.episodes_list_state.clone());
|
f.render_stateful_widget(list, area, &mut app.episodes_list_state.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_queue_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
fn render_queue_tab(f: &mut Frame, area: Rect, app: &App) {
|
||||||
if app.queue.is_empty() {
|
if app.queue.is_empty() {
|
||||||
let loading_text = if app.loading {
|
let loading_text = if app.loading {
|
||||||
"Loading queue..."
|
"Loading queue..."
|
||||||
@@ -838,7 +838,7 @@ fn render_queue_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
|||||||
|
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(title),
|
Cell::from(title),
|
||||||
Cell::from(status),
|
Cell::from(status.as_str()),
|
||||||
Cell::from(progress),
|
Cell::from(progress),
|
||||||
Cell::from(quality),
|
Cell::from(quality),
|
||||||
Cell::from(size),
|
Cell::from(size),
|
||||||
@@ -846,30 +846,32 @@ fn render_queue_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let table = Table::new(rows)
|
let table = Table::new(
|
||||||
.header(header.style(Style::default().fg(Color::Yellow)))
|
rows,
|
||||||
.block(
|
&[
|
||||||
Block::default()
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.title("Download Queue"),
|
|
||||||
)
|
|
||||||
.widths(&[
|
|
||||||
Constraint::Percentage(40),
|
Constraint::Percentage(40),
|
||||||
Constraint::Percentage(15),
|
Constraint::Percentage(15),
|
||||||
Constraint::Percentage(15),
|
Constraint::Percentage(15),
|
||||||
Constraint::Percentage(15),
|
Constraint::Percentage(15),
|
||||||
Constraint::Percentage(15),
|
Constraint::Percentage(15),
|
||||||
])
|
],
|
||||||
.highlight_style(
|
)
|
||||||
Style::default()
|
.header(header.style(Style::default().fg(Color::Yellow)))
|
||||||
.fg(Color::Yellow)
|
.block(
|
||||||
.add_modifier(Modifier::BOLD),
|
Block::default()
|
||||||
);
|
.borders(Borders::ALL)
|
||||||
|
.title("Download Queue"),
|
||||||
|
)
|
||||||
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::Yellow)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
);
|
||||||
|
|
||||||
f.render_stateful_widget(table, area, &mut app.queue_table_state.clone());
|
f.render_stateful_widget(table, area, &mut app.queue_table_state.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_history_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
fn render_history_tab(f: &mut Frame, area: Rect, app: &App) {
|
||||||
if app.history.is_empty() {
|
if app.history.is_empty() {
|
||||||
let loading_text = if app.loading {
|
let loading_text = if app.loading {
|
||||||
"Loading history..."
|
"Loading history..."
|
||||||
@@ -910,32 +912,34 @@ fn render_history_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
|||||||
Cell::from(date),
|
Cell::from(date),
|
||||||
Cell::from(series),
|
Cell::from(series),
|
||||||
Cell::from(episode),
|
Cell::from(episode),
|
||||||
Cell::from(event),
|
Cell::from(event.as_str()),
|
||||||
Cell::from(quality),
|
Cell::from(quality),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let table = Table::new(rows)
|
let table = Table::new(
|
||||||
.header(header.style(Style::default().fg(Color::Yellow)))
|
rows,
|
||||||
.block(Block::default().borders(Borders::ALL).title("History"))
|
&[
|
||||||
.widths(&[
|
|
||||||
Constraint::Percentage(20),
|
Constraint::Percentage(20),
|
||||||
Constraint::Percentage(30),
|
Constraint::Percentage(30),
|
||||||
Constraint::Percentage(15),
|
Constraint::Percentage(15),
|
||||||
Constraint::Percentage(20),
|
Constraint::Percentage(20),
|
||||||
Constraint::Percentage(15),
|
Constraint::Percentage(15),
|
||||||
])
|
],
|
||||||
.highlight_style(
|
)
|
||||||
Style::default()
|
.header(header.style(Style::default().fg(Color::Yellow)))
|
||||||
.fg(Color::Yellow)
|
.block(Block::default().borders(Borders::ALL).title("History"))
|
||||||
.add_modifier(Modifier::BOLD),
|
.highlight_style(
|
||||||
);
|
Style::default()
|
||||||
|
.fg(Color::Yellow)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
);
|
||||||
|
|
||||||
f.render_stateful_widget(table, area, &mut app.history_table_state.clone());
|
f.render_stateful_widget(table, area, &mut app.history_table_state.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_health_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
fn render_health_tab(f: &mut Frame, area: Rect, app: &App) {
|
||||||
if app.health.is_empty() {
|
if app.health.is_empty() {
|
||||||
let loading_text = if app.loading {
|
let loading_text = if app.loading {
|
||||||
"Loading health status..."
|
"Loading health status..."
|
||||||
@@ -990,7 +994,7 @@ fn render_health_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
|||||||
f.render_stateful_widget(list, area, &mut app.health_list_state.clone());
|
f.render_stateful_widget(list, area, &mut app.health_list_state.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_search_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
fn render_search_tab(f: &mut Frame, area: Rect, app: &App) {
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([Constraint::Length(3), Constraint::Min(0)])
|
.constraints([Constraint::Length(3), Constraint::Min(0)])
|
||||||
@@ -1068,7 +1072,7 @@ fn render_search_tab<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_footer<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
fn render_footer(f: &mut Frame, area: Rect, app: &App) {
|
||||||
let help_text = if app.input_mode {
|
let help_text = if app.input_mode {
|
||||||
"ESC: Cancel | Enter: Search | Type to enter search term"
|
"ESC: Cancel | Enter: Search | Type to enter search term"
|
||||||
} else {
|
} else {
|
||||||
@@ -1100,7 +1104,7 @@ fn render_footer<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
|||||||
f.render_widget(paragraph, area);
|
f.render_widget(paragraph, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_error_popup<B: Backend>(f: &mut Frame<B>, area: Rect, app: &App) {
|
fn render_error_popup(f: &mut Frame, area: Rect, app: &App) {
|
||||||
if let Some(error) = &app.error_message {
|
if let Some(error) = &app.error_message {
|
||||||
let popup_area = centered_rect(60, 20, area);
|
let popup_area = centered_rect(60, 20, area);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user