Files: 7a1861362de764504e02614b8c8bc5837fdc089d / app / page / calendar.js
4758 bytesRaw
1 | const nest = require('depnest') |
2 | const { h, Array: MutantArray, Struct, computed, watch, throttle } = require('mutant') |
3 | const pull = require('pull-stream') |
4 | const { isMsg } = require('ssb-ref') |
5 | |
6 | exports.gives = nest('app.page.calendar') |
7 | |
8 | exports.needs = nest({ |
9 | 'sbot.pull.stream': 'first' |
10 | }) |
11 | |
12 | exports.create = (api) => { |
13 | return nest('app.page.calendar', calendarPage) |
14 | |
15 | function calendarPage (location) { |
16 | const d = new Date() |
17 | const state = Struct({ |
18 | today: new Date(d.getFullYear(), d.getMonth(), d.getDate()), |
19 | year: d.getFullYear(), |
20 | events: MutantArray([]) |
21 | }) |
22 | |
23 | watch(state.year, year => getEvents(year, state.events)) |
24 | |
25 | return h('CalendarPage', { title: '/calendar' }, [ |
26 | Calendar(state) |
27 | ]) |
28 | } |
29 | |
30 | function getEvents (year, events) { |
31 | const query = [{ |
32 | $filter: { |
33 | value: { |
34 | timestamp: {$gt: Number(new Date(year, 0, 1))}, // ordered by published time |
35 | content: { |
36 | type: 'about', |
37 | startDateTime: { |
38 | epoch: {$gt: 0} |
39 | } |
40 | } |
41 | } |
42 | } |
43 | }, { |
44 | $map: { |
45 | key: ['value', 'content', 'about'], // gathering |
46 | date: ['value', 'content', 'startDateTime', 'epoch'] |
47 | } |
48 | }] |
49 | |
50 | const opts = { |
51 | reverse: false, |
52 | live: true, |
53 | query |
54 | } |
55 | |
56 | pull( |
57 | api.sbot.pull.stream(server => server.query.read(opts)), |
58 | pull.filter(m => !m.sync), |
59 | pull.filter(r => isMsg(r.key) && Number.isInteger(r.date)), |
60 | pull.map(r => { |
61 | return { key: r.key, date: new Date(r.date) } |
62 | }), |
63 | pull.drain(({ key, date }) => { |
64 | var target = events.find(ev => ev.data.key === key) |
65 | if (target && target.date <= date) events.delete(target) |
66 | |
67 | events.push({ date, data: { key } }) |
68 | }) |
69 | ) |
70 | } |
71 | } |
72 | |
73 | // ////////////////// extract below into a module /////////////////////// |
74 | |
75 | // Thanks to nomand for the inspiration and code (https://github.com/nomand/Letnice), |
76 | // they formed the foundation of this work |
77 | |
78 | const MONTHS = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ] |
79 | const DAYS = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ] |
80 | |
81 | function Calendar (state) { |
82 | // TODO assert events is an Array of object |
83 | // of form { date, data } |
84 | |
85 | return h('Calendar', [ |
86 | Header(state.year), |
87 | h('div.months', computed(throttle(state, 100), ({ today, year, events }) => { |
88 | return MONTHS.map((month, monthIndex) => { |
89 | return Month({ month, monthIndex, today, year, events }) |
90 | }) |
91 | })) |
92 | ]) |
93 | } |
94 | |
95 | function Header (year) { |
96 | return h('div.header', [ |
97 | h('div.year', [ |
98 | year, |
99 | h('a', { 'ev-click': () => year.set(year() - 1) }, '-'), |
100 | h('a', { 'ev-click': () => year.set(year() + 1) }, '+') |
101 | ]) |
102 | ]) |
103 | } |
104 | |
105 | function Month ({ month, monthIndex, today, year, events }) { |
106 | const monthLength = new Date(year, monthIndex + 1, 0).getDate() |
107 | // NOTE Date takes month as a monthIndex i.e. april = 3 |
108 | // and day = 0 goes back a day |
109 | const days = Array(monthLength).fill().map((_, i) => i + 1) |
110 | |
111 | var date |
112 | var dateEnd |
113 | var weekday |
114 | var week |
115 | var offset = getDay(new Date(year, monthIndex, 1)) - 1 |
116 | |
117 | return h('CalendarMonth', [ |
118 | h('div.month-name', month.substr(0, 2)), |
119 | h('div.days', { style: {display: 'grid'} }, [ |
120 | DAYS.map((day, i) => DayName(day, i)), |
121 | days.map(Day) |
122 | ]) |
123 | ]) |
124 | |
125 | function Day (day) { |
126 | date = new Date(year, monthIndex, day) |
127 | dateEnd = new Date(year, monthIndex, day + 1) |
128 | weekday = getDay(date) |
129 | week = Math.ceil((day + offset) / 7) |
130 | |
131 | const eventsOnDay = events.filter(e => { |
132 | return e.date >= date && e.date < dateEnd |
133 | }) |
134 | |
135 | const opts = { |
136 | attributes: { |
137 | 'title': `${year}-${monthIndex + 1}-${day}`, |
138 | 'data-date': `${year}-${monthIndex + 1}-${day}` |
139 | }, |
140 | style: { |
141 | 'grid-row': `${weekday} / ${weekday + 1}`, |
142 | 'grid-column': `${week + 1} / ${week + 2}` |
143 | // column moved by 1 to make space for labels |
144 | }, |
145 | classList: [ |
146 | date < today ? '-past' : '-future', |
147 | eventsOnDay.length ? '-events' : '' |
148 | ] |
149 | } |
150 | |
151 | if (!eventsOnDay.length) return h('CalendarDay', opts) |
152 | |
153 | return h('CalendarDay', opts, [ |
154 | // TODO try a FontAwesome circle |
155 | h('div', [ |
156 | // Math.random() > 0.3 ? h('div') : '' |
157 | ]) |
158 | ]) |
159 | } |
160 | } |
161 | |
162 | function DayName (day, index) { |
163 | return h('CalendarDayName', { |
164 | style: { |
165 | 'grid-row': `${index + 1} / ${index + 2}`, |
166 | 'grid-column': '1 / 2' |
167 | } |
168 | }, day.substr(0, 1)) |
169 | } |
170 | |
171 | function getDay (date) { |
172 | const dayIndex = date.getDay() |
173 | return dayIndex === 0 ? 7 : dayIndex |
174 | |
175 | // Weeks run 0...6 (Sun - Sat) |
176 | // this shifts those days around by 1 |
177 | } |
178 | |
179 |
Built with git-ssb-web