mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-21 18:37:43 +08:00
407 lines
11 KiB
JavaScript
407 lines
11 KiB
JavaScript
import {
|
|
formatDataToArray,
|
|
walk,
|
|
getTopAncestorsFomNodeList,
|
|
getNodeListBoundingRect,
|
|
createUid
|
|
} from '../utils'
|
|
|
|
// 解析要添加外框的节点实例列表
|
|
const parseAddNodeList = list => {
|
|
// 找出顶层节点
|
|
list = getTopAncestorsFomNodeList(list)
|
|
const cache = {}
|
|
const uidToParent = {}
|
|
// 找出列表中节点在兄弟节点中的索引,并和父节点关联起来
|
|
list.forEach(node => {
|
|
const parent = node.parent
|
|
if (parent) {
|
|
const pUid = parent.uid
|
|
uidToParent[pUid] = parent
|
|
const index = node.getIndexInBrothers()
|
|
const data = {
|
|
node,
|
|
index
|
|
}
|
|
if (cache[pUid]) {
|
|
if (
|
|
!cache[pUid].find(item => {
|
|
return item.index === data.index
|
|
})
|
|
) {
|
|
cache[pUid].push(data)
|
|
}
|
|
} else {
|
|
cache[pUid] = [data]
|
|
}
|
|
}
|
|
})
|
|
const res = []
|
|
Object.keys(cache).forEach(uid => {
|
|
const indexList = cache[uid]
|
|
const parentNode = uidToParent[uid]
|
|
if (indexList.length > 1) {
|
|
// 多个节点
|
|
const rangeList = indexList
|
|
.map(item => {
|
|
return item.index
|
|
})
|
|
.sort((a, b) => {
|
|
return a - b
|
|
})
|
|
const minIndex = rangeList[0]
|
|
const maxIndex = rangeList[rangeList.length - 1]
|
|
let curStart = -1
|
|
let curEnd = -1
|
|
for (let i = minIndex; i <= maxIndex; i++) {
|
|
// 连续索引
|
|
if (rangeList.includes(i)) {
|
|
if (curStart === -1) {
|
|
curStart = i
|
|
}
|
|
curEnd = i
|
|
} else {
|
|
// 连续断开
|
|
if (curStart !== -1 && curEnd !== -1) {
|
|
res.push({
|
|
node: parentNode,
|
|
range: [curStart, curEnd]
|
|
})
|
|
}
|
|
curStart = -1
|
|
curEnd = -1
|
|
}
|
|
}
|
|
// 不要忘了最后一段索引
|
|
if (curStart !== -1 && curEnd !== -1) {
|
|
res.push({
|
|
node: parentNode,
|
|
range: [curStart, curEnd]
|
|
})
|
|
}
|
|
} else {
|
|
// 单个节点
|
|
res.push({
|
|
node: parentNode,
|
|
range: [indexList[0].index, indexList[0].index]
|
|
})
|
|
}
|
|
})
|
|
return res
|
|
}
|
|
|
|
// 解析获取节点的子节点生成的外框列表
|
|
const getNodeOuterFrameList = node => {
|
|
const children = node.children
|
|
if (!children || children.length <= 0) return
|
|
const res = []
|
|
const map = {}
|
|
children.forEach((item, index) => {
|
|
const outerFrameData = item.getData('outerFrame')
|
|
if (!outerFrameData) return
|
|
const groupId = outerFrameData.groupId
|
|
if (groupId) {
|
|
if (!map[groupId]) {
|
|
map[groupId] = []
|
|
}
|
|
map[groupId].push({
|
|
node: item,
|
|
index
|
|
})
|
|
} else {
|
|
res.push({
|
|
nodeList: [item],
|
|
range: [index, index]
|
|
})
|
|
}
|
|
})
|
|
Object.keys(map).forEach(id => {
|
|
const list = map[id]
|
|
res.push({
|
|
nodeList: list.map(item => {
|
|
return item.node
|
|
}),
|
|
range: [list[0].index, list[list.length - 1].index]
|
|
})
|
|
})
|
|
return res
|
|
}
|
|
|
|
// 默认外框样式
|
|
const defaultStyle = {
|
|
radius: 5,
|
|
strokeWidth: 2,
|
|
strokeColor: '#0984e3',
|
|
strokeDasharray: '5,5',
|
|
fill: 'rgba(9,132,227,0.05)'
|
|
}
|
|
|
|
// 外框插件
|
|
class OuterFrame {
|
|
constructor(opt = {}) {
|
|
this.mindMap = opt.mindMap
|
|
this.draw = null
|
|
this.createDrawContainer()
|
|
this.outerFrameElList = []
|
|
this.activeOuterFrame = null
|
|
this.bindEvent()
|
|
}
|
|
|
|
// 创建容器
|
|
createDrawContainer() {
|
|
this.draw = this.mindMap.draw.group()
|
|
this.draw.addClass('smm-outer-frame-container')
|
|
this.draw.back() // 最底层
|
|
this.draw.forward() // 连线层上面
|
|
}
|
|
|
|
// 绑定事件
|
|
bindEvent() {
|
|
this.renderOuterFrames = this.renderOuterFrames.bind(this)
|
|
this.mindMap.on('node_tree_render_end', this.renderOuterFrames)
|
|
this.mindMap.on('data_change', this.renderOuterFrames)
|
|
// 监听画布和节点点击事件,用于清除当前激活的连接线
|
|
this.clearActiveOuterFrame = this.clearActiveOuterFrame.bind(this)
|
|
this.mindMap.on('draw_click', this.clearActiveOuterFrame)
|
|
this.mindMap.on('node_click', this.clearActiveOuterFrame)
|
|
|
|
this.addOuterFrame = this.addOuterFrame.bind(this)
|
|
this.mindMap.command.add('ADD_OUTER_FRAME', this.addOuterFrame)
|
|
|
|
this.removeActiveOuterFrame = this.removeActiveOuterFrame.bind(this)
|
|
this.mindMap.keyCommand.addShortcut(
|
|
'Del|Backspace',
|
|
this.removeActiveOuterFrame
|
|
)
|
|
}
|
|
|
|
// 解绑事件
|
|
unBindEvent() {
|
|
this.mindMap.off('node_tree_render_end', this.renderOuterFrames)
|
|
this.mindMap.off('data_change', this.renderOuterFrames)
|
|
this.mindMap.off('draw_click', this.clearActiveOuterFrame)
|
|
this.mindMap.off('node_click', this.clearActiveOuterFrame)
|
|
this.mindMap.command.remove('ADD_OUTER_FRAME', this.addOuterFrame)
|
|
this.mindMap.keyCommand.removeShortcut(
|
|
'Del|Backspace',
|
|
this.removeActiveOuterFrame
|
|
)
|
|
}
|
|
|
|
// 给节点添加外框数据
|
|
/*
|
|
config: {
|
|
text: '',
|
|
radius: 5,
|
|
strokeWidth: 2,
|
|
strokeColor: '#0984e3',
|
|
strokeDasharray: '5,5',
|
|
fill: 'rgba(9,132,227,0.05)'
|
|
}
|
|
*/
|
|
addOuterFrame(appointNodes, config = {}) {
|
|
appointNodes = formatDataToArray(appointNodes)
|
|
const activeNodeList = this.mindMap.renderer.activeNodeList
|
|
if (activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
|
return
|
|
}
|
|
let nodeList = appointNodes.length > 0 ? appointNodes : activeNodeList
|
|
nodeList = nodeList.filter(node => {
|
|
return !node.isRoot && !node.isGeneralization
|
|
})
|
|
const list = parseAddNodeList(nodeList)
|
|
list.forEach(({ node, range }) => {
|
|
const childNodeList = node.children.slice(range[0], range[1] + 1)
|
|
const groupId = createUid()
|
|
childNodeList.forEach(child => {
|
|
let outerFrame = child.getData('outerFrame')
|
|
// 检查该外框是否已存在
|
|
if (outerFrame) {
|
|
outerFrame = {
|
|
...outerFrame,
|
|
...config,
|
|
groupId
|
|
}
|
|
} else {
|
|
outerFrame = {
|
|
...config,
|
|
groupId
|
|
}
|
|
}
|
|
this.mindMap.execCommand('SET_NODE_DATA', child, {
|
|
outerFrame
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
// 获取当前激活的外框
|
|
getActiveOuterFrame() {
|
|
return this.activeOuterFrame
|
|
? {
|
|
...this.activeOuterFrame
|
|
}
|
|
: null
|
|
}
|
|
|
|
// 删除当前激活的外框
|
|
removeActiveOuterFrame() {
|
|
if (!this.activeOuterFrame) return
|
|
const { node, range } = this.activeOuterFrame
|
|
this.getRangeNodeList(node, range).forEach(child => {
|
|
this.mindMap.execCommand('SET_NODE_DATA', child, {
|
|
outerFrame: null
|
|
})
|
|
})
|
|
this.mindMap.emit('outer_frame_delete')
|
|
}
|
|
|
|
// 更新当前激活的外框
|
|
// 执行了该方法后请立即隐藏你的样式面板,因为会清除当前激活的外框
|
|
updateActiveOuterFrame(config = {}) {
|
|
if (!this.activeOuterFrame) return
|
|
const { node, range } = this.activeOuterFrame
|
|
this.getRangeNodeList(node, range).forEach(node => {
|
|
const outerFrame = node.getData('outerFrame')
|
|
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
|
outerFrame: {
|
|
...outerFrame,
|
|
...config
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
// 获取某个节点指定范围的带外框的子节点列表
|
|
getRangeNodeList(node, range) {
|
|
return node.children.slice(range[0], range[1] + 1).filter(child => {
|
|
return child.getData('outerFrame')
|
|
})
|
|
}
|
|
|
|
// 渲染外框
|
|
renderOuterFrames() {
|
|
this.clearOuterFrameElList()
|
|
let tree = this.mindMap.renderer.root
|
|
if (!tree) return
|
|
const t = this.mindMap.draw.transform()
|
|
const { outerFramePaddingX, outerFramePaddingY } = this.mindMap.opt
|
|
walk(
|
|
tree,
|
|
null,
|
|
cur => {
|
|
if (!cur) return
|
|
const outerFrameList = getNodeOuterFrameList(cur)
|
|
if (outerFrameList && outerFrameList.length > 0) {
|
|
outerFrameList.forEach(({ nodeList, range }) => {
|
|
if (range[0] === -1 || range[1] === -1) return
|
|
const { left, top, width, height } =
|
|
getNodeListBoundingRect(nodeList)
|
|
if (
|
|
!Number.isFinite(left) ||
|
|
!Number.isFinite(top) ||
|
|
!Number.isFinite(width) ||
|
|
!Number.isFinite(height)
|
|
)
|
|
return
|
|
const el = this.createOuterFrameEl(
|
|
(left -
|
|
outerFramePaddingX -
|
|
this.mindMap.elRect.left -
|
|
t.translateX) /
|
|
t.scaleX,
|
|
(top -
|
|
outerFramePaddingY -
|
|
this.mindMap.elRect.top -
|
|
t.translateY) /
|
|
t.scaleY,
|
|
(width + outerFramePaddingX * 2) / t.scaleX,
|
|
(height + outerFramePaddingY * 2) / t.scaleY,
|
|
nodeList[0].getData('outerFrame') // 使用第一个节点的外框样式
|
|
)
|
|
el.on('click', e => {
|
|
e.stopPropagation()
|
|
this.setActiveOuterFrame(el, cur, range)
|
|
})
|
|
})
|
|
}
|
|
},
|
|
() => {},
|
|
true,
|
|
0
|
|
)
|
|
}
|
|
|
|
// 激活外框
|
|
setActiveOuterFrame(el, node, range) {
|
|
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
|
this.clearActiveOuterFrame()
|
|
this.activeOuterFrame = {
|
|
el,
|
|
node,
|
|
range
|
|
}
|
|
el.stroke({
|
|
dasharray: 'none'
|
|
})
|
|
this.mindMap.emit('outer_frame_active', el, node, range)
|
|
}
|
|
|
|
// 清除当前激活的外框
|
|
clearActiveOuterFrame() {
|
|
if (!this.activeOuterFrame) return
|
|
const { el } = this.activeOuterFrame
|
|
el.stroke({
|
|
dasharray: el.cacheStyle.dasharray || defaultStyle.strokeDasharray
|
|
})
|
|
this.activeOuterFrame = null
|
|
}
|
|
|
|
// 创建外框元素
|
|
createOuterFrameEl(x, y, width, height, styleConfig = {}) {
|
|
styleConfig = { ...defaultStyle, ...styleConfig }
|
|
const el = this.draw
|
|
.rect()
|
|
.size(width, height)
|
|
.radius(styleConfig.radius)
|
|
.stroke({
|
|
width: styleConfig.strokeWidth,
|
|
color: styleConfig.strokeColor,
|
|
dasharray: styleConfig.strokeDasharray
|
|
})
|
|
.fill({
|
|
color: styleConfig.fill
|
|
})
|
|
.x(x)
|
|
.y(y)
|
|
el.cacheStyle = {
|
|
dasharray: styleConfig.strokeDasharray
|
|
}
|
|
this.outerFrameElList.push(el)
|
|
return el
|
|
}
|
|
|
|
// 清除外框元素
|
|
clearOuterFrameElList() {
|
|
this.outerFrameElList.forEach(item => {
|
|
item.remove()
|
|
})
|
|
this.outerFrameElList = []
|
|
this.activeOuterFrame = null
|
|
}
|
|
|
|
// 插件被移除前做的事情
|
|
beforePluginRemove() {
|
|
this.unBindEvent()
|
|
}
|
|
|
|
// 插件被卸载前做的事情
|
|
beforePluginDestroy() {
|
|
this.unBindEvent()
|
|
}
|
|
}
|
|
|
|
OuterFrame.instanceName = 'outerFrame'
|
|
|
|
export default OuterFrame
|