Skip to content

Commit 4731dba

Browse files
authoredMar 10, 2023
Updates for drag drop demo (#8796)

File tree

5 files changed

+391
-3
lines changed

5 files changed

+391
-3
lines changed
 

‎packages/react-integration/demo-app-ts/src/components/demos/TopologyDemo/useTopologyOptions.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ export const useTopologyOptions = (
366366
/>
367367
<Button
368368
variant="link"
369-
isDisabled={numNodes === undefined || numNodes < 2 || numEdges === undefined || numGroups === undefined}
369+
isDisabled={numNodes === undefined || numNodes < 1 || numEdges === undefined || numGroups === undefined}
370370
onClick={() => setCreationCounts({ numNodes, numEdges, numGroups })}
371371
>
372372
Apply
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
id: Drag and Drop
3+
section: topology
4+
sortValue: 24
5+
sourceLink: https://github.com/patternfly/patternfly-react/blob/main/packages/react-topology/src/components/TopologyView/examples/TopologyDragDropDemo.tsx
6+
propComponents: ['DefaultNode', 'DefaultEdge', 'withDndDrop']
7+
---
8+
Note: Topology lives in its own package at [`@patternfly/react-topology`](https://www.npmjs.com/package/@patternfly/react-topology)
9+
10+
import {
11+
ColaLayout,
12+
DefaultEdge,
13+
DefaultGroup,
14+
DefaultNode,
15+
EdgeStyle,
16+
GraphComponent,
17+
graphDropTargetSpec,
18+
groupDropTargetSpec,
19+
ModelKind,
20+
nodeDragSourceSpec,
21+
nodeDropTargetSpec,
22+
NodeShape,
23+
NodeStatus,
24+
SELECTION_EVENT,
25+
Visualization,
26+
VisualizationProvider,
27+
VisualizationSurface,
28+
withDndDrop,
29+
withDragNode,
30+
withPanZoom,
31+
withSelection,
32+
withTargetDrag,
33+
} from '@patternfly/react-topology';
34+
import Icon1 from '@patternfly/react-icons/dist/esm/icons/regions-icon';
35+
import Icon2 from '@patternfly/react-icons/dist/esm/icons/folder-open-icon';
36+
37+
import './topology-example.css';
38+
39+
### Drag and Drop
40+
41+
To add drag and drop functionality, in your component factory:
42+
43+
Nodes can be dragged about the canvas by using the `useDragNode` hook or by wrapping the node component with `withDragNode`.
44+
These utilities will provide a `dragNodeRef`. This ref should be added to outer element of the Node where the user can click and drag the node.
45+
`DefaultNode` accepts the `dragNodeRef` and adds it appropriately.
46+
47+
Edges can be dragged in order to change the source and/or target of the edge using the `withSourceDrag` and/or `withTargetDrag`. These utilities
48+
will provide a `sourceDragRef` and a `targetDragRef` which should be added to the respective terminals for the edge. `DefaultEdge`
49+
accepts these refs and adds them to the appropriate terminals.
50+
51+
52+
```ts file='./TopologyDragDropDemo.tsx'
53+
```
54+
55+
## Functions
56+
### withDragNode
57+
```noLive
58+
/**
59+
* Parameters:
60+
* spec: The drag source spec
61+
* Returns:
62+
* function that takes the draggable node component and returns the draggable component
63+
* which is also passed 'dragNodeRef' to attach to the element.
64+
**/
65+
export const withDragNode = (spec?: DragSourceSpec) =>
66+
(wrappedComponent: React.FunctionComponent) => React.ComponentType);
67+
```
68+
### withSourceDrag
69+
```noLive
70+
/**
71+
* Parameters:
72+
* spec: The drag source spec
73+
* Returns:
74+
* function that takes the draggable edge component and returns the draggable component
75+
* which is also passed 'sourceDragRef' to attach to the element.
76+
**/
77+
export const withSourceDrag = (spec: DragSourceSpec) =>
78+
(wrappedComponent: React.FunctionComponent) => React.ComponentType);
79+
80+
```
81+
### withTargetDrag
82+
```noLive
83+
/**
84+
* Parameters:
85+
* spec: The drag source spec
86+
* Returns:
87+
* function that takes the draggable edge component and returns the draggable component
88+
* which is also passed 'targetDragRef' to attach to the element.
89+
**/
90+
export const withTargetDrag = (spec: DragSourceSpec) =>
91+
(wrappedComponent: React.FunctionComponent) => React.ComponentType);
92+
```
93+
### withDndDrop
94+
```noLive
95+
/**
96+
* Parameters:
97+
* spec: The drop target spec
98+
* Returns:
99+
* function that takes the droppable component and returns the droppable component
100+
* which is also passed 'dndDropRef' to attach to the element's drop zone.
101+
**/
102+
export const withDndDrop = (spec: DropTargetSpec) =>
103+
(wrappedComponent: React.FunctionComponent) => React.ComponentType);
104+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
/* eslint-disable no-console */
2+
import * as React from 'react';
3+
4+
// eslint-disable-next-line patternfly-react/import-tokens-icons
5+
import { RegionsIcon as Icon1 } from '@patternfly/react-icons';
6+
// eslint-disable-next-line patternfly-react/import-tokens-icons
7+
import { FolderOpenIcon as Icon2 } from '@patternfly/react-icons';
8+
9+
import {
10+
ColaLayout,
11+
ComponentFactory,
12+
DefaultEdge,
13+
DefaultGroup,
14+
DefaultNode,
15+
DragObjectWithType,
16+
Edge,
17+
EdgeStyle,
18+
Graph,
19+
GraphComponent,
20+
graphDropTargetSpec,
21+
groupDropTargetSpec,
22+
Layout,
23+
LayoutFactory,
24+
Model,
25+
ModelKind,
26+
Node,
27+
nodeDragSourceSpec,
28+
nodeDropTargetSpec,
29+
NodeModel,
30+
NodeShape,
31+
NodeStatus,
32+
SELECTION_EVENT,
33+
Visualization,
34+
VisualizationProvider,
35+
VisualizationSurface,
36+
withDndDrop,
37+
withDragNode,
38+
WithDndDropProps,
39+
WithDragNodeProps,
40+
withPanZoom,
41+
withSelection,
42+
WithSelectionProps,
43+
withTargetDrag
44+
} from '@patternfly/react-topology';
45+
46+
interface CustomNodeProps {
47+
element: Node;
48+
}
49+
50+
const BadgeColors = [
51+
{
52+
name: 'A',
53+
badgeColor: '#ace12e',
54+
badgeTextColor: '#0f280d',
55+
badgeBorderColor: '#486b00'
56+
},
57+
{
58+
name: 'B',
59+
badgeColor: '#F2F0FC',
60+
badgeTextColor: '#5752d1',
61+
badgeBorderColor: '#CBC1FF'
62+
}
63+
];
64+
65+
const CustomNode: React.FC<CustomNodeProps & WithSelectionProps & WithDragNodeProps & WithDndDropProps> = ({
66+
element,
67+
selected,
68+
onSelect,
69+
...rest
70+
}) => {
71+
const data = element.getData();
72+
const Icon = data.icon;
73+
const badgeColors = BadgeColors.find(badgeColor => badgeColor.name === data.badge);
74+
return (
75+
<DefaultNode
76+
element={element}
77+
showStatusDecorator
78+
badge={data.badge}
79+
badgeColor={badgeColors?.badgeColor}
80+
badgeTextColor={badgeColors?.badgeTextColor}
81+
badgeBorderColor={badgeColors?.badgeBorderColor}
82+
onSelect={onSelect}
83+
selected={selected}
84+
{...rest}
85+
>
86+
<g transform={`translate(25, 25)`}>
87+
<Icon style={{ color: '#393F44' }} width={25} height={25} />
88+
</g>
89+
</DefaultNode>
90+
);
91+
};
92+
93+
const customLayoutFactory: LayoutFactory = (type: string, graph: Graph): Layout | undefined =>
94+
new ColaLayout(graph, { layoutOnDrag: false });
95+
96+
const CONNECTOR_TARGET_DROP = 'connector-target-drop';
97+
98+
const customComponentFactory: ComponentFactory = (kind: ModelKind, type: string): any => {
99+
switch (type) {
100+
case 'group':
101+
return withDndDrop(groupDropTargetSpec)(withDragNode(nodeDragSourceSpec('group'))(withSelection()(DefaultGroup)));
102+
default:
103+
switch (kind) {
104+
case ModelKind.graph:
105+
return withDndDrop(graphDropTargetSpec())(withPanZoom()(GraphComponent));
106+
case ModelKind.node:
107+
return withDndDrop(nodeDropTargetSpec([CONNECTOR_TARGET_DROP]))(
108+
withDragNode(nodeDragSourceSpec('node', true, true))(CustomNode)
109+
);
110+
case ModelKind.edge:
111+
return withTargetDrag<
112+
DragObjectWithType,
113+
Node,
114+
{ dragging?: boolean },
115+
{
116+
element: Edge;
117+
}
118+
>({
119+
item: { type: CONNECTOR_TARGET_DROP },
120+
begin: (monitor, props) => {
121+
props.element.raise();
122+
return props.element;
123+
},
124+
drag: (event, monitor, props) => {
125+
props.element.setEndPoint(event.x, event.y);
126+
},
127+
end: (dropResult, monitor, props) => {
128+
if (monitor.didDrop() && dropResult && props) {
129+
props.element.setTarget(dropResult);
130+
}
131+
props.element.setEndPoint();
132+
},
133+
collect: monitor => ({
134+
dragging: monitor.isDragging()
135+
})
136+
})(DefaultEdge);
137+
default:
138+
return undefined;
139+
}
140+
}
141+
};
142+
143+
const NODE_DIAMETER = 75;
144+
145+
const NODES: NodeModel[] = [
146+
{
147+
id: 'node-0',
148+
type: 'node',
149+
label: 'Node 0',
150+
width: NODE_DIAMETER,
151+
height: NODE_DIAMETER,
152+
shape: NodeShape.ellipse,
153+
status: NodeStatus.danger,
154+
data: {
155+
badge: 'B',
156+
icon: Icon1
157+
}
158+
},
159+
{
160+
id: 'node-1',
161+
type: 'node',
162+
label: 'Node 1',
163+
width: NODE_DIAMETER,
164+
height: NODE_DIAMETER,
165+
shape: NodeShape.hexagon,
166+
status: NodeStatus.warning,
167+
data: {
168+
badge: 'B',
169+
icon: Icon1
170+
}
171+
},
172+
{
173+
id: 'node-2',
174+
type: 'node',
175+
label: 'Node 2',
176+
width: NODE_DIAMETER,
177+
height: NODE_DIAMETER,
178+
shape: NodeShape.octagon,
179+
status: NodeStatus.success,
180+
data: {
181+
badge: 'A',
182+
icon: Icon1
183+
}
184+
},
185+
{
186+
id: 'node-3',
187+
type: 'node',
188+
label: 'Node 3',
189+
width: NODE_DIAMETER,
190+
height: NODE_DIAMETER,
191+
shape: NodeShape.rhombus,
192+
status: NodeStatus.info,
193+
data: {
194+
badge: 'A',
195+
icon: Icon1
196+
}
197+
},
198+
{
199+
id: 'node-4',
200+
type: 'node',
201+
label: 'Node 4',
202+
width: NODE_DIAMETER,
203+
height: NODE_DIAMETER,
204+
shape: NodeShape.hexagon,
205+
status: NodeStatus.default,
206+
data: {
207+
badge: 'C',
208+
icon: Icon2
209+
}
210+
},
211+
{
212+
id: 'node-5',
213+
type: 'node',
214+
label: 'Node 5',
215+
width: NODE_DIAMETER,
216+
height: NODE_DIAMETER,
217+
shape: NodeShape.rect,
218+
data: {
219+
badge: 'C',
220+
icon: Icon1
221+
}
222+
},
223+
{
224+
id: 'Group-1',
225+
children: ['node-0', 'node-1', 'node-2'],
226+
type: 'group',
227+
group: true,
228+
label: 'Group-1',
229+
style: {
230+
padding: 40
231+
}
232+
}
233+
];
234+
235+
const EDGES = [
236+
{
237+
id: 'edge-node-4-node-5',
238+
type: 'edge',
239+
source: 'node-4',
240+
target: 'node-5',
241+
edgeStyle: EdgeStyle.default
242+
},
243+
{
244+
id: 'edge-node-0-node-2',
245+
type: 'edge',
246+
source: 'node-0',
247+
target: 'node-2',
248+
edgeStyle: EdgeStyle.default
249+
}
250+
];
251+
252+
export const TopologyDragDropDemo: React.FC = () => {
253+
const [selectedIds, setSelectedIds] = React.useState<string[]>([]);
254+
255+
const controller = React.useMemo(() => {
256+
const model: Model = {
257+
nodes: NODES,
258+
edges: EDGES,
259+
graph: {
260+
id: 'g1',
261+
type: 'graph',
262+
layout: 'Cola'
263+
}
264+
};
265+
266+
const newController = new Visualization();
267+
newController.registerLayoutFactory(customLayoutFactory);
268+
newController.registerComponentFactory(customComponentFactory);
269+
270+
newController.addEventListener(SELECTION_EVENT, setSelectedIds);
271+
272+
newController.fromModel(model, false);
273+
274+
return newController;
275+
}, []);
276+
277+
return (
278+
<VisualizationProvider controller={controller}>
279+
<VisualizationSurface state={{ selectedIds }} />
280+
</VisualizationProvider>
281+
);
282+
};

‎packages/react-topology/src/components/TopologyView/examples/TopologyPipelinesGettingStarted.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ import './topology-pipelines-example.css';
6767
```ts file='./TopologyPipelinesGettingStartedDemo.tsx'
6868
```
6969

70-
#### getSpacerNodes
70+
## Functions
71+
### getSpacerNodes
7172
```noLive
7273
/**
7374
* parameters:
@@ -86,7 +87,7 @@ const getSpacerNodes = (
8687
): PipelineNodeModel[]
8788
```
8889

89-
#### getEdgesFromNodes
90+
### getEdgesFromNodes
9091
```noLive
9192
/**
9293
* parameters:

‎packages/react-topology/src/components/TopologyView/examples/topology-example.css

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
.ws-react-t-context-menu,
44
.ws-react-t-custom-edges,
55
.ws-react-t-custom-nodes,
6+
.ws-react-t-drag-and-drop,
67
.ws-react-t-getting-started,
78
.ws-react-t-layouts,
89
.ws-react-t-panzoom,

0 commit comments

Comments
 (0)
Please sign in to comment.