Files
jellytau/src/lib/components/library/LibraryGrid.svelte
T
dtourolle ad48d89dfe chore(format): run prettier over src/ and scripts/
Formatting was configured but never enforced: `bun run format:check`
reported 199 unformatted files and ran in no workflow and in no git hook,
so .prettierrc (printWidth 100, trailing commas) described an intention
rather than the tree.

This is the one-time sweep that makes the check gateable. Whitespace and
token-reflow only -- no behavioural change: `bun run check` reports 0
errors and all 1053 frontend tests pass before and after.

Kept out of every other commit on purpose. A 199-file diff mixed with
real changes is unreviewable, and the next commit turns format:check
into a hard CI gate so this cannot silently accumulate again.
2026-08-21 17:41:44 +02:00

114 lines
3.8 KiB
Svelte

<!-- TRACES: UR-029, UR-037, UR-051 | DR-042, DR-069, DR-070 -->
<script lang="ts">
import type { MediaItem, Library } from "$lib/api/types";
import MediaCard from "./MediaCard.svelte";
import LibraryListView from "./LibraryListView.svelte";
import { library, viewMode } from "$lib/stores/library";
interface Props {
items: (MediaItem | Library)[];
title?: string;
loading?: boolean;
showViewToggle?: 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,
musicContent = false,
onItemClick,
sizeLabelFor,
downloadedBadgeFor,
onItemRemove,
}: Props = $props();
</script>
<div class="space-y-4">
<div class="flex items-center justify-between">
{#if title}
<h2 class="text-xl font-semibold text-white">{title}</h2>
{:else}
<div></div>
{/if}
{#if showViewToggle && items.length > 0}
<div class="flex gap-1">
<button
onclick={() => library.setViewMode("grid")}
class="p-2 rounded transition-colors {$viewMode === 'grid'
? 'bg-[var(--color-jellyfin)] text-white'
: 'text-gray-400 hover:bg-white/10 hover:text-white'}"
aria-label="Grid view"
title="Grid view"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M3 3h8v8H3V3zm0 10h8v8H3v-8zm10-10h8v8h-8V3zm0 10h8v8h-8v-8z" />
</svg>
</button>
<button
onclick={() => library.setViewMode("list")}
class="p-2 rounded transition-colors {$viewMode === 'list'
? 'bg-[var(--color-jellyfin)] text-white'
: 'text-gray-400 hover:bg-white/10 hover:text-white'}"
aria-label="List view"
title="List view"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z" />
</svg>
</button>
</div>
{/if}
</div>
{#if loading}
<div class="flex gap-4 overflow-hidden">
{#each Array(6) as _}
<div class="w-36 flex-shrink-0 animate-pulse">
<div
class="{musicContent
? 'aspect-square'
: 'aspect-[2/3]'} bg-[var(--color-surface)] rounded-lg"
></div>
<div class="mt-2 h-4 bg-[var(--color-surface)] rounded w-3/4"></div>
<div class="mt-1 h-3 bg-[var(--color-surface)] rounded w-1/2"></div>
</div>
{/each}
</div>
{:else if items.length === 0}
<div class="text-center py-12 text-gray-400">
<p>No items found</p>
</div>
{:else if $viewMode === "list"}
<LibraryListView {items} showProgress={true} {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">
{#each items as item, index (item.id)}
<div data-grid-index={index}>
<MediaCard
{item}
showProgress={true}
sizeLabel={sizeLabelFor?.(item)}
downloadedBadge={downloadedBadgeFor?.(item)}
onRemove={onItemRemove ? () => onItemRemove(item) : undefined}
onclick={() => onItemClick?.(item)}
/>
</div>
{/each}
</div>
{/if}
</div>