@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,8 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { DragOutlined } from '@ant-design/icons';
|
|
11
|
+
import type { Modifier } from '@dnd-kit/core';
|
|
11
12
|
import { DndContext, DndContextProps, DragOverlay, useDraggable, useDroppable } from '@dnd-kit/core';
|
|
12
|
-
import
|
|
13
|
+
import type { Transform } from '@dnd-kit/utilities';
|
|
14
|
+
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
|
|
13
15
|
import { createPortal } from 'react-dom';
|
|
14
16
|
import { FlowModel } from '../../models';
|
|
15
17
|
import { useFlowEngine } from '../../provider';
|
|
@@ -19,18 +21,229 @@ export * from './findModelUidPosition';
|
|
|
19
21
|
export * from './gridDragPlanner';
|
|
20
22
|
|
|
21
23
|
export const EMPTY_COLUMN_UID = 'EMPTY_COLUMN';
|
|
24
|
+
export const TOOLBAR_DRAG_ACTIVITY_EVENT = 'nb-toolbar-drag-activity';
|
|
25
|
+
const TOOLBAR_DRAG_ANCHOR_EVENT = 'nb-toolbar-drag-anchor';
|
|
26
|
+
const MENU_SUBMENU_POPUP_SELECTOR = '.ant-menu-submenu-popup';
|
|
27
|
+
|
|
28
|
+
type ToolbarDragAnchorPoint = {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type ToolbarDragAnchorDetail = {
|
|
34
|
+
modelUid: string;
|
|
35
|
+
point: ToolbarDragAnchorPoint | null;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const resolveOverlayAnchorTransform = ({
|
|
39
|
+
activeId,
|
|
40
|
+
active,
|
|
41
|
+
transform,
|
|
42
|
+
activeNodeRect,
|
|
43
|
+
dragAnchorPoint,
|
|
44
|
+
}: {
|
|
45
|
+
activeId: string | null;
|
|
46
|
+
active: { id: string | number } | null | undefined;
|
|
47
|
+
transform: Transform;
|
|
48
|
+
activeNodeRect: { top: number; left: number } | null;
|
|
49
|
+
dragAnchorPoint: ToolbarDragAnchorPoint | null;
|
|
50
|
+
}): Transform => {
|
|
51
|
+
if (!activeId || active?.id !== activeId || !dragAnchorPoint || !activeNodeRect) {
|
|
52
|
+
return transform;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
...transform,
|
|
57
|
+
x: transform.x + dragAnchorPoint.x - activeNodeRect.left,
|
|
58
|
+
y: transform.y + dragAnchorPoint.y - activeNodeRect.top,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const resolveDraggableHostNode = (activatorNode: HTMLElement | null) => {
|
|
63
|
+
const ownerDocument = activatorNode?.ownerDocument;
|
|
64
|
+
const floatToolbarContainer = activatorNode?.closest<HTMLElement>('.nb-toolbar-container[data-model-uid]');
|
|
65
|
+
const toolbarModelUid = floatToolbarContainer?.getAttribute('data-model-uid');
|
|
66
|
+
|
|
67
|
+
if (!ownerDocument || !toolbarModelUid) {
|
|
68
|
+
return activatorNode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const matchedHosts = Array.from(
|
|
72
|
+
ownerDocument.querySelectorAll<HTMLElement>(
|
|
73
|
+
`[data-has-float-menu="true"][data-float-menu-model-uid="${toolbarModelUid}"]`,
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
const popupRoot = floatToolbarContainer?.closest<HTMLElement>(MENU_SUBMENU_POPUP_SELECTOR);
|
|
77
|
+
|
|
78
|
+
if (popupRoot) {
|
|
79
|
+
return (
|
|
80
|
+
matchedHosts.find((hostNode) => hostNode.closest(MENU_SUBMENU_POPUP_SELECTOR) === popupRoot) || activatorNode
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
matchedHosts.find((hostNode) => !hostNode.closest(MENU_SUBMENU_POPUP_SELECTOR)) || matchedHosts[0] || activatorNode
|
|
86
|
+
);
|
|
87
|
+
};
|
|
22
88
|
|
|
23
89
|
// 可拖拽图标组件
|
|
24
|
-
export const DragHandler: FC<{ model: FlowModel; children
|
|
90
|
+
export const DragHandler: FC<{ model: FlowModel; children?: React.ReactNode }> = ({
|
|
25
91
|
model,
|
|
26
92
|
children = <DragOutlined />,
|
|
27
93
|
}) => {
|
|
28
|
-
const { attributes, listeners, setNodeRef } = useDraggable({ id: model.uid });
|
|
94
|
+
const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef } = useDraggable({ id: model.uid });
|
|
95
|
+
const dragHandlerRef = useRef<HTMLSpanElement | null>(null);
|
|
96
|
+
const draggableNodeRef = useRef<HTMLElement | null>(null);
|
|
97
|
+
const pointerPressCleanupRef = useRef<(() => void) | null>(null);
|
|
98
|
+
const isDraggingRef = useRef(isDragging);
|
|
99
|
+
const isPointerPressActiveRef = useRef(false);
|
|
100
|
+
const isToolbarDragActiveRef = useRef(false);
|
|
101
|
+
const syncDraggableNodeRef = useCallback(
|
|
102
|
+
(activatorNode: HTMLElement | null) => {
|
|
103
|
+
const nextNode = resolveDraggableHostNode(activatorNode);
|
|
104
|
+
|
|
105
|
+
if (draggableNodeRef.current === nextNode) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
draggableNodeRef.current = nextNode;
|
|
110
|
+
setNodeRef(nextNode);
|
|
111
|
+
},
|
|
112
|
+
[setNodeRef],
|
|
113
|
+
);
|
|
114
|
+
const setDragHandlerNodeRef = useCallback(
|
|
115
|
+
(node: HTMLSpanElement | null) => {
|
|
116
|
+
dragHandlerRef.current = node;
|
|
117
|
+
setActivatorNodeRef(node);
|
|
118
|
+
syncDraggableNodeRef(node);
|
|
119
|
+
},
|
|
120
|
+
[setActivatorNodeRef, syncDraggableNodeRef],
|
|
121
|
+
);
|
|
122
|
+
const dispatchToolbarDragActivity = useCallback(
|
|
123
|
+
(active: boolean) => {
|
|
124
|
+
const ownerDocument = dragHandlerRef.current?.ownerDocument;
|
|
125
|
+
if (!ownerDocument) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ownerDocument.dispatchEvent(
|
|
130
|
+
new CustomEvent(TOOLBAR_DRAG_ACTIVITY_EVENT, {
|
|
131
|
+
detail: { active, modelUid: model.uid },
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
[model.uid],
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const dispatchToolbarDragAnchor = useCallback(
|
|
139
|
+
(detail: Pick<ToolbarDragAnchorDetail, 'point'>) => {
|
|
140
|
+
const ownerDocument = dragHandlerRef.current?.ownerDocument;
|
|
141
|
+
if (!ownerDocument) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
ownerDocument.dispatchEvent(
|
|
146
|
+
new CustomEvent<ToolbarDragAnchorDetail>(TOOLBAR_DRAG_ANCHOR_EVENT, {
|
|
147
|
+
detail: { modelUid: model.uid, point: detail.point },
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
[model.uid],
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const clearPointerPressListeners = useCallback(() => {
|
|
155
|
+
pointerPressCleanupRef.current?.();
|
|
156
|
+
pointerPressCleanupRef.current = null;
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
const syncToolbarDragActivity = useCallback(() => {
|
|
160
|
+
const nextActive = isPointerPressActiveRef.current || isDraggingRef.current;
|
|
161
|
+
if (nextActive === isToolbarDragActiveRef.current) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
isToolbarDragActiveRef.current = nextActive;
|
|
166
|
+
dispatchToolbarDragActivity(nextActive);
|
|
167
|
+
}, [dispatchToolbarDragActivity]);
|
|
168
|
+
|
|
169
|
+
const handlePointerPressEnd = useCallback(() => {
|
|
170
|
+
isPointerPressActiveRef.current = false;
|
|
171
|
+
clearPointerPressListeners();
|
|
172
|
+
syncToolbarDragActivity();
|
|
173
|
+
}, [clearPointerPressListeners, syncToolbarDragActivity]);
|
|
174
|
+
|
|
175
|
+
const registerPointerPressListeners = useCallback(() => {
|
|
176
|
+
const ownerDocument = dragHandlerRef.current?.ownerDocument;
|
|
177
|
+
const ownerWindow = ownerDocument?.defaultView;
|
|
178
|
+
if (!ownerDocument) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
clearPointerPressListeners();
|
|
183
|
+
|
|
184
|
+
const handlePointerEnd = () => {
|
|
185
|
+
handlePointerPressEnd();
|
|
186
|
+
};
|
|
187
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
188
|
+
if (event.key === 'Escape') {
|
|
189
|
+
handlePointerPressEnd();
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
ownerDocument.addEventListener('pointerup', handlePointerEnd, true);
|
|
194
|
+
ownerDocument.addEventListener('pointercancel', handlePointerEnd, true);
|
|
195
|
+
ownerDocument.addEventListener('keydown', handleKeyDown, true);
|
|
196
|
+
ownerWindow?.addEventListener('blur', handlePointerEnd);
|
|
197
|
+
|
|
198
|
+
pointerPressCleanupRef.current = () => {
|
|
199
|
+
ownerDocument.removeEventListener('pointerup', handlePointerEnd, true);
|
|
200
|
+
ownerDocument.removeEventListener('pointercancel', handlePointerEnd, true);
|
|
201
|
+
ownerDocument.removeEventListener('keydown', handleKeyDown, true);
|
|
202
|
+
ownerWindow?.removeEventListener('blur', handlePointerEnd);
|
|
203
|
+
};
|
|
204
|
+
}, [clearPointerPressListeners, handlePointerPressEnd]);
|
|
205
|
+
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
syncDraggableNodeRef(dragHandlerRef.current);
|
|
208
|
+
}, [syncDraggableNodeRef]);
|
|
209
|
+
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
isDraggingRef.current = isDragging;
|
|
212
|
+
syncToolbarDragActivity();
|
|
213
|
+
}, [isDragging, syncToolbarDragActivity]);
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
return () => {
|
|
217
|
+
if (isToolbarDragActiveRef.current) {
|
|
218
|
+
dispatchToolbarDragActivity(false);
|
|
219
|
+
}
|
|
220
|
+
isPointerPressActiveRef.current = false;
|
|
221
|
+
isDraggingRef.current = false;
|
|
222
|
+
isToolbarDragActiveRef.current = false;
|
|
223
|
+
clearPointerPressListeners();
|
|
224
|
+
};
|
|
225
|
+
}, [clearPointerPressListeners, dispatchToolbarDragActivity]);
|
|
226
|
+
|
|
29
227
|
return (
|
|
30
228
|
<span
|
|
31
|
-
ref={
|
|
229
|
+
ref={setDragHandlerNodeRef}
|
|
32
230
|
{...listeners}
|
|
33
231
|
{...attributes}
|
|
232
|
+
onPointerDownCapture={(event) => {
|
|
233
|
+
if (event.button !== 0) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
dispatchToolbarDragAnchor({
|
|
238
|
+
point: {
|
|
239
|
+
x: event.clientX,
|
|
240
|
+
y: event.clientY,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
isPointerPressActiveRef.current = true;
|
|
244
|
+
syncToolbarDragActivity();
|
|
245
|
+
registerPointerPressListeners();
|
|
246
|
+
}}
|
|
34
247
|
style={{
|
|
35
248
|
cursor: 'grab',
|
|
36
249
|
}}
|
|
@@ -74,19 +287,56 @@ export const Droppable: FC<{ model: FlowModel<any>; children: React.ReactNode }>
|
|
|
74
287
|
export const DndProvider: FC<DndContextProps & PersistOptions> = ({
|
|
75
288
|
persist = true,
|
|
76
289
|
children,
|
|
290
|
+
onDragStart,
|
|
77
291
|
onDragEnd,
|
|
292
|
+
onDragCancel,
|
|
78
293
|
...restProps
|
|
79
294
|
}) => {
|
|
80
295
|
const [activeId, setActiveId] = useState<string | null>(null);
|
|
296
|
+
const [dragAnchorPoint, setDragAnchorPoint] = useState<ToolbarDragAnchorDetail['point']>(null);
|
|
81
297
|
const flowEngine = useFlowEngine();
|
|
298
|
+
|
|
299
|
+
useEffect(() => {
|
|
300
|
+
if (typeof document === 'undefined') {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const handleToolbarDragAnchor = (event: Event) => {
|
|
305
|
+
const customEvent = event as CustomEvent<ToolbarDragAnchorDetail>;
|
|
306
|
+
setDragAnchorPoint(customEvent.detail?.point || null);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
document.addEventListener(TOOLBAR_DRAG_ANCHOR_EVENT, handleToolbarDragAnchor as EventListener);
|
|
310
|
+
return () => {
|
|
311
|
+
document.removeEventListener(TOOLBAR_DRAG_ANCHOR_EVENT, handleToolbarDragAnchor as EventListener);
|
|
312
|
+
};
|
|
313
|
+
}, []);
|
|
314
|
+
|
|
315
|
+
const overlayAnchorModifier = useCallback<Modifier>(
|
|
316
|
+
({ active, activeNodeRect, transform }) => {
|
|
317
|
+
const nextTransform: Transform = resolveOverlayAnchorTransform({
|
|
318
|
+
activeId,
|
|
319
|
+
active,
|
|
320
|
+
transform,
|
|
321
|
+
activeNodeRect,
|
|
322
|
+
dragAnchorPoint,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return nextTransform;
|
|
326
|
+
},
|
|
327
|
+
[activeId, dragAnchorPoint],
|
|
328
|
+
);
|
|
329
|
+
|
|
82
330
|
return (
|
|
83
331
|
<DndContext
|
|
332
|
+
{...restProps}
|
|
84
333
|
onDragStart={(event) => {
|
|
85
334
|
setActiveId(event.active.id as string);
|
|
86
|
-
|
|
335
|
+
onDragStart?.(event);
|
|
87
336
|
}}
|
|
88
337
|
onDragEnd={(event) => {
|
|
89
338
|
setActiveId(null);
|
|
339
|
+
setDragAnchorPoint(null);
|
|
90
340
|
// 如果没有 onDragEnd 回调,则默认调用 flowEngine 的 moveModel 方法
|
|
91
341
|
if (!onDragEnd) {
|
|
92
342
|
if (event.over) {
|
|
@@ -97,32 +347,46 @@ export const DndProvider: FC<DndContextProps & PersistOptions> = ({
|
|
|
97
347
|
onDragEnd(event);
|
|
98
348
|
}
|
|
99
349
|
}}
|
|
350
|
+
onDragCancel={(event) => {
|
|
351
|
+
setActiveId(null);
|
|
352
|
+
setDragAnchorPoint(null);
|
|
353
|
+
onDragCancel?.(event);
|
|
354
|
+
}}
|
|
100
355
|
{...restProps}
|
|
101
356
|
>
|
|
102
357
|
{children}
|
|
103
|
-
{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
whiteSpace: 'nowrap',
|
|
111
|
-
background: '#fff',
|
|
112
|
-
border: '1px solid #1890ff',
|
|
113
|
-
borderRadius: 4,
|
|
114
|
-
padding: '4px 12px',
|
|
115
|
-
color: '#1890ff',
|
|
116
|
-
// fontSize: 18,
|
|
117
|
-
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
|
|
118
|
-
}}
|
|
358
|
+
{typeof document !== 'undefined'
|
|
359
|
+
? createPortal(
|
|
360
|
+
<DragOverlay
|
|
361
|
+
dropAnimation={null}
|
|
362
|
+
modifiers={[overlayAnchorModifier]}
|
|
363
|
+
zIndex={2000}
|
|
364
|
+
style={{ pointerEvents: 'none' }}
|
|
119
365
|
>
|
|
120
|
-
{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
366
|
+
{activeId && (
|
|
367
|
+
<span
|
|
368
|
+
data-testid="flow-drag-preview"
|
|
369
|
+
style={{
|
|
370
|
+
display: 'inline-flex',
|
|
371
|
+
alignItems: 'center',
|
|
372
|
+
whiteSpace: 'nowrap',
|
|
373
|
+
background: '#fff',
|
|
374
|
+
border: '1px solid #1890ff',
|
|
375
|
+
borderRadius: 4,
|
|
376
|
+
padding: '4px 12px',
|
|
377
|
+
color: '#1890ff',
|
|
378
|
+
pointerEvents: 'none',
|
|
379
|
+
// fontSize: 18,
|
|
380
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
|
|
381
|
+
}}
|
|
382
|
+
>
|
|
383
|
+
{flowEngine.translate('Dragging')}
|
|
384
|
+
</span>
|
|
385
|
+
)}
|
|
386
|
+
</DragOverlay>,
|
|
387
|
+
document.body,
|
|
388
|
+
)
|
|
389
|
+
: null}
|
|
126
390
|
</DndContext>
|
|
127
391
|
);
|
|
128
392
|
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { Select } from 'antd';
|
|
10
|
+
import { Select, Tooltip } from 'antd';
|
|
11
11
|
import React, { useEffect, useRef, useState } from 'react';
|
|
12
12
|
import { useFlowEngineContext } from '../../../../provider';
|
|
13
13
|
|
|
@@ -19,6 +19,7 @@ export interface SelectWithTitleProps {
|
|
|
19
19
|
itemKey?: string;
|
|
20
20
|
onChange?: (...args: any[]) => void;
|
|
21
21
|
dropdownRender?: any;
|
|
22
|
+
tooltip?: any;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export function SelectWithTitle({
|
|
@@ -28,6 +29,7 @@ export function SelectWithTitle({
|
|
|
28
29
|
options,
|
|
29
30
|
fieldNames,
|
|
30
31
|
itemKey,
|
|
32
|
+
tooltip,
|
|
31
33
|
...others
|
|
32
34
|
}: SelectWithTitleProps) {
|
|
33
35
|
const [open, setOpen] = useState(false);
|
|
@@ -66,6 +68,17 @@ export function SelectWithTitle({
|
|
|
66
68
|
setValue(val);
|
|
67
69
|
onChange?.({ [itemKey]: val });
|
|
68
70
|
};
|
|
71
|
+
const titleNode = (
|
|
72
|
+
<span
|
|
73
|
+
style={{
|
|
74
|
+
whiteSpace: 'nowrap', // 不换行
|
|
75
|
+
flexShrink: 0, // 不被挤压
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{title}
|
|
79
|
+
</span>
|
|
80
|
+
);
|
|
81
|
+
|
|
69
82
|
return (
|
|
70
83
|
<div
|
|
71
84
|
style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}
|
|
@@ -79,14 +92,13 @@ export function SelectWithTitle({
|
|
|
79
92
|
}, 200);
|
|
80
93
|
}}
|
|
81
94
|
>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
</span>
|
|
95
|
+
{tooltip ? (
|
|
96
|
+
<Tooltip title={tooltip} placement="top" destroyTooltipOnHide>
|
|
97
|
+
{titleNode}
|
|
98
|
+
</Tooltip>
|
|
99
|
+
) : (
|
|
100
|
+
titleNode
|
|
101
|
+
)}
|
|
90
102
|
<Select
|
|
91
103
|
{...others}
|
|
92
104
|
open={open}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { ExclamationCircleOutlined, MenuOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
|
11
|
+
import { css } from '@emotion/css';
|
|
11
12
|
import type { DropdownProps, MenuProps } from 'antd';
|
|
12
13
|
import { App, Dropdown, Modal, Tooltip, theme } from 'antd';
|
|
13
14
|
import React, { startTransition, useCallback, useEffect, useMemo, useState, FC } from 'react';
|
|
@@ -26,6 +27,26 @@ import { useNiceDropdownMaxHeight } from '../../../../hooks';
|
|
|
26
27
|
import { SwitchWithTitle } from '../component/SwitchWithTitle';
|
|
27
28
|
import { SelectWithTitle } from '../component/SelectWithTitle';
|
|
28
29
|
import type { FlowSettingsContext } from '../../../../flowContext';
|
|
30
|
+
|
|
31
|
+
const findExtraMenuItemByKey = (
|
|
32
|
+
items: FlowModelExtraMenuItem[],
|
|
33
|
+
targetKey: string,
|
|
34
|
+
): FlowModelExtraMenuItem | undefined => {
|
|
35
|
+
for (const item of items) {
|
|
36
|
+
const itemKey = String(item?.key ?? '');
|
|
37
|
+
if (itemKey === targetKey) {
|
|
38
|
+
return item;
|
|
39
|
+
}
|
|
40
|
+
if (item.children?.length) {
|
|
41
|
+
const matched = findExtraMenuItemByKey(item.children, targetKey);
|
|
42
|
+
if (matched) {
|
|
43
|
+
return matched;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
};
|
|
49
|
+
|
|
29
50
|
// Type definitions for better type safety
|
|
30
51
|
interface StepInfo {
|
|
31
52
|
stepKey: string;
|
|
@@ -188,15 +209,42 @@ interface DefaultSettingsIconProps {
|
|
|
188
209
|
showCopyUidButton?: boolean;
|
|
189
210
|
menuLevels?: number; // Menu levels: 1=current model only (default), 2=include sub-models
|
|
190
211
|
flattenSubMenus?: boolean; // Whether to flatten sub-menus: false=group by model (default), true=flatten all
|
|
212
|
+
onDropdownVisibleChange?: (open: boolean) => void;
|
|
213
|
+
getPopupContainer?: DropdownProps['getPopupContainer'];
|
|
191
214
|
[key: string]: any; // Allow additional props
|
|
192
215
|
}
|
|
193
216
|
|
|
217
|
+
const TOOLBAR_ICONS_SELECTOR = '.nb-toolbar-container-icons';
|
|
218
|
+
const TOOLBAR_CONTAINER_SELECTOR = '.nb-toolbar-container';
|
|
219
|
+
const TOOLBAR_DROPDOWN_OVERLAY_CLASS = css`
|
|
220
|
+
width: max-content;
|
|
221
|
+
min-width: max-content;
|
|
222
|
+
|
|
223
|
+
.ant-dropdown-menu {
|
|
224
|
+
width: max-content;
|
|
225
|
+
min-width: max-content;
|
|
226
|
+
}
|
|
227
|
+
`;
|
|
228
|
+
|
|
229
|
+
const getToolbarPopupContainer = (triggerNode?: HTMLElement | null) => {
|
|
230
|
+
if (!triggerNode) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
(triggerNode.closest(TOOLBAR_ICONS_SELECTOR) as HTMLElement | null) ||
|
|
236
|
+
(triggerNode.closest(TOOLBAR_CONTAINER_SELECTOR) as HTMLElement | null)
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
194
240
|
export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
195
241
|
model,
|
|
196
242
|
showDeleteButton = true,
|
|
197
243
|
showCopyUidButton = true,
|
|
198
244
|
menuLevels = 1, // 默认一级菜单
|
|
199
245
|
flattenSubMenus = true,
|
|
246
|
+
onDropdownVisibleChange,
|
|
247
|
+
getPopupContainer,
|
|
200
248
|
}) => {
|
|
201
249
|
const { message } = App.useApp();
|
|
202
250
|
const t = useMemo(() => getT(model), [model]);
|
|
@@ -210,15 +258,38 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
210
258
|
const [isLoading, setIsLoading] = useState(true);
|
|
211
259
|
const closeDropdown = useCallback(() => {
|
|
212
260
|
setVisible(false);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
261
|
+
onDropdownVisibleChange?.(false);
|
|
262
|
+
}, [onDropdownVisibleChange]);
|
|
263
|
+
const resolvePopupContainer = useCallback<NonNullable<DropdownProps['getPopupContainer']>>(
|
|
264
|
+
(triggerNode) => {
|
|
265
|
+
// 工具栏自身容器必须优先,保证鼠标从 icon 移到菜单时仍处于同一 hover 树。
|
|
266
|
+
// 弹窗场景的裁剪问题由 useFloatToolbarPortal 负责把 toolbar 挂到正确的 popup host。
|
|
267
|
+
return (
|
|
268
|
+
getToolbarPopupContainer(triggerNode) ||
|
|
269
|
+
getPopupContainer?.(triggerNode) ||
|
|
270
|
+
triggerNode?.parentElement ||
|
|
271
|
+
document.body
|
|
272
|
+
);
|
|
273
|
+
},
|
|
274
|
+
[getPopupContainer],
|
|
275
|
+
);
|
|
276
|
+
const handleOpenChange: DropdownProps['onOpenChange'] = useCallback(
|
|
277
|
+
(nextOpen: boolean, info) => {
|
|
278
|
+
if (info.source === 'trigger' || nextOpen) {
|
|
279
|
+
// 当鼠标快速滑过时,终止菜单的渲染,防止卡顿
|
|
280
|
+
startTransition(() => {
|
|
281
|
+
setVisible(nextOpen);
|
|
282
|
+
});
|
|
283
|
+
onDropdownVisibleChange?.(nextOpen);
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
[onDropdownVisibleChange],
|
|
287
|
+
);
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
return () => {
|
|
290
|
+
onDropdownVisibleChange?.(false);
|
|
291
|
+
};
|
|
292
|
+
}, [onDropdownVisibleChange]);
|
|
222
293
|
const dropdownMaxHeight = useNiceDropdownMaxHeight([visible]);
|
|
223
294
|
useEffect(() => {
|
|
224
295
|
let mounted = true;
|
|
@@ -422,7 +493,11 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
422
493
|
return;
|
|
423
494
|
}
|
|
424
495
|
|
|
425
|
-
const extra =
|
|
496
|
+
const extra =
|
|
497
|
+
findExtraMenuItemByKey(extraMenuItems, originalKey) || findExtraMenuItemByKey(extraMenuItems, cleanKey);
|
|
498
|
+
if (extra?.disabled) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
426
501
|
if (extra?.onClick) {
|
|
427
502
|
closeDropdown();
|
|
428
503
|
extra.onClick();
|
|
@@ -833,6 +908,9 @@ export const DefaultSettingsIcon: React.FC<DefaultSettingsIconProps> = ({
|
|
|
833
908
|
|
|
834
909
|
return (
|
|
835
910
|
<Dropdown
|
|
911
|
+
getPopupContainer={resolvePopupContainer}
|
|
912
|
+
overlayClassName={TOOLBAR_DROPDOWN_OVERLAY_CLASS}
|
|
913
|
+
overlayStyle={{ width: 'max-content', minWidth: 'max-content' }}
|
|
836
914
|
onOpenChange={handleOpenChange}
|
|
837
915
|
open={visible}
|
|
838
916
|
menu={{
|