feat: show recently played from library on front pages

This commit is contained in:
Aleksi Lassila
2025-02-08 05:40:27 +02:00
parent 4a20351911
commit 9c1ec7f6ea
10 changed files with 144 additions and 35 deletions

View File

@@ -166,13 +166,21 @@ export interface PaginatedResponseDto {
export interface MovieDto {
id?: string;
tmdbId: string;
tmdbMovie?: object;
}
export interface Series {
id?: string;
tmdbId: string;
tmdbSeries?: object;
}
export interface LibraryItemDto {
tmdbId: string;
mediaType: 'Movie' | 'Series' | 'Episode';
playStates?: PlayStateDto[];
metadata?: MovieDto;
movieMetadata?: MovieDto;
seriesMetadata?: Series;
}
export interface SuccessResponseDto {

View File

@@ -140,7 +140,7 @@
{:then items} -->
{#each items as item}
<TmdbCard
item={'tmdbMovie' in item ? item.tmdbMovie : item.tmdbSeries}
item={item.metadata}
progress={item.playStates?.[0]?.progress || 0}
on:enter={scrollIntoView({ all: 64 })}
size="dynamic"

View File

@@ -1,19 +1,39 @@
<script lang="ts">
import Container from '$components/Container.svelte';
import HeroShowcase from '../components/HeroShowcase/HeroShowcase.svelte';
import { TMDB_MOVIE_GENRES, TmdbApi, tmdbApi } from '../apis/tmdb/tmdb-api';
import { getShowcasePropsFromTmdbMovie } from '../components/HeroShowcase/HeroShowcase';
import Carousel from '../components/Carousel/Carousel.svelte';
import { scrollIntoView } from '../selectable';
import { libraryItemsDataStore } from '$lib/stores/data.store';
import { derived } from 'svelte/store';
import { jellyfinApi } from '../apis/jellyfin/jellyfin-api';
import JellyfinCard from '../components/Card/JellyfinCard.svelte';
import { formatDateToYearMonthDay } from '../utils';
import { TMDB_MOVIE_GENRES, TmdbApi, tmdbApi } from '../apis/tmdb/tmdb-api';
import TmdbCard from '../components/Card/TmdbCard.svelte';
import { navigate } from '../components/StackRouter/StackRouter';
import Carousel from '../components/Carousel/Carousel.svelte';
import DetachedPage from '../components/DetachedPage/DetachedPage.svelte';
import { getShowcasePropsFromTmdbMovie } from '../components/HeroShowcase/HeroShowcase';
import HeroShowcase from '../components/HeroShowcase/HeroShowcase.svelte';
import { navigate } from '../components/StackRouter/StackRouter';
import { scrollIntoView } from '../selectable';
import { formatDateToYearMonthDay } from '../utils';
const continueWatching = jellyfinApi.getContinueWatching('movie');
const recentlyAdded = jellyfinApi.getRecentlyAdded('movie');
const { ...libraryData } = libraryItemsDataStore.getRequest();
const libraryContinueWatching = derived(libraryData, (libraryData) => {
if (!libraryData) return [];
const movies = libraryData.filter((i) => i.mediaType === 'Movie' && i.playStates?.length);
movies.sort((a, b) => {
const aMax = Math.max(
...(a.playStates?.map((p) => new Date(p.lastPlayedAt).getTime()) || [0])
);
const bMax = Math.max(
...(b.playStates?.map((p) => new Date(p.lastPlayedAt).getTime()) || [0])
);
return bMax - aMax;
});
return movies.map((i) => i.metadata);
});
// const continueWatching = jellyfinApi.getContinueWatching('movie');
// const recentlyAdded = jellyfinApi.getRecentlyAdded('movie');
const popularMovies = tmdbApi.getPopularMovies();
@@ -69,7 +89,16 @@
/>
</div>
<div class="my-16 space-y-8 relative z-10">
{#await continueWatching then continueWatching}
{#if $libraryContinueWatching.length}
<Carousel scrollClass="px-32" on:enter={scrollIntoView({ vertical: 128 })}>
<span slot="header">Continue Watching</span>
{#each $libraryContinueWatching as item}
<TmdbCard on:enter={scrollIntoView({ horizontal: 128 })} size="lg" {item} />
{/each}
</Carousel>
{/if}
<!-- {#await continueWatching then continueWatching}
{#if continueWatching?.length}
<Carousel scrollClass="px-32" on:enter={scrollIntoView({ vertical: 128 })}>
<span slot="header">Continue Watching</span>
@@ -89,7 +118,7 @@
{/if}
{/await}
{/if}
{/await}
{/await} -->
{#await popularMovies then popularMovies}
<Carousel scrollClass="px-32" on:enter={scrollIntoView({ vertical: 128 })}>

View File

@@ -12,9 +12,31 @@
import { navigate } from '../components/StackRouter/StackRouter';
import { TMDB_SERIES_GENRES } from '../apis/tmdb/tmdb-api.js';
import DetachedPage from '../components/DetachedPage/DetachedPage.svelte';
import { libraryItemsDataStore } from '$lib/stores/data.store';
import { derived } from 'svelte/store';
const continueWatching = jellyfinApi.getContinueWatchingSeries();
const recentlyAdded = jellyfinApi.getRecentlyAdded('series');
const { ...libraryData } = libraryItemsDataStore.getRequest();
const libraryContinueWatching = derived(libraryData, (libraryData) => {
if (!libraryData) return [];
const series = libraryData.filter((i) => i.mediaType === 'Series' && i.playStates?.length);
series.sort((a, b) => {
const aMax = Math.max(
...(a.playStates?.map((p) => new Date(p.lastPlayedAt).getTime()) || [0])
);
const bMax = Math.max(
...(b.playStates?.map((p) => new Date(p.lastPlayedAt).getTime()) || [0])
);
return bMax - aMax;
});
return series.map((i) => i.metadata);
});
// const continueWatching = jellyfinApi.getContinueWatchingSeries();
// const recentlyAdded = jellyfinApi.getRecentlyAdded('series');
const nowStreaming = getNowStreaming();
const upcomingSeries = fetchUpcomingSeries();
@@ -62,7 +84,16 @@
/>
</div>
<div class="my-16 space-y-8 relative z-10">
{#await continueWatching then continueWatching}
{#if $libraryContinueWatching.length}
<Carousel scrollClass="px-32" on:enter={scrollIntoView({ vertical: 128 })}>
<span slot="header">Continue Watching</span>
{#each $libraryContinueWatching as item}
<TmdbCard on:enter={scrollIntoView({ horizontal: 128 })} size="lg" {item} />
{/each}
</Carousel>
{/if}
<!-- {#await continueWatching then continueWatching}
{#if continueWatching?.length}
<Carousel scrollClass="px-32" on:enter={scrollIntoView({ vertical: 128 })}>
<span slot="header">Continue Watching</span>
@@ -82,7 +113,7 @@
{/if}
{/await}
{/if}
{/await}
{/await} -->
{#await popular then popular}
<Carousel scrollClass="px-32" on:enter={scrollIntoView({ vertical: 128 })}>

View File

@@ -1,6 +1,11 @@
import { derived, get, type Readable, writable } from 'svelte/store';
import { jellyfinApi } from '../apis/jellyfin/jellyfin-api';
import { tmdbApi, type TmdbMovieFull2, type TmdbSeries2 } from '../apis/tmdb/tmdb-api';
import {
tmdbApi,
type TmdbMovieFull2,
type TmdbSeries2,
type TmdbSeriesFull2
} from '../apis/tmdb/tmdb-api';
import { awaitAppInitialization, reiverrApiNew, user } from './user.store';
type AwaitableStoreValue<R, T = { data?: R }> = {
@@ -236,16 +241,12 @@ export const libraryItemsDataStore = useRequestsStore(() =>
reiverrApiNew.users
.getLibraryItems(get(user)?.id as string)
.then((r) =>
Promise.all(
r.data.items.map((i) =>
i.mediaType === 'Movie'
? tmdbApi
.getTmdbMovie(Number(i.tmdbId))
.then((movie) => ({ tmdbMovie: movie!, playStates: i.playStates }))
: tmdbApi
.getTmdbSeries(Number(i.tmdbId))
.then((series) => ({ tmdbSeries: series!, playStates: i.playStates }))
)
).then((i) => i.filter((i) => ('tmdbMovie' in i ? !!i.tmdbMovie : !!i.tmdbSeries)))
r.data.items.map((i) => ({
...i,
metadata:
(i.movieMetadata?.tmdbMovie as TmdbMovieFull2) ||
(i.seriesMetadata?.tmdbSeries as TmdbSeriesFull2)
}))
)
.then((i) => i.filter((i) => !!i.metadata))
);