diff --git a/src/lib/apis/tmdb/tmdbApi.ts b/src/lib/apis/tmdb/tmdbApi.ts index a234aa9..0f8f6ec 100644 --- a/src/lib/apis/tmdb/tmdbApi.ts +++ b/src/lib/apis/tmdb/tmdbApi.ts @@ -260,6 +260,24 @@ export const getTmdbSeriesCredits = (tmdbId: number) => } }).then((res) => res.data?.cast || []); +export const getTmdbMovieRecommendations = (tmdbId: number) => + TmdbApiOpen.get('/3/movie/{movie_id}/recommendations', { + params: { + path: { + movie_id: tmdbId + } + } + }).then((res) => res.data?.results || []); + +export const getTmdbMovieSimilar = (tmdbId: number) => + TmdbApiOpen.get('/3/movie/{movie_id}/similar', { + params: { + path: { + movie_id: tmdbId + } + } + }).then((res) => res.data?.results || []); + // Deprecated hereon forward /** @deprecated */ diff --git a/src/lib/components/DetailsPage/DetailsPage.svelte b/src/lib/components/DetailsPage/DetailsPage.svelte new file mode 100644 index 0000000..8a3cc4d --- /dev/null +++ b/src/lib/components/DetailsPage/DetailsPage.svelte @@ -0,0 +1,127 @@ + + +
+
+
+
+ +
+ +
+ +
+
+ +
+
+

{tagline}

+ +
+

{overview}

