@nocobase/flow-engine 2.1.0-alpha.3 → 2.1.0-alpha.30
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/FieldModelRenderer.js +2 -2
- package/lib/components/FlowModelRenderer.d.ts +3 -1
- package/lib/components/FlowModelRenderer.js +12 -6
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
- package/lib/components/dnd/gridDragPlanner.js +601 -21
- package/lib/components/dnd/index.d.ts +19 -1
- package/lib/components/dnd/index.js +243 -23
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +68 -10
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
- package/lib/components/subModel/AddSubModelButton.js +27 -1
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +1 -1
- package/lib/components/subModel/utils.js +2 -2
- package/lib/data-source/index.d.ts +73 -0
- package/lib/data-source/index.js +211 -1
- package/lib/executor/FlowExecutor.js +31 -8
- package/lib/flowContext.d.ts +2 -0
- package/lib/flowContext.js +31 -1
- package/lib/flowEngine.d.ts +151 -1
- package/lib/flowEngine.js +389 -15
- package/lib/flowI18n.js +2 -1
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +34 -6
- package/lib/lazy-helper.d.ts +14 -0
- package/lib/lazy-helper.js +71 -0
- package/lib/locale/en-US.json +1 -0
- package/lib/locale/index.d.ts +2 -0
- package/lib/locale/zh-CN.json +1 -0
- package/lib/models/DisplayItemModel.d.ts +1 -1
- package/lib/models/EditableItemModel.d.ts +1 -1
- package/lib/models/FilterableItemModel.d.ts +1 -1
- package/lib/models/flowModel.d.ts +13 -10
- package/lib/models/flowModel.js +78 -18
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +20 -12
- package/lib/runjs-context/snippets/index.js +13 -2
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
- package/lib/scheduler/ModelOperationScheduler.js +3 -2
- package/lib/types.d.ts +47 -1
- package/lib/utils/createCollectionContextMeta.js +6 -2
- 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/runjsTemplateCompat.js +1 -1
- package/lib/utils/runjsValue.js +41 -11
- 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 +6 -5
- package/src/JSRunner.ts +68 -4
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +27 -1
- package/src/__tests__/flow-engine.test.ts +166 -0
- package/src/__tests__/flowContext.test.ts +65 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/objectVariable.test.ts +24 -0
- package/src/__tests__/provider.test.tsx +24 -2
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +16 -0
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
- package/src/components/FieldModelRenderer.tsx +2 -1
- package/src/components/FlowModelRenderer.tsx +18 -6
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
- package/src/components/__tests__/dnd.test.ts +44 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
- package/src/components/__tests__/gridDragPlanner.test.ts +512 -3
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +743 -19
- package/src/components/dnd/index.tsx +291 -27
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +88 -10
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +189 -3
- package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
- package/src/components/subModel/AddSubModelButton.tsx +32 -2
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +142 -32
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +1 -1
- package/src/data-source/__tests__/index.test.ts +34 -1
- package/src/data-source/index.ts +258 -2
- package/src/executor/FlowExecutor.ts +34 -9
- package/src/executor/__tests__/flowExecutor.test.ts +57 -0
- package/src/flowContext.ts +37 -3
- package/src/flowEngine.ts +445 -11
- package/src/flowI18n.ts +2 -1
- package/src/flowSettings.ts +40 -6
- package/src/lazy-helper.tsx +57 -0
- package/src/locale/en-US.json +1 -0
- package/src/locale/zh-CN.json +1 -0
- package/src/models/DisplayItemModel.tsx +1 -1
- package/src/models/EditableItemModel.tsx +1 -1
- package/src/models/FilterableItemModel.tsx +1 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
- package/src/models/__tests__/flowModel.test.ts +19 -3
- package/src/models/flowModel.tsx +119 -33
- package/src/provider.tsx +41 -25
- package/src/reactive/__tests__/observer.test.tsx +82 -0
- package/src/reactive/observer.tsx +87 -25
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +22 -12
- package/src/runjs-context/snippets/index.ts +12 -1
- package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
- package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
- package/src/scheduler/ModelOperationScheduler.ts +14 -3
- package/src/types.ts +60 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
- package/src/utils/__tests__/runjsValue.test.ts +11 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/createCollectionContextMeta.ts +6 -2
- package/src/utils/index.ts +2 -1
- package/src/utils/parsePathnameToViewParams.ts +2 -2
- package/src/utils/runjsTemplateCompat.ts +1 -1
- package/src/utils/runjsValue.ts +50 -11
- 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/models/flowModel.tsx
CHANGED
|
@@ -11,8 +11,6 @@ import { batch, define, observable, observe } from '@formily/reactive';
|
|
|
11
11
|
import _ from 'lodash';
|
|
12
12
|
import React from 'react';
|
|
13
13
|
import { uid } from 'uid/secure';
|
|
14
|
-
import { openRequiredParamsStepFormDialog as openRequiredParamsStepFormDialogFn } from '../components/settings/wrappers/contextual/StepRequiredSettingsDialog';
|
|
15
|
-
import { openStepSettingsDialog as openStepSettingsDialogFn } from '../components/settings/wrappers/contextual/StepSettingsDialog';
|
|
16
14
|
import { Emitter } from '../emitter';
|
|
17
15
|
import { InstanceFlowRegistry } from '../flow-registry/InstanceFlowRegistry';
|
|
18
16
|
import { FlowContext, FlowModelContext, FlowRuntimeContext } from '../flowContext';
|
|
@@ -36,7 +34,9 @@ import type {
|
|
|
36
34
|
import { IModelComponentProps, ReadonlyModelProps } from '../types';
|
|
37
35
|
import { isInheritedFrom, setupRuntimeContextSteps } from '../utils';
|
|
38
36
|
// import { FlowExitAllException } from '../utils/exceptions';
|
|
39
|
-
import { Typography } from 'antd
|
|
37
|
+
import { Typography } from 'antd';
|
|
38
|
+
import type { MenuProps } from 'antd';
|
|
39
|
+
import { observer } from '..';
|
|
40
40
|
import { ModelActionRegistry } from '../action-registry/ModelActionRegistry';
|
|
41
41
|
import { buildSubModelItem } from '../components/subModel/utils';
|
|
42
42
|
import { ModelEventRegistry } from '../event-registry/ModelEventRegistry';
|
|
@@ -46,8 +46,6 @@ import { FlowSettingsOpenOptions } from '../flowSettings';
|
|
|
46
46
|
import type { ScheduleOptions } from '../scheduler/ModelOperationScheduler';
|
|
47
47
|
import type { DispatchEventOptions, EventDefinition } from '../types';
|
|
48
48
|
import { ForkFlowModel } from './forkFlowModel';
|
|
49
|
-
import type { MenuProps } from 'antd';
|
|
50
|
-
import { observer } from '..';
|
|
51
49
|
|
|
52
50
|
// 使用 WeakMap 为每个类缓存一个 ModelActionRegistry 实例
|
|
53
51
|
const classActionRegistries = new WeakMap<typeof FlowModel, ModelActionRegistry>();
|
|
@@ -62,16 +60,22 @@ const modelMetas = new WeakMap<typeof FlowModel, FlowModelMeta>();
|
|
|
62
60
|
const modelGlobalRegistries = new WeakMap<typeof FlowModel, GlobalFlowRegistry>();
|
|
63
61
|
|
|
64
62
|
type BaseMenuItem = NonNullable<MenuProps['items']>[number];
|
|
65
|
-
type
|
|
63
|
+
type MenuBaseItem = Omit<Exclude<BaseMenuItem, null>, 'key' | 'children'>;
|
|
66
64
|
|
|
67
|
-
export type FlowModelExtraMenuItem =
|
|
65
|
+
export type FlowModelExtraMenuItem = MenuBaseItem & {
|
|
68
66
|
key: React.Key;
|
|
69
67
|
group?: string;
|
|
70
68
|
sort?: number;
|
|
69
|
+
label?: React.ReactNode;
|
|
70
|
+
disabled?: boolean;
|
|
71
71
|
onClick?: () => void;
|
|
72
|
+
children?: FlowModelExtraMenuItem[];
|
|
72
73
|
};
|
|
73
74
|
|
|
74
|
-
type FlowModelExtraMenuItemInput = Omit<FlowModelExtraMenuItem, 'key'> & {
|
|
75
|
+
type FlowModelExtraMenuItemInput = Omit<FlowModelExtraMenuItem, 'key' | 'children'> & {
|
|
76
|
+
key?: React.Key;
|
|
77
|
+
children?: FlowModelExtraMenuItemInput[];
|
|
78
|
+
};
|
|
75
79
|
|
|
76
80
|
type ExtraMenuItemEntry = {
|
|
77
81
|
group?: string;
|
|
@@ -88,6 +92,66 @@ type ExtraMenuItemEntry = {
|
|
|
88
92
|
|
|
89
93
|
const classMenuExtensions = new WeakMap<typeof FlowModel, Set<ExtraMenuItemEntry>>();
|
|
90
94
|
|
|
95
|
+
const sortExtraMenuItems = (items: FlowModelExtraMenuItem[]) => {
|
|
96
|
+
return [...items].sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0));
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const isFlowModelExtraMenuItem = (item: FlowModelExtraMenuItem | null): item is FlowModelExtraMenuItem => {
|
|
100
|
+
return item !== null;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const normalizeExtraMenuItem = (
|
|
104
|
+
item: FlowModelExtraMenuItemInput,
|
|
105
|
+
{
|
|
106
|
+
group,
|
|
107
|
+
sort,
|
|
108
|
+
prefix,
|
|
109
|
+
path,
|
|
110
|
+
}: {
|
|
111
|
+
group: string;
|
|
112
|
+
sort: number;
|
|
113
|
+
prefix: string;
|
|
114
|
+
path: string;
|
|
115
|
+
},
|
|
116
|
+
): FlowModelExtraMenuItem | null => {
|
|
117
|
+
if (!item) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const normalizedGroup = item.group || group;
|
|
122
|
+
const normalizedSort = typeof item.sort === 'number' ? item.sort : sort;
|
|
123
|
+
const normalizedChildren = sortExtraMenuItems(
|
|
124
|
+
(item.children || [])
|
|
125
|
+
.map((child, index) =>
|
|
126
|
+
normalizeExtraMenuItem(child, {
|
|
127
|
+
group: normalizedGroup,
|
|
128
|
+
sort: normalizedSort,
|
|
129
|
+
prefix,
|
|
130
|
+
path: `${path}-${index}`,
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
.filter(isFlowModelExtraMenuItem),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
...item,
|
|
138
|
+
key: item.key ?? `${prefix}-${normalizedGroup}-${path}`,
|
|
139
|
+
group: normalizedGroup,
|
|
140
|
+
sort: normalizedSort,
|
|
141
|
+
children: normalizedChildren.length ? normalizedChildren : undefined,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
async function loadOpenStepSettingsDialog() {
|
|
146
|
+
const mod = await import('../components/settings/wrappers/contextual/StepSettingsDialog');
|
|
147
|
+
return mod.openStepSettingsDialog;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function loadOpenRequiredParamsStepFormDialog() {
|
|
151
|
+
const mod = await import('../components/settings/wrappers/contextual/StepRequiredSettingsDialog');
|
|
152
|
+
return mod.openRequiredParamsStepFormDialog;
|
|
153
|
+
}
|
|
154
|
+
|
|
91
155
|
export enum ModelRenderMode {
|
|
92
156
|
ReactElement = 'reactElement',
|
|
93
157
|
RenderFunction = 'renderFunction',
|
|
@@ -208,11 +272,14 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
208
272
|
if (changed.type === 'set' && _.isEqual(changed.value, changed.oldValue)) {
|
|
209
273
|
return;
|
|
210
274
|
}
|
|
275
|
+
const hasLastAutoRun = !!this._lastAutoRunParams;
|
|
211
276
|
|
|
212
277
|
if (this.flowEngine) {
|
|
213
278
|
this.invalidateFlowCache('beforeRender');
|
|
214
279
|
}
|
|
215
|
-
|
|
280
|
+
if (hasLastAutoRun) {
|
|
281
|
+
this._rerunLastAutoRun();
|
|
282
|
+
}
|
|
216
283
|
this.forks.forEach((fork) => {
|
|
217
284
|
fork.rerender();
|
|
218
285
|
});
|
|
@@ -695,6 +762,8 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
695
762
|
} else {
|
|
696
763
|
this.props = { ...this.props, ...props };
|
|
697
764
|
}
|
|
765
|
+
|
|
766
|
+
this._options.props = { ...this.props };
|
|
698
767
|
}
|
|
699
768
|
|
|
700
769
|
getProps(): ReadonlyModelProps {
|
|
@@ -858,6 +927,11 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
858
927
|
}
|
|
859
928
|
}, 100);
|
|
860
929
|
|
|
930
|
+
private resetAutoRunState(): void {
|
|
931
|
+
this._rerunLastAutoRun?.cancel?.();
|
|
932
|
+
this._lastAutoRunParams = null;
|
|
933
|
+
}
|
|
934
|
+
|
|
861
935
|
/**
|
|
862
936
|
* 通用事件分发钩子:开始
|
|
863
937
|
* 子类可覆盖;beforeRender 事件可通过抛出 FlowExitException 提前终止。
|
|
@@ -951,7 +1025,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
951
1025
|
}
|
|
952
1026
|
|
|
953
1027
|
// 创建缓存的响应式包装器组件工厂(只创建一次)
|
|
954
|
-
const createReactiveWrapper = (modelInstance:
|
|
1028
|
+
const createReactiveWrapper = (modelInstance: FlowModel) => {
|
|
955
1029
|
const ReactiveWrapper = observer(() => {
|
|
956
1030
|
// 触发响应式更新的关键属性访问(读取 run/渲染目标的 props)
|
|
957
1031
|
const renderTarget = modelInstance;
|
|
@@ -977,6 +1051,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
977
1051
|
model: renderTarget,
|
|
978
1052
|
});
|
|
979
1053
|
return () => {
|
|
1054
|
+
renderTarget.resetAutoRunState();
|
|
980
1055
|
if (typeof renderTarget.onUnmount === 'function') {
|
|
981
1056
|
renderTarget.onUnmount();
|
|
982
1057
|
}
|
|
@@ -1177,17 +1252,17 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1177
1252
|
return model;
|
|
1178
1253
|
}
|
|
1179
1254
|
|
|
1180
|
-
filterSubModels<K extends keyof Structure['subModels']
|
|
1255
|
+
filterSubModels<K extends keyof NonNullable<Structure['subModels']>, R>(
|
|
1181
1256
|
subKey: K,
|
|
1182
|
-
callback: (model: ArrayElementType<Structure['subModels'][K]>, index: number) => boolean,
|
|
1183
|
-
): ArrayElementType<Structure['subModels'][K]>[] {
|
|
1257
|
+
callback: (model: ArrayElementType<NonNullable<Structure['subModels']>[K]>, index: number) => boolean,
|
|
1258
|
+
): ArrayElementType<NonNullable<Structure['subModels']>[K]>[] {
|
|
1184
1259
|
const model = (this.subModels as any)[subKey as string];
|
|
1185
1260
|
|
|
1186
1261
|
if (!model) {
|
|
1187
1262
|
return [];
|
|
1188
1263
|
}
|
|
1189
1264
|
|
|
1190
|
-
const results: ArrayElementType<Structure['subModels'][K]>[] = [];
|
|
1265
|
+
const results: ArrayElementType<NonNullable<Structure['subModels']>[K]>[] = [];
|
|
1191
1266
|
|
|
1192
1267
|
_.castArray(model)
|
|
1193
1268
|
.sort((a, b) => (a.sortIndex || 0) - (b.sortIndex || 0))
|
|
@@ -1201,9 +1276,9 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1201
1276
|
return results;
|
|
1202
1277
|
}
|
|
1203
1278
|
|
|
1204
|
-
mapSubModels<K extends keyof Structure['subModels']
|
|
1279
|
+
mapSubModels<K extends keyof NonNullable<Structure['subModels']>, R>(
|
|
1205
1280
|
subKey: K,
|
|
1206
|
-
callback: (model: ArrayElementType<Structure['subModels'][K]>, index: number) => R,
|
|
1281
|
+
callback: (model: ArrayElementType<NonNullable<Structure['subModels']>[K]>, index: number) => R,
|
|
1207
1282
|
): R[] {
|
|
1208
1283
|
const model = (this.subModels as any)[subKey as string];
|
|
1209
1284
|
|
|
@@ -1223,7 +1298,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1223
1298
|
return results;
|
|
1224
1299
|
}
|
|
1225
1300
|
|
|
1226
|
-
hasSubModel<K extends keyof Structure['subModels']
|
|
1301
|
+
hasSubModel<K extends keyof NonNullable<Structure['subModels']>>(subKey: K) {
|
|
1227
1302
|
const subModel = (this.subModels as any)[subKey as string];
|
|
1228
1303
|
if (!subModel) {
|
|
1229
1304
|
return false;
|
|
@@ -1231,10 +1306,10 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1231
1306
|
return _.castArray(subModel).length > 0;
|
|
1232
1307
|
}
|
|
1233
1308
|
|
|
1234
|
-
findSubModel<K extends keyof Structure['subModels']
|
|
1309
|
+
findSubModel<K extends keyof NonNullable<Structure['subModels']>, R>(
|
|
1235
1310
|
subKey: K,
|
|
1236
|
-
callback: (model: ArrayElementType<Structure['subModels'][K]>) => R,
|
|
1237
|
-
): ArrayElementType<Structure['subModels'][K]> | null {
|
|
1311
|
+
callback: (model: ArrayElementType<NonNullable<Structure['subModels']>[K]>) => R,
|
|
1312
|
+
): ArrayElementType<NonNullable<Structure['subModels']>[K]> | null {
|
|
1238
1313
|
const model = (this.subModels as any)[subKey as string];
|
|
1239
1314
|
|
|
1240
1315
|
if (!model) {
|
|
@@ -1244,7 +1319,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1244
1319
|
return (
|
|
1245
1320
|
(_.castArray(model).find((item) => {
|
|
1246
1321
|
return (callback as (model: any) => R)(item);
|
|
1247
|
-
}) as ArrayElementType<Structure['subModels'][K]> | undefined) || null
|
|
1322
|
+
}) as ArrayElementType<NonNullable<Structure['subModels']>[K]> | undefined) || null
|
|
1248
1323
|
);
|
|
1249
1324
|
}
|
|
1250
1325
|
|
|
@@ -1369,7 +1444,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1369
1444
|
* @param {string} stepKey 步骤的唯一标识符
|
|
1370
1445
|
* @returns {void}
|
|
1371
1446
|
*/
|
|
1372
|
-
openStepSettingsDialog(flowKey: string, stepKey: string) {
|
|
1447
|
+
async openStepSettingsDialog(flowKey: string, stepKey: string) {
|
|
1373
1448
|
// 创建流程运行时上下文
|
|
1374
1449
|
const flow = this.getFlow(flowKey);
|
|
1375
1450
|
const step = flow?.steps?.[stepKey];
|
|
@@ -1383,7 +1458,9 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1383
1458
|
setupRuntimeContextSteps(ctx, flow.steps, this, flowKey);
|
|
1384
1459
|
ctx.defineProperty('currentStep', { value: step });
|
|
1385
1460
|
|
|
1386
|
-
|
|
1461
|
+
const openStepSettingsDialog = await loadOpenStepSettingsDialog();
|
|
1462
|
+
|
|
1463
|
+
return openStepSettingsDialog({
|
|
1387
1464
|
model: this,
|
|
1388
1465
|
flowKey,
|
|
1389
1466
|
stepKey,
|
|
@@ -1399,7 +1476,9 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1399
1476
|
* @returns {Promise<any>} 返回表单提交的值
|
|
1400
1477
|
*/
|
|
1401
1478
|
async configureRequiredSteps(dialogWidth?: number | string, dialogTitle?: string) {
|
|
1402
|
-
|
|
1479
|
+
const openRequiredParamsStepFormDialog = await loadOpenRequiredParamsStepFormDialog();
|
|
1480
|
+
|
|
1481
|
+
return openRequiredParamsStepFormDialog({
|
|
1403
1482
|
model: this,
|
|
1404
1483
|
dialogWidth,
|
|
1405
1484
|
dialogTitle,
|
|
@@ -1432,6 +1511,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1432
1511
|
const data = {
|
|
1433
1512
|
uid: this.uid,
|
|
1434
1513
|
..._.omit(this._options, ['flowEngine']),
|
|
1514
|
+
props: { ...this.props },
|
|
1435
1515
|
stepParams: this.stepParams,
|
|
1436
1516
|
sortIndex: this.sortIndex,
|
|
1437
1517
|
flowRegistry: {},
|
|
@@ -1586,6 +1666,7 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1586
1666
|
seen.add(Cls);
|
|
1587
1667
|
const reg = classMenuExtensions.get(Cls);
|
|
1588
1668
|
if (reg) {
|
|
1669
|
+
let entryIndex = 0;
|
|
1589
1670
|
for (const entry of reg) {
|
|
1590
1671
|
if (entry.matcher && !entry.matcher(model)) continue;
|
|
1591
1672
|
const items =
|
|
@@ -1593,16 +1674,21 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
1593
1674
|
const group = entry.group || 'common-actions';
|
|
1594
1675
|
const sort = entry.sort ?? 0;
|
|
1595
1676
|
const prefix = entry.keyPrefix || Cls.name || 'extra';
|
|
1596
|
-
(
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1677
|
+
sortExtraMenuItems(
|
|
1678
|
+
(items || [])
|
|
1679
|
+
.map((it, idx: number) =>
|
|
1680
|
+
normalizeExtraMenuItem(it, {
|
|
1681
|
+
group,
|
|
1682
|
+
sort,
|
|
1683
|
+
prefix,
|
|
1684
|
+
path: `${entryIndex}-${idx}`,
|
|
1685
|
+
}),
|
|
1686
|
+
)
|
|
1687
|
+
.filter(isFlowModelExtraMenuItem),
|
|
1688
|
+
).forEach((it) => {
|
|
1689
|
+
collected.push(it);
|
|
1605
1690
|
});
|
|
1691
|
+
entryIndex += 1;
|
|
1606
1692
|
}
|
|
1607
1693
|
}
|
|
1608
1694
|
const ParentClass = Object.getPrototypeOf(Cls) as typeof FlowModel;
|
package/src/provider.tsx
CHANGED
|
@@ -45,34 +45,50 @@ export const FlowEngineGlobalsContextProvider: React.FC<{ children: React.ReactN
|
|
|
45
45
|
const engine = useFlowEngine();
|
|
46
46
|
const config = useContext(ConfigProvider.ConfigContext);
|
|
47
47
|
const { token } = theme.useToken();
|
|
48
|
+
const isDarkTheme = React.useMemo(() => {
|
|
49
|
+
const algorithm = config?.theme?.algorithm;
|
|
50
|
+
if (Array.isArray(algorithm)) {
|
|
51
|
+
return algorithm.includes(theme.darkAlgorithm);
|
|
52
|
+
}
|
|
53
|
+
return algorithm === theme.darkAlgorithm;
|
|
54
|
+
}, [config]);
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (value) {
|
|
64
|
-
engine.context.defineProperty(key, { value });
|
|
65
|
-
}
|
|
56
|
+
// 这些全局能力需要在 children 首次渲染前就可读,不能等到 effect 后再挂到上下文。
|
|
57
|
+
engine.context.defineProperty('viewer', {
|
|
58
|
+
cache: false,
|
|
59
|
+
get: (ctx) => new FlowViewer(ctx, { drawer, embed, popover, dialog }),
|
|
60
|
+
});
|
|
61
|
+
for (const item of Object.entries({
|
|
62
|
+
antdConfig: config,
|
|
63
|
+
modal,
|
|
64
|
+
message,
|
|
65
|
+
notification,
|
|
66
|
+
})) {
|
|
67
|
+
const [key, value] = item;
|
|
68
|
+
if (value) {
|
|
69
|
+
engine.context.defineProperty(key, { value });
|
|
66
70
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
}
|
|
72
|
+
// 将 themeToken 定义为 observable, 使组件能够响应主题的变更。
|
|
73
|
+
engine.context.defineProperty('themeToken', {
|
|
74
|
+
get: () => token,
|
|
75
|
+
observable: true,
|
|
76
|
+
cache: true,
|
|
77
|
+
});
|
|
78
|
+
// 统一把暗色模式暴露到 Flow 上下文,避免 flow 侧继续依赖 global-theme。
|
|
79
|
+
engine.context.defineProperty('isDarkTheme', {
|
|
80
|
+
get: () => isDarkTheme,
|
|
81
|
+
observable: true,
|
|
82
|
+
cache: true,
|
|
83
|
+
info: {
|
|
84
|
+
description: 'Whether current theme algorithm is dark mode.',
|
|
85
|
+
detail: 'boolean',
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
74
90
|
engine.reactView.refresh();
|
|
75
|
-
}, [engine, drawer, modal, message, notification, config, popover, token, dialog, embed]);
|
|
91
|
+
}, [engine, drawer, modal, message, notification, config, popover, token, dialog, embed, isDarkTheme]);
|
|
76
92
|
|
|
77
93
|
return (
|
|
78
94
|
<ConfigProvider {...config} locale={engine.context.locales?.antd} popupMatchSelectWidth={false}>
|
|
@@ -208,4 +208,86 @@ describe('observer', () => {
|
|
|
208
208
|
expect(screen.getByText('Count: 0')).toBeInTheDocument();
|
|
209
209
|
expect(screen.queryByText('Count: 1')).not.toBeInTheDocument();
|
|
210
210
|
});
|
|
211
|
+
|
|
212
|
+
it('should flush pending update without TDZ error when context becomes active before timer callback runs', async () => {
|
|
213
|
+
vi.useFakeTimers();
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const model = observable({ count: 0 });
|
|
217
|
+
const pageActive = observable.ref(false);
|
|
218
|
+
const tabActive = observable.ref(true);
|
|
219
|
+
|
|
220
|
+
const context = {
|
|
221
|
+
pageActive,
|
|
222
|
+
tabActive,
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
(useFlowContext as any).mockReturnValue(context);
|
|
226
|
+
|
|
227
|
+
const Component = observer(() => <div>Count: {model.count}</div>);
|
|
228
|
+
|
|
229
|
+
render(<Component />);
|
|
230
|
+
|
|
231
|
+
expect(screen.getByText('Count: 0')).toBeInTheDocument();
|
|
232
|
+
|
|
233
|
+
act(() => {
|
|
234
|
+
model.count++;
|
|
235
|
+
pageActive.value = true;
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await act(async () => {
|
|
239
|
+
await vi.runAllTimersAsync();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(screen.getByText('Count: 1')).toBeInTheDocument();
|
|
243
|
+
} finally {
|
|
244
|
+
vi.useRealTimers();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should cleanup pending timer and listener on unmount', async () => {
|
|
249
|
+
vi.useFakeTimers();
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const model = observable({ count: 0 });
|
|
253
|
+
const pageActive = observable.ref(false);
|
|
254
|
+
const tabActive = observable.ref(true);
|
|
255
|
+
const renderSpy = vi.fn();
|
|
256
|
+
|
|
257
|
+
const context = {
|
|
258
|
+
pageActive,
|
|
259
|
+
tabActive,
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
(useFlowContext as any).mockReturnValue(context);
|
|
263
|
+
|
|
264
|
+
const Component = observer(() => {
|
|
265
|
+
renderSpy(model.count);
|
|
266
|
+
return <div>Count: {model.count}</div>;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const { unmount } = render(<Component />);
|
|
270
|
+
|
|
271
|
+
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
272
|
+
expect(screen.getByText('Count: 0')).toBeInTheDocument();
|
|
273
|
+
|
|
274
|
+
act(() => {
|
|
275
|
+
model.count++;
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
unmount();
|
|
279
|
+
|
|
280
|
+
act(() => {
|
|
281
|
+
pageActive.value = true;
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
await act(async () => {
|
|
285
|
+
await vi.runAllTimersAsync();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
289
|
+
} finally {
|
|
290
|
+
vi.useRealTimers();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
211
293
|
});
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { observer as originalObserver, IObserverOptions, ReactFC } from '@formily/reactive-react';
|
|
11
11
|
import React, { useMemo, useEffect, useRef } from 'react';
|
|
12
12
|
import { useFlowContext } from '../FlowContextProvider';
|
|
13
|
-
import {
|
|
13
|
+
import { reaction } from '@formily/reactive';
|
|
14
14
|
import { FlowEngineContext } from '..';
|
|
15
15
|
|
|
16
16
|
type ObserverComponentProps<P, Options extends IObserverOptions> = Options extends {
|
|
@@ -30,12 +30,67 @@ export const observer = <P, Options extends IObserverOptions = IObserverOptions>
|
|
|
30
30
|
const ctxRef = useRef(ctx);
|
|
31
31
|
ctxRef.current = ctx;
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// 保存延迟更新的监听器,避免重复创建监听。
|
|
34
34
|
const pendingDisposerRef = useRef<(() => void) | null>(null);
|
|
35
|
+
// 保存延迟创建监听器的定时器,避免组件卸载后仍继续调度。
|
|
36
|
+
const pendingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
/**
|
|
39
|
+
* 清理挂起的可见性监听器。
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* clearPendingDisposer();
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
const clearPendingDisposer = () => {
|
|
47
|
+
if (pendingDisposerRef.current) {
|
|
48
|
+
pendingDisposerRef.current();
|
|
49
|
+
pendingDisposerRef.current = null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 清理挂起的定时器。
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* clearPendingTimer();
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
const clearPendingTimer = () => {
|
|
62
|
+
if (pendingTimerRef.current) {
|
|
63
|
+
clearTimeout(pendingTimerRef.current);
|
|
64
|
+
pendingTimerRef.current = null;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 判断当前页面与标签页是否允许立即更新。
|
|
70
|
+
*
|
|
71
|
+
* @returns 当前上下文是否处于可更新状态。
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* if (isContextActive()) {
|
|
75
|
+
* updater();
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
const isContextActive = () => {
|
|
80
|
+
const pageActive = getPageActive(ctxRef.current);
|
|
81
|
+
const tabActive = ctxRef.current?.tabActive?.value;
|
|
82
|
+
|
|
83
|
+
return pageActive !== false && tabActive !== false;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// 组件卸载时统一清理所有挂起任务,避免异步回调在卸载后继续运行。
|
|
37
87
|
useEffect(() => {
|
|
38
88
|
return () => {
|
|
89
|
+
if (pendingTimerRef.current) {
|
|
90
|
+
clearTimeout(pendingTimerRef.current);
|
|
91
|
+
pendingTimerRef.current = null;
|
|
92
|
+
}
|
|
93
|
+
|
|
39
94
|
if (pendingDisposerRef.current) {
|
|
40
95
|
pendingDisposerRef.current();
|
|
41
96
|
pendingDisposerRef.current = null;
|
|
@@ -47,38 +102,45 @@ export const observer = <P, Options extends IObserverOptions = IObserverOptions>
|
|
|
47
102
|
() =>
|
|
48
103
|
originalObserver(Component, {
|
|
49
104
|
scheduler(updater) {
|
|
50
|
-
|
|
51
|
-
|
|
105
|
+
if (!isContextActive()) {
|
|
106
|
+
if (pendingTimerRef.current || pendingDisposerRef.current) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 通过异步任务打断同步调度,避免连续触发时形成递归更新。
|
|
111
|
+
pendingTimerRef.current = setTimeout(() => {
|
|
112
|
+
pendingTimerRef.current = null;
|
|
52
113
|
|
|
53
|
-
if (pageActive === false || tabActive === false) {
|
|
54
|
-
// Avoid stack overflow
|
|
55
|
-
setTimeout(() => {
|
|
56
|
-
// If there is already a pending updater, do nothing
|
|
57
114
|
if (pendingDisposerRef.current) {
|
|
58
115
|
return;
|
|
59
116
|
}
|
|
60
117
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
118
|
+
if (isContextActive()) {
|
|
119
|
+
updater();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 只监听组合后的“是否可更新”状态,条件恢复后执行一次并立即销毁。
|
|
124
|
+
pendingDisposerRef.current = reaction(
|
|
125
|
+
() => isContextActive(),
|
|
126
|
+
(active) => {
|
|
127
|
+
if (!active) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
clearPendingDisposer();
|
|
67
132
|
updater();
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'FlowObserverPendingUpdate',
|
|
136
|
+
},
|
|
137
|
+
);
|
|
73
138
|
});
|
|
74
139
|
return;
|
|
75
140
|
}
|
|
76
141
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
pendingDisposerRef.current();
|
|
80
|
-
pendingDisposerRef.current = null;
|
|
81
|
-
}
|
|
142
|
+
clearPendingTimer();
|
|
143
|
+
clearPendingDisposer();
|
|
82
144
|
|
|
83
145
|
updater();
|
|
84
146
|
},
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// 为避免在模块初始化阶段引入 FlowContext(从而触发循环依赖),不要在顶层导入各类 RunJSContext。
|
|
11
11
|
// 在需要默认映射时(首次 resolve)再使用 createRequire 同步加载对应模块。
|
|
12
12
|
|
|
13
|
-
export type RunJSVersion = 'v1' | (string & {});
|
|
13
|
+
export type RunJSVersion = 'v1' | 'v2' | (string & {});
|
|
14
14
|
export type RunJSContextCtor = new (delegate: any) => any;
|
|
15
15
|
export type RunJSContextMeta = {
|
|
16
16
|
scenes?: string[];
|
|
@@ -41,17 +41,27 @@ export async function setupRunJSContexts() {
|
|
|
41
41
|
import('./contexts/JSCollectionActionRunJSContext'),
|
|
42
42
|
]);
|
|
43
43
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
const registerBuiltins = (version: 'v1' | 'v2') => {
|
|
45
|
+
RunJSContextRegistry.register(version, '*', FlowRunJSContext);
|
|
46
|
+
RunJSContextRegistry.register(version, 'JSBlockModel', JSBlockRunJSContext, { scenes: ['block'] });
|
|
47
|
+
RunJSContextRegistry.register(version, 'JSFieldModel', JSFieldRunJSContext, { scenes: ['detail'] });
|
|
48
|
+
RunJSContextRegistry.register(version, 'JSEditableFieldModel', JSEditableFieldRunJSContext, { scenes: ['form'] });
|
|
49
|
+
RunJSContextRegistry.register(version, 'JSItemModel', JSItemRunJSContext, { scenes: ['form'] });
|
|
50
|
+
RunJSContextRegistry.register(version, 'JSItemActionModel', JSItemRunJSContext, { scenes: ['table'] });
|
|
51
|
+
RunJSContextRegistry.register(version, 'JSColumnModel', JSColumnRunJSContext, { scenes: ['table'] });
|
|
52
|
+
RunJSContextRegistry.register(version, 'FormJSFieldItemModel', FormJSFieldItemRunJSContext, { scenes: ['form'] });
|
|
53
|
+
RunJSContextRegistry.register(version, 'JSRecordActionModel', JSRecordActionRunJSContext, { scenes: ['table'] });
|
|
54
|
+
RunJSContextRegistry.register(version, 'JSCollectionActionModel', JSCollectionActionRunJSContext, {
|
|
55
|
+
scenes: ['table'],
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const versions: Array<'v1' | 'v2'> = ['v1', 'v2'];
|
|
60
|
+
for (const version of versions) {
|
|
61
|
+
registerBuiltins(version);
|
|
62
|
+
await applyRunJSContextContributions(version);
|
|
63
|
+
markRunJSContextsSetupDone(version);
|
|
64
|
+
}
|
|
65
|
+
|
|
55
66
|
done = true;
|
|
56
|
-
markRunJSContextsSetupDone(v1);
|
|
57
67
|
}
|