UNPKG

@logicflow/extension

Version:
359 lines (358 loc) 19.4 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Label = void 0; var core_1 = require("@logicflow/core"); var lodash_es_1 = require("lodash-es"); var LabelOverlay_1 = __importDefault(require("./LabelOverlay")); var utils_1 = require("./utils"); var Label = /** @class */ (function () { function Label(_a) { var lf = _a.lf, options = _a.options; var _b, _c, _d; this.labelInitPositionMap = new Map(); this.lf = lf; // DONE: 根据 options 初始化一些插件配置,比如是否支持多个 label 等,生效在所有 label 中 this.options = options !== null && options !== void 0 ? options : {}; this.textOverflowMode = (_b = options.textOverflowMode) !== null && _b !== void 0 ? _b : 'default'; this.isMultiple = (_c = options.isMultiple) !== null && _c !== void 0 ? _c : true; this.labelWidth = options.labelWidth; this.maxCount = (_d = options.maxCount) !== null && _d !== void 0 ? _d : Infinity; // DONE: 1. 启用插件时,将当前画布的 textMode 更新为 TextMode.LABEL。 // 如果将其又重新设置为 TextModel.TEXT,则需要 disable 掉 Label 工具,enable TextEditTool lf.graphModel.editConfigModel.updateTextMode(core_1.TextMode.LABEL); // TODO: 2. 做一些插件需要的事件监听 this.addEventListeners(); // TODO: 3. 自定义快捷键,比如 delete,选中 label 时,移除 label this.rewriteShortcut(); // 插件中注册 LabelOverlay 工具,用于 label 的编辑 lf.tool.registerTool(LabelOverlay_1.default.toolName, LabelOverlay_1.default); // LabelOverlay 和 TextEditTool 互斥,所以将它 disable 掉 lf.tool.disableTool('text-edit-tool'); } /** * 格式化元素的 Label 配置,后续初始化 Label 用统一的数据格式 * 主要是将 _label 类型 string | LabelConfig | LabelConfig[] 统一转换为 LabelConfig[] * @param graphModel 当前图的 model * @param element 当前元素 model * @return LabelConfig[] */ Label.prototype.formatConfig = function (graphModel, element) { var _a = graphModel.editConfigModel, nodeTextEdit = _a.nodeTextEdit, edgeTextEdit = _a.edgeTextEdit, nodeTextDraggable = _a.nodeTextDraggable, edgeTextDraggable = _a.edgeTextDraggable; var _b = this, textOverflowMode = _b.textOverflowMode, isMultiple = _b.isMultiple, maxCount = _b.maxCount, labelWidth = _b.labelWidth; var text = element.text, zIndex = element.zIndex, _c = element.properties, _label = _c._label, _d = _c._labelOption, _labelOption = _d === void 0 ? {} : _d; // 当前元素的 Label 相关配置 var curLabelConfig = _label; var _e = _labelOption, curIsMultiple = _e.isMultiple, curMaxCount = _e.maxCount; // REMIND: 对 3 种可能得数据类型进行处理 var formatConfig = []; // 保存格式化后的 LabelConfig if ((0, lodash_es_1.isArray)(curLabelConfig)) { // 1. 数组的话就是 LabelConfig[] 类型 // 判断是否开启 isMultiple, 如果开启了,判断是否超过最大数量。超出就截取 var size = curMaxCount !== null && curMaxCount !== void 0 ? curMaxCount : maxCount; // 优先级,当设置 multiple 时,元素的 maxCount 优先级高于插件的 maxCount if (isMultiple && curIsMultiple) { if (curLabelConfig.length > size) { formatConfig = curLabelConfig.slice(0, size); } else { formatConfig = curLabelConfig; } } else { formatConfig = [curLabelConfig[0]]; } } else if ((0, lodash_es_1.isObject)(curLabelConfig)) { // 2. 对象的话就是 LabelConfig 类型 formatConfig = [curLabelConfig]; } else if (typeof curLabelConfig === 'string' || !curLabelConfig) { // 3. 字符串或者为空的话就是 string 类型,基于 text 的数据合成 LabelConfig 信息(主要复用 text 的 x,y 信息) var config = __assign(__assign({}, text), { content: curLabelConfig || (text === null || text === void 0 ? void 0 : text.value), draggable: element.BaseType === 'edge' ? edgeTextDraggable : nodeTextDraggable }); formatConfig = config.value ? [config] : []; } // TODO: 针对 Edge,在 edge 更新时 重新计算 Label 的位置 if (element.BaseType === 'edge') { // 判断当前 label,是否在 edge 的路径上,如果不在,就重新计算位置 formatConfig = (0, lodash_es_1.map)(formatConfig, function (config) { return config; }); } // DONE: 再根据一些全局配置,比如是否支持垂直显示等,对 LabelConfig 进行二次处理 // 优先级:全局配置 > 元素配置。比如全局设置 isMultiple 为 true 时,才可以使用 局部的 isMultiple 设置才生效 // 当全局 isMultiple 为 false 时,局部的 isMultiple 不生效 return (0, lodash_es_1.map)(formatConfig, function (config) { if (!config.id) { config.id = (0, core_1.createUuid)(); } var value = config.value, content = config.content, vertical = config.vertical, editable = config.editable, draggable = config.draggable, labelTextOverflowMode = config.textOverflowMode; var textEdit = element.BaseType === 'node' ? nodeTextEdit : edgeTextEdit; var textDraggable = element.BaseType === 'node' ? nodeTextDraggable : edgeTextDraggable; return __assign(__assign({}, config), { zIndex: zIndex, labelWidth: labelWidth, content: content !== null && content !== void 0 ? content : value, vertical: vertical !== null && vertical !== void 0 ? vertical : false, editable: Boolean(textEdit) && editable, draggable: Boolean(textDraggable) && draggable, textOverflowMode: labelTextOverflowMode !== null && labelTextOverflowMode !== void 0 ? labelTextOverflowMode : textOverflowMode }); }); }; /** * 根据初始化的数据,格式化 Label 的数据格式后,统一更新到元素的 properties._label 中,保证后续的渲染以这个数据格式进行 * @param graphModel */ Label.prototype.setupLabels = function (graphModel) { var _this = this; // const elements = [...graphModel.nodes, ...graphModel.edges] var elements = graphModel.sortElements; // TODO: 1. 筛选出当前画布上,textMode 为 TextMode.LABEL 的元素(在支持元素级别的 textMode 时,需要做这个筛选) // REMIND: 本期先只支持全局配置,所以判断全局的 textMode 即可 (0, lodash_es_1.forEach)(elements, function (element) { // DONE: 2. 在此处做数据的转换 // 输入:NodeConfig.properties._label: string | LabelConfig | LabelConfig[] // 输出:NodeData.properties._label: LabelData | LabelData[] // 是否需要根据 isMultiple 控制是否返回数组或对象 or 直接全部返回数组 ❓❓❓ -> 目前直接全部返回数组 _this.rewriteInnerMethods(element); var formatLabelConfig = _this.formatConfig(graphModel, element); // FIX: BUG Here: 格式化后的 labelConfig 没有同步到 element 上,导致每次重新渲染时,都会重新格式化,且重新生成 id // 但如果在此处通过 setProperty 更新元素的 _label 时,又会导致死循环 element.setProperty('_label', formatLabelConfig); }); }; /** * 给元素添加一个 label * @param element * @param position */ Label.prototype.addLabel = function (element, position) { var _a, _b, _c; var _d = this, isMultiple = _d.isMultiple, maxCount = _d.maxCount; var _e = element.properties, _label = _e._label, _labelOption = _e._labelOption; var curLabelConfig = (_a = _label) !== null && _a !== void 0 ? _a : []; var curLabelOption = (_b = _labelOption) !== null && _b !== void 0 ? _b : {}; var len = curLabelConfig.length; var newLabel = { id: (0, core_1.createUuid)(), x: position.x, y: position.y, content: "Label".concat(len + 1), value: "Label".concat(len + 1), style: {}, draggable: true, editable: true, vertical: false, }; // 全局的isMultiple为false,或全局isMultiple为true但局部isMultiple指明是false,或当前label长度已经达到上线时,不允许添加多个 label if (!isMultiple || (isMultiple && curLabelOption.isMultiple === false) || len >= ((_c = curLabelOption === null || curLabelOption === void 0 ? void 0 : curLabelOption.maxCount) !== null && _c !== void 0 ? _c : maxCount)) { return; } curLabelConfig.push(newLabel); element.setProperty('_label', curLabelConfig); }; /** * 移除元素的某个 label * @private */ // private removeLabel() {} Label.prototype.addEventListeners = function () { var _this = this; var graphModel = this.lf.graphModel; var eventCenter = graphModel.eventCenter, editConfigModel = graphModel.editConfigModel; eventCenter.on('graph:rendered', function (_a) { var graphModel = _a.graphModel; _this.setupLabels(graphModel); }); // 监听元素双击事件,给元素增加 Label eventCenter.on('node:dbclick,edge:dbclick', function (_a) { var e = _a.e, data = _a.data; // DONE: 增加 label 的数据信息到 element model var target = graphModel.getElement(data.id); // DONE: 将 clientX 和 clientY 转换为画布坐标 var _b = graphModel.getPointByClient({ x: e.clientX, y: e.clientY, }).canvasOverlayPosition, x1 = _b.x, y1 = _b.y; var point = { x: x1, y: y1, }; if (target && editConfigModel.textMode === core_1.TextMode.LABEL) { _this.addLabel(target, point); } }); // 监听 node:resize 事件,在 resize 时,重新计算 label 的位置信息 eventCenter.on('node:resize', function (_a) { var _b, _c; var preData = _a.preData, data = _a.data, model = _a.model; var _d = (_b = preData.properties) !== null && _b !== void 0 ? _b : {}, preWidth = _d.width, preHeight = _d.height, _e = _d._label, _label = _e === void 0 ? [] : _e; var _f = (_c = data.properties) !== null && _c !== void 0 ? _c : {}, curWidth = _f.width, curHeight = _f.height; if (preWidth && preHeight && curWidth && curHeight) { var origin_1 = { x: preData.x, y: preData.y, width: preWidth, height: preHeight, }; var scaled_1 = { x: data.x, y: data.y, width: curWidth, height: curHeight, }; var newLabelConfig = (0, lodash_es_1.map)(_label, function (label) { var x = label.x, y = label.y; var newPoint = (0, utils_1.calcPointAfterResize)(origin_1, scaled_1, { x: x, y: y }); return __assign(__assign({}, label), newPoint); }); model.setProperty('_label', newLabelConfig); } }); // 监听 node:rotate 事件,在 rotate 时,重新计算 Label 的位置信息 eventCenter.on('node:rotate', function (_a) { var model = _a.model; var x = model.x, y = model.y, rotate = model.rotate, _b = model.properties._label, _label = _b === void 0 ? [] : _b; var center = { x: x, y: y }; var newLabelConfig = (0, lodash_es_1.map)(_label, function (label) { if (!label.id) return label; var point = { x: label.x, y: label.y }; if (_this.labelInitPositionMap.has(label.id)) { point = _this.labelInitPositionMap.get(label.id); } else { _this.labelInitPositionMap.set(label.id, point); } // 弧度转角度 var theta = rotate * (180 / Math.PI); if (theta < 0) theta += 360; var radian = theta * (Math.PI / 180); var newPoint = (0, utils_1.rotatePointAroundCenter)(point, center, radian); return __assign(__assign(__assign({}, label), newPoint), { rotate: theta }); }); model.setProperty('_label', newLabelConfig); }); // 监听元素新增事件,元素label格式化 eventCenter.on('node:dnd-add,node:add,edge:add', function (_a) { var data = _a.data; var element = graphModel.getElement(data.id); if (element) { _this.rewriteInnerMethods(element); // 检查_label是否已经是格式化后的数组格式,如果是则跳过格式化 // 这样可以避免在复制粘贴时重复处理已经格式化好的label数据 var currentLabel = element.properties._label; if (!(0, lodash_es_1.isArray)(currentLabel) || currentLabel.length === 0 || !currentLabel[0].id) { var formatedLabel = _this.formatConfig(graphModel, data); element.setProperty('_label', formatedLabel); } } }); }; /** * 重写元素的一些方法,以支持 Label 的拖拽、编辑等 * @param element */ Label.prototype.rewriteInnerMethods = function (element) { // 重写 edgeModel/nodeModel moveText 方法,在 move text 时,以相同的逻辑移动 label element.moveText = function (deltaX, deltaY) { if (!element.text) return; var _a = element.text, x = _a.x, y = _a.y, value = _a.value, draggable = _a.draggable, editable = _a.editable; element.text = { value: value, editable: editable, draggable: draggable, x: x + deltaX, y: y + deltaY, }; var properties = (0, lodash_es_1.cloneDeep)(element.getProperties()); // 重新计算新的 label 位置信息 if ((0, lodash_es_1.isArray)(properties._label)) { var nextLabel = (0, lodash_es_1.map)(properties._label, function (label) { return __assign(__assign({}, label), { x: label.x + deltaX, y: label.y + deltaY }); }); element === null || element === void 0 ? void 0 : element.setProperty('_label', nextLabel); } }; // TODO: others methods ??? }; Label.prototype.rewriteShortcut = function () { var _this = this; var _a = this.lf, keyboard = _a.keyboard, graphModel = _a.graphModel; var keyboardOptions = keyboard.options.keyboard; keyboard.off(['backspace']); keyboard.on(['backspace'], function () { if (!(keyboardOptions === null || keyboardOptions === void 0 ? void 0 : keyboardOptions.enabled)) return true; if (graphModel.textEditElement) return true; var elements = graphModel.getSelectElements(true); _this.lf.clearSelectElements(); var editConfigModel = _this.lf.graphModel.editConfigModel; elements.edges.forEach(function (edge) { var properties = edge.properties; if (properties && !(0, lodash_es_1.isEmpty)(properties._label) && editConfigModel.textMode === core_1.TextMode.LABEL) { var newLabelList = properties._label.filter(function (label) { return !label.isSelected; }); // 如果两个labelList长度不一致,说明有选中的元素,此时backspace做的动作是删除label if (!(0, lodash_es_1.isEqual)(newLabelList.length, properties._label.length)) { var edgeModel = graphModel.getEdgeModelById(edge.id); edgeModel === null || edgeModel === void 0 ? void 0 : edgeModel.setProperty('_label', newLabelList); return; } } edge.id && _this.lf.deleteEdge(edge.id); }); elements.nodes.forEach(function (node) { var properties = node.properties; if (properties && !(0, lodash_es_1.isEmpty)(properties._label) && editConfigModel.textMode === core_1.TextMode.LABEL) { var newLabelList = properties._label.filter(function (label) { return !label.isSelected; }); if (!(0, lodash_es_1.isEqual)(newLabelList.length, properties._label.length)) { var nodeModel = graphModel.getNodeModelById(node.id); nodeModel === null || nodeModel === void 0 ? void 0 : nodeModel.setProperty('_label', newLabelList); return; } } node.id && _this.lf.deleteNode(node.id); }); return false; }); }; /** * 更新当前渲染使用的 Text or Label 模式 */ Label.prototype.updateTextMode = function (textMode) { var editConfigModel = this.lf.graphModel.editConfigModel; if (textMode === editConfigModel.textMode) return; editConfigModel.updateTextMode(textMode); if (textMode === core_1.TextMode.LABEL) { this.lf.tool.enableTool(LabelOverlay_1.default.toolName); this.lf.tool.disableTool('text-edit-tool'); } else if (textMode === core_1.TextMode.TEXT) { this.lf.tool.enableTool('text-edit-tool'); this.lf.tool.disableTool(LabelOverlay_1.default.toolName); } }; Label.prototype.render = function () { }; Label.prototype.destroy = function () { }; Label.pluginName = 'label'; return Label; }()); exports.Label = Label; exports.default = Label;