Compare commits

...

4 Commits

Author SHA1 Message Date
5c2a455d73 🔧 Upgraded react-router to 7 and refactored 2025-02-17 17:17:08 -05:00
b83d40d7e6 Bumped react to 19 2025-02-17 16:44:39 -05:00
dependabot[bot]
0615d08e7d Bump vite from 5.2.14 to 5.4.12 (#127)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.14 to 5.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 16:29:57 -05:00
21a127509b Airdcpp regression (#123)
* 🔧 Fixing broken AirDCPP search

* 🔧 Fixing broken DC++ downloads

* 🔧 Todo to move method out to core service

* 🔧 Fixing the DC++ download bundles

* 🔧 Bundles endpoint integration

* 🔧 Fixed the download bundles page

*  Added an active hub badge to DC++ search

* 🔧 Fixing autodownload functionality

* 🔧 Fixed PullList source

---------

Signed-off-by: Rishi Ghan <rishi.ghan@gmail.com>
2025-02-17 16:27:48 -05:00
25 changed files with 3662 additions and 3424 deletions

View File

@@ -53,19 +53,18 @@
"pretty-bytes": "^5.6.0",
"prop-types": "^15.8.1",
"qs": "^6.10.5",
"react": "^18.3.1",
"react": "^19.0.0",
"react-collapsible": "^2.9.0",
"react-comic-viewer": "^0.4.0",
"react-day-picker": "^8.10.0",
"react-dom": "^18.2.0",
"react-dom": "^19.0.0",
"react-fast-compare": "^3.2.0",
"react-final-form": "^6.5.9",
"react-final-form-arrays": "^3.1.4",
"react-i18next": "^14.1.0",
"react-loader-spinner": "^4.0.0",
"react-modal": "^3.15.1",
"react-router": "^6.9.0",
"react-router-dom": "^6.9.0",
"react-router": "^7.1.5",
"react-select": "^5.8.0",
"react-select-async-paginate": "^0.7.2",
"react-sliding-pane": "^7.1.0",
@@ -74,7 +73,7 @@
"socket.io-client": "^4.3.2",
"styled-components": "^6.1.0",
"threetwo-ui-typings": "^1.0.14",
"vite": "^5.2.14",
"vite": "^5.4.12",
"vite-plugin-html": "^3.2.0",
"websocket": "^1.0.34",
"zustand": "^4.4.6"
@@ -98,8 +97,8 @@
"@types/jest": "^26.0.20",
"@types/lodash": "^4.14.168",
"@types/node": "^14.14.34",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/react-redux": "^7.1.25",
"autoprefixer": "^10.4.16",
"body-parser": "^1.19.0",

View File

@@ -1,5 +1,5 @@
import React, { ReactElement } from "react";
import { Outlet } from "react-router-dom";
import { Outlet } from "react-router";
import { Navbar2 } from "./shared/Navbar2";
import { ToastContainer } from "react-toastify";
import "../assets/scss/App.scss";

View File

@@ -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,18 +33,20 @@ export const AcquisitionPanel = (
priority: PriorityEnum;
}
interface SearchResult {
result: {
id: number;
};
search_id: number;
id: string;
// Add other properties as needed
slots: any;
type: any;
users: any;
name: string;
dupe: Boolean;
size: number;
}
const handleSearch = (searchQuery) => {
// Use the already connected socket instance to emit events
socketIOInstance.emit("initiateSearch", searchQuery);
};
const {
data: settings,
isLoading,
@@ -110,43 +110,36 @@ 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: `192.168.1.119:5600`,
hostname: `127.0.0.1:5600`,
username: `user`,
password: `pass`,
},
(data: any) => console.log(data),
);
});
};
socketIOInstance.on("searchResultAdded", ({ groupedResult }: any) => {
socketIOInstance.on("searchResultAdded", ({ result }: any) => {
setAirDCPPSearchResults((previousState) => {
const exists = previousState.some(
(item) => groupedResult.result.id === item.result.id,
);
const exists = previousState.some((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) => {
// ...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]);
});
@@ -177,7 +170,7 @@ export const AcquisitionPanel = (
size: Number,
type: any,
config: any,
): void => {
): Promise<void> => {
socketIOInstance.emit(
"call",
"socket.download",
@@ -199,7 +192,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,
};
@@ -208,7 +201,7 @@ export const AcquisitionPanel = (
return (
<>
<div className="mt-5">
<div className="mt-5 mb-3">
{!isEmpty(hubs?.data) ? (
<Form
onSubmit={getDCPPSearchResults}
@@ -254,16 +247,24 @@ export const AcquisitionPanel = (
)}
/>
) : (
<div className="">
<article className="">
<div className="">
No AirDC++ hub configured. Please configure it in{" "}
<code>Settings &gt; AirDC++ &gt; Hubs</code>.
</div>
</article>
</div>
<article
role="alert"
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>.
</article>
)}
</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 */}
{!isNil(airDCPPSearchInstance) &&
@@ -274,7 +275,7 @@ export const AcquisitionPanel = (
<dl>
<dt>
<div className="mb-1">
{hubs?.data.map((value, idx) => (
{hubs?.data.map((value, idx: string) => (
<span className="tag is-warning" key={idx}>
{value.identity.name}
</span>
@@ -313,7 +314,7 @@ export const AcquisitionPanel = (
)}
{/* AirDC++ results */}
<div className="columns">
<div className="">
{!isNil(airDCPPSearchResults) && !isEmpty(airDCPPSearchResults) ? (
<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">
@@ -334,118 +335,121 @@ export const AcquisitionPanel = (
</tr>
</thead>
<tbody className="divide-y divide-slate-100 dark:divide-gray-500">
{map(airDCPPSearchResults, ({ result, search_id }, idx) => {
return (
<tr
key={idx}
className={
!isNil(result.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" ? (
<i className="fas fa-folder"></i>
) : null}
{ellipsize(result.name, 70)}
</p>
{map(
airDCPPSearchResults,
({ dupe, type, name, id, slots, users, size }, idx) => {
return (
<tr
key={idx}
className={
!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">
{type.id === "directory" ? (
<i className="fas fa-folder"></i>
) : null}
{ellipsize(name, 70)}
</p>
<dl>
<dd>
<div className="inline-flex flex-row gap-2">
{!isNil(result.dupe) ? (
<dl>
<dd>
<div className="inline-flex flex-row gap-2">
{!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="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 className="text-md text-slate-500 dark:text-slate-900">
Dupe
{users.user.nicks}
</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="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--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 className="text-md text-slate-500 dark:text-slate-900">
{flag}
</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">
{flag}
</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 className="text-md text-slate-500 dark:text-slate-900">
{type.str}
</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">
{result.type.str}
<span className="text-md text-slate-500 dark:text-slate-900">
{slots.total} slots; {slots.free} free
</span>
</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">
{result.slots.total} slots; {result.slots.free} free
</span>
</span>
</td>
<td className="px-2">
<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={() =>
download(
airDCPPSearchInstance.id,
result.id,
comicObjectId,
result.name,
result.size,
result.type,
{
protocol: `ws`,
hostname: `localhost:5600`,
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>
);
})}
</td>
<td className="px-2">
<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={() =>
download(
airDCPPSearchInstance.id,
id,
comicObjectId,
name,
size,
type,
{
protocol: `ws`,
hostname: `192.168.1.119:5600`,
username: `admin`,
password: `password`,
},
)
}
>
<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>

View File

@@ -1,11 +1,17 @@
import React, { ReactElement, useCallback, useState } from "react";
import PropTypes from "prop-types";
import { fetchMetronResource } from "../../../actions/metron.actions";
import Creatable from "react-select/creatable";
import { withAsyncPaginate } from "react-select-async-paginate";
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 [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;

View File

@@ -1,5 +1,5 @@
import React, { useState, ReactElement, useCallback } from "react";
import { useParams } from "react-router-dom";
import { useParams } from "react-router";
import Card from "../shared/Carda";
import { ComicVineMatchPanel } from "./ComicVineMatchPanel";

View File

@@ -1,5 +1,5 @@
import React, { ReactElement } from "react";
import { useParams } from "react-router-dom";
import { useParams } from "react-router";
import { ComicDetail } from "../ComicDetail/ComicDetail";
import { useQuery } from "@tanstack/react-query";
import { LIBRARY_SERVICE_BASE_URI } from "../../constants/endpoints";

View File

@@ -1,12 +1,21 @@
import React, { ReactElement } from "react";
import PropTypes from "prop-types";
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
import dayjs from "dayjs";
import { isEmpty, isUndefined } from "lodash";
import Card from "../shared/Carda";
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;
return (
<div className="text-slate-500 dark:text-gray-400">
@@ -107,13 +116,3 @@ export const ComicVineDetails = (props): ReactElement => {
};
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 { RootState } from "threetwo-ui-typings";
import { isEmpty, 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,10 +9,11 @@ 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";
import { useParams } from "react-router-dom";
import { useParams } from "react-router";
interface IDownloadsPanelProps {
key: number;
@@ -22,13 +23,11 @@ export const DownloadsPanel = (
props: IDownloadsPanelProps,
): ReactElement | null => {
const { comicObjectId } = useParams<{ comicObjectId: string }>();
const [bundles, setBundles] = useState([]);
const [infoHashes, setInfoHashes] = useState<string[]>([]);
const [torrentDetails, setTorrentDetails] = useState([]);
const [activeTab, setActiveTab] = useState("torrents");
const { airDCPPSocketInstance, socketIOInstance } = useStore(
const [activeTab, setActiveTab] = useState("directconnect");
const { socketIOInstance } = useStore(
useShallow((state: any) => ({
airDCPPSocketInstance: state.airDCPPSocketInstance,
socketIOInstance: state.socketIOInstance,
})),
);
@@ -44,32 +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",
});
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
// triggered by the active tab been set to "torrents"
@@ -83,14 +79,9 @@ export const DownloadsPanel = (
},
}),
queryKey: [activeTab],
enabled: activeTab !== "" && activeTab === "torrents",
});
useEffect(() => {
getBundles(comicObject).then((result) => {
setBundles(result);
});
}, [comicObject]);
console.log(bundles);
return (
<div className="columns is-multiline">
<div>
@@ -135,10 +126,14 @@ export const DownloadsPanel = (
</div>
</div>
{activeTab === "torrents" && <TorrentDownloads data={torrentDetails} />}
{!isEmpty(airDCPPSocketInstance) &&
!isEmpty(bundles) &&
activeTab === "directconnect" && <AirDCPPBundles data={bundles} />}
{activeTab === "torrents" ? (
<TorrentDownloads data={torrentDetails} />
) : null}
{!isNil(bundles?.data) && bundles?.data.length !== 0 ? (
<AirDCPPBundles data={bundles.data} />
) : (
"nutin"
)}
</div>
);
};

View File

@@ -1,10 +1,36 @@
import React, { ReactElement } from "react";
import PropTypes from "prop-types";
import prettyBytes from "pretty-bytes";
import { isEmpty } from "lodash";
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 } =
props.data;
return (
@@ -98,30 +124,3 @@ export const RawFileDetails = (props): ReactElement => {
};
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: {
prowlarrQuery: {
port: "9696",
apiKey: "c4f42e265fb044dc81f7e88bd41c3367",
apiKey: "38c2656e8f5d4790962037b8c4416a8f",
offset: 0,
categories: [7030],
query: searchTerm.issueName,

View File

@@ -4,7 +4,7 @@ import Card from "../shared/Carda";
import Header from "../shared/Header";
import { importToDB } from "../../actions/fileops.actions";
import ellipsize from "ellipsize";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import axios from "axios";
import rateLimiter from "axios-rate-limit";
@@ -86,8 +86,8 @@ export const PullList = (): ReactElement => {
<span className="text-md">
Pull List aggregated for the week from{" "}
<span className="underline">
<a href="https://leagueofcomicgeeks.com/comics/new-comics">
League Of Comic Geeks
<a href="https://www.tfaw.com/comics/new-releases.html">
Things From Another World
</a>
<i className="icon-[solar--arrow-right-up-outline] w-4 h-4" />
</span>
@@ -132,13 +132,13 @@ export const PullList = (): ReactElement => {
<div key={idx} className="keen-slider__slide">
<Card
orientation={"vertical-2"}
imageUrl={issue.cover}
imageUrl={issue.coverImageUrl}
hasDetails
title={ellipsize(issue.name, 25)}
>
<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">
{issue.publisher}
{issue.publicationDate}
</span>
<div className="flex flex-row justify-end">
<button

View File

@@ -1,6 +1,6 @@
import React, { ReactElement } from "react";
import Card from "../shared/Carda";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import ellipsize from "ellipsize";
import { isEmpty, isNil, isUndefined, map } from "lodash";
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";

View File

@@ -1,7 +1,7 @@
import { map, unionBy } from "lodash";
import React, { ReactElement } from "react";
import ellipsize from "ellipsize";
import { Link, useNavigate } from "react-router-dom";
import { Link, useNavigate } from "react-router";
import Card from "../shared/Carda";
import Header from "../shared/Header";

View File

@@ -1,6 +1,6 @@
import React, { ReactElement } from "react";
import Card from "../shared/Carda";
import { Link, useNavigate } from "react-router-dom";
import { Link, useNavigate } from "react-router";
import ellipsize from "ellipsize";
import { isEmpty, isNil, isUndefined, map } from "lodash";
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";

View File

@@ -1,6 +1,6 @@
import React, { useMemo, ReactElement, useState, useEffect } from "react";
import PropTypes from "prop-types";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { isEmpty, isNil, isUndefined } from "lodash";
import MetadataPanel from "../shared/MetadataPanel";
import T2Table from "../shared/T2Table";

View File

@@ -14,7 +14,7 @@ import { isNil, isEmpty, isUndefined } from "lodash";
import Masonry from "react-masonry-css";
import Card from "../shared/Carda";
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import { LIBRARY_SERVICE_HOST } from "../../constants/endpoints";
interface ILibraryGridProps {}

View File

@@ -1,7 +1,7 @@
import React, { ReactElement } from "react";
import PropTypes from "prop-types";
import { Form, Field } from "react-final-form";
import { Link } from "react-router-dom";
import { Link } from "react-router";
export const SearchBar = (props): ReactElement => {
const { searchHandler } = props;

View File

@@ -4,7 +4,7 @@ import Card from "../shared/Carda";
import T2Table from "../shared/T2Table";
import ellipsize from "ellipsize";
import { convert } from "html-to-text";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { SEARCH_SERVICE_BASE_URI } from "../../constants/endpoints";

View File

@@ -83,7 +83,7 @@ const renderCard = (props: ICardProps): ReactElement => {
case "vertical-2":
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
alt="Home"
src={props.imageUrl}

View File

@@ -1,5 +1,5 @@
import React, { ReactElement } from "react";
import { Link } from "react-router-dom";
import { Link } from "react-router";
type IHeaderProps = {
headerContent: string;

View File

@@ -1,5 +1,5 @@
import React, { ReactElement, useState } from "react";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import { useDarkMode } from "../../hooks/useDarkMode";
export const Navbar2 = (): ReactElement => {

View File

@@ -1,5 +1,4 @@
import React, { ReactElement, useMemo, useState } from "react";
import PropTypes from "prop-types";
import {
ColumnDef,
flexRender,
@@ -9,7 +8,19 @@ import {
PaginationState,
} 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 {
sourceData,
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;

View File

@@ -1,12 +1,8 @@
import React from "react";
import { render } from "react-dom";
import { createRoot } from "react-dom/client";
import App from "./components/App";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { createBrowserRouter, RouterProvider } from "react-router";
import Settings from "./components/Settings/Settings";
import { ErrorPage } from "./components/shared/ErrorPage";
const rootEl = document.getElementById("root");
const root = createRoot(rootEl);
import i18n from "./shared/utils/i18n.util";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import Import from "./components/Import/Import";
@@ -20,34 +16,48 @@ import WantedComics from "./components/WantedComics/WantedComics";
const queryClient = new QueryClient();
const router = createBrowserRouter([
const router = createBrowserRouter(
[
{
path: "/",
element: <App />,
errorElement: <ErrorPage />,
children: [
{ path: "/", element: <Dashboard /> },
{ path: "dashboard", element: <Dashboard /> },
{ path: "settings", element: <Settings /> },
{ path: "library", element: <TabulatedContentContainer category="library" /> },
{ path: "comic/details/:comicObjectId", element: <ComicDetailContainer /> },
{ path: "import", element: <Import path={"./comics"} /> },
{ path: "search", element: <Search /> },
{ path: "volume/details/:comicObjectId", element: <VolumeDetails /> },
{ path: "volumes", element: <Volumes /> },
{ path: "wanted", element: <WantedComics /> },
],
},
],
{
path: "/",
element: <App />,
errorElement: <ErrorPage />,
children: [
{ path: "/", element: <Dashboard /> },
{ path: "dashboard", element: <Dashboard /> },
{ path: "settings", element: <Settings /> },
{
path: "library",
element: <TabulatedContentContainer category="library" />,
},
{
path: "comic/details/:comicObjectId",
element: <ComicDetailContainer />,
},
{ path: "import", element: <Import path={"./comics"} /> },
{ path: "search", element: <Search /> },
{ path: "volume/details/:comicObjectId", element: <VolumeDetails /> },
{ path: "volumes", element: <Volumes /> },
{ path: "wanted", element: <WantedComics /> },
],
},
]);
root.render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>,
future: {
v7_relativeSplatPath: true,
v7_fetcherPersist: true,
v7_normalizeFormMethod: true,
v7_partialHydration: true,
v7_skipActionErrorRevalidation: true,
},
}
);
const rootElement = document.getElementById("root");
if (rootElement) {
const root = createRoot(rootElement);
root.render(
<QueryClientProvider client={queryClient}>
<RouterProvider
router={router}
future={{ v7_startTransition: true }}
/>
</QueryClientProvider>
);
} else {
console.error("Root element not found");
}

View File

@@ -45,8 +45,8 @@ export const determineCoverFile = (data): any => {
// 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)) {

6479
yarn.lock

File diff suppressed because it is too large Load Diff