From 75b250258c53fd25f68d7e4aad71a9dd21d4d8c8 Mon Sep 17 00:00:00 2001 From: Aleksi Lassila Date: Tue, 11 Jul 2023 13:52:49 +0300 Subject: [PATCH] Library page improvements --- README.md | 1 + src/lib/apis/radarr/radarrApi.ts | 16 +- src/lib/components/Card/Card.svelte | 19 +- src/routes/discover/+page.svelte | 2 +- src/routes/library/+page.svelte | 225 ++++++++++++++++------ src/routes/movie/[id]/+server.ts | 8 +- src/routes/movie/[id]/file/+server.ts | 4 +- src/routes/movie/[id]/radarr/+server.ts | 8 +- src/routes/movie/[id]/releases/+server.ts | 6 +- 9 files changed, 198 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 1ab724d..505ae71 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,4 @@ Further ideas - [ ] Similar movies & shows, actor pages and recommendations - [ ] Watchlist management +- [ ] Download a movie file diff --git a/src/lib/apis/radarr/radarrApi.ts b/src/lib/apis/radarr/radarrApi.ts index 02e531d..fcbe377 100644 --- a/src/lib/apis/radarr/radarrApi.ts +++ b/src/lib/apis/radarr/radarrApi.ts @@ -47,7 +47,7 @@ export const getRadarrMovieByTmdbId = (tmdbId: string): Promise r.data?.find((m) => (m.tmdbId as any) == tmdbId)); -export const addRadarrMovie = async (tmdbId: string) => { +export const addRadarrMovie = async (tmdbId: number) => { const tmdbMovie = await getTmdbMovie(tmdbId); const radarrMovie = await lookupRadarrMovieByTmdbId(tmdbId); console.log('fetched movies', tmdbMovie, radarrMovie); @@ -62,9 +62,9 @@ export const addRadarrMovie = async (tmdbId: string) => { profileId: qualityProfile, rootFolderPath: '/movies', minimumAvailability: 'announced', - title: tmdbMovie.title, - tmdbId: tmdbMovie.id, - year: Number((await tmdbMovie).release_date.slice(0, 4)), + title: tmdbMovie.title || tmdbMovie.original_title || '', + tmdbId: tmdbMovie.id || 0, + year: Number(tmdbMovie.release_date?.slice(0, 4)), monitored: false, tags: [], searchNow: false @@ -124,14 +124,14 @@ export const getRadarrDownloads = (): Promise => } }).then((r) => (r.data?.records?.filter((record) => record.movie) as RadarrDownload[]) || []); -export const getRadarrDownloadById = (radarrId: number) => - getRadarrDownloads().then((downloads) => downloads.find((d) => d.movie.id === radarrId)); +export const getRadarrDownloadsById = (radarrId: number) => + getRadarrDownloads().then((downloads) => downloads.filter((d) => d.movie.id === radarrId)); -const lookupRadarrMovieByTmdbId = (tmdbId: string) => +const lookupRadarrMovieByTmdbId = (tmdbId: number) => RadarrApi.get('/api/v3/movie/lookup/tmdb', { params: { query: { - tmdbId: Number(tmdbId) + tmdbId } } }).then((r) => r.data as any as RadarrMovie); diff --git a/src/lib/components/Card/Card.svelte b/src/lib/components/Card/Card.svelte index 7135029..457793b 100644 --- a/src/lib/components/Card/Card.svelte +++ b/src/lib/components/Card/Card.svelte @@ -5,16 +5,18 @@ import { Clock, Star } from 'radix-icons-svelte'; export let tmdbId: string; + export let type: 'movie' | 'series' = 'movie'; export let title: string; export let genres: string[]; - export let runtimeMinutes: number; + export let runtimeMinutes = 0; + export let seasons = 0; export let completionTime = ''; export let backdropUrl: string; export let rating: number; export let available = true; export let progress = 0; - export let type: 'dynamic' | 'normal' | 'large' = 'normal'; + export let size: 'dynamic' | 'normal' | 'large' = 'normal'; export let randomProgress = false; if (randomProgress) { progress = Math.random() > 0.3 ? Math.random() * 100 : 0; @@ -23,15 +25,15 @@
window.open('/movie/' + tmdbId, '_self')} + on:click={() => window.open(`/${type}/${tmdbId}`, '_self')} class="h-full w-full opacity-0 hover:opacity-100 transition-opacity flex flex-col justify-between cursor-pointer p-2 px-3 relative z-[1] peer" style={progress > 0 ? 'padding-bottom: 0.6rem;' : ''} > @@ -59,6 +61,11 @@
{/if} + {#if seasons} +
+ {seasons} seasons +
+ {/if} {#if rating}
diff --git a/src/routes/discover/+page.svelte b/src/routes/discover/+page.svelte index 322f61d..d61ceba 100644 --- a/src/routes/discover/+page.svelte +++ b/src/routes/discover/+page.svelte @@ -47,7 +47,7 @@ {:then { popularMovies: movies }} {#each movies ? [...movies].reverse() : [] as movie (movie.tmdbId)} - + {/each} {/await} diff --git a/src/routes/library/+page.svelte b/src/routes/library/+page.svelte index 9c9b5f4..753a9b0 100644 --- a/src/routes/library/+page.svelte +++ b/src/routes/library/+page.svelte @@ -4,8 +4,13 @@ import IconButton from '$lib/components/IconButton.svelte'; import RadarrStats from '$lib/components/SourceStats/RadarrStats.svelte'; import SonarrStats from '$lib/components/SourceStats/SonarrStats.svelte'; - import { library } from '$lib/stores/library.store'; + import { + library, + type PlayableRadarrMovie, + type PlayableSonarrSeries + } from '$lib/stores/library.store'; import { ChevronDown, MagnifyingGlass, TextAlignBottom, Trash } from 'radix-icons-svelte'; + import type { ComponentProps } from 'svelte'; const watched = []; @@ -14,11 +19,90 @@ const headerStyle = 'uppercase tracking-widest font-bold'; const headerContaienr = 'flex items-center justify-between mt-2'; - function sortByAdded(arr: any[]) { + let itemsVisible: 'all' | 'movies' | 'shows' = 'all'; + let sortBy: 'added' | 'rating' | 'year' | 'size' | 'name' = 'added'; + + let downloadingProps: ComponentProps[] = []; + let availableProps: ComponentProps[] = []; + let unavailableProps: ComponentProps[] = []; + + function itemIsSeries( + item: PlayableRadarrMovie | PlayableSonarrSeries + ): item is PlayableSonarrSeries { + return (item as PlayableSonarrSeries).seasons !== undefined; + } + + function itemIsMovie( + item: PlayableRadarrMovie | PlayableSonarrSeries + ): item is PlayableRadarrMovie { + return (item as PlayableRadarrMovie).isAvailable !== undefined; + } + + library.subscribe(async (libraryPromise) => { + const libraryData = await libraryPromise; + + const items = filterItems(sortItems([...libraryData.movies, ...libraryData.series])); + + for (let item of items) { + let props: ComponentProps; + if (itemIsSeries(item)) { + props = { + size: 'dynamic', + type: 'series', + tmdbId: String(item.tmdbId), + title: item.title || '', + genres: item.genres || [], + backdropUrl: item.cardBackdropUrl, + rating: item.ratings?.tmdb?.value || item.ratings?.imdb?.value || 7.5, + seasons: item.seasons?.length || 0 + }; + } else if (itemIsMovie(item)) { + props = { + size: 'dynamic', + type: 'movie', + tmdbId: String(item.tmdbId), + title: item.title || '', + genres: item.genres || [], + backdropUrl: item.cardBackdropUrl, + rating: item.ratings?.tmdb?.value || item.ratings?.imdb?.value || 7.5, + runtimeMinutes: item.runtime || 0 + }; + } else { + console.log('RETURNING'); + return; + } + + if (item.download) { + downloadingProps.push({ + ...props, + progress: item.download.progress, + completionTime: item.download.completionTime + }); + } else if ( + ((item as PlayableRadarrMovie)?.isAvailable && (item as PlayableRadarrMovie)?.movieFile) || + (item as PlayableSonarrSeries)?.seasons?.find( + (season) => !!season?.statistics?.episodeFileCount + ) + ) { + console.log(item); + availableProps.push(props); + } else { + unavailableProps.push({ ...props, available: false }); + } + } + + downloadingProps = downloadingProps; + availableProps = availableProps; + unavailableProps = unavailableProps; + }); + + function sortItems(arr: any[]) { return arr.sort((a, b) => ((a.added || '') > (b.added || '') ? -1 : 1)); } - console.log($library); + function filterItems(arr: any[]) { + return arr; + }
@@ -81,38 +165,102 @@ {/each}
{:then libraryData} - {@const downloading = sortByAdded([ + - {#if downloading.length > 0} + {#if downloadingProps.length > 0}

- Downloading {downloading.length} + Downloading {downloadingProps.length}

- {#each downloading as movie (movie)} + {#each downloadingProps as props} + + {/each} + +
+ {/if} + + {#if availableProps.length > 0} +
+

+ Available {availableProps.length} +

+ + + +
+
+ {#each availableProps as props} + + {/each} + +
+ {/if} + + {#if unavailableProps.length > 0} +
+

+ Unavailable {unavailableProps.length} +

+ + + +
+
+ {#each unavailableProps as props} + + {/each} +
{/if} {/await} diff --git a/src/routes/movie/[id]/+server.ts b/src/routes/movie/[id]/+server.ts index 7cbe146..cb2c6b1 100644 --- a/src/routes/movie/[id]/+server.ts +++ b/src/routes/movie/[id]/+server.ts @@ -1,9 +1,9 @@ import { error, json } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit'; import { getJellyfinItemByTmdbId } from '$lib/apis/jellyfin/jellyfinApi'; -import { getRadarrMovieByTmdbId, getRadarrDownloadById } from '$lib/apis/radarr/radarrApi'; +import { getRadarrMovieByTmdbId, getRadarrDownloadsById } from '$lib/apis/radarr/radarrApi'; -export const parseMovieId = (params: any) => { +export const _parseMovieId = (params: any) => { const { id: tmdbId } = params; if (!tmdbId) throw error(400, 'NO_TMDB_ID'); @@ -12,12 +12,12 @@ export const parseMovieId = (params: any) => { }; export const GET = (async ({ params }) => { - const tmdbId = parseMovieId(params); + const tmdbId = _parseMovieId(params); const jellyfinMoviePromise = getJellyfinItemByTmdbId(tmdbId); const radarrMoviePromise = getRadarrMovieByTmdbId(tmdbId); const radarrMovieQueuedPromise = radarrMoviePromise.then((movie) => - movie?.id ? getRadarrDownloadById(movie.id) : undefined + movie?.id ? getRadarrDownloadsById(movie.id) : undefined ); const [jellyfinItem, radarrMovie, radarrDownloads] = await Promise.all([ diff --git a/src/routes/movie/[id]/file/+server.ts b/src/routes/movie/[id]/file/+server.ts index 1fffc3f..335407f 100644 --- a/src/routes/movie/[id]/file/+server.ts +++ b/src/routes/movie/[id]/file/+server.ts @@ -1,11 +1,11 @@ import type { RequestHandler } from '@sveltejs/kit'; import { json } from '@sveltejs/kit'; -import { parseMovieId } from '../+server'; +import { _parseMovieId } from '../+server'; import { addRadarrMovie, deleteRadarrMovie } from '$lib/apis/radarr/radarrApi'; // Delete download export const DELETE = (async ({ params }) => { - const radarrMovieId = parseMovieId(params); + const radarrMovieId = _parseMovieId(params); const success = await deleteRadarrMovie(radarrMovieId); diff --git a/src/routes/movie/[id]/radarr/+server.ts b/src/routes/movie/[id]/radarr/+server.ts index 3141441..d78818c 100644 --- a/src/routes/movie/[id]/radarr/+server.ts +++ b/src/routes/movie/[id]/radarr/+server.ts @@ -1,19 +1,19 @@ -import { parseMovieId } from '../+server'; +import { _parseMovieId } from '../+server'; import { addRadarrMovie, 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 tmdbId = _parseMovieId(params); - const response = await addRadarrMovie(tmdbId); + const response = await addRadarrMovie(Number(tmdbId)); return json(response); }) satisfies RequestHandler; export const GET = (async ({ params }) => { - const tmdbId = parseMovieId(params); + const tmdbId = _parseMovieId(params); const response = await getRadarrMovieByTmdbId(tmdbId); diff --git a/src/routes/movie/[id]/releases/+server.ts b/src/routes/movie/[id]/releases/+server.ts index ba9a00b..cf545f0 100644 --- a/src/routes/movie/[id]/releases/+server.ts +++ b/src/routes/movie/[id]/releases/+server.ts @@ -1,6 +1,6 @@ import type { RequestHandler } from '@sveltejs/kit'; import { json } from '@sveltejs/kit'; -import { parseMovieId } from '../+server'; +import { _parseMovieId } from '../+server'; import { cancelDownloadRadarrMovie, addRadarrMovie, @@ -9,7 +9,7 @@ import { } from '$lib/apis/radarr/radarrApi'; export const GET = (async ({ params }) => { - const radarrId = parseMovieId(params); + const radarrId = _parseMovieId(params); const releases: any[] = (await fetchRadarrReleases(radarrId)) || []; @@ -42,7 +42,7 @@ export const POST = (async ({ params, request }) => { }) satisfies RequestHandler; export const DELETE = (async ({ params }) => { - const downloadId = parseMovieId(params); + const downloadId = _parseMovieId(params); const success = await cancelDownloadRadarrMovie(downloadId);