🏗️ Trying out react-query
This commit is contained in:
@@ -20,8 +20,8 @@
|
|||||||
"@dnd-kit/utilities": "^3.2.1",
|
"@dnd-kit/utilities": "^3.2.1",
|
||||||
"@fortawesome/fontawesome-free": "^6.3.0",
|
"@fortawesome/fontawesome-free": "^6.3.0",
|
||||||
"@redux-devtools/extension": "^3.2.5",
|
"@redux-devtools/extension": "^3.2.5",
|
||||||
"@reduxjs/toolkit": "^1.9.7",
|
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
|
"@tanstack/react-query": "^5.0.5",
|
||||||
"@tanstack/react-table": "^8.9.3",
|
"@tanstack/react-table": "^8.9.3",
|
||||||
"@types/mime-types": "^2.1.0",
|
"@types/mime-types": "^2.1.0",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
@@ -56,7 +56,6 @@
|
|||||||
"react-loader-spinner": "^4.0.0",
|
"react-loader-spinner": "^4.0.0",
|
||||||
"react-masonry-css": "^1.0.16",
|
"react-masonry-css": "^1.0.16",
|
||||||
"react-modal": "^3.15.1",
|
"react-modal": "^3.15.1",
|
||||||
"react-redux": "^8.0.5",
|
|
||||||
"react-router": "^6.9.0",
|
"react-router": "^6.9.0",
|
||||||
"react-router-dom": "^6.9.0",
|
"react-router-dom": "^6.9.0",
|
||||||
"react-select": "^5.3.2",
|
"react-select": "^5.3.2",
|
||||||
@@ -66,9 +65,6 @@
|
|||||||
"react-stickynode": "^4.1.0",
|
"react-stickynode": "^4.1.0",
|
||||||
"react-textarea-autosize": "^8.3.4",
|
"react-textarea-autosize": "^8.3.4",
|
||||||
"reapop": "^4.2.1",
|
"reapop": "^4.2.1",
|
||||||
"redux-first-history": "^5.1.1",
|
|
||||||
"redux-socket.io-middleware": "^1.0.4",
|
|
||||||
"redux-thunk": "^2.4.2",
|
|
||||||
"slick-carousel": "^1.8.1",
|
"slick-carousel": "^1.8.1",
|
||||||
"socket.io-client": "^4.3.2",
|
"socket.io-client": "^4.3.2",
|
||||||
"styled-components": "^6.0.7",
|
"styled-components": "^6.0.7",
|
||||||
@@ -86,6 +82,8 @@
|
|||||||
"@storybook/react": "^7.4.1",
|
"@storybook/react": "^7.4.1",
|
||||||
"@storybook/react-vite": "^7.4.1",
|
"@storybook/react-vite": "^7.4.1",
|
||||||
"@storybook/testing-library": "^0.2.0",
|
"@storybook/testing-library": "^0.2.0",
|
||||||
|
"@tanstack/eslint-plugin-query": "^5.0.5",
|
||||||
|
"@tanstack/react-query-devtools": "^5.1.0",
|
||||||
"@tsconfig/node14": "^1.0.0",
|
"@tsconfig/node14": "^1.0.0",
|
||||||
"@types/ellipsize": "^0.1.1",
|
"@types/ellipsize": "^0.1.1",
|
||||||
"@types/express": "^4.17.8",
|
"@types/express": "^4.17.8",
|
||||||
|
|||||||
@@ -23,7 +23,11 @@ import {
|
|||||||
AIRDCPP_DOWNLOAD_PROGRESS_TICK,
|
AIRDCPP_DOWNLOAD_PROGRESS_TICK,
|
||||||
LS_SINGLE_IMPORT,
|
LS_SINGLE_IMPORT,
|
||||||
} from "../constants/action-types";
|
} from "../constants/action-types";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method that initializes an AirDC++ socket connection
|
* Method that initializes an AirDC++ socket connection
|
||||||
@@ -32,128 +36,93 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
const AirDCPPSocketComponent = (): ReactElement => {
|
const AirDCPPSocketComponent = (): ReactElement => {
|
||||||
const airDCPPConfiguration = useContext(AirDCPPSocketContext);
|
// const airDCPPConfiguration = useContext(AirDCPPSocketContext);
|
||||||
const dispatch = useDispatch();
|
// const dispatch = useDispatch();
|
||||||
|
//
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const initializeAirDCPPEventListeners = async () => {
|
// const initializeAirDCPPEventListeners = async () => {
|
||||||
if (
|
// if (
|
||||||
!isUndefined(airDCPPConfiguration.airDCPPState) &&
|
// !isUndefined(airDCPPConfiguration.airDCPPState) &&
|
||||||
!isEmpty(airDCPPConfiguration.airDCPPState.settings) &&
|
// !isEmpty(airDCPPConfiguration.airDCPPState.settings) &&
|
||||||
!isEmpty(airDCPPConfiguration.airDCPPState.socket)
|
// !isEmpty(airDCPPConfiguration.airDCPPState.socket)
|
||||||
) {
|
// ) {
|
||||||
await airDCPPConfiguration.airDCPPState.socket.addListener(
|
// await airDCPPConfiguration.airDCPPState.socket.addListener(
|
||||||
"queue",
|
// "queue",
|
||||||
"queue_bundle_added",
|
// "queue_bundle_added",
|
||||||
async (data) => {
|
// async (data) => {
|
||||||
console.log("JEMEN:", data);
|
// console.log("JEMEN:", data);
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
// download tick listener
|
// // download tick listener
|
||||||
await airDCPPConfiguration.airDCPPState.socket.addListener(
|
// await airDCPPConfiguration.airDCPPState.socket.addListener(
|
||||||
`queue`,
|
// `queue`,
|
||||||
"queue_bundle_tick",
|
// "queue_bundle_tick",
|
||||||
async (downloadProgressData) => {
|
// async (downloadProgressData) => {
|
||||||
dispatch({
|
// dispatch({
|
||||||
type: AIRDCPP_DOWNLOAD_PROGRESS_TICK,
|
// type: AIRDCPP_DOWNLOAD_PROGRESS_TICK,
|
||||||
downloadProgressData,
|
// downloadProgressData,
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
// download complete listener
|
// // download complete listener
|
||||||
await airDCPPConfiguration.airDCPPState.socket.addListener(
|
// await airDCPPConfiguration.airDCPPState.socket.addListener(
|
||||||
`queue`,
|
// `queue`,
|
||||||
"queue_bundle_status",
|
// "queue_bundle_status",
|
||||||
async (bundleData) => {
|
// async (bundleData) => {
|
||||||
let count = 0;
|
// let count = 0;
|
||||||
if (bundleData.status.completed && bundleData.status.downloaded) {
|
// if (bundleData.status.completed && bundleData.status.downloaded) {
|
||||||
// dispatch the action for raw import, with the metadata
|
// // dispatch the action for raw import, with the metadata
|
||||||
if (count < 1) {
|
// if (count < 1) {
|
||||||
console.log(`[AirDCPP]: Download complete.`);
|
// console.log(`[AirDCPP]: Download complete.`);
|
||||||
dispatch({
|
// dispatch({
|
||||||
type: LS_SINGLE_IMPORT,
|
// type: LS_SINGLE_IMPORT,
|
||||||
meta: { remote: true },
|
// meta: { remote: true },
|
||||||
data: bundleData,
|
// data: bundleData,
|
||||||
});
|
// });
|
||||||
count += 1;
|
// count += 1;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
console.log(
|
// console.log(
|
||||||
"[AirDCPP]: Listener registered - listening to queue bundle download ticks",
|
// "[AirDCPP]: Listener registered - listening to queue bundle download ticks",
|
||||||
);
|
// );
|
||||||
console.log(
|
// console.log(
|
||||||
"[AirDCPP]: Listener registered - listening to queue bundle changes",
|
// "[AirDCPP]: Listener registered - listening to queue bundle changes",
|
||||||
);
|
// );
|
||||||
console.log(
|
// console.log(
|
||||||
"[AirDCPP]: Listener registered - listening to transfer completion",
|
// "[AirDCPP]: Listener registered - listening to transfer completion",
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
initializeAirDCPPEventListeners();
|
// initializeAirDCPPEventListeners();
|
||||||
}, [airDCPPConfiguration]);
|
// }, [airDCPPConfiguration]);
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
export const App = (): ReactElement => {
|
export const App = (): ReactElement => {
|
||||||
const dispatch = useDispatch();
|
// useEffect(() => {
|
||||||
useEffect(() => {
|
// // Check if there is a sessionId in localStorage
|
||||||
// Check if there is a sessionId in localStorage
|
// const sessionId = localStorage.getItem("sessionId");
|
||||||
const sessionId = localStorage.getItem("sessionId");
|
// if (!isNil(sessionId)) {
|
||||||
if (!isNil(sessionId)) {
|
// // Resume the session
|
||||||
// Resume the session
|
// dispatch({
|
||||||
dispatch({
|
// type: "RESUME_SESSION",
|
||||||
type: "RESUME_SESSION",
|
// meta: { remote: true },
|
||||||
meta: { remote: true },
|
// session: { sessionId },
|
||||||
session: { sessionId },
|
// });
|
||||||
});
|
// } else {
|
||||||
} else {
|
// // Inititalize the session and persist the sessionId to localStorage
|
||||||
// Inititalize the session and persist the sessionId to localStorage
|
// socketIOConnectionInstance.on("sessionInitialized", (sessionId) => {
|
||||||
socketIOConnectionInstance.on("sessionInitialized", (sessionId) => {
|
// localStorage.setItem("sessionId", sessionId);
|
||||||
localStorage.setItem("sessionId", sessionId);
|
// });
|
||||||
});
|
// }
|
||||||
}
|
// }, []);
|
||||||
}, []);
|
|
||||||
return (
|
return (
|
||||||
<SocketIOProvider socket={socketIOConnectionInstance}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<AirDCPPSocketContextProvider>
|
{/* The rest of your application */}
|
||||||
<div>
|
<ReactQueryDevtools initialIsOpen={true} />
|
||||||
<AirDCPPSocketComponent />
|
{/* <AirDCPPSocketComponent /> */};
|
||||||
<Routes>
|
</QueryClientProvider>
|
||||||
<Route path="/" element={<Dashboard />} />
|
|
||||||
<Route path="/import" element={<Import path={"./comics"} />} />
|
|
||||||
<Route
|
|
||||||
path="/library"
|
|
||||||
element={<TabulatedContentContainer category="library" />}
|
|
||||||
/>
|
|
||||||
<Route path="/library-grid" element={<LibraryGrid />} />
|
|
||||||
<Route path="/downloads" element={<Downloads data={{}} />} />
|
|
||||||
<Route path="/search" element={<Search />} />
|
|
||||||
<Route
|
|
||||||
path={"/comic/details/:comicObjectId"}
|
|
||||||
element={<ComicDetailContainer />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path={"/volume/details/:comicObjectId"}
|
|
||||||
element={<VolumeDetail />}
|
|
||||||
/>
|
|
||||||
<Route path="/settings" element={<Settings />} />
|
|
||||||
<Route
|
|
||||||
path="/pull-list/all"
|
|
||||||
element={<TabulatedContentContainer category="pullList" />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/wanted/all"
|
|
||||||
element={<TabulatedContentContainer category="wanted" />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/volumes/all"
|
|
||||||
element={<TabulatedContentContainer category="volumes" />}
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
</div>
|
|
||||||
</AirDCPPSocketContextProvider>
|
|
||||||
</SocketIOProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { isEmpty, isNil } from "lodash";
|
|||||||
import Header from "../shared/Header";
|
import Header from "../shared/Header";
|
||||||
|
|
||||||
export const Dashboard = (): ReactElement => {
|
export const Dashboard = (): ReactElement => {
|
||||||
const dispatch = useDispatch();
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// dispatch(fetchVolumeGroups());
|
// dispatch(fetchVolumeGroups());
|
||||||
// dispatch(
|
// dispatch(
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import React, { ReactElement, useCallback } from "react";
|
import React, { ReactElement, useCallback } from "react";
|
||||||
import { saveSettings } from "../../../actions/settings.actions";
|
import { saveSettings } from "../../../actions/settings.actions";
|
||||||
import { ConnectionForm } from "../../shared/ConnectionForm/ConnectionForm";
|
import { ConnectionForm } from "../../shared/ConnectionForm/ConnectionForm";
|
||||||
import { useConnectToQBittorrentClientQuery } from "../../../services/torrents.api";
|
|
||||||
|
|
||||||
export const QbittorrentConnectionForm = (): ReactElement => {
|
export const QbittorrentConnectionForm = (): ReactElement => {
|
||||||
const { data, isLoading } = useConnectToQBittorrentClientQuery({});
|
|
||||||
const onSubmit = useCallback(async (values) => {
|
const onSubmit = useCallback(async (values) => {
|
||||||
try {
|
try {
|
||||||
dispatch(saveSettings(values, "bittorrent"));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
@@ -15,13 +12,11 @@ export const QbittorrentConnectionForm = (): ReactElement => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isLoading && (
|
<ConnectionForm
|
||||||
<ConnectionForm
|
initialData={data?.bittorrent.client.host}
|
||||||
initialData={data?.bittorrent.client.host}
|
submitHandler={onSubmit}
|
||||||
submitHandler={onSubmit}
|
formHeading={"Qbittorrent Configuration"}
|
||||||
formHeading={"Qbittorrent Configuration"}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<pre>{JSON.stringify(data?.qbittorrentClientInfo, null, 2)}</pre>
|
<pre>{JSON.stringify(data?.qbittorrentClientInfo, null, 2)}</pre>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,22 +7,18 @@ import { ServiceStatuses } from "../ServiceStatuses/ServiceStatuses";
|
|||||||
import settingsObject from "../../constants/settings/settingsMenu.json";
|
import settingsObject from "../../constants/settings/settingsMenu.json";
|
||||||
import { isUndefined, map } from "lodash";
|
import { isUndefined, map } from "lodash";
|
||||||
|
|
||||||
interface ISettingsProps { }
|
interface ISettingsProps {}
|
||||||
|
|
||||||
export const Settings = (props: ISettingsProps): ReactElement => {
|
export const Settings = (props: ISettingsProps): ReactElement => {
|
||||||
const [active, setActive] = useState("gen-db");
|
const [active, setActive] = useState("gen-db");
|
||||||
const settingsContent = [
|
const settingsContent = [
|
||||||
{
|
{
|
||||||
id: "adc-hubs",
|
id: "adc-hubs",
|
||||||
content: <div key="adc-hubs">{<AirDCPPHubsForm />}</div>,
|
content: <div key="adc-hubs">{/* <AirDCPPHubsForm /> */}</div>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "adc-connection",
|
id: "adc-connection",
|
||||||
content: (
|
content: <div key="adc-connection">{/* <AirDCPPSettingsForm /> */}</div>,
|
||||||
<div key="adc-connection">
|
|
||||||
<AirDCPPSettingsForm />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "qbt-connection",
|
id: "qbt-connection",
|
||||||
@@ -34,15 +30,11 @@ export const Settings = (props: ISettingsProps): ReactElement => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "core-service",
|
id: "core-service",
|
||||||
content: <ServiceStatuses />,
|
content: <>a</>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "flushdb",
|
id: "flushdb",
|
||||||
content: (
|
content: <div key="flushdb">{/* <SystemSettingsForm /> */}</div>,
|
||||||
<div key="flushdb">
|
|
||||||
<SystemSettingsForm />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,46 +2,10 @@ import React, { useContext } from "react";
|
|||||||
import { SearchBar } from "../GlobalSearchBar/SearchBar";
|
import { SearchBar } from "../GlobalSearchBar/SearchBar";
|
||||||
import { DownloadProgressTick } from "../ComicDetail/DownloadProgressTick";
|
import { DownloadProgressTick } from "../ComicDetail/DownloadProgressTick";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { isUndefined } from "lodash";
|
import { isUndefined } from "lodash";
|
||||||
import { format, fromUnixTime } from "date-fns";
|
import { format, fromUnixTime } from "date-fns";
|
||||||
|
|
||||||
const Navbar: React.FunctionComponent = (props) => {
|
const Navbar: React.FunctionComponent = (props) => {
|
||||||
const downloadProgressTick = useSelector(
|
|
||||||
(state: RootState) => state.airdcpp.downloadProgressData,
|
|
||||||
);
|
|
||||||
|
|
||||||
const airDCPPSocketConnectionStatus = useSelector(
|
|
||||||
(state: RootState) => state.airdcpp.isAirDCPPSocketConnected,
|
|
||||||
);
|
|
||||||
const airDCPPSessionInfo = useSelector(
|
|
||||||
(state: RootState) => state.airdcpp.airDCPPSessionInfo,
|
|
||||||
);
|
|
||||||
const qBittorrentConnectionInfo = useSelector(
|
|
||||||
(state: RootState) => state.settings.data,
|
|
||||||
);
|
|
||||||
const socketDisconnectionReason = useSelector(
|
|
||||||
(state: RootState) => state.airdcpp.socketDisconnectionReason,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Import-related selector hooks
|
|
||||||
const successfulImportJobCount = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.successfulJobCount,
|
|
||||||
);
|
|
||||||
const failedImportJobCount = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.failedJobCount,
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastQueueJob = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.lastQueueJob,
|
|
||||||
);
|
|
||||||
const libraryQueueImportStatus = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.LSQueueImportStatus,
|
|
||||||
);
|
|
||||||
|
|
||||||
const allImportJobResults = useSelector(
|
|
||||||
(state: RootState) => state.fileOps.importJobStatistics,
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<nav className="navbar is-fixed-top">
|
<nav className="navbar is-fixed-top">
|
||||||
<div className="navbar-brand">
|
<div className="navbar-brand">
|
||||||
@@ -91,8 +55,6 @@ const Navbar: React.FunctionComponent = (props) => {
|
|||||||
Downloads
|
Downloads
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<SearchBar />
|
|
||||||
|
|
||||||
<Link to="/search" className="navbar-item">
|
<Link to="/search" className="navbar-item">
|
||||||
Search ComicVine
|
Search ComicVine
|
||||||
</Link>
|
</Link>
|
||||||
@@ -101,102 +63,7 @@ const Navbar: React.FunctionComponent = (props) => {
|
|||||||
<div className="navbar-end">
|
<div className="navbar-end">
|
||||||
<a className="navbar-item is-hidden-desktop-only"></a>
|
<a className="navbar-item is-hidden-desktop-only"></a>
|
||||||
|
|
||||||
<div className="navbar-item has-dropdown is-hoverable">
|
|
||||||
<a className="navbar-link is-arrowless">
|
|
||||||
<i className="fa-solid fa-download"></i>
|
|
||||||
{downloadProgressTick && <div className="pulsating-circle"></div>}
|
|
||||||
</a>
|
|
||||||
{!isUndefined(downloadProgressTick) ? (
|
|
||||||
<div className="navbar-dropdown is-right is-boxed">
|
|
||||||
<a className="navbar-item">
|
|
||||||
<DownloadProgressTick data={downloadProgressTick} />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!isUndefined(libraryQueueImportStatus) &&
|
|
||||||
location.hash !== "#/import" ? (
|
|
||||||
<div className="navbar-item has-dropdown is-hoverable">
|
|
||||||
<a className="navbar-link is-arrowless">
|
|
||||||
<i className="fa-solid fa-file-import has-text-warning-dark"></i>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div className="navbar-dropdown is-right is-boxed">
|
|
||||||
<a className="navbar-item">
|
|
||||||
<ul>
|
|
||||||
{successfulImportJobCount > 0 ? (
|
|
||||||
<li className="mb-2">
|
|
||||||
<span className="tag is-success mr-2">
|
|
||||||
{successfulImportJobCount}
|
|
||||||
</span>
|
|
||||||
imported.
|
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
{failedImportJobCount > 0 ? (
|
|
||||||
<li>
|
|
||||||
<span className="tag is-danger mr-2">
|
|
||||||
{failedImportJobCount}
|
|
||||||
</span>
|
|
||||||
failed to import.
|
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
</ul>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{/* AirDC++ socket connection status */}
|
{/* AirDC++ socket connection status */}
|
||||||
<div className="navbar-item has-dropdown is-hoverable">
|
|
||||||
{airDCPPSocketConnectionStatus ? (
|
|
||||||
<>
|
|
||||||
<a className="navbar-link is-arrowless">
|
|
||||||
<i className="fa-solid fa-tower-cell"></i>
|
|
||||||
</a>
|
|
||||||
<div className="navbar-dropdown pr-2 pl-2 is-right airdcpp-status is-boxed">
|
|
||||||
{/* AirDC++ Session Information */}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Last login was{" "}
|
|
||||||
<span className="tag">
|
|
||||||
{format(
|
|
||||||
fromUnixTime(airDCPPSessionInfo.user.last_login),
|
|
||||||
"dd MMMM, yyyy",
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<hr className="navbar-divider" />
|
|
||||||
<p>
|
|
||||||
<span className="tag has-text-success">
|
|
||||||
{airDCPPSessionInfo.user.username}
|
|
||||||
</span>
|
|
||||||
connected to{" "}
|
|
||||||
<span className="tag has-text-success">
|
|
||||||
{airDCPPSessionInfo.system_info.client_version}
|
|
||||||
</span>{" "}
|
|
||||||
with session ID{" "}
|
|
||||||
<span className="tag has-text-success">
|
|
||||||
{airDCPPSessionInfo.session_id}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* <pre>{JSON.stringify(airDCPPSessionInfo, null, 2)}</pre> */}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<a className="navbar-link is-arrowless has-text-danger">
|
|
||||||
<i className="fa-solid fa-bolt"></i>
|
|
||||||
</a>
|
|
||||||
<div className="navbar-dropdown pr-2 pl-2 is-right is-boxed">
|
|
||||||
<pre>
|
|
||||||
{JSON.stringify(socketDisconnectionReason, null, 2)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="navbar-item has-dropdown is-hoverable is-mega">
|
<div className="navbar-item has-dropdown is-hoverable is-mega">
|
||||||
<div className="navbar-link flex">Blog</div>
|
<div className="navbar-link flex">Blog</div>
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
|
||||||
import type { RootState, AppDispatch } from "../store";
|
|
||||||
|
|
||||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
|
||||||
export const useAppDispatch: () => AppDispatch = useDispatch;
|
|
||||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { render } from "react-dom";
|
import { render } from "react-dom";
|
||||||
import { Provider, connect } from "react-redux";
|
|
||||||
import { HistoryRouter as Router } from "redux-first-history/rr6";
|
|
||||||
import { store, history } from "./store/index";
|
|
||||||
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 Settings from "./components/Settings/Settings";
|
||||||
const rootEl = document.getElementById("root");
|
const rootEl = document.getElementById("root");
|
||||||
const root = createRoot(rootEl);
|
const root = createRoot(rootEl);
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <App />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/settings",
|
||||||
|
element: <Settings />,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<Provider store={store}>
|
<React.StrictMode>
|
||||||
<Router history={history}>
|
<RouterProvider router={router} />
|
||||||
<App />
|
</React.StrictMode>,
|
||||||
</Router>
|
|
||||||
</Provider>,
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
|
||||||
|
|
||||||
// initialize an empty api service that we'll inject endpoints into later as needed
|
|
||||||
export const emptySplitApi = createApi({
|
|
||||||
baseQuery: fetchBaseQuery({ baseUrl: "http://" }),
|
|
||||||
endpoints: () => ({}),
|
|
||||||
});
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { emptySplitApi } from "./empty.api";
|
|
||||||
import { useConnectToQBittorrentClientQuery } from "./torrents.api";
|
|
||||||
|
|
||||||
export const settingsApi = emptySplitApi.injectEndpoints({
|
|
||||||
endpoints: (builder) => ({
|
|
||||||
getAllSettings: builder.query({
|
|
||||||
query: () => "localhost:3000/api/settings/getAllSettings",
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
overrideExisting: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const { useGetAllSettingsQuery } = settingsApi;
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { emptySplitApi } from "./empty.api";
|
|
||||||
|
|
||||||
export const torrentsApi = emptySplitApi.injectEndpoints({
|
|
||||||
endpoints: (builder) => ({
|
|
||||||
connectToQBittorrentClient: builder.query({
|
|
||||||
queryFn: async (_arg, _queryApi, _extraOptions, fetchWithBQ) => {
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
data: { bittorrent },
|
|
||||||
} = await fetchWithBQ("localhost:3000/api/settings/getAllSettings");
|
|
||||||
await fetchWithBQ({
|
|
||||||
url: "localhost:3060/api/qbittorrent/connect",
|
|
||||||
method: "POST",
|
|
||||||
body: bittorrent?.client?.host,
|
|
||||||
});
|
|
||||||
const { data } = await fetchWithBQ({
|
|
||||||
url: "localhost:3060/api/qbittorrent/getClientInfo",
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: { bittorrent, qbittorrentClientInfo: data },
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
overrideExisting: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const { useConnectToQBittorrentClientQuery } = torrentsApi;
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { createHashHistory } from "history";
|
|
||||||
import thunk from "redux-thunk";
|
|
||||||
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
|
|
||||||
import { createReduxHistoryContext } from "redux-first-history";
|
|
||||||
import socketIoMiddleware from "redux-socket.io-middleware";
|
|
||||||
import socketIOMiddleware from "../shared/middleware/SocketIOMiddleware";
|
|
||||||
import socketIOConnectionInstance from "../shared/socket.io/instance";
|
|
||||||
import settingsReducer from "../reducers/settings.reducer";
|
|
||||||
import { settingsApi } from "../services/settings.api";
|
|
||||||
|
|
||||||
const customSocketIOMiddleware = socketIOMiddleware(socketIOConnectionInstance);
|
|
||||||
|
|
||||||
const { createReduxHistory, routerMiddleware, routerReducer } =
|
|
||||||
createReduxHistoryContext({
|
|
||||||
history: createHashHistory(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const rootReducer = (history) => ({
|
|
||||||
settings: settingsReducer,
|
|
||||||
[settingsApi.reducerPath]: settingsApi.reducer,
|
|
||||||
router: routerReducer,
|
|
||||||
});
|
|
||||||
|
|
||||||
const preloadedState = {};
|
|
||||||
export const store = configureStore({
|
|
||||||
middleware: [
|
|
||||||
socketIoMiddleware(socketIOConnectionInstance),
|
|
||||||
customSocketIOMiddleware,
|
|
||||||
thunk,
|
|
||||||
routerMiddleware,
|
|
||||||
settingsApi.middleware,
|
|
||||||
],
|
|
||||||
reducer: rootReducer(createHashHistory()),
|
|
||||||
preloadedState,
|
|
||||||
});
|
|
||||||
export type AppDispatch = typeof store.dispatch;
|
|
||||||
export type RootState = ReturnType<typeof store.getState>;
|
|
||||||
export type AppThunk<ReturnType = void> = ThunkAction<
|
|
||||||
ReturnType,
|
|
||||||
RootState,
|
|
||||||
unknown,
|
|
||||||
Action<string>
|
|
||||||
>;
|
|
||||||
export const history = createReduxHistory(store);
|
|
||||||
Reference in New Issue
Block a user