feat: loading skeleton on the library page
The library loader streams its one-shot bundle (also the auth gate), so the active sub-tab shows a skeleton while it loads — a grid skeleton for the Collections tab, a row skeleton for Bookmarks/History/Page-tags — instead of only the global nav bar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
@@ -1558,7 +1558,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.120.0"
|
version = "0.121.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.120.0"
|
version = "0.121.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "mangalord"
|
default-run = "mangalord"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mangalord-frontend",
|
"name": "mangalord-frontend",
|
||||||
"version": "0.120.0",
|
"version": "0.121.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
import CollectionsGrid from '$lib/components/CollectionsGrid.svelte';
|
import CollectionsGrid from '$lib/components/CollectionsGrid.svelte';
|
||||||
import PageTagsList from '$lib/components/PageTagsList.svelte';
|
import PageTagsList from '$lib/components/PageTagsList.svelte';
|
||||||
import HistoryList from '$lib/components/HistoryList.svelte';
|
import HistoryList from '$lib/components/HistoryList.svelte';
|
||||||
|
import ListRowSkeleton from '$lib/components/ListRowSkeleton.svelte';
|
||||||
|
import MangaGridSkeleton from '$lib/components/MangaGridSkeleton.svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
@@ -66,37 +68,42 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if !data.authenticated}
|
{#await data.result}
|
||||||
<p class="hint" data-testid="library-signin">
|
{#if activeTab === 'collections'}
|
||||||
<a href="/login?next=/library">Sign in</a> to see your library.
|
<MangaGridSkeleton count={6} />
|
||||||
</p>
|
|
||||||
{:else if data.error}
|
|
||||||
<p class="error" role="alert" data-testid="library-error">
|
|
||||||
Couldn't load library: {data.error}
|
|
||||||
</p>
|
|
||||||
{:else if activeTab === 'bookmarks'}
|
|
||||||
{#if data.bookmarks.length === 0}
|
|
||||||
<p class="hint" data-testid="library-bookmarks-empty">No bookmarks yet.</p>
|
|
||||||
{:else}
|
{:else}
|
||||||
<BookmarkList bookmarks={data.bookmarks} testid="library-bookmark-list" />
|
<ListRowSkeleton />
|
||||||
{/if}
|
{/if}
|
||||||
{:else if activeTab === 'collections'}
|
{:then r}
|
||||||
{#if data.collections.length === 0}
|
{#if !r.authenticated}
|
||||||
<p class="hint" data-testid="library-collections-empty">
|
<p class="hint" data-testid="library-signin">
|
||||||
You don't have any collections yet. Open any manga and use
|
<a href="/login?next=/library">Sign in</a> to see your library.
|
||||||
<strong>Add to collection</strong> to start one.
|
|
||||||
</p>
|
</p>
|
||||||
|
{:else if r.error}
|
||||||
|
<p class="error" role="alert" data-testid="library-error">
|
||||||
|
Couldn't load library: {r.error}
|
||||||
|
</p>
|
||||||
|
{:else if activeTab === 'bookmarks'}
|
||||||
|
{#if r.bookmarks.length === 0}
|
||||||
|
<p class="hint" data-testid="library-bookmarks-empty">No bookmarks yet.</p>
|
||||||
|
{:else}
|
||||||
|
<BookmarkList bookmarks={r.bookmarks} testid="library-bookmark-list" />
|
||||||
|
{/if}
|
||||||
|
{:else if activeTab === 'collections'}
|
||||||
|
{#if r.collections.length === 0}
|
||||||
|
<p class="hint" data-testid="library-collections-empty">
|
||||||
|
You don't have any collections yet. Open any manga and use
|
||||||
|
<strong>Add to collection</strong> to start one.
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<CollectionsGrid collections={r.collections} />
|
||||||
|
{/if}
|
||||||
|
{:else if activeTab === 'page-tags'}
|
||||||
|
<PageTagsList initialItems={r.pageTags} initialDistinct={r.distinctPageTags} />
|
||||||
{:else}
|
{:else}
|
||||||
<CollectionsGrid collections={data.collections} />
|
<HistoryList entries={r.history} onClear={clearOne} testid="library-history" />
|
||||||
{/if}
|
{/if}
|
||||||
{:else if activeTab === 'page-tags'}
|
{/await}
|
||||||
<PageTagsList
|
|
||||||
initialItems={data.pageTags}
|
|
||||||
initialDistinct={data.distinctPageTags}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<HistoryList entries={data.history} onClear={clearOne} testid="library-history" />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.library-heading {
|
.library-heading {
|
||||||
|
|||||||
@@ -26,30 +26,36 @@ export const load: PageLoad = async () => {
|
|||||||
distinctPageTags: [] as Awaited<ReturnType<typeof listMyDistinctPageTags>>,
|
distinctPageTags: [] as Awaited<ReturnType<typeof listMyDistinctPageTags>>,
|
||||||
error: null as string | null
|
error: null as string | null
|
||||||
};
|
};
|
||||||
try {
|
// Streamed so the active sub-tab shows a skeleton while the one-shot fetch
|
||||||
const [bookmarks, collections, history, pageTags, distinctPageTags] =
|
// (also the auth gate) is in flight.
|
||||||
await Promise.all([
|
return {
|
||||||
listMyBookmarks(),
|
result: (async () => {
|
||||||
listMyCollections({ limit: 200 }),
|
try {
|
||||||
listMyReadProgress({ limit: 100 }),
|
const [bookmarks, collections, history, pageTags, distinctPageTags] =
|
||||||
listMyPageTags({ limit: 100 }),
|
await Promise.all([
|
||||||
listMyDistinctPageTags(undefined, 100)
|
listMyBookmarks(),
|
||||||
]);
|
listMyCollections({ limit: 200 }),
|
||||||
return {
|
listMyReadProgress({ limit: 100 }),
|
||||||
...empty,
|
listMyPageTags({ limit: 100 }),
|
||||||
bookmarks: bookmarks.items,
|
listMyDistinctPageTags(undefined, 100)
|
||||||
collections: collections.items,
|
]);
|
||||||
history: history.items,
|
return {
|
||||||
pageTags: pageTags.items,
|
...empty,
|
||||||
distinctPageTags
|
bookmarks: bookmarks.items,
|
||||||
};
|
collections: collections.items,
|
||||||
} catch (e) {
|
history: history.items,
|
||||||
if (e instanceof ApiError && e.status === 401) {
|
pageTags: pageTags.items,
|
||||||
return { ...empty, authenticated: false };
|
distinctPageTags
|
||||||
}
|
};
|
||||||
if (e instanceof ApiError) {
|
} catch (e) {
|
||||||
return { ...empty, error: e.message };
|
if (e instanceof ApiError && e.status === 401) {
|
||||||
}
|
return { ...empty, authenticated: false };
|
||||||
throw e;
|
}
|
||||||
}
|
if (e instanceof ApiError) {
|
||||||
|
return { ...empty, error: e.message };
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user