feat: add chase and gradient flash notification effects with priority queue
Some checks failed
Lint & Test / test (push) Failing after 28s
Some checks failed
Lint & Test / test (push) Failing after 28s
New notification effects: - Chase: light bounces across strip with Gaussian glow tail - Gradient flash: bright center fades to edges with exponential decay Queue priority: notifications with color_override get high priority and interrupt the current effect. Also fixes transient preview for notification sources — adds WebSocket "fire" command so inline preview works without a saved source, plus auto-fires on preview open so the effect is visible immediately.
This commit is contained in:
@@ -31,12 +31,14 @@ export function ensureNotificationEffectIconSelect() {
|
||||
const sel = document.getElementById('css-editor-notification-effect') as HTMLSelectElement | null;
|
||||
if (!sel) return;
|
||||
const items = [
|
||||
{ value: 'flash', icon: _icon(P.zap), label: t('color_strip.notification.effect.flash'), desc: t('color_strip.notification.effect.flash.desc') },
|
||||
{ value: 'pulse', icon: _icon(P.activity), label: t('color_strip.notification.effect.pulse'), desc: t('color_strip.notification.effect.pulse.desc') },
|
||||
{ value: 'sweep', icon: _icon(P.fastForward), label: t('color_strip.notification.effect.sweep'), desc: t('color_strip.notification.effect.sweep.desc') },
|
||||
{ value: 'flash', icon: _icon(P.zap), label: t('color_strip.notification.effect.flash'), desc: t('color_strip.notification.effect.flash.desc') },
|
||||
{ value: 'pulse', icon: _icon(P.activity), label: t('color_strip.notification.effect.pulse'), desc: t('color_strip.notification.effect.pulse.desc') },
|
||||
{ value: 'sweep', icon: _icon(P.fastForward), label: t('color_strip.notification.effect.sweep'), desc: t('color_strip.notification.effect.sweep.desc') },
|
||||
{ value: 'chase', icon: _icon(P.rocket), label: t('color_strip.notification.effect.chase'), desc: t('color_strip.notification.effect.chase.desc') },
|
||||
{ value: 'gradient_flash', icon: _icon(P.rainbow), label: t('color_strip.notification.effect.gradient_flash'), desc: t('color_strip.notification.effect.gradient_flash.desc') },
|
||||
];
|
||||
if (_notificationEffectIconSelect) { _notificationEffectIconSelect.updateItems(items); return; }
|
||||
_notificationEffectIconSelect = new IconSelect({ target: sel, items, columns: 3 });
|
||||
_notificationEffectIconSelect = new IconSelect({ target: sel, items, columns: 2 });
|
||||
}
|
||||
|
||||
export function ensureNotificationFilterModeIconSelect() {
|
||||
|
||||
@@ -15,11 +15,12 @@ import {
|
||||
import { EntitySelect } from '../core/entity-palette.ts';
|
||||
import { hexToRgbArray, getGradientStops } from './css-gradient-editor.ts';
|
||||
import { testNotification, _getAnimationPayload, _colorCycleGetColors } from './color-strips.ts';
|
||||
import { notificationGetAppColorsDict } from './color-strips-notification.ts';
|
||||
|
||||
/* ── Preview config builder ───────────────────────────────────── */
|
||||
|
||||
const _PREVIEW_TYPES = new Set([
|
||||
'static', 'gradient', 'color_cycle', 'effect', 'daylight', 'candlelight',
|
||||
'static', 'gradient', 'color_cycle', 'effect', 'daylight', 'candlelight', 'notification',
|
||||
]);
|
||||
|
||||
function _collectPreviewConfig() {
|
||||
@@ -44,6 +45,17 @@ function _collectPreviewConfig() {
|
||||
config = { source_type: 'daylight', speed: parseFloat((document.getElementById('css-editor-daylight-speed') as HTMLInputElement).value), use_real_time: (document.getElementById('css-editor-daylight-real-time') as HTMLInputElement).checked, latitude: parseFloat((document.getElementById('css-editor-daylight-latitude') as HTMLInputElement).value), longitude: parseFloat((document.getElementById('css-editor-daylight-longitude') as HTMLInputElement).value) };
|
||||
} else if (sourceType === 'candlelight') {
|
||||
config = { source_type: 'candlelight', color: hexToRgbArray((document.getElementById('css-editor-candlelight-color') as HTMLInputElement).value), intensity: parseFloat((document.getElementById('css-editor-candlelight-intensity') as HTMLInputElement).value), num_candles: parseInt((document.getElementById('css-editor-candlelight-num-candles') as HTMLInputElement).value) || 3, speed: parseFloat((document.getElementById('css-editor-candlelight-speed') as HTMLInputElement).value), wind_strength: parseFloat((document.getElementById('css-editor-candlelight-wind') as HTMLInputElement).value), candle_type: (document.getElementById('css-editor-candlelight-type') as HTMLSelectElement).value };
|
||||
} else if (sourceType === 'notification') {
|
||||
const filterList = (document.getElementById('css-editor-notification-filter-list') as HTMLInputElement).value.split('\n').map(s => s.trim()).filter(Boolean);
|
||||
config = {
|
||||
source_type: 'notification',
|
||||
notification_effect: (document.getElementById('css-editor-notification-effect') as HTMLInputElement).value,
|
||||
duration_ms: parseInt((document.getElementById('css-editor-notification-duration') as HTMLInputElement).value) || 1500,
|
||||
default_color: (document.getElementById('css-editor-notification-default-color') as HTMLInputElement).value,
|
||||
app_filter_mode: (document.getElementById('css-editor-notification-filter-mode') as HTMLInputElement).value,
|
||||
app_filter_list: filterList,
|
||||
app_colors: notificationGetAppColorsDict(),
|
||||
};
|
||||
}
|
||||
const clockEl = document.getElementById('css-editor-clock') as HTMLSelectElement | null;
|
||||
if (clockEl && clockEl.value) config.clock_id = clockEl.value;
|
||||
@@ -245,6 +257,14 @@ function _cssTestConnect(sourceId: string, ledCount: number, fps?: number) {
|
||||
_cssTestWs.onopen = () => {
|
||||
if (gen !== _cssTestGeneration) return;
|
||||
_cssTestWs!.send(JSON.stringify(_cssTestTransientConfig));
|
||||
// Auto-fire notification after stream starts so user sees the effect immediately
|
||||
if (_cssTestTransientConfig.source_type === 'notification') {
|
||||
setTimeout(() => {
|
||||
if (gen === _cssTestGeneration && _cssTestWs && _cssTestWs.readyState === WebSocket.OPEN) {
|
||||
_cssTestWs.send(JSON.stringify({ action: 'fire', color: _cssTestTransientConfig.default_color }));
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -826,6 +846,12 @@ function _cssTestRenderStripAxis(canvasId: string, ledCount: number) {
|
||||
}
|
||||
|
||||
export function fireCssTestNotification() {
|
||||
// Transient preview: fire via WebSocket command
|
||||
if (_cssTestTransientConfig && _cssTestWs && _cssTestWs.readyState === WebSocket.OPEN) {
|
||||
_cssTestWs.send(JSON.stringify({ action: 'fire', color: _cssTestTransientConfig.default_color }));
|
||||
return;
|
||||
}
|
||||
// Saved source: fire via REST endpoint
|
||||
for (const id of _cssTestNotificationIds) {
|
||||
testNotification(id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user