@nocobase/client-v2 2.1.0-alpha.40 → 2.1.0-alpha.45

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 (278) hide show
  1. package/es/Application.d.ts +7 -0
  2. package/es/BaseApplication.d.ts +13 -0
  3. package/es/RouterManager.d.ts +1 -0
  4. package/es/collection-field-interface/CollectionFieldInterface.d.ts +51 -15
  5. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +82 -3
  6. package/es/collection-manager/field-configure.d.ts +80 -0
  7. package/es/collection-manager/field-validation.d.ts +43 -0
  8. package/es/collection-manager/filter-operators/index.d.ts +46 -0
  9. package/es/collection-manager/filter-operators/operators.d.ts +30 -0
  10. package/es/collection-manager/interfaces/checkbox.d.ts +1 -41
  11. package/es/collection-manager/interfaces/checkboxGroup.d.ts +12 -44
  12. package/es/collection-manager/interfaces/collection.d.ts +12 -51
  13. package/es/collection-manager/interfaces/color.d.ts +1 -16
  14. package/es/collection-manager/interfaces/createdAt.d.ts +1 -44
  15. package/es/collection-manager/interfaces/createdBy.d.ts +0 -4
  16. package/es/collection-manager/interfaces/dateOnly.d.ts +7 -44
  17. package/es/collection-manager/interfaces/datetime.d.ts +1 -44
  18. package/es/collection-manager/interfaces/datetimeNoTz.d.ts +1 -44
  19. package/es/collection-manager/interfaces/email.d.ts +1 -29
  20. package/es/collection-manager/interfaces/id.d.ts +1 -16
  21. package/es/collection-manager/interfaces/index.d.ts +2 -3
  22. package/es/collection-manager/interfaces/input.d.ts +1 -102
  23. package/es/collection-manager/interfaces/integer.d.ts +1 -95
  24. package/es/collection-manager/interfaces/json.d.ts +16 -7
  25. package/es/collection-manager/interfaces/m2m.d.ts +11 -19
  26. package/es/collection-manager/interfaces/m2o.d.ts +11 -19
  27. package/es/collection-manager/interfaces/markdown.d.ts +1 -63
  28. package/es/collection-manager/interfaces/multipleSelect.d.ts +12 -44
  29. package/es/collection-manager/interfaces/nanoid.d.ts +1 -34
  30. package/es/collection-manager/interfaces/number.d.ts +1 -87
  31. package/es/collection-manager/interfaces/o2m.d.ts +12 -24
  32. package/es/collection-manager/interfaces/obo.d.ts +207 -0
  33. package/es/collection-manager/interfaces/oho.d.ts +207 -0
  34. package/es/collection-manager/interfaces/password.d.ts +1 -56
  35. package/es/collection-manager/interfaces/percent.d.ts +1 -84
  36. package/es/collection-manager/interfaces/phone.d.ts +1 -25
  37. package/es/collection-manager/interfaces/properties/index.d.ts +0 -28
  38. package/es/collection-manager/interfaces/radioGroup.d.ts +1 -29
  39. package/es/collection-manager/interfaces/richText.d.ts +1 -63
  40. package/es/collection-manager/interfaces/select.d.ts +12 -44
  41. package/es/collection-manager/interfaces/snowflake-id.d.ts +1 -34
  42. package/es/collection-manager/interfaces/tableoid.d.ts +1 -10
  43. package/es/collection-manager/interfaces/textarea.d.ts +1 -51
  44. package/es/collection-manager/interfaces/time.d.ts +1 -16
  45. package/es/collection-manager/interfaces/types.d.ts +3 -12
  46. package/es/collection-manager/interfaces/unixTimestamp.d.ts +1 -44
  47. package/es/collection-manager/interfaces/updatedAt.d.ts +1 -44
  48. package/es/collection-manager/interfaces/updatedBy.d.ts +0 -4
  49. package/es/collection-manager/interfaces/url.d.ts +1 -20
  50. package/es/collection-manager/interfaces/uuid.d.ts +1 -34
  51. package/es/collection-manager/template-fields.d.ts +53 -0
  52. package/es/components/KeepAlive.d.ts +22 -0
  53. package/es/components/RouterBridge.d.ts +9 -0
  54. package/es/components/form/DialogFormLayout.d.ts +5 -29
  55. package/es/components/form/VariableInput.d.ts +53 -2
  56. package/es/components/form/filter/CollectionFilter.d.ts +49 -0
  57. package/es/components/form/filter/CollectionFilterItem.d.ts +49 -0
  58. package/es/components/form/filter/DateFilterDynamicComponent.d.ts +57 -0
  59. package/es/components/form/filter/FilterValueInput.d.ts +29 -0
  60. package/es/components/form/filter/index.d.ts +11 -0
  61. package/es/components/form/filter/useFilterActionProps.d.ts +96 -0
  62. package/es/components/form/index.d.ts +1 -0
  63. package/es/data-source/ExtendCollectionsProvider.d.ts +50 -0
  64. package/es/data-source/index.d.ts +9 -0
  65. package/es/flow/FlowPage.d.ts +2 -1
  66. package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
  67. package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
  68. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
  69. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
  70. package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
  71. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +11 -0
  72. package/es/flow/admin-shell/admin-layout/index.d.ts +3 -0
  73. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +3 -2
  74. package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
  75. package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
  76. package/es/flow/components/FlowRoute.d.ts +10 -1
  77. package/es/flow/components/filter/index.d.ts +2 -0
  78. package/es/flow/components/filter/useFilterOptions.d.ts +66 -0
  79. package/es/flow/index.d.ts +4 -0
  80. package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
  81. package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +84 -0
  82. package/es/flow/models/blocks/assign-form/index.d.ts +1 -0
  83. package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
  84. package/es/flow/models/blocks/form/FormActionModel.d.ts +9 -2
  85. package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
  86. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
  87. package/es/flow-compat/passwordUtils.d.ts +1 -1
  88. package/es/index.d.ts +6 -0
  89. package/es/index.mjs +552 -459
  90. package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
  91. package/es/layout-manager/LayoutManager.d.ts +22 -0
  92. package/es/layout-manager/LayoutRoute.d.ts +14 -0
  93. package/es/layout-manager/index.d.ts +13 -0
  94. package/es/layout-manager/types.d.ts +20 -0
  95. package/es/layout-manager/utils.d.ts +14 -0
  96. package/es/nocobase-buildin-plugin/index.d.ts +3 -10
  97. package/es/settings-center/index.d.ts +1 -1
  98. package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
  99. package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
  100. package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
  101. package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
  102. package/es/settings-center/plugin-manager/types.d.ts +34 -0
  103. package/lib/index.js +552 -459
  104. package/package.json +8 -7
  105. package/src/Application.tsx +51 -12
  106. package/src/BaseApplication.tsx +32 -0
  107. package/src/PluginSettingsManager.ts +1 -1
  108. package/src/RouterManager.tsx +17 -1
  109. package/src/__tests__/PluginSettingsManager.test.ts +41 -2
  110. package/src/__tests__/app.test.tsx +17 -1
  111. package/src/__tests__/globalDeps.test.ts +1 -0
  112. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +45 -2
  113. package/src/__tests__/plugin-manager.test.tsx +177 -0
  114. package/src/__tests__/settings-center.test.tsx +24 -2
  115. package/src/collection-field-interface/CollectionFieldInterface.ts +71 -77
  116. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +201 -4
  117. package/src/collection-manager/field-configure.ts +548 -0
  118. package/src/collection-manager/field-validation.ts +195 -0
  119. package/src/collection-manager/filter-operators/index.ts +176 -0
  120. package/src/collection-manager/{interfaces/properties → filter-operators}/operators.ts +24 -13
  121. package/src/collection-manager/interfaces/checkbox.ts +2 -9
  122. package/src/collection-manager/interfaces/checkboxGroup.ts +2 -10
  123. package/src/collection-manager/interfaces/collection.ts +2 -15
  124. package/src/collection-manager/interfaces/color.ts +2 -2
  125. package/src/collection-manager/interfaces/createdAt.ts +2 -2
  126. package/src/collection-manager/interfaces/createdBy.ts +1 -12
  127. package/src/collection-manager/interfaces/dateOnly.ts +8 -2
  128. package/src/collection-manager/interfaces/datetime.ts +2 -2
  129. package/src/collection-manager/interfaces/datetimeNoTz.ts +2 -2
  130. package/src/collection-manager/interfaces/email.ts +2 -9
  131. package/src/collection-manager/interfaces/id.ts +1 -2
  132. package/src/collection-manager/interfaces/index.ts +2 -3
  133. package/src/collection-manager/interfaces/input.ts +2 -133
  134. package/src/collection-manager/interfaces/integer.ts +2 -71
  135. package/src/collection-manager/interfaces/json.tsx +17 -11
  136. package/src/collection-manager/interfaces/m2m.tsx +0 -21
  137. package/src/collection-manager/interfaces/m2o.tsx +0 -22
  138. package/src/collection-manager/interfaces/markdown.ts +2 -51
  139. package/src/collection-manager/interfaces/multipleSelect.ts +2 -14
  140. package/src/collection-manager/interfaces/nanoid.ts +2 -2
  141. package/src/collection-manager/interfaces/number.ts +2 -85
  142. package/src/collection-manager/interfaces/o2m.tsx +1 -22
  143. package/src/collection-manager/interfaces/obo.tsx +145 -0
  144. package/src/collection-manager/interfaces/oho.tsx +145 -0
  145. package/src/collection-manager/interfaces/password.ts +2 -44
  146. package/src/collection-manager/interfaces/percent.ts +2 -74
  147. package/src/collection-manager/interfaces/phone.ts +2 -2
  148. package/src/collection-manager/interfaces/properties/index.ts +0 -133
  149. package/src/collection-manager/interfaces/radioGroup.ts +2 -2
  150. package/src/collection-manager/interfaces/richText.ts +2 -51
  151. package/src/collection-manager/interfaces/select.ts +2 -14
  152. package/src/collection-manager/interfaces/snowflake-id.ts +2 -2
  153. package/src/collection-manager/interfaces/tableoid.ts +1 -2
  154. package/src/collection-manager/interfaces/textarea.ts +2 -51
  155. package/src/collection-manager/interfaces/time.ts +2 -2
  156. package/src/collection-manager/interfaces/types.ts +4 -12
  157. package/src/collection-manager/interfaces/unixTimestamp.tsx +2 -2
  158. package/src/collection-manager/interfaces/updatedAt.ts +2 -2
  159. package/src/collection-manager/interfaces/updatedBy.ts +1 -12
  160. package/src/collection-manager/interfaces/url.ts +2 -4
  161. package/src/collection-manager/interfaces/uuid.ts +2 -2
  162. package/src/collection-manager/template-fields.ts +109 -0
  163. package/src/components/KeepAlive.tsx +131 -0
  164. package/src/components/README.md +90 -6
  165. package/src/components/README.zh-CN.md +90 -7
  166. package/src/components/RouterBridge.tsx +28 -4
  167. package/src/components/__tests__/KeepAlive.test.tsx +63 -0
  168. package/src/components/__tests__/RouterBridge.test.tsx +27 -0
  169. package/src/components/form/DialogFormLayout.tsx +5 -29
  170. package/src/components/form/VariableInput.tsx +101 -28
  171. package/src/components/form/__tests__/VariableInput.test.ts +85 -0
  172. package/src/components/form/filter/CollectionFilter.tsx +111 -0
  173. package/src/components/form/filter/CollectionFilterItem.tsx +184 -0
  174. package/src/components/form/filter/DateFilterDynamicComponent.tsx +283 -0
  175. package/src/components/form/filter/FilterValueInput.tsx +198 -0
  176. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +247 -0
  177. package/src/components/form/filter/__tests__/DateFilterDynamicComponent.test.tsx +148 -0
  178. package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +243 -0
  179. package/src/components/form/filter/__tests__/compileFilterGroup.test.ts +146 -0
  180. package/src/components/form/filter/index.ts +13 -0
  181. package/src/components/form/filter/useFilterActionProps.ts +203 -0
  182. package/src/components/form/index.tsx +1 -0
  183. package/src/data-source/ExtendCollectionsProvider.tsx +144 -0
  184. package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
  185. package/src/data-source/index.ts +10 -0
  186. package/src/flow/FlowPage.tsx +35 -7
  187. package/src/flow/__tests__/FlowPage.test.tsx +79 -0
  188. package/src/flow/__tests__/FlowRoute.test.tsx +529 -2
  189. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
  190. package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
  191. package/src/flow/actions/aclCheck.tsx +4 -0
  192. package/src/flow/actions/aclCheckRefresh.tsx +4 -0
  193. package/src/flow/actions/dateTimeFormat.tsx +12 -8
  194. package/src/flow/actions/linkageRules.tsx +122 -0
  195. package/src/flow/actions/openView.tsx +28 -4
  196. package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
  197. package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
  198. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
  199. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
  200. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +35 -7
  201. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
  202. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +0 -12
  203. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
  204. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
  205. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +139 -0
  206. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +1 -26
  207. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
  208. package/src/flow/admin-shell/admin-layout/index.ts +3 -0
  209. package/src/flow/admin-shell/admin-layout/useApplications.tsx +34 -1
  210. package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
  211. package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
  212. package/src/flow/components/AdminLayout.tsx +4 -154
  213. package/src/flow/components/FlowRoute.tsx +105 -15
  214. package/src/flow/components/filter/index.ts +3 -0
  215. package/src/flow/components/filter/useFilterOptions.ts +102 -0
  216. package/src/flow/index.ts +4 -0
  217. package/src/flow/models/actions/UpdateRecordActionModel.tsx +14 -95
  218. package/src/flow/models/actions/UpdateRecordActionUtils.ts +4 -7
  219. package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +26 -1
  220. package/src/flow/models/base/ActionModel.tsx +8 -1
  221. package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
  222. package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
  223. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
  224. package/src/flow/models/base/RouteModel.tsx +1 -1
  225. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +63 -2
  226. package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +206 -0
  227. package/src/flow/models/blocks/assign-form/index.ts +1 -0
  228. package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
  229. package/src/flow/models/blocks/form/FormActionModel.tsx +30 -3
  230. package/src/flow/models/blocks/form/FormItemModel.tsx +8 -1
  231. package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
  232. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +71 -0
  233. package/src/flow/models/blocks/form/submitHandler.ts +8 -1
  234. package/src/flow/models/blocks/form/submitValues.ts +4 -1
  235. package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
  236. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
  237. package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
  238. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
  239. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
  240. package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
  241. package/src/flow/models/fields/CollectionSelectorFieldModel.tsx +8 -2
  242. package/src/flow/models/fields/DisplayEnumFieldModel.tsx +8 -2
  243. package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
  244. package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
  245. package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
  246. package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
  247. package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
  248. package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
  249. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
  250. package/src/index.ts +6 -0
  251. package/src/layout-manager/LayoutContentRoute.tsx +90 -0
  252. package/src/layout-manager/LayoutManager.tsx +185 -0
  253. package/src/layout-manager/LayoutRoute.tsx +138 -0
  254. package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
  255. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
  256. package/src/layout-manager/index.ts +14 -0
  257. package/src/layout-manager/types.ts +22 -0
  258. package/src/layout-manager/utils.ts +37 -0
  259. package/src/nocobase-buildin-plugin/index.tsx +69 -67
  260. package/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts +1 -0
  261. package/src/settings-center/index.ts +1 -1
  262. package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
  263. package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
  264. package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
  265. package/src/settings-center/plugin-manager/index.tsx +254 -0
  266. package/src/settings-center/plugin-manager/types.ts +35 -0
  267. package/src/settings-center/utils.tsx +8 -1
  268. package/src/theme/__tests__/globalStyles.test.ts +24 -0
  269. package/src/theme/globalStyles.ts +10 -0
  270. package/src/utils/globalDeps.ts +2 -0
  271. package/es/collection-manager/interfaces/linkTo.d.ts +0 -90
  272. package/es/collection-manager/interfaces/o2o.d.ts +0 -621
  273. package/es/collection-manager/interfaces/properties/operators.d.ts +0 -294
  274. package/es/collection-manager/interfaces/subTable.d.ts +0 -172
  275. package/src/collection-manager/interfaces/linkTo.ts +0 -120
  276. package/src/collection-manager/interfaces/o2o.tsx +0 -561
  277. package/src/collection-manager/interfaces/subTable.ts +0 -218
  278. package/src/settings-center/PluginManagerPage.tsx +0 -162
