feat(keyhandler): add onNodeRemove callback

This commit is contained in:
moklick
2019-07-24 16:44:26 +02:00
parent 7b32ee660c
commit 4be80dd985
8 changed files with 166 additions and 43 deletions
+92 -33
View File
@@ -29798,6 +29798,8 @@
var UPDATE_SELECTION = 'UPDATE_SELECTION'; var UPDATE_SELECTION = 'UPDATE_SELECTION';
var SET_SELECTION = 'SET_SELECTION'; var SET_SELECTION = 'SET_SELECTION';
var SET_NODES_SELECTION = 'SET_NODES_SELECTION'; var SET_NODES_SELECTION = 'SET_NODES_SELECTION';
var SET_SELECTED_NODES_IDS = 'SET_SELECTED_NODES_IDS';
var REMOVE_NODES = 'REMOVE_NODES';
var initialState = { var initialState = {
width: 0, width: 0,
height: 0, height: 0,
@@ -29894,12 +29896,31 @@
}); });
} }
case REMOVE_NODES:
{
var ids = action.payload.ids;
var nextEdges = state.edges.filter(function (e) {
return !ids.includes(e.data.target) && !ids.includes(e.data.source);
});
var nextNodes = state.nodes.filter(function (n) {
return !ids.includes(n.data.id);
});
console.log(ids);
console.log(state.edges, nextEdges);
console.log(state.nodes, nextNodes);
return _objectSpread2({}, state, {
nodes: nextNodes,
edges: nextEdges
});
}
case SET_NODES: case SET_NODES:
case SET_EDGES: case SET_EDGES:
case UPDATE_TRANSFORM: case UPDATE_TRANSFORM:
case INIT_D3: case INIT_D3:
case UPDATE_SIZE: case UPDATE_SIZE:
case SET_SELECTION: case SET_SELECTION:
case SET_SELECTED_NODES_IDS:
return _objectSpread2({}, state, {}, action.payload); return _objectSpread2({}, state, {}, action.payload);
default: default:
@@ -29991,6 +30012,15 @@
} }
}; };
}; };
var setSelectedNodesIds = function setSelectedNodesIds(ids) {
var idArray = Array.isArray(ids) ? ids : [ids];
return {
type: SET_SELECTED_NODES_IDS,
payload: {
selectedNodeIds: idArray
}
};
};
var updateSelection = function updateSelection(selection) { var updateSelection = function updateSelection(selection) {
return { return {
type: UPDATE_SELECTION, type: UPDATE_SELECTION,
@@ -30001,6 +30031,7 @@
}; };
var GraphContext = React.createContext({}); var GraphContext = React.createContext({});
var Provider = function Provider(props) { var Provider = function Provider(props) {
var onNodeClick = props.onNodeClick, var onNodeClick = props.onNodeClick,
children = props.children; children = props.children;
@@ -30284,17 +30315,11 @@
var NodesSelection = (function () { var NodesSelection = (function () {
var graphContext = React.useContext(GraphContext); var graphContext = React.useContext(GraphContext);
var state = graphContext.state, var state = graphContext.state;
dispatch = graphContext.dispatch;
return React__default.createElement("div", { return React__default.createElement("div", {
className: "react-graph__nodesselection", className: "react-graph__nodesselection",
style: { style: {
transform: "translate(".concat(state.transform[0], "px,").concat(state.transform[1], "px) scale(").concat(state.transform[2], ")") transform: "translate(".concat(state.transform[0], "px,").concat(state.transform[1], "px) scale(").concat(state.transform[2], ")")
},
onClick: function onClick() {
return dispatch(setNodesSelection({
isActive: false
}));
} }
}, React__default.createElement("div", { }, React__default.createElement("div", {
className: "react-graph__nodesselection-rect", className: "react-graph__nodesselection-rect",
@@ -30344,11 +30369,15 @@
var GraphView = function GraphView(props) { var GraphView = function GraphView(props) {
var zoomPane = React.useRef(null); var zoomPane = React.useRef(null);
var graphContext = React.useContext(GraphContext);
var _useContext = React.useContext(GraphContext),
state = _useContext.state,
dispatch = _useContext.dispatch;
var shiftPressed = useKeyPress('Shift'); var shiftPressed = useKeyPress('Shift');
React.useEffect(function () { React.useEffect(function () {
var selection = select(zoomPane.current).call(d3ZoomInstance); var selection = select(zoomPane.current).call(d3ZoomInstance);
graphContext.dispatch(initD3({ dispatch(initD3({
zoom: d3ZoomInstance, zoom: d3ZoomInstance,
selection: selection selection: selection
})); }));
@@ -30362,35 +30391,35 @@
return false; return false;
} }
graphContext.dispatch(updateTransform(event.transform)); dispatch(updateTransform(event.transform));
props.onMove(); props.onMove();
}); });
if (graphContext.state.d3Selection) { if (state.d3Selection) {
// we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced // we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced
var graphTransform = identity$1.translate(graphContext.state.transform[0], graphContext.state.transform[1]).scale(graphContext.state.transform[2]); var graphTransform = identity$1.translate(state.transform[0], state.transform[1]).scale(state.transform[2]);
graphContext.state.d3Selection.call(graphContext.state.d3Zoom.transform, graphTransform); state.d3Selection.call(state.d3Zoom.transform, graphTransform);
} }
} }
}, [shiftPressed]); }, [shiftPressed]);
React.useEffect(function () { React.useEffect(function () {
return graphContext.dispatch(updateSize(props.size)); return dispatch(updateSize(props.size));
}, [props.size.width, props.size.height]); }, [props.size.width, props.size.height]);
React.useEffect(function () { React.useEffect(function () {
if (graphContext.state.d3Initialised) { if (state.d3Initialised) {
props.onLoad({ props.onLoad({
nodes: graphContext.state.nodes, nodes: state.nodes,
edges: graphContext.state.edges, edges: state.edges,
fitView: function fitView$1() { fitView: function fitView$1() {
return graphContext.dispatch(fitView()); return dispatch(fitView());
} }
}); });
} }
}, [graphContext.state.d3Initialised]); }, [state.d3Initialised]);
React.useEffect(function () { React.useEffect(function () {
props.onChange({ props.onChange({
nodes: graphContext.state.nodes, nodes: state.nodes,
edges: graphContext.state.edges edges: state.edges
}); });
}); });
return React__default.createElement("div", { return React__default.createElement("div", {
@@ -30398,10 +30427,15 @@
}, React__default.createElement(NodeRenderer, { }, React__default.createElement(NodeRenderer, {
nodeTypes: props.nodeTypes nodeTypes: props.nodeTypes
}), React__default.createElement(EdgeRenderer, { }), React__default.createElement(EdgeRenderer, {
width: graphContext.state.width, width: state.width,
height: graphContext.state.height height: state.height
}), shiftPressed && React__default.createElement(UserSelection, null), graphContext.state.nodesSelectionActive && React__default.createElement(NodesSelection, null), React__default.createElement("div", { }), shiftPressed && React__default.createElement(UserSelection, null), state.nodesSelectionActive && React__default.createElement(NodesSelection, null), React__default.createElement("div", {
className: "react-graph__zoompane", className: "react-graph__zoompane",
onClick: function onClick() {
return dispatch(setNodesSelection({
isActive: false
}));
},
ref: zoomPane ref: zoomPane
})); }));
}; };
@@ -30410,6 +30444,23 @@
monitorHeight: true monitorHeight: true
})(GraphView); })(GraphView);
var GlobalKeyHandler = (function (props) {
var _useContext = React.useContext(GraphContext),
state = _useContext.state,
dispatch = _useContext.dispatch;
var removePressed = useKeyPress('Backspace');
React.useEffect(function () {
if (removePressed && state.selectedNodeIds.length) {
props.onNodeRemove(state.selectedNodeIds);
dispatch(setNodesSelection({
isActive: false
}));
}
}, [removePressed]);
return null;
});
var Handle = (function (props) { var Handle = (function (props) {
return React__default.createElement("div", _extends({ return React__default.createElement("div", _extends({
className: "react-graph__handle" className: "react-graph__handle"
@@ -32747,7 +32798,10 @@
var wrapNode = (function (NodeComponent) { var wrapNode = (function (NodeComponent) {
return function (props) { return function (props) {
var nodeElement = React.useRef(null); var nodeElement = React.useRef(null);
var graphContext = React.useContext(GraphContext);
var _useContext = React.useContext(GraphContext),
state = _useContext.state,
dispatch = _useContext.dispatch;
var _useState = React.useState({ var _useState = React.useState({
x: 0, x: 0,
@@ -32763,12 +32817,12 @@
var position = __rg.position; var position = __rg.position;
var id = data.id; var id = data.id;
var _graphContext$state$t = _slicedToArray(graphContext.state.transform, 3), var _state$transform = _slicedToArray(state.transform, 3),
x = _graphContext$state$t[0], x = _state$transform[0],
y = _graphContext$state$t[1], y = _state$transform[1],
k = _graphContext$state$t[2]; k = _state$transform[2];
var selected = graphContext.state.selectedNodeIds.includes(id); var selected = state.selectedNodeIds.includes(id);
var nodeClasses = classnames('react-graph__node', { var nodeClasses = classnames('react-graph__node', {
selected: selected selected: selected
}); });
@@ -32776,7 +32830,7 @@
var bounds = nodeElement.current.getBoundingClientRect(); var bounds = nodeElement.current.getBoundingClientRect();
var unscaledWith = Math.round(bounds.width * (1 / k)); var unscaledWith = Math.round(bounds.width * (1 / k));
var unscaledHeight = Math.round(bounds.height * (1 / k)); var unscaledHeight = Math.round(bounds.height * (1 / k));
graphContext.dispatch(updateNodeData(id, { dispatch(updateNodeData(id, {
width: unscaledWith, width: unscaledWith,
height: unscaledHeight height: unscaledHeight
})); }));
@@ -32804,7 +32858,7 @@
x: e.clientX * (1 / k), x: e.clientX * (1 / k),
y: e.clientY * (1 / k) y: e.clientY * (1 / k)
}; };
graphContext.dispatch(updateNodePos(id, { dispatch(updateNodePos(id, {
x: unscaledPos.x - x - offset.x, x: unscaledPos.x - x - offset.x,
y: unscaledPos.y - y - offset.y y: unscaledPos.y - y - offset.y
})); }));
@@ -32821,6 +32875,7 @@
return false; return false;
} }
dispatch(setSelectedNodesIds(id));
onNodeClick({ onNodeClick({
data: data, data: data,
position: position position: position
@@ -32900,7 +32955,8 @@
onLoad = _this$props.onLoad, onLoad = _this$props.onLoad,
onMove = _this$props.onMove, onMove = _this$props.onMove,
onChange = _this$props.onChange, onChange = _this$props.onChange,
elements = _this$props.elements; elements = _this$props.elements,
onNodeRemove = _this$props.onNodeRemove;
var _elements$map$reduce = elements.map(parseElements).reduce(separateElements, {}), var _elements$map$reduce = elements.map(parseElements).reduce(separateElements, {}),
nodes = _elements$map$reduce.nodes, nodes = _elements$map$reduce.nodes,
@@ -32918,6 +32974,8 @@
onMove: onMove, onMove: onMove,
onChange: onChange, onChange: onChange,
nodeTypes: this.nodeTypes nodeTypes: this.nodeTypes
}), React__default.createElement(GlobalKeyHandler, {
onNodeRemove: onNodeRemove
}), children)); }), children));
} }
}]); }]);
@@ -32927,6 +32985,7 @@
ReactGraph.defaultProps = { ReactGraph.defaultProps = {
onNodeClick: function onNodeClick() {}, onNodeClick: function onNodeClick() {},
onNodeRemove: function onNodeRemove() {},
onLoad: function onLoad() {}, onLoad: function onLoad() {},
onMove: function onMove() {}, onMove: function onMove() {},
onChange: function onChange() {}, onChange: function onChange() {},
+1
View File
@@ -86,6 +86,7 @@ class App extends PureComponent {
<Graph <Graph
elements={this.state.elements} elements={this.state.elements}
onNodeClick={node => console.log(node)} onNodeClick={node => console.log(node)}
onNodeRemove={nodeIds => console.log('remove', nodeIds)}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
onLoad={graphInstance => this.onLoad(graphInstance)} onLoad={graphInstance => this.onLoad(graphInstance)}
onChange={(elements) => this.onChange(elements)} onChange={(elements) => this.onChange(elements)}
+19
View File
@@ -0,0 +1,19 @@
import { useEffect, useContext } from 'react';
import { useKeyPress } from '../hooks';
import { setNodesSelection } from '../state/actions';
import { GraphContext } from '../GraphContext';
export default (props) => {
const { state, dispatch } = useContext(GraphContext);
const removePressed = useKeyPress('Backspace');
useEffect(() => {
if (removePressed && state.selectedNodeIds.length) {
props.onNodeRemove(state.selectedNodeIds);
dispatch(setNodesSelection({ isActive: false }));
}
}, [removePressed])
return null;
}
+11 -1
View File
@@ -1,4 +1,4 @@
import React, { createContext, useEffect, useReducer } from 'react'; import React, { createContext, useEffect, useReducer, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import isEqual from 'lodash.isequal'; import isEqual from 'lodash.isequal';
@@ -7,6 +7,16 @@ import { setNodes, setEdges } from '../state/actions';
export const GraphContext = createContext({}); export const GraphContext = createContext({});
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
export const Provider = (props) => { export const Provider = (props) => {
const { const {
onNodeClick, onNodeClick,
+8 -6
View File
@@ -3,20 +3,20 @@ import ReactDraggable from 'react-draggable';
import cx from 'classnames'; import cx from 'classnames';
import { GraphContext } from '../../GraphContext'; import { GraphContext } from '../../GraphContext';
import { updateNodeData, updateNodePos } from '../../state/actions'; import { updateNodeData, updateNodePos, setSelectedNodesIds } from '../../state/actions';
const isInputTarget = (e) => ['INPUT', 'SELECT', 'TEXTAREA'].includes(e.target.nodeName); const isInputTarget = (e) => ['INPUT', 'SELECT', 'TEXTAREA'].includes(e.target.nodeName);
export default NodeComponent => (props) => { export default NodeComponent => (props) => {
const nodeElement = useRef(null); const nodeElement = useRef(null);
const graphContext = useContext(GraphContext); const { state, dispatch } = useContext(GraphContext);
const [offset, setOffset] = useState({ x: 0, y: 0 }); const [offset, setOffset] = useState({ x: 0, y: 0 });
const { data, onNodeClick, __rg } = props; const { data, onNodeClick, __rg } = props;
const { position } = __rg; const { position } = __rg;
const { id } = data; const { id } = data;
const [ x, y, k ] = graphContext.state.transform; const [ x, y, k ] = state.transform;
const selected = graphContext.state.selectedNodeIds.includes(id); const selected = state.selectedNodeIds.includes(id);
const nodeClasses = cx('react-graph__node', { selected }); const nodeClasses = cx('react-graph__node', { selected });
useEffect(() => { useEffect(() => {
@@ -24,7 +24,7 @@ export default NodeComponent => (props) => {
const unscaledWith = Math.round(bounds.width * (1 / k)); const unscaledWith = Math.round(bounds.width * (1 / k));
const unscaledHeight = Math.round(bounds.height * (1 / k)); const unscaledHeight = Math.round(bounds.height * (1 / k));
graphContext.dispatch(updateNodeData(id, { width: unscaledWith, height: unscaledHeight })); dispatch(updateNodeData(id, { width: unscaledWith, height: unscaledHeight }));
}, []); }, []);
return ( return (
@@ -50,7 +50,7 @@ export default NodeComponent => (props) => {
y: e.clientY * (1 / k) y: e.clientY * (1 / k)
} }
graphContext.dispatch(updateNodePos(id, { dispatch(updateNodePos(id, {
x: unscaledPos.x - x - offset.x, x: unscaledPos.x - x - offset.x,
y: unscaledPos.y - y - offset.y y: unscaledPos.y - y - offset.y
})); }));
@@ -65,6 +65,8 @@ export default NodeComponent => (props) => {
if (isInputTarget(e)) { if (isInputTarget(e)) {
return false; return false;
} }
dispatch(setSelectedNodesIds(id));
onNodeClick({ data, position }); onNodeClick({ data, position });
}} }}
> >
+7 -2
View File
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
import { parseElements, separateElements } from './graph-utils'; import { parseElements, separateElements } from './graph-utils';
import GraphView from './GraphView'; import GraphView from './GraphView';
import GlobalKeyHandler from './GlobalKeyHandler';
import { Provider } from './GraphContext'; import { Provider } from './GraphContext';
import { createNodeTypes } from './NodeRenderer/utils'; import { createNodeTypes } from './NodeRenderer/utils';
@@ -20,7 +21,7 @@ class ReactGraph extends PureComponent {
render() { render() {
const { const {
style, onNodeClick, children, onLoad, onMove, onChange, elements style, onNodeClick, children, onLoad, onMove, onChange, elements, onNodeRemove
} = this.props; } = this.props;
const { nodes, edges } = elements const { nodes, edges } = elements
@@ -36,6 +37,9 @@ class ReactGraph extends PureComponent {
onChange={onChange} onChange={onChange}
nodeTypes={this.nodeTypes} nodeTypes={this.nodeTypes}
/> />
<GlobalKeyHandler
onNodeRemove={onNodeRemove}
/>
{children} {children}
</Provider> </Provider>
</div> </div>
@@ -44,7 +48,8 @@ class ReactGraph extends PureComponent {
} }
ReactGraph.defaultProps = { ReactGraph.defaultProps = {
onNodeClick: () => {}, onNodeClick: () => {},
onNodeRemove: () => {},
onLoad: () => {}, onLoad: () => {},
onMove: () => {}, onMove: () => {},
onChange: () => {}, onChange: () => {},
+14 -1
View File
@@ -1,7 +1,8 @@
import { import {
UPDATE_TRANSFORM, UPDATE_SIZE, SET_NODES, SET_EDGES, UPDATE_TRANSFORM, UPDATE_SIZE, SET_NODES, SET_EDGES,
UPDATE_NODE_DATA, UPDATE_NODE_POS, INIT_D3, FIT_VIEW, UPDATE_NODE_DATA, UPDATE_NODE_POS, INIT_D3, FIT_VIEW,
UPDATE_SELECTION, SET_SELECTION, SET_NODES_SELECTION UPDATE_SELECTION, SET_SELECTION, SET_NODES_SELECTION,
SET_SELECTED_NODES_IDS, REMOVE_NODES
} from './index'; } from './index';
export const updateTransform = (transform) => { export const updateTransform = (transform) => {
@@ -65,6 +66,18 @@ export const setNodesSelection = ({ isActive, selection }) => {
return { type: SET_NODES_SELECTION, payload: { nodesSelectionActive: isActive, selection } }; return { type: SET_NODES_SELECTION, payload: { nodesSelectionActive: isActive, selection } };
}; };
export const setSelectedNodesIds = (ids) => {
const idArray = Array.isArray(ids) ? ids : [ids];
return { type: SET_SELECTED_NODES_IDS, payload: { selectedNodeIds: idArray } };
};
export const removeNodes = (ids) => {
const idArray = Array.isArray(ids) ? ids : [ids];
return { type: REMOVE_NODES, payload: { ids: idArray } };
}
export const updateSelection = (selection) => { export const updateSelection = (selection) => {
return { return {
type: UPDATE_SELECTION, type: UPDATE_SELECTION,
+14
View File
@@ -13,6 +13,8 @@ export const FIT_VIEW = 'FIT_VIEW';
export const UPDATE_SELECTION = 'UPDATE_SELECTION'; export const UPDATE_SELECTION = 'UPDATE_SELECTION';
export const SET_SELECTION = 'SET_SELECTION'; export const SET_SELECTION = 'SET_SELECTION';
export const SET_NODES_SELECTION = 'SET_NODES_SELECTION'; export const SET_NODES_SELECTION = 'SET_NODES_SELECTION';
export const SET_SELECTED_NODES_IDS = 'SET_SELECTED_NODES_IDS';
export const REMOVE_NODES = 'REMOVE_NODES';
export const initialState = { export const initialState = {
width: 0, width: 0,
@@ -98,12 +100,24 @@ export const reducer = (state, action) => {
return { ...state, ...action.payload, selectedNodesBbox }; return { ...state, ...action.payload, selectedNodesBbox };
} }
case REMOVE_NODES: {
const { ids } = action.payload;
const nextEdges = state.edges.filter(e => !ids.includes(e.data.target) && !ids.includes(e.data.source));
const nextNodes = state.nodes.filter(n => !ids.includes(n.data.id));
console.log(ids,);
console.log( state.edges, nextEdges, );
console.log(state.nodes, nextNodes);
return { ...state, nodes: nextNodes, edges: nextEdges };
}
case SET_NODES: case SET_NODES:
case SET_EDGES: case SET_EDGES:
case UPDATE_TRANSFORM: case UPDATE_TRANSFORM:
case INIT_D3: case INIT_D3:
case UPDATE_SIZE: case UPDATE_SIZE:
case SET_SELECTION: case SET_SELECTION:
case SET_SELECTED_NODES_IDS:
return { ...state, ...action.payload }; return { ...state, ...action.payload };
default: default:
return state; return state;