# HG changeset patch # User drewp@bigasterisk.com # Date 2022-05-25 08:11:41 # Node ID ad7ab7027907fe1fe643562e436b4a26de525054 # Parent b62c78f3538055cc9621fa67602b67e7f7bcd69c clean up non-elements; get the lit elements at least to work with autoformat diff --git a/light9/web/live/ActiveSettings.ts b/light9/web/live/ActiveSettings.ts --- a/light9/web/live/ActiveSettings.ts +++ b/light9/web/live/ActiveSettings.ts @@ -1,7 +1,18 @@ -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; + keyForSetting: Map; 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) { 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 @@ -1,16 +1,34 @@ +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); } } 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,72 +1,71 @@ - - - - \ No newline at end of file + render() { + return html` + + None + + + `; + } + 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) + ); + } +} 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 @@ -1,70 +1,76 @@ - - - - - + render() { + return html` + + + + `; + } -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); + } + } +} 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,178 +1,187 @@ - - - - +@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` + + +

device control

+ +
+
+ + + +
+
+ +
+ +
+ `; + } + + 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 + ); + } +} diff --git a/light9/web/live/Light9LiveDeviceControl.ts b/light9/web/live/Light9LiveDeviceControl.ts --- a/light9/web/live/Light9LiveDeviceControl.ts +++ b/light9/web/live/Light9LiveDeviceControl.ts @@ -1,200 +1,197 @@ - - - - + `; + } -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