@@ -0,0 +1,502 @@
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 {
11
+ FlowEngine,
12
+ type FlowContext,
13
+ FlowModel,
14
+ observable,
15
+ parsePathnameToViewParams,
16
+ ViewNavigation,
17
+ type ViewParam,
18
+ } from '@nocobase/flow-engine';
19
+ import React from 'react';
20
+ import { getViewDiffAndUpdateHidden, getKey } from '../getViewDiffAndUpdateHidden';
21
+ import { getOpenViewStepParams } from '../flows/openViewFlow';
22
+ import { RouteModel } from '../models/base/RouteModel';
23
+ import { resolveViewParamsToViewList, updateViewListHidden, type ViewItem } from '../resolveViewParamsToViewList';
24
+ import type { LayoutDefinition } from '../../layout-manager/types';
25
+
26
+ export interface RoutePageMeta {
27
+ active: boolean;
28
+ refreshDesktopRoutes?: () => Promise<unknown>;
29
+ layoutContentElement?: HTMLElement | null;
30
+ }
31
+
32
+ export interface BaseLayoutRouteCoordinatorOptions {
33
+ layout?: LayoutDefinition;
34
+ layoutContext?: FlowContext;
35
+ basePathname?: string;
36
+ }
37
+
38
+ interface ViewRuntimeState {
39
+ destroy: (force?: boolean) => void;
40
+ update: (value: any) => void;
41
+ activate?: (forceRefresh?: boolean) => void;
42
+ deactivate?: () => void;
43
+ navigation: ViewNavigation;
44
+ }
45
+
46
+ interface RoutePageRuntime {
47
+ pageUid: string;
48
+ routeModel: RouteModel;
49
+ meta: RoutePageMeta;
50
+ viewState: Record<string, ViewRuntimeState>;
51
+ prevViewList: ViewItem[];
52
+ hasStepNavigated: boolean;
53
+ forceStop: boolean;
54
+ }
55
+
56
+ interface RouteLike {
57
+ layoutRouteName?: string;
58
+ params?: { name?: string };
59
+ pathname?: string;
60
+ pageUid?: string;
61
+ layoutBasePathname?: string;
62
+ layoutRoute?: {
63
+ type: string;
64
+ pageUid?: string;
65
+ basePathname?: string;
66
+ } | null;
67
+ }
68
+
69
+ const hasUsableSourceId = (sourceId: unknown) => sourceId !== undefined && sourceId !== null && String(sourceId) !== '';
70
+
71
+ const normalizeBasePathname = (basePathname?: string) => {
72
+ return `/${(basePathname || '/admin').replace(/^\/+/, '').replace(/\/+$/, '')}`;
73
+ };
74
+
75
+ const getDefaultBasePathnameFromRoutePath = (routePath?: string) => {
76
+ if (routePath?.startsWith('/')) {
77
+ return normalizeBasePathname(routePath);
78
+ }
79
+ return '';
80
+ };
81
+
82
+ /**
83
+ * 通用 Layout 路由协调器。
84
+ *
85
+ * 负责把当前路由路径解析为 v2 view stack,并驱动 RouteModel 上的弹窗、抽屉和 page tab 状态。
86
+ * Admin、Embed 等布局只需要提供当前 Layout 的基准路径,即可复用同一套 view 编排逻辑。
87
+ */
88
+ export class BaseLayoutRouteCoordinator {
89
+ protected readonly flowEngine: FlowEngine;
90
+ protected readonly layout: LayoutDefinition | undefined;
91
+ private readonly layoutContext?: FlowContext;
92
+ private basePathname: string;
93
+ private readonly runtimes = new Map<string, RoutePageRuntime>();
94
+ private layoutContentElement: HTMLElement | null = null;
95
+
96
+ constructor(flowEngine: FlowEngine, options: BaseLayoutRouteCoordinatorOptions = {}) {
97
+ this.flowEngine = flowEngine;
98
+ this.layout = options.layout;
99
+ this.layoutContext = options.layoutContext;
100
+ this.basePathname =
101
+ options.basePathname ||
102
+ getDefaultBasePathnameFromRoutePath(options.layout?.routePath) ||
103
+ (options.layout ? '' : normalizeBasePathname());
104
+ }
105
+
106
+ setLayoutContentElement(element: HTMLElement | null) {
107
+ this.layoutContentElement = element;
108
+ }
109
+
110
+ registerPage(pageUid: string, meta: RoutePageMeta) {
111
+ const routeModel = this.getOrCreateRouteModel(pageUid);
112
+ const runtime: RoutePageRuntime = {
113
+ pageUid,
114
+ routeModel,
115
+ meta: {
116
+ active: !!meta.active,
117
+ refreshDesktopRoutes: meta.refreshDesktopRoutes,
118
+ layoutContentElement: meta.layoutContentElement || null,
119
+ },
120
+ viewState: {},
121
+ prevViewList: [],
122
+ hasStepNavigated: false,
123
+ forceStop: false,
124
+ };
125
+
126
+ this.ensureRouteModelContext(runtime);
127
+ this.runtimes.set(pageUid, runtime);
128
+ this.syncPageMeta(pageUid, meta);
129
+
130
+ return runtime.routeModel;
131
+ }
132
+
133
+ syncPageMeta(pageUid: string, meta: Partial<RoutePageMeta>) {
134
+ const runtime = this.runtimes.get(pageUid);
135
+ if (!runtime) {
136
+ return;
137
+ }
138
+
139
+ const nextActive = typeof meta.active === 'boolean' ? meta.active : undefined;
140
+ runtime.meta = {
141
+ ...runtime.meta,
142
+ ...meta,
143
+ active: runtime.meta.active,
144
+ layoutContentElement: meta.layoutContentElement ?? runtime.meta.layoutContentElement,
145
+ };
146
+
147
+ if (typeof nextActive === 'boolean') {
148
+ this.setRuntimeActive(runtime, nextActive);
149
+ }
150
+ }
151
+
152
+ unregisterPage(pageUid: string) {
153
+ this.cleanupPage(pageUid);
154
+ this.runtimes.delete(pageUid);
155
+ }
156
+
157
+ syncRoute(routeLike: RouteLike) {
158
+ if (routeLike?.layoutRouteName && this.layout?.routeName && routeLike.layoutRouteName !== this.layout.routeName) {
159
+ this.runtimes.forEach((runtime) => {
160
+ this.setRuntimeActive(runtime, false);
161
+ });
162
+ return;
163
+ }
164
+
165
+ const activePageUid = routeLike?.pageUid || routeLike?.layoutRoute?.pageUid || routeLike?.params?.name;
166
+ const pathname = routeLike?.pathname;
167
+ const nextBasePathname = routeLike?.layoutBasePathname || routeLike?.layoutRoute?.basePathname;
168
+
169
+ if (nextBasePathname) {
170
+ this.basePathname = normalizeBasePathname(nextBasePathname);
171
+ }
172
+
173
+ this.runtimes.forEach((runtime) => {
174
+ this.setRuntimeActive(runtime, runtime.pageUid === activePageUid);
175
+ });
176
+
177
+ if (!activePageUid || !pathname) {
178
+ return;
179
+ }
180
+
181
+ this.runtimes.forEach((runtime) => {
182
+ if (runtime.pageUid !== activePageUid) {
183
+ runtime.forceStop = true;
184
+ }
185
+ });
186
+
187
+ const runtime = this.runtimes.get(activePageUid);
188
+ if (!runtime) {
189
+ return;
190
+ }
191
+
192
+ runtime.forceStop = false;
193
+ this.syncRuntimeWithPathname(runtime, pathname);
194
+ }
195
+
196
+ cleanupPage(pageUid: string) {
197
+ const runtime = this.runtimes.get(pageUid);
198
+ if (!runtime) {
199
+ return;
200
+ }
201
+
202
+ runtime.forceStop = true;
203
+ runtime.prevViewList.forEach((viewItem) => {
204
+ this.flowEngine.removeModelWithSubModels(viewItem.params.viewUid);
205
+ runtime.viewState[getKey(viewItem)]?.destroy?.();
206
+ delete runtime.viewState[getKey(viewItem)];
207
+ });
208
+ runtime.prevViewList = [];
209
+ runtime.hasStepNavigated = false;
210
+ }
211
+
212
+ destroy() {
213
+ Array.from(this.runtimes.keys()).forEach((pageUid) => {
214
+ this.cleanupPage(pageUid);
215
+ this.runtimes.delete(pageUid);
216
+ });
217
+ }
218
+
219
+ protected getCurrentRouteByPageUid(pageUid: string) {
220
+ return this.flowEngine.context.routeRepository?.getRouteBySchemaUid?.(pageUid) || {};
221
+ }
222
+
223
+ private setRuntimeActive(runtime: RoutePageRuntime, active: boolean) {
224
+ const wasActive = !!runtime.meta.active;
225
+ runtime.meta.active = active;
226
+ if (runtime.routeModel.context.pageActive?.value !== active) {
227
+ runtime.routeModel.context.pageActive.value = active;
228
+ }
229
+ this.syncViewListVisibility(runtime);
230
+ if (wasActive === active) {
231
+ return;
232
+ }
233
+
234
+ this.notifyRuntimeActiveChange(runtime, active);
235
+ }
236
+
237
+ private notifyRuntimeActiveChange(runtime: RoutePageRuntime, active: boolean) {
238
+ const rootViewItem = runtime.prevViewList[0];
239
+ if (!rootViewItem) {
240
+ return;
241
+ }
242
+
243
+ const viewState = runtime.viewState[getKey(rootViewItem)];
244
+ if (active) {
245
+ viewState?.activate?.(true);
246
+ return;
247
+ }
248
+
249
+ viewState?.deactivate?.();
250
+ }
251
+
252
+ private syncRuntimeWithPathname(runtime: RoutePageRuntime, pathname: string) {
253
+ try {
254
+ if (!this.basePathname) {
255
+ return;
256
+ }
257
+
258
+ const viewStack = parsePathnameToViewParams(pathname, { basePath: this.basePathname });
259
+ const viewList = resolveViewParamsToViewList(this.flowEngine, viewStack, runtime.routeModel);
260
+
261
+ if (this.shouldStepNavigate(runtime, viewList)) {
262
+ this.stepNavigate(viewList, 0);
263
+ runtime.hasStepNavigated = true;
264
+ this.scheduleInitialDeepLinkReplay(runtime, pathname);
265
+ return;
266
+ }
267
+
268
+ const { viewsToClose, viewsToOpen } = getViewDiffAndUpdateHidden(runtime.prevViewList, viewList);
269
+
270
+ if (viewsToClose.length) {
271
+ viewsToClose.forEach((viewItem) => {
272
+ runtime.viewState[getKey(viewItem)]?.destroy?.(true);
273
+ delete runtime.viewState[getKey(viewItem)];
274
+ });
275
+ updateViewListHidden(viewList);
276
+ }
277
+
278
+ if (viewsToOpen.length) {
279
+ void this.handleOpenViews(runtime, viewList, viewsToOpen);
280
+ }
281
+
282
+ if (viewsToClose.length === 0 && viewsToOpen.length === 0) {
283
+ const currentViewItem = viewList.at(-1);
284
+ if (currentViewItem) {
285
+ runtime.viewState[getKey(currentViewItem)]?.navigation.setViewStack(viewList.map((item) => item.params));
286
+ }
287
+ }
288
+
289
+ runtime.prevViewList = [...viewList];
290
+ this.syncViewListVisibility(runtime);
291
+ } catch (error) {
292
+ console.error(`[NocoBase] Failed to resolve view params to view list:`, error);
293
+ }
294
+ }
295
+
296
+ private syncViewListVisibility(runtime: RoutePageRuntime, viewList = runtime.prevViewList) {
297
+ if (!viewList.length) {
298
+ return;
299
+ }
300
+
301
+ if (runtime.meta.active) {
302
+ updateViewListHidden(viewList);
303
+ return;
304
+ }
305
+
306
+ viewList.forEach((viewItem) => {
307
+ viewItem.hidden.value = true;
308
+ });
309
+ }
310
+
311
+ private shouldStepNavigate(runtime: RoutePageRuntime, viewList: ViewItem[]) {
312
+ return runtime.prevViewList.length === 0 && viewList.length > 1 && !runtime.hasStepNavigated;
313
+ }
314
+
315
+ private scheduleInitialDeepLinkReplay(runtime: RoutePageRuntime, pathname: string) {
316
+ Promise.resolve()
317
+ .then(() => {
318
+ if (runtime.forceStop || runtime.prevViewList.length > 0) {
319
+ return;
320
+ }
321
+
322
+ this.syncRuntimeWithPathname(runtime, pathname);
323
+ })
324
+ .catch(() => {
325
+ // ignore
326
+ });
327
+ }
328
+
329
+ private stepNavigate(viewList: ViewItem[], index: number) {
330
+ if (!viewList[index]) {
331
+ return;
332
+ }
333
+
334
+ if (index === 0) {
335
+ new ViewNavigation(this.flowEngine.context, [], { basePath: this.basePathname }).navigateTo(
336
+ viewList[index].params,
337
+ {
338
+ replace: true,
339
+ },
340
+ );
341
+ } else {
342
+ new ViewNavigation(
343
+ this.flowEngine.context,
344
+ viewList.slice(0, index).map((item) => item.params),
345
+ { basePath: this.basePathname },
346
+ ).navigateTo(viewList[index].params);
347
+ }
348
+
349
+ this.stepNavigate(viewList, index + 1);
350
+ }
351
+
352
+ private async handleOpenViews(runtime: RoutePageRuntime, viewList: ViewItem[], viewsToOpen: ViewItem[]) {
353
+ const missingModels = viewsToOpen.filter((v) => !v.model);
354
+ if (missingModels.length > 0) {
355
+ await Promise.all(
356
+ missingModels.map(async (viewItem) => {
357
+ try {
358
+ viewItem.model = await this.flowEngine.loadModel({ uid: viewItem.modelUid });
359
+ } catch (error) {
360
+ console.error(`[NocoBase] Failed to load model ${viewItem.modelUid}:`, error);
361
+ }
362
+ }),
363
+ );
364
+ }
365
+
366
+ if (runtime.forceStop) {
367
+ return;
368
+ }
369
+
370
+ this.syncViewListVisibility(runtime, viewList);
371
+ this.openViews(runtime, viewList, viewsToOpen, 0);
372
+ }
373
+
374
+ private openViews(runtime: RoutePageRuntime, viewList: ViewItem[], viewsToOpen: ViewItem[], index: number) {
375
+ if (!viewsToOpen[index]) {
376
+ return;
377
+ }
378
+
379
+ const viewItem = viewsToOpen[index];
380
+ if (!viewItem.model) {
381
+ return;
382
+ }
383
+
384
+ const destroyRef = React.createRef<(result?: any, force?: boolean) => void>();
385
+ const updateRef = React.createRef<(value: any) => void>();
386
+ const activateRef = React.createRef<(forceRefresh?: boolean) => void>();
387
+ const deactivateRef = React.createRef<() => void>();
388
+ const openViewParams = getOpenViewStepParams(viewItem.model);
389
+ const associationName =
390
+ openViewParams?.associationName && !hasUsableSourceId(viewItem.params.sourceId)
391
+ ? null
392
+ : openViewParams?.associationName;
393
+ const openerUids = viewList.slice(0, viewItem.index).map((item) => item.params.viewUid);
394
+ const navigation = new ViewNavigation(
395
+ this.flowEngine.context,
396
+ viewList.slice(0, viewItem.index + 1).map((item) => item.params),
397
+ { basePath: this.basePathname },
398
+ );
399
+
400
+ viewItem.model.dispatchEvent('click', {
401
+ target: this.layoutContentElement || runtime.meta.layoutContentElement,
402
+ collectionName: openViewParams?.collectionName,
403
+ associationName,
404
+ dataSourceKey: openViewParams?.dataSourceKey,
405
+ destroyRef,
406
+ updateRef,
407
+ activateRef,
408
+ deactivateRef,
409
+ openerUids,
410
+ ...viewItem.params,
411
+ pageActive: runtime.meta.active,
412
+ activationControlledByLayout: true,
413
+ navigation,
414
+ onOpen: () => {
415
+ this.openViews(runtime, viewList, viewsToOpen, index + 1);
416
+ },
417
+ hidden: viewItem.hidden,
418
+ isMobileLayout: !!this.layoutContext?.isMobileLayout,
419
+ triggerByRouter: true,
420
+ });
421
+
422
+ runtime.viewState[getKey(viewItem)] = {
423
+ destroy: (_force?: boolean) => destroyRef.current?.(),
424
+ update: (value: any) => updateRef.current?.(value),
425
+ activate: (forceRefresh?: boolean) => activateRef.current?.(forceRefresh),
426
+ deactivate: () => deactivateRef.current?.(),
427
+ navigation,
428
+ };
429
+ }
430
+
431
+ private ensureRouteModelContext(runtime: RoutePageRuntime) {
432
+ if (this.layoutContext) {
433
+ runtime.routeModel.context.addDelegate(this.layoutContext);
434
+ }
435
+
436
+ if (!runtime.routeModel.context.pageActive) {
437
+ runtime.routeModel.context.defineProperty('pageActive', {
438
+ value: observable.ref(false),
439
+ info: {
440
+ description:
441
+ 'Whether current page route is active (keep-alive). This is an observable.ref<boolean> (use ctx.pageActive.value to read/write).',
442
+ detail: 'observable.ref<boolean>',
443
+ },
444
+ });
445
+ }
446
+
447
+ runtime.routeModel.context.defineProperty('currentRoute', {
448
+ get: () => this.getCurrentRouteByPageUid(runtime.pageUid),
449
+ // 当前路由来自 routeRepository,菜单更新后必须实时读取最新对象。
450
+ cache: false,
451
+ });
452
+
453
+ runtime.routeModel.context.defineProperty('refreshDesktopRoutes', {
454
+ get: () => runtime.meta.refreshDesktopRoutes,
455
+ // refresh 回调来自页面桥接层,切页后也需要读取最新引用。
456
+ cache: false,
457
+ });
458
+ runtime.routeModel.context.defineProperty('layout', {
459
+ get: () => this.layout || this.layoutContext?.layout,
460
+ // RouteModel 是打开根页面的入口,根页面及其内部 schema 需要读取当前 Layout 定义。
461
+ cache: false,
462
+ });
463
+ runtime.routeModel.context.defineProperty('layoutRoute', {
464
+ get: () => this.layoutContext?.layoutRoute,
465
+ cache: false,
466
+ });
467
+ runtime.routeModel.context.defineProperty('layoutContext', {
468
+ get: () => this.layoutContext,
469
+ cache: false,
470
+ });
471
+ }
472
+
473
+ private getOrCreateRouteModel(pageUid: string): RouteModel {
474
+ const existing = this.flowEngine.getModel(pageUid);
475
+ if (existing instanceof RouteModel) {
476
+ return existing as RouteModel;
477
+ }
478
+
479
+ if (existing) {
480
+ this.flowEngine.removeModelWithSubModels(pageUid);
481
+ }
482
+
483
+ return this.flowEngine.createModel({
484
+ uid: pageUid,
485
+ use: 'RouteModel',
486
+ }) as RouteModel;
487
+ }
488
+ }
489
+
490
+ /**
491
+ * 将 pathname 解析结果和 pageUid 对齐,便于测试里复用。
492
+ */
493
+ export function toViewStack(pathname: string, options: BaseLayoutRouteCoordinatorOptions = {}): ViewParam[] {
494
+ const basePath = options.basePathname || getDefaultBasePathnameFromRoutePath(options.layout?.routePath);
495
+ if (!basePath) {
496
+ return [];
497
+ }
498
+
499
+ return parsePathnameToViewParams(pathname, {
500
+ basePath,
501
+ });
502
+ }