mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-22 00:35:12 +02:00
Completed discovery page
This commit is contained in:
@@ -4,14 +4,14 @@
|
||||
import { TMDB_IMAGES } from '$lib/constants';
|
||||
import { Clock, Star } from 'radix-icons-svelte';
|
||||
|
||||
export let tmdbId: string;
|
||||
export let tmdbId: number;
|
||||
export let type: 'movie' | 'series' = 'movie';
|
||||
export let title: string;
|
||||
export let genres: string[];
|
||||
export let genres: string[] = [];
|
||||
export let runtimeMinutes = 0;
|
||||
export let seasons = 0;
|
||||
export let completionTime = '';
|
||||
export let backdropUrl: string;
|
||||
export let backdropUri: string;
|
||||
export let rating: number;
|
||||
|
||||
export let available = true;
|
||||
@@ -23,22 +23,19 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div
|
||||
tabindex={0}
|
||||
<a
|
||||
class={classNames(
|
||||
'rounded overflow-hidden relative shadow-2xl shrink-0 aspect-video selectable',
|
||||
'rounded overflow-hidden relative shadow-lg shrink-0 aspect-video selectable block hover:text-inherit',
|
||||
{
|
||||
'h-40': size === 'md',
|
||||
'h-60': size === 'lg',
|
||||
'w-full': size === 'dynamic'
|
||||
}
|
||||
)}
|
||||
href={`/${type}/${tmdbId}`}
|
||||
>
|
||||
<div style={'width: ' + progress + '%'} class="h-[2px] bg-zinc-200 bottom-0 absolute z-[1]" />
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
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;' : ''}
|
||||
>
|
||||
@@ -82,7 +79,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={"background-image: url('" + TMDB_IMAGES + backdropUrl + "')"}
|
||||
style={"background-image: url('" + TMDB_IMAGES + backdropUri + "')"}
|
||||
class="absolute inset-0 bg-center bg-cover peer-hover:scale-105 transition-transform"
|
||||
/>
|
||||
<div
|
||||
@@ -91,4 +88,4 @@
|
||||
'bg-[#00000055] peer-hover:bg-darken': !available
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
3
src/lib/components/Card/CardGrid.svelte
Normal file
3
src/lib/components/Card/CardGrid.svelte
Normal file
@@ -0,0 +1,3 @@
|
||||
<div class="grid gap-x-4 gap-y-8 grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -1,42 +1,47 @@
|
||||
import type { RadarrMovie } from '$lib/apis/radarr/radarrApi';
|
||||
import { fetchTmdbMovieImages } from '$lib/apis/tmdb/tmdbApi';
|
||||
import type { TmdbMovie } from '$lib/apis/tmdb/tmdbApi';
|
||||
import {
|
||||
fetchTmdbMovieImages,
|
||||
getTmdbMovieBackdrop,
|
||||
getTmdbMovieImages,
|
||||
getTmdbSeriesBackdrop,
|
||||
getTmdbSeriesImages
|
||||
} from '$lib/apis/tmdb/tmdbApi';
|
||||
import type { TmdbMovie, TmdbMovie2, TmdbSeries2 } from '$lib/apis/tmdb/tmdbApi';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import type Card from './Card.svelte';
|
||||
|
||||
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
|
||||
);
|
||||
export const fetchCardTmdbMovieProps = async (movie: TmdbMovie2): Promise<ComponentProps<Card>> => {
|
||||
const backdropUri = getTmdbMovieBackdrop(movie.id || 0);
|
||||
|
||||
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),
|
||||
tmdbId: movie.id || 0,
|
||||
title: movie.title || '',
|
||||
genres: movie.genres?.map((g) => g.name || '') || [],
|
||||
runtimeMinutes: movie.runtime,
|
||||
backdropUrl: (await backdropUrl) || '',
|
||||
backdropUri: (await backdropUri) || '',
|
||||
rating: movie.vote_average || 0
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchCardTmdbSeriesProps = async (
|
||||
series: TmdbSeries2
|
||||
): Promise<ComponentProps<Card>> => {
|
||||
const backdropUri = getTmdbSeriesBackdrop(series.id || 0);
|
||||
|
||||
return {
|
||||
tmdbId: series.id || 0,
|
||||
title: series.name || '',
|
||||
genres: series.genres?.map((g) => g.name || '') || [],
|
||||
runtimeMinutes: series.episode_run_time?.[0],
|
||||
backdropUri: (await backdropUri) || '',
|
||||
rating: series.vote_average || 0,
|
||||
type: 'series'
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchCardTmdbProps = async (
|
||||
item: TmdbSeries2 | TmdbMovie2
|
||||
): Promise<ComponentProps<Card>> => {
|
||||
if ('name' in item) return fetchCardTmdbSeriesProps(item);
|
||||
return fetchCardTmdbMovieProps(item);
|
||||
};
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
<div class="flex gap-2">
|
||||
<IconButton
|
||||
on:click={() => {
|
||||
carousel?.scrollTo({ left: scrollX - carousel?.clientWidth, behavior: 'smooth' });
|
||||
carousel?.scrollTo({ left: scrollX - carousel?.clientWidth * 0.8, behavior: 'smooth' });
|
||||
}}
|
||||
>
|
||||
<ChevronLeft size={20} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
on:click={() => {
|
||||
carousel?.scrollTo({ left: scrollX + carousel?.clientWidth, behavior: 'smooth' });
|
||||
carousel?.scrollTo({ left: scrollX + carousel?.clientWidth * 0.8, behavior: 'smooth' });
|
||||
}}
|
||||
>
|
||||
<ChevronRight size={20} />
|
||||
|
||||
27
src/lib/components/GenreCard.svelte
Normal file
27
src/lib/components/GenreCard.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import type { Genre } from '$lib/discover';
|
||||
import { capitalize } from '$lib/utils';
|
||||
|
||||
export let genre: Genre;
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<a
|
||||
class="rounded-xl overflow-hidden relative shadow-2xl shrink-0 aspect-[21/9] selectable h-40 block"
|
||||
href={`/discover/genre/${genre.name}`}
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="h-full w-full flex flex-col items-center justify-center cursor-pointer p-2 px-3 relative z-[1] peer"
|
||||
>
|
||||
<h1 class="font-bold text-2xl tracking-wider">
|
||||
{capitalize(genre.name)}
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
style={"background-image: url('/genres/" + genre.name + ".jpg')"}
|
||||
class="absolute inset-0 bg-center bg-cover peer-hover:scale-105 transition-transform"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-darken bg-opacity-60" />
|
||||
</a>
|
||||
34
src/lib/components/GridPage/GridPage.svelte
Normal file
34
src/lib/components/GridPage/GridPage.svelte
Normal file
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { ChevronLeft } from 'radix-icons-svelte';
|
||||
import IconButton from '../IconButton.svelte';
|
||||
import CardGrid from '../Card/CardGrid.svelte';
|
||||
import CardPlaceholder from '../Card/CardPlaceholder.svelte';
|
||||
import { capitalize } from '$lib/utils';
|
||||
|
||||
export let title: string;
|
||||
</script>
|
||||
|
||||
<div class="pt-24 p-8 bg-black">
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="flex flex-col gap-1 items-start">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<h1 class="font-bold text-5xl">{capitalize(title)}</h1>
|
||||
<div
|
||||
class="flex items-center cursor-pointer hover:text-zinc-200 text-zinc-400 transition-colors"
|
||||
on:click={() => window?.history?.back()}
|
||||
>
|
||||
<ChevronLeft size={20} />
|
||||
<h2>Back</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-8">
|
||||
<CardGrid>
|
||||
<slot>
|
||||
{#each [...Array(20).keys()] as index (index)}
|
||||
<CardPlaceholder size="dynamic" {index} />
|
||||
{/each}
|
||||
</slot>
|
||||
</CardGrid>
|
||||
</div>
|
||||
17
src/lib/components/NetworkCard.svelte
Normal file
17
src/lib/components/NetworkCard.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import type { Network } from '$lib/discover';
|
||||
|
||||
export let network: Network;
|
||||
</script>
|
||||
|
||||
<a
|
||||
href={`/discover/network/${network.name}`}
|
||||
class="border rounded-xl h-52 w-96 bg-stone-800 border-stone-700 cursor-pointer p-12 text-zinc-300 hover:text-amber-200 transition-all relative group selectable"
|
||||
>
|
||||
<div
|
||||
class="absolute inset-10 bg-zinc-300 hover:bg-amber-200 group-hover:scale-105 transition-all"
|
||||
style={"mask-image: url('/networks/" +
|
||||
network.name +
|
||||
".svg'); mask-size: contain; mask-repeat: no-repeat; mask-position: center;"}
|
||||
/>
|
||||
</a>
|
||||
45
src/lib/components/PeopleCard/PeopleCard.svelte
Normal file
45
src/lib/components/PeopleCard/PeopleCard.svelte
Normal file
@@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { TMDB_IMAGES } from '$lib/constants';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export let tmdbId: number;
|
||||
export let knownFor: string[];
|
||||
export let name: string;
|
||||
export let backdropUri: string;
|
||||
export let department: string;
|
||||
export let size: 'dynamic' | 'md' | 'lg' = 'lg';
|
||||
</script>
|
||||
|
||||
<a
|
||||
class={classNames(
|
||||
'rounded-xl overflow-hidden relative shadow-lg shrink-0 aspect-[4/5] selectable block hover:text-inherit',
|
||||
{
|
||||
'h-40': size === 'md',
|
||||
'h-52': size === 'lg',
|
||||
'w-full': size === 'dynamic'
|
||||
}
|
||||
)}
|
||||
href={`/person/${tmdbId}`}
|
||||
>
|
||||
<div
|
||||
class="h-full w-full transition-opacity flex flex-col justify-between cursor-pointer relative z-[1] peer group"
|
||||
>
|
||||
<div class="opacity-0 group-hover:opacity-100">
|
||||
<!-- <h2 class="text-sm text-zinc-300 tracking-wider font-medium line-clamp-2">
|
||||
{knownFor.join(', ')}
|
||||
</h2> -->
|
||||
</div>
|
||||
<div class="bg-gradient-to-t from-darken from-20% to-transparent p-2 px-3 pt-8">
|
||||
<h2
|
||||
class="text-xs text-zinc-300 tracking-wider font-medium opacity-0 group-hover:opacity-100"
|
||||
>
|
||||
{department}
|
||||
</h2>
|
||||
<h1 class="font-bold tracking-wider text-lg">{name}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={"background-image: url('" + TMDB_IMAGES + backdropUri + "')"}
|
||||
class="absolute inset-0 bg-center bg-cover peer-hover:scale-105 transition-transform"
|
||||
/>
|
||||
</a>
|
||||
Reference in New Issue
Block a user