From 68a981da71dd8d124827c24058be24cd32f24f4a Mon Sep 17 00:00:00 2001 From: Odei Maiz <33152403+odeimaiz@users.noreply.github.com> Date: Sat, 9 Apr 2022 11:44:09 +0200 Subject: [PATCH] Metamodeling II + Markers (#2966) --- .../schemas/project-v0.0.1-converted.yaml | 12 ++ api/specs/common/schemas/project-v0.0.1.json | 17 ++ .../src/models_library/projects_nodes_ui.py | 8 + .../src/models_library/projects_ui.py | 3 +- .../tests/unit/test_db_comp_tasks.py | 1 + .../api/v0/schemas/project-v0.0.1.json | 17 ++ .../api/v0/openapi.yaml | 12 ++ .../api/v0/schemas/project-v0.0.1.json | 17 ++ .../component/editor/AnnotationEditor.js | 71 ++++++- .../component/form/renderer/PropForm.js | 13 +- .../osparc/component/widget/NodeOutputs.js | 50 ++++- .../osparc/component/widget/NodeTreeItem.js | 167 ++++++++++++---- .../osparc/component/widget/NodesTree.js | 182 ++++++------------ .../component/widget/StudyTitleOnlyTree.js | 35 +++- .../component/widget/logger/LoggerView.js | 4 +- .../osparc/component/workbench/BaseNodeUI.js | 19 +- .../osparc/component/workbench/NodeUI.js | 57 +++++- .../osparc/component/workbench/WorkbenchUI.js | 13 +- .../source/class/osparc/data/model/Node.js | 25 +++ .../source/class/osparc/data/model/Study.js | 4 + .../class/osparc/data/model/Workbench.js | 56 +++++- .../class/osparc/desktop/WorkbenchView.js | 9 +- .../source/class/osparc/theme/Appearance.js | 8 +- .../class/osparc/ui/message/FlashMessage.js | 44 +++-- .../client/source/class/osparc/utils/Ports.js | 8 +- .../source/class/osparc/utils/Services.js | 6 +- .../client/source/class/osparc/utils/Utils.js | 14 +- .../api/v0/openapi.yaml | 12 ++ .../api/v0/schemas/project-v0.0.1.json | 17 ++ 29 files changed, 670 insertions(+), 231 deletions(-) diff --git a/api/specs/common/schemas/project-v0.0.1-converted.yaml b/api/specs/common/schemas/project-v0.0.1-converted.yaml index 8204f678497..8fbb2ec5e31 100644 --- a/api/specs/common/schemas/project-v0.0.1-converted.yaml +++ b/api/specs/common/schemas/project-v0.0.1-converted.yaml @@ -378,6 +378,18 @@ properties: description: The y position example: - '15' + marker: + type: object + additionalProperties: false + required: + - color + properties: + color: + type: string + description: Marker's color + example: + - '#FF0000' + - '#0000FF' additionalProperties: true slideshow: type: object diff --git a/api/specs/common/schemas/project-v0.0.1.json b/api/specs/common/schemas/project-v0.0.1.json index 2b9250b87d5..551f065314d 100644 --- a/api/specs/common/schemas/project-v0.0.1.json +++ b/api/specs/common/schemas/project-v0.0.1.json @@ -513,6 +513,23 @@ ] } } + }, + "marker": { + "type": "object", + "additionalProperties": false, + "required": [ + "color" + ], + "properties": { + "color": { + "type": "string", + "description": "Marker's color", + "examples": [ + "#FF0000", + "#0000FF" + ] + } + } } } } diff --git a/packages/models-library/src/models_library/projects_nodes_ui.py b/packages/models-library/src/models_library/projects_nodes_ui.py index d8ca77f1f98..aa55332ccba 100644 --- a/packages/models-library/src/models_library/projects_nodes_ui.py +++ b/packages/models-library/src/models_library/projects_nodes_ui.py @@ -3,6 +3,7 @@ """ from pydantic import BaseModel, Extra, Field +from pydantic.color import Color class Position(BaseModel): @@ -11,3 +12,10 @@ class Position(BaseModel): class Config: extra = Extra.forbid + + +class Marker(BaseModel): + color: Color = Field(...) + + class Config: + extra = Extra.forbid diff --git a/packages/models-library/src/models_library/projects_ui.py b/packages/models-library/src/models_library/projects_ui.py index 59405508464..86bd0df9ec9 100644 --- a/packages/models-library/src/models_library/projects_ui.py +++ b/packages/models-library/src/models_library/projects_ui.py @@ -8,11 +8,12 @@ from pydantic.color import Color from .projects_nodes_io import NodeID, NodeIDStr -from .projects_nodes_ui import Position +from .projects_nodes_ui import Marker, Position class WorkbenchUI(BaseModel): position: Position = Field(..., description="The node position in the workbench") + marker: Optional[Marker] = None class Config: extra = Extra.forbid diff --git a/services/director-v2/tests/unit/test_db_comp_tasks.py b/services/director-v2/tests/unit/test_db_comp_tasks.py index 8cad85397e0..81d9537903d 100644 --- a/services/director-v2/tests/unit/test_db_comp_tasks.py +++ b/services/director-v2/tests/unit/test_db_comp_tasks.py @@ -60,6 +60,7 @@ def test_only_filepicker_service_gets_some_service_details( "simcore/services/frontend/nodes-group/macros/", # FIXME: PC->OM: This front-end service needs to be re-defined "simcore/services/frontend/nodes-group", "simcore/services/frontend/parameter/", + "simcore/services/frontend/iterator-consumer/probe/", ] for frontend_service_key in all_frontend_services: if frontend_service_key in EXCLUDE: diff --git a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json index 2b9250b87d5..551f065314d 100644 --- a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json +++ b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json @@ -513,6 +513,23 @@ ] } } + }, + "marker": { + "type": "object", + "additionalProperties": false, + "required": [ + "color" + ], + "properties": { + "color": { + "type": "string", + "description": "Marker's color", + "examples": [ + "#FF0000", + "#0000FF" + ] + } + } } } } diff --git a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml index 8e1506586fc..be06e693867 100644 --- a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml @@ -1182,6 +1182,18 @@ components: description: The y position example: - '15' + marker: + type: object + additionalProperties: false + required: + - color + properties: + color: + type: string + description: Marker's color + example: + - '#FF0000' + - '#0000FF' additionalProperties: true slideshow: type: object diff --git a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json index 2b9250b87d5..551f065314d 100644 --- a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json +++ b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json @@ -513,6 +513,23 @@ ] } } + }, + "marker": { + "type": "object", + "additionalProperties": false, + "required": [ + "color" + ], + "properties": { + "color": { + "type": "string", + "description": "Marker's color", + "examples": [ + "#FF0000", + "#0000FF" + ] + } + } } } } diff --git a/services/web/client/source/class/osparc/component/editor/AnnotationEditor.js b/services/web/client/source/class/osparc/component/editor/AnnotationEditor.js index 7ca0c33e7a9..31a1d208127 100644 --- a/services/web/client/source/class/osparc/component/editor/AnnotationEditor.js +++ b/services/web/client/source/class/osparc/component/editor/AnnotationEditor.js @@ -38,28 +38,45 @@ qx.Class.define("osparc.component.editor.AnnotationEditor", { properties: { annotation: { check: "osparc.component.workbench.Annotation", + init: null, + nullable: true, apply: "__applyAnnotation" + }, + + marker: { + check: "Object", + init: null, + nullable: true, + apply: "__applyMarker" } }, members: { - __applyAnnotation: function(annotation) { - this._removeAll(); - - let row = 0; + __addColor: function() { this._add(new qx.ui.basic.Label(this.tr("Color")), { - row, + row: 0, column: 0 }); const colorPicker = new osparc.component.form.ColorPicker(); - annotation.bind("color", colorPicker, "color"); - colorPicker.bind("color", annotation, "color"); this._add(colorPicker, { - row, + row: 0, column: 1 }); - row++; + return colorPicker; + }, + + __applyAnnotation: function(annotation) { + this._removeAll(); + + if (annotation === null) { + return; + } + const colorPicker = this.__addColor(); + annotation.bind("color", colorPicker, "color"); + colorPicker.bind("color", annotation, "color"); + + let row = 1; if (annotation.getType() === "text") { const attrs = annotation.getAttributes(); this._add(new qx.ui.basic.Label(this.tr("Text")), { @@ -86,6 +103,42 @@ qx.Class.define("osparc.component.editor.AnnotationEditor", { }); row++; } + + this.__makeItModal(); + }, + + __applyMarker: function(marker) { + this._removeAll(); + + if (marker === null) { + return; + } + + const colorPicker = this.__addColor(); + marker.bind("color", colorPicker, "color"); + colorPicker.bind("color", marker, "color"); + + this.__makeItModal(); + }, + + __makeItModal: function() { + this.show(); + + const showHint = () => this.show(); + const hideHint = () => this.exclude(); + const tapListener = event => { + if (osparc.utils.Utils.isMouseOnElement(this, event)) { + return; + } + hideHint(); + this.set({ + annotation: null, + marker: null + }); + document.removeEventListener("mousedown", tapListener); + }; + showHint(); + document.addEventListener("mousedown", tapListener); } } }); 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 88d2f403a92..13a0876b65c 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 @@ -746,7 +746,8 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { if (!this.__isPortAvailable(toPortId)) { return false; } - this.getControlLink(toPortId).setEnabled(false); + const ctrlLink = this.getControlLink(toPortId); + ctrlLink.setEnabled(false); this._form.getControl(toPortId)["link"] = { nodeUuid: fromNodeId, output: fromPortId @@ -756,9 +757,17 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { const fromNode = workbench.getNode(fromNodeId); const port = fromNode.getOutput(fromPortId); const fromPortLabel = port ? port.label : null; - fromNode.bind("label", this.getControlLink(toPortId), "value", { + fromNode.bind("label", ctrlLink, "value", { converter: label => label + ": " + fromPortLabel }); + // Hack: Show tooltip if element is disabled + const addToolTip = () => { + ctrlLink.getContentElement().removeAttribute("title"); + const toolTipText = fromNode.getLabel() + ":\n" + fromPortLabel; + ctrlLink.getContentElement().setAttribute("title", toolTipText); + }; + fromNode.addListener("changeLabel", () => addToolTip()); + addToolTip(); this.__portLinkAdded(toPortId, fromNodeId, fromPortId); diff --git a/services/web/client/source/class/osparc/component/widget/NodeOutputs.js b/services/web/client/source/class/osparc/component/widget/NodeOutputs.js index 941472e4181..de690328909 100644 --- a/services/web/client/source/class/osparc/component/widget/NodeOutputs.js +++ b/services/web/client/source/class/osparc/component/widget/NodeOutputs.js @@ -34,10 +34,13 @@ qx.Class.define("osparc.component.widget.NodeOutputs", { construct: function(node, ports) { this.base(arguments); - const layout = new qx.ui.layout.Grid(5, 5); - layout.setColumnFlex(1, 1); - layout.setColumnMaxWidth(1, 130); - this._setLayout(layout); + const grid = new qx.ui.layout.Grid(5, 5); + grid.setColumnMaxWidth(this.self().POS.NAME, 140); + grid.setColumnFlex(this.self().POS.VALUE, 1); + Object.keys(this.self().POS).forEach((_, idx) => { + grid.setColumnAlign(idx, "left", "middle"); + }); + this._setLayout(grid); this.set({ node, @@ -56,9 +59,19 @@ qx.Class.define("osparc.component.widget.NodeOutputs", { ports: { nullable: false, apply: "__populateLayout" + }, + + offerProbes: { + check: "Boolean", + init: false, + event: "changeOfferProbes" } }, + events: { + "probeRequested": "qx.event.type.Data" + }, + statics: { POS: { KEY: { @@ -78,6 +91,9 @@ qx.Class.define("osparc.component.widget.NodeOutputs", { }, UNIT: { col: 5 + }, + PROBE: { + col: 6 } } }, @@ -86,9 +102,11 @@ qx.Class.define("osparc.component.widget.NodeOutputs", { __populateLayout: function() { this._removeAll(); - const ports = Object.values(this.getPorts()); - for (let i=0; i val ? "visible" : "excluded" + }); + probeBtn.addListener("execute", () => this.getNode().fireDataEvent("probeRequested", { + portId: portKey, + nodeId: this.getNode().getNodeId() + })); + this._add(probeBtn, { + row: i, + column: this.self().POS.PROBE.col + }); } } } diff --git a/services/web/client/source/class/osparc/component/widget/NodeTreeItem.js b/services/web/client/source/class/osparc/component/widget/NodeTreeItem.js index abf3957c5a0..fda5afb4f12 100644 --- a/services/web/client/source/class/osparc/component/widget/NodeTreeItem.js +++ b/services/web/client/source/class/osparc/component/widget/NodeTreeItem.js @@ -44,23 +44,42 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { this.base(arguments); this.set({ - indent: 8, + indent: 6, + allowGrowX: true, alignY: "middle" }); this.getContentElement().setStyles({ - "border-radius": "6px" + "border-radius": "4px" }); this.__setNotHoveredStyle(); this.__attachEventHandlers(); + + osparc.utils.Utils.setIdToWidget(this, "nodeTreeItem"); }, properties: { - nodeId : { - check : "String", - event: "changeNodeId", - apply: "__applyNodeId", - nullable : true + study: { + check: "osparc.data.model.Study", + init: null, + nullable: false, + apply: "__applyStudy", + event: "changeStudy" + }, + + node: { + check: "osparc.data.model.Node", + init: null, + nullable: false, + apply: "__applyNode", + event: "changeNode" + }, + + id: { + check: "String", + init: null, + nullable: false, + event: "changeId" } }, @@ -74,11 +93,77 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { members: { __optionsMenu: null, + __applyStudy: function(study) { + osparc.utils.Utils.setMoreToWidget(this, "root"); + + this.setIcon("@FontAwesome5Solid/home/14"); + study.bind("name", this, "label"); + this.getChildControl("delete-button").exclude(); + }, + + __applyNode: function(node) { + osparc.utils.Utils.setMoreToWidget(this, node.getNodeId()); + + if (node.isFilePicker()) { + const icon = osparc.utils.Services.getIcon("file"); + this.setIcon(icon+"14"); + } else if (node.isParameter()) { + const icon = osparc.utils.Services.getIcon("parameter"); + this.setIcon(icon+"14"); + } else if (node.isIterator()) { + const icon = osparc.utils.Services.getIcon("iterator"); + this.setIcon(icon+"14"); + } else if (node.isProbe()) { + const icon = osparc.utils.Services.getIcon("probe"); + this.setIcon(icon+"14"); + } else { + const icon = osparc.utils.Services.getIcon(node.getMetaData().type); + if (icon) { + this.setIcon(icon+"14"); + } + } + + // "bind" running/interactive status to icon color + if (node.isDynamic()) { + node.getStatus().bind("interactive", this.getChildControl("icon"), "textColor", { + converter: status => osparc.utils.StatusUI.getColor(status) + }); + } else if (node.isComputational()) { + node.getStatus().bind("running", this.getChildControl("icon"), "textColor", { + converter: status => osparc.utils.StatusUI.getColor(status) + }); + } + + node.bind("label", this, "label"); + + if (node.isDynamic()) { + this.getChildControl("fullscreen-button").show(); + } + + const markerBtn = this.getChildControl("marker-button"); + markerBtn.show(); + node.bind("marker", markerBtn, "label", { + converter: val => val ? this.tr("Remove Marker") : this.tr("Add Marker") + }); + + const marker = this.getChildControl("marker"); + const updateMarker = () => { + node.bind("marker", marker, "visibility", { + converter: val => val ? "visible" : "excluded" + }); + if (node.getMarker()) { + node.getMarker().bind("color", marker, "textColor"); + } + }; + node.addListener("changeMarker", () => updateMarker()); + updateMarker(); + }, + _createChildControlImpl: function(id) { let control; switch (id) { case "buttons": { - control = new qx.ui.container.Composite(new qx.ui.layout.HBox(0).set({ + control = new qx.ui.container.Composite(new qx.ui.layout.HBox().set({ alignY: "middle" })); control.exclude(); @@ -93,7 +178,7 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { alignY: "middle", visibility: "excluded" }); - control.addListener("execute", () => this.fireDataEvent("fullscreenNode", this.getNodeId())); + control.addListener("execute", () => this.fireDataEvent("fullscreenNode", this.getId())); const part = this.getChildControl("buttons"); part.add(control); break; @@ -112,33 +197,48 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { part.add(control); break; } - case "options-rename-button": { + case "rename-button": { control = new qx.ui.menu.Button().set({ label: this.tr("Rename"), icon: "@FontAwesome5Solid/i-cursor/10" }); - control.addListener("execute", () => this.fireDataEvent("renameNode", this.getNodeId())); + control.addListener("execute", () => this.fireDataEvent("renameNode", this.getId())); + const optionsMenu = this.getChildControl("options-menu-button"); + optionsMenu.getMenu().add(control); + break; + } + case "marker-button": { + control = new qx.ui.menu.Button().set({ + icon: "@FontAwesome5Solid/bookmark/10", + visibility: "excluded" + }); + control.addListener("execute", () => { + if (this.getNode().getMarker()) { + this.getNode().removeMarker(); + } else { + this.getNode().addMarker(); + } + }); const optionsMenu = this.getChildControl("options-menu-button"); optionsMenu.getMenu().add(control); break; } - case "options-info-button": { + case "info-button": { control = new qx.ui.menu.Button().set({ - label: this.tr("Information"), + label: this.tr("Information..."), icon: "@FontAwesome5Solid/info/10" }); - control.addListener("execute", () => this.fireDataEvent("infoNode", this.getNodeId())); + control.addListener("execute", () => this.fireDataEvent("infoNode", this.getId())); const optionsMenu = this.getChildControl("options-menu-button"); optionsMenu.getMenu().add(control); break; } - case "options-delete-button": { + case "delete-button": { control = new qx.ui.menu.Button().set({ - appearance: "danger-button", label: this.tr("Delete"), icon: "@FontAwesome5Solid/trash/10" }); - control.addListener("execute", () => this.fireDataEvent("deleteNode", this.getNodeId())); + control.addListener("execute", () => this.fireDataEvent("deleteNode", this.getId())); const optionsMenu = this.getChildControl("options-menu-button"); optionsMenu.getMenu().add(control); break; @@ -149,8 +249,8 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { alignY: "middle", cursor: "copy" }); - control.addListener("tap", () => osparc.utils.Utils.copyTextToClipboard(this.getNodeId())); - this.bind("nodeId", control, "value", { + control.addListener("tap", () => osparc.utils.Utils.copyTextToClipboard(this.getId())); + this.bind("id", control, "value", { converter: value => value && value.substring(0, 8) }); const permissions = osparc.data.Permissions.getInstance(); @@ -158,7 +258,16 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { converter: () => permissions.canDo("study.nodestree.uuid.read") ? "visible" : "excluded" }); this.addWidget(control); + break; } + case "marker": + control = new qx.ui.basic.Image().set({ + source: "@FontAwesome5Solid/bookmark/12", + padding: 4, + visibility: "excluded" + }); + this.addWidget(control); + break; } return control || this.base(arguments, id); @@ -189,20 +298,12 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { }); this.getChildControl("fullscreen-button"); - this.getChildControl("options-rename-button"); - this.getChildControl("options-info-button"); - this.getChildControl("options-delete-button"); + this.getChildControl("rename-button"); + this.getChildControl("marker-button"); + this.getChildControl("info-button"); + this.getChildControl("delete-button"); this.getChildControl("node-id"); - }, - - __applyNodeId: function(nodeId) { - const study = osparc.store.Store.getInstance().getCurrentStudy(); - osparc.utils.Utils.setIdToWidget(this, "nodeTreeItem"); - if (nodeId === study.getUuid()) { - osparc.utils.Utils.setMoreToWidget(this, "root"); - } else { - osparc.utils.Utils.setMoreToWidget(this, nodeId); - } + this.getChildControl("marker"); }, __attachEventHandlers: function() { @@ -233,7 +334,7 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { }, __setNotHoveredStyle: function() { - osparc.utils.Utils.removeBorder(this); + osparc.utils.Utils.hideBorder(this); } } }); diff --git a/services/web/client/source/class/osparc/component/widget/NodesTree.js b/services/web/client/source/class/osparc/component/widget/NodesTree.js index 138e9e42db2..fab422db33e 100644 --- a/services/web/client/source/class/osparc/component/widget/NodesTree.js +++ b/services/web/client/source/class/osparc/component/widget/NodesTree.js @@ -41,6 +41,7 @@ qx.Class.define("osparc.component.widget.NodesTree", { this.set({ decorator: "service-tree", + hideRoot: true, openMode: "none", contentPadding: 0, padding: 0 @@ -78,17 +79,17 @@ qx.Class.define("osparc.component.widget.NodesTree", { return osparc.utils.Services.getSorting(node.getMetaData().type); }, - convertModel: function(nodes) { + nodesToModel: function(nodes) { const children = []; for (let nodeId in nodes) { const node = nodes[nodeId]; const nodeInTree = { label: node.getLabel(), - children: node.isContainer() ? this.convertModel(node.getInnerNodes()) : [], - isContainer: node.isContainer(), - nodeId: node.getNodeId(), + children: [], sortingValue: this.self().getSortingValue(node), - statusColor: null + statusColor: null, + id: node.getNodeId(), + node }; children.push(nodeInTree); } @@ -99,141 +100,80 @@ qx.Class.define("osparc.component.widget.NodesTree", { members: { __currentNodeId: null, - _applyStudy: function() { - this.populateTree(); - }, - getCurrentNodeId: function() { return this.__currentNodeId; }, - setCurrentNodeId: function(nodeId) { this.__currentNodeId = nodeId; }, - __getOneSelectedRow: function() { - const selection = this.getSelection(); - if (selection && selection.toArray().length > 0) { - return selection.toArray()[0]; - } - return null; + _applyStudy: function() { + this.populateTree(); }, populateTree: function() { - const data = this.__getModelData(); - let newModel = qx.data.marshal.Json.createModel(data, true); - let oldModel = this.getModel(); - if (JSON.stringify(newModel) !== JSON.stringify(oldModel)) { - const study = this.getStudy(); - study.bind("name", newModel, "label"); - this.setModel(newModel); - this.setDelegate({ - createItem: () => { - const nodeTreeItem = new osparc.component.widget.NodeTreeItem(); - nodeTreeItem.addListener("fullscreenNode", e => this.__openFullscreen(e.getData())); - nodeTreeItem.addListener("renameNode", e => this.__openItemRenamer(e.getData())); - nodeTreeItem.addListener("infoNode", e => this.__openNodeInfo(e.getData())); - nodeTreeItem.addListener("deleteNode", e => this.__deleteNode(e.getData())); - return nodeTreeItem; - }, - bindItem: (c, item, id) => { - c.bindDefaultProperties(item, id); - c.bindProperty("nodeId", "nodeId", null, item, id); - c.bindProperty("label", "label", null, item, id); - const node = study.getWorkbench().getNode(item.getModel().getNodeId()); - if (item.getModel().getNodeId() === study.getUuid()) { - item.setIcon("@FontAwesome5Solid/home/14"); - item.getChildControl("options-delete-button").exclude(); - } else if (node) { - node.bind("label", item.getModel(), "label"); - - // set icon - if (node.isFilePicker()) { - const icon = osparc.utils.Services.getIcon("file"); - item.setIcon(icon+"14"); - } else if (node.isParameter()) { - const icon = osparc.utils.Services.getIcon("parameter"); - item.setIcon(icon+"14"); - } else if (node.isIterator()) { - const icon = osparc.utils.Services.getIcon("iterator"); - item.setIcon(icon+"14"); - } else if (node.isProbe()) { - const icon = osparc.utils.Services.getIcon("probe"); - item.setIcon(icon+"14"); - } else { - const icon = osparc.utils.Services.getIcon(node.getMetaData().type); - if (icon) { - item.setIcon(icon+"14"); - } - } - - // "bind" running/interactive status to icon color - if (node.isDynamic()) { - node.getStatus().addListener("changeInteractive", e => this.__updateColor(node.getNodeId(), e.getData())); - this.__updateColor(node.getNodeId(), node.getStatus().getInteractive()); - } else if (node.isComputational()) { - node.getStatus().addListener("changeRunning", e => this.__updateColor(node.getNodeId(), e.getData())); - this.__updateColor(node.getNodeId(), node.getStatus().getRunning()); - } - c.bindProperty("statusColor", "textColor", { - converter: statusColor => osparc.utils.StatusUI.getColor(statusColor) - }, item.getChildControl("icon"), id); - - // add fullscreen - if (node.isDynamic()) { - item.getChildControl("fullscreen-button").show(); - } - - node.addListener("keyChanged", () => this.populateTree(), this); - } - }, - configureItem: item => { - item.addListener("tap", () => { - this.__openItem(item.getModel().getNodeId()); - this.nodeSelected(item.getModel().getNodeId()); - }, this); - // This is needed to keep the label flexible - item.addListener("resize", e => item.setMaxWidth(100), this); - }, - sorter: (itemA, itemB) => itemA.getSortingValue() - itemB.getSortingValue() - }); - const nChildren = newModel.getChildren().length; - this.setHeight(nChildren*21 + 12); - } - }, - - __getModelData: function() { + const data = this.__getNodesModelData(); + const newModel = qx.data.marshal.Json.createModel(data, true); + this.setModel(newModel); const study = this.getStudy(); - const topLevelNodes = study.getWorkbench().getNodes(); - const data = this.__getRootModelData(); - data.children = this.self().convertModel(topLevelNodes); - return data; + this.setDelegate(this._getDelegate(study)); + const nChildren = newModel.getChildren().length; + this.setHeight(nChildren*21 + 12); }, - __getRootModelData: function() { + __getNodesModelData: function() { const study = this.getStudy(); + const nodes = study.getWorkbench().getNodes(); const data = { - label: study.getName(), - children: [], - isContainer: true, - nodeId: study.getUuid(), - sortingValue: 0 + label: "Study", + children: this.self().nodesToModel(nodes), + sortingValue: 0, + id: study.getUuid() }; return data; }, - __updateColor: function(nodeId, status) { - const nodeInTree = this.__getNodeModel(this.getModel(), nodeId); - if (nodeInTree) { - nodeInTree.setStatusColor(status); - } + _getDelegate: function(study) { + return { + createItem: () => { + const nodeTreeItem = new osparc.component.widget.NodeTreeItem(); + nodeTreeItem.addListener("fullscreenNode", e => this.__openFullscreen(e.getData())); + nodeTreeItem.addListener("renameNode", e => this._openItemRenamer(e.getData())); + nodeTreeItem.addListener("infoNode", e => this.__openNodeInfo(e.getData())); + nodeTreeItem.addListener("deleteNode", e => this.__deleteNode(e.getData())); + return nodeTreeItem; + }, + bindItem: (c, item, id) => { + console.log(item.getModel()); + c.bindDefaultProperties(item, id); + c.bindProperty("label", "label", null, item, id); + c.bindProperty("id", "id", null, item, id); + c.bindProperty("study", "study", null, item, id); + c.bindProperty("node", "node", null, item, id); + const node = study.getWorkbench().getNode(item.getModel().getId()); + if (item.getModel().getId() === study.getUuid()) { + item.getChildControl("delete-button").exclude(); + } else if (node) { + node.addListener("keyChanged", () => this.populateTree(), this); + } + }, + configureItem: item => { + item.addListener("tap", () => { + this.__openItem(item.getModel().getId()); + this.nodeSelected(item.getModel().getId()); + }, this); + // This is needed to keep the label flexible + item.addListener("resize", e => item.setMaxWidth(100), this); + }, + sorter: (itemA, itemB) => itemA.getSortingValue() - itemB.getSortingValue() + }; }, __getNodeModel: function(model, nodeId) { - if (model.getNodeId() === nodeId) { + if (model.getId() === nodeId) { return model; - } else if (model.getIsContainer() && model.getChildren() !== null) { + } else if (model.getChildren() !== null) { let node = null; let children = model.getChildren().toArray(); for (let i = 0; node === null && i < children.length; i++) { @@ -265,9 +205,9 @@ qx.Class.define("osparc.component.widget.NodesTree", { } }, - __openItemRenamer: function(nodeId) { + _openItemRenamer: function(nodeId) { if (nodeId === undefined && this.__getSelection()) { - nodeId = this.__getSelection().getNodeId(); + nodeId = this.__getSelection().getId(); } if (nodeId) { const study = this.getStudy(); @@ -293,7 +233,7 @@ qx.Class.define("osparc.component.widget.NodesTree", { __openNodeInfo: function(nodeId) { if (nodeId === undefined && this.__getSelection()) { - nodeId = this.__getSelection().getNodeId(); + nodeId = this.__getSelection().getId(); } if (nodeId) { const study = this.getStudy(); @@ -316,7 +256,7 @@ qx.Class.define("osparc.component.widget.NodesTree", { __deleteNode: function(nodeId) { if (nodeId === undefined && this.__getSelection()) { - nodeId = this.__getSelection().getNodeId(); + nodeId = this.__getSelection().getId(); } if (nodeId) { this.fireDataEvent("removeNode", nodeId); @@ -335,7 +275,7 @@ qx.Class.define("osparc.component.widget.NodesTree", { this.addListener("keypress", keyEvent => { switch (keyEvent.getKeyIdentifier()) { case "F2": - this.__openItemRenamer(); + this._openItemRenamer(); break; case "I": this.__openNodeInfo(); diff --git a/services/web/client/source/class/osparc/component/widget/StudyTitleOnlyTree.js b/services/web/client/source/class/osparc/component/widget/StudyTitleOnlyTree.js index ad16d5486db..4c08d91a2d9 100644 --- a/services/web/client/source/class/osparc/component/widget/StudyTitleOnlyTree.js +++ b/services/web/client/source/class/osparc/component/widget/StudyTitleOnlyTree.js @@ -18,21 +18,44 @@ qx.Class.define("osparc.component.widget.StudyTitleOnlyTree", { extend: osparc.component.widget.NodesTree, + construct: function() { + this.base(arguments, null, "label", "children"); + + this.set({ + hideRoot: false + }); + }, + members: { + // override populateTree: function() { - this.base(arguments); - this.getModel().getChildren().removeAll(); + const data = this.__getStudyModelData(); + const newModel = qx.data.marshal.Json.createModel(data, true); + this.setModel(newModel); + const study = this.getStudy(); this.setDelegate({ - ...this.getDelegate(), + ...this._getDelegate(study), createItem: () => { const studyTreeItem = new osparc.component.widget.NodeTreeItem(); - studyTreeItem.addListener("renameNode", e => this.__openItemRenamer(e.getData())); - studyTreeItem.addListener("infoNode", e => this.__openStudyInfo()); + studyTreeItem.addListener("renameNode", e => this._openItemRenamer(e.getData())); + studyTreeItem.addListener("infoNode", () => this.__openStudyInfo()); return studyTreeItem; } }); }, + __getStudyModelData: function() { + const study = this.getStudy(); + const data = { + label: study.getName(), + children: [], + sortingValue: 0, + id: study.getUuid(), + study + }; + return data; + }, + __openStudyInfo: function() { const studyDetails = new osparc.studycard.Large(this.getStudy()); const title = this.tr("Study Details"); @@ -43,7 +66,7 @@ qx.Class.define("osparc.component.widget.StudyTitleOnlyTree", { selectStudyItem: function() { this.setSelection(new qx.data.Array([this.getModel()])); - this.fireDataEvent("nodeSelected", this.getModel().getNodeId()); + this.fireDataEvent("nodeSelected", this.getModel().getId()); } } }); diff --git a/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js b/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js index 9a18febfe36..4627ed7cd07 100644 --- a/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js +++ b/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js @@ -202,9 +202,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", { toolbar.add(logLevelSelectBox); const copyToClipboardButton = this.getChildControl("copy-to-clipboard"); - copyToClipboardButton.addListener("execute", e => { - this.__copyLogsToClipboard(); - }, this); + copyToClipboardButton.addListener("execute", () => this.__copyLogsToClipboard(), this); toolbar.add(copyToClipboardButton); return toolbar; diff --git a/services/web/client/source/class/osparc/component/workbench/BaseNodeUI.js b/services/web/client/source/class/osparc/component/workbench/BaseNodeUI.js index 0e21b4b5712..567fec66fc3 100644 --- a/services/web/client/source/class/osparc/component/workbench/BaseNodeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/BaseNodeUI.js @@ -49,7 +49,7 @@ qx.Class.define("osparc.component.workbench.BaseNodeUI", { const menuBtn = this.__getMenuButton(); this.getChildControl("captionbar").add(menuBtn, { row: 0, - column: 2 + column: this.self().CAPTION_POS.MENU }); const captionTitle = this.getChildControl("title"); @@ -91,6 +91,14 @@ qx.Class.define("osparc.component.workbench.BaseNodeUI", { NODE_CONNECTED: "@FontAwesome5Regular/dot-circle/18", NODE_DISCONNECTED: "@FontAwesome5Regular/circle/18", + CAPTION_POS: { + ICON: 0, // from qooxdoo + TITLE: 1, // from qooxdoo + LOCK: 2, + MARKER: 3, + MENU: 4 + }, + captionHeight: function() { return osparc.theme.Appearance.appearances["window-small-cap/captionbar"].style().height || osparc.theme.Appearance.appearances["window-small-cap/captionbar"].style().minHeight; @@ -100,6 +108,7 @@ qx.Class.define("osparc.component.workbench.BaseNodeUI", { events: { "renameNode": "qx.event.type.Data", "infoNode": "qx.event.type.Data", + "markerClicked": "qx.event.type.Data", "removeNode": "qx.event.type.Data", "edgeDragStart": "qx.event.type.Data", "edgeDragOver": "qx.event.type.Data", @@ -135,8 +144,14 @@ qx.Class.define("osparc.component.workbench.BaseNodeUI", { renameBtn.addListener("execute", () => this.fireDataEvent("renameNode", this.getNodeId())); optionsMenu.add(renameBtn); + const markerBtn = this._markerBtn = new qx.ui.menu.Button().set({ + icon: "@FontAwesome5Solid/bookmark/10", + visibility: "excluded" + }); + optionsMenu.add(markerBtn); + const infoBtn = new qx.ui.menu.Button().set({ - label: this.tr("Information"), + label: this.tr("Information..."), icon: "@FontAwesome5Solid/info/10" }); infoBtn.addListener("execute", () => this.fireDataEvent("infoNode", this.getNodeId())); diff --git a/services/web/client/source/class/osparc/component/workbench/NodeUI.js b/services/web/client/source/class/osparc/component/workbench/NodeUI.js index 7ca86392c7c..d81b43ca9f2 100644 --- a/services/web/client/source/class/osparc/component/workbench/NodeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/NodeUI.js @@ -87,6 +87,29 @@ qx.Class.define("osparc.component.workbench.NodeUI", { _createChildControlImpl: function(id) { let control; switch (id) { + case "lock": + control = new qx.ui.basic.Image().set({ + source: "@FontAwesome5Solid/lock/12", + padding: 4, + visibility: "excluded" + }); + this.getChildControl("captionbar").add(control, { + row: 0, + column: osparc.component.workbench.BaseNodeUI.CAPTION_POS.LOCK + }); + break; + case "marker": + control = new qx.ui.basic.Image().set({ + source: "@FontAwesome5Solid/bookmark/12", + padding: 4, + visibility: "excluded" + }); + this.getChildControl("captionbar").add(control, { + row: 0, + column: osparc.component.workbench.BaseNodeUI.CAPTION_POS.MARKER + }); + control.addListener("tap", () => this.fireDataEvent("markerClicked", this.getNode().getNodeId())); + break; case "chips": { control = new qx.ui.container.Composite(new qx.ui.layout.Flow(3, 3).set({ alignY: "middle" @@ -201,6 +224,36 @@ qx.Class.define("osparc.component.workbench.NodeUI", { convertToParameter.addListener("execute", () => node.convertToParameter("int"), this); this._optionsMenu.add(convertToParameter); } + + const lock = this.getChildControl("lock"); + if (node.getPropsForm()) { + node.getPropsForm().bind("enabled", lock, "visibility", { + converter: val => val ? "excluded" : "visible" + }); + } + this._markerBtn.show(); + this.getNode().bind("marker", this._markerBtn, "label", { + converter: val => val ? this.tr("Remove Marker") : this.tr("Add Marker") + }); + this._markerBtn.addListener("execute", () => { + if (this.getNode().getMarker()) { + this.getNode().removeMarker(); + } else { + this.getNode().addMarker(); + } + }); + + const marker = this.getChildControl("marker"); + const updateMarker = () => { + node.bind("marker", marker, "visibility", { + converter: val => val ? "visible" : "excluded" + }); + if (node.getMarker()) { + node.getMarker().bind("color", marker, "textColor"); + } + }; + node.addListener("changeMarker", () => updateMarker()); + updateMarker(); }, __applyType: function(type) { @@ -258,9 +311,7 @@ qx.Class.define("osparc.component.workbench.NodeUI", { const title = this.getChildControl("title"); title.set({ wrap: true, - maxHeight: 28, - minWidth: width-16, - maxWidth: width-16 + maxHeight: 28 }); const outputs = this.getNode().getOutputs(); diff --git a/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js b/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js index 3e9b12ef2eb..5a56c85ebb1 100644 --- a/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js +++ b/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js @@ -592,6 +592,7 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { node.addListener("keyChanged", () => this.activeNodeChanged(nodeUI), this); nodeUI.populateNodeLayout(this.__svgLayer); nodeUI.addListener("renameNode", e => this.__openNodeRenamer(e.getData()), this); + nodeUI.addListener("markerClicked", e => this.__openMarkerEditor(e.getData()), this); nodeUI.addListener("infoNode", e => this.__openNodeInfo(e.getData()), this); nodeUI.addListener("removeNode", e => this.fireDataEvent("removeNode", e.getData()), this); @@ -1218,7 +1219,6 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { } else if (this.__isSelectedItemAnAnnotation()) { const annotation = this.__getAnnotation(oldId); annotation.setSelected(false); - this.__annotationEditor.exclude(); } } @@ -1230,7 +1230,6 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { const annotation = this.__getAnnotation(newID); this.__setSelectedAnnotations([annotation]); this.__annotationEditor.setAnnotation(annotation); - this.__annotationEditor.show(); } else { this.fireDataEvent("changeSelectedNode", newID); } @@ -1548,6 +1547,16 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { treeItemRenamer.open(); }, + __openMarkerEditor: function(nodeId) { + if (nodeId) { + const node = this.getStudy().getWorkbench().getNode(nodeId); + const marker = node.getMarker(); + if (marker) { + this.__annotationEditor.setMarker(marker); + } + } + }, + __openNodeInfo: function(nodeId) { if (nodeId) { const node = this.getStudy().getWorkbench().getNode(nodeId); 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 9ac297de9b4..ce3600bc469 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -164,6 +164,13 @@ qx.Class.define("osparc.data.model.Node", { nullable: true }, + marker: { + check: "qx.core.Object", + init: null, + nullable: true, + event: "changeMarker" + }, + inputConnected: { check: "Boolean", init: false, @@ -205,6 +212,7 @@ qx.Class.define("osparc.data.model.Node", { "fileRequested": "qx.event.type.Data", "parameterRequested": "qx.event.type.Data", "filePickerRequested": "qx.event.type.Data", + "probeRequested": "qx.event.type.Data", "showInLogger": "qx.event.type.Data", "outputListChanged": "qx.event.type.Event", "changeInputNodes": "qx.event.type.Event" @@ -442,6 +450,9 @@ qx.Class.define("osparc.data.model.Node", { if ("position" in nodeUIData) { this.setPosition(nodeUIData.position); } + if ("marker" in nodeUIData) { + this.addMarker(nodeUIData.marker); + } }, populateInputOutputData: function(nodeData) { @@ -612,6 +623,20 @@ qx.Class.define("osparc.data.model.Node", { } }, + addMarker: function(marker) { + if (marker === undefined) { + marker = { + color: osparc.utils.Utils.getRandomColor() + }; + } + const markerModel = qx.data.marshal.Json.createModel(marker, true); + this.setMarker(markerModel); + }, + + removeMarker: function() { + this.setMarker(null); + }, + __setInputData: function(inputs) { if (this.__settingsForm && inputs) { const inputData = {}; 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 35b589b697c..a7901ce3cbe 100644 --- a/services/web/client/source/class/osparc/data/model/Study.js +++ b/services/web/client/source/class/osparc/data/model/Study.js @@ -357,6 +357,10 @@ qx.Class.define("osparc.data.model.Study", { }, nodeUpdated: function(nodeUpdatedData) { + const studyId = nodeUpdatedData["project_id"]; + if (studyId !== this.getUuid()) { + return; + } const nodeId = nodeUpdatedData["node_id"]; const nodeData = nodeUpdatedData["data"]; const workbench = this.getWorkbench(); diff --git a/services/web/client/source/class/osparc/data/model/Workbench.js b/services/web/client/source/class/osparc/data/model/Workbench.js index 53f6eb9fc4d..b5186552224 100644 --- a/services/web/client/source/class/osparc/data/model/Workbench.js +++ b/services/web/client/source/class/osparc/data/model/Workbench.js @@ -350,10 +350,17 @@ qx.Class.define("osparc.data.model.Workbench", { } = e.getData(); this.__filePickerNodeRequested(nodeId, portId, file); }, this); + node.addListener("probeRequested", e => { + const { + portId, + nodeId + } = e.getData(); + this.__probeNodeRequested(nodeId, portId); + }, this); } }, - getFreePosition: function(node, toTheLeft = true) { + __getFreePosition: function(node, toTheLeft = true) { // do not overlap the new node2 with other nodes const pos = node.getPosition(); const nodeWidth = osparc.component.workbench.NodeUI.NODE_WIDTH; @@ -384,7 +391,7 @@ qx.Class.define("osparc.data.model.Workbench", { __connectFilePicker: function(nodeId, portId) { return new Promise((resolve, reject) => { const requesterNode = this.getNode(nodeId); - const freePos = this.getFreePosition(requesterNode); + const freePos = this.__getFreePosition(requesterNode); // create a new FP const filePickerMetadata = osparc.utils.Services.getFilePicker(); @@ -440,7 +447,7 @@ qx.Class.define("osparc.data.model.Workbench", { const pm = this.createNode(pmMD["key"], pmMD["version"], null, parent); // do not overlap the new Parameter Node with other nodes - const freePos = this.getFreePosition(requesterNode); + const freePos = this.__getFreePosition(requesterNode); pm.setPosition(freePos); // create connection @@ -460,6 +467,39 @@ qx.Class.define("osparc.data.model.Workbench", { } }, + __probeNodeRequested: function(nodeId, portId) { + const requesterNode = this.getNode(nodeId); + + // create a new ProbeNode + const requesterPortMD = requesterNode.getMetaData()["outputs"][portId]; + const type = osparc.utils.Ports.getPortType(requesterNode.getMetaData()["outputs"], portId); + const probeMD = osparc.utils.Services.getProbeMetadata(type); + if (probeMD) { + const parentNodeId = requesterNode.getParentNodeId(); + const parent = parentNodeId ? this.getNode(parentNodeId) : null; + const probeNode = this.createNode(probeMD["key"], probeMD["version"], null, parent); + probeNode.setLabel(requesterPortMD.label); + + // do not overlap the new Parameter Node with other nodes + const freePos = this.__getFreePosition(requesterNode, false); + probeNode.setPosition(freePos); + + // create connection + const probeId = probeNode.getNodeId(); + probeNode.addInputNode(nodeId); + probeNode.addPortLink("in_1", nodeId, portId) + .then(success => { + if (success) { + this.fireEvent("reloadModel"); + } else { + this.removeNode(probeId); + const msg = qx.locale.Manager.tr("Probe couldn't be assigned"); + osparc.component.message.FlashMessenger.getInstance().logAs(msg, "ERROR"); + } + }); + } + }, + addNode: function(node, parentNode) { const nodeId = node.getNodeId(); if (parentNode) { @@ -526,10 +566,10 @@ qx.Class.define("osparc.data.model.Workbench", { } if (leftNodeId) { const leftNode = this.getNode(leftNodeId); - node.setPosition(this.getFreePosition(leftNode, false)); + node.setPosition(this.__getFreePosition(leftNode, false)); } else if (rightNodeId) { const rightNode = this.getNode(rightNodeId); - node.setPosition(this.getFreePosition(rightNode, true)); + node.setPosition(this.__getFreePosition(rightNode, true)); } else { node.setPosition({ x: 20, @@ -856,6 +896,12 @@ qx.Class.define("osparc.data.model.Workbench", { const node = nodes[nodeId]; workbenchUI[nodeId] = {}; workbenchUI[nodeId]["position"] = node.getPosition(); + const marker = node.getMarker(); + if (marker) { + workbenchUI[nodeId]["marker"] = { + color: marker.getColor() + }; + } } return workbenchUI; } diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js index 32515a169a8..e5333c96137 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js @@ -317,7 +317,6 @@ qx.Class.define("osparc.desktop.WorkbenchView", { const nodesTree = this.__nodesTree = new osparc.component.widget.NodesTree().set({ backgroundColor: primaryColumnBGColor, - hideRoot: true, allowGrowY: true, minHeight: 5 }); @@ -651,10 +650,6 @@ qx.Class.define("osparc.desktop.WorkbenchView", { if (!socket.slotExists(slotName)) { socket.on(slotName, data => { const d = JSON.parse(data); - const studyId = d["project_id"]; - if (studyId !== this.getStudy().getUuid()) { - return; - } this.getStudy().nodeUpdated(d); }, this); } @@ -1025,7 +1020,9 @@ qx.Class.define("osparc.desktop.WorkbenchView", { } if (node.hasOutputs()) { - const nodeOutputs = new osparc.component.widget.NodeOutputs(node, node.getMetaData().outputs); + const nodeOutputs = new osparc.component.widget.NodeOutputs(node, node.getMetaData().outputs).set({ + offerProbes: true + }); this.__outputsPage.add(nodeOutputs); } diff --git a/services/web/client/source/class/osparc/theme/Appearance.js b/services/web/client/source/class/osparc/theme/Appearance.js index 00306eb84c4..1dbbe5694bc 100644 --- a/services/web/client/source/class/osparc/theme/Appearance.js +++ b/services/web/client/source/class/osparc/theme/Appearance.js @@ -472,14 +472,14 @@ qx.Theme.define("osparc.theme.Appearance", { --------------------------------------------------------------------------- */ "flash": { - style: state => ({ - padding: 10, - backgroundColor: "background-main-2", + style: () => ({ + padding: 12, + backgroundColor: "background-main-3", decorator: "flash" }) }, "flash/badge": { - style: state => ({ + style: () => ({ decorator: "flash-badge" }) }, diff --git a/services/web/client/source/class/osparc/ui/message/FlashMessage.js b/services/web/client/source/class/osparc/ui/message/FlashMessage.js index 6697e51ad96..f944c72eefd 100644 --- a/services/web/client/source/class/osparc/ui/message/FlashMessage.js +++ b/services/web/client/source/class/osparc/ui/message/FlashMessage.js @@ -12,6 +12,7 @@ Authors: * Ignacio Pascual (ignapas) + * Odei Maiz (odeimaiz) ************************************************************************ */ @@ -32,13 +33,16 @@ qx.Class.define("osparc.ui.message.FlashMessage", { this._setLayout(new qx.ui.layout.HBox(10)); this.set({ - maxWidth: 340, + maxWidth: 350, allowStretchX: false, alignX: "center" }); const badge = this.getChildControl("badge"); - badge.setBackgroundColor(this.self().LOG_LEVEL_COLOR_MAP[level]); + badge.set({ + source: this.self().LOG_LEVEL_COLOR_MAP[level].icon+"16", + textColor: this.self().LOG_LEVEL_COLOR_MAP[level].color + }); if (message) { this.setMessage(message); @@ -61,10 +65,22 @@ qx.Class.define("osparc.ui.message.FlashMessage", { statics: { LOG_LEVEL_COLOR_MAP: { - "INFO": "blue", - "DEBUG": "yellow", - "WARNING": "orange", - "ERROR": "red" + "INFO": { + color: "ready-green", + icon: "@FontAwesome5Solid/check/" + }, + "DEBUG": { + color: "warning-yellow", + icon: "@FontAwesome5Solid/info/" + }, + "WARNING": { + color: "busy-orange", + icon: "@FontAwesome5Solid/exclamation-triangle/" + }, + "ERROR": { + color: "failed-red", + icon: "@FontAwesome5Solid/exclamation-/" + } } }, @@ -77,6 +93,12 @@ qx.Class.define("osparc.ui.message.FlashMessage", { _createChildControlImpl: function(id) { let control; switch (id) { + case "badge": + control = new qx.ui.basic.Image().set({ + alignY: "middle" + }); + this._add(control); + break; case "message": control = new qx.ui.basic.Label().set({ font: "text-14", @@ -92,16 +114,6 @@ qx.Class.define("osparc.ui.message.FlashMessage", { }); this._add(control); break; - case "badge": - control = new qx.ui.core.Widget().set({ - height: 10, - width: 10, - allowStretchX: false, - allowStretchY: false, - alignY: "middle" - }); - this._add(control); - break; } return control || this.base(arguments, id); }, diff --git a/services/web/client/source/class/osparc/utils/Ports.js b/services/web/client/source/class/osparc/utils/Ports.js index eb14e6f0890..2789c291abf 100644 --- a/services/web/client/source/class/osparc/utils/Ports.js +++ b/services/web/client/source/class/osparc/utils/Ports.js @@ -38,10 +38,14 @@ qx.Class.define("osparc.utils.Ports", { }, getPortType: function(portsMetadata, portId) { + let portType = null; if (portId in portsMetadata) { - return portsMetadata[portId]["type"]; + portType = portsMetadata[portId]["type"]; + if (portType === "ref_contentSchema" && "contentSchema" in portsMetadata[portId]) { + portType = portsMetadata[portId]["contentSchema"]["type"]; + } } - return null; + return portType; } } }); diff --git a/services/web/client/source/class/osparc/utils/Services.js b/services/web/client/source/class/osparc/utils/Services.js index a70445d566b..098ac7d7638 100644 --- a/services/web/client/source/class/osparc/utils/Services.js +++ b/services/web/client/source/class/osparc/utils/Services.js @@ -93,7 +93,7 @@ qx.Class.define("osparc.utils.Services", { return null; }, - getSorting(type) { + getSorting: function(type) { const typeInfo = this.getType(type); if (typeInfo) { return typeInfo["sorting"]; @@ -290,6 +290,10 @@ qx.Class.define("osparc.utils.Services", { return this.self().getLatest(this.servicesCached, "simcore/services/frontend/parameter/"+type); }, + getProbeMetadata: function(type) { + return this.self().getLatest(this.servicesCached, "simcore/services/frontend/iterator-consumer/probe/"+type); + }, + getNodesGroup: function() { return this.self().getLatest(this.servicesCached, "simcore/services/frontend/nodes-group"); }, diff --git a/services/web/client/source/class/osparc/utils/Utils.js b/services/web/client/source/class/osparc/utils/Utils.js index 0a29333975c..872377f9d6e 100644 --- a/services/web/client/source/class/osparc/utils/Utils.js +++ b/services/web/client/source/class/osparc/utils/Utils.js @@ -131,15 +131,9 @@ qx.Class.define("osparc.utils.Utils", { }, isDevelopmentPlatform: function() { - return new Promise((resolve, reject) => { + return new Promise(resolve => { osparc.utils.LibVersions.getPlatformName() - .then(platformName => { - if (["dev", "master"].includes(platformName)) { - resolve(true); - } else { - resolve(false); - } - }); + .then(platformName => resolve(["dev", "master"].includes(platformName))); }); }, @@ -499,6 +493,10 @@ qx.Class.define("osparc.utils.Utils", { document.body.removeChild(textArea); + if (copied) { + osparc.component.message.FlashMessenger.getInstance().logAs(qx.locale.Manager.tr("Copied to clipboard")); + } + return copied; }, diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index de42645b95d..fb73ffe925e 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -2028,6 +2028,18 @@ paths: description: The y position example: - '15' + marker: + type: object + additionalProperties: false + required: + - color + properties: + color: + type: string + description: Marker's color + example: + - '#FF0000' + - '#0000FF' additionalProperties: true slideshow: type: object diff --git a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json index 2b9250b87d5..551f065314d 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json +++ b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json @@ -513,6 +513,23 @@ ] } } + }, + "marker": { + "type": "object", + "additionalProperties": false, + "required": [ + "color" + ], + "properties": { + "color": { + "type": "string", + "description": "Marker's color", + "examples": [ + "#FF0000", + "#0000FF" + ] + } + } } } }