Changeset - c4eab47d3c83
[Not reviewed]
default
1 5 1
drewp@bigasterisk.com - 3 years ago 2022-05-26 05:58:35
drewp@bigasterisk.com
WIP half-ported live/ page to working TS
6 files changed with 189 insertions and 177 deletions:
0 comments (0 inline, 0 general)
light9/web/live/GraphToControls.ts
Show inline comments
 
@@ -64,18 +64,18 @@ export class GraphToControls {
 
  }
 

	
 
  ctxForEffect(effect: NamedNode): NamedNode {
 
    return this.graph.Uri(effect.value.replace("light9.bigasterisk.com/effect", "light9.bigasterisk.com/show/dance2019/effect"));
 
  }
 

	
 
  setEffect(effect: NamedNode) {
 
  setEffect(effect: NamedNode | null) {
 
    this.clearSettings();
 
    this.effect = effect;
 
    this.ctx = this.ctxForEffect(effect);
 
    this.ctx = !effect ? null : this.ctxForEffect(effect);
 
    // are these going to pile up? consider @graph.triggerHandler('GTC sync')
 
    return this.graph.runHandler(this.syncFromGraph.bind(this), "GraphToControls sync");
 
    this.graph.runHandler(this.syncFromGraph.bind(this), "GraphToControls sync");
 
  }
 

	
 
  newEffect() {
 
    // wrong- this should be our editor's scratch effect, promoted to a
 
    // real one when you name it.
 
    const U = this.graph.U();
light9/web/live/Light9DeviceControl.ts
Show inline comments
 
file renamed from light9/web/live/Light9LiveDeviceControl.ts to light9/web/live/Light9DeviceControl.ts
 
import debug from "debug";
 
const log = debug("devcontrol");
 
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");
 

	
 
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 {
 
@@ -52,113 +76,105 @@ export class Light9DeviceControl extends
 
      }
 
    `,
 
  ];
 

	
 
  render() {
 
    return html`
 
      <div class$="device {{devClasses}}">
 
        <h2 style$="[[bgStyle]]" xon-click="onClick">
 
          <resource-display id="mainLabel" graph="{{graph}}" uri="{{uri}}"></resource-display>
 
          a <resource-display minor graph="{{graph}}" uri="{{deviceClass}}"></resource-display>
 
      <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>
 
        <template is="dom-repeat" items="{{deviceAttrs}}" as="dattr">
 
          <div xon-click="onAttrClick" class$="deviceAttr {{dattr.attrClasses}}">
 
            <span>attr <resource-display minor graph="{{graph}}" uri="{{dattr.uri}}"></resource-display></span>
 
            <light9-live-control
 
              graph="{{graph}}"
 
              device="{{uri}}"
 
              device-attr-row="{{dattr}}"
 
              effect="{{effect}}"
 
              graph-to-controls="{{graphToControls}}"
 
            ></light9-live-control>
 
          </div>
 
        </template>
 
        ${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>
 
    `;
 
  }
 

	
 
  static getter_properties: {
 
      graph: { type: any; notify: boolean; }; uri: { type: any; notify: boolean; }; effect: { type: any; }; deviceClass: { type: any; notify: boolean; }; // the uri str
 
      deviceAttrs: { type: any; notify: boolean; }; graphToControls: { ...; }; bgStyle: { ...; }; devClasses: { ...; }; // the css kind
 
  };
 
  selectedAttrs: any;
 
  graph: any;
 
  uri: any;
 
  devClasses: string;
 
  deviceClass: any;
 
  deviceAttrs: {};
 
  shadowRoot: any;
 
  static initClass() {
 
    this.getter_properties = {
 
      graph: { type: Object, notify: true },
 
      uri: { type: String, notify: true },
 
      effect: { type: String },
 
      deviceClass: { type: String, notify: true }, // the uri str
 
      deviceAttrs: { type: Array, notify: true },
 
      graphToControls: { type: Object },
 
      bgStyle: { type: String, computed: "_bgStyle(deviceClass)" },
 
      devClasses: { type: String, value: "" }, // the css kind
 
    };
 
    this.getter_observers = ["onGraph(graph)"];
 
  }
 
  @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<string> = new Set();
 

	
 
  constructor() {
 
    super();
 
    getTopGraph().then((g) => {
 
      this.graph = g;
 
      this.graph.runHandler(this.configureFromGraphz.bind(this), `${this.uri.value} update`);
 
    });
 
    this.selectedAttrs = new Set(); // uri strings
 
  }
 
  _bgStyle(deviceClass: { value: any; length: number; charCodeAt: (arg0: number) => number }) {
 

	
 
  _bgStyle(deviceClass: NamedNode | null): string {
 
    if (!deviceClass) return "";
 
    let hash = 0;
 
    deviceClass = deviceClass.value;
 
    for (let start = deviceClass.length - 10, i = start, end = deviceClass.length, asc = start <= end; asc ? i < end : i > end; asc ? i++ : i--) {
 
      hash += deviceClass.charCodeAt(i);
 
    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%);`;
 
  }
 

	
 
  onGraph() {
 
    return this.graph.runHandler(this.update.bind(this), `${this.uri.value} update`);
 
  setDeviceSelected(isSel: any) {
 
    this.devClasses = isSel ? "selected" : "";
 
  }
 

	
 
  setDeviceSelected(isSel: any) {
 
    return (this.devClasses = isSel ? "selected" : "");
 
  }
 

	
 
  setAttrSelected(devAttr: { value: any }, isSel: any) {
 
  setAttrSelected(devAttr: NamedNode, isSel: boolean) {
 
    if (isSel) {
 
      this.selectedAttrs.add(devAttr.value);
 
    } else {
 
      this.selectedAttrs.delete(devAttr.value);
 
    }
 
    return this.update();
 
    return this.configureFromGraphz();
 
  }
 

	
 
  update(patch: null) {
 
    const U = (x: string) => this.graph.Uri(x);
 
    if (patch != null && !SyncedGraph.patchContainsPreds(patch, [U("rdf:type"), U(":deviceAttr"), U(":dataType"), U(":choice")])) {
 
  configureFromGraphz(patch?: Patch) {
 
    const U = this.graph.U();
 
    if (patch != null && !patchContainsPreds(patch, [U("rdf:type"), U(":deviceAttr"), U(":dataType"), U(":choice")])) {
 
      return;
 
    }
 
    this.deviceClass = this.graph.uriValue(this.uri, U("rdf:type"));
 
    this.deviceAttrs = [];
 
    return Array.from(_.unique(this.graph.sortedUris(this.graph.objects(this.deviceClass, U(":deviceAttr"))))).map((da: any) =>
 
      this.push("deviceAttrs", this.attrRow(da))
 
    Array.from(unique(this.graph.sortedUris(this.graph.objects(this.deviceClass, U(":deviceAttr"))))).map((da: NamedNode) =>
 
      this.deviceAttrs.push(this.attrRow(da))
 
    );
 
  }
 
  push(arg0: string, arg1: { uri: { value: any }; dataType: any; showColorPicker: any; attrClasses: string }) {
 
    throw new Error("Method not implemented.");
 
    this.requestUpdate();
 
  }
 

	
 
  attrRow(devAttr: { value: any }) {
 
    let x: { value: any };
 
  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.value) ? "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")));
 
@@ -179,19 +195,19 @@ export class Light9DeviceControl extends
 
      }
 
    }
 
    return daRow;
 
  }
 

	
 
  clear() {
 
    return Array.from(this.shadowRoot.querySelectorAll("light9-live-control")).map((lc: { clear: () => any }) => lc.clear());
 
    Array.from(this.shadowRoot!.querySelectorAll("light9-live-control")).map((lc: Element) => (lc as Light9LiveControl).clear());
 
  }
 

	
 
  onClick(ev: any) {
 
    return log("click", this.uri);
 
    log("click", this.uri);
 
    // select, etc
 
  }
 
  // select, etc
 

	
 
  onAttrClick(ev: { model: { dattr: { uri: any } } }) {
 
    return log("attr click", this.uri, ev.model.dattr.uri);
 
    log("attr click", this.uri, ev.model.dattr.uri);
 
    // select
 
  }
 
}
 
