From 8acfd0f4d9cdeb148e81e180507a54c1304269e9 Mon Sep 17 00:00:00 2001 From: Aleksi Lassila Date: Fri, 31 May 2024 18:25:57 +0300 Subject: [PATCH] feat: Add downloads to library page --- src/lib/apis/radarr/radarr-api.ts | 8 +- src/lib/apis/sonarr/sonarr-api.ts | 7 +- src/lib/components/AnimateScale.svelte | 2 +- src/lib/components/Card/Card.svelte | 240 +++++++++++++------------ src/lib/components/CardGrid.svelte | 21 ++- src/lib/components/LazyImg.svelte | 2 +- src/lib/pages/LibraryPage.svelte | 72 ++++++-- src/lib/stores/data.store.ts | 6 +- src/lib/utils.ts | 19 ++ 9 files changed, 220 insertions(+), 157 deletions(-) diff --git a/src/lib/apis/radarr/radarr-api.ts b/src/lib/apis/radarr/radarr-api.ts index 36c0552..8182249 100644 --- a/src/lib/apis/radarr/radarr-api.ts +++ b/src/lib/apis/radarr/radarr-api.ts @@ -199,7 +199,7 @@ export class RadarrApi implements Api { }) .then((res) => res.response.ok) || Promise.resolve(false); - getRadarrDownloads = (): Promise => + getDownloads = (): Promise => this.getClient() ?.GET('/api/v3/queue', { params: { @@ -212,12 +212,10 @@ export class RadarrApi implements Api { Promise.resolve([]); getDownloadsById = (radarrId: number) => - this.getRadarrDownloads().then((downloads) => downloads.filter((d) => d.movie.id === radarrId)); + this.getDownloads().then((downloads) => downloads.filter((d) => d.movie.id === radarrId)); getRadarrDownloadsByTmdbId = (tmdbId: number) => - this.getRadarrDownloads().then((downloads) => - downloads.filter((d) => d.movie.tmdbId === tmdbId) - ); + this.getDownloads().then((downloads) => downloads.filter((d) => d.movie.tmdbId === tmdbId)); private lookupRadarrMovieByTmdbId = (tmdbId: number) => this.getClient() diff --git a/src/lib/apis/sonarr/sonarr-api.ts b/src/lib/apis/sonarr/sonarr-api.ts index e43e276..931b6a7 100644 --- a/src/lib/apis/sonarr/sonarr-api.ts +++ b/src/lib/apis/sonarr/sonarr-api.ts @@ -248,7 +248,7 @@ export class SonarrApi implements ApiAsync { .then((res) => res.response.ok) || Promise.resolve(false) ); - getSonarrDownloads = (): Promise => + getDownloads = (): Promise => this.getClient().then( (client) => client @@ -271,9 +271,8 @@ export class SonarrApi implements ApiAsync { ); getDownloadsBySeriesId = (sonarrId: number) => - this.getSonarrDownloads().then((downloads) => - downloads.filter((d) => d.seriesId === sonarrId) - ) || Promise.resolve([]); + this.getDownloads().then((downloads) => downloads.filter((d) => d.seriesId === sonarrId)) || + Promise.resolve([]); removeFromSonarr = (id: number): Promise => this.getClient().then( diff --git a/src/lib/components/AnimateScale.svelte b/src/lib/components/AnimateScale.svelte index a1a799a..ba73f98 100644 --- a/src/lib/components/AnimateScale.svelte +++ b/src/lib/components/AnimateScale.svelte @@ -9,7 +9,7 @@
; - let dimensions = getDimensions(window.innerWidth); - - function getDimensions(viewportWidth: number) { - const minWidth = 240; - - const margin = 128; - const gap = 32; - - const cols = Math.floor((gap - 2 * margin + viewportWidth) / (minWidth + gap)); - const scale = -(gap * (cols - 1) + 2 * margin - viewportWidth) / (cols * minWidth); - - const newWidth = minWidth * scale; - const newHeight = (3 / 2) * newWidth; - - return { - width: newWidth, - height: newHeight - }; - } + let dimensions = getCardDimensions(window.innerWidth); - (dimensions = getDimensions(e.currentTarget.innerWidth))} /> + (dimensions = getCardDimensions(e.currentTarget.innerWidth))} /> - - { - if (tmdbId || tvdbId) { - // navigate(navigateWithType ? `${type}/${tmdbId || tvdbId}` : `${tmdbId || tvdbId}`); - navigate(`/${type}/${tmdbId || tvdbId}`); - } - }} - on:enter - class={classNames( - 'relative flex flex-shrink-0 rounded-xl group hover:text-inherit overflow-hidden text-left cursor-pointer', - 'selectable', - { - 'aspect-video': orientation === 'landscape', - 'aspect-[2/3]': orientation === 'portrait', - 'w-32 h-48': size === 'sm' && orientation === 'portrait', - 'h-32 w-56': size === 'sm' && orientation === 'landscape', - 'w-44 h-64': size === 'md' && orientation === 'portrait', - 'h-44 w-80': size === 'md' && orientation === 'landscape', - // 'w-60 h-96': size === 'lg' && orientation === 'portrait', - 'h-60 w-96': size === 'lg' && orientation === 'landscape', - 'w-full h-96': size === 'dynamic', - 'shadow-lg': shadow - } - )} - style={`width: ${dimensions.width}px; height: ${dimensions.height}px;`} - focusOnClick - bind:hasFocus - > - - - - - - - - +
+ {#if group} +
+ +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
+ {/if} + + { + if (tmdbId || tvdbId) navigate(`/${type}/${tmdbId || tvdbId}`); + }} + on:enter + class={classNames( + 'relative flex flex-shrink-0 rounded-xl group hover:text-inherit overflow-hidden text-left cursor-pointer', + 'selectable', + { + 'aspect-video': orientation === 'landscape', + 'aspect-[2/3]': orientation === 'portrait', + 'w-32 h-48': size === 'sm' && orientation === 'portrait', + 'h-32 w-56': size === 'sm' && orientation === 'landscape', + 'w-44 h-64': size === 'md' && orientation === 'portrait', + 'h-44 w-80': size === 'md' && orientation === 'landscape', + // 'w-60 h-96': size === 'lg' && orientation === 'portrait', + 'h-60 w-96': size === 'lg' && orientation === 'landscape', + 'w-full h-96': size === 'dynamic', + 'shadow-lg': shadow + } + )} + style={`width: ${dimensions.width}px; height: ${dimensions.height}px;`} + focusOnClick + bind:hasFocus + > + + + + + + + + + + + + + + + + + + + + + + - - {#if jellyfinId} -
- { - e.preventDefault(); - jellyfinId && true; //playerState.streamJellyfinId(jellyfinId); - }} - class="sm:opacity-0 group-hover:opacity-100 transition-opacity" - /> -
- {/if} - {#if progress} -
- -
- {/if} -
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {#if jellyfinId} +
+ { + e.preventDefault(); + jellyfinId && true; //playerState.streamJellyfinId(jellyfinId); + }} + class="sm:opacity-0 group-hover:opacity-100 transition-opacity" + /> +
+ {/if} + {#if progress} +
+ +
+ {/if} + + +
diff --git a/src/lib/components/CardGrid.svelte b/src/lib/components/CardGrid.svelte index 04864bb..e8b34c7 100644 --- a/src/lib/components/CardGrid.svelte +++ b/src/lib/components/CardGrid.svelte @@ -2,10 +2,14 @@ import Container from '../../Container.svelte'; import { onMount } from 'svelte'; import classNames from 'classnames'; + import { getCardDimensions } from '../utils'; export let direction: 'horizontal' | 'vertical' = 'vertical'; - let cols: number = 1; + let cols = getCardDimensions(window.innerWidth).columns; + $: console.log('cols', cols); + + // let cols: number = 1; const calculateRows = () => { const width = window.innerWidth; if (direction === 'vertical') { @@ -34,11 +38,13 @@ } }; - onMount(() => { - calculateRows(); - }); + // onMount(() => { + // calculateRows(); + // }); + (cols = getCardDimensions(e.currentTarget.innerWidth).columns)} /> + - + diff --git a/src/lib/components/LazyImg.svelte b/src/lib/components/LazyImg.svelte index 7697d34..5827066 100644 --- a/src/lib/components/LazyImg.svelte +++ b/src/lib/components/LazyImg.svelte @@ -13,7 +13,7 @@
import { settings } from '../stores/settings.store'; - import { jellyfinItemsStore } from '../stores/data.store'; import CarouselPlaceholderItems from '../components/Carousel/CarouselPlaceholderItems.svelte'; - import Container from '../../Container.svelte'; import { jellyfinApi } from '../apis/jellyfin/jellyfin-api'; import CardGrid from '../components/CardGrid.svelte'; import JellyfinCard from '../components/Card/JellyfinCard.svelte'; import { scrollIntoView } from '../selectable'; import DetachedPage from '../components/DetachedPage/DetachedPage.svelte'; + import Carousel from '../components/Carousel/Carousel.svelte'; + import { sonarrApi } from '../apis/sonarr/sonarr-api'; + import { radarrApi } from '../apis/radarr/radarr-api'; + import Card from '../components/Card/Card.svelte'; + import type { ComponentProps } from 'svelte'; const libraryItemsP = jellyfinApi.getLibraryItems(); + let sonarrDownloads: Promise[]> = sonarrApi.getDownloads().then((items) => + items + .filter( + (value, index, self) => index === self.findIndex((t) => t.seriesId === value.seriesId) + ) + .map((i) => ({ + backdropUrl: i.series.images?.find((i) => i.coverType === 'poster')?.remoteUrl || '', + group: true + })) + ); + let radarrDownloads: Promise[]> = radarrApi.getDownloads().then((items) => + items.map((i) => ({ + backdropUrl: i.movie.images?.find((i) => i.coverType === 'poster')?.remoteUrl || '' + })) + ); settings.update((prev) => ({ ...prev, @@ -23,22 +41,38 @@ })); - -
-
Library
+ + {#await Promise.all([sonarrDownloads, radarrDownloads]) then [sonarrDownloads, radarrDownloads]} + {#if sonarrDownloads?.length || radarrDownloads?.length} + + Downloading + {#each sonarrDownloads as props} + + {/each} + + {#each radarrDownloads as props} + + {/each} + + {/if} + {/await} +
+
+
Library
+
+ + {#await libraryItemsP} + + {:then items} + {#each items as item} + + {/each} + {/await} +
- - {#await libraryItemsP} - - {:then items} - {#each items as item} - - {/each} - {/await} -
diff --git a/src/lib/stores/data.store.ts b/src/lib/stores/data.store.ts index 5992c0d..754459e 100644 --- a/src/lib/stores/data.store.ts +++ b/src/lib/stores/data.store.ts @@ -83,7 +83,7 @@ export function createJellyfinItemStore(tmdbId: number | Promise) { }; } -export const sonarrSeriesStore = _createDataFetchStore(sonarrApi.getSonarrDownloads); +export const sonarrSeriesStore = _createDataFetchStore(sonarrApi.getDownloads); export const radarrMoviesStore = _createDataFetchStore(radarrApi.getRadarrMovies); export function createRadarrMovieStore(tmdbId: number) { @@ -131,8 +131,8 @@ export function createSonarrSeriesStore(name: Promise | string) { }; } -export const sonarrDownloadsStore = _createDataFetchStore(sonarrApi.getSonarrDownloads); -export const radarrDownloadsStore = _createDataFetchStore(radarrApi.getRadarrDownloads); +export const sonarrDownloadsStore = _createDataFetchStore(sonarrApi.getDownloads); +export const radarrDownloadsStore = _createDataFetchStore(radarrApi.getDownloads); export const servarrDownloadsStore = (() => { const store = derived([sonarrDownloadsStore, radarrDownloadsStore], ([sonarr, radarr]) => { return { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 879dc4b..920fddc 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -133,3 +133,22 @@ export function subscribeUntil(store: Readable, fn: (value: T) => boolean) } }); } + +export function getCardDimensions(viewportWidth: number) { + const minWidth = 240; + + const margin = 128; + const gap = 32; + + const cols = Math.floor((gap - 2 * margin + viewportWidth) / (minWidth + gap)); + const scale = -(gap * (cols - 1) + 2 * margin - viewportWidth) / (cols * minWidth); + + const newWidth = minWidth * scale; + const newHeight = (3 / 2) * newWidth; + + return { + width: newWidth, + height: newHeight, + columns: cols + }; +}