aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2022-01-09 09:43:21 +0100
committerJoris2022-01-09 10:11:29 +0100
commitbd59a5128c05dcd550e91bbdd0cd9d5996a65586 (patch)
tree541f7d49253ad3e7c8dfab480f33a2b10107b0d2
parentce978143f1360e16e85587644055a9f83d11c64c (diff)
Persist events to sqlite db
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock159
-rw-r--r--Cargo.toml5
-rw-r--r--README.md25
-rw-r--r--flake.lock18
-rw-r--r--flake.nix3
-rw-r--r--src/app.rs151
-rw-r--r--src/db/migrations/1-init.sql9
-rw-r--r--src/db/mod.rs36
-rw-r--r--src/main.rs59
-rw-r--r--src/model/event.rs45
-rw-r--r--src/style.css10
12 files changed, 367 insertions, 154 deletions
diff --git a/.gitignore b/.gitignore
index 2f7896d..35faccf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
target/
+database.db*
diff --git a/Cargo.lock b/Cargo.lock
index a31031f..6a4cc74 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,17 @@
version = 3
[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
name = "anyhow"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -65,9 +76,12 @@ dependencies = [
name = "calendar"
version = "0.1.0"
dependencies = [
+ "anyhow",
"async-channel",
"chrono",
"gtk4",
+ "rusqlite",
+ "rusqlite_migration",
]
[[package]]
@@ -89,6 +103,12 @@ dependencies = [
]
[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -123,6 +143,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
[[package]]
+name = "fallible-iterator"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
+
+[[package]]
+name = "fallible-streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
+
+[[package]]
name = "field-offset"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -134,24 +166,24 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.17"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
+checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
-version = "0.3.17"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
+checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
[[package]]
name = "futures-executor"
-version = "0.3.17"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c"
+checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a"
dependencies = [
"futures-core",
"futures-task",
@@ -166,17 +198,16 @@ checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
[[package]]
name = "futures-task"
-version = "0.3.17"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
+checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72"
[[package]]
name = "futures-util"
-version = "0.3.17"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
+checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164"
dependencies = [
- "autocfg",
"futures-core",
"futures-task",
"pin-project-lite",
@@ -243,6 +274,17 @@ dependencies = [
]
[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
name = "gio"
version = "0.14.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -441,6 +483,24 @@ dependencies = [
]
[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "hashlink"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
+dependencies = [
+ "hashbrown",
+]
+
+[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -460,9 +520,34 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.107"
+version = "0.2.112"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
+
+[[package]]
+name = "libsqlite3-sys"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cafc7c74096c336d9d27145f7ebd4f4b6f95ba16aa5a282387267e6925cb58"
+dependencies = [
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
@@ -494,9 +579,9 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.8.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "pango"
@@ -586,23 +671,49 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.32"
+version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
+checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
-version = "1.0.10"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
dependencies = [
"proc-macro2",
]
[[package]]
+name = "rusqlite"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ba4d3462c8b2e4d7f4fcfcf2b296dc6b65404fbbc7b63daa37fd485c149daf7"
+dependencies = [
+ "bitflags",
+ "chrono",
+ "fallible-iterator",
+ "fallible-streaming-iterator",
+ "hashlink",
+ "libsqlite3-sys",
+ "memchr",
+ "smallvec",
+]
+
+[[package]]
+name = "rusqlite_migration"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5a5fa55374b33f1acdafffb85530a63223b15fbd7704dc1a5c30f17d90c200a"
+dependencies = [
+ "log",
+ "rusqlite",
+]
+
+[[package]]
name = "rustc_version"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -667,9 +778,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "1.0.81"
+version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
+checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
dependencies = [
"proc-macro2",
"quote",
@@ -766,6 +877,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
name = "version-compare"
version = "0.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 277ac45..07c2e20 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,9 @@ authors = ["Joris Guyonvarch"]
edition = "2018"
[dependencies]
+anyhow = "1.0"
+async-channel = "1.6"
chrono = "0.4"
gtk4 = { version = "0.3", features = ["v4_2"] }
-async-channel = "1.6"
+rusqlite = { version = "0.26", features = [ "chrono" ] }
+rusqlite_migration = "0.5"
diff --git a/README.md b/README.md
index 3eb537b..2ec9e8d 100644
--- a/README.md
+++ b/README.md
@@ -14,17 +14,14 @@ nix develop --command cargo run
## CRUD
-1. Complete dialog form.
-2. Save to DB
-3. Read events from DB on startup.
-4. Modify an event when double clicking.
-5. Delete an event (Right click > Delete).
+1. Modify an event with double click.
+2. Delete an event with right click.
-## Complex event
+## Complexify event
Be able to specify repetition.
-1. Modelize an event.
+1. Modelize repetition.
2. Update the form.
3. Update the view.
4. Update a repetition event.
@@ -32,15 +29,15 @@ Be able to specify repetition.
## API
1. Give DB path with CLI arg.
-2. Get list of today events.
+2. Get list of today’s events.
-## Calendar focus
+## Navigate around
1. Select previous week (up arrow, scrolling).
2. Select Next week (down arrow, scrolling).
3. Select the default focus.
-## Type
+## Categorize events
1. CRUD for list of types (name + color).
2. Show / hide depending on the type.
@@ -49,5 +46,9 @@ Be able to specify repetition.
- Drag & drop events.
- Show an indicator when a day can be scrolled vertically.
-- Multi day events
-- Prevent to launch multiple instances
+- Multi day events.
+- Prevent to launch multiple instances.
+- Show a date picker in dialog form.
+- Apply a style on times in the calendar (bold ?).
+- Print errors on forms when validating.
+- Validate the form when pressing enter on any field
diff --git a/flake.lock b/flake.lock
index 3a7077c..211bffc 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"flake-utils": {
"locked": {
- "lastModified": 1637014545,
- "narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=",
+ "lastModified": 1638122382,
+ "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=",
"owner": "numtide",
"repo": "flake-utils",
- "rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4",
+ "rev": "74f7e4319258e287b0f9cb95426c9853b282730b",
"type": "github"
},
"original": {
@@ -17,11 +17,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1637427387,
- "narHash": "sha256-Ity623Tj1mekcvT2Qyh/j4XB3HNhPdvt2LGZLdFjL3I=",
+ "lastModified": 1641710945,
+ "narHash": "sha256-hPCSOq9IcWi+ALWKNbKCeuq7ozthppKDE+VXbOkOggM=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "be2bc44d0e12d0fbb5f89fe410fa6c3baa64ef3c",
+ "rev": "e8daaa85d484e132ffbeac78651a2e6e56b9f8fb",
"type": "github"
},
"original": {
@@ -47,11 +47,11 @@
]
},
"locked": {
- "lastModified": 1637374599,
- "narHash": "sha256-mIzHDYJmUMPO5TLJuYEfZ0suCIAbEEJ0SfaGRuPruLE=",
+ "lastModified": 1641696140,
+ "narHash": "sha256-Q7bQ0MSq201ah4Q+3SznEmMR4Kn9pY6ta8pL6KAjZ78=",
"owner": "oxalica",
"repo": "rust-overlay",
- "rev": "2f57158fe403075e918fb05a689313b227883be8",
+ "rev": "a1b1977429de5d69a332dd87700ffb00525335f9",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index b146d72..e6561a2 100644
--- a/flake.nix
+++ b/flake.nix
@@ -19,11 +19,12 @@
{
devShell = mkShell {
buildInputs = [
- rust-bin.stable."1.56.1".default
+ rust-bin.stable."1.57.0".default
cargo-watch
pkgconfig
graphene
gtk4
+ sqlite
];
};
}
diff --git a/src/app.rs b/src/app.rs
index 5717c12..0eb2b1e 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,15 +1,18 @@
use gtk4 as gtk;
use async_channel::{Receiver, Sender};
-use chrono::{Datelike, NaiveDate, Weekday};
+use chrono::{Datelike, NaiveDate, NaiveTime, Weekday};
use gtk::gdk::Display;
-use gtk::glib;
use gtk::glib::signal::Inhibit;
+use gtk::glib;
use gtk::prelude::*;
+use rusqlite::Connection;
use std::future::Future;
use std::rc::Rc;
-use crate::model::event::{Event, Time};
+use crate::model::event;
+use crate::model::event::Event;
+use crate::db;
/// Spawns a task on the default executor, without waiting for it to complete
pub fn spawn<F>(future: F)
@@ -35,24 +38,25 @@ static MONTHES: [&str; 12] = [
"Jan", "Fév", "Mar", "Avr", "Mai", "Juin", "Juil", "Aoû", "Sep", "Oct", "Nov", "Déc",
];
-pub fn run(events: Vec<Event>) {
- let application = gtk::Application::new(Some("me.guyonvarch.calendar"), Default::default());
- application.connect_startup(move |app| build_ui(app, &events));
- application.run();
+pub fn run(conn: Connection) {
+ let conn = Rc::new(conn);
+ let app = gtk::Application::new(Some("me.guyonvarch.calendar"), Default::default());
+ app.connect_startup(|_| load_style());
+ app.connect_activate(move |app| build_ui(conn.clone(), app));
+ app.run();
}
-fn build_ui(app: &gtk::Application, events: &Vec<Event>) {
- load_style();
+fn build_ui(conn: Rc<Connection>, app: &gtk::Application) {
let (tx, rx) = async_channel::unbounded();
- let app = App::new(app, tx.clone(), &events);
- spawn(event_handler(rx, tx, app))
+ let app = App::new(conn.clone(), app, tx.clone());
+ spawn(event_handler(conn, rx, tx, app))
}
-async fn event_handler(rx: Receiver<Msg>, tx: Sender<Msg>, mut app: App) {
+async fn event_handler(conn: Rc<Connection>, rx: Receiver<Msg>, tx: Sender<Msg>, mut app: App) {
while let Ok(msg) = rx.recv().await {
match msg {
Msg::ShowAddForm { date } => {
- spawn(add_event_dialog(tx.clone(), Rc::clone(&app.window), date));
+ add_event_dialog(Rc::clone(&conn), tx.clone(), Rc::clone(&app.window), date).await;
}
Msg::AddEvent { event } => {
let date = event.date.clone();
@@ -85,7 +89,7 @@ struct App {
}
impl App {
- fn new(app: &gtk::Application, tx: Sender<Msg>, events: &Vec<Event>) -> Self {
+ fn new(conn: Rc<Connection>, app: &gtk::Application, tx: Sender<Msg>) -> Self {
let window = Rc::new(
gtk::ApplicationWindow::builder()
.application(app)
@@ -107,6 +111,8 @@ impl App {
let start_date =
NaiveDate::from_isoywd(today.year(), today.iso_week().week(), Weekday::Mon);
+
+ let events = db::list(&conn).unwrap_or(vec!());
show_days(tx, &grid, &start_date, &today, &events);
window.connect_close_request(move |window| {
@@ -196,7 +202,7 @@ fn day_entry(
.iter()
.filter(|e| e.date == *date)
.collect::<Vec<&Event>>();
- events.sort_by_key(|e| e.time);
+ events.sort_by_key(|e| e.start);
if !events.is_empty() {
vbox.append(&day_events(events));
@@ -263,34 +269,103 @@ fn day_events(events: Vec<&Event>) -> gtk::Box {
vbox
}
-async fn add_event_dialog(tx: Sender<Msg>, window: Rc<gtk::ApplicationWindow>, date: NaiveDate) {
+static DATE_FORMAT: &str = "%d/%m/%Y";
+
+async fn add_event_dialog(conn: Rc<Connection>, tx: Sender<Msg>, window: Rc<gtk::ApplicationWindow>, date: NaiveDate) {
let dialog = gtk::Dialog::builder()
.transient_for(&*window)
.modal(true)
- .title("Ajouter un évènement")
+ .title("Ajouter")
+ .css_classes(vec!["g-Form".to_string()])
.build();
let content_area = dialog.content_area();
- let label = gtk::Label::builder().label(&format!("{:?}", date)).build();
- content_area.append(&label);
-
- let entry = gtk::Entry::builder().build();
- content_area.append(&entry);
-
- dialog.add_buttons(&[
- ("Annuler", gtk::ResponseType::Cancel),
- ("Créer", gtk::ResponseType::Ok),
- ]);
-
- let answer = dialog.run_future().await;
- if answer == gtk::ResponseType::Ok {
- let event = Event {
- date,
- time: Time::AllDay,
- name: entry.buffer().text(),
- };
-
- send_message(tx, Msg::AddEvent { event: event });
+
+ let vbox = gtk::Box::builder()
+ .orientation(gtk::Orientation::Vertical)
+ .build();
+ vbox.add_css_class("g-Form__Inputs");
+ content_area.append(&vbox);
+
+ let name = entry("");
+ vbox.append(&label("Événement"));
+ vbox.append(&name);
+
+ let date = entry(&date.format(DATE_FORMAT).to_string());
+ vbox.append(&label("Jour"));
+ vbox.append(&date);
+
+ let start = entry("");
+ vbox.append(&label("Début"));
+ vbox.append(&start);
+
+ let end = entry("");
+ vbox.append(&label("Fin"));
+ vbox.append(&end);
+
+ let button = gtk::Button::with_label("Créer");
+ vbox.append(&button);
+ button.connect_clicked(glib::clone!(@weak dialog => move |_| {
+ match validate_event(date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text()) {
+ Some(event) => {
+ match db::insert(&conn, &event) {
+ Ok(_) => {
+ send_message(tx.clone(), Msg::AddEvent { event: event });
+ dialog.close()
+ },
+ Err(_) => ()
+ }
+ },
+ None => ()
+ }
+ }));
+
+ dialog.run_future().await;
+}
+
+fn entry(text: &str) -> gtk::Entry {
+ gtk::Entry::builder().text(text).margin_bottom(10).build()
+}
+
+fn label(text: &str) -> gtk::Label {
+ gtk::Label::builder()
+ .label(text)
+ .halign(gtk::Align::Start)
+ .margin_bottom(5)
+ .build()
+}
+
+fn validate_event(date: String, name: String, start: String, end: String) -> Option<Event> {
+ let start = validate_time(start)?;
+ let end = validate_time(end)?;
+
+ match (start, end) {
+ (Some(s), Some(e)) if s > e => None?,
+ _ => (),
+ }
+
+ Some(Event {
+ date: NaiveDate::parse_from_str(&date, DATE_FORMAT).ok()?,
+ name: validate_name(name)?,
+ start,
+ end,
+ })
+}
+
+fn validate_time(time: String) -> Option<Option<NaiveTime>> {
+ let time = time.trim();
+ if time.is_empty() {
+ Some(None)
+ } else {
+ event::parse_time(time).map(|t| Some(t))
+ }
+}
+
+fn validate_name(name: String) -> Option<String> {
+ let name = name.trim();
+ if name.is_empty() {
+ None
+ } else {
+ Some(name.to_string())
}
- dialog.close();
}
diff --git a/src/db/migrations/1-init.sql b/src/db/migrations/1-init.sql
new file mode 100644
index 0000000..72fab80
--- /dev/null
+++ b/src/db/migrations/1-init.sql
@@ -0,0 +1,9 @@
+CREATE TABLE IF NOT EXISTS "events" (
+ "id" INTEGER PRIMARY KEY,
+ "date" VARCHAR NOT NULL,
+ "start" VARCHAR NULL,
+ "end" VARCHAR NULL,
+ "name" VARCHAR NOT NULL,
+ "created" TIMESTAMP NOT NULL,
+ "updated" TIMESTAMP NOT NULL
+);
diff --git a/src/db/mod.rs b/src/db/mod.rs
new file mode 100644
index 0000000..3348673
--- /dev/null
+++ b/src/db/mod.rs
@@ -0,0 +1,36 @@
+use anyhow::Result;
+use rusqlite::{params, Connection};
+use rusqlite_migration::{Migrations, M};
+
+use crate::model::event::Event;
+
+pub fn init() -> Result<Connection> {
+ let mut conn = Connection::open("database.db")?;
+ let migrations = Migrations::new(vec![M::up(include_str!("migrations/1-init.sql"))]);
+ migrations.to_latest(&mut conn)?;
+ Ok(conn)
+}
+
+pub fn insert(conn: &Connection, event: &Event) -> Result<()> {
+ conn.execute(
+ "INSERT INTO events (date, start, end, name, created, updated) VALUES (?, ?, ?, ?, datetime(), datetime())",
+ params![event.date, event.start, event.end, event.name]
+ )?;
+
+ Ok(())
+}
+
+pub fn list(conn: &Connection) -> Result<Vec<Event>> {
+ let mut stmt = conn.prepare("SELECT date, start, end, name FROM events")?;
+
+ let iter = stmt.query_map([], |row|
+ Ok(Event {
+ date: row.get(0)?,
+ start: row.get(1)?,
+ end: row.get(2)?,
+ name: row.get(3)?,
+ })
+ )?;
+
+ Ok(iter.map(|r| r.unwrap()).collect())
+}
diff --git a/src/main.rs b/src/main.rs
index 9dbed82..f30e38e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,59 +1,10 @@
mod app;
mod model;
+mod db;
-use chrono::{NaiveDate, NaiveTime};
+use anyhow::Result;
-use crate::model::event::{Event, Time};
-
-fn main() {
- let events = test_events();
- app::run(events)
-}
-
-fn test_events() -> Vec<Event> {
- [
- Event {
- date: NaiveDate::from_ymd(2021, 11, 29),
- time: Time::AllDay,
- name: "Début de la semaine".to_string(),
- },
- Event {
- date: NaiveDate::from_ymd(2021, 12, 4),
- time: Time::AllDay,
- name: "Fin de la semaine".to_string(),
- },
- Event {
- date: NaiveDate::from_ymd(2021, 12, 4),
- time: Time::Time {
- start: NaiveTime::from_hms(15, 0, 0),
- end: Some(NaiveTime::from_hms(15, 30, 0)),
- },
- name: "Appel".to_string(),
- },
- Event {
- date: NaiveDate::from_ymd(2021, 12, 4),
- time: Time::Time {
- start: NaiveTime::from_hms(12, 0, 0),
- end: Some(NaiveTime::from_hms(14, 0, 0)),
- },
- name: "Repas".to_string(),
- },
- Event {
- date: NaiveDate::from_ymd(2021, 12, 4),
- time: Time::Time {
- start: NaiveTime::from_hms(8, 0, 0),
- end: None,
- },
- name: "Promener le chien".to_string(),
- },
- Event {
- date: NaiveDate::from_ymd(2021, 12, 4),
- time: Time::Time {
- start: NaiveTime::from_hms(9, 0, 0),
- end: None,
- },
- name: "Thé".to_string(),
- },
- ]
- .to_vec()
+fn main() -> Result<()> {
+ let conn = db::init()?;
+ Ok(app::run(conn))
}
diff --git a/src/model/event.rs b/src/model/event.rs
index d1d9775..2650c47 100644
--- a/src/model/event.rs
+++ b/src/model/event.rs
@@ -1,35 +1,31 @@
use chrono::Timelike;
use chrono::{NaiveDate, NaiveTime};
+// #[derive(Debug, Clone, sqlx::FromRow)]
#[derive(Debug, Clone)]
pub struct Event {
pub date: NaiveDate,
- pub time: Time,
+ pub start: Option<NaiveTime>,
+ pub end: Option<NaiveTime>,
pub name: String,
}
impl Event {
pub fn pprint(&self) -> String {
- match self.time {
- Time::AllDay => self.name.clone(),
- Time::Time { start, end: None } => format!("{} {}", pprint_time(start), self.name),
- Time::Time {
- start,
- end: Some(e),
- } => format!("{}-{} {}", pprint_time(start), pprint_time(e), self.name),
- }
+ let start = self.start.map(pprint_time).unwrap_or_default();
+ let end = self
+ .end
+ .map(|t| format!("-{}", pprint_time(t)))
+ .unwrap_or_default();
+ let space = if self.start.is_some() || self.end.is_some() {
+ " "
+ } else {
+ ""
+ };
+ format!("{}{}{}{}", start, end, space, self.name)
}
}
-#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Eq, Ord)]
-pub enum Time {
- AllDay,
- Time {
- start: NaiveTime,
- end: Option<NaiveTime>,
- },
-}
-
fn pprint_time(t: NaiveTime) -> String {
if t.minute() == 0 {
format!("{}h", t.hour())
@@ -37,3 +33,16 @@ fn pprint_time(t: NaiveTime) -> String {
format!("{}h{}", t.hour(), t.minute())
}
}
+
+pub fn parse_time(t: &str) -> Option<NaiveTime> {
+ match t.split('h').collect::<Vec<&str>>()[..] {
+ [hours, minutes] => {
+ if minutes.trim().is_empty() {
+ NaiveTime::from_hms_opt(hours.parse().ok()?, 0, 0)
+ } else {
+ NaiveTime::from_hms_opt(hours.parse().ok()?, minutes.parse().ok()?, 0)
+ }
+ }
+ _ => None,
+ }
+}
diff --git a/src/style.css b/src/style.css
index 0a8292f..5cd1394 100644
--- a/src/style.css
+++ b/src/style.css
@@ -33,3 +33,13 @@
.g-Calendar__DayEvent:hover {
background-color: pink;
}
+
+.g-Form {
+ background-color: white;
+ color: black;
+ padding: 10px;
+}
+
+.g-Form__Input {
+ text-align: left;
+}