mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
Today, August 1st, I was surprised to see WTF missing the date header over my calendar events. It turns out that when I extended the `dayDivider` function to use a default (epoch) time for considering whether to print a header over the first event (when `prevEvent == nil`), I didn't consider that 1 out of every ~30 days will happen to have the same day of the month as the epoch time. To fix this and make date headers show up on the 1st of the month again, dates are truncated to midnight and compared for equality, rather than just comparing a component of them. I *think* converting times to local time before truncating to midnight is the correct way to do this - otherwise, midnight in one time zone would never equal midnight in another time zone. As it happens, all my meetings take place in San Francisco, so I'm not the best test vector for how this works with meetings/calls in different time zones.
226 lines
4.8 KiB
Go
226 lines
4.8 KiB
Go
package gcal
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/senorprogrammer/wtf/wtf"
|
|
)
|
|
|
|
func (widget *Widget) sortedEvents() ([]*CalEvent, []*CalEvent) {
|
|
allDayEvents := []*CalEvent{}
|
|
timedEvents := []*CalEvent{}
|
|
|
|
for _, calEvent := range widget.calEvents {
|
|
if calEvent.AllDay() {
|
|
allDayEvents = append(allDayEvents, calEvent)
|
|
} else {
|
|
timedEvents = append(timedEvents, calEvent)
|
|
}
|
|
}
|
|
|
|
return allDayEvents, timedEvents
|
|
}
|
|
|
|
func (widget *Widget) display() {
|
|
if widget.calEvents == nil || len(widget.calEvents) == 0 {
|
|
return
|
|
}
|
|
|
|
widget.mutex.Lock()
|
|
defer widget.mutex.Unlock()
|
|
|
|
_, timedEvents := widget.sortedEvents()
|
|
widget.View.SetText(widget.contentFrom(timedEvents))
|
|
}
|
|
|
|
func (widget *Widget) contentFrom(calEvents []*CalEvent) string {
|
|
if (calEvents == nil) || (len(calEvents) == 0) {
|
|
return ""
|
|
}
|
|
|
|
var str string
|
|
var prevEvent *CalEvent
|
|
|
|
for _, calEvent := range calEvents {
|
|
timestamp := fmt.Sprintf("[%s]%s", widget.descriptionColor(calEvent), calEvent.Timestamp())
|
|
|
|
title := fmt.Sprintf("[%s]%s",
|
|
widget.titleColor(calEvent),
|
|
widget.eventSummary(calEvent, calEvent.ConflictsWith(calEvents)),
|
|
)
|
|
|
|
lineOne := fmt.Sprintf(
|
|
"%s %s %s %s[white]\n",
|
|
widget.dayDivider(calEvent, prevEvent),
|
|
widget.responseIcon(calEvent),
|
|
timestamp,
|
|
title,
|
|
)
|
|
|
|
str = str + fmt.Sprintf("%s %s%s\n",
|
|
lineOne,
|
|
widget.location(calEvent),
|
|
widget.timeUntil(calEvent),
|
|
)
|
|
|
|
if (widget.location(calEvent) != "") || (widget.timeUntil(calEvent) != "") {
|
|
str = str + "\n"
|
|
}
|
|
|
|
prevEvent = calEvent
|
|
}
|
|
|
|
return str
|
|
}
|
|
|
|
func (widget *Widget) dayDivider(event, prevEvent *CalEvent) string {
|
|
var prevStartTime time.Time
|
|
|
|
if prevEvent != nil {
|
|
prevStartTime = prevEvent.Start()
|
|
}
|
|
|
|
// round times to midnight for comparison
|
|
toMidnight := func(t time.Time) time.Time {
|
|
t = t.Local()
|
|
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
|
}
|
|
prevStartDay := toMidnight(prevStartTime)
|
|
eventStartDay := toMidnight(event.Start())
|
|
|
|
if !eventStartDay.Equal(prevStartDay) {
|
|
|
|
return fmt.Sprintf("[%s::b]",
|
|
wtf.Config.UString("wtf.mods.gcal.colors.day", "forestgreen")) +
|
|
event.Start().Format(wtf.FullDateFormat) +
|
|
"\n"
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (widget *Widget) descriptionColor(calEvent *CalEvent) string {
|
|
if calEvent.Past() {
|
|
return wtf.Config.UString("wtf.mods.gcal.colors.past", "gray")
|
|
} else {
|
|
return wtf.Config.UString("wtf.mods.gcal.colors.description", "white")
|
|
}
|
|
}
|
|
|
|
func (widget *Widget) eventSummary(calEvent *CalEvent, conflict bool) string {
|
|
summary := calEvent.event.Summary
|
|
|
|
if calEvent.Now() {
|
|
summary = fmt.Sprintf(
|
|
"%s %s",
|
|
wtf.Config.UString("wtf.mods.gcal.currentIcon", "🔸"),
|
|
summary,
|
|
)
|
|
}
|
|
|
|
if conflict {
|
|
return fmt.Sprintf("%s %s", wtf.Config.UString("wtf.mods.gcal.conflictIcon", "🚨"), summary)
|
|
} else {
|
|
return summary
|
|
}
|
|
}
|
|
|
|
// timeUntil returns the number of hours or days until the event
|
|
// If the event is in the past, returns nil
|
|
func (widget *Widget) timeUntil(calEvent *CalEvent) string {
|
|
duration := time.Until(calEvent.Start()).Round(time.Minute)
|
|
|
|
if duration < 0 {
|
|
return ""
|
|
}
|
|
|
|
days := duration / (24 * time.Hour)
|
|
duration -= days * (24 * time.Hour)
|
|
|
|
hours := duration / time.Hour
|
|
duration -= hours * time.Hour
|
|
|
|
mins := duration / time.Minute
|
|
|
|
untilStr := ""
|
|
|
|
color := "[lightblue]"
|
|
if days > 0 {
|
|
untilStr = fmt.Sprintf("%dd", days)
|
|
} else if hours > 0 {
|
|
untilStr = fmt.Sprintf("%dh", hours)
|
|
} else {
|
|
untilStr = fmt.Sprintf("%dm", mins)
|
|
if mins < 30 {
|
|
color = "[red]"
|
|
}
|
|
}
|
|
|
|
return color + untilStr + "[white]"
|
|
}
|
|
|
|
func (widget *Widget) titleColor(calEvent *CalEvent) string {
|
|
color := wtf.Config.UString("wtf.mods.gcal.colors.title", "white")
|
|
|
|
for _, untypedArr := range wtf.Config.UList("wtf.mods.gcal.colors.highlights") {
|
|
highlightElements := wtf.ToStrs(untypedArr.([]interface{}))
|
|
|
|
match, _ := regexp.MatchString(
|
|
strings.ToLower(highlightElements[0]),
|
|
strings.ToLower(calEvent.event.Summary),
|
|
)
|
|
|
|
if match == true {
|
|
color = highlightElements[1]
|
|
}
|
|
}
|
|
|
|
if calEvent.Past() {
|
|
color = wtf.Config.UString("wtf.mods.gcal.colors.past", "gray")
|
|
}
|
|
|
|
return color
|
|
}
|
|
|
|
func (widget *Widget) location(calEvent *CalEvent) string {
|
|
if wtf.Config.UBool("wtf.mods.gcal.displayLocation", true) == false {
|
|
return ""
|
|
}
|
|
|
|
if calEvent.event.Location == "" {
|
|
return ""
|
|
}
|
|
|
|
return fmt.Sprintf(
|
|
"[%s]%s ",
|
|
widget.descriptionColor(calEvent),
|
|
calEvent.event.Location,
|
|
)
|
|
}
|
|
|
|
func (widget *Widget) responseIcon(calEvent *CalEvent) string {
|
|
if false == wtf.Config.UBool("wtf.mods.gcal.displayResponseStatus", true) {
|
|
return ""
|
|
}
|
|
|
|
icon := "[gray]"
|
|
|
|
switch calEvent.ResponseFor(wtf.Config.UString("wtf.mods.gcal.email")) {
|
|
case "accepted":
|
|
return icon + "✔︎"
|
|
case "declined":
|
|
return icon + "✘"
|
|
case "needsAction":
|
|
return icon + "?"
|
|
case "tentative":
|
|
return icon + "~"
|
|
default:
|
|
return icon + " "
|
|
}
|
|
|
|
return " "
|
|
}
|