diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 159 | ||||
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | README.md | 25 | ||||
-rw-r--r-- | flake.lock | 18 | ||||
-rw-r--r-- | flake.nix | 3 | ||||
-rw-r--r-- | src/app.rs | 151 | ||||
-rw-r--r-- | src/db/migrations/1-init.sql | 9 | ||||
-rw-r--r-- | src/db/mod.rs | 36 | ||||
-rw-r--r-- | src/main.rs | 59 | ||||
-rw-r--r-- | src/model/event.rs | 45 | ||||
-rw-r--r-- | src/style.css | 10 |
12 files changed, 367 insertions, 154 deletions
@@ -1 +1,2 @@ target/ +database.db* @@ -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" @@ -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" @@ -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 @@ -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": { @@ -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 ]; }; } @@ -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: >k::Application, events: &Vec<Event>) { - load_style(); +fn build_ui(conn: Rc<Connection>, app: >k::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: >k::Application, tx: Sender<Msg>, events: &Vec<Event>) -> Self { + fn new(conn: Rc<Connection>, app: >k::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; +} |