add websockets to get downloads list

This commit is contained in:
2026-02-20 22:54:26 +03:00
parent e87d02b1cb
commit de495c0f78
8 changed files with 121 additions and 43 deletions

View File

@@ -1,27 +1,62 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { useSearchQuery, type Album } from "./api/useSearchQuery";
import { useDebouncedValue } from "./hooks/useDebouncedValue";
import { getContrastYIQ } from "./getContrastYIQ";
import { useDownloadAlbumMutation } from "./api/useDownloadAlbumMutation";
import { useDownloadsQuery } from "./api/useDownloadsQuery";
import toast from "react-hot-toast";
type DownloadItem = {
id: number;
status: "ADDED" | "IN_PROGRESS" | "COMPLETED";
album: Album;
installedFiles: number;
totalFiles: number;
};
export default function App() {
const [search, setSearch] = useState("");
const debouncedSearch = useDebouncedValue(search);
const [downloads, setDownloads] = useState<DownloadItem[]>([]);
const { data } = useSearchQuery({
query: debouncedSearch,
});
const { data: downloads } = useDownloadsQuery();
const downloadAlbumMutation = useDownloadAlbumMutation();
useEffect(() => {
const ws = new WebSocket("/ws/downloads");
ws.onopen = () => {
console.log("ws connection opened");
};
ws.onclose = () => {
console.log("ws connection closed");
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data) as { downloads: DownloadItem[] };
const items = [...data.downloads].sort((a, b) => {
if (a.status === "COMPLETED" && b.status !== "COMPLETED") {
return -1;
}
if (a.status !== "COMPLETED" && b.status === "COMPLETED") {
return 1;
}
return a.album.title.localeCompare(b.album.title);
});
setDownloads(items);
};
}, []);
const handleDownloadAlbum = (album: Album) => {
downloadAlbumMutation.mutate(
{ albumId: album.id },
{
onError(error) {
console.log({ error });
toast.error(error.message);
},
},
);
@@ -42,6 +77,7 @@ export default function App() {
/>
<div>
<p>{item.album.title}</p>
<p>Status: {formatDownloadStatus(item.status)}</p>
<p>
Downloaded {item.installedFiles}/{item.totalFiles}
</p>
@@ -102,3 +138,14 @@ export default function App() {
</div>
);
}
const formatDownloadStatus = (status: DownloadItem["status"]): string => {
switch (status) {
case "ADDED":
return "added";
case "IN_PROGRESS":
return "in progress";
case "COMPLETED":
return "completed";
}
};

View File

@@ -1,19 +0,0 @@
import { useQuery } from "@tanstack/react-query";
import type { Album } from "./useSearchQuery";
export type DownloadItem = {
id: number;
album: Album;
installedFiles: number;
totalFiles: number;
};
export const useDownloadsQuery = () => {
return useQuery({
queryKey: ["downloads"],
queryFn: async () => {
const resp = await fetch("/downloads");
return (await resp.json()) as DownloadItem[];
},
});
};

View File

@@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.tsx";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "react-hot-toast";
const queryClient = new QueryClient({
defaultOptions: {
@@ -15,6 +16,7 @@ const queryClient = new QueryClient({
createRoot(document.getElementById("root")!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<Toaster />
<App />
</QueryClientProvider>
</StrictMode>,