cleanup: dedupe formatBytes — renderer-stats + renderer-archive copies hoist to renderer-shared
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) <noreply@anthropic.com>
This commit is contained in:
parent
a62080cb44
commit
2b09b7868a
@ -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 {
|
||||
<span class="archive-result-date">${escapeHtml(date)}</span>
|
||||
</div>
|
||||
<div class="archive-result-filename" title="${escapeHtml(hit.fullPath)}">${escapeHtml(hit.fileName)}</div>
|
||||
<div class="archive-result-size">${escapeHtml(formatBytesForArchive(hit.size))}</div>
|
||||
<div class="archive-result-size">${escapeHtml(formatBytes(hit.size))}</div>
|
||||
</div>
|
||||
<div class="archive-result-actions">
|
||||
<button type="button" class="queue-detail-btn" onclick="openFilePath('${safeFullAttr}')">${escapeHtml(UI_TEXT.static.archiveOpen || 'Oeffnen')}</button>
|
||||
|
||||
@ -29,6 +29,20 @@ function applyHtml(el: HTMLElement, html: string): void {
|
||||
(el as unknown as Record<string, string>)[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
|
||||
|
||||
@ -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
|
||||
<div class="stats-top-row">
|
||||
<div class="stats-top-meta">
|
||||
<span><strong>${escapeHtml(s.streamer)}</strong> <span class="stats-top-meta-sub"><span aria-hidden="true">·</span> ${s.fileCount} ${escapeHtml(UI_TEXT.static.statsFiles)}</span></span>
|
||||
<span class="stats-top-meta-sub">${formatBytesForStats(s.bytes)} <span class="stats-top-share">(${sharePct}%)</span></span>
|
||||
<span class="stats-top-meta-sub">${formatBytes(s.bytes)} <span class="stats-top-share">(${sharePct}%)</span></span>
|
||||
</div>
|
||||
<div class="stats-top-bar-track">
|
||||
<div class="stats-top-bar-fill" style="width: ${pct}%;"></div>
|
||||
${(s.liveBytes > 0 || s.vodBytes > 0) ? `<div class="stats-top-bar-labels">
|
||||
${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)}` : ''}
|
||||
</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
@ -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 {
|
||||
<div class="stats-activity-row">${bars}</div>
|
||||
<div class="stats-activity-summary">${escapeHtml(UI_TEXT.static.statsActivitySummary
|
||||
.replace('{count}', String(totalCount))
|
||||
.replace('{size}', formatBytesForStats(totalBytes)))}</div>
|
||||
.replace('{size}', formatBytes(totalBytes)))}</div>
|
||||
`);
|
||||
}
|
||||
|
||||
@ -142,7 +142,7 @@ function renderStatsSizeBuckets(buckets: ArchiveStatsBucket[]): void {
|
||||
<div class="stats-bucket-row">
|
||||
<div class="stats-bucket-meta">
|
||||
<span>${escapeHtml(b.label)}</span>
|
||||
<span class="stats-bucket-meta-sub">${b.count} <span aria-hidden="true">·</span> ${formatBytesForStats(b.bytes)}</span>
|
||||
<span class="stats-bucket-meta-sub">${b.count} <span aria-hidden="true">·</span> ${formatBytes(b.bytes)}</span>
|
||||
</div>
|
||||
<div class="stats-bucket-bar-track">
|
||||
<div class="stats-bucket-bar-fill" style="width: ${pct}%;"></div>
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user