// select
light9/web/live/Light9Listbox.ts
Show inline comments
 
import debug from "debug";
 
const log = debug("listbox");
 
import { css, html, LitElement } from "lit";
 
import { css, html, LitElement, PropertyValues } from "lit";
 
import { customElement, property } from "lit/decorators.js";
 

	
 
export type Choice = {uri:string,label:string}
 
@customElement("light9-listbox")
 
export class Light9Listbox extends LitElement {
 
  static styles = [
 
    css`
 
      paper-listbox {
 
        --paper-listbox-background-color: none;
 
@@ -37,35 +37,39 @@ export class Light9Listbox extends LitEl
 
        <template is="dom-repeat" items="{{choices}}">
 
          <paper-item on-focus="selectOnFocus" uri="{{item.uri}}">{{item.label}}</paper-item>
 
        </template>
 
      </paper-listbox>
 
    `;
 
  }
 
  properties: {
 
    choices: { type: Array };
 
    value: { type: String; notify: true };
 
  };
 
  observers: ["onValue(value)"];
 
  @property() choices: Array<Choice> = [];
 
  @property() value: String | null = null;
 

	
 
  constructor() {
 
    super();
 
  }
 
  selectOnFocus(ev) {
 
    if (ev.target.uri === undefined) {
 
      // *don't* clear for this, or we can't cycle through all choices (including none) with up/down keys
 
      //this.clear();
 
      //return;
 
    }
 
    this.value = ev.target.uri;
 
  }
 
