mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-17 22:08:25 +08:00
1966 lines
58 KiB
JavaScript
1966 lines
58 KiB
JavaScript
import merge from 'deepmerge'
|
||
import LogicalStructure from '../../layouts/LogicalStructure'
|
||
import MindMap from '../../layouts/MindMap'
|
||
import CatalogOrganization from '../../layouts/CatalogOrganization'
|
||
import OrganizationStructure from '../../layouts/OrganizationStructure'
|
||
import Timeline from '../../layouts/Timeline'
|
||
import VerticalTimeline from '../../layouts/VerticalTimeline'
|
||
import Fishbone from '../../layouts/Fishbone'
|
||
import TextEdit from './TextEdit'
|
||
import {
|
||
copyNodeTree,
|
||
simpleDeepClone,
|
||
walk,
|
||
bfsWalk,
|
||
loadImage,
|
||
isUndef,
|
||
getTopAncestorsFomNodeList,
|
||
addDataToAppointNodes,
|
||
createUidForAppointNodes,
|
||
formatDataToArray,
|
||
removeFromParentNodeData,
|
||
createUid,
|
||
getNodeDataIndex,
|
||
getNodeIndexInNodeList,
|
||
setDataToClipboard,
|
||
getDataFromClipboard,
|
||
htmlEscape,
|
||
parseAddGeneralizationNodeList,
|
||
checkNodeListIsEqual,
|
||
createSmmFormatData,
|
||
checkSmmFormatData,
|
||
checkIsNodeStyleDataKey,
|
||
removeRichTextStyes,
|
||
formatGetNodeGeneralization
|
||
} from '../../utils'
|
||
import { shapeList } from './node/Shape'
|
||
import { lineStyleProps } from '../../themes/default'
|
||
import { CONSTANTS, ERROR_TYPES } from '../../constants/constant'
|
||
import { Polygon } from '@svgdotjs/svg.js'
|
||
|
||
// 布局列表
|
||
const layouts = {
|
||
// 逻辑结构图
|
||
[CONSTANTS.LAYOUT.LOGICAL_STRUCTURE]: LogicalStructure,
|
||
// 思维导图
|
||
[CONSTANTS.LAYOUT.MIND_MAP]: MindMap,
|
||
// 目录组织图
|
||
[CONSTANTS.LAYOUT.CATALOG_ORGANIZATION]: CatalogOrganization,
|
||
// 组织结构图
|
||
[CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE]: OrganizationStructure,
|
||
// 时间轴
|
||
[CONSTANTS.LAYOUT.TIMELINE]: Timeline,
|
||
// 时间轴2
|
||
[CONSTANTS.LAYOUT.TIMELINE2]: Timeline,
|
||
// 竖向时间轴
|
||
[CONSTANTS.LAYOUT.VERTICAL_TIMELINE]: VerticalTimeline,
|
||
// 鱼骨图
|
||
[CONSTANTS.LAYOUT.FISHBONE]: Fishbone
|
||
}
|
||
|
||
// 渲染
|
||
class Render {
|
||
// 构造函数
|
||
constructor(opt = {}) {
|
||
this.opt = opt
|
||
this.mindMap = opt.mindMap
|
||
this.themeConfig = this.mindMap.themeConfig
|
||
// 渲染树,操作过程中修改的都是这里的数据
|
||
this.renderTree = this.mindMap.opt.data
|
||
? merge({}, this.mindMap.opt.data)
|
||
: null
|
||
// 是否重新渲染
|
||
this.reRender = false
|
||
// 是否正在渲染中
|
||
this.isRendering = false
|
||
// 是否存在等待渲染
|
||
this.hasWaitRendering = false
|
||
this.waitRenderingParams = []
|
||
// 用于缓存节点
|
||
this.nodeCache = {}
|
||
this.lastNodeCache = {}
|
||
// 触发render的来源
|
||
this.renderSource = ''
|
||
// 当前激活的节点列表
|
||
this.activeNodeList = []
|
||
// 根节点
|
||
this.root = null
|
||
// 文本编辑框,需要再bindEvent之前实例化,否则单击事件只能触发隐藏文本编辑框,而无法保存文本修改
|
||
this.textEdit = new TextEdit(this)
|
||
// 当前复制的数据
|
||
this.lastBeingCopyData = null
|
||
this.beingCopyData = null
|
||
this.beingPasteText = ''
|
||
this.beingPasteImgSize = 0
|
||
this.currentBeingPasteType = ''
|
||
// 节点高亮框
|
||
this.highlightBoxNode = null
|
||
// 上一次节点激活数据
|
||
this.lastActiveNode = null
|
||
this.lastActiveNodeList = []
|
||
// 布局
|
||
this.setLayout()
|
||
// 绑定事件
|
||
this.bindEvent()
|
||
// 注册命令
|
||
this.registerCommands()
|
||
// 注册快捷键
|
||
this.registerShortcutKeys()
|
||
}
|
||
|
||
// 设置布局结构
|
||
setLayout() {
|
||
this.layout = new (
|
||
layouts[this.mindMap.opt.layout]
|
||
? layouts[this.mindMap.opt.layout]
|
||
: layouts[CONSTANTS.LAYOUT.LOGICAL_STRUCTURE]
|
||
)(this, this.mindMap.opt.layout)
|
||
}
|
||
|
||
// 重新设置思维导图数据
|
||
setData(data) {
|
||
if (this.mindMap.richText) {
|
||
this.renderTree = data ? this.mindMap.richText.handleSetData(data) : null
|
||
} else {
|
||
this.renderTree = data
|
||
}
|
||
}
|
||
|
||
// 绑定事件
|
||
bindEvent() {
|
||
// 画布点击事件清除当前激活节点列表
|
||
this.mindMap.on('draw_click', e => {
|
||
this.clearActiveNodeListOnDrawClick(e, 'click')
|
||
})
|
||
// 画布右键事件事件清除当前激活节点列表
|
||
this.mindMap.on('contextmenu', e => {
|
||
this.clearActiveNodeListOnDrawClick(e, 'contextmenu')
|
||
})
|
||
// 鼠标双击回到根节点
|
||
this.mindMap.svg.on('dblclick', () => {
|
||
if (!this.mindMap.opt.enableDblclickBackToRootNode) return
|
||
this.setRootNodeCenter()
|
||
})
|
||
// let timer = null
|
||
// this.mindMap.on('view_data_change', () => {
|
||
// clearTimeout(timer)
|
||
// timer = setTimeout(() => {
|
||
// this.render()
|
||
// }, 300)
|
||
// })
|
||
}
|
||
|
||
// 注册命令
|
||
registerCommands() {
|
||
// 全选
|
||
this.selectAll = this.selectAll.bind(this)
|
||
this.mindMap.command.add('SELECT_ALL', this.selectAll)
|
||
// 回退
|
||
this.back = this.back.bind(this)
|
||
this.mindMap.command.add('BACK', this.back)
|
||
// 前进
|
||
this.forward = this.forward.bind(this)
|
||
this.mindMap.command.add('FORWARD', this.forward)
|
||
// 插入同级节点
|
||
this.insertNode = this.insertNode.bind(this)
|
||
this.mindMap.command.add('INSERT_NODE', this.insertNode)
|
||
// 插入多个同级节点
|
||
this.insertMultiNode = this.insertMultiNode.bind(this)
|
||
this.mindMap.command.add('INSERT_MULTI_NODE', this.insertMultiNode)
|
||
// 插入子节点
|
||
this.insertChildNode = this.insertChildNode.bind(this)
|
||
this.mindMap.command.add('INSERT_CHILD_NODE', this.insertChildNode)
|
||
// 插入多个子节点
|
||
this.insertMultiChildNode = this.insertMultiChildNode.bind(this)
|
||
this.mindMap.command.add(
|
||
'INSERT_MULTI_CHILD_NODE',
|
||
this.insertMultiChildNode
|
||
)
|
||
// 插入父节点
|
||
this.insertParentNode = this.insertParentNode.bind(this)
|
||
this.mindMap.command.add('INSERT_PARENT_NODE', this.insertParentNode)
|
||
// 上移节点
|
||
this.upNode = this.upNode.bind(this)
|
||
this.mindMap.command.add('UP_NODE', this.upNode)
|
||
// 下移节点
|
||
this.downNode = this.downNode.bind(this)
|
||
this.mindMap.command.add('DOWN_NODE', this.downNode)
|
||
// 将一个节点上移一个层级
|
||
this.moveUpOneLevel = this.moveUpOneLevel.bind(this)
|
||
this.mindMap.command.add('MOVE_UP_ONE_LEVEL', this.moveUpOneLevel)
|
||
// 移动节点
|
||
this.insertAfter = this.insertAfter.bind(this)
|
||
this.mindMap.command.add('INSERT_AFTER', this.insertAfter)
|
||
this.insertBefore = this.insertBefore.bind(this)
|
||
this.mindMap.command.add('INSERT_BEFORE', this.insertBefore)
|
||
this.moveNodeTo = this.moveNodeTo.bind(this)
|
||
this.mindMap.command.add('MOVE_NODE_TO', this.moveNodeTo)
|
||
// 删除节点
|
||
this.removeNode = this.removeNode.bind(this)
|
||
this.mindMap.command.add('REMOVE_NODE', this.removeNode)
|
||
// 仅删除当前节点
|
||
this.removeCurrentNode = this.removeCurrentNode.bind(this)
|
||
this.mindMap.command.add('REMOVE_CURRENT_NODE', this.removeCurrentNode)
|
||
// 粘贴节点
|
||
this.pasteNode = this.pasteNode.bind(this)
|
||
this.mindMap.command.add('PASTE_NODE', this.pasteNode)
|
||
// 剪切节点
|
||
this.cutNode = this.cutNode.bind(this)
|
||
this.mindMap.command.add('CUT_NODE', this.cutNode)
|
||
// 修改节点单个样式
|
||
this.setNodeStyle = this.setNodeStyle.bind(this)
|
||
this.mindMap.command.add('SET_NODE_STYLE', this.setNodeStyle)
|
||
// 修改节点多个样式
|
||
this.setNodeStyles = this.setNodeStyles.bind(this)
|
||
this.mindMap.command.add('SET_NODE_STYLES', this.setNodeStyles)
|
||
// 切换节点是否激活
|
||
this.setNodeActive = this.setNodeActive.bind(this)
|
||
this.mindMap.command.add('SET_NODE_ACTIVE', this.setNodeActive)
|
||
// 清除所有激活节点
|
||
this.clearActiveNode = this.clearActiveNode.bind(this)
|
||
this.mindMap.command.add('CLEAR_ACTIVE_NODE', this.clearActiveNode)
|
||
// 切换节点是否展开
|
||
this.setNodeExpand = this.setNodeExpand.bind(this)
|
||
this.mindMap.command.add('SET_NODE_EXPAND', this.setNodeExpand)
|
||
// 展开所有节点
|
||
this.expandAllNode = this.expandAllNode.bind(this)
|
||
this.mindMap.command.add('EXPAND_ALL', this.expandAllNode)
|
||
// 收起所有节点
|
||
this.unexpandAllNode = this.unexpandAllNode.bind(this)
|
||
this.mindMap.command.add('UNEXPAND_ALL', this.unexpandAllNode)
|
||
// 展开到指定层级
|
||
this.expandToLevel = this.expandToLevel.bind(this)
|
||
this.mindMap.command.add('UNEXPAND_TO_LEVEL', this.expandToLevel)
|
||
// 设置节点数据
|
||
this.setNodeData = this.setNodeData.bind(this)
|
||
this.mindMap.command.add('SET_NODE_DATA', this.setNodeData)
|
||
// 设置节点文本
|
||
this.setNodeText = this.setNodeText.bind(this)
|
||
this.mindMap.command.add('SET_NODE_TEXT', this.setNodeText)
|
||
// 设置节点图片
|
||
this.setNodeImage = this.setNodeImage.bind(this)
|
||
this.mindMap.command.add('SET_NODE_IMAGE', this.setNodeImage)
|
||
// 设置节点图标
|
||
this.setNodeIcon = this.setNodeIcon.bind(this)
|
||
this.mindMap.command.add('SET_NODE_ICON', this.setNodeIcon)
|
||
// 设置节点超链接
|
||
this.setNodeHyperlink = this.setNodeHyperlink.bind(this)
|
||
this.mindMap.command.add('SET_NODE_HYPERLINK', this.setNodeHyperlink)
|
||
// 设置节点备注
|
||
this.setNodeNote = this.setNodeNote.bind(this)
|
||
this.mindMap.command.add('SET_NODE_NOTE', this.setNodeNote)
|
||
// 设置节点附件
|
||
this.setNodeAttachment = this.setNodeAttachment.bind(this)
|
||
this.mindMap.command.add('SET_NODE_ATTACHMENT', this.setNodeAttachment)
|
||
// 设置节点标签
|
||
this.setNodeTag = this.setNodeTag.bind(this)
|
||
this.mindMap.command.add('SET_NODE_TAG', this.setNodeTag)
|
||
// 设置节点公式
|
||
this.insertFormula = this.insertFormula.bind(this)
|
||
this.mindMap.command.add('INSERT_FORMULA', this.insertFormula)
|
||
// 添加节点概要
|
||
this.addGeneralization = this.addGeneralization.bind(this)
|
||
this.mindMap.command.add('ADD_GENERALIZATION', this.addGeneralization)
|
||
// 删除节点概要
|
||
this.removeGeneralization = this.removeGeneralization.bind(this)
|
||
this.mindMap.command.add('REMOVE_GENERALIZATION', this.removeGeneralization)
|
||
// 设置节点自定义位置
|
||
this.setNodeCustomPosition = this.setNodeCustomPosition.bind(this)
|
||
this.mindMap.command.add(
|
||
'SET_NODE_CUSTOM_POSITION',
|
||
this.setNodeCustomPosition
|
||
)
|
||
// 一键整理布局
|
||
this.resetLayout = this.resetLayout.bind(this)
|
||
this.mindMap.command.add('RESET_LAYOUT', this.resetLayout)
|
||
// 设置节点形状
|
||
this.setNodeShape = this.setNodeShape.bind(this)
|
||
this.mindMap.command.add('SET_NODE_SHAPE', this.setNodeShape)
|
||
// 定位节点
|
||
this.goTargetNode = this.goTargetNode.bind(this)
|
||
this.mindMap.command.add('GO_TARGET_NODE', this.goTargetNode)
|
||
// 一键去除节点自定义样式
|
||
this.removeCustomStyles = this.removeCustomStyles.bind(this)
|
||
this.mindMap.command.add('REMOVE_CUSTOM_STYLES', this.removeCustomStyles)
|
||
// 一键去除所有节点自定义样式
|
||
this.removeAllNodeCustomStyles = this.removeAllNodeCustomStyles.bind(this)
|
||
this.mindMap.command.add(
|
||
'REMOVE_ALL_NODE_CUSTOM_STYLES',
|
||
this.removeAllNodeCustomStyles
|
||
)
|
||
}
|
||
|
||
// 注册快捷键
|
||
registerShortcutKeys() {
|
||
// 插入下级节点
|
||
this.mindMap.keyCommand.addShortcut('Tab', () => {
|
||
this.mindMap.execCommand('INSERT_CHILD_NODE')
|
||
})
|
||
// 插入下级节点
|
||
this.mindMap.keyCommand.addShortcut('Insert', () => {
|
||
this.mindMap.execCommand('INSERT_CHILD_NODE')
|
||
})
|
||
// 插入同级节点
|
||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||
this.mindMap.execCommand('INSERT_NODE')
|
||
})
|
||
// 插入父节点
|
||
this.mindMap.keyCommand.addShortcut('Shift+Tab', () => {
|
||
this.mindMap.execCommand('INSERT_PARENT_NODE')
|
||
})
|
||
// 插入概要
|
||
this.mindMap.keyCommand.addShortcut('Control+g', () => {
|
||
this.mindMap.execCommand('ADD_GENERALIZATION')
|
||
})
|
||
// 展开/收起节点
|
||
this.toggleActiveExpand = this.toggleActiveExpand.bind(this)
|
||
this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand)
|
||
// 删除节点
|
||
this.mindMap.keyCommand.addShortcut('Del|Backspace', () => {
|
||
this.mindMap.execCommand('REMOVE_NODE')
|
||
})
|
||
// 仅删除当前节点
|
||
this.mindMap.keyCommand.addShortcut('Shift+Backspace', () => {
|
||
this.mindMap.execCommand('REMOVE_CURRENT_NODE')
|
||
})
|
||
// 节点编辑时某些快捷键会存在冲突,需要暂时去除
|
||
this.mindMap.on('before_show_text_edit', () => {
|
||
this.startTextEdit()
|
||
})
|
||
this.mindMap.on('hide_text_edit', () => {
|
||
this.endTextEdit()
|
||
})
|
||
// 全选
|
||
this.mindMap.keyCommand.addShortcut('Control+a', () => {
|
||
this.mindMap.execCommand('SELECT_ALL')
|
||
})
|
||
// 一键整理布局
|
||
this.mindMap.keyCommand.addShortcut('Control+l', () => {
|
||
this.mindMap.execCommand('RESET_LAYOUT')
|
||
})
|
||
// 上移节点
|
||
this.mindMap.keyCommand.addShortcut('Control+Up', () => {
|
||
this.mindMap.execCommand('UP_NODE')
|
||
})
|
||
// 下移节点
|
||
this.mindMap.keyCommand.addShortcut('Control+Down', () => {
|
||
this.mindMap.execCommand('DOWN_NODE')
|
||
})
|
||
// 复制节点、
|
||
this.mindMap.keyCommand.addShortcut('Control+c', () => {
|
||
this.copy()
|
||
})
|
||
// 剪切节点
|
||
this.mindMap.keyCommand.addShortcut('Control+x', () => {
|
||
this.cut()
|
||
})
|
||
// 粘贴节点
|
||
this.mindMap.keyCommand.addShortcut('Control+v', () => {
|
||
this.paste()
|
||
})
|
||
// 根节点居中显示
|
||
this.mindMap.keyCommand.addShortcut('Control+Enter', () => {
|
||
this.setRootNodeCenter()
|
||
})
|
||
}
|
||
|
||
// 派发节点激活事件
|
||
emitNodeActiveEvent(node = null, activeNodeList = [...this.activeNodeList]) {
|
||
let isChange = false
|
||
isChange = this.lastActiveNode !== node
|
||
if (!isChange) {
|
||
isChange = !checkNodeListIsEqual(this.lastActiveNodeList, activeNodeList)
|
||
}
|
||
if (!isChange) return
|
||
this.lastActiveNode = node
|
||
this.lastActiveNodeList = [...activeNodeList]
|
||
this.mindMap.batchExecution.push('emitNodeActiveEvent', () => {
|
||
this.mindMap.emit('node_active', node, activeNodeList)
|
||
})
|
||
}
|
||
|
||
// 鼠标点击画布时清空当前激活节点列表
|
||
clearActiveNodeListOnDrawClick(e, eventType) {
|
||
if (this.activeNodeList.length <= 0) return
|
||
// 清除激活状态
|
||
let isTrueClick = true
|
||
// 是否是左键多选节点,右键拖动画布
|
||
const { useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
|
||
// 如果鼠标按下和松开的距离较大,则不认为是点击事件
|
||
if (
|
||
eventType === 'contextmenu'
|
||
? !useLeftKeySelectionRightKeyDrag
|
||
: useLeftKeySelectionRightKeyDrag
|
||
) {
|
||
const mousedownPos = this.mindMap.event.mousedownPos
|
||
isTrueClick =
|
||
Math.abs(e.clientX - mousedownPos.x) <= 5 &&
|
||
Math.abs(e.clientY - mousedownPos.y) <= 5
|
||
}
|
||
if (isTrueClick) {
|
||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||
}
|
||
}
|
||
|
||
// 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突
|
||
startTextEdit() {
|
||
this.mindMap.keyCommand.save()
|
||
}
|
||
|
||
// 结束文字编辑,会恢复回车键和删除键相关快捷键
|
||
endTextEdit() {
|
||
this.mindMap.keyCommand.restore()
|
||
}
|
||
|
||
// 清空节点缓存池
|
||
clearCache() {
|
||
this.layout.lru.clear()
|
||
this.nodeCache = {}
|
||
this.lastNodeCache = {}
|
||
}
|
||
|
||
// 渲染
|
||
render(callback = () => {}, source) {
|
||
// 切换主题时,被收起的节点需要添加样式复位的标注
|
||
if (source === CONSTANTS.CHANGE_THEME) {
|
||
this.resetUnExpandNodeStyle()
|
||
}
|
||
// 如果当前还没有渲染完毕,不再触发渲染
|
||
if (this.isRendering) {
|
||
// 等待当前渲染完毕后再进行一次渲染
|
||
this.hasWaitRendering = true
|
||
this.waitRenderingParams = [callback, source]
|
||
return
|
||
}
|
||
this.isRendering = true
|
||
// 触发当前重新渲染的来源
|
||
this.renderSource = source
|
||
// 节点缓存
|
||
this.lastNodeCache = this.nodeCache
|
||
this.nodeCache = {}
|
||
// 重新渲染需要清除激活状态
|
||
if (this.reRender) {
|
||
this.clearActiveNodeList()
|
||
}
|
||
// 如果没有节点数据
|
||
if (!this.renderTree) {
|
||
this.isRendering = false
|
||
this.mindMap.emit('node_tree_render_end')
|
||
return
|
||
}
|
||
this.mindMap.emit('node_tree_render_start')
|
||
// 计算布局
|
||
this.layout.doLayout(root => {
|
||
// 删除本次渲染时不再需要的节点
|
||
Object.keys(this.lastNodeCache).forEach(uid => {
|
||
if (!this.nodeCache[uid]) {
|
||
// 从激活节点列表里删除
|
||
this.removeNodeFromActiveList(this.lastNodeCache[uid])
|
||
this.emitNodeActiveEvent()
|
||
// 调用节点的销毁方法
|
||
this.lastNodeCache[uid].destroy()
|
||
}
|
||
})
|
||
// 更新根节点
|
||
this.root = root
|
||
// 渲染节点
|
||
this.root.render(() => {
|
||
this.isRendering = false
|
||
callback && callback()
|
||
if (this.hasWaitRendering) {
|
||
const params = this.waitRenderingParams
|
||
this.hasWaitRendering = false
|
||
this.waitRenderingParams = []
|
||
this.render(...params)
|
||
} else {
|
||
this.renderSource = ''
|
||
if (this.reRender) {
|
||
this.reRender = false
|
||
}
|
||
// 触发一次保存,因为修改了渲染树的数据
|
||
if (
|
||
this.mindMap.richText &&
|
||
[CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source)
|
||
) {
|
||
this.mindMap.command.addHistory()
|
||
}
|
||
}
|
||
this.mindMap.emit('node_tree_render_end')
|
||
})
|
||
})
|
||
this.emitNodeActiveEvent()
|
||
}
|
||
|
||
// 给当前被收起来的节点数据添加文本复位标志
|
||
resetUnExpandNodeStyle() {
|
||
if (!this.renderTree) return
|
||
walk(this.renderTree, null, node => {
|
||
if (!node.data.expand) {
|
||
walk(node, null, node2 => {
|
||
node2.data.resetRichText = true
|
||
})
|
||
return true
|
||
}
|
||
})
|
||
}
|
||
|
||
// 清除当前所有激活节点,并会触发事件
|
||
clearActiveNode() {
|
||
if (this.activeNodeList.length <= 0) {
|
||
return
|
||
}
|
||
this.clearActiveNodeList()
|
||
this.emitNodeActiveEvent(null, [])
|
||
}
|
||
|
||
// 清除当前激活的节点列表
|
||
clearActiveNodeList() {
|
||
this.activeNodeList.forEach(item => {
|
||
this.mindMap.execCommand('SET_NODE_ACTIVE', item, false)
|
||
})
|
||
this.activeNodeList = []
|
||
}
|
||
|
||
// 添加节点到激活列表里
|
||
addNodeToActiveList(node, notEmitBeforeNodeActiveEvent = false) {
|
||
if (
|
||
this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate &&
|
||
node.userList.length > 0
|
||
)
|
||
return
|
||
const index = this.findActiveNodeIndex(node)
|
||
if (index === -1) {
|
||
if (!notEmitBeforeNodeActiveEvent) {
|
||
this.mindMap.emit('before_node_active', node, this.activeNodeList)
|
||
}
|
||
this.mindMap.execCommand('SET_NODE_ACTIVE', node, true)
|
||
this.activeNodeList.push(node)
|
||
}
|
||
}
|
||
|
||
// 在激活列表里移除某个节点
|
||
removeNodeFromActiveList(node) {
|
||
let index = this.findActiveNodeIndex(node)
|
||
if (index === -1) {
|
||
return
|
||
}
|
||
this.mindMap.execCommand('SET_NODE_ACTIVE', node, false)
|
||
this.activeNodeList.splice(index, 1)
|
||
}
|
||
|
||
// 检索某个节点在激活列表里的索引
|
||
findActiveNodeIndex(node) {
|
||
return getNodeIndexInNodeList(node, this.activeNodeList)
|
||
}
|
||
|
||
// 全选
|
||
selectAll() {
|
||
if (this.mindMap.opt.readonly) return
|
||
walk(
|
||
this.root,
|
||
null,
|
||
node => {
|
||
if (!node.getData('isActive')) {
|
||
this.addNodeToActiveList(node)
|
||
}
|
||
},
|
||
null,
|
||
true,
|
||
0,
|
||
0
|
||
)
|
||
this.emitNodeActiveEvent()
|
||
}
|
||
|
||
// 回退
|
||
back(step) {
|
||
this.backForward('back', step)
|
||
}
|
||
|
||
// 前进
|
||
forward(step) {
|
||
this.backForward('forward', step)
|
||
}
|
||
|
||
// 前进回退
|
||
backForward(type, step) {
|
||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||
const data = this.mindMap.command[type](step)
|
||
if (data) {
|
||
this.renderTree = data
|
||
this.mindMap.render()
|
||
}
|
||
}
|
||
|
||
// 获取创建新节点的行为
|
||
getNewNodeBehavior(openEdit = false, handleMultiNodes = false) {
|
||
const { createNewNodeBehavior } = this.mindMap.opt
|
||
let focusNewNode = false // 是否激活新节点
|
||
let inserting = false // 新节点是否进入编辑模式
|
||
switch (createNewNodeBehavior) {
|
||
// 默认会激活新创建的节点,并且进入编辑模式。如果同时创建了多个新节点,那么只会激活而不会进入编辑模式
|
||
case CONSTANTS.CREATE_NEW_NODE_BEHAVIOR.DEFAULT:
|
||
focusNewNode = handleMultiNodes || !openEdit
|
||
inserting = handleMultiNodes ? false : openEdit // 如果同时对多个节点插入子节点,那么无需进入编辑模式
|
||
break
|
||
// 不激活新创建的节点
|
||
case CONSTANTS.CREATE_NEW_NODE_BEHAVIOR.NOT_ACTIVE:
|
||
focusNewNode = false
|
||
inserting = false
|
||
break
|
||
// 只激活新创建的节点,不进入编辑模式
|
||
case CONSTANTS.CREATE_NEW_NODE_BEHAVIOR.ACTIVE_ONLY:
|
||
focusNewNode = true
|
||
inserting = false
|
||
break
|
||
default:
|
||
break
|
||
}
|
||
return {
|
||
focusNewNode,
|
||
inserting
|
||
}
|
||
}
|
||
|
||
// 插入同级节点
|
||
insertNode(
|
||
openEdit = true,
|
||
appointNodes = [],
|
||
appointData = null,
|
||
appointChildren = []
|
||
) {
|
||
appointNodes = formatDataToArray(appointNodes)
|
||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||
return
|
||
}
|
||
this.textEdit.hideEditTextBox()
|
||
const {
|
||
defaultInsertSecondLevelNodeText,
|
||
defaultInsertBelowSecondLevelNodeText
|
||
} = this.mindMap.opt
|
||
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||
const handleMultiNodes = list.length > 1
|
||
const isRichText = !!this.mindMap.richText
|
||
const { focusNewNode, inserting } = this.getNewNodeBehavior(
|
||
openEdit,
|
||
handleMultiNodes
|
||
)
|
||
const params = {
|
||
expand: true,
|
||
richText: isRichText,
|
||
resetRichText: isRichText,
|
||
isActive: focusNewNode // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态
|
||
}
|
||
// 动态指定的子节点数据也需要添加相关属性
|
||
appointChildren = addDataToAppointNodes(appointChildren, {
|
||
...params
|
||
})
|
||
list.forEach(node => {
|
||
if (node.isGeneralization || node.isRoot) {
|
||
return
|
||
}
|
||
const parent = node.parent
|
||
const isOneLayer = node.layerIndex === 1
|
||
// 新插入节点的默认文本
|
||
const text = isOneLayer
|
||
? defaultInsertSecondLevelNodeText
|
||
: defaultInsertBelowSecondLevelNodeText
|
||
// 计算插入位置
|
||
const index = getNodeDataIndex(node)
|
||
const newNodeData = {
|
||
inserting,
|
||
data: {
|
||
text: text,
|
||
...params,
|
||
uid: createUid(),
|
||
...(appointData || {})
|
||
},
|
||
children: [...createUidForAppointNodes(appointChildren)]
|
||
}
|
||
parent.nodeData.children.splice(index + 1, 0, newNodeData)
|
||
})
|
||
// 如果同时对多个节点插入子节点,需要清除原来激活的节点
|
||
if (focusNewNode) {
|
||
this.clearActiveNodeList()
|
||
}
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 插入多个同级节点
|
||
insertMultiNode(appointNodes, nodeList) {
|
||
if (!nodeList || nodeList.length <= 0) return
|
||
appointNodes = formatDataToArray(appointNodes)
|
||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||
return
|
||
}
|
||
this.textEdit.hideEditTextBox()
|
||
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||
const isRichText = !!this.mindMap.richText
|
||
const { focusNewNode } = this.getNewNodeBehavior(false, true)
|
||
const params = {
|
||
expand: true,
|
||
richText: isRichText,
|
||
resetRichText: isRichText,
|
||
isActive: focusNewNode
|
||
}
|
||
nodeList = addDataToAppointNodes(nodeList, params)
|
||
list.forEach(node => {
|
||
if (node.isGeneralization || node.isRoot) {
|
||
return
|
||
}
|
||
const parent = node.parent
|
||
// 计算插入位置
|
||
const index = getNodeDataIndex(node)
|
||
const newNodeList = createUidForAppointNodes(simpleDeepClone(nodeList))
|
||
parent.nodeData.children.splice(index + 1, 0, ...newNodeList)
|
||
})
|
||
if (focusNewNode) {
|
||
this.clearActiveNodeList()
|
||
}
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 插入子节点
|
||
insertChildNode(
|
||
openEdit = true,
|
||
appointNodes = [],
|
||
appointData = null,
|
||
appointChildren = []
|
||
) {
|
||
appointNodes = formatDataToArray(appointNodes)
|
||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||
return
|
||
}
|
||
this.textEdit.hideEditTextBox()
|
||
const {
|
||
defaultInsertSecondLevelNodeText,
|
||
defaultInsertBelowSecondLevelNodeText
|
||
} = this.mindMap.opt
|
||
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||
const handleMultiNodes = list.length > 1
|
||
const isRichText = !!this.mindMap.richText
|
||
const { focusNewNode, inserting } = this.getNewNodeBehavior(
|
||
openEdit,
|
||
handleMultiNodes
|
||
)
|
||
const params = {
|
||
expand: true,
|
||
richText: isRichText,
|
||
resetRichText: isRichText,
|
||
isActive: focusNewNode
|
||
}
|
||
// 动态指定的子节点数据也需要添加相关属性
|
||
appointChildren = addDataToAppointNodes(appointChildren, {
|
||
...params
|
||
})
|
||
list.forEach(node => {
|
||
if (node.isGeneralization) {
|
||
return
|
||
}
|
||
if (!node.nodeData.children) {
|
||
node.nodeData.children = []
|
||
}
|
||
const text = node.isRoot
|
||
? defaultInsertSecondLevelNodeText
|
||
: defaultInsertBelowSecondLevelNodeText
|
||
const newNode = {
|
||
inserting,
|
||
data: {
|
||
text: text,
|
||
uid: createUid(),
|
||
...params,
|
||
...(appointData || {})
|
||
},
|
||
children: [...createUidForAppointNodes(appointChildren)]
|
||
}
|
||
node.nodeData.children.push(newNode)
|
||
// 插入子节点时自动展开子节点
|
||
node.setData({
|
||
expand: true
|
||
})
|
||
})
|
||
// 如果同时对多个节点插入子节点,需要清除原来激活的节点
|
||
if (focusNewNode) {
|
||
this.clearActiveNodeList()
|
||
}
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 插入多个子节点
|
||
insertMultiChildNode(appointNodes, childList) {
|
||
if (!childList || childList.length <= 0) return
|
||
appointNodes = formatDataToArray(appointNodes)
|
||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||
return
|
||
}
|
||
this.textEdit.hideEditTextBox()
|
||
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||
const isRichText = !!this.mindMap.richText
|
||
const { focusNewNode } = this.getNewNodeBehavior(false, true)
|
||
const params = {
|
||
expand: true,
|
||
richText: isRichText,
|
||
resetRichText: isRichText,
|
||
isActive: focusNewNode
|
||
}
|
||
childList = addDataToAppointNodes(childList, params)
|
||
list.forEach(node => {
|
||
if (node.isGeneralization) {
|
||
return
|
||
}
|
||
if (!node.nodeData.children) {
|
||
node.nodeData.children = []
|
||
}
|
||
childList = createUidForAppointNodes(childList)
|
||
node.nodeData.children.push(...childList)
|
||
// 插入子节点时自动展开子节点
|
||
node.setData({
|
||
expand: true
|
||
})
|
||
})
|
||
if (focusNewNode) {
|
||
this.clearActiveNodeList()
|
||
}
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 插入父节点
|
||
insertParentNode(openEdit = true, appointNodes, appointData) {
|
||
appointNodes = formatDataToArray(appointNodes)
|
||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||
return
|
||
}
|
||
this.textEdit.hideEditTextBox()
|
||
const {
|
||
defaultInsertSecondLevelNodeText,
|
||
defaultInsertBelowSecondLevelNodeText
|
||
} = this.mindMap.opt
|
||
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||
const handleMultiNodes = list.length > 1
|
||
const isRichText = !!this.mindMap.richText
|
||
const { focusNewNode, inserting } = this.getNewNodeBehavior(
|
||
openEdit,
|
||
handleMultiNodes
|
||
)
|
||
const params = {
|
||
expand: true,
|
||
richText: isRichText,
|
||
resetRichText: isRichText,
|
||
isActive: focusNewNode
|
||
}
|
||
list.forEach(node => {
|
||
if (node.isGeneralization || node.isRoot) {
|
||
return
|
||
}
|
||
const text =
|
||
node.layerIndex === 1
|
||
? defaultInsertSecondLevelNodeText
|
||
: defaultInsertBelowSecondLevelNodeText
|
||
const newNode = {
|
||
inserting,
|
||
data: {
|
||
text: text,
|
||
uid: createUid(),
|
||
...params,
|
||
...(appointData || {})
|
||
},
|
||
children: [node.nodeData]
|
||
}
|
||
node.setData({
|
||
resetRichText: true
|
||
})
|
||
const parent = node.parent
|
||
// 获取当前节点所在位置
|
||
const index = getNodeDataIndex(node)
|
||
parent.nodeData.children.splice(index, 1, newNode)
|
||
})
|
||
// 如果同时对多个节点插入子节点,需要清除原来激活的节点
|
||
if (focusNewNode) {
|
||
this.clearActiveNodeList()
|
||
}
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 上移节点,多个节点只会操作第一个节点
|
||
upNode() {
|
||
if (this.activeNodeList.length <= 0) {
|
||
return
|
||
}
|
||
let node = this.activeNodeList[0]
|
||
if (node.isRoot) {
|
||
return
|
||
}
|
||
let parent = node.parent
|
||
let childList = parent.children
|
||
let index = getNodeIndexInNodeList(node, childList)
|
||
if (index === -1 || index === 0) {
|
||
return
|
||
}
|
||
let insertIndex = index - 1
|
||
// 节点实例
|
||
childList.splice(index, 1)
|
||
childList.splice(insertIndex, 0, node)
|
||
// 节点数据
|
||
parent.nodeData.children.splice(index, 1)
|
||
parent.nodeData.children.splice(insertIndex, 0, node.nodeData)
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 下移节点,多个节点只会操作第一个节点
|
||
downNode() {
|
||
if (this.activeNodeList.length <= 0) {
|
||
return
|
||
}
|
||
let node = this.activeNodeList[0]
|
||
if (node.isRoot) {
|
||
return
|
||
}
|
||
let parent = node.parent
|
||
let childList = parent.children
|
||
let index = getNodeIndexInNodeList(node, childList)
|
||
if (index === -1 || index === childList.length - 1) {
|
||
return
|
||
}
|
||
let insertIndex = index + 1
|
||
// 节点实例
|
||
childList.splice(index, 1)
|
||
childList.splice(insertIndex, 0, node)
|
||
// 节点数据
|
||
parent.nodeData.children.splice(index, 1)
|
||
parent.nodeData.children.splice(insertIndex, 0, node.nodeData)
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 将节点上移一个层级,多个节点只会操作第一个节点
|
||
moveUpOneLevel(node) {
|
||
node = node || this.activeNodeList[0]
|
||
if (!node || node.isRoot || node.layerIndex <= 1) {
|
||
return
|
||
}
|
||
const parent = node.parent
|
||
const grandpa = parent.parent
|
||
const index = getNodeIndexInNodeList(node, parent.children)
|
||
const parentIndex = getNodeIndexInNodeList(parent, grandpa.children)
|
||
// 节点数据
|
||
this.checkNodeLayerChange(node, parent)
|
||
parent.nodeData.children.splice(index, 1)
|
||
grandpa.nodeData.children.splice(parentIndex + 1, 0, node.nodeData)
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 移除节点数据的自定义样式的内部方法
|
||
_handleRemoveCustomStyles(nodeData) {
|
||
let hasCustomStyles = false
|
||
Object.keys(nodeData).forEach(key => {
|
||
if (checkIsNodeStyleDataKey(key)) {
|
||
hasCustomStyles = true
|
||
delete nodeData[key]
|
||
}
|
||
})
|
||
// 如果是富文本,那么还要处理富文本内容
|
||
if (hasCustomStyles && this.mindMap.richText) {
|
||
nodeData.resetRichText = true
|
||
nodeData.text = removeRichTextStyes(nodeData.text)
|
||
}
|
||
return hasCustomStyles
|
||
}
|
||
|
||
// 一键去除自定义样式
|
||
removeCustomStyles(node) {
|
||
node = node || this.activeNodeList[0]
|
||
if (!node) {
|
||
return
|
||
}
|
||
const hasCustomStyles = this._handleRemoveCustomStyles(node.getData())
|
||
if (hasCustomStyles) {
|
||
this.reRenderNodeCheckChange(node)
|
||
}
|
||
}
|
||
|
||
// 一键去除所有节点自定义样式
|
||
removeAllNodeCustomStyles(appointNodes) {
|
||
appointNodes = formatDataToArray(appointNodes)
|
||
let hasCustomStyles = false
|
||
// 指定了节点列表,那么遍历该节点列表
|
||
if (appointNodes.length > 0) {
|
||
appointNodes.forEach(node => {
|
||
const _hasCustomStyles = this._handleRemoveCustomStyles(node.getData())
|
||
if (_hasCustomStyles) hasCustomStyles = true
|
||
})
|
||
} else {
|
||
// 否则遍历整棵树
|
||
if (!this.renderTree) return
|
||
walk(this.renderTree, null, node => {
|
||
const _hasCustomStyles = this._handleRemoveCustomStyles(node.data)
|
||
if (_hasCustomStyles) hasCustomStyles = true
|
||
// 不要忘记概要节点
|
||
const generalizationList = formatGetNodeGeneralization(node.data)
|
||
if (generalizationList.length > 0) {
|
||
generalizationList.forEach(generalizationData => {
|
||
const _hasCustomStyles =
|
||
this._handleRemoveCustomStyles(generalizationData)
|
||
if (_hasCustomStyles) hasCustomStyles = true
|
||
})
|
||
}
|
||
})
|
||
}
|
||
if (hasCustomStyles) {
|
||
this.mindMap.reRender()
|
||
}
|
||
}
|
||
|
||
// 复制节点
|
||
copy() {
|
||
this.beingCopyData = this.copyNode()
|
||
if (!this.beingCopyData) return
|
||
setDataToClipboard(createSmmFormatData(this.beingCopyData))
|
||
}
|
||
|
||
// 剪切节点
|
||
cut() {
|
||
this.mindMap.execCommand('CUT_NODE', copyData => {
|
||
this.beingCopyData = copyData
|
||
setDataToClipboard(createSmmFormatData(copyData))
|
||
})
|
||
}
|
||
|
||
// 粘贴
|
||
async paste() {
|
||
const {
|
||
errorHandler,
|
||
handleIsSplitByWrapOnPasteCreateNewNode,
|
||
handleNodePasteImg
|
||
} = this.mindMap.opt
|
||
// 读取剪贴板的文字和图片
|
||
let text = null
|
||
let img = null
|
||
try {
|
||
const res = await getDataFromClipboard()
|
||
text = res.text
|
||
img = res.img
|
||
} catch (error) {
|
||
errorHandler(ERROR_TYPES.READ_CLIPBOARD_ERROR, error)
|
||
}
|
||
// 检查剪切板数据是否有变化
|
||
// 通过图片大小来判断图片是否发生变化,可能是不准确的,但是目前没有其他好方法
|
||
const imgSize = img ? img.size : 0
|
||
if (this.beingPasteText !== text || this.beingPasteImgSize !== imgSize) {
|
||
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CLIP_BOARD
|
||
this.beingPasteText = text
|
||
this.beingPasteImgSize = imgSize
|
||
}
|
||
// 检查要粘贴的节点数据是否有变化,节点优先级高于剪切板
|
||
if (this.lastBeingCopyData !== this.beingCopyData) {
|
||
this.lastBeingCopyData = this.beingCopyData
|
||
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CANVAS
|
||
}
|
||
// 粘贴剪切板的数据
|
||
if (this.currentBeingPasteType === CONSTANTS.PASTE_TYPE.CLIP_BOARD) {
|
||
// 存在文本,则创建子节点
|
||
if (text) {
|
||
// 判断粘贴的是否是simple-mind-map的数据
|
||
let smmData = null
|
||
let useDefault = true
|
||
// 用户自定义处理
|
||
if (this.mindMap.opt.customHandleClipboardText) {
|
||
try {
|
||
const res = await this.mindMap.opt.customHandleClipboardText(text)
|
||
if (!isUndef(res)) {
|
||
useDefault = false
|
||
const checkRes = checkSmmFormatData(res)
|
||
if (checkRes.isSmm) {
|
||
smmData = checkRes.data
|
||
} else {
|
||
text = checkRes.data
|
||
}
|
||
}
|
||
} catch (error) {
|
||
errorHandler(ERROR_TYPES.CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR, error)
|
||
}
|
||
}
|
||
// 默认处理
|
||
if (useDefault) {
|
||
const checkRes = checkSmmFormatData(text)
|
||
if (checkRes.isSmm) {
|
||
smmData = checkRes.data
|
||
} else {
|
||
text = checkRes.data
|
||
}
|
||
}
|
||
if (smmData) {
|
||
this.mindMap.execCommand(
|
||
'INSERT_MULTI_CHILD_NODE',
|
||
[],
|
||
Array.isArray(smmData) ? smmData : [smmData]
|
||
)
|
||
} else {
|
||
text = htmlEscape(text)
|
||
const textArr = text
|
||
.split(new RegExp('\r?\n|(?<!\n)\r', 'g'))
|
||
.filter(item => {
|
||
return !!item
|
||
})
|
||
// 判断是否需要根据换行自动分割节点
|
||
if (textArr.length > 1 && handleIsSplitByWrapOnPasteCreateNewNode) {
|
||
handleIsSplitByWrapOnPasteCreateNewNode()
|
||
.then(() => {
|
||
this.mindMap.execCommand(
|
||
'INSERT_MULTI_CHILD_NODE',
|
||
[],
|
||
textArr.map(item => {
|
||
return {
|
||
data: {
|
||
text: item
|
||
},
|
||
children: []
|
||
}
|
||
})
|
||
)
|
||
})
|
||
.catch(() => {
|
||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||
text
|
||
})
|
||
})
|
||
} else {
|
||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||
text
|
||
})
|
||
}
|
||
}
|
||
}
|
||
// 存在图片,则添加到当前激活节点
|
||
if (img) {
|
||
try {
|
||
let imgData = null
|
||
// 自定义图片处理函数
|
||
if (handleNodePasteImg && typeof handleNodePasteImg === 'function') {
|
||
imgData = await handleNodePasteImg(img)
|
||
} else {
|
||
imgData = await loadImage(img)
|
||
}
|
||
if (this.activeNodeList.length > 0) {
|
||
this.activeNodeList.forEach(node => {
|
||
this.mindMap.execCommand('SET_NODE_IMAGE', node, {
|
||
url: imgData.url,
|
||
title: '',
|
||
width: imgData.size.width,
|
||
height: imgData.size.height
|
||
})
|
||
})
|
||
}
|
||
} catch (error) {
|
||
errorHandler(ERROR_TYPES.LOAD_CLIPBOARD_IMAGE_ERROR, error)
|
||
}
|
||
}
|
||
} else {
|
||
// 粘贴节点数据
|
||
if (this.beingCopyData) {
|
||
this.mindMap.execCommand('PASTE_NODE', this.beingCopyData)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 将节点移动到另一个节点的前面
|
||
insertBefore(node, exist) {
|
||
this.insertTo(node, exist, 'before')
|
||
}
|
||
|
||
// 将节点移动到另一个节点的后面
|
||
insertAfter(node, exist) {
|
||
this.insertTo(node, exist, 'after')
|
||
}
|
||
|
||
// 将节点移动到另一个节点的前面或后面
|
||
insertTo(node, exist, dir = 'before') {
|
||
let nodeList = formatDataToArray(node)
|
||
nodeList = nodeList.filter(item => {
|
||
return !item.isRoot
|
||
})
|
||
if (dir === 'after') {
|
||
nodeList.reverse()
|
||
}
|
||
nodeList.forEach(item => {
|
||
this.checkNodeLayerChange(item, exist)
|
||
// 移动节点
|
||
let nodeParent = item.parent
|
||
let nodeBorthers = nodeParent.children
|
||
let nodeIndex = getNodeIndexInNodeList(item, nodeBorthers)
|
||
if (nodeIndex === -1) {
|
||
return
|
||
}
|
||
nodeBorthers.splice(nodeIndex, 1)
|
||
nodeParent.nodeData.children.splice(nodeIndex, 1)
|
||
|
||
// 目标节点
|
||
let existParent = exist.parent
|
||
let existBorthers = existParent.children
|
||
let existIndex = getNodeIndexInNodeList(exist, existBorthers)
|
||
if (existIndex === -1) {
|
||
return
|
||
}
|
||
if (dir === 'after') {
|
||
existIndex++
|
||
}
|
||
existBorthers.splice(existIndex, 0, item)
|
||
existParent.nodeData.children.splice(existIndex, 0, item.nodeData)
|
||
})
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 如果是富文本模式,那么某些层级变化需要更新样式
|
||
checkNodeLayerChange(node, toNode, toNodeIsParent = false) {
|
||
if (this.mindMap.richText) {
|
||
const toIndex = toNodeIsParent ? toNode.layerIndex + 1 : toNode.layerIndex
|
||
let nodeLayerChanged =
|
||
(node.layerIndex === 1 && toIndex !== 1) ||
|
||
(node.layerIndex !== 1 && toIndex === 1)
|
||
if (nodeLayerChanged) {
|
||
node.setData({
|
||
resetRichText: true
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 移除节点
|
||
removeNode(appointNodes = []) {
|
||
appointNodes = formatDataToArray(appointNodes)
|
||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||
return
|
||
}
|
||
// 删除节点后需要激活的节点
|
||
let needActiveNode = null
|
||
let isAppointNodes = appointNodes.length > 0
|
||
let list = isAppointNodes ? appointNodes : this.activeNodeList
|
||
let root = list.find(node => {
|
||
return node.isRoot
|
||
})
|
||
if (root) {
|
||
this.clearActiveNodeList()
|
||
root.children = []
|
||
root.nodeData.children = []
|
||
} else {
|
||
// 如果只选中了一个节点,删除后激活其兄弟节点或者父节点
|
||
needActiveNode = this.getNextActiveNode(list)
|
||
for (let i = 0; i < list.length; i++) {
|
||
const node = list[i]
|
||
const currentEditNode = this.textEdit.getCurrentEditNode()
|
||
if (
|
||
currentEditNode &&
|
||
currentEditNode.getData('uid') === node.getData('uid')
|
||
) {
|
||
// 如果当前节点正在编辑中,那么先完成编辑
|
||
this.textEdit.hideEditTextBox()
|
||
}
|
||
if (isAppointNodes) list.splice(i, 1)
|
||
if (node.isGeneralization) {
|
||
this.deleteNodeGeneralization(node)
|
||
this.removeNodeFromActiveList(node)
|
||
i--
|
||
} else {
|
||
this.removeNodeFromActiveList(node)
|
||
removeFromParentNodeData(node)
|
||
i--
|
||
}
|
||
}
|
||
}
|
||
this.activeNodeList = []
|
||
// 激活被删除节点的兄弟节点或父节点
|
||
if (needActiveNode) {
|
||
this.addNodeToActiveList(needActiveNode)
|
||
}
|
||
this.emitNodeActiveEvent()
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 删除概要节点,即从所属节点里删除该概要
|
||
deleteNodeGeneralization(node) {
|
||
const targetNode = node.generalizationBelongNode
|
||
const index = targetNode.getGeneralizationNodeIndex(node)
|
||
let generalization = targetNode.getData('generalization')
|
||
if (Array.isArray(generalization)) {
|
||
generalization.splice(index, 1)
|
||
} else {
|
||
generalization = null
|
||
}
|
||
// 删除概要节点
|
||
this.mindMap.execCommand('SET_NODE_DATA', targetNode, {
|
||
generalization
|
||
})
|
||
this.closeHighlightNode()
|
||
}
|
||
|
||
// 仅删除当前节点
|
||
removeCurrentNode(appointNodes = []) {
|
||
appointNodes = formatDataToArray(appointNodes)
|
||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||
return
|
||
}
|
||
let isAppointNodes = appointNodes.length > 0
|
||
let list = isAppointNodes ? appointNodes : this.activeNodeList
|
||
list = list.filter(node => {
|
||
return !node.isRoot
|
||
})
|
||
// 删除节点后需要激活的节点,如果只选中了一个节点,删除后激活其兄弟节点或者父节点
|
||
let needActiveNode = this.getNextActiveNode(list)
|
||
for (let i = 0; i < list.length; i++) {
|
||
let node = list[i]
|
||
if (node.isGeneralization) {
|
||
// 删除概要节点
|
||
this.deleteNodeGeneralization(node)
|
||
} else {
|
||
const parent = node.parent
|
||
const index = getNodeDataIndex(node)
|
||
parent.nodeData.children.splice(
|
||
index,
|
||
1,
|
||
...(node.nodeData.children || [])
|
||
)
|
||
}
|
||
}
|
||
this.activeNodeList = []
|
||
// 激活被删除节点的兄弟节点或父节点
|
||
if (needActiveNode) {
|
||
this.addNodeToActiveList(needActiveNode)
|
||
}
|
||
this.emitNodeActiveEvent()
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 计算下一个可激活的节点
|
||
getNextActiveNode(deleteList) {
|
||
// 删除多个节点不自动激活相邻节点
|
||
if (deleteList.length !== 1) return null
|
||
// 被删除的节点不在当前激活的节点列表里,不激活相邻节点
|
||
if (this.findActiveNodeIndex(deleteList[0]) === -1) return null
|
||
let needActiveNode = null
|
||
if (
|
||
this.activeNodeList.length === 1 &&
|
||
!this.activeNodeList[0].isGeneralization &&
|
||
this.mindMap.opt.deleteNodeActive
|
||
) {
|
||
const node = this.activeNodeList[0]
|
||
const broList = node.parent.children
|
||
const nodeIndex = getNodeIndexInNodeList(node, broList)
|
||
// 如果后面有兄弟节点
|
||
if (nodeIndex < broList.length - 1) {
|
||
needActiveNode = broList[nodeIndex + 1]
|
||
} else {
|
||
// 如果前面有兄弟节点
|
||
if (nodeIndex > 0) {
|
||
needActiveNode = broList[nodeIndex - 1]
|
||
} else {
|
||
// 没有兄弟节点
|
||
needActiveNode = node.parent
|
||
}
|
||
}
|
||
}
|
||
return needActiveNode
|
||
}
|
||
|
||
// 复制节点
|
||
copyNode() {
|
||
if (this.activeNodeList.length <= 0) {
|
||
return null
|
||
}
|
||
const nodeList = getTopAncestorsFomNodeList(this.activeNodeList)
|
||
return nodeList.map(node => {
|
||
return copyNodeTree({}, node, true)
|
||
})
|
||
}
|
||
|
||
// 剪切节点
|
||
cutNode(callback) {
|
||
if (this.activeNodeList.length <= 0) {
|
||
return
|
||
}
|
||
// 找出激活节点中的顶层节点列表,并过滤掉根节点
|
||
const nodeList = getTopAncestorsFomNodeList(this.activeNodeList).filter(
|
||
node => {
|
||
return !node.isRoot
|
||
}
|
||
)
|
||
// 复制数据
|
||
const copyData = nodeList.map(node => {
|
||
return copyNodeTree({}, node, true)
|
||
})
|
||
// 从父节点的数据中移除
|
||
nodeList.forEach(node => {
|
||
removeFromParentNodeData(node)
|
||
})
|
||
// 清空激活节点列表
|
||
this.clearActiveNodeList()
|
||
this.mindMap.render()
|
||
if (callback && typeof callback === 'function') {
|
||
callback(copyData)
|
||
}
|
||
}
|
||
|
||
// 移动节点作为另一个节点的子节点
|
||
moveNodeTo(node, toNode) {
|
||
let nodeList = formatDataToArray(node)
|
||
nodeList = nodeList.filter(item => {
|
||
return !item.isRoot
|
||
})
|
||
nodeList.forEach(item => {
|
||
this.checkNodeLayerChange(item, toNode, true)
|
||
this.removeNodeFromActiveList(item)
|
||
removeFromParentNodeData(item)
|
||
toNode.nodeData.children.push(item.nodeData)
|
||
})
|
||
this.emitNodeActiveEvent()
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 粘贴节点到节点
|
||
pasteNode(data) {
|
||
data = formatDataToArray(data)
|
||
if (this.activeNodeList.length <= 0 || data.length <= 0) {
|
||
return
|
||
}
|
||
this.activeNodeList.forEach(node => {
|
||
node.nodeData.children.push(
|
||
...data.map(item => {
|
||
const newData = simpleDeepClone(item)
|
||
createUidForAppointNodes([newData], true)
|
||
return newData
|
||
})
|
||
)
|
||
})
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 设置节点样式
|
||
setNodeStyle(node, prop, value) {
|
||
let data = {
|
||
[prop]: value
|
||
}
|
||
// 如果开启了富文本,则需要应用到富文本上
|
||
if (this.mindMap.richText) {
|
||
this.mindMap.richText.setNotActiveNodeStyle(node, {
|
||
[prop]: value
|
||
})
|
||
}
|
||
this.setNodeDataRender(node, data)
|
||
// 更新了连线的样式
|
||
if (lineStyleProps.includes(prop)) {
|
||
;(node.parent || node).renderLine(true)
|
||
}
|
||
}
|
||
|
||
// 设置节点多个样式
|
||
setNodeStyles(node, style) {
|
||
let data = { ...style }
|
||
// 如果开启了富文本,则需要应用到富文本上
|
||
if (this.mindMap.richText) {
|
||
this.mindMap.richText.setNotActiveNodeStyle(node, style)
|
||
}
|
||
this.setNodeDataRender(node, data)
|
||
// 更新了连线的样式
|
||
let props = Object.keys(style)
|
||
let hasLineStyleProps = false
|
||
props.forEach(key => {
|
||
if (lineStyleProps.includes(key)) {
|
||
hasLineStyleProps = true
|
||
}
|
||
})
|
||
if (hasLineStyleProps) {
|
||
;(node.parent || node).renderLine(true)
|
||
}
|
||
}
|
||
|
||
// 设置节点是否激活
|
||
setNodeActive(node, active) {
|
||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||
isActive: active
|
||
})
|
||
node.updateNodeByActive(active)
|
||
}
|
||
|
||
// 设置节点是否展开
|
||
setNodeExpand(node, expand) {
|
||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||
expand
|
||
})
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 展开所有
|
||
expandAllNode() {
|
||
if (!this.renderTree) return
|
||
walk(
|
||
this.renderTree,
|
||
null,
|
||
node => {
|
||
if (!node.data.expand) {
|
||
node.data.expand = true
|
||
}
|
||
},
|
||
null,
|
||
true,
|
||
0,
|
||
0
|
||
)
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 收起所有
|
||
unexpandAllNode(isSetRootNodeCenter = true) {
|
||
if (!this.renderTree) return
|
||
walk(
|
||
this.renderTree,
|
||
null,
|
||
(node, parent, isRoot) => {
|
||
if (!isRoot && node.children && node.children.length > 0) {
|
||
node.data.expand = false
|
||
}
|
||
},
|
||
null,
|
||
true,
|
||
0,
|
||
0
|
||
)
|
||
this.mindMap.render(() => {
|
||
if (isSetRootNodeCenter) {
|
||
this.setRootNodeCenter()
|
||
}
|
||
})
|
||
}
|
||
|
||
// 展开到指定层级
|
||
expandToLevel(level) {
|
||
if (!this.renderTree) return
|
||
walk(
|
||
this.renderTree,
|
||
null,
|
||
(node, parent, isRoot, layerIndex) => {
|
||
const expand = layerIndex < level
|
||
if (expand) {
|
||
node.data.expand = true
|
||
} else if (!isRoot && node.children && node.children.length > 0) {
|
||
node.data.expand = false
|
||
}
|
||
},
|
||
null,
|
||
true,
|
||
0,
|
||
0
|
||
)
|
||
this.mindMap.render()
|
||
}
|
||
|
||
// 切换激活节点的展开状态
|
||
toggleActiveExpand() {
|
||
this.activeNodeList.forEach(node => {
|
||
if (node.nodeData.children.length <= 0) {
|
||
return
|
||
}
|
||
this.toggleNodeExpand(node)
|
||
})
|
||
}
|
||
|
||
// 切换节点展开状态
|
||
toggleNodeExpand(node) {
|
||
this.mindMap.execCommand('SET_NODE_EXPAND', node, !node.getData('expand'))
|
||
}
|
||
|
||
// 设置节点文本
|
||
setNodeText(node, text, richText, resetRichText) {
|
||
richText = richText === undefined ? node.getData('richText') : richText
|
||
this.setNodeDataRender(node, {
|
||
text,
|
||
richText,
|
||
resetRichText
|
||
})
|
||
}
|
||
|
||
// 设置节点图片
|
||
setNodeImage(node, data) {
|
||
const {
|
||
url,
|
||
title,
|
||
width,
|
||
height,
|
||
custom = false
|
||
} = data || { url: '', title: '', width: 0, height: 0, custom: false }
|
||
this.setNodeDataRender(node, {
|
||
image: url,
|
||
imageTitle: title || '',
|
||
imageSize: {
|
||
width,
|
||
height,
|
||
custom
|
||
}
|
||
})
|
||
}
|
||
|
||
// 设置节点图标
|
||
setNodeIcon(node, icons) {
|
||
this.setNodeDataRender(node, {
|
||
icon: icons
|
||
})
|
||
}
|
||
|
||
// 设置节点超链接
|
||
setNodeHyperlink(node, link, title = '') {
|
||
this.setNodeDataRender(node, {
|
||
hyperlink: link,
|
||
hyperlinkTitle: title
|
||
})
|
||
}
|
||
|
||
// 设置节点备注
|
||
setNodeNote(node, note) {
|
||
this.setNodeDataRender(node, {
|
||
note
|
||
})
|
||
}
|
||
|
||
// 设置节点附件
|
||
setNodeAttachment(node, url, name = '') {
|
||
this.setNodeDataRender(node, {
|
||
attachmentUrl: url,
|
||
attachmentName: name
|
||
})
|
||
}
|
||
|
||
// 设置节点标签
|
||
setNodeTag(node, tag) {
|
||
this.setNodeDataRender(node, {
|
||
tag
|
||
})
|
||
}
|
||
|
||
// 设置节点公式
|
||
insertFormula(formula, appointNodes = []) {
|
||
// 只在富文本模式下可用,并且需要注册Formula插件
|
||
if (!this.mindMap.richText || !this.mindMap.formula) return
|
||
appointNodes = formatDataToArray(appointNodes)
|
||
const list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||
list.forEach(node => {
|
||
this.mindMap.formula.insertFormulaToNode(node, formula)
|
||
})
|
||
}
|
||
|
||
// 添加节点概要
|
||
addGeneralization(data, openEdit = true) {
|
||
if (this.activeNodeList.length <= 0) {
|
||
return
|
||
}
|
||
const nodeList = this.activeNodeList.filter(node => {
|
||
return (
|
||
!node.isRoot &&
|
||
!node.isGeneralization &&
|
||
!node.checkHasSelfGeneralization()
|
||
)
|
||
})
|
||
const list = parseAddGeneralizationNodeList(nodeList)
|
||
const isRichText = !!this.mindMap.richText
|
||
const { focusNewNode, inserting } = this.getNewNodeBehavior(
|
||
openEdit,
|
||
list.length > 1
|
||
)
|
||
list.forEach(item => {
|
||
const newData = {
|
||
inserting,
|
||
...(data || {
|
||
text: this.mindMap.opt.defaultGeneralizationText
|
||
}),
|
||
range: item.range || null,
|
||
uid: createUid(),
|
||
richText: isRichText,
|
||
resetRichText: isRichText,
|
||
isActive: focusNewNode
|
||
}
|
||
let generalization = item.node.getData('generalization')
|
||
if (generalization) {
|
||
if (Array.isArray(generalization)) {
|
||
generalization.push(newData)
|
||
} else {
|
||
generalization = [generalization, newData]
|
||
}
|
||
} else {
|
||
generalization = [newData]
|
||
}
|
||
this.mindMap.execCommand('SET_NODE_DATA', item.node, {
|
||
generalization
|
||
})
|
||
// 插入子节点时自动展开子节点
|
||
item.node.setData({
|
||
expand: true
|
||
})
|
||
})
|
||
// 需要清除原来激活的节点
|
||
if (focusNewNode) {
|
||
this.clearActiveNodeList()
|
||
}
|
||
this.mindMap.render(() => {
|
||
// 修复祖先节点存在概要时位置未更新的问题
|
||
// 修复同时给存在上下级关系的节点添加概要时重叠的问题
|
||
this.mindMap.render()
|
||
})
|
||
}
|
||
|
||
// 删除节点概要
|
||
removeGeneralization() {
|
||
if (this.activeNodeList.length <= 0) {
|
||
return
|
||
}
|
||
this.activeNodeList.forEach(node => {
|
||
if (!node.checkHasGeneralization()) {
|
||
return
|
||
}
|
||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||
generalization: null
|
||
})
|
||
})
|
||
this.mindMap.render()
|
||
this.closeHighlightNode()
|
||
}
|
||
|
||
// 设置节点自定义位置
|
||
setNodeCustomPosition(node, left = undefined, top = undefined) {
|
||
let nodeList = [node] || this.activeNodeList
|
||
nodeList.forEach(item => {
|
||
this.mindMap.execCommand('SET_NODE_DATA', item, {
|
||
customLeft: left,
|
||
customTop: top
|
||
})
|
||
})
|
||
}
|
||
|
||
// 一键整理布局,即去除自定义位置
|
||
resetLayout() {
|
||
walk(
|
||
this.root,
|
||
null,
|
||
node => {
|
||
node.customLeft = undefined
|
||
node.customTop = undefined
|
||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||
customLeft: undefined,
|
||
customTop: undefined
|
||
})
|
||
this.mindMap.render()
|
||
},
|
||
null,
|
||
true,
|
||
0,
|
||
0
|
||
)
|
||
}
|
||
|
||
// 设置节点形状
|
||
setNodeShape(node, shape) {
|
||
if (!shape || !shapeList.includes(shape)) {
|
||
return
|
||
}
|
||
let nodeList = [node] || this.activeNodeList
|
||
nodeList.forEach(item => {
|
||
this.setNodeStyle(item, 'shape', shape)
|
||
})
|
||
}
|
||
|
||
// 定位到指定节点
|
||
goTargetNode(node, callback = () => {}) {
|
||
let uid = typeof node === 'string' ? node : node.getData('uid')
|
||
if (!uid) return
|
||
this.expandToNodeUid(uid, () => {
|
||
let targetNode = this.findNodeByUid(uid)
|
||
if (targetNode) {
|
||
targetNode.active()
|
||
this.moveNodeToCenter(targetNode)
|
||
callback(targetNode)
|
||
}
|
||
})
|
||
}
|
||
|
||
// 更新节点数据
|
||
setNodeData(node, data) {
|
||
Object.keys(data).forEach(key => {
|
||
node.nodeData.data[key] = data[key]
|
||
})
|
||
}
|
||
|
||
// 设置节点数据,并判断是否渲染
|
||
setNodeDataRender(node, data, notRender = false) {
|
||
this.mindMap.execCommand('SET_NODE_DATA', node, data)
|
||
this.reRenderNodeCheckChange(node, notRender)
|
||
}
|
||
|
||
// 重新节点某个节点,判断节点大小是否发生了改变,是的话触发重绘
|
||
reRenderNodeCheckChange(node, notRender) {
|
||
let changed = node.reRender()
|
||
if (changed) {
|
||
if (!notRender) this.mindMap.render()
|
||
} else {
|
||
this.mindMap.emit('node_tree_render_end')
|
||
}
|
||
}
|
||
|
||
// 移动节点到画布中心
|
||
moveNodeToCenter(node) {
|
||
const { resetScaleOnMoveNodeToCenter } = this.mindMap.opt
|
||
let { transform, state } = this.mindMap.view.getTransformData()
|
||
let { left, top, width, height } = node
|
||
if (!resetScaleOnMoveNodeToCenter) {
|
||
left *= transform.scaleX
|
||
top *= transform.scaleY
|
||
width *= transform.scaleX
|
||
height *= transform.scaleY
|
||
}
|
||
let halfWidth = this.mindMap.width / 2
|
||
let halfHeight = this.mindMap.height / 2
|
||
let nodeCenterX = left + width / 2
|
||
let nodeCenterY = top + height / 2
|
||
let targetX = halfWidth - state.x
|
||
let targetY = halfHeight - state.y
|
||
let offsetX = targetX - nodeCenterX
|
||
let offsetY = targetY - nodeCenterY
|
||
this.mindMap.view.translateX(offsetX)
|
||
this.mindMap.view.translateY(offsetY)
|
||
if (resetScaleOnMoveNodeToCenter) {
|
||
this.mindMap.view.setScale(1)
|
||
}
|
||
}
|
||
|
||
// 回到中心主题,即设置根节点到画布中心
|
||
setRootNodeCenter() {
|
||
this.moveNodeToCenter(this.root)
|
||
}
|
||
|
||
// 展开到指定uid的节点
|
||
expandToNodeUid(uid, callback = () => {}) {
|
||
if (!this.renderTree) {
|
||
callback()
|
||
return
|
||
}
|
||
let parentsList = []
|
||
let isGeneralization = false
|
||
const cache = {}
|
||
bfsWalk(this.renderTree, (node, parent) => {
|
||
if (node.data.uid === uid) {
|
||
parentsList = parent ? [...cache[parent.data.uid], parent] : []
|
||
return 'stop'
|
||
}
|
||
const generalizationList = formatGetNodeGeneralization(node.data)
|
||
generalizationList.forEach(item => {
|
||
if (item.uid === uid) {
|
||
parentsList = parent ? [...cache[parent.data.uid], parent] : []
|
||
isGeneralization = true
|
||
}
|
||
})
|
||
if (isGeneralization) {
|
||
return 'stop'
|
||
}
|
||
cache[node.data.uid] = parent ? [...cache[parent.data.uid], parent] : []
|
||
})
|
||
let needRender = false
|
||
parentsList.forEach(node => {
|
||
if (!node.data.expand) {
|
||
needRender = true
|
||
node.data.expand = true
|
||
}
|
||
})
|
||
// 如果是展开到概要节点,那么父节点下的所有节点都需要开
|
||
if (isGeneralization) {
|
||
const lastNode = parentsList[parentsList.length - 1]
|
||
if (lastNode) {
|
||
walk(lastNode, null, node => {
|
||
if (!node.data.expand) {
|
||
needRender = true
|
||
node.data.expand = true
|
||
}
|
||
})
|
||
}
|
||
}
|
||
if (needRender) {
|
||
this.mindMap.render(callback)
|
||
} else {
|
||
callback()
|
||
}
|
||
}
|
||
|
||
// 根据uid找到对应的节点实例
|
||
findNodeByUid(uid) {
|
||
let res = null
|
||
walk(this.root, null, node => {
|
||
if (node.getData('uid') === uid) {
|
||
res = node
|
||
return true
|
||
}
|
||
// 概要节点
|
||
let isGeneralization = false
|
||
;(node._generalizationList || []).forEach(item => {
|
||
if (item.generalizationNode.getData('uid') === uid) {
|
||
res = item.generalizationNode
|
||
isGeneralization = true
|
||
}
|
||
})
|
||
if (isGeneralization) {
|
||
return true
|
||
}
|
||
})
|
||
return res
|
||
}
|
||
|
||
// 高亮节点或子节点
|
||
highlightNode(node, range) {
|
||
// 如果当前正在渲染,那么不进行高亮,因为节点位置可能不正确
|
||
if (this.isRendering) return
|
||
const { highlightNodeBoxStyle = {} } = this.mindMap.opt
|
||
if (!this.highlightBoxNode) {
|
||
this.highlightBoxNode = new Polygon()
|
||
.stroke({
|
||
color: highlightNodeBoxStyle.stroke || 'transparent'
|
||
})
|
||
.fill({
|
||
color: highlightNodeBoxStyle.fill || 'transparent'
|
||
})
|
||
}
|
||
let minx = Infinity,
|
||
miny = Infinity,
|
||
maxx = -Infinity,
|
||
maxy = -Infinity
|
||
if (range) {
|
||
const children = node.children.slice(range[0], range[1] + 1)
|
||
children.forEach(child => {
|
||
if (child.left < minx) {
|
||
minx = child.left
|
||
}
|
||
if (child.top < miny) {
|
||
miny = child.top
|
||
}
|
||
const right = child.left + child.width
|
||
const bottom = child.top + child.height
|
||
if (right > maxx) {
|
||
maxx = right
|
||
}
|
||
if (bottom > maxy) {
|
||
maxy = bottom
|
||
}
|
||
})
|
||
} else {
|
||
minx = node.left
|
||
miny = node.top
|
||
maxx = node.left + node.width
|
||
maxy = node.top + node.height
|
||
}
|
||
this.highlightBoxNode.plot([
|
||
[minx, miny],
|
||
[maxx, miny],
|
||
[maxx, maxy],
|
||
[minx, maxy]
|
||
])
|
||
this.mindMap.otherDraw.add(this.highlightBoxNode)
|
||
}
|
||
|
||
// 关闭高亮
|
||
closeHighlightNode() {
|
||
this.highlightBoxNode.remove()
|
||
}
|
||
}
|
||
|
||
export default Render
|