协作增加状态同步

This commit is contained in:
wanglin2
2023-09-28 15:32:41 +08:00
parent 1beb03eaa6
commit 1550f032d9
5 changed files with 208 additions and 2 deletions

View File

@@ -204,5 +204,10 @@ export const defaultOpt = {
},
// 自定义标签的颜色
// {pass: 'green, unpass: 'red'}
tagsColorMap: {}
tagsColorMap: {},
// 节点协作样式配置
cooperateStyle: {
avatarSize: 22,// 头像大小
fontSize: 12,// 如果是文字头像,那么文字的大小
}
}

View File

@@ -6,6 +6,7 @@ import nodeExpandBtnMethods from './nodeExpandBtn'
import nodeCommandWrapsMethods from './nodeCommandWraps'
import nodeCreateContentsMethods from './nodeCreateContents'
import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect'
import nodeCooperateMethods from './nodeCooperate'
import { CONSTANTS } from '../../../constants/constant'
// 节点类
@@ -55,6 +56,8 @@ class Node {
this.parent = opt.parent || null
// 子节点
this.children = opt.children || []
// 当前同时操作该节点的用户列表
this.userList = []
// 节点内容的容器
this.group = null
this.shapeNode = null // 节点形状节点
@@ -74,6 +77,7 @@ class Node {
this._openExpandNode = null
this._closeExpandNode = null
this._fillExpandNode = null
this._userListGroup = null
this._lines = []
this._generalizationLine = null
this._generalizationNode = null
@@ -121,6 +125,10 @@ class Node {
Object.keys(nodeCreateContentsMethods).forEach(item => {
this[item] = nodeCreateContentsMethods[item].bind(this)
})
// 协同相关
Object.keys(nodeCooperateMethods).forEach((item) => {
this[item] = nodeCooperateMethods[item].bind(this)
})
// 初始化
this.getSize()
}
@@ -283,6 +291,7 @@ class Node {
this.group.add(this.shapeNode)
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
this.renderExpandBtnPlaceholderRect()
this.createUserListNode()
// 概要节点添加一个带所属节点id的类名
if (this.isGeneralization && this.generalizationBelongNode) {
this.group.addClass('generalization_' + this.generalizationBelongNode.uid)
@@ -527,6 +536,7 @@ class Node {
}
// 更新概要
this.renderGeneralization()
this.updateUserListNode()
// 更新节点位置
let t = this.group.transform()
// // 如果上次不在可视区内,且本次也不在,那么直接返回

View File

@@ -0,0 +1,104 @@
import { Circle, G, Text, Image } from '@svgdotjs/svg.js'
import { generateColorByContent } from '../../../utils/index'
// 协同相关功能
// 创建容器
function createUserListNode() {
// 如果没有注册协作插件,那么需要创建
if (!this.mindMap.cooperate) return
this._userListGroup = new G()
this.group.add(this._userListGroup)
}
// 创建文本头像
function createTextAvatar(item) {
const { avatarSize, fontSize } = this.mindMap.opt.cooperateStyle
const g = new G()
const str = item.isMore ? item.name : String(item.name)[0]
// 圆
const circle = new Circle().size(avatarSize, avatarSize)
circle.fill({
color: item.color || generateColorByContent(str)
})
// 文本
const text = new Text()
.text(str)
.fill({
color: '#fff'
})
.css({
'font-size': fontSize
})
.dx(-fontSize / 2)
.dy((avatarSize - fontSize) / 2)
g.add(circle).add(text)
return g
}
// 创建图片头像
function createImageAvatar(item) {
const { avatarSize } = this.mindMap.opt.cooperateStyle
return new Image().load(item.avatar).size(avatarSize, avatarSize)
}
// 更新渲染
function updateUserListNode() {
if (!this._userListGroup) return
const { avatarSize } = this.mindMap.opt.cooperateStyle
this._userListGroup.clear()
// 根据当前节点长度计算最多能显示几个
const length = this.userList.length
const maxShowCount = Math.floor(this.width / avatarSize)
const list = []
if (length > maxShowCount) {
// 如果当前用户数量比最多能显示的多,最后需要显示一个提示信息
list.push(...this.userList.slice(0, maxShowCount - 1), {
isMore: true,
name: '+' + (length - maxShowCount + 1)
})
} else {
list.push(...this.userList)
}
list.forEach((item, index) => {
let node = null
if (item.avatar) {
node = this.createImageAvatar(item)
} else {
node = this.createTextAvatar(item)
}
node.x(index * avatarSize).cy(-avatarSize / 2)
this._userListGroup.add(node)
})
}
// 添加用户
function addUser(userInfo) {
if (
this.userList.find(item => {
return item.id == userInfo.id
})
)
return
this.userList.push(userInfo)
this.updateUserListNode()
}
// 移除用户
function removeUser(userInfo) {
const index = this.userList.findIndex(item => {
return item.id == userInfo.id
})
if (index === -1) return
this.userList.splice(index, 1)
this.updateUserListNode()
}
export default {
createUserListNode,
updateUserListNode,
createTextAvatar,
createImageAvatar,
addUser,
removeUser
}

View File

@@ -1,6 +1,6 @@
import * as Y from 'yjs'
import { WebrtcProvider } from 'y-webrtc'
import { isSameObject, simpleDeepClone } from '../utils/index'
import { isSameObject, simpleDeepClone, getType, isUndef } from '../utils/index'
// 协同插件
class Cooperate {
@@ -12,7 +12,10 @@ class Cooperate {
this.provider = new WebrtcProvider('demo-room', this.ydoc, {
signaling: ['ws://10.16.83.11:4444']
})
this.awareness = this.provider.awareness
this.currentData = null
this.userInfo = null
this.currentAwarenessData = []
// 处理数据
if (this.mindMap.opt.data) {
@@ -34,6 +37,14 @@ class Cooperate {
// 监听思维导图改变
this.onDataChange = this.onDataChange.bind(this)
this.mindMap.on('data_change', this.onDataChange)
// 监听思维导图节点激活事件
this.onNodeActive = this.onNodeActive.bind(this)
this.mindMap.on('node_active', this.onNodeActive)
// 监听状态同步事件
this.onAwareness = this.onAwareness.bind(this)
this.awareness.on('change', this.onAwareness)
}
// 解绑事件
@@ -66,6 +77,72 @@ class Cooperate {
this.updateChanges(res)
}
// 节点激活状态改变后触发状态显示同步
onNodeActive(node, nodeList) {
if (this.userInfo) {
this.awareness.setLocalStateField(this.userInfo.name, {
userInfo: {
// 用户信息
...this.userInfo
},
nodeIdList: nodeList.map(item => {
// 当前激活的节点id列表
return item.uid
})
})
}
}
// 设置用户信息
/**
* {
* id: '', // 必传用户唯一的id
* name: '', // 用户名称。name和avatar两个只传一个即可如果都传了会显示avatar
* avatar: '', // 用户头像
* color: '' // 如果没有传头像,那么会以一个圆形来显示名称的第一个字,文字的颜色为白色,圆的颜色可以通过该字段设置
* }
**/
setUserInfo(userInfo) {
if (
getType(userInfo) !== 'Object' ||
isUndef(userInfo.id) ||
(isUndef(userInfo.name) && isUndef(userInfo.avatar))
)
return
this.userInfo = userInfo || null
}
// 监听状态同步事件
onAwareness() {
const walk = (list, callback) => {
list.forEach(value => {
const userName = Object.keys(value)[0]
if (!userName) return
const data = value[userName]
const userInfo = data.userInfo
const nodeIdList = data.nodeIdList
nodeIdList.forEach(uid => {
const node = this.mindMap.renderer.findNodeByUid(uid)
if (node) {
callback(node, userInfo)
}
})
})
}
// 清除之前的状态
walk(this.currentAwarenessData, (node, userInfo) => {
node.removeUser(userInfo)
})
// 设置当前状态
const data = Array.from(this.awareness.getStates().values())
this.currentAwarenessData = data
walk(data, (node, userInfo) => {
// 不显示自己
if (userInfo.id === this.userInfo.id) return
node.addUser(userInfo)
})
}
// 将树结构转平级对象
/*
{

View File

@@ -389,6 +389,16 @@ export default {
if (hasFileURL) {
this.$bus.$emit('handle_file_url')
}
if (this.$route.query.userName) {
this.mindMap.cooperate.setUserInfo({
id: Math.random(),
name: this.$route.query.userName,
color: ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399'][
Math.floor(Math.random() * 5)
],
avatar: Math.random() > 0.5 ? 'https://img0.baidu.com/it/u=4270674549,2416627993&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1696006800&t=4d32871d14a7224a4591d0c3c7a97311' : ''
})
}
},
// url中是否存在要打开的文件