+
+
+ + +
+
+
+
+ +
+
+ + + + + + +
+
+ + + + + + +
+
+ + + + + + +
+
diff --git a/src/lib/components/Modal/Modal.ts b/src/lib/components/Modal/Modal.ts index 99c75a0..0a99d9a 100644 --- a/src/lib/components/Modal/Modal.ts +++ b/src/lib/components/Modal/Modal.ts @@ -48,7 +48,7 @@ export function createModalProps(onClose: () => void, onBack?: () => void) { return { close, - back, + back: onBack ? back : undefined, id }; } diff --git a/src/routes/movie/[id]/+page.server.ts b/src/routes/movie/[id]/+page.server.ts deleted file mode 100644 index 079482e..0000000 --- a/src/routes/movie/[id]/+page.server.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getTmdbMovie } from '$lib/apis/tmdb/tmdbApi'; -import type { PageServerLoad } from './$types'; - -export const load = (async ({ params }) => { - return { - movie: await getTmdbMovie(Number(params.id)) - }; -}) satisfies PageServerLoad; diff --git a/src/routes/movie/[id]/+page.svelte b/src/routes/movie/[id]/+page.svelte index bf1b664..993aa6c 100644 --- a/src/routes/movie/[id]/+page.svelte +++ b/src/routes/movie/[id]/+page.svelte @@ -1,28 +1,13 @@ -{#await $library then libraryData} - {#if data.movie} - {@const movie = data.movie} - g.name || '') || []} - runtime={movie?.runtime || 0} - tmdbRating={movie?.vote_average || 0} - starring={movie?.credits?.cast?.slice(0, 5)} - videos={movie.videos?.results || []} - backdropPath={movie?.backdrop_path || ''} - showDetails={true} - jellyfinId={libraryData.items[movie.id]?.jellyfinId} - /> - {/if} -{/await} +{#key tmdbId} + +{/key} diff --git a/src/routes/movie/[id]/+page.ts b/src/routes/movie/[id]/+page.ts new file mode 100644 index 0000000..7f19f60 --- /dev/null +++ b/src/routes/movie/[id]/+page.ts @@ -0,0 +1,7 @@ +import type { PageLoad } from './$types'; + +export const load = (async ({ params }) => { + return { + tmdbId: params.id + }; +}) satisfies PageLoad; diff --git a/src/routes/movie/[id]/+server.ts b/src/routes/movie/[id]/+server.ts deleted file mode 100644 index cb2c6b1..0000000 --- a/src/routes/movie/[id]/+server.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { error, json } from '@sveltejs/kit'; -import type { RequestHandler } from '@sveltejs/kit'; -import { getJellyfinItemByTmdbId } from '$lib/apis/jellyfin/jellyfinApi'; -import { getRadarrMovieByTmdbId, getRadarrDownloadsById } from '$lib/apis/radarr/radarrApi'; - -export const _parseMovieId = (params: any) => { - const { id: tmdbId } = params; - - if (!tmdbId) throw error(400, 'NO_TMDB_ID'); - - return tmdbId; -}; - -export const GET = (async ({ params }) => { - const tmdbId = _parseMovieId(params); - - const jellyfinMoviePromise = getJellyfinItemByTmdbId(tmdbId); - const radarrMoviePromise = getRadarrMovieByTmdbId(tmdbId); - const radarrMovieQueuedPromise = radarrMoviePromise.then((movie) => - movie?.id ? getRadarrDownloadsById(movie.id) : undefined - ); - - const [jellyfinItem, radarrMovie, radarrDownloads] = await Promise.all([ - jellyfinMoviePromise, - radarrMoviePromise, - radarrMovieQueuedPromise - ]); - - return json({ - canStream: !!jellyfinItem, - hasLocalFiles: radarrMovie?.hasFile || !!jellyfinItem, - isAdded: !!radarrMovie, - isDownloading: !!radarrDownloads?.length, - - jellyfinItem, - radarrMovie, - radarrDownloads - }); -}) satisfies RequestHandler; diff --git a/src/routes/movie/[id]/MoviePage.svelte b/src/routes/movie/[id]/MoviePage.svelte new file mode 100644 index 0000000..f9911b1 --- /dev/null +++ b/src/routes/movie/[id]/MoviePage.svelte @@ -0,0 +1,306 @@ + + +{#await tmdbMoviePromise then movie} + + {new Date(movie?.release_date || Date.now()).getFullYear()} + {movie?.runtime} min + {movie?.vote_average?.toFixed(1)} TMDB + + {@const progress = $itemStore.item?.continueWatching?.progress} + {#if progress} +
+
+
+ {/if} + + + + {#if $itemStore.loading} +
+ {:else if $itemStore.item?.jellyfinItem} + + {:else if !$itemStore.item?.radarrMovie} + + {:else} + + {/if} + + + +
+

Directed By

+

+ {movie?.credits.crew?.filter((c) => c.job == 'Director').map((p) => p.name)} +

+
+
+

Release Date

+

+ {new Date(movie?.release_date || Date.now()).toLocaleDateString('en', { + year: 'numeric', + month: 'short', + day: 'numeric' + })} +

+
+ {#if movie?.budget} +
+

Budget

+

+ {movie?.budget?.toLocaleString('en-US', { + style: 'currency', + currency: 'USD' + })} +

+
+ {/if} + {#if movie?.revenue} +
+

Revenue

+

+ {movie?.revenue?.toLocaleString('en-US', { + style: 'currency', + currency: 'USD' + })} +

+
+ {/if} +
+

Status

+

+ {movie?.status} +

+
+
+

Runtime

+

+ {movie?.runtime} Minutes +

+
+ +
+ + + {#if !$itemStore.loading && $itemStore.item} + {@const item = $itemStore.item} + {#if item.radarrMovie?.movieFile?.quality} +
+

Video

+

+ {item.radarrMovie?.movieFile?.quality.quality?.name} +

+
+ {/if} + {#if item.radarrMovie?.movieFile?.size} +
+

Size On Disk

+

+ {formatSize(item.radarrMovie?.movieFile?.size || 0)} +

+
+ {/if} + {#if $itemStore.item?.download} +
+

Download Completed In

+

+ {formatMinutesToTime( + (new Date($itemStore.item?.download.completionTime).getTime() - Date.now()) / + 1000 / + 60 + )} +

+
+ {/if} + +
+ + +
+ + {:else if $itemStore.loading} +
+
+
+
+ {/if} + + +
Cast & Crew
+ + {#await castProps} + + {:then props} + {#each props as prop} + + {/each} + {/await} + + +
Recommendations
+ + {#await tmdbRecommendationProps} + + {:then props} + {#each props as prop} + + {/each} + {/await} + + +
Similar Titles
+ + {#await tmdbSimilarProps} + + {:then props} + {#each props as prop} + + {/each} + {/await} + + +{/await} + +{#if requestModalVisible} + {@const radarrMovie = $itemStore.item?.radarrMovie} + {#if radarrMovie && radarrMovie.id && radarrMovie?.movieFile} + + {/if} +{/if} diff --git a/src/routes/movie/[id]/file/+server.ts b/src/routes/movie/[id]/file/+server.ts deleted file mode 100644 index e9b8067..0000000 --- a/src/routes/movie/[id]/file/+server.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { RequestHandler } from '@sveltejs/kit'; -import { json } from '@sveltejs/kit'; -import { _parseMovieId } from '../+server'; -import { addMovieToRadarr, deleteRadarrMovie } from '$lib/apis/radarr/radarrApi'; - -// Delete download -export const DELETE = (async ({ params }) => { - const radarrMovieId = _parseMovieId(params); - - const success = await deleteRadarrMovie(radarrMovieId); - - return json({ success }); -}) satisfies RequestHandler; diff --git a/src/routes/movie/[id]/radarr/+server.ts b/src/routes/movie/[id]/radarr/+server.ts deleted file mode 100644 index 912189c..0000000 --- a/src/routes/movie/[id]/radarr/+server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { _parseMovieId } from '../+server'; -import { addMovieToRadarr, getRadarrMovieByTmdbId } from '$lib/apis/radarr/radarrApi'; -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from '@sveltejs/kit'; - -// Add to radarr -export const POST = (async ({ params }) => { - const tmdbId = _parseMovieId(params); - - const response = await addMovieToRadarr(Number(tmdbId)); - - return json(response); -}) satisfies RequestHandler; - -export const GET = (async ({ params }) => { - const tmdbId = _parseMovieId(params); - - const response = await getRadarrMovieByTmdbId(tmdbId); - - return json(response); -}) satisfies RequestHandler; diff --git a/src/routes/movie/[id]/releases/+server.ts b/src/routes/movie/[id]/releases/+server.ts deleted file mode 100644 index 8494675..0000000 --- a/src/routes/movie/[id]/releases/+server.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { RequestHandler } from '@sveltejs/kit'; -import { json } from '@sveltejs/kit'; -import { _parseMovieId } from '../+server'; -import { - cancelDownloadRadarrMovie, - addMovieToRadarr, - fetchRadarrReleases, - downloadRadarrMovie -} from '$lib/apis/radarr/radarrApi'; - -export const GET = (async ({ params }) => { - const radarrId = _parseMovieId(params); - - const releases: any[] = (await fetchRadarrReleases(radarrId)) || []; - - let filtered = releases.slice(); - - filtered.sort((a, b) => b.seeders - a.seeders); - filtered = filtered.filter((release) => release.quality.quality.resolution > 720).slice(0, 5); - - const releasesSkipped = releases.length - filtered.length; - - releases.sort((a, b) => b.size - a.size); - filtered.sort((a, b) => b.size - a.size); - - return json({ - filtered, - releasesSkipped, - allReleases: releases - }); -}) satisfies RequestHandler; - -// Download movie -export const POST = (async ({ params, request }) => { - const body = await request.json(); - - if (!body.guid) throw new Error('NO_GUID'); - - const response = await downloadRadarrMovie(body.guid); - - return json(response); -}) satisfies RequestHandler; - -export const DELETE = (async ({ params }) => { - const downloadId = _parseMovieId(params); - - const success = await cancelDownloadRadarrMovie(downloadId); - - return json({ success }); -}) satisfies RequestHandler; diff --git a/src/routes/series/[id]/SeriesPage.svelte b/src/routes/series/[id]/SeriesPage.svelte index 5d72df2..dcfd359 100644 --- a/src/routes/series/[id]/SeriesPage.svelte +++ b/src/routes/series/[id]/SeriesPage.svelte @@ -23,7 +23,7 @@ import { capitalize, formatMinutesToTime, formatSize } from '$lib/utils'; import classNames from 'classnames'; import { Archive, ChevronLeft, ChevronRight, DotFilled, Plus } from 'radix-icons-svelte'; - import { tick, type ComponentProps, onMount } from 'svelte'; + import type { ComponentProps } from 'svelte'; import { fade } from 'svelte/transition'; export let tmdbId: number; @@ -35,7 +35,10 @@ let visibleEpisodeIndex: number | undefined = undefined; let requestModalVisible = false; - const requestModalProps = createModalProps(() => (requestModalVisible = false)); + const requestModalProps = createModalProps( + () => (requestModalVisible = false), + () => {} + ); let episodeProps: ComponentProps[][] = []; let episodeComponents: HTMLDivElement[] = []; @@ -104,12 +107,12 @@ await library.refresh(); } - let addToRadarrLoading = false; + let addToSonarrLoading = false; function addToSonarr() { - addToRadarrLoading = true; + addToSonarrLoading = true; addSeriesToSonarr(tmdbId) .then(refresh) - .finally(() => (addToRadarrLoading = false)); + .finally(() => (addToSonarrLoading = false)); } let didFocusNextEpisode = false; @@ -174,7 +177,7 @@ Next Episode {:else if !$itemStore.item?.sonarrSeries} - {:else} @@ -344,7 +347,7 @@

Spoken Languages

- {series?.spoken_languages?.map((l) => capitalize(l.name || '')).join(', ')} + {series?.spoken_languages?.map((l) => capitalize(l.english_name || '')).join(', ')}