Files @ 62dc1b3644a0
Branch filter:

Location: light9/web/live/Light9AttrControl.ts

drewp@bigasterisk.com
collector client uses rdf types, not strings
import debug from "debug";
import { css, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { Literal, NamedNode } from "n3";
import { SubEvent } from "sub-events";
import { getTopGraph } from "../RdfdbSyncedGraph";
import { SyncedGraph } from "../SyncedGraph";
import { ControlValue, Effect } from "./Effect";
import { DeviceAttrRow } from "./Light9DeviceControl";
export { Slider } from "@material/mwc-slider";
export { Light9ColorPicker } from "../light9-color-picker";
export { Light9Listbox } from "./Light9Listbox";
const log = debug("settings.dev.attr");

type DataTypeNames = "scalar" | "color" | "choice";
const makeType = (d: DataTypeNames) => new NamedNode(`http://light9.bigasterisk.com/${d}`);

// UI for one device attr (of any type).
@customElement("light9-attr-control")
export class Light9AttrControl extends LitElement {
  graph!: SyncedGraph;

  static styles = [
    css`
      #colorControls {
        display: flex;
        align-items: center;
      }
      #colorControls > * {
        margin: 0 3px;
      }
      :host {
      }
      mwc-slider {
        width: 250px;
      }
    `,
  ];

  @property() deviceAttrRow: DeviceAttrRow | null = null;
  @state() dataType: DataTypeNames = "scalar";
  @property() effect: Effect | null = null;
  @property() enableChange: boolean = false;
  @property() value: ControlValue | null = null; // e.g. color string

  constructor() {
    super();
    getTopGraph().then((g) => {
      this.graph = g;
      if (this.deviceAttrRow === null) throw new Error();
    });
  }

  connectedCallback(): void {
    super.connectedCallback();
    setTimeout(() => {
      // only needed once per page layout
      this.shadowRoot?.querySelector("mwc-slider")?.layout(/*skipUpdateUI=*/ false);
    }, 1);
  }

  render() {
    if (this.deviceAttrRow === null) throw new Error();
    if (this.dataType == "scalar") {
      const v = this.value || 0;
      return html`<mwc-slider .value=${v} step=${1 / 255} min="0" max="1" @input=${this.onValueInput}></mwc-slider> `;
    } else if ((this.dataType = "color")) {
      const v = this.value || "#000";
      return html`
        <div id="colorControls">
          <button @click=${this.goBlack}>0.0</button>
          <light9-color-picker .color=${v} @input=${this.onValueInput}></light9-color-picker>
        </div>
      `;
    } else if (this.dataType == "choice") {
      return html`<light9-listbox .choices=${this.deviceAttrRow.choices} .value=${this.value}> </light9-listbox> `;
    }
  }

  updated(changedProperties: PropertyValues<this>) {
    super.updated(changedProperties);

    if (changedProperties.has("deviceAttrRow")) {
      this.onDeviceAttrRowProperty();
    }
    if (changedProperties.has("effect")) {
      this.onEffectProperty();
    }
    if (changedProperties.has("value")) {
      this.onValueProperty();
    }
  }

  private onValueProperty() {
    if (this.deviceAttrRow === null) throw new Error();
    if (!this.graph) {
      log('ignoring value change- no graph yet')
      return;
    }
    if (this.effect === null) {
      this.value = null;
    } else {
      const p = this.effect.edit(
        //
        this.deviceAttrRow.device,
        this.deviceAttrRow.uri,
        this.value
      );
      if (!p.isEmpty()) {
        log("Effect told us to graph.patch this:\n", p.dump());
        this.graph.applyAndSendPatch(p);
      }
    }
  }

  private onEffectProperty() {
    if (this.effect === null) {
      log('no effect obj yet')
      return;
    }
    // effect will read graph changes on its own, but emit an event when it does
    this.effect.settingsChanged.subscribe(() => {
      this.effectSettingsChanged();
    });
    this.effectSettingsChanged();
  }

  private effectSettingsChanged() {
    // something in the settings graph is new
    if (this.deviceAttrRow === null) throw new Error();
    if (this.effect === null) throw new Error();
    // log("graph->ui on ", this.deviceAttrRow.device, this.deviceAttrRow.uri);
    const v = this.effect.currentValue(this.deviceAttrRow.device, this.deviceAttrRow.uri);
    this.onGraphValueChanged(v);
  }

  private onDeviceAttrRowProperty() {
    if (this.deviceAttrRow === null) throw new Error();
    const d = this.deviceAttrRow.dataType;
    if (d.equals(makeType("scalar"))) {
      this.dataType = "scalar";
    } else if (d.equals(makeType("color"))) {
      this.dataType = "color";
    } else if (d.equals(makeType("choice"))) {
      this.dataType = "choice";
    }
  }

  onValueInput(ev: CustomEvent) {
    if (ev.detail === undefined) {
      // not sure what this is, but it seems to be followed by good events
      return;
    }
    // log(ev.type, ev.detail.value);
    this.value = ev.detail.value;
    // this.graphToControls.controlChanged(this.device, this.deviceAttrRow.uri, ev.detail.value);
  }

  onGraphValueChanged(v: ControlValue | null) {
    if (this.deviceAttrRow === null) throw new Error();
    // log("change: control must display", v, "for", this.deviceAttrRow.device.value, this.deviceAttrRow.uri.value);
    // this.enableChange = false;
    if (this.dataType == "scalar") {
      if (v !== null) {
        this.value = v;
      } else {
        this.value = 0;
      }
    } else if (this.dataType == "color") {
      this.value = v;
    }
  }

  goBlack() {
    this.value = "#000000";
  }

  onChoice(value: any) {
    // if (value != null) {
    //   value = this.graph.Uri(value);
    // } else {
    //   value = null;
    // }
  }

  onChange(value: any) {
    // if (typeof value === "number" && isNaN(value)) {
    //   return;
    // } // let onChoice do it
    // //log('change: control tells graph', @deviceAttrRow.uri.value, value)
    // if (value === undefined) {
    //   value = null;
    // }
  }
}