Remove idle color feature, simplify power to turn-off only, fix settings serial port bug

- Remove static/idle color from entire stack (storage, API, processing, UI, CSS, locales)
- Simplify device power button to turn-off only (send black frame, no toggle)
- Send black frame on serial port close (AdalightClient.close)
- Fix settings modal serial port dropdown showing WLED devices due to stale deviceType

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 04:04:28 +03:00
parent 1f6c913343
commit 8a0730d91b
12 changed files with 29 additions and 278 deletions

View File

@@ -423,39 +423,6 @@ section {
}
/* Static color picker — inline in card-subtitle */
.static-color-control {
display: inline-flex;
align-items: center;
gap: 4px;
}
.static-color-picker {
width: 22px;
height: 18px;
padding: 0;
border: 1px solid var(--border-color);
border-radius: 3px;
cursor: pointer;
background: none;
vertical-align: middle;
}
.btn-clear-color {
background: none;
border: none;
color: #777;
font-size: 0.75rem;
cursor: pointer;
padding: 0 2px;
line-height: 1;
border-radius: 3px;
transition: color 0.2s;
}
.btn-clear-color:hover {
color: var(--danger-color);
}
.section-header {
display: flex;
align-items: center;

View File

@@ -30,8 +30,7 @@ import {
import {
showSettings, closeDeviceSettingsModal, forceCloseDeviceSettingsModal,
saveDeviceSettings, updateBrightnessLabel, saveCardBrightness,
saveDeviceStaticColor, clearDeviceStaticColor,
toggleDevicePower, removeDevice, loadDevices,
turnOffDevice, removeDevice, loadDevices,
updateSettingsBaudFpsHint,
} from './features/devices.js';
import {
@@ -149,9 +148,7 @@ Object.assign(window, {
saveDeviceSettings,
updateBrightnessLabel,
saveCardBrightness,
saveDeviceStaticColor,
clearDeviceStaticColor,
toggleDevicePower,
turnOffDevice,
removeDevice,
loadDevices,
updateSettingsBaudFpsHint,

View File

@@ -69,7 +69,7 @@ export function createDeviceCard(device) {
return `
<div class="card" data-device-id="${device.id}">
<div class="card-top-actions">
${(device.capabilities || []).includes('power_control') ? `<button class="card-top-btn card-power-btn" onclick="toggleDevicePower('${device.id}')" title="${t('device.button.power_toggle')}"></button>` : ''}
${(device.capabilities || []).includes('power_control') ? `<button class="card-top-btn card-power-btn" onclick="turnOffDevice('${device.id}')" title="${t('device.button.power_off')}"></button>` : ''}
<button class="card-remove-btn" onclick="removeDevice('${device.id}')" title="${t('device.button.remove')}">&#x2715;</button>
</div>
<div class="card-header">
@@ -85,7 +85,6 @@ export function createDeviceCard(device) {
${ledCount ? `<span class="card-meta" title="${t('device.led_count')}">💡 ${ledCount}</span>` : ''}
${state.device_led_type ? `<span class="card-meta">🔌 ${state.device_led_type.replace(/ RGBW$/, '')}</span>` : ''}
<span class="card-meta" title="${state.device_rgbw ? 'RGBW' : 'RGB'}"><span class="channel-indicator"><span class="ch" style="background:#e53935"></span><span class="ch" style="background:#43a047"></span><span class="ch" style="background:#1e88e5"></span>${state.device_rgbw ? '<span class="ch" style="background:#eee"></span>' : ''}</span></span>
${(device.capabilities || []).includes('static_color') ? `<span class="card-meta static-color-control" data-color-wrap="${device.id}"><input type="color" class="static-color-picker" value="${device.static_color ? rgbToHex(...device.static_color) : '#000000'}" data-device-color="${device.id}" onchange="saveDeviceStaticColor('${device.id}', this.value)" title="${t('device.static_color.hint')}"><button class="btn-clear-color" onclick="clearDeviceStaticColor('${device.id}')" title="${t('device.static_color.clear')}" ${!device.static_color ? 'style="display:none"' : ''}>&#x2715;</button></span>` : ''}
</div>
${(device.capabilities || []).includes('brightness_control') ? `
<div class="brightness-control${_deviceBrightnessCache[device.id] == null ? ' brightness-loading' : ''}" data-brightness-wrap="${device.id}">
@@ -106,26 +105,21 @@ export function createDeviceCard(device) {
`;
}
export async function toggleDevicePower(deviceId) {
export async function turnOffDevice(deviceId) {
try {
const getResp = await fetchWithAuth(`/devices/${deviceId}/power`);
if (!getResp.ok) { showToast('Failed to get power state', 'error'); return; }
const current = await getResp.json();
const newState = !current.on;
const setResp = await fetchWithAuth(`/devices/${deviceId}/power`, {
method: 'PUT',
body: JSON.stringify({ on: newState })
body: JSON.stringify({ on: false })
});
if (setResp.ok) {
showToast(t(newState ? 'device.power.on_success' : 'device.power.off_success'), 'success');
showToast(t('device.power.off_success'), 'success');
} else {
const error = await setResp.json();
showToast(error.detail || 'Failed', 'error');
}
} catch (error) {
if (error.isAuth) return;
showToast('Failed to toggle power', 'error');
showToast('Failed to turn off device', 'error');
}
}
@@ -162,6 +156,11 @@ export async function showSettings(deviceId) {
const device = await deviceResponse.json();
const isAdalight = isSerialDevice(device.device_type);
const caps = device.capabilities || [];
// Set modal state before populating fields (so async helpers read correct type)
settingsModal.deviceType = device.device_type;
settingsModal.capabilities = caps;
document.getElementById('settings-device-id').value = device.id;
document.getElementById('settings-device-name').value = device.name;
@@ -181,7 +180,6 @@ export async function showSettings(deviceId) {
serialGroup.style.display = 'none';
}
const caps = device.capabilities || [];
const ledCountGroup = document.getElementById('settings-led-count-group');
if (caps.includes('manual_led_count')) {
ledCountGroup.style.display = '';
@@ -205,9 +203,6 @@ export async function showSettings(deviceId) {
}
document.getElementById('settings-auto-shutdown').checked = !!device.auto_shutdown;
settingsModal.deviceType = device.device_type;
settingsModal.capabilities = caps;
settingsModal.snapshot();
settingsModal.open();
@@ -314,54 +309,6 @@ export async function fetchDeviceBrightness(deviceId) {
}
}
// Static color helpers
export function rgbToHex(r, g, b) {
return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
}
export function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : null;
}
export async function saveDeviceStaticColor(deviceId, hexValue) {
const rgb = hexToRgb(hexValue);
try {
await fetch(`${API_BASE}/devices/${deviceId}/color`, {
method: 'PUT',
headers: { ...getHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({ color: rgb })
});
const wrap = document.querySelector(`[data-color-wrap="${deviceId}"]`);
if (wrap) {
const clearBtn = wrap.querySelector('.btn-clear-color');
if (clearBtn) clearBtn.style.display = '';
}
} catch (err) {
console.error('Failed to set static color:', err);
showToast('Failed to set static color', 'error');
}
}
export async function clearDeviceStaticColor(deviceId) {
try {
await fetch(`${API_BASE}/devices/${deviceId}/color`, {
method: 'PUT',
headers: { ...getHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify({ color: null })
});
const picker = document.querySelector(`[data-device-color="${deviceId}"]`);
if (picker) picker.value = '#000000';
const wrap = document.querySelector(`[data-color-wrap="${deviceId}"]`);
if (wrap) {
const clearBtn = wrap.querySelector('.btn-clear-color');
if (clearBtn) clearBtn.style.display = 'none';
}
} catch (err) {
console.error('Failed to clear static color:', err);
}
}
// FPS hint helpers (shared with device-discovery)
export function _computeMaxFps(baudRate, ledCount, deviceType) {
if (!baudRate || !ledCount || ledCount < 1) return null;

View File

@@ -131,8 +131,7 @@
"device.button.calibrate": "Calibrate",
"device.button.remove": "Remove",
"device.button.webui": "Open Device Web UI",
"device.button.power_toggle": "Toggle Power",
"device.power.on_success": "Device turned on",
"device.button.power_off": "Turn Off",
"device.power.off_success": "Device turned off",
"device.status.connected": "Connected",
"device.status.disconnected": "Disconnected",
@@ -159,9 +158,6 @@
"device.health.online": "Online",
"device.health.offline": "Offline",
"device.health.checking": "Checking...",
"device.static_color": "Idle Color",
"device.static_color.hint": "Color shown when device is idle",
"device.static_color.clear": "Clear idle color",
"device.tutorial.start": "Start tutorial",
"device.tip.metadata": "Device info (LED count, type, color channels) is auto-detected from the device",
"device.tip.brightness": "Slide to adjust device brightness",

View File

@@ -131,8 +131,7 @@
"device.button.calibrate": "Калибровка",
"device.button.remove": "Удалить",
"device.button.webui": "Открыть веб-интерфейс устройства",
"device.button.power_toggle": "Вкл/Выкл",
"device.power.on_success": "Устройство включено",
"device.button.power_off": "Выключить",
"device.power.off_success": "Устройство выключено",
"device.status.connected": "Подключено",
"device.status.disconnected": "Отключено",
@@ -159,9 +158,6 @@
"device.health.online": "Онлайн",
"device.health.offline": "Недоступен",
"device.health.checking": "Проверка...",
"device.static_color": "Цвет ожидания",
"device.static_color.hint": "Цвет, когда устройство в режиме ожидания",
"device.static_color.clear": "Очистить цвет ожидания",
"device.tutorial.start": "Начать обучение",
"device.tip.metadata": "Информация об устройстве (кол-во LED, тип, цветовые каналы) определяется автоматически",
"device.tip.brightness": "Перетащите для регулировки яркости",