From 1ca6a37cb50763f7797e3109aaedf28a3366e6b3 Mon Sep 17 00:00:00 2001 From: herbinator222 Date: Sat, 2 Sep 2023 07:44:44 +0200 Subject: [PATCH 1/4] Added german translation --- src/lib/components/Lang/I18n.svelte | 3 + src/lib/lang/de.json | 102 ++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/lib/lang/de.json diff --git a/src/lib/components/Lang/I18n.svelte b/src/lib/components/Lang/I18n.svelte index 80cd97c..3b08331 100644 --- a/src/lib/components/Lang/I18n.svelte +++ b/src/lib/components/Lang/I18n.svelte @@ -1,11 +1,14 @@ -
-
+
+
{heading}
{ @@ -40,7 +44,10 @@
(scrollX = carousel?.scrollLeft || scrollX)} diff --git a/src/lib/components/EpisodeCard/EpisodeCard.svelte b/src/lib/components/EpisodeCard/EpisodeCard.svelte index 874c874..a1f8553 100644 --- a/src/lib/components/EpisodeCard/EpisodeCard.svelte +++ b/src/lib/components/EpisodeCard/EpisodeCard.svelte @@ -39,7 +39,10 @@ setJellyfinItemUnwatched(jellyfinId).finally(() => jellyfinItemsStore.refreshIn(5000)); } - function handlePlay() { + function handlePlay(e: MouseEvent) { + e.preventDefault(); + e.stopPropagation(); + if (!jellyfinId) return; playerState.streamJellyfinId(jellyfinId); @@ -67,7 +70,7 @@ 'aspect-video bg-center bg-cover rounded-lg overflow-hidden transition-opacity shadow-lg selectable flex-shrink-0 placeholder-image relative', 'flex flex-col px-2 lg:px-3 py-2 gap-2 text-left', { - 'h-40': size === 'md', + 'h-44': size === 'md', 'h-full': size === 'dynamic', group: !!jellyfinId, 'cursor-default': !jellyfinId @@ -79,10 +82,10 @@ >
diff --git a/src/lib/components/Navbar/Navbar.svelte b/src/lib/components/Navbar/Navbar.svelte index b1a9e1a..7879de7 100644 --- a/src/lib/components/Navbar/Navbar.svelte +++ b/src/lib/components/Navbar/Navbar.svelte @@ -62,9 +62,9 @@ {$_('navbar.home')} - + {$_('navbar.library')} diff --git a/src/lib/components/PageDots.svelte b/src/lib/components/PageDots.svelte new file mode 100644 index 0000000..cec18e9 --- /dev/null +++ b/src/lib/components/PageDots.svelte @@ -0,0 +1,41 @@ + + +
+ {#each Array.from({ length }) as _, i} + + + +
onJump(i)}> + +
+ {/each} +
diff --git a/src/lib/components/Poster/Poster.svelte b/src/lib/components/Poster/Poster.svelte index 22466ff..e3f9fb6 100644 --- a/src/lib/components/Poster/Poster.svelte +++ b/src/lib/components/Poster/Poster.svelte @@ -20,6 +20,7 @@ export let rating: number | undefined = undefined; export let progress = 0; + export let shadow = false; export let size: 'dynamic' | 'md' | 'lg' | 'sm' = 'md'; export let orientation: 'portrait' | 'landscape' = 'landscape'; @@ -37,7 +38,7 @@ } }} class={classNames( - 'relative flex shadow-lg rounded-xl selectable group hover:text-inherit flex-shrink-0 overflow-hidden text-left', + 'relative flex rounded-xl selectable group hover:text-inherit flex-shrink-0 overflow-hidden text-left', { 'aspect-video': orientation === 'landscape', 'aspect-[2/3]': orientation === 'portrait', @@ -47,7 +48,8 @@ 'h-44': size === 'md' && orientation === 'landscape', 'w-60': size === 'lg' && orientation === 'portrait', 'h-60': size === 'lg' && orientation === 'landscape', - 'w-full': size === 'dynamic' + 'w-full': size === 'dynamic', + 'shadow-lg': shadow } )} > diff --git a/src/lib/components/TitleShowcase/TitleShowcase.svelte b/src/lib/components/TitleShowcase/TitleShowcase.svelte deleted file mode 100644 index 7c99628..0000000 --- a/src/lib/components/TitleShowcase/TitleShowcase.svelte +++ /dev/null @@ -1,254 +0,0 @@ - - -
-
- {#if UIVisible} -
-
-
-

{releaseDate.getFullYear()}

- -

{formatMinutesToTime(runtime)}

- -

{tmdbRating.toFixed(1)} TMDB

-
-

= 15 - })} - in:fly|global={{ y: -10, delay: ANIMATION_DURATION, duration: ANIMATION_DURATION }} - out:fly|global={{ y: 10, duration: ANIMATION_DURATION }} - > - {title} -

