Skip to content

Commit f51d68b

Browse files
authored
Prettier output-port and link representation in NodeView (#321)
* Nicer port representation (with value if available) for input nodes * Nicer link representation in form: Linked to [nodeLabel]: [portLabel] * Fake data moved from Store to Fake data * From #290: api specs for tree output widgets * Port compatibility check
1 parent 062f221 commit f51d68b

File tree

17 files changed

+583
-575
lines changed

17 files changed

+583
-575
lines changed

.travis.yml

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ matrix:
2323
services:
2424
- docker
2525

26+
cache: pip
27+
2628
before_install:
2729
- python --version
2830
- uname -a
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
$schema: http://json-schema.org/draft-07/schema#
2+
$id: https://simcore.io/api/specs/shared/schemas/node-output-tree-api-v0.0.1.yaml
3+
4+
title: node output tree api
5+
description: nodes using the tree representation for the output
6+
must be able to handle the following requests
7+
type: object
8+
required:
9+
# the validator does not appreciate when required is missing here... and
10+
# sadly does not throw any meaningful error about it... so for now I put this...
11+
- getItemList
12+
- getItem
13+
properties:
14+
getItemList:
15+
description: a list of items making up one level of the tree
16+
type: object
17+
properties:
18+
request:
19+
summary: oa3 json schema description of the request structure.
20+
description: |
21+
If no `rootKey` is specified, the first level of the tree is returned.
22+
The `filter` will return any items matching the filter string as well as any
23+
folder items containing matching items further down the tree.
24+
type: object
25+
properties:
26+
rootKey:
27+
type: string
28+
filter:
29+
type: string
30+
response:
31+
type: array
32+
items:
33+
type: object
34+
properties:
35+
key:
36+
type: string
37+
label:
38+
type: string
39+
folder:
40+
type: boolean
41+
getItem:
42+
description: get details about an item in the list
43+
type: object
44+
properties:
45+
request:
46+
type: object
47+
required:
48+
- key
49+
properties:
50+
key:
51+
type: string
52+
response:
53+
type: object

api/specs/webserver/v0/node-v0.0.1.yaml

+8-8
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,10 @@ paths:
6969
schema:
7070
type: object
7171
# oneOf:
72-
# - $ref: '../../shared/schemas/node-output-list-api-v0.0.1.yaml#/properties/getItemList/request'
73-
# - $ref: '../../shared/schemas/node-output-list-api-v0.0.1.yaml#/properties/getItem/request'
72+
# - $ref: '../../shared/schemas/node-output-list-api-v0.0.1.yaml#/properties/getItemList/properties/request'
73+
# - $ref: '../../shared/schemas/node-output-list-api-v0.0.1.yaml#/properties/getItem/properties/request'
74+
# - $ref: '../../shared/schemas/node-output-tree-api-v0.0.1.yaml#/properties/getItemList/properties/request'
75+
# - $ref: '../../shared/schemas/node-output-tree-api-v0.0.1.yaml#/properties/getItem/properties/request'
7476
responses:
7577
default:
7678
description: node type specific api call according to the node type presented
@@ -79,8 +81,10 @@ paths:
7981
schema:
8082
type: object
8183
# oneOf:
82-
# - $ref: '../../shared/schemas/node-output-list-api-v0.0.1.yaml#/properties/getItemList/response'
83-
# - $ref: '../../shared/schemas/node-output-list-api-v0.0.1.yaml#/properties/getItem/response'
84+
# - $ref: '../../shared/schemas/node-output-list-api-v0.0.1.yaml#/properties/getItemList/properties/response'
85+
# - $ref: '../../shared/schemas/node-output-list-api-v0.0.1.yaml#/properties/getItem/properties/response'
86+
# - $ref: '../../shared/schemas/node-output-tree-api-v0.0.1.yaml#/properties/getItemList/properties/response'
87+
# - $ref: '../../shared/schemas/node-output-tree-api-v0.0.1.yaml#/properties/getItem/properties/response'
8488

8589
/node/{nodeInstanceUUID}/iframe:
8690
get:
@@ -122,10 +126,6 @@ components:
122126
type: string
123127
config:
124128
type: object
125-
apiCalls:
126-
type: object
127-
# oneOf:
128-
# - $ref: '../../shared/schemas/node-output-list-api-v0.0.1.yaml#/properties'
129129
outputApiCall:
130130
type: object
131131
properties:

services/web/client/source/class/qxapp/component/form/Auto.js

+26-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,14 @@ qx.Class.define("qxapp.component.form.Auto", {
6666
/**
6767
* @param structure {Array} form structure
6868
*/
69-
construct : function(content) {
69+
construct : function(content, nodeModel) {
70+
// nodeModel is necessary for creating links
71+
if (nodeModel) {
72+
this.setNodeModel(nodeModel);
73+
} else {
74+
this.setNodeModel(null);
75+
}
76+
7077
this.base(arguments);
7178
this.__ctrlMap = {};
7279
this.__ctrlLinkMap = {};
@@ -86,6 +93,13 @@ qx.Class.define("qxapp.component.form.Auto", {
8693
this);
8794
},
8895

96+
properties: {
97+
nodeModel: {
98+
check: "qxapp.data.model.NodeModel",
99+
nullable: true
100+
}
101+
},
102+
89103
events : {
90104
/**
91105
* fire when the form changes content and
@@ -530,7 +544,17 @@ qx.Class.define("qxapp.component.form.Auto", {
530544
nodeUuid: fromNodeId,
531545
output: fromPortId
532546
};
533-
this.getControlLink(toPortId).setValue("Linked to " + fromNodeId + ": " + fromPortId);
547+
548+
const workbenchModel = this.getNodeModel().getWorkbenchModel();
549+
const fromNode = workbenchModel.getNodeModel(fromNodeId);
550+
const fromNodeLabel = fromNode.getLabel(fromNodeId);
551+
const port = fromNode.getOutput(fromPortId);
552+
const fromPortLabel = port ? port.label : null;
553+
if (fromNodeLabel && fromPortLabel) {
554+
this.getControlLink(toPortId).setValue("Linked to " + fromNodeLabel + ": " + fromPortLabel);
555+
} else {
556+
this.getControlLink(toPortId).setValue("Linked to " + fromNodeId + ": " + fromPortId);
557+
}
534558

535559
this.fireDataEvent("linkAdded", toPortId);
536560
},

services/web/client/source/class/qxapp/component/form/renderer/PropForm.js

+26-5
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,17 @@ qx.Class.define("qxapp.component.form.renderer.PropForm", {
2121
*
2222
* @param vizWidget {Widget} visualization widget to embedd
2323
*/
24-
construct: function(form, nodeModel) {
24+
construct: function(form, workbenchModel, nodeModel) {
25+
// workbenchModel and nodeModel are necessary for creating links
26+
if (workbenchModel) {
27+
this.setWorkbenchModel(workbenchModel);
28+
} else {
29+
this.setWorkbenchModel(null);
30+
}
2531
if (nodeModel) {
2632
this.setNodeModel(nodeModel);
33+
} else {
34+
this.setNodeModel(null);
2735
}
2836

2937
this.base(arguments, form);
@@ -40,8 +48,14 @@ qx.Class.define("qxapp.component.form.renderer.PropForm", {
4048
},
4149

4250
properties: {
51+
workbenchModel: {
52+
check: "qxapp.data.model.WorkbenchModel",
53+
nullable: true
54+
},
55+
4356
nodeModel: {
44-
check: "qxapp.data.model.NodeModel"
57+
check: "qxapp.data.model.NodeModel",
58+
nullable: true
4559
}
4660
},
4761

@@ -145,12 +159,19 @@ qx.Class.define("qxapp.component.form.renderer.PropForm", {
145159
}
146160
},
147161

148-
__arePortsCompatible: function(node1, port1, node2, port2) {
149-
return qxapp.data.Store.getInstance().arePortsCompatible(node1, port1, node2, port2);
162+
__arePortsCompatible: function(node1Id, port1Id, node2Id, port2Id) {
163+
if (this.getWorkbenchModel()) {
164+
const node1 = this.getWorkbenchModel().getNodeModel(node1Id);
165+
const port1 = node1.getOutput(port1Id);
166+
const node2 = this.getWorkbenchModel().getNodeModel(node2Id);
167+
const port2 = node2.getInput(port2Id);
168+
return qxapp.data.Store.getInstance().arePortsCompatible(port1, port2);
169+
}
170+
return false;
150171
},
151172

152173
__createDropMechanism: function(uiElement, portId) {
153-
if (this.isPropertyInitialized("nodeModel")) {
174+
if (this.getNodeModel()) {
154175
uiElement.setDroppable(true);
155176
uiElement.nodeId = this.getNodeModel().getNodeId();
156177
uiElement.portId = portId;

services/web/client/source/class/qxapp/component/widget/InputsMapper.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ qx.Class.define("qxapp.component.widget.InputsMapper", {
2222

2323
let that = this;
2424
tree.setDelegate({
25-
createItem: () => new qxapp.component.widget.inputs.NodeOutputListItem(),
25+
createItem: () => new qxapp.component.widget.inputs.NodeOutputTreeItem(),
2626
bindItem: (c, item, id) => {
2727
c.bindDefaultProperties(item, id);
2828
// c.bindProperty("key", "key", null, item, id);
@@ -78,7 +78,7 @@ qx.Class.define("qxapp.component.widget.InputsMapper", {
7878
const nodeInstanceUUID = null;
7979
const itemProps = qxapp.data.Store.getInstance().getItem(nodeInstanceUUID, fromPortKey, newItem.getKey());
8080
if (itemProps) {
81-
let form = new qxapp.component.form.Auto(itemProps);
81+
let form = new qxapp.component.form.Auto(itemProps, this.getNodeModel());
8282
let propsWidget = new qxapp.component.form.renderer.PropForm(form);
8383
newItem["propsWidget"] = propsWidget;
8484
}

services/web/client/source/class/qxapp/component/widget/NodePorts.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ qx.Class.define("qxapp.component.widget.NodePorts", {
6262
return this.getNodeModel().getMetaData();
6363
},
6464

65-
populateNodeLayout: function() {
65+
populatePortsData: function() {
6666
const metaData = this.getNodeModel().getMetaData();
6767
this.__inputPort = {};
6868
this.__outputPort = {};
@@ -92,15 +92,21 @@ qx.Class.define("qxapp.component.widget.NodePorts", {
9292
let widget = null;
9393
switch (port.type) {
9494
case "node-output-list-api-v0.0.1": {
95-
let nodeOutputList = new qxapp.component.widget.inputs.NodeOutputList(this.getNodeModel(), port, portKey);
96-
widget = nodeOutputList.getOutputWidget();
95+
console.log("widget for ", port.type, " to be implemented");
96+
// let nodeOutputList = new qxapp.component.widget.inputs.NodeOutputList(this.getNodeModel(), port, portKey);
97+
// widget = nodeOutputList.getOutputWidget();
9798
break;
9899
}
99100
case "node-output-list-icon-api-v0.0.1": {
100101
let nodeOutputList = new qxapp.component.widget.inputs.NodeOutputListIcon(this.getNodeModel(), port, portKey);
101102
widget = nodeOutputList.getOutputWidget();
102103
break;
103104
}
105+
case "node-output-tree-api-v0.0.1": {
106+
let nodeOutputList = new qxapp.component.widget.inputs.NodeOutputTree(this.getNodeModel(), port, portKey);
107+
widget = nodeOutputList.getOutputWidget();
108+
break;
109+
}
104110
}
105111
if (widget !== null) {
106112
this._add(widget, {

services/web/client/source/class/qxapp/component/widget/inputs/NodeOutputLabel.js

+81-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
/* ************************************************************************
2+
Copyright: 2018 ITIS Foundation
3+
License: MIT
4+
Authors: Odei Maiz <[email protected]>
5+
Utf8Check: äöü
6+
************************************************************************ */
7+
8+
/**
9+
* Creates the widget that shows the outputs of the node in a key: value way.
10+
* If the value is an object, it will show the internal key-value pairs
11+
* [PortLabel]: [PortValue]
12+
*
13+
*/
14+
115
qx.Class.define("qxapp.component.widget.inputs.NodeOutputLabel", {
216
extend: qx.ui.core.Widget,
317

@@ -6,15 +20,28 @@ qx.Class.define("qxapp.component.widget.inputs.NodeOutputLabel", {
620

721
this.setNodeModel(nodeModel);
822

9-
let toolTip = new qx.ui.tooltip.ToolTip(port.description);
10-
let portLabel = this.__portLabel = new qx.ui.basic.Label(port.label).set({
11-
toolTip: toolTip,
12-
textAlign: "right",
13-
allowGrowX: true,
14-
paddingRight: 20
23+
this._setLayout(new qx.ui.layout.HBox(5));
24+
25+
let portLabel = this._createChildControlImpl("portLabel");
26+
portLabel.set({
27+
value: "<b>" + port.label + "</b>: ",
28+
toolTip: new qx.ui.tooltip.ToolTip(port.description)
29+
});
30+
31+
let portOutput = this._createChildControlImpl("portOutput");
32+
let outputValue = "Unknown value";
33+
if (Object.prototype.hasOwnProperty.call(port, "value")) {
34+
if (typeof port.value === "object") {
35+
outputValue = this.__pretifyObject(port.value);
36+
} else {
37+
outputValue = JSON.stringify(port.value);
38+
}
39+
}
40+
portOutput.set({
41+
value: outputValue
1542
});
1643

17-
this.__createDragMechanism(portLabel, portKey);
44+
this.__createDragMechanism(this, portKey);
1845
},
1946

2047
properties: {
@@ -25,7 +52,39 @@ qx.Class.define("qxapp.component.widget.inputs.NodeOutputLabel", {
2552
},
2653

2754
members: {
28-
__portLabel: null,
55+
_createChildControlImpl: function(id) {
56+
let control;
57+
switch (id) {
58+
case "portLabel": {
59+
const title14Font = qx.bom.Font.fromConfig(qxapp.theme.Font.fonts["title-14"]);
60+
control = new qx.ui.basic.Label().set({
61+
font: title14Font,
62+
textAlign: "right",
63+
allowGrowX: true,
64+
padding: 15,
65+
rich: true
66+
});
67+
this._add(control, {
68+
flex: 1
69+
});
70+
break;
71+
}
72+
case "portOutput": {
73+
const title14Font = qx.bom.Font.fromConfig(qxapp.theme.Font.fonts["title-14"]);
74+
control = new qx.ui.basic.Label().set({
75+
font: title14Font,
76+
textAlign: "right",
77+
allowGrowX: true,
78+
padding: 15,
79+
rich: true
80+
});
81+
this._add(control);
82+
break;
83+
}
84+
}
85+
86+
return control || this.base(arguments, id);
87+
},
2988

3089
__createDragMechanism: function(uiPort, portKey) {
3190
uiPort.setDraggable(true);
@@ -40,8 +99,21 @@ qx.Class.define("qxapp.component.widget.inputs.NodeOutputLabel", {
4099
}, this);
41100
},
42101

102+
__pretifyObject: function(object) {
103+
let myText = "";
104+
const entries = Object.entries(object);
105+
for (let i=0; i<entries.length; i++) {
106+
const entry = entries[i];
107+
myText += String(entry[0]);
108+
myText += ": ";
109+
myText += String(entry[1]);
110+
myText += "<br/>";
111+
}
112+
return myText;
113+
},
114+
43115
getOutputWidget: function() {
44-
return this.__portLabel;
116+
return this;
45117
}
46118
}
47119
});

services/web/client/source/class/qxapp/component/widget/inputs/NodeOutputList.js renamed to services/web/client/source/class/qxapp/component/widget/inputs/NodeOutputTree.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
qx.Class.define("qxapp.component.widget.inputs.NodeOutputList", {
1+
qx.Class.define("qxapp.component.widget.inputs.NodeOutputTree", {
22
extend: qx.ui.core.Widget,
33

44
construct: function(nodeModel, port, portKey) {
@@ -11,7 +11,7 @@ qx.Class.define("qxapp.component.widget.inputs.NodeOutputList", {
1111
});
1212

1313
tree.setDelegate({
14-
createItem: () => new qxapp.component.widget.inputs.NodeOutputListItem(),
14+
createItem: () => new qxapp.component.widget.inputs.NodeOutputTreeItem(),
1515
bindItem: (c, item, id) => {
1616
c.bindDefaultProperties(item, id);
1717
c.bindProperty("key", "model", null, item, id);

services/web/client/source/class/qxapp/component/widget/inputs/NodeOutputListItem.js renamed to services/web/client/source/class/qxapp/component/widget/inputs/NodeOutputTreeItem.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
qx.Class.define("qxapp.component.widget.inputs.NodeOutputListItem", {
1+
qx.Class.define("qxapp.component.widget.inputs.NodeOutputTreeItem", {
22
extend: qx.ui.tree.VirtualTreeItem,
33

44
construct: function() {

0 commit comments

Comments
 (0)