Skip to content

Commit f98ad3a

Browse files
authored
✨ [Frontend] Conversation Messages: Listen to WebSocket (#7963)
1 parent 615c826 commit f98ad3a

File tree

5 files changed

+242
-94
lines changed

5 files changed

+242
-94
lines changed

services/static-webserver/client/source/class/osparc/conversation/AddMessage.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ qx.Class.define("osparc.conversation.AddMessage", {
3636
},
3737

3838
events: {
39-
"commentAdded": "qx.event.type.Data",
40-
"messageEdited": "qx.event.type.Data",
39+
"messageAdded": "qx.event.type.Data",
40+
"messageUpdated": "qx.event.type.Data",
4141
},
4242

4343
members: {
@@ -227,7 +227,7 @@ qx.Class.define("osparc.conversation.AddMessage", {
227227
if (content) {
228228
osparc.study.Conversations.addMessage(this.__studyData["uuid"], this.__conversationId, content)
229229
.then(data => {
230-
this.fireDataEvent("commentAdded", data);
230+
this.fireDataEvent("messageAdded", data);
231231
commentField.getChildControl("text-area").setValue("");
232232
});
233233
}
@@ -239,7 +239,7 @@ qx.Class.define("osparc.conversation.AddMessage", {
239239
if (content) {
240240
osparc.study.Conversations.editMessage(this.__studyData["uuid"], this.__conversationId, this.__message["messageId"], content)
241241
.then(data => {
242-
this.fireDataEvent("messageEdited", data);
242+
this.fireDataEvent("messageUpdated", data);
243243
commentField.getChildControl("text-area").setValue("");
244244
});
245245
}
@@ -249,7 +249,7 @@ qx.Class.define("osparc.conversation.AddMessage", {
249249
if (userGid) {
250250
osparc.study.Conversations.notifyUser(this.__studyData["uuid"], this.__conversationId, userGid)
251251
.then(data => {
252-
this.fireDataEvent("commentAdded", data);
252+
this.fireDataEvent("messageAdded", data);
253253
const potentialCollaborators = osparc.store.Groups.getInstance().getPotentialCollaborators();
254254
if (userGid in potentialCollaborators) {
255255
if ("getUserId" in potentialCollaborators[userGid]) {

services/static-webserver/client/source/class/osparc/conversation/Conversation.js

Lines changed: 108 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ qx.Class.define("osparc.conversation.Conversation", {
2727
this.base(arguments);
2828

2929
this.__studyData = studyData;
30+
this.__messages = [];
3031

3132
if (conversationId) {
3233
this.setConversationId(conversationId);
@@ -46,7 +47,7 @@ qx.Class.define("osparc.conversation.Conversation", {
4647

4748
this.__buildLayout();
4849

49-
this.fetchMessages();
50+
this.__reloadMessages();
5051
},
5152

5253
properties: {
@@ -64,6 +65,7 @@ qx.Class.define("osparc.conversation.Conversation", {
6465

6566
members: {
6667
__studyData: null,
68+
__messages: null,
6769
__nextRequestParams: null,
6870
__messagesTitle: null,
6971
__messagesList: null,
@@ -159,24 +161,44 @@ qx.Class.define("osparc.conversation.Conversation", {
159161
});
160162

161163
this.__loadMoreMessages = new osparc.ui.form.FetchButton(this.tr("Load more messages..."));
162-
this.__loadMoreMessages.addListener("execute", () => this.fetchMessages(false));
164+
this.__loadMoreMessages.addListener("execute", () => this.__reloadMessages(false));
163165
this._add(this.__loadMoreMessages);
164166

165167
if (osparc.data.model.Study.canIWrite(this.__studyData["accessRights"])) {
166168
const addMessages = new osparc.conversation.AddMessage(this.__studyData, this.getConversationId());
167169
addMessages.setPaddingLeft(10);
168-
addMessages.addListener("commentAdded", e => {
170+
addMessages.addListener("messageAdded", e => {
169171
const data = e.getData();
170172
if (data["conversationId"]) {
171173
this.setConversationId(data["conversationId"]);
174+
this.addMessage(data);
172175
}
173-
this.fetchMessages();
174176
});
175177
this._add(addMessages);
176178
}
177179
},
178180

179-
fetchMessages: function(removeMessages = true) {
181+
__getNextRequest: function() {
182+
const params = {
183+
url: {
184+
studyId: this.__studyData["uuid"],
185+
conversationId: this.getConversationId(),
186+
offset: 0,
187+
limit: 42
188+
}
189+
};
190+
const nextRequestParams = this.__nextRequestParams;
191+
if (nextRequestParams) {
192+
params.url.offset = nextRequestParams.offset;
193+
params.url.limit = nextRequestParams.limit;
194+
}
195+
const options = {
196+
resolveWResponse: true
197+
};
198+
return osparc.data.Resources.fetch("conversations", "getMessagesPage", params, options);
199+
},
200+
201+
__reloadMessages: function(removeMessages = true) {
180202
if (this.getConversationId() === null) {
181203
this.__messagesTitle.setValue(this.tr("No messages yet"));
182204
this.__messagesList.hide();
@@ -189,15 +211,14 @@ qx.Class.define("osparc.conversation.Conversation", {
189211
this.__loadMoreMessages.setFetching(true);
190212

191213
if (removeMessages) {
214+
this.__messages = [];
192215
this.__messagesList.removeAll();
193216
}
194217

195218
this.__getNextRequest()
196219
.then(resp => {
197220
const messages = resp["data"];
198-
// it's not provided by the backend
199-
messages.forEach(message => message["studyId"] = this.__studyData["uuid"]);
200-
this.__addMessages(messages);
221+
messages.forEach(message => this.addMessage(message));
201222
this.__nextRequestParams = resp["_links"]["next"];
202223
if (this.__nextRequestParams === null) {
203224
this.__loadMoreMessages.exclude();
@@ -206,48 +227,91 @@ qx.Class.define("osparc.conversation.Conversation", {
206227
.finally(() => this.__loadMoreMessages.setFetching(false));
207228
},
208229

209-
__getNextRequest: function() {
210-
const params = {
211-
url: {
212-
studyId: this.__studyData["uuid"],
213-
conversationId: this.getConversationId(),
214-
offset: 0,
215-
limit: 42
216-
}
217-
};
218-
const nextRequestParams = this.__nextRequestParams;
219-
if (nextRequestParams) {
220-
params.url.offset = nextRequestParams.offset;
221-
params.url.limit = nextRequestParams.limit;
222-
}
223-
const options = {
224-
resolveWResponse: true
225-
};
226-
return osparc.data.Resources.fetch("conversations", "getMessagesPage", params, options);
227-
},
228-
229-
__addMessages: function(messages) {
230-
const nMessages = messages.filter(msg => msg["type"] === "MESSAGE").length;
230+
__updateMessagesNumber: function() {
231+
const nMessages = this.__messages.filter(msg => msg["type"] === "MESSAGE").length;
231232
if (nMessages === 1) {
232233
this.__messagesTitle.setValue(this.tr("1 Message"));
233234
} else if (nMessages > 1) {
234235
this.__messagesTitle.setValue(nMessages + this.tr(" Messages"));
235236
}
237+
},
236238

237-
messages.forEach(message => {
238-
let control = null;
239-
switch (message["type"]) {
240-
case "MESSAGE":
241-
control = new osparc.conversation.MessageUI(message, this.__studyData);
242-
control.addListener("messageEdited", () => this.fetchMessages());
243-
control.addListener("messageDeleted", () => this.fetchMessages());
244-
break;
245-
case "NOTIFICATION":
246-
control = new osparc.conversation.NotificationUI(message);
247-
break;
248-
}
249-
if (control) {
250-
this.__messagesList.add(control);
239+
addMessage: function(message) {
240+
// backend doesn't provide the projectId
241+
message["projectId"] = this.__studyData["uuid"];
242+
243+
// ignore it if it was already there
244+
const messageIndex = this.__messages.findIndex(msg => msg["messageId"] === message["messageId"]);
245+
if (messageIndex !== -1) {
246+
return;
247+
}
248+
249+
// determine insertion index for most‐recent‐first order
250+
const newTime = new Date(message["created"]);
251+
let insertAt = this.__messages.findIndex(m => new Date(m["created"]) < newTime);
252+
if (insertAt === -1) {
253+
insertAt = this.__messages.length;
254+
}
255+
256+
// Insert the message in the messages array
257+
this.__messages.splice(insertAt, 0, message);
258+
259+
// Add the UI element to the messages list
260+
let control = null;
261+
switch (message["type"]) {
262+
case "MESSAGE":
263+
control = new osparc.conversation.MessageUI(message, this.__studyData);
264+
control.addListener("messageUpdated", e => this.updateMessage(e.getData()));
265+
control.addListener("messageDeleted", e => this.deleteMessage(e.getData()));
266+
break;
267+
case "NOTIFICATION":
268+
control = new osparc.conversation.NotificationUI(message);
269+
break;
270+
}
271+
if (control) {
272+
// insert into the UI at the same position
273+
this.__messagesList.addAt(control, insertAt);
274+
}
275+
276+
this.__updateMessagesNumber();
277+
},
278+
279+
deleteMessage: function(message) {
280+
// remove it from the messages array
281+
const messageIndex = this.__messages.findIndex(msg => msg["messageId"] === message["messageId"]);
282+
if (messageIndex === -1) {
283+
return;
284+
}
285+
this.__messages.splice(messageIndex, 1);
286+
287+
// Remove the UI element from the messages list
288+
const children = this.__messagesList.getChildren();
289+
const controlIndex = children.findIndex(
290+
ctrl => ("getMessage" in ctrl && ctrl.getMessage()["messageId"] === message["messageId"])
291+
);
292+
if (controlIndex > -1) {
293+
this.__messagesList.remove(children[controlIndex]);
294+
}
295+
296+
this.__updateMessagesNumber();
297+
},
298+
299+
updateMessage: function(message) {
300+
// backend doesn't provide the projectId
301+
message["projectId"] = this.__studyData["uuid"];
302+
303+
// Replace the message in the messages array
304+
const messageIndex = this.__messages.findIndex(msg => msg["messageId"] === message["messageId"]);
305+
if (messageIndex === -1) {
306+
return;
307+
}
308+
this.__messages[messageIndex] = message;
309+
310+
// Update the UI element from the messages list
311+
this.__messagesList.getChildren().forEach(control => {
312+
if ("getMessage" in control && control.getMessage()["messageId"] === message["messageId"]) {
313+
control.setMessage(message);
314+
return;
251315
}
252316
});
253317
},

services/static-webserver/client/source/class/osparc/conversation/MessageUI.js

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,16 @@ qx.Class.define("osparc.conversation.MessageUI", {
2626
construct: function(message, studyData = null) {
2727
this.base(arguments);
2828

29-
this.__message = message;
3029
this.__studyData = studyData;
3130

32-
const isMyMessage = this.self().isMyMessage(this.__message);
3331
const layout = new qx.ui.layout.Grid(12, 4);
3432
layout.setColumnFlex(1, 1); // content
35-
layout.setColumnFlex(isMyMessage ? 0 : 2, 3); // spacer
3633
this._setLayout(layout);
3734
this.setPadding(5);
3835

39-
this.__buildLayout();
36+
this.set({
37+
message,
38+
});
4039
},
4140

4241
statics: {
@@ -46,15 +45,22 @@ qx.Class.define("osparc.conversation.MessageUI", {
4645
},
4746

4847
events: {
49-
"messageEdited": "qx.event.type.Event",
50-
"messageDeleted": "qx.event.type.Event",
48+
"messageUpdated": "qx.event.type.Data",
49+
"messageDeleted": "qx.event.type.Data",
5150
},
5251

53-
members: {
54-
__message: null,
52+
properties: {
53+
message: {
54+
check: "Object",
55+
init: null,
56+
nullable: false,
57+
apply: "__applyMessage",
58+
},
59+
},
5560

61+
members: {
5662
_createChildControlImpl: function(id) {
57-
const isMyMessage = this.self().isMyMessage(this.__message);
63+
const isMyMessage = this.self().isMyMessage(this.getMessage());
5864
let control;
5965
switch (id) {
6066
case "thumbnail":
@@ -140,20 +146,29 @@ qx.Class.define("osparc.conversation.MessageUI", {
140146
return control || this.base(arguments, id);
141147
},
142148

143-
__buildLayout: function() {
149+
__applyMessage: function(message) {
150+
const isMyMessage = this.self().isMyMessage(message);
151+
this._getLayout().setColumnFlex(isMyMessage ? 0 : 2, 3); // spacer
152+
144153
const thumbnail = this.getChildControl("thumbnail");
145154

146155
const userName = this.getChildControl("user-name");
147156

148-
const date = new Date(this.__message["modified"]);
149-
const date2 = osparc.utils.Utils.formatDateAndTime(date);
157+
const createdDateData = new Date(message["created"]);
158+
const createdDate = osparc.utils.Utils.formatDateAndTime(createdDateData);
150159
const lastUpdate = this.getChildControl("last-updated");
151-
lastUpdate.setValue(date2);
160+
if (message["created"] === message["modified"]) {
161+
lastUpdate.setValue(createdDate);
162+
} else {
163+
const updatedDateData = new Date(message["modified"]);
164+
const updatedDate = osparc.utils.Utils.formatDateAndTime(updatedDateData);
165+
lastUpdate.setValue(createdDate + " (" + this.tr("edited") + " "+ updatedDate + ")");
166+
}
152167

153168
const messageContent = this.getChildControl("message-content");
154-
messageContent.setValue(this.__message["content"]);
169+
messageContent.setValue(message["content"]);
155170

156-
osparc.store.Users.getInstance().getUser(this.__message["userGroupId"])
171+
osparc.store.Users.getInstance().getUser(message["userGroupId"])
157172
.then(user => {
158173
if (user) {
159174
thumbnail.setSource(user.getThumbnail());
@@ -170,7 +185,7 @@ qx.Class.define("osparc.conversation.MessageUI", {
170185

171186
this.getChildControl("spacer");
172187

173-
if (this.self().isMyMessage(this.__message)) {
188+
if (this.self().isMyMessage(message)) {
174189
const menuButton = this.getChildControl("menu-button");
175190

176191
const menu = new qx.ui.menu.Menu().set({
@@ -189,20 +204,24 @@ qx.Class.define("osparc.conversation.MessageUI", {
189204
},
190205

191206
__editMessage: function() {
192-
const addMessage = new osparc.conversation.AddMessage(this.__studyData, this.__message["conversationId"], this.__message);
207+
const message = this.getMessage();
208+
209+
const addMessage = new osparc.conversation.AddMessage(this.__studyData, message["conversationId"], message);
193210
const title = this.tr("Edit message");
194211
const win = osparc.ui.window.Window.popUpInWindow(addMessage, title, 570, 135).set({
195212
clickAwayClose: false,
196213
resizable: true,
197214
showClose: true,
198215
});
199-
addMessage.addListener("messageEdited", () => {
216+
addMessage.addListener("messageUpdated", e => {
200217
win.close();
201-
this.fireDataEvent("messageEdited");
218+
this.fireDataEvent("messageUpdated", e.getData());
202219
});
203220
},
204221

205222
__deleteMessage: function() {
223+
const message = this.getMessage();
224+
206225
const win = new osparc.ui.window.Confirmation(this.tr("Delete message?")).set({
207226
caption: this.tr("Delete"),
208227
confirmText: this.tr("Delete"),
@@ -211,8 +230,8 @@ qx.Class.define("osparc.conversation.MessageUI", {
211230
win.open();
212231
win.addListener("close", () => {
213232
if (win.getConfirmed()) {
214-
osparc.study.Conversations.deleteMessage(this.__message["studyId"], this.__message["conversationId"], this.__message["messageId"])
215-
.then(() => this.fireEvent("messageDeleted"))
233+
osparc.study.Conversations.deleteMessage(message)
234+
.then(() => this.fireDataEvent("messageDeleted", message))
216235
.catch(err => osparc.FlashMessenger.logError(err));
217236
}
218237
});

0 commit comments

Comments
 (0)