diff --git a/media_server/static/js/links.js b/media_server/static/js/links.js index f1a0f78..d6cee1a 100644 --- a/media_server/static/js/links.js +++ b/media_server/static/js/links.js @@ -182,8 +182,7 @@ export async function loadDisplayMonitors() { ${t('display.contrast')} + data-display-slider="contrast" data-monitor-id="${monitor.id}"> ${contrastValue}% `; } @@ -296,8 +295,7 @@ export async function loadDisplayMonitors() { ${t('display.brightness')} + data-display-slider="brightness" data-monitor-id="${monitor.id}"> ${brightnessValue}% ${contrastRow} @@ -306,10 +304,15 @@ export async function loadDisplayMonitors() { container.appendChild(card); }); - // Bind a single delegated click handler for the power buttons. - // Avoids inline onclick="..." with interpolated monitor data. + // Bind a single delegated click handler for the power buttons, + // plus input/change handlers for the brightness & contrast sliders. + // Avoids inline on* attributes (blocked by script-src 'self' CSP). container.removeEventListener('click', _onPowerButtonClick); container.addEventListener('click', _onPowerButtonClick); + container.removeEventListener('input', _onDisplaySliderInput); + container.addEventListener('input', _onDisplaySliderInput); + container.removeEventListener('change', _onDisplaySliderChange); + container.addEventListener('change', _onDisplaySliderChange); // Enhance every tuning with an IconSelect now that the // cards are in the DOM (IconSelect needs offsetParent + sibling). @@ -456,6 +459,30 @@ function _onPowerButtonClick(event) { if (Number.isFinite(id)) toggleDisplayPower(id); } +function _onDisplaySliderInput(event) { + const el = event.target.closest('input[data-display-slider]'); + if (!el) return; + const id = Number(el.dataset.monitorId); + if (!Number.isFinite(id)) return; + if (el.dataset.displaySlider === 'brightness') { + onDisplayBrightnessInput(id, el.value); + } else if (el.dataset.displaySlider === 'contrast') { + onDisplayContrastInput(id, el.value); + } +} + +function _onDisplaySliderChange(event) { + const el = event.target.closest('input[data-display-slider]'); + if (!el) return; + const id = Number(el.dataset.monitorId); + if (!Number.isFinite(id)) return; + if (el.dataset.displaySlider === 'brightness') { + onDisplayBrightnessChange(id, el.value); + } else if (el.dataset.displaySlider === 'contrast') { + onDisplayContrastChange(id, el.value); + } +} + export async function toggleDisplayPower(monitorId) { const btn = document.getElementById(`power-btn-${monitorId}`); const isOn = btn && btn.classList.contains('on'); diff --git a/media_server/static/js/player.js b/media_server/static/js/player.js index 6a8f392..fb8672a 100644 --- a/media_server/static/js/player.js +++ b/media_server/static/js/player.js @@ -207,20 +207,39 @@ export function renderAccentSwatches() { const swatches = accentPresets.map(p => `` ).join(''); const customRow = ` - + ${t('accent.custom')} - + `; dropdown.innerHTML = swatches + customRow; + + // Wire CSP-safe handlers (script-src 'self' blocks inline on* attributes). + dropdown.querySelectorAll('.accent-swatch[data-accent-color]').forEach(el => { + el.addEventListener('click', () => { + selectAccentColor(el.dataset.accentColor, el.dataset.accentHover); + }); + }); + const customRowEl = dropdown.querySelector('[data-accent-custom-row]'); + const customInput = dropdown.querySelector('#accentCustomInput'); + if (customRowEl && customInput) { + customRowEl.addEventListener('click', (e) => { + // The native color popup only opens from a user-initiated click on + // the . Forward clicks on the row to the input — except when + // the input itself was the source (avoids re-entry). + if (e.target !== customInput) customInput.click(); + }); + customInput.addEventListener('click', (e) => e.stopPropagation()); + customInput.addEventListener('change', () => { + selectAccentColor(customInput.value, lightenColor(customInput.value, 15)); + }); + } } export function selectAccentColor(color, hover) {