Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

develop to main #1

Merged
merged 5 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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