Split adaptive value source into explicit adaptive_time and adaptive_scene types
Replace single "adaptive" type with adaptive_mode sub-selector by two distinct source types in the dropdown. Removes the adaptive_mode field entirely — the source_type itself carries the mode. Clearer UX with "Adaptive (Time of Day)" and "Adaptive (Scene)" as separate options. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -112,7 +112,7 @@ import {
|
||||
import {
|
||||
showValueSourceModal, closeValueSourceModal, saveValueSource,
|
||||
editValueSource, deleteValueSource, onValueSourceTypeChange,
|
||||
onAdaptiveModeChange, addSchedulePoint,
|
||||
addSchedulePoint,
|
||||
} from './features/value-sources.js';
|
||||
|
||||
// Layer 5: calibration
|
||||
@@ -331,7 +331,6 @@ Object.assign(window, {
|
||||
editValueSource,
|
||||
deleteValueSource,
|
||||
onValueSourceTypeChange,
|
||||
onAdaptiveModeChange,
|
||||
addSchedulePoint,
|
||||
|
||||
// calibration
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/**
|
||||
* Value Sources — CRUD for scalar value sources (static, animated, audio, adaptive).
|
||||
* Value Sources — CRUD for scalar value sources (static, animated, audio, adaptive_time, adaptive_scene).
|
||||
*
|
||||
* Value sources produce a float 0.0-1.0 used for dynamic brightness control
|
||||
* on LED targets. Four subtypes: static (constant), animated (waveform),
|
||||
* audio (audio-reactive), adaptive (time-of-day schedule or scene brightness).
|
||||
* on LED targets. Five subtypes: static (constant), animated (waveform),
|
||||
* audio (audio-reactive), adaptive_time (time-of-day schedule),
|
||||
* adaptive_scene (scene brightness analysis).
|
||||
*
|
||||
* Card rendering is handled by streams.js (Value tab).
|
||||
* This module manages the editor modal and API operations.
|
||||
@@ -49,10 +50,11 @@ export async function showValueSourceModal(editData) {
|
||||
document.getElementById('value-source-mode').value = editData.mode || 'rms';
|
||||
_setSlider('value-source-sensitivity', editData.sensitivity ?? 1.0);
|
||||
_setSlider('value-source-smoothing', editData.smoothing ?? 0.3);
|
||||
} else if (editData.source_type === 'adaptive') {
|
||||
document.getElementById('value-source-adaptive-mode').value = editData.adaptive_mode || 'time_of_day';
|
||||
onAdaptiveModeChange();
|
||||
} else if (editData.source_type === 'adaptive_time') {
|
||||
_populateScheduleUI(editData.schedule);
|
||||
_setSlider('value-source-adaptive-min-value', editData.min_value ?? 0);
|
||||
_setSlider('value-source-adaptive-max-value', editData.max_value ?? 1);
|
||||
} else if (editData.source_type === 'adaptive_scene') {
|
||||
_populatePictureSourceDropdown(editData.picture_source_id || '');
|
||||
document.getElementById('value-source-scene-behavior').value = editData.scene_behavior || 'complement';
|
||||
_setSlider('value-source-scene-sensitivity', editData.sensitivity ?? 1.0);
|
||||
@@ -75,7 +77,6 @@ export async function showValueSourceModal(editData) {
|
||||
_setSlider('value-source-sensitivity', 1.0);
|
||||
_setSlider('value-source-smoothing', 0.3);
|
||||
// Adaptive defaults
|
||||
document.getElementById('value-source-adaptive-mode').value = 'time_of_day';
|
||||
_populateScheduleUI([]);
|
||||
_populatePictureSourceDropdown('');
|
||||
document.getElementById('value-source-scene-behavior').value = 'complement';
|
||||
@@ -97,7 +98,10 @@ export function onValueSourceTypeChange() {
|
||||
document.getElementById('value-source-static-section').style.display = type === 'static' ? '' : 'none';
|
||||
document.getElementById('value-source-animated-section').style.display = type === 'animated' ? '' : 'none';
|
||||
document.getElementById('value-source-audio-section').style.display = type === 'audio' ? '' : 'none';
|
||||
document.getElementById('value-source-adaptive-section').style.display = type === 'adaptive' ? '' : 'none';
|
||||
document.getElementById('value-source-adaptive-time-section').style.display = type === 'adaptive_time' ? '' : 'none';
|
||||
document.getElementById('value-source-adaptive-scene-section').style.display = type === 'adaptive_scene' ? '' : 'none';
|
||||
document.getElementById('value-source-adaptive-range-section').style.display =
|
||||
(type === 'adaptive_time' || type === 'adaptive_scene') ? '' : 'none';
|
||||
|
||||
// Populate audio dropdown when switching to audio type
|
||||
if (type === 'audio') {
|
||||
@@ -107,19 +111,12 @@ export function onValueSourceTypeChange() {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize adaptive sub-sections
|
||||
if (type === 'adaptive') {
|
||||
onAdaptiveModeChange();
|
||||
// Populate picture source dropdown when switching to scene type
|
||||
if (type === 'adaptive_scene') {
|
||||
_populatePictureSourceDropdown('');
|
||||
}
|
||||
}
|
||||
|
||||
export function onAdaptiveModeChange() {
|
||||
const mode = document.getElementById('value-source-adaptive-mode').value;
|
||||
document.getElementById('value-source-tod-section').style.display = mode === 'time_of_day' ? '' : 'none';
|
||||
document.getElementById('value-source-scene-section').style.display = mode === 'scene' ? '' : 'none';
|
||||
}
|
||||
|
||||
// ── Save ──────────────────────────────────────────────────────
|
||||
|
||||
export async function saveValueSource() {
|
||||
@@ -149,23 +146,22 @@ export async function saveValueSource() {
|
||||
payload.mode = document.getElementById('value-source-mode').value;
|
||||
payload.sensitivity = parseFloat(document.getElementById('value-source-sensitivity').value);
|
||||
payload.smoothing = parseFloat(document.getElementById('value-source-smoothing').value);
|
||||
} else if (sourceType === 'adaptive') {
|
||||
payload.adaptive_mode = document.getElementById('value-source-adaptive-mode').value;
|
||||
} else if (sourceType === 'adaptive_time') {
|
||||
payload.schedule = _getScheduleFromUI();
|
||||
if (payload.schedule.length < 2) {
|
||||
errorEl.textContent = t('value_source.error.schedule_min');
|
||||
errorEl.style.display = '';
|
||||
return;
|
||||
}
|
||||
payload.min_value = parseFloat(document.getElementById('value-source-adaptive-min-value').value);
|
||||
payload.max_value = parseFloat(document.getElementById('value-source-adaptive-max-value').value);
|
||||
} else if (sourceType === 'adaptive_scene') {
|
||||
payload.picture_source_id = document.getElementById('value-source-picture-source').value;
|
||||
payload.scene_behavior = document.getElementById('value-source-scene-behavior').value;
|
||||
payload.sensitivity = parseFloat(document.getElementById('value-source-scene-sensitivity').value);
|
||||
payload.smoothing = parseFloat(document.getElementById('value-source-scene-smoothing').value);
|
||||
payload.min_value = parseFloat(document.getElementById('value-source-adaptive-min-value').value);
|
||||
payload.max_value = parseFloat(document.getElementById('value-source-adaptive-max-value').value);
|
||||
if (payload.adaptive_mode === 'time_of_day') {
|
||||
payload.schedule = _getScheduleFromUI();
|
||||
if (payload.schedule.length < 2) {
|
||||
errorEl.textContent = t('value_source.error.schedule_min');
|
||||
errorEl.style.display = '';
|
||||
return;
|
||||
}
|
||||
} else if (payload.adaptive_mode === 'scene') {
|
||||
payload.picture_source_id = document.getElementById('value-source-picture-source').value;
|
||||
payload.scene_behavior = document.getElementById('value-source-scene-behavior').value;
|
||||
payload.sensitivity = parseFloat(document.getElementById('value-source-scene-sensitivity').value);
|
||||
payload.smoothing = parseFloat(document.getElementById('value-source-scene-smoothing').value);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -224,7 +220,7 @@ export async function deleteValueSource(sourceId) {
|
||||
// ── Card rendering (used by streams.js) ───────────────────────
|
||||
|
||||
export function createValueSourceCard(src) {
|
||||
const typeIcons = { static: '📊', animated: '🔄', audio: '🎵', adaptive: '🌤️' };
|
||||
const typeIcons = { static: '📊', animated: '🔄', audio: '🎵', adaptive_time: '🕐', adaptive_scene: '🌤️' };
|
||||
const icon = typeIcons[src.source_type] || '🎚️';
|
||||
|
||||
let propsHtml = '';
|
||||
@@ -245,23 +241,19 @@ export function createValueSourceCard(src) {
|
||||
<span class="stream-card-prop" title="${escapeHtml(t('value_source.audio_source'))}">${escapeHtml(audioName)}</span>
|
||||
<span class="stream-card-prop">${modeLabel.toUpperCase()}</span>
|
||||
`;
|
||||
} else if (src.source_type === 'adaptive') {
|
||||
if (src.adaptive_mode === 'scene') {
|
||||
const ps = _cachedStreams.find(s => s.id === src.picture_source_id);
|
||||
const psName = ps ? ps.name : (src.picture_source_id || '-');
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop">${t('value_source.adaptive_mode.scene')}</span>
|
||||
<span class="stream-card-prop">${escapeHtml(psName)}</span>
|
||||
<span class="stream-card-prop">${src.scene_behavior || 'complement'}</span>
|
||||
`;
|
||||
} else {
|
||||
const pts = (src.schedule || []).length;
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop">${t('value_source.adaptive_mode.time_of_day')}</span>
|
||||
<span class="stream-card-prop">${pts} ${t('value_source.schedule.points')}</span>
|
||||
<span class="stream-card-prop">${src.min_value ?? 0}–${src.max_value ?? 1}</span>
|
||||
`;
|
||||
}
|
||||
} else if (src.source_type === 'adaptive_time') {
|
||||
const pts = (src.schedule || []).length;
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop">${pts} ${t('value_source.schedule.points')}</span>
|
||||
<span class="stream-card-prop">${src.min_value ?? 0}–${src.max_value ?? 1}</span>
|
||||
`;
|
||||
} else if (src.source_type === 'adaptive_scene') {
|
||||
const ps = _cachedStreams.find(s => s.id === src.picture_source_id);
|
||||
const psName = ps ? ps.name : (src.picture_source_id || '-');
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop">${escapeHtml(psName)}</span>
|
||||
<span class="stream-card-prop">${src.scene_behavior || 'complement'}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
|
||||
@@ -774,11 +774,12 @@
|
||||
"value_source.name.placeholder": "Brightness Pulse",
|
||||
"value_source.name.hint": "A descriptive name for this value source",
|
||||
"value_source.type": "Type:",
|
||||
"value_source.type.hint": "Static outputs a constant value. Animated cycles through a waveform. Audio reacts to sound input. Adaptive adjusts based on time of day or scene brightness.",
|
||||
"value_source.type.hint": "Static outputs a constant value. Animated cycles through a waveform. Audio reacts to sound input. Adaptive types adjust brightness automatically based on time of day or scene content.",
|
||||
"value_source.type.static": "Static",
|
||||
"value_source.type.animated": "Animated",
|
||||
"value_source.type.audio": "Audio",
|
||||
"value_source.type.adaptive": "Adaptive",
|
||||
"value_source.type.adaptive_time": "Adaptive (Time of Day)",
|
||||
"value_source.type.adaptive_scene": "Adaptive (Scene)",
|
||||
"value_source.value": "Value:",
|
||||
"value_source.value.hint": "Constant output value (0.0 = off, 1.0 = full brightness)",
|
||||
"value_source.waveform": "Waveform:",
|
||||
@@ -804,10 +805,6 @@
|
||||
"value_source.sensitivity.hint": "Gain multiplier for the audio signal (higher = more reactive)",
|
||||
"value_source.smoothing": "Smoothing:",
|
||||
"value_source.smoothing.hint": "Temporal smoothing (0 = instant response, 1 = very smooth/slow)",
|
||||
"value_source.adaptive_mode": "Adaptive Mode:",
|
||||
"value_source.adaptive_mode.hint": "Time of Day adjusts brightness on a daily schedule. Scene analyzes picture brightness in real time.",
|
||||
"value_source.adaptive_mode.time_of_day": "Time of Day",
|
||||
"value_source.adaptive_mode.scene": "Scene Brightness",
|
||||
"value_source.schedule": "Schedule:",
|
||||
"value_source.schedule.hint": "Define at least 2 time points. Brightness interpolates linearly between them, wrapping at midnight.",
|
||||
"value_source.schedule.add": "+ Add Point",
|
||||
|
||||
@@ -774,11 +774,12 @@
|
||||
"value_source.name.placeholder": "Пульс яркости",
|
||||
"value_source.name.hint": "Описательное имя для этого источника значений",
|
||||
"value_source.type": "Тип:",
|
||||
"value_source.type.hint": "Статический выдаёт постоянное значение. Анимированный циклически меняет форму волны. Аудио реагирует на звук. Адаптивный подстраивается под время суток или яркость сцены.",
|
||||
"value_source.type.hint": "Статический выдаёт постоянное значение. Анимированный циклически меняет форму волны. Аудио реагирует на звук. Адаптивные типы автоматически подстраивают яркость по времени суток или содержимому сцены.",
|
||||
"value_source.type.static": "Статический",
|
||||
"value_source.type.animated": "Анимированный",
|
||||
"value_source.type.audio": "Аудио",
|
||||
"value_source.type.adaptive": "Адаптивный",
|
||||
"value_source.type.adaptive_time": "Адаптивный (Время суток)",
|
||||
"value_source.type.adaptive_scene": "Адаптивный (Сцена)",
|
||||
"value_source.value": "Значение:",
|
||||
"value_source.value.hint": "Постоянное выходное значение (0.0 = выкл, 1.0 = полная яркость)",
|
||||
"value_source.waveform": "Форма волны:",
|
||||
@@ -804,10 +805,6 @@
|
||||
"value_source.sensitivity.hint": "Множитель усиления аудиосигнала (выше = более реактивный)",
|
||||
"value_source.smoothing": "Сглаживание:",
|
||||
"value_source.smoothing.hint": "Временное сглаживание (0 = мгновенный отклик, 1 = очень плавный/медленный)",
|
||||
"value_source.adaptive_mode": "Адаптивный режим:",
|
||||
"value_source.adaptive_mode.hint": "Время суток регулирует яркость по дневному расписанию. Сцена анализирует яркость изображения в реальном времени.",
|
||||
"value_source.adaptive_mode.time_of_day": "Время суток",
|
||||
"value_source.adaptive_mode.scene": "Яркость сцены",
|
||||
"value_source.schedule": "Расписание:",
|
||||
"value_source.schedule.hint": "Определите минимум 2 временные точки. Яркость линейно интерполируется между ними, с переходом через полночь.",
|
||||
"value_source.schedule.add": "+ Добавить точку",
|
||||
|
||||
Reference in New Issue
Block a user