mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-26 18:55:12 +02:00
Improved cards, library and discover page performance
This commit is contained in:
@@ -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>
|
||||
|
||||
38
src/routes/components/Card/CardProvider.svelte
Normal file
38
src/routes/components/Card/CardProvider.svelte
Normal 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}
|
||||
42
src/routes/components/Card/card.ts
Normal file
42
src/routes/components/Card/card.ts
Normal 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
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user