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 } from "../patch"; import { getTopGraph } from "../RdfdbSyncedGraph"; import { SyncedGraph } from "../SyncedGraph"; import { Choice } from "./Light9Listbox"; import { Light9AttrControl } from "./Light9AttrControl"; import { Effect } from "./Effect"; export { ResourceDisplay } from "../ResourceDisplay"; export { Light9AttrControl }; const log = debug("settings.dev"); export interface DeviceAttrRow { uri: NamedNode; //devattr device: NamedNode; attrClasses: string; // the css kind dataType: NamedNode; choices: Choice[]; // choiceSize: number; // max: number; } // Widgets for one device with multiple Light9LiveControl rows for the attr(s). @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!: Effect; @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.syncDeviceAttrsFromGraph.bind(this), `${this.uri.value} update`); }); this.selectedAttrs = new Set(); } _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); } else { this.selectedAttrs.delete(devAttr); } } syncDeviceAttrsFromGraph(patch?: Patch) { const U = this.graph.U(); if (patch && !patch.containsAnyPreds([U("rdf:type"), U(":deviceAttr"), U(":dataType"), U(":choice")])) { return; } try { this.deviceClass = this.graph.uriValue(this.uri, U("rdf:type")); } catch (e) { // what's likely is we're going through a graph reload and the graph // is gone but the controls remain } 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, device: this.uri, dataType, attrClasses: this.selectedAttrs.has(devAttr) ? "selected" : "", choices: [] as Choice[], choiceSize: 0, max: 1, }; if (dataType.equals(U(":choice"))) { 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.max = 1; if (dataType.equals(U(":angle"))) { // varies daRow.max = 1; } } return daRow; } clear() { // why can't we just set their values ? what's diff about // the clear state, and should it be represented with `null` value? throw new Error(); // 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 } }