Add a colour-wheel widget to the dashboard
A custom hue ring — a conic gradient, not a canvas — with saturation and brightness sliders beside or under it depending on the tile's shape, sized for a wall panel and reachable from a keyboard. It publishes [h, s, v] by default, which is what the reference installation's DMX encoders read, and `format` switches that to [r, g, b] or "#rrggbb". `usePublish` moves to its own module so a widget in a file of its own can reach it without importing `widgets.tsx` back.
This commit is contained in:
@@ -60,9 +60,17 @@ WidgetType = Literal[
|
||||
"slider",
|
||||
"input",
|
||||
"dropdown",
|
||||
"color",
|
||||
]
|
||||
|
||||
INPUT_WIDGETS = {"button", "switch", "slider", "input", "dropdown"}
|
||||
INPUT_WIDGETS = {"button", "switch", "slider", "input", "dropdown", "color"}
|
||||
|
||||
#: What a colour widget puts on the wire, by the format it was configured for.
|
||||
#: The default is what the Node-RED installation this ports already sends its
|
||||
#: DMX encoders — ``[h, s, v]``, hue in degrees and the other two in percent —
|
||||
#: and the two alternatives exist because fixtures differ. Mirrored in the
|
||||
#: client (``frontend/src/components/Dashboard/ColorWidget.tsx``).
|
||||
COLOR_DTYPES = {"hsv": "list", "rgb": "list", "hex": "str"}
|
||||
|
||||
#: What a widget may be pointed at, by payload type. A switch that reads a
|
||||
#: float has nothing to show and nothing safe to send, so the pairing belongs
|
||||
@@ -80,6 +88,9 @@ WIDGET_DTYPES: dict[str, set[str]] = {
|
||||
"notification": {"record"},
|
||||
"bar": {"float", "int"},
|
||||
"forecast": {"list"},
|
||||
# Either shape a colour can travel as; which of the two this widget means
|
||||
# is its ``format``, checked against ``COLOR_DTYPES`` below.
|
||||
"color": {"list", "str"},
|
||||
# An icon maps weather strings, bool hints and numbers alike, and a clock
|
||||
# binds nothing at all, so neither has a row to be held to.
|
||||
}
|
||||
@@ -107,6 +118,19 @@ class WidgetDef(BaseModel):
|
||||
refresh_s, range_s}``: it publishes ``{range_s, interval_s}`` to
|
||||
``request`` exactly as a slider publishes a value, and draws the ``series``
|
||||
a flow answers with on ``message``.
|
||||
|
||||
A colour widget picks what it publishes with ``format``, because fixtures
|
||||
differ and a change node per tile is not the answer:
|
||||
|
||||
- ``hsv`` (the default) — ``[h, s, v]``, hue 0-360 degrees, saturation and
|
||||
value 0-100 percent. What the Node-RED installation this ports feeds its
|
||||
3CH/4CH DMX encoders, which divide by 360 and by 100.
|
||||
- ``rgb`` — ``[r, g, b]``, each 0-255. The conventional range; the
|
||||
reference's own encoders produce it after converting.
|
||||
- ``hex`` — ``"#rrggbb"``, lowercase. Conventional likewise.
|
||||
|
||||
The first two are a ``list`` message, the third a ``str``, which is what
|
||||
``COLOR_DTYPES`` records and the check below holds a binding to.
|
||||
"""
|
||||
|
||||
id: str
|
||||
@@ -217,6 +241,19 @@ class WidgetDef(BaseModel):
|
||||
if isinstance(inner, list) and len(inner) > BAR_SEGMENTS:
|
||||
raise ValueError(f"a bar nests at most {BAR_SEGMENTS} readings")
|
||||
|
||||
if self.type == "color":
|
||||
# The row above allows both shapes a colour travels as; the format
|
||||
# is what decides which of them this widget actually sends. An
|
||||
# unknown one is read as the default, exactly as the client does.
|
||||
fmt = str(self.config.get("format") or "hsv")
|
||||
want = COLOR_DTYPES.get(fmt, "list")
|
||||
bound = str(self.config.get("dtype") or "")
|
||||
if bound and bound != want:
|
||||
raise ValueError(
|
||||
f"a colour widget sending {fmt} needs a '{want}' message, "
|
||||
f"not a '{bound}'"
|
||||
)
|
||||
|
||||
allowed = WIDGET_DTYPES.get(self.type)
|
||||
if not allowed:
|
||||
return self
|
||||
@@ -558,6 +595,7 @@ def default_dashboard(name: str) -> DashboardDef:
|
||||
|
||||
__all__ = [
|
||||
"BAR_SEGMENTS",
|
||||
"COLOR_DTYPES",
|
||||
"DASHBOARD_DIR",
|
||||
"HISTORY_CAP",
|
||||
"INPUT_WIDGETS",
|
||||
|
||||
Reference in New Issue
Block a user