Compare commits
9 Commits
elasticsea
...
service-st
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d8dd3ca3e | |||
| 3102b489c8 | |||
| 355812513b | |||
| d026cf1a71 | |||
| b8f0b5426e | |||
| 25b706c4f2 | |||
| 0e72d64009 | |||
| c05fb2f341 | |||
| df1fbc7dd3 |
@@ -44,10 +44,6 @@
|
||||
"babel-preset-minify": "^0.5.2",
|
||||
"better-docs": "^2.7.2",
|
||||
"comlink-loader": "^2.0.0",
|
||||
"compromise": "^13.11.3",
|
||||
"compromise-dates": "^2.2.1",
|
||||
"compromise-numbers": "^1.4.0",
|
||||
"compromise-sentences": "^0.3.0",
|
||||
"date-fns": "^2.28.0",
|
||||
"dayjs": "^1.10.6",
|
||||
"ellipsize": "^0.1.0",
|
||||
@@ -138,7 +134,6 @@
|
||||
"bulma": "^0.9.3",
|
||||
"clean-webpack-plugin": "^1.0.0",
|
||||
"comlink": "^4.3.0",
|
||||
"compromise-strict": "^0.0.2",
|
||||
"concurrently": "^4.0.0",
|
||||
"copy-webpack-plugin": "^9.0.1",
|
||||
"css-loader": "^5.1.2",
|
||||
@@ -182,4 +177,4 @@
|
||||
"resolutions": {
|
||||
"@storybook/react/webpack": "^5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
PriorityEnum,
|
||||
SearchResponse,
|
||||
} from "threetwo-ui-typings";
|
||||
import { LIBRARY_SERVICE_BASE_URI, SEARCH_SERVICE_BASE_URI } from "../constants/endpoints";
|
||||
import {
|
||||
LIBRARY_SERVICE_BASE_URI,
|
||||
SEARCH_SERVICE_BASE_URI,
|
||||
} from "../constants/endpoints";
|
||||
import {
|
||||
AIRDCPP_SEARCH_RESULTS_ADDED,
|
||||
AIRDCPP_SEARCH_RESULTS_UPDATED,
|
||||
@@ -18,6 +21,8 @@ import {
|
||||
IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
|
||||
AIRDCPP_TRANSFERS_FETCHED,
|
||||
LIBRARY_ISSUE_BUNDLES,
|
||||
AIRDCPP_SOCKET_CONNECTED,
|
||||
AIRDCPP_SOCKET_DISCONNECTED,
|
||||
} from "../constants/action-types";
|
||||
import { isNil } from "lodash";
|
||||
import axios from "axios";
|
||||
@@ -32,190 +37,199 @@ function sleep(ms: number): Promise<NodeJS.Timeout> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export const toggleAirDCPPSocketConnectionStatus =
|
||||
(status: String, payload?: any) => async (dispatch) => {
|
||||
switch (status) {
|
||||
case "connected":
|
||||
dispatch({
|
||||
type: AIRDCPP_SOCKET_CONNECTED,
|
||||
data: payload,
|
||||
});
|
||||
break;
|
||||
|
||||
case "disconnected":
|
||||
dispatch({
|
||||
type: AIRDCPP_SOCKET_DISCONNECTED,
|
||||
data: payload,
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("Can't set AirDC++ socket status.");
|
||||
break;
|
||||
}
|
||||
};
|
||||
export const search =
|
||||
(data: SearchData, ADCPPSocket: any, credentials: any) =>
|
||||
async (dispatch) => {
|
||||
try {
|
||||
if (!ADCPPSocket.isConnected()) {
|
||||
await ADCPPSocket.connect(
|
||||
credentials.username,
|
||||
credentials.password,
|
||||
true,
|
||||
);
|
||||
}
|
||||
const instance: SearchInstance = await ADCPPSocket.post("search");
|
||||
dispatch({
|
||||
type: AIRDCPP_SEARCH_IN_PROGRESS,
|
||||
});
|
||||
|
||||
// We want to get notified about every new result in order to make the user experience better
|
||||
await ADCPPSocket.addListener(
|
||||
`search`,
|
||||
"search_result_added",
|
||||
async (groupedResult) => {
|
||||
// ...add the received result in the UI
|
||||
// (it's probably a good idea to have some kind of throttling for the UI updates as there can be thousands of results)
|
||||
|
||||
dispatch({
|
||||
type: AIRDCPP_SEARCH_RESULTS_ADDED,
|
||||
groupedResult,
|
||||
});
|
||||
},
|
||||
instance.id,
|
||||
);
|
||||
|
||||
// We also want to update the existing items in our list when new hits arrive for the previously listed files/directories
|
||||
await ADCPPSocket.addListener(
|
||||
`search`,
|
||||
"search_result_updated",
|
||||
async (groupedResult) => {
|
||||
// ...update properties of the existing result in the UI
|
||||
dispatch({
|
||||
type: AIRDCPP_SEARCH_RESULTS_UPDATED,
|
||||
groupedResult,
|
||||
});
|
||||
},
|
||||
instance.id,
|
||||
);
|
||||
|
||||
// We need to show something to the user in case the search won't yield any results so that he won't be waiting forever)
|
||||
// Wait for 5 seconds for any results to arrive after the searches were sent to the hubs
|
||||
await ADCPPSocket.addListener(
|
||||
`search`,
|
||||
"search_hub_searches_sent",
|
||||
async (searchInfo) => {
|
||||
await sleep(5000);
|
||||
|
||||
// Check the number of received results (in real use cases we should know that even without calling the API)
|
||||
const currentInstance = await ADCPPSocket.get(
|
||||
`search/${instance.id}`,
|
||||
);
|
||||
if (currentInstance.result_count === 0) {
|
||||
// ...nothing was received, show an informative message to the user
|
||||
console.log("No more search results.");
|
||||
}
|
||||
|
||||
// The search can now be considered to be "complete"
|
||||
// If there's an "in progress" indicator in the UI, that could also be disabled here
|
||||
dispatch({
|
||||
type: AIRDCPP_HUB_SEARCHES_SENT,
|
||||
searchInfo,
|
||||
instance,
|
||||
});
|
||||
},
|
||||
instance.id,
|
||||
);
|
||||
// Finally, perform the actual search
|
||||
await ADCPPSocket.post(`search/${instance.id}/hub_search`, data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
async (dispatch) => {
|
||||
try {
|
||||
if (!ADCPPSocket.isConnected()) {
|
||||
await ADCPPSocket();
|
||||
}
|
||||
};
|
||||
const instance: SearchInstance = await ADCPPSocket.post("search");
|
||||
dispatch({
|
||||
type: AIRDCPP_SEARCH_IN_PROGRESS,
|
||||
});
|
||||
|
||||
// We want to get notified about every new result in order to make the user experience better
|
||||
await ADCPPSocket.addListener(
|
||||
`search`,
|
||||
"search_result_added",
|
||||
async (groupedResult) => {
|
||||
// ...add the received result in the UI
|
||||
// (it's probably a good idea to have some kind of throttling for the UI updates as there can be thousands of results)
|
||||
|
||||
dispatch({
|
||||
type: AIRDCPP_SEARCH_RESULTS_ADDED,
|
||||
groupedResult,
|
||||
});
|
||||
},
|
||||
instance.id,
|
||||
);
|
||||
|
||||
// We also want to update the existing items in our list when new hits arrive for the previously listed files/directories
|
||||
await ADCPPSocket.addListener(
|
||||
`search`,
|
||||
"search_result_updated",
|
||||
async (groupedResult) => {
|
||||
// ...update properties of the existing result in the UI
|
||||
dispatch({
|
||||
type: AIRDCPP_SEARCH_RESULTS_UPDATED,
|
||||
groupedResult,
|
||||
});
|
||||
},
|
||||
instance.id,
|
||||
);
|
||||
|
||||
// We need to show something to the user in case the search won't yield any results so that he won't be waiting forever)
|
||||
// Wait for 5 seconds for any results to arrive after the searches were sent to the hubs
|
||||
await ADCPPSocket.addListener(
|
||||
`search`,
|
||||
"search_hub_searches_sent",
|
||||
async (searchInfo) => {
|
||||
await sleep(5000);
|
||||
|
||||
// Check the number of received results (in real use cases we should know that even without calling the API)
|
||||
const currentInstance = await ADCPPSocket.get(
|
||||
`search/${instance.id}`,
|
||||
);
|
||||
if (currentInstance.result_count === 0) {
|
||||
// ...nothing was received, show an informative message to the user
|
||||
console.log("No more search results.");
|
||||
}
|
||||
|
||||
// The search can now be considered to be "complete"
|
||||
// If there's an "in progress" indicator in the UI, that could also be disabled here
|
||||
dispatch({
|
||||
type: AIRDCPP_HUB_SEARCHES_SENT,
|
||||
searchInfo,
|
||||
instance,
|
||||
});
|
||||
},
|
||||
instance.id,
|
||||
);
|
||||
// Finally, perform the actual search
|
||||
await ADCPPSocket.post(`search/${instance.id}/hub_search`, data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadAirDCPPItem =
|
||||
(searchInstanceId: Number,
|
||||
(
|
||||
searchInstanceId: Number,
|
||||
resultId: String,
|
||||
comicObjectId: String,
|
||||
name: String, size: Number, type: any,
|
||||
name: String,
|
||||
size: Number,
|
||||
type: any,
|
||||
ADCPPSocket: any,
|
||||
credentials: any,
|
||||
): void =>
|
||||
async (dispatch) => {
|
||||
try {
|
||||
if (!ADCPPSocket.isConnected()) {
|
||||
await ADCPPSocket.connect(
|
||||
`${credentials.username}`,
|
||||
`${credentials.password}`,
|
||||
true,
|
||||
);
|
||||
}
|
||||
let bundleDBImportResult = {};
|
||||
const downloadResult = await ADCPPSocket.post(
|
||||
`search/${searchInstanceId}/results/${resultId}/download`,
|
||||
);
|
||||
|
||||
if (!isNil(downloadResult)) {
|
||||
bundleDBImportResult = await axios({
|
||||
method: "POST",
|
||||
url: `${LIBRARY_SERVICE_BASE_URI}/applyAirDCPPDownloadMetadata`,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
data: {
|
||||
bundleId: downloadResult.bundle_info.id,
|
||||
comicObjectId,
|
||||
name,
|
||||
size,
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: AIRDCPP_RESULT_DOWNLOAD_INITIATED,
|
||||
downloadResult,
|
||||
bundleDBImportResult,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
|
||||
comicBookDetail: bundleDBImportResult.data,
|
||||
IMS_inProgress: false,
|
||||
});
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
async (dispatch) => {
|
||||
try {
|
||||
if (!ADCPPSocket.isConnected()) {
|
||||
await ADCPPSocket.connect();
|
||||
}
|
||||
};
|
||||
let bundleDBImportResult = {};
|
||||
const downloadResult = await ADCPPSocket.post(
|
||||
`search/${searchInstanceId}/results/${resultId}/download`,
|
||||
);
|
||||
|
||||
export const getBundlesForComic =
|
||||
(comicObjectId: string, ADCPPSocket: any, credentials: any) =>
|
||||
async (dispatch) => {
|
||||
try {
|
||||
if (!ADCPPSocket.isConnected()) {
|
||||
await ADCPPSocket.connect(
|
||||
`${credentials.username}`,
|
||||
`${credentials.password}`,
|
||||
true,
|
||||
);
|
||||
}
|
||||
const comicObject = await axios({
|
||||
if (!isNil(downloadResult)) {
|
||||
bundleDBImportResult = await axios({
|
||||
method: "POST",
|
||||
url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`,
|
||||
url: `${LIBRARY_SERVICE_BASE_URI}/applyAirDCPPDownloadMetadata`,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
data: {
|
||||
id: `${comicObjectId}`,
|
||||
bundleId: downloadResult.bundle_info.id,
|
||||
comicObjectId,
|
||||
name,
|
||||
size,
|
||||
type,
|
||||
},
|
||||
});
|
||||
// get only the bundles applicable for the comic
|
||||
if (comicObject.data.acquisition.directconnect) {
|
||||
const filteredBundles = comicObject.data.acquisition.directconnect.downloads.map(
|
||||
|
||||
dispatch({
|
||||
type: AIRDCPP_RESULT_DOWNLOAD_INITIATED,
|
||||
downloadResult,
|
||||
bundleDBImportResult,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
|
||||
comicBookDetail: bundleDBImportResult.data,
|
||||
IMS_inProgress: false,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getBundlesForComic =
|
||||
(comicObjectId: string, ADCPPSocket: any, credentials: any) =>
|
||||
async (dispatch) => {
|
||||
try {
|
||||
if (!ADCPPSocket.isConnected()) {
|
||||
await ADCPPSocket.connect();
|
||||
}
|
||||
const comicObject = await axios({
|
||||
method: "POST",
|
||||
url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
data: {
|
||||
id: `${comicObjectId}`,
|
||||
},
|
||||
});
|
||||
// get only the bundles applicable for the comic
|
||||
if (comicObject.data.acquisition.directconnect) {
|
||||
const filteredBundles =
|
||||
comicObject.data.acquisition.directconnect.downloads.map(
|
||||
async ({ bundleId }) => {
|
||||
return await ADCPPSocket.get(`queue/bundles/${bundleId}`);
|
||||
},
|
||||
);
|
||||
dispatch({
|
||||
type: AIRDCPP_BUNDLES_FETCHED,
|
||||
bundles: await Promise.all(filteredBundles),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
dispatch({
|
||||
type: AIRDCPP_BUNDLES_FETCHED,
|
||||
bundles: await Promise.all(filteredBundles),
|
||||
});
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getTransfers =
|
||||
(ADCPPSocket: any, credentials: any) => async (dispatch) => {
|
||||
try {
|
||||
if (!ADCPPSocket.isConnected()) {
|
||||
await ADCPPSocket.connect(
|
||||
`${credentials.username}`,
|
||||
`${credentials.password}`,
|
||||
true,
|
||||
);
|
||||
await ADCPPSocket.connect();
|
||||
}
|
||||
const bundles = await ADCPPSocket.get("queue/bundles/1/85", {});
|
||||
if (!isNil(bundles)) {
|
||||
@@ -234,7 +248,6 @@ export const getTransfers =
|
||||
type: LIBRARY_ISSUE_BUNDLES,
|
||||
issue_bundles,
|
||||
});
|
||||
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
|
||||
@@ -62,6 +62,10 @@ pre {
|
||||
margin-left: -300px;
|
||||
min-width: 500px;
|
||||
}
|
||||
.airdcpp-status {
|
||||
min-width: 300px;
|
||||
line-height: 1.7rem;
|
||||
}
|
||||
body {
|
||||
background: #454a59;
|
||||
}
|
||||
|
||||
@@ -137,4 +137,4 @@ export const App = (): ReactElement => {
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
||||
@@ -8,10 +8,9 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { searchIssue } from "../../actions/fileops.actions";
|
||||
import ellipsize from "ellipsize";
|
||||
|
||||
|
||||
/**
|
||||
* Component that tabulates the contents of the user's ThreeTwo Library.
|
||||
*
|
||||
*
|
||||
* @component
|
||||
* @example
|
||||
* <Library />
|
||||
@@ -20,9 +19,10 @@ export const Library = (): ReactElement => {
|
||||
const searchResults = useSelector(
|
||||
(state: RootState) => state.fileOps.libraryComics,
|
||||
);
|
||||
const searchError = useSelector(
|
||||
(state: RootState) => state.fileOps.librarySearchError,
|
||||
);
|
||||
const searchError = useSelector((state: RootState) => {
|
||||
console.log(state);
|
||||
return state.fileOps.librarySearchError;
|
||||
});
|
||||
const dispatch = useDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
@@ -36,7 +36,7 @@ export const Library = (): ReactElement => {
|
||||
from: 0,
|
||||
},
|
||||
type: "all",
|
||||
trigger: "libraryPage"
|
||||
trigger: "libraryPage",
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -89,63 +89,67 @@ export const Library = (): ReactElement => {
|
||||
const WantedStatus = ({ value }) => {
|
||||
return !value ? <span className="tag is-info is-light">Wanted</span> : null;
|
||||
};
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
header: "Comic Metadata",
|
||||
footer: 1,
|
||||
columns: [
|
||||
{
|
||||
header: "File Details",
|
||||
id: "fileDetails",
|
||||
minWidth: 400,
|
||||
accessorKey: "_source",
|
||||
cell: info => {
|
||||
return <MetadataPanel data={info.getValue()} />;
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
header: "Comic Metadata",
|
||||
footer: 1,
|
||||
columns: [
|
||||
{
|
||||
header: "File Details",
|
||||
id: "fileDetails",
|
||||
minWidth: 400,
|
||||
accessorKey: "_source",
|
||||
cell: (info) => {
|
||||
return <MetadataPanel data={info.getValue()} />;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "ComicInfo.xml",
|
||||
accessorKey: "_source.sourcedMetadata.comicInfo",
|
||||
align: "center",
|
||||
minWidth: 250,
|
||||
cell: info =>
|
||||
!isEmpty(info.getValue()) ? (
|
||||
<ComicInfoXML data={info.getValue()} />
|
||||
) : (
|
||||
<span className="tag mt-5">No ComicInfo.xml</span>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
header: "Additional Metadata",
|
||||
columns: [
|
||||
{
|
||||
header: "Publisher",
|
||||
accessorKey:
|
||||
"_source.sourcedMetadata.comicvine.volumeInformation",
|
||||
cell: info => {
|
||||
return (
|
||||
!isNil(info.getValue()) && (
|
||||
<h6 className="is-size-7 has-text-weight-bold">
|
||||
{info.getValue().publisher.name}
|
||||
</h6>
|
||||
)
|
||||
);
|
||||
{
|
||||
header: "ComicInfo.xml",
|
||||
accessorKey: "_source.sourcedMetadata.comicInfo",
|
||||
align: "center",
|
||||
minWidth: 250,
|
||||
cell: (info) =>
|
||||
!isEmpty(info.getValue()) ? (
|
||||
<ComicInfoXML data={info.getValue()} />
|
||||
) : (
|
||||
<span className="tag mt-5">No ComicInfo.xml</span>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Something",
|
||||
accessorKey: "_source.acquisition.source.wanted",
|
||||
cell: info => {
|
||||
!isUndefined(info.getValue()) ?
|
||||
<WantedStatus value={info.getValue().toString()} /> : "Nothing";
|
||||
],
|
||||
},
|
||||
{
|
||||
header: "Additional Metadata",
|
||||
columns: [
|
||||
{
|
||||
header: "Publisher",
|
||||
accessorKey: "_source.sourcedMetadata.comicvine.volumeInformation",
|
||||
cell: (info) => {
|
||||
return (
|
||||
!isNil(info.getValue()) && (
|
||||
<h6 className="is-size-7 has-text-weight-bold">
|
||||
{info.getValue().publisher.name}
|
||||
</h6>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
], []);
|
||||
|
||||
{
|
||||
header: "Something",
|
||||
accessorKey: "_source.acquisition.source.wanted",
|
||||
cell: (info) => {
|
||||
!isUndefined(info.getValue()) ? (
|
||||
<WantedStatus value={info.getValue().toString()} />
|
||||
) : (
|
||||
"Nothing"
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
/**
|
||||
* Pagination control that fetches the next x (pageSize) items
|
||||
@@ -153,7 +157,7 @@ export const Library = (): ReactElement => {
|
||||
* @param {number} pageIndex
|
||||
* @param {number} pageSize
|
||||
* @returns void
|
||||
*
|
||||
*
|
||||
**/
|
||||
const nextPage = useCallback((pageIndex: number, pageSize: number) => {
|
||||
dispatch(
|
||||
@@ -173,7 +177,6 @@ export const Library = (): ReactElement => {
|
||||
);
|
||||
}, []);
|
||||
|
||||
|
||||
/**
|
||||
* Pagination control that fetches the previous x (pageSize) items
|
||||
* based on the y (pageIndex) offset from the ThreeTwo Elasticsearch index
|
||||
@@ -199,7 +202,7 @@ export const Library = (): ReactElement => {
|
||||
from,
|
||||
},
|
||||
type: "all",
|
||||
trigger: "libraryPage"
|
||||
trigger: "libraryPage",
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -229,25 +232,27 @@ export const Library = (): ReactElement => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
): <div className="columns">
|
||||
<div className="column is-two-thirds">
|
||||
<article className="message is-link">
|
||||
<div className="message-body">
|
||||
No comics were found in the library, Elasticsearch reports no
|
||||
indices. Try importing a few comics into the library and come
|
||||
back.
|
||||
</div>
|
||||
</article>
|
||||
<pre>
|
||||
{!isUndefined(searchError.data) &&
|
||||
JSON.stringify(
|
||||
searchError.data.meta.body.error.root_cause,
|
||||
null,
|
||||
4,
|
||||
)}
|
||||
</pre>
|
||||
) : (
|
||||
<div className="columns">
|
||||
<div className="column is-two-thirds">
|
||||
<article className="message is-link">
|
||||
<div className="message-body">
|
||||
No comics were found in the library, Elasticsearch reports no
|
||||
indices. Try importing a few comics into the library and come
|
||||
back.
|
||||
</div>
|
||||
</article>
|
||||
<pre>
|
||||
{!isUndefined(searchError.data) &&
|
||||
JSON.stringify(
|
||||
searchError.data.meta.body.error.root_cause,
|
||||
null,
|
||||
4,
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div> }
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -3,15 +3,23 @@ import { SearchBar } from "./GlobalSearchBar/SearchBar";
|
||||
import { DownloadProgressTick } from "./ComicDetail/DownloadProgressTick";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { isUndefined, isEmpty } from "lodash";
|
||||
import { AirDCPPSocketContext } from "../context/AirDCPPSocket";
|
||||
import { isUndefined } from "lodash";
|
||||
import { format, fromUnixTime } from "date-fns";
|
||||
|
||||
const Navbar: React.FunctionComponent = (props) => {
|
||||
const downloadProgressTick = useSelector(
|
||||
(state: RootState) => state.airdcpp.downloadProgressData,
|
||||
);
|
||||
const airDCPPConfiguration = useContext(AirDCPPSocketContext);
|
||||
console.log(airDCPPConfiguration)
|
||||
|
||||
const airDCPPSocketConnectionStatus = useSelector(
|
||||
(state: RootState) => state.airdcpp.isAirDCPPSocketConnected,
|
||||
);
|
||||
const airDCPPSessionInfo = useSelector(
|
||||
(state: RootState) => state.airdcpp.airDCPPSessionInfo,
|
||||
);
|
||||
const socketDisconnectionReason = useSelector(
|
||||
(state: RootState) => state.airdcpp.socketDisconnectionReason,
|
||||
);
|
||||
return (
|
||||
<nav className="navbar is-fixed-top">
|
||||
<div className="navbar-brand">
|
||||
@@ -74,12 +82,10 @@ console.log(airDCPPConfiguration)
|
||||
<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>
|
||||
)}
|
||||
{downloadProgressTick && <div className="pulsating-circle"></div>}
|
||||
</a>
|
||||
{!isUndefined(downloadProgressTick) ? (
|
||||
<div className="navbar-dropdown download-progress-meter">
|
||||
<div className="navbar-dropdown is-right">
|
||||
<a className="navbar-item">
|
||||
<DownloadProgressTick data={downloadProgressTick} />
|
||||
</a> </div>
|
||||
@@ -87,18 +93,55 @@ console.log(airDCPPConfiguration)
|
||||
</div>
|
||||
{/* AirDC++ socket connection status */}
|
||||
<div className="navbar-item has-dropdown is-hoverable">
|
||||
<a className="navbar-link is-arrowless has-text-success">
|
||||
{!isEmpty(airDCPPConfiguration.airDCPPState.socketConnectionInformation) ? (
|
||||
<i className="fa-solid fa-bolt"></i>) : null}
|
||||
</a>
|
||||
<div className="navbar-dropdown download-progress-meter">
|
||||
<a className="navbar-item">
|
||||
<pre>{JSON.stringify(airDCPPConfiguration.airDCPPState.socketConnectionInformation, null, 2)}</pre>
|
||||
</a>
|
||||
</div>
|
||||
{airDCPPSocketConnectionStatus ? (
|
||||
<>
|
||||
<a className="navbar-link is-arrowless has-text-success">
|
||||
<i className="fa-solid fa-bolt"></i>
|
||||
</a>
|
||||
<div className="navbar-dropdown pt-4 pr-2 pl-2 is-right airdcpp-status">
|
||||
{/* 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">
|
||||
<pre>
|
||||
{JSON.stringify(socketDisconnectionReason, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className="navbar-item has-dropdown is-hoverable is-mega">
|
||||
<div className="navbar-link flex">Blog</div>
|
||||
<div id="blogDropdown" className="navbar-dropdown">
|
||||
|
||||
@@ -115,6 +115,8 @@ export const AIRDCPP_FILE_DOWNLOAD_COMPLETED =
|
||||
export const LS_SINGLE_IMPORT = "LS_SINGLE_IMPORT";
|
||||
export const AIRDCPP_BUNDLES_FETCHED = "AIRDCPP_BUNDLES_FETCHED";
|
||||
export const AIRDCPP_DOWNLOAD_PROGRESS_TICK = "AIRDCPP_DOWNLOAD_PROGRESS_TICK";
|
||||
export const AIRDCPP_SOCKET_CONNECTED = "AIRDCPP_SOCKET_CONNECTED";
|
||||
export const AIRDCPP_SOCKET_DISCONNECTED = "AIRDCPP_SOCKET_DISCONNECTED";
|
||||
|
||||
// Transfers
|
||||
export const AIRDCPP_TRANSFERS_FETCHED = "AIRDCPP_TRANSFERS_FETCHED";
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { isEmpty, isUndefined } from "lodash";
|
||||
import React, { createContext, useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { toggleAirDCPPSocketConnectionStatus } from "../actions/airdcpp.actions";
|
||||
import { getSettings } from "../actions/settings.actions";
|
||||
|
||||
import AirDCPPSocket from "../services/DcppSearchService";
|
||||
|
||||
const AirDCPPSocketContextProvider = ({ children }) => {
|
||||
@@ -51,17 +53,35 @@ const AirDCPPSocketContextProvider = ({ children }) => {
|
||||
client: { host },
|
||||
},
|
||||
} = configuration;
|
||||
|
||||
const initializedAirDCPPSocket = new AirDCPPSocket({
|
||||
protocol: `${host.protocol}`,
|
||||
hostname: `${host.hostname}:${host.port}`,
|
||||
username: `${host.username}`,
|
||||
password: `${host.password}`,
|
||||
});
|
||||
|
||||
let socketConnectionInformation = await initializedAirDCPPSocket.connect(
|
||||
`${host.username}`,
|
||||
`${host.password}`,
|
||||
true,
|
||||
);
|
||||
// connect and disconnect handlers
|
||||
initializedAirDCPPSocket.onConnected = (sessionInfo) => {
|
||||
dispatch(toggleAirDCPPSocketConnectionStatus("connected", sessionInfo));
|
||||
};
|
||||
initializedAirDCPPSocket.onDisconnected = async (
|
||||
reason,
|
||||
code,
|
||||
wasClean,
|
||||
) => {
|
||||
dispatch(
|
||||
toggleAirDCPPSocketConnectionStatus("disconnected", {
|
||||
reason,
|
||||
code,
|
||||
wasClean,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const socketConnectionInformation = await initializedAirDCPPSocket.connect();
|
||||
|
||||
// update the state with the new socket connection information
|
||||
persistSettings({
|
||||
...airDCPPState,
|
||||
airDCPPState: {
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
AIRDCPP_BUNDLES_FETCHED,
|
||||
AIRDCPP_TRANSFERS_FETCHED,
|
||||
LIBRARY_ISSUE_BUNDLES,
|
||||
AIRDCPP_SOCKET_CONNECTED,
|
||||
AIRDCPP_SOCKET_DISCONNECTED,
|
||||
} from "../constants/action-types";
|
||||
import { LOCATION_CHANGE } from "redux-first-history";
|
||||
import { isNil, isUndefined } from "lodash";
|
||||
@@ -24,6 +26,9 @@ const initialState = {
|
||||
downloadFileStatus: {},
|
||||
bundles: [],
|
||||
transfers: [],
|
||||
isAirDCPPSocketConnected: false,
|
||||
airDCPPSessionInfo: {},
|
||||
socketDisconnectionReason: {},
|
||||
};
|
||||
|
||||
function airdcppReducer(state = initialState, action) {
|
||||
@@ -95,8 +100,23 @@ function airdcppReducer(state = initialState, action) {
|
||||
...state,
|
||||
transfers: action.bundles,
|
||||
};
|
||||
|
||||
case AIRDCPP_SOCKET_CONNECTED:
|
||||
return {
|
||||
...state,
|
||||
isAirDCPPSocketConnected: true,
|
||||
airDCPPSessionInfo: action.data,
|
||||
};
|
||||
|
||||
case AIRDCPP_SOCKET_DISCONNECTED:
|
||||
return {
|
||||
...state,
|
||||
isAirDCPPSocketConnected: false,
|
||||
socketDisconnectionReason: action.data,
|
||||
};
|
||||
case LOCATION_CHANGE:
|
||||
return {
|
||||
...state,
|
||||
searchResults: [],
|
||||
isAirDCPPSearchInProgress: false,
|
||||
searchInfo: null,
|
||||
@@ -105,7 +125,6 @@ function airdcppReducer(state = initialState, action) {
|
||||
bundleDBImportResult: null,
|
||||
// bundles: [],
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ class AirDCPPSocket {
|
||||
}
|
||||
const options = {
|
||||
url: `${socketProtocol}://${configuration.hostname}/api/v1/`,
|
||||
autoReconnect: false,
|
||||
autoReconnect: true,
|
||||
reconnectInterval: 5,
|
||||
logLevel: "verbose",
|
||||
ignoredListenerEvents: [
|
||||
@@ -18,6 +18,8 @@ class AirDCPPSocket {
|
||||
"hash_statistics",
|
||||
"hub_counts_updated",
|
||||
],
|
||||
username: `${configuration.username}`,
|
||||
password: `${configuration.password}`,
|
||||
};
|
||||
const AirDCPPSocketInstance = Socket(options, window.WebSocket as any);
|
||||
return AirDCPPSocketInstance;
|
||||
|
||||
@@ -18,7 +18,7 @@ function sleep(ms: number): Promise<NodeJS.Timeout> {
|
||||
}
|
||||
|
||||
export const search = async (data: SearchData) => {
|
||||
await SocketService.connect("admin", "password");
|
||||
await SocketService.connect();
|
||||
const instance: SearchInstance = await SocketService.post("search");
|
||||
const unsubscribe = await SocketService.addListener(
|
||||
"search",
|
||||
|
||||
21
yarn.lock
21
yarn.lock
@@ -6686,13 +6686,6 @@ character-reference-invalid@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560"
|
||||
integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==
|
||||
|
||||
chevrotain@7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/chevrotain/-/chevrotain-7.0.3.tgz#f9621971a726eba804f7763edace2183d157c5d7"
|
||||
integrity sha512-G634q7M5EiqNNv+0MKcQES2jmqabbi4PvUDpzjG2t+i1XQFaMCz0o8BZ8lbQbZex4RqkzJ3pOy+UwNLFlQm4eg==
|
||||
dependencies:
|
||||
regexp-to-ast "0.5.0"
|
||||
|
||||
chokidar@^2.1.8:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
||||
@@ -7120,14 +7113,7 @@ compromise-sentences@^0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/compromise-sentences/-/compromise-sentences-0.3.0.tgz#61d34039323155bc20dad10f24340daaabf86e26"
|
||||
integrity sha512-L6d5PXmmyPyWRMoJURYOM0yILhtV37zCOy9ZW8Xd6KgY+2qQBTwuokekaGL9MDpSH3/grRDL+AHKytYpGrTEmA==
|
||||
|
||||
compromise-strict@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/compromise-strict/-/compromise-strict-0.0.2.tgz#e4bf4cc386e8795c6408d3606ebb2cd31cab9fbd"
|
||||
integrity sha512-1ApkPa6kjmyS1ZFhDRZgttDcnVv+Ci8U0kIIsxRBLCN0vautyPm6FROvBwoLimlU3pgIH3hMIpdNdVfe2yj3Gw==
|
||||
dependencies:
|
||||
chevrotain "7.0.3"
|
||||
|
||||
compromise@^13.11.3, compromise@^13.11.4:
|
||||
compromise@^13.11.4:
|
||||
version "13.11.4"
|
||||
resolved "https://registry.yarnpkg.com/compromise/-/compromise-13.11.4.tgz#3136e6d2c2cca822ba7cebd140c9f27f405b67f1"
|
||||
integrity sha512-nBITcNdqIHSVDDluaG6guyFFCSNXN+Hu87fU8VlhkE5Z0PwTZN1nro2O7a8JcUH88nB5EOzrxd9zKfXLSNFqcg==
|
||||
@@ -15855,11 +15841,6 @@ regex-not@^1.0.0, regex-not@^1.0.2:
|
||||
extend-shallow "^3.0.2"
|
||||
safe-regex "^1.1.0"
|
||||
|
||||
regexp-to-ast@0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz#56c73856bee5e1fef7f73a00f1473452ab712a24"
|
||||
integrity sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==
|
||||
|
||||
regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
|
||||
|
||||
Reference in New Issue
Block a user