use custom Task type

This commit is contained in:
Fabien Freling 2025-06-23 13:05:23 +02:00
parent 01566042b3
commit 5450884bb0
6 changed files with 79 additions and 100 deletions

7
Cargo.lock generated
View file

@ -214,6 +214,12 @@ dependencies = [
"syn",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dsl_auto_type"
version = "0.1.3"
@ -737,6 +743,7 @@ dependencies = [
"color-eyre",
"crossterm",
"diesel",
"dotenvy",
"ratatui",
]

View file

@ -12,3 +12,4 @@ ratatui = "0.29.0"
color-eyre = "0.6.3"
diesel = { version = "2.2", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] }
dotenvy = "0.15"

View file

@ -18,6 +18,7 @@
rust-toolchain
cargo-generate
diesel-cli
sqlite
just
lazysql

14
src/lib.rs Normal file
View file

@ -0,0 +1,14 @@
pub mod models;
pub mod schema;
use diesel::prelude::*;
use dotenvy::dotenv;
use std::env;
pub fn establish_connection() -> SqliteConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
SqliteConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}

View file

@ -1,4 +1,5 @@
use color_eyre::Result;
use diesel::prelude::*;
use ratatui::{
DefaultTerminal,
buffer::Buffer,
@ -15,6 +16,8 @@ use ratatui::{
StatefulWidget, Widget, Wrap,
},
};
use todo::establish_connection;
use todo::models::*;
const TODO_HEADER_STYLE: Style = Style::new().fg(SLATE.c100).bg(BLUE.c800);
const NORMAL_ROW_BG: Color = SLATE.c950;
@ -26,91 +29,32 @@ const COMPLETED_TEXT_FG_COLOR: Color = GREEN.c500;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
let app = App::default().load_db();
let app_result = app.run(terminal);
ratatui::restore();
app_result
}
struct App {
should_exit: bool,
todo_list: TodoList,
tasks: TaskList,
sqlite: SqliteConnection,
}
struct TodoList {
items: Vec<TodoItem>,
struct TaskList {
items: Vec<Task>,
state: ListState,
}
#[derive(Debug)]
struct TodoItem {
todo: String,
info: String,
status: Status,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum Status {
Todo,
Completed,
}
impl Default for App {
fn default() -> Self {
Self {
should_exit: false,
todo_list: TodoList::from_iter([
(
Status::Todo,
"Rewrite everything with Rust!",
"I can't hold my inner voice. He tells me to rewrite the complete universe with Rust",
),
(
Status::Completed,
"Rewrite all of your tui apps with Ratatui",
"Yes, you heard that right. Go and replace your tui with Ratatui.",
),
(
Status::Todo,
"Pet your cat",
"Minnak loves to be pet by you! Don't forget to pet and give some treats!",
),
(
Status::Todo,
"Walk with your dog",
"Max is bored, go walk with him!",
),
(
Status::Completed,
"Pay the bills",
"Pay the train subscription!!!",
),
(
Status::Completed,
"Refactor list example",
"If you see this info that means I completed this task!",
),
]),
}
}
}
impl FromIterator<(Status, &'static str, &'static str)> for TodoList {
fn from_iter<I: IntoIterator<Item = (Status, &'static str, &'static str)>>(iter: I) -> Self {
let items = iter
.into_iter()
.map(|(status, todo, info)| TodoItem::new(status, todo, info))
.collect();
let state = ListState::default();
Self { items, state }
}
}
impl TodoItem {
fn new(status: Status, todo: &str, info: &str) -> Self {
Self {
status,
todo: todo.to_string(),
info: info.to_string(),
tasks: TaskList {
items: Vec::new(),
state: ListState::default(),
},
sqlite: establish_connection(),
}
}
}
@ -126,6 +70,17 @@ impl App {
Ok(())
}
fn load_db(mut self) -> Self {
use todo::schema::tasks::dsl::*;
self.tasks.items = tasks
.select(Task::as_select())
.load(&mut self.sqlite)
.expect("Error loading posts");
self
}
fn handle_key(&mut self, key: KeyEvent) {
if key.kind != KeyEventKind::Press {
return;
@ -143,32 +98,32 @@ impl App {
}
fn select_none(&mut self) {
self.todo_list.state.select(None);
self.tasks.state.select(None);
}
fn select_next(&mut self) {
self.todo_list.state.select_next();
self.tasks.state.select_next();
}
fn select_previous(&mut self) {
self.todo_list.state.select_previous();
self.tasks.state.select_previous();
}
fn select_first(&mut self) {
self.todo_list.state.select_first();
self.tasks.state.select_first();
}
fn select_last(&mut self) {
self.todo_list.state.select_last();
self.tasks.state.select_last();
}
/// Changes the status of the selected list item
fn toggle_status(&mut self) {
if let Some(i) = self.todo_list.state.selected() {
self.todo_list.items[i].status = match self.todo_list.items[i].status {
Status::Completed => Status::Todo,
Status::Todo => Status::Completed,
}
}
// if let Some(i) = self.tasks.state.selected() {
// self.tasks.items[i].status = match self.tasks.items[i].status {
// Status::Completed => Status::Todo,
// Status::Todo => Status::Completed,
// }
// }
}
}
@ -216,13 +171,14 @@ impl App {
// Iterate through all elements in the `items` and stylize them.
let items: Vec<ListItem> = self
.todo_list
.tasks
.items
.iter()
.enumerate()
.map(|(i, todo_item)| {
.map(|(i, task)| {
let color = alternate_colors(i);
ListItem::from(todo_item).bg(color)
let item = item_from_task(task);
item.bg(color)
})
.collect();
@ -235,16 +191,13 @@ impl App {
// We need to disambiguate this trait method as both `Widget` and `StatefulWidget` share the
// same method name `render`.
StatefulWidget::render(list, area, buf, &mut self.todo_list.state);
StatefulWidget::render(list, area, buf, &mut self.tasks.state);
}
fn render_selected_item(&self, area: Rect, buf: &mut Buffer) {
// We get the info depending on the item's state.
let info = if let Some(i) = self.todo_list.state.selected() {
match self.todo_list.items[i].status {
Status::Completed => format!("✓ DONE: {}", self.todo_list.items[i].info),
Status::Todo => format!("☐ TODO: {}", self.todo_list.items[i].info),
}
let info = if let Some(i) = self.tasks.state.selected() {
format!("☐ TODO: {}", self.tasks.items[i].title)
} else {
"Nothing selected...".to_string()
};
@ -275,14 +228,7 @@ const fn alternate_colors(i: usize) -> Color {
}
}
impl From<&TodoItem> for ListItem<'_> {
fn from(value: &TodoItem) -> Self {
let line = match value.status {
Status::Todo => Line::styled(format!("{}", value.todo), TEXT_FG_COLOR),
Status::Completed => {
Line::styled(format!("{}", value.todo), COMPLETED_TEXT_FG_COLOR)
}
};
fn item_from_task(task: &Task) -> ListItem<'_> {
let line = Line::styled(format!("{}", task.title), TEXT_FG_COLOR);
ListItem::new(line)
}
}

10
src/models.rs Normal file
View file

@ -0,0 +1,10 @@
use diesel::prelude::*;
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::tasks)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct Task {
pub id: i32,
pub title: String,
pub description: String,
}