@nocobase/client-v2 2.2.0-alpha.1 → 2.2.0-alpha.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.
- package/es/APIClient.d.ts +1 -3
- package/es/BaseApplication.d.ts +3 -0
- package/es/PluginSettingsManager.d.ts +33 -0
- package/es/RouteRepository.d.ts +29 -6
- package/es/RouterManager.d.ts +15 -0
- package/es/authRedirect.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
- package/es/components/form/TypedVariableInput.d.ts +31 -5
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/filter/CollectionFilter.d.ts +2 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +2 -0
- package/es/entry-actions/EntryActionManager.d.ts +24 -0
- package/es/entry-actions/index.d.ts +9 -0
- package/es/flow/actions/afterSuccess.d.ts +8 -1
- package/es/flow/actions/index.d.ts +2 -2
- package/es/flow/actions/linkageRules.d.ts +2 -0
- package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +3 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
- package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
- package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
- package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
- package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/RunJSValueEditor.d.ts +1 -0
- package/es/flow/components/filter/FilterGroup.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
- package/es/flow/index.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/models/base/ActionModelCore.d.ts +2 -0
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
- package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
- package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
- package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
- package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
- package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
- package/es/flow/routeTransientInputArgs.d.ts +14 -0
- package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
- package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
- package/es/flow/utils/formValueDeps.d.ts +32 -0
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
- package/es/flow-compat/routeTypes.d.ts +1 -0
- package/es/index.d.ts +8 -1
- package/es/index.mjs +367 -181
- package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
- package/es/utils/index.d.ts +1 -0
- package/es/utils/markdownSanitize.d.ts +11 -0
- package/lib/index.js +367 -181
- package/lib/locale/languageCodes.js +3 -1
- package/package.json +9 -7
- package/src/APIClient.ts +1 -10
- package/src/Application.tsx +4 -0
- package/src/BaseApplication.tsx +13 -6
- package/src/PluginSettingsManager.ts +53 -0
- package/src/RouteRepository.ts +151 -24
- package/src/RouterManager.tsx +142 -1
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/RouteRepository.test.ts +239 -0
- package/src/__tests__/RouterManager.test.ts +125 -0
- package/src/__tests__/app.test.tsx +73 -0
- package/src/__tests__/authRedirect.test.ts +17 -0
- package/src/__tests__/browserChecker.test.ts +61 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +234 -2
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +40 -1
- package/src/authRedirect.ts +38 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/collection-manager/field-configure.ts +1 -1
- package/src/collection-manager/field-validation.ts +1 -1
- package/src/collection-manager/interfaces/id.ts +1 -1
- package/src/collection-manager/interfaces/m2m.tsx +2 -2
- package/src/collection-manager/interfaces/m2o.tsx +2 -2
- package/src/collection-manager/interfaces/nanoid.ts +1 -1
- package/src/collection-manager/interfaces/o2m.tsx +2 -2
- package/src/collection-manager/interfaces/obo.tsx +2 -2
- package/src/collection-manager/interfaces/oho.tsx +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +2 -2
- package/src/collection-manager/interfaces/uuid.ts +1 -1
- package/src/components/AppComponents.tsx +19 -3
- package/src/components/README.md +7 -1
- package/src/components/README.zh-CN.md +6 -1
- package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
- package/src/components/category-tabs/index.ts +10 -0
- package/src/components/form/JsonTextArea.tsx +25 -11
- package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
- package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
- package/src/components/form/TypedVariableInput.tsx +452 -101
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/filter/CollectionFilter.tsx +4 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/form/filter/useFilterActionProps.ts +37 -6
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +2 -0
- package/src/entry-actions/EntryActionManager.ts +76 -0
- package/src/entry-actions/index.ts +10 -0
- package/src/flow/FlowPage.tsx +38 -3
- package/src/flow/__tests__/FlowPage.test.tsx +201 -27
- package/src/flow/__tests__/FlowRoute.test.tsx +1181 -80
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +115 -0
- package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +120 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1124 -0
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
- package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
- package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
- package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
- package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
- package/src/flow/actions/afterSuccess.tsx +142 -3
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/dataScopeFilter.ts +20 -1
- package/src/flow/actions/dateTimeFormat.tsx +42 -28
- package/src/flow/actions/index.ts +3 -1
- package/src/flow/actions/linkageRules.tsx +209 -47
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/actions/openView.tsx +59 -5
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +74 -14
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
- package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
- package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
- package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +435 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
- package/src/flow/admin-shell/admin-layout/index.ts +1 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
- package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
- package/src/flow/common/Markdown/Display.tsx +14 -3
- package/src/flow/common/Markdown/Edit.tsx +19 -7
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
- package/src/flow/components/FieldAssignValueInput.tsx +91 -20
- package/src/flow/components/FlowRoute.tsx +256 -53
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
- package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
- package/src/flow/components/fieldAssignOptions.ts +155 -27
- package/src/flow/components/filter/FilterGroup.tsx +33 -5
- package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
- package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
- package/src/flow/flows/editMarkdownFlow.tsx +1 -1
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/index.ts +4 -1
- package/src/flow/internal/components/Markdown/util.ts +2 -1
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
- package/src/flow/models/actions/JSActionModel.tsx +2 -7
- package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
- package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
- package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
- package/src/flow/models/base/ActionModel.tsx +21 -8
- package/src/flow/models/base/ActionModelCore.tsx +11 -4
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +387 -50
- package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
- package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +817 -13
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
- package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +59 -6
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +143 -4
- package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
- package/src/flow/models/blocks/form/submitHandler.ts +17 -3
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
- package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
- package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +38 -14
- package/src/flow/models/blocks/table/TableBlockModel.tsx +42 -19
- package/src/flow/models/blocks/table/TableColumnModel.tsx +72 -6
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +243 -1
- package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
- package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
- package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
- package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +31 -7
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +61 -17
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
- package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
- package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
- package/src/flow/models/fields/InputFieldModel.tsx +40 -2
- package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
- package/src/flow/models/fields/JSFieldModel.tsx +2 -7
- package/src/flow/models/fields/JSItemModel.tsx +2 -14
- package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
- package/src/flow/models/fields/TextareaFieldModel.tsx +72 -3
- package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
- package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
- package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +131 -0
- package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
- package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
- package/src/flow/resolveViewParamsToViewList.tsx +11 -3
- package/src/flow/routeTransientInputArgs.ts +27 -0
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +300 -0
- package/src/flow/utils/dataScopeFormValueClear.ts +219 -81
- package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
- package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
- package/src/flow/utils/formValueDeps.ts +170 -0
- package/src/flow-compat/FieldValidation.tsx +122 -60
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/flow-compat/fieldValidationConstants.ts +1 -1
- package/src/flow-compat/routeTypes.ts +1 -0
- package/src/index.ts +14 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/locale/languageCodes.ts +3 -1
- package/src/nocobase-buildin-plugin/index.tsx +63 -15
- package/src/settings-center/AdminSettingsLayout.tsx +10 -2
- package/src/settings-center/SystemSettingsPage.tsx +1 -2
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/plugin-manager/index.tsx +3 -0
- package/src/settings-center/utils.tsx +0 -6
- package/src/utils/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -0
- package/src/utils/markdownSanitize.ts +88 -0
|
@@ -8,26 +8,49 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { type FlowEngine, useFlowContext, useFlowEngine } from '@nocobase/flow-engine';
|
|
11
|
+
import { Button, Result } from 'antd';
|
|
11
12
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
12
|
-
import {
|
|
13
|
-
import { useParams } from 'react-router-dom';
|
|
13
|
+
import { useTranslation } from 'react-i18next';
|
|
14
|
+
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
|
15
|
+
import { getModernClientPrefix, stripModernClientPrefix } from '../../authRedirect';
|
|
14
16
|
import { useApp } from '../../hooks/useApp';
|
|
15
|
-
import { NocoBaseDesktopRouteType } from '../../flow-compat';
|
|
16
|
-
import {
|
|
17
|
+
import { NocoBaseDesktopRouteType, type NocoBaseDesktopRoute } from '../../flow-compat';
|
|
18
|
+
import {
|
|
19
|
+
resolveAdminRouteRuntimeTarget,
|
|
20
|
+
toRouterNavigationPath,
|
|
21
|
+
} from '../admin-shell/admin-layout/resolveAdminRouteRuntimeTarget';
|
|
17
22
|
import { getAdminLayoutModel, type AdminLayoutModel } from '../admin-shell/admin-layout/AdminLayoutModel';
|
|
18
23
|
import { getLayoutModel, type BaseLayoutModel } from '../admin-shell/BaseLayoutModel';
|
|
19
24
|
import { useLayoutRoutePage } from '../admin-shell/useLayoutRoutePage';
|
|
20
25
|
import { AppNotFound } from '../../components';
|
|
21
26
|
import { useKeepAlive } from '../../components/KeepAlive';
|
|
27
|
+
import { registerDeviceTypeContext } from '../internal/registerDeviceTypeContext';
|
|
22
28
|
|
|
23
29
|
type FlowRouteGuardState = {
|
|
30
|
+
pageUid?: string;
|
|
24
31
|
pending: boolean;
|
|
25
32
|
allowBridge: boolean;
|
|
26
33
|
notFound: boolean;
|
|
34
|
+
legacyPageUnsupported?: boolean;
|
|
27
35
|
};
|
|
28
36
|
|
|
29
37
|
export type LegacyPageBehavior = 'redirect' | 'notFound' | 'bridge';
|
|
30
38
|
|
|
39
|
+
type FlowRouteLayoutContext = {
|
|
40
|
+
authCheck?: boolean;
|
|
41
|
+
routePath?: string;
|
|
42
|
+
routeName?: string;
|
|
43
|
+
uid?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type FlowRouteRepositoryLike = {
|
|
47
|
+
refreshAccessible?: () => Promise<unknown>;
|
|
48
|
+
isAccessibleLoaded?: () => boolean;
|
|
49
|
+
ensureAccessibleLoaded?: () => Promise<unknown>;
|
|
50
|
+
getRouteBySchemaUid?: (schemaUid: string) => NocoBaseDesktopRoute | undefined;
|
|
51
|
+
listAccessible?: () => NocoBaseDesktopRoute[];
|
|
52
|
+
};
|
|
53
|
+
|
|
31
54
|
export type FlowRouteProps = {
|
|
32
55
|
pageUid?: string;
|
|
33
56
|
active?: boolean;
|
|
@@ -38,7 +61,7 @@ export type FlowRouteProps = {
|
|
|
38
61
|
const getDefaultAdminLayoutModel = (flowEngine: FlowEngine) =>
|
|
39
62
|
getAdminLayoutModel<AdminLayoutModel>(flowEngine, { required: true });
|
|
40
63
|
|
|
41
|
-
const getDefaultLayoutModel = (flowEngine: FlowEngine, contextLayout?:
|
|
64
|
+
const getDefaultLayoutModel = (flowEngine: FlowEngine, contextLayout?: FlowRouteLayoutContext) => {
|
|
42
65
|
const layout = contextLayout || flowEngine.context.layout;
|
|
43
66
|
|
|
44
67
|
if (layout?.uid) {
|
|
@@ -48,7 +71,10 @@ const getDefaultLayoutModel = (flowEngine: FlowEngine, contextLayout?: any) => {
|
|
|
48
71
|
return getDefaultAdminLayoutModel(flowEngine);
|
|
49
72
|
};
|
|
50
73
|
|
|
51
|
-
const getDefaultLegacyPageBehavior = (
|
|
74
|
+
const getDefaultLegacyPageBehavior = (
|
|
75
|
+
flowEngine: FlowEngine,
|
|
76
|
+
contextLayout?: FlowRouteLayoutContext,
|
|
77
|
+
): LegacyPageBehavior => {
|
|
52
78
|
const layout = contextLayout || flowEngine.context.layout;
|
|
53
79
|
|
|
54
80
|
if (layout?.routeName && layout.routeName !== 'admin') {
|
|
@@ -58,6 +84,46 @@ const getDefaultLegacyPageBehavior = (flowEngine: FlowEngine, contextLayout?: an
|
|
|
58
84
|
return 'redirect';
|
|
59
85
|
};
|
|
60
86
|
|
|
87
|
+
const shouldRequireAccessibleRoute = (contextLayout?: FlowRouteLayoutContext) => contextLayout?.authCheck !== false;
|
|
88
|
+
|
|
89
|
+
const findAccessibleRouteByIdentity = (
|
|
90
|
+
routes: NocoBaseDesktopRoute[] | undefined,
|
|
91
|
+
pageUid: string,
|
|
92
|
+
): NocoBaseDesktopRoute | undefined => {
|
|
93
|
+
if (!Array.isArray(routes)) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const route of routes) {
|
|
98
|
+
if (
|
|
99
|
+
(route.type !== NocoBaseDesktopRouteType.group && route.schemaUid === pageUid) ||
|
|
100
|
+
(route.type === NocoBaseDesktopRouteType.group && route.id != null && String(route.id) === pageUid)
|
|
101
|
+
) {
|
|
102
|
+
return route;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const matched = findAccessibleRouteByIdentity(route.children, pageUid);
|
|
106
|
+
if (matched) {
|
|
107
|
+
return matched;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return undefined;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const getAccessibleRouteByPageUid = (
|
|
115
|
+
routeRepository: FlowRouteRepositoryLike | undefined,
|
|
116
|
+
pageUid: string,
|
|
117
|
+
): NocoBaseDesktopRoute | undefined => {
|
|
118
|
+
const routeBySchemaUid = routeRepository?.getRouteBySchemaUid?.(pageUid);
|
|
119
|
+
|
|
120
|
+
if (routeBySchemaUid && routeBySchemaUid.type !== NocoBaseDesktopRouteType.group) {
|
|
121
|
+
return routeBySchemaUid;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return findAccessibleRouteByIdentity(routeRepository?.listAccessible?.(), pageUid);
|
|
125
|
+
};
|
|
126
|
+
|
|
61
127
|
const hasFlowModel = async (flowEngine: FlowEngine, pageUid: string) => {
|
|
62
128
|
if (flowEngine.getModel(pageUid)) {
|
|
63
129
|
return true;
|
|
@@ -94,31 +160,9 @@ const BridgeFlowRoute = ({
|
|
|
94
160
|
const layoutContentRef = useRef<HTMLDivElement>(null);
|
|
95
161
|
|
|
96
162
|
useEffect(() => {
|
|
97
|
-
flowEngine.context.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
meta: {
|
|
101
|
-
type: 'string',
|
|
102
|
-
title: flowEngine.translate('Current device type'),
|
|
103
|
-
interface: 'select',
|
|
104
|
-
uiSchema: {
|
|
105
|
-
enum: [
|
|
106
|
-
{ label: flowEngine.translate('Computer'), value: 'computer' },
|
|
107
|
-
{ label: flowEngine.translate('Mobile'), value: 'mobile' },
|
|
108
|
-
{ label: flowEngine.translate('Tablet'), value: 'tablet' },
|
|
109
|
-
{ label: flowEngine.translate('SmartTv'), value: 'smarttv' },
|
|
110
|
-
{ label: flowEngine.translate('Console'), value: 'console' },
|
|
111
|
-
{ label: flowEngine.translate('Wearable'), value: 'wearable' },
|
|
112
|
-
{ label: flowEngine.translate('Embedded'), value: 'embedded' },
|
|
113
|
-
],
|
|
114
|
-
'x-component': 'Select',
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
info: {
|
|
118
|
-
description: 'Current device type (computer/mobile/tablet/...).',
|
|
119
|
-
detail: 'string',
|
|
120
|
-
},
|
|
121
|
-
});
|
|
163
|
+
if (!flowEngine.context.getPropertyOptions('deviceType')) {
|
|
164
|
+
registerDeviceTypeContext(flowEngine);
|
|
165
|
+
}
|
|
122
166
|
}, [flowEngine]);
|
|
123
167
|
|
|
124
168
|
useLayoutRoutePage({
|
|
@@ -133,11 +177,58 @@ const BridgeFlowRoute = ({
|
|
|
133
177
|
return <div ref={layoutContentRef} />;
|
|
134
178
|
};
|
|
135
179
|
|
|
180
|
+
type RouteLocation = {
|
|
181
|
+
pathname: string;
|
|
182
|
+
search: string;
|
|
183
|
+
hash: string;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const getLegacyPageHref = (app: { getPublicPath: () => string }, location: RouteLocation) => {
|
|
187
|
+
const modernPublicPath = app.getPublicPath();
|
|
188
|
+
const browserLocationMatchesPublicPath = window.location.pathname.startsWith(modernPublicPath);
|
|
189
|
+
const currentLocation = browserLocationMatchesPublicPath ? window.location : location;
|
|
190
|
+
const pathWithinModernClient = currentLocation.pathname.startsWith(modernPublicPath)
|
|
191
|
+
? currentLocation.pathname.slice(modernPublicPath.length)
|
|
192
|
+
: currentLocation.pathname.replace(/^\/+/, '');
|
|
193
|
+
return `${stripModernClientPrefix(modernPublicPath)}${pathWithinModernClient}${currentLocation.search}${
|
|
194
|
+
currentLocation.hash
|
|
195
|
+
}`;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const LegacyPageUnsupported = ({
|
|
199
|
+
app,
|
|
200
|
+
location,
|
|
201
|
+
}: {
|
|
202
|
+
app: { getPublicPath: () => string };
|
|
203
|
+
location: RouteLocation;
|
|
204
|
+
}) => {
|
|
205
|
+
const { t } = useTranslation();
|
|
206
|
+
const modernClientPath = `/${getModernClientPrefix()}/`;
|
|
207
|
+
const withModernClientPath = (message: string) => message.replaceAll('{{modernClientPath}}', modernClientPath);
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<Result
|
|
211
|
+
status="warning"
|
|
212
|
+
title={withModernClientPath(t('This page is not supported in the {{modernClientPath}} branch'))}
|
|
213
|
+
subTitle={withModernClientPath(
|
|
214
|
+
t(
|
|
215
|
+
'The {{modernClientPath}} branch only supports new pages. This page is a legacy page. Please open it from the original entry.',
|
|
216
|
+
),
|
|
217
|
+
)}
|
|
218
|
+
extra={
|
|
219
|
+
<Button href={getLegacyPageHref(app, location)} type="primary">
|
|
220
|
+
{t('Open from the original entry')}
|
|
221
|
+
</Button>
|
|
222
|
+
}
|
|
223
|
+
/>
|
|
224
|
+
);
|
|
225
|
+
};
|
|
226
|
+
|
|
136
227
|
/**
|
|
137
228
|
* 管理后台动态页面路由组件。
|
|
138
229
|
*
|
|
139
|
-
* 负责读取当前路由页面 UID
|
|
140
|
-
*
|
|
230
|
+
* 负责读取当前路由页面 UID,并把页面生命周期桥接到 AdminLayout host model。
|
|
231
|
+
* 设备变量通常由 PluginFlowEngine 共享初始化提供;独立渲染时会在挂载后补充注册。
|
|
141
232
|
*
|
|
142
233
|
* @example
|
|
143
234
|
* ```tsx
|
|
@@ -148,20 +239,52 @@ const BridgeFlowRoute = ({
|
|
|
148
239
|
* @throws {Error} 当缺少 `route.params.name` 时抛出异常
|
|
149
240
|
*/
|
|
150
241
|
const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
242
|
+
const {
|
|
243
|
+
active,
|
|
244
|
+
getLayoutModel: getLayoutModelProp,
|
|
245
|
+
legacyPageBehavior: legacyPageBehaviorProp,
|
|
246
|
+
pageUid: pageUidProp,
|
|
247
|
+
} = props;
|
|
151
248
|
const flowEngine = useFlowEngine();
|
|
152
|
-
const flowContext = useFlowContext<
|
|
249
|
+
const flowContext = useFlowContext<{ layout?: FlowRouteLayoutContext }>();
|
|
153
250
|
const contextLayout = flowContext?.layout;
|
|
251
|
+
const propsLayoutModel = useMemo(() => getLayoutModelProp?.(flowEngine), [flowEngine, getLayoutModelProp]);
|
|
252
|
+
const rawRouteLayout = contextLayout || propsLayoutModel?.layout;
|
|
253
|
+
const routeLayoutUid = rawRouteLayout?.uid;
|
|
254
|
+
const routeLayoutRouteName = rawRouteLayout?.routeName;
|
|
255
|
+
const routeLayoutRoutePath = rawRouteLayout?.routePath;
|
|
256
|
+
const routeLayoutAuthCheck = rawRouteLayout?.authCheck;
|
|
257
|
+
const routeLayout = useMemo(() => {
|
|
258
|
+
if (
|
|
259
|
+
!routeLayoutUid &&
|
|
260
|
+
!routeLayoutRouteName &&
|
|
261
|
+
!routeLayoutRoutePath &&
|
|
262
|
+
typeof routeLayoutAuthCheck === 'undefined'
|
|
263
|
+
) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
authCheck: routeLayoutAuthCheck,
|
|
268
|
+
routeName: routeLayoutRouteName,
|
|
269
|
+
routePath: routeLayoutRoutePath,
|
|
270
|
+
uid: routeLayoutUid,
|
|
271
|
+
};
|
|
272
|
+
}, [routeLayoutAuthCheck, routeLayoutRouteName, routeLayoutRoutePath, routeLayoutUid]);
|
|
154
273
|
const getLayoutModel = useMemo(
|
|
155
|
-
() =>
|
|
156
|
-
[
|
|
274
|
+
() => getLayoutModelProp || ((engine: FlowEngine) => getDefaultLayoutModel(engine, routeLayout)),
|
|
275
|
+
[getLayoutModelProp, routeLayout],
|
|
157
276
|
);
|
|
158
|
-
const legacyPageBehavior =
|
|
277
|
+
const legacyPageBehavior = legacyPageBehaviorProp || getDefaultLegacyPageBehavior(flowEngine, routeLayout);
|
|
159
278
|
const app = useApp();
|
|
160
|
-
const
|
|
279
|
+
const location = useLocation();
|
|
280
|
+
const navigate = useNavigate();
|
|
281
|
+
const routeRepository = flowEngine.context.routeRepository as FlowRouteRepositoryLike | undefined;
|
|
161
282
|
const params = useParams();
|
|
162
|
-
const pageUid =
|
|
283
|
+
const pageUid = pageUidProp || params?.name;
|
|
284
|
+
const hasNestedRoutePath = typeof params?.tabUid !== 'undefined' || typeof params?.['*'] !== 'undefined';
|
|
163
285
|
const skipRouteRepositoryCheck = !routeRepository;
|
|
164
286
|
const [guardState, setGuardState] = useState<FlowRouteGuardState>({
|
|
287
|
+
pageUid: undefined,
|
|
165
288
|
pending: true,
|
|
166
289
|
allowBridge: false,
|
|
167
290
|
notFound: false,
|
|
@@ -173,19 +296,24 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
173
296
|
throw new Error('[NocoBase] FlowRoute requires pageUid or route.params.name.');
|
|
174
297
|
}
|
|
175
298
|
|
|
299
|
+
useEffect(() => {
|
|
300
|
+
replaceTriggeredRef.current = false;
|
|
301
|
+
}, [location.hash, location.pathname, location.search, pageUid]);
|
|
302
|
+
|
|
176
303
|
useEffect(() => {
|
|
177
304
|
let active = true;
|
|
178
305
|
const requestId = ++requestIdRef.current;
|
|
306
|
+
const requiresAccessibleRoute = shouldRequireAccessibleRoute(routeLayout);
|
|
179
307
|
|
|
180
308
|
const run = async () => {
|
|
181
|
-
setGuardState({ pending: true, allowBridge: false, notFound: false });
|
|
309
|
+
setGuardState({ pageUid, pending: true, allowBridge: false, notFound: false });
|
|
182
310
|
|
|
183
|
-
if (!skipRouteRepositoryCheck && !routeRepository?.isAccessibleLoaded?.()) {
|
|
311
|
+
if (requiresAccessibleRoute && !skipRouteRepositoryCheck && !routeRepository?.isAccessibleLoaded?.()) {
|
|
184
312
|
try {
|
|
185
313
|
await routeRepository?.ensureAccessibleLoaded?.();
|
|
186
314
|
} catch (_error) {
|
|
187
315
|
if (active && requestId === requestIdRef.current) {
|
|
188
|
-
setGuardState({ pending: false, allowBridge: true, notFound: false });
|
|
316
|
+
setGuardState({ pageUid, pending: false, allowBridge: true, notFound: false });
|
|
189
317
|
}
|
|
190
318
|
return;
|
|
191
319
|
}
|
|
@@ -195,23 +323,67 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
195
323
|
return;
|
|
196
324
|
}
|
|
197
325
|
|
|
198
|
-
const route =
|
|
326
|
+
const route =
|
|
327
|
+
skipRouteRepositoryCheck || !requiresAccessibleRoute
|
|
328
|
+
? undefined
|
|
329
|
+
: getAccessibleRouteByPageUid(routeRepository, pageUid);
|
|
330
|
+
if (!route && !skipRouteRepositoryCheck && requiresAccessibleRoute) {
|
|
331
|
+
setGuardState({ pageUid, pending: false, allowBridge: false, notFound: true });
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
199
335
|
if (!route && legacyPageBehavior === 'notFound') {
|
|
200
336
|
const flowModelExists = await hasFlowModel(flowEngine, pageUid);
|
|
201
337
|
if (active && requestId === requestIdRef.current) {
|
|
202
|
-
setGuardState({ pending: false, allowBridge: flowModelExists, notFound: !flowModelExists });
|
|
338
|
+
setGuardState({ pageUid, pending: false, allowBridge: flowModelExists, notFound: !flowModelExists });
|
|
203
339
|
}
|
|
204
340
|
return;
|
|
205
341
|
}
|
|
206
342
|
|
|
343
|
+
if (route?.type === NocoBaseDesktopRouteType.group) {
|
|
344
|
+
if (hasNestedRoutePath) {
|
|
345
|
+
setGuardState({ pageUid, pending: false, allowBridge: false, notFound: true });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const target = resolveAdminRouteRuntimeTarget({
|
|
350
|
+
app,
|
|
351
|
+
route,
|
|
352
|
+
layout: routeLayout,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (target.reason === 'emptyGroup') {
|
|
356
|
+
setGuardState({ pageUid, pending: false, allowBridge: false, notFound: false });
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (target.runtimePath) {
|
|
361
|
+
if (replaceTriggeredRef.current) {
|
|
362
|
+
setGuardState({ pageUid, pending: false, allowBridge: false, notFound: false });
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
replaceTriggeredRef.current = true;
|
|
367
|
+
if (target.navigationMode === 'document') {
|
|
368
|
+
window.location.replace(target.runtimePath);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
navigate(toRouterNavigationPath(target.runtimePath, app.router?.getBasename?.()), { replace: true });
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
setGuardState({ pageUid, pending: false, allowBridge: false, notFound: true });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
207
379
|
if (route?.type === NocoBaseDesktopRouteType.page) {
|
|
208
380
|
if (legacyPageBehavior === 'notFound') {
|
|
209
|
-
setGuardState({ pending: false, allowBridge: false, notFound: true });
|
|
381
|
+
setGuardState({ pageUid, pending: false, allowBridge: false, notFound: true });
|
|
210
382
|
return;
|
|
211
383
|
}
|
|
212
384
|
|
|
213
385
|
if (legacyPageBehavior === 'bridge') {
|
|
214
|
-
setGuardState({ pending: false, allowBridge: true, notFound: false });
|
|
386
|
+
setGuardState({ pageUid, pending: false, allowBridge: true, notFound: false });
|
|
215
387
|
return;
|
|
216
388
|
}
|
|
217
389
|
|
|
@@ -234,26 +406,42 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
234
406
|
|
|
235
407
|
if (target.reason === 'unsupportedV2Runtime') {
|
|
236
408
|
if (active && requestId === requestIdRef.current) {
|
|
237
|
-
setGuardState({
|
|
409
|
+
setGuardState({
|
|
410
|
+
pageUid,
|
|
411
|
+
pending: false,
|
|
412
|
+
allowBridge: false,
|
|
413
|
+
notFound: false,
|
|
414
|
+
legacyPageUnsupported: true,
|
|
415
|
+
});
|
|
238
416
|
}
|
|
239
417
|
return;
|
|
240
418
|
}
|
|
241
419
|
}
|
|
242
420
|
|
|
243
421
|
if (active && requestId === requestIdRef.current) {
|
|
244
|
-
setGuardState({ pending: false, allowBridge: true, notFound: false });
|
|
422
|
+
setGuardState({ pageUid, pending: false, allowBridge: true, notFound: false });
|
|
245
423
|
}
|
|
246
424
|
};
|
|
247
425
|
|
|
248
|
-
|
|
426
|
+
run();
|
|
249
427
|
|
|
250
428
|
return () => {
|
|
251
429
|
active = false;
|
|
252
430
|
};
|
|
253
|
-
}, [
|
|
431
|
+
}, [
|
|
432
|
+
app,
|
|
433
|
+
flowEngine,
|
|
434
|
+
hasNestedRoutePath,
|
|
435
|
+
legacyPageBehavior,
|
|
436
|
+
navigate,
|
|
437
|
+
pageUid,
|
|
438
|
+
routeLayout,
|
|
439
|
+
routeRepository,
|
|
440
|
+
skipRouteRepositoryCheck,
|
|
441
|
+
]);
|
|
254
442
|
|
|
255
443
|
const content = useMemo(() => {
|
|
256
|
-
if (guardState.pending) {
|
|
444
|
+
if (guardState.pageUid !== pageUid || guardState.pending) {
|
|
257
445
|
return null;
|
|
258
446
|
}
|
|
259
447
|
|
|
@@ -261,12 +449,27 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
261
449
|
return <AppNotFound />;
|
|
262
450
|
}
|
|
263
451
|
|
|
452
|
+
if (guardState.legacyPageUnsupported) {
|
|
453
|
+
return <LegacyPageUnsupported app={app} location={location} />;
|
|
454
|
+
}
|
|
455
|
+
|
|
264
456
|
if (!guardState.allowBridge) {
|
|
265
457
|
return null;
|
|
266
458
|
}
|
|
267
459
|
|
|
268
|
-
return <BridgeFlowRoute pageUid={pageUid} active={
|
|
269
|
-
}, [
|
|
460
|
+
return <BridgeFlowRoute pageUid={pageUid} active={active} getLayoutModel={getLayoutModel} />;
|
|
461
|
+
}, [
|
|
462
|
+
active,
|
|
463
|
+
app,
|
|
464
|
+
getLayoutModel,
|
|
465
|
+
guardState.allowBridge,
|
|
466
|
+
guardState.legacyPageUnsupported,
|
|
467
|
+
guardState.notFound,
|
|
468
|
+
guardState.pageUid,
|
|
469
|
+
guardState.pending,
|
|
470
|
+
location,
|
|
471
|
+
pageUid,
|
|
472
|
+
]);
|
|
270
473
|
|
|
271
474
|
return content;
|
|
272
475
|
};
|
|
@@ -15,6 +15,7 @@ export interface RunJSValueEditorProps {
|
|
|
15
15
|
t?: (key: string) => string;
|
|
16
16
|
value?: unknown;
|
|
17
17
|
onChange?: (value: RunJSValue) => void;
|
|
18
|
+
disabled?: boolean;
|
|
18
19
|
height?: string;
|
|
19
20
|
scene?: string;
|
|
20
21
|
containerStyle?: React.CSSProperties;
|
|
@@ -25,6 +26,7 @@ export const RunJSValueEditor: React.FC<RunJSValueEditorProps> = (props) => {
|
|
|
25
26
|
t,
|
|
26
27
|
value,
|
|
27
28
|
onChange,
|
|
29
|
+
disabled,
|
|
28
30
|
height = '200px',
|
|
29
31
|
scene = 'formValue',
|
|
30
32
|
containerStyle = { flex: 1, minWidth: 0 },
|
|
@@ -38,9 +40,15 @@ export const RunJSValueEditor: React.FC<RunJSValueEditorProps> = (props) => {
|
|
|
38
40
|
<div style={containerStyle}>
|
|
39
41
|
<CodeEditor
|
|
40
42
|
value={current.code}
|
|
41
|
-
onChange={(code) =>
|
|
43
|
+
onChange={(code) => {
|
|
44
|
+
if (disabled) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
onChange?.({ ...current, code });
|
|
48
|
+
}}
|
|
42
49
|
version={current.version}
|
|
43
50
|
height={height}
|
|
51
|
+
readonly={disabled}
|
|
44
52
|
enableLinter
|
|
45
53
|
placeholder={placeholderText}
|
|
46
54
|
scene={scene}
|
|
@@ -10,19 +10,24 @@
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
12
12
|
import { render, screen, userEvent, waitFor } from '@nocobase/test/client';
|
|
13
|
-
import { ActionScene, FlowEngine, FlowModel, GLOBAL_EMBED_CONTAINER_ID } from '@nocobase/flow-engine';
|
|
13
|
+
import { ActionScene, FlowEngine, FlowModel, GLOBAL_EMBED_CONTAINER_ID, useFlowContext } from '@nocobase/flow-engine';
|
|
14
14
|
import { DynamicFlowsIcon } from '../DynamicFlowsIcon';
|
|
15
15
|
|
|
16
16
|
const mockState = vi.hoisted(() => ({
|
|
17
17
|
capturedSelectProps: [] as any[],
|
|
18
|
+
capturedTabsProps: [] as any[],
|
|
18
19
|
flowContextValue: undefined as any,
|
|
19
20
|
}));
|
|
20
21
|
|
|
21
22
|
vi.mock('@nocobase/flow-engine', async (importOriginal) => {
|
|
22
23
|
const actual = await importOriginal<typeof import('@nocobase/flow-engine')>();
|
|
24
|
+
const React = await import('react');
|
|
23
25
|
return {
|
|
24
26
|
...actual,
|
|
25
|
-
useFlowContext: () =>
|
|
27
|
+
useFlowContext: () => {
|
|
28
|
+
const context = React.useContext(actual.FlowReactContext);
|
|
29
|
+
return context?.view ? context : mockState.flowContextValue;
|
|
30
|
+
},
|
|
26
31
|
};
|
|
27
32
|
});
|
|
28
33
|
|
|
@@ -62,6 +67,33 @@ vi.mock('antd', async (importOriginal) => {
|
|
|
62
67
|
return <div data-testid={String(props.placeholder || 'select')} />;
|
|
63
68
|
},
|
|
64
69
|
Space: ({ children }: any) => <div>{children}</div>,
|
|
70
|
+
Tabs: (props: any) => {
|
|
71
|
+
mockState.capturedTabsProps.push(props);
|
|
72
|
+
const { items, activeKey, onChange, tabBarStyle } = props;
|
|
73
|
+
const currentKey = activeKey || items?.[0]?.key;
|
|
74
|
+
return (
|
|
75
|
+
<div data-testid="dynamic-flow-source-tabs">
|
|
76
|
+
<div role="tablist" style={tabBarStyle}>
|
|
77
|
+
{items?.map((item: any) => (
|
|
78
|
+
<button
|
|
79
|
+
key={item.key}
|
|
80
|
+
role="tab"
|
|
81
|
+
aria-selected={item.key === currentKey}
|
|
82
|
+
type="button"
|
|
83
|
+
onClick={() => onChange?.(item.key)}
|
|
84
|
+
>
|
|
85
|
+
{item.label}
|
|
86
|
+
</button>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
{items?.map((item: any) => (
|
|
90
|
+
<div key={item.key} hidden={item.key !== currentKey}>
|
|
91
|
+
{item.children}
|
|
92
|
+
</div>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
},
|
|
65
97
|
Tooltip: ({ children }: any) => <>{children}</>,
|
|
66
98
|
};
|
|
67
99
|
});
|
|
@@ -83,6 +115,8 @@ const openDynamicFlowsEditor = async (model: FlowModel) => {
|
|
|
83
115
|
expect(trigger).not.toBeNull();
|
|
84
116
|
await userEvent.click(trigger as Element);
|
|
85
117
|
|
|
118
|
+
await waitFor(() => expect(model.context.viewer.embed).toHaveBeenCalled());
|
|
119
|
+
|
|
86
120
|
const embedCall = (model.context.viewer.embed as any).mock.calls.at(-1)?.[0];
|
|
87
121
|
expect(embedCall?.content).toBeTruthy();
|
|
88
122
|
|
|
@@ -163,6 +197,38 @@ const createModel = (options: { preventClose?: boolean; hiddenClose?: boolean; s
|
|
|
163
197
|
return model;
|
|
164
198
|
};
|
|
165
199
|
|
|
200
|
+
const createPeerModel = (model: FlowModel, uid: string, saveStepParams = vi.fn(async () => undefined)) => {
|
|
201
|
+
const PeerModel = model.constructor as typeof FlowModel;
|
|
202
|
+
const peer = new PeerModel({
|
|
203
|
+
uid,
|
|
204
|
+
use: PeerModel,
|
|
205
|
+
flowEngine: model.flowEngine,
|
|
206
|
+
stepParams: {},
|
|
207
|
+
subModels: {},
|
|
208
|
+
flowRegistry: {
|
|
209
|
+
flow1: {
|
|
210
|
+
title: 'Template event flow',
|
|
211
|
+
on: 'click',
|
|
212
|
+
steps: {
|
|
213
|
+
notifyStep: {
|
|
214
|
+
use: 'notify',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
} as any);
|
|
220
|
+
|
|
221
|
+
peer.context.defineProperty('message', {
|
|
222
|
+
value: {
|
|
223
|
+
error: vi.fn(),
|
|
224
|
+
success: vi.fn(),
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
vi.spyOn(peer, 'saveStepParams').mockImplementation(saveStepParams);
|
|
228
|
+
|
|
229
|
+
return peer;
|
|
230
|
+
};
|
|
231
|
+
|
|
166
232
|
const createView = () => {
|
|
167
233
|
let destroyed = false;
|
|
168
234
|
let closingPromise: Promise<boolean | void> | undefined;
|
|
@@ -189,6 +255,9 @@ const createView = () => {
|
|
|
189
255
|
destroyed = true;
|
|
190
256
|
}),
|
|
191
257
|
beforeClose: undefined as any,
|
|
258
|
+
inputArgs: {
|
|
259
|
+
pageActive: true,
|
|
260
|
+
},
|
|
192
261
|
};
|
|
193
262
|
return view;
|
|
194
263
|
};
|
|
@@ -196,6 +265,7 @@ const createView = () => {
|
|
|
196
265
|
describe('DynamicFlowsIcon', () => {
|
|
197
266
|
beforeEach(() => {
|
|
198
267
|
mockState.capturedSelectProps.length = 0;
|
|
268
|
+
mockState.capturedTabsProps.length = 0;
|
|
199
269
|
mockState.flowContextValue = {
|
|
200
270
|
modal: {
|
|
201
271
|
confirm: vi.fn(),
|
|
@@ -377,4 +447,80 @@ describe('DynamicFlowsIcon', () => {
|
|
|
377
447
|
expect(view.destroy).toHaveBeenCalledTimes(1);
|
|
378
448
|
expect(model.context.message.success).toHaveBeenCalledWith('Configuration saved');
|
|
379
449
|
});
|
|
450
|
+
|
|
451
|
+
it('renders source tabs and closes the editor when saving one source', async () => {
|
|
452
|
+
const model = createModel();
|
|
453
|
+
const targetSaveStepParams = vi.fn(async () => undefined);
|
|
454
|
+
const target = createPeerModel(model, 'target-model', targetSaveStepParams);
|
|
455
|
+
|
|
456
|
+
model.flowEngine.flowSettings.registerDynamicFlowSourceProvider({
|
|
457
|
+
key: 'test-source-provider',
|
|
458
|
+
getSources: () => [{ key: 'referenced-template', label: 'Referenced template', model: target }],
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const { view } = await openDynamicFlowsEditor(model);
|
|
462
|
+
|
|
463
|
+
expect(screen.getByRole('tab', { name: 'Current block' })).toHaveAttribute('aria-selected', 'true');
|
|
464
|
+
expect(mockState.capturedTabsProps.at(-1)?.tabBarStyle?.paddingLeft).toBeGreaterThan(0);
|
|
465
|
+
await userEvent.click(screen.getByRole('tab', { name: 'Referenced template' }));
|
|
466
|
+
|
|
467
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
468
|
+
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
|
469
|
+
|
|
470
|
+
expect(targetSaveStepParams).toHaveBeenCalledTimes(1);
|
|
471
|
+
expect(target.flowRegistry.getFlows().size).toBe(2);
|
|
472
|
+
expect(view.destroy).toHaveBeenCalledTimes(1);
|
|
473
|
+
expect(target.context.message.success).toHaveBeenCalledWith('Configuration saved');
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('saves all dirty sources without discard confirmation', async () => {
|
|
477
|
+
const saveStepParams = vi.fn(async () => undefined);
|
|
478
|
+
const model = createModel({ saveStepParams });
|
|
479
|
+
const targetSaveStepParams = vi.fn(async () => undefined);
|
|
480
|
+
const target = createPeerModel(model, 'target-model', targetSaveStepParams);
|
|
481
|
+
mockState.flowContextValue.modal.confirm.mockResolvedValue(false);
|
|
482
|
+
|
|
483
|
+
model.flowEngine.flowSettings.registerDynamicFlowSourceProvider({
|
|
484
|
+
key: 'test-source-unsaved-provider',
|
|
485
|
+
getSources: () => [{ key: 'referenced-template', label: 'Referenced template', model: target }],
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
const { view } = await openDynamicFlowsEditor(model);
|
|
489
|
+
|
|
490
|
+
await userEvent.click(screen.getByRole('tab', { name: 'Referenced template' }));
|
|
491
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
492
|
+
await userEvent.click(screen.getByRole('tab', { name: 'Current block' }));
|
|
493
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
494
|
+
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
|
495
|
+
|
|
496
|
+
expect(saveStepParams).toHaveBeenCalledTimes(1);
|
|
497
|
+
expect(targetSaveStepParams).toHaveBeenCalledTimes(1);
|
|
498
|
+
expect(mockState.flowContextValue.modal.confirm).not.toHaveBeenCalled();
|
|
499
|
+
expect(view.close).toHaveBeenCalledTimes(1);
|
|
500
|
+
expect(view.destroy).toHaveBeenCalledTimes(1);
|
|
501
|
+
expect(model.flowRegistry.getFlows().size).toBe(2);
|
|
502
|
+
expect(target.flowRegistry.getFlows().size).toBe(2);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('provides the active source model context inside source tabs', async () => {
|
|
506
|
+
const model = createModel();
|
|
507
|
+
const target = createPeerModel(model, 'target-model');
|
|
508
|
+
const seenModelUids: string[] = [];
|
|
509
|
+
const ContextProbe = () => {
|
|
510
|
+
const ctx = useFlowContext<any>();
|
|
511
|
+
seenModelUids.push(ctx?.model?.uid);
|
|
512
|
+
return null;
|
|
513
|
+
};
|
|
514
|
+
model.flowEngine.flowSettings.renderStepForm = vi.fn(() => <ContextProbe />) as any;
|
|
515
|
+
|
|
516
|
+
model.flowEngine.flowSettings.registerDynamicFlowSourceProvider({
|
|
517
|
+
key: 'test-source-context-provider',
|
|
518
|
+
getSources: () => [{ key: 'referenced-template', label: 'Referenced template', model: target }],
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
await openDynamicFlowsEditor(model);
|
|
522
|
+
await userEvent.click(screen.getByRole('tab', { name: 'Referenced template' }));
|
|
523
|
+
|
|
524
|
+
await waitFor(() => expect(seenModelUids).toContain('target-model'));
|
|
525
|
+
});
|
|
380
526
|
});
|