Add CSPT entity, processed CSS source type, reverse filter, and UI improvements
- Add Color Strip Processing Template (CSPT) entity: reusable filter chains for 1D LED strip postprocessing (backend, storage, API, frontend CRUD) - Add "processed" color strip source type that wraps another CSS source and applies a CSPT filter chain (dataclass, stream, schema, modal, cards) - Add Reverse filter for strip LED order reversal - Add CSPT and processed CSS nodes/edges to visual graph editor - Add CSPT test preview WS endpoint with input source selection - Add device settings CSPT template selector (add + edit modals with hints) - Use icon grids for palette quantization preset selector in filter lists - Use EntitySelect for template references and test modal source selectors - Fix filters.css_filter_template.desc missing localization - Fix icon grid cell height inequality (grid-auto-rows: 1fr) - Rename "Processed" subtab to "Processing Templates" - Localize all new strings (en/ru/zh) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
import {
|
||||
_discoveryScanRunning, set_discoveryScanRunning,
|
||||
_discoveryCache, set_discoveryCache,
|
||||
csptCache,
|
||||
} from '../core/state.js';
|
||||
import { API_BASE, fetchWithAuth, isSerialDevice, isMockDevice, isMqttDevice, isWsDevice, isOpenrgbDevice, isDmxDevice, isEspnowDevice, isHueDevice, isUsbhidDevice, isSpiDevice, isChromaDevice, isGameSenseDevice, escapeHtml } from '../core/api.js';
|
||||
import { devicesCache } from '../core/state.js';
|
||||
@@ -12,7 +13,8 @@ import { t } from '../core/i18n.js';
|
||||
import { showToast, desktopFocus } from '../core/ui.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
import { _computeMaxFps, _renderFpsHint } from './devices.js';
|
||||
import { getDeviceTypeIcon, ICON_RADIO, ICON_GLOBE, ICON_CPU, ICON_KEYBOARD, ICON_MOUSE, ICON_HEADPHONES, ICON_PLUG, ICON_TARGET_ICON, ICON_ACTIVITY } from '../core/icons.js';
|
||||
import { getDeviceTypeIcon, ICON_RADIO, ICON_GLOBE, ICON_CPU, ICON_KEYBOARD, ICON_MOUSE, ICON_HEADPHONES, ICON_PLUG, ICON_TARGET_ICON, ICON_ACTIVITY, ICON_TEMPLATE } from '../core/icons.js';
|
||||
import { EntitySelect } from '../core/entity-palette.js';
|
||||
import { IconSelect, showTypePicker } from '../core/icon-select.js';
|
||||
|
||||
class AddDeviceModal extends Modal {
|
||||
@@ -30,6 +32,7 @@ class AddDeviceModal extends Modal {
|
||||
sendLatency: document.getElementById('device-send-latency')?.value || '0',
|
||||
zones: JSON.stringify(_getCheckedZones('device-zone-list')),
|
||||
zoneMode: _getZoneMode(),
|
||||
csptId: document.getElementById('device-css-processing-template')?.value || '',
|
||||
dmxProtocol: document.getElementById('device-dmx-protocol')?.value || 'artnet',
|
||||
dmxStartUniverse: document.getElementById('device-dmx-start-universe')?.value || '0',
|
||||
dmxStartChannel: document.getElementById('device-dmx-start-channel')?.value || '1',
|
||||
@@ -53,6 +56,7 @@ function _buildDeviceTypeItems() {
|
||||
}
|
||||
|
||||
let _deviceTypeIconSelect = null;
|
||||
let _csptEntitySelect = null;
|
||||
|
||||
function _ensureDeviceTypeIconSelect() {
|
||||
const sel = document.getElementById('device-type');
|
||||
@@ -61,6 +65,30 @@ function _ensureDeviceTypeIconSelect() {
|
||||
_deviceTypeIconSelect = new IconSelect({ target: sel, items: _buildDeviceTypeItems(), columns: 3 });
|
||||
}
|
||||
|
||||
function _ensureCsptEntitySelect() {
|
||||
const sel = document.getElementById('device-css-processing-template');
|
||||
if (!sel) return;
|
||||
const templates = csptCache.data || [];
|
||||
// Populate native <select> options
|
||||
sel.innerHTML = `<option value="">—</option>` +
|
||||
templates.map(tp => `<option value="${tp.id}">${tp.name}</option>`).join('');
|
||||
if (_csptEntitySelect) _csptEntitySelect.destroy();
|
||||
if (templates.length > 0) {
|
||||
_csptEntitySelect = new EntitySelect({
|
||||
target: sel,
|
||||
getItems: () => (csptCache.data || []).map(tp => ({
|
||||
value: tp.id,
|
||||
label: tp.name,
|
||||
icon: ICON_TEMPLATE,
|
||||
desc: '',
|
||||
})),
|
||||
placeholder: t('palette.search'),
|
||||
allowNone: true,
|
||||
noneLabel: '—',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Icon-grid DMX protocol selector ─────────────────────────── */
|
||||
|
||||
function _buildDmxProtocolItems() {
|
||||
@@ -583,6 +611,8 @@ export function showAddDevice(presetType = null) {
|
||||
const scanBtn = document.getElementById('scan-network-btn');
|
||||
if (scanBtn) scanBtn.disabled = false;
|
||||
_ensureDeviceTypeIconSelect();
|
||||
// Populate CSPT template selector
|
||||
csptCache.fetch().then(() => _ensureCsptEntitySelect());
|
||||
|
||||
// Pre-select type and hide the type selector (already chosen)
|
||||
document.getElementById('device-type').value = presetType;
|
||||
@@ -775,6 +805,8 @@ export async function handleAddDevice(event) {
|
||||
if (isGameSenseDevice(deviceType)) {
|
||||
body.gamesense_device_type = document.getElementById('device-gamesense-device-type')?.value || 'keyboard';
|
||||
}
|
||||
const csptId = document.getElementById('device-css-processing-template')?.value;
|
||||
if (csptId) body.default_css_processing_template_id = csptId;
|
||||
if (lastTemplateId) body.capture_template_id = lastTemplateId;
|
||||
|
||||
const response = await fetchWithAuth('/devices', {
|
||||
|
||||
Reference in New Issue
Block a user