[V1] Created Toaster & Augmented Explore Tab

This commit is contained in:
Youwes09
2026-02-23 11:36:52 -06:00
parent cd2d79f80c
commit 7b61f85833
11 changed files with 704 additions and 137 deletions
+57 -2
View File
@@ -1,12 +1,16 @@
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { gql } from "./lib/client";
import { GET_DOWNLOAD_STATUS } from "./lib/queries";
import "./styles/global.css";
import { useStore } from "./store";
import Layout from "./components/layout/Layout";
import Reader from "./components/pages/Reader";
import Settings from "./components/settings/Settings";
import TitleBar from "./components/layout/TitleBar";
import Toaster from "./components/layout/Toaster";
import type { DownloadStatus, DownloadQueueItem } from "./lib/types";
import s from "./App.module.css";
export default function App() {
@@ -14,6 +18,41 @@ export default function App() {
const settingsOpen = useStore((s) => s.settingsOpen);
const settings = useStore((s) => s.settings);
const setActiveDownloads = useStore((s) => s.setActiveDownloads);
const addToast = useStore((s) => s.addToast);
// Ref-based snapshot of the last known queue so we can diff across polls/events
const prevQueueRef = useRef<DownloadQueueItem[]>([]);
/** Compare old queue → new queue and toast for anything that finished. */
function detectCompletions(prev: DownloadQueueItem[], next: DownloadQueueItem[]) {
for (const item of prev) {
if (item.state !== "DOWNLOADING") continue;
const stillPresent = next.some((q) => q.chapter.id === item.chapter.id);
if (!stillPresent) {
const manga = item.chapter.manga;
addToast({
kind: "success",
title: "Chapter downloaded",
body: manga
? `${manga.title}${item.chapter.name}`
: item.chapter.name,
duration: 4000,
});
}
}
}
function applyQueue(next: DownloadQueueItem[]) {
detectCompletions(prevQueueRef.current, next);
prevQueueRef.current = next;
setActiveDownloads(
next.map((item) => ({
chapterId: item.chapter.id,
mangaId: item.chapter.mangaId,
progress: item.progress,
}))
);
}
useEffect(() => {
document.documentElement.style.zoom = `${settings.uiScale * 1.5}%`;
@@ -33,7 +72,22 @@ export default function App() {
return () => { invoke("kill_server").catch(() => {}); };
}, [settings.autoStartServer, settings.serverBinary]);
// Global Tauri download-progress listener — no polling, always current
// Global download status poller — always running, regardless of which page is open.
// This is the single source of truth for completion toasts.
useEffect(() => {
function poll() {
gql<{ downloadStatus: DownloadStatus }>(GET_DOWNLOAD_STATUS)
.then((d) => applyQueue(d.downloadStatus.queue))
.catch(console.error);
}
poll(); // immediate first fetch
const id = setInterval(poll, 2000);
return () => clearInterval(id);
}, []);
// Tauri real-time event — supplements the poller for instant UI badge updates.
// The payload is a lighter shape (no chapter name/manga), so we only use it
// for active download progress, not for completion detection.
useEffect(() => {
type DlPayload = { chapterId: number; mangaId: number; progress: number }[];
const unsub = listen<DlPayload>("download-progress", (e) => {
@@ -49,6 +103,7 @@ export default function App() {
{activeChapter ? <Reader /> : <Layout />}
</div>
{settingsOpen && <Settings />}
<Toaster />
</div>
);
}