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;