@nocobase/client-v2 2.1.0-beta.37 → 2.1.0-beta.38

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.
Files changed (123) hide show
  1. package/es/Application.d.ts +1 -0
  2. package/es/BaseApplication.d.ts +3 -0
  3. package/es/RouterManager.d.ts +1 -0
  4. package/es/components/KeepAlive.d.ts +22 -0
  5. package/es/components/RouterBridge.d.ts +9 -0
  6. package/es/data-source/ExtendCollectionsProvider.d.ts +28 -2
  7. package/es/flow/FlowPage.d.ts +2 -1
  8. package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
  9. package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
  10. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
  11. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
  12. package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
  13. package/es/flow/admin-shell/admin-layout/index.d.ts +2 -0
  14. package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
  15. package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
  16. package/es/flow/components/FlowRoute.d.ts +10 -1
  17. package/es/flow/index.d.ts +4 -0
  18. package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
  19. package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
  20. package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
  21. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
  22. package/es/index.d.ts +1 -0
  23. package/es/index.mjs +484 -437
  24. package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
  25. package/es/layout-manager/LayoutManager.d.ts +22 -0
  26. package/es/layout-manager/LayoutRoute.d.ts +14 -0
  27. package/es/layout-manager/index.d.ts +13 -0
  28. package/es/layout-manager/types.d.ts +20 -0
  29. package/es/layout-manager/utils.d.ts +14 -0
  30. package/es/settings-center/index.d.ts +1 -1
  31. package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
  32. package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
  33. package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
  34. package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
  35. package/es/settings-center/plugin-manager/types.d.ts +34 -0
  36. package/lib/index.js +484 -437
  37. package/package.json +8 -7
  38. package/src/Application.tsx +27 -12
  39. package/src/BaseApplication.tsx +6 -0
  40. package/src/PluginSettingsManager.ts +1 -1
  41. package/src/RouterManager.tsx +17 -1
  42. package/src/__tests__/PluginSettingsManager.test.ts +41 -2
  43. package/src/__tests__/app.test.tsx +8 -1
  44. package/src/__tests__/globalDeps.test.ts +1 -0
  45. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +45 -2
  46. package/src/__tests__/plugin-manager.test.tsx +177 -0
  47. package/src/__tests__/settings-center.test.tsx +24 -2
  48. package/src/components/KeepAlive.tsx +131 -0
  49. package/src/components/RouterBridge.tsx +28 -4
  50. package/src/components/__tests__/KeepAlive.test.tsx +63 -0
  51. package/src/components/__tests__/RouterBridge.test.tsx +27 -0
  52. package/src/data-source/ExtendCollectionsProvider.tsx +94 -20
  53. package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
  54. package/src/flow/FlowPage.tsx +35 -7
  55. package/src/flow/__tests__/FlowPage.test.tsx +79 -0
  56. package/src/flow/__tests__/FlowRoute.test.tsx +529 -2
  57. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
  58. package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
  59. package/src/flow/actions/aclCheck.tsx +4 -0
  60. package/src/flow/actions/aclCheckRefresh.tsx +4 -0
  61. package/src/flow/actions/dateTimeFormat.tsx +12 -8
  62. package/src/flow/actions/linkageRules.tsx +122 -0
  63. package/src/flow/actions/openView.tsx +28 -4
  64. package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
  65. package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
  66. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
  67. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
  68. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +4 -4
  69. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
  70. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +0 -12
  71. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
  72. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
  73. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +1 -26
  74. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
  75. package/src/flow/admin-shell/admin-layout/index.ts +2 -0
  76. package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
  77. package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
  78. package/src/flow/components/AdminLayout.tsx +4 -154
  79. package/src/flow/components/FlowRoute.tsx +105 -15
  80. package/src/flow/index.ts +4 -0
  81. package/src/flow/models/base/ActionModel.tsx +8 -1
  82. package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
  83. package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
  84. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
  85. package/src/flow/models/base/RouteModel.tsx +1 -1
  86. package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
  87. package/src/flow/models/blocks/form/FormItemModel.tsx +8 -1
  88. package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
  89. package/src/flow/models/blocks/form/submitValues.ts +4 -1
  90. package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
  91. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
  92. package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
  93. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
  94. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
  95. package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
  96. package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
  97. package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
  98. package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
  99. package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
  100. package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
  101. package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
  102. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
  103. package/src/index.ts +1 -0
  104. package/src/layout-manager/LayoutContentRoute.tsx +90 -0
  105. package/src/layout-manager/LayoutManager.tsx +185 -0
  106. package/src/layout-manager/LayoutRoute.tsx +138 -0
  107. package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
  108. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
  109. package/src/layout-manager/index.ts +14 -0
  110. package/src/layout-manager/types.ts +22 -0
  111. package/src/layout-manager/utils.ts +37 -0
  112. package/src/nocobase-buildin-plugin/index.tsx +56 -48
  113. package/src/settings-center/index.ts +1 -1
  114. package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
  115. package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
  116. package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
  117. package/src/settings-center/plugin-manager/index.tsx +254 -0
  118. package/src/settings-center/plugin-manager/types.ts +35 -0
  119. package/src/settings-center/utils.tsx +8 -1
  120. package/src/theme/__tests__/globalStyles.test.ts +24 -0
  121. package/src/theme/globalStyles.ts +10 -0
  122. package/src/utils/globalDeps.ts +2 -0
  123. package/src/settings-center/PluginManagerPage.tsx +0 -162
