diff --git a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js
index 8a268861358..70b9abda163 100644
--- a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js
+++ b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js
@@ -812,7 +812,7 @@ qx.Class.define("osparc.dashboard.CardBase", {
this.setBlocked(projectLocked ? "IN_USE" : false);
if (projectLocked) {
- this.__showBlockedCardFromStatus("IN_USE", state["shareState"]);
+ this.__showBlockedCardFromStatus("IN_USE", state);
}
if (pipelineState) {
@@ -896,10 +896,10 @@ qx.Class.define("osparc.dashboard.CardBase", {
avatarGroup.setUserGroupIds(currentUserGroupIds);
},
- __showBlockedCardFromStatus: function(reason, shareState) {
+ __showBlockedCardFromStatus: function(reason, state) {
switch (reason) {
case "IN_USE":
- this.__blockedInUse(shareState);
+ this.__blockedInUse(state);
break;
case "IN_DEBT":
this.__blockedInDebt();
@@ -907,9 +907,9 @@ qx.Class.define("osparc.dashboard.CardBase", {
}
},
- __blockedInUse: function(shareState) {
- const status = shareState["status"];
- const currentUserGroupIds = shareState["currentUserGroupids"];
+ __blockedInUse: function(state) {
+ const projectStatus = osparc.study.Utils.state.getProjectStatus(state);
+ const currentUserGroupIds = osparc.study.Utils.state.getCurrentGroupIds(state);
const usersStore = osparc.store.Users.getInstance();
const userPromises = currentUserGroupIds.map(userGroupId => usersStore.getUser(userGroupId));
const usernames = [];
@@ -925,7 +925,7 @@ qx.Class.define("osparc.dashboard.CardBase", {
console.error("Failed to fetch user data for avatars:", error);
})
.finally(() => {
- switch (status) {
+ switch (projectStatus) {
case "CLOSING":
image = "@FontAwesome5Solid/key/";
toolTip += this.tr("Closing...");
diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js
index 5855044dc11..acc86c34cdd 100644
--- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js
+++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js
@@ -81,9 +81,10 @@ qx.Class.define("osparc.dashboard.ResourceDetails", {
this.__resourceModel["resourceType"] = resourceData["resourceType"];
this.__addPages();
}
- if (resourceData["functionClass"] === osparc.data.model.Function.FUNCTION_CLASS.PROJECT) {
+ // use latestResourceData, resourceData doesn't have the functionClass nor the templateId
+ if (latestResourceData["functionClass"] === osparc.data.model.Function.FUNCTION_CLASS.PROJECT) {
// this is only required for functions that have a template linked
- osparc.store.Templates.fetchTemplate(resourceData["templateId"])
+ osparc.store.Templates.fetchTemplate(latestResourceData["templateId"])
.then(templateData => {
// prefetch function's underlying template's services metadata
osparc.store.Services.getStudyServicesMetadata(templateData)
@@ -592,7 +593,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", {
page.setEnabled(enabled);
const lazyLoadContent = () => {
- const resourceModel = osparc.utils.Resources.isFunction(this.__resourceData) ? this.__resourceModel.getTemplate() : this.__resourceData;
+ const resourceModel = osparc.utils.Resources.isFunction(this.__resourceData) ? this.__resourceModel.getTemplate() : this.__resourceModel;
const preview = new osparc.study.StudyPreview(resourceModel);
page.addToContent(preview);
this.__widgets.push(preview);
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 048f69ebfc0..0e40eface10 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
@@ -98,11 +98,6 @@ qx.Class.define("osparc.data.model.Node", {
event: "changeLabel"
},
- inputAccess: {
- check: "Object",
- nullable: true
- },
-
dynamicV2: {
check: "Boolean",
init: false,
@@ -171,7 +166,8 @@ qx.Class.define("osparc.data.model.Node", {
check: "qx.core.Object",
init: null,
nullable: true,
- event: "changeMarker"
+ event: "changeMarker",
+ apply: "__applyMarker",
},
inputConnected: {
@@ -198,6 +194,7 @@ qx.Class.define("osparc.data.model.Node", {
events: {
"updateStudyDocument": "qx.event.type.Event",
+ "projectDocumentChanged": "qx.event.type.Data",
"reloadModel": "qx.event.type.Event",
"retrieveInputs": "qx.event.type.Data",
"keyChanged": "qx.event.type.Event",
@@ -213,6 +210,33 @@ qx.Class.define("osparc.data.model.Node", {
},
statics: {
+ // Properties of the Node class that should not be listened to
+ ListenChangesProps: [
+ // "study", // immutable
+ "key",
+ "version",
+ // "nodeId", // immutable
+ "label",
+ "inputs", // own listener
+ "inputsUnits", // own listener
+ // "dynamicV2", // frontend only
+ // "serviceUrl", // frontend only
+ // "portsConnected", // frontend only
+ "outputs", // listen to changes only if this is a frontend node
+ // "status", // backend driven
+ // "errors", // frontend only
+ "bootOptions",
+ // "propsForm", // frontend only
+ // "outputsForm", // frontend only
+ // "marker", // own listener
+ // "inputConnected", // frontend only
+ // "outputConnected", // frontend only
+ // "logger", // frontend only
+ "inputNodes", // !! not a property but goes into the model
+ "inputsRequired", // !! not a property but goes into the model
+ "progress", // !! not a property but goes into the model
+ ],
+
isFrontend: function(metadata) {
return (metadata && metadata.key && metadata.key.includes("/frontend/"));
},
@@ -406,15 +430,19 @@ qx.Class.define("osparc.data.model.Node", {
return this.__metaData;
},
+ hasPropsForm: function() {
+ return this.isPropertyInitialized("propsForm") && this.getPropsForm();
+ },
+
__getInputData: function() {
- if (this.isPropertyInitialized("propsForm") && this.getPropsForm()) {
+ if (this.hasPropsForm()) {
return this.getPropsForm().getValues();
}
return {};
},
__getInputUnits: function() {
- if (this.isPropertyInitialized("propsForm") && this.getPropsForm()) {
+ if (this.hasPropsForm()) {
const changedUnits = this.getPropsForm().getChangedXUnits();
if (Object.keys(changedUnits).length) {
return changedUnits;
@@ -503,8 +531,11 @@ qx.Class.define("osparc.data.model.Node", {
populateInputOutputData: function(nodeData) {
this.__setInputData(nodeData.inputs);
this.__setInputUnits(nodeData.inputsUnits);
- this.__setInputDataAccess(nodeData.inputAccess);
if (this.getPropsForm()) {
+ const study = this.getStudy();
+ if (study && study.isReadOnly()) {
+ this.getPropsForm().setEnabled(false);
+ }
this.getPropsForm().makeInputsDynamic();
}
this.setOutputData(nodeData.outputs);
@@ -669,6 +700,31 @@ qx.Class.define("osparc.data.model.Node", {
this.setMarker(null);
},
+ __applyMarker: function(marker) {
+ if (marker) {
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "add",
+ "path": `/ui/workbench/${this.getNodeId()}/marker`,
+ "value": marker.getColor(),
+ "osparc-resource": "ui",
+ });
+ marker.addListener("changeColor", e => {
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/ui/workbench/${this.getNodeId()}/marker`,
+ "value": e.getData(),
+ "osparc-resource": "ui",
+ });
+ });
+ } else {
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "delete",
+ "path": `/ui/workbench/${this.getNodeId()}/marker`,
+ "osparc-resource": "ui",
+ });
+ }
+ },
+
__setInputData: function(inputs) {
if (this.__settingsForm && inputs) {
const inputData = {};
@@ -692,18 +748,6 @@ qx.Class.define("osparc.data.model.Node", {
}
},
- __setInputDataAccess: function(inputAccess) {
- if (inputAccess) {
- this.setInputAccess(inputAccess);
- this.getPropsForm().setAccessLevel(inputAccess);
- }
-
- const study = this.getStudy();
- if (study && study.isReadOnly() && this.getPropsForm()) {
- this.getPropsForm().setEnabled(false);
- }
- },
-
setOutputData: function(outputs) {
if (outputs) {
let hasOutputs = false;
@@ -733,7 +777,8 @@ qx.Class.define("osparc.data.model.Node", {
this.getStatus().setModified(false);
}
- this.fireDataEvent("changeOutputs", this.getOutputs());
+ // event was fired in the outputs setter
+ // this.fireDataEvent("changeOutputs", this.getOutputs());
}
},
@@ -880,7 +925,7 @@ qx.Class.define("osparc.data.model.Node", {
if (index > -1) {
// remove node connection
this.__inputNodes.splice(index, 1);
- this.fireDataEvent("changeInputNodes");
+ this.fireEvent("changeInputNodes");
return true;
}
return false;
@@ -1186,13 +1231,25 @@ qx.Class.define("osparc.data.model.Node", {
},
setPosition: function(pos) {
- const {
- x,
- y
- } = pos;
+ const {x, y} = pos;
+ if (x === this.__posX && y === this.__posY) {
+ return; // no change
+ }
+
// keep positions positive
this.__posX = parseInt(x) < 0 ? 0 : parseInt(x);
this.__posY = parseInt(y) < 0 ? 0 : parseInt(y);
+
+ const nodeId = this.getNodeId();
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/ui/workbench/${nodeId}/position`,
+ "value": {
+ "x": this.__posX,
+ "y": this.__posY,
+ },
+ "osparc-resource": "ui",
+ });
},
getPosition: function() {
@@ -1241,7 +1298,121 @@ qx.Class.define("osparc.data.model.Node", {
}
},
- serialize: function(clean = true) {
+ listenToChanges: function() {
+ const nodeId = this.getNodeId();
+ const propertyKeys = Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.Node));
+ this.self().ListenChangesProps.forEach(key => {
+ switch (key) {
+ case "inputs":
+ if (this.hasPropsForm()) {
+ // listen to changes in the props form
+ this.getPropsForm().addListener("changeData", () => {
+ const data = this.__getInputData();
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/workbench/${nodeId}/inputs`,
+ "value": data,
+ "osparc-resource": "node",
+ });
+ });
+ // listen to changes in link and unlink of ports
+ this.getPropsForm().addListener("linkFieldModified", () => {
+ const data = this.__getInputData();
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/workbench/${nodeId}/inputs`,
+ "value": data,
+ "osparc-resource": "node",
+ });
+ });
+ }
+ break;
+ case "inputsUnits":
+ if (this.hasPropsForm()) {
+ this.getPropsForm().addListener("unitChanged", () => {
+ const data = this.__getInputUnits();
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/workbench/${nodeId}/inputsUnits`,
+ "value": data,
+ "osparc-resource": "node",
+ });
+ });
+ }
+ break;
+ case "inputNodes":
+ this.addListener("changeInputNodes", () => {
+ const data = this.getInputNodes();
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/workbench/${nodeId}/inputNodes`,
+ "value": data,
+ "osparc-resource": "node",
+ });
+ }, this);
+ break;
+ case "inputsRequired":
+ this.addListener("changeInputsRequired", () => {
+ const data = this.getInputsRequired();
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/workbench/${nodeId}/inputsRequired`,
+ "value": data,
+ "osparc-resource": "node",
+ });
+ }, this);
+ break;
+ case "progress":
+ if (this.isFilePicker()) {
+ this.getStatus().addListener("changeProgress", e => {
+ const data = e.getData();
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/workbench/${nodeId}/progress`,
+ "value": data,
+ "osparc-resource": "node",
+ });
+ });
+ }
+ break;
+ case "outputs":
+ if (this.isFilePicker() || this.isParameter()) {
+ this.addListener("changeOutputs", e => {
+ let data = e.getData();
+ if (this.isFilePicker()) {
+ data = osparc.file.FilePicker.serializeOutput(this.getOutputs());
+ } else if (this.isParameter()) {
+ data = this.__getOutputsData();
+ }
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/workbench/${nodeId}/outputs`,
+ "value": data,
+ "osparc-resource": "node",
+ });
+ }, this);
+ }
+ break;
+ default:
+ if (propertyKeys.includes(key)) {
+ this.addListener("change" + qx.lang.String.firstUp(key), e => {
+ const data = e.getData();
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/workbench/${nodeId}/` + key,
+ "value": data,
+ "osparc-resource": "node",
+ });
+ }, this);
+ } else {
+ console.error(`Property "${key}" is not a valid property of osparc.data.model.Node`);
+ }
+ break;
+ }
+ });
+ },
+
+ serialize: function() {
// node generic
let nodeEntry = {
key: this.getKey(),
@@ -1249,16 +1420,10 @@ qx.Class.define("osparc.data.model.Node", {
label: this.getLabel(),
inputs: this.__getInputData(),
inputsUnits: this.__getInputUnits(),
- inputAccess: this.getInputAccess(),
inputNodes: this.getInputNodes(),
inputsRequired: this.getInputsRequired(),
bootOptions: this.getBootOptions()
};
- if (!clean) {
- nodeEntry.progress = this.getStatus().getProgress();
- nodeEntry.outputs = this.__getOutputsData();
- nodeEntry.state = this.getStatus().serialize();
- }
if (this.isFilePicker()) {
nodeEntry.outputs = osparc.file.FilePicker.serializeOutput(this.getOutputs());
@@ -1276,6 +1441,18 @@ qx.Class.define("osparc.data.model.Node", {
}
return filteredNodeEntry;
- }
+ },
+
+ serializeUI: function() {
+ const uiInfo = {}
+ uiInfo["position"] = this.getPosition();
+ const marker = this.getMarker();
+ if (marker) {
+ uiInfo["marker"] = {
+ "color": marker.getColor()
+ };
+ }
+ return uiInfo;
+ },
}
});
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 d7f37db2e00..d260144d4c0 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
@@ -147,7 +147,6 @@ qx.Class.define("osparc.data.model.Service", {
nullable: false
},
- // ------ ignore for serializing ------
xType: {
check: "String",
nullable: true,
@@ -160,30 +159,6 @@ qx.Class.define("osparc.data.model.Service", {
init: 0,
event: "changeHits",
nullable: false
- }
- // ------ ignore for serializing ------
- },
-
- statics: {
- IgnoreSerializationProps: [
- "xType",
- "hits",
- ]
+ },
},
-
- members: {
- __serviceData: null,
-
- serialize: function() {
- let jsonObject = {};
- const propertyKeys = this.self().getProperties();
- propertyKeys.forEach(key => {
- if (this.self().IgnoreSerializationProps.includes(key)) {
- return;
- }
- jsonObject[key] = this.get(key);
- });
- return jsonObject;
- },
- }
});
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 984fa029f5a..7e9cc12053b 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
@@ -215,7 +215,6 @@ qx.Class.define("osparc.data.model.Study", {
event: "changeTemplateType"
},
- // ------ ignore for serializing ------
state: {
check: "Object",
nullable: true,
@@ -256,10 +255,44 @@ qx.Class.define("osparc.data.model.Study", {
event: "changeSavePending",
init: false
},
- // ------ ignore for serializing ------
+ },
+
+ events: {
+ "projectDocumentChanged": "qx.event.type.Data",
},
statics: {
+ // Properties of the Study class that should not be listened to
+ ListenChangesProps: [
+ // "uuid", // immutable
+ // "workspaceId", // own patch
+ // "folderId", // own patch
+ "name",
+ "description",
+ // "prjOwner", // immutable
+ // "accessRights", // own patch
+ // "creationDate", // immutable
+ // "lastChangeDate", // backend sets it
+ "thumbnail",
+ "workbench", // own listener
+ "ui", // own listener
+ // "tags", // own patch
+ // "classifiers", // own patch
+ // "quality", // own patch
+ // "permalink", // backend sets it
+ "dev",
+ // "type", // immutable
+ "templateType",
+ // "state", // backend sets it
+ // "pipelineRunning", // backend sets it
+ // "readOnly", // frontend only
+ // "trashedAt", // backend sets it
+ // "trashedBy", // backend sets it
+ // "savePending", // frontend only
+ ],
+
+ // Properties of the Study class that should not be serialized
+ // when serializing the study object to send it to the backend
IgnoreSerializationProps: [
"permalink",
"state",
@@ -269,10 +302,6 @@ qx.Class.define("osparc.data.model.Study", {
"savePending",
],
- IgnoreModelizationProps: [
- "dev"
- ],
-
OwnPatch: [
"accessRights",
"workbench"
@@ -361,21 +390,46 @@ qx.Class.define("osparc.data.model.Study", {
}
return overallProgress/nCompNodes;
},
-
- isRunning: function(state) {
- return [
- "PUBLISHED",
- "PENDING",
- "WAITING_FOR_RESOURCES",
- "WAITING_FOR_CLUSTER",
- "STARTED",
- "RETRY"
- ].includes(state);
- },
},
members: {
- serialize: function(clean = true) {
+ listenToChanges: function() {
+ const propertyKeys = this.self().getProperties();
+ this.self().ListenChangesProps.forEach(key => {
+ switch (key) {
+ case "workbench":
+ this.getWorkbench().addListener("projectDocumentChanged", e => {
+ const data = e.getData();
+ this.fireDataEvent("projectDocumentChanged", data);
+ }, this);
+ break;
+ case "ui":
+ this.getUi().listenToChanges();
+ this.getUi().addListener("projectDocumentChanged", e => {
+ const data = e.getData();
+ this.fireDataEvent("projectDocumentChanged", data);
+ }, this);
+ break;
+ default:
+ if (propertyKeys.includes(key)) {
+ this.addListener("change" + qx.lang.String.firstUp(key), e => {
+ const data = e.getData();
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": "/" + key,
+ "value": data,
+ "osparc-resource": "study",
+ });
+ }, this);
+ } else {
+ console.error(`Property "${key}" is not a valid property of osparc.data.model.Study`);
+ }
+ break;
+ }
+ });
+ },
+
+ serialize: function() {
let jsonObject = {};
const propertyKeys = this.self().getProperties();
propertyKeys.forEach(key => {
@@ -383,7 +437,7 @@ qx.Class.define("osparc.data.model.Study", {
return;
}
if (key === "workbench") {
- jsonObject[key] = this.getWorkbench().serialize(clean);
+ jsonObject[key] = this.getWorkbench().serialize();
return;
}
if (key === "ui") {
@@ -575,12 +629,7 @@ qx.Class.define("osparc.data.model.Study", {
},
__applyState: function(value) {
- if (value && "state" in value) {
- const isRunning = this.self().isRunning(value["state"]["value"]);
- this.setPipelineRunning(isRunning);
- } else {
- this.setPipelineRunning(false);
- }
+ this.setPipelineRunning(osparc.study.Utils.state.isPipelineRunning(value));
},
getDisableServiceAutoStart: function() {
diff --git a/services/static-webserver/client/source/class/osparc/data/model/StudyUI.js b/services/static-webserver/client/source/class/osparc/data/model/StudyUI.js
index 04733b429ff..54ce78cde8d 100644
--- a/services/static-webserver/client/source/class/osparc/data/model/StudyUI.js
+++ b/services/static-webserver/client/source/class/osparc/data/model/StudyUI.js
@@ -42,6 +42,15 @@ qx.Class.define("osparc.data.model.StudyUI", {
this.addAnnotation(annotation);
});
}
+
+ this.getSlideshow().addListener("changeSlideshow", () => {
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": "/ui/slideshow",
+ "value": this.getSlideshow().serialize(),
+ "osparc-resource": "study-ui",
+ });
+ }, this);
},
properties: {
@@ -85,12 +94,24 @@ qx.Class.define("osparc.data.model.StudyUI", {
},
},
+ events: {
+ "projectDocumentChanged": "qx.event.type.Data",
+ },
+
statics: {
TEMPLATE_TYPE: "TEMPLATE",
TUTORIAL_TYPE: "TUTORIAL",
HYPERTOOL_TYPE: "HYPERTOOL",
HYPERTOOL_ICON: "https://raw.githubusercontent.com/ZurichMedTech/s4l-assets/refs/heads/main/app/icons/hypertool.png",
PIPELINE_ICON: "https://raw.githubusercontent.com/ZurichMedTech/s4l-assets/refs/heads/main/app/icons/diagram.png",
+
+ ListenChangesProps: [
+ // "workbench", it's handled by osparc.data.model.Workbench
+ "slideshow",
+ "currentNodeId", // eventually don't patch it, it is personal, only the last closing sets it
+ "mode", // eventually don't patch it, it is personal, only the last closing sets it
+ "annotations", // TODO
+ ],
},
members: {
@@ -102,10 +123,30 @@ qx.Class.define("osparc.data.model.StudyUI", {
addAnnotation: function(annotation) {
this.getAnnotations()[annotation.getId()] = annotation;
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "add",
+ "path": `/ui/annotations/${annotation.getId()}`,
+ "value": annotation.serialize(),
+ "osparc-resource": "study-ui",
+ });
+ annotation.addListener("annotationChanged", () => {
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/ui/annotations/${annotation.getId()}`,
+ "value": annotation.serialize(),
+ "osparc-resource": "study-ui",
+ });
+ }, this);
},
removeAnnotation: function(annotationId) {
if (annotationId in this.getAnnotations()) {
+ const annotation = this.getAnnotations()[annotationId]
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "delete",
+ "path": `/ui/annotations/${annotation.getId()}`,
+ "osparc-resource": "study-ui",
+ });
delete this.getAnnotations()[annotationId];
}
},
@@ -115,6 +156,29 @@ qx.Class.define("osparc.data.model.StudyUI", {
this.getSlideshow().removeNode(nodeId);
},
+ listenToChanges: function() {
+ const propertyKeys = Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.StudyUI));
+ this.self().ListenChangesProps.forEach(key => {
+ switch (key) {
+ default:
+ if (propertyKeys.includes(key)) {
+ this.addListener(`change${qx.lang.String.firstUp(key)}`, () => {
+ const data = this.serialize();
+ this.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/ui/${key}`,
+ "value": data,
+ "osparc-resource": "study-ui",
+ });
+ }, this);
+ } else {
+ console.error(`Property "${key}" is not a valid property of osparc.data.model.StudyUI`);
+ }
+ break;
+ }
+ });
+ },
+
serialize: function() {
const currentStudy = osparc.store.Store.getInstance().getCurrentStudy();
let jsonObject = {};
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 3e0d3eea769..605e9bd77f2 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
@@ -51,6 +51,7 @@ qx.Class.define("osparc.data.model.Workbench", {
events: {
"updateStudyDocument": "qx.event.type.Event",
+ "projectDocumentChanged": "qx.event.type.Data",
"restartAutoSaveTimer": "qx.event.type.Event",
"pipelineChanged": "qx.event.type.Event",
"reloadModel": "qx.event.type.Event",
@@ -273,6 +274,10 @@ qx.Class.define("osparc.data.model.Workbench", {
__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);
+ }
node.addListener("keyChanged", () => this.fireEvent("reloadModel"), this);
node.addListener("changeInputNodes", () => this.fireDataEvent("pipelineChanged"), this);
node.addListener("reloadModel", () => this.fireEvent("reloadModel"), this);
@@ -541,9 +546,9 @@ qx.Class.define("osparc.data.model.Workbench", {
return false;
}
+ this.fireEvent("restartAutoSaveTimer");
let node = this.getNode(nodeId);
if (node) {
- this.fireEvent("restartAutoSaveTimer");
// remove the node in the backend first
const removed = await node.removeNode();
if (removed) {
@@ -621,8 +626,10 @@ qx.Class.define("osparc.data.model.Workbench", {
const rightNode = this.getNode(rightNodeId);
if (rightNode) {
// no need to make any changes to a just removed node (it would trigger a patch call)
- rightNode.removeInputNode(leftNodeId);
+ // first remove the port connections
rightNode.removeNodePortConnections(leftNodeId);
+ // then the node connection
+ rightNode.removeInputNode(leftNodeId);
}
delete this.__edges[edgeId];
@@ -743,16 +750,15 @@ qx.Class.define("osparc.data.model.Workbench", {
}
},
- serialize: function(clean = true) {
+ serialize: function() {
if (this.__workbenchInitData !== null) {
// workbench is not initialized
return this.__workbenchInitData;
}
- let workbench = {};
- const allModels = this.getNodes();
- const nodes = Object.values(allModels);
+ const workbench = {};
+ const nodes = Object.values(this.getNodes());
for (const node of nodes) {
- const data = node.serialize(clean);
+ const data = node.serialize();
if (data) {
workbench[node.getNodeId()] = data;
}
@@ -765,17 +771,12 @@ qx.Class.define("osparc.data.model.Workbench", {
// workbenchUI is not initialized
return this.__workbenchUIInitData;
}
- let workbenchUI = {};
- const nodes = this.getNodes();
- for (const nodeId in nodes) {
- const node = nodes[nodeId];
- workbenchUI[nodeId] = {};
- workbenchUI[nodeId]["position"] = node.getPosition();
- const marker = node.getMarker();
- if (marker) {
- workbenchUI[nodeId]["marker"] = {
- color: marker.getColor()
- };
+ const workbenchUI = {};
+ const nodes = Object.values(this.getNodes());
+ for (const node of nodes) {
+ const data = node.serializeUI();
+ if (data) {
+ workbenchUI[node.getNodeId()] = data;
}
}
return workbenchUI;
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 440ea8db6ee..177fef9a788 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js
@@ -81,6 +81,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
});
this.__updatingStudy = 0;
+ this.__throttledPatchPending = false;
},
events: {
@@ -106,6 +107,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
statics: {
AUTO_SAVE_INTERVAL: 3000,
DIFF_CHECK_INTERVAL: 300,
+ THROTTLE_PATCH_TIME: 500,
READ_ONLY_TEXT: qx.locale.Manager.tr("You do not have writing permissions.
Your changes will not be saved."),
},
@@ -121,6 +123,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
__updatingStudy: null,
__updateThrottled: null,
__nodesSlidesTree: null,
+ __throttledPatchPending: null,
setStudyData: function(studyData) {
if (this.__settingStudy) {
@@ -246,8 +249,13 @@ qx.Class.define("osparc.desktop.StudyEditor", {
this.nodeSelected(nodeId);
}, this);
- workbench.addListener("updateStudyDocument", () => this.updateStudyDocument());
- workbench.addListener("restartAutoSaveTimer", () => this.__restartAutoSaveTimer());
+ if (osparc.utils.Utils.eventDrivenPatch()) {
+ study.listenToChanges(); // this includes the listener on the workbench and ui
+ study.addListener("projectDocumentChanged", e => this.projectDocumentChanged(e.getData()), this);
+ } else {
+ workbench.addListener("updateStudyDocument", () => this.updateStudyDocument());
+ workbench.addListener("restartAutoSaveTimer", () => this.__restartAutoSaveTimer());
+ }
},
__setStudyDataInBackend: function(studyData) {
@@ -802,6 +810,11 @@ qx.Class.define("osparc.desktop.StudyEditor", {
// ------------------ AUTO SAVER ------------------
__startAutoSaveTimer: function() {
+ if (osparc.utils.Utils.eventDrivenPatch()) {
+ // If event driven patch is enabled, auto save is not needed
+ return;
+ }
+
// Save every 3 seconds
const timer = this.__autoSaveTimer = new qx.event.Timer(this.self().AUTO_SAVE_INTERVAL);
timer.addListener("interval", () => {
@@ -829,6 +842,11 @@ qx.Class.define("osparc.desktop.StudyEditor", {
// ---------------- SAVING TIMER ------------------
__startSavingTimer: function() {
+ if (osparc.utils.Utils.eventDrivenPatch()) {
+ // If event driven patch is enabled, saving timer indicator is not needed
+ return;
+ }
+
const timer = this.__savingTimer = new qx.event.Timer(this.self().DIFF_CHECK_INTERVAL);
timer.addListener("interval", () => {
if (!osparc.wrapper.WebSocket.getInstance().isConnected()) {
@@ -872,7 +890,9 @@ qx.Class.define("osparc.desktop.StudyEditor", {
// didStudyChange takes around 0.5ms
didStudyChange: function() {
const studyDiffs = this.__getStudyDiffs();
- return Boolean(Object.keys(studyDiffs.delta).length);
+ const changed = Boolean(Object.keys(studyDiffs.delta).length);
+ this.getStudy().setSavePending(changed);
+ return changed;
},
__checkStudyChanges: function() {
@@ -886,6 +906,27 @@ qx.Class.define("osparc.desktop.StudyEditor", {
}
},
+ /**
+ * @param {JSON Patch} data It will soon be used to patch the project document https://datatracker.ietf.org/doc/html/rfc6902
+ */
+ projectDocumentChanged: function(data) {
+ data["userGroupId"] = osparc.auth.Data.getInstance().getGroupId();
+ if (osparc.utils.Utils.isDevelopmentPlatform()) {
+ console.log("projectDocumentChanged", data);
+ }
+
+ this.getStudy().setSavePending(true);
+ // throttling: do not update study document right after a change, wait for THROTTLE_PATCH_TIME
+ if (!this.__throttledPatchPending) {
+ this.__throttledPatchPending = true;
+
+ setTimeout(() => {
+ this.updateStudyDocument();
+ this.__throttledPatchPending = false;
+ }, this.self().THROTTLE_PATCH_TIME);
+ }
+ },
+
updateStudyDocument: function() {
if (!osparc.data.model.Study.canIWrite(this.getStudy().getAccessRights())) {
return new Promise(resolve => {
@@ -893,6 +934,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
});
}
+ this.getStudy().setSavePending(true);
this.__updatingStudy++;
const studyDiffs = this.__getStudyDiffs();
return this.getStudy().patchStudyDelayed(studyDiffs.delta, studyDiffs.sourceStudy)
@@ -908,6 +950,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
throw error;
})
.finally(() => {
+ this.getStudy().setSavePending(false);
this.__updatingStudy--;
if (this.__updateThrottled && this.__updatingStudy === 0) {
this.__updateThrottled = false;
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 aa01d10340c..0d9e6183018 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js
@@ -1042,7 +1042,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
vBox.add(header);
// INPUTS FORM
- if (node.isPropertyInitialized("propsForm") && node.getPropsForm()) {
+ if (node.hasPropsForm()) {
const inputsForm = node.getPropsForm();
const inputs = new osparc.desktop.PanelView(this.tr("Inputs"), inputsForm);
inputs._innerContainer.set({
diff --git a/services/static-webserver/client/source/class/osparc/editor/AnnotationEditor.js b/services/static-webserver/client/source/class/osparc/editor/AnnotationEditor.js
index 08eedabdd1e..c50fba4456e 100644
--- a/services/static-webserver/client/source/class/osparc/editor/AnnotationEditor.js
+++ b/services/static-webserver/client/source/class/osparc/editor/AnnotationEditor.js
@@ -166,6 +166,8 @@ qx.Class.define("osparc.editor.AnnotationEditor", {
const colorPicker = this.getChildControl("color-picker");
marker.bind("color", colorPicker, "value");
colorPicker.bind("value", marker, "color");
+
+ this.getChildControl("delete-btn").exclude();
},
addDeleteButton: function() {
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 6600cf4706a..c3a89731b08 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
@@ -41,7 +41,6 @@ qx.Class.define("osparc.form.renderer.PropForm", {
"fileRequested": "qx.event.type.Data",
"filePickerRequested": "qx.event.type.Data",
"parameterRequested": "qx.event.type.Data",
- "changeChildVisibility": "qx.event.type.Event"
},
properties: {
@@ -545,29 +544,6 @@ qx.Class.define("osparc.form.renderer.PropForm", {
});
},
- // overridden
- setAccessLevel: function(data) {
- const entry = this.self().GRID_POS;
- const disableables = osparc.form.renderer.PropFormBase.getDisableables();
- Object.entries(data).forEach(([portId, visibility]) => {
- Object.values(entry).forEach(entryPos => {
- const layoutElement = this._getLayoutChild(portId, entryPos);
- if (layoutElement && layoutElement.child) {
- const control = layoutElement.child;
- if (control) {
- const vis = visibility === this._visibility.hidden ? "excluded" : "visible";
- const enabled = visibility === this._visibility.readWrite;
- control.setVisibility(vis);
- if (disableables.includes(entryPos)) {
- control.setEnabled(enabled);
- }
- }
- }
- });
- });
- this.fireEvent("changeChildVisibility");
- },
-
setPortErrorMessage: function(portId, msg) {
const infoButton = this._getInfoFieldChild(portId);
if (infoButton && "child" in infoButton) {
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 fbc0a9a4077..dce44818824 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
@@ -48,6 +48,8 @@ qx.Class.define("osparc.form.renderer.PropFormBase", {
grid.setColumnFlex(this.self().GRID_POS.FIELD_LINK_UNLINK, 0);
grid.setColumnMinWidth(this.self().GRID_POS.CTRL_FIELD, 50);
Object.keys(this.self().GRID_POS).forEach((_, idx) => grid.setColumnAlign(idx, "left", "middle"));
+
+ form.addListener("changeData", e => this.fireDataEvent("changeData", e.getData()), this);
},
properties: {
@@ -57,6 +59,11 @@ qx.Class.define("osparc.form.renderer.PropFormBase", {
}
},
+ events: {
+ "changeData": "qx.event.type.Data",
+ "unitChanged": "qx.event.type.Data",
+ },
+
statics: {
GRID_POS: {
LABEL: 0,
@@ -68,13 +75,6 @@ qx.Class.define("osparc.form.renderer.PropFormBase", {
ROW_HEIGHT: 28,
- getDisableables: function() {
- return [
- this.GRID_POS.LABEL,
- this.GRID_POS.CTRL_FIELD
- ];
- },
-
updateUnitLabelPrefix: function(item) {
const {
unitShort,
@@ -319,13 +319,6 @@ qx.Class.define("osparc.form.renderer.PropFormBase", {
return false;
},
- /**
- * @abstract
- */
- setAccessLevel: function() {
- throw new Error("Abstract method called!");
- },
-
__createInfoWHint: function(hint) {
const infoWHint = new osparc.form.PortInfoHint(hint);
return infoWHint;
@@ -376,6 +369,10 @@ qx.Class.define("osparc.form.renderer.PropFormBase", {
}
item.setValue(newValue);
this.self().updateUnitLabelPrefix(item);
+ this.fireDataEvent("unitChanged", {
+ portId: item.key,
+ prefix: newPrefix,
+ });
},
_getLayoutChild: function(portId, column) {
diff --git a/services/static-webserver/client/source/class/osparc/form/renderer/PropFormEditor.js b/services/static-webserver/client/source/class/osparc/form/renderer/PropFormEditor.js
deleted file mode 100644
index 83cef4b5996..00000000000
--- a/services/static-webserver/client/source/class/osparc/form/renderer/PropFormEditor.js
+++ /dev/null
@@ -1,201 +0,0 @@
-/* ************************************************************************
-
- osparc - the simcore frontend
-
- https://osparc.io
-
- Copyright:
- 2020 IT'IS Foundation, https://itis.swiss
-
- License:
- MIT: https://opensource.org/licenses/MIT
-
- Authors:
- * Odei Maiz (odeimaiz)
-
-************************************************************************ */
-
-/**
- * An extension of the PropFormBase that is able to handle the Access Level of each entry.
- */
-
-qx.Class.define("osparc.form.renderer.PropFormEditor", {
- extend: osparc.form.renderer.PropFormBase,
-
- /**
- * @param form {osparc.form.Auto} form widget to embed
- * @param node {osparc.data.model.Node} Node owning the widget
- */
- construct: function(form, node) {
- this.base(arguments, form, node);
-
- this.__ctrlRadioButtonsMap = {};
- this.__addAccessLevelRBs();
- },
-
- statics: {
- GRID_POS: {
- ...osparc.form.renderer.PropFormBase.GRID_POS,
- ACCESS_LEVEL: Object.keys(osparc.form.renderer.PropFormBase.GRID_POS).length
- }
- },
-
- // eslint-disable-next-line qx-rules/no-refs-in-members
- members: {
- _accessLevel: {
- hidden: 0,
- readOnly: 1,
- readAndWrite: 2
- },
-
- __ctrlRadioButtonsMap: null,
-
- // overridden
- setAccessLevel: function(data) {
- for (const key in data) {
- const control = this.__getRadioButtonsFieldChild(key);
- if (control) {
- const group = this.__ctrlRadioButtonsMap[key];
- switch (data[key]) {
- case this._visibility.hidden: {
- group.setSelection([group.getSelectables()[0]]);
- break;
- }
- case this._visibility.readOnly: {
- group.setSelection([group.getSelectables()[1]]);
- break;
- }
- case this._visibility.readWrite: {
- group.setSelection([group.getSelectables()[2]]);
- break;
- }
- }
- }
- }
- },
-
- linkAdded: function(portId, controlLink) {
- let data = this._getCtrlFieldChild(portId);
- if (data) {
- let child = data.child;
- let idx = data.idx;
- const layoutProps = child.getLayoutProperties();
- controlLink.oldCtrl = child;
- this._removeAt(idx);
- this._addAt(controlLink, idx, {
- row: layoutProps.row,
- column: this.self().GRID_POS.CTRL_FIELD
- });
- }
- },
-
- linkRemoved: function(portId) {
- let data = this._getCtrlFieldChild(portId);
- if (data) {
- let child = data.child;
- let idx = data.idx;
- const layoutProps = child.getLayoutProperties();
- this._removeAt(idx);
- this._addAt(child.oldCtrl, idx, {
- row: layoutProps.row,
- column: this.self().GRID_POS.CTRL_FIELD
- });
- }
- },
-
- __addAccessLevelRBs: function() {
- Object.keys(this._form.getControls()).forEach(portId => {
- this.__addAccessLevelRB(portId);
- });
- },
-
- __addAccessLevelRB: function(portId) {
- const rbHidden = new qx.ui.form.RadioButton(this.tr("Not Visible"));
- rbHidden.accessLevel = this._visibility.hidden;
- rbHidden.portId = portId;
- const rbReadOnly = new qx.ui.form.RadioButton(this.tr("Read Only"));
- rbReadOnly.accessLevel = this._visibility.readOnly;
- rbReadOnly.portId = portId;
- const rbEditable = new qx.ui.form.RadioButton(this.tr("Editable"));
- rbEditable.accessLevel = this._visibility.readWrite;
- rbEditable.portId = portId;
-
- const groupBox = new qx.ui.container.Composite(new qx.ui.layout.HBox(10));
- groupBox.add(rbHidden);
- groupBox.add(rbReadOnly);
- groupBox.add(rbEditable);
-
- const group = new qx.ui.form.RadioGroup(rbHidden, rbReadOnly, rbEditable);
- group.setSelection([rbEditable]);
- this.__ctrlRadioButtonsMap[portId] = group;
- group.addListener("changeSelection", this.__onAccessLevelChanged, this);
-
- const ctrlField = this._getCtrlFieldChild(portId);
- if (ctrlField) {
- const idx = ctrlField.idx;
- const child = ctrlField.child;
- const layoutProps = child.getLayoutProperties();
- this._addAt(groupBox, idx, {
- row: layoutProps.row,
- column: this.self().GRID_POS.ACCESS_LEVEL
- });
- }
- },
-
- __onAccessLevelChanged: function(e) {
- const selectedButton = e.getData()[0];
- const {
- accessLevel,
- portId
- } = selectedButton;
-
- const data = {};
- data[portId] = accessLevel;
-
- this.__setAccessLevel(data);
-
- let inputAccess = this.getNode().getInputAccess();
- if (inputAccess === null) {
- inputAccess = {};
- }
- inputAccess[portId] = accessLevel;
- this.getNode().setInputAccess(inputAccess);
-
- const propsForm = this.getNode().getPropsForm();
- propsForm.setAccessLevel(data);
- },
-
- __addDelTag: function(label) {
- const newLabel = "" + label + "";
- return newLabel;
- },
-
- __removeDelTag: function(label) {
- let newLabel = label.replace("", "");
- newLabel = newLabel.replace("", "");
- return newLabel;
- },
-
- __setAccessLevel: function(data) {
- for (const key in data) {
- const label = this._getLabelFieldChild(key).child;
- const newLabel = data[key] === this._visibility.hidden ? this.__addDelTag(label.getValue()) : this.__removeDelTag(label.getValue());
- label.setValue(newLabel);
-
- const enabled = data[key] === this._visibility.readWrite;
- const disableables = osparc.form.renderer.PropFormBase.getDisableables();
- const ctrls = [];
- disableables.forEach(disableable => ctrls.push(this._getLayoutChild(key, disableable)));
- ctrls.forEach(ctrl => {
- if (ctrl) {
- ctrl.child.setEnabled(enabled);
- }
- });
- }
- },
-
- __getRadioButtonsFieldChild: function(portId) {
- return this._getLayoutChild(portId, this.self().GRID_POS.ACCESS_LEVEL);
- }
- }
-});
diff --git a/services/static-webserver/client/source/class/osparc/jobs/JobsButton.js b/services/static-webserver/client/source/class/osparc/jobs/JobsButton.js
index 3b2dc6fb4a4..7799830bade 100644
--- a/services/static-webserver/client/source/class/osparc/jobs/JobsButton.js
+++ b/services/static-webserver/client/source/class/osparc/jobs/JobsButton.js
@@ -101,13 +101,10 @@ qx.Class.define("osparc.jobs.JobsButton", {
__attachSocketListener: function() {
const socket = osparc.wrapper.WebSocket.getInstance();
- socket.on("projectStateUpdated", content => {
- // for now, we can only access the activity of my user, not the whole project...
- if (osparc.study.Utils.amIRunningTheStudy(content)) {
- // we know that I am running at least one study
+ socket.on("projectStateUpdated", data => {
+ if (osparc.study.Utils.state.isPipelineRunning(data["data"])) {
this.__updateJobsButton(true);
}
- // ...in the next iteration: listen to main store's "studyStateChanged", which will cover all users
}, this);
},
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 291c028422d..762f7134351 100644
--- a/services/static-webserver/client/source/class/osparc/node/BootOptionsView.js
+++ b/services/static-webserver/client/source/class/osparc/node/BootOptionsView.js
@@ -50,13 +50,23 @@ qx.Class.define("osparc.node.BootOptionsView", {
if (selection.length) {
buttonsLayout.setEnabled(false);
const newBootModeId = selection[0].bootModeId;
- node.setBootOptions({
+ const data = {
"boot_mode": newBootModeId
- });
+ };
+ node.setBootOptions(data);
node.fireEvent("updateStudyDocument");
+ node.fireDataEvent("projectDocumentChanged", {
+ "op": "replace",
+ "path": `/workbench/${nodeId}/bootOptions`,
+ "value": data,
+ "osparc-resource": "node",
+ });
+ // add timeout to make sure the node is saved before starting it
setTimeout(() => {
buttonsLayout.setEnabled(true);
- node.requestStartNode();
+ if (!node.getStudy().getDisableServiceAutoStart()) {
+ node.requestStartNode();
+ }
}, osparc.desktop.StudyEditor.AUTO_SAVE_INTERVAL);
}
}, this);
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 ea6ed0fbb28..23a390106f4 100644
--- a/services/static-webserver/client/source/class/osparc/node/LifeCycleView.js
+++ b/services/static-webserver/client/source/class/osparc/node/LifeCycleView.js
@@ -111,16 +111,32 @@ qx.Class.define("osparc.node.LifeCycleView", {
updateButton.addListener("execute", () => {
updateButton.setFetching(true);
const latestCompatible = osparc.store.Services.getLatestCompatible(node.getKey(), node.getVersion());
+ const newData = {};
if (node.getKey() !== latestCompatible["key"]) {
- node.setKey(latestCompatible["key"]);
+ newData["key"] = latestCompatible["key"];
}
if (node.getVersion() !== latestCompatible["version"]) {
- node.setVersion(latestCompatible["version"]);
+ newData["version"] = latestCompatible["version"];
}
+ node.set(newData);
node.fireEvent("updateStudyDocument");
+ node.fireDataEvent("projectDocumentChanged", [{
+ "op": "replace",
+ "path": `/workbench/${nodeId}/key`,
+ "value": latestCompatible["key"],
+ "osparc-resource": "node",
+ }, {
+ "op": "replace",
+ "path": `/workbench/${nodeId}/version`,
+ "value": latestCompatible["version"],
+ "osparc-resource": "node",
+ }]);
+ // add timeout to make sure the node is saved before starting it
setTimeout(() => {
updateButton.setFetching(false);
- node.requestStartNode();
+ if (!node.getStudy().getDisableServiceAutoStart()) {
+ node.requestStartNode();
+ }
}, osparc.desktop.StudyEditor.AUTO_SAVE_INTERVAL);
});
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 cee5d1fb130..a0f26f40645 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
@@ -57,7 +57,6 @@ qx.Class.define("osparc.node.slideshow.NodeView", {
const node = this.getNode();
const propsForm = node.getPropsForm();
if (propsForm && node.hasInputs()) {
- propsForm.addListener("changeChildVisibility", () => this.__checkSettingsVisibility(), this);
this._settingsLayout.add(propsForm);
}
this.__checkSettingsVisibility();
diff --git a/services/static-webserver/client/source/class/osparc/snapshots/IterationsView.js b/services/static-webserver/client/source/class/osparc/snapshots/IterationsView.js
index 5f3a3866bad..0d643fa6a22 100644
--- a/services/static-webserver/client/source/class/osparc/snapshots/IterationsView.js
+++ b/services/static-webserver/client/source/class/osparc/snapshots/IterationsView.js
@@ -133,7 +133,7 @@ qx.Class.define("osparc.snapshots.IterationsView", {
const iteration = new osparc.data.model.Study(iterationData);
iteration.setReadOnly(true);
iteration.nodeUpdated(dataUpdate);
- const iterationDataUpdated = iteration.serialize(false);
+ const iterationDataUpdated = iteration.serialize();
this.__iterations.splice(idx, 1, iterationDataUpdated);
// update maximum once every 2"
@@ -180,7 +180,7 @@ qx.Class.define("osparc.snapshots.IterationsView", {
this.__iterationsSection.remove(this.__iterationsTable);
}
- const iterationsTable = this.__iterationsTable = new osparc.snapshots.Iterations(this.__study.serialize(false));
+ const iterationsTable = this.__iterationsTable = new osparc.snapshots.Iterations(this.__study.serialize());
iterationsTable.populateTable(this.__iterations);
iterationsTable.addListener("cellTap", e => {
const selectedRow = e.getRow();
diff --git a/services/static-webserver/client/source/class/osparc/study/Utils.js b/services/static-webserver/client/source/class/osparc/study/Utils.js
index c32211ba728..800becc4380 100644
--- a/services/static-webserver/client/source/class/osparc/study/Utils.js
+++ b/services/static-webserver/client/source/class/osparc/study/Utils.js
@@ -419,23 +419,24 @@ qx.Class.define("osparc.study.Utils", {
}
return undefined;
},
- },
- // used in the "projectStateUpdated" socket event
- amIRunningTheStudy: function(content) {
- if (content && "data" in content) {
- const state = content["data"];
- const currentGroupIds = this.state.getCurrentGroupIds(state);
- if (currentGroupIds.includes(osparc.auth.Data.getInstance().getGroupId())) {
- const pipelineState = this.state.getPipelineState(state);
- return [
- "PUBLISHED",
- "STARTED",
- "STOPPING",
- ].includes(pipelineState);
+ PIPELINE_RUNNING_STATES: [
+ "PUBLISHED",
+ "PENDING",
+ "WAITING_FOR_RESOURCES",
+ "WAITING_FOR_CLUSTER",
+ "STARTED",
+ "STOPPING",
+ "RETRY",
+ ],
+
+ isPipelineRunning: function(state) {
+ const pipelineState = this.getPipelineState(state);
+ if (pipelineState) {
+ return this.PIPELINE_RUNNING_STATES.includes(pipelineState);
}
- }
- return false;
+ return false;
+ },
},
__getBlockedState: function(studyData) {
@@ -533,7 +534,7 @@ qx.Class.define("osparc.study.Utils", {
resolve(osparc.data.model.StudyUI.PIPELINE_ICON);
} else {
const productIcon = osparc.dashboard.CardBase.PRODUCT_ICON;
- const wbServices = this.self().getNonFrontendNodes(studyData);
+ const wbServices = this.getNonFrontendNodes(studyData);
if (wbServices.length === 1) {
const wbService = wbServices[0];
osparc.store.Services.getService(wbService.key, wbService.version)
diff --git a/services/static-webserver/client/source/class/osparc/utils/Utils.js b/services/static-webserver/client/source/class/osparc/utils/Utils.js
index 846c1d6e98a..03898c10a63 100644
--- a/services/static-webserver/client/source/class/osparc/utils/Utils.js
+++ b/services/static-webserver/client/source/class/osparc/utils/Utils.js
@@ -496,6 +496,10 @@ qx.Class.define("osparc.utils.Utils", {
return (["dev", "master"].includes(platformName));
},
+ eventDrivenPatch: function() {
+ return osparc.utils.DisabledPlugins.isRTCEnabled();
+ },
+
getEditButton: function(isVisible = true) {
return new qx.ui.form.Button(null, "@FontAwesome5Solid/pencil-alt/12").set({
appearance: "form-button-outlined",
diff --git a/services/static-webserver/client/source/class/osparc/workbench/Annotation.js b/services/static-webserver/client/source/class/osparc/workbench/Annotation.js
index 746e5d176dd..4b98e89c6c6 100644
--- a/services/static-webserver/client/source/class/osparc/workbench/Annotation.js
+++ b/services/static-webserver/client/source/class/osparc/workbench/Annotation.js
@@ -77,7 +77,7 @@ qx.Class.define("osparc.workbench.Annotation", {
attributes: {
check: "Object",
- nullable: false,
+ nullable: false
},
svgCanvas: {
@@ -95,7 +95,8 @@ qx.Class.define("osparc.workbench.Annotation", {
"annotationClicked": "qx.event.type.Data",
"annotationStartedMoving": "qx.event.type.Event",
"annotationMoving": "qx.event.type.Event",
- "annotationStoppedMoving": "qx.event.type.Event"
+ "annotationStoppedMoving": "qx.event.type.Event",
+ "annotationChanged": "qx.event.type.Event",
},
members: {
@@ -174,6 +175,7 @@ qx.Class.define("osparc.workbench.Annotation", {
osparc.wrapper.Svg.updateTextColor(representation, color);
break;
}
+ this.fireEvent("annotationChanged");
}
},
@@ -223,6 +225,7 @@ qx.Class.define("osparc.workbench.Annotation", {
this.getAttributes().x = x;
this.getAttributes().y = y;
}
+ this.fireEvent("annotationChanged");
},
setText: function(newText) {
@@ -230,6 +233,7 @@ qx.Class.define("osparc.workbench.Annotation", {
const representation = this.getRepresentation();
if (representation) {
osparc.wrapper.Svg.updateText(representation, newText);
+ this.fireEvent("annotationChanged");
}
},
@@ -238,6 +242,7 @@ qx.Class.define("osparc.workbench.Annotation", {
const representation = this.getRepresentation();
if (representation) {
osparc.wrapper.Svg.updateTextSize(representation, fontSize);
+ this.fireEvent("annotationChanged");
}
},
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 b0f2bc4bec0..476a3583322 100644
--- a/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js
+++ b/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js
@@ -709,10 +709,14 @@ qx.Class.define("osparc.workbench.NodeUI", {
};
},
+ moveNodeTo: function(pos) {
+ this.moveTo(pos.x, pos.y);
+ },
+
setPosition: function(pos) {
const node = this.getNode();
node.setPosition(pos);
- this.moveTo(node.getPosition().x, node.getPosition().y);
+ this.moveNodeTo(pos);
},
snapToGrid: function() {
@@ -724,11 +728,10 @@ qx.Class.define("osparc.workbench.NodeUI", {
const snapGrid = 20;
const snapX = Math.round(x/snapGrid)*snapGrid;
const snapY = Math.round(y/snapGrid)*snapGrid;
- node.setPosition({
+ this.setPosition({
x: snapX,
y: snapY
});
- this.moveTo(node.getPosition().x, node.getPosition().y);
},
__applyThumbnail: function(thumbnailSrc) {
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 4114221d7c0..df48c91120e 100644
--- a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js
+++ b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js
@@ -443,7 +443,8 @@ qx.Class.define("osparc.workbench.WorkbenchUI", {
__itemMoving: function(itemId, xDiff, yDiff) {
this.getSelectedNodeUIs().forEach(selectedNodeUI => {
if (itemId !== selectedNodeUI.getNodeId()) {
- selectedNodeUI.setPosition({
+ // do not touch the position, just move the node, this will happen in __itemStoppedMoving
+ selectedNodeUI.moveNodeTo({
x: selectedNodeUI.initPos.x + xDiff,
y: selectedNodeUI.initPos.y + yDiff
});
@@ -464,10 +465,24 @@ qx.Class.define("osparc.workbench.WorkbenchUI", {
this.getSelectedNodeUIs().forEach(selectedNodeUI => delete selectedNodeUI["initPos"]);
this.getSelectedAnnotations().forEach(selectedAnnotation => delete selectedAnnotation["initPos"]);
- if (nodeUI && osparc.Preferences.getInstance().isSnapNodeToGrid()) {
- this.getSelectedNodeUIs().forEach(selectedNodeUI => selectedNodeUI.snapToGrid());
- // make sure nodeUI is moved, then update edges
- setTimeout(() => this.__updateNodeUIPos(nodeUI), 10);
+ // the moving item could be an annotation, so we need to check if it is a nodeUI
+ if (nodeUI) {
+ this.getSelectedNodeUIs().forEach(selectedNodeUI => {
+ if (nodeUI !== selectedNodeUI) {
+ // now set the position
+ const layoutProps = selectedNodeUI.getLayoutProperties();
+ selectedNodeUI.setPosition({
+ x: layoutProps.left,
+ y: layoutProps.top,
+ });
+ }
+ });
+
+ if (osparc.Preferences.getInstance().isSnapNodeToGrid()) {
+ this.getSelectedNodeUIs().forEach(selectedNodeUI => selectedNodeUI.snapToGrid());
+ // make sure nodeUI is moved, then update edges
+ setTimeout(() => this.__updateNodeUIPos(nodeUI), 10);
+ }
}
this.__updateWorkbenchBounds();