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/+layout.svelte | 60 +++++---- frontend/src/routes/+page.svelte | 8 +- frontend/src/routes/archives/+page.svelte | 131 +++++++++--------- frontend/src/routes/archives/[id]/+page.svelte | 65 ++++----- frontend/src/routes/artists/+page.svelte | 88 ++++++------ frontend/src/routes/characters/+page.svelte | 88 ++++++------ frontend/src/routes/circles/+page.svelte | 88 ++++++------ frontend/src/routes/comics/+page.svelte | 107 ++++++++------- frontend/src/routes/comics/[id]/+page.svelte | 177 ++++++++++++------------- frontend/src/routes/namespaces/+page.svelte | 88 ++++++------ frontend/src/routes/statistics/+page.svelte | 4 +- frontend/src/routes/tags/+page.svelte | 100 +++++++------- frontend/src/routes/worlds/+page.svelte | 91 ++++++------- 13 files changed, 528 insertions(+), 567 deletions(-) (limited to 'frontend/src/routes') diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 6af3b88..29a1c16 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -1,5 +1,6 @@ diff --git a/frontend/src/routes/archives/+page.svelte b/frontend/src/routes/archives/+page.svelte index 545058a..3fc4ed4 100644 --- a/frontend/src/routes/archives/+page.svelte +++ b/frontend/src/routes/archives/+page.svelte @@ -3,10 +3,7 @@ import { archivesQuery } from '$gql/Queries'; import type { ArchiveFragment } from '$gql/graphql'; import { ArchiveSortLabel } from '$lib/Enums'; - import { ArchiveFilterContext, initFilterContext } from '$lib/Filter'; - import { initPaginationContext } from '$lib/Pagination'; - import { initSelectionContext } from '$lib/Selection'; - import { initSortContext } from '$lib/Sort'; + import { ArchiveFilterContext } from '$lib/Filter.svelte'; import Card from '$lib/components/Card.svelte'; import Empty from '$lib/components/Empty.svelte'; import Guard from '$lib/components/Guard.svelte'; @@ -17,6 +14,7 @@ import Pagination from '$lib/pagination/Pagination.svelte'; import Pill from '$lib/pills/Pill.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 FilterOrganized from '$lib/toolbar/FilterOrganized.svelte'; @@ -29,90 +27,91 @@ import Toolbar from '$lib/toolbar/Toolbar.svelte'; import { getContextClient } from '@urql/svelte'; import { filesize } from 'filesize'; - import type { PageData } from './$types'; + import type { PageProps } from './$types'; - let client = getContextClient(); + let { data }: PageProps = $props(); + let pagination = $derived(data.pagination); + let sort = $derived(data.sort); - export let data: PageData; + const client = getContextClient(); + let result = $derived(archivesQuery(client, { ...data })); + let archives = $derived($result.data?.archives); - $: result = archivesQuery(client, { - pagination: data.pagination, - filter: data.filter, - sort: data.sort + let selection = initSelectionContext('Archive', (a) => a.name); + $effect(() => { + if (archives) { + selection.view = archives.edges; + } }); - $: archives = $result.data?.archives; - - const selection = initSelectionContext('Archive', (a) => a.name); - $: if (archives) { - $selection.view = archives.edges; - $pagination.total = archives.count; - } - - const pagination = initPaginationContext(); - $: $pagination.update = data.pagination; - - const filter = initFilterContext(); - $: $filter = new ArchiveFilterContext(data.filter); - - const sort = initSortContext(data.sort, ArchiveSortLabel); - $: $sort.update = data.sort; - - function refresh() { - result.reexecute({ requestPolicy: 'network-only' }); - } + let filter = $state(new ArchiveFilterContext(data.filter)); + $effect(() => { + filter = new ArchiveFilterContext(data.filter); + }); - - - - - - - - - - - - - + {#snippet start()} + + + + + + + {/snippet} + {#snippet center()} + + + + + {/snippet} + {#snippet end()} + result.reexecute({ requestPolicy: 'network-only' })} /> + {/snippet} {#if archives} - +
{#each archives.edges as { id, name, cover, size, pageCount }, index (id)} - - - -
- - - - - - -
-
+ + {#snippet children({ onclick, selected })} + + {#snippet overlay()} + + {/snippet} +
+ + {#snippet icon()} + + {/snippet} + + + {#snippet icon()} + + {/snippet} + +
+
+ {/snippet}
{:else} {/each}
- + {:else} {/if} diff --git a/frontend/src/routes/archives/[id]/+page.svelte b/frontend/src/routes/archives/[id]/+page.svelte index 50a2940..56c3273 100644 --- a/frontend/src/routes/archives/[id]/+page.svelte +++ b/frontend/src/routes/archives/[id]/+page.svelte @@ -2,9 +2,6 @@ import { updateArchives } from '$gql/Mutations'; import { archiveQuery } from '$gql/Queries'; import { Direction, Layout, type FullArchiveFragment, type PageFragment } from '$gql/graphql'; - import { initReaderContext } from '$lib/Reader'; - import { initSelectionContext } from '$lib/Selection'; - import { setTabContext } from '$lib/Tabs'; import { toastFinally } from '$lib/Toasts'; import Guard from '$lib/components/Guard.svelte'; import Head from '$lib/components/Head.svelte'; @@ -12,52 +9,40 @@ import Grid from '$lib/containers/Grid.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 { initSelectionContext } from '$lib/selection/Selection.svelte'; import ArchiveDelete from '$lib/tabs/ArchiveDelete.svelte'; import ArchiveDetails from '$lib/tabs/ArchiveDetails.svelte'; import ArchiveEdit from '$lib/tabs/ArchiveEdit.svelte'; import Tab from '$lib/tabs/Tab.svelte'; import Tabs from '$lib/tabs/Tabs.svelte'; import { getContextClient } from '@urql/svelte'; - import type { PageData } from './$types'; + import type { PageProps } from './$types'; - export let data: PageData; + let { data }: PageProps = $props(); const client = getContextClient(); const reader = initReaderContext(); - setTabContext({ - tabs: { - details: { title: 'Details' }, - edit: { title: 'Edit' }, - deletion: { title: 'Delete' } - }, - current: 'details' - }); - - $: result = archiveQuery(client, { id: data.id }); - function updateCover(event: CustomEvent) { - updateArchives(client, { ids: archive.id, input: { cover: { id: event.detail } } }).catch( - toastFinally - ); + function updateCover(id: number) { + updateArchives(client, { ids: data.id, input: { cover: { id } } }).catch(toastFinally); } - let archive: FullArchiveFragment; + let selection = initSelectionContext( + 'Page', + (p) => p.path, + (p) => p.comicId === null + ); - $: $result, update(); - function update() { - if (!$result.stale && $result.data?.archive.__typename === 'FullArchive') { - archive = structuredClone($result.data.archive); + let result = $derived(archiveQuery(client, { id: data.id })); + let archive: FullArchiveFragment | undefined = $state(); - $reader.pages = archive.pages; + $effect(() => { + if (!$result.stale && $result.data?.archive.__typename === 'FullArchive') { + archive = $result.data.archive; + reader.pages = $result.data.archive.pages; + selection.view = $result.data.archive.pages; } - } - - const selection = initSelectionContext('Page', (p) => p.path); - $selection.selectable = (p) => p.comicId === null; - - $: if (archive) { - $selection.view = archive.pages; - } + }); @@ -70,24 +55,20 @@
- ($reader = $reader.open(e.detail))} - on:cover={updateCover} - /> +
{:else} diff --git a/frontend/src/routes/artists/+page.svelte b/frontend/src/routes/artists/+page.svelte index e07338c..c907470 100644 --- a/frontend/src/routes/artists/+page.svelte +++ b/frontend/src/routes/artists/+page.svelte @@ -3,10 +3,7 @@ import { artistsQuery, fetchArtist } from '$gql/Queries'; import type { Artist } from '$gql/graphql'; import { ArtistSortLabel } from '$lib/Enums'; - import { BasicFilterContext, initFilterContext } from '$lib/Filter'; - import { initPaginationContext } from '$lib/Pagination'; - import { initSelectionContext } from '$lib/Selection'; - import { initSortContext } from '$lib/Sort'; + import { BasicFilterContext } from '$lib/Filter.svelte'; import { toastFinally } from '$lib/Toasts'; import AddButton from '$lib/components/AddButton.svelte'; import Cardlet from '$lib/components/Cardlet.svelte'; @@ -19,6 +16,7 @@ import EditArtist from '$lib/dialogs/EditArtist.svelte'; import Pagination from '$lib/pagination/Pagination.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 Search from '$lib/toolbar/Search.svelte'; @@ -27,38 +25,32 @@ import SelectionControls from '$lib/toolbar/SelectionControls.svelte'; import Toolbar from '$lib/toolbar/Toolbar.svelte'; import { getContextClient } from '@urql/svelte'; - import { openModal } from 'svelte-modals'; - import type { PageData } from './$types'; + import { modals } from 'svelte-modals'; + import type { PageProps } from './$types'; + + let { data }: PageProps = $props(); + let pagination = $derived(data.pagination); + let sort = $derived(data.sort); const client = getContextClient(); - export let data: PageData; + let result = $derived(artistsQuery(client, { ...data })); + let artists = $derived($result.data?.artists); - $: result = artistsQuery(client, { - pagination: data.pagination, - filter: data.filter, - sort: data.sort + let selection = initSelectionContext('Artist', (a) => a.name); + $effect(() => { + if (artists) { + selection.view = artists.edges; + } }); - $: artists = $result.data?.artists; - - const selection = initSelectionContext('Artist', (a) => a.name); - $: if (artists) { - $selection.view = artists.edges; - $pagination.total = artists.count; - } - - const filter = initFilterContext(); - $: $filter = new BasicFilterContext(data.filter); - - const sort = initSortContext(data.sort, ArtistSortLabel); - $: $sort.update = data.sort; - - const pagination = initPaginationContext(); - $: $pagination.update = data.pagination; + let filter = $state(new BasicFilterContext(data.filter)); + $effect(() => { + filter = new BasicFilterContext(data.filter); + }); const edit = (id: number) => { fetchArtist(client, id) - .then((artist) => openModal(EditArtist, { artist })) + .then((artist) => modals.open(EditArtist, { artist })) .catch(toastFinally); }; @@ -67,34 +59,40 @@ - - - - - - - - - - openModal(AddArtist)} /> - + {#snippet start()} + + + + {/snippet} + {#snippet center()} + + + + {/snippet} + {#snippet end()} + modals.open(AddArtist)} /> + {/snippet} {#if artists} - +
{#each artists.edges as { id, name }, index (id)} - - - - + + {#snippet children({ onclick, selected })} + + {#snippet overlay()} + + {/snippet} + + {/snippet} {:else} {/each}
- + {:else} {/if} diff --git a/frontend/src/routes/characters/+page.svelte b/frontend/src/routes/characters/+page.svelte index 0934bab..04c72cb 100644 --- a/frontend/src/routes/characters/+page.svelte +++ b/frontend/src/routes/characters/+page.svelte @@ -3,10 +3,7 @@ import { charactersQuery, fetchCharacter } from '$gql/Queries'; import type { Character } from '$gql/graphql'; import { CharacterSortLabel } from '$lib/Enums'; - import { BasicFilterContext, initFilterContext } from '$lib/Filter'; - import { initPaginationContext } from '$lib/Pagination'; - import { initSelectionContext } from '$lib/Selection'; - import { initSortContext } from '$lib/Sort'; + import { BasicFilterContext } from '$lib/Filter.svelte'; import { toastFinally } from '$lib/Toasts'; import AddButton from '$lib/components/AddButton.svelte'; import Cardlet from '$lib/components/Cardlet.svelte'; @@ -19,6 +16,7 @@ import EditCharacter from '$lib/dialogs/EditCharacter.svelte'; import Pagination from '$lib/pagination/Pagination.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 Search from '$lib/toolbar/Search.svelte'; @@ -27,38 +25,32 @@ import SelectionControls from '$lib/toolbar/SelectionControls.svelte'; import Toolbar from '$lib/toolbar/Toolbar.svelte'; import { getContextClient } from '@urql/svelte'; - import { openModal } from 'svelte-modals'; - import type { PageData } from './$types'; + import { modals } from 'svelte-modals'; + import type { PageProps } from './$types'; + + let { data }: PageProps = $props(); + let pagination = $derived(data.pagination); + let sort = $derived(data.sort); const client = getContextClient(); - export let data: PageData; + let result = $derived(charactersQuery(client, { ...data })); + let characters = $derived($result.data?.characters); - $: result = charactersQuery(client, { - pagination: data.pagination, - filter: data.filter, - sort: data.sort + let selection = initSelectionContext('Character', (a) => a.name); + $effect(() => { + if (characters) { + selection.view = characters.edges; + } }); - $: characters = $result.data?.characters; - - const selection = initSelectionContext('Character', (c) => c.name); - $: if (characters) { - $selection.view = characters.edges; - $pagination.total = characters.count; - } - - const filter = initFilterContext(); - $: $filter = new BasicFilterContext(data.filter); - - const sort = initSortContext(data.sort, CharacterSortLabel); - $: $sort.update = data.sort; - - const pagination = initPaginationContext(); - $: $pagination.update = data.pagination; + let filter = $state(new BasicFilterContext(data.filter)); + $effect(() => { + filter = new BasicFilterContext(data.filter); + }); const edit = (id: number) => { fetchCharacter(client, id) - .then((character) => openModal(EditCharacter, { character })) + .then((character) => modals.open(EditCharacter, { character })) .catch(toastFinally); }; @@ -67,34 +59,40 @@ - - - - - - - - - - openModal(AddCharacter)} /> - + {#snippet start()} + + + + {/snippet} + {#snippet center()} + + + + {/snippet} + {#snippet end()} + modals.open(AddCharacter)} /> + {/snippet} {#if characters} - +
{#each characters.edges as { id, name }, index (id)} - - - - + + {#snippet children({ onclick, selected })} + + {#snippet overlay()} + + {/snippet} + + {/snippet} {:else} {/each}
- + {:else} {/if} diff --git a/frontend/src/routes/circles/+page.svelte b/frontend/src/routes/circles/+page.svelte index 14b0866..57520f8 100644 --- a/frontend/src/routes/circles/+page.svelte +++ b/frontend/src/routes/circles/+page.svelte @@ -3,10 +3,7 @@ import { circlesQuery, fetchCircle } from '$gql/Queries'; import type { Circle } from '$gql/graphql'; import { CircleSortLabel } from '$lib/Enums'; - import { BasicFilterContext, initFilterContext } from '$lib/Filter'; - import { initPaginationContext } from '$lib/Pagination'; - import { initSelectionContext } from '$lib/Selection'; - import { initSortContext } from '$lib/Sort'; + import { BasicFilterContext } from '$lib/Filter.svelte'; import { toastFinally } from '$lib/Toasts'; import AddButton from '$lib/components/AddButton.svelte'; import Cardlet from '$lib/components/Cardlet.svelte'; @@ -19,6 +16,7 @@ import EditCircle from '$lib/dialogs/EditCircle.svelte'; import Pagination from '$lib/pagination/Pagination.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 Search from '$lib/toolbar/Search.svelte'; @@ -27,38 +25,32 @@ import SelectionControls from '$lib/toolbar/SelectionControls.svelte'; import Toolbar from '$lib/toolbar/Toolbar.svelte'; import { getContextClient } from '@urql/svelte'; - import { openModal } from 'svelte-modals'; - import type { PageData } from './$types'; + import { modals } from 'svelte-modals'; + import type { PageProps } from './$types'; + + let { data }: PageProps = $props(); + let pagination = $derived(data.pagination); + let sort = $derived(data.sort); const client = getContextClient(); - export let data: PageData; + let result = $derived(circlesQuery(client, { ...data })); + let circles = $derived($result.data?.circles); - $: result = circlesQuery(client, { - pagination: data.pagination, - filter: data.filter, - sort: data.sort + let selection = initSelectionContext('Circle', (a) => a.name); + $effect(() => { + if (circles) { + selection.view = circles.edges; + } }); - $: circles = $result.data?.circles; - - const selection = initSelectionContext('Circle', (c) => c.name); - $: if (circles) { - $selection.view = circles.edges; - $pagination.total = circles.count; - } - - const filter = initFilterContext(); - $: $filter = new BasicFilterContext(data.filter); - - const sort = initSortContext(data.sort, CircleSortLabel); - $: $sort.update = data.sort; - - const pagination = initPaginationContext(); - $: $pagination.update = data.pagination; + let filter = $state(new BasicFilterContext(data.filter)); + $effect(() => { + filter = new BasicFilterContext(data.filter); + }); const edit = (id: number) => { fetchCircle(client, id) - .then((circle) => openModal(EditCircle, { circle })) + .then((circle) => modals.open(EditCircle, { circle })) .catch(toastFinally); }; @@ -67,34 +59,40 @@ - - - - - - - - - - openModal(AddCircle)} /> - + {#snippet start()} + + + + {/snippet} + {#snippet center()} + + + + {/snippet} + {#snippet end()} + modals.open(AddCircle)} /> + {/snippet} {#if circles} - +
{#each circles.edges as { id, name }, index (id)} - - - - + + {#snippet children({ onclick, selected })} + + {#snippet overlay()} + + {/snippet} + + {/snippet} {:else} {/each}
- + {:else} {/if} 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} diff --git a/frontend/src/routes/namespaces/+page.svelte b/frontend/src/routes/namespaces/+page.svelte index f6568f9..04f7737 100644 --- a/frontend/src/routes/namespaces/+page.svelte +++ b/frontend/src/routes/namespaces/+page.svelte @@ -3,10 +3,7 @@ import { fetchNamespace, namespacesQuery } from '$gql/Queries'; import type { Namespace } from '$gql/graphql'; import { NamespaceSortLabel } from '$lib/Enums'; - import { BasicFilterContext, initFilterContext } from '$lib/Filter'; - import { initPaginationContext } from '$lib/Pagination'; - import { initSelectionContext } from '$lib/Selection'; - import { initSortContext } from '$lib/Sort'; + import { BasicFilterContext } from '$lib/Filter.svelte'; import { toastFinally } from '$lib/Toasts'; import AddButton from '$lib/components/AddButton.svelte'; import Cardlet from '$lib/components/Cardlet.svelte'; @@ -19,6 +16,7 @@ import EditNamespace from '$lib/dialogs/EditNamespace.svelte'; import Pagination from '$lib/pagination/Pagination.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 Search from '$lib/toolbar/Search.svelte'; @@ -27,38 +25,32 @@ import SelectionControls from '$lib/toolbar/SelectionControls.svelte'; import Toolbar from '$lib/toolbar/Toolbar.svelte'; import { getContextClient } from '@urql/svelte'; - import { openModal } from 'svelte-modals'; - import type { PageData } from './$types'; + import { modals } from 'svelte-modals'; + import type { PageProps } from './$types'; + + let { data }: PageProps = $props(); + let pagination = $derived(data.pagination); + let sort = $derived(data.sort); const client = getContextClient(); - export let data: PageData; + let result = $derived(namespacesQuery(client, { ...data })); + let namespaces = $derived($result.data?.namespaces); - $: result = namespacesQuery(client, { - pagination: data.pagination, - filter: data.filter, - sort: data.sort + let selection = initSelectionContext('Namespace', (n) => n.name); + $effect(() => { + if (namespaces) { + selection.view = namespaces.edges; + } }); - $: namespaces = $result.data?.namespaces; - - const selection = initSelectionContext('Namespace', (n) => n.name); - $: if (namespaces) { - $selection.view = namespaces.edges; - $pagination.total = namespaces.count; - } - - const filter = initFilterContext(); - $: $filter = new BasicFilterContext(data.filter); - - const sort = initSortContext(data.sort, NamespaceSortLabel); - $: $sort.update = data.sort; - - const pagination = initPaginationContext(); - $: $pagination.update = data.pagination; + let filter = $state(new BasicFilterContext(data.filter)); + $effect(() => { + filter = new BasicFilterContext(data.filter); + }); const edit = (id: number) => { fetchNamespace(client, id) - .then((namespace) => openModal(EditNamespace, { namespace })) + .then((namespace) => modals.open(EditNamespace, { namespace })) .catch(toastFinally); }; @@ -67,34 +59,40 @@ - - - - - - - - - - openModal(AddNamespace)} /> - + {#snippet start()} + + + + {/snippet} + {#snippet center()} + + + + {/snippet} + {#snippet end()} + modals.open(AddNamespace)} /> + {/snippet} {#if namespaces} - +
{#each namespaces.edges as { id, name }, index (id)} - - - - + + {#snippet children({ onclick, selected })} + + {#snippet overlay()} + + {/snippet} + + {/snippet} {:else} {/each}
- + {:else} {/if} diff --git a/frontend/src/routes/statistics/+page.svelte b/frontend/src/routes/statistics/+page.svelte index 7497bcf..1a5bb27 100644 --- a/frontend/src/routes/statistics/+page.svelte +++ b/frontend/src/routes/statistics/+page.svelte @@ -5,8 +5,8 @@ import StatGroup from '$lib/statistics/StatGroup.svelte'; import { getContextClient } from '@urql/svelte'; - $: query = statisticsQuery(getContextClient()); - $: totals = $query.data?.statistics.total; + let query = $derived(statisticsQuery(getContextClient())); + let totals = $derived($query.data?.statistics.total); diff --git a/frontend/src/routes/tags/+page.svelte b/frontend/src/routes/tags/+page.svelte index e0909ad..30554c7 100644 --- a/frontend/src/routes/tags/+page.svelte +++ b/frontend/src/routes/tags/+page.svelte @@ -3,10 +3,7 @@ import { fetchTag, tagsQuery } from '$gql/Queries'; import { type Tag } from '$gql/graphql'; import { TagSortLabel } from '$lib/Enums'; - import { TagFilterContext, initFilterContext } from '$lib/Filter'; - import { initPaginationContext } from '$lib/Pagination'; - import { initSelectionContext } from '$lib/Selection'; - import { initSortContext } from '$lib/Sort'; + import { TagFilterContext } from '$lib/Filter.svelte'; import { toastFinally } from '$lib/Toasts'; import AddButton from '$lib/components/AddButton.svelte'; import Cardlet from '$lib/components/Cardlet.svelte'; @@ -17,10 +14,11 @@ import Column from '$lib/containers/Column.svelte'; import AddTag from '$lib/dialogs/AddTag.svelte'; import EditTag from '$lib/dialogs/EditTag.svelte'; - import UpdateTagsDialog from '$lib/dialogs/UpdateTags.svelte'; + import UpdateTags from '$lib/dialogs/UpdateTags.svelte'; import TagFilterForm from '$lib/filter/TagFilterForm.svelte'; import Pagination from '$lib/pagination/Pagination.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'; @@ -31,39 +29,33 @@ import ToggleAdvancedFilters from '$lib/toolbar/ToggleAdvancedFilters.svelte'; import Toolbar from '$lib/toolbar/Toolbar.svelte'; import { getContextClient } from '@urql/svelte'; - import { openModal } from 'svelte-modals'; - import type { PageData } from './$types'; + import { modals } from 'svelte-modals'; + import type { PageProps } from './$types'; - const client = getContextClient(); - - export let data: PageData; + let { data }: PageProps = $props(); + let pagination = $derived(data.pagination); + let sort = $derived(data.sort); - $: result = tagsQuery(client, { - pagination: data.pagination, - filter: data.filter, - sort: data.sort - }); - - $: tags = $result.data?.tags; + const client = getContextClient(); + let result = $derived(tagsQuery(client, { ...data })); + let tags = $derived($result.data?.tags); const selection = initSelectionContext('Tag', (t) => t.name); - $: if (tags) { - $selection.view = tags.edges; - $pagination.total = tags.count; - } - - const filter = initFilterContext(); - $: $filter = new TagFilterContext(data.filter); - - const sort = initSortContext(data.sort, TagSortLabel); - $: $sort.update = data.sort; + $effect(() => { + if (tags) { + selection.view = tags.edges; + } + }); - const pagination = initPaginationContext(); - $: $pagination.update = data.pagination; + let filter = $state(new TagFilterContext(data.filter)); + let filterSize = $derived(filter.includes + filter.excludes); + $effect(() => { + filter = new TagFilterContext(data.filter); + }); const edit = (id: number) => { fetchTag(client, id) - .then((tag) => openModal(EditTag, { tag })) + .then((tag) => modals.open(EditTag, { tag })) .catch(toastFinally); }; @@ -72,37 +64,45 @@ - - - - - - - - - - - - openModal(AddTag)} /> - - + {#snippet start()} + + + + + {/snippet} + {#snippet center({ expanded, toggle })} + + + + + {/snippet} + {#snippet end()} + modals.open(AddTag)} /> + {/snippet} + {#snippet expansion()} + + {/snippet} {#if tags} - +
{#each tags.edges as { id, name, description }, index (id)} - - - - + + {#snippet children({ onclick, selected })} + + {#snippet overlay()} + + {/snippet} + + {/snippet} {:else} {/each}
- + {:else} {/if} diff --git a/frontend/src/routes/worlds/+page.svelte b/frontend/src/routes/worlds/+page.svelte index e0366e9..f223a61 100644 --- a/frontend/src/routes/worlds/+page.svelte +++ b/frontend/src/routes/worlds/+page.svelte @@ -3,10 +3,7 @@ import { fetchWorld, worldsQuery } from '$gql/Queries'; import type { World } from '$gql/graphql'; import { WorldSortLabel } from '$lib/Enums'; - import { BasicFilterContext, initFilterContext } from '$lib/Filter'; - import { initPaginationContext } from '$lib/Pagination'; - import { initSelectionContext } from '$lib/Selection'; - import { initSortContext } from '$lib/Sort'; + import { BasicFilterContext } from '$lib/Filter.svelte'; import { toastFinally } from '$lib/Toasts'; import AddButton from '$lib/components/AddButton.svelte'; import Cardlet from '$lib/components/Cardlet.svelte'; @@ -19,6 +16,7 @@ import EditWorld from '$lib/dialogs/EditWorld.svelte'; import Pagination from '$lib/pagination/Pagination.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 Search from '$lib/toolbar/Search.svelte'; @@ -27,75 +25,74 @@ import SelectionControls from '$lib/toolbar/SelectionControls.svelte'; import Toolbar from '$lib/toolbar/Toolbar.svelte'; import { getContextClient } from '@urql/svelte'; - import { openModal } from 'svelte-modals'; - import type { PageData } from './$types'; + import { modals } from 'svelte-modals'; + import type { PageProps } from './$types'; - const client = getContextClient(); + let { data }: PageProps = $props(); + let pagination = $derived(data.pagination); + let sort = $derived(data.sort); - export let data: PageData; + const client = getContextClient(); + let result = $derived(worldsQuery(client, { ...data })); + let worlds = $derived($result.data?.worlds); - $: result = worldsQuery(client, { - pagination: data.pagination, - filter: data.filter, - sort: data.sort + let selection = initSelectionContext('World', (a) => a.name); + $effect(() => { + if (worlds) { + selection.view = worlds.edges; + } }); - $: worlds = $result.data?.worlds; - - const selection = initSelectionContext('World', (w) => w.name); - $: if (worlds) { - $selection.view = worlds.edges; - $pagination.total = worlds.count; - } - - const filter = initFilterContext(); - $: $filter = new BasicFilterContext(data.filter); - - const sort = initSortContext(data.sort, WorldSortLabel); - $: $sort.update = data.sort; - - const pagination = initPaginationContext(); - $: $pagination.update = data.pagination; + let filter = $state(new BasicFilterContext(data.filter)); + $effect(() => { + filter = new BasicFilterContext(data.filter); + }); const edit = (id: number) => { fetchWorld(client, id) - .then((world) => openModal(EditWorld, { world })) + .then((world) => modals.open(EditWorld, { world })) .catch(toastFinally); }; - + - - - - - - - - - - openModal(AddWorld)} /> - + {#snippet start()} + + + + {/snippet} + {#snippet center()} + + + + {/snippet} + {#snippet end()} + modals.open(AddWorld)} /> + {/snippet} {#if worlds} - +
{#each worlds.edges as { id, name }, index (id)} - - - - + + {#snippet children({ onclick, selected })} + + {#snippet overlay()} + + {/snippet} + + {/snippet} {:else} {/each}
- + {:else} {/if} -- cgit v1.2.3-2-gb3c3