@nocobase/flow-engine 2.1.0-beta.1 → 2.1.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -661
- package/README.md +79 -10
- package/lib/JSRunner.d.ts +10 -1
- package/lib/JSRunner.js +50 -5
- package/lib/ViewScopedFlowEngine.js +5 -1
- package/lib/components/dnd/gridDragPlanner.js +6 -2
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
- package/lib/components/subModel/AddSubModelButton.js +15 -0
- package/lib/executor/FlowExecutor.js +31 -8
- package/lib/flowContext.js +4 -1
- package/lib/flowEngine.d.ts +19 -0
- package/lib/flowEngine.js +29 -1
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +19 -12
- package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
- package/lib/scheduler/ModelOperationScheduler.js +3 -2
- package/lib/types.d.ts +1 -1
- package/lib/utils/index.d.ts +2 -2
- package/lib/utils/index.js +4 -0
- package/lib/utils/parsePathnameToViewParams.js +1 -1
- package/lib/utils/schema-utils.d.ts +7 -1
- package/lib/utils/schema-utils.js +19 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +20 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +20 -3
- package/lib/views/usePage.d.ts +2 -1
- package/lib/views/usePage.js +10 -3
- package/package.json +5 -5
- package/src/JSRunner.ts +68 -4
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +27 -1
- package/src/__tests__/runjsContext.test.ts +13 -0
- package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
- package/src/components/__tests__/gridDragPlanner.test.ts +88 -0
- package/src/components/dnd/gridDragPlanner.ts +8 -2
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
- package/src/components/subModel/AddSubModelButton.tsx +16 -0
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +50 -0
- package/src/executor/FlowExecutor.ts +34 -9
- package/src/executor/__tests__/flowExecutor.test.ts +57 -0
- package/src/flowContext.ts +5 -3
- package/src/flowEngine.ts +33 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +21 -12
- package/src/scheduler/ModelOperationScheduler.ts +14 -3
- package/src/types.ts +1 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/index.ts +2 -1
- package/src/utils/parsePathnameToViewParams.ts +2 -2
- package/src/utils/schema-utils.ts +30 -1
- package/src/views/FlowView.tsx +11 -1
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +25 -3
- package/src/views/useDrawer.tsx +25 -3
- package/src/views/usePage.tsx +12 -3
package/src/views/useDialog.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } f
|
|
|
19
19
|
import { FlowEngineProvider } from '../provider';
|
|
20
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
21
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
22
|
+
import { runViewBeforeClose } from './runViewBeforeClose';
|
|
22
23
|
|
|
23
24
|
let uuid = 0;
|
|
24
25
|
|
|
@@ -89,12 +90,18 @@ export function useDialog() {
|
|
|
89
90
|
ctx.addDelegate(flowContext.engine.context);
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
// 幂等保护:防止 FlowPage 路由清理时二次调用 destroy
|
|
94
|
+
let destroyed = false;
|
|
95
|
+
|
|
92
96
|
// 构造 currentDialog 实例
|
|
93
97
|
const currentDialog = {
|
|
94
98
|
type: 'dialog' as const,
|
|
95
99
|
inputArgs: config.inputArgs || {},
|
|
96
100
|
preventClose: !!config.preventClose,
|
|
101
|
+
beforeClose: undefined,
|
|
97
102
|
destroy: (result?: any) => {
|
|
103
|
+
if (destroyed) return;
|
|
104
|
+
destroyed = true;
|
|
98
105
|
config.onClose?.();
|
|
99
106
|
dialogRef.current?.destroy();
|
|
100
107
|
closeFunc?.();
|
|
@@ -107,18 +114,24 @@ export function useDialog() {
|
|
|
107
114
|
scopedEngine.unlinkFromStack();
|
|
108
115
|
},
|
|
109
116
|
update: (newConfig) => dialogRef.current?.update(newConfig),
|
|
110
|
-
close: (result?: any, force?: boolean) => {
|
|
117
|
+
close: async (result?: any, force?: boolean) => {
|
|
111
118
|
if (config.preventClose && !force) {
|
|
112
|
-
return;
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const shouldClose = await runViewBeforeClose(currentDialog, { result, force });
|
|
123
|
+
if (!shouldClose) {
|
|
124
|
+
return false;
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
116
128
|
// 交由路由系统来销毁当前视图
|
|
117
129
|
config.inputArgs.navigation.back();
|
|
118
|
-
return;
|
|
130
|
+
return true;
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
currentDialog.destroy(result);
|
|
134
|
+
return true;
|
|
122
135
|
},
|
|
123
136
|
Footer: FooterComponent,
|
|
124
137
|
Header: HeaderComponent,
|
|
@@ -140,6 +153,15 @@ export function useDialog() {
|
|
|
140
153
|
get: () => currentDialog,
|
|
141
154
|
resolveOnServer: createViewRecordResolveOnServer(ctx, () => getViewRecordFromParent(flowContext, ctx)),
|
|
142
155
|
});
|
|
156
|
+
// 注册视图销毁回调,供外部(如 afterSuccess)通过引擎栈遍历来关闭多层弹窗。
|
|
157
|
+
// 对路由触发的弹窗:先 navigation.back() 清理 URL(replace 方式),再 destroy() 立即移除元素;
|
|
158
|
+
// 对非路由弹窗:直接 destroy()。destroy() 已做幂等保护,FlowPage 后续清理不会重复执行。
|
|
159
|
+
scopedEngine.setDestroyView(() => {
|
|
160
|
+
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
161
|
+
config.inputArgs.navigation.back();
|
|
162
|
+
}
|
|
163
|
+
currentDialog.destroy();
|
|
164
|
+
});
|
|
143
165
|
// 顶层 popup 变量:弹窗记录/数据源/上级弹窗链(去重封装)
|
|
144
166
|
registerPopupVariable(ctx, currentDialog);
|
|
145
167
|
// 内部组件,在 Provider 内部计算 content
|
package/src/views/useDrawer.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } f
|
|
|
19
19
|
import { FlowEngineProvider } from '../provider';
|
|
20
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
21
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
22
|
+
import { runViewBeforeClose } from './runViewBeforeClose';
|
|
22
23
|
|
|
23
24
|
export function useDrawer() {
|
|
24
25
|
const holderRef = React.useRef(null);
|
|
@@ -118,12 +119,18 @@ export function useDrawer() {
|
|
|
118
119
|
ctx.addDelegate(flowContext.engine.context);
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
// 幂等保护:防止 FlowPage 路由清理时二次调用 destroy
|
|
123
|
+
let destroyed = false;
|
|
124
|
+
|
|
121
125
|
// 构造 currentDrawer 实例
|
|
122
126
|
const currentDrawer = {
|
|
123
127
|
type: 'drawer' as const,
|
|
124
128
|
inputArgs: config.inputArgs || {},
|
|
125
129
|
preventClose: !!config.preventClose,
|
|
130
|
+
beforeClose: undefined,
|
|
126
131
|
destroy: (result?: any) => {
|
|
132
|
+
if (destroyed) return;
|
|
133
|
+
destroyed = true;
|
|
127
134
|
config.onClose?.();
|
|
128
135
|
drawerRef.current?.destroy();
|
|
129
136
|
closeFunc?.();
|
|
@@ -136,18 +143,24 @@ export function useDrawer() {
|
|
|
136
143
|
scopedEngine.unlinkFromStack();
|
|
137
144
|
},
|
|
138
145
|
update: (newConfig) => drawerRef.current?.update(newConfig),
|
|
139
|
-
close: (result?: any, force?: boolean) => {
|
|
146
|
+
close: async (result?: any, force?: boolean) => {
|
|
140
147
|
if (config.preventClose && !force) {
|
|
141
|
-
return;
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const shouldClose = await runViewBeforeClose(currentDrawer, { result, force });
|
|
152
|
+
if (!shouldClose) {
|
|
153
|
+
return false;
|
|
142
154
|
}
|
|
143
155
|
|
|
144
156
|
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
145
157
|
// 交由路由系统来销毁当前视图
|
|
146
158
|
config.inputArgs.navigation.back();
|
|
147
|
-
return;
|
|
159
|
+
return true;
|
|
148
160
|
}
|
|
149
161
|
|
|
150
162
|
currentDrawer.destroy(result);
|
|
163
|
+
return true;
|
|
151
164
|
},
|
|
152
165
|
Footer: FooterComponent,
|
|
153
166
|
Header: HeaderComponent,
|
|
@@ -169,6 +182,15 @@ export function useDrawer() {
|
|
|
169
182
|
get: () => currentDrawer,
|
|
170
183
|
resolveOnServer: createViewRecordResolveOnServer(ctx, () => getViewRecordFromParent(flowContext, ctx)),
|
|
171
184
|
});
|
|
185
|
+
// 注册视图销毁回调,供外部(如 afterSuccess)通过引擎栈遍历来关闭多层弹窗。
|
|
186
|
+
// 对路由触发的弹窗:先 navigation.back() 清理 URL(replace 方式),再 destroy() 立即移除元素;
|
|
187
|
+
// 对非路由弹窗:直接 destroy()。destroy() 已做幂等保护,FlowPage 后续清理不会重复执行。
|
|
188
|
+
scopedEngine.setDestroyView(() => {
|
|
189
|
+
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
190
|
+
config.inputArgs.navigation.back();
|
|
191
|
+
}
|
|
192
|
+
currentDrawer.destroy();
|
|
193
|
+
});
|
|
172
194
|
// 顶层 popup 变量:弹窗记录/数据源/上级弹窗链(去重封装)
|
|
173
195
|
registerPopupVariable(ctx, currentDrawer);
|
|
174
196
|
|
package/src/views/usePage.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import { VIEW_ACTIVATED_EVENT, bumpViewActivatedVersion, resolveOpenerEngine } f
|
|
|
19
19
|
import { FlowEngineProvider } from '../provider';
|
|
20
20
|
import { createViewScopedEngine } from '../ViewScopedFlowEngine';
|
|
21
21
|
import { createViewRecordResolveOnServer, getViewRecordFromParent } from '../utils/variablesParams';
|
|
22
|
+
import { runViewBeforeClose } from './runViewBeforeClose';
|
|
22
23
|
|
|
23
24
|
let uuid = 0;
|
|
24
25
|
|
|
@@ -122,6 +123,7 @@ export function usePage() {
|
|
|
122
123
|
type: 'embed' as const,
|
|
123
124
|
inputArgs: viewInputArgs,
|
|
124
125
|
preventClose: !!config.preventClose,
|
|
126
|
+
beforeClose: undefined,
|
|
125
127
|
destroy: (result?: any) => {
|
|
126
128
|
config.onClose?.();
|
|
127
129
|
resolvePromise?.(result);
|
|
@@ -145,18 +147,24 @@ export function usePage() {
|
|
|
145
147
|
scopedEngine.unlinkFromStack();
|
|
146
148
|
},
|
|
147
149
|
update: (newConfig) => pageRef.current?.update(newConfig),
|
|
148
|
-
close: (result?: any, force?: boolean) => {
|
|
150
|
+
close: async (result?: any, force?: boolean) => {
|
|
149
151
|
if (preventClose && !force) {
|
|
150
|
-
return;
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const shouldClose = await runViewBeforeClose(currentPage, { result, force });
|
|
156
|
+
if (!shouldClose) {
|
|
157
|
+
return false;
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
if (config.triggerByRouter && config.inputArgs?.navigation?.back) {
|
|
154
161
|
// 交由路由系统来销毁当前视图
|
|
155
162
|
config.inputArgs.navigation.back();
|
|
156
|
-
return;
|
|
163
|
+
return true;
|
|
157
164
|
}
|
|
158
165
|
|
|
159
166
|
currentPage.destroy(result);
|
|
167
|
+
return true;
|
|
160
168
|
},
|
|
161
169
|
Header: HeaderComponent,
|
|
162
170
|
Footer: FooterComponent,
|
|
@@ -178,6 +186,7 @@ export function usePage() {
|
|
|
178
186
|
// 仅当访问关联字段或前端无本地记录数据时,才交给服务端解析
|
|
179
187
|
resolveOnServer: createViewRecordResolveOnServer(ctx, () => getViewRecordFromParent(flowContext, ctx)),
|
|
180
188
|
});
|
|
189
|
+
// embed 视图不注册 destroyView,afterSuccess 关闭弹窗时自然跳过
|
|
181
190
|
// 顶层 popup 变量:弹窗记录/数据源/上级弹窗链(去重封装)
|
|
182
191
|
registerPopupVariable(ctx, currentPage);
|
|
183
192
|
|