refactor: independent per-day heading + table, no nested mega-table

Each day is now a div.day-section with a heading bar and its own
independent <table>, rather than rows inside a single monolithic table.
Toggle shows/hides the per-day table element directly. Highlights panel
is a div between the heading and table, not a table row.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 20:26:30 +02:00
parent f7e7d5bfaa
commit 1e6ad2f3de
+56 -46
View File
@@ -677,18 +677,21 @@ button.cut:hover:not(:disabled){background:#1e3a8a}
.filter-bar input[type=date]{background:var(--bg);border:1px solid var(--brd); .filter-bar input[type=date]{background:var(--bg);border:1px solid var(--brd);
color:var(--txt);padding:3px 6px;border-radius:4px;font-size:13px;color-scheme:dark} color:var(--txt);padding:3px 6px;border-radius:4px;font-size:13px;color-scheme:dark}
.filter-bar input:focus{outline:2px solid var(--accent);outline-offset:1px} .filter-bar input:focus{outline:2px solid var(--accent);outline-offset:1px}
/* day groups */ /* day sections */
tr.day-head td{background:var(--surf);padding:7px 10px;border-bottom:2px solid var(--brd);border-top:2px solid var(--brd)} .day-section{margin-bottom:18px}
.day-heading-bar{background:var(--surf);padding:7px 10px;border:1px solid var(--brd);
border-radius:6px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.day-heading-bar.open{border-radius:6px 6px 0 0}
.day-toggle{background:none;border:none;color:var(--txt);font-size:13px;font-weight:600; .day-toggle{background:none;border:none;color:var(--txt);font-size:13px;font-weight:600;
cursor:pointer;padding:2px 0;display:inline-flex;align-items:center;gap:8px} cursor:pointer;padding:2px 0;display:inline-flex;align-items:center;gap:8px;flex:1 1 auto}
.day-toggle:hover{color:var(--accent)} .day-toggle:hover{color:var(--accent)}
.day-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:2px} .day-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:2px}
.day-meta{color:var(--muted);font-size:12px;font-weight:400} .day-meta{color:var(--muted);font-size:12px;font-weight:400}
button.day-hl{color:var(--green);border-color:#166534;background:#052e16;font-size:11px; button.day-hl{color:var(--green);border-color:#166534;background:#052e16;font-size:11px}
margin-left:14px;vertical-align:middle}
button.day-hl:hover:not(:disabled){background:#0a3d1f} button.day-hl:hover:not(:disabled){background:#0a3d1f}
button.day-hl:disabled{opacity:.5;cursor:default} button.day-hl:disabled{opacity:.5;cursor:default}
tr.day-hl-row td{background:var(--bg);padding:8px 12px 12px} .day-hl-container{background:var(--bg);border:1px solid var(--brd);border-top:none;padding:8px 12px 12px}
table.day-table{width:100%;border-collapse:collapse;border:1px solid var(--brd);border-top:none}
svg.day-timeline{display:block;width:100%;height:22px} svg.day-timeline{display:block;width:100%;height:22px}
.day-tl-labels{display:flex;justify-content:space-between;font-size:10px; .day-tl-labels{display:flex;justify-content:space-between;font-size:10px;
color:var(--muted);font-family:ui-monospace,monospace;margin-top:2px} color:var(--muted);font-family:ui-monospace,monospace;margin-top:2px}
@@ -727,19 +730,7 @@ svg.day-timeline{display:block;width:100%;height:22px}
<button id="filter-clear" aria-label="Clear all filters">✕ Clear</button> <button id="filter-clear" aria-label="Clear all filters">✕ Clear</button>
</div> </div>
<div class="wrap" id="main"> <div class="wrap" id="main">
<table aria-label="Recordings archive"> <div id="tbody" role="region" aria-label="Recordings archive"></div>
<thead>
<tr>
<th scope="col">File</th>
<th scope="col">Date</th>
<th scope="col">Duration</th>
<th scope="col">Size</th>
<th scope="col">Loudness</th>
<th scope="col"><span class="sr">Actions</span></th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<div id="empty" class="empty" style="display:none" role="status">No recordings found.</div> <div id="empty" class="empty" style="display:none" role="status">No recordings found.</div>
</div> </div>
<script> <script>
@@ -1084,8 +1075,8 @@ function _attachFileRowHandlers(f, isRec, expanded, dayId) {
} }
function renderFiles(files) { function renderFiles(files) {
const tbody = document.getElementById('tbody'); const container = document.getElementById('tbody');
tbody.innerHTML = ''; container.innerHTML = '';
recMap.clear(); recMap.clear();
sectionMap.clear(); sectionMap.clear();
@@ -1111,31 +1102,52 @@ function renderFiles(files) {
const sizeStr = ' · ' + fmtSize(totalSize); const sizeStr = ' · ' + fmtSize(totalSize);
const fileStr = `${dayFiles.length} file${dayFiles.length !== 1 ? 's' : ''}`; const fileStr = `${dayFiles.length} file${dayFiles.length !== 1 ? 's' : ''}`;
// Day heading row // Day section wrapper
const headRow = document.createElement('tr'); const section = document.createElement('div');
headRow.className = 'day-head'; section.className = 'day-section';
headRow.id = 'dayhead-' + dayId; section.id = 'daysec-' + dayId;
headRow.innerHTML = `<td colspan="6">
// Heading bar
const headBar = document.createElement('div');
headBar.className = 'day-heading-bar' + (expanded ? ' open' : '');
headBar.id = 'dayhead-' + dayId;
headBar.innerHTML = `
<button class="day-toggle" id="daytgl-${dayId}" <button class="day-toggle" id="daytgl-${dayId}"
aria-expanded="${expanded}" aria-expanded="${expanded}"
aria-controls="${dayId}-body" aria-controls="daytbl-${dayId}"
aria-label="${expanded ? 'Collapse' : 'Expand'} ${esc(day)}"> aria-label="${expanded ? 'Collapse' : 'Expand'} ${esc(day)}">
<span class="day-arrow" aria-hidden="true">${expanded ? '' : ''}</span> <span class="day-arrow" aria-hidden="true">${expanded ? '' : ''}</span>
<strong>${esc(day)}</strong> <strong>${esc(day)}</strong>
<span class="day-meta">${fileStr}${durStr}${sizeStr}</span> <span class="day-meta">${fileStr}${durStr}${sizeStr}</span>
</button> </button>
${canHl ? `<button class="day-hl" id="dayhln-${dayId}" ${canHl ? `<button class="day-hl" id="dayhln-${dayId}"
aria-label="Show day highlights for ${esc(day)}">★ Highlights</button>` : ''} aria-label="Show day highlights for ${esc(day)}">★ Highlights</button>` : ''}`;
</td>`; section.appendChild(headBar);
tbody.appendChild(headRow);
// Highlights row (hidden until button clicked) // Highlights panel (hidden until button clicked)
const hlRow = document.createElement('tr'); const hlDiv = document.createElement('div');
hlRow.className = 'day-hl-row'; hlDiv.className = 'day-hl-container';
hlRow.id = 'dayhl-' + dayId; hlDiv.id = 'dayhl-' + dayId;
hlRow.hidden = true; hlDiv.hidden = true;
hlRow.innerHTML = `<td colspan="6"><div id="dayhlc-${dayId}"></div></td>`; hlDiv.innerHTML = `<div id="dayhlc-${dayId}"></div>`;
tbody.appendChild(hlRow); section.appendChild(hlDiv);
// Per-day table
const table = document.createElement('table');
table.className = 'day-table';
table.id = 'daytbl-' + dayId;
table.setAttribute('aria-label', `Recordings for ${day}`);
if (!expanded) table.hidden = true;
table.innerHTML = `<thead><tr>
<th scope="col">File</th>
<th scope="col">Date</th>
<th scope="col">Duration</th>
<th scope="col">Size</th>
<th scope="col">Loudness</th>
<th scope="col"><span class="sr">Actions</span></th>
</tr></thead>`;
const dayTbody = document.createElement('tbody');
table.appendChild(dayTbody);
// File rows // File rows
dayFiles.forEach(f => { dayFiles.forEach(f => {
@@ -1147,8 +1159,6 @@ function renderFiles(files) {
const tr = document.createElement('tr'); const tr = document.createElement('tr');
tr.className = 'data-row'; tr.className = 'data-row';
tr.id = 'row-'+i; tr.id = 'row-'+i;
tr.setAttribute('data-day', dayId);
if (!expanded) tr.hidden = true;
const recBadge = `<span id="rec-${i}" class="badge-rec"${isRec?'':' hidden'} const recBadge = `<span id="rec-${i}" class="badge-rec"${isRec?'':' hidden'}
aria-label="Currently recording" aria-hidden="${isRec?'false':'true'}"> aria-label="Currently recording" aria-hidden="${isRec?'false':'true'}">
@@ -1179,13 +1189,12 @@ function renderFiles(files) {
${isRec ? 'disabled title="Cannot delete while recording"' : ''}>✕ Delete</button> ${isRec ? 'disabled title="Cannot delete while recording"' : ''}>✕ Delete</button>
</div> </div>
</td>`; </td>`;
tbody.appendChild(tr); dayTbody.appendChild(tr);
const prow = document.createElement('tr'); const prow = document.createElement('tr');
prow.className = 'player-row'; prow.className = 'player-row';
prow.id = 'prow-'+i; prow.id = 'prow-'+i;
prow.hidden = true; prow.hidden = true;
prow.setAttribute('data-day', dayId);
const durLabel = f.duration != null const durLabel = f.duration != null
? `<div class="muted" style="font-size:11px;margin-top:3px">Duration: ${fmtDur(f.duration)}</div>` ? `<div class="muted" style="font-size:11px;margin-top:3px">Duration: ${fmtDur(f.duration)}</div>`
: ''; : '';
@@ -1205,11 +1214,14 @@ function renderFiles(files) {
aria-label="Download cut of ${esc(f.name)}">↓ Download cut</button> aria-label="Download cut of ${esc(f.name)}">↓ Download cut</button>
</div> </div>
</td>`; </td>`;
tbody.appendChild(prow); dayTbody.appendChild(prow);
_attachFileRowHandlers(f, isRec, expanded, dayId); _attachFileRowHandlers(f, isRec, expanded, dayId);
}); });
section.appendChild(table);
container.appendChild(section);
// Day toggle handler // Day toggle handler
document.getElementById('daytgl-' + dayId).addEventListener('click', () => { document.getElementById('daytgl-' + dayId).addEventListener('click', () => {
const nowExp = !dayExpanded.get(dayId); const nowExp = !dayExpanded.get(dayId);
@@ -1218,11 +1230,9 @@ function renderFiles(files) {
tgl.setAttribute('aria-expanded', nowExp); tgl.setAttribute('aria-expanded', nowExp);
tgl.setAttribute('aria-label', (nowExp ? 'Collapse' : 'Expand') + ' ' + day); tgl.setAttribute('aria-label', (nowExp ? 'Collapse' : 'Expand') + ' ' + day);
tgl.querySelector('.day-arrow').textContent = nowExp ? '' : ''; tgl.querySelector('.day-arrow').textContent = nowExp ? '' : '';
document.querySelectorAll(`tr[data-day="${dayId}"].data-row`).forEach(r => { headBar.classList.toggle('open', nowExp);
r.hidden = !nowExp; document.getElementById('daytbl-' + dayId).hidden = !nowExp;
});
if (!nowExp) { if (!nowExp) {
// Collapse: pause any open players, hide player rows and highlights
dayFiles.forEach(f => { dayFiles.forEach(f => {
const prow = document.getElementById('prow-' + f._idx); const prow = document.getElementById('prow-' + f._idx);
if (prow && !prow.hidden) { if (prow && !prow.hidden) {