feat(downloads): explain unknown remaining sizes

This commit is contained in:
Sucukdeluxe
2026-08-21 01:06:03 +02:00
parent 869dbc25be
commit 8fb92caa30
7 changed files with 53 additions and 4 deletions
+2 -1
View File
@@ -95,7 +95,7 @@ import {
StatisticsSidebarStatus,
type StatisticsViewActions
} from "./views/statistics/StatisticsView";
import { buildDownloadsViewModel, formatRemainingDownloadBytes, getDownloadQueueTotalBytes, getDownloadSpeedBps, getPendingDownloadItemCount, getRemainingDownloadBytes, type DownloadDisplayMode, type DownloadSidebarFilter } from "./views/downloads/downloads-model";
import { buildDownloadsViewModel, formatRemainingDownloadBytes, formatRemainingDownloadTooltip, getDownloadQueueTotalBytes, getDownloadSpeedBps, getPendingDownloadItemCount, getRemainingDownloadBytes, type DownloadDisplayMode, type DownloadSidebarFilter } from "./views/downloads/downloads-model";
import { downloadColumnDefinitions, type DownloadSortColumn } from "./views/downloads/DownloadsTable";
import { DeleteConfirmationDialog } from "./views/downloads/DeleteConfirmationDialog";
import { beginDownloadColumnDrag, clearDownloadColumnDrag, commitDownloadColumnDrag, createDownloadColumnOrderPersistence, DOWNLOAD_COLUMN_MOVE_DURATION_MS, updateDownloadColumnDrag, type DownloadColumnDragSession, type DownloadColumnOrderPersistence } from "./views/downloads/column-drag";
@@ -4826,6 +4826,7 @@ export function App(): ReactElement {
totalBytes: downloadQueueTotalBytes,
remaining: formatRemainingDownloadBytes(downloadRemaining),
remainingBytes: downloadRemaining.bytes,
remainingTooltip: formatRemainingDownloadTooltip(downloadRemaining),
hosters: providerStats.length,
speed: liveDownloadSpeedBps > 0 ? formatSpeedMbps(liveDownloadSpeedBps) : "0 B/s",
eta: snapshot.etaText
+4
View File
@@ -251,6 +251,8 @@ function translateDynamic(value: string, language: AppLanguage): string {
}
const remaining = value.match(/^(.+) von (.+) übrig$/);
if (remaining) return `${remaining[1]} of ${remaining[2]} remaining`;
const unknownRemaining = value.match(/^Noch unbekannte Dateigrößen: (\d+)\. Die tatsächliche Restmenge kann höher sein\.$/);
if (unknownRemaining) return `Unknown file sizes: ${unknownRemaining[1]}. The actual remaining amount may be higher.`;
const actionsFor = value.match(/^Aktionen für (.+)$/);
if (actionsFor) return `Actions for ${actionsFor[1]}`;
const assignment = value.match(/^(.+) Zuordnung entfernen$/);
@@ -416,6 +418,8 @@ function translateDynamic(value: string, language: AppLanguage): string {
}
const remaining = value.match(/^(.+) of (.+) remaining$/);
if (remaining) return `${remaining[1]} von ${remaining[2]} übrig`;
const unknownRemaining = value.match(/^Unknown file sizes: (\d+)\. The actual remaining amount may be higher\.$/);
if (unknownRemaining) return `Noch unbekannte Dateigrößen: ${unknownRemaining[1]}. Die tatsächliche Restmenge kann höher sein.`;
const actionsFor = value.match(/^Actions for (.+)$/);
if (actionsFor) return `Aktionen für ${actionsFor[1]}`;
const assignment = value.match(/^Remove (.+) assignment$/);
@@ -21,6 +21,7 @@ export interface DownloadsStatusModel {
totalBytes: number;
remaining: string;
remainingBytes: number;
remainingTooltip?: string;
hosters: number;
speed: string;
eta: string;
@@ -106,15 +107,18 @@ export function DownloadsSidebar({ actions, model }: { actions: DownloadsViewAct
export function DownloadsSidebarStatus({ model }: { model: DownloadsViewModel }): ReactElement {
const speed = model.status.speed.replace(/^Geschwindigkeit:\s*/i, "");
const eta = model.status.eta.replace(/^ETA:\s*/i, "");
const entries = [
const entries: Array<{ label: string; metric: string; numericValue: number; value: string; tooltip?: string }> = [
{ label: "Pakete", metric: "packages", numericValue: model.status.packages, value: integerFormatter.format(model.status.packages) },
{ label: "Links", metric: "links", numericValue: model.status.links, value: integerFormatter.format(model.status.links) },
{ label: "Sitzung", metric: "session", numericValue: model.status.sessionBytes, value: model.status.session },
{ label: "Verbleibend", metric: "remaining", numericValue: model.status.remainingBytes, value: model.status.remaining },
{ label: "Verbleibend", metric: "remaining", numericValue: model.status.remainingBytes, value: model.status.remaining, tooltip: model.status.remainingTooltip },
{ label: "Gesamt", metric: "total", numericValue: model.status.totalBytes, value: model.status.total },
{ label: "Hoster", metric: "hosters", numericValue: model.status.hosters, value: integerFormatter.format(model.status.hosters) }
];
return <section className="downloads-sidebar-status" data-visual-region="downloads-sidebar-status" aria-label="Downloadstatus">{entries.map((entry) => <div key={entry.metric}><span>{entry.label}</span><RollingMetricValue numericValue={entry.numericValue} value={entry.value} /></div>)}<div><span>Geschwindigkeit</span><strong data-status-metric="speed">{speed}</strong></div><div><span>ETA</span><strong data-status-metric="eta">{eta}</strong></div></section>;
return <section className="downloads-sidebar-status" data-visual-region="downloads-sidebar-status" aria-label="Downloadstatus">{entries.map((entry) => {
const tooltipId = entry.tooltip ? `downloads-status-${entry.metric}-tooltip` : undefined;
return <div aria-describedby={tooltipId} className={entry.tooltip ? "has-tooltip" : undefined} key={entry.metric} title={entry.tooltip || undefined}><span>{entry.label}</span><RollingMetricValue numericValue={entry.numericValue} value={entry.value} />{entry.tooltip ? <span className="downloads-visually-hidden" id={tooltipId}>{entry.tooltip}</span> : null}</div>;
})}<div><span>Geschwindigkeit</span><strong data-status-metric="speed">{speed}</strong></div><div><span>ETA</span><strong data-status-metric="eta">{eta}</strong></div></section>;
}
export function DownloadsToolbar({ actions, model }: { actions: DownloadsViewActions; model: DownloadsViewModel }): ReactElement {
@@ -121,6 +121,11 @@ export function formatRemainingDownloadBytes(summary: { bytes: number; unknownIt
return summary.bytes > 0 ? `${humanSize(summary.bytes)}` : "Unbekannt";
}
export function formatRemainingDownloadTooltip(summary: { bytes: number; unknownItems: number }): string {
if (summary.unknownItems <= 0) return "";
return `Noch unbekannte Dateigrößen: ${summary.unknownItems}. Die tatsächliche Restmenge kann höher sein.`;
}
export function getDownloadSpeedBps(packageSpeeds: Record<string, number>): number {
let total = 0;
for (const speed of Object.values(packageSpeeds)) {
@@ -155,6 +155,21 @@
font-size: 12px;
}
.downloads-sidebar-status .has-tooltip {
cursor: help;
}
.downloads-visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
border: 0;
white-space: nowrap;
}
.downloads-sidebar-status strong {
color: var(--ui-text);
font-weight: 600;