Files
jellytau/src/lib/components/library/CastSection.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

115 lines
3.2 KiB
Svelte

<!-- TRACES: UR-035 | JA-029 | DR-040, DR-044 -->
<script lang="ts">
import type { Person, PersonType } from "$lib/api/types";
import { goto } from "$app/navigation";
import CachedImage from "$lib/components/common/CachedImage.svelte";
interface Props {
people: Person[];
title?: string;
}
let { people, title = "Cast & Crew" }: Props = $props();
// Group people by type
const groupedPeople = $derived.by(() => {
const groups: Record<string, Person[]> = {
Actor: [],
Director: [],
Writer: [],
Producer: [],
Composer: [],
Other: [],
};
for (const person of people) {
// Skip people without valid ID (can occur if API response is incomplete)
if (!person.id || person.id.trim() === "") {
continue;
}
const type = person.type;
if (!type) continue;
if (type in groups) {
groups[type].push(person);
} else {
groups.Other.push(person);
}
}
return groups;
});
// Order: Actors first, then Directors, Writers, etc.
const orderedTypes = ["Actor", "Director", "Writer", "Producer", "Composer", "Other"] as const;
// Get display name for type
function getTypeName(type: string): string {
switch (type) {
case "Actor":
return "Cast";
case "Director":
return "Directors";
case "Writer":
return "Writers";
case "Producer":
return "Producers";
case "Composer":
return "Composers";
default:
return "Other";
}
}
function handlePersonClick(person: Person) {
goto(`/library/${person.id}`);
}
</script>
<section class="space-y-6">
<h2 class="text-xl font-semibold text-white">{title}</h2>
{#each orderedTypes as type}
{#if groupedPeople[type]?.length > 0}
<div class="space-y-3">
<h3 class="text-sm font-medium text-gray-400 uppercase tracking-wide">
{getTypeName(type)}
</h3>
<div class="flex gap-4 overflow-x-auto pb-2 scrollbar-thin scrollbar-thumb-gray-700">
{#each groupedPeople[type] as person (person.id)}
<button
type="button"
class="flex-shrink-0 w-24 group text-left"
onclick={() => handlePersonClick(person)}
>
<!-- Person image -->
<div class="w-24 h-24 rounded-full overflow-hidden bg-[var(--color-surface)] mb-2">
<CachedImage
itemId={person.id ?? ""}
imageType="Primary"
tag={person.primaryImageTag}
maxWidth={200}
alt={person.name}
class="w-full h-full object-cover group-hover:scale-110 transition-transform"
/>
</div>
<!-- Name and role -->
<p
class="text-sm font-medium text-white truncate group-hover:text-[var(--color-jellyfin)] transition-colors"
>
{person.name}
</p>
{#if person.role}
<p class="text-xs text-gray-400 truncate">
{person.role}
</p>
{/if}
</button>
{/each}
</div>
</div>
{/if}
{/each}
</section>