summaryrefslogtreecommitdiff
path: root/src/html_calendar.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/html_calendar.rs')
-rw-r--r--src/html_calendar.rs192
1 files changed, 192 insertions, 0 deletions
diff --git a/src/html_calendar.rs b/src/html_calendar.rs
new file mode 100644
index 0000000..b16b987
--- /dev/null
+++ b/src/html_calendar.rs
@@ -0,0 +1,192 @@
+use std::collections::HashMap;
+use chrono::{NaiveTime, Duration, Local};
+use crate::task::*;
+
+pub enum CalendarPrivacy {
+ Public,
+ Private,
+}
+
+pub fn tasks_to_html(tasks: &Vec<Task>, privacy: CalendarPrivacy) -> String {
+ let public_tags = HashMap::from([
+ ("busy", "I will be genuinely busy, e.g., a meeting with others."),
+ ("rough", "The nature of the event (e.g., a hike) makes it difficult to preduct the exact start/end times."),
+ ("tentative", "This event timing is only tentative."),
+ ("join-me", "This is an open event; if you're interested in joining please reach out!"),
+ ("self", "This is scheduled time for me to complete a specific work or personal task; I can usually reschedule such blocks when requested."),
+ ]);
+
+ let mut html = "<html><head><meta charset=\"UTF-8\"><title>Calendar</title><link rel=\"stylesheet\" href=\"calendar_style.css\"></link></head><body>".to_string();
+
+ let n_days = 14;
+ let start_period = Local::now().date().naive_local();
+ let end_period = start_period + Duration::days(n_days);
+ let mut week_task_ids: Vec<usize> = Vec::new();
+ for (i, task) in tasks.iter().enumerate() {
+ if task.date >= start_period && task.date < end_period {
+ week_task_ids.push(i);
+ }
+ }
+
+ let min_incr: i64 = 15;
+ let timespans_per_day = (24 * 60 ) / min_incr;
+ let mut table: Vec<Vec<Option<usize>>> = Vec::new();
+ for i in 0..timespans_per_day {
+ table.push(Vec::new());
+ for _ in 0..n_days {
+ table[i as usize].push(None);
+ }
+ }
+
+ html.push_str("<table>");
+ html.push_str("<tr><th>Time</th>");
+ for offset in 0..n_days {
+ html.push_str("<th>");
+ html.push_str(&(start_period + Duration::days(offset)).format("%a %-m/%-d/%y").to_string());
+ html.push_str("</th>");
+ }
+ html.push_str("</tr>");
+
+ week_task_ids.sort_by(|a, b| cmp_tasks(&tasks[*a], &tasks[*b]));
+
+ for i in 0..timespans_per_day {
+ let timespan_start = NaiveTime::from_hms(0, 0, 0) + Duration::minutes(i * min_incr);
+ let timespan_end = NaiveTime::from_hms(0, 0, 0) + Duration::minutes((i + 1) * min_incr);
+ for offset in 0..n_days {
+ // (1) Find all task ids that intersect this timespan on this day.
+ let this_date = start_period + Duration::days(offset);
+ let on_this_date: Vec<usize>
+ = week_task_ids.iter().map(|&idx| idx)
+ .filter(|&idx| tasks[idx].date == this_date).collect();
+ let intersecting: Vec<usize>
+ = on_this_date.iter().map(|&idx| idx)
+ .filter(|&idx| does_overlap(&timespan_start, &timespan_end, &tasks[idx])).collect();
+ // (2) Find the event ending first and place it in the table.
+ table[i as usize][offset as usize] = intersecting.iter()
+ .map(|&idx| idx)
+ .min_by_key(|&idx| tasks[idx].end_time.expect("Should have an end time at this point..."));
+ }
+ }
+ for row_idx in 0..timespans_per_day {
+ let timespan_start = NaiveTime::from_hms(0, 0, 0) + Duration::minutes(row_idx * min_incr);
+ html.push_str("<tr><td><b>");
+ html.push_str(&timespan_start.format("%l:%M %p").to_string());
+ html.push_str("</b></td>");
+ for col_idx in 0..n_days {
+ let task_idx = table[row_idx as usize][col_idx as usize];
+ match task_idx {
+ Some(idx) => {
+ if row_idx == 0 || table[(row_idx - 1) as usize][col_idx as usize] != task_idx {
+ let mut rowspan = 0;
+ for i in row_idx..timespans_per_day {
+ if table[i as usize][col_idx as usize] == task_idx {
+ rowspan += 1;
+ } else {
+ break;
+ }
+ }
+ html.push_str("<td class=\"has-task");
+ for tag in &tasks[idx].tags {
+ if public_tags.contains_key(tag.as_str()) {
+ html.push_str(" tag-");
+ html.push_str(tag.as_str());
+ }
+ }
+ html.push_str("\" rowspan=\"");
+ html.push_str(rowspan.to_string().as_str());
+ html.push_str("\">");
+ html.push_str("<a href=\"#");
+ html.push_str("task-");
+ html.push_str(idx.to_string().as_str());
+ html.push_str("\">");
+ match privacy {
+ CalendarPrivacy::Public => {
+ let mut any_yet = false;
+ for tag in &tasks[idx].tags {
+ if public_tags.contains_key(tag.as_str()) {
+ if any_yet { html.push_str(", "); }
+ html.push_str(tag.as_str());
+ any_yet = true;
+ }
+ }
+ if tasks[idx].tags.contains(&&"public".to_string()) {
+ if any_yet { html.push_str(": \""); }
+ html.push_str(&tasks[idx].details.as_str());
+ html.push_str("\"");
+ any_yet = true;
+ }
+ if !any_yet {
+ html.push_str("has-task");
+ }
+ },
+ CalendarPrivacy::Private => {
+ html.push_str(&tasks[idx].details.as_str());
+ },
+ }
+ html.push_str("</a></td>");
+ }
+ },
+ _ => {
+ html.push_str("<td></td>");
+ },
+ }
+ }
+ html.push_str("</tr>");
+ }
+ html.push_str("</table><ul>");
+ for i in week_task_ids.iter() {
+ let task = &tasks[*i];
+ let is_public = task.tags.contains(&"public".to_string());
+ match (&privacy, &task.start_time, &is_public) {
+ (CalendarPrivacy::Public, None, false) => continue,
+ _ => (),
+ }
+ html.push_str("<li id=\"task-");
+ html.push_str(i.to_string().as_str());
+ html.push_str("\">");
+ html.push_str(task.date.format("%a %-m/%-d/%y ").to_string().as_str());
+ match [task.start_time, task.end_time] {
+ [Some(start), Some(end)] => {
+ html.push_str(start.format("%l:%M%p").to_string().as_str());
+ html.push_str(" -- ");
+ html.push_str(end.format("%l:%M%p").to_string().as_str());
+ }
+ _ => (),
+ }
+ html.push_str("<ul>");
+ match privacy {
+ CalendarPrivacy::Public => {
+ if task.tags.contains(&"public".to_string()) {
+ html.push_str("<li><b>Description:</b> ");
+ html.push_str(task.details.as_str());
+ html.push_str("</li>");
+ }
+ for tag in &task.tags {
+ if public_tags.contains_key(&tag.as_str()) {
+ html.push_str("<li>Tagged <b>");
+ html.push_str(tag.as_str());
+ html.push_str(":</b> ");
+ html.push_str(public_tags.get(&tag.as_str()).expect(""));
+ html.push_str("</li>");
+ }
+ }
+ },
+ CalendarPrivacy::Private => {
+ html.push_str("<li><b>Description:</b> ");
+ html.push_str(task.details.as_str());
+ html.push_str("</li><li>Tagged: ");
+ for (i, tag) in task.tags.iter().enumerate() {
+ if i > 0 { html.push_str(", "); }
+ html.push_str("<b>");
+ html.push_str(tag.as_str());
+ html.push_str("</b>");
+ }
+ html.push_str("</li>");
+ }
+ }
+ html.push_str("</ul>");
+ html.push_str("</li>");
+ }
+ html.push_str("</ul><a href=\"https://lair.masot.net/git/wtd\">src</a></body></html>");
+ return html;
+}
generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback