summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/lib/reader
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/lib/reader')
-rw-r--r--frontend/src/lib/reader/PageView.svelte67
-rw-r--r--frontend/src/lib/reader/Reader.svelte39
-rw-r--r--frontend/src/lib/reader/ReaderPage.svelte24
-rw-r--r--frontend/src/lib/reader/components/CloseReaderButton.svelte19
-rw-r--r--frontend/src/lib/reader/components/ReaderMenuButton.svelte16
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>