diff --git a/web.py b/web.py
index 0594e0e..0415f49 100644
--- a/web.py
+++ b/web.py
@@ -564,6 +564,15 @@ button.chip:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.player-row td{padding:0 10px 10px;background:var(--bg);border-bottom:1px solid var(--brd)}
audio{width:100%;height:36px;border-radius:4px;display:block;
color-scheme:dark;accent-color:var(--accent)}
+/* filter bar */
+.filter-bar{display:flex;align-items:center;gap:10px;padding:8px 28px;
+ border-bottom:1px solid var(--brd);background:var(--surf);flex-wrap:wrap}
+.filter-bar label{font-size:13px;color:var(--muted);white-space:nowrap}
+.filter-bar input[type=text]{width:180px;background:var(--bg);border:1px solid var(--brd);
+ color:var(--txt);padding:3px 6px;border-radius:4px;font-size:13px}
+.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}
+.filter-bar input:focus{outline:2px solid var(--accent);outline-offset:1px}
@@ -580,6 +589,15 @@ audio{width:100%;height:36px;border-radius:4px;display:block;
aria-describedby="threshold-hint">
RMS 0–1 · sections above this value are marked loud
+
+
+
+
+
+
+
+
+
@@ -623,6 +641,8 @@ const recMap = new Map();
// idx -> [{start,end}], populated after analysis
const sectionMap = new Map();
let activePlayerIdx = null;
+// full file list from server, annotated with stable _idx
+let allFiles = [];
function togglePlayer(idx, filename) {
const prow = document.getElementById('prow-'+idx);
@@ -780,10 +800,13 @@ async function deleteFile(idx, filename) {
document.getElementById('row-'+idx)?.remove();
document.getElementById('prow-'+idx)?.remove();
recMap.delete(idx);
- const remaining = document.querySelectorAll('tr.data-row').length;
- document.getElementById('subtitle').textContent =
- `${remaining} recording${remaining!==1?'s':''} found`;
- if (!remaining) document.getElementById('empty').style.display = '';
+ allFiles = allFiles.filter(f => f._idx !== idx);
+ const visible = document.querySelectorAll('tr.data-row').length;
+ const total = allFiles.length;
+ document.getElementById('subtitle').textContent = total === visible
+ ? `${total} recording${total!==1?'s':''} found`
+ : `${visible} of ${total} recording${total!==1?'s':''} shown`;
+ if (!visible) document.getElementById('empty').style.display = '';
updateStorage();
} else {
const d = await r.json().catch(()=>({}));
@@ -807,32 +830,21 @@ async function updateStorage() {
} catch(e) {}
}
-async function load() {
- const refreshBtn = document.getElementById('refresh-btn');
- refreshBtn.disabled = true;
- document.getElementById('subtitle').textContent = 'Loading…';
- recMap.clear();
-
- let files;
- try {
- files = await (await fetch('/api/files')).json();
- } catch(e) {
- document.getElementById('subtitle').textContent = 'Error loading files';
- refreshBtn.disabled = false;
- return;
- }
-
+function renderFiles(files) {
const tbody = document.getElementById('tbody');
tbody.innerHTML = '';
+ recMap.clear();
+ sectionMap.clear();
- const n = files.length;
- document.getElementById('subtitle').textContent =
- `${n} recording${n!==1?'s':''} found`;
- document.getElementById('empty').style.display = n ? 'none' : '';
- updateStorage();
- if (!n) { refreshBtn.disabled = false; return; }
+ const total = allFiles.length;
+ const visible = files.length;
+ document.getElementById('subtitle').textContent = total === visible
+ ? `${total} recording${total!==1?'s':''} found`
+ : `${visible} of ${total} recording${total!==1?'s':''} shown`;
+ document.getElementById('empty').style.display = visible ? 'none' : '';
- files.forEach((f, i) => {
+ files.forEach(f => {
+ const i = f._idx;
const ext = f.ext;
const canAnalyse = ext === 'wav' || ext === 'flac';
const isRec = !!f.recording;
@@ -909,10 +921,40 @@ async function load() {
.addEventListener('click', () => deleteFile(i, f.name));
}
- // ---- register for live-status polling ----
recMap.set(i, f.name);
});
+}
+function applyFilters() {
+ const nameQ = document.getElementById('filter-name').value.toLowerCase().trim();
+ const fromD = document.getElementById('filter-from').value;
+ const toD = document.getElementById('filter-to').value;
+ const filtered = allFiles.filter(f => {
+ if (nameQ && !f.name.toLowerCase().includes(nameQ)) return false;
+ if (fromD && f.date < fromD + ' 00:00:00') return false;
+ if (toD && f.date > toD + ' 23:59:59') return false;
+ return true;
+ });
+ renderFiles(filtered);
+}
+
+async function load() {
+ const refreshBtn = document.getElementById('refresh-btn');
+ refreshBtn.disabled = true;
+ document.getElementById('subtitle').textContent = 'Loading…';
+
+ let files;
+ try {
+ files = await (await fetch('/api/files')).json();
+ } catch(e) {
+ document.getElementById('subtitle').textContent = 'Error loading files';
+ refreshBtn.disabled = false;
+ return;
+ }
+
+ allFiles = files.map((f, i) => ({...f, _idx: i}));
+ updateStorage();
+ applyFilters();
refreshBtn.disabled = false;
}
@@ -933,6 +975,16 @@ async function pollStatus() {
document.getElementById('refresh-btn').addEventListener('click', load);
+document.getElementById('filter-name').addEventListener('input', applyFilters);
+document.getElementById('filter-from').addEventListener('change', applyFilters);
+document.getElementById('filter-to').addEventListener('change', applyFilters);
+document.getElementById('filter-clear').addEventListener('click', () => {
+ document.getElementById('filter-name').value = '';
+ document.getElementById('filter-from').value = '';
+ document.getElementById('filter-to').value = '';
+ applyFilters();
+});
+
// Seed threshold input from server config, then start
fetch('/api/config').then(r => r.json()).then(cfg => {
if (cfg.threshold != null)