diff --git a/src/renderer-archive.ts b/src/renderer-archive.ts
index 181b66e..e42ca66 100644
--- a/src/renderer-archive.ts
+++ b/src/renderer-archive.ts
@@ -2,15 +2,6 @@ let archiveStreamerSelectPopulated = false;
let archiveSearchInFlight = false;
let archiveSearchDebounceTimer: number | null = null;
-function formatBytesForArchive(bytes: number): string {
- if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
- 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`;
- if (bytes < 1024 * 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
- return `${(bytes / (1024 * 1024 * 1024 * 1024)).toFixed(2)} TB`;
-}
-
function populateArchiveStreamerSelect(): void {
if (archiveStreamerSelectPopulated) return;
const select = document.getElementById('archiveSearchStreamer') as HTMLSelectElement | null;
@@ -118,7 +109,7 @@ function renderArchiveSearchResults(result: ArchiveSearchResult): void {
${escapeHtml(date)}
${escapeHtml(hit.fileName)}
- ${escapeHtml(formatBytesForArchive(hit.size))}
+ ${escapeHtml(formatBytes(hit.size))}
diff --git a/src/renderer-shared.ts b/src/renderer-shared.ts
index 3d304f6..634d318 100644
--- a/src/renderer-shared.ts
+++ b/src/renderer-shared.ts
@@ -29,6 +29,20 @@ function applyHtml(el: HTMLElement, html: string): void {
(el as unknown as Record
)[key] = html;
}
+/* Generic file-size formatter for the renderer. Scales B -> KB -> MB
+ -> GB -> TB; returns '0 B' for zero / negative / non-finite input.
+ Used by the archive search results and the stats card. Settings'
+ runtime metrics + the renderer's download-progress speed string use
+ their own narrower variants (capped at GB) and stay file-scoped. */
+function formatBytes(bytes: number): string {
+ if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
+ 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`;
+ if (bytes < 1024 * 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
+ return `${(bytes / (1024 * 1024 * 1024 * 1024)).toFixed(2)} TB`;
+}
+
/* localStorage helpers — every renderer module that persists state was
wrapping its get/set calls in the same try/catch idiom to handle
environments where localStorage isn't writable (private-browsing
diff --git a/src/renderer-stats.ts b/src/renderer-stats.ts
index 7f0f919..fb3191d 100644
--- a/src/renderer-stats.ts
+++ b/src/renderer-stats.ts
@@ -38,12 +38,12 @@ function renderStatsSummary(stats: ArchiveStats): void {
}
const cards: Array<{ label: string; value: string; sub?: string }> = [
- { label: UI_TEXT.static.statsTotalRecordings, value: String(stats.liveCount + stats.vodCount), sub: formatBytesForStats(stats.liveBytes + stats.vodBytes) },
- { label: UI_TEXT.static.statsLiveRecordings, value: String(stats.liveCount), sub: formatBytesForStats(stats.liveBytes) },
- { label: UI_TEXT.static.statsVodRecordings, value: String(stats.vodCount), sub: formatBytesForStats(stats.vodBytes) },
+ { label: UI_TEXT.static.statsTotalRecordings, value: String(stats.liveCount + stats.vodCount), sub: formatBytes(stats.liveBytes + stats.vodBytes) },
+ { label: UI_TEXT.static.statsLiveRecordings, value: String(stats.liveCount), sub: formatBytes(stats.liveBytes) },
+ { label: UI_TEXT.static.statsVodRecordings, value: String(stats.vodCount), sub: formatBytes(stats.vodBytes) },
{ label: UI_TEXT.static.statsStreamers, value: String(stats.streamerCount) },
- { label: UI_TEXT.static.statsAvgSize, value: stats.avgRecordingSizeBytes > 0 ? formatBytesForStats(stats.avgRecordingSizeBytes) : '-' },
- { label: UI_TEXT.static.statsChatFiles, value: String(stats.chatCount), sub: formatBytesForStats(stats.chatBytes) }
+ { label: UI_TEXT.static.statsAvgSize, value: stats.avgRecordingSizeBytes > 0 ? formatBytes(stats.avgRecordingSizeBytes) : '-' },
+ { label: UI_TEXT.static.statsChatFiles, value: String(stats.chatCount), sub: formatBytes(stats.chatBytes) }
];
applyHtml(grid, cards.map((c) => `
@@ -72,13 +72,13 @@ function renderStatsTopStreamers(top: ArchiveStatsTopStreamer[], totalBytes: num
${escapeHtml(s.streamer)} · ${s.fileCount} ${escapeHtml(UI_TEXT.static.statsFiles)}
- ${formatBytesForStats(s.bytes)} (${sharePct}%)
+ ${formatBytes(s.bytes)} (${sharePct}%)
${(s.liveBytes > 0 || s.vodBytes > 0) ? `
- ${s.liveBytes > 0 ? `LIVE ${formatBytesForStats(s.liveBytes)}` : ''}
- ${s.vodBytes > 0 ? `VOD ${formatBytesForStats(s.vodBytes)}` : ''}
+ ${s.liveBytes > 0 ? `LIVE ${formatBytes(s.liveBytes)}` : ''}
+ ${s.vodBytes > 0 ? `VOD ${formatBytes(s.vodBytes)}` : ''}
` : ''}
@@ -103,7 +103,7 @@ function renderStatsActivity(days: ArchiveStatsDay[]): void {
const bars = days.map((d, idx) => {
const heightPct = Math.max(4, Math.round((d.count / maxCount) * 100));
- const tooltip = `${d.date}: ${d.count} ${UI_TEXT.static.statsFiles} - ${formatBytesForStats(d.bytes)}`;
+ const tooltip = `${d.date}: ${d.count} ${UI_TEXT.static.statsFiles} - ${formatBytes(d.bytes)}`;
const showLabel = idx === 0 || idx === days.length - 1 || idx % 7 === 0;
const dayLabel = showLabel ? d.date.slice(5) : '';
return `
@@ -122,7 +122,7 @@ function renderStatsActivity(days: ArchiveStatsDay[]): void {
${bars}
${escapeHtml(UI_TEXT.static.statsActivitySummary
.replace('{count}', String(totalCount))
- .replace('{size}', formatBytesForStats(totalBytes)))}
+ .replace('{size}', formatBytes(totalBytes)))}
`);
}
@@ -142,7 +142,7 @@ function renderStatsSizeBuckets(buckets: ArchiveStatsBucket[]): void {
${escapeHtml(b.label)}
- ${b.count} · ${formatBytesForStats(b.bytes)}
+ ${b.count} · ${formatBytes(b.bytes)}
@@ -152,14 +152,6 @@ function renderStatsSizeBuckets(buckets: ArchiveStatsBucket[]): void {
}).join(''));
}
-function formatBytesForStats(bytes: number): string {
- if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
- 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`;
- if (bytes < 1024 * 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
- return `${(bytes / (1024 * 1024 * 1024 * 1024)).toFixed(2)} TB`;
-}
(window as unknown as { refreshArchiveStats: typeof refreshArchiveStats }).refreshArchiveStats = refreshArchiveStats;