@logicflow/extension
Version:
LogicFlow Extensions
712 lines (711 loc) • 29.2 kB
JavaScript
"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;