From 71579eb69ee7770f3b847328e9b6f69748eec3b6 Mon Sep 17 00:00:00 2001 From: Odei Maiz <33152403+odeimaiz@users.noreply.github.com> Date: Thu, 21 Oct 2021 11:03:36 +0200 Subject: [PATCH] Study Editor redesign (#2571) --- .../component/form/ToggleButtonContainer.js | 9 +- .../component/form/renderer/PropForm.js | 144 ++- .../component/form/renderer/PropFormBase.js | 9 +- .../osparc/component/node/BaseNodeView.js | 18 +- .../osparc/component/node/ParameterEditor.js | 118 +-- .../osparc/component/task/TasksButton.js | 7 +- .../osparc/component/widget/NodeTreeItem.js | 146 +-- .../osparc/component/widget/NodesTree.js | 132 ++- .../component/widget/StudyTitleOnlyTree.js | 27 + .../osparc/component/workbench/WorkbenchUI.js | 48 +- .../source/class/osparc/data/model/Node.js | 21 +- .../class/osparc/data/model/Slideshow.js | 13 + .../source/class/osparc/data/model/Study.js | 4 - .../class/osparc/data/model/Workbench.js | 140 +-- .../source/class/osparc/desktop/MainPage.js | 17 +- .../class/osparc/desktop/SlideshowView.js | 56 +- .../class/osparc/desktop/StartStopButtons.js | 8 +- .../class/osparc/desktop/StudyEditor.js | 64 +- .../class/osparc/desktop/WorkbenchPanel.js | 56 + .../class/osparc/desktop/WorkbenchToolbar.js | 52 - .../class/osparc/desktop/WorkbenchView.js | 953 ++++++++++-------- .../source/class/osparc/file/FilePicker.js | 153 ++- .../source/class/osparc/file/FilesTree.js | 27 +- .../class/osparc/navigation/NavigationBar.js | 164 +-- .../class/osparc/navigation/StudyMenu.js | 120 +++ .../source/class/osparc/ui/hint/InfoHint.js | 7 +- .../source/class/osparc/utils/Services.js | 16 + .../client/source/class/osparc/utils/Utils.js | 18 + tests/e2e/portal/CC_Human.js | 2 - tests/e2e/portal/CC_Rabbit.js | 2 - tests/e2e/portal/Kember.js | 2 - tests/e2e/portal/opencor.js | 2 - tests/e2e/tests/tags.test.js | 12 +- tests/e2e/tutorials/isolve-gpu.js | 2 - tests/e2e/tutorials/isolve-mpi.js | 2 - tests/e2e/tutorials/sleepers.js | 4 +- tests/e2e/tutorials/tutorialBase.js | 14 +- tests/e2e/utils/auto.js | 12 +- tests/e2e/utils/utils.js | 19 +- 39 files changed, 1435 insertions(+), 1185 deletions(-) create mode 100644 services/web/client/source/class/osparc/component/widget/StudyTitleOnlyTree.js create mode 100644 services/web/client/source/class/osparc/desktop/WorkbenchPanel.js create mode 100644 services/web/client/source/class/osparc/navigation/StudyMenu.js diff --git a/services/web/client/source/class/osparc/component/form/ToggleButtonContainer.js b/services/web/client/source/class/osparc/component/form/ToggleButtonContainer.js index 8cc83b15c61..230ae7ac8ba 100644 --- a/services/web/client/source/class/osparc/component/form/ToggleButtonContainer.js +++ b/services/web/client/source/class/osparc/component/form/ToggleButtonContainer.js @@ -36,12 +36,8 @@ qx.Class.define("osparc.component.form.ToggleButtonContainer", { add: function(child, options) { if (child instanceof qx.ui.form.ToggleButton) { this.base(arguments, child, options); - child.addListener("changeValue", e => { - this.fireDataEvent("changeSelection", this.getSelection()); - }, this); - child.addListener("changeVisibility", e => { - this.fireDataEvent("changeVisibility", this.getVisibles()); - }, this); + child.addListener("changeValue", () => this.fireDataEvent("changeSelection", this.getSelection()), this); + child.addListener("changeVisibility", () => this.fireDataEvent("changeVisibility", this.getVisibles()), this); if (this.getMode() === "list") { const width = this.getBounds().width - 15; child.setWidth(width); @@ -57,6 +53,7 @@ qx.Class.define("osparc.component.form.ToggleButtonContainer", { resetSelection: function() { this.getChildren().map(button => button.setValue(false)); this.__lastSelectedIdx = null; + this.fireDataEvent("changeSelection", this.getSelection()); }, /** 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 8316ca53795..351d08b12f8 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 @@ -24,13 +24,12 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { this.setStudy(study); } this.__ctrlLinkMap = {}; - this.__ctrlParamMap = {}; + this.__linkUnlinkStackMap = {}; this.__fieldOptsBtnMap = {}; this.base(arguments, form, node); this.__addLinkCtrls(); - this.__addParamCtrls(); this.setDroppable(true); this.__attachDragoverHighlighter(); @@ -38,6 +37,7 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { events: { "linkFieldModified": "qx.event.type.Data", + "fileRequested": "qx.event.type.Data", "filePickerRequested": "qx.event.type.Data", "parameterNodeRequested": "qx.event.type.Data", "changeChildVisibility": "qx.event.type.Event" @@ -91,16 +91,33 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { }, __ctrlLinkMap: null, - __ctrlParamMap: null, + __linkUnlinkStackMap: null, __fieldOptsBtnMap: null, - __createFieldOpts: function(field) { + __createLinkUnlinkStack: function(field) { + const linkUnlinkStack = new qx.ui.container.Stack(); + + const linkOptions = this.__createLinkOpts(field); + linkUnlinkStack.add(linkOptions); + + const unlinkBtn = new qx.ui.form.Button(null, "@FontAwesome5Solid/unlink/12").set({ + toolTipText: this.tr("Unlink") + }); + unlinkBtn.addListener("execute", () => this.removePortLink(field.key), this); + linkUnlinkStack.add(unlinkBtn); + + this.__linkUnlinkStackMap[field.key] = linkUnlinkStack; + + return linkUnlinkStack; + }, + + __createLinkOpts: function(field) { const optionsMenu = new qx.ui.menu.Menu().set({ - position: "bottom-right" + position: "bottom-left" }); const fieldOptsBtn = new qx.ui.form.MenuButton().set({ menu: optionsMenu, - icon: "@FontAwesome5Solid/ellipsis-v/14", + icon: "@FontAwesome5Solid/link/12", focusable: false, allowGrowX: false, alignX: "center" @@ -125,8 +142,12 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { optionsMenu.addSeparator(); } + const studyUI = this.getStudy().getUi(); if (["FileButton"].includes(field.widgetType)) { const menuButton = this.__getSelectFileButton(field.key); + studyUI.bind("mode", menuButton, "visibility", { + converter: mode => mode === "workbench" ? "visible" : "excluded" + }); optionsMenu.add(menuButton); } if (this.self().isFieldParametrizable(field)) { @@ -142,8 +163,8 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { newParamBtn, paramsMenuBtn ].forEach(btn => { - field.bind("visibility", btn, "visibility", { - converter: visibility => (visibility === "visible" && areParamsEnabled) ? "visible" : "excluded" + studyUI.bind("mode", btn, "visibility", { + converter: mode => mode === "workbench" && areParamsEnabled ? "visible" : "excluded" }); }); }); @@ -199,7 +220,7 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { __getSelectFileButton: function(portId) { const selectFileButton = new qx.ui.menu.Button(this.tr("Select File")); - selectFileButton.addListener("execute", () => this.fireDataEvent("filePickerRequested", portId), this); + selectFileButton.addListener("execute", () => this.fireDataEvent("fileRequested", portId), this); return selectFileButton; }, @@ -278,11 +299,11 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { for (let i = 0; i < items.length; i++) { const item = items[i]; - const fieldOpts = this.__createFieldOpts(item); + const fieldOpts = this.__createLinkUnlinkStack(item); if (fieldOpts) { this._add(fieldOpts, { row, - column: this.self().gridPos.fieldOptions + column: this.self().gridPos.fieldLinkUnlink }); } @@ -464,12 +485,24 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { } const compatible = destinations[node2Id][portId]; - if (compatible === true) { + if (compatible) { // stop propagation, so that the form doesn't attend it (and preventDefault it) e.stopPropagation(); this.__highlightCompatibles(portId); } } + + if (e.supportsType("osparc-file-link")) { + const data = e.getData("osparc-file-link"); + if ("dragData" in data && "type" in uiElement) { + const compatible = uiElement.type.includes("data:"); + if (compatible) { + // stop propagation, so that the form doesn't attend it (and preventDefault it) + e.stopPropagation(); + this.__highlightCompatibles(portId); + } + } + } }, this); uiElement.addListener("drop", e => { @@ -480,6 +513,15 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { const port1Key = data["port1Key"]; this.getNode().addPortLink(port2Key, node1Id, port1Key); } + if (e.supportsType("osparc-file-link")) { + const data = e.getData("osparc-file-link"); + this.fireDataEvent("filePickerRequested", { + portId, + file: { + data: data["dragData"] + } + }); + } }, this); } }, @@ -600,7 +642,7 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { }); }, - __linkAdded: function(portId, fromNodeId, fromPortId) { + __portLinkAdded: function(portId, fromNodeId, fromPortId) { let data = this._getCtrlFieldChild(portId); if (data) { let child = data.child; @@ -608,29 +650,17 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { const layoutProps = child.getLayoutProperties(); this._remove(child); - const hBox = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); - - hBox.add(this.getControlLink(portId), { - flex: 1 - }); - - const unlinkBtn = new qx.ui.form.Button(null, "@FontAwesome5Solid/unlink/14").set({ - toolTipText: this.tr("Unlink") - }); - unlinkBtn.addListener("execute", function() { - this.removePortLink(portId); - }, this); - hBox.add(unlinkBtn); - - hBox.key = portId; - - this._addAt(hBox, idx, { + const ctrlLink = this.getControlLink(portId); + ctrlLink.setEnabled(false); + ctrlLink.key = portId; + this._addAt(ctrlLink, idx, { row: layoutProps.row, column: this.self().gridPos.ctrlField }); - if (portId in this.__fieldOptsBtnMap) { - this.__fieldOptsBtnMap[portId].setEnabled(false); + if (portId in this.__linkUnlinkStackMap) { + const stack = this.__linkUnlinkStackMap[portId]; + stack.setSelection([stack.getSelectables()[1]]); } const linkFieldModified = { @@ -645,14 +675,9 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { __portLinkRemoved: function(portId) { if (this.__resetCtrlField(portId)) { - // enable fieldOpts button - const fieldOpts = this._getFieldOptsChild(portId); - if (fieldOpts) { - fieldOpts.child.setEnabled(true); - } - - if (portId in this.__fieldOptsBtnMap) { - this.__fieldOptsBtnMap[portId].setEnabled(true); + if (portId in this.__linkUnlinkStackMap) { + const stack = this.__linkUnlinkStackMap[portId]; + stack.setSelection([stack.getSelectables()[0]]); } const linkFieldModified = { @@ -703,7 +728,7 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { converter: label => label + ": " + fromPortLabel }); - this.__linkAdded(toPortId, fromNodeId, fromPortId); + this.__portLinkAdded(toPortId, fromNodeId, fromPortId); return true; }, @@ -723,42 +748,7 @@ qx.Class.define("osparc.component.form.renderer.PropForm", { } this.__portLinkRemoved(toPortId); - }, - /* /LINKS */ - - /* PARAMETERS */ - getControlParam: function(key) { - return this.__ctrlParamMap[key]; - }, - - __addParamCtrl: function(portId) { - const controlParam = this.__createFieldCtrl(portId); - this.__ctrlParamMap[portId] = controlParam; - }, - - __addParamCtrls: function() { - this.__getPortKeys().forEach(portId => { - this.__addParamCtrl(portId); - }); - }, - - __parameterRemoved: function(portId) { - this.__resetCtrlField(portId); - }, - - removeParameter: function(portId) { - this.getControlParam(portId).setEnabled(false); - let ctrlField = this._form.getControl(portId); - if (ctrlField && "parameter" in ctrlField) { - delete ctrlField.parameter; - } - - this.__parameterRemoved(portId); - }, - /* /PARAMETERS */ - - __getRetrieveFieldChild: function(portId) { - return this._getLayoutChild(portId, this.self().gridPos.retrieveStatus); } + /* /LINKS */ } }); 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 8962b5d3ccf..0a5e2b37842 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 @@ -60,14 +60,13 @@ qx.Class.define("osparc.component.form.renderer.PropFormBase", { info: 1, ctrlField: 2, unit: 3, - fieldOptions: 4 + fieldLinkUnlink: 4 }, getDisableables: function() { return [ this.gridPos.label, - this.gridPos.ctrlField, - this.gridPos.fieldOptions + this.gridPos.ctrlField ]; } }, @@ -243,10 +242,6 @@ qx.Class.define("osparc.component.form.renderer.PropFormBase", { _getCtrlFieldChild: function(portId) { return this._getLayoutChild(portId, this.self().gridPos.ctrlField); - }, - - _getFieldOptsChild: function(portId) { - return this._getLayoutChild(portId, this.self().gridPos.fieldOptions); } } }); diff --git a/services/web/client/source/class/osparc/component/node/BaseNodeView.js b/services/web/client/source/class/osparc/component/node/BaseNodeView.js index ae327d3b4b4..dd3db322279 100644 --- a/services/web/client/source/class/osparc/component/node/BaseNodeView.js +++ b/services/web/client/source/class/osparc/component/node/BaseNodeView.js @@ -42,6 +42,15 @@ qx.Class.define("osparc.component.node.BaseNodeView", { layout: new qx.ui.layout.VBox() }); return settingsGroupBox; + }, + + openNodeDataManager: function(node) { + const nodeDataManager = new osparc.component.widget.NodeDataManager(node); + const win = osparc.ui.window.Window.popUpInWindow(nodeDataManager, node.getLabel(), 900, 600).set({ + appearance: "service-window" + }); + const closeBtn = win.getChildControl("close-button"); + osparc.utils.Utils.setIdToWidget(closeBtn, "nodeDataManagerCloseBtn"); } }, @@ -249,7 +258,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", { const buttonsLayout = this.__buttonContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); const filesBtn = this.__outFilesButton = new qx.ui.form.Button(this.tr("Output Files"), "@FontAwesome5Solid/folder-open/14"); - osparc.utils.Utils.setIdToWidget(filesBtn, "nodeViewFilesBtn"); + osparc.utils.Utils.setIdToWidget(filesBtn, "nodeOutputFilesBtn"); filesBtn.addListener("execute", () => this.__openNodeDataManager(), this); buttonsLayout.add(filesBtn); @@ -375,12 +384,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", { }, __openNodeDataManager: function() { - const nodeDataManager = new osparc.component.widget.NodeDataManager(this.getNode()); - const win = osparc.ui.window.Window.popUpInWindow(nodeDataManager, this.getNode().getLabel(), 900, 600).set({ - appearance: "service-window" - }); - const closeBtn = win.getChildControl("close-button"); - osparc.utils.Utils.setIdToWidget(closeBtn, "nodeDataManagerCloseBtn"); + this.self().openNodeDataManager(this.getNode()); }, __openServiceDetails: function() { diff --git a/services/web/client/source/class/osparc/component/node/ParameterEditor.js b/services/web/client/source/class/osparc/component/node/ParameterEditor.js index fb00488679c..84d3e0745a4 100644 --- a/services/web/client/source/class/osparc/component/node/ParameterEditor.js +++ b/services/web/client/source/class/osparc/component/node/ParameterEditor.js @@ -17,22 +17,15 @@ qx.Class.define("osparc.component.node.ParameterEditor", { - extend: qx.ui.core.Widget, + extend: qx.ui.form.renderer.Single, construct: function(node) { - this.base(arguments); - - this._setLayout(new qx.ui.layout.VBox()); - this.set({ node }); - this.set({ - appearance: "settings-groupbox", - maxWidth: 800, - alignX: "center" - }); + const form = this.__form = new qx.ui.form.Form(); + this.base(arguments, form); }, statics: { @@ -52,11 +45,6 @@ qx.Class.define("osparc.component.node.ParameterEditor", { } }, - events: { - "ok": "qx.event.type.Event", - "cancel": "qx.event.type.Event" - }, - properties: { node: { check: "osparc.data.model.Node" @@ -64,6 +52,8 @@ qx.Class.define("osparc.component.node.ParameterEditor", { }, members: { + __form: null, + _createChildControlImpl: function(id) { let control; switch (id) { @@ -98,84 +88,31 @@ qx.Class.define("osparc.component.node.ParameterEditor", { case "boolean": control = new qx.ui.form.CheckBox(); break; - case "cancel-button": { - control = new qx.ui.form.Button(this.tr("Cancel")).set({ - allowGrowX: false - }); - const commandEsc = new qx.ui.command.Command("Esc"); - control.setCommand(commandEsc); - control.addListener("execute", () => this.fireEvent("cancel")); - break; - } - case "ok-button": { - control = new qx.ui.form.Button(this.tr("OK")).set({ - allowGrowX: false - }); - control.addListener("execute", () => { - this.fireEvent("ok"); - }); - break; - } } return control || this.base(arguments, id); }, - formForSlideshow: function() { - const form = this.__buildForm(); - const node = this.getNode(); - form.getItem("label").addListener("changeValue", e => node.setLabel(e.getData())); - form.getItem("value").addListener("changeValue", e => osparc.component.node.ParameterEditor.setParameterOutputValue(node, e.getData())); - - this._removeAll(); - const renderer = new qx.ui.form.renderer.Single(form); - - const settingsGroupBox = osparc.component.node.BaseNodeView.createSettingsGroupBox(this.tr("Settings")); - this._add(settingsGroupBox); - settingsGroupBox.add(renderer); - - return form; - }, - - popUpInWindow: function() { - const form = this.__buildForm(); - this.__addButtons(form); - + buildForm: function(allSettings = true) { this._removeAll(); - const renderer = new qx.ui.form.renderer.Single(form); - this._add(renderer); - - const win = osparc.ui.window.Window.popUpInWindow(this, "Edit Parameter", 250, 175); - this.addListener("ok", () => { - const node = this.getNode(); - const label = form.getItem("label").getValue(); - node.setLabel(label); - const val = form.getItem("value").getValue(); - osparc.component.node.ParameterEditor.setParameterOutputValue(node, val); - win.close(); - }, this); - this.addListener("cancel", () => { - win.close(); - }, this); - }, - - __buildForm: function() { - const form = new qx.ui.form.Form(); const node = this.getNode(); - const label = this.getChildControl("label"); - form.add(label, "Label", null, "label"); - label.setValue(node.getLabel()); - const type = this.self().getParameterOutputType(node); - const typeBox = this.getChildControl("data-type"); - typeBox.getSelectables().forEach(selectable => { - if (selectable.type === type) { - typeBox.setSelection([selectable]); - typeBox.setEnabled(false); - } - }); - form.add(typeBox, "Data Type", null, "type"); + if (allSettings) { + const label = this.getChildControl("label"); + label.setValue(node.getLabel()); + label.bind("value", node, "label"); + this.__form.add(label, "Label", null, "label"); + + const typeBox = this.getChildControl("data-type"); + typeBox.getSelectables().forEach(selectable => { + if (selectable.type === type) { + typeBox.setSelection([selectable]); + typeBox.setEnabled(false); + } + }); + this.__form.add(typeBox, "Data Type", null, "type"); + } const valueField = this.getChildControl(type); const outputs = node.getOutputs(); @@ -186,17 +123,8 @@ qx.Class.define("osparc.component.node.ParameterEditor", { valueField.setValue(String(outputs["out_1"]["value"])); } } - form.add(valueField, "Value", null, "value"); - - return form; - }, - - __addButtons: function(form) { - // buttons - const cancelButton = this.getChildControl("cancel-button"); - form.addButton(cancelButton); - const okButton = this.getChildControl("ok-button"); - form.addButton(okButton); + valueField.addListener("changeValue", e => osparc.component.node.ParameterEditor.setParameterOutputValue(node, e.getData())); + this.__form.add(valueField, "Value", null, "value"); } } }); diff --git a/services/web/client/source/class/osparc/component/task/TasksButton.js b/services/web/client/source/class/osparc/component/task/TasksButton.js index d4a5464afee..fd770e80cdc 100644 --- a/services/web/client/source/class/osparc/component/task/TasksButton.js +++ b/services/web/client/source/class/osparc/component/task/TasksButton.js @@ -95,12 +95,7 @@ qx.Class.define("osparc.component.task.TasksButton", { const tapListener = event => { const tasks = osparc.component.task.Tasks.getInstance(); const tasksContainer = tasks.getTasksContainer(); - const tasksContainerElement = tasksContainer.getContentElement().getDomElement(); - const boundRect = tasksContainerElement.getBoundingClientRect(); - if (event.x > boundRect.x && - event.y > boundRect.y && - event.x < (boundRect.x + boundRect.width) && - event.y < (boundRect.y + boundRect.height)) { + if (osparc.utils.Utils.isMouseOnElement(tasksContainer, event)) { return; } // eslint-disable-next-line no-underscore-dangle 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 90db29b9a37..a22ec34f522 100644 --- a/services/web/client/source/class/osparc/component/widget/NodeTreeItem.js +++ b/services/web/client/source/class/osparc/component/widget/NodeTreeItem.js @@ -40,11 +40,12 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { extend: qx.ui.tree.VirtualTreeItem, - construct: function(study) { - this.__study = study; - + construct: function() { this.base(arguments); + this.getContentElement().setStyles({ + "border-radius": "6px" + }); this.__setNotHoveredStyle(); this.__attachEventHandlers(); }, @@ -53,19 +54,19 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { nodeId : { check : "String", event: "changeNodeId", - apply: "_applyNodeId", + apply: "__applyNodeId", nullable : true } }, events: { - "openNode": "qx.event.type.Data", + "fullscreenNode": "qx.event.type.Data", "renameNode": "qx.event.type.Data", "deleteNode": "qx.event.type.Data" }, members: { - __study: null, + __optionsMenu: null, _createChildControlImpl: function(id) { let control; @@ -78,36 +79,28 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { this.addWidget(control); break; } - case "open-btn": { - const part = this.getChildControl("buttons"); + case "fullscreen-button": { control = new qx.ui.form.Button().set({ + icon: "@MaterialIcons/fullscreen/14", backgroundColor: "transparent", - toolTipText: this.tr("Open"), - icon: "@FontAwesome5Solid/edit/9" + toolTipText: this.tr("Full Screen"), + alignY: "middle", + visibility: "excluded" }); - control.addListener("execute", () => this.fireDataEvent("openNode", this.getNodeId())); - part.add(control); - break; - } - case "rename-btn": { + control.addListener("execute", () => this.fireDataEvent("fullscreenNode", this.getNodeId())); const part = this.getChildControl("buttons"); - control = new qx.ui.form.Button().set({ - backgroundColor: "transparent", - toolTipText: this.tr("Rename"), - icon: "@FontAwesome5Solid/i-cursor/9" - }); - control.addListener("execute", () => this.fireDataEvent("renameNode", this.getNodeId())); part.add(control); break; } - case "delete-btn": { - const part = this.getChildControl("buttons"); - control = new qx.ui.form.Button().set({ - backgroundColor: "transparent", - toolTipText: this.tr("Delete"), - icon: "@FontAwesome5Solid/trash/9" + case "options-menu-button": { + const optionsMenu = this.__getOptionsMenu(); + control = new qx.ui.form.MenuButton().set({ + menu: optionsMenu, + icon: "@FontAwesome5Solid/ellipsis-v/9", + allowGrowX: false, + alignY: "middle" }); - control.addListener("execute", () => this.fireDataEvent("deleteNode", this.getNodeId())); + const part = this.getChildControl("buttons"); part.add(control); break; } @@ -137,6 +130,11 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { // The standard tree icon follows this.addIcon(); + this.getChildControl("icon").set({ + alignX: "center", + alignY: "middle", + width: 22 + }); // The label this.addLabel(); @@ -150,37 +148,43 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { flex: 1 }); - this.getChildControl("open-btn"); - const studyId = this.__study.getUuid(); - const readOnly = this.__study.isReadOnly(); - this.getChildControl("rename-btn").setEnabled(!readOnly); - this.getChildControl("delete-btn").setEnabled(!readOnly && studyId !== this.getNodeId()); + this.getChildControl("fullscreen-button"); + this.getChildControl("options-menu-button"); this.getChildControl("node-id"); }, - _applyNodeId: function(nodeId) { - const study = osparc.store.Store.getInstance().getCurrentStudy(); - if (nodeId === study.getUuid()) { - osparc.utils.Utils.setIdToWidget(this, "nodeTreeItem_root"); - } else { - osparc.utils.Utils.setIdToWidget(this, "nodeTreeItem_" + nodeId); - } - const opnBtn = this.getChildControl("open-btn"); - osparc.utils.Utils.setIdToWidget(opnBtn, "openNodeBtn_"+this.getNodeId()); - }, + __getOptionsMenu: function() { + const optionsMenu = this.__optionsMenu = new qx.ui.menu.Menu().set({ + position: "bottom-right" + }); - __setHoveredStyle: function() { - this.getContentElement().setStyles({ - "border-radius": "4px", - "border": "1px solid " + qx.theme.manager.Color.getInstance().resolve("background-selected") + const renameButton = new qx.ui.menu.Button().set({ + backgroundColor: "transparent", + label: this.tr("Rename"), + icon: "@FontAwesome5Solid/i-cursor/9" }); - }, + renameButton.addListener("execute", () => this.fireDataEvent("renameNode", this.getNodeId())); + optionsMenu.add(renameButton); - __setNotHoveredStyle: function() { - this.getContentElement().setStyles({ - "border-radius": "4px", - "border": "1px solid transparent" + const deleteButton = new qx.ui.menu.Button().set({ + backgroundColor: "transparent", + label: this.tr("Delete"), + icon: "@FontAwesome5Solid/trash/9" }); + deleteButton.addListener("execute", () => this.fireDataEvent("deleteNode", this.getNodeId())); + optionsMenu.add(deleteButton); + + return optionsMenu; + }, + + __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); + } }, __attachEventHandlers: function() { @@ -189,33 +193,33 @@ qx.Class.define("osparc.component.widget.NodeTreeItem", { this.__setHoveredStyle(); }); this.addListener("mouseout", () => { - if (!this.hasState("selected")) { + if (this.__optionsMenu.isVisible()) { + const hideButtonsIfMouseOut = event => { + if (osparc.utils.Utils.isMouseOnElement(this.__optionsMenu, event, 5)) { + return; + } + document.removeEventListener("mousemove", hideButtonsIfMouseOut); + this.getChildControl("buttons").exclude(); + this.__optionsMenu.exclude(); + }; + document.addEventListener("mousemove", hideButtonsIfMouseOut); + } else { this.getChildControl("buttons").exclude(); } this.__setNotHoveredStyle(); }); }, - // overridden - addState: function(state) { - this.base(arguments, state); - - if (state === "selected") { - this.getChildControl("buttons").show(); - } + __setHoveredStyle: function() { + this.getContentElement().setStyles({ + "border": "1px solid " + qx.theme.manager.Color.getInstance().resolve("background-selected") + }); }, - // overridden - removeState: function(state) { - this.base(arguments, state); - if (state === "selected") { - this.getChildControl("buttons").exclude(); - const studyId = this.__study.getUuid(); - const readOnly = this.__study.isReadOnly(); - this.getChildControl("rename-btn").setEnabled(!readOnly); - // disable delete button if the study is read only or if it's the study node item - this.getChildControl("delete-btn").setEnabled(!readOnly && studyId !== this.getNodeId()); - } + __setNotHoveredStyle: function() { + this.getContentElement().setStyles({ + "border": "1px solid transparent" + }); } } }); 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 b284fcd1636..365db09ec1c 100644 --- a/services/web/client/source/class/osparc/component/widget/NodesTree.js +++ b/services/web/client/source/class/osparc/component/widget/NodesTree.js @@ -18,9 +18,7 @@ /** * Widget that shows workbench hierarchy in tree view. * - * It contains: - * - Toolbar for adding, removing or renaming nodes - * - VirtualTree populated with NodeTreeItems + * VirtualTree populated with NodeTreeItems * * Helps the user navigating through nodes and gives a hierarchical view of containers. Also allows * some operations. @@ -36,23 +34,26 @@ */ qx.Class.define("osparc.component.widget.NodesTree", { - extend: qx.ui.core.Widget, + extend: qx.ui.tree.VirtualTree, construct: function() { - this.base(arguments); + this.base(arguments, null, "label", "children"); - this._setLayout(new qx.ui.layout.VBox()); - - this.__tree = this._createChildControlImpl("tree"); + this.set({ + decorator: "service-tree", + openMode: "none", + contentPadding: 0, + padding: 0 + }); + osparc.utils.Utils.setIdToWidget(this, "nodesTree"); this.__attachEventHandlers(); }, events: { "nodeSelected": "qx.event.type.Data", - "removeNode": "qx.event.type.Data", - "exportNode": "qx.event.type.Data", - "changeSelectedNode": "qx.event.type.Data" + "fullscreenNode": "qx.event.type.Data", + "removeNode": "qx.event.type.Data" }, properties: { @@ -84,50 +85,29 @@ qx.Class.define("osparc.component.widget.NodesTree", { }, members: { - __tree: null, __currentNodeId: null, - _createChildControlImpl: function(id) { - let control; - switch (id) { - case "tree": - control = this.__buildTree(); - this._add(control, { - flex: 1 - }); - break; - } - - return control || this.base(arguments, id); - }, - _applyStudy: function() { this.populateTree(); }, + getCurrentNodeId: function() { + return this.__currentNodeId; + }, + + setCurrentNodeId: function(nodeId) { this.__currentNodeId = nodeId; }, __getOneSelectedRow: function() { - const selection = this.__tree.getSelection(); + const selection = this.getSelection(); if (selection && selection.toArray().length > 0) { return selection.toArray()[0]; } return null; }, - __buildTree: function() { - const tree = new qx.ui.tree.VirtualTree(null, "label", "children").set({ - decorator: "service-tree", - openMode: "none", - contentPadding: 0, - padding: 0 - }); - osparc.utils.Utils.setIdToWidget(tree, "nodesTree"); - return tree; - }, - populateTree: function() { const study = this.getStudy(); const topLevelNodes = study.getWorkbench().getNodes(); @@ -138,20 +118,16 @@ qx.Class.define("osparc.component.widget.NodesTree", { isContainer: true }; let newModel = qx.data.marshal.Json.createModel(data, true); - let oldModel = this.__tree.getModel(); + let oldModel = this.getModel(); if (JSON.stringify(newModel) !== JSON.stringify(oldModel)) { study.bind("name", newModel, "label"); - this.__tree.setModel(newModel); - this.__tree.setDelegate({ + this.setModel(newModel); + this.setDelegate({ createItem: () => { - const nodeTreeItem = new osparc.component.widget.NodeTreeItem(study); - nodeTreeItem.addListener("openNode", e => this.__openItem(e.getData())); + 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("deleteNode", e => this.__deleteNode(e.getData())); - const readOnly = study.isReadOnly(); - // disable rename and delete button if the study is read only - nodeTreeItem.getChildControl("rename-btn").setEnabled(!readOnly); - nodeTreeItem.getChildControl("delete-btn").setEnabled(!readOnly); return nodeTreeItem; }, bindItem: (c, item, id) => { @@ -162,14 +138,31 @@ qx.Class.define("osparc.component.widget.NodesTree", { node.bind("label", item.getModel(), "label"); } c.bindProperty("label", "label", null, item, id); + if (item.getModel().getNodeId() === study.getUuid()) { + item.setIcon("@FontAwesome5Solid/home/14"); + item.getChildControl("options-menu-button").exclude(); + } + if (node) { + if (node.isDynamic()) { + item.getChildControl("fullscreen-button").show(); + } + 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 { + const icon = osparc.utils.Services.getIcon(node.getMetaData().type); + if (icon) { + item.setIcon(icon+"14"); + } + } + } }, configureItem: item => { - item.addListener("dbltap", () => { - this.__openItem(item.getModel().getNodeId()); - this.__selectedItem(item); - }, this); item.addListener("tap", () => { - this.__selectedItem(item); + this.__openItem(item.getModel().getNodeId()); this.nodeSelected(item.getModel().getNodeId()); }, this); } @@ -192,7 +185,7 @@ qx.Class.define("osparc.component.widget.NodesTree", { }, __getSelection: function() { - let treeSelection = this.__tree.getSelection(); + let treeSelection = this.getSelection(); if (treeSelection.length < 1) { return null; } @@ -200,31 +193,20 @@ qx.Class.define("osparc.component.widget.NodesTree", { return selectedItem; }, - __selectedItem: function(item) { - const nodeId = item.getModel().getNodeId(); - this.fireDataEvent("changeSelectedNode", nodeId); - }, - - __exportDAG: function() { - const selectedItem = this.__getSelection(); - if (selectedItem) { - if (selectedItem.getIsContainer()) { - const nodeId = selectedItem.getNodeId(); - this.fireDataEvent("exportNode", nodeId); - } else { - osparc.component.message.FlashMessenger.getInstance().logAs(this.tr("Only Groups can be exported."), "ERROR"); - } + __openItem: function(nodeId) { + if (nodeId) { + this.fireDataEvent("nodeSelected", nodeId); } }, - __openItem: function(nodeId) { + __openFullscreen: function(nodeId) { if (nodeId) { - this.fireDataEvent("nodeSelected", nodeId); + this.fireDataEvent("fullscreenNode", nodeId); } }, __openItemRenamer: function(nodeId) { - const renameItem = nodeId === undefined ? this.__getSelection() : this.__getNodeInTree(this.__tree.getModel(), nodeId); + const renameItem = nodeId === undefined ? this.__getSelection() : this.__getNodeInTree(this.getModel(), nodeId); if (renameItem) { const treeItemRenamer = new osparc.component.widget.Renamer(renameItem.getLabel()); treeItemRenamer.addListener("labelChanged", e => { @@ -266,22 +248,22 @@ qx.Class.define("osparc.component.widget.NodesTree", { }, nodeSelected: function(nodeId) { - const dataModel = this.__tree.getModel(); + const dataModel = this.getModel(); const item = this.__getNodeInTree(dataModel, nodeId); if (item) { - this.__tree.openNodeAndParents(item); - this.__tree.setSelection(new qx.data.Array([item])); + this.openNodeAndParents(item); + this.setSelection(new qx.data.Array([item])); } }, __attachEventHandlers: function() { - this.addListener("keypress", function(keyEvent) { + this.addListener("keypress", keyEvent => { if (keyEvent.getKeyIdentifier() === "Delete") { this.__deleteNode(); } }, this); - this.addListener("keypress", function(keyEvent) { + this.addListener("keypress", keyEvent => { if (keyEvent.getKeyIdentifier() === "F2") { this.__openItemRenamer(); } diff --git a/services/web/client/source/class/osparc/component/widget/StudyTitleOnlyTree.js b/services/web/client/source/class/osparc/component/widget/StudyTitleOnlyTree.js new file mode 100644 index 00000000000..5ff1ad2d87c --- /dev/null +++ b/services/web/client/source/class/osparc/component/widget/StudyTitleOnlyTree.js @@ -0,0 +1,27 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2021 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.component.widget.StudyTitleOnlyTree", { + extend: osparc.component.widget.NodesTree, + + members: { + populateTree: function() { + this.base(arguments); + this.getModel().getChildren().removeAll(); + } + } +}); 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 9dfef760822..06c20dac429 100644 --- a/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js +++ b/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js @@ -65,7 +65,7 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { return borderStyle; }, - TOP_OFFSET: osparc.navigation.NavigationBar.HEIGHT + 46, + TOP_OFFSET: osparc.navigation.NavigationBar.HEIGHT + 46 + 40, ZOOM_VALUES: [ 0.2, @@ -96,6 +96,7 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { properties: { study: { check: "osparc.data.model.Study", + apply: "__applyStudy", nullable: false }, @@ -130,6 +131,12 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { __dropHint: null, __panning: null, + __applyStudy: function(study) { + study.getWorkbench().addListener("reloadModel", () => { + this.__reloadCurrentModel(); + }, this); + }, + _addItemsToLayout: function() { this.__addInputNodesLayout(); this._addWorkbenchLayer(); @@ -367,6 +374,10 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { }, _addNodeUIToWorkbench: function(nodeUI, position) { + if (!("x" in position) || isNaN(position["x"]) || position["x"] < 0) { + console.error("not a valid position"); + return; + } this.__updateWorkbenchLayoutSize(position); const node = nodeUI.getNode(); @@ -445,7 +456,7 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { }, this); nodeUI.addListener("tap", e => { - this.__activeNodeChanged(nodeUI, e.isCtrlPressed()); + this.activeNodeChanged(nodeUI, e.isCtrlPressed()); e.stopPropagation(); }, this); @@ -488,7 +499,7 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { qx.event.message.Bus.dispatchByName("changeWorkbenchSelection", []); }, - __activeNodeChanged: function(activeNode, isControlPressed = false) { + activeNodeChanged: function(activeNode, isControlPressed = false) { if (isControlPressed) { if (this.__selectedNodes.includes(activeNode)) { const index = this.__selectedNodes.indexOf(activeNode); @@ -529,6 +540,10 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { // build representation const nodeUI1 = this.getNodeUI(node1Id); const nodeUI2 = this.getNodeUI(node2Id); + if (nodeUI1.getCurrentBounds() === null || nodeUI2.getCurrentBounds() === null) { + console.error("bounds not ready"); + return null; + } const port1 = nodeUI1.getOutputPort(); const port2 = nodeUI2.getInputPort(); if (port1 && port2) { @@ -998,7 +1013,13 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { } }, - _loadModel: function(model) { + __reloadCurrentModel: function() { + if (this._currentModel) { + this.loadModel(this._currentModel); + } + }, + + _loadModel: async function(model) { this.clearAll(); this.resetSelectedNodes(); this._currentModel = model; @@ -1017,11 +1038,22 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { // create nodes let nodes = isContainer ? model.getInnerNodes() : model.getNodes(); + const nodeUIs = []; for (const nodeId in nodes) { const node = nodes[nodeId]; const nodeUI = this._createNodeUI(nodeId); this.__createDragDropMechanism(nodeUI); this._addNodeUIToWorkbench(nodeUI, node.getPosition()); + nodeUIs.push(nodeUI); + } + + const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); + const allNodesVisible = nodeUIss => nodeUIss.every(nodeUI => nodeUI.getCurrentBounds() !== null); + + let tries = 0; + while (!allNodesVisible(nodeUIs) && tries < 10) { + await sleep(50); + tries++; } // create edges @@ -1223,8 +1255,8 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { _addEventListeners: function() { this.addListener("appear", () => { // Reset filters and sidebars - osparc.component.filter.UIFilterController.getInstance().resetGroup("workbench"); - osparc.component.filter.UIFilterController.getInstance().setContainerVisibility("workbench", "visible"); + // osparc.component.filter.UIFilterController.getInstance().resetGroup("workbench"); + // osparc.component.filter.UIFilterController.getInstance().setContainerVisibility("workbench", "visible"); qx.event.message.Bus.getInstance().dispatchByName("maximizeIframe", false); @@ -1262,8 +1294,8 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { this.addListener("disappear", () => { // Reset filters - osparc.component.filter.UIFilterController.getInstance().resetGroup("workbench"); - osparc.component.filter.UIFilterController.getInstance().setContainerVisibility("workbench", "excluded"); + // osparc.component.filter.UIFilterController.getInstance().resetGroup("workbench"); + // osparc.component.filter.UIFilterController.getInstance().setContainerVisibility("workbench", "excluded"); commandDel.setEnabled(false); commandEsc.setEnabled(false); 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 33b8861a531..8951d27cee0 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -205,8 +205,9 @@ qx.Class.define("osparc.data.model.Node", { events: { "retrieveInputs": "qx.event.type.Data", - "filePickerRequested": "qx.event.type.Data", + "fileRequested": "qx.event.type.Data", "parameterNodeRequested": "qx.event.type.Data", + "filePickerRequested": "qx.event.type.Data", "showInLogger": "qx.event.type.Data", "outputListChanged": "qx.event.type.Event", "changeInputNodes": "qx.event.type.Event" @@ -544,7 +545,7 @@ qx.Class.define("osparc.data.model.Node", { }, this); [ - "filePickerRequested", + "fileRequested", "parameterNodeRequested" ].forEach(nodeRequestSignal => { propsForm.addListener(nodeRequestSignal, e => { @@ -555,6 +556,15 @@ qx.Class.define("osparc.data.model.Node", { }); }, this); }); + + propsForm.addListener("filePickerRequested", e => { + const data = e.getData(); + this.fireDataEvent("filePickerRequested", { + portId: data.portId, + nodeId: this.getNodeId(), + file: data.file + }); + }, this); }, __addSettingsEditor: function(inputs) { @@ -724,8 +734,9 @@ qx.Class.define("osparc.data.model.Node", { .then(compatible => { if (compatible) { resolve(this.getPropsForm().addPortLink(toPortId, fromNodeId, fromPortId)); + } else { + resolve(false); } - resolve(false); }); }); }, @@ -1211,8 +1222,8 @@ qx.Class.define("osparc.data.model.Node", { getPosition: function() { return { - x: this.__posX, - y: this.__posY + x: this.__posX || 0, + y: this.__posY || 0 }; }, diff --git a/services/web/client/source/class/osparc/data/model/Slideshow.js b/services/web/client/source/class/osparc/data/model/Slideshow.js index 300e2cd7ed9..1101e67c6ee 100644 --- a/services/web/client/source/class/osparc/data/model/Slideshow.js +++ b/services/web/client/source/class/osparc/data/model/Slideshow.js @@ -101,6 +101,19 @@ qx.Class.define("osparc.data.model.Slideshow", { return -1; }, + addNodeToSlideshow: function(newNode, leftNodeId, rightNodeId) { + const nodeId = newNode.getNodeId(); + if (leftNodeId) { + let leftPos = this.getPosition(leftNodeId); + this.insertNode(nodeId, leftPos+1); + } else if (rightNodeId) { + const rightPos = this.getPosition(rightNodeId); + this.insertNode(nodeId, rightPos); + } else { + this.insertNode(nodeId, 0); + } + }, + __moveNode: function(nodes, from, to) { let numberOfDeletedElm = 1; const elm = nodes.splice(from, numberOfDeletedElm)[0]; 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 aab62125f11..dbdd04abd65 100644 --- a/services/web/client/source/class/osparc/data/model/Study.js +++ b/services/web/client/source/class/osparc/data/model/Study.js @@ -169,10 +169,6 @@ qx.Class.define("osparc.data.model.Study", { } }, - events: { - "changeParameters": "qx.event.type.Event" - }, - statics: { createMyNewStudyObject: function() { let myNewStudyObject = {}; 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 12390a94022..98a982a60c2 100644 --- a/services/web/client/source/class/osparc/data/model/Workbench.js +++ b/services/web/client/source/class/osparc/data/model/Workbench.js @@ -50,7 +50,9 @@ qx.Class.define("osparc.data.model.Workbench", { events: { "pipelineChanged": "qx.event.type.Event", + "reloadModel": "qx.event.type.Event", "retrieveInputs": "qx.event.type.Data", + "fileRequested": "qx.event.type.Data", "openNode": "qx.event.type.Data", "showInLogger": "qx.event.type.Data" }, @@ -300,25 +302,34 @@ qx.Class.define("osparc.data.model.Workbench", { __initNodeSignals: function(node) { if (node) { - node.addListener("showInLogger", e => { - this.fireDataEvent("showInLogger", e.getData()); - }, this); - node.addListener("retrieveInputs", e => { - this.fireDataEvent("retrieveInputs", e.getData()); - }, this); - node.addListener("filePickerRequested", e => { + node.addListener("showInLogger", e => this.fireDataEvent("showInLogger", e.getData()), this); + node.addListener("retrieveInputs", e => this.fireDataEvent("retrieveInputs", e.getData()), this); + node.addListener("fileRequested", e => this.fireDataEvent("fileRequested", e.getData()), this); + node.addListener("parameterNodeRequested", e => { const { portId, nodeId } = e.getData(); - this.__filePickerRequested(nodeId, portId); + this.__parameterNodeRequested(nodeId, portId); }, this); - node.addListener("parameterNodeRequested", e => { + node.addListener("filePickerRequested", e => { const { portId, - nodeId + nodeId, + file } = e.getData(); - this.__parameterNodeRequested(nodeId, portId); + this.connectFilePicker(nodeId, portId) + .then(filePicker => { + const fileObj = file.data; + osparc.file.FilePicker.setOutputValueFromStore( + filePicker, + fileObj.getLocation(), + fileObj.getDatasetId(), + fileObj.getFileId(), + fileObj.getLabel() + ); + this.fireEvent("reloadModel"); + }); }, this); } }, @@ -351,65 +362,33 @@ qx.Class.define("osparc.data.model.Workbench", { }; }, - __filePickerRequested: function(nodeId, portId) { - // Create/Reuse File Picker. Reuse it if a File Picker is already - // connecteted and it is not used anywhere else - const requesterNode = this.getNode(nodeId); - const link = requesterNode.getPropsForm().getLink(portId); - let isUsed = false; - if (link) { - const connectedFPID = link["nodeUuid"]; - // check if it's used by another port - const links1 = requesterNode.getPropsForm().getLinks(); - const matches = links1.filter(link1 => link1["nodeUuid"] === connectedFPID); - isUsed = matches.length > 1; - - // check if it's used by other nodes - const allNodes = this.getNodes(true); - const nodeIds = Object.keys(allNodes); - for (let i=0; i link2["nodeUuid"] === connectedFPID); - } - } - - if (link === null || isUsed) { + connectFilePicker: function(nodeId, portId) { + return new Promise((resolve, reject) => { + const requesterNode = this.getNode(nodeId); const freePos = this.getFreePosition(requesterNode); // create a new FP - const fpMD = osparc.utils.Services.getFilePicker(); + const filePickerMetadata = osparc.utils.Services.getFilePicker(); const parentNodeId = requesterNode.getParentNodeId(); const parent = parentNodeId ? this.getNode(parentNodeId) : null; - const fp = this.createNode(fpMD["key"], fpMD["version"], null, parent); - fp.setPosition(freePos); - - // remove old connection if any - if (link !== null) { - requesterNode.getPropsForm().removePortLink(portId); - } + const filePicker = this.createNode(filePickerMetadata["key"], filePickerMetadata["version"], null, parent); + filePicker.setPosition(freePos); // create connection - const fpId = fp.getNodeId(); - requesterNode.addInputNode(fpId); - requesterNode.addPortLink(portId, fpId, "outFile") + const filePickerId = filePicker.getNodeId(); + requesterNode.addInputNode(filePickerId); + requesterNode.addPortLink(portId, filePickerId, "outFile") .then(success => { if (success) { - this.fireDataEvent("openNode", fpId); + resolve(filePicker); } else { - this.removeNode(fpId); + this.removeNode(filePickerId); const msg = qx.locale.Manager.tr("File couldn't be assigned"); osparc.component.message.FlashMessenger.getInstance().logAs(msg, "ERROR"); + reject(); } }); - } else { - const connectedFPID = link["nodeUuid"]; - this.fireDataEvent("openNode", connectedFPID); - } + }); }, __parameterNodeRequested: function(nodeId, portId) { @@ -434,6 +413,7 @@ qx.Class.define("osparc.data.model.Workbench", { .then(success => { if (success) { this.fireDataEvent("openNode", pmId); + this.fireEvent("reloadModel"); } else { this.removeNode(pmId); const msg = qx.locale.Manager.tr("Parameter couldn't be assigned"); @@ -452,11 +432,6 @@ qx.Class.define("osparc.data.model.Workbench", { } node.setParentNodeId(parentNode ? parentNode.getNodeId() : null); this.fireEvent("pipelineChanged"); - if (node.isParameter()) { - if (this.getStudy()) { - this.getStudy().fireEvent("changeParameters"); - } - } }, moveNode: function(node, newParent, oldParent) { @@ -501,16 +476,49 @@ qx.Class.define("osparc.data.model.Workbench", { } this.fireEvent("pipelineChanged"); - if (node.isParameter()) { - if (this.getStudy()) { - this.getStudy().fireEvent("changeParameters"); - } - } return true; } return false; }, + addServiceBetween: function(service, leftNodeId, rightNodeId) { + // create node + const node = this.createNode(service.getKey(), service.getVersion()); + if (!node) { + return null; + } + if (leftNodeId) { + const leftNode = this.getNode(leftNodeId); + node.setPosition(this.getFreePosition(leftNode, false)); + } else if (rightNodeId) { + const rightNode = this.getNode(rightNodeId); + node.setPosition(this.getFreePosition(rightNode, true)); + } else { + node.setPosition({ + x: 20, + y: 20 + }); + } + + // break previous connection + if (leftNodeId && rightNodeId) { + const edge = this.getEdge(null, leftNodeId, rightNodeId); + if (edge) { + this.removeEdge(edge.getEdgeId()); + } + } + + // create connections + if (leftNodeId) { + this.createEdge(null, leftNodeId, node.getNodeId()); + } + if (rightNodeId) { + this.createEdge(null, node.getNodeId(), rightNodeId); + } + + return node; + }, + removeEdge: function(edgeId) { if (!osparc.data.Permissions.getInstance().canDo("study.edge.delete", true)) { return false; diff --git a/services/web/client/source/class/osparc/desktop/MainPage.js b/services/web/client/source/class/osparc/desktop/MainPage.js index fa2f20964cd..e5c2463f773 100644 --- a/services/web/client/source/class/osparc/desktop/MainPage.js +++ b/services/web/client/source/class/osparc/desktop/MainPage.js @@ -119,6 +119,18 @@ qx.Class.define("osparc.desktop.MainPage", { } }, this); + navBar.addListener("takeSnapshot", () => { + if (this.__studyEditor) { + this.__studyEditor.takeSnapshot(); + } + }, this); + + navBar.addListener("showSnapshots", () => { + if (this.__studyEditor) { + this.__studyEditor.showSnapshots(); + } + }, this); + return navBar; }, @@ -359,9 +371,7 @@ qx.Class.define("osparc.desktop.MainPage", { this.__navBar.setPageContext(pageContext); studyEditor.setPageContext(pageContext); - this.__studyEditor.addListener("forceBackToDashboard", () => { - this.__showDashboard(); - }, this); + this.__studyEditor.addListener("forceBackToDashboard", () => this.__showDashboard(), this); }, __getStudyEditor: function() { @@ -369,6 +379,7 @@ qx.Class.define("osparc.desktop.MainPage", { return this.__studyEditor; } const studyEditor = new osparc.desktop.StudyEditor(); + studyEditor.addListener("snapshotTaken", () => this.__navBar.getChildControl("study-options-menu").evalSnapshotsButtons(), this); studyEditor.addListener("startSnapshot", e => { const snapshotId = e.getData(); this.__startSnapshot(this.__studyEditor.getStudy().getUuid(), snapshotId); diff --git a/services/web/client/source/class/osparc/desktop/SlideshowView.js b/services/web/client/source/class/osparc/desktop/SlideshowView.js index 603e3e35fb3..79a7147be0b 100644 --- a/services/web/client/source/class/osparc/desktop/SlideshowView.js +++ b/services/web/client/source/class/osparc/desktop/SlideshowView.js @@ -126,8 +126,15 @@ qx.Class.define("osparc.desktop.SlideshowView", { let view; if (node.isParameter()) { - view = new osparc.component.node.ParameterEditor(node); - view.formForSlideshow(); + view = osparc.component.node.BaseNodeView.createSettingsGroupBox(this.tr("Settings")); + const renderer = new osparc.component.node.ParameterEditor(node); + renderer.buildForm(); + renderer.set({ + appearance: "settings-groupbox", + maxWidth: 800, + alignX: "center" + }); + view.add(renderer); } else { if (node.isContainer()) { view = new osparc.component.node.GroupNodeView(); @@ -223,52 +230,15 @@ qx.Class.define("osparc.desktop.SlideshowView", { __addServiceBetween: function(service, leftNodeId, rightNodeId) { const workbench = this.getStudy().getWorkbench(); - // create node - const node = workbench.createNode(service.getKey(), service.getVersion()); - if (!node) { + const node = workbench.addServiceBetween(service, leftNodeId, rightNodeId); + if (node === null) { return; } - if (leftNodeId) { - const leftNode = workbench.getNode(leftNodeId); - node.setPosition(workbench.getFreePosition(leftNode, false)); - } else if (rightNodeId) { - const rightNode = workbench.getNode(rightNodeId); - node.setPosition(workbench.getFreePosition(rightNode, true)); - } else { - node.setPosition({ - x: 20, - y: 20 - }); - } - - // break previous connection - if (leftNodeId && rightNodeId) { - const edge = workbench.getEdge(null, leftNodeId, rightNodeId); - if (edge) { - workbench.removeEdge(edge.getEdgeId()); - } - } - - // create connections - if (leftNodeId) { - workbench.createEdge(null, leftNodeId, node.getNodeId()); - } - if (rightNodeId) { - workbench.createEdge(null, node.getNodeId(), rightNodeId); - } // add to the slideshow const slideshow = this.getStudy().getUi().getSlideshow(); - const nodeId = node.getNodeId(); - if (leftNodeId) { - let leftPos = slideshow.getPosition(leftNodeId); - slideshow.insertNode(nodeId, leftPos+1); - } else if (rightNodeId) { - const rightPos = slideshow.getPosition(rightNodeId); - slideshow.insertNode(nodeId, rightPos); - } else { - slideshow.insertNode(nodeId, 0); - } + slideshow.addNodeToSlideshow(node, leftNodeId, rightNodeId); + this.__slideshowToolbar.populateButtons(); }, diff --git a/services/web/client/source/class/osparc/desktop/StartStopButtons.js b/services/web/client/source/class/osparc/desktop/StartStopButtons.js index bcde42254bf..4b2dabd86b7 100644 --- a/services/web/client/source/class/osparc/desktop/StartStopButtons.js +++ b/services/web/client/source/class/osparc/desktop/StartStopButtons.js @@ -95,10 +95,6 @@ qx.Class.define("osparc.desktop.StartStopButtons", { const clustersSelectBox = this.__createClustersSelectBox(); this._add(clustersSelectBox); - const stopButton = this.__createStopButton(); - stopButton.setEnabled(false); - this._add(stopButton); - const startButton = this.__createStartButton(); this._add(startButton); @@ -106,6 +102,10 @@ qx.Class.define("osparc.desktop.StartStopButtons", { visibility: "excluded" }); this._add(startSplitButton); + + const stopButton = this.__createStopButton(); + stopButton.setEnabled(false); + this._add(stopButton); }, __createClustersSelectBox: function() { diff --git a/services/web/client/source/class/osparc/desktop/StudyEditor.js b/services/web/client/source/class/osparc/desktop/StudyEditor.js index 0721dab8891..db12eb928e0 100644 --- a/services/web/client/source/class/osparc/desktop/StudyEditor.js +++ b/services/web/client/source/class/osparc/desktop/StudyEditor.js @@ -26,10 +26,6 @@ qx.Class.define("osparc.desktop.StudyEditor", { const viewsStack = this.__viewsStack = new qx.ui.container.Stack(); const workbenchView = this.__workbenchView = new osparc.desktop.WorkbenchView(); - workbenchView.addListener("startSnapshot", e => { - this.getStudy().removeIFrames(); - this.fireDataEvent("startSnapshot", e.getData()); - }); viewsStack.add(workbenchView); const slideshowView = this.__slideshowView = new osparc.desktop.SlideshowView(); @@ -61,6 +57,7 @@ qx.Class.define("osparc.desktop.StudyEditor", { events: { "forceBackToDashboard": "qx.event.type.Event", + "snapshotTaken": "qx.event.type.Event", "startSnapshot": "qx.event.type.Data" }, @@ -369,14 +366,6 @@ qx.Class.define("osparc.desktop.StudyEditor", { this.__viewsStack.setVisibility(show ? "visible" : "excluded"); }, - /** - * Destructor - */ - destruct: function() { - osparc.store.Store.getInstance().setCurrentStudy(null); - this.__stopAutoSaveTimer(); - }, - nodeSelected: function(nodeId) { this.__workbenchView.nodeSelected(nodeId); this.__slideshowView.nodeSelected(nodeId); @@ -400,6 +389,49 @@ qx.Class.define("osparc.desktop.StudyEditor", { } }, + takeSnapshot: function() { + const editSnapshotView = new osparc.component.snapshots.EditSnapshotView(); + const tagCtrl = editSnapshotView.getChildControl("tags"); + const study = this.getStudy(); + study.getSnapshots() + .then(snapshots => { + tagCtrl.setValue("V"+snapshots.length); + }); + const title = this.tr("Take Snapshot"); + const win = osparc.ui.window.Window.popUpInWindow(editSnapshotView, title, 400, 180); + editSnapshotView.addListener("takeSnapshot", () => { + const tag = editSnapshotView.getTag(); + const message = editSnapshotView.getMessage(); + const params = { + url: { + "studyId": study.getUuid() + }, + data: { + "tag": tag, + "message": message + } + }; + osparc.data.Resources.fetch("snapshots", "takeSnapshot", params) + .then(data => this.fireEvent("snapshotTaken")) + .catch(err => osparc.component.message.FlashMessenger.getInstance().logAs(err.message, "ERROR")); + + win.close(); + }, this); + editSnapshotView.addListener("cancel", () => win.close(), this); + }, + + showSnapshots: function() { + const study = this.getStudy(); + const snapshots = new osparc.component.snapshots.SnapshotsView(study); + const title = this.tr("Snapshots"); + const win = osparc.ui.window.Window.popUpInWindow(snapshots, title, 1000, 500); + snapshots.addListener("openSnapshot", e => { + win.close(); + const snapshotId = e.getData(); + this.fireDataEvent("startSnapshot", snapshotId); + }); + }, + __startAutoSaveTimer: function() { const diffPatcher = osparc.wrapper.JsonDiffPatch.getInstance(); // Save every 3 seconds @@ -467,6 +499,14 @@ qx.Class.define("osparc.desktop.StudyEditor", { if (this.getStudy()) { this.getStudy().stopStudy(); } + }, + + /** + * Destructor + */ + destruct: function() { + osparc.store.Store.getInstance().setCurrentStudy(null); + this.__stopAutoSaveTimer(); } } }); diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchPanel.js b/services/web/client/source/class/osparc/desktop/WorkbenchPanel.js new file mode 100644 index 00000000000..cbf5daa9762 --- /dev/null +++ b/services/web/client/source/class/osparc/desktop/WorkbenchPanel.js @@ -0,0 +1,56 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2021 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + + +qx.Class.define("osparc.desktop.WorkbenchPanel", { + extend: qx.ui.core.Widget, + + construct: function() { + this.base(arguments); + + this._setLayout(new qx.ui.layout.VBox()); + + const toolbar = this.__toolbar = new osparc.desktop.WorkbenchToolbar(); + toolbar.set({ + margin: 5 + }); + toolbar.getChildControl("breadcrumb-navigation").exclude(); + toolbar.getContentElement().setStyles({ + "border-radius": "12px", + "border": "1px solid " + qx.theme.manager.Color.getInstance().resolve("background-main") + }); + this._add(toolbar); + + const workbenchUI = this.__workbenchUI = new osparc.component.workbench.WorkbenchUI(); + this._add(workbenchUI, { + flex: 1 + }); + }, + + members: { + __toolbar: null, + __workbenchUI: null, + + getMainView: function() { + return this.__workbenchUI; + }, + + getToolbar: function() { + return this.__toolbar; + } + } +}); diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js b/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js index 4fa04562705..3bcb36ef578 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js @@ -24,11 +24,6 @@ qx.Class.define("osparc.desktop.WorkbenchToolbar", { this.__attachEventHandlers(); }, - events: { - "takeSnapshot": "qx.event.type.Event", - "showSnapshots": "qx.event.type.Event" - }, - members: { __navNodes: null, @@ -47,30 +42,6 @@ qx.Class.define("osparc.desktop.WorkbenchToolbar", { }); break; } - case "take-snapshot-btn": { - control = new osparc.ui.form.FetchButton(this.tr("Take Snapshot")).set({ - icon: "@FontAwesome5Solid/code-branch/14", - ...osparc.navigation.NavigationBar.BUTTON_OPTIONS, - allowGrowX: false - }); - control.addListener("execute", () => { - this.fireDataEvent("takeSnapshot"); - }, this); - this._add(control); - break; - } - case "snapshots-btn": { - control = new qx.ui.form.Button(this.tr("Snapshots")).set({ - icon: "@FontAwesome5Solid/copy/14", - ...osparc.navigation.NavigationBar.BUTTON_OPTIONS, - allowGrowX: false - }); - control.addListener("execute", () => { - this.fireDataEvent("showSnapshots"); - }, this); - this._add(control); - break; - } } return control || this.base(arguments, id); }, @@ -79,14 +50,6 @@ qx.Class.define("osparc.desktop.WorkbenchToolbar", { _buildLayout: function() { this.getChildControl("breadcrumb-navigation"); - this._add(new qx.ui.core.Spacer(20)); - - const takeSnapshotBtn = this.getChildControl("take-snapshot-btn"); - takeSnapshotBtn.exclude(); - - const snapshotsBtn = this.getChildControl("snapshots-btn"); - snapshotsBtn.exclude(); - const startStopBtns = this._startStopBtns = this.getChildControl("start-stop-btns"); startStopBtns.exclude(); }, @@ -97,21 +60,6 @@ qx.Class.define("osparc.desktop.WorkbenchToolbar", { if (study) { const nodeIds = study.getWorkbench().getPathIds(study.getUi().getCurrentNodeId()); this.__navNodes.populateButtons(nodeIds); - - const takeSnapshotBtn = this.getChildControl("take-snapshot-btn"); - takeSnapshotBtn.setVisibility(osparc.data.Permissions.getInstance().canDo("study.snapshot.create") ? "visible" : "excluded"); - - study.getWorkbench().addListener("nNodesChanged", this.evalSnapshotsBtn, this); - this.evalSnapshotsBtn(); - } - }, - - evalSnapshotsBtn: async function() { - const study = this.getStudy(); - if (study) { - const hasSnapshots = await study.hasSnapshots(); - const snapshotsBtn = this.getChildControl("snapshots-btn"); - hasSnapshots ? snapshotsBtn.show() : snapshotsBtn.exclude(); } }, diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js index 2380346e681..98cebf81b21 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js @@ -25,28 +25,16 @@ qx.Class.define("osparc.desktop.WorkbenchView", { construct: function() { this.base(arguments, "horizontal"); - const sidePanel = this.__sidePanel = new osparc.desktop.SidePanel().set({ - minWidth: 0, - width: Math.min(parseInt(window.innerWidth * 0.25), 350) - }); - osparc.utils.Utils.addBorder(sidePanel, 2, "right"); - const scroll = this.__scrollContainer = new qx.ui.container.Scroll().set({ - minWidth: 0 - }); - scroll.add(sidePanel); + this.getChildControl("splitter").setWidth(1); - this.add(scroll, 0); // flex 0 - - const mainPanel = this.__mainPanel = new osparc.desktop.MainPanel(); - this.add(mainPanel, 1); // flex 1 + this.__sidePanels = this.getChildControl("side-panels"); + this.getChildControl("main-panel-tabs"); + this.__workbenchPanel = new osparc.desktop.WorkbenchPanel(); + this.__workbenchUI = this.__workbenchPanel.getMainView(); this.__attachEventHandlers(); }, - events: { - "startSnapshot": "qx.event.type.Data" - }, - properties: { study: { check: "osparc.data.model.Study", @@ -56,28 +44,379 @@ qx.Class.define("osparc.desktop.WorkbenchView", { }, members: { - __sidePanel: null, - __scrollContainer: null, - __mainPanel: null, - __workbenchUI: null, - __nodeView: null, - __groupNodeView: null, + __sidePanels: null, + __studyTreeItem: null, __nodesTree: null, - __extraView: null, + __filesTree: null, + __storagePage: null, + __settingsPage: null, + __outputsPage: null, + __workbenchPanel: null, + __workbenchPanelPage: null, + __workbenchUI: null, + __iframePage: null, __loggerView: null, __currentNodeId: null, + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "side-panels": { + control = new qx.ui.splitpane.Pane("horizontal").set({ + minWidth: 0, + width: Math.min(parseInt(window.innerWidth * 0.4), 550) + }); + control.getChildControl("splitter").setWidth(1); + osparc.utils.Utils.addBorder(control, 2, "right"); + this.add(control, 0); // flex 0 + break; + } + case "side-panel-left-tabs": { + control = new qx.ui.tabview.TabView().set({ + minWidth: 250, + contentPadding: 6, + barPosition: "top" + }); + osparc.utils.Utils.addBorder(control, 2, "right"); + const sidePanels = this.getChildControl("side-panels"); + sidePanels.add(control, 1); // flex 1 + break; + } + case "side-panel-right-tabs": { + control = new qx.ui.tabview.TabView().set({ + minWidth: 300, + contentPadding: 6, + barPosition: "top" + }); + const sidePanels = this.getChildControl("side-panels"); + sidePanels.add(control, 1); // flex 1 + break; + } + case "main-panel-tabs": { + control = new qx.ui.tabview.TabView().set({ + contentPadding: 0, + barPosition: "top" + }); + this.add(control, 1); // flex 1 + break; + } + } + return control || this.base(arguments, id); + }, + _applyStudy: function(study) { if (study) { this.__initViews(); this.__connectEvents(); this.__attachSocketEventHandlers(); } - this.__mainPanel.getToolbar().setStudy(study); + this.__workbenchPanel.getToolbar().setStudy(study); + }, + + __createTabPage: function(icon, tooltip, widget) { + const tabPage = new qx.ui.tabview.Page().set({ + layout: new qx.ui.layout.VBox(5), + backgroundColor: "background-main", + icon: icon+"/24" + }); + tabPage.getChildControl("button").set({ + toolTipText: tooltip + }); + if (widget) { + tabPage.add(widget, { + flex: 1 + }); + } + return tabPage; + }, + + __initViews: function() { + const study = this.getStudy(); + if (study === null) { + return; + } + this.__initPrimaryColumn(); + this.__initSecondaryColumn(); + this.__initMainView(); + }, + + __initPrimaryColumn: function() { + const study = this.getStudy(); + + const tabViewPrimary = this.getChildControl("side-panel-left-tabs"); + this.__removePages(tabViewPrimary); + + const topBar = tabViewPrimary.getChildControl("bar"); + this.__addTopBarSpacer(topBar); + + const homeAndNodesTree = new qx.ui.container.Composite(new qx.ui.layout.VBox(6)); + + const studyTreeItem = this.__studyTreeItem = new osparc.component.widget.StudyTitleOnlyTree().set({ + minHeight: 21, + maxHeight: 24 + }); + studyTreeItem.setStudy(study); + homeAndNodesTree.add(studyTreeItem); + + const nodesTree = this.__nodesTree = new osparc.component.widget.NodesTree().set({ + hideRoot: true + }); + nodesTree.setStudy(study); + homeAndNodesTree.add(nodesTree, { + flex: 1 + }); + const nodesPage = this.__createTabPage("@FontAwesome5Solid/list", this.tr("Nodes"), homeAndNodesTree); + tabViewPrimary.add(nodesPage); + + const filesTree = this.__filesTree = new osparc.file.FilesTree().set({ + dragMechanism: true, + hideRoot: true + }); + filesTree.populateTree(); + const storagePage = this.__storagePage = this.__createTabPage("@FontAwesome5Solid/database", this.tr("Storage"), filesTree); + tabViewPrimary.add(storagePage); + + this.__addTopBarSpacer(topBar); + }, + + __initSecondaryColumn: function() { + const tabViewSecondary = this.getChildControl("side-panel-right-tabs"); + this.__removePages(tabViewSecondary); + + const topBar = tabViewSecondary.getChildControl("bar"); + this.__addTopBarSpacer(topBar); + + const infoPage = this.__infoPage = this.__createTabPage("@FontAwesome5Solid/info", this.tr("Information")); + infoPage.exclude(); + tabViewSecondary.add(infoPage); + + const settingsPage = this.__settingsPage = this.__createTabPage("@FontAwesome5Solid/sign-in-alt", this.tr("Settings")); + settingsPage.exclude(); + tabViewSecondary.add(settingsPage); + + const outputsPage = this.__outputsPage = this.__createTabPage("@FontAwesome5Solid/sign-out-alt", this.tr("Outputs")); + osparc.utils.Utils.setIdToWidget(outputsPage.getChildControl("button"), "outputsTabButton"); + outputsPage.exclude(); + tabViewSecondary.add(outputsPage); + + this.__addTopBarSpacer(topBar); + + this.__populateSecondPanel(); + }, + + __initMainView: function() { + const study = this.getStudy(); + + const tabViewMain = this.getChildControl("main-panel-tabs"); + this.__removePages(tabViewMain); + + const topBar = tabViewMain.getChildControl("bar"); + this.__addTopBarSpacer(topBar); + + this.__workbenchUI.setStudy(study); + this.__workbenchUI.loadModel(study.getWorkbench()); + const workbenchPanelPage = this.__workbenchPanelPage = this.__createTabPage("@FontAwesome5Solid/object-group", this.tr("Workbench"), this.__workbenchPanel); + tabViewMain.add(workbenchPanelPage); + + const iframePage = this.__iframePage = this.__createTabPage("@FontAwesome5Solid/desktop", this.tr("Interactive")); + osparc.utils.Utils.setIdToWidget(iframePage.getChildControl("button"), "iframeTabButton"); + tabViewMain.add(iframePage); + + const loggerView = this.__loggerView = new osparc.component.widget.logger.LoggerView(); + const logsPage = this.__logsPage = this.__createTabPage("@FontAwesome5Solid/file-alt", this.tr("Logger"), loggerView); + osparc.utils.Utils.setIdToWidget(logsPage.getChildControl("button"), "loggerTabButton"); + tabViewMain.add(logsPage); + + this.__addTopBarSpacer(topBar); + }, + + __removePages: function(tabView) { + const pages = tabView.getChildren(); + // remove pages + for (let i=pages.length-1; i>=0; i--) { + tabView.remove(pages[i]); + } + // remove spacers + const topBar = tabView.getChildControl("bar"); + topBar.removeAll(); + }, + + __addTopBarSpacer: function(tabViewTopBar) { + const spacer = new qx.ui.core.Spacer(); + tabViewTopBar.add(spacer, { + flex: 1 + }); + }, + + __connectEvents: function() { + const studyTreeItem = this.__studyTreeItem; + const nodesTree = this.__nodesTree; + const workbenchUI = this.__workbenchUI; + + studyTreeItem.addListener("nodeSelected", () => { + nodesTree.resetSelection(); + this.__populateSecondPanel(this.getStudy()); + this.__evalIframe(); + this.__openWorkbenchTab(); + }); + + nodesTree.addListener("nodeSelected", e => { + studyTreeItem.resetSelection(); + const nodeId = e.getData(); + const workbench = this.getStudy().getWorkbench(); + const node = workbench.getNode(nodeId); + if (node) { + this.__populateSecondPanel(node); + if (node.isDynamic()) { + this.__openIframeTab(node); + } else { + this.__openWorkbenchTab(); + } + } + const nodeUI = workbenchUI.getNodeUI(nodeId); + if (nodeUI) { + if (nodeUI.classname.includes("NodeUI")) { + workbenchUI.activeNodeChanged(nodeUI); + } + } + }); + nodesTree.addListener("fullscreenNode", e => { + studyTreeItem.resetSelection(); + const nodeId = e.getData(); + const workbench = this.getStudy().getWorkbench(); + const node = workbench.getNode(nodeId); + if (node) { + this.__populateSecondPanel(node); + this.__openIframeTab(node); + this.__maximizeIframe(true); + } + const nodeUI = workbenchUI.getNodeUI(nodeId); + if (nodeUI) { + if (nodeUI.classname.includes("NodeUI")) { + workbenchUI.activeNodeChanged(nodeUI); + } + } + }, this); + nodesTree.addListener("removeNode", e => { + const nodeId = e.getData(); + this.__removeNode(nodeId); + }, this); + + workbenchUI.addListener("removeNode", e => { + const nodeId = e.getData(); + this.__removeNode(nodeId); + }, this); + workbenchUI.addListener("removeEdge", e => { + const edgeId = e.getData(); + this.__removeEdge(edgeId); + }, this); + workbenchUI.addListener("changeSelectedNode", e => { + studyTreeItem.resetSelection(); + const nodeId = e.getData(); + this.__nodesTree.nodeSelected(nodeId); + const workbench = this.getStudy().getWorkbench(); + const node = workbench.getNode(nodeId); + this.__populateSecondPanel(node); + this.__evalIframe(node); + }); + workbenchUI.addListener("nodeSelected", e => { + studyTreeItem.resetSelection(); + const nodeId = e.getData(); + this.__nodesTree.nodeSelected(nodeId); + const workbench = this.getStudy().getWorkbench(); + const node = workbench.getNode(nodeId); + this.__populateSecondPanel(node); + this.__openIframeTab(node); + }, this); + + const workbench = this.getStudy().getWorkbench(); + workbench.addListener("pipelineChanged", this.__workbenchChanged, this); + + workbench.addListener("showInLogger", e => { + const data = e.getData(); + const nodeId = data.nodeId; + const msg = data.msg; + this.getLogger().info(nodeId, msg); + }, this); + + workbench.addListener("fileRequested", () => { + if (this.getStudy().getUi().getMode() === "workbench") { + const tabViewLeftPanel = this.getChildControl("side-panel-left-tabs"); + tabViewLeftPanel.setSelection([this.__storagePage]); + } + }, this); + }, + + __attachSocketEventHandlers: function() { + // Listen to socket + const socket = osparc.wrapper.WebSocket.getInstance(); + + // callback for incoming logs + const slotName = "logger"; + if (!socket.slotExists(slotName)) { + socket.on(slotName, function(jsonString) { + const data = JSON.parse(jsonString); + if (Object.prototype.hasOwnProperty.call(data, "project_id") && this.getStudy().getUuid() !== data["project_id"]) { + // Filtering out logs from other studies + return; + } + const nodeId = data["Node"]; + const messages = data["Messages"]; + this.getLogger().infos(nodeId, messages); + const nodeLogger = this.__getNodeLogger(nodeId); + if (nodeLogger) { + nodeLogger.infos(nodeId, messages); + } + }, this); + } + socket.emit(slotName); + + // callback for incoming progress + const slotName2 = "progress"; + if (!socket.slotExists(slotName2)) { + socket.on(slotName2, function(data) { + const d = JSON.parse(data); + const nodeId = d["Node"]; + const progress = Number.parseFloat(d["Progress"]).toFixed(4); + const workbench = this.getStudy().getWorkbench(); + const node = workbench.getNode(nodeId); + if (node) { + node.getStatus().setProgress(progress); + } else if (osparc.data.Permissions.getInstance().isTester()) { + console.log("Ignored ws 'progress' msg", d); + } + }, this); + } + + // callback for node updates + const slotName3 = "nodeUpdated"; + if (!socket.slotExists(slotName3)) { + socket.on(slotName3, data => { + const d = JSON.parse(data); + const nodeId = d["Node"]; + const nodeData = d["data"]; + const workbench = this.getStudy().getWorkbench(); + const node = workbench.getNode(nodeId); + if (node && nodeData) { + node.setOutputData(nodeData.outputs); + if ("progress" in nodeData) { + const progress = Number.parseInt(nodeData["progress"]); + node.getStatus().setProgress(progress); + } + node.populateStates(nodeData); + } else if (osparc.data.Permissions.getInstance().isTester()) { + console.log("Ignored ws 'nodeUpdated' msg", d); + } + }, this); + } + }, + + __createPanelView: function(caption, widget) { + return new osparc.desktop.PanelView(caption, widget); }, getStartStopButtons: function() { - return this.__mainPanel.getToolbar().getStartStopButtons(); + return this.__workbenchPanel.getToolbar().getStartStopButtons(); }, getSelectedNodes: function() { @@ -85,7 +424,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", { }, getSelectedNodeIDs: function() { - if (this.__mainPanel.getMainView() === this.__workbenchUI) { + if (this.__workbenchPanel.getMainView() === this.__workbenchUI) { return this.__workbenchUI.getSelectedNodeIDs(); } return [this.__currentNodeId]; @@ -93,53 +432,188 @@ qx.Class.define("osparc.desktop.WorkbenchView", { nodeSelected: function(nodeId) { const study = this.getStudy(); - if (nodeId === null) { nodeId = study.getUuid(); } - const workbench = study.getWorkbench(); - const node = workbench.getNode(nodeId); - if (node && node.isParameter()) { - const parameterEditor = new osparc.component.node.ParameterEditor(node); - parameterEditor.popUpInWindow(); - return; - } + this.__currentNodeId = nodeId; + study.getUi().setCurrentNodeId(nodeId); if (this.__nodesTree) { - this.__nodesTree.setCurrentNodeId(nodeId); + this.__nodesTree.nodeSelected(nodeId); } - if (this.__nodeView) { - this.__nodeView.restoreIFrame(); + + const node = study.getWorkbench().getNode(nodeId); + this.__populateSecondPanel(node); + }, + + __evalIframe: function(node) { + if (node && node.getIFrame()) { + this.__iframePage.getChildControl("button").set({ + enabled: true + }); + this.__addIframe(node); + } else { + this.__iframePage.getChildControl("button").set({ + enabled: false + }); + } + }, + + __openWorkbenchTab: function() { + const tabViewMain = this.getChildControl("main-panel-tabs"); + tabViewMain.setSelection([this.__workbenchPanelPage]); + }, + + __openIframeTab: function(node) { + this.__evalIframe(node); + const tabViewMain = this.getChildControl("main-panel-tabs"); + if (node && node.getIFrame()) { + tabViewMain.setSelection([this.__iframePage]); + } else { + tabViewMain.setSelection([this.__workbenchPanelPage]); } - if (this.__groupNodeView) { - this.__groupNodeView.restoreIFrame(); + }, + + __maximizeIframe: function(maximize) { + this.getBlocker().setStyles({ + display: maximize ? "none" : "block" + }); + + this.getChildControl("side-panels").setVisibility(maximize ? "excluded" : "visible"); + + const tabViewMain = this.getChildControl("main-panel-tabs"); + const mainViewtopBar = tabViewMain.getChildControl("bar"); + mainViewtopBar.setVisibility(maximize ? "excluded" : "visible"); + }, + + __addIframe: function(node) { + this.__iframePage.removeAll(); + + const loadingPage = node.getLoadingPage(); + const iFrame = node.getIFrame(); + if (loadingPage && iFrame) { + [ + loadingPage, + iFrame + ].forEach(widget => { + if (widget) { + widget.addListener("maximize", () => this.__maximizeIframe(true), this); + widget.addListener("restore", () => this.__maximizeIframe(false), this); + } + }); + this.__iFrameChanged(node); + + iFrame.addListener("load", () => this.__iFrameChanged(node), this); + } else { + // This will keep what comes after at the bottom + this.__iframePage.add(new qx.ui.core.Spacer(), { + flex: 1 + }); } - const prevNodeId = this.__currentNodeId; - this.__currentNodeId = nodeId; - study.getUi().setCurrentNodeId(nodeId); + }, - if (node === null || nodeId === study.getUuid()) { - this.__showInMainView(this.__workbenchUI, study.getUuid()); - this.__workbenchUI.loadModel(workbench); - } else if (node.isContainer()) { - this.__groupNodeView.setNode(node); - this.__showInMainView(this.__workbenchUI, nodeId); - this.__workbenchUI.loadModel(node); - this.__groupNodeView.populateLayout(); - } else if (node.isFilePicker()) { - const nodeView = new osparc.component.node.FilePickerNodeView(); - nodeView.setNode(node); - this.__showInMainView(nodeView, nodeId); - nodeView.populateLayout(); - nodeView.addListener("itemSelected", () => { - this.nodeSelected(prevNodeId); - }, this); + __iFrameChanged: function(node) { + this.__iframePage.removeAll(); + + 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 + }); + }, + + __populateSecondPanel: function(node) { + this.__infoPage.removeAll(); + this.__settingsPage.removeAll(); + this.__outputsPage.removeAll(); + this.__infoPage.getChildControl("button").exclude(); + this.__settingsPage.getChildControl("button").exclude(); + this.__outputsPage.getChildControl("button").exclude(); + + if (node instanceof osparc.data.model.Study) { + this.__populateSecondPanelStudy(node); + } else if (node && node.isFilePicker()) { + this.__populateSecondPanelFilePicker(node); + } else if (node && node.isParameter()) { + this.__populateSecondPanelParameter(node); + } else if (node) { + this.__populateSecondPanelNode(node); + } + }, + + __populateSecondPanelStudy: function(study) { + this.__infoPage.getChildControl("button").show(); + this.getChildControl("side-panel-right-tabs").setSelection([this.__infoPage]); + + this.__infoPage.add(new osparc.studycard.Medium(study), { + flex: 1 + }); + }, + + __populateSecondPanelFilePicker: function(filePicker) { + if (osparc.file.FilePicker.hasOutputAssigned(filePicker.getOutputs())) { + this.__infoPage.getChildControl("button").show(); + this.getChildControl("side-panel-right-tabs").setSelection([this.__infoPage]); + + const view = osparc.file.FilePicker.buildInfoView(filePicker); + view.setEnabled(false); + this.__infoPage.add(view); } else { - this.__nodeView.setNode(node); - this.__showInMainView(this.__nodeView, nodeId); - this.__nodeView.populateLayout(); + this.__settingsPage.getChildControl("button").show(); + this.getChildControl("side-panel-right-tabs").setSelection([this.__settingsPage]); + + const filePickerView = new osparc.file.FilePicker(filePicker); + filePickerView.buildLayout(); + filePickerView.getChildControl("files-tree").set({ + hideRoot: true, + showLeafs: true + }); + filePickerView.getChildControl("folder-viewer").exclude(); + filePickerView.getChildControl("files-add").exclude(); + filePickerView.getChildControl("selected-file-layout").getChildControl("download-button").exclude(); + filePickerView.addListener("itemSelected", () => this.__populateSecondPanel(filePicker)); + this.__settingsPage.add(filePickerView, { + flex: 1 + }); + } + }, + + __populateSecondPanelParameter: function(parameter) { + this.__settingsPage.getChildControl("button").show(); + this.getChildControl("side-panel-right-tabs").setSelection([this.__settingsPage]); + + const view = new osparc.component.node.ParameterEditor(parameter); + view.buildForm(false); + this.__settingsPage.add(view, { + flex: 1 + }); + }, + + __populateSecondPanelNode: function(node) { + this.__settingsPage.getChildControl("button").show(); + this.__outputsPage.getChildControl("button").show(); + this.getChildControl("side-panel-right-tabs").setSelection([this.__settingsPage]); + + if (node.isPropertyInitialized("propsForm") && node.getPropsForm()) { + this.__settingsPage.add(node.getPropsForm(), { + flex: 1 + }); } + + const portTree = new osparc.component.widget.inputs.NodeOutputTree(node, node.getMetaData().outputs).set({ + allowGrowY: false + }); + this.__outputsPage.add(portTree); + + const outputFilesBtn = new qx.ui.form.Button(this.tr("Artifacts"), "@FontAwesome5Solid/folder-open/14").set({ + allowGrowX: false + }); + osparc.utils.Utils.setIdToWidget(outputFilesBtn, "nodeOutputFilesBtn"); + outputFilesBtn.addListener("execute", () => osparc.component.node.BaseNodeView.openNodeDataManager(node)); + this.__outputsPage.add(outputFilesBtn); }, getLogger: function() { @@ -169,79 +643,6 @@ qx.Class.define("osparc.desktop.WorkbenchView", { }); }, - __takeSnapshot: function() { - const editSnapshotView = new osparc.component.snapshots.EditSnapshotView(); - const tagCtrl = editSnapshotView.getChildControl("tags"); - const study = this.getStudy(); - study.getSnapshots() - .then(snapshots => { - tagCtrl.setValue("V"+snapshots.length); - }); - const title = this.tr("Take Snapshot"); - const win = osparc.ui.window.Window.popUpInWindow(editSnapshotView, title, 400, 180); - editSnapshotView.addListener("takeSnapshot", () => { - const tag = editSnapshotView.getTag(); - const message = editSnapshotView.getMessage(); - const workbenchToolbar = this.__mainPanel.getToolbar(); - const takeSnapshotBtn = workbenchToolbar.getChildControl("take-snapshot-btn"); - takeSnapshotBtn.setFetching(true); - const params = { - url: { - "studyId": study.getUuid() - }, - data: { - "tag": tag, - "message": message - } - }; - osparc.data.Resources.fetch("snapshots", "takeSnapshot", params) - .then(data => { - workbenchToolbar.evalSnapshotsBtn(); - }) - .catch(err => osparc.component.message.FlashMessenger.getInstance().logAs(err.message, "ERROR")) - .finally(takeSnapshotBtn.setFetching(false)); - - win.close(); - }, this); - editSnapshotView.addListener("cancel", () => { - win.close(); - }, this); - }, - - __showSnapshots: function() { - const study = this.getStudy(); - const snapshots = new osparc.component.snapshots.SnapshotsView(study); - const title = this.tr("Snapshots"); - const win = osparc.ui.window.Window.popUpInWindow(snapshots, title, 1000, 500); - snapshots.addListener("openSnapshot", e => { - win.close(); - const snapshotId = e.getData(); - this.fireDataEvent("startSnapshot", snapshotId); - }); - }, - - __showWorkbenchUI: function() { - const workbench = this.getStudy().getWorkbench(); - const currentNode = workbench.getNode(this.__currentNodeId); - if (currentNode === this.__workbenchUI.getCurrentModel()) { - this.__showInMainView(this.__workbenchUI, this.__currentNodeId); - } else { - osparc.component.message.FlashMessenger.getInstance().logAs("No Workbench view for this node", "ERROR"); - } - }, - - __showSettings: function() { - const workbench = this.getStudy().getWorkbench(); - const currentNode = workbench.getNode(this.__currentNodeId); - if (this.__groupNodeView.isPropertyInitialized("node") && currentNode === this.__groupNodeView.getNode()) { - this.__showInMainView(this.__groupNodeView, this.__currentNodeId); - } else if (this.__nodeView.isPropertyInitialized("node") && currentNode === this.__nodeView.getNode()) { - this.__showInMainView(this.__nodeView, this.__currentNodeId); - } else { - osparc.component.message.FlashMessenger.getInstance().logAs("No Settings view for this node", "ERROR"); - } - }, - __isSelectionEmpty: function(selectedNodeUIs) { if (selectedNodeUIs === null || selectedNodeUIs.length === 0) { return true; @@ -305,19 +706,9 @@ qx.Class.define("osparc.desktop.WorkbenchView", { this.__workbenchUI.resetSelectedNodes(); }, - __maximizeIframe: function(maximize) { - this.getBlocker().setStyles({ - display: maximize ? "none" : "block" - }); - this.__scrollContainer.setVisibility(maximize ? "excluded" : "visible"); - }, - __attachEventHandlers: function() { - const blocker = this.getBlocker(); - blocker.addListener("tap", this.__sidePanel.toggleCollapsed.bind(this.__sidePanel)); - - const splitter = this.getChildControl("splitter"); - splitter.setWidth(1); + // const blocker = this.getBlocker(); + // blocker.addListener("tap", this.getChildControl("side-panels").toggleCollapsed.bind(this.getChildControl("side-panels"))); const maximizeIframeCb = msg => { this.__maximizeIframe(msg.getData()); @@ -330,76 +721,10 @@ qx.Class.define("osparc.desktop.WorkbenchView", { this.addListener("disappear", () => { qx.event.message.Bus.getInstance().unsubscribe("maximizeIframe", maximizeIframeCb, this); }, this); - - const controlsBar = this.__mainPanel.getControls(); - controlsBar.addListener("showWorkbench", this.__showWorkbenchUI, this); - controlsBar.addListener("showSettings", this.__showSettings, this); - controlsBar.addListener("groupSelection", this.__groupSelection, this); - controlsBar.addListener("ungroupSelection", this.__ungroupSelection, this); - }, - - __initViews: function() { - const study = this.getStudy(); - - const nodesTree = this.__nodesTree = new osparc.component.widget.NodesTree(); - nodesTree.setStudy(study); - nodesTree.addListener("removeNode", e => { - if (this.getStudy().isReadOnly()) { - return; - } - - const nodeId = e.getData(); - this.__removeNode(nodeId); - }, this); - this.__sidePanel.addOrReplaceAt(new osparc.desktop.PanelView(this.tr("Nodes"), nodesTree), 0, { - flex: 1 - }); - - const extraView = this.__extraView = new osparc.studycard.Medium(study); - this.__sidePanel.addListener("panelResized", e => { - const bounds = e.getData(); - extraView.checkResize(bounds); - }, this); - this.__sidePanel.addOrReplaceAt(new osparc.desktop.PanelView(this.tr("Study information"), extraView), 1, { - flex: 1 - }); - - const loggerView = this.__loggerView = new osparc.component.widget.logger.LoggerView(); - const loggerPanel = new osparc.desktop.PanelView(this.tr("Logger"), loggerView); - osparc.utils.Utils.setIdToWidget(loggerPanel.getTitleLabel(), "studyLoggerTitleLabel"); - this.__sidePanel.addOrReplaceAt(loggerPanel, 2, { - flex: 1 - }); - if (!osparc.data.Permissions.getInstance().canDo("study.logger.debug.read")) { - loggerPanel.setCollapsed(true); - } - - const workbenchUI = this.__workbenchUI = new osparc.component.workbench.WorkbenchUI(study.getWorkbench()); - workbenchUI.setStudy(study); - workbenchUI.addListener("removeNode", e => { - const nodeId = e.getData(); - this.__removeNode(nodeId); - }, this); - workbenchUI.addListener("removeEdge", e => { - const edgeId = e.getData(); - this.__removeEdge(edgeId); - }, this); - - this.__nodeView = new osparc.component.node.NodeView().set({ - minHeight: 200 - }); - - this.__groupNodeView = new osparc.component.node.GroupNodeView().set({ - minHeight: 200 - }); }, __removeNode: function(nodeId) { - if (nodeId === this.__currentNodeId) { - return; - } - const preferencesSettings = osparc.desktop.preferences.Preferences.getInstance(); if (preferencesSettings.getConfirmDeleteNode()) { const msg = this.tr("Are you sure you want to delete node?"); @@ -427,6 +752,9 @@ qx.Class.define("osparc.desktop.WorkbenchView", { } this.__workbenchUI.clearNode(nodeId); } + if (this.__nodesTree.getCurrentNodeId() === this.__currentNodeId) { + this.nodeSelected(this.getStudy().getUuid()); + } }, __removeEdge: function(edgeId) { @@ -456,200 +784,27 @@ qx.Class.define("osparc.desktop.WorkbenchView", { } }, - __showInMainView: function(widget, nodeId) { - this.__mainPanel.setMainView(widget); - - if (widget.getNode && widget.getInputsView) { - setTimeout(() => { - widget.getInputsView().setCollapsed(widget.getNode().getInputNodes().length === 0); - }, 150); - } - - this.__nodesTree.nodeSelected(nodeId); - this.__loggerView.setCurrentNodeId(nodeId); - - const controlsBar = this.__mainPanel.getControls(); - controlsBar.setWorkbenchVisibility(widget === this.__workbenchUI); - controlsBar.setExtraViewVisibility(this.__groupNodeView && this.__groupNodeView.getNode() && nodeId === this.__groupNodeView.getNode().getNodeId()); - }, - __workbenchChanged: function() { this.__nodesTree.populateTree(); this.__nodesTree.nodeSelected(this.__currentNodeId); }, - __connectEvents: function() { - const workbench = this.getStudy().getWorkbench(); - workbench.addListener("pipelineChanged", this.__workbenchChanged, this); - - workbench.addListener("showInLogger", ev => { - const data = ev.getData(); - const nodeId = data.nodeId; - const msg = data.msg; - this.getLogger().info(nodeId, msg); - }, this); - - const workbenchUI = this.__workbenchUI; - const workbenchToolbar = this.__mainPanel.getToolbar(); - const nodesTree = this.__nodesTree; - [ - nodesTree, - workbenchToolbar, - workbenchUI - ].forEach(widget => { - widget.addListener("nodeSelected", e => { - const nodeId = e.getData(); - this.nodeSelected(nodeId); - }, this); - }); - if (!workbenchToolbar.hasListener("takeSnapshot")) { - workbenchToolbar.addListener("takeSnapshot", this.__takeSnapshot, this); - } - if (!workbenchToolbar.hasListener("showSnapshots")) { - workbenchToolbar.addListener("showSnapshots", this.__showSnapshots, this); - } - - nodesTree.addListener("changeSelectedNode", e => { - const node = workbenchUI.getNodeUI(e.getData()); - if (node && node.classname.includes("NodeUI")) { - node.setActive(true); - } - }); - nodesTree.addListener("exportNode", e => { - const nodeId = e.getData(); - this.__exportMacro(nodeId); - }); - - workbenchUI.addListener("changeSelectedNode", e => { - const nodeId = e.getData(); - nodesTree.nodeSelected(nodeId); - }); - }, - - __exportMacro: function(nodeId) { - if (!osparc.data.Permissions.getInstance().canDo("study.node.export", true)) { - return; - } - const node = this.getStudy().getWorkbench().getNode(nodeId); - if (node && node.isContainer()) { - const exportDAGView = new osparc.component.study.ExportDAG(node); - const window = new qx.ui.window.Window(this.tr("Export: ") + node.getLabel()).set({ - appearance: "service-window", - layout: new qx.ui.layout.Grow(), - autoDestroy: true, - contentPadding: 0, - width: 700, - height: 700, - showMinimize: false, - modal: true - }); - window.add(exportDAGView); - window.center(); - window.open(); - - window.addListener("close", () => { - exportDAGView.tearDown(); - }, this); - - exportDAGView.addListener("finished", () => { - window.close(); - }, this); - } - }, - - __attachSocketEventHandlers: function() { - // Listen to socket - const socket = osparc.wrapper.WebSocket.getInstance(); - - // callback for incoming logs - const slotName = "logger"; - if (!socket.slotExists(slotName)) { - socket.on(slotName, function(jsonString) { - const data = JSON.parse(jsonString); - if (Object.prototype.hasOwnProperty.call(data, "project_id") && this.getStudy().getUuid() !== data["project_id"]) { - // Filtering out logs from other studies - return; - } - const nodeId = data["Node"]; - const messages = data["Messages"]; - this.getLogger().infos(nodeId, messages); - const nodeLogger = this.__getNodeLogger(nodeId); - if (nodeLogger) { - nodeLogger.infos(nodeId, messages); - } - }, this); - } - socket.emit(slotName); - - // callback for incoming progress - const slotName2 = "progress"; - if (!socket.slotExists(slotName2)) { - socket.on(slotName2, function(data) { - const d = JSON.parse(data); - const nodeId = d["Node"]; - const progress = Number.parseFloat(d["Progress"]).toFixed(4); - const workbench = this.getStudy().getWorkbench(); - const node = workbench.getNode(nodeId); - if (node) { - node.getStatus().setProgress(progress); - } else if (osparc.data.Permissions.getInstance().isTester()) { - console.log("Ignored ws 'progress' msg", d); - } - }, this); - } - - // callback for node updates - const slotName3 = "nodeUpdated"; - if (!socket.slotExists(slotName3)) { - socket.on(slotName3, data => { - const d = JSON.parse(data); - const nodeId = d["Node"]; - const nodeData = d["data"]; - const workbench = this.getStudy().getWorkbench(); - const node = workbench.getNode(nodeId); - if (node && nodeData) { - node.setOutputData(nodeData.outputs); - if ("progress" in nodeData) { - const progress = Number.parseInt(nodeData["progress"]); - node.getStatus().setProgress(progress); - } - node.populateStates(nodeData); - } else if (osparc.data.Permissions.getInstance().isTester()) { - console.log("Ignored ws 'nodeUpdated' msg", d); - } - }, this); - } - }, - - __checkMaximizeable: function() { - this.__scrollContainer.setVisibility("visible"); - this.__nodeView._maximizeIFrame(false); // eslint-disable-line no-underscore-dangle - const node = this.getStudy().getWorkbench().getNode(this.__currentNodeId); - if (node && node.getIFrame() && (node.getInputNodes().length === 0)) { - node.getLoadingPage().maximizeIFrame(true); - node.getIFrame().maximizeIFrame(true); - } - }, - openFirstNode: function() { - const validNodeIds = []; - const allNodes = this.getStudy().getWorkbench().getNodes(true); - Object.values(allNodes).forEach(node => { - if (!node.isFilePicker()) { - validNodeIds.push(node.getNodeId()); - } - }); - const preferencesSettings = osparc.desktop.preferences.Preferences.getInstance(); - if (validNodeIds.length === 1 && preferencesSettings.getAutoOpenNode()) { - this.nodeSelected(validNodeIds[0]); - // Todo Odei: A bit of a hack - qx.event.Timer.once(() => { - this.__checkMaximizeable(); - }, this, 10); - } else { - this.nodeSelected(this.getStudy().getUuid()); + if (preferencesSettings.getAutoOpenNode()) { + const nodes = this.getStudy().getWorkbench().getNodes(true); + const validNodes = Object.values(nodes).filter(node => node.isComputational() || node.isDynamic()); + if (validNodes.length === 1 && validNodes[0].isDynamic()) { + const dynamicNode = validNodes[0]; + this.nodeSelected(dynamicNode.getNodeId()); + qx.event.Timer.once(() => { + this.__openIframeTab(dynamicNode); + this.__maximizeIframe(true); + }, this, 10); + return; + } } + this.nodeSelected(this.getStudy().getUuid()); } } }); diff --git a/services/web/client/source/class/osparc/file/FilePicker.js b/services/web/client/source/class/osparc/file/FilePicker.js index c2a5cb00fa9..b878b79e477 100644 --- a/services/web/client/source/class/osparc/file/FilePicker.js +++ b/services/web/client/source/class/osparc/file/FilePicker.js @@ -68,7 +68,7 @@ qx.Class.define("osparc.file.FilePicker", { }, getOutputLabel: function(outputs) { - const outFileValue = this.getOutput(outputs); + const outFileValue = osparc.file.FilePicker.getOutput(outputs); if (outFileValue) { if ("label" in outFileValue) { return outFileValue.label; @@ -85,23 +85,115 @@ qx.Class.define("osparc.file.FilePicker", { }, isOutputFromStore: function(outputs) { - const outFileValue = this.getOutput(outputs); - return (outFileValue && typeof outFileValue === "object" && "path" in outFileValue); + const outFileValue = osparc.file.FilePicker.getOutput(outputs); + return (osparc.utils.Utils.isObject(outFileValue) && "path" in outFileValue); }, isOutputDownloadLink: function(outputs) { - const outFileValue = this.getOutput(outputs); - return (outFileValue && typeof outFileValue === "object" && "downloadLink" in outFileValue); + const outFileValue = osparc.file.FilePicker.getOutput(outputs); + return (osparc.utils.Utils.isObject(outFileValue) && "downloadLink" in outFileValue); }, extractLabelFromLink: function(outputs) { - const outFileValue = this.getOutput(outputs); + const outFileValue = osparc.file.FilePicker.getOutput(outputs); return osparc.file.FileDownloadLink.extractLabelFromLink(outFileValue["downloadLink"]); }, + hasOutputAssigned: function(outputs) { + return osparc.file.FilePicker.isOutputFromStore(outputs) || osparc.file.FilePicker.isOutputDownloadLink(outputs); + }, + + __setOutputValue: function(node, outputValue) { + const outputs = node.getOutputs(); + outputs["outFile"]["value"] = outputValue; + node.setOutputs({}); + node.setOutputs(outputs); + node.getStatus().setHasOutputs(true); + node.getStatus().setModified(false); + const outLabel = osparc.file.FilePicker.getOutputLabel(outputs); + if (outLabel) { + node.setLabel(outputValue.label); + } + node.getStatus().setProgress(100); + }, + + setOutputValueFromStore: function(node, store, dataset, path, label) { + if (store !== undefined && path) { + // eslint-disable-next-line no-underscore-dangle + osparc.file.FilePicker.__setOutputValue(node, { + store, + dataset, + path, + label + }); + } + }, + + __setOutputValueFromLink: function(node, downloadLink, label) { + if (downloadLink) { + // eslint-disable-next-line no-underscore-dangle + osparc.file.FilePicker.__setOutputValue(node, { + downloadLink, + label: label ? label : "" + }); + } + }, + + buildFileFromStoreInfoView: function(node, form) { + const outValue = osparc.file.FilePicker.getOutput(node.getOutputs()); + const params = { + url: { + locationId: outValue.store, + datasetId: outValue.dataset + } + }; + osparc.data.Resources.fetch("storageFiles", "getByLocationAndDataset", params) + .then(files => { + const fileMetadata = files.find(file => file.file_uuid === outValue.path); + if (fileMetadata) { + for (let [key, value] of Object.entries(fileMetadata)) { + const entry = new qx.ui.form.TextField(); + form.add(entry, key, null, key); + if (value) { + entry.setValue(value.toString()); + } + } + } + }); + }, + + buildDownloadLinkInfoView: function(node, form) { + const outputs = node.getOutputs(); + + const outFileValue = osparc.file.FilePicker.getOutput(outputs); + const urlEntry = new qx.ui.form.TextField().set({ + value: outFileValue + }); + form.add(urlEntry, "url", null, "url"); + + const label = osparc.file.FilePicker.extractLabelFromLink(outputs); + if (label) { + const labelEntry = new qx.ui.form.TextField().set({ + value: label + }); + form.add(labelEntry, "label", null, "label"); + } + }, + + buildInfoView: function(node) { + const form = new qx.ui.form.Form(); + if (osparc.file.FilePicker.isOutputFromStore(node.getOutputs())) { + this.self().buildFileFromStoreInfoView(node, form); + } else if (osparc.file.FilePicker.isOutputDownloadLink(node.getOutputs())) { + this.self().buildDownloadLinkInfoView(node, form); + } + + return new qx.ui.form.renderer.Single(form); + }, + serializeOutput: function(outputs) { let output = {}; - const outFileValue = this.self().getOutput(outputs); + const outFileValue = osparc.file.FilePicker.getOutput(outputs); if (outFileValue) { output["outFile"] = outFileValue; } @@ -201,6 +293,15 @@ qx.Class.define("osparc.file.FilePicker", { return control || this.base(arguments, id); }, + setOutputValueFromStore: function(store, dataset, path, label) { + this.self().setOutputValueFromStore(this.getNode(), store, dataset, path, label); + }, + + __setOutputValueFromLink: function(downloadLink, label) { + // eslint-disable-next-line no-underscore-dangle + this.self().__setOutputValueFromLink(this.getNode(), downloadLink, label); + }, + __reloadFilesTree: function() { if (this.__filesTree) { this.__selectedFileFound = false; @@ -265,7 +366,7 @@ qx.Class.define("osparc.file.FilePicker", { filesAdd.addListener("fileAdded", e => { const fileMetadata = e.getData(); if ("location" in fileMetadata && "dataset" in fileMetadata && "path" in fileMetadata && "name" in fileMetadata) { - this.__setOutputValueFromStore(fileMetadata["location"], fileMetadata["dataset"], fileMetadata["path"], fileMetadata["name"]); + this.setOutputValueFromStore(fileMetadata["location"], fileMetadata["dataset"], fileMetadata["path"], fileMetadata["name"]); } this.__reloadFilesTree(); filesTree.loadFilePath(this.__getOutputFile()["value"]); @@ -316,7 +417,7 @@ qx.Class.define("osparc.file.FilePicker", { __itemSelected: function() { const selectedItem = this.__selectedFileLayout.getItemSelected(); if (selectedItem && osparc.file.FilesTree.isFile(selectedItem)) { - this.__setOutputValueFromStore(selectedItem.getLocation(), selectedItem.getDatasetId(), selectedItem.getFileId(), selectedItem.getLabel()); + this.setOutputValueFromStore(selectedItem.getLocation(), selectedItem.getDatasetId(), selectedItem.getFileId(), selectedItem.getLabel()); this.fireEvent("itemSelected"); } }, @@ -326,40 +427,6 @@ qx.Class.define("osparc.file.FilePicker", { return outputs["outFile"]; }, - __setOutputValue: function(outputValue) { - const outputs = this.getNode().getOutputs(); - outputs["outFile"]["value"] = outputValue; - this.getNode().setOutputs({}); - this.getNode().setOutputs(outputs); - this.getNode().getStatus().setHasOutputs(true); - this.getNode().getStatus().setModified(false); - const outLabel = this.self().getOutputLabel(outputs); - if (outLabel) { - this.getNode().setLabel(outputValue.label); - } - this.getNode().getStatus().setProgress(100); - }, - - __setOutputValueFromStore: function(store, dataset, path, label) { - if (store !== undefined && path) { - this.__setOutputValue({ - store, - dataset, - path, - label - }); - } - }, - - __setOutputValueFromLink: function(downloadLink, label) { - if (downloadLink) { - this.__setOutputValue({ - downloadLink, - label: label ? label : "" - }); - } - }, - __checkSelectedFileIsListed: function() { if (this.__selectedFileFound === false && this.self().isOutputFromStore(this.getNode().getOutputs())) { const outFile = this.__getOutputFile(); diff --git a/services/web/client/source/class/osparc/file/FilesTree.js b/services/web/client/source/class/osparc/file/FilesTree.js index 1e565355c09..67fd2846dae 100644 --- a/services/web/client/source/class/osparc/file/FilesTree.js +++ b/services/web/client/source/class/osparc/file/FilesTree.js @@ -19,7 +19,7 @@ * VirtualTree that is able to build its content. * * Elements in the tree also accept Drag and/or Drop mechanisms which are implemented here. - * "osparc-filePath" type is used for the Drag&Drop. + * "osparc-file-link" type is used for the Drag&Drop. * * If a file is dropped into a folder, this class will start the copying proccess fireing * "fileCopied" event if successful @@ -207,7 +207,7 @@ qx.Class.define("osparc.file.FilesTree", { const treeName = "Node Files"; this.__resetTree(treeName); const rootModel = this.getModel(); - osparc.file.FilesTree.addLoadingChild(rootModel); + this.self().addLoadingChild(rootModel); const dataStore = osparc.store.Data.getInstance(); dataStore.getNodeFiles(nodeId) @@ -242,7 +242,7 @@ qx.Class.define("osparc.file.FilesTree", { this.__resetTree(treeName); const rootModel = this.getModel(); rootModel.getChildren().removeAll(); - osparc.file.FilesTree.addLoadingChild(rootModel); + this.self().addLoadingChild(rootModel); const dataStore = osparc.store.Data.getInstance(); dataStore.getLocations() @@ -263,7 +263,7 @@ qx.Class.define("osparc.file.FilesTree", { const locationModel = this.__getLocationModel(locationId); if (locationModel) { locationModel.getChildren().removeAll(); - osparc.file.FilesTree.addLoadingChild(locationModel); + this.self().addLoadingChild(locationModel); } } @@ -394,7 +394,7 @@ qx.Class.define("osparc.file.FilesTree", { __filesToRoot: function(files) { const currentModel = this.getModel(); - osparc.file.FilesTree.removeLoadingChild(currentModel); + this.self().removeLoadingChild(currentModel); files.forEach(file => file["pathLabel"] = currentModel.getPathLabel().concat(file["label"])); const newModelToAdd = qx.data.marshal.Json.createModel(files, true); @@ -425,7 +425,7 @@ qx.Class.define("osparc.file.FilesTree", { datasetData.loaded = false; datasetData["pathLabel"] = locationModel.getPathLabel().concat(datasetData["label"]); const datasetModel = qx.data.marshal.Json.createModel(datasetData, true); - osparc.file.FilesTree.addLoadingChild(datasetModel); + this.self().addLoadingChild(datasetModel); locationModel.getChildren().append(datasetModel); // add cached files @@ -578,11 +578,16 @@ qx.Class.define("osparc.file.FilesTree", { __createDragMechanism: function(treeItem) { treeItem.setDraggable(true); treeItem.addListener("dragstart", e => { - if (osparc.file.FilesTree.isFile(e.getOriginalTarget())) { + const origin = e.getOriginalTarget(); + if (this.self().isFile(origin)) { // Register supported actions e.addAction("copy"); // Register supported types - e.addType("osparc-filePath"); + e.addType("osparc-file-link"); + + e.addData("osparc-file-link", { + dragData: origin.getModel() + }); } else { e.preventDefault(); } @@ -593,8 +598,8 @@ qx.Class.define("osparc.file.FilesTree", { treeItem.setDroppable(true); treeItem.addListener("dragover", e => { let compatible = false; - if (osparc.file.FilesTree.isDir(e.getOriginalTarget())) { - if (e.supportsType("osparc-filePath")) { + if (this.self().isDir(e.getOriginalTarget())) { + if (e.supportsType("osparc-file-link")) { compatible = true; } } @@ -604,7 +609,7 @@ qx.Class.define("osparc.file.FilesTree", { }, this); treeItem.addListener("drop", e => { - if (e.supportsType("osparc-filePath")) { + if (e.supportsType("osparc-file-link")) { const from = e.getRelatedTarget(); const to = e.getCurrentTarget(); const dataStore = osparc.store.Data.getInstance(); diff --git a/services/web/client/source/class/osparc/navigation/NavigationBar.js b/services/web/client/source/class/osparc/navigation/NavigationBar.js index b6d5a9ddc15..52fde350e69 100644 --- a/services/web/client/source/class/osparc/navigation/NavigationBar.js +++ b/services/web/client/source/class/osparc/navigation/NavigationBar.js @@ -66,7 +66,9 @@ qx.Class.define("osparc.navigation.NavigationBar", { "slidesGuidedStart": "qx.event.type.Event", "slidesAppStart": "qx.event.type.Event", "slidesStop": "qx.event.type.Event", - "slidesEdit": "qx.event.type.Event" + "slidesEdit": "qx.event.type.Event", + "takeSnapshot": "qx.event.type.Event", + "showSnapshots": "qx.event.type.Event" }, properties: { @@ -103,50 +105,20 @@ qx.Class.define("osparc.navigation.NavigationBar", { members: { __serverStatics: null, - __startSlidesButton: null, - __startAppButton: null, - __EditSlidesButton: null, buildLayout: function() { this.getChildControl("logo"); - this._add(new qx.ui.core.Spacer(20)); + this._add(new qx.ui.core.Spacer(30)); this.getChildControl("dashboard-button"); this.getChildControl("dashboard-label"); - this._add(new qx.ui.core.Spacer(20)); - - this.getChildControl("slideshow-menu-button").set({ - visibility: "excluded" - }); - this.getChildControl("slideshow-stop").set({ - visibility: "excluded" - }); - - this._add(new qx.ui.core.Spacer(20)); + this._add(new qx.ui.core.Spacer(30)); + this.getChildControl("study-options-menu"); this.getChildControl("read-only-icon"); - const studyTitle = this.getChildControl("study-title"); - studyTitle.addListener("editValue", evt => { - if (evt.getData() !== studyTitle.getValue()) { - studyTitle.setFetching(true); - const params = { - name: evt.getData() - }; - this.getStudy().updateStudy(params) - .then(() => { - studyTitle.setFetching(false); - }) - .catch(err => { - studyTitle.setFetching(false); - console.error(err); - osparc.component.message.FlashMessenger.getInstance().logAs(this.tr("There was an error while updating the title."), "ERROR"); - }); - } - }, this); - this._add(new qx.ui.core.Spacer(), { flex: 1 }); @@ -163,20 +135,17 @@ qx.Class.define("osparc.navigation.NavigationBar", { _createChildControlImpl: function(id) { let control; switch (id) { - case "logo": { + case "logo": control = osparc.component.widget.LogoOnOff.getInstance(); this._add(control); break; - } case "dashboard-button": control = new osparc.ui.form.FetchButton(this.tr("Dashboard"), "@FontAwesome5Solid/arrow-left/16").set({ ...this.self().BUTTON_OPTIONS, font: "title-14" }); osparc.utils.Utils.setIdToWidget(control, "dashboardBtn"); - control.addListener("execute", () => { - this.fireEvent("dashboardPressed"); - }, this); + control.addListener("execute", () => this.fireEvent("dashboardPressed"), this); this._add(control); break; case "dashboard-label": @@ -185,6 +154,20 @@ qx.Class.define("osparc.navigation.NavigationBar", { }); this._add(control); break; + case "study-options-menu": + control = new osparc.navigation.StudyMenu(); + [ + "slidesGuidedStart", + "slidesAppStart", + "slidesStop", + "slidesEdit", + "takeSnapshot", + "showSnapshots" + ].forEach(signalName => { + control.addListener(signalName, () => this.fireEvent(signalName)); + }); + this._add(control); + break; case "read-only-icon": control = new qx.ui.basic.Image("@FontAwesome5Solid/eye/22").set({ visibility: "excluded", @@ -193,28 +176,10 @@ qx.Class.define("osparc.navigation.NavigationBar", { }); this._add(control); break; - case "slideshow-menu-button": - control = this.__createSlideMenuBtn(); - this._add(control); - break; - case "slideshow-stop": - control = this.__createSlideStopBtn(); - this._add(control); - break; - case "study-title": - control = new osparc.ui.form.EditLabel().set({ - visibility: "excluded", - labelFont: "title-14", - inputFont: "text-14", - editable: osparc.data.Permissions.getInstance().canDo("study.update") - }); - this._add(control); - break; - case "tasks-button": { + case "tasks-button": control = new osparc.component.task.TasksButton(); this._add(control); break; - } case "manual": control = this.__createManualMenuBtn(); control.set(this.self().BUTTON_OPTIONS); @@ -243,89 +208,19 @@ qx.Class.define("osparc.navigation.NavigationBar", { case "dashboard": this.getChildControl("dashboard-label").show(); this.getChildControl("dashboard-button").exclude(); + this.getChildControl("study-options-menu").exclude(); this.getChildControl("read-only-icon").exclude(); - this.__resetSlidesBtnsVis(); - this.getChildControl("study-title").exclude(); break; case "workbench": case "guided": case "app": this.getChildControl("dashboard-label").exclude(); this.getChildControl("dashboard-button").show(); - this.__resetSlidesBtnsVis(); - this.getChildControl("study-title").show(); + this.getChildControl("study-options-menu").show(); break; } }, - __resetSlidesBtnsVis: function() { - const slideshowMenuBtn = this.getChildControl("slideshow-menu-button"); - const slideshowStopBtn = this.getChildControl("slideshow-stop"); - const slidesBtnsVisible = ["workbench", "guided", "app"].includes(this.getPageContext()); - if (slidesBtnsVisible) { - const study = this.getStudy(); - const areSlidesEnabled = osparc.data.Permissions.getInstance().canDo("study.slides"); - if (areSlidesEnabled) { - this.__startSlidesButton.setEnabled(study.hasSlideshow()); - this.__startAppButton.setEnabled(study.getWorkbench().isPipelineLinear()); - const isOwner = osparc.data.model.Study.isOwner(study); - this.__editSlidesButton.setEnabled(areSlidesEnabled && isOwner); - - if (["guided", "app"].includes(this.getPageContext())) { - slideshowMenuBtn.exclude(); - slideshowStopBtn.show(); - } else if (this.getPageContext() === "workbench") { - slideshowMenuBtn.show(); - slideshowStopBtn.exclude(); - } - } - } else { - slideshowMenuBtn.exclude(); - slideshowStopBtn.exclude(); - } - }, - - __createSlideMenuBtn: function() { - const slidesMenuBtn = new qx.ui.form.MenuButton(this.tr("Slideshow"), "@FontAwesome5Solid/caret-square-right/16").set({ - ...this.self().BUTTON_OPTIONS, - iconPosition: "left" - }); - const splitButtonMenu = new qx.ui.menu.Menu(); - slidesMenuBtn.setMenu(splitButtonMenu); - - const startGuidedBtn = this.__startSlidesButton = new qx.ui.menu.Button(this.tr("Start Guided Mode")); - startGuidedBtn.addListener("execute", () => { - this.fireEvent("slidesGuidedStart"); - }, this); - splitButtonMenu.add(startGuidedBtn); - - const startAppBtn = this.__startAppButton = new qx.ui.menu.Button(this.tr("Start App Mode")); - startAppBtn.addListener("execute", () => { - this.fireEvent("slidesAppStart"); - }); - splitButtonMenu.add(startAppBtn); - - splitButtonMenu.addSeparator(); - - const editSlidesBtn = this.__editSlidesButton = new qx.ui.menu.Button(this.tr("Edit Slideshow")); - editSlidesBtn.addListener("execute", () => { - this.fireEvent("slidesEdit"); - }, this); - splitButtonMenu.add(editSlidesBtn); - - return slidesMenuBtn; - }, - - __createSlideStopBtn: function() { - const stopBtn = new qx.ui.form.Button(this.tr("Slideshow"), "@FontAwesome5Solid/stop/16").set({ - ...this.self().BUTTON_OPTIONS - }); - stopBtn.addListener("execute", () => { - this.fireEvent("slidesStop"); - }, this); - return stopBtn; - }, - __createManualMenuBtn: function() { const manuals = []; if (this.__serverStatics && this.__serverStatics.manualMainUrl) { @@ -512,17 +407,10 @@ qx.Class.define("osparc.navigation.NavigationBar", { _applyStudy: function(study) { if (study) { - study.bind("name", this.getChildControl("study-title"), "value"); study.bind("readOnly", this.getChildControl("read-only-icon"), "visibility", { converter: value => value ? "visible" : "excluded" }); - study.getWorkbench().addListener("pipelineChanged", () => { - this.__resetSlidesBtnsVis(); - }); - study.getUi().getSlideshow().addListener("changeSlideshow", () => { - this.__resetSlidesBtnsVis(); - }); - this.__resetSlidesBtnsVis(); + this.getChildControl("study-options-menu").setStudy(study); } } } diff --git a/services/web/client/source/class/osparc/navigation/StudyMenu.js b/services/web/client/source/class/osparc/navigation/StudyMenu.js new file mode 100644 index 00000000000..f3769243d17 --- /dev/null +++ b/services/web/client/source/class/osparc/navigation/StudyMenu.js @@ -0,0 +1,120 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2021 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.navigation.StudyMenu", { + extend: qx.ui.form.MenuButton, + + construct: function() { + this.base(arguments, this.tr("Study options")); + + this.set({ + ...osparc.navigation.NavigationBar.BUTTON_OPTIONS + }); + + this.__populateMenu(); + }, + + events: { + "slidesGuidedStart": "qx.event.type.Event", + "slidesAppStart": "qx.event.type.Event", + "slidesStop": "qx.event.type.Event", + "slidesEdit": "qx.event.type.Event", + "takeSnapshot": "qx.event.type.Event", + "showSnapshots": "qx.event.type.Event" + }, + + properties: { + study: { + check: "osparc.data.model.Study", + nullable: true, + apply: "_applyStudy" + } + }, + + members: { + __startSlidesButton: null, + __startAppButton: null, + __editSlidesButton: null, + __stopSlidesButton: null, + __takeSnapshotButton: null, + __showSnapshotsButton: null, + + __populateMenu: function() { + const studyButtonMenu = new qx.ui.menu.Menu(); + this.setMenu(studyButtonMenu); + + const editSlidesBtn = this.__editSlidesButton = new qx.ui.menu.Button(this.tr("Edit Slideshow")); + editSlidesBtn.addListener("execute", () => this.fireEvent("slidesEdit"), this); + studyButtonMenu.add(editSlidesBtn); + + const startGuidedBtn = this.__startSlidesButton = new qx.ui.menu.Button(this.tr("Start Guided Mode")); + startGuidedBtn.addListener("execute", () => this.fireEvent("slidesGuidedStart"), this); + studyButtonMenu.add(startGuidedBtn); + + const startAppBtn = this.__startAppButton = new qx.ui.menu.Button(this.tr("Start App Mode")); + startAppBtn.addListener("execute", () => this.fireEvent("slidesAppStart"), this); + studyButtonMenu.add(startAppBtn); + + const stopSlidesBtn = this.__stopSlidesButton = new qx.ui.menu.Button(this.tr("Stop Slideshow")); + stopSlidesBtn.addListener("execute", () => this.fireEvent("slidesStop"), this); + studyButtonMenu.add(stopSlidesBtn); + + studyButtonMenu.addSeparator(); + + const takeSnapshotBtn = this.__takeSnapshotButton = new qx.ui.menu.Button(this.tr("Take Snapshot")); + takeSnapshotBtn.addListener("execute", () => this.fireEvent("takeSnapshot"), this); + studyButtonMenu.add(takeSnapshotBtn); + + const showSnapshotsBtn = this.__showSnapshotsButton = new qx.ui.menu.Button(this.tr("Show Snapshots")); + showSnapshotsBtn.addListener("execute", () => this.fireEvent("showSnapshots"), this); + studyButtonMenu.add(showSnapshotsBtn); + }, + + _applyStudy: function(study) { + if (study) { + study.getWorkbench().addListener("pipelineChanged", () => this.evalSlidesButtons()); + study.getUi().getSlideshow().addListener("changeSlideshow", () => this.evalSlidesButtons()); + study.getUi().addListener("changeMode", () => this.evalSlidesButtons()); + this.evalSlidesButtons(); + this.evalSnapshotsButtons(); + } + }, + + evalSlidesButtons: function() { + const study = this.getStudy(); + if (study) { + const editorContext = this.getStudy().getUi().getMode(); + const areSlidesEnabled = osparc.data.Permissions.getInstance().canDo("study.slides"); + const isOwner = osparc.data.model.Study.isOwner(study); + this.__editSlidesButton.setEnabled(editorContext === "workbench" && areSlidesEnabled && isOwner); + this.__startSlidesButton.setEnabled(editorContext !== "guided" && study.hasSlideshow()); + this.__startAppButton.setEnabled(editorContext !== "app" && study.getWorkbench().isPipelineLinear()); + this.__stopSlidesButton.setEnabled(["guided", "app"].includes(editorContext)); + } + }, + + evalSnapshotsButtons: async function() { + const study = this.getStudy(); + if (study) { + this.__takeSnapshotButton.setEnabled(osparc.data.Permissions.getInstance().canDo("study.snapshot.create")); + + const hasSnapshots = await study.hasSnapshots(); + this.__showSnapshotsButton.setEnabled(hasSnapshots); + } + } + } +}); 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 cddd136d16c..1a6f9c51d87 100644 --- a/services/web/client/source/class/osparc/ui/hint/InfoHint.js +++ b/services/web/client/source/class/osparc/ui/hint/InfoHint.js @@ -61,12 +61,7 @@ qx.Class.define("osparc.ui.hint.InfoHint", { // Make hint "modal" when info button is clicked const tapListener = event => { - const hintElement = hint.getContentElement().getDomElement(); - const boundRect = hintElement.getBoundingClientRect(); - if (event.x > boundRect.x && - event.y > boundRect.y && - event.x < (boundRect.x + boundRect.width) && - event.y < (boundRect.y + boundRect.height)) { + if (osparc.utils.Utils.isMouseOnElement(hint, event)) { return; } hideHint(); diff --git a/services/web/client/source/class/osparc/utils/Services.js b/services/web/client/source/class/osparc/utils/Services.js index 45da271dd18..879d998061d 100644 --- a/services/web/client/source/class/osparc/utils/Services.js +++ b/services/web/client/source/class/osparc/utils/Services.js @@ -40,6 +40,14 @@ qx.Class.define("osparc.utils.Services", { dynamic: { label: "Interactive", icon: "@FontAwesome5Solid/mouse-pointer/" + }, + parameter: { + label: "", + icon: "@FontAwesome5Solid/sliders-h/" + }, + file: { + label: "", + icon: "@FontAwesome5Solid/file/" } }, @@ -63,6 +71,14 @@ qx.Class.define("osparc.utils.Services", { return this.TYPES[type.trim().toLowerCase()]; }, + getIcon: function(type) { + const typeInfo = this.getType(type); + if (typeInfo) { + return typeInfo["icon"]; + } + return typeInfo[""]; + }, + convertArrayToObject: function(servicesArray) { let services = {}; for (let i = 0; i < servicesArray.length; i++) { diff --git a/services/web/client/source/class/osparc/utils/Utils.js b/services/web/client/source/class/osparc/utils/Utils.js index 3078abd83ec..d7a3202c792 100644 --- a/services/web/client/source/class/osparc/utils/Utils.js +++ b/services/web/client/source/class/osparc/utils/Utils.js @@ -43,6 +43,18 @@ qx.Class.define("osparc.utils.Utils", { el.style["transformOrigin"] = oString; }, + isMouseOnElement: function(element, event, offset = 0) { + const domElement = element.getContentElement().getDomElement(); + const boundRect = domElement.getBoundingClientRect(); + if (event.x > boundRect.x - offset && + event.y > boundRect.y - offset && + event.x < (boundRect.x + boundRect.width) + offset && + event.y < (boundRect.y + boundRect.height) + offset) { + return true; + } + return false; + }, + sleep: function(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }, @@ -499,6 +511,12 @@ qx.Class.define("osparc.utils.Utils", { } }, + setMoreToWidget: (qWidget, id) => { + if (qWidget.getContentElement) { + qWidget.getContentElement().setAttribute("osparc-test-more", id); + } + }, + getClientSessionID: function() { // https://stackoverflow.com/questions/11896160/any-way-to-identify-browser-tab-in-javascript const clientSessionID = sessionStorage.getItem("clientsessionid") ? sessionStorage.getItem("clientsessionid") : osparc.utils.Utils.uuidv4(); diff --git a/tests/e2e/portal/CC_Human.js b/tests/e2e/portal/CC_Human.js index 3aaf32af2e3..a3fba794494 100644 --- a/tests/e2e/portal/CC_Human.js +++ b/tests/e2e/portal/CC_Human.js @@ -26,10 +26,8 @@ async function runTutorial () { await tutorial.waitFor(10000, 'Some time for loading the workbench'); await utils.takeScreenshot(page, screenshotPrefix + 'workbench_loaded'); - await tutorial.showLogger(true); await tutorial.runPipeline(); await tutorial.waitForStudyDone(studyId, 1800000); - await tutorial.showLogger(false); const outFiles0 = [ "vm_1Hz.txt", diff --git a/tests/e2e/portal/CC_Rabbit.js b/tests/e2e/portal/CC_Rabbit.js index 21aa41e02be..10b64140158 100644 --- a/tests/e2e/portal/CC_Rabbit.js +++ b/tests/e2e/portal/CC_Rabbit.js @@ -26,10 +26,8 @@ async function runTutorial () { await tutorial.waitFor(10000, 'Some time for loading the workbench'); await utils.takeScreenshot(page, screenshotPrefix + 'workbench_loaded'); - await tutorial.showLogger(true); await tutorial.runPipeline(); await tutorial.waitForStudyDone(studyId, 1500000); - await tutorial.showLogger(false); const outFiles0 = [ "logs.zip", diff --git a/tests/e2e/portal/Kember.js b/tests/e2e/portal/Kember.js index 5b2d107dc8b..e6a047965ec 100644 --- a/tests/e2e/portal/Kember.js +++ b/tests/e2e/portal/Kember.js @@ -30,10 +30,8 @@ async function runTutorial () { await tutorial.waitFor(10000, 'Some time for loading the workbench'); await utils.takeScreenshot(page, screenshotPrefix + 'workbench_loaded'); - await tutorial.showLogger(true); await tutorial.runPipeline(); await tutorial.waitForStudyDone(studyId, 120000); - await tutorial.showLogger(false); const outFiles = [ "logs.zip", diff --git a/tests/e2e/portal/opencor.js b/tests/e2e/portal/opencor.js index 904fa0111ef..4e23b7255db 100644 --- a/tests/e2e/portal/opencor.js +++ b/tests/e2e/portal/opencor.js @@ -26,10 +26,8 @@ async function runTutorial () { await tutorial.waitFor(10000, 'Some time for loading the workbench'); await utils.takeScreenshot(page, screenshotPrefix + 'workbench_loaded'); - await tutorial.showLogger(true); await tutorial.runPipeline(); await tutorial.waitForStudyDone(studyId, 30000); - await tutorial.showLogger(false); const outFiles = [ "results.json", diff --git a/tests/e2e/tests/tags.test.js b/tests/e2e/tests/tags.test.js index b8385b5091f..6d9ef5ecac8 100644 --- a/tests/e2e/tests/tags.test.js +++ b/tests/e2e/tests/tags.test.js @@ -8,7 +8,6 @@ describe('tags testing', () => { pass, } = utils.getUserAndPass(); - const STUDY_NAME = 'study_tag_test'; const TAG_NAME = 'tag_test'; const TAG_NAME_2 = 'tag_test_2'; let studyId = null; @@ -48,15 +47,8 @@ describe('tags testing', () => { await auto.register(page, user, pass); // Create new study await waitAndClick(page, '[osparc-test-id="newStudyBtn"]'); - // Edit its title and go back to dashboard - await waitAndClick(page, '[qxclass="osparc.navigation.NavigationBar"] [qxclass="osparc.ui.form.EditLabel"]'); - await page.keyboard.type(STUDY_NAME); - await page.keyboard.press('Enter'); - await page.waitForFunction(studyName => { - return document.querySelector( - '[qxclass="osparc.navigation.NavigationBar"] [qxclass="osparc.ui.form.EditLabel"] [qxclass="qx.ui.basic.Label"]' - ).innerText === studyName; - }, {}, STUDY_NAME); + // Wait until project is created and Dashboard button is enabled + await utils.sleep(4000); await auto.toDashboard(page); }, ourTimeout * 2); diff --git a/tests/e2e/tutorials/isolve-gpu.js b/tests/e2e/tutorials/isolve-gpu.js index 1faecd9a712..ba8a49f2b4d 100644 --- a/tests/e2e/tutorials/isolve-gpu.js +++ b/tests/e2e/tutorials/isolve-gpu.js @@ -27,10 +27,8 @@ async function runTutorial() { await tutorial.waitFor(5000, 'Some time for loading the workbench'); - await tutorial.showLogger(true); await tutorial.runPipeline(); await tutorial.waitForStudyDone(studyId, 30000); - await tutorial.showLogger(false); const outFiles = [ "logs.zip", diff --git a/tests/e2e/tutorials/isolve-mpi.js b/tests/e2e/tutorials/isolve-mpi.js index d45914539bd..838d923bdc3 100644 --- a/tests/e2e/tutorials/isolve-mpi.js +++ b/tests/e2e/tutorials/isolve-mpi.js @@ -25,10 +25,8 @@ async function runTutorial() { await tutorial.waitFor(5000, 'Some time for loading the workbench'); - await tutorial.showLogger(true); await tutorial.runPipeline(); await tutorial.waitForStudyDone(studyId, 120000); - await tutorial.showLogger(false); const outFiles = [ "logs.zip", diff --git a/tests/e2e/tutorials/sleepers.js b/tests/e2e/tutorials/sleepers.js index 938cff5cb90..f0aad7ef55c 100644 --- a/tests/e2e/tutorials/sleepers.js +++ b/tests/e2e/tutorials/sleepers.js @@ -25,16 +25,14 @@ async function runTutorial() { await tutorial.waitFor(5000, 'Some time for loading the workbench'); - await tutorial.showLogger(true); await tutorial.runPipeline(); await tutorial.waitForStudyDone(studyId, 60000); - await tutorial.showLogger(false); const outFiles = [ "logs.zip", "out_1" ]; - await tutorial.checkNodeOutputs(0, outFiles); + await tutorial.checkNodeOutputs(0, outFiles, true, false); } catch(err) { tutorial.setTutorialFailed(true); diff --git a/tests/e2e/tutorials/tutorialBase.js b/tests/e2e/tutorials/tutorialBase.js index 39ac3d00c58..d55490cbe8d 100644 --- a/tests/e2e/tutorials/tutorialBase.js +++ b/tests/e2e/tutorials/tutorialBase.js @@ -332,7 +332,7 @@ class TutorialBase { await this.takeScreenshot("openNodeRetrieveAndRestart_after"); } - async checkNodeOutputs(nodePos, fileNames, checkNFiles=true) { + async checkNodeOutputs(nodePos, fileNames, checkNFiles=true, checkFileNames=true) { try { await this.openNodeFiles(nodePos); await this.takeScreenshot("checkNodeOutputs_before"); @@ -342,11 +342,13 @@ class TutorialBase { assert(files.length === fileNames.length, 'Number of files is incorrect') console.log('Number of files is correct') } - assert( - fileNames.every(fileName => files.some(file => file.includes(fileName))), - 'File names are incorrect' - ) - console.log('File names are correct') + if (checkFileNames) { + assert( + fileNames.every(fileName => files.some(file => file.includes(fileName))), + 'File names are incorrect' + ) + console.log('File names are correct') + } } catch (err) { console.error("Results don't match", err); diff --git a/tests/e2e/utils/auto.js b/tests/e2e/utils/auto.js index c50466e7635..5c3e9519b58 100644 --- a/tests/e2e/utils/auto.js +++ b/tests/e2e/utils/auto.js @@ -295,16 +295,14 @@ async function openNode(page, pos) { const children = await utils.getNodeTreeItemIDs(page); console.log("children", children); - if (children.length < pos + 1) { + if (pos >= children.length) { console.log("Node tree items not found"); return null; } - const nodeWidgetId = children[pos]; - const childId = '[osparc-test-id="' + nodeWidgetId + '"]'; - const nodeId = nodeWidgetId.replace("nodeTreeItem_", ""); + const nodeId = children[pos]; + const childId = '[osparc-test-more="' + nodeId + '"]'; await utils.waitAndClick(page, childId); - await utils.waitAndClick(page, '[osparc-test-id="openNodeBtn_' + nodeId + '"]'); return nodeId; } @@ -323,6 +321,7 @@ async function openLastNode(page) { async function restoreIFrame(page) { console.log("Restoring iFrame"); + await utils.waitAndClick(page, '[osparc-test-id="iframeTabButton"]') await utils.waitAndClick(page, '[osparc-test-id="restoreBtn"]') } @@ -335,7 +334,8 @@ async function maximizeIFrame(page) { async function openNodeFiles(page) { console.log("Opening Data produced by Node"); - await utils.waitAndClick(page, '[osparc-test-id="nodeViewFilesBtn"]') + await utils.waitAndClick(page, '[osparc-test-id="outputsTabButton"]'); + await utils.waitAndClick(page, '[osparc-test-id="nodeOutputFilesBtn"]'); } async function checkDataProducedByNode(page, nFiles = 1, itemSuffix = 'NodeFiles') { diff --git a/tests/e2e/utils/utils.js b/tests/e2e/utils/utils.js index eb03d936384..46f59f03268 100644 --- a/tests/e2e/utils/utils.js +++ b/tests/e2e/utils/utils.js @@ -107,16 +107,15 @@ function getDomain(url) { async function getNodeTreeItemIDs(page) { const childrenIDs = await page.evaluate((selector) => { const children = []; - const treeRoot = document.querySelector(selector); - if (treeRoot.parentElement) { - const tree = treeRoot.parentElement; - for (let i = 1; i < tree.children.length; i++) { - const child = tree.children[i]; - children.push(child.getAttribute("osparc-test-id")); + const nodeTreeItems = document.querySelectorAll(selector); + nodeTreeItems.forEach(nodeTreeItem => { + const nodeId = nodeTreeItem.getAttribute("osparc-test-more") + if (nodeId !== "root") { + children.push(nodeId); } - } + }); return children; - }, '[osparc-test-id="nodeTreeItem_root"]'); + }, '[osparc-test-id="nodeTreeItem"]'); return childrenIDs; } @@ -459,8 +458,8 @@ function isElementVisible(page, selector) { } async function clickLoggerTitle(page) { - console.log("Click LoggerTitle"); - await this.waitAndClick(page, '[osparc-test-id="studyLoggerTitleLabel"]') + console.log("Click Logger"); + await this.waitAndClick(page, '[osparc-test-id="loggerTabButton"]') }