import React from 'react'; import R from 'ramda'; import {Actions} from 'flummox'; import matchRoute from '../util/matchRoute'; import {log, levels as logLevels} from '../util/log'; import jsonApiRequest from '../di/jsonApiRequest'; const logger = log('RouterActions'); const debugFn = logger(logLevels.debug); const logFn = logger(logLevels.log); const routes = [ ['/shop/items/:id', 'ShopItemPage'] ]; function createRouteHandler(Handler, props) { let routerHandler; if (Handler.routerHandler) { routerHandler = () => { return Handler.routerHandler(props); }; } return class RouteHandler extends React.Component { static routerHandler = routerHandler; render() { return ; } }; } const notFoundFallback = R.curry(function notFoundFallback(getter, type, isArray, jsonApiResponse) { if (type === 'Shop::Item' && !isArray) { return createRouteHandler(getter('ShopItemPage'), { id: jsonApiResponse.data.id }); } return getter('NotFoundPage'); }); const matchHandlerByController = R.curry(function matchHandlerByController(getter, controller, action, props) { if (controller === 'items' && action === 'show') { return createRouteHandler(getter('ShopItemPage'), { id: props.id }); } return null; }); const matchHandlerByUrl = R.curry(function matchHandlerByUrl(getter, url) { const route = routes .map(([pattern, handlerName]) => { const props = matchRoute(pattern, url); return props ? {handlerName, props} : null; }) .filter(result => !!result) .shift(); if (!route) { return null; } return createRouteHandler(getter(route.handlerName), route.props); }); export default class RouterActions extends Actions { constructor(flux) { super(); window.addEventListener('popstate', ({state}) => this.handlePopState({state})); this.flux = flux; this.getter = (key) => { logFn(`Matched handler "${key}"`); return flux.getStore('router').getPage(key); }; this.notFoundFallback = notFoundFallback(this.getter); this.matchHandlerByController = matchHandlerByController(this.getter); this.matchHandlerByUrl = matchHandlerByUrl(this.getter); } fill(pages) { return pages; } async initialize({controller, action, props}) { const url = [document.location.pathname, document.location.search].join(''); let handler; window.history.replaceState({url}, document.title); if (controller && action) { handler = this.matchHandlerByController(controller, action, props || {}); } else { handler = this.matchHandlerByUrl(url); } if (!handler) { handler = (await this.handleNotFound({url})).handler; } return {url, handler}; } async navigateTo({url}) { if (this.flux.getStore('router').getUrl() === url) { return {url, handler: this.flux.getStore('router').getHandler()}; } let loading = false; let cached = false; let handler; if ((handler = this.flux.getStore('router').getHandlerForUrl(url))) { cached = true; } else { handler = this.matchHandlerByUrl(url); } if (!handler) { loading = true; this.setLoading({loading: true}); handler = (await this.handleNotFound({url})).handler; } if (handler.routerHandler && !cached) { if (!loading) { loading = true; this.setLoading({loading: true}); } await handler.routerHandler(); } window.history.pushState({url}, handler.windowTitle || document.title, url); if (loading) { this.setLoading({loading: false}); } return {url, handler}; } async handleNotFound({url}) { try { url += url.indexOf('?') >= 0 ? '&' : '?'; url += '_random=' + Math.random(); const jsonApiResponse = await jsonApiRequest(url, { method: 'GET' }); let handler; if (jsonApiResponse && jsonApiResponse.data) { const isArray = Array.isArray(jsonApiResponse.data); const item = isArray ? jsonApiResponse.data[0] : jsonApiResponse.data; if (item) { handler = this.notFoundFallback(item.type, isArray, jsonApiResponse); } } if (!handler) { handler = this.getter('NotFoundPage'); } return {jsonApiResponse, url, handler}; } catch (error) { this.flux.getActions('error').handleServerError({error}); return {error, url}; } } async handlePopState({state}) { if (!state || !state.url) { window.location.reload(); return {}; } const {url} = state; let handler = this.flux.getStore('router').getHandlerForUrl(url) || this.matchHandlerByUrl(url); if (!handler) { this.setLoading({loading: true}); handler = (await this.handleNotFound({url})).handler; this.setLoading({loading: false}); } return {url, handler}; } setLoading({loading}) { return {loading}; } }