UNPKG

flow-graph-designer

Version:
833 lines (775 loc) 30.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 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 _propTypes = require("prop-types"); var _propTypes2 = _interopRequireDefault(_propTypes); var _react = require("react"); var _react2 = _interopRequireDefault(_react); var _reactDom = require("react-dom"); var _constants = require("../constants"); var _utils = require("../utils"); var _recycle = require("./recycle"); var _recycle2 = _interopRequireDefault(_recycle); var _workspace = require("./workspace.less"); var _workspace2 = _interopRequireDefault(_workspace); var _stateshot = require("stateshot"); 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; } var UNEXPAND_STATE_ID_PREFIX = "unexpand-state-id-"; var Workspace = function (_React$Component) { _inherits(Workspace, _React$Component); function Workspace(props) { _classCallCheck(this, Workspace); var _this = _possibleConstructorReturn(this, (Workspace.__proto__ || Object.getPrototypeOf(Workspace)).call(this, props)); _this.state = {}; _this.handleDragOver = _this.handleDragOver.bind(_this); _this.handleDrop = _this.handleDrop.bind(_this); _this.renderLine = _this.renderLine.bind(_this); _this.renderNode = _this.renderNode.bind(_this); _this.handlePaste = _this.handlePaste.bind(_this); _this.handleCopy = _this.handleCopy.bind(_this); _this.handleFocus = _this.handleFocus.bind(_this); _this.handleBlur = _this.handleBlur.bind(_this); _this.handleKeyUp = _this.handleKeyUp.bind(_this); _this.handleClearState = _this.handleClearState.bind(_this); _this.handleUpdateData = _this.handleUpdateData.bind(_this); _this.history = new _stateshot.History(); return _this; } _createClass(Workspace, [{ key: "handleUpdateData", value: function handleUpdateData(data) { this.setState({ data: data }); this.history.pushSync(data); } }, { key: "handleKeyUp", value: function handleKeyUp(event) { var _this2 = this; if (event.key === "Delete") { var _state = this.state, data = _state.data, currentId = _state.currentId; if (!currentId) { return; } var sourceParentNode = (0, _utils.getParentNodeById)(data, currentId); var sourceIndex = sourceParentNode.children.findIndex(function (x) { return x.id === currentId; }); var _getNewFlowByDel = (0, _utils.getNewFlowByDel)({ config: data, sourceId: currentId }), flow = _getNewFlowByDel.flow, nodes = _getNewFlowByDel.nodes; if (this.props.onChange && typeof this.props.onChange === "function") { this.props.onChange({ data: flow, detail: { action: "del", position: { id: sourceParentNode.id, index: sourceIndex }, nodes: nodes } }); } this.handleClearState(); this.setState({ currentId: null }); this.handleUpdateData(flow); return; } if (event.ctrlKey) { switch (event.key) { case "z": (function () { var data = _this2.history.undo().get(); _this2.setState({ data: data }); if (_this2.props.onChange && typeof _this2.props.onChange === "function") { _this2.props.onChange({ data: data, detail: { action: "undo" } }); } })(); break; case "y": (function () { var data = _this2.history.redo().get(); _this2.setState({ data: data }); if (_this2.props.onChange && typeof _this2.props.onChange === "function") { _this2.props.onChange({ data: data, detail: { action: "undo" } }); } })(); break; } } } }, { key: "handleCopy", value: function handleCopy(event) { var _state2 = this.state, data = _state2.data, currentId = _state2.currentId; var node = (0, _utils.getNodeById)(data, currentId); var text = JSON.stringify(node); if (text) { event.clipboardData.setData("text/plain", "flow-graph-designer:" + text); } event.preventDefault(); } }, { key: "handlePaste", value: function handlePaste(event) { var clipboardData = event.clipboardData; if (!(clipboardData && clipboardData.items)) { return; } var text = clipboardData.getData("text/plain"); if (text.startsWith("flow-graph-designer:")) { var _state3 = this.state, prevData = _state3.data, currentId = _state3.currentId; var nodeStr = text.substring("flow-graph-designer:".length); var node = function () { try { var value = JSON.parse(nodeStr); return value; } catch (e) { return null; } }(); if (!node) { return; } var _ref = function () { var unPasteResult = { containerId: null, containerIndex: null }; if (!currentId && node.type !== "case") { // 如果没有当前节点,粘贴到根节点内部最后面的位置 return { containerId: "root", containerIndex: prevData.children.length }; } var currentNode = (0, _utils.getNodeById)(prevData, currentId); // 粘贴到内的情况 if (currentNode.type === "loop" && node.type !== "case" || currentNode.type === "case" && node.type !== "case" || currentNode.type === "switch" && node.type === "case") { return { containerId: currentId, containerIndex: currentNode.children.length }; } if (node.type === "case" && currentNode.type !== "case") { return unPasteResult; } var containerNode = (0, _utils.getParentNodeById)(prevData, currentId); var containerIndex = 1 + containerNode.children.findIndex(function (x) { return x.id === currentId; }); return { containerId: containerNode.id, containerIndex: containerIndex }; }(), containerId = _ref.containerId, containerIndex = _ref.containerIndex; if (!containerId) { return; } var _getNewFlowByPaste = (0, _utils.getNewFlowByPaste)({ config: prevData, sourceNode: node, containerId: containerId, containerIndex: containerIndex }), data = _getNewFlowByPaste.data, copyDetail = _getNewFlowByPaste.copyDetail; var sourceParentNode = (0, _utils.getParentNodeById)(data, node.id); var sourceIndex = sourceParentNode.children.findIndex(function (x) { return x.id === node.id; }); if (this.props.onChange && typeof this.props.onChange === "function") { this.props.onChange({ data: data, detail: { action: "paste", position: { id: sourceParentNode.id, index: sourceIndex }, position2: { id: containerId, index: containerIndex }, nodes: copyDetail.map(function (x) { return x.from; }), nodes2: copyDetail.map(function (x) { return x.to; }) } }); } this.handleClearState(); this.handleUpdateData(data); return; } } }, { key: "handleFocus", value: function handleFocus() { document.addEventListener("copy", this.handleCopy); document.addEventListener("paste", this.handlePaste); document.addEventListener("keyup", this.handleKeyUp); } }, { key: "handleBlur", value: function handleBlur() { document.removeEventListener("copy", this.handleCopy); document.removeEventListener("paste", this.handlePaste); document.removeEventListener("keyup", this.handleKeyUp); } }, { key: "handleClick", value: function handleClick(id) { var _this3 = this; return function (e) { e.stopPropagation(); _this3.setState({ currentId: id }); if (_this3.props.onClick && typeof _this3.props.onClick === "function") { _this3.props.onClick(id); } }; } }, { key: "handleClickExpandIcon", value: function handleClickExpandIcon(id) { var _this4 = this; return function (e) { e.stopPropagation(); var stateId = "" + UNEXPAND_STATE_ID_PREFIX + id; _this4.setState(_defineProperty({}, stateId, !_this4.state[stateId])); }; } }, { key: "handleDragStart", value: function handleDragStart(id) { var _this5 = this; return function (e) { e.dataTransfer.setData("dragId", id); e.dataTransfer.setDragImage(e.target, 10, 10); e.stopPropagation(); var dragNode = (0, _utils.getNodeById)(_this5.state.data, id); var isDragCase = dragNode.type === "case"; _this5.setState({ dragId: id, isDragCase: isDragCase }); }; } }, { key: "handleDragOver", value: function handleDragOver(_ref2) { var _this6 = this; var containerId = _ref2.containerId, containerIndex = _ref2.containerIndex; return function (e) { e.preventDefault(); e.stopPropagation(); if (_this6.state.containerId !== containerId || _this6.state.containerInedx !== containerIndex) { _this6.setState({ containerId: containerId, containerIndex: containerIndex }); } }; } }, { key: "handleDragLeave", value: function handleDragLeave(_ref3) { var _this7 = this; var containerId = _ref3.containerId, containerIndex = _ref3.containerIndex; return function (e) { e.stopPropagation(); if (_this7.state.containerId === containerId && _this7.state.containerIndex === containerIndex) { _this7.setState({ containerId: null, containerIndex: null }); } }; } }, { key: "handleClearState", value: function handleClearState() { this.setState({ containerId: null, containerIndex: null, dragId: null, isDragCase: false }); } }, { key: "handleDrop", value: function handleDrop(e) { e.stopPropagation(); var sourceId = e.dataTransfer.getData("dragId"); var nodeName = e.dataTransfer.getData("nodeName"); var method = e.dataTransfer.getData("method"); var _state4 = this.state, containerId = _state4.containerId, containerIndex = _state4.containerIndex; // 如果是从图标栏拖拽节点到垃圾箱,取消拖拽动作并返回 if (method === "new" && containerId === "recycle") { this.handleClearState(); return; } // 创建新的节点 if (method === "new") { var type = e.dataTransfer.getData("type"); var _action = e.dataTransfer.getData("action"); var node = (0, _utils.getNewNode)(type, _action, nodeName, (0, _utils.getNewIdFunc)(this.state.data)); // 在流程配置中增加新建的节点 var _getNewFlowByAdd = (0, _utils.getNewFlowByAdd)({ config: this.state.data, node: node, containerId: containerId, containerIndex: containerIndex }), flow = _getNewFlowByAdd.flow, nodes = _getNewFlowByAdd.nodes; if (this.props.onChange && typeof this.props.onChange === "function") { this.props.onChange({ data: flow, detail: { action: "add", position: { id: containerId, index: containerIndex }, nodes: nodes } }); } this.handleClearState(); this.handleUpdateData(flow); return; } var prevData = this.state.data; var sourceParentNode = (0, _utils.getParentNodeById)(prevData, sourceId); var sourceIndex = sourceParentNode.children.findIndex(function (x) { return x.id === sourceId; }); if (containerId === "recycle") { var _getNewFlowByDel2 = (0, _utils.getNewFlowByDel)({ config: this.state.data, sourceId: sourceId }), _flow = _getNewFlowByDel2.flow, _nodes = _getNewFlowByDel2.nodes; if (this.props.onChange && typeof this.props.onChange === "function") { this.props.onChange({ data: _flow, detail: { action: "del", position: { id: sourceParentNode.id, index: sourceIndex }, nodes: _nodes } }); } this.handleClearState(); this.setState({ currentId: null }); this.handleUpdateData(_flow); return; } var action = e.nativeEvent.ctrlKey || e.nativeEvent.metaKey ? "copy" : "move"; if (action === "move") { // 如果新的位置和当前位置没有变化,取消行动 if (sourceParentNode.id === containerId && (containerIndex === sourceIndex || containerIndex === sourceIndex + 1)) { this.handleClearState(); return; } } if (action === "copy") { var _getNewFlowByCopy = (0, _utils.getNewFlowByCopy)({ config: prevData, sourceId: sourceId, containerId: containerId, containerIndex: containerIndex }), data = _getNewFlowByCopy.data, copyDetail = _getNewFlowByCopy.copyDetail; if (this.props.onChange && typeof this.props.onChange === "function") { this.props.onChange({ data: data, detail: { action: "copy", position: { id: sourceParentNode.id, index: sourceIndex }, position2: { id: containerId, index: containerIndex }, nodes: copyDetail.map(function (x) { return x.from; }), nodes2: copyDetail.map(function (x) { return x.to; }) } }); } this.handleClearState(); this.handleUpdateData(data); return; } else if (action === "move") { var _getNewFlowByMove = (0, _utils.getNewFlowByMove)({ config: prevData, sourceId: sourceId, containerId: containerId, containerIndex: containerIndex }), _flow2 = _getNewFlowByMove.flow, _nodes2 = _getNewFlowByMove.nodes; if (_flow2 === prevData) { this.handleClearState(); return; } if (this.props.onChange && typeof this.props.onChange === "function") { this.props.onChange({ data: _flow2, detail: { action: "move", position: { id: sourceParentNode.id, index: sourceIndex }, position2: { id: containerId, index: containerIndex }, nodes: _nodes2 } }); } this.handleClearState(); this.handleUpdateData(_flow2); return; } } }, { key: "renderLine", value: function renderLine(_ref4) { var containerId = _ref4.containerId, containerIndex = _ref4.containerIndex, hasArrow = _ref4.hasArrow, style = _ref4.style; var arrowClassName = hasArrow ? "arrow-line" : ""; var dragoverClassName = this.state.containerId === containerId && this.state.containerIndex === containerIndex ? "dragover" : ""; return _react2.default.createElement( "div", { className: "flow-line " + arrowClassName + " " + dragoverClassName, key: "line-" + containerId + ":{containerIndex}", style: style, onDragOver: this.handleDragOver({ containerId: containerId, containerIndex: containerIndex }), onDragLeave: this.handleDragLeave({ containerId: containerId, containerIndex: containerIndex }), onDrop: this.handleDrop }, _react2.default.createElement("div", { className: "line" }), _react2.default.createElement("div", { className: "rect" }), _react2.default.createElement("div", { className: "line" }), hasArrow && _react2.default.createElement("div", { className: "arrow" }) ); } }, { key: "renderHalfHeightLine", value: function renderHalfHeightLine() { return _react2.default.createElement( "div", { className: "flow-line" }, _react2.default.createElement("div", { className: "line" }) ); } }, { key: "renderNode", value: function renderNode(node) { var _this8 = this; var unexpandState = this.state["" + UNEXPAND_STATE_ID_PREFIX + node.id]; if (node.type === "loop") { return _react2.default.createElement( "div", { key: node.id, onClick: this.handleClick(node.id), className: (unexpandState ? "unexpand" : "") + " flow-node loop " + (this.state.dragId === node.id ? "draged" : "") + " " + (this.state.currentId === node.id ? "clicked" : ""), draggable: "true", onDragStart: this.handleDragStart(node.id) }, _react2.default.createElement( "div", { className: "title", onClick: this.handleClickExpandIcon(node.id) }, _react2.default.createElement( "div", null, _react2.default.createElement("img", { className: "expand-icon", src: _constants.image.expand }), _react2.default.createElement("img", { className: "unexpand-icon", src: _constants.image.unexpand }), _react2.default.createElement( "span", { style: { verticalAlign: "top" } }, node.name ), _react2.default.createElement("img", { src: _constants.image.loop, style: { width: "18px", height: "18px", marginLeft: "4px", marginTop: "2px" } }) ) ), this.renderLine({ containerId: node.id, containerIndex: 0 }), _react2.default.createElement( "div", { className: "flow-body" }, node.children.map(function (x, index) { return [_this8.renderNode(x), _this8.renderLine({ containerId: node.id, containerIndex: index + 1, hasArrow: true })]; }) ) ); } else if (node.type === "switch") { return _react2.default.createElement( "div", { key: node.id, className: (unexpandState ? "unexpand" : "") + " flow-node switch " + (this.state.dragId === node.id ? "draged" : "") + " " + (this.state.currentId === node.id ? "clicked" : ""), draggable: "true", onClick: this.handleClick(node.id), onDragStart: this.handleDragStart(node.id) }, _react2.default.createElement( "div", { className: "title", onClick: this.handleClickExpandIcon(node.id) }, _react2.default.createElement( "div", null, _react2.default.createElement("img", { className: "expand-icon", src: _constants.image.expand }), _react2.default.createElement("img", { className: "unexpand-icon", src: _constants.image.unexpand }), _react2.default.createElement( "span", { style: { verticalAlign: "top" } }, node.name ), _react2.default.createElement("img", { src: _constants.image.switch, style: { width: "18px", height: "18px", marginLeft: "4px", marginTop: "3px" } }) ) ), this.renderHalfHeightLine(), _react2.default.createElement( "div", { className: "switch-body" }, node.children.map(function (x, index) { return [_react2.default.createElement( "div", { className: "flow-node case", key: "flow-node-" + x.id }, _react2.default.createElement( "div", { className: "flow-node-line-up" }, _react2.default.createElement("div", { className: "line left-div" }), _react2.default.createElement("div", { className: "line right-div" }) ), _react2.default.createElement( "div", { className: "flow-node-rect-wrap" }, _react2.default.createElement( "div", { className: "flow-node-rect " + (_this8.state.dragId === x.id ? "draged" : "") + " " + (_this8.state.currentId === x.id ? "clicked" : ""), draggable: "true", onClick: _this8.handleClick(x.id), onDragStart: _this8.handleDragStart(x.id), onDragOver: _this8.handleDragOver({ containerId: node.id, containerIndex: index + 1, id: x.id }), onDragLeave: _this8.handleDragLeave({ containerId: node.id, containerIndex: index + 1 }), onDrop: _this8.handleDrop }, _react2.default.createElement( "div", { className: "title" }, _react2.default.createElement( "div", null, x.name ), _react2.default.createElement("img", { src: _constants.image.case, style: { width: "18px", height: "18px" } }) ), _this8.renderLine({ containerId: x.id, containerIndex: 0, style: { flexGrow: x.children && x.children.length ? 0 : 1 } }), _react2.default.createElement( "div", { className: "flow-body" }, x.children && x.children.map(function (y, yIndex) { return [_this8.renderNode(y), _this8.renderLine({ containerId: x.id, containerIndex: yIndex + 1, hasArrow: true })]; }) ) ), _this8.state.containerId === node.id && _this8.state.containerIndex === index + 1 && _this8.state.isDragCase && _this8.state.dragId !== x.id && _react2.default.createElement("div", { className: "insert-rect-space" }) ), _react2.default.createElement( "div", { className: "flow-node-line-down" }, _react2.default.createElement("div", { className: "line left-div" }), _react2.default.createElement("div", { className: "line right-div" }) ) )]; }) ), this.renderHalfHeightLine() ); } var color = (0, _utils.getColorByNode)(node, this.props.template); return _react2.default.createElement( "div", { key: node.id, className: "flow-node normal " + (this.state.dragId === node.id ? "draged" : "") + " " + (this.state.currentId === node.id ? "clicked" : ""), style: { backgroundColor: color, boxShadow: "0 0 0 2px white, 0 0 0 3px " + color }, onClick: this.handleClick(node.id), draggable: "true", onDragStart: this.handleDragStart(node.id) }, node.name || "节点" ); } }, { key: "render", value: function render() { var _this9 = this; if (!this.state._has_getDerivedStateFromProps_func) { console.error("Component flow-graph-designer need react with version 16.4 or above."); return null; } if (!this.state.data) { return null; } var theme = this.props.theme || "theme-1"; return _react2.default.createElement( "div", { className: "flow-designer-workspace-wrap " + _workspace2.default.mainClass, style: this.props.style || {}, ref: function ref(_ref5) { _this9.ref = _ref5; }, tabIndex: 0, onClick: this.handleClearState, onFocus: this.handleFocus, onBlur: this.handleBlur }, window.recycleWrap && (0, _reactDom.createPortal)(_react2.default.createElement(_recycle2.default, { visible: !!this.state.dragId, onDragOver: this.handleDragOver({ containerId: "recycle", containerIndex: null }), onDragLeave: this.handleDragLeave({ containerId: "recycle", containerIndex: null }), onDrop: this.handleDrop }), window.recycleWrap), _react2.default.createElement( "div", { className: "main " + theme }, _react2.default.createElement( "div", { className: "flow-body root" }, _react2.default.createElement( "div", { className: "node-begin" }, _react2.default.createElement("img", { src: _constants.image.begin, style: { width: "32px", height: "32px" } }) ), this.renderLine({ containerId: "root", containerIndex: 0 }), this.state.data.children.map(function (x, index) { var hasArrow = index !== _this9.state.data.children.length - 1; return [_this9.renderNode(x), _this9.renderLine({ containerId: "root", containerIndex: index + 1, hasArrow: hasArrow })]; }), _react2.default.createElement( "div", { className: "node-end" }, _react2.default.createElement("img", { src: _constants.image.end, style: { width: "32px", height: "32px" } }) ) ) ) ); } }], [{ key: "getDerivedStateFromProps", value: function getDerivedStateFromProps(nextProps, prevState) { // render 函数将根据 state 中的 data 进行渲染 // state 中的 data 除了会被 props 更新外,也会被用户的拖拽操作设置 var data = function (_data) { if (!_data) { return { id: "root", children: [] }; } if (!_data.children) { return _extends({}, _data, { children: [] }); } return _data; }(nextProps.data); var dataFromProps = JSON.stringify(data); if (!prevState.dataFromProps || dataFromProps !== prevState.dataFromProps) { // 如果 props 中的 data 发生了变化,修改 state 中的 data return { _has_getDerivedStateFromProps_func: true, dataFromProps: dataFromProps, // 记录每次从 props 获取到的 data 对象 data: data }; } return null; } }]); return Workspace; }(_react2.default.Component); Workspace.propTypes = { data: _propTypes2.default.shape({ id: _propTypes2.default.string, children: _propTypes2.default.array }), template: _propTypes2.default.object, theme: _propTypes2.default.string, style: _propTypes2.default.object, onClick: _propTypes2.default.func, onChange: _propTypes2.default.func }; exports.default = Workspace;