Mercurial > code > home > repos > front-door-display
changeset 15:20d1fa4250c0
refactor
author | drewp@bigasterisk.com |
---|---|
date | Thu, 06 Jun 2024 17:52:28 -0700 |
parents | 899f12179b85 |
children | 719c8cc4d8b2 |
files | src/DisplayEvent.ts src/FdClock.ts src/FdCountdown.ts src/UpcomingEvents.ts src/main.ts src/parseRdf.ts |
diffstat | 6 files changed, 234 insertions(+), 214 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/DisplayEvent.ts Thu Jun 06 17:52:28 2024 -0700 @@ -0,0 +1,45 @@ +import { addHours, endOfToday, endOfTomorrow, format, isAfter, isWithinInterval, parseISO, startOfToday } from "date-fns"; +import { TemplateResult, html } from "lit"; +import { DataFactory, NamedNode, Quad_Subject, Store, Term } from "n3"; +import { getLiteral } from "./parseRdf"; +import { hideFeeds, hideTitles } from "./private"; +const EV = "http://bigasterisk.com/event#"; +const { namedNode } = DataFactory; + +export class DisplayEvent { + constructor(private store: Store, private graph: Term, public uri: Quad_Subject) {} + get title(): string { + return getLiteral(this.store, this.graph, this.uri, namedNode(EV + "title"), "(unnamed)"); + } + get start(): string { + return getLiteral(this.store, this.graph, this.uri, namedNode(EV + "start"), null); + } + get feed(): NamedNode { + return namedNode(getLiteral(this.store, this.graph, this.uri, namedNode(EV + "feed"), null)); + } + shortDate(): TemplateResult { + const t = parseISO(this.start); + return html`<span class="d">${format(t, "EEE, MMM d,")}</span> <span class="t">${format(t, "HH:mm")}</span>`; + } + inHowLong(): TemplateResult { + // returns start()-now, like '5 days' + const t = parseISO(this.start).valueOf(); + const now = Date.now(); + const daysAway = (t - now) / 1000 / 86400; + const prec = daysAway < 2 ? 1 : 0; + const cls = "until " + (daysAway < 2 ? "until-2d" : daysAway < 7 ? "until-7d" : daysAway < 30 ? "until-1mo" : ""); + return html`<span class="${cls}">${daysAway.toFixed(prec)} days</span>`; + } + show(): boolean { + const now = new Date(); + const t = parseISO(this.start); + + const start = startOfToday(); + let end = endOfToday(); + if (isAfter(now, addHours(startOfToday(), 18))) { + end = endOfTomorrow(); + } + + return isWithinInterval(t, { start, end }) && !hideTitles.has(this.title) && !hideFeeds.has(this.feed.value); + } +}
--- a/src/FdClock.ts Thu Jun 06 17:51:38 2024 -0700 +++ b/src/FdClock.ts Thu Jun 06 17:52:28 2024 -0700 @@ -1,7 +1,8 @@ import { css, html, LitElement } from "lit"; import { shared } from "./shared"; +import { customElement } from "lit/decorators.js"; - +@customElement("fd-clock") export class FdClock extends LitElement { constructor() { super(); @@ -25,4 +26,3 @@ return html` <span class="t">${h}:${m.slice(0, 2)}:${s.slice(0, 2)}</span> `; } } -customElements.define("fd-clock", FdClock);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/FdCountdown.ts Thu Jun 06 17:52:28 2024 -0700 @@ -0,0 +1,83 @@ +import { LitElement, css, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { sortBy } from "lodash"; +import { DataFactory, Quad_Subject, Store } from "n3"; +import { shared } from "./shared"; +import { EV, fetchGraph, parseGraph, RDF } from "./parseRdf"; +import { DisplayEvent } from "./DisplayEvent"; + +const { namedNode } = DataFactory; + +@customElement("fd-countdown") +export class FdCountdown extends LitElement { + static styles = [ + shared, + css` + ol { + list-style: none; + font-size: 16px; + background: #cd66bb2e; + padding: 9px; + width: fit-content; + position: relative; + top: -21px; + border-radius: 14px; + } + span.d { + opacity: 0.5; + } + li:has(.until) { + color: #666; + } + li:has(.until-2d) { + color: #fff; + } + li:has(.until-7d) { + color: #ccc; + } + li:has(.until-30d) { + color: #999; + } + li:has(.until)::before { + display: inline-block; + width: 1.4em; + content: ""; + } + li:has(.until-2d)::before { + content: "🌕"; + } + li:has(.until-7d)::before { + content: "🌙"; + } + li:has(.until-30d)::before { + content: "🌑"; + } + `, + ]; + @property() evs: DisplayEvent[]; + constructor() { + super(); + this.evs = []; + this.load(); + setInterval(this.load.bind(this), 5 * 60 * 1000); + } + async load() { + const store = new Store(); + const r = await fetchGraph("/gcalendarwatch/graph/calendar/countdown"); + await parseGraph(r, (store: Store) => { + const graph = namedNode(EV + "gcalendar"); + this.evs = []; + store.getSubjects(namedNode(RDF + "type"), namedNode(EV + "Event"), graph).forEach((ev: Quad_Subject) => { + const de = new DisplayEvent(store, graph, ev); + this.evs = [...this.evs, de]; + }); + this.evs = sortBy(this.evs, "start"); + }); + } + render() { + return html`<h1 data-text="Coming Soon">Coming Soon</h1> + <ol> + ${this.evs.map((d) => html`<li>In ${d.inHowLong()}, ${d.title}</li>`)} + </ol> `; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/UpcomingEvents.ts Thu Jun 06 17:52:28 2024 -0700 @@ -0,0 +1,62 @@ +import { LitElement, css, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { sortBy } from "lodash"; +import { DataFactory, Quad_Subject, Store } from "n3"; +import { shared } from "./shared"; +import { EV, fetchGraph, parseGraph, RDF } from "./parseRdf"; +import { DisplayEvent } from "./DisplayEvent"; + +const { namedNode } = DataFactory; + +@customElement("fd-upcoming-events") +export class UpcomingEvents extends LitElement { + static styles = [ + shared, + css` + ol { + list-style-type: circle; + font-size: 16px; + background: #cd66bb2e; + padding: 9px; + width: fit-content; + position: relative; + top: -21px; + border-radius: 14px; + } + span.d { + opacity: 0.5; + } + `, + ]; + @property() evs: DisplayEvent[]; + constructor() { + super(); + this.evs = []; + this.load(); + setInterval(this.load.bind(this), 5 * 60 * 1000); + } + + async load() { + const r = await fetchGraph("/gcalendarwatch/graph/calendar/upcoming"); + await parseGraph(r, (store: Store) => { + const graph = namedNode(EV + "gcalendar"); + this.evs = []; + store.getSubjects(namedNode(RDF + "type"), namedNode(EV + "Event"), graph).forEach((ev: Quad_Subject) => { + const de = new DisplayEvent(store, graph, ev); + if (de.show()) { + this.evs = [...this.evs, de]; + } + }); + this.evs = sortBy(this.evs, "start"); + }); + } + + render() { + return html` + <h1 data-text="Calendar">Calendar</h1> + <ol> + ${this.evs.map((d) => html`<li><span class="date">${d.shortDate()}</span> ${d.title}</li>`)} + </ol> + `; + } +}
--- a/src/main.ts Thu Jun 06 17:51:38 2024 -0700 +++ b/src/main.ts Thu Jun 06 17:52:28 2024 -0700 @@ -1,214 +1,4 @@ -import { addHours, endOfToday, endOfTomorrow, format, isAfter, isWithinInterval, parseISO, startOfToday } from "date-fns"; -import { LitElement, TemplateResult, css, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; -import { sortBy } from "lodash"; -import { DataFactory, NamedNode, Parser, Quad_Predicate, Quad_Subject, Store, Term } from "n3"; -import { hideFeeds, hideTitles } from "./private"; -import { shared } from "./shared"; -const { namedNode } = DataFactory; export { FdClock } from "./FdClock"; +export { FdCountdown } from "./FdCountdown"; +export { UpcomingEvents } from "./UpcomingEvents"; export { WeekGuide } from "./WeekGuide"; -const EV = "http://bigasterisk.com/event#"; -const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; - -function getLiteral(store: Store, graph: Term, subj: Quad_Subject, pred: Quad_Predicate, missing: string | null): string { - let out = null; - store.getObjects(subj, pred, graph).forEach((attr) => { - out = attr.value; - }); - if (!out) { - if (missing === null) { - throw new Error(); - } - return missing; - } - return out; -} - -class DisplayEvent { - constructor(private store: Store, private graph: Term, public uri: Quad_Subject) {} - get title(): string { - return getLiteral(this.store, this.graph, this.uri, namedNode(EV + "title"), "(unnamed)"); - } - get start(): string { - return getLiteral(this.store, this.graph, this.uri, namedNode(EV + "start"), null); - } - get feed(): NamedNode { - return namedNode(getLiteral(this.store, this.graph, this.uri, namedNode(EV + "feed"), null)); - } - shortDate(): TemplateResult { - const t = parseISO(this.start); - return html`<span class="d">${format(t, "EEE, MMM d,")}</span> <span class="t">${format(t, "HH:mm")}</span>`; - } - inHowLong(): TemplateResult { - // returns start()-now, like '5 days' - const t = parseISO(this.start).valueOf(); - const now = Date.now(); - const daysAway = (t - now) / 1000 / 86400; - const prec = daysAway < 2 ? 1 : 0; - const cls = "until " + (daysAway < 2 ? "until-2d" : daysAway < 7 ? "until-7d" : daysAway < 30 ? "until-1mo" : ""); - return html`<span class="${cls}">${daysAway.toFixed(prec)} days</span>`; - } - show(): boolean { - const now = new Date(); - const t = parseISO(this.start); - - const start = startOfToday(); - let end = endOfToday(); - if (isAfter(now, addHours(startOfToday(), 18))) { - end = endOfTomorrow(); - } - - return isWithinInterval(t, { start, end }) && !hideTitles.has(this.title) && !hideFeeds.has(this.feed.value); - } -} - -async function fetchGraph(url: string) { - return await fetch(url, { - method: "GET", - headers: { - Accept: "application/json", - "X-Pomerium-Authorization": document.cookie.substring(document.cookie.indexOf("=") + 1), - }, - }); -} - -async function parseGraph(r: Response, done: (store: Store) => void) { - const store = new Store(); - const n3txt = await r.text(); - const parser = new Parser({ format: "application/trig" }); - parser.parse(n3txt, (error, quad, prefixes) => { - if (quad) { - store.addQuad(quad); - } else { - done(store); - } - }); -} - -@customElement("fd-upcoming-events") -export class UpcomingEvents extends LitElement { - static styles = [ - shared, - css` - ol { - list-style-type: circle; - font-size: 16px; - background: #cd66bb2e; - padding: 9px; - width: fit-content; - position: relative; - top: -21px; - border-radius: 14px; - } - span.d { - opacity: 0.5; - } - `, - ]; - @property() evs: DisplayEvent[]; - constructor() { - super(); - this.evs = []; - this.load(); - setInterval(this.load.bind(this), 5 * 60 * 1000); - } - - async load() { - const r = await fetchGraph("/gcalendarwatch/graph/calendar/upcoming"); - await parseGraph(r, (store: Store) => { - const graph = namedNode(EV + "gcalendar"); - this.evs = []; - store.getSubjects(namedNode(RDF + "type"), namedNode(EV + "Event"), graph).forEach((ev: Quad_Subject) => { - const de = new DisplayEvent(store, graph, ev); - if (de.show()) { - this.evs = [...this.evs, de]; - } - }); - this.evs = sortBy(this.evs, "start"); - }); - } - - render() { - return html` - <h1 data-text="Calendar">Calendar</h1> - <ol> - ${this.evs.map((d) => html`<li><span class="date">${d.shortDate()}</span> ${d.title}</li>`)} - </ol> - `; - } -} - -@customElement("fd-countdown") -export class FdCountdown extends LitElement { - static styles = [ - shared, - css` - ol { - list-style: none; - font-size: 16px; - background: #cd66bb2e; - padding: 9px; - width: fit-content; - position: relative; - top: -21px; - border-radius: 14px; - } - span.d { - opacity: 0.5; - } - li:has(.until) { - color: #666; - } - li:has(.until-2d) { - color: #fff; - } - li:has(.until-7d) { - color: #ccc; - } - li:has(.until-30d) { - color: #999; - } - li:has(.until)::before { - display: inline-block; - width: 1.4em; - content: ""; - } - li:has(.until-2d)::before { - content: "🌕"; - } - li:has(.until-7d)::before { - content: "🌙"; - } - li:has(.until-30d)::before { - content: "🌑"; - } - `, - ]; - @property() evs: DisplayEvent[]; - constructor() { - super(); - this.evs = []; - this.load(); - setInterval(this.load.bind(this), 5 * 60 * 1000); - } - async load() { - const store = new Store(); - const r = await fetchGraph("/gcalendarwatch/graph/calendar/countdown"); - await parseGraph(r, (store: Store) => { - const graph = namedNode(EV + "gcalendar"); - this.evs = []; - store.getSubjects(namedNode(RDF + "type"), namedNode(EV + "Event"), graph).forEach((ev: Quad_Subject) => { - const de = new DisplayEvent(store, graph, ev); - this.evs = [...this.evs, de]; - }); - this.evs = sortBy(this.evs, "start"); - }); - } - render() { - return html`<h1 data-text="Coming Soon">Coming Soon</h1> - <ol> - ${this.evs.map((d) => html`<li>In ${d.inHowLong()}, ${d.title}</li>`)} - </ol> `; - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/parseRdf.ts Thu Jun 06 17:52:28 2024 -0700 @@ -0,0 +1,40 @@ +import { Parser, Quad_Predicate, Quad_Subject, Store, Term } from "n3"; +export const EV = "http://bigasterisk.com/event#"; +export const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + +export function getLiteral(store: Store, graph: Term, subj: Quad_Subject, pred: Quad_Predicate, missing: string | null): string { + let out = null; + store.getObjects(subj, pred, graph).forEach((attr) => { + out = attr.value; + }); + if (!out) { + if (missing === null) { + throw new Error(); + } + return missing; + } + return out; +} + +export async function fetchGraph(url: string) { + return await fetch(url, { + method: "GET", + headers: { + Accept: "application/json", + "X-Pomerium-Authorization": document.cookie.substring(document.cookie.indexOf("=") + 1), + }, + }); +} + +export async function parseGraph(r: Response, done: (store: Store) => void) { + const store = new Store(); + const n3txt = await r.text(); + const parser = new Parser({ format: "application/trig" }); + parser.parse(n3txt, (error, quad, prefixes) => { + if (quad) { + store.addQuad(quad); + } else { + done(store); + } + }); +}