@nocobase/client-v2 2.2.0-beta.1 → 2.2.0-beta.11

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 (252) hide show
  1. package/es/BaseApplication.d.ts +3 -0
  2. package/es/PluginSettingsManager.d.ts +33 -0
  3. package/es/RouteRepository.d.ts +29 -6
  4. package/es/RouterManager.d.ts +15 -0
  5. package/es/authRedirect.d.ts +1 -0
  6. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  7. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  8. package/es/components/form/JsonTextArea.d.ts +2 -1
  9. package/es/components/form/VariableJsonTextArea.d.ts +19 -0
  10. package/es/components/form/index.d.ts +1 -0
  11. package/es/components/index.d.ts +1 -0
  12. package/es/entry-actions/EntryActionManager.d.ts +24 -0
  13. package/es/entry-actions/index.d.ts +9 -0
  14. package/es/flow/actions/afterSuccess.d.ts +8 -1
  15. package/es/flow/actions/index.d.ts +1 -1
  16. package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
  17. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
  18. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
  19. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
  20. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
  21. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
  22. package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
  23. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
  24. package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
  25. package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
  26. package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
  27. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
  28. package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
  29. package/es/flow/models/base/ActionModelCore.d.ts +2 -0
  30. package/es/flow/models/base/GridModel.d.ts +1 -1
  31. package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
  32. package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
  33. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  34. package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
  35. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
  36. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
  37. package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
  38. package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
  39. package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
  40. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  41. package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
  42. package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
  43. package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
  44. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
  45. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
  46. package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
  47. package/es/flow/routeTransientInputArgs.d.ts +14 -0
  48. package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
  49. package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
  50. package/es/flow/utils/formValueDeps.d.ts +32 -0
  51. package/es/flow-compat/FieldValidation.d.ts +2 -1
  52. package/es/index.d.ts +4 -1
  53. package/es/index.mjs +315 -167
  54. package/es/utils/markdownSanitize.d.ts +11 -0
  55. package/lib/index.js +315 -167
  56. package/package.json +8 -7
  57. package/src/BaseApplication.tsx +12 -5
  58. package/src/PluginSettingsManager.ts +53 -0
  59. package/src/RouteRepository.ts +151 -24
  60. package/src/RouterManager.tsx +142 -1
  61. package/src/__tests__/PluginSettingsManager.test.ts +13 -0
  62. package/src/__tests__/RouteRepository.test.ts +239 -0
  63. package/src/__tests__/RouterManager.test.ts +125 -0
  64. package/src/__tests__/app.test.tsx +55 -0
  65. package/src/__tests__/authRedirect.test.ts +17 -0
  66. package/src/__tests__/exports.test.ts +16 -0
  67. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
  68. package/src/__tests__/plugin-manager.test.tsx +44 -2
  69. package/src/__tests__/settings-center.test.tsx +40 -1
  70. package/src/authRedirect.ts +38 -0
  71. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  72. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  73. package/src/collection-manager/field-configure.ts +1 -1
  74. package/src/collection-manager/interfaces/id.ts +1 -1
  75. package/src/collection-manager/interfaces/m2m.tsx +2 -2
  76. package/src/collection-manager/interfaces/m2o.tsx +2 -2
  77. package/src/collection-manager/interfaces/nanoid.ts +1 -1
  78. package/src/collection-manager/interfaces/o2m.tsx +2 -2
  79. package/src/collection-manager/interfaces/obo.tsx +2 -2
  80. package/src/collection-manager/interfaces/oho.tsx +2 -2
  81. package/src/collection-manager/interfaces/properties/index.ts +2 -2
  82. package/src/collection-manager/interfaces/uuid.ts +1 -1
  83. package/src/components/AppComponents.tsx +19 -3
  84. package/src/components/form/JsonTextArea.tsx +21 -11
  85. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +145 -2
  86. package/src/components/form/ScanInput/useCodeScanner.ts +154 -2
  87. package/src/components/form/VariableJsonTextArea.tsx +175 -0
  88. package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
  89. package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
  90. package/src/components/form/index.tsx +1 -0
  91. package/src/components/index.ts +1 -0
  92. package/src/entry-actions/EntryActionManager.ts +76 -0
  93. package/src/entry-actions/index.ts +10 -0
  94. package/src/flow/FlowPage.tsx +38 -3
  95. package/src/flow/__tests__/FlowPage.test.tsx +201 -27
  96. package/src/flow/__tests__/FlowRoute.test.tsx +1088 -105
  97. package/src/flow/__tests__/getKey.test.ts +7 -0
  98. package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
  99. package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
  100. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +120 -0
  101. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
  102. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
  103. package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
  104. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
  105. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
  106. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  107. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
  108. package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
  109. package/src/flow/actions/afterSuccess.tsx +142 -3
  110. package/src/flow/actions/customVariable.tsx +1 -2
  111. package/src/flow/actions/dataScopeFilter.ts +20 -1
  112. package/src/flow/actions/dateTimeFormat.tsx +42 -28
  113. package/src/flow/actions/index.ts +1 -1
  114. package/src/flow/actions/linkageRules.tsx +123 -35
  115. package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
  116. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  117. package/src/flow/actions/openView.tsx +59 -5
  118. package/src/flow/actions/runjs.tsx +2 -14
  119. package/src/flow/actions/validation.tsx +62 -30
  120. package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
  121. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
  122. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
  123. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +74 -13
  124. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
  125. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
  126. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
  127. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
  128. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
  129. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
  130. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
  131. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  132. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  133. package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
  134. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
  135. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
  136. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
  137. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
  138. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  139. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
  140. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
  141. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  142. package/src/flow/common/Markdown/Display.tsx +14 -3
  143. package/src/flow/common/Markdown/Edit.tsx +19 -7
  144. package/src/flow/components/DefaultValue.tsx +1 -2
  145. package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
  146. package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
  147. package/src/flow/components/FieldAssignValueInput.tsx +15 -11
  148. package/src/flow/components/FlowRoute.tsx +186 -25
  149. package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
  150. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
  151. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +134 -0
  152. package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
  153. package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
  154. package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
  155. package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
  156. package/src/flow/components/fieldAssignOptions.ts +155 -27
  157. package/src/flow/flows/editMarkdownFlow.tsx +1 -1
  158. package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
  159. package/src/flow/index.ts +1 -1
  160. package/src/flow/internal/components/Markdown/util.ts +2 -1
  161. package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
  162. package/src/flow/models/actions/JSActionModel.tsx +2 -7
  163. package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
  164. package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
  165. package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
  166. package/src/flow/models/base/ActionModel.tsx +21 -8
  167. package/src/flow/models/base/ActionModelCore.tsx +11 -4
  168. package/src/flow/models/base/GridModel.tsx +38 -7
  169. package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
  170. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
  171. package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
  172. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
  173. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
  174. package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
  175. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +32 -3
  176. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
  177. package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
  178. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
  179. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +111 -0
  180. package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
  181. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
  182. package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
  183. package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
  184. package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
  185. package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
  186. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  187. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  188. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  189. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
  190. package/src/flow/models/blocks/form/submitHandler.ts +17 -3
  191. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
  192. package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
  193. package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
  194. package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
  195. package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
  196. package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
  197. package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
  198. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +38 -14
  199. package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
  200. package/src/flow/models/blocks/table/TableColumnModel.tsx +42 -5
  201. package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
  202. package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
  203. package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
  204. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
  205. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +210 -1
  206. package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
  207. package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
  208. package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
  209. package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
  210. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +30 -6
  211. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +61 -17
  212. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
  213. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
  214. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
  215. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
  216. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
  217. package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
  218. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
  219. package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
  220. package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
  221. package/src/flow/models/fields/InputFieldModel.tsx +40 -2
  222. package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
  223. package/src/flow/models/fields/JSFieldModel.tsx +2 -7
  224. package/src/flow/models/fields/JSItemModel.tsx +2 -14
  225. package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
  226. package/src/flow/models/fields/TextareaFieldModel.tsx +72 -3
  227. package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
  228. package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
  229. package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
  230. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +131 -0
  231. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
  232. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
  233. package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
  234. package/src/flow/resolveViewParamsToViewList.tsx +11 -3
  235. package/src/flow/routeTransientInputArgs.ts +27 -0
  236. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +300 -0
  237. package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
  238. package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
  239. package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
  240. package/src/flow/utils/formValueDeps.ts +170 -0
  241. package/src/flow-compat/FieldValidation.tsx +122 -60
  242. package/src/flow-compat/Popover.tsx +43 -4
  243. package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
  244. package/src/index.ts +10 -1
  245. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
  246. package/src/nocobase-buildin-plugin/index.tsx +51 -15
  247. package/src/settings-center/AdminSettingsLayout.tsx +10 -2
  248. package/src/settings-center/SystemSettingsPage.tsx +1 -2
  249. package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
  250. package/src/settings-center/plugin-manager/index.tsx +3 -0
  251. package/src/settings-center/utils.tsx +0 -6
  252. package/src/utils/markdownSanitize.ts +88 -0
