diff options
Diffstat (limited to 'src/html_calendar.rs')
-rw-r--r-- | src/html_calendar.rs | 192 |
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(×pan_start, ×pan_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(×pan_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; +} |