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:
@@ -10,7 +10,9 @@ import { Modal } from '../core/modal.js';
|
||||
import { CardSection } from '../core/card-sections.js';
|
||||
import { updateTabBadge } from './tabs.js';
|
||||
import { ICON_SETTINGS, ICON_START, ICON_PAUSE, ICON_CLOCK, ICON_AUTOMATION, ICON_HELP, ICON_OK, ICON_TIMER, ICON_MONITOR, ICON_RADIO, ICON_SCENE, ICON_CLONE } 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 { attachProcessPicker } from '../core/process-picker.js';
|
||||
import { csScenes, createSceneCard } from './scene-presets.js';
|
||||
|
||||
@@ -33,6 +35,23 @@ class AutomationEditorModal extends Modal {
|
||||
const automationModal = new AutomationEditorModal();
|
||||
const csAutomations = new CardSection('automations', { titleKey: 'automations.title', gridClass: 'devices-grid', addCardOnclick: "openAutomationEditor()", keyAttr: 'data-automation-id' });
|
||||
|
||||
/* ── Condition logic IconSelect ───────────────────────────────── */
|
||||
|
||||
const _icon = (d) => `<svg class="icon" viewBox="0 0 24 24">${d}</svg>`;
|
||||
|
||||
let _conditionLogicIconSelect = null;
|
||||
|
||||
function _ensureConditionLogicIconSelect() {
|
||||
const sel = document.getElementById('automation-editor-logic');
|
||||
if (!sel) return;
|
||||
const items = [
|
||||
{ value: 'or', icon: _icon(P.zap), label: t('automations.condition_logic.or'), desc: t('automations.condition_logic.or.desc') },
|
||||
{ value: 'and', icon: _icon(P.link), label: t('automations.condition_logic.and'), desc: t('automations.condition_logic.and.desc') },
|
||||
];
|
||||
if (_conditionLogicIconSelect) { _conditionLogicIconSelect.updateItems(items); return; }
|
||||
_conditionLogicIconSelect = new IconSelect({ target: sel, items, columns: 2 });
|
||||
}
|
||||
|
||||
// Re-render automations when language changes (only if tab is active)
|
||||
document.addEventListener('languageChanged', () => {
|
||||
if (apiKey && (localStorage.getItem('activeTab') || 'dashboard') === 'automations') loadAutomations();
|
||||
@@ -207,6 +226,8 @@ export async function openAutomationEditor(automationId, cloneData) {
|
||||
errorEl.style.display = 'none';
|
||||
condList.innerHTML = '';
|
||||
|
||||
_ensureConditionLogicIconSelect();
|
||||
|
||||
// Fetch scenes for selector
|
||||
try {
|
||||
await scenePresetsCache.fetch();
|
||||
@@ -227,6 +248,7 @@ export async function openAutomationEditor(automationId, cloneData) {
|
||||
nameInput.value = automation.name;
|
||||
enabledInput.checked = automation.enabled;
|
||||
logicSelect.value = automation.condition_logic;
|
||||
if (_conditionLogicIconSelect) _conditionLogicIconSelect.setValue(automation.condition_logic);
|
||||
|
||||
for (const c of automation.conditions) {
|
||||
addAutomationConditionRow(c);
|
||||
@@ -250,6 +272,7 @@ export async function openAutomationEditor(automationId, cloneData) {
|
||||
nameInput.value = (cloneData.name || '') + ' (Copy)';
|
||||
enabledInput.checked = cloneData.enabled !== false;
|
||||
logicSelect.value = cloneData.condition_logic || 'or';
|
||||
if (_conditionLogicIconSelect) _conditionLogicIconSelect.setValue(cloneData.condition_logic || 'or');
|
||||
|
||||
// Clone conditions (strip webhook tokens — they must be unique)
|
||||
for (const c of (cloneData.conditions || [])) {
|
||||
@@ -269,6 +292,7 @@ export async function openAutomationEditor(automationId, cloneData) {
|
||||
nameInput.value = '';
|
||||
enabledInput.checked = true;
|
||||
logicSelect.value = 'or';
|
||||
if (_conditionLogicIconSelect) _conditionLogicIconSelect.setValue('or');
|
||||
_initSceneSelector('automation-scene', null);
|
||||
_initSceneSelector('automation-fallback-scene', null);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user