🚨 Implemented a notification system for background import
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -33,6 +33,7 @@ function fileOpsReducer(state = initialState, action) {
|
||||
return {
|
||||
...state,
|
||||
isSocketConnected: action.isSocketConnected,
|
||||
socketId: action.socketId,
|
||||
};
|
||||
case IMS_RAW_IMPORT_SUCCESSFUL:
|
||||
return {
|
||||
|
||||
@@ -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,
|
||||
|
||||
47
yarn.lock
47
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user