client/app/services/routes.ts (53 lines of code) (raw):
import { isString, isObject, filter, sortBy } from "lodash";
import React from "react";
import { Context, Route as UniversalRouterRoute } from "universal-router";
import pathToRegexp from "path-to-regexp";
export interface CurrentRoute<P> {
id: string | null;
key?: string;
title: string;
routeParams: P;
}
export interface RedashRoute<P = {}, C extends Context = Context, R = any> extends UniversalRouterRoute<C, R> {
path: string; // we don't use other UniversalRouterRoute options, path should be available and should be a string
key?: string; // generated in Router.jsx
title: string;
render?: (currentRoute: CurrentRoute<P>) => React.ReactNode;
getApiKey?: () => string;
}
interface RouteItem extends RedashRoute<any> {
id: string | null;
}
function getRouteParamsCount(path: string) {
const tokens = pathToRegexp.parse(path);
return filter(tokens, isObject).length;
}
class Routes {
_items: RouteItem[] = [];
_sorted = false;
get items(): RouteItem[] {
if (!this._sorted) {
this._items = sortBy(this._items, [
item => getRouteParamsCount(item.path), // simple definitions first, with more params - last
item => -item.path.length, // longer first
item => item.path, // if same type and length - sort alphabetically
]);
this._sorted = true;
}
return this._items;
}
public register<P>(id: string, route: RedashRoute<P>) {
const idOrNull = isString(id) ? id : null;
this.unregister(idOrNull);
if (isObject(route)) {
this._items = [...this.items, { ...route, id: idOrNull }];
this._sorted = false;
}
}
public unregister(id: string | null) {
if (isString(id)) {
// removing item does not break their order (if already sorted)
this._items = filter(this.items, item => item.id !== id);
}
}
}
export default new Routes();