Add routing

This commit is contained in:
Vasili Karaev 2021-08-05 18:27:50 +03:00
parent 18d32055ba
commit 0670d29457
4 changed files with 146 additions and 0 deletions

View File

@ -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"
}

15
routing/Route.ts Normal file
View File

@ -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

94
routing/Router.tsx Normal file
View File

@ -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) => (
<Route
exact
key={contextPath}
component={component}
path={contextPath}
/>
)
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 = (
<Route
exact
key={newContextPath}
render={() => <Redirect to={item.redirect as string} />}
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 (
<Switch>
{children as JSX.TChildren[]}
</Switch>
)
}
return (
<BrowserRouter>
<Container key={contextPath}>
<Switch>
{children as JSX.TChildren[]}
</Switch>
</Container>
</BrowserRouter>
)
}
interface Props {
routeConfig: RouteDefinition[]
component: React.ComponentType
baseUrlPath: string
}
const Router: React.FC<Props> = (props: Props) => {
const { routeConfig, component, baseUrlPath } = props
const preparedRoutes = prepareRoutes(routeConfig)
return renderRouteConfig(component, preparedRoutes, baseUrlPath)
}
export default Router

33
routing/config.ts Normal file
View File

@ -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),
)