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:
2026-07-23 20:02:55 +02:00
parent 8f4f651bac
commit f25deba824
14 changed files with 1418 additions and 224 deletions
+15 -3
View File
@@ -1,3 +1,4 @@
<!-- TRACES: UR-029, UR-051 | DR-069, DR-070 -->
<script lang="ts">
import type { MediaItem, Library } from "$lib/api/types";
import MediaCard from "./MediaCard.svelte";
@@ -9,12 +10,20 @@
title?: string;
loading?: boolean;
showViewToggle?: boolean;
forceGrid?: boolean;
musicContent?: boolean;
onItemClick?: (item: MediaItem | Library) => void;
/**
* Optional per-item secondary label (e.g. on-disk size for the Downloaded
* surface), forwarded to each card. TRACES: UR-056 | DR-085
*/
sizeLabelFor?: (item: MediaItem | Library) => string | undefined;
/** Optional per-item container download badge for the Downloaded surface. */
downloadedBadgeFor?: (item: MediaItem | Library) => "full" | "partial" | undefined;
/** Optional per-item remove-from-device handler for the Downloaded surface. */
onItemRemove?: (item: MediaItem | Library) => void;
}
let { items, title, loading = false, showViewToggle = true, forceGrid = false, musicContent = false, onItemClick }: Props = $props();
let { items, title, loading = false, showViewToggle = true, musicContent = false, onItemClick, sizeLabelFor, downloadedBadgeFor, onItemRemove }: Props = $props();
</script>
<div class="space-y-4">
@@ -65,7 +74,7 @@
<div class="text-center py-12 text-gray-400">
<p>No items found</p>
</div>
{:else if !forceGrid && $viewMode === "list"}
{:else if $viewMode === "list"}
<LibraryListView {items} showProgress={true} onItemClick={onItemClick} />
{:else}
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
@@ -74,6 +83,9 @@
<MediaCard
{item}
showProgress={true}
sizeLabel={sizeLabelFor?.(item)}
downloadedBadge={downloadedBadgeFor?.(item)}
onRemove={onItemRemove ? () => onItemRemove(item) : undefined}
onclick={() => onItemClick?.(item)}
/>
</div>