diff --git a/src/lib/apis/tmdb/tmdbApi.ts b/src/lib/apis/tmdb/tmdbApi.ts index a54db89..edc7265 100644 --- a/src/lib/apis/tmdb/tmdbApi.ts +++ b/src/lib/apis/tmdb/tmdbApi.ts @@ -91,7 +91,7 @@ export const getTmdbSeriesFromTvdbId = async (tvdbId: string) => }).then((res) => res.data?.tv_results?.[0] as TmdbSeries2 | undefined); export const getTmdbIdFromTvdbId = async (tvdbId: number) => - getTmdbSeriesFromTvdbId(tvdbId).then((res: any) => res?.id as number | undefined); + getTmdbSeriesFromTvdbId(String(tvdbId)).then((res: any) => res?.id as number | undefined); export const getTmdbSeries = async (tmdbId: number): Promise => await TmdbApiOpen.get('/3/tv/{series_id}', { @@ -280,6 +280,16 @@ export const searchTmdbTitles = (query: string) => } }).then((res) => res.data?.results || []); +export const getTmdbItemBackdrop = (item: { + images: { backdrops: { file_path: string; iso_639_1: string }[] }; +}) => + ( + item?.images?.backdrops?.find((b) => b.iso_639_1 === get(settings)?.language) || + item?.images?.backdrops?.find((b) => b.iso_639_1 === 'en') || + item?.images?.backdrops?.find((b) => b.iso_639_1) || + item?.images?.backdrops?.[0] + )?.file_path; + export const TMDB_MOVIE_GENRES = [ { id: 28, diff --git a/src/lib/components/Card/Card.svelte b/src/lib/components/Card/Card.svelte index 6241a8c..bbe3070 100644 --- a/src/lib/components/Card/Card.svelte +++ b/src/lib/components/Card/Card.svelte @@ -55,7 +55,7 @@ )} on:click={() => { if (openInModal) { - openTitleModal(tmdbId, type, title); + openTitleModal(tmdbId, type); } else { window.location.href = `/${type}/${tmdbId}`; } diff --git a/src/lib/components/Card/CardPlaceholder.svelte b/src/lib/components/Card/CardPlaceholder.svelte index d39fdc0..772e9bf 100644 --- a/src/lib/components/Card/CardPlaceholder.svelte +++ b/src/lib/components/Card/CardPlaceholder.svelte @@ -4,12 +4,17 @@ export let index = 0; export let size: 'dynamic' | 'md' | 'lg' = 'md'; + export let orientation: 'portrait' | 'landscape' = 'landscape';
-
anchored && e.stopPropagation()}> +
{ + if (anchored) { + e.stopPropagation(); + handleOpen(e); + } + }} +>
@@ -75,12 +85,11 @@ ? `left: ${ fixedPosition.x - (fixedPosition.x > windowWidth / 2 ? menu?.clientWidth : 0) }px; top: ${ - fixedPosition.y - - (bottom ? (fixedPosition.y > windowHeight / 2 ? menu?.clientHeight : 0) : 0) + fixedPosition.y - (fixedPosition.y > windowHeight / 2 ? menu?.clientHeight : 0) }px;` : menu?.getBoundingClientRect()?.left > windowWidth / 2 - ? `right: 0;${bottom ? 'bottom: 40px;' : ''}` - : `left: 0;${bottom ? 'bottom: 40px;' : ''}`} + ? `right: 0;${fixedPosition.y > windowHeight / 2 ? 'bottom: 100%;' : ''}` + : `left: 0;${fixedPosition.y > windowHeight / 2 ? 'bottom: 100%;' : ''}`} bind:this={menu} in:fly|global={{ y: 5, duration: 100, delay: anchored ? 0 : 100 }} out:fly|global={{ y: 5, duration: 100 }} diff --git a/src/lib/components/ContextMenu/ContextMenuButton.svelte b/src/lib/components/ContextMenu/ContextMenuButton.svelte index c78d83c..8c66510 100644 --- a/src/lib/components/ContextMenu/ContextMenuButton.svelte +++ b/src/lib/components/ContextMenu/ContextMenuButton.svelte @@ -1,25 +1,12 @@
- + - - +
diff --git a/src/lib/components/IconButton.svelte b/src/lib/components/IconButton.svelte index 9c0be2a..46188b5 100644 --- a/src/lib/components/IconButton.svelte +++ b/src/lib/components/IconButton.svelte @@ -13,7 +13,7 @@ }, $$restProps.class )} - on:click|stopPropagation + on:click > diff --git a/src/lib/components/LazyImg.svelte b/src/lib/components/LazyImg.svelte index a20d6bc..4209275 100644 --- a/src/lib/components/LazyImg.svelte +++ b/src/lib/components/LazyImg.svelte @@ -11,18 +11,18 @@ } - +
+ +
diff --git a/src/lib/components/Poster/Poster.svelte b/src/lib/components/Poster/Poster.svelte index ccd0f45..f989bc9 100644 --- a/src/lib/components/Poster/Poster.svelte +++ b/src/lib/components/Poster/Poster.svelte @@ -5,6 +5,7 @@ import ProgressBar from '../ProgressBar.svelte'; import { playerState } from '../VideoPlayer/VideoPlayer'; import LazyImg from '../LazyImg.svelte'; + import { Star } from 'radix-icons-svelte'; export let tmdbId: number | undefined = undefined; export let tvdbId: number | undefined = undefined; @@ -14,18 +15,24 @@ export let title = ''; export let subtitle = ''; - + export let rating: number | undefined = undefined; export let progress = 0; - export let size: 'dynamic' | 'md' = 'md'; + export let size: 'dynamic' | 'md' | 'lg' = 'md'; + export let orientation: 'portrait' | 'landscape' = 'landscape';
-

