{stat.label}
++ {stat.value} + {#if stat.suffix} + {stat.suffix} + {/if} +
+diff --git a/src/lib/ui/chrome/Sidebar.svelte b/src/lib/ui/chrome/Sidebar.svelte index 9f4716a..88e71f7 100644 --- a/src/lib/ui/chrome/Sidebar.svelte +++ b/src/lib/ui/chrome/Sidebar.svelte @@ -2,7 +2,7 @@ import { page } from '$app/stores' import { House, Books, MagnifyingGlass, - DownloadSimple, PuzzlePiece, GearSix, ChartLineUp, + DownloadSimple, PuzzlePiece, GearSix, ChartLineUp, ClockCounterClockwise, } from 'phosphor-svelte' import logoUrl from '$lib/assets/moku-icon-wordmark.svg' @@ -13,6 +13,7 @@ { path: '/downloads', label: 'Downloads', icon: DownloadSimple }, { path: '/extensions', label: 'Extensions', icon: PuzzlePiece }, { path: '/tracking', label: 'Tracking', icon: ChartLineUp }, + { path: '/history', label: 'History', icon: ClockCounterClockwise }, ] as const const TAB_SIZE = 36 @@ -42,7 +43,7 @@ {#if activeIndex >= 0}
{/if} - {#each TABS as tab} + {#each TABS as tab (tab.path)} + import { onMount } from 'svelte' + import { BookOpen, Books, ClockCounterClockwise, DownloadSimple } from 'phosphor-svelte' + import { loadLibrary } from '$lib/request-manager/manga' + import { downloadCount } from '$lib/state/downloads.svelte' + import { historyState, initHistoryState } from '$lib/state/history.svelte' + import { libraryState } from '$lib/state/library.svelte' - \ No newline at end of file + const recentHistory = $derived(historyState.history.slice(0, 8)) + + const stats = $derived.by(() => [ + { + label: 'Library Manga', + value: libraryState.items.length, + icon: Books, + }, + { + label: 'Chapters Read', + value: historyState.readingStats.totalChaptersRead, + icon: BookOpen, + }, + { + label: 'Active Downloads', + value: downloadCount, + icon: DownloadSimple, + }, + { + label: 'Current Streak', + value: historyState.readingStats.currentStreakDays, + icon: ClockCounterClockwise, + suffix: 'days', + }, + ]) + + onMount(async () => { + await initHistoryState() + + if (libraryState.items.length === 0) { + await loadLibrary({ inLibrary: true }) + } + }) + + function formatTimestamp(value: number): string { + if (!value) return 'Unknown' + return new Date(value).toLocaleString() + } + + +Dashboard
+Quick read stats and recent progress across your library.
+{stat.label}
++ {stat.value} + {#if stat.suffix} + {stat.suffix} + {/if} +
+{entry.mangaTitle}
+ +browse
\ No newline at end of file + + +{filteredSources.length} available
+Unable to load sources.
+ {extensionsState.error} + +{source.displayName}
+ ++ {currentSource?.lang?.toUpperCase() ?? 'N/A'} + {#if currentSource?.isNsfw} + · NSFW + {/if} +
+Unable to browse this source.
+ {extensionsState.browseError} + +downloads
\ No newline at end of file + + +{downloadCount} total · {activeDownloads.length} active · {queuedDownloads.length} queued
+Unable to load downloads.
+ {downloadsState.error} + +{item.mangaTitle}
+ +extensions
\ No newline at end of file + + +{filteredExtensions.length} shown · {extensionsState.items.length} total
+Unable to load extensions.
+ {extensionsState.error} + +{extension.name}
+ ++ {historyState.history.length} reads · + {historyState.bookmarks.length} bookmarks · + {historyState.readingStats.totalChaptersRead} chapters completed +
+Unable to load local history data.
+ {historyStatus.error} +{entry.mangaTitle}
+ +{entry.mangaTitle}
+ +tracking
\ No newline at end of file + + +{visibleTrackers.length} trackers · {records.length} records
+Unable to load tracking data.
+ {trackingState.error} + +{item.record.title}
+ +