Work on hero showcase component
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -66,5 +66,8 @@
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"gsap": "^3.12.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 || ''
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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';
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user