feat(wrapper): add onSelectionChange handler

This commit is contained in:
moklick
2020-05-25 14:00:46 +02:00
parent 3d80fb6586
commit f154f59e74
9 changed files with 62 additions and 43 deletions
+1
View File
@@ -72,6 +72,7 @@ const BasicFlow = () => (
- `onConnect`: connect handler
- `onLoad`: editor load handler
- `onMove`: move handler
- `onSelectionChange`: fired when element selection changes
- `nodeTypes`: object with [node types](#node-types--custom-nodes)
- `edgeTypes`: object with [edge types](#edge-types--custom-edges)
- `style`: css style passed to the wrapper
+2
View File
@@ -5,6 +5,7 @@ import ReactFlow, { removeElements, addEdge, MiniMap, Controls } from 'react-flo
const onNodeDragStart = node => console.log('drag start', node);
const onNodeDragStop = node => console.log('drag stop', node);
const onElementClick = element => console.log('click', element);
const onSelectionChange = elements => console.log('selection change', elements);
const onLoad = (graph) => {
console.log('graph loaded:', graph);
graph.fitView();
@@ -53,6 +54,7 @@ const OverviewFlow = () => {
onConnect={onConnect}
onNodeDragStart={onNodeDragStart}
onNodeDragStop={onNodeDragStop}
onSelectionChange={onSelectionChange}
style={{ width: '100%', height: '100%' }}
onLoad={onLoad}
connectionLineStyle={{ stroke: '#ddd', strokeWidth: 2 }}
+14 -10
View File
@@ -50,9 +50,11 @@ export default memo(() => {
};
const offsetX: number = scaledClient.x - position.x - tx;
const offsetY: number = scaledClient.y - position.y - ty;
const selectedNodes = (state.selectedElements.filter(isNode) as Node[]).map(
(selectedNode) => state.nodes.find((node) => node.id === selectedNode.id)! as Node
);
const selectedNodes = state.selectedElements
? (state.selectedElements.filter(isNode) as Node[]).map(
(selectedNode) => state.nodes.find((node) => node.id === selectedNode.id)! as Node
)
: [];
const nextStartPositions = getStartPositions(selectedNodes);
@@ -68,14 +70,16 @@ export default memo(() => {
y: evt.clientY / tScale,
};
(state.selectedElements.filter(isNode) as Node[]).forEach((node) => {
const pos: XYPosition = {
x: startPositions[node.id].x + scaledClient.x - position.x - offset.x - tx,
y: startPositions[node.id].y + scaledClient.y - position.y - offset.y - ty,
};
if (state.selectedElements) {
(state.selectedElements.filter(isNode) as Node[]).forEach((node) => {
const pos: XYPosition = {
x: startPositions[node.id].x + scaledClient.x - position.x - offset.x - tx,
y: startPositions[node.id].y + scaledClient.y - position.y - offset.y - ty,
};
updateNodePos({ id: node.id, pos });
});
updateNodePos({ id: node.id, pos });
});
}
};
return (
+22
View File
@@ -0,0 +1,22 @@
import { useEffect } from 'react';
import { Elements } from '../../types';
import { useStoreState } from '../../store/hooks';
interface SelectionListenerProps {
onSelectionChange: (elements: Elements | null) => void;
}
// This is just a helper component for calling the onSelectionChange listener.
// As soon as easy-peasy has implemented the effectOn hook, we can remove this compeonent
// and use the hook instead. https://github.com/ctrlplusb/easy-peasy/pull/459
export default ({ onSelectionChange }: SelectionListenerProps) => {
const selectedElements = useStoreState((s) => s.selectedElements);
useEffect(() => {
onSelectionChange(selectedElements);
}, [selectedElements]);
return null;
};
+4 -4
View File
@@ -117,7 +117,7 @@ function renderEdge(
edge: Edge,
props: EdgeRendererProps,
nodes: Node[],
selectedElements: Elements,
selectedElements: Elements | null,
isInteractive: boolean
) {
const [sourceId, sourceHandleId] = edge.source.split('__');
@@ -154,9 +154,9 @@ function renderEdge(
targetPosition
);
const isSelected = (selectedElements as Edge[]).some(
(elm) => isEdge(elm) && elm.source === sourceId && elm.target === targetId
);
const isSelected = selectedElements
? (selectedElements as Edge[]).some((elm) => isEdge(elm) && elm.source === sourceId && elm.target === targetId)
: false;
return (
<EdgeComponent
+2 -2
View File
@@ -16,7 +16,7 @@ function renderNode(
node: Node,
props: NodeRendererProps,
transform: Transform,
selectedElements: Elements,
selectedElements: Elements | null,
isInteractive: boolean
) {
const nodeType = node.type || 'default';
@@ -25,7 +25,7 @@ function renderNode(
console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`);
}
const isSelected = selectedElements.some(({ id }) => id === node.id);
const isSelected = selectedElements ? selectedElements.some(({ id }) => id === node.id) : false;
return (
<NodeComponent
+4 -1
View File
@@ -14,6 +14,7 @@ import DefaultNode from '../../components/Nodes/DefaultNode';
import InputNode from '../../components/Nodes/InputNode';
import OutputNode from '../../components/Nodes/OutputNode';
import { createNodeTypes } from '../NodeRenderer/utils';
import SelectionListener from '../../components/SelectionListener';
import BezierEdge from '../../components/Edges/BezierEdge';
import StraightEdge from '../../components/Edges/StraightEdge';
import StepEdge from '../../components/Edges/StepEdge';
@@ -32,6 +33,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
onConnect: (connection: Edge | Connection) => void;
onLoad: OnLoadFunc;
onMove: () => void;
onSelectionChange: (elements: Elements | null) => void;
nodeTypes: NodeTypesType;
edgeTypes: EdgeTypesType;
connectionLineType: string;
@@ -62,6 +64,7 @@ const ReactFlow = ({
onConnect,
onNodeDragStart,
onNodeDragStop,
onSelectionChange,
connectionLineType,
connectionLineStyle,
deleteKeyCode,
@@ -105,6 +108,7 @@ const ReactFlow = ({
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
isInteractive={isInteractive}
/>
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
{children}
</StoreProvider>
</div>
@@ -143,7 +147,6 @@ ReactFlow.defaultProps = {
snapGrid: [16, 16],
onlyRenderVisibleNodes: true,
isInteractive: true,
className: '',
};
export default ReactFlow;
+4 -7
View File
@@ -11,22 +11,19 @@ interface HookParams {
}
export default ({ deleteKeyCode, onElementsRemove }: HookParams): void => {
const state = useStoreState(s => ({
const state = useStoreState((s) => ({
selectedElements: s.selectedElements,
edges: s.edges,
}));
const setNodesSelection = useStoreActions(a => a.setNodesSelection);
const setNodesSelection = useStoreActions((a) => a.setNodesSelection);
const deleteKeyPressed = useKeyPress(deleteKeyCode);
useEffect(() => {
if (deleteKeyPressed && state.selectedElements.length) {
if (deleteKeyPressed && state.selectedElements) {
let elementsToRemove = state.selectedElements;
// we also want to remove the edges if only one node is selected
if (
state.selectedElements.length === 1 &&
!isEdge(state.selectedElements[0])
) {
if (state.selectedElements.length === 1 && !isEdge(state.selectedElements[0])) {
const node = (state.selectedElements[0] as unknown) as Node;
const connectedEdges = getConnectedEdges([node], state.edges);
elementsToRemove = [...state.selectedElements, ...connectedEdges];
+9 -19
View File
@@ -56,7 +56,7 @@ export interface StoreModel {
transform: Transform;
nodes: Node[];
edges: Edge[];
selectedElements: Elements;
selectedElements: Elements | null;
selectedNodesBbox: Rect;
d3Zoom: ZoomBehavior<Element, unknown> | null;
@@ -95,8 +95,6 @@ export interface StoreModel {
setSelectedElements: Action<StoreModel, Elements | Node | Edge>;
updateSelection: Action<StoreModel, SelectionRect>;
updateTransform: Action<StoreModel, TransformXYK>;
updateSize: Action<StoreModel, Dimensions>;
@@ -122,7 +120,7 @@ const storeModel: StoreModel = {
transform: [0, 0, 1],
nodes: [],
edges: [],
selectedElements: [],
selectedElements: null,
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
d3Zoom: null,
@@ -250,8 +248,11 @@ const storeModel: StoreModel = {
const selectedElementsUpdated = !isEqual(nextSelectedElements, state.selectedElements);
state.selection = nextRect;
state.selectedElements = selectedElementsUpdated ? nextSelectedElements : state.selectedElements;
state.userSelectionRect = nextRect;
if (selectedElementsUpdated) {
state.selectedElements = nextSelectedElements.length > 0 ? nextSelectedElements : null;
}
}),
unsetUserSelection: action((state) => {
@@ -261,7 +262,7 @@ const storeModel: StoreModel = {
state.selectionActive = false;
state.userSelectionRect = { ...state.userSelectionRect, draw: false };
state.nodesSelectionActive = false;
state.selectedElements = [];
state.selectedElements = null;
return;
}
@@ -283,7 +284,7 @@ const storeModel: StoreModel = {
setNodesSelection: action((state, { isActive, selection }) => {
if (!isActive || typeof selection === 'undefined') {
state.nodesSelectionActive = false;
state.selectedElements = [];
state.selectedElements = null;
return;
}
@@ -291,7 +292,7 @@ const storeModel: StoreModel = {
if (!selectedNodes.length) {
state.nodesSelectionActive = false;
state.selectedElements = [];
state.selectedElements = null;
return;
}
@@ -311,17 +312,6 @@ const storeModel: StoreModel = {
state.selectedElements = selectedElements;
}),
updateSelection: action((state, selection) => {
const selectedNodes = getNodesInside(state.nodes, selection, state.transform);
const selectedEdges = getConnectedEdges(selectedNodes, state.edges);
const nextSelectedElements = [...selectedNodes, ...selectedEdges];
const selectedElementsUpdated = !isEqual(nextSelectedElements, state.selectedElements);
state.selection = selection;
state.selectedElements = selectedElementsUpdated ? nextSelectedElements : state.selectedElements;
}),
updateTransform: action((state, transform) => {
state.transform = [transform.x, transform.y, transform.k];
}),