Add visual selectors to automation and KC target editors

Automation editor:
- IconSelect grid for condition logic (OR/AND) with descriptions

KC target editor:
- IconSelect for color mode (average/median/dominant) with SVG previews
- EntitySelect palette for picture source, pattern template, brightness source

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 10:12:57 +03:00
parent 8061c26bef
commit 5b4813368b
6 changed files with 140 additions and 2 deletions

View File

@@ -19,7 +19,10 @@ import {
ICON_CLONE, ICON_EDIT, ICON_TEST, ICON_START, ICON_STOP, ICON_PAUSE,
ICON_LINK_SOURCE, ICON_PATTERN_TEMPLATE, ICON_FPS, ICON_PALETTE,
} from '../core/icons.js';
import * as P from '../core/icon-paths.js';
import { wrapCard } from '../core/card-colors.js';
import { IconSelect } from '../core/icon-select.js';
import { EntitySelect } from '../core/entity-palette.js';
class KCEditorModal extends Modal {
constructor() {
@@ -41,6 +44,94 @@ class KCEditorModal extends Modal {
const kcEditorModal = new KCEditorModal();
/* ── Visual selectors ─────────────────────────────────────────── */
const _icon = (d) => `<svg class="icon" viewBox="0 0 24 24">${d}</svg>`;
let _kcColorModeIconSelect = null;
let _kcSourceEntitySelect = null;
let _kcPatternEntitySelect = null;
let _kcBrightnessEntitySelect = null;
// Inline SVG previews for color modes
const _COLOR_MODE_SVG = {
average: '<svg viewBox="0 0 60 24" width="60" height="24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="52" height="16" rx="3" opacity="0.3" fill="currentColor"/><path d="M30 8v8" stroke-width="1.5"/><path d="M20 10v4" stroke-width="1.5"/><path d="M40 10v4" stroke-width="1.5"/></svg>',
median: '<svg viewBox="0 0 60 24" width="60" height="24" fill="none" stroke="currentColor" stroke-width="2"><rect x="6" y="14" width="8" height="6" rx="1" fill="currentColor" opacity="0.3"/><rect x="18" y="8" width="8" height="12" rx="1" fill="currentColor" opacity="0.5"/><rect x="30" y="4" width="8" height="16" rx="1" fill="currentColor" opacity="0.7"/><rect x="42" y="10" width="8" height="10" rx="1" fill="currentColor" opacity="0.4"/></svg>',
dominant: '<svg viewBox="0 0 60 24" width="60" height="24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="20" cy="12" r="4" opacity="0.2" fill="currentColor"/><circle cx="30" cy="12" r="8" fill="currentColor" opacity="0.6"/><circle cx="42" cy="12" r="3" opacity="0.15" fill="currentColor"/></svg>',
};
function _ensureColorModeIconSelect() {
const sel = document.getElementById('kc-editor-interpolation');
if (!sel) return;
const items = [
{ value: 'average', icon: _COLOR_MODE_SVG.average, label: t('kc.interpolation.average'), desc: t('kc.interpolation.average.desc') },
{ value: 'median', icon: _COLOR_MODE_SVG.median, label: t('kc.interpolation.median'), desc: t('kc.interpolation.median.desc') },
{ value: 'dominant', icon: _COLOR_MODE_SVG.dominant, label: t('kc.interpolation.dominant'), desc: t('kc.interpolation.dominant.desc') },
];
if (_kcColorModeIconSelect) { _kcColorModeIconSelect.updateItems(items); return; }
_kcColorModeIconSelect = new IconSelect({ target: sel, items, columns: 3 });
}
function _ensureSourceEntitySelect(sources) {
const sel = document.getElementById('kc-editor-source');
if (!sel) return;
if (_kcSourceEntitySelect) _kcSourceEntitySelect.destroy();
if (sources.length > 0) {
_kcSourceEntitySelect = new EntitySelect({
target: sel,
getItems: () => sources.map(s => ({
value: s.id,
label: s.name,
icon: getPictureSourceIcon(s.stream_type),
desc: s.stream_type,
})),
placeholder: t('palette.search'),
});
}
}
function _ensurePatternEntitySelect(patTemplates) {
const sel = document.getElementById('kc-editor-pattern-template');
if (!sel) return;
if (_kcPatternEntitySelect) _kcPatternEntitySelect.destroy();
if (patTemplates.length > 0) {
_kcPatternEntitySelect = new EntitySelect({
target: sel,
getItems: () => patTemplates.map(pt => {
const rectCount = (pt.rectangles || []).length;
return {
value: pt.id,
label: pt.name,
icon: _icon(P.fileText),
desc: `${rectCount} rect${rectCount !== 1 ? 's' : ''}`,
};
}),
placeholder: t('palette.search'),
});
}
}
function _ensureBrightnessEntitySelect() {
const sel = document.getElementById('kc-editor-brightness-vs');
if (!sel) return;
if (_kcBrightnessEntitySelect) _kcBrightnessEntitySelect.destroy();
if (_cachedValueSources.length > 0) {
_kcBrightnessEntitySelect = new EntitySelect({
target: sel,
getItems: () => {
const items = [{ value: '', label: t('kc.brightness_vs.none'), icon: _icon(P.sunDim), desc: '' }];
return items.concat(_cachedValueSources.map(vs => ({
value: vs.id,
label: vs.name,
icon: getValueSourceIcon(vs.source_type),
desc: vs.source_type,
})));
},
placeholder: t('palette.search'),
});
}
}
export function patchKCTargetMetrics(target) {
const card = document.querySelector(`[data-kc-target-id="${target.id}"]`);
if (!card) return;
@@ -400,13 +491,13 @@ function _populateKCBrightnessVsDropdown(selectedId = '') {
// Keep the first "None" option, remove the rest
while (sel.options.length > 1) sel.remove(1);
_cachedValueSources.forEach(vs => {
const icon = getValueSourceIcon(vs.source_type);
const opt = document.createElement('option');
opt.value = vs.id;
opt.textContent = vs.name;
sel.appendChild(opt);
});
sel.value = selectedId || '';
_ensureBrightnessEntitySelect();
}
export async function showKCEditor(targetId = null, cloneData = null) {
@@ -442,6 +533,11 @@ export async function showKCEditor(targetId = null, cloneData = null) {
patSelect.appendChild(opt);
});
// Set up visual selectors
_ensureColorModeIconSelect();
_ensureSourceEntitySelect(sources);
_ensurePatternEntitySelect(patTemplates);
if (targetId) {
const resp = await fetch(`${API_BASE}/picture-targets/${targetId}`, { headers: getHeaders() });
if (!resp.ok) throw new Error('Failed to load target');
@@ -454,6 +550,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
document.getElementById('kc-editor-fps').value = kcSettings.fps ?? 10;
document.getElementById('kc-editor-fps-value').textContent = kcSettings.fps ?? 10;
document.getElementById('kc-editor-interpolation').value = kcSettings.interpolation_mode ?? 'average';
if (_kcColorModeIconSelect) _kcColorModeIconSelect.setValue(kcSettings.interpolation_mode ?? 'average');
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 || '';
@@ -467,6 +564,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
document.getElementById('kc-editor-fps').value = kcSettings.fps ?? 10;
document.getElementById('kc-editor-fps-value').textContent = kcSettings.fps ?? 10;
document.getElementById('kc-editor-interpolation').value = kcSettings.interpolation_mode ?? 'average';
if (_kcColorModeIconSelect) _kcColorModeIconSelect.setValue(kcSettings.interpolation_mode ?? 'average');
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 || '';
@@ -479,6 +577,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
document.getElementById('kc-editor-fps').value = 10;
document.getElementById('kc-editor-fps-value').textContent = '10';
document.getElementById('kc-editor-interpolation').value = 'average';
if (_kcColorModeIconSelect) _kcColorModeIconSelect.setValue('average');
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;