summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/lib/reader
diff options
context:
space:
mode:
authorWolfgang Müller2025-02-13 17:52:16 +0100
committerWolfgang Müller2025-02-13 17:52:16 +0100
commitdc4db405d2991d3ec6a114f3b08d3fccd057d3ee (patch)
tree2c620c9af2062ba09fa591f8b3ed961664adab58 /frontend/src/lib/reader
parent4df870d793123be95c8af031a340a39b5b8402ac (diff)
downloadhircine-dc4db405d2991d3ec6a114f3b08d3fccd057d3ee.tar.gz
frontend: Migrate to Svelte 5
Diffstat (limited to 'frontend/src/lib/reader')
-rw-r--r--frontend/src/lib/reader/PageView.svelte26
-rw-r--r--frontend/src/lib/reader/Reader.svelte76
-rw-r--r--frontend/src/lib/reader/ReaderPage.svelte14
-rw-r--r--frontend/src/lib/reader/components/CloseReaderButton.svelte15
-rw-r--r--frontend/src/lib/reader/components/PageIndicator.svelte4
-rw-r--r--frontend/src/lib/reader/components/ReaderMenuButton.svelte11
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>