diff --git a/src/commands.rs b/src/commands.rs index 9e17806..636677b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -5,16 +5,25 @@ use crate::{ persistence::Db, }; -pub fn execute(mut db: Db, args: &TrsArgs) -> Result<(), TrsError> { - let sub_command = &args.sub_command; - match sub_command { - TrsSubCommand::AddChannel(add_args) => add_channel(&mut db, add_args), - TrsSubCommand::ListChannels(list_args) => list_channels(&mut db, list_args), - TrsSubCommand::RemoveChannel(delete_args) => delete_channel(&mut db, delete_args), +pub struct RssChannelD { + pub id: i64, + pub title: String, + pub link: String, + pub description: String, +} + +impl RssChannelD { + fn new(id: i64, title: String, link: String, description: String) -> Self { + RssChannelD { + id, + title, + link, + description, + } } } -fn add_channel(db: &mut Db, args: &AddChannelArgs) -> Result<(), TrsError> { +pub fn add_channel(db: &mut Db, args: &AddChannelArgs) -> Result<(), TrsError> { let client = reqwest::blocking::Client::new(); let rss = client.get(&args.link).send().map_err(|e| { TrsError::ReqwestError( @@ -39,7 +48,7 @@ fn add_channel(db: &mut Db, args: &AddChannelArgs) -> Result<(), TrsError> { Ok(()) } -fn list_channels(conn: &mut Db, args: &ListChannelArgs) -> Result<(), TrsError> { +pub fn list_channels(conn: &mut Db, args: &ListChannelArgs) -> Result, TrsError> { let channels_iter = conn.list_channels .query_map([args.limit.unwrap_or_else(|| 999)], |row| { @@ -51,18 +60,18 @@ fn list_channels(conn: &mut Db, args: &ListChannelArgs) -> Result<(), TrsError> )) })?; + let mut channels = Vec::new(); + for row in channels_iter { let (id, name, link, description) = row?; - println!( - "ID: {}, Name: {}, Link: {}, Description: {}", - id, name, link, description - ); + let channel = RssChannelD::new(id, name, link, description); + channels.push(channel); } - Ok(()) + Ok(channels) } -fn delete_channel(db: &mut Db, args: &RemoveChannelArgs) -> Result<(), TrsError> { +pub fn remove_channel(db: &mut Db, args: &RemoveChannelArgs) -> Result<(), TrsError> { let rows_affected = db .remove_channel .execute([args.id]) diff --git a/src/main.rs b/src/main.rs index 39f2f83..c1a6300 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use args::TrsArgs; +use args::{TrsArgs, TrsSubCommand}; use error::Result; pub mod args; pub mod commands; @@ -10,16 +10,26 @@ pub mod ui; fn main() -> Result<()> { if std::env::args().len() < 2 { let terminal = ratatui::init(); - ui::ui(terminal)?; + let conn = persistence::init_connection()?; + let db = persistence::init_db(&conn)?; + ui::ui(db, terminal)?; ratatui::restore(); return Ok(()); } let args = argh::from_env::(); let conn = persistence::init_connection()?; - let db = persistence::init_db(&conn)?; - commands::execute(db, &args).map_err(|e| { - eprintln!("Error executing command: {}", e); - e - }) + let mut db = persistence::init_db(&conn)?; + match args.sub_command { + TrsSubCommand::AddChannel(args) => commands::add_channel(&mut db, &args), + TrsSubCommand::ListChannels(args) => { + let channels = commands::list_channels(&mut db, &args)?; + for channel in channels { + println!("{}: {} ({})", channel.id, channel.title, channel.link); + } + + return Ok(()); + } + TrsSubCommand::RemoveChannel(args) => commands::remove_channel(&mut db, &args), + } } diff --git a/src/ui.rs b/src/ui.rs index d283042..c022411 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,29 +1,110 @@ use std::io::Stdout; -use crate::error::{Result, TrsError}; +use crate::{ + args::ListChannelArgs, + commands::{self, RssChannelD}, + error::{Result, TrsError}, + persistence::Db, +}; use crossterm::event::{self, Event, KeyEventKind}; -use ratatui::{prelude::CrosstermBackend, Terminal}; +use ratatui::{ + prelude::*, + widgets::{Block, Borders, Paragraph}, +}; + +struct ChannelsWidget<'a> { + channels: &'a Vec, +} + +impl<'a> Widget for ChannelsWidget<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + let columns = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![ + Constraint::Length(5), + Constraint::Length(50), + Constraint::Fill(1), + Constraint::Length(5), + ]) + .split(area); + + let rows = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![ + Constraint::Length(3), + Constraint::Fill(1), + Constraint::Length(5), + ]) + .split(columns[1]); + + let channel_rows = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![Constraint::Length(1); 10]) + .split(rows[1]) + .to_vec(); + + Block::default() + .borders(Borders::RIGHT) + .render(rows[1], buf); + + for (row, channel) in channel_rows.into_iter().zip(self.channels) { + let id = Span::styled( + format!("{}. ", channel.id), + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), + ); + let title = Span::styled( + channel.title.clone(), + Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD), + ); + let line = Line::from(vec![id, title]); + let para = Paragraph::new(line).block(Block::default()); + para.render(row, buf); + } + } +} struct AppState { exit: bool, + channels: Vec, } -pub fn ui(mut terminal: Terminal>) -> Result<()> { - let mut app_state = AppState { exit: false }; +pub fn ui(mut db: Db, mut terminal: Terminal>) -> Result<()> { + let mut app_state = AppState { + channels: Vec::new(), + exit: false, + }; + + let channels = commands::list_channels(&mut db, &ListChannelArgs { limit: None })?; + app_state.channels = channels; + loop { + draw(&app_state, &mut terminal)?; + handle_events(&mut app_state)?; if app_state.exit { break; } - - draw(&app_state, &mut terminal)?; } Ok(()) } fn draw(app_state: &AppState, terminal: &mut Terminal>) -> Result<()> { - todo!() + terminal + .draw(|f| { + let channel_widget = ChannelsWidget { + channels: &app_state.channels, + }; + + f.render_widget(channel_widget, f.area()); + }) + .map_err(|e| TrsError::TuiError(e))?; + + Ok(()) } fn handle_events(state: &mut AppState) -> Result<()> {