add websockets to get downloads list
This commit is contained in:
@@ -12,7 +12,8 @@
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
"react-dom": "^19.2.0",
|
||||
"react-hot-toast": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
|
||||
26
web/pnpm-lock.yaml
generated
26
web/pnpm-lock.yaml
generated
@@ -20,6 +20,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^19.2.0
|
||||
version: 19.2.4(react@19.2.4)
|
||||
react-hot-toast:
|
||||
specifier: ^2.6.0
|
||||
version: 2.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^9.39.1
|
||||
@@ -742,6 +745,11 @@ packages:
|
||||
resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
goober@2.1.18:
|
||||
resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==}
|
||||
peerDependencies:
|
||||
csstype: ^3.0.10
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
@@ -975,6 +983,13 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^19.2.4
|
||||
|
||||
react-hot-toast@2.6.0:
|
||||
resolution: {integrity: sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: '>=16'
|
||||
react-dom: '>=16'
|
||||
|
||||
react-refresh@0.18.0:
|
||||
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1836,6 +1851,10 @@ snapshots:
|
||||
|
||||
globals@16.5.0: {}
|
||||
|
||||
goober@2.1.18(csstype@3.2.3):
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
@@ -2015,6 +2034,13 @@ snapshots:
|
||||
react: 19.2.4
|
||||
scheduler: 0.27.0
|
||||
|
||||
react-hot-toast@2.6.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
goober: 2.1.18(csstype@3.2.3)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
react-refresh@0.18.0: {}
|
||||
|
||||
react@19.2.4: {}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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[];
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -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>,
|
||||
|
||||
Reference in New Issue
Block a user