diff --git a/server/src/wled_controller/static/css/layout.css b/server/src/wled_controller/static/css/layout.css index bc652f2..5f1079a 100644 --- a/server/src/wled_controller/static/css/layout.css +++ b/server/src/wled_controller/static/css/layout.css @@ -278,6 +278,7 @@ h2 { } .color-picker-popover.anchor-right { right: 0; } .color-picker-popover.anchor-left { left: 0; } +.color-picker-popover.cp-fixed { z-index: 1000; } @keyframes color-picker-pop-in { from { opacity: 0; transform: translateY(-4px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } diff --git a/server/src/wled_controller/static/js/core/card-sections.js b/server/src/wled_controller/static/js/core/card-sections.js index 22c1a2d..00afb59 100644 --- a/server/src/wled_controller/static/js/core/card-sections.js +++ b/server/src/wled_controller/static/js/core/card-sections.js @@ -86,13 +86,13 @@ export class CardSection {
- ${t(this.titleKey)} + ${t(this.titleKey)} ${count} ${this.headerExtra ? `${this.headerExtra}` : ''}
- + data-i18n-placeholder="section.filter.placeholder" placeholder="${t('section.filter.placeholder')}" autocomplete="off"> +
diff --git a/server/src/wled_controller/static/js/core/color-picker.js b/server/src/wled_controller/static/js/core/color-picker.js index 0688583..89c5711 100644 --- a/server/src/wled_controller/static/js/core/color-picker.js +++ b/server/src/wled_controller/static/js/core/color-picker.js @@ -73,29 +73,56 @@ window._cpToggle = function (id) { // Close all other pickers first (and drop their card elevation) document.querySelectorAll('.color-picker-popover').forEach(p => { if (p.id !== `cp-pop-${id}`) { - p.style.display = 'none'; - const card = p.closest('.card, .template-card'); - if (card) card.classList.remove('cp-elevated'); + _cpClosePopover(p); } }); const pop = document.getElementById(`cp-pop-${id}`); if (!pop) return; const show = pop.style.display === 'none'; - pop.style.display = show ? '' : 'none'; + if (!show) { + _cpClosePopover(pop); + return; + } + pop.style.display = ''; // Elevate the card so the popover isn't clipped by sibling cards const card = pop.closest('.card, .template-card'); - if (card) card.classList.toggle('cp-elevated', show); - if (show) { - // Mark active dot - const swatch = document.getElementById(`cp-swatch-${id}`); - const cur = swatch ? (_rgbToHex(swatch.style.backgroundColor) || swatch.style.background) : ''; - pop.querySelectorAll('.color-picker-dot').forEach(d => { - const dHex = _rgbToHex(d.style.backgroundColor || d.style.background); - d.classList.toggle('active', dHex.toLowerCase() === cur.toLowerCase()); - }); + if (card) card.classList.toggle('cp-elevated', true); + + // On small screens, if inside an overflow container (e.g. header toolbar), + // switch to fixed positioning so the popover isn't clipped. + const wrapper = pop.closest('.color-picker-wrapper'); + if (wrapper && window.innerWidth <= 600) { + const rect = wrapper.getBoundingClientRect(); + pop.style.position = 'fixed'; + pop.style.top = (rect.bottom + 4) + 'px'; + pop.style.right = Math.max(4, window.innerWidth - rect.right) + 'px'; + pop.style.left = 'auto'; + pop.classList.add('cp-fixed'); } + + // Mark active dot + const swatch = document.getElementById(`cp-swatch-${id}`); + const cur = swatch ? (_rgbToHex(swatch.style.backgroundColor) || swatch.style.background) : ''; + pop.querySelectorAll('.color-picker-dot').forEach(d => { + const dHex = _rgbToHex(d.style.backgroundColor || d.style.background); + d.classList.toggle('active', dHex.toLowerCase() === cur.toLowerCase()); + }); }; +/** Reset popover positioning and close. */ +function _cpClosePopover(pop) { + pop.style.display = 'none'; + if (pop.classList.contains('cp-fixed')) { + pop.classList.remove('cp-fixed'); + pop.style.position = ''; + pop.style.top = ''; + pop.style.right = ''; + pop.style.left = ''; + } + const card = pop.closest('.card, .template-card'); + if (card) card.classList.remove('cp-elevated'); +} + window._cpPick = function (id, hex) { // Update swatch const swatch = document.getElementById(`cp-swatch-${id}`); @@ -103,16 +130,14 @@ window._cpPick = function (id, hex) { // Update native input const native = document.getElementById(`cp-native-${id}`); if (native) native.value = hex; - // Mark active dot + // Mark active dot and close const pop = document.getElementById(`cp-pop-${id}`); if (pop) { pop.querySelectorAll('.color-picker-dot').forEach(d => { const dHex = _rgbToHex(d.style.backgroundColor || d.style.background); d.classList.toggle('active', dHex.toLowerCase() === hex.toLowerCase()); }); - pop.style.display = 'none'; - const card = pop.closest('.card, .template-card'); - if (card) card.classList.remove('cp-elevated'); + _cpClosePopover(pop); } // Fire callback if (_callbacks[id]) _callbacks[id](hex); @@ -126,20 +151,14 @@ window._cpReset = function (id, resetColor) { const pop = document.getElementById(`cp-pop-${id}`); if (pop) { pop.querySelectorAll('.color-picker-dot').forEach(d => d.classList.remove('active')); - pop.style.display = 'none'; - const card = pop.closest('.card, .template-card'); - if (card) card.classList.remove('cp-elevated'); + _cpClosePopover(pop); } // Fire callback with empty string to signal removal if (_callbacks[id]) _callbacks[id](''); }; export function closeAllColorPickers() { - document.querySelectorAll('.color-picker-popover').forEach(p => { - p.style.display = 'none'; - const card = p.closest('.card, .template-card'); - if (card) card.classList.remove('cp-elevated'); - }); + document.querySelectorAll('.color-picker-popover').forEach(p => _cpClosePopover(p)); } // Close on outside click diff --git a/server/src/wled_controller/static/js/features/displays.js b/server/src/wled_controller/static/js/features/displays.js index 4fbcada..4f50ffb 100644 --- a/server/src/wled_controller/static/js/features/displays.js +++ b/server/src/wled_controller/static/js/features/displays.js @@ -25,6 +25,12 @@ export function openDisplayPicker(callback, selectedIndex, engineType = null) { _pickerEngineType = engineType || null; const lightbox = document.getElementById('display-picker-lightbox'); + // Use "Select a Device" title for engines with own display lists (camera, scrcpy, etc.) + const titleEl = lightbox.querySelector('.display-picker-title'); + if (titleEl) { + titleEl.textContent = t(_pickerEngineType ? 'displays.picker.title.device' : 'displays.picker.title'); + } + lightbox.classList.add('active'); requestAnimationFrame(() => { diff --git a/server/src/wled_controller/static/js/features/streams.js b/server/src/wled_controller/static/js/features/streams.js index 539e8d1..ea38de5 100644 --- a/server/src/wled_controller/static/js/features/streams.js +++ b/server/src/wled_controller/static/js/features/streams.js @@ -1295,8 +1295,8 @@ function renderPictureSourcesList(streams) { ]; const tabBar = `
${tabs.map(tab => - `` - ).join('')}
`; + `` + ).join('')}
`; const renderAudioSourceCard = (src) => { const isMono = src.source_type === 'mono'; diff --git a/server/src/wled_controller/static/js/features/targets.js b/server/src/wled_controller/static/js/features/targets.js index 801cf5d..4dddb80 100644 --- a/server/src/wled_controller/static/js/features/targets.js +++ b/server/src/wled_controller/static/js/features/targets.js @@ -569,8 +569,8 @@ export async function loadTargetsTab() { ]; const tabBar = `
${subTabs.map(tab => - `` - ).join('')}
`; + `` + ).join('')}
`; // Use window.createPatternTemplateCard to avoid circular import const createPatternTemplateCard = window.createPatternTemplateCard || (() => ''); diff --git a/server/src/wled_controller/static/locales/en.json b/server/src/wled_controller/static/locales/en.json index 4f14816..0fdca58 100644 --- a/server/src/wled_controller/static/locales/en.json +++ b/server/src/wled_controller/static/locales/en.json @@ -38,6 +38,7 @@ "displays.none": "No displays available", "displays.failed": "Failed to load displays", "displays.picker.title": "Select a Display", + "displays.picker.title.device": "Select a Device", "displays.picker.select": "Select display...", "displays.picker.click_to_select": "Click to select this display", "displays.picker.adb_connect": "Connect ADB device", diff --git a/server/src/wled_controller/static/locales/ru.json b/server/src/wled_controller/static/locales/ru.json index 45347b3..cdd949f 100644 --- a/server/src/wled_controller/static/locales/ru.json +++ b/server/src/wled_controller/static/locales/ru.json @@ -38,6 +38,7 @@ "displays.none": "Нет доступных дисплеев", "displays.failed": "Не удалось загрузить дисплеи", "displays.picker.title": "Выберите Дисплей", + "displays.picker.title.device": "Выберите Устройство", "displays.picker.select": "Выберите дисплей...", "displays.picker.click_to_select": "Нажмите, чтобы выбрать этот дисплей", "displays.picker.adb_connect": "Подключить ADB устройство", diff --git a/server/src/wled_controller/static/locales/zh.json b/server/src/wled_controller/static/locales/zh.json index 620d94a..2e57951 100644 --- a/server/src/wled_controller/static/locales/zh.json +++ b/server/src/wled_controller/static/locales/zh.json @@ -38,6 +38,7 @@ "displays.none": "没有可用的显示器", "displays.failed": "加载显示器失败", "displays.picker.title": "选择显示器", + "displays.picker.title.device": "选择设备", "displays.picker.select": "选择显示器...", "displays.picker.click_to_select": "点击选择此显示器", "displays.picker.adb_connect": "连接 ADB 设备",