diff --git a/light9/web/live/GraphToControls.ts b/light9/web/live/GraphToControls.ts --- a/light9/web/live/GraphToControls.ts +++ b/light9/web/live/GraphToControls.ts @@ -67,12 +67,12 @@ export class GraphToControls { 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() { diff --git a/light9/web/live/Light9LiveDeviceControl.ts b/light9/web/live/Light9DeviceControl.ts rename from light9/web/live/Light9LiveDeviceControl.ts rename to light9/web/live/Light9DeviceControl.ts --- a/light9/web/live/Light9LiveDeviceControl.ts +++ b/light9/web/live/Light9DeviceControl.ts @@ -1,10 +1,34 @@ 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 { @@ -55,100 +79,86 @@ export class Light9DeviceControl extends render() { return html` -
-

- - a +
+

+ + a

- + ${this.deviceAttrs.map( + (dattr: DeviceAttrRow) => html` +
+ attr + +
+ ` + )}
`; } - 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 = 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 = { @@ -156,6 +166,12 @@ export class Light9DeviceControl extends 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; @@ -182,16 +198,16 @@ export class Light9DeviceControl extends } 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 diff --git a/light9/web/live/Light9Listbox.ts b/light9/web/live/Light9Listbox.ts --- a/light9/web/live/Light9Listbox.ts +++ b/light9/web/live/Light9Listbox.ts @@ -1,8 +1,8 @@ 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 = [ @@ -40,11 +40,12 @@ export class Light9Listbox extends LitEl `; } - properties: { - choices: { type: Array }; - value: { type: String; notify: true }; - }; - observers: ["onValue(value)"]; + @property() choices: Array = []; + @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 @@ -53,19 +54,22 @@ export class Light9Listbox extends LitEl } 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; } } diff --git a/light9/web/live/Light9LiveControl.ts b/light9/web/live/Light9LiveControl.ts --- a/light9/web/live/Light9LiveControl.ts +++ b/light9/web/live/Light9LiveControl.ts @@ -2,9 +2,12 @@ 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 { @@ -61,41 +64,38 @@ export class Light9LiveControl extends L `; } - 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 diff --git a/light9/web/live/Light9LiveControls.ts b/light9/web/live/Light9LiveControls.ts --- a/light9/web/live/Light9LiveControls.ts +++ b/light9/web/live/Light9LiveControls.ts @@ -1,11 +1,20 @@ 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 { @@ -32,85 +41,67 @@ export class Light9LiveControls extends render() { return html` - +

device control

- +
- + ${this.devices.map( + (device: NamedNode) => html` + + ` + )}
`; } - 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 = []; + // 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) { @@ -140,7 +131,7 @@ export class Light9LiveControls extends } 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); @@ -151,7 +142,7 @@ export class Light9LiveControls extends return this.graphToControls.emptyEffect(); } - update() { + configureFromGraph() { const U = (x: string) => this.graph.Uri(x); const newDevs = []; diff --git a/light9/web/live/index.html b/light9/web/live/index.html --- a/light9/web/live/index.html +++ b/light9/web/live/index.html @@ -3,7 +3,8 @@ device control - + +