local storage - fin
This commit is contained in:
parent
f686c51611
commit
cac9af75f2
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
/.idea
|
/.idea
|
||||||
/todo-db.db3
|
/todolist-db.db3
|
74
src/db.rs
74
src/db.rs
@ -1,10 +1,8 @@
|
|||||||
|
use crate::todo::{ChangeType, TaskColumn};
|
||||||
|
use rusqlite::{params, Connection, Error, Result};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use rusqlite::{params, Connection, Result};
|
|
||||||
use crate::def;
|
|
||||||
use crate::def::Task;
|
|
||||||
|
|
||||||
pub(crate) fn startup(path: &Path) -> Result<(Connection)> {
|
pub(crate) fn startup(path: &Path) -> Result<(Connection)> {
|
||||||
// let conn = Connection::open_in_memory()?;
|
|
||||||
let conn = Connection::open(path)?;
|
let conn = Connection::open(path)?;
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
@ -16,32 +14,74 @@ pub(crate) fn startup(path: &Path) -> Result<(Connection)> {
|
|||||||
()
|
()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// todo
|
|
||||||
|
|
||||||
Ok(conn)
|
Ok(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_tasks(conn: &Connection) -> Result<()> {
|
pub(crate) fn get_tasks(conn: &Connection) -> std::result::Result<Vec<TaskColumn>, Error> {
|
||||||
let mut stmt = conn.prepare("SELECT checked, value FROM tasks")?;
|
let mut stmt = conn.prepare("SELECT id, checked, value FROM tasks")?;
|
||||||
let task_iter = stmt.query_map([], |row| {
|
let tasks_iter = stmt.query_map([], |row| {
|
||||||
Ok(Task {
|
Ok(TaskColumn {
|
||||||
checked: row.get(0)?,
|
id: row.get(0)?,
|
||||||
value: row.get(1)?,
|
checked: row.get(1)?,
|
||||||
|
value: row.get(2)?,
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for task in task_iter {
|
let mut tasks = Vec::new();
|
||||||
println!("{:?}", task);
|
for task in tasks_iter {
|
||||||
|
tasks.push(task?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn insert_task(task: Task, conn: &Connection) -> Result<()> {
|
pub(crate) fn insert_task(conn: &Connection, task: TaskColumn) -> Result<i64> {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO tasks (checked, value) VALUES (?1, ?2)",
|
"INSERT INTO tasks (checked, value) VALUES (?1, ?2)",
|
||||||
(&task.checked, &task.value),
|
(&task.checked, &task.value),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
Ok(conn.last_insert_rowid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// happens if we un/select task or we rename the task
|
||||||
|
pub(crate) fn update_task(conn: &Connection, task: TaskColumn, change: ChangeType) -> Result<()> {
|
||||||
|
let (query, params) = match change {
|
||||||
|
ChangeType::Checked => (
|
||||||
|
"UPDATE tasks SET checked = ?1 WHERE id = ?2",
|
||||||
|
params![&task.checked, &task.id]
|
||||||
|
),
|
||||||
|
ChangeType::Value => (
|
||||||
|
"UPDATE tasks SET value = ?1 WHERE id = ?2",
|
||||||
|
params![&task.value, &task.id]
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
conn.execute(query, params)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_unselect_tasks(conn: &Connection, checked: bool) -> Result<()> {
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE tasks SET checked = ?1",
|
||||||
|
(&checked,)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn delete_tasks(conn: &Connection, id: Option<usize>) -> Result<()> {
|
||||||
|
let (query, params) = match id {
|
||||||
|
Some(t) => (
|
||||||
|
"DELETE FROM tasks WHERE id = ?1",
|
||||||
|
params![t.clone()]
|
||||||
|
),
|
||||||
|
None => (
|
||||||
|
"DELETE FROM tasks",
|
||||||
|
params![]
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
conn.execute(query, params)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
35
src/def.rs
35
src/def.rs
@ -1,35 +0,0 @@
|
|||||||
use iced::Event;
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Task {
|
|
||||||
pub(crate) checked: bool,
|
|
||||||
pub(crate) value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TaskData {
|
|
||||||
pub(crate) checked: bool,
|
|
||||||
pub(crate) value: String,
|
|
||||||
pub(crate) edit: bool,
|
|
||||||
pub(crate) can_update: bool,
|
|
||||||
pub(crate) can_delete: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Todo {
|
|
||||||
pub(crate) new_task: String,
|
|
||||||
pub(crate) updated_task: String,
|
|
||||||
pub(crate) tasks: Vec<TaskData>,
|
|
||||||
pub(crate) completed_tasks: usize,
|
|
||||||
pub(crate) local_storage: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Message {
|
|
||||||
AddTask,
|
|
||||||
CheckTask(bool, usize),
|
|
||||||
EditTask(usize),
|
|
||||||
DeleteTask(usize),
|
|
||||||
DeleteAll,
|
|
||||||
ContentUpdated(bool, String),
|
|
||||||
Event(Event),
|
|
||||||
TaskPush(Option<usize>),
|
|
||||||
StorageToggle(bool),
|
|
||||||
}
|
|
26
src/main.rs
26
src/main.rs
@ -1,18 +1,14 @@
|
|||||||
use std::path::Path;
|
|
||||||
use iced::window::Settings;
|
use iced::window::Settings;
|
||||||
use iced::Size;
|
use iced::Size;
|
||||||
use def::Todo;
|
use todo::Todo;
|
||||||
use crate::db::startup;
|
|
||||||
use crate::def::Task;
|
|
||||||
|
|
||||||
mod todo;
|
mod todo;
|
||||||
mod def;
|
|
||||||
mod db;
|
mod db;
|
||||||
|
|
||||||
/*fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
let settings = Settings {
|
let settings = Settings {
|
||||||
size: Size::new(500.0, 600.0),
|
size: Size::new(500.0, 600.0),
|
||||||
resizable: false,
|
//resizable: false,
|
||||||
..Settings::default()
|
..Settings::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -21,18 +17,20 @@ mod db;
|
|||||||
.theme(Todo::theme)
|
.theme(Todo::theme)
|
||||||
.window(settings)
|
.window(settings)
|
||||||
.run()
|
.run()
|
||||||
}*/
|
}
|
||||||
|
|
||||||
fn main() {
|
/*fn main() {
|
||||||
let path: &Path = Path::new("./todo-db.db3");
|
let path: &Path = Path::new("./todolist-db.db3");
|
||||||
let conn = startup(path);
|
let conn = startup(path);
|
||||||
let t = Task {
|
let t = Task {
|
||||||
checked: true,
|
checked: true,
|
||||||
value: String::from("Troll Ink"),
|
value: String::from("JJ"),
|
||||||
};
|
};
|
||||||
let c = conn.unwrap();
|
let c = conn.unwrap();
|
||||||
/*let r = db::insert_task(t, &c);
|
let r = db::insert_task(t, &c);
|
||||||
println!("{:?}", r);*/
|
println!("{:?}", r);
|
||||||
|
|
||||||
let rr = db::get_tasks(&c);
|
let rr = db::get_tasks(&c);
|
||||||
}
|
|
||||||
|
let x = c.close();
|
||||||
|
}*/
|
179
src/todo.rs
179
src/todo.rs
@ -1,26 +1,77 @@
|
|||||||
use iced::{event, keyboard, widget, Center, Element, Event, Length, Subscription, Task, Theme};
|
use iced::{event, keyboard, widget, Center, Element, Event, Length, Subscription, Task, Theme};
|
||||||
use iced::keyboard::key;
|
use iced::keyboard::key;
|
||||||
use iced::widget::{button, center, checkbox, column, row, scrollable, text_input, Space, Text};
|
use iced::widget::{button, center, checkbox, column, row, scrollable, text_input, Space, Text};
|
||||||
use crate::def::{Message, TaskData, Todo};
|
use rusqlite::Connection;
|
||||||
|
use crate::db;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TaskColumn {
|
||||||
|
pub(crate) id: usize,
|
||||||
|
pub(crate) checked: bool,
|
||||||
|
pub(crate) value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TaskData {
|
||||||
|
id: usize,
|
||||||
|
checked: bool,
|
||||||
|
value: String,
|
||||||
|
edit: bool,
|
||||||
|
can_update: bool,
|
||||||
|
can_delete: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Todo {
|
||||||
|
new_task: String,
|
||||||
|
updated_task: String,
|
||||||
|
tasks: Vec<TaskData>,
|
||||||
|
completed_tasks: usize,
|
||||||
|
local_storage: bool,
|
||||||
|
conn: Connection,
|
||||||
|
select_all: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
AddTask,
|
||||||
|
CheckTask(bool, usize),
|
||||||
|
EditTask(usize),
|
||||||
|
DeleteTask(usize),
|
||||||
|
DeleteAll,
|
||||||
|
ContentUpdated(bool, String),
|
||||||
|
Event(Event),
|
||||||
|
TaskPush(Option<usize>),
|
||||||
|
StorageToggle(bool),
|
||||||
|
ToggleUnselect(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ChangeType {
|
||||||
|
Checked,
|
||||||
|
Value,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Todo {
|
impl Default for Todo {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Todo::new()
|
Todo::new()
|
||||||
// startup checks
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Todo {
|
impl Todo {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
let mut init = Self {
|
||||||
new_task: String::new(),
|
new_task: String::new(),
|
||||||
updated_task: String::new(),
|
updated_task: String::new(),
|
||||||
tasks: Vec::new(),
|
tasks: Vec::new(),
|
||||||
completed_tasks: 0,
|
completed_tasks: 0,
|
||||||
local_storage: true,
|
local_storage: true, // todo eventually get this info from db - if no db set it to true
|
||||||
}
|
conn: db::startup("./todolist-db.db3".as_ref()).expect("[ERROR] Failed to access the local storage"),
|
||||||
|
select_all: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::load_data_from_db(&mut init);
|
||||||
|
|
||||||
|
init
|
||||||
}
|
}
|
||||||
pub(crate) fn update(&mut self, message: Message) -> Task<Message> {
|
pub(crate) fn update(&mut self, message: Message) -> Task<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::ContentUpdated(new, value) => {
|
Message::ContentUpdated(new, value) => {
|
||||||
if new {
|
if new {
|
||||||
@ -34,31 +85,60 @@ impl Todo {
|
|||||||
Message::AddTask => {
|
Message::AddTask => {
|
||||||
if self.new_task.is_empty() { return Task::none(); }
|
if self.new_task.is_empty() { return Task::none(); }
|
||||||
|
|
||||||
let data = TaskData {
|
|
||||||
|
let result = db::insert_task(&self.conn, TaskColumn {
|
||||||
|
id: 0,
|
||||||
checked: false,
|
checked: false,
|
||||||
value: self.new_task.to_string(),
|
value: self.new_task.to_string(),
|
||||||
edit: false,
|
});
|
||||||
can_update: true,
|
|
||||||
can_delete: true,
|
match result {
|
||||||
};
|
Ok(t) => {
|
||||||
|
let data = TaskData {
|
||||||
|
id: t as usize,
|
||||||
|
checked: false,
|
||||||
|
value: self.new_task.to_string(),
|
||||||
|
edit: false,
|
||||||
|
can_update: true,
|
||||||
|
can_delete: true,
|
||||||
|
};
|
||||||
|
self.tasks.push(data);
|
||||||
|
},
|
||||||
|
Err(e) => eprintln!("[ERROR] Failed to insert new task into DB:\n{e}"),
|
||||||
|
}
|
||||||
|
|
||||||
self.tasks.push(data);
|
|
||||||
self.new_task = String::new();
|
self.new_task = String::new();
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
},
|
},
|
||||||
Message::DeleteTask(id) => {
|
Message::DeleteTask(index) => {
|
||||||
if self.tasks[id].checked {
|
if self.tasks[index].checked {
|
||||||
self.completed_tasks -= 1;
|
self.completed_tasks -= 1;
|
||||||
}
|
}
|
||||||
self.tasks.remove(id);
|
|
||||||
|
if let Err(e) = db::delete_tasks(&self.conn, Some(self.tasks[index].id)) {
|
||||||
|
eprintln!("[ERROR] Failed to delete task '{}':\n{e}", self.tasks[index].value);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tasks.remove(index);
|
||||||
|
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
},
|
}
|
||||||
Message::CheckTask(choice, id) => {
|
Message::CheckTask(choice, id) => {
|
||||||
self.completed_tasks = if choice { self.completed_tasks + 1 } else { self.completed_tasks - 1 };
|
self.completed_tasks = if choice { self.completed_tasks + 1 } else { self.completed_tasks - 1 };
|
||||||
self.tasks[id].checked = choice;
|
self.tasks[id].checked = choice;
|
||||||
|
|
||||||
|
let result = db::update_task(&self.conn, TaskColumn {
|
||||||
|
id: self.tasks[id].id,
|
||||||
|
checked: self.tasks[id].checked,
|
||||||
|
value: self.tasks[id].value.to_string(),
|
||||||
|
}, ChangeType::Checked);
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
eprintln!("[ERROR] Failed to update checkbox in local storage:\n{e}");
|
||||||
|
}
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
},
|
},
|
||||||
Message::EditTask(id) => {
|
Message::EditTask(id) => {
|
||||||
@ -70,6 +150,16 @@ impl Todo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.tasks[id].value = self.updated_task.clone();
|
self.tasks[id].value = self.updated_task.clone();
|
||||||
|
let result = db::update_task(&self.conn, TaskColumn {
|
||||||
|
id: self.tasks[id].id,
|
||||||
|
checked: self.tasks[id].checked,
|
||||||
|
value: self.tasks[id].value.to_string(),
|
||||||
|
}, ChangeType::Value);
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
eprintln!("[ERROR] Failed to update task '{}' in local storage:\n{e}", self.tasks[id].value);
|
||||||
|
}
|
||||||
|
|
||||||
set_update = true;
|
set_update = true;
|
||||||
self.updated_task = String::new();
|
self.updated_task = String::new();
|
||||||
} else {
|
} else {
|
||||||
@ -82,6 +172,10 @@ impl Todo {
|
|||||||
widget::focus_previous()
|
widget::focus_previous()
|
||||||
},
|
},
|
||||||
Message::DeleteAll => {
|
Message::DeleteAll => {
|
||||||
|
if let Err(e) = db::delete_tasks(&self.conn, None) {
|
||||||
|
eprintln!("[ERROR] Failed to delete all tasks:\n{e}")
|
||||||
|
}
|
||||||
|
|
||||||
self.new_task = String::from("");
|
self.new_task = String::from("");
|
||||||
self.updated_task = String::from("");
|
self.updated_task = String::from("");
|
||||||
self.tasks.clear();
|
self.tasks.clear();
|
||||||
@ -121,20 +215,41 @@ impl Todo {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Message::StorageToggle(toggle) => {
|
Message::StorageToggle(toggle) => {
|
||||||
self.local_storage = toggle;
|
// todo must be enabled eventually
|
||||||
|
//self.local_storage = toggle;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
todo
|
todo
|
||||||
here we only call for storage change not implement the whole system
|
here we only call for storage change not implement the whole system
|
||||||
as the system should be running since the program startup
|
as the system should be running since the program startup
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// also how do we even implement that
|
||||||
|
|
||||||
|
Task::none()
|
||||||
|
},
|
||||||
|
Message::ToggleUnselect(toggle) => {
|
||||||
|
self.select_all = toggle;
|
||||||
|
for task in &mut self.tasks {
|
||||||
|
task.checked = toggle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if toggle {
|
||||||
|
self.completed_tasks = self.tasks.len();
|
||||||
|
} else {
|
||||||
|
self.completed_tasks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = db::update_unselect_tasks(&self.conn, toggle) {
|
||||||
|
eprintln!("[ERROR] Failed to update un/select all operation in database:\n{e}");
|
||||||
|
}
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn view(&self) -> Element<Message> {
|
pub(crate) fn view(&self) -> Element<Message> {
|
||||||
let input = text_input("Enter new task", &self.new_task)
|
let input = text_input("Enter new task", &self.new_task)
|
||||||
.width(300)
|
.width(300)
|
||||||
.size(25)
|
.size(25)
|
||||||
@ -186,8 +301,9 @@ impl Todo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let status = Text::new(format!("{} / {}", self.completed_tasks, self.tasks.len()));
|
let status = Text::new(format!("{} / {}", self.completed_tasks, self.tasks.len()));
|
||||||
|
let unselect = checkbox("Un/select all", self.select_all).on_toggle(Message::ToggleUnselect);
|
||||||
let storage = checkbox("Local storage", self.local_storage).on_toggle(Message::StorageToggle);
|
let storage = checkbox("Local storage", self.local_storage).on_toggle(Message::StorageToggle);
|
||||||
let footer = row![status, Space::with_width(Length::Fill), storage].padding(10);
|
let footer = row![status, Space::with_width(Length::Fill), unselect, storage].padding(10).spacing(10);
|
||||||
|
|
||||||
let mut output = column![new_task.padding(10)];
|
let mut output = column![new_task.padding(10)];
|
||||||
output = if self.tasks.is_empty() { output.push(saved_tasks.height(Length::Fill)) } else { output.push(scrollable(saved_tasks).height(Length::Fill).spacing(10)) };
|
output = if self.tasks.is_empty() { output.push(saved_tasks.height(Length::Fill)) } else { output.push(scrollable(saved_tasks).height(Length::Fill).spacing(10)) };
|
||||||
@ -196,11 +312,11 @@ impl Todo {
|
|||||||
center(output).into()
|
center(output).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn theme(&self) -> Theme {
|
pub(crate) fn theme(&self) -> Theme {
|
||||||
Theme::Dark
|
Theme::Dark
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn subscription(&self) -> Subscription<Message> {
|
pub(crate) fn subscription(&self) -> Subscription<Message> {
|
||||||
event::listen().map(Message::Event)
|
event::listen().map(Message::Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,4 +328,25 @@ impl Todo {
|
|||||||
task.can_delete = enable;
|
task.can_delete = enable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_data_from_db(&mut self) {
|
||||||
|
if let Ok(t) = db::get_tasks(&self.conn) {
|
||||||
|
for item in t {
|
||||||
|
let data = TaskData {
|
||||||
|
id: item.id,
|
||||||
|
checked: item.checked,
|
||||||
|
value: item.value,
|
||||||
|
edit: false,
|
||||||
|
can_update: true,
|
||||||
|
can_delete: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if item.checked {
|
||||||
|
self.completed_tasks += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tasks.push(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user