From 2b09b7868abf1a623ed94fe65daae3ecf99b926f Mon Sep 17 00:00:00 2001 From: xRangerDE Date: Mon, 11 May 2026 09:45:10 +0200 Subject: [PATCH] =?UTF-8?q?cleanup:=20dedupe=20formatBytes=20=E2=80=94=20r?= =?UTF-8?q?enderer-stats=20+=20renderer-archive=20copies=20hoist=20to=20re?= =?UTF-8?q?nderer-shared?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit renderer-stats.ts and renderer-archive.ts each had their own byte-size formatter (formatBytesForStats / formatBytesForArchive). The two were textually identical: both handle the B -> KB -> MB -> GB -> TB ladder with the same toFixed precision and return '0 B' for non-finite / zero / negative input. Hoisted to renderer-shared.ts as plain formatBytes. Removed both per-file copies and renamed all 14 call sites across the two modules. The two narrower variants in renderer-settings.ts (formatBytesForMetrics — caps at GB) and renderer.ts (formatBytesRenderer — caps at GB, less protection) stay file-scoped because they have different scale/protection semantics for their specific contexts (runtime metrics + download progress, which never reach TB). Continues the renderer-shared consolidation from 4.6.127 (applyHtml/escapeHtml). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/renderer-archive.ts | 11 +---------- src/renderer-shared.ts | 14 ++++++++++++++ src/renderer-stats.ts | 30 +++++++++++------------------- 3 files changed, 26 insertions(+), 29 deletions(-) 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;