diff --git a/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js b/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js
index 850672e96572..29eb686f241d 100644
--- a/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js
+++ b/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js
@@ -117,7 +117,7 @@ qx.Class.define("osparc.data.model.IframeHandler", {
}
const node = this.getNode();
- const thumbnail = node.getMetaData()["thumbnail"];
+ const thumbnail = node.getMetadata()["thumbnail"];
if (thumbnail) {
loadingPage.setLogo(thumbnail);
}
@@ -141,7 +141,7 @@ qx.Class.define("osparc.data.model.IframeHandler", {
status = node.getStatus().getInteractive();
}
const statusText = status ? (status.charAt(0).toUpperCase() + status.slice(1)) : this.tr("Starting");
- const metadata = node.getMetaData();
+ const metadata = node.getMetadata();
const versionDisplay = osparc.service.Utils.extractVersionDisplay(metadata);
return statusText + " " + node.getLabel() + " v" + versionDisplay + "";
},
diff --git a/services/static-webserver/client/source/class/osparc/data/model/Node.js b/services/static-webserver/client/source/class/osparc/data/model/Node.js
index 76b4200e77dc..7e0ea962091a 100644
--- a/services/static-webserver/client/source/class/osparc/data/model/Node.js
+++ b/services/static-webserver/client/source/class/osparc/data/model/Node.js
@@ -41,14 +41,13 @@ qx.Class.define("osparc.data.model.Node", {
/**
* @param study {osparc.data.model.Study} Study or Serialized Study Object
- * @param metadata {Object} service's metadata
+ * @param key {String} unique key of the service represented by the node
+ * @param version {String} version of the service represented by the node
* @param nodeId {String} uuid of the service represented by the node (not needed for new Nodes)
*/
- construct: function(study, metadata, nodeId) {
+ construct: function(study, key, version, nodeId) {
this.base(arguments);
- this.__metaData = metadata;
- this.setOutputs({});
this.__inputNodes = [];
this.__inputsRequired = [];
@@ -57,12 +56,10 @@ qx.Class.define("osparc.data.model.Node", {
}
this.set({
nodeId: nodeId || osparc.utils.Utils.uuidV4(),
- key: metadata["key"],
- version: metadata["version"],
+ key,
+ version,
status: new osparc.data.model.NodeStatus(this)
});
-
- this.populateWithMetadata();
},
properties: {
@@ -76,14 +73,12 @@ qx.Class.define("osparc.data.model.Node", {
key: {
check: "String",
nullable: true,
- apply: "__applyNewMetaData"
},
version: {
check: "String",
nullable: true,
event: "changeVersion",
- apply: "__applyNewMetaData"
},
nodeId: {
@@ -91,6 +86,14 @@ qx.Class.define("osparc.data.model.Node", {
nullable: false
},
+ metadata: {
+ check: "Object",
+ init: null,
+ nullable: false,
+ event: "changeMetadata",
+ apply: "__applyMetadata",
+ },
+
label: {
check: "String",
init: "Node",
@@ -199,8 +202,8 @@ qx.Class.define("osparc.data.model.Node", {
"retrieveInputs": "qx.event.type.Data",
"keyChanged": "qx.event.type.Event",
"changePosition": "qx.event.type.Data",
- "createEdge": "qx.event.type.Data",
- "removeEdge": "qx.event.type.Data",
+ "edgeCreated": "qx.event.type.Data",
+ "edgeRemoved": "qx.event.type.Data",
"fileRequested": "qx.event.type.Data",
"parameterRequested": "qx.event.type.Data",
"filePickerRequested": "qx.event.type.Data",
@@ -272,6 +275,10 @@ qx.Class.define("osparc.data.model.Node", {
return (metadata && metadata.type && metadata.type === "computational");
},
+ isUnknown: function(metadata) {
+ return (metadata && metadata.key && metadata.key === osparc.store.Services.UNKNOWN_SERVICE_KEY);
+ },
+
isUpdatable: function(metadata) {
return osparc.service.Utils.isUpdatable(metadata);
},
@@ -353,7 +360,6 @@ qx.Class.define("osparc.data.model.Node", {
},
members: {
- __metaData: null,
__inputNodes: null,
__inputsRequired: null,
__settingsForm: null,
@@ -371,7 +377,7 @@ qx.Class.define("osparc.data.model.Node", {
},
isInKey: function(str) {
- if (this.getMetaData() === null) {
+ if (this.getMetadata() === null) {
return false;
}
if (this.getKey() === null) {
@@ -381,60 +387,51 @@ qx.Class.define("osparc.data.model.Node", {
},
isFilePicker: function() {
- return osparc.data.model.Node.isFilePicker(this.getMetaData());
+ return osparc.data.model.Node.isFilePicker(this.getMetadata());
},
isParameter: function() {
- return osparc.data.model.Node.isParameter(this.getMetaData());
+ return osparc.data.model.Node.isParameter(this.getMetadata());
},
isIterator: function() {
- return osparc.data.model.Node.isIterator(this.getMetaData());
+ return osparc.data.model.Node.isIterator(this.getMetadata());
},
isProbe: function() {
- return osparc.data.model.Node.isProbe(this.getMetaData());
+ return osparc.data.model.Node.isProbe(this.getMetadata());
},
isDynamic: function() {
- return osparc.data.model.Node.isDynamic(this.getMetaData());
+ return osparc.data.model.Node.isDynamic(this.getMetadata());
},
isComputational: function() {
- return osparc.data.model.Node.isComputational(this.getMetaData());
+ return osparc.data.model.Node.isComputational(this.getMetadata());
+ },
+
+ isUnknown: function() {
+ return osparc.data.model.Node.isUnknown(this.getMetadata());
},
isUpdatable: function() {
- return osparc.data.model.Node.isUpdatable(this.getMetaData());
+ return osparc.data.model.Node.isUpdatable(this.getMetadata());
},
isDeprecated: function() {
- return osparc.data.model.Node.isDeprecated(this.getMetaData());
+ return osparc.data.model.Node.isDeprecated(this.getMetadata());
},
isRetired: function() {
- return osparc.data.model.Node.isRetired(this.getMetaData());
+ return osparc.data.model.Node.isRetired(this.getMetadata());
},
hasBootModes: function() {
- return osparc.data.model.Node.hasBootModes(this.getMetaData());
+ return osparc.data.model.Node.hasBootModes(this.getMetadata());
},
getMinVisibleInputs: function() {
- return osparc.data.model.Node.getMinVisibleInputs(this.getMetaData());
- },
-
- __applyNewMetaData: function(newV, oldV) {
- if (oldV !== null) {
- const metadata = osparc.store.Services.getMetadata(this.getKey(), this.getVersion());
- if (metadata) {
- this.__metaData = metadata;
- }
- }
- },
-
- getMetaData: function() {
- return this.__metaData;
+ return osparc.data.model.Node.getMinVisibleInputs(this.getMetadata());
},
hasPropsForm: function() {
@@ -482,8 +479,26 @@ qx.Class.define("osparc.data.model.Node", {
return Object.keys(this.getOutputs()).length;
},
- populateWithMetadata: function() {
- const metadata = this.__metaData;
+ fetchMetadataAndPopulate: function(nodeData, nodeUiData) {
+ this.__initNodeData = nodeData;
+ this.__initNodeUiData = nodeUiData;
+ return osparc.store.Services.getService(this.getKey(), this.getVersion())
+ .then(serviceMetadata => {
+ this.setMetadata(serviceMetadata);
+ this.populateNodeData(nodeData);
+ // old place to store the position
+ this.populateNodeUIData(nodeData);
+ // new place to store the position and marker
+ this.populateNodeUIData(nodeUiData);
+ })
+ .catch(err => {
+ console.log(err);
+ const errorMsg = qx.locale.Manager.tr("Service metadata missing");
+ osparc.FlashMessenger.logError(errorMsg);
+ });
+ },
+
+ __applyMetadata: function(metadata) {
if (metadata) {
if (metadata.name) {
this.setLabel(metadata.name);
@@ -496,9 +511,13 @@ qx.Class.define("osparc.data.model.Node", {
if (this.getPropsForm()) {
this.getPropsForm().makeInputsDynamic();
}
+ } else {
+ this.setInputs({});
}
if (metadata.outputs) {
this.setOutputs(metadata.outputs);
+ } else {
+ this.setOutputs({});
}
}
},
@@ -508,17 +527,15 @@ qx.Class.define("osparc.data.model.Node", {
if (nodeData.label) {
this.setLabel(nodeData.label);
}
- this.populateInputOutputData(nodeData);
+ this.__populateInputOutputData(nodeData);
this.populateStates(nodeData);
if (nodeData.bootOptions) {
this.setBootOptions(nodeData.bootOptions);
}
}
-
if (this.isParameter()) {
this.__initParameter();
}
-
if (osparc.store.Store.getInstance().getCurrentStudy()) {
// do not initialize the logger and iframe if the study isn't open
this.__initLogger();
@@ -527,15 +544,17 @@ qx.Class.define("osparc.data.model.Node", {
},
populateNodeUIData: function(nodeUIData) {
- if ("position" in nodeUIData) {
- this.setPosition(nodeUIData.position);
- }
- if ("marker" in nodeUIData) {
- this.addMarker(nodeUIData.marker);
+ if (nodeUIData) {
+ if ("position" in nodeUIData) {
+ this.setPosition(nodeUIData.position);
+ }
+ if ("marker" in nodeUIData) {
+ this.addMarker(nodeUIData.marker);
+ }
}
},
- populateInputOutputData: function(nodeData) {
+ __populateInputOutputData: function(nodeData) {
this.__setInputData(nodeData.inputs);
this.__setInputUnits(nodeData.inputsUnits);
if (this.getPropsForm()) {
@@ -591,23 +610,6 @@ qx.Class.define("osparc.data.model.Node", {
__applyPropsForm: function(propsForm) {
osparc.utils.Utils.setIdToWidget(propsForm, "settingsForm_" + this.getNodeId());
-
- const checkIsPipelineRunning = () => {
- const isPipelineRunning = this.getStudy().isPipelineRunning();
- this.getPropsForm().setEnabled(!isPipelineRunning);
- };
- this.getStudy().addListener("changeState", () => checkIsPipelineRunning(), this);
-
- // potentially disabling the inputs form might have side effects if the deserialization is not over
- if (this.getWorkbench().isDeserialized()) {
- checkIsPipelineRunning();
- } else {
- this.getWorkbench().addListener("changeDeserialized", e => {
- if (e.getData()) {
- checkIsPipelineRunning();
- }
- }, this);
- }
},
/**
@@ -734,8 +736,8 @@ qx.Class.define("osparc.data.model.Node", {
__setInputData: function(inputs) {
if (this.__settingsForm && inputs) {
- const inputData = {};
const inputLinks = {};
+ const inputData = {};
const inputsCopy = osparc.utils.Utils.deepCloneObject(inputs);
for (let key in inputsCopy) {
if (osparc.utils.Ports.isDataALink(inputsCopy[key])) {
@@ -831,8 +833,8 @@ qx.Class.define("osparc.data.model.Node", {
// errors to port
if (loc.length > 2) {
const portKey = loc[2];
- if (this.hasInputs() && portKey in this.getMetaData()["inputs"]) {
- errorMsgData["msg"] = this.getMetaData()["inputs"][portKey]["label"] + ": " + errorMsgData["msg"];
+ if (this.hasInputs() && portKey in this.getMetadata()["inputs"]) {
+ errorMsgData["msg"] = this.getMetadata()["inputs"][portKey]["label"] + ": " + errorMsgData["msg"];
} else {
errorMsgData["msg"] = portKey + ": " + errorMsgData["msg"];
}
@@ -845,7 +847,7 @@ qx.Class.define("osparc.data.model.Node", {
});
} else if (this.hasInputs()) {
// reset port errors
- Object.keys(this.getMetaData()["inputs"]).forEach(portKey => {
+ Object.keys(this.getMetadata()["inputs"]).forEach(portKey => {
this.getPropsForm().setPortErrorMessage(portKey, null);
});
}
@@ -1135,7 +1137,7 @@ qx.Class.define("osparc.data.model.Node", {
checkState: function() {
if (this.isDynamic()) {
- const metadata = this.getMetaData();
+ const metadata = this.getMetadata();
const msg = "Starting " + metadata.key + ":" + metadata.version + "...";
const msgData = {
nodeId: this.getNodeId(),
@@ -1154,7 +1156,7 @@ qx.Class.define("osparc.data.model.Node", {
stopDynamicService: function() {
if (this.isDynamic()) {
- const metadata = this.getMetaData();
+ const metadata = this.getMetadata();
const msg = "Stopping " + metadata.key + ":" + metadata.version + "...";
const msgData = {
nodeId: this.getNodeId(),
@@ -1285,8 +1287,11 @@ qx.Class.define("osparc.data.model.Node", {
if (newMetadata) {
const value = this.__getInputData()["linspace_start"];
const label = this.getLabel();
- this.setKey(newMetadata["key"]);
- this.populateWithMetadata();
+ this.set({
+ key: newMetadata["key"],
+ version: newMetadata["version"],
+ });
+ this.setMetadata(newMetadata);
this.populateNodeData();
this.setLabel(label);
osparc.node.ParameterEditor.setParameterOutputValue(this, value);
@@ -1298,12 +1303,15 @@ qx.Class.define("osparc.data.model.Node", {
if (!["int"].includes(type)) {
return;
}
- const metadata = osparc.store.Services.getLatest("simcore/services/frontend/data-iterator/int-range")
- if (metadata) {
+ const newMetadata = osparc.store.Services.getLatest("simcore/services/frontend/data-iterator/int-range")
+ if (newMetadata) {
const value = this.__getOutputData("out_1");
const label = this.getLabel();
- this.setKey(metadata["key"]);
- this.populateWithMetadata();
+ this.set({
+ key: newMetadata["key"],
+ version: newMetadata["version"],
+ });
+ this.setMetadata(newMetadata);
this.populateNodeData();
this.setLabel(label);
this.__setInputData({
@@ -1459,7 +1467,7 @@ qx.Class.define("osparc.data.model.Node", {
case "inputNodes":
if (op === "add") {
const inputNodeId = value;
- this.fireDataEvent("createEdge", {
+ this.fireDataEvent("edgeCreated", {
nodeId1: inputNodeId,
nodeId2: this.getNodeId(),
});
@@ -1468,7 +1476,7 @@ qx.Class.define("osparc.data.model.Node", {
const index = path.split("/")[4];
// make sure index is valid
if (index >= 0 && index < this.__inputNodes.length) {
- this.fireDataEvent("removeEdge", {
+ this.fireDataEvent("edgeRemoved", {
nodeId1: this.__inputNodes[index],
nodeId2: this.getNodeId(),
});
diff --git a/services/static-webserver/client/source/class/osparc/data/model/NodeUnknown.js b/services/static-webserver/client/source/class/osparc/data/model/NodeUnknown.js
new file mode 100644
index 000000000000..8eae34e51fd3
--- /dev/null
+++ b/services/static-webserver/client/source/class/osparc/data/model/NodeUnknown.js
@@ -0,0 +1,57 @@
+/* ************************************************************************
+
+ osparc - the simcore frontend
+
+ https://osparc.io
+
+ Copyright:
+ 2025 IT'IS Foundation, https://itis.swiss
+
+ License:
+ MIT: https://opensource.org/licenses/MIT
+
+ Authors:
+ * Odei Maiz (odeimaiz)
+
+************************************************************************ */
+
+/**
+ * Class that stores Node data without a known metadata.
+ */
+
+qx.Class.define("osparc.data.model.NodeUnknown", {
+ extend: osparc.data.model.Node,
+
+ /**
+ * @param study {osparc.data.model.Study} Study or Serialized Study Object
+ * @param key {String} service's key
+ * @param version {String} service's version
+ * @param nodeId {String} uuid of the service represented by the node (not needed for new Nodes)
+ */
+ construct: function(study, key, version, nodeId) {
+ // use the unknown metadata
+ const metadata = osparc.store.Services.getUnknownServiceMetadata();
+ this.base(arguments, study, metadata, nodeId);
+
+ // but keep the original key and version
+ if (key && version) {
+ this.set({
+ key,
+ version,
+ });
+ }
+ },
+
+ members: {
+ // override
+ serialize: function() {
+ /*
+ if (this.getKey() === osparc.store.Services.UNKNOWN_SERVICE_KEY) {
+ return null;
+ }
+ */
+
+ return null;
+ }
+ }
+});
diff --git a/services/static-webserver/client/source/class/osparc/data/model/Service.js b/services/static-webserver/client/source/class/osparc/data/model/Service.js
index d260144d4c05..a9b5382c63d0 100644
--- a/services/static-webserver/client/source/class/osparc/data/model/Service.js
+++ b/services/static-webserver/client/source/class/osparc/data/model/Service.js
@@ -31,18 +31,18 @@ qx.Class.define("osparc.data.model.Service", {
this.set({
key: serviceData.key,
version: serviceData.version,
- versionDisplay: serviceData.versionDisplay,
+ versionDisplay: serviceData.versionDisplay || null,
name: serviceData.name,
- description: serviceData.description,
- thumbnail: serviceData.thumbnail,
- serviceType: serviceData.type,
- contact: serviceData.contact,
- authors: serviceData.authors,
- owner: serviceData.owner || "",
+ description: serviceData.description || null,
+ thumbnail: serviceData.thumbnail || null,
+ serviceType: serviceData.type || null,
+ contact: serviceData.contact || null,
+ authors: serviceData.authors || null,
+ owner: serviceData.owner || null,
accessRights: serviceData.accessRights,
- bootOptions: serviceData.bootOptions,
+ bootOptions: serviceData.bootOptions || null,
classifiers: serviceData.classifiers || [],
- quality: serviceData.quality || null,
+ quality: serviceData.quality || {},
xType: serviceData.xType || null,
hits: serviceData.hits || 0,
});
diff --git a/services/static-webserver/client/source/class/osparc/data/model/Study.js b/services/static-webserver/client/source/class/osparc/data/model/Study.js
index 8f9e82b98512..03b0f0dd4c8c 100644
--- a/services/static-webserver/client/source/class/osparc/data/model/Study.js
+++ b/services/static-webserver/client/source/class/osparc/data/model/Study.js
@@ -574,7 +574,7 @@ qx.Class.define("osparc.data.model.Study", {
// The frontend controls its output values, progress and states.
// If a File Picker is uploading a file, the backend could override the current state with some older state.
if (node) {
- if (nodeData && !osparc.data.model.Node.isFrontend(node.getMetaData())) {
+ if (nodeData && !osparc.data.model.Node.isFrontend(node.getMetadata())) {
node.setOutputData(nodeData.outputs);
if ("progress" in nodeData) {
const progress = Number.parseInt(nodeData["progress"]);
diff --git a/services/static-webserver/client/source/class/osparc/data/model/Workbench.js b/services/static-webserver/client/source/class/osparc/data/model/Workbench.js
index eed47e42c1c7..8bb1f1a056b6 100644
--- a/services/static-webserver/client/source/class/osparc/data/model/Workbench.js
+++ b/services/static-webserver/client/source/class/osparc/data/model/Workbench.js
@@ -54,6 +54,7 @@ qx.Class.define("osparc.data.model.Workbench", {
"projectDocumentChanged": "qx.event.type.Data",
"restartAutoSaveTimer": "qx.event.type.Event",
"pipelineChanged": "qx.event.type.Event",
+ "nodeAdded": "qx.event.type.Data",
"nodeRemoved": "qx.event.type.Data",
"reloadModel": "qx.event.type.Event",
"retrieveInputs": "qx.event.type.Data",
@@ -111,6 +112,63 @@ qx.Class.define("osparc.data.model.Workbench", {
this.__workbenchUIInitData = null;
},
+ __deserialize: function(workbenchInitData, uiData = {}) {
+ const nodeDatas = {};
+ const nodeUiDatas = {};
+ for (const nodeId in workbenchInitData) {
+ const nodeData = workbenchInitData[nodeId];
+ nodeDatas[nodeId] = nodeData;
+ if (uiData["workbench"] && nodeId in uiData["workbench"]) {
+ nodeUiDatas[nodeId] = uiData["workbench"][nodeId];
+ }
+ }
+ this.__deserializeNodes(nodeDatas, nodeUiDatas)
+ .then(() => {
+ this.__deserializeEdges(workbenchInitData);
+ this.setDeserialized(true);
+ });
+ },
+
+ __deserializeNodes: function(nodeDatas, nodeUiDatas) {
+ const nodesPromises = [];
+ for (const nodeId in nodeDatas) {
+ const nodeData = nodeDatas[nodeId];
+ const nodeUiData = nodeUiDatas[nodeId];
+ const node = this.__createNode(nodeData["key"], nodeData["version"], nodeId);
+ nodesPromises.push(node.fetchMetadataAndPopulate(nodeData, nodeUiData));
+ }
+ return Promise.allSettled(nodesPromises);
+ },
+
+ __createNode: function(key, version, nodeId) {
+ const node = new osparc.data.model.Node(this.getStudy(), key, version, nodeId);
+ this.__addNode(node);
+ this.__initNodeSignals(node);
+ osparc.utils.Utils.localCache.serviceToFavs(key);
+ return node;
+ },
+
+
+ __deserializeEdges: function(workbenchData) {
+ for (const nodeId in workbenchData) {
+ const node = this.getNode(nodeId);
+ if (node === null) {
+ continue;
+ }
+ const nodeData = workbenchData[nodeId];
+ const inputNodeIds = nodeData.inputNodes || [];
+ inputNodeIds.forEach(inputNodeId => {
+ const inputNode = this.getNode(inputNodeId);
+ if (inputNode === null) {
+ return;
+ }
+ const edge = new osparc.data.model.Edge(null, inputNode, node);
+ this.addEdge(edge);
+ node.addInputNode(inputNodeId);
+ });
+ }
+ },
+
// starts the dynamic services
initWorkbench: function() {
const allModels = this.getNodes();
@@ -273,21 +331,13 @@ qx.Class.define("osparc.data.model.Workbench", {
nodeRight.setInputConnected(true);
},
- __createNode: function(study, metadata, uuid) {
- const node = new osparc.data.model.Node(study, metadata, uuid);
- if (osparc.utils.Utils.eventDrivenPatch()) {
- node.listenToChanges();
- node.addListener("projectDocumentChanged", e => this.fireDataEvent("projectDocumentChanged", e.getData()), this);
+ createUnknownNode: function(nodeId) {
+ if (nodeId === undefined) {
+ nodeId = osparc.utils.Utils.uuidV4();
}
- node.addListener("keyChanged", () => this.fireEvent("reloadModel"), this);
- node.addListener("changeInputNodes", () => this.fireDataEvent("pipelineChanged"), this);
- node.addListener("reloadModel", () => this.fireEvent("reloadModel"), this);
- node.addListener("updateStudyDocument", () => this.fireEvent("updateStudyDocument"), this);
- osparc.utils.Utils.localCache.serviceToFavs(metadata.key);
-
- this.__initNodeSignals(node);
+ const node = new osparc.data.model.NodeUnknown(this.getStudy(), null, null, nodeId);
this.__addNode(node);
-
+ node.populateNodeData();
return node;
},
@@ -315,16 +365,16 @@ qx.Class.define("osparc.data.model.Workbench", {
};
try {
- const metadata = await osparc.store.Services.getService(key, version);
const resp = await osparc.data.Resources.fetch("studies", "addNode", params);
const nodeId = resp["node_id"];
this.fireEvent("restartAutoSaveTimer");
- const node = this.__createNode(this.getStudy(), metadata, nodeId);
- node.populateNodeData();
- this.__giveUniqueNameToNode(node, node.getLabel());
- node.checkState();
-
+ const node = this.__createNode(key, version, nodeId);
+ node.fetchMetadataAndPopulate()
+ .then(() => {
+ this.__giveUniqueNameToNode(node, node.getLabel());
+ node.checkState();
+ });
return node;
} catch (err) {
let errorMsg = "";
@@ -344,50 +394,57 @@ 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("fileRequested", e => this.fireDataEvent("fileRequested", e.getData()), this);
- node.addListener("filePickerRequested", e => {
- const {
- portId,
- nodeId,
- file
- } = e.getData();
- this.__filePickerNodeRequested(nodeId, portId, file);
- }, this);
- node.addListener("parameterRequested", e => {
- const {
- portId,
- nodeId
- } = e.getData();
- this.__parameterNodeRequested(nodeId, portId);
- }, this);
- node.addListener("probeRequested", e => {
- const {
- portId,
- nodeId
- } = e.getData();
- this.__probeNodeRequested(nodeId, portId);
- }, this);
- node.addListener("fileUploaded", () => {
- // downstream nodes might have started downloading file picker's output.
- // show feedback to the user
- const downstreamNodes = this.__getDownstreamNodes(node);
- downstreamNodes.forEach(downstreamNode => {
- downstreamNode.getPortIds().forEach(portId => {
- const link = downstreamNode.getLink(portId);
- if (link && link["nodeUuid"] === node.getNodeId() && link["output"] === "outFile") {
- // connected to file picker's output
- setTimeout(() => {
- // start retrieving state after 2"
- downstreamNode.retrieveInputs(portId);
- }, 2000);
- }
- });
- });
- }, this);
+ if (osparc.utils.Utils.eventDrivenPatch()) {
+ node.listenToChanges();
+ node.addListener("projectDocumentChanged", e => this.fireDataEvent("projectDocumentChanged", e.getData()), this);
}
+ node.addListener("keyChanged", () => this.fireEvent("reloadModel"), this);
+ node.addListener("changeInputNodes", () => this.fireDataEvent("pipelineChanged"), this);
+ node.addListener("reloadModel", () => this.fireEvent("reloadModel"), this);
+ node.addListener("updateStudyDocument", () => this.fireEvent("updateStudyDocument"), this);
+
+ 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("filePickerRequested", e => {
+ const {
+ portId,
+ nodeId,
+ file
+ } = e.getData();
+ this.__filePickerNodeRequested(nodeId, portId, file);
+ }, this);
+ node.addListener("parameterRequested", e => {
+ const {
+ portId,
+ nodeId
+ } = e.getData();
+ this.__parameterNodeRequested(nodeId, portId);
+ }, this);
+ node.addListener("probeRequested", e => {
+ const {
+ portId,
+ nodeId
+ } = e.getData();
+ this.__probeNodeRequested(nodeId, portId);
+ }, this);
+ node.addListener("fileUploaded", () => {
+ // downstream nodes might have started downloading file picker's output.
+ // show feedback to the user
+ const downstreamNodes = this.__getDownstreamNodes(node);
+ downstreamNodes.forEach(downstreamNode => {
+ downstreamNode.getPortIds().forEach(portId => {
+ const link = downstreamNode.getLink(portId);
+ if (link && link["nodeUuid"] === node.getNodeId() && link["output"] === "outFile") {
+ // connected to file picker's output
+ setTimeout(() => {
+ // start retrieving state after 2"
+ downstreamNode.retrieveInputs(portId);
+ }, 2000);
+ }
+ });
+ });
+ }, this);
},
getFreePosition: function(node, toTheLeft = true) {
@@ -476,7 +533,7 @@ qx.Class.define("osparc.data.model.Workbench", {
const requesterNode = this.getNode(nodeId);
// create a new ParameterNode
- const type = osparc.utils.Ports.getPortType(requesterNode.getMetaData()["inputs"], portId);
+ const type = osparc.utils.Ports.getPortType(requesterNode.getMetadata()["inputs"], portId);
const parameterMetadata = osparc.store.Services.getParameterMetadata(type);
if (parameterMetadata) {
const parameterNode = await this.createNode(parameterMetadata["key"], parameterMetadata["version"]);
@@ -505,8 +562,8 @@ qx.Class.define("osparc.data.model.Workbench", {
const requesterNode = this.getNode(nodeId);
// create a new ProbeNode
- const requesterPortMD = requesterNode.getMetaData()["outputs"][portId];
- const type = osparc.utils.Ports.getPortType(requesterNode.getMetaData()["outputs"], portId);
+ const requesterPortMD = requesterNode.getMetadata()["outputs"][portId];
+ const type = osparc.utils.Ports.getPortType(requesterNode.getMetadata()["outputs"], portId);
const probeMetadata = osparc.store.Services.getProbeMetadata(type);
if (probeMetadata) {
const probeNode = await this.createNode(probeMetadata["key"], probeMetadata["version"]);
@@ -536,7 +593,14 @@ qx.Class.define("osparc.data.model.Workbench", {
__addNode: function(node) {
const nodeId = node.getNodeId();
this.__nodes[nodeId] = node;
- this.fireEvent("pipelineChanged");
+ const nodeAdded = () => {
+ this.fireEvent("pipelineChanged");
+ };
+ if (node.getMetadata()) {
+ nodeAdded();
+ } else {
+ node.addListenerOnce("changeMetadata", () => nodeAdded(), this);
+ }
},
removeNode: async function(nodeId) {
@@ -675,88 +739,6 @@ qx.Class.define("osparc.data.model.Workbench", {
}
},
- __populateNodesData: function(workbenchData, workbenchUIData) {
- Object.entries(workbenchData).forEach(([nodeId, nodeData]) => {
- this.getNode(nodeId).populateNodeData(nodeData);
-
- if ("position" in nodeData) {
- // old way for storing the position
- this.getNode(nodeId).populateNodeUIData(nodeData);
- }
- if (workbenchUIData && "workbench" in workbenchUIData && nodeId in workbenchUIData.workbench) {
- this.getNode(nodeId).populateNodeUIData(workbenchUIData.workbench[nodeId]);
- }
- });
- },
-
- __deserialize: function(workbenchInitData, workbenchUIInitData) {
- this.__deserializeNodes(workbenchInitData, workbenchUIInitData)
- .then(() => {
- this.__deserializeEdges(workbenchInitData);
- workbenchInitData = null;
- workbenchUIInitData = null;
- this.setDeserialized(true);
- });
- },
-
- __deserializeNodes: function(workbenchData, workbenchUIData = {}) {
- const nodeIds = Object.keys(workbenchData);
- const serviceMetadataPromises = [];
- nodeIds.forEach(nodeId => {
- const nodeData = workbenchData[nodeId];
- serviceMetadataPromises.push(osparc.store.Services.getService(nodeData.key, nodeData.version));
- });
- return Promise.allSettled(serviceMetadataPromises)
- .then(results => {
- const missing = results.filter(result => result.status === "rejected" || result.value === null)
- if (missing.length) {
- const errorMsg = qx.locale.Manager.tr("Service metadata missing");
- osparc.FlashMessenger.logError(errorMsg);
- return;
- }
- const values = results.map(result => result.value);
- // Create first all the nodes
- for (let i=0; i {
- const node = this.getNode(nodeId);
- this.__giveUniqueNameToNode(node, node.getLabel());
- });
- });
- },
-
- __deserializeEdges: function(workbenchData) {
- for (const nodeId in workbenchData) {
- const nodeData = workbenchData[nodeId];
- const node = this.getNode(nodeId);
- if (node === null) {
- continue;
- }
- this.__addInputOutputNodesAndEdges(node, nodeData.inputNodes);
- }
- },
-
- __addInputOutputNodesAndEdges: function(node, inputOutputNodeIds) {
- if (inputOutputNodeIds) {
- inputOutputNodeIds.forEach(inputOutputNodeId => {
- const node1 = this.getNode(inputOutputNodeId);
- if (node1 === null) {
- return;
- }
- const edge = new osparc.data.model.Edge(null, node1, node);
- this.addEdge(edge);
- node.addInputNode(inputOutputNodeId);
- });
- }
- },
-
serialize: function() {
if (this.__workbenchInitData !== null) {
// workbench is not initialized
@@ -836,11 +818,17 @@ qx.Class.define("osparc.data.model.Workbench", {
return Promise.all(promises);
},
- updateWorkbenchFromPatches: function(workbenchPatches) {
+ /**
+ * Update the workbench from the given patches.
+ * @param workbenchPatches {Array} Array of workbench patches.
+ * @param uiPatches {Array} Array of UI patches. They might contain info (position) about new nodes.
+ */
+ updateWorkbenchFromPatches: function(workbenchPatches, uiPatches) {
// group the patches by nodeId
const nodesAdded = [];
const nodesRemoved = [];
const workbenchPatchesByNode = {};
+ const workbenchUiPatchesByNode = {};
workbenchPatches.forEach(workbenchPatch => {
const nodeId = workbenchPatch.path.split("/")[2];
@@ -865,10 +853,20 @@ qx.Class.define("osparc.data.model.Workbench", {
if (nodesRemoved.length) {
this.__removeNodesFromPatches(nodesRemoved, workbenchPatchesByNode);
}
+
// second, add nodes if any
if (nodesAdded.length) {
// this will call update nodes once finished
- this.__addNodesFromPatches(nodesAdded, workbenchPatchesByNode);
+ nodesAdded.forEach(nodeId => {
+ const uiPatchFound = uiPatches.find(uiPatch => {
+ const pathParts = uiPatch.path.split("/");
+ return uiPatch.op === "add" && pathParts.length === 4 && pathParts[3] === nodeId;
+ });
+ if (uiPatchFound) {
+ workbenchUiPatchesByNode[nodeId] = uiPatchFound;
+ }
+ });
+ this.__addNodesFromPatches(nodesAdded, workbenchPatchesByNode, workbenchUiPatchesByNode);
} else {
// third, update nodes
this.__updateNodesFromPatches(workbenchPatchesByNode);
@@ -892,12 +890,8 @@ qx.Class.define("osparc.data.model.Workbench", {
});
},
- __addNodesFromPatches: function(nodesAdded, workbenchPatchesByNode) {
- // not solved yet, log the user out to avoid issues
- qx.core.Init.getApplication().logout(qx.locale.Manager.tr("Potentially conflicting updates coming from a collaborator"));
- return;
-
- const promises = nodesAdded.map(nodeId => {
+ __addNodesFromPatches: function(nodesAdded, workbenchPatchesByNode, workbenchUiPatchesByNode = {}) {
+ nodesAdded.forEach(nodeId => {
const addNodePatch = workbenchPatchesByNode[nodeId].find(workbenchPatch => {
const pathParts = workbenchPatch.path.split("/");
return pathParts.length === 3 && workbenchPatch.op === "add";
@@ -908,18 +902,25 @@ qx.Class.define("osparc.data.model.Workbench", {
if (index > -1) {
workbenchPatchesByNode[nodeId].splice(index, 1);
}
- // this is an async operation with an await
- return this.createNode(nodeData["key"], nodeData["version"]);
+
+ const nodeUiData = workbenchUiPatchesByNode[nodeId] && workbenchUiPatchesByNode[nodeId]["value"] ? workbenchUiPatchesByNode[nodeId]["value"] : {};
+
+ const node = this.__createNode(nodeData["key"], nodeData["version"], nodeId);
+ node.fetchMetadataAndPopulate(nodeData, nodeUiData)
+ .then(() => {
+ this.fireDataEvent("nodeAdded", node);
+ node.checkState();
+ // check it was already linked
+ if (nodeData.inputNodes && nodeData.inputNodes.length > 0) {
+ nodeData.inputNodes.forEach(inputNodeId => {
+ node.fireDataEvent("edgeCreated", {
+ nodeId1: inputNodeId,
+ nodeId2: nodeId,
+ });
+ });
+ }
+ });
});
- return Promise.all(promises)
- .then(nodes => {
- // may populate it
- // after adding nodes, we can apply the patches
- this.__updateNodesFromPatches(workbenchPatchesByNode);
- })
- .catch(err => {
- console.error("Error adding nodes from patches:", err);
- });
},
__updateNodesFromPatches: function(workbenchPatchesByNode) {
@@ -933,5 +934,82 @@ qx.Class.define("osparc.data.model.Workbench", {
node.updateNodeFromPatch(nodePatches);
});
},
+
+ /**
+ * @deprecated This method is deprecated and will be removed in a future release.
+ * Please use `__deserialize` instead for deserializing workbench data.
+ * Migration: Replace calls to `__deserializeOld` with `__deserialize`.
+ */
+ __deserializeOld: function(workbenchInitData, workbenchUIInitData) {
+ this.__deserializeNodesOld(workbenchInitData, workbenchUIInitData)
+ .then(() => {
+ this.__deserializeEdges(workbenchInitData);
+ workbenchInitData = null;
+ workbenchUIInitData = null;
+ this.setDeserialized(true);
+ });
+ },
+
+ __deserializeNodesOld: function(workbenchData, workbenchUIData = {}) {
+ const nodeIds = Object.keys(workbenchData);
+ const serviceMetadataPromises = [];
+ nodeIds.forEach(nodeId => {
+ const nodeData = workbenchData[nodeId];
+ serviceMetadataPromises.push(osparc.store.Services.getService(nodeData.key, nodeData.version));
+ });
+ return Promise.allSettled(serviceMetadataPromises)
+ .then(results => {
+ const missing = results.filter(result => result.status === "rejected" || result.value === null)
+ if (missing.length) {
+ const errorMsg = qx.locale.Manager.tr("Service metadata missing");
+ osparc.FlashMessenger.logError(errorMsg);
+ return;
+ }
+ const values = results.map(result => result.value);
+ // Create first all the nodes
+ for (let i=0; i this.fireDataEvent("projectDocumentChanged", e.getData()), this);
+ }
+ node.addListener("keyChanged", () => this.fireEvent("reloadModel"), this);
+ node.addListener("changeInputNodes", () => this.fireDataEvent("pipelineChanged"), this);
+ node.addListener("reloadModel", () => this.fireEvent("reloadModel"), this);
+ node.addListener("updateStudyDocument", () => this.fireEvent("updateStudyDocument"), this);
+ osparc.utils.Utils.localCache.serviceToFavs(metadata["key"]);
+
+ this.__initNodeSignals(node);
+ this.__addNode(node);
+
+ return node;
+ },
+
+ __populateNodesDataOld: function(workbenchData, workbenchUIData) {
+ Object.entries(workbenchData).forEach(([nodeId, nodeData]) => {
+ this.getNode(nodeId).populateNodeData(nodeData);
+
+ if ("position" in nodeData) {
+ // old place to store the position
+ this.getNode(nodeId).populateNodeUIData(nodeData);
+ }
+ if (workbenchUIData && "workbench" in workbenchUIData && nodeId in workbenchUIData["workbench"]) {
+ // new place to store the position and marker
+ this.getNode(nodeId).populateNodeUIData(workbenchUIData["workbench"][nodeId]);
+ }
+ });
+ },
}
});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js b/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js
index db2902c0539b..6c41adcdb189 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js
@@ -160,6 +160,8 @@ qx.Class.define("osparc.desktop.StudyEditor", {
__studyEditorIdlingTracker: null,
__lastSyncedProjectDocument: null,
__lastSyncedProjectVersion: null,
+ __pendingProjectData: null,
+ __applyProjectDocumentTimer: null,
__updatingStudy: null,
__updateThrottled: null,
__nodesSlidesTree: null,
@@ -334,57 +336,96 @@ qx.Class.define("osparc.desktop.StudyEditor", {
if (data["projectId"] === this.getStudy().getUuid()) {
if (data["clientSessionId"] && data["clientSessionId"] === osparc.utils.Utils.getClientSessionID()) {
// ignore my own updates
- console.debug("Ignoring my own projectDocument:updated event", data);
+ console.debug("ProjectDocument Discarded: My own", data);
return;
}
-
- const documentVersion = data["version"];
- if (this.__lastSyncedProjectVersion && documentVersion <= this.__lastSyncedProjectVersion) {
- // ignore old updates
- console.debug("Ignoring old projectDocument:updated event", data);
- return;
- }
- this.__lastSyncedProjectVersion = documentVersion;
-
- const updatedStudy = data["document"];
- // curate projectDocument:updated document
- this.self().curateBackendProjectDocument(updatedStudy);
-
- const myStudy = this.getStudy().serialize();
- // curate myStudy
- this.self().curateFrontendProjectDocument(myStudy);
-
- this.__blockUpdates = true;
- const delta = osparc.wrapper.JsonDiffPatch.getInstance().diff(myStudy, updatedStudy);
- const jsonPatches = osparc.wrapper.JsonDiffPatch.getInstance().deltaToJsonPatches(delta);
- const uiPatches = [];
- const workbenchPatches = [];
- const studyPatches = [];
- for (const jsonPatch of jsonPatches) {
- if (jsonPatch.path.startsWith('/ui/')) {
- uiPatches.push(jsonPatch);
- } else if (jsonPatch.path.startsWith('/workbench/')) {
- workbenchPatches.push(jsonPatch);
- } else {
- studyPatches.push(jsonPatch);
- }
- }
- if (workbenchPatches.length > 0) {
- this.getStudy().getWorkbench().updateWorkbenchFromPatches(workbenchPatches);
- }
- if (uiPatches.length > 0) {
- this.getStudy().getUi().updateUiFromPatches(uiPatches);
- }
- if (studyPatches.length > 0) {
- this.getStudy().updateStudyFromPatches(studyPatches);
- }
-
- this.__blockUpdates = false;
+ this.__projectDocumentReceived(data);
}
}, this);
}
},
+ __projectDocumentReceived: function(data) {
+ const documentVersion = data["version"];
+
+ // Ignore outdated updates
+ if (this.__lastSyncedProjectVersion && documentVersion <= this.__lastSyncedProjectVersion) {
+ // ignore old updates
+ console.debug("ProjectDocument Discarded: Ignoring old", data);
+ return;
+ }
+
+ // Always keep the latest version in pending buffer
+ if (!this.__pendingProjectData || documentVersion > (this.__pendingProjectData.version || 0)) {
+ this.__pendingProjectData = data;
+ }
+
+ // Reset the timer if it's already running
+ if (this.__applyProjectDocumentTimer) {
+ console.debug("ProjectDocument Discarded: Resetting applyProjectDocument timer");
+ clearTimeout(this.__applyProjectDocumentTimer);
+ }
+
+ // Throttle applying updates
+ this.__applyProjectDocumentTimer = setTimeout(() => {
+ if (!this.__pendingProjectData) {
+ return;
+ }
+ this.__applyProjectDocumentTimer = null;
+
+ // Apply the latest buffered project document
+ const latestData = this.__pendingProjectData;
+ this.__pendingProjectData = null;
+
+ this.__applyProjectDocument(latestData);
+ }, 3*this.self().THROTTLE_PATCH_TIME);
+ // make it 3 times longer.
+ // when another client adds a node:
+ // - there is a POST call
+ // - then (after the throttle) a PATCH on its position
+ // without waiting for it 3 times, this client might place it on the default 0,0
+ },
+
+ __applyProjectDocument: function(data) {
+ console.debug("ProjectDocument applying:", data);
+ this.__lastSyncedProjectVersion = data["version"];
+ const updatedProjectDocument = data["document"];
+
+ // curate projectDocument:updated document
+ this.self().curateBackendProjectDocument(updatedProjectDocument);
+
+ const myStudy = this.getStudy().serialize();
+ // curate myStudy
+ this.self().curateFrontendProjectDocument(myStudy);
+
+ this.__blockUpdates = true;
+ const delta = osparc.wrapper.JsonDiffPatch.getInstance().diff(myStudy, updatedProjectDocument);
+ const jsonPatches = osparc.wrapper.JsonDiffPatch.getInstance().deltaToJsonPatches(delta);
+ const uiPatches = [];
+ const workbenchPatches = [];
+ const studyPatches = [];
+ for (const jsonPatch of jsonPatches) {
+ if (jsonPatch.path.startsWith('/ui/')) {
+ uiPatches.push(jsonPatch);
+ } else if (jsonPatch.path.startsWith('/workbench/')) {
+ workbenchPatches.push(jsonPatch);
+ } else {
+ studyPatches.push(jsonPatch);
+ }
+ }
+ if (workbenchPatches.length > 0) {
+ this.getStudy().getWorkbench().updateWorkbenchFromPatches(workbenchPatches, uiPatches);
+ }
+ if (uiPatches.length > 0) {
+ this.getStudy().getUi().updateUiFromPatches(uiPatches);
+ }
+ if (studyPatches.length > 0) {
+ this.getStudy().updateStudyFromPatches(studyPatches);
+ }
+
+ this.__blockUpdates = false;
+ },
+
__listenToLogger: function() {
const socket = osparc.wrapper.WebSocket.getInstance();
diff --git a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js
index 29c45220dba4..c836eba7bac8 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js
@@ -246,9 +246,13 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
this.__connectEvents();
study.getWorkbench().addListener("pipelineChanged", () => this.__evalSlidesButtons());
+ study.getWorkbench().addListener("nodeAdded", e => {
+ const node = e.getData();
+ this.__nodeAdded(node);
+ });
study.getWorkbench().addListener("nodeRemoved", e => {
const {nodeId, connectedEdgeIds} = e.getData();
- this.nodeRemoved(nodeId, connectedEdgeIds);
+ this.__nodeRemoved(nodeId, connectedEdgeIds);
});
study.getUi().getSlideshow().addListener("changeSlideshow", () => this.__evalSlidesButtons());
study.getUi().addListener("changeMode", () => this.__evalSlidesButtons());
@@ -798,6 +802,12 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
} else if (node) {
this.__populateSecondaryColumnNode(node);
}
+
+ if (node instanceof osparc.data.model.Node) {
+ node.getStudy().bind("pipelineRunning", this.__serviceOptionsPage, "enabled", {
+ converter: pipelineRunning => !pipelineRunning
+ });
+ }
},
__populateSecondaryColumnStudy: function(study) {
@@ -1039,7 +1049,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
this.__serviceOptionsPage.bind("width", vBox, "width");
// HEADER
- const nodeMetadata = node.getMetaData();
+ const nodeMetadata = node.getMetadata();
const version = osparc.store.Services.getVersionDisplay(nodeMetadata["key"], nodeMetadata["version"]);
const header = new qx.ui.basic.Label(`${nodeMetadata["name"]} ${version}`).set({
paddingLeft: 5
@@ -1127,6 +1137,19 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
this.addListener("disappear", () => qx.event.message.Bus.getInstance().unsubscribe("maximizeIframe", maximizeIframeCb, this), this);
},
+ __nodeAdded: function(node) {
+ this.__workbenchUI.addNode(node, node.getPosition());
+ },
+
+ __nodeRemoved: function(nodeId, connectedEdgeIds) {
+ // remove first the connected edges
+ connectedEdgeIds.forEach(edgeId => {
+ this.__workbenchUI.clearEdge(edgeId);
+ });
+ // then remove the node
+ this.__workbenchUI.clearNode(nodeId);
+ },
+
__removeNode: function(nodeId) {
const workbench = this.getStudy().getWorkbench();
const node = workbench.getNode(nodeId);
@@ -1182,15 +1205,6 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
}
},
- nodeRemoved: function(nodeId, connectedEdgeIds) {
- // remove first the connected edges
- connectedEdgeIds.forEach(edgeId => {
- this.__workbenchUI.clearEdge(edgeId);
- });
- // then remove the node
- this.__workbenchUI.clearNode(nodeId);
- },
-
__removeEdge: function(edgeId) {
const workbench = this.getStudy().getWorkbench();
const removed = workbench.removeEdge(edgeId);
diff --git a/services/static-webserver/client/source/class/osparc/form/renderer/PropForm.js b/services/static-webserver/client/source/class/osparc/form/renderer/PropForm.js
index c3a89731b081..25412ac55419 100644
--- a/services/static-webserver/client/source/class/osparc/form/renderer/PropForm.js
+++ b/services/static-webserver/client/source/class/osparc/form/renderer/PropForm.js
@@ -908,6 +908,7 @@ qx.Class.define("osparc.form.renderer.PropForm", {
if (!this.__isPortAvailable(toPortId)) {
return false;
}
+
const ctrlLink = this.getControlLink(toPortId);
ctrlLink.setEnabled(false);
this._form.getControl(toPortId)["link"] = {
@@ -926,21 +927,28 @@ qx.Class.define("osparc.form.renderer.PropForm", {
ctrlLink.addListener("mouseover", () => highlightEdgeUI(true));
ctrlLink.addListener("mouseout", () => highlightEdgeUI(false));
- const workbench = study.getWorkbench();
- const fromNode = workbench.getNode(fromNodeId);
- const port = fromNode.getOutput(fromPortId);
- const fromPortLabel = port ? port.label : null;
- fromNode.bind("label", ctrlLink, "value", {
- converter: label => label + ": " + fromPortLabel
- });
- // Hack: Show tooltip if element is disabled
- const addToolTip = () => {
- ctrlLink.getContentElement().removeAttribute("title");
- const toolTipText = fromNode.getLabel() + ":\n" + fromPortLabel;
- ctrlLink.getContentElement().setAttribute("title", toolTipText);
- };
- fromNode.addListener("changeLabel", () => addToolTip());
- addToolTip();
+ const fromNode = study.getWorkbench().getNode(fromNodeId);
+ const prettifyLinkString = () => {
+ const port = fromNode.getOutput(fromPortId);
+ const fromPortLabel = port ? port.label : null;
+ fromNode.bind("label", ctrlLink, "value", {
+ converter: label => label + ": " + fromPortLabel
+ });
+
+ // Hack: Show tooltip if element is disabled
+ const addToolTip = () => {
+ ctrlLink.getContentElement().removeAttribute("title");
+ const toolTipText = fromNode.getLabel() + ":\n" + fromPortLabel;
+ ctrlLink.getContentElement().setAttribute("title", toolTipText);
+ };
+ fromNode.addListener("changeLabel", () => addToolTip());
+ addToolTip();
+ }
+ if (fromNode.getMetadata()) {
+ prettifyLinkString();
+ } else {
+ fromNode.addListenerOnce("changeMetadata", () => prettifyLinkString(), this);
+ }
this.__portLinkAdded(toPortId, fromNodeId, fromPortId);
diff --git a/services/static-webserver/client/source/class/osparc/form/renderer/PropFormBase.js b/services/static-webserver/client/source/class/osparc/form/renderer/PropFormBase.js
index dce44818824e..ac7a1a3bd4de 100644
--- a/services/static-webserver/client/source/class/osparc/form/renderer/PropFormBase.js
+++ b/services/static-webserver/client/source/class/osparc/form/renderer/PropFormBase.js
@@ -225,7 +225,7 @@ qx.Class.define("osparc.form.renderer.PropFormBase", {
const changedXUnits = this.getChangedXUnits();
Object.keys(changedXUnits).forEach(portId => {
const ctrl = this._form.getControl(portId);
- const nodeMD = this.getNode().getMetaData();
+ const nodeMD = this.getNode().getMetadata();
const {
unitPrefix
} = osparc.utils.Units.decomposeXUnit(nodeMD.inputs[portId]["x_unit"]);
@@ -273,7 +273,7 @@ qx.Class.define("osparc.form.renderer.PropFormBase", {
const ctrl = this._form.getControl(portId);
xUnits[portId] = osparc.utils.Units.composeXUnit(ctrl.unit, ctrl.unitPrefix);
}
- const nodeMD = this.getNode().getMetaData();
+ const nodeMD = this.getNode().getMetadata();
const changedXUnits = {};
for (const portId in xUnits) {
if (xUnits[portId] === null) {
@@ -350,7 +350,7 @@ qx.Class.define("osparc.form.renderer.PropFormBase", {
if (unit && unitRegistered) {
unitLabel.addListener("pointerover", () => unitLabel.setCursor("pointer"), this);
unitLabel.addListener("pointerout", () => unitLabel.resetCursor(), this);
- const nodeMD = this.getNode().getMetaData();
+ const nodeMD = this.getNode().getMetadata();
const originalUnit = "x_unit" in nodeMD.inputs[item.key] ? osparc.utils.Units.decomposeXUnit(nodeMD.inputs[item.key]["x_unit"]) : null;
unitLabel.addListener("tap", () => {
const nextPrefix = osparc.utils.Units.getNextPrefix(item.unitPrefix, originalUnit.unitPrefix);
diff --git a/services/static-webserver/client/source/class/osparc/info/ServiceUtils.js b/services/static-webserver/client/source/class/osparc/info/ServiceUtils.js
index 8f3685a6c4c9..54b44fa0cc8c 100644
--- a/services/static-webserver/client/source/class/osparc/info/ServiceUtils.js
+++ b/services/static-webserver/client/source/class/osparc/info/ServiceUtils.js
@@ -106,15 +106,17 @@ qx.Class.define("osparc.info.ServiceUtils", {
wrap: true,
maxWidth: 220,
});
- authors.set({
- value: serviceData["authors"].map(author => author["name"]).join(", "),
- });
- serviceData["authors"].forEach(author => {
- const oldTTT = authors.getToolTipText();
+ if (serviceData["authors"]) {
authors.set({
- toolTipText: (oldTTT ? oldTTT : "") + `${author["email"]} - ${author["affiliation"]}
`
+ value: serviceData["authors"].map(author => author["name"]).join(", "),
});
- });
+ serviceData["authors"].forEach(author => {
+ const oldTTT = authors.getToolTipText();
+ authors.set({
+ toolTipText: (oldTTT ? oldTTT : "") + `${author["email"]} - ${author["affiliation"]}
`
+ });
+ });
+ }
return authors;
},
@@ -122,20 +124,27 @@ qx.Class.define("osparc.info.ServiceUtils", {
* @param serviceData {Object} Serialized Service Object
*/
createAccessRights: function(serviceData) {
- let permissions = "";
- const myGID = osparc.auth.Data.getInstance().getGroupId();
- const ar = serviceData["accessRights"];
- if (myGID in ar) {
- if (ar[myGID]["write"]) {
- permissions = qx.locale.Manager.tr("Write");
- } else if (ar[myGID]["execute"]) {
- permissions = qx.locale.Manager.tr("Execute");
+ const allMyGIds = osparc.store.Groups.getInstance().getAllMyGroupIds();
+ const accessRights = serviceData["accessRights"];
+ const permissions = new Set();
+ allMyGIds.forEach(gId => {
+ if (gId in accessRights) {
+ if (accessRights[gId]["write"]) {
+ permissions.add("write");
+ } else if (accessRights[gId]["execute"]) {
+ permissions.add("read");
+ }
}
+ });
+ const accessRightsLabel = new qx.ui.basic.Label();
+ if (permissions.has("write")) {
+ accessRightsLabel.setValue(osparc.data.Roles.SERVICES["write"].label);
+ } else if (permissions.has("read")) {
+ accessRightsLabel.setValue(osparc.data.Roles.SERVICES["read"].label);
} else {
- permissions = qx.locale.Manager.tr("Public");
+ accessRightsLabel.setValue(qx.locale.Manager.tr("Public"));
}
- const accessRights = new qx.ui.basic.Label(permissions);
- return accessRights;
+ return accessRightsLabel;
},
/**
diff --git a/services/static-webserver/client/source/class/osparc/info/StudyUtils.js b/services/static-webserver/client/source/class/osparc/info/StudyUtils.js
index 3ea10b1efe9e..16b8f39af2dc 100644
--- a/services/static-webserver/client/source/class/osparc/info/StudyUtils.js
+++ b/services/static-webserver/client/source/class/osparc/info/StudyUtils.js
@@ -84,21 +84,31 @@ qx.Class.define("osparc.info.StudyUtils", {
* @param study {osparc.data.model.Study} Study Model
*/
createAccessRights: function(study) {
- const accessRights = new qx.ui.basic.Label();
- let permissions = "";
- const myGID = osparc.auth.Data.getInstance().getGroupId();
- const ar = study.getAccessRights();
- if (myGID in ar) {
- if (ar[myGID]["delete"]) {
- permissions = qx.locale.Manager.tr("Owner");
- } else if (ar[myGID]["write"]) {
- permissions = qx.locale.Manager.tr("Editor");
- } else if (ar[myGID]["read"]) {
- permissions = qx.locale.Manager.tr("User");
+ const allMyGIds = osparc.store.Groups.getInstance().getAllMyGroupIds();
+ const accessRights = study.getAccessRights();
+ const permissions = new Set();
+ allMyGIds.forEach(gId => {
+ if (gId in accessRights) {
+ if (accessRights[gId]["delete"]) {
+ permissions.add("delete");
+ } else if (accessRights[gId]["write"]) {
+ permissions.add("write");
+ } else if (accessRights[gId]["read"]) {
+ permissions.add("read");
+ }
}
+ });
+ const accessRightsLabel = new qx.ui.basic.Label();
+ if (permissions.has("delete")) {
+ accessRightsLabel.setValue(osparc.data.Roles.STUDY["delete"].label);
+ } else if (permissions.has("write")) {
+ accessRightsLabel.setValue(osparc.data.Roles.STUDY["write"].label);
+ } else if (permissions.has("read")) {
+ accessRightsLabel.setValue(osparc.data.Roles.STUDY["read"].label);
+ } else {
+ accessRightsLabel.setValue(qx.locale.Manager.tr("Public"));
}
- accessRights.setValue(permissions);
- return accessRights;
+ return accessRightsLabel;
},
/**
diff --git a/services/static-webserver/client/source/class/osparc/node/BootOptionsView.js b/services/static-webserver/client/source/class/osparc/node/BootOptionsView.js
index 762f71343519..8725dddfce69 100644
--- a/services/static-webserver/client/source/class/osparc/node/BootOptionsView.js
+++ b/services/static-webserver/client/source/class/osparc/node/BootOptionsView.js
@@ -38,7 +38,7 @@ qx.Class.define("osparc.node.BootOptionsView", {
const buttonsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10));
- const nodeMetadata = node.getMetaData();
+ const nodeMetadata = node.getMetadata();
const workbenchData = node.getWorkbench().serialize();
const nodeId = node.getNodeId();
const bootModeSB = osparc.data.model.Node.getBootModesSelectBox(nodeMetadata, workbenchData, nodeId);
diff --git a/services/static-webserver/client/source/class/osparc/node/LifeCycleView.js b/services/static-webserver/client/source/class/osparc/node/LifeCycleView.js
index 23a390106f4c..d67382c725da 100644
--- a/services/static-webserver/client/source/class/osparc/node/LifeCycleView.js
+++ b/services/static-webserver/client/source/class/osparc/node/LifeCycleView.js
@@ -65,7 +65,7 @@ qx.Class.define("osparc.node.LifeCycleView", {
const node = this.getNode();
if (node.isDeprecated()) {
- const deprecateDateLabel = new qx.ui.basic.Label(osparc.service.Utils.getDeprecationDateText(node.getMetaData())).set({
+ const deprecateDateLabel = new qx.ui.basic.Label(osparc.service.Utils.getDeprecationDateText(node.getMetadata())).set({
rich: true
});
this._add(deprecateDateLabel);
diff --git a/services/static-webserver/client/source/class/osparc/node/ParameterEditor.js b/services/static-webserver/client/source/class/osparc/node/ParameterEditor.js
index a762a75af38a..b1993171f0c9 100644
--- a/services/static-webserver/client/source/class/osparc/node/ParameterEditor.js
+++ b/services/static-webserver/client/source/class/osparc/node/ParameterEditor.js
@@ -30,7 +30,7 @@ qx.Class.define("osparc.node.ParameterEditor", {
statics: {
getParameterOutputType: function(node) {
- const metadata = node.getMetaData();
+ const metadata = node.getMetadata();
return osparc.service.Utils.getParameterType(metadata);
},
diff --git a/services/static-webserver/client/source/class/osparc/node/slideshow/BaseNodeView.js b/services/static-webserver/client/source/class/osparc/node/slideshow/BaseNodeView.js
index 86b9b713f188..26250a85f8dd 100644
--- a/services/static-webserver/client/source/class/osparc/node/slideshow/BaseNodeView.js
+++ b/services/static-webserver/client/source/class/osparc/node/slideshow/BaseNodeView.js
@@ -217,7 +217,7 @@ qx.Class.define("osparc.node.slideshow.BaseNodeView", {
__openServiceDetails: function() {
const node = this.getNode();
- const metadata = node.getMetaData();
+ const metadata = node.getMetadata();
const serviceDetails = new osparc.info.ServiceLarge(metadata, {
nodeId: node.getNodeId(),
label: node.getLabel(),
@@ -277,13 +277,6 @@ qx.Class.define("osparc.node.slideshow.BaseNodeView", {
return this._settingsLayout;
},
- /**
- * @abstract
- */
- isSettingsGroupShowable: function() {
- throw new Error("Abstract method called!");
- },
-
/**
* @abstract
*/
diff --git a/services/static-webserver/client/source/class/osparc/node/slideshow/FilePickerView.js b/services/static-webserver/client/source/class/osparc/node/slideshow/FilePickerView.js
index cd66e03d48aa..82f3c0480f72 100644
--- a/services/static-webserver/client/source/class/osparc/node/slideshow/FilePickerView.js
+++ b/services/static-webserver/client/source/class/osparc/node/slideshow/FilePickerView.js
@@ -27,11 +27,6 @@ qx.Class.define("osparc.node.slideshow.FilePickerView", {
},
members: {
- // overridden
- isSettingsGroupShowable: function() {
- return false;
- },
-
// overridden
_addSettings: function() {
return;
diff --git a/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js b/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js
index 1d74811ad0cf..f702aa747bf0 100644
--- a/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js
+++ b/services/static-webserver/client/source/class/osparc/node/slideshow/NodeView.js
@@ -38,13 +38,6 @@ qx.Class.define("osparc.node.slideshow.NodeView", {
statics: {
LOGGER_HEIGHT: 28,
-
- isPropsFormShowable: function(node) {
- if (node && ("getPropsForm" in node) && node.getPropsForm()) {
- return node.getPropsForm().hasVisibleInputs();
- }
- return false;
- },
},
members: {
@@ -55,11 +48,22 @@ qx.Class.define("osparc.node.slideshow.NodeView", {
this._settingsLayout.removeAll();
const node = this.getNode();
- const propsForm = node.getPropsForm();
- if (propsForm && node.hasInputs()) {
- this._settingsLayout.add(propsForm);
+ if (
+ node.isComputational() &&
+ node.hasInputs() &&
+ "getPropsForm" in node &&
+ node.getPropsForm() &&
+ node.getPropsForm().hasVisibleInputs()
+ ) {
+ this._settingsLayout.add(node.getPropsForm());
}
- this.__checkSettingsVisibility();
+
+ const showSettings = node.isComputational();
+ this._settingsLayout.setVisibility(showSettings ? "visible" : "excluded");
+
+ node.getStudy().bind("pipelineRunning", this._settingsLayout, "enabled", {
+ converter: pipelineRunning => !pipelineRunning
+ });
this._mainView.add(this._settingsLayout);
},
@@ -128,19 +132,6 @@ qx.Class.define("osparc.node.slideshow.NodeView", {
this.base(arguments, node);
},
- __checkSettingsVisibility: function() {
- const isSettingsGroupShowable = this.isSettingsGroupShowable();
- this._settingsLayout.setVisibility(isSettingsGroupShowable ? "visible" : "excluded");
- },
-
- isSettingsGroupShowable: function() {
- const node = this.getNode();
- if (node.isComputational()) {
- return this.self().isPropsFormShowable(node);
- }
- return false;
- },
-
__iFrameChanged: function() {
this._iFrameLayout.removeAll();
diff --git a/services/static-webserver/client/source/class/osparc/share/AddCollaborators.js b/services/static-webserver/client/source/class/osparc/share/AddCollaborators.js
index 1099aa6c1cbd..9dc2a7192c56 100644
--- a/services/static-webserver/client/source/class/osparc/share/AddCollaborators.js
+++ b/services/static-webserver/client/source/class/osparc/share/AddCollaborators.js
@@ -16,7 +16,7 @@
************************************************************************ */
/**
- * Widget that offers the "Share with..." button to add collaborators to a resource.
+ * Widget that offers the "Share" button to add collaborators to a resource.
* It also provides the "Check Organization..." direct access.
* As output, once the user select n gid in the NewCollaboratorsManager pop up window,
* an event is fired with the list of collaborators.
@@ -60,7 +60,9 @@ qx.Class.define("osparc.share.AddCollaborators", {
this._add(control);
break;
case "share-with":
- control = new qx.ui.form.Button(this.tr("Share with...")).set({
+ control = new qx.ui.form.Button().set({
+ icon: "@FontAwesome5Solid/share-alt/12",
+ label: this.tr("Share"),
appearance: "form-button",
alignX: "left",
allowGrowX: false
diff --git a/services/static-webserver/client/source/class/osparc/share/Collaborators.js b/services/static-webserver/client/source/class/osparc/share/Collaborators.js
index 63509d88871b..3a15baabd6c4 100644
--- a/services/static-webserver/client/source/class/osparc/share/Collaborators.js
+++ b/services/static-webserver/client/source/class/osparc/share/Collaborators.js
@@ -286,10 +286,13 @@ qx.Class.define("osparc.share.Collaborators", {
__createCollaboratorsListSection: function() {
const vBox = new qx.ui.container.Composite(new qx.ui.layout.VBox(5));
- const header = new qx.ui.container.Composite(new qx.ui.layout.HBox());
+ const header = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
- const label = new qx.ui.basic.Label(this.tr("Shared with"));
- label.set({allowGrowX: true});
+ const label = new qx.ui.basic.Label(this.tr("Shared with:"));
+ label.set({
+ allowGrowX: true,
+ alignY: "middle",
+ });
header.add(label, {
flex: 1
});
@@ -306,7 +309,8 @@ qx.Class.define("osparc.share.Collaborators", {
decorator: "no-border",
spacing: 3,
width: 150,
- padding: 0
+ padding: 0,
+ backgroundColor: "transparent",
});
const collaboratorsModel = this.__collaboratorsModel = new qx.data.Array();
diff --git a/services/static-webserver/client/source/class/osparc/share/ShareTemplateWith.js b/services/static-webserver/client/source/class/osparc/share/ShareTemplateWith.js
index 5a26282b57d8..9c3ebe915e64 100644
--- a/services/static-webserver/client/source/class/osparc/share/ShareTemplateWith.js
+++ b/services/static-webserver/client/source/class/osparc/share/ShareTemplateWith.js
@@ -53,7 +53,10 @@ qx.Class.define("osparc.share.ShareTemplateWith", {
value: this.tr("Make the template accessible to:"),
font: "text-14",
});
- addCollaborators.getChildControl("share-with").setLabel(this.tr("Share with..."));
+ addCollaborators.getChildControl("share-with").set({
+ icon: "@FontAwesome5Solid/share-alt/12",
+ label: this.tr("Share"),
+ });
this._add(addCollaborators);
this._add(this.__selectedCollabs);
diff --git a/services/static-webserver/client/source/class/osparc/store/Groups.js b/services/static-webserver/client/source/class/osparc/store/Groups.js
index e367f0e9cbb3..2aa6794a6871 100644
--- a/services/static-webserver/client/source/class/osparc/store/Groups.js
+++ b/services/static-webserver/client/source/class/osparc/store/Groups.js
@@ -158,6 +158,15 @@ qx.Class.define("osparc.store.Groups", {
return Object.keys(this.getOrganizations());
},
+ getAllMyGroupIds: function() {
+ return [
+ this.getMyGroupId(),
+ ...this.getOrganizationIds().map(gId => parseInt(gId)),
+ this.getEveryoneProductGroup().getGroupId(),
+ this.getEveryoneGroup().getGroupId(),
+ ]
+ },
+
getGroup: function(groupId) {
const groups = [];
diff --git a/services/static-webserver/client/source/class/osparc/store/Services.js b/services/static-webserver/client/source/class/osparc/store/Services.js
index 676eb86cb80d..224a88064d19 100644
--- a/services/static-webserver/client/source/class/osparc/store/Services.js
+++ b/services/static-webserver/client/source/class/osparc/store/Services.js
@@ -24,6 +24,8 @@ qx.Class.define("osparc.store.Services", {
__studyServicesPromisesCached: {},
__pricingPlansCached: {},
+ UNKNOWN_SERVICE_KEY: "simcore/services/frontend/unknown",
+
getServicesLatest: function(useCache = true) {
return new Promise(resolve => {
if (useCache && Object.keys(this.__servicesCached)) {
@@ -102,56 +104,56 @@ qx.Class.define("osparc.store.Services", {
},
getService: function(key, version, useCache = true) {
+ if (!this.__servicesPromisesCached) {
+ this.__servicesPromisesCached = {};
+ }
+ if (!(key in this.__servicesPromisesCached)) {
+ this.__servicesPromisesCached[key] = {};
+ }
+
// avoid request deduplication
- if (key in this.__servicesPromisesCached && version in this.__servicesPromisesCached[key]) {
+ if (this.__servicesPromisesCached[key][version]) {
return this.__servicesPromisesCached[key][version];
}
- // Create a new promise
- const promise = new Promise((resolve, reject) => {
- if (
- useCache &&
- this.__isInCache(key, version) &&
- (
- this.__servicesCached[key][version] === null ||
- "history" in this.__servicesCached[key][version]
- )
- ) {
- resolve(this.__servicesCached[key][version]);
- return;
- }
+ if (
+ useCache &&
+ this.__isInCache(key, version) &&
+ (
+ this.__servicesCached[key][version] === null ||
+ "history" in this.__servicesCached[key][version]
+ )
+ ) {
+ return Promise.resolve(this.__servicesCached[key][version]);
+ }
- if (!(key in this.__servicesPromisesCached)) {
- this.__servicesPromisesCached[key] = {};
- }
- const params = {
- url: osparc.data.Resources.getServiceUrl(key, version)
- };
- this.__servicesPromisesCached[key][version] = osparc.data.Resources.fetch("services", "getOne", params)
- .then(service => {
- this.__addServiceToCache(service);
- // Resolve the promise locally before deleting it
- resolve(service);
- })
- .catch(err => {
- // Store null in cache to avoid repeated failed requests
- this.__addToCache(key, version, null);
- console.error(err);
- reject(err);
- })
- .finally(() => {
- // Remove the promise from the cache
- delete this.__servicesPromisesCached[key][version];
- });
- });
+ const params = {
+ url: osparc.data.Resources.getServiceUrl(key, version)
+ };
+ const fetchPromise = osparc.data.Resources.fetch("services", "getOne", params)
+ .then(service => {
+ this.__addServiceToCache(service);
+ // Resolve the promise locally before deleting it
+ return service;
+ })
+ .catch(err => {
+ // Store null in cache to avoid repeated failed requests
+ this.__addToCache(key, version, null);
+ console.error(err);
+ throw err;
+ })
+ .finally(() => {
+ // Remove the promise from the cache
+ delete this.__servicesPromisesCached[key][version];
+ });
// Store the promise in the cache
// The point of keeping this assignment outside of the main Promise block is to
// ensure that the promise is immediately stored in the cache before any asynchronous
// operations (like fetch) are executed. This prevents duplicate requests for the
// same key and version when multiple consumers call getService concurrently.
- this.__servicesPromisesCached[key][version] = promise;
- return promise;
+ this.__servicesPromisesCached[key][version] = fetchPromise;
+ return fetchPromise;
},
getStudyServices: function(studyId) {
@@ -400,6 +402,37 @@ qx.Class.define("osparc.store.Services", {
return this.getLatest("simcore/services/frontend/iterator-consumer/probe/"+type);
},
+ getUnknownServiceMetadata: function() {
+ const key = this.UNKNOWN_SERVICE_KEY;
+ const version = "0.0.0";
+ const versionDisplay = "Unknown";
+ const releaseInfo = {
+ version,
+ versionDisplay,
+ retired: null,
+ released: "2025-08-07T11:00:00.000000",
+ compatibility: null,
+ };
+ return {
+ key,
+ version,
+ versionDisplay,
+ description: "Unknown App",
+ type: "frontend",
+ name: "Unknown",
+ inputs: {},
+ outputs: {},
+ accessRights: {
+ 1: {
+ execute: true,
+ write: false,
+ }
+ },
+ release: releaseInfo,
+ history: [releaseInfo],
+ };
+ },
+
__addServiceToCache: function(service) {
this.__addHit(service);
this.__addTSRInfo(service);
diff --git a/services/static-webserver/client/source/class/osparc/store/Support.js b/services/static-webserver/client/source/class/osparc/store/Support.js
index 028030c826dc..13c8cbd8d051 100644
--- a/services/static-webserver/client/source/class/osparc/store/Support.js
+++ b/services/static-webserver/client/source/class/osparc/store/Support.js
@@ -160,12 +160,15 @@ qx.Class.define("osparc.store.Support", {
},
getMailToLabel: function(email, subject) {
- const mailto = new qx.ui.basic.Label(this.mailToLink(email, subject, false)).set({
+ const mailto = new qx.ui.basic.Label().set({
font: "text-14",
allowGrowX: true, // let it grow to make it easier to select
selectable: true,
rich: true,
});
+ if (email) {
+ mailto.setValue(this.mailToLink(email, subject, false));
+ }
return mailto;
},
diff --git a/services/static-webserver/client/source/class/osparc/ui/basic/Thumbnail.js b/services/static-webserver/client/source/class/osparc/ui/basic/Thumbnail.js
index ff7c6f5d98d2..876abfbdb1de 100644
--- a/services/static-webserver/client/source/class/osparc/ui/basic/Thumbnail.js
+++ b/services/static-webserver/client/source/class/osparc/ui/basic/Thumbnail.js
@@ -83,7 +83,7 @@ qx.Class.define("osparc.ui.basic.Thumbnail", {
__applySource: function(val) {
const image = this.getChildControl("image");
if (val) {
- if (osparc.utils.Utils.isValidHttpUrl(val)) {
+ if (!val.startsWith("osparc/") && osparc.utils.Utils.isValidHttpUrl(val)) {
osparc.utils.Utils.setUrlSourceToImage(image, val);
} else {
image.setSource(val);
diff --git a/services/static-webserver/client/source/class/osparc/ui/list/ListItem.js b/services/static-webserver/client/source/class/osparc/ui/list/ListItem.js
index 89f0d7c87b7c..0f9130d755a4 100644
--- a/services/static-webserver/client/source/class/osparc/ui/list/ListItem.js
+++ b/services/static-webserver/client/source/class/osparc/ui/list/ListItem.js
@@ -60,6 +60,7 @@ qx.Class.define("osparc.ui.list.ListItem", {
padding: 5,
minHeight: 48,
alignY: "middle",
+ decorator: "rounded",
});
this.addListener("pointerover", this._onPointerOver, this);
diff --git a/services/static-webserver/client/source/class/osparc/widget/NodeOutputs.js b/services/static-webserver/client/source/class/osparc/widget/NodeOutputs.js
index c8da28a0c48e..7410c6c547ea 100644
--- a/services/static-webserver/client/source/class/osparc/widget/NodeOutputs.js
+++ b/services/static-webserver/client/source/class/osparc/widget/NodeOutputs.js
@@ -46,7 +46,7 @@ qx.Class.define("osparc.widget.NodeOutputs", {
this.set({
node,
- ports: node.getMetaData().outputs
+ ports: node.getMetadata().outputs
});
node.addListener("changeOutputs", () => this.__outputsChanged(), this);
@@ -198,18 +198,21 @@ qx.Class.define("osparc.widget.NodeOutputs", {
valueWidget = new osparc.ui.basic.LinkLabel();
if ("store" in value) {
// it's a file
- const download = true;
- const locationId = value.store;
- const fileId = value.path;
const filename = value.filename || osparc.file.FilePicker.getFilenameFromPath(value);
valueWidget.setValue(filename);
valueWidget.eTag = value["eTag"];
- osparc.store.Data.getInstance().getPresignedLink(download, locationId, fileId)
- .then(presignedLinkData => {
- if ("resp" in presignedLinkData && presignedLinkData.resp) {
- valueWidget.setUrl(presignedLinkData.resp.link);
- }
- });
+ const download = true;
+ const locationId = value.store;
+ const fileId = value.path;
+ // request the presigned link only when the widget is shown
+ valueWidget.addListenerOnce("appear", () => {
+ osparc.store.Data.getInstance().getPresignedLink(download, locationId, fileId)
+ .then(presignedLinkData => {
+ if ("resp" in presignedLinkData && presignedLinkData.resp) {
+ valueWidget.setUrl(presignedLinkData.resp.link);
+ }
+ });
+ });
} else if ("downloadLink" in value) {
// it's a link
const filename = (value.filename && value.filename.length > 0) ? value.filename : osparc.file.FileDownloadLink.extractLabelFromLink(value["downloadLink"]);
diff --git a/services/static-webserver/client/source/class/osparc/widget/NodesTree.js b/services/static-webserver/client/source/class/osparc/widget/NodesTree.js
index e6e9fb599c67..309389183865 100644
--- a/services/static-webserver/client/source/class/osparc/widget/NodesTree.js
+++ b/services/static-webserver/client/source/class/osparc/widget/NodesTree.js
@@ -74,34 +74,40 @@ qx.Class.define("osparc.widget.NodesTree", {
statics: {
__getSortingValue: function(node) {
- if (node.isFilePicker()) {
- return osparc.service.Utils.getSorting("file");
- } else if (node.isParameter()) {
- return osparc.service.Utils.getSorting("parameter");
- } else if (node.isIterator()) {
- return osparc.service.Utils.getSorting("iterator");
- } else if (node.isProbe()) {
- return osparc.service.Utils.getSorting("probe");
+ if (node.getMetadata()) {
+ if (node.isFilePicker()) {
+ return osparc.service.Utils.getSorting("file");
+ } else if (node.isParameter()) {
+ return osparc.service.Utils.getSorting("parameter");
+ } else if (node.isIterator()) {
+ return osparc.service.Utils.getSorting("iterator");
+ } else if (node.isProbe()) {
+ return osparc.service.Utils.getSorting("probe");
+ }
+ return osparc.service.Utils.getSorting(node.getMetadata().type);
}
- return osparc.service.Utils.getSorting(node.getMetaData().type);
+ return null;
},
__getIcon: function(node) {
let icon = null;
- if (node.isFilePicker()) {
- icon = osparc.service.Utils.getIcon("file");
- } else if (node.isParameter()) {
- icon = osparc.service.Utils.getIcon("parameter");
- } else if (node.isIterator()) {
- icon = osparc.service.Utils.getIcon("iterator");
- } else if (node.isProbe()) {
- icon = osparc.service.Utils.getIcon("probe");
- } else {
- icon = osparc.service.Utils.getIcon(node.getMetaData().type);
- }
- if (icon) {
- icon += "14";
+ if (node.getMetadata()) {
+ if (node.isFilePicker()) {
+ icon = osparc.service.Utils.getIcon("file");
+ } else if (node.isParameter()) {
+ icon = osparc.service.Utils.getIcon("parameter");
+ } else if (node.isIterator()) {
+ icon = osparc.service.Utils.getIcon("iterator");
+ } else if (node.isProbe()) {
+ icon = osparc.service.Utils.getIcon("probe");
+ } else {
+ icon = osparc.service.Utils.getIcon(node.getMetadata().type);
+ }
+ if (icon) {
+ icon += "14";
+ }
}
+
return icon;
},
@@ -125,22 +131,32 @@ qx.Class.define("osparc.widget.NodesTree", {
id: node.getNodeId(),
label: "Node",
children: [],
- icon: this.__getIcon(node),
+ icon: "",
iconColor: "text",
sortingValue: this.__getSortingValue(node),
node
};
+
const nodeModel = qx.data.marshal.Json.createModel(nodeData, true);
node.bind("label", nodeModel, "label");
- if (node.isDynamic()) {
- node.getStatus().bind("interactive", nodeModel, "iconColor", {
- converter: status => osparc.service.StatusUI.getColor(status)
- });
- } else if (node.isComputational()) {
- node.getStatus().bind("running", nodeModel, "iconColor", {
- converter: status => osparc.service.StatusUI.getColor(status)
- });
+ const populateWithMetadata = () => {
+ if (node.isDynamic()) {
+ node.getStatus().bind("interactive", nodeModel, "iconColor", {
+ converter: status => osparc.service.StatusUI.getColor(status)
+ });
+ } else if (node.isComputational()) {
+ node.getStatus().bind("running", nodeModel, "iconColor", {
+ converter: status => osparc.service.StatusUI.getColor(status)
+ });
+ }
+ nodeModel.setIcon(this.__getIcon(node));
}
+ if (node.getMetadata()) {
+ populateWithMetadata();
+ } else {
+ node.addListenerOnce("changeMetadata", () => populateWithMetadata(), this);
+ }
+
return nodeModel;
}
},
@@ -290,7 +306,7 @@ qx.Class.define("osparc.widget.NodesTree", {
});
} else {
const node = study.getWorkbench().getNode(nodeId);
- const metadata = node.getMetaData();
+ const metadata = node.getMetadata();
const serviceDetails = new osparc.info.ServiceLarge(metadata, {
nodeId,
label: node.getLabel(),
diff --git a/services/static-webserver/client/source/class/osparc/widget/Renamer.js b/services/static-webserver/client/source/class/osparc/widget/Renamer.js
index fbba9f5184e4..a5c1929b363f 100644
--- a/services/static-webserver/client/source/class/osparc/widget/Renamer.js
+++ b/services/static-webserver/client/source/class/osparc/widget/Renamer.js
@@ -41,7 +41,7 @@ qx.Class.define("osparc.widget.Renamer", {
this.base(arguments, winTitle || this.tr("Rename"));
const maxWidth = 350;
- const minWidth = 150;
+ const minWidth = 200;
const labelWidth = oldLabel ? Math.min(Math.max(parseInt(oldLabel.length*4), minWidth), maxWidth) : minWidth;
this.set({
layout: new qx.ui.layout.VBox(5),
diff --git a/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js b/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js
index 1213ec57078c..d7005896e898 100644
--- a/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js
+++ b/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js
@@ -63,7 +63,7 @@ qx.Class.define("osparc.workbench.NodeUI", {
captionBar.set({
cursor: "move",
paddingRight: 0,
- paddingLeft: this.self().PORT_WIDTH
+ paddingLeft: this.self().PORT_DIAMETER - 6,
});
const menuBtn = this.__getMenuButton();
@@ -105,8 +105,8 @@ qx.Class.define("osparc.workbench.NodeUI", {
},
type: {
- check: ["normal", "file", "parameter", "iterator", "probe"],
- init: "normal",
+ check: ["computational", "dynamic", "file", "parameter", "iterator", "probe", "unknown"],
+ init: null,
nullable: false,
apply: "__applyType"
},
@@ -133,8 +133,8 @@ qx.Class.define("osparc.workbench.NodeUI", {
NODE_WIDTH: 180,
NODE_HEIGHT: 80,
FILE_NODE_WIDTH: 120,
- PORT_HEIGHT: 18,
- PORT_WIDTH: 11,
+ PORT_DIAMETER: 18,
+ PORT_MARGIN_TOP: 4,
CONTENT_PADDING: 2,
PORT_CONNECTED: "@FontAwesome5Regular/dot-circle/18",
PORT_DISCONNECTED: "@FontAwesome5Regular/circle/18",
@@ -220,13 +220,20 @@ qx.Class.define("osparc.workbench.NodeUI", {
column: this.self().CAPTION_POS.DEPRECATED
});
break;
- case "chips": {
+ case "middle-container":
control = new qx.ui.container.Composite(new qx.ui.layout.Flow(3, 3).set({
alignY: "middle"
})).set({
- margin: [3, 4]
+ padding: [3, 4]
});
- let nodeType = this.getNode().getMetaData().type;
+ this.add(control, {
+ row: 0,
+ column: 1
+ });
+ break;
+ case "node-type-chip": {
+ control = new osparc.ui.basic.Chip();
+ let nodeType = this.getNode().getMetadata().type;
if (this.getNode().isIterator()) {
nodeType = "iterator";
} else if (this.getNode().isProbe()) {
@@ -234,32 +241,39 @@ qx.Class.define("osparc.workbench.NodeUI", {
}
const type = osparc.service.Utils.getType(nodeType);
if (type) {
- const chip = new osparc.ui.basic.Chip().set({
+ control.set({
icon: type.icon + "14",
toolTipText: type.label
});
- control.add(chip);
+ } else if (this.getNode().isUnknown()) {
+ control.set({
+ icon: "@FontAwesome5Solid/question/14",
+ toolTipText: "Unknown",
+ });
}
- const nodeStatus = new osparc.ui.basic.NodeStatusUI(this.getNode());
- control.add(nodeStatus);
- const statusLabel = nodeStatus.getChildControl("label");
+ this.getChildControl("middle-container").add(control);
+ break;
+ }
+ case "node-status-ui": {
+ control = new osparc.ui.basic.NodeStatusUI(this.getNode()).set({
+ maxHeight: 20,
+ font: "text-10",
+ });
+ const statusLabel = control.getChildControl("label");
const requestOpenLogger = () => this.fireEvent("requestOpenLogger");
const evaluateLabel = () => {
const failed = statusLabel.getValue() === "Unsuccessful";
statusLabel.setCursor(failed ? "pointer" : "auto");
- if (nodeStatus.hasListener("tap")) {
- nodeStatus.removeListener("tap", requestOpenLogger);
+ if (control.hasListener("tap")) {
+ control.removeListener("tap", requestOpenLogger);
}
if (failed) {
- nodeStatus.addListener("tap", requestOpenLogger);
+ control.addListener("tap", requestOpenLogger);
}
};
evaluateLabel();
statusLabel.addListener("changeValue", evaluateLabel);
- this.add(control, {
- row: 0,
- column: 1
- });
+ this.getChildControl("middle-container").add(control);
break;
}
case "progress":
@@ -293,16 +307,19 @@ qx.Class.define("osparc.workbench.NodeUI", {
});
this.resetThumbnail();
- this.__createWindowLayout();
+ this.__createContentLayout();
},
- __createWindowLayout: function() {
+ __createContentLayout: function() {
const node = this.getNode();
+ if (node) {
+ this.getChildControl("middle-container").removeAll();
+ this.getChildControl("node-type-chip");
+ this.getChildControl("node-status-ui");
- this.getChildControl("chips").show();
-
- if (node.isComputational() || node.isFilePicker() || node.isIterator()) {
- this.getChildControl("progress").show();
+ if (node.isComputational() || node.isFilePicker() || node.isIterator()) {
+ this.getChildControl("progress");
+ }
}
},
@@ -313,7 +330,7 @@ qx.Class.define("osparc.workbench.NodeUI", {
setTimeout(() => this.fireEvent("updateNodeDecorator"), 50);
}
});
- const metadata = node.getMetaData();
+ const metadata = node.getMetadata();
this.__createPorts(true, Boolean((metadata && metadata.inputs && Object.keys(metadata.inputs).length)));
this.__createPorts(false, Boolean((metadata && metadata.outputs && Object.keys(metadata.outputs).length)));
if (node.isComputational() || node.isFilePicker()) {
@@ -321,7 +338,11 @@ qx.Class.define("osparc.workbench.NodeUI", {
converter: val => val === null ? 0 : val
});
}
- if (node.isFilePicker()) {
+ if (node.isComputational()) {
+ this.setType("computational");
+ } else if (node.isDynamic()) {
+ this.setType("dynamic");
+ } else if (node.isFilePicker()) {
this.setType("file");
} else if (node.isParameter()) {
this.setType("parameter");
@@ -330,6 +351,8 @@ qx.Class.define("osparc.workbench.NodeUI", {
this.setType("iterator");
} else if (node.isProbe()) {
this.setType("probe");
+ } else if (node.isUnknown()) {
+ this.setType("unknown");
}
this.addListener("resize", () => {
setTimeout(() => this.fireEvent("updateNodeDecorator"), 50);
@@ -375,11 +398,10 @@ qx.Class.define("osparc.workbench.NodeUI", {
}
const lock = this.getChildControl("lock");
- if (node.getPropsForm()) {
- node.getPropsForm().bind("enabled", lock, "visibility", {
- converter: val => val ? "excluded" : "visible"
- });
- }
+ node.getStudy().bind("pipelineRunning", lock, "visibility", {
+ converter: pipelineRunning => pipelineRunning ? "visible" : "excluded"
+ });
+
this.__markerBtn.show();
this.getNode().bind("marker", this.__markerBtn, "label", {
converter: val => val ? this.tr("Remove Marker") : this.tr("Add Marker")
@@ -411,7 +433,7 @@ qx.Class.define("osparc.workbench.NodeUI", {
textColor: osparc.service.StatusUI.getColor("deprecated")
});
let ttMsg = osparc.service.Utils.DEPRECATED_SERVICE_TEXT;
- const deprecatedDateMsg = osparc.service.Utils.getDeprecationDateText(node.getMetaData());
+ const deprecatedDateMsg = osparc.service.Utils.getDeprecationDateText(node.getMetadata());
if (deprecatedDateMsg) {
ttMsg = ttMsg + "
" + deprecatedDateMsg;
}
@@ -468,6 +490,9 @@ qx.Class.define("osparc.workbench.NodeUI", {
case "probe":
this.__turnIntoProbeUI();
break;
+ case "unknown":
+ this.__turnIntoUnknownUI();
+ break;
}
},
@@ -493,8 +518,8 @@ qx.Class.define("osparc.workbench.NodeUI", {
const width = this.self().FILE_NODE_WIDTH;
this.__setNodeUIWidth(width);
- const chipContainer = this.getChildControl("chips");
- chipContainer.exclude();
+ const middleContainer = this.getChildControl("middle-container");
+ middleContainer.exclude();
if (this.hasChildControl("progress")) {
this.getChildControl("progress").exclude();
@@ -527,8 +552,8 @@ qx.Class.define("osparc.workbench.NodeUI", {
const label = new qx.ui.basic.Label().set({
font: "text-18"
});
- const chipContainer = this.getChildControl("chips");
- chipContainer.add(label);
+ const middleContainer = this.getChildControl("middle-container");
+ middleContainer.add(label);
this.getNode().bind("outputs", label, "value", {
converter: outputs => {
@@ -578,13 +603,22 @@ qx.Class.define("osparc.workbench.NodeUI", {
paddingLeft: 5,
font: "text-12"
});
- const chipContainer = this.getChildControl("chips");
- chipContainer.add(linkLabel);
+ const middleContainer = this.getChildControl("middle-container");
+ middleContainer.add(linkLabel);
this.getNode().getPropsForm().addListener("linkFieldModified", () => this.__setProbeValue(linkLabel), this);
this.__setProbeValue(linkLabel);
},
+ __turnIntoUnknownUI: function() {
+ const width = 110;
+ this.__setNodeUIWidth(width);
+
+ this.setEnabled(false);
+
+ this.fireEvent("updateNodeDecorator");
+ },
+
__checkTurnIntoIteratorUI: function() {
const outputs = this.getNode().getOutputs();
const portKey = "out_1";
@@ -631,9 +665,9 @@ qx.Class.define("osparc.workbench.NodeUI", {
converter: outputs => {
if (portKey in outputs && "value" in outputs[portKey] && outputs[portKey]["value"]) {
const val = outputs[portKey]["value"];
- if (this.getNode().getMetaData()["key"].includes("probe/array")) {
+ if (this.getNode().getMetadata()["key"].includes("probe/array")) {
return "[" + val.join(",") + "]";
- } else if (this.getNode().getMetaData()["key"].includes("probe/file")) {
+ } else if (this.getNode().getMetadata()["key"].includes("probe/file")) {
const filename = val.filename || osparc.file.FilePicker.getFilenameFromPath(val);
populateLinkLabel(val);
return filename;
@@ -815,16 +849,17 @@ qx.Class.define("osparc.workbench.NodeUI", {
__createPort: function(isInput, placeholder = false) {
let port = null;
- const width = this.self().PORT_HEIGHT;
+ const width = this.self().PORT_DIAMETER;
if (placeholder) {
port = new qx.ui.core.Spacer(width, width);
} else {
port = new qx.ui.basic.Image().set({
source: this.self().PORT_DISCONNECTED, // disconnected by default
height: width,
+ width: width,
+ marginTop: this.self().PORT_MARGIN_TOP,
draggable: true,
droppable: true,
- width: width,
alignY: "top",
backgroundColor: "background-main"
});
@@ -858,7 +893,7 @@ qx.Class.define("osparc.workbench.NodeUI", {
const bounds = this.getCurrentBounds();
const captionHeight = Math.max(this.getChildControl("captionbar").getSizeHint().height, this.self().captionHeight());
const x = port.isInput ? bounds.left - 6 : bounds.left + bounds.width - 1;
- const y = bounds.top + captionHeight + this.self().PORT_HEIGHT/2 + 2;
+ const y = bounds.top + captionHeight + this.self().PORT_DIAMETER/2 + this.self().PORT_MARGIN_TOP + 2;
return [x, y];
},
diff --git a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js
index a074727aaa51..6ca961dd2755 100644
--- a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js
+++ b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js
@@ -351,10 +351,7 @@ qx.Class.define("osparc.workbench.WorkbenchUI", {
let nodeUI = null;
try {
const node = await this.__getWorkbench().createNode(service.getKey(), service.getVersion());
- nodeUI = this._createNodeUI(node.getNodeId());
- this._addNodeUIToWorkbench(nodeUI, pos);
- qx.ui.core.queue.Layout.flush();
- this.__createDragDropMechanism(nodeUI);
+ nodeUI = this.addNode(node, pos);
} catch (err) {
console.error(err);
} finally {
@@ -364,6 +361,20 @@ qx.Class.define("osparc.workbench.WorkbenchUI", {
return nodeUI;
},
+ addNode: function(node, pos) {
+ if (pos === undefined) {
+ pos = {
+ x: 0,
+ y: 0,
+ };
+ }
+ const nodeUI = this._createNodeUI(node.getNodeId());
+ this._addNodeUIToWorkbench(nodeUI, pos);
+ qx.ui.core.queue.Layout.flush();
+ this.__createDragDropMechanism(nodeUI);
+ return nodeUI;
+ },
+
__getNodesBounds: function() {
if (this.__nodesUI.length === 0) {
return null;
@@ -663,12 +674,12 @@ qx.Class.define("osparc.workbench.WorkbenchUI", {
const nodeUI = new osparc.workbench.NodeUI(node);
this.bind("scale", nodeUI, "scale");
node.addListener("keyChanged", () => this.__selectNode(nodeUI), this);
- node.addListener("createEdge", e => {
+ node.addListener("edgeCreated", e => {
const data = e.getData();
const { nodeId1, nodeId2 } = data;
this._createEdgeBetweenNodes(nodeId1, nodeId2, false);
});
- node.addListener("removeEdge", e => {
+ node.addListener("edgeRemoved", e => {
const data = e.getData();
const { nodeId1, nodeId2 } = data;
this.__removeEdgeBetweenNodes(nodeId1, nodeId2);
@@ -1706,7 +1717,7 @@ qx.Class.define("osparc.workbench.WorkbenchUI", {
__openNodeInfo: function(nodeId) {
if (nodeId) {
const node = this.getStudy().getWorkbench().getNode(nodeId);
- const metadata = node.getMetaData();
+ const metadata = node.getMetadata();
const serviceDetails = new osparc.info.ServiceLarge(metadata, {
nodeId,
label: node.getLabel(),