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 104f0f515049..8109cfad7b3f 100644 --- a/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js +++ b/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js @@ -91,6 +91,13 @@ qx.Class.define("osparc.conversation.AddMessage", { } case "comment-field": control = new osparc.editor.MarkdownEditor(); + control.addListener("keydown", e => { + if (e.isCtrlPressed() && e.getKeyIdentifier() === "Enter") { + this.__addComment(); + e.stopPropagation(); + e.preventDefault(); + } + }, this); control.getChildControl("buttons").exclude(); const layout = this.getChildControl("add-comment-layout"); layout.add(control, { diff --git a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js index 70b9abda1636..0f8667f59710 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js @@ -24,14 +24,6 @@ qx.Class.define("osparc.dashboard.CardBase", { construct: function() { this.base(arguments); - if (osparc.utils.DisabledPlugins.isRTCEnabled()) { - // "IN_USE" is not a blocker anymore - const inUseIdx = qx.util.PropertyUtil.getProperties(osparc.dashboard.CardBase).blocked.check.indexOf("IN_USE"); - if (inUseIdx > -1) { - qx.util.PropertyUtil.getProperties(osparc.dashboard.CardBase).blocked.check.splice(inUseIdx, 1); - } - } - [ "pointerover", "focus" diff --git a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js index 8fd19482aea6..9e2094aebeed 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -410,9 +410,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { resourcesList.forEach(study => { const state = study["state"]; - const projectLocked = osparc.study.Utils.state.isProjectLocked(state); const projectStatus = osparc.study.Utils.state.getProjectStatus(state); - if (projectLocked && projectStatus === "CLOSING") { + if (projectStatus === "CLOSING") { // websocket might have already notified that the state was closed. // But the /projects calls response got after the ws message. Ask again to make sure const delay = 2000; @@ -887,6 +886,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { requestParams.accessRights = "public"; break; case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: + delete requestParams.orderBy; // functions are not ordered yet + requestParams.includeExtras = "true"; break; case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: { requestParams.type = "user"; diff --git a/services/static-webserver/client/source/class/osparc/desktop/MainPage.js b/services/static-webserver/client/source/class/osparc/desktop/MainPage.js index dde0e01db564..706d99032d2f 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/MainPage.js +++ b/services/static-webserver/client/source/class/osparc/desktop/MainPage.js @@ -257,7 +257,7 @@ qx.Class.define("osparc.desktop.MainPage", { // these operations need to be done after template creation osparc.store.Study.getInstance().addCollaborators(templateData, templateAccessRights); if (templateType) { - osparc.store.Study.getInstance().patchTemplateType(templateData["uuid"], templateType) + osparc.store.Study.getInstance().patchTemplateType(templateData, templateType) .then(() => { if (tutorialBrowser && templateType === osparc.data.model.StudyUI.TUTORIAL_TYPE) { tutorialBrowser.reloadResources(false); @@ -309,7 +309,7 @@ qx.Class.define("osparc.desktop.MainPage", { const currentStudy = store.getCurrentStudy(); while (currentStudy.isLocked()) { await osparc.utils.Utils.sleep(1000); - osparc.store.Study.getInstance().getStudyState(studyId); + osparc.store.Study.getInstance().fetchStudyState(studyId); } this.__loadingPage.setMessages([]); this.__openSnapshot(studyId, snapshotId); @@ -355,7 +355,7 @@ qx.Class.define("osparc.desktop.MainPage", { const currentStudy = store.getCurrentStudy(); while (currentStudy.isLocked()) { await osparc.utils.Utils.sleep(1000); - osparc.store.Study.getInstance().getStudyState(studyId); + osparc.store.Study.getInstance().fetchStudyState(studyId); } this.__loadingPage.setMessages([]); this.__openIteration(iterationUuid); diff --git a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js index a57e7231aae0..29c45220dba4 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js @@ -442,7 +442,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", { this.__addTopBarSpacer(topBar); - const commentsButton = new qx.ui.form.Button().set({ + const conversationButton = new qx.ui.form.Button().set({ appearance: "form-button-outlined", toolTipText: this.tr("Conversations"), icon: "@FontAwesome5Solid/comments/16", @@ -450,8 +450,9 @@ qx.Class.define("osparc.desktop.WorkbenchView", { marginTop: 7, ...osparc.navigation.NavigationBar.BUTTON_OPTIONS }); - commentsButton.addListener("execute", () => osparc.study.Conversations.popUpInWindow(study.serialize())); - topBar.add(commentsButton); + osparc.study.Conversations.makeButtonBlink(conversationButton); + conversationButton.addListener("execute", () => osparc.study.Conversations.popUpInWindow(study.serialize())); + topBar.add(conversationButton); const startAppButtonTB = this.__startAppButtonTB = new qx.ui.form.Button().set({ appearance: "form-button-outlined", diff --git a/services/static-webserver/client/source/class/osparc/info/StudyLarge.js b/services/static-webserver/client/source/class/osparc/info/StudyLarge.js index c97be0a9196c..cfc4b9ec604e 100644 --- a/services/static-webserver/client/source/class/osparc/info/StudyLarge.js +++ b/services/static-webserver/client/source/class/osparc/info/StudyLarge.js @@ -95,7 +95,7 @@ qx.Class.define("osparc.info.StudyLarge", { if (selected) { saveBtn.setFetching(true); const templateType = selected.getModel(); - osparc.store.Study.getInstance().patchTemplateType(this.getStudy().getUuid(), templateType) + osparc.store.Study.getInstance().patchTemplateType(this.getStudy().serialize(), templateType) .then(() => osparc.FlashMessenger.logAs(this.tr("Template type updated, please reload"), "INFO")) .catch(err => osparc.FlashMessenger.logError(err)) .finally(() => saveBtn.setFetching(false)); diff --git a/services/static-webserver/client/source/class/osparc/jobs/JobsButton.js b/services/static-webserver/client/source/class/osparc/jobs/JobsButton.js index 7799830bade6..39ca0b4ff16b 100644 --- a/services/static-webserver/client/source/class/osparc/jobs/JobsButton.js +++ b/services/static-webserver/client/source/class/osparc/jobs/JobsButton.js @@ -66,8 +66,8 @@ qx.Class.define("osparc.jobs.JobsButton", { textColor: osparc.navigation.NavigationBar.BG_COLOR, }); this._add(control, { - bottom: 10, - right: 2 + bottom: -4, + right: -4, }); break; case "is-active-icon": @@ -75,8 +75,8 @@ qx.Class.define("osparc.jobs.JobsButton", { textColor: "strong-main", }); this._add(control, { - bottom: 12, - right: 4 + bottom: -2, + right: -2, }); break; } @@ -109,6 +109,7 @@ qx.Class.define("osparc.jobs.JobsButton", { }, __updateJobsButton: function(isActive) { + isActive = true; this.getChildControl("icon"); [ this.getChildControl("is-active-icon-outline"), diff --git a/services/static-webserver/client/source/class/osparc/notification/NotificationsButton.js b/services/static-webserver/client/source/class/osparc/notification/NotificationsButton.js index 6b532da51dba..8387d2e81fd0 100644 --- a/services/static-webserver/client/source/class/osparc/notification/NotificationsButton.js +++ b/services/static-webserver/client/source/class/osparc/notification/NotificationsButton.js @@ -45,7 +45,7 @@ qx.Class.define("osparc.notification.NotificationsButton", { let control; switch (id) { case "icon": { - control = new qx.ui.basic.Image(); + control = new qx.ui.basic.Image("@FontAwesome5Regular/bell/22"); const iconContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox().set({ alignY: "middle", })).set({ @@ -57,6 +57,24 @@ qx.Class.define("osparc.notification.NotificationsButton", { }); break; } + case "is-active-icon-outline": + control = new qx.ui.basic.Image("@FontAwesome5Solid/circle/12").set({ + textColor: osparc.navigation.NavigationBar.BG_COLOR, + }); + this._add(control, { + bottom: -4, + right: -4, + }); + break; + case "is-active-icon": + control = new qx.ui.basic.Image("@FontAwesome5Solid/circle/8").set({ + textColor: "strong-main", + }); + this._add(control, { + bottom: -2, + right: -2, + }); + break; case "number": control = new qx.ui.basic.Label().set({ backgroundColor: "error", @@ -82,16 +100,15 @@ qx.Class.define("osparc.notification.NotificationsButton", { const notifications = notificationManager.getNotifications(); notifications.forEach(notification => notification.addListener("changeRead", () => this.__updateButton(), this)); - const nUnreadNotifications = notifications.filter(notification => notification.getRead() === false).length; - const icon = this.getChildControl("icon"); - icon.set({ - source: nUnreadNotifications > 0 ? "@FontAwesome5Solid/bell/22" : "@FontAwesome5Regular/bell/22", - textColor: nUnreadNotifications > 0 ? "strong-main" : "text" - }); - const number = this.getChildControl("number"); - number.set({ - value: nUnreadNotifications.toString(), - visibility: nUnreadNotifications > 0 ? "visible" : "excluded" + let nUnreadNotifications = notifications.filter(notification => notification.getRead() === false).length; + nUnreadNotifications = 5; + [ + this.getChildControl("is-active-icon-outline"), + this.getChildControl("is-active-icon"), + ].forEach(control => { + control.set({ + visibility: nUnreadNotifications > 0 ? "visible" : "excluded" + }); }); }, diff --git a/services/static-webserver/client/source/class/osparc/store/Study.js b/services/static-webserver/client/source/class/osparc/store/Study.js index 8e565ebf7df2..b20bc06d6174 100644 --- a/services/static-webserver/client/source/class/osparc/store/Study.js +++ b/services/static-webserver/client/source/class/osparc/store/Study.js @@ -178,7 +178,7 @@ qx.Class.define("osparc.store.Study", { return osparc.data.Resources.fetch("studies", "updateMetadata", params); }, - getStudyState: function(studyId) { + fetchStudyState: function(studyId) { osparc.data.Resources.fetch("studies", "state", { url: { "studyId": studyId diff --git a/services/static-webserver/client/source/class/osparc/study/Conversations.js b/services/static-webserver/client/source/class/osparc/study/Conversations.js index 9f37585b656d..c8dd29f9ae93 100644 --- a/services/static-webserver/client/source/class/osparc/study/Conversations.js +++ b/services/static-webserver/client/source/class/osparc/study/Conversations.js @@ -52,17 +52,39 @@ qx.Class.define("osparc.study.Conversations", { PROJECT_ANNOTATION: "PROJECT_ANNOTATION", }, + CHANNELS: { + CONVERSATION_CREATED: "conversation:created", + CONVERSATION_UPDATED: "conversation:updated", + CONVERSATION_DELETED: "conversation:deleted", + CONVERSATION_MESSAGE_CREATED: "conversation:message:created", + CONVERSATION_MESSAGE_UPDATED: "conversation:message:updated", + CONVERSATION_MESSAGE_DELETED: "conversation:message:deleted", + }, + popUpInWindow: function(studyData, openConversationId = null) { const conversations = new osparc.study.Conversations(studyData, openConversationId); const title = qx.locale.Manager.tr("Conversations"); const viewWidth = 600; const viewHeight = 700; - const win = osparc.ui.window.Window.popUpInWindow(conversations, title, viewWidth, viewHeight); + const win = osparc.ui.window.Window.popUpInWindow(conversations, title, viewWidth, viewHeight).set({ + maxHeight: viewHeight, + }); win.addListener("close", () => { conversations.destroy(); }, this); return win; }, + + makeButtonBlink: function(button) { + const socket = osparc.wrapper.WebSocket.getInstance(); + Object.values(osparc.study.Conversations.CHANNELS).forEach(eventName => { + socket.on(eventName, () => { + if (button) { + osparc.utils.Utils.makeButtonBlink(button); + } + }); + }); + }, }, members: { @@ -95,20 +117,20 @@ qx.Class.define("osparc.study.Conversations", { const socket = osparc.wrapper.WebSocket.getInstance(); [ - "conversation:created", - "conversation:updated", - "conversation:deleted", + this.self().CHANNELS.CONVERSATION_CREATED, + this.self().CHANNELS.CONVERSATION_UPDATED, + this.self().CHANNELS.CONVERSATION_DELETED, ].forEach(eventName => { const eventHandler = conversation => { if (conversation) { switch (eventName) { - case "conversation:created": + case this.self().CHANNELS.CONVERSATION_CREATED: this.__addConversationPage(conversation); break; - case "conversation:updated": + case this.self().CHANNELS.CONVERSATION_UPDATED: this.__updateConversationName(conversation); break; - case "conversation:deleted": + case this.self().CHANNELS.CONVERSATION_DELETED: this.__removeConversationPage(conversation["conversationId"]); break; } @@ -119,9 +141,9 @@ qx.Class.define("osparc.study.Conversations", { }); [ - "conversation:message:created", - "conversation:message:updated", - "conversation:message:deleted", + this.self().CHANNELS.CONVERSATION_MESSAGE_CREATED, + this.self().CHANNELS.CONVERSATION_MESSAGE_UPDATED, + this.self().CHANNELS.CONVERSATION_MESSAGE_DELETED, ].forEach(eventName => { const eventHandler = message => { if (message) { @@ -129,13 +151,13 @@ qx.Class.define("osparc.study.Conversations", { const conversationPage = this.__getConversationPage(conversationId); if (conversationPage) { switch (eventName) { - case "conversation:message:created": + case this.self().CHANNELS.CONVERSATION_MESSAGE_CREATED: conversationPage.addMessage(message); break; - case "conversation:message:updated": + case this.self().CHANNELS.CONVERSATION_MESSAGE_UPDATED: conversationPage.updateMessage(message); break; - case "conversation:message:deleted": + case this.self().CHANNELS.CONVERSATION_MESSAGE_DELETED: conversationPage.deleteMessage(message); break; } 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 9f68010a56fe..beb742fbb667 100644 --- a/services/static-webserver/client/source/class/osparc/utils/Utils.js +++ b/services/static-webserver/client/source/class/osparc/utils/Utils.js @@ -321,23 +321,32 @@ qx.Class.define("osparc.utils.Utils", { }, makeButtonBlink: function(button, nTimes = 1) { - const onTime = 1000; - const oldBgColor = button.getBackgroundColor(); + const baseColor = button.getBackgroundColor(); + const blinkColor = "strong-main"; + const interval = 500; let count = 0; - const blinkIt = btn => { - count++; - btn.setBackgroundColor("strong-main"); - setTimeout(() => { - btn && btn.setBackgroundColor(oldBgColor); - }, onTime); - }; + // If a blink is already in progress, cancel it + if (button._blinkingIntervalId) { + clearInterval(button._blinkingIntervalId); + button.setBackgroundColor(baseColor); // reset to base + } + + const blinkInterval = setInterval(() => { + if (button && button.getContentElement()) { + button.setBackgroundColor((count % 2 === 0) ? blinkColor : baseColor); + count++; + + if (count >= nTimes * 2) { + clearInterval(blinkInterval); + button.setBackgroundColor(baseColor); + button._blinkingIntervalId = null; // cleanup + } + } + }, interval); - // make it "blink": show it as strong button during onTime" nTimes - blinkIt(button); - const intervalId = setInterval(() => { - (count < nTimes) ? blinkIt(button) : clearInterval(intervalId); - }, 2*onTime); + // Store interval ID on the button + button._blinkingIntervalId = blinkInterval; }, hardRefresh: function() { diff --git a/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js b/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js index d887ff0c6b18..1213ec57078c 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js @@ -180,10 +180,6 @@ qx.Class.define("osparc.workbench.NodeUI", { __deleteBtn: null, __nodeMoving: null, - getNodeType: function() { - return "service"; - }, - getNodeId: function() { return this.getNode().getNodeId(); }, @@ -343,6 +339,7 @@ qx.Class.define("osparc.workbench.NodeUI", { __applyNode: function(node) { node.addListener("changePosition", e => { this.moveNodeTo(e.getData()); + this.fireEvent("nodeMovingStop"); }); if (node.isDynamic()) { diff --git a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js index 60fae2ad5c1b..a074727aaa51 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js @@ -904,11 +904,7 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { }, __updateEdges: function(nodeUI) { - let edgesInvolved = []; - if (nodeUI.getNodeType() === "service") { - edgesInvolved = this.__getWorkbench().getConnectedEdges(nodeUI.getNodeId()); - } - + const edgesInvolved = this.__getWorkbench().getConnectedEdges(nodeUI.getNodeId()); edgesInvolved.forEach(edgeId => { const edgeUI = this.__getEdgeUI(edgeId); if (edgeUI) { @@ -1090,16 +1086,11 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { }, getNodeUI: function(nodeId) { - return this.__nodesUI.find(nodeUI => nodeUI.getNodeType() === "service" && nodeUI.getNodeId() === nodeId); + return this.__nodesUI.find(nodeUI => nodeUI.getNodeId() === nodeId); }, __getEdgeUI: function(edgeId) { - for (let i = 0; i < this.__edgesUI.length; i++) { - if (this.__edgesUI[i].getEdgeId() === edgeId) { - return this.__edgesUI[i]; - } - } - return null; + return this.__edgesUI.find(edgeUI => edgeUI.getEdgeId() === edgeId); }, clearNode(nodeId) {