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/routes/archives | |
download | hircine-0.1.0.tar.gz |
Initial commit0.1.0
Diffstat (limited to '')
-rw-r--r-- | frontend/src/routes/archives/+page.svelte | 119 | ||||
-rw-r--r-- | frontend/src/routes/archives/+page.ts | 12 | ||||
-rw-r--r-- | frontend/src/routes/archives/[id]/+page.svelte | 99 | ||||
-rw-r--r-- | frontend/src/routes/archives/[id]/+page.ts | 5 |
4 files changed, 235 insertions, 0 deletions
diff --git a/frontend/src/routes/archives/+page.svelte b/frontend/src/routes/archives/+page.svelte new file mode 100644 index 0000000..545058a --- /dev/null +++ b/frontend/src/routes/archives/+page.svelte @@ -0,0 +1,119 @@ +<script lang="ts"> + import { deleteArchives, updateArchives } from '$gql/Mutations'; + 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 Card from '$lib/components/Card.svelte'; + import Empty from '$lib/components/Empty.svelte'; + import Guard from '$lib/components/Guard.svelte'; + import Head from '$lib/components/Head.svelte'; + import RefreshButton from '$lib/components/RefreshButton.svelte'; + import Cards from '$lib/containers/Cards.svelte'; + import Column from '$lib/containers/Column.svelte'; + import Pagination from '$lib/pagination/Pagination.svelte'; + import Pill from '$lib/pills/Pill.svelte'; + import Selectable from '$lib/selection/Selectable.svelte'; + import SelectionOverlay from '$lib/selection/SelectionOverlay.svelte'; + import DeleteSelection from '$lib/toolbar/DeleteSelection.svelte'; + import FilterOrganized from '$lib/toolbar/FilterOrganized.svelte'; + import MarkOrganized from '$lib/toolbar/MarkOrganized.svelte'; + import MarkSelection from '$lib/toolbar/MarkSelection.svelte'; + import Search from '$lib/toolbar/Search.svelte'; + import SelectItems from '$lib/toolbar/SelectItems.svelte'; + import SelectSort from '$lib/toolbar/SelectSort.svelte'; + import SelectionControls from '$lib/toolbar/SelectionControls.svelte'; + import Toolbar from '$lib/toolbar/Toolbar.svelte'; + import { getContextClient } from '@urql/svelte'; + import { filesize } from 'filesize'; + import type { PageData } from './$types'; + + let client = getContextClient(); + + export let data: PageData; + + $: result = archivesQuery(client, { + pagination: data.pagination, + filter: data.filter, + sort: data.sort + }); + + $: archives = $result.data?.archives; + + const selection = initSelectionContext<ArchiveFragment>('Archive', (a) => a.name); + $: if (archives) { + $selection.view = archives.edges; + $pagination.total = archives.count; + } + + const pagination = initPaginationContext(); + $: $pagination.update = data.pagination; + + const filter = initFilterContext<ArchiveFilterContext>(); + $: $filter = new ArchiveFilterContext(data.filter); + + const sort = initSortContext(data.sort, ArchiveSortLabel); + $: $sort.update = data.sort; + + function refresh() { + result.reexecute({ requestPolicy: 'network-only' }); + } +</script> + +<Head section="Archives" /> + +<Column> + <Toolbar> + <SelectionControls slot="start"> + <MarkSelection> + <MarkOrganized mutation={updateArchives} /> + </MarkSelection> + <DeleteSelection + mutation={deleteArchives} + warning="Deleting an archive will also delete its archive file on disk as well as all comics that belong to it." + /> + </SelectionControls> + <svelte:fragment slot="center"> + <Search name="Archives" bind:field={$filter.include.controls.path.contains} /> + <FilterOrganized /> + <SelectSort /> + <SelectItems /> + </svelte:fragment> + <RefreshButton slot="end" on:click={refresh} /> + </Toolbar> + {#if archives} + <Pagination /> + <main> + <Cards> + {#each archives.edges as { id, name, cover, size, pageCount }, index (id)} + <Selectable {index} {id} let:handle let:selected> + <Card + ellipsis={false} + href={id.toString()} + details={{ title: name, cover: cover }} + on:click={handle} + > + <SelectionOverlay position="left" {selected} slot="overlay" /> + <div class="flex gap-1 text-xs"> + <Pill name={`${pageCount} pages`}> + <span class="icon-[material-symbols--note] mr-0.5" slot="icon" /> + </Pill> + <Pill name={filesize(size, { base: 2 })}> + <span class="icon-[material-symbols--hard-drive] mr-0.5" slot="icon" /> + </Pill> + </div> + </Card> + </Selectable> + {:else} + <Empty /> + {/each} + </Cards> + </main> + <Pagination /> + {:else} + <Guard {result} /> + {/if} +</Column> diff --git a/frontend/src/routes/archives/+page.ts b/frontend/src/routes/archives/+page.ts new file mode 100644 index 0000000..88acade --- /dev/null +++ b/frontend/src/routes/archives/+page.ts @@ -0,0 +1,12 @@ +import { ArchiveSort, type ArchiveFilterInput } from '$gql/graphql'; +import { parseFilter, parsePaginationData, parseSortData } from '$lib/Navigation'; + +export const trailingSlash = 'always'; + +export function load({ url }: { url: URL; params: Record<string, string> }) { + return { + sort: parseSortData(url.searchParams, ArchiveSort.Path), + filter: parseFilter<ArchiveFilterInput>(url.searchParams), + pagination: parsePaginationData(url.searchParams, 24) + }; +} diff --git a/frontend/src/routes/archives/[id]/+page.svelte b/frontend/src/routes/archives/[id]/+page.svelte new file mode 100644 index 0000000..50a2940 --- /dev/null +++ b/frontend/src/routes/archives/[id]/+page.svelte @@ -0,0 +1,99 @@ +<script lang="ts"> + 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'; + import Titlebar from '$lib/components/Titlebar.svelte'; + 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 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'; + + export let data: PageData; + 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<number>) { + updateArchives(client, { ids: archive.id, input: { cover: { id: event.detail } } }).catch( + toastFinally + ); + } + + let archive: FullArchiveFragment; + + $: $result, update(); + function update() { + if (!$result.stale && $result.data?.archive.__typename === 'FullArchive') { + archive = structuredClone($result.data.archive); + + $reader.pages = archive.pages; + } + } + + const selection = initSelectionContext<PageFragment>('Page', (p) => p.path); + $selection.selectable = (p) => p.comicId === null; + + $: if (archive) { + $selection.view = archive.pages; + } +</script> + +<Head section="Archive" title={archive?.name} /> + +{#if archive} + <Grid> + <header> + <Titlebar title={archive.name} /> + </header> + + <aside> + <Tabs> + <Tab id="details"> + <ArchiveDetails {archive} /> + </Tab> + <Tab id="edit"> + <ArchiveEdit {archive} /> + </Tab> + <Tab id="deletion"> + <ArchiveDelete {archive} /> + </Tab> + </Tabs> + </aside> + + <main class="overflow-auto"> + <Gallery + pages={archive.pages} + on:open={(e) => ($reader = $reader.open(e.detail))} + on:cover={updateCover} + /> + </main> + </Grid> +{:else} + <Guard {result} /> +{/if} + +<Reader> + <PageView layout={Layout.Single} direction={Direction.LeftToRight} /> +</Reader> diff --git a/frontend/src/routes/archives/[id]/+page.ts b/frontend/src/routes/archives/[id]/+page.ts new file mode 100644 index 0000000..d872ba2 --- /dev/null +++ b/frontend/src/routes/archives/[id]/+page.ts @@ -0,0 +1,5 @@ +export function load({ params }: { params: Record<string, string> }) { + return { + id: +params.id + }; +} |