UNPKG

@datawheel/canon-core

Version:

Reusable React environment and components for creating visualization engines.

161 lines (136 loc) 4.76 kB
/* 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"));