feat(edges): allow different types
This commit is contained in:
@@ -85,7 +85,7 @@ class App extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<Graph
|
<Graph
|
||||||
elements={this.state.elements}
|
elements={this.state.elements}
|
||||||
onNodeClick={node => console.log(node)}
|
onElementClick={node => console.log('clicked', node)}
|
||||||
onNodeRemove={nodeIds => console.log('remove', nodeIds)}
|
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)}
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ export default (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<path
|
<path
|
||||||
className="react-graph__edge"
|
|
||||||
d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`}
|
d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`}
|
||||||
onClick={e => console.log('on edge click')}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import ReactDraggable from 'react-draggable';
|
||||||
|
import cx from 'classnames';
|
||||||
|
|
||||||
|
import { GraphContext } from '../../GraphContext';
|
||||||
|
import { updateNodePos, setSelectedNodesIds } from '../../state/actions';
|
||||||
|
|
||||||
|
const isInputTarget = (e) => ['INPUT', 'SELECT', 'TEXTAREA'].includes(e.target.nodeName);
|
||||||
|
|
||||||
|
export default EdgeComponent => (props) => {
|
||||||
|
const { state, dispatch } = useContext(GraphContext);
|
||||||
|
const { data, onClick } = props;
|
||||||
|
const { id } = data;
|
||||||
|
const [ x, y, k ] = state.transform;
|
||||||
|
const selected = state.selectedNodeIds.includes(id);
|
||||||
|
const edgeClasses = cx('react-graph__edge', { selected });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g
|
||||||
|
className={edgeClasses}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (isInputTarget(e)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatch(setSelectedNodesIds(id));
|
||||||
|
onClick({ data });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EdgeComponent {...props} />
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
+28
-24
@@ -1,30 +1,34 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import Edge from './Edge';
|
|
||||||
import { Consumer } from '../GraphContext';
|
import { Consumer } from '../GraphContext';
|
||||||
|
|
||||||
function renderEdge(e, nodes) {
|
|
||||||
const sourceNode = nodes.find(n => n.data.id === e.data.source);
|
|
||||||
const targetNode = nodes.find(n => n.data.id === e.data.target);
|
|
||||||
|
|
||||||
if (!sourceNode) {
|
|
||||||
throw new Error(`couldn't create edge for source id: ${e.data.source}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetNode) {
|
|
||||||
throw new Error(`couldn't create edge for source id: ${e.data.target}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Edge
|
|
||||||
key={`${e.data.source}-${e.data.target}`}
|
|
||||||
sourceNode={sourceNode}
|
|
||||||
targetNode={targetNode}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class EdgeRenderer extends PureComponent {
|
class EdgeRenderer extends PureComponent {
|
||||||
|
renderEdge(e, nodes, onElementClick) {
|
||||||
|
const edgeType = e.data.type || 'default';
|
||||||
|
const sourceNode = nodes.find(n => n.data.id === e.data.source);
|
||||||
|
const targetNode = nodes.find(n => n.data.id === e.data.target);
|
||||||
|
|
||||||
|
if (!sourceNode) {
|
||||||
|
throw new Error(`couldn't create edge for source id: ${e.data.source}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetNode) {
|
||||||
|
throw new Error(`couldn't create edge for source id: ${e.data.target}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EdgeComponent = this.props.edgeTypes[edgeType] || this.props.edgeTypes.default;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EdgeComponent
|
||||||
|
key={`${e.data.source}-${e.data.target}`}
|
||||||
|
sourceNode={sourceNode}
|
||||||
|
targetNode={targetNode}
|
||||||
|
onClick={onElementClick}
|
||||||
|
{...e}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { width, height } = this.props;
|
const { width, height } = this.props;
|
||||||
|
|
||||||
@@ -34,7 +38,7 @@ class EdgeRenderer extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Consumer>
|
<Consumer>
|
||||||
{({ state }) => (
|
{({ state, onElementClick }) => (
|
||||||
<svg
|
<svg
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
@@ -43,7 +47,7 @@ class EdgeRenderer extends PureComponent {
|
|||||||
<g
|
<g
|
||||||
transform={`translate(${state.transform[0]},${state.transform[1]}) scale(${state.transform[2]})`}
|
transform={`translate(${state.transform[0]},${state.transform[1]}) scale(${state.transform[2]})`}
|
||||||
>
|
>
|
||||||
{state.edges.map(e => renderEdge(e, state.nodes))}
|
{state.edges.map(e => this.renderEdge(e, state.nodes, onElementClick))}
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import DefaultEdge from './EdgeTypes/DefaultEdge';
|
||||||
|
import wrapEdge from './EdgeTypes/wrapEdge';
|
||||||
|
|
||||||
|
export function createEdgeTypes(edgeTypes) {
|
||||||
|
const standardTypes = {
|
||||||
|
default: wrapEdge(edgeTypes.default || DefaultEdge),
|
||||||
|
};
|
||||||
|
|
||||||
|
const specialTypes = Object
|
||||||
|
.keys(DefaultEdge)
|
||||||
|
.filter(k => !['default'].includes(k))
|
||||||
|
.reduce((res, key) => {
|
||||||
|
res[key] = wrapEdge(nodeTypes[key] || DefaultEdge);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...standardTypes,
|
||||||
|
...specialTypes
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ function usePrevious(value) {
|
|||||||
|
|
||||||
export const Provider = (props) => {
|
export const Provider = (props) => {
|
||||||
const {
|
const {
|
||||||
onNodeClick,
|
onElementClick,
|
||||||
children
|
children
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ export const Provider = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const graphContext = {
|
const graphContext = {
|
||||||
onNodeClick,
|
onElementClick,
|
||||||
state,
|
state,
|
||||||
dispatch
|
dispatch
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -74,7 +74,11 @@ const GraphView = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="react-graph__renderer">
|
<div className="react-graph__renderer">
|
||||||
<NodeRenderer nodeTypes={props.nodeTypes} />
|
<NodeRenderer nodeTypes={props.nodeTypes} />
|
||||||
<EdgeRenderer width={state.width} height={state.height} />
|
<EdgeRenderer
|
||||||
|
width={state.width}
|
||||||
|
height={state.height}
|
||||||
|
edgeTypes={props.edgeTypes}
|
||||||
|
/>
|
||||||
{shiftPressed && <UserSelection />}
|
{shiftPressed && <UserSelection />}
|
||||||
{state.nodesSelectionActive && <NodesSelection />}
|
{state.nodesSelectionActive && <NodesSelection />}
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default NodeComponent => (props) => {
|
|||||||
const { state, dispatch } = 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, onClick, __rg } = props;
|
||||||
const { position } = __rg;
|
const { position } = __rg;
|
||||||
const { id } = data;
|
const { id } = data;
|
||||||
const [ x, y, k ] = state.transform;
|
const [ x, y, k ] = state.transform;
|
||||||
@@ -70,7 +70,7 @@ export default NodeComponent => (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch(setSelectedNodesIds(id));
|
dispatch(setSelectedNodesIds(id));
|
||||||
onNodeClick({ data, position });
|
onClick({ data, position });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<NodeComponent {...props} />
|
<NodeComponent {...props} />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Consumer } from '../GraphContext';
|
|||||||
|
|
||||||
class NodeRenderer extends PureComponent {
|
class NodeRenderer extends PureComponent {
|
||||||
|
|
||||||
renderNode(d, onNodeClick) {
|
renderNode(d, onElementClick) {
|
||||||
const nodeType = d.data.type || 'default';
|
const nodeType = d.data.type || 'default';
|
||||||
if (!this.props.nodeTypes[nodeType]) {
|
if (!this.props.nodeTypes[nodeType]) {
|
||||||
console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`);
|
console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`);
|
||||||
@@ -15,7 +15,7 @@ class NodeRenderer extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<NodeComponent
|
<NodeComponent
|
||||||
key={d.data.id}
|
key={d.data.id}
|
||||||
onNodeClick={onNodeClick}
|
onClick={onElementClick}
|
||||||
{...d}
|
{...d}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -24,14 +24,14 @@ class NodeRenderer extends PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Consumer>
|
<Consumer>
|
||||||
{({ onNodeClick, state }) => (
|
{({ onElementClick, state }) => (
|
||||||
<div
|
<div
|
||||||
className="react-graph__nodes"
|
className="react-graph__nodes"
|
||||||
style={{
|
style={{
|
||||||
transform: `translate(${state.transform[0]}px,${state.transform[1]}px) scale(${state.transform[2]})`
|
transform: `translate(${state.transform[0]}px,${state.transform[1]}px) scale(${state.transform[2]})`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{state.nodes.map(d => this.renderNode(d, onNodeClick))}
|
{state.nodes.map(d => this.renderNode(d, onElementClick))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Consumer>
|
</Consumer>
|
||||||
|
|||||||
+12
-4
@@ -4,11 +4,14 @@ import { parseElements, separateElements } from './graph-utils';
|
|||||||
import GraphView from './GraphView';
|
import GraphView from './GraphView';
|
||||||
import GlobalKeyHandler from './GlobalKeyHandler';
|
import GlobalKeyHandler from './GlobalKeyHandler';
|
||||||
import { Provider } from './GraphContext';
|
import { Provider } from './GraphContext';
|
||||||
import { createNodeTypes } from './NodeRenderer/utils';
|
|
||||||
|
|
||||||
import DefaultNode from './NodeRenderer/NodeTypes/DefaultNode';
|
import DefaultNode from './NodeRenderer/NodeTypes/DefaultNode';
|
||||||
import InputNode from './NodeRenderer/NodeTypes/InputNode';
|
import InputNode from './NodeRenderer/NodeTypes/InputNode';
|
||||||
import OutputNode from './NodeRenderer/NodeTypes/OutputNode';
|
import OutputNode from './NodeRenderer/NodeTypes/OutputNode';
|
||||||
|
import { createNodeTypes } from './NodeRenderer/utils';
|
||||||
|
|
||||||
|
import DefaultEdge from './EdgeRenderer/EdgeTypes/DefaultEdge';
|
||||||
|
import { createEdgeTypes } from './EdgeRenderer/utils';
|
||||||
|
|
||||||
import './style.css';
|
import './style.css';
|
||||||
|
|
||||||
@@ -17,11 +20,12 @@ class ReactGraph extends PureComponent {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.nodeTypes = createNodeTypes(props.nodeTypes);
|
this.nodeTypes = createNodeTypes(props.nodeTypes);
|
||||||
|
this.edgeTypes = createEdgeTypes(props.edgeTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
style, onNodeClick, children, onLoad, onMove, onChange, elements, onNodeRemove
|
style, onElementClick, children, onLoad, onMove, onChange, elements, onNodeRemove
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { nodes, edges } = elements
|
const { nodes, edges } = elements
|
||||||
@@ -30,12 +34,13 @@ class ReactGraph extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style} className="react-graph">
|
<div style={style} className="react-graph">
|
||||||
<Provider nodes={nodes} edges={edges} onNodeClick={onNodeClick}>
|
<Provider nodes={nodes} edges={edges} onElementClick={onElementClick}>
|
||||||
<GraphView
|
<GraphView
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
onMove={onMove}
|
onMove={onMove}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
nodeTypes={this.nodeTypes}
|
nodeTypes={this.nodeTypes}
|
||||||
|
edgeTypes={this.edgeTypes}
|
||||||
/>
|
/>
|
||||||
<GlobalKeyHandler
|
<GlobalKeyHandler
|
||||||
onNodeRemove={onNodeRemove}
|
onNodeRemove={onNodeRemove}
|
||||||
@@ -48,7 +53,7 @@ class ReactGraph extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ReactGraph.defaultProps = {
|
ReactGraph.defaultProps = {
|
||||||
onNodeClick: () => {},
|
onElementClick: () => {},
|
||||||
onNodeRemove: () => {},
|
onNodeRemove: () => {},
|
||||||
onLoad: () => {},
|
onLoad: () => {},
|
||||||
onMove: () => {},
|
onMove: () => {},
|
||||||
@@ -57,6 +62,9 @@ ReactGraph.defaultProps = {
|
|||||||
input: InputNode,
|
input: InputNode,
|
||||||
default: DefaultNode,
|
default: DefaultNode,
|
||||||
output: OutputNode
|
output: OutputNode
|
||||||
|
},
|
||||||
|
edgeTypes: {
|
||||||
|
default: DefaultEdge
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user