@nocobase/client-v2 2.2.0-alpha.1 → 2.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/PluginSettingsManager.d.ts +33 -0
- package/es/RouteRepository.d.ts +21 -6
- package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/TypedVariableInput.d.ts +22 -4
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +2 -0
- package/es/flow/actions/afterSuccess.d.ts +8 -1
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/components/FieldAssignValueInput.d.ts +2 -0
- package/es/flow/index.d.ts +1 -0
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
- package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
- package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
- package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
- package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
- package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
- package/es/flow/utils/formValueDeps.d.ts +32 -0
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/index.d.ts +2 -1
- package/es/index.mjs +233 -137
- package/lib/index.js +234 -138
- package/package.json +7 -7
- package/src/PluginSettingsManager.ts +53 -0
- package/src/RouteRepository.ts +126 -24
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/RouteRepository.test.ts +216 -0
- package/src/__tests__/browserChecker.test.ts +61 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +40 -1
- package/src/collection-manager/field-configure.ts +1 -1
- package/src/collection-manager/interfaces/id.ts +1 -1
- package/src/collection-manager/interfaces/m2m.tsx +2 -2
- package/src/collection-manager/interfaces/m2o.tsx +2 -2
- package/src/collection-manager/interfaces/nanoid.ts +1 -1
- package/src/collection-manager/interfaces/o2m.tsx +2 -2
- package/src/collection-manager/interfaces/obo.tsx +2 -2
- package/src/collection-manager/interfaces/oho.tsx +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +2 -2
- package/src/collection-manager/interfaces/uuid.ts +1 -1
- package/src/components/AppComponents.tsx +19 -3
- package/src/components/README.md +7 -1
- package/src/components/README.zh-CN.md +6 -1
- package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
- package/src/components/category-tabs/index.ts +10 -0
- package/src/components/form/JsonTextArea.tsx +21 -11
- package/src/components/form/TypedVariableInput.tsx +416 -93
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +266 -9
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +2 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +443 -5
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
- package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +58 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
- package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
- package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +104 -0
- package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
- package/src/flow/actions/afterSuccess.tsx +142 -3
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/dataScopeFilter.ts +10 -0
- package/src/flow/actions/dateTimeFormat.tsx +42 -28
- package/src/flow/actions/index.ts +1 -1
- package/src/flow/actions/linkageRules.tsx +123 -35
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
- package/src/flow/actions/openView.tsx +38 -4
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/admin-shell/BaseLayoutModel.tsx +25 -3
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +5 -2
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +13 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +34 -10
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +310 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +44 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
- package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +82 -0
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +166 -2
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
- package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
- package/src/flow/components/FieldAssignValueInput.tsx +10 -5
- package/src/flow/components/FlowRoute.tsx +56 -11
- package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
- package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
- package/src/flow/components/fieldAssignOptions.ts +155 -27
- package/src/flow/flows/editMarkdownFlow.tsx +1 -1
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/index.ts +2 -1
- package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
- package/src/flow/models/actions/JSActionModel.tsx +2 -7
- package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
- package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
- package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
- package/src/flow/models/base/ActionModel.tsx +12 -1
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
- package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +31 -3
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +77 -0
- package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
- package/src/flow/models/blocks/form/submitHandler.ts +17 -3
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +880 -102
- package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +257 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +2 -7
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +36 -13
- package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
- package/src/flow/models/blocks/table/TableColumnModel.tsx +36 -3
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +159 -1
- package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
- package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
- package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
- package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +29 -6
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +16 -4
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
- package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
- package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
- package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
- package/src/flow/models/fields/InputFieldModel.tsx +40 -2
- package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
- package/src/flow/models/fields/JSFieldModel.tsx +2 -7
- package/src/flow/models/fields/JSItemModel.tsx +2 -14
- package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
- package/src/flow/models/fields/TextareaFieldModel.tsx +49 -3
- package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
- package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
- package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +100 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +93 -10
- package/src/flow/resolveViewParamsToViewList.tsx +11 -3
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +258 -0
- package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
- package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
- package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
- package/src/flow/utils/formValueDeps.ts +170 -0
- package/src/flow-compat/FieldValidation.tsx +122 -60
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/index.ts +8 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/nocobase-buildin-plugin/index.tsx +44 -14
- package/src/settings-center/AdminSettingsLayout.tsx +10 -2
- package/src/settings-center/SystemSettingsPage.tsx +1 -2
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/plugin-manager/index.tsx +3 -0
- package/src/settings-center/utils.tsx +0 -6
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { ThunderboltOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
|
12
|
+
import { css } from '@emotion/css';
|
|
12
13
|
import {
|
|
13
14
|
ActionDefinition,
|
|
14
15
|
ActionScene,
|
|
16
|
+
FlowContext,
|
|
17
|
+
FlowContextProvider,
|
|
15
18
|
FlowEngineContext,
|
|
16
19
|
FlowEventPhase,
|
|
17
20
|
FlowModel,
|
|
@@ -31,8 +34,9 @@ import {
|
|
|
31
34
|
DetachedFlowRegistry,
|
|
32
35
|
replaceFlowRegistry,
|
|
33
36
|
serializeFlowRegistry,
|
|
37
|
+
type DynamicFlowSource,
|
|
34
38
|
} from '@nocobase/flow-engine';
|
|
35
|
-
import { Collapse, Input, Button, Space, Tooltip, Empty, Dropdown, Select } from 'antd';
|
|
39
|
+
import { Collapse, Input, Button, Space, Tooltip, Empty, Dropdown, Select, Tabs, theme } from 'antd';
|
|
36
40
|
import { uid } from '@formily/shared';
|
|
37
41
|
import { useUpdate } from 'ahooks';
|
|
38
42
|
import _ from 'lodash';
|
|
@@ -41,6 +45,15 @@ type FlowOnObject = Exclude<FlowDefinition['on'], string | undefined>;
|
|
|
41
45
|
type FlowRegistryAvailability = {
|
|
42
46
|
hasFlow(flowKey: string): boolean;
|
|
43
47
|
};
|
|
48
|
+
type ClosableView = {
|
|
49
|
+
close?: () => Promise<unknown> | unknown;
|
|
50
|
+
beforeClose?: (payload: Record<string, unknown>) => Promise<boolean | void> | boolean | void;
|
|
51
|
+
destroy?: () => void;
|
|
52
|
+
};
|
|
53
|
+
type DynamicFlowsEditorApi = {
|
|
54
|
+
hasUnsavedChanges: () => boolean;
|
|
55
|
+
save: () => Promise<boolean>;
|
|
56
|
+
};
|
|
44
57
|
|
|
45
58
|
function isFlowOnObject(on: FlowDefinition['on']): on is FlowOnObject {
|
|
46
59
|
return !!on && typeof on === 'object';
|
|
@@ -97,17 +110,36 @@ function validateFlowOnPhase(onObj: FlowOnObject): 'flowKey' | 'stepKey' | undef
|
|
|
97
110
|
}
|
|
98
111
|
}
|
|
99
112
|
|
|
113
|
+
const confirmDiscardUnsavedChanges = async (ctx: FlowEngineContext, t: (key: string) => string) => {
|
|
114
|
+
return (
|
|
115
|
+
(await ctx.modal?.confirm?.({
|
|
116
|
+
title: t('Unsaved changes'),
|
|
117
|
+
content: t("Are you sure you don't want to save?"),
|
|
118
|
+
okText: t('Confirm'),
|
|
119
|
+
cancelText: t('Cancel'),
|
|
120
|
+
})) ?? true
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
100
124
|
export const DynamicFlowsIcon: React.FC<{ model: FlowModel }> = (props) => {
|
|
101
125
|
const { model } = props;
|
|
102
126
|
const t = React.useMemo(() => model.translate.bind(model), [model]);
|
|
103
127
|
|
|
104
|
-
const handleClick = () => {
|
|
128
|
+
const handleClick = async () => {
|
|
105
129
|
const target = document.querySelector<HTMLDivElement>(`#${GLOBAL_EMBED_CONTAINER_ID}`);
|
|
106
130
|
|
|
107
131
|
if (!target) {
|
|
108
132
|
return;
|
|
109
133
|
}
|
|
110
134
|
|
|
135
|
+
const sources = await model.flowEngine.flowSettings.getDynamicFlowSources(model);
|
|
136
|
+
const content =
|
|
137
|
+
sources.length > 1 ? (
|
|
138
|
+
<DynamicFlowsSourceTabs sources={sources} />
|
|
139
|
+
) : (
|
|
140
|
+
<DynamicFlowsEditor model={sources[0].model} />
|
|
141
|
+
);
|
|
142
|
+
|
|
111
143
|
model.context.viewer.embed({
|
|
112
144
|
type: 'embed',
|
|
113
145
|
target,
|
|
@@ -124,13 +156,231 @@ export const DynamicFlowsIcon: React.FC<{ model: FlowModel }> = (props) => {
|
|
|
124
156
|
target.style.minWidth = 'auto';
|
|
125
157
|
}
|
|
126
158
|
},
|
|
127
|
-
content
|
|
159
|
+
content,
|
|
128
160
|
});
|
|
129
161
|
};
|
|
130
162
|
|
|
131
163
|
return <ThunderboltOutlined style={{ cursor: 'pointer' }} onClick={handleClick} />;
|
|
132
164
|
};
|
|
133
165
|
|
|
166
|
+
const DynamicFlowsSourceTabs = observer(({ sources }: { sources: DynamicFlowSource[] }) => {
|
|
167
|
+
const [activeKey, setActiveKey] = React.useState(sources[0]?.key);
|
|
168
|
+
const [submitLoading, setSubmitLoading] = React.useState(false);
|
|
169
|
+
const ctx = useFlowContext<FlowEngineContext>();
|
|
170
|
+
const { token } = theme.useToken();
|
|
171
|
+
const dirtySourceKeysRef = React.useRef(new Set<string>());
|
|
172
|
+
const editorApisRef = React.useRef(new Map<string, DynamicFlowsEditorApi>());
|
|
173
|
+
const t = React.useMemo(
|
|
174
|
+
() => sources[0]?.model.translate.bind(sources[0].model) || ((key: string) => key),
|
|
175
|
+
[sources],
|
|
176
|
+
);
|
|
177
|
+
const tabBarStyle = React.useMemo(
|
|
178
|
+
() => ({
|
|
179
|
+
paddingLeft: token.paddingLG,
|
|
180
|
+
paddingRight: token.paddingLG,
|
|
181
|
+
marginBottom: 0,
|
|
182
|
+
}),
|
|
183
|
+
[token.paddingLG],
|
|
184
|
+
);
|
|
185
|
+
const handleDirtyChange = React.useCallback((sourceKey: string, dirty: boolean) => {
|
|
186
|
+
if (dirty) {
|
|
187
|
+
dirtySourceKeysRef.current.add(sourceKey);
|
|
188
|
+
} else {
|
|
189
|
+
dirtySourceKeysRef.current.delete(sourceKey);
|
|
190
|
+
}
|
|
191
|
+
}, []);
|
|
192
|
+
const handleRegisterEditor = React.useCallback((sourceKey: string, api?: DynamicFlowsEditorApi) => {
|
|
193
|
+
if (api) {
|
|
194
|
+
editorApisRef.current.set(sourceKey, api);
|
|
195
|
+
} else {
|
|
196
|
+
editorApisRef.current.delete(sourceKey);
|
|
197
|
+
}
|
|
198
|
+
}, []);
|
|
199
|
+
const closeView = React.useCallback(async () => {
|
|
200
|
+
const view = ctx.view as ClosableView | undefined;
|
|
201
|
+
if (!view) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (typeof view.close === 'function') {
|
|
206
|
+
return (await view.close()) !== false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const allowed = await view.beforeClose?.({});
|
|
210
|
+
if (allowed === false) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
view.destroy?.();
|
|
215
|
+
return true;
|
|
216
|
+
}, [ctx.view]);
|
|
217
|
+
const handleSaveAllSources = React.useCallback(async () => {
|
|
218
|
+
setSubmitLoading(true);
|
|
219
|
+
try {
|
|
220
|
+
const orderedSources = [
|
|
221
|
+
...sources.filter((source) => source.key === activeKey),
|
|
222
|
+
...sources.filter((source) => source.key !== activeKey),
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
for (const source of orderedSources) {
|
|
226
|
+
const api = editorApisRef.current.get(source.key);
|
|
227
|
+
if (!api) {
|
|
228
|
+
if (dirtySourceKeysRef.current.has(source.key)) {
|
|
229
|
+
setActiveKey(source.key);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const shouldSave =
|
|
236
|
+
source.key === activeKey || dirtySourceKeysRef.current.has(source.key) || api.hasUnsavedChanges();
|
|
237
|
+
if (!shouldSave) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const saved = await api.save();
|
|
242
|
+
if (!saved) {
|
|
243
|
+
setActiveKey(source.key);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await closeView();
|
|
249
|
+
} finally {
|
|
250
|
+
setSubmitLoading(false);
|
|
251
|
+
}
|
|
252
|
+
}, [activeKey, closeView, sources]);
|
|
253
|
+
|
|
254
|
+
React.useEffect(() => {
|
|
255
|
+
const view = ctx.view;
|
|
256
|
+
if (!view) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const previousBeforeClose = view.beforeClose;
|
|
261
|
+
const beforeClose = async (payload) => {
|
|
262
|
+
if (dirtySourceKeysRef.current.size > 0) {
|
|
263
|
+
const confirmed = await confirmDiscardUnsavedChanges(ctx, t);
|
|
264
|
+
if (!confirmed) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const result = await previousBeforeClose?.(payload);
|
|
270
|
+
return result !== false;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
view.beforeClose = beforeClose;
|
|
274
|
+
|
|
275
|
+
return () => {
|
|
276
|
+
if (view.beforeClose === beforeClose) {
|
|
277
|
+
view.beforeClose = previousBeforeClose;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}, [ctx, t]);
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<Tabs
|
|
284
|
+
className={dynamicFlowsSourceTabsClass}
|
|
285
|
+
size="small"
|
|
286
|
+
activeKey={activeKey}
|
|
287
|
+
onChange={setActiveKey}
|
|
288
|
+
tabBarStyle={tabBarStyle}
|
|
289
|
+
items={sources.map((source) => ({
|
|
290
|
+
key: source.key,
|
|
291
|
+
label: source.label,
|
|
292
|
+
forceRender: true,
|
|
293
|
+
children: (
|
|
294
|
+
<DynamicFlowsSourceEditor
|
|
295
|
+
key={source.key}
|
|
296
|
+
source={source}
|
|
297
|
+
active={source.key === activeKey}
|
|
298
|
+
onDirtyChange={(dirty) => handleDirtyChange(source.key, dirty)}
|
|
299
|
+
onRegisterEditor={(api) => handleRegisterEditor(source.key, api)}
|
|
300
|
+
onSaveRequest={handleSaveAllSources}
|
|
301
|
+
submitLoading={submitLoading}
|
|
302
|
+
/>
|
|
303
|
+
),
|
|
304
|
+
}))}
|
|
305
|
+
/>
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
type DynamicFlowsSourceEditorProps = {
|
|
310
|
+
source: DynamicFlowSource;
|
|
311
|
+
active: boolean;
|
|
312
|
+
onDirtyChange: (dirty: boolean) => void;
|
|
313
|
+
onRegisterEditor: (api?: DynamicFlowsEditorApi) => void;
|
|
314
|
+
onSaveRequest: () => Promise<void>;
|
|
315
|
+
submitLoading: boolean;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const DynamicFlowsSourceEditor = observer(
|
|
319
|
+
({
|
|
320
|
+
source,
|
|
321
|
+
active,
|
|
322
|
+
onDirtyChange,
|
|
323
|
+
onRegisterEditor,
|
|
324
|
+
onSaveRequest,
|
|
325
|
+
submitLoading,
|
|
326
|
+
}: DynamicFlowsSourceEditorProps) => {
|
|
327
|
+
const hostCtx = useFlowContext<FlowEngineContext>();
|
|
328
|
+
const sourceContext = React.useMemo(() => {
|
|
329
|
+
const context = new FlowContext();
|
|
330
|
+
if (hostCtx instanceof FlowContext) {
|
|
331
|
+
context.addDelegate(hostCtx);
|
|
332
|
+
}
|
|
333
|
+
if (source.model.context instanceof FlowContext) {
|
|
334
|
+
context.addDelegate(source.model.context);
|
|
335
|
+
}
|
|
336
|
+
if (hostCtx?.view) {
|
|
337
|
+
context.defineProperty('view', { value: hostCtx.view });
|
|
338
|
+
}
|
|
339
|
+
if (hostCtx?.modal) {
|
|
340
|
+
context.defineProperty('modal', { value: hostCtx.modal });
|
|
341
|
+
}
|
|
342
|
+
return context;
|
|
343
|
+
}, [hostCtx, source.model]);
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
<FlowContextProvider context={sourceContext}>
|
|
347
|
+
<DynamicFlowsEditor
|
|
348
|
+
key={source.model.uid}
|
|
349
|
+
model={source.model}
|
|
350
|
+
active={active}
|
|
351
|
+
guardBeforeClose={false}
|
|
352
|
+
onDirtyChange={onDirtyChange}
|
|
353
|
+
onRegisterEditor={onRegisterEditor}
|
|
354
|
+
onSaveRequest={onSaveRequest}
|
|
355
|
+
submitLoading={submitLoading}
|
|
356
|
+
/>
|
|
357
|
+
</FlowContextProvider>
|
|
358
|
+
);
|
|
359
|
+
},
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const dynamicFlowsSourceTabsClass = css`
|
|
363
|
+
height: 100%;
|
|
364
|
+
display: flex;
|
|
365
|
+
flex-direction: column;
|
|
366
|
+
|
|
367
|
+
> .ant-tabs-nav {
|
|
368
|
+
margin-bottom: 0;
|
|
369
|
+
flex-shrink: 0;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
> .ant-tabs-content-holder {
|
|
373
|
+
flex: 1;
|
|
374
|
+
min-height: 0;
|
|
375
|
+
overflow: hidden;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
> .ant-tabs-content-holder > .ant-tabs-content,
|
|
379
|
+
> .ant-tabs-content-holder > .ant-tabs-content > .ant-tabs-tabpane {
|
|
380
|
+
height: 100%;
|
|
381
|
+
}
|
|
382
|
+
`;
|
|
383
|
+
|
|
134
384
|
const styles: Record<string, React.CSSProperties> = {
|
|
135
385
|
sectionHeader: {
|
|
136
386
|
display: 'flex',
|
|
@@ -165,6 +415,15 @@ const styles: Record<string, React.CSSProperties> = {
|
|
|
165
415
|
marginInlineStart: 2,
|
|
166
416
|
marginInlineEnd: 8,
|
|
167
417
|
},
|
|
418
|
+
editorFooter: {
|
|
419
|
+
display: 'flex',
|
|
420
|
+
justifyContent: 'flex-end',
|
|
421
|
+
gap: 8,
|
|
422
|
+
padding: '8px 16px',
|
|
423
|
+
borderTop: '1px solid #f0f0f0',
|
|
424
|
+
backgroundColor: '#fff',
|
|
425
|
+
flexShrink: 0,
|
|
426
|
+
},
|
|
168
427
|
};
|
|
169
428
|
|
|
170
429
|
const SectionHeader = ({ color, children }: { color: string; children: React.ReactNode }) => {
|
|
@@ -458,19 +717,39 @@ const EventConfigSection = observer(
|
|
|
458
717
|
},
|
|
459
718
|
);
|
|
460
719
|
|
|
461
|
-
|
|
720
|
+
type DynamicFlowsEditorProps = {
|
|
721
|
+
model: FlowModel;
|
|
722
|
+
active?: boolean;
|
|
723
|
+
guardBeforeClose?: boolean;
|
|
724
|
+
onDirtyChange?: (dirty: boolean) => void;
|
|
725
|
+
onRegisterEditor?: (api?: DynamicFlowsEditorApi) => void;
|
|
726
|
+
onSaveRequest?: () => Promise<void>;
|
|
727
|
+
submitLoading?: boolean;
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
const DynamicFlowsEditor = observer((props: DynamicFlowsEditorProps) => {
|
|
462
731
|
const { model } = props;
|
|
732
|
+
const active = props.active ?? true;
|
|
733
|
+
const guardBeforeClose = props.guardBeforeClose ?? true;
|
|
734
|
+
const onDirtyChange = props.onDirtyChange;
|
|
735
|
+
const onRegisterEditor = props.onRegisterEditor;
|
|
736
|
+
const onSaveRequest = props.onSaveRequest;
|
|
463
737
|
const ctx = useFlowContext<FlowEngineContext>();
|
|
464
738
|
const flowEngine = model.flowEngine;
|
|
465
739
|
const latestFlows = serializeFlowRegistry(model.flowRegistry);
|
|
466
740
|
const initialFlowsRef = React.useRef(latestFlows);
|
|
467
741
|
const [draftFlowRegistry] = React.useState(() => new DetachedFlowRegistry(latestFlows));
|
|
468
|
-
const [
|
|
742
|
+
const [internalSubmitLoading, setInternalSubmitLoading] = React.useState(false);
|
|
743
|
+
const submitLoading = props.submitLoading ?? internalSubmitLoading;
|
|
469
744
|
const t = React.useMemo(() => model.translate.bind(model), [model]);
|
|
470
745
|
const hasUnsavedChanges = React.useCallback(() => {
|
|
471
746
|
return !_.isEqual(initialFlowsRef.current, serializeFlowRegistry(draftFlowRegistry));
|
|
472
747
|
}, [draftFlowRegistry]);
|
|
473
748
|
|
|
749
|
+
React.useEffect(() => {
|
|
750
|
+
onDirtyChange?.(hasUnsavedChanges());
|
|
751
|
+
});
|
|
752
|
+
|
|
474
753
|
React.useEffect(() => {
|
|
475
754
|
if (_.isEqual(initialFlowsRef.current, latestFlows) || hasUnsavedChanges()) {
|
|
476
755
|
return;
|
|
@@ -481,17 +760,19 @@ const DynamicFlowsEditor = observer((props: { model: FlowModel }) => {
|
|
|
481
760
|
}, [draftFlowRegistry, hasUnsavedChanges, latestFlows]);
|
|
482
761
|
|
|
483
762
|
React.useEffect(() => {
|
|
763
|
+
if (!guardBeforeClose) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
|
|
484
767
|
const view = ctx.view;
|
|
768
|
+
if (!view) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
485
772
|
const previousBeforeClose = view.beforeClose;
|
|
486
773
|
const beforeClose = async (payload) => {
|
|
487
774
|
if (hasUnsavedChanges()) {
|
|
488
|
-
const confirmed =
|
|
489
|
-
(await ctx.modal?.confirm?.({
|
|
490
|
-
title: t('Unsaved changes'),
|
|
491
|
-
content: t("Are you sure you don't want to save?"),
|
|
492
|
-
okText: t('Confirm'),
|
|
493
|
-
cancelText: t('Cancel'),
|
|
494
|
-
})) ?? true;
|
|
775
|
+
const confirmed = await confirmDiscardUnsavedChanges(ctx, t);
|
|
495
776
|
|
|
496
777
|
if (!confirmed) {
|
|
497
778
|
return false;
|
|
@@ -509,7 +790,7 @@ const DynamicFlowsEditor = observer((props: { model: FlowModel }) => {
|
|
|
509
790
|
view.beforeClose = previousBeforeClose;
|
|
510
791
|
}
|
|
511
792
|
};
|
|
512
|
-
}, [ctx, hasUnsavedChanges, t]);
|
|
793
|
+
}, [ctx, guardBeforeClose, hasUnsavedChanges, t]);
|
|
513
794
|
|
|
514
795
|
// 添加新流
|
|
515
796
|
const handleAddFlow = () => {
|
|
@@ -677,6 +958,152 @@ const DynamicFlowsEditor = observer((props: { model: FlowModel }) => {
|
|
|
677
958
|
),
|
|
678
959
|
};
|
|
679
960
|
});
|
|
961
|
+
const closeView = React.useCallback(async () => {
|
|
962
|
+
const view = ctx.view as ClosableView | undefined;
|
|
963
|
+
|
|
964
|
+
if (!view) {
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (typeof view.close === 'function') {
|
|
969
|
+
return (await view.close()) !== false;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const allowed = await view.beforeClose?.({});
|
|
973
|
+
if (allowed === false) {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
view.destroy?.();
|
|
978
|
+
return true;
|
|
979
|
+
}, [ctx.view]);
|
|
980
|
+
const saveDraft = React.useCallback(async () => {
|
|
981
|
+
setInternalSubmitLoading(true);
|
|
982
|
+
const invalid = draftFlowRegistry
|
|
983
|
+
.mapFlows((flow) => {
|
|
984
|
+
if (!isFlowOnObject(flow.on)) return;
|
|
985
|
+
normalizeFlowOnPhase(flow.on);
|
|
986
|
+
const invalidType = validateFlowOnPhase(flow.on);
|
|
987
|
+
if (!invalidType) return;
|
|
988
|
+
return { type: invalidType };
|
|
989
|
+
})
|
|
990
|
+
.filter(Boolean)[0] as { type: 'flowKey' | 'stepKey' } | undefined;
|
|
991
|
+
|
|
992
|
+
if (invalid) {
|
|
993
|
+
const msg =
|
|
994
|
+
invalid.type === 'flowKey' ? t('Please select a built-in flow') : t('Please select a built-in flow step');
|
|
995
|
+
model.context?.message?.error?.(msg);
|
|
996
|
+
setInternalSubmitLoading(false);
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
const previousFlows = serializeFlowRegistry(model.flowRegistry);
|
|
1000
|
+
const afterSaves: Array<() => Promise<void>> = [];
|
|
1001
|
+
|
|
1002
|
+
try {
|
|
1003
|
+
for (const flow of draftFlowRegistry.mapFlows((it) => it)) {
|
|
1004
|
+
for (const step of flow.mapSteps((it) => it)) {
|
|
1005
|
+
const serialized = step.serialize();
|
|
1006
|
+
const actionDef = step.use ? model.getAction(step.use) : undefined;
|
|
1007
|
+
|
|
1008
|
+
const beforeParamsSave = serialized.beforeParamsSave || actionDef?.beforeParamsSave;
|
|
1009
|
+
const afterParamsSave = serialized.afterParamsSave || actionDef?.afterParamsSave;
|
|
1010
|
+
|
|
1011
|
+
if (typeof beforeParamsSave !== 'function' && typeof afterParamsSave !== 'function') {
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
const runtimeCtx = new FlowRuntimeContext(model, flow.key, 'settings');
|
|
1016
|
+
setupRuntimeContextSteps(runtimeCtx, flow.steps, model, flow.key);
|
|
1017
|
+
runtimeCtx.defineProperty('currentStep', { value: serialized });
|
|
1018
|
+
|
|
1019
|
+
const currentValues = { ...(step.defaultParams || {}) };
|
|
1020
|
+
const previousParams = { ...(step.defaultParams || {}) };
|
|
1021
|
+
|
|
1022
|
+
if (typeof beforeParamsSave === 'function') {
|
|
1023
|
+
await beforeParamsSave(runtimeCtx as any, currentValues, previousParams);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (typeof afterParamsSave === 'function') {
|
|
1027
|
+
afterSaves.push(async () => {
|
|
1028
|
+
await afterParamsSave(runtimeCtx as any, currentValues, previousParams);
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
step.defaultParams = currentValues;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
replaceFlowRegistry(model.flowRegistry, serializeFlowRegistry(draftFlowRegistry));
|
|
1037
|
+
await model.flowRegistry.save();
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
replaceFlowRegistry(model.flowRegistry, previousFlows);
|
|
1040
|
+
setInternalSubmitLoading(false);
|
|
1041
|
+
model.context?.message?.error?.('Steps post-save hooks failed to run');
|
|
1042
|
+
console.error(error);
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
initialFlowsRef.current = serializeFlowRegistry(model.flowRegistry);
|
|
1047
|
+
|
|
1048
|
+
try {
|
|
1049
|
+
for (const runAfterSave of afterSaves) {
|
|
1050
|
+
await runAfterSave();
|
|
1051
|
+
}
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
model.context?.message?.error?.('Steps post-save hooks failed to run');
|
|
1054
|
+
console.error(error);
|
|
1055
|
+
}
|
|
1056
|
+
// 保存事件流定义后,失效 beforeRender 缓存并触发一次重跑,确保改动立刻生效
|
|
1057
|
+
const beforeRenderFlows = model.flowRegistry
|
|
1058
|
+
.mapFlows((flow) => {
|
|
1059
|
+
if (isBeforeRenderFlow(flow)) {
|
|
1060
|
+
return flow;
|
|
1061
|
+
}
|
|
1062
|
+
})
|
|
1063
|
+
.filter(Boolean);
|
|
1064
|
+
if (beforeRenderFlows.length > 0) {
|
|
1065
|
+
model.rerender(); // 不阻塞,后续保存
|
|
1066
|
+
}
|
|
1067
|
+
setInternalSubmitLoading(false);
|
|
1068
|
+
onDirtyChange?.(false);
|
|
1069
|
+
model.context?.message?.success?.(t('Configuration saved'));
|
|
1070
|
+
return true;
|
|
1071
|
+
}, [draftFlowRegistry, model, onDirtyChange, t]);
|
|
1072
|
+
React.useEffect(() => {
|
|
1073
|
+
if (!onRegisterEditor) {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
onRegisterEditor({
|
|
1077
|
+
hasUnsavedChanges,
|
|
1078
|
+
save: saveDraft,
|
|
1079
|
+
});
|
|
1080
|
+
return () => {
|
|
1081
|
+
onRegisterEditor();
|
|
1082
|
+
};
|
|
1083
|
+
}, [hasUnsavedChanges, onRegisterEditor, saveDraft]);
|
|
1084
|
+
|
|
1085
|
+
const footerButtons = (
|
|
1086
|
+
<Space>
|
|
1087
|
+
<Button onClick={closeView}>{t('Cancel')}</Button>
|
|
1088
|
+
<Button
|
|
1089
|
+
type="primary"
|
|
1090
|
+
loading={submitLoading}
|
|
1091
|
+
onClick={async () => {
|
|
1092
|
+
if (onSaveRequest) {
|
|
1093
|
+
await onSaveRequest();
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
const saved = await saveDraft();
|
|
1097
|
+
if (saved) {
|
|
1098
|
+
await closeView();
|
|
1099
|
+
}
|
|
1100
|
+
}}
|
|
1101
|
+
>
|
|
1102
|
+
{t('Save')}
|
|
1103
|
+
</Button>
|
|
1104
|
+
</Space>
|
|
1105
|
+
);
|
|
1106
|
+
const ViewFooter = ctx.view?.Footer;
|
|
680
1107
|
|
|
681
1108
|
return (
|
|
682
1109
|
<div
|
|
@@ -720,119 +1147,13 @@ const DynamicFlowsEditor = observer((props: { model: FlowModel }) => {
|
|
|
720
1147
|
</Button>
|
|
721
1148
|
</div>
|
|
722
1149
|
|
|
723
|
-
{
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
borderTop: '1px solid #f0f0f0',
|
|
731
|
-
backgroundColor: '#fff',
|
|
732
|
-
flexShrink: 0,
|
|
733
|
-
}}
|
|
734
|
-
>
|
|
735
|
-
<Button onClick={() => ctx.view.close()}>{t('Cancel')}</Button>
|
|
736
|
-
<Button
|
|
737
|
-
type="primary"
|
|
738
|
-
loading={submitLoading}
|
|
739
|
-
onClick={async () => {
|
|
740
|
-
setSubmitLoading(true);
|
|
741
|
-
const invalid = draftFlowRegistry
|
|
742
|
-
.mapFlows((flow) => {
|
|
743
|
-
if (!isFlowOnObject(flow.on)) return;
|
|
744
|
-
normalizeFlowOnPhase(flow.on);
|
|
745
|
-
const invalidType = validateFlowOnPhase(flow.on);
|
|
746
|
-
if (!invalidType) return;
|
|
747
|
-
return { type: invalidType };
|
|
748
|
-
})
|
|
749
|
-
.filter(Boolean)[0] as { type: 'flowKey' | 'stepKey' } | undefined;
|
|
750
|
-
|
|
751
|
-
if (invalid) {
|
|
752
|
-
const msg =
|
|
753
|
-
invalid.type === 'flowKey'
|
|
754
|
-
? t('Please select a built-in flow')
|
|
755
|
-
: t('Please select a built-in flow step');
|
|
756
|
-
model.context?.message?.error?.(msg);
|
|
757
|
-
setSubmitLoading(false);
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
const previousFlows = serializeFlowRegistry(model.flowRegistry);
|
|
761
|
-
const afterSaves: Array<() => Promise<void>> = [];
|
|
762
|
-
|
|
763
|
-
try {
|
|
764
|
-
for (const flow of draftFlowRegistry.mapFlows((it) => it)) {
|
|
765
|
-
for (const step of flow.mapSteps((it) => it)) {
|
|
766
|
-
const serialized = step.serialize();
|
|
767
|
-
const actionDef = step.use ? model.getAction(step.use) : undefined;
|
|
768
|
-
|
|
769
|
-
const beforeParamsSave = serialized.beforeParamsSave || actionDef?.beforeParamsSave;
|
|
770
|
-
const afterParamsSave = serialized.afterParamsSave || actionDef?.afterParamsSave;
|
|
771
|
-
|
|
772
|
-
if (typeof beforeParamsSave !== 'function' && typeof afterParamsSave !== 'function') {
|
|
773
|
-
continue;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const runtimeCtx = new FlowRuntimeContext(model, flow.key, 'settings');
|
|
777
|
-
setupRuntimeContextSteps(runtimeCtx, flow.steps, model, flow.key);
|
|
778
|
-
runtimeCtx.defineProperty('currentStep', { value: serialized });
|
|
779
|
-
|
|
780
|
-
const currentValues = { ...(step.defaultParams || {}) };
|
|
781
|
-
const previousParams = { ...(step.defaultParams || {}) };
|
|
782
|
-
|
|
783
|
-
if (typeof beforeParamsSave === 'function') {
|
|
784
|
-
await beforeParamsSave(runtimeCtx as any, currentValues, previousParams);
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
if (typeof afterParamsSave === 'function') {
|
|
788
|
-
afterSaves.push(async () => {
|
|
789
|
-
await afterParamsSave(runtimeCtx as any, currentValues, previousParams);
|
|
790
|
-
});
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
step.defaultParams = currentValues;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
replaceFlowRegistry(model.flowRegistry, serializeFlowRegistry(draftFlowRegistry));
|
|
798
|
-
await model.flowRegistry.save();
|
|
799
|
-
} catch (error) {
|
|
800
|
-
replaceFlowRegistry(model.flowRegistry, previousFlows);
|
|
801
|
-
setSubmitLoading(false);
|
|
802
|
-
model.context?.message?.error?.('Steps post-save hooks failed to run');
|
|
803
|
-
console.error(error);
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
initialFlowsRef.current = serializeFlowRegistry(model.flowRegistry);
|
|
808
|
-
|
|
809
|
-
try {
|
|
810
|
-
for (const runAfterSave of afterSaves) {
|
|
811
|
-
await runAfterSave();
|
|
812
|
-
}
|
|
813
|
-
} catch (error) {
|
|
814
|
-
model.context?.message?.error?.('Steps post-save hooks failed to run');
|
|
815
|
-
console.error(error);
|
|
816
|
-
}
|
|
817
|
-
// 保存事件流定义后,失效 beforeRender 缓存并触发一次重跑,确保改动立刻生效
|
|
818
|
-
const beforeRenderFlows = model.flowRegistry
|
|
819
|
-
.mapFlows((flow) => {
|
|
820
|
-
if (isBeforeRenderFlow(flow)) {
|
|
821
|
-
return flow;
|
|
822
|
-
}
|
|
823
|
-
})
|
|
824
|
-
.filter(Boolean);
|
|
825
|
-
if (beforeRenderFlows.length > 0) {
|
|
826
|
-
model.rerender(); // 不阻塞,后续保存
|
|
827
|
-
}
|
|
828
|
-
setSubmitLoading(false);
|
|
829
|
-
model.context?.message?.success?.(t('Configuration saved'));
|
|
830
|
-
ctx.view.destroy();
|
|
831
|
-
}}
|
|
832
|
-
>
|
|
833
|
-
{t('Save')}
|
|
834
|
-
</Button>
|
|
835
|
-
</div>
|
|
1150
|
+
{active ? (
|
|
1151
|
+
ViewFooter ? (
|
|
1152
|
+
<ViewFooter>{footerButtons}</ViewFooter>
|
|
1153
|
+
) : (
|
|
1154
|
+
<div style={styles.editorFooter}>{footerButtons}</div>
|
|
1155
|
+
)
|
|
1156
|
+
) : null}
|
|
836
1157
|
</div>
|
|
837
1158
|
);
|
|
838
1159
|
});
|