Compare commits
18 Commits
comicvine-
...
react-19-u
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c2a455d73 | |||
| b83d40d7e6 | |||
|
|
0615d08e7d | ||
| 21a127509b | |||
|
|
e92b86792e | ||
|
|
0f9bbd8dc1 | ||
|
|
2786f0e2a4 | ||
|
|
8dcf643444 | ||
| 628e1f72e2 | |||
| f4c498bce3 | |||
|
|
bf406c8b6b | ||
|
|
786ced6c21 | ||
| c6c3f2eaf7 | |||
| e2f1d5a307 | |||
| 2879df114d | |||
| 2c66e2f6af | |||
| 217df2f899 | |||
| 9ab15df0a8 |
@@ -12,7 +12,8 @@ RUN apk --no-cache add g++ make libpng-dev git python3 autoconf automake libjpeg
|
|||||||
|
|
||||||
# Install node modules
|
# Install node modules
|
||||||
RUN yarn install --ignore-engines
|
RUN yarn install --ignore-engines
|
||||||
|
# Explicitly install sass
|
||||||
|
RUN yarn add -D sass
|
||||||
# Copy the rest of the application
|
# Copy the rest of the application
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|||||||
23
package.json
23
package.json
@@ -29,14 +29,14 @@
|
|||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"airdcpp-apisocket": "^2.5.0-beta.2",
|
"airdcpp-apisocket": "^2.5.0-beta.2",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.7.4",
|
||||||
"axios-cache-interceptor": "^1.0.1",
|
"axios-cache-interceptor": "^1.0.1",
|
||||||
"axios-rate-limit": "^1.3.0",
|
"axios-rate-limit": "^1.3.0",
|
||||||
"babel-plugin-styled-components": "^2.1.4",
|
"babel-plugin-styled-components": "^2.1.4",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"dayjs": "^1.10.6",
|
"dayjs": "^1.10.6",
|
||||||
"ellipsize": "^0.5.1",
|
"ellipsize": "^0.5.1",
|
||||||
"express": "^4.19.2",
|
"express": "^4.20.0",
|
||||||
"filename-parser": "^1.0.2",
|
"filename-parser": "^1.0.2",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"final-form-arrays": "^3.0.2",
|
"final-form-arrays": "^3.0.2",
|
||||||
@@ -53,30 +53,27 @@
|
|||||||
"pretty-bytes": "^5.6.0",
|
"pretty-bytes": "^5.6.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"qs": "^6.10.5",
|
"qs": "^6.10.5",
|
||||||
"react": "^18.2.0",
|
"react": "^19.0.0",
|
||||||
"react-collapsible": "^2.9.0",
|
"react-collapsible": "^2.9.0",
|
||||||
"react-comic-viewer": "^0.4.0",
|
"react-comic-viewer": "^0.4.0",
|
||||||
"react-day-picker": "^8.10.0",
|
"react-day-picker": "^8.10.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-fast-compare": "^3.2.0",
|
"react-fast-compare": "^3.2.0",
|
||||||
"react-final-form": "^6.5.9",
|
"react-final-form": "^6.5.9",
|
||||||
"react-final-form-arrays": "^3.1.4",
|
"react-final-form-arrays": "^3.1.4",
|
||||||
"react-i18next": "^14.1.0",
|
"react-i18next": "^14.1.0",
|
||||||
"react-loader-spinner": "^4.0.0",
|
"react-loader-spinner": "^4.0.0",
|
||||||
"react-modal": "^3.15.1",
|
"react-modal": "^3.15.1",
|
||||||
"react-router": "^6.9.0",
|
"react-router": "^7.1.5",
|
||||||
"react-router-dom": "^6.9.0",
|
|
||||||
"react-select": "^5.8.0",
|
"react-select": "^5.8.0",
|
||||||
"react-select-async-paginate": "^0.7.2",
|
"react-select-async-paginate": "^0.7.2",
|
||||||
"react-sliding-pane": "^7.1.0",
|
"react-sliding-pane": "^7.1.0",
|
||||||
"react-textarea-autosize": "^8.3.4",
|
"react-textarea-autosize": "^8.3.4",
|
||||||
"reapop": "^4.2.1",
|
"react-toastify": "^10.0.5",
|
||||||
"socket.io-client": "^4.3.2",
|
"socket.io-client": "^4.3.2",
|
||||||
"styled-components": "^6.1.0",
|
"styled-components": "^6.1.0",
|
||||||
"threetwo-ui-typings": "^1.0.14",
|
"threetwo-ui-typings": "^1.0.14",
|
||||||
|
"vite": "^5.4.12",
|
||||||
"vite": "^5.2.7",
|
|
||||||
|
|
||||||
"vite-plugin-html": "^3.2.0",
|
"vite-plugin-html": "^3.2.0",
|
||||||
"websocket": "^1.0.34",
|
"websocket": "^1.0.34",
|
||||||
"zustand": "^4.4.6"
|
"zustand": "^4.4.6"
|
||||||
@@ -100,8 +97,8 @@
|
|||||||
"@types/jest": "^26.0.20",
|
"@types/jest": "^26.0.20",
|
||||||
"@types/lodash": "^4.14.168",
|
"@types/lodash": "^4.14.168",
|
||||||
"@types/node": "^14.14.34",
|
"@types/node": "^14.14.34",
|
||||||
"@types/react": "^18.0.28",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^19.0.0",
|
||||||
"@types/react-redux": "^7.1.25",
|
"@types/react-redux": "^7.1.25",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
@@ -115,7 +112,7 @@
|
|||||||
"eslint-plugin-prettier": "^3.3.1",
|
"eslint-plugin-prettier": "^3.3.1",
|
||||||
"eslint-plugin-react": "^7.22.0",
|
"eslint-plugin-react": "^7.22.0",
|
||||||
"eslint-plugin-storybook": "^0.6.13",
|
"eslint-plugin-storybook": "^0.6.13",
|
||||||
"express": "^4.19.2",
|
"express": "^4.20.0",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
"jest": "^29.6.3",
|
"jest": "^29.6.3",
|
||||||
"nodemon": "^3.0.1",
|
"nodemon": "^3.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router";
|
||||||
import { Navbar2 } from "./shared/Navbar2";
|
import { Navbar2 } from "./shared/Navbar2";
|
||||||
|
import { ToastContainer } from "react-toastify";
|
||||||
import "../assets/scss/App.scss";
|
import "../assets/scss/App.scss";
|
||||||
|
|
||||||
export const App = (): ReactElement => {
|
export const App = (): ReactElement => {
|
||||||
@@ -8,6 +9,7 @@ export const App = (): ReactElement => {
|
|||||||
<>
|
<>
|
||||||
<Navbar2 />
|
<Navbar2 />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
<ToastContainer stacked hideProgressBar />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -34,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,
|
||||||
@@ -109,42 +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", (data: SearchResult) => {
|
socketIOInstance.on("searchResultAdded", ({ result }: any) => {
|
||||||
setAirDCPPSearchResults((previousState) => {
|
setAirDCPPSearchResults((previousState) => {
|
||||||
const exists = previousState.some(
|
const exists = previousState.some((item) => result.id === item.id);
|
||||||
(item) => data.result.id === item.result.id,
|
|
||||||
);
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return [...previousState, data];
|
return [...previousState, result];
|
||||||
}
|
}
|
||||||
return previousState;
|
return previousState;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
socketIOInstance.on("searchResultUpdated", (groupedResult: SearchResult) => {
|
socketIOInstance.on("searchResultUpdated", ({ result }: any) => {
|
||||||
// ...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 === groupedResult.result.id,
|
(bundle) => bundle.id === result.id,
|
||||||
);
|
);
|
||||||
const updatedState = [...airDCPPSearchResults];
|
const updatedState = [...airDCPPSearchResults];
|
||||||
if (!isNil(difference(updatedState[bundleToUpdateIndex], groupedResult))) {
|
if (!isNil(difference(updatedState[bundleToUpdateIndex], result))) {
|
||||||
updatedState[bundleToUpdateIndex] = groupedResult;
|
updatedState[bundleToUpdateIndex] = result;
|
||||||
}
|
}
|
||||||
setAirDCPPSearchResults((state) => [...state, ...updatedState]);
|
setAirDCPPSearchResults((state) => [...state, ...updatedState]);
|
||||||
});
|
});
|
||||||
@@ -175,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",
|
||||||
@@ -197,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,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -206,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}
|
||||||
@@ -252,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"
|
||||||
AirDC++ is not configured. Please configure it in{" "}
|
>
|
||||||
<code>Settings > AirDC++ > Connection</code>.
|
No AirDC++ hub configured. Please configure it in{" "}
|
||||||
</div>
|
<code>Settings > AirDC++ > 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) &&
|
||||||
@@ -272,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>
|
||||||
@@ -311,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">
|
||||||
@@ -332,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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, ReactElement, useCallback } from "react";
|
import React, { useState, ReactElement, useCallback } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router";
|
||||||
import Card from "../shared/Carda";
|
import Card from "../shared/Carda";
|
||||||
import { ComicVineMatchPanel } from "./ComicVineMatchPanel";
|
import { ComicVineMatchPanel } from "./ComicVineMatchPanel";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router";
|
||||||
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";
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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,10 +9,11 @@ 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";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router";
|
||||||
|
|
||||||
interface IDownloadsPanelProps {
|
interface IDownloadsPanelProps {
|
||||||
key: number;
|
key: number;
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Card from "../shared/Carda";
|
|||||||
import Header from "../shared/Header";
|
import Header from "../shared/Header";
|
||||||
import { importToDB } from "../../actions/fileops.actions";
|
import { importToDB } from "../../actions/fileops.actions";
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router";
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import rateLimiter from "axios-rate-limit";
|
import rateLimiter from "axios-rate-limit";
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
import Card from "../shared/Carda";
|
import Card from "../shared/Carda";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router";
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
import { isEmpty, isNil, isUndefined, map } from "lodash";
|
import { isEmpty, isNil, isUndefined, map } from "lodash";
|
||||||
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
|
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { map, unionBy } from "lodash";
|
import { map, unionBy } from "lodash";
|
||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router";
|
||||||
import Card from "../shared/Carda";
|
import Card from "../shared/Carda";
|
||||||
import Header from "../shared/Header";
|
import Header from "../shared/Header";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
import Card from "../shared/Carda";
|
import Card from "../shared/Carda";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router";
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
import { isEmpty, isNil, isUndefined, map } from "lodash";
|
import { isEmpty, isNil, isUndefined, map } from "lodash";
|
||||||
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
|
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useMemo, ReactElement, useState, useEffect } from "react";
|
import React, { useMemo, ReactElement, useState, useEffect } from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router";
|
||||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
import { isEmpty, isNil, isUndefined } from "lodash";
|
||||||
import MetadataPanel from "../shared/MetadataPanel";
|
import MetadataPanel from "../shared/MetadataPanel";
|
||||||
import T2Table from "../shared/T2Table";
|
import T2Table from "../shared/T2Table";
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { isNil, isEmpty, isUndefined } from "lodash";
|
|||||||
import Masonry from "react-masonry-css";
|
import Masonry from "react-masonry-css";
|
||||||
import Card from "../shared/Carda";
|
import Card from "../shared/Carda";
|
||||||
import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils";
|
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";
|
import { LIBRARY_SERVICE_HOST } from "../../constants/endpoints";
|
||||||
|
|
||||||
interface ILibraryGridProps {}
|
interface ILibraryGridProps {}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { Form, Field } from "react-final-form";
|
import { Form, Field } from "react-final-form";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router";
|
||||||
|
|
||||||
export const SearchBar = (props): ReactElement => {
|
export const SearchBar = (props): ReactElement => {
|
||||||
const { searchHandler } = props;
|
const { searchHandler } = props;
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
markEntireVolumeWanted,
|
markEntireVolumeWanted,
|
||||||
resourceType,
|
resourceType,
|
||||||
}) => {
|
}) => {
|
||||||
console.log("jigni", comicObject);
|
|
||||||
let volumeInformation = {};
|
let volumeInformation = {};
|
||||||
let issues = [];
|
let issues = [];
|
||||||
switch (resourceType) {
|
switch (resourceType) {
|
||||||
@@ -73,11 +72,12 @@ export const Search = ({}: ISearchProps): ReactElement => {
|
|||||||
// Add issue metadata
|
// Add issue metadata
|
||||||
issues.push({
|
issues.push({
|
||||||
id,
|
id,
|
||||||
api_detail_url,
|
url: api_detail_url,
|
||||||
image,
|
image,
|
||||||
coverDate: cover_date,
|
coverDate: cover_date,
|
||||||
issueNumber: issue_number,
|
issueNumber: issue_number,
|
||||||
});
|
});
|
||||||
|
console.log(issues);
|
||||||
// Get volume metadata from CV
|
// Get volume metadata from CV
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
url: `${COMICVINE_SERVICE_URI}/getVolumes`,
|
url: `${COMICVINE_SERVICE_URI}/getVolumes`,
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { ReactElement, useEffect, useState, useContext } from "react";
|
import React, { ReactElement, useState } from "react";
|
||||||
import { Form, Field } from "react-final-form";
|
import { Form, Field } from "react-final-form";
|
||||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
import { isEmpty, isNil, isUndefined } from "lodash";
|
||||||
import Select from "react-select";
|
import Select from "react-select";
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { produce } from "immer";
|
||||||
import { AIRDCPP_SERVICE_BASE_URI } from "../../../constants/endpoints";
|
import { AIRDCPP_SERVICE_BASE_URI } from "../../../constants/endpoints";
|
||||||
|
|
||||||
export const AirDCPPHubsForm = (): ReactElement => {
|
export const AirDCPPHubsForm = (): ReactElement => {
|
||||||
@@ -13,6 +14,7 @@ export const AirDCPPHubsForm = (): ReactElement => {
|
|||||||
data: settings,
|
data: settings,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
|
refetch,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["settings"],
|
queryKey: ["settings"],
|
||||||
queryFn: async () =>
|
queryFn: async () =>
|
||||||
@@ -20,11 +22,9 @@ export const AirDCPPHubsForm = (): ReactElement => {
|
|||||||
url: "http://localhost:3000/api/settings/getAllSettings",
|
url: "http://localhost:3000/api/settings/getAllSettings",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
}),
|
}),
|
||||||
|
staleTime: Infinity,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the hubs list from an AirDCPP Socket
|
|
||||||
*/
|
|
||||||
const { data: hubs } = useQuery({
|
const { data: hubs } = useQuery({
|
||||||
queryKey: ["hubs"],
|
queryKey: ["hubs"],
|
||||||
queryFn: async () =>
|
queryFn: async () =>
|
||||||
@@ -37,14 +37,16 @@ export const AirDCPPHubsForm = (): ReactElement => {
|
|||||||
}),
|
}),
|
||||||
enabled: !isEmpty(settings?.data.directConnect?.client?.host),
|
enabled: !isEmpty(settings?.data.directConnect?.client?.host),
|
||||||
});
|
});
|
||||||
let hubList = {};
|
|
||||||
|
let hubList: any[] = [];
|
||||||
if (!isNil(hubs)) {
|
if (!isNil(hubs)) {
|
||||||
hubList = hubs?.data.map(({ hub_url, identity }) => ({
|
hubList = hubs?.data.map(({ hub_url, identity }) => ({
|
||||||
value: hub_url,
|
value: hub_url,
|
||||||
label: identity.name,
|
label: identity.name,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
const { mutate } = useMutation({
|
|
||||||
|
const mutation = useMutation({
|
||||||
mutationFn: async (values) =>
|
mutationFn: async (values) =>
|
||||||
await axios({
|
await axios({
|
||||||
url: `http://localhost:3000/api/settings/saveSettings`,
|
url: `http://localhost:3000/api/settings/saveSettings`,
|
||||||
@@ -55,82 +57,112 @@ export const AirDCPPHubsForm = (): ReactElement => {
|
|||||||
settingsKey: "directConnect",
|
settingsKey: "directConnect",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
onSuccess: () => {
|
onSuccess: (data) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["settings"] });
|
queryClient.setQueryData(["settings"], (oldData: any) =>
|
||||||
|
produce(oldData, (draft: any) => {
|
||||||
|
draft.data.directConnect.client = {
|
||||||
|
...draft.data.directConnect.client,
|
||||||
|
...data.data.directConnect.client,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const validate = async () => {};
|
|
||||||
|
const validate = async (values) => {
|
||||||
|
const errors = {};
|
||||||
|
// Add any validation logic here if needed
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
const SelectAdapter = ({ input, ...rest }) => {
|
const SelectAdapter = ({ input, ...rest }) => {
|
||||||
return <Select {...input} {...rest} isClearable isMulti />;
|
return <Select {...input} {...rest} isClearable isMulti />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return <div>Error loading settings.</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isEmpty(hubList) && !isUndefined(hubs) ? (
|
{!isEmpty(hubList) && !isUndefined(hubs) ? (
|
||||||
<Form
|
<Form
|
||||||
onSubmit={mutate}
|
onSubmit={(values) => {
|
||||||
|
mutation.mutate(values);
|
||||||
|
}}
|
||||||
validate={validate}
|
validate={validate}
|
||||||
render={({ handleSubmit }) => (
|
render={({ handleSubmit }) => (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit} className="mt-10">
|
||||||
<div>
|
<h2 className="text-xl">Configure DC++ Hubs</h2>
|
||||||
<h3 className="title">Hubs</h3>
|
<article
|
||||||
|
role="alert"
|
||||||
|
className="mt-4 rounded-lg 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"
|
||||||
|
>
|
||||||
<h6 className="subtitle has-text-grey-light">
|
<h6 className="subtitle has-text-grey-light">
|
||||||
Select the hubs you want to perform searches against.
|
Select the hubs you want to perform searches against. Your
|
||||||
|
selection in the dropdown <strong>will replace</strong> the
|
||||||
|
existing selection.
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</article>
|
||||||
<div className="field">
|
|
||||||
<label className="label">AirDC++ Host</label>
|
|
||||||
<div className="control">
|
|
||||||
<Field
|
|
||||||
name="hubs"
|
|
||||||
component={SelectAdapter}
|
|
||||||
className="basic-multi-select"
|
|
||||||
placeholder="Select Hubs to Search Against"
|
|
||||||
options={hubList}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" className="button is-primary">
|
<div className="field">
|
||||||
|
<label className="block py-1 mt-3">AirDC++ Host</label>
|
||||||
|
<Field
|
||||||
|
name="hubs"
|
||||||
|
component={SelectAdapter}
|
||||||
|
className="basic-multi-select"
|
||||||
|
placeholder="Select Hubs to Search Against"
|
||||||
|
options={hubList}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="flex space-x-1 sm:mt-5 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-4 py-2 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||||
|
>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<article
|
||||||
<article
|
role="alert"
|
||||||
role="alert"
|
className="mt-4 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 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"
|
>
|
||||||
>
|
<div className="message-body">
|
||||||
<div className="message-body">
|
No configured hubs detected in AirDC++. <br />
|
||||||
No configured hubs detected in AirDC++. <br />
|
Configure to a hub in AirDC++ and then select a default hub here.
|
||||||
Configure to a hub in AirDC++ and then select a default hub here.
|
</div>
|
||||||
</div>
|
</article>
|
||||||
</article>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{!isEmpty(settings?.data.directConnect?.client.hubs) ? (
|
{!isEmpty(settings?.data.directConnect?.client.hubs) ? (
|
||||||
<>
|
<>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<article className="message is-warning">
|
<article className="message is-warning">
|
||||||
<div className="message-body is-size-6 is-family-secondary">
|
<div className="message-body is-size-6 is-family-secondary"></div>
|
||||||
Your selection in the dropdown <strong>will replace</strong> the
|
|
||||||
existing selection.
|
|
||||||
</div>
|
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<div className="box mt-3">
|
<div>
|
||||||
<h6>Default Hub For Searches:</h6>
|
<span className="flex items-center mt-10 mb-4">
|
||||||
{settings?.data.directConnect?.client.hubs.map(
|
<span className="text-xl text-slate-500 dark:text-slate-200 pr-5">
|
||||||
({ value, label }) => (
|
Default Hub for Searches
|
||||||
<div key={value}>
|
</span>
|
||||||
<div>{label}</div>
|
<span className="h-px flex-1 bg-slate-200 dark:bg-slate-400"></span>
|
||||||
<span className="is-size-7">{value}</span>
|
</span>
|
||||||
</div>
|
<div className="block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-slate-400 dark:border-gray-700">
|
||||||
),
|
{settings?.data.directConnect?.client.hubs.map(
|
||||||
)}
|
({ value, label }) => (
|
||||||
|
<div key={value}>
|
||||||
|
<div>{label}</div>
|
||||||
|
<span className="is-size-7">{value}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Card from "../shared/Carda";
|
|||||||
import T2Table from "../shared/T2Table";
|
import T2Table from "../shared/T2Table";
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
import { convert } from "html-to-text";
|
import { convert } from "html-to-text";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { SEARCH_SERVICE_BASE_URI } from "../../constants/endpoints";
|
import { SEARCH_SERVICE_BASE_URI } from "../../constants/endpoints";
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router";
|
||||||
|
|
||||||
type IHeaderProps = {
|
type IHeaderProps = {
|
||||||
headerContent: string;
|
headerContent: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement, useState } from "react";
|
import React, { ReactElement, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router";
|
||||||
import { useDarkMode } from "../../hooks/useDarkMode";
|
import { useDarkMode } from "../../hooks/useDarkMode";
|
||||||
|
|
||||||
export const Navbar2 = (): ReactElement => {
|
export const Navbar2 = (): ReactElement => {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import React from "react";
|
|
||||||
import { render } from "react-dom";
|
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import App from "./components/App";
|
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 Settings from "./components/Settings/Settings";
|
||||||
import { ErrorPage } from "./components/shared/ErrorPage";
|
import { ErrorPage } from "./components/shared/ErrorPage";
|
||||||
const rootEl = document.getElementById("root");
|
|
||||||
const root = createRoot(rootEl);
|
|
||||||
import i18n from "./shared/utils/i18n.util";
|
import i18n from "./shared/utils/i18n.util";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import Import from "./components/Import/Import";
|
import Import from "./components/Import/Import";
|
||||||
@@ -20,34 +16,48 @@ import WantedComics from "./components/WantedComics/WantedComics";
|
|||||||
|
|
||||||
const queryClient = new QueryClient();
|
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: "/",
|
future: {
|
||||||
element: <App />,
|
v7_relativeSplatPath: true,
|
||||||
errorElement: <ErrorPage />,
|
v7_fetcherPersist: true,
|
||||||
children: [
|
v7_normalizeFormMethod: true,
|
||||||
{ path: "/", element: <Dashboard /> },
|
v7_partialHydration: true,
|
||||||
{ path: "dashboard", element: <Dashboard /> },
|
v7_skipActionErrorRevalidation: true,
|
||||||
{ 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>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import io from "socket.io-client";
|
|||||||
import { SOCKET_BASE_URI } from "../constants/endpoints";
|
import { SOCKET_BASE_URI } from "../constants/endpoints";
|
||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
import { QueryClient } from "@tanstack/react-query";
|
import { QueryClient } from "@tanstack/react-query";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import "react-toastify/dist/ReactToastify.min.css";
|
||||||
|
|
||||||
/* Broadly, this file sets up:
|
/* Broadly, this file sets up:
|
||||||
* 1. The zustand-based global client state
|
* 1. The zustand-based global client state
|
||||||
@@ -82,6 +84,7 @@ if (!isNil(sessionId)) {
|
|||||||
"call",
|
"call",
|
||||||
"socket.resumeSession",
|
"socket.resumeSession",
|
||||||
{
|
{
|
||||||
|
namespace: "/",
|
||||||
sessionId,
|
sessionId,
|
||||||
},
|
},
|
||||||
(data) => console.log(data),
|
(data) => console.log(data),
|
||||||
@@ -129,6 +132,11 @@ socketIOInstance.on("LS_COVER_EXTRACTION_FAILED", (data) => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socketIOInstance.on("searchResultsAvailable", (data) => {
|
||||||
|
console.log(data);
|
||||||
|
toast(`Results found for query: ${JSON.stringify(data.query, null, 2)}`);
|
||||||
|
});
|
||||||
|
|
||||||
// 1b. Clear the localStorage sessionId upon receiving the
|
// 1b. Clear the localStorage sessionId upon receiving the
|
||||||
// LS_IMPORT_QUEUE_DRAINED event
|
// LS_IMPORT_QUEUE_DRAINED event
|
||||||
socketIOInstance.on("LS_IMPORT_QUEUE_DRAINED", (data) => {
|
socketIOInstance.on("LS_IMPORT_QUEUE_DRAINED", (data) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user