@nocobase/client-v2 2.1.0-alpha.40 → 2.1.0-alpha.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/Application.d.ts +7 -0
- package/es/BaseApplication.d.ts +13 -0
- package/es/RouterManager.d.ts +1 -0
- package/es/authRedirect.d.ts +12 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +51 -15
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +82 -3
- package/es/collection-manager/field-configure.d.ts +80 -0
- package/es/collection-manager/field-validation.d.ts +43 -0
- package/es/collection-manager/filter-operators/index.d.ts +46 -0
- package/es/collection-manager/filter-operators/operators.d.ts +30 -0
- package/es/collection-manager/interfaces/checkbox.d.ts +1 -41
- package/es/collection-manager/interfaces/checkboxGroup.d.ts +12 -44
- package/es/collection-manager/interfaces/collection.d.ts +12 -51
- package/es/collection-manager/interfaces/color.d.ts +1 -16
- package/es/collection-manager/interfaces/createdAt.d.ts +1 -44
- package/es/collection-manager/interfaces/createdBy.d.ts +0 -4
- package/es/collection-manager/interfaces/dateOnly.d.ts +7 -44
- package/es/collection-manager/interfaces/datetime.d.ts +1 -44
- package/es/collection-manager/interfaces/datetimeNoTz.d.ts +1 -44
- package/es/collection-manager/interfaces/email.d.ts +1 -29
- package/es/collection-manager/interfaces/id.d.ts +1 -16
- package/es/collection-manager/interfaces/index.d.ts +2 -3
- package/es/collection-manager/interfaces/input.d.ts +1 -102
- package/es/collection-manager/interfaces/integer.d.ts +1 -95
- package/es/collection-manager/interfaces/json.d.ts +16 -7
- package/es/collection-manager/interfaces/m2m.d.ts +11 -19
- package/es/collection-manager/interfaces/m2o.d.ts +11 -19
- package/es/collection-manager/interfaces/markdown.d.ts +1 -63
- package/es/collection-manager/interfaces/multipleSelect.d.ts +12 -44
- package/es/collection-manager/interfaces/nanoid.d.ts +1 -34
- package/es/collection-manager/interfaces/number.d.ts +1 -87
- package/es/collection-manager/interfaces/o2m.d.ts +12 -24
- package/es/collection-manager/interfaces/obo.d.ts +207 -0
- package/es/collection-manager/interfaces/oho.d.ts +207 -0
- package/es/collection-manager/interfaces/password.d.ts +1 -56
- package/es/collection-manager/interfaces/percent.d.ts +1 -84
- package/es/collection-manager/interfaces/phone.d.ts +1 -25
- package/es/collection-manager/interfaces/properties/index.d.ts +0 -28
- package/es/collection-manager/interfaces/radioGroup.d.ts +1 -29
- package/es/collection-manager/interfaces/richText.d.ts +1 -63
- package/es/collection-manager/interfaces/select.d.ts +12 -44
- package/es/collection-manager/interfaces/snowflake-id.d.ts +1 -34
- package/es/collection-manager/interfaces/tableoid.d.ts +1 -10
- package/es/collection-manager/interfaces/textarea.d.ts +1 -51
- package/es/collection-manager/interfaces/time.d.ts +1 -16
- package/es/collection-manager/interfaces/types.d.ts +3 -12
- package/es/collection-manager/interfaces/unixTimestamp.d.ts +1 -44
- package/es/collection-manager/interfaces/updatedAt.d.ts +1 -44
- package/es/collection-manager/interfaces/updatedBy.d.ts +0 -4
- package/es/collection-manager/interfaces/url.d.ts +1 -20
- package/es/collection-manager/interfaces/uuid.d.ts +1 -34
- package/es/collection-manager/template-fields.d.ts +53 -0
- package/es/components/KeepAlive.d.ts +22 -0
- package/es/components/RouterBridge.d.ts +9 -0
- package/es/components/form/DialogFormLayout.d.ts +5 -29
- package/es/components/form/TypedVariableInput.d.ts +75 -0
- package/es/components/form/VariableInput.d.ts +53 -2
- package/es/components/form/filter/CollectionFilter.d.ts +49 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +49 -0
- package/es/components/form/filter/DateFilterDynamicComponent.d.ts +57 -0
- package/es/components/form/filter/FilterValueInput.d.ts +29 -0
- package/es/components/form/filter/index.d.ts +11 -0
- package/es/components/form/filter/useFilterActionProps.d.ts +96 -0
- package/es/components/form/index.d.ts +2 -0
- package/es/data-source/ExtendCollectionsProvider.d.ts +50 -0
- package/es/data-source/index.d.ts +9 -0
- package/es/flow/FlowPage.d.ts +2 -1
- package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
- package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
- package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +11 -0
- package/es/flow/admin-shell/admin-layout/index.d.ts +3 -0
- package/es/flow/admin-shell/admin-layout/useApplications.d.ts +3 -2
- package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
- package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
- package/es/flow/components/ConditionBuilder.d.ts +1 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/components/FieldAssignValueInput.d.ts +1 -0
- package/es/flow/components/FlowRoute.d.ts +10 -1
- package/es/flow/components/fieldAssignOptions.d.ts +10 -0
- package/es/flow/components/filter/LinkageFilterItem.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +1 -0
- package/es/flow/components/filter/index.d.ts +2 -0
- package/es/flow/components/filter/metaTreeAssociationDepth.d.ts +13 -0
- package/es/flow/components/filter/useFilterOptions.d.ts +66 -0
- package/es/flow/index.d.ts +4 -0
- package/es/flow/models/base/AssociationFieldGroupModel.d.ts +2 -33
- package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
- package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +92 -0
- package/es/flow/models/blocks/assign-form/index.d.ts +1 -0
- package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
- package/es/flow/models/blocks/form/FormActionModel.d.ts +9 -2
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +2 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/index.d.ts +6 -0
- package/es/index.mjs +552 -459
- package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
- package/es/layout-manager/LayoutManager.d.ts +22 -0
- package/es/layout-manager/LayoutRoute.d.ts +14 -0
- package/es/layout-manager/index.d.ts +13 -0
- package/es/layout-manager/types.d.ts +20 -0
- package/es/layout-manager/utils.d.ts +14 -0
- package/es/nocobase-buildin-plugin/index.d.ts +3 -10
- package/es/settings-center/index.d.ts +1 -1
- package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
- package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
- package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
- package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
- package/es/settings-center/plugin-manager/types.d.ts +34 -0
- package/lib/index.js +552 -459
- package/package.json +8 -7
- package/src/Application.tsx +51 -12
- package/src/BaseApplication.tsx +32 -0
- package/src/PluginSettingsManager.ts +1 -1
- package/src/RouterManager.tsx +17 -1
- package/src/__tests__/PluginSettingsManager.test.ts +41 -2
- package/src/__tests__/app.test.tsx +17 -1
- package/src/__tests__/globalDeps.test.ts +1 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +50 -2
- package/src/__tests__/plugin-manager.test.tsx +177 -0
- package/src/__tests__/settings-center.test.tsx +24 -2
- package/src/authRedirect.ts +43 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +71 -77
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +201 -4
- package/src/collection-manager/field-configure.ts +548 -0
- package/src/collection-manager/field-validation.ts +195 -0
- package/src/collection-manager/filter-operators/index.ts +176 -0
- package/src/collection-manager/{interfaces/properties → filter-operators}/operators.ts +24 -13
- package/src/collection-manager/interfaces/checkbox.ts +2 -9
- package/src/collection-manager/interfaces/checkboxGroup.ts +2 -10
- package/src/collection-manager/interfaces/collection.ts +2 -15
- package/src/collection-manager/interfaces/color.ts +2 -2
- package/src/collection-manager/interfaces/createdAt.ts +2 -2
- package/src/collection-manager/interfaces/createdBy.ts +1 -12
- package/src/collection-manager/interfaces/dateOnly.ts +8 -2
- package/src/collection-manager/interfaces/datetime.ts +2 -2
- package/src/collection-manager/interfaces/datetimeNoTz.ts +2 -2
- package/src/collection-manager/interfaces/email.ts +2 -9
- package/src/collection-manager/interfaces/id.ts +1 -2
- package/src/collection-manager/interfaces/index.ts +2 -3
- package/src/collection-manager/interfaces/input.ts +2 -133
- package/src/collection-manager/interfaces/integer.ts +2 -71
- package/src/collection-manager/interfaces/json.tsx +17 -11
- package/src/collection-manager/interfaces/m2m.tsx +0 -21
- package/src/collection-manager/interfaces/m2o.tsx +0 -22
- package/src/collection-manager/interfaces/markdown.ts +2 -51
- package/src/collection-manager/interfaces/multipleSelect.ts +2 -14
- package/src/collection-manager/interfaces/nanoid.ts +2 -2
- package/src/collection-manager/interfaces/number.ts +2 -85
- package/src/collection-manager/interfaces/o2m.tsx +1 -22
- package/src/collection-manager/interfaces/obo.tsx +145 -0
- package/src/collection-manager/interfaces/oho.tsx +145 -0
- package/src/collection-manager/interfaces/password.ts +2 -44
- package/src/collection-manager/interfaces/percent.ts +2 -74
- package/src/collection-manager/interfaces/phone.ts +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +0 -133
- package/src/collection-manager/interfaces/radioGroup.ts +2 -2
- package/src/collection-manager/interfaces/richText.ts +2 -51
- package/src/collection-manager/interfaces/select.ts +2 -14
- package/src/collection-manager/interfaces/snowflake-id.ts +2 -2
- package/src/collection-manager/interfaces/tableoid.ts +1 -2
- package/src/collection-manager/interfaces/textarea.ts +2 -51
- package/src/collection-manager/interfaces/time.ts +2 -2
- package/src/collection-manager/interfaces/types.ts +4 -12
- package/src/collection-manager/interfaces/unixTimestamp.tsx +2 -2
- package/src/collection-manager/interfaces/updatedAt.ts +2 -2
- package/src/collection-manager/interfaces/updatedBy.ts +1 -12
- package/src/collection-manager/interfaces/url.ts +2 -4
- package/src/collection-manager/interfaces/uuid.ts +2 -2
- package/src/collection-manager/template-fields.ts +109 -0
- package/src/components/KeepAlive.tsx +131 -0
- package/src/components/README.md +138 -6
- package/src/components/README.zh-CN.md +138 -7
- package/src/components/RouterBridge.tsx +28 -4
- package/src/components/__tests__/KeepAlive.test.tsx +63 -0
- package/src/components/__tests__/RouterBridge.test.tsx +27 -0
- package/src/components/form/DialogFormLayout.tsx +5 -29
- package/src/components/form/TypedVariableInput.tsx +441 -0
- package/src/components/form/VariableInput.tsx +101 -28
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +152 -0
- package/src/components/form/__tests__/VariableInput.test.ts +85 -0
- package/src/components/form/filter/CollectionFilter.tsx +111 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +184 -0
- package/src/components/form/filter/DateFilterDynamicComponent.tsx +283 -0
- package/src/components/form/filter/FilterValueInput.tsx +198 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +247 -0
- package/src/components/form/filter/__tests__/DateFilterDynamicComponent.test.tsx +148 -0
- package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +243 -0
- package/src/components/form/filter/__tests__/compileFilterGroup.test.ts +146 -0
- package/src/components/form/filter/index.ts +13 -0
- package/src/components/form/filter/useFilterActionProps.ts +203 -0
- package/src/components/form/index.tsx +2 -0
- package/src/data-source/ExtendCollectionsProvider.tsx +144 -0
- package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
- package/src/data-source/index.ts +10 -0
- package/src/flow/FlowPage.tsx +35 -7
- package/src/flow/__tests__/FlowPage.test.tsx +79 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +537 -3
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +100 -0
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +23 -1
- package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
- package/src/flow/actions/aclCheck.tsx +4 -0
- package/src/flow/actions/aclCheckRefresh.tsx +4 -0
- package/src/flow/actions/dataScope.tsx +8 -1
- package/src/flow/actions/dateTimeFormat.tsx +12 -8
- package/src/flow/actions/linkageRules.tsx +127 -2
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -3
- package/src/flow/actions/openView.tsx +28 -4
- package/src/flow/actions/setTargetDataScope.tsx +1 -1
- package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
- package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +35 -7
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +15 -13
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
- package/src/flow/admin-shell/admin-layout/AppListRender.tsx +139 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +76 -27
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
- package/src/flow/admin-shell/admin-layout/index.ts +3 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +11 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +2 -4
- package/src/flow/admin-shell/admin-layout/useApplications.tsx +34 -1
- package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
- package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
- package/src/flow/common/Markdown/Edit.tsx +32 -4
- package/src/flow/components/AdminLayout.tsx +4 -154
- package/src/flow/components/ConditionBuilder.tsx +12 -2
- package/src/flow/components/FieldAssignRulesEditor.tsx +37 -46
- package/src/flow/components/FieldAssignValueInput.tsx +8 -4
- package/src/flow/components/FlowRoute.tsx +105 -15
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +112 -1
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +71 -1
- package/src/flow/components/code-editor/__tests__/runjsCompletions.test.ts +272 -27
- package/src/flow/components/code-editor/runjsCompletions.ts +490 -9
- package/src/flow/components/fieldAssignOptions.ts +23 -6
- package/src/flow/components/filter/LinkageFilterItem.tsx +22 -9
- package/src/flow/components/filter/VariableFilterItem.tsx +27 -9
- package/src/flow/components/filter/__tests__/fieldsToOptions.test.ts +48 -0
- package/src/flow/components/filter/__tests__/metaTreeAssociationDepth.test.ts +60 -0
- package/src/flow/components/filter/fieldsToOptions.ts +9 -4
- package/src/flow/components/filter/index.ts +3 -0
- package/src/flow/components/filter/metaTreeAssociationDepth.ts +69 -0
- package/src/flow/components/filter/useFilterOptions.ts +102 -0
- package/src/flow/index.ts +4 -0
- package/src/flow/models/actions/FilterActionModel.tsx +15 -3
- package/src/flow/models/actions/UpdateRecordActionModel.tsx +14 -95
- package/src/flow/models/actions/UpdateRecordActionUtils.ts +4 -7
- package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +116 -1
- package/src/flow/models/base/ActionModel.tsx +8 -1
- package/src/flow/models/base/AssociationFieldGroupModel.tsx +20 -3
- package/src/flow/models/base/GridModel.tsx +1 -1
- package/src/flow/models/base/PageModel/ChildPageModel.tsx +5 -1
- package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
- package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
- package/src/flow/models/base/PageModel/__tests__/ChildPageModel.test.ts +12 -0
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
- package/src/flow/models/base/RouteModel.tsx +1 -1
- package/src/flow/models/base/__tests__/AssociationFieldGroupModel.test.ts +86 -0
- package/src/flow/models/base/__tests__/GridModel.render.test.tsx +53 -0
- package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +12 -3
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +77 -6
- package/src/flow/models/blocks/assign-form/__tests__/assignFieldValuesFlow.editor.test.tsx +216 -0
- package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +308 -0
- package/src/flow/models/blocks/assign-form/index.ts +1 -0
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +1 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +6 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/connectFields.tsx +48 -6
- package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +30 -3
- package/src/flow/models/blocks/form/FormAssociationItemModel.tsx +9 -0
- package/src/flow/models/blocks/form/FormItemModel.tsx +17 -1
- package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/__tests__/FormItemModel.defineChildren.test.ts +27 -2
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +71 -0
- package/src/flow/models/blocks/form/submitHandler.ts +8 -1
- package/src/flow/models/blocks/form/submitValues.ts +4 -1
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +29 -0
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +5 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
- package/src/flow/models/blocks/table/TableColumnModel.tsx +15 -5
- package/src/flow/models/blocks/table/TableCustomColumnModel.tsx +2 -2
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +18 -0
- package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +6 -7
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
- package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
- package/src/flow/models/fields/CollectionSelectorFieldModel.tsx +8 -2
- package/src/flow/models/fields/DisplayEnumFieldModel.tsx +8 -2
- package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
- package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
- package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
- package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +5 -3
- package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +70 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
- package/src/index.ts +6 -0
- package/src/layout-manager/LayoutContentRoute.tsx +90 -0
- package/src/layout-manager/LayoutManager.tsx +185 -0
- package/src/layout-manager/LayoutRoute.tsx +138 -0
- package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
- package/src/layout-manager/index.ts +14 -0
- package/src/layout-manager/types.ts +22 -0
- package/src/layout-manager/utils.ts +37 -0
- package/src/nocobase-buildin-plugin/index.tsx +69 -67
- package/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts +1 -0
- package/src/settings-center/index.ts +1 -1
- package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
- package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
- package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
- package/src/settings-center/plugin-manager/index.tsx +254 -0
- package/src/settings-center/plugin-manager/types.ts +35 -0
- package/src/settings-center/utils.tsx +8 -1
- package/src/theme/__tests__/globalStyles.test.ts +24 -0
- package/src/theme/globalStyles.ts +10 -0
- package/src/utils/globalDeps.ts +2 -0
- package/es/collection-manager/interfaces/linkTo.d.ts +0 -90
- package/es/collection-manager/interfaces/o2o.d.ts +0 -621
- package/es/collection-manager/interfaces/properties/operators.d.ts +0 -294
- package/es/collection-manager/interfaces/subTable.d.ts +0 -172
- package/src/collection-manager/interfaces/linkTo.ts +0 -120
- package/src/collection-manager/interfaces/o2o.tsx +0 -561
- package/src/collection-manager/interfaces/subTable.ts +0 -218
- package/src/settings-center/PluginManagerPage.tsx +0 -162
|
@@ -0,0 +1,131 @@
|
|
|
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 { RouteContext } from '@ant-design/pro-layout';
|
|
11
|
+
import _ from 'lodash';
|
|
12
|
+
import React, { createContext, FC, memo, useContext, useRef } from 'react';
|
|
13
|
+
import {
|
|
14
|
+
UNSAFE_DataRouterContext,
|
|
15
|
+
UNSAFE_DataRouterStateContext,
|
|
16
|
+
UNSAFE_LocationContext,
|
|
17
|
+
UNSAFE_RouteContext,
|
|
18
|
+
} from 'react-router-dom';
|
|
19
|
+
|
|
20
|
+
const KeepAliveContext = createContext(true);
|
|
21
|
+
const hidden = { display: 'none' };
|
|
22
|
+
|
|
23
|
+
export const KeepAliveProvider: FC<{ active: boolean; parentActive: boolean }> = memo(
|
|
24
|
+
({ children, active, parentActive }) => {
|
|
25
|
+
const currentLocationContext = useContext(UNSAFE_LocationContext);
|
|
26
|
+
const currentRouteContext = useContext(UNSAFE_RouteContext);
|
|
27
|
+
const currentDataRouterContext = useContext(UNSAFE_DataRouterContext);
|
|
28
|
+
const currentDataRouterStateContext = useContext(UNSAFE_DataRouterStateContext);
|
|
29
|
+
const routeContextValue = useContext(RouteContext);
|
|
30
|
+
|
|
31
|
+
const prevLocationContextRef = useRef(currentLocationContext);
|
|
32
|
+
const prevRouteContextRef = useRef(currentRouteContext);
|
|
33
|
+
const prevDataRouterContextRef = useRef(currentDataRouterContext);
|
|
34
|
+
const prevDataRouterStateContextRef = useRef(currentDataRouterStateContext);
|
|
35
|
+
const prevRouteContextValueRef = useRef(routeContextValue);
|
|
36
|
+
|
|
37
|
+
if (active) {
|
|
38
|
+
prevDataRouterContextRef.current = currentDataRouterContext;
|
|
39
|
+
prevDataRouterStateContextRef.current = currentDataRouterStateContext;
|
|
40
|
+
prevRouteContextValueRef.current = routeContextValue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (
|
|
44
|
+
active &&
|
|
45
|
+
!_.isEqual(_.omit(prevLocationContextRef.current.location, 'key'), _.omit(currentLocationContext.location, 'key'))
|
|
46
|
+
) {
|
|
47
|
+
prevLocationContextRef.current = currentLocationContext;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (active && !_.isEqual(prevRouteContextRef.current, currentRouteContext)) {
|
|
51
|
+
prevRouteContextRef.current = currentRouteContext;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div style={active ? { height: '100%' } : hidden}>
|
|
56
|
+
<RouteContext.Provider value={prevRouteContextValueRef.current}>
|
|
57
|
+
<UNSAFE_DataRouterContext.Provider value={prevDataRouterContextRef.current}>
|
|
58
|
+
<UNSAFE_DataRouterStateContext.Provider value={prevDataRouterStateContextRef.current}>
|
|
59
|
+
<UNSAFE_LocationContext.Provider value={prevLocationContextRef.current}>
|
|
60
|
+
<UNSAFE_RouteContext.Provider value={prevRouteContextRef.current}>
|
|
61
|
+
<KeepAliveContext.Provider value={parentActive === false ? false : active}>
|
|
62
|
+
{children}
|
|
63
|
+
</KeepAliveContext.Provider>
|
|
64
|
+
</UNSAFE_RouteContext.Provider>
|
|
65
|
+
</UNSAFE_LocationContext.Provider>
|
|
66
|
+
</UNSAFE_DataRouterStateContext.Provider>
|
|
67
|
+
</UNSAFE_DataRouterContext.Provider>
|
|
68
|
+
</RouteContext.Provider>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
export const useKeepAlive = () => {
|
|
75
|
+
const active = useContext(KeepAliveContext);
|
|
76
|
+
return { active };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
interface KeepAliveProps {
|
|
80
|
+
uid: string;
|
|
81
|
+
children: (uid: string) => React.ReactNode;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const MINIMUM_CACHED_PAGES = 5;
|
|
85
|
+
const MAXIMUM_CACHED_PAGES = 15;
|
|
86
|
+
|
|
87
|
+
const getMaxPageCount = () => {
|
|
88
|
+
const baseCount = MINIMUM_CACHED_PAGES;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const memory = (navigator as any).deviceMemory;
|
|
92
|
+
if (memory) {
|
|
93
|
+
return Math.min(Math.max(baseCount, memory * 3), MAXIMUM_CACHED_PAGES);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const cores = navigator.hardwareConcurrency;
|
|
97
|
+
if (cores) {
|
|
98
|
+
return cores >= 8 ? MAXIMUM_CACHED_PAGES : cores >= 4 ? 7 : baseCount;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return baseCount;
|
|
102
|
+
} catch (e) {
|
|
103
|
+
return baseCount;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const MAX_RENDERED_PAGE_COUNT = getMaxPageCount();
|
|
108
|
+
|
|
109
|
+
export const KeepAlive: FC<KeepAliveProps> = React.memo(({ children, uid }) => {
|
|
110
|
+
const { active } = useKeepAlive();
|
|
111
|
+
const renderedUidListRef = useRef<string[]>([]);
|
|
112
|
+
|
|
113
|
+
if (!renderedUidListRef.current.includes(uid)) {
|
|
114
|
+
renderedUidListRef.current.push(uid);
|
|
115
|
+
if (renderedUidListRef.current.length > MAX_RENDERED_PAGE_COUNT) {
|
|
116
|
+
renderedUidListRef.current = renderedUidListRef.current.slice(-MAX_RENDERED_PAGE_COUNT);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<>
|
|
122
|
+
{renderedUidListRef.current.map((renderedUid) => (
|
|
123
|
+
<KeepAliveProvider active={renderedUid === uid} key={renderedUid} parentActive={active}>
|
|
124
|
+
{children(renderedUid)}
|
|
125
|
+
</KeepAliveProvider>
|
|
126
|
+
))}
|
|
127
|
+
</>
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
KeepAlive.displayName = 'KeepAlive';
|
package/src/components/README.md
CHANGED
|
@@ -14,9 +14,9 @@ Grouped by purpose: form containers, form fields, data table, utilities.
|
|
|
14
14
|
|
|
15
15
|
#### DrawerFormLayout
|
|
16
16
|
|
|
17
|
-
Drawer-style form layout. Pair with `ctx.viewer.drawer({ content })`.
|
|
17
|
+
Drawer-style form layout. Pair with `ctx.viewer.drawer({ closable: true, content })`.
|
|
18
18
|
|
|
19
|
-
- Top:
|
|
19
|
+
- Top: title only; the native close X is rendered by antd Drawer — you must pass `closable: true` on the `viewer.drawer` call for it to appear
|
|
20
20
|
- Bottom: default Cancel / Submit buttons; override the whole footer with `footer`
|
|
21
21
|
- Middle: caller-supplied `<Form>` instance + fields
|
|
22
22
|
|
|
@@ -25,6 +25,7 @@ import { DrawerFormLayout } from '@nocobase/client-v2';
|
|
|
25
25
|
|
|
26
26
|
ctx.viewer.drawer({
|
|
27
27
|
width: '50%',
|
|
28
|
+
closable: true, // restore antd Drawer's native close X
|
|
28
29
|
content: () => (
|
|
29
30
|
<DrawerFormLayout
|
|
30
31
|
title={t('Add authenticator')}
|
|
@@ -41,17 +42,19 @@ ctx.viewer.drawer({
|
|
|
41
42
|
|
|
42
43
|
Key props:
|
|
43
44
|
|
|
44
|
-
- `title`: title node
|
|
45
|
-
- `
|
|
45
|
+
- `title`: title node
|
|
46
|
+
- `onSubmit`: callback; the drawer closes automatically once it resolves. Throw to keep the drawer open (e.g. on a validation error)
|
|
46
47
|
- `submitting`: drives the Submit button's loading state
|
|
47
48
|
- `submitText` / `cancelText`: button labels
|
|
48
49
|
- `footer`: full override of the footer content (replaces the default Cancel + Submit pair)
|
|
49
50
|
|
|
51
|
+
To intercept close (e.g. dirty-form confirmation), use the lower-level `viewer.drawer({ preventClose, beforeClose })` hooks — this layout no longer wraps a custom cancel handler.
|
|
52
|
+
|
|
50
53
|
#### DialogFormLayout
|
|
51
54
|
|
|
52
55
|
Dialog-style form layout, the centered counterpart of `DrawerFormLayout`. Pair with `ctx.viewer.dialog({ closable: true, content })`.
|
|
53
56
|
|
|
54
|
-
The only visual difference from the drawer version
|
|
57
|
+
The only visual difference from the drawer version is where the native close X sits — antd Drawer renders it at the top-left of the title bar, antd Modal at the top-right. Both layouts rely on the caller passing `closable: true` at the viewer call site; neither renders a close icon itself.
|
|
55
58
|
|
|
56
59
|
```tsx
|
|
57
60
|
import { DialogFormLayout } from '@nocobase/client-v2';
|
|
@@ -73,7 +76,7 @@ When to pick which:
|
|
|
73
76
|
- **Drawer**: long forms with lots of fields that benefit from a full-height side panel (settings-page "Add / Edit")
|
|
74
77
|
- **Dialog**: short forms that ask for quick confirmation (bind, change password, two-factor verify)
|
|
75
78
|
|
|
76
|
-
Props are identical to `DrawerFormLayout`
|
|
79
|
+
Props are nearly identical to `DrawerFormLayout`, with one extra: `DialogFormLayout` accepts an `onCancel` callback (fired by both the Cancel button and the native X) for "discard changes" confirmations.
|
|
77
80
|
|
|
78
81
|
### Form fields
|
|
79
82
|
|
|
@@ -161,10 +164,59 @@ Key props:
|
|
|
161
164
|
- `namespaces`: restrict the picker to specific top-level namespaces. Omit to expose every registered top-level property
|
|
162
165
|
- `extraNodes`: static leaves appended after the namespace-filtered nodes. Use for variables that only make sense in the current page (e.g. `$resetLink`)
|
|
163
166
|
- `converters`: override the default path ↔ string converters. `EnvVariableInput` uses this hook to lock its output to `$env`
|
|
167
|
+
- `delimiters`: token pair wrapping the stored variable reference. Defaults to `['{{', '}}']` (Handlebars HTML-escaped). Pass `['{{{', '}}}']` for fields rendered as HTML where escaping would corrupt the variable value — e.g. the in-app message body
|
|
164
168
|
- `value` / `onChange` / `placeholder` / `disabled`: standard controlled-input props
|
|
165
169
|
|
|
166
170
|
Under the hood `VariableInput` wraps `VariableHybridInput` (inline pills), `VariableTextArea` wraps `TextAreaWithContextSelector` (textarea + variable button). Both share the same MetaTree.
|
|
167
171
|
|
|
172
|
+
#### TypedVariableInput
|
|
173
|
+
|
|
174
|
+
Typed-constant + variable hybrid input. Ported from v1 `Variable.Input`'s `useTypedConstant` pattern: an italic `x` button on the right triggers a Cascader switcher `[Null | Constant<types> | Variable<…namespaces>]`; the left side renders the matching editor (`Input` / `InputNumber` / `Select(True/False)` / `DatePicker`) or a pill carrying the variable path.
|
|
175
|
+
|
|
176
|
+
Reach for this when a field **accepts both** a typed literal **and** a variable reference. The canonical example is `plugin-notification-email`'s SMTP `port` and `secure` fields: users can type a numeric port / boolean flag, or pass `{{ $env.SMTP_PORT }}` to read from environment variables.
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { TypedVariableInput } from '@nocobase/client-v2';
|
|
180
|
+
|
|
181
|
+
// Port — numeric constant + $env variable
|
|
182
|
+
<Form.Item name={['options', 'port']} label={t('Port')} initialValue={465}>
|
|
183
|
+
<TypedVariableInput
|
|
184
|
+
types={[['number', { min: 1, max: 65535, step: 1 }]]}
|
|
185
|
+
namespaces={['$env']}
|
|
186
|
+
/>
|
|
187
|
+
</Form.Item>
|
|
188
|
+
|
|
189
|
+
// Secure mode — boolean constant + $env variable
|
|
190
|
+
<Form.Item name={['options', 'secure']} label={t('Secure')} initialValue={true}>
|
|
191
|
+
<TypedVariableInput types={['boolean']} namespaces={['$env']} />
|
|
192
|
+
</Form.Item>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Key props:
|
|
196
|
+
|
|
197
|
+
- `types`: allowed constant types. Shape mirrors v1 `useTypedConstant` — pass bare type names (`['number', 'boolean']`) or `[type, editorProps]` tuples (`[['number', { min, max, step }]]`) to forward props to the underlying antd editor. Defaults to `['string', 'number', 'boolean', 'date']`. **Even when only one type is allowed, the `Constant` entry still expands into a typed submenu** (Number / Boolean / Date / String) — matches v1 so users can see what type the constant is
|
|
198
|
+
- `namespaces`: restrict the variable picker to specific top-level namespaces (e.g. `['$env']`). Omit to expose every namespace registered on `flowEngine.context`
|
|
199
|
+
- `extraNodes`: static leaves appended after the namespace-filtered nodes
|
|
200
|
+
- `nullable`: whether to expose the `Null` switcher entry. Default `true`. Combined with `Form.Item.rules={[{ required: true }]}`, the user can explicitly clear the field but submission is still blocked by validation — mirrors v1's "Null + required" pairing
|
|
201
|
+
- `delimiters`: variable-token delimiters, default `['{{', '}}']` — same as `VariableInput`
|
|
202
|
+
- `value` / `onChange` / `placeholder` / `disabled` / `style` / `className`: standard controlled-input props
|
|
203
|
+
|
|
204
|
+
Value shape:
|
|
205
|
+
|
|
206
|
+
- Constant: stored as the native type (`number` / `boolean` / `Date` / `string`)
|
|
207
|
+
- Variable: a string like `'{{ $env.SMTP_PORT }}'`
|
|
208
|
+
- Null: `null`
|
|
209
|
+
|
|
210
|
+
When **not** to use it:
|
|
211
|
+
|
|
212
|
+
- **Pure literal fields** (users will never pass a variable) → use the antd primitive directly (`InputNumber` / `Select` / `DatePicker` / `Input`) and skip the Cascader column overhead
|
|
213
|
+
- **Pure variable fields** (users will never pass a literal) → use `EnvVariableInput` (`$env`-only, with optional password masking) or `VariableInput` (general-purpose)
|
|
214
|
+
|
|
215
|
+
Capabilities skipped (present in v1, not yet ported to v2):
|
|
216
|
+
|
|
217
|
+
- `object` constant type (JSON editor) — v2 has no inline "JSON editor + Cascader switcher" yet; add when there's a concrete caller
|
|
218
|
+
- Async `loadChildren` cascading — most MetaTree namespaces are already eagerly resolved by `useFilteredMetaTree`, so this hasn't been needed
|
|
219
|
+
|
|
168
220
|
#### FileSizeInput
|
|
169
221
|
|
|
170
222
|
A byte-valued size input paired with a unit selector (Byte / KB / MB / GB). The persisted value is always in bytes; the displayed number is derived from the picked unit.
|
|
@@ -272,6 +324,48 @@ Companion exports:
|
|
|
272
324
|
- `PAGE_SIZE_OPTIONS`: suggested page-size dropdown values `[5, 10, 20, 50, 100, 200]`
|
|
273
325
|
- `SortHandle`: standalone handle component, exported from `@nocobase/client-v2` for embedding into custom columns
|
|
274
326
|
|
|
327
|
+
### Filter
|
|
328
|
+
|
|
329
|
+
#### CollectionFilter
|
|
330
|
+
|
|
331
|
+
Filter button bound to a Collection. Clicking opens a Popover hosting a multi-condition filter form (field picker + operator + value control). Submit dismisses the Popover and emits the compiled NocoBase filter via `onChange`; Reset keeps the Popover open and emits `undefined`.
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
import { CollectionFilter, ExtendCollectionsProvider } from '@nocobase/client-v2';
|
|
335
|
+
import lockedUsersCollection from '../../collections/locked-users';
|
|
336
|
+
|
|
337
|
+
function Page() {
|
|
338
|
+
const main = engine.context.dataSourceManager?.getDataSource?.('main');
|
|
339
|
+
const collection = main?.getCollection?.(lockedUsersCollection.name);
|
|
340
|
+
|
|
341
|
+
const listRequest = useRequest(
|
|
342
|
+
async (filter) => api.resource('lockedUsers').list({ ...(filter ? { filter } : {}) }),
|
|
343
|
+
{ defaultParams: [undefined] },
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<ExtendCollectionsProvider collections={[lockedUsersCollection]}>
|
|
348
|
+
<CollectionFilter collection={collection} onChange={listRequest.run} t={t} />
|
|
349
|
+
{/* table … */}
|
|
350
|
+
</ExtendCollectionsProvider>
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Key props:
|
|
356
|
+
|
|
357
|
+
- `collection`: the Collection that drives the field picker. The button is disabled while it's `undefined`
|
|
358
|
+
- `onChange: (filter) => void`: fired on Submit and Reset with the compiled NocoBase filter (`undefined` on Reset). Most pages forward straight to `listRequest.run`
|
|
359
|
+
- `t`: translator. Pass `useT()` from a plugin's `locale.ts` so server-side `{{t("…")}}` macros in field / operator labels get expanded — plain react-i18next's `t` leaves them as literal template strings
|
|
360
|
+
- `filterableFieldNames`: whitelist of root-level field names to expose
|
|
361
|
+
- `noIgnore`: bypass the whitelist
|
|
362
|
+
- `buttonText`: override the trigger label; defaults to `t('Filter')`
|
|
363
|
+
- `showCount`: show the `(N)` condition-count badge on the trigger; defaults to `true`
|
|
364
|
+
- `popoverProps` / `buttonProps`: pass-through to the antd `Popover` / `Button`
|
|
365
|
+
- `popoverMinWidth`: min-width of the popover body; defaults to `520`
|
|
366
|
+
|
|
367
|
+
If the target Collection is `schema-only` (not auto-published from the server to the v2 data source), wrap the page in `<ExtendCollectionsProvider>` so `CollectionFilter` can resolve it by name.
|
|
368
|
+
|
|
275
369
|
### Utilities
|
|
276
370
|
|
|
277
371
|
#### createFormRegistry
|
|
@@ -302,6 +396,44 @@ Use this when a plugin needs an extension point for "same name + same shape + di
|
|
|
302
396
|
|
|
303
397
|
Re-registering the same `name` overwrites the previous entry and emits a `console.warn` — HMR doesn't throw, and unintended duplicates surface in dev.
|
|
304
398
|
|
|
399
|
+
## data-source/
|
|
400
|
+
|
|
401
|
+
Components that wire collections / data sources into the React tree. Exported from the top level of `@nocobase/client-v2`.
|
|
402
|
+
|
|
403
|
+
### ExtendCollectionsProvider
|
|
404
|
+
|
|
405
|
+
Mount-scoped collection injector. On mount it registers the given collections into the target data source; on unmount it removes them. A `dataSource:loaded` listener re-applies the registration so mid-session reloads don't wipe injected collections.
|
|
406
|
+
|
|
407
|
+
```tsx
|
|
408
|
+
import { ExtendCollectionsProvider } from '@nocobase/client-v2';
|
|
409
|
+
import lockedUsersCollection from '../../collections/locked-users';
|
|
410
|
+
|
|
411
|
+
// Module-level constant — keeps the reference stable so the provider's
|
|
412
|
+
// effect doesn't re-run on every parent re-render.
|
|
413
|
+
const collections = [lockedUsersCollection];
|
|
414
|
+
|
|
415
|
+
export function LockedUsersPage() {
|
|
416
|
+
return (
|
|
417
|
+
<ExtendCollectionsProvider collections={collections}>
|
|
418
|
+
<LockedUsersPageInner />
|
|
419
|
+
</ExtendCollectionsProvider>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Key props:
|
|
425
|
+
|
|
426
|
+
- `collections: CollectionOptions[]`: collections to inject. The provider only adds names that aren't already present, and on unmount removes only the ones it added
|
|
427
|
+
- `dataSource`: target data source key; defaults to `'main'`
|
|
428
|
+
- `children`: subtree covered by the injection
|
|
429
|
+
|
|
430
|
+
When to use:
|
|
431
|
+
|
|
432
|
+
- The server-side collection is `schema-only` and doesn't get auto-published to the client data source (e.g. `lockedUsers`)
|
|
433
|
+
- You need a client-side mirror that should be visible only inside the current page, not registered globally
|
|
434
|
+
|
|
435
|
+
Typical pairing: use together with `<CollectionFilter>` — the provider makes the collection resolvable; the filter button consumes it.
|
|
436
|
+
|
|
305
437
|
## When to add a new component here
|
|
306
438
|
|
|
307
439
|
- Two or more plugins need the same field or container shape — promote it to this folder
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
#### DrawerFormLayout
|
|
16
16
|
|
|
17
|
-
抽屉形态的表单 layout。配合 `ctx.viewer.drawer({ content })` 用。
|
|
17
|
+
抽屉形态的表单 layout。配合 `ctx.viewer.drawer({ closable: true, content })` 用。
|
|
18
18
|
|
|
19
|
-
- 顶部 Header
|
|
19
|
+
- 顶部 Header:只放标题;左侧的关闭 X 来自 antd Drawer——必须在 `viewer.drawer` 上显式传 `closable: true` 才会出现
|
|
20
20
|
- 底部 Footer:默认 Cancel / Submit 两个按钮;可以用 `footer` 完全替换
|
|
21
21
|
- 中间 children:调用方自己放 `<Form>` 实例 + 字段
|
|
22
22
|
|
|
@@ -25,6 +25,7 @@ import { DrawerFormLayout } from '@nocobase/client-v2';
|
|
|
25
25
|
|
|
26
26
|
ctx.viewer.drawer({
|
|
27
27
|
width: '50%',
|
|
28
|
+
closable: true, // 关键:开启 antd Drawer 原生关闭 X
|
|
28
29
|
content: () => (
|
|
29
30
|
<DrawerFormLayout
|
|
30
31
|
title={t('添加认证器')}
|
|
@@ -41,17 +42,19 @@ ctx.viewer.drawer({
|
|
|
41
42
|
|
|
42
43
|
主要属性:
|
|
43
44
|
|
|
44
|
-
- `title
|
|
45
|
-
- `
|
|
45
|
+
- `title`:标题节点
|
|
46
|
+
- `onSubmit`:回调,resolve 后会自动关闭抽屉。throw 可以让抽屉保持打开(比如校验失败)
|
|
46
47
|
- `submitting`:驱动 Submit 按钮的 loading
|
|
47
48
|
- `submitText` / `cancelText`:按钮文字
|
|
48
49
|
- `footer`:完全自定义 Footer 内容(覆盖默认两个按钮)
|
|
49
50
|
|
|
51
|
+
需要在关闭前做「未保存改动」之类的确认,用更底层的 `viewer.drawer({ preventClose, beforeClose })`,这层 layout 不再包装 cancel 拦截。
|
|
52
|
+
|
|
50
53
|
#### DialogFormLayout
|
|
51
54
|
|
|
52
|
-
弹窗形态的表单 layout,跟 `DrawerFormLayout`
|
|
55
|
+
弹窗形态的表单 layout,跟 `DrawerFormLayout` 同形。配合 `ctx.viewer.dialog({ closable: true, content })` 用。
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
视觉上的差异只有关闭 X 的位置——Drawer 是 antd Drawer 自带的左上角 X,Dialog 是 antd Modal 自带的右上角 X。两边都依赖在 viewer 调用处显式传 `closable: true`,layout 自己都不渲染 close 图标。
|
|
55
58
|
|
|
56
59
|
```tsx
|
|
57
60
|
import { DialogFormLayout } from '@nocobase/client-v2';
|
|
@@ -73,7 +76,7 @@ ctx.viewer.dialog({
|
|
|
73
76
|
- **Drawer**:长表单、字段多、需要从一侧滑出占用整面(比如设置页的「添加 / 编辑」)
|
|
74
77
|
- **Dialog**:短表单、需要快速确认(比如绑定、修改密码、二次验证)
|
|
75
78
|
|
|
76
|
-
属性跟 `DrawerFormLayout`
|
|
79
|
+
属性跟 `DrawerFormLayout` 基本一致,可以直接换。唯一区别:`DialogFormLayout` 多一个 `onCancel` 回调(Cancel 按钮和原生 X 都会触发),用于「丢弃改动」之类的确认。
|
|
77
80
|
|
|
78
81
|
### 表单字段
|
|
79
82
|
|
|
@@ -161,10 +164,59 @@ import { VariableInput, VariableTextArea } from '@nocobase/client-v2';
|
|
|
161
164
|
- `namespaces`:限定可选的顶层命名空间。不传就用 `flowEngine.context` 里全部已注册的
|
|
162
165
|
- `extraNodes`:在命名空间过滤后追加几条静态变量(用于 `$resetLink` 这类只在当前页面有意义的局部变量)
|
|
163
166
|
- `converters`:覆盖默认的 path ↔ string 转换器。`EnvVariableInput` 就是用这个钩子把输出锁定到 `$env`
|
|
167
|
+
- `delimiters`:变量在存储字符串里使用的开闭分隔符,默认 `['{{', '}}']`(对应 Handlebars 的 HTML 转义形式)。若字段最终以 HTML 渲染、转义会破坏变量内容(如站内信正文),传 `['{{{', '}}}']` 走 Handlebars 的原样输出形式
|
|
164
168
|
- `value` / `onChange` / `placeholder` / `disabled`:标准受控字段属性
|
|
165
169
|
|
|
166
170
|
底层共用 `VariableHybridInput`(`VariableInput`)和 `TextAreaWithContextSelector`(`VariableTextArea`),用同一套 MetaTree 数据。
|
|
167
171
|
|
|
172
|
+
#### TypedVariableInput
|
|
173
|
+
|
|
174
|
+
类型化常量 + 变量混合输入器。移植 v1 `Variable.Input` 的 `useTypedConstant` 形态:右侧斜体 `x` 按钮触发 Cascader 切换 `[空值 | 常量<types> | 变量和密钥<…namespaces>]`,左侧根据当前模式渲染对应编辑器(`Input` / `InputNumber` / `Select(True/False)` / `DatePicker`)或一颗带变量路径的 pill。
|
|
175
|
+
|
|
176
|
+
用于字段**同时接受**字面量**和**变量引用的场景。最典型的就是 `plugin-notification-email` 的 SMTP `port` 和 `secure`:可以填具体数字 / 布尔值,也可以填 `{{ $env.SMTP_PORT }}` 走环境变量。
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { TypedVariableInput } from '@nocobase/client-v2';
|
|
180
|
+
|
|
181
|
+
// 端口:数字常量 + $env 变量
|
|
182
|
+
<Form.Item name={['options', 'port']} label={t('端口')} initialValue={465}>
|
|
183
|
+
<TypedVariableInput
|
|
184
|
+
types={[['number', { min: 1, max: 65535, step: 1 }]]}
|
|
185
|
+
namespaces={['$env']}
|
|
186
|
+
/>
|
|
187
|
+
</Form.Item>
|
|
188
|
+
|
|
189
|
+
// 安全模式:布尔常量 + $env 变量
|
|
190
|
+
<Form.Item name={['options', 'secure']} label={t('安全模式')} initialValue={true}>
|
|
191
|
+
<TypedVariableInput types={['boolean']} namespaces={['$env']} />
|
|
192
|
+
</Form.Item>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
主要属性:
|
|
196
|
+
|
|
197
|
+
- `types`:允许的常量类型。形态对齐 v1 `useTypedConstant`,可以传裸类型名 `['number', 'boolean']`,也可以传 `[type, editorProps]` 元组 `[['number', { min, max, step }]]` 把 props 透传给底层 antd 编辑器。默认 `['string', 'number', 'boolean', 'date']`。**即使只允许一种类型,「常量」入口也会展开二级菜单**(数字 / 逻辑值 / 日期 / 字符串)——跟 v1 一致,让用户能直观看到当前常量是什么类型
|
|
198
|
+
- `namespaces`:限定变量 picker 可选的顶层命名空间(如 `['$env']`)。不传就用 `flowEngine.context` 里所有已注册命名空间
|
|
199
|
+
- `extraNodes`:在命名空间过滤后追加几条静态变量节点
|
|
200
|
+
- `nullable`:是否暴露「空值」入口,默认 `true`。配合 `Form.Item.rules={[{ required: true }]}` 可以让用户能手动清空、但提交时会被校验拦截——跟 v1 的「空值 + required」组合一致
|
|
201
|
+
- `delimiters`:变量 token 开闭分隔符,默认 `['{{', '}}']`,跟 `VariableInput` 一致
|
|
202
|
+
- `value` / `onChange` / `placeholder` / `disabled` / `style` / `className`:标准受控字段属性
|
|
203
|
+
|
|
204
|
+
值的形态:
|
|
205
|
+
|
|
206
|
+
- 常量:原生类型直接存(`number` / `boolean` / `Date` / `string`)
|
|
207
|
+
- 变量:字符串 `'{{ $env.SMTP_PORT }}'`
|
|
208
|
+
- 空值:`null`
|
|
209
|
+
|
|
210
|
+
什么时候**不该用**:
|
|
211
|
+
|
|
212
|
+
- **纯字面量字段**(用户不会想填变量)→ 直接用 antd `InputNumber` / `Select` / `DatePicker` / `Input`,省掉 Cascader 那一格的视觉开销
|
|
213
|
+
- **纯变量字段**(用户不会想填字面量)→ 用 `EnvVariableInput`(`$env` 专用,带 password mask)或 `VariableInput`(更通用)
|
|
214
|
+
|
|
215
|
+
跳过的能力(v1 有但 v2 还没补):
|
|
216
|
+
|
|
217
|
+
- `object` 类型(JSON 编辑器)——v2 还没对应的「内联 JSON 编辑器 + Cascader 切换」组件,等真有需求再补
|
|
218
|
+
- 异步 `loadChildren` 分支——大多数命名空间的 MetaTree 已经由 `useFilteredMetaTree` 提前展平,没遇到刚需
|
|
219
|
+
|
|
168
220
|
#### FileSizeInput
|
|
169
221
|
|
|
170
222
|
文件大小输入器。值统一存字节数,UI 上配一个单位选择器(Byte / KB / MB / GB)。
|
|
@@ -270,6 +322,48 @@ import { Table, DEFAULT_PAGE_SIZE } from '@nocobase/client-v2';
|
|
|
270
322
|
- `PAGE_SIZE_OPTIONS`:建议的分页选项 `[5, 10, 20, 50, 100, 200]`
|
|
271
323
|
- `SortHandle`:从 `@nocobase/client-v2` 导出的独立手柄组件,可以嵌进自定义列
|
|
272
324
|
|
|
325
|
+
### 筛选
|
|
326
|
+
|
|
327
|
+
#### CollectionFilter
|
|
328
|
+
|
|
329
|
+
绑定 Collection 的筛选按钮。点击展开 Popover,里面是多条件筛选表单(字段选择器 + 操作符 + 取值控件)。Submit 收起 Popover 并通过 `onChange` 发出 NocoBase filter 参数;Reset 保持 Popover 打开并发出 `undefined`。
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
import { CollectionFilter, ExtendCollectionsProvider } from '@nocobase/client-v2';
|
|
333
|
+
import lockedUsersCollection from '../../collections/locked-users';
|
|
334
|
+
|
|
335
|
+
function Page() {
|
|
336
|
+
const main = engine.context.dataSourceManager?.getDataSource?.('main');
|
|
337
|
+
const collection = main?.getCollection?.(lockedUsersCollection.name);
|
|
338
|
+
|
|
339
|
+
const listRequest = useRequest(
|
|
340
|
+
async (filter) => api.resource('lockedUsers').list({ ...(filter ? { filter } : {}) }),
|
|
341
|
+
{ defaultParams: [undefined] },
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<ExtendCollectionsProvider collections={[lockedUsersCollection]}>
|
|
346
|
+
<CollectionFilter collection={collection} onChange={listRequest.run} t={t} />
|
|
347
|
+
{/* table … */}
|
|
348
|
+
</ExtendCollectionsProvider>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
主要属性:
|
|
354
|
+
|
|
355
|
+
- `collection`:作为字段来源的 Collection。`undefined` 时按钮 disabled
|
|
356
|
+
- `onChange: (filter) => void`:Submit 或 Reset 时触发,参数是编译好的 NocoBase filter(Reset 时为 `undefined`)。常见做法是直接转给 `listRequest.run`
|
|
357
|
+
- `t`:翻译函数。建议传 `useT()`(来自插件 `locale.ts`),它会自动展开服务端返回的 `{{t("…")}}` 模板,否则字段标签、操作符标签可能显示成字面模板
|
|
358
|
+
- `filterableFieldNames`:白名单,限制顶层可选字段
|
|
359
|
+
- `noIgnore`:忽略白名单
|
|
360
|
+
- `buttonText`:覆盖按钮文字,默认 `t('Filter')`
|
|
361
|
+
- `showCount`:是否在按钮上显示当前条件数 `(N)`,默认 `true`
|
|
362
|
+
- `popoverProps` / `buttonProps`:透传给 antd `Popover` / `Button`
|
|
363
|
+
- `popoverMinWidth`:Popover 内容最小宽度,默认 `520`
|
|
364
|
+
|
|
365
|
+
要筛选的 Collection 如果是 `schema-only`(服务端没自动发布到客户端 data source),用 `<ExtendCollectionsProvider>` 包一下当前页面,让 `CollectionFilter` 能解析到。
|
|
366
|
+
|
|
273
367
|
### 工具
|
|
274
368
|
|
|
275
369
|
#### createFormRegistry
|
|
@@ -300,6 +394,43 @@ storageTypes.unregister('local');
|
|
|
300
394
|
|
|
301
395
|
`name` 重复注册会用新条目覆盖旧的,同时打 `console.warn`——HMR 时不抛错,开发期能看到意外的重复。
|
|
302
396
|
|
|
397
|
+
## data-source/
|
|
398
|
+
|
|
399
|
+
跟数据源 / Collection 注册相关的组件。从 `@nocobase/client-v2` 顶层 export。
|
|
400
|
+
|
|
401
|
+
### ExtendCollectionsProvider
|
|
402
|
+
|
|
403
|
+
挂载期 Collection 注入器。在组件挂载时把传入的 collection 注册到目标 data source,卸载时移除;会监听 `dataSource:loaded` 自动重新注入,确保数据源 reload 时不会被清掉。
|
|
404
|
+
|
|
405
|
+
```tsx
|
|
406
|
+
import { ExtendCollectionsProvider } from '@nocobase/client-v2';
|
|
407
|
+
import lockedUsersCollection from '../../collections/locked-users';
|
|
408
|
+
|
|
409
|
+
// 模块级常量——保证引用稳定,避免 provider 每次父级重渲染都重跑 effect
|
|
410
|
+
const collections = [lockedUsersCollection];
|
|
411
|
+
|
|
412
|
+
export function LockedUsersPage() {
|
|
413
|
+
return (
|
|
414
|
+
<ExtendCollectionsProvider collections={collections}>
|
|
415
|
+
<LockedUsersPageInner />
|
|
416
|
+
</ExtendCollectionsProvider>
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
主要属性:
|
|
422
|
+
|
|
423
|
+
- `collections: CollectionOptions[]`:本次要注入的 Collection。Provider 只会注册当时不存在的那些,卸载时也只移除自己注册过的
|
|
424
|
+
- `dataSource`:目标 data source key,默认 `'main'`
|
|
425
|
+
- `children`:被注入 Collection 覆盖的子树
|
|
426
|
+
|
|
427
|
+
什么时候用:
|
|
428
|
+
|
|
429
|
+
- 服务端 collection 是 `schema-only`,不会自动发布到客户端 data source(比如 `lockedUsers`)
|
|
430
|
+
- 需要一个纯客户端的 collection 镜像,只对当前页面有效,不污染全局
|
|
431
|
+
|
|
432
|
+
常见搭配:跟 `<CollectionFilter>` 一起用——前者把 collection 挂上,后者读取并渲染筛选表单。
|
|
433
|
+
|
|
303
434
|
## 怎么决定加不加新组件
|
|
304
435
|
|
|
305
436
|
- 出现两个及以上插件需要同一形态的字段或容器——抽到这里
|
|
@@ -7,11 +7,32 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { useLocation, useMatch, useMatches, useParams } from 'react-router-dom';
|
|
10
|
+
import { useEffect } from 'react';
|
|
11
|
+
import { useLocation, useMatches, useParams } from 'react-router-dom';
|
|
13
12
|
import { Application } from '../Application';
|
|
14
13
|
|
|
14
|
+
type LayoutMatchLike = {
|
|
15
|
+
id: string;
|
|
16
|
+
pathname: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type LayoutDefinitionLike = {
|
|
20
|
+
routeName: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function findDeepestLayoutMatch(layouts: LayoutDefinitionLike[] = [], matches: LayoutMatchLike[] = []) {
|
|
24
|
+
const layoutRouteNames = new Set(layouts.map((layout) => layout.routeName));
|
|
25
|
+
|
|
26
|
+
for (let index = matches.length - 1; index >= 0; index -= 1) {
|
|
27
|
+
const match = matches[index];
|
|
28
|
+
if (layoutRouteNames.has(match.id)) {
|
|
29
|
+
return match;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
15
36
|
export function useRouterSync(app: Application) {
|
|
16
37
|
const params = useParams();
|
|
17
38
|
const location = useLocation();
|
|
@@ -20,13 +41,16 @@ export function useRouterSync(app: Application) {
|
|
|
20
41
|
useEffect(() => {
|
|
21
42
|
const last = matches[matches.length - 1];
|
|
22
43
|
if (!last) return;
|
|
44
|
+
const layoutMatch = findDeepestLayoutMatch(app.layoutManager?.listLayouts?.(), matches);
|
|
23
45
|
engine.context['_observableCache']['route'] = {
|
|
24
46
|
name: last.id,
|
|
25
47
|
pathname: last.pathname,
|
|
26
48
|
path: last.handle?.['path'] || null,
|
|
27
49
|
params,
|
|
50
|
+
layoutRouteName: layoutMatch?.id,
|
|
51
|
+
layoutBasePathname: layoutMatch?.pathname,
|
|
28
52
|
};
|
|
29
|
-
}, [engine.context, params, matches]);
|
|
53
|
+
}, [app, engine.context, params, matches]);
|
|
30
54
|
useEffect(() => {
|
|
31
55
|
engine.context['_observableCache']['location'] = location;
|
|
32
56
|
}, [engine.context, location]);
|
|
@@ -0,0 +1,63 @@
|
|
|
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 { render, screen, waitFor } from '@testing-library/react';
|
|
11
|
+
import React, { useEffect } from 'react';
|
|
12
|
+
import { createMemoryRouter, Outlet, RouterProvider, useParams } from 'react-router-dom';
|
|
13
|
+
import { describe, expect, it } from 'vitest';
|
|
14
|
+
import { KeepAlive } from '../KeepAlive';
|
|
15
|
+
|
|
16
|
+
describe('KeepAlive', () => {
|
|
17
|
+
it('keeps inactive outlet pages mounted while switching route params', async () => {
|
|
18
|
+
const events: string[] = [];
|
|
19
|
+
|
|
20
|
+
const Page = () => {
|
|
21
|
+
const { name } = useParams();
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
events.push(`mount:${name}`);
|
|
24
|
+
return () => {
|
|
25
|
+
events.push(`unmount:${name}`);
|
|
26
|
+
};
|
|
27
|
+
}, [name]);
|
|
28
|
+
|
|
29
|
+
return <div>page {name}</div>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const Layout = () => {
|
|
33
|
+
const { name } = useParams();
|
|
34
|
+
return <KeepAlive uid={name || ''}>{() => <Outlet />}</KeepAlive>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const router = createMemoryRouter(
|
|
38
|
+
[
|
|
39
|
+
{
|
|
40
|
+
path: '/:name',
|
|
41
|
+
element: <Layout />,
|
|
42
|
+
children: [{ index: true, element: <Page /> }],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
{
|
|
46
|
+
initialEntries: ['/page-a'],
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
render(<RouterProvider router={router} />);
|
|
51
|
+
|
|
52
|
+
expect(await screen.findByText('page page-a')).toBeInTheDocument();
|
|
53
|
+
|
|
54
|
+
await router.navigate('/page-b');
|
|
55
|
+
|
|
56
|
+
expect(await screen.findByText('page page-b')).toBeInTheDocument();
|
|
57
|
+
expect(screen.getByText('page page-a')).toBeInTheDocument();
|
|
58
|
+
|
|
59
|
+
await waitFor(() => {
|
|
60
|
+
expect(events).toEqual(['mount:page-a', 'mount:page-b']);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|