| // Copyright (C) 2018 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import * as m from 'mithril'; |
| |
| import {Actions, DeferredAction} from '../common/actions'; |
| |
| interface RouteMap { |
| [route: string]: m.Component; |
| } |
| |
| export const ROUTE_PREFIX = '#!'; |
| |
| export class Router { |
| constructor( |
| private defaultRoute: string, private routes: RouteMap, |
| private dispatch: (a: DeferredAction) => void) { |
| if (!(defaultRoute in routes)) { |
| throw Error('routes must define a component for defaultRoute.'); |
| } |
| |
| window.onhashchange = () => this.navigateToCurrentHash(); |
| } |
| |
| /** |
| * Parses and returns the current route string from |window.location.hash|. |
| * May return routes that are not defined in |this.routes|. |
| */ |
| getRouteFromHash(): string { |
| const prefixLength = ROUTE_PREFIX.length; |
| const hash = window.location.hash; |
| |
| // Do not try to parse route if prefix doesn't match. |
| if (hash.substring(0, prefixLength) !== ROUTE_PREFIX) return ''; |
| |
| return hash.split('?')[0].substring(prefixLength); |
| } |
| |
| /** |
| * Sets |route| on |window.location.hash|. If |route| if not defined in |
| * |this.routes|, dispatches a navigation to |this.defaultRoute|. |
| */ |
| setRouteOnHash(route: string) { |
| history.pushState(undefined, "", ROUTE_PREFIX + route); |
| |
| if (!(route in this.routes)) { |
| console.info( |
| `Route ${route} not known redirecting to ${this.defaultRoute}.`); |
| this.dispatch(Actions.navigate({route: this.defaultRoute})); |
| } |
| } |
| |
| /** |
| * Dispatches navigation action to |this.getRouteFromHash()| if that is |
| * defined in |this.routes|, otherwise to |this.defaultRoute|. |
| */ |
| navigateToCurrentHash() { |
| const hashRoute = this.getRouteFromHash(); |
| const newRoute = hashRoute in this.routes ? hashRoute : this.defaultRoute; |
| this.dispatch(Actions.navigate({route: newRoute})); |
| // TODO(dproy): Handle case when new route has a permalink. |
| } |
| |
| /** |
| * Returns the component for given |route|. If |route| is not defined, returns |
| * component of |this.defaultRoute|. |
| */ |
| resolve(route: string|null): m.Component { |
| if (!route || !(route in this.routes)) { |
| return this.routes[this.defaultRoute]; |
| } |
| return this.routes[route]; |
| } |
| |
| static param(key: string) { |
| const hash = window.location.hash; |
| const paramStart = hash.indexOf('?'); |
| if (paramStart === -1) return undefined; |
| return m.parseQueryString(hash.substring(paramStart))[key]; |
| } |
| } |