feat: Implemented jellyfin api
This commit is contained in:
2
.idea/reiverr.iml
generated
2
.idea/reiverr.iml
generated
@@ -9,6 +9,8 @@
|
|||||||
<excludeFolder url="file://$MODULE_DIR$/.svelte-kit/output" />
|
<excludeFolder url="file://$MODULE_DIR$/.svelte-kit/output" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/tizen/dist" />
|
<excludeFolder url="file://$MODULE_DIR$/tizen/dist" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tizen/.buildResult" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import type createClient from 'openapi-fetch';
|
import type createClient from 'openapi-fetch';
|
||||||
|
|
||||||
export abstract class Api<Paths extends NonNullable<unknown>> {
|
export interface Api<Paths extends NonNullable<unknown>> {
|
||||||
protected abstract baseUrl: string;
|
getClient(): ReturnType<typeof createClient<Paths>>;
|
||||||
protected abstract client: ReturnType<typeof createClient<Paths>>;
|
|
||||||
protected abstract isLoggedIn: boolean;
|
|
||||||
|
|
||||||
getApi() {
|
|
||||||
return this.client;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export abstract class Api<Paths extends NonNullable<unknown>> {
|
||||||
|
// protected abstract baseUrl: string;
|
||||||
|
// protected abstract client: ReturnType<typeof createClient<Paths>>;
|
||||||
|
// protected abstract isLoggedIn: boolean;
|
||||||
|
//
|
||||||
|
// getApi() {
|
||||||
|
// return this.client;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|||||||
@@ -4,11 +4,82 @@ import { get } from 'svelte/store';
|
|||||||
import type { components, paths } from './jellyfin.generated';
|
import type { components, paths } from './jellyfin.generated';
|
||||||
import { settings } from '../../stores/settings.store';
|
import { settings } from '../../stores/settings.store';
|
||||||
import type { DeviceProfile } from './playback-profiles';
|
import type { DeviceProfile } from './playback-profiles';
|
||||||
|
import type { Api } from '../api.interface';
|
||||||
|
import { appState } from '../../stores/app-state.store';
|
||||||
|
|
||||||
export type JellyfinItem = components['schemas']['BaseItemDto'];
|
export type JellyfinItem = components['schemas']['BaseItemDto'];
|
||||||
|
|
||||||
export const JELLYFIN_DEVICE_ID = 'Reiverr Client';
|
export const JELLYFIN_DEVICE_ID = 'Reiverr Client';
|
||||||
|
|
||||||
|
export class JellyfinApi implements Api<paths> {
|
||||||
|
getClient() {
|
||||||
|
const jellyfinSettings = get(appState).user?.settings.jellyfin;
|
||||||
|
const baseUrl = jellyfinSettings?.baseUrl;
|
||||||
|
const apiKey = jellyfinSettings?.apiKey;
|
||||||
|
|
||||||
|
return createClient<paths>({
|
||||||
|
baseUrl,
|
||||||
|
headers: {
|
||||||
|
Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${apiKey}"`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserId() {
|
||||||
|
return get(appState).user?.settings.jellyfin.userId || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContinueWatching(): Promise<JellyfinItem[] | undefined> {
|
||||||
|
return this.getClient()
|
||||||
|
.GET('/Users/{userId}/Items/Resume', {
|
||||||
|
params: {
|
||||||
|
path: {
|
||||||
|
userId: this.getUserId()
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
mediaTypes: ['Video'],
|
||||||
|
fields: ['ProviderIds', 'Genres']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((r) => r.data?.Items || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLibraryItems() {
|
||||||
|
return (
|
||||||
|
this.getClient()
|
||||||
|
.GET('/Users/{userId}/Items', {
|
||||||
|
params: {
|
||||||
|
path: {
|
||||||
|
userId: this.getUserId()
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
hasTmdbId: true,
|
||||||
|
recursive: true,
|
||||||
|
includeItemTypes: ['Movie', 'Series'],
|
||||||
|
fields: ['ProviderIds', 'Genres', 'DateLastMediaAdded', 'DateCreated']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((r) => r.data?.Items || []) || Promise.resolve([])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPosterUrl(item: JellyfinItem, quality = 100, original = false) {
|
||||||
|
return item.ImageTags?.Primary
|
||||||
|
? `${get(appState).user?.settings.jellyfin.baseUrl}/Items/${
|
||||||
|
item?.Id
|
||||||
|
}/Images/Primary?quality=${quality}${original ? '' : '&fillWidth=432'}&tag=${
|
||||||
|
item?.ImageTags?.Primary
|
||||||
|
}`
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const jellyfinApi = new JellyfinApi();
|
||||||
|
export const getReiverrApiClient = jellyfinApi.getClient;
|
||||||
|
|
||||||
|
/*
|
||||||
function getJellyfinApi() {
|
function getJellyfinApi() {
|
||||||
const baseUrl = get(settings)?.jellyfin.baseUrl;
|
const baseUrl = get(settings)?.jellyfin.baseUrl;
|
||||||
const apiKey = get(settings)?.jellyfin.apiKey;
|
const apiKey = get(settings)?.jellyfin.apiKey;
|
||||||
@@ -51,23 +122,6 @@ export const getJellyfinNextUp = async () =>
|
|||||||
})
|
})
|
||||||
.then((r) => r.data?.Items || []);
|
.then((r) => r.data?.Items || []);
|
||||||
|
|
||||||
export const getJellyfinItems = async () =>
|
|
||||||
getJellyfinApi()
|
|
||||||
?.GET('/Users/{userId}/Items', {
|
|
||||||
params: {
|
|
||||||
path: {
|
|
||||||
userId: get(settings)?.jellyfin.userId || ''
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
hasTmdbId: true,
|
|
||||||
recursive: true,
|
|
||||||
includeItemTypes: ['Movie', 'Series'],
|
|
||||||
fields: ['ProviderIds', 'Genres', 'DateLastMediaAdded', 'DateCreated']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((r) => r.data?.Items || []) || Promise.resolve([]);
|
|
||||||
|
|
||||||
// export const getJellyfinSeries = () =>
|
// export const getJellyfinSeries = () =>
|
||||||
// JellyfinApi.get('/Users/{userId}/Items', {
|
// JellyfinApi.get('/Users/{userId}/Items', {
|
||||||
// params: {
|
// params: {
|
||||||
@@ -285,13 +339,6 @@ export const getJellyfinUsers = async (
|
|||||||
.then((res) => res.data || [])
|
.then((res) => res.data || [])
|
||||||
.catch(() => []);
|
.catch(() => []);
|
||||||
|
|
||||||
export const getJellyfinPosterUrl = (item: JellyfinItem, quality = 100, original = false) =>
|
|
||||||
item.ImageTags?.Primary
|
|
||||||
? `${get(settings).jellyfin.baseUrl}/Items/${item?.Id}/Images/Primary?quality=${quality}${
|
|
||||||
original ? '' : '&fillWidth=432'
|
|
||||||
}&tag=${item?.ImageTags?.Primary}`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
export const getJellyfinBackdrop = (item: JellyfinItem, quality = 100) => {
|
export const getJellyfinBackdrop = (item: JellyfinItem, quality = 100) => {
|
||||||
if (item.BackdropImageTags?.length) {
|
if (item.BackdropImageTags?.length) {
|
||||||
return `${get(settings).jellyfin.baseUrl}/Items/${
|
return `${get(settings).jellyfin.baseUrl}/Items/${
|
||||||
@@ -303,3 +350,4 @@ export const getJellyfinBackdrop = (item: JellyfinItem, quality = 100) => {
|
|||||||
}/Images/Primary?quality=${quality}&tag=${item?.ImageTags?.Primary}`;
|
}/Images/Primary?quality=${quality}&tag=${item?.ImageTags?.Primary}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
@@ -2,11 +2,9 @@ import createClient from 'openapi-fetch';
|
|||||||
import type { paths } from './reiverr.generated';
|
import type { paths } from './reiverr.generated';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { appState } from '../../stores/app-state.store';
|
import { appState } from '../../stores/app-state.store';
|
||||||
|
import type { Api } from '../api.interface';
|
||||||
|
|
||||||
interface ApiInterface<Paths extends NonNullable<unknown>> {
|
export class ReiverrApi implements Api<paths> {
|
||||||
getClient(): ReturnType<typeof createClient<Paths>>;
|
|
||||||
}
|
|
||||||
export class ReiverrApi implements ApiInterface<paths> {
|
|
||||||
getClient(basePath?: string) {
|
getClient(basePath?: string) {
|
||||||
const token = get(appState).token;
|
const token = get(appState).token;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import {
|
import {
|
||||||
setJellyfinItemUnwatched,
|
setJellyfinItemUnwatched,
|
||||||
setJellyfinItemWatched
|
setJellyfinItemWatched
|
||||||
} from '../../../lib/apis/jellyfin/jellyfinApi';
|
} from '../../apis/jellyfin/jellyfin-api';
|
||||||
import { jellyfinItemsStore } from '../../../lib/stores/data.store';
|
import { jellyfinItemsStore } from '../../../lib/stores/data.store';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Check } from 'radix-icons-svelte';
|
import { Check } from 'radix-icons-svelte';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getJellyfinEpisodes, type JellyfinItem } from '../../lib/apis/jellyfin/jellyfinApi';
|
import { getJellyfinEpisodes, type JellyfinItem } from '../apis/jellyfin/jellyfin-api';
|
||||||
import { addSeriesToSonarr, type SonarrSeries } from '../../lib/apis/sonarr/sonarrApi';
|
import { addSeriesToSonarr, type SonarrSeries } from '../../lib/apis/sonarr/sonarrApi';
|
||||||
import {
|
import {
|
||||||
getTmdbIdFromTvdbId,
|
getTmdbIdFromTvdbId,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
reportJellyfinPlaybackProgress,
|
reportJellyfinPlaybackProgress,
|
||||||
reportJellyfinPlaybackStarted,
|
reportJellyfinPlaybackStarted,
|
||||||
reportJellyfinPlaybackStopped
|
reportJellyfinPlaybackStopped
|
||||||
} from '../../apis/jellyfin/jellyfinApi';
|
} from '../../apis/jellyfin/jellyfin-api';
|
||||||
import getDeviceProfile from '../../apis/jellyfin/playback-profiles';
|
import getDeviceProfile from '../../apis/jellyfin/playback-profiles';
|
||||||
import { getQualities } from '../../apis/jellyfin/qualities';
|
import { getQualities } from '../../apis/jellyfin/qualities';
|
||||||
import { settings } from '../../stores/settings.store';
|
import { settings } from '../../stores/settings.store';
|
||||||
|
|||||||
30
src/lib/components/Card/JellyfinCard.svelte
Normal file
30
src/lib/components/Card/JellyfinCard.svelte
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { jellyfinApi, type JellyfinItem } from '../../apis/jellyfin/jellyfin-api';
|
||||||
|
import Card from './Card.svelte';
|
||||||
|
|
||||||
|
export let item: JellyfinItem;
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// tmdbId: Number(item.ProviderIds?.Tmdb) || 0,
|
||||||
|
// jellyfinId: item.Id,
|
||||||
|
// title: item.Name || undefined,
|
||||||
|
// subtitle: item.Genres?.join(', ') || undefined,
|
||||||
|
// backdropUrl: getJellyfinPosterUrl(item, 80),
|
||||||
|
// size: 'dynamic',
|
||||||
|
// ...(item.Type === 'Movie' ? { type: 'movie' } : { type: 'series' }),
|
||||||
|
// orientation: 'portrait',
|
||||||
|
// rating: item.CommunityRating || undefined
|
||||||
|
// };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
tmdbId={Number(item.ProviderIds?.Tmdb) || 0}
|
||||||
|
jellyfinId={item.Id}
|
||||||
|
title={item.Name || undefined}
|
||||||
|
subtitle={item.Genres?.join(', ') || undefined}
|
||||||
|
backdropUrl={jellyfinApi.getPosterUrl(item, 80)}
|
||||||
|
size="dynamic"
|
||||||
|
type={item.Type === 'Movie' ? 'movie' : 'series'}
|
||||||
|
orientation="portrait"
|
||||||
|
rating={item.CommunityRating || undefined}
|
||||||
|
/>
|
||||||
3
src/lib/components/CardGrid.svelte
Normal file
3
src/lib/components/CardGrid.svelte
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<div class="grid gap-x-4 gap-y-8 grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import IconButton from '../IconButton.svelte';
|
import IconButton from '../IconButton.svelte';
|
||||||
import { ChevronLeft, ChevronRight } from 'radix-icons-svelte';
|
import { ChevronLeft, ChevronRight } from 'radix-icons-svelte';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -13,14 +12,15 @@
|
|||||||
export let scrollClass = '';
|
export let scrollClass = '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={classNames('flex flex-col gap-4 group/carousel', $$restProps.class)}>
|
<div class={classNames('flex flex-col group/carousel', $$restProps.class)}>
|
||||||
<div class={'flex justify-between items-center gap-4 ' + scrollClass}>
|
<div class={'flex justify-between items-center mb-2 ' + scrollClass}>
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
<div class="font-semibold text-xl">{heading}</div>
|
<div class="font-semibold text-xl">{heading}</div>
|
||||||
</slot>
|
</slot>
|
||||||
<div
|
<div
|
||||||
class={classNames(
|
class={classNames(
|
||||||
'flex gap-2 sm:opacity-0 transition-opacity sm:group-hover/carousel:opacity-100',
|
'flex gap-2 ml-4',
|
||||||
|
//'sm:opacity-0 transition-opacity sm:group-hover/carousel:opacity-100',
|
||||||
{
|
{
|
||||||
hidden: (carousel?.scrollWidth || 0) === (carousel?.clientWidth || 0)
|
hidden: (carousel?.scrollWidth || 0) === (carousel?.clientWidth || 0)
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<Container horizontal>
|
<Container horizontal>
|
||||||
<div
|
<div
|
||||||
class={classNames(
|
class={classNames(
|
||||||
'flex overflow-x-scroll items-center overflow-y-visible relative scrollbar-hide p-1',
|
'flex overflow-x-scroll items-center overflow-y-visible relative scrollbar-hide',
|
||||||
scrollClass
|
scrollClass
|
||||||
)}
|
)}
|
||||||
bind:this={carousel}
|
bind:this={carousel}
|
||||||
@@ -59,14 +59,12 @@
|
|||||||
</Container>
|
</Container>
|
||||||
{#if scrollX > 50}
|
{#if scrollX > 50}
|
||||||
<div
|
<div
|
||||||
transition:fade={{ duration: 200 }}
|
|
||||||
class={'absolute inset-y-0 left-0 w-0 sm:w-16 md:w-24 bg-gradient-to-r ' +
|
class={'absolute inset-y-0 left-0 w-0 sm:w-16 md:w-24 bg-gradient-to-r ' +
|
||||||
gradientFromColor}
|
gradientFromColor}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if carousel && scrollX < carousel?.scrollWidth - carousel?.clientWidth - 50}
|
{#if carousel && scrollX < carousel?.scrollWidth - carousel?.clientWidth - 50}
|
||||||
<div
|
<div
|
||||||
transition:fade={{ duration: 200 }}
|
|
||||||
class={'absolute inset-y-0 right-0 w-0 sm:w-16 md:w-24 bg-gradient-to-l ' +
|
class={'absolute inset-y-0 right-0 w-0 sm:w-16 md:w-24 bg-gradient-to-l ' +
|
||||||
gradientFromColor}
|
gradientFromColor}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -125,7 +125,7 @@
|
|||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Container class="z-10">
|
<Container class="z-10 pt-8">
|
||||||
<slot />
|
<slot />
|
||||||
</Container>
|
</Container>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
reportJellyfinPlaybackProgress,
|
reportJellyfinPlaybackProgress,
|
||||||
reportJellyfinPlaybackStarted,
|
reportJellyfinPlaybackStarted,
|
||||||
reportJellyfinPlaybackStopped
|
reportJellyfinPlaybackStopped
|
||||||
} from '../../apis/jellyfin/jellyfinApi';
|
} from '../../apis/jellyfin/jellyfin-api';
|
||||||
import getDeviceProfile from '../../apis/jellyfin/playback-profiles';
|
import getDeviceProfile from '../../apis/jellyfin/playback-profiles';
|
||||||
import { getQualities } from '../../apis/jellyfin/qualities';
|
import { getQualities } from '../../apis/jellyfin/qualities';
|
||||||
import { settings } from '../../stores/settings.store';
|
import { settings } from '../../stores/settings.store';
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import type { TitleType } from '../types';
|
import type { TitleType } from '../types';
|
||||||
import type { ComponentProps } from 'svelte';
|
import type { ComponentProps } from 'svelte';
|
||||||
import Poster from '../components/Card/Card.svelte';
|
import Poster from '../components/Card/Card.svelte';
|
||||||
import { getJellyfinItems, type JellyfinItem } from '../apis/jellyfin/jellyfinApi';
|
import { type JellyfinItem } from '../apis/jellyfin/jellyfin-api';
|
||||||
import { jellyfinItemsStore } from '../stores/data.store';
|
import { jellyfinItemsStore } from '../stores/data.store';
|
||||||
import Carousel from '../components/Carousel/Carousel.svelte';
|
import Carousel from '../components/Carousel/Carousel.svelte';
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
@@ -62,12 +62,12 @@
|
|||||||
|
|
||||||
const fetchPopularMovies = () => getTmdbPopularMovies();
|
const fetchPopularMovies = () => getTmdbPopularMovies();
|
||||||
|
|
||||||
const fetchLibraryItems = async () => {
|
// const fetchLibraryItems = async () => {
|
||||||
const items = await getJellyfinItems();
|
// const items = await getJellyfinItems();
|
||||||
const props = await fetchCardProps(items, 'series');
|
// const props = await fetchCardProps(items, 'series');
|
||||||
console.log('JellyfinItems', items, props);
|
// console.log('JellyfinItems', items, props);
|
||||||
return props;
|
// return props;
|
||||||
};
|
// };
|
||||||
|
|
||||||
function parseIncludedLanguages(includedLanguages: string) {
|
function parseIncludedLanguages(includedLanguages: string) {
|
||||||
return includedLanguages.replace(' ', '').split(',').join('|');
|
return includedLanguages.replace(' ', '').split(',').join('|');
|
||||||
@@ -76,8 +76,8 @@
|
|||||||
|
|
||||||
<Container focusOnMount>
|
<Container focusOnMount>
|
||||||
<HeroShowcase items={getTmdbPopularMovies().then(getShowcasePropsFromTmdb)}>
|
<HeroShowcase items={getTmdbPopularMovies().then(getShowcasePropsFromTmdb)}>
|
||||||
<Carousel scrollClass="px-2 sm:px-8 2xl:px-16">
|
<Carousel scrollClass="px-8">
|
||||||
<div slot="title" class="text-lg font-semibold text-zinc-300">
|
<div slot="title" class="text-xl font-semibold text-zinc-300">
|
||||||
{$_('discover.streamingNow')}
|
{$_('discover.streamingNow')}
|
||||||
</div>
|
</div>
|
||||||
{#await fetchNowStreaming()}
|
{#await fetchNowStreaming()}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { settings } from '../stores/settings.store';
|
import { settings } from '../stores/settings.store';
|
||||||
import { jellyfinItemsStore } from '../stores/data.store';
|
import { jellyfinItemsStore } from '../stores/data.store';
|
||||||
import Carousel from '../components/Carousel/Carousel.svelte';
|
|
||||||
import CarouselPlaceholderItems from '../components/Carousel/CarouselPlaceholderItems.svelte';
|
import CarouselPlaceholderItems from '../components/Carousel/CarouselPlaceholderItems.svelte';
|
||||||
import Container from '../../Container.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';
|
||||||
|
|
||||||
|
const libraryItemsP = jellyfinApi.getLibraryItems();
|
||||||
|
|
||||||
settings.update((prev) => ({
|
settings.update((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -21,9 +25,15 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Container focusOnMount>
|
<Container focusOnMount class="pl-20">
|
||||||
<div>LibraryPage</div>
|
<div>LibraryPage</div>
|
||||||
<Carousel>
|
<CardGrid>
|
||||||
<CarouselPlaceholderItems />
|
{#await libraryItemsP}
|
||||||
</Carousel>
|
<CarouselPlaceholderItems />
|
||||||
|
{:then items}
|
||||||
|
{#each items as item}
|
||||||
|
<JellyfinCard {item} />
|
||||||
|
{/each}
|
||||||
|
{/await}
|
||||||
|
</CardGrid>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { derived, writable } from 'svelte/store';
|
import { derived, writable } from 'svelte/store';
|
||||||
import { settings } from './settings.store';
|
import { settings } from './settings.store';
|
||||||
import { getJellyfinItems, type JellyfinItem } from '../apis/jellyfin/jellyfinApi';
|
import { jellyfinApi, type JellyfinItem } from '../apis/jellyfin/jellyfin-api';
|
||||||
import {
|
import {
|
||||||
getSonarrDownloads,
|
getSonarrDownloads,
|
||||||
getSonarrSeries,
|
getSonarrSeries,
|
||||||
@@ -59,7 +59,7 @@ function _createDataFetchStore<T>(fn: () => Promise<T>) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const jellyfinItemsStore = _createDataFetchStore(getJellyfinItems);
|
export const jellyfinItemsStore = _createDataFetchStore(jellyfinApi.getLibraryItems);
|
||||||
|
|
||||||
export function createJellyfinItemStore(tmdbId: number | Promise<number>) {
|
export function createJellyfinItemStore(tmdbId: number | Promise<number>) {
|
||||||
const store = writable<{ loading: boolean; item?: JellyfinItem }>({
|
const store = writable<{ loading: boolean; item?: JellyfinItem }>({
|
||||||
|
|||||||
Reference in New Issue
Block a user