From 5b18c957669b483e1eecb7b8d12a4e2893d63f12 Mon Sep 17 00:00:00 2001 From: Aleksi Lassila Date: Sun, 31 Mar 2024 00:25:55 +0200 Subject: [PATCH] feat: Request utils --- .eslintrc.cjs | 9 +- package.json | 2 +- src/lib/apis/jellyfin/jellyfin-api.ts | 2 +- src/lib/apis/radarr/radarr-api.ts | 264 ++++++++++++++++++ src/lib/apis/radarr/radarrApi.ts | 233 ---------------- src/lib/components/Button.svelte | 20 +- .../LibraryItemContextItems.svelte | 2 +- src/lib/components/DetatchedPage.svelte | 0 src/lib/components/Sidebar/Sidebar.svelte | 2 +- src/lib/constants.ts | 3 + src/lib/pages/MoviePage.svelte | 60 +++- src/lib/stores/data.store.ts | 89 +++++- src/lib/utils.ts | 5 +- 13 files changed, 430 insertions(+), 261 deletions(-) create mode 100644 src/lib/apis/radarr/radarr-api.ts delete mode 100644 src/lib/apis/radarr/radarrApi.ts create mode 100644 src/lib/components/DetatchedPage.svelte diff --git a/.eslintrc.cjs b/.eslintrc.cjs index ebc1958..ccd1e7c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -23,8 +23,13 @@ module.exports = { files: ['*.svelte'], parser: 'svelte-eslint-parser', parserOptions: { - parser: '@typescript-eslint/parser' + parser: '@typescript-eslint/parser', } } - ] + ], + rules: { + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-explicit-any': 'off', + } }; diff --git a/package.json b/package.json index cf01aa9..2322254 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "dev": "vite", "build": "vite build", - "build:tizen": "vite build --outDir tizen/dist", + "build:tizen": "set VITE_PLATFORM=tv&& vite build --outDir tizen/dist", "preview": "vite preview", "preview:tizen": "vite build --outDir tizen/dist && vite preview --outDir tizen/dist", "deploy": "PORT=9494 NODE_ENV=production node build/", diff --git a/src/lib/apis/jellyfin/jellyfin-api.ts b/src/lib/apis/jellyfin/jellyfin-api.ts index 2627c78..a482d82 100644 --- a/src/lib/apis/jellyfin/jellyfin-api.ts +++ b/src/lib/apis/jellyfin/jellyfin-api.ts @@ -190,7 +190,7 @@ export class JellyfinApi implements Api { } export const jellyfinApi = new JellyfinApi(); -export const getJellyfinApiClient = jellyfinApi.getClient; +export const jellyfinApiClient = jellyfinApi.getClient; /* function getJellyfinApi() { diff --git a/src/lib/apis/radarr/radarr-api.ts b/src/lib/apis/radarr/radarr-api.ts new file mode 100644 index 0000000..188e413 --- /dev/null +++ b/src/lib/apis/radarr/radarr-api.ts @@ -0,0 +1,264 @@ +import axios from 'axios'; +import createClient from 'openapi-fetch'; +import { get } from 'svelte/store'; +import { settings } from '../../stores/settings.store'; +import type { components, paths } from './radarr.generated'; +import { getTmdbMovie } from '../tmdb/tmdb-api'; +import { log } from '../../utils'; +import { appState } from '../../stores/app-state.store'; +import type { Api } from '../api.interface'; + +export type RadarrMovie = components['schemas']['MovieResource']; +export type MovieFileResource = components['schemas']['MovieFileResource']; +export type ReleaseResource = components['schemas']['ReleaseResource']; +export type RadarrDownload = components['schemas']['QueueResource'] & { movie: RadarrMovie }; +export type DiskSpaceInfo = components['schemas']['DiskSpaceResource']; + +export interface RadarrMovieOptions { + title: string; + qualityProfileId: number; + minimumAvailability: 'announced' | 'inCinemas' | 'released'; + tags: number[]; + profileId: number; + year: number; + rootFolderPath: string; + tmdbId: number; + monitored?: boolean; + searchNow?: boolean; +} + +export class RadarrApi implements Api { + getClient() { + const radarrSettings = get(appState).user?.settings.radarr; + const baseUrl = radarrSettings?.baseUrl; + const apiKey = radarrSettings?.apiKey; + + return createClient({ + baseUrl, + headers: { + 'X-Api-Key': apiKey + } + }); + } + + getBaseUrl() { + return get(appState)?.user?.settings?.radarr.baseUrl || ''; + } + + getSettings() { + return get(appState).user?.settings.radarr; + } + + getMovieByTmdbId = (tmdbId: number): Promise => + this.getClient() + ?.GET('/api/v3/movie', { + params: { + query: { + tmdbId: Number(tmdbId) + } + } + }) + .then((r) => r.data?.find((m) => m.tmdbId == tmdbId)) || Promise.resolve(undefined); + + getRadarrMovies = (): Promise => + this.getClient() + ?.GET('/api/v3/movie', { + params: {} + }) + .then((r) => r.data || []) || Promise.resolve([]); + + addMovieToRadarr = async (tmdbId: number) => { + const tmdbMovie = await getTmdbMovie(tmdbId); + const radarrMovie = await this.lookupRadarrMovieByTmdbId(tmdbId); + + if (radarrMovie?.id) throw new Error('Movie already exists'); + + if (!tmdbMovie) throw new Error('Movie not found'); + + const options: RadarrMovieOptions = { + qualityProfileId: get(appState).user?.settings.radarr.qualityProfileId || 0, + profileId: get(appState).user?.settings.radarr?.qualityProfileId || 0, + rootFolderPath: get(appState).user?.settings.radarr.rootFolderPath || '', + minimumAvailability: 'announced', + title: tmdbMovie.title || tmdbMovie.original_title || '', + tmdbId: tmdbMovie.id || 0, + year: Number(tmdbMovie.release_date?.slice(0, 4)), + monitored: false, + tags: [], + searchNow: false + }; + + return ( + this.getClient() + ?.POST('/api/v3/movie', { + params: {}, + body: options + }) + .then((r) => r.data) || Promise.resolve(undefined) + ); + }; + + cancelDownloadRadarrMovie = async (downloadId: number) => { + const deleteResponse = await this.getClient() + ?.DELETE('/api/v3/queue/{id}', { + params: { + path: { + id: downloadId + }, + query: { + blocklist: false, + removeFromClient: true + } + } + }) + .then((r) => log(r)); + + return !!deleteResponse?.response.ok; + }; + + fetchRadarrReleases = (movieId: number) => + this.getClient() + ?.GET('/api/v3/release', { params: { query: { movieId: movieId } } }) + .then((r) => r.data || []) || Promise.resolve([]); + + downloadRadarrMovie = (guid: string, indexerId: number) => + this.getClient() + ?.POST('/api/v3/release', { + params: {}, + body: { + indexerId, + guid + } + }) + .then((res) => res.response.ok) || Promise.resolve(false); + + deleteRadarrMovieFile = (id: number) => + this.getClient() + ?.DELETE('/api/v3/moviefile/{id}', { + params: { + path: { + id + } + } + }) + .then((res) => res.response.ok) || Promise.resolve(false); + + getRadarrDownloads = (): Promise => + this.getClient() + ?.GET('/api/v3/queue', { + params: { + query: { + includeMovie: true + } + } + }) + .then((r) => (r.data?.records?.filter((record) => record.movie) as RadarrDownload[]) || []) || + Promise.resolve([]); + + getRadarrDownloadsById = (radarrId: number) => + this.getRadarrDownloads().then((downloads) => downloads.filter((d) => d.movie.id === radarrId)); + + getRadarrDownloadsByTmdbId = (tmdbId: number) => + this.getRadarrDownloads().then((downloads) => + downloads.filter((d) => d.movie.tmdbId === tmdbId) + ); + + private lookupRadarrMovieByTmdbId = (tmdbId: number) => + this.getClient() + ?.GET('/api/v3/movie/lookup/tmdb', { + params: { + query: { + tmdbId + } + } + }) + .then((r) => r.data as unknown as RadarrMovie) || Promise.resolve(undefined); + + getDiskSpace = (): Promise => + this.getClient() + ?.GET('/api/v3/diskspace', {}) + .then((d) => d.data || []) || Promise.resolve([]); + + removeFromRadarr = (id: number) => + this.getClient() + ?.DELETE('/api/v3/movie/{id}', { + params: { + path: { + id + } + } + }) + .then((res) => res.response.ok) || Promise.resolve(false); + + getRadarrHealth = async ( + baseUrl: string | undefined = undefined, + apiKey: string | undefined = undefined + ) => + axios + .get((baseUrl || this.getBaseUrl()) + '/api/v3/health', { + headers: { + 'X-Api-Key': apiKey || this.getSettings()?.apiKey + } + }) + .then((res) => res.status === 200) + .catch(() => false); + + getRootFolders = async ( + baseUrl: string | undefined = undefined, + apiKey: string | undefined = undefined + ) => + axios + .get( + (baseUrl || this.getBaseUrl()) + '/api/v3/rootFolder', + { + headers: { + 'X-Api-Key': apiKey || this.getSettings()?.apiKey + } + } + ) + .then((res) => res.data || []); + + getQualityProfiles = async ( + baseUrl: string | undefined = undefined, + apiKey: string | undefined = undefined + ) => + axios + .get( + (baseUrl || get(appState)?.user?.settings.radarr.baseUrl) + '/api/v3/qualityprofile', + { + headers: { + 'X-Api-Key': apiKey || get(appState)?.user?.settings.radarr.apiKey + } + } + ) + .then((res) => res.data || []); + + getRadarrPosterUrl(item: RadarrMovie, original = false) { + const url = + get(settings).radarr.baseUrl + + (item.images?.find((i) => i.coverType === 'poster')?.url || ''); + + if (!original) return url.replace('poster.jpg', `poster-${500}.jpg`); + + return url; + } +} + +export const radarrApi = new RadarrApi(); +export const radarrApiClient = radarrApi.getClient; + +// function getRadarrApi() { +// const baseUrl = get(settings)?.radarr.baseUrl; +// const apiKey = get(settings)?.radarr.apiKey; +// const rootFolder = get(settings)?.radarr.rootFolderPath; +// const qualityProfileId = get(settings)?.radarr.qualityProfileId; +// +// if (!baseUrl || !apiKey || !rootFolder || !qualityProfileId) return undefined; +// +// return createClient({ +// baseUrl, +// headers: { +// 'X-Api-Key': apiKey +// } +// }); +// } diff --git a/src/lib/apis/radarr/radarrApi.ts b/src/lib/apis/radarr/radarrApi.ts deleted file mode 100644 index 069e677..0000000 --- a/src/lib/apis/radarr/radarrApi.ts +++ /dev/null @@ -1,233 +0,0 @@ -import axios from 'axios'; -import createClient from 'openapi-fetch'; -import { get } from 'svelte/store'; -import { settings } from '../../stores/settings.store'; -import type { components, paths } from './radarr.generated'; -import { getTmdbMovie } from '../tmdb/tmdb-api'; -import { log } from '../../utils'; - -export type RadarrMovie = components['schemas']['MovieResource']; -export type MovieFileResource = components['schemas']['MovieFileResource']; -export type ReleaseResource = components['schemas']['ReleaseResource']; -export type RadarrDownload = components['schemas']['QueueResource'] & { movie: RadarrMovie }; -export type DiskSpaceInfo = components['schemas']['DiskSpaceResource']; - -export interface RadarrMovieOptions { - title: string; - qualityProfileId: number; - minimumAvailability: 'announced' | 'inCinemas' | 'released'; - tags: number[]; - profileId: number; - year: number; - rootFolderPath: string; - tmdbId: number; - monitored?: boolean; - searchNow?: boolean; -} - -function getRadarrApi() { - const baseUrl = get(settings)?.radarr.baseUrl; - const apiKey = get(settings)?.radarr.apiKey; - const rootFolder = get(settings)?.radarr.rootFolderPath; - const qualityProfileId = get(settings)?.radarr.qualityProfileId; - - if (!baseUrl || !apiKey || !rootFolder || !qualityProfileId) return undefined; - - return createClient({ - baseUrl, - headers: { - 'X-Api-Key': apiKey - } - }); -} - -export const getRadarrMovies = (): Promise => - getRadarrApi() - ?.GET('/api/v3/movie', { - params: {} - }) - .then((r) => r.data || []) || Promise.resolve([]); - -export const getRadarrMovieByTmdbId = (tmdbId: string): Promise => - getRadarrApi() - ?.GET('/api/v3/movie', { - params: { - query: { - tmdbId: Number(tmdbId) - } - } - }) - .then((r) => r.data?.find((m) => (m.tmdbId as any) == tmdbId)) || Promise.resolve(undefined); - -export const addMovieToRadarr = async (tmdbId: number) => { - const tmdbMovie = await getTmdbMovie(tmdbId); - const radarrMovie = await lookupRadarrMovieByTmdbId(tmdbId); - - if (radarrMovie?.id) throw new Error('Movie already exists'); - - if (!tmdbMovie) throw new Error('Movie not found'); - - const options: RadarrMovieOptions = { - qualityProfileId: get(settings)?.radarr.qualityProfileId || 0, - profileId: get(settings)?.radarr.profileId || 0, - rootFolderPath: get(settings)?.radarr.rootFolderPath || '', - minimumAvailability: 'announced', - title: tmdbMovie.title || tmdbMovie.original_title || '', - tmdbId: tmdbMovie.id || 0, - year: Number(tmdbMovie.release_date?.slice(0, 4)), - monitored: false, - tags: [], - searchNow: false - }; - - return ( - getRadarrApi() - ?.POST('/api/v3/movie', { - params: {}, - body: options - }) - .then((r) => r.data) || Promise.resolve(undefined) - ); -}; - -export const cancelDownloadRadarrMovie = async (downloadId: number) => { - const deleteResponse = await getRadarrApi() - ?.DELETE('/api/v3/queue/{id}', { - params: { - path: { - id: downloadId - }, - query: { - blocklist: false, - removeFromClient: true - } - } - }) - .then((r) => log(r)); - - return !!deleteResponse?.response.ok; -}; - -export const fetchRadarrReleases = (movieId: number) => - getRadarrApi() - ?.GET('/api/v3/release', { params: { query: { movieId: movieId } } }) - .then((r) => r.data || []) || Promise.resolve([]); - -export const downloadRadarrMovie = (guid: string, indexerId: number) => - getRadarrApi() - ?.POST('/api/v3/release', { - params: {}, - body: { - indexerId, - guid - } - }) - .then((res) => res.response.ok) || Promise.resolve(false); - -export const deleteRadarrMovie = (id: number) => - getRadarrApi() - ?.DELETE('/api/v3/moviefile/{id}', { - params: { - path: { - id - } - } - }) - .then((res) => res.response.ok) || Promise.resolve(false); - -export const getRadarrDownloads = (): Promise => - getRadarrApi() - ?.GET('/api/v3/queue', { - params: { - query: { - includeMovie: true - } - } - }) - .then((r) => (r.data?.records?.filter((record) => record.movie) as RadarrDownload[]) || []) || - Promise.resolve([]); - -export const getRadarrDownloadsById = (radarrId: number) => - getRadarrDownloads().then((downloads) => downloads.filter((d) => d.movie.id === radarrId)); - -export const getRadarrDownloadsByTmdbId = (tmdbId: number) => - getRadarrDownloads().then((downloads) => downloads.filter((d) => d.movie.tmdbId === tmdbId)); - -const lookupRadarrMovieByTmdbId = (tmdbId: number) => - getRadarrApi() - ?.GET('/api/v3/movie/lookup/tmdb', { - params: { - query: { - tmdbId - } - } - }) - .then((r) => r.data as any as RadarrMovie) || Promise.resolve(undefined); - -export const getDiskSpace = (): Promise => - getRadarrApi() - ?.GET('/api/v3/diskspace', {}) - .then((d) => d.data || []) || Promise.resolve([]); - -export const removeFromRadarr = (id: number) => - getRadarrApi() - ?.DELETE('/api/v3/movie/{id}', { - params: { - path: { - id - } - } - }) - .then((res) => res.response.ok) || Promise.resolve(false); - -export const getRadarrHealth = async ( - baseUrl: string | undefined = undefined, - apiKey: string | undefined = undefined -) => - axios - .get((baseUrl || get(settings)?.radarr.baseUrl) + '/api/v3/health', { - headers: { - 'X-Api-Key': apiKey || get(settings)?.radarr.apiKey - } - }) - .then((res) => res.status === 200) - .catch(() => false); - -export const getRadarrRootFolders = async ( - baseUrl: string | undefined = undefined, - apiKey: string | undefined = undefined -) => - axios - .get( - (baseUrl || get(settings)?.sonarr.baseUrl) + '/api/v3/rootFolder', - { - headers: { - 'X-Api-Key': apiKey || get(settings)?.sonarr.apiKey - } - } - ) - .then((res) => res.data || []); - -export const getRadarrQualityProfiles = async ( - baseUrl: string | undefined = undefined, - apiKey: string | undefined = undefined -) => - axios - .get( - (baseUrl || get(settings)?.sonarr.baseUrl) + '/api/v3/qualityprofile', - { - headers: { - 'X-Api-Key': apiKey || get(settings)?.sonarr.apiKey - } - } - ) - .then((res) => res.data || []); - -export function getRadarrPosterUrl(item: RadarrMovie, original = false) { - const url = - get(settings).radarr.baseUrl + (item.images?.find((i) => i.coverType === 'poster')?.url || ''); - - if (!original) return url.replace('poster.jpg', `poster-${500}.jpg`); - - return url; -} diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte index 409cd54..9ca2130 100644 --- a/src/lib/components/Button.svelte +++ b/src/lib/components/Button.svelte @@ -2,16 +2,32 @@ import Container from '../../Container.svelte'; import type { Readable } from 'svelte/store'; import classNames from 'classnames'; + + export let inactive: boolean = false; + let hasFoucus: Readable; + {#if $$slots.icon} +
+ +
+ {/if} + {#if $$slots['icon-after']} +
+ +
+ {/if}
diff --git a/src/lib/components/ContextMenu/LibraryItemContextItems.svelte b/src/lib/components/ContextMenu/LibraryItemContextItems.svelte index d42df96..0c8bc57 100644 --- a/src/lib/components/ContextMenu/LibraryItemContextItems.svelte +++ b/src/lib/components/ContextMenu/LibraryItemContextItems.svelte @@ -4,7 +4,7 @@ setJellyfinItemWatched, type JellyfinItem } from '$lib/apis/jellyfin/jellyfinApi'; - import type { RadarrMovie } from '../../apis/radarr/radarrApi'; + import type { RadarrMovie } from '../../apis/radarr/radarr-api'; import type { SonarrSeries } from '../../apis/sonarr/sonarrApi'; import { jellyfinItemsStore } from '../../stores/data.store'; import { settings } from '../../stores/settings.store'; diff --git a/src/lib/components/DetatchedPage.svelte b/src/lib/components/DetatchedPage.svelte new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/components/Sidebar/Sidebar.svelte b/src/lib/components/Sidebar/Sidebar.svelte index 2b9c28f..bf1edf9 100644 --- a/src/lib/components/Sidebar/Sidebar.svelte +++ b/src/lib/components/Sidebar/Sidebar.svelte @@ -40,7 +40,7 @@ - navigate('/movie/695721')}> + navigate('/movie/359410')}>
diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 23812c9..971f041 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -6,3 +6,6 @@ export const TMDB_POSTER_SMALL = 'https://www.themoviedb.org/t/p/w342'; export const TMDB_PROFILE_SMALL = 'https://www.themoviedb.org/t/p/w185'; export const PLACEHOLDER_BACKDROP = '/plcaeholder.jpg'; + +export const PLATFORM_TV: boolean = import.meta.env.VITE_PLATFORM === 'tv'; +export const PLATFORM_WEB: boolean = !PLATFORM_TV; diff --git a/src/lib/pages/MoviePage.svelte b/src/lib/pages/MoviePage.svelte index 890f94b..c2ed646 100644 --- a/src/lib/pages/MoviePage.svelte +++ b/src/lib/pages/MoviePage.svelte @@ -2,28 +2,42 @@ import Container from '../../Container.svelte'; import HeroCarousel from '../components/HeroCarousel/HeroCarousel.svelte'; import { tmdbApi } from '../apis/tmdb/tmdb-api'; - import { TMDB_IMAGES_ORIGINAL } from '../constants'; + import { PLATFORM_WEB, TMDB_IMAGES_ORIGINAL } from '../constants'; import classNames from 'classnames'; - import { DotFilled } from 'radix-icons-svelte'; + import { DotFilled, ExternalLink, Plus } from 'radix-icons-svelte'; import Button from '../components/Button.svelte'; import { jellyfinApi } from '../apis/jellyfin/jellyfin-api'; import VideoPlayer from '../components/VideoPlayer/VideoPlayer.svelte'; + import { radarrApi } from '../apis/radarr/radarr-api'; + import { useActionRequests, useRequest } from '../stores/data.store'; export let id: string; - const movieDataP = tmdbApi.getTmdbMovie(Number(id)); - const jellyfinItem = jellyfinApi.getLibraryItemFromTmdbId(id); + const { promise: movieDataP } = useRequest(tmdbApi.getTmdbMovie, Number(id)); + const { promise: jellyfinItemP } = useRequest( + (id: string) => jellyfinApi.getLibraryItemFromTmdbId(id), + id + ); + const { promise: radarrItemP, refresh: refreshRadarrItem } = useRequest( + radarrApi.getMovieByTmdbId, + Number(id) + ); let playbackId: string = ''; let heroIndex: number; + + const { requests, isFetching, data } = useActionRequests({ + handleAddToRadarr: (id: number) => + radarrApi.addMovieToRadarr(id).finally(() => refreshRadarrItem(Number(id))) + });
movie?.images.backdrops ?.sort((a, b) => (b.vote_count || 0) - (a.vote_count || 0)) @@ -32,7 +46,7 @@ )} >
- {#await movieDataP then movie} + {#await $movieDataP then movie} {#if movie}

{new Date(movie.release_date || Date.now())?.getFullYear()} @@ -65,12 +79,32 @@

{/if} {/await} - {#await jellyfinItem then item} - {#if item} -
- -
- {/if} + {#await Promise.all([$jellyfinItemP, $radarrItemP]) then [jellyfinItem, radarrItem]} + + {#if jellyfinItem} + + {:else if radarrItem} + + {:else} + + {/if} + {#if PLATFORM_WEB} + + + {/if} + {/await}
diff --git a/src/lib/stores/data.store.ts b/src/lib/stores/data.store.ts index 7095871..4054de8 100644 --- a/src/lib/stores/data.store.ts +++ b/src/lib/stores/data.store.ts @@ -7,7 +7,7 @@ import { type SonarrDownload, type SonarrSeries } from '../apis/sonarr/sonarrApi'; -import { getRadarrDownloads, getRadarrMovies, type RadarrDownload } from '../apis/radarr/radarrApi'; +import { radarrApi, type RadarrDownload } from '../apis/radarr/radarr-api'; async function waitForSettings() { return new Promise((resolve) => { @@ -89,7 +89,7 @@ export function createJellyfinItemStore(tmdbId: number | Promise) { } export const sonarrSeriesStore = _createDataFetchStore(getSonarrSeries); -export const radarrMoviesStore = _createDataFetchStore(getRadarrMovies); +export const radarrMoviesStore = _createDataFetchStore(radarrApi.getRadarrMovies); export function createRadarrMovieStore(tmdbId: number) { const store = derived(radarrMoviesStore, (s) => { @@ -137,7 +137,7 @@ export function createSonarrSeriesStore(name: Promise | string) { } export const sonarrDownloadsStore = _createDataFetchStore(getSonarrDownloads); -export const radarrDownloadsStore = _createDataFetchStore(getRadarrDownloads); +export const radarrDownloadsStore = _createDataFetchStore(radarrApi.getRadarrDownloads); export const servarrDownloadsStore = (() => { const store = derived([sonarrDownloadsStore, radarrDownloadsStore], ([sonarr, radarr]) => { return { @@ -213,3 +213,86 @@ export function createSonarrDownloadStore( refresh: async () => sonarrDownloadsStore.refresh() }; } + +export const useActionRequests =

Promise>>( + values: P +) => { + const initialFetching: Record = {} as any; + Object.keys(values).forEach((key) => { + initialFetching[key as keyof P] = false; + }); + + const fetching = writable(initialFetching); + + const initialData: Record> | undefined> = {} as any; + Object.keys(values).forEach((key) => { + initialData[key as keyof P] = undefined; + }); + + const data = writable(initialData); + + const methods: P = {} as any; + Object.keys(values).forEach((key) => { + // @ts-expect-error + methods[key as keyof P] = async (...args: any[]) => { + fetching.update((f) => ({ ...f, [key]: true })); + values[key as keyof P]?.(...args) + .then((d) => data.update((prev) => ({ ...prev, [key]: d }))) + .finally(() => { + fetching.update((f) => ({ ...f, [key]: false })); + }); + }; + }); + + return { + requests: methods, + data: { + subscribe: data.subscribe + }, + isFetching: { + subscribe: fetching.subscribe + } + }; +}; +export const useRequest =

Promise, A extends any[]>( + fn: P, + ...initialArgs: A +) => { + const request = writable>(undefined); + const data = writable> | undefined>(undefined); + const isLoading = writable(true); + const isFetching = writable(true); + + function refresh(...args: A): ReturnType

{ + isFetching.set(true); + const p: ReturnType

= fn(...args) + .then((res) => { + data.set(res); + return res; + }) + .finally(() => { + isFetching.set(false); + }); + + request.set(p); + return p; + } + + refresh(...initialArgs); + + return { + promise: { + subscribe: request.subscribe + }, + data: { + subscribe: data.subscribe + }, + isLoading: { + subscribe: isLoading.subscribe + }, + isFetching: { + subscribe: isFetching.subscribe + }, + refresh + }; +}; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index a00c569..d2fc21b 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,7 +1,4 @@ -import { get, writable } from 'svelte/store'; -import type { SonarrSeries } from './apis/sonarr/sonarrApi'; -import type { RadarrMovie } from './apis/radarr/radarrApi'; -import { settings } from './stores/settings.store'; +import { writable } from 'svelte/store'; export function formatMinutesToTime(minutes: number) { const days = Math.floor(minutes / 60 / 24);