Changeset - ad7ab7027907
[Not reviewed]
default
0 6 0
drewp@bigasterisk.com - 3 years ago 2022-05-25 08:11:41
drewp@bigasterisk.com
clean up non-elements; get the lit elements at least to work with autoformat
6 files changed with 741 insertions and 680 deletions:
0 comments (0 inline, 0 general)
light9/web/live/ActiveSettings.ts
Show inline comments
 
class ActiveSettings {
 
  graph: any;
 
  settings: any;
 
  keyForSetting: any;
 
import debug from "debug";
 
import { NamedNode } from "n3";
 
import { SyncedGraph } from "../SyncedGraph";
 
const log = debug("active");
 

	
 
interface SettingRow {
 
  setting: NamedNode;
 
  onChangeFunc: (x: null | undefined | string) => void;
 
  jsValue?: string;
 
}
 

	
 
export class ActiveSettings {
 
  graph: SyncedGraph;
 
  settings: Map<string, SettingRow>;
 
  keyForSetting: Map<string, string>;
 
  onChanged: any;
 
  constructor(graph: any) {
 
    // The settings we're showing (or would like to but the widget
 
@@ -11,14 +22,12 @@ class ActiveSettings {
 
    this.settings = new Map();
 
    this.keyForSetting = new Map(); // setting uri str -> dev+attr
 

	
 

	
 

	
 
    // Registered graphValueChanged funcs, by dev+attr. Kept even when
 
    // settings are deleted.
 
    this.onChanged = new Map();
 
  }
 

	
 
  addSettingsRow(device: { value: string; }, deviceAttr: { value: string; }, setting: { value: string; }, value: any) {
 
  addSettingsRow(device: NamedNode, deviceAttr: NamedNode, setting: NamedNode, value: any) {
 
    const key = device.value + " " + deviceAttr.value;
 
    if (this.settings.has(key)) {
 
      throw new Error("repeated setting on " + key);
 
@@ -29,7 +38,7 @@ class ActiveSettings {
 
    this.settings.set(key, {
 
      setting,
 
      onChangeFunc: this.onChanged[key],
 
      jsValue: value
 
      jsValue: value,
 
    });
 
    this.keyForSetting.set(setting.value, key);
 
    if (this.onChanged[key] != null) {
 
@@ -37,54 +46,65 @@ class ActiveSettings {
 
    }
 
  }
 

	
 
  has(setting: { value: any; }) {
 
  has(setting: { value: any }) {
 
    return this.keyForSetting.has(setting.value);
 
  }
 

	
 
  setValue(setting: { value: any; }, value: any) {
 
    const row = this.settings.get(this.keyForSetting.get(setting.value));
 
  setValue(setting: { value: any }, value: any) {
 
    const k = this.keyForSetting.get(setting.value);
 
    if (!k) throw new Error("not found");
 
    const row = this.settings.get(k);
 
    if (!row) throw new Error(`${setting.value} not found`);
 
    row.jsValue = value;
 
    if (row.onChangeFunc != null) { return row.onChangeFunc(value); }
 
    if (row.onChangeFunc != null) {
 
      return row.onChangeFunc(value);
 
    }
 
  }
 

	
 
  registerWidget(device: { value: string; }, deviceAttr: { value: string; }, graphValueChanged: any) {
 
  registerWidget(device: NamedNode, deviceAttr: NamedNode, graphValueChanged: any) {
 
    const key = device.value + " " + deviceAttr.value;
 
    this.onChanged[key] = graphValueChanged;
 

	
 
    if (this.settings.has(key)) {
 
      const row = this.settings.get(key);
 
      row.onChangeFunc = graphValueChanged;
 
      return row.onChangeFunc(row.jsValue);
 
    }
 
    const row = this.settings.get(key);
 
    if (!row) throw new Error(`${key} not found`);
 

	
 
    row.onChangeFunc = graphValueChanged;
 
    row.onChangeFunc(row.jsValue);
 
  }
 

	
 
  effectSettingLookup(device: { value: string; }, attr: { value: string; }) {
 
  effectSettingLookup(device: NamedNode, attr: NamedNode): NamedNode | null {
 
    const key = device.value + " " + attr.value;
 
    if (this.settings.has(key)) {
 
      return this.settings.get(key).setting;
 
    const row = this.settings.get(key);
 
    if (row) {
 
      return row.setting;
 
    }
 

	
 
    return null;
 
  }
 

	
 
  deleteSetting(setting: { value: string; }) {
 
    log('deleteSetting ' + setting.value);
 
  deleteSetting(setting: NamedNode) {
 
    log("deleteSetting " + setting.value);
 
    const key = this.keyForSetting.get(setting.value);
 
    if (!key) throw new Error("not found");
 
    const row = this.settings.get(key);
 
    if ((row != null) && !row.setting.equals(setting)) {
 
      throw new Error('corrupt row for ' + setting.value);
 
    if (row && !row.setting.equals(setting)) {
 
      throw new Error("corrupt row for " + setting.value);
 
    }
 
    if ((row != null ? row.onChangeFunc : undefined) != null) { row.onChangeFunc(null); }
 
    if (row) {
 
      row.onChangeFunc(null);
 
    }
 
    this.settings.delete(key);
 
    return this.keyForSetting.delete(setting);
 
    return this.keyForSetting.delete(setting.value);
 
  }
 

	
 
  clear() {
 
    new Map(this.settings).forEach(function (row: { onChangeFunc: (arg0: any) => any; }, key: any) {
 
      if (row.onChangeFunc != null) { return row.onChangeFunc(null); }
 
    this.settings.forEach((row: { onChangeFunc: (arg0: any) => any }, key: any) => {
 
      if (row.onChangeFunc != null) {
 
        return row.onChangeFunc(null);
 
      }
 
    });
 
    this.settings.clear();
 
    return this.keyForSetting.clear();
 
    this.keyForSetting.clear();
 
  }
 

	
 
  forAll(cb: (arg0: any) => any) {
light9/web/live/GraphToControls.ts
Show inline comments
 
import debug from "debug";
 
import { BlankNode, Literal, NamedNode, Quad_Object, Quad_Predicate, Quad_Subject, Term } from "n3";
 
import { some } from "underscore";
 
import { Patch } from "../patch";
 
import { SyncedGraph } from "../SyncedGraph";
 
import { ActiveSettings } from "./ActiveSettings";
 
const log = debug("g2c");
 

	
 
const valuePred = function(graph: { Uri: (arg0: any) => any; }, attr: { equals: (arg0: any) => any; }) {
 
  const U = (x: string) => graph.Uri(x);
 
  const scaledAttributeTypes = [U(':color'), U(':brightness'), U(':uv')];
 
  if (_.some(scaledAttributeTypes,
 
      (            x: any) => attr.equals(x))) { return U(':scaledValue'); } else { return U(':value'); }
 
const valuePred = function (graph: SyncedGraph, attr: NamedNode) {
 
  const U = graph.U();
 
  const scaledAttributeTypes = [U(":color"), U(":brightness"), U(":uv")];
 
  if (some(scaledAttributeTypes, (x: NamedNode) => attr.equals(x))) {
 
    return U(":scaledValue");
 
  } else {
 
    return U(":value");
 
  }
 
};
 

	
 
const log = debug('live');
 
function isUri(x: Term | number | string): x is NamedNode {
 
  return typeof x == "object" && x.termType == "NamedNode";
 
}
 

	
 
type ControlValue = number | string | NamedNode | null;
 
// Like element.set(path, newArray), but minimizes splices.
 
// Dotted paths don't work yet.
 
const syncArray = function(element: this, path: string, newArray: { length?: any; }, isElementEqual: { (a: any, b: any): boolean; (arg0: any, arg1: any): any; }) {
 
const syncArray = function (
 
  element: Element,
 
  path: string,
 
  newArray: { length?: any },
 
  isElementEqual: { (a: any, b: any): boolean; (arg0: any, arg1: any): any }
 
) {
 
  let pos = 0;
 
  let newPos = 0;
 

	
 
@@ -20,79 +38,76 @@ const syncArray = function(element: this
 
        pos += 1;
 
        newPos += 1;
 
      } else {
 
        element.splice('devices', pos, 1);
 
        element.splice("devices", pos, 1);
 
      }
 
    } else {
 
      element.push('devices', newArray[newPos]);
 
      element.push("devices", newArray[newPos]);
 
      pos += 1;
 
      newPos += 1;
 
    }
 
  }
 

	
 
  if (pos < element[path].length) {
 
    return element.splice('devices', pos, element[path].length - pos);
 
    return element.splice("devices", pos, element[path].length - pos);
 
  }
 
};
 

	
 

	
 
class GraphToControls {
 
  graph: any;
 
export class GraphToControls {
 
  activeSettings: ActiveSettings;
 
  effect: any;
 
  ctx: any;
 
  effect: NamedNode | null = null;
 
  ctx: NamedNode | null = null;
 
  // More efficient bridge between liveControl widgets and graph edits,
 
  // as opposed to letting each widget scan the graph and push lots of
 
  // tiny patches to it.
 
  constructor(graph: any) {
 
    this.graph = graph;
 
  constructor(public graph: SyncedGraph) {
 
    this.activeSettings = new ActiveSettings(this.graph);
 
    this.effect = null;
 
  }
 

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

	
 
  setEffect(effect: any) {
 
  setEffect(effect: NamedNode) {
 
    this.clearSettings();
 
    this.effect = effect;
 
    this.ctx = this.ctxForEffect(this.effect);
 
    this.ctx = this.ctxForEffect(effect);
 
    // are these going to pile up? consider @graph.triggerHandler('GTC sync')
 
    return this.graph.runHandler(this.syncFromGraph.bind(this), 'GraphToControls sync');
 
    return 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 = (x: string) => this.graph.Uri(x);
 
    const effect = this.graph.nextNumberedResource(U('http://light9.bigasterisk.com/effect/effect'));
 
    const U = this.graph.U();
 
    const effect = this.graph.nextNumberedResource(U("http://light9.bigasterisk.com/effect/effect"));
 
    const ctx = this.ctxForEffect(effect);
 
    const quad = (s: any, p: any, o: any) => this.graph.Quad(s, p, o, ctx);
 
    const quad = (s: Quad_Subject, p: Quad_Predicate, o: Quad_Object) => this.graph.Quad(s, p, o, ctx);
 

	
 
    const addQuads = [
 
      quad(effect, U('rdf:type'), U(':Effect')),
 
      quad(effect, U('rdfs:label'), this.graph.Literal(effect.value.replace(/.*\//, ""))),
 
      quad(effect, U(':publishAttr'), U(':strength'))
 
      quad(effect, U("rdf:type"), U(":Effect")),
 
      quad(effect, U("rdfs:label"), this.graph.Literal(effect.value.replace(/.*\//, ""))),
 
      quad(effect, U(":publishAttr"), U(":strength")),
 
    ];
 
    const patch = { addQuads, delQuads: [] };
 
    log('init new effect', patch);
 
    const patch = { adds: addQuads, dels: [] } as Patch;
 
    log("init new effect", patch);
 
    this.graph.applyAndSendPatch(patch);
 
    return effect;
 
  }
 

	
 
  syncFromGraph() {
 
    const U = (x: string) => this.graph.Uri(x);
 
    if (!this.effect) { return; }
 
    log('syncFromGraph', this.effect);
 
    const U = this.graph.U();
 
    if (!this.effect) {
 
      return;
 
    }
 
    log("syncFromGraph", this.effect);
 

	
 
    const toClear = new Set(this.activeSettings.allSettingsStr());
 

	
 
    for (let setting of Array.from(this.graph.objects(this.effect, U(':setting')))) {
 
      var value: { id: { match: (arg0: {}) => any; }; };
 
      const dev = this.graph.uriValue(setting, U(':device'));
 
      const devAttr = this.graph.uriValue(setting, U(':deviceAttr'));
 
    for (let setting of Array.from(this.graph.objects(this.effect, U(":setting")))) {
 
      if (!isUri(setting)) throw new Error();
 
      var value: { id: { match: (arg0: {}) => any } };
 
      const dev = this.graph.uriValue(setting, U(":device"));
 
      const devAttr = this.graph.uriValue(setting, U(":deviceAttr"));
 

	
 
      const pred = valuePred(this.graph, devAttr);
 
      try {
 
@@ -127,80 +142,91 @@ class GraphToControls {
 
    return this.activeSettings.registerWidget(device, deviceAttr, graphValueChanged);
 
  }
 

	
 
  shouldBeStored(deviceAttr: any, value: string | number) {
 
  shouldBeStored(deviceAttr: any, value: ControlValue) {
 
    // this is a bug for zoom=0, since collector will default it to
 
    // stick at the last setting if we don't explicitly send the
 
    // 0. rx/ry similar though not the exact same deal because of
 
    // their remap.
 
    return (value != null) && (value !== 0) && (value !== '#000000');
 
    return value != null && value !== 0 && value !== "#000000";
 
  }
 

	
 
  emptyEffect() {
 
    return this.activeSettings.forAll(this._removeEffectSetting.bind(this));
 
  }
 

	
 
  controlChanged(device: any, deviceAttr: any, value: string) {
 
  controlChanged(device: NamedNode, deviceAttr: NamedNode, value: ControlValue) {
 
    // todo: controls should be disabled if there's no effect and they won't do anything.
 
    if (!this.effect) { return; }
 
    if (!this.effect) {
 
      return;
 
    }
 

	
 
    // value is float or #color or (Uri or null)
 
    if ((value === undefined) || ((typeof value === "number") && isNaN(value)) || ((typeof value === "object") && (value !== null) && !value.id)) {
 
      throw new Error("controlChanged sent bad value " + value);
 
    }
 

	
 
    const effectSetting = this.activeSettings.effectSettingLookup(device, deviceAttr);
 

	
 
    // sometimes this misses an existing setting, which leads to a mess
 
    if (this.shouldBeStored(deviceAttr, value)) {
 
      if ((effectSetting == null)) {
 
      if (effectSetting == null) {
 
        return this._addEffectSetting(device, deviceAttr, value);
 
      } else {
 
        return this._patchExistingEffectSetting(effectSetting, deviceAttr, value);
 
      }
 
    } else {
 
      return this._removeEffectSetting(effectSetting);
 
      if (effectSetting !== null) {
 
        return this._removeEffectSetting(effectSetting);
 
      }
 
    }
 
  }
 

	
 
  _nodeForValue(value: { id: any; }) {
 
    if (value.id != null) {
 
  _nodeForValue(value: ControlValue) {
 
    if (value === null) {
 
      throw new Error("no value");
 
    }
 
    if (isUri(value)) {
 
      return value;
 
    }
 
    return this.graph.prettyLiteral(value);
 
  }
 

	
 
  _addEffectSetting(device: any, deviceAttr: { value: any; }, value: any) {
 
    log('change: _addEffectSetting', deviceAttr.value, value);
 
  _addEffectSetting(device: NamedNode, deviceAttr: NamedNode, value: ControlValue) {
 
    log("change: _addEffectSetting", deviceAttr.value, value);
 
    const U = (x: string) => this.graph.Uri(x);
 
    const quad = (s: any, p: any, o: any) => this.graph.Quad(s, p, o, this.ctx);
 
    const effectSetting = this.graph.nextNumberedResource(this.effect.value + '_set');
 
    const quad = (s: Quad_Subject, p: Quad_Predicate, o: Quad_Object) => this.graph.Quad(s, p, o, this.ctx);
 
    if (!this.effect) throw new Error("effect unset");
 
    const effectSetting = this.graph.nextNumberedResource(this.effect.value + "_set");
 
    this.activeSettings.addSettingsRow(device, deviceAttr, effectSetting, value);
 
    const addQuads = [
 
      quad(this.effect, U(':setting'), effectSetting),
 
      quad(effectSetting, U(':device'), device),
 
      quad(effectSetting, U(':deviceAttr'), deviceAttr),
 
      quad(effectSetting, valuePred(this.graph, deviceAttr), this._nodeForValue(value))
 
      quad(this.effect, U(":setting"), effectSetting),
 
      quad(effectSetting, U(":device"), device),
 
      quad(effectSetting, U(":deviceAttr"), deviceAttr),
 
      quad(effectSetting, valuePred(this.graph, deviceAttr), this._nodeForValue(value)),
 
    ];
 
    const patch = { addQuads, delQuads: [] };
 
    log('save', patch);
 
    const patch = { adds: addQuads, dels: [] } as Patch;
 
    log("save", patch);
 
    return this.graph.applyAndSendPatch(patch);
 
  }
 

	
 
  _patchExistingEffectSetting(effectSetting: { value: any; }, deviceAttr: any, value: any) {
 
    log('change: patch existing', effectSetting.value);
 
  _patchExistingEffectSetting(effectSetting: NamedNode, deviceAttr: NamedNode, value: ControlValue) {
 
    if (!this.ctx) throw new Error("no ctx");
 
    log("change: patch existing", effectSetting.value);
 
    this.activeSettings.setValue(effectSetting, value);
 
    return this.graph.patchObject(effectSetting, valuePred(this.graph, deviceAttr), this._nodeForValue(value), this.ctx);
 
    return this.graph.patchObject(
 
      effectSetting, //
 
      valuePred(this.graph, deviceAttr),
 
      this._nodeForValue(value),
 
      this.ctx
 
    );
 
  }
 

	
 
  _removeEffectSetting(effectSetting: { value: any; }) {
 
  _removeEffectSetting(effectSetting: NamedNode) {
 
    const U = (x: string) => this.graph.Uri(x);
 
    const quad = (s: any, p: any, o: any) => this.graph.Quad(s, p, o, this.ctx);
 
    if (effectSetting != null) {
 
      log('change: _removeEffectSetting', effectSetting.value);
 
      const toDel = [quad(this.effect, U(':setting'), effectSetting, this.ctx)];
 
      log("change: _removeEffectSetting", effectSetting.value);
 
      const toDel = [this.graph.Quad(this.effect, U(":setting"), effectSetting, this.ctx)];
 
      for (let q of Array.from(this.graph.graph.getQuads(effectSetting))) {
 
        toDel.push(q);
 
      }
 
      this.graph.applyAndSendPatch({ delQuads: toDel, addQuads: [] });
 
      this.graph.applyAndSendPatch({ dels: toDel, adds: [] } as Patch);
 
      return this.activeSettings.deleteSetting(effectSetting);
 
    }
 
  }
light9/web/live/Light9Listbox.ts
Show inline comments
 
<dom-module id="light9-listbox">
 
  <template>
 
    <style>
 
     paper-listbox {
 
         --paper-listbox-background-color: none;
 
         --paper-listbox-color: white;
 
         --paper-listbox: {
 
             /* measure biggest item? use flex for columns? */
 
             column-width: 9em;
 
         }
 
     }
 
     paper-item {
 
         --paper-item-min-height: 0;
 
         --paper-item: {
 
             display: block;
 
             border: 1px outset #0f440f;
 
             margin: 0 1px 5px 0;
 
             background: #0b1d0b;
 
         }
 
     }
 
     paper-item.iron-selected {
 
         background: #7b7b4a;
 
     }
 
    </style>
 
    <paper-listbox id="list"
 
                   selected="{{value}}"
 
                   attr-for-selected="uri"
 
                   on-focus-changed="selectOnFocus"
 
    >
 
      <paper-item on-focus="selectOnFocus">None</paper-item>
 
      <template is="dom-repeat" items="{{choices}}">
 
        <paper-item on-focus="selectOnFocus" uri="{{item.uri}}">{{item.label}}</paper-item>
 
      </template>
 
    </paper-listbox>
 
import debug from "debug";
 
const log = debug("listbox");
 
import { css, html, LitElement } from "lit";
 
import { customElement, property } from "lit/decorators.js";
 

	
 
@customElement("light9-listbox")
 
export class Light9Listbox extends LitElement {
 
  static styles = [
 
    css`
 
      paper-listbox {
 
        --paper-listbox-background-color: none;
 
        --paper-listbox-color: white;
 
        --paper-listbox: {
 
          /* measure biggest item? use flex for columns? */
 
          column-width: 9em;
 
        }
 
      }
 
      paper-item {
 
        --paper-item-min-height: 0;
 
        --paper-item: {
 
          display: block;
 
          border: 1px outset #0f440f;
 
          margin: 0 1px 5px 0;
 
          background: #0b1d0b;
 
        }
 
      }
 
      paper-item.iron-selected {
 
        background: #7b7b4a;
 
      }
 
    `,
 
  ];
 

	
 
  </template>
 
  <script>
 
   HTMLImports.whenReady(function () {
 
     Polymer({
 
       is: "light9-listbox",
 
       properties: {
 
         choices: { type: Array },
 
         value: { type: String, notify: true },
 
       },
 
       observers: ['onValue(value)'],
 
       selectOnFocus: function(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: function(value) {
 
         if (value === null) {
 
           this.clear();
 
         }
 
       },
 
       clear: function() {
 
         this.async(function() {
 
           this.querySelectorAll('paper-item').forEach(
 
             function(item) { item.blur(); });
 
           this.value = undefined;
 
         }.bind(this));
 

	
 
       },
 
       
 
     });
 
   });
 
  </script>
 
</dom-module>
 
\ No newline at end of file
 
  render() {
 
    return html`
 
      <paper-listbox id="list" selected="{{value}}" attr-for-selected="uri" on-focus-changed="selectOnFocus">
 
        <paper-item on-focus="selectOnFocus">None</paper-item>
 
        <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)"];
 
  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) {
 
    if (value === null) {
 
      this.clear();
 
    }
 
  }
 
  clear() {
 
    this.async(
 
      function () {
 
        this.querySelectorAll("paper-item").forEach(function (item) {
 
          item.blur();
 
        });
 
        this.value = undefined;
 
      }.bind(this)
 
    );
 
  }
 
}
light9/web/live/Light9LiveControl.ts
Show inline comments
 

	
 
<dom-module id="light9-live-control">
 
<template>
 
  <style>
 
   #colorControls {
 
       display: flex;
 
       align-items: center;
 
   }
 
   #colorControls > * {
 
       margin: 0 3px;
 
   }
 
   #colorControls paper-slider {
 
import debug from "debug";
 
const log = debug("control");
 
import { css, html, LitElement } from "lit";
 
import { customElement, property } from "lit/decorators.js";
 

	
 
   }
 
   paper-slider { width: 100%; height: 25px; }
 
  </style>
 

	
 
  <style is="custom-style">
 
   paper-slider {
 
       --paper-slider-knob-color: var(--paper-red-500);
 
       --paper-slider-active-color: var(--paper-red-500);
 
@customElement("light9-live-control")
 
export class Light9LiveControl extends LitElement {
 
  static styles = [
 
    css`
 
      #colorControls {
 
        display: flex;
 
        align-items: center;
 
      }
 
      #colorControls > * {
 
        margin: 0 3px;
 
      }
 
      #colorControls paper-slider {
 
      }
 
      paper-slider {
 
        width: 100%;
 
        height: 25px;
 
      }
 

	
 
       --paper-slider-font-color: white;
 
       --paper-slider-input: {
 
           width: 75px;
 
      paper-slider {
 
        --paper-slider-knob-color: var(--paper-red-500);
 
        --paper-slider-active-color: var(--paper-red-500);
 

	
 
           background: black;
 
           display: inline-block;
 
       }
 
   }
 
   
 
  </style>
 
        --paper-slider-font-color: white;
 
        --paper-slider-input: {
 
          width: 75px;
 

	
 
  <template is="dom-if" if="{{deviceAttrRow.useSlider}}">
 
    <paper-slider min="0"
 
                  max="{{deviceAttrRow.max}}"
 
                  step=".001"
 
                  editable
 
                  content-type="application/json"
 
                  value="{{sliderWriteValue}}"
 
                  immediate-value="{{immediateSlider}}"></paper-slider>
 
  </template>
 
  <template is="dom-if" if="{{deviceAttrRow.useColor}}">
 
    <div id="colorControls">
 
      <button on-click="goBlack">0.0</button>
 
      <light9-color-picker color="{{value}}"></light9-color-picker>
 
      
 
    </div>
 
  </template>
 
  <template is="dom-if" if="{{deviceAttrRow.useChoice}}">
 
    <light9-listbox choices="{{deviceAttrRow.choices}}" value="{{choiceValue}}">
 
    </light9-listbox>
 
  </template>
 
          background: black;
 
          display: inline-block;
 
        }
 
      }
 
    `,
 
  ];
 

	
 
</template>
 

	
 
</dom-module>
 
  render() {
 
    return html`
 
      <template is="dom-if" if="{{deviceAttrRow.useSlider}}">
 
        <paper-slider
 
          min="0"
 
          max="{{deviceAttrRow.max}}"
 
          step=".001"
 
          editable
 
          content-type="application/json"
 
          value="{{sliderWriteValue}}"
 
          immediate-value="{{immediateSlider}}"
 
        ></paper-slider>
 
      </template>
 
      <template is="dom-if" if="{{deviceAttrRow.useColor}}">
 
        <div id="colorControls">
 
          <button on-click="goBlack">0.0</button>
 
          <light9-color-picker color="{{value}}"></light9-color-picker>
 
        </div>
 
      </template>
 
      <template is="dom-if" if="{{deviceAttrRow.useChoice}}">
 
        <light9-listbox choices="{{deviceAttrRow.choices}}" value="{{choiceValue}}"> </light9-listbox>
 
      </template>
 
    `;
 
  }
 

	
 
const coffeeElementSetupLight9LiveControl = (function() {
 
    class Light9LiveControl extends Polymer.Element {
 
      static is: string;
 
      static getter_properties: {
 
          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: { ...; };
 
      };
 
      static getter_observers: {};
 
  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;
 
@@ -74,88 +80,92 @@ const coffeeElementSetupLight9LiveContro
 
      graphToControls: any;
 
      graph: any;
 
      pickedChoice: any;
 
      static initClass() {
 
        this.is = 'light9-live-control';
 
        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)'
 
          ];
 
      }
 
      constructor() {
 
        super();
 
        this.enableChange = false; // until 1st graph read
 
      }
 
      onSlider() { return this.value = this.immediateSlider; }
 
      goBlack() { return this.value = "#000000"; }
 
      onGraphToControls(gtc: { register: (arg0: any, arg1: any, arg2: any) => void; }) {
 
        gtc.register(this.device, this.deviceAttrRow.uri, this.graphValueChanged.bind(this));
 
        return this.enableChange = true;
 
      }
 
        device(device: any, uri: any, arg2: any) {
 
            throw new Error("Method not implemented.");
 
        }
 
  
 
      graphValueChanged(v: { value: any, }) {
 
        log('change: control gets', v);
 
        this.enableChange = false;
 
        if (v === null) {
 
          this.clear();
 
        } else {
 
          this.value = v;
 
        }
 
        if (this.deviceAttrRow.useSlider) { this.sliderWriteValue = v; }
 
        if (this.deviceAttrRow.useChoice) { this.choiceValue = (v === null ? v : v.value); }
 
        return this.enableChange = true;
 
      }
 
  
 
      onChoice(value: any) {
 
        if ((this.graphToControls == null) || !this.enableChange) { return; }
 
        if (value != null) {
 
          value = this.graph.Uri(value);
 
        } else {
 
          value = null;
 
        }
 
        return this.graphToControls.controlChanged(this.device, this.deviceAttrRow.uri, value);
 
      }
 
      
 
      onChange(value: any) {
 
        if ((this.graphToControls == null) || !this.enableChange) { return; }
 
        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;
 
        }
 
        return this.graphToControls.controlChanged(this.device, this.deviceAttrRow.uri, value);
 
      }
 
  
 
      clear() {
 
        this.pickedChoice = null;
 
        this.sliderWriteValue = 0;
 
        if (this.deviceAttrRow.useColor) {
 
          return this.value = '#000000';
 
        } else if (this.deviceAttrRow.useChoice) {
 
          return this.value = (this.pickedChoice = null);
 
        } else {
 
          return this.value = (this.immediateSlider = 0);
 
        }
 
      }
 
  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)"];
 
  }
 
  constructor() {
 
    super();
 
    this.enableChange = false; // until 1st graph read
 
  }
 
  onSlider() {
 
    return (this.value = this.immediateSlider);
 
  }
 
  goBlack() {
 
    return (this.value = "#000000");
 
  }
 
  onGraphToControls(gtc: { register: (arg0: any, arg1: any, arg2: any) => void }) {
 
    gtc.register(this.device, this.deviceAttrRow.uri, this.graphValueChanged.bind(this));
 
    return (this.enableChange = true);
 
  }
 
  device(device: any, uri: any, arg2: any) {
 
    throw new Error("Method not implemented.");
 
  }
 

	
 
  graphValueChanged(v: { value: any }) {
 
    log("change: control gets", v);
 
    this.enableChange = false;
 
    if (v === null) {
 
      this.clear();
 
    } else {
 
      this.value = v;
 
    }
 
    if (this.deviceAttrRow.useSlider) {
 
      this.sliderWriteValue = v;
 
    }
 
    
 
    Light9LiveControl.initClass();
 
    return Light9LiveControl;
 
  })();
 
  
 
\ No newline at end of file
 
    if (this.deviceAttrRow.useChoice) {
 
      this.choiceValue = v === null ? v : v.value;
 
    }
 
    return (this.enableChange = true);
 
  }
 

	
 
  onChoice(value: any) {
 
    if (this.graphToControls == null || !this.enableChange) {
 
      return;
 
    }
 
    if (value != null) {
 
      value = this.graph.Uri(value);
 
    } else {
 
      value = null;
 
    }
 
    return this.graphToControls.controlChanged(this.device, this.deviceAttrRow.uri, value);
 
  }
 

	
 
  onChange(value: any) {
 
    if (this.graphToControls == null || !this.enableChange) {
 
      return;
 
    }
 
    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;
 
    }
 
    return this.graphToControls.controlChanged(this.device, this.deviceAttrRow.uri, value);
 
  }
 

	
 
  clear() {
 
    this.pickedChoice = null;
 
    this.sliderWriteValue = 0;
 
    if (this.deviceAttrRow.useColor) {
 
      return (this.value = "#000000");
 
    } else if (this.deviceAttrRow.useChoice) {
 
      return (this.value = this.pickedChoice = null);
 
    } else {
 
      return (this.value = this.immediateSlider = 0);
 
    }
 
  }
 
}
light9/web/live/Light9LiveControls.ts
Show inline comments
 

	
 
<dom-module id="light9-live-controls">
 
<template>
 
  <style>
 
   :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 {
 

	
 
   }
 
  </style>
 
  <rdfdb-synced-graph graph="{{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>
 
      <button on-click="clearAll">clear settings in this effect</button>
 
    </div>
 
  </div>
 
import debug from "debug";
 
const log = debug("controls");
 
import { css, html, LitElement } from "lit";
 
import { customElement, property } from "lit/decorators.js";
 
import { GraphToControls } from "./GraphToControls";
 

	
 
  <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>
 
  </div>
 
  
 
</template>
 
</dom-module>
 
@customElement("light9-live-controls")
 
export class Light9LiveControls extends LitElement {
 
  static styles = [
 
    css`
 
      :host {
 
        display: flex;
 
        flex-direction: column;
 
      }
 
      #preview {
 
        width: 100%;
 
      }
 
      #deviceControls {
 
        flex-grow: 1;
 
        position: relative;
 
        width: 100%;
 
        overflow-y: auto;
 
      }
 

	
 
const coffeeElementSetupLight9LiveControls = (function() {
 
    class Light9LiveControls extends Polymer.Element {
 
      static is: string;
 
      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.is = "light9-live-controls";
 
        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)'
 
          ];
 
      light9-live-device-control > div {
 
        break-inside: avoid-column;
 
      }
 
      light9-live-device-control {
 
      }
 
  
 
      constructor() {
 
        super();
 
        this.graphToControls = null;
 
        this.okToWriteUrl = false;
 
      }
 
  
 
      ready() {
 
        super.ready(...arguments).ready();
 
        return this.currentSettings = {};
 
      }
 
  
 
      onGraph() {
 
        this.graphToControls = new GraphToControls(this.graph);
 
        this.graph.runHandler(this.update.bind(this), 'Light9LiveControls update');
 
  
 
        // need graph to be loaded, so we don't make double settings? not sure.
 
        return setTimeout(this.setFromUrl.bind(this), 1);
 
      }
 
  
 
      setFromUrl() {
 
        // 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;
 
        }
 
        return this.okToWriteUrl = true;
 
      }
 
  
 
      writeToUrl(effectStr: any) {
 
        if (!this.okToWriteUrl) { return; }
 
        const u = new URL(window.location.href);
 
        if (u.searchParams.get('effect') === effectStr) {
 
          return;        
 
        }
 
        u.searchParams.set('effect', effectStr);
 
        window.history.replaceState({}, "", u.href);
 
        return log('wrote new url', u.href);
 
      }
 
  
 
      newEffect() {
 
        return this.effectChoice = this.graphToControls.newEffect().value;
 
    `,
 
  ];
 

	
 
  render() {
 
    return html`
 
      <rdfdb-synced-graph graph="{{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>
 
          <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>
 
      </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)"];
 
  }
 

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

	
 
  ready() {
 
    super.ready(...arguments).ready();
 
    return (this.currentSettings = {});
 
  }
 

	
 
  onGraph() {
 
    this.graphToControls = new GraphToControls(this.graph);
 
    this.graph.runHandler(this.update.bind(this), "Light9LiveControls update");
 

	
 
    // need graph to be loaded, so we don't make double settings? not sure.
 
    return setTimeout(this.setFromUrl.bind(this), 1);
 
  }
 

	
 
  setFromUrl() {
 
    // 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;
 
    }
 
    return (this.okToWriteUrl = true);
 
  }
 

	
 
  writeToUrl(effectStr: any) {
 
    if (!this.okToWriteUrl) {
 
      return;
 
    }
 
    const u = new URL(window.location.href);
 
    if (u.searchParams.get("effect") === effectStr) {
 
      return;
 
    }
 
    u.searchParams.set("effect", effectStr);
 
    window.history.replaceState({}, "", u.href);
 
    return log("wrote new url", u.href);
 
  }
 

	
 
  newEffect() {
 
    return (this.effectChoice = this.graphToControls.newEffect().value);
 
  }
 

	
 
  onEffectChoice() {
 
    const U = (x: any) => this.graph.Uri(x);
 
    if (this.effectChoice == null) {
 
      // unlink
 
      if (this.graphToControls != null) {
 
        this.graphToControls.setEffect(null);
 
      }
 
        
 
      onEffectChoice() {
 
        const U = (x: any) => this.graph.Uri(x);
 
        if ((this.effectChoice == null)) {
 
          // unlink
 
          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)); }
 
        }
 
        return this.writeToUrl(this.effectChoice);
 
      }
 
   
 
      clearAll() {
 
        // clears the effect!
 
        return this.graphToControls.emptyEffect();
 
      }
 
      
 
      update() {
 
        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)) {
 
              continue;
 
            }
 
            newDevs.push({uri: dev});
 
          }
 
        }
 
  
 
        //log("controls update now has #{newDevs.length} devices")
 
        syncArray(this, 'devices', newDevs, (a: { uri: { value: any; }; }, b: { uri: { value: any; }; }) => a.uri.value === b.uri.value);
 
  
 
        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);
 
    } else {
 
      log("load", this.effectChoice);
 
      if (this.graphToControls != null) {
 
        this.graphToControls.setEffect(this.graph.Uri(this.effectChoice));
 
      }
 
    }
 
    
 
    Light9LiveControls.initClass();
 
    return Light9LiveControls;
 
  })();
 
\ No newline at end of file
 
    return this.writeToUrl(this.effectChoice);
 
  }
 

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

	
 
  update() {
 
    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)) {
 
          continue;
 
        }
 
        newDevs.push({ uri: dev });
 
      }
 
    }
 

	
 
    //log("controls update now has #{newDevs.length} devices")
 
    syncArray(this, "devices", newDevs, (a: { uri: { value: any } }, b: { uri: { value: any } }) => a.uri.value === b.uri.value);
 

	
 
    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
 
    );
 
  }
 
}
light9/web/live/Light9LiveDeviceControl.ts
Show inline comments
 

	
 
<dom-module id="light9-live-device-control">
 
<template>
 
  <style>
 
   :host {
 
       display: inline-block;
 
   }
 
   .device {
 
       border: 2px solid #151e2d;
 
       margin: 4px;
 
       padding: 1px;
 
       background: #171717;  /* deviceClass gradient added later */
 
       break-inside: avoid-column;
 
       width: 335px;
 
       
 
   }
 
   .deviceAttr {
 
       border-top: 1px solid #272727;
 
       padding-bottom: 2px;
 
       display: flex;
 
   }
 
   .deviceAttr > span {
 
import debug from "debug";
 
const log = debug("devcontrol");
 
import { css, html, LitElement } from "lit";
 
import { customElement, property } from "lit/decorators.js";
 

	
 
   }
 
   .deviceAttr > light9-live-control {
 
       flex-grow: 1;
 
   }
 
   h2 {
 
       font-size: 110%;
 
       padding: 4px;
 
       margin-top: 0;
 
       margin-bottom: 0;
 
   }
 
   .device, h2 {
 
       border-top-right-radius: 15px;
 
   }
 
@customElement("light9-device-control")
 
export class Light9DeviceControl extends LitElement {
 
  static styles = [
 
    css`
 
      :host {
 
        display: inline-block;
 
      }
 
      .device {
 
        border: 2px solid #151e2d;
 
        margin: 4px;
 
        padding: 1px;
 
        background: #171717; /* deviceClass gradient added later */
 
        break-inside: avoid-column;
 
        width: 335px;
 
      }
 
      .deviceAttr {
 
        border-top: 1px solid #272727;
 
        padding-bottom: 2px;
 
        display: flex;
 
      }
 
      .deviceAttr > span {
 
      }
 
      .deviceAttr > light9-live-control {
 
        flex-grow: 1;
 
      }
 
      h2 {
 
        font-size: 110%;
 
        padding: 4px;
 
        margin-top: 0;
 
        margin-bottom: 0;
 
      }
 
      .device,
 
      h2 {
 
        border-top-right-radius: 15px;
 
      }
 

	
 
   #mainLabel {
 
       font-size: 120%; 
 
       color: #9ab8fd;
 
       text-decoration: initial;
 
   }
 
   .device.selected h2 {
 
       outline: 3px solid #ffff0047;
 
   }
 
   .deviceAttr.selected {
 
       background: #cada1829;
 
   }
 
  </style>
 
  <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>
 
    </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>
 
      #mainLabel {
 
        font-size: 120%;
 
        color: #9ab8fd;
 
        text-decoration: initial;
 
      }
 
      .device.selected h2 {
 
        outline: 3px solid #ffff0047;
 
      }
 
      .deviceAttr.selected {
 
        background: #cada1829;
 
      }
 
    `,
 
  ];
 

	
 
  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>
 
        </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>
 
      </div>
 
    </template>
 
  </div>
 
</template>
 
</dom-module>
 
    `;
 
  }
 

	
 
const coffeeElementSetupLight9LiveDeviceControl = (function() {
 
    class Light9LiveDeviceControl extends Polymer.Element {
 
      static is: string;
 
      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
 
      };
 
      static getter_observers: {};
 
      selectedAttrs: any;
 
      graph: any;
 
      uri: any;
 
      devClasses: string;
 
      deviceClass: any;
 
      deviceAttrs: {};
 
      shadowRoot: any;
 
      static initClass() {
 
        this.is = "light9-live-device-control";
 
        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)'
 
          ];
 
      }
 
      constructor() {
 
        super();
 
        this.selectedAttrs = new Set(); // uri strings
 
      }
 
      _bgStyle(deviceClass: { value: any, length: number, charCodeAt: (arg0: number) => number, }) {
 
        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 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) {
 
        return this.devClasses = isSel ? 'selected' : '';
 
      }
 
  
 
      setAttrSelected(devAttr: { value: any, }, isSel: any) {
 
        if (isSel) {
 
          this.selectedAttrs.add(devAttr.value);
 
        } else {
 
          this.selectedAttrs.delete(devAttr.value);
 
  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)"];
 
  }
 
  constructor() {
 
    super();
 
    this.selectedAttrs = new Set(); // uri strings
 
  }
 
  _bgStyle(deviceClass: { value: any; length: number; charCodeAt: (arg0: number) => number }) {
 
    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 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) {
 
    return (this.devClasses = isSel ? "selected" : "");
 
  }
 

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

	
 
  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")])) {
 
      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))
 
    );
 
  }
 
  push(arg0: string, arg1: { uri: { value: any }; dataType: any; showColorPicker: any; attrClasses: string }) {
 
    throw new Error("Method not implemented.");
 
  }
 

	
 
  attrRow(devAttr: { value: any }) {
 
    let x: { value: any };
 
    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" : "",
 
    };
 
    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")));
 
      daRow.choices = (() => {
 
        const result = [];
 
        for (x of Array.from(choiceUris)) {
 
          result.push({ uri: x.value, label: this.graph.labelOrTail(x) });
 
        }
 
        return this.update();
 
      }
 
      
 
      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')])) { 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)));
 
      }
 
        push(arg0: string, arg1: { uri: { value: any, }, dataType: any, showColorPicker: any, attrClasses: string, }) {
 
            throw new Error("Method not implemented.");
 
        }
 
  
 
      attrRow(devAttr: { value: any, }) {
 
        let x: { value: any; };
 
        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' : ''
 
          };
 
        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')));
 
          daRow.choices = ((() => {
 
            const result = [];
 
            for (x of Array.from(choiceUris)) {             result.push({uri: x.value, label: this.graph.labelOrTail(x)});
 
            }
 
            return result;
 
          })());
 
          daRow.choiceSize = Math.min(choiceUris.length + 1, 10);
 
        } else {
 
          daRow.useSlider = true;
 
          daRow.max = 1;
 
          if (dataType.equals(U(':angle'))) {
 
            // varies
 
            daRow.max = 1;
 
          }
 
        }
 
        return daRow;
 
      }
 
        
 
      clear() {
 
        return Array.from(this.shadowRoot.querySelectorAll("light9-live-control")).map((lc: { clear: () => any; }) =>
 
          lc.clear());
 
      }
 
  
 
      onClick(ev: any) {
 
        return log('click', this.uri);
 
      }
 
        // select, etc
 
  
 
      onAttrClick(ev: { model: { dattr: { uri: any, }, }, }) {
 
        return log('attr click', this.uri, ev.model.dattr.uri);
 
        return result;
 
      })();
 
      daRow.choiceSize = Math.min(choiceUris.length + 1, 10);
 
    } else {
 
      daRow.useSlider = true;
 
      daRow.max = 1;
 
      if (dataType.equals(U(":angle"))) {
 
        // varies
 
        daRow.max = 1;
 
      }
 
    }
 
        // select
 
    
 
    Light9LiveDeviceControl.initClass();
 
    return Light9LiveDeviceControl;
 
  })();
 
  
 
  
 
\ No newline at end of file
 
    return daRow;
 
  }
 

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

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

	
 
  onAttrClick(ev: { model: { dattr: { uri: any } } }) {
 
    return log("attr click", this.uri, ev.model.dattr.uri);
 
  }
 
}
 
// select
0 comments (0 inline, 0 general)