pagraphcontrol/components/graph/satellites-graph.js
2018-12-02 23:51:26 +03:00

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 };