diff options
Diffstat (limited to 'frontend/src/lib/reader')
-rw-r--r-- | frontend/src/lib/reader/PageView.svelte | 67 | ||||
-rw-r--r-- | frontend/src/lib/reader/Reader.svelte | 39 | ||||
-rw-r--r-- | frontend/src/lib/reader/ReaderPage.svelte | 24 | ||||
-rw-r--r-- | frontend/src/lib/reader/components/CloseReaderButton.svelte | 19 | ||||
-rw-r--r-- | frontend/src/lib/reader/components/ReaderMenuButton.svelte | 16 |
5 files changed, 165 insertions, 0 deletions
diff --git a/frontend/src/lib/reader/PageView.svelte b/frontend/src/lib/reader/PageView.svelte new file mode 100644 index 0000000..cc4d10e --- /dev/null +++ b/frontend/src/lib/reader/PageView.svelte @@ -0,0 +1,67 @@ +<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 ReaderPage from './ReaderPage.svelte'; + + const reader = getReaderContext(); + + export let direction: Direction; + export let layout: Layout; + + let chunks: Chunk[] = []; + let lookup: number[] = []; + + let main: PageFragment; + let secondary: PageFragment | undefined; + + function gotoChunk(to: number) { + if (to < 0 || to >= chunks.length) return; + + $reader.page = chunks[to].index; + } + + 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()); + + function clickMain(event: MouseEvent & { currentTarget: EventTarget | null }) { + if (event.currentTarget instanceof Element) { + const rect = event.currentTarget.getBoundingClientRect(); + + if (event.clientX - rect.left < rect.width / 2) { + clickLeft(); + } else { + clickRight(); + } + } + } + + $: [chunks, lookup] = partition($reader.pages, layout); + $: layout, ({ main, secondary } = chunks[lookup[$reader.page]]); +</script> + +<svelte:document + use:binds={[ + ['ArrowLeft', clickLeft], + ['ArrowRight', clickRight], + ['ArrowUp', prev], + ['ArrowDown', next], + ['PageUp', prev], + ['PageDown', next], + [' ', next], + ['Backspace', prev] + ]} +/> + +{#if !secondary} + <ReaderPage page={main} on:click={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" /> +{:else} + <ReaderPage page={secondary} on:click={next} --justify="flex-end" /> + <ReaderPage page={main} on:click={prev} --justify="flex-start" /> +{/if} diff --git a/frontend/src/lib/reader/Reader.svelte b/frontend/src/lib/reader/Reader.svelte new file mode 100644 index 0000000..0b1450a --- /dev/null +++ b/frontend/src/lib/reader/Reader.svelte @@ -0,0 +1,39 @@ +<script lang="ts"> + import { trapFocus } from '$lib/Actions'; + import { getReaderContext } from '$lib/Reader'; + import { fadeDefault, slideXDefault } from '$lib/Transitions'; + import { fade, slide } from 'svelte/transition'; + import CloseReaderButton from './components/CloseReaderButton.svelte'; + import ReaderMenuButton from './components/ReaderMenuButton.svelte'; + + const reader = getReaderContext(); +</script> + +{#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} + <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" /> + </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} + <ReaderMenuButton /> + {/if} + <CloseReaderButton /> + </div> + + <div class="flex grow"> + <slot /> + </div> + </main> + </div> +{/if} diff --git a/frontend/src/lib/reader/ReaderPage.svelte b/frontend/src/lib/reader/ReaderPage.svelte new file mode 100644 index 0000000..fb3e780 --- /dev/null +++ b/frontend/src/lib/reader/ReaderPage.svelte @@ -0,0 +1,24 @@ +<script lang="ts"> + import type { PageFragment } from '$gql/graphql'; + import { src } from '$lib/Utils'; + + export let page: PageFragment; +</script> + +<!-- svelte-ignore a11y-click-events-have-key-events --> +<!-- svelte-ignore a11y-no-static-element-interactions --> +<div class="flex grow" on:click> + <img + class="h-auto w-auto object-contain" + width={page.image.width} + height={page.image.height} + src={src(page.image, 'full')} + alt={page.path} + /> +</div> + +<style> + div { + justify-content: var(--justify); + } +</style> diff --git a/frontend/src/lib/reader/components/CloseReaderButton.svelte b/frontend/src/lib/reader/components/CloseReaderButton.svelte new file mode 100644 index 0000000..0c88323 --- /dev/null +++ b/frontend/src/lib/reader/components/CloseReaderButton.svelte @@ -0,0 +1,19 @@ +<script lang="ts"> + import { getReaderContext } from '$lib/Reader'; + import { accelerator } from '$lib/Shortcuts'; + + const reader = getReaderContext(); +</script> + +<button + type="button" + class="btn floating" + title="Close reader" + on:click={() => { + $reader.visible = false; + $reader.sidebar = false; + }} + use:accelerator={'Escape'} +> + <span class="icon-lg icon-[material-symbols--close]" /> +</button> diff --git a/frontend/src/lib/reader/components/ReaderMenuButton.svelte b/frontend/src/lib/reader/components/ReaderMenuButton.svelte new file mode 100644 index 0000000..aa20206 --- /dev/null +++ b/frontend/src/lib/reader/components/ReaderMenuButton.svelte @@ -0,0 +1,16 @@ +<script lang="ts"> + import { getReaderContext } from '$lib/Reader'; + import { accelerator } from '$lib/Shortcuts'; + + const reader = getReaderContext(); +</script> + +<button + type="button" + class="btn floating invisible xl:visible" + title={`${$reader.sidebar ? 'Hide' : 'Show'} menu`} + on:click={() => ($reader.sidebar = !$reader.sidebar)} + use:accelerator={'z'} +> + <span class="icon-lg icon-[material-symbols--dock-to-right]" /> +</button> |