Skip to content

Commit b78726a

Browse files
authored
Merge pull request #390
feat(21481): Create nodes from dropping an edge from a source * fix(21481): fix handle name * feat(21481): add support for creating nodes on drop * feat(21481): add mapping for valid dropped nodes * feat(21481): add visual feedback for the dropped node * fix(21481): remove optional chaining * fix(21481): fix rebase
1 parent 5752ba6 commit b78726a

File tree

5 files changed

+259
-6
lines changed

5 files changed

+259
-6
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { FC, useMemo } from 'react'
2+
import { ConnectionLineComponentProps, getSimpleBezierPath } from 'reactflow'
3+
import { Tag } from '@chakra-ui/react'
4+
5+
import { getConnectedNodeFrom } from '@datahub/utils/node.utils.ts'
6+
import { NodeIcon } from '@datahub/components/helpers'
7+
8+
const ConnectionLine: FC<ConnectionLineComponentProps> = ({
9+
fromHandle,
10+
fromNode,
11+
fromX,
12+
fromY,
13+
toY,
14+
toX,
15+
...props
16+
}) => {
17+
const droppedNode = useMemo(() => {
18+
return getConnectedNodeFrom(fromNode?.type, fromHandle?.id)
19+
}, [fromHandle?.id, fromNode?.type])
20+
21+
const pathParams = {
22+
sourceX: fromX,
23+
sourceY: fromY,
24+
sourcePosition: props.fromPosition,
25+
targetX: toX,
26+
targetY: toY,
27+
targetPosition: props.toPosition,
28+
}
29+
30+
const [d] = getSimpleBezierPath(pathParams)
31+
32+
return (
33+
<g>
34+
<path fill="none" stroke="grey" strokeWidth={1.5} className="animated" d={d} />
35+
{props.connectionStatus === null && (
36+
<foreignObject x={toX} y={toY - 20} width="50px" height="50px">
37+
<Tag size="lg" colorScheme="gray" borderRadius="full" variant="outline">
38+
<NodeIcon type={droppedNode?.type} />
39+
</Tag>
40+
</foreignObject>
41+
)}
42+
</g>
43+
)
44+
}
45+
46+
export default ConnectionLine

hivemq-edge/src/frontend/src/extensions/datahub/components/pages/PolicyEditor.tsx

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import React, { FC, useCallback, useMemo, useRef, useState } from 'react'
2-
import ReactFlow, { Connection, Node, ReactFlowInstance, ReactFlowProvider, XYPosition } from 'reactflow'
2+
import ReactFlow, {
3+
Connection,
4+
HandleType,
5+
Node,
6+
NodeAddChange,
7+
ReactFlowInstance,
8+
ReactFlowProvider,
9+
XYPosition,
10+
} from 'reactflow'
311
import { Outlet } from 'react-router-dom'
412
import { useTranslation } from 'react-i18next'
513
import { Box } from '@chakra-ui/react'
@@ -8,19 +16,30 @@ import styles from './PolicyEditor.module.scss'
816

917
import { CustomNodeTypes } from '@datahub/designer/mappings.tsx'
1018
import useDataHubDraftStore from '@datahub/hooks/useDataHubDraftStore.ts'
11-
import { getNodeId, getNodePayload, isValidPolicyConnection } from '@datahub/utils/node.utils.ts'
19+
import { getConnectedNodeFrom, getNodeId, getNodePayload, isValidPolicyConnection } from '@datahub/utils/node.utils.ts'
1220
import CanvasControls from '@datahub/components/controls/CanvasControls.tsx'
1321
import Minimap from '@datahub/components/controls/Minimap.tsx'
1422
import DesignerToolbox from '@datahub/components/controls/DesignerToolbox.tsx'
1523
import ToolboxSelectionListener from '@datahub/components/controls/ToolboxSelectionListener.tsx'
1624
import { CopyPasteListener } from '@datahub/components/controls/CopyPasteListener.tsx'
1725
import CopyPasteStatus from '@datahub/components/controls/CopyPasteStatus.tsx'
26+
import ConnectionLine from '@datahub/components/nodes/ConnectionLine.tsx'
27+
28+
export type OnConnectStartParams = {
29+
nodeId: string | null
30+
handleId: string | null
31+
handleType: HandleType | null
32+
}
33+
interface OnConnectStartParamsNode extends OnConnectStartParams {
34+
type: string | undefined
35+
}
1836

