🚨 Implemented a notification system for background import

This commit is contained in:
2021-09-16 09:24:06 -07:00
parent b40f63289a
commit 476a55614e
8 changed files with 202 additions and 191 deletions

View File

@@ -56,8 +56,9 @@
"react-dom": "^17.0.1",
"react-fast-compare": "^3.2.0",
"react-final-form": "^6.5.3",
"react-hot-toast": "^2.1.1",
"react-loader-spinner": "^4.0.0",
"react-notification-system": "^0.4.0",
"react-notification-system-redux": "^2.0.1",
"react-select": "^4.3.1",
"react-sliding-pane": "^7.0.0",
"react-table": "^7.7.0",

View File

@@ -16,6 +16,13 @@ import {
import { refineQuery } from "../shared/utils/filenameparser.utils";
import sortBy from "array-sort-by";
import { io } from "socket.io-client";
import {
success,
error,
warning,
info,
removeAll,
} from "react-notification-system-redux";
export async function walkFolder(path: string): Promise<Array<IFolderData>> {
return axios
@@ -71,7 +78,16 @@ export const fetchComicBookMetadata = (options) => async (dispatch) => {
},
};
const walkedFolders = await walkFolder("./comics");
dispatch(
success({
// uid: 'once-please', // you can specify your own uid if required
title: "Import Started",
message: `${socket.id} connected. ${walkedFolders.length} comics scanned.`,
dismissible: "click",
position: "tr",
autoDismiss: 0,
}),
);
await axios
.request({
url: "http://localhost:8050/api/getComicCovers",
@@ -87,6 +103,10 @@ export const fetchComicBookMetadata = (options) => async (dispatch) => {
socket.on("coverExtracted", (data) => {
console.log(data);
dispatch({
type: IMS_COMICBOOK_METADATA_FETCHED,
data,
});
});
};

View File

@@ -79,7 +79,8 @@ $border-color: red;
.card-title {
margin-bottom: 0.4rem;
}
.cv-icon, i {
.cv-icon,
i {
margin: 4px 4px 4px 0;
}
padding: 0.5rem 1rem;
@@ -152,8 +153,9 @@ $border-color: red;
}
}
// Comic Detail
// Import
// Comic Detail
.comic-detail {
dl {
dd {
@@ -161,9 +163,9 @@ $border-color: red;
}
}
.button {
.airdcpp-text {
margin: 0 0 0 0.2rem;
}
.airdcpp-text {
margin: 0 0 0 0.2rem;
}
}
}
// Search

View File

@@ -1,4 +1,5 @@
import * as React from "react";
import React, { ReactElement } from "react";
import { useSelector } from "react-redux";
import { hot } from "react-hot-loader";
import Dashboard from "./Dashboard";
@@ -11,38 +12,68 @@ import Settings from "./Settings";
import { Switch, Route } from "react-router";
import Navbar from "./Navbar";
import "../assets/scss/App.scss";
import Notifications from "react-notification-system-redux";
class App extends React.Component {
public render() {
return (
<div>
<Navbar />
<Switch>
<Route exact path="/">
<Dashboard />
</Route>
<Route path="/import">
<Import path={"./comics"} />
</Route>
<Route path="/library">
<Library />
</Route>
<Route path="/search">
<Search />
</Route>
<Route
path={"/comic/details/:comicObjectId"}
component={ComicDetail}
/>
<Route path="/settings">
<Settings />
</Route>
</Switch>
</div>
);
}
}
declare let module: Record<string, unknown>;
//Optional styling
const style = {
Containers: {
DefaultStyle: {
fontFamily: "inherit",
position: "fixed",
padding: "0 10px 10px 10px",
zIndex: 9998,
WebkitBoxSizing: "border-box",
MozBoxSizing: "border-box",
boxSizing: "border-box",
height: "auto",
},
tr: {
top: "40px",
},
},
NotificationItem: {
// Override the notification item
success: {
// Applied to every notification, regardless of the notification level
borderTop: "none",
backgroundColor: "none",
borderRadius: "0.4rem",
WebkitBoxShadow: "-7px 11px 25px -9px rgba(0, 0, 0, 0.3)",
MozBoxShadow: "-7px 11px 25px -9px rgba(0, 0, 0, 0.3)",
boxShadow: "-7px 11px 25px -9px rgba(0, 0, 0, 0.3)",
},
},
};
export const App = (): ReactElement => {
const notifications = useSelector((state: RootState) => state.notifications);
return (
<div>
<Navbar />
<Notifications
notifications={notifications}
style={style}
newOnTop={true}
/>
<Switch>
<Route exact path="/">
<Dashboard />
</Route>
<Route path="/import">
<Import path={"./comics"} />
</Route>
<Route path="/library">
<Library />
</Route>
<Route path="/search">
<Search />
</Route>
<Route path={"/comic/details/:comicObjectId"} component={ComicDetail} />
<Route path="/settings">
<Settings />
</Route>
</Switch>
</div>
);
};
export default hot(module)(App);

View File

@@ -1,153 +1,86 @@
import * as React from "react";
import { isUndefined } from "lodash";
import { connect } from "react-redux";
import React, { ReactElement, useCallback } from "react";
import { isNil, isUndefined } from "lodash";
import { useSelector, useDispatch } from "react-redux";
import { fetchComicBookMetadata } from "../actions/fileops.actions";
import { IFolderData } from "threetwo-ui-typings";
import DynamicList, { createCache } from "react-window-dynamic-list";
import toast, { Toaster } from "react-hot-toast";
;
interface IProps {
matches: unknown;
fetchComicMetadata: any;
matches?: unknown;
fetchComicMetadata?: any;
path: string;
covers: any;
covers?: any;
}
interface IState {
folderWalkResults?: Array<IFolderData>;
searchPaneIndex: number;
fileOps: any;
}
class Import extends React.Component<IProps, IState> {
/**
* Returns the average of two numbers.
*
* @remarks
* This method is part of the {@link core-library#Statistics | Statistics subsystem}.
*
* @param x - The first input number
* @param y - The second input number
* @returns The arithmetic mean of `x` and `y`
*
* @beta
*/
constructor(props: IProps) {
super(props);
this.state = {
folderWalkResults: [],
searchPaneIndex: 0,
fileOps: [],
};
}
public toggleSearchResultsPane(paneId: number): void {
this.setState({
searchPaneIndex: paneId,
});
}
/**
* Returns the average of two numbers.
*
* @remarks
* This method is part of the {@link core-library#Statistics | Statistics subsystem}.
*
* @param x - The first input number
* @param y - The second input number
* @returns The arithmetic mean of `x` and `y`
*
* @beta
*/
public initiateImport = () => {
if (typeof this.props.path !== "undefined") {
this.props.fetchComicMetadata();
toast.custom(
<div className="card">
<div className="card-content">Saokaaate</div>
</div>,
{
position: "top-right",
},
);
export const Import = (props: IProps): ReactElement => {
const dispatch = useDispatch();
const isSocketConnected = useSelector((state: RootState) => {
console.log(state);
return state.fileOps.isSocketConnected;
});
const initiateImport = useCallback(() => {
if (typeof props.path !== "undefined") {
console.log("asdasd");
dispatch(fetchComicBookMetadata(props.path));
}
};
}, [dispatch]);
public cache = createCache();
return (
<div className="container">
<section className="section is-small">
<h1 className="title">Import</h1>
{isSocketConnected}
<article className="message is-dark">
<div className="message-body">
<p className="mb-2">
<span className="tag is-medium is-info is-light">
Import Only
</span>
will add comics identified from the mapped folder into the local
db.
</p>
<p>
<span className="tag is-medium is-info is-light">
Import and Tag
</span>
will scan the ComicVine, shortboxed APIs and import comics from
the mapped folder with the additional metadata.
</p>
</div>
</article>
<p className="buttons">
<button className="button is-medium" onClick={initiateImport}>
<span className="icon">
<i className="fas fa-file-import"></i>
</span>
<span>Import Only</span>
</button>
public renderRow = ({ index, style }) => (
<div style={style} className="min is-size-7">
<span className="tag is-light">{index}</span>
<div className="tags has-addons">
<span className="tag is-success">cover</span>
<span className="tag is-success is-light has-text-weight-medium">
{this.props.covers[index].comicBookCoverMetadata.name}
</span>
</div>
imported from
<div className="tags has-addons">
<span className="tag is-success">path</span>
<span className="tag is-success is-light has-text-weight-medium">
{this.props.covers[index].comicBookCoverMetadata.path}
</span>
</div>
<div className="db-import-result-panel">
<pre className="has-background-success-light">
<span className="icon">
<i className="fas fa-database"></i>
</span>
{JSON.stringify(this.props.covers[index].dbImportResult, null, 2)}
</pre>
</div>
<button className="button is-medium">
<span className="icon">
<i className="fas fa-tag"></i>
</span>
<span>Import and Tag</span>
</button>
</p>
</section>
</div>
);
};
public render() {
return (
<div className="container">
<section className="section is-small">
<h1 className="title">Import</h1>
<article className="message is-dark">
<div className="message-body">
<p className="mb-2">
<span className="tag is-medium is-info is-light">
Import Only
</span>
will add comics identified from the mapped folder into the local
db.
</p>
<p>
<span className="tag is-medium is-info is-light">
Import and Tag
</span>
will scan the ComicVine, shortboxed APIs and import comics from
the mapped folder with the additional metadata.
</p>
</div>
</article>
<Toaster />
<p className="buttons">
<button className="button is-medium" onClick={this.initiateImport}>
<span className="icon">
<i className="fas fa-file-import"></i>
</span>
<span>Import Only</span>
</button>
<button className="button is-medium">
<span className="icon">
<i className="fas fa-tag"></i>
</span>
<span>Import and Tag</span>
</button>
</p>
{!isUndefined(this.state.folderWalkResults) ? <div></div> : null}
</section>
</div>
);
}
}
function mapStateToProps(state: IState) {
console.log("state", state);
return {
// matches: state.comicInfo.searchResults,
// covers: state.fileOps.comicBookMetadata,
};
}
const mapDispatchToProps = (dispatch, ownProps) => ({
fetchComicMetadata() {
dispatch(fetchComicBookMetadata(ownProps.path));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(Import);
export default Import;

View File

@@ -33,6 +33,7 @@ function fileOpsReducer(state = initialState, action) {
return {
...state,
isSocketConnected: action.isSocketConnected,
socketId: action.socketId,
};
case IMS_RAW_IMPORT_SUCCESSFUL:
return {

View File

@@ -3,9 +3,11 @@ import { connectRouter } from "connected-react-router";
import comicinfoReducer from "../reducers/comicinfo.reducer";
import fileOpsReducer from "../reducers/fileops.reducer";
import airdcppReducer from "../reducers/airdcpp.reducer";
import { reducer as notifications } from "react-notification-system-redux";
export default (history) =>
combineReducers({
notifications,
comicInfo: comicinfoReducer,
fileOps: fileOpsReducer,
airdcpp: airdcppReducer,

View File

@@ -4191,6 +4191,14 @@ create-error-class@^3.0.0:
dependencies:
capture-stack-trace "^1.0.0"
create-react-class@^15.5.1:
version "15.7.0"
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.7.0.tgz#7499d7ca2e69bb51d13faf59bd04f0c65a1d6c1e"
integrity sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==
dependencies:
loose-envify "^1.3.1"
object-assign "^4.1.1"
cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz"
@@ -6306,11 +6314,6 @@ globule@^1.0.0:
lodash "~4.17.10"
minimatch "~3.0.2"
goober@^2.0.35:
version "2.0.41"
resolved "https://registry.yarnpkg.com/goober/-/goober-2.0.41.tgz#0a3d786ff9917bcf2a096eef703bf717838cbec9"
integrity sha512-kwjegMT5018zWydhOQlQneCgCtrKJaPsru7TaBWmTYV0nsMeUrM6L6O8JmNYb9UbPMgWcmtf+9p4Y3oJabIH1A==
got@^6.7.1:
version "6.7.1"
resolved "https://registry.npmjs.org/got/-/got-6.7.1.tgz"
@@ -10499,7 +10502,7 @@ promzard@^0.3.0:
dependencies:
read "1"
prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -10907,13 +10910,6 @@ react-hot-loader@^4.13.0:
shallowequal "^1.1.0"
source-map "^0.7.3"
react-hot-toast@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.1.1.tgz#56409ab406b534e9e58274cf98d80355ba0fdda0"
integrity sha512-Odrp4wue0fHh0pOfZt5H+9nWCMtqs3wdlFSzZPp7qsxfzmbE26QmGWIh6hG43CukiPeOjA8WQhBJU8JwtWvWbQ==
dependencies:
goober "^2.0.35"
react-input-autosize@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85"
@@ -10953,6 +10949,31 @@ react-modal@^3.12.1:
react-lifecycles-compat "^3.0.0"
warning "^4.0.3"
react-notification-system-redux@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/react-notification-system-redux/-/react-notification-system-redux-2.0.1.tgz#e1f47e788d344fac9b5183402bb96c73033e5854"
integrity sha512-Ed/IgnuXDc+dNDsnn/qPdZdpGN/2xJgiJY/y3z1UFRHbOUklDE+WLdk/JNhe5Xx33MBCqqfQClAc8cIl+wHnoA==
dependencies:
prop-types "^15.6.0"
react-notification-system "^0.2.x"
react-notification-system@^0.2.x:
version "0.2.17"
resolved "https://registry.yarnpkg.com/react-notification-system/-/react-notification-system-0.2.17.tgz#a60eddbb62225ad8f9fc5d7837546bf6cdb36818"
integrity sha1-pg7du2IiWtj5/F14N1Rr9s2zaBg=
dependencies:
create-react-class "^15.5.1"
object-assign "^4.0.1"
prop-types "^15.5.6"
react-notification-system@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/react-notification-system/-/react-notification-system-0.4.0.tgz#30db3963a176ee103b4a3bb1494c2b5578d5c024"
integrity sha512-5WhXnjkYC07zqXruCiUXDU9iHjVxZlL1zgHpNgXk91A5ghV1AHrWVrJYo1XM4SnwlKy5NLdftkaTl+pTuVFAqw==
dependencies:
object-assign "^4.0.1"
prop-types "^15.5.6"
react-redux@^7.2.3:
version "7.2.4"
resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz"