Merge pull request #1 from wbkd/refactor-state
refactor(state): use easy-peasy
This commit is contained in:
Generated
+61
@@ -3057,6 +3057,20 @@
|
|||||||
"readable-stream": "^2.0.2"
|
"readable-stream": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"easy-peasy": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/easy-peasy/-/easy-peasy-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-qcsr81F6rZN5hEZ1lYRDUHtatxn5Qb4cF5uj5cNXKEXLXmATCxktNC53B2XvccA7K8zSCwUeoFckrCOvLeINWA==",
|
||||||
|
"requires": {
|
||||||
|
"immer-peasy": "3.1.3",
|
||||||
|
"memoizerific": "^1.11.3",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"redux": "^4.0.4",
|
||||||
|
"redux-thunk": "^2.3.0",
|
||||||
|
"shallowequal": "^1.1.0",
|
||||||
|
"ts-toolbelt": "^3.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ecc-jsbn": {
|
"ecc-jsbn": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||||
@@ -4523,6 +4537,11 @@
|
|||||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
|
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"immer-peasy": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer-peasy/-/immer-peasy-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-WzoZ96A93jOmcDOLNChMWAqy+ZU8vEYQx2DcKjgo7P5SToiJs+GL+5yQbWzH8X02Lhvv6xrGgVNa1xbki66Eow=="
|
||||||
|
},
|
||||||
"import-cwd": {
|
"import-cwd": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
||||||
@@ -5207,6 +5226,11 @@
|
|||||||
"integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
|
"integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"map-or-similar": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz",
|
||||||
|
"integrity": "sha1-beJlMXSt+12e3DPGnT6Sobdvrwg="
|
||||||
|
},
|
||||||
"map-visit": {
|
"map-visit": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
|
||||||
@@ -5287,6 +5311,14 @@
|
|||||||
"integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==",
|
"integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"memoizerific": {
|
||||||
|
"version": "1.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz",
|
||||||
|
"integrity": "sha1-fIekZGREwy11Q4VwkF8tvRsagFo=",
|
||||||
|
"requires": {
|
||||||
|
"map-or-similar": "^1.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"merge-source-map": {
|
"merge-source-map": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz",
|
||||||
@@ -7090,6 +7122,20 @@
|
|||||||
"readable-stream": "^2.0.2"
|
"readable-stream": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redux": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"symbol-observable": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redux-thunk": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
|
||||||
|
},
|
||||||
"regenerate": {
|
"regenerate": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
|
||||||
@@ -7700,6 +7746,11 @@
|
|||||||
"integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=",
|
"integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"shallowequal": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||||
|
},
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||||
@@ -8179,6 +8230,11 @@
|
|||||||
"util.promisify": "~1.0.0"
|
"util.promisify": "~1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"symbol-observable": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
|
||||||
|
},
|
||||||
"symbol-tree": {
|
"symbol-tree": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||||
@@ -8321,6 +8377,11 @@
|
|||||||
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
|
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"ts-toolbelt": {
|
||||||
|
"version": "3.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-3.8.1.tgz",
|
||||||
|
"integrity": "sha512-XDAWEHVscqto6pG0gPSbYaEfEBcl9V0JK5pQIQJ3M+6sYnxK5ZOPyyRA/aq78WxKlLvBSXCPJ1fPX+7qCCg2Zg=="
|
||||||
|
},
|
||||||
"tty-browserify": {
|
"tty-browserify": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"d3-selection": "^1.4.0",
|
"d3-selection": "^1.4.0",
|
||||||
"d3-zoom": "^1.7.3",
|
"d3-zoom": "^1.7.3",
|
||||||
|
"easy-peasy": "^3.0.2",
|
||||||
"fast-deep-equal": "^3.0.0-beta.1",
|
"fast-deep-equal": "^3.0.0-beta.1",
|
||||||
"react": "^16.9.0",
|
"react": "^16.9.0",
|
||||||
"react-dom": "^16.9.0",
|
"react-dom": "^16.9.0",
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import React, { createContext, useState, memo, useMemo } from 'react';
|
|
||||||
|
|
||||||
export const ConnectionContext = createContext({});
|
|
||||||
|
|
||||||
export const Provider = memo(({ onConnect, children }) => {
|
|
||||||
const [sourceId, setSourceId] = useState(null);
|
|
||||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
||||||
|
|
||||||
const connectionContext = useMemo(() => ({
|
|
||||||
sourceId,
|
|
||||||
setSourceId,
|
|
||||||
position,
|
|
||||||
setPosition,
|
|
||||||
onConnect
|
|
||||||
}), [sourceId, position.x, position.y]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ConnectionContext.Provider value={connectionContext}>
|
|
||||||
{children}
|
|
||||||
</ConnectionContext.Provider>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Provider.displayName = 'ConnectionProvider';
|
|
||||||
Provider.whyDidYouRender = false;
|
|
||||||
|
|
||||||
export const { Consumer } = ConnectionContext;
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
import { setSelectedElements } from '../../state/actions';
|
|
||||||
import { isInputNode } from '../../utils';
|
import { isInputNode } from '../../utils';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
export default EdgeComponent => {
|
export default EdgeComponent => {
|
||||||
const EdgeWrapper = memo((props) => {
|
const EdgeWrapper = memo((props) => {
|
||||||
const {
|
const {
|
||||||
source, target, animated, type,
|
source, target, animated, type,
|
||||||
dispatch, selected, onClick
|
selected, onClick
|
||||||
} = props;
|
} = props;
|
||||||
const edgeClasses = cx('react-graph__edge', { selected, animated });
|
const edgeClasses = cx('react-graph__edge', { selected, animated });
|
||||||
const onEdgeClick = (evt) => {
|
const onEdgeClick = (evt) => {
|
||||||
@@ -16,7 +16,7 @@ export default EdgeComponent => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(setSelectedElements({ source, target }));
|
store.dispatch.setSelectedElements({ source, target });
|
||||||
onClick({ source, target, type });
|
onClick({ source, target, type });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+18
-12
@@ -1,7 +1,6 @@
|
|||||||
import React, { memo, useContext } from 'react';
|
import React, { memo } from 'react';
|
||||||
|
import { useStoreState } from 'easy-peasy';
|
||||||
|
|
||||||
import { GraphContext } from '../GraphContext';
|
|
||||||
import { ConnectionContext } from '../ConnectionContext';
|
|
||||||
import ConnectionLine from '../ConnectionLine';
|
import ConnectionLine from '../ConnectionLine';
|
||||||
import { isEdge } from '../graph-utils';
|
import { isEdge } from '../graph-utils';
|
||||||
|
|
||||||
@@ -36,10 +35,10 @@ function getEdgePositions(sourceNode, targetNode) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderEdge(e, props, graphContext) {
|
function renderEdge(e, props, state) {
|
||||||
const edgeType = e.type || 'default';
|
const edgeType = e.type || 'default';
|
||||||
const sourceNode = graphContext.state.nodes.find(n => n.id === e.source);
|
const sourceNode = state.nodes.find(n => n.id === e.source);
|
||||||
const targetNode = graphContext.state.nodes.find(n => n.id === e.target);
|
const targetNode = state.nodes.find(n => n.id === e.target);
|
||||||
|
|
||||||
if (!sourceNode) {
|
if (!sourceNode) {
|
||||||
throw new Error(`couldn't create edge for source id: ${e.source}`);
|
throw new Error(`couldn't create edge for source id: ${e.source}`);
|
||||||
@@ -51,7 +50,7 @@ function renderEdge(e, props, graphContext) {
|
|||||||
|
|
||||||
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
|
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
|
||||||
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(sourceNode, targetNode);
|
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(sourceNode, targetNode);
|
||||||
const selected = graphContext.state.selectedElements
|
const selected = state.selectedElements
|
||||||
.filter(isEdge)
|
.filter(isEdge)
|
||||||
.find(elm => elm.source === e.source && elm.target === e.target);
|
.find(elm => elm.source === e.source && elm.target === e.target);
|
||||||
|
|
||||||
@@ -62,7 +61,6 @@ function renderEdge(e, props, graphContext) {
|
|||||||
type={e.type}
|
type={e.type}
|
||||||
onClick={props.onElementClick}
|
onClick={props.onElementClick}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
dispatch={graphContext.dispatch}
|
|
||||||
animated={e.animated}
|
animated={e.animated}
|
||||||
style={e.style}
|
style={e.style}
|
||||||
source={e.source}
|
source={e.source}
|
||||||
@@ -76,8 +74,14 @@ function renderEdge(e, props, graphContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const EdgeRenderer = memo((props) => {
|
const EdgeRenderer = memo((props) => {
|
||||||
const graphContext = useContext(GraphContext);
|
const state = useStoreState(s => ({
|
||||||
const { position, sourceId : connectionSourceId } = useContext(ConnectionContext);
|
nodes: s.nodes,
|
||||||
|
edges: s.edges,
|
||||||
|
transform: s.transform,
|
||||||
|
selectedElements: s.selectedElements,
|
||||||
|
connectionSourceId: s.connectionSourceId,
|
||||||
|
position: s.connectionPosition
|
||||||
|
}));
|
||||||
const {
|
const {
|
||||||
width, height, connectionLineStyle, connectionLineType
|
width, height, connectionLineStyle, connectionLineType
|
||||||
} = props;
|
} = props;
|
||||||
@@ -86,7 +90,7 @@ const EdgeRenderer = memo((props) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { transform, edges, nodes } = graphContext.state;
|
const { transform, edges, nodes, connectionSourceId, position } = state;
|
||||||
const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`;
|
const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -96,7 +100,7 @@ const EdgeRenderer = memo((props) => {
|
|||||||
className="react-graph__edges"
|
className="react-graph__edges"
|
||||||
>
|
>
|
||||||
<g transform={transformStyle}>
|
<g transform={transformStyle}>
|
||||||
{edges.map(e => renderEdge(e, props, graphContext))}
|
{edges.map(e => renderEdge(e, props, state))}
|
||||||
{connectionSourceId && (
|
{connectionSourceId && (
|
||||||
<ConnectionLine
|
<ConnectionLine
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
@@ -113,4 +117,6 @@ const EdgeRenderer = memo((props) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
EdgeRenderer.displayName = 'EdgeRenderer';
|
||||||
|
|
||||||
export default EdgeRenderer;
|
export default EdgeRenderer;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useEffect, useContext, memo } from 'react';
|
import { useEffect, memo } from 'react';
|
||||||
|
import { useStoreState, useStoreActions } from 'easy-peasy';
|
||||||
|
|
||||||
import useKeyPress from '../hooks/useKeyPress';
|
import useKeyPress from '../hooks/useKeyPress';
|
||||||
import { setNodesSelection } from '../state/actions';
|
|
||||||
import { GraphContext } from '../GraphContext';
|
|
||||||
import { isEdge, getConnectedEdges } from '../graph-utils';
|
import { isEdge, getConnectedEdges } from '../graph-utils';
|
||||||
|
|
||||||
export default memo((props) => {
|
export default memo((props) => {
|
||||||
const { state, dispatch } = useContext(GraphContext);
|
const state = useStoreState(s => ({ selectedElements: s.selectedElements, edges: s.edges }))
|
||||||
|
const setNodesSelection = useStoreActions(a => a.setNodesSelection);
|
||||||
const removePressed = useKeyPress('Backspace');
|
const removePressed = useKeyPress('Backspace');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -20,7 +20,7 @@ export default memo((props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
props.onElementsRemove(elementsToRemove);
|
props.onElementsRemove(elementsToRemove);
|
||||||
dispatch(setNodesSelection({ isActive: false }));
|
setNodesSelection({ isActive: false });
|
||||||
}
|
}
|
||||||
}, [removePressed])
|
}, [removePressed])
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
import React, { createContext, useEffect, useReducer, useMemo } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import isEqual from 'fast-deep-equal';
|
|
||||||
|
|
||||||
import { reducer, initialState } from '../state';
|
|
||||||
import { setNodes, setEdges } from '../state/actions';
|
|
||||||
import { parseElement, isNode, isEdge } from '../graph-utils';
|
|
||||||
|
|
||||||
export const GraphContext = createContext({});
|
|
||||||
|
|
||||||
export const Provider = ({
|
|
||||||
elements,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const [state, dispatch] = useReducer(reducer, initialState);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const nodes = elements.filter(isNode);
|
|
||||||
const edges = elements.filter(isEdge).map(parseElement, state.transform);
|
|
||||||
|
|
||||||
const nextNodes = nodes.map(propNode => {
|
|
||||||
const existingNode = state.nodes.find(n => n.id === propNode.id);
|
|
||||||
|
|
||||||
if (existingNode) {
|
|
||||||
const data = !isEqual(existingNode.data, propNode.data) ?
|
|
||||||
{ ...existingNode.data, ...propNode.data } : existingNode.data;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...existingNode,
|
|
||||||
data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseElement(propNode, state.transform);
|
|
||||||
});
|
|
||||||
|
|
||||||
const nodesChanged = !isEqual(state.nodes, nextNodes);
|
|
||||||
const edgesChanged = !isEqual(state.edges, edges);
|
|
||||||
|
|
||||||
if (nodesChanged) {
|
|
||||||
dispatch(setNodes(nextNodes));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (edgesChanged) {
|
|
||||||
dispatch(setEdges(edges));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const graphContext = useMemo(() => ({
|
|
||||||
state,
|
|
||||||
dispatch
|
|
||||||
}), [state]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GraphContext.Provider
|
|
||||||
value={graphContext}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</GraphContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const { Consumer } = GraphContext;
|
|
||||||
|
|
||||||
Provider.displayName = 'GraphProvider';
|
|
||||||
|
|
||||||
Provider.propTypes = {
|
|
||||||
elements: PropTypes.arrayOf(PropTypes.object)
|
|
||||||
};
|
|
||||||
|
|
||||||
Provider.defaultProps = {
|
|
||||||
elements: [],
|
|
||||||
};
|
|
||||||
+32
-28
@@ -1,25 +1,33 @@
|
|||||||
import React, { useEffect, useContext, useRef, memo } from 'react';
|
import React, { useEffect, useRef, memo } from 'react';
|
||||||
|
import { useStoreState, useStoreActions } from 'easy-peasy';
|
||||||
|
|
||||||
import { Provider } from '../ConnectionContext';
|
|
||||||
import { GraphContext } from '../GraphContext';
|
|
||||||
import NodeRenderer from '../NodeRenderer';
|
import NodeRenderer from '../NodeRenderer';
|
||||||
import EdgeRenderer from '../EdgeRenderer';
|
import EdgeRenderer from '../EdgeRenderer';
|
||||||
import UserSelection from '../UserSelection';
|
import UserSelection from '../UserSelection';
|
||||||
import NodesSelection from '../NodesSelection';
|
import NodesSelection from '../NodesSelection';
|
||||||
import { setNodesSelection } from '../state/actions';
|
|
||||||
import { updateSize, fitView, zoomIn, zoomOut } from '../state/view-actions';
|
|
||||||
import useKeyPress from '../hooks/useKeyPress';
|
import useKeyPress from '../hooks/useKeyPress';
|
||||||
import useD3Zoom from '../hooks/useD3Zoom';
|
import useD3Zoom from '../hooks/useD3Zoom';
|
||||||
import { getDimensions } from '../utils';
|
import { getDimensions } from '../utils';
|
||||||
|
import { fitView, zoomIn, zoomOut } from '../graph-utils';
|
||||||
|
|
||||||
const GraphView = memo((props) => {
|
const GraphView = memo((props) => {
|
||||||
const zoomPane = useRef();
|
const zoomPane = useRef();
|
||||||
const rendererNode = useRef();
|
const rendererNode = useRef();
|
||||||
const { state, dispatch } = useContext(GraphContext);
|
const state = useStoreState(s => ({
|
||||||
|
width: s.width,
|
||||||
|
height: s.height,
|
||||||
|
nodes: s.nodes,
|
||||||
|
edges: s.edges,
|
||||||
|
d3Initialised: s.d3Initialised,
|
||||||
|
nodesSelectionActive: s.nodesSelectionActive
|
||||||
|
}));
|
||||||
|
const updateSize = useStoreActions(actions => actions.updateSize);
|
||||||
|
const setNodesSelection = useStoreActions(actions => actions.setNodesSelection);
|
||||||
|
|
||||||
const shiftPressed = useKeyPress('Shift');
|
const shiftPressed = useKeyPress('Shift');
|
||||||
const updateDimensions = () => {
|
const updateDimensions = () => {
|
||||||
const size = getDimensions(rendererNode.current);
|
const size = getDimensions(rendererNode.current);
|
||||||
dispatch(updateSize(size));
|
updateSize(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -36,37 +44,33 @@ const GraphView = memo((props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.d3Initialised) {
|
if (state.d3Initialised) {
|
||||||
props.onLoad({
|
props.onLoad({
|
||||||
nodes: state.nodes,
|
fitView,
|
||||||
edges: state.edges,
|
zoomIn,
|
||||||
fitView: opts => dispatch(fitView(opts)),
|
zoomOut
|
||||||
zoomIn: () => dispatch(zoomIn()),
|
|
||||||
zoomOut: () => dispatch(zoomOut())
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [state.d3Initialised]);
|
}, [state.d3Initialised]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="react-graph__renderer" ref={rendererNode}>
|
<div className="react-graph__renderer" ref={rendererNode}>
|
||||||
<Provider onConnect={props.onConnect}>
|
<NodeRenderer
|
||||||
<NodeRenderer
|
nodeTypes={props.nodeTypes}
|
||||||
nodeTypes={props.nodeTypes}
|
onElementClick={props.onElementClick}
|
||||||
onElementClick={props.onElementClick}
|
onNodeDragStop={props.onNodeDragStop}
|
||||||
onNodeDragStop={props.onNodeDragStop}
|
/>
|
||||||
/>
|
<EdgeRenderer
|
||||||
<EdgeRenderer
|
width={state.width}
|
||||||
width={state.width}
|
height={state.height}
|
||||||
height={state.height}
|
edgeTypes={props.edgeTypes}
|
||||||
edgeTypes={props.edgeTypes}
|
onElementClick={props.onElementClick}
|
||||||
onElementClick={props.onElementClick}
|
connectionLineType={props.connectionLineType}
|
||||||
connectionLineType={props.connectionLineType}
|
connectionLineStyle={props.connectionLineStyle}
|
||||||
connectionLineStyle={props.connectionLineStyle}
|
/>
|
||||||
/>
|
|
||||||
</Provider>
|
|
||||||
{shiftPressed && <UserSelection />}
|
{shiftPressed && <UserSelection />}
|
||||||
{state.nodesSelectionActive && <NodesSelection />}
|
{state.nodesSelectionActive && <NodesSelection />}
|
||||||
<div
|
<div
|
||||||
className="react-graph__zoompane"
|
className="react-graph__zoompane"
|
||||||
onClick={() => dispatch(setNodesSelection({ isActive: false }))}
|
onClick={() => setNodesSelection({ isActive: false })}
|
||||||
ref={zoomPane}
|
ref={zoomPane}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import React, { memo, useContext } from 'react';
|
import React, { memo, useContext } from 'react';
|
||||||
|
import { useStoreActions, useStoreState } from 'easy-peasy';
|
||||||
|
|
||||||
import BaseHandle from './BaseHandle';
|
import BaseHandle from './BaseHandle';
|
||||||
import NodeIdContext from '../NodeIdContext'
|
import NodeIdContext from '../NodeIdContext'
|
||||||
import { ConnectionContext } from '../../ConnectionContext';
|
|
||||||
|
|
||||||
export default memo((props) => {
|
const SourceHandle = memo((props) => {
|
||||||
const nodeId = useContext(NodeIdContext);
|
const nodeId = useContext(NodeIdContext);
|
||||||
const { setPosition, setSourceId, onConnect } = useContext(ConnectionContext);
|
const { setPosition, setSourceId } = useStoreActions(a => ({
|
||||||
|
setPosition: a.setConnectionPosition,
|
||||||
|
setSourceId: a.setConnectionSourceId
|
||||||
|
}));
|
||||||
|
const onConnect = useStoreState(s => s.onConnect);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseHandle
|
<BaseHandle
|
||||||
@@ -19,3 +23,7 @@ export default memo((props) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
SourceHandle.displayName = 'SourceHandle';
|
||||||
|
|
||||||
|
export default SourceHandle;
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import React, { memo, useContext } from 'react';
|
import React, { memo, useContext } from 'react';
|
||||||
|
import { useStoreActions, useStoreState } from 'easy-peasy';
|
||||||
|
|
||||||
import BaseHandle from './BaseHandle';
|
import BaseHandle from './BaseHandle';
|
||||||
import { ConnectionContext } from '../../ConnectionContext';
|
|
||||||
import NodeIdContext from '../NodeIdContext'
|
import NodeIdContext from '../NodeIdContext'
|
||||||
|
|
||||||
const TargetHandle = memo((props) => {
|
const TargetHandle = memo((props) => {
|
||||||
const nodeId = useContext(NodeIdContext);
|
const nodeId = useContext(NodeIdContext);
|
||||||
const { setPosition, setSourceId, onConnect } = useContext(ConnectionContext);
|
const { setPosition, setSourceId } = useStoreActions(a => ({
|
||||||
|
setPosition: a.setConnectionPosition,
|
||||||
|
setSourceId: a.setConnectionSourceId
|
||||||
|
}));
|
||||||
|
const onConnect = useStoreState(s => s.onConnect);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseHandle
|
<BaseHandle
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import React, { useEffect, useRef, useState, memo } from 'react';
|
|||||||
import ReactDraggable from 'react-draggable';
|
import ReactDraggable from 'react-draggable';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
import { updateNodeData, updateNodePos, setSelectedElements } from '../../state/actions';
|
|
||||||
import { getDimensions, isInputNode } from '../../utils';
|
import { getDimensions, isInputNode } from '../../utils';
|
||||||
import { Provider } from '../NodeIdContext';
|
import { Provider } from '../NodeIdContext';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
const isHandle = e => (
|
const isHandle = e => (
|
||||||
e.target.className &&
|
e.target.className &&
|
||||||
@@ -29,7 +29,7 @@ const getHandleBounds = (sel, nodeElement, parentBounds, k) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const onStart = (evt, { dispatch, setOffset, onClick, id, type, data, position, transform }) => {
|
const onStart = (evt, { setOffset, onClick, id, type, data, position, transform }) => {
|
||||||
if (isInputNode(evt) || isHandle(evt)) {
|
if (isInputNode(evt) || isHandle(evt)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -42,22 +42,22 @@ const onStart = (evt, { dispatch, setOffset, onClick, id, type, data, position,
|
|||||||
const offsetY = scaledClient.y - position.y - transform[1];
|
const offsetY = scaledClient.y - position.y - transform[1];
|
||||||
const node = { id, type, position, data };
|
const node = { id, type, position, data };
|
||||||
|
|
||||||
dispatch(setSelectedElements({ id, type }));
|
store.dispatch.setSelectedElements({ id, type });
|
||||||
setOffset({ x: offsetX, y: offsetY });
|
setOffset({ x: offsetX, y: offsetY });
|
||||||
onClick(node);
|
onClick(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDrag = (evt, { dispatch, setDragging, id, offset, transform }) => {
|
const onDrag = (evt, { setDragging, id, offset, transform }) => {
|
||||||
const scaledClient = {
|
const scaledClient = {
|
||||||
x: evt.clientX * (1 / transform[2]),
|
x: evt.clientX * (1 / transform[2]),
|
||||||
y: evt.clientY * (1 / transform[2])
|
y: evt.clientY * (1 / transform[2])
|
||||||
};
|
};
|
||||||
|
|
||||||
setDragging(true);
|
setDragging(true);
|
||||||
dispatch(updateNodePos(id, {
|
store.dispatch.updateNodePos({ id, pos: {
|
||||||
x: scaledClient.x - transform[0] - offset.x,
|
x: scaledClient.x - transform[0] - offset.x,
|
||||||
y: scaledClient.y - transform[1] - offset.y
|
y: scaledClient.y - transform[1] - offset.y
|
||||||
}));
|
}});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onStop = ({ onNodeDragStop, setDragging, isDragging, id, type, position, data }) => {
|
const onStop = ({ onNodeDragStop, setDragging, isDragging, id, type, position, data }) => {
|
||||||
@@ -78,7 +78,7 @@ export default NodeComponent => {
|
|||||||
const [isDragging, setDragging] = useState(false);
|
const [isDragging, setDragging] = useState(false);
|
||||||
const {
|
const {
|
||||||
id, type, data, transform, xPos, yPos, selected,
|
id, type, data, transform, xPos, yPos, selected,
|
||||||
dispatch, onClick, onNodeDragStop, style
|
onClick, onNodeDragStop, style
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const position = { x: xPos, y: yPos };
|
const position = { x: xPos, y: yPos };
|
||||||
@@ -93,13 +93,13 @@ export default NodeComponent => {
|
|||||||
target: getHandleBounds('.target', nodeElement.current, bounds, transform[2])
|
target: getHandleBounds('.target', nodeElement.current, bounds, transform[2])
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(updateNodeData(id, { ...dimensions, handleBounds }));
|
store.dispatch.updateNodeData({ id, ...dimensions, handleBounds });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactDraggable.DraggableCore
|
<ReactDraggable.DraggableCore
|
||||||
onStart={evt => onStart(evt, { dispatch, onClick, id, type, data, setOffset, transform, position })}
|
onStart={evt => onStart(evt, { onClick, id, type, data, setOffset, transform, position })}
|
||||||
onDrag={evt => onDrag(evt, { dispatch, setDragging, id, offset, transform })}
|
onDrag={evt => onDrag(evt, { setDragging, id, offset, transform })}
|
||||||
onStop={() => onStop({ onNodeDragStop, isDragging, setDragging, id, type, position, data })}
|
onStop={() => onStop({ onNodeDragStop, isDragging, setDragging, id, type, position, data })}
|
||||||
scale={transform[2]}
|
scale={transform[2]}
|
||||||
>
|
>
|
||||||
|
|||||||
+13
-10
@@ -1,9 +1,9 @@
|
|||||||
import React, { memo, useContext } from 'react';
|
import React, { memo } from 'react';
|
||||||
|
import { useStoreState } from 'easy-peasy';
|
||||||
|
|
||||||
import { GraphContext } from '../GraphContext';
|
|
||||||
import { isNode } from '../graph-utils';
|
import { isNode } from '../graph-utils';
|
||||||
|
|
||||||
function renderNode(d, props, graphContext) {
|
function renderNode(d, props, state) {
|
||||||
const nodeType = d.type || 'default';
|
const nodeType = d.type || 'default';
|
||||||
|
|
||||||
if (!props.nodeTypes[nodeType]) {
|
if (!props.nodeTypes[nodeType]) {
|
||||||
@@ -11,7 +11,7 @@ function renderNode(d, props, graphContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NodeComponent = props.nodeTypes[nodeType] || props.nodeTypes.default;
|
const NodeComponent = props.nodeTypes[nodeType] || props.nodeTypes.default;
|
||||||
const selected = graphContext.state.selectedElements
|
const selected = state.selectedElements
|
||||||
.filter(isNode)
|
.filter(isNode)
|
||||||
.map(e => e.id)
|
.map(e => e.id)
|
||||||
.includes(d.id);
|
.includes(d.id);
|
||||||
@@ -26,9 +26,7 @@ function renderNode(d, props, graphContext) {
|
|||||||
yPos={d.__rg.position.y}
|
yPos={d.__rg.position.y}
|
||||||
onClick={props.onElementClick}
|
onClick={props.onElementClick}
|
||||||
onNodeDragStop={props.onNodeDragStop}
|
onNodeDragStop={props.onNodeDragStop}
|
||||||
dispatch={graphContext.dispatch}
|
transform={state.transform}
|
||||||
transform={graphContext.state.transform}
|
|
||||||
getNodeById={graphContext.getNodeById}
|
|
||||||
selected={selected}
|
selected={selected}
|
||||||
style={d.style}
|
style={d.style}
|
||||||
/>
|
/>
|
||||||
@@ -36,8 +34,13 @@ function renderNode(d, props, graphContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NodeRenderer = memo((props) => {
|
const NodeRenderer = memo((props) => {
|
||||||
const graphContext = useContext(GraphContext);
|
const state = useStoreState(s => ({
|
||||||
const { transform, nodes } = graphContext.state;
|
nodes: s.nodes,
|
||||||
|
transform: s.transform,
|
||||||
|
selectedElements: s.selectedElements
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { transform, nodes } = state;
|
||||||
const transformStyle = { transform : `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})` };
|
const transformStyle = { transform : `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})` };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -45,7 +48,7 @@ const NodeRenderer = memo((props) => {
|
|||||||
className="react-graph__nodes"
|
className="react-graph__nodes"
|
||||||
style={transformStyle}
|
style={transformStyle}
|
||||||
>
|
>
|
||||||
{nodes.map(d => renderNode(d, props, graphContext))}
|
{nodes.map(d => renderNode(d, props, state))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { useContext, useState, memo } from 'react';
|
import React, { useState, memo } from 'react';
|
||||||
import ReactDraggable from 'react-draggable';
|
import ReactDraggable from 'react-draggable';
|
||||||
|
import { useStoreState, useStoreActions } from 'easy-peasy';
|
||||||
|
|
||||||
import { GraphContext } from '../GraphContext';
|
|
||||||
import { isNode } from '../graph-utils';
|
import { isNode } from '../graph-utils';
|
||||||
import { updateNodePos } from '../state/actions';
|
|
||||||
|
|
||||||
function getStartPositions(elements) {
|
function getStartPositions(elements) {
|
||||||
return elements
|
return elements
|
||||||
@@ -21,10 +20,14 @@ function getStartPositions(elements) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default memo(() => {
|
export default memo(() => {
|
||||||
const graphContext = useContext(GraphContext);
|
|
||||||
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
||||||
const [startPositions, setStartPositions] = useState({});
|
const [startPositions, setStartPositions] = useState({});
|
||||||
const { state, dispatch } = graphContext;
|
const state = useStoreState(s => ({
|
||||||
|
transform: s.transform,
|
||||||
|
selectedNodesBbox: s.selectedNodesBbox,
|
||||||
|
selectedElements: s.selectedElements
|
||||||
|
}));
|
||||||
|
const updateNodePos = useStoreActions(a => a.updateNodePos);
|
||||||
const [x, y, k] = state.transform;
|
const [x, y, k] = state.transform;
|
||||||
const position = state.selectedNodesBbox;
|
const position = state.selectedNodesBbox;
|
||||||
|
|
||||||
@@ -48,10 +51,10 @@ export default memo(() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
state.selectedElements.filter(isNode).forEach(node => {
|
state.selectedElements.filter(isNode).forEach(node => {
|
||||||
dispatch(updateNodePos(node.id, {
|
updateNodePos({ id: node.id, pos: {
|
||||||
x: startPositions[node.id].x + scaledClient.x - position.x - offset.x - x ,
|
x: startPositions[node.id].x + scaledClient.x - position.x - offset.x - x ,
|
||||||
y: startPositions[node.id].y + scaledClient.y - position.y - offset.y - y
|
y: startPositions[node.id].y + scaledClient.y - position.y - offset.y - y
|
||||||
}));
|
}});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,7 +62,7 @@ export default memo(() => {
|
|||||||
<div
|
<div
|
||||||
className="react-graph__nodesselection"
|
className="react-graph__nodesselection"
|
||||||
style={{
|
style={{
|
||||||
transform: `translate(${state.transform[0]}px,${state.transform[1]}px) scale(${state.transform[2]})`
|
transform: `translate(${x}px,${y}px) scale(${k})`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ReactDraggable
|
<ReactDraggable
|
||||||
@@ -79,4 +82,4 @@ export default memo(() => {
|
|||||||
</ReactDraggable>
|
</ReactDraggable>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
import React, { useRef, useContext, useEffect } from 'react';
|
import React, { useRef, useEffect } from 'react';
|
||||||
|
import { useStoreState } from 'easy-peasy';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import { isFunction } from '../../utils'
|
import { isFunction } from '../../utils'
|
||||||
import { getNodesInside } from '../../graph-utils';
|
import { getNodesInside } from '../../graph-utils';
|
||||||
import { GraphContext } from '../../GraphContext';
|
|
||||||
|
|
||||||
const baseStyle = {
|
const baseStyle = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
zIndex: 5,
|
zIndex: 5,
|
||||||
bottom: 10,
|
bottom: 10,
|
||||||
right: 10,
|
right: 10,
|
||||||
width: 200,
|
width: 200
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ({ style = {}, className, bgColor = '#f8f8f8', nodeColor = '#ddd' }) => {
|
export default ({ style = {}, className, bgColor = '#f8f8f8', nodeColor = '#ddd' }) => {
|
||||||
const canvasNode = useRef(null);
|
const canvasNode = useRef(null);
|
||||||
const { state } = useContext(GraphContext);
|
const state = useStoreState(s => ({
|
||||||
const mapClasses = classnames('react-graph__minimap', className);
|
width: s.width,
|
||||||
|
height: s.height,
|
||||||
|
nodes: s.nodes,
|
||||||
|
transform: s.transform,
|
||||||
|
})); const mapClasses = classnames('react-graph__minimap', className);
|
||||||
const nodePositions = state.nodes.map(n => n.__rg.position);
|
const nodePositions = state.nodes.map(n => n.__rg.position);
|
||||||
const width = style.width || baseStyle.width;
|
const width = style.width || baseStyle.width;
|
||||||
const height = (state.height / (state.width || 1)) * width;
|
const height = (state.height / (state.width || 1)) * width;
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { useEffect, memo } from 'react';
|
||||||
|
import { useStoreState, useStoreActions } from 'easy-peasy';
|
||||||
|
import isEqual from 'fast-deep-equal';
|
||||||
|
|
||||||
|
import { parseElement, isNode, isEdge } from '../graph-utils';
|
||||||
|
|
||||||
|
const ElementUpdater = (props) => {
|
||||||
|
const state = useStoreState(s => ({
|
||||||
|
nodes: s.nodes,
|
||||||
|
edges: s.edges,
|
||||||
|
transform: s.transform
|
||||||
|
}));
|
||||||
|
|
||||||
|
const setNodes = useStoreActions(a => a.setNodes);
|
||||||
|
const setEdges = useStoreActions(a => a.setEdges);
|
||||||
|
const setOnConnect = useStoreActions(a => a.setOnConnect);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const nodes = props.elements.filter(isNode);
|
||||||
|
const edges = props.elements.filter(isEdge).map(parseElement);
|
||||||
|
|
||||||
|
const nextNodes = nodes.map(propNode => {
|
||||||
|
const existingNode = state.nodes.find(n => n.id === propNode.id);
|
||||||
|
|
||||||
|
if (existingNode) {
|
||||||
|
const data = !isEqual(existingNode.data, propNode.data) ?
|
||||||
|
{ ...existingNode.data, ...propNode.data } : existingNode.data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...existingNode,
|
||||||
|
data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseElement(propNode, state.transform);
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodesChanged = !isEqual(state.nodes, nextNodes);
|
||||||
|
const edgesChanged = !isEqual(state.edges, edges);
|
||||||
|
|
||||||
|
if (nodesChanged) {
|
||||||
|
setNodes(nextNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edgesChanged) {
|
||||||
|
setEdges(edges);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOnConnect(props.onConnect);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
ElementUpdater.displayName = 'ElementUpdater';
|
||||||
|
|
||||||
|
export default memo(ElementUpdater);
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo, useEffect } from 'react';
|
||||||
|
import isEqual from 'fast-deep-equal';
|
||||||
|
import { StoreProvider } from 'easy-peasy';
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
const whyDidYouRender = require('@welldone-software/why-did-you-render');
|
const whyDidYouRender = require('@welldone-software/why-did-you-render');
|
||||||
@@ -7,7 +9,6 @@ if (process.env.NODE_ENV !== 'production') {
|
|||||||
|
|
||||||
import GraphView from '../GraphView';
|
import GraphView from '../GraphView';
|
||||||
import GlobalKeyHandler from '../GlobalKeyHandler';
|
import GlobalKeyHandler from '../GlobalKeyHandler';
|
||||||
import { Provider } from '../GraphContext';
|
|
||||||
|
|
||||||
import DefaultNode from '../NodeRenderer/NodeTypes/DefaultNode';
|
import DefaultNode from '../NodeRenderer/NodeTypes/DefaultNode';
|
||||||
import InputNode from '../NodeRenderer/NodeTypes/InputNode';
|
import InputNode from '../NodeRenderer/NodeTypes/InputNode';
|
||||||
@@ -18,6 +19,8 @@ import BezierEdge from '../EdgeRenderer/EdgeTypes/BezierEdge';
|
|||||||
import StraightEdge from '../EdgeRenderer/EdgeTypes/StraightEdge';
|
import StraightEdge from '../EdgeRenderer/EdgeTypes/StraightEdge';
|
||||||
import StepEdge from '../EdgeRenderer/EdgeTypes/StepEdge';
|
import StepEdge from '../EdgeRenderer/EdgeTypes/StepEdge';
|
||||||
import { createEdgeTypes } from '../EdgeRenderer/utils';
|
import { createEdgeTypes } from '../EdgeRenderer/utils';
|
||||||
|
import store from '../store';
|
||||||
|
import ElementUpdater from './ElementUpdater';
|
||||||
|
|
||||||
import '../style.css';
|
import '../style.css';
|
||||||
|
|
||||||
@@ -31,7 +34,8 @@ const ReactGraph = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style} className="react-graph">
|
<div style={style} className="react-graph">
|
||||||
<Provider elements={elements}>
|
<StoreProvider store={store}>
|
||||||
|
<ElementUpdater elements={elements} onConnect={onConnect} />
|
||||||
<GraphView
|
<GraphView
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
onMove={onMove}
|
onMove={onMove}
|
||||||
@@ -41,13 +45,12 @@ const ReactGraph = ({
|
|||||||
edgeTypes={edgeTypesParsed}
|
edgeTypes={edgeTypesParsed}
|
||||||
connectionLineType={connectionLineType}
|
connectionLineType={connectionLineType}
|
||||||
connectionLineStyle={connectionLineStyle}
|
connectionLineStyle={connectionLineStyle}
|
||||||
onConnect={onConnect}
|
|
||||||
/>
|
/>
|
||||||
<GlobalKeyHandler
|
<GlobalKeyHandler
|
||||||
onElementsRemove={onElementsRemove}
|
onElementsRemove={onElementsRemove}
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
</Provider>
|
</StoreProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import React, { useEffect, useRef, useState, useContext, memo } from 'react';
|
import React, { useEffect, useRef, useState, memo } from 'react';
|
||||||
|
import { useStoreActions } from 'easy-peasy';
|
||||||
import { GraphContext } from '../GraphContext';
|
|
||||||
import { updateSelection, setSelection, setNodesSelection } from '../state/actions';
|
|
||||||
|
|
||||||
const initialRect = {
|
const initialRect = {
|
||||||
startX: 0,
|
startX: 0,
|
||||||
@@ -25,7 +23,9 @@ function getMousePosition(evt) {
|
|||||||
export default memo(() => {
|
export default memo(() => {
|
||||||
const selectionPane = useRef(null);
|
const selectionPane = useRef(null);
|
||||||
const [rect, setRect] = useState(initialRect);
|
const [rect, setRect] = useState(initialRect);
|
||||||
const { dispatch } = useContext(GraphContext);
|
const setSelection = useStoreActions(a => a.setSelection);
|
||||||
|
const updateSelection = useStoreActions(a => a.updateSelection);
|
||||||
|
const setNodesSelection = useStoreActions(a => a.setNodesSelection);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function onMouseDown(evt) {
|
function onMouseDown(evt) {
|
||||||
@@ -40,7 +40,7 @@ export default memo(() => {
|
|||||||
draw: true
|
draw: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
dispatch(setSelection(true));
|
setSelection(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseMove(evt) {
|
function onMouseMove(evt) {
|
||||||
@@ -60,7 +60,7 @@ export default memo(() => {
|
|||||||
height: negativeY ? currentRect.startY - mousePos.y : mousePos.y - currentRect.startY,
|
height: negativeY ? currentRect.startY - mousePos.y : mousePos.y - currentRect.startY,
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(updateSelection(nextRect));
|
updateSelection(nextRect);
|
||||||
|
|
||||||
return nextRect;
|
return nextRect;
|
||||||
});
|
});
|
||||||
@@ -68,8 +68,8 @@ export default memo(() => {
|
|||||||
|
|
||||||
function onMouseUp() {
|
function onMouseUp() {
|
||||||
setRect((currentRect) => {
|
setRect((currentRect) => {
|
||||||
dispatch(setNodesSelection({ isActive: true, selection: currentRect }));
|
setNodesSelection({ isActive: true, selection: currentRect });
|
||||||
dispatch(setSelection(false));
|
setSelection(false);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...currentRect,
|
...currentRect,
|
||||||
|
|||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
import { action } from 'easy-peasy';
|
||||||
|
import isEqual from 'fast-deep-equal';
|
||||||
|
|
||||||
|
import { getBoundingBox, getNodesInside, getConnectedEdges } from './graph-utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setOnConnect: action((state, onConnect) => {
|
||||||
|
state.onConnect = onConnect;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setNodes: action((state, nodes) => {
|
||||||
|
state.nodes = nodes;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setEdges: action((state, edges) => {
|
||||||
|
state.edges = edges;
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateNodeData: action((state, { id, ...data }) => {
|
||||||
|
state.nodes.forEach((n) => {
|
||||||
|
if (n.id === id) {
|
||||||
|
n.__rg = {
|
||||||
|
...n.__rg,
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateNodePos: action((state, { id, pos }) => {
|
||||||
|
state.nodes.forEach((n) => {
|
||||||
|
if (n.id === id) {
|
||||||
|
n.__rg = {
|
||||||
|
...n.__rg,
|
||||||
|
position: pos
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
setSelection: action((state, isActive) => {
|
||||||
|
state.selectionActive = isActive;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setNodesSelection: action((state, { isActive, selection }) => {
|
||||||
|
if (!isActive) {
|
||||||
|
state.nodesSelectionActive = false;
|
||||||
|
state.selectedElements = [];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedNodes = getNodesInside(state.nodes, selection, state.transform);
|
||||||
|
const selectedNodesBbox = getBoundingBox(selectedNodes);
|
||||||
|
|
||||||
|
state.selection = selection;
|
||||||
|
state.nodesSelectionActive = true;
|
||||||
|
state.selectedNodesBbox = selectedNodesBbox;
|
||||||
|
state.nodesSelectionActive = true;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setSelectedElements: action((state, elements) => {
|
||||||
|
const selectedElementsArr = Array.isArray(elements) ? elements : [elements];
|
||||||
|
const selectedElementsUpdated = !isEqual(selectedElementsArr, state.selectedElements);
|
||||||
|
const selectedElements = selectedElementsUpdated ? selectedElementsArr : state.selectedElements;
|
||||||
|
|
||||||
|
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];
|
||||||
|
}),
|
||||||
|
|
||||||
|
updateSize: action((state, size) => {
|
||||||
|
state.width = size.width;
|
||||||
|
state.height = size.height;
|
||||||
|
}),
|
||||||
|
|
||||||
|
initD3: action((state, { zoom, selection }) => {
|
||||||
|
state.d3Zoom = zoom;
|
||||||
|
state.d3Selection = selection;
|
||||||
|
state.d3Initialised = true;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setConnectionPosition: action((state, position) => {
|
||||||
|
state.connectionPosition = position;
|
||||||
|
}),
|
||||||
|
|
||||||
|
setConnectionSourceId: action((state, sourceId) => {
|
||||||
|
state.connectionSourceId = sourceId;
|
||||||
|
})
|
||||||
|
};
|
||||||
+30
-1
@@ -1,3 +1,6 @@
|
|||||||
|
import { zoomIdentity } from 'd3-zoom';
|
||||||
|
import store from './store';
|
||||||
|
|
||||||
export const isEdge = element => element.source && element.target;
|
export const isEdge = element => element.source && element.target;
|
||||||
|
|
||||||
export const isNode = element => !element.source && !element.target;
|
export const isNode = element => !element.source && !element.target;
|
||||||
@@ -142,11 +145,37 @@ export const getConnectedEdges = (nodes, edges) => {
|
|||||||
return edges.filter(e => nodeIds.includes(e.source) || nodeIds.includes(e.target))
|
return edges.filter(e => nodeIds.includes(e.source) || nodeIds.includes(e.target))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fitView = ({ padding = 0 } = {}) => {
|
||||||
|
const state = store.getState();
|
||||||
|
const bounds = getBoundingBox(state.nodes);
|
||||||
|
const maxBoundsSize = Math.max(bounds.width, bounds.height);
|
||||||
|
const k = Math.min(state.width, state.height) / (maxBoundsSize + (maxBoundsSize * padding));
|
||||||
|
const boundsCenterX = bounds.x + (bounds.width / 2);
|
||||||
|
const boundsCenterY = bounds.y + (bounds.height / 2);
|
||||||
|
const transform = [(state.width / 2) - (boundsCenterX * k), (state.height / 2) - (boundsCenterY * k)];
|
||||||
|
const fittedTransform = zoomIdentity.translate(transform[0], transform[1]).scale(k);
|
||||||
|
|
||||||
|
state.d3Selection.call(state.d3Zoom.transform, fittedTransform);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const zoomIn = () => {
|
||||||
|
const state = store.getState();
|
||||||
|
state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] + 0.2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const zoomOut = () => {
|
||||||
|
const state = store.getState();
|
||||||
|
state.d3Zoom.scaleTo(state.d3Selection, state.transform[2] - 0.2);
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
isEdge,
|
isEdge,
|
||||||
separateElements,
|
separateElements,
|
||||||
getBoundingBox,
|
getBoundingBox,
|
||||||
graphPosToZoomedPos,
|
graphPosToZoomedPos,
|
||||||
getConnectedEdges,
|
getConnectedEdges,
|
||||||
parseElement
|
parseElement,
|
||||||
|
fitView,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut
|
||||||
};
|
};
|
||||||
|
|||||||
+15
-7
@@ -1,18 +1,26 @@
|
|||||||
import { useEffect, useContext } from 'react';
|
import { useEffect } from 'react';
|
||||||
import * as d3Zoom from 'd3-zoom';
|
import * as d3Zoom from 'd3-zoom';
|
||||||
import { select, event } from 'd3-selection';
|
import { select, event } from 'd3-selection';
|
||||||
|
import { useStoreState, useStoreActions } from 'easy-peasy';
|
||||||
import { updateTransform, initD3 } from '../state/view-actions';
|
|
||||||
import { GraphContext } from '../GraphContext';
|
|
||||||
|
|
||||||
const d3ZoomInstance = d3Zoom.zoom().scaleExtent([0.5, 2]);
|
const d3ZoomInstance = d3Zoom.zoom().scaleExtent([0.5, 2]);
|
||||||
|
|
||||||
export default function useD3Zoom(zoomPane, onMove, shiftPressed) {
|
export default function useD3Zoom(zoomPane, onMove, shiftPressed) {
|
||||||
const { state, dispatch } = useContext(GraphContext);
|
const state = useStoreState(s => ({
|
||||||
|
transform: s.transform,
|
||||||
|
d3Selection: s.d3Selection,
|
||||||
|
d3Zoom: s.d3Zoom,
|
||||||
|
edges: s.edged,
|
||||||
|
d3Initialised: s.d3Initialised,
|
||||||
|
nodesSelectionActive: s.nodesSelectionActive
|
||||||
|
}));
|
||||||
|
|
||||||
|
const initD3 = useStoreActions(actions => actions.initD3);
|
||||||
|
const updateTransform = useStoreActions(actions => actions.updateTransform);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const selection = select(zoomPane.current).call(d3ZoomInstance);
|
const selection = select(zoomPane.current).call(d3ZoomInstance);
|
||||||
dispatch(initD3({ zoom: d3ZoomInstance, selection }));
|
initD3({ zoom: d3ZoomInstance, selection });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -24,7 +32,7 @@ export default function useD3Zoom(zoomPane, onMove, shiftPressed) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(updateTransform(event.transform));
|
updateTransform(event.transform);
|
||||||
|
|
||||||
onMove();
|
onMove();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
import {
|
|
||||||
SET_NODES, SET_EDGES, UPDATE_NODE_DATA, UPDATE_NODE_POS,
|
|
||||||
UPDATE_SELECTION, SET_SELECTION, SET_NODES_SELECTION,
|
|
||||||
SET_SELECTED_ELEMENTS, REMOVE_NODES,
|
|
||||||
} from './index';
|
|
||||||
|
|
||||||
export const setNodes = nodes => ({
|
|
||||||
type: SET_NODES,
|
|
||||||
payload: { nodes }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const setEdges = edges => ({
|
|
||||||
type: SET_EDGES,
|
|
||||||
payload: { edges }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateNodeData = (id, data) => ({
|
|
||||||
type: UPDATE_NODE_DATA,
|
|
||||||
payload: { id, data }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateNodePos = (id, pos) => ({
|
|
||||||
type: UPDATE_NODE_POS,
|
|
||||||
payload: { id, pos }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const setSelection = isActive => ({
|
|
||||||
type: SET_SELECTION,
|
|
||||||
payload: { selectionActive: isActive }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const setNodesSelection = ({ isActive, selection }) => ({
|
|
||||||
type: SET_NODES_SELECTION,
|
|
||||||
payload: { nodesSelectionActive: isActive, selection }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const setSelectedElements = elements => ({
|
|
||||||
type: SET_SELECTED_ELEMENTS,
|
|
||||||
payload: {
|
|
||||||
selectedElements: Array.isArray(elements) ? elements : [elements]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const removeNodes = ids => ({
|
|
||||||
type: REMOVE_NODES,
|
|
||||||
payload: { ids: Array.isArray(ids) ? ids : [ids] }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateSelection = selection => ({
|
|
||||||
type: UPDATE_SELECTION,
|
|
||||||
payload: { selection }
|
|
||||||
});
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
import { zoomIdentity } from 'd3-zoom';
|
|
||||||
import isEqual from 'fast-deep-equal';
|
|
||||||
|
|
||||||
import { getBoundingBox, getNodesInside, getConnectedEdges } from '../graph-utils';
|
|
||||||
|
|
||||||
export const SET_EDGES = 'SET_EDGES';
|
|
||||||
export const SET_NODES = 'SET_NODES';
|
|
||||||
export const UPDATE_NODE_DATA = 'UPDATE_NODE_DATA';
|
|
||||||
export const UPDATE_NODE_POS = 'UPDATE_NODE_POS';
|
|
||||||
export const UPDATE_TRANSFORM = 'UPDATE_TRANSFORM';
|
|
||||||
export const UPDATE_SIZE = 'UPDATE_SIZE';
|
|
||||||
export const INIT_D3 = 'INIT_D3';
|
|
||||||
export const FIT_VIEW = 'FIT_VIEW';
|
|
||||||
export const ZOOM_IN = 'ZOOM_IN';
|
|
||||||
export const ZOOM_OUT = 'ZOOM_OUT';
|
|
||||||
export const UPDATE_SELECTION = 'UPDATE_SELECTION';
|
|
||||||
export const SET_SELECTION = 'SET_SELECTION';
|
|
||||||
export const SET_NODES_SELECTION = 'SET_NODES_SELECTION';
|
|
||||||
export const SET_SELECTED_ELEMENTS = 'SET_SELECTED_ELEMENTS';
|
|
||||||
export const REMOVE_NODES = 'REMOVE_NODES';
|
|
||||||
|
|
||||||
export const initialState = {
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
transform: [0, 0, 1],
|
|
||||||
nodes: [],
|
|
||||||
edges: [],
|
|
||||||
selectedElements: [],
|
|
||||||
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
|
||||||
|
|
||||||
d3Zoom: null,
|
|
||||||
d3Selection: null,
|
|
||||||
d3Initialised: false,
|
|
||||||
|
|
||||||
nodesSelectionActive: false,
|
|
||||||
selectionActive: false,
|
|
||||||
selection: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const reducer = (state, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case UPDATE_NODE_DATA: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
nodes: state.nodes.map((n) => {
|
|
||||||
if (n.id === action.payload.id) {
|
|
||||||
n.__rg = {
|
|
||||||
...n.__rg,
|
|
||||||
...action.payload.data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case UPDATE_NODE_POS: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
nodes: state.nodes.map((n) => {
|
|
||||||
if (n.id === action.payload.id) {
|
|
||||||
n.__rg = {
|
|
||||||
...n.__rg,
|
|
||||||
position: action.payload.pos
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return n;
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case FIT_VIEW: {
|
|
||||||
const bounds = getBoundingBox(state.nodes);
|
|
||||||
const maxBoundsSize = Math.max(bounds.width, bounds.height);
|
|
||||||
const k = Math.min(state.width, state.height) / (maxBoundsSize + (maxBoundsSize * action.payload.padding));
|
|
||||||
const boundsCenterX = bounds.x + (bounds.width / 2);
|
|
||||||
const boundsCenterY = bounds.y + (bounds.height / 2);
|
|
||||||
const transform = [(state.width / 2) - (boundsCenterX * k), (state.height / 2) - (boundsCenterY * k)];
|
|
||||||
const fittedTransform = zoomIdentity.translate(transform[0], transform[1]).scale(k);
|
|
||||||
|
|
||||||
state.d3Selection.call(state.d3Zoom.transform, fittedTransform);
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
case ZOOM_IN: {
|
|
||||||
const { transform } = state;
|
|
||||||
state.d3Zoom.scaleTo(state.d3Selection, transform[2] + 0.2);
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
case ZOOM_OUT: {
|
|
||||||
const { transform } = state;
|
|
||||||
state.d3Zoom.scaleTo(state.d3Selection, transform[2] - 0.2);
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
case UPDATE_SELECTION: {
|
|
||||||
const selectedNodes = getNodesInside(state.nodes, action.payload.selection, state.transform);
|
|
||||||
const selectedEdges = getConnectedEdges(selectedNodes, state.edges);
|
|
||||||
|
|
||||||
const nextSelectedElements = [...selectedNodes, ...selectedEdges];
|
|
||||||
const selectedElementsUpdated = !isEqual(nextSelectedElements, state.selectedElements);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
...action.payload,
|
|
||||||
selectedElements: selectedElementsUpdated ? nextSelectedElements: state.selectedElements
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case SET_NODES_SELECTION: {
|
|
||||||
if (!action.payload.nodesSelectionActive) {
|
|
||||||
return { ...state, nodesSelectionActive: false, selectedElements: [] };
|
|
||||||
}
|
|
||||||
const selectedNodes = getNodesInside(state.nodes, action.payload.selection, state.transform);
|
|
||||||
const selectedNodesBbox = getBoundingBox(selectedNodes);
|
|
||||||
|
|
||||||
return { ...state, ...action.payload, selectedNodesBbox };
|
|
||||||
}
|
|
||||||
case SET_SELECTED_ELEMENTS: {
|
|
||||||
const selectedElementsUpdated = !isEqual(action.payload.selectedElements, state.selectedElements);
|
|
||||||
const selectedElements = selectedElementsUpdated ? action.payload.selectedElements : state.selectedElements;
|
|
||||||
|
|
||||||
return { ...state, selectedElements, odesSelectionActive: false };
|
|
||||||
}
|
|
||||||
// unused
|
|
||||||
case REMOVE_NODES: {
|
|
||||||
const { ids } = action.payload;
|
|
||||||
const nextEdges = state.edges.filter(e => !ids.includes(e.target) && !ids.includes(e.source));
|
|
||||||
const nextNodes = state.nodes.filter(n => !ids.includes(n.id));
|
|
||||||
|
|
||||||
return { ...state, nodes: nextNodes, edges: nextEdges };
|
|
||||||
}
|
|
||||||
case SET_NODES:
|
|
||||||
case SET_EDGES:
|
|
||||||
case UPDATE_TRANSFORM:
|
|
||||||
case INIT_D3:
|
|
||||||
case UPDATE_SIZE:
|
|
||||||
case SET_SELECTION:
|
|
||||||
return { ...state, ...action.payload };
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import {
|
|
||||||
UPDATE_TRANSFORM, UPDATE_SIZE, INIT_D3,
|
|
||||||
FIT_VIEW, ZOOM_IN, ZOOM_OUT
|
|
||||||
} from './index';
|
|
||||||
|
|
||||||
export const updateTransform = transform => ({
|
|
||||||
type: UPDATE_TRANSFORM,
|
|
||||||
payload: { transform: [transform.x, transform.y, transform.k] }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateSize = size => ({
|
|
||||||
type: UPDATE_SIZE,
|
|
||||||
payload: size
|
|
||||||
});
|
|
||||||
|
|
||||||
export const initD3 = ({ zoom, selection }) => ({
|
|
||||||
type: INIT_D3,
|
|
||||||
payload: { d3Zoom: zoom, d3Selection: selection, d3Initialised: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fitView = ({ padding = 0 } = {}) => ({
|
|
||||||
type: FIT_VIEW,
|
|
||||||
payload: { padding }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const zoomIn = () => ({
|
|
||||||
type: ZOOM_IN
|
|
||||||
});
|
|
||||||
|
|
||||||
export const zoomOut = () => ({
|
|
||||||
type: ZOOM_OUT
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { createStore } from 'easy-peasy';
|
||||||
|
|
||||||
|
import actions from './actions';
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
transform: [0, 0, 1],
|
||||||
|
nodes: [],
|
||||||
|
edges: [],
|
||||||
|
selectedElements: [],
|
||||||
|
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
||||||
|
|
||||||
|
d3Zoom: null,
|
||||||
|
d3Selection: null,
|
||||||
|
d3Initialised: false,
|
||||||
|
|
||||||
|
nodesSelectionActive: false,
|
||||||
|
selectionActive: false,
|
||||||
|
selection: {},
|
||||||
|
|
||||||
|
connectionSourceId: null,
|
||||||
|
connectionPosition: { x: 0, y: 0 },
|
||||||
|
|
||||||
|
onConnect: () => {},
|
||||||
|
|
||||||
|
...actions
|
||||||
|
});
|
||||||
|
|
||||||
|
export default store;
|
||||||
Reference in New Issue
Block a user