Comprehensive WebUI review: 41 UX/feature/CSS improvements
Safety & Correctness: - Add confirmation dialogs to Stop All, turnOffDevice - i18n confirm dialog (title, yes, no buttons) - Fix duplicate tutorial-overlay ID - Define missing CSS variables (--radius, --text-primary, --hover-bg, --input-bg) - Fix toast z-index conflict with confirm dialog (2500 → 3000) UX Consistency: - Add backdrop-close to test modals - Add device clone feature (only entity without it) - Add sync clocks to command palette - Replace 20+ hardcoded accent colors with CSS vars/color-mix() - Remove dead .badge duplicate from components.css - Make calibration elements keyboard-accessible (div → button) - Add aria-labels to color picker swatches - Fix pattern canvas mobile horizontal scroll - Fix graph editor mobile bottom clipping Polish: - Add empty-state messages to all CardSection instances - Convert 21 px font-sizes to rem - Add scroll-behavior: smooth with reduced-motion override - Add @media print styles - Add :focus-visible to 4 missing interactive elements - Fix settings modal close label (Cancel → Close) - Fix api-key submit button i18n New Features: - Command palette actions: start/stop targets, activate scenes, enable/disable - Bulk start/stop API endpoints (POST /output-targets/bulk/start|stop) - OS notification history viewer modal - Scene "used by" automation reference count on cards - Clock elapsed time display on Streams tab cards - Device "last seen" relative timestamp on cards - Audio device refresh button in edit modal - Composite layer drag-to-reorder - MQTT settings panel (broker config with JSON persistence) - WebSocket log viewer with level filtering and ring buffer - Runtime log-level adjustment (GET/PUT endpoints + settings UI) - Animated value source waveform canvas preview - Gradient custom preset save/delete (localStorage) - API key read-only display in settings - Backup metadata (file size, auto/manual badges) - Server restart button with confirm + overlay - Partial config export/import per entity type - Progressive disclosure in target editor (Advanced section) CSS Architecture: - Define radius scale tokens (--radius-sm/md/lg/pill) - Scope .cs-filter selectors to remove 7 !important overrides - Consolidate duplicate toggle switch (filter-list → settings-toggle) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -582,7 +582,7 @@ export function onSerialPortFocus() {
|
||||
}
|
||||
}
|
||||
|
||||
export function showAddDevice(presetType = null) {
|
||||
export function showAddDevice(presetType = null, cloneData = null) {
|
||||
// When no type specified: show type picker first
|
||||
if (!presetType) {
|
||||
showTypePicker({
|
||||
@@ -623,6 +623,47 @@ export function showAddDevice(presetType = null) {
|
||||
|
||||
addDeviceModal.open();
|
||||
onDeviceTypeChanged();
|
||||
|
||||
// Prefill fields from clone data (after onDeviceTypeChanged shows/hides fields)
|
||||
if (cloneData) {
|
||||
document.getElementById('device-name').value = (cloneData.name || '') + ' (Copy)';
|
||||
// Clear URL — devices must have unique addresses, user must enter a new one
|
||||
const urlInput = document.getElementById('device-url');
|
||||
if (urlInput) urlInput.value = '';
|
||||
// Prefill LED count
|
||||
const ledCountInput = document.getElementById('device-led-count');
|
||||
if (ledCountInput && cloneData.led_count) ledCountInput.value = cloneData.led_count;
|
||||
// Prefill baud rate for serial devices
|
||||
if (isSerialDevice(presetType)) {
|
||||
const baudSelect = document.getElementById('device-baud-rate');
|
||||
if (baudSelect && cloneData.baud_rate) baudSelect.value = String(cloneData.baud_rate);
|
||||
}
|
||||
// Prefill mock device fields
|
||||
if (isMockDevice(presetType)) {
|
||||
const ledTypeEl = document.getElementById('device-led-type');
|
||||
if (ledTypeEl) ledTypeEl.value = cloneData.rgbw ? 'rgbw' : 'rgb';
|
||||
const sendLatencyEl = document.getElementById('device-send-latency');
|
||||
if (sendLatencyEl) sendLatencyEl.value = cloneData.send_latency_ms ?? 0;
|
||||
}
|
||||
// Prefill DMX fields
|
||||
if (isDmxDevice(presetType)) {
|
||||
const dmxProto = document.getElementById('device-dmx-protocol');
|
||||
if (dmxProto && cloneData.dmx_protocol) dmxProto.value = cloneData.dmx_protocol;
|
||||
const dmxUniverse = document.getElementById('device-dmx-start-universe');
|
||||
if (dmxUniverse && cloneData.dmx_start_universe != null) dmxUniverse.value = cloneData.dmx_start_universe;
|
||||
const dmxChannel = document.getElementById('device-dmx-start-channel');
|
||||
if (dmxChannel && cloneData.dmx_start_channel != null) dmxChannel.value = cloneData.dmx_start_channel;
|
||||
}
|
||||
// Prefill CSPT template selector (after fetch completes)
|
||||
if (cloneData.default_css_processing_template_id) {
|
||||
csptCache.fetch().then(() => {
|
||||
_ensureCsptEntitySelect();
|
||||
const csptEl = document.getElementById('device-css-processing-template');
|
||||
if (csptEl) csptEl.value = cloneData.default_css_processing_template_id;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
desktopFocus(document.getElementById('device-name'));
|
||||
addDeviceModal.snapshot();
|
||||
@@ -984,3 +1025,18 @@ function _showGameSenseFields(show) {
|
||||
const el = document.getElementById('device-gamesense-device-type-group');
|
||||
if (el) el.style.display = show ? '' : 'none';
|
||||
}
|
||||
|
||||
/* ── Clone device ──────────────────────────────────────────────── */
|
||||
|
||||
export async function cloneDevice(deviceId) {
|
||||
try {
|
||||
const resp = await fetchWithAuth(`/devices/${deviceId}`);
|
||||
if (!resp.ok) throw new Error('Failed to load device');
|
||||
const device = await resp.json();
|
||||
showAddDevice(device.device_type || 'wled', device);
|
||||
} catch (error) {
|
||||
if (error.isAuth) return;
|
||||
console.error('Failed to clone device:', error);
|
||||
showToast(t('device.error.clone_failed'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user