@datawheel/canon-core
Version:
Reusable React environment and components for creating visualization engines.
161 lines (136 loc) • 4.76 kB
JSX
/* eslint react/display-name:0 */
import {setConfig} from "react-hot-loader";
setConfig({logLevel: "error", showReactDomPatchNotification: false});
import React from "react";
import {hydrate} from "react-dom";
import {createHistory} from "history";
import {applyRouterMiddleware, Router, RouterContext, useRouterHistory} from "react-router";
import {syncHistoryWithStore} from "react-router-redux";
import {animateScroll} from "react-scroll";
import {I18nextProvider} from "react-i18next";
import {Provider} from "react-redux";
import {selectAll} from "d3-selection";
import createRoutes from "$app/routes";
import configureStore from "./storeConfig";
import {LOADING_END, LOADING_START} from "./consts";
import preRenderMiddleware from "./middlewares/preRenderMiddleware";
const {basename} = window.__INITIAL_STATE__.location;
const browserHistory = useRouterHistory(createHistory)({basename});
import canonConfig from "$root/canon.js";
const reduxMiddleware = canonConfig.reduxMiddleware || false;
const store = configureStore(window.__INITIAL_STATE__, browserHistory, reduxMiddleware);
const history = syncHistoryWithStore(browserHistory, store);
const routes = createRoutes(store);
import i18n from "i18next";
import yn from "yn";
import defaultTranslations from "./i18n/canon";
import CanonProvider from "./CanonProvider";
const {locale, resources} = window.__INITIAL_STATE__.i18n;
const {CANON_LOGLOCALE, NODE_ENV} = window.__INITIAL_STATE__.env;
const name = window.__APP_NAME__;
const resourceObj = {canon: {[name]: defaultTranslations}};
if (locale !== "canon") resourceObj[locale] = {[name]: resources};
i18n
.init({
fallbackLng: "canon",
lng: locale,
debug: NODE_ENV !== "production" ? yn(CANON_LOGLOCALE) : false,
ns: [name],
defaultNS: name,
react: {
wait: true,
withRef: true
},
resources: resourceObj
});
/**
Scrolls to a page element if it exists on the page.
*/
function scrollToHash(hash, wait = true) {
const elem = hash && hash.indexOf("#") === 0 ? document.getElementById(hash.slice(1)) : false;
if (elem) {
const offset = elem.getBoundingClientRect().top;
if (offset) {
animateScroll.scrollMore(offset);
setTimeout(() => {
elem.focus();
}, 100);
}
}
else if (wait) {
setTimeout(() => {
scrollToHash(hash, false);
}, 100);
}
}
/**
Middleware that captures all router requests and detects the following:
* Smooth scrolling to anchor links
* Initiatlize SSR needs loading
*/
function renderMiddleware() {
return {
renderRouterContext: (child, props) => {
const needs = props.components.filter(comp => comp && (comp.need || comp.preneed || comp.postneed));
const {action, hash, pathname, query, search, state} = props.location;
/** */
function postRender() {
if (!window.__SSR__) {
if (typeof window.ga === "function") {
setTimeout(() => {
const trackers = window.ga.getAll().map(t => t.get("name"));
trackers
.forEach(key => {
window.ga(`${key}.set`, "title", document.title);
window.ga(`${key}.set`, "page", pathname + search);
window.ga(`${key}.send`, "pageview");
});
}, 0);
}
}
if (hash) scrollToHash(hash);
else window.scrollTo(0, 0);
}
if (action !== "REPLACE" || !Object.keys(query).length) {
selectAll(".d3plus-tooltip").remove();
if (window.__SSR__ || state === "HASH" || !needs.length) {
postRender();
window.__SSR__ = false;
}
else {
store.dispatch({type: LOADING_START});
document.body.scrollTop = document.documentElement.scrollTop = 0;
preRenderMiddleware(store, props)
.then(() => {
store.dispatch({type: LOADING_END});
postRender();
});
}
}
return <RouterContext {...props}/>;
}
};
}
const helmet = window.__HELMET_DEFAULT__;
/**
Wraps the top-level router component in the CanonProvider
*/
function createElement(Component, props) {
if (props.children && props.route.path === "/") {
return <CanonProvider router={props.router} helmet={helmet} locale={locale}>
<Component {...props} />
</CanonProvider>;
}
else {
return <Component {...props} />;
}
}
hydrate(
<I18nextProvider i18n={i18n}>
<Provider store={store}>
<Router createElement={createElement} history={history} render={applyRouterMiddleware(renderMiddleware())}>
{routes}
</Router>
</Provider>
</I18nextProvider>,
document.getElementById("React-Container"));