diff --git a/src/app.html b/src/app.html index de1b55f..b88fb0d 100644 --- a/src/app.html +++ b/src/app.html @@ -10,7 +10,7 @@ %sveltekit.head% - +
%sveltekit.body%
diff --git a/src/lib/tmdb-api.ts b/src/lib/tmdb-api.ts index b1a53d3..ab8d5c3 100644 --- a/src/lib/tmdb-api.ts +++ b/src/lib/tmdb-api.ts @@ -8,14 +8,37 @@ export const TmdbApi = axios.create({ } }); -export async function fetchMovieDetails(imdbId: string | number) { +export async function fetchMovieDetails(imdbId: string | number): Promise { return { ...(await TmdbApi.get('/movie/' + imdbId).then((res) => res.data)), - videos: await TmdbApi.get('/movie/' + imdbId + '/videos').then((res) => res.data.results), - credits: await TmdbApi.get('/movie/' + imdbId + '/credits').then((res) => res.data.cast) + videos: await TmdbApi.get('/movie/' + imdbId + '/videos').then( + (res) => res.data.results + ), + images: await TmdbApi.get('/movie/' + imdbId + '/images').then((res) => { + return { + backdrops: res.data.backdrops, + logos: res.data.logos, + posters: res.data.posters + }; + }), + credits: await TmdbApi.get('/movie/' + imdbId + '/credits').then( + (res) => res.data.cast + ) }; } +export interface TmdbMovieFull extends TmdbMovie { + videos: Video[]; + images: { + backdrops: Backdrop[]; + logos: Logo[]; + posters: Poster[]; + }; + credits: CastMember[]; +} + +export type MovieDetailsResponse = TmdbMovie; + export interface TmdbMovie { adult: boolean; backdrop_path: string; @@ -119,3 +142,40 @@ export interface Video { published_at: string; id: string; } + +export interface ImagesResponse { + backdrops: Backdrop[]; + id: number; + logos: Logo[]; + posters: Poster[]; +} + +export interface Backdrop { + aspect_ratio: number; + height: number; + iso_639_1?: string; + file_path: string; + vote_average: number; + vote_count: number; + width: number; +} + +export interface Logo { + aspect_ratio: number; + height: number; + iso_639_1: string; + file_path: string; + vote_average: number; + vote_count: number; + width: number; +} + +export interface Poster { + aspect_ratio: number; + height: number; + iso_639_1?: string; + file_path: string; + vote_average: number; + vote_count: number; + width: number; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..2f3c77f --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,12 @@ +import type { Genre } from '$lib/tmdb-api'; + +export function getRuntime(minutes: number) { + const hours = Math.floor(minutes / 60); + const mins = Math.floor(minutes % 60); + + return `${hours > 0 ? hours + 'h ' : ''}${mins}min`; +} + +export function formatGenres(genres: Genre[]) { + return genres.map((genre) => genre.name.charAt(0).toUpperCase() + genre.name.slice(1)).join(', '); +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 535c571..a433292 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,7 +1,7 @@
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 422ef4f..da2830d 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,7 @@ + +
+ {value} +
diff --git a/src/routes/components/SmallHorizontalPoster/SmallHorizontalPoster.svelte b/src/routes/components/SmallHorizontalPoster/SmallHorizontalPoster.svelte new file mode 100644 index 0000000..9286438 --- /dev/null +++ b/src/routes/components/SmallHorizontalPoster/SmallHorizontalPoster.svelte @@ -0,0 +1,57 @@ + + +
+
+
window.open('/movie/' + tmdbMovie.id, '_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;' : ''} + > +
+

{tmdbMovie.original_title}

+
+ {formatGenres(tmdbMovie.genres)} +
+
+
+ {#if progressType === 'watched'} +
+ {progress + ? getRuntime(tmdbMovie.runtime - tmdbMovie.runtime * (progress / 100)) + ' left' + : getRuntime(tmdbMovie.runtime)} +
+ {:else if progressType === 'downloading'} +
+ {Math.floor(progress) + '% Downloaded'} +
+ {/if} +
+
+
+
diff --git a/src/routes/SmallPoster.svelte b/src/routes/components/SmallPoster/SmallPoster.svelte similarity index 91% rename from src/routes/SmallPoster.svelte rename to src/routes/components/SmallPoster/SmallPoster.svelte index b2d52de..f4a9166 100644 --- a/src/routes/SmallPoster.svelte +++ b/src/routes/components/SmallPoster/SmallPoster.svelte @@ -3,14 +3,15 @@ import { onMount } from 'svelte'; export let tmdbId; + export let progress = 0; + export let randomProgress = false; + if (randomProgress) progress = Math.random() > 0.5 ? Math.round(Math.random() * 100) : 100; export let type: 'movie' | 'tv' = 'movie'; let bg = ''; let title = 'Loading...'; - const progress = Math.random() > 0.5 ? Math.round(Math.random() * 100) : 100; - onMount(() => { TmdbApi.get('/' + type + '/' + tmdbId) .then((res) => res.data) @@ -35,9 +36,7 @@ class="bg-center bg-cover aspect-[2/3] h-72 shadow-2xl m-1.5" style={"background-image: url('" + bg + "')"} > -
+
diff --git a/src/routes/library/+page.svelte b/src/routes/library/+page.svelte index 178802b..81a2bf5 100644 --- a/src/routes/library/+page.svelte +++ b/src/routes/library/+page.svelte @@ -1,6 +1,75 @@ -
- Contains all the titles available locally, the ones already watched previously (greyed out at the - bottom), and the ones that are in some sort of watchlist and available via any source. + + +
+
+ + + + + + {#if downloading.length > 0} +

Downloading

+
+ {#each downloading as movie (movie.id)} + + {/each} +
+ {/if} + + {#if available.length > 0} +

Available

+
+ {#each available as movie (movie.id)} + + {/each} +
+ {/if} + + {#if unavailable.length > 0} +

Unavailable

+
+ {#each unavailable as movie (movie.id)} + + {/each} +
+ {/if} + + {#if watched.length > 0} +

Watched

+ {/if} +
diff --git a/src/routes/library/+page.ts b/src/routes/library/+page.ts new file mode 100644 index 0000000..9375b9d --- /dev/null +++ b/src/routes/library/+page.ts @@ -0,0 +1,34 @@ +import type { PageLoad } from './$types'; +import { radarrApi } from '$lib/servarr-api'; +import { fetchMovieDetails } from '$lib/tmdb-api'; + +export const load = (async () => { + const radarrMovies = await radarrApi + .get('/api/v3/movie', { + params: {} + }) + .then((r) => r.data); + + let tmdbMovies; + if (radarrMovies) { + tmdbMovies = await Promise.all( + radarrMovies.filter((m) => m.tmdbId).map((m) => fetchMovieDetails(m.tmdbId as any)) + ); + } + + console.log('radarrMovies', radarrMovies); + + return { + radarrMovies, + tmdbMovies, + downloading: await radarrApi + .get('/api/v3/queue', { + params: { + query: { + includeMovie: true + } + } + }) + .then((r) => r.data?.records) + }; +}) satisfies PageLoad; diff --git a/src/routes/movie/[id]/+page.svelte b/src/routes/movie/[id]/+page.svelte index 6f66691..25aa979 100644 --- a/src/routes/movie/[id]/+page.svelte +++ b/src/routes/movie/[id]/+page.svelte @@ -1,7 +1,7 @@ - + diff --git a/tailwind.config.js b/tailwind.config.js index 378a147..61dec0a 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -6,6 +6,9 @@ export default { fontFamily: { sans: ['Inter', 'sans-serif'], display: ['Inter', 'system', 'sans-serif'] + }, + colors: { + darken: '#070501bf' } } },