diff --git a/services/static-webserver/client/source/class/osparc/auth/Data.js b/services/static-webserver/client/source/class/osparc/auth/Data.js index 55668cbd5668..b42c4582b18c 100644 --- a/services/static-webserver/client/source/class/osparc/auth/Data.js +++ b/services/static-webserver/client/source/class/osparc/auth/Data.js @@ -163,6 +163,12 @@ qx.Class.define("osparc.auth.Data", { let friendlyRole = role.replace(/_/g, " "); friendlyRole = osparc.utils.Utils.firstsUp(friendlyRole); return friendlyRole; - } + }, + + getAvatar: function(size) { + const email = this.getEmail(); + const userName = this.getUserName(); + return osparc.utils.Avatar.emailToThumbnail(email, userName, size); + }, } }); 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 0e9ee0e108d7..0d37b8ae6e31 100644 --- a/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js +++ b/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js @@ -53,8 +53,9 @@ qx.Class.define("osparc.conversation.AddMessage", { }, events: { - "messageAdded": "qx.event.type.Data", - "messageUpdated": "qx.event.type.Data", + "addMessage": "qx.event.type.Data", + "updateMessage": "qx.event.type.Data", + "notifyUser": "qx.event.type.Data", }, members: { @@ -70,9 +71,9 @@ qx.Class.define("osparc.conversation.AddMessage", { } case "thumbnail": { control = osparc.utils.Utils.createThumbnail(32); - const meGroup = osparc.store.Groups.getInstance().getGroupMe(); + const authStore = osparc.auth.Data.getInstance(); control.set({ - source: meGroup.getThumbnail(), + source: authStore.getAvatar(32), alignX: "center", alignY: "middle", marginRight: 8, @@ -171,70 +172,19 @@ qx.Class.define("osparc.conversation.AddMessage", { }, addComment: function() { - const conversationId = this.getConversationId(); - if (conversationId) { - return this.__postMessage(); - } else { - const studyData = this.getStudyData(); - let promise = null; - if (studyData) { - // create new project conversation first - promise = osparc.store.ConversationsProject.getInstance().postConversation(studyData["uuid"]) - } else { - // support conversation - const extraContext = {}; - const currentStudy = osparc.store.Store.getInstance().getCurrentStudy() - if (currentStudy) { - extraContext["projectId"] = currentStudy.getUuid(); - } - promise = osparc.store.ConversationsSupport.getInstance().postConversation(extraContext); - } - return promise - .then(data => { - this.setConversationId(data["conversationId"]); - return this.__postMessage(); - }); - } - }, - - __postMessage: function() { const commentField = this.getChildControl("comment-field"); const content = commentField.getChildControl("text-area").getValue(); - let promise = null; if (content) { - const studyData = this.getStudyData(); - const conversationId = this.getConversationId(); - if (studyData) { - promise = osparc.store.ConversationsProject.getInstance().postMessage(studyData["uuid"], conversationId, content); - } else { - promise = osparc.store.ConversationsSupport.getInstance().postMessage(conversationId, content); - } - return promise - .then(data => { - this.fireDataEvent("messageAdded", data); - commentField.getChildControl("text-area").setValue(""); - return data; - }); + this.fireDataEvent("addMessage", content); + commentField.getChildControl("text-area").setValue(""); } - return Promise.reject(); }, __editComment: function() { const commentField = this.getChildControl("comment-field"); const content = commentField.getChildControl("text-area").getValue(); if (content) { - const studyData = this.getStudyData(); - const conversationId = this.getConversationId(); - const message = this.getMessage(); - if (studyData) { - promise = osparc.store.ConversationsProject.getInstance().editMessage(studyData["uuid"], conversationId, message["messageId"], content); - } else { - promise = osparc.store.ConversationsSupport.getInstance().editMessage(conversationId, message["messageId"], content); - } - promise.then(data => { - this.fireDataEvent("messageUpdated", data); - commentField.getChildControl("text-area").setValue(""); - }); + this.fireDataEvent("updateMessage", content); } }, @@ -273,7 +223,7 @@ qx.Class.define("osparc.conversation.AddMessage", { // This check only works if the project is directly shared with the user. // If it's shared through a group, it might be a bit confusing if (userGid in studyData["accessRights"]) { - this.__addNotify(userGid); + this.__doNotifyUser(userGid); } else { const msg = this.tr("This user has no access to the project. Do you want to share it?"); const win = new osparc.ui.window.Confirmation(msg).set({ @@ -290,7 +240,7 @@ qx.Class.define("osparc.conversation.AddMessage", { }; osparc.store.Study.getInstance().addCollaborators(studyData, newCollaborators) .then(() => { - this.__addNotify(userGid); + this.__doNotifyUser(userGid); const potentialCollaborators = osparc.store.Groups.getInstance().getPotentialCollaborators() if (userGid in potentialCollaborators && "getUserId" in potentialCollaborators[userGid]) { const uid = potentialCollaborators[userGid].getUserId(); @@ -303,47 +253,13 @@ qx.Class.define("osparc.conversation.AddMessage", { } }, - __addNotify: function(userGid) { - const studyData = this.getStudyData(); - if (!studyData) { - return; - } - - const conversationId = this.getConversationId(); - if (conversationId) { - this.__postNotify(userGid); - } else { - // create new conversation first - osparc.store.ConversationsProject.getInstance().postConversation(studyData["uuid"]) - .then(data => { - this.setConversationId(data["conversationId"]); - this.__postNotify(userGid); - }); - } - }, - - __postNotify: function(userGid) { + __doNotifyUser: function(userGid) { const studyData = this.getStudyData(); if (!studyData) { return; } - if (userGid) { - const conversationId = this.getConversationId(); - osparc.store.ConversationsProject.getInstance().notifyUser(studyData["uuid"], conversationId, userGid) - .then(data => { - this.fireDataEvent("messageAdded", data); - const potentialCollaborators = osparc.store.Groups.getInstance().getPotentialCollaborators(); - if (userGid in potentialCollaborators) { - if ("getUserId" in potentialCollaborators[userGid]) { - const uid = potentialCollaborators[userGid].getUserId(); - osparc.notification.Notifications.pushConversationNotification(uid, studyData["uuid"]); - } - const msg = "getLabel" in potentialCollaborators[userGid] ? potentialCollaborators[userGid].getLabel() + this.tr(" was notified") : this.tr("Notification sent"); - osparc.FlashMessenger.logAs(msg, "INFO"); - } - }); - } + this.fireDataEvent("notifyUser", userGid); }, /* NOTIFY USERS */ } 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 1264f9e7a8cf..fc3d5ea21c96 100644 --- a/services/static-webserver/client/source/class/osparc/conversation/MessageUI.js +++ b/services/static-webserver/client/source/class/osparc/conversation/MessageUI.js @@ -40,6 +40,9 @@ qx.Class.define("osparc.conversation.MessageUI", { statics: { isMyMessage: function(message) { + if (message["userGroupId"] === "system") { + return false; + } return message && osparc.auth.Data.getInstance().getGroupId() === message["userGroupId"]; } }, @@ -163,15 +166,19 @@ qx.Class.define("osparc.conversation.MessageUI", { const messageContent = this.getChildControl("message-content"); messageContent.setValue(message["content"]); - osparc.store.Users.getInstance().getUser(message["userGroupId"]) - .then(user => { - thumbnail.setUser(user); - userName.setValue(user ? user.getLabel() : "Unknown user"); - }) - .catch(() => { + if (message["userGroupId"] === "system") { + userName.setValue("Support"); + } else { + osparc.store.Users.getInstance().getUser(message["userGroupId"]) + .then(user => { + thumbnail.setUser(user); + userName.setValue(user ? user.getLabel() : "Unknown user"); + }) + .catch(() => { thumbnail.setSource(osparc.utils.Avatar.emailToThumbnail()); userName.setValue("Unknown user"); - }); + }); + } this.getChildControl("spacer"); @@ -207,9 +214,19 @@ qx.Class.define("osparc.conversation.MessageUI", { resizable: true, showClose: true, }); - addMessage.addListener("messageUpdated", e => { - win.close(); - this.fireDataEvent("messageUpdated", e.getData()); + 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); + } else { + promise = osparc.store.ConversationsSupport.getInstance().editMessage(conversationId, messageId, content); + } + promise.then(data => { + win.close(); + this.fireDataEvent("messageUpdated", data); + }); }); }, 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 1d168b736e87..a28bd4d4b699 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 @@ -242,7 +242,7 @@ qx.Class.define("osparc.data.model.Conversation", { renameConversation: function(newName) { osparc.store.ConversationsSupport.getInstance().renameConversation(this.getConversationId(), newName) .then(() => { - this.setNameAlias(newName); + this.setName(newName); }); }, diff --git a/services/static-webserver/client/source/class/osparc/data/model/NodeProgressSequence.js b/services/static-webserver/client/source/class/osparc/data/model/NodeProgressSequence.js index 152eb59dc37b..56ee50c4a2a5 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/NodeProgressSequence.js +++ b/services/static-webserver/client/source/class/osparc/data/model/NodeProgressSequence.js @@ -231,6 +231,7 @@ qx.Class.define("osparc.data.model.NodeProgressSequence", { __initLayout: function() { this.__mainLoadingPage = new qx.ui.container.Composite(new qx.ui.layout.VBox(8)).set({ maxWidth: 400, + decorator: "rounded", }); const sequenceLoadingPage = new osparc.widget.ProgressSequence(qx.locale.Manager.tr("LOADING ...")); diff --git a/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js b/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js index 4c8882f9958d..32b15b48a3b6 100644 --- a/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js +++ b/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js @@ -307,7 +307,7 @@ qx.Class.define("osparc.navigation.NavigationBar", { break; } case "help": - control = this.__createHelpMenuBtn().set({ + control = this.__createHelpBtn().set({ ...this.self().RIGHT_BUTTON_OPTS }); osparc.utils.Utils.setIdToWidget(control, "helpNavigationBtn"); @@ -361,22 +361,12 @@ qx.Class.define("osparc.navigation.NavigationBar", { }, this); }, - __createHelpMenuBtn: function() { - const menu = new qx.ui.menu.Menu().set({ - position: "top-right", - appearance: "menu-wider", - }); - const menuButton = new qx.ui.form.MenuButton(null, "@FontAwesome5Regular/question-circle/24", menu).set({ + __createHelpBtn: function() { + const helpButton = new qx.ui.form.Button(null, "@FontAwesome5Regular/question-circle/24").set({ backgroundColor: "transparent" }); - - osparc.utils.Utils.setIdToWidget(menu, "helpNavigationMenu"); - - // add support conversations - osparc.store.Support.addSupportConversationsToMenu(menu); - - - return menuButton; + helpButton.addListener("execute", () => osparc.support.SupportCenter.openWindow()); + return helpButton; }, __createLoginBtn: function() { diff --git a/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js b/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js index 4f6e5dd19caf..55800e221bc6 100644 --- a/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js +++ b/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js @@ -85,6 +85,14 @@ qx.Class.define("osparc.navigation.UserMenu", { control.addListener("execute", () => osparc.desktop.organizations.OrganizationsWindow.openWindow(), this); this.add(control); break; + case "help-button": + control = new qx.ui.menu.Button().set({ + label: qx.locale.Manager.tr("Help & Support"), + icon: "@FontAwesome5Solid/question-circle/16", + }); + control.addListener("execute", () => osparc.support.SupportCenter.openWindow()); + this.add(control); + break; case "market": control = new qx.ui.menu.Button(this.tr("The Shop")); control.addListener("execute", () => osparc.vipMarket.MarketWindow.openWindow()); @@ -213,7 +221,7 @@ qx.Class.define("osparc.navigation.UserMenu", { this.addSeparator(); // quick starts and manuals - osparc.store.Support.addSupportConversationsToMenu(this); + this.getChildControl("help-button"); this.addSeparator(); this.getChildControl("theme-switcher"); diff --git a/services/static-webserver/client/source/class/osparc/store/Groups.js b/services/static-webserver/client/source/class/osparc/store/Groups.js index b167643b80cb..aa30ddf596bc 100644 --- a/services/static-webserver/client/source/class/osparc/store/Groups.js +++ b/services/static-webserver/client/source/class/osparc/store/Groups.js @@ -110,7 +110,7 @@ qx.Class.define("osparc.store.Groups", { groupMe.set({ label: myAuthData.getUserName(), description, - thumbnail: osparc.utils.Avatar.emailToThumbnail(myAuthData.getEmail(), myAuthData.getUserName()), + thumbnail: myAuthData.getAvatar(32), }) return orgs; }); diff --git a/services/static-webserver/client/source/class/osparc/store/Support.js b/services/static-webserver/client/source/class/osparc/store/Support.js index 2d98078a161e..5ebaa5deb7d9 100644 --- a/services/static-webserver/client/source/class/osparc/store/Support.js +++ b/services/static-webserver/client/source/class/osparc/store/Support.js @@ -19,33 +19,6 @@ qx.Class.define("osparc.store.Support", { return osparc.store.VendorInfo.getManuals(); }, - addSupportConversationsToMenu: function(menu) { - const supportCenterButton = new qx.ui.menu.Button().set({ - label: qx.locale.Manager.tr("Help & Support"), - icon: "@FontAwesome5Solid/question-circle/16", - }); - supportCenterButton.addListener("execute", () => osparc.support.SupportCenter.openWindow()); - menu.add(supportCenterButton); - - const askAQuestionButton = new qx.ui.menu.Button().set({ - label: qx.locale.Manager.tr("Ask a Question"), - icon: "@FontAwesome5Solid/comments/16", - visibility: "excluded", - }); - askAQuestionButton.addListener("execute", () => osparc.support.SupportCenter.openWindow("conversations")); - menu.add(askAQuestionButton); - - const updateAskAQuestionButton = () => { - const isSupportEnabled = osparc.store.Groups.getInstance().isSupportEnabled(); - askAQuestionButton.set({ - visibility: isSupportEnabled ? "visible" : "excluded", - }); - } - - updateAskAQuestionButton(); - osparc.store.Groups.getInstance().addListener("changeSupportGroup", () => updateAskAQuestionButton()); - }, - __getQuickStartInfo: function() { const quickStart = osparc.product.quickStart.Utils.getQuickStart(); if (quickStart) { 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 7ad954b7e0bf..3907e5930a86 100644 --- a/services/static-webserver/client/source/class/osparc/study/Conversation.js +++ b/services/static-webserver/client/source/class/osparc/study/Conversation.js @@ -191,26 +191,65 @@ qx.Class.define("osparc.study.Conversation", { this.__loadMoreMessages.addListener("execute", () => this.__reloadMessages(false)); this._add(this.__loadMoreMessages); - const addMessages = new osparc.conversation.AddMessage().set({ + const addMessage = new osparc.conversation.AddMessage().set({ studyData: this.__studyData, conversationId: this.getConversationId(), enabled: osparc.data.model.Study.canIWrite(this.__studyData["accessRights"]), paddingLeft: 10, }); - addMessages.addListener("messageAdded", e => { - const data = e.getData(); - if (data["conversationId"] && this.getConversation() === null) { - osparc.store.ConversationsProject.getInstance().getConversation(this.__studyData["uuid"], data["conversationId"]) - .then(conversationData => { - const conversation = new osparc.data.model.Conversation(conversationData); - this.setConversation(conversation); + addMessage.addListener("addMessage", e => { + const content = e.getData(); + const conversation = this.getConversation(); + if (conversation) { + this.__postMessage(content); + } else { + // create new conversation first + osparc.store.ConversationsProject.getInstance().postConversation(this.__studyData["uuid"]) + .then(data => { + const newConversation = new osparc.data.model.Conversation(data); + this.setConversation(newConversation); + this.__postMessage(content); }); + } + }); + addMessage.addListener("notifyUser", e => { + const userGid = e.getData(); + const conversation = this.getConversation(); + if (conversation) { + this.__postNotify(userGid); } else { - this.getConversation().addMessage(data); - this.addMessage(data); + // create new conversation first + osparc.store.ConversationsProject.getInstance().postConversation(this.__studyData["uuid"]) + .then(data => { + const newConversation = new osparc.data.model.Conversation(data); + this.setConversation(newConversation); + this.__postNotify(userGid); + }); } }); - this._add(addMessages); + this._add(addMessage); + }, + + __postMessage: function(content) { + const conversationId = this.getConversation().getConversationId(); + osparc.store.ConversationsProject.getInstance().postMessage(this.__studyData["uuid"], conversationId, content); + }, + + __postNotify: function(userGid) { + const conversationId = this.getConversation().getConversationId(); + osparc.store.ConversationsProject.getInstance().notifyUser(this.__studyData["uuid"], conversationId, userGid) + .then(data => { + this.fireDataEvent("messageAdded", data); + const potentialCollaborators = osparc.store.Groups.getInstance().getPotentialCollaborators(); + if (userGid in potentialCollaborators) { + if ("getUserId" in potentialCollaborators[userGid]) { + const uid = potentialCollaborators[userGid].getUserId(); + osparc.notification.Notifications.pushConversationNotification(uid, this.__studyData["uuid"]); + } + const msg = "getLabel" in potentialCollaborators[userGid] ? potentialCollaborators[userGid].getLabel() + this.tr(" was notified") : this.tr("Notification sent"); + osparc.FlashMessenger.logAs(msg, "INFO"); + } + }); }, __getNextRequest: function() { @@ -265,6 +304,9 @@ qx.Class.define("osparc.study.Conversation", { }, __updateMessagesNumber: function() { + if (!this.__messagesTitle) { + return; + } const nMessages = this.__messages.filter(msg => msg["type"] === "MESSAGE").length; if (nMessages === 0) { this.__messagesTitle.setValue(this.tr("No Messages yet")); 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 3ff136a1336a..93031b0063f3 100644 --- a/services/static-webserver/client/source/class/osparc/support/Conversation.js +++ b/services/static-webserver/client/source/class/osparc/support/Conversation.js @@ -54,6 +54,15 @@ qx.Class.define("osparc.support.Conversation", { }, }, + statics: { + SYSTEM_MESSAGE_TYPE: { + ASK_A_QUESTION: "askAQuestion", + BOOK_A_CALL: "bookACall", + REPORT_OEC: "reportOEC", + FOLLOW_UP: "followUp", + }, + }, + members: { __messages: null, @@ -80,7 +89,7 @@ qx.Class.define("osparc.support.Conversation", { break; case "load-more-button": control = new osparc.ui.form.FetchButton(this.tr("Load more messages...")); - control.addListener("execute", () => this.__reloadMessages(false)); + control.addListener("execute", () => this.__reloadMessages()); this._addAt(control, 2); break; case "support-suggestion": @@ -127,22 +136,58 @@ qx.Class.define("osparc.support.Conversation", { this.getChildControl("spacer-top"); this.getChildControl("messages-container"); const addMessages = this.getChildControl("add-message"); - addMessages.addListener("messageAdded", e => { - const data = e.getData(); - if (data["conversationId"] && this.getConversation() === null) { - osparc.store.ConversationsSupport.getInstance().getConversation(data["conversationId"]) - .then(conversation => { - this.setConversation(conversation); - }); + addMessages.addListener("addMessage", e => { + const content = e.getData(); + const conversation = this.getConversation(); + if (conversation) { + this.__postMessage(content); } else { - this.getConversation().addMessage(data); - this.addMessage(data); + // create new conversation first + const extraContext = {}; + const currentStudy = osparc.store.Store.getInstance().getCurrentStudy() + if (currentStudy) { + extraContext["projectId"] = currentStudy.getUuid(); + } + osparc.store.ConversationsSupport.getInstance().postConversation(extraContext) + .then(data => { + let prePostMessagePromise = new Promise((resolve) => resolve()); + let isBookACall = false; + // make these checks first, setConversation will reload messages + if ( + this.__messages.length === 1 && + this.__messages[0]["systemMessageType"] === osparc.support.Conversation.SYSTEM_MESSAGE_TYPE.BOOK_A_CALL + ) { + isBookACall = true; + } + const newConversation = new osparc.data.model.Conversation(data); + this.setConversation(newConversation); + if (isBookACall) { + // add a first message + prePostMessagePromise = this.__postMessage("Book a Call"); + // rename the conversation + newConversation.renameConversation("Book a Call"); + } + prePostMessagePromise + .then(() => { + // add the actual message + return this.__postMessage(content); + }) + .then(() => { + setTimeout(() => this.addSystemMessage("followUp"), 1000); + }); + }); } }); }, + __postMessage: function(content) { + const conversationId = this.getConversation().getConversationId(); + return osparc.store.ConversationsSupport.getInstance().postMessage(conversationId, content); + }, + __applyConversation: function(conversation) { - this.__reloadMessages(true); + this.clearAllMessages(); + this.__reloadMessages(); if (conversation) { conversation.addListener("messageAdded", e => { @@ -222,24 +267,15 @@ qx.Class.define("osparc.support.Conversation", { }); }, - __reloadMessages: function(removeMessages = true) { - const messagesContainer = this.getChildControl("messages-container"); + __reloadMessages: function() { const loadMoreMessages = this.getChildControl("load-more-button"); if (this.getConversation() === null) { - messagesContainer.hide(); loadMoreMessages.hide(); return; } - messagesContainer.show(); loadMoreMessages.show(); loadMoreMessages.setFetching(true); - - if (removeMessages) { - this.__messages = []; - messagesContainer.removeAll(); - } - this.getConversation().getNextMessages() .then(resp => { const messages = resp["data"]; @@ -251,6 +287,38 @@ qx.Class.define("osparc.support.Conversation", { .finally(() => loadMoreMessages.setFetching(false)); }, + addSystemMessage: function(type) { + type = type || "askAQuestion"; + + const now = new Date(); + const systemMessage = { + "conversationId": null, + "created": now.toISOString(), + "messageId": `system-${now.getTime()}`, + "modified": now.toISOString(), + "type": "MESSAGE", + "userGroupId": "system", + }; + let msg = null; + const greet = "Hi " + osparc.auth.Data.getInstance().getUserName() + ",\n"; + switch (type) { + case osparc.support.Conversation.SYSTEM_MESSAGE_TYPE.ASK_A_QUESTION: + msg = greet + "Have a question or feedback?\nWe are happy to assist!"; + break; + case osparc.support.Conversation.SYSTEM_MESSAGE_TYPE.BOOK_A_CALL: + msg = greet + "Let us know what your availability is and we will get back to you shortly to schedule a meeting."; + break; + case osparc.support.Conversation.SYSTEM_MESSAGE_TYPE.FOLLOW_UP: + msg = "A support ticket has been created.\nOur team will review your request and contact you soon."; + break; + } + if (msg) { + systemMessage["content"] = msg; + systemMessage["systemMessageType"] = type; + } + this.addMessage(systemMessage); + }, + addMessage: function(message) { // ignore it if it was already there const messageIndex = this.__messages.findIndex(msg => msg["messageId"] === message["messageId"]); @@ -294,6 +362,11 @@ qx.Class.define("osparc.support.Conversation", { }, 50); }, + clearAllMessages: function() { + this.__messages = []; + this.getChildControl("messages-container").removeAll(); + }, + deleteMessage: function(message) { // remove it from the messages array const messageIndex = this.__messages.findIndex(msg => msg["messageId"] === message["messageId"]); diff --git a/services/static-webserver/client/source/class/osparc/support/ConversationPage.js b/services/static-webserver/client/source/class/osparc/support/ConversationPage.js index c85395200826..c6382edfa733 100644 --- a/services/static-webserver/client/source/class/osparc/support/ConversationPage.js +++ b/services/static-webserver/client/source/class/osparc/support/ConversationPage.js @@ -88,49 +88,64 @@ qx.Class.define("osparc.support.ConversationPage", { control = new qx.ui.container.Composite(new qx.ui.layout.VBox(2)); this.getChildControl("conversation-header-center-layout").addAt(control, 1); break; - case "open-project-button": - control = new qx.ui.form.Button().set({ - maxWidth: 26, + case "buttons-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5).set({ + alignY: "middle", + })).set({ maxHeight: 24, + }); + this.getChildControl("conversation-header-layout").addAt(control, 2); + break; + case "rename-conversation-button": { + control = new qx.ui.form.Button().set({ + icon: "@FontAwesome5Solid/i-cursor/12", + toolTipText: this.tr("Rename"), alignX: "center", alignY: "middle", + }); + control.addListener("execute", () => this.__renameConversation()); + this.getChildControl("buttons-layout").addAt(control, 0); + break; + } + case "open-project-button": + control = new qx.ui.form.Button().set({ icon: "@FontAwesome5Solid/external-link-alt/12", + alignX: "center", + alignY: "middle", }); control.addListener("execute", () => this.__openProjectDetails()); - this.getChildControl("conversation-header-layout").addAt(control, 2); + this.getChildControl("buttons-layout").addAt(control, 1); break; - case "set-appointment-button": { + case "copy-ticket-id-button": { control = new qx.ui.form.Button().set({ - maxWidth: 26, - maxHeight: 24, - padding: [0, 6], + icon: "@FontAwesome5Solid/copy/12", + toolTipText: this.tr("Copy Ticket ID"), alignX: "center", alignY: "middle", - icon: "@FontAwesome5Solid/clock/12", }); - control.addListener("execute", () => this.__openAppointmentDetails()); - this.getChildControl("conversation-header-layout").addAt(control, 3); + control.addListener("execute", () => this.__copyTicketId()); + this.getChildControl("buttons-layout").addAt(control, 2); break; } - case "conversation-options": { - control = new qx.ui.form.MenuButton().set({ - maxWidth: 24, - maxHeight: 24, + case "open-ticket-link-button": { + control = new qx.ui.form.Button().set({ + icon: "@FontAwesome5Solid/link/12", + toolTipText: this.tr("Open Ticket"), alignX: "center", alignY: "middle", - icon: "@FontAwesome5Solid/ellipsis-v/12", }); - const menu = new qx.ui.menu.Menu().set({ - position: "bottom-right", - }); - control.setMenu(menu); - const renameButton = new qx.ui.menu.Button().set({ - label: this.tr("Rename"), - icon: "@FontAwesome5Solid/i-cursor/10" + this.getChildControl("buttons-layout").addAt(control, 3); + break; + } + case "set-appointment-button": { + control = new qx.ui.form.Button().set({ + icon: "@FontAwesome5Solid/clock/12", + toolTipText: this.tr("Set Appointment"), + alignX: "center", + alignY: "middle", }); - renameButton.addListener("execute", () => this.__renameConversation()); - menu.add(renameButton); - this.getChildControl("conversation-header-layout").addAt(control, 4); + control.addListener("execute", () => this.__openAppointmentDetails()); + this.getChildControl("buttons-layout").addAt(control, 4); break; } case "conversation-content": @@ -145,17 +160,38 @@ qx.Class.define("osparc.support.ConversationPage", { return control || this.base(arguments, id); }, + proposeConversation: function(type) { + type = type || osparc.support.Conversation.SYSTEM_MESSAGE_TYPE.ASK_A_QUESTION; + this.setConversation(null); + + const title = this.getChildControl("conversation-title"); + const conversationContent = this.getChildControl("conversation-content"); + conversationContent.clearAllMessages(); + switch (type) { + case osparc.support.Conversation.SYSTEM_MESSAGE_TYPE.ASK_A_QUESTION: + title.setValue(this.tr("Ask a Question")); + break; + case osparc.support.Conversation.SYSTEM_MESSAGE_TYPE.BOOK_A_CALL: + title.setValue(this.tr("Book a Call")); + break; + case osparc.support.Conversation.SYSTEM_MESSAGE_TYPE.REPORT_OEC: + title.setValue(this.tr("Report an Error")); + break; + } + conversationContent.addSystemMessage(type); + }, + __applyConversation: function(conversation) { const title = this.getChildControl("conversation-title"); if (conversation) { conversation.bind("nameAlias", title, "value"); - } else { - title.setValue(this.tr("Ask a Question")); } const extraContextLayout = this.getChildControl("conversation-extra-layout"); - const amISupporter = osparc.store.Groups.getInstance().amIASupportUser(); + extraContextLayout.removeAll(); if (conversation) { + const amISupporter = osparc.store.Groups.getInstance().amIASupportUser(); + const createExtraContextLabel = text => { return new qx.ui.basic.Label(text).set({ font: "text-12", @@ -169,13 +205,14 @@ qx.Class.define("osparc.support.ConversationPage", { extraContextLayout.removeAll(); const extraContext = conversation.getExtraContext(); if (extraContext && Object.keys(extraContext).length) { - const ticketIdLabel = createExtraContextLabel(`Ticket ID: ${conversation.getConversationId()}`); + const ticketIdLabel = createExtraContextLabel(`Ticket ID: ${osparc.utils.Utils.uuidToShort(conversation.getConversationId())}`); extraContextLayout.add(ticketIdLabel); const contextProjectId = conversation.getContextProjectId(); if (contextProjectId && amISupporter) { - const projectIdLabel = createExtraContextLabel(`Project ID: ${contextProjectId}`); + const projectIdLabel = createExtraContextLabel(`Project ID: ${osparc.utils.Utils.uuidToShort(contextProjectId)}`); extraContextLayout.add(projectIdLabel); } + /* const appointment = conversation.getAppointment(); if (appointment) { const appointmentLabel = createExtraContextLabel(); @@ -194,32 +231,19 @@ qx.Class.define("osparc.support.ConversationPage", { appointmentLabel.setValue(appointmentText); extraContextLayout.add(appointmentLabel); } + */ } }; updateExtraContext(); conversation.addListener("changeExtraContext", () => updateExtraContext(), this); } - const openProjectButton = this.getChildControl("open-project-button"); - if (conversation && conversation.getContextProjectId()) { - openProjectButton.show(); - } else { - openProjectButton.exclude(); - } - - const setAppointmentButton = this.getChildControl("set-appointment-button"); - if (conversation && conversation.getAppointment() && amISupporter) { - setAppointmentButton.show(); - } else { - setAppointmentButton.exclude(); - } + this.getChildControl("buttons-layout").setVisibility(conversation ? "visible" : "excluded"); - const options = this.getChildControl("conversation-options"); - if (conversation && conversation.amIOwner()) { - options.show(); - } else { - options.exclude(); - } + this.getChildControl("rename-conversation-button"); + const openProjectButton = this.getChildControl("open-project-button"); + openProjectButton.setVisibility(conversation && conversation.getContextProjectId() ? "visible" : "excluded"); + this.getChildControl("copy-ticket-id-button"); }, __openProjectDetails: function() { @@ -237,6 +261,13 @@ qx.Class.define("osparc.support.ConversationPage", { } }, + __copyTicketId: function() { + if (this.getConversation()) { + const conversationId = this.getConversation().getConversationId(); + osparc.utils.Utils.copyTextToClipboard(conversationId); + } + }, + __openAppointmentDetails: function() { const win = new osparc.widget.DateTimeChooser(); win.addListener("dateChanged", e => { 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 e359c3ee9e7f..95dc2ff46366 100644 --- a/services/static-webserver/client/source/class/osparc/support/Conversations.js +++ b/services/static-webserver/client/source/class/osparc/support/Conversations.js @@ -23,10 +23,6 @@ qx.Class.define("osparc.support.Conversations", { this.base(arguments); this._setLayout(new qx.ui.layout.VBox(10)); - - this.__noConversationsLabel = new qx.ui.basic.Label("No conversations yet — your messages will appear here.").set({ - padding: 5, - }); this.__conversationListItems = []; this.__fetchConversations(); @@ -39,7 +35,6 @@ qx.Class.define("osparc.support.Conversations", { }, members: { - __noConversationsLabel: null, __conversationListItems: null, _createChildControlImpl: function(id) { @@ -72,9 +67,6 @@ qx.Class.define("osparc.support.Conversations", { .then(conversations => { if (conversations.length) { conversations.forEach(conversation => this.__addConversation(conversation)); - } else { - // No conversations found - this.getChildControl("conversations-layout").add(this.__noConversationsLabel); } }) .finally(() => { @@ -91,12 +83,6 @@ qx.Class.define("osparc.support.Conversations", { }, __addConversation: function(conversation) { - const conversationsLayout = this.getChildControl("conversations-layout"); - // remove the noConversationsLabel - if (conversationsLayout && conversationsLayout.getChildren().indexOf(this.__noConversationsLabel) > -1) { - conversationsLayout.remove(this.__noConversationsLabel); - } - // ignore it if it was already there const conversationId = conversation.getConversationId(); const conversationItemFound = this.__getConversationItem(conversationId); @@ -107,6 +93,7 @@ qx.Class.define("osparc.support.Conversations", { const conversationListItem = new osparc.support.ConversationListItem(); conversationListItem.setConversation(conversation); conversationListItem.addListener("tap", () => this.fireDataEvent("openConversation", conversationId, this)); + const conversationsLayout = this.getChildControl("conversations-layout"); conversationsLayout.add(conversationListItem); this.__conversationListItems.push(conversationListItem); diff --git a/services/static-webserver/client/source/class/osparc/support/ConversationsPage.js b/services/static-webserver/client/source/class/osparc/support/ConversationsPage.js index ae56985526a8..e838b636f83e 100644 --- a/services/static-webserver/client/source/class/osparc/support/ConversationsPage.js +++ b/services/static-webserver/client/source/class/osparc/support/ConversationsPage.js @@ -24,7 +24,6 @@ qx.Class.define("osparc.support.ConversationsPage", { this._setLayout(new qx.ui.layout.VBox(15)); - this.getChildControl("conversations-intro-text"); this.getChildControl("conversations-list"); this.getChildControl("ask-a-question-button"); this.getChildControl("book-a-call-button"); @@ -32,27 +31,13 @@ qx.Class.define("osparc.support.ConversationsPage", { events: { "openConversation": "qx.event.type.Data", - "createConversationBookCall": "qx.event.type.Event", + "createConversation": "qx.event.type.Data", }, members: { _createChildControlImpl: function(id) { let control; switch (id) { - case "conversations-intro-text": { - control = new qx.ui.basic.Label().set({ - rich: true, - font: "text-14", - }); - const isSupportUser = osparc.store.Groups.getInstance().amIASupportUser(); - control.set({ - value: isSupportUser ? - this.tr("Thanks for being here! Let's help every user feel supported.") : - this.tr("Need help or want to share feedback? You're in the right place."), - }); - this._add(control); - break; - } case "conversations-list": { control = new osparc.support.Conversations(); control.addListener("openConversation", e => { @@ -78,7 +63,7 @@ qx.Class.define("osparc.support.ConversationsPage", { allowGrowX: false, center: true, }); - control.addListener("execute", () => this.fireDataEvent("openConversation", null), this); + control.addListener("execute", () => this.fireDataEvent("createConversation", "askAQuestion"), this); this.getChildControl("buttons-layout").add(control); break; case "book-a-call-button": @@ -87,7 +72,7 @@ qx.Class.define("osparc.support.ConversationsPage", { allowGrowX: false, center: true, }); - control.addListener("execute", () => this.fireEvent("createConversationBookCall"), this); + control.addListener("execute", () => this.fireDataEvent("createConversation", "bookACall"), this); this.getChildControl("buttons-layout").add(control); break; } diff --git a/services/static-webserver/client/source/class/osparc/support/HomePage.js b/services/static-webserver/client/source/class/osparc/support/HomePage.js index a72c06145afb..5a6dcc6440aa 100644 --- a/services/static-webserver/client/source/class/osparc/support/HomePage.js +++ b/services/static-webserver/client/source/class/osparc/support/HomePage.js @@ -28,15 +28,15 @@ qx.Class.define("osparc.support.HomePage", { padding: 5, }); - this.getChildControl("conversations-intro-text"); if (osparc.store.Groups.getInstance().isSupportEnabled()) { - this.getChildControl("ask-a-question"); + this.getChildControl("ask-a-question-button"); + this.getChildControl("book-a-call-button"); } this.__populateButtons(); }, events: { - "openConversation": "qx.event.type.Event", + "createConversation": "qx.event.type.Data", }, statics: { @@ -66,32 +66,34 @@ qx.Class.define("osparc.support.HomePage", { _createChildControlImpl: function(id) { let control; switch (id) { - case "conversations-intro-text": { - control = new qx.ui.basic.Label().set({ - rich: true, - font: "text-16", - }); - const isSupportUser = osparc.store.Groups.getInstance().amIASupportUser(); - const userName = osparc.auth.Data.getInstance().getUserName(); - control.set({ - value: isSupportUser ? - userName + ", " + this.tr("thanks for being here!
Let's help every user feel supported.") : - this.tr("Hi there 👋
How can we help?"), + case "conversation-buttons-layout": { + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(10)).set({ + // align it with the rest of the buttons in section boxes + marginLeft: 11, + marginRight: 11, }); this._add(control); break; } - case "ask-a-question": + case "ask-a-question-button": control = new qx.ui.form.Button(this.tr("Ask a Question"), "@FontAwesome5Solid/comments/16").set({ gap: 8, appearance: "strong-button", center: true, - // align it with the rest of the buttons in section boxes - marginLeft: 11, - marginRight: 11, + width: 183, }); - control.addListener("execute", () => this.fireEvent("openConversation")); - this._add(control); + control.addListener("execute", () => this.fireDataEvent("createConversation", "askAQuestion")); + this.getChildControl("conversation-buttons-layout").add(control, { flex: 1 }); + break; + case "book-a-call-button": + control = new qx.ui.form.Button(this.tr("Book a Call"), "@FontAwesome5Solid/phone/16").set({ + gap: 8, + appearance: "strong-button", + center: true, + width: 183, + }); + control.addListener("execute", () => this.fireDataEvent("createConversation", "bookACall")); + this.getChildControl("conversation-buttons-layout").add(control, { flex: 1 }); break; case "learning-box": control = new osparc.widget.SectionBox(this.tr("Learning"), "@FontAwesome5Solid/graduation-cap/14"); diff --git a/services/static-webserver/client/source/class/osparc/support/SupportCenter.js b/services/static-webserver/client/source/class/osparc/support/SupportCenter.js index 3ea13e6d2524..9203ce7f7b7d 100644 --- a/services/static-webserver/client/source/class/osparc/support/SupportCenter.js +++ b/services/static-webserver/client/source/class/osparc/support/SupportCenter.js @@ -129,7 +129,7 @@ qx.Class.define("osparc.support.SupportCenter", { break; case "home-page": control = new osparc.support.HomePage(); - control.addListener("openConversation", () => this.openConversation(), this); + control.addListener("createConversation", e => this.createConversation(e.getData()), this); this.getChildControl("main-stack").add(control); break; case "conversations-stack": @@ -138,11 +138,8 @@ qx.Class.define("osparc.support.SupportCenter", { break; case "conversations-page": control = new osparc.support.ConversationsPage(); - control.addListener("openConversation", e => { - const conversationId = e.getData(); - this.openConversation(conversationId); - }, this); - control.addListener("createConversationBookCall", () => this.createConversationBookCall(), this); + control.addListener("openConversation", e => this.openConversation(e.getData()), this); + control.addListener("createConversation", e => this.createConversation(e.getData()), this); this.getChildControl("conversations-stack").add(control); break; case "conversation-page": @@ -196,35 +193,13 @@ qx.Class.define("osparc.support.SupportCenter", { conversationPage.setConversation(conversation); this.__showConversation(); }); - } else { - conversationPage.setConversation(null); - this.__showConversation(); } }, - createConversationBookCall: function() { + createConversation: function(type) { const conversationPage = this.getChildControl("conversation-page"); - conversationPage.setConversation(null); + conversationPage.proposeConversation(type); this.__showConversation(); - conversationPage.postMessage(osparc.support.SupportCenter.REQUEST_CALL_MESSAGE) - .then(data => { - const conversationId = data["conversationId"]; - osparc.store.ConversationsSupport.getInstance().getConversation(conversationId) - .then(conversation => { - // update conversation name and patch extra_context - conversation.renameConversation("Book a call"); - conversation.patchExtraContext({ - ...conversation.getExtraContext(), - "appointment": "requested" - }); - // This should be an automatic response in the chat - const msg = this.tr("Your request has been sent.
Our support team will get back to you."); - osparc.FlashMessenger.logAs(msg, "INFO"); - }); - }) - .catch(err => { - console.error("Error sending request call message", err); - }); }, } }); diff --git a/services/static-webserver/client/source/class/osparc/theme/Decoration.js b/services/static-webserver/client/source/class/osparc/theme/Decoration.js index 882519058902..0e295ac3c168 100644 --- a/services/static-webserver/client/source/class/osparc/theme/Decoration.js +++ b/services/static-webserver/client/source/class/osparc/theme/Decoration.js @@ -26,6 +26,14 @@ qx.Theme.define("osparc.theme.Decoration", { } }, + "chat-bubble": { + style: { + radius: 4, + width: 1, + color: "text-disabled" + } + }, + "separator-strong": { style: { widthTop: 1, diff --git a/services/static-webserver/client/source/class/osparc/ui/message/FlashMessageOEC.js b/services/static-webserver/client/source/class/osparc/ui/message/FlashMessageOEC.js index 8fd9344fd097..d6069ac75979 100644 --- a/services/static-webserver/client/source/class/osparc/ui/message/FlashMessageOEC.js +++ b/services/static-webserver/client/source/class/osparc/ui/message/FlashMessageOEC.js @@ -28,7 +28,7 @@ qx.Class.define("osparc.ui.message.FlashMessageOEC", { construct: function(message, duration, supportId) { this.base(arguments, message, "ERROR", duration ? duration*2 : null); - if (osparc.store.Groups.getInstance().isSupportEnabled()) { + if (osparc.store.Groups.getInstance().isSupportEnabled() && false) { this.getChildControl("contact-support"); } else { const oecAtom = this.getChildControl("oec-atom"); @@ -114,7 +114,7 @@ qx.Class.define("osparc.ui.message.FlashMessageOEC", { __openSupportChat: function() { const supportCenter = osparc.support.SupportCenter.openWindow(); - supportCenter.openConversation(null); + supportCenter.createConversation("reportOEC"); const textToAddMessageField = msg => { if (supportCenter.getChildControl("conversation-page")) { diff --git a/services/static-webserver/client/source/class/osparc/user/UserProfile.js b/services/static-webserver/client/source/class/osparc/user/UserProfile.js index 3767cd4e1d50..0aefd38b835d 100644 --- a/services/static-webserver/client/source/class/osparc/user/UserProfile.js +++ b/services/static-webserver/client/source/class/osparc/user/UserProfile.js @@ -42,6 +42,12 @@ qx.Class.define("osparc.user.UserProfile", { COUNTRY: 4, POSTAL_CODE: 5, }, + + createLabel: function() { + return new qx.ui.basic.Label().set({ + selectable: true, + }); + }, }, properties: { @@ -81,7 +87,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().TOP_GRID.USERNAME, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("top-info").add(control, { row: this.self().TOP_GRID.USERNAME, column: 1 @@ -93,7 +99,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().TOP_GRID.FULLNAME, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("top-info").add(control, { row: this.self().TOP_GRID.FULLNAME, column: 1 @@ -105,7 +111,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().TOP_GRID.EMAIL, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("top-info").add(control, { row: this.self().TOP_GRID.EMAIL, column: 1 @@ -117,7 +123,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().TOP_GRID.PHONE, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("top-info").add(control, { row: this.self().TOP_GRID.PHONE, column: 1 @@ -129,7 +135,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().TOP_GRID.USER_ID, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("top-info").add(control, { row: this.self().TOP_GRID.USER_ID, column: 1 @@ -141,7 +147,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().TOP_GRID.GROUP_ID, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("top-info").add(control, { row: this.self().TOP_GRID.GROUP_ID, column: 1 @@ -153,7 +159,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().MIDDLE_GRID.INSTITUTION, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("middle-info").add(control, { row: this.self().MIDDLE_GRID.INSTITUTION, column: 1 @@ -164,7 +170,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().MIDDLE_GRID.ADDRESS, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("middle-info").add(control, { row: this.self().MIDDLE_GRID.ADDRESS, column: 1 @@ -175,7 +181,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().MIDDLE_GRID.CITY, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("middle-info").add(control, { row: this.self().MIDDLE_GRID.CITY, column: 1 @@ -186,7 +192,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().MIDDLE_GRID.STATE, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("middle-info").add(control, { row: this.self().MIDDLE_GRID.STATE, column: 1 @@ -197,7 +203,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().MIDDLE_GRID.COUNTRY, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("middle-info").add(control, { row: this.self().MIDDLE_GRID.COUNTRY, column: 1 @@ -208,7 +214,7 @@ qx.Class.define("osparc.user.UserProfile", { row: this.self().MIDDLE_GRID.POSTAL_CODE, column: 0 }); - control = new qx.ui.basic.Label(); + control = this.self().createLabel(); this.getChildControl("middle-info").add(control, { row: this.self().MIDDLE_GRID.POSTAL_CODE, column: 1 diff --git a/services/static-webserver/client/source/class/osparc/utils/Utils.js b/services/static-webserver/client/source/class/osparc/utils/Utils.js index be585a044cc7..b3c9730d9e49 100644 --- a/services/static-webserver/client/source/class/osparc/utils/Utils.js +++ b/services/static-webserver/client/source/class/osparc/utils/Utils.js @@ -778,6 +778,10 @@ qx.Class.define("osparc.utils.Utils", { (c ^ window.crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); }, + uuidToShort: function() { + return this.uuidV4().split("-")[0]; + }, + isInZ43: function() { return window.location.hostname.includes("speag"); },