From dc4db405d2991d3ec6a114f3b08d3fccd057d3ee Mon Sep 17 00:00:00 2001 From: Wolfgang Müller Date: Thu, 13 Feb 2025 17:52:16 +0100 Subject: frontend: Migrate to Svelte 5 --- frontend/src/routes/comics/+page.svelte | 107 ++++++++-------- frontend/src/routes/comics/[id]/+page.svelte | 177 +++++++++++++-------------- 2 files changed, 135 insertions(+), 149 deletions(-) (limited to 'frontend/src/routes/comics') diff --git a/frontend/src/routes/comics/+page.svelte b/frontend/src/routes/comics/+page.svelte index 353d69c..372fd1a 100644 --- a/frontend/src/routes/comics/+page.svelte +++ b/frontend/src/routes/comics/+page.svelte @@ -3,10 +3,7 @@ import { comicsQuery } from '$gql/Queries'; import { type ComicFragment } from '$gql/graphql'; import { ComicSortLabel } from '$lib/Enums'; - import { ComicFilterContext, initFilterContext } from '$lib/Filter'; - import { initPaginationContext } from '$lib/Pagination'; - import { initSelectionContext } from '$lib/Selection'; - import { initSortContext } from '$lib/Sort'; + import { ComicFilterContext } from '$lib/Filter.svelte'; import Card, { comicCard } from '$lib/components/Card.svelte'; import Empty from '$lib/components/Empty.svelte'; import Guard from '$lib/components/Guard.svelte'; @@ -18,6 +15,7 @@ import Pagination from '$lib/pagination/Pagination.svelte'; import ComicPills from '$lib/pills/ComicPills.svelte'; import Selectable from '$lib/selection/Selectable.svelte'; + import { initSelectionContext } from '$lib/selection/Selection.svelte'; import SelectionOverlay from '$lib/selection/SelectionOverlay.svelte'; import DeleteSelection from '$lib/toolbar/DeleteSelection.svelte'; import EditSelection from '$lib/toolbar/EditSelection.svelte'; @@ -35,81 +33,82 @@ import ToggleAdvancedFilters from '$lib/toolbar/ToggleAdvancedFilters.svelte'; import Toolbar from '$lib/toolbar/Toolbar.svelte'; import { getContextClient } from '@urql/svelte'; - import type { PageData } from './$types'; + import type { PageProps } from './$types'; - export let data: PageData; + let { data }: PageProps = $props(); + let pagination = $derived(data.pagination); + let sort = $derived(data.sort); const client = getContextClient(); - - $: result = comicsQuery(client, { - pagination: data.pagination, - filter: data.filter, - sort: data.sort - }); - - $: comics = $result.data?.comics; + let result = $derived(comicsQuery(client, { ...data })); + let comics = $derived($result.data?.comics); const selection = initSelectionContext('Comic', (c) => c.title); - $: if (comics) { - $selection.view = comics.edges; - $pagination.total = comics.count; - } - - const filter = initFilterContext(); - $: $filter = new ComicFilterContext(data.filter); - - const sort = initSortContext(data.sort, ComicSortLabel); - $: $sort.update = data.sort; + $effect(() => { + if (comics) { + selection.view = comics.edges; + } + }); - const pagination = initPaginationContext(); - $: $pagination.update = data.pagination; + let filter = $state(new ComicFilterContext(data.filter)); + $effect(() => { + filter = new ComicFilterContext(data.filter); + }); - - - -
- -
- -
- - -
- - - + {#snippet start()} + + + +
+ +
+ +
+ + +
+ {/snippet} + {#snippet center({ expanded, toggle })} + +
- - - + + +
- - -
- + + + {/snippet} + {#snippet expansion()} + + {/snippet}
{#if comics} - +
{#each comics.edges as comic, index (comic.id)} - - - - - + + {#snippet children({ onclick, selected })} + + {#snippet overlay()} + + {/snippet} + + + {/snippet} {:else} {/each}
- + {:else} {/if} diff --git a/frontend/src/routes/comics/[id]/+page.svelte b/frontend/src/routes/comics/[id]/+page.svelte index cfc5840..48c588f 100644 --- a/frontend/src/routes/comics/[id]/+page.svelte +++ b/frontend/src/routes/comics/[id]/+page.svelte @@ -2,12 +2,14 @@ import { beforeNavigate } from '$app/navigation'; import { updateComics } from '$gql/Mutations'; import { comicQuery } from '$gql/Queries'; - import { comicEquals } from '$gql/Utils'; - import { UpdateMode, type FullComicFragment, type UpdateComicInput } from '$gql/graphql'; - import { initReaderContext } from '$lib/Reader'; - import { initScraperContext } from '$lib/Scraper'; - import { initSelectionContext } from '$lib/Selection'; - import { setTabContext } from '$lib/Tabs'; + import { omitIdentifiers, type OmitIdentifiers } from '$gql/Utils'; + import { + UpdateMode, + type FullComicFragment, + type PageFragment, + type UpdateComicInput + } from '$gql/graphql'; + import { comicPending } from '$lib/Form'; import { toastFinally } from '$lib/Toasts'; import { preventOnPending } from '$lib/Utils'; import BookmarkButton from '$lib/components/BookmarkButton.svelte'; @@ -21,155 +23,140 @@ import ComicForm from '$lib/forms/ComicForm.svelte'; import Gallery from '$lib/gallery/Gallery.svelte'; import PageView from '$lib/reader/PageView.svelte'; - import Reader from '$lib/reader/Reader.svelte'; + import Reader, { initReaderContext } from '$lib/reader/Reader.svelte'; import ComicScrapeForm from '$lib/scraper/ComicScrapeForm.svelte'; + import { initScraperContext } from '$lib/scraper/Scraper.svelte'; + import { initSelectionContext } from '$lib/selection/Selection.svelte'; import ComicDelete from '$lib/tabs/ComicDelete.svelte'; import ComicDetails from '$lib/tabs/ComicDetails.svelte'; import Tab from '$lib/tabs/Tab.svelte'; import Tabs from '$lib/tabs/Tabs.svelte'; import SelectionControls from '$lib/toolbar/SelectionControls.svelte'; import { getContextClient } from '@urql/svelte'; - import type { PageData } from './$types'; + import { untrack } from 'svelte'; + import type { PageProps } from './$types'; + let { data }: PageProps = $props(); const client = getContextClient(); const reader = initReaderContext(); - const selection = initSelectionContext(); const scraper = initScraperContext(); - const tabContext = setTabContext({ - tabs: { - details: { title: 'Details' }, - edit: { title: 'Edit' }, - scrape: { title: 'Scrape' }, - deletion: { title: 'Delete' } - }, - current: 'details' - }); + const selection = initSelectionContext('Page', (p) => p.path); - export let data: PageData; - $: result = comicQuery(client, { id: data.id }); - - let comic: FullComicFragment; - let original: Readonly; - let updatePartial = false; - - $: $result, update(); - $: pending = !comicEquals(comic, original); - $: $tabContext.tabs.edit.badge = pending; - - function update() { - if (!$result.stale && $result.data?.comic.__typename === 'FullComic') { - original = $result.data.comic; - if (updatePartial) { - comic.pages = structuredClone(original.pages); - comic.favourite = original.favourite; - comic.bookmarked = original.bookmarked; - comic.organized = original.organized; - comic.updatedAt = original.updatedAt; - updatePartial = false; - } else { - comic = structuredClone(original); - } - - $reader.pages = original.pages; - $selection.view = comic.pages; - $scraper.selector = undefined; - } + let comic: FullComicFragment | undefined = $state(); + let input: OmitIdentifiers | undefined = $state(); + let updateInput = $state(true); + + function invalidateInput() { + updateInput = true; } - function toggle(field: keyof Omit) { - updateComics(client, { ids: comic.id, input: { [field]: !comic[field] } }) - .then(() => (updatePartial = true)) - .catch(toastFinally); + function submit(input: UpdateComicInput) { + updateComics(client, { ids: data.id, input }).then(invalidateInput).catch(toastFinally); } - function updateComic(event: CustomEvent) { - updateComics(client, { ids: comic.id, input: event.detail }).catch(toastFinally); + function toggle(field: keyof Pick) { + if (!comic) return; + updateComics(client, { ids: data.id, input: { [field]: !comic[field] } }).catch(toastFinally); } - function updateCover(event: CustomEvent) { - updateComics(client, { ids: comic.id, input: { cover: { id: event.detail } } }) - .then(() => (updatePartial = true)) - .catch(toastFinally); + function updateCover(id: number) { + updateComics(client, { ids: data.id, input: { cover: { id } } }).catch(toastFinally); } function removePages() { updateComics(client, { - ids: comic.id, - input: { pages: { ids: $selection.ids, options: { mode: UpdateMode.Remove } } } + ids: data.id, + input: { pages: { ids: selection.ids, options: { mode: UpdateMode.Remove } } } }) - .then(() => { - updatePartial = true; - $selection = $selection.clear(); - }) + .then(selection.clear) .catch(toastFinally); } beforeNavigate((navigation) => preventOnPending(navigation, pending)); + let result = $derived(comicQuery(client, { id: data.id })); + let pending = $derived(comicPending(comic, input)); + + $effect(() => { + if (!$result.stale) { + untrack(() => { + if ($result.data?.comic.__typename === 'FullComic') { + comic = $result.data.comic; + + if (updateInput) { + input = omitIdentifiers($result.data.comic); + updateInput = false; + } + + reader.pages = comic.pages; + selection.view = comic.pages; + scraper.reset(); + } + }); + } + }); - + -{#if comic} +{#if comic && input}
toggle('favourite')} + title={comic.title} + subtitle={comic.originalTitle} + favourite={comic.favourite} + onfavourite={() => toggle('favourite')} />
- ($reader = $reader.open(e.detail))} - on:cover={updateCover} - /> +
- - -
- -
-
-
+ {#snippet sidebar()} + {#if input} + +
+ +
+
+ {/if} + {/snippet}
{:else} -- cgit v1.2.3-2-gb3c3