@nocobase/flow-engine 2.1.0-alpha.3 → 2.1.0-alpha.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -661
- package/README.md +79 -10
- package/lib/JSRunner.d.ts +10 -1
- package/lib/JSRunner.js +50 -5
- package/lib/ViewScopedFlowEngine.js +5 -1
- package/lib/components/FieldModelRenderer.js +2 -2
- package/lib/components/FlowModelRenderer.d.ts +3 -1
- package/lib/components/FlowModelRenderer.js +12 -6
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
- package/lib/components/dnd/gridDragPlanner.js +601 -21
- package/lib/components/dnd/index.d.ts +19 -1
- package/lib/components/dnd/index.js +243 -23
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +68 -10
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
- package/lib/components/subModel/AddSubModelButton.js +27 -1
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +1 -1
- package/lib/components/subModel/utils.js +2 -2
- package/lib/data-source/index.d.ts +73 -0
- package/lib/data-source/index.js +211 -1
- package/lib/executor/FlowExecutor.js +31 -8
- package/lib/flowContext.d.ts +2 -0
- package/lib/flowContext.js +31 -1
- package/lib/flowEngine.d.ts +151 -1
- package/lib/flowEngine.js +389 -15
- package/lib/flowI18n.js +2 -1
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +34 -6
- package/lib/lazy-helper.d.ts +14 -0
- package/lib/lazy-helper.js +71 -0
- package/lib/locale/en-US.json +1 -0
- package/lib/locale/index.d.ts +2 -0
- package/lib/locale/zh-CN.json +1 -0
- package/lib/models/DisplayItemModel.d.ts +1 -1
- package/lib/models/EditableItemModel.d.ts +1 -1
- package/lib/models/FilterableItemModel.d.ts +1 -1
- package/lib/models/flowModel.d.ts +13 -10
- package/lib/models/flowModel.js +78 -18
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +20 -12
- package/lib/runjs-context/snippets/index.js +13 -2
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
- package/lib/scheduler/ModelOperationScheduler.js +3 -2
- package/lib/types.d.ts +47 -1
- package/lib/utils/createCollectionContextMeta.js +6 -2
- package/lib/utils/index.d.ts +2 -2
- package/lib/utils/index.js +4 -0
- package/lib/utils/parsePathnameToViewParams.js +1 -1
- package/lib/utils/runjsTemplateCompat.js +1 -1
- package/lib/utils/runjsValue.js +41 -11
- package/lib/utils/schema-utils.d.ts +7 -1
- package/lib/utils/schema-utils.js +19 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +20 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +20 -3
- package/lib/views/usePage.d.ts +2 -1
- package/lib/views/usePage.js +10 -3
- package/package.json +6 -5
- package/src/JSRunner.ts +68 -4
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +27 -1
- package/src/__tests__/flow-engine.test.ts +166 -0
- package/src/__tests__/flowContext.test.ts +65 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/objectVariable.test.ts +24 -0
- package/src/__tests__/provider.test.tsx +24 -2
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +16 -0
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
- package/src/components/FieldModelRenderer.tsx +2 -1
- package/src/components/FlowModelRenderer.tsx +18 -6
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
- package/src/components/__tests__/dnd.test.ts +44 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
- package/src/components/__tests__/gridDragPlanner.test.ts +512 -3
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +743 -19
- package/src/components/dnd/index.tsx +291 -27
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +88 -10
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +189 -3
- package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
- package/src/components/subModel/AddSubModelButton.tsx +32 -2
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +142 -32
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +1 -1
- package/src/data-source/__tests__/index.test.ts +34 -1
- package/src/data-source/index.ts +258 -2
- package/src/executor/FlowExecutor.ts +34 -9
- package/src/executor/__tests__/flowExecutor.test.ts +57 -0
- package/src/flowContext.ts +37 -3
- package/src/flowEngine.ts +445 -11
- package/src/flowI18n.ts +2 -1
- package/src/flowSettings.ts +40 -6
- package/src/lazy-helper.tsx +57 -0
- package/src/locale/en-US.json +1 -0
- package/src/locale/zh-CN.json +1 -0
- package/src/models/DisplayItemModel.tsx +1 -1
- package/src/models/EditableItemModel.tsx +1 -1
- package/src/models/FilterableItemModel.tsx +1 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
- package/src/models/__tests__/flowModel.test.ts +19 -3
- package/src/models/flowModel.tsx +119 -33
- package/src/provider.tsx +41 -25
- package/src/reactive/__tests__/observer.test.tsx +82 -0
- package/src/reactive/observer.tsx +87 -25
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +22 -12
- package/src/runjs-context/snippets/index.ts +12 -1
- package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
- package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
- package/src/scheduler/ModelOperationScheduler.ts +14 -3
- package/src/types.ts +60 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
- package/src/utils/__tests__/runjsValue.test.ts +11 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/createCollectionContextMeta.ts +6 -2
- package/src/utils/index.ts +2 -1
- package/src/utils/parsePathnameToViewParams.ts +2 -2
- package/src/utils/runjsTemplateCompat.ts +1 -1
- package/src/utils/runjsValue.ts +50 -11
- package/src/utils/schema-utils.ts +30 -1
- package/src/views/FlowView.tsx +11 -1
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +25 -3
- package/src/views/useDrawer.tsx +25 -3
- package/src/views/usePage.tsx +12 -3
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
|
11
|
+
import { createPortal } from 'react-dom';
|
|
11
12
|
import { Alert, Space } from 'antd';
|
|
12
13
|
import { css } from '@emotion/css';
|
|
13
14
|
import { FlowModel } from '../../../../models';
|
|
@@ -18,16 +19,270 @@ import { FlowEngine } from '../../../../flowEngine';
|
|
|
18
19
|
import { getT } from '../../../../utils';
|
|
19
20
|
import { useFlowContext } from '../../../..';
|
|
20
21
|
import { observer } from '../../../../reactive';
|
|
22
|
+
import {
|
|
23
|
+
omitToolbarPortalInsetStyle,
|
|
24
|
+
ToolbarPortalRect,
|
|
25
|
+
ToolbarPortalRenderSnapshot,
|
|
26
|
+
useFloatToolbarPortal,
|
|
27
|
+
} from './useFloatToolbarPortal';
|
|
28
|
+
import { useFloatToolbarVisibility } from './useFloatToolbarVisibility';
|
|
29
|
+
|
|
30
|
+
type ToolbarPosition = 'inside' | 'above' | 'below';
|
|
31
|
+
const TOOLBAR_ITEM_WIDTH = 19;
|
|
32
|
+
const DEFAULT_POPUP_BASE_Z_INDEX = 1000;
|
|
33
|
+
|
|
34
|
+
interface BaseFloatContextMenuProps {
|
|
35
|
+
children?: React.ReactNode;
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
showDeleteButton?: boolean;
|
|
38
|
+
showCopyUidButton?: boolean;
|
|
39
|
+
containerStyle?: React.CSSProperties;
|
|
40
|
+
/** 自定义工具栏样式,`top/left/right/bottom` 会作为 portal overlay 的 inset 使用。 */
|
|
41
|
+
toolbarStyle?: React.CSSProperties;
|
|
42
|
+
className?: string;
|
|
43
|
+
/**
|
|
44
|
+
* @default true
|
|
45
|
+
*/
|
|
46
|
+
showBorder?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* @default true
|
|
49
|
+
*/
|
|
50
|
+
showBackground?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
showTitle?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* @default false
|
|
57
|
+
*/
|
|
58
|
+
showDragHandle?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Settings menu levels: 1=current model only (default), 2=include sub-models
|
|
61
|
+
*/
|
|
62
|
+
settingsMenuLevel?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Extra toolbar items to add to this context menu instance
|
|
65
|
+
*/
|
|
66
|
+
extraToolbarItems?: ToolbarItemConfig[];
|
|
67
|
+
/**
|
|
68
|
+
* @default true
|
|
69
|
+
*/
|
|
70
|
+
showDynamicFlowsEditor?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* @default 'inside'
|
|
73
|
+
*/
|
|
74
|
+
toolbarPosition?: ToolbarPosition;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const getFloatMenuInstanceId = (model?: FlowModel | null) => {
|
|
78
|
+
if (!model) {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const forkId = (model as any)?.isFork ? (model as any)?.forkId : undefined;
|
|
83
|
+
return forkId == null || forkId === '' ? String(model.uid || '') : `${String(model.uid || '')}::${String(forkId)}`;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const hostContainerStyles = css`
|
|
87
|
+
position: relative;
|
|
88
|
+
|
|
89
|
+
&.has-button-child {
|
|
90
|
+
display: inline-block;
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
const toolbarPositionClassNames: Record<ToolbarPosition, string> = {
|
|
95
|
+
inside: 'nb-toolbar-position-inside',
|
|
96
|
+
above: 'nb-toolbar-position-above',
|
|
97
|
+
below: 'nb-toolbar-position-below',
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const toolbarContainerStyles = ({
|
|
101
|
+
showBackground,
|
|
102
|
+
showBorder,
|
|
103
|
+
ctx,
|
|
104
|
+
toolbarZIndex,
|
|
105
|
+
}: {
|
|
106
|
+
showBackground: boolean;
|
|
107
|
+
showBorder: boolean;
|
|
108
|
+
ctx: any;
|
|
109
|
+
toolbarZIndex: number;
|
|
110
|
+
}) => css`
|
|
111
|
+
z-index: ${toolbarZIndex};
|
|
112
|
+
opacity: 0;
|
|
113
|
+
pointer-events: none;
|
|
114
|
+
overflow: visible;
|
|
115
|
+
transition: opacity 0.12s ease;
|
|
116
|
+
background: ${showBackground ? 'var(--colorBgSettingsHover)' : 'transparent'};
|
|
117
|
+
border: ${showBorder ? '2px solid var(--colorBorderSettingsHover)' : 'none'};
|
|
118
|
+
border-radius: ${ctx.themeToken.borderRadiusLG}px;
|
|
119
|
+
|
|
120
|
+
&.nb-toolbar-visible {
|
|
121
|
+
opacity: 1;
|
|
122
|
+
transition-delay: 0.1s;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
&.nb-toolbar-portal {
|
|
126
|
+
top: 0;
|
|
127
|
+
left: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
&.nb-toolbar-portal-fixed {
|
|
131
|
+
position: fixed;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&.nb-toolbar-portal-absolute {
|
|
135
|
+
position: absolute;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
&.nb-in-template {
|
|
139
|
+
background: var(--colorTemplateBgSettingsHover);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
> .nb-toolbar-container-title {
|
|
143
|
+
pointer-events: none;
|
|
144
|
+
position: absolute;
|
|
145
|
+
top: 2px;
|
|
146
|
+
left: 2px;
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
gap: 4px;
|
|
150
|
+
height: 16px;
|
|
151
|
+
padding: 0;
|
|
152
|
+
font-size: 12px;
|
|
153
|
+
line-height: 16px;
|
|
154
|
+
border-bottom-right-radius: 2px;
|
|
155
|
+
border-radius: 2px;
|
|
156
|
+
|
|
157
|
+
.title-tag {
|
|
158
|
+
display: inline-flex;
|
|
159
|
+
padding: 0 3px;
|
|
160
|
+
border-radius: 2px;
|
|
161
|
+
background: var(--colorSettings);
|
|
162
|
+
color: #fff;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
21
165
|
|
|
22
|
-
|
|
166
|
+
> .nb-toolbar-container-icons {
|
|
167
|
+
display: none;
|
|
168
|
+
position: absolute;
|
|
169
|
+
right: 2px;
|
|
170
|
+
line-height: 16px;
|
|
171
|
+
pointer-events: all;
|
|
172
|
+
|
|
173
|
+
&.nb-toolbar-position-inside {
|
|
174
|
+
top: 2px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
&.nb-toolbar-position-above {
|
|
178
|
+
top: 0;
|
|
179
|
+
transform: translateY(-100%);
|
|
180
|
+
padding-bottom: 0;
|
|
181
|
+
margin-bottom: -2px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
&.nb-toolbar-position-below {
|
|
185
|
+
top: 0;
|
|
186
|
+
transform: translateY(100%);
|
|
187
|
+
padding-top: 2px;
|
|
188
|
+
margin-top: -2px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.ant-space-item {
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
justify-content: center;
|
|
195
|
+
width: 16px;
|
|
196
|
+
height: 16px;
|
|
197
|
+
padding: 2px;
|
|
198
|
+
line-height: 16px;
|
|
199
|
+
background-color: var(--colorSettings);
|
|
200
|
+
color: #fff;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
&.nb-toolbar-visible > .nb-toolbar-container-icons {
|
|
205
|
+
display: block;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
> .resize-handle {
|
|
209
|
+
position: absolute;
|
|
210
|
+
display: flex;
|
|
211
|
+
align-items: center;
|
|
212
|
+
justify-content: center;
|
|
213
|
+
pointer-events: all;
|
|
214
|
+
opacity: 0.6;
|
|
215
|
+
border-radius: 4px;
|
|
216
|
+
background: var(--colorSettings);
|
|
217
|
+
|
|
218
|
+
&:hover {
|
|
219
|
+
opacity: 0.9;
|
|
220
|
+
background: var(--colorSettingsHover, var(--colorSettings));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
&::before {
|
|
224
|
+
content: '';
|
|
225
|
+
position: absolute;
|
|
226
|
+
border-radius: 50%;
|
|
227
|
+
background: rgba(255, 255, 255, 0.9);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
&::after {
|
|
231
|
+
content: '';
|
|
232
|
+
position: absolute;
|
|
233
|
+
border-radius: 50%;
|
|
234
|
+
background: rgba(255, 255, 255, 0.9);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
> .resize-handle-left {
|
|
239
|
+
top: 50%;
|
|
240
|
+
left: -4px;
|
|
241
|
+
width: 6px;
|
|
242
|
+
height: 20px;
|
|
243
|
+
cursor: ew-resize;
|
|
244
|
+
transform: translateY(-50%);
|
|
245
|
+
|
|
246
|
+
&::before {
|
|
247
|
+
top: 6px;
|
|
248
|
+
left: 50%;
|
|
249
|
+
width: 2px;
|
|
250
|
+
height: 2px;
|
|
251
|
+
transform: translateX(-50%);
|
|
252
|
+
box-shadow:
|
|
253
|
+
0 4px 0 rgba(255, 255, 255, 0.9),
|
|
254
|
+
0 8px 0 rgba(255, 255, 255, 0.9);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
> .resize-handle-right {
|
|
259
|
+
top: 50%;
|
|
260
|
+
right: -4px;
|
|
261
|
+
width: 6px;
|
|
262
|
+
height: 20px;
|
|
263
|
+
cursor: ew-resize;
|
|
264
|
+
transform: translateY(-50%);
|
|
265
|
+
|
|
266
|
+
&::before {
|
|
267
|
+
top: 6px;
|
|
268
|
+
left: 50%;
|
|
269
|
+
width: 2px;
|
|
270
|
+
height: 2px;
|
|
271
|
+
transform: translateX(-50%);
|
|
272
|
+
box-shadow:
|
|
273
|
+
0 4px 0 rgba(255, 255, 255, 0.9),
|
|
274
|
+
0 8px 0 rgba(255, 255, 255, 0.9);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
`;
|
|
278
|
+
|
|
279
|
+
// 检测直接子节点里是否有按钮,保留原来的 inline-block 兼容行为。
|
|
23
280
|
const detectButtonInDOM = (container: HTMLElement): boolean => {
|
|
24
281
|
if (!container) return false;
|
|
25
282
|
|
|
26
|
-
// 只检测直接子元素中的button
|
|
27
283
|
const directChildren = container.children;
|
|
28
284
|
for (let i = 0; i < directChildren.length; i++) {
|
|
29
285
|
const child = directChildren[i];
|
|
30
|
-
// 检查是否是button元素或具有button特征的元素
|
|
31
286
|
if (child.tagName === 'BUTTON' || child.getAttribute('role') === 'button' || child.classList.contains('ant-btn')) {
|
|
32
287
|
return true;
|
|
33
288
|
}
|
|
@@ -36,436 +291,192 @@ const detectButtonInDOM = (container: HTMLElement): boolean => {
|
|
|
36
291
|
return false;
|
|
37
292
|
};
|
|
38
293
|
|
|
39
|
-
//
|
|
294
|
+
// 渲染工具栏项目,并让设置菜单与工具栏共享同一个 popup 容器。
|
|
40
295
|
const renderToolbarItems = (
|
|
41
296
|
model: FlowModel,
|
|
297
|
+
modelInstanceId: string,
|
|
42
298
|
showDeleteButton: boolean,
|
|
43
299
|
showCopyUidButton: boolean,
|
|
44
300
|
flowEngine: FlowEngine,
|
|
45
301
|
settingsMenuLevel?: number,
|
|
46
302
|
extraToolbarItems?: ToolbarItemConfig[],
|
|
303
|
+
showDynamicFlowsEditor = true,
|
|
304
|
+
onSettingsMenuOpenChange?: (open: boolean) => void,
|
|
305
|
+
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement,
|
|
47
306
|
) => {
|
|
48
307
|
const toolbarItems = flowEngine?.flowSettings?.getToolbarItems?.() || [];
|
|
49
|
-
|
|
50
|
-
// 合并额外的工具栏项目
|
|
51
308
|
const allToolbarItems = [...toolbarItems, ...(extraToolbarItems || [])];
|
|
52
309
|
|
|
53
|
-
// 按 sort 字段排序
|
|
54
310
|
allToolbarItems.sort((a, b) => (a.sort || 0) - (b.sort || 0)).reverse();
|
|
55
311
|
|
|
56
312
|
return allToolbarItems
|
|
57
313
|
.filter((itemConfig: ToolbarItemConfig) => {
|
|
58
|
-
|
|
314
|
+
if (itemConfig.key === 'dynamic-flows-editor' && showDynamicFlowsEditor === false) {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
59
317
|
return itemConfig.visible ? itemConfig.visible(model) : true;
|
|
60
318
|
})
|
|
61
319
|
.map((itemConfig: ToolbarItemConfig) => {
|
|
62
|
-
// 渲染项目组件
|
|
63
320
|
const ItemComponent = itemConfig.component;
|
|
64
321
|
|
|
65
|
-
// 对于默认设置项目,传递额外的 props
|
|
66
322
|
if (itemConfig.key === 'settings-menu') {
|
|
67
323
|
return (
|
|
68
324
|
<ItemComponent
|
|
69
325
|
key={itemConfig.key}
|
|
70
326
|
model={model}
|
|
71
|
-
id={
|
|
327
|
+
id={modelInstanceId}
|
|
72
328
|
showDeleteButton={showDeleteButton}
|
|
73
329
|
showCopyUidButton={showCopyUidButton}
|
|
74
330
|
menuLevels={settingsMenuLevel}
|
|
331
|
+
onDropdownVisibleChange={onSettingsMenuOpenChange}
|
|
332
|
+
getPopupContainer={getPopupContainer}
|
|
75
333
|
/>
|
|
76
334
|
);
|
|
77
335
|
}
|
|
78
336
|
|
|
79
|
-
// 其他项目只传递 model
|
|
80
337
|
return <ItemComponent key={itemConfig.key} model={model} />;
|
|
81
338
|
});
|
|
82
339
|
};
|
|
83
340
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
/* 当有.hide-parent-menu类时隐藏菜单 */
|
|
125
|
-
&.hide-parent-menu > .nb-toolbar-container {
|
|
126
|
-
opacity: 0 !important;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
> .nb-toolbar-container {
|
|
130
|
-
position: absolute;
|
|
131
|
-
top: 0;
|
|
132
|
-
bottom: 0;
|
|
133
|
-
left: 0;
|
|
134
|
-
right: 0;
|
|
135
|
-
z-index: 999;
|
|
136
|
-
opacity: 0;
|
|
137
|
-
background: ${showBackground ? 'var(--colorBgSettingsHover)' : ''};
|
|
138
|
-
border: ${showBorder ? '2px solid var(--colorBorderSettingsHover)' : ''};
|
|
139
|
-
border-radius: ${ctx.themeToken.borderRadiusLG}px;
|
|
140
|
-
pointer-events: none;
|
|
141
|
-
min-width: ${TOOLBAR_ITEM_WIDTH * toolbarCount}px;
|
|
142
|
-
|
|
143
|
-
&.nb-in-template {
|
|
144
|
-
background: var(--colorTemplateBgSettingsHover);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
> .nb-toolbar-container-title {
|
|
148
|
-
pointer-events: none;
|
|
149
|
-
position: absolute;
|
|
150
|
-
font-size: 12px;
|
|
151
|
-
padding: 0;
|
|
152
|
-
line-height: 16px;
|
|
153
|
-
height: 16px;
|
|
154
|
-
border-bottom-right-radius: 2px;
|
|
155
|
-
border-radius: 2px;
|
|
156
|
-
top: 2px;
|
|
157
|
-
left: 2px;
|
|
158
|
-
display: flex;
|
|
159
|
-
align-items: center;
|
|
160
|
-
gap: 4px;
|
|
161
|
-
|
|
162
|
-
.title-tag {
|
|
163
|
-
padding: 0 3px;
|
|
164
|
-
border-radius: 2px;
|
|
165
|
-
background: var(--colorSettings);
|
|
166
|
-
color: #fff;
|
|
167
|
-
display: inline-flex;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
> .nb-toolbar-container-icons {
|
|
172
|
-
display: none; // 防止遮挡其它 icons
|
|
173
|
-
position: absolute;
|
|
174
|
-
right: 2px;
|
|
175
|
-
${toolbarPositionToCSS[toolbarPosition] || ''}
|
|
176
|
-
line-height: 16px;
|
|
177
|
-
pointer-events: all;
|
|
178
|
-
|
|
179
|
-
.ant-space-item {
|
|
180
|
-
background-color: var(--colorSettings);
|
|
181
|
-
color: #fff;
|
|
182
|
-
line-height: 16px;
|
|
183
|
-
width: 16px;
|
|
184
|
-
height: 16px;
|
|
185
|
-
padding: 2px;
|
|
186
|
-
display: flex;
|
|
187
|
-
align-items: center;
|
|
188
|
-
justify-content: center;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/* 拖拽把手样式 - 参考 AirTable 样式 */
|
|
193
|
-
> .resize-handle {
|
|
194
|
-
position: absolute;
|
|
195
|
-
pointer-events: all;
|
|
196
|
-
background: var(--colorSettings);
|
|
197
|
-
opacity: 0.6;
|
|
198
|
-
border-radius: 4px;
|
|
199
|
-
display: flex;
|
|
200
|
-
align-items: center;
|
|
201
|
-
justify-content: center;
|
|
202
|
-
|
|
203
|
-
&:hover {
|
|
204
|
-
opacity: 0.9;
|
|
205
|
-
background: var(--colorSettingsHover, var(--colorSettings));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
&::before {
|
|
209
|
-
content: '';
|
|
210
|
-
position: absolute;
|
|
211
|
-
background: rgba(255, 255, 255, 0.9);
|
|
212
|
-
border-radius: 50%;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
&::after {
|
|
216
|
-
content: '';
|
|
217
|
-
position: absolute;
|
|
218
|
-
background: rgba(255, 255, 255, 0.9);
|
|
219
|
-
border-radius: 50%;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
> .resize-handle-left {
|
|
224
|
-
left: -4px;
|
|
225
|
-
top: 50%;
|
|
226
|
-
transform: translateY(-50%);
|
|
227
|
-
width: 6px;
|
|
228
|
-
height: 20px;
|
|
229
|
-
cursor: ew-resize;
|
|
230
|
-
|
|
231
|
-
&::before {
|
|
232
|
-
width: 2px;
|
|
233
|
-
height: 2px;
|
|
234
|
-
top: 6px;
|
|
235
|
-
left: 50%;
|
|
236
|
-
transform: translateX(-50%);
|
|
237
|
-
box-shadow:
|
|
238
|
-
0 4px 0 rgba(255, 255, 255, 0.9),
|
|
239
|
-
0 8px 0 rgba(255, 255, 255, 0.9);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
> .resize-handle-right {
|
|
244
|
-
right: -4px;
|
|
245
|
-
top: 50%;
|
|
246
|
-
transform: translateY(-50%);
|
|
247
|
-
width: 6px;
|
|
248
|
-
height: 20px;
|
|
249
|
-
cursor: ew-resize;
|
|
250
|
-
|
|
251
|
-
&::before {
|
|
252
|
-
width: 2px;
|
|
253
|
-
height: 2px;
|
|
254
|
-
top: 6px;
|
|
255
|
-
left: 50%;
|
|
256
|
-
transform: translateX(-50%);
|
|
257
|
-
box-shadow:
|
|
258
|
-
0 4px 0 rgba(255, 255, 255, 0.9),
|
|
259
|
-
0 8px 0 rgba(255, 255, 255, 0.9);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
> .resize-handle-bottom {
|
|
264
|
-
bottom: -4px;
|
|
265
|
-
left: 50%;
|
|
266
|
-
transform: translateX(-50%);
|
|
267
|
-
width: 20px;
|
|
268
|
-
height: 6px;
|
|
269
|
-
cursor: ns-resize;
|
|
270
|
-
|
|
271
|
-
&::before {
|
|
272
|
-
width: 2px;
|
|
273
|
-
height: 2px;
|
|
274
|
-
left: 6px;
|
|
275
|
-
top: 50%;
|
|
276
|
-
transform: translateY(-50%);
|
|
277
|
-
box-shadow:
|
|
278
|
-
4px 0 0 rgba(255, 255, 255, 0.9),
|
|
279
|
-
8px 0 0 rgba(255, 255, 255, 0.9);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
`;
|
|
341
|
+
const buildToolbarContainerClassName = ({
|
|
342
|
+
showBackground,
|
|
343
|
+
showBorder,
|
|
344
|
+
ctx,
|
|
345
|
+
toolbarZIndex,
|
|
346
|
+
portalRenderSnapshot,
|
|
347
|
+
isToolbarVisible,
|
|
348
|
+
className,
|
|
349
|
+
}: {
|
|
350
|
+
showBackground: boolean;
|
|
351
|
+
showBorder: boolean;
|
|
352
|
+
ctx: any;
|
|
353
|
+
toolbarZIndex: number;
|
|
354
|
+
portalRenderSnapshot: ToolbarPortalRenderSnapshot | null;
|
|
355
|
+
isToolbarVisible: boolean;
|
|
356
|
+
className?: string;
|
|
357
|
+
}) =>
|
|
358
|
+
[
|
|
359
|
+
toolbarContainerStyles({ showBackground, showBorder, ctx, toolbarZIndex }),
|
|
360
|
+
'nb-toolbar-portal',
|
|
361
|
+
portalRenderSnapshot?.positioningMode === 'absolute' ? 'nb-toolbar-portal-absolute' : 'nb-toolbar-portal-fixed',
|
|
362
|
+
isToolbarVisible ? 'nb-toolbar-visible' : '',
|
|
363
|
+
className?.includes('nb-in-template') ? 'nb-in-template' : '',
|
|
364
|
+
]
|
|
365
|
+
.filter(Boolean)
|
|
366
|
+
.join(' ');
|
|
367
|
+
|
|
368
|
+
const buildToolbarContainerStyle = (
|
|
369
|
+
portalRect: ToolbarPortalRect,
|
|
370
|
+
toolbarStyle?: React.CSSProperties,
|
|
371
|
+
toolbarItemCount = 0,
|
|
372
|
+
): React.CSSProperties => ({
|
|
373
|
+
top: `${portalRect.top}px`,
|
|
374
|
+
left: `${portalRect.left}px`,
|
|
375
|
+
width: `${portalRect.width}px`,
|
|
376
|
+
height: `${portalRect.height}px`,
|
|
377
|
+
minWidth: toolbarItemCount ? `${TOOLBAR_ITEM_WIDTH * toolbarItemCount}px` : undefined,
|
|
378
|
+
...omitToolbarPortalInsetStyle(toolbarStyle),
|
|
379
|
+
});
|
|
284
380
|
|
|
285
|
-
|
|
286
|
-
interface ModelProvidedProps {
|
|
381
|
+
interface ModelProvidedProps extends BaseFloatContextMenuProps {
|
|
287
382
|
model: FlowModel<any>;
|
|
288
|
-
children?: React.ReactNode;
|
|
289
|
-
enabled?: boolean;
|
|
290
|
-
showDeleteButton?: boolean;
|
|
291
|
-
showCopyUidButton?: boolean;
|
|
292
|
-
containerStyle?: React.CSSProperties;
|
|
293
|
-
toolbarStyle?: React.CSSProperties;
|
|
294
|
-
className?: string;
|
|
295
|
-
/**
|
|
296
|
-
* @default true
|
|
297
|
-
*/
|
|
298
|
-
showBorder?: boolean;
|
|
299
|
-
/**
|
|
300
|
-
* @default true
|
|
301
|
-
*/
|
|
302
|
-
showBackground?: boolean;
|
|
303
|
-
/**
|
|
304
|
-
* @default false
|
|
305
|
-
*/
|
|
306
|
-
showTitle?: boolean;
|
|
307
|
-
/**
|
|
308
|
-
* @default false
|
|
309
|
-
*/
|
|
310
|
-
showDragHandle?: boolean;
|
|
311
|
-
/**
|
|
312
|
-
* Settings menu levels: 1=current model only (default), 2=include sub-models
|
|
313
|
-
*/
|
|
314
|
-
settingsMenuLevel?: number;
|
|
315
|
-
/**
|
|
316
|
-
* Extra toolbar items to add to this context menu instance
|
|
317
|
-
*/
|
|
318
|
-
extraToolbarItems?: ToolbarItemConfig[];
|
|
319
|
-
/**
|
|
320
|
-
* @default 'inside'
|
|
321
|
-
*/
|
|
322
|
-
toolbarPosition?: 'inside' | 'above' | 'below';
|
|
323
383
|
}
|
|
324
384
|
|
|
325
|
-
interface ModelByIdProps {
|
|
385
|
+
interface ModelByIdProps extends BaseFloatContextMenuProps {
|
|
326
386
|
uid: string;
|
|
327
387
|
modelClassName: string;
|
|
328
|
-
children?: React.ReactNode;
|
|
329
|
-
enabled?: boolean;
|
|
330
|
-
showDeleteButton?: boolean;
|
|
331
|
-
showCopyUidButton?: boolean;
|
|
332
|
-
containerStyle?: React.CSSProperties;
|
|
333
|
-
className?: string;
|
|
334
|
-
/**
|
|
335
|
-
* @default true
|
|
336
|
-
*/
|
|
337
|
-
showBorder?: boolean;
|
|
338
|
-
/**
|
|
339
|
-
* @default true
|
|
340
|
-
*/
|
|
341
|
-
showBackground?: boolean;
|
|
342
|
-
/**
|
|
343
|
-
* @default false
|
|
344
|
-
*/
|
|
345
|
-
showTitle?: boolean;
|
|
346
|
-
/**
|
|
347
|
-
* Settings menu levels: 1=current model only (default), 2=include sub-models
|
|
348
|
-
*/
|
|
349
|
-
settingsMenuLevel?: number;
|
|
350
|
-
/**
|
|
351
|
-
* Extra toolbar items to add to this context menu instance
|
|
352
|
-
*/
|
|
353
|
-
extraToolbarItems?: ToolbarItemConfig[];
|
|
354
|
-
/**
|
|
355
|
-
* @default 'inside'
|
|
356
|
-
*/
|
|
357
|
-
toolbarPosition?: 'inside' | 'above' | 'below';
|
|
358
388
|
}
|
|
359
389
|
|
|
360
390
|
type FlowsFloatContextMenuProps = ModelProvidedProps | ModelByIdProps;
|
|
361
391
|
|
|
362
|
-
// 判断是否是通过ID获取模型的props
|
|
392
|
+
// 判断是否是通过ID获取模型的 props
|
|
363
393
|
const isModelByIdProps = (props: FlowsFloatContextMenuProps): props is ModelByIdProps => {
|
|
364
394
|
return 'uid' in props && 'modelClassName' in props && Boolean(props.uid) && Boolean(props.modelClassName);
|
|
365
395
|
};
|
|
366
396
|
|
|
367
397
|
/**
|
|
368
|
-
* FlowsFloatContextMenu组件 -
|
|
398
|
+
* FlowsFloatContextMenu组件 - 悬浮配置工具栏组件
|
|
369
399
|
*
|
|
370
400
|
* 功能特性:
|
|
371
401
|
* - 鼠标悬浮显示右上角配置图标
|
|
372
402
|
* - 点击图标显示配置菜单
|
|
373
403
|
* - 支持删除功能
|
|
374
404
|
* - Wrapper 模式支持
|
|
375
|
-
* -
|
|
376
|
-
* -
|
|
405
|
+
* - 使用 portal overlay 避免被宿主或祖先裁剪
|
|
406
|
+
* - 设置菜单与工具栏共享同一个 popup 容器
|
|
377
407
|
*
|
|
378
408
|
* 支持两种使用方式:
|
|
379
|
-
* 1. 直接提供model:
|
|
380
|
-
* 2. 通过uid和modelClassName获取model:
|
|
409
|
+
* 1. 直接提供 model: `<FlowsFloatContextMenu model={myModel}>{children}</FlowsFloatContextMenu>`
|
|
410
|
+
* 2. 通过 uid 和 modelClassName 获取 model:
|
|
411
|
+
* `<FlowsFloatContextMenu uid="model1" modelClassName="MyModel">{children}</FlowsFloatContextMenu>`
|
|
381
412
|
*
|
|
382
413
|
* @param props.children 子组件,必须提供
|
|
383
|
-
* @param props.enabled 是否启用悬浮菜单,默认为true
|
|
384
|
-
* @param props.showDeleteButton 是否显示删除按钮,默认为true
|
|
385
|
-
* @param props.showCopyUidButton 是否显示复制UID按钮,默认为true
|
|
414
|
+
* @param props.enabled 是否启用悬浮菜单,默认为 true
|
|
415
|
+
* @param props.showDeleteButton 是否显示删除按钮,默认为 true
|
|
416
|
+
* @param props.showCopyUidButton 是否显示复制 UID 按钮,默认为 true
|
|
386
417
|
* @param props.containerStyle 容器自定义样式
|
|
418
|
+
* @param props.toolbarStyle 工具栏自定义样式;`top/left/right/bottom` 会作为 portal overlay 的 inset 使用
|
|
387
419
|
* @param props.className 容器自定义类名
|
|
388
|
-
* @param props.showTitle 是否在边框左上角显示模型title,默认为false
|
|
420
|
+
* @param props.showTitle 是否在边框左上角显示模型 title,默认为 false
|
|
389
421
|
* @param props.settingsMenuLevel 设置菜单层级:1=仅当前模型(默认),2=包含子模型
|
|
390
422
|
* @param props.extraToolbarItems 额外的工具栏项目,仅应用于此实例
|
|
391
423
|
*/
|
|
392
424
|
const FlowsFloatContextMenu: React.FC<FlowsFloatContextMenuProps> = observer((props) => {
|
|
393
425
|
const ctx = useFlowContext();
|
|
394
|
-
// Only render if flowSettings is enabled
|
|
395
426
|
if (!ctx.flowSettingsEnabled) {
|
|
396
427
|
return <>{props.children}</>;
|
|
397
428
|
}
|
|
398
429
|
if (isModelByIdProps(props)) {
|
|
399
430
|
return <FlowsFloatContextMenuWithModelById {...props} />;
|
|
400
|
-
} else {
|
|
401
|
-
return <FlowsFloatContextMenuWithModel {...props} />;
|
|
402
431
|
}
|
|
432
|
+
return <FlowsFloatContextMenuWithModel {...props} />;
|
|
403
433
|
});
|
|
404
434
|
|
|
405
|
-
const ResizeHandles: React.FC<{
|
|
435
|
+
const ResizeHandles: React.FC<{
|
|
436
|
+
model: FlowModel;
|
|
437
|
+
onDragStart: () => void;
|
|
438
|
+
onDragEnd: () => void;
|
|
439
|
+
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
|
440
|
+
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
|
|
441
|
+
}> = (props) => {
|
|
406
442
|
const isDraggingRef = useRef<boolean>(false);
|
|
407
|
-
const dragTypeRef = useRef<'left' | 'right' |
|
|
443
|
+
const dragTypeRef = useRef<'left' | 'right' | null>(null);
|
|
408
444
|
const dragStartPosRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
409
445
|
const { onDragStart, onDragEnd } = props;
|
|
410
446
|
|
|
411
|
-
//
|
|
447
|
+
// 把拖拽位移转成上层已约定的 resize 事件。
|
|
412
448
|
const handleDragMove = useCallback(
|
|
413
449
|
(e: MouseEvent) => {
|
|
414
450
|
if (!isDraggingRef.current || !dragTypeRef.current) return;
|
|
415
451
|
|
|
416
452
|
const deltaX = e.clientX - dragStartPosRef.current.x;
|
|
417
|
-
const deltaY = e.clientY - dragStartPosRef.current.y;
|
|
418
|
-
|
|
419
|
-
let resizeDistance = 0;
|
|
420
453
|
|
|
421
454
|
switch (dragTypeRef.current) {
|
|
422
455
|
case 'left':
|
|
423
|
-
|
|
424
|
-
resizeDistance = -deltaX;
|
|
425
|
-
props.model.parent.emitter.emit('onResizeLeft', { resizeDistance, model: props.model });
|
|
456
|
+
props.model.parent.emitter.emit('onResizeLeft', { resizeDistance: -deltaX, model: props.model });
|
|
426
457
|
break;
|
|
427
|
-
|
|
428
458
|
case 'right':
|
|
429
|
-
|
|
430
|
-
resizeDistance = deltaX;
|
|
431
|
-
props.model.parent.emitter.emit('onResizeRight', { resizeDistance, model: props.model });
|
|
459
|
+
props.model.parent.emitter.emit('onResizeRight', { resizeDistance: deltaX, model: props.model });
|
|
432
460
|
break;
|
|
433
|
-
|
|
434
|
-
case 'bottom':
|
|
435
|
-
// 底部把手:向下拖为正数,向上拖为负数
|
|
436
|
-
resizeDistance = deltaY;
|
|
437
|
-
props.model.parent.emitter.emit('onResizeBottom', { resizeDistance, model: props.model });
|
|
438
|
-
break;
|
|
439
|
-
|
|
440
|
-
case 'corner': {
|
|
441
|
-
// 右下角把手:同时计算宽度和高度变化
|
|
442
|
-
const widthDelta = deltaX;
|
|
443
|
-
const heightDelta = deltaY;
|
|
444
|
-
props.model.parent.emitter.emit('onResizeCorner', { widthDelta, heightDelta, model: props.model });
|
|
445
|
-
break;
|
|
446
|
-
}
|
|
447
461
|
}
|
|
448
462
|
},
|
|
449
463
|
[props.model],
|
|
450
464
|
);
|
|
451
465
|
|
|
452
|
-
// 拖拽结束处理函数
|
|
453
466
|
const handleDragEnd = useCallback(() => {
|
|
454
467
|
isDraggingRef.current = false;
|
|
455
468
|
dragTypeRef.current = null;
|
|
456
469
|
dragStartPosRef.current = { x: 0, y: 0 };
|
|
457
470
|
|
|
458
|
-
// 移除全局事件监听
|
|
459
471
|
document.removeEventListener('mousemove', handleDragMove);
|
|
460
472
|
document.removeEventListener('mouseup', handleDragEnd);
|
|
461
473
|
|
|
462
474
|
props.model.parent.emitter.emit('onResizeEnd');
|
|
463
475
|
onDragEnd?.();
|
|
464
|
-
}, [handleDragMove, props.model
|
|
476
|
+
}, [handleDragMove, onDragEnd, props.model]);
|
|
465
477
|
|
|
466
|
-
// 拖拽开始处理函数
|
|
467
478
|
const handleDragStart = useCallback(
|
|
468
|
-
(e: React.MouseEvent, type: 'left' | 'right'
|
|
479
|
+
(e: React.MouseEvent, type: 'left' | 'right') => {
|
|
469
480
|
e.preventDefault();
|
|
470
481
|
e.stopPropagation();
|
|
471
482
|
|
|
@@ -473,7 +484,6 @@ const ResizeHandles: React.FC<{ model: FlowModel; onDragStart: () => void; onDra
|
|
|
473
484
|
dragTypeRef.current = type;
|
|
474
485
|
dragStartPosRef.current = { x: e.clientX, y: e.clientY };
|
|
475
486
|
|
|
476
|
-
// 添加全局事件监听
|
|
477
487
|
document.addEventListener('mousemove', handleDragMove);
|
|
478
488
|
document.addEventListener('mouseup', handleDragEnd);
|
|
479
489
|
|
|
@@ -484,27 +494,25 @@ const ResizeHandles: React.FC<{ model: FlowModel; onDragStart: () => void; onDra
|
|
|
484
494
|
|
|
485
495
|
return (
|
|
486
496
|
<>
|
|
487
|
-
{/* 拖拽把手 */}
|
|
488
497
|
<div
|
|
489
498
|
className="resize-handle resize-handle-left"
|
|
490
499
|
title="拖拽调节宽度"
|
|
500
|
+
onMouseEnter={props.onMouseEnter}
|
|
501
|
+
onMouseLeave={props.onMouseLeave}
|
|
491
502
|
onMouseDown={(e) => handleDragStart(e, 'left')}
|
|
492
|
-
|
|
503
|
+
/>
|
|
493
504
|
<div
|
|
494
505
|
className="resize-handle resize-handle-right"
|
|
495
506
|
title="拖拽调节宽度"
|
|
507
|
+
onMouseEnter={props.onMouseEnter}
|
|
508
|
+
onMouseLeave={props.onMouseLeave}
|
|
496
509
|
onMouseDown={(e) => handleDragStart(e, 'right')}
|
|
497
|
-
|
|
498
|
-
{/* <div
|
|
499
|
-
className="resize-handle resize-handle-bottom"
|
|
500
|
-
title="拖拽调节高度"
|
|
501
|
-
onMouseDown={(e) => handleDragStart(e, 'bottom')}
|
|
502
|
-
></div> */}
|
|
510
|
+
/>
|
|
503
511
|
</>
|
|
504
512
|
);
|
|
505
513
|
};
|
|
506
514
|
|
|
507
|
-
//
|
|
515
|
+
// 使用直接传入的 model 渲染工具栏。
|
|
508
516
|
const FlowsFloatContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
|
|
509
517
|
({
|
|
510
518
|
model,
|
|
@@ -520,37 +528,100 @@ const FlowsFloatContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
|
|
|
520
528
|
showDragHandle = false,
|
|
521
529
|
settingsMenuLevel,
|
|
522
530
|
extraToolbarItems,
|
|
531
|
+
showDynamicFlowsEditor = true,
|
|
523
532
|
toolbarStyle,
|
|
524
533
|
toolbarPosition = 'inside',
|
|
525
534
|
}: ModelProvidedProps) => {
|
|
526
|
-
const [
|
|
527
|
-
const [hasButton, setHasButton] = useState<boolean>(false);
|
|
535
|
+
const [hasButton, setHasButton] = useState(false);
|
|
528
536
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
529
|
-
const flowEngine = useFlowEngine();
|
|
530
|
-
const [style, setStyle] = useState<React.CSSProperties>({});
|
|
531
537
|
const toolbarContainerRef = useRef<HTMLDivElement>(null);
|
|
532
|
-
const
|
|
538
|
+
const portalActionsRef = useRef<{
|
|
539
|
+
updatePortalRect: () => void;
|
|
540
|
+
schedulePortalRectUpdate: () => void;
|
|
541
|
+
}>({
|
|
542
|
+
updatePortalRect: () => {},
|
|
543
|
+
schedulePortalRectUpdate: () => {},
|
|
544
|
+
});
|
|
545
|
+
const modelUid = getFloatMenuInstanceId(model);
|
|
546
|
+
const flowEngine = useFlowEngine();
|
|
547
|
+
const updatePortalRectProxy = useCallback(() => {
|
|
548
|
+
portalActionsRef.current.updatePortalRect();
|
|
549
|
+
}, []);
|
|
550
|
+
const schedulePortalRectUpdateProxy = useCallback(() => {
|
|
551
|
+
portalActionsRef.current.schedulePortalRectUpdate();
|
|
552
|
+
}, []);
|
|
553
|
+
const {
|
|
554
|
+
isToolbarVisible,
|
|
555
|
+
shouldRenderToolbar,
|
|
556
|
+
handleSettingsMenuOpenChange,
|
|
557
|
+
handleChildHover,
|
|
558
|
+
handleHostMouseEnter,
|
|
559
|
+
handleHostMouseLeave,
|
|
560
|
+
handleToolbarMouseEnter,
|
|
561
|
+
handleToolbarMouseLeave,
|
|
562
|
+
handleResizeDragStart,
|
|
563
|
+
handleResizeDragEnd,
|
|
564
|
+
} = useFloatToolbarVisibility({
|
|
565
|
+
modelUid,
|
|
566
|
+
containerRef,
|
|
567
|
+
toolbarContainerRef,
|
|
568
|
+
updatePortalRect: updatePortalRectProxy,
|
|
569
|
+
schedulePortalRectUpdate: schedulePortalRectUpdateProxy,
|
|
570
|
+
});
|
|
571
|
+
const { portalRect, portalRenderSnapshot, getPopupContainer, updatePortalRect, schedulePortalRectUpdate } =
|
|
572
|
+
useFloatToolbarPortal({
|
|
573
|
+
active: shouldRenderToolbar,
|
|
574
|
+
containerRef,
|
|
575
|
+
toolbarContainerRef,
|
|
576
|
+
toolbarStyle,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
portalActionsRef.current.updatePortalRect = updatePortalRect;
|
|
580
|
+
portalActionsRef.current.schedulePortalRectUpdate = schedulePortalRectUpdate;
|
|
581
|
+
|
|
582
|
+
const toolbarItems = useMemo(
|
|
583
|
+
() =>
|
|
584
|
+
model
|
|
585
|
+
? renderToolbarItems(
|
|
586
|
+
model,
|
|
587
|
+
modelUid,
|
|
588
|
+
showDeleteButton,
|
|
589
|
+
showCopyUidButton,
|
|
590
|
+
flowEngine,
|
|
591
|
+
settingsMenuLevel,
|
|
592
|
+
extraToolbarItems,
|
|
593
|
+
showDynamicFlowsEditor,
|
|
594
|
+
handleSettingsMenuOpenChange,
|
|
595
|
+
getPopupContainer,
|
|
596
|
+
)
|
|
597
|
+
: [],
|
|
598
|
+
[
|
|
599
|
+
extraToolbarItems,
|
|
600
|
+
flowEngine,
|
|
601
|
+
getPopupContainer,
|
|
602
|
+
handleSettingsMenuOpenChange,
|
|
603
|
+
model,
|
|
604
|
+
settingsMenuLevel,
|
|
605
|
+
showCopyUidButton,
|
|
606
|
+
showDeleteButton,
|
|
607
|
+
showDynamicFlowsEditor,
|
|
608
|
+
],
|
|
609
|
+
);
|
|
533
610
|
|
|
534
|
-
// 检测DOM中是否包含button元素
|
|
535
611
|
useEffect(() => {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
612
|
+
const container = containerRef.current;
|
|
613
|
+
if (!container) {
|
|
614
|
+
return;
|
|
539
615
|
}
|
|
540
|
-
}, [children]); // 当children变化时重新检测
|
|
541
616
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
617
|
+
const syncHasButton = () => {
|
|
618
|
+
setHasButton(detectButtonInDOM(container));
|
|
619
|
+
};
|
|
545
620
|
|
|
546
|
-
|
|
547
|
-
if (containerRef.current) {
|
|
548
|
-
const hasButtonElement = detectButtonInDOM(containerRef.current);
|
|
549
|
-
setHasButton(hasButtonElement);
|
|
550
|
-
}
|
|
551
|
-
});
|
|
621
|
+
syncHasButton();
|
|
552
622
|
|
|
553
|
-
observer
|
|
623
|
+
const observer = new MutationObserver(syncHasButton);
|
|
624
|
+
observer.observe(container, {
|
|
554
625
|
childList: true,
|
|
555
626
|
subtree: true,
|
|
556
627
|
attributes: true,
|
|
@@ -562,75 +633,81 @@ const FlowsFloatContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
|
|
|
562
633
|
};
|
|
563
634
|
}, []);
|
|
564
635
|
|
|
565
|
-
const handleChildHover = useCallback((e: React.MouseEvent) => {
|
|
566
|
-
const target = e.target as HTMLElement;
|
|
567
|
-
const childWithMenu = target.closest('[data-has-float-menu]');
|
|
568
|
-
|
|
569
|
-
// 如果悬浮的是子元素(且不是当前容器),则隐藏当前菜单
|
|
570
|
-
if (childWithMenu && childWithMenu !== containerRef.current) {
|
|
571
|
-
setHideMenu(true);
|
|
572
|
-
} else {
|
|
573
|
-
setHideMenu(false);
|
|
574
|
-
}
|
|
575
|
-
}, []);
|
|
576
|
-
|
|
577
636
|
if (!model) {
|
|
578
637
|
const t = getT(model || ({} as FlowModel));
|
|
579
638
|
return <Alert message={t('Invalid model provided')} type="error" />;
|
|
580
639
|
}
|
|
581
640
|
|
|
582
|
-
// 如果未启用或没有children,直接返回children
|
|
583
641
|
if (!enabled || !children) {
|
|
584
642
|
return <>{children}</>;
|
|
585
643
|
}
|
|
586
644
|
|
|
645
|
+
const toolbarZIndex = (model.context.themeToken?.zIndexPopupBase || DEFAULT_POPUP_BASE_Z_INDEX) + 1;
|
|
646
|
+
const toolbarContainerClassName = buildToolbarContainerClassName({
|
|
647
|
+
showBackground,
|
|
648
|
+
showBorder,
|
|
649
|
+
ctx: model.context,
|
|
650
|
+
toolbarZIndex,
|
|
651
|
+
portalRenderSnapshot,
|
|
652
|
+
isToolbarVisible,
|
|
653
|
+
className,
|
|
654
|
+
});
|
|
655
|
+
const toolbarContainerStyle = buildToolbarContainerStyle(portalRect, toolbarStyle, toolbarItems.length);
|
|
656
|
+
|
|
657
|
+
const toolbarNode = shouldRenderToolbar ? (
|
|
658
|
+
<div
|
|
659
|
+
ref={toolbarContainerRef}
|
|
660
|
+
className={`nb-toolbar-container ${toolbarContainerClassName}`}
|
|
661
|
+
style={toolbarContainerStyle}
|
|
662
|
+
data-model-uid={modelUid}
|
|
663
|
+
>
|
|
664
|
+
{showTitle && (model.title || model.extraTitle) && (
|
|
665
|
+
<div className="nb-toolbar-container-title">
|
|
666
|
+
{model.title && <span className="title-tag">{model.title}</span>}
|
|
667
|
+
{model.extraTitle && <span className="title-tag">{model.extraTitle}</span>}
|
|
668
|
+
</div>
|
|
669
|
+
)}
|
|
670
|
+
<div
|
|
671
|
+
className={`nb-toolbar-container-icons ${toolbarPositionClassNames[toolbarPosition]}`}
|
|
672
|
+
onClick={(e) => e.stopPropagation()}
|
|
673
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
674
|
+
onMouseMove={(e) => e.stopPropagation()}
|
|
675
|
+
onMouseEnter={handleToolbarMouseEnter}
|
|
676
|
+
onMouseLeave={handleToolbarMouseLeave}
|
|
677
|
+
>
|
|
678
|
+
<Space size={3} align="center">
|
|
679
|
+
{toolbarItems}
|
|
680
|
+
</Space>
|
|
681
|
+
</div>
|
|
682
|
+
|
|
683
|
+
{showDragHandle && (
|
|
684
|
+
<ResizeHandles
|
|
685
|
+
model={model}
|
|
686
|
+
onMouseEnter={handleToolbarMouseEnter}
|
|
687
|
+
onMouseLeave={handleToolbarMouseLeave}
|
|
688
|
+
onDragStart={handleResizeDragStart}
|
|
689
|
+
onDragEnd={handleResizeDragEnd}
|
|
690
|
+
/>
|
|
691
|
+
)}
|
|
692
|
+
</div>
|
|
693
|
+
) : null;
|
|
694
|
+
|
|
587
695
|
return (
|
|
588
696
|
<div
|
|
589
697
|
ref={containerRef}
|
|
590
|
-
className={`${
|
|
591
|
-
showBackground,
|
|
592
|
-
showBorder,
|
|
593
|
-
ctx: model.context,
|
|
594
|
-
toolbarPosition,
|
|
595
|
-
toolbarCount: getToolbarCount(flowEngine, extraToolbarItems),
|
|
596
|
-
})} ${hideMenu ? 'hide-parent-menu' : ''} ${hasButton ? 'has-button-child' : ''} ${className || ''}`}
|
|
698
|
+
className={`${hostContainerStyles} ${hasButton ? 'has-button-child' : ''} ${className || ''}`}
|
|
597
699
|
style={containerStyle}
|
|
598
700
|
data-has-float-menu="true"
|
|
701
|
+
data-float-menu-model-uid={modelUid}
|
|
599
702
|
onMouseMove={handleChildHover}
|
|
703
|
+
onMouseEnter={handleHostMouseEnter}
|
|
704
|
+
onMouseLeave={handleHostMouseLeave}
|
|
600
705
|
>
|
|
601
706
|
{children}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
<div className="nb-toolbar-container-title">
|
|
607
|
-
{model.title && <span className="title-tag">{model.title}</span>}
|
|
608
|
-
{model.extraTitle && <span className="title-tag">{model.extraTitle}</span>}
|
|
609
|
-
</div>
|
|
610
|
-
)}
|
|
611
|
-
<div
|
|
612
|
-
className="nb-toolbar-container-icons"
|
|
613
|
-
onClick={(e) => e.stopPropagation()}
|
|
614
|
-
onMouseDown={(e) => e.stopPropagation()}
|
|
615
|
-
onMouseMove={(e) => e.stopPropagation()}
|
|
616
|
-
>
|
|
617
|
-
<Space size={3} align="center">
|
|
618
|
-
{renderToolbarItems(
|
|
619
|
-
model,
|
|
620
|
-
showDeleteButton,
|
|
621
|
-
showCopyUidButton,
|
|
622
|
-
flowEngine,
|
|
623
|
-
settingsMenuLevel,
|
|
624
|
-
extraToolbarItems,
|
|
625
|
-
)}
|
|
626
|
-
</Space>
|
|
627
|
-
</div>
|
|
628
|
-
|
|
629
|
-
{/* 拖拽把手 */}
|
|
630
|
-
{showDragHandle && (
|
|
631
|
-
<ResizeHandles model={model} onDragStart={() => setStyle({ opacity: 1 })} onDragEnd={() => setStyle({})} />
|
|
632
|
-
)}
|
|
633
|
-
</div>
|
|
707
|
+
{toolbarNode &&
|
|
708
|
+
(portalRenderSnapshot?.mountElement
|
|
709
|
+
? createPortal(toolbarNode, portalRenderSnapshot.mountElement)
|
|
710
|
+
: toolbarNode)}
|
|
634
711
|
</div>
|
|
635
712
|
);
|
|
636
713
|
},
|
|
@@ -639,22 +716,9 @@ const FlowsFloatContextMenuWithModel: React.FC<ModelProvidedProps> = observer(
|
|
|
639
716
|
},
|
|
640
717
|
);
|
|
641
718
|
|
|
642
|
-
// 通过
|
|
719
|
+
// 通过 uid + modelClassName 解析 model,再复用主实现。
|
|
643
720
|
const FlowsFloatContextMenuWithModelById: React.FC<ModelByIdProps> = observer(
|
|
644
|
-
({
|
|
645
|
-
uid,
|
|
646
|
-
modelClassName,
|
|
647
|
-
children,
|
|
648
|
-
enabled = true,
|
|
649
|
-
showDeleteButton = true,
|
|
650
|
-
showCopyUidButton = true,
|
|
651
|
-
containerStyle,
|
|
652
|
-
className,
|
|
653
|
-
showTitle = false,
|
|
654
|
-
settingsMenuLevel,
|
|
655
|
-
extraToolbarItems: extraToolbarItems,
|
|
656
|
-
toolbarPosition,
|
|
657
|
-
}) => {
|
|
721
|
+
({ uid, modelClassName, children, ...restProps }) => {
|
|
658
722
|
const model = useFlowModelById(uid, modelClassName);
|
|
659
723
|
const flowEngine = useFlowEngine();
|
|
660
724
|
|
|
@@ -663,18 +727,7 @@ const FlowsFloatContextMenuWithModelById: React.FC<ModelByIdProps> = observer(
|
|
|
663
727
|
}
|
|
664
728
|
|
|
665
729
|
return (
|
|
666
|
-
<FlowsFloatContextMenuWithModel
|
|
667
|
-
model={model}
|
|
668
|
-
enabled={enabled}
|
|
669
|
-
showDeleteButton={showDeleteButton}
|
|
670
|
-
showCopyUidButton={showCopyUidButton}
|
|
671
|
-
containerStyle={containerStyle}
|
|
672
|
-
className={className}
|
|
673
|
-
showTitle={showTitle}
|
|
674
|
-
settingsMenuLevel={settingsMenuLevel}
|
|
675
|
-
extraToolbarItems={extraToolbarItems}
|
|
676
|
-
toolbarPosition={toolbarPosition}
|
|
677
|
-
>
|
|
730
|
+
<FlowsFloatContextMenuWithModel model={model} {...restProps}>
|
|
678
731
|
{children}
|
|
679
732
|
</FlowsFloatContextMenuWithModel>
|
|
680
733
|
);
|
|
@@ -685,9 +738,3 @@ const FlowsFloatContextMenuWithModelById: React.FC<ModelByIdProps> = observer(
|
|
|
685
738
|
);
|
|
686
739
|
|
|
687
740
|
export { FlowsFloatContextMenu };
|
|
688
|
-
|
|
689
|
-
function getToolbarCount(flowEngine, extraToolbarItems) {
|
|
690
|
-
const toolbarItems = flowEngine?.flowSettings?.getToolbarItems?.() || [];
|
|
691
|
-
const allToolbarItems = [...toolbarItems, ...(extraToolbarItems || [])];
|
|
692
|
-
return allToolbarItems.length;
|
|
693
|
-
}
|