diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/cli/mod.rs | 4 | ||||
| -rw-r--r-- | src/db/categories.rs | 25 | ||||
| -rw-r--r-- | src/db/event-color.rs | 7 | ||||
| -rw-r--r-- | src/db/event_color.rs | 14 | ||||
| -rw-r--r-- | src/db/events.rs | 139 | ||||
| -rw-r--r-- | src/db/migrations/2-categories.sql | 10 | ||||
| -rw-r--r-- | src/db/migrations/3-event-color.sql | 5 | ||||
| -rw-r--r-- | src/db/mod.rs | 137 | ||||
| -rw-r--r-- | src/gui/app.rs | 40 | ||||
| -rw-r--r-- | src/gui/calendar.rs | 56 | ||||
| -rw-r--r-- | src/gui/form/mod.rs | 54 | ||||
| -rw-r--r-- | src/gui/style.css | 12 | ||||
| -rw-r--r-- | src/gui/update.rs | 22 | ||||
| -rw-r--r-- | src/model/category.rs | 8 | ||||
| -rw-r--r-- | src/model/event.rs | 3 | ||||
| -rw-r--r-- | src/model/mod.rs | 1 | 
16 files changed, 349 insertions, 188 deletions
| diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 81c5895..6ce50af 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -6,8 +6,8 @@ use crate::{db, model::event};  pub fn today(conn: &Connection) -> Result<String> {      let today = Local::today().naive_local(); -    let mut events = db::list_non_recurring_between(conn, today, today)?; -    let recurring_events = db::list_recurring(conn)?; +    let mut events = db::events::list_non_recurring_between(conn, today, today)?; +    let recurring_events = db::events::list_recurring(conn)?;      let repetitions = event::repetitions_between(&recurring_events, today, today);      for repetition in repetitions.values().flatten() {          events.push(repetition.clone()); diff --git a/src/db/categories.rs b/src/db/categories.rs new file mode 100644 index 0000000..ebefb6d --- /dev/null +++ b/src/db/categories.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use rusqlite::Connection; +use uuid::Uuid; + +use crate::model::category::Category; + +pub fn list(conn: &Connection) -> Result<Vec<Category>> { +    let mut stmt = conn.prepare("SELECT id, name, color FROM categories")?; + +    let iter = stmt.query_map([], |row| { +        Ok(read_category(row.get(0)?, row.get(1)?, row.get(2)?)) +    })?; + +    let mut res = vec![]; +    for category in iter { +        res.push(category??) +    } +    Ok(res) +} + +fn read_category(id: String, name: String, color: String) -> Result<Category> { +    let id = Uuid::parse_str(&id)?; + +    Ok(Category { id, name, color }) +} diff --git a/src/db/event-color.rs b/src/db/event-color.rs new file mode 100644 index 0000000..18612e4 --- /dev/null +++ b/src/db/event-color.rs @@ -0,0 +1,7 @@ + + +pub fn get_default_color(conn: &Connection) -> Result<String> { +} + +// pub fn set_default_color(conon: &Connection, color: &str) -> Result<()> { +// } diff --git a/src/db/event_color.rs b/src/db/event_color.rs new file mode 100644 index 0000000..33d350b --- /dev/null +++ b/src/db/event_color.rs @@ -0,0 +1,14 @@ +use anyhow::Result; +use rusqlite::Connection; + +pub fn get_default_color(conn: &Connection) -> Result<String> { +    let mut stmt = conn.prepare("SELECT * FROM event_color LIMIT 1")?; + +    let iter = stmt.query_map([], |row| row.get(0))?; + +    let mut res = vec![]; +    for color in iter { +        res.push(color?) +    } +    Ok(res.first().unwrap_or(&"blue".to_string()).clone()) +} diff --git a/src/db/events.rs b/src/db/events.rs new file mode 100644 index 0000000..86206bb --- /dev/null +++ b/src/db/events.rs @@ -0,0 +1,139 @@ +use anyhow::Result; +use chrono::{NaiveDate, NaiveTime}; +use rusqlite::{params, Connection}; +use uuid::Uuid; + +use crate::model::event::Event; + +pub fn insert(conn: &Connection, event: &Event) -> Result<()> { +    let repetition = match &event.repetition { +        Some(r) => Some(serde_json::to_string(&r)?), +        None => None, +    }; + +    let category = event.category.map(|id| id.to_hyphenated().to_string()); + +    conn.execute( +        "INSERT INTO events (id, date, start, end, name, repetition, category, created, updated) VALUES (?, ?, ?, ?, ?, ?, ?, datetime(), datetime())", +        params![event.id.to_hyphenated().to_string(), event.date, event.start, event.end, event.name, repetition, category] +    )?; + +    Ok(()) +} + +pub fn update(conn: &Connection, event: &Event) -> Result<()> { +    let repetition = match &event.repetition { +        Some(r) => Some(serde_json::to_string(&r)?), +        None => None, +    }; + +    let category = event.category.map(|id| id.to_hyphenated().to_string()); + +    conn.execute( +        "UPDATE events SET date = ?, start = ?, end = ?, name = ?, repetition = ?, category = ?, updated = datetime() WHERE id = ?", +        params![event.date, event.start, event.end, event.name, repetition, category, event.id.to_hyphenated().to_string()] +    )?; + +    Ok(()) +} + +pub fn delete(conn: &Connection, id: &Uuid) -> Result<()> { +    conn.execute( +        "DELETE FROM events WHERE id = ?", +        params![id.to_hyphenated().to_string()], +    )?; + +    Ok(()) +} + +pub fn list_recurring(conn: &Connection) -> Result<Vec<Event>> { +    let mut stmt = conn.prepare( +        " +        SELECT id, date, start, end, name, repetition, category +        FROM events +        WHERE repetition IS NOT NULL", +    )?; + +    let iter = stmt.query_map([], |row| { +        Ok(read_event( +            row.get(0)?, +            row.get(1)?, +            row.get(2)?, +            row.get(3)?, +            row.get(4)?, +            row.get(5)?, +            row.get(6)?, +        )) +    })?; + +    let mut res = vec![]; +    for event in iter { +        res.push(event??) +    } +    Ok(res) +} + +pub fn list_non_recurring_between( +    conn: &Connection, +    start: NaiveDate, +    end: NaiveDate, +) -> Result<Vec<Event>> { +    let mut stmt = conn.prepare( +        " +        SELECT id, date, start, end, name, category +        FROM events +        WHERE  +            repetition IS NULL  +            AND date >= ? +            AND date <= ? +    ", +    )?; + +    let iter = stmt.query_map([start, end], |row| { +        Ok(read_event( +            row.get(0)?, +            row.get(1)?, +            row.get(2)?, +            row.get(3)?, +            row.get(4)?, +            None, +            row.get(5)?, +        )) +    })?; + +    let mut res = vec![]; +    for event in iter { +        res.push(event??) +    } +    Ok(res) +} + +fn read_event( +    id: String, +    date: NaiveDate, +    start: Option<NaiveTime>, +    end: Option<NaiveTime>, +    name: String, +    repetition: Option<String>, +    category: Option<String>, +) -> Result<Event> { +    let id = Uuid::parse_str(&id)?; +    let repetition = match repetition { +        Some(r) => Some(serde_json::from_str(&r)?), +        None => None, +    }; +    let category = match category { +        Some(c) => Some(Uuid::parse_str(&c)?), +        None => None, +    }; + +    Ok(Event { +        id, +        date, +        start, +        end, +        name, +        repetition, +        category, +    }) +} diff --git a/src/db/migrations/2-categories.sql b/src/db/migrations/2-categories.sql index 27e9699..0b373d0 100644 --- a/src/db/migrations/2-categories.sql +++ b/src/db/migrations/2-categories.sql @@ -1,13 +1,11 @@  CREATE TABLE IF NOT EXISTS "categories" (    "id" TEXT PRIMARY KEY, /* UUID */ -  "name" TEXT NOT NULL, -  "color" TEXT NOT NULL, /* HEXA */ +  "name" TEXT NOT NULL UNIQUE, +  "color" TEXT NOT NULL, /* COLOR */    "created" TEXT NOT NULL, /* DATETIME */    "updated" TEXT NOT NULL /* DATETIME */  ); -INSERT INTO  -  "categories" ("id", "name", "color", "created", "updated")  -  VALUES ("b2253284-1572-43d5-96ec-bf6ad448f46a", "Perso", "#a8c2e0", datetime(), datetime()); -  ALTER TABLE "events" ADD COLUMN "category" TEXT REFERENCES "categories" ("id"); /* UUID */ + +PRAGMA foreign_keys = ON diff --git a/src/db/migrations/3-event-color.sql b/src/db/migrations/3-event-color.sql new file mode 100644 index 0000000..ec589ea --- /dev/null +++ b/src/db/migrations/3-event-color.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS "event_color" ( +  "color" TEXT NOT NULL /* COLOR */ +); + +INSERT INTO "event_color" ("color") VALUES ("#a8c2e0"); diff --git a/src/db/mod.rs b/src/db/mod.rs index 101c874..20e7f81 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,143 +1,18 @@ +pub mod categories; +pub mod event_color; +pub mod events; +  use anyhow::Result; -use chrono::{NaiveDate, NaiveTime}; -use rusqlite::{params, Connection}; +use rusqlite::Connection;  use rusqlite_migration::{Migrations, M}; -use uuid::Uuid; - -use crate::model::event::Event;  pub fn init(db_path: &str) -> Result<Connection> {      let mut conn = Connection::open(db_path)?;      let migrations = Migrations::new(vec![          M::up(include_str!("migrations/1-init.sql")),          M::up(include_str!("migrations/2-categories.sql")), +        M::up(include_str!("migrations/3-event-color.sql")),      ]);      migrations.to_latest(&mut conn)?;      Ok(conn)  } - -pub fn insert(conn: &Connection, event: &Event) -> Result<()> { -    let repetition = match &event.repetition { -        Some(r) => Some(serde_json::to_string(&r)?), -        None => None, -    }; - -    conn.execute( -        "INSERT INTO events (id, date, start, end, name, repetition, created, updated) VALUES (?, ?, ?, ?, ?, ?, datetime(), datetime())", -        params![event.id.to_hyphenated().to_string(), event.date, event.start, event.end, event.name, repetition] -    )?; - -    Ok(()) -} - -pub fn update(conn: &Connection, event: &Event) -> Result<()> { -    let repetition = match &event.repetition { -        Some(r) => Some(serde_json::to_string(&r)?), -        None => None, -    }; - -    conn.execute( -        "UPDATE events SET date = ?, start = ?, end = ?, name = ?, repetition = ?, updated = datetime() WHERE id = ?", -        params![event.date, event.start, event.end, event.name, repetition, event.id.to_hyphenated().to_string()] -    )?; - -    Ok(()) -} - -pub fn delete(conn: &Connection, id: &Uuid) -> Result<()> { -    conn.execute( -        "DELETE FROM events WHERE id = ?", -        params![id.to_hyphenated().to_string()], -    )?; - -    Ok(()) -} - -pub fn list_recurring(conn: &Connection) -> Result<Vec<Event>> { -    let mut stmt = conn.prepare( -        " -        SELECT id, date, start, end, name, repetition -        FROM events -        WHERE repetition IS NOT NULL", -    )?; - -    let iter = stmt.query_map([], |row| { -        Ok(read_recurring_event( -            row.get(0)?, -            row.get(1)?, -            row.get(2)?, -            row.get(3)?, -            row.get(4)?, -            row.get(5)?, -        )) -    })?; - -    let mut res = vec![]; -    for event in iter { -        res.push(event??) -    } -    Ok(res) -} - -fn read_recurring_event( -    uuid: String, -    date: NaiveDate, -    start: Option<NaiveTime>, -    end: Option<NaiveTime>, -    name: String, -    repetition: Option<String>, -) -> Result<Event> { -    let id = Uuid::parse_str(&uuid)?; -    let repetition = match repetition { -        Some(r) => Some(serde_json::from_str(&r)?), -        None => None, -    }; - -    Ok(Event { -        id, -        date, -        start, -        end, -        name, -        repetition, -    }) -} - -pub fn list_non_recurring_between( -    conn: &Connection, -    start: NaiveDate, -    end: NaiveDate, -) -> Result<Vec<Event>> { -    let mut stmt = conn.prepare( -        " -        SELECT id, date, start, end, name  -        FROM events -        WHERE  -            repetition IS NULL  -            AND date >= ? -            AND date <= ? -    ", -    )?; - -    let iter = stmt.query_map([start, end], |row| { -        let uuid: String = row.get(0)?; -        let date = row.get(1)?; -        let start = row.get(2)?; -        let end = row.get(3)?; -        let name = row.get(4)?; -        Ok(Uuid::parse_str(&uuid).map(|id| Event { -            id, -            date, -            start, -            end, -            name, -            repetition: None, -        })) -    })?; - -    let mut res = vec![]; -    for event in iter { -        res.push(event??) -    } -    Ok(res) -} diff --git a/src/gui/app.rs b/src/gui/app.rs index 8cd7096..4ed864b 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -10,18 +10,23 @@ use std::rc::Rc;  use crate::gui::calendar;  use crate::gui::update::Msg; -use crate::{db, model::event::Event}; +use crate::{db, model::category::Category, model::event::Event};  pub struct App {      pub conn: Rc<Connection>,      pub window: Rc<gtk::ApplicationWindow>, -    pub grid: gtk::Grid, +    pub tx: Sender<Msg>, +    // Calendar +    pub calendar: gtk::Grid,      pub events: Vec<Event>, // TODO: use Hashmap to have fast access to events by id ?      pub recurring_events: Vec<Event>, // TODO: use Hashmap to have fast access to events by id ?      pub today: NaiveDate,      pub start_date: NaiveDate,      pub end_date: NaiveDate, -    pub tx: Sender<Msg>, +    // Categories +    // pub categories: gtk::Box, +    pub categories: Vec<Category>, +    pub default_color: String,  }  impl App { @@ -29,7 +34,7 @@ impl App {          let window = Rc::new(              gtk::ApplicationWindow::builder()                  .application(app) -                .title("Calendar") +                .title("Calendrier")                  .default_width(800)                  .default_height(600)                  .visible(true) @@ -41,19 +46,32 @@ impl App {              NaiveDate::from_isoywd(today.year(), today.iso_week().week(), Weekday::Mon);          let end_date = start_date + Duration::days(7 * 4 - 1); -        let events = db::list_non_recurring_between(&conn, start_date, end_date)?; -        let recurring_events = db::list_recurring(&conn)?; +        let events = db::events::list_non_recurring_between(&conn, start_date, end_date)?; +        let recurring_events = db::events::list_recurring(&conn)?; +        let categories = db::categories::list(&conn)?; +        let default_color = db::event_color::get_default_color(&conn)?; -        let grid = calendar::create( +        let calendar = calendar::create(              tx.clone(),              today,              start_date,              end_date,              &events,              &recurring_events, +            &categories, +            &default_color,          ); -        window.set_child(Some(&grid)); +        // let categories = gtk::Box::builder() +        //     .orientation(gtk::Orientation::Vertical) +        //     .build(); + +        // let notebook = gtk::Notebook::builder().build(); +        // notebook.append_page(&calendar, Some(>k::Label::new(Some("Calendrier")))); +        // notebook.append_page(&categories, Some(>k::Label::new(Some("Catégories")))); +        // window.set_child(Some(¬ebook)); + +        window.set_child(Some(&calendar));          window.connect_close_request(move |window| {              if let Some(application) = window.application() { @@ -65,13 +83,15 @@ impl App {          Ok(Self {              conn,              window, -            grid, +            tx, +            calendar,              events,              recurring_events,              today,              start_date,              end_date, -            tx, +            categories, +            default_color,          })      }  } diff --git a/src/gui/calendar.rs b/src/gui/calendar.rs index 547c087..f5f9c10 100644 --- a/src/gui/calendar.rs +++ b/src/gui/calendar.rs @@ -6,7 +6,7 @@ use gtk::glib;  use gtk::prelude::*;  use std::collections::HashMap; -use crate::{gui::update, gui::update::Msg, gui::App, model::event, model::event::Event}; +use crate::{gui::update, gui::update::Msg, gui::App, model::event, model::event::Event, model::category::Category};  static DAYS: [&str; 7] = ["LUN", "MAR", "MER", "JEU", "VEN", "SAM", "DIM"];  static MONTHES: [&str; 12] = [ @@ -20,6 +20,8 @@ pub fn create(      end_date: NaiveDate,      events: &[Event],      recurring_events: &[Event], +    categories: &[Category], +    default_color: &str,  ) -> gtk::Grid {      let grid = gtk::Grid::builder().build(); @@ -28,7 +30,7 @@ pub fn create(      }      let repetitions = event::repetitions_between(recurring_events, start_date, end_date); -    attach_days(tx.clone(), &grid, start_date, today, events, &repetitions); +    attach_days(tx.clone(), &grid, start_date, today, events, &repetitions, categories, default_color);      let event_controller_key = gtk::EventControllerKey::new();      event_controller_key.connect_key_released(glib::clone!(@strong tx => move |_, _, keycode, _| { @@ -50,12 +52,14 @@ fn attach_days(      today: NaiveDate,      events: &[Event],      repetitions: &HashMap<NaiveDate, Vec<Event>>, +    categories: &[Category], +    default_color: &str,  ) {      let mut d = start_date;      for row in 1..5 {          for col in 0..7 {              grid.attach( -                &day_entry(tx.clone(), d, today, events, repetitions), +                &day_entry(tx.clone(), d, today, events, repetitions, categories, default_color),                  col,                  row,                  1, @@ -66,14 +70,21 @@ fn attach_days(      }  } -pub fn refresh_date(app: &App, date: NaiveDate, repetitions: &HashMap<NaiveDate, Vec<Event>>) { +pub fn refresh_date( +    app: &App,  +    date: NaiveDate,  +    repetitions: &HashMap<NaiveDate,  +    Vec<Event>>,  +    categories: &[Category], +    default_color: &str +) {      let d = date.signed_duration_since(app.start_date).num_days();      let col = (d % 7) as i32;      let row = 1 + (d / 7) as i32; -    app.grid.attach( -        &day_entry(app.tx.clone(), date, app.today, &app.events, repetitions), +    app.calendar.attach( +        &day_entry(app.tx.clone(), date, app.today, &app.events, repetitions, categories, default_color),          col,          row,          1, @@ -101,6 +112,8 @@ pub fn day_entry(      today: NaiveDate,      events: &[Event],      repetitions: &HashMap<NaiveDate, Vec<Event>>, +    categories: &[Category], +    default_color: &str,  ) -> gtk::ScrolledWindow {      let vbox = gtk::Box::builder()          .orientation(gtk::Orientation::Vertical) @@ -131,7 +144,7 @@ pub fn day_entry(      events.sort_by_key(|e| e.start);      if !events.is_empty() { -        vbox.append(&day_events(date, tx, events)); +        vbox.append(&day_events(date, tx, events, categories, default_color));      }      gtk::ScrolledWindow::builder() @@ -164,7 +177,13 @@ fn day_label(today: NaiveDate, date: NaiveDate) -> gtk::Label {      label  } -fn day_events(date: NaiveDate, tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box { +fn day_events( +    date: NaiveDate,  +    tx: Sender<Msg>,  +    events: Vec<&Event>,  +    categories: &[Category], +    default_color: &str, +) -> gtk::Box {      let vbox = gtk::Box::builder()          .orientation(gtk::Orientation::Vertical)          .build(); @@ -175,6 +194,25 @@ fn day_events(date: NaiveDate, tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box              .hexpand(true)              .build(); +        let background_color = categories.iter().find(|c| event.category == Some(c.id)).map(|c| c.color.clone()).unwrap_or_else(|| default_color.to_string()); + +        let provider = gtk::CssProvider::new(); +        provider.load_from_data(format!(" +          .event {{ +            background-color: {}; +            color: white; +            border-radius: 4px; +            padding: 4px; +            margin: 4px; +          }} + +          .event:hover {{ +            filter: brightness(120%); +          }} +        ", background_color).as_bytes()); +        hbox.style_context().add_provider(&provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); +        hbox.style_context().add_class("event"); +          let gesture = gtk::GestureClick::new();          gesture.connect_pressed(              glib::clone!(@strong event, @strong tx => move |gesture, n, _, _| { @@ -190,8 +228,6 @@ fn day_events(date: NaiveDate, tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box          );          hbox.add_controller(&gesture); -        hbox.add_css_class("g-Calendar__DayEvent"); -          let event_txt = &event.pprint();          let label = gtk::Label::builder()              .label(event_txt) diff --git a/src/gui/form/mod.rs b/src/gui/form/mod.rs index bb43ef5..cc77a19 100644 --- a/src/gui/form/mod.rs +++ b/src/gui/form/mod.rs @@ -15,7 +15,7 @@ use uuid::Uuid;  use crate::{      db,      gui::{update, update::Msg, App}, -    model::{event, event::Event, repetition::Repetition}, +    model::{category::Category, event, event::Event, repetition::Repetition},  };  pub async fn repetition_dialog(app: &App, date: NaiveDate, event: &Event) { @@ -148,6 +148,19 @@ pub async fn show(app: &App, target: Target) {      column1.append(&utils::label("Fin"));      column1.append(&end); +    let dropdown_categories = get_dropdown_categories(&app.categories); +    let category_dropdown = gtk::DropDown::from_strings( +        &dropdown_categories +            .iter() +            .map(|s| s.as_str()) +            .collect::<Vec<&str>>(), +    ); +    category_dropdown.set_margin_bottom(10); +    let selected = get_selected_category(&event, &app.categories).unwrap_or_else(|| "".to_string()); +    category_dropdown.set_selected(dropdown_categories.iter().position(|d| d == &selected).unwrap_or(0) as u32); +    column1.append(&utils::label("Catégorie")); +    column1.append(&category_dropdown); +      // Second column      let repetition = match target { @@ -174,7 +187,8 @@ pub async fn show(app: &App, target: Target) {      lines.append(&button);      let conn = app.conn.clone();      let tx = app.tx.clone(); -    button.connect_clicked(glib::clone!(@weak dialog, @strong target, @strong event => move |_| { +    let categories = app.categories.clone(); +    button.connect_clicked(glib::clone!(@weak dialog, @strong target, @strong event, @strong categories => move |_| {          let removed_occurences = match &target {              Target::Update { event, .. } => {                  event.repetition.as_ref().map(|r| r.removed_occurences.clone()).unwrap_or_default() @@ -187,11 +201,15 @@ pub async fn show(app: &App, target: Target) {                      Target::Update {event} => event.id,                      _ => Uuid::new_v4(),                  }; -                match event::validate(id, date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text(), repetition) { + +                // Find category id from selected id +                let category = categories.iter().find(|c| c.name == dropdown_categories[category_dropdown.selected() as usize]).map(|c| c.id); + +                match event::validate(id, date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text(), repetition, category) {                      Some(new) => {                          match &target {                              Target::New {..} => { -                                match db::insert(&conn, &new) { +                                match db::events::insert(&conn, &new) {                                      Ok(_) => {                                          update::send(tx.clone(), Msg::AddEvent { new });                                          dialog.close() @@ -200,7 +218,7 @@ pub async fn show(app: &App, target: Target) {                                  }                              }                              Target::Update {event} => { -                                match db::update(&conn, &new) { +                                match db::events::update(&conn, &new) {                                      Ok(_) => {                                          update::send(tx.clone(), Msg::UpdateEvent { old: event.clone(), new });                                          dialog.close() @@ -212,7 +230,7 @@ pub async fn show(app: &App, target: Target) {                                  // TODO: improve intermediate error state                                  match delete_repetition_occurence(&conn, event, *date) {                                      Ok(occurence) => { -                                        match db::insert(&conn, &new) { +                                        match db::events::insert(&conn, &new) {                                              Ok(_) => {                                                  update::send(tx.clone(), Msg::UpdateEventOccurence {                                                      event: event.clone(), @@ -231,7 +249,7 @@ pub async fn show(app: &App, target: Target) {                              Target::UpdateFromOccurence { date, event } => {                                  match update_repetition_until(&conn, *date - Duration::days(1), event) {                                      Ok(updated) => { -                                        match db::insert(&conn, &new) { +                                        match db::events::insert(&conn, &new) {                                              Ok(_) => {                                                  update::send(tx.clone(), Msg::UpdateRepeatedFrom {                                                      old: event.clone(), @@ -288,7 +306,7 @@ pub async fn show(app: &App, target: Target) {                      }                  }                  _ => { -                    let operation = db::delete(&conn, &event.id); +                    let operation = db::events::delete(&conn, &event.id);                      if operation.is_ok() {                          update::send(tx.clone(), Msg::DeleteEvent { event: event.clone() });                          dialog.close() @@ -301,6 +319,22 @@ pub async fn show(app: &App, target: Target) {      dialog.run_future().await;  } +fn get_dropdown_categories(categories: &[Category]) -> Vec<String> { +    let mut xs = categories +        .iter() +        .map(|c| c.name.clone()) +        .collect::<Vec<String>>(); +    xs.push("".to_string()); +    xs.sort(); +    xs +} + +fn get_selected_category(event: &Option<Event>, categories: &[Category]) -> Option<String> { +    let id = event.as_ref()?.category?; +    let category = categories.iter().find(|c| c.id == id)?; +    Some(category.name.clone()) +} +  #[derive(Error, Debug)]  enum DeleteError {      #[error("Repetition not found")] @@ -320,7 +354,7 @@ fn delete_repetition_occurence(              let mut repetition = repetition.clone();              repetition.removed_occurences.insert(occurence);              event.repetition = Some(repetition); -            db::update(conn, &event).map(|_| occurence) +            db::events::update(conn, &event).map(|_| occurence)          } else {              Err(anyhow::Error::new(DeleteError::OccurenceNotFound))          } @@ -336,6 +370,6 @@ fn update_repetition_until(conn: &Connection, date: NaiveDate, event: &Event) ->          removed_occurences: r.removed_occurences.clone(),          until: Some(date),      }); -    db::update(conn, &with_repetition_until)?; +    db::events::update(conn, &with_repetition_until)?;      Ok(with_repetition_until)  } diff --git a/src/gui/style.css b/src/gui/style.css index 59ae30d..1b592d3 100644 --- a/src/gui/style.css +++ b/src/gui/style.css @@ -26,18 +26,6 @@    font-weight: bold;  } -.g-Calendar__DayEvent { -  background-color: #A8C2E0; -  color: white; -  border-radius: 4px; -  padding: 4px; -  margin: 4px; -} - -.g-Calendar__DayEvent:hover { -  background-color: pink; -} -  .g-Dialog {    background-color: white;    color: black; diff --git a/src/gui/update.rs b/src/gui/update.rs index bd4e7a9..7b3625c 100644 --- a/src/gui/update.rs +++ b/src/gui/update.rs @@ -166,24 +166,32 @@ pub async fn event_handler(rx: Receiver<Msg>, mut app: App) {                  refresh(&app, &HashSet::from([date]))              }              Msg::SelectPreviousWeek => { -                app.grid.remove_row(4); -                app.grid.insert_row(1); +                app.calendar.remove_row(4); +                app.calendar.insert_row(1);                  app.start_date -= Duration::days(7);                  app.end_date -= Duration::days(7); -                match db::list_non_recurring_between(&app.conn, app.start_date, app.end_date) { +                match db::events::list_non_recurring_between( +                    &app.conn, +                    app.start_date, +                    app.end_date, +                ) {                      Ok(events) => app.events = events,                      Err(err) => eprintln!("{}", err),                  };                  refresh(&app, &HashSet::from_iter(week_from(app.start_date)));              }              Msg::SelectNextWeek => { -                app.grid.remove_row(1); -                app.grid.insert_row(4); +                app.calendar.remove_row(1); +                app.calendar.insert_row(4);                  app.start_date += Duration::days(7);                  app.end_date += Duration::days(7); -                match db::list_non_recurring_between(&app.conn, app.start_date, app.end_date) { +                match db::events::list_non_recurring_between( +                    &app.conn, +                    app.start_date, +                    app.end_date, +                ) {                      Ok(events) => app.events = events,                      Err(err) => eprintln!("{}", err),                  }; @@ -275,7 +283,7 @@ fn refresh(app: &App, dates: &HashSet<NaiveDate>) {      for date in dates {          if date >= &app.start_date && date <= &app.end_date { -            calendar::refresh_date(app, *date, &repetitions) +            calendar::refresh_date(app, *date, &repetitions, &app.categories, &app.default_color)          }      }  } diff --git a/src/model/category.rs b/src/model/category.rs new file mode 100644 index 0000000..2f65671 --- /dev/null +++ b/src/model/category.rs @@ -0,0 +1,8 @@ +use uuid::Uuid; + +#[derive(Debug, Clone)] +pub struct Category { +    pub id: Uuid, +    pub name: String, +    pub color: String, +} diff --git a/src/model/event.rs b/src/model/event.rs index e556f6e..6cc16a2 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -16,6 +16,7 @@ pub struct Event {      pub end: Option<NaiveTime>,      pub name: String,      pub repetition: Option<Repetition>, +    pub category: Option<Uuid>,  }  impl Event { @@ -62,6 +63,7 @@ pub fn validate(      start: String,      end: String,      repetition: Option<Repetition>, +    category: Option<Uuid>,  ) -> Option<Event> {      let start = validation::time(start)?;      let end = validation::time(end)?; @@ -78,5 +80,6 @@ pub fn validate(          start,          end,          repetition, +        category,      })  } diff --git a/src/model/mod.rs b/src/model/mod.rs index 0aefbc6..2491bb4 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,3 +1,4 @@ +pub mod category;  pub mod event;  pub mod repetition;  pub mod time; | 
