First commit
This commit is contained in:
32
src/client/components/App.tsx
Normal file
32
src/client/components/App.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as React from "react";
|
||||
import { hot } from "react-hot-loader";
|
||||
import Dashboard from "./Dashboard";
|
||||
import Import from "./Import";
|
||||
|
||||
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
|
||||
import Navbar from "./Navbar";
|
||||
import "../assets/scss/App.scss";
|
||||
|
||||
class App extends React.Component<Record<string, unknown>, undefined> {
|
||||
public render() {
|
||||
return (
|
||||
<div>
|
||||
<Router>
|
||||
<Navbar />
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<Dashboard />
|
||||
</Route>
|
||||
<Route path="/import">
|
||||
<Import />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare let module: Record<string, unknown>;
|
||||
|
||||
export default hot(module)(App);
|
||||
27
src/client/components/Dashboard.tsx
Normal file
27
src/client/components/Dashboard.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import * as React from "react";
|
||||
import ZeroState from "./ZeroState";
|
||||
|
||||
interface IProps {}
|
||||
interface IState {}
|
||||
|
||||
class Dashboard extends React.Component<IProps, IState> {
|
||||
public render() {
|
||||
return (
|
||||
<section className="section">
|
||||
<h1 className="title">Dashboard</h1>
|
||||
<h2 className="subtitle">
|
||||
A simple container to divide your page into <strong>sections</strong>,
|
||||
like the one you're currently reading.
|
||||
</h2>
|
||||
<ZeroState
|
||||
header={"Set the source directory"}
|
||||
message={
|
||||
"No comics were found! Please point ThreeTwo! to a directory..."
|
||||
}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
186
src/client/components/Import.tsx
Normal file
186
src/client/components/Import.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import * as React from "react";
|
||||
import { hot } from "react-hot-loader";
|
||||
import _ from "lodash";
|
||||
import axios from "axios";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
import { comicinfoAPICall } from "../actions/comicinfo.actions";
|
||||
import MatchResult from "./MatchResult";
|
||||
import {
|
||||
IFolderData,
|
||||
IComicVineSearchMatch,
|
||||
} from "../shared/interfaces/comicinfo.interfaces";
|
||||
import { folderWalk } from "../shared/utils/folder.utils";
|
||||
// import {} from "../constants/comicvine.mock";
|
||||
import * as Comlink from "comlink";
|
||||
import WorkerAdd from "../workers/extractCovers.worker";
|
||||
interface IProps {
|
||||
matches: unknown;
|
||||
}
|
||||
interface IState {
|
||||
folderWalkResults?: Array<IFolderData>;
|
||||
searchPaneIndex: number;
|
||||
}
|
||||
|
||||
class Import extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
folderWalkResults: undefined,
|
||||
searchPaneIndex: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
public toggleSearchResultsPane(paneId: number): void {
|
||||
this.setState({
|
||||
searchPaneIndex: paneId,
|
||||
});
|
||||
}
|
||||
|
||||
public async init() {
|
||||
// WebWorkers use `postMessage` and therefore work with Comlink.
|
||||
const { add } = Comlink.wrap(new WorkerAdd());
|
||||
const res = await add(1, 3);
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
public async startFolderWalk(): Promise<void> {
|
||||
this.init();
|
||||
const folderWalkResults = await folderWalk();
|
||||
this.setState({
|
||||
folderWalkResults,
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div>
|
||||
<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>
|
||||
<p className="buttons">
|
||||
<button
|
||||
className="button is-medium"
|
||||
onClick={() => this.startFolderWalk()}
|
||||
>
|
||||
<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>
|
||||
{/* Folder walk results */}
|
||||
{!_.isUndefined(this.state.folderWalkResults) ? (
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Format</th>
|
||||
<th>Is File</th>
|
||||
</tr>
|
||||
{!_.isUndefined(this.state.folderWalkResults) &&
|
||||
this.state.folderWalkResults.map((result, idx) => (
|
||||
<tr key={idx}>
|
||||
<td>
|
||||
{!result.isLink && !result.isFile ? (
|
||||
<span className="icon-text">
|
||||
<span className="icon">
|
||||
<i className="fas fa-folder"></i>
|
||||
</span>
|
||||
<span>{result.name}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="ml-5">{result.name}</span>
|
||||
)}
|
||||
|
||||
{this.state.searchPaneIndex === idx &&
|
||||
!_.isUndefined(this.props.matches) ? (
|
||||
<MatchResult
|
||||
queryData={result}
|
||||
matchData={this.props.matches}
|
||||
visible={true}
|
||||
/>
|
||||
) : null}
|
||||
</td>
|
||||
<td>
|
||||
{!_.isEmpty(result.extension) ? (
|
||||
<span className="tag is-info">
|
||||
{result.extension}
|
||||
</span>
|
||||
) : null}
|
||||
</td>
|
||||
<td>{result.isFile.toString()}</td>
|
||||
<td>
|
||||
<button
|
||||
key={idx}
|
||||
className="button is-small is-primary is-outlined"
|
||||
onClick={(e) => {
|
||||
this.props.findMatches(e, result);
|
||||
this.toggleSearchResultsPane(idx);
|
||||
}}
|
||||
>
|
||||
Find Match
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
</table>
|
||||
) : null}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
matches: state.comicInfo.searchResults,
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
findMatches(e, results) {
|
||||
dispatch(
|
||||
comicinfoAPICall({
|
||||
callURIAction: "search",
|
||||
callMethod: "get",
|
||||
callParams: {
|
||||
format: "json",
|
||||
query: results.name,
|
||||
limit: 10,
|
||||
offset: 5,
|
||||
sort: "name:asc",
|
||||
resources: "issue",
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Import);
|
||||
57
src/client/components/MatchResult.tsx
Normal file
57
src/client/components/MatchResult.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
IComicVineSearchMatch,
|
||||
IFolderData,
|
||||
} from "../shared/interfaces/comicinfo.interfaces";
|
||||
import _ from "lodash";
|
||||
import { autoMatcher } from "../shared/utils/query.transformer";
|
||||
|
||||
interface IProps {
|
||||
matchData: unknown;
|
||||
visible: boolean;
|
||||
queryData: IFolderData;
|
||||
}
|
||||
|
||||
interface IState {}
|
||||
|
||||
class MatchResult extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
console.log(this.props);
|
||||
autoMatcher(this.props.queryData, this.props.matchData.results);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return this.props.visible ? (
|
||||
<div>
|
||||
<h3>Matches</h3>
|
||||
<div className="search-results-container">
|
||||
{this.props.matchData.results.map((result, idx) => {
|
||||
return (
|
||||
<div key={idx} className="search-result">
|
||||
<img className="cover-image" src={result.image.thumb_url} />
|
||||
<div className="search-result-details">
|
||||
<h5>{result.volume.name}</h5>
|
||||
|
||||
{!_.isEmpty(result.extension) ? (
|
||||
<span className="tag is-info">{result.extension}</span>
|
||||
) : null}
|
||||
|
||||
<span className="tag is-info">
|
||||
Issue Number: {result.issue_number}
|
||||
</span>
|
||||
<p>{result.site_detail_url}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
|
||||
export default MatchResult;
|
||||
212
src/client/components/Navbar.tsx
Normal file
212
src/client/components/Navbar.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
import * as React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const Navbar: React.FunctionComponent = (props) => {
|
||||
return (
|
||||
<nav className="navbar ">
|
||||
<div className="navbar-brand">
|
||||
<a className="navbar-item" href="http://bulma.io">
|
||||
<img
|
||||
src="http://bulma.io/images/bulma-logo.png"
|
||||
alt="Bulma: a modern CSS framework based on Flexbox"
|
||||
width="112"
|
||||
height="28"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a
|
||||
className="navbar-item is-hidden-desktop"
|
||||
href="https://github.com/jgthms/bulma"
|
||||
target="_blank"
|
||||
>
|
||||
<span className="icon">
|
||||
<i className="fa fa-github"></i>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
className="navbar-item is-hidden-desktop"
|
||||
href="https://twitter.com/jgthms"
|
||||
target="_blank"
|
||||
>
|
||||
<span className="icon">
|
||||
<i className="fa fa-twitter"></i>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<div className="navbar-burger burger" data-target="navMenubd-example">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="navMenubd-example" className="navbar-menu">
|
||||
<div className="navbar-start">
|
||||
<Link to="/" className="navbar-item">
|
||||
Dashboard
|
||||
</Link>
|
||||
|
||||
<Link to="/import" className="navbar-item">
|
||||
Import
|
||||
</Link>
|
||||
|
||||
<div className="navbar-item has-dropdown is-hoverable">
|
||||
<a
|
||||
className="navbar-link is-active"
|
||||
href="/documentation/overview/start/"
|
||||
>
|
||||
Docs
|
||||
</a>
|
||||
<div className="navbar-dropdown ">
|
||||
<a className="navbar-item " href="/documentation/overview/start/">
|
||||
Overview
|
||||
</a>
|
||||
|
||||
<a
|
||||
className="navbar-item is-active"
|
||||
href="http://bulma.io/documentation/components/breadcrumb/"
|
||||
>
|
||||
Components
|
||||
</a>
|
||||
|
||||
<hr className="navbar-divider" />
|
||||
<div className="navbar-item">
|
||||
<div>
|
||||
<p className="is-size-6-desktop">
|
||||
<strong className="has-text-info">0.5.1</strong>
|
||||
</p>
|
||||
|
||||
<small>
|
||||
<a className="bd-view-all-versions" href="/versions">
|
||||
View all versions
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<div className="container is-fluid">
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<h1 className="title is-6 is-mega-menu-title">
|
||||
Sub Menu Title
|
||||
</h1>
|
||||
<a className="navbar-item" href="/2017/08/03/list-of-tags/">
|
||||
<div className="navbar-content">
|
||||
<p>
|
||||
<small className="has-text-info">03 Aug 2017</small>
|
||||
</p>
|
||||
<p>New feature: list of tags</p>
|
||||
</div>
|
||||
</a>
|
||||
<a className="navbar-item" href="/2017/08/03/list-of-tags/">
|
||||
<div className="navbar-content">
|
||||
<p>
|
||||
<small className="has-text-info">03 Aug 2017</small>
|
||||
</p>
|
||||
<p>New feature: list of tags</p>
|
||||
</div>
|
||||
</a>
|
||||
<a className="navbar-item" href="/2017/08/03/list-of-tags/">
|
||||
<div className="navbar-content">
|
||||
<p>
|
||||
<small className="has-text-info">03 Aug 2017</small>
|
||||
</p>
|
||||
<p>New feature: list of tags</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div className="column">
|
||||
<h1 className="title is-6 is-mega-menu-title">
|
||||
Sub Menu Title
|
||||
</h1>
|
||||
|
||||
<a
|
||||
className="navbar-item "
|
||||
href="http://bulma.io/documentation/columns/basics/"
|
||||
>
|
||||
Columns
|
||||
</a>
|
||||
</div>
|
||||
<div className="column">
|
||||
<h1 className="title is-6 is-mega-menu-title">
|
||||
Sub Menu Title
|
||||
</h1>
|
||||
|
||||
<a className="navbar-item" href="/2017/08/03/list-of-tags/">
|
||||
<div className="navbar-content">
|
||||
<p>
|
||||
<small className="has-text-info">03 Aug 2017</small>
|
||||
</p>
|
||||
<p>New feature: list of tags</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div className="column">
|
||||
<h1 className="title is-6 is-mega-menu-title">
|
||||
Sub Menu Title
|
||||
</h1>
|
||||
<a
|
||||
className="navbar-item "
|
||||
href="/documentation/overview/start/"
|
||||
>
|
||||
Overview
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="navbar-divider" />
|
||||
<div className="navbar-item">
|
||||
<div className="navbar-content">
|
||||
<div className="level is-mobile">
|
||||
<div className="level-left">
|
||||
<div className="level-item">
|
||||
<strong>Stay up to date!</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="level-right">
|
||||
<div className="level-item">
|
||||
<a
|
||||
className="button bd-is-rss is-small"
|
||||
href="http://bulma.io/atom.xml"
|
||||
>
|
||||
<span className="icon is-small">
|
||||
<i className="fa fa-rss"></i>
|
||||
</span>
|
||||
<span>Subscribe</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="navbar-end">
|
||||
<a
|
||||
className="navbar-item is-hidden-desktop-only"
|
||||
href="https://github.com/jgthms/bulma"
|
||||
target="_blank"
|
||||
></a>
|
||||
<div className="navbar-item">
|
||||
<div className="field is-grouped">
|
||||
<p className="control"></p>
|
||||
<p className="control">Settings</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
18
src/client/components/ZeroState.tsx
Normal file
18
src/client/components/ZeroState.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as React from "react";
|
||||
|
||||
interface ZeroStateProps {
|
||||
header: string;
|
||||
message: string;
|
||||
}
|
||||
const ZeroState: React.FunctionComponent<ZeroStateProps> = (props) => {
|
||||
return (
|
||||
<article className="message is-info">
|
||||
<div className="message-body">
|
||||
<p>{ props.header }</p>
|
||||
{ props.message }
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZeroState;
|
||||
Reference in New Issue
Block a user