mirror of
https://github.com/maxdorninger/MediaManager.git
synced 2026-04-22 05:45:14 +02:00
add ability to import unknown tv shows
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
|
||||
let {
|
||||
directory,
|
||||
isTv,
|
||||
children
|
||||
}: {
|
||||
directory: string;
|
||||
isTv: boolean;
|
||||
children: any;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Card.Root class="col-span-full flex h-full flex-col overflow-x-hidden sm:col-span-1">
|
||||
<Card.Header>
|
||||
<Card.Title class="flex h-12 items-center leading-tight">
|
||||
An importable {isTv ? 'TV show' : 'movie'} was detected!
|
||||
</Card.Title>
|
||||
<Card.Description>
|
||||
The detected {isTv ? 'TV show' : 'movie'} is in this directory:
|
||||
<code
|
||||
class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
|
||||
>
|
||||
{directory}
|
||||
</code>
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content class="flex flex-1 items-center justify-center">
|
||||
{@render children?.()}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script lang="ts">
|
||||
import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
|
||||
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import client from '$lib/api';
|
||||
import type { components } from '$lib/api/api';
|
||||
import { Spinner } from '$lib/components/ui/spinner';
|
||||
import SuggestedMediaCard from '$lib/components/import-media/suggested-media-card.svelte';
|
||||
|
||||
let {
|
||||
isTv,
|
||||
name,
|
||||
candidates,
|
||||
children
|
||||
}: {
|
||||
isTv: boolean;
|
||||
name: string;
|
||||
candidates: components['schemas']['MetaDataProviderSearchResult'][];
|
||||
children: any;
|
||||
} = $props();
|
||||
let dialogOpen = $state(false);
|
||||
let submitRequestError = $state<string | null>(null);
|
||||
let isImporting = $state<boolean>(false);
|
||||
|
||||
async function handleImportMedia(media: components['schemas']['MetaDataProviderSearchResult']) {
|
||||
isImporting = true;
|
||||
submitRequestError = null;
|
||||
|
||||
let { data } = await client.POST('/api/v1/tv/shows', {
|
||||
params: {
|
||||
query: {
|
||||
metadata_provider: media.metadata_provider as 'tmdb' | 'tvdb',
|
||||
show_id: media.external_id
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log('oida:', data);
|
||||
let showId = data?.id ?? 'no_id';
|
||||
const { error } = await client.POST('/api/v1/tv/importable/{show_id}', {
|
||||
params: {
|
||||
path: {
|
||||
show_id: showId
|
||||
},
|
||||
query: {
|
||||
directory: name
|
||||
}
|
||||
}
|
||||
});
|
||||
isImporting = false;
|
||||
|
||||
if (error) {
|
||||
toast.error('Failed to import');
|
||||
} else {
|
||||
dialogOpen = false;
|
||||
toast.success('Imported successfully!');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open={dialogOpen}>
|
||||
<Dialog.Trigger
|
||||
class={buttonVariants({ variant: 'default' })}
|
||||
onclick={() => {
|
||||
dialogOpen = true;
|
||||
}}
|
||||
>
|
||||
{@render children?.()}
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Content class="max-h-[90vh] w-fit min-w-[80vw] overflow-y-auto">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Import unknown {isTv ? 'show' : 'movie'} "{name}"</Dialog.Title>
|
||||
<Dialog.Description
|
||||
>Select the {isTv ? 'show' : 'movie'} that is in this directory to import it!
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<div
|
||||
class="grid w-full auto-rows-min gap-4 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-4"
|
||||
>
|
||||
{#if !isImporting}
|
||||
{#each candidates as candidate (candidate.external_id)}
|
||||
<SuggestedMediaCard result={candidate} action={() => handleImportMedia(candidate)}
|
||||
></SuggestedMediaCard>
|
||||
{:else}
|
||||
No {isTv ? 'shows' : 'movies'} were found, change the directory's name for better search results!
|
||||
{/each}
|
||||
{:else}
|
||||
<Spinner class="size-8"></Spinner>
|
||||
{/if}
|
||||
{#if submitRequestError}
|
||||
<p class="col-span-full text-center text-sm text-red-500">{submitRequestError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button disabled={isImporting} onclick={() => (dialogOpen = false)} variant="outline"
|
||||
>Cancel
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,58 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import { ImageOff } from 'lucide-svelte';
|
||||
import type { components } from '$lib/api/api';
|
||||
|
||||
let {
|
||||
result,
|
||||
action
|
||||
}: {
|
||||
result: components['schemas']['MetaDataProviderSearchResult'];
|
||||
action: () => void;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Card.Root class="col-span-full flex h-full flex-col overflow-x-hidden sm:col-span-1">
|
||||
<Card.Header>
|
||||
<Card.Title class="flex h-12 items-center leading-tight">
|
||||
{result.name}
|
||||
{#if result.year != null}
|
||||
({result.year})
|
||||
{/if}
|
||||
</Card.Title>
|
||||
<Card.Description class="truncate">
|
||||
{result.overview !== '' ? result.overview : 'No overview available'}
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content class="flex flex-1 items-center justify-center">
|
||||
{#if result.poster_path != null}
|
||||
<img
|
||||
class="h-full w-full rounded-lg object-contain"
|
||||
src={result.poster_path}
|
||||
alt="{result.name}'s Poster Image"
|
||||
/>
|
||||
{:else}
|
||||
<div class="flex h-full w-full items-center justify-center">
|
||||
<ImageOff class="h-12 w-12 text-gray-400" />
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
<Card.Footer class="flex flex-col items-start gap-2 rounded-b-lg border-t bg-card p-4">
|
||||
<Button class="w-full font-semibold" onclick={() => action()}>
|
||||
Import using this metadata
|
||||
</Button>
|
||||
<div class="flex w-full items-center gap-2">
|
||||
{#if result.vote_average != null}
|
||||
<span class="flex items-center text-sm font-medium text-yellow-600">
|
||||
<svg class="mr-1 h-4 w-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"
|
||||
><path
|
||||
d="M10 15l-5.878 3.09 1.122-6.545L.488 6.91l6.561-.955L10 0l2.951 5.955 6.561.955-4.756 4.635 1.122 6.545z"
|
||||
/></svg
|
||||
>
|
||||
Rating: {Math.round(result.vote_average)}/10
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
Reference in New Issue
Block a user