Add command palette entity selector for all editor dropdowns

Replace plain <select> dropdowns with a searchable command palette modal
for 16 entity selectors across 6 editors (targets, streams, CSS sources,
value sources, audio sources, pattern templates). Unified EntityPalette
singleton + EntitySelect wrapper in core/entity-palette.js.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 00:17:44 +03:00
parent b4d89e271d
commit 6fc0e20e1d
12 changed files with 657 additions and 7 deletions

View File

@@ -15,7 +15,8 @@ import { API_BASE, fetchWithAuth, escapeHtml } from '../core/api.js';
import { t } from '../core/i18n.js';
import { showToast, showConfirm, lockBody, unlockBody } from '../core/ui.js';
import { Modal } from '../core/modal.js';
import { ICON_MUSIC } from '../core/icons.js';
import { ICON_MUSIC, getAudioSourceIcon, ICON_AUDIO_TEMPLATE, ICON_AUDIO_INPUT, ICON_AUDIO_LOOPBACK } from '../core/icons.js';
import { EntitySelect } from '../core/entity-palette.js';
import { loadPictureSources } from './streams.js';
class AudioSourceModal extends Modal {
@@ -36,6 +37,11 @@ class AudioSourceModal extends Modal {
const audioSourceModal = new AudioSourceModal();
// ── EntitySelect instances for audio source editor ──
let _asTemplateEntitySelect = null;
let _asDeviceEntitySelect = null;
let _asParentEntitySelect = null;
// ── Modal ─────────────────────────────────────────────────────
export async function showAudioSourceModal(sourceType, editData) {
@@ -242,6 +248,20 @@ function _filterDevicesBySelectedTemplate() {
const match = Array.from(select.options).find(o => o.textContent === prevName);
if (match) select.value = match.value;
}
if (_asDeviceEntitySelect) _asDeviceEntitySelect.destroy();
if (devices.length > 0) {
_asDeviceEntitySelect = new EntitySelect({
target: select,
getItems: () => devices.map(d => ({
value: `${d.index}:${d.is_loopback ? '1' : '0'}`,
label: d.name,
icon: d.is_loopback ? ICON_AUDIO_LOOPBACK : ICON_AUDIO_INPUT,
desc: d.is_loopback ? 'Loopback' : 'Input',
})),
placeholder: t('palette.search'),
});
}
}
function _selectAudioDevice(deviceIndex, isLoopback) {
@@ -259,6 +279,19 @@ function _loadMultichannelSources(selectedId) {
select.innerHTML = multichannel.map(s =>
`<option value="${s.id}"${s.id === selectedId ? ' selected' : ''}>${escapeHtml(s.name)}</option>`
).join('');
if (_asParentEntitySelect) _asParentEntitySelect.destroy();
if (multichannel.length > 0) {
_asParentEntitySelect = new EntitySelect({
target: select,
getItems: () => multichannel.map(s => ({
value: s.id,
label: s.name,
icon: getAudioSourceIcon('multichannel'),
})),
placeholder: t('palette.search'),
});
}
}
function _loadAudioTemplates(selectedId) {
@@ -268,6 +301,20 @@ function _loadAudioTemplates(selectedId) {
select.innerHTML = templates.map(t =>
`<option value="${t.id}"${t.id === selectedId ? ' selected' : ''}>${escapeHtml(t.name)} (${t.engine_type.toUpperCase()})</option>`
).join('');
if (_asTemplateEntitySelect) _asTemplateEntitySelect.destroy();
if (templates.length > 0) {
_asTemplateEntitySelect = new EntitySelect({
target: select,
getItems: () => templates.map(tmpl => ({
value: tmpl.id,
label: tmpl.name,
icon: ICON_AUDIO_TEMPLATE,
desc: tmpl.engine_type.toUpperCase(),
})),
placeholder: t('palette.search'),
});
}
}
// ── Audio Source Test (real-time spectrum) ────────────────────