Deepanjan Roy | 112ff6a | 2018-09-10 08:31:43 -0400 | [diff] [blame] | 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 | |
| 15 | import * as m from 'mithril'; |
| 16 | |
Hector Dearman | 4d8d73d | 2018-10-04 13:16:29 +0100 | [diff] [blame] | 17 | import {Actions, DeferredAction} from '../common/actions'; |
Deepanjan Roy | 112ff6a | 2018-09-10 08:31:43 -0400 | [diff] [blame] | 18 | |
| 19 | interface RouteMap { |
| 20 | [route: string]: m.Component; |
| 21 | } |
| 22 | |
| 23 | export const ROUTE_PREFIX = '#!'; |
| 24 | |
| 25 | export class Router { |
| 26 | constructor( |
| 27 | private defaultRoute: string, private routes: RouteMap, |
Hector Dearman | 4d8d73d | 2018-10-04 13:16:29 +0100 | [diff] [blame] | 28 | private dispatch: (a: DeferredAction) => void) { |
Deepanjan Roy | 112ff6a | 2018-09-10 08:31:43 -0400 | [diff] [blame] | 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, undefined, ROUTE_PREFIX + route); |
| 56 | |
| 57 | if (!(route in this.routes)) { |
Hector Dearman | 4544f37 | 2018-09-19 12:32:36 +0100 | [diff] [blame] | 58 | console.info( |
| 59 | `Route ${route} not known redirecting to ${this.defaultRoute}.`); |
Hector Dearman | 4d8d73d | 2018-10-04 13:16:29 +0100 | [diff] [blame] | 60 | this.dispatch(Actions.navigate({route: this.defaultRoute})); |
Deepanjan Roy | 112ff6a | 2018-09-10 08:31:43 -0400 | [diff] [blame] | 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; |
Hector Dearman | 4d8d73d | 2018-10-04 13:16:29 +0100 | [diff] [blame] | 71 | this.dispatch(Actions.navigate({route: newRoute})); |
Deepanjan Roy | 112ff6a | 2018-09-10 08:31:43 -0400 | [diff] [blame] | 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 | 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 | } |