diff --git a/server/src/wled_controller/static/js/features/color-strips.js b/server/src/wled_controller/static/js/features/color-strips.js index 1efd3bc..20cb0f0 100644 --- a/server/src/wled_controller/static/js/features/color-strips.js +++ b/server/src/wled_controller/static/js/features/color-strips.js @@ -179,27 +179,15 @@ export function onCSSTypeChange() { // Animation section — shown for static/gradient only const animSection = document.getElementById('css-editor-animation-section'); const animTypeSelect = document.getElementById('css-editor-animation-type'); - const noneOpt = ``; - if (type === 'static') { + if (type === 'static' || type === 'gradient') { animSection.style.display = ''; - animTypeSelect.innerHTML = noneOpt + - `` + - `` + - `` + - `` + - `` + - ``; - } else if (type === 'gradient') { - animSection.style.display = ''; - animTypeSelect.innerHTML = noneOpt + - `` + - `` + - `` + - `` + - `` + - `` + - `` + - ``; + const opts = type === 'gradient' + ? ['none','breathing','gradient_shift','wave','strobe','sparkle','pulse','candle','rainbow_fade'] + : ['none','breathing','strobe','sparkle','pulse','candle','rainbow_fade']; + animTypeSelect.innerHTML = opts.map(v => + `` + ).join(''); + _ensureAnimationTypeIconSelect(type); } else { animSection.style.display = 'none'; } @@ -265,15 +253,14 @@ function _getAnimationPayload() { function _loadAnimationState(anim) { // Set type after onCSSTypeChange() has populated the dropdown - if (anim && anim.enabled && anim.type) { - document.getElementById('css-editor-animation-type').value = anim.type; - } else { - document.getElementById('css-editor-animation-type').value = 'none'; - } + const val = (anim && anim.enabled && anim.type) ? anim.type : 'none'; + document.getElementById('css-editor-animation-type').value = val; + if (_animationTypeIconSelect) _animationTypeIconSelect.setValue(val); _syncAnimationSpeedState(); } export function onAnimationTypeChange() { + if (_animationTypeIconSelect) _animationTypeIconSelect.setValue(document.getElementById('css-editor-animation-type').value); _syncAnimationSpeedState(); } @@ -316,6 +303,7 @@ function _gradientStripHTML(pts, w = 80, h = 16) { /* ── Effect / audio palette IconSelect instances ─────────────── */ +let _animationTypeIconSelect = null; let _interpolationIconSelect = null; let _effectTypeIconSelect = null; let _effectPaletteIconSelect = null; @@ -422,6 +410,36 @@ function _ensureNotificationFilterModeIconSelect() { _notificationFilterModeIconSelect = new IconSelect({ target: sel, items, columns: 3 }); } +function _buildAnimationTypeItems(cssType) { + const items = [ + { value: 'none', icon: _icon(P.square), label: t('color_strip.animation.type.none'), desc: t('color_strip.animation.type.none.desc') }, + { value: 'breathing', icon: _icon(P.activity), label: t('color_strip.animation.type.breathing'), desc: t('color_strip.animation.type.breathing.desc') }, + ]; + if (cssType === 'gradient') { + items.push( + { value: 'gradient_shift', icon: _icon(P.fastForward), label: t('color_strip.animation.type.gradient_shift'), desc: t('color_strip.animation.type.gradient_shift.desc') }, + { value: 'wave', icon: _icon(P.rainbow), label: t('color_strip.animation.type.wave'), desc: t('color_strip.animation.type.wave.desc') }, + ); + } + items.push( + { value: 'strobe', icon: _icon(P.zap), label: t('color_strip.animation.type.strobe'), desc: t('color_strip.animation.type.strobe.desc') }, + { value: 'sparkle', icon: _icon(P.sparkles), label: t('color_strip.animation.type.sparkle'), desc: t('color_strip.animation.type.sparkle.desc') }, + { value: 'pulse', icon: _icon(P.trendingUp),label: t('color_strip.animation.type.pulse'), desc: t('color_strip.animation.type.pulse.desc') }, + { value: 'candle', icon: _icon(P.flame), label: t('color_strip.animation.type.candle'), desc: t('color_strip.animation.type.candle.desc') }, + { value: 'rainbow_fade', icon: _icon(P.rainbow), label: t('color_strip.animation.type.rainbow_fade'), desc: t('color_strip.animation.type.rainbow_fade.desc') }, + ); + return items; +} + +function _ensureAnimationTypeIconSelect(cssType) { + const sel = document.getElementById('css-editor-animation-type'); + if (!sel) return; + const items = _buildAnimationTypeItems(cssType); + // Destroy and recreate — options change between static/gradient + if (_animationTypeIconSelect) { _animationTypeIconSelect.destroy(); _animationTypeIconSelect = null; } + _animationTypeIconSelect = new IconSelect({ target: sel, items, columns: 2 }); +} + /* ── Effect type helpers ──────────────────────────────────────── */ // Palette color control points — mirrors _PALETTE_DEFS in effect_stream.py diff --git a/server/src/wled_controller/static/js/features/targets.js b/server/src/wled_controller/static/js/features/targets.js index 3679e72..bb1bb02 100644 --- a/server/src/wled_controller/static/js/features/targets.js +++ b/server/src/wled_controller/static/js/features/targets.js @@ -28,6 +28,8 @@ import { ICON_WARNING, ICON_PALETTE, ICON_WRENCH, ICON_TEMPLATE, } from '../core/icons.js'; import { EntitySelect } from '../core/entity-palette.js'; +import { IconSelect } from '../core/icon-select.js'; +import * as P from '../core/icon-paths.js'; import { wrapCard } from '../core/card-colors.js'; import { TagInput, renderTagChips } from '../core/tag-input.js'; import { CardSection } from '../core/card-sections.js'; @@ -246,6 +248,7 @@ function _updateBrightnessThresholdVisibility() { let _deviceEntitySelect = null; let _cssEntitySelect = null; let _brightnessVsEntitySelect = null; +let _protocolIconSelect = null; function _populateCssDropdown(selectedId = '') { const select = document.getElementById('target-editor-css-source'); @@ -306,6 +309,19 @@ function _ensureTargetEntitySelects() { }); } +const _pIcon = (d) => `${d}`; + +function _ensureProtocolIconSelect() { + const sel = document.getElementById('target-editor-protocol'); + if (!sel) return; + const items = [ + { value: 'ddp', icon: _pIcon(P.radio), label: t('targets.protocol.ddp'), desc: t('targets.protocol.ddp.desc') }, + { value: 'http', icon: _pIcon(P.globe), label: t('targets.protocol.http'), desc: t('targets.protocol.http.desc') }, + ]; + if (_protocolIconSelect) { _protocolIconSelect.updateItems(items); return; } + _protocolIconSelect = new IconSelect({ target: sel, items, columns: 2 }); +} + export async function showTargetEditor(targetId = null, cloneData = null) { try { // Load devices, CSS sources, and value sources for dropdowns @@ -402,6 +418,8 @@ export async function showTargetEditor(targetId = null, cloneData = null) { // Entity palette selectors _ensureTargetEntitySelects(); + _ensureProtocolIconSelect(); + if (_protocolIconSelect) _protocolIconSelect.setValue(document.getElementById('target-editor-protocol').value); // Auto-name generation _targetNameManuallyEdited = !!(targetId || cloneData); diff --git a/server/src/wled_controller/static/locales/en.json b/server/src/wled_controller/static/locales/en.json index 55e2b29..5cb6716 100644 --- a/server/src/wled_controller/static/locales/en.json +++ b/server/src/wled_controller/static/locales/en.json @@ -1213,6 +1213,10 @@ "targets.adaptive_fps.hint": "Automatically reduce send rate when the device becomes unresponsive, and gradually recover when it stabilizes. Recommended for WiFi devices with weak signal.", "targets.protocol": "Protocol:", "targets.protocol.hint": "DDP sends pixels via fast UDP (recommended for most setups). HTTP uses the JSON API — slower but reliable, limited to ~500 LEDs.", + "targets.protocol.ddp": "DDP (UDP)", + "targets.protocol.ddp.desc": "Fast raw UDP packets — recommended", + "targets.protocol.http": "HTTP", + "targets.protocol.http.desc": "JSON API — slower, ≤500 LEDs", "targets.protocol.serial": "Serial", "search.open": "Search (Ctrl+K)", "search.placeholder": "Search entities... (Ctrl+K)", diff --git a/server/src/wled_controller/static/locales/ru.json b/server/src/wled_controller/static/locales/ru.json index 8b3ae62..b6f9968 100644 --- a/server/src/wled_controller/static/locales/ru.json +++ b/server/src/wled_controller/static/locales/ru.json @@ -1213,6 +1213,10 @@ "targets.adaptive_fps.hint": "Автоматически снижает частоту отправки, когда устройство перестаёт отвечать, и постепенно восстанавливает её при стабилизации. Рекомендуется для WiFi-устройств со слабым сигналом.", "targets.protocol": "Протокол:", "targets.protocol.hint": "DDP отправляет пиксели по быстрому UDP (рекомендуется). HTTP использует JSON API — медленнее, но надёжнее, ограничение ~500 LED.", + "targets.protocol.ddp": "DDP (UDP)", + "targets.protocol.ddp.desc": "Быстрые UDP-пакеты — рекомендуется", + "targets.protocol.http": "HTTP", + "targets.protocol.http.desc": "JSON API — медленнее, ≤500 LED", "targets.protocol.serial": "Serial", "search.open": "Поиск (Ctrl+K)", "search.placeholder": "Поиск... (Ctrl+K)", diff --git a/server/src/wled_controller/static/locales/zh.json b/server/src/wled_controller/static/locales/zh.json index ff1ac91..2ae51ae 100644 --- a/server/src/wled_controller/static/locales/zh.json +++ b/server/src/wled_controller/static/locales/zh.json @@ -1213,6 +1213,10 @@ "targets.adaptive_fps.hint": "当设备无响应时自动降低发送速率,稳定后逐步恢复。推荐用于信号较弱的WiFi设备。", "targets.protocol": "协议:", "targets.protocol.hint": "DDP通过快速UDP发送像素(推荐)。HTTP使用JSON API——较慢但可靠,限制约500个LED。", + "targets.protocol.ddp": "DDP (UDP)", + "targets.protocol.ddp.desc": "快速UDP数据包 - 推荐", + "targets.protocol.http": "HTTP", + "targets.protocol.http.desc": "JSON API - 较慢,≤500 LED", "targets.protocol.serial": "串口", "search.open": "搜索 (Ctrl+K)", "search.placeholder": "搜索实体... (Ctrl+K)",