Skip to content

Commit

Permalink
Allow extensions to replace splash page
Browse files Browse the repository at this point in the history
The splash page can now be replaced by
providing a compatable react component.
  • Loading branch information
jameshadfield committed Dec 31, 2018
1 parent 27ef50d commit c73f590
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 131 deletions.
4 changes: 3 additions & 1 deletion server.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ if (args.dev) {
/* if we are in dev-mode, we need to import specific libraries & set up hot reloading */
const webpack = require("webpack"); // eslint-disable-line
if (args.extend) {
process.env.EXTEND_AUSPICE_JSON = JSON.stringify(JSON.parse(fs.readFileSync(args.extend, {encoding: 'utf8'})));
const extensionData = JSON.parse(fs.readFileSync(args.extend, {encoding: 'utf8'}));
process.env.EXTENSION_PATH = path.dirname(args.extend);
process.env.EXTEND_AUSPICE_DATA = JSON.stringify(extensionData);
}
const webpackConfig = require(process.env.WEBPACK_CONFIG ? process.env.WEBPACK_CONFIG : './webpack.config.dev'); // eslint-disable-line
const compiler = webpack(webpackConfig);
Expand Down
136 changes: 22 additions & 114 deletions src/components/splash/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import React from "react";
import { connect } from "react-redux";
import Title from "../framework/title";
import NavBar from "../navBar";
import Flex from "../../components/framework/flex";
import { logos } from "./logos";
import { CenterContent } from "./centerContent";
import { changePage } from "../../actions/navigation";
import DefaultSplashContent from "./splash";
import { hasExtension, getExtension } from "../../util/extensions";
import ErrorBoundary from "../../util/errorBoundry";
import { fetchJSON } from "../../util/serverInteraction";
import { charonAPIAddress, controlsHiddenWidth } from "../../util/globals";
import { changePage } from "../../actions/navigation";

const SplashContent = hasExtension("splashComponent") ?
getExtension("splashComponent") :
DefaultSplashContent;
/* TODO: check that when compiling DefaultSplashContent isn't included if extension is defined */


@connect((state) => ({
errorMessage: state.general.errorMessage,
Expand All @@ -28,116 +32,20 @@ class Splash extends React.Component {
console.warn(err.message);
});
}
formatDataset(fields) {
let path = fields.join("/");
if (this.state.source !== "live") {
path = this.state.source + "/" + path;
}
return (
<li key={path}>
<div
style={{color: "#5097BA", textDecoration: "none", cursor: "pointer", fontWeight: "400", fontSize: "94%"}}
onClick={() => this.props.dispatch(changePage({path, push: true}))}
>
{path}
</div>
</li>
);
}
listAvailable() {
if (!this.state.source) return null;
if (!this.state.available) {
if (this.state.source === "live" || this.state.source === "staging") {
return (
<CenterContent>
<div style={{fontSize: "18px"}}>
{`No available ${this.state.source} datasets. Try "/local/" for local datasets.`}
</div>
</CenterContent>
);
}
return null;
}

let listJSX;
/* make two columns for wide screens */
if (this.props.browserDimensions.width > 1000) {
const secondColumnStart = Math.ceil(this.state.available.length / 2);
listJSX = (
<div style={{display: "flex", flexWrap: "wrap"}}>
<div style={{flex: "1 50%", minWidth: "0"}}>
<ul>
{this.state.available.slice(0, secondColumnStart).map((data) => this.formatDataset(data))}
</ul>
</div>
<div style={{flex: "1 50%", minWidth: "0"}}>
<ul>
{this.state.available.slice(secondColumnStart).map((data) => this.formatDataset(data))}
</ul>
</div>
</div>
);
} else {
listJSX = (
<ul style={{marginLeft: "-22px"}}>
{this.state.available.map((data) => this.formatDataset(data))}
</ul>
);
}
return (
<CenterContent>
<div>
<div style={{fontSize: "26px"}}>
{`Available ${this.state.narratives ? "Narratives" : "Datasets"} for source ${this.state.source}`}
</div>
{listJSX}
</div>
</CenterContent>
);
}
render() {
const isMobile = this.props.browserDimensions.width < controlsHiddenWidth;
return (
<div>
<NavBar minified={isMobile}/>

<div className="static container">
<Flex justifyContent="center">
<Title/>
</Flex>
<div className="row">
<h1 style={{textAlign: "center", marginTop: "-10px", fontSize: "29px"}}> Real-time tracking of virus evolution </h1>
</div>
{/* First: either display the error message or the intro-paragraph */}
{this.props.errorMessage || this.state.errorMessage ? (
<CenterContent>
<div>
<p style={{color: "rgb(222, 60, 38)", fontWeight: 600, fontSize: "24px"}}>
{"😱 404, or an error has occured 😱"}
</p>
<p style={{color: "rgb(222, 60, 38)", fontWeight: 400, fontSize: "18px"}}>
{`Details: ${this.props.errorMessage || this.state.errorMessage}`}
</p>
<p style={{fontSize: "16px"}}>
{"If this keeps happening, or you believe this is a bug, please "}
<a href={"mailto:[email protected]"}>{"get in contact with us."}</a>
</p>
</div>
</CenterContent>
) : (
<p style={{maxWidth: 600, marginTop: 0, marginRight: "auto", marginBottom: 20, marginLeft: "auto", textAlign: "center", fontSize: 16, fontWeight: 300, lineHeight: 1.42857143}}>
Nextstrain is an open-source project to harness the scientific and public health potential of pathogen genome data. We provide a continually-updated view of publicly available data with powerful analytics and visualizations showing pathogen evolution and epidemic spread. Our goal is to aid epidemiological understanding and improve outbreak response.
</p>
)}
{/* Secondly, list the available datasets / narratives */}
{this.listAvailable()}
{/* Finally, the footer (logos) */}
<CenterContent>
{logos}
</CenterContent>

</div>
</div>
<ErrorBoundary>
<SplashContent
isMobile={this.props.browserDimensions.width < controlsHiddenWidth}
source={this.state.source}
available={this.state.available}
narratives={this.state.narratives}
browserDimensions={this.props.browserDimensions}
dispatch={this.props.dispatch}
errorMessage={this.props.errorMessage || this.state.errorMessage}
changePage={changePage}
/>
</ErrorBoundary>
);
}
}
Expand Down
123 changes: 123 additions & 0 deletions src/components/splash/splash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React from "react";
import Title from "../framework/title";
import NavBar from "../navBar";
import Flex from "../../components/framework/flex";
import { logos } from "./logos";
import { CenterContent } from "./centerContent";


const formatDataset = (source, fields, dispatch, changePage) => {
let path = fields.join("/");
if (source !== "live") {
path = source + "/" + path;
}
return (
<li key={path}>
<div
style={{color: "#5097BA", textDecoration: "none", cursor: "pointer", fontWeight: "400", fontSize: "94%"}}
onClick={() => dispatch(changePage({path, push: true}))}
>
{path}
</div>
</li>
);
};

const listAvailable = (source, available, narratives, browserDimensions, dispatch, changePage) => {
if (!source) return null;
if (!available) {
if (source === "live" || source === "staging") {
return (
<CenterContent>
<div style={{fontSize: "18px"}}>
{`No available ${source} datasets. Try "/local/" for local datasets.`}
</div>
</CenterContent>
);
}
return null;
}


let listJSX;
/* make two columns for wide screens */
if (browserDimensions.width > 1000) {
const secondColumnStart = Math.ceil(available.length / 2);
listJSX = (
<div style={{display: "flex", flexWrap: "wrap"}}>
<div style={{flex: "1 50%", minWidth: "0"}}>
<ul>
{available.slice(0, secondColumnStart).map((data) => formatDataset(source, data, dispatch, changePage))}
</ul>
</div>
<div style={{flex: "1 50%", minWidth: "0"}}>
<ul>
{available.slice(secondColumnStart).map((data) => formatDataset(source, data, dispatch, changePage))}
</ul>
</div>
</div>
);
} else {
listJSX = (
<ul style={{marginLeft: "-22px"}}>
{available.map((data) => formatDataset(source, data, dispatch, changePage))}
</ul>
);
}
return (
<CenterContent>
<div>
<div style={{fontSize: "26px"}}>
{`Available ${narratives ? "Narratives" : "Datasets"} for source ${source}`}
</div>
{listJSX}
</div>
</CenterContent>
);
};


const SplashContent = ({isMobile, source, available, narratives, browserDimensions, dispatch, errorMessage, changePage}) => (
<div>
<NavBar minified={isMobile}/>

<div className="static container">
<Flex justifyContent="center">
<Title/>
</Flex>
<div className="row">
<h1 style={{textAlign: "center", marginTop: "-10px", fontSize: "29px"}}> Real-time tracking of virus evolution </h1>
</div>
{/* First: either display the error message or the intro-paragraph */}
{errorMessage ? (
<CenterContent>
<div>
<p style={{color: "rgb(222, 60, 38)", fontWeight: 600, fontSize: "24px"}}>
{"😱 404, or an error has occured 😱"}
</p>
<p style={{color: "rgb(222, 60, 38)", fontWeight: 400, fontSize: "18px"}}>
{`Details: ${errorMessage}`}
</p>
<p style={{fontSize: "16px"}}>
{"If this keeps happening, or you believe this is a bug, please "}
<a href={"mailto:[email protected]"}>{"get in contact with us."}</a>
</p>
</div>
</CenterContent>
) : (
<p style={{maxWidth: 600, marginTop: 0, marginRight: "auto", marginBottom: 20, marginLeft: "auto", textAlign: "center", fontSize: 16, fontWeight: 300, lineHeight: 1.42857143}}>
Nextstrain is an open-source project to harness the scientific and public health potential of pathogen genome data. We provide a continually-updated view of publicly available data with powerful analytics and visualizations showing pathogen evolution and epidemic spread. Our goal is to aid epidemiological understanding and improve outbreak response.
</p>
)}
{/* Secondly, list the available datasets / narratives */}
{listAvailable(source, available, narratives, browserDimensions, dispatch, changePage)}
{/* Finally, the footer (logos) */}
<CenterContent>
{logos}
</CenterContent>

</div>
</div>
);

export default SplashContent;
2 changes: 0 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import "./css/boxed.css";
import "./css/select.css";
import "./css/narrative.css";

import "./util/extensions"

const store = configureStore();

/* set up non-redux state storage for the animation - use this conservitavely! */
Expand Down
30 changes: 30 additions & 0 deletions src/util/errorBoundry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}

