Compare commits

...

1 Commits

Author SHA1 Message Date
you dbf89b12af feat(packets): clear-filters button (#964)
Add a "✕ Clear" button in the packets filter bar that appears when any
filter is active. Clicking it resets all filters (hash, node, observer,
type, channel, wireshark expression, region, My Nodes), clears
localStorage filter entries, resets all DOM inputs/multi-selects, updates
the URL hash to plain #/packets, and triggers a table re-render.

The button visibility is managed by updatePacketsUrl() which runs after
every filter change, so it automatically appears/disappears as filters
are toggled.

Closes #964
2026-05-02 18:27:04 +00:00
2 changed files with 100 additions and 0 deletions
+58
View File
@@ -59,6 +59,12 @@
function updatePacketsUrl() {
history.replaceState(null, '', '#/packets' + buildPacketsQuery(savedTimeWindowMin, RegionFilter.getRegionParam()));
// Update clear-filters button visibility
var cb = document.getElementById('clearFiltersBtn');
if (cb) {
var active = !!(filters.hash || filters.node || filters.observer || filters.channel || filters.type || filters._filterExpr || filters.myNodes) || !!RegionFilter.getRegionParam();
cb.style.display = active ? '' : 'none';
}
}
let filtersBuilt = false;
@@ -785,6 +791,7 @@
</div>
<div class="filter-bar" id="pktFilters">
<button class="btn filter-toggle-btn" id="filterToggleBtn">Filters ▾</button>
<button class="btn btn-clear-filters" id="clearFiltersBtn" title="Clear all filters" style="display:none;font-size:12px;padding:2px 8px;color:var(--text-muted);border:1px solid var(--border);border-radius:4px;background:transparent;cursor:pointer">✕ Clear</button>
<div class="filter-group">
<input type="text" placeholder="Packet hash…" id="fHash" aria-label="Filter by packet hash" title="Filter packets by hex hash prefix">
<div class="node-filter-wrap" style="position:relative">
@@ -1065,6 +1072,57 @@
this.textContent = bar.classList.contains('filters-expanded') ? 'Filters ▴' : 'Filters ▾';
});
// --- Clear filters button ---
const clearBtn = document.getElementById('clearFiltersBtn');
if (clearBtn) clearBtn.addEventListener('click', function() {
// Reset filters object
filters.hash = undefined;
filters.node = undefined;
filters.nodeName = undefined;
filters.observer = undefined;
filters.channel = undefined;
filters.type = undefined;
filters._filterExpr = undefined;
filters._packetFilter = null;
filters.myNodes = false;
_observerFilterSet = null;
// Clear localStorage filter entries
localStorage.removeItem('meshcore-observer-filter');
localStorage.removeItem('meshcore-type-filter');
// Reset DOM inputs
document.getElementById('fHash').value = '';
document.getElementById('fNode').value = '';
var pfInput = document.getElementById('packetFilterInput');
if (pfInput) { pfInput.value = ''; pfInput.classList.remove('filter-active', 'filter-error'); }
var pfError = document.getElementById('packetFilterError');
if (pfError) pfError.style.display = 'none';
var pfCount = document.getElementById('packetFilterCount');
if (pfCount) pfCount.style.display = 'none';
document.getElementById('fChannel').value = '';
document.getElementById('fMyNodes').classList.remove('active');
// Reset observer multi-select
var obMenu = document.getElementById('observerMenu');
if (obMenu) obMenu.querySelectorAll('input[type=checkbox]').forEach(function(cb) { cb.checked = false; });
document.getElementById('observerTrigger').textContent = 'All Observers ▾';
// Reset type multi-select
var typeMenu = document.getElementById('typeMenu');
if (typeMenu) typeMenu.querySelectorAll('input[type=checkbox]').forEach(function(cb) { cb.checked = false; });
document.getElementById('typeTrigger').textContent = 'All Types ▾';
// Reset region filter
RegionFilter.setSelected([]);
// Update URL and reload
updatePacketsUrl();
loadPackets();
});
// Show clear button if page loaded with active filters (e.g. from URL params)
updatePacketsUrl();
// Filter event listeners
document.getElementById('fHash').value = filters.hash || '';
document.getElementById('fHash').addEventListener('input', debounce((e) => { filters.hash = e.target.value || undefined; updatePacketsUrl(); loadPackets(); }, 300));
+42
View File
@@ -0,0 +1,42 @@
/* test-clear-filters.js — unit test for clear-filters button (#964) */
'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
console.log('--- test-clear-filters.js ---');
const src = fs.readFileSync(path.join(__dirname, 'public', 'packets.js'), 'utf-8');
// Test 1: button HTML present
assert(src.includes('clearFiltersBtn'), 'clearFiltersBtn ID should exist');
assert(src.includes('✕ Clear'), 'Clear button label should exist');
// Test 2: clear handler resets all filter keys
for (const k of ['hash', 'node', 'observer', 'channel', 'type', '_filterExpr']) {
assert(src.includes(`filters.${k} = undefined`), `should reset filters.${k}`);
}
assert(src.includes('filters._packetFilter = null'), 'should reset _packetFilter');
assert(src.includes('filters.myNodes = false'), 'should reset myNodes');
// Test 3: clears localStorage
assert(src.includes("localStorage.removeItem('meshcore-observer-filter')"), 'clear observer localStorage');
assert(src.includes("localStorage.removeItem('meshcore-type-filter')"), 'clear type localStorage');
// Test 4: resets RegionFilter
assert(src.includes('RegionFilter.setSelected([])'), 'reset RegionFilter');
// Test 5: visibility toggle in updatePacketsUrl
assert(src.includes("cb.style.display = active ?"), 'toggle clear btn visibility');
// Test 6: resets DOM inputs
assert(src.includes("getElementById('fHash').value = ''"), 'clear hash input');
assert(src.includes("getElementById('fNode').value = ''"), 'clear node input');
assert(src.includes("getElementById('fChannel').value = ''"), 'clear channel select');
// Test 7: resets multi-select checkboxes
assert(src.includes('observerMenu'), 'clear observer checkboxes');
assert(src.includes('typeMenu'), 'clear type checkboxes');
console.log('All 7 tests passed ✅');