diff --git a/userscript/manga-bookmark.user.js b/userscript/manga-bookmark.user.js index 155d81e..35f360e 100644 --- a/userscript/manga-bookmark.user.js +++ b/userscript/manga-bookmark.user.js @@ -852,6 +852,9 @@ // ============================================================ let root, panelOpen = false; + // True while refresh() is waiting on the backend. Only changes what the list + // draws when there is nothing cached to draw instead. + let loading = false; let activeTab = "all"; // "all" | "favorites" | "archived"; resets each page, by design function buildUI() { @@ -1071,7 +1074,11 @@ if (s !== "reading") return false; return activeTab !== "favorites" || b.favorite; }); - if (items.length === 0) { + if (items.length === 0 && loading) { + // Nothing cached and the fetch is still out — without this the panel + // looks frozen on the first open after a cold start. + listEl.appendChild(el("div", { class: "empty" }, [el("div", { class: "spinner" })])); + } else if (items.length === 0) { const empty = { favorites: "No favourites yet.", archived: "Nothing archived.", @@ -1154,12 +1161,16 @@ async function refresh() { await drain(); // push what we owe before adopting the server's view of it + loading = true; + render(); try { const list = await apiGet(); setList(overlayPending(list)); - render(); } catch (e) { - render(); // fall back to cache + // Offline or backend down: keep whatever the cache holds. + } finally { + loading = false; + render(); } } @@ -1380,6 +1391,12 @@ .tab.active { color: #eee; border-bottom-color: #6d28d9; } #list { overflow-y: auto; flex: 1; padding: 8px 0; } .empty { color: #9a9aa5; text-align: center; padding: 30px 16px; font-size: 14px; } + .spinner { + width: 26px; height: 26px; margin: 0 auto; + border: 3px solid #33333d; border-top-color: #6d28d9; border-radius: 50%; + animation: spin .8s linear infinite; + } + @keyframes spin { to { transform: rotate(360deg); } } .item { display: flex; flex-direction: row; gap: 10px; padding: 10px 16px; border-bottom: 1px solid #2a2a33;