changeset 2411:66a4db80ce6e

keep 44fc
author drewp@bigasterisk.com
date Sat, 18 May 2024 23:22:00 -0700
parents 44fc477970bf (diff) 6697a68800d2 (current diff)
children 560c9eab6647
files web/TiledHome.ts
diffstat 15 files changed, 141 insertions(+), 84 deletions(-) [+]
line wrap: on
line diff
--- a/pdm.lock	Fri May 17 17:48:26 2024 -0700
+++ b/pdm.lock	Sat May 18 23:22:00 2024 -0700
@@ -5,7 +5,7 @@
 groups = ["default", "dev"]
 strategy = ["cross_platform", "inherit_metadata"]
 lock_version = "4.4.1"
-content_hash = "sha256:5393d5c679935ba9f042f2b4f4d6efd58dbf03519b2e9f25e08f1e9d421e52f1"
+content_hash = "sha256:6fac24ed6ab93fd328a74d22973a01c77d9de5b4a0b22cd111a884afd99f235f"
 
 [[package]]
 name = "aiohttp"
@@ -2315,19 +2315,6 @@
 ]
 
 [[package]]
-name = "zmq"
-version = "0.0.0"
-summary = "You are probably looking for pyzmq."
-groups = ["default"]
-dependencies = [
-    "pyzmq",
-]
-files = [
-    {file = "zmq-0.0.0.tar.gz", hash = "sha256:6b1a1de53338646e8c8405803cffb659e8eb7bb02fff4c9be62a7acfac8370c9"},
-    {file = "zmq-0.0.0.zip", hash = "sha256:21cfc6be254c9bc25e4dabb8a3b2006a4227966b7b39a637426084c8dc6901f7"},
-]
-
-[[package]]
 name = "zope-interface"
 version = "6.3"
 requires_python = ">=3.7"
--- a/pyproject.toml	Fri May 17 17:48:26 2024 -0700
+++ b/pyproject.toml	Sat May 18 23:22:00 2024 -0700
@@ -38,7 +38,6 @@
     "scipy>=1.9.3",
     "braillegraph>=0.6",
     "tenacity>=8.2.2",
-    "zmq>=0.0.0",
     "mido>=1.2.10",
     "alsa-midi>=1.0.1",
     "treq>=22.2.0",
--- a/src/light9/collector/collector.py	Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/collector/collector.py	Sat May 18 23:22:00 2024 -0700
@@ -1,24 +1,38 @@
 import logging
 import time
 from typing import Dict, List, Set, Tuple, cast
-from light9.typedgraph import typedValue
 
 from prometheus_client import Summary
 from rdfdb.syncedgraph.syncedgraph import SyncedGraph
-from rdflib import URIRef
 
 from light9.collector.device import resolve, toOutputAttrs
 from light9.collector.output import Output as OutputInstance
 from light9.collector.weblisteners import WebListeners
 from light9.effect.settings import DeviceSettings
 from light9.namespaces import L9, RDF
-from light9.newtypes import (ClientSessionType, ClientType, DeviceAttr, DeviceClass, DeviceSetting, DeviceUri, DmxIndex, DmxMessageIndex, OutputAttr,
-                             OutputRange, OutputUri, OutputValue, UnixTime, VTUnion, uriTail)
+from light9.newtypes import (
+    ClientSessionType,
+    ClientType,
+    DeviceAttr,
+    DeviceClass,
+    DeviceUri,
+    DmxIndex,
+    DmxMessageIndex,
+    OutputAttr,
+    OutputRange,
+    OutputUri,
+    OutputValue,
+    UnixTime,
+    VTUnion,
+    uriTail,
+)
+from light9.typedgraph import typedValue
 
 log = logging.getLogger('collector')
 
 STAT_SETATTR = Summary('set_attr', 'setAttr calls')
 
+
 def makeDmxMessageIndex(base: DmxIndex, offset: DmxIndex) -> DmxMessageIndex:
     return DmxMessageIndex(base + offset - 1)
 
--- a/src/light9/collector/collector_client_asyncio.py	Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/collector/collector_client_asyncio.py	Sat May 18 23:22:00 2024 -0700
@@ -1,12 +1,12 @@
-import asyncio
 import json
 import logging
 import time
-from light9 import networking
-from light9.effect.settings import DeviceSettings
+
 import zmq.asyncio
 from prometheus_client import Summary
 
+from light9.effect.settings import DeviceSettings
+
 log = logging.getLogger('coll_client')
 
 ZMQ_SEND = Summary('zmq_send', 'calls')
