UNPKG

@logicflow/extension

Version:
712 lines (711 loc) 29.2 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 __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Menu = void 0; var DefaultNodeMenuKey = 'lf:defaultNodeMenu'; var DefaultEdgeMenuKey = 'lf:defaultEdgeMenu'; var DefaultGraphMenuKey = 'lf:defaultGraphMenu'; var DefaultSelectionMenuKey = 'lf:defaultSelectionMenu'; var menuKeyMap = { nodeMenu: DefaultNodeMenuKey, edgeMenu: DefaultEdgeMenuKey, graphMenu: DefaultGraphMenuKey, selectionMenu: DefaultSelectionMenuKey, }; var defaultMenuConfig = { nodeMenu: [], edgeMenu: [], graphMenu: [], selectionMenu: [], }; var Menu = /** @class */ (function () { function Menu(_a) { var lf = _a.lf; var _this = this; this.__currentData = null; this.__isSilentMode = false; this.lf = lf; this.__menuDOM = document.createElement('ul'); this.__isSilentMode = lf.graphModel.editConfigModel.isSilentMode; this.menuTypeMap = new Map(); this.init(); this.lf.setMenuConfig = function (config) { _this.setMenuConfig(config); }; this.lf.addMenuConfig = function (config) { _this.addMenuConfig(config); }; this.lf.setMenuByType = function (config) { _this.setMenuByType(config); }; this.lf.changeMenuItemDisableStatus = function (menuKey, text, disabled) { _this.changeMenuItemDisableStatus(menuKey, text, disabled); }; this.lf.getMenuConfig = function (menuKey) { return _this.getMenuConfig(menuKey); }; this.lf.resetMenuConfigByType = function (menuType) { _this.resetMenuConfigByType(menuType); }; } /** * 初始化设置默认内置菜单栏 */ Menu.prototype.init = function () { var _this = this; var _a, _b, _c; defaultMenuConfig.nodeMenu = [ { text: '删除', callback: function (node) { _this.lf.deleteNode(node.id); }, }, { text: '编辑文本', callback: function (node) { _this.lf.graphModel.editText(node.id); }, }, { text: '复制', callback: function (node) { _this.lf.cloneNode(node.id); }, }, ]; (_a = this.menuTypeMap) === null || _a === void 0 ? void 0 : _a.set(DefaultNodeMenuKey, defaultMenuConfig.nodeMenu); defaultMenuConfig.edgeMenu = [ { text: '删除', callback: function (edge) { _this.lf.deleteEdge(edge.id); }, }, { text: '编辑文本', callback: function (edge) { _this.lf.graphModel.editText(edge.id); }, }, ]; (_b = this.menuTypeMap) === null || _b === void 0 ? void 0 : _b.set(DefaultEdgeMenuKey, defaultMenuConfig.edgeMenu); defaultMenuConfig.graphMenu = []; defaultMenuConfig.selectionMenu = [ { text: '删除', callback: function (elements) { _this.lf.clearSelectElements(); elements.edges.forEach(function (edge) { return _this.lf.deleteEdge(edge.id); }); elements.nodes.forEach(function (node) { return _this.lf.deleteNode(node.id); }); }, }, ]; (_c = this.menuTypeMap) === null || _c === void 0 ? void 0 : _c.set(DefaultSelectionMenuKey, defaultMenuConfig.selectionMenu); }; Menu.prototype.showMenu = function (x, y, menuList, options) { // 在静默模式下不显示菜单 if (this.__isSilentMode) return; if (!menuList || !menuList.length) return; var menu = this.__menuDOM; if (menu) { // 菜单容器不变,需要先清空内部的菜单项 menu.innerHTML = ''; menu.append.apply(menu, __spreadArray([], __read(this.__getMenuDom(menuList)), false)); // 菜单中没有项,不显示 if (!menu.children.length) return; menu.style.display = 'block'; if (!options) { menu.style.top = "".concat(y, "px"); menu.style.left = "".concat(x, "px"); return; } // https://github.com/didi/LogicFlow/issues/1019 // 根据边界判断菜单的left 和 top var width = options.width, height = options.height, clientX = options.clientX, clientY = options.clientY; var graphModel = this.lf.graphModel; var menuWidth = menu.offsetWidth; var menuIsRightShow = true; // ======先进行可视屏幕范围的判断======= // 浏览器窗口可视区域兼容性写法 // eslint-disable-next-line max-len var windowMaxX = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; var rightDistance = windowMaxX - clientX; // ======先进行可视屏幕范围的判断======= // ========再进行画布范围的判断======== var graphRect = graphModel.rootEl.getBoundingClientRect(); var graphMaxX = graphRect.left + graphRect.width; if (graphMaxX < windowMaxX) { // 画布右边小于可视屏幕范围的最右边,取画布右边作为极限值,计算出当前触摸点距离右边极限值的距离 rightDistance = graphMaxX - clientX; } // ========再进行画布范围的判断======== // 根据当前触摸点距离右边的距离 跟 menuWidth进行比较 if (rightDistance < menuWidth) { // 空间不足够,显示在左边 menuIsRightShow = false; } if (menuIsRightShow) { menu.style.left = "".concat(x, "px"); } else { menu.style.left = "".concat(x - width, "px"); } var menuHeight = menu.offsetHeight; var menuIsBottomShow = true; // ======先进行可视屏幕范围的判断======= // 浏览器窗口可视区域兼容性写法 // eslint-disable-next-line max-len var windowMaxY = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; var bottomDistance = windowMaxY - clientY; // ======先进行可视屏幕范围的判断======= // ========再进行画布范围的判断======== var graphMaxY = graphRect.top + graphRect.height; if (graphMaxY < windowMaxY) { // 画布底部小于可视屏幕范围的最底边,取画布底部作为极限值,计算出当前触摸点距离底部极限值的距离 bottomDistance = graphMaxY - clientY; } // ========再进行画布范围的判断======== if (bottomDistance < menuHeight) { // 如果下边距离太小,无法显示menu,则向上显示 menuIsBottomShow = false; } if (menuIsBottomShow) { menu.style.top = "".concat(y, "px"); } else { menu.style.top = "".concat(y - height, "px"); } } }; /** * 通用的菜单配置处理方法 */ Menu.prototype.processMenuConfig = function (config, operation) { var _this = this; if (!config) return; var menuTypes = [ 'nodeMenu', 'edgeMenu', 'graphMenu', 'selectionMenu', ]; menuTypes.forEach(function (menuType) { var _a, _b, _c, _d; var menuConfig = config[menuType]; var menuKey = menuKeyMap[menuType]; if (menuConfig === undefined) return; if (operation === 'set') { // 设置菜单配置 (_a = _this.menuTypeMap) === null || _a === void 0 ? void 0 : _a.set(menuKey, menuConfig ? menuConfig : []); } else if (operation === 'add' && Array.isArray(menuConfig)) { // 追加菜单配置(只支持数组类型) var existingMenuList = (_c = (_b = _this.menuTypeMap) === null || _b === void 0 ? void 0 : _b.get(menuKey)) !== null && _c !== void 0 ? _c : []; (_d = _this.menuTypeMap) === null || _d === void 0 ? void 0 : _d.set(menuKey, existingMenuList.concat(menuConfig)); } }); }; /** * 创建图片元素 */ Menu.prototype.createImageElement = function (src, alt) { var img = document.createElement('img'); img.src = src; img.alt = alt; img.style.width = '16px'; img.style.height = '16px'; img.style.objectFit = 'contain'; return img; }; /** * 检查是否为图片文件路径 */ Menu.prototype.isImageFile = function (iconString) { var imageExtensions = [ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.bmp', ]; return imageExtensions.some(function (ext) { return iconString.toLowerCase().includes(ext); }); }; /** * 处理图标逻辑 */ Menu.prototype.processIcon = function (iconContainer, icon, text) { var _a; if (typeof icon !== 'string') { // 如果icon是true,保持原有逻辑(创建空的图标容器) return; } var iconString = icon; // 1. base64格式的图片数据 if (iconString.startsWith('data:image/')) { var img = this.createImageElement(iconString, text || 'icon'); iconContainer.appendChild(img); return; } // 2. 图片文件路径 if (this.isImageFile(iconString)) { var img = this.createImageElement(iconString, text || 'icon'); iconContainer.appendChild(img); return; } // 3. HTML内容(包含< >标签) if (iconString.includes('<') && iconString.includes('>')) { iconContainer.innerHTML = iconString; return; } // 4. CSS类名(以空格分隔的多个类名或以.开头) if (iconString.includes(' ') || iconString.startsWith('.')) { var iconClasses = iconString.replace(/^\./, '').split(' '); (_a = iconContainer.classList).add.apply(_a, __spreadArray([], __read(iconClasses), false)); return; } // 5. 单个CSS类名 iconContainer.classList.add(iconString); }; /** * 获取 Menu DOM * @param list 菜单项 * @return 菜单项 DOM */ Menu.prototype.__getMenuDom = function (list) { var _this = this; var menuList = []; list && list.length > 0 && list.forEach(function (item) { var element = document.createElement('li'); if (item.className) { element.className = "lf-menu-item ".concat(item.disabled ? 'lf-menu-item__disabled' : '', " ").concat(item.className); } else { element.className = "lf-menu-item ".concat(item.disabled ? 'lf-menu-item__disabled' : ''); } if (item.icon) { var icon = document.createElement('span'); icon.className = 'lf-menu-item-icon'; _this.processIcon(icon, item.icon, item.text); element.appendChild(icon); } var text = document.createElement('span'); text.className = 'lf-menu-item-text'; if (item.text) { text.innerText = item.text; } element.appendChild(text); if (item.disabled) { element.setAttribute('disabled', 'true'); } ; element.onclickCallback = item.callback; menuList.push(element); }); return menuList; }; /** * 更新菜单项DOM元素的禁用状态 * @param text 菜单项文本 * @param disabled 是否禁用 */ Menu.prototype.updateMenuItemDOMStatus = function (text, disabled) { if (!this.__menuDOM || this.__menuDOM.style.display === 'none') { return; } // 查找对应的菜单项DOM元素 var menuItems = Array.from(this.__menuDOM.querySelectorAll('.lf-menu-item')); var targetMenuItem = menuItems.find(function (menuItemElement) { var textElement = menuItemElement.querySelector('.lf-menu-item-text'); return (textElement === null || textElement === void 0 ? void 0 : textElement.textContent) === text; }); if (targetMenuItem) { var element = targetMenuItem; if (disabled) { element.classList.add('lf-menu-item__disabled'); element.setAttribute('disabled', 'true'); } else { element.classList.remove('lf-menu-item__disabled'); element.removeAttribute('disabled'); } } }; /** * 设置静默模式监听器 * 当 isSilentMode 变化时,动态更新菜单的显隐状态 */ Menu.prototype.setupSilentModeListener = function () { var _this = this; // 创建并保存事件处理器引用 this.__editConfigChangeHandler = function (_a) { var data = _a.data; var newIsSilentMode = data.isSilentMode; if (newIsSilentMode !== _this.__isSilentMode) { _this.__isSilentMode = newIsSilentMode; _this.updateMenuVisibility(!newIsSilentMode); } }; // 监听编辑配置变化 this.lf.on('editConfig:changed', this.__editConfigChangeHandler); }; /** * 更新菜单显隐状态 */ Menu.prototype.updateMenuVisibility = function (visible) { if (!this.__menuDOM) return; if (visible) { if (this.__currentData) { this.__menuDOM.style.display = 'block'; } } else { this.__menuDOM.style.display = 'none'; this.__currentData = null; // 清除当前数据 } }; /** * 检查菜单是否正在显示并重新渲染 */ Menu.prototype.refreshCurrentMenu = function () { var _a; var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; if (!this.__menuDOM || this.__menuDOM.style.display === 'none' || !this.__currentData) { return; } // 保存当前菜单的位置 var _m = this.__menuDOM.style, left = _m.left, top = _m.top; // 根据当前数据类型获取对应的菜单配置 var menuList = []; // 判断当前数据类型并获取相应菜单 if (this.__currentData && typeof this.__currentData === 'object') { if ('sourceNodeId' in this.__currentData && 'targetNodeId' in this.__currentData) { // 边菜单 var model = this.lf.graphModel.getEdgeModelById(this.__currentData.id); if (model) { var typeMenus = (_b = this.menuTypeMap) === null || _b === void 0 ? void 0 : _b.get(model.type); if (model.menu && Array.isArray(model.menu)) { menuList = model.menu; } else if (typeMenus) { menuList = typeMenus; } else { menuList = (_d = (_c = this.menuTypeMap) === null || _c === void 0 ? void 0 : _c.get(DefaultEdgeMenuKey)) !== null && _d !== void 0 ? _d : []; } } } else if ('id' in this.__currentData && 'type' in this.__currentData) { // 节点菜单 var model = this.lf.graphModel.getNodeModelById(this.__currentData.id); if (model) { var typeMenus = (_e = this.menuTypeMap) === null || _e === void 0 ? void 0 : _e.get(model.type); if (model.menu && Array.isArray(model.menu)) { menuList = model.menu; } else if (typeMenus) { menuList = typeMenus; } else { menuList = (_g = (_f = this.menuTypeMap) === null || _f === void 0 ? void 0 : _f.get(DefaultNodeMenuKey)) !== null && _g !== void 0 ? _g : []; } } } else if ('nodes' in this.__currentData && 'edges' in this.__currentData) { // 选区菜单 menuList = (_j = (_h = this.menuTypeMap) === null || _h === void 0 ? void 0 : _h.get(DefaultSelectionMenuKey)) !== null && _j !== void 0 ? _j : []; } else { // 画布菜单 menuList = (_l = (_k = this.menuTypeMap) === null || _k === void 0 ? void 0 : _k.get(DefaultGraphMenuKey)) !== null && _l !== void 0 ? _l : []; } } // 重新渲染菜单 if (menuList && menuList.length > 0) { this.__menuDOM.innerHTML = ''; (_a = this.__menuDOM).append.apply(_a, __spreadArray([], __read(this.__getMenuDom(menuList)), false)); // 恢复菜单位置(如果有的话) if (left) this.__menuDOM.style.left = left; if (top) this.__menuDOM.style.top = top; } else { // 如果没有菜单项,隐藏菜单 this.__menuDOM.style.display = 'none'; this.__currentData = null; } }; /** * 设置指定类型元素的菜单 */ Menu.prototype.setMenuByType = function (_a) { var _b; var type = _a.type, menu = _a.menu; if (!type || !menu) { return; } (_b = this.menuTypeMap) === null || _b === void 0 ? void 0 : _b.set(type, menu); this.refreshCurrentMenu(); // 实时更新DOM }; Menu.prototype.getMenuConfig = function (menuKey) { var _a, _b; return (_b = (_a = this.menuTypeMap) === null || _a === void 0 ? void 0 : _a.get(menuKeyMap[menuKey])) !== null && _b !== void 0 ? _b : []; }; Menu.prototype.resetMenuConfigByType = function (menuKey) { var _a; this.setMenuConfig((_a = {}, _a[menuKey] = defaultMenuConfig[menuKey], _a)); // setMenuConfig 已经包含了 refreshCurrentMenu 调用 }; Menu.prototype.resetAllMenuConfig = function () { this.setMenuConfig(defaultMenuConfig); // setMenuConfig 已经包含了 refreshCurrentMenu 调用 }; // 复写菜单 Menu.prototype.setMenuConfig = function (config) { this.processMenuConfig(config, 'set'); this.refreshCurrentMenu(); // 实时更新DOM }; // 在默认菜单后面追加菜单项 Menu.prototype.addMenuConfig = function (config) { this.processMenuConfig(config, 'add'); this.refreshCurrentMenu(); // 实时更新DOM }; /** * @deprecated * 复写添加 */ Menu.prototype.changeMenuItem = function (type, config) { if (type === 'add') { this.addMenuConfig(config); } else if (type === 'reset') { this.setMenuConfig(config); } else { throw new Error("The first parameter of changeMenuConfig should be 'add' or 'reset'"); } // addMenuConfig 和 setMenuConfig 已经包含了 refreshCurrentMenu 调用 }; Menu.prototype.changeMenuItemDisableStatus = function (menuKey, text, disabled) { var _a, _b; if (!menuKey || !text) { console.warn('params is vaild'); return; } var menuList = (_b = (_a = this.menuTypeMap) === null || _a === void 0 ? void 0 : _a.get(menuKeyMap[menuKey])) !== null && _b !== void 0 ? _b : []; if (!menuList.length) { console.warn("menuMap: ".concat(menuKey, " is not exist")); return; } var menuItem = menuList.find(function (item) { return item.text === text; }); if (!menuItem) { console.warn("menuItem: ".concat(text, " is not exist")); return; } menuItem.disabled = disabled; // 如果菜单当前正在显示,则同时更新DOM元素的样式 this.updateMenuItemDOMStatus(text, disabled); }; Menu.prototype.render = function (lf, container) { var _this = this; if (lf.graphModel.editConfigModel.isSilentMode) return; this.__container = container; this.__currentData = null; // 当前展示的菜单所属元素的model数据 // 监听 isSilentMode 变化 this.setupSilentModeListener(); if (this.__menuDOM) { this.__menuDOM.className = 'lf-menu'; container.appendChild(this.__menuDOM); // 将选项的click事件委托至menu容器 // 在捕获阶段拦截并执行 this.__menuDOM.addEventListener('click', function (event) { event.stopPropagation(); var target = event.target; // 菜单有多层dom,需要精确获取菜单项所对应的dom // 除菜单项dom外,应考虑两种情况 // 1. 菜单项的子元素 2. 菜单外层容器 while (Array.from(target.classList).indexOf('lf-menu-item') === -1 && Array.from(target.classList).indexOf('lf-menu') === -1) { target = target === null || target === void 0 ? void 0 : target.parentElement; } if (Array.from(target.classList).indexOf('lf-menu-item__disabled') > -1) return; if (Array.from(target.classList).indexOf('lf-menu-item') > -1) { // 如果菜单项被禁用,则不执行回调 ; target.onclickCallback(_this.__currentData, target); // 点击后隐藏menu if (_this.__menuDOM) { _this.__menuDOM.style.display = 'none'; } _this.__currentData = null; } else { // 如果点击区域不在菜单项内 console.warn('点击区域不在菜单项内,请检查代码!'); } }, true); } // 通过事件控制菜单的显示和隐藏 this.lf.on('node:contextmenu', function (_a) { var _b, _c; var data = _a.data, position = _a.position, e = _a.e; var _d = position.domOverlayPosition, x = _d.x, y = _d.y; var id = data.id; var model = _this.lf.graphModel.getNodeModelById(id); if (!model || _this.__isSilentMode) return; var menuList = []; var typeMenus = (_b = _this.menuTypeMap) === null || _b === void 0 ? void 0 : _b.get(model.type); // 1.如果单个节点自定义了菜单,以单个节点自定义为准 if (model && model.menu && Array.isArray(model.menu)) { menuList = model.menu; } else if (typeMenus) { // 2.如果当前节点类型定义了菜单,再取该配置 menuList = typeMenus; } else { // 3.最后取全局默认 menuList = (_c = _this.menuTypeMap) === null || _c === void 0 ? void 0 : _c.get(DefaultNodeMenuKey); } _this.__currentData = data; _this.showMenu(x, y, menuList, { width: model.width, height: model.height, clientX: e.clientX, clientY: e.clientY, }); }); this.lf.on('edge:contextmenu', function (_a) { var _b, _c, _d; var data = _a.data, position = _a.position, e = _a.e; var _e = position.domOverlayPosition, x = _e.x, y = _e.y; var id = data.id; var model = _this.lf.graphModel.getEdgeModelById(id); if (!model || _this.__isSilentMode) return; var menuList = []; var typeMenus = (_b = _this.menuTypeMap) === null || _b === void 0 ? void 0 : _b.get(model.type); // 菜单优先级: model.menu > typeMenus > defaultEdgeMenu,注释同上节点 if (model && model.menu && Array.isArray(model.menu)) { menuList = model.menu; } else if (typeMenus) { menuList = typeMenus; } else { menuList = (_d = (_c = _this.menuTypeMap) === null || _c === void 0 ? void 0 : _c.get(DefaultEdgeMenuKey)) !== null && _d !== void 0 ? _d : []; } _this.__currentData = data; _this.showMenu(x, y, menuList, { width: model.width, height: model.height, clientX: e.clientX, clientY: e.clientY, }); }); this.lf.on('blank:contextmenu', function (_a) { var _b, _c; var position = _a.position; if (_this.__isSilentMode) return; var menuList = (_c = (_b = _this.menuTypeMap) === null || _b === void 0 ? void 0 : _b.get(DefaultGraphMenuKey)) !== null && _c !== void 0 ? _c : []; var _d = position.domOverlayPosition, x = _d.x, y = _d.y; _this.__currentData = __assign({}, position.canvasOverlayPosition); _this.showMenu(x, y, menuList); }); this.lf.on('selection:contextmenu', function (_a) { var _b; var data = _a.data, position = _a.position; if (_this.__isSilentMode) return; var menuList = (_b = _this.menuTypeMap) === null || _b === void 0 ? void 0 : _b.get(DefaultSelectionMenuKey); var _c = position.domOverlayPosition, x = _c.x, y = _c.y; _this.__currentData = data; _this.showMenu(x, y, menuList); }); this.lf.on('node:mousedown', function () { _this.__menuDOM.style.display = 'none'; }); this.lf.on('edge:click', function () { _this.__menuDOM.style.display = 'none'; }); this.lf.on('blank:click', function () { _this.__menuDOM.style.display = 'none'; }); }; Menu.prototype.destroy = function () { var _a; // 清理事件监听器 if (this.__editConfigChangeHandler) { this.lf.off('editConfig:changed', this.__editConfigChangeHandler); } if (this.__menuDOM) { (_a = this === null || this === void 0 ? void 0 : this.__container) === null || _a === void 0 ? void 0 : _a.removeChild(this.__menuDOM); this.__menuDOM = undefined; } }; Menu.pluginName = 'menu'; return Menu; }()); exports.Menu = Menu; exports.default = Menu;