use custom Task type
This commit is contained in:
parent
01566042b3
commit
5450884bb0
6 changed files with 79 additions and 100 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -214,6 +214,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dotenvy"
|
||||||
|
version = "0.15.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dsl_auto_type"
|
name = "dsl_auto_type"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
@ -737,6 +743,7 @@ dependencies = [
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"diesel",
|
"diesel",
|
||||||
|
"dotenvy",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -12,3 +12,4 @@ ratatui = "0.29.0"
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
|
|
||||||
diesel = { version = "2.2", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] }
|
diesel = { version = "2.2", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] }
|
||||||
|
dotenvy = "0.15"
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
rust-toolchain
|
rust-toolchain
|
||||||
cargo-generate
|
cargo-generate
|
||||||
diesel-cli
|
diesel-cli
|
||||||
|
sqlite
|
||||||
|
|
||||||
just
|
just
|
||||||
lazysql
|
lazysql
|
||||||
|
|
14
src/lib.rs
Normal file
14
src/lib.rs
Normal 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))
|
||||||
|
}
|
144
src/main.rs
144
src/main.rs
|
@ -1,4 +1,5 @@
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use diesel::prelude::*;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
DefaultTerminal,
|
DefaultTerminal,
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
|
@ -15,6 +16,8 @@ use ratatui::{
|
||||||
StatefulWidget, Widget, Wrap,
|
StatefulWidget, Widget, Wrap,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use todo::establish_connection;
|
||||||
|
use todo::models::*;
|
||||||
|
|
||||||
const TODO_HEADER_STYLE: Style = Style::new().fg(SLATE.c100).bg(BLUE.c800);
|
const TODO_HEADER_STYLE: Style = Style::new().fg(SLATE.c100).bg(BLUE.c800);
|
||||||
const NORMAL_ROW_BG: Color = SLATE.c950;
|
const NORMAL_ROW_BG: Color = SLATE.c950;
|
||||||
|
@ -26,91 +29,32 @@ const COMPLETED_TEXT_FG_COLOR: Color = GREEN.c500;
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
let terminal = ratatui::init();
|
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();
|
ratatui::restore();
|
||||||
app_result
|
app_result
|
||||||
}
|
}
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
should_exit: bool,
|
should_exit: bool,
|
||||||
todo_list: TodoList,
|
tasks: TaskList,
|
||||||
|
sqlite: SqliteConnection,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TodoList {
|
struct TaskList {
|
||||||
items: Vec<TodoItem>,
|
items: Vec<Task>,
|
||||||
state: ListState,
|
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 {
|
impl Default for App {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
should_exit: false,
|
should_exit: false,
|
||||||
todo_list: TodoList::from_iter([
|
tasks: TaskList {
|
||||||
(
|
items: Vec::new(),
|
||||||
Status::Todo,
|
state: ListState::default(),
|
||||||
"Rewrite everything with Rust!",
|
},
|
||||||
"I can't hold my inner voice. He tells me to rewrite the complete universe with Rust",
|
sqlite: establish_connection(),
|
||||||
),
|
|
||||||
(
|
|
||||||
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(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,6 +70,17 @@ impl App {
|
||||||
Ok(())
|
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) {
|
fn handle_key(&mut self, key: KeyEvent) {
|
||||||
if key.kind != KeyEventKind::Press {
|
if key.kind != KeyEventKind::Press {
|
||||||
return;
|
return;
|
||||||
|
@ -143,32 +98,32 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_none(&mut self) {
|
fn select_none(&mut self) {
|
||||||
self.todo_list.state.select(None);
|
self.tasks.state.select(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_next(&mut self) {
|
fn select_next(&mut self) {
|
||||||
self.todo_list.state.select_next();
|
self.tasks.state.select_next();
|
||||||
}
|
}
|
||||||
fn select_previous(&mut self) {
|
fn select_previous(&mut self) {
|
||||||
self.todo_list.state.select_previous();
|
self.tasks.state.select_previous();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_first(&mut self) {
|
fn select_first(&mut self) {
|
||||||
self.todo_list.state.select_first();
|
self.tasks.state.select_first();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_last(&mut self) {
|
fn select_last(&mut self) {
|
||||||
self.todo_list.state.select_last();
|
self.tasks.state.select_last();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes the status of the selected list item
|
/// Changes the status of the selected list item
|
||||||
fn toggle_status(&mut self) {
|
fn toggle_status(&mut self) {
|
||||||
if let Some(i) = self.todo_list.state.selected() {
|
// if let Some(i) = self.tasks.state.selected() {
|
||||||
self.todo_list.items[i].status = match self.todo_list.items[i].status {
|
// self.tasks.items[i].status = match self.tasks.items[i].status {
|
||||||
Status::Completed => Status::Todo,
|
// Status::Completed => Status::Todo,
|
||||||
Status::Todo => Status::Completed,
|
// Status::Todo => Status::Completed,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,13 +171,14 @@ impl App {
|
||||||
|
|
||||||
// Iterate through all elements in the `items` and stylize them.
|
// Iterate through all elements in the `items` and stylize them.
|
||||||
let items: Vec<ListItem> = self
|
let items: Vec<ListItem> = self
|
||||||
.todo_list
|
.tasks
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, todo_item)| {
|
.map(|(i, task)| {
|
||||||
let color = alternate_colors(i);
|
let color = alternate_colors(i);
|
||||||
ListItem::from(todo_item).bg(color)
|
let item = item_from_task(task);
|
||||||
|
item.bg(color)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -235,16 +191,13 @@ impl App {
|
||||||
|
|
||||||
// We need to disambiguate this trait method as both `Widget` and `StatefulWidget` share the
|
// We need to disambiguate this trait method as both `Widget` and `StatefulWidget` share the
|
||||||
// same method name `render`.
|
// 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) {
|
fn render_selected_item(&self, area: Rect, buf: &mut Buffer) {
|
||||||
// We get the info depending on the item's state.
|
// We get the info depending on the item's state.
|
||||||
let info = if let Some(i) = self.todo_list.state.selected() {
|
let info = if let Some(i) = self.tasks.state.selected() {
|
||||||
match self.todo_list.items[i].status {
|
format!("☐ TODO: {}", self.tasks.items[i].title)
|
||||||
Status::Completed => format!("✓ DONE: {}", self.todo_list.items[i].info),
|
|
||||||
Status::Todo => format!("☐ TODO: {}", self.todo_list.items[i].info),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
"Nothing selected...".to_string()
|
"Nothing selected...".to_string()
|
||||||
};
|
};
|
||||||
|
@ -275,14 +228,7 @@ const fn alternate_colors(i: usize) -> Color {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&TodoItem> for ListItem<'_> {
|
fn item_from_task(task: &Task) -> ListItem<'_> {
|
||||||
fn from(value: &TodoItem) -> Self {
|
let line = Line::styled(format!(" ☐ {}", task.title), TEXT_FG_COLOR);
|
||||||
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)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ListItem::new(line)
|
ListItem::new(line)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
10
src/models.rs
Normal file
10
src/models.rs
Normal 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,
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue