<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"> <!-- eslint-disable-next-line svelte/require-each-key --> {#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>