Fix: Trigger Recently-Fetched Data for RecentActivity (#63)

This commit is contained in:
Youwes09
2026-05-03 13:06:02 -05:00
parent 0d53e3f102
commit 7b2ae74c02
3 changed files with 60 additions and 32 deletions
@@ -2,19 +2,26 @@
import { Play, ArrowRight, BookOpen, Clock } from "phosphor-svelte"; import { Play, ArrowRight, BookOpen, Clock } from "phosphor-svelte";
import Thumbnail from "@shared/manga/Thumbnail.svelte"; import Thumbnail from "@shared/manga/Thumbnail.svelte";
import type { HistoryEntry } from "@store/state.svelte"; import type { HistoryEntry } from "@store/state.svelte";
import type { Manga } from "@types";
import { timeAgo } from "../lib/homeHelpers"; import { timeAgo } from "../lib/homeHelpers";
let { let {
entries, entries,
libraryManga,
onresume, onresume,
onviewhistory, onviewhistory,
onopenlibrary, onopenlibrary,
}: { }: {
entries: HistoryEntry[]; entries: HistoryEntry[];
libraryManga: Manga[];
onresume: (entry: HistoryEntry) => void; onresume: (entry: HistoryEntry) => void;
onviewhistory: () => void; onviewhistory: () => void;
onopenlibrary: () => void; onopenlibrary: () => void;
} = $props(); } = $props();
function thumbFor(entry: HistoryEntry): string {
return libraryManga.find(m => m.id === entry.mangaId)?.thumbnailUrl ?? entry.thumbnailUrl ?? "";
}
</script> </script>
<div class="section"> <div class="section">
@@ -31,7 +38,7 @@
{#if entries.length > 0} {#if entries.length > 0}
{#each entries as entry (entry.chapterId)} {#each entries as entry (entry.chapterId)}
<button class="row" onclick={() => onresume(entry)}> <button class="row" onclick={() => onresume(entry)}>
<Thumbnail src={entry.thumbnailUrl} alt={entry.mangaTitle} class="row-thumb" /> <Thumbnail src={thumbFor(entry)} alt={entry.mangaTitle} class="row-thumb" />
<div class="row-info"> <div class="row-info">
<span class="row-title">{entry.mangaTitle}</span> <span class="row-title">{entry.mangaTitle}</span>
<span class="row-sub"> <span class="row-sub">
@@ -191,4 +198,4 @@
.placeholder-cta:hover { background: rgba(255,255,255,0.14); color: rgba(255,255,255,0.9); } .placeholder-cta:hover { background: rgba(255,255,255,0.14); color: rgba(255,255,255,0.9); }
@keyframes pulse { 0%, 100% { opacity: 0.4 } 50% { opacity: 0.7 } } @keyframes pulse { 0%, 100% { opacity: 0.4 } 50% { opacity: 0.7 } }
</style> </style>
+30 -28
View File
@@ -86,11 +86,22 @@
let activeIdx = $state(0); let activeIdx = $state(0);
const activeSlot = $derived(resolvedSlots[activeIdx]); const activeSlot = $derived(resolvedSlots[activeIdx]);
const heroThumbSrc = $derived(
activeSlot?.kind === "pinned" ? (activeSlot.manga?.thumbnailUrl ?? "") : const heroManga = $derived(
activeSlot?.kind === "continue" ? (activeSlot.entry?.thumbnailUrl ?? "") : "" activeSlot?.kind === "pinned" ? activeSlot.manga :
activeSlot?.kind === "continue" ? libraryManga.find(m => m.id === activeSlot.entry?.mangaId) : null
); );
const heroEntry = $derived(activeSlot?.kind === "continue" ? activeSlot.entry : null);
const heroMangaId = $derived(heroManga?.id ?? heroEntry?.mangaId ?? null);
const heroTitle = $derived(heroManga?.title ?? heroEntry?.mangaTitle ?? "");
const heroThumbSrc = $derived(
heroManga?.thumbnailUrl
?? (activeSlot?.kind === "continue" ? activeSlot.entry?.thumbnailUrl : undefined)
?? ""
);
let heroThumb = $state(""); let heroThumb = $state("");
$effect(() => { $effect(() => {
const path = heroThumbSrc; const path = heroThumbSrc;
@@ -100,21 +111,8 @@
.catch(() => { heroThumb = ""; }); .catch(() => { heroThumb = ""; });
}); });
const heroTitle = $derived(
activeSlot?.kind === "pinned" ? (activeSlot.manga?.title ?? "") :
activeSlot?.kind === "continue" ? (activeSlot.entry?.mangaTitle ?? "") : ""
);
const heroManga = $derived(
activeSlot?.kind === "pinned" ? activeSlot.manga :
activeSlot?.kind === "continue" ? libraryManga.find(m => m.id === activeSlot.entry?.mangaId) : null
);
const heroEntry = $derived(activeSlot?.kind === "continue" ? activeSlot.entry : null);
const heroMangaId = $derived(heroEntry?.mangaId ?? heroManga?.id ?? null);
const heroNewChapter = $derived( const heroNewChapter = $derived(
heroManga heroManga ? (libraryManga.find(m => m.id === heroManga!.id) as any)?.latestUploadedChapter ?? null : null
? (libraryManga.find(m => m.id === heroManga!.id) as any)?.latestUploadedChapter ?? null
: null
); );
function cycleNext() { activeIdx = (activeIdx + 1) % TOTAL_SLOTS; heroChapters = []; heroAllChapters = []; } function cycleNext() { activeIdx = (activeIdx + 1) % TOTAL_SLOTS; heroChapters = []; heroAllChapters = []; }
@@ -158,6 +156,10 @@
let resuming = $state(false); let resuming = $state(false);
function liveMangaStub(): Manga {
return heroManga ?? { id: heroMangaId!, title: heroTitle, thumbnailUrl: heroThumbSrc } as any;
}
async function openChapter(chapter: Chapter) { async function openChapter(chapter: Chapter) {
if (!heroMangaId) return; if (!heroMangaId) return;
resuming = true; resuming = true;
@@ -168,13 +170,12 @@
all = [...d.chapters.nodes].sort((a, b) => a.sourceOrder - b.sourceOrder); all = [...d.chapters.nodes].sort((a, b) => a.sourceOrder - b.sourceOrder);
} }
if (all.length) { if (all.length) {
const manga = heroManga ?? { id: heroMangaId, title: heroTitle, thumbnailUrl: heroManga?.thumbnailUrl ?? "" } as any; store.activeManga = liveMangaStub();
store.activeManga = manga;
const list = buildReaderChapterList(all, store.settings.mangaPrefs?.[heroMangaId]); const list = buildReaderChapterList(all, store.settings.mangaPrefs?.[heroMangaId]);
const target = list.find(c => c.id === chapter.id) ?? list[0]; const target = list.find(c => c.id === chapter.id) ?? list[0];
if (target) openReader(target, list); if (target) openReader(target, list);
} }
} catch { store.activeManga = { id: heroMangaId, title: heroTitle, thumbnailUrl: heroManga?.thumbnailUrl ?? "" } as any; } } catch { store.activeManga = liveMangaStub(); }
finally { resuming = false; } finally { resuming = false; }
} }
@@ -190,24 +191,24 @@
const list = buildReaderChapterList(raw, store.settings.mangaPrefs?.[heroEntry.mangaId]); const list = buildReaderChapterList(raw, store.settings.mangaPrefs?.[heroEntry.mangaId]);
const ch = list.find(c => c.id === heroEntry!.chapterId) ?? list[0]; const ch = list.find(c => c.id === heroEntry!.chapterId) ?? list[0];
if (ch) { if (ch) {
store.activeManga = heroManga ?? { id: heroEntry.mangaId, title: heroEntry.mangaTitle, thumbnailUrl: heroEntry.thumbnailUrl } as any; store.activeManga = liveMangaStub();
openReader(ch, list); openReader(ch, list);
} }
} catch { store.activeManga = { id: heroEntry.mangaId, title: heroEntry.mangaTitle, thumbnailUrl: heroEntry.thumbnailUrl } as any; } } catch { store.activeManga = liveMangaStub(); }
finally { resuming = false; } finally { resuming = false; }
} }
async function resumeEntry(entry: HistoryEntry) { async function resumeEntry(entry: HistoryEntry) {
const liveManga = libraryManga.find(m => m.id === entry.mangaId);
const stub = liveManga ?? { id: entry.mangaId, title: entry.mangaTitle, thumbnailUrl: liveManga?.thumbnailUrl ?? entry.thumbnailUrl } as any;
try { try {
const d = await gql<{ chapters: { nodes: Chapter[] } }>(GET_CHAPTERS, { mangaId: entry.mangaId }); const d = await gql<{ chapters: { nodes: Chapter[] } }>(GET_CHAPTERS, { mangaId: entry.mangaId });
const raw = [...d.chapters.nodes].sort((a, b) => a.sourceOrder - b.sourceOrder); const raw = [...d.chapters.nodes].sort((a, b) => a.sourceOrder - b.sourceOrder);
const list = buildReaderChapterList(raw, store.settings.mangaPrefs?.[entry.mangaId]); const list = buildReaderChapterList(raw, store.settings.mangaPrefs?.[entry.mangaId]);
const ch = list.find(c => c.id === entry.chapterId) ?? list[0]; const ch = list.find(c => c.id === entry.chapterId) ?? list[0];
if (ch) { store.activeManga = stub;
store.activeManga = { id: entry.mangaId, title: entry.mangaTitle, thumbnailUrl: entry.thumbnailUrl } as any; if (ch) openReader(ch, list);
openReader(ch, list); } catch { store.activeManga = stub; }
} else store.activeManga = { id: entry.mangaId, title: entry.mangaTitle, thumbnailUrl: entry.thumbnailUrl } as any;
} catch { store.activeManga = { id: entry.mangaId, title: entry.mangaTitle, thumbnailUrl: entry.thumbnailUrl } as any; }
} }
let pickerOpen = $state(false); let pickerOpen = $state(false);
@@ -256,6 +257,7 @@
<div class="mid-left"> <div class="mid-left">
<ActivityFeed <ActivityFeed
entries={recentHistory} entries={recentHistory}
{libraryManga}
onresume={resumeEntry} onresume={resumeEntry}
onviewhistory={() => setNavPage("history")} onviewhistory={() => setNavPage("history")}
onopenlibrary={() => setNavPage("library")} onopenlibrary={() => setNavPage("library")}
+21 -2
View File
@@ -1,10 +1,29 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte";
import { ClockCounterClockwise, Trash, MagnifyingGlass, Books, Fire, BookOpen, Clock, TrendUp } from "phosphor-svelte"; import { ClockCounterClockwise, Trash, MagnifyingGlass, Books, Fire, BookOpen, Clock, TrendUp } from "phosphor-svelte";
import Thumbnail from "@shared/manga/Thumbnail.svelte"; import Thumbnail from "@shared/manga/Thumbnail.svelte";
import { store, clearHistory, setPreviewManga } from "@store/state.svelte"; import { store, clearHistory, setPreviewManga } from "@store/state.svelte";
import { gql } from "@api/client";
import { GET_LIBRARY } from "@api/queries/manga";
import { cache, CACHE_KEYS } from "@core/cache";
import type { HistoryEntry } from "@store/state.svelte"; import type { HistoryEntry } from "@store/state.svelte";
import type { Manga } from "@types";
import { timeAgo, dayLabel, formatReadTime } from "@core/util"; import { timeAgo, dayLabel, formatReadTime } from "@core/util";
let libraryManga = $state<Manga[]>([]);
onMount(() => {
cache.get(CACHE_KEYS.LIBRARY, () =>
gql<{ mangas: { nodes: Manga[] } }>(GET_LIBRARY).then(d => d.mangas.nodes)
)
.then(m => { libraryManga = m; })
.catch(() => {});
});
function thumbFor(mangaId: number, fallback: string): string {
return libraryManga.find(m => m.id === mangaId)?.thumbnailUrl ?? fallback ?? "";
}
let search = $state(""); let search = $state("");
let confirmClear = $state(false); let confirmClear = $state(false);
@@ -173,9 +192,9 @@
</div> </div>
<div class="session-list"> <div class="session-list">
{#each items as session (session.latestChapterId)} {#each items as session (session.latestChapterId)}
<button class="session-row" onclick={() => setPreviewManga({ id: session.mangaId, title: session.mangaTitle, thumbnailUrl: session.thumbnailUrl } as any)}> <button class="session-row" onclick={() => setPreviewManga({ id: session.mangaId, title: session.mangaTitle, thumbnailUrl: thumbFor(session.mangaId, session.thumbnailUrl) } as any)}>
<div class="thumb-wrap"> <div class="thumb-wrap">
<Thumbnail src={session.thumbnailUrl} alt={session.mangaTitle} class="thumb" /> <Thumbnail src={thumbFor(session.mangaId, session.thumbnailUrl)} alt={session.mangaTitle} class="thumb" />
{#if session.chapterCount > 1} {#if session.chapterCount > 1}
<span class="session-count">{session.chapterCount}</span> <span class="session-count">{session.chapterCount}</span>
{/if} {/if}