359 lines
10 KiB
Svelte
359 lines
10 KiB
Svelte
<script lang="ts">
|
|
import Container from '../../Container.svelte';
|
|
import Button from '../components/Button.svelte';
|
|
import Toggle from '../components/Toggle.svelte';
|
|
import { localSettings } from '../stores/localstorage.store';
|
|
import classNames from 'classnames';
|
|
import Tab from '../components/Tab/Tab.svelte';
|
|
import { useTabs } from '../components/Tab/Tab';
|
|
import SonarrIntegration from '../components/Integrations/SonarrIntegration.svelte';
|
|
import RadarrIntegration from '../components/Integrations/RadarrIntegration.svelte';
|
|
import type { JellyfinUser } from '../apis/jellyfin/jellyfin-api';
|
|
import JellyfinIntegration from '../components/Integrations/JellyfinIntegration.svelte';
|
|
import JellyfinIntegrationUsersDialog from '../components/Integrations/JellyfinIntegrationUsersDialog.svelte';
|
|
import { tmdbApi } from '../apis/tmdb/tmdb-api';
|
|
import SelectField from '../components/SelectField.svelte';
|
|
import { ArrowRight, Exit, Pencil1, Pencil2, Trash } from 'radix-icons-svelte';
|
|
import TmdbIntegrationConnectDialog from '../components/Integrations/TmdbIntegrationConnectDialog.svelte';
|
|
import { createModal } from '../components/Modal/modal.store';
|
|
import DetachedPage from '../components/DetachedPage/DetachedPage.svelte';
|
|
import { user } from '../stores/user.store';
|
|
import { sessions } from '../stores/session.store';
|
|
import TextField from '../components/TextField.svelte';
|
|
import EditProfileModal from '../components/Dialog/EditProfileModal.svelte';
|
|
import { scrollIntoView } from '../selectable';
|
|
|
|
enum Tabs {
|
|
Interface,
|
|
Account,
|
|
About
|
|
}
|
|
|
|
const tab = useTabs(Tabs.Account);
|
|
|
|
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 lastKey = '';
|
|
let tizenMediaKey = '';
|
|
$: tmdbAccount = $user?.settings.tmdb.userId ? tmdbApi.getAccountDetails() : undefined;
|
|
|
|
// onMount(() => {
|
|
// if (isTizen()) {
|
|
// const myMediaKeyChangeListener = {
|
|
// onpressed: function (key: string) {
|
|
// console.log('Pressed key: ' + key);
|
|
// tizenMediaKey = key;
|
|
// }
|
|
// };
|
|
//
|
|
// // eslint-disable-next-line no-undef
|
|
// tizen?.tvinputdevice?.registerKey?.('MediaPlayPause');
|
|
// (tizen as any)?.mediakey?.setMediaKeyEventListener?.(myMediaKeyChangeListener);
|
|
// }
|
|
// });
|
|
|
|
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() {
|
|
sessions.removeSession();
|
|
}
|
|
</script>
|
|
|
|
<svelte:window
|
|
on:keydown={(e) => {
|
|
lastKeyCode = e.keyCode;
|
|
lastKey = e.key;
|
|
}}
|
|
/>
|
|
|
|
<DetachedPage class="px-32 py-16">
|
|
<Container
|
|
direction="horizontal"
|
|
class="flex space-x-8 header3 pb-3 border-b-2 border-secondary-700 w-full mb-8"
|
|
>
|
|
<Container
|
|
on:enter={() => tab.set(Tabs.Interface)}
|
|
on:clickOrSelect={() => tab.set(Tabs.Interface)}
|
|
let:hasFocus
|
|
focusOnClick
|
|
>
|
|
<span
|
|
class={classNames('cursor-pointer', {
|
|
'text-secondary-400': $tab !== Tabs.Interface,
|
|
'text-primary-500': hasFocus
|
|
})}
|
|
>
|
|
Options
|
|
</span>
|
|
</Container>
|
|
<Container
|
|
on:enter={() => tab.set(Tabs.Account)}
|
|
on:clickOrSelect={() => tab.set(Tabs.Account)}
|
|
let:hasFocus
|
|
focusOnClick
|
|
>
|
|
<span
|
|
class={classNames('cursor-pointer', {
|
|
'text-secondary-400': $tab !== Tabs.Account,
|
|
'text-primary-500': hasFocus
|
|
})}
|
|
>
|
|
Account
|
|
</span>
|
|
</Container>
|
|
<Container
|
|
on:enter={() => tab.set(Tabs.About)}
|
|
on:clickOrSelect={() => tab.set(Tabs.About)}
|
|
let:hasFocus
|
|
focusOnClick
|
|
>
|
|
<span
|
|
class={classNames('cursor-pointer', {
|
|
'text-secondary-400': $tab !== Tabs.About,
|
|
'text-primary-500': hasFocus
|
|
})}
|
|
>
|
|
About
|
|
</span>
|
|
</Container>
|
|
</Container>
|
|
|
|
<Container class="grid">
|
|
<Tab {...tab} tab={Tabs.Interface} class="">
|
|
<div class="flex items-center justify-between text-lg font-medium text-secondary-100 py-2">
|
|
<label class="mr-2">Animate scrolling</label>
|
|
<Toggle
|
|
checked={$localSettings.animateScrolling}
|
|
on:change={({ detail }) =>
|
|
localSettings.update((p) => ({ ...p, animateScrolling: detail }))}
|
|
/>
|
|
</div>
|
|
<div class="flex items-center justify-between text-lg font-medium text-secondary-100 py-2">
|
|
<label class="mr-2">Use CSS Transitions</label>
|
|
<Toggle
|
|
checked={$localSettings.useCssTransitions}
|
|
on:change={({ detail }) =>
|
|
localSettings.update((p) => ({ ...p, useCssTransitions: detail }))}
|
|
/>
|
|
</div>
|
|
<div class="flex items-center justify-between text-lg font-medium text-secondary-100 py-2">
|
|
<label class="mr-2">Check for Updates</label>
|
|
<Toggle
|
|
checked={$localSettings.checkForUpdates}
|
|
on:change={({ detail }) =>
|
|
localSettings.update((p) => ({ ...p, checkForUpdates: detail }))}
|
|
/>
|
|
</div>
|
|
</Tab>
|
|
|
|
<Tab {...tab} tab={Tabs.Account} class="space-y-16">
|
|
<div>
|
|
<h1 class="font-semibold text-2xl text-secondary-100 mb-8">Profile</h1>
|
|
<Container class="bg-primary-800 rounded-xl p-8" on:enter={scrollIntoView({ top: 9999 })}>
|
|
<SelectField
|
|
value={$user?.name || ''}
|
|
on:clickOrSelect={() => {
|
|
const u = $user;
|
|
if (u)
|
|
createModal(EditProfileModal, {
|
|
user: u
|
|
});
|
|
}}
|
|
>
|
|
Logged in as
|
|
<Pencil2 slot="icon" let:size let:iconClass {size} class={classNames(iconClass)} />
|
|
</SelectField>
|
|
<Container direction="horizontal" class="flex space-x-4">
|
|
<Button type="primary-dark" icon={Exit} on:clickOrSelect={handleLogOut}>Log Out</Button>
|
|
</Container>
|
|
</Container>
|
|
</div>
|
|
|
|
<div>
|
|
<h1 class="font-semibold text-2xl text-secondary-100 mb-8">Integrations</h1>
|
|
<Container direction="horizontal" class="gap-16 grid grid-cols-2">
|
|
<Container class="flex flex-col space-y-16">
|
|
<Container
|
|
class="bg-primary-800 rounded-xl p-8"
|
|
on:enter={scrollIntoView({ vertical: 64 })}
|
|
>
|
|
<h1 class="mb-4 header2">Sonarr</h1>
|
|
<SonarrIntegration
|
|
on:change={({ detail }) => {
|
|
sonarrBaseUrl = detail.baseUrl;
|
|
sonarrApiKey = detail.apiKey;
|
|
sonarrStale = detail.stale;
|
|
}}
|
|
/>
|
|
<div class="flex">
|
|
<Button disabled={!sonarrStale} type="primary-dark" action={handleSaveSonarr}>
|
|
Save
|
|
</Button>
|
|
</div>
|
|
</Container>
|
|
|
|
<Container
|
|
class="bg-primary-800 rounded-xl p-8"
|
|
on:enter={scrollIntoView({ vertical: 64 })}
|
|
>
|
|
<h1 class="mb-4 header2">Radarr</h1>
|
|
<RadarrIntegration
|
|
on:change={({ detail }) => {
|
|
radarrBaseUrl = detail.baseUrl;
|
|
radarrApiKey = detail.apiKey;
|
|
radarrStale = detail.stale;
|
|
}}
|
|
/>
|
|
<div class="flex">
|
|
<Button disabled={!radarrStale} type="primary-dark" action={handleSaveRadarr}>
|
|
Save
|
|
</Button>
|
|
</div>
|
|
</Container>
|
|
</Container>
|
|
|
|
<Container class="flex flex-col space-y-16">
|
|
<Container
|
|
class="bg-primary-800 rounded-xl p-8"
|
|
on:enter={scrollIntoView({ vertical: 64 })}
|
|
>
|
|
<h1 class="mb-4 header2">Tmdb Account</h1>
|
|
{#await tmdbAccount then tmdbAccount}
|
|
{#if tmdbAccount}
|
|
<SelectField value={tmdbAccount.username || ''} action={handleDisconnectTmdb}>
|
|
Connected to
|
|
<Trash
|
|
slot="icon"
|
|
let:size
|
|
let:iconClass
|
|
{size}
|
|
class={classNames(iconClass, '')}
|
|
/>
|
|
</SelectField>
|
|
{:else}
|
|
<div class="flex space-x-4">
|
|
<Button
|
|
type="primary-dark"
|
|
iconAfter={ArrowRight}
|
|
on:clickOrSelect={() => createModal(TmdbIntegrationConnectDialog, {})}
|
|
>Connect</Button
|
|
>
|
|
</div>
|
|
{/if}
|
|
{/await}
|
|
</Container>
|
|
|
|
<Container
|
|
class="bg-primary-800 rounded-xl p-8"
|
|
on:enter={scrollIntoView({ vertical: 64 })}
|
|
>
|
|
<h1 class="mb-4 header2">Jellyfin</h1>
|
|
<JellyfinIntegration
|
|
bind:jellyfinUser
|
|
on:change={({ detail }) => {
|
|
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)
|
|
})}
|
|
/>
|
|
<div class="flex">
|
|
<Button disabled={!jellyfinStale} type="primary-dark" action={handleSaveJellyfin}>
|
|
Save
|
|
</Button>
|
|
</div>
|
|
</Container>
|
|
</Container>
|
|
</Container>
|
|
</div>
|
|
</Tab>
|
|
|
|
<Tab {...tab} tab={Tabs.About}>
|
|
<div>
|
|
Version: {REIVERR_VERSION}
|
|
</div>
|
|
User agent: {window?.navigator?.userAgent}
|
|
<div>Last key code: {lastKeyCode}</div>
|
|
<div>Last key: {lastKey}</div>
|
|
{#if tizenMediaKey}
|
|
<div>Tizen media key: {tizenMediaKey}</div>
|
|
{/if}
|
|
<div class="flex space-x-4 mt-4">
|
|
<Button on:clickOrSelect={handleLogOut} class="hover:bg-red-500">Log Out</Button>
|
|
</div>
|
|
</Tab>
|
|
</Container>
|
|
</DetachedPage>
|