UNPKG

mind.svg.js

Version:

Display and operate MindMap using SVG in browser

276 lines (250 loc) 10.7 kB
import { Constants } from "./mind.svg.main"; import * as Event from "./mind.svg.event"; import * as Extend from "./mind.svg.extend"; const EVENT = Event.Constants; // 拖拽事件记录变量的符号 const SYMBOLE_DRAG_EVENT_LOG = Symbol("mind.svg.drag.event.log"); // 拖拽显示盒的风格 const CLASS_DRAG_BOX = "mind-topic-drag-box"; // 拖拽连接线的风格 const CLASS_DRAG_LINE = "mind-topic-drag-line"; // 拖拽盒的强制风格 const STYLE_DRAG_BOX_FORCED = "position: absolute;"; // 查询是否可以被拖拽 const EVENT_QUERY_DRAGGABLE = "mindevent.query.draggable"; // 通知拖拽已启动 const EVENT_DRAG_START = "mindevent.drag.start"; // 通知识别到一个待选的拖拽悬停元素 const EVENT_DRAG_POTENTIAL_HOVER = "mindevent.drag.potential.hover"; // 悬停定时器的超时事件 const TIMEOUR_HOVER = 900; /** * 从对象中获取拖拽事件记录 * @param {*} _obj */ function dragEventLog(_obj) { return _obj[SYMBOLE_DRAG_EVENT_LOG] || (_obj[SYMBOLE_DRAG_EVENT_LOG] = {}); } /** * 准备一个拖拽盒,并对起进行锚定 * @param {*} _mind * @param {*} _eventLog * @param {*} _x 仅当此处开始的两个坐标参数有传入时,才会对拖拽盒进行锚定并显示 * @param {*} _y */ function dragBox(_mind, _eventLog, _x, _y) { // 确保有一个推拽盒 const container = _mind.container; let boxNode = container.firstDescendant(`div.${CLASS_DRAG_BOX}`); if (!boxNode) { boxNode = container.createChild("div").attr("class", CLASS_DRAG_BOX); } // 让拖拽盒的主题文本与被推拽的主题一致 const topic = _eventLog.dragingTopic; topic && (boxNode.text = topic.topicData("title")); // 锚定拖拽盒的位置 if ((_x !== undefined) && (_y !== undefined)) { let {x:showX, y:showY} = container.translatePoint(_x, _y); boxNode.attr("style", `${STYLE_DRAG_BOX_FORCED} left:${showX + 3}px; top:${(showY - (boxNode.height / 2)) - container.top}px;`); } return boxNode; } /** * 准备拖拽线 * @param {*} _mind * @param {*} _dragBox * @param {*} _eventLog */ function dragLine(_mind, _dragBox, _eventLog) { const svg = _mind.svg; const line = svg.firstDescendant(`path.${CLASS_DRAG_LINE}`) || svg.createSVGChild("path").attr("class", CLASS_DRAG_LINE); let hoverTopic; if (_eventLog && (hoverTopic = _eventLog.hoverTopic)) { const start = hoverTopic.titleZone; const endOrign = _dragBox.globalRect; const {x:endX, y:endY} = svg.translatePoint(endOrign.x + endOrign.width / 2, endOrign.y + endOrign.height / 2); line.attr({ d: `M${start.x + start.width / 2} ${start.y + start.height / 2}L${endX} ${endY}`, style: "display: unset;" }); } return line; } /** * 悬停定时器 */ function hoverTimerProc(_topic) { const eventLog = dragEventLog(_topic); eventLog.hoverTimer = undefined; _topic.fireEvent(EVENT_DRAG_POTENTIAL_HOVER, {topic: _topic}); } // 在Topic类中扩展拖拽的实现 Extend.extendEventHandler("Topic", { // 鼠标按下,检查是否可以拖拽 [Event.namedHandler("mousedown", Constants.STAMP_TOPIC_TITLE)] (_event) { const eventLog = dragEventLog(this); const mouseButton = (_event.which || (_event.button + 1)); if ((mouseButton === 1) && (this.level > 0)) { eventLog.waitDrag = this.fireEvent(EVENT_QUERY_DRAGGABLE, this, true); Event.handledEvent(_event); } }, // 鼠标移动,如果允许拖拽则启动拖拽 [Event.namedHandler("mousemove", Constants.STAMP_TOPIC_TITLE)](_event) { const eventLog = dragEventLog(this); const mouseButton = (_event.which || (_event.button + 1)); if ((mouseButton === 1) && eventLog.waitDrag) { eventLog.waitDrag = undefined; eventLog.hoverTimer && clearTimeout(eventLog.hoverTimer); eventLog.hoverTimer = undefined; this.fireEvent(EVENT_DRAG_START, {topic: this, x: _event.clientX, y: _event.clientY}); } Event.continueSiblingHandler(_event); }, // 鼠标移入,启动悬停计数 [Event.namedHandler("mouseover", Constants.STAMP_TOPIC_TITLE)](_event) { const eventLog = dragEventLog(this); const mouseButton = (_event.which || (_event.button + 1)); if (mouseButton === 1) { eventLog.hoverTimer && clearTimeout(eventLog.hoverTimer); eventLog.hoverTimer = setTimeout(hoverTimerProc, TIMEOUR_HOVER, this); } Event.continueSiblingHandler(_event); }, // 鼠标移出, 取消拖拽等待以及清除悬停计数 [Event.namedHandler("mouseout", Constants.STAMP_TOPIC_TITLE)](_event) { const eventLog = dragEventLog(this); eventLog.waitDrag = undefined; eventLog.hoverTimer && clearTimeout(eventLog.hoverTimer); eventLog.hoverTimer = undefined; Event.continueSiblingHandler(_event); }, // 鼠标抬起,取消拖拽等待以及清除悬停计数 [Event.namedHandler("mouseup", Constants.STAMP_TOPIC_TITLE)](_event) { const eventLog = dragEventLog(this); eventLog.waitDrag = undefined; eventLog.hoverTimer && clearTimeout(eventLog.hoverTimer); eventLog.hoverTimer = undefined; Event.continueSiblingHandler(_event); } }); // 在MindSVG主容器中扩展拖拽的实现 Extend.extendEventHandler("MindSVG", { // 响应是否允许拖拽的查询事件 [Event.namedHandler(EVENT_QUERY_DRAGGABLE)] (_event) { _event.detail.result = this.config("draggable"); }, // 响应拖拽启动事件 [Event.namedHandler(EVENT_DRAG_START)] (_event) { const eventLog = dragEventLog(this); const value = _event.detail.value; const topic = value.topic; eventLog.dragingTopic = topic; if (topic) { const box = dragBox(this, eventLog, value.x, value.y); eventLog.hoverTopic = topic.parent; dragLine(this, box, eventLog); topic.visible = false; } Event.handledEvent(_event); }, // 响应拖拽入的候选主题的处理事件 [Event.namedHandler(EVENT_DRAG_POTENTIAL_HOVER)] (_event) { const eventLog = dragEventLog(this); const potentialTopic = _event.detail.value.topic; if (eventLog.dragingTopic) { eventLog.hoverTopic = potentialTopic; const box = dragBox(this, eventLog); dragLine(this, box, eventLog); } Event.handledEvent(_event); }, // 响应拖拽过程事件 [Event.namedHandler("mousemove")] (_event) { const eventLog = dragEventLog(this); const button = (_event.which || (_event.button + 1)); if ((button === 1) && eventLog.dragingTopic) { const box = dragBox(this, eventLog, _event.clientX, _event.clientY); dragLine(this, box, eventLog); Event.handledEvent(_event); } }, // 响应拖拽结束事件 [Event.namedHandler("mouseup")] (_event) { const eventLog = dragEventLog(this); const button = (_event.which || (_event.button + 1)); if ((button === 1) && eventLog.dragingTopic) { // 隐藏拖拽盒与拖拽线 const box = dragBox(this, eventLog); box.attr("style", "display: none;"); dragLine(this, box).attr("style", "display:none;"); // 发送推拽确认事件,可用于应用层历史记录或者应用层阻止对某个拖拽的生效 const dragingTopic = eventLog.dragingTopic; const newParent = eventLog.hoverTopic; const originParent = dragingTopic.parent; const originSibling = dragingTopic.previousSibling; let originDirection = (originParent.level === 0 ? dragingTopic.direction() : null); const canDrop = this.fireEvent(EVENT.EVENT_CONFIRM_DRAG, { dragingTopic, originParent, newParent }, true); let endEventData = undefined; if (newParent && canDrop) { // 计算拖拽后的主题的布局方向 const ptY = _event.clientY; let isLeft = false; let newDirection; if (newParent.level === 0) { if (_event.clientX < newParent.itemZone.x) { newDirection = Constants.CONNECT_DIRECTION_LEFT; isLeft = true; } else { newDirection = Constants.CONNECT_DIRECTION_RIGHT; } } else { newDirection = null; } dragingTopic.direction(newDirection); // 计算拖拽后主题的临近兄弟主题 let prevSibling = undefined; for (let sibling of newParent.childrenTopics()) { if (!dragingTopic.isSame(sibling) && (!isLeft || sibling.direction() === Constants.CONNECT_DIRECTION_LEFT)) { const rect = sibling.itemZone; if (ptY < rect.y) { break; } else { prevSibling = sibling; } } } // 完成拖放 prevSibling ? eventLog.dragingTopic.insertNextTo(prevSibling, false) : eventLog.dragingTopic.insertTo(newParent, true, false); endEventData = { dragingTopic, originParent, originSibling, originDirection, newParent, newSibling: prevSibling, newDirection }; } dragingTopic.focus(true); eventLog.dragingTopic.visible = true; eventLog.dragingTopic = undefined; Event.handledEvent(_event); this.fireEvent(EVENT.EVENT_END_DRAG, endEventData); } } }); // 扩展配置项 Extend.extend("DEFAULT_CONFIGS", { draggable: false }); // 导出 export const DragEvent = { EVENT_DRAG_START, EVENT_QUERY_DRAGGABLE };