Both loaders now stream their manga grid instead of blocking on it: the cheap header renders immediately (and still handles 404/401 in the loader) while a MangaGridSkeleton stands in until the grid resolves, then swaps in without a layout shift. Collection detail seeds its optimistic-removal state from the stream via a guarded effect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
40 lines
1.4 KiB
TypeScript
40 lines
1.4 KiB
TypeScript
import { error, redirect } from '@sveltejs/kit';
|
|
import { ApiError } from '$lib/api/client';
|
|
import {
|
|
getCollection,
|
|
listCollectionMangas
|
|
} from '$lib/api/collections';
|
|
import { listCollectionPages } from '$lib/api/page_collections';
|
|
import type { PageLoad } from './$types';
|
|
|
|
export const ssr = false;
|
|
|
|
export const load: PageLoad = async ({ params, url }) => {
|
|
let collection;
|
|
try {
|
|
// Awaited so the header renders immediately and 401/404 are handled by
|
|
// the framework (redirect / not-found) rather than the happy path.
|
|
collection = await getCollection(params.id);
|
|
} catch (e) {
|
|
if (e instanceof ApiError) {
|
|
if (e.status === 401) {
|
|
const next = encodeURIComponent(url.pathname);
|
|
redirect(302, `/login?next=${next}`);
|
|
}
|
|
if (e.status === 404 || e.status === 403) {
|
|
error(404, 'Collection not found');
|
|
}
|
|
}
|
|
throw e;
|
|
}
|
|
return {
|
|
collection,
|
|
// Streamed so the manga/page grids show a skeleton while they load
|
|
// instead of the whole page blocking on them.
|
|
content: Promise.all([
|
|
listCollectionMangas(params.id, { limit: 200 }),
|
|
listCollectionPages(params.id, { limit: 200 })
|
|
]).then(([mangas, pages]) => ({ mangas: mangas.items, pages: pages.items }))
|
|
};
|
|
};
|