aboutsummaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
authorJoris2022-03-12 13:27:29 +0100
committerJoris2022-03-12 13:36:09 +0100
commitd584df359640176ec4bc06f59d1e8d42ab17a413 (patch)
tree6cfaf676fc2ecf4e61067aa376fb2bed0d984d79 /src/gui
parentaad7b9601dfa05255d5c24f4a6377d9a25646d45 (diff)
downloadcalendar-d584df359640176ec4bc06f59d1e8d42ab17a413.tar.gz
calendar-d584df359640176ec4bc06f59d1e8d42ab17a413.tar.bz2
calendar-d584df359640176ec4bc06f59d1e8d42ab17a413.zip
Update and delete recurring events
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/app.rs17
-rw-r--r--src/gui/calendar.rs28
-rw-r--r--src/gui/form/mod.rs238
-rw-r--r--src/gui/form/repetition.rs47
-rw-r--r--src/gui/mod.rs9
-rw-r--r--src/gui/style.css2
-rw-r--r--src/gui/update.rs107
7 files changed, 356 insertions, 92 deletions
diff --git a/src/gui/app.rs b/src/gui/app.rs
index ebaceb3..9f37301 100644
--- a/src/gui/app.rs
+++ b/src/gui/app.rs
@@ -1,5 +1,6 @@
use gtk4 as gtk;
+use anyhow::Result;
use async_channel::Sender;
use chrono::{Datelike, Duration, NaiveDate, Weekday};
use gtk::glib::signal::Inhibit;
@@ -16,7 +17,7 @@ pub struct App {
pub window: Rc<gtk::ApplicationWindow>,
pub grid: gtk::Grid,
pub events: Vec<Event>,
- pub repeated_events: Vec<Event>,
+ pub recurring_events: Vec<Event>,
pub today: NaiveDate,
pub start_date: NaiveDate,
pub end_date: NaiveDate,
@@ -24,7 +25,7 @@ pub struct App {
}
impl App {
- pub fn new(conn: Rc<Connection>, app: &gtk::Application, tx: Sender<Msg>) -> Self {
+ pub fn new(conn: Rc<Connection>, app: &gtk::Application, tx: Sender<Msg>) -> Result<Self> {
let window = Rc::new(
gtk::ApplicationWindow::builder()
.application(app)
@@ -40,8 +41,8 @@ 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_repeated_between(&conn, start_date, end_date).unwrap_or_default();
- let repeated_events = db::list_repeated(&conn).unwrap_or_default();
+ let events = db::list_non_recurring_between(&conn, start_date, end_date)?;
+ let recurring_events = db::list_recurring(&conn)?;
let grid = calendar::create(
tx.clone(),
@@ -49,7 +50,7 @@ impl App {
start_date,
end_date,
&events,
- &repeated_events,
+ &recurring_events,
);
window.set_child(Some(&grid));
@@ -61,16 +62,16 @@ impl App {
Inhibit(false)
});
- Self {
+ Ok(Self {
conn,
window,
grid,
events,
- repeated_events,
+ recurring_events,
today,
start_date,
end_date,
tx,
- }
+ })
}
}
diff --git a/src/gui/calendar.rs b/src/gui/calendar.rs
index 3f5b6a7..cad2465 100644
--- a/src/gui/calendar.rs
+++ b/src/gui/calendar.rs
@@ -19,7 +19,7 @@ pub fn create(
start_date: NaiveDate,
end_date: NaiveDate,
events: &[Event],
- repeated_events: &[Event],
+ recurring_events: &[Event],
) -> gtk::Grid {
let grid = gtk::Grid::builder().build();
@@ -27,7 +27,7 @@ pub fn create(
grid.attach(&day_title(col), col, 0, 1, 1);
}
- let repetitions = event::repetitions_between(repeated_events, start_date, end_date);
+ let repetitions = event::repetitions_between(recurring_events, start_date, end_date);
attach_days(tx.clone(), &grid, start_date, today, events, &repetitions);
let event_controller_key = gtk::EventControllerKey::new();
@@ -126,12 +126,12 @@ pub fn day_entry(
.iter()
.filter(|e| e.date == date)
.collect::<Vec<&Event>>();
- let repeated_events = repetitions.get(&date).cloned().unwrap_or_default();
- events.extend(repeated_events.iter());
+ let recurring_events = repetitions.get(&date).cloned().unwrap_or_default();
+ events.extend(recurring_events.iter());
events.sort_by_key(|e| e.start);
if !events.is_empty() {
- vbox.append(&day_events(tx, events));
+ vbox.append(&day_events(date, tx, events));
}
gtk::ScrolledWindow::builder()
@@ -147,18 +147,24 @@ fn day_label(today: NaiveDate, date: NaiveDate) -> gtk::Label {
.label(&format!(
"{} {}",
date.day(),
- if date == today || date.day() == 1 { MONTHES[date.month0() as usize] } else { "" }
+ if date == today || date.day() == 1 {
+ MONTHES[date.month0() as usize]
+ } else {
+ ""
+ }
))
.halign(gtk::Align::Start)
.build();
label.add_css_class("g-Calendar__DayNumber");
- if date.day() == 1 { label.add_css_class("g-Calendar__DayNumber--FirstOfMonth") }
+ if date.day() == 1 {
+ label.add_css_class("g-Calendar__DayNumber--FirstOfMonth")
+ }
label
}
-fn day_events(tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box {
+fn day_events(date: NaiveDate, tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box {
let vbox = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.build();
@@ -174,7 +180,11 @@ fn day_events(tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box {
glib::clone!(@strong event, @strong tx => move |gesture, n, _, _| {
gesture.set_state(gtk::EventSequenceState::Claimed);
if n == 2 {
- update::send(tx.clone(), Msg::ShowUpdateForm { event: event.clone() });
+ if event.repetition.is_some() {
+ update::send(tx.clone(), Msg::ShowRepetitionDialog { date, event: event.clone() });
+ } else {
+ update::send(tx.clone(), Msg::ShowUpdateForm { event: event.clone() });
+ }
}
}),
);
diff --git a/src/gui/form/mod.rs b/src/gui/form/mod.rs
index 57ccac7..68e6539 100644
--- a/src/gui/form/mod.rs
+++ b/src/gui/form/mod.rs
@@ -2,8 +2,13 @@ mod repetition;
use gtk4 as gtk;
+use anyhow::Result;
+use chrono::{NaiveDate, NaiveTime};
use gtk::glib;
use gtk::prelude::*;
+use rusqlite::Connection;
+use thiserror::Error;
+use uuid::Uuid;
use crate::{
db,
@@ -11,12 +16,70 @@ use crate::{
model::{event, event::Event},
};
-pub async fn show(app: &App, event: Event, is_new: bool) {
+pub async fn repetition_dialog(app: &App, date: NaiveDate, event: Event) {
let dialog = gtk::Dialog::builder()
.transient_for(&*app.window)
.modal(true)
- .title(if is_new { "Ajouter" } else { "Modifier" })
- .css_classes(vec!["g-Form".to_string()])
+ .title("Modifier")
+ .css_classes(vec!["g-Dialog".to_string()])
+ .build();
+
+ let content_area = dialog.content_area();
+
+ let lines = gtk::Box::builder()
+ .orientation(gtk::Orientation::Vertical)
+ .build();
+ content_area.append(&lines);
+
+ let button = gtk::Button::builder()
+ .label("Cette occurence")
+ .margin_bottom(10)
+ .build();
+ lines.append(&button);
+ let tx = app.tx.clone();
+ button.connect_clicked(glib::clone!(@weak dialog, @strong event => move |_| {
+ update::send(tx.clone(), Msg::ShowUpdateRepetitionForm { date, event: event.clone() });
+ dialog.close()
+ }));
+
+ let button = gtk::Button::builder()
+ .label("Toutes les occurences")
+ .build();
+ lines.append(&button);
+ let tx = app.tx.clone();
+ button.connect_clicked(glib::clone!(@weak dialog, @strong event => move |_| {
+ update::send(tx.clone(), Msg::ShowUpdateForm { event: event.clone() });
+ dialog.close()
+ }));
+
+ dialog.run_future().await;
+}
+
+#[derive(Clone)]
+pub enum Target {
+ New { date: NaiveDate },
+ Update { event: Event },
+ UpdateRepetition { event: Event, date: NaiveDate },
+}
+
+pub async fn show(app: &App, target: Target) {
+ let event = match target {
+ Target::New { .. } => None,
+ Target::Update { ref event } => Some(event.clone()),
+ Target::UpdateRepetition { ref event, .. } => Some(event.clone()),
+ };
+
+ let title = if event.is_some() {
+ "Modifier"
+ } else {
+ "Ajouter"
+ };
+
+ let dialog = gtk::Dialog::builder()
+ .transient_for(&*app.window)
+ .modal(true)
+ .title(title)
+ .css_classes(vec!["g-Dialog".to_string()])
.build();
let content_area = dialog.content_area();
@@ -29,7 +92,6 @@ pub async fn show(app: &App, event: Event, is_new: bool) {
let columns = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.build();
- columns.add_css_class("g-Form__Columns");
lines.append(&columns);
// First column
@@ -37,79 +99,145 @@ pub async fn show(app: &App, event: Event, is_new: bool) {
let column1 = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.build();
- column1.add_css_class("g-Form__Inputs");
columns.append(&column1);
- let name = entry(&event.name);
+ let name = event.as_ref().map(|e| entry(&e.name)).unwrap_or_default();
column1.append(&label("Événement"));
column1.append(&name);
- let date = entry(&event.date.format(event::DATE_FORMAT).to_string());
+ let date = match target {
+ Target::New { date } => date,
+ Target::Update { ref event } => event.date,
+ Target::UpdateRepetition { date, .. } => date,
+ };
+ let date = entry(&date.format(event::DATE_FORMAT).to_string());
column1.append(&label("Jour"));
column1.append(&date);
- let start = entry(
- &event
- .start
- .map(event::pprint_time)
- .unwrap_or_else(|| "".to_string()),
- );
+ let start = event
+ .as_ref()
+ .map(|e| time_entry(e.start))
+ .unwrap_or_else(|| entry(""));
column1.append(&label("Début"));
column1.append(&start);
- let end = entry(
- &event
- .end
- .map(event::pprint_time)
- .unwrap_or_else(|| "".to_string()),
- );
+ let end = event
+ .as_ref()
+ .map(|e| time_entry(e.end))
+ .unwrap_or_else(|| entry(""));
column1.append(&label("Fin"));
column1.append(&end);
// Second column
- let repetition_model = repetition::view(&event);
+ let repetition = match target {
+ Target::Update { ref event } => event.repetition.as_ref(),
+ _ => None,
+ };
+ let repetition_model = repetition::view(repetition);
columns.append(&repetition_model.view);
// Buttons
+ let button_title = match target {
+ Target::New { .. } => "Créer",
+ Target::Update { .. } => "Modifier",
+ Target::UpdateRepetition { .. } => "Modifier l’occurence",
+ };
+
let button = gtk::Button::builder()
- .label(if is_new { "Créer" } else { "Modifier" })
+ .label(button_title)
.margin_bottom(10)
.build();
lines.append(&button);
let conn = app.conn.clone();
let tx = app.tx.clone();
- button.connect_clicked(glib::clone!(@weak dialog, @strong event => move |_| {
+ button.connect_clicked(glib::clone!(@weak dialog, @strong target, @strong event => move |_| {
match repetition::validate(&repetition_model) {
Ok(repetition) => {
- match event::validate(event.id, date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text(), repetition) {
+ let id = match &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) {
Some(new) => {
- match if is_new { db::insert(&conn, &new) } else { db::update(&conn, &new) } {
- Ok(_) => {
- let msg = if is_new { Msg::AddEvent { new } } else { Msg::UpdateEvent { old: event.clone(), new } };
- update::send(tx.clone(), msg);
- dialog.close()
- },
- Err(err) => eprintln!("Error when upserting event: {err}")
+ match &target {
+ Target::New {..} => {
+ match db::insert(&conn, &new) {
+ Ok(_) => {
+ update::send(tx.clone(), Msg::AddEvent { new });
+ dialog.close()
+ },
+ Err(err) => eprintln!("Error when inserting event: {}", err)
+ }
+ }
+ Target::Update {event} => {
+ match db::update(&conn, &new) {
+ Ok(_) => {
+ update::send(tx.clone(), Msg::UpdateEvent { old: event.clone(), new });
+ dialog.close()
+ },
+ Err(err) => eprintln!("Error when updating event: {}", err)
+ }
+ }
+ Target::UpdateRepetition { event, date } => {
+ // TODO: improve intermediate error state
+ match delete_repetition_occurence(&conn, event, *date) {
+ Ok(occurence) => {
+ match db::insert(&conn, &new) {
+ Ok(_) => {
+ update::send(tx.clone(), Msg::UpdateEventOccurence {
+ event: event.clone(),
+ occurence,
+ date: *date,
+ new
+ })
+ }
+ Err(err) => eprintln!("Error when updating repetition: {}", err)
+ };
+ dialog.close()
+ },
+ Err(err) => eprintln!("Error when updating repetition: {}", err)
+ }
+ }
}
}
- None => eprintln!("Event is not valid: {event:?}")
+ None => eprintln!("Event is not valid.")
}
},
- Err(message) => eprintln!("{message}")
+ Err(message) => eprintln!("{}", message)
}
}));
- if !is_new {
- let button = gtk::Button::builder().label("Supprimer").build();
+ if let Some(event) = event {
+ let label = match target {
+ Target::Update { .. } => "Supprimer",
+ _ => "Supprimer l’occurence",
+ };
+ let button = gtk::Button::builder().label(label).build();
lines.append(&button);
let conn = app.conn.clone();
let tx = app.tx.clone();
button.connect_clicked(glib::clone!(@weak dialog => move |_| {
- if db::delete(&conn, &event.id).is_ok() {
- update::send(tx.clone(), Msg::DeleteEvent { event: event.clone() });
- dialog.close()
+ match target {
+ Target::UpdateRepetition { date, .. } => {
+ match delete_repetition_occurence(&conn, &event, date) {
+ Ok(occurence) => {
+ update::send(tx.clone(), Msg::DeleteOccurence { event: event.clone(), date, occurence });
+ dialog.close()
+ }
+ Err(err) => {
+ eprintln!("{:?}", err);
+ }
+ }
+ }
+ _ => {
+ let operation = db::delete(&conn, &event.id);
+ if operation.is_ok() {
+ update::send(tx.clone(), Msg::DeleteEvent { event: event.clone() });
+ dialog.close()
+ }
+ }
}
}));
}
@@ -117,6 +245,42 @@ pub async fn show(app: &App, event: Event, is_new: bool) {
dialog.run_future().await;
}
+#[derive(Error, Debug)]
+enum DeleteError {
+ #[error("Repetition not found")]
+ RepetitionNotFound,
+ #[error("Occurence not found")]
+ OccurenceNotFound,
+}
+
+fn delete_repetition_occurence(
+ conn: &Connection,
+ event: &Event,
+ occurence_date: NaiveDate,
+) -> Result<usize> {
+ if let Some(ref repetition) = event.repetition {
+ if let Some(occurence) = repetition.occurence_index(event.date, occurence_date) {
+ let mut event = event.clone();
+ let mut repetition = repetition.clone();
+ repetition.removed_occurences.insert(occurence);
+ event.repetition = Some(repetition);
+ db::update(conn, &event).map(|_| occurence)
+ } else {
+ Err(anyhow::Error::new(DeleteError::OccurenceNotFound))
+ }
+ } else {
+ Err(anyhow::Error::new(DeleteError::RepetitionNotFound))
+ }
+}
+
+fn time_entry(time: Option<NaiveTime>) -> gtk::Entry {
+ entry(
+ &time
+ .map(event::pprint_time)
+ .unwrap_or_else(|| "".to_string()),
+ )
+}
+
fn entry(text: &str) -> gtk::Entry {
gtk::Entry::builder().text(text).margin_bottom(10).build()
}
diff --git a/src/gui/form/repetition.rs b/src/gui/form/repetition.rs
index accb091..1d36765 100644
--- a/src/gui/form/repetition.rs
+++ b/src/gui/form/repetition.rs
@@ -2,13 +2,11 @@ use gtk4 as gtk;
use chrono::{Weekday, Weekday::*};
use gtk::prelude::*;
+use std::collections::HashSet;
-use crate::{
- model::event::Event,
- model::{
- repetition,
- repetition::{DayOfMonth, Repetition},
- },
+use crate::model::{
+ repetition,
+ repetition::{DayOfMonth, Frequency, Repetition},
};
static WEEKDAYS_STR: [&str; 7] = [
@@ -29,7 +27,7 @@ pub struct Model {
yearly_radio: gtk::CheckButton,
}
-pub fn view(event: &Event) -> Model {
+pub fn view(repetition: Option<&Repetition>) -> Model {
let view = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.build();
@@ -39,12 +37,14 @@ pub fn view(event: &Event) -> Model {
let no_radio = gtk::CheckButton::builder()
.label("Non")
- .active(event.repetition.is_none())
+ .active(repetition.is_none())
.build();
view.append(&no_radio);
- let default = match event.repetition {
- Some(Repetition::Daily { period }) => period.to_string(),
+ let frequency = repetition.as_ref().map(|r| r.frequency.clone());
+
+ let default = match frequency {
+ Some(Frequency::Daily { period }) => period.to_string(),
_ => "".to_string(),
};
let day_interval_entry = gtk::Entry::builder().text(&default).build();
@@ -56,8 +56,8 @@ pub fn view(event: &Event) -> Model {
);
view.append(&day_interval_box);
- let default = match event.repetition {
- Some(Repetition::Monthly {
+ let default = match frequency {
+ Some(Frequency::Monthly {
day: DayOfMonth::Day { day },
}) => day.to_string(),
_ => "".to_string(),
@@ -67,8 +67,8 @@ pub fn view(event: &Event) -> Model {
radio_input(&no_radio, !default.is_empty(), &monthly_entry, "Mensuel");
view.append(&monthly_box);
- let (active, default) = match event.repetition {
- Some(Repetition::Monthly {
+ let (active, default) = match frequency {
+ Some(Frequency::Monthly {
day: DayOfMonth::Weekday { weekday },
}) => (true, weekday),
_ => (false, Mon),
@@ -83,7 +83,7 @@ pub fn view(event: &Event) -> Model {
let yearly_radio = gtk::CheckButton::builder()
.group(&no_radio)
.label("Annuel")
- .active(event.repetition == Some(Repetition::Yearly))
+ .active(frequency == Some(Frequency::Yearly))
.build();
view.append(&yearly_radio);
@@ -127,24 +127,29 @@ fn label(text: &str) -> gtk::Label {
}
pub fn validate(model: &Model) -> Result<Option<Repetition>, String> {
- if model.no_radio.is_active() {
+ let frequency = if model.no_radio.is_active() {
Ok(None)
} else if model.day_interval_radio.is_active() {
let period = repetition::validate_period(&model.day_interval_entry.buffer().text())?;
- Ok(Some(Repetition::Daily { period }))
+ Ok(Some(Frequency::Daily { period }))
} else if model.monthly_radio.is_active() {
let day = repetition::validate_day(&model.monthly_entry.buffer().text())?;
- Ok(Some(Repetition::Monthly {
+ Ok(Some(Frequency::Monthly {
day: DayOfMonth::Day { day },
}))
} else if model.first_day_radio.is_active() {
let weekday = WEEKDAYS[model.first_day_dropdown.selected() as usize];
- Ok(Some(Repetition::Monthly {
+ Ok(Some(Frequency::Monthly {
day: DayOfMonth::Weekday { weekday },
}))
} else if model.yearly_radio.is_active() {
- Ok(Some(Repetition::Yearly))
+ Ok(Some(Frequency::Yearly))
} else {
Err("Aucune option n’a été sélectionnée".to_string())
- }
+ }?;
+
+ Ok(frequency.map(|frequency| Repetition {
+ frequency,
+ removed_occurences: HashSet::new(),
+ }))
}
diff --git a/src/gui/mod.rs b/src/gui/mod.rs
index c33500b..e7f457f 100644
--- a/src/gui/mod.rs
+++ b/src/gui/mod.rs
@@ -24,8 +24,13 @@ pub fn run(conn: Connection) {
fn build_ui(conn: Rc<Connection>, app: &gtk::Application) {
let (tx, rx) = async_channel::unbounded();
- let app = App::new(conn, app, tx);
- utils::spawn(update::event_handler(rx, app))
+ match App::new(conn, app, tx) {
+ Ok(app) => utils::spawn(update::event_handler(rx, app)),
+ Err(err) => {
+ eprintln!("{}", err);
+ std::process::exit(1)
+ }
+ }
}
fn load_style() {
diff --git a/src/gui/style.css b/src/gui/style.css
index 70385d1..59ae30d 100644
--- a/src/gui/style.css
+++ b/src/gui/style.css
@@ -38,7 +38,7 @@
background-color: pink;
}
-.g-Form {
+.g-Dialog {
background-color: white;
color: black;
padding: 10px;
diff --git a/src/gui/update.rs b/src/gui/update.rs
index c8dfa6d..419a6e4 100644
--- a/src/gui/update.rs
+++ b/src/gui/update.rs
@@ -2,6 +2,7 @@ use async_channel::{Receiver, Sender};
use chrono::{Duration, NaiveDate};
use gtk4::prelude::GridExt;
use std::collections::HashSet;
+use std::iter::FromIterator;
use crate::{
gui::{calendar, form, utils, App},
@@ -15,11 +16,41 @@ pub fn send(tx: Sender<Msg>, msg: Msg) {
}
pub enum Msg {
- ShowAddForm { date: NaiveDate },
- ShowUpdateForm { event: Event },
- AddEvent { new: Event },
- UpdateEvent { old: Event, new: Event },
- DeleteEvent { event: Event },
+ ShowAddForm {
+ date: NaiveDate,
+ },
+ ShowRepetitionDialog {
+ date: NaiveDate,
+ event: Event,
+ },
+ ShowUpdateForm {
+ event: Event,
+ },
+ ShowUpdateRepetitionForm {
+ date: NaiveDate,
+ event: Event,
+ },
+ AddEvent {
+ new: Event,
+ },
+ UpdateEvent {
+ old: Event,
+ new: Event,
+ },
+ UpdateEventOccurence {
+ event: Event,
+ occurence: usize,
+ date: NaiveDate,
+ new: Event,
+ },
+ DeleteEvent {
+ event: Event,
+ },
+ DeleteOccurence {
+ event: Event,
+ date: NaiveDate,
+ occurence: usize,
+ },
SelectPreviousWeek,
SelectNextWeek,
}
@@ -27,8 +58,14 @@ pub enum Msg {
pub async fn event_handler(rx: Receiver<Msg>, mut app: App) {
while let Ok(msg) = rx.recv().await {
match msg {
- Msg::ShowAddForm { date } => form::show(&app, event::init(date), true).await,
- Msg::ShowUpdateForm { event } => form::show(&app, event, false).await,
+ Msg::ShowAddForm { date } => form::show(&app, form::Target::New { date }).await,
+ Msg::ShowRepetitionDialog { date, event } => {
+ form::repetition_dialog(&app, date, event).await
+ }
+ Msg::ShowUpdateForm { event } => form::show(&app, form::Target::Update { event }).await,
+ Msg::ShowUpdateRepetitionForm { date, event } => {
+ form::show(&app, form::Target::UpdateRepetition { event, date }).await
+ }
Msg::AddEvent { new } => {
let refresh_dates = add(&mut app, &new);
refresh(&app, &refresh_dates)
@@ -38,10 +75,29 @@ pub async fn event_handler(rx: Receiver<Msg>, mut app: App) {
refresh_dates.extend(add(&mut app, &new));
refresh(&app, &refresh_dates);
}
+ Msg::UpdateEventOccurence {
+ event,
+ occurence,
+ date,
+ new,
+ } => {
+ remove_occurence(&mut app, &event, occurence);
+ let mut refresh_dates = add(&mut app, &new);
+ refresh_dates.insert(date);
+ refresh(&app, &refresh_dates)
+ }
Msg::DeleteEvent { event } => {
let refresh_dates = remove(&mut app, &event);
refresh(&app, &refresh_dates)
}
+ Msg::DeleteOccurence {
+ event,
+ date,
+ occurence,
+ } => {
+ remove_occurence(&mut app, &event, occurence);
+ refresh(&app, &HashSet::from([date]))
+ }
Msg::SelectPreviousWeek => {
app.grid.remove_row(4);
app.grid.insert_row(1);
@@ -66,12 +122,10 @@ pub async fn event_handler(rx: Receiver<Msg>, mut app: App) {
/// Remove event and return dates that should be refreshed.
fn remove(app: &mut App, event: &Event) -> HashSet<NaiveDate> {
if event.repetition.is_some() {
- match app.repeated_events.iter().position(|e| e.id == event.id) {
+ match app.recurring_events.iter().position(|e| e.id == event.id) {
Some(index) => {
- app.repeated_events.remove(index);
- let mut dates = repetition_dates(app, event);
- dates.insert(event.date);
- dates
+ app.recurring_events.remove(index);
+ repetition_dates(app, event)
}
None => {
eprintln!("Event not found when trying to delete {:?}", event);
@@ -92,10 +146,35 @@ fn remove(app: &mut App, event: &Event) -> HashSet<NaiveDate> {
}
}
+/// Remove event repetition
+fn remove_occurence(app: &mut App, event: &Event, occurence: usize) {
+ match app.recurring_events.iter().position(|e| e.id == event.id) {
+ Some(index) => {
+ let event = app.recurring_events.get_mut(index).unwrap();
+ match event.repetition.clone() {
+ Some(mut repetition) => {
+ repetition.removed_occurences.insert(occurence);
+ event.repetition = Some(repetition.clone())
+ }
+ None => eprintln!(
+ "Repetition not found when trying to delete repetition of {:?}",
+ event
+ ),
+ }
+ }
+ None => {
+ eprintln!(
+ "Event not found when trying to delete repetition of {:?}",
+ event
+ )
+ }
+ }
+}
+
/// Add event and return dates that should be refreshed.
fn add(app: &mut App, event: &Event) -> HashSet<NaiveDate> {
if event.repetition.is_some() {
- app.repeated_events.push(event.clone());
+ app.recurring_events.push(event.clone());
let mut dates = repetition_dates(app, event);
dates.insert(event.date);
dates
@@ -114,7 +193,7 @@ fn repetition_dates(app: &App, event: &Event) -> HashSet<NaiveDate> {
/// Refresh app for the given dates.
fn refresh(app: &App, dates: &HashSet<NaiveDate>) {
let repetitions =
- event::repetitions_between(&app.repeated_events, app.start_date, app.end_date);
+ event::repetitions_between(&app.recurring_events, app.start_date, app.end_date);
for date in dates {
calendar::refresh_date(app, *date, &repetitions)