/* eslint-env browser */ import { createBrowserHistory, createHashHistory, createMemoryHistory, } from 'history'; import React from 'react'; import { NavigationActions, getNavigation, NavigationProvider, } from '@react-navigation/core'; /* eslint-disable import/no-commonjs */ const queryString = require('query-string'); const getPathAndParamsFromLocation = location => { const path = encodeURI(location.pathname.substr(1)); const params = queryString.parse(location.search); return { path, params }; }; const matchPathAndParams = (a, b) => { if (a.path !== b.path) { return false; } if (queryString.stringify(a.params) !== queryString.stringify(b.params)) { return false; } return true; }; function getHistory(history) { if (typeof history === 'string') { switch (history) { case 'browser': return createBrowserHistory(); case 'hash': return createHashHistory(); case 'memory': return createMemoryHistory(); default: throw new Error( '@react-navigation/web: createBrowserApp() Invalid value for options.history ' + history ); } } return history || createBrowserHistory(); } export default function createBrowserApp(App, { history: historyOption } = {}) { const history = getHistory(historyOption); let currentPathAndParams = getPathAndParamsFromLocation(history.location); const initAction = App.router.getActionForPathAndParams( currentPathAndParams.path, currentPathAndParams.params ) || NavigationActions.init(); const setHistoryListener = dispatch => { history.listen(location => { const pathAndParams = getPathAndParamsFromLocation(location); if (matchPathAndParams(pathAndParams, currentPathAndParams)) { return; } currentPathAndParams = pathAndParams; const action = App.router.getActionForPathAndParams( pathAndParams.path, pathAndParams.params ); if (action) { dispatch(action); } else { dispatch(initAction); } }); }; class WebApp extends React.Component { state = { nav: App.router.getStateForAction(initAction) }; _title = document.title; _actionEventSubscribers = new Set(); componentDidMount() { setHistoryListener(this.dispatch); this.updateTitle(); this._actionEventSubscribers.forEach(subscriber => subscriber({ type: 'action', action: initAction, state: this.state.nav, lastState: null, }) ); } componentDidUpdate() { this.updateTitle(); } updateTitle() { const { state } = this._navigation; const childKey = state.routes[state.index].key; const activeNav = this._navigation.getChildNavigation(childKey); const opts = App.router.getScreenOptions(activeNav); this._title = opts.title || opts.headerTitle; if (this._title) { document.title = this._title; } } _onNavigationStateChange(prevNav, nav, action) { if (typeof this.props.onNavigationStateChange === 'function') { this.props.onNavigationStateChange(prevNav, nav, action); } } render() { this._navigation = getNavigation( App.router, this.state.nav, this.dispatch, this._actionEventSubscribers, () => this.props.screenProps, () => this._navigation ); return ( <NavigationProvider value={this._navigation}> <App {...this.props} navigation={this._navigation} /> </NavigationProvider> ); } dispatch = action => { const lastState = this.state.nav; const newState = App.router.getStateForAction(action, lastState); const dispatchEvents = () => this._actionEventSubscribers.forEach(subscriber => subscriber({ type: 'action', action, state: newState, lastState, }) ); if (newState && newState !== lastState) { this.setState({ nav: newState }, () => { this._onNavigationStateChange(lastState, newState, action); dispatchEvents(); }); const pathAndParams = App.router.getPathAndParamsForState && App.router.getPathAndParamsForState(newState); if ( pathAndParams && !matchPathAndParams(pathAndParams, currentPathAndParams) ) { currentPathAndParams = pathAndParams; history.push( `/${pathAndParams.path}?${queryString.stringify( pathAndParams.params )}` ); } } else { dispatchEvents(); } }; } return WebApp; }