@@ -7,339 +7,21 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import {
11
- FlowEngine,
12
- FlowModel,
13
- observable,
14
- parsePathnameToViewParams,
15
- ViewNavigation,
16
- type ViewParam,
17
- } from '@nocobase/flow-engine';
18
- import React from 'react';
19
- import { getViewDiffAndUpdateHidden, getKey } from '../getViewDiffAndUpdateHidden';
20
- import { getOpenViewStepParams } from '../flows/openViewFlow';
21
- import { resolveViewParamsToViewList, updateViewListHidden, type ViewItem } from '../resolveViewParamsToViewList';
22
-
23
- export interface RoutePageMeta {
24
- active: boolean;
25
- refreshDesktopRoutes?: () => Promise<unknown>;
26
- layoutContentElement?: HTMLElement | null;
27
- }
28
-
29
- interface ViewRuntimeState {
30
- destroy: (force?: boolean) => void;
31
- update: (value: any) => void;
32
- navigation: ViewNavigation;
33
- }
34
-
35
- interface RoutePageRuntime {
36
- pageUid: string;
37
- routeModel: FlowModel;
38
- meta: RoutePageMeta;
39
- viewState: Record<string, ViewRuntimeState>;
40
- prevViewList: ViewItem[];
41
- hasStepNavigated: boolean;
42
- forceStop: boolean;
43
- }
44
-
45
- interface RouteLike {
46
- params?: { name?: string };
47
- pathname?: string;
48
- }
49
-
50
- const hasUsableSourceId = (sourceId: unknown) => sourceId !== undefined && sourceId !== null && String(sourceId) !== '';
10
+ import type { FlowEngine } from '@nocobase/flow-engine';
11
+ import { BaseLayoutRouteCoordinator, type BaseLayoutRouteCoordinatorOptions } from './BaseLayoutRouteCoordinator';
51
12
 
52
13
  /**
53
- * 管理 admin 场景下每个 page 的 v2 视图栈编排。
54
- * 该协调器只负责状态机和开关视图,不直接绑定 React 生命周期。
14
+ * Admin Layout 路由协调器。
15
+ *
16
+ * 该类保留旧导出路径,内部复用通用 BaseLayoutRouteCoordinator。
55
17
  */
