diff web/live/Light9AttrControl.ts @ 2376:4556eebe5d73

topdir reorgs; let pdm have its src/ dir; separate vite area from light9/
author drewp@bigasterisk.com
date Sun, 12 May 2024 19:02:10 -0700
parents light9/web/live/Light9AttrControl.ts@06bf6dae8e64
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/live/Light9AttrControl.ts	Sun May 12 19:02:10 2024 -0700
@@ -0,0 +1,195 @@
+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;
+    // }
+  }
+}