feat(downloads): browsable downloaded library with on-disk usage
Replace the flat download list with a Downloaded browse surface that reuses the online grids/cards/detail pages, filtered to on-device media, plus a demoted Transfers tab. Add repository browse commands (getDownloadedLibraries/Items, disk usage) with offline/hybrid implementations, a downloadedCatalog service, formatBytes helper, and per-item/device disk-usage labels on cards and grids. Regenerated bindings. Also carries the inseparable UR-052 offline-filter hunks in offline.rs/hybrid.rs. TRACES: UR-055 | DR-081, DR-082, DR-083, DR-084; UR-056 | DR-085
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
// Shared byte-size formatter for the Downloads surface.
|
||||
//
|
||||
// One formatter, used everywhere disk usage is shown (cards, detail pages, the
|
||||
// device total, and the remove-reclaim prompt) so units are consistent. We use
|
||||
// DECIMAL units (1 GB = 1000 MB), matching how phone storage screens and file
|
||||
// browsers present sizes, and show 2–3 significant figures.
|
||||
//
|
||||
// TRACES: UR-056 | DR-085
|
||||
|
||||
const UNITS = ["B", "KB", "MB", "GB", "TB", "PB"] as const;
|
||||
|
||||
/**
|
||||
* Format a byte count as a human-readable size string (decimal units).
|
||||
*
|
||||
* Examples: 0 → "0 B", 340_000_000 → "340 MB", 1_200_000_000 → "1.2 GB".
|
||||
*
|
||||
* - Bytes render as whole numbers (no "0.5 B").
|
||||
* - KB and above show enough decimals for 2–3 significant figures: values
|
||||
* ≥ 100 render with no decimals, ≥ 10 with one, otherwise two.
|
||||
* - Negative / non-finite inputs are treated as 0 (sizes are never negative).
|
||||
*/
|
||||
export function formatBytes(bytes: number): string {
|
||||
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
||||
|
||||
let value = bytes;
|
||||
let unitIndex = 0;
|
||||
while (value >= 1000 && unitIndex < UNITS.length - 1) {
|
||||
value /= 1000;
|
||||
unitIndex += 1;
|
||||
}
|
||||
|
||||
// Bytes are always whole; larger units get 2–3 significant figures.
|
||||
let formatted: string;
|
||||
if (unitIndex === 0) {
|
||||
formatted = Math.round(value).toString();
|
||||
} else if (value >= 100) {
|
||||
formatted = Math.round(value).toString();
|
||||
} else if (value >= 10) {
|
||||
formatted = value.toFixed(1);
|
||||
} else {
|
||||
formatted = value.toFixed(2);
|
||||
}
|
||||
|
||||
// Trim trailing zeros ("1.20" → "1.2", "1.00" → "1") for a cleaner label.
|
||||
if (formatted.includes(".")) {
|
||||
formatted = formatted.replace(/\.?0+$/, "");
|
||||
}
|
||||
|
||||
return `${formatted} ${UNITS[unitIndex]}`;
|
||||
}
|
||||
Reference in New Issue
Block a user