  onValue(value) {
 
  updated(changedProperties: PropertyValues) {
 
    if (changedProperties.has("value")) {
 
      if (this.value === null) {
 
        this.clear();
 
      }
 
    }
 
  }
 
  onValue(value: String | null) {
 
    if (value === null) {
 
      this.clear();
 
    }
 
  }
 
  clear() {
 
    this.async(
 
      function () {
 
        this.querySelectorAll("paper-item").forEach(function (item) {
 
          item.blur();
 
        });
 
        this.value = undefined;
 
      }.bind(this)
 
    );
 
    this.querySelectorAll("paper-item").forEach(function (item) {
 
      item.blur();
 
    });
 
    this.value = null;
 
  }
 
}
light9/web/live/Light9LiveControl.ts
Show inline comments
 
import debug from "debug";
 
const log = debug("control");
 
import { css, html, LitElement } from "lit";
 
import { customElement, property } from "lit/decorators.js";
 
import { SyncedGraph } from "../SyncedGraph";
 

	
 
@customElement("light9-live-control")
 
export class Light9LiveControl extends LitElement {
 
  graph!:SyncedGraph
 

	
 
  static styles = [
 
    css`
 
      #colorControls {
 
        display: flex;
 
        align-items: center;
 
      }
 
@@ -58,47 +61,44 @@ export class Light9LiveControl extends L
 
      <template is="dom-if" if="{{deviceAttrRow.useChoice}}">
 
        <light9-listbox choices="{{deviceAttrRow.choices}}" value="{{choiceValue}}"> </light9-listbox>
 
      </template>
 
    `;
 
  }
 

	
 
  graph: { type: any; notify: boolean; }
 
  device: { type: any; };
 
  deviceAttrRow: { type: any; }; // object returned from attrRow, below
 
  value: { type: any; notify: boolean; }; // null, Uri, float, str
 
  choiceValue: { type: any; };
 
  immediateSlider: { notify: boolean; observer: string; };
 
  sliderWriteValue: { ...; };
 
  pickedChoice: { ...; };
 
  graphToControls: { ...; };
 

	
 

	
 

	
 
      enableChange: boolean;
 
      value: any;
 
      immediateSlider: any;
 
      deviceAttrRow: any;
 
      sliderWriteValue: { value: any; };
 
      choiceValue: any;
 
      graphToControls: any;
 
      graph: any;
 
      pickedChoice: any;
 
