refactor: MoviePage
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { TMDB_IMAGES_ORIGINAL, TMDB_POSTER_SMALL } from '$lib/constants';
|
||||
import type { TitleType } from '$lib/types';
|
||||
import type { TitleId, TitleType } from '$lib/types';
|
||||
import classNames from 'classnames';
|
||||
import { ChevronLeft, Cross2, DotFilled, ExternalLink } from 'radix-icons-svelte';
|
||||
import Carousel from '../Carousel/Carousel.svelte';
|
||||
@@ -11,15 +11,17 @@
|
||||
export let isModal = false;
|
||||
export let handleCloseModal: () => void = () => {};
|
||||
|
||||
export let tmdbId: number;
|
||||
export let type: TitleType;
|
||||
|
||||
export let backdropUriCandidates: string[];
|
||||
export let posterPath: string;
|
||||
export let title: string;
|
||||
|
||||
export let tagline: string;
|
||||
export let overview: string;
|
||||
export let titleInformation:
|
||||
| {
|
||||
tmdbId: number;
|
||||
type: TitleType;
|
||||
title: string;
|
||||
tagline: string;
|
||||
overview: string;
|
||||
backdropUriCandidates: string[];
|
||||
posterPath: string;
|
||||
}
|
||||
| undefined = undefined;
|
||||
|
||||
let topHeight: number;
|
||||
let bottomHeight: number;
|
||||
@@ -42,9 +44,14 @@
|
||||
fixed: !isModal
|
||||
})}
|
||||
>
|
||||
<LazyImg src={TMDB_IMAGES_ORIGINAL + getBackdropUri(backdropUriCandidates)} class="h-full">
|
||||
<div class="absolute inset-0 bg-darken" />
|
||||
</LazyImg>
|
||||
{#if titleInformation}
|
||||
<LazyImg
|
||||
src={TMDB_IMAGES_ORIGINAL + getBackdropUri(titleInformation.backdropUriCandidates)}
|
||||
class="h-full"
|
||||
>
|
||||
<div class="absolute inset-0 bg-darken" />
|
||||
</LazyImg>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Mobile -->
|
||||
@@ -52,38 +59,13 @@
|
||||
style={'height: ' + imageHeight.toFixed() + 'px'}
|
||||
class="sm:hidden fixed inset-x-0 bg-center bg-cover bg-stone-950"
|
||||
>
|
||||
<LazyImg src={TMDB_IMAGES_ORIGINAL + posterPath} class="h-full">
|
||||
<div class="absolute inset-0 bg-darken" />
|
||||
</LazyImg>
|
||||
{#if titleInformation}
|
||||
<LazyImg src={TMDB_IMAGES_ORIGINAL + titleInformation.posterPath} class="h-full">
|
||||
<div class="absolute inset-0 bg-darken" />
|
||||
</LazyImg>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- <div
|
||||
style={"background-image: url('" +
|
||||
TMDB_IMAGES_ORIGINAL +
|
||||
getBackdropUri(backdropUriCandidates) +
|
||||
"'); height: " +
|
||||
imageHeight.toFixed() +
|
||||
'px'}
|
||||
class={classNames('hidden sm:block inset-x-0 bg-center bg-cover bg-stone-950', {
|
||||
absolute: isModal,
|
||||
fixed: !isModal
|
||||
})}
|
||||
>
|
||||
<div class="absolute inset-0 bg-darken" />
|
||||
</div> -->
|
||||
|
||||
<!-- <div
|
||||
style={"background-image: url('" +
|
||||
TMDB_IMAGES_ORIGINAL +
|
||||
posterPath +
|
||||
"'); height: " +
|
||||
imageHeight.toFixed() +
|
||||
'px'}
|
||||
class="sm:hidden fixed inset-x-0 bg-center bg-cover bg-stone-950"
|
||||
>
|
||||
<div class="absolute inset-0 bg-darken" />
|
||||
</div> -->
|
||||
|
||||
<div
|
||||
class={classNames('flex flex-col relative z-[1]', {
|
||||
'h-[85vh] sm:h-screen': !isModal,
|
||||
@@ -97,11 +79,16 @@
|
||||
bind:clientHeight={topHeight}
|
||||
>
|
||||
{#if isModal}
|
||||
<a href={`/${type}/${tmdbId}`} class="absolute top-8 right-4 sm:right-8 z-10">
|
||||
<IconButton>
|
||||
<ExternalLink size={20} />
|
||||
</IconButton>
|
||||
</a>
|
||||
{#if titleInformation}
|
||||
<a
|
||||
href={`/${titleInformation.type}/${titleInformation.tmdbId}`}
|
||||
class="absolute top-8 right-4 sm:right-8 z-10"
|
||||
>
|
||||
<IconButton>
|
||||
<ExternalLink size={20} />
|
||||
</IconButton>
|
||||
</a>
|
||||
{/if}
|
||||
<div class="absolute top-8 left-4 sm:left-8 z-10">
|
||||
<button class="flex items-center sm:hidden font-medium" on:click={handleCloseModal}>
|
||||
<ChevronLeft size={20} />
|
||||
@@ -116,26 +103,26 @@
|
||||
{/if}
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-stone-950 to-30%" />
|
||||
<div class="z-[1] flex-1 flex justify-end gap-8 items-end max-w-screen-2xl mx-auto">
|
||||
<div
|
||||
class="aspect-[2/3] w-52 bg-center bg-cover rounded-md hidden sm:block"
|
||||
style={"background-image: url('" + TMDB_POSTER_SMALL + posterPath + "')"}
|
||||
/>
|
||||
{#if titleInformation}
|
||||
<div
|
||||
class="aspect-[2/3] w-52 bg-center bg-cover rounded-md hidden sm:block"
|
||||
style={"background-image: url('" + TMDB_POSTER_SMALL + titleInformation.posterPath + "')"}
|
||||
/>
|
||||
{:else}
|
||||
<div class="aspect-[2/3] w-52 bg-center bg-cover rounded-md hidden sm:block placeholder" />
|
||||
{/if}
|
||||
<div class="flex-1 flex gap-4 justify-between flex-col lg:flex-row lg:items-end">
|
||||
<div>
|
||||
<div class="text-zinc-300 text-sm uppercase font-semibold flex items-center gap-1">
|
||||
<p class="flex-shrink-0">
|
||||
<slot name="title-info-1" />
|
||||
</p>
|
||||
<DotFilled />
|
||||
<p class="flex-shrink-0">
|
||||
<slot name="title-info-2" />
|
||||
</p>
|
||||
<DotFilled />
|
||||
<p class="flex-shrink-0"><slot name="title-info-3" /></p>
|
||||
<!-- <DotFilled />
|
||||
<p class="line-clamp-1">{series?.genres?.map((g) => g.name).join(', ')}</p> -->
|
||||
<slot name="title-info">
|
||||
<div class="placeholder-text">Placeholder Long</div>
|
||||
</slot>
|
||||
</div>
|
||||
<h1 class="text-4xl sm:text-5xl md:text-6xl font-semibold">{title}</h1>
|
||||
{#if titleInformation}
|
||||
<h1 class="text-4xl sm:text-5xl md:text-6xl font-semibold">{titleInformation.title}</h1>
|
||||
{:else}
|
||||
<h1 class="text-4xl sm:text-5xl md:text-6xl placeholder-text mt-2">Placeholder</h1>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<slot name="title-right" />
|
||||
@@ -162,9 +149,10 @@
|
||||
<div
|
||||
class="flex flex-col gap-3 max-w-5xl row-span-3 col-span-4 sm:col-span-6 lg:col-span-3 mb-4 lg:mr-8"
|
||||
>
|
||||
<div class="flex gap-4 justify-between">
|
||||
<h1 class="font-semibold text-xl sm:text-2xl">{tagline}</h1>
|
||||
<!-- <div class="flex items-center gap-4">
|
||||
{#if titleInformation}
|
||||
<div class="flex gap-4 justify-between">
|
||||
<h1 class="font-semibold text-xl sm:text-2xl">{titleInformation.tagline}</h1>
|
||||
<!-- <div class="flex items-center gap-4">
|
||||
<a
|
||||
target="_blank"
|
||||
href={'https://www.themoviedb.org/tv/' + tmdbId}
|
||||
@@ -192,8 +180,22 @@
|
||||
<Globe size={15} />
|
||||
</a>
|
||||
{/if} -->
|
||||
</div>
|
||||
<p class="pl-4 border-l-2 text-sm sm:text-base">{overview}</p>
|
||||
</div>
|
||||
<p class="pl-4 border-l-2 text-sm sm:text-base">{titleInformation.overview}</p>
|
||||
{:else}
|
||||
<div class="flex gap-4 justify-between">
|
||||
<h1 class="font-semibold text-xl sm:text-2xl placeholder-text">Placeholder</h1>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="mr-4 placeholder w-1 flex-shrink-0 rounded" />
|
||||
<p class="text-sm sm:text-base placeholder-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet sem eget
|
||||
dolor lobortis mollis. Aliquam semper imperdiet mi nec viverra. Praesent ac ligula
|
||||
congue, aliquam diam nec, ullamcorper libero. Nunc mattis rhoncus justo, ac pretium
|
||||
urna vehicula et.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</slot>
|
||||
<slot name="info-components" />
|
||||
|
||||
@@ -24,41 +24,51 @@
|
||||
import { settings } from '$lib/stores/settings.store';
|
||||
import { formatMinutesToTime, formatSize } from '$lib/utils';
|
||||
import classNames from 'classnames';
|
||||
import { Archive, ChevronRight, Plus } from 'radix-icons-svelte';
|
||||
import { Archive, ChevronRight, DotFilled, Plus } from 'radix-icons-svelte';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
|
||||
export let tmdbId: number;
|
||||
export let isModal = false;
|
||||
export let handleCloseModal: () => void = () => {};
|
||||
|
||||
const tmdbUrl = 'https://www.themoviedb.org/movie/' + tmdbId;
|
||||
const data = loadInitialPageData();
|
||||
|
||||
const jellyfinItemStore = createJellyfinItemStore(tmdbId);
|
||||
const radarrMovieStore = createRadarrMovieStore(tmdbId);
|
||||
const radarrDownloadStore = createRadarrDownloadStore(radarrMovieStore);
|
||||
|
||||
const jellyfinItem = $jellyfinItemStore.item;
|
||||
const radarrMovie = $radarrMovieStore.item;
|
||||
async function loadInitialPageData() {
|
||||
const tmdbMoviePromise = getTmdbMovie(tmdbId);
|
||||
|
||||
const tmdbMoviePromise = getTmdbMovie(tmdbId);
|
||||
const tmdbRecommendationProps = getTmdbMovieRecommendations(tmdbId)
|
||||
.then((r) => Promise.all(r.map(fetchCardTmdbProps)))
|
||||
.then((r) => r.filter((p) => p.backdropUrl));
|
||||
const tmdbSimilarProps = getTmdbMovieSimilar(tmdbId)
|
||||
.then((r) => Promise.all(r.map(fetchCardTmdbProps)))
|
||||
.then((r) => r.filter((p) => p.backdropUrl));
|
||||
const castProps: Promise<ComponentProps<PeopleCard>[]> = tmdbMoviePromise.then((m) =>
|
||||
Promise.all(
|
||||
m?.credits?.cast?.slice(0, 20).map((m) => ({
|
||||
tmdbId: m.id || 0,
|
||||
backdropUri: m.profile_path || '',
|
||||
name: m.name || '',
|
||||
subtitle: m.character || m.known_for_department || ''
|
||||
})) || []
|
||||
)
|
||||
);
|
||||
const tmdbRecommendationProps = getTmdbMovieRecommendations(tmdbId)
|
||||
.then((r) => Promise.all(r.map(fetchCardTmdbProps)))
|
||||
.then((r) => r.filter((p) => p.backdropUrl));
|
||||
const tmdbSimilarProps = getTmdbMovieSimilar(tmdbId)
|
||||
.then((r) => Promise.all(r.map(fetchCardTmdbProps)))
|
||||
.then((r) => r.filter((p) => p.backdropUrl));
|
||||
|
||||
const castPropsPromise: Promise<ComponentProps<PeopleCard>[]> = tmdbMoviePromise.then((m) =>
|
||||
Promise.all(
|
||||
m?.credits?.cast?.slice(0, 20).map((m) => ({
|
||||
tmdbId: m.id || 0,
|
||||
backdropUri: m.profile_path || '',
|
||||
name: m.name || '',
|
||||
subtitle: m.character || m.known_for_department || ''
|
||||
})) || []
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
tmdbMovie: await tmdbMoviePromise,
|
||||
tmdbRecommendationProps: await tmdbRecommendationProps,
|
||||
tmdbSimilarProps: await tmdbSimilarProps,
|
||||
castProps: await castPropsPromise
|
||||
};
|
||||
}
|
||||
|
||||
function play() {
|
||||
if (jellyfinItem?.Id) playerState.streamJellyfinId(jellyfinItem?.Id);
|
||||
if ($jellyfinItemStore.item?.Id) playerState.streamJellyfinId($jellyfinItemStore.item?.Id);
|
||||
}
|
||||
|
||||
async function refreshRadarr() {
|
||||
@@ -74,42 +84,45 @@
|
||||
}
|
||||
|
||||
function openRequestModal() {
|
||||
if (!radarrMovie?.id) return;
|
||||
if (!$radarrMovieStore.item?.id) return;
|
||||
|
||||
modalStack.create(RequestModal, {
|
||||
radarrId: radarrMovie?.id
|
||||
radarrId: $radarrMovieStore.item?.id
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await tmdbMoviePromise then movie}
|
||||
{#await data}
|
||||
<TitlePageLayout {isModal} {handleCloseModal} />
|
||||
{:then { tmdbMovie, tmdbRecommendationProps, tmdbSimilarProps, castProps }}
|
||||
{@const movie = tmdbMovie}
|
||||
<TitlePageLayout
|
||||
titleInformation={{
|
||||
tmdbId,
|
||||
type: 'movie',
|
||||
title: movie?.title || 'Movie',
|
||||
backdropUriCandidates: movie?.images?.backdrops?.map((b) => b.file_path || '') || [],
|
||||
posterPath: movie?.poster_path || '',
|
||||
tagline: movie?.tagline || movie?.title || '',
|
||||
overview: movie?.overview || ''
|
||||
}}
|
||||
{isModal}
|
||||
{tmdbId}
|
||||
{handleCloseModal}
|
||||
type="movie"
|
||||
title={movie?.title || 'Movie'}
|
||||
backdropUriCandidates={movie?.images?.backdrops?.map((b) => b.file_path || '') || []}
|
||||
posterPath={movie?.poster_path || ''}
|
||||
tagline={movie?.tagline || movie?.title || ''}
|
||||
overview={movie?.overview || ''}
|
||||
>
|
||||
<svelte:fragment slot="title-info-1"
|
||||
>{new Date(movie?.release_date || Date.now()).getFullYear()}</svelte:fragment
|
||||
>
|
||||
<svelte:fragment slot="title-info-2">
|
||||
{@const progress = jellyfinItem?.UserData?.PlayedPercentage}
|
||||
<svelte:fragment slot="title-info">
|
||||
{new Date(movie?.release_date || Date.now()).getFullYear()}
|
||||
<DotFilled />
|
||||
{@const progress = $jellyfinItemStore.item?.UserData?.PlayedPercentage}
|
||||
{#if progress}
|
||||
{progress.toFixed()} min left
|
||||
{:else}
|
||||
{movie?.runtime} min
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title-info-3">
|
||||
<DotFilled />
|
||||
<a href={tmdbUrl} target="_blank">{movie?.vote_average?.toFixed(1)} TMDB</a>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="episodes-carousel">
|
||||
{@const progress = jellyfinItem?.UserData?.PlayedPercentage}
|
||||
{@const progress = $jellyfinItemStore.item?.UserData?.PlayedPercentage}
|
||||
{#if progress}
|
||||
<div
|
||||
class={classNames('px-2 sm:px-4 lg:px-8', {
|
||||
@@ -128,6 +141,8 @@
|
||||
{#if $jellyfinItemStore.loading || $radarrMovieStore.loading}
|
||||
<div class="placeholder h-10 w-48 rounded-xl" />
|
||||
{:else}
|
||||
{@const jellyfinItem = $jellyfinItemStore.item}
|
||||
{@const radarrMovie = $radarrMovieStore.item}
|
||||
<OpenInButton title={movie?.title} {jellyfinItem} {radarrMovie} type="movie" {tmdbId} />
|
||||
{#if jellyfinItem}
|
||||
<Button type="primary" on:click={play}>
|
||||
@@ -203,6 +218,7 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="servarr-components">
|
||||
{@const radarrMovie = $radarrMovieStore.item}
|
||||
{#if radarrMovie}
|
||||
{#if radarrMovie?.movieFile?.quality}
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
@@ -284,11 +300,3 @@
|
||||
</svelte:fragment>
|
||||
</TitlePageLayout>
|
||||
{/await}
|
||||
|
||||
<!-- {#if requestModalVisible} -->
|
||||
<!-- {@const radarrMovie = $itemStore.item?.radarrMovie} -->
|
||||
<!-- {#if radarrMovie && radarrMovie.id} -->
|
||||
<!-- <RequestModal modalProps={requestModalProps} radarrId={radarrMovie.id} /> -->
|
||||
<!-- {/if} -->
|
||||
<!-- {/if} -->
|
||||
<!-- -->
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
import type { TitleId } from '$lib/types';
|
||||
import { capitalize, formatMinutesToTime, formatSize } from '$lib/utils';
|
||||
import classNames from 'classnames';
|
||||
import { Archive, ChevronLeft, ChevronRight, Plus } from 'radix-icons-svelte';
|
||||
import { Archive, ChevronLeft, ChevronRight, DotFilled, Plus } from 'radix-icons-svelte';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
@@ -46,9 +46,43 @@
|
||||
const sonarrDownloadStore = createSonarrDownloadStore(sonarrSeriesStore);
|
||||
|
||||
let seasonSelectVisible = false;
|
||||
let visibleSeasonNumber: number | undefined = undefined;
|
||||
let visibleSeasonNumber: number = 1;
|
||||
let visibleEpisodeIndex: number | undefined = undefined;
|
||||
|
||||
let jellyfinEpisodeData: {
|
||||
[key: string]: {
|
||||
jellyfinId: string | undefined;
|
||||
progress: number;
|
||||
watched: boolean;
|
||||
};
|
||||
} = {};
|
||||
let episodeComponents: HTMLDivElement[] = [];
|
||||
let nextJellyfinEpisode: JellyfinItem | undefined = undefined;
|
||||
|
||||
// Refresh jellyfin episode data
|
||||
jellyfinItemStore.subscribe(async (value) => {
|
||||
const item = value.item;
|
||||
if (!item?.Id) return;
|
||||
const episodes = await getJellyfinEpisodes(item.Id);
|
||||
|
||||
episodes?.forEach((episode) => {
|
||||
const key = `S${episode?.ParentIndexNumber}E${episode?.IndexNumber}`;
|
||||
|
||||
if (!nextJellyfinEpisode && episode?.UserData?.Played === false) {
|
||||
nextJellyfinEpisode = episode;
|
||||
}
|
||||
|
||||
jellyfinEpisodeData[key] = {
|
||||
jellyfinId: episode?.Id,
|
||||
progress: episode?.UserData?.PlayedPercentage || 0,
|
||||
watched: episode?.UserData?.Played || false
|
||||
};
|
||||
});
|
||||
|
||||
if (!nextJellyfinEpisode) nextJellyfinEpisode = episodes?.[0];
|
||||
visibleSeasonNumber = nextJellyfinEpisode?.ParentIndexNumber || visibleSeasonNumber;
|
||||
});
|
||||
|
||||
async function loadInitialPageData() {
|
||||
const tmdbId = await (titleId.provider === 'tvdb'
|
||||
? getTmdbIdFromTvdbId(titleId.id)
|
||||
@@ -107,74 +141,6 @@
|
||||
};
|
||||
}
|
||||
|
||||
let jellyfinEpisodeData: {
|
||||
[key: string]: {
|
||||
jellyfinId: string | undefined;
|
||||
progress: number;
|
||||
watched: boolean;
|
||||
};
|
||||
} = {};
|
||||
let episodeComponents: HTMLDivElement[] = [];
|
||||
let nextJellyfinEpisode: JellyfinItem | undefined = undefined;
|
||||
|
||||
// Refresh jellyfin episode data
|
||||
jellyfinItemStore.subscribe(async (value) => {
|
||||
const item = value.item;
|
||||
if (!item?.Id) return;
|
||||
const episodes = await getJellyfinEpisodes(item.Id);
|
||||
|
||||
episodes?.forEach((episode) => {
|
||||
const key = `S${episode?.ParentIndexNumber}E${episode?.IndexNumber}`;
|
||||
|
||||
if (!nextJellyfinEpisode && episode?.UserData?.Played === false) {
|
||||
nextJellyfinEpisode = episode;
|
||||
}
|
||||
|
||||
jellyfinEpisodeData[key] = {
|
||||
jellyfinId: episode?.Id,
|
||||
progress: episode?.UserData?.PlayedPercentage || 0,
|
||||
watched: episode?.UserData?.Played || false
|
||||
};
|
||||
});
|
||||
|
||||
if (!nextJellyfinEpisode) nextJellyfinEpisode = episodes?.[0];
|
||||
visibleSeasonNumber = nextJellyfinEpisode?.ParentIndexNumber || visibleSeasonNumber || 1;
|
||||
});
|
||||
|
||||
// jellyfinItemStore.promise.then(async (jellyfinItem) => {
|
||||
// const jellyfinEpisodes = jellyfinItem?.Id ? await getJellyfinEpisodes(jellyfinItem?.Id) : [];
|
||||
// const tmdbSeasons = await tmdbSeasonsPromise;
|
||||
|
||||
// tmdbSeasons.forEach((season) => {
|
||||
// const episodes: ComponentProps<EpisodeCard>[] = [];
|
||||
// season?.episodes?.forEach((tmdbEpisode) => {
|
||||
// const jellyfinEpisode = jellyfinEpisodes?.find(
|
||||
// (e) =>
|
||||
// e?.IndexNumber === tmdbEpisode?.episode_number &&
|
||||
// e?.ParentIndexNumber === tmdbEpisode?.season_number
|
||||
// );
|
||||
|
||||
// if (!nextJellyfinEpisode && jellyfinEpisode?.UserData?.Played === false) {
|
||||
// nextJellyfinEpisode = jellyfinEpisode;
|
||||
// }
|
||||
|
||||
// episodes.push({
|
||||
// title: tmdbEpisode?.name || '',
|
||||
// subtitle: `Episode ${tmdbEpisode?.episode_number}`,
|
||||
// backdropUrl: TMDB_BACKDROP_SMALL + tmdbEpisode?.still_path || '',
|
||||
// progress: jellyfinEpisode?.UserData?.PlayedPercentage || 0,
|
||||
// watched: jellyfinEpisode?.UserData?.Played || false,
|
||||
// jellyfinId: jellyfinEpisode?.Id,
|
||||
// airDate: tmdbEpisode.air_date ? new Date(tmdbEpisode.air_date) : undefined
|
||||
// });
|
||||
// });
|
||||
// episodeProps[season?.season_number || 0] = episodes;
|
||||
// });
|
||||
|
||||
// if (!nextJellyfinEpisode) nextJellyfinEpisode = jellyfinEpisodes?.[0];
|
||||
// visibleSeasonNumber = nextJellyfinEpisode?.ParentIndexNumber || visibleSeasonNumber || 1;
|
||||
// });
|
||||
|
||||
function playNextEpisode() {
|
||||
if (nextJellyfinEpisode?.Id) playerState.streamJellyfinId(nextJellyfinEpisode?.Id || '');
|
||||
}
|
||||
@@ -231,23 +197,39 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await data then { tmdbSeries, tmdbId, ...data }}
|
||||
{#await data}
|
||||
<TitlePageLayout {isModal} {handleCloseModal}>
|
||||
<div slot="episodes-carousel">
|
||||
<Carousel
|
||||
gradientFromColor="from-stone-950"
|
||||
class={classNames('px-2 sm:px-4 lg:px-8', {
|
||||
'2xl:px-0': !isModal
|
||||
})}
|
||||
heading="Episodes"
|
||||
>
|
||||
<CarouselPlaceholderItems />
|
||||
</Carousel>
|
||||
</div>
|
||||
</TitlePageLayout>
|
||||
{:then { tmdbSeries, tmdbId, ...data }}
|
||||
<TitlePageLayout
|
||||
{tmdbId}
|
||||
type="series"
|
||||
titleInformation={{
|
||||
tmdbId,
|
||||
type: 'series',
|
||||
backdropUriCandidates: tmdbSeries?.images?.backdrops?.map((b) => b.file_path || '') || [],
|
||||
posterPath: tmdbSeries?.poster_path || '',
|
||||
title: tmdbSeries?.name || '',
|
||||
tagline: tmdbSeries?.tagline || tmdbSeries?.name || '',
|
||||
overview: tmdbSeries?.overview || ''
|
||||
}}
|
||||
{isModal}
|
||||
{handleCloseModal}
|
||||
backdropUriCandidates={tmdbSeries?.images?.backdrops?.map((b) => b.file_path || '') || []}
|
||||
posterPath={tmdbSeries?.poster_path || ''}
|
||||
title={tmdbSeries?.name || ''}
|
||||
tagline={tmdbSeries?.tagline || tmdbSeries?.name || ''}
|
||||
overview={tmdbSeries?.overview || ''}
|
||||
>
|
||||
<svelte:fragment slot="title-info-1">
|
||||
<svelte:fragment slot="title-info">
|
||||
{new Date(tmdbSeries?.first_air_date || Date.now()).getFullYear()}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title-info-2">{tmdbSeries?.status}</svelte:fragment>
|
||||
<svelte:fragment slot="title-info-3">
|
||||
<DotFilled />
|
||||
{tmdbSeries?.status}
|
||||
<DotFilled />
|
||||
<a href={data.tmdbUrl} target="_blank">{tmdbSeries?.vote_average?.toFixed(1)} TMDB</a>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -295,7 +277,7 @@
|
||||
<UiCarousel slot="title" class="flex gap-6">
|
||||
{#each [...Array(tmdbSeries?.number_of_seasons || 0).keys()].map((i) => i + 1) as seasonNumber}
|
||||
{@const season = tmdbSeries?.seasons?.find((s) => s.season_number === seasonNumber)}
|
||||
{@const isSelected = season?.season_number === (visibleSeasonNumber || 1)}
|
||||
{@const isSelected = season?.season_number === visibleSeasonNumber}
|
||||
<button
|
||||
class={classNames(
|
||||
'font-medium tracking-wide transition-colors flex-shrink-0 flex items-center gap-1',
|
||||
@@ -329,7 +311,7 @@
|
||||
{/each}
|
||||
</UiCarousel>
|
||||
{#key visibleSeasonNumber}
|
||||
{#each data.tmdbEpisodeProps[(visibleSeasonNumber || 1) - 1] || [] as props, i}
|
||||
{#each data.tmdbEpisodeProps[visibleSeasonNumber - 1] || [] as props, i}
|
||||
{@const jellyfinData = jellyfinEpisodeData[`S${visibleSeasonNumber}E${i + 1}`]}
|
||||
<div bind:this={episodeComponents[i]}>
|
||||
<EpisodeCard
|
||||
|
||||
Reference in New Issue
Block a user