WIP: Refactor data types

This commit is contained in:
Joseph Ferano 2022-12-09 04:25:50 +04:00
parent 9b4341ccf1
commit c999764a28
3 changed files with 87 additions and 107 deletions

View File

@ -1,20 +1,26 @@
use indexmap::IndexMap;
use int_enum::IntEnum;
// use indexmap::IndexMap;
// use int_enum::IntEnum;
use serde::{Deserialize, Serialize};
use std::cmp::min;
#[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(Debug, Serialize, Deserialize)]
pub struct Column {
pub name: String,
pub selected_task_idx: usize,
pub tasks: Vec<Task>,
}
impl Column {
pub fn get_selected_task(&self) -> Option<&Task> {
self.tasks.get(self.selected_task_idx)
}
pub fn get_selected_task_mut(&mut self) -> Option<&mut Task> {
self.tasks.get_mut(self.selected_task_idx)
}
}
// #[derive(Deserialize, Serialize, Debug, Clone, Copy)]
@ -37,7 +43,8 @@ impl Default for Task {
#[derive(Deserialize, Serialize, Debug)]
pub struct Project {
pub name: String,
pub tasks_per_column: IndexMap<TaskStatus, Vec<Task>>,
pub selected_column_idx: usize,
pub columns: Vec<Column>
}
#[derive(Debug, thiserror::Error)]
@ -52,12 +59,8 @@ 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![]),
]),
columns: vec![],
selected_column_idx: 0,
}
}
@ -70,137 +73,112 @@ impl Project {
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);
}
// pub fn add_task(&mut self, status: Column, task: Task) {
// self.tasks_per_column.entry(status).or_default().push(task);
// }
pub fn save(&self) {
let json = serde_json::to_string_pretty(&self).unwrap();
std::fs::write("kanban-tui.json", json).unwrap();
let _json = serde_json::to_string_pretty(&self).unwrap();
// std::fs::write("kanban-tui.json", json).unwrap();
}
pub fn get_selected_column(&self) -> &Column {
&self.columns[self.selected_column_idx]
}
pub fn get_selected_column_mut(&mut self) -> &mut Column {
&mut self.columns[self.selected_column_idx]
}
}
impl Default for Project {
fn default() -> Self {
Project {
name: String::new(),
tasks_per_column: IndexMap::new(),
columns: vec![],
selected_column_idx: 0,
}
}
}
pub struct AppState {
pub selected_column: usize,
pub selected_task: [usize; 4],
pub project: Project,
pub quit: bool,
pub columns: Vec<Column>,
pub popup_text: Option<String>,
}
impl AppState {
pub fn new(project: Project) -> Self {
AppState {
selected_column: 0,
selected_task: [0, 0, 0, 0],
quit: false,
popup_text: None,
project,
columns: vec![],
}
}
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)
let task_idx = &mut self.project.get_selected_column_mut().selected_task_idx;
*task_idx = 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;
}
let column = &mut self.project.get_selected_column_mut();
let task_idx = &mut column.selected_task_idx;
*task_idx = min(*task_idx, column.tasks.len() - 1)
}
pub fn select_previous_column(&mut self) {
self.selected_column = self.selected_column.saturating_sub(1);
pub fn select_previous_column(&mut self) -> &Column {
self.project.selected_column_idx = self.project.selected_column_idx.saturating_sub(1);
&self.project.columns[self.project.selected_column_idx]
}
pub fn select_next_column(&mut self) {
self.selected_column = min(
self.selected_column + 1,
self.project.tasks_per_column.len() - 1,
)
pub fn select_next_column(&mut self) -> &Column {
self.project.selected_column_idx = min(
self.project.selected_column_idx + 1,
self.project.columns.len() - 1,
);
&self.project.columns[self.project.selected_column_idx]
}
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;
self.project.save();
let column = self.project.get_selected_column_mut();
if let Some(task) = column.get_selected_task_mut() {
if self.project.selected_column_idx > 0 {
column.tasks.remove(column.selected_task_idx);
self.select_previous_column().tasks.push(*task);
self.project.save();
}
}
}
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;
self.project.save();
let column = self.project.get_selected_column();
if let Some(task) = column.get_selected_task() {
if self.project.selected_column_idx < self.project.columns.len() {
column.tasks.remove(column.selected_task_idx);
self.select_next_column().tasks.push(*task);
self.project.save();
}
}
}
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;
let column = self.project.get_selected_column();
if column.selected_task_idx > 0 {
column.tasks.swap(column.selected_task_idx, column.selected_task_idx - 1);
column.selected_task_idx = column.selected_task_idx - 1;
self.project.save();
}
}
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;
let column = self.project.get_selected_column();
if column.selected_task_idx < column.tasks.len() - 1 {
column.tasks.swap(column.selected_task_idx, column.selected_task_idx + 1);
column.selected_task_idx = column.selected_task_idx + 1;
self.project.save();
}
}

View File

@ -7,13 +7,13 @@ pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> {
match key.code {
KeyCode::Char('q') => state.quit = true,
KeyCode::Char('h') |
KeyCode::Left => state.select_previous_column(),
KeyCode::Left => { state.select_previous_column(); },
KeyCode::Char('j') |
KeyCode::Down => state.select_next_task(),
KeyCode::Char('k') |
KeyCode::Up => state.select_previous_task(),
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('>') |

View File

@ -11,20 +11,22 @@ fn draw_tasks<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
.direction(Direction::Horizontal)
.constraints(
vec![
Constraint::Percentage(100 / state.project.tasks_per_column.len() as u16);
state.project.tasks_per_column.len()
Constraint::Percentage(100 / state.project.columns.len() as u16);
state.project.columns.len()
]
.as_ref(),
)
.split(*area);
for (i, (status, tasks)) in state.project.tasks_per_column.iter().enumerate() {
let items: Vec<ListItem> = tasks
for (i, column) in state.project.columns.iter().enumerate() {
let items: Vec<ListItem> = column.tasks
.iter()
.enumerate()
.map(|(j, task)| {
let mut style = Style::default();
if i == state.selected_column && j == state.selected_task[state.selected_column] {
let col_idx = state.project.selected_column_idx;
let task_idx = state.project.get_selected_column().selected_task_idx;
if i == col_idx && j == task_idx {
style = style.fg(Color::White).add_modifier(Modifier::BOLD);
} else {
style = style.fg(Color::White);
@ -35,10 +37,10 @@ fn draw_tasks<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
})
.collect();
let mut style = Style::default();
if i == state.selected_column {
if i == state.project.selected_column_idx {
style = style.fg(Color::Green);
};
let mut s = Span::raw(format!("{:?}", status));
let mut s = Span::raw(format!("{:?}", column.name));
s.style = Style::default()
.add_modifier(Modifier::BOLD | Modifier::ITALIC | Modifier::UNDERLINED)
.fg(Color::White);
@ -50,7 +52,7 @@ fn draw_tasks<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
fn draw_task_info<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
let block = Block::default().title("TASK INFO").borders(Borders::ALL);
if let Some(task) = state.get_selected_task() {
if let Some(task) = state.project.get_selected_column().get_selected_task() {
let p = Paragraph::new(task.description.as_str())
.block(block)
.wrap(Wrap { trim: true });