Skip to content

Commit 63e662c

Browse files
authored
NodeModel owns persistentIFrame (#316)
* Use persistent iFrame * "Loading OSPARC..." site can be shown (it should only be shown for those dynamic services that will provide an iFrame. There is no way to know that at the moment, so it is never shown) * Place iFrame together with settings * Button for maximizing and restoring iframe * Button for restarting iFrame
1 parent 049a185 commit 63e662c

File tree

8 files changed

+429
-103
lines changed

8 files changed

+429
-103
lines changed

services/web/client/compile.json

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
},
5151
{
5252
"uri" : "resource/qxapp/*.png"
53+
},
54+
{
55+
"uri" : "resource/qxapp/loading/*"
5356
}
5457
]
5558
},

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

+89-69
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,15 @@ qx.Class.define("qxapp.component.widget.NodeView", {
4343
let mainLayout = this.__mainLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(10));
4444
mainLayout.set({
4545
alignX: "center",
46-
paddingTop: 20,
47-
paddingRight: 30,
48-
paddingBottom: 20,
49-
paddingLeft: 30
46+
padding: 5
5047
});
5148
this.add(mainLayout, {
5249
flex: 1
5350
});
5451

55-
this.__nodesUI = [];
56-
57-
this.__initTitle();
58-
this.__initSettings();
52+
this.__settingsLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox());
53+
this.__mapperLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(10));
54+
this.__iFrameLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(10));
5955
this.__initButtons();
6056
},
6157