-
-
- {#each genres.slice(0, 3) as genre} - - {genre} - - {/each} -
-
- {/if} - -
-
- - {#if trailerId} - - {/if} -
-
- - {#if !trailerVisible} -
- {/if} - {#if trailerId && $settings.autoplayTrailers && trailerMounted} -
- -
- {/if} - {#if UIVisible} -
- {:else if !UIVisible} -
- {/if} - {#if UIVisible} -
-
- - - -
-
-
-
-
- - - -
-
-
- {/if} -
diff --git a/src/lib/components/TitleShowcase/TitleShowcaseBackground.svelte b/src/lib/components/TitleShowcase/TitleShowcaseBackground.svelte new file mode 100644 index 0000000..344b349 --- /dev/null +++ b/src/lib/components/TitleShowcase/TitleShowcaseBackground.svelte @@ -0,0 +1,94 @@ + + + + +{#if !trailerVisible} + {#key tmdbId} +
+ {/key} +{/if} +{#if trailerId && $settings.autoplayTrailers && trailerMounted} +
+ +
+{/if} +{#if UIVisible} +
+{:else if !UIVisible} +
+{/if} diff --git a/src/lib/components/TitleShowcase/TitleShowcaseVisuals.svelte b/src/lib/components/TitleShowcase/TitleShowcaseVisuals.svelte new file mode 100644 index 0000000..71a2b89 --- /dev/null +++ b/src/lib/components/TitleShowcase/TitleShowcaseVisuals.svelte @@ -0,0 +1,96 @@ + + +
+ +
+
+
+

{releaseDate.getFullYear()}

+ +

{formatMinutesToTime(runtime)}

+ +

{tmdbRating.toFixed(1)} TMDB

+
+ +
+
+ {#each genres.slice(0, 3) as genre} + + {genre} + + {/each} +
+
+
diff --git a/src/lib/components/TitleShowcase/TitleShowcasesContainer.svelte b/src/lib/components/TitleShowcase/TitleShowcasesContainer.svelte new file mode 100644 index 0000000..c5ba43b --- /dev/null +++ b/src/lib/components/TitleShowcase/TitleShowcasesContainer.svelte @@ -0,0 +1,165 @@ + + +
+
+ {#await tmdbPopularMoviesPromise then movies} + {@const movie = movies[showcaseIndex]} + + {#key movie?.id} + g.name || '') || []} + runtime={movie?.runtime || 0} + releaseDate={new Date(movie?.release_date || Date.now())} + tmdbRating={movie?.vote_average || 0} + posterUri={movie?.poster_path || ''} + {hideUI} + /> + {/key} +
+ + {#if !hideUI} +
+ + + +
+ {/if} +
+ v.site === 'YouTube' && v.type === 'Trailer') + ?.key} + backdropUri={movie?.backdrop_path || ''} + /> + {/await} +
+
+ {#if !continueWatchingEmpty} + +
Continue Watching
+ {#await nextUpProps} + + {:then props} + {#each props as prop} + (window.location.href = `/${prop.type}/${prop.tmdbId}`)} + {...prop} + /> + {/each} + {/await} +
+ {/if} +
+
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8b4bbd5..2153611 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2,15 +2,34 @@ import { getJellyfinBackdrop, getJellyfinContinueWatching, - getJellyfinNextUp + getJellyfinNextUp, + type JellyfinItem } from '$lib/apis/jellyfin/jellyfinApi'; - import { getTmdbMovie, getTmdbPopularMovies } from '$lib/apis/tmdb/tmdbApi'; + import { + TmdbApiOpen, + getPosterProps, + getTmdbMovie, + getTmdbPopularMovies + } from '$lib/apis/tmdb/tmdbApi'; import Carousel from '$lib/components/Carousel/Carousel.svelte'; import CarouselPlaceholderItems from '$lib/components/Carousel/CarouselPlaceholderItems.svelte'; import EpisodeCard from '$lib/components/EpisodeCard/EpisodeCard.svelte'; - import TitleShowcase from '$lib/components/TitleShowcase/TitleShowcase.svelte'; + import GenreCard from '$lib/components/GenreCard.svelte'; + import NetworkCard from '$lib/components/NetworkCard.svelte'; + import PeopleCard from '$lib/components/PeopleCard/PeopleCard.svelte'; + import Poster from '$lib/components/Poster/Poster.svelte'; + import TitleShowcase from '$lib/components/TitleShowcase/TitleShowcaseBackground.svelte'; + import { genres, networks } from '$lib/discover'; import { jellyfinItemsStore } from '$lib/stores/data.store'; - import { log } from '$lib/utils'; + import { settings } from '$lib/stores/settings.store'; + import type { TitleType } from '$lib/types'; + import { formatDateToYearMonthDay } from '$lib/utils'; + import type { ComponentProps } from 'svelte'; + import { fade } from 'svelte/transition'; + import { _ } from 'svelte-i18n'; + import LazyImg from '$lib/components/LazyImg.svelte'; + import { TMDB_IMAGES_ORIGINAL } from '$lib/constants'; + import TitleShowcases from '$lib/components/TitleShowcase/TitleShowcasesContainer.svelte'; let continueWatchingVisible = true; @@ -74,9 +93,139 @@ (showcaseIndex - 1 + (await tmdbPopularMoviesPromise).length) % (await tmdbPopularMoviesPromise).length; } + + const jellyfinItemsPromise = new Promise((resolve) => { + jellyfinItemsStore.subscribe((data) => { + if (data.loading) return; + resolve(data.data || []); + }); + }); + + const fetchCardProps = async ( + items: { + name?: string; + title?: string; + id?: number; + vote_average?: number; + number_of_seasons?: number; + first_air_date?: string; + poster_path?: string; + }[], + type: TitleType | undefined = undefined + ): Promise[]> => { + const filtered = $settings.discover.excludeLibraryItems + ? items.filter( + async (item) => + !(await jellyfinItemsPromise).find((i) => i.ProviderIds?.Tmdb === String(item.id)) + ) + : items; + + return Promise.all(filtered.map(async (item) => getPosterProps(item, type))).then((props) => + props.filter((p) => p.backdropUrl) + ); + }; + + const trendingItemsPromise = TmdbApiOpen.get('/3/trending/all/{time_window}', { + params: { + path: { + time_window: 'day' + }, + query: { + language: $settings.language + } + } + }).then((res) => res.data?.results || []); + + const fetchTrendingProps = () => trendingItemsPromise.then(fetchCardProps); + + const fetchTrendingActorProps = () => + TmdbApiOpen.get('/3/trending/person/{time_window}', { + params: { + path: { + time_window: 'week' + } + } + }) + .then((res) => res.data?.results || []) + .then((actors) => + actors + .filter((a) => a.profile_path) + .map((actor) => ({ + tmdbId: actor.id || 0, + backdropUri: actor.profile_path || '', + name: actor.name || '', + subtitle: actor.known_for_department || '' + })) + ); + + const fetchUpcomingMovies = () => + TmdbApiOpen.get('/3/discover/movie', { + params: { + query: { + 'primary_release_date.gte': formatDateToYearMonthDay(new Date()), + sort_by: 'popularity.desc', + language: $settings.language, + region: $settings.discover.region, + with_original_language: parseIncludedLanguages($settings.discover.includedLanguages) + } + } + }) + .then((res) => res.data?.results || []) + .then(fetchCardProps); + + const fetchUpcomingSeries = () => + TmdbApiOpen.get('/3/discover/tv', { + params: { + query: { + 'first_air_date.gte': formatDateToYearMonthDay(new Date()), + sort_by: 'popularity.desc', + language: $settings.language, + with_original_language: parseIncludedLanguages($settings.discover.includedLanguages) + } + } + }) + .then((res) => res.data?.results || []) + .then((i) => fetchCardProps(i, 'series')); + + const fetchDigitalReleases = () => + TmdbApiOpen.get('/3/discover/movie', { + params: { + query: { + with_release_type: 4, + sort_by: 'popularity.desc', + 'release_date.lte': formatDateToYearMonthDay(new Date()), + language: $settings.language, + with_original_language: parseIncludedLanguages($settings.discover.includedLanguages) + // region: $settings.discover.region + } + } + }) + .then((res) => res.data?.results || []) + .then(fetchCardProps); + + const fetchNowStreaming = () => + TmdbApiOpen.get('/3/discover/tv', { + params: { + query: { + 'air_date.gte': formatDateToYearMonthDay(new Date()), + 'first_air_date.lte': formatDateToYearMonthDay(new Date()), + sort_by: 'popularity.desc', + language: $settings.language, + with_original_language: parseIncludedLanguages($settings.discover.includedLanguages) + } + } + }) + .then((res) => res.data?.results || []) + .then((i) => fetchCardProps(i, 'series')); + + function parseIncludedLanguages(includedLanguages: string) { + return includedLanguages.replace(' ', '').split(',').join('|'); + } -
+ + + -