Add audio sources as first-class entities, add mapped CSS type, simplify target editor for mapped sources
- Audio sources moved to separate tab with dedicated CRUD API, store, and editor modal - New "mapped" color strip source type: assigns different CSS sources to distinct LED sub-ranges (zones) - Mapped stream runtime with per-zone sub-streams, auto-sizing, hot-update support - Target editor auto-collapses segments UI when mapped CSS is selected - Delete protection for CSS sources referenced by mapped zones - Compact header/footer layout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,56 @@ function _createTargetFpsChart(canvasId, history, fpsTarget, maxHwFps) {
|
||||
// --- Segment editor state ---
|
||||
let _editorCssSources = []; // populated when editor opens
|
||||
|
||||
/**
|
||||
* When the selected CSS source is a mapped type, collapse the segment UI
|
||||
* to a single source dropdown — range fields, reverse, header, and "Add Segment"
|
||||
* are hidden because the mapped CSS already defines spatial zones internally.
|
||||
*/
|
||||
export function syncSegmentsMappedMode() {
|
||||
const list = document.getElementById('target-editor-segment-list');
|
||||
if (!list) return;
|
||||
const rows = list.querySelectorAll('.segment-row');
|
||||
if (rows.length === 0) return;
|
||||
|
||||
const firstSelect = rows[0].querySelector('.segment-css-select');
|
||||
const selectedId = firstSelect ? firstSelect.value : '';
|
||||
const selectedSource = _editorCssSources.find(s => s.id === selectedId);
|
||||
const isMapped = selectedSource && selectedSource.source_type === 'mapped';
|
||||
|
||||
// Remove extra segments when switching to mapped
|
||||
if (isMapped && rows.length > 1) {
|
||||
for (let i = rows.length - 1; i >= 1; i--) rows[i].remove();
|
||||
}
|
||||
|
||||
// Toggle visibility of range/reverse/header within the first row
|
||||
const firstRow = list.querySelector('.segment-row');
|
||||
if (firstRow) {
|
||||
const header = firstRow.querySelector('.segment-row-header');
|
||||
const rangeFields = firstRow.querySelector('.segment-range-fields');
|
||||
const reverseLabel = firstRow.querySelector('.segment-reverse-label');
|
||||
if (header) header.style.display = isMapped ? 'none' : '';
|
||||
if (rangeFields) rangeFields.style.display = isMapped ? 'none' : '';
|
||||
if (reverseLabel) reverseLabel.style.display = isMapped ? 'none' : '';
|
||||
}
|
||||
|
||||
// Hide/show "Add Segment" button
|
||||
const addBtn = document.querySelector('#target-editor-segments-group > .btn-sm');
|
||||
if (addBtn) addBtn.style.display = isMapped ? 'none' : '';
|
||||
|
||||
// Swap label: "Segments:" ↔ "Color Strip Source:"
|
||||
const group = document.getElementById('target-editor-segments-group');
|
||||
if (group) {
|
||||
const label = group.querySelector('.label-row label');
|
||||
const hintToggle = group.querySelector('.hint-toggle');
|
||||
const hint = group.querySelector('.input-hint');
|
||||
if (label) label.textContent = isMapped
|
||||
? t('targets.color_strip_source')
|
||||
: t('targets.segments');
|
||||
if (hintToggle) hintToggle.style.display = isMapped ? 'none' : '';
|
||||
if (hint) hint.style.display = 'none'; // collapse hint on switch
|
||||
}
|
||||
}
|
||||
|
||||
function _serializeSegments() {
|
||||
const rows = document.querySelectorAll('.segment-row');
|
||||
const segments = [];
|
||||
@@ -195,7 +245,7 @@ function _renderSegmentRowInner(index, segment) {
|
||||
<button type="button" class="btn-icon-inline btn-danger-text" onclick="removeTargetSegment(this)" title="${t('targets.segment.remove')}">×</button>
|
||||
</div>
|
||||
<div class="segment-row-fields">
|
||||
<select class="segment-css-select" onchange="window._targetAutoName && window._targetAutoName()">${options}</select>
|
||||
<select class="segment-css-select" onchange="window._targetAutoName && window._targetAutoName(); syncSegmentsMappedMode()">${options}</select>
|
||||
<div class="segment-range-fields">
|
||||
<label>${t('targets.segment.start')}</label>
|
||||
<input type="number" class="segment-start" min="0" value="${start}" placeholder="0">
|
||||
@@ -293,6 +343,8 @@ export async function showTargetEditor(targetId = null, cloneData = null) {
|
||||
addTargetSegment();
|
||||
}
|
||||
|
||||
syncSegmentsMappedMode();
|
||||
|
||||
// Auto-name generation
|
||||
_targetNameManuallyEdited = !!(targetId || cloneData);
|
||||
document.getElementById('target-editor-name').oninput = () => { _targetNameManuallyEdited = true; };
|
||||
|
||||
Reference in New Issue
Block a user