{title}

+

{title}

{subtitle}

@@ -52,7 +59,13 @@
-
+
+ {#if rating} +

+ {rating.toFixed(1)} +

+ {/if} +
@@ -75,7 +88,7 @@ {/if} {#if progress}
diff --git a/src/lib/components/ProgressBar.svelte b/src/lib/components/ProgressBar.svelte index 94ba297..5ea568c 100644 --- a/src/lib/components/ProgressBar.svelte +++ b/src/lib/components/ProgressBar.svelte @@ -1,7 +1,16 @@
-
+
diff --git a/src/lib/components/TitlePageLayout/OpenInButton.svelte b/src/lib/components/TitlePageLayout/OpenInButton.svelte index eaa28e6..6439cf7 100644 --- a/src/lib/components/TitlePageLayout/OpenInButton.svelte +++ b/src/lib/components/TitlePageLayout/OpenInButton.svelte @@ -3,6 +3,8 @@ import type { RadarrMovie } from '$lib/apis/radarr/radarrApi'; import type { SonarrSeries } from '$lib/apis/sonarr/sonarrApi'; import type { TitleType } from '$lib/types'; + import { DotsVertical } from 'radix-icons-svelte'; + import Button from '../Button.svelte'; import ContextMenuButton from '../ContextMenu/ContextMenuButton.svelte'; import LibraryItemContextItems from '../ContextMenu/LibraryItemContextItems.svelte'; @@ -20,4 +22,7 @@ + diff --git a/src/lib/components/TitlePageLayout/TitlePageModal.svelte b/src/lib/components/TitlePageLayout/TitlePageModal.svelte index 9cee96a..11bf22e 100644 --- a/src/lib/components/TitlePageLayout/TitlePageModal.svelte +++ b/src/lib/components/TitlePageLayout/TitlePageModal.svelte @@ -6,7 +6,6 @@ import { modalStack } from '../../stores/modal.store'; export let tmdbId: number; - export let title: string = ''; export let type: TitleType; export let modalId: symbol; @@ -26,7 +25,7 @@ {#if type === 'movie'} {:else} - + {/if}
diff --git a/src/lib/components/TitleShowcase/TitleShowcase.svelte b/src/lib/components/TitleShowcase/TitleShowcase.svelte index d85c8f0..e4947c6 100644 --- a/src/lib/components/TitleShowcase/TitleShowcase.svelte +++ b/src/lib/components/TitleShowcase/TitleShowcase.svelte @@ -123,7 +123,7 @@ out:fade|global={{ duration: ANIMATION_DURATION }} >
- {#if trailerId} diff --git a/src/lib/components/VideoPlayer/VideoPlayer.svelte b/src/lib/components/VideoPlayer/VideoPlayer.svelte index cf378d3..c64ec14 100644 --- a/src/lib/components/VideoPlayer/VideoPlayer.svelte +++ b/src/lib/components/VideoPlayer/VideoPlayer.svelte @@ -34,6 +34,7 @@ import Slider from './Slider.svelte'; import { playerState } from './VideoPlayer'; import { linear } from 'svelte/easing'; + import ContextMenuButton from '../ContextMenu/ContextMenuButton.svelte'; export let modalId: symbol; @@ -366,8 +367,8 @@ 'cursor-none': !uiVisible } )} - in:fade|global={{ duration: 500, easing: linear }} - out:fade|global={{ duration: 300, easing: linear }} + in:fade|global={{ duration: 300, easing: linear }} + out:fade|global={{ duration: 200, easing: linear }} >
handleUserInteraction(false)} on:touchend|preventDefault={() => handleUserInteraction(true)} - on:dblclick|preventDefault={() => (fullscreen = !fullscreen)} - on:click={() => (paused = !paused)} in:fade|global={{ duration: 500, delay: 1200, easing: linear }} > @@ -398,6 +397,8 @@ bind:muted={mute} class="sm:w-full sm:h-full" playsinline={true} + on:dblclick|preventDefault={() => (fullscreen = !fullscreen)} + on:click={() => (paused = !paused)} /> {#if uiVisible} @@ -436,29 +437,22 @@
-
- - - {#each getQualities(resolution) as quality} - handleSelectQuality(quality.maxBitrate)} - > - {quality.name} - - {/each} - - - - - -
+ + + {#each getQualities(resolution) as quality} + handleSelectQuality(quality.maxBitrate)} + > + {quality.name} + + {/each} + + + + + { mute = !mute; diff --git a/src/lib/lang/en.json b/src/lib/lang/en.json index 5723093..39ea874 100644 --- a/src/lib/lang/en.json +++ b/src/lib/lang/en.json @@ -21,7 +21,7 @@ "upcomingSeries": "Upcoming Series", "genres": "Genres", "newDigitalReleases": "New Digital Releases", - "streamingNow": "On Streaming Now", + "streamingNow": "Streaming Now", "TVNetworks": "TV Networks" }, "library": { diff --git a/src/lib/stores/data.store.ts b/src/lib/stores/data.store.ts index 1e78105..1ef4b76 100644 --- a/src/lib/stores/data.store.ts +++ b/src/lib/stores/data.store.ts @@ -7,7 +7,8 @@ import { import { getSonarrDownloads, getSonarrSeries, - type SonarrDownload + type SonarrDownload, + type SonarrSeries } from '$lib/apis/sonarr/sonarrApi'; import { derived, writable } from 'svelte/store'; import { settings } from './settings.store'; @@ -101,20 +102,27 @@ export function createRadarrMovieStore(tmdbId: number) { }; } -export function createSonarrSeriesStore(name: string) { +export function createSonarrSeriesStore(name: Promise | string) { function shorten(str: string) { return str.toLowerCase().replace(/[^a-zA-Z0-9]/g, ''); } - const store = derived(sonarrSeriesStore, (s) => { - return { + const store = writable<{ loading: boolean; item?: SonarrSeries }>({ + loading: true, + item: undefined + }); + + sonarrSeriesStore.subscribe(async (s) => { + const awaited = await name; + + store.set({ loading: s.loading, item: s.data?.find( (i) => - shorten(i.titleSlug || '') === shorten(name) || - i.alternateTitles?.find((t) => shorten(t.title || '') === shorten(name)) + shorten(i.titleSlug || '') === shorten(awaited) || + i.alternateTitles?.find((t) => shorten(t.title || '') === shorten(awaited)) ) - }; + }); }); return { diff --git a/src/lib/stores/localstorage.store.ts b/src/lib/stores/localstorage.store.ts index fc6f25b..dbdb7f7 100644 --- a/src/lib/stores/localstorage.store.ts +++ b/src/lib/stores/localstorage.store.ts @@ -1,15 +1,15 @@ import { writable } from 'svelte/store'; -export function createLocalStorageStore(key: string) { - const store = writable(JSON.parse(localStorage.getItem(key) || 'null') || null); +export function createLocalStorageStore(key: string, defaultValue: T) { + const store = writable(JSON.parse(localStorage.getItem(key) || 'null') || defaultValue); return { subscribe: store.subscribe, - set: (value: T | null) => { + set: (value: T) => { localStorage.setItem(key, JSON.stringify(value)); store.set(value); } }; } -export const skippedVersion = createLocalStorageStore('skipped-version'); +export const skippedVersion = createLocalStorageStore('skipped-version', null); diff --git a/src/lib/stores/modal.store.ts b/src/lib/stores/modal.store.ts index 2aab7b0..a5ae225 100644 --- a/src/lib/stores/modal.store.ts +++ b/src/lib/stores/modal.store.ts @@ -61,9 +61,9 @@ function createDynamicModalStack() { export const modalStack = createDynamicModalStack(); let lastTitleModal: symbol | undefined = undefined; -export function openTitleModal(tmdbId: number, type: TitleType, title = '') { +export function openTitleModal(tmdbId: number, type: TitleType) { if (lastTitleModal) { modalStack.close(lastTitleModal); } - lastTitleModal = modalStack.create(TitlePageModal, { tmdbId, type, title }); + lastTitleModal = modalStack.create(TitlePageModal, { tmdbId, type }); } diff --git a/src/routes/discover/+page.svelte b/src/routes/discover/+page.svelte index 3ac4ff9..3ca021d 100644 --- a/src/routes/discover/+page.svelte +++ b/src/routes/discover/+page.svelte @@ -1,24 +1,22 @@
{:then props} {#each props as prop (prop.tmdbId)} - + {/each} {/await} @@ -160,7 +174,7 @@
{:then props} {#each props as prop (prop.tmdbId)} - + {/each} {/await} @@ -190,7 +204,7 @@ {:then props} {#each props as prop (prop.tmdbId)} - + {/each} {/await} @@ -204,7 +218,7 @@ {:then props} {#each props as prop (prop.tmdbId)} - + {/each} {/await} @@ -213,7 +227,7 @@ {:then props} {#each props as prop (prop.tmdbId)} - + {/each} {/await} diff --git a/src/routes/library/+page.svelte b/src/routes/library/+page.svelte index 3a1482c..6713810 100644 --- a/src/routes/library/+page.svelte +++ b/src/routes/library/+page.svelte @@ -44,7 +44,8 @@ capitalize(item.status || ''), type: 'series', progress: 100 * (((item.size || 0) - (item.sizeleft || 0)) / (item.size || 1)), - backdropUrl: item.series.images?.find((i) => i.coverType === 'poster')?.url || '' + backdropUrl: item.series.images?.find((i) => i.coverType === 'poster')?.url || '', + orientation: 'portrait' })) || []; const radarrProps: ComponentProps[] = @@ -54,7 +55,8 @@ subtitle: capitalize(item.status || ''), type: 'movie', backdropUrl: item.movie.images?.find((i) => i.coverType === 'poster')?.url || '', - progress: 100 * (((item.size || 0) - (item.sizeleft || 0)) / (item.size || 1)) + progress: 100 * (((item.size || 0) - (item.sizeleft || 0)) / (item.size || 1)), + orientation: 'portrait' })) || []; downloadProps = [...(sonarrProps || []), ...(radarrProps || [])]; @@ -90,7 +92,7 @@ class="bg-center bg-cover col-start-1 row-start-1 col-span-2 row-span-3 relative pt-24" >
-
+
showcase?.Id && playerState.streamJellyfinId(showcase?.Id)} > - Watch + Play - - -
- -
- -
- - {$_('library.sort.byTitle')} - - +
+
+ +
+ + +
- - - - +
+
+ + + {#each SORT_OPTIONS as sortOption} + { + sortBy.set(sortOption); + page = 0; + }} + > + {sortOption} + + {/each} + + {#each SORT_ORDER as order} + { + sortOrder.set(order); + page = 0; + }} + > + {order} + + {/each} + + +
+ {$sortBy} + +
+
+
+ + + +
-
-
- {#await posterPropsPromise} - {#each [...Array(20).keys()] as index (index)} - - {/each} - {:then props} - {#each props as prop} - +
+ {#if libraryLoading} + {#each [...Array(20).keys()] as index (index)} + + {/each} {:else} -
No items.
- {/each} - {:catch error} -

{error.message}

- {/await} -
- -{#await posterPropsPromise then props} -
- + {#each posterProps.slice(0, PAGE_SIZE + page * PAGE_SIZE) as prop} + + {:else} +
+ {openTab === 'available' + ? 'Your Jellyfin library items will appear here.' + : openTab === 'watched' + ? 'Your watched Jellyfin items will appear here.' + : "Your Radarr and Sonarr items that aren't available will appear here."} +
+ {/each} + {/if}
-{/await} + + {#if !libraryLoading && posterProps.length > 0} +
+ +
+ {/if} +
diff --git a/src/routes/movie/[id]/MoviePage.svelte b/src/routes/movie/[id]/MoviePage.svelte index 9a0d8a8..86582d4 100644 --- a/src/routes/movie/[id]/MoviePage.svelte +++ b/src/routes/movie/[id]/MoviePage.svelte @@ -131,7 +131,7 @@ {#if jellyfinItem} {:else if !radarrMovie && $settings.radarr.baseUrl && $settings.radarr.apiKey}