feat(patterns): add new pattern suite and improve mobile controls
Add a broad set of LED patterns with metadata/tests and update zone/profile preset seeding, while refining mobile/desktop UI behavior for scrolling, brightness controls, and bulk pattern sending.
This commit is contained in:
@@ -175,39 +175,6 @@ async function postDriverSequence(sequence, targetMacs, delayS) {
|
||||
return res.json().catch(() => ({}));
|
||||
}
|
||||
|
||||
// Send a select message for a preset to all devices on the current zone (ESP-NOW or Wi-Fi).
|
||||
const sendSelectForCurrentTabDevices = async (presetId, sectionEl) => {
|
||||
const section = sectionEl || document.querySelector('.presets-section[data-zone-id]');
|
||||
if (!section || !presetId) {
|
||||
return;
|
||||
}
|
||||
const deviceNames = tabDeviceNamesFromSection(section);
|
||||
|
||||
if (!deviceNames.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const select = {};
|
||||
deviceNames.forEach((name) => {
|
||||
if (name) {
|
||||
select[name] = [presetId];
|
||||
}
|
||||
});
|
||||
|
||||
const targetMacs =
|
||||
typeof window.tabsManager !== 'undefined' &&
|
||||
typeof window.tabsManager.resolveTabDeviceMacs === 'function'
|
||||
? await window.tabsManager.resolveTabDeviceMacs(deviceNames)
|
||||
: [];
|
||||
|
||||
try {
|
||||
await postDriverSequence([{ v: '1', select }], targetMacs);
|
||||
} catch (err) {
|
||||
console.error('sendSelectForCurrentTabDevices:', err);
|
||||
alert('Failed to send preset selection to devices.');
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const presetsButton = document.getElementById('presets-btn');
|
||||
const presetsModal = document.getElementById('presets-modal');
|
||||
@@ -1332,7 +1299,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// Work out the preset ID: for existing presets use currentEditId, otherwise fall back to name
|
||||
const presetId = currentEditId || payload.name;
|
||||
// Try sends preset first, then select; never persist on device.
|
||||
await sendPresetViaEspNow(presetId, payload, deviceNames, false, false);
|
||||
await sendPresetViaEspNow(presetId, payload, deviceNames, false, false, '2');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1346,8 +1313,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const section = document.querySelector('.presets-section[data-zone-id]');
|
||||
const deviceNames = tabDeviceNamesFromSection(section);
|
||||
const presetId = currentEditId || payload.name;
|
||||
await sendPresetViaEspNow(presetId, payload, deviceNames, true, true, '1');
|
||||
await updateTabDefaultPreset(presetId);
|
||||
await sendDefaultPreset(presetId, deviceNames);
|
||||
await sendDefaultPreset('1', deviceNames);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1379,7 +1347,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
throw new Error('Failed to save preset');
|
||||
}
|
||||
|
||||
// Same device targeting as Try: zone tab supplies names → /presets/push gets targets + select.
|
||||
// Same device targeting as Try: zone tab supplies names and selection without persistence.
|
||||
const section = document.querySelector('.presets-section[data-zone-id]');
|
||||
const deviceNames = tabDeviceNamesFromSection(section);
|
||||
|
||||
@@ -1388,18 +1356,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (saved && typeof saved === 'object') {
|
||||
if (currentEditId) {
|
||||
// PUT returns the preset object directly; use the existing ID
|
||||
await sendPresetViaEspNow(currentEditId, saved, deviceNames, true, false);
|
||||
await sendPresetViaEspNow(currentEditId, saved, deviceNames, false, false, '2');
|
||||
} else {
|
||||
// POST returns { id: preset }
|
||||
const entries = Object.entries(saved);
|
||||
if (entries.length > 0) {
|
||||
const [newId, presetData] = entries[0];
|
||||
await sendPresetViaEspNow(newId, presetData, deviceNames, true, false);
|
||||
await sendPresetViaEspNow(newId, presetData, deviceNames, false, false, '2');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback: send what we just built
|
||||
await sendPresetViaEspNow(currentEditId || payload.name, payload, deviceNames, true, false);
|
||||
await sendPresetViaEspNow(currentEditId || payload.name, payload, deviceNames, false, false, '2');
|
||||
}
|
||||
|
||||
await loadPresets();
|
||||
@@ -1454,7 +1422,14 @@ const coercePresetInt = (v, def = 0) => {
|
||||
// 1) preset payload (optionally with save)
|
||||
// 2) optional select for device names (never with save)
|
||||
// saveToDevice defaults to true.
|
||||
const sendPresetViaEspNow = async (presetId, preset, deviceNames, saveToDevice = true, setDefault = false) => {
|
||||
const sendPresetViaEspNow = async (
|
||||
presetId,
|
||||
preset,
|
||||
deviceNames,
|
||||
saveToDevice = true,
|
||||
setDefault = false,
|
||||
devicePresetId = null,
|
||||
) => {
|
||||
try {
|
||||
const baseColors = Array.isArray(preset.colors) && preset.colors.length
|
||||
? preset.colors
|
||||
@@ -1462,10 +1437,11 @@ const sendPresetViaEspNow = async (presetId, preset, deviceNames, saveToDevice =
|
||||
const paletteColors = await getCurrentProfilePaletteColors();
|
||||
const colors = resolveColorsWithPaletteRefs(baseColors, preset.palette_refs, paletteColors);
|
||||
|
||||
const wirePresetId = devicePresetId != null ? String(devicePresetId) : String(presetId);
|
||||
const presetMessage = {
|
||||
v: '1',
|
||||
presets: {
|
||||
[presetId]: {
|
||||
[wirePresetId]: {
|
||||
pattern: preset.pattern || 'off',
|
||||
colors,
|
||||
delay: typeof preset.delay === 'number' ? preset.delay : 100,
|
||||
@@ -1486,7 +1462,7 @@ const sendPresetViaEspNow = async (presetId, preset, deviceNames, saveToDevice =
|
||||
presetMessage.save = true;
|
||||
}
|
||||
if (setDefault) {
|
||||
presetMessage.default = presetId;
|
||||
presetMessage.default = wirePresetId;
|
||||
}
|
||||
|
||||
const names = Array.isArray(deviceNames) ? deviceNames : [];
|
||||
@@ -1502,7 +1478,7 @@ const sendPresetViaEspNow = async (presetId, preset, deviceNames, saveToDevice =
|
||||
const select = {};
|
||||
names.forEach((name) => {
|
||||
if (name) {
|
||||
select[name] = [presetId];
|
||||
select[name] = [wirePresetId];
|
||||
}
|
||||
});
|
||||
if (Object.keys(select).length > 0) {
|
||||
@@ -1879,7 +1855,8 @@ const createPresetButton = (presetId, preset, zoneId, isSelected = false) => {
|
||||
button.classList.add('active');
|
||||
selectedPresets[zoneId] = presetId;
|
||||
const section = row.closest('.presets-section');
|
||||
sendSelectForCurrentTabDevices(presetId, section).catch((err) => {
|
||||
const deviceNames = tabDeviceNamesFromSection(section);
|
||||
sendPresetViaEspNow(presetId, preset, deviceNames, false, false, '2').catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user