56
- export class AdminLayoutRouteCoordinator {
57
- private readonly flowEngine: FlowEngine;
58
- private readonly runtimes = new Map<string, RoutePageRuntime>();
59
- private layoutContentElement: HTMLElement | null = null;
60
-
61
- private setRuntimeActive(runtime: RoutePageRuntime, active: boolean) {
62
- runtime.meta.active = active;
63
- if (runtime.routeModel.context.pageActive?.value !== active) {
64
- runtime.routeModel.context.pageActive.value = active;
65
- }
66
- }
67
-
68
- private getCurrentRouteByPageUid(pageUid: string) {
69
- return this.flowEngine.context.routeRepository?.getRouteBySchemaUid?.(pageUid) || {};
70
- }
71
-
72
- constructor(flowEngine: FlowEngine) {
73
- this.flowEngine = flowEngine;
74
- }
75
-
76
- setLayoutContentElement(element: HTMLElement | null) {
77
- this.layoutContentElement = element;
78
- }
79
-
80
- registerPage(pageUid: string, meta: RoutePageMeta) {
81
- const routeModel = this.getOrCreateRouteModel(pageUid);
82
- const runtime: RoutePageRuntime = {
83
- pageUid,
84
- routeModel,
85
- meta: {
86
- active: !!meta.active,
87
- refreshDesktopRoutes: meta.refreshDesktopRoutes,
88
- layoutContentElement: meta.layoutContentElement || null,
89
- },
90
- viewState: {},
91
- prevViewList: [],
92
- hasStepNavigated: false,
93
- forceStop: false,
94
- };
95
-
96
- this.ensureRouteModelContext(runtime);
97
- this.runtimes.set(pageUid, runtime);
98
- this.syncPageMeta(pageUid, meta);
99
- this.syncRoute(this.flowEngine.context.route || {});
100
-
101
- return runtime.routeModel;
102
- }
103
-
104
- syncPageMeta(pageUid: string, meta: Partial<RoutePageMeta>) {
105
- const runtime = this.runtimes.get(pageUid);
106
- if (!runtime) {
107
- return;
108
- }
109
-
110
- runtime.meta = {
111
- ...runtime.meta,
112
- ...meta,
113
- active: typeof meta.active === 'boolean' ? meta.active : runtime.meta.active,
114
- layoutContentElement: meta.layoutContentElement ?? runtime.meta.layoutContentElement,
115
- };
116
-
117
- this.setRuntimeActive(runtime, runtime.meta.active);
118
- }
119
-
120
- unregisterPage(pageUid: string) {
121
- this.cleanupPage(pageUid);
122
- this.runtimes.delete(pageUid);
123
- }
124
-
125
- syncRoute(routeLike: RouteLike) {
126
- const activePageUid = routeLike?.params?.name;
127
- const pathname = routeLike?.pathname;
128
-
129
- this.runtimes.forEach((runtime) => {
130
- this.setRuntimeActive(runtime, runtime.pageUid === activePageUid);
131
- });
132
-
133
- if (!activePageUid || !pathname) {
134
- return;
135
- }
136
-
137
- this.runtimes.forEach((runtime) => {
138
- if (runtime.pageUid !== activePageUid) {
139
- runtime.forceStop = true;
140
- }
141
- });
142
-
143
- const runtime = this.runtimes.get(activePageUid);
144
- if (!runtime) {
145
- return;
146
- }
147
-
148
- runtime.forceStop = false;
149
- this.syncRuntimeWithPathname(runtime, pathname);
150
- }
151
-
152
- cleanupPage(pageUid: string) {
153
- const runtime = this.runtimes.get(pageUid);
154
- if (!runtime) {
155
- return;
156
- }
157
-
158
- runtime.forceStop = true;
159
- runtime.prevViewList.forEach((viewItem) => {
160
- this.flowEngine.removeModelWithSubModels(viewItem.params.viewUid);
161
- runtime.viewState[getKey(viewItem)]?.destroy?.();
162
- delete runtime.viewState[getKey(viewItem)];
18
+ export class AdminLayoutRouteCoordinator extends BaseLayoutRouteCoordinator {
19
+ constructor(flowEngine: FlowEngine, options: BaseLayoutRouteCoordinatorOptions = {}) {
20
+ super(flowEngine, {
21
+ basePathname: '/admin',
22
+ ...options,
163
23
  });
164
- runtime.prevViewList = [];
165
- runtime.hasStepNavigated = false;
166
- }
167
-
168
- destroy() {
169
- Array.from(this.runtimes.keys()).forEach((pageUid) => {
170
- this.cleanupPage(pageUid);
171
- this.runtimes.delete(pageUid);
172
- });
173
- }
174
-
175
- private syncRuntimeWithPathname(runtime: RoutePageRuntime, pathname: string) {
176
- try {
177
- const viewStack = parsePathnameToViewParams(pathname);
178
- const viewList = resolveViewParamsToViewList(this.flowEngine, viewStack, runtime.routeModel);
179
-
180
- if (this.shouldStepNavigate(runtime, viewList)) {
181
- this.stepNavigate(viewList, 0);
182
- runtime.hasStepNavigated = true;
183
- return;
184
- }
185
-
186
- const { viewsToClose, viewsToOpen } = getViewDiffAndUpdateHidden(runtime.prevViewList, viewList);
187
-
188
- if (viewsToClose.length) {
189
- viewsToClose.forEach((viewItem) => {
190
- runtime.viewState[getKey(viewItem)]?.destroy?.(true);
191
- delete runtime.viewState[getKey(viewItem)];
192
- });
193
- updateViewListHidden(viewList);
194
- }
195
-
196
- if (viewsToOpen.length) {
197
- void this.handleOpenViews(runtime, viewList, viewsToOpen);
198
- }
199
-
200
- if (viewsToClose.length === 0 && viewsToOpen.length === 0) {
201
- const currentViewItem = viewList.at(-1);
202
- if (currentViewItem) {
203
- runtime.viewState[getKey(currentViewItem)]?.navigation.setViewStack(viewList.map((item) => item.params));
204
- }
205
- }
206
-
207
- runtime.prevViewList = [...viewList];
208
- } catch (error) {
209
- console.error(`[NocoBase] Failed to resolve view params to view list:`, error);
210
- }
211
- }
212
-
213
- private shouldStepNavigate(runtime: RoutePageRuntime, viewList: ViewItem[]) {
214
- return runtime.prevViewList.length === 0 && viewList.length > 1 && !runtime.hasStepNavigated;
215
- }
216
-
217
- private stepNavigate(viewList: ViewItem[], index: number) {
218
- if (!viewList[index]) {
219
- return;
220
- }
221
-
222
- if (index === 0) {
223
- new ViewNavigation(this.flowEngine.context, []).navigateTo(viewList[index].params, { replace: true });
224
- } else {
225
- new ViewNavigation(
226
- this.flowEngine.context,
227
- viewList.slice(0, index).map((item) => item.params),
228
- ).navigateTo(viewList[index].params);
229
- }
230
-
231
- this.stepNavigate(viewList, index + 1);
232
- }
233
-
234
- private async handleOpenViews(runtime: RoutePageRuntime, viewList: ViewItem[], viewsToOpen: ViewItem[]) {
235
- const missingModels = viewsToOpen.filter((v) => !v.model);
236
- if (missingModels.length > 0) {
237
- await Promise.all(
238
- missingModels.map(async (viewItem) => {
239
- try {
240
- viewItem.model = await this.flowEngine.loadModel({ uid: viewItem.modelUid });
241
- } catch (error) {
242
- console.error(`[NocoBase] Failed to load model ${viewItem.modelUid}:`, error);
243
- }
244
- }),
245
- );
246
- }
247
-
248
- if (runtime.forceStop) {
249
- return;
250
- }
251
-
252
- updateViewListHidden(viewList);
253
- this.openViews(runtime, viewList, viewsToOpen, 0);
254
- }
255
-
256
- private openViews(runtime: RoutePageRuntime, viewList: ViewItem[], viewsToOpen: ViewItem[], index: number) {
257
- if (!viewsToOpen[index]) {
258
- return;
259
- }
260
-
261
- const viewItem = viewsToOpen[index];
262
- if (!viewItem.model) {
263
- return;
264
- }
265
-
266
- const destroyRef = React.createRef<(result?: any, force?: boolean) => void>();
267
- const updateRef = React.createRef<(value: any) => void>();
268
- const openViewParams = getOpenViewStepParams(viewItem.model);
269
- const associationName =
270
- openViewParams?.associationName && !hasUsableSourceId(viewItem.params.sourceId)
271
- ? null
272
- : openViewParams?.associationName;
273
- const openerUids = viewList.slice(0, viewItem.index).map((item) => item.params.viewUid);
274
- const navigation = new ViewNavigation(
275
- this.flowEngine.context,
276
- viewList.slice(0, viewItem.index + 1).map((item) => item.params),
277
- );
278
-
279
- viewItem.model.dispatchEvent('click', {
280
- target: runtime.meta.layoutContentElement || this.layoutContentElement,
281
- collectionName: openViewParams?.collectionName,
282
- associationName,
283
- dataSourceKey: openViewParams?.dataSourceKey,
284
- destroyRef,
285
- updateRef,
286
- openerUids,
287
- ...viewItem.params,
288
- navigation,
289
- onOpen: () => {
290
- this.openViews(runtime, viewList, viewsToOpen, index + 1);
291
- },
292
- hidden: viewItem.hidden,
293
- isMobileLayout: !!this.flowEngine.context.isMobileLayout,
294
- triggerByRouter: true,
295
- });
296
-
297
- runtime.viewState[getKey(viewItem)] = {
298
- destroy: (_force?: boolean) => destroyRef.current?.(),
299
- update: (value: any) => updateRef.current?.(value),
300
- navigation,
301
- };
302
- }
303
-
304
- private ensureRouteModelContext(runtime: RoutePageRuntime) {
305
- if (!runtime.routeModel.context.pageActive) {
306
- runtime.routeModel.context.defineProperty('pageActive', {
307
- value: observable.ref(false),
308
- info: {
309
- description:
310
- 'Whether current page route is active (keep-alive). This is an observable.ref<boolean> (use ctx.pageActive.value to read/write).',
311
- detail: 'observable.ref<boolean>',
312
- },
313
- });
314
- }
315
-
316
- runtime.routeModel.context.defineProperty('currentRoute', {
317
- get: () => this.getCurrentRouteByPageUid(runtime.pageUid),
318
- // 当前路由来自 routeRepository,菜单更新后必须实时读取最新对象。
319
- cache: false,
320
- });
321
-
322
- runtime.routeModel.context.defineProperty('refreshDesktopRoutes', {
323
- get: () => runtime.meta.refreshDesktopRoutes,
324
- // refresh 回调来自页面桥接层,切页后也需要读取最新引用。
325
- cache: false,
326
- });
327
- }
328
-
329
- private getOrCreateRouteModel(pageUid: string): FlowModel {
330
- return (
331
- this.flowEngine.getModel(pageUid) ||
332
- this.flowEngine.createModel({
333
- uid: pageUid,
334
- use: 'RouteModel',
335
- })
336
- );
337
24
  }
338
25
  }
339
26
 
340
- /**
341
- * 将 pathname 解析结果和 pageUid 对齐,便于测试里复用。
342
- */
343
- export function toViewStack(pathname: string): ViewParam[] {
344
- return parsePathnameToViewParams(pathname);
345
- }
27
+ export { toViewStack, type BaseLayoutRouteCoordinatorOptions, type RoutePageMeta } from './BaseLayoutRouteCoordinator';