view src/main.ts @ 22:178e020289c1

'full' button; other clean up
author drewp@bigasterisk.com
date Mon, 29 Jan 2024 13:01:23 -0800
parents 4e350dcdc4fe
children
line wrap: on
line source

import { LitElement, TemplateResult, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";

interface Light {
  name: string;
  address: { url?: string; label: string };
  requestingColor: string;
  requestingDeviceColor: object;
  emittingColor: string;
  online: boolean;
  latencyMs: number;
}

@customElement("lb-page")
export class LbPage extends LitElement {
  static styles = [
    css`
      :host {
        display: flex;
        flex-direction: column;
        height: 100vh;
      }
      table {
        border-collapse: collapse;
      }
      td,
      th {
        border: 1px solid #aaa;
        text-align: center;
      }
      .color {
        display: inline-block;
        width: 30px;
        height: 30px;
        border-radius: 50%;
        margin: 3px;
        vertical-align: middle;
      }
      .col-group-1 {
        background: #e0ecf4;
      }
      .col-group-2 {
        background: #e0f3db;
      }
      .col-group-3 {
        background: #fee8c8;
      }

      @media (max-width: 1200px) {
        .opt {
          display: none;
        }
      }
    `,
  ];

  // bug https://github.com/lit/lit/issues/4305
  @((state as any)()) lights: Light[] = [];
  @((state as any)()) lightByName: Map<string, Light> = new Map();
  @((state as any)()) reportTime: Date = new Date(0);

  connectedCallback(): void {
    super.connectedCallback();
    const es = new EventSource("api/table");
    es.onmessage = (ev) => {
      console.log("got table update");
      const body = JSON.parse(ev.data);
      this.lights = body.lights.map((wrappedLight: any) => wrappedLight.light as Light);
      this.rebuildLightByName();
      this.reportTime = new Date(body.now * 1000);
    };
  }
  private rebuildLightByName() {
    this.lightByName = new Map();
    this.lights.forEach((light: any) => {
      this.lightByName.set(light.name, light);
    });
  }

  render() {
    return html`
      <h1>Light-bridge</h1>

      <table>
        ${this.renderLightHeaders()} ${this.lights.map(this.renderLightRow.bind(this))}
      </table>
      <p>Updated ${this.reportTime.toLocaleString("sv")}</p>
      <p>
        <a href="metrics">metrics</a> | <a href="https://bigasterisk.com/code/light-bridge/files/tip/">code</a> |
        <a href="api/graph">graph</a>
      </p>
      <bigast-loginbar></bigast-loginbar>
    `;
  }

  renderLightHeaders() {
    return html`<tr>
      <th class="col-group-1">light</th>
      <th class="col-group-1">address</th>
      <th class="col-group-2">requested color</th>
      <th class="col-group-2 opt">requested device color</th>
      <th class="col-group-3">emitting color</th>
      <th class="col-group-3 opt">online</th>
      <th class="col-group-3 opt">latency</th>
    </tr>`;
  }

  renderLightRow(d: Light) {
    return html`
      <tr>
        <td class="col-group-1">${d.name}</td>
        <td class="col-group-1"><code>${this.renderLinked(d.address)}</code></td>
        <td class="col-group-2">
          <code>${d.requestingColor}</code>
          <input type="color" @input=${this.onRequestingColor.bind(this, d.name, null)} .value="${d.requestingColor}" />
          <button @click=${this.onRequestingColor.bind(this, d.name, "#000000")}>off</button>
          <button @click=${this.onRequestingColor.bind(this, d.name, "#ffffff")}>full</button>
        </td>
        <td class="col-group-2 opt"><code>${JSON.stringify(d.requestingDeviceColor)}</code></td>
        <td class="col-group-3">${d.emittingColor} <span class="color" style="background: ${d.emittingColor}"></span></td>
        <td class="col-group-3 opt">${d.online ? "✔" : ""}</td>
        <td class="col-group-3 opt">${d.latencyMs} ms</td>
      </tr>
    `;
  }

  private renderLinked(link: { url?: string; label: string }): TemplateResult {
    var addr = html`${link.label}`;
    if (link.url) {
      addr = html`<a href="${link.url}">${addr}</a>`;
    }
    return addr;
  }

  async onRequestingColor(lightName: string, preset: string | null, ev: InputEvent) {
    const currentRequest = this.lightByName.get(lightName)!.requestingColor;
    const value = preset || (ev.target as HTMLInputElement).value;
    console.log("LbPage ~ onRequestingColor ~ value === currentRequest:", value, currentRequest);
    if (value === currentRequest) {
      return;
    }
    const url = new URL("api/output", location as any);
    url.searchParams.append("light", lightName);
    fetch(url, { method: "PUT", body: value }); // todo: only run one fetch at a time, per light

    // sometime after this, the SSE table will send us back the change we made (probably)
  }
}