mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-28 03:25:13 +02:00
feat: improve hero showcase details
This commit is contained in:
@@ -1,19 +1,30 @@
|
||||
<script lang="ts">
|
||||
import classNames from 'classnames';
|
||||
import { DotFilled } from 'radix-icons-svelte';
|
||||
import HeroTitleInfo from '$lib/pages/TitlePages/HeroTitleInfo.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { TMDB_IMAGES_ORIGINAL, TMDB_POSTER_SMALL } from '../../constants';
|
||||
import { registrars } from '../../selectable.js';
|
||||
import HeroCarousel from '../HeroCarousel/HeroCarousel.svelte';
|
||||
import type { ShowcaseItemProps } from './HeroShowcase';
|
||||
|
||||
type ShowcaseItem = {
|
||||
id: number;
|
||||
type: 'movie' | 'tv';
|
||||
posterUri: string;
|
||||
backdropUri: string;
|
||||
trailerUrl?: string;
|
||||
title: string;
|
||||
overview: string;
|
||||
infoProperties: { label: string; href?: string }[];
|
||||
url?: string;
|
||||
};
|
||||
|
||||
export let items: Promise<ShowcaseItem[]> = Promise.resolve([]);
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
select: ShowcaseItemProps | undefined;
|
||||
select: ShowcaseItem | undefined;
|
||||
}>();
|
||||
|
||||
export let items: Promise<ShowcaseItemProps[]> = Promise.resolve([]);
|
||||
let awaitedItems: undefined | ShowcaseItemProps[];
|
||||
let awaitedItems: undefined | ShowcaseItem[];
|
||||
items.then((items) => (awaitedItems = items));
|
||||
|
||||
function openItem() {
|
||||
@@ -24,7 +35,7 @@
|
||||
</script>
|
||||
|
||||
<HeroCarousel
|
||||
urls={items.then((items) => items.map((i) => `${TMDB_IMAGES_ORIGINAL}${i.backdropUrl}`))}
|
||||
urls={items.then((items) => items.map((i) => `${TMDB_IMAGES_ORIGINAL}${i.backdropUri}`))}
|
||||
bind:index={showcaseIndex}
|
||||
on:enter
|
||||
on:navigate={({ detail }) => {
|
||||
@@ -50,46 +61,20 @@
|
||||
<div class="flex-1 flex items-end">
|
||||
<div class="mr-8">
|
||||
<!-- <Card orientation="portrait" backdropUrl={TMDB_POSTER_SMALL + item.posterUrl} />-->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="bg-center bg-cover rounded-xl w-44 h-64 cursor-pointer"
|
||||
style={`background-image: url("${TMDB_POSTER_SMALL + item.posterUrl}")`}
|
||||
style={`background-image: url("${TMDB_POSTER_SMALL + item.posterUri}")`}
|
||||
on:click={openItem}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class={classNames(
|
||||
'text-left font-medium tracking-wider text-stone-200 hover:text-amber-200 max-w-xl mt-2',
|
||||
{
|
||||
'text-4xl sm:text-5xl 2xl:text-6xl': item?.title.length < 15,
|
||||
'text-3xl sm:text-4xl 2xl:text-5xl': item?.title.length >= 15
|
||||
}
|
||||
)}
|
||||
on:click={openItem}
|
||||
>
|
||||
{item?.title}
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center gap-1 uppercase text-zinc-300 font-semibold tracking-wider mt-2"
|
||||
>
|
||||
<p class="flex-shrink-0">{item.year}</p>
|
||||
<!-- <DotFilled />
|
||||
<p class="flex-shrink-0">{item.runtime}</p> -->
|
||||
<DotFilled />
|
||||
<p class="flex-shrink-0"><a href={item.url}>{item.rating} TMDB</a></p>
|
||||
</div>
|
||||
<div class="text-stone-300 font-medium line-clamp-3 opacity-75 max-w-2xl mt-4">
|
||||
{item.overview}
|
||||
</div>
|
||||
<!-- <div class="flex items-center">
|
||||
{#each item?.genres.slice(0, 3) as genre}
|
||||
<span
|
||||
class="backdrop-blur-lg rounded-full bg-zinc-400 bg-opacity-20 p-1.5 px-4 font-medium text-sm flex-grow-0 mr-4"
|
||||
>
|
||||
{genre}
|
||||
</span>
|
||||
{/each}
|
||||
</div> -->
|
||||
<HeroTitleInfo
|
||||
title={item.title}
|
||||
properties={item.infoProperties}
|
||||
overview={item.overview ?? ''}
|
||||
onClickTitle={openItem}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import { formatMinutesToTime } from '../../utils';
|
||||
import type { tmdbApi } from '../../apis/tmdb/tmdb-api';
|
||||
|
||||
export type RatingSource = 'tmdb'; // TODO: Add more rating sources & move elsewhere
|
||||
|
||||
export type ShowcaseItemProps = {
|
||||
posterUrl: string;
|
||||
backdropUrl: string;
|
||||
|
||||
id: number;
|
||||
trailerUrl?: string;
|
||||
|
||||
title: string;
|
||||
overview?: string;
|
||||
year?: number;
|
||||
runtime?: string;
|
||||
rating?: string;
|
||||
ratingSource?: RatingSource;
|
||||
genres: string[];
|
||||
url?: string;
|
||||
};
|
||||
|
||||
export async function getShowcasePropsFromTmdbMovie(
|
||||
response: Awaited<ReturnType<typeof tmdbApi.getPopularMovies>>
|
||||
): Promise<ShowcaseItemProps[]> {
|
||||
return response.slice(0, 10).map((movie) => ({
|
||||
id: movie.id || 0,
|
||||
title: movie.title || '',
|
||||
posterUrl: movie.poster_path || '',
|
||||
backdropUrl: movie.backdrop_path || '',
|
||||
rating: movie.vote_average?.toFixed(1) || '0',
|
||||
genres: [], //(movie as any)?.genres?.map((genre: any) => genre?.name),
|
||||
year: movie.release_date ? new Date(movie.release_date).getFullYear() : undefined,
|
||||
runtime: formatMinutesToTime((movie as any).runtime || 0),
|
||||
ratingSource: 'tmdb',
|
||||
trailerUrl: '',
|
||||
url: `https://www.themoviedb.org/movie/${movie.id}`,
|
||||
overview: movie.overview || ''
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getShowcasePropsFromTmdbSeries(
|
||||
response: Awaited<ReturnType<typeof tmdbApi.getPopularSeries>>
|
||||
): Promise<ShowcaseItemProps[]> {
|
||||
return response.slice(0, 10).map((series) => ({
|
||||
id: series.id || 0,
|
||||
title: series.name || '',
|
||||
posterUrl: series.poster_path || '',
|
||||
backdropUrl: series.backdrop_path || '',
|
||||
rating: series.vote_average?.toFixed(1) || '0',
|
||||
genres: [], //(series as any)?.genres?.map((genre: any) => genre?.name),
|
||||
year: series.first_air_date ? new Date(series.first_air_date).getFullYear() : undefined,
|
||||
runtime: formatMinutesToTime((series as any).runtime || 0),
|
||||
ratingSource: 'tmdb',
|
||||
trailerUrl: '',
|
||||
url: `https://www.themoviedb.org/movie/${series.id}`,
|
||||
overview: series.overview || ''
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import type { TmdbMovie2 } from '$lib/apis/tmdb/tmdb-api';
|
||||
import { formatMinutesToTime, formatThousands } from '$lib/utils';
|
||||
import { navigate } from '../StackRouter/StackRouter';
|
||||
import HeroShowcase from './HeroShowcase.svelte';
|
||||
|
||||
export let movies: Promise<TmdbMovie2[]>;
|
||||
$: items = movies.then((movies) =>
|
||||
movies.map((movie) => ({
|
||||
id: movie.id ?? 0,
|
||||
type: 'movie' as const,
|
||||
posterUri: movie.poster_path ?? '',
|
||||
backdropUri: movie.backdrop_path ?? '',
|
||||
title: `${movie.title}`,
|
||||
overview: movie.overview ?? '',
|
||||
infoProperties: [
|
||||
...(movie.release_date
|
||||
? [{ label: new Date(movie.release_date).getFullYear().toString() }]
|
||||
: []),
|
||||
...(movie.runtime ? [{ label: formatMinutesToTime(movie.runtime) }] : []),
|
||||
...(movie.vote_average
|
||||
? [
|
||||
{
|
||||
label: `${movie.vote_average.toFixed(1)} TMDB (${formatThousands(
|
||||
movie.vote_count ?? 0
|
||||
)})`,
|
||||
href: `https://www.themoviedb.org/movie/${movie.id}`
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(movie.genres ? [{ label: movie.genres.map((genre) => genre.name).join(', ') }] : [])
|
||||
]
|
||||
}))
|
||||
);
|
||||
</script>
|
||||
|
||||
<HeroShowcase on:select={({ detail }) => navigate(`/movie/${detail?.id}`)} {items} />
|
||||
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import type { TmdbMovie2, TmdbSeries2 } from '$lib/apis/tmdb/tmdb-api';
|
||||
import { formatMinutesToTime, formatThousands } from '$lib/utils';
|
||||
import { navigate } from '../StackRouter/StackRouter';
|
||||
import HeroShowcase from './HeroShowcase.svelte';
|
||||
|
||||
export let series: Promise<TmdbSeries2[]>;
|
||||
$: items = series.then((series) =>
|
||||
series.map((series) => ({
|
||||
id: series.id ?? 0,
|
||||
type: 'movie' as const,
|
||||
posterUri: series.poster_path ?? '',
|
||||
backdropUri: series.backdrop_path ?? '',
|
||||
title: `${series.name}`,
|
||||
overview: series.overview ?? '',
|
||||
infoProperties: [
|
||||
...(series.status !== 'Ended'
|
||||
? [{ label: `Since ${new Date(series.first_air_date ?? 0).getFullYear()}` }]
|
||||
: series.last_air_date
|
||||
? [{ label: `Ended ${new Date(series.last_air_date).getFullYear()}` }]
|
||||
: []),
|
||||
...(series.vote_average
|
||||
? [
|
||||
{
|
||||
label: `${series.vote_average.toFixed(1)} TMDB (${formatThousands(
|
||||
series.vote_count ?? 0
|
||||
)})`,
|
||||
href: `https://www.themoviedb.org/tv/${series.id}`
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(series.genres ? [{ label: series.genres.map((genre) => genre.name).join(', ') }] : [])
|
||||
]
|
||||
}))
|
||||
);
|
||||
</script>
|
||||
|
||||
<HeroShowcase on:select={({ detail }) => navigate(`/series/${detail?.id}`)} {items} />
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<button
|
||||
class={classNames(
|
||||
'text-zinc-300 hover:text-zinc-50 p-1 flex items-center justify-center selectable rounded-sm flex-shrink-0 bg-transparent',
|
||||
'text-zinc-300 hover:text-zinc-50 p-1 flex items-center justify-center rounded-sm flex-shrink-0 bg-transparent',
|
||||
{
|
||||
'opacity-30 cursor-not-allowed pointer-events-none': disabled,
|
||||
'cursor-pointer': !disabled
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
<!-- Background -->
|
||||
<div
|
||||
class={classNames(
|
||||
'absolute inset-y-0 left-0 w-[25vw] transition-opacity bg-gradient-to-r from-secondary-900 to-transparent',
|
||||
'absolute inset-y-0 left-0 min-w-[40rem] w-[25vw] transition-opacity bg-gradient-to-r from-secondary-900 to-transparent',
|
||||
{
|
||||
'opacity-0': !$isNavBarOpen,
|
||||
'group-hover:opacity-100 pointer-events-none': true
|
||||
@@ -146,7 +146,7 @@
|
||||
<Person class="w-8 h-8" />
|
||||
<span
|
||||
class={classNames(
|
||||
'text-xl font-medium transition-opacity flex items-center absolute inset-y-0 left-20',
|
||||
'text-xl font-medium transition-opacity flex items-center absolute inset-y-0 left-20 text-nowrap',
|
||||
{
|
||||
'opacity-0 pointer-events-none': $isNavBarOpen === false,
|
||||
'group-hover:opacity-100 group-hover:pointer-events-auto': true
|
||||
|
||||
Reference in New Issue
Block a user