--- a/src/light9/collector/device.py	Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/collector/device.py	Sat May 18 23:22:00 2024 -0700
@@ -1,11 +1,20 @@
 import logging
-from typing import Dict, List, Any, TypeVar, cast
-from light9.namespaces import L9
+from typing import Dict, List, cast
+
+import colormath.color_conversions
+from colormath.color_objects import CMYColor, sRGBColor
 from rdflib import Literal, URIRef
 from webcolors import hex_to_rgb, rgb_to_hex
-from colormath.color_objects import sRGBColor, CMYColor
-import colormath.color_conversions
-from light9.newtypes import VT, DeviceClass, HexColor, OutputAttr, OutputValue, DeviceUri, DeviceAttr, VTUnion
+
+from light9.namespaces import L9
+from light9.newtypes import (
+    DeviceAttr,
+    DeviceClass,
+    HexColor,
+    OutputAttr,
+    OutputValue,
+    VTUnion,
+)
 
 log = logging.getLogger('device')
 
@@ -50,10 +59,7 @@
     return cast(HexColor, rgb_to_hex(tuple(maxes)))
 
 
-def resolve(
-        deviceType: DeviceClass,
-        deviceAttr: DeviceAttr,
-        values: List[VTUnion]) -> VTUnion:  # todo: return should be VT
+def resolve(deviceType: DeviceClass, deviceAttr: DeviceAttr, values: List[VTUnion]) -> VTUnion:  # todo: return should be VT
     """
     return one value to use for this attr, given a set of them that
     have come in simultaneously. len(values) >= 1.
@@ -82,8 +88,10 @@
 
 def toOutputAttrs(
         deviceType: DeviceClass,
-        deviceAttrSettings: Dict[DeviceAttr, VTUnion  # TODO
-                                ]) -> Dict[OutputAttr, OutputValue]:
+        deviceAttrSettings: Dict[
+            DeviceAttr,
+            VTUnion  # TODO
+        ]) -> Dict[OutputAttr, OutputValue]:
     return dict((OutputAttr(u), OutputValue(v)) for u, v in untype_toOutputAttrs(deviceType, deviceAttrSettings).items())
 
 
@@ -134,10 +142,12 @@
         return {L9['red']: r, L9['green']: g, L9['blue']: b}
     elif deviceType == L9['LedPar90']:
         r, g, b = rgbAttr(L9['color'])
-        return {L9['master']: 255, L9['red']: r, L9['green']: g, L9['blue']: b, L9['white']: 0}
+        w = _8bit(floatAttr(L9['white']))
+        return {L9['master']: 255, L9['red']: r, L9['green']: g, L9['blue']: b, L9['white']: w}
     elif deviceType == L9['LedPar54']:
         r, g, b = rgbAttr(L9['color'])
-        return {L9['master']: 255, L9['red']: r, L9['green']: g, L9['blue']: b, L9['white']: 0, L9['strobe']: 0}
+        w = _8bit(floatAttr(L9['white']))
+        return {L9['master']: 255, L9['red']: r, L9['green']: g, L9['blue']: b, L9['white']: w, L9['strobe']: 0}
     elif deviceType == L9['SimpleDimmer']:
         return {L9['level']: _8bit(floatAttr(L9['brightness']))}
     elif deviceType == L9['MegaFlash']:
--- a/src/light9/collector/weblisteners.py	Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/collector/weblisteners.py	Sat May 18 23:22:00 2024 -0700
@@ -1,13 +1,11 @@
 import asyncio
 import io
-import json
 import logging
 import time
 from typing import Any, Awaitable, Dict, List, Protocol, Tuple
 
 import fastavro
 from fastavro.schema import load_schema
-from light9.collector.output import Output as OutputInstance
 from light9.newtypes import (DeviceUri, DmxIndex, DmxMessageIndex, OutputAttr, OutputUri, OutputValue)
 import starlette.websockets
 import websockets
@@ -97,5 +95,4 @@
         out = io.BytesIO()
         fastavro.schemaless_writer(out, self.CollectorUpdateSchema, {'OutputAttrsSet': {'dev': dev, 'attrs': attrRows}})
         msg = out.getvalue()
-        log.info(f'made update message {len(msg)=}')
         return msg
--- a/src/light9/effect/edit.py	Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/edit.py	Sat May 18 23:22:00 2024 -0700
@@ -1,11 +1,11 @@
-from rdflib import URIRef, Literal
+import treq
+from rdfdb.patch import Patch
+from rdflib import Literal, URIRef
 from twisted.internet.defer import inlineCallbacks, returnValue
-import treq
 
 from light9 import networking
 from light9.curvecalc.curve import CurveResource
 from light9.namespaces import L9, RDF, RDFS
-from rdfdb.patch import Patch
 
 
 def clamp(x, lo, hi):
@@ -53,13 +53,10 @@
             ] + [(effect, L9['code'], c, ctx) for c in droppedCodes])
         elif L9['Submaster'] in droppedTypes:
             quads.extend([
-                (effect, L9['code'], Literal('out = %s * env' % dropped.n3()),
-                 ctx),
+                (effect, L9['code'], Literal('out = %s * env' % dropped.n3()), ctx),
             ])
         else:
-            raise NotImplementedError(
-                "don't know how to add an effect from %r (types=%r)" %
-                (dropped, droppedTypes))
+            raise NotImplementedError("don't know how to add an effect from %r (types=%r)" % (dropped, droppedTypes))
 
         _maybeAddMusicLine(quads, effect, song, ctx)
 
@@ -90,8 +87,7 @@
         if L9['Effect'] in droppedTypes:
             musicStatus = yield getMusicStatus()
             songTime = musicStatus['t']
-            note = _makeNote(graph, song, note, quads, ctx, dropped, songTime,
-                             event, fade)
+            note = _makeNote(graph, song, note, quads, ctx, dropped, songTime, event, fade)
         else:
             raise NotImplementedError
 
@@ -99,8 +95,7 @@
 
 
 def _point(ctx, uri, t, v):
-    return [(uri, L9['time'], Literal(round(t, 3)), ctx),
-            (uri, L9['value'], Literal(round(v, 3)), ctx)]
+    return [(uri, L9['time'], Literal(round(t, 3)), ctx), (uri, L9['value'], Literal(round(v, 3)), ctx)]
 
 
 def _finishCurve(graph, note, quads, ctx, songTime):
@@ -110,9 +105,7 @@
 
     pt2 = graph.sequentialUri(curve + 'p')
     pt3 = graph.sequentialUri(curve + 'p')
-    quads.extend([(curve, L9['point'], pt2, ctx)] +
-                 _point(ctx, pt2, songTime - origin, 1) +
-                 [(curve, L9['point'], pt3, ctx)] +
+    quads.extend([(curve, L9['point'], pt2, ctx)] + _point(ctx, pt2, songTime - origin, 1) + [(curve, L9['point'], pt3, ctx)] +
                  _point(ctx, pt3, songTime - origin + .5, 0))
 
 
@@ -199,7 +192,5 @@
 
     for spoc in quads:
         if spoc[1] == L9['code'] and 'music' in spoc[2]:
-            quads.extend([(effect, L9['code'],
-                           Literal('music = %s' % musicCurveForSong(song).n3()),
-                           ctx)])
+            quads.extend([(effect, L9['code'], Literal('music = %s' % musicCurveForSong(song).n3()), ctx)])
             break
--- a/src/light9/effect/effect_functions.py	Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/effect_functions.py	Sat May 18 23:22:00 2024 -0700
@@ -1,5 +1,6 @@
 import logging
 import random
+from typing import cast
 
 from PIL import Image
 from webcolors import rgb_to_hex
@@ -7,13 +8,14 @@
 from light9.effect.scale import scale
 from light9.effect.settings import DeviceSettings
 from light9.namespaces import L9
+from light9.newtypes import HexColor
 
 random.seed(0)
 
 log = logging.getLogger('effectfunc')
 
 
-def sample8(img, x, y, repeat=False):
+def sample8(img, x, y, repeat=False) -> tuple[int, int, int]:
     if not (0 <= y < img.height):
         return (0, 0, 0)
     if 0 <= x < img.width:
@@ -54,10 +56,10 @@
 ) -> DeviceSettings:
     x = int((songTime / period) * image.width)
     out = []
-    for y, (d, da, v) in enumerate(devs.asOrderedList()):
+    for y, (d, da, v) in enumerate(devs.asList()):
         if da != L9['color']:
             continue
         color8 = sample8(image, x, y, repeat=True)
-        color = rgb_to_hex(tuple(color8))
-        out.append((d, da, scale(color, strength * v)))
-    return DeviceSettings(devs.graph, out)
\ No newline at end of file
+        color = HexColor(rgb_to_hex(color8))
+        out.append((d, da, scale(color, strength * cast(float, v))))
+    return DeviceSettings(devs.graph, out)
--- a/src/light9/effect/effecteval2.py	Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/effecteval2.py	Sat May 18 23:22:00 2024 -0700
@@ -1,6 +1,6 @@
-import traceback
 import inspect
 import logging
+import traceback
 from dataclasses import dataclass
 from typing import Callable, List, Optional
 
@@ -11,7 +11,14 @@
 from light9.effect.effect_function_library import EffectFunctionLibrary
 from light9.effect.settings import DeviceSettings, EffectSettings
 from light9.namespaces import L9
-from light9.newtypes import (DeviceAttr, DeviceUri, EffectAttr, EffectFunction, EffectUri, VTUnion)
+from light9.newtypes import (
+    DeviceAttr,
+    DeviceUri,
+    EffectAttr,
+    EffectFunction,
+    EffectUri,
+    VTUnion,
+)
 from light9.typedgraph import typedValue
 
 log = logging.getLogger('effecteval')
@@ -92,7 +99,7 @@
         for arg in c.funcArgs:
             if arg.annotation == DeviceSettings:
                 v = c.devSettings
-                if v is None: # asked for ds but we have none
+                if v is None:  # asked for ds but we have none
                     log.debug("%s asked for devs but we have none in config", self.uri)
                     return DeviceSettings(self.graph, [])
             elif arg.name == 'songTime':
--- a/src/light9/effect/scale.py	Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/scale.py	Sat May 18 23:22:00 2024 -0700
@@ -1,9 +1,12 @@
+import logging
 from decimal import Decimal
 
 from webcolors import hex_to_rgb, rgb_to_hex
 
 from light9.newtypes import VTUnion
 
+log = logging.getLogger('scale')
+
 
 def scale(value: VTUnion, strength: float):
     if isinstance(value, Decimal):
--- a/src/light9/effect/sequencer/eval_faders.py	Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/sequencer/eval_faders.py	Sat May 18 23:22:00 2024 -0700
@@ -1,6 +1,6 @@
-import traceback
 import logging
 import time
+import traceback
 from dataclasses import dataclass
 from typing import List, Optional, cast
 
--- a/src/light9/effect/sequencer/service.py	Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/sequencer/service.py	Sat May 18 23:22:00 2024 -0700
@@ -14,8 +14,8 @@
 from starlette.routing import Route
 from starlette_exporter import PrometheusMiddleware, handle_metrics
 
+from light9 import networking
 from light9.background_loop import loop_forever
-from light9 import networking
 from light9.collector.collector_client_asyncio import sendToCollector
 from light9.effect.effect_function_library import EffectFunctionLibrary
 from light9.effect.sequencer.eval_faders import FaderEval
@@ -51,10 +51,10 @@
 def main():
     graph = SyncedGraph(networking.rdfdb.url, "effectSequencer")
     logging.getLogger('sse_starlette.sse').setLevel(logging.INFO)
- 
+
     logging.getLogger('autodepgraphapi').setLevel(logging.INFO)
     logging.getLogger('syncedgraph').setLevel(logging.INFO)
- 
+
     logging.getLogger('effecteval').setLevel(logging.INFO)
     logging.getLogger('seq.fader').setLevel(logging.INFO)
 
--- a/src/light9/effect/settings.py	Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/settings.py	Sat May 18 23:22:00 2024 -0700
@@ -19,7 +19,7 @@
 from light9.collector.device import resolve
 from light9.localsyncedgraph import LocalSyncedGraph
 from light9.namespaces import L9, RDF
-from light9.newtypes import (DeviceAttr, DeviceUri, EffectAttr, HexColor, VTUnion)
+from light9.newtypes import DeviceAttr, DeviceUri, EffectAttr, HexColor, VTUnion
 
 log = logging.getLogger('settings')
 
--- a/web/TiledHome.ts	Fri May 17 17:48:26 2024 -0700
+++ b/web/TiledHome.ts	Sat May 18 23:22:00 2024 -0700
@@ -1,25 +1,15 @@
 import debug from "debug";
 import * as FlexLayout from "flexlayout-react";
-import { LitElement, html } from "lit";
+import { LitElement, TemplateResult, html } from "lit";
 import { customElement } from "lit/decorators.js";
 import * as React from "react";
 import { createRoot } from "react-dom/client";
 import { getTopGraph } from "./RdfdbSyncedGraph";
 import { SyncedGraph } from "./SyncedGraph";
+import { panelDisplayName, panelElementNames, panelUrl } from "./panels";
 export { RdfdbSyncedGraph } from "./RdfdbSyncedGraph";
-export { Light9CollectorUi } from "./collector/Light9CollectorUi";
-export { Light9FadeUi } from "./fade/Light9FadeUi";
-export { Light9DeviceSettings } from "./live/Light9DeviceSettings";
 const log = debug("home");
 
-const config: FlexLayout.IJsonModel = {
-  global: {
-    tabEnableRename: false,
-  },
-  borders: [],
-  layout: fullLayout(persistedLayout) as FlexLayout.IJsonRowNode,
-};
-
 // see https://github.com/lit/lit/tree/main/packages/labs/react
 
 // Store flexlayout panels in per-browser localstorage.
@@ -31,7 +21,7 @@
       {
         type: "tabset",
         weight: 50,
-        children: [{ type: "tab", name: "devsettings", component: "light9-device-settings" }],
+        children: [{ type: "tab", name: "fade", component: "light9-fade-ui" }],
       },
       {
         type: "tabset",
@@ -53,6 +43,9 @@
   }
 }
 
+// This lets lit call a method on a react element.
+let addTab: (component: string) => void;
+
 class Main extends React.Component {
   state: { model: FlexLayout.Model; persistence: PersistentLayout };
   constructor(props: any) {
@@ -75,6 +68,15 @@
   };
 
   render() {
+    addTab = (component) => {
+      const name = panelDisplayName(component);
+      if (name === undefined) throw new Error("no such panel: " + component);
+      const newTab = { type: "tab", name: name, component: component };
+      const firstTabSet = this.state.model.getRoot().getChildren()[0];
+      const action = FlexLayout.Actions.addNode(newTab, firstTabSet.getId(), FlexLayout.DockLocation.LEFT, 0);
+      this.state.model.doAction(action);
+    };
+
     return React.createElement(FlexLayout.Layout, {
       model: this.state.model,
       realtimeResize: true,
@@ -92,15 +94,31 @@
 export class Light9HomeStatus extends LitElement {
   graph!: SyncedGraph;
   render() {
-    return html`<rdfdb-synced-graph></rdfdb-synced-graph>
-      <a href="metrics/">metrics</a> `;
+    return html`
+      <rdfdb-synced-graph></rdfdb-synced-graph>
+      <a href="metrics/">metrics</a>
+      Open tab or new window: ${panelElementNames().map((elem) => this.linkToPanelPage(elem))}
+    `;
   }
+
+  linkToPanelPage(elem: string): TemplateResult {
+    return html`<a @click=${this.onClickPanelLink} data-panel-elem="${elem}" href=${panelUrl(elem)}> ${panelDisplayName(elem)} </a> `;
+  }
+
   constructor() {
     super();
     getTopGraph().then((g) => {
       this.graph = g;
     });
   }
+
+  onClickPanelLink(ev: MouseEvent) {
+    ev.preventDefault();
+
+    const a = ev.target as HTMLAnchorElement;
+    const elem = a.dataset.panelElem!;
+    addTab(elem);
+  }
 }
 
 const root = createRoot(document.getElementById("container")!);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/panels.ts	Sat May 18 23:22:00 2024 -0700
@@ -0,0 +1,29 @@
+export { Light9AscoltamiUi } from "./ascoltami/Light9AscoltamiUi";
+export { Light9CollectorUi } from "./collector/Light9CollectorUi";
+export { Light9EffectListing } from "./effects/Light9EffectListing";
+export { Light9FadeUi } from "./fade/Light9FadeUi";
+export { Light9DeviceSettings } from "./live/Light9DeviceSettings";
+
+const panels: Map<string, Array<string>> = new Map([
+  ["light9-ascoltami-ui", ["ascoltami", "ascoltami"]],
+  ["light9-collector-ui", ["collector", "collector"]],
+  ["light9-device-settings", ["device-settings", "live"]],
+  ["light9-effect-listing", ["effect-listing" , "effectListing"]],
+  ["light9-fade-ui", ["fade", "fade"]],
+]);
+
+export function panelElementNames(): string[] {
+  return Array.from(panels.keys());
+}
+
+export function panelUrl(elem: string): string {
+    const row = panels.get(elem);
+    if (!row) throw new Error("No panel: " + elem);
+    return "/" + row[1] + "/";
+  }
+
+export function panelDisplayName(elem: string): string {
+    const row = panels.get(elem);
+    if (!row) throw new Error("No panel: " + elem);
+    return row[0];
+}  
\ No newline at end of file