Files @ 69ca2b2fc133
Branch filter:

Location: light9/web/collector/Light9CollectorUi.ts

drewp@bigasterisk.com
overcomplicated attempt at persisting the pane layout in the rdf graph

this was hard because we have to somehow wait for the graph to load before config'ing the panes
import debug from "debug";
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { NamedNode } from "n3";
import ReconnectingWebSocket from "reconnectingwebsocket";
import { sortBy, uniq } from "underscore";
import { Patch } from "../patch";
import { getTopGraph } from "../RdfdbSyncedGraph";
import { SyncedGraph } from "../SyncedGraph";
import { Light9CollectorDevice } from "./Light9CollectorDevice";
export { RdfdbSyncedGraph } from "../RdfdbSyncedGraph";
export { Light9CollectorDevice };
import { avro } from "../lib/avro";
debug.enable("*");
const log = debug("collector");

@customElement("light9-collector-ui")
export class Light9CollectorUi extends LitElement {
  graph!: SyncedGraph;
  render() {
    return html`<rdfdb-synced-graph></rdfdb-synced-graph>
      <h1>Collector</h1>

      <h2>Devices</h2>
      <div style="column-width: 11em">${this.devices.map((d) => html`<light9-collector-device .uri=${d}></light9-collector-device>`)}</div> `;
  }

  @property() devices: NamedNode[] = [];

  constructor() {
    super();
    getTopGraph().then((g) => {
      this.graph = g;
      this.graph.runHandler(this.findDevices.bind(this), "findDevices");
    });

    this.setupListener();
  }

  async setupListener() {
    const CollectorUpdateType = await avro.loadType("CollectorUpdate");
    const ws = new ReconnectingWebSocket(`ws://${location.host}/service/collector/updates`);
    ws.addEventListener("message", async (ev: ReconnectingWebSocket.MessageEvent) => {
      const jsMsg = await avro.parseBlob(CollectorUpdateType, ev.data);

      const outputAttrsSet = jsMsg.OutputAttrsSet;
      if (outputAttrsSet) {
        this.updateDev(outputAttrsSet.dev, outputAttrsSet.attrs);
      }
    });
  }
  findDevices(patch?: Patch) {
    const U = this.graph.U();

    this.devices = [];
    this.clearDeviceChildElementCache();
    let classes = this.graph.subjects(U("rdf:type"), U(":DeviceClass"));
    uniq(sortBy(classes, "value"), true).forEach((dc) => {
      sortBy(this.graph.subjects(U("rdf:type"), dc), "value").forEach((dev) => {
        this.devices.push(dev as NamedNode);
      });
    });
  }

  deviceElements: Map<string, Light9CollectorDevice> = new Map();

  clearDeviceChildElementCache() {
    this.deviceElements = new Map();
  }

  findDeviceChildElement(uri: string): Light9CollectorDevice | undefined {
    const known = this.deviceElements.get(uri);
    if (known) {
      return known;
    }

    for (const el of this.shadowRoot!.querySelectorAll("light9-collector-device")) {
      const eld = el as Light9CollectorDevice;
      if (eld.uri.value == uri) {
        this.deviceElements.set(uri, eld);
        return eld;
      }
    }

    return undefined;
  }

  updateDev(uri: string, attrs: { attr: string; chan: string; val: string; valClass: string }[]) {
    const el = this.findDeviceChildElement(uri);
    if (!el) {
      // unresolved race: updates come in before we have device elements to display them
      setTimeout(() => this.updateDev(uri, attrs), 300);
      return;
    }
    el.setAttrs(attrs);
  }
}