1937
const PolicyEditor: FC = () => {
2038
const { t } = useTranslation('datahub')
2139
const reactFlowWrapper = useRef(null)
2240
const [reactFlowInstance, setReactFlowInstance] = useState<ReactFlowInstance | null>(null)
2341
const { nodes, edges, onNodesChange, onEdgesChange, onConnect, onAddNodes } = useDataHubDraftStore()
42+
const edgeConnectStart = useRef<OnConnectStartParamsNode | undefined>(undefined)
2443

2544
const nodeTypes = useMemo(() => CustomNodeTypes, [])
2645

@@ -64,6 +83,60 @@ const PolicyEditor: FC = () => {
6483
[onAddNodes, reactFlowInstance]
6584
)
6685

86+
const onConnectStart = useCallback(
87+
(_: unknown, params: OnConnectStartParams) => {
88+
const nodeFound = nodes.find((e) => e.id === params.nodeId)
89+
edgeConnectStart.current = undefined
90+
if (nodeFound) {
91+
edgeConnectStart.current = { ...params, type: nodeFound.type }
92+
}
93+
},
94+
[nodes]
95+
)
96+
97+
const onConnectEnd = useCallback(
98+
(event: MouseEvent | TouchEvent) => {
99+
const targetElement = event.target as Element
100+
const isTargetCanvas = targetElement.classList.contains('react-flow__pane')
101+
102+
if (isTargetCanvas && edgeConnectStart.current && reactFlowInstance) {
103+
const { type, handleId, nodeId } = edgeConnectStart.current
104+
105+
const droppedNode = getConnectedNodeFrom(type, handleId)
106+
if (droppedNode) {
107+
const id = getNodeId()
108+
const newNode: Node = {
109+
id,
110+
position: reactFlowInstance.screenToFlowPosition({
111+
x: (event as MouseEvent).clientX || (event as TouchEvent).touches[0].clientX,
112+
y: (event as MouseEvent).clientY || (event as TouchEvent).touches[0].clientY,
113+
}),
114+
type: droppedNode.type,
115+
data: getNodePayload(droppedNode.type),
116+
}
117+
118+
const edgeConnection: Connection = droppedNode.isSource
119+
? {
120+
target: nodeId,
121+
targetHandle: handleId,
122+
source: id,
123+
sourceHandle: droppedNode.handle,
124+
}
125+
: {
126+
source: nodeId,
127+
sourceHandle: handleId,
128+
target: id,
129+
targetHandle: droppedNode.handle,
130+
}
131+
132+
onAddNodes([{ item: newNode, type: 'add' } as NodeAddChange])
133+
onConnect(edgeConnection)
134+
}
135+
}
136+
},
137+
[onAddNodes, onConnect, reactFlowInstance]
138+
)
139+
67140
return (
68141
<>
69142
<ReactFlowProvider>
@@ -76,15 +149,15 @@ const PolicyEditor: FC = () => {
76149
onNodesChange={onNodesChange}
77150
onEdgesChange={onEdgesChange}
78151
// onEdgeUpdate={onEdgeUpdate}
79-
// onConnectStart={onConnectStart}
80-
// onConnectEnd={onConnectEnd}
152+
onConnectStart={onConnectStart}
153+
onConnectEnd={onConnectEnd}
81154
onConnect={onConnect}
155+
connectionLineComponent={ConnectionLine}
82156
onInit={setReactFlowInstance}
83157
fitView
84158
snapToGrid
85159
snapGrid={[25, 25]}
86160
className={styles.dataHubFlow}
87-
// nodesConnectable
88161
onDragOver={onDragOver}
89162
onDrop={onDrop}
90163
isValidConnection={checkValidity}

hivemq-edge/src/frontend/src/extensions/datahub/designer/behavior_policy/BehaviorPolicyNode.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const BehaviorPolicyNode: FC<NodeProps<BehaviorPolicyData>> = (props) =>
2727
</VStack>
2828
</NodeWrapper>
2929
<CustomHandle type="target" position={Position.Left} id={BehaviorPolicyData.Handle.CLIENT_FILTER} />
30-
<CustomHandle type="source" position={Position.Right} id="transitions" />
30+
<CustomHandle type="source" position={Position.Right} id={BehaviorPolicyData.Handle.TRANSITIONS} />
3131
</>
3232
)
3333
}

hivemq-edge/src/frontend/src/extensions/datahub/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ export interface BehaviorPolicyData extends DataHubNodeData {
285285
export namespace BehaviorPolicyData {
286286
export enum Handle {
287287
CLIENT_FILTER = 'clientFilter',
288+
TRANSITIONS = 'transitions',
288289
}
289290
}
290291

hivemq-edge/src/frontend/src/extensions/datahub/utils/node.utils.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,136 @@ export const isDataPolicyNodeType = (node: Node): node is Node<DataPolicyData> =
164164

165165
export const isBehaviorPolicyNodeType = (node: Node): node is Node<BehaviorPolicyData> =>
166166
node.type === DataHubNodeType.BEHAVIOR_POLICY
167+
168+
interface ValidDropConnection {
169+
type: DataHubNodeType
170+
handle: string | null
171+
isSource: boolean
172+
}
173+
174+
// TODO[NVL] Would a map object->Object make this process easier and more performant?
175+
export const getConnectedNodeFrom = (node?: string, handle?: string | null): ValidDropConnection | undefined => {
176+
if (node === DataHubNodeType.TOPIC_FILTER) {
177+
return {
178+
type: DataHubNodeType.DATA_POLICY,
179+
handle: DataPolicyData.Handle.TOPIC_FILTER,
180+
isSource: false,
181+
}
182+
}
183+
if (node === DataHubNodeType.CLIENT_FILTER) {
184+
return {
185+
type: DataHubNodeType.BEHAVIOR_POLICY,
186+
handle: BehaviorPolicyData.Handle.CLIENT_FILTER,
187+
isSource: false,
188+
}
189+
}
190+
if (node === DataHubNodeType.DATA_POLICY && handle === DataPolicyData.Handle.TOPIC_FILTER) {
191+
return {
192+
type: DataHubNodeType.TOPIC_FILTER,
193+
handle: null,
194+
isSource: true,
195+
}
196+
}
197+
if (node === DataHubNodeType.DATA_POLICY && handle === DataPolicyData.Handle.VALIDATION) {
198+
return {
199+
type: DataHubNodeType.VALIDATOR,
200+
handle: null,
201+
isSource: true,
202+
}
203+
}
204+
if (
205+
node === DataHubNodeType.DATA_POLICY &&
206+
(handle === DataPolicyData.Handle.ON_SUCCESS || handle === DataPolicyData.Handle.ON_ERROR)
207+
) {
208+
return {
209+
type: DataHubNodeType.OPERATION,
210+
handle: null,
211+
isSource: false,
212+
}
213+
}
214+
if (node === DataHubNodeType.VALIDATOR && handle === 'source') {
215+
return {
216+
type: DataHubNodeType.DATA_POLICY,
217+
handle: DataPolicyData.Handle.VALIDATION,
218+
isSource: false,
219+
}
220+
}
221+
if (node === DataHubNodeType.VALIDATOR && handle === 'target') {
222+
return {
223+
type: DataHubNodeType.SCHEMA,
224+
handle: null,
225+
isSource: true,
226+
}
227+
}
228+
if (node === DataHubNodeType.SCHEMA) {
229+
// There are two possibilities: DataHubNodeType.VALIDATOR and DataHubNodeType.OPERATION (transform)
230+
// We need some form of feedback
231+
return undefined
232+
}
233+
if (node === DataHubNodeType.OPERATION && handle === OperationData.Handle.OUTPUT) {
234+
return {
235+
type: DataHubNodeType.OPERATION,
236+
handle: OperationData.Handle.INPUT,
237+
isSource: false,
238+
}
239+
}
240+
if (node === DataHubNodeType.OPERATION && handle === OperationData.Handle.INPUT) {
241+
return {
242+
type: DataHubNodeType.OPERATION,
243+
handle: OperationData.Handle.OUTPUT,
244+
isSource: true,
245+
}
246+
}
247+
if (
248+
node === DataHubNodeType.OPERATION &&
249+
(handle === OperationData.Handle.SCHEMA ||
250+
handle === OperationData.Handle.SERIALISER ||
251+
handle === OperationData.Handle.DESERIALISER)
252+
) {
253+
return {
254+
type: DataHubNodeType.SCHEMA,
255+
handle: null,
256+
isSource: true,
257+
}
258+
}
259+
if (node === DataHubNodeType.OPERATION && handle === OperationData.Handle.FUNCTION) {
260+
return {
261+
type: DataHubNodeType.FUNCTION,
262+
handle: null,
263+
isSource: true,
264+
}
265+
}
266+
if (node === DataHubNodeType.FUNCTION) {
267+
// This is mapping to a specific data content of the OPERATION node. Not supported yet
268+
return undefined
269+
}
270+
if (node === DataHubNodeType.BEHAVIOR_POLICY && handle === BehaviorPolicyData.Handle.CLIENT_FILTER) {
271+
return {
272+
type: DataHubNodeType.CLIENT_FILTER,
273+
handle: null,
274+
isSource: true,
275+
}
276+
}
277+
if (node === DataHubNodeType.BEHAVIOR_POLICY && handle === BehaviorPolicyData.Handle.TRANSITIONS) {
278+
return {
279+
type: DataHubNodeType.TRANSITION,
280+
handle: null,
281+
isSource: false,
282+
}
283+
}
284+
if (node === DataHubNodeType.TRANSITION && handle === TransitionData.Handle.BEHAVIOR_POLICY) {
285+
return {
286+
type: DataHubNodeType.BEHAVIOR_POLICY,
287+
handle: BehaviorPolicyData.Handle.TRANSITIONS,
288+
isSource: true,
289+
}
290+
}
291+
if (node === DataHubNodeType.TRANSITION && handle === TransitionData.Handle.OPERATION) {
292+
return {
293+
type: DataHubNodeType.OPERATION,
294+
handle: null,
295+
isSource: false,
296+
}
297+
}
298+
return undefined
299+
}

0 commit comments

Comments
 (0)