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 bb7e87a3a759..ff58b7ddd827 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 @@ -193,12 +193,11 @@ qx.Class.define("osparc.data.model.NodeStatus", { const compRunning = this.getRunning(); const hasOutputs = this.getHasOutputs(); const modified = this.getModified(); - const hasDependencies = this.hasDependencies(); if (["PUBLISHED", "PENDING", "WAITING_FOR_RESOURCES", "WAITING_FOR_CLUSTER", "STARTED"].includes(compRunning)) { this.setOutput("busy"); } else if ([null, false].includes(hasOutputs)) { this.setOutput("not-available"); - } else if (hasOutputs && (modified || hasDependencies)) { + } else if (hasOutputs && modified) { this.setOutput("out-of-date"); } else if (hasOutputs && !modified) { this.setOutput("up-to-date"); @@ -218,30 +217,10 @@ qx.Class.define("osparc.data.model.NodeStatus", { // currentStatus is only applicable to computational services this.setRunning(state.currentStatus); } - if ("modified" in state) { - if (this.getHasOutputs()) { - // File Picker can't have a modified output - this.setModified((state.modified || this.hasDependencies()) && !this.getNode().isFilePicker()); - } else { - this.setModified(null); - } - } + this.setModified("modified" in state ? state.modified : null); if ("lock_state" in state) { this.getLockState().stateReceived(state.lock_state); } }, - - serialize: function() { - const state = {}; - state["dependencies"] = this.getDependencies() ? this.getDependencies() : []; - if (this.getNode().isComputational()) { - state["currentStatus"] = this.getRunning(); - } - state["modified"] = null; - // File Picker can't have a modified output - if (this.getHasOutputs() && !this.getNode().isFilePicker()) { - state["modified"] = this.hasDependencies(); - } - } } }); diff --git a/services/static-webserver/client/source/class/osparc/desktop/SlideshowView.js b/services/static-webserver/client/source/class/osparc/desktop/SlideshowView.js index 007038747383..ded648e6dc08 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/SlideshowView.js +++ b/services/static-webserver/client/source/class/osparc/desktop/SlideshowView.js @@ -405,15 +405,17 @@ qx.Class.define("osparc.desktop.SlideshowView", { }, __requestServiceBetween: function(leftNodeId, rightNodeId) { - const srvCat = new osparc.workbench.ServiceCatalog(); - srvCat.setContext(leftNodeId, rightNodeId); - srvCat.addListener("addService", e => { - const data = e.getData(); - const service = data.service; - this.__addServiceBetween(service, leftNodeId, rightNodeId); - }, this); - srvCat.center(); - srvCat.open(); + if (osparc.workbench.ServiceCatalog.canItBeOpened(this.getStudy())) { + const srvCat = new osparc.workbench.ServiceCatalog(); + srvCat.setContext(leftNodeId, rightNodeId); + srvCat.addListener("addService", e => { + const data = e.getData(); + const service = data.service; + this.__addServiceBetween(service, leftNodeId, rightNodeId); + }, this); + srvCat.center(); + srvCat.open(); + } }, __addServiceBetween: async function(service, leftNodeId, rightNodeId) { diff --git a/services/static-webserver/client/source/class/osparc/navigation/BreadcrumbsSlideshow.js b/services/static-webserver/client/source/class/osparc/navigation/BreadcrumbsSlideshow.js index e69e8b66ded0..a1830a0e15e6 100644 --- a/services/static-webserver/client/source/class/osparc/navigation/BreadcrumbsSlideshow.js +++ b/services/static-webserver/client/source/class/osparc/navigation/BreadcrumbsSlideshow.js @@ -104,12 +104,12 @@ qx.Class.define("osparc.navigation.BreadcrumbsSlideshow", { if (node.isFilePicker()) { osparc.service.StatusUI.setupFilePickerIcon(node, statusIcon); } else { - const check = node.isDynamic() ? "interactive" : "output"; - node.getStatus().bind(check, statusIcon, "source", { + const checkProp = node.isDynamic() ? "interactive" : "output"; + node.getStatus().bind(checkProp, statusIcon, "source", { converter: output => osparc.service.StatusUI.getIconSource(output), onUpdate: (_, target) => osparc.service.StatusUI.updateCircleAnimation(target) }); - node.getStatus().bind(check, statusIcon, "textColor", { + node.getStatus().bind(checkProp, statusIcon, "textColor", { converter: output => osparc.service.StatusUI.getColor(output) }, this); } diff --git a/services/static-webserver/client/source/class/osparc/service/StatusUI.js b/services/static-webserver/client/source/class/osparc/service/StatusUI.js index 13631e61983b..f7397027e794 100644 --- a/services/static-webserver/client/source/class/osparc/service/StatusUI.js +++ b/services/static-webserver/client/source/class/osparc/service/StatusUI.js @@ -199,12 +199,12 @@ qx.Class.define("osparc.service.StatusUI", { // output case "busy": return "busy-orange"; + case "not-available": + return "workbench-edge"; case "out-of-date": return "failed-red"; - /* case "up-to-date": return "ready-green"; - */ default: return "text"; diff --git a/services/static-webserver/client/source/class/osparc/theme/ColorDark.js b/services/static-webserver/client/source/class/osparc/theme/ColorDark.js index c6d467605915..a7fb3673b67b 100644 --- a/services/static-webserver/client/source/class/osparc/theme/ColorDark.js +++ b/services/static-webserver/client/source/class/osparc/theme/ColorDark.js @@ -136,8 +136,6 @@ qx.Theme.define("osparc.theme.ColorDark", { // OSPARC - "workbench-edge-comp-active": "#777777", - "workbench-edge-api-active": "#BBBBBB", "workbench-start-hint": "#505050", "workbench-view-navbar": "c00", "workbench-view-splitter": "#000000", diff --git a/services/static-webserver/client/source/class/osparc/theme/ColorLight.js b/services/static-webserver/client/source/class/osparc/theme/ColorLight.js index 1c6488ceba06..e6b8583074ad 100644 --- a/services/static-webserver/client/source/class/osparc/theme/ColorLight.js +++ b/services/static-webserver/client/source/class/osparc/theme/ColorLight.js @@ -136,8 +136,6 @@ qx.Theme.define("osparc.theme.ColorLight", { // OSPARC - "workbench-edge-comp-active": "#888888", - "workbench-edge-api-active": "#444444", "workbench-start-hint": "#AFAFAF", "workbench-view-navbar": "c02", "workbench-view-splitter": "background-main-3", diff --git a/services/static-webserver/client/source/class/osparc/theme/mixin/Color.js b/services/static-webserver/client/source/class/osparc/theme/mixin/Color.js index aaa58363b215..75bc4def7a25 100644 --- a/services/static-webserver/client/source/class/osparc/theme/mixin/Color.js +++ b/services/static-webserver/client/source/class/osparc/theme/mixin/Color.js @@ -51,6 +51,7 @@ qx.Theme.define("osparc.theme.mixin.Color", { "logger-warning-message": "warning-yellow", "logger-error-message": "failed-red", + "workbench-edge": "#787878", "workbench-edge-selected": "busy-orange", diff --git a/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js b/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js index 7c1b40ef3a02..cdd5a45d580a 100644 --- a/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js +++ b/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js @@ -301,11 +301,13 @@ qx.Class.define("osparc.widget.PersistentIframe", { }, __handleIframeMessage: function(data, nodeId) { - if (data["type"]) { - switch (data["type"]) { + const action = data["type"]; + if (action) { + const message = data["message"]; + switch (action) { + case "changeTheme": case "theme": { // switch theme driven by the iframe - const message = data["message"]; if (message && message.includes("osparc;theme=")) { const themeName = message.replace("osparc;theme=", ""); const validThemes = osparc.ui.switch.ThemeSwitcher.getValidThemes(); @@ -319,7 +321,7 @@ qx.Class.define("osparc.widget.PersistentIframe", { } case "openMarket": { if (osparc.product.Utils.showS4LStore()) { - const category = data["message"] && data["message"]["category"]; + const category = message && message["category"]; setTimeout(() => osparc.vipMarket.MarketWindow.openWindow(nodeId, category), 100); } break; @@ -333,16 +335,16 @@ qx.Class.define("osparc.widget.PersistentIframe", { case "openFunction": { // this is the MetaModeling service trying to show function/template information let functionData = null; - if (data["message"] && data["message"]["uuid"]) { + if (message && message["uuid"]) { // new version, the uuid is from the function functionData = { - "uuid": data["message"]["uuid"], + "uuid": message["uuid"], "resourceType": "function", }; - } else if (data["message"] && data["message"]["functionId"]) { + } else if (message && message["functionId"]) { // old version, the uuid is from the template functionData = { - "uuid": data["message"]["functionId"], + "uuid": message["functionId"], "resourceType": "functionedTemplate", }; } @@ -362,10 +364,10 @@ qx.Class.define("osparc.widget.PersistentIframe", { case "openSupport": { const supportCenterWindow = osparc.support.SupportCenter.openWindow(); // for now prefill the text box with the question - if (data["message"] && data["message"]["question"]) { + if (message && message["question"]) { supportCenterWindow.proposeConversation( osparc.support.Conversation.SYSTEM_MESSAGE_TYPE.ESCALATE_TO_SUPPORT, - `From your last question: "${data["message"]["question"]}"` + `From your last question: "${message["question"]}"` ); } break; diff --git a/services/static-webserver/client/source/class/osparc/workbench/EdgeUI.js b/services/static-webserver/client/source/class/osparc/workbench/EdgeUI.js index 40e72e134f99..c7e496069f6e 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/EdgeUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/EdgeUI.js @@ -48,7 +48,7 @@ qx.Class.define("osparc.workbench.EdgeUI", { representation.hint = hint; if (edge.getInputNode()) { - edge.getInputNode().getStatus().addListener("changeModified", () => this.__updateEdgeState()); + edge.getInputNode().getStatus().addListener("changeOutput", () => this.__updateEdgeState()); } edge.addListener("changePortConnected", () => this.__updateEdgeState()); @@ -69,13 +69,8 @@ qx.Class.define("osparc.workbench.EdgeUI", { }, statics: { - getEdgeColor: function(modified) { - let newColor = null; - if (modified === null) { - newColor = qx.theme.manager.Color.getInstance().resolve("workbench-edge-comp-active"); - } else { - newColor = osparc.service.StatusUI.getColor(modified ? "failed" : "ready"); - } + getEdgeColor: function(outputState) { + const newColor = osparc.service.StatusUI.getColor(outputState); const colorHex = qx.theme.manager.Color.getInstance().resolve(newColor); return colorHex; }, @@ -89,10 +84,10 @@ qx.Class.define("osparc.workbench.EdgeUI", { __updateEdgeState: function() { const inputNode = this.getEdge().getInputNode(); const portConnected = this.getEdge().isPortConnected(); - const modified = inputNode ? inputNode.getStatus().getModified() : false; + const output = inputNode ? inputNode.getStatus().getOutput() : null; // color - const colorHex = this.self().getEdgeColor(modified); + const colorHex = this.self().getEdgeColor(output); osparc.wrapper.Svg.updateCurveColor(this.getRepresentation(), colorHex); // shape @@ -102,7 +97,7 @@ qx.Class.define("osparc.workbench.EdgeUI", { const hint = this.getHint(); if (this.getEdge().isPortConnected() === false) { hint.setText(this.self().noPortsConnectedText(this.getEdge())); - } else if (modified) { + } else if (output === "out-of-date") { hint.setText("Out-of-date"); } else { hint.setText(null); 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 bbbc448d7d05..a4c381eb948d 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js @@ -138,10 +138,11 @@ qx.Class.define("osparc.workbench.NodeUI", { CAPTION_POS: { ICON: 0, // from qooxdoo TITLE: 1, // from qooxdoo - LOCK: 2, - MARKER: 3, - DEPRECATED: 4, - MENU: 5 + MODIFIED_STAR: 2, + LOCK: 3, + MARKER: 4, + DEPRECATED: 5, + MENU: 6, }, captionHeight: function() { @@ -164,6 +165,7 @@ qx.Class.define("osparc.workbench.NodeUI", { "nodeMovingStop": "qx.event.type.Event", "updateNodeDecorator": "qx.event.type.Event", "requestOpenLogger": "qx.event.type.Event", + "requestOpenServiceCatalog": "qx.event.type.Data", "highlightEdge": "qx.event.type.Data", }, @@ -184,6 +186,21 @@ qx.Class.define("osparc.workbench.NodeUI", { _createChildControlImpl: function(id) { let control; switch (id) { + case "modified-star": + control = new qx.ui.basic.Label("*").set({ + font: "text-14", + toolTipText: this.tr("Needs to run to update the outputs"), + padding: 4, + paddingTop: 0, + paddingBottom: 0, + visibility: "excluded", + alignY: "top", + }); + this.getChildControl("captionbar").add(control, { + row: 0, + column: this.self().CAPTION_POS.MODIFIED_STAR + }); + break; case "lock": control = new qx.ui.basic.Image().set({ source: "@FontAwesome5Solid/lock/12", @@ -419,7 +436,20 @@ qx.Class.define("osparc.workbench.NodeUI", { this.__optionsMenu.add(convertToParameter); } - const lockState = node.getStatus().getLockState(); + const nodeStatus = node.getStatus(); + const modifiedStar = this.getChildControl("modified-star"); + const evaluateShowStar = () => { + const modified = nodeStatus.getModified(); + const isRunning = osparc.data.model.NodeStatus.isComputationalRunning(node); + modifiedStar.set({ + visibility: modified && !isRunning ? "visible" : "excluded" + }); + }; + evaluateShowStar(); + nodeStatus.addListener("changeModified", evaluateShowStar); + nodeStatus.addListener("changeRunning", evaluateShowStar); + + const lockState = nodeStatus.getLockState(); const lock = this.getChildControl("lock"); lockState.bind("locked", lock, "visibility", { converter: locked => { @@ -744,6 +774,9 @@ qx.Class.define("osparc.workbench.NodeUI", { return; } const port = this.__createPort(isInput); + port.addListener("tap", () => { + this.fireDataEvent("requestOpenServiceCatalog", isInput); + }, this); port.addListener("mouseover", () => { port.setSource(this.self().PORT_CONNECTED); }, this); @@ -756,10 +789,10 @@ qx.Class.define("osparc.workbench.NodeUI", { if (isInput) { this.getNode().getStatus().bind("dependencies", port, "textColor", { converter: dependencies => { - if (dependencies !== null) { - return osparc.service.StatusUI.getColor(dependencies.length ? "modified" : "ready"); + if (dependencies) { + return dependencies.length ? "failed-red" : "ready-green"; } - return osparc.service.StatusUI.getColor(); + return "workbench-edge"; } }); this.getNode().bind("inputConnected", port, "source", { diff --git a/services/static-webserver/client/source/class/osparc/workbench/ServiceCatalog.js b/services/static-webserver/client/source/class/osparc/workbench/ServiceCatalog.js index 42ee0571d9e4..26b5f7a9e327 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/ServiceCatalog.js +++ b/services/static-webserver/client/source/class/osparc/workbench/ServiceCatalog.js @@ -82,7 +82,26 @@ qx.Class.define("osparc.workbench.ServiceCatalog", { statics: { LATEST: "latest", Width: 580, - Height: 500 + Height: 500, + + canItBeOpened: function(study) { + if (study) { + if (study.isReadOnly()) { + osparc.FlashMessenger.logError("Nodes can't be added to a read-only project"); + return false; + } + if (!osparc.data.model.Study.canIWrite(study.getAccessRights())) { + osparc.FlashMessenger.logError("You don't have permissions to add nodes to this project"); + return false; + } + if (study.isPipelineRunning()) { + osparc.FlashMessenger.logError(osparc.data.model.Workbench.CANT_ADD_NODE); + return false; + } + return true; + } + return true; + }, }, members: { 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 e68c6774f326..85a336fe17a6 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js @@ -292,30 +292,38 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { this.openServiceCatalog(nodePos); }, - openServiceCatalog: function(nodePos) { - if (this.getStudy().isReadOnly()) { - return null; + __openServiceCatalogWithContext: function(nodeUI, isNodeInput) { + const freePos = this.getStudy().getWorkbench().getFreePosition(nodeUI.getNode(), isNodeInput); + const srvCat = this.openServiceCatalog(freePos); + if (srvCat) { + if (isNodeInput) { + srvCat.setContext(null, nodeUI.getNodeId()); + } else { + srvCat.setContext(nodeUI.getNodeId(), null); + } } - if (this.getStudy().isPipelineRunning()) { - osparc.FlashMessenger.logError(osparc.data.model.Workbench.CANT_ADD_NODE); - return null; + }, + + openServiceCatalog: function(nodePos) { + if (osparc.workbench.ServiceCatalog.canItBeOpened(this.getStudy())) { + const srvCat = new osparc.workbench.ServiceCatalog(); + srvCat.addListener("addService", async e => { + const { + service, + nodeLeftId, + nodeRightId + } = e.getData(); + const nodeUI = await this.__addNode(service, nodePos); + if (nodeUI && nodeLeftId !== null || nodeRightId !== null) { + const newNodeId = nodeUI.getNodeId(); + this._createEdgeBetweenNodes(nodeLeftId ? nodeLeftId : newNodeId, nodeRightId ? nodeRightId : newNodeId, true); + } + }, this); + srvCat.center(); + srvCat.open(); + return srvCat; } - const srvCat = new osparc.workbench.ServiceCatalog(); - srvCat.addListener("addService", async e => { - const { - service, - nodeLeftId, - nodeRightId - } = e.getData(); - const nodeUI = await this.__addNode(service, nodePos); - if (nodeUI && nodeLeftId !== null || nodeRightId !== null) { - const newNodeId = nodeUI.getNodeId(); - this._createEdgeBetweenNodes(nodeLeftId ? nodeLeftId : newNodeId, nodeRightId ? nodeRightId : newNodeId, true); - } - }, this); - srvCat.center(); - srvCat.open(); - return srvCat; + return null; }, __createTemporaryNodeUI: function(pos) { @@ -705,6 +713,7 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { edgeFound.setHighlighted(highlight); } }); + nodeUI.addListener("requestOpenServiceCatalog", e => this.__openServiceCatalogWithContext(nodeUI, e.getData()), this); return nodeUI; }, @@ -1045,8 +1054,8 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { portLabel.setSource(osparc.workbench.NodeUI.PORT_CONNECTED); if (!this.__tempEdgeIsInput) { - const modified = nodeUI.getNode().getStatus().getModified(); - const colorHex = osparc.workbench.EdgeUI.getEdgeColor(modified); + const output = nodeUI.getNode().getStatus().getOutput(); + const colorHex = osparc.workbench.EdgeUI.getEdgeColor(output); osparc.wrapper.Svg.updateCurveColor(this.__tempEdgeRepr, colorHex); } }, @@ -1397,23 +1406,11 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { }, addServiceInput: { "text": "\uf090", // in - "action": () => { - const freePos = this.getStudy().getWorkbench().getFreePosition(nodeUI.getNode(), true); - const srvCat = this.openServiceCatalog(freePos); - if (srvCat) { - srvCat.setContext(null, nodeUI.getNodeId()); - } - } + "action": () => this.__openServiceCatalogWithContext(nodeUI, true) }, addServiceOutput: { "text": "\uf08b", // out - "action": () => { - const freePos = this.getStudy().getWorkbench().getFreePosition(nodeUI.getNode(), false); - const srvCat = this.openServiceCatalog(freePos); - if (srvCat) { - srvCat.setContext(nodeUI.getNodeId(), null); - } - } + "action": () => this.__openServiceCatalogWithContext(nodeUI, false) }, noAction: { "text": "\uf05e", // verboten diff --git a/services/static-webserver/client/source/class/osparc/wrapper/Svg.js b/services/static-webserver/client/source/class/osparc/wrapper/Svg.js index 0cecafe73440..fa53674ea67d 100644 --- a/services/static-webserver/client/source/class/osparc/wrapper/Svg.js +++ b/services/static-webserver/client/source/class/osparc/wrapper/Svg.js @@ -67,7 +67,7 @@ qx.Class.define("osparc.wrapper.Svg", { drawCurve: function(draw, controls, dashed) { const edgeWidth = 3; const arrowSize = 4; - const edgeColor = qx.theme.manager.Color.getInstance().getTheme().colors["workbench-edge-comp-active"]; + const edgeColor = qx.theme.manager.Color.getInstance().getTheme().colors["workbench-edge"]; osparc.wrapper.Svg.curateCurveControls(controls); @@ -391,7 +391,7 @@ qx.Class.define("osparc.wrapper.Svg", { /* / ANNOTATIONS */ drawDashedRect: function(draw, width, height, x, y) { - const edgeColor = qx.theme.manager.Color.getInstance().getTheme().colors["workbench-edge-comp-active"]; + const edgeColor = qx.theme.manager.Color.getInstance().getTheme().colors["workbench-edge"]; const rect = draw.rect(width, height) .fill("none") .stroke({