  static initClass() {
 
    this.getter_properties = {
 
      graph: { type: Object, notify: true },
 
      device: { type: Object },
 
      deviceAttrRow: { type: Object }, // object returned from attrRow, below
 
      value: { type: Object, notify: true }, // null, Uri, float, str
 
      choiceValue: { type: Object },
 

	
 
      immediateSlider: { notify: true, observer: "onSlider" },
 
      sliderWriteValue: { type: Number },
 

	
 
      pickedChoice: { observer: "onChange" },
 
      graphToControls: { type: Object },
 
    };
 
    this.getter_observers = ["onChange(value)", "onGraphToControls(graphToControls)", "onChoice(choiceValue)"];
 
  }
 
  // "onChange(value)", 
 
  // "onChoice(choiceValue)"];
 
  // "onGraphToControls(graphToControls)", 
 
  // choiceValue: { type: any; };
 
  // choiceValue: { type: Object },
 
  // choiceValue: any;
 
  // device: { type: any; };
 
  // device: { type: Object },
 
  // deviceAttrRow: { type: any; }; // object returned from attrRow, below
 
  // deviceAttrRow: { type: Object }, // object returned from attrRow, below
 
  // deviceAttrRow: any;
 
  // enableChange: boolean;
 
  // graph: { type: Object, notify: true },
 
  // graphToControls: { ...; };
 
  // graphToControls: { type: Object },
 
  // graphToControls: any;
 
  // immediateSlider: { notify: boolean; observer: string; };
 
  // immediateSlider: { notify: true, observer: "onSlider" },
 
  // immediateSlider: any;
 
  // pickedChoice: { ...; };
 
  // pickedChoice: { observer: "onChange" },
 
  // pickedChoice: any;
 
  // sliderWriteValue: { ...; };
 
  // sliderWriteValue: { type: Number },
 
  // sliderWriteValue: { value: any; };
 
  // value: { type: any; notify: boolean; }; // null, Uri, float, str
 
  // value: { type: Object, notify: true }, // null, Uri, float, str
 
  // value: any;
 
  
 
  constructor() {
 
    super();
 
    this.enableChange = false; // until 1st graph read
 
  }
 
