Improved cards, library and discover page performance

This commit is contained in:
Aleksi Lassila
2023-06-22 15:52:04 +03:00
parent db9cbd4f79
commit b97befa31c
11 changed files with 247 additions and 152 deletions

View File

@@ -1,13 +1,20 @@
<script lang="ts">
import type { TmdbMovie, TmdbMovieFull } from '$lib/tmdb-api';
import type { Genre, TmdbMovie, TmdbMovieFull } from '$lib/tmdb-api';
import { formatGenres, formatMinutesToTime } from '$lib/utils';
import classNames from 'classnames';
import { TMDB_IMAGES } from '$lib/constants';
import { onMount } from 'svelte';
import { fetchTmdbMovie, fetchTmdbMovieImages, TmdbApi } from '$lib/tmdb-api';
import CardPlaceholder from './CardPlaceholder.svelte';
import { Clock, Star, StarFilled } from 'radix-icons-svelte';
export let tmdbId: string;
export let tmdbId;
export let title;
export let genres: string[];
export let runtimeMinutes;
export let completionTime;
export let backdropUrl;
export let rating: number;
export let available = true;
export let progress = 0;
@@ -17,68 +24,64 @@
if (randomProgress) {
progress = Math.random() > 0.3 ? Math.random() * 100 : 0;
}
let tmdbMovie: TmdbMovie;
let backdropUrl;
onMount(async () => {
if (!tmdbId) return;
fetchTmdbMovieImages(String(tmdbId))
.then(
(r) =>
(backdropUrl = TMDB_IMAGES + r.backdrops.filter((b) => b.iso_639_1 === 'en')[0].file_path)
)
.catch((err) => (backdropUrl = null));
fetchTmdbMovie(tmdbId).then((movie) => (tmdbMovie = movie));
});
</script>
{#if !tmdbMovie || !backdropUrl}
<CardPlaceholder {large} />
{:else}
<div
class={classNames('rounded overflow-hidden relative shadow-2xl shrink-0', {
'h-40 w-72': !large,
'h-60 w-96': large
})}
>
<div style={'width: ' + progress + '%'} class="h-[2px] bg-zinc-200 bottom-0 absolute z-[1]" />
<div
class={classNames('rounded overflow-hidden relative shadow-2xl shrink-0', {
'h-40 w-72': !large,
'h-60 w-96': large
})}
on:click={() => window.open('/movie/' + 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;' : ''}
>
<div style={'width: ' + progress + '%'} class="h-[2px] bg-zinc-200 bottom-0 absolute z-[1]" />
<div
on:click={() => 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;' : ''}
>
<div>
<h1 class="font-bold tracking-wider text-lg">{tmdbMovie.original_title}</h1>
<div class="text-xs text-zinc-300 tracking-wider font-medium">
{formatGenres(tmdbMovie.genres)}
</div>
</div>
<div class="flex justify-between items-end">
{#if progressType === 'watched'}
<div class="text-sm font-medium text-zinc-200">
{progress
? formatMinutesToTime(tmdbMovie.runtime - tmdbMovie.runtime * (progress / 100)) +
' left'
: formatMinutesToTime(tmdbMovie.runtime)}
</div>
{:else if progressType === 'downloading'}
<div class="text-sm font-medium text-zinc-200">
{Math.floor(progress) + '% Downloaded'}
</div>
{/if}
<div>
<h1 class="font-bold tracking-wider text-lg">{title}</h1>
<div class="text-xs text-zinc-300 tracking-wider font-medium">
{genres.map((genre) => genre.charAt(0).toUpperCase() + genre.slice(1)).join(', ')}
</div>
</div>
<div
style={"background-image: url('" + backdropUrl + "')"}
class="absolute inset-0 bg-center bg-cover peer-hover:scale-105 transition-transform"
/>
<div
class={classNames('absolute inset-0 transition-opacity', {
'bg-darken opacity-0 peer-hover:opacity-100': available,
'bg-[#00000055] peer-hover:bg-darken': !available
})}
/>
<div class="flex justify-between items-end">
{#if completionTime}
<div class="text-sm font-medium text-zinc-200 tracking-wide">
Downloaded in <b
>{formatMinutesToTime((new Date(completionTime).getTime() - Date.now()) / 1000 / 60)}</b
>
</div>
{:else}
{#if runtimeMinutes}
<div class="flex gap-1.5 items-center">
<Clock />
<div class="text-sm text-zinc-200">
{progress
? formatMinutesToTime(runtimeMinutes - runtimeMinutes * (progress / 100)) + ' left'
: formatMinutesToTime(runtimeMinutes)}
</div>
</div>
{/if}
{#if rating}
<div class="flex gap-1.5 items-center">
<Star />
<div class="text-sm text-zinc-200">
{rating.toFixed(1)}
</div>
</div>
{/if}
{/if}
</div>
</div>
{/if}
<div
style={"background-image: url('" + TMDB_IMAGES + backdropUrl + "')"}
class="absolute inset-0 bg-center bg-cover peer-hover:scale-105 transition-transform"
/>
<div
class={classNames('absolute inset-0 transition-opacity', {
'bg-darken opacity-0 peer-hover:opacity-100': available,
'bg-[#00000055] peer-hover:bg-darken': !available
})}
/>
</div>

View File

@@ -0,0 +1,38 @@
<script lang="ts">
import type { TmdbMovie } from '$lib/tmdb-api';
import { onMount } from 'svelte';
import { fetchTmdbMovie, fetchTmdbMovieImages } from '$lib/tmdb-api';
import { TMDB_IMAGES } from '$lib/constants';
import { getJellyfinItemByTmdbId } from '$lib/jellyfin/jellyfin';
import CardPlaceholder from './CardPlaceholder.svelte';
import Card from './Card.svelte';
export let tmdbId: string;
export let type: 'default' | 'download' | 'in-library' = 'default';
let tmdbMoviePromise: Promise<TmdbMovie>;
let jellyfinItemPromise;
let radarrItemPromise;
let backdropUrlPromise;
onMount(async () => {
if (!tmdbId) throw new Error('No tmdbId provided');
backdropUrlPromise = fetchTmdbMovieImages(String(tmdbId)).then(
(r) => TMDB_IMAGES + r.backdrops.filter((b) => b.iso_639_1 === 'en')[0].file_path
);
tmdbMoviePromise = fetchTmdbMovie(tmdbId);
if (type === 'in-library') jellyfinItemPromise = getJellyfinItemByTmdbId(tmdbId);
if (type === 'download')
radarrItemPromise = fetch(`/movie/${tmdbId}/radarr`).then((r) => r.json());
});
</script>
{#await Promise.all([tmdbMoviePromise, jellyfinItemPromise, backdropUrlPromise])}
<CardPlaceholder {...$$restProps} />
{:then [tmdbMovie, jellyfinItem, backdropUrl]}
<Card {...$$restProps} {tmdbMovie} {backdropUrl} {jellyfinItem} />
{:catch err}
Error
{/await}

View File

@@ -0,0 +1,42 @@
import type { RadarrMovie } from '$lib/radarr/radarr';
import { fetchTmdbMovieImages } from '$lib/tmdb-api';
import type { TmdbMovie } from '$lib/tmdb-api';
export interface CardProps {
tmdbId: string;
title: string;
genres: string[];
runtimeMinutes: number;
backdropUrl: string;
rating: number;
}
export const fetchCardProps = async (movie: RadarrMovie): Promise<CardProps> => {
const backdropUrl = fetchTmdbMovieImages(String(movie.tmdbId)).then(
(r) => r.backdrops.filter((b) => b.iso_639_1 === 'en')[0].file_path
);
return {
tmdbId: String(movie.tmdbId),
title: String(movie.title),
genres: movie.genres as string[],
runtimeMinutes: movie.runtime as any,
backdropUrl: await backdropUrl,
rating: movie.ratings?.tmdb?.value || movie.ratings?.imdb?.value || 0
};
};
export const fetchCardPropsTmdb = async (movie: TmdbMovie): Promise<CardProps> => {
const backdropUrl = fetchTmdbMovieImages(String(movie.id))
.then((r) => r.backdrops.filter((b) => b.iso_639_1 === 'en')[0]?.file_path)
.catch(console.error);
return {
tmdbId: String(movie.id),
title: String(movie.original_title),
genres: movie.genres.map((g) => g.name),
runtimeMinutes: movie.runtime,
backdropUrl: (await backdropUrl) || '',
rating: movie.vote_average || 0
};
};