refactor: Persons page

This commit is contained in:
Aleksi Lassila
2023-09-18 02:15:07 +03:00
parent cc0e90e0d0
commit d6cf6aae60
5 changed files with 34 additions and 283 deletions

View File

@@ -1,219 +0,0 @@
<script lang="ts">
import { TMDB_IMAGES_ORIGINAL, TMDB_POSTER_SMALL } from '$lib/constants';
import type { TitleId, TitleType } from '$lib/types';
import classNames from 'classnames';
import { ChevronLeft, Cross2, DotFilled, ExternalLink } from 'radix-icons-svelte';
import Carousel from '../Carousel/Carousel.svelte';
import CarouselPlaceholderItems from '../Carousel/CarouselPlaceholderItems.svelte';
import IconButton from '../IconButton.svelte';
import LazyImg from '../LazyImg.svelte';
export let isModal = false;
export let handleCloseModal: () => void = () => {};
export let titleInformation:
| {
tmdbId: number;
type: TitleType;
title: string;
tagline: string;
overview: string;
backdropUriCandidates: string[];
posterPath: string;
}
| undefined = undefined;
let topHeight: number;
let bottomHeight: number;
let windowHeight: number;
let imageHeight: number;
$: imageHeight = isModal && topHeight ? topHeight : windowHeight - bottomHeight * 0.3;
function getBackdropUri(uris: string[]) {
return uris[Math.max(2, Math.floor(uris.length / 8))] || uris[uris.length - 1] || '';
}
</script>
<svelte:window bind:outerHeight={windowHeight} />
<!-- Desktop -->
<div
style={'height: ' + imageHeight.toFixed() + 'px'}
class={classNames('hidden sm:block inset-x-0 bg-center bg-cover bg-stone-950', {
absolute: isModal,
fixed: !isModal
})}
>
{#if titleInformation}
<LazyImg
src={TMDB_IMAGES_ORIGINAL + getBackdropUri(titleInformation.backdropUriCandidates)}
class="h-full"
>
<div class="absolute inset-0 bg-darken" />
</LazyImg>
{/if}
</div>
<!-- Mobile -->
<div
style={'height: ' + imageHeight.toFixed() + 'px'}
class="sm:hidden fixed inset-x-0 bg-center bg-cover bg-stone-950"
>
{#if titleInformation}
<LazyImg src={TMDB_IMAGES_ORIGINAL + titleInformation.posterPath} class="h-full">
<div class="absolute inset-0 bg-darken" />
</LazyImg>
{/if}
</div>
<div
class={classNames('flex flex-col relative z-[1]', {
'h-[85vh] sm:h-screen': !isModal,
'': isModal
})}
>
<div
class={classNames('flex-1 relative flex pt-24 px-2 sm:px-4 lg:px-8 pb-6', {
'min-h-[60vh]': isModal
})}
bind:clientHeight={topHeight}
>
{#if isModal}
{#if titleInformation}
<a
href={`/${titleInformation.type}/${titleInformation.tmdbId}`}
class="absolute top-8 right-4 sm:right-8 z-10"
>
<IconButton>
<ExternalLink size={20} />
</IconButton>
</a>
{/if}
<div class="absolute top-8 left-4 sm:left-8 z-10">
<button class="flex items-center sm:hidden font-medium" on:click={handleCloseModal}>
<ChevronLeft size={20} />
Back
</button>
<div class="hidden sm:block">
<IconButton on:click={handleCloseModal}>
<Cross2 size={20} />
</IconButton>
</div>
</div>
{/if}
<div class="absolute inset-0 bg-gradient-to-t from-stone-950 to-30%" />
<div class="z-[1] flex-1 flex justify-end gap-8 items-end max-w-screen-2xl mx-auto">
{#if titleInformation}
<div
class="aspect-[2/3] w-52 bg-center bg-cover rounded-md hidden sm:block"
style={"background-image: url('" + TMDB_POSTER_SMALL + titleInformation.posterPath + "')"}
/>
{:else}
<div class="aspect-[2/3] w-52 bg-center bg-cover rounded-md hidden sm:block placeholder" />
{/if}
<div class="flex-1 flex gap-4 justify-between flex-col lg:flex-row lg:items-end">
<div>
<div class="text-zinc-300 text-sm uppercase font-semibold flex items-center gap-1">
<slot name="title-info">
<div class="placeholder-text">Placeholder Long</div>
</slot>
</div>
{#if titleInformation}
<h1 class="text-4xl sm:text-5xl md:text-6xl font-semibold">{titleInformation.title}</h1>
{:else}
<h1 class="text-4xl sm:text-5xl md:text-6xl placeholder-text mt-2">Placeholder</h1>
{/if}
</div>
<div class="flex-shrink-0">
<slot name="title-right" />
</div>
</div>
</div>
</div>
<div bind:clientHeight={bottomHeight} class="pb-6 bg-stone-950">
<div class="max-w-screen-2xl mx-auto">
<slot name="episodes-carousel" />
</div>
</div>
</div>
<div
class={classNames('flex flex-col gap-6 bg-stone-950 px-2 sm:px-4 lg:px-8 pb-6 relative', {
'2xl:px-0': !isModal
})}
>
<div
class="grid grid-cols-4 sm:grid-cols-6 gap-4 sm:gap-x-8 rounded-xl max-w-screen-2xl 2xl:mx-auto py-4"
>
<slot name="info-description">
<div
class="flex flex-col gap-3 max-w-5xl row-span-3 col-span-4 sm:col-span-6 lg:col-span-3 mb-4 lg:mr-8"
>
{#if titleInformation}
<div class="flex gap-4 justify-between">
<h1 class="font-semibold text-xl sm:text-2xl">{titleInformation.tagline}</h1>
<!-- <div class="flex items-center gap-4">
<a
target="_blank"
href={'https://www.themoviedb.org/tv/' + tmdbId}
class="opacity-60 hover:opacity-100"
>
<img src="/tmdb.svg" alt="tmdb" width="25px" />
</a>
{#if $itemStore.item?.sonarrSeries?.titleSlug}
<a
target="_blank"
href={PUBLIC_SONARR_BASE_URL +
'/series/' +
$itemStore.item?.sonarrSeries?.titleSlug}
class="opacity-60 hover:opacity-100"
>
<img src="/sonarr.svg" alt="sonarr" width="15px" />
</a>
{/if}
{#if series?.homepage}
<a
target="_blank"
href={series.homepage}
class="flex gap-1 items-center opacity-60 hover:opacity-100"
>
<Globe size={15} />
</a>
{/if} -->
</div>
<p class="pl-4 border-l-2 text-sm sm:text-base">{titleInformation.overview}</p>
{:else}
<div class="flex gap-4 justify-between">
<h1 class="font-semibold text-xl sm:text-2xl placeholder-text">Placeholder</h1>
</div>
<div class="flex">
<div class="mr-4 placeholder w-1 flex-shrink-0 rounded" />
<p class="text-sm sm:text-base placeholder-text">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet sem eget
dolor lobortis mollis. Aliquam semper imperdiet mi nec viverra. Praesent ac ligula
congue, aliquam diam nec, ullamcorper libero. Nunc mattis rhoncus justo, ac pretium
urna vehicula et.
</p>
</div>
{/if}
</div>
</slot>
<slot name="info-components" />
</div>
<div class="max-w-screen-2xl 2xl:mx-auto w-full">
<Carousel gradientFromColor="from-stone-950">
<slot name="movie-carousel-title" slot="title" />
<slot name="movie-carousel">
<CarouselPlaceholderItems />
</slot>
</Carousel>
</div>
<div class="max-w-screen-2xl 2xl:mx-auto w-full">
<Carousel gradientFromColor="from-stone-950">
<slot name="tv-carousel-title" slot="title" />
<slot name="tv-carousel">
<CarouselPlaceholderItems />
</slot>
</Carousel>
</div>
</div>

View File

@@ -1,25 +0,0 @@
<script lang="ts">
import type { TitleId } from '$lib/types';
import { fly } from 'svelte/transition';
import PersonPage from '../../../routes/person/[id]/PersonPage.svelte';
import { modalStack } from '../../stores/modal.store';
export let titleId: TitleId;
export let modalId: symbol;
function handleCloseModal() {
modalStack.close(modalId);
}
</script>
<div
class="max-w-screen-2xl overflow-x-hidden overflow-y-scroll h-screen sm:mx-4 lg:mx-12 xl:mx-16 scrollbar-hide"
>
<div
class="relative overflow-hidden"
in:fly|global={{ y: 20, duration: 200, delay: 200 }}
out:fly|global={{ y: 20, duration: 200 }}
>
<PersonPage tmdbId={titleId.id} isModal={true} {handleCloseModal} />
</div>
</div>

View File

@@ -1,19 +0,0 @@
import type { TitleType } from '$lib/types';
import { writable } from 'svelte/store';
type Type = TitleType | undefined;
function createPersonPageModalStore() {
const store = writable<{ personId: number | undefined; type: Type }>({
personId: undefined,
type: undefined
});
return {
subscribe: store.subscribe,
set: (personId: number | undefined, type: Type) => store.set({ personId, type }),
close: () => store.set({ personId: undefined, type: undefined })
};
}
export const personPageModal = createPersonPageModalStore();

View File

@@ -1,7 +1,6 @@
import type { TitleId, TitleType } from '$lib/types';
import type { TitleId } from '$lib/types';
import { writable } from 'svelte/store';
import TitlePageModal from '../components/TitlePageLayout/TitlePageModal.svelte';
import PersonPageModal from '../components/PersonPageLayout/PersonPageModal.svelte';
type ModalItem = {
id: symbol;

View File

@@ -1,22 +1,18 @@
<script lang="ts">
import { getTmdbPerson } from '$lib/apis/tmdb/tmdbApi';
import Button from '$lib/components/Button.svelte';
import Card from '$lib/components/Card/Card.svelte';
import { fetchCardTmdbProps } from '$lib/components/Card/card';
import Carousel from '$lib/components/Carousel/Carousel.svelte';
import CarouselPlaceholderItems from '$lib/components/Carousel/CarouselPlaceholderItems.svelte';
import PersonPageLayout from '$lib/components/PersonPageLayout/PersonPageLayout.svelte';
import Poster from '$lib/components/Poster/Poster.svelte';
import TitlePageLayout from '$lib/components/TitlePageLayout/TitlePageLayout.svelte';
import FacebookIcon from '$lib/components/svgs/FacebookIcon.svelte';
import ImdbIcon from '$lib/components/svgs/ImdbIcon.svelte';
import TiktokIcon from '$lib/components/svgs/TiktokIcon.svelte';
import TmdbIcon from '$lib/components/svgs/TmdbIcon.svelte';
import TwitterIcon from '$lib/components/svgs/TwitterIcon.svelte';
import YoutubeIcon from '$lib/components/svgs/YoutubeIcon.svelte';
import TiktokIcon from '$lib/components/svgs/TiktokIcon.svelte';
import { DotFilled, InstagramLogo } from 'radix-icons-svelte';
import TmdbIcon from '$lib/components/svgs/TmdbIcon.svelte';
import TitlePageLayout from '$lib/components/TitlePageLayout/TitlePageLayout.svelte';
import Carousel from '$lib/components/Carousel/Carousel.svelte';
import Poster from '$lib/components/Poster/Poster.svelte';
import type { ComponentProps } from 'svelte';
import { TMDB_POSTER_SMALL } from '$lib/constants';
import { DotFilled, InstagramLogo } from 'radix-icons-svelte';
import type { ComponentProps } from 'svelte';
const GENDER_OPTIONS = ['Not set', 'Female', 'Male', 'Non-binary'] as const;
@@ -102,20 +98,37 @@
tmdbId: i.id,
title: (i as any).title ?? (i as any).name ?? '',
subtitle: (i as any).job ?? (i as any).character ?? '',
backdropUrl: TMDB_POSTER_SMALL + i.poster_path
}));
backdropUrl: i.poster_path ? TMDB_POSTER_SMALL + i.poster_path : ''
}))
.filter((i) => i.backdropUrl);
const movieCredits =
tmdbPerson.movie_credits.cast?.filter(
(value, index, self) => index === self.findIndex((t) => t.id === value.id)
).length || 0;
const seriesCredits =
tmdbPerson.tv_credits.cast?.filter(
(value, index, self) => index === self.findIndex((t) => t.id === value.id)
).length || 0;
const crewCredits =
tmdbPerson.movie_credits.crew?.filter(
(value, index, self) => index === self.findIndex((t) => t.id === value.id)
).length || 0;
return {
tmdbPerson,
tmdbSocials,
knownForProps
knownForProps,
movieCredits,
seriesCredits,
crewCredits
};
}
</script>
{#await data}
<PersonPageLayout {isModal} {handleCloseModal} />
{:then { tmdbPerson, tmdbSocials, knownForProps }}
<TitlePageLayout {isModal} {handleCloseModal} />
{:then { tmdbPerson, tmdbSocials, knownForProps, movieCredits, seriesCredits, crewCredits }}
{@const person = tmdbPerson}
<TitlePageLayout
titleInformation={{
@@ -132,10 +145,12 @@
>
<svelte:fragment slot="title-info">
{#if person?.homepage}
<p>{person?.homepage}</p>
<a href={person?.homepage} target="_blank">Homepage</a>
<DotFilled />
{/if}
<a href={tmdbUrl} target="_blank">Popularity: {person?.popularity?.toFixed(1)} on TMDB</a>
{#if movieCredits + seriesCredits + crewCredits > 0}
<p>{movieCredits + seriesCredits + crewCredits} Credits</p>
{/if}
</svelte:fragment>
<svelte:fragment slot="title-right" />