mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-20 07:53:32 +02:00
refactor: pretty much the whole backend module hierarchy
This commit is contained in:
@@ -49,7 +49,7 @@
|
||||
<AnimatedSelection
|
||||
hasFocus={$hasFocus || $hasSecondaryFocus}
|
||||
enabled={!disabled}
|
||||
class={classNames('flex items-center font-medium tracking-wide ', {}, $$restProps.class)}
|
||||
class={classNames('inline-flex items-center font-medium tracking-wide ', {}, $$restProps.class)}
|
||||
>
|
||||
<Container
|
||||
bind:hasFocus
|
||||
@@ -83,9 +83,7 @@
|
||||
'!bg-red-500': confirmDanger && armed
|
||||
})}
|
||||
>
|
||||
<div
|
||||
class="flex-1 text-center text-nowrap flex items-center justify-center relative *:flex-1"
|
||||
>
|
||||
<div class="flex-1 text-center text-nowrap flex items-center justify-center relative">
|
||||
{#if $$slots.icon}
|
||||
<div class="mr-2">
|
||||
<slot name="icon" />
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
{#if backdropUrl}
|
||||
<LazyImg src={backdropUrl} class="absolute inset-0" />
|
||||
{:else}
|
||||
<div class="absolute inset-0 bg-secondary-700 header4 flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-secondary-700 h1 flex items-center justify-center">
|
||||
{title}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import IconButton from '../IconButton.svelte';
|
||||
import IconButton from '../FloatingIconButton.svelte';
|
||||
import { ChevronLeft, ChevronRight } from 'radix-icons-svelte';
|
||||
import classNames from 'classnames';
|
||||
import Container from '../Container.svelte';
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
<div class={classNames('flex flex-col group/carousel', $$restProps.class)}>
|
||||
<div class={'flex justify-between items-center mb-2 ' + scrollClass}>
|
||||
<div class="header2">
|
||||
<div class="h3">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import { Selectable, type EnterEvent, type NavigateEvent, type KeyEvent } from '../selectable';
|
||||
import classNames from 'classnames';
|
||||
import type { ContainerProps } from './Container.type';
|
||||
|
||||
type $$Props = ContainerProps;
|
||||
|
||||
/**
|
||||
* The basis for d-pad navigation. It's a mess, but it works™️
|
||||
@@ -20,14 +23,14 @@
|
||||
playPause: KeyEvent;
|
||||
}>();
|
||||
|
||||
export let name: string = '';
|
||||
export let direction: 'vertical' | 'horizontal' | 'grid' = 'vertical';
|
||||
export let gridCols: number = 0;
|
||||
export let focusOnMount = false;
|
||||
export let trapFocus = false;
|
||||
export let debugOutline = false;
|
||||
export let focusOnClick = false;
|
||||
export let focusedChild = false;
|
||||
export let name: Required<ContainerProps>['name'] = '';
|
||||
export let direction: Required<ContainerProps>['direction'] = 'vertical';
|
||||
export let gridCols: Required<ContainerProps>['gridCols'] = 0;
|
||||
export let focusOnMount: Required<ContainerProps>['focusOnMount'] = false;
|
||||
export let trapFocus: Required<ContainerProps>['trapFocus'] = false;
|
||||
export let debugOutline: Required<ContainerProps>['debugOutline'] = false;
|
||||
export let focusOnClick: Required<ContainerProps>['focusOnClick'] = false;
|
||||
export let focusedChild: Required<ContainerProps>['focusedChild'] = false;
|
||||
|
||||
export let disabled = false;
|
||||
|
||||
@@ -95,7 +98,7 @@
|
||||
export const focusIndex = rest.focusIndex;
|
||||
export const activeChild = rest.activeChild;
|
||||
|
||||
export let tag = 'div';
|
||||
export let tag: Required<ContainerProps>['tag'] = 'div';
|
||||
|
||||
$: selectable.setIsDisabled(disabled);
|
||||
$: selectable.setGridColumns(gridCols);
|
||||
|
||||
25
src/lib/components/Container.type.ts
Normal file
25
src/lib/components/Container.type.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// export let name: string = '';
|
||||
// export let direction: 'vertical' | 'horizontal' | 'grid' = 'vertical';
|
||||
// export let gridCols: number = 0;
|
||||
// export let focusOnMount = false;
|
||||
// export let trapFocus = false;
|
||||
// export let debugOutline = false;
|
||||
// export let focusOnClick = false;
|
||||
// export let focusedChild = false;
|
||||
|
||||
import type { SvelteHTMLElements } from "svelte/elements";
|
||||
|
||||
// export let disabled = false;
|
||||
|
||||
export type ContainerProps = SvelteHTMLElements['div'] & {
|
||||
name?: string;
|
||||
direction?: 'vertical' | 'horizontal' | 'grid';
|
||||
gridCols?: number;
|
||||
focusOnMount?: boolean;
|
||||
trapFocus?: boolean;
|
||||
debugOutline?: boolean;
|
||||
focusOnClick?: boolean;
|
||||
focusedChild?: boolean;
|
||||
disabled?: boolean;
|
||||
tag?: string;
|
||||
};
|
||||
@@ -4,6 +4,12 @@
|
||||
import { get } from 'svelte/store';
|
||||
import Sidebar from '../Sidebar/Sidebar.svelte';
|
||||
import classNames from 'classnames';
|
||||
import type { ContainerProps } from '../Container.type';
|
||||
|
||||
interface $$Props extends ContainerProps {
|
||||
topmost?: boolean;
|
||||
sidebar?: boolean;
|
||||
}
|
||||
|
||||
export let topmost = true;
|
||||
export let sidebar = true;
|
||||
@@ -45,7 +51,7 @@
|
||||
{#if sidebar}
|
||||
<Sidebar />
|
||||
{/if}
|
||||
<Container on:back={handleGoToTop} focusOnMount class={classNames($$restProps.class)}>
|
||||
<Container {...$$restProps} on:back={handleGoToTop} focusOnMount>
|
||||
<slot {handleGoBack} registrar={topSelectable.registrar} />
|
||||
</Container>
|
||||
</Container>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</script>
|
||||
|
||||
<Dialog>
|
||||
<div class="header2 mb-4">
|
||||
<div class="h3 mb-4">
|
||||
{header}
|
||||
</div>
|
||||
<div class="font-medium text-secondary-300 mb-8">
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
|
||||
<Dialog class="grid" size={'dynamic'}>
|
||||
<Tab {...tab} tab={Tabs.EditProfile} class="space-y-4 max-w-lg">
|
||||
<h1 class="header2">
|
||||
<h1 class="h3">
|
||||
{createNew ? 'Create Account' : 'Edit Profile'}
|
||||
</h1>
|
||||
<TextField bind:value={name}>name</TextField>
|
||||
@@ -236,7 +236,7 @@
|
||||
detail.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<h1 class="header2 mb-6">Select Profile Picture</h1>
|
||||
<h1 class="h3 mb-6">Select Profile Picture</h1>
|
||||
<Container direction="grid" gridCols={3} class="grid grid-cols-3 gap-4 w-max">
|
||||
<ProfileIcon
|
||||
url={profilePictures.ana}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
/* For a11y*/
|
||||
}}
|
||||
>
|
||||
<Panel {size} class={$$restProps.class}>
|
||||
<Panel {size} class={$$restProps.class} onClose={handleClose}>
|
||||
<slot close={handleClose} />
|
||||
</Panel>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import SelectItem from '../SelectItem.svelte';
|
||||
import { modalStack } from '../Modal/modal.store';
|
||||
|
||||
// TODO: Add labels to the options
|
||||
export let title: string = 'Select';
|
||||
export let subtitle: string = '';
|
||||
export let options: string[];
|
||||
@@ -21,7 +22,7 @@
|
||||
<Dialog>
|
||||
<div class="mb-4">
|
||||
<slot>
|
||||
<h1 class="header1">{title}</h1>
|
||||
<h1 class="h4">{title}</h1>
|
||||
<p class="text-secondary-300">{subtitle}</p>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="flex items-center justify-center text-secondary-500 mb-4">
|
||||
<InfoCircled size={64} />
|
||||
</div>
|
||||
<h1 class="header2 text-center">Update Available</h1>
|
||||
<h1 class="h3 text-center">Update Available</h1>
|
||||
<div class="body mb-8 text-center">Reiverr {version} is now available.</div>
|
||||
<Container class="space-y-4">
|
||||
<Button type="primary-dark" on:clickOrSelect={dismiss}>Dismiss</Button>
|
||||
|
||||
52
src/lib/components/FloatingIconButton.svelte
Normal file
52
src/lib/components/FloatingIconButton.svelte
Normal file
@@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import classNames from 'classnames';
|
||||
import Container from './Container.svelte';
|
||||
|
||||
export let disabled = false;
|
||||
export let type: 'floating' | 'solid' = 'floating';
|
||||
export let container = false;
|
||||
|
||||
// const getClass = (hasFocus?: boolean) => type === 'floating' ? classNames(
|
||||
// 'text-zinc-300 hover:text-zinc-50 p-1 flex items-center justify-center rounded-sm flex-shrink-0 bg-transparent',
|
||||
// {
|
||||
// 'opacity-30 cursor-not-allowed pointer-events-none': disabled,
|
||||
// 'cursor-pointer': !disabled
|
||||
// },
|
||||
// $$restProps.class
|
||||
// ) : classNames(
|
||||
// 'group-hover:bg-primary-500 group-hover:text-secondary-800 rounded-full p-3',
|
||||
// {
|
||||
// 'bg-primary-500 text-secondary-800': hasFocus
|
||||
// },
|
||||
// $$restProps.class
|
||||
// )
|
||||
</script>
|
||||
|
||||
{#if !container}
|
||||
<button
|
||||
class={classNames(
|
||||
'text-secondary-400 hover:text-secondary-200 p-1 flex items-center justify-center rounded-sm flex-shrink-0 bg-transparent',
|
||||
{
|
||||
'opacity-30 cursor-not-allowed pointer-events-none': disabled,
|
||||
'cursor-pointer': !disabled
|
||||
},
|
||||
$$restProps.class
|
||||
)}
|
||||
on:click
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
{:else}
|
||||
<Container on:clickOrSelect let:hasFocus class="cursor-pointer group">
|
||||
<div
|
||||
class={classNames(
|
||||
'group-hover:bg-primary-500 group-hover:text-secondary-800 rounded-full p-3',
|
||||
{
|
||||
'bg-primary-500 text-secondary-800': hasFocus
|
||||
}
|
||||
)}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</Container>
|
||||
{/if}
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Container from '../Container.svelte';
|
||||
import HeroShowcaseBackground from './HeroBackground.svelte';
|
||||
import IconButton from '../IconButton.svelte';
|
||||
import IconButton from '../FloatingIconButton.svelte';
|
||||
import { ChevronRight } from 'radix-icons-svelte';
|
||||
import PageDots from '../HeroShowcase/PageDots.svelte';
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
import classNames from 'classnames';
|
||||
|
||||
export let disabled = false;
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={classNames(
|
||||
'text-zinc-300 hover:text-zinc-50 p-1 flex items-center justify-center rounded-sm flex-shrink-0 bg-transparent',
|
||||
{
|
||||
'opacity-30 cursor-not-allowed pointer-events-none': disabled,
|
||||
'cursor-pointer': !disabled
|
||||
},
|
||||
$$restProps.class
|
||||
)}
|
||||
on:click
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
@@ -15,7 +15,7 @@
|
||||
</script>
|
||||
|
||||
<Dialog>
|
||||
<h1 class="header1 mb-2">Users</h1>
|
||||
<h1 class="h4 mb-2">Users</h1>
|
||||
<div class="space-y-4">
|
||||
{#each users as user}
|
||||
<SelectItem
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<h1 class="header2 mb-2">Connect a TMDB Account</h1>
|
||||
<h1 class="h3 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>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</script>
|
||||
|
||||
<Container class="flex flex-col" focusOnMount>
|
||||
<h1 class="header2 w-full mb-2">Login to Reiverr</h1>
|
||||
<h1 class="h3 w-full mb-2">Login to Reiverr</h1>
|
||||
<div class="body mb-4">
|
||||
If this is your first time logging in, a new account will be created based on your credentials.
|
||||
</div>
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
>
|
||||
<div class="z-10 mb-8">
|
||||
<div class="h-24" />
|
||||
<h1 class="header2">Add {title} to Sonarr?</h1>
|
||||
<h1 class="h3">Add {title} to Sonarr?</h1>
|
||||
<div class="font-medium text-secondary-300 mb-8">
|
||||
Before you can fetch episodes, you need to add this series to Sonarr.
|
||||
</div>
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
>
|
||||
<div class="z-10 mb-8">
|
||||
<div class="h-24" />
|
||||
<h1 class="header2">Add {title} to Sonarr?</h1>
|
||||
<h1 class="h3">Add {title} to Sonarr?</h1>
|
||||
<div class="font-medium text-secondary-300 mb-8">
|
||||
Before you can fetch episodes, you need to add this series to Sonarr.
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div {...$$restProps}>
|
||||
<div class="header4">
|
||||
<div class="h1">
|
||||
<slot name="title" />
|
||||
</div>
|
||||
<div class="header1">
|
||||
<div class="h4">
|
||||
<slot name="subtitle" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,10 +64,10 @@
|
||||
</script>
|
||||
|
||||
<Container trapFocus class="py-8 h-full flex flex-col">
|
||||
<h1 class="header4 mx-12">
|
||||
<h1 class="h1 mx-12">
|
||||
<slot name="title" />
|
||||
</h1>
|
||||
<h2 class="header1 mx-12 mb-8">
|
||||
<h2 class="h4 mx-12 mb-8">
|
||||
<slot name="subtitle" />
|
||||
</h2>
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<script lang="ts">
|
||||
import classNames from 'classnames';
|
||||
import IconButton from './FloatingIconButton.svelte';
|
||||
import { Cross1 } from 'radix-icons-svelte';
|
||||
|
||||
export let size: 'sm' | 'full' | 'lg' | 'dynamic' = 'sm';
|
||||
export let onClose: (() => void) | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={classNames(
|
||||
'bg-primary-800 rounded-2xl p-12 relative shadow-xl flex flex-col transition-[max-width]',
|
||||
'bg-primary-800 rounded-2xl px-8 py-8 relative shadow-xl flex flex-col transition-[max-width]',
|
||||
{
|
||||
'flex-1 max-h-full max-w-lg min-h-0 overflow-y-auto scrollbar-hide': size === 'sm',
|
||||
'flex-1 h-full overflow-hidden': size === 'full',
|
||||
@@ -16,5 +19,12 @@
|
||||
$$restProps.class
|
||||
)}
|
||||
>
|
||||
{#if onClose}
|
||||
<div class="absolute top-4 right-4">
|
||||
<IconButton on:click={onClose}>
|
||||
<Cross1 size={20} />
|
||||
</IconButton>
|
||||
</div>
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
const dispatch = createEventDispatcher<{ clickOrSelect: null }>();
|
||||
|
||||
export let color: 'secondary' | 'primary' = 'secondary';
|
||||
export let value: string;
|
||||
export let disabled: boolean = false;
|
||||
export let action: (() => Promise<any>) | undefined = undefined;
|
||||
@@ -27,11 +28,13 @@
|
||||
|
||||
<Container
|
||||
class={classNames(
|
||||
'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 group',
|
||||
'flex items-center justify-between rounded-xl px-6 py-2.5 font-medium',
|
||||
'border-2 border-transparent focus:border-primary-500 hover:border-primary-500 group',
|
||||
{
|
||||
'cursor-pointer': !_disabled,
|
||||
'cursor-not-allowed pointer-events-none opacity-40': _disabled
|
||||
'cursor-not-allowed pointer-events-none opacity-40': _disabled,
|
||||
'bg-primary-900': color === 'secondary',
|
||||
'bg-secondary-800': color === 'primary'
|
||||
},
|
||||
$$restProps.class
|
||||
)}
|
||||
@@ -39,12 +42,16 @@
|
||||
let:hasFocus
|
||||
>
|
||||
<div>
|
||||
<h1 class="text-secondary-300 font-semibold tracking-wide text-sm">
|
||||
<slot />
|
||||
</h1>
|
||||
<span>
|
||||
{value}
|
||||
</span>
|
||||
{#if !$$slots.content}
|
||||
<h1 class="text-secondary-300 font-semibold tracking-wide text-sm">
|
||||
<slot />
|
||||
</h1>
|
||||
<span>
|
||||
{value}
|
||||
</span>
|
||||
{:else}
|
||||
<slot name="content" />
|
||||
{/if}
|
||||
</div>
|
||||
<slot
|
||||
name="icon"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
export let index: number = tab;
|
||||
export let openTab: Writable<number>;
|
||||
export let size: 'hug' | 'stretch' = 'hug';
|
||||
export let direction: 'horizontal' | 'vertical' = 'horizontal';
|
||||
|
||||
let selectable: Selectable;
|
||||
|
||||
@@ -46,11 +47,22 @@
|
||||
disabled={!active}
|
||||
>
|
||||
<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
|
||||
})}
|
||||
class={classNames(
|
||||
$$restProps.class,
|
||||
'transition-[transform,opacity]',
|
||||
{
|
||||
'opacity-0 pointer-events-none': !active
|
||||
},
|
||||
direction === 'horizontal'
|
||||
? {
|
||||
'-translate-x-10': !active && $openTab >= index,
|
||||
'translate-x-10': !active && $openTab < index
|
||||
}
|
||||
: {
|
||||
'-translate-y-10': !active && $openTab >= index,
|
||||
'translate-y-10': !active && $openTab < index
|
||||
}
|
||||
)}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Cross2 } from 'radix-icons-svelte';
|
||||
import IconButton from './IconButton.svelte';
|
||||
import IconButton from './FloatingIconButton.svelte';
|
||||
import axios from 'axios';
|
||||
import Button from './Button.svelte';
|
||||
import { skippedVersion } from '../stores/localstorage.store';
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<Dialog {modalId}>
|
||||
<div>
|
||||
<h1 class="header2 mb-4 flex items-center space-x-4">
|
||||
<h1 class="h3 mb-4 flex items-center space-x-4">
|
||||
<span>Audio</span>
|
||||
<ChatBubble size={32} />
|
||||
</h1>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</script>
|
||||
|
||||
<Dialog {modalId}>
|
||||
<h1 class="header2 mb-4 flex items-center space-x-4">
|
||||
<h1 class="h3 mb-4 flex items-center space-x-4">
|
||||
<span>Subtitles</span>
|
||||
<TextAlignLeft size={32} />
|
||||
</h1>
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
<div class="text-secondary-300 font-medium text-wider text-xl mb-1 tracking-wide">
|
||||
{subtitle}
|
||||
</div>
|
||||
<h1 class="header4">{title}</h1>
|
||||
<h1 class="h1">{title}</h1>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
{#if subtitleInfo?.availableSubtitles?.length}
|
||||
|
||||
Reference in New Issue
Block a user