From 61c385404bd5798a773366886aec423f22d25572 Mon Sep 17 00:00:00 2001 From: kageru Date: Sat, 25 Jan 2020 22:15:41 +0100 Subject: [PATCH] Add WIP timesheet --- Cargo.toml | 1 + src/main.rs | 1 + src/timesheet.rs | 104 ++++++++++++++++++++++++++++++++++++++++++ src/todolist.rs | 2 +- src/tracc.rs | 115 ++++++++++++++++++++++++++--------------------- 5 files changed, 170 insertions(+), 53 deletions(-) create mode 100644 src/timesheet.rs diff --git a/Cargo.toml b/Cargo.toml index 78a91d8..137f9b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ tui = "0.8.0" termion = "1.5" serde_json = "1" serde = { version = "1", features = ["derive"] } +time = { version = "0.2", features = ["serde"] } diff --git a/src/main.rs b/src/main.rs index 2f5199b..81c7257 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use tui::backend::TermionBackend; use tui::Terminal; mod todolist; mod tracc; +mod timesheet; use tracc::Tracc; fn main() -> Result<(), io::Error> { diff --git a/src/timesheet.rs b/src/timesheet.rs new file mode 100644 index 0000000..5a61e04 --- /dev/null +++ b/src/timesheet.rs @@ -0,0 +1,104 @@ +use serde::{Deserialize, Serialize}; +use serde_json::from_reader; +use std::fmt; +use std::fs::File; +use std::io::BufReader; +use time::Time; + +pub struct TimeSheet { + pub times: Vec, + pub selected: usize, + pub register: Option, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct TimePoint { + text: String, + time: Time, +} + +impl TimePoint { + pub fn new(text: &str) -> Self { + Self { + text: String::from(text), + time: Time::now(), + } + } +} + +impl fmt::Display for TimePoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[{}] {}", self.time.format("%H:%M"), self.text) + } +} + +impl TimeSheet { + pub fn new() -> Self { + Self { + times: vec![ + TimePoint::new("A test value"), + TimePoint::new("A second test value"), + ], + selected: 0, + register: None, + } + } + + pub fn printable(&self) -> Vec { + self.times.iter().map(TimePoint::to_string).collect() + } +} +/* +impl TimeSheet { + pub fn selection_down(&mut self) { + self.selected = (self.selected + 1).min(self.todos.len().saturating_sub(1)); + } + + pub fn selection_up(&mut self) { + self.selected = self.selected.saturating_sub(1); + } + + pub fn insert(&mut self, todo: Todo) { + if self.selected == self.todos.len().saturating_sub(1) { + self.todos.push(todo); + self.selected = self.todos.len() - 1; + } else { + self.todos.insert(self.selected + 1, todo); + self.selected += 1; + } + } + + pub fn remove_current(&mut self) -> Option { + if self.todos.is_empty() { + return None; + } + let index = self.selected; + self.selected = index.min(self.todos.len().saturating_sub(2)); + return Some(self.todos.remove(index)); + } + + pub fn toggle_current(&mut self) { + self.todos[self.selected].done = !self.todos[self.selected].done; + } + + fn current(&self) -> &Todo { + &self.todos[self.selected] + } + + pub fn normal_mode(&mut self) { + if self.current().text.is_empty() { + self.remove_current(); + self.selected = self.selected.saturating_sub(1); + } + } + + pub fn append_to_current(&mut self, chr: char) { + self.todos[self.selected].text.push(chr); + } + + pub fn current_pop(&mut self) { + self.todos[self.selected].text.pop(); + } + +} +*/ diff --git a/src/todolist.rs b/src/todolist.rs index c6cf12d..af5d251 100644 --- a/src/todolist.rs +++ b/src/todolist.rs @@ -5,7 +5,6 @@ use std::fmt; use std::io::BufReader; pub struct TodoList { - // We use owned strings here because they’re easier to manipulate when editing. pub todos: Vec, pub selected: usize, pub register: Option, @@ -13,6 +12,7 @@ pub struct TodoList { #[derive(Serialize, Deserialize, Default, Clone)] pub struct Todo { + // We use owned strings here because they’re easier to manipulate when editing. text: String, done: bool, } diff --git a/src/tracc.rs b/src/tracc.rs index b423170..f610412 100644 --- a/src/tracc.rs +++ b/src/tracc.rs @@ -1,4 +1,5 @@ use super::todolist::TodoList; +use super::timesheet::TimeSheet; use std::default::Default; use std::io::{self, Write}; use termion::event::Key; @@ -16,27 +17,35 @@ pub enum Mode { Normal, } +#[derive(PartialEq)] +enum Focus { + Top, + Bottom, +} + pub struct Tracc { todos: TodoList, + times: TimeSheet, terminal: Terminal, input_mode: Mode, - top_panel_selected: bool, + focus: Focus, } impl Tracc { pub fn new(terminal: Terminal) -> Self { Self { todos: TodoList::open_or_create(JSON_PATH), + times: TimeSheet::new(), terminal, input_mode: Mode::Normal, - top_panel_selected: true, + focus: Focus::Top, } } pub fn run(&mut self) -> Result<(), io::Error> { let mut inputs = io::stdin().keys(); loop { - refresh(&mut self.terminal, &self.todos, self.top_panel_selected)?; + self.refresh()?; // I need to find a better way to handle inputs. This is awful. let input = inputs.next().unwrap()?; match self.input_mode { @@ -61,7 +70,12 @@ impl Tracc { self.todos.insert(self.todos.register.clone().unwrap()); } } - Key::Char('\t') => self.top_panel_selected = !self.top_panel_selected, + Key::Char('\t') => { + self.focus = match self.focus { + Focus::Top => Focus::Bottom, + Focus::Bottom => Focus::Top, + } + } _ => (), }, Mode::Insert => match input { @@ -88,56 +102,53 @@ impl Tracc { self.input_mode = mode; Ok(()) } -} -fn refresh(terminal: &mut Terminal, todos: &TodoList, top_selected: bool) -> Result<(), io::Error> { - fn selectable_list<'a, C: AsRef>( - title: &'a str, - content: &'a [C], - selected: Option, - ) -> SelectableList<'a> { - SelectableList::default() - .block( - Block::default() - .title(title) - .borders(Borders::TOP | Borders::RIGHT | Borders::LEFT), - ) - .items(content) - .select(selected.into()) - .highlight_style(Style::default().fg(Color::LightGreen)) - .highlight_symbol(">") + fn refresh(&mut self) -> Result<(), io::Error> { + fn selectable_list<'a, C: AsRef>( + title: &'a str, + content: &'a [C], + selected: Option, + ) -> SelectableList<'a> { + SelectableList::default() + .block( + Block::default() + .title(title) + .borders(Borders::TOP | Borders::RIGHT | Borders::LEFT), + ) + .items(content) + .select(selected.into()) + .highlight_style(Style::default().fg(Color::LightGreen)) + .highlight_symbol(">") + } + + let printable_todos = self.todos.printable(); + let top_focus = Some(self.todos.selected).filter(|_| self.focus == Focus::Top); + let printable_times = self.times.printable(); + let bottom_focus = Some(self.times.selected).filter(|_| self.focus == Focus::Bottom); + + self.terminal.draw(|mut frame| { + let size = frame.size(); + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(42), + Constraint::Percentage(42), + Constraint::Percentage(16), + ] + .as_ref(), + ) + .split(size); + selectable_list(" t r a c c ", &printable_todos, top_focus) + .render(&mut frame, chunks[0]); + selectable_list(" 🕑 ", &printable_times, bottom_focus) + .render(&mut frame, chunks[1]); + Paragraph::new([Text::raw("Sum for today: 1:12")].iter()) + .block(Block::default().borders(Borders::ALL)) + .render(&mut frame, chunks[2]); + })?; + Ok(()) } - - terminal.draw(|mut frame| { - let size = frame.size(); - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage(42), - Constraint::Percentage(42), - Constraint::Percentage(16), - ] - .as_ref(), - ) - .split(size); - selectable_list( - " t r a c c ", - &todos.printable(), - Some(todos.selected).filter(|_| top_selected), - ) - .render(&mut frame, chunks[0]); - selectable_list( - " 🕑 ", - &["[08:23] start", "[09:35] end"], - Some(0).filter(|_| !top_selected), - ) - .render(&mut frame, chunks[1]); - Paragraph::new([Text::raw("Sum for today: 1:12")].iter()) - .block(Block::default().borders(Borders::ALL)) - .render(&mut frame, chunks[2]); - })?; - Ok(()) } fn persist_todos(todos: &TodoList, path: &str) {