summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/lib/scraper
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--frontend/src/lib/scraper/ComicScrapeForm.svelte138
-rw-r--r--frontend/src/lib/scraper/components/SelectorButton.svelte22
-rw-r--r--frontend/src/lib/scraper/components/SelectorGroup.svelte35
-rw-r--r--frontend/src/lib/scraper/components/SelectorItem.svelte24
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>