Animation None option, FPS min 1, serial COM lifecycle fixes
- Replace animation Enable checkbox with None option in effect selector; show effect description tooltip; disable speed slider when None selected - Allow target FPS range 1-90 (was 10-90) across UI and backend validation - Scope serial COM connections to target lifetime (no idle caching); use temporary connections for power-off/test mode - Fix serial black frame on stop: flush after write, delay after task cancel to prevent race with in-flight thread pool write Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -89,7 +89,7 @@ import {
|
||||
// Layer 5: color-strip sources
|
||||
import {
|
||||
showCSSEditor, closeCSSEditorModal, forceCSSEditorClose, saveCSSEditor, deleteColorStrip,
|
||||
onCSSTypeChange, colorCycleAddColor, colorCycleRemoveColor,
|
||||
onCSSTypeChange, onAnimationTypeChange, colorCycleAddColor, colorCycleRemoveColor,
|
||||
} from './features/color-strips.js';
|
||||
|
||||
// Layer 5: calibration
|
||||
@@ -274,6 +274,7 @@ Object.assign(window, {
|
||||
saveCSSEditor,
|
||||
deleteColorStrip,
|
||||
onCSSTypeChange,
|
||||
onAnimationTypeChange,
|
||||
colorCycleAddColor,
|
||||
colorCycleRemoveColor,
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ class CSSEditorModal extends Modal {
|
||||
frame_interpolation: document.getElementById('css-editor-frame-interpolation').checked,
|
||||
led_count: document.getElementById('css-editor-led-count').value,
|
||||
gradient_stops: type === 'gradient' ? JSON.stringify(_gradientStops) : '[]',
|
||||
animation_enabled: document.getElementById('css-editor-animation-enabled').checked,
|
||||
animation_type: document.getElementById('css-editor-animation-type').value,
|
||||
animation_speed: document.getElementById('css-editor-animation-speed').value,
|
||||
cycle_speed: document.getElementById('css-editor-cycle-speed').value,
|
||||
@@ -50,9 +49,10 @@ export function onCSSTypeChange() {
|
||||
// Animation section — shown for static/gradient only (color_cycle is always animating)
|
||||
const animSection = document.getElementById('css-editor-animation-section');
|
||||
const animTypeSelect = document.getElementById('css-editor-animation-type');
|
||||
const noneOpt = `<option value="none">${t('color_strip.animation.type.none')}</option>`;
|
||||
if (type === 'static') {
|
||||
animSection.style.display = '';
|
||||
animTypeSelect.innerHTML =
|
||||
animTypeSelect.innerHTML = noneOpt +
|
||||
`<option value="breathing">${t('color_strip.animation.type.breathing')}</option>` +
|
||||
`<option value="strobe">${t('color_strip.animation.type.strobe')}</option>` +
|
||||
`<option value="sparkle">${t('color_strip.animation.type.sparkle')}</option>` +
|
||||
@@ -61,7 +61,7 @@ export function onCSSTypeChange() {
|
||||
`<option value="rainbow_fade">${t('color_strip.animation.type.rainbow_fade')}</option>`;
|
||||
} else if (type === 'gradient') {
|
||||
animSection.style.display = '';
|
||||
animTypeSelect.innerHTML =
|
||||
animTypeSelect.innerHTML = noneOpt +
|
||||
`<option value="breathing">${t('color_strip.animation.type.breathing')}</option>` +
|
||||
`<option value="gradient_shift">${t('color_strip.animation.type.gradient_shift')}</option>` +
|
||||
`<option value="wave">${t('color_strip.animation.type.wave')}</option>` +
|
||||
@@ -73,6 +73,7 @@ export function onCSSTypeChange() {
|
||||
} else {
|
||||
animSection.style.display = 'none';
|
||||
}
|
||||
_syncAnimationSpeedState();
|
||||
|
||||
if (type === 'gradient') {
|
||||
requestAnimationFrame(() => gradientRenderAll());
|
||||
@@ -80,22 +81,41 @@ export function onCSSTypeChange() {
|
||||
}
|
||||
|
||||
function _getAnimationPayload() {
|
||||
const type = document.getElementById('css-editor-animation-type').value;
|
||||
return {
|
||||
enabled: document.getElementById('css-editor-animation-enabled').checked,
|
||||
type: document.getElementById('css-editor-animation-type').value,
|
||||
enabled: type !== 'none',
|
||||
type: type !== 'none' ? type : 'breathing',
|
||||
speed: parseFloat(document.getElementById('css-editor-animation-speed').value),
|
||||
};
|
||||
}
|
||||
|
||||
function _loadAnimationState(anim) {
|
||||
document.getElementById('css-editor-animation-enabled').checked = !!(anim && anim.enabled);
|
||||
const speedEl = document.getElementById('css-editor-animation-speed');
|
||||
speedEl.value = (anim && anim.speed != null) ? anim.speed : 1.0;
|
||||
document.getElementById('css-editor-animation-speed-val').textContent =
|
||||
parseFloat(speedEl.value).toFixed(1);
|
||||
// Set type after onCSSTypeChange() has populated the dropdown
|
||||
if (anim && anim.type) {
|
||||
if (anim && anim.enabled && anim.type) {
|
||||
document.getElementById('css-editor-animation-type').value = anim.type;
|
||||
} else {
|
||||
document.getElementById('css-editor-animation-type').value = 'none';
|
||||
}
|
||||
_syncAnimationSpeedState();
|
||||
}
|
||||
|
||||
export function onAnimationTypeChange() {
|
||||
_syncAnimationSpeedState();
|
||||
}
|
||||
|
||||
function _syncAnimationSpeedState() {
|
||||
const type = document.getElementById('css-editor-animation-type').value;
|
||||
const isNone = type === 'none';
|
||||
document.getElementById('css-editor-animation-speed').disabled = isNone;
|
||||
const descEl = document.getElementById('css-editor-animation-type-desc');
|
||||
if (descEl) {
|
||||
const desc = t('color_strip.animation.type.' + type + '.desc') || '';
|
||||
descEl.textContent = desc;
|
||||
descEl.style.display = desc ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user