Skip to content

Commit 8604b4b

Browse files
author
xiaofeng
committed
bugfix:undo/redo不同步问题修复
1 parent f9c5a0c commit 8604b4b

File tree

10 files changed

+216
-12
lines changed

10 files changed

+216
-12
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.xiaoju.framework.handler;
2+
3+
import com.corundumstudio.socketio.BroadcastOperations;
4+
import com.corundumstudio.socketio.SocketIOClient;
5+
import com.corundumstudio.socketio.SocketIOServer;
6+
import com.fasterxml.jackson.databind.JsonNode;
7+
import com.fasterxml.jackson.databind.node.ArrayNode;
8+
import com.flipkart.zjsonpatch.CompatibilityFlags;
9+
import com.flipkart.zjsonpatch.JsonPatch;
10+
11+
import java.util.EnumSet;
12+
import java.util.concurrent.ExecutorService;
13+
14+
import static com.flipkart.zjsonpatch.CompatibilityFlags.*;
15+
16+
public class RedoIngressTask extends IngressTask {
17+
PushMessage data;
18+
19+
public RedoIngressTask(SocketIOClient client, SocketIOServer socketIOServer, RoomEntity room, ExecutorService executorEgressService, PushMessage data) {
20+
super(client, socketIOServer, room, executorEgressService);
21+
this.data = data;
22+
}
23+
@Override
24+
public void run() {
25+
room.lock();
26+
LOGGER.info(data.getMessage());
27+
try {
28+
ArrayNode patch = (ArrayNode) jsonMapper.readTree(data.getMessage());
29+
JsonNode roomContent = jsonMapper.readTree(room.getCaseContent());
30+
EnumSet<CompatibilityFlags> flags = CompatibilityFlags.defaults();
31+
flags.add(MISSING_VALUES_AS_NULLS);
32+
flags.add(ALLOW_MISSING_TARGET_OBJECT_ON_REPLACE);
33+
flags.add(REMOVE_NONE_EXISTING_ARRAY_ELEMENT);
34+
JsonNode roomContentNew = JsonPatch.apply(patch, roomContent, flags);
35+
room.setCaseContent(roomContentNew.toString());
36+
37+
ClientEntity clientEntity = getRoomFromClient(client);
38+
String roomId = clientEntity.getRoomId();
39+
BroadcastOperations broadcastOperations = socketIOServer.getRoomOperations(roomId);
40+
LOGGER.info("get redo info");
41+
broadcastOperations.sendEvent("redo", client, PushMessage.builder().message(data.getMessage()).build());
42+
43+
} catch (Exception e) {
44+
LOGGER.error("redo error");
45+
46+
} finally {
47+
room.unlock();
48+
}
49+
50+
}
51+
}

case-server/src/main/java/com/xiaoju/framework/handler/SocketIOServiceImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ public void start() {
193193
executorIngressService.submit(new LockIngressTask(client, socketIOServer, clientRoomMap.get(client), executorEgressService, data));
194194
});
195195

