UNPKG

leo-mind-map

Version:

一个简单的web在线思维导图

285 lines (262 loc) 7.94 kB
import * as Y from 'yjs' import { WebrtcProvider } from 'y-webrtc' import { isSameObject, simpleDeepClone, getType, isUndef, transformTreeDataToObject, transformObjectToTreeData } from '../utils/index' // 协同插件 class Cooperate { constructor(opt) { this.opt = opt this.mindMap = opt.mindMap // yjs文档 this.ydoc = new Y.Doc() // 共享数据 this.ymap = null // 连接提供者 this.provider = null // 感知数据 this.awareness = null this.currentAwarenessData = [] this.waitNodeUidMap = {} // 该列表中的uid对应的节点还未渲染完毕 // 当前的平级对象类型的思维导图数据 this.currentData = null // 用户信息 this.userInfo = null // 是否正在重新设置思维导图数据 this.isSetData = false // 绑定事件 this.bindEvent() // 处理实例化时传入的思维导图数据 if (this.mindMap.opt.data) { this.initData(this.mindMap.opt.data) } } // 初始化数据 initData(data) { data = simpleDeepClone(data) // 解绑原来的数据 if (this.ymap) { this.ymap.unobserve(this.onObserve) } // 创建共享数据 this.ymap = this.ydoc.getMap() // 思维导图树结构转平级对象结构 this.currentData = transformTreeDataToObject(data) // 将思维导图数据添加到共享数据中 Object.keys(this.currentData).forEach(uid => { this.ymap.set(uid, this.currentData[uid]) }) // 监听数据同步 this.onObserve = this.onObserve.bind(this) this.ymap.observe(this.onObserve) } // 获取yjs doc实例 getDoc() { return this.ydoc } // 设置连接提供者 setProvider(provider, webrtcProviderConfig = {}) { const { roomName, signalingList, ...otherConfig } = webrtcProviderConfig this.provider = provider || new WebrtcProvider(roomName, this.ydoc, { signaling: signalingList, ...otherConfig }) this.awareness = this.provider.awareness // 监听状态同步事件 this.onAwareness = this.onAwareness.bind(this) this.awareness.on('change', this.onAwareness) } // 绑定事件 bindEvent() { // 监听思维导图改变 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.onNodeTreeRenderEnd = this.onNodeTreeRenderEnd.bind(this) this.mindMap.on('node_tree_render_end', this.onNodeTreeRenderEnd) // 监听设置思维导图数据事件 this.onSetData = this.onSetData.bind(this) this.mindMap.on('set_data', this.onSetData) } // 解绑事件 unBindEvent() { if (this.ymap) { this.ymap.unobserve(this.onObserve) } this.mindMap.off('data_change', this.onDataChange) this.mindMap.off('node_active', this.onNodeActive) this.mindMap.off('node_tree_render_end', this.onNodeTreeRenderEnd) this.mindMap.off('set_data', this.onSetData) this.ydoc.destroy() } // 数据同步时的处理,更新当前思维导图 onObserve(event) { const data = event.target.toJSON() // 如果数据没有改变直接返回 if (isSameObject(data, this.currentData)) return this.currentData = data // 平级对象转树结构 const res = transformObjectToTreeData(data) if (!res) return // 更新思维导图画布 this.mindMap.updateData(res) } // 当前思维导图改变后的处理,触发同步 onDataChange(data) { if (this.isSetData) { this.isSetData = false return } const res = transformTreeDataToObject(data) this.updateChanges(res) } // 找出更新点 updateChanges(data) { const { beforeCooperateUpdate } = this.mindMap.opt const oldData = this.currentData this.currentData = data this.ydoc.transact(() => { // 找出新增的或修改的 const createOrUpdateList = [] Object.keys(data).forEach(uid => { // 新增的或已经存在的,如果数据发生了改变 if (!oldData[uid] || !isSameObject(oldData[uid], data[uid])) { createOrUpdateList.push({ uid, data: data[uid], oldData: oldData[uid] }) } }) if (beforeCooperateUpdate && createOrUpdateList.length > 0) { beforeCooperateUpdate({ type: 'createOrUpdate', list: createOrUpdateList, data }) } createOrUpdateList.forEach(item => { this.ymap.set(item.uid, item.data) }) // 找出删除的 const deleteList = [] Object.keys(oldData).forEach(uid => { if (!data[uid]) { deleteList.push({ uid, data: oldData[uid] }) } }) if (beforeCooperateUpdate && deleteList.length > 0) { beforeCooperateUpdate({ type: 'delete', list: deleteList }) } deleteList.forEach(item => { this.ymap.delete(item.uid) }) }) } // 节点激活状态改变后触发感知数据同步 onNodeActive(node, nodeList) { if (this.userInfo) { this.awareness.setLocalStateField(this.userInfo.name, { // 用户信息 userInfo: { ...this.userInfo }, // 当前激活的节点id列表 nodeIdList: nodeList.map(item => { return item.uid }) }) } } // 节点树渲染完毕事件 onNodeTreeRenderEnd() { Object.keys(this.waitNodeUidMap).forEach(uid => { const node = this.mindMap.renderer.findNodeByUid(uid) if (node) { node.addUser(this.waitNodeUidMap[uid]) } }) this.waitNodeUidMap = {} } // 监听思维导图数据的重新设置事件 onSetData(data) { this.isSetData = true this.initData(data) } // 设置用户信息 /** * { * 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) callback(uid, node, userInfo) }) }) } // 清除之前的数据 walk(this.currentAwarenessData, (uid, node, userInfo) => { if (node) { node.removeUser(userInfo) } }) // 设置当前数据 const data = Array.from(this.awareness.getStates().values()) this.currentAwarenessData = data this.waitNodeUidMap = {} walk(data, (uid, node, userInfo) => { // 不显示自己 if (userInfo.id === this.userInfo.id) return if (node) { node.addUser(userInfo) } else { this.waitNodeUidMap[uid] = userInfo } }) } // 插件被移除前做的事情 beforePluginRemove() { this.unBindEvent() } // 插件被卸载前做的事情 beforePluginDestroy() { this.unBindEvent() } } Cooperate.instanceName = 'cooperate' export default Cooperate