mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-08 15:09:27 +08:00
* refactor: optimize page tabs routing * test: add e2e test for page tabs * feat: add popup routing * fix: resolve nested issue * refactor: rename file utils to pagePopupUtils * perf: enhance animation and overall performance * fix: fix filterByTK * fix(sourceId): resolve error when sourceId is undefined * fix: fix List and GridCard * fix: fix params not fresh * fix: fix parent record * fix: resolve the issue on block data not refreshing after popup closure * feat: bind tab with URL in popups * feat(sub-page): enable popup to open in page mode * chore: optimize * feat: support association fields * fix: address the issue of no data in associaiton field * fix: resolve the issue with opening nested dialog in association field * fix: fix the issue of dialog content not refreshing * perf: use useNavigateNoUpdate to replace useNavigate * perf: enhance popups performance by avoiding unnecessary rendering * fix: fix tab page * fix: fix bulk edit action * chore: fix unit test * chore: fix unit tests * fix: fix bug to pass e2e tests * chore: fix build * fix: fix bugs to pass e2e tests * chore: avoid crashing * chore: make e2e tests pass * chore: make e2e tests pass * chore: fix unit tests * fix(multi-app): fix known issues * fix(Duplicate): should no page mode * chore: fix build * fix(mobile): fix known issues * fix: fix open mode of Add new * refactor: rename 'popupUid' to 'popupuid' * refactor: rename 'subPageUid' tp 'subpageuid' * refactor(subpage): simplify configuration of router * fix(variable): refresh data after value change * test: add e2e test for sub page * refactor: refactor and add tests * fix: fix association field * refactor(subPage): avoid blank page occurrences * chore: fix unit tests * fix: correct first-click context setting for association fields * refactor: use Action's uid for subpage * refactor: rename x-nb-popup-context to x-action-context and move it to Action schema * feat: add context during the creation of actions * chore: fix build * chore: make e2e tests pass * fix(addChild): fix context of Add child * fix: avoid loss or query string * fix: avoid including 'popups' in the path * fix: resolve issue with popup variables and add tests * chore(e2e): fix e2e test * fix(sideMenu): resolve the disappearing sidebar issue and add tests * chore(e2e): fix e2e test * fix: should refresh block data after mutiple popups closed * chore: fix e2e test * fix(associationField): fix wrong context * fix: address issue with special characters
181 lines
5.0 KiB
TypeScript
181 lines
5.0 KiB
TypeScript
/**
|
|
* This file is part of the NocoBase (R) project.
|
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
* Authors: NocoBase Team.
|
|
*
|
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
*/
|
|
|
|
import { get, set } from 'lodash';
|
|
import React, { ComponentType } from 'react';
|
|
import {
|
|
BrowserRouter,
|
|
BrowserRouterProps,
|
|
HashRouter,
|
|
HashRouterProps,
|
|
MemoryRouter,
|
|
MemoryRouterProps,
|
|
RouteObject,
|
|
useRoutes,
|
|
} from 'react-router-dom';
|
|
import { Application } from './Application';
|
|
import { CustomRouterContextProvider } from './CustomRouterContextProvider';
|
|
import { BlankComponent, RouterContextCleaner } from './components';
|
|
|
|
export interface BrowserRouterOptions extends Omit<BrowserRouterProps, 'children'> {
|
|
type?: 'browser';
|
|
}
|
|
export interface HashRouterOptions extends Omit<HashRouterProps, 'children'> {
|
|
type?: 'hash';
|
|
}
|
|
export interface MemoryRouterOptions extends Omit<MemoryRouterProps, 'children'> {
|
|
type?: 'memory';
|
|
}
|
|
export type RouterOptions = (HashRouterOptions | BrowserRouterOptions | MemoryRouterOptions) & {
|
|
renderComponent?: RenderComponentType;
|
|
routes?: Record<string, RouteType>;
|
|
};
|
|
export type ComponentTypeAndString<T = any> = ComponentType<T> | string;
|
|
export interface RouteType extends Omit<RouteObject, 'children' | 'Component'> {
|
|
Component?: ComponentTypeAndString;
|
|
}
|
|
export type RenderComponentType = (Component: ComponentTypeAndString, props?: any) => React.ReactNode;
|
|
|
|
export class RouterManager {
|
|
protected routes: Record<string, RouteType> = {};
|
|
protected options: RouterOptions;
|
|
public app: Application;
|
|
|
|
constructor(options: RouterOptions = {}, app: Application) {
|
|
this.options = options;
|
|
this.app = app;
|
|
this.routes = options.routes || {};
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
getRoutesTree(): RouteObject[] {
|
|
type RouteTypeWithChildren = RouteType & { children?: RouteTypeWithChildren };
|
|
const routes: Record<string, RouteTypeWithChildren> = {};
|
|
|
|
/**
|
|
* { 'a': { name: '1' }, 'a.b': { name: '2' }, 'a.c': { name: '3' } };
|
|
* =>
|
|
* { a: { name: '1', children: { b: { name: '2' }, c: {name: '3'} } } }
|
|
*/
|
|
for (const [name, route] of Object.entries(this.routes)) {
|
|
set(routes, name.split('.').join('.children.'), { ...get(routes, name.split('.').join('.children.')), ...route });
|
|
}
|
|
|
|
/**
|
|
* get RouteObject tree
|
|
*
|
|
* @example
|
|
* { a: { name: '1', children: { b: { name: '2' }, c: { children: { d: { name: '3' } } } } } }
|
|
* =>
|
|
* { name: '1', children: [{ name: '2' }, { name: '3' }] }
|
|
*/
|
|
const buildRoutesTree = (routes: RouteTypeWithChildren): RouteObject[] => {
|
|
return Object.values(routes).reduce<RouteObject[]>((acc, item) => {
|
|
if (Object.keys(item).length === 1 && item.children) {
|
|
acc.push(...buildRoutesTree(item.children));
|
|
} else {
|
|
const { Component, element, children, ...reset } = item;
|
|
let ele = element;
|
|
if (Component) {
|
|
if (typeof Component === 'string') {
|
|
ele = this.app.renderComponent(Component);
|
|
} else {
|
|
ele = React.createElement(Component);
|
|
}
|
|
}
|
|
const res = {
|
|
...reset,
|
|
element: ele,
|
|
children: children ? buildRoutesTree(children) : undefined,
|
|
} as RouteObject;
|
|
acc.push(res);
|
|
}
|
|
return acc;
|
|
}, []);
|
|
};
|
|
|
|
return buildRoutesTree(routes);
|
|
}
|
|
|
|
getRoutes() {
|
|
return this.routes;
|
|
}
|
|
|
|
setType(type: RouterOptions['type']) {
|
|
this.options.type = type;
|
|
}
|
|
|
|
getBasename() {
|
|
return this.options.basename;
|
|
}
|
|
|
|
setBasename(basename: string) {
|
|
this.options.basename = basename;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
getRouterComponent(children?: React.ReactNode) {
|
|
const { type = 'browser', ...opts } = this.options;
|
|
const Routers = {
|
|
hash: HashRouter,
|
|
browser: BrowserRouter,
|
|
memory: MemoryRouter,
|
|
};
|
|
|
|
const ReactRouter = Routers[type];
|
|
const routes = this.getRoutesTree();
|
|
|
|
const RenderRoutes = () => {
|
|
const element = useRoutes(routes);
|
|
return element;
|
|
};
|
|
|
|
const RenderRouter: React.FC<{ BaseLayout?: ComponentType }> = ({ BaseLayout = BlankComponent }) => {
|
|
return (
|
|
<RouterContextCleaner>
|
|
<ReactRouter {...opts}>
|
|
<CustomRouterContextProvider>
|
|
<BaseLayout>
|
|
<RenderRoutes />
|
|
{children}
|
|
</BaseLayout>
|
|
</CustomRouterContextProvider>
|
|
</ReactRouter>
|
|
</RouterContextCleaner>
|
|
);
|
|
};
|
|
|
|
return RenderRouter;
|
|
}
|
|
|
|
add(name: string, route: RouteType) {
|
|
this.routes[name] = route;
|
|
}
|
|
|
|
get(name: string) {
|
|
return this.routes[name];
|
|
}
|
|
|
|
has(name: string) {
|
|
return !!this.get(name);
|
|
}
|
|
|
|
remove(name: string) {
|
|
delete this.routes[name];
|
|
}
|
|
}
|
|
|
|
export function createRouterManager(options?: RouterOptions, app?: Application) {
|
|
return new RouterManager(options, app);
|
|
}
|