From 7c2249ed08a29b87a93bbcdccac5a7600abe6720 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Tue, 13 Jun 2023 22:51:04 +0700 Subject: [PATCH] Selected column and task now persist. Reworked the borrowing for task edit --- sql/migrations.sql | 3 +- src/app.rs | 16 ++-------- src/db.rs | 72 +++++++++++++++++++++++++++++++---------- src/input.rs | 80 ++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 126 insertions(+), 45 deletions(-) diff --git a/sql/migrations.sql b/sql/migrations.sql index 2f88e2f..0c43138 100644 --- a/sql/migrations.sql +++ b/sql/migrations.sql @@ -18,10 +18,11 @@ create table if not exists kb_column ); -create table if not exists setting +create table if not exists app_state ( key text not null primary key, value text not null ); insert into kb_column(name) values ("Todo"),("InProgress"),("Done"),("Ideas"); +insert into app_state(key, value) values ("selected_column", "0"); diff --git a/src/app.rs b/src/app.rs index 1c9b884..d589963 100644 --- a/src/app.rs +++ b/src/app.rs @@ -12,7 +12,7 @@ mod tests; #[derive(Debug, Serialize, Deserialize)] pub struct Column { - pub id: i64, + pub id: i32, pub name: String, pub selected_task_idx: usize, pub tasks: Vec, @@ -89,17 +89,6 @@ impl State<'_> { } 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, - } - } - pub fn add_task(&mut self, task: Task) { self.tasks.push(task); self.select_last_task(); @@ -188,11 +177,12 @@ impl Project { /// This function will return an error if it has issues reading from the database pub fn load(conn: &Connection) -> Result> { let columns = db::get_all_columns(conn)?; + let selected_column = db::get_selected_column(conn); Ok(Project { name: String::from("Kanban Board"), columns, - selected_column_idx: 0, + selected_column_idx: selected_column, }) } diff --git a/src/db.rs b/src/db.rs index 410abbc..a4226d2 100644 --- a/src/db.rs +++ b/src/db.rs @@ -35,23 +35,19 @@ pub fn get_tasks_by_column(conn: &Connection, column_name: &String) -> Result Result> { - let mut stmt = conn.prepare("select id, name from kb_column")?; - let query_rows = stmt.query_map((), |row| { - Ok((row.get::(0)?, row.get::(1)?)) - })?; - let mut columns: Vec = Vec::new(); - for row in query_rows { - let r = &row?; - let name = &r.1; - let tasks = get_tasks_by_column(conn, name)?; - let col = Column { - id: r.0, - name: name.clone(), - tasks, - selected_task_idx: 0, - }; - columns.push(col); - } + let mut stmt = conn.prepare("select id, name, selected_task from kb_column")?; + let columns = stmt + .query_map((), |row| { + let name = row.get(1)?; + Ok(Column { + id: row.get(0)?, + tasks: get_tasks_by_column(conn, &name)?, + name, + selected_task_idx: row.get(2)?, + }) + })? + .filter_map(Result::ok) + .collect(); Ok(columns) } @@ -121,6 +117,7 @@ pub fn move_task_to_column(conn: &Connection, task: &Task, target_column: &Colum ) .unwrap(); stmt.execute((&task.id, &target_column.id)).unwrap(); + set_selected_task_for_column(conn, target_column.selected_task_idx, target_column.id); } /// . @@ -153,3 +150,44 @@ pub fn swap_task_order(conn: &mut Connection, task1: &Task, task2: &Task) { tx.commit().unwrap(); } + +/// +/// Panics if something goes wrong with the SQL +pub fn set_selected_column(conn: &Connection, column_id: usize) { + let mut stmt = conn + .prepare("insert or replace into app_state(key, value) values (?1, ?2)") + .unwrap(); + stmt.execute((&"selected_column", column_id.to_string())) + .unwrap(); +} + +/// +/// Panics if something goes wrong with the SQL +pub fn get_selected_column(conn: &Connection) -> usize { + let mut stmt = conn + .prepare("select value from app_state where key = ?1") + .unwrap(); + stmt.query_row(&["selected_column"], |row| { + let value: String = row.get::(0).unwrap(); + Ok(value.parse::().unwrap()) + }) + .unwrap() +} + +/// +/// Panics if something goes wrong with the SQL +pub fn set_selected_task_for_column(conn: &Connection, task_idx: usize, column_id: i32) { + let mut stmt = conn + .prepare("update kb_column set selected_task = ?2 where id = ?1") + .unwrap(); + stmt.execute((column_id, task_idx)).unwrap(); +} + +/// +/// Panics if something goes wrong with the SQL +pub fn get_selected_task_for_column(conn: &Connection, column_id: i32) -> usize { + let mut stmt = conn + .prepare("select selected_task from kb_column where key = ?1") + .unwrap(); + stmt.query_row([column_id], |row| row.get(0)).unwrap() +} diff --git a/src/input.rs b/src/input.rs index 360bff4..06f066d 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,11 +1,17 @@ use crate::app::{State, TaskEditFocus, TaskState}; -use crate::db; +use crate::{db, Column}; use crossterm::event; use crossterm::event::{Event, KeyCode}; +use rusqlite::Connection; -pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent, mut task: TaskState<'_>) { - let project = &mut state.project; - let column = project.get_selected_column_mut(); +// pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent, mut task: TaskState<'_>) { +pub fn handle_task_edit( + db_conn: &Connection, + column: &mut Column, + key: event::KeyEvent, + task_opt: &mut Option>, +) { + let mut task = task_opt.as_mut().unwrap(); match task.focus { // TODO: Handle wrapping around the enum rather than doing it manually TaskEditFocus::Title => match key.code { @@ -33,13 +39,13 @@ pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent, mut task: T 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); + db::update_task_text(db_conn, selected_task); } } else { - let task = db::insert_new_task(&state.db_conn, title, description, column); + let task = db::insert_new_task(db_conn, title, description, column); column.add_task(task); } - state.task_edit_state = None; + *task_opt = None; } _ => (), }, @@ -47,7 +53,7 @@ pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent, mut task: T KeyCode::Tab => task.focus = TaskEditFocus::Title, KeyCode::BackTab => task.focus = TaskEditFocus::ConfirmBtn, KeyCode::Enter => { - state.task_edit_state = None; + *task_opt = None; } _ => (), }, @@ -61,14 +67,44 @@ pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) { KeyCode::Char('q') => state.quit = true, KeyCode::Char('h') | KeyCode::Left => { project.select_previous_column(); + db::set_selected_column(&state.db_conn, project.selected_column_idx); + } + KeyCode::Char('j') | KeyCode::Down => { + column.select_next_task(); + db::set_selected_task_for_column( + &state.db_conn, + column.selected_task_idx, + project.get_selected_column().id, + ); + } + KeyCode::Char('k') | KeyCode::Up => { + column.select_previous_task(); + db::set_selected_task_for_column( + &state.db_conn, + column.selected_task_idx, + project.get_selected_column().id, + ); } - KeyCode::Char('j') | KeyCode::Down => column.select_next_task(), - KeyCode::Char('k') | KeyCode::Up => column.select_previous_task(), KeyCode::Char('l') | KeyCode::Right => { project.select_next_column(); + db::set_selected_column(&state.db_conn, project.selected_column_idx); + } + KeyCode::Char('g') => { + column.select_first_task(); + db::set_selected_task_for_column( + &state.db_conn, + column.selected_task_idx, + project.get_selected_column().id, + ); + } + KeyCode::Char('G') => { + column.select_last_task(); + db::set_selected_task_for_column( + &state.db_conn, + column.selected_task_idx, + project.get_selected_column().id, + ); } - KeyCode::Char('g') => column.select_first_task(), - KeyCode::Char('G') => column.select_last_task(), KeyCode::Char('H') => { if !column.tasks.is_empty() { project.move_task_previous_column(); @@ -90,6 +126,11 @@ pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) { let task1 = column.get_selected_task().unwrap(); let task2 = column.get_previous_task().unwrap(); db::swap_task_order(&mut state.db_conn, task1, task2); + db::set_selected_task_for_column( + &state.db_conn, + column.selected_task_idx, + project.get_selected_column().id, + ); } } KeyCode::Char('K') => { @@ -97,6 +138,11 @@ pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) { let task1 = column.get_selected_task().unwrap(); let task2 = column.get_next_task().unwrap(); db::swap_task_order(&mut state.db_conn, task1, task2); + db::set_selected_task_for_column( + &state.db_conn, + column.selected_task_idx, + project.get_selected_column().id, + ); } } KeyCode::Char('n') => state.task_edit_state = Some(TaskState::default()), @@ -107,6 +153,11 @@ pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) { if !column.tasks.is_empty() { db::delete_task(&state.db_conn, column.get_selected_task().unwrap()); column.remove_task(); + db::set_selected_task_for_column( + &state.db_conn, + column.selected_task_idx, + project.get_selected_column().id, + ); } } _ => {} @@ -122,8 +173,9 @@ pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) { /// Shouldn't really panic because there are checks to ensure we can unwrap safely pub fn handle(state: &mut State<'_>) -> Result<(), std::io::Error> { if let Event::Key(key) = event::read()? { - if let Some(task) = state.task_edit_state.take() { - handle_task_edit(state, key, task); + if let Some(_) = state.task_edit_state { + let mut column = state.project.get_selected_column_mut(); + handle_task_edit(&state.db_conn, &mut column, key, &mut state.task_edit_state); } else { handle_main(state, key); }