196+
socketIOServer.addEventListener("undo", PushMessage.class, (client, data, ackSender) -> {
197+
executorIngressService.submit(new UndoIngressTask(client, socketIOServer, clientRoomMap.get(client), executorEgressService, data));
198+
});
199+
200+
socketIOServer.addEventListener("redo", PushMessage.class, (client, data, ackSender) -> {
201+
executorIngressService.submit(new RedoIngressTask(client, socketIOServer, clientRoomMap.get(client), executorEgressService, data));
202+
});
203+
196204
socketIOServer.addEventListener("save", EditMessage.class, (client, data, ackSender) -> {
197205
executorIngressService.submit(new SaveIngressTask(client, socketIOServer, clientRoomMap.get(client), executorEgressService, data, caseBackupMapper));
198206
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.xiaoju.framework.handler;
2+
3+
import com.corundumstudio.socketio.BroadcastOperations;
4+
import com.corundumstudio.socketio.SocketIOClient;
5+
import com.corundumstudio.socketio.SocketIOServer;
6+
import com.fasterxml.jackson.databind.JsonNode;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.fasterxml.jackson.databind.node.ArrayNode;
9+
import com.flipkart.zjsonpatch.CompatibilityFlags;
10+
import com.flipkart.zjsonpatch.JsonDiff;
11+
import com.flipkart.zjsonpatch.JsonPatch;
12+
13+
import java.util.EnumSet;
14+
import java.util.concurrent.ExecutorService;
15+
16+
import static com.flipkart.zjsonpatch.CompatibilityFlags.*;
17+
import static com.flipkart.zjsonpatch.DiffFlags.ADD_ORIGINAL_VALUE_ON_REPLACE;
18+
import static com.flipkart.zjsonpatch.DiffFlags.OMIT_MOVE_OPERATION;
19+
20+
public class UndoIngressTask extends IngressTask {
21+
PushMessage data;
22+
23+
public UndoIngressTask(SocketIOClient client, SocketIOServer socketIOServer, RoomEntity room, ExecutorService executorEgressService, PushMessage data) {
24+
super(client, socketIOServer, room, executorEgressService);
25+
this.data = data;
26+
}
27+
@Override
28+
public void run() {
29+
room.lock();
30+
LOGGER.info(data.getMessage());
31+
try {
32+
ArrayNode patch = (ArrayNode) jsonMapper.readTree(data.getMessage());
33+
JsonNode roomContent = jsonMapper.readTree(room.getCaseContent());
34+
EnumSet<CompatibilityFlags> flags = CompatibilityFlags.defaults();
35+
flags.add(MISSING_VALUES_AS_NULLS);
36+
flags.add(ALLOW_MISSING_TARGET_OBJECT_ON_REPLACE);
37+
flags.add(REMOVE_NONE_EXISTING_ARRAY_ELEMENT);
38+
JsonNode roomContentNew = JsonPatch.apply(patch, roomContent, flags);
39+
room.setCaseContent(roomContentNew.toString());
40+
41+
ClientEntity clientEntity = getRoomFromClient(client);
42+
String roomId = clientEntity.getRoomId();
43+
BroadcastOperations broadcastOperations = socketIOServer.getRoomOperations(roomId);
44+
LOGGER.info("get undo info");
45+
broadcastOperations.sendEvent("undo", client, PushMessage.builder().message(data.getMessage()).build());
46+
47+
} catch (Exception e) {
48+
LOGGER.error("undo error");
49+
50+
} finally {
51+
room.unlock();
52+
}
53+
}
54+
55+
public static void main(String[] args) {
56+
String patch = "[{\"op\":\"replace\",\"path\":\"/base\",\"value\":162},{\"op\":\"replace\",\"path\":\"/root/children/0/children/0/children/1/children/0/data/priority\",\"value\":2}]";
57+
String content = "{\"root\":{\"data\":{\"created\":1669199611709,\"text\":\"查酒频次配置优化\",\"id\":\"57mb9fudv61682grgejqsccdvb\",\"expandState\":\"expand\"},\"children\":[{\"data\":{\"created\":1669199611717,\"text\":\"逻辑交互\",\"id\":\"0e97nsn07v0ifro18b0jf1c9b2\",\"expandState\":\"expand\",\"priority\":1},\"children\":[{\"data\":{\"id\":\"cqqtxfg14bc0\",\"created\":1677245556248,\"text\":\"啊啊\",\"priority\":2},\"children\":[{\"data\":{\"id\":\"cqqu3jg4oc80\",\"created\":1677246035146,\"text\":\"4\"},\"children\":[{\"data\":{\"id\":\"d07rrni373s0\",\"created\":1711871406605,\"text\":\"fff\",\"priority\":3},\"children\":[{\"data\":{\"id\":\"d0agf41ee9c0\",\"created\":1712144071528,\"text\":\"44\"},\"children\":[]}]},{\"data\":{\"id\":\"d07rxsvpnko0\",\"created\":1711871888498,\"text\":\"dd\",\"hyperlink\":\"http://image.baidu.com\"},\"children\":[{\"data\":{\"id\":\"d0agf0ovji00\",\"created\":1712144064241,\"text\":\"abc\"},\"children\":[]}]},{\"data\":{\"id\":\"d0agf78ggz5s\",\"created\":1712144078486,\"text\":\"io\",\"resource\":null,\"priority\":1},\"children\":[]}]},{\"data\":{\"id\":\"d074zy04y9k0\",\"created\":1711807170850,\"text\":\"10\"},\"children\":[{\"data\":{\"id\":\"d07rsiih7xs0\",\"created\":1711871474109,\"text\":\"dddd \"},\"children\":[{\"data\":{\"id\":\"d07rxmqbwtc0\",\"created\":1711871875112,\"text\":\"分支主题\",\"priority\":null},\"children\":[]}]}]},{\"data\":{\"id\":\"cqqtxzd581s0\",\"created\":1677245599609,\"text\":\"0\"},\"children\":[{\"data\":{\"id\":\"d07rrd5ljio0\",\"created\":1711871384082,\"text\":\"3\"},\"children\":[]},{\"data\":{\"id\":\"d07rrptmaow0\",\"created\":1711871411656,\"text\":\"ddd\"},\"children\":[]}]}]}]}]},\"template\":\"right\",\"theme\":\"fresh-blue\",\"version\":\"1.4.43\",\"base\":160,\"right\":1}";
58+
try {
59+
ObjectMapper jsonMapper = new ObjectMapper();
60+
61+
ArrayNode patchNode = (ArrayNode) jsonMapper.readTree(patch);
62+
JsonNode roomContent = jsonMapper.readTree(content);
63+
EnumSet<CompatibilityFlags> flags = CompatibilityFlags.defaults();
64+
flags.add(MISSING_VALUES_AS_NULLS);
65+
flags.add(ALLOW_MISSING_TARGET_OBJECT_ON_REPLACE);
66+
JsonNode roomContentNew = JsonPatch.apply(patchNode, roomContent, flags);
67+
System.out.println(roomContentNew.toString());
68+
} catch (Exception e) {
69+
System.out.println("error");
70+
e.printStackTrace();
71+
}
72+
}
73+
}
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,15 @@
1-
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>AgileTC</title><style type="text/css">body{overflow: auto !important;}</style><script>window.routerBase = "/";</script></head><body><div id="root"></div><script th:src="@{/umi.9b090468.js}"></script><script src="/umi.c2ea7af7.js"></script></body></html>
1+
<!DOCTYPE html>
2+
<html xmlns:th="http://www.thymeleaf.org">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width,initial-scale=1" />
7+
<title>AgileTC</title>
8+
<style type="text/css">body{overflow: auto !important;}</style>
9+
</head>
10+
<body>
11+
<div id="root"></div>
12+
<script th:src="@{/umi.6c0feca0.js}"></script>
13+
</body>
14+
</html>
15+

case-server/src/main/resources/web/dist/umi.c2ea7af7.js renamed to case-server/src/main/resources/web/dist/umi.6c0feca0.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

case-server/src/main/resources/web/dist/vendors.0418a3c6.async.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

case-server/src/main/resources/web/dist/vendors.7739688d.async.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

case-server/src/main/resources/web/src/components/react-mindmap-editor/index.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ class KityminderEditor extends Component {
243243
key: 'Ctrl + Z',
244244
enable: () => {
245245
if (this.groupNode) {
246+
246247
return this.groupNode.undo();
247248
}
248249
return !readOnly;
@@ -344,6 +345,17 @@ class KityminderEditor extends Component {
344345

345346
this.hotbox = hotbox;
346347
};
348+
handleUndoAck = (data) => {
349+
350+
console.log('undo ack get', data)
351+
this.groupNode.undo(data)
352+
}
353+
handleRedoAck = (data) => {
354+
console.log('redo ack get', data)
355+
this.groupNode.redo(data)
356+
357+
}
358+
347359
// handleUndo = () => {
348360
// this.ws.sendMessage('1undo');
349361
// const { undoCnt, redoCnt } = this.state;
@@ -432,7 +444,9 @@ class KityminderEditor extends Component {
432444
if (ctrlKey && window.event.keyCode === 90) {
433445
// this.expectedBase = this.minder.getBase() - 1;
434446
// this.handleUndo();
447+
console.log('begin undo')
435448
e.preventDefault();
449+
436450
this.groupNode.undo();
437451
}
438452
if (ctrlKey && window.event.keyCode === 89) {
@@ -464,6 +478,7 @@ class KityminderEditor extends Component {
464478
caseObj.right = window.minderData.right || 1;
465479

466480
const patch = this.groupNode.getAndResetPatch();
481+
console.log('send patch', patch)
467482
if (patch.length === 1 && patch[0].path === '/base') {
468483
e.minder._status = 'normal';
469484
return;
@@ -482,6 +497,7 @@ class KityminderEditor extends Component {
482497
// const { undoCnt } = this.state;
483498
// this.setState({ undoCnt: undoCnt + 1 });
484499
// this.websocketHeartbeatJs.send(JSON.stringify({ case: caseObj, patch }));
500+
485501
this.ws.sendMessage('edit', { caseContent: JSON.stringify(caseObj), patch: JSON.stringify(patch), caseVersion: caseObj.base });
486502
this.base = e.minder.getBase();
487503
this.expectedBase = this.base + 1;
@@ -865,7 +881,7 @@ class KityminderEditor extends Component {
865881
const childProps = {
866882
...this.props,
867883
minder,
868-
isLock,
884+
isLock
869885
};
870886

871887
const tabContentClass = `toolbar has-right-border`;
@@ -887,6 +903,8 @@ class KityminderEditor extends Component {
887903
onClose={this.handleWsClose}
888904
wsMinder={this.minder}
889905
handleLock={this.handleLock}
906+
handleUndoAck={this.handleUndoAck}
907+
handleRedoAck={this.handleRedoAck}
890908
handleWsUserStat={this.handleWsUserStat}
891909
// onError={e => {
892910
// notification.info({
@@ -1009,6 +1027,7 @@ class KityminderEditor extends Component {
10091027
ref={groupNode => (this.groupNode = groupNode)}
10101028
initData={this.initData}
10111029
{...childProps}
1030+
wsInstance={this.ws}
10121031
/>
10131032
{!readOnly && (
10141033
<Nodes

case-server/src/main/resources/web/src/components/react-mindmap-editor/socket.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,26 @@ class Socket extends React.Component {
111111
}
112112

113113
});
114-
114+
websocket.on('undo', evt => {
115+
console.log('undo info', evt.message);
116+
try {
117+
if (typeof this.props.handleUndoAck === 'function') {
118+
this.props.handleUndoAck(evt.message);
119+
}
120+
} catch(e) {
121+
alert('客户端接受通知消息异常,请刷新重试');
122+
}
123+
})
124+
websocket.on('redo', evt => {
125+
console.log('redo info', evt.message);
126+
try {
127+
if (typeof this.props.handleRedoAck === 'function') {
128+
this.props.handleRedoAck(evt.message);
129+
}
130+
} catch(e) {
131+
alert('客户端接受通知消息异常,请刷新重试');
132+
}
133+
})
115134
// message 0:加锁;1:解锁;2:加/解锁成功;3:加/解锁失败
116135
websocket.on('lock', evt => {
117136
console.log('lock info', evt.message);
@@ -144,7 +163,6 @@ class Socket extends React.Component {
144163

145164
sendMessage(type, message) {
146165
let websocket = this.state.ws;
147-
console.log('-- message --', message);
148166
// var jsonObject = {userName: 'userName', message: message};
149167
websocket.emit(type, message);
150168
}
@@ -181,6 +199,8 @@ Socket.propTypes = {
181199
onOpen: PropTypes.func,
182200
onClose: PropTypes.func,
183201
handleLock: PropTypes.func,
202+
handleUndoAck: PropTypes.func,
203+
handleRedoAck: PropTypes.func,
184204
handleWsUserStat: PropTypes.func
185205
}
186206

case-server/src/main/resources/web/src/components/react-mindmap-editor/toolbar/DoGroup.js

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ class DoGroup extends Component {
3030
makeUndoDiff = () => {
3131
const { minder } = this.props;
3232
let { undoDiffs, lastSnap } = this.state;
33-
3433
const headSnap = minder.exportJson();
3534
console.log('----headsnap---', headSnap);
3635
const diff = jsonDiff.compare(headSnap, lastSnap);
@@ -45,7 +44,6 @@ class DoGroup extends Component {
4544
undoDiffs.push(diff);
4645
doDiffs.push(doDiff);
4746
}
48-
4947
while (undoDiffs.length > MAX_HISTORY) {
5048
undoDiffs.shift();
5149
doDiffs.shift();
@@ -60,32 +58,52 @@ class DoGroup extends Component {
6058
let { lastSnap, redoDiffs } = this.state;
6159
let revertSnap = minder.exportJson();
6260
redoDiffs.push(jsonDiff.compare(revertSnap, lastSnap));
61+
6362
lastSnap = revertSnap;
6463
this.setState({ redoDiffs, lastSnap });
6564
};
65+
6666
// 撤销
67-
undo = () => {
67+
undo = (notifyInfo) => {
68+
this.notifyInfo = notifyInfo;
6869
this.setState({ patchLock: true }, () => {
70+
console.log('notifyInfo', this.notifyInfo)
6971
const { minder } = this.props;
7072
let { undoDiffs } = this.state;
7173
const undoDiff = undoDiffs.pop();
7274
doDiffs.pop();
7375
if (undoDiff) {
74-
minder.applyPatches(undoDiff);
76+
77+
if (this.notifyInfo instanceof Object) {
78+
this.props.wsInstance.sendMessage('undo',{ message: JSON.stringify(undoDiff) })
79+
minder.applyPatches(undoDiff)
80+
} else {
81+
minder.applyPatches(JSON.parse(notifyInfo || '{}'));
82+
}
83+
7584
this.makeRedoDiff();
7685
}
7786
this.setState({ patchLock: false });
7887
});
7988
};
8089
// 重做
81-
redo = () => {
90+
redo = (notifyInfo) => {
91+
this.notifyInfo = notifyInfo;
8292
this.setState({ patchLock: true }, () => {
8393
const { minder } = this.props;
8494
let { redoDiffs } = this.state;
8595
const redoDiff = redoDiffs.pop();
96+
97+
console.log('pop redoDiffs ?', redoDiffs.length)
8698
if (redoDiff) {
87-
minder.applyPatches(redoDiff);
99+
if (this.notifyInfo instanceof Object) {
100+
this.props.wsInstance.sendMessage('redo',{ message: JSON.stringify(redoDiff) })
101+
minder.applyPatches(redoDiff);
102+
} else {
103+
minder.applyPatches(JSON.parse(notifyInfo || '{}'));
104+
}
88105
this.makeUndoDiff();
106+
doDiffs.pop()
89107
}
90108
this.setState({ patchLock: false });
91109
});
@@ -110,6 +128,7 @@ class DoGroup extends Component {
110128
};
111129
hasRedo = () => {
112130
const { redoDiffs } = this.state;
131+
console.log('has redoDiffs ?', redoDiffs.length)
113132
return !!redoDiffs.length;
114133
};
115134

0 commit comments

Comments
 (0)