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:
2024-05-11 18:51:28 -04:00
committed by GitHub
parent f57bd35cd4
commit 9ab15df0a8
36 changed files with 1348 additions and 2069 deletions

View File

@@ -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.

View File

@@ -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}
)}
</>
);
};

View File

@@ -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>