Update and move task order up/down, read ids, more column helper methods
This commit is contained in:
parent
4477bac258
commit
00ac0c351d
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
||||
/.idea/
|
||||
/kanban-tui.json
|
||||
/kanban.json
|
||||
/db.sqlite
|
||||
|
81
src/app.rs
81
src/app.rs
@ -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
101
src/db.rs
@ -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();
|
||||
}
|
||||
|
44
src/input.rs
44
src/input.rs
@ -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();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user