⚙️Settings-driven AirDC++ configuration first draft

This commit is contained in:
2021-11-19 13:49:39 -08:00
parent 2104f12e8f
commit 725c156e88
10 changed files with 161 additions and 85 deletions

View File

@@ -30,10 +30,16 @@ function sleep(ms: number): Promise<NodeJS.Timeout> {
}
export const search =
(data: SearchData, ADCPPSocket: any) => async (dispatch) => {
(data: SearchData, ADCPPSocket: any, credentials: any) =>
async (dispatch) => {
try {
console.log(credentials);
if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect("admin", "password", true);
await ADCPPSocket.connect(
credentials.username,
credentials.password,
true,
);
}
const instance: SearchInstance = await ADCPPSocket.post("search");
dispatch({

View File

@@ -1,5 +1,8 @@
import axios from "axios";
import { SETTINGS_OBJECT_FETCHED } from "../constants/action-types";
import {
SETTINGS_OBJECT_FETCHED,
SETTINGS_OBJECT_DELETED,
} from "../constants/action-types";
import { SETTINGS_SERVICE_BASE_URI } from "../constants/endpoints";
export const saveSettings =
@@ -29,3 +32,17 @@ export const getSettings = (settingsKey?) => async (dispatch) => {
});
}
};
export const deleteSettings = () => async (dispatch) => {
const result = await axios({
url: `${SETTINGS_SERVICE_BASE_URI}/deleteSettings`,
method: "POST",
});
if (result.data.ok === 1) {
dispatch({
type: SETTINGS_OBJECT_FETCHED,
data: {},
});
}
};

View File

@@ -1,14 +1,16 @@
import React, { useCallback, useContext, ReactElement } from "react";
import React, { useCallback, useContext, ReactElement, useEffect } from "react";
import {
search,
downloadAirDCPPItem,
getBundlesForComic,
} from "../actions/airdcpp.actions";
import { SocketContext } from "../context/AirDCPPSocket";
import { useDispatch, useSelector } from "react-redux";
import { RootState, SearchInstance } from "threetwo-ui-typings";
import ellipsize from "ellipsize";
import { isEmpty, isNil, isUndefined, map } from "lodash";
import { AirDCPPSocketContext } from "../context/AirDCPPSocket";
import { getSettings } from "../actions/settings.actions";
import AirDCPPSocket from "../services/DcppSearchService";
interface IAcquisitionPanelProps {
comicBookMetadata: any;
}
@@ -21,7 +23,6 @@ export const AcquisitionPanel = (
const sanitizedVolumeName = volumeName.replace(/[^a-zA-Z0-9 ]/g, "");
const issueName = props.comicBookMetadata.sourcedMetadata.comicvine.name;
// local state
// Selectors for picking state
const airDCPPSearchResults = useSelector((state: RootState) => {
return state.airdcpp.searchResults;
@@ -35,18 +36,31 @@ export const AcquisitionPanel = (
const searchInstance: SearchInstance = useSelector(
(state: RootState) => state.airdcpp.searchInstance,
);
const airDCPPClientSettings = useSelector(
(state: RootState) => state.settings.data,
);
const userSettings = useSelector((state: RootState) => state.settings.data);
const { ADCPPSocket, setADCPPSocket } = useContext(AirDCPPSocketContext);
const dispatch = useDispatch();
const ADCPPSocket = useContext(SocketContext);
useEffect(() => {
dispatch(getSettings());
}, []);
if (isEmpty(ADCPPSocket) && !isEmpty(userSettings)) {
setADCPPSocket(
new AirDCPPSocket({
hostname: `${userSettings.directConnect.client.hostname}`,
}),
);
}
const getDCPPSearchResults = useCallback(
(searchQuery) => {
console.log(ADCPPSocket);
dispatch(search(searchQuery, ADCPPSocket));
dispatch(
search(searchQuery, ADCPPSocket, {
username: `${userSettings.directConnect.client.username}`,
password: `${userSettings.directConnect.client.password}`,
}),
);
},
[dispatch, ADCPPSocket],
);
@@ -80,8 +94,9 @@ export const AcquisitionPanel = (
return (
<>
{JSON.stringify(ADCPPSocket)}
<div className="comic-detail columns">
{!isEmpty(ADCPPSocket) && !isUndefined(ADCPPSocket) ? (
{!isEmpty(ADCPPSocket) ? (
<div className="column is-one-fifth">
<button
className={

View File

@@ -1,25 +1,31 @@
import React, { ReactElement, useEffect } from "react";
import React, { ReactElement, useCallback, useContext, useEffect } from "react";
import { Form, Field } from "react-final-form";
import { useDispatch, useSelector } from "react-redux";
import { saveSettings, getSettings } from "../actions/settings.actions";
import { useSelector, useDispatch } from "react-redux";
import {
getSettings,
saveSettings,
deleteSettings,
} from "../actions/settings.actions";
import { AirDCPPSettingsConfirmation } from "./AirDCPPSettings/AirDCPPSettingsConfirmation";
import axios from "axios";
import { AirDCPPSocketContext } from "../context/AirDCPPSocket";
import AirDCPPSocket from "../services/DcppSearchService";
import { isUndefined, isEmpty } from "lodash";
export const AirDCPPSettingsForm = (): ReactElement => {
const airdcppClientSettings = useSelector(
const airDCPPClientSettings = useSelector(
(state: RootState) => state.settings.data,
);
const { ADCPPSocket, setADCPPSocket } = useContext(AirDCPPSocketContext);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getSettings());
}, [dispatch]);
}, []);
const onSubmit = async (values) => {
try {
const fqdn = values.protocol + values.hostname;
const airdcppResponse = await axios({
const airDCPPResponse = await axios({
url: `${fqdn}/api/v1/sessions/authorize`,
method: "POST",
data: {
@@ -27,18 +33,37 @@ export const AirDCPPSettingsForm = (): ReactElement => {
password: values.password,
},
});
if (airdcppResponse.status === 200) {
dispatch(saveSettings(values, airdcppResponse.data));
if (airDCPPResponse.status === 200) {
dispatch(saveSettings(values, airDCPPResponse.data));
setADCPPSocket(
new AirDCPPSocket({
hostname: `${values.hostname}`,
}),
);
const hubList = await axios({
url: `${fqdn}/api/v1/hubs`,
method: "GET",
params: {
username: values.username,
password: values.password,
},
});
}
} catch (error) {
console.log(error);
}
};
const removeSettings = useCallback(async () => {
dispatch(deleteSettings());
setADCPPSocket({});
}, []);
const validate = async () => {};
const initFormData =
!isEmpty(airdcppClientSettings.directConnect) ||
!isUndefined(airdcppClientSettings.directConnect)
? airdcppClientSettings.directConnect.client
!isEmpty(airDCPPClientSettings.directConnect) ||
!isUndefined(airDCPPClientSettings.directConnect)
? airDCPPClientSettings.directConnect.client
: {};
return (
<>
@@ -112,17 +137,19 @@ export const AirDCPPSettingsForm = (): ReactElement => {
{!isEmpty(initFormData) ? "Update" : "Save"}
</button>
</p>
{!isUndefined(airdcppClientSettings) ? (
<p className="control">
<button className="button is-danger">Delete</button>
</p>
) : null}
</div>
</form>
)}
/>
{!isEmpty(airdcppClientSettings) ? (
<AirDCPPSettingsConfirmation settings={airdcppClientSettings} />
{!isEmpty(airDCPPClientSettings) ? (
<p className="control">
<button className="button is-danger" onClick={removeSettings}>
Delete
</button>
</p>
) : null}
{!isEmpty(airDCPPClientSettings) ? (
<AirDCPPSettingsConfirmation settings={airDCPPClientSettings} />
) : null}
</>
);

View File

@@ -1,5 +1,5 @@
import React, { ReactElement } from "react";
import { useSelector } from "react-redux";
import React, { ReactElement, useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { hot } from "react-hot-loader";
import Dashboard from "./Dashboard";
@@ -14,6 +14,10 @@ import { Switch, Route } from "react-router";
import Navbar from "./Navbar";
import "../assets/scss/App.scss";
import Notifications from "react-notification-system-redux";
import { getSettings } from "../actions/settings.actions";
import { AirDCPPSocketContext } from "../context/AirDCPPSocket";
import { isEmpty, isUndefined } from "lodash";
import AirDCPPSocket from "../services/DcppSearchService";
//Optional styling
const style = {
@@ -58,39 +62,48 @@ const style = {
},
},
};
export const App = (): ReactElement => {
const notifications = useSelector((state: RootState) => state.notifications);
const [ADCPPSocket, setADCPPSocket] = useState({});
return (
<div>
<Navbar />
<Notifications
notifications={notifications}
style={style}
newOnTop={true}
allowHTML={true}
/>
<Switch>
<Route exact path="/">
<Dashboard />
</Route>
<Route path="/import">
<Import path={"./comics"} />
</Route>
<Route path="/library">
<Library />
</Route>
<Route path="/library-grid">
<LibraryGrid />
</Route>
<Route path="/search">
<Search />
</Route>
<Route path={"/comic/details/:comicObjectId"} component={ComicDetail} />
<Route path="/settings">
<Settings />
</Route>
</Switch>
</div>
<AirDCPPSocketContext.Provider value={{ ADCPPSocket, setADCPPSocket }}>
<div>
<Navbar />
<Notifications
notifications={notifications}
style={style}
newOnTop={true}
allowHTML={true}
/>
<Switch>
<Route exact path="/">
<Dashboard />
</Route>
<Route path="/import">
<Import path={"./comics"} />
</Route>
<Route path="/library">
<Library />
</Route>
<Route path="/library-grid">
<LibraryGrid />
</Route>
<Route path="/search">
<Search />
</Route>
<Route
path={"/comic/details/:comicObjectId"}
component={ComicDetail}
/>
<Route path="/settings">
<Settings />
</Route>
</Switch>
</div>
</AirDCPPSocketContext.Provider>
);
};

View File

@@ -5,7 +5,6 @@ import {
} from "../actions/airdcpp.actions";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "threetwo-ui-typings";
import { SocketContext } from "../context/AirDCPPSocket";
import { isNil, map } from "lodash";
import prettyBytes from "pretty-bytes";
import dayjs from "dayjs";
@@ -27,6 +26,7 @@ export const DownloadsPanel = (
});
console.log("BANDYA", bundles);
const ADCPPSocket = useContext(SocketContext);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getBundlesForComic(props.comicObjectId, ADCPPSocket));

View File

@@ -67,3 +67,4 @@ export const LS_COVER_EXTRACTED = "LS_COVER_EXTRACTED";
export const SETTINGS_CALL_IN_PROGRESS = "SETTINGS_CALL_IN_PROGRESS";
export const SETTINGS_OBJECT_FETCHED = "SETTINGS_OBJECT_FETCHED";
export const SETTINGS_CALL_FAILED = "SETTINGS_CALL_FAILED";
export const SETTINGS_OBJECT_DELETED = "SETTINGS_OBJECT_DELETED";

View File

@@ -1,15 +1,5 @@
import React from "react";
import AirDCPPSocket from "../services/DcppSearchService";
import axios from "axios";
import { SETTINGS_SERVICE_BASE_URI } from "../constants/endpoints";
const socketInitConfiguration = await axios({
url: `${SETTINGS_SERVICE_BASE_URI}/getSettings`,
method: "POST",
});
const AirDCPPSocketContext = React.createContext(null);
export const airDCPPSocket = new AirDCPPSocket({
hostname: `${socketInitConfiguration.data.directConnect.client.hostname}`,
});
export const SocketContext = React.createContext(airDCPPSocket);
export { AirDCPPSocketContext };

View File

@@ -3,18 +3,17 @@ import { render } from "react-dom";
import { Provider } from "react-redux";
import { ConnectedRouter } from "connected-react-router";
import configureStore, { history } from "./store/index";
import { SocketContext, airDCPPSocket } from "./context/AirDCPPSocket";
import App from "./components/App";
const store = configureStore({});
const rootEl = document.getElementById("root");
render(
<SocketContext.Provider value={airDCPPSocket}>
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>
</SocketContext.Provider>,
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
rootEl,
);

View File

@@ -1,6 +1,7 @@
import {
SETTINGS_CALL_FAILED,
SETTINGS_OBJECT_FETCHED,
SETTINGS_OBJECT_DELETED,
SETTINGS_CALL_IN_PROGRESS,
} from "../constants/action-types";
const initialState = {
@@ -23,6 +24,13 @@ function settingsReducer(state = initialState, action) {
inProgress: false,
};
case SETTINGS_OBJECT_DELETED:
return {
...state,
data: action.data,
inProgress: false,
};
default:
return { ...state };
}