diff --git a/tests/test_dom_patchers.mjs b/tests/test_dom_patchers.mjs
index 444858c..d9a1cfe 100644
--- a/tests/test_dom_patchers.mjs
+++ b/tests/test_dom_patchers.mjs
@@ -66,6 +66,7 @@ const scheduleSearchUiRefresh = eval(`(${extractMethod(searchPanelMethodsSource,
const wireLocalMatchButtons = eval(`(${extractMethod(searchPanelMethodsSource, 'wireLocalMatchButtons')})`);
const patchQueuedSelections = eval(`(${extractMethod(queueMethodsSource, 'patchQueuedSelections')})`);
const patchDownloadsPanelElement = eval(`(${extractMethod(queueMethodsSource, 'patchDownloadsPanelElement')})`);
+const updateQueueToggleIcon = eval(`(${extractMethod(queueMethodsSource, 'updateQueueToggleIcon')})`);
const patchLoadedModelsProgress = eval(`(${extractMethod(renderFormatMethodsSource, 'patchLoadedModelsProgress')})`);
const patchAnalysisProgress = eval(`(${extractMethod(renderFormatMethodsSource, 'patchAnalysisProgress')})`);
@@ -125,6 +126,31 @@ test('sidebar active helper recognizes supported state attributes and classes',
assert.equal(domPatchUtils.isSidebarButtonActive(null), false);
});
+test('queue toggle icon shows queued and active download counts', () => {
+ const window = new Window();
+ const queueToggleIcon = window.document.createElement('button');
+ queueToggleIcon.innerHTML = `
+
+
+ `;
+ const dialog = {
+ queueToggleIcon,
+ activeTab: 'missing',
+ queueCollapsed: true,
+ pendingResolutions: [{}, {}, {}],
+ getActiveQueuePanelDownloads: () => [{ downloadId: 'download-1' }],
+ };
+
+ updateQueueToggleIcon.call(dialog);
+
+ assert.equal(queueToggleIcon.querySelector('.mr-queue-toggle-count-queued').textContent, '3');
+ assert.equal(queueToggleIcon.querySelector('.mr-queue-toggle-count-downloads').textContent, '1');
+ assert.equal(
+ queueToggleIcon.getAttribute('aria-label'),
+ 'Show queue and downloads panel. Queued: 3. Downloads: 1.'
+ );
+});
+
test('instant actions handle pointer and click as one idempotent action', () => {
assert.equal(typeof domPatchUtils.bindInstantAction, 'function');
diff --git a/web/css/resolver-shell.css b/web/css/resolver-shell.css
index 1c66526..4af4b79 100644
--- a/web/css/resolver-shell.css
+++ b/web/css/resolver-shell.css
@@ -462,6 +462,7 @@
border: 0;
border-radius: 0;
box-shadow: none;
+ overflow: visible;
}
#queue-toggle-icon::before {
@@ -477,6 +478,37 @@
transition: var(--mr-transition-smooth);
}
+#queue-toggle-icon .mr-queue-toggle-count {
+ position: absolute;
+ left: 50%;
+ z-index: 1;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 14px;
+ height: 14px;
+ padding: 0 2px;
+ box-sizing: border-box;
+ color: var(--mr-text-muted, #b1b1b1);
+ background: var(--mr-bg, #121212);
+ border: 1px solid rgba(255, 255, 255, 0.16);
+ border-radius: 999px;
+ font-size: 9px;
+ font-variant-numeric: tabular-nums;
+ font-weight: 800;
+ line-height: 1;
+ pointer-events: none;
+ transform: translateX(-50%);
+}
+
+#queue-toggle-icon .mr-queue-toggle-count-queued {
+ top: -11px;
+}
+
+#queue-toggle-icon .mr-queue-toggle-count-downloads {
+ bottom: -11px;
+}
+
#queue-toggle-icon:hover,
#queue-toggle-icon:focus-visible {
opacity: 1;
@@ -490,6 +522,13 @@
box-shadow: var(--mr-shadow-focus-ring);
}
+#queue-toggle-icon:hover .mr-queue-toggle-count,
+#queue-toggle-icon:focus-visible .mr-queue-toggle-count,
+#queue-toggle-icon.is-collapsed .mr-queue-toggle-count {
+ color: var(--mr-accent, #72d38a);
+ border-color: rgba(114, 211, 138, 0.45);
+}
+
.mr-queue-panel,
.mr-queue-stack {
display: flex;
diff --git a/web/resolver/actions/queue_methods.js b/web/resolver/actions/queue_methods.js
index a310218..e1fafa6 100644
--- a/web/resolver/actions/queue_methods.js
+++ b/web/resolver/actions/queue_methods.js
@@ -61,7 +61,14 @@ export const queueMethods = {
type: "button",
"aria-label": "Toggle queued selections",
onclick: (event) => this.onQueueEdgeClick(event)
- });
+ }, [
+ $el("span.mr-queue-toggle-count.mr-queue-toggle-count-queued", {
+ "aria-hidden": "true"
+ }),
+ $el("span.mr-queue-toggle-count.mr-queue-toggle-count-downloads", {
+ "aria-hidden": "true"
+ })
+ ]);
this.queueToggleIcon.addEventListener(
typeof PointerEvent === 'function' ? 'pointerdown' : 'mousedown',
(event) => this.startQueueEdgeDrag(event)
@@ -152,6 +159,7 @@ export const queueMethods = {
},
updateQueuePanel({ force = false } = {}) {
+ this.updateQueueToggleIcon();
if (!this.queueList || !this.queueHeader) return;
if (this._queuePanelUpdateTimer) {
clearTimeout(this._queuePanelUpdateTimer);
@@ -1639,15 +1647,26 @@ export const queueMethods = {
updateQueueToggleIcon() {
if (!this.queueToggleIcon) return;
this.queueToggleIcon.style.display = this.activeTab === 'missing' ? '' : 'none';
- this.queueToggleIcon.textContent = '';
this.queueToggleIcon.removeAttribute('data-tooltip');
this.queueToggleIcon.removeAttribute('title');
+ const queueCount = Array.isArray(this.pendingResolutions) ? this.pendingResolutions.length : 0;
+ const downloadCount = this.getActiveQueuePanelDownloads().length;
+ const queuedCountElement = this.queueToggleIcon.querySelector('.mr-queue-toggle-count-queued');
+ const downloadsCountElement = this.queueToggleIcon.querySelector('.mr-queue-toggle-count-downloads');
+ if (queuedCountElement) queuedCountElement.textContent = String(queueCount);
+ if (downloadsCountElement) downloadsCountElement.textContent = String(downloadCount);
if (this.queueCollapsed) {
this.queueToggleIcon.classList.add('is-collapsed');
- this.queueToggleIcon.setAttribute('aria-label', 'Show queue and downloads panel');
+ this.queueToggleIcon.setAttribute(
+ 'aria-label',
+ `Show queue and downloads panel. Queued: ${queueCount}. Downloads: ${downloadCount}.`
+ );
} else {
this.queueToggleIcon.classList.remove('is-collapsed');
- this.queueToggleIcon.setAttribute('aria-label', 'Hide queue and downloads panel');
+ this.queueToggleIcon.setAttribute(
+ 'aria-label',
+ `Hide queue and downloads panel. Queued: ${queueCount}. Downloads: ${downloadCount}.`
+ );
}
},