Added task shifting up and down and left and right.
Francesco added some stuff as well.
This commit is contained in:
		
							parent
							
								
									c38fa823c9
								
							
						
					
					
						commit
						ae99fa193c
					
				
							
								
								
									
										8
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -2,6 +2,12 @@
 | 
				
			|||||||
# It is not intended for manual editing.
 | 
					# It is not intended for manual editing.
 | 
				
			||||||
version = 3
 | 
					version = 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "anyhow"
 | 
				
			||||||
 | 
					version = "1.0.66"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "autocfg"
 | 
					name = "autocfg"
 | 
				
			||||||
version = "1.1.0"
 | 
					version = "1.1.0"
 | 
				
			||||||
@ -99,11 +105,13 @@ checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
 | 
				
			|||||||
name = "kanban-tui"
 | 
					name = "kanban-tui"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "anyhow",
 | 
				
			||||||
 "crossterm",
 | 
					 "crossterm",
 | 
				
			||||||
 "indexmap",
 | 
					 "indexmap",
 | 
				
			||||||
 "int-enum",
 | 
					 "int-enum",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 "serde_json",
 | 
					 "serde_json",
 | 
				
			||||||
 | 
					 "thiserror",
 | 
				
			||||||
 "tui",
 | 
					 "tui",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -11,4 +11,8 @@ crossterm = "0.25"
 | 
				
			|||||||
serde = { version = "1.0.148" , features = [ "derive" ] }
 | 
					serde = { version = "1.0.148" , features = [ "derive" ] }
 | 
				
			||||||
serde_json = "1.0.89"
 | 
					serde_json = "1.0.89"
 | 
				
			||||||
indexmap = { version = "1.9.2" , features = [ "serde" ] }
 | 
					indexmap = { version = "1.9.2" , features = [ "serde" ] }
 | 
				
			||||||
