UNPKG

flowpoints

Version:

A developer-friendly library for creating flowcharts and diagrams.

321 lines (269 loc) 14.5 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _Flowpoint = require('./Flowpoint.js'); var _Flowpoint2 = _interopRequireDefault(_Flowpoint); var _Helpers = require('./Helpers.js'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 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; } // Component class var Flowspace = function (_Component) { _inherits(Flowspace, _Component); function Flowspace(props) { _classCallCheck(this, Flowspace); var _this = _possibleConstructorReturn(this, (Flowspace.__proto__ || Object.getPrototypeOf(Flowspace)).call(this, props)); _this.state = {}; // Helper variables _this.didMount = false; // Used to determine when drawing of connections should start // Binding class methods _this.updateFlowspace = _this.updateFlowspace.bind(_this); _this.handleFlowspaceClick = _this.handleFlowspaceClick.bind(_this); return _this; } _createClass(Flowspace, [{ key: 'updateFlowspace', value: function updateFlowspace(key, pos) { this.setState(_defineProperty({}, key, pos)); } }, { key: 'componentDidMount', value: function componentDidMount() { this.didMount = true; } }, { key: 'handleFlowspaceClick', value: function handleFlowspaceClick(e) { // Testing click if this.props.onClick is defined if (this.props.onClick) { // Testing helper variable var isSpaceClick = false; // Testing click target (don't fire if flowpoint or connection was clicked) if (e.target) { var test = ['flowcontainer', 'flowspace', 'flowconnections']; if (test.includes(e.target.className.baseVal)) isSpaceClick = true; if (test.includes(e.target.className)) isSpaceClick = true; } // Potentially triggering user-defined onClick if (isSpaceClick) { this.props.onClick(e); e.stopPropagation(); } } } }, { key: 'render', value: function render() { var _this2 = this; // Colors var theme_colors = (0, _Helpers.getColor)(this.props.theme || 'indigo'); var background_color = (0, _Helpers.getColor)(this.props.background || 'white'); var selected = this.props.selected ? Array.isArray(this.props.selected) ? this.props.selected : [this.props.selected] : []; // Helper variables var connections = []; var paths = []; var gradients = []; var defs = {}; var maxX = 0; var maxY = 0; // Extracting connections and adding updateFlowspace to all children var newKeys = []; var childrenWithProps = _react2.default.Children.map(this.props.children, function (child) { if (child.type === _Flowpoint2.default) { var outputs = child.props.outputs; // Outputs can be defined as array or object if (outputs instanceof Array) { outputs.map(function (out_key) { connections.push({ a: child.key, b: out_key, width: _this2.props.connectionSize || 4, outputLoc: 'auto', inputLoc: 'auto', outputColor: theme_colors.p, inputColor: _this2.props.noFade ? theme_colors.p : theme_colors.a, dash: undefined, onClick: null }); }); } else if (outputs instanceof Object) { Object.keys(outputs).map(function (out_key) { var output = outputs[out_key]; connections.push({ a: child.key, b: out_key, width: output.width || _this2.props.connectionSize || 4, outputLoc: output.output || 'auto', inputLoc: output.input || 'auto', outputColor: output.outputColor || theme_colors.p, inputColor: output.inputColor || (_this2.props.noFade ? theme_colors.p : theme_colors.a), arrowStart: output.arrowStart, arrowEnd: output.arrowEnd, dash: output.dash !== undefined ? output.dash > 0 ? output.dash : undefined : undefined, onClick: output.onClick ? function (e) { output.onClick(child.key, out_key, e); } : _this2.props.onLineClick ? function (e) { _this2.props.onLineClick(child.key, out_key, e); } : null }); }); }; // Adding to newKeys newKeys.push(child.key); // Returning updated child element return _react2.default.cloneElement(child, { updateFlowspace: _this2.updateFlowspace, id: child.key, selected: child.props.selected || selected.includes(child.key), spaceColor: background_color, variant: child.props.variant || _this2.props.variant || 'paper', theme: child.props.theme || _this2.props.theme || 'indigo' }); }; }); // Removing unused positions Object.keys(this.state).map(function (testkey) { if (!newKeys.includes(testkey)) delete _this2.state[testkey]; }); // Drawing of connections will only start after Flowspace have been mounted once. if (this.didMount) { // Getting flowspace size Object.keys(this.state).map(function (key) { var point = _this2.state[key]; maxX = Math.max(maxX, point.x + 4 * point.width); maxY = Math.max(maxY, point.y + 4 * point.height); }); // Looping through connections and adding paths and gradients. var newCons = []; connections.map(function (connection) { // Loop specifics var pa = _this2.state[connection.a]; var pb = _this2.state[connection.b]; var con_key = connection.a + '_' + connection.b; var grad_name = 'grad_' + con_key; // Adding to this cycle's connections newCons.push(con_key); // Continuing only if both pa and pb are defined if (pa && pb) { // Calculate new positions or get old ones var positions = (0, _Helpers.AutoGetLoc)(pa, pb, connection.outputLoc, connection.inputLoc, connection.a, connection.b, _this2.state, _this2.props.avoidCollisions === false ? false : true); // Display arrows - if set then connection-specific overrides flowspace var markerStart = _this2.props.arrowStart; if (connection.arrowStart !== undefined) markerStart = connection.arrowStart; var markerEnd = _this2.props.arrowEnd; if (connection.arrowEnd !== undefined) markerEnd = connection.arrowEnd; // Adding coloured arrow-marker definitions to list (if not already present) if (markerStart && !defs[connection.outputColor]) defs[connection.outputColor] = _react2.default.createElement( 'marker', { id: "arrow" + connection.outputColor, viewBox: '0 0 50 50', markerWidth: '5', markerHeight: '5', refX: '45', refY: '24', orient: 'auto-start-reverse', markerUnits: 'strokeWidth' }, _react2.default.createElement('path', { d: 'M0,0 L50,20 v8 L0,48 L6,24 Z', fill: connection.outputColor, 'stroke-width': '0', opacity: '1' }) ); if (markerEnd && !defs[connection.inputColor]) defs[connection.inputColor] = _react2.default.createElement( 'marker', { id: "arrow" + connection.inputColor, viewBox: '0 0 50 50', markerWidth: '5', markerHeight: '5', refX: '45', refY: '24', orient: 'auto-start-reverse', markerUnits: 'strokeWidth' }, _react2.default.createElement('path', { d: 'M0,0 L50,20 v8 L0,48 L6,24 Z', fill: connection.inputColor, 'stroke-width': '0', opacity: '1' }) ); // Calculating bezier offsets and adding new path to list var d = Math.round(Math.pow(Math.pow(positions.output.x - positions.input.x, 2) + Math.pow(positions.output.y - positions.input.y, 2), 0.5) / 2); var pathkey = 'path_' + connection.a + '_' + connection.b; var isSelectedLine = false; if (_this2.props.selectedLine) { if (_this2.props.selectedLine.a === connection.a && _this2.props.selectedLine.b === connection.b) isSelectedLine = true; } paths.push(_react2.default.createElement('path', { key: pathkey, className: 'flowconnection', style: { transition: 'stroke-width 0.15s ease-in-out', strokeDasharray: connection.dash }, d: 'M' + positions.output.x + ',' + positions.output.y + 'C' + (positions.output.x + (positions.output.offsetX > 0 ? Math.min(d, positions.output.offsetX) : Math.max(-d, positions.output.offsetX))) + ',' + (positions.output.y + (positions.output.offsetY > 0 ? Math.min(d, positions.output.offsetY) : Math.max(-d, positions.output.offsetY))) + ' ' + (positions.input.x + (positions.input.offsetX > 0 ? Math.min(d, positions.input.offsetX) : Math.max(-d, positions.input.offsetX))) + ',' + (positions.input.y + (positions.input.offsetY > 0 ? Math.min(d, positions.input.offsetY) : Math.max(-d, positions.input.offsetY))) + ' ' + (positions.input.x - 0.01) + ',' + (positions.input.y - 0.01), fill: 'none', stroke: 'url(#' + grad_name + ')', strokeWidth: parseInt(connection.width) + (isSelectedLine ? 3 : 0), onClick: connection.onClick, markerStart: markerStart ? 'url(#arrow' + connection.outputColor + ')' : null, markerEnd: markerEnd ? 'url(#arrow' + connection.inputColor + ')' : null })); // Calculating how x and y should affect gradient var p1 = { x: 0, y: 0 }; var p2 = { x: 0, y: 0 }; var maxD = Math.max(Math.abs(positions.output.x - positions.input.x), Math.abs(positions.output.y - positions.input.y)) + 1e-5; if (Math.abs(positions.output.x - positions.input.x) > Math.abs(positions.output.y - positions.input.y)) { if (positions.output.x > positions.input.x) { p1.x = maxD; } else { p2.x = maxD; } } else { if (positions.output.y > positions.input.y) { p1.y = maxD; } else { p2.y = maxD; } } p1.x /= maxD; p1.y /= maxD; p2.x /= maxD; p2.y /= maxD; // Adding gradient to list gradients.push(_react2.default.createElement( 'linearGradient', { key: grad_name, id: grad_name, x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }, _react2.default.createElement('stop', { offset: '0', stopColor: connection.outputColor }), _react2.default.createElement('stop', { offset: '1', stopColor: connection.inputColor }) )); } }); } // Adding scroll (settings for overflow will be overwritten if defined by user) var style = { overflow: 'scroll', backgroundColor: background_color.p }; if (this.props.style) { Object.keys(this.props.style).map(function (propkey) { style[propkey] = _this2.props.style[propkey]; }); }; // Returning finished Flowspace return _react2.default.createElement( 'div', { style: style, onClick: this.handleFlowspaceClick, className: 'flowcontainer' }, _react2.default.createElement( 'div', { style: { width: maxX, height: maxY, position: 'relative', overflow: 'visible' }, className: 'flowspace' }, _react2.default.createElement( 'div', { ref: function ref(_ref) { if (_this2.props.getDiagramRef) _this2.props.getDiagramRef(_ref); }, style: { width: '100%', height: '100%', backgroundColor: background_color.p } }, _react2.default.createElement( 'svg', { style: { width: '100%', height: '100%', position: 'absolute', overflow: 'visible' }, className: 'flowconnections' }, _react2.default.createElement( 'defs', null, Object.values(defs) ), gradients, paths ), childrenWithProps ) ) ); } }]); return Flowspace; }(_react.Component); exports.default = Flowspace;