diff --git a/src/renderer-locale-de.ts b/src/renderer-locale-de.ts
index 6360d72..9d12633 100644
--- a/src/renderer-locale-de.ts
+++ b/src/renderer-locale-de.ts
@@ -227,6 +227,10 @@ const UI_TEXT_DE = {
bulkClear: 'Loeschen',
bulkAddedToQueue: '{count} VODs zur Warteschlange hinzugefuegt.',
bulkAddSkipped: 'Keine VODs hinzugefuegt (bereits in Queue oder ungueltig).',
+ bulkMarkDownloaded: 'Als heruntergeladen markieren',
+ bulkUnmark: 'Markierung entfernen',
+ bulkMarkedDownloaded: '{count} VODs als heruntergeladen markiert.',
+ bulkUnmarkedDownloaded: 'Markierung von {count} VODs entfernt.',
alreadyDownloaded: 'Bereits heruntergeladen',
hideDownloaded: 'Bereits geladene ausblenden',
hideDownloadedTitle: 'VODs ausblenden, die als bereits heruntergeladen markiert sind',
diff --git a/src/renderer-locale-en.ts b/src/renderer-locale-en.ts
index fd47a7d..0610217 100644
--- a/src/renderer-locale-en.ts
+++ b/src/renderer-locale-en.ts
@@ -227,6 +227,10 @@ const UI_TEXT_EN = {
bulkClear: 'Clear',
bulkAddedToQueue: 'Added {count} VODs to the queue.',
bulkAddSkipped: 'No VODs were added (already in queue or invalid).',
+ bulkMarkDownloaded: 'Mark as downloaded',
+ bulkUnmark: 'Unmark',
+ bulkMarkedDownloaded: 'Marked {count} VODs as downloaded.',
+ bulkUnmarkedDownloaded: 'Removed {count} VODs from the downloaded list.',
alreadyDownloaded: 'Already downloaded',
hideDownloaded: 'Hide downloaded',
hideDownloadedTitle: 'Hide VODs that are marked as already downloaded',
diff --git a/src/renderer-streamers.ts b/src/renderer-streamers.ts
index 97e416b..b8a7dec 100644
--- a/src/renderer-streamers.ts
+++ b/src/renderer-streamers.ts
@@ -228,7 +228,7 @@ function buildVodCardHtml(vod: VOD, streamer: string, downloadedIds?: Set
-
${safeDisplayTitle}
+
${safeDisplayTitle}
${date}
${escapeHtml(vod.duration)}
@@ -831,6 +831,34 @@ function clearVodSelection(): void {
if (lastLoadedStreamer) renderVodGridFromCurrentState();
}
+async function bulkMarkSelectedDownloaded(mark: boolean): Promise {
+ const urls = Array.from(selectedVodUrls);
+ if (urls.length === 0) return;
+
+ let updated = 0;
+ for (const url of urls) {
+ const vod = lastLoadedVods.find((v) => v.url === url);
+ if (!vod || !vod.id) continue;
+ try {
+ const result = await window.api.markVodDownloaded(vod.id, mark);
+ if (result?.success) updated++;
+ } catch { /* keep going */ }
+ }
+
+ if (updated === 0) return;
+
+ try { config = await window.api.getConfig(); } catch { /* ignore */ }
+ selectedVodUrls.clear();
+ updateVodBulkBar();
+ if (lastLoadedStreamer) renderVodGridFromCurrentState();
+
+ const toast = (window as unknown as { showAppToast?: (msg: string, kind?: 'info' | 'warn') => void }).showAppToast;
+ if (toast) {
+ const template = mark ? UI_TEXT.vods.bulkMarkedDownloaded : UI_TEXT.vods.bulkUnmarkedDownloaded;
+ toast(template.replace('{count}', String(updated)), 'info');
+ }
+}
+
async function bulkAddSelectedVodsToQueue(): Promise {
const urls = Array.from(selectedVodUrls);
if (urls.length === 0 || !lastLoadedStreamer) return;
diff --git a/src/renderer-texts.ts b/src/renderer-texts.ts
index d0cf864..02bbb3b 100644
--- a/src/renderer-texts.ts
+++ b/src/renderer-texts.ts
@@ -198,6 +198,8 @@ function applyLanguageToStaticUI(): void {
refreshVodSortSelectLabels();
}
setText('vodBulkAddBtn', UI_TEXT.vods.bulkAddToQueue);
+ setText('vodBulkMarkBtn', UI_TEXT.vods.bulkMarkDownloaded);
+ setText('vodBulkUnmarkBtn', UI_TEXT.vods.bulkUnmark);
setText('vodBulkClearBtn', UI_TEXT.vods.bulkClear);
if (typeof updateVodBulkBar === 'function') {
// Repopulate the count text in the new locale
diff --git a/src/renderer.ts b/src/renderer.ts
index 786eb76..91d929e 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -143,9 +143,14 @@ async function init(): Promise {
byId('mergeProgressText').textContent = Math.round(percent) + '%';
});
- // Update stats bar
- updateStatsBar();
- const _statsInterval = setInterval(updateStatsBar, 5000);
+ // Update stats bar — paused while the window is hidden so we don't
+ // burn IPC chatter on a tab nobody is looking at.
+ void updateStatsBar();
+ startStatsBarPolling();
+ document.addEventListener('visibilitychange', () => {
+ if (document.hidden) stopStatsBarPolling();
+ else startStatsBarPolling();
+ });
if (config.client_id && config.client_secret) {
await connect();
@@ -302,6 +307,21 @@ function updateStatusBarQueueSummary(): void {
.replace('{pending}', String(pending));
}
+let statsBarPollTimer: number | null = null;
+
+function startStatsBarPolling(): void {
+ stopStatsBarPolling();
+ if (document.hidden) return;
+ statsBarPollTimer = window.setInterval(updateStatsBar, 5000);
+}
+
+function stopStatsBarPolling(): void {
+ if (statsBarPollTimer !== null) {
+ window.clearInterval(statsBarPollTimer);
+ statsBarPollTimer = null;
+ }
+}
+
async function updateStatsBar(): Promise {
try {
const metrics = await window.api.getRuntimeMetrics();