mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-17 22:08:25 +08:00
Feat:新增演示插件
This commit is contained in:
@@ -318,5 +318,7 @@ export const defaultOpt = {
|
||||
}
|
||||
*/
|
||||
addContentToHeader: null,
|
||||
addContentToFooter: null
|
||||
addContentToFooter: null,
|
||||
// 演示插件配置
|
||||
demonstrateConfig: null
|
||||
}
|
||||
|
||||
@@ -23,6 +23,18 @@ class Command {
|
||||
this.mindMap.opt.addHistoryTime,
|
||||
this
|
||||
)
|
||||
// 是否暂停收集历史数据
|
||||
this.isPause = false
|
||||
}
|
||||
|
||||
// 暂停收集历史数据
|
||||
pause() {
|
||||
this.isPause = true
|
||||
}
|
||||
|
||||
// 恢复收集历史数据
|
||||
recovery() {
|
||||
this.isPause = false
|
||||
}
|
||||
|
||||
// 清空历史数据
|
||||
@@ -88,7 +100,7 @@ class Command {
|
||||
|
||||
// 添加回退数据
|
||||
addHistory() {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
if (this.mindMap.opt.readonly || this.isPause) {
|
||||
return
|
||||
}
|
||||
const lastData =
|
||||
|
||||
@@ -1506,7 +1506,7 @@ class Render {
|
||||
}
|
||||
|
||||
// 收起所有
|
||||
unexpandAllNode() {
|
||||
unexpandAllNode(isSetRootNodeCenter = true) {
|
||||
if (!this.renderTree) return
|
||||
walk(
|
||||
this.renderTree,
|
||||
@@ -1522,7 +1522,9 @@ class Render {
|
||||
0
|
||||
)
|
||||
this.mindMap.render(() => {
|
||||
this.setRootNodeCenter()
|
||||
if (isSetRootNodeCenter) {
|
||||
this.setRootNodeCenter()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -280,11 +280,12 @@ class View {
|
||||
}
|
||||
|
||||
// 适应画布大小
|
||||
fit() {
|
||||
const { fitPadding } = this.mindMap.opt
|
||||
fit(getRbox = () => {}, enlarge = false, fitPadding) {
|
||||
fitPadding =
|
||||
fitPadding === undefined ? this.mindMap.opt.fitPadding : fitPadding
|
||||
const draw = this.mindMap.draw
|
||||
const origTransform = draw.transform()
|
||||
const rect = draw.rbox()
|
||||
const rect = getRbox() || draw.rbox()
|
||||
const drawWidth = rect.width / origTransform.scaleX
|
||||
const drawHeight = rect.height / origTransform.scaleY
|
||||
const drawRatio = drawWidth / drawHeight
|
||||
@@ -294,7 +295,7 @@ class View {
|
||||
const elRatio = elWidth / elHeight
|
||||
let newScale = 0
|
||||
let flag = ''
|
||||
if (drawWidth <= elWidth && drawHeight <= elHeight) {
|
||||
if (drawWidth <= elWidth && drawHeight <= elHeight && !enlarge) {
|
||||
newScale = 1
|
||||
flag = 1
|
||||
} else {
|
||||
@@ -312,7 +313,7 @@ class View {
|
||||
newScale = newWidth / drawWidth
|
||||
}
|
||||
this.setScale(newScale)
|
||||
const newRect = draw.rbox()
|
||||
const newRect = getRbox() || draw.rbox()
|
||||
// 需要考虑画布容器距浏览器窗口左上角的距离
|
||||
newRect.x -= this.mindMap.elRect.left
|
||||
newRect.y -= this.mindMap.elRect.top
|
||||
|
||||
309
simple-mind-map/src/plugins/Demonstrate.js
Normal file
309
simple-mind-map/src/plugins/Demonstrate.js
Normal file
@@ -0,0 +1,309 @@
|
||||
import {
|
||||
walk,
|
||||
getNodeTreeBoundingRect,
|
||||
fullscrrenEvent,
|
||||
fullScreen,
|
||||
exitFullScreen
|
||||
} from '../utils/index'
|
||||
import { keyMap } from '../core/command/keyMap'
|
||||
|
||||
const defaultConfig = {
|
||||
boxShadowColor: 'rgba(0, 0, 0, 0.8)', // 高亮框四周的区域颜色
|
||||
borderRadius: '5px', // 高亮框的圆角大小
|
||||
transition: 'all 0.3s ease-out', // 高亮框动画的过渡
|
||||
zIndex: 9999, // 高亮框元素的层级
|
||||
padding: 20, // 高亮框的内边距
|
||||
margin: 50 // 高亮框的外边距
|
||||
}
|
||||
|
||||
// 演示插件
|
||||
class Demonstrate {
|
||||
constructor(opt) {
|
||||
this.mindMap = opt.mindMap
|
||||
this.stepList = []
|
||||
this.currentStepIndex = 0
|
||||
this.maskEl = null
|
||||
this.highlightEl = null
|
||||
this.transformState = null
|
||||
this.renderTree = null
|
||||
this.config = Object.assign(
|
||||
{ ...defaultConfig },
|
||||
this.mindMap.opt.demonstrateConfig || {}
|
||||
)
|
||||
}
|
||||
|
||||
// 进入演示模式
|
||||
enter() {
|
||||
// 全屏
|
||||
this.bindFullscreenEvent()
|
||||
// 如果已经全屏了
|
||||
if (document.fullscreenElement === this.mindMap.el) {
|
||||
this._enter()
|
||||
} else {
|
||||
// 否则申请全屏
|
||||
fullScreen(this.mindMap.el)
|
||||
}
|
||||
}
|
||||
|
||||
_enter() {
|
||||
// 记录演示前的画布状态
|
||||
this.transformState = this.mindMap.view.getTransformData()
|
||||
// 记录演示前的画布数据
|
||||
this.renderTree = this.mindMap.getData()
|
||||
// 暂停收集历史记录
|
||||
this.mindMap.command.pause()
|
||||
// 暂停思维导图快捷键响应
|
||||
this.mindMap.keyCommand.pause()
|
||||
// 创建高亮元素
|
||||
this.createHighlightEl()
|
||||
// 计算步骤数据
|
||||
this.getStepList()
|
||||
// 收起所有节点
|
||||
this.mindMap.execCommand('UNEXPAND_ALL', false)
|
||||
const onRenderEnd = () => {
|
||||
this.mindMap.off('node_tree_render_end', onRenderEnd)
|
||||
// 聚焦到第一步
|
||||
this.jump(this.currentStepIndex)
|
||||
this.bindEvent()
|
||||
}
|
||||
this.mindMap.on('node_tree_render_end', onRenderEnd)
|
||||
}
|
||||
|
||||
// 退出演示模式
|
||||
exit() {
|
||||
exitFullScreen(this.mindMap.el)
|
||||
this.mindMap.updateData(this.renderTree)
|
||||
this.mindMap.view.setTransformData(this.transformState)
|
||||
this.renderTree = null
|
||||
this.transformState = null
|
||||
this.stepList = []
|
||||
this.currentStepIndex = 0
|
||||
this.unBindEvent()
|
||||
this.removeHighlightEl()
|
||||
this.mindMap.command.recovery()
|
||||
this.mindMap.keyCommand.recovery()
|
||||
this.mindMap.emit('exit_demonstrate')
|
||||
}
|
||||
|
||||
// 创建高亮元素
|
||||
createHighlightEl() {
|
||||
if (!this.highlightEl) {
|
||||
// 遮罩元素
|
||||
this.maskEl = document.createElement('div')
|
||||
this.maskEl.style.cssText = `
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: ${this.config.zIndex};
|
||||
`
|
||||
this.mindMap.el.appendChild(this.maskEl)
|
||||
// 高亮元素
|
||||
this.highlightEl = document.createElement('div')
|
||||
this.highlightEl.style.cssText = `
|
||||
position: absolute;
|
||||
box-shadow: 0 0 0 5000px ${this.config.boxShadowColor};
|
||||
border-radius: ${this.config.borderRadius};
|
||||
transition: ${this.config.transition};
|
||||
z-index: ${this.config.zIndex + 1};
|
||||
`
|
||||
this.mindMap.el.appendChild(this.highlightEl)
|
||||
}
|
||||
}
|
||||
|
||||
// 移除高亮元素
|
||||
removeHighlightEl() {
|
||||
if (this.highlightEl) {
|
||||
this.mindMap.el.removeChild(this.highlightEl)
|
||||
this.highlightEl = null
|
||||
}
|
||||
if (this.maskEl) {
|
||||
this.mindMap.el.removeChild(this.maskEl)
|
||||
this.maskEl = null
|
||||
}
|
||||
}
|
||||
|
||||
// 更新高亮元素的位置和大小
|
||||
updateHighlightEl({ left, top, width, height }) {
|
||||
const padding = this.config.padding
|
||||
if (left) {
|
||||
this.highlightEl.style.left = left - padding + 'px'
|
||||
}
|
||||
if (top) {
|
||||
this.highlightEl.style.top = top - padding + 'px'
|
||||
}
|
||||
if (width) {
|
||||
this.highlightEl.style.width = width + padding * 2 + 'px'
|
||||
}
|
||||
if (height) {
|
||||
this.highlightEl.style.height = height + padding * 2 + 'px'
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bindEvent() {
|
||||
this.onKeydown = this.onKeydown.bind(this)
|
||||
window.addEventListener('keydown', this.onKeydown)
|
||||
}
|
||||
|
||||
// 绑定全屏事件
|
||||
bindFullscreenEvent() {
|
||||
this.onFullscreenChange = this.onFullscreenChange.bind(this)
|
||||
document.addEventListener(fullscrrenEvent, this.onFullscreenChange)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
unBindEvent() {
|
||||
window.removeEventListener('keydown', this.onKeydown)
|
||||
document.removeEventListener(fullscrrenEvent, this.onFullscreenChange)
|
||||
}
|
||||
|
||||
// 全屏状态改变
|
||||
onFullscreenChange() {
|
||||
if (!document.fullscreenElement) {
|
||||
this.exit()
|
||||
} else if (document.fullscreenElement === this.mindMap.el) {
|
||||
this._enter()
|
||||
}
|
||||
}
|
||||
|
||||
// 按键事件
|
||||
onKeydown(e) {
|
||||
// 上一个
|
||||
if (e.keyCode === keyMap.Left) {
|
||||
this.prev()
|
||||
} else if (e.keyCode === keyMap.Right) {
|
||||
// 下一个
|
||||
this.next()
|
||||
} else if (e.keyCode === keyMap.Esc) {
|
||||
// 退出演示
|
||||
this.exit()
|
||||
}
|
||||
}
|
||||
|
||||
// 上一张
|
||||
prev() {
|
||||
if (this.currentStepIndex > 0) {
|
||||
this.jump(this.currentStepIndex - 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 下一张
|
||||
next() {
|
||||
const stepLength = this.stepList.length
|
||||
if (this.currentStepIndex < stepLength - 1) {
|
||||
this.jump(this.currentStepIndex + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到某一张
|
||||
jump(index) {
|
||||
this.currentStepIndex = index
|
||||
this.mindMap.emit(
|
||||
'demonstrate_jump',
|
||||
this.currentStepIndex,
|
||||
this.stepList.length
|
||||
)
|
||||
const step = this.stepList[index]
|
||||
// 这一步的节点数据
|
||||
const nodeData = step.node
|
||||
// 该节点的uid
|
||||
const uid = nodeData.data.uid
|
||||
// 根据uid在画布上找到该节点实例
|
||||
const node = this.mindMap.renderer.findNodeByUid(uid)
|
||||
// 如果该节点实例不存在,那么先展开到该节点
|
||||
if (!node) {
|
||||
this.mindMap.renderer.expandToNodeUid(uid, () => {
|
||||
this.jump(index)
|
||||
})
|
||||
return
|
||||
}
|
||||
// 1.聚焦到某个节点
|
||||
if (step.type === 'node') {
|
||||
// 适应画布大小
|
||||
this.mindMap.view.fit(
|
||||
() => {
|
||||
return node.group.rbox()
|
||||
},
|
||||
true,
|
||||
this.config.padding + this.config.margin
|
||||
)
|
||||
const rect = node.group.rbox()
|
||||
this.updateHighlightEl({
|
||||
left: rect.x,
|
||||
top: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
})
|
||||
} else {
|
||||
// 2.聚焦到某个节点的所有子节点
|
||||
// 聚焦该节点的所有子节点
|
||||
const task = () => {
|
||||
// 先收起该节点所有子节点的子节点
|
||||
nodeData.children.forEach(item => {
|
||||
item.data.expand = false
|
||||
})
|
||||
this.mindMap.render(() => {
|
||||
// 适应画布大小
|
||||
this.mindMap.view.fit(
|
||||
() => {
|
||||
const res = getNodeTreeBoundingRect(node, 0, 0, 0, 0, true)
|
||||
return {
|
||||
...res,
|
||||
x: res.left,
|
||||
y: res.top
|
||||
}
|
||||
},
|
||||
true,
|
||||
this.config.padding + this.config.margin
|
||||
)
|
||||
const res = getNodeTreeBoundingRect(node, 0, 0, 0, 0, true)
|
||||
this.updateHighlightEl(res)
|
||||
})
|
||||
}
|
||||
// 如果该节点是收起状态,那么需要先展开
|
||||
if (!nodeData.data.expand) {
|
||||
this.mindMap.execCommand('SET_NODE_EXPAND', node, true)
|
||||
const onRenderEnd = () => {
|
||||
this.mindMap.off('node_tree_render_end', onRenderEnd)
|
||||
task()
|
||||
}
|
||||
this.mindMap.on('node_tree_render_end', onRenderEnd)
|
||||
} else {
|
||||
// 否则直接聚焦
|
||||
task()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 深度度优先遍历所有节点,返回步骤列表
|
||||
getStepList() {
|
||||
walk(this.mindMap.renderer.renderTree, null, node => {
|
||||
this.stepList.push({
|
||||
type: 'node',
|
||||
node
|
||||
})
|
||||
if (node.children.length > 1) {
|
||||
this.stepList.push({
|
||||
type: 'children',
|
||||
node
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
|
||||
// 插件被卸载前做的事情
|
||||
beforePluginDestroy() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
}
|
||||
|
||||
Demonstrate.instanceName = 'demonstrate'
|
||||
|
||||
export default Demonstrate
|
||||
@@ -1370,24 +1370,35 @@ export const handleGetSvgDataExtraContent = ({
|
||||
}
|
||||
|
||||
// 获取指定节点的包围框信息
|
||||
export const getNodeTreeBoundingRect = (node, x, y, paddingX, paddingY) => {
|
||||
export const getNodeTreeBoundingRect = (
|
||||
node,
|
||||
x = 0,
|
||||
y = 0,
|
||||
paddingX = 0,
|
||||
paddingY = 0,
|
||||
excludeSelf = false
|
||||
) => {
|
||||
let minX = Infinity
|
||||
let maxX = -Infinity
|
||||
let minY = Infinity
|
||||
let maxY = -Infinity
|
||||
const walk = root => {
|
||||
const { x, y, width, height } = root.group.findOne('.smm-node-shape').rbox()
|
||||
if (x < minX) {
|
||||
minX = x
|
||||
}
|
||||
if (x + width > maxX) {
|
||||
maxX = x + width
|
||||
}
|
||||
if (y < minY) {
|
||||
minY = y
|
||||
}
|
||||
if (y + height > maxY) {
|
||||
maxY = y + height
|
||||
const walk = (root, isRoot) => {
|
||||
if (!(isRoot && excludeSelf)) {
|
||||
const { x, y, width, height } = root.group
|
||||
.findOne('.smm-node-shape')
|
||||
.rbox()
|
||||
if (x < minX) {
|
||||
minX = x
|
||||
}
|
||||
if (x + width > maxX) {
|
||||
maxX = x + width
|
||||
}
|
||||
if (y < minY) {
|
||||
minY = y
|
||||
}
|
||||
if (y + height > maxY) {
|
||||
maxY = y + height
|
||||
}
|
||||
}
|
||||
if (root._generalizationList.length > 0) {
|
||||
root._generalizationList.forEach(item => {
|
||||
@@ -1400,7 +1411,7 @@ export const getNodeTreeBoundingRect = (node, x, y, paddingX, paddingY) => {
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(node)
|
||||
walk(node, true)
|
||||
|
||||
minX = minX - x + paddingX
|
||||
minY = minY - y + paddingY
|
||||
@@ -1414,3 +1425,39 @@ export const getNodeTreeBoundingRect = (node, x, y, paddingX, paddingY) => {
|
||||
height: maxY - minY
|
||||
}
|
||||
}
|
||||
|
||||
// 全屏事件检测
|
||||
const getOnfullscreEnevt = () => {
|
||||
if (document.documentElement.requestFullScreen) {
|
||||
return 'fullscreenchange'
|
||||
} else if (document.documentElement.webkitRequestFullScreen) {
|
||||
return 'webkitfullscreenchange'
|
||||
} else if (document.documentElement.mozRequestFullScreen) {
|
||||
return 'mozfullscreenchange'
|
||||
} else if (document.documentElement.msRequestFullscreen) {
|
||||
return 'msfullscreenchange'
|
||||
}
|
||||
}
|
||||
export const fullscrrenEvent = getOnfullscreEnevt()
|
||||
|
||||
// 全屏
|
||||
export const fullScreen = element => {
|
||||
if (element.requestFullScreen) {
|
||||
element.requestFullScreen()
|
||||
} else if (element.webkitRequestFullScreen) {
|
||||
element.webkitRequestFullScreen()
|
||||
} else if (element.mozRequestFullScreen) {
|
||||
element.mozRequestFullScreen()
|
||||
}
|
||||
}
|
||||
|
||||
// 退出全屏
|
||||
export const exitFullScreen = () => {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen()
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user