feat: add initial tui
This commit is contained in:
parent
cc393ce2c0
commit
035fb15a9d
@ -5,6 +5,9 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4.41", features = ["serde"] }
|
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 = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.141"
|
serde_json = "1.0.141"
|
||||||
uuid = { version = "1.17.0", features = ["serde", "v4"] }
|
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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::log::Item;
|
use crate::log::Item;
|
||||||
@ -12,18 +24,59 @@ pub static APP_NAME: &str = "lw";
|
|||||||
pub struct App {
|
pub struct App {
|
||||||
logs: Vec<Item>,
|
logs: Vec<Item>,
|
||||||
config: PathBuf,
|
config: PathBuf,
|
||||||
|
#[serde(skip)]
|
||||||
|
state: ListState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
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<()> {
|
pub fn add(&mut self, item: Item) -> Result<()> {
|
||||||
self.logs.push(item);
|
self.logs.push(item);
|
||||||
self.save()
|
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<()> {
|
pub fn remove<T: AsRef<str>>(&mut self, id: T) -> Result<()> {
|
||||||
self.logs.retain(|i| i.id() != id.as_ref());
|
self.logs.retain(|i| i.id() != id.as_ref());
|
||||||
self.save()
|
self.save()
|
||||||
@ -61,6 +114,81 @@ impl Default for App {
|
|||||||
Self {
|
Self {
|
||||||
logs: vec![],
|
logs: vec![],
|
||||||
config: dir,
|
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()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn content(&self) -> String {
|
||||||
|
self.content.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, content: String) {
|
pub fn update(&mut self, content: String) {
|
||||||
self.content = content;
|
self.content = content;
|
||||||
self.modified = Local::now();
|
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};
|
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 mut app = App::default();
|
||||||
let item = Item::from("hello_world");
|
|
||||||
let id = item.id();
|
let result = app.run(terminal);
|
||||||
app.add(item)?;
|
ratatui::restore();
|
||||||
println!("{app:?}");
|
result
|
||||||
app.remove(id)?;
|
|
||||||
app.save()?;
|
|
||||||
println!("{app:?}");
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user