Update UI for palettes, presets, and patterns
This commit is contained in:
381
src/static/presets.js
Normal file
381
src/static/presets.js
Normal file
@@ -0,0 +1,381 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const presetsButton = document.getElementById('presets-btn');
|
||||
const presetsModal = document.getElementById('presets-modal');
|
||||
const presetsCloseButton = document.getElementById('presets-close-btn');
|
||||
const presetsList = document.getElementById('presets-list');
|
||||
const presetsAddButton = document.getElementById('preset-add-btn');
|
||||
const presetEditorModal = document.getElementById('preset-editor-modal');
|
||||
const presetEditorCloseButton = document.getElementById('preset-editor-close-btn');
|
||||
const presetNameInput = document.getElementById('preset-name-input');
|
||||
const presetPatternInput = document.getElementById('preset-pattern-input');
|
||||
const presetColorsInput = document.getElementById('preset-colors-input');
|
||||
const presetBrightnessInput = document.getElementById('preset-brightness-input');
|
||||
const presetDelayInput = document.getElementById('preset-delay-input');
|
||||
const presetSaveButton = document.getElementById('preset-save-btn');
|
||||
const presetClearButton = document.getElementById('preset-clear-btn');
|
||||
const presetAddFromPaletteButton = document.getElementById('preset-add-from-palette-btn');
|
||||
|
||||
if (!presetsButton || !presetsModal || !presetsList || !presetSaveButton || !presetClearButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentEditId = null;
|
||||
let cachedPresets = {};
|
||||
let cachedPatterns = {};
|
||||
|
||||
const getNumberInput = (id) => {
|
||||
const input = document.getElementById(id);
|
||||
if (!input) {
|
||||
return 0;
|
||||
}
|
||||
return parseInt(input.value, 10) || 0;
|
||||
};
|
||||
|
||||
const parseColors = (value) => {
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
return value
|
||||
.split(',')
|
||||
.map((color) => color.trim())
|
||||
.filter((color) => color.length > 0)
|
||||
.map((color) => (color.startsWith('#') ? color : `#${color}`));
|
||||
};
|
||||
|
||||
const setFormValues = (preset) => {
|
||||
if (!presetNameInput || !presetPatternInput || !presetColorsInput || !presetBrightnessInput || !presetDelayInput) {
|
||||
return;
|
||||
}
|
||||
presetNameInput.value = preset.name || '';
|
||||
presetPatternInput.value = preset.pattern || '';
|
||||
presetColorsInput.value = Array.isArray(preset.colors) ? preset.colors.join(',') : '';
|
||||
presetBrightnessInput.value = preset.brightness || 0;
|
||||
presetDelayInput.value = preset.delay || 0;
|
||||
document.getElementById('preset-n1-input').value = preset.n1 || 0;
|
||||
document.getElementById('preset-n2-input').value = preset.n2 || 0;
|
||||
document.getElementById('preset-n3-input').value = preset.n3 || 0;
|
||||
document.getElementById('preset-n4-input').value = preset.n4 || 0;
|
||||
document.getElementById('preset-n5-input').value = preset.n5 || 0;
|
||||
document.getElementById('preset-n6-input').value = preset.n6 || 0;
|
||||
document.getElementById('preset-n7-input').value = preset.n7 || 0;
|
||||
document.getElementById('preset-n8-input').value = preset.n8 || 0;
|
||||
};
|
||||
|
||||
const clearForm = () => {
|
||||
currentEditId = null;
|
||||
setFormValues({
|
||||
name: '',
|
||||
pattern: '',
|
||||
colors: [],
|
||||
brightness: 0,
|
||||
delay: 0,
|
||||
n1: 0,
|
||||
n2: 0,
|
||||
n3: 0,
|
||||
n4: 0,
|
||||
n5: 0,
|
||||
n6: 0,
|
||||
n7: 0,
|
||||
n8: 0,
|
||||
});
|
||||
};
|
||||
|
||||
const openEditor = () => {
|
||||
if (presetEditorModal) {
|
||||
presetEditorModal.classList.add('active');
|
||||
}
|
||||
loadPatterns().then(() => {
|
||||
updatePresetNLabels(presetPatternInput ? presetPatternInput.value : '');
|
||||
});
|
||||
};
|
||||
|
||||
const closeEditor = () => {
|
||||
if (presetEditorModal) {
|
||||
presetEditorModal.classList.remove('active');
|
||||
}
|
||||
};
|
||||
|
||||
const buildPresetPayload = () => {
|
||||
return {
|
||||
name: presetNameInput ? presetNameInput.value.trim() : '',
|
||||
pattern: presetPatternInput ? presetPatternInput.value.trim() : '',
|
||||
colors: parseColors(presetColorsInput ? presetColorsInput.value : ''),
|
||||
brightness: presetBrightnessInput ? parseInt(presetBrightnessInput.value, 10) || 0 : 0,
|
||||
delay: presetDelayInput ? parseInt(presetDelayInput.value, 10) || 0 : 0,
|
||||
n1: getNumberInput('preset-n1-input'),
|
||||
n2: getNumberInput('preset-n2-input'),
|
||||
n3: getNumberInput('preset-n3-input'),
|
||||
n4: getNumberInput('preset-n4-input'),
|
||||
n5: getNumberInput('preset-n5-input'),
|
||||
n6: getNumberInput('preset-n6-input'),
|
||||
n7: getNumberInput('preset-n7-input'),
|
||||
n8: getNumberInput('preset-n8-input'),
|
||||
};
|
||||
};
|
||||
|
||||
const loadPatterns = async () => {
|
||||
if (!presetPatternInput) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/patterns', {
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
const patterns = await response.json();
|
||||
cachedPatterns = patterns || {};
|
||||
const entries = Object.keys(cachedPatterns);
|
||||
const desiredPattern = presetPatternInput.value;
|
||||
presetPatternInput.innerHTML = '<option value="">Pattern</option>';
|
||||
entries.forEach((patternName) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = patternName;
|
||||
option.textContent = patternName;
|
||||
presetPatternInput.appendChild(option);
|
||||
});
|
||||
if (desiredPattern && cachedPatterns[desiredPattern]) {
|
||||
presetPatternInput.value = desiredPattern;
|
||||
} else if (entries.length > 0) {
|
||||
let defaultPattern = entries[0];
|
||||
for (const patternName of entries) {
|
||||
const config = cachedPatterns[patternName];
|
||||
const hasMapping = config && Object.values(config).some((value) => {
|
||||
return typeof value === 'string' && value.startsWith('n');
|
||||
});
|
||||
if (hasMapping) {
|
||||
defaultPattern = patternName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
presetPatternInput.value = defaultPattern;
|
||||
}
|
||||
updatePresetNLabels(presetPatternInput.value);
|
||||
} catch (error) {
|
||||
console.warn('Failed to load patterns:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const updatePresetNLabels = (patternName) => {
|
||||
const labels = {};
|
||||
for (let i = 1; i <= 8; i++) {
|
||||
labels[`n${i}`] = `n${i}:`;
|
||||
}
|
||||
const patternConfig = cachedPatterns && cachedPatterns[patternName];
|
||||
if (patternConfig && typeof patternConfig === 'object') {
|
||||
Object.entries(patternConfig).forEach(([label, key]) => {
|
||||
if (typeof key === 'string' && key.startsWith('n')) {
|
||||
labels[key] = `${label}:`;
|
||||
}
|
||||
});
|
||||
}
|
||||
for (let i = 1; i <= 8; i++) {
|
||||
const labelEl = document.getElementById(`preset-n${i}-label`);
|
||||
if (labelEl) {
|
||||
labelEl.textContent = labels[`n${i}`];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderPresets = (presets) => {
|
||||
presetsList.innerHTML = '';
|
||||
cachedPresets = presets || {};
|
||||
const entries = Object.entries(cachedPresets);
|
||||
if (!entries.length) {
|
||||
const empty = document.createElement('p');
|
||||
empty.className = 'muted-text';
|
||||
empty.textContent = 'No presets found.';
|
||||
presetsList.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
entries.forEach(([presetId, preset]) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'profiles-row';
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.textContent = (preset && preset.name) || presetId;
|
||||
|
||||
const details = document.createElement('span');
|
||||
const pattern = preset && preset.pattern ? preset.pattern : '-';
|
||||
details.textContent = pattern;
|
||||
details.style.color = '#aaa';
|
||||
details.style.fontSize = '0.85em';
|
||||
|
||||
const editButton = document.createElement('button');
|
||||
editButton.className = 'btn btn-secondary btn-small';
|
||||
editButton.textContent = 'Edit';
|
||||
editButton.addEventListener('click', () => {
|
||||
currentEditId = presetId;
|
||||
setFormValues(preset || {});
|
||||
openEditor();
|
||||
});
|
||||
|
||||
const deleteButton = document.createElement('button');
|
||||
deleteButton.className = 'btn btn-danger btn-small';
|
||||
deleteButton.textContent = 'Delete';
|
||||
deleteButton.addEventListener('click', async () => {
|
||||
const confirmed = confirm(`Delete preset "${label.textContent}"?`);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/presets/${presetId}`, {
|
||||
method: 'DELETE',
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete preset');
|
||||
}
|
||||
await loadPresets();
|
||||
if (currentEditId === presetId) {
|
||||
clearForm();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Delete preset failed:', error);
|
||||
alert('Failed to delete preset.');
|
||||
}
|
||||
});
|
||||
|
||||
row.appendChild(label);
|
||||
row.appendChild(details);
|
||||
row.appendChild(editButton);
|
||||
row.appendChild(deleteButton);
|
||||
presetsList.appendChild(row);
|
||||
});
|
||||
};
|
||||
|
||||
const loadPresets = async () => {
|
||||
presetsList.innerHTML = '';
|
||||
const loading = document.createElement('p');
|
||||
loading.className = 'muted-text';
|
||||
loading.textContent = 'Loading presets...';
|
||||
presetsList.appendChild(loading);
|
||||
|
||||
try {
|
||||
const response = await fetch('/presets', {
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load presets');
|
||||
}
|
||||
const presets = await response.json();
|
||||
renderPresets(presets);
|
||||
} catch (error) {
|
||||
console.error('Load presets failed:', error);
|
||||
presetsList.innerHTML = '';
|
||||
const errorMessage = document.createElement('p');
|
||||
errorMessage.className = 'muted-text';
|
||||
errorMessage.textContent = 'Failed to load presets.';
|
||||
presetsList.appendChild(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
presetsModal.classList.add('active');
|
||||
loadPresets();
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
presetsModal.classList.remove('active');
|
||||
};
|
||||
|
||||
presetsButton.addEventListener('click', openModal);
|
||||
if (presetsCloseButton) {
|
||||
presetsCloseButton.addEventListener('click', closeModal);
|
||||
}
|
||||
if (presetsAddButton) {
|
||||
presetsAddButton.addEventListener('click', () => {
|
||||
clearForm();
|
||||
openEditor();
|
||||
});
|
||||
}
|
||||
if (presetEditorCloseButton) {
|
||||
presetEditorCloseButton.addEventListener('click', closeEditor);
|
||||
}
|
||||
presetClearButton.addEventListener('click', clearForm);
|
||||
if (presetPatternInput) {
|
||||
presetPatternInput.addEventListener('change', () => {
|
||||
updatePresetNLabels(presetPatternInput.value);
|
||||
});
|
||||
}
|
||||
if (presetAddFromPaletteButton) {
|
||||
presetAddFromPaletteButton.addEventListener('click', () => {
|
||||
const openButton = document.getElementById('color-palette-btn');
|
||||
if (openButton) {
|
||||
openButton.click();
|
||||
}
|
||||
const modal = document.getElementById('color-palette-modal');
|
||||
const modalList = document.getElementById('palette-container');
|
||||
if (modal) {
|
||||
modal.classList.add('active');
|
||||
}
|
||||
if (!modalList || !presetColorsInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handlePick = (event) => {
|
||||
const row = event.target.closest('[data-color]');
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
const picked = row.dataset.color;
|
||||
if (!picked) {
|
||||
return;
|
||||
}
|
||||
const currentColors = parseColors(presetColorsInput.value);
|
||||
if (!currentColors.includes(picked)) {
|
||||
currentColors.push(picked);
|
||||
presetColorsInput.value = currentColors.join(',');
|
||||
}
|
||||
if (modal) {
|
||||
modal.classList.remove('active');
|
||||
}
|
||||
modalList.removeEventListener('click', handlePick);
|
||||
};
|
||||
|
||||
modalList.addEventListener('click', handlePick);
|
||||
});
|
||||
}
|
||||
presetSaveButton.addEventListener('click', async () => {
|
||||
const payload = buildPresetPayload();
|
||||
if (!payload.name) {
|
||||
alert('Preset name is required.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const url = currentEditId ? `/presets/${currentEditId}` : '/presets';
|
||||
const method = currentEditId ? 'PUT' : 'POST';
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to save preset');
|
||||
}
|
||||
await loadPresets();
|
||||
clearForm();
|
||||
closeEditor();
|
||||
} catch (error) {
|
||||
console.error('Save preset failed:', error);
|
||||
alert('Failed to save preset.');
|
||||
}
|
||||
});
|
||||
|
||||
presetsModal.addEventListener('click', (event) => {
|
||||
if (event.target === presetsModal) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
if (presetEditorModal) {
|
||||
presetEditorModal.addEventListener('click', (event) => {
|
||||
if (event.target === presetEditorModal) {
|
||||
closeEditor();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clearForm();
|
||||
});
|
||||
Reference in New Issue
Block a user