From 503cb2661508c56e5fe4afdf7a7a613cb62f75ea Mon Sep 17 00:00:00 2001 From: Sucukdeluxe <259325684+Sucukdeluxe@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:55:26 +0200 Subject: [PATCH] feat: reset all queue filters together Add one contextual reset action that clears the upload sidebar, filename search, host filter, and status filter in a single render while restoring selection and scroll state consistently. --- renderer/app.js | 38 ++++++++++++++++++++++++++++++++++ renderer/i18n.js | 1 + renderer/index.html | 1 + renderer/styles.css | 7 +++++++ tests/i18n.test.js | 1 + tests/startup-renderer.test.js | 1 + tests/ui-smoke.js | 33 +++++++++++++++++++++++++++++ 7 files changed, 82 insertions(+) diff --git a/renderer/app.js b/renderer/app.js index 445ae9b..8d32f90 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -3917,6 +3917,7 @@ function setUploadSidebarFilter(value) { _lastVisibleRange = { start: -1, end: -1 }; const container = document.getElementById('queueContainer'); if (container) container.scrollTop = 0; + syncQueueFilterResetAction(); renderQueueTable(); } @@ -3927,6 +3928,40 @@ function applyQueueDetailFilters() { _queueFilterCache = { filter: '', source: null, result: [] }; _normalizeQueueSelectionToVisible(); _lastVisibleRange = { start: -1, end: -1 }; + syncQueueFilterResetAction(); + renderQueueTable(); +} + +function _hasActiveQueueFilters() { + return uploadSidebarFilter !== 'all' || Boolean(queueSearchQuery || queueHosterFilter || queueStatusFilter); +} + +function syncQueueFilterResetAction() { + const button = document.getElementById('queueFilterResetBtn'); + if (!button) return; + const active = _hasActiveQueueFilters(); + button.hidden = !active; + button.disabled = !active; +} + +function resetQueueFilters() { + uploadSidebarFilter = 'all'; + queueSearchQuery = ''; + queueHosterFilter = ''; + queueStatusFilter = ''; + const search = document.getElementById('queueSearchInput'); + const hoster = document.getElementById('queueHosterFilter'); + const status = document.getElementById('queueStatusFilter'); + if (search) search.value = ''; + if (hoster) hoster.value = ''; + if (status) status.value = ''; + _syncSidebarFilterButtons('[data-upload-sidebar-target]', 'uploadSidebarTarget', 'all'); + _queueFilterCache = { filter: '', source: null, result: [] }; + _normalizeQueueSelectionToVisible(); + _lastVisibleRange = { start: -1, end: -1 }; + const container = document.getElementById('queueContainer'); + if (container) container.scrollTop = 0; + syncQueueFilterResetAction(); renderQueueTable(); } @@ -3938,6 +3973,7 @@ function syncQueueHosterFilterOptions() { select.innerHTML = `${names.map(name => ``).join('')}`; select.value = names.includes(current) ? current : ''; queueHosterFilter = select.value; + syncQueueFilterResetAction(); } function updateUploadSidebarSummary(stats = _computeQueueStats()) { @@ -7237,6 +7273,8 @@ function setupListeners() { document.getElementById('queueSearchInput').addEventListener('input', applyQueueDetailFilters); document.getElementById('queueHosterFilter').addEventListener('change', applyQueueDetailFilters); document.getElementById('queueStatusFilter').addEventListener('change', applyQueueDetailFilters); + document.getElementById('queueFilterResetBtn').addEventListener('click', resetQueueFilters); + syncQueueFilterResetAction(); const historyRetentionPicker = document.getElementById('historyRetentionPicker'); const historyRetentionTrigger = document.getElementById('historyRetentionTrigger'); diff --git a/renderer/i18n.js b/renderer/i18n.js index 7572221..c7f8f50 100644 --- a/renderer/i18n.js +++ b/renderer/i18n.js @@ -107,6 +107,7 @@ ['Eine neue Version ist verfügbar!', 'A new version is available!'], ['Bereit zum Download', 'Ready to download'], ['Abbrechen', 'Cancel'], + ['Filter zurücksetzen', 'Reset filters'], ['Jetzt installieren', 'Install now'], ['Jetzt updaten', 'Install now'], ['Alle Dateien', 'All files'], diff --git a/renderer/index.html b/renderer/index.html index 9cb4396..fc2a9bc 100644 --- a/renderer/index.html +++ b/renderer/index.html @@ -297,6 +297,7 @@ +
diff --git a/renderer/styles.css b/renderer/styles.css index 7eb5686..4278bbd 100644 --- a/renderer/styles.css +++ b/renderer/styles.css @@ -401,6 +401,13 @@ body { background: var(--border); margin: 0 4px; } +.queue-filter-reset { + margin-left: auto; + white-space: nowrap; +} +.queue-filter-reset[hidden] { + display: none; +} .queue-container { flex: 1 1 0; min-height: 0; diff --git a/tests/i18n.test.js b/tests/i18n.test.js index 3c88322..488fb3a 100644 --- a/tests/i18n.test.js +++ b/tests/i18n.test.js @@ -116,6 +116,7 @@ test('rare account, backup, update, and confirmation states translate in both di ['Ein Update wird bereits vorbereitet', 'An update is already being prepared'], ['Die Anwendung ist noch nicht bereit, das Update sicher zu installieren', 'The application is not yet ready to install the update safely'], ['Update abgebrochen', 'Update canceled'], + ['Filter zurücksetzen', 'Reset filters'], ['Download abbrechen', 'Cancel download'], ['Abbrechen…', 'Cancelling…'], ['Download abgebrochen', 'Download canceled'], diff --git a/tests/startup-renderer.test.js b/tests/startup-renderer.test.js index 4d930e3..3426693 100644 --- a/tests/startup-renderer.test.js +++ b/tests/startup-renderer.test.js @@ -177,6 +177,7 @@ test('header occupies its final geometry before asynchronous initialization', () assert.match(css, /\.update-dialog\s*\{[^}]*width:\s*min\(576px,\s*100%\);/su); assert.match(css, /\.update-release-notes\s*\{[^}]*height:\s*min\(264px,\s*48vh\);/su); assert.match(html, /class="update-progress-footer"[\s\S]*id="updateProgressDetails"[\s\S]*id="updateProgressSize"[\s\S]*id="updateProgressSpeed"[\s\S]*id="updateProgressEta"[\s\S]*id="updateProgressText"/u); + assert.match(html, /id="queueFilterResetBtn"[^>]*hidden[^>]*>Filter zurücksetzen { check('Status changes drop selections that leave the upload filter', uploadSelectionScope.statusChangeSelected.length === 0 && uploadSelectionScope.statusChangeVisible.join('|') === 'scope-active-z'); check('Selected upload actions ignore hidden stale selections', uploadSelectionScope.hiddenAction.selected.length === 0 && uploadSelectionScope.hiddenAction.retryDisabled && uploadSelectionScope.hiddenAction.moveDisabled); + const queueFilterReset = await wc.executeJavaScript(\`(() => { + queueJobs = [ + { id: 'filter-a', file: 'C:/ui/filter-a.bin', fileName: 'alpha.bin', hoster: 'byse.sx', status: 'uploading', bytesUploaded: 10, bytesTotal: 100, progress: .1 }, + { id: 'filter-b', file: 'C:/ui/filter-b.bin', fileName: 'beta.bin', hoster: 'doodstream.com', status: 'error', bytesUploaded: 0, bytesTotal: 100, progress: 0 } + ]; + selectedJobIds.clear(); + rebuildJobIndex(); + renderQueueTable(); + setUploadSidebarFilter('active'); + document.getElementById('queueSearchInput').value = 'alpha'; + document.getElementById('queueHosterFilter').value = 'byse.sx'; + document.getElementById('queueStatusFilter').value = 'active'; + applyQueueDetailFilters(); + const button = document.getElementById('queueFilterResetBtn'); + const before = { hidden: button.hidden, disabled: button.disabled }; + button.click(); + const after = { + hidden: button.hidden, + disabled: button.disabled, + sidebar: uploadSidebarFilter, + search: document.getElementById('queueSearchInput').value, + hoster: document.getElementById('queueHosterFilter').value, + status: document.getElementById('queueStatusFilter').value, + allPressed: document.querySelector('[data-upload-sidebar-target="all"]').getAttribute('aria-pressed'), + visible: _sortedJobsCache.map(job => job.id).join('|') + }; + queueJobs = []; + rebuildJobIndex(); + renderQueueTable(); + return { before, after }; + })()\`); + check('Queue filter reset clears sidebar, search, host, and status together', queueFilterReset.before.hidden === false && queueFilterReset.before.disabled === false && queueFilterReset.after.hidden === true && queueFilterReset.after.disabled === true && queueFilterReset.after.sidebar === 'all' && queueFilterReset.after.search === '' && queueFilterReset.after.hoster === '' && queueFilterReset.after.status === '' && queueFilterReset.after.allPressed === 'true' && queueFilterReset.after.visible === 'filter-a|filter-b'); + const queueSelectionAnchor = await wc.executeJavaScript(\`(() => { queueJobs = ['a', 'b', 'c', 'd'].map(id => ({ id: 'anchor-' + id, file: 'C:/ui/anchor-' + id + '.bin', fileName: 'anchor-' + id + '.bin', hoster: 'byse.sx', status: 'queued', bytesUploaded: 0, bytesTotal: 100, progress: 0 })); selectedJobIds.clear();