@@ -0,0 +1,289 @@
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 { SettingOutlined } from '@ant-design/icons';
11
+ import { css } from '@emotion/css';
12
+ import { Icon } from '../../../components';
13
+ import { ActionModel } from '../../models/base';
14
+ import {
15
+ AddSubModelButton,
16
+ DndProvider,
17
+ DragHandler,
18
+ Droppable,
19
+ FlowModel,
20
+ FlowModelRenderer,
21
+ FlowSettingsButton,
22
+ mergeSubModelItems,
23
+ observer,
24
+ type SubModelItem,
25
+ type SubModelItemsType,
26
+ } from '@nocobase/flow-engine';
27
+ import { Avatar, Button, Card, Tooltip, Typography } from 'antd';
28
+ import React from 'react';
29
+
30
+ const defaultIconColors = ['#3d8bff', '#00a8b5', '#35b26b', '#f5a623', '#ff7a45', '#e85d75', '#9254de', '#597ef7'];
31
+
32
+ function getDefaultIconColor(title?: React.ReactNode) {
33
+ const source = typeof title === 'string' ? title : '';
34
+ const hash = Array.from(source).reduce((sum, char) => sum + char.charCodeAt(0), 0);
35
+ return defaultIconColors[hash % defaultIconColors.length];
36
+ }
37
+
38
+ type AppSwitcherActionPanelStructure = {
39
+ subModels: {
40
+ actions?: ActionModel[];
41
+ };
42
+ };
43
+
44
+ function isRenderableActionModel(action: unknown): action is ActionModel {
45
+ if (!action || typeof action !== 'object') {
46
+ return false;
47
+ }
48
+
49
+ const candidate = action as Partial<ActionModel>;
50
+ return typeof candidate.getTitle === 'function' && typeof candidate.onClick === 'function';
51
+ }
52
+
53
+ type EntryActionAvailability = {
54
+ isEntryActionAvailable?: () => boolean;
55
+ getEntryActionUnavailableMessage?: () => string | undefined;
56
+ };
57
+
58
+ function isEntryActionUnavailable(action: ActionModel) {
59
+ const candidate = action as ActionModel & EntryActionAvailability;
60
+ return typeof candidate.isEntryActionAvailable === 'function' && !candidate.isEntryActionAvailable();
61
+ }
62
+
63
+ function getEntryActionUnavailableMessage(action: ActionModel) {
64
+ const candidate = action as ActionModel & EntryActionAvailability;
65
+ return candidate.getEntryActionUnavailableMessage?.();
66
+ }
67
+
68
+ export class AppSwitcherActionPanelModel extends FlowModel<AppSwitcherActionPanelStructure> {
69
+ getConfigureActionsItems(): SubModelItemsType {
70
+ const linkAction: SubModelItem = {
71
+ key: 'app-switcher:link',
72
+ label: this.context.t('Link'),
73
+ createModelOptions: {
74
+ use: 'LinkActionModel',
75
+ },
76
+ };
77
+ const jsAction: SubModelItem = {
78
+ key: 'app-switcher:js-action',
79
+ label: this.context.t('JS action'),
80
+ createModelOptions: {
81
+ use: 'JSActionModel',
82
+ },
83
+ };
84
+
85
+ return mergeSubModelItems([[linkAction], this.context.app.entryActionManager.getItems('app-switcher'), [jsAction]]);
86
+ }
87
+
88
+ renderConfigureActions() {
89
+ return <AppSwitcherConfigureActionsButton model={this} />;
90
+ }
91
+
92
+ private getRenderableActions(options: { includeHidden?: boolean; includeUnavailable?: boolean } = {}) {
93
+ return this.mapSubModels('actions', (action) => action)
94
+ .filter(isRenderableActionModel)
95
+ .filter((action) => options.includeUnavailable || !isEntryActionUnavailable(action))
96
+ .filter((action) => options.includeHidden || !action.hidden);
97
+ }
98
+
99
+ hasActions() {
100
+ return this.getRenderableActions().length > 0;
101
+ }
102
+
103
+ renderContent() {
104
+ const token = this.context.themeToken;
105
+ const designable = !!this.context.flowSettingsEnabled;
106
+ const actions = this.getRenderableActions({ includeHidden: designable, includeUnavailable: designable });
107
+ const columnCount = Math.min(Math.max(actions.length || 1, 1), 2);
108
+ const buttonResetClass = css`
109
+ &.ant-btn {
110
+ display: block;
111
+ width: 100%;
112
+ height: 100%;
113
+ padding: 0;
114
+ border: none;
115
+ box-shadow: none;
116
+ background: none;
117
+ color: ${token.colorText};
118
+ text-align: left;
119
+ }
120
+ &.ant-btn > span {
121
+ display: block;
122
+ width: 100%;
123
+ height: 100%;
124
+ }
125
+ .ant-btn-icon {
126
+ display: none;
127
+ }
128
+ `;
129
+ const contentClass = css`
130
+ max-width: calc(100vw - 48px);
131
+ max-height: calc(100vh - 48px);
132
+ overflow: auto;
133
+ padding: ${token.paddingXS}px;
134
+ `;
135
+ const listClass = css`
136
+ display: grid;
137
+ grid-template-columns: repeat(${columnCount}, 260px);
138
+ gap: ${token.marginXXS}px;
139
+ margin: 0;
140
+ padding: 0;
141
+ list-style: none;
142
+ `;
143
+
144
+ return (
145
+ <div className={contentClass}>
146
+ {actions.length ? (
147
+ <DndProvider>
148
+ <ul className={listClass}>
149
+ {actions.map((action) => {
150
+ const { icon = 'AppstoreOutlined', color } = action.props;
151
+ const title = action.getTitle();
152
+ const entryActionUnavailable = isEntryActionUnavailable(action);
153
+ const renderActionContent = (compact = false) => (
154
+ <Card
155
+ bordered={false}
156
+ className={css`
157
+ height: 56px;
158
+ border-radius: ${token.borderRadius}px;
159
+ box-shadow: none !important;
160
+ opacity: ${compact ? token.opacityLoading : 1};
161
+
162
+ &:hover {
163
+ background: ${token.colorBgTextHover};
164
+ box-shadow: none !important;
165
+ }
166
+
167
+ .ant-card-body {
168
+ height: 100%;
169
+ padding: ${token.paddingXS}px;
170
+ display: flex;
171
+ align-items: center;
172
+ gap: ${token.marginXS}px;
173
+ }
174
+ `}
175
+ styles={{ body: { background: 'transparent' } }}
176
+ >
177
+ <Avatar
178
+ size={32}
179
+ shape="square"
180
+ icon={<Icon type={icon as string} />}
181
+ style={{
182
+ flex: '0 0 auto',
183
+ borderRadius: 6,
184
+ background: color || getDefaultIconColor(title),
185
+ }}
186
+ />
187
+ <Typography.Text
188
+ ellipsis
189
+ title={typeof title === 'string' ? title : undefined}
190
+ style={{
191
+ minWidth: 0,
192
+ color: token.colorText,
193
+ fontSize: token.fontSize,
194
+ lineHeight: token.lineHeight,
195
+ }}
196
+ >
197
+ {title}
198
+ </Typography.Text>
199
+ </Card>
200
+ );
201
+ action.enableEditDanger = false;
202
+ action.enableEditType = false;
203
+ action.enableEditIconOnly = false;
204
+ action.enableEditColor = true;
205
+ action.renderButton = () => {
206
+ if (entryActionUnavailable) {
207
+ return (
208
+ <Tooltip title={getEntryActionUnavailableMessage(action)}>
209
+ <Button className={buttonResetClass} onClick={action.onClick.bind(action)}>
210
+ {renderActionContent(true)}
211
+ </Button>
212
+ </Tooltip>
213
+ );
214
+ }
215
+ return (
216
+ <Button className={buttonResetClass} onClick={action.onClick.bind(action)}>
217
+ {renderActionContent()}
218
+ </Button>
219
+ );
220
+ };
221
+ action.renderHiddenInConfig = () => (
222
+ <Tooltip
223
+ title={
224
+ entryActionUnavailable
225
+ ? getEntryActionUnavailableMessage(action)
226
+ : this.context.t('The button is hidden and only visible when the UI Editor is active')
227
+ }
228
+ >
229
+ <Button className={buttonResetClass} onClick={action.onClick.bind(action)}>
230
+ {renderActionContent(true)}
231
+ </Button>
232
+ </Tooltip>
233
+ );
234
+
235
+ return (
236
+ <li key={action.uid} style={{ width: 260, height: 56, listStyle: 'none' }}>
237
+ <Droppable model={action}>
238
+ <FlowModelRenderer
239
+ model={action}
240
+ showFlowSettings={{ showBackground: false, showBorder: false, toolbarPosition: 'above' }}
241
+ extraToolbarItems={
242
+ designable
243
+ ? [
244
+ {
245
+ key: 'drag-handler',
246
+ component: DragHandler,
247
+ sort: 1,
248
+ },
249
+ ]
250
+ : []
251
+ }
252
+ />
253
+ </Droppable>
254
+ </li>
255
+ );
256
+ })}
257
+ </ul>
258
+ </DndProvider>
259
+ ) : (
260
+ <div style={{ width: 260, minHeight: token.marginXS }} />
261
+ )}
262
+ {designable && <div style={{ marginTop: token.marginXS }}>{this.renderConfigureActions()}</div>}
263
+ </div>
264
+ );
265
+ }
266
+ }
267
+
268
+ AppSwitcherActionPanelModel.define({
269
+ label: 'App switcher actions',
270
+ children: false,
271
+ createModelOptions: {
272
+ use: 'AppSwitcherActionPanelModel',
273
+ },
274
+ });
275
+
276
+ const AppSwitcherConfigureActionsButton = observer(({ model }: { model: AppSwitcherActionPanelModel }) => {
277
+ const entryActionsRevision = model.context.app.entryActionManager.revision;
278
+ return (
279
+ <AddSubModelButton
280
+ key={`app-switcher-add-actions-${entryActionsRevision}`}
281
+ model={model}
282
+ items={model.getConfigureActionsItems()}
283
+ subModelKey="actions"
284
+ keepDropdownOpen
285
+ >
286
+ <FlowSettingsButton icon={<SettingOutlined />}>{model.context.t('Actions')}</FlowSettingsButton>
287
+ </AddSubModelButton>
288
+ );
289
+ });
@@ -70,9 +70,7 @@ const SettingsMenu: React.FC = () => {
70
70
  key: 'userManual',
71
71
  label: (
72
72
  <a
73
- href={
74
- isSimplifiedChinese ? 'https://v2.docs.nocobase.com/cn/guide/' : 'https://v2.docs.nocobase.com/guide/'
75
- }
73
+ href={isSimplifiedChinese ? 'https://docs.nocobase.com/cn/guide/' : 'https://docs.nocobase.com/guide/'}
76
74
  target="_blank"
77
75
  rel="noreferrer"
78
76
  >
@@ -0,0 +1,188 @@
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 { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
11
+ import { act, render, screen, waitFor } from '@testing-library/react';
12
+ import React, { useEffect } from 'react';
13
+ import { createMemoryRouter, RouterProvider, useParams } from 'react-router-dom';
14
+ import { describe, expect, it, vi } from 'vitest';
15
+ import type { LayoutDefinition } from '../../../../layout-manager/types';
16
+ import { AdminLayoutComponent } from '../AdminLayoutComponent';
17
+ import { AdminLayoutModel } from '../AdminLayoutModel';
18
+ import { AdminLayoutContent } from '../AdminLayoutSlotModels';
19
+
20
+ vi.mock('@ant-design/pro-layout', async () => {
21
+ const ReactModule = await import('react');
22
+ const RouteContext = ReactModule.createContext({ isMobile: false });
23
+
24
+ return {
25
+ default: (props: { children?: React.ReactNode }) =>
26
+ ReactModule.createElement(
27
+ RouteContext.Provider,
28
+ { value: { isMobile: false } },
29
+ ReactModule.createElement('div', { 'data-testid': 'pro-layout' }, props.children),
30
+ ),
31
+ RouteContext,
32
+ };
33
+ });
34
+
35
+ vi.mock('../../../system-settings', () => ({
36
+ useSystemSettings: () => ({
37
+ loading: false,
38
+ data: {
39
+ data: {
40
+ title: 'NocoBase',
41
+ },
42
+ },
43
+ }),
44
+ }));
45
+
46
+ vi.mock('../useApplications', () => ({
47
+ useApplications: () => ({
48
+ appList: [],
49
+ }),
50
+ }));
51
+
52
+ vi.mock('../AppListRender', () => ({
53
+ useAppListRender: () => undefined,
54
+ }));
55
+
56
+ describe('AdminLayoutComponent', () => {
57
+ it('keeps custom admin layout pages alive by layout route name', async () => {
58
+ const layout: LayoutDefinition = {
59
+ routeName: 'admin2',
60
+ routePath: '/admin2',
61
+ rootRouteName: 'admin2',
62
+ uid: 'custom-admin-layout-model',
63
+ layoutModelClass: 'AdminLayoutModel',
64
+ rootPageModelClass: 'RootPageModel',
65
+ childPageModelClass: 'ChildPageModel',
66
+ authCheck: true,
67
+ };
68
+ const events: string[] = [];
69
+ const engine = new FlowEngine();
70
+ engine.context.defineProperty('routeRepository', {
71
+ value: {
72
+ listAccessible: () => [],
73
+ },
74
+ });
75
+
76
+ const Page = () => {
77
+ const { name } = useParams();
78
+ useEffect(() => {
79
+ events.push(`mount:${name}`);
80
+ return () => {
81
+ events.push(`unmount:${name}`);
82
+ };
83
+ }, [name]);
84
+
85
+ return <div data-testid={`admin-layout-page-${name}`}>page {name}</div>;
86
+ };
87
+
88
+ const router = createMemoryRouter(
89
+ [
90
+ {
91
+ id: layout.routeName,
92
+ path: layout.routePath,
93
+ element: <AdminLayoutContent layout={layout} />,
94
+ children: [
95
+ {
96
+ id: 'admin2.__page',
97
+ path: ':name',
98
+ element: <Page />,
99
+ },
100
+ ],
101
+ },
102
+ ],
103
+ {
104
+ initialEntries: ['/admin2/page-a'],
105
+ },
106
+ );
107
+
108
+ render(
109
+ <FlowEngineProvider engine={engine}>
110
+ <RouterProvider router={router} />
111
+ </FlowEngineProvider>,
112
+ );
113
+
114
+ expect(await screen.findByTestId('admin-layout-page-page-a')).toBeInTheDocument();
115
+
116
+ await act(async () => {
117
+ await router.navigate('/admin2/page-b');
118
+ });
119
+
120
+ expect(await screen.findByTestId('admin-layout-page-page-b')).toBeInTheDocument();
121
+ expect(screen.getByTestId('admin-layout-page-page-a')).toBeInTheDocument();
122
+ expect(events).toEqual(['mount:page-a', 'mount:page-b']);
123
+ });
124
+
125
+ it('activates the current layout before loading accessible routes', async () => {
126
+ const layout: LayoutDefinition = {
127
+ routeName: 'admin2',
128
+ routePath: '/admin2',
129
+ rootRouteName: 'admin2',
130
+ uid: 'custom-admin-layout-model',
131
+ layoutModelClass: 'AdminLayoutModel',
132
+ rootPageModelClass: 'RootPageModel',
133
+ childPageModelClass: 'ChildPageModel',
134
+ authCheck: true,
135
+ };
136
+ const deactivateLayout = vi.fn();
137
+ const activateLayout = vi.fn(() => deactivateLayout);
138
+ const ensureAccessibleLoaded = vi.fn(async () => []);
139
+ const engine = new FlowEngine();
140
+ engine.context.defineProperty('routeRepository', {
141
+ value: {
142
+ activateLayout,
143
+ ensureAccessibleLoaded,
144
+ listAccessible: () => [],
145
+ subscribe: vi.fn(),
146
+ unsubscribe: vi.fn(),
147
+ },
148
+ });
149
+ engine.context.defineProperty('t', {
150
+ value: (key: string) => key,
151
+ });
152
+ const model = engine.createModel<AdminLayoutModel>({
153
+ uid: layout.uid,
154
+ use: AdminLayoutModel,
155
+ props: {
156
+ layout,
157
+ },
158
+ });
159
+
160
+ const router = createMemoryRouter(
161
+ [
162
+ {
163
+ id: layout.routeName,
164
+ path: layout.routePath,
165
+ element: <AdminLayoutComponent model={model} />,
166
+ },
167
+ ],
168
+ {
169
+ initialEntries: ['/admin2'],
170
+ },
171
+ );
172
+ const { unmount } = render(
173
+ <FlowEngineProvider engine={engine}>
174
+ <RouterProvider router={router} />
175
+ </FlowEngineProvider>,
176
+ );
177
+
178
+ await waitFor(() => {
179
+ expect(ensureAccessibleLoaded).toHaveBeenCalled();
180
+ });
181
+ expect(activateLayout).toHaveBeenCalledWith(layout);
182
+ expect(activateLayout.mock.invocationCallOrder[0]).toBeLessThan(ensureAccessibleLoaded.mock.invocationCallOrder[0]);
183
+
184
+ unmount();
185
+
186
+ expect(deactivateLayout).toHaveBeenCalledTimes(1);
187
+ });
188
+ });
@@ -318,6 +318,50 @@ describe('AdminLayoutModel menu items', () => {
318
318
  expect(route.children[1]._model).toBe(adminLayoutModel.subModels.menuItems?.[1]);
319
319
  });
320
320
 
321
+ it('should generate ProLayout route tree under a custom admin layout route path', () => {
322
+ const adminLayoutModel = engine.createModel<AdminLayoutModel>({
323
+ uid: 'admin2-layout-model',
324
+ use: AdminLayoutModel,
325
+ props: {
326
+ layout: {
327
+ routeName: 'admin2',
328
+ routePath: '/admin2',
329
+ uid: 'admin2-layout-model',
330
+ layoutModelClass: 'AdminLayoutModel',
331
+ },
332
+ },
333
+ });
334
+
335
+ adminLayoutModel.syncMenuRoutes([
336
+ {
337
+ id: 1,
338
+ title: 'Group',
339
+ type: NocoBaseDesktopRouteType.group,
340
+ children: [
341
+ {
342
+ id: 11,
343
+ title: 'Page 1',
344
+ schemaUid: 'page-1',
345
+ type: NocoBaseDesktopRouteType.flowPage,
346
+ },
347
+ ],
348
+ },
349
+ ]);
350
+
351
+ const route = adminLayoutModel.toProLayoutRoute({
352
+ designable: false,
353
+ isMobile: false,
354
+ t: (title) => title,
355
+ });
356
+
357
+ expect(route.children[0].path).toBe('/admin2/1');
358
+ expect(route.children[0].redirect).toBe('/admin2/page-1');
359
+ expect(route.children[0]._runtimePath).toBe('/apps/demo/v2/admin2/page-1');
360
+ expect(route.children[0].routes?.[0].path).toBe('/admin2/page-1');
361
+ expect(route.children[0].routes?.[0].redirect).toBe('/admin2/page-1');
362
+ expect(route.children[0].routes?.[0]._runtimePath).toBe('/apps/demo/v2/admin2/page-1');
363
+ });
364
+
321
365
  it('should filter legacy page menu routes but keep empty groups in v2 admin layout', () => {
322
366
  const adminLayoutModel = engine.createModel<AdminLayoutModel>({
323
367
  uid: 'admin-layout-model',
@@ -1128,6 +1172,46 @@ describe('AdminLayoutModel menu items', () => {
1128
1172
  expect(adminLayoutModel.menuRouteRefreshVersion).toBe(refreshBefore + 1);
1129
1173
  });
1130
1174
 
1175
+ it('should refresh the owning admin layout when hiding custom layout menu routes dynamically', () => {
1176
+ const defaultAdminLayoutModel = engine.createModel<AdminLayoutModel>({
1177
+ uid: ADMIN_LAYOUT_MODEL_UID,
1178
+ use: AdminLayoutModel,
1179
+ });
1180
+ const customAdminLayoutModel = engine.createModel<AdminLayoutModel>({
1181
+ uid: 'custom-admin-layout-model',
1182
+ use: AdminLayoutModel,
1183
+ props: {
1184
+ layout: {
1185
+ routeName: 'admin2',
1186
+ routePath: '/admin2',
1187
+ rootRouteName: 'admin2',
1188
+ uid: 'custom-admin-layout-model',
1189
+ layoutModelClass: 'AdminLayoutModel',
1190
+ rootPageModelClass: 'RootPageModel',
1191
+ childPageModelClass: 'ChildPageModel',
1192
+ authCheck: true,
1193
+ },
1194
+ },
1195
+ });
1196
+ const model = engine.createModel<AdminLayoutMenuItemModel>({
1197
+ uid: 'menu-item-custom-layout-dynamic-hidden',
1198
+ use: AdminLayoutMenuItemModel,
1199
+ props: {
1200
+ route: createRoute(),
1201
+ },
1202
+ });
1203
+
1204
+ model.setParent(customAdminLayoutModel);
1205
+
1206
+ const defaultRefreshBefore = defaultAdminLayoutModel.menuRouteRefreshVersion;
1207
+ const customRefreshBefore = customAdminLayoutModel.menuRouteRefreshVersion;
1208
+
1209
+ model.setHidden(true);
1210
+
1211
+ expect(customAdminLayoutModel.menuRouteRefreshVersion).toBe(customRefreshBefore + 1);
1212
+ expect(defaultAdminLayoutModel.menuRouteRefreshVersion).toBe(defaultRefreshBefore);
1213
+ });
1214
+
1131
1215
  it('should render hidden menu item with opacity and keep original title in config mode', () => {
1132
1216
  const model = engine.createModel<AdminLayoutMenuItemModel>({
1133
1217
  uid: 'menu-item-hidden-in-config',
@@ -2222,6 +2306,21 @@ describe('AdminLayoutModel menu items', () => {
2222
2306
  );
2223
2307
  });
2224
2308
 
2309
+ it('should include router basename when opening same-origin links in a new window', async () => {
2310
+ await openAdminLayoutMenuLink({
2311
+ context: engine.context as any,
2312
+ href: '/admin2/page',
2313
+ params: [{ name: 'from', value: 'admin' }],
2314
+ openInNewWindow: true,
2315
+ isMobile: false,
2316
+ closeMobileMenu: vi.fn(),
2317
+ navigate: navigateMock,
2318
+ basenameOfCurrentRouter: '/v',
2319
+ });
2320
+
2321
+ expect(window.open).toHaveBeenCalledWith('/v/admin2/page?from=admin', '_blank', 'noopener,noreferrer');
2322
+ });
2323
+
2225
2324
  it('should resolve sibling move options for non-group drag target', () => {
2226
2325
  const activeModel = engine.createModel<AdminLayoutMenuItemModel>({
2227
2326
  uid: 'drag-source-page',