Unify process picker, improve notification CSS editor, remove notification led_count

- Extract shared process picker module (core/process-picker.js) used by
  both automation conditions and notification CSS app filter
- Remove led_count property from notification CSS source (backend + frontend)
- Replace comma-separated app filter with newline-separated textarea + browse
- Inline color cycle add button (+) into the color row
- Fix notification app color layout to horizontal rows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 22:58:36 +03:00
parent a330a8c0f0
commit d6bda9afed
12 changed files with 179 additions and 91 deletions

View File

@@ -9,8 +9,9 @@ import { showToast, showConfirm, setTabRefreshing } from '../core/ui.js';
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 } from '../core/icons.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 { wrapCard } from '../core/card-colors.js';
import { attachProcessPicker } from '../core/process-picker.js';
import { csScenes, createSceneCard } from './scene-presets.js';
class AutomationEditorModal extends Modal {
@@ -558,11 +559,8 @@ function addAutomationConditionRow(condition) {
</div>
</div>
`;
const browseBtn = container.querySelector('.btn-browse-apps');
const picker = container.querySelector('.process-picker');
browseBtn.addEventListener('click', () => toggleProcessPicker(picker, row));
const searchInput = container.querySelector('.process-picker-search');
searchInput.addEventListener('input', () => filterProcessPicker(picker));
const textarea = container.querySelector('.condition-apps');
attachProcessPicker(container, textarea);
}
renderFields(condType, condition);
@@ -573,65 +571,7 @@ function addAutomationConditionRow(condition) {
list.appendChild(row);
}
async function toggleProcessPicker(picker, row) {
if (picker.style.display !== 'none') {
picker.style.display = 'none';
return;
}
const listEl = picker.querySelector('.process-picker-list');
const searchEl = picker.querySelector('.process-picker-search');
searchEl.value = '';
listEl.innerHTML = `<div class="process-picker-loading">${t('common.loading')}</div>`;
picker.style.display = '';
try {
const resp = await fetchWithAuth('/system/processes');
if (!resp.ok) throw new Error('Failed to fetch processes');
const data = await resp.json();
const textarea = row.querySelector('.condition-apps');
const existing = new Set(textarea.value.split('\n').map(a => a.trim().toLowerCase()).filter(Boolean));
picker._processes = data.processes;
picker._existing = existing;
renderProcessPicker(picker, data.processes, existing);
searchEl.focus();
} catch (e) {
listEl.innerHTML = `<div class="process-picker-loading" style="color:var(--danger-color)">${e.message}</div>`;
}
}
function renderProcessPicker(picker, processes, existing) {
const listEl = picker.querySelector('.process-picker-list');
if (processes.length === 0) {
listEl.innerHTML = `<div class="process-picker-loading">${t('automations.condition.application.no_processes')}</div>`;
return;
}
listEl.innerHTML = processes.map(p => {
const added = existing.has(p.toLowerCase());
return `<div class="process-picker-item${added ? ' added' : ''}" data-process="${escapeHtml(p)}">${escapeHtml(p)}${added ? ' \u2713' : ''}</div>`;
}).join('');
listEl.querySelectorAll('.process-picker-item:not(.added)').forEach(item => {
item.addEventListener('click', () => {
const proc = item.dataset.process;
const row = picker.closest('.automation-condition-row');
const textarea = row.querySelector('.condition-apps');
const current = textarea.value.trim();
textarea.value = current ? current + '\n' + proc : proc;
item.classList.add('added');
item.textContent = proc + ' \u2713';
picker._existing.add(proc.toLowerCase());
});
});
}
function filterProcessPicker(picker) {
const query = picker.querySelector('.process-picker-search').value.toLowerCase();
const filtered = (picker._processes || []).filter(p => p.includes(query));
renderProcessPicker(picker, filtered, picker._existing || new Set());
}
function getAutomationEditorConditions() {
const rows = document.querySelectorAll('#automation-conditions-list .automation-condition-row');