/* global WP_DEFINE_IS_NODE */
import React, { Children } from 'react';
import createMemoryHistory from 'react-router/lib/createMemoryHistory';
import debugLib from 'debug';
const debug = debugLib('SlimmingWorld:queryRouting');
/**
* Creates a memoryHistory instance for a query parameter with query routing. Listens for changes
* in the main history and syncs the query parameter to the created query history.
*
* This is only called in a web build. On the server side we don't do any further navigation after
* first render, so we aren't interested in the history.
* @param mainHistory {Object} The main history object
* @param queryParam {string} A query parameter to create a history for
* @returns {Object} A history object for the query parameter
*/
const createQueryHistory = (mainHistory, queryParam) => {
const history = createMemoryHistory();
history.listen(({ action, pathname, search, hash }) => {
const location = mainHistory.getCurrentLocation();
debug(`Navigate within [${queryParam}]: { action: "${action}", pathname: "${pathname}"}`);
if (action) {
if (action === 'POP') {
mainHistory.goBack();
} else if (location.query[queryParam] !== pathname) {
// custom 'escape' to navigate with the main router instead of the query routing
// technically this conflicts with the normal '../' path navigation, but since we
// only use the Pages enum, we can 'reserve' that pattern for this use case.
if (pathname.startsWith('../')) {
mainHistory[action.toLowerCase()]({
pathname: pathname.slice(2),
search,
hash,
});
} else {
// apply the pathname to the query while keeping the normal location the same
mainHistory[action.toLowerCase()]({
...location,
query: {
...(location.query || {}),
[queryParam]: pathname,
},
});
}
}
}
});
return history;
};
/**
* Processes the <QueryRouting> configuration nested inside the routing config. Will
* create history instances for each defined <QueryRouting>.
* @function processQueryRouting
* @param route The current route. This function will be called recursively
* @param mainHistory The history instance of the main routing
*/
const processQueryRouting = (route, mainHistory) => {
const queryRoutes = {};
// handle null children
if (!route) {
return route;
}
// We build a new array of children which we use to clone a new <Route> component below
const newChildren =
(route.props && route.props.children && Children.toArray(route.props.children)) || null;
if (newChildren) {
// we walk through the children in reverse because we might remove children in the loop
for (let i = newChildren.length - 1; i >= 0; i--) {
if (newChildren[i].type.displayName === 'QueryRouting') {
// child is <QueryRouting />. Process the config and remove the child
const queryParam = newChildren[i].props.query;
const history = WP_DEFINE_IS_NODE ? null : createQueryHistory(mainHistory, queryParam);
// save the config on the queryRoutes object, which will be attached as a prop on the
// <Route> Component below
queryRoutes[queryParam] = {
routes: newChildren[i],
history,
};
// remove the <QueryRouting> child. react-router does not understand this
newChildren.splice(i, 1);
} else {
// child is a normal <Route />. Process that route recursively
newChildren[i] = processQueryRouting(newChildren[i], mainHistory);
}
}
}
return React.cloneElement(
route,
{
queryRoutes,
},
newChildren,
);
};
export default processQueryRouting;