diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 31913765..30f5b20d 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -25,7 +25,7 @@ import { setDataToClipboard, getDataFromClipboard, htmlEscape, - checkHasSupSubRelation + parseAddGeneralizationNodeList } from '../../utils' import { shapeList } from './node/Shape' import { lineStyleProps } from '../../themes/default' @@ -1032,14 +1032,7 @@ class Render { let node = list[i] if (isAppointNodes) list.splice(i, 1) if (node.isGeneralization) { - // 删除概要节点 - this.mindMap.execCommand( - 'SET_NODE_DATA', - node.generalizationBelongNode, - { - generalization: null - } - ) + this.deleteNodeGeneralization(node) this.removeNodeFromActiveList(node) i-- } else { @@ -1058,6 +1051,22 @@ class Render { 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 + }) + } + // 仅删除当前节点 removeCurrentNode(appointNodes = []) { appointNodes = formatDataToArray(appointNodes) @@ -1075,13 +1084,7 @@ class Render { let node = list[i] if (node.isGeneralization) { // 删除概要节点 - this.mindMap.execCommand( - 'SET_NODE_DATA', - node.generalizationBelongNode, - { - generalization: null - } - ) + this.deleteNodeGeneralization(node) } else { const parent = node.parent const index = getNodeDataIndex(node) @@ -1403,29 +1406,41 @@ class Render { if (this.activeNodeList.length <= 0) { return } - let hasAncestorsExistGeneralization = false - this.activeNodeList.forEach(node => { - if (node.getData('generalization') || node.isRoot) { - return - } - hasAncestorsExistGeneralization = node.ancestorHasGeneralization() - this.mindMap.execCommand('SET_NODE_DATA', node, { - generalization: data || { + const nodeList = this.activeNodeList.filter(node => { + return ( + !node.isRoot && !node.isGeneralization && !node.checkHasSelfGeneralization() + ) + }) + const list = parseAddGeneralizationNodeList(nodeList) + list.forEach(item => { + const newData = { + ...(data || { text: this.mindMap.opt.defaultGeneralizationText + }), + range: item.range || null + } + 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 }) // 插入子节点时自动展开子节点 - node.setData({ + item.node.setData({ expand: true }) }) - const hasSupSubRelation = checkHasSupSubRelation(this.activeNodeList) this.mindMap.render(() => { // 修复祖先节点存在概要时位置未更新的问题 // 修复同时给存在上下级关系的节点添加概要时重叠的问题 - if (hasSupSubRelation || hasAncestorsExistGeneralization) { - this.mindMap.render() - } + this.mindMap.render() }) } @@ -1435,7 +1450,7 @@ class Render { return } this.activeNodeList.forEach(node => { - if (!node.getData('generalization')) { + if (!node.checkHasGeneralization()) { return } this.mindMap.execCommand('SET_NODE_DATA', node, { diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js index f3e17f82..4421546d 100644 --- a/simple-mind-map/src/core/render/node/Node.js +++ b/simple-mind-map/src/core/render/node/Node.js @@ -83,8 +83,7 @@ class Node { this._fillExpandNode = null this._userListGroup = null this._lines = [] - this._generalizationLine = null - this._generalizationNode = null + this._generalizationList = [] this._unVisibleRectRegionNode = null this._isMouseenter = false // 尺寸信息 @@ -751,10 +750,7 @@ class Node { item.setOpacity(val) }) // 概要节点 - if (this._generalizationNode) { - this._generalizationLine.opacity(val) - this._generalizationNode.group.opacity(val) - } + this.setGeneralizationOpacity(val) } // 隐藏子节点 @@ -861,11 +857,11 @@ class Node { return false } - // 检查是否存在概要的祖先节点 + // 检查是否存在有概要的祖先节点 ancestorHasGeneralization() { let node = this.parent while (node) { - if (node.getData('generalization')) { + if (node.checkHasGeneralization()) { return true } node = node.parent @@ -939,6 +935,15 @@ class Node { }) } + // 获取该节点在兄弟节点列表中的索引 + getIndexInBrothers() { + return this.parent && this.parent.children + ? this.parent.children.findIndex(item => { + return item.uid === this.uid + }) + : -1 + } + // 获取padding值 getPaddingVale() { let { isActive } = this.getData() diff --git a/simple-mind-map/src/core/render/node/nodeGeneralization.js b/simple-mind-map/src/core/render/node/nodeGeneralization.js index 61514324..cfbd042b 100644 --- a/simple-mind-map/src/core/render/node/nodeGeneralization.js +++ b/simple-mind-map/src/core/render/node/nodeGeneralization.js @@ -1,9 +1,30 @@ import Node from './Node' import { createUid } from '../../../utils/index' +// 获取节点概要数据 +function formatGetGeneralization() { + const data = this.getData('generalization') + return Array.isArray(data) ? data : data ? [data] : [] +} + // 检查是否存在概要 function checkHasGeneralization() { - return !!this.getData('generalization') + return this.formatGetGeneralization().length > 0 +} + +// 检查是否存在自身的概要,非子节点区间 +function checkHasSelfGeneralization() { + const list = this.formatGetGeneralization() + return !!list.find(item => { + return !item.range || item.range.length <= 0 + }) +} + +// 获取概要节点所在的概要列表里的索引 +function getGeneralizationNodeIndex(node) { + return this._generalizationList.findIndex(item => { + return item.generalizationNode.uid === node.uid + }) } // 创建概要节点 @@ -11,26 +32,47 @@ function createGeneralizationNode() { if (this.isGeneralization || !this.checkHasGeneralization()) { return } - if (!this._generalizationLine) { - this._generalizationLine = this.lineDraw.path() - } - if (!this._generalizationNode) { - this._generalizationNode = new Node({ - data: { - data: this.getData('generalization') - }, - uid: createUid(), - renderer: this.renderer, - mindMap: this.mindMap, - isGeneralization: true - }) - this._generalizationNodeWidth = this._generalizationNode.width - this._generalizationNodeHeight = this._generalizationNode.height - this._generalizationNode.generalizationBelongNode = this - if (this.getData('generalization').isActive) { - this.renderer.addNodeToActiveList(this._generalizationNode) + let maxWidth = 0 + let maxHeight = 0 + const list = this.formatGetGeneralization() + list.forEach((item, index) => { + let cur = this._generalizationList[index] + if (!cur) { + cur = this._generalizationList[index] = {} } - } + // 所属节点 + cur.node = this + // 区间范围 + cur.range = item.range + // 线和节点 + if (!cur.generalizationLine) { + cur.generalizationLine = this.lineDraw.path() + } + if (!cur.generalizationNode) { + cur.generalizationNode = new Node({ + data: { + data: item + }, + uid: createUid(), + renderer: this.renderer, + mindMap: this.mindMap, + isGeneralization: true + }) + } + // 关联所属节点 + cur.generalizationNode.generalizationBelongNode = this + // 大小 + if (cur.generalizationNode.width > maxWidth) + maxWidth = cur.generalizationNode.width + if (cur.generalizationNode.height > maxHeight) + maxHeight = cur.generalizationNode.height + // 如果该概要为激活状态,那么加入激活节点列表 + if (item.isActive) { + this.renderer.addNodeToActiveList(cur.generalizationNode) + } + }) + this._generalizationNodeWidth = maxWidth + this._generalizationNodeHeight = maxHeight } // 更新概要节点 @@ -43,39 +85,64 @@ function updateGeneralization() { // 渲染概要节点 function renderGeneralization() { if (this.isGeneralization) return - if (!this.checkHasGeneralization()) { + this.updateGeneralizationData() + const list = this.formatGetGeneralization() + if (list.length <= 0 || this.getData('expand') === false) { this.removeGeneralization() - this._generalizationNodeWidth = 0 - this._generalizationNodeHeight = 0 return } - if (this.getData('expand') === false) { + if (list.length !== this._generalizationList.length) { this.removeGeneralization() - return } this.createGeneralizationNode() - this.renderer.layout.renderGeneralization( - this, - this._generalizationLine, - this._generalizationNode - ) - this.style.generalizationLine(this._generalizationLine) - this._generalizationNode.render() + this.renderer.layout.renderGeneralization(this._generalizationList) + this._generalizationList.forEach(item => { + this.style.generalizationLine(item.generalizationLine) + item.generalizationNode.render() + }) +} + +// 更新节点概要数据 +function updateGeneralizationData() { + const childrenLength = this.children.length + const list = this.formatGetGeneralization() + const newList = [] + list.forEach(item => { + if (!item.range) { + newList.push(item) + return + } + if ( + item.range.length > 0 && + item.range[0] <= childrenLength - 1 && + item.range[1] <= childrenLength - 1 + ) { + newList.push(item) + } + }) + if (newList.length !== list.length) { + this.setData({ + generalization: newList + }) + } } // 删除概要节点 function removeGeneralization() { if (this.isGeneralization) return - if (this._generalizationLine) { - this._generalizationLine.remove() - this._generalizationLine = null - } - if (this._generalizationNode) { - // 删除概要节点时要同步从激活节点里删除 - this.renderer.removeNodeFromActiveList(this._generalizationNode) - this._generalizationNode.remove() - this._generalizationNode = null - } + this._generalizationList.forEach(item => { + if (item.generalizationLine) { + item.generalizationLine.remove() + item.generalizationLine = null + } + if (item.generalizationNode) { + // 删除概要节点时要同步从激活节点里删除 + this.renderer.removeNodeFromActiveList(item.generalizationNode) + item.generalizationNode.remove() + item.generalizationNode = null + } + }) + this._generalizationList = [] // hack修复当激活一个节点时创建概要,然后立即激活创建的概要节点后会重复创建概要节点并且无法删除的问题 if (this.generalizationBelongNode) { this.nodeDraw @@ -87,31 +154,40 @@ function removeGeneralization() { // 隐藏概要节点 function hideGeneralization() { if (this.isGeneralization) return - if (this._generalizationLine) { - this._generalizationLine.hide() - } - if (this._generalizationNode) { - this._generalizationNode.hide() - } + this._generalizationList.forEach(item => { + if (item.generalizationLine) item.generalizationLine.hide() + if (item.generalizationNode) item.generalizationNode.hide() + }) } // 显示概要节点 function showGeneralization() { if (this.isGeneralization) return - if (this._generalizationLine) { - this._generalizationLine.show() - } - if (this._generalizationNode) { - this._generalizationNode.show() - } + this._generalizationList.forEach(item => { + if (item.generalizationLine) item.generalizationLine.show() + if (item.generalizationNode) item.generalizationNode.show() + }) +} + +// 设置概要节点的透明度 +function setGeneralizationOpacity(val) { + this._generalizationList.forEach(item => { + item.generalizationLine.opacity(val) + item.generalizationNode.group.opacity(val) + }) } export default { + formatGetGeneralization, checkHasGeneralization, + checkHasSelfGeneralization, + getGeneralizationNodeIndex, createGeneralizationNode, updateGeneralization, + updateGeneralizationData, renderGeneralization, removeGeneralization, hideGeneralization, - showGeneralization + showGeneralization, + setGeneralizationOpacity } diff --git a/simple-mind-map/src/layouts/Base.js b/simple-mind-map/src/layouts/Base.js index f8cdc6b0..a7398e55 100644 --- a/simple-mind-map/src/layouts/Base.js +++ b/simple-mind-map/src/layouts/Base.js @@ -346,6 +346,50 @@ class Base { } } + // 获取指定索引区间的子节点的边界范围 + getChildrenBoundaries(node, dir, startIndex = 0, endIndex) { + let { generalizationLineMargin, generalizationNodeMargin } = + this.mindMap.themeConfig + const children = node.children.slice(startIndex, endIndex + 1) + let left = Infinity + let right = -Infinity + let top = Infinity + let bottom = -Infinity + children.forEach(item => { + const cur = this.getNodeBoundaries(item, dir) + left = cur.left < left ? cur.left : left + right = cur.right > right ? cur.right : right + top = cur.top < top ? cur.top : top + bottom = cur.bottom > bottom ? cur.bottom : bottom + }) + return { + left, + right, + top, + bottom, + generalizationLineMargin, + generalizationNodeMargin + } + } + + // 获取节点概要的渲染边界 + getNodeGeneralizationRenderBoundaries(item, dir) { + let res = null + // 区间 + if (item.range) { + res = this.getChildrenBoundaries( + item.node, + dir, + item.range[0], + item.range[1] + ) + } else { + // 整体概要 + res = this.getNodeBoundaries(item.node, dir) + } + return res + } + // 获取节点实际存在几个子节点 getNodeActChildrenLength(node) { return node.nodeData.children && node.nodeData.children.length diff --git a/simple-mind-map/src/layouts/CatalogOrganization.js b/simple-mind-map/src/layouts/CatalogOrganization.js index 84092778..ce6e3cd8 100644 --- a/simple-mind-map/src/layouts/CatalogOrganization.js +++ b/simple-mind-map/src/layouts/CatalogOrganization.js @@ -72,11 +72,7 @@ class CatalogOrganization extends Base { this.root, null, (node, parent, isRoot, layerIndex) => { - if ( - node.getData('expand') && - node.children && - node.children.length - ) { + if (node.getData('expand') && node.children && node.children.length) { let marginX = this.getMarginX(layerIndex + 1) let marginY = this.getMarginY(layerIndex + 1) if (isRoot) { @@ -339,24 +335,27 @@ class CatalogOrganization extends Base { } // 创建概要节点 - renderGeneralization(node, gLine, gNode) { - let { - top, - bottom, - right, - generalizationLineMargin, - generalizationNodeMargin - } = this.getNodeBoundaries(node, 'h') - let x1 = right + generalizationLineMargin - let y1 = top - let x2 = right + generalizationLineMargin - let y2 = bottom - let cx = x1 + 20 - let cy = y1 + (y2 - y1) / 2 - let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` - gLine.plot(path) - gNode.left = right + generalizationNodeMargin - gNode.top = top + (bottom - top - gNode.height) / 2 + renderGeneralization(list) { + list.forEach(item => { + let { + top, + bottom, + right, + generalizationLineMargin, + generalizationNodeMargin + } = this.getNodeGeneralizationRenderBoundaries(item, 'h') + let x1 = right + generalizationLineMargin + let y1 = top + let x2 = right + generalizationLineMargin + let y2 = bottom + let cx = x1 + 20 + let cy = y1 + (y2 - y1) / 2 + let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` + item.generalizationLine.plot(path) + item.generalizationNode.left = right + generalizationNodeMargin + item.generalizationNode.top = + top + (bottom - top - item.generalizationNode.height) / 2 + }) } // 渲染展开收起按钮的隐藏占位元素 diff --git a/simple-mind-map/src/layouts/Fishbone.js b/simple-mind-map/src/layouts/Fishbone.js index 618c9880..98f0b1fb 100644 --- a/simple-mind-map/src/layouts/Fishbone.js +++ b/simple-mind-map/src/layouts/Fishbone.js @@ -357,24 +357,27 @@ class Fishbone extends Base { } // 创建概要节点 - renderGeneralization(node, gLine, gNode) { - let { - top, - bottom, - right, - generalizationLineMargin, - generalizationNodeMargin - } = this.getNodeBoundaries(node, 'h') - let x1 = right + generalizationLineMargin - let y1 = top - let x2 = right + generalizationLineMargin - let y2 = bottom - let cx = x1 + 20 - let cy = y1 + (y2 - y1) / 2 - let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` - gLine.plot(path) - gNode.left = right + generalizationNodeMargin - gNode.top = top + (bottom - top - gNode.height) / 2 + renderGeneralization(list) { + list.forEach(item => { + let { + top, + bottom, + right, + generalizationLineMargin, + generalizationNodeMargin + } = this.getNodeGeneralizationRenderBoundaries(item, 'h') + let x1 = right + generalizationLineMargin + let y1 = top + let x2 = right + generalizationLineMargin + let y2 = bottom + let cx = x1 + 20 + let cy = y1 + (y2 - y1) / 2 + let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` + item.generalizationLine.plot(path) + item.generalizationNode.left = right + generalizationNodeMargin + item.generalizationNode.top = + top + (bottom - top - item.generalizationNode.height) / 2 + }) } // 渲染展开收起按钮的隐藏占位元素 diff --git a/simple-mind-map/src/layouts/LogicalStructure.js b/simple-mind-map/src/layouts/LogicalStructure.js index bc7bd793..572c4eb1 100644 --- a/simple-mind-map/src/layouts/LogicalStructure.js +++ b/simple-mind-map/src/layouts/LogicalStructure.js @@ -77,11 +77,7 @@ class LogicalStructure extends Base { this.root, null, (node, parent, isRoot, layerIndex) => { - if ( - node.getData('expand') && - node.children && - node.children.length - ) { + if (node.getData('expand') && node.children && node.children.length) { let marginY = this.getMarginY(layerIndex + 1) // 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半 let top = node.top + node.height / 2 - node.childrenAreaHeight / 2 @@ -269,24 +265,27 @@ class LogicalStructure extends Base { } // 创建概要节点 - renderGeneralization(node, gLine, gNode) { - let { - top, - bottom, - right, - generalizationLineMargin, - generalizationNodeMargin - } = this.getNodeBoundaries(node, 'h') - let x1 = right + generalizationLineMargin - let y1 = top - let x2 = right + generalizationLineMargin - let y2 = bottom - let cx = x1 + 20 - let cy = y1 + (y2 - y1) / 2 - let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` - gLine.plot(path) - gNode.left = right + generalizationNodeMargin - gNode.top = top + (bottom - top - gNode.height) / 2 + renderGeneralization(list) { + list.forEach(item => { + let { + top, + bottom, + right, + generalizationLineMargin, + generalizationNodeMargin + } = this.getNodeGeneralizationRenderBoundaries(item, 'h') + let x1 = right + generalizationLineMargin + let y1 = top + let x2 = right + generalizationLineMargin + let y2 = bottom + let cx = x1 + 20 + let cy = y1 + (y2 - y1) / 2 + let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` + item.generalizationLine.plot(path) + item.generalizationNode.left = right + generalizationNodeMargin + item.generalizationNode.top = + top + (bottom - top - item.generalizationNode.height) / 2 + }) } // 渲染展开收起按钮的隐藏占位元素 diff --git a/simple-mind-map/src/layouts/MindMap.js b/simple-mind-map/src/layouts/MindMap.js index 36abe5de..3641933a 100644 --- a/simple-mind-map/src/layouts/MindMap.js +++ b/simple-mind-map/src/layouts/MindMap.js @@ -358,32 +358,34 @@ class MindMap extends Base { } // 创建概要节点 - renderGeneralization(node, gLine, gNode) { - let isLeft = node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT - let { - top, - bottom, - left, - right, - generalizationLineMargin, - generalizationNodeMargin - } = this.getNodeBoundaries(node, 'h', isLeft) - let x = isLeft - ? left - generalizationLineMargin - : right + generalizationLineMargin - let x1 = x - let y1 = top - let x2 = x - let y2 = bottom - let cx = x1 + (isLeft ? -20 : 20) - let cy = y1 + (y2 - y1) / 2 - let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` - gLine.plot(path) - gNode.left = - x + - (isLeft ? -generalizationNodeMargin : generalizationNodeMargin) - - (isLeft ? gNode.width : 0) - gNode.top = top + (bottom - top - gNode.height) / 2 + renderGeneralization(list) { + list.forEach(item => { + let isLeft = item.node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT + let { + top, + bottom, + left, + right, + generalizationLineMargin, + generalizationNodeMargin + } = this.getNodeGeneralizationRenderBoundaries(item, 'h') + let x = isLeft + ? left - generalizationLineMargin + : right + generalizationLineMargin + let x1 = x + let y1 = top + let x2 = x + let y2 = bottom + let cx = x1 + (isLeft ? -20 : 20) + let cy = y1 + (y2 - y1) / 2 + let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` + item.generalizationLine.plot(path) + item.generalizationNode.left = + x + + (isLeft ? -generalizationNodeMargin : generalizationNodeMargin) - + (isLeft ? item.generalizationNode.width : 0) + item.generalizationNode.top = top + (bottom - top - item.generalizationNode.height) / 2 + }) } // 渲染展开收起按钮的隐藏占位元素 diff --git a/simple-mind-map/src/layouts/OrganizationStructure.js b/simple-mind-map/src/layouts/OrganizationStructure.js index 93192c7a..482c0781 100644 --- a/simple-mind-map/src/layouts/OrganizationStructure.js +++ b/simple-mind-map/src/layouts/OrganizationStructure.js @@ -243,24 +243,27 @@ class OrganizationStructure extends Base { } // 创建概要节点 - renderGeneralization(node, gLine, gNode) { - let { - bottom, - left, - right, - generalizationLineMargin, - generalizationNodeMargin - } = this.getNodeBoundaries(node, 'v') - let x1 = left - let y1 = bottom + generalizationLineMargin - let x2 = right - let y2 = bottom + generalizationLineMargin - let cx = x1 + (x2 - x1) / 2 - let cy = y1 + 20 - let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` - gLine.plot(path) - gNode.top = bottom + generalizationNodeMargin - gNode.left = left + (right - left - gNode.width) / 2 + renderGeneralization(list) { + list.forEach(item => { + let { + bottom, + left, + right, + generalizationLineMargin, + generalizationNodeMargin + } = this.getNodeGeneralizationRenderBoundaries(item, 'v') + let x1 = left + let y1 = bottom + generalizationLineMargin + let x2 = right + let y2 = bottom + generalizationLineMargin + let cx = x1 + (x2 - x1) / 2 + let cy = y1 + 20 + let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` + item.generalizationLine.plot(path) + item.generalizationNode.top = bottom + generalizationNodeMargin + item.generalizationNode.left = left + (right - left - item.generalizationNode.width) / 2 + }) + } // 渲染展开收起按钮的隐藏占位元素 diff --git a/simple-mind-map/src/layouts/Timeline.js b/simple-mind-map/src/layouts/Timeline.js index 82fbc9f0..d9d51c2e 100644 --- a/simple-mind-map/src/layouts/Timeline.js +++ b/simple-mind-map/src/layouts/Timeline.js @@ -80,11 +80,7 @@ class Timeline extends Base { this.root, null, (node, parent, isRoot, layerIndex, index) => { - if ( - node.getData('expand') && - node.children && - node.children.length - ) { + if (node.getData('expand') && node.children && node.children.length) { let marginX = this.getMarginX(layerIndex + 1) let marginY = this.getMarginY(layerIndex + 1) if (isRoot) { @@ -315,24 +311,26 @@ class Timeline extends Base { } // 创建概要节点 - renderGeneralization(node, gLine, gNode) { - let { - top, - bottom, - right, - generalizationLineMargin, - generalizationNodeMargin - } = this.getNodeBoundaries(node, 'h') - let x1 = right + generalizationLineMargin - let y1 = top - let x2 = right + generalizationLineMargin - let y2 = bottom - let cx = x1 + 20 - let cy = y1 + (y2 - y1) / 2 - let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` - gLine.plot(path) - gNode.left = right + generalizationNodeMargin - gNode.top = top + (bottom - top - gNode.height) / 2 + renderGeneralization(list) { + list.forEach(item => { + let { + top, + bottom, + right, + generalizationLineMargin, + generalizationNodeMargin + } = this.getNodeGeneralizationRenderBoundaries(item, 'h') + let x1 = right + generalizationLineMargin + let y1 = top + let x2 = right + generalizationLineMargin + let y2 = bottom + let cx = x1 + 20 + let cy = y1 + (y2 - y1) / 2 + let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` + item.generalizationLine.plot(path) + item.generalizationNode.left = right + generalizationNodeMargin + item.generalizationNode.top = top + (bottom - top - item.generalizationNode.height) / 2 + }) } // 渲染展开收起按钮的隐藏占位元素 diff --git a/simple-mind-map/src/layouts/VerticalTimeline.js b/simple-mind-map/src/layouts/VerticalTimeline.js index bdd06189..7f844fb0 100644 --- a/simple-mind-map/src/layouts/VerticalTimeline.js +++ b/simple-mind-map/src/layouts/VerticalTimeline.js @@ -97,11 +97,7 @@ class VerticalTimeline extends Base { this.root, null, (node, parent, isRoot, layerIndex, index) => { - if ( - node.getData('expand') && - node.children && - node.children.length - ) { + if (node.getData('expand') && node.children && node.children.length) { let marginY = this.getMarginY(layerIndex + 1) // 定位二级节点的top if (isRoot) { @@ -386,32 +382,35 @@ class VerticalTimeline extends Base { } // 创建概要节点 - renderGeneralization(node, gLine, gNode) { - let isLeft = node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT - let { - top, - bottom, - left, - right, - generalizationLineMargin, - generalizationNodeMargin - } = this.getNodeBoundaries(node, 'h', isLeft) - let x = isLeft - ? left - generalizationLineMargin - : right + generalizationLineMargin - let x1 = x - let y1 = top - let x2 = x - let y2 = bottom - let cx = x1 + (isLeft ? -20 : 20) - let cy = y1 + (y2 - y1) / 2 - let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` - gLine.plot(path) - gNode.left = - x + - (isLeft ? -generalizationNodeMargin : generalizationNodeMargin) - - (isLeft ? gNode.width : 0) - gNode.top = top + (bottom - top - gNode.height) / 2 + renderGeneralization(list) { + list.forEach(item => { + let isLeft = item.node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT + let { + top, + bottom, + left, + right, + generalizationLineMargin, + generalizationNodeMargin + } = this.getNodeGeneralizationRenderBoundaries(item, 'h') + let x = isLeft + ? left - generalizationLineMargin + : right + generalizationLineMargin + let x1 = x + let y1 = top + let x2 = x + let y2 = bottom + let cx = x1 + (isLeft ? -20 : 20) + let cy = y1 + (y2 - y1) / 2 + let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` + item.generalizationLine.plot(path) + item.generalizationNode.left = + x + + (isLeft ? -generalizationNodeMargin : generalizationNodeMargin) - + (isLeft ? item.generalizationNode.width : 0) + item.generalizationNode.top = + top + (bottom - top - item.generalizationNode.height) / 2 + }) } // 渲染展开收起按钮的隐藏占位元素 diff --git a/simple-mind-map/src/parse/xmind.js b/simple-mind-map/src/parse/xmind.js index 378b6ede..c2a2db76 100644 --- a/simple-mind-map/src/parse/xmind.js +++ b/simple-mind-map/src/parse/xmind.js @@ -1,12 +1,18 @@ import JSZip from 'jszip' import xmlConvert from 'xml-js' +import { getTextFromHtml, isUndef } from '../utils/index' import { - getTextFromHtml, - imgToDataUrl, - parseDataUrl, - getImageSize, - isUndef -} from '../utils/index' + getSummaryText, + getSummaryText2, + getRoot, + getItemByName, + getElementsByType, + addSummaryData, + handleNodeImageFromXmind, + handleNodeImageToXmind, + getXmindContentXmlData, + parseNodeGeneralizationToXmind +} from '../utils/xmind' // 解析.xmind文件 const parseXmindFile = file => { @@ -43,18 +49,18 @@ const parseXmindFile = file => { // 转换xmind数据 const transformXmind = async (content, files) => { - let data = JSON.parse(content)[0] - let nodeTree = data.rootTopic - let newTree = {} - let waitLoadImageList = [] - let walk = async (node, newNode) => { + const data = JSON.parse(content)[0] + const nodeTree = data.rootTopic + const newTree = {} + const waitLoadImageList = [] + const walk = async (node, newNode) => { newNode.data = { // 节点内容 text: isUndef(node.title) ? '' : node.title } // 节点备注 if (node.notes) { - let notesData = node.notes.realHTML || node.notes.plain + const notesData = node.notes.realHTML || node.notes.plain newNode.data.note = notesData ? notesData.content || '' : '' } // 超链接 @@ -65,51 +71,27 @@ const transformXmind = async (content, files) => { if (node.labels && node.labels.length > 0) { newNode.data.tag = node.labels } - // 概要 - if (node.currentSummary) { - newNode.data.generalization = { - text: node.currentSummary.title, - expand: true, - isActive: false, - } - } - // 图片 - if (node.image && /\.(jpg|jpeg|png|gif|webp)$/.test(node.image.src)) { - // 处理异步逻辑 - let resolve = null - let promise = new Promise(_resolve => { - resolve = _resolve - }) - waitLoadImageList.push(promise) - try { - // 读取图片 - let imageType = /\.([^.]+)$/.exec(node.image.src)[1] - let imageBase64 = - `data:image/${imageType};base64,` + - (await files['resources/' + node.image.src.split('/')[1]].async( - 'base64' - )) - newNode.data.image = imageBase64 - // 如果图片尺寸不存在 - if (!node.image.width && !node.image.height) { - let imageSize = await getImageSize(imageBase64) - newNode.data.imageSize = { - width: imageSize.width, - height: imageSize.height - } - } else { - newNode.data.imageSize = { - width: node.image.width, - height: node.image.height - } - } - resolve() - } catch (error) { - console.log(error) - resolve() - } + handleNodeImageFromXmind(node, newNode, waitLoadImageList, files) + // 概要 + const selfSummary = [] + const childrenSummary = [] + if (newNode._summary) { + selfSummary.push(newNode._summary) } + if (Array.isArray(node.summaries) && node.summaries.length > 0) { + node.summaries.forEach(item => { + addSummaryData( + selfSummary, + childrenSummary, + () => { + return getSummaryText(node, item.topicId) + }, + item.range + ) + }) + } + newNode.data.generalization = selfSummary // 子节点 newNode.children = [] if ( @@ -117,42 +99,12 @@ const transformXmind = async (content, files) => { node.children.attached && node.children.attached.length > 0 ) { - // 分析概要位置 - // xmind 支持合并概要,现在的组件不支持合并概要,分别拆分开来 - let summariesPosition = {} - if (node.summaries && node.summaries.length > 0) { - node.summaries.forEach(item => { - // 使用正则表达式提取位置数字,例如 (2,3) - const match = item.range.match(/\((\d+),(\d+)\)/) - const firstNumber = match ? parseInt(match[1], 10) : null - const secondNumber = match ? parseInt(match[2], 10) : null - summariesPosition[item.topicId] = [] - for (let i = firstNumber; i <= secondNumber; i++) { - summariesPosition[item.topicId].push(i) - } - }) - } - - let summariesPositionData = {} - if (node.children.summary && node.children.summary.length > 0) { - node.children.summary.forEach(summary => { - if (Object.prototype.hasOwnProperty.call(summariesPosition, summary.id)) { - summariesPosition[summary.id].forEach (index => { - summariesPositionData[index] = summary - }) - } - }) - } - node.children.attached.forEach((item, index) => { - let currentSummary = null - if (Object.prototype.hasOwnProperty.call(summariesPositionData, index)) { - currentSummary = summariesPositionData[index] - } - - let newChild = {} + const newChild = {} newNode.children.push(newChild) - item.currentSummary = currentSummary + if (childrenSummary[index]) { + newChild._summary = childrenSummary[index] + } walk(item, newChild) }) } @@ -164,39 +116,21 @@ const transformXmind = async (content, files) => { // 转换旧版xmind数据,xmind8 const transformOldXmind = content => { - let data = JSON.parse(content) - let elements = data.elements - let root = null - let getRoot = arr => { - if (!arr) return - for (let i = 0; i < arr.length; i++) { - if (!root && arr[i].name === 'topic') { - root = arr[i] - return - } - } - arr.forEach(item => { - getRoot(item.elements) - }) - } - getRoot(elements) - let newTree = {} - let getItemByName = (arr, name) => { - return arr.find(item => { - return item.name === name - }) - } - let walk = (node, newNode) => { - let nodeElements = node.elements + const data = JSON.parse(content) + const elements = data.elements + const root = getRoot(elements) + const newTree = {} + const walk = (node, newNode) => { + const nodeElements = node.elements let nodeTitle = getItemByName(nodeElements, 'title') nodeTitle = nodeTitle && nodeTitle.elements && nodeTitle.elements[0].text + // 节点内容 newNode.data = { - // 节点内容 text: isUndef(nodeTitle) ? '' : nodeTitle } + // 节点备注 try { - // 节点备注 - let notesElement = getItemByName(nodeElements, 'notes') + const notesElement = getItemByName(nodeElements, 'notes') if (notesElement) { newNode.data.note = notesElement.elements[0].elements[0].elements[0].text @@ -204,8 +138,8 @@ const transformOldXmind = content => { } catch (error) { console.log(error) } + // 超链接 try { - // 超链接 if ( node.attributes && node.attributes['xlink:href'] && @@ -216,9 +150,9 @@ const transformOldXmind = content => { } catch (error) { console.log(error) } + // 标签 try { - // 标签 - let labelsElement = getItemByName(nodeElements, 'labels') + const labelsElement = getItemByName(nodeElements, 'labels') if (labelsElement) { newNode.data.tag = labelsElement.elements.map(item => { return item.elements[0].text @@ -227,89 +161,50 @@ const transformOldXmind = content => { } catch (error) { console.log(error) } - + const childrenItem = getItemByName(nodeElements, 'children') // 概要 - if (node.currentSummary) { - let summaryText = '' - if (node.currentSummary - && node.currentSummary.elements - && node.currentSummary.elements[0] - && node.currentSummary.elements[0].elements - && node.currentSummary.elements[0].elements[0]) { - summaryText = node.currentSummary.elements[0].elements[0].text + const selfSummary = [] + const childrenSummary = [] + try { + if (newNode._summary) { + selfSummary.push(newNode._summary) } - newNode.data.generalization = { - text: summaryText, - expand: true, - isActive: false, + const summariesItem = getItemByName(nodeElements, 'summaries') + if ( + summariesItem && + Array.isArray(summariesItem.elements) && + summariesItem.elements.length > 0 + ) { + summariesItem.elements.forEach(item => { + addSummaryData( + selfSummary, + childrenSummary, + () => { + return getSummaryText2(childrenItem, item.attributes['topic-id']) + }, + item.attributes.range + ) + }) } + } catch (error) { + console.log(error) } - + newNode.data.generalization = selfSummary // 子节点 newNode.children = [] - - let _children = getItemByName(nodeElements, 'children') - if (_children && _children.elements && _children.elements.length > 0) { - let summaryNode = null - let summariesNode = getItemByName(nodeElements, 'summaries') - _children.elements.forEach(item => { - if (item.name === 'topics' && item.attributes.type === 'summary') { - summaryNode = item - return - } - }) - - // 分析概要位置 - // xmind 支持合并概要,现在的组件不支持合并概要,分别拆分开来 - let summariesPosition = {} - if (typeof summariesNode != 'undefined' && summariesNode.elements && summariesNode.elements.length > 0) { - summariesNode.elements.forEach(item => { - // 使用正则表达式提取位置数字,例如 (2,3) - const match = item.attributes.range.match(/\((\d+),(\d+)\)/) - const firstNumber = match ? parseInt(match[1], 10) : null - const secondNumber = match ? parseInt(match[2], 10) : null - summariesPosition[item.attributes['topic-id']] = [] - for (let i = firstNumber; i <= secondNumber; i++) { - summariesPosition[item.attributes['topic-id']].push(i) - } - }) - } - - let summariesPositionData = {} - if (summaryNode !== null && summaryNode.elements && summaryNode.elements.length > 0) { - summaryNode.elements.forEach(summary => { - if (Object.prototype.hasOwnProperty.call(summariesPosition, summary.attributes.id)) { - summariesPosition[summary.attributes.id].forEach (index => { - summariesPositionData[index] = summary - }) - } - }) - } - - _children.elements.forEach((item, index) => { - if (item.name === 'topics') { - if (item.attributes.type !== 'summary') { - (item.elements || []).forEach((item2, index2) => { - let newChild = {} - let currentSummary = null - if (Object.prototype.hasOwnProperty.call(summariesPositionData, index2)) { - currentSummary = summariesPositionData[index2] - } - newNode.children.push(newChild) - item2.currentSummary = currentSummary - walk(item2, newChild) - }) - } - } else { - let newChild = {} - let currentSummary = null - if (Object.prototype.hasOwnProperty.call(summariesPositionData, index)) { - currentSummary = summariesPositionData[index] - } - newNode.children.push(newChild) - item.currentSummary = currentSummary - walk(item, newChild) + if ( + childrenItem && + childrenItem.elements && + childrenItem.elements.length > 0 + ) { + const children = getElementsByType(childrenItem.elements, 'attached') + children.forEach((item, index) => { + const newChild = {} + newNode.children.push(newChild) + if (childrenSummary[index]) { + newChild._summary = childrenSummary[index] } + walk(item, newChild) }) } } @@ -323,190 +218,17 @@ const transformToXmind = async (data, name) => { const id = 'simpleMindMap_' + Date.now() const imageList = [] // 转换核心数据 - let newTree = { - legend: {}, - topicPositioning: "fixed", - theme: { - "boundary": { - "styleId": "94a6c549-690c-4f1e-b18f-457f114abcb0", - "type": "boundary", - "properties": { - "fo:font-style": "normal", - "svg:fill": "#D5E9FC", - "fo:font-family": "NeverMind", - "shape-class": "org.xmind.boundaryShape.roundedRect", - "fo:font-size": "13pt", - "fo:color": "#FFFFFF", - "fo:font-weight": "500", - "line-pattern": "dash", - "line-color": "#0288D1" - } - }, - "subTopic": { - "styleId": "ddd92ae3-f2b4-48ee-9e47-2dacee6acac4", - "type": "topic", - "properties": { - "fo:text-align": "left", - "fo:font-style": "normal", - "svg:fill": "none", - "fo:font-family": "NeverMind", - "fo:font-size": "13pt", - "shape-class": "org.xmind.topicShape.underline", - "fo:color": "#333333", - "fo:font-weight": "500", - "line-class": "org.xmind.branchConnection.roundedElbow" - } - }, - "summary": { - "styleId": "8aa88864-8667-4627-a763-a84973e0109b", - "type": "summary", - "properties": { - "line-width": "2", - "shape-class": "org.xmind.summaryShape.round", - "line-color": "#0288D1" - } - }, - "calloutTopic": { - "styleId": "9bffcc6a-0526-4db5-b304-ea3830a42846", - "type": "topic", - "properties": { - "fo:font-style": "normal", - "fo:font-family": "NeverMind", - "fo:font-size": "13pt", - "fo:color": "#FFFFFF", - "fo:font-weight": "600", - "callout-shape-class": "org.xmind.calloutTopicShape.balloon.roundedRect" - } - }, - "summaryTopic": { - "styleId": "a82dc72d-29bc-4d81-bd44-3f1bee0ed35c", - "type": "topic", - "properties": { - "fo:font-style": "normal", - "line-width": "1", - "svg:fill": "#333333", - "fo:font-family": "NeverMind", - "fo:font-size": "13pt", - "shape-class": "org.xmind.topicShape.roundedRect", - "border-line-width": "1", - "fo:font-weight": "600", - "line-class": "org.xmind.branchConnection.roundedElbow", - "border-line-color": "none", - "line-color": "#333333" - } - }, - "floatingTopic": { - "styleId": "8286b472-0630-4970-8bf1-510a831dc2b9", - "type": "topic", - "properties": { - "fo:font-style": "normal", - "line-width": "1", - "svg:fill": "#333333", - "fo:font-family": "NeverMind", - "fo:font-size": "13pt", - "shape-class": "org.xmind.topicShape.roundedRect", - "border-line-width": "0", - "fo:color": "#FFFFFF", - "fo:font-weight": "600", - "line-class": "org.xmind.branchConnection.roundedElbow", - "border-line-color": "none", - "line-color": "#333333" - } - }, - "importantTopic": { - "type": "topic", - "properties": { - "svg:fill": "#FFFF00", - "fo:color": "#333333", - "fo:font-weight": "bold" - } - }, - "expiredTopic": { - "type": "topic", - "properties": { - "fo:font-style": "italic", - "fo:text-decoration": " line-through" - } - }, - "centralTopic": { - "styleId": "9a94e1a0-7e67-48df-a231-7fa0c60b7b97", - "type": "topic", - "properties": { - "fo:font-style": "normal", - "line-width": "2", - "svg:fill": "#0288D1", - "fo:font-family": "NeverMind", - "fo:font-size": "28pt", - "shape-class": "org.xmind.topicShape.roundedRect", - "border-line-width": "0", - "fo:font-weight": "600", - "line-class": "org.xmind.branchConnection.curve", - "line-color": "#333333" - } - }, - "mainTopic": { - "styleId": "7565fe37-2200-4483-b343-91bdf53e563e", - "type": "topic", - "properties": { - "fo:font-style": "normal", - "fo:text-align": "left", - "line-width": "1", - "fo:font-family": "NeverMind", - "fo:font-size": "20pt", - "border-line-width": "2", - "fo:font-weight": "600", - "line-class": "org.xmind.branchConnection.roundedElbow", - "line-color": "#333333", - "border-line-color": "#333333" - } - }, - "id": "6518e97a4149b5f96691ab3b5d", - "relationship": { - "styleId": "1611e291-8cc1-4500-93a5-69281d5bedf0", - "type": "relationship", - "properties": { - "line-width": "2", - "fo:font-family": "NeverMind", - "shape-class": "org.xmind.relationshipShape.curved", - "fo:font-size": "13pt", - "fo:color": "#333333", - "fo:font-weight": "normal", - "line-color": "#0288D1" - } - }, - "minorTopic": { - "type": "topic", - "properties": { - "svg:fill": "#FFCB88", - "fo:color": "#333333", - "fo:font-weight": "bold" - } - }, - "map": { - "styleId": "7e90467a-d643-4fa9-84f2-f8f0166e5afb", - "type": "map", - "properties": {} - } - }, - id, - } - + let newTree = {} let waitLoadImageList = [] - let walk = async (node, newNode, isRoot) => { let newData = { id: node.data.uid, - title: getTextFromHtml(node.data.text), - extensions: [], - markers: [], - labels: [], - notes: {}, - comments: [], + structureClass: 'org.xmind.ui.logic.right', + title: getTextFromHtml(node.data.text), // 节点文本 children: { - attached: [], - }, + attached: [] + } } - // 备注 if (node.data.note !== undefined) { newData.notes = { @@ -526,113 +248,64 @@ const transformToXmind = async (data, name) => { if (node.data.tag !== undefined) { newData.labels = node.data.tag || [] } - // 图片 - if (node.data.image) { - // 处理异步逻辑 - let resolve = null - let promise = new Promise(_resolve => { - resolve = _resolve - }) - waitLoadImageList.push(promise) - try { - let imgName = '' - let imgData = node.data.image - // base64之外的其他图片要先转换成data:url - if (!/^data:/.test(node.data.image)) { - imgData = await imgToDataUrl(node.data.image) - } - // 从data:url中解析出图片类型和ase64 - let dataUrlRes = parseDataUrl(imgData) - imgName = 'image_' + imageList.length + '.' + dataUrlRes.type - imageList.push({ - name: imgName, - data: dataUrlRes.base64 - }) - newData.image = { - src: 'xap:resources/' + imgName, - width: node.data.imageSize.width, - height: node.data.imageSize.height - } - resolve() - } catch (error) { - console.log(error) - resolve() - } - } - + handleNodeImageToXmind(node, newNode, waitLoadImageList, imageList) // 样式 // 暂时不考虑样式 if (isRoot) { + newData.class = 'topic' + newNode.id = id + newNode.class = 'sheet' newNode.title = name - newNode.structureClass = "org.xmind.ui.map.unbalanced" + newNode.extensions = [] + newNode.topicPositioning = 'fixed' + newNode.topicOverlapping = 'overlap' + newNode.coreVersion = '2.100.0' newNode.rootTopic = newData } else { Object.keys(newData).forEach(key => { newNode[key] = newData[key] }) } + // 概要 + const { summary, summaries } = parseNodeGeneralizationToXmind(node) + if (isRoot) { + if (summaries.length > 0) { + newNode.rootTopic.children.summary = summary + newNode.rootTopic.summaries = summaries + } + } else { + if (summaries.length > 0) { + newNode.children.summary = summary + newNode.summaries = summaries + } + } + // 子节点 if (node.children && node.children.length > 0) { - let summary = [] - let summaries = [] - node.children.forEach((child,index) => { - if (child.data.generalization) { - let summaryTopicId = node.data.uid+'_topic_id_' + index - let summaryTitle = getTextFromHtml(child.data.generalization.text) - summary.push({ - id: summaryTopicId, - title: summaryTitle, - attributedTitle: [ - { - text: summaryTitle - } - ] - }) - - summaries.push({ - id: node.data.uid+'_range_id_' + index, - range: "("+index+","+index+")", - topicId: summaryTopicId - }) - } + node.children.forEach(child => { let newChild = {} walk(child, newChild) newData.children.attached.push(newChild) }) - - if (isRoot) { - if (summaries.length > 0) { - newNode.rootTopic.children.summary = summary - newNode.rootTopic.summaries = summaries - } - } else { - if (summaries.length > 0) { - newNode.children.summary = summary - newNode.summaries = summaries - } - } } } walk(data, newTree, true) - await Promise.all(waitLoadImageList) - const contentData = [newTree] - // 创建压缩包 const zip = new JSZip() zip.file('content.json', JSON.stringify(contentData)) zip.file( 'metadata.json', - `{"dataStructureVersion":"2","creator":{"name":"mind-map"},"layoutEngineVersion":"3"}` + `{"modifier":"","dataStructureVersion":"2","creator":{"name":"mind-map"},"layoutEngineVersion":"3","activeSheetId":"${id}"}` ) - zip.file( - 'content.xml', - ` Warning 警告 Attention Warnung 경고 This file can not be opened normally, please do not modify and save, otherwise the contents will be permanently lost! You can try using XMind 8 Update 3 or later version to open 该文件无法正常打开,请勿修改并保存,否则文件内容将会永久性丢失! 你可以尝试使用 XMind 8 Update 3 或更新版本打开 該文件無法正常打開,請勿修改並保存,否則文件內容將會永久性丟失! 你可以嘗試使用 XMind 8 Update 3 或更新版本打開 この文書は正常に開かないので、修正して保存しないようにしてください。そうでないと、書類の内容が永久に失われます。! XMind 8 Update 3 や更新版を使って開くこともできます Datei kann nicht richtig geöffnet werden. Bitte ändern Sie diese Datei nicht und speichern Sie sie, sonst wird die Datei endgültig gelöscht werden. Bitte versuchen Sie, diese Datei mit XMind 8 Update 3 oder später zu öffnen. Ce fichier ne peut pas ouvert normalement, veuillez le rédiger et sauvegarder, sinon le fichier sera perdu en permanence. Vous pouvez essayer d'ouvrir avec XMind 8 Update 3 ou avec une version plus récente. 파일을 정상적으로 열 수 없으며, 수정 및 저장하지 마십시오. 그렇지 않으면 파일의 내용이 영구적으로 손실됩니다! XMind 8 Update 3 또는 이후 버전을 사용하여 -1 Sheet 1 ` - ) - + zip.file('content.xml', getXmindContentXmlData()) const manifestData = { - 'file-entries': { 'content.json': {}, 'metadata.json': {}, 'Thumbnails/thumbnail.png':{} } + 'file-entries': { + 'content.json': {}, + 'metadata.json': {}, + 'Thumbnails/thumbnail.png': {} + } } // 图片 if (imageList.length > 0) { diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index eac4b3de..917b308e 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -737,6 +737,56 @@ export const checkHasSupSubRelation = list => { return false } +// 解析要添加概要的节点实例列表 +export const parseAddGeneralizationNodeList = 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 => { + if (cache[uid].length > 1) { + const rangeList = cache[uid] + .map(item => { + return item.index + }) + .sort((a, b) => { + return a - b + }) + res.push({ + node: uidToParent[uid], + range: [rangeList[0], rangeList[rangeList.length - 1]] + }) + } else { + res.push({ + node: cache[uid][0].node + }) + } + }) + return res +} + // 判断两个矩形是否重叠 export const checkTwoRectIsOverlap = ( minx1, diff --git a/simple-mind-map/src/utils/xmind.js b/simple-mind-map/src/utils/xmind.js new file mode 100644 index 00000000..373bd7da --- /dev/null +++ b/simple-mind-map/src/utils/xmind.js @@ -0,0 +1,256 @@ +import { + getImageSize, + imgToDataUrl, + parseDataUrl, + getTextFromHtml, + createUid +} from './index' + +// 解析出新xmind的概要文本 +export const getSummaryText = (node, topicId) => { + if (node.children.summary && node.children.summary.length > 0) { + for (let i = 0; i < node.children.summary.length; i++) { + const cur = node.children.summary[i] + if (cur.id === topicId) { + return cur.title + } + } + } +} + +// 解析出旧xmind的概要文本 +export const getSummaryText2 = (item, topicId) => { + const summaryElements = getElementsByType(item.elements, 'summary') + if (summaryElements && summaryElements && summaryElements.length > 0) { + for (let i = 0; i < summaryElements.length; i++) { + const cur = summaryElements[i] + if (cur.attributes.id === topicId) { + return cur.elements && + cur.elements[0] && + cur.elements[0].elements && + cur.elements[0].elements[0] + ? cur.elements[0].elements[0].text + : '' + } + } + } + return '' +} + +// 解析旧版xmind数据时,找出根节点 +export const getRoot = list => { + let root = null + const walk = arr => { + if (!arr) return + for (let i = 0; i < arr.length; i++) { + if (!root && arr[i].name === 'topic') { + root = arr[i] + return + } + } + arr.forEach(item => { + walk(item.elements) + }) + } + walk(list) + return root +} + +// 解析旧版xmind数据,从一个数组中根据name找出该项 +export const getItemByName = (arr, name) => { + return arr.find(item => { + return item.name === name + }) +} + +// 解析旧版xmind数据,从一个数组中根据attributes.type找出该项 +export const getElementsByType = (arr, type) => { + return arr.find(el => { + return el.attributes.type === type + }).elements +} + +// 解析xmind数据,将概要转换为smm支持的结构 +export const addSummaryData = (selfList, childrenList, getText, range) => { + const summaryData = { + expand: true, + isActive: false, + text: getText(), + range: null + } + const match = range.match(/\((\d+),(\d+)\)/) + if (match) { + const startIndex = Number(match[1]) + const endIndex = Number(match[2]) + if (startIndex === endIndex) { + childrenList[startIndex] = summaryData + } else { + summaryData.range = [startIndex, endIndex] + selfList.push(summaryData) + } + } else { + selfList.push(summaryData) + } +} + +// 解析xmind数据时,解析其中的图片数据 +export const handleNodeImageFromXmind = async ( + node, + newNode, + promiseList, + files +) => { + if (node.image && /\.(jpg|jpeg|png|gif|webp)$/.test(node.image.src)) { + // 处理异步逻辑 + let resolve = null + const promise = new Promise(_resolve => { + resolve = _resolve + }) + promiseList.push(promise) + try { + // 读取图片 + const imageType = /\.([^.]+)$/.exec(node.image.src)[1] + const imageBase64 = + `data:image/${imageType};base64,` + + (await files['resources/' + node.image.src.split('/')[1]].async( + 'base64' + )) + newNode.data.image = imageBase64 + // 如果图片尺寸不存在 + if (!node.image.width && !node.image.height) { + const imageSize = await getImageSize(imageBase64) + newNode.data.imageSize = { + width: imageSize.width, + height: imageSize.height + } + } else { + newNode.data.imageSize = { + width: node.image.width, + height: node.image.height + } + } + resolve() + } catch (error) { + console.log(error) + resolve() + } + } +} + +// 导出为xmind时,处理图片数据 +export const handleNodeImageToXmind = async ( + node, + newData, + promiseList, + imageList +) => { + if (node.data.image) { + // 处理异步逻辑 + let resolve = null + let promise = new Promise(_resolve => { + resolve = _resolve + }) + promiseList.push(promise) + try { + let imgName = '' + let imgData = node.data.image + console.log(1, imgData) + // base64之外的其他图片要先转换成data:url + if (!/^data:/.test(node.data.image)) { + imgData = await imgToDataUrl(node.data.image) + console.log(2, imgData) + } + // 从data:url中解析出图片类型和ase64 + let dataUrlRes = parseDataUrl(imgData) + console.log(3, dataUrlRes) + imgName = 'image_' + imageList.length + '.' + dataUrlRes.type + imageList.push({ + name: imgName, + data: dataUrlRes.base64 + }) + newData.image = { + src: 'xap:resources/' + imgName, + width: node.data.imageSize.width, + height: node.data.imageSize.height + } + resolve() + } catch (error) { + console.log(error) + resolve() + } + } +} + +export const getXmindContentXmlData = () => { + return ` Warning 警告 Attention Warnung 경고 This file can not be opened normally, please do not modify and save, otherwise the contents will be permanently lost! You can try using XMind 8 Update 3 or later version to open 该文件无法正常打开,请勿修改并保存,否则文件内容将会永久性丢失! 你可以尝试使用 XMind 8 Update 3 或更新版本打开 該文件無法正常打開,請勿修改並保存,否則文件內容將會永久性丟失! 你可以嘗試使用 XMind 8 Update 3 或更新版本打開 この文書は正常に開かないので、修正して保存しないようにしてください。そうでないと、書類の内容が永久に失われます。! XMind 8 Update 3 や更新版を使って開くこともできます Datei kann nicht richtig geöffnet werden. Bitte ändern Sie diese Datei nicht und speichern Sie sie, sonst wird die Datei endgültig gelöscht werden. Bitte versuchen Sie, diese Datei mit XMind 8 Update 3 oder später zu öffnen. Ce fichier ne peut pas ouvert normalement, veuillez le rédiger et sauvegarder, sinon le fichier sera perdu en permanence. Vous pouvez essayer d'ouvrir avec XMind 8 Update 3 ou avec une version plus récente. 파일을 정상적으로 열 수 없으며, 수정 및 저장하지 마십시오. 그렇지 않으면 파일의 내용이 영구적으로 손실됩니다! XMind 8 Update 3 또는 이후 버전을 사용하여 -1 Sheet 1 ` +} + +// 获取节点的概要列表 +const formatGetGeneralization = data => { + const generalization = data.generalization + return Array.isArray(generalization) + ? generalization + : generalization + ? [generalization] + : [] +} + +// 获取节点自身的概要,非子节点区间 +const getSelfGeneralization = data => { + const list = formatGetGeneralization(data) + return list.filter(item => { + return !item.range || item.range.length <= 0 + }) +} + +// 获取节点区间概要 +const getRangeGeneralization = data => { + const list = formatGetGeneralization(data) + return list.filter(item => { + return item.range && item.range.length > 0 + }) +} + +// 导出为xmind时,将概要转换为xmind的格式 +export const parseNodeGeneralizationToXmind = node => { + const summary = [] + const summaries = [] + const collectSummary = (item, startIndex, endIndex) => { + const summaryTopicId = createUid() + const summaryTitle = getTextFromHtml(item.text) + summary.push({ + id: summaryTopicId, + title: summaryTitle, + attributedTitle: [ + { + text: summaryTitle + } + ] + }) + summaries.push({ + id: createUid(), + range: '(' + startIndex + ',' + endIndex + ')', + topicId: summaryTopicId + }) + } + // 在xmind中,概要都是保存在父节点的 + // 而在simple-mind-map中,区间概要保存在父节点中,不带区间的保存在自身 + // 所以先要过滤出自身的区间概要 + const generalizationList = getRangeGeneralization(node.data) + generalizationList.forEach(item => { + collectSummary(item, item.range[0], item.range[1]) + }) + + // 遍历子节点,找出子节点自身的概要 + ;(node.children || []).forEach((child, childIndex) => { + const list = getSelfGeneralization(child.data) + list.forEach(item => { + collectSummary(item, childIndex, childIndex) + }) + }) + + return { + summary, + summaries + } +} diff --git a/web/src/pages/Doc/catalogList.js b/web/src/pages/Doc/catalogList.js index 89167f03..3be383c5 100644 --- a/web/src/pages/Doc/catalogList.js +++ b/web/src/pages/Doc/catalogList.js @@ -11,7 +11,7 @@ let langList = [ } ] let StartList = ['introduction', 'start', 'deploy', 'client', 'translate', 'changelog'] -let CourseList = new Array(24).fill(0).map((_, index) => { +let CourseList = new Array(25).fill(0).map((_, index) => { return 'course' + (index + 1) }) let APIList = [ diff --git a/web/src/pages/Doc/routerList.js b/web/src/pages/Doc/routerList.js index 706eb1d5..4d79cbe0 100644 --- a/web/src/pages/Doc/routerList.js +++ b/web/src/pages/Doc/routerList.js @@ -31,6 +31,7 @@ export default [ { path: 'course22', title: '如何实现搜索、替换' }, { path: 'course23', title: '如何渲染滚动条' }, { path: 'course24', title: '如何开发一个插件' }, + { path: 'course25', title: '关于概要' }, { path: 'doExport', title: 'Export 插件' }, { path: 'drag', title: 'Drag插件' }, { path: 'introduction', title: '简介' }, diff --git a/web/src/pages/Doc/zh/course25/index.md b/web/src/pages/Doc/zh/course25/index.md new file mode 100644 index 00000000..b1bf7123 --- /dev/null +++ b/web/src/pages/Doc/zh/course25/index.md @@ -0,0 +1,17 @@ +# 关于概要 + +概要的功能非常的不完善,具体如下: + +1.选中单个节点添加单个概要。 + +2.同时选中多个节点添加概要时,具有同一个父节点的子节点的概要会被合并成一个,称为区间概要,其他的仍旧作为单个概要。 + +3.给子节点添加了区间概要时自身无法再添加单个概要。 + +4.区间概要不会随着区间内的子节点变化而变化,只会在子节点数量无法满足区间时自动删除该区间概要。 + +5.概要节点后面无法再继续添加概要。 + +6.概要节点间、概要节点和普通节点可能会冲突重叠。 + +概要功能不会再完善,所以对概要功能要求高的项目请谨慎选择`simple-mind-map`。 \ No newline at end of file diff --git a/web/src/pages/Doc/zh/course25/index.vue b/web/src/pages/Doc/zh/course25/index.vue new file mode 100644 index 00000000..40208ebf --- /dev/null +++ b/web/src/pages/Doc/zh/course25/index.vue @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file