feat: run the tui

This commit is contained in:
uttarayan21
2025-10-08 14:58:33 +05:30
parent 3cb82612be
commit c18e92ff01
4 changed files with 49 additions and 48 deletions

View File

@@ -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;

View File

@@ -1,3 +0,0 @@
pub mod errors;
use errors::*;
mod api;

View File

@@ -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(())
} }

View File

@@ -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);