Files @ 0617b6006ec4
Branch filter:

Location: light9/light9/web/live/Light9DeviceControl.ts - annotation

drewp@bigasterisk.com
ts cleanup
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
1b6e7016e3de
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
1b6e7016e3de
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
1b6e7016e3de
c4eab47d3c83
1b6e7016e3de
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
1b6e7016e3de
c4eab47d3c83
1b6e7016e3de
c4eab47d3c83
1b6e7016e3de
c4eab47d3c83
c4eab47d3c83
1b6e7016e3de
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
1b6e7016e3de
c4eab47d3c83
1b6e7016e3de
1b6e7016e3de
1b6e7016e3de
1b6e7016e3de
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
1b6e7016e3de
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
1b6e7016e3de
1b6e7016e3de
1b6e7016e3de
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
c4eab47d3c83
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, patchContainsPreds } from "../patch";
import { getTopGraph } from "../RdfdbSyncedGraph";
import { SyncedGraph } from "../SyncedGraph";
import { GraphToControls } from "./GraphToControls";
import { Choice } from "./Light9Listbox";
import { Light9LiveControl } from "./Light9LiveControl";
export { ResourceDisplay } from "../ResourceDisplay";
export { Light9LiveControl };
const log = debug("devcontrol");

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

@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)}" xon-click="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 xon-click="onAttrClick" class="deviceAttr ${dattr.attrClasses}">
              <span>attr <resource-display minor .uri="${dattr.uri}"></resource-display></span>
              <light9-live-control
                .device="${this.uri}"
                .deviceAttrRow="${dattr}"
                .effect="${this.effect}"
                .graphToControls="${this.graphToControls}"
              ></light9-live-control>
            </div>
          `
        )}
      </div>
    `;
  }

  @property() uri!: NamedNode;
  @property() effect!: NamedNode;
  @property() graphToControls!: GraphToControls;

  @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);
    }
    // this.syncDeviceAttrsFromGraph();
  }

  syncDeviceAttrsFromGraph(patch?: Patch) {
    const U = this.graph.U();
    if (patch != null && !patchContainsPreds(patch, [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,
      dataType,
      showColorPicker: dataType.equals(U(":color")),
      attrClasses: this.selectedAttrs.has(devAttr) ? "selected" : "",
      useColor: false,
      useChoice: false,
      choices: [] as Choice[],
      choiceSize: 0,
      useSlider: false,
      max: 1,
    };
    if (dataType.equals(U(":color"))) {
      daRow.useColor = true;
    } else if (dataType.equals(U(":choice"))) {
      daRow.useChoice = true;
      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.useSlider = true;
      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
  }
}