diff --git a/src/index.html b/src/index.html
index 439d887..b572402 100644
--- a/src/index.html
+++ b/src/index.html
@@ -218,6 +218,7 @@
+
diff --git a/src/renderer.ts b/src/renderer.ts
index 7755e7a..73302fa 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -96,6 +96,10 @@ async function init(): Promise {
byId('mergeProgressText').textContent = Math.round(percent) + '%';
});
+ // Update stats bar
+ updateStatsBar();
+ setInterval(updateStatsBar, 5000);
+
if (config.client_id && config.client_secret) {
await connect();
} else {
@@ -119,9 +123,56 @@ async function init(): Promise {
scheduleQueueSync(document.hidden ? 600 : 150);
});
+ // Keyboard shortcuts
+ document.addEventListener('keydown', (e) => {
+ // Skip if user is typing in an input field
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLSelectElement) return;
+
+ if (e.key === 'Delete' && selectedQueueIds.length > 0) {
+ // Delete selected queue items
+ const idsToRemove = [...selectedQueueIds];
+ selectedQueueIds = [];
+ (async () => {
+ for (const id of idsToRemove) {
+ queue = await window.api.removeFromQueue(id);
+ }
+ renderQueue();
+ })();
+ }
+
+ if ((e.key === 's' || e.key === 'S') && !e.ctrlKey && !e.altKey && !e.metaKey) {
+ e.preventDefault();
+ toggleDownload();
+ }
+ });
+
scheduleQueueSync(QUEUE_SYNC_DEFAULT_MS);
}
+function formatBytesRenderer(bytes: number): string {
+ if (bytes < 1024) return `${bytes} B`;
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
+}
+
+function formatSpeedRenderer(bytesPerSec: number): string {
+ if (bytesPerSec < 1024) return `${bytesPerSec.toFixed(0)} B/s`;
+ if (bytesPerSec < 1024 * 1024) return `${(bytesPerSec / 1024).toFixed(1)} KB/s`;
+ return `${(bytesPerSec / (1024 * 1024)).toFixed(1)} MB/s`;
+}
+
+async function updateStatsBar(): Promise {
+ try {
+ const metrics = await window.api.getRuntimeMetrics();
+ const bar = byId('statsBar');
+ if (!bar) return;
+ const totalDL = formatBytesRenderer(metrics.downloadedBytesTotal);
+ const avgSpeed = metrics.avgSpeedBytesPerSec > 0 ? formatSpeedRenderer(metrics.avgSpeedBytesPerSec) : '-';
+ bar.textContent = `${totalDL} | ${avgSpeed} avg | ${metrics.downloadsCompleted} done | ${metrics.downloadsFailed} failed`;
+ } catch { }
+}
+
let toastHideTimer: number | null = null;
let queueSyncTimer: number | null = null;
let queueSyncInFlight = false;
diff --git a/src/styles.css b/src/styles.css
index b5e96e7..7587dd9 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -386,6 +386,14 @@ body {
flex-shrink: 0;
}
+.stats-bar {
+ padding: 6px 15px;
+ font-size: 10px;
+ color: var(--text-secondary);
+ border-top: 1px solid rgba(255,255,255,0.1);
+ flex-shrink: 0;
+}
+
.btn {
flex: 1;
padding: 10px;