mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 01:09:56 -05:00
Integrate updates into recent activity page
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<script lang="ts">
|
||||
import HistoryPanel from "./HistoryPanel.svelte";
|
||||
import UpdatesPanel from "./UpdatesPanel.svelte";
|
||||
|
||||
type RecentTab = "updates" | "history";
|
||||
let tab = $state<RecentTab>("updates");
|
||||
</script>
|
||||
|
||||
<div class="root anim-fade-in">
|
||||
<div class="header">
|
||||
<span class="heading">Recent</span>
|
||||
<div class="tabs">
|
||||
<button class="tab" class:active={tab === "updates"} onclick={() => tab = "updates"}>
|
||||
Updates
|
||||
</button>
|
||||
<button class="tab" class:active={tab === "history"} onclick={() => tab = "history"}>
|
||||
Reading history
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
{#if tab === "updates"}
|
||||
<UpdatesPanel />
|
||||
{:else}
|
||||
<HistoryPanel />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-4);
|
||||
padding: var(--sp-4) var(--sp-6);
|
||||
border-bottom: 1px solid var(--border-dim);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-family: var(--font-ui);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--weight-medium);
|
||||
color: var(--text-muted);
|
||||
letter-spacing: var(--tracking-wider);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--border-dim);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-family: var(--font-ui);
|
||||
font-size: var(--text-2xs);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
text-transform: uppercase;
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-faint);
|
||||
white-space: nowrap;
|
||||
transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.tab:hover { color: var(--text-muted); }
|
||||
.tab.active { background: var(--accent-muted); color: var(--accent-fg); border-color: var(--accent-dim); }
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
+34
-8
@@ -4,7 +4,7 @@
|
||||
import { gql } from "@api/client";
|
||||
import { GET_RECENTLY_UPDATED, GET_CHAPTERS } from "@api/queries";
|
||||
import { store, openReader, setActiveManga, addToast } from "@store/state.svelte";
|
||||
import { dayLabel, timeAgo } from "@core/util";
|
||||
import { dayLabel } from "@core/util";
|
||||
import { buildReaderChapterList } from "@features/series/lib/chapterList";
|
||||
import Thumbnail from "@shared/manga/Thumbnail.svelte";
|
||||
import type { Chapter, Manga } from "@types";
|
||||
@@ -48,6 +48,24 @@
|
||||
return Array.from(map.entries()).map(([label, items]) => ({ label, items })) as UpdateGroup[];
|
||||
});
|
||||
|
||||
const lastUpdatedTs = $derived(
|
||||
store.lastLibraryRefresh > 0
|
||||
? store.lastLibraryRefresh
|
||||
: (updates.length > 0 ? fetchedAtMs(updates[0]) : null)
|
||||
);
|
||||
|
||||
const lastUpdatedLabel = $derived(
|
||||
lastUpdatedTs
|
||||
? new Date(lastUpdatedTs).toLocaleString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: "Never"
|
||||
);
|
||||
|
||||
function mangaStub(item: RecentUpdate): Manga {
|
||||
return {
|
||||
id: item.manga?.id ?? item.mangaId,
|
||||
@@ -116,7 +134,8 @@
|
||||
<div class="header">
|
||||
<div class="heading-group">
|
||||
<ArrowsClockwise size={13} weight="light" class="heading-icon" />
|
||||
<span class="heading">Updates</span>
|
||||
<span class="heading">Library updates</span>
|
||||
<span class="last-updated">Last updated: {lastUpdatedLabel}</span>
|
||||
</div>
|
||||
<button class="icon-btn" onclick={loadUpdates} disabled={loading} title="Refresh updates">
|
||||
{#if loading}<CircleNotch size={14} weight="light" class="anim-spin" />
|
||||
@@ -173,12 +192,11 @@
|
||||
|
||||
<span class="chapter-title">{chapterLabel(item)}</span>
|
||||
|
||||
<div class="meta-row">
|
||||
<span>{timeAgo(fetchedAtMs(item))}</span>
|
||||
{#if (item.lastPageRead ?? 0) > 0 && !item.isRead}
|
||||
<span>· Resume p.{item.lastPageRead}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if (item.lastPageRead ?? 0) > 0 && !item.isRead}
|
||||
<div class="meta-row">
|
||||
<span>Resume p.{item.lastPageRead}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="row-end">
|
||||
@@ -231,6 +249,14 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.last-updated {
|
||||
font-family: var(--font-ui);
|
||||
font-size: var(--text-2xs);
|
||||
color: var(--text-faint);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { store } from "@store/state.svelte";
|
||||
import Sidebar from "@shared/chrome/Sidebar.svelte";
|
||||
import RecentActivity from "@shared/chrome/RecentActivity.svelte";
|
||||
import Library from "@features/library/components/Library.svelte";
|
||||
import SeriesDetail from "@features/series/components/SeriesDetail.svelte";
|
||||
import Home from "@features/home/components/Home.svelte";
|
||||
@@ -10,7 +9,7 @@
|
||||
import Downloads from "@features/downloads/components/Downloads.svelte";
|
||||
import Extensions from "@features/extensions/components/Extensions.svelte";
|
||||
import Tracking from "@features/tracking/components/Tracking.svelte";
|
||||
import Updates from "@features/updates/components/Updates.svelte";
|
||||
import Recent from "@features/recent/components/Recent.svelte";
|
||||
</script>
|
||||
|
||||
<div class="frame">
|
||||
@@ -25,12 +24,10 @@
|
||||
<Home />
|
||||
{:else if store.navPage === "library"}
|
||||
<Library />
|
||||
{:else if store.navPage === "updates"}
|
||||
<Updates />
|
||||
{:else if store.navPage === "search"}
|
||||
<Search />
|
||||
{:else if store.navPage === "history"}
|
||||
<RecentActivity />
|
||||
<Recent />
|
||||
{:else if store.navPage === "downloads"}
|
||||
<Downloads />
|
||||
{:else if store.navPage === "extensions"}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { House, Books, MagnifyingGlass, ArrowsClockwise, ClockCounterClockwise, DownloadSimple, PuzzlePiece, GearSix, ChartLineUp } from "phosphor-svelte";
|
||||
import { House, Books, MagnifyingGlass, ClockCounterClockwise, DownloadSimple, PuzzlePiece, GearSix, ChartLineUp } from "phosphor-svelte";
|
||||
import { store } from "@store/state.svelte";
|
||||
import type { NavPage } from "@store/state.svelte";
|
||||
|
||||
const TABS: { id: NavPage; label: string; icon: any }[] = [
|
||||
{ id: "home", label: "Home", icon: House },
|
||||
{ id: "library", label: "Library", icon: Books },
|
||||
{ id: "updates", label: "Updates", icon: ArrowsClockwise },
|
||||
{ id: "search", label: "Search", icon: MagnifyingGlass },
|
||||
{ id: "history", label: "History", icon: ClockCounterClockwise },
|
||||
{ id: "history", label: "Recent", icon: ClockCounterClockwise },
|
||||
{ id: "downloads", label: "Downloads", icon: DownloadSimple },
|
||||
{ id: "extensions", label: "Extensions", icon: PuzzlePiece },
|
||||
{ id: "tracking", label: "Tracking", icon: ChartLineUp },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type NavPage =
|
||||
| "home" | "library" | "updates" | "sources" | "explore"
|
||||
| "home" | "library" | "sources" | "explore"
|
||||
| "downloads" | "extensions" | "history" | "search" | "tracking";
|
||||
|
||||
class AppStore {
|
||||
|
||||
Reference in New Issue
Block a user