@nocobase/flow-engine 2.0.0-alpha.53 → 2.0.0-alpha.55
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/components/settings/wrappers/component/SelectWithTitle.d.ts +18 -0
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +135 -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 +73 -14
- package/lib/flowEngine.js +5 -1
- package/lib/flowSettings.d.ts +2 -2
- package/lib/flowSettings.js +7 -2
- package/lib/resources/sqlResource.js +2 -1
- package/lib/types.d.ts +4 -1
- package/package.json +4 -4
- package/src/__tests__/flowEngine.destroyModel.test.ts +74 -0
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +108 -0
- package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +82 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +91 -26
- package/src/components/settings/wrappers/contextual/StepSettings.tsx +1 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +11 -15
- package/src/flowEngine.ts +7 -1
- package/src/flowSettings.ts +13 -5
- package/src/resources/sqlResource.ts +2 -1
- package/src/types.ts +3 -1
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
import React from 'react';
|
|
10
|
+
export interface SelectWithTitleProps {
|
|
11
|
+
title?: any;
|
|
12
|
+
getDefaultValue?: any;
|
|
13
|
+
options?: any;
|
|
14
|
+
fieldNames?: any;
|
|
15
|
+
itemKey?: string;
|
|
16
|
+
onChange?: (...args: any[]) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare function SelectWithTitle({ title, getDefaultValue, onChange, options, fieldNames, itemKey, ...others }: SelectWithTitleProps): React.JSX.Element;
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
20
|
+
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from))
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
25
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
35
|
+
mod
|
|
36
|
+
));
|
|
37
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
38
|
+
var SelectWithTitle_exports = {};
|
|
39
|
+
__export(SelectWithTitle_exports, {
|
|
40
|
+
SelectWithTitle: () => SelectWithTitle
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(SelectWithTitle_exports);
|
|
43
|
+
var import_antd = require("antd");
|
|
44
|
+
var import_react = __toESM(require("react"));
|
|
45
|
+
var import_provider = require("../../../../provider");
|
|
46
|
+
function SelectWithTitle({
|
|
47
|
+
title,
|
|
48
|
+
getDefaultValue,
|
|
49
|
+
onChange,
|
|
50
|
+
options,
|
|
51
|
+
fieldNames,
|
|
52
|
+
itemKey,
|
|
53
|
+
...others
|
|
54
|
+
}) {
|
|
55
|
+
const [open, setOpen] = (0, import_react.useState)(false);
|
|
56
|
+
const [value, setValue] = (0, import_react.useState)("");
|
|
57
|
+
const ctx = (0, import_provider.useFlowEngineContext)();
|
|
58
|
+
(0, import_react.useEffect)(() => {
|
|
59
|
+
let cancelled = false;
|
|
60
|
+
const run = /* @__PURE__ */ __name(async () => {
|
|
61
|
+
if (!getDefaultValue) return;
|
|
62
|
+
try {
|
|
63
|
+
const val = await getDefaultValue();
|
|
64
|
+
if (cancelled || !val) return;
|
|
65
|
+
const entries = Object.entries(val);
|
|
66
|
+
if (!entries.length) return;
|
|
67
|
+
const [key, result] = entries[0];
|
|
68
|
+
setValue(result);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error(e);
|
|
71
|
+
}
|
|
72
|
+
}, "run");
|
|
73
|
+
run();
|
|
74
|
+
return () => {
|
|
75
|
+
cancelled = true;
|
|
76
|
+
};
|
|
77
|
+
}, [getDefaultValue]);
|
|
78
|
+
const timerRef = (0, import_react.useRef)(null);
|
|
79
|
+
const handleChange = /* @__PURE__ */ __name((val) => {
|
|
80
|
+
setValue(val);
|
|
81
|
+
onChange == null ? void 0 : onChange({ [itemKey]: val });
|
|
82
|
+
}, "handleChange");
|
|
83
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
84
|
+
"div",
|
|
85
|
+
{
|
|
86
|
+
style: { alignItems: "center", display: "flex", justifyContent: "space-between" },
|
|
87
|
+
onClick: (e) => {
|
|
88
|
+
e.stopPropagation();
|
|
89
|
+
setOpen((v) => !v);
|
|
90
|
+
},
|
|
91
|
+
onMouseLeave: () => {
|
|
92
|
+
timerRef.current = setTimeout(() => {
|
|
93
|
+
setOpen(false);
|
|
94
|
+
}, 200);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
/* @__PURE__ */ import_react.default.createElement(
|
|
98
|
+
"span",
|
|
99
|
+
{
|
|
100
|
+
style: {
|
|
101
|
+
whiteSpace: "nowrap",
|
|
102
|
+
// 不换行
|
|
103
|
+
flexShrink: 0
|
|
104
|
+
// 不被挤压
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
title
|
|
108
|
+
),
|
|
109
|
+
/* @__PURE__ */ import_react.default.createElement(
|
|
110
|
+
import_antd.Select,
|
|
111
|
+
{
|
|
112
|
+
...others,
|
|
113
|
+
open,
|
|
114
|
+
popupMatchSelectWidth: false,
|
|
115
|
+
bordered: false,
|
|
116
|
+
value,
|
|
117
|
+
onChange: handleChange,
|
|
118
|
+
popupClassName: `select-popup-${title.replaceAll(" ", "-")}`,
|
|
119
|
+
fieldNames,
|
|
120
|
+
options,
|
|
121
|
+
labelRender: (props) => ctx.t(props.label),
|
|
122
|
+
optionRender: (o) => ctx.t(o.label),
|
|
123
|
+
style: { textAlign: "right", minWidth: 100 },
|
|
124
|
+
onMouseEnter: () => {
|
|
125
|
+
clearTimeout(timerRef.current);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
__name(SelectWithTitle, "SelectWithTitle");
|
|
132
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
133
|
+
0 && (module.exports = {
|
|
134
|
+
SelectWithTitle
|
|
135
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
import { FC } from 'react';
|
|
10
|
+
export declare const SwitchWithTitle: FC;
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
20
|
+
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from))
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
25
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
35
|
+
mod
|
|
36
|
+
));
|
|
37
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
38
|
+
var SwitchWithTitle_exports = {};
|
|
39
|
+
__export(SwitchWithTitle_exports, {
|
|
40
|
+
SwitchWithTitle: () => SwitchWithTitle
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(SwitchWithTitle_exports);
|
|
43
|
+
var import_react = require("@formily/react");
|
|
44
|
+
var import_antd = require("antd");
|
|
45
|
+
var import_react2 = __toESM(require("react"));
|
|
46
|
+
var import_provider = require("../../../../provider");
|
|
47
|
+
const ml32 = { marginLeft: 32 };
|
|
48
|
+
const SwitchWithTitle = (0, import_react.observer)(
|
|
49
|
+
({ title, onChange, getDefaultValue, disabled, itemKey, ...others }) => {
|
|
50
|
+
const [checked, setChecked] = (0, import_react2.useState)(false);
|
|
51
|
+
const ctx = (0, import_provider.useFlowEngineContext)();
|
|
52
|
+
(0, import_react2.useEffect)(() => {
|
|
53
|
+
let cancelled = false;
|
|
54
|
+
const run = /* @__PURE__ */ __name(async () => {
|
|
55
|
+
if (!getDefaultValue) return;
|
|
56
|
+
try {
|
|
57
|
+
const val = await getDefaultValue();
|
|
58
|
+
if (cancelled || !val) return;
|
|
59
|
+
const entries = Object.entries(val);
|
|
60
|
+
if (!entries.length) return;
|
|
61
|
+
const [key, value] = entries[0];
|
|
62
|
+
setChecked(!!value);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error(e);
|
|
65
|
+
}
|
|
66
|
+
}, "run");
|
|
67
|
+
run();
|
|
68
|
+
return () => {
|
|
69
|
+
cancelled = true;
|
|
70
|
+
};
|
|
71
|
+
}, [getDefaultValue]);
|
|
72
|
+
const handleChange = /* @__PURE__ */ __name((val) => {
|
|
73
|
+
setChecked(val);
|
|
74
|
+
onChange == null ? void 0 : onChange({ [itemKey]: val });
|
|
75
|
+
}, "handleChange");
|
|
76
|
+
const handleWrapperClick = /* @__PURE__ */ __name(() => {
|
|
77
|
+
if (disabled) return;
|
|
78
|
+
handleChange(!checked);
|
|
79
|
+
}, "handleWrapperClick");
|
|
80
|
+
return /* @__PURE__ */ import_react2.default.createElement(
|
|
81
|
+
"div",
|
|
82
|
+
{
|
|
83
|
+
style: {
|
|
84
|
+
alignItems: "center",
|
|
85
|
+
display: "flex",
|
|
86
|
+
justifyContent: "space-between",
|
|
87
|
+
cursor: disabled ? "not-allowed" : "pointer"
|
|
88
|
+
},
|
|
89
|
+
onClick: handleWrapperClick
|
|
90
|
+
},
|
|
91
|
+
title,
|
|
92
|
+
/* @__PURE__ */ import_react2.default.createElement(
|
|
93
|
+
import_antd.Switch,
|
|
94
|
+
{
|
|
95
|
+
size: "small",
|
|
96
|
+
...others,
|
|
97
|
+
checkedChildren: others.checkedChildren ? ctx.t(others.checkedChildren) : void 0,
|
|
98
|
+
unCheckedChildren: others.unCheckedChildren ? ctx.t(others.unCheckedChildren) : void 0,
|
|
99
|
+
checked,
|
|
100
|
+
style: ml32,
|
|
101
|
+
disabled
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
108
|
+
0 && (module.exports = {
|
|
109
|
+
SwitchWithTitle
|
|
110
|
+
});
|
|
@@ -46,6 +46,8 @@ var import_react = __toESM(require("react"));
|
|
|
46
46
|
var import_models = require("../../../../models");
|
|
47
47
|
var import_utils = require("../../../../utils");
|
|
48
48
|
var import_hooks = require("../../../../hooks");
|
|
49
|
+
var import_SwitchWithTitle = require("../component/SwitchWithTitle");
|
|
50
|
+
var import_SelectWithTitle = require("../component/SelectWithTitle");
|
|
49
51
|
const findSubModelByKey = /* @__PURE__ */ __name((model, subModelKey) => {
|
|
50
52
|
var _a;
|
|
51
53
|
if (!model || !subModelKey || typeof subModelKey !== "string") {
|
|
@@ -82,6 +84,18 @@ const findSubModelByKey = /* @__PURE__ */ __name((model, subModelKey) => {
|
|
|
82
84
|
return subModel instanceof import_models.FlowModel ? subModel : null;
|
|
83
85
|
}
|
|
84
86
|
}, "findSubModelByKey");
|
|
87
|
+
const componentMap = {
|
|
88
|
+
switch: import_SwitchWithTitle.SwitchWithTitle,
|
|
89
|
+
select: import_SelectWithTitle.SelectWithTitle
|
|
90
|
+
};
|
|
91
|
+
const MenuLabelItem = /* @__PURE__ */ __name(({ title, uiMode, itemProps }) => {
|
|
92
|
+
const type = (uiMode == null ? void 0 : uiMode.type) || uiMode;
|
|
93
|
+
const Component = type ? componentMap[type] : null;
|
|
94
|
+
if (!Component) {
|
|
95
|
+
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, title);
|
|
96
|
+
}
|
|
97
|
+
return /* @__PURE__ */ import_react.default.createElement(Component, { title, ...itemProps });
|
|
98
|
+
}, "MenuLabelItem");
|
|
85
99
|
const DefaultSettingsIcon = /* @__PURE__ */ __name(({
|
|
86
100
|
model,
|
|
87
101
|
showDeleteButton = true,
|
|
@@ -238,28 +252,33 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
|
|
|
238
252
|
Object.entries(flow.steps).map(async ([stepKey, stepDefinition]) => {
|
|
239
253
|
var _a;
|
|
240
254
|
const actionStep = stepDefinition;
|
|
255
|
+
let step = actionStep;
|
|
241
256
|
if (await (0, import_utils.shouldHideStepInSettings)(targetModel, flow, actionStep)) {
|
|
242
257
|
return null;
|
|
243
258
|
}
|
|
259
|
+
let uiMode = await (0, import_utils.resolveUiMode)(actionStep.uiMode, targetModel.context);
|
|
244
260
|
const hasStepUiSchema = actionStep.uiSchema != null;
|
|
245
261
|
let hasActionUiSchema = false;
|
|
246
262
|
let stepTitle = actionStep.title;
|
|
247
263
|
if (actionStep.use) {
|
|
248
264
|
try {
|
|
249
265
|
const action = (_a = targetModel.getAction) == null ? void 0 : _a.call(targetModel, actionStep.use);
|
|
266
|
+
step = { ...action || {}, ...actionStep };
|
|
267
|
+
uiMode = await (0, import_utils.resolveUiMode)((action == null ? void 0 : action.uiMode) || uiMode, targetModel.context);
|
|
250
268
|
hasActionUiSchema = action && action.uiSchema != null;
|
|
251
269
|
stepTitle = stepTitle || (action == null ? void 0 : action.title);
|
|
252
270
|
} catch (error) {
|
|
253
271
|
console.warn(t("Failed to get action {{action}}", { action: actionStep.use }), ":", error);
|
|
254
272
|
}
|
|
255
273
|
}
|
|
256
|
-
|
|
274
|
+
const selectOrSwitchMode = ["select", "switch"].includes((uiMode == null ? void 0 : uiMode.type) || uiMode);
|
|
275
|
+
if (!selectOrSwitchMode && !hasStepUiSchema && !hasActionUiSchema) {
|
|
257
276
|
return null;
|
|
258
277
|
}
|
|
259
278
|
let mergedUiSchema = {};
|
|
260
279
|
try {
|
|
261
280
|
const resolvedSchema = await (0, import_utils.resolveStepUiSchema)(targetModel, flow, actionStep);
|
|
262
|
-
if (!resolvedSchema) {
|
|
281
|
+
if (!resolvedSchema && !selectOrSwitchMode) {
|
|
263
282
|
return null;
|
|
264
283
|
}
|
|
265
284
|
mergedUiSchema = resolvedSchema;
|
|
@@ -269,11 +288,12 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
|
|
|
269
288
|
}
|
|
270
289
|
return {
|
|
271
290
|
stepKey,
|
|
272
|
-
step
|
|
291
|
+
step,
|
|
273
292
|
uiSchema: mergedUiSchema,
|
|
274
293
|
title: t(stepTitle) || stepKey,
|
|
275
|
-
modelKey
|
|
294
|
+
modelKey,
|
|
276
295
|
// 添加模型标识
|
|
296
|
+
uiMode
|
|
277
297
|
};
|
|
278
298
|
})
|
|
279
299
|
).then((steps) => steps.filter(Boolean));
|
|
@@ -403,19 +423,55 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
|
|
|
403
423
|
if (flattenSubMenus) {
|
|
404
424
|
configurableFlowsAndSteps.forEach(({ flow, steps, modelKey }) => {
|
|
405
425
|
const groupKey = generateUniqueKey(`flow-group-${modelKey ? `${modelKey}-` : ""}${flow.key}`);
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
426
|
+
if (flow.options.divider === "top") {
|
|
427
|
+
items.push({
|
|
428
|
+
type: "divider"
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
if (flow.options.enableTitle) {
|
|
432
|
+
items.push({
|
|
433
|
+
key: groupKey,
|
|
434
|
+
label: t(flow.title) || flow.key,
|
|
435
|
+
type: "group"
|
|
436
|
+
});
|
|
437
|
+
}
|
|
411
438
|
steps.forEach((stepInfo) => {
|
|
412
439
|
const baseMenuKey = modelKey ? `${modelKey}:${flow.key}:${stepInfo.stepKey}` : `${flow.key}:${stepInfo.stepKey}`;
|
|
413
440
|
const uniqueKey = generateUniqueKey(baseMenuKey);
|
|
441
|
+
const uiMode = stepInfo.uiMode;
|
|
442
|
+
const subModel = findSubModelByKey(model, stepInfo.modelKey);
|
|
443
|
+
const targetModel = subModel || model;
|
|
444
|
+
const stepParams = targetModel.getStepParams(flow.key, stepInfo.stepKey) || {};
|
|
445
|
+
const itemProps = {
|
|
446
|
+
getDefaultValue: /* @__PURE__ */ __name(async () => {
|
|
447
|
+
var _a;
|
|
448
|
+
let defaultParams = await (0, import_utils.resolveDefaultParams)(stepInfo.step.defaultParams, targetModel.context);
|
|
449
|
+
if (stepInfo.step.use) {
|
|
450
|
+
const action = (_a = targetModel.getAction) == null ? void 0 : _a.call(targetModel, stepInfo.step.use);
|
|
451
|
+
defaultParams = await (0, import_utils.resolveDefaultParams)(action.defaultParams, targetModel.context);
|
|
452
|
+
}
|
|
453
|
+
return { ...defaultParams, ...stepParams };
|
|
454
|
+
}, "getDefaultValue"),
|
|
455
|
+
onChange: /* @__PURE__ */ __name(async (val) => {
|
|
456
|
+
var _a;
|
|
457
|
+
targetModel.setStepParams(flow.key, stepInfo.stepKey, val);
|
|
458
|
+
if (typeof stepInfo.step.beforeParamsSave === "function") {
|
|
459
|
+
await stepInfo.step.beforeParamsSave(targetModel.context, val, stepParams);
|
|
460
|
+
}
|
|
461
|
+
await targetModel.saveStepParams();
|
|
462
|
+
(_a = message == null ? void 0 : message.success) == null ? void 0 : _a.call(message, t("Configuration saved"));
|
|
463
|
+
if (typeof stepInfo.step.afterParamsSave === "function") {
|
|
464
|
+
await stepInfo.step.afterParamsSave(targetModel.context, val, stepParams);
|
|
465
|
+
}
|
|
466
|
+
}, "onChange"),
|
|
467
|
+
...(uiMode == null ? void 0 : uiMode.props) || {},
|
|
468
|
+
itemKey: uiMode == null ? void 0 : uiMode.key
|
|
469
|
+
};
|
|
414
470
|
items.push({
|
|
415
471
|
key: uniqueKey,
|
|
416
|
-
label: t(stepInfo.title)
|
|
472
|
+
label: /* @__PURE__ */ import_react.default.createElement(MenuLabelItem, { title: t(stepInfo.title), uiMode, itemProps })
|
|
417
473
|
});
|
|
418
|
-
if (flow.key === "popupSettings") {
|
|
474
|
+
if (flow.key === "popupSettings" && baseMenuKey.includes("openView")) {
|
|
419
475
|
const copyKey = generateUniqueKey(`copy-pop-uid:${baseMenuKey}`);
|
|
420
476
|
items.push({
|
|
421
477
|
key: copyKey,
|
|
@@ -423,6 +479,11 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
|
|
|
423
479
|
});
|
|
424
480
|
}
|
|
425
481
|
});
|
|
482
|
+
if (flow.options.divider === "bottom") {
|
|
483
|
+
items.push({
|
|
484
|
+
type: "divider"
|
|
485
|
+
});
|
|
486
|
+
}
|
|
426
487
|
});
|
|
427
488
|
} else {
|
|
428
489
|
const modelGroups = /* @__PURE__ */ new Map();
|
|
@@ -494,9 +555,7 @@ const DefaultSettingsIcon = /* @__PURE__ */ __name(({
|
|
|
494
555
|
const items = [...menuItems];
|
|
495
556
|
if (showCopyUidButton || showDeleteButton) {
|
|
496
557
|
items.push({
|
|
497
|
-
|
|
498
|
-
label: t("Common actions"),
|
|
499
|
-
type: "group"
|
|
558
|
+
type: "divider"
|
|
500
559
|
});
|
|
501
560
|
if (showCopyUidButton && model.uid) {
|
|
502
561
|
items.push({
|
package/lib/flowEngine.js
CHANGED
|
@@ -759,7 +759,11 @@ const _FlowEngine = class _FlowEngine {
|
|
|
759
759
|
if (this.ensureModelRepository()) {
|
|
760
760
|
await this._modelRepository.destroy(uid);
|
|
761
761
|
}
|
|
762
|
-
|
|
762
|
+
const modelInstance = this._modelInstances.get(uid);
|
|
763
|
+
const parent = modelInstance == null ? void 0 : modelInstance.parent;
|
|
764
|
+
const result = this.removeModel(uid);
|
|
765
|
+
parent && parent.emitter.emit("onSubModelDestroyed", modelInstance);
|
|
766
|
+
return result;
|
|
763
767
|
}
|
|
764
768
|
/**
|
|
765
769
|
* Duplicate a model tree via repository API.
|
package/lib/flowSettings.d.ts
CHANGED
|
@@ -25,8 +25,8 @@ export interface FlowSettingsOpenOptions {
|
|
|
25
25
|
/** 指定打开的步骤 key(配合 flowKey 使用) */
|
|
26
26
|
stepKey?: string;
|
|
27
27
|
/** 弹窗展现形式(drawer 或 dialog) */
|
|
28
|
-
uiMode?: 'dialog' | 'drawer' | 'embed' | {
|
|
29
|
-
type?: 'dialog' | 'drawer' | 'embed';
|
|
28
|
+
uiMode?: 'select' | 'switch' | 'dialog' | 'drawer' | 'embed' | {
|
|
29
|
+
type?: 'dialog' | 'drawer' | 'embed' | 'select' | 'switch';
|
|
30
30
|
props?: {
|
|
31
31
|
title?: string;
|
|
32
32
|
width?: number;
|
package/lib/flowSettings.js
CHANGED
|
@@ -466,12 +466,11 @@ const _FlowSettings = class _FlowSettings {
|
|
|
466
466
|
if (!preset && (!step || await (0, import_utils.shouldHideStepInSettings)(model, flow, step))) continue;
|
|
467
467
|
if (preset && !step.preset) continue;
|
|
468
468
|
const mergedUiSchema = await (0, import_utils.resolveStepUiSchema)(model, flow, step);
|
|
469
|
-
if (!mergedUiSchema || Object.keys(mergedUiSchema).length === 0) continue;
|
|
470
469
|
let stepTitle = step.title;
|
|
471
470
|
let beforeParamsSave = step.beforeParamsSave;
|
|
472
471
|
let afterParamsSave = step.afterParamsSave;
|
|
473
472
|
let actionDefaultParams = {};
|
|
474
|
-
let uiMode2;
|
|
473
|
+
let uiMode2 = step.uiMode;
|
|
475
474
|
if (step.use) {
|
|
476
475
|
const action = (_b = model.getAction) == null ? void 0 : _b.call(model, step.use);
|
|
477
476
|
if (action) {
|
|
@@ -497,6 +496,9 @@ const _FlowSettings = class _FlowSettings {
|
|
|
497
496
|
...resolvedDefaultParams || {},
|
|
498
497
|
...modelStepParams
|
|
499
498
|
};
|
|
499
|
+
if ((!mergedUiSchema || Object.keys(mergedUiSchema).length === 0) && !["select", "switch"].includes((uiMode2 == null ? void 0 : uiMode2.type) || uiMode2)) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
500
502
|
entries.push({
|
|
501
503
|
flowKey: fk,
|
|
502
504
|
flowTitle: t(flow.title) || fk,
|
|
@@ -522,6 +524,9 @@ const _FlowSettings = class _FlowSettings {
|
|
|
522
524
|
const viewer = model.context.viewer;
|
|
523
525
|
const resolvedUiMode = entries.length === 1 ? await (0, import_utils.resolveUiMode)(entries[0].uiMode || uiMode, entries[0].ctx) : uiMode;
|
|
524
526
|
const modeType = typeof resolvedUiMode === "string" ? resolvedUiMode : resolvedUiMode.type || "dialog";
|
|
527
|
+
if (["select", "switch"].includes(modeType)) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
525
530
|
const openView = viewer[modeType || "dialog"].bind(viewer);
|
|
526
531
|
const flowEngine = model.flowEngine;
|
|
527
532
|
const scopes = {
|
|
@@ -265,10 +265,11 @@ const _SQLResource = class _SQLResource extends import_baseRecordResource.BaseRe
|
|
|
265
265
|
try {
|
|
266
266
|
this.clearError();
|
|
267
267
|
this.loading = true;
|
|
268
|
+
this.emit("loading");
|
|
268
269
|
const { data, meta } = await this.run();
|
|
269
270
|
this.setData(data).setMeta(meta);
|
|
270
|
-
this.emit("refresh");
|
|
271
271
|
this.loading = false;
|
|
272
|
+
this.emit("refresh");
|
|
272
273
|
resolve();
|
|
273
274
|
} catch (error) {
|
|
274
275
|
this.setError(error);
|
package/lib/types.d.ts
CHANGED
|
@@ -94,6 +94,8 @@ export interface FlowDefinitionOptions<TModel extends FlowModel = FlowModel> {
|
|
|
94
94
|
* 仅填补缺失,不覆盖已有。固定返回形状:{ [stepKey]: params }
|
|
95
95
|
*/
|
|
96
96
|
defaultParams?: Record<string, any> | ((ctx: FlowModelContext) => StepParam | Promise<StepParam>);
|
|
97
|
+
enableTitle?: boolean;
|
|
98
|
+
divider?: 'top' | 'bottom';
|
|
97
99
|
}
|
|
98
100
|
export interface IModelComponentProps {
|
|
99
101
|
[key: string]: any;
|
|
@@ -185,8 +187,9 @@ export interface DispatchEventOptions {
|
|
|
185
187
|
*/
|
|
186
188
|
export type EventDefinition<TModel extends FlowModel = FlowModel, TCtx extends FlowContext = FlowContext> = ActionDefinition<TModel, TCtx>;
|
|
187
189
|
export type StepUIMode = 'dialog' | 'drawer' | 'embed' | {
|
|
188
|
-
type?: 'dialog' | 'drawer' | 'embed';
|
|
190
|
+
type?: 'dialog' | 'drawer' | 'embed' | 'select' | 'switch';
|
|
189
191
|
props?: Record<string, any>;
|
|
192
|
+
key?: string;
|
|
190
193
|
};
|
|
191
194
|
/**
|
|
192
195
|
* Step definition with unified support for both registered actions and inline handlers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.55",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.0.0-alpha.
|
|
12
|
-
"@nocobase/shared": "2.0.0-alpha.
|
|
11
|
+
"@nocobase/sdk": "2.0.0-alpha.55",
|
|
12
|
+
"@nocobase/shared": "2.0.0-alpha.55",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"dayjs": "^1.11.9",
|
|
15
15
|
"dompurify": "^3.0.2",
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
],
|
|
37
37
|
"author": "NocoBase Team",
|
|
38
38
|
"license": "AGPL-3.0",
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "7e65ad6b6e4e76a51f82c69b04b563fbcc7e1c25"
|
|
40
40
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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 { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
11
|
+
import { FlowEngine } from '../flowEngine';
|
|
12
|
+
import { FlowModel } from '../models';
|
|
13
|
+
|
|
14
|
+
describe('FlowEngine destroyModel', () => {
|
|
15
|
+
let engine: FlowEngine;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
engine = new FlowEngine();
|
|
19
|
+
engine.registerModels({ FlowModel });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('calls repository.destroy when repository exists and emits onSubModelDestroyed on parent', async () => {
|
|
23
|
+
const parent = engine.createModel({ uid: 'parent', use: 'FlowModel' });
|
|
24
|
+
const child = engine.createModel({
|
|
25
|
+
uid: 'child',
|
|
26
|
+
use: 'FlowModel',
|
|
27
|
+
parentId: 'parent',
|
|
28
|
+
subKey: 'child',
|
|
29
|
+
subType: 'object',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const repo = { destroy: vi.fn().mockResolvedValue(true) } as any;
|
|
33
|
+
(engine as any)._modelRepository = repo;
|
|
34
|
+
|
|
35
|
+
const emitSpy = vi.spyOn(parent.emitter, 'emit');
|
|
36
|
+
|
|
37
|
+
const res = await engine.destroyModel('child');
|
|
38
|
+
|
|
39
|
+
expect(repo.destroy).toHaveBeenCalledWith('child');
|
|
40
|
+
expect(res).toBe(true);
|
|
41
|
+
expect(emitSpy).toHaveBeenCalledWith('onSubModelDestroyed', child);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('emits onSubModelDestroyed even if there is no repository', async () => {
|
|
45
|
+
const parent = engine.createModel({ uid: 'parent', use: 'FlowModel' });
|
|
46
|
+
const child = engine.createModel({
|
|
47
|
+
uid: 'child',
|
|
48
|
+
use: 'FlowModel',
|
|
49
|
+
parentId: 'parent',
|
|
50
|
+
subKey: 'child',
|
|
51
|
+
subType: 'object',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ensure no repository
|
|
55
|
+
(engine as any)._modelRepository = null;
|
|
56
|
+
|
|
57
|
+
const emitSpy = vi.spyOn(parent.emitter, 'emit');
|
|
58
|
+
|
|
59
|
+
const res = await engine.destroyModel('child');
|
|
60
|
+
|
|
61
|
+
expect(res).toBe(true);
|
|
62
|
+
expect(emitSpy).toHaveBeenCalledWith('onSubModelDestroyed', child);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns false when destroying a non-existent model but still calls repo.destroy if repo exists', async () => {
|
|
66
|
+
const repo = { destroy: vi.fn().mockResolvedValue(true) } as any;
|
|
67
|
+
(engine as any)._modelRepository = repo;
|
|
68
|
+
|
|
69
|
+
const res = await engine.destroyModel('no-such-uid');
|
|
70
|
+
|
|
71
|
+
expect(repo.destroy).toHaveBeenCalledWith('no-such-uid');
|
|
72
|
+
expect(res).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
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 { Select } from 'antd';
|
|
11
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
12
|
+
import { useFlowEngineContext } from '../../../../provider';
|
|
13
|
+
|
|
14
|
+
export interface SelectWithTitleProps {
|
|
15
|
+
title?: any;
|
|
16
|
+
getDefaultValue?: any;
|
|
17
|
+
options?: any;
|
|
18
|
+
fieldNames?: any;
|
|
19
|
+
itemKey?: string;
|
|
20
|
+
onChange?: (...args: any[]) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function SelectWithTitle({
|
|
24
|
+
title,
|
|
25
|
+
getDefaultValue,
|
|
26
|
+
onChange,
|
|
27
|
+
options,
|
|
28
|
+
fieldNames,
|
|
29
|
+
itemKey,
|
|
30
|
+
...others
|
|
31
|
+
}: SelectWithTitleProps) {
|
|
32
|
+
const [open, setOpen] = useState(false);
|
|
33
|
+
const [value, setValue] = useState<any>('');
|
|
34
|
+
const ctx = useFlowEngineContext();
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
let cancelled = false;
|
|
37
|
+
|
|
38
|
+
const run = async () => {
|
|
39
|
+
if (!getDefaultValue) return;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const val = await getDefaultValue();
|
|
43
|
+
if (cancelled || !val) return;
|
|
44
|
+
|
|
45
|
+
const entries = Object.entries(val);
|
|
46
|
+
if (!entries.length) return;
|
|
47
|
+
|
|
48
|
+
const [key, result] = entries[0];
|
|
49
|
+
setValue(result);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error(e);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
run();
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
cancelled = true;
|
|
59
|
+
};
|
|
60
|
+
}, [getDefaultValue]);
|
|
61
|
+
|
|
62
|
+
const timerRef = useRef<any>(null);
|
|
63
|
+
|
|
64
|
+
const handleChange = (val: any) => {
|
|
65
|
+
setValue(val);
|
|
66
|
+
onChange?.({ [itemKey]: val });
|
|
67
|
+
};
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}
|
|
71
|
+
onClick={(e) => {
|
|
72
|
+
e.stopPropagation();
|
|
73
|
+
setOpen((v) => !v);
|
|
74
|
+
}}
|
|
75
|
+
onMouseLeave={() => {
|
|
76
|
+
timerRef.current = setTimeout(() => {
|
|
77
|
+
setOpen(false);
|
|
78
|
+
}, 200);
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<span
|
|
82
|
+
style={{
|
|
83
|
+
whiteSpace: 'nowrap', // 不换行
|
|
84
|
+
flexShrink: 0, // 不被挤压
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
{title}
|
|
88
|
+
</span>
|
|
89
|
+
<Select
|
|
90
|
+
{...others}
|
|
91
|
+
open={open}
|
|
92
|
+
popupMatchSelectWidth={false}
|
|
93
|
+
bordered={false}
|
|
94
|
+
value={value}
|
|
95
|
+
onChange={handleChange}
|
|
96
|
+
popupClassName={`select-popup-${title.replaceAll(' ', '-')}`}
|
|
97
|
+
fieldNames={fieldNames}
|
|
98
|
+
options={options}
|
|
99
|
+
labelRender={(props) => ctx.t(props.label)}
|
|
100
|
+
optionRender={(o) => ctx.t(o.label)}
|
|
101
|
+
style={{ textAlign: 'right', minWidth: 100 }}
|
|
102
|
+
onMouseEnter={() => {
|
|
103
|
+
clearTimeout(timerRef.current);
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { observer } from '@formily/react';
|
|
11
|
+
import { Switch } from 'antd';
|
|
12
|
+
import React, { FC, useEffect, useState } from 'react';
|
|
13
|
+
import { useFlowEngineContext } from '../../../../provider';
|
|
14
|
+
|
|
15
|
+
const ml32 = { marginLeft: 32 };
|
|
16
|
+
|
|
17
|
+
export const SwitchWithTitle: FC = observer(
|
|
18
|
+
({ title, onChange, getDefaultValue, disabled, itemKey, ...others }: any) => {
|
|
19
|
+
const [checked, setChecked] = useState<boolean>(false);
|
|
20
|
+
const ctx = useFlowEngineContext();
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
let cancelled = false;
|
|
24
|
+
|
|
25
|
+
const run = async () => {
|
|
26
|
+
if (!getDefaultValue) return;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const val = await getDefaultValue();
|
|
30
|
+
if (cancelled || !val) return;
|
|
31
|
+
|
|
32
|
+
const entries = Object.entries(val);
|
|
33
|
+
if (!entries.length) return;
|
|
34
|
+
|
|
35
|
+
const [key, value] = entries[0];
|
|
36
|
+
setChecked(!!value);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error(e);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
run();
|
|
43
|
+
|
|
44
|
+
return () => {
|
|
45
|
+
cancelled = true;
|
|
46
|
+
};
|
|
47
|
+
}, [getDefaultValue]);
|
|
48
|
+
|
|
49
|
+
const handleChange = (val: boolean) => {
|
|
50
|
+
setChecked(val);
|
|
51
|
+
onChange?.({ [itemKey]: val });
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// 点击整个容器时触发
|
|
55
|
+
const handleWrapperClick = () => {
|
|
56
|
+
if (disabled) return;
|
|
57
|
+
handleChange(!checked);
|
|
58
|
+
};
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
style={{
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
display: 'flex',
|
|
64
|
+
justifyContent: 'space-between',
|
|
65
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
66
|
+
}}
|
|
67
|
+
onClick={handleWrapperClick}
|
|
68
|
+
>
|
|
69
|
+
{title}
|
|
70
|
+
<Switch
|
|
71
|
+
size="small"
|
|
72
|
+
{...others}
|
|
73
|
+
checkedChildren={others.checkedChildren ? ctx.t(others.checkedChildren) : undefined}
|
|
74
|
+
unCheckedChildren={others.unCheckedChildren ? ctx.t(others.unCheckedChildren) : undefined}
|
|
75
|
+
checked={checked}
|
|
76
|
+
style={ml32}
|
|
77
|
+
disabled={disabled}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
);
|
|
@@ -10,13 +10,19 @@
|
|
|
10
10
|
import { ExclamationCircleOutlined, MenuOutlined } from '@ant-design/icons';
|
|
11
11
|
import type { DropdownProps, MenuProps } from 'antd';
|
|
12
12
|
import { App, Dropdown, Modal } from 'antd';
|
|
13
|
-
import React, { startTransition, useCallback, useEffect, useMemo, useState } from 'react';
|
|
13
|
+
import React, { startTransition, useCallback, useEffect, useMemo, useState, FC } from 'react';
|
|
14
14
|
import { FlowModel } from '../../../../models';
|
|
15
|
-
import { StepDefinition } from '../../../../types';
|
|
16
|
-
import {
|
|
17
|
-
|
|
15
|
+
import { StepDefinition, StepUIMode } from '../../../../types';
|
|
16
|
+
import {
|
|
17
|
+
getT,
|
|
18
|
+
resolveStepUiSchema,
|
|
19
|
+
shouldHideStepInSettings,
|
|
20
|
+
resolveDefaultParams,
|
|
21
|
+
resolveUiMode,
|
|
22
|
+
} from '../../../../utils';
|
|
18
23
|
import { useNiceDropdownMaxHeight } from '../../../../hooks';
|
|
19
|
-
|
|
24
|
+
import { SwitchWithTitle } from '../component/SwitchWithTitle';
|
|
25
|
+
import { SelectWithTitle } from '../component/SelectWithTitle';
|
|
20
26
|
// Type definitions for better type safety
|
|
21
27
|
interface StepInfo {
|
|
22
28
|
stepKey: string;
|
|
@@ -24,6 +30,7 @@ interface StepInfo {
|
|
|
24
30
|
uiSchema: Record<string, any>;
|
|
25
31
|
title: string;
|
|
26
32
|
modelKey?: string;
|
|
33
|
+
uiMode?: StepUIMode;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
interface FlowInfo {
|
|
@@ -85,6 +92,22 @@ const findSubModelByKey = (model: FlowModel, subModelKey: string): FlowModel | n
|
|
|
85
92
|
}
|
|
86
93
|
};
|
|
87
94
|
|
|
95
|
+
const componentMap = {
|
|
96
|
+
switch: SwitchWithTitle,
|
|
97
|
+
select: SelectWithTitle,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const MenuLabelItem = ({ title, uiMode, itemProps }) => {
|
|
101
|
+
const type = uiMode?.type || uiMode;
|
|
102
|
+
const Component = type ? componentMap[type] : null;
|
|
103
|
+
|
|
104
|
+
if (!Component) {
|
|
105
|
+
return <>{title}</>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return <Component title={title} {...itemProps} />;
|
|
109
|
+
};
|
|
110
|
+
|
|
88
111
|
/**
|
|
89
112
|
* 默认的设置菜单图标组件
|
|
90
113
|
* 提供原有的配置菜单功能
|
|
@@ -281,12 +304,12 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
281
304
|
const configurableSteps = await Promise.all(
|
|
282
305
|
Object.entries(flow.steps).map(async ([stepKey, stepDefinition]) => {
|
|
283
306
|
const actionStep = stepDefinition;
|
|
284
|
-
|
|
307
|
+
let step = actionStep;
|
|
285
308
|
// 支持静态与动态 hideInSettings
|
|
286
309
|
if (await shouldHideStepInSettings(targetModel, flow, actionStep)) {
|
|
287
310
|
return null;
|
|
288
311
|
}
|
|
289
|
-
|
|
312
|
+
let uiMode: any = await resolveUiMode(actionStep.uiMode, (targetModel as any).context);
|
|
290
313
|
// 检查是否有uiSchema(静态或动态)
|
|
291
314
|
const hasStepUiSchema = actionStep.uiSchema != null;
|
|
292
315
|
|
|
@@ -296,15 +319,18 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
296
319
|
if (actionStep.use) {
|
|
297
320
|
try {
|
|
298
321
|
const action = targetModel.getAction?.(actionStep.use);
|
|
322
|
+
step = { ...(action || {}), ...actionStep };
|
|
323
|
+
uiMode = await resolveUiMode(action?.uiMode || uiMode, (targetModel as any).context);
|
|
299
324
|
hasActionUiSchema = action && action.uiSchema != null;
|
|
300
325
|
stepTitle = stepTitle || action?.title;
|
|
301
326
|
} catch (error) {
|
|
302
327
|
console.warn(t('Failed to get action {{action}}', { action: actionStep.use }), ':', error);
|
|
303
328
|
}
|
|
304
329
|
}
|
|
330
|
+
const selectOrSwitchMode = ['select', 'switch'].includes(uiMode?.type || uiMode);
|
|
305
331
|
|
|
306
332
|
// 如果都没有uiSchema(静态或动态),返回null
|
|
307
|
-
if (!hasStepUiSchema && !hasActionUiSchema) {
|
|
333
|
+
if (!selectOrSwitchMode && !hasStepUiSchema && !hasActionUiSchema) {
|
|
308
334
|
return null;
|
|
309
335
|
}
|
|
310
336
|
|
|
@@ -316,7 +342,7 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
316
342
|
const resolvedSchema = await resolveStepUiSchema(targetModel, flow, actionStep);
|
|
317
343
|
|
|
318
344
|
// 如果解析后没有可配置的UI Schema,跳过此步骤
|
|
319
|
-
if (!resolvedSchema) {
|
|
345
|
+
if (!resolvedSchema && !selectOrSwitchMode) {
|
|
320
346
|
return null;
|
|
321
347
|
}
|
|
322
348
|
|
|
@@ -325,13 +351,13 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
325
351
|
console.warn(t('Failed to resolve uiSchema for step {{stepKey}}', { stepKey }), ':', error);
|
|
326
352
|
return null;
|
|
327
353
|
}
|
|
328
|
-
|
|
329
354
|
return {
|
|
330
355
|
stepKey,
|
|
331
|
-
step
|
|
356
|
+
step,
|
|
332
357
|
uiSchema: mergedUiSchema,
|
|
333
358
|
title: t(stepTitle) || stepKey,
|
|
334
359
|
modelKey, // 添加模型标识
|
|
360
|
+
uiMode,
|
|
335
361
|
};
|
|
336
362
|
}),
|
|
337
363
|
).then((steps) => steps.filter(Boolean));
|
|
@@ -489,13 +515,19 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
489
515
|
// 平铺模式:只有流程分组,没有模型层级
|
|
490
516
|
configurableFlowsAndSteps.forEach(({ flow, steps, modelKey }: FlowInfo) => {
|
|
491
517
|
const groupKey = generateUniqueKey(`flow-group-${modelKey ? `${modelKey}-` : ''}${flow.key}`);
|
|
492
|
-
|
|
518
|
+
if (flow.options.divider === 'top') {
|
|
519
|
+
items.push({
|
|
520
|
+
type: 'divider',
|
|
521
|
+
});
|
|
522
|
+
}
|
|
493
523
|
// 在平铺模式下始终按流程分组
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
524
|
+
if (flow.options.enableTitle) {
|
|
525
|
+
items.push({
|
|
526
|
+
key: groupKey,
|
|
527
|
+
label: t(flow.title) || flow.key,
|
|
528
|
+
type: 'group',
|
|
529
|
+
});
|
|
530
|
+
}
|
|
499
531
|
|
|
500
532
|
steps.forEach((stepInfo: StepInfo) => {
|
|
501
533
|
// 构建菜单项key,为子模型包含modelKey
|
|
@@ -504,14 +536,39 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
504
536
|
: `${flow.key}:${stepInfo.stepKey}`;
|
|
505
537
|
|
|
506
538
|
const uniqueKey = generateUniqueKey(baseMenuKey);
|
|
507
|
-
|
|
539
|
+
const uiMode = stepInfo.uiMode;
|
|
540
|
+
const subModel: any = findSubModelByKey(model, stepInfo.modelKey);
|
|
541
|
+
const targetModel = subModel || model;
|
|
542
|
+
const stepParams = targetModel.getStepParams(flow.key, stepInfo.stepKey) || {};
|
|
543
|
+
const itemProps = {
|
|
544
|
+
getDefaultValue: async () => {
|
|
545
|
+
let defaultParams = await resolveDefaultParams(stepInfo.step.defaultParams, targetModel.context);
|
|
546
|
+
if (stepInfo.step.use) {
|
|
547
|
+
const action = targetModel.getAction?.(stepInfo.step.use);
|
|
548
|
+
defaultParams = await resolveDefaultParams(action.defaultParams, targetModel.context);
|
|
549
|
+
}
|
|
550
|
+
return { ...defaultParams, ...stepParams };
|
|
551
|
+
},
|
|
552
|
+
onChange: async (val) => {
|
|
553
|
+
targetModel.setStepParams(flow.key, stepInfo.stepKey, val);
|
|
554
|
+
if (typeof stepInfo.step.beforeParamsSave === 'function') {
|
|
555
|
+
await stepInfo.step.beforeParamsSave(targetModel.context, val, stepParams);
|
|
556
|
+
}
|
|
557
|
+
await targetModel.saveStepParams();
|
|
558
|
+
message?.success?.(t('Configuration saved'));
|
|
559
|
+
if (typeof stepInfo.step.afterParamsSave === 'function') {
|
|
560
|
+
await stepInfo.step.afterParamsSave(targetModel.context, val, stepParams);
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
...((uiMode as any)?.props || {}),
|
|
564
|
+
itemKey: (uiMode as any)?.key,
|
|
565
|
+
};
|
|
508
566
|
items.push({
|
|
509
567
|
key: uniqueKey,
|
|
510
|
-
label: t(stepInfo.title)
|
|
568
|
+
label: <MenuLabelItem title={t(stepInfo.title)} uiMode={uiMode} itemProps={itemProps} />,
|
|
511
569
|
});
|
|
512
|
-
|
|
513
570
|
// add per-step copy popup uid under each configurable step
|
|
514
|
-
if (flow.key === 'popupSettings') {
|
|
571
|
+
if (flow.key === 'popupSettings' && baseMenuKey.includes('openView')) {
|
|
515
572
|
const copyKey = generateUniqueKey(`copy-pop-uid:${baseMenuKey}`);
|
|
516
573
|
items.push({
|
|
517
574
|
key: copyKey,
|
|
@@ -519,6 +576,11 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
519
576
|
});
|
|
520
577
|
}
|
|
521
578
|
});
|
|
579
|
+
if (flow.options.divider === 'bottom') {
|
|
580
|
+
items.push({
|
|
581
|
+
type: 'divider',
|
|
582
|
+
});
|
|
583
|
+
}
|
|
522
584
|
});
|
|
523
585
|
} else {
|
|
524
586
|
// 层级模式:真正的子菜单结构
|
|
@@ -542,7 +604,6 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
542
604
|
// 直接添加当前模型的flows
|
|
543
605
|
flows.forEach(({ flow, steps }: FlowInfo) => {
|
|
544
606
|
const groupKey = generateUniqueKey(`flow-group-${flow.key}`);
|
|
545
|
-
|
|
546
607
|
items.push({
|
|
547
608
|
key: groupKey,
|
|
548
609
|
label: t(flow.title) || flow.key,
|
|
@@ -608,12 +669,16 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
608
669
|
const items = [...menuItems];
|
|
609
670
|
|
|
610
671
|
if (showCopyUidButton || showDeleteButton) {
|
|
611
|
-
// 使用分组呈现常用操作(不再使用分割线)
|
|
612
672
|
items.push({
|
|
613
|
-
|
|
614
|
-
label: t('Common actions'),
|
|
615
|
-
type: 'group' as const,
|
|
673
|
+
type: 'divider',
|
|
616
674
|
});
|
|
675
|
+
// 使用分组呈现常用操作(不再使用分割线)
|
|
676
|
+
|
|
677
|
+
// items.push({
|
|
678
|
+
// key: 'common-actions',
|
|
679
|
+
// label: t('Common actions'),
|
|
680
|
+
// type: 'group' as const,
|
|
681
|
+
// });
|
|
617
682
|
|
|
618
683
|
// 添加复制uid按钮
|
|
619
684
|
if (showCopyUidButton && model.uid) {
|
|
@@ -49,9 +49,8 @@ const openStepSettings = async ({ model, flowKey, stepKey, width = 600, title }:
|
|
|
49
49
|
|
|
50
50
|
// 解析 uiMode,支持函数式
|
|
51
51
|
const resolvedUiMode = await resolveUiMode(step.uiMode, ctx);
|
|
52
|
-
|
|
53
52
|
// 提取模式和属性
|
|
54
|
-
let settingMode: 'dialog' | 'drawer' | 'embed';
|
|
53
|
+
let settingMode: 'dialog' | 'drawer' | 'embed' | 'select' | 'switch';
|
|
55
54
|
let uiModeProps: Record<string, any> = {};
|
|
56
55
|
let cleanup: () => void;
|
|
57
56
|
|
|
@@ -140,23 +140,19 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
140
140
|
),
|
|
141
141
|
);
|
|
142
142
|
|
|
143
|
-
//
|
|
143
|
+
// 等待菜单渲染完成,并且只包含静态流的步骤
|
|
144
144
|
await waitFor(() => {
|
|
145
145
|
const menu = (globalThis as any).__lastDropdownMenu;
|
|
146
146
|
expect(menu).toBeTruthy();
|
|
147
147
|
const items = (menu?.items || []) as any[];
|
|
148
|
-
const
|
|
149
|
-
expect(
|
|
148
|
+
const keys = items.map((it) => String(it.key || ''));
|
|
149
|
+
expect(keys.some((k) => k.startsWith('static1:'))).toBe(true);
|
|
150
|
+
expect(keys.some((k) => k.startsWith('dyn1:'))).toBe(false);
|
|
150
151
|
});
|
|
151
152
|
|
|
152
153
|
const menu = (globalThis as any).__lastDropdownMenu;
|
|
153
154
|
const items = (menu?.items || []) as any[];
|
|
154
155
|
|
|
155
|
-
// groups for flows are labeled with flow.title; ensure static group exists, dynamic group不存在
|
|
156
|
-
const groupLabels = items.filter((it) => it.type === 'group').map((it) => String(it.label));
|
|
157
|
-
expect(groupLabels).toContain('Static Flow');
|
|
158
|
-
expect(groupLabels).not.toContain('Dynamic Flow');
|
|
159
|
-
|
|
160
156
|
// 静态流的 step 存在(key: `${flowKey}:${stepKey}`),动态流 step 不存在
|
|
161
157
|
expect(items.some((it) => String(it.key || '').startsWith('static1:'))).toBe(true);
|
|
162
158
|
expect(items.some((it) => String(it.key || '').startsWith('dyn1:'))).toBe(false);
|
|
@@ -361,7 +357,7 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
361
357
|
});
|
|
362
358
|
});
|
|
363
359
|
|
|
364
|
-
it('adds "Copy popup UID" for popupSettings
|
|
360
|
+
it('adds "Copy popup UID" for popupSettings openView step (current model and sub-model)', async () => {
|
|
365
361
|
class Parent extends FlowModel {}
|
|
366
362
|
class Child extends FlowModel {}
|
|
367
363
|
const engine = new FlowEngine();
|
|
@@ -372,13 +368,13 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
372
368
|
Parent.registerFlow({
|
|
373
369
|
key: 'popupSettings',
|
|
374
370
|
title: 'Popup',
|
|
375
|
-
steps: {
|
|
371
|
+
steps: { openView: { title: 'Open view', uiSchema: { a: { type: 'string', 'x-component': 'Input' } } } },
|
|
376
372
|
});
|
|
377
373
|
// sub model popupSettings
|
|
378
374
|
Child.registerFlow({
|
|
379
375
|
key: 'popupSettings',
|
|
380
376
|
title: 'Popup Child',
|
|
381
|
-
steps: {
|
|
377
|
+
steps: { openView: { title: 'Open view', uiSchema: { a: { type: 'string', 'x-component': 'Input' } } } },
|
|
382
378
|
});
|
|
383
379
|
parent.addSubModel('items', child);
|
|
384
380
|
|
|
@@ -408,18 +404,18 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
408
404
|
await waitFor(() => {
|
|
409
405
|
const m = (globalThis as any).__lastDropdownMenu;
|
|
410
406
|
const is = (m?.items || []) as any[];
|
|
411
|
-
const current = is.find((it) => String(it.key) === 'copy-pop-uid:popupSettings:
|
|
412
|
-
const sub = is.find((it) => String(it.key).startsWith('copy-pop-uid:items[0]:popupSettings:
|
|
407
|
+
const current = is.find((it) => String(it.key) === 'copy-pop-uid:popupSettings:openView');
|
|
408
|
+
const sub = is.find((it) => String(it.key).startsWith('copy-pop-uid:items[0]:popupSettings:openView'));
|
|
413
409
|
expect(current).toBeTruthy();
|
|
414
410
|
expect(sub).toBeTruthy();
|
|
415
411
|
});
|
|
416
412
|
|
|
417
413
|
// click and verify clipboard(直接使用最新的 menu)
|
|
418
414
|
const menu = (globalThis as any).__lastDropdownMenu;
|
|
419
|
-
menu.onClick?.({ key: 'copy-pop-uid:popupSettings:
|
|
415
|
+
menu.onClick?.({ key: 'copy-pop-uid:popupSettings:openView' });
|
|
420
416
|
expect((navigator as any).clipboard.writeText).toHaveBeenCalledWith('parent-2');
|
|
421
417
|
|
|
422
|
-
menu.onClick?.({ key: 'copy-pop-uid:items[0]:popupSettings:
|
|
418
|
+
menu.onClick?.({ key: 'copy-pop-uid:items[0]:popupSettings:openView' });
|
|
423
419
|
expect((navigator as any).clipboard.writeText).toHaveBeenCalledWith('child-2');
|
|
424
420
|
});
|
|
425
421
|
|
package/src/flowEngine.ts
CHANGED
|
@@ -902,7 +902,13 @@ export class FlowEngine {
|
|
|
902
902
|
if (this.ensureModelRepository()) {
|
|
903
903
|
await this._modelRepository.destroy(uid);
|
|
904
904
|
}
|
|
905
|
-
|
|
905
|
+
|
|
906
|
+
const modelInstance = this._modelInstances.get(uid) as FlowModel;
|
|
907
|
+
const parent = modelInstance?.parent;
|
|
908
|
+
const result = this.removeModel(uid);
|
|
909
|
+
parent && parent.emitter.emit('onSubModelDestroyed', modelInstance);
|
|
910
|
+
|
|
911
|
+
return result;
|
|
906
912
|
}
|
|
907
913
|
|
|
908
914
|
/**
|
package/src/flowSettings.ts
CHANGED
|
@@ -53,11 +53,13 @@ export interface FlowSettingsOpenOptions {
|
|
|
53
53
|
stepKey?: string;
|
|
54
54
|
/** 弹窗展现形式(drawer 或 dialog) */
|
|
55
55
|
uiMode?:
|
|
56
|
+
| 'select'
|
|
57
|
+
| 'switch'
|
|
56
58
|
| 'dialog'
|
|
57
59
|
| 'drawer'
|
|
58
60
|
| 'embed'
|
|
59
61
|
| {
|
|
60
|
-
type?: 'dialog' | 'drawer' | 'embed';
|
|
62
|
+
type?: 'dialog' | 'drawer' | 'embed' | 'select' | 'switch';
|
|
61
63
|
props?: {
|
|
62
64
|
title?: string;
|
|
63
65
|
width?: number;
|
|
@@ -597,14 +599,12 @@ export class FlowSettings {
|
|
|
597
599
|
|
|
598
600
|
// 解析合并后的 uiSchema(包含 action 的 schema)
|
|
599
601
|
const mergedUiSchema = await resolveStepUiSchema(model, flow, step);
|
|
600
|
-
if (!mergedUiSchema || Object.keys(mergedUiSchema).length === 0) continue;
|
|
601
|
-
|
|
602
602
|
// 计算标题与 hooks
|
|
603
603
|
let stepTitle: string = step.title;
|
|
604
604
|
let beforeParamsSave = step.beforeParamsSave;
|
|
605
605
|
let afterParamsSave = step.afterParamsSave;
|
|
606
606
|
let actionDefaultParams: Record<string, any> = {};
|
|
607
|
-
let uiMode;
|
|
607
|
+
let uiMode = step.uiMode;
|
|
608
608
|
if (step.use) {
|
|
609
609
|
const action = model.getAction?.(step.use);
|
|
610
610
|
if (action) {
|
|
@@ -633,7 +633,12 @@ export class FlowSettings {
|
|
|
633
633
|
...(resolvedDefaultParams || {}),
|
|
634
634
|
...modelStepParams,
|
|
635
635
|
};
|
|
636
|
-
|
|
636
|
+
if (
|
|
637
|
+
(!mergedUiSchema || Object.keys(mergedUiSchema).length === 0) &&
|
|
638
|
+
!['select', 'switch'].includes(uiMode?.type || uiMode)
|
|
639
|
+
) {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
637
642
|
entries.push({
|
|
638
643
|
flowKey: fk,
|
|
639
644
|
flowTitle: t(flow.title) || fk,
|
|
@@ -664,6 +669,9 @@ export class FlowSettings {
|
|
|
664
669
|
const resolvedUiMode =
|
|
665
670
|
entries.length === 1 ? await resolveUiMode(entries[0].uiMode || uiMode, entries[0].ctx) : uiMode;
|
|
666
671
|
const modeType = typeof resolvedUiMode === 'string' ? resolvedUiMode : resolvedUiMode.type || 'dialog';
|
|
672
|
+
if (['select', 'switch'].includes(modeType)) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
667
675
|
const openView = viewer[modeType || 'dialog'].bind(viewer);
|
|
668
676
|
const flowEngine = (model as any).flowEngine as FlowEngine;
|
|
669
677
|
const scopes = {
|
|
@@ -276,10 +276,11 @@ export class SQLResource<TData = any> extends BaseRecordResource<TData> {
|
|
|
276
276
|
try {
|
|
277
277
|
this.clearError();
|
|
278
278
|
this.loading = true;
|
|
279
|
+
this.emit('loading');
|
|
279
280
|
const { data, meta } = await this.run();
|
|
280
281
|
this.setData(data).setMeta(meta);
|
|
281
|
-
this.emit('refresh');
|
|
282
282
|
this.loading = false;
|
|
283
|
+
this.emit('refresh');
|
|
283
284
|
resolve();
|
|
284
285
|
} catch (error) {
|
|
285
286
|
this.setError(error);
|
package/src/types.ts
CHANGED
|
@@ -110,6 +110,8 @@ export interface FlowDefinitionOptions<TModel extends FlowModel = FlowModel> {
|
|
|
110
110
|
* 仅填补缺失,不覆盖已有。固定返回形状:{ [stepKey]: params }
|
|
111
111
|
*/
|
|
112
112
|
defaultParams?: Record<string, any> | ((ctx: FlowModelContext) => StepParam | Promise<StepParam>);
|
|
113
|
+
enableTitle?: boolean;
|
|
114
|
+
divider?: 'top' | 'bottom';
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
export interface IModelComponentProps {
|
|
@@ -242,7 +244,7 @@ export type StepUIMode =
|
|
|
242
244
|
| 'embed'
|
|
243
245
|
// | 'switch'
|
|
244
246
|
// | 'select'
|
|
245
|
-
| { type?: 'dialog' | 'drawer' | 'embed'; props?: Record<string, any
|
|
247
|
+
| { type?: 'dialog' | 'drawer' | 'embed' | 'select' | 'switch'; props?: Record<string, any>; key?: string };
|
|
246
248
|
// | { type: 'switch'; props?: Record<string, any> }
|
|
247
249
|
// | { type: 'select'; props?: Record<string, any> }
|
|
248
250
|
|