diff --git a/services/static-webserver/client/source/class/osparc/auth/ui/RequestAccount.js b/services/static-webserver/client/source/class/osparc/auth/ui/RequestAccount.js index fed29c54090b..79d61170746d 100644 --- a/services/static-webserver/client/source/class/osparc/auth/ui/RequestAccount.js +++ b/services/static-webserver/client/source/class/osparc/auth/ui/RequestAccount.js @@ -113,6 +113,7 @@ qx.Class.define("osparc.auth.ui.RequestAccount", { const country = new qx.ui.form.SelectBox().set({ required: true }); + country.getChildControl("arrow").syncAppearance(); // force sync to show the arrow doubleSpaced.push(country); const countries = osparc.store.StaticInfo.getCountries(); countries.forEach(c => { @@ -144,6 +145,7 @@ qx.Class.define("osparc.auth.ui.RequestAccount", { case "s4lacad": case "s4ldesktopacad": { const application = new qx.ui.form.SelectBox(); + application.getChildControl("arrow").syncAppearance(); // force sync to show the arrow osparc.product.Utils.S4L_TOPICS.forEach(topic => { const lItem = new qx.ui.form.ListItem(topic.label, null, topic.id).set({ rich: true @@ -160,6 +162,7 @@ qx.Class.define("osparc.auth.ui.RequestAccount", { } case "osparc": { const application = new qx.ui.form.SelectBox(); + application.getChildControl("arrow").syncAppearance(); // force sync to show the arrow [{ id: "other", label: "Other" @@ -207,6 +210,7 @@ qx.Class.define("osparc.auth.ui.RequestAccount", { const hear = new qx.ui.form.SelectBox(); + hear.getChildControl("arrow").syncAppearance(); // force sync to show the arrow let hearOptions = []; switch (osparc.product.Utils.getProductName()) { case "osparc": diff --git a/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js b/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js index b2c1314d3cf9..0d7d8cbde7b5 100644 --- a/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js +++ b/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js @@ -44,7 +44,7 @@ qx.Class.define("osparc.conversation.AddMessage", { }, message: { - check: "Object", + check: "osparc.data.model.Message", init: null, nullable: true, event: "changeMessage", @@ -81,15 +81,20 @@ qx.Class.define("osparc.conversation.AddMessage", { this.getChildControl("add-comment-layout").add(control); break; } - case "comment-field": + case "comment-field": { control = new osparc.editor.MarkdownEditor(); control.addListener("textChanged", () => this.__addCommentPressed(), this); control.setCompact(true); - control.getChildControl("text-area").set({ + const textArea = control.getChildControl("text-area"); + textArea.set({ maxLength: osparc.data.model.Conversation.MAX_CONTENT_LENGTH, }); + textArea.addListener("appear", () => { + textArea.focus(); + textArea.activate(); + }); // make it visually connected to the button - control.getChildControl("text-area").getContentElement().setStyles({ + textArea.getContentElement().setStyles({ "border-top-right-radius": "0px", // no roundness there to match the arrow button }); // make it more compact @@ -97,6 +102,7 @@ qx.Class.define("osparc.conversation.AddMessage", { flex: 1 }); break; + } case "add-comment-button": control = new qx.ui.form.Button(null, "@FontAwesome5Solid/arrow-up/16").set({ toolTipText: this.tr("Ctrl+Enter"), @@ -129,7 +135,7 @@ qx.Class.define("osparc.conversation.AddMessage", { control = new qx.ui.form.Button("🔔 " + this.tr("Notify user")).set({ appearance: "form-button", allowGrowX: false, - alignX: "right" + alignX: "right", }); control.addListener("execute", () => this.__notifyUserTapped()); this._add(control); @@ -161,7 +167,7 @@ qx.Class.define("osparc.conversation.AddMessage", { if (message) { // edit mode const commentField = this.getChildControl("comment-field"); - commentField.setText(message["content"]); + commentField.setText(message.getContent()); } }, diff --git a/services/static-webserver/client/source/class/osparc/conversation/Conversation.js b/services/static-webserver/client/source/class/osparc/conversation/MessageList.js similarity index 66% rename from services/static-webserver/client/source/class/osparc/conversation/Conversation.js rename to services/static-webserver/client/source/class/osparc/conversation/MessageList.js index e36342ce1953..7750a90b6068 100644 --- a/services/static-webserver/client/source/class/osparc/conversation/Conversation.js +++ b/services/static-webserver/client/source/class/osparc/conversation/MessageList.js @@ -16,7 +16,7 @@ ************************************************************************ */ -qx.Class.define("osparc.conversation.Conversation", { +qx.Class.define("osparc.conversation.MessageList", { extend: qx.ui.core.Widget, /** @@ -25,8 +25,6 @@ qx.Class.define("osparc.conversation.Conversation", { construct: function(conversation) { this.base(arguments); - this._messages = []; - this._setLayout(new qx.ui.layout.VBox(5)); this._buildLayout(); @@ -51,8 +49,6 @@ qx.Class.define("osparc.conversation.Conversation", { }, members: { - _messages: null, - _createChildControlImpl: function(id) { let control; switch (id) { @@ -104,15 +100,11 @@ qx.Class.define("osparc.conversation.Conversation", { if (conversation) { conversation.addListener("messageAdded", e => { const data = e.getData(); - this.addMessage(data); - }); - conversation.addListener("messageUpdated", e => { - const data = e.getData(); - this.updateMessage(data); + this.__messageAdded(data); }); conversation.addListener("messageDeleted", e => { const data = e.getData(); - this.deleteMessage(data); + this.__messageDeleted(data); }); } }, @@ -128,12 +120,12 @@ qx.Class.define("osparc.conversation.Conversation", { return; } + this.getConversation().getMessages().forEach(message => this.__messageAdded(message)); + loadMoreMessages.show(); loadMoreMessages.setFetching(true); this.getConversation().getNextMessages() .then(resp => { - const messages = resp["data"]; - messages.forEach(message => this.addMessage(message)); if (resp["_links"]["next"] === null && loadMoreMessages) { loadMoreMessages.exclude(); } @@ -146,40 +138,34 @@ qx.Class.define("osparc.conversation.Conversation", { }, getMessages: function() { - return this._messages; + return this.getConversation().getMessages(); }, clearAllMessages: function() { - this._messages = []; this.getChildControl("messages-container").removeAll(); - this.fireEvent("messagesChanged"); }, - addMessage: function(message) { + __getMessageUI: function(messageId) { + const messagesContainer = this.getChildControl("messages-container"); + return messagesContainer.getChildren().find( + ctrl => ("getMessage" in ctrl && ctrl.getMessage().getMessageId() === messageId) + ); + }, + + __messageAdded: function(message) { // ignore it if it was already there - const messageIndex = this._messages.findIndex(msg => msg["messageId"] === message["messageId"]); - if (messageIndex !== -1) { + const existingMessageUI = this.__getMessageUI(message.getMessageId()); + if (existingMessageUI) { return; } - // determine insertion index for latest‐first order - const newTime = new Date(message["created"]); - let insertAt = this._messages.findIndex(m => new Date(m["created"]) > newTime); - if (insertAt === -1) { - insertAt = this._messages.length; - } - - // Insert the message in the messages array - this._messages.splice(insertAt, 0, message); - // Add the UI element to the messages list let control = null; - switch (message["type"]) { + switch (message.getType()) { case "MESSAGE": control = this._createMessageUI(message); - control.addListener("messageUpdated", e => this.updateMessage(e.getData())); - control.addListener("messageDeleted", e => this.deleteMessage(e.getData())); + control.addListener("messageDeleted", e => this.__messageDeleted(e.getData())); break; case "NOTIFICATION": control = new osparc.conversation.NotificationUI(message); @@ -187,6 +173,7 @@ qx.Class.define("osparc.conversation.Conversation", { } if (control) { // insert into the UI at the same position + const insertAt = this.getConversation().getMessageIndex(message.getMessageId()); const messagesContainer = this.getChildControl("messages-container"); messagesContainer.addAt(control, insertAt); } @@ -201,44 +188,15 @@ qx.Class.define("osparc.conversation.Conversation", { this.fireEvent("messagesChanged"); }, - deleteMessage: function(message) { - // remove it from the messages array - const messageIndex = this._messages.findIndex(msg => msg["messageId"] === message["messageId"]); - if (messageIndex === -1) { - return; - } - this._messages.splice(messageIndex, 1); - + __messageDeleted: function(message) { // Remove the UI element from the messages list - const messagesContainer = this.getChildControl("messages-container"); - const children = messagesContainer.getChildren(); - const controlIndex = children.findIndex( - ctrl => ("getMessage" in ctrl && ctrl.getMessage()["messageId"] === message["messageId"]) - ); - if (controlIndex > -1) { - messagesContainer.remove(children[controlIndex]); + const existingMessageUI = this.__getMessageUI(message.getMessageId()); + if (existingMessageUI) { + const messagesContainer = this.getChildControl("messages-container"); + messagesContainer.remove(existingMessageUI); } this.fireEvent("messagesChanged"); }, - - updateMessage: function(message) { - // Replace the message in the messages array - const messageIndex = this._messages.findIndex(msg => msg["messageId"] === message["messageId"]); - if (messageIndex === -1) { - return; - } - this._messages[messageIndex] = message; - - // Update the UI element from the messages list - const messagesContainer = this.getChildControl("messages-container"); - const messageUI = messagesContainer.getChildren().find(control => { - return "getMessage" in control && control.getMessage()["messageId"] === message["messageId"]; - }); - if (messageUI) { - // Force a new reference - messageUI.setMessage(Object.assign({}, message)); - } - }, } }); diff --git a/services/static-webserver/client/source/class/osparc/conversation/MessageUI.js b/services/static-webserver/client/source/class/osparc/conversation/MessageUI.js index d7ded5e9d36c..cb57114265eb 100644 --- a/services/static-webserver/client/source/class/osparc/conversation/MessageUI.js +++ b/services/static-webserver/client/source/class/osparc/conversation/MessageUI.js @@ -20,7 +20,7 @@ qx.Class.define("osparc.conversation.MessageUI", { extend: qx.ui.core.Widget, /** - * @param message {Object} message data + * @param message {osparc.data.model.Message} message * @param studyData {Object?null} serialized Study Data */ construct: function(message, studyData = null) { @@ -38,10 +38,10 @@ qx.Class.define("osparc.conversation.MessageUI", { statics: { isMyMessage: function(message) { - if (message["userGroupId"] === "system") { + if (message.getUserGroupId() === osparc.data.model.Message.SYSTEM_MESSAGE_ID) { return false; } - return message && osparc.auth.Data.getInstance().getGroupId() === message["userGroupId"]; + return message && osparc.auth.Data.getInstance().getGroupId() === message.getUserGroupId(); } }, @@ -52,10 +52,10 @@ qx.Class.define("osparc.conversation.MessageUI", { properties: { message: { - check: "Object", + check: "osparc.data.model.Message", init: null, nullable: false, - apply: "__applyMessage", + apply: "_applyMessage", }, }, @@ -134,27 +134,30 @@ qx.Class.define("osparc.conversation.MessageUI", { return control || this.base(arguments, id); }, - __applyMessage: function(message) { - const createdDateData = new Date(message["created"]); - const createdDate = osparc.utils.Utils.formatDateAndTime(createdDateData); - const lastUpdate = this.getChildControl("last-updated"); - if (message["created"] === message["modified"]) { - lastUpdate.setValue(createdDate); - } else { - const updatedDateData = new Date(message["modified"]); - const updatedDate = osparc.utils.Utils.formatDateAndTime(updatedDateData); - lastUpdate.setValue(createdDate + " (" + this.tr("edited") + " "+ updatedDate + ")"); - } + _applyMessage: function(message) { + const updateLastUpdate = () => { + const createdDate = osparc.utils.Utils.formatDateAndTime(message.getCreated()); + let value = ""; + if (message.getCreated().getTime() === message.getModified().getTime()) { + value = createdDate; + } else { + const updatedDate = osparc.utils.Utils.formatDateAndTime(message.getModified()); + value = createdDate + " (" + this.tr("edited") + " "+ updatedDate + ")"; + } + this.getChildControl("last-updated").setValue(value); + }; + updateLastUpdate(); + message.addListener("changeModified", () => updateLastUpdate()); const messageContent = this.getChildControl("message-content"); - messageContent.setValue(message["content"]); + message.bind("content", messageContent, "value"); const avatar = this.getChildControl("avatar"); const userName = this.getChildControl("user-name"); - if (message["userGroupId"] === "system") { + if (message.getUserGroupId() === osparc.data.model.Message.SYSTEM_MESSAGE_ID) { userName.setValue("Support"); } else { - osparc.store.Users.getInstance().getUser(message["userGroupId"]) + osparc.store.Users.getInstance().getUser(message.getUserGroupId()) .then(user => { avatar.setUser(user); userName.setValue(user ? user.getLabel() : "Unknown user"); @@ -188,23 +191,22 @@ qx.Class.define("osparc.conversation.MessageUI", { const addMessage = new osparc.conversation.AddMessage().set({ studyData: this.__studyData, - conversationId: message["conversationId"], + conversationId: message.getConversationId(), message, }); + addMessage.getChildControl("notify-user-button").exclude(); const title = this.tr("Edit message"); - const win = osparc.ui.window.Window.popUpInWindow(addMessage, title, 570, 135).set({ + const win = osparc.ui.window.Window.popUpInWindow(addMessage, title, 570, 120).set({ clickAwayClose: false, resizable: true, showClose: true, }); addMessage.addListener("updateMessage", e => { const content = e.getData(); - const conversationId = message["conversationId"]; - const messageId = message["messageId"]; if (this.__studyData) { - promise = osparc.store.ConversationsProject.getInstance().editMessage(this.__studyData["uuid"], conversationId, messageId, content); + promise = osparc.store.ConversationsProject.getInstance().editMessage(message, content, this.__studyData["uuid"]); } else { - promise = osparc.store.ConversationsSupport.getInstance().editMessage(conversationId, messageId, content); + promise = osparc.store.ConversationsSupport.getInstance().editMessage(message, content); } promise.then(data => { win.close(); @@ -226,7 +228,7 @@ qx.Class.define("osparc.conversation.MessageUI", { if (win.getConfirmed()) { let promise = null; if (this.__studyData) { - promise = osparc.store.ConversationsProject.getInstance().deleteMessage(message); + promise = osparc.store.ConversationsProject.getInstance().deleteMessage(message, this.__studyData["uuid"]); } else { promise = osparc.store.ConversationsSupport.getInstance().deleteMessage(message); } diff --git a/services/static-webserver/client/source/class/osparc/conversation/NotificationUI.js b/services/static-webserver/client/source/class/osparc/conversation/NotificationUI.js index 34247eea09ec..ef9bc1a1dd71 100644 --- a/services/static-webserver/client/source/class/osparc/conversation/NotificationUI.js +++ b/services/static-webserver/client/source/class/osparc/conversation/NotificationUI.js @@ -17,99 +17,35 @@ qx.Class.define("osparc.conversation.NotificationUI", { - extend: qx.ui.core.Widget, - - /** - * @param message {Object} message - */ - construct: function(message) { - this.base(arguments); - - const isMyMessage = osparc.conversation.MessageUI.isMyMessage(message); - const layout = new qx.ui.layout.Grid(4, 4); - layout.setColumnFlex(isMyMessage ? 0 : 3, 3); // spacer - layout.setRowAlign(0, "center", "middle"); - this._setLayout(layout); - this.setPadding(5); - - this.set({ - message, - }); - }, - - properties: { - message: { - check: "Object", - init: null, - nullable: false, - apply: "__applyMessage", - }, - }, + extend: osparc.conversation.MessageUI, members: { - // spacer - date - content - (thumbnail-spacer) - // (thumbnail-spacer) - content - date - spacer _createChildControlImpl: function(id) { - const isMyMessage = osparc.conversation.MessageUI.isMyMessage(this.getMessage()); let control; switch (id) { - case "thumbnail-spacer": - control = new qx.ui.core.Spacer().set({ - width: 32, - }); - this._add(control, { - row: 0, - column: isMyMessage ? 3 : 0, - }); - break; - case "message-content": + case "notification-content": control = new qx.ui.basic.Label().set({ + paddingTop: 5, + paddingBottom: 5, }); - control.getContentElement().setStyles({ - "text-align": isMyMessage ? "right" : "left", - }); - this._add(control, { - row: 0, - column: isMyMessage ? 2 : 1, - }); - break; - case "last-updated": - control = new qx.ui.basic.Label().set({ - font: "text-12" - }); - this._add(control, { - row: 0, - column: isMyMessage ? 1 : 2, - }); - break; - case "spacer": - control = new qx.ui.core.Spacer(); - this._add(control, { - row: 0, - column: isMyMessage ? 0 : 3, - }); + this.getChildControl("main-layout").addAt(control, 2); break; } - return control || this.base(arguments, id); }, - __applyMessage: function(message) { - this._removeAll(); + _applyMessage: function(message) { + this.base(arguments, message); - this.getChildControl("thumbnail-spacer"); + this.getChildControl("menu-button").hide(); + this.getChildControl("message-bubble").exclude(); const isMyMessage = osparc.conversation.MessageUI.isMyMessage(message); - const modifiedDate = new Date(message["modified"]); - const date = osparc.utils.Utils.formatDateAndTime(modifiedDate); - const lastUpdate = this.getChildControl("last-updated"); - lastUpdate.setValue(isMyMessage ? date + " -" : " - " + date); - - const messageContent = this.getChildControl("message-content"); - const notifierUserGroupId = parseInt(message["userGroupId"]); - const notifiedUserGroupId = parseInt(message["content"]); let msgContent = "🔔 "; + const messageContent = this.getChildControl("notification-content"); + const notifierUserGroupId = parseInt(message.getUserGroupId()); + const notifiedUserGroupId = parseInt(message.getContent()); Promise.all([ osparc.store.Users.getInstance().getUser(notifierUserGroupId), osparc.store.Users.getInstance().getUser(notifiedUserGroupId), @@ -141,8 +77,6 @@ qx.Class.define("osparc.conversation.NotificationUI", { .finally(() => { messageContent.setValue(msgContent); }); - - this.getChildControl("spacer"); } } }); diff --git a/services/static-webserver/client/source/class/osparc/data/model/Conversation.js b/services/static-webserver/client/source/class/osparc/data/model/Conversation.js index f09ae798c51f..cdb58a0b79ac 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Conversation.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Conversation.js @@ -133,14 +133,14 @@ qx.Class.define("osparc.data.model.Conversation", { }, firstMessage: { - check: "Object", + check: "osparc.data.model.Message", nullable: true, init: null, event: "changeFirstMessage", }, lastMessage: { - check: "Object", + check: "osparc.data.model.Message", nullable: true, init: null, event: "changeLastMessage", @@ -174,7 +174,7 @@ qx.Class.define("osparc.data.model.Conversation", { __applyLastMessage: function(lastMessage) { const name = this.getName(); if (!name || name === "null") { - this.setNameAlias(lastMessage ? lastMessage.content : ""); + this.setNameAlias(lastMessage ? lastMessage.getContent() : ""); } }, @@ -185,19 +185,19 @@ qx.Class.define("osparc.data.model.Conversation", { this.self().CHANNELS.CONVERSATION_MESSAGE_UPDATED, this.self().CHANNELS.CONVERSATION_MESSAGE_DELETED, ].forEach(eventName => { - const eventHandler = message => { - if (message) { - const conversationId = message["conversationId"]; + const eventHandler = messageData => { + if (messageData) { + const conversationId = messageData["conversationId"]; if (conversationId === this.getConversationId()) { switch (eventName) { case this.self().CHANNELS.CONVERSATION_MESSAGE_CREATED: - this.addMessage(message); + this.addMessage(messageData); break; case this.self().CHANNELS.CONVERSATION_MESSAGE_UPDATED: - this.updateMessage(message); + this.updateMessage(messageData); break; case this.self().CHANNELS.CONVERSATION_MESSAGE_DELETED: - this.deleteMessage(message); + this.deleteMessage(messageData); break; } } @@ -217,17 +217,19 @@ qx.Class.define("osparc.data.model.Conversation", { .then(resp => { const messages = resp["data"]; if (messages.length) { - this.addMessage(messages[0]); - this.setLastMessage(messages[0]); + const lastMessage = new osparc.data.model.Message(messages[0]); + this.setLastMessage(lastMessage); } // fetch first message only if there is more than one message if (resp["_meta"]["total"] === 1) { - this.setFirstMessage(messages[0]); + const firstMessage = new osparc.data.model.Message(messages[0]); + this.setFirstMessage(firstMessage); } else if (resp["_meta"]["total"] > 1) { osparc.store.ConversationsSupport.getInstance().fetchFirstMessage(this.getConversationId(), resp["_meta"]) .then(firstMessages => { if (firstMessages.length) { - this.setFirstMessage(firstMessages[0]); + const firstMessage = new osparc.data.model.Message(firstMessages[0]); + this.setFirstMessage(firstMessage); } }); } @@ -266,8 +268,8 @@ qx.Class.define("osparc.data.model.Conversation", { osparc.data.Resources.fetch("conversationsSupport", "getMessagesPage", params, options); return promise .then(resp => { - const messages = resp["data"]; - messages.forEach(message => this.addMessage(message)); + const messagesData = resp["data"]; + messagesData.forEach(messageData => this.addMessage(messageData)); this.__nextRequestParams = resp["_links"]["next"]; return resp; }) @@ -287,39 +289,60 @@ qx.Class.define("osparc.data.model.Conversation", { }); }, - addMessage: function(message) { - if (message) { - const found = this.__messages.find(msg => msg["messageId"] === message["messageId"]); - if (!found) { - this.__messages.push(message); - this.fireDataEvent("messageAdded", message); - } - // latest first - this.__messages.sort((a, b) => new Date(b.created) - new Date(a.created)); - this.setLastMessage(this.__messages[0]); + getMessages: function() { + return this.__messages; + }, + + getMessageIndex: function(messageId) { + return this.__messages.findIndex(msg => msg.getMessageId() === messageId); + }, + + messageExists: function(messageId) { + return this.__messages.some(msg => msg.getMessageId() === messageId); + }, + + addMessage: function(messageData) { + let message = this.__messages.find(msg => msg.getMessageId() === messageData["messageId"]); + if (!message) { + message = new osparc.data.model.Message(messageData); + this.__messages.push(message); + osparc.data.model.Message.sortMessagesByDate(this.__messages); + this.fireDataEvent("messageAdded", message); + this.__evalFirstAndLastMessage(); } + return message; }, - updateMessage: function(message) { - if (message) { - const found = this.__messages.find(msg => msg["messageId"] === message["messageId"]); + updateMessage: function(messageData) { + if (messageData) { + const found = this.__messages.find(msg => msg.getMessageId() === messageData["messageId"]); if (found) { - Object.assign(found, message); + found.setData(messageData); this.fireDataEvent("messageUpdated", found); + this.__evalFirstAndLastMessage(); } } }, - deleteMessage: function(message) { - if (message) { - const found = this.__messages.find(msg => msg["messageId"] === message["messageId"]); + deleteMessage: function(messageData) { + if (messageData) { + const found = this.__messages.find(msg => msg.getMessageId() === messageData["messageId"]); if (found) { this.__messages.splice(this.__messages.indexOf(found), 1); this.fireDataEvent("messageDeleted", found); + this.__evalFirstAndLastMessage(); } } }, + __evalFirstAndLastMessage: function() { + if (this.__messages && this.__messages.length) { + // newest first + this.setFirstMessage(this.__messages[this.__messages.length - 1]); + this.setLastMessage(this.__messages[0]); + } + }, + getContextProjectId: function() { if (this.getExtraContext() && "projectId" in this.getExtraContext()) { return this.getExtraContext()["projectId"]; diff --git a/services/static-webserver/client/source/class/osparc/data/model/Message.js b/services/static-webserver/client/source/class/osparc/data/model/Message.js new file mode 100644 index 000000000000..4b775d18f5a3 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/data/model/Message.js @@ -0,0 +1,110 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +/** + * Class that stores Message data. + */ + +qx.Class.define("osparc.data.model.Message", { + extend: qx.core.Object, + + /** + * @param messageData {Object} Object containing the serialized Message Data + * */ + construct: function(messageData) { + this.base(arguments); + + this.setData(messageData); + }, + + properties: { + messageId: { + check: "String", + nullable: false, + init: null, + event: "changeMessageId", + }, + + conversationId: { + check: "String", + nullable: true, // system messages have null conversationId + init: null, + event: "changeConversationId", + }, + + userGroupId: { + check: "Number", + nullable: false, + init: null, + event: "changeUserGroupId", + }, + + content: { + check: "String", + nullable: false, + init: null, + event: "changeContent", + }, + + type: { + check: [ + "MESSAGE", + "NOTIFICATION", + ], + nullable: false, + init: null, + event: "changeType", + }, + + created: { + check: "Date", + nullable: false, + init: null, + event: "changeCreated", + }, + + modified: { + check: "Date", + nullable: false, + init: null, + event: "changeModified", + }, + }, + + statics: { + SYSTEM_MESSAGE_ID: -1, + + sortMessagesByDate: function(messages) { + // oldest first: higher in the list. Latest at the bottom + messages.sort((a, b) => a.getCreated() - b.getCreated()); + }, + }, + + members: { + setData: function(messageData) { + this.set({ + messageId: messageData.messageId, + conversationId: messageData.conversationId, + content: messageData.content, + userGroupId: messageData.userGroupId, + created: new Date(messageData.created), + modified: new Date(messageData.modified), + type: messageData.type, + }); + }, + } +}); diff --git a/services/static-webserver/client/source/class/osparc/data/model/Node.js b/services/static-webserver/client/source/class/osparc/data/model/Node.js index 0426f16e8532..a1e99d5631d0 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Node.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Node.js @@ -330,6 +330,7 @@ qx.Class.define("osparc.data.model.Node", { } const bootModeSB = new qx.ui.form.SelectBox(); + bootModeSB.getChildControl("arrow").syncAppearance(); // force sync to show the arrow this.populateBootModes(bootModeSB, nodeMetadata, workbench, nodeId); return bootModeSB; }, diff --git a/services/static-webserver/client/source/class/osparc/desktop/account/ProfilePage.js b/services/static-webserver/client/source/class/osparc/desktop/account/ProfilePage.js index e15647e63946..f786949cbf4b 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/account/ProfilePage.js +++ b/services/static-webserver/client/source/class/osparc/desktop/account/ProfilePage.js @@ -460,6 +460,7 @@ qx.Class.define("osparc.desktop.account.ProfilePage", { const twoFAPreferenceSB = new qx.ui.form.SelectBox().set({ allowGrowX: false }); + twoFAPreferenceSB.getChildControl("arrow").syncAppearance(); // force sync to show the arrow [{ id: "SMS", label: "SMS" diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js b/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js index c5609b789e00..e7c8391f7dda 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js @@ -244,6 +244,7 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", { width: 300, appearance: "appmotion-buy-credits-select" }); + paymentMethodField.getChildControl("arrow").syncAppearance(); // force sync to show the arrow paymentMethodLayout.add(paymentMethodField); const addNewPaymentMethod = new qx.ui.basic.Label(this.tr("Add Payment Method")).set({ marginLeft: 15, diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsSummary.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsSummary.js index 6d59821089e9..5f46e4a0249d 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsSummary.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsSummary.js @@ -112,6 +112,7 @@ qx.Class.define("osparc.desktop.credits.CreditsSummary", { alignX: "center", backgroundColor: "transparent" }); + control.getChildControl("arrow").syncAppearance(); // force sync to show the arrow this.self().TIME_RANGES.forEach(tr => { const trItem = new qx.ui.form.ListItem(tr.label, null, tr.key); control.add(trItem); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js b/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js index baf94205c9bd..7c44a204b747 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js @@ -46,6 +46,7 @@ qx.Class.define("osparc.desktop.credits.ResourceInTableViewer", { allowStretchX: false, width: 200 }); + control.getChildControl("arrow").syncAppearance(); // force sync to show the arrow layout = this.getChildControl("wallet-selector-layout"); layout.add(control); break; diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Utils.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Utils.js index d9a4c93ec810..a26cd6cb6f3e 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/Utils.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Utils.js @@ -103,6 +103,7 @@ qx.Class.define("osparc.desktop.credits.Utils", { const walletSelector = new qx.ui.form.SelectBox().set({ maxWidth: 250 }); + walletSelector.getChildControl("arrow").syncAppearance(); // force sync to show the arrow const populateSelectBox = selectBox => { selectBox.removeAll(); diff --git a/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/GeneralPage.js b/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/GeneralPage.js index d464c861d195..fcabd3892ebf 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/GeneralPage.js +++ b/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/GeneralPage.js @@ -55,6 +55,7 @@ qx.Class.define("osparc.desktop.preferences.pages.GeneralPage", { const walletIndicatorVisibilitySB = new qx.ui.form.SelectBox().set({ allowGrowX: false }); + walletIndicatorVisibilitySB.getChildControl("arrow").syncAppearance(); // force sync to show the arrow [{ id: "always", label: "Always" diff --git a/services/static-webserver/client/source/class/osparc/form/Auto.js b/services/static-webserver/client/source/class/osparc/form/Auto.js index 3661970cbf3a..07cb31f3f5f5 100644 --- a/services/static-webserver/client/source/class/osparc/form/Auto.js +++ b/services/static-webserver/client/source/class/osparc/form/Auto.js @@ -521,6 +521,7 @@ qx.Class.define("osparc.form.Auto", { break; case "SelectBox": control = new qx.ui.form.SelectBox(); + control.getChildControl("arrow").syncAppearance(); // force sync to show the arrow setup = this.__setupSelectBox; s.set["minWidth"] = 80; break; diff --git a/services/static-webserver/client/source/class/osparc/metadata/QualityEditor.js b/services/static-webserver/client/source/class/osparc/metadata/QualityEditor.js index 7ac8377433eb..b972c911096a 100644 --- a/services/static-webserver/client/source/class/osparc/metadata/QualityEditor.js +++ b/services/static-webserver/client/source/class/osparc/metadata/QualityEditor.js @@ -340,6 +340,7 @@ qx.Class.define("osparc.metadata.QualityEditor", { const targetRule = copyTSRTarget[ruleKey]; if (targetRule.level !== undefined) { const targetsBox = new qx.ui.form.SelectBox(); + targetsBox.getChildControl("arrow").syncAppearance(); // force sync to show the arrow const conformanceLevels = osparc.metadata.Quality.getConformanceLevel(); Object.values(conformanceLevels).forEach(conformanceLevel => { let text = `${conformanceLevel.level} - `; diff --git a/services/static-webserver/client/source/class/osparc/node/ParameterEditor.js b/services/static-webserver/client/source/class/osparc/node/ParameterEditor.js index b1993171f0c9..90d33be64620 100644 --- a/services/static-webserver/client/source/class/osparc/node/ParameterEditor.js +++ b/services/static-webserver/client/source/class/osparc/node/ParameterEditor.js @@ -62,6 +62,7 @@ qx.Class.define("osparc.node.ParameterEditor", { control = new qx.ui.form.SelectBox().set({ allowGrowX: false }); + control.getChildControl("arrow").syncAppearance(); // force sync to show the arrow [ "number", "integer", diff --git a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js index 944abc528b24..37ce1b288456 100644 --- a/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js +++ b/services/static-webserver/client/source/class/osparc/node/TierSelectionView.js @@ -43,6 +43,7 @@ qx.Class.define("osparc.node.TierSelectionView", { allowGrowX: false, allowGrowY: false }); + tierBox.getChildControl("arrow").syncAppearance(); // force sync to show the arrow tiersLayout.add(tierBox); const node = this.getNode(); diff --git a/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js b/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js index 7a63a3041dc1..2e869b27c1e1 100644 --- a/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js +++ b/services/static-webserver/client/source/class/osparc/pricing/PlanEditor.js @@ -135,6 +135,7 @@ qx.Class.define("osparc.pricing.PlanEditor", { control = new qx.ui.form.SelectBox().set({ font: "text-14", }); + control.getChildControl("arrow").syncAppearance(); // force sync to show the arrow [ "TIER", "LICENSE", diff --git a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js index 79ed43ca52f3..e1d07ba806c0 100644 --- a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js +++ b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js @@ -148,7 +148,7 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { allowGrowX: false, backgroundColor: "transparent", }); - control.getChildControl("arrow").syncAppearance(); + control.getChildControl("arrow").syncAppearance(); // force sync to show the arrow this.getChildControl("access-rights-layout").add(control); break; case "access-rights-helper": { diff --git a/services/static-webserver/client/source/class/osparc/store/ConversationsProject.js b/services/static-webserver/client/source/class/osparc/store/ConversationsProject.js index fc513663524c..345a991dcef3 100644 --- a/services/static-webserver/client/source/class/osparc/store/ConversationsProject.js +++ b/services/static-webserver/client/source/class/osparc/store/ConversationsProject.js @@ -128,7 +128,9 @@ qx.Class.define("osparc.store.ConversationsProject", { .catch(err => osparc.FlashMessenger.logError(err)); }, - editMessage: function(studyId, conversationId, messageId, message) { + editMessage: function(message, content, studyId) { + const conversationId = message.getConversationId(); + const messageId = message.getMessageId(); const params = { url: { studyId, @@ -136,19 +138,21 @@ qx.Class.define("osparc.store.ConversationsProject", { messageId, }, data: { - "content": message, + content, }, }; return osparc.data.Resources.fetch("conversationsStudies", "editMessage", params) .catch(err => osparc.FlashMessenger.logError(err)); }, - deleteMessage: function(message) { + deleteMessage: function(message, studyId) { + const conversationId = message.getConversationId(); + const messageId = message.getMessageId(); const params = { url: { - studyId: message["projectId"], - conversationId: message["conversationId"], - messageId: message["messageId"], + studyId, + conversationId, + messageId, }, }; return osparc.data.Resources.fetch("conversationsStudies", "deleteMessage", params) diff --git a/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js b/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js index 89f849bf68b9..6201f104db21 100644 --- a/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js +++ b/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js @@ -164,13 +164,13 @@ qx.Class.define("osparc.store.ConversationsSupport", { return osparc.data.Resources.fetch("conversationsSupport", "getMessagesPage", params); }, - postMessage: function(conversationId, message) { + postMessage: function(conversationId, content) { const params = { url: { conversationId, }, data: { - "content": message, + content, "type": "MESSAGE", } }; @@ -178,14 +178,16 @@ qx.Class.define("osparc.store.ConversationsSupport", { .catch(err => osparc.FlashMessenger.logError(err)); }, - editMessage: function(conversationId, messageId, message) { + editMessage: function(message, content) { + const conversationId = message.getConversationId(); + const messageId = message.getMessageId(); const params = { url: { conversationId, messageId, }, data: { - "content": message, + content, }, }; return osparc.data.Resources.fetch("conversationsSupport", "editMessage", params) @@ -193,10 +195,12 @@ qx.Class.define("osparc.store.ConversationsSupport", { }, deleteMessage: function(message) { + const conversationId = message.getConversationId(); + const messageId = message.getMessageId(); const params = { url: { - conversationId: message["conversationId"], - messageId: message["messageId"], + conversationId, + messageId, }, }; return osparc.data.Resources.fetch("conversationsSupport", "deleteMessage", params) @@ -206,11 +210,5 @@ qx.Class.define("osparc.store.ConversationsSupport", { __addToCache: function(conversation) { this.__conversationsCached[conversation.getConversationId()] = conversation; }, - - __addMessageToConversation: function(conversationId, messageData) { - if (conversationId in this.__conversationsCached) { - this.__conversationsCached[conversationId].addMessage(messageData); - } - }, } }); diff --git a/services/static-webserver/client/source/class/osparc/study/Conversation.js b/services/static-webserver/client/source/class/osparc/study/Conversation.js index 1535202eca84..a72ad5ab4833 100644 --- a/services/static-webserver/client/source/class/osparc/study/Conversation.js +++ b/services/static-webserver/client/source/class/osparc/study/Conversation.js @@ -17,7 +17,7 @@ qx.Class.define("osparc.study.Conversation", { - extend: osparc.conversation.Conversation, + extend: osparc.conversation.MessageList, /** * @param studyData {String} Study Data @@ -70,6 +70,7 @@ qx.Class.define("osparc.study.Conversation", { }); }, + // overridden _createMessageUI: function(message) { const messageUI = new osparc.conversation.MessageUI(message, this.__studyData); messageUI.getChildControl("message-content").set({ diff --git a/services/static-webserver/client/source/class/osparc/study/ConversationPage.js b/services/static-webserver/client/source/class/osparc/study/ConversationPage.js index 582d0190d79b..64c76471f07d 100644 --- a/services/static-webserver/client/source/class/osparc/study/ConversationPage.js +++ b/services/static-webserver/client/source/class/osparc/study/ConversationPage.js @@ -179,8 +179,8 @@ qx.Class.define("osparc.study.ConversationPage", { __updateMessagesNumber: function() { const nMessagesLabel = this.getChildControl("n-messages"); - const messages = this.getChildControl("conversation").getMessages(); - const nMessages = messages.filter(msg => msg["type"] === "MESSAGE").length; + const messages = this.getConversation().getMessages(); + const nMessages = messages.filter(msg => msg.getType() === "MESSAGE").length; if (nMessages === 0) { nMessagesLabel.setValue(this.tr("No Messages yet")); } else if (nMessages === 1) { diff --git a/services/static-webserver/client/source/class/osparc/study/Utils.js b/services/static-webserver/client/source/class/osparc/study/Utils.js index 57bff3573829..e095aa764904 100644 --- a/services/static-webserver/client/source/class/osparc/study/Utils.js +++ b/services/static-webserver/client/source/class/osparc/study/Utils.js @@ -185,6 +185,7 @@ qx.Class.define("osparc.study.Utils", { const templateTypeSB = new qx.ui.form.SelectBox().set({ allowGrowX: false, }); + templateTypeSB.getChildControl("arrow").syncAppearance(); // force sync to show the arrow const templateTypes = [{ label: "Template", id: osparc.data.model.StudyUI.TEMPLATE_TYPE, diff --git a/services/static-webserver/client/source/class/osparc/support/BookACallTopicSelector.js b/services/static-webserver/client/source/class/osparc/support/BookACallTopicSelector.js index 9c39a10e75ac..a4c4ebcc83ef 100644 --- a/services/static-webserver/client/source/class/osparc/support/BookACallTopicSelector.js +++ b/services/static-webserver/client/source/class/osparc/support/BookACallTopicSelector.js @@ -75,6 +75,7 @@ qx.Class.define("osparc.support.BookACallTopicSelector", { control = new qx.ui.form.SelectBox().set({ marginLeft: 20, }); + control.getChildControl("arrow").syncAppearance(); // force sync to show the arrow this.getChildControl("content-box").add(control); this.getChildControl("specific-intro-button").bind("value", control, "visibility", { converter: val => val ? "visible" : "excluded" diff --git a/services/static-webserver/client/source/class/osparc/support/Conversation.js b/services/static-webserver/client/source/class/osparc/support/Conversation.js index f8c891185056..550c3af11555 100644 --- a/services/static-webserver/client/source/class/osparc/support/Conversation.js +++ b/services/static-webserver/client/source/class/osparc/support/Conversation.js @@ -17,7 +17,7 @@ qx.Class.define("osparc.support.Conversation", { - extend: osparc.conversation.Conversation, + extend: osparc.conversation.MessageList, /** * @param conversation {osparc.data.model.Conversation} Conversation @@ -218,16 +218,18 @@ qx.Class.define("osparc.support.Conversation", { } if (msg) { const now = new Date(); - const systemMessage = { + const systemMessageData = { "conversationId": null, "content": msg, "created": now.toISOString(), "messageId": `system-${now.getTime()}`, "modified": now.toISOString(), "type": "MESSAGE", - "userGroupId": "system", + "userGroupId": osparc.data.model.Message.SYSTEM_MESSAGE_ID, }; - this.addMessage(systemMessage); + const systemMessage = new osparc.data.model.Message(systemMessageData); + const messageUI = new osparc.conversation.MessageUI(systemMessage); + this.getChildControl("messages-container").add(messageUI); } }, diff --git a/services/static-webserver/client/source/class/osparc/support/ConversationListItem.js b/services/static-webserver/client/source/class/osparc/support/ConversationListItem.js index d605405d07bd..60e9015d6d25 100644 --- a/services/static-webserver/client/source/class/osparc/support/ConversationListItem.js +++ b/services/static-webserver/client/source/class/osparc/support/ConversationListItem.js @@ -63,17 +63,17 @@ qx.Class.define("osparc.support.ConversationListItem", { const conversation = this.getConversation(); const lastMessage = conversation.getLastMessage(); if (lastMessage) { - const date = osparc.utils.Utils.formatDateAndTime(new Date(lastMessage.created)); + const date = osparc.utils.Utils.formatDateAndTime(lastMessage.getCreated()); this.set({ role: date, }); - const userGroupId = lastMessage.userGroupId; + const userGroupId = lastMessage.getUserGroupId(); osparc.store.Users.getInstance().getUser(userGroupId) .then(user => { if (user) { this.set({ thumbnail: user.getThumbnail(), - subtitle: user.getLabel() + ": " + lastMessage["content"], + subtitle: user.getLabel() + ": " + lastMessage.getContent(), }); } }); @@ -84,7 +84,7 @@ qx.Class.define("osparc.support.ConversationListItem", { const conversation = this.getConversation(); const firstMessage = conversation.getFirstMessage(); if (firstMessage) { - const userGroupId = firstMessage.userGroupId; + const userGroupId = firstMessage.getUserGroupId(); osparc.store.Users.getInstance().getUser(userGroupId) .then(user => { if (user) { @@ -93,7 +93,7 @@ qx.Class.define("osparc.support.ConversationListItem", { if (amISupporter) { subSubtitle += " by " + user.getLabel(); } - const date = osparc.utils.Utils.formatDateAndTime(new Date(firstMessage.created)); + const date = osparc.utils.Utils.formatDateAndTime(firstMessage.getCreated()); subSubtitle += " on " + date; this.set({ subSubtitle, diff --git a/services/static-webserver/client/source/class/osparc/support/Conversations.js b/services/static-webserver/client/source/class/osparc/support/Conversations.js index 95dc2ff46366..0d4c83417e35 100644 --- a/services/static-webserver/client/source/class/osparc/support/Conversations.js +++ b/services/static-webserver/client/source/class/osparc/support/Conversations.js @@ -78,11 +78,11 @@ qx.Class.define("osparc.support.Conversations", { __listenToNewConversations: function() { osparc.store.ConversationsSupport.getInstance().addListener("conversationCreated", e => { const conversation = e.getData(); - this.__addConversation(conversation); + this.__addConversation(conversation, 0); }); }, - __addConversation: function(conversation) { + __addConversation: function(conversation, position) { // ignore it if it was already there const conversationId = conversation.getConversationId(); const conversationItemFound = this.__getConversationItem(conversationId); @@ -94,7 +94,11 @@ qx.Class.define("osparc.support.Conversations", { conversationListItem.setConversation(conversation); conversationListItem.addListener("tap", () => this.fireDataEvent("openConversation", conversationId, this)); const conversationsLayout = this.getChildControl("conversations-layout"); - conversationsLayout.add(conversationListItem); + if (position !== undefined) { + conversationsLayout.addAt(conversationListItem, position); + } else { + conversationsLayout.add(conversationListItem); + } this.__conversationListItems.push(conversationListItem); return conversationListItem; diff --git a/services/static-webserver/client/source/class/osparc/widget/logger/LoggerView.js b/services/static-webserver/client/source/class/osparc/widget/logger/LoggerView.js index 1658268f5630..411b4bbe572d 100644 --- a/services/static-webserver/client/source/class/osparc/widget/logger/LoggerView.js +++ b/services/static-webserver/client/source/class/osparc/widget/logger/LoggerView.js @@ -165,6 +165,7 @@ qx.Class.define("osparc.widget.logger.LoggerView", { appearance: "toolbar-selectbox", maxWidth: 80 }); + control.getChildControl("arrow").syncAppearance(); // force sync to show the arrow let logLevelSet = false; Object.keys(this.self().LOG_LEVELS).forEach(logLevelKey => { const logLevel = this.self().LOG_LEVELS[logLevelKey]; diff --git a/services/static-webserver/client/source/class/osparc/workbench/ServiceCatalog.js b/services/static-webserver/client/source/class/osparc/workbench/ServiceCatalog.js index 26b5f7a9e327..8a8f332a43e6 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/ServiceCatalog.js +++ b/services/static-webserver/client/source/class/osparc/workbench/ServiceCatalog.js @@ -177,6 +177,7 @@ qx.Class.define("osparc.workbench.ServiceCatalog", { const selectBox = this.__versionsBox = new qx.ui.form.SelectBox().set({ enabled: false }); + selectBox.getChildControl("arrow").syncAppearance(); // force sync to show the arrow layout.add(selectBox); const infoBtn = this.__infoBtn = new qx.ui.form.Button(null, "@MaterialIcons/info_outline/16").set({ enabled: false