fix: Tab transition animations
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { modalStack } from '../Modal/modal.store';
|
import { modalStack } from '../Modal/modal.store';
|
||||||
|
import Panel from '../Panel.svelte';
|
||||||
|
|
||||||
export let size: 'sm' | 'full' | 'lg' | 'dynamic' = 'sm';
|
export let size: 'sm' | 'full' | 'lg' | 'dynamic' = 'sm';
|
||||||
|
|
||||||
@@ -16,20 +17,12 @@
|
|||||||
class="h-full flex items-center justify-center bg-primary-900/75 py-20 px-32"
|
class="h-full flex items-center justify-center bg-primary-900/75 py-20 px-32"
|
||||||
transition:fade={{ duration: 100 }}
|
transition:fade={{ duration: 100 }}
|
||||||
on:click|self={() => handleClose()}
|
on:click|self={() => handleClose()}
|
||||||
|
on:keypress={() => {
|
||||||
|
/* For a11y*/
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<Panel {size} class={$$restProps.class}>
|
||||||
class={classNames(
|
|
||||||
'bg-primary-800 rounded-2xl p-12 relative shadow-xl flex flex-col transition-[max-width]',
|
|
||||||
{
|
|
||||||
'flex-1 max-w-lg min-h-0 overflow-y-auto scrollbar-hide': size === 'sm',
|
|
||||||
'flex-1 h-full overflow-hidden': size === 'full',
|
|
||||||
'flex-1 max-w-[56rem] min-h-0 overflow-y-auto scrollbar-hide': size === 'lg',
|
|
||||||
'': size === 'dynamic'
|
|
||||||
},
|
|
||||||
$$restProps.class
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<slot close={handleClose} />
|
<slot close={handleClose} />
|
||||||
</div>
|
</Panel>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -103,8 +103,8 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Dialog class="grid" size={$tab === Tabs.EditProfile ? 'sm' : 'dynamic'}>
|
<Dialog class="grid" size={'dynamic'}>
|
||||||
<Tab {...tab} tab={Tabs.EditProfile} class="space-y-4">
|
<Tab {...tab} tab={Tabs.EditProfile} class="space-y-4 max-w-lg">
|
||||||
<h1 class="header2">Edit Profile</h1>
|
<h1 class="header2">Edit Profile</h1>
|
||||||
<TextField bind:value={name}>name</TextField>
|
<TextField bind:value={name}>name</TextField>
|
||||||
<SelectField value={profilePictureTitle} on:clickOrSelect={() => tab.set(Tabs.ProfilePictures)}>
|
<SelectField value={profilePictureTitle} on:clickOrSelect={() => tab.set(Tabs.ProfilePictures)}>
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h1 class="header2 mb-6">Select Profile Picture</h1>
|
<h1 class="header2 mb-6">Select Profile Picture</h1>
|
||||||
<Container direction="grid" gridCols={3} class="grid grid-cols-3 gap-4">
|
<Container direction="grid" gridCols={3} class="grid grid-cols-3 gap-4 w-max">
|
||||||
<ProfileIcon
|
<ProfileIcon
|
||||||
url={profilePictures.ana}
|
url={profilePictures.ana}
|
||||||
on:clickOrSelect={() => setProfilePicture(profilePictures.ana)}
|
on:clickOrSelect={() => setProfilePicture(profilePictures.ana)}
|
||||||
|
|||||||
@@ -15,9 +15,15 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Dialog>
|
<Dialog>
|
||||||
{#each users as user}
|
<h1 class="header1 mb-2">Users</h1>
|
||||||
<SelectItem selected={user.Id === selectedUser?.Id} on:clickOrSelect={() => handleSelect(user)}>
|
<div class="space-y-4">
|
||||||
{user.Name}
|
{#each users as user}
|
||||||
</SelectItem>
|
<SelectItem
|
||||||
{/each}
|
selected={user.Id === selectedUser?.Id}
|
||||||
|
on:clickOrSelect={() => handleSelect(user)}
|
||||||
|
>
|
||||||
|
{user.Name}
|
||||||
|
</SelectItem>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
20
src/lib/components/Panel.svelte
Normal file
20
src/lib/components/Panel.svelte
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
export let size: 'sm' | 'full' | 'lg' | 'dynamic' = 'sm';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={classNames(
|
||||||
|
'bg-primary-800 rounded-2xl p-12 relative shadow-xl flex flex-col transition-[max-width]',
|
||||||
|
{
|
||||||
|
'flex-1 max-w-lg min-h-0 overflow-y-auto scrollbar-hide': size === 'sm',
|
||||||
|
'flex-1 h-full overflow-hidden': size === 'full',
|
||||||
|
'flex-1 max-w-[56rem] min-h-0 overflow-y-auto scrollbar-hide': size === 'lg',
|
||||||
|
'max-w-max overflow-hidden': size === 'dynamic'
|
||||||
|
},
|
||||||
|
$$restProps.class
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Container
|
<Container
|
||||||
class="flex items-center justify-between bg-primary-900 rounded-xl px-6 py-2.5 mb-4 font-medium
|
class="flex items-center justify-between bg-primary-900 rounded-xl px-6 py-2.5 font-medium
|
||||||
border-2 border-transparent focus:border-primary-500 hover:border-primary-500 cursor-pointer group"
|
border-2 border-transparent focus:border-primary-500 hover:border-primary-500 cursor-pointer group"
|
||||||
on:clickOrSelect
|
on:clickOrSelect
|
||||||
on:enter
|
on:enter
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
export let tab: number;
|
export let tab: number;
|
||||||
export let index: number = tab;
|
export let index: number = tab;
|
||||||
export let openTab: Writable<number>;
|
export let openTab: Writable<number>;
|
||||||
|
export let size: 'hug' | 'stretch' = 'hug';
|
||||||
|
|
||||||
let selectable: Selectable;
|
let selectable: Selectable;
|
||||||
|
|
||||||
@@ -33,19 +34,24 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Container
|
<Container
|
||||||
class={classNames(
|
class={classNames('col-start-1 col-end-1 row-start-1 row-end-1', {
|
||||||
$$restProps.class,
|
'absolute pointer-events-none left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2':
|
||||||
'transition-all col-start-1 col-end-1 row-start-1 row-end-1',
|
!active && size === 'hug',
|
||||||
{
|
'absolute pointer-events-none inset-0': !active && size === 'stretch',
|
||||||
'opacity-0 pointer-events-none absolute inset-0': !active,
|
'': active
|
||||||
'-translate-x-10': !active && $openTab >= index,
|
})}
|
||||||
'translate-x-10': !active && $openTab < index
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
bind:selectable
|
bind:selectable
|
||||||
on:back
|
on:back
|
||||||
on:navigate={handleNavigate}
|
on:navigate={handleNavigate}
|
||||||
disabled={!active}
|
disabled={!active}
|
||||||
>
|
>
|
||||||
<slot />
|
<div
|
||||||
|
class={classNames($$restProps.class, 'transition-[transform,opacity]', {
|
||||||
|
'opacity-0 pointer-events-none': !active,
|
||||||
|
'-translate-x-10': !active && $openTab >= index,
|
||||||
|
'translate-x-10': !active && $openTab < index
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
|
import type { ComponentProps } from 'svelte';
|
||||||
|
import Tab from './Tab.svelte';
|
||||||
|
|
||||||
export function useTabs(defaultTab: number) {
|
export function useTabs(defaultTab: number, props: Pick<ComponentProps<Tab>, 'size'> = {}) {
|
||||||
const openTab = writable<number>(defaultTab);
|
const openTab = writable<number>(defaultTab);
|
||||||
|
|
||||||
const next = () => openTab.update((n) => n + 1);
|
const next = () => openTab.update((n) => n + 1);
|
||||||
const previous = () => openTab.update((n) => n - 1);
|
const previous = () => openTab.update((n) => n - 1);
|
||||||
|
|
||||||
return { subscribe: openTab.subscribe, openTab, set: openTab.set, next, previous };
|
return { subscribe: openTab.subscribe, openTab, set: openTab.set, next, previous, ...props };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
About
|
About
|
||||||
}
|
}
|
||||||
|
|
||||||
const tab = useTabs(Tabs.Account);
|
const tab = useTabs(Tabs.Interface, { size: 'stretch' });
|
||||||
|
|
||||||
let jellyfinBaseUrl = '';
|
let jellyfinBaseUrl = '';
|
||||||
let jellyfinApiKey = '';
|
let jellyfinApiKey = '';
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DetachedPage class="px-32 py-16">
|
<DetachedPage class="px-32 py-16 h-screen flex flex-col">
|
||||||
<Container
|
<Container
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
class="flex space-x-8 header3 pb-3 border-b-2 border-secondary-700 w-full mb-8"
|
class="flex space-x-8 header3 pb-3 border-b-2 border-secondary-700 w-full mb-8"
|
||||||
@@ -185,8 +185,8 @@
|
|||||||
</Container>
|
</Container>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<Container class="grid">
|
<Container class="flex-1 grid w-full overflow-y-auto scrollbar-hide relative">
|
||||||
<Tab {...tab} tab={Tabs.Interface} class="">
|
<Tab {...tab} tab={Tabs.Interface} class="w-full">
|
||||||
<div class="flex items-center justify-between text-lg font-medium text-secondary-100 py-2">
|
<div class="flex items-center justify-between text-lg font-medium text-secondary-100 py-2">
|
||||||
<label class="mr-2">Animate scrolling</label>
|
<label class="mr-2">Animate scrolling</label>
|
||||||
<Toggle
|
<Toggle
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
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';
|
||||||
|
|
||||||
enum Tabs {
|
enum Tabs {
|
||||||
Welcome,
|
Welcome,
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
TmdbConnect = Tmdb + 0.1
|
TmdbConnect = Tmdb + 0.1
|
||||||
}
|
}
|
||||||
|
|
||||||
const tab = useTabs(Tabs.Welcome);
|
const tab = useTabs(Tabs.Welcome, { ['class']: 'w-max max-w-lg' });
|
||||||
|
|
||||||
let tmdbConnectRequestToken: string | undefined = undefined;
|
let tmdbConnectRequestToken: string | undefined = undefined;
|
||||||
let tmdbConnectLink: string | undefined = undefined;
|
let tmdbConnectLink: string | undefined = undefined;
|
||||||
@@ -229,217 +230,230 @@
|
|||||||
'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';
|
'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 grid justify-items-center items-center">
|
<Container focusOnMount class="h-full w-full flex items-center justify-center" on:back={handleBack}>
|
||||||
<div class="flex flex-col bg-primary-800 rounded-2xl p-10 shadow-xl max-w-lg">
|
<Panel class="grid max-w-lg" size="dynamic">
|
||||||
<div class="relative">
|
<Tab {...tab} tab={Tabs.Welcome} on:back={({ detail }) => detail.stopPropagation()}>
|
||||||
<Tab {...tab} tab={Tabs.Welcome}>
|
<h1 class="header2 mb-2 w-full">Welcome to Reiverr</h1>
|
||||||
<h1 class="header2 mb-2">Welcome to Reiverr</h1>
|
<div class="body mb-8">
|
||||||
<div class="body mb-8">
|
Looks like this is a new account. This setup will get you started with connecting your
|
||||||
Looks like this is a new account. This setup will get you started with connecting your
|
services to get most out of Reiverr.
|
||||||
services to get most out of Reiverr.
|
</div>
|
||||||
</div>
|
<Container direction="horizontal" class="flex space-x-4">
|
||||||
<Container direction="horizontal" class="flex space-x-4">
|
<Button type="primary-dark" on:clickOrSelect={() => sessions.removeSession()}
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => sessions.removeSession()}
|
>Log Out</Button
|
||||||
>Log Out</Button
|
>
|
||||||
>
|
<div class="flex-1">
|
||||||
<div class="flex-1">
|
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>
|
|
||||||
Next
|
|
||||||
<div class="absolute inset-y-0 right-0 flex items-center justify-center">
|
|
||||||
<ArrowRight size={24} />
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab {...tab} tab={Tabs.Tmdb} on:back={handleBack}>
|
|
||||||
<h1 class="header2 mb-2">Connect a TMDB Account</h1>
|
|
||||||
<div class="body mb-8">
|
|
||||||
Connect to TMDB for personalized recommendations based on your movie reviews and
|
|
||||||
preferences.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-4 flex flex-col">
|
|
||||||
{#await connectedTmdbAccount then account}
|
|
||||||
{#if account}
|
|
||||||
<SelectField
|
|
||||||
value={account.username || ''}
|
|
||||||
on:clickOrSelect={() => {
|
|
||||||
tab.set(Tabs.TmdbConnect);
|
|
||||||
handleGenerateTMDBLink();
|
|
||||||
}}>Logged in as</SelectField
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<Button
|
|
||||||
type="primary-dark"
|
|
||||||
on:clickOrSelect={() => {
|
|
||||||
tab.set(Tabs.TmdbConnect);
|
|
||||||
handleGenerateTMDBLink();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Connect
|
|
||||||
<ArrowRight size={19} slot="icon-absolute" />
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
{/await}
|
|
||||||
|
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>
|
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>
|
||||||
{#if $user?.settings.tmdb.userId}
|
Next
|
||||||
Next
|
<div class="absolute inset-y-0 right-0 flex items-center justify-center">
|
||||||
{:else}
|
<ArrowRight size={24} />
|
||||||
Skip
|
</div>
|
||||||
{/if}
|
|
||||||
<ArrowRight size={19} slot="icon-absolute" />
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Container>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
<Tab {...tab} tab={Tabs.TmdbConnect} on:back={() => tab.set(Tabs.Tmdb)}>
|
<Tab {...tab} tab={Tabs.Tmdb}>
|
||||||
<h1 class="header2 mb-2">Connect a TMDB Account</h1>
|
<h1 class="header2 mb-2">Connect a TMDB Account</h1>
|
||||||
<div class="body mb-8">
|
<div class="body mb-8">
|
||||||
To connect your TMDB account, log in via the link below and then click "Complete
|
Connect to TMDB for personalized recommendations based on your movie reviews and
|
||||||
Connection".
|
preferences.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if tmdbConnectQrCode}
|
<div class="space-y-4 flex flex-col">
|
||||||
<div
|
{#await connectedTmdbAccount then account}
|
||||||
class="w-[150px] h-[150px] bg-contain bg-center mb-8 mx-auto"
|
{#if account}
|
||||||
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={Tabs.Jellyfin}>
|
|
||||||
<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="space-y-4 mb-4">
|
|
||||||
<TextField bind:value={jellyfinBaseUrl} isValid={jellyfinUsers.then((u) => !!u?.length)}>
|
|
||||||
Base Url
|
|
||||||
</TextField>
|
|
||||||
<TextField bind:value={jellyfinApiKey} isValid={jellyfinUsers.then((u) => !!u?.length)}>
|
|
||||||
API Key
|
|
||||||
</TextField>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#await jellyfinUsers then users}
|
|
||||||
{#if users.length}
|
|
||||||
<SelectField
|
<SelectField
|
||||||
value={jellyfinUser?.Name || 'Select User'}
|
value={account.username || ''}
|
||||||
on:clickOrSelect={() => tab.set(Tabs.SelectUser)}
|
|
||||||
>
|
|
||||||
User
|
|
||||||
</SelectField>
|
|
||||||
{/if}
|
|
||||||
{/await}
|
|
||||||
|
|
||||||
{#if jellyfinError}
|
|
||||||
<div class="text-red-500 mb-4">{jellyfinError}</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
|
||||||
{#if jellyfinBaseUrl && jellyfinApiKey && jellyfinUser}
|
|
||||||
<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={Tabs.SelectUser} on:back={() => tab.set(Tabs.Jellyfin)}>
|
|
||||||
<h1 class="header1 mb-2">Select User</h1>
|
|
||||||
{#await jellyfinUsers then users}
|
|
||||||
{#each users as user}
|
|
||||||
<SelectItem
|
|
||||||
selected={user?.Id === jellyfinUser?.Id}
|
|
||||||
on:clickOrSelect={() => {
|
on:clickOrSelect={() => {
|
||||||
jellyfinUser = user;
|
tab.set(Tabs.TmdbConnect);
|
||||||
tab.set(Tabs.Jellyfin);
|
handleGenerateTMDBLink();
|
||||||
|
}}>Logged in as</SelectField
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<Button
|
||||||
|
type="primary-dark"
|
||||||
|
on:clickOrSelect={() => {
|
||||||
|
tab.set(Tabs.TmdbConnect);
|
||||||
|
handleGenerateTMDBLink();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{user.Name}
|
Connect
|
||||||
</SelectItem>
|
<ArrowRight size={19} slot="icon-absolute" />
|
||||||
{/each}
|
</Button>
|
||||||
|
{/if}
|
||||||
{/await}
|
{/await}
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab {...tab} tab={Tabs.Sonarr}>
|
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>
|
||||||
<h1 class="header2 mb-2">Connect to Sonarr</h1>
|
{#if $user?.settings.tmdb.userId}
|
||||||
<div class="mb-8">Connect to Sonarr for requesting and managing tv shows.</div>
|
Next
|
||||||
|
|
||||||
<div class="space-y-4 mb-4">
|
|
||||||
<TextField bind:value={sonarrBaseUrl}>Base Url</TextField>
|
|
||||||
<TextField bind:value={sonarrApiKey}>API Key</TextField>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if sonarrError}
|
|
||||||
<div class="text-red-500 mb-4">{sonarrError}</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
|
||||||
{#if sonarrBaseUrl && sonarrApiKey}
|
|
||||||
<Button type="primary-dark" action={handleConnectSonarr}>Connect</Button>
|
|
||||||
{:else}
|
{:else}
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>Skip</Button>
|
Skip
|
||||||
{/if}
|
{/if}
|
||||||
</Container>
|
<ArrowRight size={19} slot="icon-absolute" />
|
||||||
</Tab>
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
<Tab {...tab} tab={Tabs.Radarr}>
|
<Tab
|
||||||
<h1 class="header2 mb-2">Connect to Radarr</h1>
|
{...tab}
|
||||||
<div class="mb-8">Connect to Radarr for requesting and managing movies.</div>
|
tab={Tabs.TmdbConnect}
|
||||||
|
on:back={({ detail }) => {
|
||||||
|
tab.set(Tabs.Tmdb);
|
||||||
|
detail.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1 class="header2 mb-2">Connect a TMDB Account</h1>
|
||||||
|
<div class="body mb-8">
|
||||||
|
To connect your TMDB account, log in via the link below and then click "Complete
|
||||||
|
Connection".
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4 mb-4">
|
{#if tmdbConnectQrCode}
|
||||||
<TextField bind:value={radarrBaseUrl}>Base Url</TextField>
|
<div
|
||||||
<TextField bind:value={radarrApiKey}>API Key</TextField>
|
class="w-[150px] h-[150px] bg-contain bg-center mb-8 mx-auto"
|
||||||
</div>
|
style={`background-image: url(${tmdbConnectQrCode})`}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if radarrError}
|
<Container direction="horizontal" class="flex space-x-4 *:flex-1">
|
||||||
<div class="text-red-500 mb-4">{radarrError}</div>
|
{#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}
|
{/if}
|
||||||
|
</Container>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
<Tab {...tab} tab={Tabs.Jellyfin}>
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
<h1 class="header2 mb-2">Connect to Jellyfin</h1>
|
||||||
{#if radarrBaseUrl && radarrApiKey}
|
<div class="mb-8 body">Connect to Jellyfin to watch movies and tv shows.</div>
|
||||||
<Button type="primary-dark" action={handleConnectRadarr}>Connect</Button>
|
|
||||||
{:else}
|
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>Skip</Button>
|
|
||||||
{/if}
|
|
||||||
</Container>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab {...tab} tab={Tabs.Complete} class={classNames('w-full')}>
|
<div class="space-y-4 mb-4">
|
||||||
<div class="flex items-center justify-center text-secondary-500 mb-4">
|
<TextField bind:value={jellyfinBaseUrl} isValid={jellyfinUsers.then((u) => !!u?.length)}>
|
||||||
<CheckCircled size={64} />
|
Base Url
|
||||||
</div>
|
</TextField>
|
||||||
<h1 class="header2 text-center w-full">All Set!</h1>
|
<TextField bind:value={jellyfinApiKey} isValid={jellyfinUsers.then((u) => !!u?.length)}>
|
||||||
<div class="header1 mb-8 text-center">Reiverr is now ready to use.</div>
|
API Key
|
||||||
|
</TextField>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Container direction="horizontal" class="inline-flex space-x-4 w-full">
|
{#await jellyfinUsers then users}
|
||||||
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()} icon={ArrowLeft}
|
{#if users.length}
|
||||||
>Back</Button
|
<SelectField
|
||||||
|
value={jellyfinUser?.Name || 'Select User'}
|
||||||
|
on:clickOrSelect={() => tab.set(Tabs.SelectUser)}
|
||||||
>
|
>
|
||||||
<div class="flex-1">
|
User
|
||||||
<Button type="primary-dark" on:clickOrSelect={finalizeSetup} iconAbsolute={ArrowRight}
|
</SelectField>
|
||||||
>Done</Button
|
{/if}
|
||||||
>
|
{/await}
|
||||||
</div>
|
|
||||||
</Container>
|
{#if jellyfinError}
|
||||||
</Tab>
|
<div class="text-red-500 mb-4">{jellyfinError}</div>
|
||||||
</div>
|
{/if}
|
||||||
</div>
|
|
||||||
|
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
||||||
|
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
||||||
|
{#if jellyfinBaseUrl && jellyfinApiKey && jellyfinUser}
|
||||||
|
<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={Tabs.SelectUser}
|
||||||
|
on:back={({ detail }) => {
|
||||||
|
tab.set(Tabs.Jellyfin);
|
||||||
|
detail.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1 class="header1 mb-2 w-96">Select User</h1>
|
||||||
|
<div class="flex flex-col space-y-4" />
|
||||||
|
{#await jellyfinUsers then users}
|
||||||
|
{#each users as user}
|
||||||
|
<SelectItem
|
||||||
|
selected={user?.Id === jellyfinUser?.Id}
|
||||||
|
on:clickOrSelect={() => {
|
||||||
|
jellyfinUser = user;
|
||||||
|
tab.set(Tabs.Jellyfin);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{user.Name}
|
||||||
|
</SelectItem>
|
||||||
|
{/each}
|
||||||
|
{/await}
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab {...tab} tab={Tabs.Sonarr}>
|
||||||
|
<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="space-y-4 mb-4">
|
||||||
|
<TextField bind:value={sonarrBaseUrl}>Base Url</TextField>
|
||||||
|
<TextField bind:value={sonarrApiKey}>API Key</TextField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if sonarrError}
|
||||||
|
<div class="text-red-500 mb-4">{sonarrError}</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
||||||
|
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
||||||
|
{#if sonarrBaseUrl && sonarrApiKey}
|
||||||
|
<Button type="primary-dark" action={handleConnectSonarr}>Connect</Button>
|
||||||
|
{:else}
|
||||||
|
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>Skip</Button>
|
||||||
|
{/if}
|
||||||
|
</Container>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab {...tab} tab={Tabs.Radarr}>
|
||||||
|
<h1 class="header2 mb-2">Connect to Radarr</h1>
|
||||||
|
<div class="mb-8">Connect to Radarr for requesting and managing movies.</div>
|
||||||
|
|
||||||
|
<div class="space-y-4 mb-4">
|
||||||
|
<TextField bind:value={radarrBaseUrl}>Base Url</TextField>
|
||||||
|
<TextField bind:value={radarrApiKey}>API Key</TextField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if radarrError}
|
||||||
|
<div class="text-red-500 mb-4">{radarrError}</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Container direction="horizontal" class="grid grid-cols-2 gap-4 mt-4">
|
||||||
|
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()}>Back</Button>
|
||||||
|
{#if radarrBaseUrl && radarrApiKey}
|
||||||
|
<Button type="primary-dark" action={handleConnectRadarr}>Connect</Button>
|
||||||
|
{:else}
|
||||||
|
<Button type="primary-dark" on:clickOrSelect={() => tab.next()}>Skip</Button>
|
||||||
|
{/if}
|
||||||
|
</Container>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab {...tab} tab={Tabs.Complete} class={classNames('w-full')}>
|
||||||
|
<div class="flex items-center justify-center text-secondary-500 mb-4">
|
||||||
|
<CheckCircled size={64} />
|
||||||
|
</div>
|
||||||
|
<h1 class="header2 text-center w-full">All Set!</h1>
|
||||||
|
<div class="header1 mb-8 text-center">Reiverr is now ready to use.</div>
|
||||||
|
|
||||||
|
<Container direction="horizontal" class="inline-flex space-x-4 w-full">
|
||||||
|
<Button type="primary-dark" on:clickOrSelect={() => tab.previous()} icon={ArrowLeft}
|
||||||
|
>Back</Button
|
||||||
|
>
|
||||||
|
<div class="flex-1">
|
||||||
|
<Button type="primary-dark" on:clickOrSelect={finalizeSetup} iconAbsolute={ArrowRight}
|
||||||
|
>Done</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</Tab>
|
||||||
|
</Panel>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
Reference in New Issue
Block a user