  onSlider() {
 
    return (this.value = this.immediateSlider);
light9/web/live/Light9LiveControls.ts
Show inline comments
 
import debug from "debug";
 
const log = debug("controls");
 
import { css, html, LitElement } from "lit";
 
import { customElement, property } from "lit/decorators.js";
 
import { customElement } 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 { GraphToControls } from "./GraphToControls";
 
export { Light9DeviceControl as Light9LiveDeviceControl } from "./Light9DeviceControl";
 

	
 
const log = debug("controls");
 

	
 
@customElement("light9-live-controls")
 
export class Light9LiveControls extends LitElement {
 
  graph!: SyncedGraph;
 

	
 
  static styles = [
 
    css`
 
      :host {
 
        display: flex;
 
        flex-direction: column;
 
      }
 
@@ -29,91 +38,73 @@ export class Light9LiveControls extends 
 
      }
 
    `,
 
  ];
 

	
 
  render() {
 
    return html`
 
      <rdfdb-synced-graph graph="{{graph}}"></rdfdb-synced-graph>
 
      <rdfdb-synced-graph></rdfdb-synced-graph>
 

	
 
      <h1>device control</h1>
 

	
 
      <div id="save">
 
        <div>
 
          <button on-click="newEffect">New effect</button>
 
          <edit-choice graph="{{graph}}" uri="{{effectChoice}}"></edit-choice>
 
          <edit-choice .uri=${this.effectChoice}></edit-choice>
 
          <button on-click="clearAll">clear settings in this effect</button>
 
        </div>
 
      </div>
 

	
 
      <div id="deviceControls">
 
        <template is="dom-repeat" items="{{devices}}" as="device">
 
          <light9-live-device-control
 
            graph="{{graph}}"
 
            uri="{{device.uri}}"
 
            effect="{{effect}}"
 
            graph-to-controls="{{graphToControls}}"
 
          ></light9-live-device-control>
 
        </template>
 
        ${this.devices.map(
 
          (device: NamedNode) => html`
 
            <light9-device-control .uri=${device} .effect=${this.effectChoice} .graphToControls=${this.graphToControls}></light9-device-control>
 
          `
 
        )}
 
      </div>
 
    `;
 
  }
 

	
 
  static getter_properties: {
 
    graph: { type: any; notify: boolean };
 
    devices: { type: any; notify: boolean; value: {} };
 
    // string uri of the effect being edited, or null. This is the
 
    // master value; GraphToControls follows.
 
    effectChoice: { type: any; notify: boolean; value: any };
 
    graphToControls: { type: any };
 
  };
 
  static getter_observers: {};
 
  graphToControls: any;
 
  okToWriteUrl: boolean;
 
  currentSettings: {};
 
  graph: any;
 
  effectChoice: any;
 
  static initClass() {
 
    this.getter_properties = {
 
      graph: { type: Object, notify: true },
 
      devices: { type: Array, notify: true, value: [] },
 
      // string uri of the effect being edited, or null. This is the
 
      // master value; GraphToControls follows.
 
      effectChoice: { type: String, notify: true, value: null },
 
      graphToControls: { type: Object },
 
    };
 
    this.getter_observers = ["onGraph(graph)", "onEffectChoice(effectChoice)"];
 
  }
 
  devices: Array<NamedNode> = [];
 
  // uri of the effect being edited, or null. This is the
 
  // master value; GraphToControls follows.
 
  effectChoice: NamedNode | null = null;
 
  graphToControls!: GraphToControls;
 
  okToWriteUrl: boolean = false;
 

	
 
  constructor() {
 
    super();
 
    this.graphToControls = null;
 
    this.okToWriteUrl = false;
 
  }
 

	
 
  ready() {
 
    super.ready(...arguments).ready();
 
    return (this.currentSettings = {});
 
    getTopGraph().then((g) => {
 
      this.graph = g;
 
      this.graph.runHandler(this.findDevices.bind(this), "findDevices");
 
      this.graphToControls = new GraphToControls(this.graph);
 
      // this.graph.runHandler(this.xupdate.bind(this), "Light9LiveControls update");
 
      this.setEffectFromUrl.bind(this);
 
    });
 
  }
 

	
 
  onGraph() {
 
    this.graphToControls = new GraphToControls(this.graph);
 
    this.graph.runHandler(this.update.bind(this), "Light9LiveControls update");
 
  findDevices(patch?: Patch) {
 
    const U = this.graph.U();
 

	
 
    // need graph to be loaded, so we don't make double settings? not sure.
 
    return setTimeout(this.setFromUrl.bind(this), 1);
 
    this.devices = [];
 
    let classes = this.graph.subjects(U("rdf:type"), U(":DeviceClass"));
 
    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();
 
  }
 

	
 
  setFromUrl() {
 
  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 url", effect);
 
      this.effectChoice = effect;
 
      this.effectChoice = this.graph.Uri(effect);
 
    }
 
    return (this.okToWriteUrl = true);
 
    this.okToWriteUrl = true;
 
  }
 

	
 
  writeToUrl(effectStr: any) {
 
    if (!this.okToWriteUrl) {
 
      return;
 
    }
 
@@ -137,24 +128,24 @@ export class Light9LiveControls extends 
 
      if (this.graphToControls != null) {
 
        this.graphToControls.setEffect(null);
 
      }
 
    } else {
 
      log("load", this.effectChoice);
 
      if (this.graphToControls != null) {
 
        this.graphToControls.setEffect(this.graph.Uri(this.effectChoice));
 
        this.graphToControls.setEffect(this.effectChoice);
 
      }
 
    }
 
    return this.writeToUrl(this.effectChoice);
 
  }
 

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

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

	
 
    const newDevs = [];
 
    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)) {
light9/web/live/index.html
Show inline comments
 
<!doctype html>
 
<html>
 
  <head>
 
    <title>device control</title>
 
    <meta charset="utf-8" />
 
    <link rel="stylesheet" href="/style.css">
 
    <link rel="stylesheet" href="../style.css">
 
    <script type="module" src="../live/Light9LiveControls"></script>
 
  </head>
 
  <body>
 
    <style>
 
     body, html {
 
         margin: 0;
 
     }
0 comments (0 inline, 0 general)