feat: extract built-in view presets and add filter chips

This commit is contained in:
2026-08-11 20:08:53 -04:00
parent 4c3fd66a56
commit 0139f6f5eb
4 changed files with 131 additions and 32 deletions
@@ -14,13 +14,14 @@
<ScrollArea class="h-full w-full">
<div class="mr-2 flex flex-col gap-8 p-4">
<BookList books={data.continueReading} title="Continue Reading" />
<BookList books={data.recentlyAdded} title="Recently Added" />
<BookList books={data.discover} title="Discover" />
<BookList books={data.readAgain} title="Read Again" />
<!-- One shelf per built-in preset, in the order they are defined -->
{#each data.presets as preset (preset.id)}
<BookList books={data.shelves[preset.id]} title={preset.heading} />
{/each}
</div>
{#if data.recentlyAdded.length === 0 }
<!-- Empty when no preset returned anything, i.e. the library has no books -->
{#if data.presets.every((preset) => data.shelves[preset.id].length === 0)}
<div class="flex flex-col items-center justify-center w-full h-full">
<!-- Show a CTA to upload books if the library is empty -->
<Empty.Root class="mb-[12vh]">
@@ -1,40 +1,32 @@
import { BOOK_PRESETS, presetQuery } from '$lib/presets';
export async function load({ params, fetch }) {
try {
const continueReadingResponse = await fetch(
`/api/books?libraries=${params.libraryId}&progress=in_progress&orderBy=last_accessed&sortOrder=desc`
// The shelves are the built-in presets, fetched in parallel rather than
// one after another as before.
const responses = await Promise.all(
BOOK_PRESETS.map(async (preset) => {
const response = await fetch(`/api/books?${presetQuery(preset, params.libraryId)}`);
const result = await response.json();
return [preset.id, result['items'] ?? []] as const;
})
);
let results = await continueReadingResponse.json();
let continueReading = results['items'];
const discoverResponse = await fetch(
`/api/books?libraries=${params.libraryId}&progress=unread&sortOrder=random`
);
results = await discoverResponse.json();
let discover = results['items'];
const readAgainResponse = await fetch(
`/api/books?libraries=${params.libraryId}&progress=read&sortOrder=random`
);
results = await readAgainResponse.json();
let readAgain = results['items'];
const recentlyAddedResponse = await fetch(
`/api/books?libraries=${params.libraryId}&orderBy=create_at&sortOrder=desc&progress=unread`
);
results = await recentlyAddedResponse.json();
let recentlyAdded = results['items'];
const shelves = Object.fromEntries(responses);
return {
continueReading,
discover,
readAgain,
recentlyAdded
presets: BOOK_PRESETS,
shelves
};
} catch (error) {
console.error('Error fetching books from library: ', error);
// Same shape as the success path, so the page renders its empty state
// instead of the caller having to guard every field. The previous
// {status, error} return was consumed by nothing.
return {
status: error.status || 500,
error: error.message
presets: BOOK_PRESETS,
shelves: Object.fromEntries(BOOK_PRESETS.map((preset) => [preset.id, []]))
};
}
}