Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/glance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pages:
widgets:
- type: calendar
first-day-of-week: monday
ics:
- https://www.voetbalkrant.com/soccer/calendar/team/team_43_nl.ics
- https://www.voetbalkrant.com/soccer/calendar/team/team_1_nl.ics

- type: rss
limit: 10
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
require (
github.com/PuerkitoBio/goquery v1.10.3 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/arran4/golang-ical v0.3.2
github.com/ebitengine/purego v0.8.4 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiU
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/arran4/golang-ical v0.3.2 h1:MGNjcXJFSuCXmYX/RpZhR2HDCYoFuK8vTPFLEdFC3JY=
github.com/arran4/golang-ical v0.3.2/go.mod h1:xblDGxxIUMWwFZk9dlECUlc1iXNV65LJZOTHLVwu8bo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
16 changes: 16 additions & 0 deletions internal/glance/static/css/widget-calendar.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
color: var(--color-text-highlight);
}

.calendar-event-date {
color: var(--color-primary)
}

.calendar-spillover-date {
color: var(--color-text-subdue);
}
Expand Down Expand Up @@ -69,3 +73,15 @@
height: 2rem;
margin-left: 0.7rem;
}

.calendar-event-tooltip {
position: absolute;
background: rgba(0,0,0,0.8);
color: white;
padding: 5px 10px;
border-radius: 4px;
pointer-events: none;
display: none;
z-index: 9999;
font-size: 12px;
}
73 changes: 67 additions & 6 deletions internal/glance/static/js/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ const undoEntrance = slideFade({ direction: "left", distance: "100%", duration:

export default function(element) {
element.swapWith(Calendar(
Number(element.dataset.firstDayOfWeek ?? 1)
Number(element.dataset.firstDayOfWeek ?? 1),
element.dataset.events
));
}

// TODO: when viewing the previous/next month, display the current date if it's within the spill-over days
function Calendar(firstDay) {
function Calendar(firstDay, event) {
let header, dates;
let advanceTimeTicker;
let now = new Date();
Expand All @@ -63,7 +64,7 @@ function Calendar(firstDay) {

const calendar = elem().classes("calendar").append(
header = Header(nextClicked, prevClicked, undoClicked),
dates = Dates(firstDay)
dates = Dates(firstDay, event)
);

update(now);
Expand Down Expand Up @@ -127,7 +128,7 @@ function Header(nextClicked, prevClicked, undoClicked) {
});
}

function Dates(firstDay) {
function Dates(firstDay, events) {
let dates, lastRenderedDate;

const updateFullMonth = function(now, newDate) {
Expand All @@ -152,12 +153,39 @@ function Dates(firstDay) {
)
}

for (let i = 1; i <= currentMonthDays; i++, index++) {
children[index]
const tooltip = document.createElement("div");
tooltip.className = "calendar-event-tooltip"; // style this in CSS
document.body.appendChild(tooltip);
for (let i = 2; i <= currentMonthDays; i++, index++) {
const thisDate = new Date(newDate.getFullYear(), newDate.getMonth(), i);
var child = children[index];
child
.classesIf(isCurrentMonth && i === currentDate, "calendar-current-date")
.text(i);
if(events && events !== "null") {
const hasEvent = checkIfDateHasEvent(newDate, i, events);
if(hasEvent) {
child.classes("calendar-event-date")
child.addEventListener("mouseenter", (e) => {
tooltip.innerHTML = getEventsForDate(thisDate, events).join("<br>")
tooltip.style.display = "block";
tooltip.style.left = e.pageX + 10 + "px";
tooltip.style.top = e.pageY + 10 + "px";
});

child.addEventListener("mousemove", (e) => {
tooltip.style.left = e.pageX + 10 + "px";
tooltip.style.top = e.pageY + 10 + "px";
});

child.addEventListener("mouseleave", () => {
tooltip.style.display = "none";
});
}
}
}


for (let i = 0; i < nextMonthSpilloverDays; i++, index++) {
children[index].classes("calendar-spillover-date").text(i + 1);
}
Expand Down Expand Up @@ -210,3 +238,36 @@ function msTillNextDay(now) {
now.getHours() * 3_600_000
);
}

function checkIfDateHasEvent(activeMonth, date, events) {
const eventsObject = JSON.parse(events)

return eventsObject.some(event => {
const eventDate = new Date(event.Date)
return eventDate.getDate() === date && activeMonth.getMonth() === eventDate.getMonth()
})
}

function getEventsForDate(date, events) {
const eventsObject = JSON.parse(events)
//const target = formatDateLocal(date)

return eventsObject
.filter(ev => {
//const evDate = formatDateLocal(new Date(ev.Date))
const evDate = new Date(ev.Date)
return isSameDay(date, evDate)
})
.map(ev => {
const evDate = new Date(ev.Date)
const hours = String(evDate.getHours()).padStart(2, '0');
const minutes = String(evDate.getMinutes()).padStart(2, '0');
return `${hours}:${minutes} - ${ev.Name}`;
});
}

function isSameDay(dateOnly, timeDate) {
return dateOnly.getFullYear() === timeDate.getFullYear() &&
dateOnly.getMonth() === timeDate.getMonth() &&
dateOnly.getDate() === timeDate.getDate();
}
2 changes: 1 addition & 1 deletion internal/glance/templates/calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

{{ define "widget-content" }}
<div class="widget-small-content-bounds">
<div class="calendar" data-first-day-of-week="{{ .FirstDay }}"></div>
<div class="calendar" data-events="{{ .Events }}" data-first-day-of-week="{{ .FirstDay }}"></div>
</div>
{{ end }}
50 changes: 50 additions & 0 deletions internal/glance/widget-calendar.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package glance

import (
"encoding/json"
"errors"
"fmt"
"html/template"
"net/http"
"time"

ics "github.com/arran4/golang-ical"
)

var calendarWidgetTemplate = mustParseTemplate("calendar.html", "widget-base.html")
Expand All @@ -21,8 +26,15 @@ var calendarWeekdaysToInt = map[string]time.Weekday{
type calendarWidget struct {
widgetBase `yaml:",inline"`
FirstDayOfWeek string `yaml:"first-day-of-week"`
Ics []string `yaml:"ics"`
FirstDay int `yaml:"-"`
cachedHTML template.HTML `yaml:"-"`
Events string `yaml:"events"`
}

type calendarEvent struct {
Date time.Time
Name string
}

func (widget *calendarWidget) initialize() error {
Expand All @@ -34,6 +46,30 @@ func (widget *calendarWidget) initialize() error {
return errors.New("invalid first day of week")
}

var events []*ics.VEvent
var widgetEvents []calendarEvent
for _, url := range widget.Ics {
newEvents, err := ReadPublicIcs(url)
if err != nil {
fmt.Println(err)
}
events = append(events, newEvents...)
}

for _, event := range events {
startDate, _ := event.GetStartAt()
e := calendarEvent{
Date: startDate,
Name: event.GetProperty("SUMMARY").Value,
}
widgetEvents = append(widgetEvents, e)

}
jsonBytes, err := json.Marshal(widgetEvents)
if err != nil {
panic(err)
}
widget.Events = string(jsonBytes)
widget.FirstDay = int(calendarWeekdaysToInt[widget.FirstDayOfWeek])
widget.cachedHTML = widget.renderTemplate(widget, calendarWidgetTemplate)

Expand All @@ -43,3 +79,17 @@ func (widget *calendarWidget) initialize() error {
func (widget *calendarWidget) Render() template.HTML {
return widget.cachedHTML
}

func ReadPublicIcs(url string) ([]*ics.VEvent, error) {
response, err := http.Get(url)
if err != nil {
return nil, err
}
defer response.Body.Close()
cal, err := ics.ParseCalendar(response.Body)
if err != nil {
return nil, err
}
events := cal.Events()
return events, nil
}