Files @ bbff83207963
Branch filter:

Location: light9/web/live/Light9DeviceControl.ts

drewp@bigasterisk.com
minor cleanups and timings
import debug from "debug";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { NamedNode } from "n3";
import { unique } from "underscore";
import { Patch } from "../patch";
import { getTopGraph } from "../RdfdbSyncedGraph";
import { SyncedGraph } from "../SyncedGraph";
import { Choice } from "./Light9Listbox";
import { Light9AttrControl } from "./Light9AttrControl";
import { Effect } from "./Effect";
export { ResourceDisplay } from "../ResourceDisplay";
export { Light9AttrControl };
const log = debug("settings.dev");

export interface DeviceAttrRow {
  uri: NamedNode; //devattr
  device: NamedNode;
  attrClasses: string; // the css kind
  dataType: NamedNode;
  choices: Choice[];
  // choiceSize: number;
  // max: number;
}

// Widgets for one device with multiple Light9LiveControl rows for the attr(s).
@customElement("light9-device-control")
export class Light9DeviceControl extends LitElement {
  graph!: SyncedGraph;
  static styles = [
    css`
      :host {
        display: inline-block;
      }
      .device {
        border: 2px solid #151e2d;
        margin: 4px;
        padding: 1px;
        background: #171717; /* deviceClass gradient added later */
        break-inside: avoid-column;
        width: 335px;
      }
      .deviceAttr {
        border-top: 1px solid #272727;
        padding-bottom: 2px;
        display: flex;
      }
      .deviceAttr > span {
      }
      .deviceAttr > light9-live-control {
        flex-grow: 1;
      }
      h2 {
        font-size: 110%;
        padding: 4px;
        margin-top: 0;
        margin-bottom: 0;
      }
      .device,
      h2 {
        border-top-right-radius: 15px;
      }

      #mainLabel {
        font-size: 120%;
        color: #9ab8fd;
        text-decoration: initial;
      }
      .device.selected h2 {
        outline: 3px solid #ffff0047;
      }
      .deviceAttr.selected {
        background: #cada1829;
      }
    `,
  ];

  render() {
    return html`
      <div class="device ${this.devClasses}">
        <h2 style="${this._bgStyle(this.deviceClass)}" @click=${this.onClick}>
          <resource-display id="mainLabel" .uri="${this.uri}"></resource-display>
          a <resource-display minor .uri="${this.deviceClass}"></resource-display>
        </h2>

        ${this.deviceAttrs.map(
          (dattr: DeviceAttrRow) => html`
            <div @click="onAttrClick" class="deviceAttr ${dattr.attrClasses}">
              <span>
                attr
                <resource-display minor .uri=${dattr.uri}></resource-display>
              </span>
              <light9-attr-control .deviceAttrRow=${dattr} .effect=${this.effect}>
              </light9-attr-control>
            </div>
          `
        )}
      </div>
    `;
  }

  @property() uri!: NamedNode;
  @property() effect!: Effect;

  @property() devClasses: string = ""; // the css kind
  @property() deviceAttrs: DeviceAttrRow[] = [];
  @property() deviceClass: NamedNode | null = null;
  @property() selectedAttrs: Set<NamedNode> = new Set();

  constructor() {
    super();
    getTopGraph().then((g) => {
      this.graph = g;
      this.graph.runHandler(this.syncDeviceAttrsFromGraph.bind(this), `${this.uri.value} update`);
    });
    this.selectedAttrs = new Set();
  }

  _bgStyle(deviceClass: NamedNode | null): string {
    if (!deviceClass) return "";
    let hash = 0;
    const u = deviceClass.value;
    for (let i = u.length - 10; i < u.length; i++) {
      hash += u.charCodeAt(i);
    }
    const hue = (hash * 8) % 360;
    const accent = `hsl(${hue}, 49%, 22%)`;
    return `background: linear-gradient(to right, rgba(31,31,31,0) 50%, ${accent} 100%);`;
  }

  setDeviceSelected(isSel: any) {
    this.devClasses = isSel ? "selected" : "";
  }

  setAttrSelected(devAttr: NamedNode, isSel: boolean) {
    if (isSel) {
      this.selectedAttrs.add(devAttr);
    } else {
      this.selectedAttrs.delete(devAttr);
    }
  }

  syncDeviceAttrsFromGraph(patch?: Patch) {
    const U = this.graph.U();
    if (patch && !patch.containsAnyPreds([U("rdf:type"), U(":deviceAttr"), U(":dataType"), U(":choice")])) {
      return;
    }
    try {
      this.deviceClass = this.graph.uriValue(this.uri, U("rdf:type"));
    } catch (e) {
      // what's likely is we're going through a graph reload and the graph
      // is gone but the controls remain
    }
    this.deviceAttrs = [];
    Array.from(unique(this.graph.sortedUris(this.graph.objects(this.deviceClass, U(":deviceAttr"))))).map((da: NamedNode) =>
      this.deviceAttrs.push(this.attrRow(da))
    );
    this.requestUpdate();
  }

  attrRow(devAttr: NamedNode): DeviceAttrRow {
    let x: NamedNode;
    const U = (x: string) => this.graph.Uri(x);
    const dataType = this.graph.uriValue(devAttr, U(":dataType"));
    const daRow = {
      uri: devAttr,
      device: this.uri,
      dataType,
      attrClasses: this.selectedAttrs.has(devAttr) ? "selected" : "",
      choices: [] as Choice[],
      choiceSize: 0,
      max: 1,
    };
     if (dataType.equals(U(":choice"))) {
      const choiceUris = this.graph.sortedUris(this.graph.objects(devAttr, U(":choice")));
      daRow.choices = (() => {
        const result = [];
        for (x of Array.from(choiceUris)) {
          result.push({ uri: x.value, label: this.graph.labelOrTail(x) });
        }
        return result;
      })();
      daRow.choiceSize = Math.min(choiceUris.length + 1, 10);
    } else {
      daRow.max = 1;
      if (dataType.equals(U(":angle"))) {
        // varies
        daRow.max = 1;
      }
    }
    return daRow;
  }

  clear() {
    // why can't we just set their values ? what's diff about
    // the clear state, and should it be represented with `null` value?
    throw new Error();
    // Array.from(this.shadowRoot!.querySelectorAll("light9-live-control")).map((lc: Element) => (lc as Light9LiveControl).clear());
  }

  onClick(ev: any) {
    log("click", this.uri);
    // select, etc
  }

  onAttrClick(ev: { model: { dattr: { uri: any } } }) {
    log("attr click", this.uri, ev.model.dattr.uri);
    // select
  }
}