Commit d3e94bb6 authored by Maxym Borodenko's avatar Maxym Borodenko Committed by Matija Obreza
Browse files

react-router:4 without SSR

default path fix

Refactored Gui component
parent 9747c17b
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {Router, browserHistory} from 'react-router';
import {syncHistoryWithStore, routerMiddleware} from 'react-router-redux';
import {routerMiddleware, ConnectedRouter} from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
import {Provider} from 'react-redux';
import {applyMiddleware, compose, createStore} from 'redux';
import { checkAccessTokens } from 'actions/login';
import rootReducer from 'reducers/index';
import thunk from 'redux-thunk';
import routes from 'ui/routes';
import {log} from 'utilities/debug';
import App from '../src/ui/App';
// JSS & MUI
import {MuiThemeProvider} from 'material-ui/styles';
......@@ -18,42 +18,27 @@ declare const __PRELOADED_STATE__: any;
declare const window: Window & { devToolsExtension: any, __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any };
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const history = createHistory();
const initialState = __PRELOADED_STATE__ === undefined ? {} : __PRELOADED_STATE__;
const store = composeEnhancers(applyMiddleware(thunk, routerMiddleware(browserHistory)))(createStore)(rootReducer, initialState);
const appHistory = syncHistoryWithStore(browserHistory, store);
const store = composeEnhancers(applyMiddleware(thunk, routerMiddleware(history)))(createStore)(rootReducer, initialState);
if (__PRELOADED_STATE__ === undefined) {
checkAccessTokens(store.dispatch)
.then(() => {
// no SSR
ReactDOM.render(
<Provider store={ store }>
<MuiThemeProvider theme={ theme }>
<Router children={ routes } history={ appHistory }/>
</MuiThemeProvider>
</Provider>,
<Provider store={ store }>
<ConnectedRouter history={ history }>
<MuiThemeProvider theme={ theme }>
<App/>
</MuiThemeProvider>
</ConnectedRouter>
</Provider>,
document.getElementById('the-app'),
);
})
.catch((err) => {
log('Oh, oh', err);
});
} else {
// SSR
ReactDOM.hydrate(
<Provider store={ store }>
<MuiThemeProvider theme={ theme }>
<Router children={ routes } history={ appHistory } />
</MuiThemeProvider>
</Provider>,
document.getElementById('the-app'),
() => {
// We don't need the static css any more once we have launched our application.
log('Removing SSR-rendered styles');
const ssStyles = document.getElementById('server-side-styles');
ssStyles.parentNode.removeChild(ssStyles);
},
);
}
......@@ -2798,8 +2798,7 @@
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
},
"deep-diff": {
"version": "0.3.8",
......@@ -7457,6 +7456,18 @@
"prepend-http": "1.0.4",
"query-string": "4.3.4",
"sort-keys": "1.1.2"
},
"dependencies": {
"query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"dev": true,
"requires": {
"object-assign": "4.1.1",
"strict-uri-encode": "1.1.0"
}
}
}
},
"normalize.css": {
......@@ -12256,10 +12267,11 @@
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
},
"query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.0.tgz",
"integrity": "sha512-F3DkxxlY0AqD/rwe4YAwjRE2HjOkKW7TxsuteyrS/Jbwrxw887PqYBL4sWUJ9D/V1hmFns0SCD6FDyvlwo9RCQ==",
"requires": {
"decode-uri-component": "0.2.0",
"object-assign": "4.1.1",
"strict-uri-encode": "1.1.0"
}
......@@ -12542,41 +12554,56 @@
}
},
"react-router": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-3.2.0.tgz",
"integrity": "sha512-sXlLOg0TRCqnjCVskqBHGjzNjcJKUqXEKnDSuxMYJSPJNq9hROE9VsiIW2kfIq7Ev+20Iz0nxayekXyv0XNmsg==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.2.0.tgz",
"integrity": "sha512-DY6pjwRhdARE4TDw7XjxjZsbx9lKmIcyZoZ+SDO7SBJ1KUeWNxT22Kara2AC7u6/c2SYEHlEDLnzBCcNhLE8Vg==",
"requires": {
"create-react-class": "15.6.3",
"history": "3.3.0",
"hoist-non-react-statics": "1.2.0",
"history": "4.7.2",
"hoist-non-react-statics": "2.3.1",
"invariant": "2.2.2",
"loose-envify": "1.3.1",
"path-to-regexp": "1.7.0",
"prop-types": "15.6.0",
"warning": "3.0.0"
},
"dependencies": {
"history": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/history/-/history-3.3.0.tgz",
"integrity": "sha1-/O3M6PEpdTcVRdc1RhAzV5ptrpw=",
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"path-to-regexp": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
"integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
"requires": {
"invariant": "2.2.2",
"loose-envify": "1.3.1",
"query-string": "4.3.4",
"warning": "3.0.0"
"isarray": "0.0.1"
}
},
"hoist-non-react-statics": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz",
"integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs="
}
}
},
"react-router-dom": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.2.2.tgz",
"integrity": "sha512-cHMFC1ZoLDfEaMFoKTjN7fry/oczMgRt5BKfMAkTu5zEuJvUiPp1J8d0eXSVTnBh6pxlbdqDhozunOOLtmKfPA==",
"requires": {
"history": "4.7.2",
"invariant": "2.2.2",
"loose-envify": "1.3.1",
"prop-types": "15.6.0",
"react-router": "4.2.0",
"warning": "3.0.0"
}
},
"react-router-redux": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/react-router-redux/-/react-router-redux-4.0.8.tgz",
"integrity": "sha1-InQDWWtRUeGCN32rg1tdRfD4BU4="
"version": "5.0.0-alpha.9",
"resolved": "https://registry.npmjs.org/react-router-redux/-/react-router-redux-5.0.0-alpha.9.tgz",
"integrity": "sha512-euSgNIANnRXr4GydIuwA7RZCefrLQzIw5WdXspS8NPYbV+FxrKSS9MKG7U9vb6vsKHONnA4VxrVNWfnMUnUQAw==",
"requires": {
"history": "4.7.2",
"prop-types": "15.6.0",
"react-router": "4.2.0"
}
},
"react-scrollbar-size": {
"version": "2.1.0",
......
import * as React from 'react';
import { renderToString } from 'react-dom/server';
import {RouterContext, browserHistory, match} from 'react-router';
import { createLocation } from 'history';
import { Provider } from 'react-redux';
import * as serialize from 'serialize-javascript';
import { routerMiddleware } from 'react-router-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from 'reducers';
// import * as React from 'react';
// import { renderToString } from 'react-dom/server';
// import {RouterContext, browserHistory, match} from 'react-router';
// import { createLocation } from 'history';
// import { Provider } from 'react-redux';
// import * as serialize from 'serialize-javascript';
// import { routerMiddleware } from 'react-router-redux';
// import { createStore, applyMiddleware } from 'redux';
// import thunk from 'redux-thunk';
// import rootReducer from 'reducers';
// import { create as createJss } from 'jss';
// import jssPreset from 'jss-preset-default';
import { JssProvider, SheetsRegistry } from 'react-jss';
import createGenerateClassName from 'material-ui/styles/createGenerateClassName';
import { MuiThemeProvider } from 'material-ui/styles';
import theme from 'ui/theme';
import routes from 'ui/routes';
import fetchComponentData from './fetchComponentData';
import checkAuthToken from './checkAuthToken';
// import { JssProvider, SheetsRegistry } from 'react-jss';
// import createGenerateClassName from 'material-ui/styles/createGenerateClassName';
// import { MuiThemeProvider } from 'material-ui/styles';
// import theme from 'ui/theme';
//
// import routes from 'ui/routes';
// import fetchComponentData from './fetchComponentData';
// import checkAuthToken from './checkAuthToken';
const prerenderer = (html) => (req, res) => {
console.log('Init prerenderer, request url:', req.url);
const startTime = Date.now();
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
const location = createLocation(req.url);
const store = createStore(rootReducer, {} as any, applyMiddleware(thunk, routerMiddleware(browserHistory)));
match({routes, location}, (err, redirectLocation, renderProps) => {
console.log('Init react-router match method, location:', location);
if (err) {
console.error(err);
return res.status(500).end('Internal server error');
}
if (!renderProps) {
console.error('No render props!!!');
return res.status(404).end('Not found');
}
if (redirectLocation) {
console.log('Redirected to', redirectLocation);
return res.redirect(redirectLocation).end();
}
function renderView() {
// const jss = createJss(jssPreset());
const sheets = new SheetsRegistry();
const generateClassName = createGenerateClassName();
const InitialView = (
<Provider store={ store }>
<JssProvider generateClassName={ generateClassName } registry={ sheets }>
<MuiThemeProvider theme={ theme } sheetsManager={ new Map() }>
<RouterContext { ...renderProps } />
</MuiThemeProvider>
</JssProvider>
</ Provider >
);
const componentHTML = renderToString(InitialView);
const initialState = store.getState();
const titleState = store.getState().pageTitle.title;
if (process.env.DEBUG) {
console.log('serverCSS:', sheets.toString());
console.log('componentHTML:', componentHTML);
console.log('initialState:', initialState);
}
return html
.replace('SERVER_RENDERED_CSS', sheets.toString())
.replace('SERVER_RENDERED_STATE', serialize(initialState, {isJSON: true}))
.replace('SERVER_RENDERED_HTML', componentHTML)
.replace('SERVER_RENDERED_TITLE', titleState);
}
checkAuthToken(req, res, store.dispatch)
.then(() => {
console.log('Start fetchComponentData method');
return fetchComponentData(store.dispatch, renderProps.components, renderProps);
})
.then(() => {
console.log('Start renderView method');
return renderView();
})
.then((html) => {
const serverRenderTime = `${Date.now() - startTime}ms`;
console.log('Server render time:', startTime, Date.now(), serverRenderTime);
// console.log(html);
const r = html.replace('__SERVER_RENDER_TIME__', serverRenderTime);
// console.log(r);
return res.set('Content-Type', 'text/html').send(r);
})
.catch((err) => {
console.error('Error:', err);
res.status(500).end(err.message);
});
});
// console.log('Init prerenderer, request url:', req.url);
// const startTime = Date.now();
// res.header('Access-Control-Allow-Origin', '*');
// res.header('Access-Control-Allow-Headers', 'X-Requested-With');
// const location = createLocation(req.url);
// const store = createStore(rootReducer, {} as any, applyMiddleware(thunk, routerMiddleware(browserHistory)));
//
// match({routes, location}, (err, redirectLocation, renderProps) => {
// console.log('Init react-router match method, location:', location);
// if (err) {
// console.error(err);
// return res.status(500).end('Internal server error');
// }
//
// if (!renderProps) {
// console.error('No render props!!!');
// return res.status(404).end('Not found');
// }
//
// if (redirectLocation) {
// console.log('Redirected to', redirectLocation);
// return res.redirect(redirectLocation).end();
// }
//
// function renderView() {
// // const jss = createJss(jssPreset());
// const sheets = new SheetsRegistry();
// const generateClassName = createGenerateClassName();
//
// const InitialView = (
// <Provider store={ store }>
// <JssProvider generateClassName={ generateClassName } registry={ sheets }>
// <MuiThemeProvider theme={ theme } sheetsManager={ new Map() }>
// <RouterContext { ...renderProps } />
// </MuiThemeProvider>
// </JssProvider>
// </ Provider >
// );
//
// const componentHTML = renderToString(InitialView);
// const initialState = store.getState();
// const titleState = store.getState().pageTitle.title;
//
// if (process.env.DEBUG) {
// console.log('serverCSS:', sheets.toString());
// console.log('componentHTML:', componentHTML);
// console.log('initialState:', initialState);
// }
//
// return html
// .replace('SERVER_RENDERED_CSS', sheets.toString())
// .replace('SERVER_RENDERED_STATE', serialize(initialState, {isJSON: true}))
// .replace('SERVER_RENDERED_HTML', componentHTML)
// .replace('SERVER_RENDERED_TITLE', titleState);
// }
//
// checkAuthToken(req, res, store.dispatch)
// .then(() => {
// console.log('Start fetchComponentData method');
// return fetchComponentData(store.dispatch, renderProps.components, renderProps);
// })
// .then(() => {
// console.log('Start renderView method');
// return renderView();
// })
// .then((html) => {
// const serverRenderTime = `${Date.now() - startTime}ms`;
// console.log('Server render time:', startTime, Date.now(), serverRenderTime);
// // console.log(html);
// const r = html.replace('__SERVER_RENDER_TIME__', serverRenderTime);
// // console.log(r);
// return res.set('Content-Type', 'text/html').send(r);
// })
// .catch((err) => {
// console.error('Error:', err);
// res.status(500).end(err.message);
// });
// });
};
export default prerenderer;
import { UPDATE_HISTORY } from 'constants/history';
export const updateHistory = () => (dispatch) => {
dispatch({type: UPDATE_HISTORY});
export const updateHistory = (lastLocation: string) => (dispatch) => {
dispatch({type: UPDATE_HISTORY, payload: lastLocation});
};
import * as update from 'immutability-helper';
import { IReducerAction } from 'model/common.model';
import { UPDATE_HISTORY } from 'constants/history';
const INITIAL_STATE = {
const INITIAL_STATE: {
hasHistory: false,
lastLocation: string;
} = {
hasHistory: false,
lastLocation: '/',
};
export default function history(state = INITIAL_STATE, action: IReducerAction = {type: ''}) {
export default function history(state = INITIAL_STATE, action: { type?: string, payload?: any } = {type: '', payload: {}}) {
switch (action.type) {
case UPDATE_HISTORY: {
return update(state, {
hasHistory: {$set: true},
lastLocation: {$set: action.payload},
});
}
......
......@@ -7,6 +7,8 @@ import {logoutRequest, loginAppRequest} from 'actions/login';
import {setAppMounted} from 'actions/appMounted';
import {updateHistory} from 'actions/history';
import { withRouter } from 'react-router-dom';
// FIXME When we start supporting other languages
import TimeAgo from 'javascript-time-ago';
import * as en from 'javascript-time-ago/locale/en';
......@@ -14,6 +16,7 @@ TimeAgo.locale(en);
import Header from './layout/Header';
import Footer from './layout/Footer';
import Routes from './Routes';
interface IAppProps extends React.ClassAttributes<any> {
children: any;
......@@ -22,9 +25,8 @@ interface IAppProps extends React.ClassAttributes<any> {
location: any;
logoutRequest: () => Promise<any>;
loginAppRequest: () => Promise<any>;
hasHistory: boolean;
setAppMounted: () => void;
updateHistory: () => void;
updateHistory: (path: string) => void;
}
class App extends React.Component<IAppProps, any> {
......@@ -36,22 +38,22 @@ class App extends React.Component<IAppProps, any> {
}
public componentWillReceiveProps(nextProps) {
const {hasHistory, updateHistory} = this.props;
if (!hasHistory) {
if (this.props.location !== null && nextProps.location !== null) {
updateHistory();
const {updateHistory} = this.props;
if (this.props.location !== null && nextProps.location !== null) {
if (this.props.location !== nextProps.location) {
updateHistory(this.props.location.pathname);
}
}
}
public render() {
const {children, login, logoutRequest, loginAppRequest, router} = this.props;
const { login, logoutRequest, loginAppRequest, router} = this.props;
log('Rendering App view');
return (
<div>
<Header login={ login } logoutRequest={ logoutRequest }
router={ router } loginAppRequest={ loginAppRequest } />
{ children }
<Routes />
<Footer />
</div>
);
......@@ -59,7 +61,6 @@ class App extends React.Component<IAppProps, any> {
}
const mapStateToProps = (state) => ({
hasHistory: state.history.hasHistory,
// accessToken: state.login.access_token,
});
......@@ -70,4 +71,4 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
updateHistory,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(App);
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
import * as React from 'react';
import { Switch, Route } from 'react-router-dom';
import ContentHeader from 'ui/common/heading/ContentHeader';
import {NotFound} from './common/not-found';
import AuthorizedRouteComponent from './common/authorized/AuthorizedRouteComponent';
import {ROLE_USER, ROLE_ADMINISTRATOR} from 'constants/userRoles';
import WelcomePage from './pages/welcome';
import LoginPage from './pages/login/LoginPage';
import SearchPage from './pages/search/SearchPage';
import DescriptorBrowsePage from './pages/descriptor/BrowsePage';
import DescriptorDisplayPage from './pages/descriptor/DisplayPage';
import DescriptorEditPage from './pages/descriptor/EditPage';
import DescriptorListDisplayPage from './pages/descriptorlist/DisplayPage';
import DescriptorListEditPage from './pages/descriptorlist/EditPage';
import DescriptorListBrowsePage from './pages/descriptorlist/BrowsePage';
import PartnerBrowsePage from './pages/partner/BrowsePage';
import PartnerDisplayPage from './pages/partner/DisplayPage';
import PartnerEditPage from './pages/partner/EditPage';
import CropBrowsePage from './pages/crop/BrowsePage';
import CropEditPage from './pages/crop/EditPage';
import VocabularyBrowsePage from './pages/vocabulary/BrowsePage';
import VocabularyEditPage from './pages/vocabulary/EditPage';
import VocabularyDisplayPage from './pages/vocabulary/DisplayPage';
import AccessionsBrowsePage from './pages/genesys/BrowsePage';
import UserProfilePage from './pages/profile/ProfilePage';
import DashboardPage from './pages/dashboard/DashboardPage';
import MyDataPage from './pages/dashboard/MyDataPage';
import DatasetBrowsePage from './pages/dataset/BrowsePage';
import DatasetDisplayPage from './pages/dataset/DisplayPage';
import DatasetStepper from './pages/dataset/dataset-stepper';
import GuiTest from './pages/ui-design/Gui';
const Routes = () => (
<div>
<Switch>
<Route exact path="/" component={ WelcomePage }/>
<Route path="/partners" component={ Partners }/>
<Route path="/datasets" component={ Datasets }/>
<Route path="/descriptors" component={ Descriptors }/>
<Route path="/descriptorlists" component={ DescriptorList }/>
<Route path="/crops" component={ Crops }/>
<Route path="/vocabulary" component={ Vocabulary }/>
<Route path="/accessions" component={ Accessions }/>
<Route path="/search" component={ SearchPage } />
<Route path="/login" component={ LoginPage }/>
<Route path="/gui" component={ GuiTests }/>
<Route path="/dashboard" component={ Dashboard }/>
<Route path="/profile" component={ Profile }/>
<Route component={ NotFound }/>
</Switch>
</div>
);
const Datasets = ({match}) => (
<div>
<Switch>
<Route exact path={ `${match.url}` } render={ (props) => (
<div>
<ContentHeader title="Datasets" subTitle="Datasets published by Genesys partners"/>
<DatasetBrowsePage { ...props }/>
</div>
) } />
<Route path={ `