🧲 Added downloads panel
This commit is contained in:
@@ -357,7 +357,12 @@ export const ComicDetail = (data: ComicDetailProps): ReactElement => {
|
|||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
name: "Downloads",
|
name: "Downloads",
|
||||||
icon: <>{acquisition?.directconnect?.downloads?.length}</>,
|
icon: (
|
||||||
|
<>
|
||||||
|
{acquisition?.directconnect?.downloads?.length +
|
||||||
|
acquisition?.torrent.length}
|
||||||
|
</>
|
||||||
|
),
|
||||||
content:
|
content:
|
||||||
!isNil(data.data) && !isEmpty(data.data) ? (
|
!isNil(data.data) && !isEmpty(data.data) ? (
|
||||||
<DownloadsPanel key={5} />
|
<DownloadsPanel key={5} />
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
import React, { ReactElement } from "react";
|
||||||
import React, { ReactElement, useContext, useEffect, useState } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { getComicBookDetailById } from "../../actions/comicinfo.actions";
|
|
||||||
import { ComicDetail } from "../ComicDetail/ComicDetail";
|
import { ComicDetail } from "../ComicDetail/ComicDetail";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
|
import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ export const DownloadsPanel = (
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryFn: async () =>
|
queryFn: async () =>
|
||||||
await axios({
|
await axios({
|
||||||
url: `${QBITTORRENT_SERVICE_BASE_URI}/getTorrentDetails`,
|
url: `${QBITTORRENT_SERVICE_BASE_URI}/getTorrentProperties`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: infoHashes,
|
data: { infoHashes },
|
||||||
}),
|
}),
|
||||||
queryKey: ["torrentProperties", infoHashes],
|
queryKey: ["torrentProperties", infoHashes],
|
||||||
});
|
});
|
||||||
@@ -88,6 +88,7 @@ export const DownloadsPanel = (
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
console.log(infoHashes);
|
||||||
return newInfoHashes;
|
return newInfoHashes;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
const [currentImage, setCurrentImage] = useState([]);
|
const [currentImage, setCurrentImage] = useState([]);
|
||||||
const [uncompressedArchive, setUncompressedArchive] = useState([]);
|
const [uncompressedArchive, setUncompressedArchive] = useState([]);
|
||||||
const [imageAnalysisResult, setImageAnalysisResult] = useState({});
|
const [imageAnalysisResult, setImageAnalysisResult] = useState({});
|
||||||
|
const [shouldRefetchComicBookData, setShouldRefetchComicBookData] =
|
||||||
|
useState(false);
|
||||||
const constructImagePaths = (data): Array<string> => {
|
const constructImagePaths = (data): Array<string> => {
|
||||||
return data?.map((path: string) =>
|
return data?.map((path: string) =>
|
||||||
escapePoundSymbol(encodeURI(`${LIBRARY_SERVICE_HOST}/${path}`)),
|
escapePoundSymbol(encodeURI(`${LIBRARY_SERVICE_HOST}/${path}`)),
|
||||||
@@ -63,6 +65,7 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
|
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
setUncompressedArchive(uncompressedArchive);
|
setUncompressedArchive(uncompressedArchive);
|
||||||
|
setShouldRefetchComicBookData(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -122,8 +125,9 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isSuccess) {
|
if (isSuccess && shouldRefetchComicBookData) {
|
||||||
queryClient.invalidateQueries({ queryKey: ["comicBookMetadata"] });
|
queryClient.invalidateQueries({ queryKey: ["comicBookMetadata"] });
|
||||||
|
setShouldRefetchComicBookData(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// sliding panel init
|
// sliding panel init
|
||||||
@@ -171,7 +175,8 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
{data.rawFileDetails.archive?.uncompressed ? (
|
{data.rawFileDetails.archive?.uncompressed &&
|
||||||
|
!isEmpty(uncompressedArchive) ? (
|
||||||
<article
|
<article
|
||||||
role="alert"
|
role="alert"
|
||||||
className="mt-4 text-md rounded-lg max-w-screen-md border-s-4 border-yellow-500 bg-yellow-50 p-4 dark:border-s-4 dark:border-yellow-600 dark:bg-yellow-300 dark:text-slate-600"
|
className="mt-4 text-md rounded-lg max-w-screen-md border-s-4 border-yellow-500 bg-yellow-50 p-4 dark:border-s-4 dark:border-yellow-600 dark:bg-yellow-300 dark:text-slate-600"
|
||||||
@@ -187,7 +192,7 @@ export const ArchiveOperations = (props): ReactElement => {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className="flex flex-row gap-2 mt-4">
|
<div className="flex flex-row gap-2 mt-4">
|
||||||
{!data.rawFileDetails?.archive?.uncompressed ? (
|
{isEmpty(uncompressedArchive) ? (
|
||||||
<button
|
<button
|
||||||
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-2 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-2 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
onClick={() => refetch()}
|
onClick={() => refetch()}
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ export const TorrentDownloads = (props) => {
|
|||||||
<dt className="text-lg">{torrent.name}</dt>
|
<dt className="text-lg">{torrent.name}</dt>
|
||||||
<p className="text-sm">{torrent.hash}</p>
|
<p className="text-sm">{torrent.hash}</p>
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
Added on{" "}
|
Added on {dayjs.unix(torrent.added_on).format("ddd, D MMM, YYYY")}
|
||||||
{dayjs.unix(torrent.addition_date).format("ddd, D MMM, YYYY")}
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-4 mt-2">
|
<div className="flex gap-4 mt-2">
|
||||||
{/* Peers */}
|
{/* Peers */}
|
||||||
|
|||||||
@@ -6,15 +6,17 @@ import {
|
|||||||
PROWLARR_SERVICE_BASE_URI,
|
PROWLARR_SERVICE_BASE_URI,
|
||||||
QBITTORRENT_SERVICE_BASE_URI,
|
QBITTORRENT_SERVICE_BASE_URI,
|
||||||
} from "../../constants/endpoints";
|
} from "../../constants/endpoints";
|
||||||
import { isNil } from "lodash";
|
import { isEmpty, isNil } from "lodash";
|
||||||
|
import ellipsize from "ellipsize";
|
||||||
|
import prettyBytes from "pretty-bytes";
|
||||||
|
|
||||||
export const TorrentSearchPanel = (props) => {
|
export const TorrentSearchPanel = (props) => {
|
||||||
const { comicObjectId, issueName } = props;
|
const { issueName, comicObjectId } = props;
|
||||||
// Initialize searchTerm with issueName from props
|
// Initialize searchTerm with issueName from props
|
||||||
const [searchTerm, setSearchTerm] = useState({ issueName });
|
const [searchTerm, setSearchTerm] = useState({ issueName });
|
||||||
const [torrentToDownload, setTorrentToDownload] = useState("");
|
const [torrentToDownload, setTorrentToDownload] = useState("");
|
||||||
|
|
||||||
const { data, isSuccess } = useQuery({
|
const { data, isSuccess, isLoading } = useQuery({
|
||||||
queryKey: ["searchResults", searchTerm.issueName],
|
queryKey: ["searchResults", searchTerm.issueName],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await axios({
|
return await axios({
|
||||||
@@ -35,11 +37,26 @@ export const TorrentSearchPanel = (props) => {
|
|||||||
},
|
},
|
||||||
enabled: !isNil(searchTerm.issueName) && searchTerm.issueName.trim() !== "", // Make sure searchTerm is not empty
|
enabled: !isNil(searchTerm.issueName) && searchTerm.issueName.trim() !== "", // Make sure searchTerm is not empty
|
||||||
});
|
});
|
||||||
|
const { data: addTorrentResult } = useQuery({
|
||||||
|
queryFn: async () =>
|
||||||
|
axios({
|
||||||
|
url: `${QBITTORRENT_SERVICE_BASE_URI}/addTorrent`,
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
comicObjectId,
|
||||||
|
torrentToDownload,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
queryKey: ["addTorrentResult", torrentToDownload],
|
||||||
|
enabled: !isEmpty(torrentToDownload),
|
||||||
|
});
|
||||||
const searchIndexer = (values) => {
|
const searchIndexer = (values) => {
|
||||||
setSearchTerm({ issueName: values.issueName }); // Update searchTerm based on the form submission
|
setSearchTerm({ issueName: values.issueName }); // Update searchTerm based on the form submission
|
||||||
};
|
};
|
||||||
|
const downloadTorrent = (evt) => {
|
||||||
|
console.log(evt);
|
||||||
|
setTorrentToDownload(evt);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
@@ -81,21 +98,104 @@ export const TorrentSearchPanel = (props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
|
||||||
{isSuccess &&
|
<article
|
||||||
data?.data.map((result, idx) => (
|
role="alert"
|
||||||
<li key={idx}>
|
className="mt-4 rounded-lg text-sm max-w-screen-md border-s-4 border-blue-500 bg-blue-50 p-4 dark:border-s-4 dark:border-blue-600 dark:bg-blue-300 dark:text-slate-600"
|
||||||
<p>{result.fileName}</p>
|
>
|
||||||
<p>{result.indexer}</p>
|
<div>
|
||||||
<button
|
The default search term is an auto-detected title; you may need to
|
||||||
className="sm:mt-0 min-w-fit rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
change it to get better matches if the auto-detected one doesn't work.
|
||||||
onClick={() => setTorrentToDownload(result.downloadUrl)}
|
</div>
|
||||||
>
|
</article>
|
||||||
Download
|
{!isEmpty(data?.data) ? (
|
||||||
</button>
|
<div className="overflow-x-auto w-fit mt-4 rounded-lg border border-gray-200 dark:border-gray-500">
|
||||||
</li>
|
<table className="min-w-full divide-y-2 divide-gray-200 dark:divide-gray-500 text-md">
|
||||||
))}
|
<thead>
|
||||||
</ul>
|
<tr>
|
||||||
|
<th className="whitespace-nowrap px-2 py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
|
Name
|
||||||
|
</th>
|
||||||
|
<th className="whitespace-nowrap py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
|
Indexer
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th className="whitespace-nowrap py-2 font-medium text-gray-900 dark:text-slate-200">
|
||||||
|
Action
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-slate-100 dark:divide-gray-500">
|
||||||
|
{data?.data.map((result, idx) => (
|
||||||
|
<tr key={idx}>
|
||||||
|
<td className="px-3 py-3 text-gray-700 dark:text-slate-300 text-md">
|
||||||
|
<p>{ellipsize(result.fileName, 90)}</p>
|
||||||
|
{/* Seeders/Leechers */}
|
||||||
|
<div className="flex gap-3 mt-2">
|
||||||
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--archive-up-minimlistic-bold-duotone] w-5 h-5"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-md text-slate-500 dark:text-slate-900">
|
||||||
|
{result.seeders} seeders
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--archive-down-minimlistic-bold-duotone] w-5 h-5"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-md text-slate-500 dark:text-slate-900">
|
||||||
|
{result.leechers} leechers
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{/* Size */}
|
||||||
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--mirror-right-bold-duotone] w-5 h-5"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-md text-slate-500 dark:text-slate-900">
|
||||||
|
{prettyBytes(result.size)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Files */}
|
||||||
|
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2 rounded-md dark:text-slate-900 dark:bg-slate-400">
|
||||||
|
<span className="pr-1 pt-1">
|
||||||
|
<i className="icon-[solar--documents-bold-duotone] w-5 h-5"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="text-md text-slate-500 dark:text-slate-900">
|
||||||
|
{result.files} files
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="px-3 py-3 text-gray-700 dark:text-slate-300 text-sm">
|
||||||
|
{result.indexer}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="px-3 py-3 text-gray-700 dark:text-slate-300 text-sm">
|
||||||
|
<button
|
||||||
|
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
|
onClick={() => downloadTorrent(result.downloadUrl)}
|
||||||
|
>
|
||||||
|
<span className="text-xs">Download</span>
|
||||||
|
<span className="w-5 h-5">
|
||||||
|
<i className="h-5 w-5 icon-[solar--download-bold-duotone]"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user