componentDidCatch(error, info) {
// You can also log the error to an error reporting service
console.error(error);
console.error(info);
}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (<h1>Something went wrong.</h1>);
}

return this.props.children;
}
}

export default ErrorBoundary;
33 changes: 22 additions & 11 deletions src/util/extensions.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@


/* set up time -- only runs once no matter how many components import this */

const registry = (() => {
console.log("EXTENSTIONS.jS");
if (!process.env.EXTENSION_DATA) {
console.log("no EXTENSION_DATA found")
console.log("no EXTENSION_DATA found");
return {};
}
const extensionJson = JSON.parse(process.env.EXTENSION_DATA);
console.log("extensionJson", extensionJson);
return extensionJson;
const extensions = JSON.parse(process.env.EXTENSION_DATA);

Object.keys(extensions).forEach((key) => {
if (key.endsWith("Component")) {
console.log("loading component", key);
/* "@extensions" is a webpack alias */
extensions[key] = require(`@extensions/${extensions[key]}`).default; // eslint-disable-line
}
});
console.log("extensions", extensions);
return extensions;
})();


export const getExtension = (what) => {
console.log("trying to get:", what)
return "something";
}
if (registry[what]) {
return registry[what];
}
console.error("Requested non-existing extension", what);
return false;
};

export const hasExtension = (what) => {
return Object.keys(registry).includes(what);
};
Loading

0 comments on commit c73f590

Please sign in to comment.