Mercurial > code > home > repos > front-door-display
comparison src/main.ts @ 13:deb0c25655eb
cleanup, add FdClock and Countdown
author | drewp@bigasterisk.com |
---|---|
date | Thu, 06 Jun 2024 16:39:51 -0700 |
parents | 33178e5e356e |
children | 20d1fa4250c0 |
comparison
equal
deleted
inserted
replaced
12:4092f674046d | 13:deb0c25655eb |
---|---|
1 import { addHours, endOfToday, endOfTomorrow, format, isAfter, isBefore, isToday, isTomorrow, isWithinInterval, parseISO, startOfToday } from "date-fns"; | 1 import { addHours, endOfToday, endOfTomorrow, format, isAfter, isWithinInterval, parseISO, startOfToday } from "date-fns"; |
2 import { css, html, LitElement, TemplateResult } from "lit"; | 2 import { LitElement, TemplateResult, css, html } from "lit"; |
3 import { customElement, property } from "lit/decorators.js"; | 3 import { customElement, property } from "lit/decorators.js"; |
4 import { sortBy } from "lodash"; | 4 import { sortBy } from "lodash"; |
5 import { DataFactory, NamedNode, Parser, Quad_Predicate, Quad_Subject, Store, Term } from "n3"; | 5 import { DataFactory, NamedNode, Parser, Quad_Predicate, Quad_Subject, Store, Term } from "n3"; |
6 import { hideFeeds, hideTitles } from "./private"; | 6 import { hideFeeds, hideTitles } from "./private"; |
7 import { shared } from "./shared"; | 7 import { shared } from "./shared"; |
8 const { namedNode } = DataFactory; | 8 const { namedNode } = DataFactory; |
9 export { FdClock } from "./FdClock"; | |
9 export { WeekGuide } from "./WeekGuide"; | 10 export { WeekGuide } from "./WeekGuide"; |
10 const EV = "http://bigasterisk.com/event#"; | 11 const EV = "http://bigasterisk.com/event#"; |
11 const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; | 12 const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; |
12 | 13 |
13 function getLiteral(store: Store, graph: Term, subj: Quad_Subject, pred: Quad_Predicate, missing: string | null): string { | 14 function getLiteral(store: Store, graph: Term, subj: Quad_Subject, pred: Quad_Predicate, missing: string | null): string { |
37 } | 38 } |
38 shortDate(): TemplateResult { | 39 shortDate(): TemplateResult { |
39 const t = parseISO(this.start); | 40 const t = parseISO(this.start); |
40 return html`<span class="d">${format(t, "EEE, MMM d,")}</span> <span class="t">${format(t, "HH:mm")}</span>`; | 41 return html`<span class="d">${format(t, "EEE, MMM d,")}</span> <span class="t">${format(t, "HH:mm")}</span>`; |
41 } | 42 } |
43 inHowLong(): TemplateResult { | |
44 // returns start()-now, like '5 days' | |
45 const t = parseISO(this.start).valueOf(); | |
46 const now = Date.now(); | |
47 const daysAway = (t - now) / 1000 / 86400; | |
48 const prec = daysAway < 2 ? 1 : 0; | |
49 const cls = "until " + (daysAway < 2 ? "until-2d" : daysAway < 7 ? "until-7d" : daysAway < 30 ? "until-1mo" : ""); | |
50 return html`<span class="${cls}">${daysAway.toFixed(prec)} days</span>`; | |
51 } | |
42 show(): boolean { | 52 show(): boolean { |
43 const now = new Date(); | 53 const now = new Date(); |
44 const t = parseISO(this.start); | 54 const t = parseISO(this.start); |
45 | 55 |
46 const start = startOfToday(); | 56 const start = startOfToday(); |
49 end = endOfTomorrow(); | 59 end = endOfTomorrow(); |
50 } | 60 } |
51 | 61 |
52 return isWithinInterval(t, { start, end }) && !hideTitles.has(this.title) && !hideFeeds.has(this.feed.value); | 62 return isWithinInterval(t, { start, end }) && !hideTitles.has(this.title) && !hideFeeds.has(this.feed.value); |
53 } | 63 } |
54 toHtml(): TemplateResult { | 64 } |
55 return html` | 65 |
56 <li> | 66 async function fetchGraph(url: string) { |
57 <span class="date">${this.shortDate()}</span> ${this.title} | 67 return await fetch(url, { |
58 <!--${this.feed}--> | 68 method: "GET", |
59 </li> | 69 headers: { |
60 `; | 70 Accept: "application/json", |
61 } | 71 "X-Pomerium-Authorization": document.cookie.substring(document.cookie.indexOf("=") + 1), |
72 }, | |
73 }); | |
74 } | |
75 | |
76 async function parseGraph(r: Response, done: (store: Store) => void) { | |
77 const store = new Store(); | |
78 const n3txt = await r.text(); | |
79 const parser = new Parser({ format: "application/trig" }); | |
80 parser.parse(n3txt, (error, quad, prefixes) => { | |
81 if (quad) { | |
82 store.addQuad(quad); | |
83 } else { | |
84 done(store); | |
85 } | |
86 }); | |
62 } | 87 } |
63 | 88 |
64 @customElement("fd-upcoming-events") | 89 @customElement("fd-upcoming-events") |
65 export class UpcomingEvents extends LitElement { | 90 export class UpcomingEvents extends LitElement { |
66 @property() evs: DisplayEvent[]; | |
67 constructor() { | |
68 super(); | |
69 this.evs = []; | |
70 this.load(); | |
71 setInterval(this.load.bind(this), 5 * 60 * 1000); | |
72 } | |
73 | |
74 async load() { | |
75 const store = new Store(); | |
76 const r = await fetch("/gcalendarwatch/graph/calendar/upcoming", | |
77 | |
78 { | |
79 method: 'GET', | |
80 headers: { | |
81 Accept: 'application/json', | |
82 'X-Pomerium-Authorization': document.cookie.substring( | |
83 document.cookie.indexOf('=') + 1, | |
84 ), | |
85 }, | |
86 } | |
87 | |
88 ); | |
89 const n3txt = await r.text(); | |
90 const parser = new Parser({ format: "application/trig" }); | |
91 parser.parse(n3txt, (error, quad, prefixes) => { | |
92 if (quad) { | |
93 store.addQuad(quad); | |
94 } else { | |
95 const graph = namedNode(EV + "gcalendar"); | |
96 this.evs = []; | |
97 store.getSubjects(namedNode(RDF + "type"), namedNode(EV + "Event"), graph).forEach((ev: Quad_Subject) => { | |
98 const de = new DisplayEvent(store, graph, ev); | |
99 if (de.show()) { | |
100 this.evs = [...this.evs, de]; | |
101 } | |
102 }); | |
103 this.evs = sortBy(this.evs, "start"); | |
104 } | |
105 }); | |
106 } | |
107 static styles = [ | 91 static styles = [ |
108 shared, | 92 shared, |
109 css` | 93 css` |
110 ol { | 94 ol { |
111 list-style-type: circle; | 95 list-style-type: circle; |
118 border-radius: 14px; | 102 border-radius: 14px; |
119 } | 103 } |
120 span.d { | 104 span.d { |
121 opacity: 0.5; | 105 opacity: 0.5; |
122 } | 106 } |
123 span.t { | |
124 color: #50fa7b; | |
125 } | |
126 `, | 107 `, |
127 ]; | 108 ]; |
109 @property() evs: DisplayEvent[]; | |
110 constructor() { | |
111 super(); | |
112 this.evs = []; | |
113 this.load(); | |
114 setInterval(this.load.bind(this), 5 * 60 * 1000); | |
115 } | |
116 | |
117 async load() { | |
118 const r = await fetchGraph("/gcalendarwatch/graph/calendar/upcoming"); | |
119 await parseGraph(r, (store: Store) => { | |
120 const graph = namedNode(EV + "gcalendar"); | |
121 this.evs = []; | |
122 store.getSubjects(namedNode(RDF + "type"), namedNode(EV + "Event"), graph).forEach((ev: Quad_Subject) => { | |
123 const de = new DisplayEvent(store, graph, ev); | |
124 if (de.show()) { | |
125 this.evs = [...this.evs, de]; | |
126 } | |
127 }); | |
128 this.evs = sortBy(this.evs, "start"); | |
129 }); | |
130 } | |
131 | |
128 render() { | 132 render() { |
129 return html` | 133 return html` |
130 <h1 data-text="Calendar">Calendar</h1> | 134 <h1 data-text="Calendar">Calendar</h1> |
131 <ol> | 135 <ol> |
132 ${this.evs.map((d) => d.toHtml())} | 136 ${this.evs.map((d) => html`<li><span class="date">${d.shortDate()}</span> ${d.title}</li>`)} |
133 </ol> | 137 </ol> |
134 `; | 138 `; |
135 } | 139 } |
136 } | 140 } |
141 | |
142 @customElement("fd-countdown") | |
143 export class FdCountdown extends LitElement { | |
144 static styles = [ | |
145 shared, | |
146 css` | |
147 ol { | |
148 list-style: none; | |
149 font-size: 16px; | |
150 background: #cd66bb2e; | |
151 padding: 9px; | |
152 width: fit-content; | |
153 position: relative; | |
154 top: -21px; | |
155 border-radius: 14px; | |
156 } | |
157 span.d { | |
158 opacity: 0.5; | |
159 } | |
160 li:has(.until) { | |
161 color: #666; | |
162 } | |
163 li:has(.until-2d) { | |
164 color: #fff; | |
165 } | |
166 li:has(.until-7d) { | |
167 color: #ccc; | |
168 } | |
169 li:has(.until-30d) { | |
170 color: #999; | |
171 } | |
172 li:has(.until)::before { | |
173 display: inline-block; | |
174 width: 1.4em; | |
175 content: ""; | |
176 } | |
177 li:has(.until-2d)::before { | |
178 content: "🌕"; | |
179 } | |
180 li:has(.until-7d)::before { | |
181 content: "🌙"; | |
182 } | |
183 li:has(.until-30d)::before { | |
184 content: "🌑"; | |
185 } | |
186 `, | |
187 ]; | |
188 @property() evs: DisplayEvent[]; | |
189 constructor() { | |
190 super(); | |
191 this.evs = []; | |
192 this.load(); | |
193 setInterval(this.load.bind(this), 5 * 60 * 1000); | |
194 } | |
195 async load() { | |
196 const store = new Store(); | |
197 const r = await fetchGraph("/gcalendarwatch/graph/calendar/countdown"); | |
198 await parseGraph(r, (store: Store) => { | |
199 const graph = namedNode(EV + "gcalendar"); | |
200 this.evs = []; | |
201 store.getSubjects(namedNode(RDF + "type"), namedNode(EV + "Event"), graph).forEach((ev: Quad_Subject) => { | |
202 const de = new DisplayEvent(store, graph, ev); | |
203 this.evs = [...this.evs, de]; | |
204 }); | |
205 this.evs = sortBy(this.evs, "start"); | |
206 }); | |
207 } | |
208 render() { | |
209 return html`<h1 data-text="Coming Soon">Coming Soon</h1> | |
210 <ol> | |
211 ${this.evs.map((d) => html`<li>In ${d.inHowLong()}, ${d.title}</li>`)} | |
212 </ol> `; | |
213 } | |
214 } |