Pedro Vila-Cerqueira • Mário Ramirez Lab
Chewie-NS Frontend:
How Chewie needs to be groomed
Chewie-NS structure
Main scripts
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose, combineReducers } from "redux";
import thunk from "redux-thunk";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
// Define the application
const app = (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
ReactDOM.render(app, document.getElementById("root"));
index.js
- Redux
- Main App
Main scripts
import React, { Component } from "react";
import {
Route,
Switch,
withRouter,
Redirect,
Link,
useRouteMatch,
} from "react-router-dom";
import { connect } from "react-redux";
// Chewie local components imports
import Aux from "./hoc/Aux/Aux";
import Stats from "./containers/Stats/Stats";
import About from "./containers/About/About";
import Locus from "./containers/Locus/Locus";
import Chewie from "./containers/Chewie/Chewie";
import Schema from "./containers/Schema/Schema";
import Logout from "./containers/Auth/Logout/Logout";
import Species from "./containers/Species/Species";
import MuiLogin from "./containers/Auth/MuiLogin/MuiLogin";
import Sequences from "./containers/Sequences/Sequences";
import MuiRegister from "./containers/Auth/MuiRegister/MuiRegister";
import Profile from "./containers/Auth/Profile/Profile";
import MuiSideDrawer from "./components/Navigation/MuiSideDrawer/MuiSideDrawer";
import * as actions from "./store/actions/index";
// Material Ui Components
import MuiLink from "@material-ui/core/Link";
import Breadcrumbs from "@material-ui/core/Breadcrumbs";
// Determine URL matches for Breadcrumb generation
function SimpleBreadcrumbs() {
const aboutMatches = useRouteMatch("/about");
const statsMatches = useRouteMatch("/stats");
const sequencesMatches = useRouteMatch("/sequences");
const speciesIdMatches = useRouteMatch("/species/:species_id");
const schemasIdMatches = useRouteMatch(
"/species/:species_id/schemas/:schema_id"
);
const locusIdMatches = useRouteMatch(
"/species/:species_id/schemas/:schema_id/locus/:locus_id"
);
const styles = {
breadcrumb: {
color: "#bb7944",
},
};
const spd = JSON.parse(localStorage.getItem("speciesD"));
const schemaName = localStorage.getItem("schemaName");
// Breadcrumbs generation
return (
<>
<Breadcrumbs aria-label="breadcrumb">
{aboutMatches && (
<div>
<MuiLink component={Link} to="/about" style={styles.breadcrumb}>
About
</MuiLink>
</div>
)}
{sequencesMatches && (
<MuiLink component={Link} to="/sequences" style={styles.breadcrumb}>
Search
</MuiLink>
)}
{statsMatches && (
<MuiLink component={Link} to="/stats" style={styles.breadcrumb}>
Schemas
</MuiLink>
)}
{speciesIdMatches && (
<MuiLink
component={Link}
to={`/species/${speciesIdMatches.params.species_id}`}
style={styles.breadcrumb}
>
<i>{spd[speciesIdMatches.params.species_id]}</i>
</MuiLink>
)}
{schemasIdMatches && (
<MuiLink
component={Link}
to={`/species/${schemasIdMatches.params.species_id}/schemas/${schemasIdMatches.params.schema_id}`}
style={styles.breadcrumb}
>
{schemaName}
</MuiLink>
)}
{locusIdMatches && (
<MuiLink
component={Link}
to={`/schemas/${locusIdMatches.params.schema_id}/locus/${locusIdMatches.params.locus_id}`}
style={styles.breadcrumb}
>
Locus {locusIdMatches.params.locus_id}
</MuiLink>
)}
</Breadcrumbs>
</>
);
}
class App extends Component {
componentDidMount() {
this.props.onTryAutoSignup();
}
render() {
// Define common app routes
let routes = (
<Aux>
<SimpleBreadcrumbs />
<Switch>
<Route path="/auth" component={MuiLogin} />
<Route path="/register" component={MuiRegister} />
<Route path="/about" component={About} />
<Route path="/" exact component={Chewie} />
<Route path="/stats" component={Stats} />
<Route path="/sequences" component={Sequences} />
<Route
path="/species/:species_id/schemas/:schema_id/locus/:locus_id"
component={Locus}
/>
<Route
path="/species/:species_id/schemas/:schema_id"
component={Schema}
/>
<Route path="/species/:species_id" component={Species} />
<Redirect to="/" />
</Switch>
</Aux>
);
if (this.props.isAuthenticated) {
// Defines user authenticated routes
routes = (
<Switch>
<Route path="/logout" component={Logout} />
<Route path="/profile" component={Profile} />
<Route path="/about" component={About} />
<Route path="/stats" component={Stats} />
<Route path="/sequences" component={Sequences} />
<Route path="/" exact component={Chewie} />
<Route
path="/species/:species_id/schemas/:schema_id/locus/:locus_id"
component={Locus}
/>
<Route
path="/species/:species_id/schemas/:schema_id"
component={Schema}
/>
<Route path="/species/:species_id" component={Species} />
<Redirect to="/" />
</Switch>
);
}
return (
<div>
<MuiSideDrawer>{routes}</MuiSideDrawer>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isAuthenticated: state.auth.token !== null,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onTryAutoSignup: () => dispatch(actions.authCheckState()),
};
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
App.js
- Breadcrumbs
- Pages/Routes
Chewie-NS structure
Chewie-NS structure
Components
Containers
- Don't have state
- Have state
import React from "react";
import classes from "./Markdown.module.css";
import ReactMarkdown from "react-markdown";
// Defines an image renderer
const imageRenderer = (props) => {
return <img className={classes.photo} alt={props.alt} src={props.src} />;
};
// Defines a link renderer
const linkRenderer = (props) => {
return (
<a href={props.href} target="_blank" rel="noopener noreferrer">
{props.children}
</a>
);
};
// Save the renderers in an object
const renderers = {
image: imageRenderer,
link: linkRenderer,
};
// Define the Markdwon component
const markdown = (props) => {
return (
<div className={classes.App}>
<ReactMarkdown source={props.markdown} renderers={ renderers } />
</div>
);
};
export default markdown;
Chewie-NS structure
Components
Containers
- Don't have state
- Have state
class Sequences extends Component {
state = {
seq: "",
};
onSubmitHandler = (event) => {
event.preventDefault();
this.props.onFetchSequence(this.state.seq);
};
onClickClear = () => {
this.setState({
seq: "",
});
};
render() {
const { classes } = this.props;
let sequenceTable = <MUIDataTable />;
let errorMessage = null;
if (this.props.error) {
errorMessage = <p>{this.props.error.message}</p>;
}
if (!this.props.loading) {
let seqData = this.props.sequence_data;
sequenceTable = (
<MuiThemeProvider theme={this.getMuiTheme()}>
<MUIDataTable
title={"Results"}
data={seqData}
columns={SEQUENCES_COLUMNS}
options={SEQUENCES_OPTIONS}
/>
</MuiThemeProvider>
);
}
return (
<div id="homeDiv">
<div id="titleDiv">
<h1 style={{ textAlign: "center" }}>Allele Search</h1>
</div>
<div id="TextAreaDiv">
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<form
className={classes.root}
onSubmit={(e) => this.onSubmitHandler(e)}
noValidate
autoComplete="off"
>
<div>
<TextField
id="outlined-textarea"
label="Alelle Sequence or Hash"
placeholder="DNA Sequence or Hash"
rows={4}
multiline
variant="outlined"
onInput={(e) => this.setState({ seq: e.target.value })}
/>
<Button
type="submit"
fullWidth
variant="contained"
className={classes.submit}
classes={{
root: classes.buttonRoot,
}}
>
SEARCH
</Button>
</div>
</form>
</div>
</Container>
</div>
<div>{errorMessage}</div>
<div>{sequenceTable}</div>
<Copyright />
</div>
);
}
}
Chewie-NS structure
Frontpage images
Chewie-NS structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Chewie Nomenclature Server</title>
</head>
<body style="background-color: #f2f2f2">
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
Add new columns to tables
import React from "react";
import { Link as RouterLink } from "react-router-dom";
// Chewie local imports
import Aux from "../../../hoc/Aux/Aux";
// Material UI imports
import Button from "@material-ui/core/Button";
import CircularProgress from "@material-ui/core/CircularProgress";
export const STATS_COLUMNS = [
{
name: "species_id",
label: "Species ID",
options: {
filter: false,
sort: true,
display: true,
setCellHeaderProps: (value) => {
return {
style: {
fontWeight: "bold",
},
};
},
},
},
{
name: "species_name",
label: "Species",
options: {
filter: true,
setCellHeaderProps: (value) => {
return {
style: {
fontWeight: "bold",
},
};
},
customBodyRender: (value, tableMeta, updateValue) => (
<Aux>
<i>{value}</i>
</Aux>
),
},
},
{
name: "nr_schemas",
label: "No. Schemas available",
options: {
filter: true,
setCellHeaderProps: (value) => {
return {
style: {
fontWeight: "bold",
},
};
},
},
},
{
name: "Schemas Details",
options: {
filter: false,
empty: true,
setCellHeaderProps: (value) => {
return {
style: {
fontWeight: "bold",
},
};
},
customBodyRender: (value, tableMeta, updateValue) => {
return (
<Button
variant="contained"
color="default"
component={RouterLink}
to={"/species/" + tableMeta.rowData[0]}
onClick={() => console.log(tableMeta.rowData[0])}
>
Schema Details
</Button>
);
},
},
},
];
export const STATS_OPTIONS = {
textLabels: {
body: {
noMatch: <CircularProgress />,
},
},
responsive: "scrollMaxHeight",
selectableRowsHeader: false,
selectableRows: "none",
selectableRowsOnClick: false,
print: false,
download: false,
search: false,
filter: false,
viewColumns: false,
};
Change table options
import React from "react";
import { Link as RouterLink } from "react-router-dom";
// Chewie local imports
import Aux from "../../../hoc/Aux/Aux";
// Material UI imports
import Button from "@material-ui/core/Button";
import CircularProgress from "@material-ui/core/CircularProgress";
export const STATS_COLUMNS = [
{
name: "species_id",
label: "Species ID",
options: {
filter: false,
sort: true,
display: true,
setCellHeaderProps: (value) => {
return {
style: {
fontWeight: "bold",
},
};
},
},
},
{
name: "species_name",
label: "Species",
options: {
filter: true,
setCellHeaderProps: (value) => {
return {
style: {
fontWeight: "bold",
},
};
},
customBodyRender: (value, tableMeta, updateValue) => (
<Aux>
<i>{value}</i>
</Aux>
),
},
},
{
name: "nr_schemas",
label: "No. Schemas available",
options: {
filter: true,
setCellHeaderProps: (value) => {
return {
style: {
fontWeight: "bold",
},
};
},
},
},
{
name: "Schemas Details",
options: {
filter: false,
empty: true,
setCellHeaderProps: (value) => {
return {
style: {
fontWeight: "bold",
},
};
},
customBodyRender: (value, tableMeta, updateValue) => {
return (
<Button
variant="contained"
color="default"
component={RouterLink}
to={"/species/" + tableMeta.rowData[0]}
onClick={() => console.log(tableMeta.rowData[0])}
>
Schema Details
</Button>
);
},
},
},
];
export const STATS_OPTIONS = {
textLabels: {
body: {
noMatch: <CircularProgress />,
},
},
responsive: "scrollMaxHeight",
selectableRowsHeader: false,
selectableRows: "none",
selectableRowsOnClick: false,
print: false,
download: false,
search: false,
filter: false,
viewColumns: false,
};
Chewie-NS structure
Higher-order components
<Aux>
<div id="schemasAvailable" style={{ float: "right" }}>
<Button
variant="contained"
color="default"
component={Link}
to="/stats"
>
Back to Available Schemas
</Button>
</div>
<div style={{ marginLeft: "5%", marginRight: "5%" }}>
<div>
<h1 style={{ textAlign: "center" }}>Schemas Overview</h1>
</div>
<div style={{ marginTop: "40px" }}>{species}</div>
<div style={{ marginTop: "40px", marginBottom: "40px" }}>
{species_plot}
</div>
<Copyright />
</div>
</Aux>
New page example
import React, { Component } from 'react';
// more imports
// define constants need below
// like styles
class Example extends Component {
state = {
data: "",
};
// lifecycle methods
// such as componentDidMount
// this method is used to
// perform API requests
componentDidMount() {
this.props.onFetchSpeciesStats();
}
render() {
// define components one-by-one
// apply some simple logic
return <div>Chewie says Aarrr wwgggh waah</div>;
}
}
export default Example;
Add new page to App.js
import React, { Component } from "react";
import {
Route,
Switch,
withRouter,
Redirect,
Link,
useRouteMatch,
} from "react-router-dom";
import { connect } from "react-redux";
// Chewie local components imports
import Aux from "./hoc/Aux/Aux";
import Stats from "./containers/Stats/Stats";
import About from "./containers/About/About";
import Locus from "./containers/Locus/Locus";
import Chewie from "./containers/Chewie/Chewie";
import Schema from "./containers/Schema/Schema";
import Logout from "./containers/Auth/Logout/Logout";
import Species from "./containers/Species/Species";
import MuiLogin from "./containers/Auth/MuiLogin/MuiLogin";
import Sequences from "./containers/Sequences/Sequences";
import MuiRegister from "./containers/Auth/MuiRegister/MuiRegister";
import Profile from "./containers/Auth/Profile/Profile";
import MuiSideDrawer from "./components/Navigation/MuiSideDrawer/MuiSideDrawer";
import Example from "./containers/Example/Example";
import * as actions from "./store/actions/index";
// Material Ui Components
import MuiLink from "@material-ui/core/Link";
import Breadcrumbs from "@material-ui/core/Breadcrumbs";
// Determine URL matches for Breadcrumb generation
function SimpleBreadcrumbs() {
const aboutMatches = useRouteMatch("/about");
const statsMatches = useRouteMatch("/stats");
const sequencesMatches = useRouteMatch("/sequences");
const speciesIdMatches = useRouteMatch("/species/:species_id");
const schemasIdMatches = useRouteMatch(
"/species/:species_id/schemas/:schema_id"
);
const locusIdMatches = useRouteMatch(
"/species/:species_id/schemas/:schema_id/locus/:locus_id"
);
const styles = {
breadcrumb: {
color: "#bb7944",
},
};
const spd = JSON.parse(localStorage.getItem("speciesD"));
const schemaName = localStorage.getItem("schemaName");
// Breadcrumbs generation
return (
<>
<Breadcrumbs aria-label="breadcrumb">
{aboutMatches && (
<div>
<MuiLink component={Link} to="/about" style={styles.breadcrumb}>
About
</MuiLink>
</div>
)}
{sequencesMatches && (
<MuiLink component={Link} to="/sequences" style={styles.breadcrumb}>
Search
</MuiLink>
)}
{statsMatches && (
<MuiLink component={Link} to="/stats" style={styles.breadcrumb}>
Schemas
</MuiLink>
)}
{speciesIdMatches && (
<MuiLink
component={Link}
to={`/species/${speciesIdMatches.params.species_id}`}
style={styles.breadcrumb}
>
<i>{spd[speciesIdMatches.params.species_id]}</i>
</MuiLink>
)}
{schemasIdMatches && (
<MuiLink
component={Link}
to={`/species/${schemasIdMatches.params.species_id}/schemas/${schemasIdMatches.params.schema_id}`}
style={styles.breadcrumb}
>
{schemaName}
</MuiLink>
)}
{locusIdMatches && (
<MuiLink
component={Link}
to={`/schemas/${locusIdMatches.params.schema_id}/locus/${locusIdMatches.params.locus_id}`}
style={styles.breadcrumb}
>
Locus {locusIdMatches.params.locus_id}
</MuiLink>
)}
</Breadcrumbs>
</>
);
}
class App extends Component {
componentDidMount() {
this.props.onTryAutoSignup();
}
render() {
// Define common app routes
let routes = (
<Aux>
<SimpleBreadcrumbs />
<Switch>
<Route path="/auth" component={MuiLogin} />
<Route path="/register" component={MuiRegister} />
<Route path="/about" component={About} />
<Route path="/example" component={Example} />
<Route path="/" exact component={Chewie} />
<Route path="/stats" component={Stats} />
<Route path="/sequences" component={Sequences} />
<Route
path="/species/:species_id/schemas/:schema_id/locus/:locus_id"
component={Locus}
/>
<Route
path="/species/:species_id/schemas/:schema_id"
component={Schema}
/>
<Route path="/species/:species_id" component={Species} />
<Redirect to="/" />
</Switch>
</Aux>
);
if (this.props.isAuthenticated) {
// Defines user authenticated routes
routes = (
<Switch>
<Route path="/logout" component={Logout} />
<Route path="/profile" component={Profile} />
<Route path="/about" component={About} />
<Route path="/stats" component={Stats} />
<Route path="/sequences" component={Sequences} />
<Route path="/" exact component={Chewie} />
<Route
path="/species/:species_id/schemas/:schema_id/locus/:locus_id"
component={Locus}
/>
<Route
path="/species/:species_id/schemas/:schema_id"
component={Schema}
/>
<Route path="/species/:species_id" component={Species} />
<Redirect to="/" />
</Switch>
);
}
return (
<div>
<MuiSideDrawer>{routes}</MuiSideDrawer>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isAuthenticated: state.auth.token !== null,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onTryAutoSignup: () => dispatch(actions.authCheckState()),
};
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
Reflect changes on NS
# stop react and nginx service
sudo docker-compose -f docker-compose-production.yaml stop nginx_react
# make changes locally or by git pull
# restart the service
sudo docker-compose -f docker-compose-production.yaml up -d --no-deps --build
Redux Concept
Action Creator
Action
dispatch
Reducer
State
import React, { Component } from 'react';
// more imports
// define constants need below
// like styles
class Example extends Component {
state = {
data: "",
};
// lifecycle methods
// such as componentDidMount
// this method is used to
// perform API requests
componentDidMount() {
this.props.onFetchSpeciesStats();
}
render() {
// define components one-by-one
// apply some simple logic
return <div>Chewie says Aarrr wwgggh waah</div>;
}
}
// Redux functions
// Map state from the central warehouse
// to the props of this component
const mapStateToProps = (state) => {
return {
stats: state.stats.stats,
loading: state.stats.loading,
error: state.stats.error,
};
};
// Map dispatch functions that trigger
// actions from redux
// to the props of this component
const mapDispatchToProps = (dispatch) => {
return {
onFetchStats: () => dispatch(actions.fetchStats()),
onFetchSpeciesStats: () => dispatch(actions.fetchStatsSpecies()),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(withErrorHandler(Stats, axios));
// export default Example;
Chewie-NS frontend
By Pedro Cerqueira
Chewie-NS frontend
- 193