@nocobase/client-v2 2.2.0-beta.9 → 3.0.0-alpha.1
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 +1 -0
- package/es/acl/ACLProvider.d.ts +10 -0
- package/es/authRedirect.d.ts +5 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/components/AttachmentUpload.d.ts +51 -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 +2 -0
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/actions/linkageRules.d.ts +3 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/NocoBaseLogo.d.ts +10 -0
- package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
- 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 +4 -2
- package/es/flow/models/base/BlockGridModel.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/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -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/routeTypes.d.ts +1 -0
- package/es/index.d.ts +8 -1
- package/es/index.mjs +190 -141
- package/es/nocobase-buildin-plugin/currentUserAuthStatus.d.ts +11 -0
- package/es/nocobase-buildin-plugin/index.d.ts +9 -0
- package/es/settings-app/SettingsApplication.d.ts +18 -0
- package/es/settings-app/SettingsBrand.d.ts +16 -0
- package/es/settings-app/SettingsBuildInPlugin.d.ts +9 -0
- package/es/settings-app/SettingsDocumentRedirect.d.ts +10 -0
- package/es/settings-app/SettingsGroupNav.d.ts +17 -0
- package/es/settings-app/SettingsPluginSettingsManager.d.ts +14 -0
- package/es/settings-app/SettingsRouterManager.d.ts +15 -0
- package/es/settings-app/SettingsSearch.d.ts +17 -0
- package/es/settings-app/SettingsShell.d.ts +10 -0
- package/es/settings-app/isSettingsApp.d.ts +18 -0
- package/es/settings-app/runtimePaths.d.ts +18 -0
- package/es/settings-app/settingsDocumentPath.d.ts +14 -0
- package/es/settings-app/settingsTheme.d.ts +87 -0
- package/es/settings-app/useSettingsThemeConfig.d.ts +22 -0
- package/es/settings-center/AdminSettingsLayout.d.ts +2 -2
- package/es/settings-center/groups.d.ts +66 -0
- package/es/settings-center/useSettingsGroups.d.ts +36 -0
- package/es/settings-center/useSettingsSearch.d.ts +35 -0
- package/es/settings-center/utils.d.ts +24 -0
- package/es/theme/index.d.ts +1 -0
- package/es/theme/itemActive.d.ts +24 -0
- package/es/theme/type.d.ts +9 -0
- package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
- package/es/utils/index.d.ts +1 -0
- package/lib/index.js +195 -146
- 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 +11 -6
- package/src/__tests__/app.test.tsx +73 -0
- package/src/__tests__/authRedirect.test.ts +137 -0
- package/src/__tests__/browserChecker.test.ts +185 -6
- package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
- package/src/__tests__/mockSettingsApplication.ts +29 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +79 -10
- package/src/__tests__/plugin-manager.test.tsx +7 -6
- package/src/__tests__/settings-application.test.ts +164 -0
- package/src/__tests__/settings-auth-routes.test.ts +71 -0
- package/src/__tests__/settings-center.test.tsx +38 -37
- package/src/__tests__/settings-layout-root.test.tsx +260 -0
- package/src/__tests__/settings-runtime-paths.test.ts +99 -0
- package/src/__tests__/settings-shell.test.tsx +200 -0
- package/src/acl/ACLProvider.tsx +21 -0
- package/src/authRedirect.ts +72 -3
- 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/AttachmentUpload.tsx +275 -0
- 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 +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/index.ts +2 -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.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 +111 -16
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +1 -57
- package/src/flow/admin-shell/admin-layout/NocoBaseLogo.tsx +77 -0
- package/src/flow/admin-shell/admin-layout/TopbarActionsBar.tsx +37 -14
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +77 -13
- 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 +91 -20
- 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/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/index.ts +3 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- 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 +6 -2
- 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/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- 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 +66 -4
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +4 -2
- package/src/flow/models/blocks/table/TableColumnModel.tsx +30 -1
- 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/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +2 -1
- 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/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -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 +52 -180
- 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 +14 -1
- package/src/locale/languageCodes.ts +3 -1
- package/src/nocobase-buildin-plugin/currentUserAuthStatus.ts +58 -0
- package/src/nocobase-buildin-plugin/index.tsx +110 -70
- package/src/settings-app/SettingsApplication.ts +31 -0
- package/src/settings-app/SettingsBrand.tsx +108 -0
- package/src/settings-app/SettingsBuildInPlugin.ts +10 -0
- package/src/settings-app/SettingsDocumentRedirect.tsx +26 -0
- package/src/settings-app/SettingsGroupNav.tsx +124 -0
- package/src/settings-app/SettingsPluginSettingsManager.ts +38 -0
- package/src/settings-app/SettingsRouterManager.ts +92 -0
- package/src/settings-app/SettingsSearch.tsx +238 -0
- package/src/settings-app/SettingsShell.tsx +167 -0
- package/src/settings-app/isSettingsApp.ts +21 -0
- package/src/settings-app/runtimePaths.ts +104 -0
- package/src/settings-app/settingsDocumentPath.ts +66 -0
- package/src/settings-app/settingsTheme.ts +301 -0
- package/src/settings-app/useSettingsThemeConfig.ts +100 -0
- package/src/settings-center/AdminSettingsLayout.tsx +122 -141
- package/src/settings-center/SystemSettingsPage.tsx +4 -170
- package/src/settings-center/groups.ts +108 -0
- package/src/settings-center/useSettingsGroups.ts +105 -0
- package/src/settings-center/useSettingsSearch.ts +173 -0
- package/src/settings-center/utils.tsx +50 -1
- package/src/theme/index.tsx +1 -0
- package/src/theme/itemActive.ts +31 -0
- package/src/theme/type.ts +10 -0
- package/src/utils/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FlowEngine, FlowModel } from '@nocobase/flow-engine';
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import { JsonFieldModel } from '../JsonFieldModel';
|
|
13
|
+
|
|
14
|
+
interface ValidationRule {
|
|
15
|
+
required?: boolean;
|
|
16
|
+
type?: string;
|
|
17
|
+
validator?: (_rule: unknown, value: unknown) => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createModels(rules: ValidationRule[]) {
|
|
21
|
+
const engine = new FlowEngine();
|
|
22
|
+
engine.registerModels({ JsonFieldModel });
|
|
23
|
+
const parent = engine.createModel<FlowModel<{ subModels: { field: JsonFieldModel } }>>({
|
|
24
|
+
use: FlowModel,
|
|
25
|
+
uid: 'json-form-item',
|
|
26
|
+
props: { rules },
|
|
27
|
+
subModels: {
|
|
28
|
+
field: {
|
|
29
|
+
use: JsonFieldModel,
|
|
30
|
+
uid: 'json-field',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return { field: parent.subModels.field, parent };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function initializeJsonValidation(field: JsonFieldModel) {
|
|
39
|
+
const step = field.getFlow('jsonInitSetting')?.steps.initValidation;
|
|
40
|
+
if (!step) {
|
|
41
|
+
throw new Error('JSON validation step is not registered');
|
|
42
|
+
}
|
|
43
|
+
step.handler(field.context, {});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('JsonFieldModel validation', () => {
|
|
47
|
+
it('replaces legacy empty rules with one serializable JSON validation rule', async () => {
|
|
48
|
+
const { field, parent } = createModels([{}, { required: true }]);
|
|
49
|
+
|
|
50
|
+
initializeJsonValidation(field);
|
|
51
|
+
|
|
52
|
+
const rules = parent.props.rules as ValidationRule[];
|
|
53
|
+
expect(rules).toHaveLength(2);
|
|
54
|
+
expect(rules[0]).toEqual({ required: true });
|
|
55
|
+
expect(rules[1]).toMatchObject({ type: 'any' });
|
|
56
|
+
expect(rules[1].validator).toBeTypeOf('function');
|
|
57
|
+
if (!rules[1].validator) {
|
|
58
|
+
throw new Error('JSON validator is missing');
|
|
59
|
+
}
|
|
60
|
+
await expect(rules[1].validator({}, { name: 'NocoBase' })).resolves.toBeUndefined();
|
|
61
|
+
await expect(rules[1].validator({}, '{"name":')).rejects.toContain('Invalid JSON format');
|
|
62
|
+
|
|
63
|
+
const persistedRules = JSON.parse(JSON.stringify(parent.serialize())).props.rules as ValidationRule[];
|
|
64
|
+
expect(persistedRules).toHaveLength(2);
|
|
65
|
+
expect(persistedRules[1]).toMatchObject({ type: 'any' });
|
|
66
|
+
expect(persistedRules[1].validator).toBeUndefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('re-registers the runtime validator without duplicating its persisted placeholder', () => {
|
|
70
|
+
const { field, parent } = createModels([{}]);
|
|
71
|
+
initializeJsonValidation(field);
|
|
72
|
+
const persistedRules = JSON.parse(JSON.stringify(parent.serialize())).props.rules as ValidationRule[];
|
|
73
|
+
parent.setProps({ rules: persistedRules });
|
|
74
|
+
|
|
75
|
+
initializeJsonValidation(field);
|
|
76
|
+
|
|
77
|
+
const rules = parent.props.rules as ValidationRule[];
|
|
78
|
+
expect(rules).toHaveLength(1);
|
|
79
|
+
expect(rules[0]).toMatchObject({ type: 'any' });
|
|
80
|
+
expect(rules[0].validator).toBeTypeOf('function');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
|
|
11
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { describe, expect, it } from 'vitest';
|
|
14
|
+
import { InputFieldModel } from '../InputFieldModel';
|
|
15
|
+
import { VariableFieldFormModel } from '../VariableFieldFormModel';
|
|
16
|
+
|
|
17
|
+
describe('VariableFieldFormModel', () => {
|
|
18
|
+
it('uses DOM-safe name and id for temporary controls created from reserved form property names', async () => {
|
|
19
|
+
const engine = new FlowEngine();
|
|
20
|
+
engine.registerModels({ InputFieldModel, VariableFieldFormModel });
|
|
21
|
+
const model = engine.createModel<VariableFieldFormModel>({
|
|
22
|
+
use: 'VariableFieldFormModel',
|
|
23
|
+
subModels: {
|
|
24
|
+
fields: [
|
|
25
|
+
{
|
|
26
|
+
use: 'InputFieldModel',
|
|
27
|
+
stepParams: {
|
|
28
|
+
fieldSettings: {
|
|
29
|
+
init: {
|
|
30
|
+
fieldPath: 'nodeName',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
const field = model.subModels.fields[0];
|
|
39
|
+
|
|
40
|
+
render(<FlowEngineProvider engine={engine}>{model.render()}</FlowEngineProvider>);
|
|
41
|
+
|
|
42
|
+
const input = await screen.findByRole('textbox');
|
|
43
|
+
await waitFor(() => {
|
|
44
|
+
expect(field.props.name).toEqual(['__nb_variable_field_nodeName']);
|
|
45
|
+
});
|
|
46
|
+
expect(input).toHaveAttribute('name', '__nb_variable_field_nodeName');
|
|
47
|
+
expect(input).toHaveAttribute('id', '__nb_variable_field_nodeName');
|
|
48
|
+
expect(field.props.id).toEqual(['__nb_variable_field_nodeName']);
|
|
49
|
+
expect(field.getStepParams('fieldSettings', 'init')).toEqual({ fieldPath: 'nodeName' });
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -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;
|
|
@@ -280,13 +294,7 @@ export function MobileLazySelect(props: Readonly<LazySelectProps>) {
|
|
|
280
294
|
showCancelButton
|
|
281
295
|
/>
|
|
282
296
|
</div>
|
|
283
|
-
<div
|
|
284
|
-
style={{
|
|
285
|
-
maxHeight: '60vh',
|
|
286
|
-
overflowY: 'auto',
|
|
287
|
-
}}
|
|
288
|
-
onScroll={handleScroll}
|
|
289
|
-
>
|
|
297
|
+
<div style={getMobileSelectListStyle(isMultiple)} onScroll={handleScroll}>
|
|
290
298
|
<CheckList multiple={isMultiple} value={selectedValueIds} onChange={handleListChange}>
|
|
291
299
|
{realOptions.map((item) => {
|
|
292
300
|
const optionValue = item?.[valueKey];
|
|
@@ -311,9 +319,11 @@ export function MobileLazySelect(props: Readonly<LazySelectProps>) {
|
|
|
311
319
|
)}
|
|
312
320
|
</div>
|
|
313
321
|
{isMultiple && (
|
|
314
|
-
<
|
|
315
|
-
{
|
|
316
|
-
|
|
322
|
+
<div style={mobileSelectConfirmFooterStyle}>
|
|
323
|
+
<Button block color="primary" onClick={handleConfirm} style={{ marginTop: '16px' }}>
|
|
324
|
+
{t('Confirm')}
|
|
325
|
+
</Button>
|
|
326
|
+
</div>
|
|
317
327
|
)}
|
|
318
328
|
</Popup>
|
|
319
329
|
</>
|
|
@@ -12,8 +12,23 @@ import { Select } from 'antd';
|
|
|
12
12
|
import { Button, CheckList, Popup, SearchBar } 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
|
+
|
|
15
29
|
export function MobileSelect(props) {
|
|
16
30
|
const { value, displayValue, onChange, onChangeComplete, disabled, options = [], mode } = props;
|
|
31
|
+
const isMultiple = ['multiple', 'tags'].includes(mode);
|
|
17
32
|
const ctx = useFlowModelContext();
|
|
18
33
|
const t = ctx.t;
|
|
19
34
|
const [visible, setVisible] = useState(false);
|
|
@@ -64,17 +79,12 @@ export function MobileSelect(props) {
|
|
|
64
79
|
<div style={{ margin: '10px' }}>
|
|
65
80
|
<SearchBar placeholder={t('search')} value={searchText} onChange={(v) => setSearchText(v)} showCancelButton />
|
|
66
81
|
</div>
|
|
67
|
-
<div
|
|
68
|
-
style={{
|
|
69
|
-
maxHeight: '60vh',
|
|
70
|
-
overflowY: 'auto',
|
|
71
|
-
}}
|
|
72
|
-
>
|
|
82
|
+
<div style={getMobileSelectListStyle(isMultiple)}>
|
|
73
83
|
<CheckList
|
|
74
|
-
multiple={
|
|
84
|
+
multiple={isMultiple}
|
|
75
85
|
value={Array.isArray(selected) ? selected : [selected]}
|
|
76
86
|
onChange={(val) => {
|
|
77
|
-
if (
|
|
87
|
+
if (isMultiple) {
|
|
78
88
|
setSelected(val);
|
|
79
89
|
} else {
|
|
80
90
|
setSelected(val[0]);
|
|
@@ -91,10 +101,12 @@ export function MobileSelect(props) {
|
|
|
91
101
|
))}
|
|
92
102
|
</CheckList>
|
|
93
103
|
</div>
|
|
94
|
-
{
|
|
95
|
-
<
|
|
96
|
-
{
|
|
97
|
-
|
|
104
|
+
{isMultiple && (
|
|
105
|
+
<div style={mobileSelectConfirmFooterStyle}>
|
|
106
|
+
<Button block color="primary" onClick={handleConfirm} style={{ marginTop: '16px' }}>
|
|
107
|
+
{t('Confirm')}
|
|
108
|
+
</Button>
|
|
109
|
+
</div>
|
|
98
110
|
)}
|
|
99
111
|
</Popup>
|
|
100
112
|
</>
|
|
@@ -14,12 +14,11 @@ import { Button, Dropdown, theme, Tooltip, type ButtonProps, type MenuProps } fr
|
|
|
14
14
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
15
15
|
import { useHotkeys } from 'react-hotkeys-hook';
|
|
16
16
|
import { useTranslation } from 'react-i18next';
|
|
17
|
-
import {
|
|
17
|
+
import { useLocation } from 'react-router-dom';
|
|
18
18
|
import { useACLRoleContext } from '../../../acl';
|
|
19
|
-
import type { BaseApplication } from '../../../BaseApplication';
|
|
20
19
|
import type { PluginSettingsPageType } from '../../../PluginSettingsManager';
|
|
21
|
-
import { shouldOpenAdminRouteInNewWindow } from '../../../RouterManager';
|
|
22
20
|
import { useApp } from '../../../hooks/useApp';
|
|
21
|
+
import { resolveStandaloneSettingsPath } from '../../../settings-app/runtimePaths';
|
|
23
22
|
import {
|
|
24
23
|
filterRenderableSettings,
|
|
25
24
|
filterVisibleSettings,
|
|
@@ -66,109 +65,6 @@ const topbarActionTriggerClassName = css`
|
|
|
66
65
|
height: 100%;
|
|
67
66
|
`;
|
|
68
67
|
|
|
69
|
-
type TopbarSettingsAppLike = Pick<
|
|
70
|
-
BaseApplication<any>,
|
|
71
|
-
'name' | 'router' | 'getPublicPath' | 'getHref' | 'layoutManager'
|
|
72
|
-
>;
|
|
73
|
-
|
|
74
|
-
const normalizeTopbarPath = (pathname?: string) => {
|
|
75
|
-
const trimmed = pathname?.trim();
|
|
76
|
-
if (!trimmed || trimmed === '/') {
|
|
77
|
-
return '/';
|
|
78
|
-
}
|
|
79
|
-
return `/${trimmed.replace(/^\/+/, '')}`;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const normalizeTopbarBasePath = (pathname?: string) => {
|
|
83
|
-
const normalized = normalizeTopbarPath(pathname).replace(/\/+$/, '');
|
|
84
|
-
return normalized === '' || normalized === '/' ? '' : normalized;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const getTopbarRouterBasePath = (app: TopbarSettingsAppLike | undefined) => {
|
|
88
|
-
return app?.router?.getBasename?.() || app?.router?.basename || app?.getPublicPath?.() || '';
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const stripTopbarRouterBasePath = (pathname: string, basename?: string) => {
|
|
92
|
-
const normalizedPath = normalizeTopbarPath(pathname);
|
|
93
|
-
const normalizedBase = normalizeTopbarBasePath(basename);
|
|
94
|
-
|
|
95
|
-
if (!normalizedBase) {
|
|
96
|
-
return normalizedPath;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (normalizedPath === normalizedBase) {
|
|
100
|
-
return '/';
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (normalizedPath.startsWith(`${normalizedBase}/`)) {
|
|
104
|
-
return normalizeTopbarPath(normalizedPath.slice(normalizedBase.length));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return normalizedPath;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const getTopbarAppPath = (pathname: string) => {
|
|
111
|
-
const normalizedPath = normalizeTopbarPath(pathname);
|
|
112
|
-
const match = /^(.*?\/(?:apps|_app)\/[^/]+)(?=\/|$)/.exec(normalizedPath);
|
|
113
|
-
const appPath = match?.[1] || '';
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
appPath,
|
|
117
|
-
routePath: appPath ? normalizeTopbarPath(normalizedPath.slice(appPath.length)) : normalizedPath,
|
|
118
|
-
};
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const getTopbarContextAppPath = (app: TopbarSettingsAppLike | undefined, basename?: string) => {
|
|
122
|
-
const basenameAppPath = getTopbarAppPath(basename || '').appPath;
|
|
123
|
-
if (basenameAppPath) {
|
|
124
|
-
return basenameAppPath;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const publicPathAppPath = getTopbarAppPath(app?.getPublicPath?.() || '').appPath;
|
|
128
|
-
if (publicPathAppPath) {
|
|
129
|
-
return publicPathAppPath;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (app?.name && app.name !== 'main' && app.getHref) {
|
|
133
|
-
return normalizeTopbarBasePath(app.getHref('/'));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return '';
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const prependTopbarAppPath = (pathname: string, appPath: string) => {
|
|
140
|
-
const normalizedPath = normalizeTopbarPath(pathname);
|
|
141
|
-
|
|
142
|
-
if (!appPath || normalizedPath === appPath || normalizedPath.startsWith(`${appPath}/`)) {
|
|
143
|
-
return normalizedPath;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return `${appPath}${normalizedPath}`;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const isAdminRuntimePath = (pathname: string) => {
|
|
150
|
-
return pathname === '/admin' || pathname.startsWith('/admin/');
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const getTopbarAdminRoutePath = (app: TopbarSettingsAppLike | undefined) => {
|
|
154
|
-
try {
|
|
155
|
-
return app?.layoutManager?.getLayout?.('admin')?.routePath;
|
|
156
|
-
} catch {
|
|
157
|
-
return undefined;
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const buildTopbarDocumentHref = (targetPath: string, basename?: string) => {
|
|
162
|
-
const normalizedTarget = normalizeTopbarPath(targetPath);
|
|
163
|
-
const normalizedBase = normalizeTopbarBasePath(basename);
|
|
164
|
-
|
|
165
|
-
if (!normalizedBase || normalizedTarget === normalizedBase || normalizedTarget.startsWith(`${normalizedBase}/`)) {
|
|
166
|
-
return normalizedTarget;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return `${normalizedBase}${normalizedTarget}`;
|
|
170
|
-
};
|
|
171
|
-
|
|
172
68
|
function TopbarExternalSettingsLabel(props: { title: React.ReactNode; link: string }) {
|
|
173
69
|
return (
|
|
174
70
|
<div
|
|
@@ -185,39 +81,13 @@ function TopbarInternalSettingsLabel(props: { title: React.ReactNode; path?: str
|
|
|
185
81
|
const app = useApp();
|
|
186
82
|
const location = useLocation();
|
|
187
83
|
const targetPath = props.path || '/admin/settings';
|
|
188
|
-
const basename = getTopbarRouterBasePath(app);
|
|
189
|
-
const currentPath = stripTopbarRouterBasePath(location.pathname, basename);
|
|
190
|
-
const currentLocationAppPath = getTopbarAppPath(currentPath);
|
|
191
|
-
const currentAppPath = currentLocationAppPath.appPath || getTopbarContextAppPath(app, basename);
|
|
192
|
-
const currentRoutePath = currentLocationAppPath.appPath ? currentLocationAppPath.routePath : currentPath;
|
|
193
|
-
const targetPathInCurrentApp = prependTopbarAppPath(stripTopbarRouterBasePath(targetPath, basename), currentAppPath);
|
|
194
|
-
|
|
195
|
-
if (currentAppPath) {
|
|
196
|
-
const href = buildTopbarDocumentHref(targetPathInCurrentApp, basename);
|
|
197
|
-
const shouldOpenInNewWindow = shouldOpenAdminRouteInNewWindow({
|
|
198
|
-
currentPathname: currentPath,
|
|
199
|
-
targetPathname: targetPathInCurrentApp,
|
|
200
|
-
basePath: basename,
|
|
201
|
-
adminRoutePath: getTopbarAdminRoutePath(app),
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
return (
|
|
205
|
-
<a
|
|
206
|
-
href={href}
|
|
207
|
-
target={shouldOpenInNewWindow ? '_blank' : undefined}
|
|
208
|
-
rel={shouldOpenInNewWindow ? 'noopener noreferrer' : undefined}
|
|
209
|
-
>
|
|
210
|
-
{props.title}
|
|
211
|
-
</a>
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (isAdminRuntimePath(currentRoutePath)) {
|
|
216
|
-
return <Link to={targetPathInCurrentApp}>{props.title}</Link>;
|
|
217
|
-
}
|
|
218
84
|
|
|
219
85
|
return (
|
|
220
|
-
<a
|
|
86
|
+
<a
|
|
87
|
+
href={resolveStandaloneSettingsPath(app, targetPath, location.pathname)}
|
|
88
|
+
target="_blank"
|
|
89
|
+
rel="noopener noreferrer"
|
|
90
|
+
>
|
|
221
91
|
{props.title}
|
|
222
92
|
</a>
|
|
223
93
|
);
|
|
@@ -238,66 +108,68 @@ export function getTopbarPluginSettingsItems(options: {
|
|
|
238
108
|
}): NonNullable<MenuProps['items']> {
|
|
239
109
|
const { settings, canManagePlugins, t } = options;
|
|
240
110
|
const topLevelSettings = filterVisibleSettings(filterRenderableSettings(settings));
|
|
241
|
-
const
|
|
111
|
+
const primarySettings = sortTopLevelSettings(
|
|
112
|
+
topLevelSettings
|
|
113
|
+
.filter(
|
|
114
|
+
(item) =>
|
|
115
|
+
((item.sort || 0) < 0 || item.name === PLUGIN_MANAGER_SETTING_NAME) &&
|
|
116
|
+
(item.name !== PLUGIN_MANAGER_SETTING_NAME || canManagePlugins),
|
|
117
|
+
)
|
|
118
|
+
.map((item) => ({
|
|
119
|
+
...item,
|
|
120
|
+
children: undefined,
|
|
121
|
+
})),
|
|
122
|
+
);
|
|
242
123
|
const settingsByKey = new Map<string, PluginSettingsPageType>();
|
|
243
124
|
const normalSettings = sortTopLevelSettings(
|
|
244
125
|
topLevelSettings
|
|
245
|
-
.filter((item) => item.name !== PLUGIN_MANAGER_SETTING_NAME)
|
|
126
|
+
.filter((item) => (item.sort || 0) >= 0 && item.name !== PLUGIN_MANAGER_SETTING_NAME)
|
|
246
127
|
.map((item) => ({
|
|
247
128
|
...item,
|
|
248
129
|
children: undefined,
|
|
249
130
|
})),
|
|
250
131
|
);
|
|
251
132
|
|
|
252
|
-
normalSettings.forEach((item) => {
|
|
133
|
+
[...primarySettings, ...normalSettings].forEach((item) => {
|
|
253
134
|
settingsByKey.set(item.key, item);
|
|
254
135
|
});
|
|
255
136
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const items: NonNullable<MenuProps['items']> = [];
|
|
282
|
-
|
|
283
|
-
if (canManagePlugins && pluginManagerSetting) {
|
|
284
|
-
items.push({
|
|
285
|
-
key: pluginManagerSetting.key,
|
|
286
|
-
icon: pluginManagerSetting.icon || <ApiOutlined />,
|
|
287
|
-
label: (
|
|
288
|
-
<TopbarInternalSettingsLabel
|
|
289
|
-
title={pluginManagerSetting.title || t('Plugin manager')}
|
|
290
|
-
path={pluginManagerSetting.path}
|
|
291
|
-
/>
|
|
292
|
-
),
|
|
137
|
+
const buildMenuItems = (targetSettings: PluginSettingsPageType[]) =>
|
|
138
|
+
(getMenuItems(targetSettings) || []).map((item: any) => {
|
|
139
|
+
if (item?.type === 'divider') {
|
|
140
|
+
return item;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const matchedSetting = settingsByKey.get(String(item.key));
|
|
144
|
+
const targetPath = matchedSetting?.path;
|
|
145
|
+
const targetLink = matchedSetting?.link;
|
|
146
|
+
const isPluginManager = matchedSetting?.name === PLUGIN_MANAGER_SETTING_NAME;
|
|
147
|
+
const targetTitle = matchedSetting?.title || (isPluginManager ? t('Plugin manager') : item.title);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
key: item.key,
|
|
151
|
+
name: matchedSetting?.name,
|
|
152
|
+
path: matchedSetting?.path,
|
|
153
|
+
link: targetLink,
|
|
154
|
+
title: targetTitle,
|
|
155
|
+
icon: isPluginManager ? matchedSetting?.icon || <ApiOutlined /> : item.icon,
|
|
156
|
+
label: targetLink ? (
|
|
157
|
+
<TopbarExternalSettingsLabel title={targetTitle} link={targetLink} />
|
|
158
|
+
) : (
|
|
159
|
+
<TopbarInternalSettingsLabel title={targetTitle} path={targetPath} />
|
|
160
|
+
),
|
|
161
|
+
};
|
|
293
162
|
});
|
|
294
|
-
}
|
|
295
163
|
|
|
296
|
-
|
|
164
|
+
const primaryItems = buildMenuItems(primarySettings);
|
|
165
|
+
const normalItems = buildMenuItems(normalSettings);
|
|
166
|
+
const items: NonNullable<MenuProps['items']> = [...primaryItems];
|
|
167
|
+
|
|
168
|
+
if (primaryItems.length && normalItems.length) {
|
|
297
169
|
items.push({ type: 'divider' });
|
|
298
170
|
}
|
|
299
171
|
|
|
300
|
-
items.push(...
|
|
172
|
+
items.push(...normalItems);
|
|
301
173
|
|
|
302
174
|
return items;
|
|
303
175
|
}
|
|
@@ -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
|
@@ -29,7 +29,16 @@ export * from './PluginSettingsManager';
|
|
|
29
29
|
export * from './layout-manager';
|
|
30
30
|
export * from './hooks';
|
|
31
31
|
export { default as languageCodes } from './locale/languageCodes';
|
|
32
|
-
export
|
|
32
|
+
export {
|
|
33
|
+
CurrentUserContext,
|
|
34
|
+
NocoBaseBuildInPlugin,
|
|
35
|
+
NocoBaseBuildInPluginV2,
|
|
36
|
+
useCurrentRoles,
|
|
37
|
+
useCurrentUserContext,
|
|
38
|
+
} from './nocobase-buildin-plugin';
|
|
39
|
+
export type { CurrentRoleOption, CurrentUserState } from './nocobase-buildin-plugin';
|
|
40
|
+
export { getRouteRuntimeVersion } from './utils/getRouteRuntimeVersion';
|
|
41
|
+
export type { RouteRuntimeVersion } from './utils/getRouteRuntimeVersion';
|
|
33
42
|
export * from './collection-field-interface/CollectionFieldInterface';
|
|
34
43
|
export * from './collection-field-interface/CollectionFieldInterfaceManager';
|
|
35
44
|
export * from './collection-manager/field-configure';
|
|
@@ -40,6 +49,8 @@ export * from './collection-manager/template-fields';
|
|
|
40
49
|
export * from './data-source';
|
|
41
50
|
export * from './entry-actions';
|
|
42
51
|
export * from './flow';
|
|
52
|
+
export { CodeEditorExtension } from './flow/components/code-editor/extension';
|
|
53
|
+
export type { CodeEditorExtra, CodeEditorExtraRegistry, EditorRef } from './flow/components/code-editor/types';
|
|
43
54
|
export {
|
|
44
55
|
DEFAULT_DATA_SOURCE_KEY,
|
|
45
56
|
IconPicker,
|
|
@@ -50,3 +61,5 @@ export {
|
|
|
50
61
|
export type { NocoBaseDesktopRoute } from './flow-compat';
|
|
51
62
|
export * from './utils/markdownSanitize';
|
|
52
63
|
export { default as AntdAppProvider } from './theme/AntdAppProvider';
|
|
64
|
+
export { isSettingsApp } from './settings-app/isSettingsApp';
|
|
65
|
+
export { MINIMAL_THEME_UID, useSettingsThemeConfig } from './settings-app/useSettingsThemeConfig';
|
|
@@ -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: '繁體中文(香港)' },
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useSyncExternalStore } from 'react';
|
|
11
|
+
|
|
12
|
+
export type CurrentUserAuthStatus = 'unknown' | 'authenticated' | 'unauthenticated' | 'redirecting';
|
|
13
|
+
|
|
14
|
+
type AuthStatusStore = {
|
|
15
|
+
getSnapshot: () => CurrentUserAuthStatus;
|
|
16
|
+
set: (status: CurrentUserAuthStatus) => void;
|
|
17
|
+
subscribe: (listener: () => void) => () => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const stores = new WeakMap<object, AuthStatusStore>();
|
|
21
|
+
|
|
22
|
+
function createAuthStatusStore(): AuthStatusStore {
|
|
23
|
+
let status: CurrentUserAuthStatus = 'unknown';
|
|
24
|
+
const listeners = new Set<() => void>();
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
getSnapshot: () => status,
|
|
28
|
+
set: (nextStatus) => {
|
|
29
|
+
if (status === nextStatus) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
status = nextStatus;
|
|
33
|
+
listeners.forEach((listener) => listener());
|
|
34
|
+
},
|
|
35
|
+
subscribe: (listener) => {
|
|
36
|
+
listeners.add(listener);
|
|
37
|
+
return () => listeners.delete(listener);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getAuthStatusStore(app: object) {
|
|
43
|
+
let store = stores.get(app);
|
|
44
|
+
if (!store) {
|
|
45
|
+
store = createAuthStatusStore();
|
|
46
|
+
stores.set(app, store);
|
|
47
|
+
}
|
|
48
|
+
return store;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function setCurrentUserAuthStatus(app: object, status: CurrentUserAuthStatus) {
|
|
52
|
+
getAuthStatusStore(app).set(status);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function useCurrentUserAuthStatus(app: object) {
|
|
56
|
+
const store = getAuthStatusStore(app);
|
|
57
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
58
|
+
}
|