diff web/live/Light9DeviceSettings.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/Light9DeviceSettings.ts@06bf6dae8e64
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/live/Light9DeviceSettings.ts	Sun May 12 19:02:10 2024 -0700
@@ -0,0 +1,153 @@
+import debug from "debug";
+import { css, html, LitElement, PropertyValues } from "lit";
+import { customElement, property } from "lit/decorators.js";
+import { NamedNode } from "n3";
+import { sortBy, uniq } from "underscore";
+import { Patch } from "../patch";
+import { getTopGraph } from "../RdfdbSyncedGraph";
+import { SyncedGraph } from "../SyncedGraph";
+import { Effect, newEffect } from "./Effect";
+export { EditChoice } from "../EditChoice";
+export { Light9DeviceControl as Light9LiveDeviceControl } from "./Light9DeviceControl";
+const log = debug("settings");
+
+@customElement("light9-device-settings")
+export class Light9DeviceSettings extends LitElement {
+  graph!: SyncedGraph;
+
+  static styles = [
+    css`
+      :host {
+        display: flex;
+        flex-direction: column;
+      }
+      #preview {
+        width: 100%;
+      }
+      #deviceControls {
+        flex-grow: 1;
+        position: relative;
+        width: 100%;
+        overflow-y: auto;
+      }
+
+      light9-device-control > div {
+        break-inside: avoid-column;
+      }
+      light9-device-control {
+        vertical-align: top;
+      }
+    `,
+  ];
+
+  render() {
+    return html`
+      <rdfdb-synced-graph></rdfdb-synced-graph>
+
+      <h1>effect DeviceSettings</h1>
+
+      <div id="save">
+        <div>
+          <button @click=${this.newEffect}>New effect</button>
+          <edit-choice .uri=${this.currentEffect ? this.currentEffect.uri : null} @edited=${this.onEffectChoice2} rename></edit-choice>
+          <button @click=${this.clearAll}>clear settings in this effect</button>
+        </div>
+      </div>
+
+      <div id="deviceControls">
+        ${this.devices.map(
+          (device: NamedNode) => html`
+            <light9-device-control .uri=${device} .effect=${this.currentEffect}> .graphToControls={this.graphToControls} </light9-device-control>
+          `
+        )}
+      </div>
+    `;
+  }
+
+  devices: Array<NamedNode> = [];
+  @property() currentEffect: Effect | null = null;
+  okToWriteUrl: boolean = false;
+
+  constructor() {
+    super();
+
+    getTopGraph().then((g) => {
+      this.graph = g;
+      this.graph.runHandler(this.compile.bind(this), "findDevices");
+      this.setEffectFromUrl();
+    });
+  }
+
+  onEffectChoice2(ev: CustomEvent) {
+    const uri = ev.detail.newValue as NamedNode;
+    this.setCurrentEffect(uri);
+  }
+  setCurrentEffect(uri: NamedNode) {
+    if (uri === null) {
+      this.currentEffect = null;
+      // todo: wipe the UI settings
+    } else {
+      this.currentEffect = new Effect(this.graph, uri);
+    }
+  }
+
+  updated(changedProperties: PropertyValues<this>) {
+    log("ctls udpated", changedProperties);
+    if (changedProperties.has("currentEffect")) {
+      log(`effectChoice to ${this.currentEffect?.uri?.value}`);
+      this.writeToUrl(this.currentEffect?.uri);
+    }
+    // this.graphToControls?.debugDump();
+  }
+
+  // Note that this doesn't fetch setting values, so it only should get rerun
+  // upon (rarer) changes to the devices etc. todo: make that be true
+  private compile(patch?: Patch) {
+    const U = this.graph.U();
+    // if (patch && !patchContainsPreds(patch, [U("rdf:type")])) {
+    //   return;
+    // }
+
+    this.devices = [];
+    let classes = this.graph.subjects(U("rdf:type"), U(":DeviceClass"));
+    log(`found ${classes.length} device classes`);
+    uniq(sortBy(classes, "value"), true).forEach((dc) => {
+      sortBy(this.graph.subjects(U("rdf:type"), dc), "value").forEach((dev) => {
+        this.devices.push(dev as NamedNode);
+      });
+    });
+    this.requestUpdate();
+  }
+
+  setEffectFromUrl() {
+    // not a continuous bidi link between url and effect; it only reads
+    // the url when the page loads.
+    const effect = new URL(window.location.href).searchParams.get("effect");
+    if (effect != null) {
+      this.currentEffect = new Effect(this.graph, this.graph.Uri(effect));
+    }
+    this.okToWriteUrl = true;
+  }
+
+  writeToUrl(effect: NamedNode | undefined) {
+    const effectStr = effect ? this.graph.shorten(effect) : "";
+    if (!this.okToWriteUrl) {
+      return;
+    }
+    const u = new URL(window.location.href);
+    if ((u.searchParams.get("effect") || "") === effectStr) {
+      return;
+    }
+    u.searchParams.set("effect", effectStr); // this escapes : and / and i wish it didn't
+    window.history.replaceState({}, "", u.href);
+    log("wrote new url", u.href);
+  }
+
+  newEffect() {
+    this.setCurrentEffect(newEffect(this.graph));
+  }
+
+  clearAll() {
+    this.currentEffect?.clearAllSettings()
+  }
+}