# HG changeset patch # User drewp@bigasterisk.com # Date 1653544715 25200 # Node ID c4eab47d3c839478558c7e909415e1ee86556618 # Parent ad7ab7027907fe1fe643562e436b4a26de525054 WIP half-ported live/ page to working TS diff -r ad7ab7027907 -r c4eab47d3c83 light9/web/live/GraphToControls.ts --- a/light9/web/live/GraphToControls.ts Wed May 25 01:11:41 2022 -0700 +++ b/light9/web/live/GraphToControls.ts Wed May 25 22:58:35 2022 -0700 @@ -67,12 +67,12 @@ 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 -r ad7ab7027907 -r c4eab47d3c83 light9/web/live/Light9DeviceControl.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/light9/web/live/Light9DeviceControl.ts Wed May 25 22:58:35 2022 -0700 @@ -0,0 +1,213 @@ +import debug from "debug"; +import { css, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { NamedNode } from "n3"; +import { unique } from "underscore"; +import { Patch, patchContainsPreds } from "../patch"; +import { getTopGraph } from "../RdfdbSyncedGraph"; +import { SyncedGraph } from "../SyncedGraph"; +import { GraphToControls } from "./GraphToControls"; +import { Choice } from "./Light9Listbox"; +import { Light9LiveControl } from "./Light9LiveControl"; +export { ResourceDisplay } from "../ResourceDisplay"; +export { Light9LiveControl }; +const log = debug("devcontrol"); + +interface DeviceAttrRow { + uri: NamedNode; //devattr + attrClasses: string; // the css kind + dataType: NamedNode; + showColorPicker: boolean; + useColor: boolean; + useChoice: boolean; + choices: Choice[]; + choiceSize: number; + useSlider: boolean; + max: number; +} + +@customElement("light9-device-control") +export class Light9DeviceControl extends LitElement { + graph!: SyncedGraph; + static styles = [ + css` + :host { + display: inline-block; + } + .device { + 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; + } + `, + ]; + + render() { + return html` +
+

+ + a +

+ ${this.deviceAttrs.map( + (dattr: DeviceAttrRow) => html` +
+ attr + +
+ ` + )} +
+ `; + } + + @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: NamedNode | null): string { + if (!deviceClass) return ""; + let hash = 0; + 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%);`; + } + + setDeviceSelected(isSel: any) { + this.devClasses = isSel ? "selected" : ""; + } + + setAttrSelected(devAttr: NamedNode, isSel: boolean) { + if (isSel) { + this.selectedAttrs.add(devAttr.value); + } else { + this.selectedAttrs.delete(devAttr.value); + } + return this.configureFromGraphz(); + } + + 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 = []; + Array.from(unique(this.graph.sortedUris(this.graph.objects(this.deviceClass, U(":deviceAttr"))))).map((da: NamedNode) => + this.deviceAttrs.push(this.attrRow(da)) + ); + this.requestUpdate(); + } + + attrRow(devAttr: NamedNode): DeviceAttrRow { + let x: NamedNode; + const U = (x: string) => this.graph.Uri(x); + const dataType = this.graph.uriValue(devAttr, U(":dataType")); + const daRow = { + uri: devAttr, + dataType, + showColorPicker: dataType.equals(U(":color")), + attrClasses: this.selectedAttrs.has(devAttr.value) ? "selected" : "", + useColor: false, + useChoice: false, + choices: [] as Choice[], + choiceSize: 0, + useSlider: false, + max: 1, + }; + if (dataType.equals(U(":color"))) { + daRow.useColor = true; + } else if (dataType.equals(U(":choice"))) { + daRow.useChoice = true; + const choiceUris = this.graph.sortedUris(this.graph.objects(devAttr, U(":choice"))); + 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() { + Array.from(this.shadowRoot!.querySelectorAll("light9-live-control")).map((lc: Element) => (lc as Light9LiveControl).clear()); + } + + onClick(ev: any) { + log("click", this.uri); + // select, etc + } + + onAttrClick(ev: { model: { dattr: { uri: any } } }) { + log("attr click", this.uri, ev.model.dattr.uri); + // select + } +} diff -r ad7ab7027907 -r c4eab47d3c83 light9/web/live/Light9Listbox.ts --- a/light9/web/live/Light9Listbox.ts Wed May 25 01:11:41 2022 -0700 +++ b/light9/web/live/Light9Listbox.ts Wed May 25 22:58:35 2022 -0700 @@ -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 @@ `; } - 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 @@ } 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 -r ad7ab7027907 -r c4eab47d3c83 light9/web/live/Light9LiveControl.ts --- a/light9/web/live/Light9LiveControl.ts Wed May 25 01:11:41 2022 -0700 +++ b/light9/web/live/Light9LiveControl.ts Wed May 25 22:58:35 2022 -0700 @@ -2,9 +2,12 @@ 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 @@ `; } - 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 -r ad7ab7027907 -r c4eab47d3c83 light9/web/live/Light9LiveControls.ts --- a/light9/web/live/Light9LiveControls.ts Wed May 25 01:11:41 2022 -0700 +++ b/light9/web/live/Light9LiveControls.ts Wed May 25 22:58:35 2022 -0700 @@ -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 @@ 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 @@ } 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 @@ return this.graphToControls.emptyEffect(); } - update() { + configureFromGraph() { const U = (x: string) => this.graph.Uri(x); const newDevs = []; diff -r ad7ab7027907 -r c4eab47d3c83 light9/web/live/Light9LiveDeviceControl.ts --- a/light9/web/live/Light9LiveDeviceControl.ts Wed May 25 01:11:41 2022 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,197 +0,0 @@ -import debug from "debug"; -const log = debug("devcontrol"); -import { css, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators.js"; - -@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; - } - `, - ]; - - render() { - return html` -
-

- - a -

- -
- `; - } - - 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 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); - } -} -// select diff -r ad7ab7027907 -r c4eab47d3c83 light9/web/live/index.html --- a/light9/web/live/index.html Wed May 25 01:11:41 2022 -0700 +++ b/light9/web/live/index.html Wed May 25 22:58:35 2022 -0700 @@ -3,7 +3,8 @@ device control - + +