feat(ui): refresh layout, help assets, and panel styling
Update the main template and client scripts for the revised navigation and zone/device panels, and add bundled help SVG assets under static. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -510,13 +510,6 @@ async function addSequenceToTab(sequenceId, zoneId) {
|
||||
const tabResponse = await fetch(`/zones/${zoneId}`, { headers: { Accept: 'application/json' } });
|
||||
if (!tabResponse.ok) throw new Error('Failed to load zone');
|
||||
const tabData = await tabResponse.json();
|
||||
if (
|
||||
typeof window.zoneAllowsSequences === 'function' &&
|
||||
!window.zoneAllowsSequences(tabData, zoneId)
|
||||
) {
|
||||
alert('This zone is for presets only. Add presets from the zone Edit menu instead.');
|
||||
return;
|
||||
}
|
||||
const list = Array.isArray(tabData.sequence_ids) ? tabData.sequence_ids.map(String) : [];
|
||||
if (list.includes(String(sequenceId))) {
|
||||
alert('Sequence is already on this zone.');
|
||||
@@ -579,15 +572,6 @@ async function refreshEditTabSequencesUi(zoneId) {
|
||||
const zoneRes = await fetch(`/zones/${zoneId}`, { headers: { Accept: 'application/json' } });
|
||||
if (!zoneRes.ok) throw new Error('zone');
|
||||
const zone = await zoneRes.json();
|
||||
if (
|
||||
typeof window.zoneAllowsSequences === 'function' &&
|
||||
!window.zoneAllowsSequences(zone, zoneId)
|
||||
) {
|
||||
currentEl.innerHTML =
|
||||
'<span class="muted-text">This zone is for presets only. Sequences are hidden.</span>';
|
||||
addEl.innerHTML = '<span class="muted-text">—</span>';
|
||||
return;
|
||||
}
|
||||
const onZone = Array.isArray(zone.sequence_ids) ? zone.sequence_ids.map(String) : [];
|
||||
const seqMap = await fetchSequencesMap();
|
||||
const onSet = new Set(onZone);
|
||||
@@ -600,11 +584,7 @@ async function refreshEditTabSequencesUi(zoneId) {
|
||||
const sdoc = seqMap[sid] || {};
|
||||
const name = sdoc.name || sid;
|
||||
const row = document.createElement('div');
|
||||
row.className = 'profiles-row';
|
||||
row.style.display = 'flex';
|
||||
row.style.justifyContent = 'space-between';
|
||||
row.style.alignItems = 'center';
|
||||
row.style.gap = '0.5rem';
|
||||
row.className = 'profiles-row edit-zone-item-row';
|
||||
const span = document.createElement('span');
|
||||
span.textContent = `${name} — ${sid}`;
|
||||
const rm = document.createElement('button');
|
||||
@@ -1081,9 +1061,16 @@ async function saveSequenceEditor() {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error((err && err.error) || res.statusText);
|
||||
}
|
||||
const created = await res.json().catch(() => null);
|
||||
if (created && typeof created === 'object') {
|
||||
const entries = Object.entries(created);
|
||||
if (entries.length > 0) {
|
||||
sequenceEditorId = String(entries[0][0]);
|
||||
const edDel = document.getElementById('sequence-editor-delete-btn');
|
||||
if (edDel) edDel.style.display = 'inline-block';
|
||||
}
|
||||
}
|
||||
}
|
||||
document.getElementById('sequence-editor-modal') && document.getElementById('sequence-editor-modal').classList.remove('active');
|
||||
stopSequenceEditorBpmPoll();
|
||||
await loadSequencesModalList();
|
||||
const zid = resolveZoneIdForPresetStripRefresh();
|
||||
if (zid && typeof window.refreshEditTabSequencesUi === 'function') {
|
||||
@@ -1164,31 +1151,12 @@ async function loadSequencesModalList() {
|
||||
const nSteps = ln.reduce((a, l) => a + l.length, 0);
|
||||
const nLanes = ln.filter((l) => l.length > 0).length || 1;
|
||||
title.textContent = `${doc.name || id} — ${nLanes} lane(s), ${nSteps} step(s)`;
|
||||
const exportBtn = document.createElement('button');
|
||||
exportBtn.type = 'button';
|
||||
exportBtn.className = 'btn btn-secondary btn-small';
|
||||
exportBtn.textContent = 'Export';
|
||||
exportBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
const response = await fetch(`/sequences/${id}/export`, {
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
if (!response.ok) throw new Error('Export failed');
|
||||
const bundle = await response.json();
|
||||
const safeName = String(doc.name || id).replace(/[^\w.-]+/g, '_');
|
||||
window.downloadJsonFile(`sequence-${safeName}.json`, bundle);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Failed to export sequence.');
|
||||
}
|
||||
});
|
||||
const edit = document.createElement('button');
|
||||
edit.type = 'button';
|
||||
edit.className = 'btn btn-secondary btn-small';
|
||||
edit.textContent = 'Edit';
|
||||
edit.addEventListener('click', () => openSequenceEditor(id, doc));
|
||||
row.appendChild(title);
|
||||
row.appendChild(exportBtn);
|
||||
row.appendChild(edit);
|
||||
listEl.appendChild(row);
|
||||
});
|
||||
@@ -1227,33 +1195,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
openSequenceEditor(null, null);
|
||||
});
|
||||
}
|
||||
const importSeqBtn = document.getElementById('import-sequence-btn');
|
||||
if (importSeqBtn) {
|
||||
importSeqBtn.addEventListener('click', async () => {
|
||||
const text = await window.pickJsonFile();
|
||||
if (!text) return;
|
||||
const bundle = window.parseJsonFileText(text);
|
||||
if (!bundle || bundle.kind !== 'sequence') {
|
||||
alert('Invalid sequence bundle file.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/sequences/import', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body: JSON.stringify({ bundle }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
throw new Error(err.error || 'Import failed');
|
||||
}
|
||||
await loadSequencesModalList();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert(e.message || 'Failed to import sequence.');
|
||||
}
|
||||
});
|
||||
}
|
||||
const openPresetsFromSeq = document.getElementById('sequences-open-presets-btn');
|
||||
if (openPresetsFromSeq) {
|
||||
openPresetsFromSeq.addEventListener('click', () => {
|
||||
|
||||
Reference in New Issue
Block a user