diff options
author | Wolfgang Müller | 2025-02-13 17:52:16 +0100 |
---|---|---|
committer | Wolfgang Müller | 2025-02-13 17:52:16 +0100 |
commit | dc4db405d2991d3ec6a114f3b08d3fccd057d3ee (patch) | |
tree | 2c620c9af2062ba09fa591f8b3ed961664adab58 /frontend/src/lib/reader | |
parent | 4df870d793123be95c8af031a340a39b5b8402ac (diff) | |
download | hircine-dc4db405d2991d3ec6a114f3b08d3fccd057d3ee.tar.gz |
frontend: Migrate to Svelte 5
Diffstat (limited to 'frontend/src/lib/reader')
-rw-r--r-- | frontend/src/lib/reader/PageView.svelte | 26 | ||||
-rw-r--r-- | frontend/src/lib/reader/Reader.svelte | 76 | ||||
-rw-r--r-- | frontend/src/lib/reader/ReaderPage.svelte | 14 | ||||
-rw-r--r-- | frontend/src/lib/reader/components/CloseReaderButton.svelte | 15 | ||||
-rw-r--r-- | frontend/src/lib/reader/components/PageIndicator.svelte | 4 | ||||
-rw-r--r-- | frontend/src/lib/reader/components/ReaderMenuButton.svelte | 11 |
6 files changed, 111 insertions, 35 deletions
diff --git a/frontend/src/lib/reader/PageView.svelte b/frontend/src/lib/reader/PageView.svelte index 08764b7..81fbb97 100644 --- a/frontend/src/lib/reader/PageView.svelte +++ b/frontend/src/lib/reader/PageView.svelte @@ -1,8 +1,8 @@ <script lang="ts"> import { Direction, Layout, type PageFragment } from '$gql/graphql'; - import { getReaderContext, partition, type Chunk } from '$lib/Reader'; import { binds } from '$lib/Shortcuts'; import { src } from '$lib/Utils'; + import { getReaderContext, partition, type Chunk } from './Reader.svelte'; import ReaderPage from './ReaderPage.svelte'; const reader = getReaderContext(); @@ -19,14 +19,14 @@ function gotoChunk(to: number) { if (to < 0 || to >= chunks.length) return; - $reader.page = chunks[to].index; + reader.page = chunks[to].index; } function pagesAround(around: number) { const peek = (at: number) => { if (at < 0 || at >= chunks.length) return []; - const pages = [chunks[at].main]; + const pages: PageFragment[] = [chunks[at].main]; if (chunks[at].secondary) { pages.push(chunks[at].secondary); @@ -38,8 +38,8 @@ return [...peek(lookup[around] + 1), ...peek(lookup[around] - 1)]; } - const next = () => gotoChunk(lookup[$reader.page] + 1); - const prev = () => gotoChunk(lookup[$reader.page] - 1); + const next = () => gotoChunk(lookup[reader.page] + 1); + const prev = () => gotoChunk(lookup[reader.page] - 1); const clickLeft = () => (direction === Direction.LeftToRight ? prev() : next()); const clickRight = () => (direction === Direction.RightToLeft ? prev() : next()); @@ -56,8 +56,8 @@ } } - $: [chunks, lookup] = partition($reader.pages, layout); - $: layout, ({ main, secondary } = chunks[lookup[$reader.page]]); + $: [chunks, lookup] = partition(reader.pages, layout); + $: layout, ({ main, secondary } = chunks[lookup[reader.page]]); </script> <svelte:document @@ -76,16 +76,16 @@ /> {#if !secondary} - <ReaderPage page={main} on:click={clickMain} --justify="center" /> + <ReaderPage page={main} onclick={clickMain} --justify="center" /> {:else if direction === Direction.LeftToRight} - <ReaderPage page={main} on:click={prev} --justify="flex-end" /> - <ReaderPage page={secondary} on:click={next} --justify="flex-start" /> + <ReaderPage page={main} onclick={prev} --justify="flex-end" /> + <ReaderPage page={secondary} onclick={next} --justify="flex-start" /> {:else} - <ReaderPage page={secondary} on:click={next} --justify="flex-end" /> - <ReaderPage page={main} on:click={prev} --justify="flex-start" /> + <ReaderPage page={secondary} onclick={next} --justify="flex-end" /> + <ReaderPage page={main} onclick={prev} --justify="flex-start" /> {/if} <div class="invisible absolute"> - {#each pagesAround($reader.page) as page} + {#each pagesAround(reader.page) as page} <img src={src(page.image, 'full')} alt="" /> {/each} </div> diff --git a/frontend/src/lib/reader/Reader.svelte b/frontend/src/lib/reader/Reader.svelte index 9bc7a82..b5cc725 100644 --- a/frontend/src/lib/reader/Reader.svelte +++ b/frontend/src/lib/reader/Reader.svelte @@ -1,32 +1,96 @@ +<script lang="ts" module> + import { Layout, type PageFragment } from '$gql/graphql'; + import { getContext, setContext } from 'svelte'; + + export interface Chunk { + main: PageFragment; + secondary?: PageFragment; + index: number; + } + + class ReaderContext { + visible = $state(false); + sidebar = $state(false); + pages: PageFragment[] = $state([]); + page = $state(0); + + open = (page: number) => { + this.page = page; + this.visible = true; + }; + } + + export function initReaderContext() { + return setContext<ReaderContext>('reader', new ReaderContext()); + } + + export function getReaderContext() { + return getContext<ReaderContext>('reader'); + } + + export function partition(pages: PageFragment[], layout: Layout): [Chunk[], number[]] { + const single = layout === Layout.Single; + const offset = layout === Layout.DoubleOffset; + + const chunks: Chunk[] = []; + const lookup: number[] = Array<number>(pages.length); + + for (let chunkIndex = 0, pageIndex = 0; pageIndex < pages.length; chunkIndex++) { + const wide = () => pages[pageIndex].image.aspectRatio > 1; + + const nextPage = () => { + lookup[pageIndex] = chunkIndex; + return pages[pageIndex++]; + }; + + const offsetFirst = pageIndex === 0 && offset; + const full = single || wide() || offsetFirst; + + const chunk: Chunk = { index: pageIndex, main: nextPage() }; + + if (!full && pageIndex < pages.length) { + if (!wide()) { + chunk.secondary = nextPage(); + } + } + + chunks.push(chunk); + } + return [chunks, lookup]; + } +</script> + <script lang="ts"> import { trapFocus } from '$lib/Actions'; - import { getReaderContext } from '$lib/Reader'; import { fadeDefault, slideXDefault } from '$lib/Transitions'; + import type { Snippet } from 'svelte'; import { fade, slide } from 'svelte/transition'; import CloseReaderButton from './components/CloseReaderButton.svelte'; import PageIndicator from './components/PageIndicator.svelte'; import ReaderMenuButton from './components/ReaderMenuButton.svelte'; + let { sidebar, children }: { sidebar?: Snippet; children?: Snippet } = $props(); + const reader = getReaderContext(); </script> -{#if $reader.visible} +{#if reader.visible} <div role="dialog" class="fixed bottom-0 left-0 right-0 top-0 z-10 flex h-full w-full bg-black" transition:fade={fadeDefault} use:trapFocus > - {#if $$slots.sidebar && $reader.sidebar} + {#if sidebar && reader.sidebar} <aside class="w-[36rem] shrink-0 bg-slate-800" transition:slide={slideXDefault}> <div class="flex h-full min-w-[36rem] flex-col gap-4 overflow-auto p-4"> - <slot name="sidebar" /> + {@render sidebar?.()} </div> </aside> {/if} <main class="relative flex grow"> <div class="absolute flex w-full p-1 text-lg [&>*:last-child]:ml-auto"> - {#if $$slots.sidebar} + {#if sidebar} <ReaderMenuButton /> {/if} <CloseReaderButton /> @@ -36,7 +100,7 @@ </div> <div class="flex grow"> - <slot /> + {@render children?.()} </div> </main> </div> diff --git a/frontend/src/lib/reader/ReaderPage.svelte b/frontend/src/lib/reader/ReaderPage.svelte index fb3e780..83b2d1b 100644 --- a/frontend/src/lib/reader/ReaderPage.svelte +++ b/frontend/src/lib/reader/ReaderPage.svelte @@ -1,13 +1,19 @@ <script lang="ts"> import type { PageFragment } from '$gql/graphql'; import { src } from '$lib/Utils'; + import type { MouseEventHandler } from 'svelte/elements'; - export let page: PageFragment; + interface Props { + page: PageFragment; + onclick: MouseEventHandler<HTMLDivElement>; + } + + let { page, onclick }: Props = $props(); </script> -<!-- svelte-ignore a11y-click-events-have-key-events --> -<!-- svelte-ignore a11y-no-static-element-interactions --> -<div class="flex grow" on:click> +<!-- svelte-ignore a11y_click_events_have_key_events --> +<!-- svelte-ignore a11y_no_static_element_interactions --> +<div class="flex grow" {onclick}> <img class="h-auto w-auto object-contain" width={page.image.width} diff --git a/frontend/src/lib/reader/components/CloseReaderButton.svelte b/frontend/src/lib/reader/components/CloseReaderButton.svelte index 0c88323..f3eb4ba 100644 --- a/frontend/src/lib/reader/components/CloseReaderButton.svelte +++ b/frontend/src/lib/reader/components/CloseReaderButton.svelte @@ -1,19 +1,22 @@ <script lang="ts"> - import { getReaderContext } from '$lib/Reader'; import { accelerator } from '$lib/Shortcuts'; + import { getReaderContext } from '../Reader.svelte'; const reader = getReaderContext(); + + function onclick() { + reader.visible = false; + reader.sidebar = false; + } </script> <button type="button" class="btn floating" title="Close reader" - on:click={() => { - $reader.visible = false; - $reader.sidebar = false; - }} + aria-label="Close reader" + {onclick} use:accelerator={'Escape'} > - <span class="icon-lg icon-[material-symbols--close]" /> + <span class="icon-lg icon-[material-symbols--close]"></span> </button> diff --git a/frontend/src/lib/reader/components/PageIndicator.svelte b/frontend/src/lib/reader/components/PageIndicator.svelte index f79fc00..35190b3 100644 --- a/frontend/src/lib/reader/components/PageIndicator.svelte +++ b/frontend/src/lib/reader/components/PageIndicator.svelte @@ -1,9 +1,9 @@ <script lang="ts"> - import { getReaderContext } from '$lib/Reader'; + import { getReaderContext } from '../Reader.svelte'; const reader = getReaderContext(); </script> <div class="floating !p-2"> - {$reader.page + 1}/{$reader.pages.length} + {reader.page + 1}/{reader.pages.length} </div> diff --git a/frontend/src/lib/reader/components/ReaderMenuButton.svelte b/frontend/src/lib/reader/components/ReaderMenuButton.svelte index aa20206..58648e8 100644 --- a/frontend/src/lib/reader/components/ReaderMenuButton.svelte +++ b/frontend/src/lib/reader/components/ReaderMenuButton.svelte @@ -1,16 +1,19 @@ <script lang="ts"> - import { getReaderContext } from '$lib/Reader'; import { accelerator } from '$lib/Shortcuts'; + import { getReaderContext } from '../Reader.svelte'; const reader = getReaderContext(); + + let title = $derived(`${reader.sidebar ? 'Hide' : 'Show'} menu`); </script> <button type="button" class="btn floating invisible xl:visible" - title={`${$reader.sidebar ? 'Hide' : 'Show'} menu`} - on:click={() => ($reader.sidebar = !$reader.sidebar)} + {title} + aria-label={title} + onclick={() => (reader.sidebar = !reader.sidebar)} use:accelerator={'z'} > - <span class="icon-lg icon-[material-symbols--dock-to-right]" /> + <span class="icon-lg icon-[material-symbols--dock-to-right]"></span> </button> |