view light9/live/Light9DeviceSettings.ts @ 2247:f5e4aa36985d

checkpoint show data
author drewp@bigasterisk.com
date Sat, 27 May 2023 01:15:29 -0700
parents 5c269c03863d
children 75dfd7eb1e77
line wrap: on
line source

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, patchContainsPreds } from "../web/patch";
import { getTopGraph } from "../web/RdfdbSyncedGraph";
import { SyncedGraph } from "../web/SyncedGraph";
import { GraphToControls } from "./GraphToControls";
import { Effect } from "./Effect";
export { EditChoice } from "../web/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-live-device-control > div {
        break-inside: avoid-column;
      }
      light9-live-device-control {
      }
    `,
  ];

  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}></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> = [];
  // uri of the effect being edited, or null. This is the
  // master value; GraphToControls follows.
  @property() currentEffect: Effect | null = null;
  // graphToControls!: GraphToControls;
  okToWriteUrl: boolean = false;

  constructor() {
    super();

    getTopGraph().then((g) => {
      this.graph = g;
      this.graph.runHandler(this.findDevices.bind(this), "findDevices");
      // this.graphToControls = new GraphToControls(this.graph);
      // this.graph.runHandler(this.update.bind(this), "Light9LiveControls update");
      this.setEffectFromUrl();
    });
  }
  onEffectChoice2(ev: CustomEvent) {
    const uri = ev.detail.newValue as NamedNode;
    if (uri === null) {
      this.currentEffect = null;
    } 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.onEffectChoice();
    }
    // this.graphToControls?.debugDump();
  }

  // Note that this doesn't fetch setting values, so it only should get rerun
  // upon (rarer) changes to the devices etc.
  findDevices(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) => {
        log(`found dev ${dev.value}`);
        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) {
      log(`found effect in url ${effect}`);
      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.effectChoice = this.graphToControls.newEffect();
  }

  onEffectChoice() {
    const U = (x: any) => this.graph.Uri(x);
    // if (this.effectChoice == null) {
    //   // unlink
    //   log("onEffectChoice unlink");
    //   if (this.graphToControls != null) {
    //     this.graphToControls.setEffect(null);
    //   }
    // } else {
    //   if (this.graphToControls != null) {
    //     this.graphToControls.setEffect(this.effectChoice);
    //   } else {
    //     throw new Error("graphToControls not set");
    //   }
    // }
    this.writeToUrl(this.currentEffect?.uri);
  }

  clearAll() {
    // clears the effect!
    return; //this.graphToControls.emptyEffect();
  }

  // configureFromGraph() {
  //   const U = (x: string) => this.graph.Uri(x);

  //   const newDevs: NamedNode[] = [];
  //   for (let dc of Array.from(this.graph.sortedUris(this.graph.subjects(U("rdf:type"), U(":DeviceClass"))))) {
  //     for (let dev of Array.from(this.graph.sortedUris(this.graph.subjects(U("rdf:type"), dc)))) {
  //       if (this.graph.contains(dev, U(":hideInLiveUi"), null)) {
  //         continue;
  //       }
  //       if (newDevs.length == 0) newDevs.push(dev);
  //     }
  //   }
  //   log("is this called?");
  //   log(`controls update now has ${newDevs.length} devices`);
  //   this.devices = newDevs;
  //   this.requestUpdate();

  //   return;

  //   // Tried css columns- big slowdown from relayout as I'm scrolling.
  //   // Tried isotope- seems to only scroll to the right.
  //   // Tried columnize- fails in jquery maybe from weird elements.

  //   // not sure how to get this run after the children are created
  //   return setTimeout(
  //     () =>
  //       $("#deviceControls").isotope({
  //         // fitColumns would be nice, but it doesn't scroll vertically
  //         layoutMode: "masonry",
  //         containerStyle: null,
  //       }),
  //     2000
  //   );
  // }
}