UNPKG

react-flow-diagram

Version:
245 lines (218 loc) 10.2 kB
var _templateObject = _taggedTemplateLiteralLoose(['\n position: absolute;\n top: 0;\n left: 0;\n text-align: center;\n display: flex;\n flex-flow: row nowrap;\n align-items: center;\n user-select: none;\n'], ['\n position: absolute;\n top: 0;\n left: 0;\n text-align: center;\n display: flex;\n flex-flow: row nowrap;\n align-items: center;\n user-select: none;\n']); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _taggedTemplateLiteralLoose(strings, raw) { strings.raw = raw; return strings; } import React from 'react'; import style from 'styled-components'; import { connect } from 'react-redux'; import { move, linkTo, addLinkedEntity, removeEntity, selectEntity } from './reducer'; import { connecting, anchorEntity } from '../canvas/reducer'; import defaultEntity from './defaultEntity'; import ContextMenu from '../contextMenu/component'; // eslint-disable-next-line import/first /* * Presentational * ==================================== */ var contextMenuActions = function contextMenuActions(props) { var remove = { action: function action() { return props.removeEntity(props.model.id); }, iconVariety: 'delete', label: 'Remove' }; var connectAction = { action: function action() { return props.connecting({ currently: true, from: props.model.id }); }, iconVariety: 'arrow', label: 'Connect' }; var addEntities = props.entityTypeNames.map(function (entityTypeName) { return { action: function action() { return props.addLinkedEntity({ entity: props.defaultEntity({ entityType: entityTypeName }), id: props.model.id }); }, iconVariety: entityTypeName, label: 'Add ' + entityTypeName }; }); return [remove].concat(addEntities, [connectAction]); }; var EntityStyle = style.div(_templateObject); var Entity = function Entity(props) { return React.createElement( EntityStyle, { style: { transform: 'translate(' + props.model.x + 'px, ' + props.model.y + 'px)', zIndex: props.isAnchored || props.isSelected ? '100' : '10', cursor: props.toBeConnected ? 'pointer' : 'move' } }, React.createElement( 'div', { onMouseDown: props.onMouseDown, onMouseLeave: props.onMouseLeave, onMouseUp: props.onMouseUp, role: 'presentation' }, props.children ), props.isSelected && React.createElement(ContextMenu, { actions: contextMenuActions(props) }) ); }; /* * Container * ==================================== */ // TODO: These signatures are probably wrong. The original action does return // an EntityAction, but after we connect we're dispatching the action, so this // signature is probably incorrect. Gotta research what's the proper signature // after connecting the component. // // NOTE: I tried wrapping them in Dispatch<> (e.g. Dispatch<Id => EntityAction>) // which seemed correct, but doing so eliminates type checking in practice // (i.e. I could name a method whatever or pass another type to an action and // the checked wouldn't complain). I need to research this. I also haven't // found any discussion about this or code examples. I'm either doing something // fundamentally wrong or being innovative :P // // Also notice I used both i.e. and e.g. in the same paragraph. Just bask in // that fact. Cherish it. Savour it. Ok, now keep reading code :D // var EntityContainerHOC = function EntityContainerHOC(WrappedComponent) { return function (_React$PureComponent) { _inherits(_class2, _React$PureComponent); function _class2() { var _temp, _this, _ret; _classCallCheck(this, _class2); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, _React$PureComponent.call.apply(_React$PureComponent, [this].concat(args))), _this), _this.state = { onMouseUpWouldBeClick: true }, _this.entityTypeNames = Object.keys(_this.props.entityTypes), _this.onMouseDown = function (ev) { ev.stopPropagation(); if (_this.props.canvas.connecting.currently) { // In this case we want to select an entity to be connected to a // previously selected entity to connect from _this.props.linkTo(_this.props.model.id); } else { // Most common behavior is that when you click on an entity, your // intention is to start dragging the entity // // The new thing is that now the anchor info is on metaenttiy, cursor // position is on canvas, and what I actually need to do is set that // this entity is starting to be selected for movement, passing the id // of the entitiy. This will "ripple" down to canvas and metaentity. // meow. // so I have to create a new action for this... _this.props.anchorEntity({ id: _this.props.model.id, isAnchored: true }); } }, _this.onMouseLeave = function (ev) { // If this magic below proves to be a hinderance, remove it. // Now that I'm tracking mouse movement on canvas, Entity mouseMove // jailbreak is not such a problem. if (_this.props.meta.isAnchored) { // If the entity is still being dragged while leaving (mouse movement // faster than state refresh on DOM) then (discussing only X // coordinate, calculations the same with Y): // // This is where the anchor point was (in relation to diagram coordinates): // this.state.anchorX + this.props.model.x // // This is where the mouse was (in relation to diagram coordinates) // this.props.canvas.cursor.x // // This is the difference: // (this.props.canvas.cursor.x) - (this.state.anchorX + this.props.model.x) // (this.props.canvas.cursor.x) - (this.state.anchorX + this.props.model.x) // // The above number signifies by how much has the mouse left the original // anchor point. If we add this difference to where we would have // calculated our original location, we're left with: // (this.props.canvas.cursor.x - this.state.anchorX) + // ((this.props.canvas.cursor.x) - (this.state.anchorX + this.props.model.x)) // // Which simplified leaves us with: // 2 * (this.props.canvas.cursor.x - this.state.anchorX) - this.props.model.x // _this.props.move({ x: 2 * (_this.props.canvas.cursor.x - _this.props.meta.anchor.x) - _this.props.model.x, y: 2 * (_this.props.canvas.cursor.y - _this.props.meta.anchor.y) - _this.props.model.y, id: _this.props.model.id }); } }, _this.onMouseUp = function (ev) { ev.stopPropagation(); if (!_this.state.onMouseUpWouldBeClick) { // Behaves as if it was spawned with a mouse drag // meaning that when you release the mouse button, // the element will de-anchor _this.props.anchorEntity({ id: '', isAnchored: false }); _this.props.selectEntity(_this.props.model.id); } // else it behaves as if it was spawned with a mouse click // meaning it needs another click to de-anchor from mouse }, _temp), _possibleConstructorReturn(_this, _ret); } _class2.prototype.componentDidMount = function componentDidMount() { var _this2 = this; var wouldBeClick = function wouldBeClick() { return _this2.setState({ onMouseUpWouldBeClick: false }); }; if (this.props.meta.isAnchored) { setTimeout(wouldBeClick, 16 * 12); } else { wouldBeClick(); } }; _class2.prototype.render = function render() { return React.createElement( Entity, { model: this.props.model, entityTypeNames: this.entityTypeNames, isAnchored: this.props.meta.isAnchored, isSelected: this.props.meta.isSelected, toBeConnected: this.props.canvas.connecting.currently, addLinkedEntity: this.props.addLinkedEntity, removeEntity: this.props.removeEntity, connecting: this.props.connecting, defaultEntity: this.props.defaultEntity, onMouseDown: this.onMouseDown, onMouseLeave: this.onMouseLeave, onMouseUp: this.onMouseUp }, React.createElement(WrappedComponent, { model: this.props.model, meta: this.props.meta }) ); }; return _class2; }(React.PureComponent); }; var mapStateToProps = function mapStateToProps(state, ownProps) { return { canvas: state.canvas, meta: state.metaEntity.find(function (metaEntity) { return metaEntity.id === ownProps.model.id; }), entityTypes: state.config.entityTypes, defaultEntity: defaultEntity(state) }; }; export default (function (WrappedComponent) { return connect(mapStateToProps, { move: move, linkTo: linkTo, addLinkedEntity: addLinkedEntity, removeEntity: removeEntity, selectEntity: selectEntity, connecting: connecting, anchorEntity: anchorEntity })(EntityContainerHOC(WrappedComponent)); });