E frontend reconciliation (2/2): align consumers to generated bindings; svelte-check clean
Reconcile the ~50 frontend files that consumed the old hand-written types against
the stricter generated bindings (141 -> 0 svelte-check errors):
- Nullable strictness: null-coalesce/guard at consumer sites; widen shared helpers
(CachedImage.tag, ticksToSeconds, formatDuration, getSessionIcon, session store
send*/transferToRemote) to accept null.
- Field-name fixes exposed by codegen: userData.played -> isPlayed; remote
NowPlayingItem uses album (not albumName); ArtistItem id/name.
- player.ts: normalize remote NowPlayingItem -> MediaItem in mergedMedia.
- ArtistItem now serializes camelCase (PascalCase alias retained for Jellyfin
deserialize); update its serialize test.
- Update downloads.test.ts to the bundled { request } shape; complete the
sessions.test.ts NowPlayingItem mock.
Frontend: svelte-check 0 errors, vitest 448 passing. Backend: 387 passing.
This commit is contained in:
parent
76c78e2edc
commit
14e9d7e03a
@ -405,15 +405,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_artist_item_serialize() {
|
||||
// Test that ArtistItem serializes to PascalCase for consistency
|
||||
// ArtistItem serializes to camelCase for the frontend; it still accepts
|
||||
// Jellyfin's PascalCase on deserialize via #[serde(alias = ...)].
|
||||
let artist = ArtistItem {
|
||||
id: "test-id".to_string(),
|
||||
name: "Test Artist".to_string(),
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&artist).expect("Failed to serialize");
|
||||
assert!(json.contains(r#""Id":"test-id""#));
|
||||
assert!(json.contains(r#""Name":"Test Artist""#));
|
||||
assert!(json.contains(r#""id":"test-id""#));
|
||||
assert!(json.contains(r#""name":"Test Artist""#));
|
||||
|
||||
let from_pascal: ArtistItem =
|
||||
serde_json::from_str(r#"{"Id":"x","Name":"Y"}"#).expect("Failed to deserialize");
|
||||
assert_eq!(from_pascal.id, "x");
|
||||
assert_eq!(from_pascal.name, "Y");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
interface Props {
|
||||
itemId: string;
|
||||
imageType?: string;
|
||||
tag?: string;
|
||||
tag?: string | null;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
class?: string;
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
continue;
|
||||
}
|
||||
const type = person.type;
|
||||
if (!type) continue;
|
||||
if (type in groups) {
|
||||
groups[type].push(person);
|
||||
} else {
|
||||
@ -84,7 +85,7 @@
|
||||
<!-- Person image -->
|
||||
<div class="w-24 h-24 rounded-full overflow-hidden bg-[var(--color-surface)] mb-2">
|
||||
<CachedImage
|
||||
itemId={person.id}
|
||||
itemId={person.id ?? ""}
|
||||
imageType="Primary"
|
||||
tag={person.primaryImageTag}
|
||||
maxWidth={200}
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each filteredPeople as person, index (person.id)}
|
||||
<button
|
||||
onclick={(e) => handlePersonClick(person.id, e)}
|
||||
onclick={(e) => handlePersonClick(person.id ?? "", e)}
|
||||
class="text-[var(--color-jellyfin)] hover:underline"
|
||||
>
|
||||
{person.name}
|
||||
|
||||
@ -84,7 +84,7 @@
|
||||
return null;
|
||||
});
|
||||
|
||||
function formatDuration(ticks?: number): string {
|
||||
function formatDuration(ticks?: number | null): string {
|
||||
if (!ticks) return "";
|
||||
const seconds = Math.floor(ticks / 10000000);
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
@ -100,7 +100,7 @@
|
||||
if (!ep.userData || !ep.runTimeTicks) {
|
||||
return 0;
|
||||
}
|
||||
return (ep.userData.playbackPositionTicks / ep.runTimeTicks) * 100;
|
||||
return ((ep.userData.playbackPositionTicks ?? 0) / ep.runTimeTicks) * 100;
|
||||
}
|
||||
|
||||
function handlePlay() {
|
||||
@ -178,7 +178,7 @@
|
||||
{episode.communityRating.toFixed(1)}
|
||||
</span>
|
||||
{/if}
|
||||
{#if episode.userData?.played}
|
||||
{#if episode.userData?.isPlayed}
|
||||
<span class="flex items-center gap-1 text-[var(--color-jellyfin)]">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
||||
@ -281,7 +281,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- Played indicator -->
|
||||
{#if ep.userData?.played}
|
||||
{#if ep.userData?.isPlayed}
|
||||
<div class="absolute top-2 right-2">
|
||||
<svg class="w-5 h-5 text-[var(--color-jellyfin)]" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
if (!episode.userData || !episode.runTimeTicks) {
|
||||
return 0;
|
||||
}
|
||||
return (episode.userData.playbackPositionTicks / episode.runTimeTicks) * 100;
|
||||
return ((episode.userData.playbackPositionTicks ?? 0) / episode.runTimeTicks) * 100;
|
||||
});
|
||||
|
||||
const duration = $derived(formatDuration(episode.runTimeTicks));
|
||||
@ -137,7 +137,7 @@
|
||||
{episode.name}
|
||||
</h3>
|
||||
<!-- Played indicator -->
|
||||
{#if episode.userData?.played}
|
||||
{#if episode.userData?.isPlayed}
|
||||
<svg class="w-4 h-4 flex-shrink-0 text-[var(--color-jellyfin)]" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
||||
</svg>
|
||||
@ -164,10 +164,10 @@
|
||||
<VideoDownloadButton
|
||||
itemId={episode.id}
|
||||
itemName={episode.name}
|
||||
seriesName={episode.seriesName}
|
||||
seasonName={episode.seasonName}
|
||||
episodeNumber={episode.indexNumber}
|
||||
seasonNumber={episode.parentIndexNumber}
|
||||
seriesName={episode.seriesName ?? undefined}
|
||||
seasonName={episode.seasonName ?? undefined}
|
||||
episodeNumber={episode.indexNumber ?? undefined}
|
||||
seasonNumber={episode.parentIndexNumber ?? undefined}
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
function getImageTag(item: MediaItem | Library): string | undefined {
|
||||
return "primaryImageTag" in item ? item.primaryImageTag : ("imageTag" in item ? item.imageTag : undefined);
|
||||
return "primaryImageTag" in item ? (item.primaryImageTag ?? undefined) : ("imageTag" in item ? (item.imageTag ?? undefined) : undefined);
|
||||
}
|
||||
|
||||
function getSubtitle(item: MediaItem | Library): string {
|
||||
@ -44,7 +44,7 @@
|
||||
if (!showProgress || !("userData" in item) || !item.userData || !("runTimeTicks" in item) || !item.runTimeTicks) {
|
||||
return 0;
|
||||
}
|
||||
return (item.userData.playbackPositionTicks / item.runTimeTicks) * 100;
|
||||
return ((item.userData.playbackPositionTicks ?? 0) / item.runTimeTicks) * 100;
|
||||
}
|
||||
|
||||
function getTrackNumber(item: MediaItem | Library): string {
|
||||
@ -61,7 +61,7 @@
|
||||
{@const duration = "runTimeTicks" in item ? formatDuration(item.runTimeTicks) : ""}
|
||||
{@const progress = getProgress(item)}
|
||||
{@const trackNum = getTrackNumber(item)}
|
||||
{@const isPlayed = "userData" in item && item.userData?.played}
|
||||
{@const isPlayed = "userData" in item && item.userData?.isPlayed}
|
||||
{@const downloadInfo = getDownloadInfo(item.id)}
|
||||
{@const isDownloaded = downloadInfo?.status === "completed"}
|
||||
{@const isDownloading = downloadInfo?.status === "downloading" || downloadInfo?.status === "pending"}
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
if (!showProgress || !("userData" in item) || !item.userData || !item.runTimeTicks) {
|
||||
return 0;
|
||||
}
|
||||
return (item.userData.playbackPositionTicks / item.runTimeTicks) * 100;
|
||||
return ((item.userData.playbackPositionTicks ?? 0) / item.runTimeTicks) * 100;
|
||||
});
|
||||
|
||||
const subtitle = $derived(() => {
|
||||
@ -112,7 +112,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- Played indicator -->
|
||||
{#if "userData" in item && item.userData?.played}
|
||||
{#if "userData" in item && item.userData?.isPlayed}
|
||||
<div class="absolute top-2 right-2">
|
||||
<svg class="w-5 h-5 text-[var(--color-jellyfin)]" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
||||
|
||||
@ -145,7 +145,7 @@
|
||||
goto(`/library/${artistId}`);
|
||||
}
|
||||
|
||||
function handleAlbumClick(albumId: string | undefined, e: Event) {
|
||||
function handleAlbumClick(albumId: string | null | undefined, e: Event) {
|
||||
if (!albumId) return;
|
||||
e.stopPropagation();
|
||||
goto(`/library/${albumId}`);
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
let dragDisabled = $state(true);
|
||||
const flipDurationMs = 200;
|
||||
|
||||
function formatDuration(ticks?: number): string {
|
||||
function formatDuration(ticks?: number | null): string {
|
||||
if (!ticks) return "";
|
||||
const seconds = Math.floor(ticks / 10000000);
|
||||
const mins = Math.floor(seconds / 60);
|
||||
|
||||
@ -15,8 +15,8 @@
|
||||
const nowPlaying = $derived(session.nowPlayingItem);
|
||||
const supportsSeek = $derived(playState?.canSeek ?? false);
|
||||
const supportsNextPrevious = $derived(
|
||||
session.supportedCommands.includes("NextTrack") &&
|
||||
session.supportedCommands.includes("PreviousTrack")
|
||||
(session.supportedCommands ?? []).includes("NextTrack") &&
|
||||
(session.supportedCommands ?? []).includes("PreviousTrack")
|
||||
);
|
||||
|
||||
async function handlePlayPause() {
|
||||
@ -107,8 +107,8 @@
|
||||
<h4 class="text-base font-medium text-white truncate">{nowPlaying.name}</h4>
|
||||
{#if nowPlaying.artists && nowPlaying.artists.length > 0}
|
||||
<p class="text-sm text-gray-400 truncate">{nowPlaying.artists.join(", ")}</p>
|
||||
{:else if nowPlaying.albumName}
|
||||
<p class="text-sm text-gray-400 truncate">{nowPlaying.albumName}</p>
|
||||
{:else if nowPlaying?.album}
|
||||
<p class="text-sm text-gray-400 truncate">{nowPlaying?.album}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@ -196,9 +196,9 @@
|
||||
{#if playState.volumeLevel !== undefined}
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
|
||||
{#if playState.isMuted || playState.volumeLevel === 0}
|
||||
{#if playState.isMuted || (playState.volumeLevel ?? 0) === 0}
|
||||
<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z" />
|
||||
{:else if playState.volumeLevel < 50}
|
||||
{:else if (playState.volumeLevel ?? 0) < 50}
|
||||
<path d="M7 9v6h4l5 5V4l-5 5H7z" />
|
||||
{:else}
|
||||
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z" />
|
||||
|
||||
@ -51,11 +51,11 @@
|
||||
<div class="flex items-center gap-3 mt-3 pt-3 border-t border-white/10">
|
||||
<div class="w-12 h-12 rounded overflow-hidden flex-shrink-0">
|
||||
<CachedImage
|
||||
itemId={nowPlaying.id}
|
||||
itemId={nowPlaying.id ?? ""}
|
||||
imageType="Primary"
|
||||
tag={nowPlaying.primaryImageTag}
|
||||
maxWidth={80}
|
||||
alt={nowPlaying.name}
|
||||
alt={nowPlaying.name ?? undefined}
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
@ -64,8 +64,8 @@
|
||||
<p class="text-sm font-medium text-white truncate">{nowPlaying.name}</p>
|
||||
{#if nowPlaying.artists && nowPlaying.artists.length > 0}
|
||||
<p class="text-xs text-gray-400 truncate">{nowPlaying.artists.join(", ")}</p>
|
||||
{:else if nowPlaying.albumName}
|
||||
<p class="text-xs text-gray-400 truncate">{nowPlaying.albumName}</p>
|
||||
{:else if nowPlaying.album}
|
||||
<p class="text-xs text-gray-400 truncate">{nowPlaying.album}</p>
|
||||
{/if}
|
||||
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
|
||||
@ -63,8 +63,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getSessionIcon(client: string): string {
|
||||
const clientLower = client.toLowerCase();
|
||||
function getSessionIcon(client: string | null | undefined): string {
|
||||
const clientLower = (client ?? "").toLowerCase();
|
||||
if (clientLower.includes("tv") || clientLower.includes("roku") || clientLower.includes("android tv")) {
|
||||
return "tv";
|
||||
} else if (clientLower.includes("web") || clientLower.includes("chrome") || clientLower.includes("firefox")) {
|
||||
|
||||
@ -68,7 +68,7 @@
|
||||
<SessionCard
|
||||
{session}
|
||||
selected={$sessions.selectedSessionId === session.id}
|
||||
onclick={() => handleSessionClick(session.id)}
|
||||
onclick={() => handleSessionClick(session.id ?? "")}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@ -80,14 +80,17 @@ describe("downloads store", () => {
|
||||
);
|
||||
|
||||
expect(mockInvoke).toHaveBeenCalledWith("download_item", {
|
||||
request: {
|
||||
itemId: "item-1",
|
||||
userId: "user-1",
|
||||
filePath: "/path/to/file.mp3",
|
||||
mimeType: "audio/mpeg",
|
||||
priority: 10,
|
||||
itemName: undefined,
|
||||
artistName: undefined,
|
||||
albumName: undefined,
|
||||
itemName: null,
|
||||
artistName: null,
|
||||
albumName: null,
|
||||
expectedSize: null,
|
||||
},
|
||||
});
|
||||
expect(downloadId).toBe(123);
|
||||
});
|
||||
|
||||
@ -73,7 +73,7 @@ function createPlaybackModeStore() {
|
||||
* - Polls remote session until track loads
|
||||
* - Stops local playback
|
||||
*/
|
||||
async function transferToRemote(sessionId: string): Promise<void> {
|
||||
async function transferToRemote(sessionId: string | null | undefined): Promise<void> {
|
||||
console.log("[PlaybackMode] Transferring to remote session:", sessionId);
|
||||
update((s) => ({ ...s, isTransferring: true, transferError: null }));
|
||||
|
||||
@ -100,11 +100,11 @@ function createPlaybackModeStore() {
|
||||
}
|
||||
|
||||
// Update local state
|
||||
sessions.selectSession(sessionId);
|
||||
sessions.selectSession(sessionId ?? null);
|
||||
update((s) => ({
|
||||
...s,
|
||||
mode: "remote",
|
||||
remoteSessionId: sessionId,
|
||||
remoteSessionId: sessionId ?? null,
|
||||
isTransferring: false,
|
||||
}));
|
||||
|
||||
|
||||
@ -40,8 +40,15 @@ describe("sessions store", () => {
|
||||
nowPlayingItem: {
|
||||
id: "item-1",
|
||||
name: "Test Song",
|
||||
type: "Audio",
|
||||
serverId: "server-1",
|
||||
Type: "Audio",
|
||||
runTimeTicks: null,
|
||||
album: null,
|
||||
albumId: null,
|
||||
albumArtist: null,
|
||||
artists: null,
|
||||
imageTags: null,
|
||||
primaryImageTag: null,
|
||||
albumPrimaryImageTag: null,
|
||||
},
|
||||
playableMediaTypes: ["Audio", "Video"],
|
||||
supportedCommands: ["PlayPause", "Stop", "Seek", "NextTrack", "PreviousTrack"],
|
||||
|
||||
@ -89,7 +89,7 @@ function createSessionsStore() {
|
||||
/**
|
||||
* Send play/pause toggle command
|
||||
*/
|
||||
async function sendPlayPause(sessionId: string): Promise<void> {
|
||||
async function sendPlayPause(sessionId: string | null | undefined): Promise<void> {
|
||||
try {
|
||||
await invoke("remote_send_command", {
|
||||
sessionId,
|
||||
@ -106,7 +106,7 @@ function createSessionsStore() {
|
||||
/**
|
||||
* Send stop command
|
||||
*/
|
||||
async function sendStop(sessionId: string): Promise<void> {
|
||||
async function sendStop(sessionId: string | null | undefined): Promise<void> {
|
||||
try {
|
||||
await invoke("remote_send_command", {
|
||||
sessionId,
|
||||
@ -122,7 +122,7 @@ function createSessionsStore() {
|
||||
/**
|
||||
* Send next track command
|
||||
*/
|
||||
async function sendNext(sessionId: string): Promise<void> {
|
||||
async function sendNext(sessionId: string | null | undefined): Promise<void> {
|
||||
try {
|
||||
await invoke("remote_send_command", {
|
||||
sessionId,
|
||||
@ -138,7 +138,7 @@ function createSessionsStore() {
|
||||
/**
|
||||
* Send previous track command
|
||||
*/
|
||||
async function sendPrevious(sessionId: string): Promise<void> {
|
||||
async function sendPrevious(sessionId: string | null | undefined): Promise<void> {
|
||||
try {
|
||||
await invoke("remote_send_command", {
|
||||
sessionId,
|
||||
@ -154,7 +154,7 @@ function createSessionsStore() {
|
||||
/**
|
||||
* Seek to position (in ticks)
|
||||
*/
|
||||
async function sendSeek(sessionId: string, positionTicks: number): Promise<void> {
|
||||
async function sendSeek(sessionId: string | null | undefined, positionTicks: number): Promise<void> {
|
||||
try {
|
||||
await invoke("remote_session_seek", {
|
||||
sessionId,
|
||||
@ -170,7 +170,7 @@ function createSessionsStore() {
|
||||
/**
|
||||
* Set volume (0-100)
|
||||
*/
|
||||
async function sendVolume(sessionId: string, volume: number): Promise<void> {
|
||||
async function sendVolume(sessionId: string | null | undefined, volume: number): Promise<void> {
|
||||
try {
|
||||
await invoke("remote_session_set_volume", {
|
||||
sessionId,
|
||||
@ -186,7 +186,7 @@ function createSessionsStore() {
|
||||
/**
|
||||
* Toggle mute
|
||||
*/
|
||||
async function sendToggleMute(sessionId: string): Promise<void> {
|
||||
async function sendToggleMute(sessionId: string | null | undefined): Promise<void> {
|
||||
try {
|
||||
await invoke("remote_send_command", {
|
||||
sessionId,
|
||||
@ -203,7 +203,7 @@ function createSessionsStore() {
|
||||
* Play item(s) on remote session
|
||||
*/
|
||||
async function playOnSession(
|
||||
sessionId: string,
|
||||
sessionId: string | null | undefined,
|
||||
itemIds: string[],
|
||||
startIndex = 0
|
||||
): Promise<void> {
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
* @param format Format type: "mm:ss" (default) or "hh:mm:ss"
|
||||
* @returns Formatted duration string or empty string if no ticks
|
||||
*/
|
||||
export function formatDuration(ticks?: number, format: "mm:ss" | "hh:mm:ss" = "mm:ss"): string {
|
||||
export function formatDuration(ticks?: number | null, format: "mm:ss" | "hh:mm:ss" = "mm:ss"): string {
|
||||
if (!ticks) return "";
|
||||
|
||||
// Jellyfin uses 10,000,000 ticks per second
|
||||
|
||||
@ -25,8 +25,8 @@ export function secondsToTicks(seconds: number): number {
|
||||
* @param ticks - Time in Jellyfin ticks
|
||||
* @returns Time in seconds
|
||||
*/
|
||||
export function ticksToSeconds(ticks: number): number {
|
||||
return ticks / TICKS_PER_SECOND;
|
||||
export function ticksToSeconds(ticks: number | null | undefined): number {
|
||||
return (ticks ?? 0) / TICKS_PER_SECOND;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -456,7 +456,7 @@
|
||||
<div class="space-y-2">
|
||||
{#if item.people.some(p => p.type === "Director")}
|
||||
<CrewLinks
|
||||
people={item.people}
|
||||
people={item.people ?? undefined}
|
||||
roleFilter={["Director"]}
|
||||
label="Directed by"
|
||||
maxShow={3}
|
||||
@ -465,7 +465,7 @@
|
||||
|
||||
{#if item.people.some(p => p.type === "Writer")}
|
||||
<CrewLinks
|
||||
people={item.people}
|
||||
people={item.people ?? undefined}
|
||||
roleFilter={["Writer"]}
|
||||
label="Written by"
|
||||
maxShow={3}
|
||||
@ -474,7 +474,7 @@
|
||||
|
||||
{#if item.people.some(p => p.type === "Composer")}
|
||||
<CrewLinks
|
||||
people={item.people}
|
||||
people={item.people ?? undefined}
|
||||
roleFilter={["Composer"]}
|
||||
label="Music by"
|
||||
maxShow={2}
|
||||
@ -486,13 +486,13 @@
|
||||
<!-- Genre Tags -->
|
||||
{#if item.genres?.length}
|
||||
<div>
|
||||
<GenreTags genres={item.genres} maxShow={6} />
|
||||
<GenreTags genres={item.genres ?? undefined} maxShow={6} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Cast Section - for Movies, Series, and Episodes -->
|
||||
{#if (item.type === "Movie" || item.type === "Series" || item.type === "Episode") && item.people?.length}
|
||||
<CastSection people={item.people} />
|
||||
<CastSection people={item.people ?? undefined} />
|
||||
{/if}
|
||||
|
||||
<!-- Related Items Section - for Movies and Series -->
|
||||
@ -500,8 +500,8 @@
|
||||
<RelatedItemsSection
|
||||
currentItemId={item.id}
|
||||
itemType={item.type}
|
||||
genres={item.genres}
|
||||
people={item.people}
|
||||
genres={item.genres ?? undefined}
|
||||
people={item.people ?? undefined}
|
||||
limit={12}
|
||||
/>
|
||||
{/if}
|
||||
@ -528,7 +528,7 @@
|
||||
<RelatedItemsSection
|
||||
currentItemId={item.id}
|
||||
itemType="MusicAlbum"
|
||||
genres={item.genres}
|
||||
genres={item.genres ?? undefined}
|
||||
artistIds={item.artistItems?.map(a => a.id)}
|
||||
limit={12}
|
||||
/>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user