mirror of
https://github.com/maxdorninger/MediaManager.git
synced 2026-04-17 15:43:28 +02:00
work on the frontend for the movies
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
|
||||
import requests
|
||||
import tmdbsimple
|
||||
from pydantic_settings import BaseSettings
|
||||
from tmdbsimple import TV, TV_Seasons
|
||||
@@ -15,6 +16,7 @@ from media_manager.movies.schemas import Movie
|
||||
|
||||
|
||||
class TmdbConfig(BaseSettings):
|
||||
TMDB_RELAY_URL: str = "https://metadata-relay.maxid.me"
|
||||
TMDB_API_KEY: str | None = None
|
||||
|
||||
|
||||
@@ -31,6 +33,15 @@ class TmdbMetadataProvider(AbstractMetadataProvider):
|
||||
if config.TMDB_API_KEY is None:
|
||||
raise InvalidConfigError("TMDB_API_KEY is not set")
|
||||
tmdbsimple.API_KEY = config.TMDB_API_KEY
|
||||
self.url = config.TMDB_RELAY_URL
|
||||
|
||||
def __get_tv_metadata(self, id: int) -> dict:
|
||||
"""
|
||||
Helper function to get TV metadata from TMDB.
|
||||
:param id: The external ID of the TV show.
|
||||
:return: A dictionary containing the TV show metadata.
|
||||
"""
|
||||
return requests.get(url=f"{self.url}/tv/shows/{id}").json()
|
||||
|
||||
def download_show_poster_image(self, show: Show) -> bool:
|
||||
show_metadata = TV(show.external_id).info()
|
||||
|
||||
@@ -6,7 +6,7 @@ from pydantic import BaseModel, Field, ConfigDict, model_validator
|
||||
|
||||
from media_manager.auth.schemas import UserRead
|
||||
from media_manager.torrent.models import Quality
|
||||
from media_manager.torrent.schemas import TorrentId, Torrent
|
||||
from media_manager.torrent.schemas import TorrentId, Torrent, TorrentStatus
|
||||
|
||||
MovieId = typing.NewType("MovieId", UUID)
|
||||
MovieRequestId = typing.NewType("MovieRequestId", UUID)
|
||||
@@ -74,9 +74,19 @@ class RichMovieRequest(MovieRequest):
|
||||
movie: Movie
|
||||
|
||||
|
||||
class MovieTorrent(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
torrent_id: TorrentId
|
||||
torrent_title: str
|
||||
status: TorrentStatus
|
||||
quality: Quality
|
||||
imported: bool
|
||||
file_path_suffix: str
|
||||
|
||||
class RichMovieTorrent(BaseModel):
|
||||
movie_id: MovieId
|
||||
name: str
|
||||
year: int | None
|
||||
metadata_provider: str
|
||||
torrents: list[Torrent]
|
||||
torrents: list[MovieTorrent]
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<Table.Row>
|
||||
<Table.Cell class="font-medium">
|
||||
<a href={'/dashboard/torrents/' + torrent.torrent_id}>
|
||||
{torrent.torrent_title}
|
||||
{isShow ? torrent.torrent_title : torrent.title}
|
||||
</a>
|
||||
</Table.Cell>
|
||||
{#if isShow}
|
||||
|
||||
51
web/src/lib/components/ui/menubar/index.ts
Normal file
51
web/src/lib/components/ui/menubar/index.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import {Menubar as MenubarPrimitive} from "bits-ui";
|
||||
import Root from "./menubar.svelte";
|
||||
import CheckboxItem from "./menubar-checkbox-item.svelte";
|
||||
import Content from "./menubar-content.svelte";
|
||||
import Item from "./menubar-item.svelte";
|
||||
import GroupHeading from "./menubar-group-heading.svelte";
|
||||
import RadioItem from "./menubar-radio-item.svelte";
|
||||
import Separator from "./menubar-separator.svelte";
|
||||
import Shortcut from "./menubar-shortcut.svelte";
|
||||
import SubContent from "./menubar-sub-content.svelte";
|
||||
import SubTrigger from "./menubar-sub-trigger.svelte";
|
||||
import Trigger from "./menubar-trigger.svelte";
|
||||
|
||||
const Menu = MenubarPrimitive.Menu;
|
||||
const Group = MenubarPrimitive.Group;
|
||||
const Sub = MenubarPrimitive.Sub;
|
||||
const RadioGroup = MenubarPrimitive.RadioGroup;
|
||||
|
||||
export {
|
||||
Root,
|
||||
CheckboxItem,
|
||||
Content,
|
||||
Item,
|
||||
GroupHeading,
|
||||
RadioItem,
|
||||
Separator,
|
||||
Shortcut,
|
||||
SubContent,
|
||||
SubTrigger,
|
||||
Trigger,
|
||||
Menu,
|
||||
Group,
|
||||
Sub,
|
||||
RadioGroup,
|
||||
//
|
||||
Root as Menubar,
|
||||
CheckboxItem as MenubarCheckboxItem,
|
||||
Content as MenubarContent,
|
||||
Item as MenubarItem,
|
||||
GroupHeading as MenubarGroupHeading,
|
||||
RadioItem as MenubarRadioItem,
|
||||
Separator as MenubarSeparator,
|
||||
Shortcut as MenubarShortcut,
|
||||
SubContent as MenubarSubContent,
|
||||
SubTrigger as MenubarSubTrigger,
|
||||
Trigger as MenubarTrigger,
|
||||
Menu as MenubarMenu,
|
||||
Group as MenubarGroup,
|
||||
Sub as MenubarSub,
|
||||
RadioGroup as MenubarRadioGroup,
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import {Menubar as MenubarPrimitive, type WithoutChildrenOrChild} from "bits-ui";
|
||||
import Check from "@lucide/svelte/icons/check";
|
||||
import Minus from "@lucide/svelte/icons/minus";
|
||||
import {cn} from "$lib/utils.js";
|
||||
import type {Snippet} from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
checked = $bindable(false),
|
||||
indeterminate = $bindable(false),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<MenubarPrimitive.CheckboxItemProps> & {
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.CheckboxItem
|
||||
{...restProps}
|
||||
bind:checked
|
||||
bind:indeterminate
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{#snippet children({checked, indeterminate})}
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if indeterminate}
|
||||
<Minus class="size-4"/>
|
||||
{:else}
|
||||
<Check class={cn("size-4", !checked && "text-transparent")}/>
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
32
web/src/lib/components/ui/menubar/menubar-content.svelte
Normal file
32
web/src/lib/components/ui/menubar/menubar-content.svelte
Normal file
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import {Menubar as MenubarPrimitive} from "bits-ui";
|
||||
import {cn} from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
sideOffset = 8,
|
||||
alignOffset = -4,
|
||||
align = "start",
|
||||
side = "bottom",
|
||||
portalProps,
|
||||
...restProps
|
||||
}: MenubarPrimitive.ContentProps & {
|
||||
portalProps?: MenubarPrimitive.PortalProps;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Portal {...portalProps}>
|
||||
<MenubarPrimitive.Content
|
||||
{...restProps}
|
||||
{align}
|
||||
{alignOffset}
|
||||
bind:ref
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground z-50 min-w-[12rem] rounded-md border p-1 shadow-md focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{side}
|
||||
{sideOffset}
|
||||
/>
|
||||
</MenubarPrimitive.Portal>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import {Menubar as MenubarPrimitive} from "bits-ui";
|
||||
import {cn} from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
...restProps
|
||||
}: MenubarPrimitive.GroupHeadingProps & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.GroupHeading
|
||||
{...restProps}
|
||||
bind:ref
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
|
||||
/>
|
||||
23
web/src/lib/components/ui/menubar/menubar-item.svelte
Normal file
23
web/src/lib/components/ui/menubar/menubar-item.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import {Menubar as MenubarPrimitive} from "bits-ui";
|
||||
import {cn} from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
...restProps
|
||||
}: MenubarPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Item
|
||||
{...restProps}
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
30
web/src/lib/components/ui/menubar/menubar-radio-item.svelte
Normal file
30
web/src/lib/components/ui/menubar/menubar-radio-item.svelte
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import {Menubar as MenubarPrimitive, type WithoutChild} from "bits-ui";
|
||||
import Circle from "@lucide/svelte/icons/circle";
|
||||
import {cn} from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<MenubarPrimitive.RadioItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.RadioItem
|
||||
{...restProps}
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{#snippet children({checked})}
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if checked}
|
||||
<Circle class="size-2 fill-current"/>
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.({checked})}
|
||||
{/snippet}
|
||||
</MenubarPrimitive.RadioItem>
|
||||
16
web/src/lib/components/ui/menubar/menubar-separator.svelte
Normal file
16
web/src/lib/components/ui/menubar/menubar-separator.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import {Menubar as MenubarPrimitive} from "bits-ui";
|
||||
import {cn} from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: MenubarPrimitive.SeparatorProps = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Separator
|
||||
{...restProps}
|
||||
bind:ref
|
||||
class={cn("bg-muted -mx-1 my-1 h-px", className)}
|
||||
/>
|
||||
20
web/src/lib/components/ui/menubar/menubar-shortcut.svelte
Normal file
20
web/src/lib/components/ui/menubar/menubar-shortcut.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type {HTMLAttributes} from "svelte/elements";
|
||||
import type {WithElementRef} from "bits-ui";
|
||||
import {cn} from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
19
web/src/lib/components/ui/menubar/menubar-sub-content.svelte
Normal file
19
web/src/lib/components/ui/menubar/menubar-sub-content.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import {Menubar as MenubarPrimitive} from "bits-ui";
|
||||
import {cn} from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: MenubarPrimitive.SubContentProps = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.SubContent
|
||||
{...restProps}
|
||||
bind:ref
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground z-50 min-w-max rounded-md border p-1 shadow-lg focus:outline-none",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
28
web/src/lib/components/ui/menubar/menubar-sub-trigger.svelte
Normal file
28
web/src/lib/components/ui/menubar/menubar-sub-trigger.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import {Menubar as MenubarPrimitive} from "bits-ui";
|
||||
import ChevronRight from "@lucide/svelte/icons/chevron-right";
|
||||
import {cn} from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
}: MenubarPrimitive.SubTriggerProps & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.SubTrigger
|
||||
{...restProps}
|
||||
bind:ref
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronRight class="ml-auto size-4"/>
|
||||
</MenubarPrimitive.SubTrigger>
|
||||
19
web/src/lib/components/ui/menubar/menubar-trigger.svelte
Normal file
19
web/src/lib/components/ui/menubar/menubar-trigger.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import {Menubar as MenubarPrimitive} from "bits-ui";
|
||||
import {cn} from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: MenubarPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Trigger
|
||||
{...restProps}
|
||||
bind:ref
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
19
web/src/lib/components/ui/menubar/menubar.svelte
Normal file
19
web/src/lib/components/ui/menubar/menubar.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import {Menubar as MenubarPrimitive} from "bits-ui";
|
||||
import {cn} from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: MenubarPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Root
|
||||
{...restProps}
|
||||
bind:ref
|
||||
class={cn(
|
||||
"bg-background flex h-9 items-center space-x-1 rounded-md border p-1 shadow-sm",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
@@ -192,6 +192,14 @@ export interface RichShowTorrent {
|
||||
torrents: RichSeasonTorrent[];
|
||||
}
|
||||
|
||||
export interface RichMovieTorrent {
|
||||
show_id: string;
|
||||
name: string;
|
||||
year: number | null;
|
||||
metadata_provider: string;
|
||||
torrents: Torrent[];
|
||||
}
|
||||
|
||||
interface SeasonRequestBase {
|
||||
min_quality: Quality;
|
||||
wanted_quality: Quality;
|
||||
|
||||
145
web/src/routes/dashboard/movies/add-movie/+page.svelte
Normal file
145
web/src/routes/dashboard/movies/add-movie/+page.svelte
Normal file
@@ -0,0 +1,145 @@
|
||||
<script lang="ts">
|
||||
import {env} from '$env/dynamic/public';
|
||||
import {Separator} from '$lib/components/ui/separator/index.js';
|
||||
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
|
||||
import * as Breadcrumb from '$lib/components/ui/breadcrumb/index.js';
|
||||
import {Input} from '$lib/components/ui/input';
|
||||
import {Label} from '$lib/components/ui/label';
|
||||
import {Button} from '$lib/components/ui/button';
|
||||
import {ChevronDown} from 'lucide-svelte';
|
||||
import * as Collapsible from '$lib/components/ui/collapsible/index.js';
|
||||
import type {MetaDataProviderShowSearchResult} from '$lib/types.js';
|
||||
import * as RadioGroup from '$lib/components/ui/radio-group/index.js';
|
||||
import AddMediaCard from '$lib/components/add-media-card.svelte';
|
||||
import {toast} from 'svelte-sonner';
|
||||
import {onMount} from "svelte";
|
||||
import * as Menubar from "$lib/components/ui/menubar/index.js";
|
||||
|
||||
const apiUrl = env.PUBLIC_API_URL;
|
||||
let searchTerm: string = $state('');
|
||||
let metadataProvider: string = $state('tmdb');
|
||||
let results: MetaDataProviderShowSearchResult[] | null = $state(null);
|
||||
onMount(search)
|
||||
|
||||
async function search() {
|
||||
let url = new URL(apiUrl + '/movies/recommended');
|
||||
if (searchTerm.length > 0) {
|
||||
let url = new URL(apiUrl + '/movies/search');
|
||||
url.searchParams.append('query', searchTerm);
|
||||
url.searchParams.append('metadata_provider', metadataProvider);
|
||||
toast.info(`Searching for "${searchTerm}" using ${metadataProvider.toUpperCase()}...`);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: 'include'
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Search failed: ${response.status} ${errorText || response.statusText}`);
|
||||
}
|
||||
results = await response.json();
|
||||
if (searchTerm.length === 0) {
|
||||
return
|
||||
}
|
||||
if (results && results.length > 0) {
|
||||
toast.success(`Found ${results.length} result(s) for "${searchTerm}".`);
|
||||
} else {
|
||||
toast.info(`No results found for "${searchTerm}".`);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : 'An unknown error occurred during search.';
|
||||
console.error('Search error:', error);
|
||||
toast.error(errorMessage);
|
||||
results = null; // Clear previous results on error
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="flex h-16 shrink-0 items-center gap-2">
|
||||
<div class="flex items-center gap-2 px-4">
|
||||
<Sidebar.Trigger class="-ml-1"/>
|
||||
<Separator class="mr-2 h-4" orientation="vertical"/>
|
||||
<Breadcrumb.Root>
|
||||
<Breadcrumb.List>
|
||||
<Breadcrumb.Item class="hidden md:block">
|
||||
<Breadcrumb.Link href="/dashboard">MediaManager</Breadcrumb.Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Separator class="hidden md:block"/>
|
||||
<Breadcrumb.Item>
|
||||
<Breadcrumb.Link href="/dashboard">Home</Breadcrumb.Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Separator class="hidden md:block"/>
|
||||
<Breadcrumb.Item>
|
||||
<Breadcrumb.Link href="/dashboard/movies">Movies</Breadcrumb.Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Separator class="hidden md:block"/>
|
||||
<Breadcrumb.Item>
|
||||
<Breadcrumb.Page>Add a Movie</Breadcrumb.Page>
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb.List>
|
||||
</Breadcrumb.Root>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex w-full max-w-[90vw] flex-1 flex-col items-center gap-4 p-4 pt-0">
|
||||
<div class="grid w-full max-w-sm items-center gap-12">
|
||||
<h1 class="scroll-m-20 text-center text-4xl font-extrabold tracking-tight lg:text-5xl">
|
||||
Add a Movie
|
||||
</h1>
|
||||
<section>
|
||||
<Label for="search-box">Movie Name</Label>
|
||||
<Input bind:value={searchTerm} id="search-box" placeholder="Show Name" type="text"/>
|
||||
<p class="text-sm text-muted-foreground">Search for a Movie to add.</p>
|
||||
</section>
|
||||
<section>
|
||||
<Collapsible.Root class="w-full space-y-1">
|
||||
<Collapsible.Trigger>
|
||||
<div class="flex items-center justify-between space-x-4 px-4">
|
||||
<h4 class="text-sm font-semibold">Advanced Settings</h4>
|
||||
<Button class="w-9 p-0" size="sm" variant="ghost">
|
||||
<ChevronDown/>
|
||||
<span class="sr-only">Toggle</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content class="space-y-1">
|
||||
<Label for="metadata-provider-selector">Choose which Metadata Provider to query.</Label>
|
||||
<RadioGroup.Root bind:value={metadataProvider} id="metadata-provider-selector">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item id="option-one" value="tmdb"/>
|
||||
<Label for="option-one">TMDB (Recommended)</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item id="option-two" value="tvdb"/>
|
||||
<Label for="option-two">TVDB</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
</section>
|
||||
<section>
|
||||
<Button onclick={search} type="submit">Search</Button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<Separator class="my-8"/>
|
||||
|
||||
{#if results != null}
|
||||
{#if results.length === 0}
|
||||
<h3 class="mx-auto">No Shows found.</h3>
|
||||
{:else}
|
||||
<div
|
||||
class="grid w-full auto-rows-min gap-4 sm:grid-cols-1
|
||||
md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5"
|
||||
>
|
||||
{#each results as result}
|
||||
<AddMediaCard result={result} isShow={false}/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -13,6 +13,7 @@
|
||||
import AddMediaCard from '$lib/components/add-media-card.svelte';
|
||||
import {toast} from 'svelte-sonner';
|
||||
import {onMount} from "svelte";
|
||||
import * as Menubar from "$lib/components/ui/menubar/index.js";
|
||||
|
||||
const apiUrl = env.PUBLIC_API_URL;
|
||||
let searchTerm: string = $state('');
|
||||
@@ -86,7 +87,7 @@
|
||||
<div class="flex w-full max-w-[90vw] flex-1 flex-col items-center gap-4 p-4 pt-0">
|
||||
<div class="grid w-full max-w-sm items-center gap-12">
|
||||
<h1 class="scroll-m-20 text-center text-4xl font-extrabold tracking-tight lg:text-5xl">
|
||||
Add a show
|
||||
Add a Show
|
||||
</h1>
|
||||
<section>
|
||||
<Label for="search-box">Show Name</Label>
|
||||
@@ -94,7 +95,7 @@
|
||||
<p class="text-sm text-muted-foreground">Search for a Show to add.</p>
|
||||
</section>
|
||||
<section>
|
||||
<Collapsible.Root class="w-[350px] space-y-2">
|
||||
<Collapsible.Root class="w-full space-y-1">
|
||||
<Collapsible.Trigger>
|
||||
<div class="flex items-center justify-between space-x-4 px-4">
|
||||
<h4 class="text-sm font-semibold">Advanced Settings</h4>
|
||||
@@ -104,7 +105,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content class="space-y-2">
|
||||
<Collapsible.Content class="space-y-1">
|
||||
<Label for="metadata-provider-selector">Choose which Metadata Provider to query.</Label>
|
||||
<RadioGroup.Root bind:value={metadataProvider} id="metadata-provider-selector">
|
||||
<div class="flex items-center space-x-2">
|
||||
|
||||
8
web/web.iml
Normal file
8
web/web.iml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Reference in New Issue
Block a user