feat: HA light target live color preview — per-entity swatches via WebSocket
Lint & Test / test (push) Successful in 1m24s
Lint & Test / test (push) Successful in 1m24s
- Cache per-entity colors in HALightTargetProcessor._update_lights()
- Broadcast colors_update to WS clients at target's update_rate
- WS endpoint: /api/v1/output-targets/{target_id}/ha-light/ws
- Frontend: connect WS when target runs, update swatch colors live
- Card shows colored boxes per mapped entity with entity name labels
This commit is contained in:
@@ -488,6 +488,9 @@ export function createHALightTargetCard(target: any, haSourceMap: Record<string,
|
||||
<div class="metric-value" data-tm="ha-status">${state.ha_connected ? ICON_OK : ICON_WARNING}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ha-light-swatches" data-ha-swatches="${target.id}">
|
||||
${_renderEntitySwatches(state.entity_colors || {}, target.ha_light_mappings || [])}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>`,
|
||||
actions: `
|
||||
@@ -560,6 +563,78 @@ export function initHALightTargetDelegation(container: HTMLElement): void {
|
||||
});
|
||||
}
|
||||
|
||||
// ── Entity color swatches ──
|
||||
|
||||
function _renderEntitySwatches(entityColors: Record<string, any>, mappings: any[]): string {
|
||||
if (!mappings.length) return '';
|
||||
return mappings.map(m => {
|
||||
const c = entityColors[m.entity_id];
|
||||
const bg = c ? c.hex : '#333';
|
||||
const label = m.entity_id.replace('light.', '');
|
||||
return `<div class="ha-light-swatch" data-entity="${escapeHtml(m.entity_id)}">
|
||||
<span class="swatch-color" style="background:${bg}"></span>
|
||||
<span class="swatch-label">${escapeHtml(label)}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ── WebSocket color preview ──
|
||||
|
||||
const _haLightWS: Record<string, WebSocket> = {};
|
||||
|
||||
export function connectHALightWS(targetId: string): void {
|
||||
if (_haLightWS[targetId]) return;
|
||||
const loc = window.location;
|
||||
const wsProto = loc.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const apiKey = (window as any).apiKey || localStorage.getItem('wled_api_key') || '';
|
||||
const url = `${wsProto}//${loc.host}/api/v1/output-targets/${targetId}/ha-light/ws?token=${encodeURIComponent(apiKey)}`;
|
||||
|
||||
const ws = new WebSocket(url);
|
||||
_haLightWS[targetId] = ws;
|
||||
|
||||
ws.onmessage = (ev) => {
|
||||
try {
|
||||
const data = JSON.parse(ev.data);
|
||||
if (data.type === 'colors_update') {
|
||||
_updateSwatchColors(targetId, data.colors);
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
delete _haLightWS[targetId];
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
delete _haLightWS[targetId];
|
||||
};
|
||||
}
|
||||
|
||||
export function disconnectHALightWS(targetId: string): void {
|
||||
const ws = _haLightWS[targetId];
|
||||
if (ws) {
|
||||
ws.close();
|
||||
delete _haLightWS[targetId];
|
||||
}
|
||||
}
|
||||
|
||||
export function disconnectAllHALightWS(): void {
|
||||
for (const id of Object.keys(_haLightWS)) {
|
||||
disconnectHALightWS(id);
|
||||
}
|
||||
}
|
||||
|
||||
function _updateSwatchColors(targetId: string, colors: Record<string, any>): void {
|
||||
const container = document.querySelector(`[data-ha-swatches="${targetId}"]`);
|
||||
if (!container) return;
|
||||
for (const [entityId, c] of Object.entries(colors)) {
|
||||
const swatch = container.querySelector(`[data-entity="${entityId}"] .swatch-color`) as HTMLElement | null;
|
||||
if (swatch) {
|
||||
swatch.style.background = (c as any).hex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Expose to global scope ──
|
||||
|
||||
window.showHALightEditor = showHALightEditor;
|
||||
|
||||
Reference in New Issue
Block a user