diff 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
line wrap: on
line diff
--- a/src/main.ts	Thu Jun 06 14:49:34 2024 -0700
+++ b/src/main.ts	Thu Jun 06 16:39:51 2024 -0700
@@ -1,11 +1,12 @@
-import { addHours, endOfToday, endOfTomorrow, format, isAfter, isBefore, isToday, isTomorrow, isWithinInterval, parseISO, startOfToday } from "date-fns";
-import { css, html, LitElement, TemplateResult } from "lit";
+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 { WeekGuide } from "./WeekGuide";
 const EV = "http://bigasterisk.com/event#";
 const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
@@ -39,6 +40,15 @@
     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);
@@ -51,59 +61,33 @@
 
     return isWithinInterval(t, { start, end }) && !hideTitles.has(this.title) && !hideFeeds.has(this.feed.value);
   }
-  toHtml(): TemplateResult {
-    return html`
-      <li>
-        <span class="date">${this.shortDate()}</span> ${this.title}
-        <!--${this.feed}-->
-      </li>
-    `;
-  }
+}
+
+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 {
-  @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 fetch("/gcalendarwatch/graph/calendar/upcoming",
-    
-    {
-      method: 'GET',
-      headers: {
-        Accept: 'application/json',
-        'X-Pomerium-Authorization': document.cookie.substring(
-          document.cookie.indexOf('=') + 1,
-        ),
-      },
-    }
-    
-    );
-    const n3txt = await r.text();
-    const parser = new Parser({ format: "application/trig" });
-    parser.parse(n3txt, (error, quad, prefixes) => {
-      if (quad) {
-        store.addQuad(quad);
-      } else {
-        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");
-      }
-    });
-  }
   static styles = [
     shared,
     css`
@@ -120,17 +104,111 @@
       span.d {
         opacity: 0.5;
       }
-      span.t {
-        color: #50fa7b;
-      }
     `,
   ];
+  @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) => d.toHtml())}
+        ${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> `;
+  }
+}