Update and move task order up/down, read ids, more column helper methods

This commit is contained in:
Joseph Ferano 2023-06-13 12:41:37 +07:00
parent 4477bac258
commit 00ac0c351d
4 changed files with 141 additions and 86 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/.idea/
/kanban-tui.json
/kanban.json
/db.sqlite

View File

@ -7,13 +7,14 @@ use std::fs::File;
use std::io::Read;
use tui_textarea::TextArea;
use crate::get_all_tasks;
use crate::db;
#[cfg(test)]
mod tests;
#[derive(Debug, Serialize, Deserialize)]
pub struct Column {
pub id: i64,
pub name: String,
pub selected_task_idx: usize,
pub tasks: Vec<Task>,
@ -94,6 +95,8 @@ impl<'a> Column {
#[must_use]
pub fn new(name: &str) -> Self {
Column {
// TODO: Get the right ID here
id: 1,
name: name.to_owned(),
tasks: vec![],
selected_task_idx: 0,
@ -115,6 +118,16 @@ impl<'a> Column {
self.tasks.get(self.selected_task_idx)
}
#[must_use]
pub fn get_previous_task(&self) -> Option<&Task> {
self.tasks.get(self.selected_task_idx - 1)
}
#[must_use]
pub fn get_next_task(&self) -> Option<&Task> {
self.tasks.get(self.selected_task_idx + 1)
}
pub fn get_selected_task_mut(&mut self) -> Option<&mut Task> {
self.tasks.get_mut(self.selected_task_idx)
}
@ -137,6 +150,28 @@ impl<'a> Column {
self.selected_task_idx = self.tasks.len() - 1;
}
pub fn move_task_up(&mut self) -> bool {
if self.selected_task_idx > 0 {
self.tasks
.swap(self.selected_task_idx, self.selected_task_idx - 1);
self.selected_task_idx -= 1;
true
} else {
false
}
}
pub fn move_task_down(&mut self) -> bool {
if self.selected_task_idx < self.tasks.len() - 1 {
self.tasks
.swap(self.selected_task_idx, self.selected_task_idx + 1);
self.selected_task_idx += 1;
true
} else {
false
}
}
#[must_use]
pub fn get_task_state_from_curr_selected_task(&self) -> Option<TaskState<'a>> {
self.get_selected_task().map(|t| TaskState {
@ -182,33 +217,16 @@ impl Project {
}
pub async fn load2(pool: &Connection) -> Result<Self, KanbanError> {
let todos = get_all_tasks(&pool).unwrap();
let columns = db::get_all_columns(&pool).unwrap();
Ok(Project {
name: String::from("Kanban Board"),
filepath: String::from("path"),
columns: todos
.iter()
.map(|(cname, tasks)| Column {
name: cname.clone(),
// TODO: Figure out how to avoid cloning here
tasks: tasks.to_vec(),
selected_task_idx: 0,
})
.collect::<Vec<Column>>(),
columns,
selected_column_idx: 0,
})
}
/// # Panics
///
/// Will panic if there's an error serializing the Json or there's an issue
/// writing the file
pub fn save(&self) {
let json = serde_json::to_string_pretty(&self).unwrap();
std::fs::write(&self.filepath, json).unwrap();
}
#[must_use]
pub fn get_selected_column(&self) -> &Column {
&self.columns[self.selected_column_idx]
@ -248,7 +266,6 @@ impl Project {
let col = self.get_selected_column_mut();
col.tasks.push(t);
col.select_last_task();
self.save();
}
}
@ -259,26 +276,4 @@ impl Project {
pub fn move_task_next_column(&mut self) {
self.move_task_to_column(true);
}
pub fn move_task_up(&mut self) {
let column = self.get_selected_column_mut();
if column.selected_task_idx > 0 {
column
.tasks
.swap(column.selected_task_idx, column.selected_task_idx - 1);
column.selected_task_idx -= 1;
self.save();
}
}
pub fn move_task_down(&mut self) {
let column = self.get_selected_column_mut();
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 += 1;
self.save();
}
}
}

101
src/db.rs
View File

@ -7,6 +7,7 @@ pub fn get_tasks_by_column(conn: &Connection, column_name: &String) -> Result<Ve
select task.id, title, description from task
join kb_column on column_id = kb_column.id
where kb_column.name = ?1
order by sort_order
"#,
)?;
let mut tasks = Vec::new();
@ -23,55 +24,87 @@ pub fn get_tasks_by_column(conn: &Connection, column_name: &String) -> Result<Ve
Ok(tasks)
}
pub fn get_all_tasks(conn: &Connection) -> Result<Vec<(String, Vec<Task>)>> {
let mut stmt = conn.prepare("select name from kb_column")?;
let columns = stmt.query_map((), |row| Ok(row.get::<usize, String>(0)?))?;
let mut tasks_by_column: Vec<(String, Vec<Task>)> = Vec::new();
for col in columns {
let name = &col?;
let tasks = get_tasks_by_column(conn, name).unwrap();
tasks_by_column.push((name.to_string(), tasks));
pub fn get_all_columns(conn: &Connection) -> Result<Vec<Column>> {
let mut stmt = conn.prepare("select id, name from kb_column")?;
let query_rows = stmt.query_map((), |row| {
Ok((row.get::<usize, i64>(0)?, row.get::<usize, String>(1)?))
})?;
let mut columns: Vec<Column> = Vec::new();
for row in query_rows {
let r = &row?;
let name = &r.1;
let tasks = get_tasks_by_column(conn, &name).unwrap();
let col = Column {
id: r.0,
name: name.clone(),
tasks: tasks,
selected_task_idx: 0,
};
columns.push(col);
}
Ok(tasks_by_column)
Ok(columns)
}
pub fn insert_new_task(conn: &Connection, title: String, description: String, _column: &Column) -> Task {
pub fn insert_new_task(
conn: &Connection,
title: String,
description: String,
column: &Column,
) -> Task {
let mut stmt = conn
.prepare("insert into task(title, description, column_id) values (?1, ?2, ?3)")
.unwrap();
stmt.execute(params![title, description, 1])
stmt.execute(params![title, description, column.id])
.unwrap();
let id = conn.last_insert_rowid();
Task { id, title, description }
Task {
id,
title,
description,
}
}
pub fn delete_task(conn: &Connection, task: &Task) {
let mut stmt = conn.prepare("delete from task where id = ?1").unwrap();
stmt.execute([task.id]).unwrap();
}
pub fn update_task_text(conn: &Connection, task: &Task) {
let mut stmt = conn
.prepare("delete from task where id = ?1")
.prepare("update task set title = ?2, description = ?3 where id = ?1")
.unwrap();
stmt.execute([task.id])
stmt.execute((&task.id, &task.title, &task.description))
.unwrap();
}
// pub async fn update_task(pool: &SqlitePool, task: &Task) {
// sqlx::query!("update task set title = ?1, description = ?2", task.title, task.description)
// .execute(pool)
// .await
// .unwrap();
// }
pub fn move_task_to_column(conn: &Connection, task: &Task, target_column: &Column) {
let mut stmt = conn
.prepare("update task set column_id = ?2, sort_order = ?3 where task.id = ?1")
.unwrap();
stmt.execute((&task.id, &target_column.id, &target_column.tasks.len()))
.unwrap();
}
// pub async fn move_task_to_column(pool: &SqlitePool, task: &Task, target_column: &Column) {
// // TODO: You have to add the id to the column
// sqlx::query!("update task set column_id = ?1", 1)
// .execute(pool)
// .await
// .unwrap();
// }
pub fn swap_task_order(conn: &mut Connection, task1: &Task, task2: &Task) {
let tx = conn.transaction().unwrap();
// pub async fn move_task_order(pool: &SqlitePool, task: &Task) {
// // TODO: We have to add some kind of ordering mechanism to tasks
// sqlx::query!("update task set column_id = ?1", 1)
// .execute(pool)
// .await
// .unwrap();
// }
tx.execute(
"create temp table temp_order as select sort_order from task where id = ?1",
&[&task1.id]
)
.unwrap();
tx.execute(
"update task set sort_order = (select sort_order from task where id = ?2) where id = ?1",
(task1.id, task2.id)
)
.unwrap();
tx.execute(
"update task set sort_order = (select sort_order from temp_order) where id = ?1",
&[&task2.id]
)
.unwrap();
tx.execute("drop table temp_order", ()).unwrap();
tx.commit().unwrap();
}

View File

@ -1,5 +1,5 @@
use crate::app::{State, TaskEditFocus, TaskState};
use crate::{db};
use crate::db;
use crossterm::event;
use crossterm::event::{Event, KeyCode};
@ -40,13 +40,18 @@ pub fn handle(state: &mut State<'_>) -> Result<(), std::io::Error> {
if let Some(selected_task) = column.get_selected_task_mut() {
selected_task.title = title;
selected_task.description = description;
db::update_task_text(&state.db_conn, &selected_task);
}
} else {
let task = db::insert_new_task(&state.db_conn, title, description, &column);
let task = db::insert_new_task(
&state.db_conn,
title,
description,
&column,
);
column.add_task(task);
}
state.task_edit_state = None;
project.save();
}
_ => (),
},
@ -72,18 +77,39 @@ pub fn handle(state: &mut State<'_>) -> Result<(), std::io::Error> {
}
KeyCode::Char('g') => column.select_first_task(),
KeyCode::Char('G') => column.select_last_task(),
KeyCode::Char('H') => project.move_task_previous_column(),
KeyCode::Char('L') => project.move_task_next_column(),
KeyCode::Char('J') => project.move_task_down(),
KeyCode::Char('K') => project.move_task_up(),
KeyCode::Char('H') => {
project.move_task_previous_column();
let col = project.get_selected_column();
let t = col.get_selected_task().unwrap();
db::move_task_to_column(&state.db_conn, &t, &col);
}
KeyCode::Char('L') => {
project.move_task_next_column();
let col = project.get_selected_column();
let t = col.get_selected_task().unwrap();
db::move_task_to_column(&state.db_conn, &t, &col);
}
KeyCode::Char('J') => {
if column.move_task_down() {
let task1 = column.get_selected_task().unwrap();
let task2 = column.get_previous_task().unwrap();
db::swap_task_order(&mut state.db_conn, &task1, &task2);
}
}
KeyCode::Char('K') => {
if column.move_task_up() {
let task1 = column.get_selected_task().unwrap();
let task2 = column.get_next_task().unwrap();
db::swap_task_order(&mut state.db_conn, &task1, &task2);
}
}
KeyCode::Char('n') => state.task_edit_state = Some(TaskState::default()),
KeyCode::Char('e') => {
state.task_edit_state = column.get_task_state_from_curr_selected_task()
}
KeyCode::Char('D') => {
column.remove_task();
db::delete_task(&state.db_conn, column.get_selected_task().unwrap());
// project.save();
column.remove_task();
}
_ => {}
},