@nocobase/flow-engine 2.0.0-alpha.9 → 2.0.0-beta.2
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/lib/BlockScopedFlowEngine.d.ts +23 -0
- package/lib/BlockScopedFlowEngine.js +92 -0
- package/lib/FlowDefinition.d.ts +6 -4
- package/lib/JSRunner.js +3 -0
- package/lib/ViewScopedFlowEngine.js +15 -1
- package/lib/acl/Acl.d.ts +12 -12
- package/lib/acl/Acl.js +78 -30
- package/lib/components/DynamicFlowsEditor.js +2 -4
- package/lib/components/FieldModelRenderer.js +10 -8
- package/lib/components/FieldSkeleton.d.ts +10 -0
- package/lib/components/FieldSkeleton.js +64 -0
- package/lib/components/FlowContextSelector.js +19 -3
- package/lib/components/FlowModelRenderer.d.ts +2 -1
- package/lib/components/FlowModelRenderer.js +34 -12
- package/lib/components/FormItem.js +5 -1
- package/lib/components/MobilePopup.d.ts +20 -0
- package/lib/components/MobilePopup.js +102 -0
- package/lib/components/MobilePopup.style.d.ts +17 -0
- package/lib/components/MobilePopup.style.js +186 -0
- package/lib/components/common/withFlowDesignMode.d.ts +1 -1
- package/lib/components/common/withFlowDesignMode.js +5 -5
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +3 -1
- package/lib/components/settings/independents/dropdown/FlowsDropdownButton.js +71 -53
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +19 -0
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +136 -0
- package/lib/components/settings/wrappers/component/SwitchWithTitle.d.ts +10 -0
- package/lib/components/settings/wrappers/component/SwitchWithTitle.js +110 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +221 -93
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +71 -54
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +2 -2
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +63 -23
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +11 -6
- package/lib/components/settings/wrappers/embedded/FlowSettings.js +42 -28
- package/lib/components/settings/wrappers/embedded/FlowsSettings.js +3 -3
- package/lib/components/settings/wrappers/embedded/FlowsSettingsContent.js +52 -32
- package/lib/components/subModel/AddSubModelButton.d.ts +7 -0
- package/lib/components/subModel/AddSubModelButton.js +78 -8
- package/lib/components/subModel/LazyDropdown.js +14 -15
- package/lib/components/subModel/utils.d.ts +1 -1
- package/lib/components/subModel/utils.js +21 -11
- package/lib/components/variables/VariableInput.js +5 -3
- package/lib/components/variables/types.d.ts +2 -0
- package/lib/components/variables/utils.js +4 -2
- package/lib/data-source/index.d.ts +43 -4
- package/lib/data-source/index.js +104 -11
- package/lib/data-source/jioToJoiSchema.js +1 -0
- package/lib/emitter.d.ts +6 -0
- package/lib/emitter.js +12 -0
- package/lib/executor/FlowExecutor.js +48 -7
- package/lib/flow-registry/GlobalFlowRegistry.d.ts +1 -0
- package/lib/flow-registry/GlobalFlowRegistry.js +3 -0
- package/lib/flow-registry/InstanceFlowRegistry.d.ts +1 -0
- package/lib/flow-registry/InstanceFlowRegistry.js +3 -0
- package/lib/flowContext.d.ts +6 -0
- package/lib/flowContext.js +111 -30
- package/lib/flowEngine.d.ts +49 -0
- package/lib/flowEngine.js +265 -10
- package/lib/flowSettings.d.ts +4 -3
- package/lib/flowSettings.js +33 -11
- package/lib/hooks/useApplyAutoFlows.d.ts +1 -0
- package/lib/hooks/useApplyAutoFlows.js +2 -2
- package/lib/index.d.ts +4 -2
- package/lib/index.js +11 -5
- package/lib/locale/de-DE.json +62 -0
- package/lib/locale/en-US.json +57 -45
- package/lib/locale/es-ES.json +62 -0
- package/lib/locale/fr-FR.json +62 -0
- package/lib/locale/hu-HU.json +62 -0
- package/lib/locale/id-ID.json +62 -0
- package/lib/locale/index.d.ts +114 -90
- package/lib/locale/it-IT.json +62 -0
- package/lib/locale/ja-JP.json +62 -0
- package/lib/locale/ko-KR.json +62 -0
- package/lib/locale/nl-NL.json +62 -0
- package/lib/locale/pt-BR.json +62 -0
- package/lib/locale/ru-RU.json +62 -0
- package/lib/locale/tr-TR.json +62 -0
- package/lib/locale/uk-UA.json +62 -0
- package/lib/locale/vi-VN.json +62 -0
- package/lib/locale/zh-CN.json +58 -46
- package/lib/locale/zh-TW.json +62 -0
- package/lib/models/CollectionFieldModel.d.ts +6 -2
- package/lib/models/CollectionFieldModel.js +60 -14
- package/lib/models/flowModel.d.ts +43 -4
- package/lib/models/flowModel.js +128 -26
- package/lib/models/forkFlowModel.d.ts +6 -2
- package/lib/models/forkFlowModel.js +9 -2
- package/lib/provider.d.ts +3 -1
- package/lib/provider.js +4 -3
- package/lib/reactive/index.d.ts +10 -0
- package/lib/reactive/index.js +41 -0
- package/lib/reactive/observer.d.ts +19 -0
- package/lib/reactive/observer.js +109 -0
- package/lib/resources/baseRecordResource.d.ts +1 -0
- package/lib/resources/baseRecordResource.js +14 -3
- package/lib/resources/multiRecordResource.d.ts +4 -2
- package/lib/resources/multiRecordResource.js +15 -6
- package/lib/resources/singleRecordResource.js +6 -3
- package/lib/resources/sqlResource.d.ts +1 -0
- package/lib/resources/sqlResource.js +22 -25
- package/lib/runjs-context/contexts/base.js +42 -6
- package/lib/runjs-context/snippets/global/clipboard-copy-text.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/global/clipboard-copy-text.snippet.js +61 -0
- package/lib/runjs-context/snippets/index.js +3 -0
- package/lib/runjs-context/snippets/scene/block/render-antd-icons.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/render-antd-icons.snippet.js +65 -0
- package/lib/runjs-context/snippets/scene/block/render-button-handler.snippet.js +6 -4
- package/lib/runjs-context/snippets/scene/block/render-info-card.snippet.js +15 -16
- package/lib/runjs-context/snippets/scene/block/render-react-jsx.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/render-react-jsx.snippet.js +58 -0
- package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +7 -7
- package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +24 -29
- package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +20 -21
- package/lib/scheduler/ModelOperationScheduler.d.ts +51 -0
- package/lib/scheduler/ModelOperationScheduler.js +262 -0
- package/lib/types.d.ts +42 -7
- package/lib/types.js +4 -3
- package/lib/utils/associationObjectVariable.d.ts +32 -0
- package/lib/utils/associationObjectVariable.js +157 -0
- package/lib/utils/createCollectionContextMeta.d.ts +1 -1
- package/lib/utils/createCollectionContextMeta.js +8 -4
- package/lib/utils/createEphemeralContext.d.ts +13 -0
- package/lib/utils/createEphemeralContext.js +140 -0
- package/lib/utils/flows.d.ts +10 -0
- package/lib/utils/flows.js +48 -0
- package/lib/utils/index.d.ts +7 -3
- package/lib/utils/index.js +20 -0
- package/lib/utils/jsxTransform.d.ts +15 -0
- package/lib/utils/jsxTransform.js +68 -0
- package/lib/utils/params-resolvers.js +3 -3
- package/lib/utils/parsePathnameToViewParams.d.ts +1 -1
- package/lib/utils/parsePathnameToViewParams.js +41 -5
- package/lib/utils/pruneFilter.d.ts +21 -0
- package/lib/utils/pruneFilter.js +52 -0
- package/lib/utils/safeGlobals.d.ts +5 -3
- package/lib/utils/safeGlobals.js +42 -1
- package/lib/utils/schema-utils.d.ts +6 -0
- package/lib/utils/schema-utils.js +71 -6
- package/lib/utils/serverContextParams.d.ts +3 -0
- package/lib/utils/serverContextParams.js +2 -0
- package/lib/utils/translation.d.ts +4 -1
- package/lib/utils/translation.js +6 -2
- package/lib/utils/variablesParams.d.ts +21 -5
- package/lib/utils/variablesParams.js +103 -34
- package/lib/views/DialogComponent.js +1 -5
- package/lib/views/DrawerComponent.js +18 -9
- package/lib/views/PageComponent.js +3 -4
- package/lib/views/ViewNavigation.d.ts +11 -15
- package/lib/views/ViewNavigation.js +37 -19
- package/lib/views/createViewMeta.d.ts +3 -2
- package/lib/views/createViewMeta.js +164 -53
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +36 -30
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +33 -26
- package/lib/views/usePage.d.ts +2 -1
- package/lib/views/usePage.js +40 -29
- package/package.json +6 -3
- package/src/BlockScopedFlowEngine.ts +88 -0
- package/src/JSRunner.ts +3 -0
- package/src/ViewScopedFlowEngine.ts +16 -0
- package/src/__tests__/JSRunner.test.ts +62 -53
- package/src/__tests__/blockScopedFlowEngine.test.ts +154 -0
- package/src/__tests__/createViewMeta.popup.test.ts +142 -0
- package/src/__tests__/flow-engine.test.ts +3 -0
- package/src/__tests__/flowContext.test.ts +70 -0
- package/src/__tests__/flowEngine.destroyModel.test.ts +74 -0
- package/src/__tests__/flowEngine.moveModel.test.ts +43 -0
- package/src/__tests__/flowEngine.removeModel.test.ts +72 -0
- package/src/__tests__/flowEngine.saveModel.test.ts +4 -0
- package/src/__tests__/flowModel.openView.navigation.test.ts +3 -2
- package/src/__tests__/flowSettings.open.test.tsx +2 -0
- package/src/__tests__/flowSettings.test.ts +2 -0
- package/src/__tests__/globalFlowRegistry.test.ts +1 -1
- package/src/__tests__/modelOperationScheduler.test.ts +346 -0
- package/src/__tests__/objectVariable.test.ts +464 -0
- package/src/__tests__/runjsRuntimeFeatures.test.ts +12 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +98 -0
- package/src/acl/Acl.tsx +85 -31
- package/src/acl/__tests__/Acl.test.tsx +43 -1
- package/src/components/DynamicFlowsEditor.tsx +0 -10
- package/src/components/FieldModelRenderer.tsx +15 -8
- package/src/components/FieldSkeleton.tsx +27 -0
- package/src/components/FlowContextSelector.tsx +20 -2
- package/src/components/FlowModelRenderer.tsx +46 -12
- package/src/components/FormItem.tsx +8 -1
- package/src/components/MobilePopup.style.ts +220 -0
- package/src/components/MobilePopup.tsx +86 -0
- package/src/components/__tests__/FlowModelRenderer.test.tsx +89 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +1 -1
- package/src/components/common/withFlowDesignMode.tsx +5 -5
- package/src/components/index.ts +1 -0
- package/src/components/settings/independents/dropdown/FlowsDropdownButton.tsx +34 -17
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +110 -0
- package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +82 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +260 -121
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +34 -18
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +56 -18
- package/src/components/settings/wrappers/contextual/StepSettings.tsx +1 -2
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +12 -6
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +565 -0
- package/src/components/settings/wrappers/embedded/FlowSettings.tsx +47 -35
- package/src/components/settings/wrappers/embedded/FlowsSettings.tsx +1 -1
- package/src/components/settings/wrappers/embedded/FlowsSettingsContent.tsx +64 -42
- package/src/components/subModel/AddSubModelButton.tsx +104 -9
- package/src/components/subModel/LazyDropdown.tsx +14 -14
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +168 -7
- package/src/components/subModel/__tests__/utils.test.ts +12 -12
- package/src/components/subModel/utils.ts +25 -6
- package/src/components/variables/VariableInput.tsx +5 -3
- package/src/components/variables/types.ts +2 -0
- package/src/components/variables/utils.ts +7 -3
- package/src/data-source/index.ts +169 -11
- package/src/data-source/jioToJoiSchema.ts +1 -0
- package/src/emitter.ts +14 -0
- package/src/executor/FlowExecutor.ts +56 -8
- package/src/executor/__tests__/ctx-defs-injection.test.ts +197 -0
- package/src/flow-registry/GlobalFlowRegistry.ts +1 -0
- package/src/flow-registry/InstanceFlowRegistry.ts +1 -0
- package/src/flow-registry/__tests__/globalFlowRegistry.test.ts +54 -0
- package/src/flowContext.ts +144 -29
- package/src/flowEngine.ts +328 -8
- package/src/flowSettings.ts +47 -19
- package/src/hooks/useApplyAutoFlows.ts +3 -3
- package/src/index.ts +4 -2
- package/src/locale/de-DE.json +62 -0
- package/src/locale/en-US.json +57 -45
- package/src/locale/es-ES.json +62 -0
- package/src/locale/fr-FR.json +62 -0
- package/src/locale/hu-HU.json +62 -0
- package/src/locale/id-ID.json +62 -0
- package/src/locale/it-IT.json +62 -0
- package/src/locale/ja-JP.json +62 -0
- package/src/locale/ko-KR.json +62 -0
- package/src/locale/nl-NL.json +62 -0
- package/src/locale/pt-BR.json +62 -0
- package/src/locale/ru-RU.json +62 -0
- package/src/locale/tr-TR.json +62 -0
- package/src/locale/uk-UA.json +62 -0
- package/src/locale/vi-VN.json +62 -0
- package/src/locale/zh-CN.json +58 -46
- package/src/locale/zh-TW.json +62 -0
- package/src/models/CollectionFieldModel.tsx +79 -17
- package/src/models/__tests__/dispatchEvent.behavior.test.ts +169 -0
- package/src/models/__tests__/flowEngine.resolveUse.test.ts +170 -0
- package/src/models/__tests__/flowModel.getFlows.sort.test.ts +29 -5
- package/src/models/__tests__/flowModel.scheduleModelOperation.test.tsx +129 -0
- package/src/models/__tests__/flowModel.test.ts +65 -27
- package/src/models/__tests__/forkFlowModel.test.ts +40 -7
- package/src/models/flowModel.tsx +192 -30
- package/src/models/forkFlowModel.ts +11 -3
- package/src/provider.tsx +5 -5
- package/src/reactive/__tests__/observer.test.tsx +211 -0
- package/src/reactive/index.ts +11 -0
- package/src/reactive/observer.tsx +101 -0
- package/src/resources/baseRecordResource.ts +15 -3
- package/src/resources/multiRecordResource.ts +17 -8
- package/src/resources/singleRecordResource.ts +6 -3
- package/src/resources/sqlResource.ts +22 -26
- package/src/runjs-context/contexts/base.ts +47 -6
- package/src/runjs-context/snippets/global/clipboard-copy-text.snippet.ts +42 -0
- package/src/runjs-context/snippets/index.ts +3 -0
- package/src/runjs-context/snippets/scene/block/render-antd-icons.snippet.ts +46 -0
- package/src/runjs-context/snippets/scene/block/render-button-handler.snippet.ts +6 -4
- package/src/runjs-context/snippets/scene/block/render-info-card.snippet.ts +15 -16
- package/src/runjs-context/snippets/scene/block/render-react-jsx.snippet.ts +39 -0
- package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +7 -7
- package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +24 -29
- package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +20 -21
- package/src/scheduler/ModelOperationScheduler.ts +304 -0
- package/src/types.ts +50 -4
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +51 -0
- package/src/utils/__tests__/flows.test.ts +65 -0
- package/src/utils/__tests__/jsxTransform.test.ts +38 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +25 -0
- package/src/utils/__tests__/pruneFilter.test.ts +38 -0
- package/src/utils/__tests__/safeGlobals.test.ts +23 -1
- package/src/utils/__tests__/utils.test.ts +114 -15
- package/src/utils/__tests__/variablesParams.test.ts +120 -0
- package/src/utils/associationObjectVariable.ts +180 -0
- package/src/utils/createCollectionContextMeta.ts +8 -3
- package/src/utils/createEphemeralContext.ts +142 -0
- package/src/utils/flows.ts +23 -0
- package/src/utils/index.ts +11 -2
- package/src/utils/jsxTransform.ts +39 -0
- package/src/utils/params-resolvers.ts +2 -2
- package/src/utils/parsePathnameToViewParams.ts +50 -6
- package/src/utils/pruneFilter.ts +41 -0
- package/src/utils/safeGlobals.ts +51 -4
- package/src/utils/schema-utils.ts +81 -3
- package/src/utils/serverContextParams.ts +5 -0
- package/src/utils/translation.ts +7 -2
- package/src/utils/variablesParams.ts +125 -42
- package/src/views/DialogComponent.tsx +1 -4
- package/src/views/DrawerComponent.tsx +19 -7
- package/src/views/PageComponent.tsx +2 -4
- package/src/views/ViewNavigation.ts +49 -43
- package/src/views/__tests__/FlowView.usePage.test.tsx +133 -0
- package/src/views/__tests__/ViewNavigation.test.ts +54 -34
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +132 -0
- package/src/views/createViewMeta.ts +179 -42
- package/src/views/useDialog.tsx +36 -24
- package/src/views/useDrawer.tsx +37 -24
- package/src/views/usePage.tsx +46 -27
|
@@ -22,28 +22,26 @@ describe('ViewNavigation', () => {
|
|
|
22
22
|
pathname: '/admin/test',
|
|
23
23
|
},
|
|
24
24
|
};
|
|
25
|
+
// Mock window.location
|
|
26
|
+
Object.defineProperty(window, 'location', {
|
|
27
|
+
value: {
|
|
28
|
+
pathname: '/admin',
|
|
29
|
+
},
|
|
30
|
+
writable: true,
|
|
31
|
+
});
|
|
25
32
|
});
|
|
26
33
|
|
|
27
34
|
describe('changeTo', () => {
|
|
28
|
-
it('should
|
|
29
|
-
viewNavigation = new ViewNavigation(mockCtx, []);
|
|
30
|
-
|
|
31
|
-
viewNavigation.changeTo({ viewUid: 'new-view' });
|
|
32
|
-
|
|
33
|
-
expect(viewNavigation.viewStack).toEqual([{ viewUid: 'new-view' }]);
|
|
34
|
-
expect(mockCtx.router.navigate).toHaveBeenCalledWith('/admin/new-view', { replace: true });
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should replace last view in viewStack', () => {
|
|
35
|
+
it('should keep viewStack unchanged', () => {
|
|
38
36
|
viewNavigation = new ViewNavigation(mockCtx, [{ viewUid: 'old-view' }]);
|
|
39
37
|
|
|
40
38
|
viewNavigation.changeTo({ viewUid: 'new-view', tabUid: 'tab1' });
|
|
41
39
|
|
|
42
|
-
expect(viewNavigation.viewStack).toEqual([{ viewUid: '
|
|
40
|
+
expect(viewNavigation.viewStack).toEqual([{ viewUid: 'old-view' }]);
|
|
43
41
|
expect(mockCtx.router.navigate).toHaveBeenCalledWith('/admin/new-view/tab/tab1', { replace: true });
|
|
44
42
|
});
|
|
45
43
|
|
|
46
|
-
it('should
|
|
44
|
+
it('should keep viewStack unchanged with complex parameters', () => {
|
|
47
45
|
viewNavigation = new ViewNavigation(mockCtx, [{ viewUid: 'view1' }, { viewUid: 'view2', tabUid: 'tab1' }]);
|
|
48
46
|
|
|
49
47
|
viewNavigation.changeTo({
|
|
@@ -53,10 +51,7 @@ describe('ViewNavigation', () => {
|
|
|
53
51
|
sourceId: 'source1',
|
|
54
52
|
});
|
|
55
53
|
|
|
56
|
-
expect(viewNavigation.viewStack).toEqual([
|
|
57
|
-
{ viewUid: 'view1' },
|
|
58
|
-
{ viewUid: 'new-view', tabUid: 'new-tab', filterByTk: '123', sourceId: 'source1' },
|
|
59
|
-
]);
|
|
54
|
+
expect(viewNavigation.viewStack).toEqual([{ viewUid: 'view1' }, { viewUid: 'view2', tabUid: 'tab1' }]); // keep unchanged
|
|
60
55
|
expect(mockCtx.router.navigate).toHaveBeenCalledWith(
|
|
61
56
|
'/admin/view1/view/new-view/tab/new-tab/filterbytk/123/sourceid/source1',
|
|
62
57
|
{ replace: true },
|
|
@@ -68,13 +63,15 @@ describe('ViewNavigation', () => {
|
|
|
68
63
|
|
|
69
64
|
viewNavigation.changeTo({ tabUid: 'new-tab' });
|
|
70
65
|
|
|
71
|
-
expect(viewNavigation.viewStack).toEqual([{ viewUid: 'view1'
|
|
66
|
+
expect(viewNavigation.viewStack).toEqual([{ viewUid: 'view1' }]);
|
|
67
|
+
expect(mockCtx.router.navigate).toHaveBeenCalledWith('/admin/view1/tab/new-tab', { replace: true });
|
|
72
68
|
});
|
|
73
69
|
});
|
|
74
70
|
|
|
75
71
|
describe('navigateTo', () => {
|
|
76
72
|
it('should navigate to new view', () => {
|
|
77
73
|
viewNavigation = new ViewNavigation(mockCtx, [{ viewUid: 'current-view' }]);
|
|
74
|
+
window.location.pathname = '/admin/current-view';
|
|
78
75
|
|
|
79
76
|
viewNavigation.navigateTo({ viewUid: 'new-view' });
|
|
80
77
|
|
|
@@ -85,20 +82,9 @@ describe('ViewNavigation', () => {
|
|
|
85
82
|
expect(call[1]).toBeUndefined();
|
|
86
83
|
});
|
|
87
84
|
|
|
88
|
-
it('should navigate back when pathname is the same', () => {
|
|
89
|
-
viewNavigation = new ViewNavigation(mockCtx, [{ viewUid: 'view1' }]);
|
|
90
|
-
// set browser location to match the generated pathname
|
|
91
|
-
window.history.pushState({}, '', '/admin/view1/view/view2');
|
|
92
|
-
|
|
93
|
-
viewNavigation.navigateTo({ viewUid: 'view2' });
|
|
94
|
-
|
|
95
|
-
// when same pathname, navigate(-1) to avoid "no reaction" UX
|
|
96
|
-
expect(mockCtx.router.navigate).toHaveBeenCalledWith(-1);
|
|
97
|
-
expect(viewNavigation.viewStack).toEqual([{ viewUid: 'view1' }]);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
85
|
it('should navigate with complex parameters', () => {
|
|
101
86
|
viewNavigation = new ViewNavigation(mockCtx, [{ viewUid: 'view1', tabUid: 'tab1' }]);
|
|
87
|
+
window.location.pathname = '/admin/view1/tab/tab1';
|
|
102
88
|
|
|
103
89
|
viewNavigation.navigateTo({
|
|
104
90
|
viewUid: 'view2',
|
|
@@ -116,25 +102,49 @@ describe('ViewNavigation', () => {
|
|
|
116
102
|
|
|
117
103
|
it('should navigate from empty viewStack', () => {
|
|
118
104
|
viewNavigation = new ViewNavigation(mockCtx, []);
|
|
105
|
+
window.location.pathname = '/admin';
|
|
119
106
|
|
|
120
107
|
viewNavigation.navigateTo({ viewUid: 'first-view' });
|
|
121
108
|
|
|
122
|
-
expect(viewNavigation.viewStack).toEqual([
|
|
109
|
+
expect(viewNavigation.viewStack).toEqual([]);
|
|
123
110
|
const call = (mockCtx.router.navigate as any).mock.calls[0];
|
|
124
111
|
expect(call[0]).toBe('/admin/first-view');
|
|
125
112
|
expect(call[1]).toBeUndefined();
|
|
126
113
|
});
|
|
114
|
+
|
|
115
|
+
it('should pass options to router.navigate', () => {
|
|
116
|
+
viewNavigation = new ViewNavigation(mockCtx, [{ viewUid: 'view1' }]);
|
|
117
|
+
window.location.pathname = '/admin/view1';
|
|
118
|
+
|
|
119
|
+
viewNavigation.navigateTo({ viewUid: 'view2' }, { replace: true });
|
|
120
|
+
|
|
121
|
+
expect(mockCtx.router.navigate).toHaveBeenCalledWith('/admin/view1/view/view2', { replace: true });
|
|
122
|
+
});
|
|
127
123
|
});
|
|
128
124
|
|
|
129
125
|
describe('back', () => {
|
|
130
|
-
it('should
|
|
126
|
+
it('should navigate to parent path', () => {
|
|
127
|
+
viewNavigation = new ViewNavigation(mockCtx, [{ viewUid: 'view1' }, { viewUid: 'view2' }]);
|
|
128
|
+
|
|
129
|
+
viewNavigation.back();
|
|
130
|
+
|
|
131
|
+
expect(mockCtx.router.navigate).toHaveBeenCalledWith('/admin/view1', { replace: true });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should navigate to root if stack has only one item', () => {
|
|
131
135
|
viewNavigation = new ViewNavigation(mockCtx, [{ viewUid: 'view1' }]);
|
|
132
|
-
// set browser location to current stack pathname so back() triggers
|
|
133
|
-
window.history.pushState({}, '', '/admin/view1');
|
|
134
136
|
|
|
135
137
|
viewNavigation.back();
|
|
136
138
|
|
|
137
|
-
expect(mockCtx.router.navigate).toHaveBeenCalledWith(
|
|
139
|
+
expect(mockCtx.router.navigate).toHaveBeenCalledWith('/admin', { replace: true });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should navigate to root if stack is empty', () => {
|
|
143
|
+
viewNavigation = new ViewNavigation(mockCtx, []);
|
|
144
|
+
|
|
145
|
+
viewNavigation.back();
|
|
146
|
+
|
|
147
|
+
expect(mockCtx.router.navigate).toHaveBeenCalledWith('/admin', { replace: true });
|
|
138
148
|
});
|
|
139
149
|
});
|
|
140
150
|
});
|
|
@@ -173,6 +183,16 @@ describe('generatePathnameFromViewParams', () => {
|
|
|
173
183
|
);
|
|
174
184
|
});
|
|
175
185
|
|
|
186
|
+
it('should encode object filterByTk as encoded key-value string', () => {
|
|
187
|
+
const path = generatePathnameFromViewParams([{ viewUid: 'xxx', filterByTk: { id: 1, tenant: 'ac' } }]);
|
|
188
|
+
expect(path).toBe('/admin/xxx/filterbytk/' + encodeURIComponent('id=1&tenant=ac'));
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should omit filterByTk segment for empty string or when absent', () => {
|
|
192
|
+
expect(generatePathnameFromViewParams([{ viewUid: 'xxx' }])).toBe('/admin/xxx');
|
|
193
|
+
expect(generatePathnameFromViewParams([{ viewUid: 'xxx', filterByTk: '' }])).toBe('/admin/xxx');
|
|
194
|
+
});
|
|
195
|
+
|
|
176
196
|
it('should match parsePathnameToViewParams test cases', () => {
|
|
177
197
|
// Test cases from parsePathnameToViewParams.test.ts
|
|
178
198
|
expect(generatePathnameFromViewParams([{ viewUid: 'xxx' }])).toBe('/admin/xxx');
|
|
@@ -0,0 +1,132 @@
|
|
|
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 React from 'react';
|
|
11
|
+
import { render } from '@testing-library/react';
|
|
12
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
13
|
+
import { useDialog } from '../useDialog';
|
|
14
|
+
import { FlowContext } from '../../flowContext';
|
|
15
|
+
|
|
16
|
+
// Mock dependencies
|
|
17
|
+
vi.mock('../provider', () => ({
|
|
18
|
+
FlowEngineProvider: ({ children }) => children,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock('../FlowContextProvider', () => ({
|
|
22
|
+
FlowViewContextProvider: ({ children }) => children,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('../ViewScopedFlowEngine', () => ({
|
|
26
|
+
createViewScopedEngine: (engine) => ({
|
|
27
|
+
context: new FlowContext(),
|
|
28
|
+
unlinkFromStack: vi.fn(),
|
|
29
|
+
}),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
vi.mock('../utils/variablesParams', () => ({
|
|
33
|
+
createViewRecordResolveOnServer: vi.fn(),
|
|
34
|
+
getViewRecordFromParent: vi.fn(),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
vi.mock('../createViewMeta', () => ({
|
|
38
|
+
registerPopupVariable: vi.fn(),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
vi.mock('../DialogComponent', () => ({
|
|
42
|
+
default: ({ children }) => <div>{children}</div>,
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
// Mock usePatchElement to return a mock close function
|
|
46
|
+
const mockCloseFunc = vi.fn();
|
|
47
|
+
const mockPatchElement = vi.fn(() => mockCloseFunc);
|
|
48
|
+
vi.mock('../usePatchElement', () => ({
|
|
49
|
+
default: () => [[], mockPatchElement],
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
describe('useDialog - close/destroy logic', () => {
|
|
53
|
+
const createMockFlowContext = () => {
|
|
54
|
+
const ctx = new FlowContext();
|
|
55
|
+
ctx.engine = {
|
|
56
|
+
context: new FlowContext(),
|
|
57
|
+
};
|
|
58
|
+
return ctx;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
vi.clearAllMocks();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const renderUseDialog = () => {
|
|
66
|
+
let api: any;
|
|
67
|
+
const TestComponent = () => {
|
|
68
|
+
const [dialogApi, contextHolder] = useDialog();
|
|
69
|
+
api = dialogApi;
|
|
70
|
+
return contextHolder as any;
|
|
71
|
+
};
|
|
72
|
+
render(<TestComponent />);
|
|
73
|
+
return api;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
it('should call destroy (and thus closeFunc) when close is called without preventClose', () => {
|
|
77
|
+
const api = renderUseDialog();
|
|
78
|
+
const flowContext = createMockFlowContext();
|
|
79
|
+
|
|
80
|
+
const dialog = api.open({}, flowContext);
|
|
81
|
+
|
|
82
|
+
dialog.close();
|
|
83
|
+
|
|
84
|
+
expect(mockCloseFunc).toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should not call destroy (and thus closeFunc) when close is called with preventClose=true', () => {
|
|
88
|
+
const api = renderUseDialog();
|
|
89
|
+
const flowContext = createMockFlowContext();
|
|
90
|
+
|
|
91
|
+
const dialog = api.open({ preventClose: true }, flowContext);
|
|
92
|
+
|
|
93
|
+
dialog.close();
|
|
94
|
+
|
|
95
|
+
expect(mockCloseFunc).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should call destroy (and thus closeFunc) when close is called with preventClose=true but force=true', () => {
|
|
99
|
+
const api = renderUseDialog();
|
|
100
|
+
const flowContext = createMockFlowContext();
|
|
101
|
+
|
|
102
|
+
const dialog = api.open({ preventClose: true }, flowContext);
|
|
103
|
+
|
|
104
|
+
dialog.close(undefined, true);
|
|
105
|
+
|
|
106
|
+
expect(mockCloseFunc).toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should delegate to navigation.back when triggerByRouter is true', () => {
|
|
110
|
+
const api = renderUseDialog();
|
|
111
|
+
const flowContext = createMockFlowContext();
|
|
112
|
+
const backMock = vi.fn();
|
|
113
|
+
|
|
114
|
+
const dialog = api.open(
|
|
115
|
+
{
|
|
116
|
+
triggerByRouter: true,
|
|
117
|
+
inputArgs: {
|
|
118
|
+
navigation: {
|
|
119
|
+
back: backMock,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
flowContext,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
dialog.close();
|
|
127
|
+
|
|
128
|
+
expect(backMock).toHaveBeenCalled();
|
|
129
|
+
// Should not call destroy directly, let router handle it
|
|
130
|
+
expect(mockCloseFunc).not.toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -13,6 +13,8 @@ import type { RecordRef } from '../utils/serverContextParams';
|
|
|
13
13
|
import type { Collection } from '../data-source';
|
|
14
14
|
import type { FlowView } from './FlowView';
|
|
15
15
|
|
|
16
|
+
type PopupModelLike = { getStepParams?: (a: string, b: string) => any } | undefined;
|
|
17
|
+
|
|
16
18
|
// 判断是否为普通对象(Plain Object),避免对类实例/代理等进行深度遍历
|
|
17
19
|
function isPlainObject(val: any) {
|
|
18
20
|
if (val === null || typeof val !== 'object') return false;
|
|
@@ -134,24 +136,48 @@ export function createViewMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
|
134
136
|
* - popup.resource:数据源信息(前端解析)
|
|
135
137
|
* - popup.parent:上级弹窗(无限级,前端解析;不存在则禁用/为空)
|
|
136
138
|
*/
|
|
137
|
-
export function createPopupMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
139
|
+
export function createPopupMeta(ctx: FlowContext, anchorView?: FlowView): PropertyMetaFactory {
|
|
138
140
|
const t = (k: string) => ctx.t(k);
|
|
139
141
|
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
const isPopupView = (view?: FlowView): boolean => {
|
|
143
|
+
if (!view) return false;
|
|
144
|
+
const stack = Array.isArray(view.navigation?.viewStack) ? view.navigation.viewStack : [];
|
|
145
|
+
return stack.length >= 2;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const hasPopupNow = (): boolean => isPopupView(anchorView ?? ctx.view);
|
|
149
|
+
|
|
150
|
+
// 统一解析锚定视图下的 RecordRef,避免在设置弹窗等二级视图中被误导
|
|
151
|
+
const resolveRecordRef = async (flowCtx: FlowContext): Promise<RecordRef | undefined> => {
|
|
152
|
+
const view = anchorView ?? (flowCtx.view as any);
|
|
153
|
+
if (!view || !isPopupView(view)) return undefined;
|
|
154
|
+
|
|
155
|
+
const base = await buildPopupRuntime(flowCtx, view);
|
|
156
|
+
const res = base?.resource;
|
|
157
|
+
if (res?.collectionName && res.filterByTk != null) {
|
|
158
|
+
return {
|
|
159
|
+
collection: res.collectionName,
|
|
160
|
+
dataSourceKey: res.dataSourceKey || 'main',
|
|
161
|
+
filterByTk: res.filterByTk,
|
|
162
|
+
associationName: res.associationName,
|
|
163
|
+
sourceId: res.sourceId,
|
|
164
|
+
};
|
|
148
165
|
}
|
|
166
|
+
return inferViewRecordRef(flowCtx);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const getCurrentCollection = async (): Promise<Collection | null> => {
|
|
170
|
+
const ref = await resolveRecordRef(ctx);
|
|
171
|
+
if (!ref?.collection) return null;
|
|
172
|
+
const ds = ctx.dataSourceManager?.getDataSource?.(ref.dataSourceKey || 'main');
|
|
173
|
+
return ds?.collectionManager?.getCollection?.(ref.collection) || null;
|
|
149
174
|
};
|
|
150
175
|
|
|
151
176
|
// 从视图堆栈推断 level 级父弹窗(level=1 上一层)
|
|
152
|
-
const getParentRecordRef = async (level: number): Promise<RecordRef | undefined> => {
|
|
177
|
+
const getParentRecordRef = async (level: number, flowCtx?: FlowContext): Promise<RecordRef | undefined> => {
|
|
153
178
|
try {
|
|
154
|
-
const
|
|
179
|
+
const useCtx = flowCtx || ctx;
|
|
180
|
+
const nav = useCtx.view?.navigation;
|
|
155
181
|
const stack = Array.isArray(nav?.viewStack) ? nav.viewStack : [];
|
|
156
182
|
if (stack.length < 2 || level < 1) return undefined;
|
|
157
183
|
const idx = stack.length - 1 - level;
|
|
@@ -159,12 +185,12 @@ export function createPopupMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
|
159
185
|
const parent = stack[idx];
|
|
160
186
|
if (!parent?.viewUid) return undefined;
|
|
161
187
|
|
|
162
|
-
let model
|
|
163
|
-
if (!model
|
|
188
|
+
let model = useCtx.engine?.getModel(parent.viewUid, true) as PopupModelLike;
|
|
189
|
+
if (!model) {
|
|
164
190
|
try {
|
|
165
|
-
model = await
|
|
191
|
+
model = (await useCtx.engine.loadModel({ uid: parent.viewUid })) as PopupModelLike;
|
|
166
192
|
} catch (e) {
|
|
167
|
-
|
|
193
|
+
(useCtx.logger || ctx.logger)?.warn?.({ err: e }, '[FlowEngine] popup.getParentRecordRef loadModel failed');
|
|
168
194
|
}
|
|
169
195
|
}
|
|
170
196
|
const params = model?.getStepParams?.('popupSettings', 'openView') || {};
|
|
@@ -172,16 +198,23 @@ export function createPopupMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
|
172
198
|
const dataSourceKey = params?.dataSourceKey || 'main';
|
|
173
199
|
const filterByTk = parent?.filterByTk ?? parent?.sourceId;
|
|
174
200
|
if (!collection || typeof filterByTk === 'undefined' || filterByTk === null) return undefined;
|
|
175
|
-
|
|
201
|
+
const ref: RecordRef = {
|
|
202
|
+
collection,
|
|
203
|
+
dataSourceKey,
|
|
204
|
+
filterByTk,
|
|
205
|
+
sourceId: parent?.sourceId,
|
|
206
|
+
associationName: params?.associationName,
|
|
207
|
+
};
|
|
208
|
+
return ref;
|
|
176
209
|
} catch (e) {
|
|
177
|
-
|
|
210
|
+
(flowCtx?.logger || ctx.logger)?.warn?.({ err: e }, '[FlowEngine] popup.getParentRecordRef failed');
|
|
178
211
|
return undefined;
|
|
179
212
|
}
|
|
180
213
|
};
|
|
181
214
|
|
|
182
215
|
const hasParentNow = (level: number): boolean => {
|
|
183
216
|
try {
|
|
184
|
-
const nav = ctx.view?.navigation;
|
|
217
|
+
const nav = (anchorView ?? ctx.view)?.navigation;
|
|
185
218
|
const stack = Array.isArray(nav?.viewStack) ? nav.viewStack : [];
|
|
186
219
|
return stack.length >= level + 1; // level=1 需要至少2层
|
|
187
220
|
} catch (_) {
|
|
@@ -196,6 +229,7 @@ export function createPopupMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
|
196
229
|
title: t('Parent popup'),
|
|
197
230
|
disabled: () => !hasParentNow(level),
|
|
198
231
|
disabledReason: () => (!hasParentNow(level) ? t('No parent popup') : undefined),
|
|
232
|
+
hidden: () => !hasParentNow(level),
|
|
199
233
|
properties: async () => {
|
|
200
234
|
const parentRef = await getParentRecordRef(level);
|
|
201
235
|
const props: Record<string, any> = {};
|
|
@@ -229,6 +263,7 @@ export function createPopupMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
|
229
263
|
properties: async () => ({
|
|
230
264
|
dataSourceKey: { type: 'string', title: t('Data source key') },
|
|
231
265
|
collectionName: { type: 'string', title: t('Collection name') },
|
|
266
|
+
associationName: { type: 'string', title: t('Association name') },
|
|
232
267
|
filterByTk: { type: 'string', title: t('filterByTk') },
|
|
233
268
|
sourceId: { type: 'string', title: t('sourceId') },
|
|
234
269
|
}),
|
|
@@ -251,10 +286,49 @@ export function createPopupMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
|
251
286
|
const meta: PropertyMeta = {
|
|
252
287
|
type: 'object',
|
|
253
288
|
title: t('Current popup'),
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
289
|
+
disabled: () => !hasPopupNow(),
|
|
290
|
+
hidden: () => !hasPopupNow(),
|
|
291
|
+
buildVariablesParams: async (c) => {
|
|
292
|
+
if (!hasPopupNow()) return undefined;
|
|
293
|
+
const ref = await resolveRecordRef(c);
|
|
294
|
+
const inputArgs = c.view?.inputArgs;
|
|
295
|
+
type PopupVariableParams = {
|
|
296
|
+
record?: RecordRef;
|
|
297
|
+
sourceRecord?: RecordRef;
|
|
298
|
+
parent?: PopupVariableParams;
|
|
299
|
+
};
|
|
300
|
+
const params: PopupVariableParams = {};
|
|
301
|
+
if (ref) {
|
|
302
|
+
const merged: RecordRef = { ...ref };
|
|
303
|
+
if (!merged.associationName && inputArgs?.associationName) {
|
|
304
|
+
merged.associationName = inputArgs.associationName;
|
|
305
|
+
}
|
|
306
|
+
if (typeof merged.sourceId === 'undefined' && typeof inputArgs?.sourceId !== 'undefined') {
|
|
307
|
+
merged.sourceId = inputArgs?.sourceId;
|
|
308
|
+
}
|
|
309
|
+
params.record = merged;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// 构建 parent 链(用于服务端解析 ctx.popup.parent[.parent...].record.*)
|
|
313
|
+
try {
|
|
314
|
+
const nav = c.view?.navigation;
|
|
315
|
+
const stack = Array.isArray(nav?.viewStack) ? nav.viewStack : [];
|
|
316
|
+
if (stack.length >= 2) {
|
|
317
|
+
let cur: Record<string, any> = params;
|
|
318
|
+
let level = 1;
|
|
319
|
+
let parentRef = await getParentRecordRef(level, c);
|
|
320
|
+
while (parentRef) {
|
|
321
|
+
if (!cur.parent) cur.parent = {};
|
|
322
|
+
cur.parent.record = parentRef;
|
|
323
|
+
cur = cur.parent;
|
|
324
|
+
level += 1;
|
|
325
|
+
parentRef = await getParentRecordRef(level, c);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} catch (err) {
|
|
329
|
+
c.logger?.debug?.({ err }, '[FlowEngine] buildVariablesParams: build parent-chain failed');
|
|
330
|
+
}
|
|
331
|
+
|
|
258
332
|
try {
|
|
259
333
|
const srcId = inputArgs?.sourceId;
|
|
260
334
|
const assoc: string | undefined = inputArgs?.associationName;
|
|
@@ -263,32 +337,58 @@ export function createPopupMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
|
263
337
|
// associationName 形如 `posts.comments`,父级集合为 `posts`
|
|
264
338
|
const parentCollectionName = String(assoc).split('.')[0];
|
|
265
339
|
if (parentCollectionName) {
|
|
266
|
-
|
|
340
|
+
params.sourceRecord = {
|
|
267
341
|
collection: parentCollectionName,
|
|
268
342
|
dataSourceKey: dsKey,
|
|
269
343
|
filterByTk: srcId,
|
|
270
344
|
};
|
|
271
345
|
}
|
|
272
346
|
}
|
|
273
|
-
} catch (
|
|
274
|
-
|
|
347
|
+
} catch (err) {
|
|
348
|
+
c.logger?.debug?.({ err }, '[FlowEngine] buildVariablesParams: infer sourceRecord failed');
|
|
275
349
|
}
|
|
276
|
-
return
|
|
350
|
+
return params;
|
|
277
351
|
},
|
|
278
352
|
properties: async () => {
|
|
279
353
|
const props: Record<string, any> = {};
|
|
280
354
|
// 当前弹窗 UID(纯前端变量)
|
|
281
355
|
props.uid = { type: 'string', title: t('Popup uid') };
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
356
|
+
// 基于锚定视图计算“当前弹窗记录”的集合与 RecordRef
|
|
357
|
+
const recordFactory: PropertyMetaFactory = async () => {
|
|
358
|
+
const col = await getCurrentCollection();
|
|
359
|
+
if (!col) return null;
|
|
360
|
+
return await buildRecordMeta(
|
|
361
|
+
() => col,
|
|
362
|
+
t('Current popup record'),
|
|
363
|
+
(c) => resolveRecordRef(c),
|
|
364
|
+
);
|
|
365
|
+
};
|
|
366
|
+
recordFactory.title = t('Current popup record');
|
|
367
|
+
recordFactory.hasChildren = true;
|
|
368
|
+
props.record = recordFactory;
|
|
286
369
|
// 当 view.inputArgs 带有 sourceId + associationName 时,提供“上级记录”变量(基于 sourceId 推断)
|
|
287
370
|
try {
|
|
288
|
-
const inputArgs =
|
|
371
|
+
const inputArgs = ctx.view?.inputArgs;
|
|
289
372
|
const srcId = inputArgs?.sourceId;
|
|
290
|
-
|
|
291
|
-
|
|
373
|
+
let assoc: string | undefined = inputArgs?.associationName;
|
|
374
|
+
let dsKey: string = inputArgs?.dataSourceKey || 'main';
|
|
375
|
+
|
|
376
|
+
// 兜底:若 associationName 缺失或不含“.”,尝试从当前视图模型的 openView 参数推断
|
|
377
|
+
if (!assoc || typeof assoc !== 'string' || !assoc.includes('.')) {
|
|
378
|
+
const nav = ctx.view?.navigation;
|
|
379
|
+
const stack = Array.isArray(nav?.viewStack) ? nav.viewStack : [];
|
|
380
|
+
const last = stack?.[stack.length - 1];
|
|
381
|
+
if (last?.viewUid) {
|
|
382
|
+
let model = ctx?.engine?.getModel(last.viewUid, true) as PopupModelLike;
|
|
383
|
+
if (!model) {
|
|
384
|
+
model = (await ctx.engine.loadModel({ uid: last.viewUid })) as PopupModelLike;
|
|
385
|
+
}
|
|
386
|
+
const p = model?.getStepParams?.('popupSettings', 'openView') || {};
|
|
387
|
+
assoc = p?.associationName || assoc;
|
|
388
|
+
dsKey = p?.dataSourceKey || dsKey;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
292
392
|
if (srcId != null && srcId !== '' && assoc && typeof assoc === 'string') {
|
|
293
393
|
const parentCollectionName = String(assoc).includes('.') ? String(assoc).split('.')[0] : undefined;
|
|
294
394
|
if (parentCollectionName) {
|
|
@@ -310,8 +410,8 @@ export function createPopupMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
|
310
410
|
}
|
|
311
411
|
}
|
|
312
412
|
}
|
|
313
|
-
} catch (
|
|
314
|
-
|
|
413
|
+
} catch (err) {
|
|
414
|
+
ctx.logger?.debug?.({ err }, '[FlowEngine] popup.properties: build sourceRecord failed');
|
|
315
415
|
}
|
|
316
416
|
const resourceMeta: PropertyMeta = {
|
|
317
417
|
type: 'object',
|
|
@@ -319,6 +419,7 @@ export function createPopupMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
|
319
419
|
properties: async () => ({
|
|
320
420
|
dataSourceKey: { type: 'string', title: t('Data source key') },
|
|
321
421
|
collectionName: { type: 'string', title: t('Collection name') },
|
|
422
|
+
associationName: { type: 'string', title: t('Association name') },
|
|
322
423
|
filterByTk: { type: 'string', title: t('filterByTk') },
|
|
323
424
|
sourceId: { type: 'string', title: t('sourceId') },
|
|
324
425
|
}),
|
|
@@ -346,6 +447,7 @@ export function createPopupMeta(ctx: FlowContext): PropertyMetaFactory {
|
|
|
346
447
|
interface PopupNodeResource {
|
|
347
448
|
dataSourceKey: string;
|
|
348
449
|
collectionName?: string;
|
|
450
|
+
associationName?: string;
|
|
349
451
|
filterByTk?: any;
|
|
350
452
|
sourceId?: any;
|
|
351
453
|
}
|
|
@@ -356,15 +458,40 @@ interface PopupNode {
|
|
|
356
458
|
parent?: PopupNode;
|
|
357
459
|
}
|
|
358
460
|
|
|
359
|
-
export async function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promise<PopupNode> {
|
|
461
|
+
export async function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promise<PopupNode | undefined> {
|
|
360
462
|
const nav = view?.navigation;
|
|
361
463
|
const stack = Array.isArray(nav?.viewStack) ? nav.viewStack : [];
|
|
464
|
+
|
|
465
|
+
const openerUids = view?.inputArgs?.openerUids;
|
|
466
|
+
const hasOpener = Array.isArray(openerUids) && openerUids.length > 0;
|
|
467
|
+
const hasStackPopup = stack.length >= 2;
|
|
468
|
+
const isPopup = hasStackPopup || hasOpener;
|
|
469
|
+
if (!isPopup) return undefined;
|
|
470
|
+
|
|
471
|
+
// 当没有 navigation 堆栈时,退回当前视图的 inputArgs 作为单节点弹窗上下文
|
|
472
|
+
if (!stack.length) {
|
|
473
|
+
const args = view?.inputArgs || {};
|
|
474
|
+
const hasAny =
|
|
475
|
+
args.collectionName || args.filterByTk != null || args.sourceId != null || args.associationName || args.viewUid;
|
|
476
|
+
if (!hasAny) return undefined;
|
|
477
|
+
return {
|
|
478
|
+
uid: args.viewUid,
|
|
479
|
+
resource: {
|
|
480
|
+
dataSourceKey: args.dataSourceKey || 'main',
|
|
481
|
+
collectionName: args.collectionName,
|
|
482
|
+
associationName: args.associationName,
|
|
483
|
+
filterByTk: args.filterByTk,
|
|
484
|
+
sourceId: args.sourceId,
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
362
489
|
const buildNode = async (idx: number): Promise<PopupNode | undefined> => {
|
|
363
490
|
if (idx < 0 || !stack[idx]?.viewUid) return undefined;
|
|
364
491
|
const viewUid = stack[idx].viewUid;
|
|
365
|
-
let model
|
|
366
|
-
if (!model
|
|
367
|
-
model = await ctx.engine?.loadModel({ uid: viewUid });
|
|
492
|
+
let model = ctx.engine?.getModel(viewUid, true) as PopupModelLike;
|
|
493
|
+
if (!model) {
|
|
494
|
+
model = (await ctx.engine?.loadModel({ uid: viewUid })) as PopupModelLike;
|
|
368
495
|
}
|
|
369
496
|
const p = model?.getStepParams?.('popupSettings', 'openView') || {};
|
|
370
497
|
const collectionName = p?.collectionName;
|
|
@@ -374,6 +501,7 @@ export async function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promi
|
|
|
374
501
|
resource: {
|
|
375
502
|
dataSourceKey,
|
|
376
503
|
collectionName,
|
|
504
|
+
associationName: p?.associationName,
|
|
377
505
|
filterByTk: stack[idx]?.filterByTk,
|
|
378
506
|
sourceId: stack[idx]?.sourceId,
|
|
379
507
|
},
|
|
@@ -390,13 +518,22 @@ export async function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promi
|
|
|
390
518
|
* 在视图上下文中注册 popup 变量(统一消除重复)
|
|
391
519
|
*/
|
|
392
520
|
export function registerPopupVariable(ctx: FlowContext, view: FlowView) {
|
|
521
|
+
// - 顶层 record / sourceRecord 及其子字段
|
|
522
|
+
// - 任意层级 parent.parent... 下的 record / sourceRecord 及其子字段
|
|
523
|
+
const POPUP_SERVER_PATH_RE =
|
|
524
|
+
/^(?:record|sourceRecord)(?:\.|$)|^parent(?:\.parent)*(?:\.(?:record|sourceRecord))(?:\.|$)/;
|
|
393
525
|
// 始终注册 popup 变量:
|
|
394
526
|
// - 若当前视图无可推断记录,仅在元信息中不呈现 record 字段;
|
|
395
527
|
// - 但仍可依据 navigation 推断并展示上级弹窗信息。
|
|
396
528
|
ctx.defineProperty('popup', {
|
|
397
529
|
get: async () => buildPopupRuntime(ctx, view),
|
|
398
|
-
meta: createPopupMeta(ctx),
|
|
399
|
-
resolveOnServer: (p: string) =>
|
|
400
|
-
|
|
530
|
+
meta: createPopupMeta(ctx, view),
|
|
531
|
+
resolveOnServer: (p: string) => {
|
|
532
|
+
try {
|
|
533
|
+
return !!p && POPUP_SERVER_PATH_RE.test(p);
|
|
534
|
+
} catch (_) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
},
|
|
401
538
|
});
|
|
402
539
|
}
|