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:
2026-08-22 12:50:59 +02:00
parent 1b1b530cfa
commit 8224d12c8c
12 changed files with 725 additions and 99 deletions
+15 -2
View File
@@ -3138,7 +3138,7 @@ export const WidgetDefSchema = {
},
type: {
type: 'string',
enum: ['stat', 'gauge', 'chart', 'markdown', 'agenda', 'notification', 'bar', 'icon', 'forecast', 'clock', 'button', 'switch', 'slider', 'input', 'dropdown'],
enum: ['stat', 'gauge', 'chart', 'markdown', 'agenda', 'notification', 'bar', 'icon', 'forecast', 'clock', 'button', 'switch', 'slider', 'input', 'dropdown', 'color'],
title: 'Type'
},
title: {
@@ -3173,7 +3173,20 @@ message. One with \`\`source: "query"\`\` asks instead, and its config is
\`\`{source, request, request_dtype: "record", message, dtype: "series",
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 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.`
} as const;
export const WorkerInfoSchema = {
+15 -2
View File
@@ -1041,10 +1041,23 @@ export type ValidationResult = {
* 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.
*/
export type WidgetDef = {
id: string;
type: 'stat' | 'gauge' | 'chart' | 'markdown' | 'agenda' | 'notification' | 'bar' | 'icon' | 'forecast' | 'clock' | 'button' | 'switch' | 'slider' | 'input' | 'dropdown';
type: 'stat' | 'gauge' | 'chart' | 'markdown' | 'agenda' | 'notification' | 'bar' | 'icon' | 'forecast' | 'clock' | 'button' | 'switch' | 'slider' | 'input' | 'dropdown' | 'color';
title?: string;
layout?: {
[key: string]: Placement;
@@ -1054,7 +1067,7 @@ export type WidgetDef = {
};
};
export type type = 'stat' | 'gauge' | 'chart' | 'markdown' | 'agenda' | 'notification' | 'bar' | 'icon' | 'forecast' | 'clock' | 'button' | 'switch' | 'slider' | 'input' | 'dropdown';
export type type = 'stat' | 'gauge' | 'chart' | 'markdown' | 'agenda' | 'notification' | 'bar' | 'icon' | 'forecast' | 'clock' | 'button' | 'switch' | 'slider' | 'input' | 'dropdown' | 'color';
export type WorkerInfo = {
name: string;