<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 { 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} <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 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"> {@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 sidebar} <ReaderMenuButton /> {/if} <CloseReaderButton /> </div> <div class="absolute bottom-0 right-0 z-10 flex p-1 text-lg"> <PageIndicator /> </div> <div class="flex grow"> {@render children?.()} </div> </main> </div> {/if}