diff options
author | Wolfgang Müller | 2024-03-05 18:08:09 +0100 |
---|---|---|
committer | Wolfgang Müller | 2024-03-05 19:25:59 +0100 |
commit | d1d654ebac2d51e3841675faeb56480e440f622f (patch) | |
tree | 56ef123c1a15a10dfd90836e4038e27efde950c6 /frontend/src/lib/scraper | |
download | hircine-0.1.0.tar.gz |
Initial commit0.1.0
Diffstat (limited to 'frontend/src/lib/scraper')
4 files changed, 219 insertions, 0 deletions
diff --git a/frontend/src/lib/scraper/ComicScrapeForm.svelte b/frontend/src/lib/scraper/ComicScrapeForm.svelte new file mode 100644 index 0000000..30ad89b --- /dev/null +++ b/frontend/src/lib/scraper/ComicScrapeForm.svelte @@ -0,0 +1,138 @@ +<script lang="ts"> + import { upsertComics } from '$gql/Mutations'; + import { comicScrapersQuery, scrapeComic } from '$gql/Queries'; + import { isError } from '$gql/Utils'; + import { OnMissing, type FullComicFragment } from '$gql/graphql'; + import { ScrapedComicSelector, getScraperContext } from '$lib/Scraper'; + import { toastError, toastFinally } from '$lib/Toasts'; + import Select from '$lib/components/Select.svelte'; + import Spinner from '$lib/components/Spinner.svelte'; + import { getContextClient } from '@urql/svelte'; + import SelectorGroup from './components/SelectorGroup.svelte'; + import SelectorItem from './components/SelectorItem.svelte'; + + let client = getContextClient(); + const context = getScraperContext(); + + export let comic: FullComicFragment; + let createMissing = false; + let loading = false; + + $: scrapersResult = comicScrapersQuery(client, { id: comic.id }); + $: scrapers = $scrapersResult.data?.comicScrapers; + + function scrape() { + loading = true; + scrapeComic(client, { id: comic.id, scraper: $context.scraper }) + .then((result) => { + 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; + } + } + }) + .catch(toastFinally) + .finally(() => (loading = false)); + } + + function updateFromScrape(createMissing: boolean) { + if (!$context.selector) return; + + upsertComics(client, { + ids: comic.id, + input: $context.selector.toInput(createMissing ? OnMissing.Create : OnMissing.Ignore) + }) + .then(() => { + $context.selector = undefined; + $context.warnings = []; + }) + .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 on:submit|preventDefault={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.hasData()} + <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 on:submit|preventDefault={() => updateFromScrape(createMissing)}> + <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> diff --git a/frontend/src/lib/scraper/components/SelectorButton.svelte b/frontend/src/lib/scraper/components/SelectorButton.svelte new file mode 100644 index 0000000..b786f89 --- /dev/null +++ b/frontend/src/lib/scraper/components/SelectorButton.svelte @@ -0,0 +1,22 @@ +<script lang="ts"> + import { Selector } from '$lib/Scraper'; + + export let selector: Selector<string>; +</script> + +<button + type="button" + class="ml-1 flex rounded-sm border-slate-700 bg-slate-900 hover:brightness-110" + on:click={() => (selector.keep = !selector.keep)} +> + <div class="flex self-center pl-1"> + {#if selector.keep} + <span class="icon-base icon-[material-symbols--check] text-green-400" /> + {:else} + <span class="icon-base icon-[material-symbols--close] text-red-400" /> + {/if} + </div> + <p class:opacity-50={!selector.keep} class="p-1 text-left"> + {selector} + </p> +</button> diff --git a/frontend/src/lib/scraper/components/SelectorGroup.svelte b/frontend/src/lib/scraper/components/SelectorGroup.svelte new file mode 100644 index 0000000..ae7287a --- /dev/null +++ b/frontend/src/lib/scraper/components/SelectorGroup.svelte @@ -0,0 +1,35 @@ +<script lang="ts"> + import { Selector } from '$lib/Scraper'; + import SelectorButton from './SelectorButton.svelte'; + + export let title: string; + export let selectors: Selector<string>[]; + + function invert() { + for (let selector of selectors) { + selector.keep = !selector.keep; + } + selectors = selectors; + } +</script> + +{#if selectors.length > 0} + <div class="group col-span-6 flex flex-col gap-1"> + <div class="flex gap-2"> + <h2>{title}</h2> + <button + type="button" + class="flex items-end opacity-0 brightness-75 transition-opacity hover:brightness-110 group-hover:opacity-100" + on:click={invert} + title="Invert selection" + > + <span class="icon-xs icon-[material-symbols--compare-arrows]"></span> + </button> + </div> + <div class="flex flex-wrap gap-y-1"> + {#each selectors as selector} + <SelectorButton {selector} /> + {/each} + </div> + </div> +{/if} diff --git a/frontend/src/lib/scraper/components/SelectorItem.svelte b/frontend/src/lib/scraper/components/SelectorItem.svelte new file mode 100644 index 0000000..dd3f5b4 --- /dev/null +++ b/frontend/src/lib/scraper/components/SelectorItem.svelte @@ -0,0 +1,24 @@ +<script lang="ts"> + import { Selector } from '$lib/Scraper'; + import SelectorButton from './SelectorButton.svelte'; + + export let title: string; + export let selector: Selector<string> | undefined; +</script> + +{#if selector} + <div class="flex flex-col gap-1"> + <h2>{title}</h2> + <SelectorButton {selector} /> + </div> +{/if} + +<style> + :root { + --span: 6; + } + + div { + grid-column: span var(--span) / span var(--span); + } +</style> |