Add dynamic brightness value source support for KC targets, fix subtab selector collision

Extend value source brightness modulation to Key Colors targets (matching LED target support).
Also fix stream subtab CSS selector collision that broke target subtab selection, and use 🔢 emoji
for value source UI elements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 12:42:00 +03:00
parent ef474fe275
commit 8f79b77fe4
10 changed files with 131 additions and 13 deletions

View File

@@ -8,6 +8,7 @@ import {
_kcNameManuallyEdited, set_kcNameManuallyEdited,
kcWebSockets,
PATTERN_RECT_BORDERS,
_cachedValueSources, set_cachedValueSources,
} from '../core/state.js';
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js';
import { t } from '../core/i18n.js';
@@ -27,6 +28,7 @@ class KCEditorModal extends Modal {
interpolation: document.getElementById('kc-editor-interpolation').value,
smoothing: document.getElementById('kc-editor-smoothing').value,
patternTemplateId: document.getElementById('kc-editor-pattern-template').value,
brightness_vs: document.getElementById('kc-editor-brightness-vs').value,
};
}
}
@@ -348,15 +350,33 @@ function _autoGenerateKCName() {
document.getElementById('kc-editor-name').value = `${sourceName} \u00b7 ${patName} (${modeName})`;
}
function _populateKCBrightnessVsDropdown(selectedId = '') {
const sel = document.getElementById('kc-editor-brightness-vs');
// Keep the first "None" option, remove the rest
while (sel.options.length > 1) sel.remove(1);
_cachedValueSources.forEach(vs => {
const typeIcons = { static: '📊', animated: '🔄', audio: '🎵' };
const icon = typeIcons[vs.source_type] || '🔢';
const opt = document.createElement('option');
opt.value = vs.id;
opt.textContent = `${icon} ${vs.name}`;
sel.appendChild(opt);
});
sel.value = selectedId || '';
}
export async function showKCEditor(targetId = null, cloneData = null) {
try {
// Load sources and pattern templates in parallel
const [sourcesResp, patResp] = await Promise.all([
// Load sources, pattern templates, and value sources in parallel
const [sourcesResp, patResp, vsResp] = await Promise.all([
fetchWithAuth('/picture-sources').catch(() => null),
fetchWithAuth('/pattern-templates').catch(() => null),
fetchWithAuth('/value-sources').catch(() => null),
]);
const sources = (sourcesResp && sourcesResp.ok) ? (await sourcesResp.json()).streams || [] : [];
const patTemplates = (patResp && patResp.ok) ? (await patResp.json()).templates || [] : [];
const valueSources = (vsResp && vsResp.ok) ? (await vsResp.json()).sources || [] : [];
set_cachedValueSources(valueSources);
// Populate source select
const sourceSelect = document.getElementById('kc-editor-source');
@@ -397,6 +417,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
document.getElementById('kc-editor-smoothing').value = kcSettings.smoothing ?? 0.3;
document.getElementById('kc-editor-smoothing-value').textContent = kcSettings.smoothing ?? 0.3;
patSelect.value = kcSettings.pattern_template_id || '';
_populateKCBrightnessVsDropdown(kcSettings.brightness_value_source_id || '');
document.getElementById('kc-editor-title').textContent = t('kc.edit');
} else if (cloneData) {
const kcSettings = cloneData.key_colors_settings || {};
@@ -409,6 +430,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
document.getElementById('kc-editor-smoothing').value = kcSettings.smoothing ?? 0.3;
document.getElementById('kc-editor-smoothing-value').textContent = kcSettings.smoothing ?? 0.3;
patSelect.value = kcSettings.pattern_template_id || '';
_populateKCBrightnessVsDropdown(kcSettings.brightness_value_source_id || '');
document.getElementById('kc-editor-title').textContent = t('kc.add');
} else {
document.getElementById('kc-editor-id').value = '';
@@ -420,6 +442,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
document.getElementById('kc-editor-smoothing').value = 0.3;
document.getElementById('kc-editor-smoothing-value').textContent = '0.3';
if (patTemplates.length > 0) patSelect.value = patTemplates[0].id;
_populateKCBrightnessVsDropdown('');
document.getElementById('kc-editor-title').textContent = t('kc.add');
}
@@ -464,6 +487,7 @@ export async function saveKCEditor() {
const interpolation = document.getElementById('kc-editor-interpolation').value;
const smoothing = parseFloat(document.getElementById('kc-editor-smoothing').value);
const patternTemplateId = document.getElementById('kc-editor-pattern-template').value;
const brightnessVsId = document.getElementById('kc-editor-brightness-vs').value;
if (!name) {
kcEditorModal.showError(t('kc.error.required'));
@@ -483,6 +507,7 @@ export async function saveKCEditor() {
interpolation_mode: interpolation,
smoothing,
pattern_template_id: patternTemplateId,
brightness_value_source_id: brightnessVsId,
},
};

View File

@@ -492,10 +492,10 @@ export async function loadPictureSources() {
}
export function switchStreamTab(tabKey) {
document.querySelectorAll('.stream-tab-btn').forEach(btn =>
document.querySelectorAll('.stream-tab-btn[data-stream-tab]').forEach(btn =>
btn.classList.toggle('active', btn.dataset.streamTab === tabKey)
);
document.querySelectorAll('.stream-tab-panel').forEach(panel =>
document.querySelectorAll('.stream-tab-panel[id^="stream-tab-"]').forEach(panel =>
panel.classList.toggle('active', panel.id === `stream-tab-${tabKey}`)
);
localStorage.setItem('activeStreamTab', tabKey);
@@ -629,7 +629,7 @@ function renderPictureSourcesList(streams) {
{ key: 'static_image', icon: '🖼️', titleKey: 'streams.group.static_image', count: staticImageStreams.length },
{ key: 'processed', icon: '🎨', titleKey: 'streams.group.processed', count: processedStreams.length },
{ key: 'audio', icon: '🔊', titleKey: 'streams.group.audio', count: _cachedAudioSources.length },
{ key: 'value', icon: '🎚️', titleKey: 'streams.group.value', count: _cachedValueSources.length },
{ key: 'value', icon: '🔢', titleKey: 'streams.group.value', count: _cachedValueSources.length },
];
const tabBar = `<div class="stream-tab-bar">${tabs.map(tab =>