Comicvine integration improvements (#109)
* ⚡️ Refactored VolumeDetail page to use react-query * 🎨 Added some icons to tabs * 📚 Wired up story arc fetching * ✅ Added status checks * 🍇 Added some integration for issues * 🔍 Improvements to CV search results * 🔍 Refining CV search UX * 🌍 Added i18n lib * 🔍 CV search metadata wrangling * 🔧 Refactored Wanted component Included # of issues in a wanted volume * 🔧 Refactoring DC++ search/download * 🔧 Refactored AirDC++ init in store * 🏗️ Automatic downloads WIP * 🏗️ Modified the Dockerfile
This commit was merged in pull request #109.
This commit is contained in:
@@ -3,20 +3,11 @@ import { Form, Field } from "react-final-form";
|
||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
||||
import Select from "react-select";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useStore } from "../../../store";
|
||||
import axios from "axios";
|
||||
import { AIRDCPP_SERVICE_BASE_URI } from "../../../constants/endpoints";
|
||||
|
||||
export const AirDCPPHubsForm = (): ReactElement => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
airDCPPSocketInstance,
|
||||
airDCPPClientConfiguration,
|
||||
airDCPPSessionInformation,
|
||||
} = useStore((state) => ({
|
||||
airDCPPSocketInstance: state.airDCPPSocketInstance,
|
||||
airDCPPClientConfiguration: state.airDCPPClientConfiguration,
|
||||
airDCPPSessionInformation: state.airDCPPSessionInformation,
|
||||
}));
|
||||
|
||||
const {
|
||||
data: settings,
|
||||
@@ -36,11 +27,19 @@ export const AirDCPPHubsForm = (): ReactElement => {
|
||||
*/
|
||||
const { data: hubs } = useQuery({
|
||||
queryKey: ["hubs"],
|
||||
queryFn: async () => await airDCPPSocketInstance.get(`hubs`),
|
||||
queryFn: async () =>
|
||||
await axios({
|
||||
url: `${AIRDCPP_SERVICE_BASE_URI}/getHubs`,
|
||||
method: "POST",
|
||||
data: {
|
||||
host: settings?.data.directConnect?.client?.host,
|
||||
},
|
||||
}),
|
||||
enabled: !isEmpty(settings?.data.directConnect?.client?.host),
|
||||
});
|
||||
let hubList = {};
|
||||
if (!isNil(hubs)) {
|
||||
hubList = hubs.map(({ hub_url, identity }) => ({
|
||||
hubList = hubs?.data.map(({ hub_url, identity }) => ({
|
||||
value: hub_url,
|
||||
label: identity.name,
|
||||
}));
|
||||
@@ -101,7 +100,10 @@ export const AirDCPPHubsForm = (): ReactElement => {
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<article className="message">
|
||||
<article
|
||||
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"
|
||||
>
|
||||
<div className="message-body">
|
||||
No configured hubs detected in AirDC++. <br />
|
||||
Configure to a hub in AirDC++ and then select a default hub here.
|
||||
|
||||
@@ -1,76 +1,65 @@
|
||||
import React, { ReactElement, useCallback } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { AirDCPPSettingsConfirmation } from "./AirDCPPSettingsConfirmation";
|
||||
import { isUndefined, isEmpty } from "lodash";
|
||||
import { ConnectionForm } from "../../shared/ConnectionForm/ConnectionForm";
|
||||
import { initializeAirDCPPSocket, useStore } from "../../../store/index";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import {
|
||||
AIRDCPP_SERVICE_BASE_URI,
|
||||
SETTINGS_SERVICE_BASE_URI,
|
||||
} from "../../../constants/endpoints";
|
||||
|
||||
export const AirDCPPSettingsForm = (): ReactElement => {
|
||||
// cherry-picking selectors for:
|
||||
// 1. initial values for the form
|
||||
// 2. If initial values are present, get the socket information to display
|
||||
const { setState } = useStore;
|
||||
const {
|
||||
airDCPPSocketConnected,
|
||||
airDCPPDisconnectionInfo,
|
||||
airDCPPSessionInformation,
|
||||
airDCPPClientConfiguration,
|
||||
airDCPPSocketInstance,
|
||||
setAirDCPPSocketInstance,
|
||||
} = useStore(
|
||||
useShallow((state) => ({
|
||||
airDCPPSocketConnected: state.airDCPPSocketConnected,
|
||||
airDCPPDisconnectionInfo: state.airDCPPDisconnectionInfo,
|
||||
airDCPPClientConfiguration: state.airDCPPClientConfiguration,
|
||||
airDCPPSessionInformation: state.airDCPPSessionInformation,
|
||||
airDCPPSocketInstance: state.airDCPPSocketInstance,
|
||||
setAirDCPPSocketInstance: state.setAirDCPPSocketInstance,
|
||||
})),
|
||||
);
|
||||
export const AirDCPPSettingsForm = () => {
|
||||
const [airDCPPSessionInformation, setAirDCPPSessionInformation] =
|
||||
useState(null);
|
||||
// Fetching all settings
|
||||
const { data: settingsData, isSuccess: settingsSuccess } = useQuery({
|
||||
queryKey: ["airDCPPSettings"],
|
||||
queryFn: () => axios.get(`${SETTINGS_SERVICE_BASE_URI}/getAllSettings`),
|
||||
});
|
||||
|
||||
/**
|
||||
* Mutation to update settings and subsequently initialize
|
||||
* AirDC++ socket with those settings
|
||||
*/
|
||||
// Fetch session information
|
||||
const fetchSessionInfo = (host) => {
|
||||
return axios.post(`${AIRDCPP_SERVICE_BASE_URI}/initialize`, { host });
|
||||
};
|
||||
|
||||
// Use effect to trigger side effects on settings fetch success
|
||||
useEffect(() => {
|
||||
if (settingsSuccess && settingsData?.data?.directConnect?.client?.host) {
|
||||
const host = settingsData.data.directConnect.client.host;
|
||||
fetchSessionInfo(host).then((response) => {
|
||||
setAirDCPPSessionInformation(response.data);
|
||||
});
|
||||
}
|
||||
}, [settingsSuccess, settingsData]);
|
||||
|
||||
// Handle setting update and subsequent AirDC++ initialization
|
||||
const { mutate } = useMutation({
|
||||
mutationFn: async (values) =>
|
||||
await axios({
|
||||
url: `http://localhost:3000/api/settings/saveSettings`,
|
||||
method: "POST",
|
||||
data: { settingsPayload: values, settingsKey: "directConnect" },
|
||||
}),
|
||||
onSuccess: async (values) => {
|
||||
const {
|
||||
data: {
|
||||
directConnect: {
|
||||
client: { host },
|
||||
},
|
||||
},
|
||||
} = values;
|
||||
const dcppSocketInstance = await initializeAirDCPPSocket(host);
|
||||
setState({
|
||||
airDCPPClientConfiguration: host,
|
||||
airDCPPSocketInstance: dcppSocketInstance,
|
||||
mutationFn: (values) => {
|
||||
console.log(values);
|
||||
return axios.post("http://localhost:3000/api/settings/saveSettings", {
|
||||
settingsPayload: values,
|
||||
settingsKey: "directConnect",
|
||||
});
|
||||
},
|
||||
onSuccess: async (response) => {
|
||||
const host = response?.data?.directConnect?.client?.host;
|
||||
if (host) {
|
||||
const response = await fetchSessionInfo(host);
|
||||
setAirDCPPSessionInformation(response.data);
|
||||
// setState({ airDCPPClientConfiguration: host });
|
||||
}
|
||||
},
|
||||
});
|
||||
const deleteSettingsMutation = useMutation(
|
||||
async () =>
|
||||
await axios.post("http://localhost:3000/api/settings/saveSettings", {
|
||||
settingsPayload: {},
|
||||
settingsKey: "directConnect",
|
||||
}),
|
||||
|
||||
const deleteSettingsMutation = useMutation(() =>
|
||||
axios.post("http://localhost:3000/api/settings/saveSettings", {
|
||||
settingsPayload: {},
|
||||
settingsKey: "directConnect",
|
||||
}),
|
||||
);
|
||||
|
||||
// const removeSettings = useCallback(async () => {
|
||||
// // airDCPPSettings.setSettings({});
|
||||
// }, []);
|
||||
//
|
||||
const initFormData = !isUndefined(airDCPPClientConfiguration)
|
||||
? airDCPPClientConfiguration
|
||||
: {};
|
||||
const initFormData = settingsData?.data?.directConnect?.client?.host ?? {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectionForm
|
||||
@@ -79,13 +68,12 @@ export const AirDCPPSettingsForm = (): ReactElement => {
|
||||
formHeading={"Configure AirDC++"}
|
||||
/>
|
||||
|
||||
{!isEmpty(airDCPPSessionInformation) ? (
|
||||
{airDCPPSessionInformation && (
|
||||
<AirDCPPSettingsConfirmation settings={airDCPPSessionInformation} />
|
||||
) : null}
|
||||
)}
|
||||
|
||||
{!isEmpty(airDCPPClientConfiguration) ? (
|
||||
{settingsData?.data && (
|
||||
<p className="control mt-4">
|
||||
as
|
||||
<button
|
||||
className="button is-danger"
|
||||
onClick={() => deleteSettingsMutation.mutate()}
|
||||
@@ -93,7 +81,7 @@ export const AirDCPPSettingsForm = (): ReactElement => {
|
||||
Delete
|
||||
</button>
|
||||
</p>
|
||||
) : null}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -48,13 +48,11 @@ export const SystemSettingsForm = (): ReactElement => {
|
||||
</article>
|
||||
|
||||
<button
|
||||
className={
|
||||
isLoading ? "button is-danger is-loading" : "button is-danger"
|
||||
}
|
||||
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-red-400 dark:border-red-200 bg-red-200 px-2 py-1 text-gray-500 hover:bg-transparent hover:text-red-600 focus:outline-none focus:ring active:text-indigo-500"
|
||||
onClick={() => flushDb()}
|
||||
>
|
||||
<span className="icon">
|
||||
<i className="fas fa-eraser"></i>
|
||||
<span className="pt-1 px-1">
|
||||
<i className="icon-[solar--trash-bin-trash-bold-duotone] w-7 h-7"></i>
|
||||
</span>
|
||||
<span>Flush DB & Temporary Folders</span>
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user