⚙️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 = export const search =
(data: SearchData, ADCPPSocket: any) => async (dispatch) => { (data: SearchData, ADCPPSocket: any, credentials: any) =>
async (dispatch) => {
try { try {
console.log(credentials);
if (!ADCPPSocket.isConnected()) { if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect("admin", "password", true); await ADCPPSocket.connect(
credentials.username,
credentials.password,
true,
);
} }
const instance: SearchInstance = await ADCPPSocket.post("search"); const instance: SearchInstance = await ADCPPSocket.post("search");
dispatch({ dispatch({

View File

@@ -1,5 +1,8 @@
import axios from "axios"; 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"; import { SETTINGS_SERVICE_BASE_URI } from "../constants/endpoints";
export const saveSettings = 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 { import {
search, search,
downloadAirDCPPItem, downloadAirDCPPItem,
getBundlesForComic, getBundlesForComic,
} from "../actions/airdcpp.actions"; } from "../actions/airdcpp.actions";
import { SocketContext } from "../context/AirDCPPSocket";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { RootState, SearchInstance } from "threetwo-ui-typings"; import { RootState, SearchInstance } from "threetwo-ui-typings";
import ellipsize from "ellipsize"; import ellipsize from "ellipsize";
import { isEmpty, isNil, isUndefined, map } from "lodash"; 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 { interface IAcquisitionPanelProps {
comicBookMetadata: any; comicBookMetadata: any;
} }
@@ -21,7 +23,6 @@ export const AcquisitionPanel = (
const sanitizedVolumeName = volumeName.replace(/[^a-zA-Z0-9 ]/g, ""); const sanitizedVolumeName = volumeName.replace(/[^a-zA-Z0-9 ]/g, "");
const issueName = props.comicBookMetadata.sourcedMetadata.comicvine.name; const issueName = props.comicBookMetadata.sourcedMetadata.comicvine.name;
// local state
// Selectors for picking state // Selectors for picking state
const airDCPPSearchResults = useSelector((state: RootState) => { const airDCPPSearchResults = useSelector((state: RootState) => {
return state.airdcpp.searchResults; return state.airdcpp.searchResults;
@@ -35,18 +36,31 @@ export const AcquisitionPanel = (
const searchInstance: SearchInstance = useSelector( const searchInstance: SearchInstance = useSelector(
(state: RootState) => state.airdcpp.searchInstance, (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 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( const getDCPPSearchResults = useCallback(
(searchQuery) => { (searchQuery) => {
console.log(ADCPPSocket); dispatch(
dispatch(search(searchQuery, ADCPPSocket)); search(searchQuery, ADCPPSocket, {
username: `${userSettings.directConnect.client.username}`,
password: `${userSettings.directConnect.client.password}`,
}),
);
}, },
[dispatch, ADCPPSocket], [dispatch, ADCPPSocket],
); );
@@ -80,8 +94,9 @@ export const AcquisitionPanel = (
return ( return (
<> <>
{JSON.stringify(ADCPPSocket)}
<div className="comic-detail columns"> <div className="comic-detail columns">
{!isEmpty(ADCPPSocket) && !isUndefined(ADCPPSocket) ? ( {!isEmpty(ADCPPSocket) ? (
<div className="column is-one-fifth"> <div className="column is-one-fifth">
<button <button
className={ 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 { Form, Field } from "react-final-form";
import { useDispatch, useSelector } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { saveSettings, getSettings } from "../actions/settings.actions"; import {
getSettings,
saveSettings,
deleteSettings,
} from "../actions/settings.actions";
import { AirDCPPSettingsConfirmation } from "./AirDCPPSettings/AirDCPPSettingsConfirmation"; import { AirDCPPSettingsConfirmation } from "./AirDCPPSettings/AirDCPPSettingsConfirmation";
import axios from "axios"; import axios from "axios";
import { AirDCPPSocketContext } from "../context/AirDCPPSocket";
import AirDCPPSocket from "../services/DcppSearchService";
import { isUndefined, isEmpty } from "lodash"; import { isUndefined, isEmpty } from "lodash";
export const AirDCPPSettingsForm = (): ReactElement => { export const AirDCPPSettingsForm = (): ReactElement => {
const airdcppClientSettings = useSelector( const airDCPPClientSettings = useSelector(
(state: RootState) => state.settings.data, (state: RootState) => state.settings.data,
); );
const { ADCPPSocket, setADCPPSocket } = useContext(AirDCPPSocketContext);
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
dispatch(getSettings()); dispatch(getSettings());
}, [dispatch]); }, []);
const onSubmit = async (values) => { const onSubmit = async (values) => {
try { try {
const fqdn = values.protocol + values.hostname; const fqdn = values.protocol + values.hostname;
const airdcppResponse = await axios({ const airDCPPResponse = await axios({
url: `${fqdn}/api/v1/sessions/authorize`, url: `${fqdn}/api/v1/sessions/authorize`,
method: "POST", method: "POST",
data: { data: {
@@ -27,18 +33,37 @@ export const AirDCPPSettingsForm = (): ReactElement => {
password: values.password, password: values.password,
}, },
}); });
if (airdcppResponse.status === 200) { if (airDCPPResponse.status === 200) {
dispatch(saveSettings(values, airdcppResponse.data)); 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) { } catch (error) {
console.log(error); console.log(error);
} }
}; };
const removeSettings = useCallback(async () => {
dispatch(deleteSettings());
setADCPPSocket({});
}, []);
const validate = async () => {}; const validate = async () => {};
const initFormData = const initFormData =
!isEmpty(airdcppClientSettings.directConnect) || !isEmpty(airDCPPClientSettings.directConnect) ||
!isUndefined(airdcppClientSettings.directConnect) !isUndefined(airDCPPClientSettings.directConnect)
? airdcppClientSettings.directConnect.client ? airDCPPClientSettings.directConnect.client
: {}; : {};
return ( return (
<> <>
@@ -112,17 +137,19 @@ export const AirDCPPSettingsForm = (): ReactElement => {
{!isEmpty(initFormData) ? "Update" : "Save"} {!isEmpty(initFormData) ? "Update" : "Save"}
</button> </button>
</p> </p>
{!isUndefined(airdcppClientSettings) ? (
<p className="control">
<button className="button is-danger">Delete</button>
</p>
) : null}
</div> </div>
</form> </form>
)} )}
/> />
{!isEmpty(airdcppClientSettings) ? ( {!isEmpty(airDCPPClientSettings) ? (
<AirDCPPSettingsConfirmation settings={airdcppClientSettings} /> <p className="control">
<button className="button is-danger" onClick={removeSettings}>
Delete
</button>
</p>
) : null}
{!isEmpty(airDCPPClientSettings) ? (
<AirDCPPSettingsConfirmation settings={airDCPPClientSettings} />
) : null} ) : null}
</> </>
); );

View File

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

View File

@@ -5,7 +5,6 @@ import {
} from "../actions/airdcpp.actions"; } from "../actions/airdcpp.actions";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { RootState } from "threetwo-ui-typings"; import { RootState } from "threetwo-ui-typings";
import { SocketContext } from "../context/AirDCPPSocket";
import { isNil, map } from "lodash"; import { isNil, map } from "lodash";
import prettyBytes from "pretty-bytes"; import prettyBytes from "pretty-bytes";
import dayjs from "dayjs"; import dayjs from "dayjs";
@@ -27,6 +26,7 @@ export const DownloadsPanel = (
}); });
console.log("BANDYA", bundles); console.log("BANDYA", bundles);
const ADCPPSocket = useContext(SocketContext); const ADCPPSocket = useContext(SocketContext);
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
dispatch(getBundlesForComic(props.comicObjectId, ADCPPSocket)); 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_CALL_IN_PROGRESS = "SETTINGS_CALL_IN_PROGRESS";
export const SETTINGS_OBJECT_FETCHED = "SETTINGS_OBJECT_FETCHED"; export const SETTINGS_OBJECT_FETCHED = "SETTINGS_OBJECT_FETCHED";
export const SETTINGS_CALL_FAILED = "SETTINGS_CALL_FAILED"; 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 React from "react";
import AirDCPPSocket from "../services/DcppSearchService";
import axios from "axios";
import { SETTINGS_SERVICE_BASE_URI } from "../constants/endpoints";
const socketInitConfiguration = await axios({ const AirDCPPSocketContext = React.createContext(null);
url: `${SETTINGS_SERVICE_BASE_URI}/getSettings`,
method: "POST",
});
export const airDCPPSocket = new AirDCPPSocket({ export { AirDCPPSocketContext };
hostname: `${socketInitConfiguration.data.directConnect.client.hostname}`,
});
export const SocketContext = React.createContext(airDCPPSocket);

View File

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

View File

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