@@ -76,40 +72,14 @@ qx.Class.define("qxapp.component.widget.NodeView", {
7672
},
7773

7874
members: {
79-
__settingsBox: null,
80-
__inputNodesLayout: null,
8175
__mainLayout: null,
82-
__nodesUI: null,
76+
__inputNodesLayout: null,
77+
__settingsLayout: null,
78+
__mapperLayout: null,
79+
__iFrameLayout: null,
8380
__buttonsLayout: null,
8481
__openFolder: null,
8582

86-
__initTitle: function() {
87-
let box = new qx.ui.layout.HBox();
88-
box.set({
89-
spacing: 10,
90-
alignX: "right"
91-
});
92-
let titleBox = new qx.ui.container.Composite(box);
93-
94-
let settLabel = new qx.ui.basic.Label(this.tr("Settings"));
95-
settLabel.set({
96-
alignX: "center",
97-
alignY: "middle"
98-
});
99-
100-
titleBox.add(settLabel, {
101-
width: "75%"
102-
});
103-
this.__mainLayout.add(titleBox);
104-
},
105-
106-
__initSettings: function() {
107-
this.__settingsBox = new qx.ui.container.Composite(new qx.ui.layout.VBox(10));
108-
this.__mainLayout.add(this.__settingsBox, {
109-
flex: 1
110-
});
111-
},
112-
11383
__initButtons: function() {
11484
let box = new qx.ui.layout.HBox();
11585
box.set({
@@ -144,16 +114,6 @@ qx.Class.define("qxapp.component.widget.NodeView", {
144114
}, this);
145115

146116
buttonsLayout.add(openFolder);
147-
this.__mainLayout.add(buttonsLayout);
148-
},
149-
150-
__getNodeUI: function(id) {
151-
for (let i = 0; i < this.__nodesUI.length; i++) {
152-
if (this.__nodesUI[i].getNodeUI() === id) {
153-
return this.__nodesUI[i];
154-
}
155-
}
156-
return null;
157117
},
158118

159119
__arePortsCompatible: function(node1, port1, node2, port2) {
@@ -228,7 +188,9 @@ qx.Class.define("qxapp.component.widget.NodeView", {
228188
return nodePorts;
229189
},
230190

231-
__createInputPortsUIs: function(nodeModel) {
191+
__addInputPortsUIs: function(nodeModel) {
192+
this.__clearInputPortsUIs();
193+
232194
// Add the default inputs if any
233195
if (Object.keys(this.getNodeModel().getInputsDefault()).length > 0) {
234196
this.__createInputPortsUI(this.getNodeModel(), false);
@@ -244,8 +206,7 @@ qx.Class.define("qxapp.component.widget.NodeView", {
244206
this.__createInputPortsUI(exposedInnerNode);
245207
}
246208
} else {
247-
let inputLabel = this.__createInputPortsUI(inputNodeModel);
248-
this.__nodesUI.push(inputLabel);
209+
this.__createInputPortsUI(inputNodeModel);
249210
}
250211
}
251212
},
@@ -257,33 +218,92 @@ qx.Class.define("qxapp.component.widget.NodeView", {
257218
}
258219
},
259220

260-
__applyNode: function(nodeModel, oldNode, propertyName) {
261-
this.__settingsBox.removeAll();
262-
this.__settingsBox.add(nodeModel.getPropsWidget());
263-
this.__createDragDropMechanism(nodeModel.getPropsWidget());
221+
__addSettings: function(propsWidget) {
222+
this.__settingsLayout.removeAll();
223+
if (propsWidget) {
224+
let box = new qx.ui.layout.HBox();
225+
box.set({
226+
spacing: 10,
227+
alignX: "right"
228+
});
229+
let titleBox = new qx.ui.container.Composite(box);
230+
let settLabel = new qx.ui.basic.Label(this.tr("Settings"));
231+
settLabel.set({
232+
alignX: "center"
233+
});
234+
titleBox.add(settLabel, {
235+
width: "75%"
236+
});
237+
238+
this.__settingsLayout.add(titleBox);
239+
this.__settingsLayout.add(propsWidget);
240+
this.__createDragDropMechanism(propsWidget);
241+
242+
this.__mainLayout.add(this.__settingsLayout);
243+
} else if (qx.ui.core.Widget.contains(this.__mainLayout, this.__settingsLayout)) {
244+
this.__mainLayout.remove(this.__settingsLayout);
245+
}
246+
},
264247

265-
if (nodeModel.getInputsMapper()) {
266-
this.__settingsBox.add(nodeModel.getInputsMapper(), {
248+
__addMapper: function(mapper) {
249+
this.__mapperLayout.removeAll();
250+
if (mapper) {
251+
this.__mapperLayout.add(mapper, {
267252
flex: 1
268253
});
254+
this.__mainLayout.add(this.__mapperLayout, {
255+
flex: 1
256+
});
257+
} else if (qx.ui.core.Widget.contains(this.__mainLayout, this.__mapperLayout)) {
258+
this.__mainLayout.remove(this.__mapperLayout);
269259
}
260+
},
270261

271-
this.__clearInputPortsUIs();
272-
this.__createInputPortsUIs(nodeModel);
262+
__addIFrame: function(iFrame) {
263+
this.__iFrameLayout.removeAll();
264+
if (iFrame) {
265+
iFrame.addListenerOnce("maximize", e => {
266+
this.__maximizeIFrame(true);
267+
}, this);
268+
iFrame.addListenerOnce("restore", e => {
269+
this.__maximizeIFrame(false);
270+
}, this);
271+
this.__maximizeIFrame(iFrame.hasState("maximized"));
272+
this.__iFrameLayout.add(iFrame, {
273+
flex: 1
274+
});
275+
this.__mainLayout.add(this.__iFrameLayout, {
276+
flex: 1
277+
});
278+
} else if (qx.ui.core.Widget.contains(this.__mainLayout, this.__iFrameLayout)) {
279+
this.__mainLayout.remove(this.__iFrameLayout);
280+
}
281+
},
273282

283+
__maximizeIFrame: function(maximize) {
284+
const othersStatus = maximize ? "excluded" : "visible";
285+
this.__inputNodesLayout.setVisibility(othersStatus);
286+
this.__settingsLayout.setVisibility(othersStatus);
287+
this.__mapperLayout.setVisibility(othersStatus);
288+
this.__buttonsLayout.setVisibility(othersStatus);
289+
},
290+
291+
__addButtons: function(nodeModel) {
274292
this.__buttonsLayout.removeAll();
275-
let iFrameButton = nodeModel.getIFrameButton();
276-
if (iFrameButton) {
277-
iFrameButton.addListener("execute", e => {
278-
this.fireDataEvent("ShowViewer", {
279-
url: nodeModel.getServiceUrl(),
280-
name: nodeModel.getLabel(),
281-
nodeId: nodeModel.getNodeId()
282-
});
283-
}, this);
284-
this.__buttonsLayout.add(iFrameButton);
293+
let restartIFrameButton = nodeModel.getRestartIFrameButton();
294+
if (restartIFrameButton) {
295+
this.__buttonsLayout.add(restartIFrameButton);
285296
}
286297
this.__buttonsLayout.add(this.__openFolder);
298+
this.__mainLayout.add(this.__buttonsLayout);
299+
},
300+
301+
__applyNode: function(nodeModel, oldNode, propertyName) {
302+
this.__addInputPortsUIs(nodeModel);
303+
this.__addSettings(nodeModel.getPropsWidget());
304+
this.__addMapper(nodeModel.getInputsMapper());
305+
this.__addIFrame(nodeModel.getIFrame());
306+
this.__addButtons(nodeModel);
287307
}
288308
}
289309
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/* ************************************************************************
2+
Copyright: 2018 ITIS Foundation
3+
License: MIT
4+
Authors: Tobi Oetiker <[email protected]>
5+
Utf8Check: äöü
6+
************************************************************************ */
7+
/**
8+
* When moving an iframe node in the dom, it reloads its content. This is
9+
* rather unfortunate when the content is another web application.
10+
* This Iframe widget solves the problem by attaching the iframe to a
11+
* permanent location and just moving it into position as the actual
12+
* widget manifests in different locations. There are limits as to where
13+
* the widget can be displayed as the widget hierarchy may prevent correct
14+
* visualisation. By default the iframe is attached to the root node of
15+
* the document, but an alternate attachment can be specified as required.
16+
*
17+
*/
18+
19+
qx.Class.define("qxapp.component.widget.PersistentIframe", {
20+
extend: qx.ui.embed.AbstractIframe,
21+
/**
22+
*
23+
* @param source {String} URL for the iframe content
24+
* @param poolEl {Element?} Dom node for attaching the iframe
25+
*/
26+
construct: function(source, el) {
27+
this.base(arguments, source);
28+
},
29+
properties :
30+
{
31+
/**
32+
* Show a Maximize Button
33+
*/
34+
showMaximize: {
35+
check: "boolean",
36+
init: false,
37+
apply: "_applyShowMaximize"
38+
}
39+
},
40+
events: {
41+
/** Fired if the iframe is restored from a minimized or maximized state */
42+
"restore" : "qx.event.type.Event",
43+
/** Fired if the iframe is maximized */
44+
"maximize" : "qx.event.type.Event"
45+
},
46+
members: {
47+
__iframe: null,
48+
__actionButton: null,
49+
// override
50+
_createContentElement : function() {
51+
let iframe = this.__iframe = new qx.ui.embed.Iframe(this.getSource()).set({
52+
zIndex: 1000
53+
});
54+
iframe.addListener("load", e => {
55+
this.fireEvent("load");
56+
});
57+
iframe.addListener("navigate", e => {
58+
this.fireDataEvent("navigate", e.getData());
59+
});
60+
61+
let standin = new qx.html.Element("div");
62+
let appRoot = this.getApplicationRoot();
63+
appRoot.add(iframe, {
64+
top:-10000
65+
});
66+
let actionButton = this.__actionButton = new qx.ui.form.Button(null, osparc.theme.osparcdark.Image.URLS["window-maximize"]+"/20").set({
67+
zIndex: 1001,
68+
backgroundColor: null,
69+
decorator: null
70+
});
71+
appRoot.add(actionButton, {
72+
top:-10000
73+
});
74+
actionButton.addListener("execute", e => {
75+
if (this.hasState("maximized")) {
76+
this.fireEvent("restore");
77+
this.removeState("maximized");
78+
actionButton.setIcon(osparc.theme.osparcdark.Image.URLS["window-maximize"]+"/20");
79+
} else {
80+
this.fireEvent("maximize");
81+
this.addState("maximized");
82+
actionButton.setIcon(osparc.theme.osparcdark.Image.URLS["window-restore"]+"/20");
83+
}
84+
});
85+
appRoot.add(actionButton);
86+
standin.addListener("appear", e => {
87+
this.__syncIframePos();
88+
});
89+
standin.addListener("disappear", e => {
90+
iframe.setLayoutProperties({
91+
top: -10000
92+
});
93+
actionButton.setLayoutProperties({
94+
top: -10000
95+
});
96+
});
97+
this.addListener("move", e => {
98+
// got to let the new layout render first or we don't see it
99+
qx.event.Timer.once(this.__syncIframePos, this, 0);
100+
});
101+
this.addListener("resize", e => {
102+
// got to let the new layout render first or we don't see it
103+
qx.event.Timer.once(this.__syncIframePos, this, 0);
104+
});
105+
this.addListener("changeVisibility", e => {
106+
var visibility = e.getData()[0];
107+
if (visibility == "none") {
108+
iframe.set({
109+
zIndex: -10000
110+
});
111+
} else {
112+
this.__syncIframePos();
113+
}
114+
});
115+
return standin;
116+
},
117+
__syncIframePos: function() {
118+
let iframeParentPos = qx.bom.element.Location.get(qx.bom.element.Location.getOffsetParent(this.__iframe.getContentElement().getDomElement()), "scroll");
119+
let divPos = qx.bom.element.Location.get(this.getContentElement().getDomElement(), "scroll");
120+
let divSize = qx.bom.element.Dimension.getSize(this.getContentElement().getDomElement());
121+
this.__iframe.setLayoutProperties({
122+
top: divPos.top - iframeParentPos.top,
123+
left: (divPos.left - iframeParentPos.left)
124+
});
125+
this.__iframe.set({
126+
width: (divSize.width),
127+
height: (divSize.height)
128+
});
129+
this.__actionButton.setLayoutProperties({
130+
top: (divPos.top - iframeParentPos.top),
131+
right: (iframeParentPos.right - iframeParentPos.left - divPos.right)
132+
});
133+
},
134+
_applyShowMaximize: function(newValue, oldValue) {
135+
this._maximizeBtn.show();
136+
},
137+
_applySource: function(newValue) {
138+
this.__iframe.setSource(newValue);
139+
},
140+
// override
141+
_getIframeElement: function() {
142+
return this.__iframe._getIframeElement(); // eslint-disable-line no-underscore-dangle
143+
},
144+
/**
145+
* Cover the iframe with a transparent blocker div element. This prevents
146+
* pointer or key events to be handled by the iframe. To release the blocker
147+
* use {@link #release}.
148+
*
149+
*/
150+
block : function() {
151+
this.__iframe.block();
152+
},
153+
154+
/**
155+
* Release the blocker set by {@link #block}.
156+
*
157+
*/
158+
release : function() {
159+
this.__iframe.release();
160+
}
161+
},
162+
destruct: function() {
163+
this.__iframe.exclude();
164+
this.__iframe.dispose();
165+
this.__iframe = undefined;
166+
}
167+
});

0 commit comments

Comments
 (0)