feat: add initial tui
This commit is contained in:
parent
cc393ce2c0
commit
035fb15a9d
@ -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"] }
|
||||
|
130
src/lib.rs
130
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<Item>,
|
||||
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<T: AsRef<str>>(&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<T: AsRef<str>>(&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(
|
||||
"<O>",
|
||||
Style::default()
|
||||
.fg(Color::DarkGray)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" Select "),
|
||||
Span::styled(
|
||||
"<Space>",
|
||||
Style::default()
|
||||
.fg(Color::DarkGray)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" Down "),
|
||||
Span::styled(
|
||||
"<J>",
|
||||
Style::default()
|
||||
.fg(Color::DarkGray)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" Up "),
|
||||
Span::styled(
|
||||
"<K>",
|
||||
Style::default()
|
||||
.fg(Color::DarkGray)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" Quit "),
|
||||
Span::styled(
|
||||
"<Q> | <ESC>",
|
||||
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<ListItem> = 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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
20
src/main.rs
20
src/main.rs
@ -1,15 +1,13 @@
|
||||
use std::error::Error;
|
||||
|
||||
use color_eyre::Result;
|
||||
use lw::{App, log::Item};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user