feat(ui): patterns list and create form layout

Made-with: Cursor
This commit is contained in:
pi
2026-04-12 00:13:58 +12:00
parent 7bdb324ebc
commit fbd4295302
3 changed files with 64 additions and 43 deletions

View File

@@ -55,8 +55,8 @@ document.addEventListener('DOMContentLoaded', () => {
if (mode === 'create') {
if (labelEl) {
labelEl.textContent = '';
labelEl.style.display = 'none';
labelEl.textContent = `${key}:`;
labelEl.style.display = '';
}
if (inputEl) {
inputEl.value = '';
@@ -203,6 +203,17 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
/** on/off are implemented in driver firmware (presets.py), not as OTA ``.py`` files. */
const FIRMWARE_BUILTIN_PATTERNS = new Set(['on', 'off']);
const isFirmwareBuiltinPattern = (patternName) => {
const id = String(patternName || '')
.trim()
.replace(/\.py$/i, '')
.toLowerCase();
return FIRMWARE_BUILTIN_PATTERNS.has(id);
};
const sendPatternToDevices = async (patternName) => {
const response = await fetch(`/patterns/${encodeURIComponent(patternName)}/send`, {
method: 'POST',
@@ -281,7 +292,9 @@ document.addEventListener('DOMContentLoaded', () => {
}
try {
const response = await fetch(`/patterns/ota/file/${encodeURIComponent(patternName)}.py`, {
const raw = String(patternName || '').trim();
const fileSegment = /\.py$/i.test(raw) ? raw : `${raw}.py`;
const response = await fetch(`/patterns/ota/file/${encodeURIComponent(fileSegment)}`, {
headers: { Accept: 'text/plain' },
});
if (!response.ok) {
@@ -315,39 +328,41 @@ document.addEventListener('DOMContentLoaded', () => {
const label = document.createElement('span');
label.textContent = patternName;
const details = document.createElement('span');
const minDelay = data && data.min_delay !== undefined ? data.min_delay : '-';
const maxDelay = data && data.max_delay !== undefined ? data.max_delay : '-';
details.textContent = `${minDelay}${maxDelay} ms`;
details.style.color = '#aaa';
details.style.fontSize = '0.85em';
const sendBtn = document.createElement('button');
sendBtn.className = 'btn btn-primary btn-small';
sendBtn.textContent = 'Send';
sendBtn.addEventListener('click', async () => {
try {
await sendPatternToDevices(patternName);
} catch (error) {
console.error('Send pattern failed:', error);
alert(error.message || 'Failed to send pattern.');
}
});
const editBtn = document.createElement('button');
editBtn.className = 'btn btn-secondary btn-small';
editBtn.textContent = 'Edit';
editBtn.addEventListener('click', async () => {
if (patternEditorModal) {
patternEditorModal.classList.add('active');
}
await loadPatternIntoEditor(patternName, data || {});
});
row.appendChild(label);
row.appendChild(details);
row.appendChild(editBtn);
row.appendChild(sendBtn);
if (isFirmwareBuiltinPattern(patternName)) {
const note = document.createElement('span');
note.className = 'muted-text';
note.style.fontSize = '0.85em';
note.textContent = 'Built-in (no OTA module)';
row.appendChild(note);
} else {
const sendBtn = document.createElement('button');
sendBtn.className = 'btn btn-primary btn-small';
sendBtn.textContent = 'Send';
sendBtn.addEventListener('click', async () => {
try {
await sendPatternToDevices(patternName);
} catch (error) {
console.error('Send pattern failed:', error);
alert(error.message || 'Failed to send pattern.');
}
});
const editBtn = document.createElement('button');
editBtn.className = 'btn btn-secondary btn-small';
editBtn.textContent = 'Edit';
editBtn.addEventListener('click', async () => {
if (patternEditorModal) {
patternEditorModal.classList.add('active');
}
await loadPatternIntoEditor(patternName, data || {});
});
row.appendChild(editBtn);
row.appendChild(sendBtn);
}
patternsList.appendChild(row);
});
};