int-enum = "0.5.0"
 | 
					int-enum = "0.5.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#error handling
 | 
				
			||||||
 | 
					thiserror = "1"
 | 
				
			||||||
 | 
					anyhow = "1"
 | 
				
			||||||
							
								
								
									
										198
									
								
								src/app.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/app.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,198 @@
 | 
				
			|||||||
 | 
					use std::cmp::min;
 | 
				
			||||||
 | 
					use indexmap::IndexMap;
 | 
				
			||||||
 | 
					use int_enum::IntEnum;
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod tests;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[repr(usize)]
 | 
				
			||||||
 | 
					#[derive(Deserialize, Serialize, Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, IntEnum)]
 | 
				
			||||||
 | 
					pub enum TaskStatus {
 | 
				
			||||||
 | 
					    Todo = 0,
 | 
				
			||||||
 | 
					    InProgress = 1,
 | 
				
			||||||
 | 
					    Done = 2,
 | 
				
			||||||
 | 
					    Ideas = 3,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// #[derive(Deserialize, Serialize, Debug, Clone, Copy)]
 | 
				
			||||||
 | 
					#[derive(Deserialize, Serialize, Debug)]
 | 
				
			||||||
 | 
					pub struct Task {
 | 
				
			||||||
 | 
					    pub title: String,
 | 
				
			||||||
 | 
					    pub description: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for Task {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Task {
 | 
				
			||||||
 | 
					            title: String::new(),
 | 
				
			||||||
 | 
					            description: String::new(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Type used mainly for serialization at this time
 | 
				
			||||||
 | 
					#[derive(Deserialize, Serialize, Debug)]
 | 
				
			||||||
 | 
					pub struct Project {
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub tasks_per_column: IndexMap<TaskStatus, Vec<Task>>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, thiserror::Error)]
 | 
				
			||||||
 | 
					pub enum KanbanError {
 | 
				
			||||||
 | 
					    #[error("There is something wrong with the json schema, it doesn't match Project struct")]
 | 
				
			||||||
 | 
					    BadJson,
 | 
				
			||||||
 | 
					    #[error("Some form of IO error occured: {0}")]
 | 
				
			||||||
 | 
					    Io(#[from] std::io::Error),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Project {
 | 
				
			||||||
 | 
					    pub fn new(name: &str) -> Self {
 | 
				
			||||||
 | 
					        Project {
 | 
				
			||||||
 | 
					            name: name.to_owned(),
 | 
				
			||||||
 | 
					            tasks_per_column: IndexMap::from(
 | 
				
			||||||
 | 
					                [(TaskStatus::Done, vec![]),
 | 
				
			||||||
 | 
					                    (TaskStatus::Todo, vec![]),
 | 
				
			||||||
 | 
					                    (TaskStatus::InProgress, vec![]),
 | 
				
			||||||
 | 
					                    (TaskStatus::Ideas, vec![])],
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn load_from_json(json: &str) -> Result<Self, KanbanError> {
 | 
				
			||||||
 | 
					        serde_json::from_str(json).map_err(|_| KanbanError::BadJson)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn load() -> Result<Self, KanbanError> {
 | 
				
			||||||
 | 
					        let json = std::fs::read_to_string("kanban-tui.json")?;
 | 
				
			||||||
 | 
					        Self::load_from_json(&json)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn add_task(&mut self, status: TaskStatus, task: Task) {
 | 
				
			||||||
 | 
					        self.tasks_per_column.entry(status).or_default().push(task);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Comment out cause this is dangerous
 | 
				
			||||||
 | 
					    pub fn save() {
 | 
				
			||||||
 | 
					        // let mut project = Project::new("Kanban Tui");
 | 
				
			||||||
 | 
					        // project.add_task(Task::default());
 | 
				
			||||||
 | 
					        // project.add_task(Task::default());
 | 
				
			||||||
 | 
					        // let json = serde_json::to_string_pretty(&project).unwrap();
 | 
				
			||||||
 | 
					        // std::fs::write("./project.json", json).unwrap();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for Project {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Project {
 | 
				
			||||||
 | 
					            name: String::new(),
 | 
				
			||||||
 | 
					            tasks_per_column: IndexMap::new(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct AppState {
 | 
				
			||||||
 | 
					    pub selected_column: usize,
 | 
				
			||||||
 | 
					    pub selected_task: [usize; 4],
 | 
				
			||||||
 | 
					    pub project: Project,
 | 
				
			||||||
 | 
					    pub quit: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl AppState {
 | 
				
			||||||
 | 
					    pub fn new(project: Project) -> Self {
 | 
				
			||||||
 | 
					        AppState {
 | 
				
			||||||
 | 
					            selected_column: 0,
 | 
				
			||||||
 | 
					            selected_task: [0, 0, 0, 0],
 | 
				
			||||||
 | 
					            quit: false,
 | 
				
			||||||
 | 
					            project,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn selected_task_idx(&self) -> usize {
 | 
				
			||||||
 | 
					        self.selected_task[self.selected_column]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn selected_task_idx_mut(&mut self) -> &mut usize {
 | 
				
			||||||
 | 
					        &mut self.selected_task[self.selected_column]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_tasks_in_active_column(&self) -> &[Task] {
 | 
				
			||||||
 | 
					        let column: TaskStatus = TaskStatus::from_int(self.selected_column).unwrap().clone();
 | 
				
			||||||
 | 
					        self.project.tasks_per_column.get(&column).unwrap()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_tasks_in_active_column_mut(&mut self) -> &mut Vec<Task> {
 | 
				
			||||||
 | 
					        let column: TaskStatus = TaskStatus::from_int(self.selected_column).unwrap().clone();
 | 
				
			||||||
 | 
					        self.project.tasks_per_column.get_mut(&column).unwrap()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_selected_task(&self) -> Option<&Task> {
 | 
				
			||||||
 | 
					        let tasks = self.get_tasks_in_active_column();
 | 
				
			||||||
 | 
					        tasks.get(self.selected_task_idx())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn select_previous_task(&mut self) {
 | 
				
			||||||
 | 
					        *self.selected_task_idx_mut() = self.selected_task_idx().saturating_sub(1)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn select_next_task(&mut self) {
 | 
				
			||||||
 | 
					        let tasks = self.get_tasks_in_active_column();
 | 
				
			||||||
 | 
					        if tasks.len() > 0 {
 | 
				
			||||||
 | 
					            let mins = min(self.selected_task_idx() + 1, tasks.len() - 1);
 | 
				
			||||||
 | 
					            *self.selected_task_idx_mut() = mins;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn select_previous_column(&mut self) {
 | 
				
			||||||
 | 
					        self.selected_column = self.selected_column.saturating_sub(1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn select_next_column(&mut self) {
 | 
				
			||||||
 | 
					        self.selected_column = min(self.selected_column + 1, self.project.tasks_per_column.len() - 1)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn move_task_previous_column(&mut self) {
 | 
				
			||||||
 | 
					        let tasks = self.get_tasks_in_active_column();
 | 
				
			||||||
 | 
					        let task_idx = self.selected_task_idx();
 | 
				
			||||||
 | 
					        if self.selected_column > 0 && tasks.len() > 0 && task_idx.clone() < tasks.len() {
 | 
				
			||||||
 | 
					            let task = self.get_tasks_in_active_column_mut().remove(task_idx);
 | 
				
			||||||
 | 
					            *self.selected_task_idx_mut() = self.selected_task_idx().saturating_sub(1);
 | 
				
			||||||
 | 
					            self.select_previous_column();
 | 
				
			||||||
 | 
					            let target_tasks = self.get_tasks_in_active_column_mut();
 | 
				
			||||||
 | 
					            target_tasks.push(task);
 | 
				
			||||||
 | 
					            *self.selected_task_idx_mut() = target_tasks.len() - 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn move_task_next_column(&mut self) {
 | 
				
			||||||
 | 
					        let tasks = self.get_tasks_in_active_column();
 | 
				
			||||||
 | 
					        let task_idx = self.selected_task_idx();
 | 
				
			||||||
 | 
					        if self.selected_column < self.project.tasks_per_column.len() && tasks.len() > 0 && task_idx < tasks.len() {
 | 
				
			||||||
 | 
					            let task = self.get_tasks_in_active_column_mut().remove(task_idx);
 | 
				
			||||||
 | 
					            *self.selected_task_idx_mut() = self.selected_task_idx().saturating_sub(1);
 | 
				
			||||||
 | 
					            self.select_next_column();
 | 
				
			||||||
 | 
					            let target_tasks = self.get_tasks_in_active_column_mut();
 | 
				
			||||||
 | 
					            target_tasks.push(task);
 | 
				
			||||||
 | 
					            *self.selected_task_idx_mut() = target_tasks.len() - 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn move_task_up(&mut self) {
 | 
				
			||||||
 | 
					        let task_idx = self.selected_task_idx();
 | 
				
			||||||
 | 
					        if task_idx > 0 {
 | 
				
			||||||
 | 
					            let tasks = self.get_tasks_in_active_column_mut();
 | 
				
			||||||
 | 
					            tasks.swap(task_idx, task_idx - 1);
 | 
				
			||||||
 | 
					            *self.selected_task_idx_mut() = task_idx - 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn move_task_down(&mut self) {
 | 
				
			||||||
 | 
					        let task_idx = self.selected_task_idx();
 | 
				
			||||||
 | 
					        let tasks = self.get_tasks_in_active_column_mut();
 | 
				
			||||||
 | 
					        if task_idx < tasks.len() - 1 {
 | 
				
			||||||
 | 
					            tasks.swap(task_idx, task_idx + 1);
 | 
				
			||||||
 | 
					            *self.selected_task_idx_mut() = task_idx + 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/app/tests.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/app/tests.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					use super::*;
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/input.rs
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/input.rs
									
									
									
									
									
								
							@ -1,6 +1,6 @@
 | 
				
			|||||||
use crossterm::event;
 | 
					use crossterm::event;
 | 
				
			||||||
use crossterm::event::{Event, KeyCode};
 | 
					use crossterm::event::{Event, KeyCode};
 | 
				
			||||||
use crate::types::{AppState};
 | 
					use crate::app::{AppState};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> {
 | 
					pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> {
 | 
				
			||||||
    if let Event::Key(key) = event::read()? {
 | 
					    if let Event::Key(key) = event::read()? {
 | 
				
			||||||
@ -14,6 +14,14 @@ pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> {
 | 
				
			|||||||
            KeyCode::Up        => state.select_previous_task(),
 | 
					            KeyCode::Up        => state.select_previous_task(),
 | 
				
			||||||
            KeyCode::Char('l') |
 | 
					            KeyCode::Char('l') |
 | 
				
			||||||
            KeyCode::Right     => state.select_next_column(),
 | 
					            KeyCode::Right     => state.select_next_column(),
 | 
				
			||||||
 | 
					            KeyCode::Char('<') |
 | 
				
			||||||
 | 
					            KeyCode::Char('H') => state.move_task_previous_column(),
 | 
				
			||||||
 | 
					            KeyCode::Char('>') |
 | 
				
			||||||
 | 
					            KeyCode::Char('L') => state.move_task_next_column(),
 | 
				
			||||||
 | 
					            KeyCode::Char('=') |
 | 
				
			||||||
 | 
					            KeyCode::Char('J') => state.move_task_down(),
 | 
				
			||||||
 | 
					            KeyCode::Char('-') |
 | 
				
			||||||
 | 
					            KeyCode::Char('K') => state.move_task_up(),
 | 
				
			||||||
            _ => {}
 | 
					            _ => {}
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					mod app;
 | 
				
			||||||
 | 
					mod ui;
 | 
				
			||||||
 | 
					mod input;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use app::*;
 | 
				
			||||||
 | 
					pub use ui::draw;
 | 
				
			||||||
 | 
					pub use input::handle_input;
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/main.rs
									
									
									
									
									
								
							@ -1,29 +1,23 @@
 | 
				
			|||||||
#![allow(dead_code)]
 | 
					#![allow(dead_code)]
 | 
				
			||||||
mod ui;
 | 
					use kanban_tui::{AppState, Project};
 | 
				
			||||||
mod types;
 | 
					 | 
				
			||||||
mod input;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::{io};
 | 
					use std::{io};
 | 
				
			||||||
use crossterm::{event::*, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}};
 | 
					use crossterm::{event::*, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}};
 | 
				
			||||||
use tui::backend::CrosstermBackend;
 | 
					use tui::backend::CrosstermBackend;
 | 
				
			||||||
use tui::Terminal;
 | 
					use tui::Terminal;
 | 
				
			||||||
use crate::input::handle_input;
 | 
					 | 
				
			||||||
use crate::types::*;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() -> Result<(), io::Error> {
 | 
					fn main() -> anyhow::Result<()> {
 | 
				
			||||||
    // setup terminal
 | 
					 | 
				
			||||||
    enable_raw_mode()?;
 | 
					    enable_raw_mode()?;
 | 
				
			||||||
    let mut stdout = io::stdout();
 | 
					    let mut stdout = io::stdout();
 | 
				
			||||||
    crossterm::execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
 | 
					    crossterm::execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
 | 
				
			||||||
    let backend = CrosstermBackend::new(stdout);
 | 
					    let backend = CrosstermBackend::new(stdout);
 | 
				
			||||||
    let mut terminal = Terminal::new(backend)?;
 | 
					    let mut terminal = Terminal::new(backend)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut state = AppState::new(Project::load());
 | 
					    let mut state = AppState::new(Project::load()?);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    loop {
 | 
					    while !state.quit {
 | 
				
			||||||
        terminal.draw(|f| ui::draw(f, &mut state))?;
 | 
					        terminal.draw(|f| kanban_tui::draw(f, &mut state))?;
 | 
				
			||||||
        handle_input(&mut state)?;
 | 
					        kanban_tui::handle_input(&mut state)?;
 | 
				
			||||||
        if state.quit { break }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // restore terminal
 | 
					    // restore terminal
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										123
									
								
								src/types.rs
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								src/types.rs
									
									
									
									
									
								
							@ -1,123 +0,0 @@
 | 
				
			|||||||
use std::cmp::min;
 | 
					 | 
				
			||||||
use indexmap::IndexMap;
 | 
					 | 
				
			||||||
use int_enum::IntEnum;
 | 
					 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[repr(usize)]
 | 
					 | 
				
			||||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, IntEnum)]
 | 
					 | 
				
			||||||
pub enum TaskStatus {
 | 
					 | 
				
			||||||
    Todo = 0,
 | 
					 | 
				
			||||||
    InProgress = 1,
 | 
					 | 
				
			||||||
    Done = 2,
 | 
					 | 
				
			||||||
    Ideas = 3,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// #[derive(Deserialize, Serialize, Debug, Clone, Copy)]
 | 
					 | 
				
			||||||
#[derive(Deserialize, Serialize, Debug)]
 | 
					 | 
				
			||||||
pub struct Task {
 | 
					 | 
				
			||||||
    pub title: String,
 | 
					 | 
				
			||||||
    pub description: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Default for Task {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Task {
 | 
					 | 
				
			||||||
            title: String::new(),
 | 
					 | 
				
			||||||
            description: String::new(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Type used mainly for serialization at this time
 | 
					 | 
				
			||||||
#[derive(Deserialize, Serialize, Debug)]
 | 
					 | 
				
			||||||
pub struct Project {
 | 
					 | 
				
			||||||
    pub name: String,
 | 
					 | 
				
			||||||
    pub tasks_per_column: IndexMap<TaskStatus, Vec<Task>>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Project {
 | 
					 | 
				
			||||||
    fn new(name: &str) -> Self {
 | 
					 | 
				
			||||||
        Project {
 | 
					 | 
				
			||||||
            name: name.to_owned(),
 | 
					 | 
				
			||||||
            tasks_per_column: IndexMap::from(
 | 
					 | 
				
			||||||
                [(TaskStatus::Done, vec![]),
 | 
					 | 
				
			||||||
                    (TaskStatus::Todo, vec![]),
 | 
					 | 
				
			||||||
                    (TaskStatus::InProgress, vec![]),
 | 
					 | 
				
			||||||
                    (TaskStatus::Ideas, vec![])],
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn load() -> Self {
 | 
					 | 
				
			||||||
        let json = std::fs::read_to_string("kanban-tui.json")
 | 
					 | 
				
			||||||
            .expect("Could not read json file");
 | 
					 | 
				
			||||||
        serde_json::from_str(&json)
 | 
					 | 
				
			||||||
            .expect("There is something wrong with the json schema, it doesn't match Project struct")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn add_task(&mut self, status: TaskStatus, task: Task) {
 | 
					 | 
				
			||||||
        self.tasks_per_column.entry(status).or_default().push(task);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Comment out cause this is dangerous
 | 
					 | 
				
			||||||
    pub fn save() {
 | 
					 | 
				
			||||||
        // let mut project = Project::new("Kanban Tui");
 | 
					 | 
				
			||||||
        // project.add_task(Task::default());
 | 
					 | 
				
			||||||
        // project.add_task(Task::default());
 | 
					 | 
				
			||||||
        // let json = serde_json::to_string_pretty(&project).unwrap();
 | 
					 | 
				
			||||||
        // std::fs::write("./project.json", json).unwrap();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Default for Project {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Project {
 | 
					 | 
				
			||||||
            name: String::new(),
 | 
					 | 
				
			||||||
            tasks_per_column: IndexMap::new(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct AppState {
 | 
					 | 
				
			||||||
    pub selected_column: usize,
 | 
					 | 
				
			||||||
    pub selected_task: [usize; 4],
 | 
					 | 
				
			||||||
    pub project: Project,
 | 
					 | 
				
			||||||
    pub quit: bool,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl AppState {
 | 
					 | 
				
			||||||
    pub fn new(project: Project) -> Self {
 | 
					 | 
				
			||||||
        AppState {
 | 
					 | 
				
			||||||
            selected_column: 0,
 | 
					 | 
				
			||||||
            selected_task: [0, 0, 0, 0],
 | 
					 | 
				
			||||||
            quit: false,
 | 
					 | 
				
			||||||
            project: project,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn get_tasks_in_active_column(&self) -> &Vec<Task> {
 | 
					 | 
				
			||||||
        let column: TaskStatus = TaskStatus::from_int(self.selected_column).unwrap().clone();
 | 
					 | 
				
			||||||
        self.project.tasks_per_column.get(&column).unwrap()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn select_previous_task(&mut self) {
 | 
					 | 
				
			||||||
        self.selected_task[self.selected_column] = self.selected_task[self.selected_column].saturating_sub(1)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn select_next_task(&mut self) {
 | 
					 | 
				
			||||||
        let tasks = self.get_tasks_in_active_column();
 | 
					 | 
				
			||||||
        if tasks.len() > 0 {
 | 
					 | 
				
			||||||
            let mins = min(self.selected_task[self.selected_column] + 1, tasks.len() - 1);
 | 
					 | 
				
			||||||
            self.selected_task[self.selected_column] = mins;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn select_previous_column(&mut self) {
 | 
					 | 
				
			||||||
        self.selected_column = self.selected_column.saturating_sub(1);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn select_next_column(&mut self) {
 | 
					 | 
				
			||||||
        self.selected_column = min(self.selected_column + 1, self.project.tasks_per_column.len() - 1)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@ -4,8 +4,7 @@ use tui::{Frame};
 | 
				
			|||||||
use tui::style::{Color, Modifier, Style};
 | 
					use tui::style::{Color, Modifier, Style};
 | 
				
			||||||
use tui::text::{Span, Spans};
 | 
					use tui::text::{Span, Spans};
 | 
				
			||||||
use tui::widgets::*;
 | 
					use tui::widgets::*;
 | 
				
			||||||
use crate::types::*;
 | 
					use crate::app::*;
 | 
				
			||||||
use int_enum::IntEnum;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn draw_tasks<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
 | 
					fn draw_tasks<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
 | 
				
			||||||
    let columns = Layout::default()
 | 
					    let columns = Layout::default()
 | 
				
			||||||
@ -47,10 +46,7 @@ fn draw_task_info<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
 | 
				
			|||||||
    let block = Block::default()
 | 
					    let block = Block::default()
 | 
				
			||||||
        .title("TASK INFO")
 | 
					        .title("TASK INFO")
 | 
				
			||||||
        .borders(Borders::ALL);
 | 
					        .borders(Borders::ALL);
 | 
				
			||||||
    let column: TaskStatus = TaskStatus::from_int(state.selected_column).unwrap();
 | 
					    if let Some(task) = state.get_selected_task() {
 | 
				
			||||||
    let tasks = state.project.tasks_per_column.get(&column).unwrap();
 | 
					 | 
				
			||||||
    if tasks.len() > 0 {
 | 
					 | 
				
			||||||
        let task: &Task = &tasks[state.selected_task[state.selected_column]];
 | 
					 | 
				
			||||||
        let p = Paragraph::new(task.description.as_str()).block(block).wrap(Wrap { trim: true });
 | 
					        let p = Paragraph::new(task.description.as_str()).block(block).wrap(Wrap { trim: true });
 | 
				
			||||||
        f.render_widget(p, *area);
 | 
					        f.render_widget(p, *area);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user