feat: Simple people pages implementation
This commit is contained in:
@@ -242,6 +242,34 @@ export class TmdbApi implements Api<paths> {
|
|||||||
})
|
})
|
||||||
.then((res) => res.data?.results || []) || Promise.resolve([]);
|
.then((res) => res.data?.results || []) || Promise.resolve([]);
|
||||||
|
|
||||||
|
getPerson = async (person_id: number) =>
|
||||||
|
this.getClient()
|
||||||
|
.GET('/3/person/{person_id}', {
|
||||||
|
params: {
|
||||||
|
path: {
|
||||||
|
person_id: person_id
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
append_to_response: 'images,movie_credits,tv_credits,external_ids'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((res) => res.data as TmdbPersonFull);
|
||||||
|
|
||||||
|
getPersonTaggedImages = async (person_id: number) =>
|
||||||
|
this.getClient()
|
||||||
|
?.GET('/3/person/{person_id}/tagged_images', {
|
||||||
|
params: {
|
||||||
|
path: {
|
||||||
|
person_id: person_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((res) => res.data?.results || []) || Promise.resolve([]);
|
||||||
|
|
||||||
|
getPersonBackdrops = async (person_id: number) =>
|
||||||
|
this.getPersonTaggedImages(person_id).then((r) => r.filter((i) => (i.aspect_ratio || 0) > 1.5));
|
||||||
|
|
||||||
// OTHER
|
// OTHER
|
||||||
|
|
||||||
// USER
|
// USER
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Container
|
<Container
|
||||||
class={classNames('fixed inset-0 z-20 bg-secondary-800 overflow-y-auto scrollbar-hide', {
|
class={classNames('fixed inset-0 z-20 bg-secondary-900 overflow-y-auto scrollbar-hide', {
|
||||||
hidden: !topmost
|
hidden: !topmost
|
||||||
})}
|
})}
|
||||||
trapFocus
|
trapFocus
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import Container from '../../../Container.svelte';
|
import Container from '../../../Container.svelte';
|
||||||
import AnimateScale from '../AnimateScale.svelte';
|
import AnimateScale from '../AnimateScale.svelte';
|
||||||
import type { Readable } from 'svelte/store';
|
import type { Readable } from 'svelte/store';
|
||||||
|
import { navigate } from '../StackRouter/StackRouter';
|
||||||
|
|
||||||
export let tmdbId: number;
|
export let tmdbId: number;
|
||||||
// export let type: TitleType = 'person';
|
// export let type: TitleType = 'person';
|
||||||
@@ -17,23 +18,19 @@
|
|||||||
<AnimateScale hasFocus={$hasFocus}>
|
<AnimateScale hasFocus={$hasFocus}>
|
||||||
<Container
|
<Container
|
||||||
class={classNames(
|
class={classNames(
|
||||||
'flex flex-col justify-start rounded-xl overflow-hidden relative shadow-lg shrink-0 selectable hover:text-inherit hover:bg-stone-800 focus-visible:bg-stone-800 bg-secondary-800 group text-left',
|
'flex flex-col justify-start rounded-xl overflow-hidden relative shadow-lg shrink-0 selectable hover:text-inherit hover:bg-stone-800 focus-visible:bg-stone-800 bg-secondary-800 group text-left cursor-pointer',
|
||||||
{
|
{
|
||||||
'w-56 h-80': size === 'md',
|
'w-56 h-80': size === 'md',
|
||||||
'h-52': size === 'lg',
|
'h-52': size === 'lg',
|
||||||
'w-full': size === 'dynamic'
|
'w-full': size === 'dynamic'
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
on:clickOrSelect
|
on:clickOrSelect={() => {
|
||||||
on:click={() => {
|
if (tmdbId) navigate(`/person/${tmdbId}`);
|
||||||
// if (openInModal) {
|
|
||||||
// openTitleModal({ type, id: tmdbId, provider: 'tmdb' });
|
|
||||||
// } else {
|
|
||||||
// window.location.href = `/${type}/${tmdbId}`;
|
|
||||||
// }
|
|
||||||
}}
|
}}
|
||||||
on:enter
|
on:enter
|
||||||
bind:hasFocus
|
bind:hasFocus
|
||||||
|
focusOnClick
|
||||||
>
|
>
|
||||||
<!-- <div-->
|
<!-- <div-->
|
||||||
<!-- class="mx-auto rounded-full overflow-hidden flex-shrink-0 aspect-square w-full bg-zinc-200 bg-opacity-20"-->
|
<!-- class="mx-auto rounded-full overflow-hidden flex-shrink-0 aspect-square w-full bg-zinc-200 bg-opacity-20"-->
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<PersonCard
|
<PersonCard
|
||||||
tmdbId={tmdbCredit.id || -1}
|
tmdbId={tmdbCredit.id || -1}
|
||||||
name={tmdbCredit.original_name || 'Unknown'}
|
name={tmdbCredit.name || 'Unknown'}
|
||||||
{subtitle}
|
{subtitle}
|
||||||
backdropUrl={TMDB_PROFILE_LARGE + tmdbCredit.profile_path}
|
backdropUrl={TMDB_PROFILE_LARGE + tmdbCredit.profile_path}
|
||||||
on:clickOrSelect
|
on:clickOrSelect
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import LibraryPage from '../../pages/LibraryPage.svelte';
|
|||||||
import SearchPage from '../../pages/SearchPage.svelte';
|
import SearchPage from '../../pages/SearchPage.svelte';
|
||||||
import PageNotFound from '../../pages/PageNotFound.svelte';
|
import PageNotFound from '../../pages/PageNotFound.svelte';
|
||||||
import ManagePage from '../../pages/ManagePage.svelte';
|
import ManagePage from '../../pages/ManagePage.svelte';
|
||||||
|
import PersonPage from '../../pages/PersonPage.svelte';
|
||||||
|
|
||||||
interface Page {
|
interface Page {
|
||||||
id: symbol;
|
id: symbol;
|
||||||
@@ -21,7 +22,11 @@ interface Route {
|
|||||||
path: string;
|
path: string;
|
||||||
component: ComponentType;
|
component: ComponentType;
|
||||||
default?: boolean;
|
default?: boolean;
|
||||||
|
// When root is navigated to, stcak is cleared
|
||||||
root?: boolean;
|
root?: boolean;
|
||||||
|
// Parent route, that is always rendered under this route,
|
||||||
|
// possibly sharing props that are a subset of the child's props.
|
||||||
|
// Child's props are also passed to these.
|
||||||
parent?: Route;
|
parent?: Route;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +210,11 @@ const movieRoute: Route = {
|
|||||||
parent: moviesHomeRoute
|
parent: moviesHomeRoute
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const personRoute: Route = {
|
||||||
|
path: '/person/:id',
|
||||||
|
component: PersonPage
|
||||||
|
};
|
||||||
|
|
||||||
const libraryRoute: Route = {
|
const libraryRoute: Route = {
|
||||||
path: '/library',
|
path: '/library',
|
||||||
component: LibraryPage,
|
component: LibraryPage,
|
||||||
@@ -236,6 +246,7 @@ export const defaultStackRouter = useStackRouter({
|
|||||||
episodeRoute,
|
episodeRoute,
|
||||||
moviesHomeRoute,
|
moviesHomeRoute,
|
||||||
movieRoute,
|
movieRoute,
|
||||||
|
personRoute,
|
||||||
libraryRoute,
|
libraryRoute,
|
||||||
searchRoute,
|
searchRoute,
|
||||||
manageRoute
|
manageRoute
|
||||||
|
|||||||
95
src/lib/pages/PersonPage.svelte
Normal file
95
src/lib/pages/PersonPage.svelte
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { TMDB_POSTER_SMALL } from '../constants.js';
|
||||||
|
import { tmdbApi } from '../apis/tmdb/tmdb-api';
|
||||||
|
import DetachedPage from '../components/DetachedPage/DetachedPage.svelte';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { DotFilled } from 'radix-icons-svelte';
|
||||||
|
import CardGrid from '../components/CardGrid.svelte';
|
||||||
|
import TmdbCard from '../components/Card/TmdbCard.svelte';
|
||||||
|
import Container from '../../Container.svelte';
|
||||||
|
import { scrollIntoView } from '../selectable';
|
||||||
|
|
||||||
|
export let id: string;
|
||||||
|
$: person = tmdbApi.getPerson(Number(id));
|
||||||
|
$: titles = person.then((person) => {
|
||||||
|
if (person.known_for_department === 'Acting') {
|
||||||
|
return [...(person.movie_credits.cast || []), ...(person.tv_credits.cast || [])].sort(
|
||||||
|
(a, b) =>
|
||||||
|
// @ts-ignore
|
||||||
|
(b.release_date ?? b.first_air_date ?? 0) > (a.release_date ?? a.first_air_date ?? 0)
|
||||||
|
? 1
|
||||||
|
: -1
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return [...(person.movie_credits.crew || []), ...(person.tv_credits.crew || [])].sort(
|
||||||
|
(a, b) =>
|
||||||
|
// @ts-ignore
|
||||||
|
(b.release_date ?? b.first_air_date ?? 0) > (a.release_date ?? a.first_air_date ?? 0)
|
||||||
|
? 1
|
||||||
|
: -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DetachedPage let:handleGoBack let:registrar>
|
||||||
|
{#await person then person}
|
||||||
|
<Container
|
||||||
|
focusOnMount
|
||||||
|
on:back={handleGoBack}
|
||||||
|
on:mount={registrar}
|
||||||
|
class="px-32 py-16 space-y-16"
|
||||||
|
>
|
||||||
|
<div class="flex space-x-8">
|
||||||
|
<div
|
||||||
|
class="bg-center bg-cover rounded-xl w-44 h-64 cursor-pointer"
|
||||||
|
style={`background-image: url("${TMDB_POSTER_SMALL + person.profile_path}")`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="flex flex-col justify-end">
|
||||||
|
<div
|
||||||
|
class={classNames(
|
||||||
|
'text-left font-medium tracking-wider text-stone-200 hover:text-amber-200 mt-2',
|
||||||
|
{
|
||||||
|
'text-4xl sm:text-5xl 2xl:text-6xl': person.name?.length || 0 < 15,
|
||||||
|
'text-3xl sm:text-4xl 2xl:text-5xl': person.name?.length || 0 >= 15
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{person.name}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-1 uppercase text-zinc-300 font-semibold tracking-wider mt-2 text-lg"
|
||||||
|
>
|
||||||
|
<p class="flex-shrink-0">
|
||||||
|
Born {new Date(person.birthday || 0).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<!-- <DotFilled />
|
||||||
|
<p class="flex-shrink-0">{movie.runtime}</p> -->
|
||||||
|
<DotFilled />
|
||||||
|
<p class="flex-shrink-0">
|
||||||
|
<a href={'https://www.themoviedb.org/person/' + id}>
|
||||||
|
{(person.movie_credits.cast?.length || 0) + (person.tv_credits.cast?.length || 0)} Credits</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-stone-300 font-medium line-clamp-3 opacity-75 max-w-4xl mt-4 text-lg">
|
||||||
|
{person.biography}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CardGrid>
|
||||||
|
{#await titles then titles}
|
||||||
|
{#each titles as title}
|
||||||
|
<TmdbCard item={title} on:enter={scrollIntoView({ vertical: 128 })} />
|
||||||
|
{/each}
|
||||||
|
{/await}
|
||||||
|
</CardGrid>
|
||||||
|
</Container>
|
||||||
|
{/await}
|
||||||
|
</DetachedPage>
|
||||||
Reference in New Issue
Block a user