265 lines
6.1 KiB
JavaScript
265 lines
6.1 KiB
JavaScript
/* global document */
|
|
|
|
const {
|
|
map,
|
|
prop,
|
|
groupBy,
|
|
flatten,
|
|
} = require('ramda');
|
|
|
|
const React = require('react');
|
|
|
|
const r = require('r-dom');
|
|
|
|
const plusMinus = require('../../utils/plus-minus');
|
|
|
|
const memoize = require('../../utils/memoize');
|
|
|
|
const {
|
|
GraphView: GraphViewBase,
|
|
} = require('./base');
|
|
|
|
const originalEdgeToSatelliteNode = edge => ({
|
|
id: `${edge.target}__satellite__${edge.id}`,
|
|
type: 'satellite',
|
|
|
|
edge: edge.id,
|
|
edgeType: edge.type,
|
|
|
|
source: edge.source,
|
|
sourceType: edge.source.type,
|
|
|
|
target: edge.target,
|
|
targetType: edge.target.type,
|
|
});
|
|
|
|
const originalEdgeAndSatelliteNodeToSatelliteEdge = (edge, satelliteNode) => {
|
|
const satelliteEdge = {
|
|
id: edge.id,
|
|
source: edge.source,
|
|
target: satelliteNode.id,
|
|
originalTarget: edge.target,
|
|
index: edge.index,
|
|
type: edge.type,
|
|
};
|
|
|
|
satelliteEdgeToOriginalEdge.set(satelliteEdge, edge);
|
|
return satelliteEdge;
|
|
};
|
|
|
|
const originalEdgeToSatellites = memoize(edge => {
|
|
const satelliteNode = originalEdgeToSatelliteNode(edge);
|
|
const satelliteEdge = originalEdgeAndSatelliteNodeToSatelliteEdge(edge, satelliteNode);
|
|
return { satelliteEdge, satelliteNode };
|
|
});
|
|
|
|
const Satellite = () => r(React.Fragment);
|
|
|
|
const satelliteSpread = 36;
|
|
|
|
const satelliteEdgeToOriginalEdge = new WeakMap();
|
|
|
|
class SatellitesGraphView extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
originalEdgesByTargetNodeKey: {},
|
|
satelliteNodesByTargetNodeKey: {},
|
|
satelliteEdges: [],
|
|
selected: null,
|
|
};
|
|
|
|
this.graphViewRef = this.props.graphViewRef || React.createRef();
|
|
|
|
Object.assign(this, {
|
|
onSwapEdge: this.onSwapEdge.bind(this),
|
|
onNodeMove: this.onNodeMove.bind(this),
|
|
|
|
onSelectEdge: this.onSelectEdge.bind(this),
|
|
onEdgeMouseDown: this.onEdgeMouseDown.bind(this),
|
|
onCreateEdge: this.onCreateEdge.bind(this),
|
|
|
|
renderNode: this.renderNode.bind(this),
|
|
renderNodeText: this.renderNodeText.bind(this),
|
|
|
|
renderEdge: this.renderEdge.bind(this),
|
|
renderEdgeText: this.renderEdgeText.bind(this),
|
|
|
|
afterRenderEdge: this.afterRenderEdge.bind(this),
|
|
});
|
|
}
|
|
|
|
static getDerivedStateFromProps(props) {
|
|
const originalEdgesByTargetNodeKey = groupBy(prop('target'), props.edges);
|
|
|
|
let { selected, moved } = props;
|
|
|
|
const satelliteEdges = [];
|
|
|
|
const satelliteNodesByTargetNodeKey = map(edges => map(edge => {
|
|
const {
|
|
satelliteNode,
|
|
satelliteEdge,
|
|
} = originalEdgeToSatellites(edge);
|
|
|
|
if (edge === selected) {
|
|
selected = satelliteEdge;
|
|
}
|
|
|
|
if (edge === moved) {
|
|
moved = satelliteEdge;
|
|
}
|
|
|
|
satelliteEdges.push(satelliteEdge);
|
|
|
|
return satelliteNode;
|
|
}, edges), originalEdgesByTargetNodeKey);
|
|
|
|
const satelliteNodes = flatten(map(node => {
|
|
const satelliteNodes = satelliteNodesByTargetNodeKey[node.id] || [];
|
|
SatellitesGraphView.repositionSatellites(node, satelliteNodes);
|
|
return satelliteNodes.concat(node);
|
|
}, props.nodes));
|
|
|
|
return {
|
|
originalEdgesByTargetNodeKey,
|
|
satelliteNodesByTargetNodeKey,
|
|
satelliteEdges,
|
|
satelliteNodes,
|
|
|
|
selected,
|
|
moved,
|
|
};
|
|
}
|
|
|
|
static repositionSatellites(position, satelliteNodes) {
|
|
const offsetY = (satelliteNodes % 2) ? 0 : (satelliteSpread / 2);
|
|
|
|
satelliteNodes.forEach((satelliteNode, i) => {
|
|
if (satelliteNode.edgeType === 'monitorSource') {
|
|
satelliteNode.x = position.x;
|
|
satelliteNode.y = position.y;
|
|
return;
|
|
}
|
|
satelliteNode.x = position.x;
|
|
satelliteNode.y = position.y +
|
|
offsetY +
|
|
(satelliteSpread * plusMinus(i)) +
|
|
((satelliteSpread / 2) * ((satelliteNodes.length + 1) % 2));
|
|
});
|
|
}
|
|
|
|
onSwapEdge(sourceNode, targetNode, edge) {
|
|
this.props.onSwapEdge(sourceNode, targetNode, edge);
|
|
|
|
const { nodeKey } = this.props;
|
|
|
|
const createdEdgeId = `edge-${sourceNode[nodeKey]}-${targetNode[nodeKey]}-container`;
|
|
const createdEdge = document.getElementById(createdEdgeId);
|
|
createdEdge.remove();
|
|
this.graphViewRef.current.forceUpdate();
|
|
}
|
|
|
|
onCreateEdge(source, target) {
|
|
const { nodeKey, onCreateEdge } = this.props;
|
|
onCreateEdge(source, target);
|
|
this.graphViewRef.current.removeEdgeElement(source[nodeKey], target[nodeKey]);
|
|
}
|
|
|
|
onNodeMove(position, nodeId, shiftKey) {
|
|
const { nodeKey } = this.props;
|
|
const satelliteNodes = this.state.satelliteNodesByTargetNodeKey[nodeId];
|
|
if (satelliteNodes) {
|
|
this.constructor.repositionSatellites(position, satelliteNodes);
|
|
satelliteNodes.forEach(satelliteNode => {
|
|
this.graphViewRef.current.handleNodeMove(satelliteNode, satelliteNode[nodeKey], shiftKey);
|
|
});
|
|
}
|
|
}
|
|
|
|
onSelectEdge(edge) {
|
|
const originalEdge = satelliteEdgeToOriginalEdge.get(edge);
|
|
if (this.props.onSelectEdge) {
|
|
this.props.onSelectEdge(originalEdge || edge);
|
|
}
|
|
}
|
|
|
|
onEdgeMouseDown(event, edge) {
|
|
const originalEdge = satelliteEdgeToOriginalEdge.get(edge);
|
|
if (this.props.onEdgeMouseDown) {
|
|
this.props.onEdgeMouseDown(event, originalEdge || edge);
|
|
}
|
|
}
|
|
|
|
renderNode(nodeRef, dgo, key, selected, hovered) {
|
|
if (dgo.type !== 'satellite') {
|
|
return this.props.renderNode(nodeRef, dgo, key, selected, hovered);
|
|
}
|
|
|
|
return r(Satellite);
|
|
}
|
|
|
|
renderNodeText(dgo, ...rest) {
|
|
if (dgo.type !== 'satellite') {
|
|
return this.props.renderNodeText(dgo, ...rest);
|
|
}
|
|
|
|
return r(React.Fragment);
|
|
}
|
|
|
|
renderEdge(...args) {
|
|
return this.props.renderEdge(...args);
|
|
}
|
|
|
|
renderEdgeText(...args) {
|
|
return this.props.renderEdgeText(...args);
|
|
}
|
|
|
|
afterRenderEdge(id, element, edge, edgeContainer) {
|
|
const originalEdge = satelliteEdgeToOriginalEdge.get(edge);
|
|
this.props.afterRenderEdge(id, element, originalEdge || edge, edgeContainer);
|
|
}
|
|
|
|
render() {
|
|
const {
|
|
satelliteEdges: edges,
|
|
satelliteNodes: nodes,
|
|
|
|
selected,
|
|
moved,
|
|
} = this.state;
|
|
|
|
return r(GraphViewBase, {
|
|
...this.props,
|
|
|
|
selected,
|
|
moved,
|
|
|
|
ref: this.graphViewRef,
|
|
|
|
nodes,
|
|
edges,
|
|
|
|
onSwapEdge: this.onSwapEdge,
|
|
onNodeMove: this.onNodeMove,
|
|
|
|
onSelectEdge: this.onSelectEdge,
|
|
|
|
onCreateEdge: this.onCreateEdge,
|
|
|
|
onEdgeMouseDown: this.onEdgeMouseDown,
|
|
|
|
renderNode: this.renderNode,
|
|
renderNodeText: this.renderNodeText,
|
|
|
|
renderEdge: this.renderEdge,
|
|
renderEdgeText: this.renderEdgeText,
|
|
|
|
afterRenderEdge: this.props.afterRenderEdge && this.afterRenderEdge,
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = { SatellitesGraphView };
|