@nocobase/client-v2 2.2.0-beta.9 → 2.2.0
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 +13 -3
- package/es/BaseApplication.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/ScanInput/useCodeScanner.d.ts +3 -2
- package/es/components/form/TypedVariableInput.d.ts +31 -5
- 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/index.d.ts +1 -0
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/actions/linkageRules.d.ts +27 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
- package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +7 -24
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/RunJSValueEditor.d.ts +1 -0
- package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
- package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +31 -0
- package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
- package/es/flow/components/field-value-variable/index.d.ts +11 -0
- package/es/flow/components/filter/FilterGroup.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
- package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
- package/es/flow/index.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/internal/utils/operatorSchemaHelper.d.ts +2 -2
- package/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
- package/es/flow/models/base/ActionModelCore.d.ts +4 -2
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/base/CollectionBlockModel.d.ts +3 -0
- 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/details/DetailsBlockModel.d.ts +3 -0
- package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
- package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +7 -0
- package/es/flow/models/blocks/form/FormGridModel.d.ts +6 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +1 -0
- package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +12 -0
- package/es/flow/models/blocks/table/utils.d.ts +1 -0
- package/es/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.d.ts +2 -1
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +0 -17
- package/es/flow/models/fields/AssociationFieldModel/recordSelectShared.d.ts +41 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/flow-compat/routeTypes.d.ts +1 -0
- package/es/index.d.ts +5 -0
- package/es/index.mjs +199 -161
- package/es/ui-operation/index.d.ts +15 -0
- package/es/ui-operation/ui-operation-codec.d.ts +10 -0
- package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
- package/es/utils/index.d.ts +1 -0
- package/lib/index.js +215 -177
- package/lib/locale/languageCodes.js +3 -1
- package/package.json +9 -7
- package/src/APIClient.ts +90 -7
- package/src/Application.tsx +7 -1
- package/src/BaseApplication.tsx +11 -6
- package/src/__tests__/APIClient.test.tsx +58 -0
- package/src/__tests__/app.test.tsx +73 -0
- package/src/__tests__/browserChecker.test.ts +61 -0
- package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +35 -2
- package/src/__tests__/settings-center.test.tsx +270 -6
- package/src/acl/ACLProvider.tsx +2 -0
- package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/collection-manager/field-validation.ts +1 -1
- 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 +4 -0
- 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/__tests__/TypedVariableInput.test.tsx +364 -12
- package/src/components/form/filter/CollectionFilter.tsx +4 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +36 -9
- package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +55 -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/table/Table.tsx +81 -6
- package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
- package/src/components/index.ts +1 -0
- package/src/flow/FlowPage.tsx +9 -1
- package/src/flow/__tests__/FlowPage.test.tsx +50 -3
- package/src/flow/__tests__/FlowRoute.test.tsx +128 -10
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
- package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -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/index.ts +2 -0
- package/src/flow/actions/linkageRules.tsx +144 -26
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +0 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +78 -59
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +228 -0
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignValueInput.tsx +72 -584
- package/src/flow/components/FlowRoute.tsx +78 -32
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
- package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
- package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +326 -0
- package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +380 -0
- package/src/flow/components/field-value-variable/dateValue.ts +223 -0
- package/src/flow/components/field-value-variable/index.ts +12 -0
- package/src/flow/components/filter/FilterGroup.tsx +33 -5
- package/src/flow/components/filter/VariableFilterItem.tsx +170 -13
- 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 +195 -1
- package/src/flow/components/placeholders/BlockPlaceholder.tsx +70 -23
- package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +57 -7
- package/src/flow/index.ts +3 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/internal/utils/operatorSchemaHelper.ts +3 -2
- package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
- package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
- package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
- package/src/flow/models/base/ActionModelCore.tsx +11 -2
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +38 -3
- package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
- 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 +790 -10
- 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__/ActionModelCore.render.test.tsx +49 -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__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +28 -53
- package/src/flow/models/blocks/details/DetailsBlockModel.tsx +8 -3
- package/src/flow/models/blocks/details/__tests__/DetailsBlockModel.initialPaginationChangeRefresh.test.ts +32 -1
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
- package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +155 -25
- package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +395 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +66 -4
- package/src/flow/models/blocks/filter-manager/FilterManager.ts +5 -0
- package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +40 -0
- package/src/flow/models/blocks/form/FormBlockModel.tsx +47 -0
- package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +8 -2
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/form/__tests__/runJsFormSubmit.test.ts +131 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +435 -0
- package/src/flow/models/blocks/form/value-runtime/rules.ts +67 -14
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +43 -19
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +29 -3
- package/src/flow/models/blocks/table/TableColumnModel.tsx +48 -9
- package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.filterReset.test.ts +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
- package/src/flow/models/blocks/table/utils.ts +7 -0
- package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +10 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
- package/src/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.tsx +24 -1
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +9 -136
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +167 -3
- package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -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/DisplayNumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
- package/src/flow/models/fields/__tests__/NumberFieldModel.test.tsx +47 -0
- package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +26 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +51 -14
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +139 -13
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/flow-compat/fieldValidationConstants.ts +1 -1
- package/src/flow-compat/routeTypes.ts +1 -0
- package/src/index.ts +5 -0
- package/src/layout-manager/LayoutContentRoute.tsx +2 -1
- package/src/layout-manager/LayoutRoute.tsx +2 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +87 -1
- package/src/locale/languageCodes.ts +3 -1
- package/src/nocobase-buildin-plugin/index.tsx +12 -0
- package/src/settings-center/AdminSettingsLayout.tsx +23 -2
- package/src/ui-operation/index.ts +33 -0
- package/src/ui-operation/ui-operation-codec.ts +54 -0
- package/src/utils/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -0
|
@@ -23,6 +23,20 @@ import {
|
|
|
23
23
|
} from '../AssociationFieldModel/recordSelectShared';
|
|
24
24
|
import _ from 'lodash';
|
|
25
25
|
|
|
26
|
+
const mobileSelectSafeAreaPaddingBottom = 'calc(12px + env(safe-area-inset-bottom, 0px))';
|
|
27
|
+
|
|
28
|
+
const mobileSelectConfirmFooterStyle: CSSProperties = {
|
|
29
|
+
paddingBottom: mobileSelectSafeAreaPaddingBottom,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function getMobileSelectListStyle(hasConfirmFooter: boolean): CSSProperties {
|
|
33
|
+
return {
|
|
34
|
+
maxHeight: '60vh',
|
|
35
|
+
overflowY: 'auto',
|
|
36
|
+
paddingBottom: hasConfirmFooter ? undefined : mobileSelectSafeAreaPaddingBottom,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
const labelClassName = css`
|
|
27
41
|
div {
|
|
28
42
|
white-space: nowrap !important;
|
|
@@ -192,6 +206,11 @@ export function MobileLazySelect(props: Readonly<LazySelectProps>) {
|
|
|
192
206
|
return;
|
|
193
207
|
}
|
|
194
208
|
const selectedId = vals[0];
|
|
209
|
+
if (selectedId === undefined) {
|
|
210
|
+
onChange(undefined as unknown as AssociationOption);
|
|
211
|
+
handleClose();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
195
214
|
const record = optionMap.get(selectedId);
|
|
196
215
|
if (record) {
|
|
197
216
|
if (valueMode === 'value') {
|
|
@@ -277,16 +296,11 @@ export function MobileLazySelect(props: Readonly<LazySelectProps>) {
|
|
|
277
296
|
value={searchText}
|
|
278
297
|
onChange={handleSearchChange}
|
|
279
298
|
onCancel={handleClose}
|
|
299
|
+
cancelText={t('Cancel')}
|
|
280
300
|
showCancelButton
|
|
281
301
|
/>
|
|
282
302
|
</div>
|
|
283
|
-
<div
|
|
284
|
-
style={{
|
|
285
|
-
maxHeight: '60vh',
|
|
286
|
-
overflowY: 'auto',
|
|
287
|
-
}}
|
|
288
|
-
onScroll={handleScroll}
|
|
289
|
-
>
|
|
303
|
+
<div style={getMobileSelectListStyle(isMultiple)} onScroll={handleScroll}>
|
|
290
304
|
<CheckList multiple={isMultiple} value={selectedValueIds} onChange={handleListChange}>
|
|
291
305
|
{realOptions.map((item) => {
|
|
292
306
|
const optionValue = item?.[valueKey];
|
|
@@ -311,9 +325,11 @@ export function MobileLazySelect(props: Readonly<LazySelectProps>) {
|
|
|
311
325
|
)}
|
|
312
326
|
</div>
|
|
313
327
|
{isMultiple && (
|
|
314
|
-
<
|
|
315
|
-
{
|
|
316
|
-
|
|
328
|
+
<div style={mobileSelectConfirmFooterStyle}>
|
|
329
|
+
<Button block color="primary" onClick={handleConfirm} style={{ marginTop: '16px' }}>
|
|
330
|
+
{t('Confirm')}
|
|
331
|
+
</Button>
|
|
332
|
+
</div>
|
|
317
333
|
)}
|
|
318
334
|
</Popup>
|
|
319
335
|
</>
|
|
@@ -9,13 +9,45 @@
|
|
|
9
9
|
|
|
10
10
|
import { useFlowModelContext } from '@nocobase/flow-engine';
|
|
11
11
|
import { Select } from 'antd';
|
|
12
|
-
import { Button, CheckList, Popup, SearchBar } from 'antd-mobile';
|
|
12
|
+
import { Button, CheckList, ConfigProvider, Popup, SearchBar, useConfig } from 'antd-mobile';
|
|
13
13
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
14
14
|
|
|
15
|
+
const mobileSelectSafeAreaPaddingBottom = 'calc(12px + env(safe-area-inset-bottom, 0px))';
|
|
16
|
+
|
|
17
|
+
const mobileSelectConfirmFooterStyle: React.CSSProperties = {
|
|
18
|
+
paddingBottom: mobileSelectSafeAreaPaddingBottom,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function getMobileSelectListStyle(hasConfirmFooter: boolean): React.CSSProperties {
|
|
22
|
+
return {
|
|
23
|
+
maxHeight: '60vh',
|
|
24
|
+
overflowY: 'auto',
|
|
25
|
+
paddingBottom: hasConfirmFooter ? undefined : mobileSelectSafeAreaPaddingBottom,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeMobileLocale(locale?: string) {
|
|
30
|
+
return locale === 'zh-CH' ? 'zh-CN' : locale;
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
export function MobileSelect(props) {
|
|
16
34
|
const { value, displayValue, onChange, onChangeComplete, disabled, options = [], mode } = props;
|
|
35
|
+
const isMultiple = ['multiple', 'tags'].includes(mode);
|
|
17
36
|
const ctx = useFlowModelContext();
|
|
18
37
|
const t = ctx.t;
|
|
38
|
+
const { locale: mobileLocale } = useConfig();
|
|
39
|
+
const searchBarLocale = useMemo(() => {
|
|
40
|
+
const currentLocale = normalizeMobileLocale(ctx.locale);
|
|
41
|
+
const inheritedLocale = normalizeMobileLocale(mobileLocale.locale);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
...mobileLocale,
|
|
45
|
+
SearchBar: {
|
|
46
|
+
...mobileLocale.SearchBar,
|
|
47
|
+
name: currentLocale === inheritedLocale ? mobileLocale.SearchBar.name : t('Search'),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}, [ctx.locale, mobileLocale, t]);
|
|
19
51
|
const [visible, setVisible] = useState(false);
|
|
20
52
|
const [selected, setSelected] = useState(value || []);
|
|
21
53
|
const [searchText, setSearchText] = useState(null);
|
|
@@ -62,19 +94,22 @@ export function MobileSelect(props) {
|
|
|
62
94
|
destroyOnClose
|
|
63
95
|
>
|
|
64
96
|
<div style={{ margin: '10px' }}>
|
|
65
|
-
<
|
|
97
|
+
<ConfigProvider locale={searchBarLocale}>
|
|
98
|
+
<SearchBar
|
|
99
|
+
placeholder={t('search')}
|
|
100
|
+
value={searchText}
|
|
101
|
+
onChange={(v) => setSearchText(v)}
|
|
102
|
+
cancelText={t('Cancel')}
|
|
103
|
+
showCancelButton
|
|
104
|
+
/>
|
|
105
|
+
</ConfigProvider>
|
|
66
106
|
</div>
|
|
67
|
-
<div
|
|
68
|
-
style={{
|
|
69
|
-
maxHeight: '60vh',
|
|
70
|
-
overflowY: 'auto',
|
|
71
|
-
}}
|
|
72
|
-
>
|
|
107
|
+
<div style={getMobileSelectListStyle(isMultiple)}>
|
|
73
108
|
<CheckList
|
|
74
|
-
multiple={
|
|
109
|
+
multiple={isMultiple}
|
|
75
110
|
value={Array.isArray(selected) ? selected : [selected]}
|
|
76
111
|
onChange={(val) => {
|
|
77
|
-
if (
|
|
112
|
+
if (isMultiple) {
|
|
78
113
|
setSelected(val);
|
|
79
114
|
} else {
|
|
80
115
|
setSelected(val[0]);
|
|
@@ -91,10 +126,12 @@ export function MobileSelect(props) {
|
|
|
91
126
|
))}
|
|
92
127
|
</CheckList>
|
|
93
128
|
</div>
|
|
94
|
-
{
|
|
95
|
-
<
|
|
96
|
-
{
|
|
97
|
-
|
|
129
|
+
{isMultiple && (
|
|
130
|
+
<div style={mobileSelectConfirmFooterStyle}>
|
|
131
|
+
<Button block color="primary" onClick={handleConfirm} style={{ marginTop: '16px' }}>
|
|
132
|
+
{t('Confirm')}
|
|
133
|
+
</Button>
|
|
134
|
+
</div>
|
|
98
135
|
)}
|
|
99
136
|
</Popup>
|
|
100
137
|
</>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import React from 'react';
|
|
10
|
+
import React, { type ComponentProps, type ReactNode } from 'react';
|
|
11
11
|
import { beforeEach, describe, it, expect, vi } from 'vitest';
|
|
12
12
|
import { act, fireEvent, render, screen } from '@nocobase/test/client';
|
|
13
13
|
import { MobileLazySelect } from '../MobileLazySelect';
|
|
@@ -23,11 +23,29 @@ const RELATION_OPTIONS = [
|
|
|
23
23
|
{ uuid: 'c7d99828-a1de-9e70-4c2d-b0139abdf02e' },
|
|
24
24
|
];
|
|
25
25
|
|
|
26
|
+
type SelectProps = ComponentProps<(typeof import('antd'))['Select']>;
|
|
27
|
+
type PopupProps = ComponentProps<(typeof import('antd-mobile'))['Popup']>;
|
|
28
|
+
type CheckListProps = ComponentProps<(typeof import('antd-mobile'))['CheckList']>;
|
|
29
|
+
type ButtonProps = ComponentProps<(typeof import('antd-mobile'))['Button']>;
|
|
30
|
+
type SearchBarProps = ComponentProps<(typeof import('antd-mobile'))['SearchBar']>;
|
|
31
|
+
type ConfigProviderProps = ComponentProps<(typeof import('antd-mobile'))['ConfigProvider']>;
|
|
32
|
+
type MobileLocale = {
|
|
33
|
+
locale: string;
|
|
34
|
+
common: { cancel: string };
|
|
35
|
+
SearchBar: { name: string };
|
|
36
|
+
};
|
|
37
|
+
|
|
26
38
|
const mockState = vi.hoisted(() => ({
|
|
27
|
-
selectProps: undefined as
|
|
28
|
-
popupProps: undefined as
|
|
29
|
-
checklistProps: undefined as
|
|
30
|
-
confirmButtonProps: undefined as
|
|
39
|
+
selectProps: undefined as SelectProps | undefined,
|
|
40
|
+
popupProps: undefined as PopupProps | undefined,
|
|
41
|
+
checklistProps: undefined as CheckListProps | undefined,
|
|
42
|
+
confirmButtonProps: undefined as ButtonProps | undefined,
|
|
43
|
+
mobileLocale: {
|
|
44
|
+
locale: 'zh-CH',
|
|
45
|
+
common: { cancel: '取消' },
|
|
46
|
+
SearchBar: { name: '搜索框' },
|
|
47
|
+
},
|
|
48
|
+
flowLocale: 'en-US',
|
|
31
49
|
}));
|
|
32
50
|
|
|
33
51
|
function resetMockState() {
|
|
@@ -35,6 +53,12 @@ function resetMockState() {
|
|
|
35
53
|
mockState.popupProps = undefined;
|
|
36
54
|
mockState.checklistProps = undefined;
|
|
37
55
|
mockState.confirmButtonProps = undefined;
|
|
56
|
+
mockState.mobileLocale = {
|
|
57
|
+
locale: 'zh-CH',
|
|
58
|
+
common: { cancel: '取消' },
|
|
59
|
+
SearchBar: { name: '搜索框' },
|
|
60
|
+
};
|
|
61
|
+
mockState.flowLocale = 'en-US';
|
|
38
62
|
}
|
|
39
63
|
|
|
40
64
|
function clickTrigger() {
|
|
@@ -115,7 +139,20 @@ vi.mock('@nocobase/flow-engine', async () => {
|
|
|
115
139
|
return {
|
|
116
140
|
...actual,
|
|
117
141
|
useFlowModelContext: () => ({
|
|
118
|
-
|
|
142
|
+
locale: mockState.flowLocale,
|
|
143
|
+
t: (value: string) =>
|
|
144
|
+
({
|
|
145
|
+
'zh-CN': {
|
|
146
|
+
Cancel: '取消',
|
|
147
|
+
Search: '搜索',
|
|
148
|
+
search: '搜索',
|
|
149
|
+
},
|
|
150
|
+
'zh-TW': {
|
|
151
|
+
Cancel: '取消',
|
|
152
|
+
Search: '搜尋',
|
|
153
|
+
search: '搜尋',
|
|
154
|
+
},
|
|
155
|
+
})[mockState.flowLocale]?.[value] || value,
|
|
119
156
|
}),
|
|
120
157
|
useFlowModel: () => ({
|
|
121
158
|
context: {
|
|
@@ -130,7 +167,7 @@ vi.mock('antd', async () => {
|
|
|
130
167
|
const actual = await vi.importActual<any>('antd');
|
|
131
168
|
return {
|
|
132
169
|
...actual,
|
|
133
|
-
Select: (props:
|
|
170
|
+
Select: (props: SelectProps) => {
|
|
134
171
|
mockState.selectProps = props;
|
|
135
172
|
return <div data-testid="antd-select" />;
|
|
136
173
|
},
|
|
@@ -138,15 +175,22 @@ vi.mock('antd', async () => {
|
|
|
138
175
|
});
|
|
139
176
|
|
|
140
177
|
vi.mock('antd-mobile', () => {
|
|
141
|
-
const MockCheckList
|
|
178
|
+
const MockCheckList = (props: CheckListProps) => {
|
|
142
179
|
mockState.checklistProps = props;
|
|
143
180
|
return <div data-testid="checklist">{props.children}</div>;
|
|
144
181
|
};
|
|
145
182
|
|
|
146
|
-
MockCheckList.Item = ({ value, children }:
|
|
183
|
+
MockCheckList.Item = ({ value, children }: { value: string | number; children?: ReactNode }) => (
|
|
184
|
+
<div data-testid={`item-${value}`}>{children}</div>
|
|
185
|
+
);
|
|
147
186
|
|
|
148
187
|
return {
|
|
149
|
-
|
|
188
|
+
ConfigProvider: ({ children, locale }: ConfigProviderProps) => {
|
|
189
|
+
mockState.mobileLocale = locale as MobileLocale;
|
|
190
|
+
return <>{children}</>;
|
|
191
|
+
},
|
|
192
|
+
useConfig: () => ({ locale: mockState.mobileLocale }),
|
|
193
|
+
Button: (props: ButtonProps) => {
|
|
150
194
|
mockState.confirmButtonProps = props;
|
|
151
195
|
return (
|
|
152
196
|
<button type="button" data-testid="confirm" onClick={props.onClick}>
|
|
@@ -154,12 +198,26 @@ vi.mock('antd-mobile', () => {
|
|
|
154
198
|
</button>
|
|
155
199
|
);
|
|
156
200
|
},
|
|
157
|
-
Popup: (props:
|
|
201
|
+
Popup: (props: PopupProps) => {
|
|
158
202
|
mockState.popupProps = props;
|
|
159
203
|
return props.visible ? <div data-testid="popup">{props.children}</div> : null;
|
|
160
204
|
},
|
|
161
|
-
SearchBar: ({ value, onChange }:
|
|
162
|
-
<
|
|
205
|
+
SearchBar: ({ value, onChange, onCancel, cancelText, placeholder, showCancelButton }: SearchBarProps) => (
|
|
206
|
+
<div>
|
|
207
|
+
<input
|
|
208
|
+
aria-label={mockState.mobileLocale.SearchBar.name}
|
|
209
|
+
data-testid="search"
|
|
210
|
+
placeholder={placeholder}
|
|
211
|
+
type="search"
|
|
212
|
+
value={value ?? ''}
|
|
213
|
+
onChange={(e) => onChange?.(e.target.value)}
|
|
214
|
+
/>
|
|
215
|
+
{showCancelButton && value ? (
|
|
216
|
+
<button type="button" onClick={onCancel}>
|
|
217
|
+
{cancelText ?? mockState.mobileLocale.common.cancel}
|
|
218
|
+
</button>
|
|
219
|
+
) : null}
|
|
220
|
+
</div>
|
|
163
221
|
),
|
|
164
222
|
CheckList: MockCheckList,
|
|
165
223
|
};
|
|
@@ -199,6 +257,47 @@ describe('MobileSelect', () => {
|
|
|
199
257
|
expect(onChangeComplete).toHaveBeenCalledTimes(1);
|
|
200
258
|
});
|
|
201
259
|
|
|
260
|
+
it('localizes the search input accessible name', () => {
|
|
261
|
+
renderMobileSelect();
|
|
262
|
+
|
|
263
|
+
openPopup();
|
|
264
|
+
|
|
265
|
+
expect(screen.getByRole('searchbox', { name: 'Search' })).toBeInTheDocument();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('renders a translated cancel action after searching', () => {
|
|
269
|
+
renderMobileSelect();
|
|
270
|
+
|
|
271
|
+
openPopup();
|
|
272
|
+
act(() => {
|
|
273
|
+
fireEvent.change(screen.getByTestId('search'), { target: { value: 'Option' } });
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('preserves the Chinese search input accessible name', () => {
|
|
280
|
+
mockState.flowLocale = 'zh-CN';
|
|
281
|
+
renderMobileSelect();
|
|
282
|
+
|
|
283
|
+
openPopup();
|
|
284
|
+
act(() => {
|
|
285
|
+
fireEvent.change(screen.getByTestId('search'), { target: { value: 'Option' } });
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(screen.getByRole('searchbox', { name: '搜索框' })).toBeInTheDocument();
|
|
289
|
+
expect(screen.getByRole('button', { name: '取消' })).toBeInTheDocument();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('does not reuse the simplified Chinese accessible name for traditional Chinese', () => {
|
|
293
|
+
mockState.flowLocale = 'zh-TW';
|
|
294
|
+
renderMobileSelect();
|
|
295
|
+
|
|
296
|
+
openPopup();
|
|
297
|
+
|
|
298
|
+
expect(screen.getByRole('searchbox', { name: '搜尋' })).toBeInTheDocument();
|
|
299
|
+
});
|
|
300
|
+
|
|
202
301
|
it('defers commit until confirm in multiple mode', () => {
|
|
203
302
|
const { onChange, onChangeComplete } = renderMobileSelect({ value: [], mode: 'multiple' });
|
|
204
303
|
openPopup();
|
|
@@ -289,6 +388,33 @@ describe('MobileLazySelect', () => {
|
|
|
289
388
|
resetMockState();
|
|
290
389
|
});
|
|
291
390
|
|
|
391
|
+
it('renders a translated cancel action after searching', () => {
|
|
392
|
+
renderMobileLazySelect();
|
|
393
|
+
|
|
394
|
+
openLazyPopup();
|
|
395
|
+
act(() => {
|
|
396
|
+
fireEvent.change(screen.getByTestId('search'), { target: { value: '11' } });
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('clears the selected relation record when it is tapped again in single mode', () => {
|
|
403
|
+
const { onChange } = renderMobileLazySelect({
|
|
404
|
+
value: RELATION_OPTIONS[0],
|
|
405
|
+
multiple: false,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
openLazyPopup();
|
|
409
|
+
expect(mockState.checklistProps?.value).toEqual([RELATION_OPTIONS[0].uuid]);
|
|
410
|
+
|
|
411
|
+
selectValues([]);
|
|
412
|
+
|
|
413
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
414
|
+
expect(onChange).toHaveBeenCalledWith(undefined);
|
|
415
|
+
expect(screen.queryByTestId('popup')).not.toBeInTheDocument();
|
|
416
|
+
});
|
|
417
|
+
|
|
292
418
|
it('keeps pending relation records selected until confirm', () => {
|
|
293
419
|
const { onChange, rerender } = renderMobileLazySelect();
|
|
294
420
|
|
|
@@ -105,9 +105,6 @@ function resolveItemDependencyPath(ctx: FlowContext, depPath: NamePath): DataSco
|
|
|
105
105
|
ctx,
|
|
106
106
|
parseFieldIndexEntries((ctx.model as any)?.context?.fieldIndex ?? (ctx as any)?.fieldIndex),
|
|
107
107
|
);
|
|
108
|
-
if (!entries.length) {
|
|
109
|
-
return wildcardDeps();
|
|
110
|
-
}
|
|
111
108
|
|
|
112
109
|
let parentDepth = 0;
|
|
113
110
|
let cursor = [...depPath];
|
|
@@ -121,6 +118,10 @@ function resolveItemDependencyPath(ctx: FlowContext, depPath: NamePath): DataSco
|
|
|
121
118
|
return wildcardDeps();
|
|
122
119
|
}
|
|
123
120
|
|
|
121
|
+
if (!entries.length) {
|
|
122
|
+
return parentDepth === 0 ? resolveRootItemDependencyPath(cursor) : emptyDeps();
|
|
123
|
+
}
|
|
124
|
+
|
|
124
125
|
if (parentDepth === entries.length) {
|
|
125
126
|
return resolveRootItemDependencyPath(cursor);
|
|
126
127
|
}
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,8 @@ export * from './layout-manager';
|
|
|
30
30
|
export * from './hooks';
|
|
31
31
|
export { default as languageCodes } from './locale/languageCodes';
|
|
32
32
|
export * from './nocobase-buildin-plugin';
|
|
33
|
+
export { getRouteRuntimeVersion } from './utils/getRouteRuntimeVersion';
|
|
34
|
+
export type { RouteRuntimeVersion } from './utils/getRouteRuntimeVersion';
|
|
33
35
|
export * from './collection-field-interface/CollectionFieldInterface';
|
|
34
36
|
export * from './collection-field-interface/CollectionFieldInterfaceManager';
|
|
35
37
|
export * from './collection-manager/field-configure';
|
|
@@ -40,6 +42,8 @@ export * from './collection-manager/template-fields';
|
|
|
40
42
|
export * from './data-source';
|
|
41
43
|
export * from './entry-actions';
|
|
42
44
|
export * from './flow';
|
|
45
|
+
export { CodeEditorExtension } from './flow/components/code-editor/extension';
|
|
46
|
+
export type { CodeEditorExtra, CodeEditorExtraRegistry, EditorRef } from './flow/components/code-editor/types';
|
|
43
47
|
export {
|
|
44
48
|
DEFAULT_DATA_SOURCE_KEY,
|
|
45
49
|
IconPicker,
|
|
@@ -49,4 +53,5 @@ export {
|
|
|
49
53
|
} from './flow-compat';
|
|
50
54
|
export type { NocoBaseDesktopRoute } from './flow-compat';
|
|
51
55
|
export * from './utils/markdownSanitize';
|
|
56
|
+
export * from './ui-operation';
|
|
52
57
|
export { default as AntdAppProvider } from './theme/AntdAppProvider';
|
|
@@ -38,11 +38,12 @@ export const LayoutContentRoute = (props: LayoutContentRouteProps) => {
|
|
|
38
38
|
id: lastMatch?.id,
|
|
39
39
|
name: lastMatch?.id,
|
|
40
40
|
pathname: location.pathname,
|
|
41
|
+
state: location.state,
|
|
41
42
|
params: (lastMatch?.params || {}) as Record<string, string | undefined>,
|
|
42
43
|
layoutRouteName: layout.routeName,
|
|
43
44
|
layoutBasePathname: layoutMatch?.pathname,
|
|
44
45
|
};
|
|
45
|
-
}, [layout.routeName, location.pathname, matches]);
|
|
46
|
+
}, [layout.routeName, location.pathname, location.state, matches]);
|
|
46
47
|
if (!model) {
|
|
47
48
|
throw new Error(`[NocoBase] Layout '${layout.routeName}' model '${layout.uid}' is not mounted.`);
|
|
48
49
|
}
|
|
@@ -71,11 +71,12 @@ export const LayoutRoute = (props: LayoutRouteProps) => {
|
|
|
71
71
|
id: lastMatch?.id,
|
|
72
72
|
name: lastMatch?.id,
|
|
73
73
|
pathname: location.pathname,
|
|
74
|
+
state: location.state,
|
|
74
75
|
params: lastMatchParams,
|
|
75
76
|
layoutRouteName: layout.routeName,
|
|
76
77
|
layoutBasePathname: layoutMatch?.pathname,
|
|
77
78
|
};
|
|
78
|
-
}, [lastMatch?.id, lastMatchParams, layout.routeName, layoutMatch?.pathname, location.pathname]);
|
|
79
|
+
}, [lastMatch?.id, lastMatchParams, layout.routeName, layoutMatch?.pathname, location.pathname, location.state]);
|
|
79
80
|
const { loading, data, error } = useRequest(
|
|
80
81
|
async () => {
|
|
81
82
|
const existingModel = flowEngine.getModel<BaseLayoutModel>(layout.uid);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { FlowEngine, FlowEngineProvider, observer } from '@nocobase/flow-engine';
|
|
11
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
11
|
+
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
12
12
|
import React from 'react';
|
|
13
13
|
import { createMemoryRouter, Outlet, RouterProvider, useOutlet } from 'react-router-dom';
|
|
14
14
|
import { describe, expect, it, vi } from 'vitest';
|
|
@@ -106,6 +106,64 @@ describe('LayoutRoute', () => {
|
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
it('syncs updated router state when navigating to the same layout pathname', async () => {
|
|
110
|
+
const syncLayoutRoute = vi.fn();
|
|
111
|
+
|
|
112
|
+
class StateTrackingLayoutModel extends TestLayoutModel {
|
|
113
|
+
syncLayoutRoute(routeLike: Parameters<BaseLayoutModel['syncLayoutRoute']>[0]) {
|
|
114
|
+
syncLayoutRoute(routeLike);
|
|
115
|
+
return super.syncLayoutRoute(routeLike);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const stateTrackingLayout: LayoutDefinition = {
|
|
120
|
+
...layout,
|
|
121
|
+
layoutModelClass: 'StateTrackingLayoutModel',
|
|
122
|
+
};
|
|
123
|
+
const engine = new FlowEngine();
|
|
124
|
+
engine.registerModels({ StateTrackingLayoutModel });
|
|
125
|
+
engine.context.defineProperty('app', {
|
|
126
|
+
value: {
|
|
127
|
+
layoutManager: {
|
|
128
|
+
getLayout: () => stateTrackingLayout,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const router = createMemoryRouter(
|
|
134
|
+
[
|
|
135
|
+
{
|
|
136
|
+
id: layout.routeName,
|
|
137
|
+
path: layout.routePath,
|
|
138
|
+
element: <LayoutRoute layoutRouteName="test" />,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
{
|
|
142
|
+
initialEntries: ['/test'],
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
render(
|
|
147
|
+
<FlowEngineProvider engine={engine}>
|
|
148
|
+
<RouterProvider router={router} />
|
|
149
|
+
</FlowEngineProvider>,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
expect(await screen.findByTestId('layout-route')).toHaveTextContent('test');
|
|
153
|
+
syncLayoutRoute.mockClear();
|
|
154
|
+
|
|
155
|
+
const routeState = {
|
|
156
|
+
__nocobaseOpenViewInputArgs: { popup: { formData: { status: 'todo' } } },
|
|
157
|
+
};
|
|
158
|
+
await act(async () => {
|
|
159
|
+
await router.navigate('/test', { state: routeState });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await waitFor(() => {
|
|
163
|
+
expect(syncLayoutRoute).toHaveBeenCalledWith(expect.objectContaining({ state: routeState }));
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
109
167
|
it('does not activate desktop route loading for generic layouts', async () => {
|
|
110
168
|
const activateLayout = vi.fn(() => vi.fn());
|
|
111
169
|
const engine = new FlowEngine();
|
|
@@ -413,6 +471,34 @@ describe('LayoutContentRoute', () => {
|
|
|
413
471
|
});
|
|
414
472
|
});
|
|
415
473
|
|
|
474
|
+
it('syncs updated router state when navigating to the same content pathname', async () => {
|
|
475
|
+
const syncLayoutRoute = vi.fn();
|
|
476
|
+
|
|
477
|
+
class StateTrackingLayoutModel extends TestLayoutModel {
|
|
478
|
+
syncLayoutRoute(routeLike: Parameters<BaseLayoutModel['syncLayoutRoute']>[0]) {
|
|
479
|
+
syncLayoutRoute(routeLike);
|
|
480
|
+
return super.syncLayoutRoute(routeLike);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const { router } = setup('/test/page-1/view/popup', layout, StateTrackingLayoutModel);
|
|
485
|
+
await waitFor(() => {
|
|
486
|
+
expect(syncLayoutRoute).toHaveBeenCalled();
|
|
487
|
+
});
|
|
488
|
+
syncLayoutRoute.mockClear();
|
|
489
|
+
|
|
490
|
+
const routeState = {
|
|
491
|
+
__nocobaseOpenViewInputArgs: { popup: { formData: { status: 'todo' } } },
|
|
492
|
+
};
|
|
493
|
+
await act(async () => {
|
|
494
|
+
await router.navigate('/test/page-1/view/popup', { state: routeState });
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
await waitFor(() => {
|
|
498
|
+
expect(syncLayoutRoute).toHaveBeenCalledWith(expect.objectContaining({ state: routeState }));
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
|
|
416
502
|
it('parses nested layout route by matched layout pathname', async () => {
|
|
417
503
|
const nestedLayout: LayoutDefinition = {
|
|
418
504
|
...layout,
|
|
@@ -54,6 +54,7 @@ export const languageCodes: Record<string, LocaleOptions> = {
|
|
|
54
54
|
'ml-IN': { label: 'മലയാളം' },
|
|
55
55
|
'mn-MN': { label: 'Монгол хэл' },
|
|
56
56
|
'ms-MY': { label: 'بهاس ملايو' },
|
|
57
|
+
'my-MM': { label: 'မြန်မာဘာသာ' },
|
|
57
58
|
'nb-NO': { label: 'Norsk bokmål' },
|
|
58
59
|
'ne-NP': { label: 'नेपाली' },
|
|
59
60
|
'nl-BE': { label: 'Vlaams' },
|
|
@@ -73,7 +74,8 @@ export const languageCodes: Record<string, LocaleOptions> = {
|
|
|
73
74
|
'tk-TK': { label: 'Turkmen' },
|
|
74
75
|
'tr-TR': { label: 'Türkçe' },
|
|
75
76
|
'uk-UA': { label: 'Українська' },
|
|
76
|
-
'ur-PK': { label: '
|
|
77
|
+
'ur-PK': { label: 'اردو' },
|
|
78
|
+
'uz-UZ': { label: 'Oʻzbekcha' },
|
|
77
79
|
'vi-VN': { label: 'Tiếng Việt' },
|
|
78
80
|
'zh-CN': { label: '简体中文' },
|
|
79
81
|
'zh-HK': { label: '繁體中文(香港)' },
|
|
@@ -240,6 +240,16 @@ const CurrentUserProvider: FC = ({ children }) => {
|
|
|
240
240
|
return;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
try {
|
|
244
|
+
await app.apiClient.auth.syncCookies();
|
|
245
|
+
} catch {
|
|
246
|
+
// Cookie bootstrap is best-effort; auth:check remains the source of truth for the current page load.
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!mounted) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
243
253
|
const userMeta = createCollectionContextMeta(
|
|
244
254
|
() => app.flowEngine.context.dataSourceManager?.getDataSource('main')?.getCollection('users') || null,
|
|
245
255
|
app.flowEngine.translate('Current user'),
|
|
@@ -298,6 +308,8 @@ const CurrentUserProvider: FC = ({ children }) => {
|
|
|
298
308
|
return <CurrentUserContext.Provider value={contextValue}>{children}</CurrentUserContext.Provider>;
|
|
299
309
|
};
|
|
300
310
|
|
|
311
|
+
CurrentUserProvider.displayName = 'CurrentUserProvider';
|
|
312
|
+
|
|
301
313
|
const RootRedirect: FC = () => {
|
|
302
314
|
const app = useApp<Application>();
|
|
303
315
|
const hasToken = !!app?.apiClient?.auth?.token;
|
|
@@ -14,7 +14,7 @@ import { FlowModelRenderer, useFlowEngine } from '@nocobase/flow-engine';
|
|
|
14
14
|
import { Layout, Menu, Result, theme } from 'antd';
|
|
15
15
|
import React, { useEffect, useMemo, useRef } from 'react';
|
|
16
16
|
import { useTranslation } from 'react-i18next';
|
|
17
|
-
import { Navigate, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom';
|
|
17
|
+
import { generatePath, Navigate, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom';
|
|
18
18
|
import { useACLRoleContext } from '../acl';
|
|
19
19
|
import { ADMIN_SETTINGS_PATH, type PluginSettingsPageType } from '../PluginSettingsManager';
|
|
20
20
|
import { useApp } from '../hooks/useApp';
|
|
@@ -68,6 +68,9 @@ export const InternalAdminSettingsLayout = () => {
|
|
|
68
68
|
const navigate = useNavigate();
|
|
69
69
|
const location = useLocation();
|
|
70
70
|
const params = useParams();
|
|
71
|
+
const routeParams = Object.fromEntries(
|
|
72
|
+
Object.entries(params).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),
|
|
73
|
+
);
|
|
71
74
|
const { token } = theme.useToken();
|
|
72
75
|
const { t } = useTranslation();
|
|
73
76
|
const { snippets = [] } = useACLRoleContext();
|
|
@@ -174,7 +177,25 @@ export const InternalAdminSettingsLayout = () => {
|
|
|
174
177
|
return <SettingsEmpty type="route" />;
|
|
175
178
|
}
|
|
176
179
|
|
|
177
|
-
if (
|
|
180
|
+
if (currentSetting.isAllow === false) {
|
|
181
|
+
const firstVisibleTabPath = currentVisibleTabs
|
|
182
|
+
.map((tab) => getDefaultSettingsPath([tab]))
|
|
183
|
+
.filter((path): path is string => typeof path === 'string')
|
|
184
|
+
.map((path) => {
|
|
185
|
+
try {
|
|
186
|
+
return generatePath(path, routeParams);
|
|
187
|
+
} catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
.find((path): path is string => typeof path === 'string');
|
|
192
|
+
if (firstVisibleTabPath) {
|
|
193
|
+
return <Navigate replace to={firstVisibleTabPath} />;
|
|
194
|
+
}
|
|
195
|
+
if (!defaultSettingsPath) {
|
|
196
|
+
return <Navigate replace to="/admin" />;
|
|
197
|
+
}
|
|
198
|
+
|
|
178
199
|
return <SettingsEmpty type="route" />;
|
|
179
200
|
}
|
|
180
201
|
|