Compare commits
No commits in common. "2d1d48599a36a3e47bc84ec6f4e4814a5ed74013" and "a62080cb44ec9176254d05208759497e527c27d5" have entirely different histories.
2d1d48599a
...
a62080cb44
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.6.128",
|
"version": "4.6.127",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.6.128",
|
"version": "4.6.127",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.6.128",
|
"version": "4.6.127",
|
||||||
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"author": "xRangerDE",
|
"author": "xRangerDE",
|
||||||
|
|||||||
@ -2,6 +2,15 @@ let archiveStreamerSelectPopulated = false;
|
|||||||
let archiveSearchInFlight = false;
|
let archiveSearchInFlight = false;
|
||||||
let archiveSearchDebounceTimer: number | null = null;
|
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 {
|
function populateArchiveStreamerSelect(): void {
|
||||||
if (archiveStreamerSelectPopulated) return;
|
if (archiveStreamerSelectPopulated) return;
|
||||||
const select = document.getElementById('archiveSearchStreamer') as HTMLSelectElement | null;
|
const select = document.getElementById('archiveSearchStreamer') as HTMLSelectElement | null;
|
||||||
@ -109,7 +118,7 @@ function renderArchiveSearchResults(result: ArchiveSearchResult): void {
|
|||||||
<span class="archive-result-date">${escapeHtml(date)}</span>
|
<span class="archive-result-date">${escapeHtml(date)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="archive-result-filename" title="${escapeHtml(hit.fullPath)}">${escapeHtml(hit.fileName)}</div>
|
<div class="archive-result-filename" title="${escapeHtml(hit.fullPath)}">${escapeHtml(hit.fileName)}</div>
|
||||||
<div class="archive-result-size">${escapeHtml(formatBytes(hit.size))}</div>
|
<div class="archive-result-size">${escapeHtml(formatBytesForArchive(hit.size))}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="archive-result-actions">
|
<div class="archive-result-actions">
|
||||||
<button type="button" class="queue-detail-btn" onclick="openFilePath('${safeFullAttr}')">${escapeHtml(UI_TEXT.static.archiveOpen || 'Oeffnen')}</button>
|
<button type="button" class="queue-detail-btn" onclick="openFilePath('${safeFullAttr}')">${escapeHtml(UI_TEXT.static.archiveOpen || 'Oeffnen')}</button>
|
||||||
|
|||||||
@ -29,20 +29,6 @@ function applyHtml(el: HTMLElement, html: string): void {
|
|||||||
(el as unknown as Record<string, string>)[key] = html;
|
(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
|
/* localStorage helpers — every renderer module that persists state was
|
||||||
wrapping its get/set calls in the same try/catch idiom to handle
|
wrapping its get/set calls in the same try/catch idiom to handle
|
||||||
environments where localStorage isn't writable (private-browsing
|
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 }> = [
|
const cards: Array<{ label: string; value: string; sub?: string }> = [
|
||||||
{ label: UI_TEXT.static.statsTotalRecordings, value: String(stats.liveCount + stats.vodCount), sub: formatBytes(stats.liveBytes + stats.vodBytes) },
|
{ 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: formatBytes(stats.liveBytes) },
|
{ label: UI_TEXT.static.statsLiveRecordings, value: String(stats.liveCount), sub: formatBytesForStats(stats.liveBytes) },
|
||||||
{ label: UI_TEXT.static.statsVodRecordings, value: String(stats.vodCount), sub: formatBytes(stats.vodBytes) },
|
{ label: UI_TEXT.static.statsVodRecordings, value: String(stats.vodCount), sub: formatBytesForStats(stats.vodBytes) },
|
||||||
{ label: UI_TEXT.static.statsStreamers, value: String(stats.streamerCount) },
|
{ label: UI_TEXT.static.statsStreamers, value: String(stats.streamerCount) },
|
||||||
{ label: UI_TEXT.static.statsAvgSize, value: stats.avgRecordingSizeBytes > 0 ? formatBytes(stats.avgRecordingSizeBytes) : '-' },
|
{ label: UI_TEXT.static.statsAvgSize, value: stats.avgRecordingSizeBytes > 0 ? formatBytesForStats(stats.avgRecordingSizeBytes) : '-' },
|
||||||
{ label: UI_TEXT.static.statsChatFiles, value: String(stats.chatCount), sub: formatBytes(stats.chatBytes) }
|
{ label: UI_TEXT.static.statsChatFiles, value: String(stats.chatCount), sub: formatBytesForStats(stats.chatBytes) }
|
||||||
];
|
];
|
||||||
|
|
||||||
applyHtml(grid, cards.map((c) => `
|
applyHtml(grid, cards.map((c) => `
|
||||||
@ -72,13 +72,13 @@ function renderStatsTopStreamers(top: ArchiveStatsTopStreamer[], totalBytes: num
|
|||||||
<div class="stats-top-row">
|
<div class="stats-top-row">
|
||||||
<div class="stats-top-meta">
|
<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><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">${formatBytes(s.bytes)} <span class="stats-top-share">(${sharePct}%)</span></span>
|
<span class="stats-top-meta-sub">${formatBytesForStats(s.bytes)} <span class="stats-top-share">(${sharePct}%)</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-top-bar-track">
|
<div class="stats-top-bar-track">
|
||||||
<div class="stats-top-bar-fill" style="width: ${pct}%;"></div>
|
<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 || s.vodBytes > 0) ? `<div class="stats-top-bar-labels">
|
||||||
${s.liveBytes > 0 ? `LIVE ${formatBytes(s.liveBytes)}` : ''}
|
${s.liveBytes > 0 ? `LIVE ${formatBytesForStats(s.liveBytes)}` : ''}
|
||||||
${s.vodBytes > 0 ? `VOD ${formatBytes(s.vodBytes)}` : ''}
|
${s.vodBytes > 0 ? `VOD ${formatBytesForStats(s.vodBytes)}` : ''}
|
||||||
</div>` : ''}
|
</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -103,7 +103,7 @@ function renderStatsActivity(days: ArchiveStatsDay[]): void {
|
|||||||
|
|
||||||
const bars = days.map((d, idx) => {
|
const bars = days.map((d, idx) => {
|
||||||
const heightPct = Math.max(4, Math.round((d.count / maxCount) * 100));
|
const heightPct = Math.max(4, Math.round((d.count / maxCount) * 100));
|
||||||
const tooltip = `${d.date}: ${d.count} ${UI_TEXT.static.statsFiles} - ${formatBytes(d.bytes)}`;
|
const tooltip = `${d.date}: ${d.count} ${UI_TEXT.static.statsFiles} - ${formatBytesForStats(d.bytes)}`;
|
||||||
const showLabel = idx === 0 || idx === days.length - 1 || idx % 7 === 0;
|
const showLabel = idx === 0 || idx === days.length - 1 || idx % 7 === 0;
|
||||||
const dayLabel = showLabel ? d.date.slice(5) : '';
|
const dayLabel = showLabel ? d.date.slice(5) : '';
|
||||||
return `
|
return `
|
||||||
@ -122,7 +122,7 @@ function renderStatsActivity(days: ArchiveStatsDay[]): void {
|
|||||||
<div class="stats-activity-row">${bars}</div>
|
<div class="stats-activity-row">${bars}</div>
|
||||||
<div class="stats-activity-summary">${escapeHtml(UI_TEXT.static.statsActivitySummary
|
<div class="stats-activity-summary">${escapeHtml(UI_TEXT.static.statsActivitySummary
|
||||||
.replace('{count}', String(totalCount))
|
.replace('{count}', String(totalCount))
|
||||||
.replace('{size}', formatBytes(totalBytes)))}</div>
|
.replace('{size}', formatBytesForStats(totalBytes)))}</div>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ function renderStatsSizeBuckets(buckets: ArchiveStatsBucket[]): void {
|
|||||||
<div class="stats-bucket-row">
|
<div class="stats-bucket-row">
|
||||||
<div class="stats-bucket-meta">
|
<div class="stats-bucket-meta">
|
||||||
<span>${escapeHtml(b.label)}</span>
|
<span>${escapeHtml(b.label)}</span>
|
||||||
<span class="stats-bucket-meta-sub">${b.count} <span aria-hidden="true">·</span> ${formatBytes(b.bytes)}</span>
|
<span class="stats-bucket-meta-sub">${b.count} <span aria-hidden="true">·</span> ${formatBytesForStats(b.bytes)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-bucket-bar-track">
|
<div class="stats-bucket-bar-track">
|
||||||
<div class="stats-bucket-bar-fill" style="width: ${pct}%;"></div>
|
<div class="stats-bucket-bar-fill" style="width: ${pct}%;"></div>
|
||||||
@ -152,6 +152,14 @@ function renderStatsSizeBuckets(buckets: ArchiveStatsBucket[]): void {
|
|||||||
}).join(''));
|
}).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;
|
(window as unknown as { refreshArchiveStats: typeof refreshArchiveStats }).refreshArchiveStats = refreshArchiveStats;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user