From dbf89b12af7b2d82f1d4aa4ce3ea279640666e27 Mon Sep 17 00:00:00 2001 From: you Date: Sat, 2 May 2026 18:27:04 +0000 Subject: [PATCH] feat(packets): clear-filters button (#964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- public/packets.js | 58 +++++++++++++++++++++++++++++++++++++++++++ test-clear-filters.js | 42 +++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 test-clear-filters.js diff --git a/public/packets.js b/public/packets.js index bbca46ec..20013458 100644 --- a/public/packets.js +++ b/public/packets.js @@ -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 @@
+
@@ -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)); diff --git a/test-clear-filters.js b/test-clear-filters.js new file mode 100644 index 00000000..906f3562 --- /dev/null +++ b/test-clear-filters.js @@ -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 ✅');