alps theme: initial layout for calendar
This commit is contained in:
parent
50cb8bef77
commit
7142da950d
4 changed files with 262 additions and 1 deletions
|
@ -18,9 +18,16 @@ import (
|
|||
type CalendarRenderData struct {
|
||||
alps.BaseRenderData
|
||||
Time time.Time
|
||||
Now time.Time
|
||||
Dates [7 * 6]time.Time
|
||||
Calendar *caldav.Calendar
|
||||
Events []CalendarObject
|
||||
PrevPage, NextPage string
|
||||
PrevTime, NextTime time.Time
|
||||
|
||||
EventsForDate func(time.Time) []CalendarObject
|
||||
DaySuffix func(n int) string
|
||||
Sub func(a, b int) int
|
||||
}
|
||||
|
||||
type EventRenderData struct {
|
||||
|
@ -96,13 +103,66 @@ func registerRoutes(p *alps.GoPlugin, u *url.URL) {
|
|||
return fmt.Errorf("failed to query calendar: %v", err)
|
||||
}
|
||||
|
||||
// TODO: Time zones are hard
|
||||
var dates [7 * 6]time.Time
|
||||
initialDate := start.UTC()
|
||||
initialDate = initialDate.AddDate(0, 0, -int(initialDate.Weekday()))
|
||||
for i := 0; i < len(dates); i += 1 {
|
||||
dates[i] = initialDate
|
||||
initialDate = initialDate.AddDate(0, 0, 1)
|
||||
}
|
||||
|
||||
eventMap := make(map[time.Time][]CalendarObject)
|
||||
for _, ev := range events {
|
||||
ev := ev // make a copy
|
||||
// TODO: include event on each date for which it is active
|
||||
co := ev.Data.Events()[0]
|
||||
startTime, _ := co.DateTimeStart(nil)
|
||||
startTime = startTime.UTC().Truncate(time.Hour * 24)
|
||||
eventMap[startTime] = append(eventMap[startTime], CalendarObject{&ev})
|
||||
}
|
||||
|
||||
return ctx.Render(http.StatusOK, "calendar.html", &CalendarRenderData{
|
||||
BaseRenderData: *alps.NewBaseRenderData(ctx),
|
||||
Time: start,
|
||||
Now: time.Now(), // TODO: Use client time zone
|
||||
Calendar: calendar,
|
||||
Dates: dates,
|
||||
Events: newCalendarObjectList(events),
|
||||
PrevPage: start.AddDate(0, -1, 0).Format(monthPageLayout),
|
||||
NextPage: start.AddDate(0, 1, 0).Format(monthPageLayout),
|
||||
PrevTime: start.AddDate(0, -1, 0),
|
||||
NextTime: start.AddDate(0, 1, 0),
|
||||
|
||||
EventsForDate: func(when time.Time) []CalendarObject {
|
||||
if events, ok := eventMap[when.Truncate(time.Hour * 24)]; ok {
|
||||
return events
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
DaySuffix: func(n int) string {
|
||||
if n % 100 >= 11 && n % 100 <= 13 {
|
||||
return "th"
|
||||
}
|
||||
return map[int]string{
|
||||
0: "th",
|
||||
1: "st",
|
||||
2: "nd",
|
||||
3: "rd",
|
||||
4: "th",
|
||||
5: "th",
|
||||
6: "th",
|
||||
7: "th",
|
||||
8: "th",
|
||||
9: "th",
|
||||
}[n % 10]
|
||||
},
|
||||
|
||||
Sub: func (a, b int) int {
|
||||
// Why isn't this built-in, come on Go
|
||||
return a - b
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -261,6 +261,11 @@ main.new-contact .actions > *:not(:last-child) {
|
|||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.actions h3 {
|
||||
align-self: center;
|
||||
margin: 0 1.3rem 0 1rem;
|
||||
}
|
||||
|
||||
.message-list-subject a { color: #77c; }
|
||||
|
||||
.message-list-unread.message-list-sender,
|
||||
|
@ -370,7 +375,7 @@ main table tfoot {
|
|||
padding: 0.3rem 0.5rem;
|
||||
}
|
||||
|
||||
.actions-pagination .button-link:first-child:not(:last-child) {
|
||||
.actions-pagination .button-link:not(:last-child) {
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
|
||||
|
@ -444,6 +449,100 @@ main table tfoot {
|
|||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
main.calendar .dates {
|
||||
flex-grow: 1;
|
||||
padding: 0.3rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
grid-template-rows: auto 1fr 1fr 1fr 1fr 1fr 1fr auto;
|
||||
grid-template-areas:
|
||||
"sunday-top monday-top wednesday-top tuesday-top thursday-top friday-top saturday-top"
|
||||
"dates dates dates dates dates dates dates"
|
||||
"dates dates dates dates dates dates dates"
|
||||
"dates dates dates dates dates dates dates"
|
||||
"dates dates dates dates dates dates dates"
|
||||
"dates dates dates dates dates dates dates"
|
||||
"dates dates dates dates dates dates dates"
|
||||
"sunday-bottom monday-bottom wednesday-bottom tuesday-bottom thursday-bottom friday-bottom saturday-bottom";
|
||||
grid-gap: 0.3rem;
|
||||
}
|
||||
|
||||
main.calendar .dates .weekday {
|
||||
text-align: center;
|
||||
font-size: 1.1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
main.calendar .dates .sunday-top { grid-area: sunday-top; }
|
||||
main.calendar .dates .monday-top { grid-area: monday-top; }
|
||||
main.calendar .dates .tuesday-top { grid-area: tuesday-top; }
|
||||
main.calendar .dates .wednesday-top { grid-area: wednesday-top; }
|
||||
main.calendar .dates .thursday-top { grid-area: thursday-top; }
|
||||
main.calendar .dates .friday-top { grid-area: friday-top; }
|
||||
main.calendar .dates .saturday-top { grid-area: saturday-top; }
|
||||
main.calendar .dates .sunday-bottom { grid-area: sunday-bottom; }
|
||||
main.calendar .dates .monday-bottom { grid-area: monday-bottom; }
|
||||
main.calendar .dates .tuesday-bottom { grid-area: tuesday-bottom; }
|
||||
main.calendar .dates .wednesday-bottom { grid-area: wednesday-bottom; }
|
||||
main.calendar .dates .thursday-bottom { grid-area: thursday-bottom; }
|
||||
main.calendar .dates .friday-bottom { grid-area: friday-bottom; }
|
||||
main.calendar .dates .saturday-bottom { grid-area: saturday-bottom; }
|
||||
|
||||
main.calendar .date {
|
||||
border: 1px solid #eee;
|
||||
padding: 0.3rem;
|
||||
background: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
main.calendar .date.active {
|
||||
background-color: #f6fff6;
|
||||
border: 1px solid #afa;
|
||||
}
|
||||
|
||||
main.calendar .date .date-link {
|
||||
position: absolute;
|
||||
top: 0; right: 0; bottom: 0; left: 0;
|
||||
}
|
||||
|
||||
main.calendar .date.extra {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
main.calendar .date ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding-left: 0.3rem;
|
||||
}
|
||||
|
||||
main.calendar .date .events {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
main.calendar .date.extra .events {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
main.calendar .events .start-time {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
main.calendar .events .overflow {
|
||||
color: #444;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
main.calendar .date h4 {
|
||||
font-weight: normal;
|
||||
text-align: right;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
main.calendar .date h4 .da { font-size: 1.2rem; }
|
||||
|
||||
input[type="submit"],
|
||||
.button,
|
||||
button,
|
||||
|
|
18
themes/alps/calendar-header.html
Normal file
18
themes/alps/calendar-header.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<div class="actions-wrap">
|
||||
<div class="actions-pagination" style="margin-left: 0;">
|
||||
<a href="?month={{.PrevPage}}" class="button-link">« {{.PrevTime.Format "January"}}</a>
|
||||
<h3>{{.Time.Format "January 2006"}}</h3>
|
||||
<a href="?month={{.NextPage}}" class="button-link">{{.NextTime.Format "January"}} »</a>
|
||||
{{if ne .Time.Month .Now.Month}}
|
||||
<a href="/calendar" class="button-link">Today »</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<form method="get" class="actions-search action-group">
|
||||
<input
|
||||
type="text"
|
||||
name="query"
|
||||
placeholder="Search {{.Calendar.Name}} events...">
|
||||
<button>Search</button>
|
||||
</form>
|
||||
</div>
|
84
themes/alps/calendar.html
Normal file
84
themes/alps/calendar.html
Normal file
|
@ -0,0 +1,84 @@
|
|||
{{template "head.html" .}}
|
||||
{{template "nav.html" .}}
|
||||
|
||||
<div class="page-wrap">
|
||||
<aside>
|
||||
<a href="/calendar/create" class="new">New event</a>
|
||||
<!-- TODO: fetch list of address books -->
|
||||
<a href="#" class="active">{{.Calendar.Name}}</a>
|
||||
<a href="#">Personal</a>
|
||||
</aside>
|
||||
|
||||
<div class="container">
|
||||
<main class="calendar">
|
||||
<section class="actions">
|
||||
{{ template "calendar-header.html" . }}
|
||||
</section>
|
||||
|
||||
<section class="dates">
|
||||
<h4 class="weekday sunday-top">Sunday</h4>
|
||||
<h4 class="weekday monday-top">Monday</h4>
|
||||
<h4 class="weekday tuesday-top">Tuesday</h4>
|
||||
<h4 class="weekday wednesday-top">Wednesday</h4>
|
||||
<h4 class="weekday thursday-top">Thursday</h4>
|
||||
<h4 class="weekday friday-top">Friday</h4>
|
||||
<h4 class="weekday saturday-top">Saturday</h4>
|
||||
|
||||
{{$base := .}}
|
||||
{{range .Dates}}
|
||||
<div class="date
|
||||
{{if ne $base.Time.Month .Month}}extra{{end}}
|
||||
{{if and (eq $base.Now.Month .Month) (eq $base.Now.Day .Day)}}active{{end}}
|
||||
">
|
||||
{{if eq $base.Time.Month .Month}}
|
||||
<a href="#" class="date-link"></a>
|
||||
{{end}}
|
||||
<div class="events">
|
||||
{{$events := (call $base.EventsForDate .)}}
|
||||
{{if $events}}
|
||||
<ul>
|
||||
{{$overflow := 0}}
|
||||
{{if gt (len $events) 3}}
|
||||
{{$overflow = call $base.Sub (len $events) 3}}
|
||||
{{$events = slice $events 0 3}}
|
||||
{{end}}
|
||||
|
||||
{{range $events}}
|
||||
{{$event := index .Data.Events 0}}
|
||||
<li>
|
||||
<span class="start-time">
|
||||
{{($event.DateTimeStart nil).Format "15:04"}}
|
||||
</span>
|
||||
{{$event.Props.Text "SUMMARY"}}
|
||||
</li>
|
||||
{{end}}
|
||||
{{if ne $overflow 0}}
|
||||
<li class="overflow">...and {{$overflow}} more</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
</div>
|
||||
<h4>
|
||||
<span class="mo">{{.Format "Jan"}}</span>
|
||||
<span class="da">{{.Format "2"}}{{call $base.DaySuffix .Day}}</span>
|
||||
</h4>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<h4 class="weekday sunday-bottom">Sunday</h4>
|
||||
<h4 class="weekday monday-bottom">Monday</h4>
|
||||
<h4 class="weekday tuesday-bottom">Tuesday</h4>
|
||||
<h4 class="weekday wednesday-bottom">Wednesday</h4>
|
||||
<h4 class="weekday thursday-bottom">Thursday</h4>
|
||||
<h4 class="weekday friday-bottom">Friday</h4>
|
||||
<h4 class="weekday saturday-bottom">Saturday</h4>
|
||||
</section>
|
||||
|
||||
<section class="actions">
|
||||
{{ template "calendar-header.html" . }}
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "foot.html"}}
|
Loading…
Reference in a new issue