@nocobase/client-v2 2.0.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +172 -0
- package/lib/Application.d.ts +124 -0
- package/lib/Application.js +489 -0
- package/lib/MockApplication.d.ts +16 -0
- package/lib/MockApplication.js +96 -0
- package/lib/Plugin.d.ts +33 -0
- package/lib/Plugin.js +89 -0
- package/lib/PluginManager.d.ts +46 -0
- package/lib/PluginManager.js +114 -0
- package/lib/PluginSettingsManager.d.ts +67 -0
- package/lib/PluginSettingsManager.js +148 -0
- package/lib/RouterManager.d.ts +61 -0
- package/lib/RouterManager.js +198 -0
- package/lib/WebSocketClient.d.ts +45 -0
- package/lib/WebSocketClient.js +217 -0
- package/lib/components/BlankComponent.d.ts +12 -0
- package/lib/components/BlankComponent.js +48 -0
- package/lib/components/MainComponent.d.ts +10 -0
- package/lib/components/MainComponent.js +54 -0
- package/lib/components/RouterBridge.d.ts +13 -0
- package/lib/components/RouterBridge.js +66 -0
- package/lib/components/RouterContextCleaner.d.ts +12 -0
- package/lib/components/RouterContextCleaner.js +61 -0
- package/lib/components/index.d.ts +10 -0
- package/lib/components/index.js +32 -0
- package/lib/context.d.ts +11 -0
- package/lib/context.js +38 -0
- package/lib/hooks/index.d.ts +11 -0
- package/lib/hooks/index.js +34 -0
- package/lib/hooks/useApp.d.ts +10 -0
- package/lib/hooks/useApp.js +41 -0
- package/lib/hooks/usePlugin.d.ts +11 -0
- package/lib/hooks/usePlugin.js +42 -0
- package/lib/hooks/useRouter.d.ts +9 -0
- package/lib/hooks/useRouter.js +41 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.js +40 -0
- package/lib/utils/index.d.ts +11 -0
- package/lib/utils/index.js +79 -0
- package/lib/utils/remotePlugins.d.ts +44 -0
- package/lib/utils/remotePlugins.js +131 -0
- package/lib/utils/requirejs.d.ts +18 -0
- package/lib/utils/requirejs.js +1361 -0
- package/lib/utils/types.d.ts +330 -0
- package/lib/utils/types.js +28 -0
- package/package.json +16 -0
- package/src/Application.tsx +539 -0
- package/src/MockApplication.tsx +53 -0
- package/src/Plugin.ts +78 -0
- package/src/PluginManager.ts +114 -0
- package/src/PluginSettingsManager.ts +182 -0
- package/src/RouterManager.tsx +239 -0
- package/src/WebSocketClient.ts +220 -0
- package/src/__tests__/app.test.tsx +141 -0
- package/src/components/BlankComponent.tsx +12 -0
- package/src/components/MainComponent.tsx +20 -0
- package/src/components/RouterBridge.tsx +38 -0
- package/src/components/RouterContextCleaner.tsx +26 -0
- package/src/components/index.ts +11 -0
- package/src/context.ts +14 -0
- package/src/hooks/index.ts +12 -0
- package/src/hooks/useApp.ts +16 -0
- package/src/hooks/usePlugin.ts +17 -0
- package/src/hooks/useRouter.ts +15 -0
- package/src/index.ts +15 -0
- package/src/utils/index.tsx +48 -0
- package/src/utils/remotePlugins.ts +140 -0
- package/src/utils/requirejs.ts +2164 -0
- package/src/utils/types.ts +375 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Application } from './Application';
|
|
11
|
+
import type { Plugin } from './Plugin';
|
|
12
|
+
import { getPlugins } from './utils/remotePlugins';
|
|
13
|
+
|
|
14
|
+
export type PluginOptions<T = any> = { name?: string; packageName?: string; config?: T };
|
|
15
|
+
export type PluginType<Opts = any> = typeof Plugin | [typeof Plugin<Opts>, PluginOptions<Opts>];
|
|
16
|
+
export type PluginData = {
|
|
17
|
+
name: string;
|
|
18
|
+
packageName: string;
|
|
19
|
+
version: string;
|
|
20
|
+
url: string;
|
|
21
|
+
type: 'local' | 'upload' | 'npm';
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class PluginManager {
|
|
25
|
+
protected pluginInstances: Map<typeof Plugin, Plugin> = new Map();
|
|
26
|
+
protected pluginsAliases: Record<string, Plugin> = {};
|
|
27
|
+
private initPlugins: Promise<void>;
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
protected _plugins: PluginType[],
|
|
31
|
+
protected loadRemotePlugins: boolean,
|
|
32
|
+
protected app: Application,
|
|
33
|
+
) {
|
|
34
|
+
this.app = app;
|
|
35
|
+
this.initPlugins = this.init(_plugins);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
async init(_plugins: PluginType[]) {
|
|
42
|
+
await this.initStaticPlugins(_plugins);
|
|
43
|
+
if (this.loadRemotePlugins) {
|
|
44
|
+
await this.initRemotePlugins();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private async initStaticPlugins(_plugins: PluginType[] = []) {
|
|
49
|
+
for await (const plugin of _plugins) {
|
|
50
|
+
const pluginClass = Array.isArray(plugin) ? plugin[0] : plugin;
|
|
51
|
+
const opts = Array.isArray(plugin) ? plugin[1] : undefined;
|
|
52
|
+
await this.add(pluginClass, opts);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async initRemotePlugins() {
|
|
57
|
+
const res = await this.app.apiClient.request({ url: 'pm:listEnabled' });
|
|
58
|
+
const pluginList: PluginData[] = res?.data?.data || [];
|
|
59
|
+
const plugins = await getPlugins({
|
|
60
|
+
requirejs: this.app.requirejs,
|
|
61
|
+
pluginData: pluginList,
|
|
62
|
+
devDynamicImport: this.app.devDynamicImport,
|
|
63
|
+
});
|
|
64
|
+
for await (const [name, pluginClass] of plugins) {
|
|
65
|
+
const info = pluginList.find((item) => item.name === name);
|
|
66
|
+
await this.add(pluginClass, info);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async add<T = any>(plugin: typeof Plugin, opts: PluginOptions<T> = {}) {
|
|
71
|
+
const instance = this.getInstance(plugin, opts);
|
|
72
|
+
|
|
73
|
+
this.pluginInstances.set(plugin, instance);
|
|
74
|
+
|
|
75
|
+
if (opts.name) {
|
|
76
|
+
this.pluginsAliases[opts.name] = instance;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (opts.packageName) {
|
|
80
|
+
this.pluginsAliases[opts.packageName] = instance;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await instance.afterAdd();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get<T extends typeof Plugin>(PluginClass: T): InstanceType<T>;
|
|
87
|
+
get<T extends {}>(name: string): T;
|
|
88
|
+
get(nameOrPluginClass: any) {
|
|
89
|
+
if (typeof nameOrPluginClass === 'string') {
|
|
90
|
+
return this.pluginsAliases[nameOrPluginClass];
|
|
91
|
+
}
|
|
92
|
+
return this.pluginInstances.get(nameOrPluginClass.default || nameOrPluginClass);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private getInstance<T>(plugin: typeof Plugin, opts?: T) {
|
|
96
|
+
return new plugin(opts, this.app);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @internal
|
|
101
|
+
*/
|
|
102
|
+
async load() {
|
|
103
|
+
await this.initPlugins;
|
|
104
|
+
|
|
105
|
+
for (const plugin of this.pluginInstances.values()) {
|
|
106
|
+
await plugin.beforeLoad();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const plugin of this.pluginInstances.values()) {
|
|
110
|
+
await plugin.load();
|
|
111
|
+
this.app.eventBus.dispatchEvent(new CustomEvent(`plugin:${plugin.options.name}:loaded`, { detail: plugin }));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { set } from 'lodash';
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { Outlet } from 'react-router-dom';
|
|
13
|
+
|
|
14
|
+
import type { Application } from './Application';
|
|
15
|
+
import type { RouteType } from './RouterManager';
|
|
16
|
+
|
|
17
|
+
export const ADMIN_SETTINGS_KEY = 'admin.settings.';
|
|
18
|
+
export const ADMIN_SETTINGS_PATH = '/admin/settings/';
|
|
19
|
+
export const SNIPPET_PREFIX = 'pm.';
|
|
20
|
+
|
|
21
|
+
export interface PluginSettingOptions {
|
|
22
|
+
title: any;
|
|
23
|
+
/**
|
|
24
|
+
* @default Outlet
|
|
25
|
+
*/
|
|
26
|
+
Component?: RouteType['Component'];
|
|
27
|
+
icon?: string;
|
|
28
|
+
/**
|
|
29
|
+
* sort, the smaller the number, the higher the priority
|
|
30
|
+
* @default 0
|
|
31
|
+
*/
|
|
32
|
+
sort?: number;
|
|
33
|
+
aclSnippet?: string;
|
|
34
|
+
link?: string;
|
|
35
|
+
isTopLevel?: boolean;
|
|
36
|
+
isPinned?: boolean;
|
|
37
|
+
[index: string]: any;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PluginSettingsPageType {
|
|
41
|
+
label?: string | React.ReactElement;
|
|
42
|
+
title: string | React.ReactElement;
|
|
43
|
+
link?: string;
|
|
44
|
+
key: string;
|
|
45
|
+
icon: any;
|
|
46
|
+
path: string;
|
|
47
|
+
sort?: number;
|
|
48
|
+
name?: string;
|
|
49
|
+
isAllow?: boolean;
|
|
50
|
+
topLevelName?: string;
|
|
51
|
+
aclSnippet: string;
|
|
52
|
+
children?: PluginSettingsPageType[];
|
|
53
|
+
[index: string]: any;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class PluginSettingsManager {
|
|
57
|
+
protected settings: Record<string, PluginSettingOptions> = {};
|
|
58
|
+
protected aclSnippets: string[] = [];
|
|
59
|
+
public app: Application;
|
|
60
|
+
private cachedList = {};
|
|
61
|
+
|
|
62
|
+
constructor(_pluginSettings: Record<string, PluginSettingOptions>, app: Application) {
|
|
63
|
+
this.app = app;
|
|
64
|
+
Object.entries(_pluginSettings || {}).forEach(([name, pluginSettingOptions]) => {
|
|
65
|
+
this.add(name, pluginSettingOptions);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
clearCache() {
|
|
70
|
+
this.cachedList = {};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
setAclSnippets(aclSnippets: string[]) {
|
|
74
|
+
this.aclSnippets = aclSnippets;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getAclSnippet(name: string) {
|
|
78
|
+
const setting = this.settings[name];
|
|
79
|
+
if (setting?.skipAclConfigure) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return setting?.aclSnippet ? setting.aclSnippet : `${SNIPPET_PREFIX}${name}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getRouteName(name: string) {
|
|
86
|
+
return `${ADMIN_SETTINGS_KEY}${name}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getRoutePath(name: string) {
|
|
90
|
+
return `${ADMIN_SETTINGS_PATH}${name.replaceAll('.', '/')}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
add(name: string, options: PluginSettingOptions) {
|
|
94
|
+
const nameArr = name.split('.');
|
|
95
|
+
const topLevelName = nameArr[0];
|
|
96
|
+
this.settings[name] = {
|
|
97
|
+
...this.settings[name],
|
|
98
|
+
Component: Outlet,
|
|
99
|
+
...options,
|
|
100
|
+
name,
|
|
101
|
+
topLevelName: options.topLevelName || topLevelName,
|
|
102
|
+
};
|
|
103
|
+
// add children
|
|
104
|
+
if (nameArr.length > 1) {
|
|
105
|
+
set(this.settings, nameArr.join('.children.'), this.settings[name]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// add route
|
|
109
|
+
this.app.router.add(this.getRouteName(name), {
|
|
110
|
+
path: this.getRoutePath(name),
|
|
111
|
+
Component: this.settings[name].Component,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
remove(name: string) {
|
|
116
|
+
// delete self and children
|
|
117
|
+
Object.keys(this.settings).forEach((key) => {
|
|
118
|
+
if (key.startsWith(name)) {
|
|
119
|
+
delete this.settings[key];
|
|
120
|
+
this.app.router.remove(`${ADMIN_SETTINGS_KEY}${key}`);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
hasAuth(name: string) {
|
|
126
|
+
if (this.aclSnippets.includes(`!${this.getAclSnippet('*')}`)) return false;
|
|
127
|
+
return this.aclSnippets.includes(`!${this.getAclSnippet(name)}`) === false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getSetting(name: string) {
|
|
131
|
+
return this.settings[name];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
has(name: string) {
|
|
135
|
+
const hasAuth = this.hasAuth(name);
|
|
136
|
+
if (!hasAuth) return false;
|
|
137
|
+
return !!this.getSetting(name);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get(name: string, filterAuth = true): PluginSettingsPageType {
|
|
141
|
+
const isAllow = this.hasAuth(name);
|
|
142
|
+
const pluginSetting = this.getSetting(name);
|
|
143
|
+
if ((filterAuth && !isAllow) || !pluginSetting) return null;
|
|
144
|
+
const children = Object.keys(pluginSetting.children || {})
|
|
145
|
+
.sort((a, b) => a.localeCompare(b)) // sort by name
|
|
146
|
+
.map((key) => this.get(pluginSetting.children[key].name, filterAuth))
|
|
147
|
+
.filter(Boolean)
|
|
148
|
+
.sort((a, b) => (a.sort || 0) - (b.sort || 0));
|
|
149
|
+
const { title, icon, aclSnippet, ...others } = pluginSetting;
|
|
150
|
+
return {
|
|
151
|
+
isTopLevel: name === pluginSetting.topLevelName,
|
|
152
|
+
...others,
|
|
153
|
+
aclSnippet: this.getAclSnippet(name),
|
|
154
|
+
title,
|
|
155
|
+
isAllow,
|
|
156
|
+
label: title,
|
|
157
|
+
icon: this.app.flowEngine.context.renderIon?.(icon),
|
|
158
|
+
path: this.getRoutePath(name),
|
|
159
|
+
key: name,
|
|
160
|
+
children: children.length ? children : undefined,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
getList(filterAuth = true): PluginSettingsPageType[] {
|
|
165
|
+
const cacheKey = JSON.stringify(filterAuth);
|
|
166
|
+
if (this.cachedList[cacheKey]) return this.cachedList[cacheKey];
|
|
167
|
+
|
|
168
|
+
return (this.cachedList[cacheKey] = Array.from(
|
|
169
|
+
new Set(Object.values(this.settings).map((item) => item.topLevelName)),
|
|
170
|
+
)
|
|
171
|
+
.sort((a, b) => a.localeCompare(b)) // sort by name
|
|
172
|
+
.map((name) => this.get(name, filterAuth))
|
|
173
|
+
.filter(Boolean)
|
|
174
|
+
.sort((a, b) => (a.sort || 0) - (b.sort || 0)));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
getAclSnippets() {
|
|
178
|
+
return Object.keys(this.settings)
|
|
179
|
+
.map((name) => this.getAclSnippet(name))
|
|
180
|
+
.filter(Boolean);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { get, set } from 'lodash';
|
|
11
|
+
import React, { ComponentType, createContext, useContext } from 'react';
|
|
12
|
+
import { matchRoutes, useParams } from 'react-router';
|
|
13
|
+
import {
|
|
14
|
+
type BrowserRouterProps,
|
|
15
|
+
createBrowserRouter,
|
|
16
|
+
createHashRouter,
|
|
17
|
+
createMemoryRouter,
|
|
18
|
+
type HashRouterProps,
|
|
19
|
+
type MemoryRouterProps,
|
|
20
|
+
Outlet,
|
|
21
|
+
type RouteObject,
|
|
22
|
+
RouterProvider,
|
|
23
|
+
useRouteError,
|
|
24
|
+
} from 'react-router-dom';
|
|
25
|
+
import { Application } from './Application';
|
|
26
|
+
import { BlankComponent, RouterContextCleaner } from './components';
|
|
27
|
+
import { RouterBridge } from './components/RouterBridge';
|
|
28
|
+
|
|
29
|
+
export interface BrowserRouterOptions extends Omit<BrowserRouterProps, 'children'> {
|
|
30
|
+
type?: 'browser';
|
|
31
|
+
}
|
|
32
|
+
export interface HashRouterOptions extends Omit<HashRouterProps, 'children'> {
|
|
33
|
+
type?: 'hash';
|
|
34
|
+
}
|
|
35
|
+
export interface MemoryRouterOptions extends Omit<MemoryRouterProps, 'children'> {
|
|
36
|
+
type?: 'memory';
|
|
37
|
+
}
|
|
38
|
+
export type RouterOptions = (HashRouterOptions | BrowserRouterOptions | MemoryRouterOptions) & {
|
|
39
|
+
renderComponent?: RenderComponentType;
|
|
40
|
+
routes?: Record<string, RouteType>;
|
|
41
|
+
};
|
|
42
|
+
export type ComponentTypeAndString<T = any> = ComponentType<T> | string;
|
|
43
|
+
export interface RouteType extends Omit<RouteObject, 'children' | 'Component'> {
|
|
44
|
+
Component?: ComponentTypeAndString;
|
|
45
|
+
skipAuthCheck?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export type RenderComponentType = (Component: ComponentTypeAndString, props?: any) => React.ReactNode;
|
|
48
|
+
|
|
49
|
+
export class RouterManager {
|
|
50
|
+
protected routes: Record<string, RouteType> = {};
|
|
51
|
+
protected options: RouterOptions;
|
|
52
|
+
public app: Application;
|
|
53
|
+
public router;
|
|
54
|
+
get basename() {
|
|
55
|
+
return this.router.basename;
|
|
56
|
+
}
|
|
57
|
+
get state() {
|
|
58
|
+
return this.router.state;
|
|
59
|
+
}
|
|
60
|
+
get navigate() {
|
|
61
|
+
return this.router.navigate;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
constructor(options: RouterOptions = {}, app: Application) {
|
|
65
|
+
this.options = options;
|
|
66
|
+
this.app = app;
|
|
67
|
+
this.routes = options.routes || {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @internal
|
|
72
|
+
*/
|
|
73
|
+
getRoutesTree(): RouteObject[] {
|
|
74
|
+
type RouteTypeWithChildren = RouteType & { children?: RouteTypeWithChildren };
|
|
75
|
+
const routes: Record<string, RouteTypeWithChildren> = {};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* { 'a': { name: '1' }, 'a.b': { name: '2' }, 'a.c': { name: '3' } };
|
|
79
|
+
* =>
|
|
80
|
+
* { a: { name: '1', children: { b: { name: '2' }, c: {name: '3'} } } }
|
|
81
|
+
*/
|
|
82
|
+
for (const [name, route] of Object.entries(this.routes)) {
|
|
83
|
+
set(routes, name.split('.').join('.children.'), { ...get(routes, name.split('.').join('.children.')), ...route });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* get RouteObject tree
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* { a: { name: '1', children: { b: { name: '2' }, c: { children: { d: { name: '3' } } } } } }
|
|
91
|
+
* =>
|
|
92
|
+
* { name: '1', children: [{ name: '2' }, { name: '3' }] }
|
|
93
|
+
*/
|
|
94
|
+
const buildRoutesTree = (routes: RouteTypeWithChildren): RouteObject[] => {
|
|
95
|
+
return Object.values(routes).reduce<RouteObject[]>((acc, item) => {
|
|
96
|
+
if (Object.keys(item).length === 1 && item.children) {
|
|
97
|
+
acc.push(...buildRoutesTree(item.children));
|
|
98
|
+
} else {
|
|
99
|
+
const { Component, element, children, ...reset } = item;
|
|
100
|
+
let ele = element;
|
|
101
|
+
if (Component) {
|
|
102
|
+
if (typeof Component === 'string') {
|
|
103
|
+
ele = this.app.renderComponent(Component);
|
|
104
|
+
} else {
|
|
105
|
+
ele = React.createElement(Component);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const res = {
|
|
109
|
+
...reset,
|
|
110
|
+
element: ele,
|
|
111
|
+
children: children ? buildRoutesTree(children) : undefined,
|
|
112
|
+
} as RouteObject;
|
|
113
|
+
acc.push(res);
|
|
114
|
+
}
|
|
115
|
+
return acc;
|
|
116
|
+
}, []);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return buildRoutesTree(routes);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getRoutes() {
|
|
123
|
+
return this.routes;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
setType(type: RouterOptions['type']) {
|
|
127
|
+
this.options.type = type;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getBasename() {
|
|
131
|
+
return this.options.basename;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setBasename(basename: string) {
|
|
135
|
+
this.options.basename = basename;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
matchRoutes(pathname: string) {
|
|
139
|
+
const routes = Object.values(this.routes);
|
|
140
|
+
// @ts-ignore
|
|
141
|
+
return matchRoutes<RouteType>(routes, pathname, this.basename);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
isSkippedAuthCheckRoute(pathname: string) {
|
|
145
|
+
const matchedRoutes = this.matchRoutes(pathname);
|
|
146
|
+
return matchedRoutes.some((match) => {
|
|
147
|
+
return match?.route?.skipAuthCheck === true;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* @internal
|
|
152
|
+
*/
|
|
153
|
+
getRouterComponent(children?: React.ReactNode) {
|
|
154
|
+
const { type = 'browser', ...opts } = this.options;
|
|
155
|
+
|
|
156
|
+
const routerCreators = {
|
|
157
|
+
hash: createHashRouter,
|
|
158
|
+
browser: createBrowserRouter,
|
|
159
|
+
memory: createMemoryRouter,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const routes = this.getRoutesTree();
|
|
163
|
+
|
|
164
|
+
const BaseLayoutContext = createContext<ComponentType>(null);
|
|
165
|
+
|
|
166
|
+
const Provider = () => {
|
|
167
|
+
const BaseLayout = useContext(BaseLayoutContext);
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
<RouterBridge app={this.app} />
|
|
171
|
+
<BaseLayout>
|
|
172
|
+
<Outlet />
|
|
173
|
+
{children}
|
|
174
|
+
</BaseLayout>
|
|
175
|
+
</>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// bubble up error to application error boundary
|
|
180
|
+
const ErrorElement = () => {
|
|
181
|
+
const error = useRouteError();
|
|
182
|
+
throw error;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
this.router = routerCreators[type](
|
|
186
|
+
[
|
|
187
|
+
{
|
|
188
|
+
element: <Provider />,
|
|
189
|
+
errorElement: <ErrorElement />,
|
|
190
|
+
children: routes,
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
opts,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const RenderRouter: React.FC<{ BaseLayout?: ComponentType }> = ({ BaseLayout = BlankComponent }) => {
|
|
197
|
+
return (
|
|
198
|
+
<BaseLayoutContext.Provider value={BaseLayout}>
|
|
199
|
+
<RouterContextCleaner>
|
|
200
|
+
<RouterProvider
|
|
201
|
+
future={{
|
|
202
|
+
v7_startTransition: true,
|
|
203
|
+
}}
|
|
204
|
+
router={this.router}
|
|
205
|
+
/>
|
|
206
|
+
</RouterContextCleaner>
|
|
207
|
+
</BaseLayoutContext.Provider>
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
return RenderRouter;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
add(name: string, route: RouteType) {
|
|
215
|
+
this.routes[name] = {
|
|
216
|
+
id: name,
|
|
217
|
+
...route,
|
|
218
|
+
handle: {
|
|
219
|
+
path: route.path,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
get(name: string) {
|
|
225
|
+
return this.routes[name];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
has(name: string) {
|
|
229
|
+
return !!this.get(name);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
remove(name: string) {
|
|
233
|
+
delete this.routes[name];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function createRouterManager(options?: RouterOptions, app?: Application) {
|
|
238
|
+
return new RouterManager(options, app);
|
|
239
|
+
}
|