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:
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user