Feat:支持拖拽调整节点宽度

This commit is contained in:
街角小林
2024-09-30 16:43:47 +08:00
parent e9058ed67e
commit 4b5a691713
6 changed files with 269 additions and 28 deletions

View File

@@ -330,7 +330,8 @@ export const nodeDataNoStylePropList = [
'number',
'range',
'customLeft',
'customTop'
'customTop',
'customTextWidth'
]
// 错误类型

View File

@@ -251,6 +251,12 @@ export const defaultOpt = {
mousedownEventPreventDefault: true,
// 在激活上粘贴用户剪贴板中的数据时,如果同时存在文本和图片,那么只粘贴文本,忽略图片
onlyPasteTextWhenHasImgAndText: true,
// 是否允许拖拽调整节点的宽度实际上压缩的是节点里面文本内容的宽度当节点文本内容宽度压缩到最小时无法继续压缩。如果节点存在图片那么最小值以图片宽度和文本内容最小宽度的最大值为准目前该特性仅在两种情况下可用1.开启了富文本模式即注册了RichText插件2.自定义节点内容)
enableDragModifyNodeWidth: true,
// 当允许拖拽调整节点的宽度时,可以通过该选项设置节点文本内容允许压缩的最小宽度
minNodeTextModifyWidth: 20,
// 同minNodeTextModifyWidth最大值传-1代表不限制
maxNodeTextModifyWidth: -1,
// 【Select插件】
// 多选节点时鼠标移动到边缘时的画布移动偏移量

View File

@@ -6,6 +6,7 @@ import nodeExpandBtnMethods from './nodeExpandBtn'
import nodeCommandWrapsMethods from './nodeCommandWraps'
import nodeCreateContentsMethods from './nodeCreateContents'
import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect'
import nodeModifyWidthMethods from './nodeModifyWidth'
import nodeCooperateMethods from './nodeCooperate'
import { CONSTANTS } from '../../../constants/constant'
import {
@@ -54,6 +55,8 @@ class MindMapNode {
this.width = opt.width || 0
// 节点高
this.height = opt.height || 0
// 自定义文本的宽度
this.customTextWidth = opt.data.data.customTextWidth || undefined
// left
this._left = opt.left || 0
// top
@@ -150,10 +153,15 @@ class MindMapNode {
proto[item] = nodeCooperateMethods[item]
})
}
// 拖拽调整节点宽度
Object.keys(nodeModifyWidthMethods).forEach(item => {
proto[item] = nodeModifyWidthMethods[item]
})
proto.bindEvent = true
}
// 初始化
this.getSize()
this.initDragHandle()
}
// 支持自定义位置
@@ -197,7 +205,8 @@ class MindMapNode {
}
// 创建节点的各个内容对象数据
createNodeData() {
// recreateTypes[] custom、image、icon、text、hyperlink、tag、note、attachment、numbers、prefix、postfix
createNodeData(recreateTypes) {
// 自定义节点内容
let {
isUseCustomNodeContent,
@@ -205,7 +214,39 @@ class MindMapNode {
createNodePrefixContent,
createNodePostfixContent
} = this.mindMap.opt
if (isUseCustomNodeContent && customCreateNodeContent) {
// 需要创建的内容类型
const typeList = [
'custom',
'image',
'icon',
'text',
'hyperlink',
'tag',
'note',
'attachment',
'numbers',
'prefix',
'postfix'
]
const createTypes = {}
if (Array.isArray(recreateTypes)) {
// 重新创建指定的内容类型
typeList.forEach(item => {
if (recreateTypes.includes(item)) {
createTypes[item] = true
}
})
} else {
// 创建所有类型
typeList.forEach(item => {
createTypes[item] = true
})
}
if (
isUseCustomNodeContent &&
customCreateNodeContent &&
createTypes.custom
) {
this._customNodeContent = customCreateNodeContent(this)
}
// 如果没有返回内容,那么还是使用内置的节点内容
@@ -213,37 +254,42 @@ class MindMapNode {
addXmlns(this._customNodeContent)
return
}
this._imgData = this.createImgNode()
this._iconData = this.createIconNode()
this._textData = this.createTextNode()
this._hyperlinkData = this.createHyperlinkNode()
this._tagData = this.createTagNode()
this._noteData = this.createNoteNode()
this._attachmentData = this.createAttachmentNode()
if (this.mindMap.numbers) {
if (createTypes.image) this._imgData = this.createImgNode()
if (createTypes.icon) this._iconData = this.createIconNode()
if (createTypes.text) this._textData = this.createTextNode()
if (createTypes.hyperlink) this._hyperlinkData = this.createHyperlinkNode()
if (createTypes.tag) this._tagData = this.createTagNode()
if (createTypes.note) this._noteData = this.createNoteNode()
if (createTypes.attachment)
this._attachmentData = this.createAttachmentNode()
if (this.mindMap.numbers && createTypes.numbers) {
this._numberData = this.mindMap.numbers.createNumberContent(this)
}
this._prefixData = createNodePrefixContent
? createNodePrefixContent(this)
: null
if (this._prefixData && this._prefixData.el) {
addXmlns(this._prefixData.el)
if (createTypes.prefix) {
this._prefixData = createNodePrefixContent
? createNodePrefixContent(this)
: null
if (this._prefixData && this._prefixData.el) {
addXmlns(this._prefixData.el)
}
}
this._postfixData = createNodePostfixContent
? createNodePostfixContent(this)
: null
if (this._postfixData && this._postfixData.el) {
addXmlns(this._postfixData.el)
if (createTypes.postfix) {
this._postfixData = createNodePostfixContent
? createNodePostfixContent(this)
: null
if (this._postfixData && this._postfixData.el) {
addXmlns(this._postfixData.el)
}
}
}
// 计算节点的宽高
getSize() {
getSize(recreateTypes) {
this.customLeft = this.getData('customLeft') || undefined
this.customTop = this.getData('customTop') || undefined
// 这里不要更新概要,不然即使概要没修改,每次也会重新渲染
// this.updateGeneralization()
this.createNodeData()
this.createNodeData(recreateTypes)
let { width, height } = this.getNodeRect()
// 判断节点尺寸是否有变化
let changed = this.width !== width || this.height !== height
@@ -258,7 +304,7 @@ class MindMapNode {
if (this.isUseCustomNodeContent()) {
let rect = this.measureCustomNodeContentSize(this._customNodeContent)
return {
width: rect.width,
width: this.hasCustomWidth() ? this.customTextWidth : rect.width,
height: rect.height
}
}
@@ -735,6 +781,8 @@ class MindMapNode {
}
}
}
// 更新拖拽手柄的显示与否
this.updateDragHandle()
// 更新概要
this.renderGeneralization(forceRender)
// 更新协同头像
@@ -770,8 +818,8 @@ class MindMapNode {
}
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
reRender() {
let sizeChange = this.getSize()
reRender(recreateTypes) {
let sizeChange = this.getSize(recreateTypes)
this.layout()
this.update()
return sizeChange
@@ -794,6 +842,7 @@ class MindMapNode {
this.hideExpandBtn()
}
this.updateNodeActiveClass()
this.updateDragHandle()
}
}
@@ -1297,6 +1346,30 @@ class MindMapNode {
createSvgTextNode(text = '') {
return new Text().text(text)
}
// 检查是否支持拖拽调整宽度
// 1.富文本模式
// 2.自定义节点内容
checkEnableDragModifyNodeWidth() {
const {
enableDragModifyNodeWidth,
isUseCustomNodeContent,
customCreateNodeContent
} = this.mindMap.opt
return (
enableDragModifyNodeWidth &&
(this.mindMap.richText ||
(isUseCustomNodeContent && customCreateNodeContent))
)
}
// 是否存在自定义宽度
hasCustomWidth() {
return (
this.checkEnableDragModifyNodeWidth() &&
this.customTextWidth !== undefined
)
}
}
export default MindMapNode

View File

@@ -115,9 +115,11 @@ function createIconNode() {
// 创建富文本节点
function createRichTextNode(specifyText) {
const hasCustomWidth = this.hasCustomWidth()
let text =
typeof specifyText === 'string' ? specifyText : this.getData('text')
const { textAutoWrapWidth, emptyTextMeasureHeightText } = this.mindMap.opt
let { textAutoWrapWidth, emptyTextMeasureHeightText } = this.mindMap.opt
textAutoWrapWidth = hasCustomWidth ? this.customTextWidth : textAutoWrapWidth
let g = new G()
// 重新设置富文本节点内容
let recoverText = false
@@ -172,6 +174,11 @@ function createRichTextNode(specifyText) {
el.classList.add('smm-richtext-node-wrap')
addXmlns(el)
el.style.maxWidth = textAutoWrapWidth + 'px'
if (hasCustomWidth) {
el.style.width = this.customTextWidth + 'px'
} else {
el.style.width = ''
}
let { width, height } = el.getBoundingClientRect()
// 如果文本为空,那么需要计算一个默认高度
if (height <= 0) {

View File

@@ -0,0 +1,151 @@
import { Rect } from '@svgdotjs/svg.js'
// 初始化拖拽
function initDragHandle() {
if (!this.checkEnableDragModifyNodeWidth()) {
return
}
// 拖拽手柄元素
this._dragHandleNodes = null
// 手柄元素的宽度
this.dragHandleWidth = 2
// 鼠标按下时的x坐标
this.dragHandleMousedownX = 0
// 鼠标是否处于按下状态
this.isDragHandleMousedown = false
// 当前拖拽的手柄序号
this.dragHandleIndex = 0
// 鼠标按下时记录当前的customTextWidth值
this.dragHandleMousedownCustomTextWidth = 0
// 鼠标按下时记录当前的手型样式
this.dragHandleMousedownBodyCursor = ''
// 鼠标按下时记录当前节点的left值
this.dragHandleMousedownLeft = 0
this.onDragMousemoveHandle = this.onDragMousemoveHandle.bind(this)
window.addEventListener('mousemove', this.onDragMousemoveHandle)
this.onDragMouseupHandle = this.onDragMouseupHandle.bind(this)
window.addEventListener('mouseup', this.onDragMouseupHandle)
this.mindMap.on('node_mouseup', this.onDragMouseupHandle)
}
// 鼠标移动事件
function onDragMousemoveHandle(e) {
if (!this.isDragHandleMousedown) return
e.stopPropagation()
e.preventDefault()
let {
minNodeTextModifyWidth,
maxNodeTextModifyWidth,
isUseCustomNodeContent,
customCreateNodeContent
} = this.mindMap.opt
const useCustomContent =
isUseCustomNodeContent && customCreateNodeContent && this._customNodeContent
document.body.style.cursor = 'ew-resize'
this.group.css({
cursor: 'ew-resize'
})
const { scaleX } = this.mindMap.draw.transform()
const ox = e.clientX - this.dragHandleMousedownX
let newWidth =
this.dragHandleMousedownCustomTextWidth +
(this.dragHandleIndex === 0 ? -ox : ox) / scaleX
newWidth = Math.max(newWidth, minNodeTextModifyWidth)
if (maxNodeTextModifyWidth !== -1) {
newWidth = Math.min(newWidth, maxNodeTextModifyWidth)
}
// 如果存在图片,那么最小值需要考虑图片宽度
if (!useCustomContent && this.getData('image')) {
const imgSize = this.getImgShowSize()
if (
this._rectInfo.textContentWidth - this.customTextWidth + newWidth <=
imgSize[0]
) {
newWidth =
imgSize[0] + this.customTextWidth - this._rectInfo.textContentWidth
}
}
this.customTextWidth = newWidth
if (this.dragHandleIndex === 0) {
this.left = this.dragHandleMousedownLeft + ox / scaleX
}
// 自定义内容不重新渲染,交给开发者
this.reRender(useCustomContent ? [] : ['text'])
}
// 鼠标松开事件
function onDragMouseupHandle() {
if (!this.isDragHandleMousedown) return
document.body.style.cursor = this.dragHandleMousedownBodyCursor
this.group.css({
cursor: 'default'
})
this.isDragHandleMousedown = false
this.dragHandleMousedownX = 0
this.dragHandleIndex = 0
this.dragHandleMousedownCustomTextWidth = 0
this.setData({
customTextWidth: this.customTextWidth
})
this.mindMap.render()
this.mindMap.emit('dragModifyNodeWidthEnd', this)
}
// 插件拖拽手柄元素
function createDragHandleNode() {
const list = [new Rect(), new Rect()]
list.forEach((node, index) => {
node
.size(this.dragHandleWidth, this.height)
.fill({
color: 'transparent'
})
.css({
cursor: 'ew-resize'
})
node.on('mousedown', e => {
e.stopPropagation()
e.preventDefault()
this.dragHandleMousedownX = e.clientX
this.dragHandleIndex = index
this.dragHandleMousedownCustomTextWidth =
this.customTextWidth === undefined
? this._textData
? this._textData.width
: this.width
: this.customTextWidth
this.dragHandleMousedownBodyCursor = document.body.style.cursor
this.dragHandleMousedownLeft = this.left
this.isDragHandleMousedown = true
})
})
return list
}
// 更新拖拽按钮的显隐和位置尺寸
function updateDragHandle() {
if (!this.checkEnableDragModifyNodeWidth()) return
if (!this._dragHandleNodes) {
this._dragHandleNodes = this.createDragHandleNode()
}
if (this.getData('isActive')) {
this._dragHandleNodes.forEach(node => {
node.height(this.height)
this.group.add(node)
})
this._dragHandleNodes[1].x(this.width - this.dragHandleWidth)
} else {
this._dragHandleNodes.forEach(node => {
node.remove()
})
}
}
export default {
initDragHandle,
onDragMousemoveHandle,
onDragMouseupHandle,
createDragHandleNode,
updateDragHandle
}

View File

@@ -180,7 +180,7 @@ class RichText {
if (this.showTextEdit) {
return
}
const {
let {
richTextEditFakeInPlace,
customInnerElsAppendTo,
nodeTextEditZIndex,
@@ -188,6 +188,9 @@ class RichText {
selectTextOnEnterEditText,
transformRichTextOnEnterEdit
} = this.mindMap.opt
textAutoWrapWidth = node.hasCustomWidth()
? node.customTextWidth
: textAutoWrapWidth
this.node = node
this.isInserting = isInserting
if (!rect) rect = node._textData.node.node.getBoundingClientRect()