diff --git a/backend/app/routes/downloads.py b/backend/app/routes/downloads.py index 1486059..83bb496 100644 --- a/backend/app/routes/downloads.py +++ b/backend/app/routes/downloads.py @@ -279,6 +279,29 @@ def my_usage(user: User = Depends(require_human), db: Session = Depends(get_db)) return quota.usage(db, user.id) +# Priority when a source has several jobs (e.g. a done one plus a fresh queued one). +_INDEX_RANK = {"done": 4, "running": 3, "paused": 2, "queued": 1} + + +@router.get("/index") +def my_download_index( + user: User = Depends(require_human), db: Session = Depends(get_db) +) -> dict: + """source_ref -> best active status, for the feed's per-card "downloaded / queued" badge. + Small + cheap so the feed can poll it without loading the full job list.""" + rows = db.execute( + select(DownloadJob.source_ref, DownloadJob.status).where( + DownloadJob.user_id == user.id, + DownloadJob.status.in_(("queued", "running", "paused", "done")), + ) + ).all() + out: dict[str, str] = {} + for ref, status in rows: + if _INDEX_RANK.get(status, 0) > _INDEX_RANK.get(out.get(ref, ""), 0): + out[ref] = status + return out + + @router.get("/shared") def shared_with_me( user: User = Depends(require_human), db: Session = Depends(get_db) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e05e41e..b33ee87 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -57,6 +57,8 @@ import AdminUsers, { focusAccessRequestsTab } from "./components/AdminUsers"; import SettingsPanel from "./components/SettingsPanel"; import NotificationsPanel from "./components/NotificationsPanel"; import Messages from "./components/Messages"; +import DownloadCenter from "./components/DownloadCenter"; +import { setNavigator } from "./lib/nav"; import ChatDock from "./components/ChatDock"; import OnboardingWizard from "./components/OnboardingWizard"; import { shouldAutoOpenOnboarding } from "./lib/onboarding"; @@ -279,6 +281,8 @@ export default function App() { } go(); } + // Expose the current setPage to decoupled callers (download toast "View", etc.). + useEffect(() => setNavigator(setPage)); function setSidebarLayout(next: SidebarLayout) { setSidebarLayoutState(next); @@ -728,6 +732,8 @@ export default function App() {
+ ) : page === "downloads" && !meQuery.data!.is_demo ? ( + ) : page === "settings" ? (