diff --git a/Cargo.toml b/Cargo.toml index a55bb28..dd37c77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,9 @@ edition = "2024" [dependencies] chrono = { version = "0.4.41", features = ["serde"] } +color-eyre = "0.6.5" +crossterm = "0.29.0" +ratatui = { version = "0.29.0", features = ["all-widgets"] } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.141" uuid = { version = "1.17.0", features = ["serde", "v4"] } diff --git a/src/lib.rs b/src/lib.rs index 9864d5a..05f03ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,17 @@ -use std::{fs, io::Result, path::PathBuf}; +use std::{fs, path::PathBuf}; +use color_eyre::Result; + +use ratatui::prelude::*; + +use ratatui::{ + DefaultTerminal, Frame, + crossterm::event::{self, Event, KeyCode}, + style::Color, + symbols::border, + text::Line, + widgets::{Block, HighlightSpacing, List, ListItem, ListState, StatefulWidget, Widget}, +}; use serde::{Deserialize, Serialize}; use crate::log::Item; @@ -12,18 +24,59 @@ pub static APP_NAME: &str = "lw"; pub struct App { logs: Vec, config: PathBuf, + #[serde(skip)] + state: ListState, } impl App { pub fn new() -> Self { Self::default() } + fn draw(&mut self, frame: &mut Frame) { + self.render(frame.area(), frame.buffer_mut()); + } + pub fn run(&mut self, mut terminal: DefaultTerminal) -> Result<()> { + loop { + // terminal.draw(render)?; + terminal.draw(|frame| self.draw(frame))?; + if let Ok(event) = event::read() + && let Event::Key(key_event) = event + && key_event.kind == event::KeyEventKind::Press + { + match key_event.code { + KeyCode::Char('q') | KeyCode::Esc => break Ok(()), + _ => {} + } + } + // match event::read() { + // Ok(event) => match event { + // Event::Key(key_event) => match key_event.kind { + // event::KeyEventKind::Press => { + // if key_event.code == KeyCode::Char('q') { + // break Ok(()); + // } + // } + // _ => {} + // }, + // _ => {} + // }, + // _ => {} + // } + } + } pub fn add(&mut self, item: Item) -> Result<()> { self.logs.push(item); self.save() } + pub fn update>(&mut self, id: T, content: T) -> Result<()> { + if let Some(item) = self.logs.iter_mut().find(|i| i.id() == id.as_ref()) { + item.update(content.as_ref().to_owned()); + } + self.save() + } + pub fn remove>(&mut self, id: T) -> Result<()> { self.logs.retain(|i| i.id() != id.as_ref()); self.save() @@ -61,6 +114,81 @@ impl Default for App { Self { logs: vec![], config: dir, + ..Default::default() } } } + +impl Widget for &mut App { + fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) + where + Self: Sized, + { + let title = Line::from(" Log Your Work ".bold()); + + let instructions = Line::from(vec![ + Span::raw(" Add "), + Span::styled( + "", + Style::default() + .fg(Color::DarkGray) + .add_modifier(Modifier::BOLD), + ), + Span::raw(" Select "), + Span::styled( + "", + Style::default() + .fg(Color::DarkGray) + .add_modifier(Modifier::BOLD), + ), + Span::raw(" Down "), + Span::styled( + "", + Style::default() + .fg(Color::DarkGray) + .add_modifier(Modifier::BOLD), + ), + Span::raw(" Up "), + Span::styled( + "", + Style::default() + .fg(Color::DarkGray) + .add_modifier(Modifier::BOLD), + ), + Span::raw(" Quit "), + Span::styled( + " | ", + Style::default() + .fg(Color::DarkGray) + .add_modifier(Modifier::BOLD), + ), + ]); + + let block = Block::bordered() + .title(title.centered()) + .title_bottom(instructions.centered()) + .border_set(border::THICK); + + let items: Vec = self + .logs + .iter() + .enumerate() + .map(|(i, item)| { + ListItem::new(Line::styled(format!("{}", item.content()), Color::White)).bg( + if i % 2 == 0 { + Color::Black + } else { + Color::DarkGray + }, + ) + }) + .collect(); + + let list = List::new(items) + .block(block) + .highlight_symbol(">") + .highlight_spacing(HighlightSpacing::Always); + + StatefulWidget::render(list, area, buf, &mut self.state); + } +} diff --git a/src/log.rs b/src/log.rs index 5d76bf0..0bba5c6 100644 --- a/src/log.rs +++ b/src/log.rs @@ -15,6 +15,10 @@ impl Item { Self::default() } + pub fn content(&self) -> String { + self.content.clone() + } + pub fn update(&mut self, content: String) { self.content = content; self.modified = Local::now(); diff --git a/src/main.rs b/src/main.rs index b856a3d..6651445 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,13 @@ -use std::error::Error; - +use color_eyre::Result; use lw::{App, log::Item}; -fn main() -> Result<(), Box> { +fn main() -> Result<()> { + color_eyre::install()?; + + let terminal = ratatui::init(); let mut app = App::default(); - let item = Item::from("hello_world"); - let id = item.id(); - app.add(item)?; - println!("{app:?}"); - app.remove(id)?; - app.save()?; - println!("{app:?}"); - Ok(()) + + let result = app.run(terminal); + ratatui::restore(); + result }