From fe6100bb796490a18dbbc8697f550abdfed7a1c3 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Thu, 26 Sep 2024 21:32:28 -0400 Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=94=A7=20Fixing=20broken=20AirDCPP=20?= =?UTF-8?q?search?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ComicDetail/AcquisitionPanel.tsx | 295 +++++++++--------- src/client/shared/utils/metadata.utils.ts | 4 +- 2 files changed, 148 insertions(+), 151 deletions(-) diff --git a/src/client/components/ComicDetail/AcquisitionPanel.tsx b/src/client/components/ComicDetail/AcquisitionPanel.tsx index e27e753..8687a03 100644 --- a/src/client/components/ComicDetail/AcquisitionPanel.tsx +++ b/src/client/components/ComicDetail/AcquisitionPanel.tsx @@ -35,10 +35,7 @@ export const AcquisitionPanel = ( priority: PriorityEnum; } interface SearchResult { - result: { id: number; - }; - search_id: number; // Add other properties as needed } @@ -46,7 +43,6 @@ export const AcquisitionPanel = ( // Use the already connected socket instance to emit events socketIOInstance.emit("initiateSearch", searchQuery); }; - const { data: settings, isLoading, @@ -122,31 +118,31 @@ export const AcquisitionPanel = ( password: `pass`, }, }, - (data: any) => console.log(data), ); }; - socketIOInstance.on("searchResultAdded", ({ groupedResult }: any) => { + socketIOInstance.on("searchResultAdded", ({ result }: any) => { + console.log("yelaweda", result); setAirDCPPSearchResults((previousState) => { const exists = previousState.some( - (item) => groupedResult.result.id === item.result.id, + (item) => result.id === item.id, ); if (!exists) { - return [...previousState, groupedResult]; + return [...previousState, result]; } return previousState; }); }); - socketIOInstance.on("searchResultUpdated", ({ updatedResult }: any) => { - console.log("endh"); + socketIOInstance.on("searchResultUpdated", ({ result }: any) => { + console.log("endh", result); // ...update properties of the existing result in the UI const bundleToUpdateIndex = airDCPPSearchResults?.findIndex( - (bundle) => bundle.result.id === updatedResult.result.id, + (bundle) => bundle.id === result.id, ); const updatedState = [...airDCPPSearchResults]; - if (!isNil(difference(updatedState[bundleToUpdateIndex], updatedResult))) { - updatedState[bundleToUpdateIndex] = updatedResult; + if (!isNil(difference(updatedState[bundleToUpdateIndex], result))) { + updatedState[bundleToUpdateIndex] = result; } setAirDCPPSearchResults((state) => [...state, ...updatedState]); }); @@ -205,7 +201,141 @@ export const AcquisitionPanel = ( search(manualQuery); }; - + console.log(airDCPPSearchResults); + // const comment = `
+ // + // + // + // + // + // + // + // + // + // + // {map(airDCPPSearchResults, ({ result, search_id }, idx) => { + // return ( + // + // + // + // + // + // + // ); + // })} + // + //
+ // Name + // + // Type + // + // Slots + // + // Actions + //
+ //

+ // {result.type.id === "directory" ? ( + // + // ) : null} + // {ellipsize(result.name, 70)} + //

+ // + //
+ //
+ //
+ // {!isNil(result.dupe) ? ( + // + // + // + // + // + // + // Dupe + // + // + // ) : null} + // + // {/* Nicks */} + // + // + // + // + // + // + // {result.users.user.nicks} + // + // + // {/* Flags */} + // {result.users.user.flags.map((flag, idx) => ( + // + // + // + // + // + // + // {flag} + // + // + // ))} + //
+ //
+ //
+ //
+ // {/* Extension */} + // + // + // + // + // + // + // {result.type.str} + // + // + // + // {/* Slots */} + // + // + // + // + // + // + // {result.slots.total} slots; {result.slots.free} free + // + // + // + // + //
+ //
`; return ( <>
@@ -313,142 +443,9 @@ export const AcquisitionPanel = ( )} {/* AirDC++ results */} -
+
{!isNil(airDCPPSearchResults) && !isEmpty(airDCPPSearchResults) ? ( -
- - - - - - - - - - - {map(airDCPPSearchResults, ({ result, search_id }, idx) => { - return ( - - - - - - - ); - })} - -
- Name - - Type - - Slots - - Actions -
-

- {result.type.id === "directory" ? ( - - ) : null} - {ellipsize(result.name, 70)} -

- -
-
-
- {!isNil(result.dupe) ? ( - - - - - - - Dupe - - - ) : null} - - {/* Nicks */} - - - - - - - {result.users.user.nicks} - - - {/* Flags */} - {result.users.user.flags.map((flag, idx) => ( - - - - - - - {flag} - - - ))} -
-
-
-
- {/* Extension */} - - - - - - - {result.type.str} - - - - {/* Slots */} - - - - - - - {result.slots.total} slots; {result.slots.free} free - - - - -
-
+ <> ) : (
{ // comicvine if (!isEmpty(data.comicvine)) { coverFile.comicvine.url = data?.comicvine?.image.small_url; - coverFile.comicvine.issueName = data.comicvine.name; - coverFile.comicvine.publisher = data.comicvine.publisher.name; + coverFile.comicvine.issueName = data.comicvine?.name; + coverFile.comicvine.publisher = data.comicvine?.publisher?.name; } // rawFileDetails if (!isEmpty(data.rawFileDetails)) { -- 2.49.1 From b06c85d6dc9114fa8b662867d0ea6423d0146416 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Sun, 13 Oct 2024 23:42:03 -0400 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=94=A7=20Fixing=20broken=20DC++=20dow?= =?UTF-8?q?nloads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ComicDetail/AcquisitionPanel.tsx | 281 +++++++++--------- .../components/ComicDetail/DownloadsPanel.tsx | 9 +- 2 files changed, 147 insertions(+), 143 deletions(-) diff --git a/src/client/components/ComicDetail/AcquisitionPanel.tsx b/src/client/components/ComicDetail/AcquisitionPanel.tsx index 8687a03..88c24d4 100644 --- a/src/client/components/ComicDetail/AcquisitionPanel.tsx +++ b/src/client/components/ComicDetail/AcquisitionPanel.tsx @@ -35,8 +35,14 @@ export const AcquisitionPanel = ( priority: PriorityEnum; } interface SearchResult { - id: number; + id: string; // Add other properties as needed + slots: any; + type: any; + users: any; + name: string; + dupe: Boolean; + size: number; } const handleSearch = (searchQuery) => { @@ -122,7 +128,6 @@ export const AcquisitionPanel = ( }; socketIOInstance.on("searchResultAdded", ({ result }: any) => { - console.log("yelaweda", result); setAirDCPPSearchResults((previousState) => { const exists = previousState.some( (item) => result.id === item.id, @@ -135,7 +140,6 @@ export const AcquisitionPanel = ( }); socketIOInstance.on("searchResultUpdated", ({ result }: any) => { - console.log("endh", result); // ...update properties of the existing result in the UI const bundleToUpdateIndex = airDCPPSearchResults?.findIndex( (bundle) => bundle.id === result.id, @@ -201,141 +205,7 @@ export const AcquisitionPanel = ( search(manualQuery); }; - console.log(airDCPPSearchResults); - // const comment = `
- // - // - // - // - // - // - // - // - // - // - // {map(airDCPPSearchResults, ({ result, search_id }, idx) => { - // return ( - // - // - // - // - // - // - // ); - // })} - // - //
- // Name - // - // Type - // - // Slots - // - // Actions - //
- //

- // {result.type.id === "directory" ? ( - // - // ) : null} - // {ellipsize(result.name, 70)} - //

- // - //
- //
- //
- // {!isNil(result.dupe) ? ( - // - // - // - // - // - // - // Dupe - // - // - // ) : null} - // - // {/* Nicks */} - // - // - // - // - // - // - // {result.users.user.nicks} - // - // - // {/* Flags */} - // {result.users.user.flags.map((flag, idx) => ( - // - // - // - // - // - // - // {flag} - // - // - // ))} - //
- //
- //
- //
- // {/* Extension */} - // - // - // - // - // - // - // {result.type.str} - // - // - // - // {/* Slots */} - // - // - // - // - // - // - // {result.slots.total} slots; {result.slots.free} free - // - // - // - // - //
- //
`; + return ( <>
@@ -445,7 +315,140 @@ export const AcquisitionPanel = ( {/* AirDC++ results */}
{!isNil(airDCPPSearchResults) && !isEmpty(airDCPPSearchResults) ? ( - <> +
+ + + + + + + + + + + {map(airDCPPSearchResults, ({ dupe, type, name, id, slots, users, size }, idx) => { + return ( + + + + + + + ); + })} + +
+ Name + + Type + + Slots + + Actions +
+

+ {type.id === "directory" ? ( + + ) : null} + {ellipsize(name, 70)} +

+ +
+
+
+ {!isNil(dupe) ? ( + + + + + + + Dupe + + + ) : null} + + {/* Nicks */} + + + + + + + {users.user.nicks} + + + {/* Flags */} + {users.user.flags.map((flag, idx) => ( + + + + + + + {flag} + + + ))} +
+
+
+
+ {/* Extension */} + + + + + + + {type.str} + + + + {/* Slots */} + + + + + + + {slots.total} slots; {slots.free} free + + + + +
+
) : (
([]); const [torrentDetails, setTorrentDetails] = useState([]); - const [activeTab, setActiveTab] = useState("torrents"); + const [activeTab, setActiveTab] = useState("directconnect"); const { airDCPPSocketInstance, socketIOInstance } = useStore( useShallow((state: any) => ({ airDCPPSocketInstance: state.airDCPPSocketInstance, socketIOInstance: state.socketIOInstance, })), ); - + // React to torrent progress data sent over websockets socketIOInstance.on("AS_TORRENT_DATA", (data) => { const torrents = data.torrents @@ -60,7 +60,7 @@ export const DownloadsPanel = ( }), }); const getBundles = async (comicObject) => { - if (comicObject?.data.acquisition.directconnect) { + if (!isNil(comicObject?.data.acquisition.directconnect)) { const filteredBundles = comicObject.data.acquisition.directconnect.downloads.map( async ({ bundleId }) => { @@ -87,6 +87,7 @@ export const DownloadsPanel = ( useEffect(() => { getBundles(comicObject).then((result) => { + console.log("mingi", result); setBundles(result); }); }, [comicObject]); -- 2.49.1 From af130384f1db71edfd6d48a50be970a33f35d620 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Wed, 16 Oct 2024 18:49:56 -0400 Subject: [PATCH 3/9] =?UTF-8?q?=F0=9F=94=A7=20Todo=20to=20move=20method=20?= =?UTF-8?q?out=20to=20core=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/components/ComicDetail/DownloadsPanel.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/components/ComicDetail/DownloadsPanel.tsx b/src/client/components/ComicDetail/DownloadsPanel.tsx index 580bf31..c94c761 100644 --- a/src/client/components/ComicDetail/DownloadsPanel.tsx +++ b/src/client/components/ComicDetail/DownloadsPanel.tsx @@ -59,6 +59,7 @@ export const DownloadsPanel = ( }, }), }); + // This method needs to be moved to an endpoint in threetwo-core-service const getBundles = async (comicObject) => { if (!isNil(comicObject?.data.acquisition.directconnect)) { const filteredBundles = -- 2.49.1 From 5d430504ec4388cc4afc4f8e574f56638784d1cf Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Fri, 18 Oct 2024 13:18:59 -0400 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=94=A7=20Fixing=20the=20DC++=20downlo?= =?UTF-8?q?ad=20bundles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ComicDetail/AcquisitionPanel.tsx | 239 +++++++++--------- src/client/components/shared/Carda.tsx | 2 +- 2 files changed, 118 insertions(+), 123 deletions(-) diff --git a/src/client/components/ComicDetail/AcquisitionPanel.tsx b/src/client/components/ComicDetail/AcquisitionPanel.tsx index 88c24d4..e21c01a 100644 --- a/src/client/components/ComicDetail/AcquisitionPanel.tsx +++ b/src/client/components/ComicDetail/AcquisitionPanel.tsx @@ -1,5 +1,4 @@ import React, { useCallback, ReactElement, useEffect, useState } from "react"; -import { getBundlesForComic, sleep } from "../../actions/airdcpp.actions"; import { SearchQuery, PriorityEnum, SearchResponse } from "threetwo-ui-typings"; import { RootState, SearchInstance } from "threetwo-ui-typings"; import ellipsize from "ellipsize"; @@ -12,7 +11,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import axios from "axios"; import { AIRDCPP_SERVICE_BASE_URI } from "../../constants/endpoints"; - interface IAcquisitionPanelProps { query: any; comicObjectId: any; @@ -35,7 +33,7 @@ export const AcquisitionPanel = ( priority: PriorityEnum; } interface SearchResult { - id: string; + id: string; // Add other properties as needed slots: any; type: any; @@ -112,26 +110,20 @@ export const AcquisitionPanel = ( */ const search = async (searchData: any) => { setAirDCPPSearchResults([]); - socketIOInstance.emit( - "call", - "socket.search", - { - query: searchData, - config: { - protocol: `ws`, - hostname: `localhost:5600`, - username: `user`, - password: `pass`, - }, + socketIOInstance.emit("call", "socket.search", { + query: searchData, + config: { + protocol: `ws`, + hostname: `localhost:5600`, + username: `user`, + password: `pass`, }, - ); + }); }; socketIOInstance.on("searchResultAdded", ({ result }: any) => { setAirDCPPSearchResults((previousState) => { - const exists = previousState.some( - (item) => result.id === item.id, - ); + const exists = previousState.some((item) => result.id === item.id); if (!exists) { return [...previousState, result]; } @@ -205,7 +197,7 @@ export const AcquisitionPanel = ( search(manualQuery); }; - + return ( <>
@@ -334,118 +326,121 @@ export const AcquisitionPanel = ( - {map(airDCPPSearchResults, ({ dupe, type, name, id, slots, users, size }, idx) => { - return ( - - -

- {type.id === "directory" ? ( - - ) : null} - {ellipsize(name, 70)} -

- -
-
-
- {!isNil(dupe) ? ( + {map( + airDCPPSearchResults, + ({ dupe, type, name, id, slots, users, size }, idx) => { + return ( + + +

+ {type.id === "directory" ? ( + + ) : null} + {ellipsize(name, 70)} +

+ +
+
+
+ {!isNil(dupe) ? ( + + + + + + + Dupe + + + ) : null} + + {/* Nicks */} - + - + - Dupe + {users.user.nicks} - ) : null} - - {/* Nicks */} - - - - - - - {users.user.nicks} - - - {/* Flags */} - {users.user.flags.map((flag, idx) => ( - - - + {/* Flags */} + {users.user.flags.map((flag, idx) => ( + + + + + + + {flag} + - - - {flag} - - - ))} -
-
-
- - - {/* Extension */} - - - + ))} +
+
+
+ + + {/* Extension */} + + + + + + + {type.str} + - - - {type.str} + + + {/* Slots */} + + + + + + + {slots.total} slots; {slots.free} free + - - - - {/* Slots */} - - - - - - - {slots.total} slots; {slots.free} free - - - - - - - - ); - })} + + + + + + ); + }, + )}
diff --git a/src/client/components/shared/Carda.tsx b/src/client/components/shared/Carda.tsx index 1e21891..f6a1390 100644 --- a/src/client/components/shared/Carda.tsx +++ b/src/client/components/shared/Carda.tsx @@ -83,7 +83,7 @@ const renderCard = (props: ICardProps): ReactElement => { case "vertical-2": return ( -
+
Home Date: Mon, 21 Oct 2024 18:03:47 -0400 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=94=A7=20Bundles=20endpoint=20integra?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- .../ComicDetail/AcquisitionPanel.tsx | 14 ++-- .../components/ComicDetail/DownloadsPanel.tsx | 66 +++++++++---------- 3 files changed, 38 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index 64ff345..786e31a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@types/mime-types": "^2.1.0", "@types/react-router-dom": "^5.3.3", "@vitejs/plugin-react": "^4.2.1", - "airdcpp-apisocket": "^2.5.0-beta.2", + "airdcpp-apisocket": "^2.4.4", "axios": "^1.6.8", "axios-cache-interceptor": "^1.0.1", "axios-rate-limit": "^1.3.0", diff --git a/src/client/components/ComicDetail/AcquisitionPanel.tsx b/src/client/components/ComicDetail/AcquisitionPanel.tsx index e21c01a..de59401 100644 --- a/src/client/components/ComicDetail/AcquisitionPanel.tsx +++ b/src/client/components/ComicDetail/AcquisitionPanel.tsx @@ -114,9 +114,9 @@ export const AcquisitionPanel = ( query: searchData, config: { protocol: `ws`, - hostname: `localhost:5600`, - username: `user`, - password: `pass`, + hostname: `192.168.1.119:5600`, + username: `admin`, + password: `password`, }, }); }; @@ -191,7 +191,7 @@ export const AcquisitionPanel = ( pattern: `${searchQuery.issueName}`, extensions: ["cbz", "cbr", "cb7"], }, - hub_urls: map(hubs?.data, (hub) => hub.hub_url), + hub_urls: [hubs?.data[0].hub_url], priority: 5, }; @@ -424,9 +424,9 @@ export const AcquisitionPanel = ( type, { protocol: `ws`, - hostname: `localhost:5600`, - username: `user`, - password: `pass`, + hostname: `192.168.1.119:5600`, + username: `admin`, + password: `password`, }, ) } diff --git a/src/client/components/ComicDetail/DownloadsPanel.tsx b/src/client/components/ComicDetail/DownloadsPanel.tsx index c94c761..9917e31 100644 --- a/src/client/components/ComicDetail/DownloadsPanel.tsx +++ b/src/client/components/ComicDetail/DownloadsPanel.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useContext, ReactElement, useState } from "react"; import { RootState } from "threetwo-ui-typings"; -import { isEmpty, isNil, map } from "lodash"; +import { isEmpty, isNil, isUndefined, map } from "lodash"; import { AirDCPPBundles } from "./AirDCPPBundles"; import { TorrentDownloads } from "./TorrentDownloads"; import { useQuery } from "@tanstack/react-query"; @@ -9,6 +9,7 @@ import { LIBRARY_SERVICE_BASE_URI, QBITTORRENT_SERVICE_BASE_URI, TORRENT_JOB_SERVICE_BASE_URI, + SOCKET_BASE_URI, } from "../../constants/endpoints"; import { useStore } from "../../store"; import { useShallow } from "zustand/react/shallow"; @@ -22,17 +23,15 @@ export const DownloadsPanel = ( props: IDownloadsPanelProps, ): ReactElement | null => { const { comicObjectId } = useParams<{ comicObjectId: string }>(); - const [bundles, setBundles] = useState([]); const [infoHashes, setInfoHashes] = useState([]); const [torrentDetails, setTorrentDetails] = useState([]); const [activeTab, setActiveTab] = useState("directconnect"); - const { airDCPPSocketInstance, socketIOInstance } = useStore( + const { socketIOInstance } = useStore( useShallow((state: any) => ({ - airDCPPSocketInstance: state.airDCPPSocketInstance, socketIOInstance: state.socketIOInstance, })), ); - + // React to torrent progress data sent over websockets socketIOInstance.on("AS_TORRENT_DATA", (data) => { const torrents = data.torrents @@ -44,33 +43,29 @@ export const DownloadsPanel = ( .filter((item) => item !== undefined); setTorrentDetails(torrents); }); - // Fetch the downloaded files and currently-downloading file(s) from AirDC++ - const { data: comicObject, isSuccess } = useQuery({ + + /** + * Query to fetch AirDC++ download bundles for a given comic resource Id + * @param {string} {comicObjectId} - A mongo id that identifies a comic document + */ + const { data: bundles } = useQuery({ queryKey: ["bundles"], queryFn: async () => await axios({ - url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`, + url: `${LIBRARY_SERVICE_BASE_URI}/getBundles`, method: "POST", - headers: { - "Content-Type": "application/json; charset=utf-8", - }, data: { - id: `${comicObjectId}`, + comicObjectId, + config: { + protocol: `ws`, + hostname: `192.168.1.119:5600`, + username: `admin`, + password: `password`, + }, }, }), + enabled: activeTab !== "" && activeTab === "directconnect", }); - // This method needs to be moved to an endpoint in threetwo-core-service - const getBundles = async (comicObject) => { - if (!isNil(comicObject?.data.acquisition.directconnect)) { - const filteredBundles = - comicObject.data.acquisition.directconnect.downloads.map( - async ({ bundleId }) => { - return await airDCPPSocketInstance.get(`queue/bundles/${bundleId}`); - }, - ); - return await Promise.all(filteredBundles); - } - }; // Call the scheduled job for fetching torrent data // triggered by the active tab been set to "torrents" @@ -84,18 +79,13 @@ export const DownloadsPanel = ( }, }), queryKey: [activeTab], + enabled: activeTab !== "" && activeTab === "torrents", }); - - useEffect(() => { - getBundles(comicObject).then((result) => { - console.log("mingi", result); - setBundles(result); - }); - }, [comicObject]); - + console.log(bundles); return (
+ {JSON.stringify(activeTab, null, 4)}
- {activeTab === "torrents" && } - {!isEmpty(airDCPPSocketInstance) && - !isEmpty(bundles) && - activeTab === "directconnect" && } + {activeTab === "torrents" ? ( + + ) : null} + {!isNil(bundles?.data) && bundles?.data.length !== 0 ? ( + + ) : ( + "nutin" + )}
); }; -- 2.49.1 From 173e3140320cafcaa8b4d6f88a81448897a1f18e Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Mon, 28 Oct 2024 13:03:46 -0400 Subject: [PATCH 6/9] =?UTF-8?q?=F0=9F=94=A7=20Fixed=20the=20download=20bun?= =?UTF-8?q?dles=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ComicDetail/AcquisitionPanel.tsx | 15 +++++++-------- .../components/ComicDetail/DownloadsPanel.tsx | 3 +-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/client/components/ComicDetail/AcquisitionPanel.tsx b/src/client/components/ComicDetail/AcquisitionPanel.tsx index de59401..436429b 100644 --- a/src/client/components/ComicDetail/AcquisitionPanel.tsx +++ b/src/client/components/ComicDetail/AcquisitionPanel.tsx @@ -246,14 +246,13 @@ export const AcquisitionPanel = ( )} /> ) : ( -
-
-
- No AirDC++ hub configured. Please configure it in{" "} - Settings > AirDC++ > Hubs. -
-
-
+
+ No AirDC++ hub configured. Please configure it in{" "} + Settings > AirDC++ > Hubs. +
)}
diff --git a/src/client/components/ComicDetail/DownloadsPanel.tsx b/src/client/components/ComicDetail/DownloadsPanel.tsx index 9917e31..2998d02 100644 --- a/src/client/components/ComicDetail/DownloadsPanel.tsx +++ b/src/client/components/ComicDetail/DownloadsPanel.tsx @@ -85,7 +85,6 @@ export const DownloadsPanel = ( return (
- {JSON.stringify(activeTab, null, 4)}