From 355c93e9e8dd34ec64d441eab7b9a26c6b675057 Mon Sep 17 00:00:00 2001 From: Aleksi Lassila Date: Tue, 18 Jun 2024 00:58:03 +0300 Subject: [PATCH] fix: And refactor onboarding integration components --- src/lib/apis/jellyfin/jellyfin-api.ts | 4 +- src/lib/apis/radarr/radarr-api.ts | 4 +- src/lib/apis/sonarr/sonarr-api.ts | 4 +- .../Integrations/JellyfinIntegration.svelte | 111 +++-- .../Integrations/RadarrIntegration.svelte | 64 ++- .../Integrations/SonarrIntegration.svelte | 66 ++- .../Integrations/TmdbIntegration.svelte | 104 ++--- .../TmdbIntegrationConnect.svelte | 10 +- src/lib/pages/ManagePage.svelte | 181 ++------ src/lib/pages/OnboardingPage.svelte | 423 ++++++------------ 10 files changed, 372 insertions(+), 599 deletions(-) diff --git a/src/lib/apis/jellyfin/jellyfin-api.ts b/src/lib/apis/jellyfin/jellyfin-api.ts index 2fbdac8..4d4433f 100644 --- a/src/lib/apis/jellyfin/jellyfin-api.ts +++ b/src/lib/apis/jellyfin/jellyfin-api.ts @@ -506,9 +506,9 @@ export class JellyfinApi implements Api { apiKey: string | undefined = undefined ): Promise => axios - .get((baseUrl || this.getBaseUrl()) + '/Users', { + .get((baseUrl ?? this.getBaseUrl()) + '/Users', { headers: { - 'X-Emby-Token': apiKey || this.getApiKey() + 'X-Emby-Token': apiKey ?? this.getApiKey() } }) .then((res) => res.data || []) diff --git a/src/lib/apis/radarr/radarr-api.ts b/src/lib/apis/radarr/radarr-api.ts index 3df52aa..6aa42c7 100644 --- a/src/lib/apis/radarr/radarr-api.ts +++ b/src/lib/apis/radarr/radarr-api.ts @@ -249,9 +249,9 @@ export class RadarrApi implements Api { apiKey: string | undefined = undefined ) => axios - .get((baseUrl || this.getBaseUrl()) + '/api/v3/health', { + .get((baseUrl ?? this.getBaseUrl()) + '/api/v3/health', { headers: { - 'X-Api-Key': apiKey || this.getSettings()?.apiKey + 'X-Api-Key': apiKey ?? this.getSettings()?.apiKey } }) .catch((e) => e.response); diff --git a/src/lib/apis/sonarr/sonarr-api.ts b/src/lib/apis/sonarr/sonarr-api.ts index b20ba72..55ca3d8 100644 --- a/src/lib/apis/sonarr/sonarr-api.ts +++ b/src/lib/apis/sonarr/sonarr-api.ts @@ -397,9 +397,9 @@ export class SonarrApi implements ApiAsync { apiKey: string | undefined = undefined ) => axios - .get((baseUrl || this.getBaseUrl()) + '/api/v3/health', { + .get((baseUrl ?? this.getBaseUrl()) + '/api/v3/health', { headers: { - 'X-Api-Key': apiKey || this.getApiKey() + 'X-Api-Key': apiKey ?? this.getApiKey() } }) .catch((e) => e.response); diff --git a/src/lib/components/Integrations/JellyfinIntegration.svelte b/src/lib/components/Integrations/JellyfinIntegration.svelte index 1e2b418..888c491 100644 --- a/src/lib/components/Integrations/JellyfinIntegration.svelte +++ b/src/lib/components/Integrations/JellyfinIntegration.svelte @@ -7,55 +7,59 @@ import { derived, get } from 'svelte/store'; const dispatch = createEventDispatcher<{ - change: { baseUrl: string; apiKey: string; stale: boolean }; - 'click-user': { user: JellyfinUser | undefined; users: JellyfinUser[] }; + 'click-user': { + user: JellyfinUser | undefined; + users: JellyfinUser[]; + setJellyfinUser: typeof setJellyfinUser; + }; }>(); - export let baseUrl = get(user)?.settings.jellyfin.baseUrl || ''; - export let apiKey = get(user)?.settings.jellyfin.apiKey || ''; + let baseUrl = get(user)?.settings.jellyfin.baseUrl || ''; + let apiKey = get(user)?.settings.jellyfin.apiKey || ''; + export let jellyfinUser: JellyfinUser | undefined = undefined; const originalBaseUrl = derived(user, (user) => user?.settings.jellyfin.baseUrl || ''); const originalApiKey = derived(user, (user) => user?.settings.jellyfin.apiKey || ''); + const originalUserId = derived(user, (user) => user?.settings.jellyfin.userId || undefined); + let timeout: ReturnType; + export let jellyfinUsers: Promise | undefined = undefined; + + let stale = false; let error = ''; - let jellyfinUsers: Promise | undefined = undefined; - export let jellyfinUser: JellyfinUser | undefined; $: { - if ($originalBaseUrl !== baseUrl && $originalApiKey !== apiKey) handleChange(); - else dispatch('change', { baseUrl, apiKey, stale: false }); + jellyfinUser; + $originalBaseUrl; + $originalApiKey; + $originalUserId; + stale = getIsStale(); } - $: if (jellyfinUser) - dispatch('change', { - baseUrl, - apiKey, - stale: - baseUrl && apiKey ? jellyfinUser?.Id !== get(user)?.settings.jellyfin.userId : !jellyfinUser - }); - handleChange(); + function getIsStale() { + return ( + (!!jellyfinUser?.Id || (!baseUrl && !apiKey && !jellyfinUser)) && + ($originalBaseUrl !== baseUrl || + $originalApiKey !== apiKey || + $originalUserId !== jellyfinUser?.Id) + ); + } + function handleChange() { clearTimeout(timeout); + stale = false; error = ''; jellyfinUsers = undefined; jellyfinUser = undefined; + if (baseUrl === '' || apiKey === '') { + stale = getIsStale(); + return; + } + const baseUrlCopy = baseUrl; const apiKeyCopy = apiKey; - - dispatch('change', { - baseUrl: '', - apiKey: '', - stale: - baseUrl === '' && - apiKey === '' && - jellyfinUser === undefined && - (baseUrl !== $originalBaseUrl || apiKey !== $originalApiKey) - }); - - if (baseUrlCopy === '' || apiKeyCopy === '') return; - timeout = setTimeout(async () => { jellyfinUsers = jellyfinApi.getJellyfinUsers(baseUrl, apiKey); @@ -64,28 +68,38 @@ if (users.length) { jellyfinUser = users.find((u) => u.Id === get(user)?.settings.jellyfin.userId); - const stale = - (baseUrlCopy !== $originalBaseUrl || apiKeyCopy !== $originalApiKey) && - jellyfinUser !== undefined; - dispatch('change', { baseUrl: baseUrlCopy, apiKey: apiKeyCopy, stale }); + // stale = !!jellyfinUser?.Id && getIsStale(); } else { error = 'Could not connect'; + stale = false; } - - // if (res.status !== 200) { - // error = - // res.status === 404 - // ? 'Server not found' - // : res.status === 401 - // ? 'Invalid api key' - // : 'Could not connect'; - // - // return; // TODO add notification - // } else { - // dispatch('change', { baseUrl: baseUrlCopy, apiKey: apiKeyCopy, stale }); - // } }, 1000); } + + const setJellyfinUser = (u: JellyfinUser) => (jellyfinUser = u); + + async function handleSave() { + if (!stale) return; + + return user.updateUser((prev) => ({ + ...prev, + settings: { + ...prev.settings, + jellyfin: { + ...prev.settings.jellyfin, + baseUrl, + apiKey, + userId: jellyfinUser?.Id || '' + } + } + })); + } + + $: empty = !baseUrl && !apiKey && !jellyfinUser; + $: unchanged = + $originalBaseUrl === baseUrl && + $originalApiKey === apiKey && + $originalUserId === jellyfinUser?.Id;
@@ -109,7 +123,8 @@ {#if users?.length} dispatch('click-user', { user: jellyfinUser, users })} + on:clickOrSelect={() => + dispatch('click-user', { user: jellyfinUser, users, setJellyfinUser })} class="mb-4" > User @@ -120,3 +135,5 @@ {#if error}
{error}
{/if} + + diff --git a/src/lib/components/Integrations/RadarrIntegration.svelte b/src/lib/components/Integrations/RadarrIntegration.svelte index ddaaf34..079a79e 100644 --- a/src/lib/components/Integrations/RadarrIntegration.svelte +++ b/src/lib/components/Integrations/RadarrIntegration.svelte @@ -5,48 +5,51 @@ import { user } from '../../stores/user.store'; import { derived, get } from 'svelte/store'; - const dispatch = createEventDispatcher<{ - change: { baseUrl: string; apiKey: string; stale: boolean }; - }>(); - - export let baseUrl = get(user)?.settings.radarr.baseUrl || ''; - export let apiKey = get(user)?.settings.radarr.apiKey || ''; + let baseUrl = get(user)?.settings.radarr.baseUrl || ''; + let apiKey = get(user)?.settings.radarr.apiKey || ''; const originalBaseUrl = derived(user, (user) => user?.settings.radarr.baseUrl || ''); const originalApiKey = derived(user, (user) => user?.settings.radarr.apiKey || ''); - let timeout: ReturnType; + + let stale = false; let error = ''; + let timeout: ReturnType; let healthCheck: Promise | undefined; $: { - if ($originalBaseUrl !== baseUrl && $originalApiKey !== apiKey) handleChange(); - else dispatch('change', { baseUrl, apiKey, stale: false }); + $originalBaseUrl; + $originalApiKey; + stale = getIsStale(); } handleChange(); + function getIsStale() { + return ( + (!!healthCheck || (!baseUrl && !apiKey)) && + ($originalBaseUrl !== baseUrl || $originalApiKey !== apiKey) + ); + } + function handleChange() { clearTimeout(timeout); + stale = false; error = ''; healthCheck = undefined; + if (baseUrl === '' || apiKey === '') { + stale = getIsStale(); + return; + } + const baseUrlCopy = baseUrl; const apiKeyCopy = apiKey; - const stale = baseUrlCopy !== $originalBaseUrl || apiKeyCopy !== $originalApiKey; - - dispatch('change', { - baseUrl: '', - apiKey: '', - stale: baseUrl === '' && apiKey === '' && stale - }); - - if (baseUrlCopy === '' || apiKeyCopy === '') return; - timeout = setTimeout(async () => { const p = radarrApi.getHealth(baseUrlCopy, apiKeyCopy); healthCheck = p.then((res) => res.status === 200); const res = await p; if (baseUrlCopy !== baseUrl || apiKeyCopy !== apiKey) return; + if (res.status !== 200) { error = res.status === 404 @@ -55,12 +58,29 @@ ? 'Invalid api key' : 'Could not connect'; - return; // TODO add notification + stale = false; // TODO add notification } else { - dispatch('change', { baseUrl: baseUrlCopy, apiKey: apiKeyCopy, stale }); + stale = getIsStale(); } }, 1000); } + + async function handleSave() { + return user.updateUser((prev) => ({ + ...prev, + settings: { + ...prev.settings, + radarr: { + ...prev.settings.radarr, + baseUrl, + apiKey + } + } + })); + } + + $: empty = !baseUrl && !apiKey; + $: unchanged = baseUrl === $originalBaseUrl && apiKey === $originalApiKey;
@@ -73,3 +93,5 @@ {#if error}
{error}
{/if} + + diff --git a/src/lib/components/Integrations/SonarrIntegration.svelte b/src/lib/components/Integrations/SonarrIntegration.svelte index 0509ff8..446e587 100644 --- a/src/lib/components/Integrations/SonarrIntegration.svelte +++ b/src/lib/components/Integrations/SonarrIntegration.svelte @@ -5,50 +5,51 @@ import { user } from '../../stores/user.store'; import { derived, get } from 'svelte/store'; - const dispatch = createEventDispatcher<{ - change: { baseUrl: string; apiKey: string; stale: boolean }; - }>(); - - export let baseUrl = get(user)?.settings.sonarr.baseUrl || ''; - export let apiKey = get(user)?.settings.sonarr.apiKey || ''; + let baseUrl = get(user)?.settings.sonarr.baseUrl || ''; + let apiKey = get(user)?.settings.sonarr.apiKey || ''; const originalBaseUrl = derived(user, (u) => u?.settings.sonarr.baseUrl || ''); const originalApiKey = derived(user, (u) => u?.settings.sonarr.apiKey || ''); - let timeout: ReturnType; + + let stale = false; let error = ''; + let timeout: ReturnType; let healthCheck: Promise | undefined; $: { - if ($originalBaseUrl !== baseUrl && $originalApiKey !== apiKey) handleChange(); - else dispatch('change', { baseUrl, apiKey, stale: false }); + $originalBaseUrl; + $originalApiKey; + stale = getIsStale(); } handleChange(); - function handleChange() { - console.log('handleChange', $originalBaseUrl, baseUrl, $originalApiKey, apiKey); + function getIsStale() { + return ( + (!!healthCheck || (!baseUrl && !apiKey)) && + ($originalBaseUrl !== baseUrl || $originalApiKey !== apiKey) + ); + } + function handleChange() { clearTimeout(timeout); + stale = false; error = ''; healthCheck = undefined; + if (baseUrl === '' || apiKey === '') { + stale = getIsStale(); + return; + } + const baseUrlCopy = baseUrl; const apiKeyCopy = apiKey; - const stale = baseUrlCopy !== $originalBaseUrl || apiKeyCopy !== $originalApiKey; - - dispatch('change', { - baseUrl: '', - apiKey: '', - stale: baseUrl === '' && apiKey === '' && stale - }); - - if (baseUrlCopy === '' || apiKeyCopy === '') return; - timeout = setTimeout(async () => { const p = sonarrApi.getHealth(baseUrlCopy, apiKeyCopy); healthCheck = p.then((res) => res.status === 200); const res = await p; if (baseUrlCopy !== baseUrl || apiKeyCopy !== apiKey) return; + if (res.status !== 200) { error = res.status === 404 @@ -57,12 +58,29 @@ ? 'Invalid api key' : 'Could not connect'; - return; // TODO add notification + stale = false; // TODO add notification } else { - dispatch('change', { baseUrl: baseUrlCopy, apiKey: apiKeyCopy, stale }); + stale = getIsStale(); } }, 1000); } + + async function handleSave() { + return user.updateUser((prev) => ({ + ...prev, + settings: { + ...prev.settings, + sonarr: { + ...prev.settings.sonarr, + baseUrl, + apiKey + } + } + })); + } + + $: empty = !baseUrl && !apiKey; + $: unchanged = baseUrl === $originalBaseUrl && apiKey === $originalApiKey;
@@ -75,3 +93,5 @@ {#if error}
{error}
{/if} + + diff --git a/src/lib/components/Integrations/TmdbIntegration.svelte b/src/lib/components/Integrations/TmdbIntegration.svelte index fa5f292..c040c2d 100644 --- a/src/lib/components/Integrations/TmdbIntegration.svelte +++ b/src/lib/components/Integrations/TmdbIntegration.svelte @@ -1,76 +1,48 @@ -
- Base Url - API Key -
- -{#if error} -
{error}
-{/if} +{#await connectedTmdbAccount then tmdbAccount} + {#if tmdbAccount} + + Connected to + + + {:else} + +
+ +
+
+ {/if} +{/await} diff --git a/src/lib/components/Integrations/TmdbIntegrationConnect.svelte b/src/lib/components/Integrations/TmdbIntegrationConnect.svelte index a07fada..0e41e63 100644 --- a/src/lib/components/Integrations/TmdbIntegrationConnect.svelte +++ b/src/lib/components/Integrations/TmdbIntegrationConnect.svelte @@ -2,7 +2,7 @@ import Container from '../../../Container.svelte'; import { tmdbApi } from '../../apis/tmdb/tmdb-api'; import Button from '../Button.svelte'; - import { createEventDispatcher } from 'svelte'; + import { createEventDispatcher, onMount } from 'svelte'; import { ExternalLink } from 'radix-icons-svelte'; import { user } from '../../stores/user.store'; @@ -13,6 +13,8 @@ let tmdbConnectQrCode: string | undefined = undefined; let tmdbError: string = ''; + handleGenerateTMDBLink(); + async function handleGenerateTMDBLink() { return tmdbApi.getConnectAccountLink().then((res) => { if (res?.status_code !== 1) return; // TODO add notification @@ -66,9 +68,9 @@ {/if} - {#if !tmdbConnectRequestToken} - - {:else if tmdbConnectLink} + + + {#if tmdbConnectLink} -
+ + +

Radarr

- { - radarrBaseUrl = detail.baseUrl; - radarrApiKey = detail.apiKey; - radarrStale = detail.stale; - }} - /> -
- -
+ + +
@@ -334,33 +246,37 @@ on:enter={scrollIntoView({ vertical: 64 })} >

Tmdb Account

- {#await tmdbAccount then tmdbAccount} - {#if tmdbAccount} - - Connected to - - - {:else} -
- -
- {/if} - {/await} + createModal(TmdbIntegrationConnectDialog, {})} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Jellyfin

{ - jellyfinBaseUrl = detail.baseUrl; - jellyfinApiKey = detail.apiKey; - jellyfinStale = detail.stale; - }} on:click-user={({ detail }) => createModal(JellyfinIntegrationUsersDialog, { selectedUser: detail.user, users: detail.users, - handleSelectUser: (u) => (jellyfinUser = u) + handleSelectUser: detail.setJellyfinUser })} - /> -
- -
+ let:handleSave + let:stale + > + +
diff --git a/src/lib/pages/OnboardingPage.svelte b/src/lib/pages/OnboardingPage.svelte index 4a9ffea..83aaf10 100644 --- a/src/lib/pages/OnboardingPage.svelte +++ b/src/lib/pages/OnboardingPage.svelte @@ -15,6 +15,11 @@ import { user } from '../stores/user.store'; import { sessions } from '../stores/session.store'; import Panel from '../components/Panel.svelte'; + import TmdbIntegrationConnect from '../components/Integrations/TmdbIntegrationConnect.svelte'; + import JellyfinIntegration from '../components/Integrations/JellyfinIntegration.svelte'; + import SonarrIntegration from '../components/Integrations/SonarrIntegration.svelte'; + import RadarrIntegration from '../components/Integrations/RadarrIntegration.svelte'; + import TmdbIntegration from '../components/Integrations/TmdbIntegration.svelte'; enum Tabs { Welcome, @@ -30,190 +35,10 @@ const tab = useTabs(Tabs.Welcome, { ['class']: 'w-max max-w-lg' }); - let tmdbConnectRequestToken: string | undefined = undefined; - let tmdbConnectLink: string | undefined = undefined; - let tmdbConnectQrCode: string | undefined = undefined; $: connectedTmdbAccount = $user?.settings.tmdb.userId && tmdbApi.getAccountDetails(); - let tmdbError: string = ''; - let jellyfinBaseUrl: string = ''; - let jellyfinApiKey: string = ''; let jellyfinUser: JellyfinUser | undefined = undefined; let jellyfinUsers: Promise = Promise.resolve([]); - let jellyfinConnectionCheckTimeout: ReturnType | undefined = undefined; - let jellyfinError: string = ''; - - let sonarrBaseUrl: string = ''; - let sonarrApiKey: string = ''; - let sonarrError: string = ''; - - let radarrBaseUrl: string = ''; - let radarrApiKey: string = ''; - let radarrError: string = ''; - - user.subscribe((user) => { - jellyfinBaseUrl = jellyfinBaseUrl || user?.settings.jellyfin.baseUrl || ''; - jellyfinApiKey = jellyfinApiKey || user?.settings.jellyfin.apiKey || ''; - - sonarrBaseUrl = sonarrBaseUrl || user?.settings.sonarr.baseUrl || ''; - sonarrApiKey = sonarrApiKey || user?.settings.sonarr.apiKey || ''; - - radarrBaseUrl = radarrBaseUrl || user?.settings.radarr.baseUrl || ''; - radarrApiKey = radarrApiKey || user?.settings.radarr.apiKey || ''; - - // if ( - // !jellyfinUser && - // appState.user?.settings.jellyfin.userId && - // jellyfinBaseUrl && - // jellyfinApiKey - // ) { - // jellyfinUsers = jellyfinApi.getJellyfinUsers(jellyfinBaseUrl, jellyfinApiKey); - // jellyfinUsers.then( - // (users) => - // (jellyfinUser = users.find((u) => u.Id === appState.user?.settings.jellyfin.userId)) - // ); - // } - }); - - $: if (jellyfinBaseUrl && jellyfinApiKey) { - clearTimeout(jellyfinConnectionCheckTimeout); - - const baseUrlCopy = jellyfinBaseUrl; - const apiKeyCopy = jellyfinApiKey; - jellyfinUser = undefined; - - jellyfinConnectionCheckTimeout = setTimeout(async () => { - jellyfinUsers = jellyfinApi - .getJellyfinUsers(jellyfinBaseUrl, jellyfinApiKey) - .then((users) => { - if (baseUrlCopy === jellyfinBaseUrl && apiKeyCopy === jellyfinApiKey) { - jellyfinUser = users.find((u) => u.Id === $user?.settings.jellyfin.userId); - jellyfinError = users.length ? '' : 'Could not connect'; - } - // console.log(users, baseUrlCopy, jellyfinBaseUrl, apiKeyCopy, jellyfinApiKey); - // jellyfinUsers = users; - // return !!users?.length; - return users; - }); - }, 500); - } - - async function handleGenerateTMDBLink() { - return tmdbApi.getConnectAccountLink().then((res) => { - if (res?.status_code !== 1) return; // TODO add notification - const link = `https://www.themoviedb.org/auth/access?request_token=${res?.request_token}`; - const qrCode = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${link}`; - tmdbConnectRequestToken = res?.request_token; - tmdbConnectLink = link; - tmdbConnectQrCode = qrCode; - }); - } - - async function completeTMDBConnect() { - if (!tmdbConnectRequestToken) return; - tmdbApi.getAccountAccessToken(tmdbConnectRequestToken).then((res) => { - const { status_code, access_token, account_id } = res || {}; - if (status_code !== 1 || !access_token || !account_id) return; // TODO add notification - - user.updateUser((prev) => ({ - ...prev, - settings: { - ...prev.settings, - tmdb: { - userId: account_id, - sessionId: access_token - } - } - })); - - tab.set(Tabs.Jellyfin); - }); - } - - async function handleConnectJellyfin() { - const userId = jellyfinUser?.Id; - const baseUrl = jellyfinBaseUrl; - const apiKey = jellyfinApiKey; - if (!userId || !baseUrl || !apiKey) return; - - await user.updateUser((prev) => ({ - ...prev, - settings: { - ...prev.settings, - jellyfin: { - ...prev.settings.jellyfin, - userId, - baseUrl, - apiKey - } - } - })); - - tab.next(); - } - - async function handleConnectSonarr() { - const baseUrl = sonarrBaseUrl; - const apiKey = sonarrApiKey; - if (!baseUrl || !apiKey) return; - const res = await sonarrApi.getHealth(baseUrl, apiKey); - - if (res.status !== 200) { - sonarrError = - res.status === 404 - ? 'Server not found' - : res.status === 401 - ? 'Invalid api key' - : 'Could not connect'; - - return; // TODO add notification - } - - await user.updateUser((prev) => ({ - ...prev, - settings: { - ...prev.settings, - sonarr: { - ...prev.settings.sonarr, - baseUrl, - apiKey - } - } - })); - - tab.next(); - } - - async function handleConnectRadarr() { - const baseUrl = radarrBaseUrl; - const apiKey = radarrApiKey; - if (!baseUrl || !apiKey) return; - const res = await radarrApi.getHealth(baseUrl, apiKey); - - if (res.status !== 200) { - res.status === 404 - ? 'Server not found' - : res.status === 401 - ? 'Invalid api key' - : 'Could not connect'; - - return; // TODO add notification - } - - await user.updateUser((prev) => ({ - ...prev, - settings: { - ...prev.settings, - radarr: { - ...prev.settings.radarr, - baseUrl, - apiKey - } - } - })); - - await finalizeSetup(); - } async function finalizeSetup() { await user.updateUser((prev) => ({ @@ -225,9 +50,6 @@ function handleBack() { tab.previous(); } - - const tabContainer = - 'col-start-1 col-end-1 row-start-1 row-end-1 flex flex-col bg-primary-800 rounded-2xl p-10 shadow-xl max-w-lg'; @@ -260,40 +82,52 @@ preferences.
-
- {#await connectedTmdbAccount then account} - {#if account} - { - tab.set(Tabs.TmdbConnect); - handleGenerateTMDBLink(); - }}>Logged in as - {:else} + tab.set(Tabs.TmdbConnect)}> + + {#if !$user?.settings.tmdb.userId} {/if} - {/await} + + + - -
+ + + + + + + + + + + + + + + + + + + + + + + -

Connect a TMDB Account

-
- To connect your TMDB account, log in via the link below and then click "Complete - Connection". -
- - {#if tmdbConnectQrCode} -
- {/if} - - - {#if !tmdbConnectRequestToken} - - {:else if tmdbConnectLink} - - - {/if} - + tab.set(Tabs.Jellyfin)} />

Connect to Jellyfin

Connect to Jellyfin to watch movies and tv shows.
-
- !!u?.length)}> - Base Url - - !!u?.length)}> - API Key - -
+ tab.set(Tabs.SelectUser)} + let:handleSave + let:stale + let:empty + let:unchanged + > + + + {#if empty || unchanged} + + {:else} + + {/if} + + - {#await jellyfinUsers then users} - {#if users.length} - tab.set(Tabs.SelectUser)} - class="mb-4" - > - User - - {/if} - {/await} + + + + + + + + - {#if jellyfinError} -
{jellyfinError}
- {/if} + + + + + + + + + + + - - - {#if jellyfinBaseUrl && jellyfinApiKey && jellyfinUser} - - {:else} - - {/if} - + + +
Select User
{#await jellyfinUsers then users} - {#each users as user} + {#each users || [] as user} { @@ -397,46 +226,48 @@

Connect to Sonarr

Connect to Sonarr for requesting and managing tv shows.
-
- Base Url - API Key -
- - {#if sonarrError} -
{sonarrError}
- {/if} - - - - {#if sonarrBaseUrl && sonarrApiKey} - - {:else} - - {/if} - + + + + {#if empty || unchanged} + + {:else} + + {/if} + +

Connect to Radarr

Connect to Radarr for requesting and managing movies.
-
- Base Url - API Key -
- - {#if radarrError} -
{radarrError}
- {/if} - - - - {#if radarrBaseUrl && radarrApiKey} - - {:else} - - {/if} - + + + + {#if empty || unchanged} + + {:else} + + {/if} + +