Skip to content

Commit 147b604

Browse files
committed
feat: 实现简易版本数据协同
1 parent ab064a6 commit 147b604

File tree

13 files changed

+7038
-4722
lines changed

13 files changed

+7038
-4722
lines changed

app/src/App.tsx

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import React, { useEffect, useLayoutEffect, useState } from "react";
2-
import { DrawShapePlugin, EBoard, IConfigService, IModelService, ITransformService, RectCtrlElement } from "@e-board/board-core";
1+
import React, { useLayoutEffect, useState } from "react";
2+
import { DrawShapePlugin, EBoard, IConfigService, IModelService, ITransformService } from "@e-board/board-core";
33
import "./styles.css";
44
import { RoamPlugin, SelectionPlugin, ClearPlugin, PicturePlugin, HotkeyPlugin } from "@e-board/board-core";
55
import { BoardCollaboration } from '@e-board/board-collaboration';
66

77
import { Panel, StageTool } from '@e-board/board-workbench';
8-
import { WebSocketProvider } from "@e-board/board-websocket";
9-
const WS_URL = 'ws://localhost:3010/collaboration';
108
const App: React.FC = () => {
119
const eboard = React.useRef<EBoard | null>(null);
1210
const [selectedElement, setSelectedElement] = useState<any>(null);
@@ -16,18 +14,7 @@ const App: React.FC = () => {
1614
return config
1715
}
1816

19-
useEffect(() => {
20-
try {
21-
const wsProvider = new WebSocketProvider();
22-
wsProvider.connect(WS_URL);
23-
wsProvider.onMessage((...args) => {
24-
console.log('Received message from server:', ...args);
25-
})
26-
} catch (err) {
27-
console.error('WebSocket 连接失败:', err);
28-
}
2917

30-
}, [])
3118

3219

3320
const initCollaboration = (board: any) => {

packages/board-collaboration/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"dependencies": {
1111
"@e-board/board-core": "workspace:*",
1212
"@e-board/board-utils": "workspace:*",
13+
"@e-board/board-websocket": "workspace:*",
1314
"inversify": "^7.2.0",
1415
"reflect-metadata": "^0.2.2"
1516
},

packages/board-collaboration/src/index.ts

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,93 @@
1-
import type { EBoard, ModelChangeEvent, ModelService } from "@e-board/board-core";
1+
import { OperationSource, type EBoard, type ElementService, type ModelChangeEvent, type ModelService } from "@e-board/board-core";
2+
import { WebSocketProvider, MsgType } from '@e-board/board-websocket';
3+
const WS_URL = 'ws://localhost:3010/collaboration'; // TODO: 这个配置应该放到app初始化时作为配置传入
24

35
class BoardCollaboration {
46
private modelService: ModelService
5-
// private websocketProvider: WebSocketProvider
7+
private elementService: ElementService
8+
private websocketProvider: WebSocketProvider | null = null;
69
private disposeList: (() => void)[] = [];
10+
private currentUserid = `user-${Math.floor(Math.random() * 1000)}`;
711
constructor(private board: EBoard) {
12+
813
this.modelService = board.getService('modelService')
14+
this.elementService = this.board.getService('elementService')
15+
916
this.init()
1017
}
1118

12-
// TODO: 初始化websocketProvider, 监听传入的数据,更新modelService中的数据
13-
1419
private init = () => {
20+
this.websocketProvider = new WebSocketProvider();
21+
try {
22+
this.websocketProvider.connect(WS_URL);
23+
this.websocketProvider.onMessage((data) => {
24+
console
25+
if (data.type === MsgType.OPERATION) {
26+
if (data.senderId === this.currentUserid) return; // 忽略自己发送的消息
27+
const operation = JSON.parse(data.data)
28+
// 外存模型转内存模型
29+
if (operation.operation === 'create') {
30+
const element = this.elementService.getElement(operation.model.type);
31+
if (!element) throw new Error(`Unregistered element type: ${operation.model.type}`);
32+
const saveInfoService = this.board.getService('saveInfoService')
33+
saveInfoService.importSaveInfo(operation.model, OperationSource.REMOTE)
34+
} else if (operation.operation === 'update') {
35+
const element = this.elementService.getElement(operation.updates.type);
36+
if (!element) throw new Error(`Unregistered element type: ${operation.updates.type}`);
37+
const model = element.saveInfoProvider.importSaveInfo(operation.data.updates)
38+
this.modelService.updateModel(model.id, model, OperationSource.REMOTE)
39+
} else if (operation.operation === 'delete') {
40+
41+
operation.data.deletedModels.forEach((m: any) => {
42+
const element = this.elementService.getElement(m.type);
43+
if (!element) throw new Error(`Unregistered element type: ${m.type}`);
44+
const model = element.saveInfoProvider.importSaveInfo(m)
45+
this.modelService.deleteModel(model.id, OperationSource.REMOTE)
46+
})
47+
} else {
48+
throw new Error(`Unsupported operation type: ${operation.operation}`);
49+
}
50+
}
51+
52+
})
53+
} catch (err) {
54+
console.error('WebSocket 连接失败:', err);
55+
}
56+
57+
1558
const { dispose } = this.modelService.onModelOperation(
1659
(operation: ModelChangeEvent) => {
60+
const type = operation.model?.type
61+
if (!type) throw new Error('Operation missing type');
62+
const element = this.elementService.getElement(type);
63+
if (!element) throw new Error(`Unregistered element type: ${type}`);
64+
const body: any = {
65+
type: MsgType.OPERATION,
66+
id: `msg-${Date.now()}`,
67+
senderId: this.currentUserid,
68+
timestamp: Date.now()
69+
}
70+
if (operation.operationSource === 'remote') return; // 忽略远程操作,防止循环广播
71+
72+
// 内存模型转外存模型
73+
if (operation.type === 'create') {
74+
75+
body.data = JSON.stringify({ operation: 'create', model: element.saveInfoProvider.parse(operation.model) })
76+
} else if (operation.type === 'update') {
77+
body.data = JSON.stringify({
78+
operation: 'update',
79+
updates: element.saveInfoProvider.parse(operation.model),
80+
previousState: element.saveInfoProvider.parse(operation.previousState)
81+
})
82+
} else if (operation.type === 'delete') {
83+
body.data = JSON.stringify({
84+
operation: 'delete',
85+
deletedModels: Array.from(operation.deletedModels?.values() || []).map(m => element.saveInfoProvider.parse(m))
86+
})
87+
} else {
88+
throw new Error(`Unsupported operation type: ${operation.type}`);
89+
}
90+
this.websocketProvider?.send(body)
1791
console.log('Received operation:', operation);
1892
}
1993
)

packages/board-collaboration/tsconfig.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,9 @@
1010
"paths": {}
1111
},
1212
"include": ["src"],
13-
"references": [{ "path": "../board-utils" }, { "path": "../board-core" }]
13+
"references": [
14+
{ "path": "../board-utils" },
15+
{ "path": "../board-core" },
16+
{ "path": "../board-websocket" }
17+
]
1418
}

packages/board-core/src/board/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { IService, IBoard, IBoardInitParams, IPluginInitParams } from "../types"
33
import { bindServices } from "./bindServices";
44
import { eBoardContainer, resetContainer } from "../common/IocContainer";
55
import { IPluginService } from "../services/pluginService/type";
6-
import { ICanvasService } from "../services/canvasService/type";
76
import { IPlugin } from "../plugins/type";
87

98
export class EBoard implements IBoard {

packages/board-core/src/services/modelService/index.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { Emitter, uuid } from "@e-board/board-utils";
2-
import { IModelService, IModel, ModelChangeType, ModelChangeEvent } from "./type";
2+
import { IModelService, IModel, ModelChangeType, ModelChangeEvent, OperationSource } from "./type";
33
import { IServiceInitParams } from "../../types";
44
import { eBoardContainer } from "../../common/IocContainer";
55
import { IElementService } from "../elementService/type";
66

77
type Model = IModel;
88

9+
10+
911
export class ModelService implements IModelService {
1012
private models: Map<string, Model>;
1113
private _modelOperation = new Emitter<ModelChangeEvent>();
@@ -30,7 +32,7 @@ export class ModelService implements IModelService {
3032
* @param description 模型描述(可选)
3133
* @returns 创建的模型
3234
*/
33-
createModel(type: string, options?: Partial<IModel>): Model {
35+
createModel(type: string, options?: Partial<IModel>, operationSource: OperationSource = OperationSource.LOCAL): Model {
3436
const model = {
3537
id: options?.id || uuid(),
3638
type,
@@ -46,7 +48,8 @@ export class ModelService implements IModelService {
4648
this._modelOperation.fire({
4749
type: ModelChangeType.CREATE,
4850
modelId: model.id,
49-
model
51+
model,
52+
operationSource,
5053
});
5154

5255
return model;
@@ -75,7 +78,7 @@ export class ModelService implements IModelService {
7578
* @param updates 需要更新的字段
7679
* @returns 更新后的模型,如果模型不存在则返回undefined
7780
*/
78-
updateModel(id: string, updates: Partial<Omit<Model, "id">>): Model | undefined {
81+
updateModel(id: string, updates: Partial<Omit<Model, "id">>, operationSource: OperationSource = OperationSource.LOCAL): Model | undefined {
7982
const model = this.models.get(id);
8083
if (!model) {
8184
return undefined;
@@ -100,7 +103,8 @@ export class ModelService implements IModelService {
100103
type: ModelChangeType.UPDATE,
101104
modelId: id,
102105
updates,
103-
previousState
106+
previousState,
107+
operationSource
104108
});
105109

106110
return updatedModel;
@@ -111,7 +115,7 @@ export class ModelService implements IModelService {
111115
* @param id 模型ID
112116
* @returns 是否删除成功
113117
*/
114-
deleteModel(id: string): boolean {
118+
deleteModel(id: string, operationSource: OperationSource = OperationSource.LOCAL): boolean {
115119
const model = this.models.get(id);
116120
if (!model) {
117121
return false;
@@ -124,7 +128,8 @@ export class ModelService implements IModelService {
124128
this._modelOperation.fire({
125129
type: ModelChangeType.DELETE,
126130
modelId: id,
127-
model
131+
model,
132+
operationSource
128133
});
129134
}
130135

packages/board-core/src/services/modelService/type.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ export enum ModelChangeType {
66
DELETE = "delete",
77
CLEAR = "clear"
88
}
9-
9+
export enum OperationSource {
10+
LOCAL = 'local',
11+
REMOTE = 'remote'
12+
}
1013
export interface ModelChangeEvent {
1114
type: ModelChangeType;
1215
modelId: string;
1316
model?: IModel;
1417
updates?: Partial<Omit<IModel, "id">>;
1518
previousState?: Partial<Omit<IModel, "id">>;
1619
deletedModels?: Map<string, IModel>;
20+
operationSource?: OperationSource;
1721
}
1822

1923
export interface IModelService<ExtensionOptions extends Record<string, any> =

packages/board-core/src/services/saveInfoService/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IServiceInitParams } from 'src/types';
22
import type EBoard from '../../board';
33
import { ISaveInfoService } from './type'
4+
import { OperationSource } from '../modelService/type';
45

56

67
class SaveInfoService implements ISaveInfoService {
@@ -39,7 +40,7 @@ class SaveInfoService implements ISaveInfoService {
3940
return saveInfoList as any[]
4041
}
4142

42-
importSaveInfo(info: any) {
43+
importSaveInfo(info: any, operationSource = OperationSource.LOCAL) {
4344
const element = this.elementService.getElement(info.type)
4445
if (!element) {
4546
console.warn(`Element of type ${info.type} not found, skipping...`)
@@ -50,11 +51,11 @@ class SaveInfoService implements ISaveInfoService {
5051
return
5152
}
5253
const model = element.saveInfoProvider.importSaveInfo(info)
53-
this.modelService.createModel(model.type, model)
54+
this.modelService.createModel(model.type, model, operationSource)
5455
}
5556

56-
importSaveInfoList(infoList: any[]) {
57-
return infoList.forEach(info => this.importSaveInfo(info))
57+
importSaveInfoList(infoList: any[], operationSource = OperationSource.LOCAL) {
58+
return infoList.forEach(info => this.importSaveInfo(info, operationSource))
5859
}
5960

6061

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./src/types";
2+
export { WebSocketProvider } from "./src/index";

packages/board-websocket/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "",
55
"main": "dist/cjs/index.js",
66
"module": "dist/esm/index.js",
7-
"types": "dist/esm/index.d.ts",
7+
"types": "index.d.ts",
88
"files": [
99
"dist"
1010
],

0 commit comments

Comments
 (0)