view src/render/StreamedGraph.ts @ 118:c2923b20bf5c

support multi labels per column
author drewp@bigasterisk.com
date Sun, 20 Mar 2022 00:54:19 -0700
parents 3cdbbd913f1d
children
line wrap: on
line source

import { html, LitElement, render, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Store } from "n3";
import { StreamedGraphClient } from "../layout/StreamedGraphClient";
import { ViewConfig } from "../layout/ViewConfig";
import { GraphView } from "./GraphView";
import { addFontToRootPage, style } from "./style";

export interface VersionedGraph {
  version: number;
  store: Store;
}

@customElement("streamed-graph")
export class StreamedGraph extends LitElement {
  @property({ type: String })
  url: string = "";
  @property({ type: String })
  view: string = "";
  @property({ type: Object })
  graph!: VersionedGraph;

  @property({ type: Boolean })
  expanded: boolean = false;

  @property({ type: String })
  status: string = "";

  sg!: StreamedGraphClient;
  graphViewDirty = true;

  static styles = style;

  render() {
    const expandAction = this.expanded ? "-" : "+";
    return html`
      <div id="ui">
        <span class="expander"
          ><button @click="${this.toggleExpand}">${expandAction}</button></span
        >
        StreamedGraph <a href="${this.url}">[source]</a>: ${this.status}
      </div>
      <div id="graphView"></div>
    `;
  }

  connectedCallback() {
    super.connectedCallback();
    addFontToRootPage();
    const emptyStore = new Store();
    this.graph = { version: -1, store: emptyStore };

    this._onUrl(this.url); // todo: watch for changes and rebuild
    if (this.expanded) {
      this.redrawGraph();
    }
  }

  toggleExpand() {
    this.expanded = !this.expanded;
    if (this.expanded) {
      this.redrawGraph();
    } else {
      this.graphViewDirty = false;
      this._graphAreaClose();
    }
  }

  redrawGraph() {
    this.graphViewDirty = true;
    const rl: () => Promise<void> = this._redrawLater.bind(this);
    requestAnimationFrame(rl);
  }

  _onUrl(url: string) {
    if (this.sg) {
      this.sg.close();
    }
    this.sg = new StreamedGraphClient(
      url,
      this.onGraphChanged.bind(this),
      (st) => {
        this.status = st;
      },
      [], //window.NS,
      []
    );
  }

  onGraphChanged() {
    this.graph = {
      version: this.graph.version + 1,
      store: this.sg.store,
    };
    if (this.expanded) {
      this.redrawGraph();
    }
    this.dispatchEvent(
      new CustomEvent("graph-changed", { detail: { graph: this.graph } })
    );
  }

  async _redrawLater() {
    if (!this.graphViewDirty) return;

    if ((this.graph as VersionedGraph).store && this.graph.store) {
      const vc = new ViewConfig();
      if (this.view) {
        await vc.readFromUrl(this.view); // too often!
      }

      await this._graphAreaShowGraph(
        new GraphView([this.url], this.graph.store, vc)
      );
      this.graphViewDirty = false;
    } else {
      this._graphAreaShowPending();
    }
  }

  _graphAreaClose() {
    this._setGraphArea(html``);
  }

  _graphAreaShowPending() {
    this._setGraphArea(html` <span>waiting for data...</span> `);
  }

  async _graphAreaShowGraph(graphView: GraphView) {
    this._setGraphArea(await graphView.makeTemplate());
  }

  _setGraphArea(tmpl: TemplateResult) {
    const el = this.shadowRoot?.getElementById("graphView");
    if (!el) {
      return;
    }
    render(tmpl, el);
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "streamed-graph": StreamedGraph;
  }
}

// // allow child nodes to combine a few graphs and statics
// //<streamed-graph id="timebankGraph"  graph="{{graph}}" expanded="true">
// //  <member-graph url="graph/timebank/events"></member-graph>
// //  <member-graph url="/some/static.n3"></member-graph>
// //</streamed-graph>