aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2022-04-24 16:31:49 +0200
committerJoris2022-04-24 16:31:49 +0200
commit47fe90ee23d8ab04645ef3c7a17459ed40c5b765 (patch)
treed1aa7d3840a3bd825dcae0a0398fec7ca310f45c
parent6d271b09303d924381cc65e7c0b5eb56833780ed (diff)
Allow to attach categories to events
-rw-r--r--README.md12
-rw-r--r--src/cli/mod.rs4
-rw-r--r--src/db/categories.rs25
-rw-r--r--src/db/event-color.rs7
-rw-r--r--src/db/event_color.rs14
-rw-r--r--src/db/events.rs139
-rw-r--r--src/db/migrations/2-categories.sql10
-rw-r--r--src/db/migrations/3-event-color.sql5
-rw-r--r--src/db/mod.rs137
-rw-r--r--src/gui/app.rs40
-rw-r--r--src/gui/calendar.rs56
-rw-r--r--src/gui/form/mod.rs54
-rw-r--r--src/gui/style.css12
-rw-r--r--src/gui/update.rs22
-rw-r--r--src/model/category.rs8
-rw-r--r--src/model/event.rs3
-rw-r--r--src/model/mod.rs1
17 files changed, 356 insertions, 193 deletions
diff --git a/README.md b/README.md
index 2fada1c..2a4c891 100644
--- a/README.md
+++ b/README.md
@@ -38,14 +38,16 @@ cargo run -- --list-today
# TODO
-## Optimizations
+## Categorize events
-- Optimize refresh.
+1. Add category form
+2. Show categories
+3. Update category
+4. Delete category
-## Categorize events
+## Optimizations
-1. CRUD for list of types (name + color).
-2. Show / hide depending on the type.
+- Optimize refresh.
## Multi day events
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(&gtk::Label::new(Some("Calendrier"))));
+ // notebook.append_page(&categories, Some(&gtk::Label::new(Some("Catégories"))));
+ // window.set_child(Some(&notebook));
+
+ 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;