From 8c5628187fc96ea805521d787dfb896558d3b480 Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Sat, 26 Oct 2024 05:58:08 -0700 Subject: [PATCH 1/5] feat: show completed tasks with DONE --- src/main.rs | 6 ++---- src/models.rs | 2 +- src/ui.rs | 17 +++++++++++++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index f3e8eba..797fae4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,9 +48,7 @@ async fn main() -> Result<(), Box> { let api_key = config.vikunja.api_key; let all_tasks = api::fetch_tasks(&instance_url, &api_key, 1).await?; - - let incomplete_tasks: Vec = - all_tasks.into_iter().filter(|task| !task.done).collect(); + let tasks_for_app = all_tasks.clone(); enable_raw_mode()?; let mut stdout = io::stdout(); @@ -60,7 +58,7 @@ async fn main() -> Result<(), Box> { terminal.hide_cursor()?; - let app = App::new(incomplete_tasks); + let app = App::new(tasks_for_app); let res = run_app(&mut terminal, app, &instance_url, &api_key).await; diff --git a/src/models.rs b/src/models.rs index 8b85d12..c9f242d 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,7 +1,7 @@ use serde::Deserialize; // Task struct -#[derive(Deserialize, Debug)] +#[derive(Clone, Deserialize, Debug)] pub struct Task { pub id: u64, pub title: String, diff --git a/src/ui.rs b/src/ui.rs index 046d516..479ccbf 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -124,7 +124,20 @@ pub async fn run_app( let tasks: Vec = app .tasks .iter() - .map(|task| ListItem::new(Line::from(task.title.clone()))) + .map(|task| { + // Check if the task is done + let content = if task.done { + // Prepend "DONE" for completed tasks + vec![ + Span::styled("DONE ", Style::default().fg(Color::Green)), + Span::raw(&task.title), + ] + } else { + vec![Span::raw(&task.title)] + }; + + ListItem::new(Line::from(content)) + }) .collect(); List::new(tasks) @@ -136,7 +149,7 @@ pub async fn run_app( ) .highlight_symbol(">> ") } else { - List::new(vec![ListItem::new("No incomplete tasks available")]) + List::new(vec![ListItem::new("No tasks available")]) .block(Block::default().borders(Borders::ALL).title("Tasks")) }; From 2dacd1b0bc522ae1472bf2f8af4e3f04738fae43 Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Sat, 26 Oct 2024 06:26:56 -0700 Subject: [PATCH 2/5] feat: add ability to toggle done tasks --- src/app.rs | 33 +++++++++++++++++++++++++++------ src/ui.rs | 15 ++++++++++----- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/app.rs b/src/app.rs index c470bdb..efa4bc3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -11,6 +11,7 @@ pub struct App { pub input_mode: InputMode, pub new_task_title: String, pub page: usize, + pub show_done_tasks: bool, } pub enum InputMode { @@ -33,9 +34,25 @@ impl App { input_mode: InputMode::Normal, new_task_title: String::new(), page: 1, + show_done_tasks: false, } } + pub async fn refresh_tasks( + &mut self, + instance_url: &str, + api_key: &str, + ) -> Result<(), Box> { + let new_tasks = fetch_tasks(instance_url, api_key, self.page).await?; + if self.show_done_tasks { + self.tasks = new_tasks; + } else { + self.tasks = new_tasks.into_iter().filter(|task| !task.done).collect(); + } + self.state.select(Some(0)); + Ok(()) + } + pub fn next_page(&mut self) { self.page += 1; } @@ -101,17 +118,21 @@ impl App { KeyCode::Char('n') => { // Next page self.next_page(); - if let Ok(new_tasks) = fetch_tasks(instance_url, api_key, self.page).await { - self.tasks = new_tasks.into_iter().filter(|task| !task.done).collect(); - self.state.select(Some(0)); + if let Err(err) = self.refresh_tasks(instance_url, api_key).await { + eprintln!("Error fetching tasks: {}", err); } } KeyCode::Char('p') => { // Previous page self.previous_page(); - if let Ok(new_tasks) = fetch_tasks(instance_url, api_key, self.page).await { - self.tasks = new_tasks.into_iter().filter(|task| !task.done).collect(); - self.state.select(Some(0)); + if let Err(err) = self.refresh_tasks(instance_url, api_key).await { + eprintln!("Error fetching tasks: {}", err); + } + } + KeyCode::Char('t') => { + self.show_done_tasks = !self.show_done_tasks; + if let Err(err) = self.refresh_tasks(instance_url, api_key).await { + eprintln!("Error fetching tasks: {}", err); } } KeyCode::Char('a') => { diff --git a/src/ui.rs b/src/ui.rs index 479ccbf..496f025 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -70,6 +70,8 @@ fn get_legend(input_mode: &InputMode) -> Text<'static> { Span::raw(": Next Page "), Span::styled(" p ", Style::default().fg(Color::Red)), Span::raw(": Previous Page "), + Span::styled(" t ", Style::default().fg(Color::Red)), + Span::raw(": Toggle Done "), Span::styled(" Enter ", Style::default().fg(Color::Red)), Span::raw(": View Details "), Span::styled(" a ", Style::default().fg(Color::Red)), @@ -119,15 +121,19 @@ pub async fn run_app( ) .split(body_chunk); + let task_title = if app.show_done_tasks { + "Tasks (All)" + } else { + "Tasks (Undone)" + }; + // Left panel: Task list let tasks_widget = if !app.tasks.is_empty() { let tasks: Vec = app .tasks .iter() .map(|task| { - // Check if the task is done let content = if task.done { - // Prepend "DONE" for completed tasks vec![ Span::styled("DONE ", Style::default().fg(Color::Green)), Span::raw(&task.title), @@ -135,13 +141,12 @@ pub async fn run_app( } else { vec![Span::raw(&task.title)] }; - ListItem::new(Line::from(content)) }) .collect(); List::new(tasks) - .block(Block::default().borders(Borders::ALL).title("Tasks")) + .block(Block::default().borders(Borders::ALL).title(task_title)) .highlight_style( Style::default() .fg(Color::Green) @@ -150,7 +155,7 @@ pub async fn run_app( .highlight_symbol(">> ") } else { List::new(vec![ListItem::new("No tasks available")]) - .block(Block::default().borders(Borders::ALL).title("Tasks")) + .block(Block::default().borders(Borders::ALL).title(task_title)) }; f.render_stateful_widget(tasks_widget, chunks[0], &mut app.state); From 4bfbcb77bda368f4e6c1c8f96fac4c422a36a62f Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Sat, 26 Oct 2024 06:29:02 -0700 Subject: [PATCH 3/5] docs: add conventional commits badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2b1da34..e16b8af 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # vikunja-tui +[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org) + This is a simple terminal user interface for [vikunja](https://vikunja.io). The purpose is to allow users to manage tasks from the terminal, using their own API key. This project is not managed or affiliated with the Vikunja team. ## Setup From 1354f4def1407f3d81f7c55fddd511b93c20dc6a Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Sat, 26 Oct 2024 06:48:29 -0700 Subject: [PATCH 4/5] fix: show undone tasks before first toggle --- src/main.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 797fae4..224ef47 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ mod app; mod models; mod ui; +use crate::api::fetch_tasks; + use app::App; use crossterm::{ execute, @@ -47,8 +49,14 @@ async fn main() -> Result<(), Box> { let instance_url = config.vikunja.instance_url; let api_key = config.vikunja.api_key; - let all_tasks = api::fetch_tasks(&instance_url, &api_key, 1).await?; - let tasks_for_app = all_tasks.clone(); + let show_done_tasks = false; + + let tasks = fetch_tasks(&instance_url, &api_key, 1).await?; + let tasks = if show_done_tasks { + tasks + } else { + tasks.into_iter().filter(|task| !task.done).collect() + }; enable_raw_mode()?; let mut stdout = io::stdout(); @@ -58,7 +66,7 @@ async fn main() -> Result<(), Box> { terminal.hide_cursor()?; - let app = App::new(tasks_for_app); + let mut app = App::new(tasks); let res = run_app(&mut terminal, app, &instance_url, &api_key).await; From 42f17199e6596990c91e821044f8aad3aa452d32 Mon Sep 17 00:00:00 2001 From: Mark Pitblado Date: Sat, 26 Oct 2024 06:49:29 -0700 Subject: [PATCH 5/5] fix: automatic cargo recommended fixes --- src/api.rs | 2 +- src/main.rs | 2 +- src/ui.rs | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/api.rs b/src/api.rs index 42b9795..1eab872 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,4 +1,4 @@ -use crate::models::{Label, Task, TaskDetail}; +use crate::models::{Task, TaskDetail}; use reqwest::Client; use std::collections::HashMap; diff --git a/src/main.rs b/src/main.rs index 224ef47..9f79278 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,7 +66,7 @@ async fn main() -> Result<(), Box> { terminal.hide_cursor()?; - let mut app = App::new(tasks); + let app = App::new(tasks); let res = run_app(&mut terminal, app, &instance_url, &api_key).await; diff --git a/src/ui.rs b/src/ui.rs index 496f025..d6137a5 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,7 +1,6 @@ use crate::app::{App, InputMode}; -use crate::models::{Label, Task}; use ansi_parser::{AnsiParser, Output}; -use crossterm::event::{self, Event as CEvent, KeyCode}; +use crossterm::event::{self, Event as CEvent}; use ratatui::{ backend::Backend, layout::{Alignment, Constraint, Direction, Layout, Rect},