diff --git a/services/web/client/source/class/osparc/component/form/PortInfoHint.js b/services/web/client/source/class/osparc/component/form/PortInfoHint.js new file mode 100644 index 00000000000..1068a445436 --- /dev/null +++ b/services/web/client/source/class/osparc/component/form/PortInfoHint.js @@ -0,0 +1,48 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2022 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.component.form.PortInfoHint", { + extend: osparc.ui.hint.InfoHint, + + statics: { + ERROR_ICON: "@MaterialIcons/error_outline/14" + }, + + properties: { + portErrorMsg: { + check: "String", + init: null, + nullable: true, + apply: "__applyPortErrorMsg" + } + }, + + members: { + __applyPortErrorMsg: function(errorMsg) { + let text = this.getHintText(); + if (errorMsg) { + const color = qx.theme.manager.Color.getInstance().resolve("failed-red"); + text += `

${errorMsg}`; + } + this._hint.setText(text); + this.set({ + source: errorMsg ? this.self().ERROR_ICON : osparc.ui.hint.InfoHint.INFO_ICON, + textColor: errorMsg ? "failed-red" : "text" + }); + } + } +}); diff --git a/services/web/client/source/class/osparc/component/form/renderer/PropForm.js b/services/web/client/source/class/osparc/component/form/renderer/PropForm.js index 13a0876b65c..09a273cc048 100644 --- a/services/web/client/source/class/osparc/component/form/renderer/PropForm.js +++ b/services/web/client/source/class/osparc/component/form/renderer/PropForm.js @@ -382,6 +382,14 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { this.fireEvent("changeChildVisibility"); }, + setPortErrorMessage: function(portId, msg) { + const infoButton = this._getInfoFieldChild(portId); + if (infoButton && "child" in infoButton) { + const infoHint = infoButton.child; + infoHint.setPortErrorMsg(msg); + } + }, + retrievingPortData: function(portId) { const status = this._retrieveStatus.retrieving; if (portId) { diff --git a/services/web/client/source/class/osparc/component/form/renderer/PropFormBase.js b/services/web/client/source/class/osparc/component/form/renderer/PropFormBase.js index cb455ffdd97..f7f5f4de392 100644 --- a/services/web/client/source/class/osparc/component/form/renderer/PropFormBase.js +++ b/services/web/client/source/class/osparc/component/form/renderer/PropFormBase.js @@ -244,7 +244,7 @@ qx.Class.define("osparc.component.form.renderer.PropFormBase", { }, _createInfoWHint: function(hint) { - const infoWHint = new osparc.ui.hint.InfoHint(hint); + const infoWHint = new osparc.component.form.PortInfoHint(hint); return infoWHint; }, @@ -328,6 +328,10 @@ qx.Class.define("osparc.component.form.renderer.PropFormBase", { return this._getLayoutChild(portId, this.self().GRID_POS.LABEL); }, + _getInfoFieldChild: function(portId) { + return this._getLayoutChild(portId, this.self().GRID_POS.INFO); + }, + _getCtrlFieldChild: function(portId) { return this._getLayoutChild(portId, this.self().GRID_POS.CTRL_FIELD); }, diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js index 08f043bef3e..a8fe1e36582 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -150,6 +150,14 @@ qx.Class.define("osparc.data.model.Node", { nullable: false }, + errors: { + check: "Array", + init: [], + nullable: true, + event: "changeErrors", + apply: "__applyErrors" + }, + // GUI elements // propsForm: { check: "osparc.component.form.renderer.PropForm", @@ -712,29 +720,41 @@ qx.Class.define("osparc.data.model.Node", { return outputsData; }, - setErrors: function(errors) { - errors.forEach(error => { - const loc = error["loc"]; - if (loc.length < 2) { - return; - } - if (loc[1] === this.getNodeId()) { - const errorMsgData = { - nodeId: this.getNodeId(), - msg: error["msg"], - level: "ERROR" - }; - if (loc.length > 2) { - const portKey = loc[2]; - if ("inputs" in this.getMetaData() && portKey in this.getMetaData()["inputs"]) { - errorMsgData["msg"] = this.getMetaData()["inputs"][portKey]["label"] + ": " + errorMsgData["msg"]; - } else { - errorMsgData["msg"] = portKey + ": " + errorMsgData["msg"]; + __applyErrors: function(errors) { + if (errors && errors.length) { + errors.forEach(error => { + const loc = error["loc"]; + if (loc.length < 2) { + return; + } + if (loc[1] === this.getNodeId()) { + const errorMsgData = { + nodeId: this.getNodeId(), + msg: error["msg"], + level: "ERROR" + }; + + // errors to port + if (loc.length > 2) { + const portKey = loc[2]; + if (this.hasInputs() && portKey in this.getMetaData()["inputs"]) { + errorMsgData["msg"] = this.getMetaData()["inputs"][portKey]["label"] + ": " + errorMsgData["msg"]; + } else { + errorMsgData["msg"] = portKey + ": " + errorMsgData["msg"]; + } + this.getPropsForm().setPortErrorMessage(portKey, errorMsgData["msg"]); } + + // errors to logger + this.fireDataEvent("showInLogger", errorMsgData); } - this.fireDataEvent("showInLogger", errorMsgData); - } - }); + }); + } else if (this.hasInputs()) { + // reset port errors + Object.keys(this.getMetaData()["inputs"]).forEach(portKey => { + this.getPropsForm().setPortErrorMessage(portKey, null); + }); + } }, // post edge creation routine diff --git a/services/web/client/source/class/osparc/data/model/Study.js b/services/web/client/source/class/osparc/data/model/Study.js index 9fca6868e7f..8fa66a0f0eb 100644 --- a/services/web/client/source/class/osparc/data/model/Study.js +++ b/services/web/client/source/class/osparc/data/model/Study.js @@ -375,9 +375,9 @@ qx.Class.define("osparc.data.model.Study", { } if (node && "errors" in nodeUpdatedData) { const errors = nodeUpdatedData["errors"]; - if (errors && errors.length) { - node.setErrors(errors); - } + node.setErrors(errors); + } else { + node.setErrors([]); } }, diff --git a/services/web/client/source/class/osparc/navigation/PrevNextButtons.js b/services/web/client/source/class/osparc/navigation/PrevNextButtons.js index b5a3b4f6ec4..cf80868bcf6 100644 --- a/services/web/client/source/class/osparc/navigation/PrevNextButtons.js +++ b/services/web/client/source/class/osparc/navigation/PrevNextButtons.js @@ -40,7 +40,7 @@ qx.Class.define("osparc.navigation.PrevNextButtons", { NEXT_BUTTON: "@FontAwesome5Solid/arrow-right/32", RUN_BUTTON: "@FontAwesome5Solid/play/32", BUSY_BUTTON: "@FontAwesome5Solid/circle-notch/32", - SELECT_FILE_BUTTON: "@FontAwesome5Solid/arrow-right/32" + SELECT_FILE_BUTTON: "@FontAwesome5Solid/file-medical/32" }, properties: { diff --git a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js index 303c5c4aaca..fb15a827d9d 100644 --- a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js +++ b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js @@ -70,6 +70,15 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { } else { this.__setupBlank(); } + node.bind("errors", this, "toolTipText", { + converter: errors => { + let errorsText = ""; + if (errors) { + errors.forEach(error => errorsText += error["msg"] + "
"); + } + return errorsText; + } + }); }, __setupComputational: function() { diff --git a/services/web/client/source/class/osparc/ui/hint/InfoHint.js b/services/web/client/source/class/osparc/ui/hint/InfoHint.js index 3deec732d72..cf3ad6e260c 100644 --- a/services/web/client/source/class/osparc/ui/hint/InfoHint.js +++ b/services/web/client/source/class/osparc/ui/hint/InfoHint.js @@ -25,7 +25,9 @@ qx.Class.define("osparc.ui.hint.InfoHint", { * @extends osparc.ui.basic.IconButton */ construct: function(hint) { - this.base(arguments, "@MaterialIcons/info_outline/14"); + this.base(arguments, this.self().INFO_ICON); + + this.__createHint(); this.bind("hintText", this, "visibility", { converter: hintText => (hintText && hintText !== "") ? "visible" : "excluded" @@ -36,6 +38,10 @@ qx.Class.define("osparc.ui.hint.InfoHint", { } }, + statics: { + INFO_ICON: "@MaterialIcons/info_outline/14" + }, + properties: { hintText: { check: "String", @@ -46,35 +52,39 @@ qx.Class.define("osparc.ui.hint.InfoHint", { }, members: { - __applyHintText: function(hintText) { - if (hintText && hintText !== "") { - const hint = new osparc.ui.hint.Hint(this, hintText).set({ - active: false - }); - - const showHint = () => hint.show(); - const hideHint = () => hint.exclude(); - - // Make hint "modal" when info button is clicked - const tapListener = event => { - if (osparc.utils.Utils.isMouseOnElement(hint, event)) { - return; - } - hideHint(); - document.removeEventListener("mousedown", tapListener); - this.addListener("mouseover", showHint); - this.addListener("mouseout", hideHint); - }; + _hint: null, + + __createHint: function() { + const hint = this._hint = new osparc.ui.hint.Hint(this).set({ + active: false + }); + const showHint = () => hint.show(); + const hideHint = () => hint.exclude(); + + // Make hint "modal" when info button is clicked + const tapListener = event => { + if (osparc.utils.Utils.isMouseOnElement(hint, event)) { + return; + } + hideHint(); + document.removeEventListener("mousedown", tapListener); this.addListener("mouseover", showHint); this.addListener("mouseout", hideHint); - this.addListener("tap", () => { - showHint(); - document.addEventListener("mousedown", tapListener); - this.removeListener("mouseover", showHint); - this.removeListener("mouseout", hideHint); - }, this); - } + }; + + this.addListener("mouseover", showHint); + this.addListener("mouseout", hideHint); + this.addListener("tap", () => { + showHint(); + document.addEventListener("mousedown", tapListener); + this.removeListener("mouseover", showHint); + this.removeListener("mouseout", hideHint); + }, this); + }, + + __applyHintText: function(hintText) { + this._hint.setText(hintText); } } });