diff --git a/web/src/lib/api/api.d.ts b/web/src/lib/api/api.d.ts index c30a019..33fa0ad 100644 --- a/web/src/lib/api/api.d.ts +++ b/web/src/lib/api/api.d.ts @@ -341,6 +341,46 @@ export interface paths { patch?: never; trace?: never; }; + '/api/v1/tv/importable': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get All Importable Shows + * @description get a list of unknown shows that were detected in the tv directory and are importable + */ + get: operations['get_all_importable_shows_api_v1_tv_importable_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/tv/importable/{show_id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Import Detected Show + * @description get a list of unknown shows that were detected in the tv directory and are importable + */ + post: operations['import_detected_show_api_v1_tv_importable__show_id__post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/api/v1/tv/shows/torrents': { parameters: { query?: never; @@ -1625,6 +1665,16 @@ export interface components { * @enum {integer} */ TorrentStatus: 1 | 2 | 3 | 4; + /** TvShowImportSuggestion */ + TvShowImportSuggestion: { + /** + * Directory + * Format: path + */ + directory: string; + /** Candidates */ + candidates: components['schemas']['MetaDataProviderSearchResult'][]; + }; /** UpdateSeasonRequest */ UpdateSeasonRequest: { min_quality: components['schemas']['Quality']; @@ -2489,15 +2539,6 @@ export interface operations { 'application/json': components['schemas']['Show']; }; }; - /** @description Show already exists */ - 409: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': string; - }; - }; /** @description Validation Error */ 422: { headers: { @@ -2591,6 +2632,69 @@ export interface operations { }; }; }; + get_all_importable_shows_api_v1_tv_importable_get: { + parameters: { + query?: { + metadata_provider?: 'tmdb' | 'tvdb'; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['TvShowImportSuggestion'][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + import_detected_show_api_v1_tv_importable__show_id__post: { + parameters: { + query: { + directory: string; + }; + header?: never; + path: { + /** @description The ID of the show */ + show_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; get_shows_with_torrents_api_v1_tv_shows_torrents_get: { parameters: { query?: never; diff --git a/web/src/lib/components/import-media/detected-media-card.svelte b/web/src/lib/components/import-media/detected-media-card.svelte new file mode 100644 index 0000000..b14003a --- /dev/null +++ b/web/src/lib/components/import-media/detected-media-card.svelte @@ -0,0 +1,32 @@ + + + + + + An importable {isTv ? 'TV show' : 'movie'} was detected! + + + The detected {isTv ? 'TV show' : 'movie'} is in this directory: + + {directory} + + + + + {@render children?.()} + + diff --git a/web/src/lib/components/import-media/import-candidates-dialog.svelte b/web/src/lib/components/import-media/import-candidates-dialog.svelte new file mode 100644 index 0000000..0237626 --- /dev/null +++ b/web/src/lib/components/import-media/import-candidates-dialog.svelte @@ -0,0 +1,99 @@ + + + + { + dialogOpen = true; + }} + > + {@render children?.()} + + + + Import unknown {isTv ? 'show' : 'movie'} "{name}" + Select the {isTv ? 'show' : 'movie'} that is in this directory to import it! + + +
+ {#if !isImporting} + {#each candidates as candidate (candidate.external_id)} + handleImportMedia(candidate)} + > + {:else} + No {isTv ? 'shows' : 'movies'} were found, change the directory's name for better search results! + {/each} + {:else} + + {/if} + {#if submitRequestError} +

{submitRequestError}

+ {/if} +
+ + + +
+
diff --git a/web/src/lib/components/import-media/suggested-media-card.svelte b/web/src/lib/components/import-media/suggested-media-card.svelte new file mode 100644 index 0000000..f8512d5 --- /dev/null +++ b/web/src/lib/components/import-media/suggested-media-card.svelte @@ -0,0 +1,58 @@ + + + + + + {result.name} + {#if result.year != null} + ({result.year}) + {/if} + + + {result.overview !== '' ? result.overview : 'No overview available'} + + + + {#if result.poster_path != null} + {result.name}'s Poster Image + {:else} +
+ +
+ {/if} +
+ + +
+ {#if result.vote_average != null} + + + Rating: {Math.round(result.vote_average)}/10 + + {/if} +
+
+
diff --git a/web/src/routes/dashboard/tv/+page.svelte b/web/src/routes/dashboard/tv/+page.svelte index 35f4207..8a78303 100644 --- a/web/src/routes/dashboard/tv/+page.svelte +++ b/web/src/routes/dashboard/tv/+page.svelte @@ -7,8 +7,11 @@ import { getFullyQualifiedMediaName } from '$lib/utils'; import MediaPicture from '$lib/components/media-picture.svelte'; import { resolve } from '$app/paths'; + import ImportCandidatesDialog from '$lib/components/import-media/import-candidates-dialog.svelte'; + import DetectedMediaCard from '$lib/components/import-media/detected-media-card.svelte'; let tvShows = page.data.tvShows; + let importables = page.data.importable; @@ -37,10 +40,27 @@ -
+

TV Shows

+ {#if importables.length > 0} +
+ {#each importables as importable (importable.directory)} + + + Import TV show + + + {/each} +
+ {/if}
diff --git a/web/src/routes/dashboard/tv/+page.ts b/web/src/routes/dashboard/tv/+page.ts index 15c7172..f2eb07e 100644 --- a/web/src/routes/dashboard/tv/+page.ts +++ b/web/src/routes/dashboard/tv/+page.ts @@ -2,6 +2,7 @@ import client from '$lib/api'; import type { PageLoad } from './$types'; export const load: PageLoad = async ({ fetch }) => { - const { data } = await client.GET('/api/v1/tv/shows', { fetch: fetch }); - return { tvShows: data }; + const tvShows = await client.GET('/api/v1/tv/shows', { fetch: fetch }); + const importable = await client.GET('/api/v1/tv/importable', { fetch: fetch }); + return { tvShows: tvShows.data, importable: importable.data }; };