Skip to content

Commit

Permalink
Merge pull request #1 from mark-pitblado/develop
Browse files Browse the repository at this point in the history
develop to main
  • Loading branch information
mark-pitblado authored Oct 27, 2024
2 parents c4739f7 + 42f1719 commit 04b8018
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 18 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/api.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::models::{Label, Task, TaskDetail};
use crate::models::{Task, TaskDetail};
use reqwest::Client;
use std::collections::HashMap;

Expand Down
33 changes: 27 additions & 6 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<dyn std::error::Error>> {
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;
}
Expand Down Expand Up @@ -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') => {
Expand Down
14 changes: 10 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ mod app;
mod models;
mod ui;

use crate::api::fetch_tasks;

use app::App;
use crossterm::{
execute,
Expand Down Expand Up @@ -47,10 +49,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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 show_done_tasks = false;

let incomplete_tasks: Vec<models::Task> =
all_tasks.into_iter().filter(|task| !task.done).collect();
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();
Expand All @@ -60,7 +66,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

terminal.hide_cursor()?;

let app = App::new(incomplete_tasks);
let app = App::new(tasks);

let res = run_app(&mut terminal, app, &instance_url, &api_key).await;

Expand Down
2 changes: 1 addition & 1 deletion src/models.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
29 changes: 23 additions & 6 deletions src/ui.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -70,6 +69,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)),
Expand Down Expand Up @@ -119,25 +120,41 @@ pub async fn run_app<B: Backend>(
)
.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<ListItem> = app
.tasks
.iter()
.map(|task| ListItem::new(Line::from(task.title.clone())))
.map(|task| {
let content = if task.done {
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)
.block(Block::default().borders(Borders::ALL).title("Tasks"))
.block(Block::default().borders(Borders::ALL).title(task_title))
.highlight_style(
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
)
.highlight_symbol(">> ")
} else {
List::new(vec![ListItem::new("No incomplete tasks available")])
.block(Block::default().borders(Borders::ALL).title("Tasks"))
List::new(vec![ListItem::new("No tasks available")])
.block(Block::default().borders(Borders::ALL).title(task_title))
};

f.render_stateful_widget(tasks_widget, chunks[0], &mut app.state);
Expand Down

0 comments on commit 04b8018

Please sign in to comment.