From 0670d29457bee245bd86f3233e54c328e5ec9320 Mon Sep 17 00:00:00 2001 From: Vasili Karaev Date: Thu, 5 Aug 2021 18:27:50 +0300 Subject: [PATCH] Add routing --- package.json | 4 ++ routing/Route.ts | 15 ++++++++ routing/Router.tsx | 94 ++++++++++++++++++++++++++++++++++++++++++++++ routing/config.ts | 33 ++++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 routing/Route.ts create mode 100644 routing/Router.tsx create mode 100644 routing/config.ts diff --git a/package.json b/package.json index 1078c84..acca6fe 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,16 @@ "version": "0.1.0", "dependencies": { "@types/qs": "^6.9.7", + "@types/react": "^17.0.15", + "@types/react-router-dom": "^5.1.8", "@types/sprintf-js": "^1.1.2", "axios": "^0.21.1", "loglevel": "^1.7.1", "modern-css-reset": "^1.4.0", "qs": "^6.10.1", "ramda": "^0.27.1", + "react": "^17.0.2", + "react-router-dom": "^5.2.0", "sass-rem": "^3.0.0", "sprintf-js": "^1.1.2" } diff --git a/routing/Route.ts b/routing/Route.ts new file mode 100644 index 0000000..9a1bdee --- /dev/null +++ b/routing/Route.ts @@ -0,0 +1,15 @@ +import React from 'react' + +interface Route { + path: string + name?: string + redirect?: string + exact?: boolean + navbar?: boolean + isIndex?: boolean + icon?: React.ComponentType + childRoutes?: Route[] + component?: React.ComponentType +} + +export default Route diff --git a/routing/Router.tsx b/routing/Router.tsx new file mode 100644 index 0000000..ca85189 --- /dev/null +++ b/routing/Router.tsx @@ -0,0 +1,94 @@ +import React from 'react' +import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom' +import { prepareRoutes } from './config' +import RouteDefinition from './Route' + +const getComponentRoute = (contextPath: string, component: React.ComponentType) => ( + +) + +const renderRouteConfig = ( + Container: React.ComponentType, + routes: RouteDefinition[], + contextPath: string, +): JSX.Element => { + // Resolve route config object in React Router v3. + const children: React.ReactNode[] = [] + + const renderRoute = (item: RouteDefinition, routeContextPath: string) => { + let newContextPath: string + + if (/(^\/)|(^\*)/.test(item.path)) { + newContextPath = item.path + } else { + newContextPath = `${routeContextPath}/${item.path}` + } + + newContextPath = newContextPath.replace(/\/+/g, '/') + + if (item.redirect) { + const route = ( + } + path={newContextPath} + /> + ) + + children.push(route) + } else if (item.component && item.childRoutes) { + const routeConfig = renderRouteConfig(item.component, item.childRoutes, newContextPath) + + children.push(routeConfig) + } else if (item.component) { + const route = getComponentRoute(newContextPath, item.component) + + children.push(route) + } else if (item.childRoutes) { + item.childRoutes.forEach(r => renderRoute(r, newContextPath)) + } + } + + routes.forEach(item => renderRoute(item, contextPath)) + + // Use Switch as the default container by default + if (!Container) { + return ( + + {children as JSX.TChildren[]} + + ) + } + + return ( + + + + {children as JSX.TChildren[]} + + + + ) +} + +interface Props { + routeConfig: RouteDefinition[] + component: React.ComponentType + baseUrlPath: string +} + +const Router: React.FC = (props: Props) => { + const { routeConfig, component, baseUrlPath } = props + + const preparedRoutes = prepareRoutes(routeConfig) + + return renderRouteConfig(component, preparedRoutes, baseUrlPath) +} + +export default Router diff --git a/routing/config.ts b/routing/config.ts new file mode 100644 index 0000000..434be7c --- /dev/null +++ b/routing/config.ts @@ -0,0 +1,33 @@ +import * as R from 'ramda' +import Route from './Route' + +// Handle isIndex property of route config: +// Duplicate it and put it as the first route rule. +const handleIndexRoute = (route: Route) => { + if (!route.childRoutes || !route.childRoutes.length) { + return + } + + const indexRoute = route.childRoutes.find(R.prop('isIndex')) + + if (indexRoute) { + const first = { + ...indexRoute, + path: route.path, + exact: true, + } + + route.childRoutes.unshift(first) + } + + route.childRoutes.forEach(handleIndexRoute) +} + +export const prepareRoutes: (route: Route[]) => Route[] = R.pipe( + R.filter((r: Route): boolean => Boolean( + r.redirect + || r.component + || (r.childRoutes && r.childRoutes.length > 0), + )), + R.forEach(handleIndexRoute), +)