diff --git a/packages/frontend/src/components/App.tsx b/packages/frontend/src/components/App.tsx index dd2be52..4d5dcdb 100644 --- a/packages/frontend/src/components/App.tsx +++ b/packages/frontend/src/components/App.tsx @@ -6,9 +6,9 @@ import history from '../util/history'; import { FeatureFlagsProvider } from 'elite-feature-flags'; import { Configuration } from 'elite-types'; import { getConfiguration } from 'elite-configuration'; -import { APP_ROUTES } from '../util/routing'; +import { getAllRegisteredAppRoutes } from '../util/routing'; -// Files must be required for decorator to work +// Files must be required (early!) for decorator to work require('../components/pages/HomePage'); require('../components/pages/LinkPage'); @@ -18,7 +18,7 @@ export const AppComponent = () => ( - {APP_ROUTES.map((routeProps, index) => ( + {getAllRegisteredAppRoutes().map((routeProps, index) => ( ))} {/* Error 404 Fallback */} diff --git a/packages/frontend/src/components/pages/support/LinkDirectory.tsx b/packages/frontend/src/components/pages/support/LinkDirectory.tsx index 8180d69..fe5179c 100644 --- a/packages/frontend/src/components/pages/support/LinkDirectory.tsx +++ b/packages/frontend/src/components/pages/support/LinkDirectory.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; -import { APP_ROUTES, getLinkForPage, getLinkDisplayNameForPage } from '../../../util/routing'; +import { getLinkForRoute, getDisplayNameForRoute, getAllRegisteredAppRoutes } from '../../../util/routing'; export const LinkDirectory = () => (
    - {APP_ROUTES.map((route, index) => ( + {getAllRegisteredAppRoutes().map((route, index) => (
  • - {getLinkDisplayNameForPage(route)} + {getDisplayNameForRoute(route)}
  • ))}
diff --git a/packages/frontend/src/util/routing.tsx b/packages/frontend/src/util/routing.tsx index b659120..c4846b9 100644 --- a/packages/frontend/src/util/routing.tsx +++ b/packages/frontend/src/util/routing.tsx @@ -1,7 +1,24 @@ import * as React from 'react'; import { RouteProps } from 'react-router'; -// TODO: move to separate package +// If necessary, add support for: H.LocationDescriptor | ((location: H.Location) => H.LocationDescriptor); +type LinkType = string; + +/** + * Each Approute can have a specific link (i.e., path with filled parameter placeholders), + * a display Name, i.e., text of the link and a nonoptional (!) path + */ +export interface AppRouteProps extends RouteProps { + // Use this if the link target differs from the path specification, + // i.e., if the path url contains paramter specifications etc + readonly link?: LinkType; + + // link text (Human readable!) + readonly displayName?: string; + + // AppRoutes must have a path - deoptionalize this property + readonly path: string; +} /** * The Routed decorator automatically creates a route for @@ -9,32 +26,41 @@ import { RouteProps } from 'react-router'; * * @param props route properties */ -export function Routed(props: AppRouteProps) { - console.log('producing route decorator:', props); - return (constructor: any) => { - APP_ROUTES.push({ +export function Routed & { render: () => any }, P = any>( + props: AppRouteProps, +): (c: new (props: any) => T) => new (props: any) => T { + return constructor => ( + registerAppRoute({ render: p => React.createElement(constructor, p), ...props, - }); - console.log('added route for', constructor.name); - return constructor; - }; + }), + constructor + ); } -// If necessary, add support for: H.LocationDescriptor | ((location: H.Location) => H.LocationDescriptor); -type LinkType = string; +/** + * Container for all registered app routes + */ +const appRoutes: { [path: string]: AppRouteProps } = {}; -// TODO: Add documentation -export interface AppRouteProps extends RouteProps { - // Use this if the link target differs from the path specification, - // i.e., if the path url contains paramter specifications etc - readonly link?: LinkType; +/** + * Function to retrieve all currently registered app routes + */ +export function getAllRegisteredAppRoutes() { + return Object.values(appRoutes); +} - // link text (Human readable!) - readonly displayName?: string; +/** + * Function for registering a new App route + * @param props AppRouteProps. Note that a render() function must be provided + */ +export function registerAppRoute(props: AppRouteProps & Required>) { + if (appRoutes[props.path]) { + throw new Error(`ERROR: detected illegal duplicate app route ${props.path}`); + } - // AppRoutes must have a path - deoptionalize this property - readonly path: string; + appRoutes[props.path] = props; + console.log('added route ', props.path); } // TODO: replace with proper container/service class @@ -43,7 +69,7 @@ export interface AppRouteProps extends RouteProps { * to link to a certain route * @param route the route to link to */ -export function getLinkForPage(route: AppRouteProps): LinkType { +export function getLinkForRoute(route: AppRouteProps): LinkType { return route.link || route.path; } @@ -54,9 +80,6 @@ export function getLinkForPage(route: AppRouteProps): LinkType { * * @param route */ -export function getLinkDisplayNameForPage(route: AppRouteProps): string { - return route.displayName || getLinkForPage(route); +export function getDisplayNameForRoute(route: AppRouteProps): string { + return route.displayName || getLinkForRoute(route); } - -// TODO: replace with proper container/service class -export const APP_ROUTES: AppRouteProps[] = [];