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();