feat: Episode page

This commit is contained in:
Aleksi Lassila
2024-04-23 18:24:05 +03:00
parent 423d8d9af4
commit 5ece8dd6f5
17 changed files with 303 additions and 57 deletions

View File

@@ -6,6 +6,7 @@
export let inactive: boolean = false;
export let focusOnMount: boolean = false;
export let style: 'primary' | 'secondary' = 'primary';
let hasFocus: Readable<boolean>;
</script>
@@ -14,12 +15,9 @@
<Container
bind:hasFocus
class={classNames(
'px-6 py-2 rounded-lg font-medium tracking-wide flex items-center',
'px-6 py-2 rounded-lg font-medium tracking-wide flex items-center selectable',
{
// 'bg-primary-500 text-secondary-700': $hasFocus,
// 'bg-secondary-700': !$hasFocus,
// 'hover:bg-primary-500 hover:text-secondary-700': true,
'bg-secondary-700 selectable': true,
'bg-secondary-700': style === 'primary',
'cursor-pointer': !inactive,
'cursor-not-allowed pointer-events-none opacity-40': inactive
},

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import Container from '../../../Container.svelte';
import classNames from 'classnames';
import { Check, CheckCircled, TriangleRight } from 'radix-icons-svelte';
import { ArrowDown, Check, TriangleRight } from 'radix-icons-svelte';
import type { Readable } from 'svelte/store';
import AnimateScale from '../AnimateScale.svelte';
@@ -22,10 +22,7 @@
class={classNames(
'w-full h-64',
'flex flex-col shrink-0',
'overflow-hidden rounded-2xl cursor-pointer group relative px-4 py-3 selectable transition-opacity',
{
'opacity-75': !isOnDeck && !$hasFocus
}
'overflow-hidden rounded-2xl cursor-pointer group relative px-4 py-3 selectable transition-opacity'
)}
on:clickOrSelect
on:enter
@@ -55,24 +52,28 @@
</div>
<!-- Background Image -->
<div
class="absolute inset-0 bg-center bg-cover"
class={classNames('absolute inset-0 bg-center bg-cover', {
// 'opacity-75': !isOnDeck && !$hasFocus
})}
style={`background-image: url('${backdropUrl}')`}
/>
<!-- Background Overlay / Tint -->
<div
class={classNames('absolute inset-0', {
'bg-gradient-to-t from-secondary-900/75 from-10% to-40% to-transparent': true
'bg-gradient-to-t from-secondary-900/75 from-10% to-40% ': true,
'to-secondary-900/25': !isOnDeck && !$hasFocus
// isOnDeck || $hasFocus,
// 'bg-gradient-to-t from-secondary-900/75 from-10% to-40% to-secondary-900/25':
// !isOnDeck && !$hasFocus
})}
/>
{#if handlePlay}
<div
class={classNames(
'group-hover:opacity-100 absolute inset-0 z-20 flex items-center justify-center'
)}
>
<div
class={classNames(
'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 absolute inset-0 z-20 flex items-center justify-center'
)}
>
{#if handlePlay}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class={classNames(
@@ -84,7 +85,17 @@
>
<TriangleRight size={32} />
</div>
</div>
{/if}
{:else if !isOnDeck}
<div
class={classNames(
'rounded-full p-4 cursor-pointer',
'bg-zinc-900/90 text-zinc-200',
'hover:bg-primary-500 hover:text-secondary-800' // group-focus-within:text-secondary-800 group-focus-within:bg-primary-500
)}
>
<ArrowDown size={19} />
</div>
{/if}
</div>
</Container>
</AnimateScale>

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import EpisodeCard from './EpisodeCard.svelte';
import type { TmdbEpisode } from '../../apis/tmdb/tmdb-api';
import type { TmdbSeasonEpisode } from '../../apis/tmdb/tmdb-api';
import { TMDB_BACKDROP_SMALL } from '../../constants';
export let episode: TmdbEpisode;
export let episode: TmdbSeasonEpisode;
export let handlePlay: (() => void) | undefined;
export let isWatched = false;
export let playbackProgress = 0;

View File

@@ -0,0 +1,17 @@
<script lang="ts">
import classNames from 'classnames';
export let title: string | undefined;
</script>
<div
class={classNames(
'text-left font-medium tracking-wider text-zinc-200 hover:text-amber-200 mt-1',
{
'text-4xl sm:text-5xl lg:text-6xl': (title?.length || 0) < 15,
'text-3xl sm:text-4xl lg:text-5xl': (title?.length || 0) >= 15
}
)}
>
{title}
</div>

View File

@@ -5,7 +5,7 @@
import { derived, get, type Readable } from 'svelte/store';
import {
tmdbApi,
type TmdbEpisode,
type TmdbSeasonEpisode,
type TmdbSeason,
type TmdbSeriesFull2
} from '../../apis/tmdb/tmdb-api';
@@ -24,9 +24,9 @@
export let nextJellyfinEpisode: Readable<JellyfinItem | undefined>;
// Exports
export let selectedTmdbEpisode: TmdbEpisode | undefined;
export let selectedTmdbEpisode: TmdbSeasonEpisode | undefined;
const containers = new Map<TmdbSeason | TmdbEpisode, Selectable>();
const containers = new Map<TmdbSeason | TmdbSeasonEpisode, Selectable>();
let scrollTop: number;
const { data: tmdbSeasons, isLoading: isTmdbSeasonsLoading } = useDependantRequest(
@@ -58,7 +58,7 @@
if (seasonSelectable) seasonSelectable.focus({ setFocusedElement: false });
}
function handleEpisodeMount(event: CustomEvent<Selectable>, tmdbEpisode: TmdbEpisode) {
function handleEpisodeMount(event: CustomEvent<Selectable>, tmdbEpisode: TmdbSeasonEpisode) {
containers.set(tmdbEpisode, event.detail);
const selectable = event.detail;

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { type Readable } from 'svelte/store';
import { tmdbApi, type TmdbEpisode, type TmdbSeriesFull2 } from '../../apis/tmdb/tmdb-api';
import { tmdbApi, type TmdbSeasonEpisode, type TmdbSeriesFull2 } from '../../apis/tmdb/tmdb-api';
import Container from '../../../Container.svelte';
import { useDependantRequest } from '../../stores/data.store';
import type { JellyfinItem } from '../../apis/jellyfin/jellyfin-api';
@@ -44,7 +44,7 @@
episodeCard.subscribe((e) => e?.focus({ setFocusedElement: false, propagate: false }));
});
function handleOpenEpisodePage(episode: TmdbEpisode) {
function handleOpenEpisodePage(episode: TmdbSeasonEpisode) {
navigate(`season/${episode.season_number}/episode/${episode.episode_number}`);
}
</script>

View File

@@ -3,7 +3,7 @@
import HeroCarousel from '../HeroCarousel/HeroCarousel.svelte';
import DetachedPage from '../DetachedPage/DetachedPage.svelte';
import { useActionRequest, useDependantRequest, useRequest } from '../../stores/data.store';
import { tmdbApi, type TmdbEpisode } from '../../apis/tmdb/tmdb-api';
import { tmdbApi, type TmdbSeasonEpisode } from '../../apis/tmdb/tmdb-api';
import { PLATFORM_WEB, TMDB_IMAGES_ORIGINAL } from '../../constants';
import classNames from 'classnames';
import { DotFilled, Download, ExternalLink, File, Play, Plus } from 'radix-icons-svelte';
@@ -48,7 +48,7 @@
sonarrApi.addSeriesToSonarr
);
let selectedTmdbEpisode: TmdbEpisode | undefined;
let selectedTmdbEpisode: TmdbSeasonEpisode | undefined;
const episodeCards = useRegistrar();
let scrollTop: number;
@@ -267,4 +267,4 @@
</div>
</DetachedPage>
<Route path="/season/:season/episode/:episode/*" component={EpisodePage} />
<Route path="/season/:season/episode/:episode/*" component={EpisodePage} {id} />