summaryrefslogblamecommitdiffstatshomepage
path: root/frontend/src/lib/scraper/ComicScrapeForm.svelte
blob: 6cc345132992b3095788e28c887d99d4d986b435 (plain) (tree)
1
2
3
4
5
6
7
8
9
10



                                                                       
                                                                                                


                                                               

                                                                                   





                                                                      



                                         
 


                                                  
 

                                                                                    
 


                                             
 


                                                                               



                                                          





















                                                                                                                 
 

                                                                                                          
                                     

                                                








                                                                 
                                        





                                                                                 
                                                                            

                                          
                                                                                                                  





                              

                                                 


                                                                                                                    
                                                                           




                                                                  
                                                 



                                                                                                        
                                                        
                                                                                 














                                                                                                                                     




















                                                                                                                                 
<script lang="ts">
	import { upsertComics } from '$gql/Mutations';
	import { comicScrapersQuery, scrapeComic } from '$gql/Queries';
	import { isError } from '$gql/Utils';
	import { OnMissing, type FullComicFragment, type ScrapeComicQuery } from '$gql/graphql';
	import { toastError, toastFinally } from '$lib/Toasts';
	import Select from '$lib/components/Select.svelte';
	import Spinner from '$lib/components/Spinner.svelte';
	import { getContextClient, type OperationResult } from '@urql/svelte';
	import { getScraperContext, ScrapedComicSelector } from './Scraper.svelte';
	import SelectorGroup from './components/SelectorGroup.svelte';
	import SelectorItem from './components/SelectorItem.svelte';

	let client = getContextClient();
	const context = getScraperContext();

	interface Props {
		comic: FullComicFragment;
		onupsert: () => void;
	}

	let { comic, onupsert }: Props = $props();
	let createMissing = $state(false);
	let loading = $state(false);

	let scrapersResult = $derived(comicScrapersQuery(client, { id: comic.id }));
	let scrapers = $derived($scrapersResult.data?.comicScrapers);

	function scrape(event: SubmitEvent) {
		event.preventDefault();
		if (!context.scraper) return;

		loading = true;
		scrapeComic(client, { id: comic.id, scraper: context.scraper })
			.then(handleScrapeResult)
			.catch(toastFinally)
			.finally(() => (loading = false));
	}

	function handleScrapeResult(result: OperationResult<ScrapeComicQuery>) {
		if (result.error) {
			toastError(result.error.message);
			return;
		}

		if (result.data) {
			if (isError(result.data.scrapeComic)) {
				toastError(result.data.scrapeComic.message);
				return;
			}

			if (result.data.scrapeComic.__typename === 'ScrapeComicResult') {
				context.selector = new ScrapedComicSelector(result.data.scrapeComic.data, comic);
				context.warnings = result.data.scrapeComic.warnings;
			}
		}
	}

	function upsert(event: SubmitEvent) {
		event.preventDefault();
		if (!context.selector) return;

		const input = context.selector.input(createMissing ? OnMissing.Create : OnMissing.Ignore);
		upsertComics(client, { ids: comic.id, input })
			.then(() => {
				onupsert();
				context.reset();
			})
			.catch(toastFinally);
	}
</script>

<div class="flex flex-col gap-4 text-sm">
	{#if scrapers && scrapers.length === 0}
		<h2 class="text-base">No scrapers available.</h2>
	{:else}
		<form onsubmit={scrape}>
			<div class="grid grid-cols-6 gap-2">
				<div class="col-span-5">
					<Select
						id="scrapers"
						options={scrapers}
						placeholder={'Select scraper...'}
						bind:value={context.scraper}
					/>
				</div>
				<button type="submit" disabled={!context.scraper} class="btn-blue">Scrape</button>
			</div>
		</form>
	{/if}

	{#if loading}
		<Spinner />
	{:else if context.selector}
		{#if context.warnings.length > 0}
			<div class="flex flex-col gap-2">
				<h2 class="flex gap-1 border-b border-slate-700 text-base font-medium">Warnings</h2>
				<ul class="ml-2 list-inside list-disc">
					{#each context.warnings as warning}
						<li>{warning}</li>
					{/each}
				</ul>
			</div>
		{/if}
		{#if !context.selector.pending()}
			<h2 class="text-base">No data to merge.</h2>
		{:else}
			<div class="flex flex-col gap-2">
				<h2 class="border-b border-slate-700 text-base font-medium">Results</h2>
				<form onsubmit={upsert}>
					<div class="grid grid-cols-6 gap-4 pb-2">
						<SelectorItem title="Title" selector={context.selector.title} />
						<SelectorItem title="Original Title" selector={context.selector.originalTitle} />
						<SelectorItem title="URL" selector={context.selector.url} />
						<SelectorItem title="Date" selector={context.selector.date} --span="2" />
						<SelectorItem title="Category" selector={context.selector.category} --span="2" />
						<SelectorItem title="Language" selector={context.selector.language} --span="2" />
						<SelectorItem title="Rating" selector={context.selector.rating} --span="2" />
						<SelectorItem title="Censorship" selector={context.selector.censorship} --span="2" />
						<SelectorItem title="Direction" selector={context.selector.direction} --span="2" />
						<SelectorItem title="Layout" selector={context.selector.layout} --span="2" />
						<SelectorGroup title="Artists" selectors={context.selector.artists} />
						<SelectorGroup title="Circles" selectors={context.selector.circles} />
						<SelectorGroup title="Characters" selectors={context.selector.characters} />
						<SelectorGroup title="Worlds" selectors={context.selector.worlds} />
						<SelectorGroup title="Tags" selectors={context.selector.tags} />
					</div>
					<div class="flex flex-col gap-2">
						<h2 class="border-b border-slate-700 text-base font-medium">Options</h2>
						<div class="flex items-center gap-1">
							<input
								class="h-4 w-4"
								type="checkbox"
								id="create-missing"
								bind:checked={createMissing}
							/>
							<label class="shrink-0" for="create-missing">Create missing items</label>
						</div>
					</div>
					<div class="flex gap-4">
						<button type="submit" class="btn-blue">Merge</button>
					</div>
				</form>
			</div>
		{/if}
	{/if}
</div>