docs(ui): update help assets and regenerate help pdf
This commit is contained in:
@@ -60,6 +60,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (nameInput && data && typeof data === 'object') {
|
||||
nameInput.value = data.device_name || 'led-controller';
|
||||
}
|
||||
const chInput = document.getElementById('wifi-channel-input');
|
||||
if (chInput && data && typeof data === 'object') {
|
||||
const ch = data.wifi_channel;
|
||||
chInput.value =
|
||||
ch !== undefined && ch !== null && ch !== '' ? String(ch) : '6';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading device settings:', error);
|
||||
}
|
||||
@@ -116,15 +122,29 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
showSettingsMessage('Device name is required', 'error');
|
||||
return;
|
||||
}
|
||||
const chRaw = document.getElementById('wifi-channel-input')
|
||||
? document.getElementById('wifi-channel-input').value
|
||||
: '6';
|
||||
const wifiChannel = parseInt(chRaw, 10);
|
||||
if (Number.isNaN(wifiChannel) || wifiChannel < 1 || wifiChannel > 11) {
|
||||
showSettingsMessage('WiFi channel must be between 1 and 11', 'error');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/settings/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ device_name: deviceName }),
|
||||
body: JSON.stringify({
|
||||
device_name: deviceName,
|
||||
wifi_channel: wifiChannel,
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (response.ok) {
|
||||
showSettingsMessage('Device name saved. It will be used on next restart.', 'success');
|
||||
showSettingsMessage(
|
||||
'Device settings saved. They will apply on next restart where relevant.',
|
||||
'success',
|
||||
);
|
||||
} else {
|
||||
showSettingsMessage(`Error: ${result.error || 'Failed to save device name'}`, 'error');
|
||||
}
|
||||
|
||||
@@ -174,6 +174,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const presetBrightnessInput = document.getElementById('preset-brightness-input');
|
||||
const presetDelayInput = document.getElementById('preset-delay-input');
|
||||
const presetDefaultButton = document.getElementById('preset-default-btn');
|
||||
const presetRemoveFromTabButton = document.getElementById('preset-remove-from-tab-btn');
|
||||
const presetSaveButton = document.getElementById('preset-save-btn');
|
||||
const presetAddFromPaletteButton = document.getElementById('preset-add-from-palette-btn');
|
||||
|
||||
@@ -532,6 +533,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
updatePresetEditorTabActionsVisibility();
|
||||
};
|
||||
|
||||
const clearForm = () => {
|
||||
@@ -565,6 +567,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
presetPatternInput.style.backgroundColor = '';
|
||||
presetPatternInput.style.cursor = '';
|
||||
}
|
||||
updatePresetEditorTabActionsVisibility();
|
||||
};
|
||||
|
||||
const getActiveTabId = () => {
|
||||
@@ -575,6 +578,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return section ? section.dataset.tabId : null;
|
||||
};
|
||||
|
||||
const updatePresetEditorTabActionsVisibility = () => {
|
||||
if (!presetRemoveFromTabButton) return;
|
||||
const show = Boolean(currentEditTabId && currentEditId);
|
||||
presetRemoveFromTabButton.hidden = !show;
|
||||
};
|
||||
|
||||
const updateTabDefaultPreset = async (presetId) => {
|
||||
const tabId = getActiveTabId();
|
||||
if (!tabId) {
|
||||
@@ -786,6 +795,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
editButton.textContent = 'Edit';
|
||||
editButton.addEventListener('click', async () => {
|
||||
currentEditId = presetId;
|
||||
currentEditTabId = null;
|
||||
const paletteColors = await getCurrentProfilePaletteColors();
|
||||
const presetForEditor = {
|
||||
...(preset || {}),
|
||||
@@ -1241,6 +1251,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (presetRemoveFromTabButton) {
|
||||
presetRemoveFromTabButton.addEventListener('click', async () => {
|
||||
if (!currentEditTabId || !currentEditId) return;
|
||||
if (!window.confirm('Remove this preset from this tab?')) return;
|
||||
await removePresetFromTab(currentEditTabId, currentEditId);
|
||||
clearForm();
|
||||
closeEditor();
|
||||
});
|
||||
}
|
||||
|
||||
presetSaveButton.addEventListener('click', async () => {
|
||||
const payload = buildPresetPayload();
|
||||
if (!payload.name) {
|
||||
@@ -1778,58 +1798,7 @@ const createPresetButton = (presetId, preset, tabId, isSelected = false) => {
|
||||
editPresetFromTab(presetId, tabId, preset);
|
||||
});
|
||||
|
||||
const defaultBtn = document.createElement('button');
|
||||
defaultBtn.type = 'button';
|
||||
defaultBtn.className = 'btn btn-secondary btn-small';
|
||||
defaultBtn.textContent = 'Default';
|
||||
defaultBtn.title = 'Set as default preset';
|
||||
defaultBtn.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (isDraggingPreset) return;
|
||||
const section = row.closest('.presets-section');
|
||||
const namesAttr = section && section.getAttribute('data-device-names');
|
||||
const deviceNames = namesAttr
|
||||
? namesAttr.split(',').map((n) => n.trim()).filter((n) => n.length > 0)
|
||||
: [];
|
||||
sendDefaultPreset(presetId, deviceNames);
|
||||
// Persist tab-level default if we know the tab from this tile.
|
||||
if (tabId) {
|
||||
try {
|
||||
const tabResponse = await fetch(`/tabs/${tabId}`, {
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
if (tabResponse.ok) {
|
||||
const tabData = await tabResponse.json();
|
||||
tabData.default_preset = presetId;
|
||||
await fetch(`/tabs/${tabId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(tabData),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to save tab default preset:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.type = 'button';
|
||||
removeBtn.className = 'btn btn-danger btn-small';
|
||||
removeBtn.textContent = 'Remove';
|
||||
removeBtn.title = 'Remove from this tab';
|
||||
removeBtn.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (isDraggingPreset) return;
|
||||
if (!window.confirm('Remove this preset from this tab?')) return;
|
||||
await removePresetFromTab(tabId, presetId);
|
||||
});
|
||||
|
||||
actions.appendChild(editBtn);
|
||||
actions.appendChild(defaultBtn);
|
||||
actions.appendChild(removeBtn);
|
||||
row.appendChild(actions);
|
||||
}
|
||||
|
||||
|
||||
@@ -620,15 +620,21 @@ body.preset-ui-run .edit-mode-only {
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
/* Edit only beside the preset tile in edit mode. */
|
||||
.preset-tile-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-auto-rows: 1fr;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
gap: 0.2rem;
|
||||
align-content: stretch;
|
||||
flex-shrink: 0;
|
||||
padding: 0.15rem 0 0.15rem 0.25rem;
|
||||
width: 6.5rem;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.preset-editor-modal-actions {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.preset-tile-actions .btn {
|
||||
|
||||
@@ -142,13 +142,6 @@ function renderTabsListModal(tabs, tabOrder, currentTabId) {
|
||||
openEditTabModal(tabId, tab);
|
||||
});
|
||||
|
||||
const sendPresetsButton = document.createElement("button");
|
||||
sendPresetsButton.className = "btn btn-secondary btn-small";
|
||||
sendPresetsButton.textContent = "Send Presets";
|
||||
sendPresetsButton.addEventListener("click", async () => {
|
||||
await sendTabPresets(tabId);
|
||||
});
|
||||
|
||||
const cloneButton = document.createElement("button");
|
||||
cloneButton.className = "btn btn-secondary btn-small";
|
||||
cloneButton.textContent = "Clone";
|
||||
@@ -233,7 +226,6 @@ function renderTabsListModal(tabs, tabOrder, currentTabId) {
|
||||
|
||||
row.appendChild(label);
|
||||
row.appendChild(applyButton);
|
||||
row.appendChild(sendPresetsButton);
|
||||
if (editMode) {
|
||||
row.appendChild(editButton);
|
||||
row.appendChild(cloneButton);
|
||||
@@ -373,69 +365,6 @@ async function loadTabContent(tabId) {
|
||||
}
|
||||
}
|
||||
|
||||
// Send all presets used by a tab via the /presets/send HTTP endpoint.
|
||||
async function sendTabPresets(tabId) {
|
||||
try {
|
||||
// Load tab data to determine which presets are used
|
||||
const tabResponse = await fetch(`/tabs/${tabId}`, {
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
if (!tabResponse.ok) {
|
||||
alert('Failed to load tab to send presets.');
|
||||
return;
|
||||
}
|
||||
const tabData = await tabResponse.json();
|
||||
|
||||
// Extract preset IDs from tab (supports grid, flat, and legacy formats)
|
||||
let presetIds = [];
|
||||
if (Array.isArray(tabData.presets_flat)) {
|
||||
presetIds = tabData.presets_flat;
|
||||
} else if (Array.isArray(tabData.presets)) {
|
||||
if (tabData.presets.length && typeof tabData.presets[0] === 'string') {
|
||||
// Flat array of IDs
|
||||
presetIds = tabData.presets;
|
||||
} else if (tabData.presets.length && Array.isArray(tabData.presets[0])) {
|
||||
// 2D grid
|
||||
presetIds = tabData.presets.flat();
|
||||
}
|
||||
}
|
||||
presetIds = (presetIds || []).filter(Boolean);
|
||||
|
||||
if (!presetIds.length) {
|
||||
alert('This tab has no presets to send.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Call server-side ESPNow sender with just the IDs; it handles chunking.
|
||||
const payload = { preset_ids: presetIds };
|
||||
if (tabData.default_preset) {
|
||||
payload.default = tabData.default_preset;
|
||||
}
|
||||
const response = await fetch('/presets/send', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
const msg = (data && data.error) || 'Failed to send presets.';
|
||||
alert(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const sent = typeof data.presets_sent === 'number' ? data.presets_sent : presetIds.length;
|
||||
const messages = typeof data.messages_sent === 'number' ? data.messages_sent : '?';
|
||||
alert(`Sent ${sent} preset(s) in ${messages} ESPNow message(s).`);
|
||||
} catch (error) {
|
||||
console.error('Failed to send tab presets:', error);
|
||||
alert('Failed to send tab presets.');
|
||||
}
|
||||
}
|
||||
|
||||
// Send all presets used by all tabs in the current profile via /presets/send.
|
||||
async function sendProfilePresets() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user