import { Indexed } from '@ledge/types'; import { ParamType, ResolveTypes, StateDeclaration, StateService, TargetState, Transition } from '@uirouter/core'; import { NgService } from './service'; export class NgRouter extends NgService { public routes: T[] = []; public getRoutes() { return this.routes; } public registerRoute(partial: Partial) { const state = { ...this.generateRouteMeta(partial), ...partial, }; if (this.isNgTransitionFn(state.onEnter)) { state.onEnter = ['$transition$', state.onEnter]; } if (this.isNgTransitionFn(state.onExit)) { state.onExit = ['$transition$', state.onExit]; } if (this.isNgTransitionFn(state.onRetain)) { state.onRetain = ['$transition$', state.onRetain]; } this.routes.push(state as T); return state as T; } public isAuthorized(): boolean | Promise { return true; } public generateIntIdParam() { const param = { type: new ParamType({ decode(value) { return parseInt(value, 10); }, encode(value) { return value && value.toString(); }, equals(a, b) { return this.is(a) && a === b; }, is(value) { const n = this.decode(value); return typeof n === 'number' && !isNaN(n); }, }), }; param.type.name = 'path'; return param; } public generateIntQueryParam() { const param = { type: new ParamType({ decode(value) { return parseInt(value, 10); }, encode(value) { return value && value.toString(); }, equals(a, b) { return this.is(a) && a === b; }, is(value) { const n = this.decode(value); return typeof n === 'number' && !isNaN(n); }, }), }; param.type.name = 'query'; return param; } protected isNgTransitionFn(item?: NgResolveFn | NgAnnotatedResolveFn): item is NgResolveFn { return typeof item === 'function'; } protected annotateResolveFunctions({ resolve = {} }: NgRoute) { for (const id of Object.keys(resolve)) { if (Array.isArray(resolve)) { continue; } const resolveFn = resolve[id]; if (this.isNgTransitionFn(resolveFn)) { resolve[id] = ['$transition$', resolveFn]; } } return resolve; } protected generateRouteMeta( { params = {}, data = {}, name = '', component = '', parent = '', }: Partial, ) { name = (name || component); let url = `/${data.isBase ? name : name.split(/(?=[A-Z])/).join('/').toLowerCase()}` .replace(/View$/, ''); for (const [key, { type = {} }] of Object.keys(params).map(x => ([x, params[x]]))) { url += type.name === 'path' ? '/:' : (url.indexOf('?') !== -1 ? '&' : '?'); url += key; } return { name, parent, url }; } } /** * @internalapi * an intermediate interface. * * Used to reset typings to `any` so the NgRoute interface can then narrow them */ interface $NgStateService$ extends StateService { current: any; } export interface NgStateService extends $NgStateService$ { current: NgRoute; label: string; parent: string; } /** * @internalapi * an intermediate interface. * * Used to reset typings to `any` so the NgRoute interface can then narrow them */ interface $NgRoute$ extends StateDeclaration { onExit?: any; onRetain?: any; onEnter?: any; views?: any; resolve?: any; } export type NgResolveFn = ($trans: Transition) => Promise; export type NgAnnotatedResolveFn = [string, NgResolveFn]; export type NgTransition = NgResolveFn | NgAnnotatedResolveFn; export interface NgRoute extends $NgRoute$ { /** * The name of the component to use for this view. */ component?: string; /** * An object which maps `resolve`s to component `bindings`. * * When using a component declaration (`component: 'myComponent'`), * each input binding for the component is supplied data from a resolve of * the same name, by default. * * You may supply data from a different resolve name by mapping it here. */ bindings?: { [key: string]: string; }; resolve?: Indexed | ResolveTypes[]; /** * Injected function which returns the HTML template. * The template will be used to render the corresponding component. * * #### Examples: * ```ts * { * // other props * template: require('./template.pug'); * } * ``` * ```ts * { * // other props * template: '
Hello, world!
'; * } * ``` */ template?: string | ((...args: any[]) => void); url: string; parent: string; label: string; onEnter?: NgTransition; onExit?: NgTransition; onRetain?: NgTransition; }