mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19: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 { gql } from "@api/client";
|
||||||
import { GET_RECENTLY_UPDATED, GET_CHAPTERS } from "@api/queries";
|
import { GET_RECENTLY_UPDATED, GET_CHAPTERS } from "@api/queries";
|
||||||
import { store, openReader, setActiveManga, addToast } from "@store/state.svelte";
|
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 { buildReaderChapterList } from "@features/series/lib/chapterList";
|
||||||
import Thumbnail from "@shared/manga/Thumbnail.svelte";
|
import Thumbnail from "@shared/manga/Thumbnail.svelte";
|
||||||
import type { Chapter, Manga } from "@types";
|
import type { Chapter, Manga } from "@types";
|
||||||
@@ -48,6 +48,24 @@
|
|||||||
return Array.from(map.entries()).map(([label, items]) => ({ label, items })) as UpdateGroup[];
|
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 {
|
function mangaStub(item: RecentUpdate): Manga {
|
||||||
return {
|
return {
|
||||||
id: item.manga?.id ?? item.mangaId,
|
id: item.manga?.id ?? item.mangaId,
|
||||||
@@ -116,7 +134,8 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="heading-group">
|
<div class="heading-group">
|
||||||
<ArrowsClockwise size={13} weight="light" class="heading-icon" />
|
<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>
|
</div>
|
||||||
<button class="icon-btn" onclick={loadUpdates} disabled={loading} title="Refresh updates">
|
<button class="icon-btn" onclick={loadUpdates} disabled={loading} title="Refresh updates">
|
||||||
{#if loading}<CircleNotch size={14} weight="light" class="anim-spin" />
|
{#if loading}<CircleNotch size={14} weight="light" class="anim-spin" />
|
||||||
@@ -173,12 +192,11 @@
|
|||||||
|
|
||||||
<span class="chapter-title">{chapterLabel(item)}</span>
|
<span class="chapter-title">{chapterLabel(item)}</span>
|
||||||
|
|
||||||
<div class="meta-row">
|
{#if (item.lastPageRead ?? 0) > 0 && !item.isRead}
|
||||||
<span>{timeAgo(fetchedAtMs(item))}</span>
|
<div class="meta-row">
|
||||||
{#if (item.lastPageRead ?? 0) > 0 && !item.isRead}
|
<span>Resume p.{item.lastPageRead}</span>
|
||||||
<span>· Resume p.{item.lastPageRead}</span>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row-end">
|
<div class="row-end">
|
||||||
@@ -231,6 +249,14 @@
|
|||||||
text-transform: uppercase;
|
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 {
|
.icon-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { store } from "@store/state.svelte";
|
import { store } from "@store/state.svelte";
|
||||||
import Sidebar from "@shared/chrome/Sidebar.svelte";
|
import Sidebar from "@shared/chrome/Sidebar.svelte";
|
||||||
import RecentActivity from "@shared/chrome/RecentActivity.svelte";
|
|
||||||
import Library from "@features/library/components/Library.svelte";
|
import Library from "@features/library/components/Library.svelte";
|
||||||
import SeriesDetail from "@features/series/components/SeriesDetail.svelte";
|
import SeriesDetail from "@features/series/components/SeriesDetail.svelte";
|
||||||
import Home from "@features/home/components/Home.svelte";
|
import Home from "@features/home/components/Home.svelte";
|
||||||
@@ -10,7 +9,7 @@
|
|||||||
import Downloads from "@features/downloads/components/Downloads.svelte";
|
import Downloads from "@features/downloads/components/Downloads.svelte";
|
||||||
import Extensions from "@features/extensions/components/Extensions.svelte";
|
import Extensions from "@features/extensions/components/Extensions.svelte";
|
||||||
import Tracking from "@features/tracking/components/Tracking.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>
|
</script>
|
||||||
|
|
||||||
<div class="frame">
|
<div class="frame">
|
||||||
@@ -25,12 +24,10 @@
|
|||||||
<Home />
|
<Home />
|
||||||
{:else if store.navPage === "library"}
|
{:else if store.navPage === "library"}
|
||||||
<Library />
|
<Library />
|
||||||
{:else if store.navPage === "updates"}
|
|
||||||
<Updates />
|
|
||||||
{:else if store.navPage === "search"}
|
{:else if store.navPage === "search"}
|
||||||
<Search />
|
<Search />
|
||||||
{:else if store.navPage === "history"}
|
{:else if store.navPage === "history"}
|
||||||
<RecentActivity />
|
<Recent />
|
||||||
{:else if store.navPage === "downloads"}
|
{:else if store.navPage === "downloads"}
|
||||||
<Downloads />
|
<Downloads />
|
||||||
{:else if store.navPage === "extensions"}
|
{:else if store.navPage === "extensions"}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
<script lang="ts">
|
<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 { store } from "@store/state.svelte";
|
||||||
import type { NavPage } from "@store/state.svelte";
|
import type { NavPage } from "@store/state.svelte";
|
||||||
|
|
||||||
const TABS: { id: NavPage; label: string; icon: any }[] = [
|
const TABS: { id: NavPage; label: string; icon: any }[] = [
|
||||||
{ id: "home", label: "Home", icon: House },
|
{ id: "home", label: "Home", icon: House },
|
||||||
{ id: "library", label: "Library", icon: Books },
|
{ id: "library", label: "Library", icon: Books },
|
||||||
{ id: "updates", label: "Updates", icon: ArrowsClockwise },
|
|
||||||
{ id: "search", label: "Search", icon: MagnifyingGlass },
|
{ 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: "downloads", label: "Downloads", icon: DownloadSimple },
|
||||||
{ id: "extensions", label: "Extensions", icon: PuzzlePiece },
|
{ id: "extensions", label: "Extensions", icon: PuzzlePiece },
|
||||||
{ id: "tracking", label: "Tracking", icon: ChartLineUp },
|
{ id: "tracking", label: "Tracking", icon: ChartLineUp },
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export type NavPage =
|
export type NavPage =
|
||||||
| "home" | "library" | "updates" | "sources" | "explore"
|
| "home" | "library" | "sources" | "explore"
|
||||||
| "downloads" | "extensions" | "history" | "search" | "tracking";
|
| "downloads" | "extensions" | "history" | "search" | "tracking";
|
||||||
|
|
||||||
class AppStore {
|
class AppStore {
|
||||||
|
|||||||
Reference in New Issue
Block a user