1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import * as m from 'mithril'; 16 17import {Actions, DeferredAction} from '../common/actions'; 18 19interface RouteMap { 20 [route: string]: m.Component; 21} 22 23export const ROUTE_PREFIX = '#!'; 24 25export class Router { 26 constructor( 27 private defaultRoute: string, private routes: RouteMap, 28 private dispatch: (a: DeferredAction) => void) { 29 if (!(defaultRoute in routes)) { 30 throw Error('routes must define a component for defaultRoute.'); 31 } 32 33 window.onhashchange = () => this.navigateToCurrentHash(); 34 } 35 36 /** 37 * Parses and returns the current route string from |window.location.hash|. 38 * May return routes that are not defined in |this.routes|. 39 */ 40 getRouteFromHash(): string { 41 const prefixLength = ROUTE_PREFIX.length; 42 const hash = window.location.hash; 43 44 // Do not try to parse route if prefix doesn't match. 45 if (hash.substring(0, prefixLength) !== ROUTE_PREFIX) return ''; 46 47 return hash.split('?')[0].substring(prefixLength); 48 } 49 50 /** 51 * Sets |route| on |window.location.hash|. If |route| if not defined in 52 * |this.routes|, dispatches a navigation to |this.defaultRoute|. 53 */ 54 setRouteOnHash(route: string) { 55 history.pushState(undefined, "", ROUTE_PREFIX + route); 56 57 if (!(route in this.routes)) { 58 console.info( 59 `Route ${route} not known redirecting to ${this.defaultRoute}.`); 60 this.dispatch(Actions.navigate({route: this.defaultRoute})); 61 } 62 } 63 64 /** 65 * Dispatches navigation action to |this.getRouteFromHash()| if that is 66 * defined in |this.routes|, otherwise to |this.defaultRoute|. 67 */ 68 navigateToCurrentHash() { 69 const hashRoute = this.getRouteFromHash(); 70 const newRoute = hashRoute in this.routes ? hashRoute : this.defaultRoute; 71 this.dispatch(Actions.navigate({route: newRoute})); 72 // TODO(dproy): Handle case when new route has a permalink. 73 } 74 75 /** 76 * Returns the component for given |route|. If |route| is not defined, returns 77 * component of |this.defaultRoute|. 78 */ 79 resolve(route: string|null): m.Component { 80 if (!route || !(route in this.routes)) { 81 return this.routes[this.defaultRoute]; 82 } 83 return this.routes[route]; 84 } 85 86 static param(key: string) { 87 const hash = window.location.hash; 88 const paramStart = hash.indexOf('?'); 89 if (paramStart === -1) return undefined; 90 return m.parseQueryString(hash.substring(paramStart))[key]; 91 } 92} 93