Compare commits

...

10 Commits

Author SHA1 Message Date
7c8d71f51b Merge branch 'main' into airdcpp-regression
Signed-off-by: Rishi Ghan <rishi.ghan@gmail.com>
2025-02-17 16:25:36 -05:00
123e273ceb 🔧 Fixed PullList source 2025-02-17 16:23:09 -05:00
2d72842166 🔧 Fixing autodownload functionality 2024-12-22 21:59:28 -05:00
c9b99e8bb4 Added an active hub badge to DC++ search 2024-11-14 19:00:51 -05:00
173e314032 🔧 Fixed the download bundles page 2024-10-28 13:03:46 -04:00
8a32dc41df 🔧 Bundles endpoint integration 2024-10-21 18:03:47 -04:00
5d430504ec 🔧 Fixing the DC++ download bundles 2024-10-18 13:18:59 -04:00
af130384f1 🔧 Todo to move method out to core service 2024-10-16 18:49:56 -04:00
b06c85d6dc 🔧 Fixing broken DC++ downloads 2024-10-13 23:42:03 -04:00
fe6100bb79 🔧 Fixing broken AirDCPP search 2024-09-26 21:32:28 -04:00
11 changed files with 268 additions and 266 deletions

View File

@@ -1,5 +1,4 @@
import React, { useCallback, ReactElement, useEffect, useState } from "react"; import React, { useCallback, ReactElement, useEffect, useState } from "react";
import { getBundlesForComic, sleep } from "../../actions/airdcpp.actions";
import { SearchQuery, PriorityEnum, SearchResponse } from "threetwo-ui-typings"; import { SearchQuery, PriorityEnum, SearchResponse } from "threetwo-ui-typings";
import { RootState, SearchInstance } from "threetwo-ui-typings"; import { RootState, SearchInstance } from "threetwo-ui-typings";
import ellipsize from "ellipsize"; import ellipsize from "ellipsize";
@@ -12,7 +11,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
import { AIRDCPP_SERVICE_BASE_URI } from "../../constants/endpoints"; import { AIRDCPP_SERVICE_BASE_URI } from "../../constants/endpoints";
interface IAcquisitionPanelProps { interface IAcquisitionPanelProps {
query: any; query: any;
comicObjectId: any; comicObjectId: any;
@@ -35,18 +33,20 @@ export const AcquisitionPanel = (
priority: PriorityEnum; priority: PriorityEnum;
} }
interface SearchResult { interface SearchResult {
result: { id: string;
id: number;
};
search_id: number;
// Add other properties as needed // Add other properties as needed
slots: any;
type: any;
users: any;
name: string;
dupe: Boolean;
size: number;
} }
const handleSearch = (searchQuery) => { const handleSearch = (searchQuery) => {
// Use the already connected socket instance to emit events // Use the already connected socket instance to emit events
socketIOInstance.emit("initiateSearch", searchQuery); socketIOInstance.emit("initiateSearch", searchQuery);
}; };
const { const {
data: settings, data: settings,
isLoading, isLoading,
@@ -110,43 +110,36 @@ export const AcquisitionPanel = (
*/ */
const search = async (searchData: any) => { const search = async (searchData: any) => {
setAirDCPPSearchResults([]); setAirDCPPSearchResults([]);
socketIOInstance.emit( socketIOInstance.emit("call", "socket.search", {
"call", query: searchData,
"socket.search", config: {
{ protocol: `ws`,
query: searchData, // hostname: `192.168.1.119:5600`,
config: { hostname: `127.0.0.1:5600`,
protocol: `ws`, username: `user`,
hostname: `localhost:5600`, password: `pass`,
username: `user`,
password: `pass`,
},
}, },
(data: any) => console.log(data), });
);
}; };
socketIOInstance.on("searchResultAdded", ({ groupedResult }: any) => { socketIOInstance.on("searchResultAdded", ({ result }: any) => {
setAirDCPPSearchResults((previousState) => { setAirDCPPSearchResults((previousState) => {
const exists = previousState.some( const exists = previousState.some((item) => result.id === item.id);
(item) => groupedResult.result.id === item.result.id,
);
if (!exists) { if (!exists) {
return [...previousState, groupedResult]; return [...previousState, result];
} }
return previousState; return previousState;
}); });
}); });
socketIOInstance.on("searchResultUpdated", ({ updatedResult }: any) => { socketIOInstance.on("searchResultUpdated", ({ result }: any) => {
console.log("endh");
// ...update properties of the existing result in the UI // ...update properties of the existing result in the UI
const bundleToUpdateIndex = airDCPPSearchResults?.findIndex( const bundleToUpdateIndex = airDCPPSearchResults?.findIndex(
(bundle) => bundle.result.id === updatedResult.result.id, (bundle) => bundle.id === result.id,
); );
const updatedState = [...airDCPPSearchResults]; const updatedState = [...airDCPPSearchResults];
if (!isNil(difference(updatedState[bundleToUpdateIndex], updatedResult))) { if (!isNil(difference(updatedState[bundleToUpdateIndex], result))) {
updatedState[bundleToUpdateIndex] = updatedResult; updatedState[bundleToUpdateIndex] = result;
} }
setAirDCPPSearchResults((state) => [...state, ...updatedState]); setAirDCPPSearchResults((state) => [...state, ...updatedState]);
}); });
@@ -177,7 +170,7 @@ export const AcquisitionPanel = (
size: Number, size: Number,
type: any, type: any,
config: any, config: any,
): void => { ): Promise<void> => {
socketIOInstance.emit( socketIOInstance.emit(
"call", "call",
"socket.download", "socket.download",
@@ -199,7 +192,7 @@ export const AcquisitionPanel = (
pattern: `${searchQuery.issueName}`, pattern: `${searchQuery.issueName}`,
extensions: ["cbz", "cbr", "cb7"], extensions: ["cbz", "cbr", "cb7"],
}, },
hub_urls: map(hubs?.data, (hub) => hub.hub_url), hub_urls: [hubs?.data[0].hub_url],
priority: 5, priority: 5,
}; };
@@ -208,7 +201,7 @@ export const AcquisitionPanel = (
return ( return (
<> <>
<div className="mt-5"> <div className="mt-5 mb-3">
{!isEmpty(hubs?.data) ? ( {!isEmpty(hubs?.data) ? (
<Form <Form
onSubmit={getDCPPSearchResults} onSubmit={getDCPPSearchResults}
@@ -254,16 +247,24 @@ export const AcquisitionPanel = (
)} )}
/> />
) : ( ) : (
<div className=""> <article
<article className=""> role="alert"
<div className=""> className="mt-4 rounded-lg text-sm 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"
No AirDC++ hub configured. Please configure it in{" "} >
<code>Settings &gt; AirDC++ &gt; Hubs</code>. No AirDC++ hub configured. Please configure it in{" "}
</div> <code>Settings &gt; AirDC++ &gt; Hubs</code>.
</article> </article>
</div>
)} )}
</div> </div>
{/* configured hub */}
{!isEmpty(hubs?.data) && (
<span className="inline-flex items-center bg-green-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-green-300">
<span className="pr-1 pt-1">
<i className="icon-[solar--server-2-bold-duotone] w-5 h-5"></i>
</span>
{hubs && hubs?.data[0].hub_url}
</span>
)}
{/* AirDC++ search instance details */} {/* AirDC++ search instance details */}
{!isNil(airDCPPSearchInstance) && {!isNil(airDCPPSearchInstance) &&
@@ -274,7 +275,7 @@ export const AcquisitionPanel = (
<dl> <dl>
<dt> <dt>
<div className="mb-1"> <div className="mb-1">
{hubs?.data.map((value, idx) => ( {hubs?.data.map((value, idx: string) => (
<span className="tag is-warning" key={idx}> <span className="tag is-warning" key={idx}>
{value.identity.name} {value.identity.name}
</span> </span>
@@ -313,7 +314,7 @@ export const AcquisitionPanel = (
)} )}
{/* AirDC++ results */} {/* AirDC++ results */}
<div className="columns"> <div className="">
{!isNil(airDCPPSearchResults) && !isEmpty(airDCPPSearchResults) ? ( {!isNil(airDCPPSearchResults) && !isEmpty(airDCPPSearchResults) ? (
<div className="overflow-x-auto w-fit mt-4 rounded-lg border border-gray-200 dark:border-gray-500"> <div className="overflow-x-auto w-fit mt-4 rounded-lg border border-gray-200 dark:border-gray-500">
<table className="min-w-full divide-y-2 divide-gray-200 dark:divide-gray-500 text-md"> <table className="min-w-full divide-y-2 divide-gray-200 dark:divide-gray-500 text-md">
@@ -334,118 +335,121 @@ export const AcquisitionPanel = (
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-100 dark:divide-gray-500"> <tbody className="divide-y divide-slate-100 dark:divide-gray-500">
{map(airDCPPSearchResults, ({ result, search_id }, idx) => { {map(
return ( airDCPPSearchResults,
<tr ({ dupe, type, name, id, slots, users, size }, idx) => {
key={idx} return (
className={ <tr
!isNil(result.dupe) key={idx}
? "bg-gray-100 dark:bg-gray-700" className={
: "w-fit text-sm" !isNil(dupe)
} ? "bg-gray-100 dark:bg-gray-700"
> : "w-fit text-sm"
<td className="whitespace-nowrap px-3 py-3 text-gray-700 dark:text-slate-300"> }
<p className="mb-2"> >
{result.type.id === "directory" ? ( <td className="whitespace-nowrap px-3 py-3 text-gray-700 dark:text-slate-300">
<i className="fas fa-folder"></i> <p className="mb-2">
) : null} {type.id === "directory" ? (
{ellipsize(result.name, 70)} <i className="fas fa-folder"></i>
</p> ) : null}
{ellipsize(name, 70)}
</p>
<dl> <dl>
<dd> <dd>
<div className="inline-flex flex-row gap-2"> <div className="inline-flex flex-row gap-2">
{!isNil(result.dupe) ? ( {!isNil(dupe) ? (
<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--copy-bold-duotone] w-5 h-5"></i>
</span>
<span className="text-md text-slate-500 dark:text-slate-900">
Dupe
</span>
</span>
) : null}
{/* Nicks */}
<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="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"> <span className="pr-1 pt-1">
<i className="icon-[solar--copy-bold-duotone] w-5 h-5"></i> <i className="icon-[solar--user-rounded-bold-duotone] w-5 h-5"></i>
</span> </span>
<span className="text-md text-slate-500 dark:text-slate-900"> <span className="text-md text-slate-500 dark:text-slate-900">
Dupe {users.user.nicks}
</span> </span>
</span> </span>
) : null} {/* Flags */}
{users.user.flags.map((flag, idx) => (
<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--tag-horizontal-bold-duotone] w-5 h-5"></i>
</span>
{/* Nicks */} <span className="text-md text-slate-500 dark:text-slate-900">
<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"> {flag}
<span className="pr-1 pt-1"> </span>
<i className="icon-[solar--user-rounded-bold-duotone] w-5 h-5"></i>
</span>
<span className="text-md text-slate-500 dark:text-slate-900">
{result.users.user.nicks}
</span>
</span>
{/* Flags */}
{result.users.user.flags.map((flag, idx) => (
<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--tag-horizontal-bold-duotone] w-5 h-5"></i>
</span> </span>
))}
</div>
</dd>
</dl>
</td>
<td>
{/* Extension */}
<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--zip-file-bold-duotone] w-5 h-5"></i>
</span>
<span className="text-md text-slate-500 dark:text-slate-900"> <span className="text-md text-slate-500 dark:text-slate-900">
{flag} {type.str}
</span> </span>
</span>
))}
</div>
</dd>
</dl>
</td>
<td>
{/* Extension */}
<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--zip-file-bold-duotone] w-5 h-5"></i>
</span> </span>
</td>
<td className="px-2">
{/* Slots */}
<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--settings-minimalistic-bold-duotone] w-5 h-5"></i>
</span>
<span className="text-md text-slate-500 dark:text-slate-900"> <span className="text-md text-slate-500 dark:text-slate-900">
{result.type.str} {slots.total} slots; {slots.free} free
</span>
</span> </span>
</span> </td>
</td> <td className="px-2">
<td className="px-2"> <button
{/* Slots */} 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"
<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"> onClick={() =>
<span className="pr-1 pt-1"> download(
<i className="icon-[solar--settings-minimalistic-bold-duotone] w-5 h-5"></i> airDCPPSearchInstance.id,
</span> id,
comicObjectId,
<span className="text-md text-slate-500 dark:text-slate-900"> name,
{result.slots.total} slots; {result.slots.free} free size,
</span> type,
</span> {
</td> protocol: `ws`,
<td className="px-2"> hostname: `192.168.1.119:5600`,
<button username: `admin`,
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" password: `password`,
onClick={() => },
download( )
airDCPPSearchInstance.id, }
result.id, >
comicObjectId, <span className="text-xs">Download</span>
result.name, <span className="w-5 h-5">
result.size, <i className="h-5 w-5 icon-[solar--download-bold-duotone]"></i>
result.type, </span>
{ </button>
protocol: `ws`, </td>
hostname: `localhost:5600`, </tr>
username: `user`, );
password: `pass`, },
}, )}
)
}
>
<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> </tbody>
</table> </table>
</div> </div>

View File

@@ -1,11 +1,17 @@
import React, { ReactElement, useCallback, useState } from "react"; import React, { ReactElement, useCallback, useState } from "react";
import PropTypes from "prop-types";
import { fetchMetronResource } from "../../../actions/metron.actions"; import { fetchMetronResource } from "../../../actions/metron.actions";
import Creatable from "react-select/creatable"; import Creatable from "react-select/creatable";
import { withAsyncPaginate } from "react-select-async-paginate"; import { withAsyncPaginate } from "react-select-async-paginate";
const CreatableAsyncPaginate = withAsyncPaginate(Creatable); const CreatableAsyncPaginate = withAsyncPaginate(Creatable);
export const AsyncSelectPaginate = (props): ReactElement => { interface AsyncSelectPaginateProps {
metronResource: string;
placeholder?: string;
value?: object;
onChange?(...args: unknown[]): unknown;
}
export const AsyncSelectPaginate = (props: AsyncSelectPaginateProps): ReactElement => {
const [value, setValue] = useState(null); const [value, setValue] = useState(null);
const [isAddingInProgress, setIsAddingInProgress] = useState(false); const [isAddingInProgress, setIsAddingInProgress] = useState(false);
@@ -38,11 +44,4 @@ export const AsyncSelectPaginate = (props): ReactElement => {
); );
}; };
AsyncSelectPaginate.propTypes = {
metronResource: PropTypes.string.isRequired,
placeholder: PropTypes.string,
value: PropTypes.object,
onChange: PropTypes.func,
};
export default AsyncSelectPaginate; export default AsyncSelectPaginate;

View File

@@ -1,12 +1,21 @@
import React, { ReactElement } from "react"; import React, { ReactElement } from "react";
import PropTypes from "prop-types";
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils"; import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { isEmpty, isUndefined } from "lodash"; import { isEmpty, isUndefined } from "lodash";
import Card from "../shared/Carda"; import Card from "../shared/Carda";
import { convert } from "html-to-text"; import { convert } from "html-to-text";
export const ComicVineDetails = (props): ReactElement => { interface ComicVineDetailsProps {
updatedAt?: string;
data?: {
name?: string;
number?: string;
resource_type?: string;
id?: number;
};
}
export const ComicVineDetails = (props: ComicVineDetailsProps): ReactElement => {
const { data, updatedAt } = props; const { data, updatedAt } = props;
return ( return (
<div className="text-slate-500 dark:text-gray-400"> <div className="text-slate-500 dark:text-gray-400">
@@ -107,13 +116,3 @@ export const ComicVineDetails = (props): ReactElement => {
}; };
export default ComicVineDetails; export default ComicVineDetails;
ComicVineDetails.propTypes = {
updatedAt: PropTypes.string,
data: PropTypes.shape({
name: PropTypes.string,
number: PropTypes.string,
resource_type: PropTypes.string,
id: PropTypes.number,
}),
};

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useContext, ReactElement, useState } from "react"; import React, { useEffect, useContext, ReactElement, useState } from "react";
import { RootState } from "threetwo-ui-typings"; import { RootState } from "threetwo-ui-typings";
import { isEmpty, map } from "lodash"; import { isEmpty, isNil, isUndefined, map } from "lodash";
import { AirDCPPBundles } from "./AirDCPPBundles"; import { AirDCPPBundles } from "./AirDCPPBundles";
import { TorrentDownloads } from "./TorrentDownloads"; import { TorrentDownloads } from "./TorrentDownloads";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
@@ -9,6 +9,7 @@ import {
LIBRARY_SERVICE_BASE_URI, LIBRARY_SERVICE_BASE_URI,
QBITTORRENT_SERVICE_BASE_URI, QBITTORRENT_SERVICE_BASE_URI,
TORRENT_JOB_SERVICE_BASE_URI, TORRENT_JOB_SERVICE_BASE_URI,
SOCKET_BASE_URI,
} from "../../constants/endpoints"; } from "../../constants/endpoints";
import { useStore } from "../../store"; import { useStore } from "../../store";
import { useShallow } from "zustand/react/shallow"; import { useShallow } from "zustand/react/shallow";
@@ -22,13 +23,11 @@ export const DownloadsPanel = (
props: IDownloadsPanelProps, props: IDownloadsPanelProps,
): ReactElement | null => { ): ReactElement | null => {
const { comicObjectId } = useParams<{ comicObjectId: string }>(); const { comicObjectId } = useParams<{ comicObjectId: string }>();
const [bundles, setBundles] = useState([]);
const [infoHashes, setInfoHashes] = useState<string[]>([]); const [infoHashes, setInfoHashes] = useState<string[]>([]);
const [torrentDetails, setTorrentDetails] = useState([]); const [torrentDetails, setTorrentDetails] = useState([]);
const [activeTab, setActiveTab] = useState("torrents"); const [activeTab, setActiveTab] = useState("directconnect");
const { airDCPPSocketInstance, socketIOInstance } = useStore( const { socketIOInstance } = useStore(
useShallow((state: any) => ({ useShallow((state: any) => ({
airDCPPSocketInstance: state.airDCPPSocketInstance,
socketIOInstance: state.socketIOInstance, socketIOInstance: state.socketIOInstance,
})), })),
); );
@@ -44,32 +43,29 @@ export const DownloadsPanel = (
.filter((item) => item !== undefined); .filter((item) => item !== undefined);
setTorrentDetails(torrents); 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"], queryKey: ["bundles"],
queryFn: async () => queryFn: async () =>
await axios({ await axios({
url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`, url: `${LIBRARY_SERVICE_BASE_URI}/getBundles`,
method: "POST", method: "POST",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
data: { data: {
id: `${comicObjectId}`, comicObjectId,
config: {
protocol: `ws`,
hostname: `192.168.1.119:5600`,
username: `admin`,
password: `password`,
},
}, },
}), }),
enabled: activeTab !== "" && activeTab === "directconnect",
}); });
const getBundles = async (comicObject) => {
if (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 // Call the scheduled job for fetching torrent data
// triggered by the active tab been set to "torrents" // triggered by the active tab been set to "torrents"
@@ -83,14 +79,9 @@ export const DownloadsPanel = (
}, },
}), }),
queryKey: [activeTab], queryKey: [activeTab],
enabled: activeTab !== "" && activeTab === "torrents",
}); });
console.log(bundles);
useEffect(() => {
getBundles(comicObject).then((result) => {
setBundles(result);
});
}, [comicObject]);
return ( return (
<div className="columns is-multiline"> <div className="columns is-multiline">
<div> <div>
@@ -135,10 +126,14 @@ export const DownloadsPanel = (
</div> </div>
</div> </div>
{activeTab === "torrents" && <TorrentDownloads data={torrentDetails} />} {activeTab === "torrents" ? (
{!isEmpty(airDCPPSocketInstance) && <TorrentDownloads data={torrentDetails} />
!isEmpty(bundles) && ) : null}
activeTab === "directconnect" && <AirDCPPBundles data={bundles} />} {!isNil(bundles?.data) && bundles?.data.length !== 0 ? (
<AirDCPPBundles data={bundles.data} />
) : (
"nutin"
)}
</div> </div>
); );
}; };

View File

@@ -1,10 +1,36 @@
import React, { ReactElement } from "react"; import React, { ReactElement } from "react";
import PropTypes from "prop-types";
import prettyBytes from "pretty-bytes"; import prettyBytes from "pretty-bytes";
import { isEmpty } from "lodash"; import { isEmpty } from "lodash";
import { format, parseISO } from "date-fns"; import { format, parseISO } from "date-fns";
export const RawFileDetails = (props): ReactElement => { interface RawFileDetailsProps {
data?: {
rawFileDetails?: {
containedIn?: string;
name?: string;
fileSize?: number;
path?: string;
extension?: string;
mimeType?: string;
cover?: {
filePath?: string;
};
};
inferredMetadata?: {
issue?: {
year?: string;
name?: string;
number?: number;
subtitle?: string;
};
};
created_at?: string;
updated_at?: string;
};
children?: any;
}
export const RawFileDetails = (props: RawFileDetailsProps): ReactElement => {
const { rawFileDetails, inferredMetadata, created_at, updated_at } = const { rawFileDetails, inferredMetadata, created_at, updated_at } =
props.data; props.data;
return ( return (
@@ -98,30 +124,3 @@ export const RawFileDetails = (props): ReactElement => {
}; };
export default RawFileDetails; export default RawFileDetails;
RawFileDetails.propTypes = {
data: PropTypes.shape({
rawFileDetails: PropTypes.shape({
containedIn: PropTypes.string,
name: PropTypes.string,
fileSize: PropTypes.number,
path: PropTypes.string,
extension: PropTypes.string,
mimeType: PropTypes.string,
cover: PropTypes.shape({
filePath: PropTypes.string,
}),
}),
inferredMetadata: PropTypes.shape({
issue: PropTypes.shape({
year: PropTypes.string,
name: PropTypes.string,
number: PropTypes.number,
subtitle: PropTypes.string,
}),
}),
created_at: PropTypes.string,
updated_at: PropTypes.string,
}),
children: PropTypes.any,
};

View File

@@ -25,7 +25,7 @@ export const TorrentSearchPanel = (props) => {
data: { data: {
prowlarrQuery: { prowlarrQuery: {
port: "9696", port: "9696",
apiKey: "c4f42e265fb044dc81f7e88bd41c3367", apiKey: "38c2656e8f5d4790962037b8c4416a8f",
offset: 0, offset: 0,
categories: [7030], categories: [7030],
query: searchTerm.issueName, query: searchTerm.issueName,

View File

@@ -86,8 +86,8 @@ export const PullList = (): ReactElement => {
<span className="text-md"> <span className="text-md">
Pull List aggregated for the week from{" "} Pull List aggregated for the week from{" "}
<span className="underline"> <span className="underline">
<a href="https://leagueofcomicgeeks.com/comics/new-comics"> <a href="https://www.tfaw.com/comics/new-releases.html">
League Of Comic Geeks Things From Another World
</a> </a>
<i className="icon-[solar--arrow-right-up-outline] w-4 h-4" /> <i className="icon-[solar--arrow-right-up-outline] w-4 h-4" />
</span> </span>
@@ -132,13 +132,13 @@ export const PullList = (): ReactElement => {
<div key={idx} className="keen-slider__slide"> <div key={idx} className="keen-slider__slide">
<Card <Card
orientation={"vertical-2"} orientation={"vertical-2"}
imageUrl={issue.cover} imageUrl={issue.coverImageUrl}
hasDetails hasDetails
title={ellipsize(issue.name, 25)} title={ellipsize(issue.name, 25)}
> >
<div className="px-1"> <div className="px-1">
<span className="inline-flex mb-2 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-1 rounded-md dark:text-slate-900 dark:bg-slate-400"> <span className="inline-flex mb-2 items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-1 rounded-md dark:text-slate-900 dark:bg-slate-400">
{issue.publisher} {issue.publicationDate}
</span> </span>
<div className="flex flex-row justify-end"> <div className="flex flex-row justify-end">
<button <button

View File

@@ -83,7 +83,7 @@ const renderCard = (props: ICardProps): ReactElement => {
case "vertical-2": case "vertical-2":
return ( return (
<div className="block rounded-md w-64 h-fit shadow-md shadow-white-400 bg-gray-200 dark:bg-slate-500"> <div className="block rounded-md max-w-64 h-fit shadow-md shadow-white-400 bg-gray-200 dark:bg-slate-500">
<img <img
alt="Home" alt="Home"
src={props.imageUrl} src={props.imageUrl}

View File

@@ -1,5 +1,4 @@
import React, { ReactElement, useMemo, useState } from "react"; import React, { ReactElement, useMemo, useState } from "react";
import PropTypes from "prop-types";
import { import {
ColumnDef, ColumnDef,
flexRender, flexRender,
@@ -9,7 +8,19 @@ import {
PaginationState, PaginationState,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
export const T2Table = (tableOptions): ReactElement => { interface T2TableProps {
sourceData?: unknown[];
totalPages?: number;
columns?: unknown[];
paginationHandlers?: {
nextPage?(...args: unknown[]): unknown;
previousPage?(...args: unknown[]): unknown;
};
rowClickHandler?(...args: unknown[]): unknown;
children?: any;
}
export const T2Table = (tableOptions: T2TableProps): ReactElement => {
const { const {
sourceData, sourceData,
columns, columns,
@@ -142,15 +153,4 @@ export const T2Table = (tableOptions): ReactElement => {
); );
}; };
T2Table.propTypes = {
sourceData: PropTypes.array,
totalPages: PropTypes.number,
columns: PropTypes.array,
paginationHandlers: PropTypes.shape({
nextPage: PropTypes.func,
previousPage: PropTypes.func,
}),
rowClickHandler: PropTypes.func,
children: PropTypes.any,
};
export default T2Table; export default T2Table;

View File

@@ -45,8 +45,8 @@ export const determineCoverFile = (data): any => {
// comicvine // comicvine
if (!isEmpty(data.comicvine)) { if (!isEmpty(data.comicvine)) {
coverFile.comicvine.url = data?.comicvine?.image.small_url; coverFile.comicvine.url = data?.comicvine?.image.small_url;
coverFile.comicvine.issueName = data.comicvine.name; coverFile.comicvine.issueName = data.comicvine?.name;
coverFile.comicvine.publisher = data.comicvine.publisher.name; coverFile.comicvine.publisher = data.comicvine?.publisher?.name;
} }
// rawFileDetails // rawFileDetails
if (!isEmpty(data.rawFileDetails)) { if (!isEmpty(data.rawFileDetails)) {

View File

@@ -3427,11 +3427,9 @@
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
"@types/react-dom@^18.0.11": "@types/react-dom@^18.0.11":
version "18.2.18" version "18.3.3"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.3.tgz#3654138d0da1b0c7916f6ed0dc1cc2b576d47650"
integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw== integrity sha512-uTYkxTLkYp41nq/ULXyXMtkNT1vu5fXJoqad6uTNCOGat5t9cLgF4vMNLBXsTOXpdOI44XzKPY1M5RRm0bQHuw==
dependencies:
"@types/react" "*"
"@types/react-redux@^7.1.25": "@types/react-redux@^7.1.25":
version "7.1.33" version "7.1.33"
@@ -3467,7 +3465,7 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@>=16", "@types/react@^18.0.28": "@types/react@*", "@types/react@>=16":
version "18.2.48" version "18.2.48"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1"
integrity sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w== integrity sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==
@@ -3476,6 +3474,14 @@
"@types/scheduler" "*" "@types/scheduler" "*"
csstype "^3.0.2" csstype "^3.0.2"
"@types/react@^18.0.28":
version "18.3.14"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.14.tgz#7ce43bbca0e15e1c4f67ad33ea3f83d75aa6756b"
integrity sha512-NzahNKvjNhVjuPBQ+2G7WlxstQ+47kXZNHlUvFakDViuIEfGY926GqhMueQFZ7woG+sPiQKlF36XfrIUVSUfFg==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/resolve@1.20.2": "@types/resolve@1.20.2":
version "1.20.2" version "1.20.2"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
@@ -3713,16 +3719,16 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0" clean-stack "^2.0.0"
indent-string "^4.0.0" indent-string "^4.0.0"
airdcpp-apisocket@^2.5.0-beta.2: airdcpp-apisocket@^2.4.4:
version "2.5.0-beta.2" version "2.4.4"
resolved "https://registry.yarnpkg.com/airdcpp-apisocket/-/airdcpp-apisocket-2.5.0-beta.2.tgz#062541095de75775bfa92b5cb4e785674beb8986" resolved "https://registry.yarnpkg.com/airdcpp-apisocket/-/airdcpp-apisocket-2.4.4.tgz#94c3b8082022982557b8cad2fc77a670709f0b2c"
integrity sha512-N/+39wYrZc/2N5CaZPG8kUSMu9shGGmLqR/0WQbJ1NiHF5VHcifN27ioldijN10KGfssFvTZBjV93m+D8ADDkQ== integrity sha512-Xn0kWSVdLJwPpOoHcdI2wzzfzZW2jTpuyZR2wCNs2UIlZhO+FTwMf3QQfNCt5gYTOld9LaiCEulxFuXDA8qrLA==
dependencies: dependencies:
chalk "^4.1.2" chalk "^4.1.2"
events "^3.3.0" events "^3.3.0"
invariant "^2.2.4" invariant "^2.2.4"
is-in-browser "^2.0.0" is-in-browser "^2.0.0"
promise "^8.3.0" promise "^8.1.0"
ajv@^6.12.4: ajv@^6.12.4:
version "6.12.6" version "6.12.6"
@@ -8865,7 +8871,7 @@ progress@^2.0.1:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise@^8.3.0: promise@^8.1.0:
version "8.3.0" version "8.3.0"
resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a"
integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg== integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==
@@ -9070,12 +9076,12 @@ react-docgen@^7.0.0:
strip-indent "^4.0.0" strip-indent "^4.0.0"
react-dom@^18.2.0: react-dom@^18.2.0:
version "18.2.0" version "18.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
scheduler "^0.23.0" scheduler "^0.23.2"
react-element-to-jsx-string@^15.0.0: react-element-to-jsx-string@^15.0.0:
version "15.0.0" version "15.0.0"
@@ -9632,10 +9638,10 @@ sass@^1.77.0:
immutable "^4.0.0" immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0" source-map-js ">=0.6.2 <2.0.0"
scheduler@^0.23.0: scheduler@^0.23.2:
version "0.23.0" version "0.23.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"