diff --git a/services/static-webserver/client/source/class/osparc/dashboard/GroupedCardContainer.js b/services/static-webserver/client/source/class/osparc/dashboard/GroupedCardContainer.js index b33655305bbd..61dc00ff91dd 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/GroupedCardContainer.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/GroupedCardContainer.js @@ -100,7 +100,7 @@ qx.Class.define("osparc.dashboard.GroupedCardContainer", { paddingBottom: 5, allowGrowX: false }); - control.getChildControl("icon").set(this.getThumbnailProps(32)); + control.getChildControl("icon").set(osparc.utils.Utils.getThumbnailProps(32)); control.getChildControl("label").set({ rich: true, wrap: true diff --git a/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js b/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js index 29eb686f241d..1aa8dee04217 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js +++ b/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js @@ -30,6 +30,7 @@ qx.Class.define("osparc.data.model.IframeHandler", { }); this.__initLoadingPage(); + this.__initLockedPage(); this.__initIFrame(); }, @@ -44,7 +45,9 @@ qx.Class.define("osparc.data.model.IframeHandler", { node: { check: "osparc.data.model.Node", init: null, - nullable: false + nullable: false, + event: "changeNode", + apply: "__applyNode", }, loadingPage: { @@ -53,6 +56,12 @@ qx.Class.define("osparc.data.model.IframeHandler", { nullable: true }, + lockedPage: { + check: "osparc.ui.message.NodeLockedPage", + init: null, + nullable: true + }, + iFrame: { check: "osparc.widget.PersistentIframe", init: null, @@ -61,7 +70,19 @@ qx.Class.define("osparc.data.model.IframeHandler", { }, events: { - "iframeChanged": "qx.event.type.Event" + "iframeStateChanged": "qx.event.type.Event" + }, + + statics: { + evalShowToolbar: function(loadingPage, study) { + if (osparc.product.Utils.isProduct("s4llite")) { + loadingPage.setShowToolbar(false); + } else { + study.getUi().bind("mode", loadingPage, "showToolbar", { + converter: mode => mode !== "standalone" + }); + } + }, }, members: { @@ -89,16 +110,14 @@ qx.Class.define("osparc.data.model.IframeHandler", { } }, + __applyNode: function(node) { + node.getStatus().getLockState().addListener("changeLocked", () => this.fireEvent("iframeStateChanged"), this); + }, + __initIFrame: function() { const iframe = new osparc.widget.PersistentIframe(); osparc.utils.Utils.setIdToWidget(iframe.getIframe(), "iframe_"+this.getNode().getNodeId()); - if (osparc.product.Utils.isProduct("s4llite")) { - iframe.setShowToolbar(false); - } else { - this.getStudy().getUi().bind("mode", iframe, "showToolbar", { - converter: mode => mode !== "standalone" - }); - } + this.self().evalShowToolbar(iframe, this.getStudy()); iframe.addListener("restart", () => this.restartIFrame(), this); iframe.getDiskUsageIndicator().setCurrentNode(this.getNode()) this.setIFrame(iframe); @@ -108,13 +127,8 @@ qx.Class.define("osparc.data.model.IframeHandler", { const loadingPage = new osparc.ui.message.Loading().set({ header: this.__getLoadingPageHeader() }); - if (osparc.product.Utils.isProduct("s4llite")) { - loadingPage.setShowToolbar(false); - } else { - this.getStudy().getUi().bind("mode", loadingPage, "showToolbar", { - converter: mode => mode !== "standalone" - }); - } + + this.self().evalShowToolbar(loadingPage, this.getStudy()); const node = this.getNode(); const thumbnail = node.getMetadata()["thumbnail"]; @@ -146,6 +160,13 @@ qx.Class.define("osparc.data.model.IframeHandler", { return statusText + " " + node.getLabel() + " v" + versionDisplay + ""; }, + __initLockedPage: function() { + const lockedPage = new osparc.ui.message.NodeLockedPage(); + this.self().evalShowToolbar(lockedPage, this.getStudy()); + this.bind("node", lockedPage, "node"); + this.setLockedPage(lockedPage); + }, + __nodeState: function() { // Check if study is still there if (this.getStudy() === null || this.__stopRequestingStatus === true) { @@ -381,7 +402,7 @@ qx.Class.define("osparc.data.model.IframeHandler", { if (this.getIFrame()) { this.getIFrame().resetSource(); } - this.fireEvent("iframeChanged"); + this.fireEvent("iframeStateChanged"); } }, @@ -418,7 +439,7 @@ qx.Class.define("osparc.data.model.IframeHandler", { // fire event to force switching to iframe's content: // it is required in those cases where the native 'load' event isn't triggered (voila) - this.fireEvent("iframeChanged"); + this.fireEvent("iframeStateChanged"); } } } 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 7e0ea962091a..d5ea36cf93fc 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 @@ -528,7 +528,8 @@ qx.Class.define("osparc.data.model.Node", { this.setLabel(nodeData.label); } this.__populateInputOutputData(nodeData); - this.populateStates(nodeData); + this.populateProgress(nodeData); + this.populateState(nodeData); if (nodeData.bootOptions) { this.setBootOptions(nodeData.bootOptions); } @@ -570,7 +571,7 @@ qx.Class.define("osparc.data.model.Node", { this.setInputsRequired(nodeData.inputsRequired || []); }, - populateStates: function(nodeData) { + populateProgress: function(nodeData) { if ("progress" in nodeData) { const progress = Number.parseInt(nodeData["progress"]); const oldProgress = this.getStatus().getProgress(); @@ -581,6 +582,9 @@ qx.Class.define("osparc.data.model.Node", { this.getStatus().setProgress(progress); } } + }, + + populateState: function(nodeData) { if ("state" in nodeData) { this.getStatus().setState(nodeData.state); } @@ -608,6 +612,10 @@ qx.Class.define("osparc.data.model.Node", { return this.getIframeHandler() ? this.getIframeHandler().getLoadingPage() : null; }, + getLockedPage: function() { + return this.getIframeHandler() ? this.getIframeHandler().getLockedPage() : null; + }, + __applyPropsForm: function(propsForm) { osparc.utils.Utils.setIdToWidget(propsForm, "settingsForm_" + this.getNodeId()); }, diff --git a/services/static-webserver/client/source/class/osparc/data/model/NodeLockState.js b/services/static-webserver/client/source/class/osparc/data/model/NodeLockState.js new file mode 100644 index 000000000000..c14add8bc80a --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/data/model/NodeLockState.js @@ -0,0 +1,73 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +qx.Class.define("osparc.data.model.NodeLockState", { + extend: qx.core.Object, + + construct: function() { + this.base(arguments); + + this.initCurrentUserGroupIds(); + this.initLocked(); + this.initStatus(); + }, + + properties: { + currentUserGroupIds: { + check: "Array", + init: [], + nullable: false, + event: "changeCurrentUserGroupIds", + }, + + locked: { + check: "Boolean", + init: false, + nullable: false, + event: "changeLocked", + }, + + status: { + // check: ["NOT_STARTED", "STARTED", "OPENED"], + check: "String", + init: "NOT_STARTED", + nullable: false, + event: "changeStatus", + } + }, + + members: { + stateReceived: function(state) { + if (state) { + this.set({ + currentUserGroupIds: "current_user_groupids" in state ? state["current_user_groupids"] : [], + locked: "locked" in state ? state["locked"] : false, + status: "status" in state ? state["status"] : "NOT_STARTED", + }); + } + }, + + isLockedBySomeoneElse: function() { + if (this.isLocked()) { + const currentUserGroupIds = this.getCurrentUserGroupIds(); + const myGroupId = osparc.auth.Data.getInstance().getGroupId(); + return !currentUserGroupIds.includes(myGroupId); + } + return false; + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/data/model/NodeStatus.js b/services/static-webserver/client/source/class/osparc/data/model/NodeStatus.js index d6f0afede9ba..f8ed911ed5c3 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/NodeStatus.js +++ b/services/static-webserver/client/source/class/osparc/data/model/NodeStatus.js @@ -93,6 +93,13 @@ qx.Class.define("osparc.data.model.NodeStatus", { hasOutputs: { check: "Boolean", init: false + }, + + lockState: { + check: "osparc.data.model.NodeLockState", + nullable: true, + init: null, + event: "changeLockState" } }, @@ -139,17 +146,20 @@ qx.Class.define("osparc.data.model.NodeStatus", { members: { __applyNode: function(node) { - const addNodeProgressSequence = () => { + const initNode = () => { if (node.isDynamic()) { const progressSequence = new osparc.data.model.NodeProgressSequence(); this.setProgressSequence(progressSequence); } + + const lockState = new osparc.data.model.NodeLockState(); + this.setLockState(lockState); }; if (node.getMetadata()) { - addNodeProgressSequence(); + initNode(); } else { - node.addListenerOnce("changeMetadata", () => addNodeProgressSequence(), this); + node.addListenerOnce("changeMetadata", () => initNode(), this); } }, @@ -217,6 +227,9 @@ qx.Class.define("osparc.data.model.NodeStatus", { this.setModified(null); } } + if ("lock_state" in state) { + this.getLockState().stateReceived(state.lock_state); + } }, serialize: function() { diff --git a/services/static-webserver/client/source/class/osparc/data/model/Study.js b/services/static-webserver/client/source/class/osparc/data/model/Study.js index 03b0f0dd4c8c..081924b2a58c 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Study.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Study.js @@ -576,11 +576,8 @@ qx.Class.define("osparc.data.model.Study", { if (node) { if (nodeData && !osparc.data.model.Node.isFrontend(node.getMetadata())) { node.setOutputData(nodeData.outputs); - if ("progress" in nodeData) { - const progress = Number.parseInt(nodeData["progress"]); - node.getStatus().setProgress(progress); - } - node.populateStates(nodeData); + node.populateProgress(nodeData); + node.populateState(nodeData); } if ("errors" in nodeUpdatedData) { const errors = nodeUpdatedData["errors"]; 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 c836eba7bac8..645aed83fbf8 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js @@ -58,7 +58,33 @@ qx.Class.define("osparc.desktop.WorkbenchView", { const win = osparc.widget.StudyDataManager.popUpInWindow(node.getStudy().serialize(), node.getNodeId(), node.getLabel()); const closeBtn = win.getChildControl("close-button"); osparc.utils.Utils.setIdToWidget(closeBtn, "nodeDataManagerCloseBtn"); - } + }, + + __handleIframeStateChange: function(node, iframeLayout) { + iframeLayout.removeAll(); + if (node && node.getIFrame()) { + const iFrame = node.getIFrame(); + const src = iFrame.getSource(); + let showPage = iFrame; + if (node.getStatus().getLockState().isLockedBySomeoneElse()) { + showPage = node.getLockedPage(); + } else if (src === null || src === "about:blank") { + showPage = node.getLoadingPage(); + } + iframeLayout.add(showPage, { + flex: 1 + }); + } + }, + + listenToIframeStateChanges: function(node, iframeLayout) { + if (node && node.getIFrame()) { + const iFrame = node.getIFrame(); + node.getIframeHandler().addListener("iframeStateChanged", () => this.__handleIframeStateChange(node, iframeLayout), this); + iFrame.addListener("load", () => this.__handleIframeStateChange(node, iframeLayout)); + this.__handleIframeStateChange(node, iframeLayout); + } + }, }, events: { @@ -357,6 +383,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", { alignX: "center", marginLeft: 10 }); + // do not allow modifying the pipeline this.getStudy().bind("pipelineRunning", addNewNodeBtn, "enabled", { converter: running => !running }); @@ -755,9 +782,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", { widget.addListener("restore", () => this.setMaximized(false), this); } }); - node.getIframeHandler().addListener("iframeChanged", () => this.__iFrameChanged(node), this); - iFrame.addListener("load", () => this.__iFrameChanged(node), this); - this.__iFrameChanged(node); + osparc.desktop.WorkbenchView.listenToIframeStateChanges(node, this.__iframePage); } else { // This will keep what comes after at the bottom this.__iframePage.add(new qx.ui.core.Spacer(), { @@ -766,20 +791,6 @@ qx.Class.define("osparc.desktop.WorkbenchView", { } }, - __iFrameChanged: function(node) { - this.__iframePage.removeAll(); - - if (node && node.getIFrame()) { - const loadingPage = node.getLoadingPage(); - const iFrame = node.getIFrame(); - const src = iFrame.getSource(); - const iFrameView = (src === null || src === "about:blank") ? loadingPage : iFrame; - this.__iframePage.add(iFrameView, { - flex: 1 - }); - } - }, - __populateSecondaryColumn: function(node) { [ this.__studyOptionsPage, @@ -803,8 +814,12 @@ qx.Class.define("osparc.desktop.WorkbenchView", { this.__populateSecondaryColumnNode(node); } - if (node instanceof osparc.data.model.Node) { - node.getStudy().bind("pipelineRunning", this.__serviceOptionsPage, "enabled", { + if ( + node instanceof osparc.data.model.Node && + node.isComputational() && + node.getPropsForm() + ) { + node.getStudy().bind("pipelineRunning", node.getPropsForm(), "enabled", { converter: pipelineRunning => !pipelineRunning }); } 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 c33ea53745f6..2fecaab66c9d 100644 --- a/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js +++ b/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js @@ -157,22 +157,22 @@ qx.Class.define("osparc.navigation.NavigationBar", { switch (id) { case "left-items": control = new qx.ui.container.Composite(new qx.ui.layout.HBox(20).set({ - alignY: "middle", alignX: "left", + alignY: "middle", })); this._addAt(control, 0, { flex: 1 }); break; case "center-items": control = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({ - alignY: "middle", alignX: "center", + alignY: "middle", })); this._addAt(control, 1); break; case "right-items": control = new qx.ui.container.Composite(new qx.ui.layout.HBox(6).set({ - alignY: "middle", alignX: "right", + alignY: "middle", })); this._addAt(control, 2, { flex: 1 }); break; @@ -254,7 +254,9 @@ qx.Class.define("osparc.navigation.NavigationBar", { case "avatar-group": { const maxWidth = osparc.WindowSizeTracker.getInstance().isCompactVersion() ? 80 : 150; control = new osparc.ui.basic.AvatarGroup(26, "right", maxWidth).set({ + hideMyself: true, alignY: "middle", + visibility: "excluded", }); this.getChildControl("right-items").add(control); break; @@ -352,11 +354,8 @@ qx.Class.define("osparc.navigation.NavigationBar", { if (this.getStudy() && data["project_uuid"] === this.getStudy().getUuid()) { const projectState = data["data"]; const currentUserGroupIds = osparc.study.Utils.state.getCurrentGroupIds(projectState); - // remove myself from the list of users - const filteredUserGroupIds = currentUserGroupIds.filter(gid => gid !== osparc.store.Groups.getInstance().getMyGroupId()); - // show the rest of the users in the avatar group const avatarGroup = this.getChildControl("avatar-group"); - avatarGroup.setUserGroupIds(filteredUserGroupIds); + avatarGroup.setUserGroupIds(currentUserGroupIds); } } }, this); diff --git a/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js b/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js index f702aa747bf0..2db39881a513 100644 --- a/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js +++ b/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js @@ -56,15 +56,16 @@ qx.Class.define("osparc.node.slideshow.NodeView", { node.getPropsForm().hasVisibleInputs() ) { this._settingsLayout.add(node.getPropsForm()); + + // lock the inputs if the node is locked + node.getStatus().getLockState().bind("locked", node.getPropsForm(), "enabled", { + converter: locked => !locked + }); } const showSettings = node.isComputational(); this._settingsLayout.setVisibility(showSettings ? "visible" : "excluded"); - node.getStudy().bind("pipelineRunning", this._settingsLayout, "enabled", { - converter: pipelineRunning => !pipelineRunning - }); - this._mainView.add(this._settingsLayout); }, @@ -75,10 +76,7 @@ qx.Class.define("osparc.node.slideshow.NodeView", { const loadingPage = this.getNode().getLoadingPage(); const iFrame = this.getNode().getIFrame(); if (loadingPage && iFrame) { - const node = this.getNode(); - node.getIframeHandler().addListener("iframeChanged", () => this.__iFrameChanged(), this); - iFrame.addListener("load", () => this.__iFrameChanged()); - this.__iFrameChanged(); + osparc.desktop.WorkbenchView.listenToIframeStateChanges(this.getNode(), this._iFrameLayout); } else { // This will keep what comes after at the bottom this._iFrameLayout.add(new qx.ui.core.Spacer(), { @@ -131,20 +129,5 @@ qx.Class.define("osparc.node.slideshow.NodeView", { _applyNode: function(node) { this.base(arguments, node); }, - - __iFrameChanged: function() { - this._iFrameLayout.removeAll(); - - const node = this.getNode(); - if (node && node.getIFrame()) { - const loadingPage = node.getLoadingPage(); - const iFrame = node.getIFrame(); - const src = iFrame.getSource(); - const iFrameView = (src === null || src === "about:blank") ? loadingPage : iFrame; - this._iFrameLayout.add(iFrameView, { - flex: 1 - }); - } - } } }); diff --git a/services/static-webserver/client/source/class/osparc/ui/basic/AvatarGroup.js b/services/static-webserver/client/source/class/osparc/ui/basic/AvatarGroup.js index e2033e97fa75..dca045afbd89 100644 --- a/services/static-webserver/client/source/class/osparc/ui/basic/AvatarGroup.js +++ b/services/static-webserver/client/source/class/osparc/ui/basic/AvatarGroup.js @@ -43,6 +43,13 @@ qx.Class.define("osparc.ui.basic.AvatarGroup", { document.addEventListener("pointermove", this.__onGlobalPointerMove); }, + properties: { + hideMyself: { + check: "Boolean", + init: false, + } + }, + members: { __avatarSize: null, __maxVisible: null, @@ -60,6 +67,12 @@ qx.Class.define("osparc.ui.basic.AvatarGroup", { return; } this.__userGroupIds = userGroupIds || []; + + if (this.isHideMyself()) { + // remove myself from the list of users + userGroupIds = userGroupIds.filter(gid => gid !== osparc.store.Groups.getInstance().getMyGroupId()); + } + const usersStore = osparc.store.Users.getInstance(); const userPromises = userGroupIds.map(userGroupId => usersStore.getUser(userGroupId)); const users = []; diff --git a/services/static-webserver/client/source/class/osparc/ui/message/Loading.js b/services/static-webserver/client/source/class/osparc/ui/message/Loading.js index 163e77b57310..ffdf2f25908f 100644 --- a/services/static-webserver/client/source/class/osparc/ui/message/Loading.js +++ b/services/static-webserver/client/source/class/osparc/ui/message/Loading.js @@ -83,67 +83,65 @@ qx.Class.define("osparc.ui.message.Loading", { }, members: { - __thumbnail: null, - __header: null, - __messagesContainer: null, - __extraWidgets: null, __maxButton: null, - __buildLayout: function() { - const maxLayout = this.__createMaximizeToolbar(); - this._add(maxLayout); - - const topSpacer = new qx.ui.core.Spacer(); - this._add(topSpacer, { - flex: 1, - }); - - const productLogoPath = osparc.product.Utils.getLogoPath(); - const thumbnail = this.__thumbnail = new osparc.ui.basic.Thumbnail(productLogoPath, this.self().ICON_WIDTH, this.self().LOGO_HEIGHT).set({ - alignX: "center" - }); - let logoHeight = this.self().LOGO_HEIGHT; - if (qx.util.ResourceManager.getInstance().getImageFormat(productLogoPath) === "png") { - logoHeight = osparc.ui.basic.Logo.getHeightKeepingAspectRatio(productLogoPath, this.self().ICON_WIDTH); - thumbnail.getChildControl("image").set({ - width: this.self().ICON_WIDTH, - height: logoHeight - }); - } else { - thumbnail.getChildControl("image").set({ - width: this.self().ICON_WIDTH, - height: logoHeight - }); + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "max-toolbar": + control = this.__createMaximizeToolbar(); + this._add(control); + break; + case "spacer-top": + control = new qx.ui.core.Spacer(); + this._add(control, { + flex: 1, + }); + break; + case "thumbnail": + control = this.__createThumbnail(); + this._add(control); + break; + case "loading-title": + control = new qx.ui.basic.Atom().set({ + icon: "@FontAwesome5Solid/circle-notch/"+this.self().STATUS_ICON_SIZE, + font: "title-18", + alignX: "center", + rich: true, + gap: 15, + allowGrowX: false, + }); + osparc.service.StatusUI.updateCircleAnimation(control.getChildControl("icon")); + control.getChildControl("label").set({ + rich: true, + wrap: true, + alignX: "center", + }); + this._add(control); + break; + case "messages-container": + control = new qx.ui.container.Composite(new qx.ui.layout.VBox(10).set({ + alignX: "center" + })); + this._add(control); + break; + case "extra-widgets-container": + control = new qx.ui.container.Composite(new qx.ui.layout.VBox(10).set({ + alignX: "center" + })); + this._add(control); + break; } - this._add(thumbnail); - - const waitingHeader = this.__header = new qx.ui.basic.Atom().set({ - icon: "@FontAwesome5Solid/circle-notch/"+this.self().STATUS_ICON_SIZE, - font: "title-18", - alignX: "center", - rich: true, - gap: 15, - allowGrowX: false, - }); - const icon = waitingHeader.getChildControl("icon"); - osparc.service.StatusUI.updateCircleAnimation(icon); - const label = waitingHeader.getChildControl("label"); - label.set({ - rich: true, - wrap: true, - alignX: "center", - }); - this._add(waitingHeader); - - const messages = this.__messagesContainer = new qx.ui.container.Composite(new qx.ui.layout.VBox(10).set({ - alignX: "center" - })); - this._add(messages); + return control || this.base(arguments, id); + }, - const extraWidgets = this.__extraWidgets = new qx.ui.container.Composite(new qx.ui.layout.VBox(10).set({ - alignX: "center" - })); - this._add(extraWidgets); + __buildLayout: function() { + this.getChildControl("max-toolbar"); + this.getChildControl("spacer-top"); + this.getChildControl("thumbnail"); + this.getChildControl("loading-title"); + this.getChildControl("messages-container"); + this.getChildControl("extra-widgets-container"); const bottomSpacer = new qx.ui.core.Spacer(); this._add(bottomSpacer, { @@ -188,36 +186,73 @@ qx.Class.define("osparc.ui.message.Loading", { return toolbarLayout; }, + __createThumbnail: function() { + const productLogoPath = osparc.product.Utils.getLogoPath(); + const thumbnail = new osparc.ui.basic.Thumbnail(productLogoPath, this.self().ICON_WIDTH, this.self().LOGO_HEIGHT).set({ + alignX: "center" + }); + let logoHeight = this.self().LOGO_HEIGHT; + if (qx.util.ResourceManager.getInstance().getImageFormat(productLogoPath) === "png") { + logoHeight = osparc.ui.basic.Logo.getHeightKeepingAspectRatio(productLogoPath, this.self().ICON_WIDTH); + thumbnail.getChildControl("image").set({ + width: this.self().ICON_WIDTH, + height: logoHeight + }); + } else { + thumbnail.getChildControl("image").set({ + width: this.self().ICON_WIDTH, + height: logoHeight + }); + } + return thumbnail; + }, + __applyLogo: function(newLogo) { const productLogoPath = osparc.product.Utils.getLogoPath(); + const thumbnail = this.getChildControl("thumbnail"); if (newLogo !== productLogoPath) { - this.__thumbnail.set({ + thumbnail.set({ maxHeight: this.self().ICON_HEIGHT, height: this.self().ICON_HEIGHT, }); - this.__thumbnail.getChildControl("image").set({ + thumbnail.getChildControl("image").set({ maxHeight: this.self().ICON_HEIGHT, height: this.self().ICON_HEIGHT, }); } - this.__thumbnail.setSource(newLogo); + thumbnail.setSource(newLogo); }, __applyHeader: function(value) { - this.__header.setLabel(value); + this._setHeaderTitle(value); + + // extract the state from the title const words = value.split(" "); if (words.length) { const state = words[0]; const iconSource = osparc.service.StatusUI.getIconSource(state.toLowerCase(), this.self().STATUS_ICON_SIZE); if (iconSource) { - this.__header.setIcon(iconSource); - osparc.service.StatusUI.updateCircleAnimation(this.__header.getChildControl("icon")); + this._setHeaderIcon(iconSource); } } }, + _setHeaderTitle: function(label) { + const loadingTitle = this.getChildControl("loading-title"); + loadingTitle.setLabel(label); + }, + + _setHeaderIcon: function(iconSource) { + const loadingTitle = this.getChildControl("loading-title"); + loadingTitle.setIcon(iconSource); + // this will stop the circle, if it's not a circle + osparc.service.StatusUI.updateCircleAnimation(loadingTitle.getChildControl("icon")); + }, + __applyMessages: function(msgs) { this.clearMessages(); + + const messagesContainer = this.getChildControl("messages-container"); if (msgs) { msgs.forEach(msg => { const text = new qx.ui.basic.Label(msg.toString()).set({ @@ -225,33 +260,36 @@ qx.Class.define("osparc.ui.message.Loading", { rich: true, wrap: true }); - this.__messagesContainer.add(text); + messagesContainer.add(text); }); - this.__messagesContainer.show(); + messagesContainer.show(); } else { - this.__messagesContainer.exclude(); + messagesContainer.exclude(); } }, clearMessages: function() { - this.__messagesContainer.removeAll(); + const messagesContainer = this.getChildControl("messages-container"); + messagesContainer.removeAll(); }, getMessageLabels: function() { - return this.__messagesContainer.getChildren(); + return this.getChildControl("messages-container").getChildren(); }, addWidgetToMessages: function(widget) { + const messagesContainer = this.getChildControl("messages-container"); if (widget) { - this.__messagesContainer.add(widget); - this.__messagesContainer.show(); + messagesContainer.add(widget); + messagesContainer.show(); } else { - this.__messagesContainer.exclude(); + messagesContainer.exclude(); } }, addExtraWidget: function(widget) { - this.__extraWidgets.add(widget); + const extraWidgetsContainer = this.getChildControl("extra-widgets-container"); + extraWidgetsContainer.add(widget); }, } }); diff --git a/services/static-webserver/client/source/class/osparc/ui/message/NodeLockedPage.js b/services/static-webserver/client/source/class/osparc/ui/message/NodeLockedPage.js new file mode 100644 index 000000000000..c12c9cdae807 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/ui/message/NodeLockedPage.js @@ -0,0 +1,111 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * The locked page + * + * ----------------------- + * | | + * | service logo | + * | locked/unlocked | + * | - who is in | + * | | + * ----------------------- + * + */ +qx.Class.define("osparc.ui.message.NodeLockedPage", { + extend: osparc.ui.message.Loading, + + + construct: function() { + this.base(arguments); + + this.__addActionsLayout(); + }, + + properties: { + node: { + check: "osparc.data.model.Node", + init: null, + nullable: false, + event: "changeNode", + apply: "__applyNode", + }, + }, + + members: { + __avatarGroup: null, + + __applyNode: function(node) { + const thumbnail = node.getMetadata()["thumbnail"]; + if (thumbnail) { + this.setLogo(thumbnail); + } + + const lockState = node.getStatus().getLockState(); + + lockState.addListener("changeLocked", this.__lockedChanged, this); + this.__lockedChanged(); + + lockState.addListener("changeCurrentUserGroupIds", this.__currentUserGroupIdsChanged, this); + this.__currentUserGroupIdsChanged(); + }, + + __addActionsLayout: function() { + const actionsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({ + alignX: "center" + })); + + const conversationButton = new qx.ui.form.Button().set({ + appearance: "form-button-outlined", + toolTipText: this.tr("Conversations"), + icon: "@FontAwesome5Solid/comments/16", + }); + conversationButton.addListener("execute", () => { + if (this.getNode()) { + const study = this.getNode().getStudy(); + osparc.study.Conversations.popUpInWindow(study.serialize()); + } + }); + actionsLayout.add(conversationButton); + + const avatarGroup = this.__avatarGroup = new osparc.ui.basic.AvatarGroup(26, "left", 50).set({ + hideMyself: true, + alignX: "center", + }); + actionsLayout.add(avatarGroup); + this.addWidgetToMessages(actionsLayout); + }, + + __lockedChanged: function() { + const lockState = this.getNode().getStatus().getLockState(); + if (lockState.isLocked()) { + this._setHeaderIcon("@FontAwesome5Solid/lock/20"); + this._setHeaderTitle(this.tr("The application is being used")); + } else { + this._setHeaderIcon("@FontAwesome5Solid/lock-open/20"); + this._setHeaderTitle(this.tr("The application is not being used")); + } + }, + + __currentUserGroupIdsChanged: function() { + const lockState = this.getNode().getStatus().getLockState(); + const currentUserGroupIds = lockState.getCurrentUserGroupIds(); + this.__avatarGroup.setUserGroupIds(currentUserGroupIds); + }, + } +}); diff --git a/services/static-webserver/client/source/class/osparc/viewer/NodeViewer.js b/services/static-webserver/client/source/class/osparc/viewer/NodeViewer.js index 0d27a021edc5..521bbb7e9d64 100644 --- a/services/static-webserver/client/source/class/osparc/viewer/NodeViewer.js +++ b/services/static-webserver/client/source/class/osparc/viewer/NodeViewer.js @@ -78,31 +78,13 @@ qx.Class.define("osparc.viewer.NodeViewer", { const iframeHandler = node.getIframeHandler(); if (iframeHandler) { iframeHandler.checkState(); - iframeHandler.addListener("iframeChanged", () => this.__iFrameChanged(), this); - iframeHandler.getIFrame().addListener("load", () => this.__iFrameChanged(), this); - this.__iFrameChanged(); - + osparc.desktop.WorkbenchView.listenToIframeStateChanges(node, this); this.__attachSocketEventHandlers(); } else { console.error(node.getLabel() + " iframe handler not ready"); } }, - __iFrameChanged: function() { - this._removeAll(); - - if (this.getNode() && this.getNode().getIframeHandler()) { - const iframeHandler = this.getNode().getIframeHandler(); - const loadingPage = iframeHandler.getLoadingPage(); - const iFrame = iframeHandler.getIFrame(); - const src = iFrame.getSource(); - const iFrameView = (src === null || src === "about:blank") ? loadingPage : iFrame; - this._add(iFrameView, { - flex: 1 - }); - } - }, - __attachSocketEventHandlers: function() { this.__listenToNodeUpdated(); this.__listenToNodeProgress(); diff --git a/services/static-webserver/client/source/class/osparc/widget/NodeTreeItem.js b/services/static-webserver/client/source/class/osparc/widget/NodeTreeItem.js index d424973944f6..ba0d3db1c8da 100644 --- a/services/static-webserver/client/source/class/osparc/widget/NodeTreeItem.js +++ b/services/static-webserver/client/source/class/osparc/widget/NodeTreeItem.js @@ -149,6 +149,7 @@ qx.Class.define("osparc.widget.NodeTreeItem", { updateMarker(); const deleteBtn = this.getChildControl("delete-button"); + // do not allow modifying the pipeline node.getStudy().bind("pipelineRunning", deleteBtn, "enabled", { converter: running => !running }); diff --git a/services/static-webserver/client/source/class/osparc/workbench/DiskUsageIndicator.js b/services/static-webserver/client/source/class/osparc/workbench/DiskUsageIndicator.js index e733be3b6bcf..4a6993a93313 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/DiskUsageIndicator.js +++ b/services/static-webserver/client/source/class/osparc/workbench/DiskUsageIndicator.js @@ -103,6 +103,10 @@ qx.Class.define("osparc.workbench.DiskUsageIndicator", { // Subscribe to disk usage data for the new node this._subscribe(node); + + node.getStatus().bind("interactive", this, "visibility", { + converter: state => state === "ready" ? "visible" : "excluded" + }); }, _subscribe: function(node) { 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 d7005896e898..277a0d964082 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js @@ -221,7 +221,7 @@ qx.Class.define("osparc.workbench.NodeUI", { }); break; case "middle-container": - control = new qx.ui.container.Composite(new qx.ui.layout.Flow(3, 3).set({ + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(3).set({ alignY: "middle" })).set({ padding: [3, 4] @@ -251,7 +251,7 @@ qx.Class.define("osparc.workbench.NodeUI", { toolTipText: "Unknown", }); } - this.getChildControl("middle-container").add(control); + this.getChildControl("middle-container").addAt(control, 0); break; } case "node-status-ui": { @@ -273,7 +273,7 @@ qx.Class.define("osparc.workbench.NodeUI", { }; evaluateLabel(); statusLabel.addListener("changeValue", evaluateLabel); - this.getChildControl("middle-container").add(control); + this.getChildControl("middle-container").addAt(control, 1); break; } case "progress": @@ -287,6 +287,14 @@ qx.Class.define("osparc.workbench.NodeUI", { colSpan: 3 }); break; + case "avatar-group": + control = new osparc.ui.basic.AvatarGroup(20, "right").set({ + hideMyself: true, + }); + this.getChildControl("middle-container").addAt(control, 2, { + flex: 1 + }); + break; case "usage-indicator": control = new osparc.workbench.DiskUsageIndicator(); this.add(control, { @@ -313,7 +321,6 @@ qx.Class.define("osparc.workbench.NodeUI", { __createContentLayout: function() { const node = this.getNode(); if (node) { - this.getChildControl("middle-container").removeAll(); this.getChildControl("node-type-chip"); this.getChildControl("node-status-ui"); @@ -397,11 +404,33 @@ qx.Class.define("osparc.workbench.NodeUI", { this.__optionsMenu.add(convertToParameter); } + const lockState = node.getStatus().getLockState(); const lock = this.getChildControl("lock"); - node.getStudy().bind("pipelineRunning", lock, "visibility", { - converter: pipelineRunning => pipelineRunning ? "visible" : "excluded" + lockState.bind("locked", lock, "visibility", { + converter: locked => { + if (locked) { + if (node.isDynamic()) { + // if it's dynamic, don't show the lock if it's me using it + const myGroupId = osparc.auth.Data.getInstance().getGroupId(); + const currentUserGroupIds = lockState.getCurrentUserGroupIds(); + return currentUserGroupIds.includes(myGroupId) ? "excluded" : "visible"; + } else { + return "visible"; + } + } + return "excluded"; + } }); + const updateUserGroupIds = () => { + const currentUserGroupIds = lockState.getCurrentUserGroupIds(); + const avatarGroup = this.getChildControl("avatar-group"); + avatarGroup.setUserGroupIds(currentUserGroupIds); + avatarGroup.setVisibility(currentUserGroupIds.length ? "visible" : "excluded"); + }; + updateUserGroupIds(); + lockState.addListener("changeCurrentUserGroupIds", updateUserGroupIds); + this.__markerBtn.show(); this.getNode().bind("marker", this.__markerBtn, "label", { converter: val => val ? this.tr("Remove Marker") : this.tr("Add Marker") @@ -420,6 +449,7 @@ qx.Class.define("osparc.workbench.NodeUI", { node.addListener("changeMarker", () => updateMarker()); updateMarker(); + // do not allow modifying the pipeline node.getStudy().bind("pipelineRunning", this.__deleteBtn, "enabled", { converter: running => !running });