mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Feat: Library Manga Updates Display
This commit is contained in:
@@ -858,12 +858,12 @@
|
||||
class="icon-btn refresh-btn"
|
||||
class:icon-btn-active={refreshing}
|
||||
class:refresh-btn-done={refreshDone}
|
||||
title={refreshing ? `Checking… ${refreshProgress.finished}/${refreshProgress.total}` : refreshDone ? "Library updated" : "Check for updates"}
|
||||
title={refreshing ? (refreshProgress.finished > 0 ? `Checking… ${refreshProgress.finished}/${refreshProgress.total}` : "Checking…") : refreshDone ? "Library updated" : "Check for updates"}
|
||||
disabled={refreshing}
|
||||
onclick={startLibraryRefresh}
|
||||
>
|
||||
<ArrowsClockwise size={15} weight="bold" class={refreshing ? "anim-spin" : ""} />
|
||||
{#if refreshing && refreshProgress.total > 0}
|
||||
{#if refreshing && refreshProgress.finished > 0}
|
||||
<span class="refresh-progress">{refreshProgress.finished}/{refreshProgress.total}</span>
|
||||
{/if}
|
||||
</button>
|
||||
@@ -971,7 +971,7 @@
|
||||
</div>
|
||||
|
||||
<!-- ── Refresh progress bar ──────────────────────────────────────────────── -->
|
||||
{#if refreshing && refreshProgress.total > 0}
|
||||
{#if refreshing && refreshProgress.finished > 0}
|
||||
{@const pct = Math.round((refreshProgress.finished / refreshProgress.total) * 100)}
|
||||
<div class="refresh-bar-wrap" aria-hidden="true">
|
||||
<div class="refresh-bar-fill" style="width:{pct}%"></div>
|
||||
@@ -1059,6 +1059,9 @@
|
||||
<Thumbnail src={m.thumbnailUrl} alt={m.title} class="cover" style="object-fit:{store.settings.libraryCropCovers ? 'cover' : 'contain'}" draggable="false" />
|
||||
{#if m.downloadCount}<span class="badge-dl">{m.downloadCount}</span>{/if}
|
||||
{#if m.unreadCount}<span class="badge-unread">{m.unreadCount}</span>{/if}
|
||||
{#if store.libraryUpdates.some(u => u.mangaId === m.id) && !store.acknowledgedUpdates.has(m.id)}
|
||||
<span class="badge-new" aria-label="New chapters"></span>
|
||||
{/if}
|
||||
{#if selectMode}
|
||||
<div class="select-overlay" aria-hidden="true">
|
||||
<div class="select-check" class:checked={isSelected}>
|
||||
@@ -1186,6 +1189,7 @@
|
||||
.cover { width: 100%; height: 100%; transition: filter var(--t-base); will-change: filter; }
|
||||
.badge-dl { position: absolute; bottom: var(--sp-1); right: var(--sp-1); min-width: 18px; height: 18px; padding: 0 3px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: bold; background: var(--accent-dim); color: var(--accent-fg); border-radius: var(--radius-sm); border: 1px solid var(--accent-muted); }
|
||||
.badge-unread { position: absolute; top: var(--sp-1); left: var(--sp-1); min-width: 18px; height: 18px; padding: 0 4px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: bold; background: var(--bg-void); color: var(--text-primary); border-radius: var(--radius-sm); border: 1px solid var(--border-strong); }
|
||||
.badge-new { position: absolute; top: 6px; right: 6px; width: 8px; height: 8px; border-radius: 50%; background: var(--accent-fg); border: 2px solid var(--bg-base); box-shadow: 0 0 6px var(--accent); }
|
||||
|
||||
/* Select overlay (checkbox) */
|
||||
.select-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.18); display: flex; align-items: flex-start; justify-content: flex-end; padding: 6px; pointer-events: none; }
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { GET_MANGA, GET_CHAPTERS, FETCH_CHAPTERS, ENQUEUE_DOWNLOAD, UPDATE_MANGA, MARK_CHAPTER_READ, MARK_CHAPTERS_READ, DELETE_DOWNLOADED_CHAPTERS, ENQUEUE_CHAPTERS_DOWNLOAD, GET_ALL_MANGA, GET_CATEGORIES, CREATE_CATEGORY, UPDATE_MANGA_CATEGORIES } from "../../lib/queries";
|
||||
import { cache, CACHE_KEYS, recordSourceAccess } from "../../lib/cache";
|
||||
import { dedupeMangaById, dedupeMangaByTitle } from "../../lib/util";
|
||||
import { store, addToast, updateSettings, openReader, setActiveManga, setGenreFilter, setNavPage, linkManga, unlinkManga, setPreviewManga, checkAndMarkCompleted as storeCheckAndMarkCompleted, clearMarkersForManga, addBookmark } from "../../store/state.svelte";
|
||||
import { store, addToast, updateSettings, openReader, setActiveManga, setGenreFilter, setNavPage, linkManga, unlinkManga, setPreviewManga, checkAndMarkCompleted as storeCheckAndMarkCompleted, clearMarkersForManga, addBookmark, acknowledgeUpdate } from "../../store/state.svelte";
|
||||
import type { MangaPrefs } from "../../store/state.svelte";
|
||||
import { DEFAULT_MANGA_PREFS } from "../../store/state.svelte";
|
||||
import type { Manga, Chapter, Category } from "../../lib/types";
|
||||
@@ -326,7 +326,7 @@
|
||||
|
||||
$effect(() => {
|
||||
const m = store.activeManga;
|
||||
if (m) untrack(() => { loadManga(m.id); loadChapters(m.id); loadCategories(m.id); });
|
||||
if (m) untrack(() => { acknowledgeUpdate(m.id); loadManga(m.id); loadChapters(m.id); loadCategories(m.id); });
|
||||
});
|
||||
|
||||
let prevChapterId: number | null = null;
|
||||
|
||||
+20
-11
@@ -449,20 +449,22 @@ class Store {
|
||||
readerSessionId: number = $state(0);
|
||||
libraryUpdates: LibraryUpdateEntry[] = $state(saved?.libraryUpdates ?? []);
|
||||
lastLibraryRefresh: number = $state(saved?.lastLibraryRefresh ?? 0);
|
||||
acknowledgedUpdates: Set<number> = $state(new Set(saved?.acknowledgedUpdateIds ?? []));
|
||||
|
||||
constructor() {
|
||||
$effect.root(() => {
|
||||
$effect(() => {
|
||||
persist({
|
||||
settings: this.settings,
|
||||
history: this.history,
|
||||
bookmarks: this.bookmarks,
|
||||
markers: this.markers,
|
||||
readLog: this.readLog,
|
||||
readingStats: this.readingStats,
|
||||
libraryUpdates: this.libraryUpdates,
|
||||
lastLibraryRefresh: this.lastLibraryRefresh,
|
||||
storeVersion: STORE_VERSION,
|
||||
settings: this.settings,
|
||||
history: this.history,
|
||||
bookmarks: this.bookmarks,
|
||||
markers: this.markers,
|
||||
readLog: this.readLog,
|
||||
readingStats: this.readingStats,
|
||||
libraryUpdates: this.libraryUpdates,
|
||||
lastLibraryRefresh: this.lastLibraryRefresh,
|
||||
acknowledgedUpdateIds: [...this.acknowledgedUpdates],
|
||||
storeVersion: STORE_VERSION,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -692,8 +694,14 @@ class Store {
|
||||
}
|
||||
|
||||
clearLibraryUpdates() {
|
||||
this.libraryUpdates = [];
|
||||
this.lastLibraryRefresh = 0;
|
||||
this.libraryUpdates = [];
|
||||
this.lastLibraryRefresh = 0;
|
||||
this.acknowledgedUpdates = new Set();
|
||||
}
|
||||
|
||||
acknowledgeUpdate(mangaId: number) {
|
||||
if (this.acknowledgedUpdates.has(mangaId)) return;
|
||||
this.acknowledgedUpdates = new Set([...this.acknowledgedUpdates, mangaId]);
|
||||
}
|
||||
|
||||
bumpReaderSession() {
|
||||
@@ -733,6 +741,7 @@ export function resetKeybinds() { sto
|
||||
export function clearDiscoverCache() { store.clearDiscoverCache(); }
|
||||
export function setLibraryUpdates(entries: LibraryUpdateEntry[]) { store.setLibraryUpdates(entries); }
|
||||
export function clearLibraryUpdates() { store.clearLibraryUpdates(); }
|
||||
export function acknowledgeUpdate(mangaId: number) { store.acknowledgeUpdate(mangaId); }
|
||||
export function bumpReaderSession() { store.bumpReaderSession(); }
|
||||
export function addBookmark(entry: Omit<BookmarkEntry, "savedAt">, label?: string) { store.addBookmark(entry, label); }
|
||||
export function removeBookmark(chapterId: number) { store.removeBookmark(chapterId); }
|
||||
|
||||
Reference in New Issue
Block a user