Work on hero showcase component

This commit is contained in:
Aleksi Lassila
2024-03-23 21:18:18 +02:00
parent a5aa9755a6
commit fafb2e1345
9 changed files with 143 additions and 66 deletions

8
package-lock.json generated
View File

@@ -7,6 +7,9 @@
"": {
"name": "reiverr",
"version": "0.9.0",
"dependencies": {
"gsap": "^3.12.5"
},
"devDependencies": {
"@jellyfin/sdk": "^0.8.2",
"@playwright/test": "^1.28.1",
@@ -4281,6 +4284,11 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
"node_modules/gsap": {
"version": "3.12.5",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.12.5.tgz",
"integrity": "sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ=="
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",

View File

@@ -66,5 +66,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"dependencies": {
"gsap": "^3.12.5"
}
}

View File

@@ -4,19 +4,21 @@
import type { ShowcaseItemProps } from './HeroShowcase';
import HeroShowcaseBackground from './HeroShowcaseBackground.svelte';
import { Selectable } from '../../selectable';
import IconButton from '../IconButton.svelte';
import { ChevronRight, DotFilled } from 'radix-icons-svelte';
import CardPlaceholder from '../Card/CardPlaceholder.svelte';
import classNames from 'classnames';
import Poster from '../Poster/Poster.svelte';
import { TMDB_POSTER_SMALL } from '../../constants';
export let items: Promise<ShowcaseItemProps[]> = Promise.resolve([]);
let showcaseIndex = 0;
let showcaseIndex = 6;
let showcaseLength = 0;
$: items.then((i) => (showcaseLength = i?.length || 0));
function onNext() {
if (showcaseIndex === showcaseLength - 1) {
Selectable.focusRight();
} else {
showcaseIndex = (showcaseIndex + 1) % showcaseLength;
}
showcaseIndex = (showcaseIndex + 1) % showcaseLength;
return true;
}
@@ -38,36 +40,88 @@
<Container class="h-screen pl-16 flex flex-col relative">
<HeroShowcaseBackground {items} index={showcaseIndex} />
<Container
class="flex-1 p-2 flex overflow-hidden"
class="flex-1 px-8 flex overflow-hidden z-10"
navigationActions={{
right: onNext,
left: onPrevious,
up: () => Selectable.focusLeft()
up: () => Selectable.focusLeft() || true
}}
>
<!--{#await items}-->
<!-- <div class="flex placeholder flex-1 rounded-2xl">-->
<!-- {showcaseIndex}-->
<!-- <div />-->
<!-- </div>-->
<!--{:then items}-->
<!-- {#each items as item}-->
<!-- <div class="flex flex-col items-center justify-center w-full h-full">-->
<!-- <img src={item.posterUrl} alt={item.title} class="w-48 h-72" />-->
<!-- <h2 class="text-lg font-bold">{item.title}</h2>-->
<!-- <p>{item.year}</p>-->
<!-- <p>{item.runtime}</p>-->
<!-- <p>{item.rating}</p>-->
<!-- <p>{item.ratingSource}</p>-->
<!-- <p>{item.genres.join(', ')}</p>-->
<!-- </div>-->
<!-- {/each}-->
<PageDots index={showcaseIndex} length={showcaseLength} {onJump} {onPrevious} {onNext} />
<!--{:catch error}-->
<!-- <p>{error.message}</p>-->
<!--{/await}-->
<div class="flex flex-1">
{#await items}
<div class="flex-1 flex items-end">
<CardPlaceholder orientation="portrait" />
<div class="flex flex-col">
<div>stats</div>
<div>title</div>
<div>genres</div>
</div>
</div>
<div class="flex flex-col justify-end">
<div class="flex flex-1 justify-end items-center">
<IconButton on:click={onNext}>
<ChevronRight size={38} />
</IconButton>
</div>
<PageDots index={showcaseIndex} length={showcaseLength} {onJump} {onPrevious} {onNext} />
</div>
{:then items}
{#if items[showcaseIndex]}
{@const item = items[showcaseIndex]}
<div class="flex-1 flex items-end">
<div class="mr-8">
<Poster orientation="portrait" backdropUrl={TMDB_POSTER_SMALL + item.posterUrl} />
</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
}
)}
>
{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> -->
</div>
</div>
{/if}
<div class="flex flex-col justify-end">
<div class="flex flex-1 justify-end items-center">
<IconButton on:click={onNext}>
<ChevronRight size={38} />
</IconButton>
</div>
<PageDots index={showcaseIndex} length={showcaseLength} {onJump} {onPrevious} {onNext} />
</div>
{:catch error}
<p>{error.message}</p>
{/await}
</div>
</Container>
<Container>
<Container class="z-10">
<slot />
</Container>
</Container>

View File

@@ -1,4 +1,5 @@
import type { getTmdbPopularMovies } from '../../apis/tmdb/tmdbApi';
import { formatMinutesToTime } from '../../utils';
export type RatingSource = 'tmdb'; // TODO: Add more rating sources & move elsewhere
@@ -9,25 +10,30 @@ export type ShowcaseItemProps = {
trailerUrl?: string;
title: string;
overview?: string;
year?: number;
runtime?: number;
rating?: number;
runtime?: string;
rating?: string;
ratingSource?: RatingSource;
genres: string[];
url?: string;
};
export async function getShowcasePropsFromTmdb(
response: Awaited<ReturnType<typeof getTmdbPopularMovies>>
): Promise<ShowcaseItemProps[]> {
console.log(response);
return response.slice(0, 10).map((movie) => ({
title: movie.title || '',
posterUrl: movie.poster_path || '',
backdropUrl: movie.backdrop_path || '',
rating: movie.vote_average,
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: (movie as any).runtime || 0,
runtime: formatMinutesToTime((movie as any).runtime || 0),
ratingSource: 'tmdb',
trailerUrl: ''
trailerUrl: '',
url: `https://www.themoviedb.org/movie/${movie.id}`,
overview: movie.overview || ''
}));
}

View File

@@ -13,22 +13,27 @@
}
</script>
<div
class="absolute inset-0 flex overflow-hidden h-full w-full"
style="perspective: 1px; -webkit-perspective: 1px;"
>
{#await items then items}
{#each items as item, index}
<div
class="w-full h-full flex-shrink-0 basis-auto relative"
style="transform-style: preserve-3d; -webkit-transform-style: preserve-3d; overflow: hidden;"
bind:this={htmlElements[index]}
>
<div class="absolute inset-0">
<div
class="flex overflow-hidden h-[60%] w-full"
style="perspective: 1px; -webkit-perspective: 1px;"
>
{#await items then items}
{#each items as item, i}
<div
class="w-full h-full flex-shrink-0 basis-auto bg-center bg-cover absolute inset-0"
style={`background-image: url('${TMDB_IMAGES_ORIGINAL}${item.backdropUrl}'); transform: translateZ(-5px) scale(6); -webkit-transform: translateZ(-5px) scale(6);`}
/>
</div>
{/each}
{/await}
class="w-full h-full flex-shrink-0 basis-auto relative"
style="transform-style: preserve-3d; -webkit-transform-style: preserve-3d; overflow: hidden;"
bind:this={htmlElements[i]}
>
<div
class="w-full h-full flex-shrink-0 basis-auto bg-center bg-cover absolute inset-0"
style={`background-image: url('${TMDB_IMAGES_ORIGINAL}${item.backdropUrl}'); transform: translateZ(-5px) scale(6); -webkit-transform: translateZ(-5px) scale(6);`}
/>
</div>
{/each}
{/await}
</div>
<div
class="bg-gradient-to-t from-stone-950 via-stone-950 via-40% to-transparent absolute inset-0"
/>
</div>

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import classNames from 'classnames';
import { DotFilled } from 'radix-icons-svelte';
export let index: number;
export let length: number;
@@ -26,16 +25,17 @@
<div class={classNames('h-[3px] bg-zinc-200 rounded-full', {})} />
</div> -->
<div on:click={() => onJump(i)}>
<DotFilled
<div
class={classNames(
'transition-transform hover:scale-150 hover:opacity-50 cursor-pointer text-zinc-200',
'cursor-pointer transition-transform hover:scale-125 hover:opacity-50 p-2.5',
{
'opacity-50': i === index,
'opacity-20': i !== index
}
)}
size={20}
/>
>
<div class={'bg-zinc-200 rounded-full w-2.5 h-2.5'} />
</div>
</div>
{/each}
</div>

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import classNames from 'classnames';
import PlayButton from './PlayButton.svelte';
import ProgressBar from './ProgressBar.svelte';
import PlayButton from '../PlayButton.svelte';
import ProgressBar from '../ProgressBar.svelte';
// import { playerState } from '../VideoPlayer/VideoPlayer';
import LazyImg from './LazyImg.svelte';
import LazyImg from '../LazyImg.svelte';
import { Star } from 'radix-icons-svelte';
import type { TitleType } from '../types';
import Container from '../../Container.svelte';
import type { TitleType } from '../../types';
import Container from '../../../Container.svelte';
import { useNavigate } from 'svelte-navigator';
export let tmdbId: number | undefined = undefined;
@@ -61,6 +61,7 @@
class="absolute inset-0 opacity-0 group-hover:opacity-30 transition-opacity bg-black"
style="filter: blur(50px); transform: scale(3);"
>
<!-- This is the tinted and blurred hover overlay -->
<LazyImg src={backdropUrl} />
</div>
<!-- <div

View File

@@ -5,7 +5,7 @@
import { settings } from '../stores/settings.store';
import type { TitleType } from '../types';
import type { ComponentProps } from 'svelte';
import Poster from '../components/Poster.svelte';
import Poster from '../components/Poster/Poster.svelte';
import { getJellyfinItems, type JellyfinItem } from '../apis/jellyfin/jellyfinApi';
import { jellyfinItemsStore } from '../stores/data.store';
import Carousel from '../components/Carousel/Carousel.svelte';

View File

@@ -88,8 +88,8 @@ export class Selectable {
if (offsetParent) {
const left = this.htmlElement.offsetLeft - offset;
console.log(boundingRect);
console.log('Scrolling to left: ', left);
// console.log(boundingRect);
// console.log('Scrolling to left: ', left);
offsetParent.scrollTo({
left,
behavior: 'smooth'
@@ -200,7 +200,7 @@ export class Selectable {
console.warn('Selectable already initialized', this);
}
console.log('Initializing', this.htmlElement);
// console.log('Initializing', this.htmlElement);
const parentSelectable = this.htmlElement.parentElement
? getParentSelectable(this.htmlElement.parentElement)