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