fix: And refactor onboarding integration components
This commit is contained in:
@@ -506,9 +506,9 @@ export class JellyfinApi implements Api<paths> {
|
|||||||
apiKey: string | undefined = undefined
|
apiKey: string | undefined = undefined
|
||||||
): Promise<JellyfinUser[]> =>
|
): Promise<JellyfinUser[]> =>
|
||||||
axios
|
axios
|
||||||
.get((baseUrl || this.getBaseUrl()) + '/Users', {
|
.get((baseUrl ?? this.getBaseUrl()) + '/Users', {
|
||||||
headers: {
|
headers: {
|
||||||
'X-Emby-Token': apiKey || this.getApiKey()
|
'X-Emby-Token': apiKey ?? this.getApiKey()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((res) => res.data || [])
|
.then((res) => res.data || [])
|
||||||
|
|||||||
@@ -249,9 +249,9 @@ export class RadarrApi implements Api<paths> {
|
|||||||
apiKey: string | undefined = undefined
|
apiKey: string | undefined = undefined
|
||||||
) =>
|
) =>
|
||||||
axios
|
axios
|
||||||
.get((baseUrl || this.getBaseUrl()) + '/api/v3/health', {
|
.get((baseUrl ?? this.getBaseUrl()) + '/api/v3/health', {
|
||||||
headers: {
|
headers: {
|
||||||
'X-Api-Key': apiKey || this.getSettings()?.apiKey
|
'X-Api-Key': apiKey ?? this.getSettings()?.apiKey
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => e.response);
|
.catch((e) => e.response);
|
||||||
|
|||||||
@@ -397,9 +397,9 @@ export class SonarrApi implements ApiAsync<paths> {
|
|||||||
apiKey: string | undefined = undefined
|
apiKey: string | undefined = undefined
|
||||||
) =>
|
) =>
|
||||||
axios
|
axios
|
||||||
.get((baseUrl || this.getBaseUrl()) + '/api/v3/health', {
|
.get((baseUrl ?? this.getBaseUrl()) + '/api/v3/health', {
|
||||||
headers: {
|
headers: {
|
||||||
'X-Api-Key': apiKey || this.getApiKey()
|
'X-Api-Key': apiKey ?? this.getApiKey()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => e.response);
|
.catch((e) => e.response);
|
||||||
|
|||||||
@@ -7,55 +7,59 @@
|
|||||||
import { derived, get } from 'svelte/store';
|
import { derived, get } from 'svelte/store';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
change: { baseUrl: string; apiKey: string; stale: boolean };
|
'click-user': {
|
||||||
'click-user': { user: JellyfinUser | undefined; users: JellyfinUser[] };
|
user: JellyfinUser | undefined;
|
||||||
|
users: JellyfinUser[];
|
||||||
|
setJellyfinUser: typeof setJellyfinUser;
|
||||||
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
export let baseUrl = get(user)?.settings.jellyfin.baseUrl || '';
|
let baseUrl = get(user)?.settings.jellyfin.baseUrl || '';
|
||||||
export let apiKey = get(user)?.settings.jellyfin.apiKey || '';
|
let apiKey = get(user)?.settings.jellyfin.apiKey || '';
|
||||||
|
export let jellyfinUser: JellyfinUser | undefined = undefined;
|
||||||
const originalBaseUrl = derived(user, (user) => user?.settings.jellyfin.baseUrl || '');
|
const originalBaseUrl = derived(user, (user) => user?.settings.jellyfin.baseUrl || '');
|
||||||
const originalApiKey = derived(user, (user) => user?.settings.jellyfin.apiKey || '');
|
const originalApiKey = derived(user, (user) => user?.settings.jellyfin.apiKey || '');
|
||||||
|
const originalUserId = derived(user, (user) => user?.settings.jellyfin.userId || undefined);
|
||||||
|
|
||||||
let timeout: ReturnType<typeof setTimeout>;
|
let timeout: ReturnType<typeof setTimeout>;
|
||||||
|
export let jellyfinUsers: Promise<JellyfinUser[]> | undefined = undefined;
|
||||||
|
|
||||||
|
let stale = false;
|
||||||
let error = '';
|
let error = '';
|
||||||
let jellyfinUsers: Promise<JellyfinUser[]> | undefined = undefined;
|
|
||||||
export let jellyfinUser: JellyfinUser | undefined;
|
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($originalBaseUrl !== baseUrl && $originalApiKey !== apiKey) handleChange();
|
jellyfinUser;
|
||||||
else dispatch('change', { baseUrl, apiKey, stale: false });
|
$originalBaseUrl;
|
||||||
|
$originalApiKey;
|
||||||
|
$originalUserId;
|
||||||
|
stale = getIsStale();
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (jellyfinUser)
|
|
||||||
dispatch('change', {
|
|
||||||
baseUrl,
|
|
||||||
apiKey,
|
|
||||||
stale:
|
|
||||||
baseUrl && apiKey ? jellyfinUser?.Id !== get(user)?.settings.jellyfin.userId : !jellyfinUser
|
|
||||||
});
|
|
||||||
|
|
||||||
handleChange();
|
handleChange();
|
||||||
|
|
||||||
|
function getIsStale() {
|
||||||
|
return (
|
||||||
|
(!!jellyfinUser?.Id || (!baseUrl && !apiKey && !jellyfinUser)) &&
|
||||||
|
($originalBaseUrl !== baseUrl ||
|
||||||
|
$originalApiKey !== apiKey ||
|
||||||
|
$originalUserId !== jellyfinUser?.Id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function handleChange() {
|
function handleChange() {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
stale = false;
|
||||||
error = '';
|
error = '';
|
||||||
jellyfinUsers = undefined;
|
jellyfinUsers = undefined;
|
||||||
jellyfinUser = undefined;
|
jellyfinUser = undefined;
|
||||||
|
|
||||||
|
if (baseUrl === '' || apiKey === '') {
|
||||||
|
stale = getIsStale();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const baseUrlCopy = baseUrl;
|
const baseUrlCopy = baseUrl;
|
||||||
const apiKeyCopy = apiKey;
|
const apiKeyCopy = apiKey;
|
||||||
|
|
||||||
dispatch('change', {
|
|
||||||
baseUrl: '',
|
|
||||||
apiKey: '',
|
|
||||||
stale:
|
|
||||||
baseUrl === '' &&
|
|
||||||
apiKey === '' &&
|
|
||||||
jellyfinUser === undefined &&
|
|
||||||
(baseUrl !== $originalBaseUrl || apiKey !== $originalApiKey)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (baseUrlCopy === '' || apiKeyCopy === '') return;
|
|
||||||
|
|
||||||
timeout = setTimeout(async () => {
|
timeout = setTimeout(async () => {
|
||||||
jellyfinUsers = jellyfinApi.getJellyfinUsers(baseUrl, apiKey);
|
jellyfinUsers = jellyfinApi.getJellyfinUsers(baseUrl, apiKey);
|
||||||
|
|
||||||
@@ -64,28 +68,38 @@
|
|||||||
|
|
||||||
if (users.length) {
|
if (users.length) {
|
||||||
jellyfinUser = users.find((u) => u.Id === get(user)?.settings.jellyfin.userId);
|
jellyfinUser = users.find((u) => u.Id === get(user)?.settings.jellyfin.userId);
|
||||||
const stale =
|
// stale = !!jellyfinUser?.Id && getIsStale();
|
||||||
(baseUrlCopy !== $originalBaseUrl || apiKeyCopy !== $originalApiKey) &&
|
|
||||||
jellyfinUser !== undefined;
|
|
||||||
dispatch('change', { baseUrl: baseUrlCopy, apiKey: apiKeyCopy, stale });
|
|
||||||
} else {
|
} else {
|
||||||
error = 'Could not connect';
|
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);
|
}, 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;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="space-y-4 mb-4">
|
<div class="space-y-4 mb-4">
|
||||||
@@ -109,7 +123,8 @@
|
|||||||
{#if users?.length}
|
{#if users?.length}
|
||||||
<SelectField
|
<SelectField
|
||||||
value={jellyfinUser?.Name || 'Select User'}
|
value={jellyfinUser?.Name || 'Select User'}
|
||||||
on:clickOrSelect={() => dispatch('click-user', { user: jellyfinUser, users })}
|
on:clickOrSelect={() =>
|
||||||
|
dispatch('click-user', { user: jellyfinUser, users, setJellyfinUser })}
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
>
|
>
|
||||||
User
|
User
|
||||||
@@ -120,3 +135,5 @@
|
|||||||
{#if error}
|
{#if error}
|
||||||
<div class="text-red-500 mb-4">{error}</div>
|
<div class="text-red-500 mb-4">{error}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<slot {handleSave} {stale} {empty} {unchanged} />
|
||||||
|
|||||||
@@ -5,48 +5,51 @@
|
|||||||
import { user } from '../../stores/user.store';
|
import { user } from '../../stores/user.store';
|
||||||
import { derived, get } from 'svelte/store';
|
import { derived, get } from 'svelte/store';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
let baseUrl = get(user)?.settings.radarr.baseUrl || '';
|
||||||
change: { baseUrl: string; apiKey: string; stale: boolean };
|
let apiKey = get(user)?.settings.radarr.apiKey || '';
|
||||||
}>();
|
|
||||||
|
|
||||||
export let baseUrl = get(user)?.settings.radarr.baseUrl || '';
|
|
||||||
export let apiKey = get(user)?.settings.radarr.apiKey || '';
|
|
||||||
const originalBaseUrl = derived(user, (user) => user?.settings.radarr.baseUrl || '');
|
const originalBaseUrl = derived(user, (user) => user?.settings.radarr.baseUrl || '');
|
||||||
const originalApiKey = derived(user, (user) => user?.settings.radarr.apiKey || '');
|
const originalApiKey = derived(user, (user) => user?.settings.radarr.apiKey || '');
|
||||||
let timeout: ReturnType<typeof setTimeout>;
|
|
||||||
|
let stale = false;
|
||||||
let error = '';
|
let error = '';
|
||||||
|
let timeout: ReturnType<typeof setTimeout>;
|
||||||
let healthCheck: Promise<boolean> | undefined;
|
let healthCheck: Promise<boolean> | undefined;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($originalBaseUrl !== baseUrl && $originalApiKey !== apiKey) handleChange();
|
$originalBaseUrl;
|
||||||
else dispatch('change', { baseUrl, apiKey, stale: false });
|
$originalApiKey;
|
||||||
|
stale = getIsStale();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange();
|
handleChange();
|
||||||
|
|
||||||
|
function getIsStale() {
|
||||||
|
return (
|
||||||
|
(!!healthCheck || (!baseUrl && !apiKey)) &&
|
||||||
|
($originalBaseUrl !== baseUrl || $originalApiKey !== apiKey)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function handleChange() {
|
function handleChange() {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
stale = false;
|
||||||
error = '';
|
error = '';
|
||||||
healthCheck = undefined;
|
healthCheck = undefined;
|
||||||
|
|
||||||
|
if (baseUrl === '' || apiKey === '') {
|
||||||
|
stale = getIsStale();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const baseUrlCopy = baseUrl;
|
const baseUrlCopy = baseUrl;
|
||||||
const apiKeyCopy = apiKey;
|
const apiKeyCopy = apiKey;
|
||||||
const stale = baseUrlCopy !== $originalBaseUrl || apiKeyCopy !== $originalApiKey;
|
|
||||||
|
|
||||||
dispatch('change', {
|
|
||||||
baseUrl: '',
|
|
||||||
apiKey: '',
|
|
||||||
stale: baseUrl === '' && apiKey === '' && stale
|
|
||||||
});
|
|
||||||
|
|
||||||
if (baseUrlCopy === '' || apiKeyCopy === '') return;
|
|
||||||
|
|
||||||
timeout = setTimeout(async () => {
|
timeout = setTimeout(async () => {
|
||||||
const p = radarrApi.getHealth(baseUrlCopy, apiKeyCopy);
|
const p = radarrApi.getHealth(baseUrlCopy, apiKeyCopy);
|
||||||
healthCheck = p.then((res) => res.status === 200);
|
healthCheck = p.then((res) => res.status === 200);
|
||||||
|
|
||||||
const res = await p;
|
const res = await p;
|
||||||
if (baseUrlCopy !== baseUrl || apiKeyCopy !== apiKey) return;
|
if (baseUrlCopy !== baseUrl || apiKeyCopy !== apiKey) return;
|
||||||
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
error =
|
error =
|
||||||
res.status === 404
|
res.status === 404
|
||||||
@@ -55,12 +58,29 @@
|
|||||||
? 'Invalid api key'
|
? 'Invalid api key'
|
||||||
: 'Could not connect';
|
: 'Could not connect';
|
||||||
|
|
||||||
return; // TODO add notification
|
stale = false; // TODO add notification
|
||||||
} else {
|
} else {
|
||||||
dispatch('change', { baseUrl: baseUrlCopy, apiKey: apiKeyCopy, stale });
|
stale = getIsStale();
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 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;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="space-y-4 mb-4">
|
<div class="space-y-4 mb-4">
|
||||||
@@ -73,3 +93,5 @@
|
|||||||
{#if error}
|
{#if error}
|
||||||
<div class="text-red-500 mb-4">{error}</div>
|
<div class="text-red-500 mb-4">{error}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<slot {handleSave} {stale} {empty} {unchanged} />
|
||||||
|
|||||||
@@ -5,50 +5,51 @@
|
|||||||
import { user } from '../../stores/user.store';
|
import { user } from '../../stores/user.store';
|
||||||
import { derived, get } from 'svelte/store';
|
import { derived, get } from 'svelte/store';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
let baseUrl = get(user)?.settings.sonarr.baseUrl || '';
|
||||||
change: { baseUrl: string; apiKey: string; stale: boolean };
|
let apiKey = get(user)?.settings.sonarr.apiKey || '';
|
||||||
}>();
|
|
||||||
|
|
||||||
export let baseUrl = get(user)?.settings.sonarr.baseUrl || '';
|
|
||||||
export let apiKey = get(user)?.settings.sonarr.apiKey || '';
|
|
||||||
const originalBaseUrl = derived(user, (u) => u?.settings.sonarr.baseUrl || '');
|
const originalBaseUrl = derived(user, (u) => u?.settings.sonarr.baseUrl || '');
|
||||||
const originalApiKey = derived(user, (u) => u?.settings.sonarr.apiKey || '');
|
const originalApiKey = derived(user, (u) => u?.settings.sonarr.apiKey || '');
|
||||||
let timeout: ReturnType<typeof setTimeout>;
|
|
||||||
|
let stale = false;
|
||||||
let error = '';
|
let error = '';
|
||||||
|
let timeout: ReturnType<typeof setTimeout>;
|
||||||
let healthCheck: Promise<boolean> | undefined;
|
let healthCheck: Promise<boolean> | undefined;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($originalBaseUrl !== baseUrl && $originalApiKey !== apiKey) handleChange();
|
$originalBaseUrl;
|
||||||
else dispatch('change', { baseUrl, apiKey, stale: false });
|
$originalApiKey;
|
||||||
|
stale = getIsStale();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange();
|
handleChange();
|
||||||
|
|
||||||
function handleChange() {
|
function getIsStale() {
|
||||||
console.log('handleChange', $originalBaseUrl, baseUrl, $originalApiKey, apiKey);
|
return (
|
||||||
|
(!!healthCheck || (!baseUrl && !apiKey)) &&
|
||||||
|
($originalBaseUrl !== baseUrl || $originalApiKey !== apiKey)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChange() {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
stale = false;
|
||||||
error = '';
|
error = '';
|
||||||
healthCheck = undefined;
|
healthCheck = undefined;
|
||||||
|
|
||||||
|
if (baseUrl === '' || apiKey === '') {
|
||||||
|
stale = getIsStale();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const baseUrlCopy = baseUrl;
|
const baseUrlCopy = baseUrl;
|
||||||
const apiKeyCopy = apiKey;
|
const apiKeyCopy = apiKey;
|
||||||
const stale = baseUrlCopy !== $originalBaseUrl || apiKeyCopy !== $originalApiKey;
|
|
||||||
|
|
||||||
dispatch('change', {
|
|
||||||
baseUrl: '',
|
|
||||||
apiKey: '',
|
|
||||||
stale: baseUrl === '' && apiKey === '' && stale
|
|
||||||
});
|
|
||||||
|
|
||||||
if (baseUrlCopy === '' || apiKeyCopy === '') return;
|
|
||||||
|
|
||||||
timeout = setTimeout(async () => {
|
timeout = setTimeout(async () => {
|
||||||
const p = sonarrApi.getHealth(baseUrlCopy, apiKeyCopy);
|
const p = sonarrApi.getHealth(baseUrlCopy, apiKeyCopy);
|
||||||
healthCheck = p.then((res) => res.status === 200);
|
healthCheck = p.then((res) => res.status === 200);
|
||||||
|
|
||||||
const res = await p;
|
const res = await p;
|
||||||
if (baseUrlCopy !== baseUrl || apiKeyCopy !== apiKey) return;
|
if (baseUrlCopy !== baseUrl || apiKeyCopy !== apiKey) return;
|
||||||
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
error =
|
error =
|
||||||
res.status === 404
|
res.status === 404
|
||||||
@@ -57,12 +58,29 @@
|
|||||||
? 'Invalid api key'
|
? 'Invalid api key'
|
||||||
: 'Could not connect';
|
: 'Could not connect';
|
||||||
|
|
||||||
return; // TODO add notification
|
stale = false; // TODO add notification
|
||||||
} else {
|
} else {
|
||||||
dispatch('change', { baseUrl: baseUrlCopy, apiKey: apiKeyCopy, stale });
|
stale = getIsStale();
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 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;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="space-y-4 mb-4">
|
<div class="space-y-4 mb-4">
|
||||||
@@ -75,3 +93,5 @@
|
|||||||
{#if error}
|
{#if error}
|
||||||
<div class="text-red-500 mb-4">{error}</div>
|
<div class="text-red-500 mb-4">{error}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<slot {handleSave} {stale} {empty} {unchanged} />
|
||||||
|
|||||||
@@ -1,76 +1,48 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import TextField from '../TextField.svelte';
|
import { tmdbApi } from '../../apis/tmdb/tmdb-api';
|
||||||
import { sonarrApi } from '../../apis/sonarr/sonarr-api';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { user } from '../../stores/user.store';
|
import { user } from '../../stores/user.store';
|
||||||
|
import SelectField from '../SelectField.svelte';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import Button from '../Button.svelte';
|
||||||
|
import { ArrowRight, Trash } from 'radix-icons-svelte';
|
||||||
|
import { derived } from 'svelte/store';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
export let handleConnectTmdb: () => void;
|
||||||
change: { baseUrl: string; apiKey: string; stale: boolean };
|
|
||||||
}>();
|
|
||||||
|
|
||||||
export let baseUrl = '';
|
const dispatch = createEventDispatcher<{ 'click-user': null }>();
|
||||||
export let apiKey = '';
|
const userId = derived(user, (user) => user?.settings.tmdb.userId);
|
||||||
let originalBaseUrl: string | undefined;
|
|
||||||
let originalApiKey: string | undefined;
|
|
||||||
let timeout: ReturnType<typeof setTimeout>;
|
|
||||||
let error = '';
|
|
||||||
let healthCheck: Promise<boolean> | undefined;
|
|
||||||
|
|
||||||
user.subscribe((user) => {
|
$: connectedTmdbAccount = !!$userId && tmdbApi.getAccountDetails();
|
||||||
baseUrl = baseUrl || user?.settings.sonarr.baseUrl || '';
|
|
||||||
apiKey = apiKey || user?.settings.sonarr.apiKey || '';
|
|
||||||
|
|
||||||
originalBaseUrl = baseUrl;
|
async function handleDisconnectTmdb() {
|
||||||
originalApiKey = apiKey;
|
return user.updateUser((prev) => ({
|
||||||
|
...prev,
|
||||||
handleChange();
|
settings: {
|
||||||
});
|
...prev.settings,
|
||||||
|
tmdb: {
|
||||||
function handleChange() {
|
...prev.settings.tmdb,
|
||||||
clearTimeout(timeout);
|
userId: '',
|
||||||
error = '';
|
sessionId: ''
|
||||||
healthCheck = undefined;
|
}
|
||||||
|
|
||||||
const baseUrlCopy = baseUrl;
|
|
||||||
const apiKeyCopy = apiKey;
|
|
||||||
const stale = baseUrlCopy !== originalBaseUrl || apiKeyCopy !== originalApiKey;
|
|
||||||
|
|
||||||
dispatch('change', {
|
|
||||||
baseUrl: '',
|
|
||||||
apiKey: '',
|
|
||||||
stale: baseUrl === '' && apiKey === ''
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
|
||||||
? '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);
|
}));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="space-y-4 mb-4">
|
{#await connectedTmdbAccount then tmdbAccount}
|
||||||
<TextField bind:value={baseUrl} isValid={healthCheck} on:change={handleChange}>Base Url</TextField
|
{#if tmdbAccount}
|
||||||
>
|
<SelectField value={tmdbAccount.username || ''} action={handleDisconnectTmdb} class="mb-4">
|
||||||
<TextField bind:value={apiKey} isValid={healthCheck} on:change={handleChange}>API Key</TextField>
|
Connected to
|
||||||
</div>
|
<Trash slot="icon" let:size let:iconClass {size} class={classNames(iconClass, '')} />
|
||||||
|
</SelectField>
|
||||||
{#if error}
|
{:else}
|
||||||
<div class="text-red-500 mb-4">{error}</div>
|
<slot>
|
||||||
{/if}
|
<div class="flex space-x-4">
|
||||||
|
<Button type="primary-dark" iconAfter={ArrowRight} on:clickOrSelect={handleConnectTmdb}>
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import Container from '../../../Container.svelte';
|
import Container from '../../../Container.svelte';
|
||||||
import { tmdbApi } from '../../apis/tmdb/tmdb-api';
|
import { tmdbApi } from '../../apis/tmdb/tmdb-api';
|
||||||
import Button from '../Button.svelte';
|
import Button from '../Button.svelte';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import { ExternalLink } from 'radix-icons-svelte';
|
import { ExternalLink } from 'radix-icons-svelte';
|
||||||
import { user } from '../../stores/user.store';
|
import { user } from '../../stores/user.store';
|
||||||
|
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
let tmdbConnectQrCode: string | undefined = undefined;
|
let tmdbConnectQrCode: string | undefined = undefined;
|
||||||
let tmdbError: string = '';
|
let tmdbError: string = '';
|
||||||
|
|
||||||
|
handleGenerateTMDBLink();
|
||||||
|
|
||||||
async function handleGenerateTMDBLink() {
|
async function handleGenerateTMDBLink() {
|
||||||
return tmdbApi.getConnectAccountLink().then((res) => {
|
return tmdbApi.getConnectAccountLink().then((res) => {
|
||||||
if (res?.status_code !== 1) return; // TODO add notification
|
if (res?.status_code !== 1) return; // TODO add notification
|
||||||
@@ -66,9 +68,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Container direction="horizontal" class="flex space-x-4 *:flex-1">
|
<Container direction="horizontal" class="flex space-x-4 *:flex-1">
|
||||||
{#if !tmdbConnectRequestToken}
|
<!--{#if !tmdbConnectRequestToken}-->
|
||||||
<Button type="primary-dark" action={handleGenerateTMDBLink}>Generate Link</Button>
|
<!-- <Button type="primary-dark" action={handleGenerateTMDBLink}>Generate Link</Button>-->
|
||||||
{:else if tmdbConnectLink}
|
{#if tmdbConnectLink}
|
||||||
<Button type="primary-dark" action={completeTMDBConnect}>Complete Connection</Button>
|
<Button type="primary-dark" action={completeTMDBConnect}>Complete Connection</Button>
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => window.open(tmdbConnectLink)}>
|
<Button type="primary-dark" on:clickOrSelect={() => window.open(tmdbConnectLink)}>
|
||||||
Open Link
|
Open Link
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
import JellyfinIntegrationUsersDialog from '../components/Integrations/JellyfinIntegrationUsersDialog.svelte';
|
import JellyfinIntegrationUsersDialog from '../components/Integrations/JellyfinIntegrationUsersDialog.svelte';
|
||||||
import { tmdbApi } from '../apis/tmdb/tmdb-api';
|
import { tmdbApi } from '../apis/tmdb/tmdb-api';
|
||||||
import SelectField from '../components/SelectField.svelte';
|
import SelectField from '../components/SelectField.svelte';
|
||||||
import { ArrowRight, Exit, Pencil1, Pencil2, Plus, Trash } from 'radix-icons-svelte';
|
import { ArrowRight, Exit, Pencil2, Plus, Trash } from 'radix-icons-svelte';
|
||||||
import TmdbIntegrationConnectDialog from '../components/Integrations/TmdbIntegrationConnectDialog.svelte';
|
import TmdbIntegrationConnectDialog from '../components/Integrations/TmdbIntegrationConnectDialog.svelte';
|
||||||
import { createModal } from '../components/Modal/modal.store';
|
import { createModal } from '../components/Modal/modal.store';
|
||||||
import DetachedPage from '../components/DetachedPage/DetachedPage.svelte';
|
import DetachedPage from '../components/DetachedPage/DetachedPage.svelte';
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
import { sessions } from '../stores/session.store';
|
import { sessions } from '../stores/session.store';
|
||||||
import EditProfileModal from '../components/Dialog/CreateOrEditProfileModal.svelte';
|
import EditProfileModal from '../components/Dialog/CreateOrEditProfileModal.svelte';
|
||||||
import { scrollIntoView } from '../selectable';
|
import { scrollIntoView } from '../selectable';
|
||||||
import Panel from '../components/Panel.svelte';
|
|
||||||
import { reiverrApi } from '../apis/reiverr/reiverr-api';
|
import { reiverrApi } from '../apis/reiverr/reiverr-api';
|
||||||
|
import TmdbIntegration from '../components/Integrations/TmdbIntegration.svelte';
|
||||||
|
|
||||||
enum Tabs {
|
enum Tabs {
|
||||||
Interface,
|
Interface,
|
||||||
@@ -32,19 +32,6 @@
|
|||||||
|
|
||||||
const tab = useTabs(Tabs.Interface, { size: 'stretch' });
|
const tab = useTabs(Tabs.Interface, { size: 'stretch' });
|
||||||
|
|
||||||
let jellyfinBaseUrl = '';
|
|
||||||
let jellyfinApiKey = '';
|
|
||||||
let jellyfinStale = false;
|
|
||||||
let jellyfinUser: JellyfinUser | undefined = undefined;
|
|
||||||
|
|
||||||
let sonarrBaseUrl = '';
|
|
||||||
let sonarrApiKey = '';
|
|
||||||
let sonarrStale = false;
|
|
||||||
|
|
||||||
let radarrBaseUrl = '';
|
|
||||||
let radarrApiKey = '';
|
|
||||||
let radarrStale = false;
|
|
||||||
|
|
||||||
let lastKeyCode = 0;
|
let lastKeyCode = 0;
|
||||||
let lastKey = '';
|
let lastKey = '';
|
||||||
let tizenMediaKey = '';
|
let tizenMediaKey = '';
|
||||||
@@ -55,63 +42,6 @@
|
|||||||
return $user?.isAdmin ? reiverrApi.getUsers() : undefined;
|
return $user?.isAdmin ? reiverrApi.getUsers() : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDisconnectTmdb() {
|
|
||||||
return user.updateUser((prev) => ({
|
|
||||||
...prev,
|
|
||||||
settings: {
|
|
||||||
...prev.settings,
|
|
||||||
tmdb: {
|
|
||||||
...prev.settings.tmdb,
|
|
||||||
userId: '',
|
|
||||||
sessionId: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSaveJellyfin() {
|
|
||||||
return user.updateUser((prev) => ({
|
|
||||||
...prev,
|
|
||||||
settings: {
|
|
||||||
...prev.settings,
|
|
||||||
jellyfin: {
|
|
||||||
...prev.settings.jellyfin,
|
|
||||||
baseUrl: jellyfinBaseUrl,
|
|
||||||
apiKey: jellyfinApiKey,
|
|
||||||
userId: jellyfinUser?.Id ?? ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSaveSonarr() {
|
|
||||||
return user.updateUser((prev) => ({
|
|
||||||
...prev,
|
|
||||||
settings: {
|
|
||||||
...prev.settings,
|
|
||||||
sonarr: {
|
|
||||||
...prev.settings.sonarr,
|
|
||||||
baseUrl: sonarrBaseUrl,
|
|
||||||
apiKey: sonarrApiKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSaveRadarr() {
|
|
||||||
return user.updateUser((prev) => ({
|
|
||||||
...prev,
|
|
||||||
settings: {
|
|
||||||
...prev.settings,
|
|
||||||
radarr: {
|
|
||||||
...prev.settings.radarr,
|
|
||||||
baseUrl: radarrBaseUrl,
|
|
||||||
apiKey: radarrApiKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLogOut() {
|
function handleLogOut() {
|
||||||
sessions.removeSession();
|
sessions.removeSession();
|
||||||
}
|
}
|
||||||
@@ -294,18 +224,9 @@
|
|||||||
on:enter={scrollIntoView({ vertical: 64 })}
|
on:enter={scrollIntoView({ vertical: 64 })}
|
||||||
>
|
>
|
||||||
<h1 class="mb-4 header1">Sonarr</h1>
|
<h1 class="mb-4 header1">Sonarr</h1>
|
||||||
<SonarrIntegration
|
<SonarrIntegration let:stale let:handleSave>
|
||||||
on:change={({ detail }) => {
|
<Button disabled={!stale} type="primary-dark" action={handleSave}>Save</Button>
|
||||||
sonarrBaseUrl = detail.baseUrl;
|
</SonarrIntegration>
|
||||||
sonarrApiKey = detail.apiKey;
|
|
||||||
sonarrStale = detail.stale;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div class="flex">
|
|
||||||
<Button disabled={!sonarrStale} type="primary-dark" action={handleSaveSonarr}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<Container
|
<Container
|
||||||
@@ -313,18 +234,9 @@
|
|||||||
on:enter={scrollIntoView({ vertical: 64 })}
|
on:enter={scrollIntoView({ vertical: 64 })}
|
||||||
>
|
>
|
||||||
<h1 class="mb-4 header1">Radarr</h1>
|
<h1 class="mb-4 header1">Radarr</h1>
|
||||||
<RadarrIntegration
|
<RadarrIntegration let:stale let:handleSave>
|
||||||
on:change={({ detail }) => {
|
<Button disabled={!stale} type="primary-dark" action={handleSave}>Save</Button>
|
||||||
radarrBaseUrl = detail.baseUrl;
|
</RadarrIntegration>
|
||||||
radarrApiKey = detail.apiKey;
|
|
||||||
radarrStale = detail.stale;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div class="flex">
|
|
||||||
<Button disabled={!radarrStale} type="primary-dark" action={handleSaveRadarr}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Container>
|
</Container>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
@@ -334,33 +246,37 @@
|
|||||||
on:enter={scrollIntoView({ vertical: 64 })}
|
on:enter={scrollIntoView({ vertical: 64 })}
|
||||||
>
|
>
|
||||||
<h1 class="mb-4 header1">Tmdb Account</h1>
|
<h1 class="mb-4 header1">Tmdb Account</h1>
|
||||||
{#await tmdbAccount then tmdbAccount}
|
<TmdbIntegration
|
||||||
{#if tmdbAccount}
|
handleConnectTmdb={() => createModal(TmdbIntegrationConnectDialog, {})}
|
||||||
<SelectField
|
/>
|
||||||
value={tmdbAccount.username || ''}
|
|
||||||
action={handleDisconnectTmdb}
|
<!--{#await tmdbAccount then tmdbAccount}-->
|
||||||
class="mb-4"
|
<!-- {#if tmdbAccount}-->
|
||||||
>
|
<!-- <SelectField-->
|
||||||
Connected to
|
<!-- value={tmdbAccount.username || ''}-->
|
||||||
<Trash
|
<!-- action={handleDisconnectTmdb}-->
|
||||||
slot="icon"
|
<!-- class="mb-4"-->
|
||||||
let:size
|
<!-- >-->
|
||||||
let:iconClass
|
<!-- Connected to-->
|
||||||
{size}
|
<!-- <Trash-->
|
||||||
class={classNames(iconClass, '')}
|
<!-- slot="icon"-->
|
||||||
/>
|
<!-- let:size-->
|
||||||
</SelectField>
|
<!-- let:iconClass-->
|
||||||
{:else}
|
<!-- {size}-->
|
||||||
<div class="flex space-x-4">
|
<!-- class={classNames(iconClass, '')}-->
|
||||||
<Button
|
<!-- />-->
|
||||||
type="primary-dark"
|
<!-- </SelectField>-->
|
||||||
iconAfter={ArrowRight}
|
<!-- {:else}-->
|
||||||
on:clickOrSelect={() => createModal(TmdbIntegrationConnectDialog, {})}
|
<!-- <div class="flex space-x-4">-->
|
||||||
>Connect</Button
|
<!-- <Button-->
|
||||||
>
|
<!-- type="primary-dark"-->
|
||||||
</div>
|
<!-- iconAfter={ArrowRight}-->
|
||||||
{/if}
|
<!-- on:clickOrSelect={() => createModal(TmdbIntegrationConnectDialog, {})}-->
|
||||||
{/await}
|
<!-- >Connect</Button-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- {/if}-->
|
||||||
|
<!--{/await}-->
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<Container
|
<Container
|
||||||
@@ -369,24 +285,17 @@
|
|||||||
>
|
>
|
||||||
<h1 class="mb-4 header1">Jellyfin</h1>
|
<h1 class="mb-4 header1">Jellyfin</h1>
|
||||||
<JellyfinIntegration
|
<JellyfinIntegration
|
||||||
bind:jellyfinUser
|
|
||||||
on:change={({ detail }) => {
|
|
||||||
jellyfinBaseUrl = detail.baseUrl;
|
|
||||||
jellyfinApiKey = detail.apiKey;
|
|
||||||
jellyfinStale = detail.stale;
|
|
||||||
}}
|
|
||||||
on:click-user={({ detail }) =>
|
on:click-user={({ detail }) =>
|
||||||
createModal(JellyfinIntegrationUsersDialog, {
|
createModal(JellyfinIntegrationUsersDialog, {
|
||||||
selectedUser: detail.user,
|
selectedUser: detail.user,
|
||||||
users: detail.users,
|
users: detail.users,
|
||||||
handleSelectUser: (u) => (jellyfinUser = u)
|
handleSelectUser: detail.setJellyfinUser
|
||||||
})}
|
})}
|
||||||
/>
|
let:handleSave
|
||||||
<div class="flex">
|
let:stale
|
||||||
<Button disabled={!jellyfinStale} type="primary-dark" action={handleSaveJellyfin}>
|
>
|
||||||
Save
|
<Button disabled={!stale} type="primary-dark" action={handleSave}>Save</Button>
|
||||||
</Button>
|
</JellyfinIntegration>
|
||||||
</div>
|
|
||||||
</Container>
|
</Container>
|
||||||
</Container>
|
</Container>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -15,6 +15,11 @@
|
|||||||
import { user } from '../stores/user.store';
|
import { user } from '../stores/user.store';
|
||||||
import { sessions } from '../stores/session.store';
|
import { sessions } from '../stores/session.store';
|
||||||
import Panel from '../components/Panel.svelte';
|
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 {
|
enum Tabs {
|
||||||
Welcome,
|
Welcome,
|
||||||
@@ -30,190 +35,10 @@
|
|||||||
|
|
||||||
const tab = useTabs(Tabs.Welcome, { ['class']: 'w-max max-w-lg' });
|
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();
|
$: connectedTmdbAccount = $user?.settings.tmdb.userId && tmdbApi.getAccountDetails();
|
||||||
let tmdbError: string = '';
|
|
||||||
|
|
||||||
let jellyfinBaseUrl: string = '';
|
|
||||||
let jellyfinApiKey: string = '';
|
|
||||||
let jellyfinUser: JellyfinUser | undefined = undefined;
|
let jellyfinUser: JellyfinUser | undefined = undefined;
|
||||||
let jellyfinUsers: Promise<JellyfinUser[]> = Promise.resolve([]);
|
let jellyfinUsers: Promise<JellyfinUser[]> = Promise.resolve([]);
|
||||||
let jellyfinConnectionCheckTimeout: ReturnType<typeof setTimeout> | 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() {
|
async function finalizeSetup() {
|
||||||
await user.updateUser((prev) => ({
|
await user.updateUser((prev) => ({
|
||||||
@@ -225,9 +50,6 @@
|
|||||||
function handleBack() {
|
function handleBack() {
|
||||||
tab.previous();
|
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';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Container focusOnMount class="h-full w-full flex items-center justify-center" on:back={handleBack}>
|
<Container focusOnMount class="h-full w-full flex items-center justify-center" on:back={handleBack}>
|
||||||
@@ -260,40 +82,52 @@
|
|||||||
preferences.
|
preferences.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4 flex flex-col">
|
<TmdbIntegration handleConnectTmdb={() => tab.set(Tabs.TmdbConnect)}>
|
||||||
{#await connectedTmdbAccount then account}
|
<Container direction="horizontal" class="flex space-x-4 *:flex-1">
|
||||||
{#if account}
|
{#if !$user?.settings.tmdb.userId}
|
||||||
<SelectField
|
|
||||||
class="mb-4"
|
|
||||||
value={account.username || ''}
|
|
||||||
on:clickOrSelect={() => {
|
|
||||||
tab.set(Tabs.TmdbConnect);
|
|
||||||
handleGenerateTMDBLink();
|
|
||||||
}}>Logged in as</SelectField
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<Button
|
<Button
|
||||||
type="primary-dark"
|
type="primary-dark"
|
||||||
on:clickOrSelect={() => {
|
on:clickOrSelect={() => {
|
||||||
tab.set(Tabs.TmdbConnect);
|
tab.set(Tabs.TmdbConnect);
|
||||||
handleGenerateTMDBLink();
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
<ArrowRight size={19} slot="icon-absolute" />
|
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{/await}
|
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>
|
||||||
|
{#if $user?.settings.tmdb.userId}
|
||||||
|
Next
|
||||||
|
{:else}
|
||||||
|
Skip
|
||||||
|
{/if}
|
||||||
|
<ArrowRight size={19} slot="icon-absolute" />
|
||||||
|
</Button>
|
||||||
|
</Container>
|
||||||
|
</TmdbIntegration>
|
||||||
|
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>
|
<!-- <div class="space-y-4 flex flex-col">-->
|
||||||
{#if $user?.settings.tmdb.userId}
|
<!-- {#await connectedTmdbAccount then account}-->
|
||||||
Next
|
<!-- {#if account}-->
|
||||||
{:else}
|
<!-- <SelectField-->
|
||||||
Skip
|
<!-- class="mb-4"-->
|
||||||
{/if}
|
<!-- value={account.username || ''}-->
|
||||||
<ArrowRight size={19} slot="icon-absolute" />
|
<!-- on:clickOrSelect={() => {-->
|
||||||
</Button>
|
<!-- tab.set(Tabs.TmdbConnect);-->
|
||||||
</div>
|
<!-- }}>Logged in as</SelectField-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- {:else}-->
|
||||||
|
<!-- <Button-->
|
||||||
|
<!-- type="primary-dark"-->
|
||||||
|
<!-- on:clickOrSelect={() => {-->
|
||||||
|
<!-- tab.set(Tabs.TmdbConnect);-->
|
||||||
|
<!-- }}-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- Connect-->
|
||||||
|
<!-- <ArrowRight size={19} slot="icon-absolute" />-->
|
||||||
|
<!-- </Button>-->
|
||||||
|
<!-- {/if}-->
|
||||||
|
<!-- {/await}-->
|
||||||
|
<!-- </div>-->
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
@@ -304,69 +138,64 @@
|
|||||||
detail.stopPropagation();
|
detail.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h1 class="header2 mb-2">Connect a TMDB Account</h1>
|
<TmdbIntegrationConnect on:connected={() => tab.set(Tabs.Jellyfin)} />
|
||||||
<div class="body mb-8">
|
|
||||||
To connect your TMDB account, log in via the link below and then click "Complete
|
|
||||||
Connection".
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if tmdbConnectQrCode}
|
|
||||||
<div
|
|
||||||
class="w-[150px] h-[150px] bg-contain bg-center mb-8 mx-auto"
|
|
||||||
style={`background-image: url(${tmdbConnectQrCode})`}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Container direction="horizontal" class="flex space-x-4 *:flex-1">
|
|
||||||
{#if !tmdbConnectRequestToken}
|
|
||||||
<Button type="primary-dark" action={handleGenerateTMDBLink}>Generate Link</Button>
|
|
||||||
{:else if tmdbConnectLink}
|
|
||||||
<Button type="primary-dark" action={completeTMDBConnect}>Complete Connection</Button>
|
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => window.open(tmdbConnectLink)}>
|
|
||||||
Open Link
|
|
||||||
<ExternalLink size={19} slot="icon-after" />
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</Container>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab {...tab} tab={Tabs.Jellyfin}>
|
<Tab {...tab} tab={Tabs.Jellyfin}>
|
||||||
<h1 class="header2 mb-2">Connect to Jellyfin</h1>
|
<h1 class="header2 mb-2">Connect to Jellyfin</h1>
|
||||||
<div class="mb-8 body">Connect to Jellyfin to watch movies and tv shows.</div>
|
<div class="mb-8 body">Connect to Jellyfin to watch movies and tv shows.</div>
|
||||||
|
|
||||||
<div class="space-y-4 mb-4">
|
<JellyfinIntegration
|
||||||
<TextField bind:value={jellyfinBaseUrl} isValid={jellyfinUsers.then((u) => !!u?.length)}>
|
bind:jellyfinUser
|
||||||
Base Url
|
bind:jellyfinUsers
|
||||||
</TextField>
|
on:click-user={() => tab.set(Tabs.SelectUser)}
|
||||||
<TextField bind:value={jellyfinApiKey} isValid={jellyfinUsers.then((u) => !!u?.length)}>
|
let:handleSave
|
||||||
API Key
|
let:stale
|
||||||
</TextField>
|
let:empty
|
||||||
</div>
|
let:unchanged
|
||||||
|
>
|
||||||
|
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
||||||
|
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
||||||
|
{#if empty || unchanged}
|
||||||
|
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>
|
||||||
|
{empty ? 'Skip' : 'Next'}
|
||||||
|
</Button>
|
||||||
|
{:else}
|
||||||
|
<Button
|
||||||
|
type="primary-dark"
|
||||||
|
disabled={!stale}
|
||||||
|
action={() => handleSave().then(tab.next)}
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</Container>
|
||||||
|
</JellyfinIntegration>
|
||||||
|
|
||||||
{#await jellyfinUsers then users}
|
<!-- <div class="space-y-4 mb-4">-->
|
||||||
{#if users.length}
|
<!-- <TextField bind:value={jellyfinBaseUrl} isValid={jellyfinUsers.then((u) => !!u?.length)}>-->
|
||||||
<SelectField
|
<!-- Base Url-->
|
||||||
value={jellyfinUser?.Name || 'Select User'}
|
<!-- </TextField>-->
|
||||||
on:clickOrSelect={() => tab.set(Tabs.SelectUser)}
|
<!-- <TextField bind:value={jellyfinApiKey} isValid={jellyfinUsers.then((u) => !!u?.length)}>-->
|
||||||
class="mb-4"
|
<!-- API Key-->
|
||||||
>
|
<!-- </TextField>-->
|
||||||
User
|
<!-- </div>-->
|
||||||
</SelectField>
|
|
||||||
{/if}
|
|
||||||
{/await}
|
|
||||||
|
|
||||||
{#if jellyfinError}
|
<!-- {#await jellyfinUsers then users}-->
|
||||||
<div class="text-red-500 mb-4">{jellyfinError}</div>
|
<!-- {#if users.length}-->
|
||||||
{/if}
|
<!-- <SelectField-->
|
||||||
|
<!-- value={jellyfinUser?.Name || 'Select User'}-->
|
||||||
|
<!-- on:clickOrSelect={() => tab.set(Tabs.SelectUser)}-->
|
||||||
|
<!-- class="mb-4"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- User-->
|
||||||
|
<!-- </SelectField>-->
|
||||||
|
<!-- {/if}-->
|
||||||
|
<!-- {/await}-->
|
||||||
|
|
||||||
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
<!-- {#if jellyfinError}-->
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
<!-- <div class="text-red-500 mb-4">{jellyfinError}</div>-->
|
||||||
{#if jellyfinBaseUrl && jellyfinApiKey && jellyfinUser}
|
<!-- {/if}-->
|
||||||
<Button type="primary-dark" action={handleConnectJellyfin}>Connect</Button>
|
|
||||||
{:else}
|
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>Skip</Button>
|
|
||||||
{/if}
|
|
||||||
</Container>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
{...tab}
|
{...tab}
|
||||||
@@ -379,7 +208,7 @@
|
|||||||
<h1 class="header1 mb-2 w-96">Select User</h1>
|
<h1 class="header1 mb-2 w-96">Select User</h1>
|
||||||
<div class="flex flex-col space-y-4" />
|
<div class="flex flex-col space-y-4" />
|
||||||
{#await jellyfinUsers then users}
|
{#await jellyfinUsers then users}
|
||||||
{#each users as user}
|
{#each users || [] as user}
|
||||||
<SelectItem
|
<SelectItem
|
||||||
selected={user?.Id === jellyfinUser?.Id}
|
selected={user?.Id === jellyfinUser?.Id}
|
||||||
on:clickOrSelect={() => {
|
on:clickOrSelect={() => {
|
||||||
@@ -397,46 +226,48 @@
|
|||||||
<h1 class="header2 mb-2">Connect to Sonarr</h1>
|
<h1 class="header2 mb-2">Connect to Sonarr</h1>
|
||||||
<div class="mb-8">Connect to Sonarr for requesting and managing tv shows.</div>
|
<div class="mb-8">Connect to Sonarr for requesting and managing tv shows.</div>
|
||||||
|
|
||||||
<div class="space-y-4 mb-4">
|
<SonarrIntegration let:stale let:handleSave let:empty let:unchanged>
|
||||||
<TextField bind:value={sonarrBaseUrl}>Base Url</TextField>
|
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
||||||
<TextField bind:value={sonarrApiKey}>API Key</TextField>
|
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
||||||
</div>
|
{#if empty || unchanged}
|
||||||
|
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>
|
||||||
{#if sonarrError}
|
{empty ? 'Skip' : 'Next'}
|
||||||
<div class="text-red-500 mb-4">{sonarrError}</div>
|
</Button>
|
||||||
{/if}
|
{:else}
|
||||||
|
<Button
|
||||||
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
type="primary-dark"
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
disabled={!stale}
|
||||||
{#if sonarrBaseUrl && sonarrApiKey}
|
action={() => handleSave().then(tab.next)}
|
||||||
<Button type="primary-dark" action={handleConnectSonarr}>Connect</Button>
|
>
|
||||||
{:else}
|
Connect
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>Skip</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</Container>
|
</Container>
|
||||||
|
</SonarrIntegration>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab {...tab} tab={Tabs.Radarr}>
|
<Tab {...tab} tab={Tabs.Radarr}>
|
||||||
<h1 class="header2 mb-2">Connect to Radarr</h1>
|
<h1 class="header2 mb-2">Connect to Radarr</h1>
|
||||||
<div class="mb-8">Connect to Radarr for requesting and managing movies.</div>
|
<div class="mb-8">Connect to Radarr for requesting and managing movies.</div>
|
||||||
|
|
||||||
<div class="space-y-4 mb-4">
|
<RadarrIntegration let:stale let:handleSave let:empty let:unchanged>
|
||||||
<TextField bind:value={radarrBaseUrl}>Base Url</TextField>
|
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
||||||
<TextField bind:value={radarrApiKey}>API Key</TextField>
|
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
||||||
</div>
|
{#if empty || unchanged}
|
||||||
|
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>
|
||||||
{#if radarrError}
|
{empty ? 'Skip' : 'Next'}
|
||||||
<div class="text-red-500 mb-4">{radarrError}</div>
|
</Button>
|
||||||
{/if}
|
{:else}
|
||||||
|
<Button
|
||||||
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
type="primary-dark"
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
disabled={!stale}
|
||||||
{#if radarrBaseUrl && radarrApiKey}
|
action={() => handleSave().then(tab.next)}
|
||||||
<Button type="primary-dark" action={handleConnectRadarr}>Connect</Button>
|
>
|
||||||
{:else}
|
Connect
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>Skip</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</Container>
|
</Container>
|
||||||
|
</RadarrIntegration>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab {...tab} tab={Tabs.Complete} class={classNames('w-full')}>
|
<Tab {...tab} tab={Tabs.Complete} class={classNames('w-full')}>
|
||||||
|
|||||||
Reference in New Issue
Block a user