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 258257c98aa..fa74778bf67 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 @@ -198,6 +198,9 @@ qx.Class.define("osparc.data.model.Node", { "reloadModel": "qx.event.type.Event", "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", "fileRequested": "qx.event.type.Data", "parameterRequested": "qx.event.type.Data", "filePickerRequested": "qx.event.type.Data", @@ -237,6 +240,10 @@ qx.Class.define("osparc.data.model.Node", { "progress", // !! not a property but goes into the model ], + getProperties: function() { + return Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.Node)); + }, + isFrontend: function(metadata) { return (metadata && metadata.key && metadata.key.includes("/frontend/")); }, @@ -524,7 +531,7 @@ qx.Class.define("osparc.data.model.Node", { this.setPosition(nodeUIData.position); } if ("marker" in nodeUIData) { - this.__addMarker(nodeUIData.marker); + this.addMarker(nodeUIData.marker); } }, @@ -682,11 +689,11 @@ qx.Class.define("osparc.data.model.Node", { if (this.getMarker()) { this.__removeMarker(); } else { - this.__addMarker(); + this.addMarker(); } }, - __addMarker: function(marker) { + addMarker: function(marker) { if (marker === undefined) { marker = { color: osparc.utils.Utils.getRandomColor() @@ -922,13 +929,14 @@ qx.Class.define("osparc.data.model.Node", { removeInputNode: function(inputNodeId) { const index = this.__inputNodes.indexOf(inputNodeId); - if (index > -1) { - // remove node connection - this.__inputNodes.splice(index, 1); - this.fireEvent("changeInputNodes"); - return true; + // make sure index is valid + if (index < 0 || index >= this.__inputNodes.length) { + return false; } - return false; + // remove node connection + this.__inputNodes.splice(index, 1); + this.fireEvent("changeInputNodes"); + return true; }, isInputNode: function(inputNodeId) { @@ -1198,7 +1206,7 @@ qx.Class.define("osparc.data.model.Node", { this.__deleteInBackend() .then(() => { resolve(true); - this.removeIFrame(); + this.nodeRemoved(); }) .catch(err => { console.error(err); @@ -1207,6 +1215,10 @@ qx.Class.define("osparc.data.model.Node", { }); }, + nodeRemoved: function() { + this.removeIFrame(); + }, + __deleteInBackend: function() { // remove node in the backend const params = { @@ -1250,6 +1262,11 @@ qx.Class.define("osparc.data.model.Node", { }, "osparc-resource": "ui", }); + + this.fireDataEvent("changePosition", { + x: this.__posX, + y: this.__posY + }); }, getPosition: function() { @@ -1300,7 +1317,7 @@ qx.Class.define("osparc.data.model.Node", { listenToChanges: function() { const nodeId = this.getNodeId(); - const propertyKeys = Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.Node)); + const nodePropertyKeys = this.self().getProperties(); this.self().ListenChangesProps.forEach(key => { switch (key) { case "inputs": @@ -1394,7 +1411,7 @@ qx.Class.define("osparc.data.model.Node", { } break; default: - if (propertyKeys.includes(key)) { + if (nodePropertyKeys.includes(key)) { this.addListener("change" + qx.lang.String.firstUp(key), e => { const data = e.getData(); this.fireDataEvent("projectDocumentChanged", { @@ -1412,6 +1429,79 @@ qx.Class.define("osparc.data.model.Node", { }); }, + updateNodeFromPatch: function(nodePatches) { + const nodePropertyKeys = this.self().getProperties(); + nodePatches.forEach(patch => { + const op = patch.op; + const path = patch.path; + const value = patch.value; + const nodeProperty = path.split("/")[3]; + switch (nodeProperty) { + case "inputs": { + const updatedPortKey = path.split("/")[4]; + const currentInputs = this.__getInputData(); + currentInputs[updatedPortKey] = value; + this.__setInputData(currentInputs); + break; + } + case "inputsUnits": { + // this is never transmitted by the frontend + const updatedPortKey = path.split("/")[4]; + const currentInputUnits = this.__getInputUnits(); + currentInputUnits[updatedPortKey] = value; + this.__setInputUnits(currentInputUnits); + break; + } + case "inputNodes": + if (op === "add") { + const inputNodeId = value; + this.fireDataEvent("createEdge", { + nodeId1: inputNodeId, + nodeId2: this.getNodeId(), + }); + } else if (op === "remove") { + // we don't have more information about the input node, so we just remove it by index + const index = path.split("/")[4]; + // make sure index is valid + if (index >= 0 && index < this.__inputNodes.length) { + this.fireDataEvent("removeEdge", { + nodeId1: this.__inputNodes[index], + nodeId2: this.getNodeId(), + }); + } + } + break; + case "inputsRequired": + console.warn(`To be implemented: patching ${nodeProperty} is not supported yet`); + break; + case "outputs": { + const updatedPortKey = path.split("/")[4]; + const currentOutputs = this.isFilePicker() ? osparc.file.FilePicker.serializeOutput(this.getOutputs()) : this.__getOutputsData(); + currentOutputs[updatedPortKey] = value; + this.setOutputData(currentOutputs); + break; + } + case "progress": + if (this.isFilePicker()) { + this.getStatus().setProgress(value); + } else { + console.warn(`To be implemented: patching ${nodeProperty} is not supported yet`); + } + break; + default: + if (nodePropertyKeys.includes(nodeProperty)) { + const setter = "set" + qx.lang.String.firstUp(nodeProperty); + if (this[setter]) { + this[setter](value); + } else { + console.warn(`Property "${nodeProperty}" does not have a setter in osparc.data.model.Node`); + } + } + break; + } + }); + }, + serialize: function() { // node generic let nodeEntry = { @@ -1419,7 +1509,7 @@ qx.Class.define("osparc.data.model.Node", { version: this.getVersion(), label: this.getLabel(), inputs: this.__getInputData(), - inputsUnits: this.__getInputUnits(), + inputsUnits: this.__getInputUnits(), // this is not working inputNodes: this.getInputNodes(), inputsRequired: this.getInputsRequired(), bootOptions: this.getBootOptions() 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 fec6e34e4bc..8f9e82b9851 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 @@ -44,8 +44,8 @@ qx.Class.define("osparc.data.model.Study", { this.set({ uuid: studyData.uuid || this.getUuid(), - workspaceId: studyData.workspaceId || null, - folderId: studyData.folderId || null, + workspaceId: studyData.workspaceId || this.getWorkspaceId(), + folderId: studyData.folderId || this.getFolderId(), name: studyData.name || this.getName(), description: studyData.description || this.getDescription(), thumbnail: studyData.thumbnail || this.getThumbnail(), @@ -60,9 +60,9 @@ qx.Class.define("osparc.data.model.Study", { permalink: studyData.permalink || this.getPermalink(), dev: studyData.dev || this.getDev(), trashedAt: studyData.trashedAt ? new Date(studyData.trashedAt) : this.getTrashedAt(), - trashedBy: studyData.trashedBy || null, - type: studyData.type, - templateType: studyData.templateType, + trashedBy: studyData.trashedBy || this.getTrashedBy(), + type: studyData.type || this.getType(), + templateType: studyData.templateType || this.getTemplateType(), }); const wbData = studyData.workbench || this.getWorkbench(); @@ -143,7 +143,7 @@ qx.Class.define("osparc.data.model.Study", { thumbnail: { check: "String", - nullable: false, + nullable: true, event: "changeThumbnail", init: "" }, @@ -747,23 +747,23 @@ qx.Class.define("osparc.data.model.Study", { * @param studyDiffs {Object} Diff Object coming from the JsonDiffPatch lib. Use only the keys, not the changes. * @param studySource {Object} Study object that was used to check the diffs on the frontend. */ - patchStudyDelayed: function(studyDiffs, studySource) { + patchStudyDiffs: function(studyDiffs, studySource) { const promises = []; if ("workbench" in studyDiffs) { - promises.push(this.getWorkbench().patchWorkbenchDelayed(studyDiffs["workbench"], studySource["workbench"])); + promises.push(this.getWorkbench().patchWorkbenchDiffs(studyDiffs["workbench"], studySource["workbench"])); delete studyDiffs["workbench"]; } - const fieldKeys = Object.keys(studyDiffs); - if (fieldKeys.length) { - fieldKeys.forEach(fieldKey => { + const changedFields = Object.keys(studyDiffs); + if (changedFields.length) { + changedFields.forEach(changedField => { // OM: can this be called all together? const patchData = {}; - if (fieldKey === "ui") { - patchData[fieldKey] = this.getUi().serialize(); + if (changedField === "ui") { + patchData[changedField] = this.getUi().serialize(); } else { - const upKey = qx.lang.String.firstUp(fieldKey); + const upKey = qx.lang.String.firstUp(changedField); const getter = "get" + upKey; - patchData[fieldKey] = this[getter](studyDiffs[fieldKey]); + patchData[changedField] = this[getter](studyDiffs[changedField]); } promises.push(osparc.store.Study.getInstance().patchStudy(this.getUuid(), patchData)) }); @@ -772,6 +772,53 @@ qx.Class.define("osparc.data.model.Study", { .then(() => { return studySource; }); - } + }, + + // unused in favor of updateStudyFromPatches + updateStudyFromDiff: function(studyDiffs) { + const studyPropertyKeys = this.self().getProperties(); + studyPropertyKeys.forEach(studyPropertyKey => { + if (studyPropertyKey in studyDiffs) { + const newValue = studyDiffs[studyPropertyKey][1]; + if ("lastChangeDate" === studyPropertyKey) { + this.setLastChangeDate(new Date(newValue)); + } else { + const upKey = qx.lang.String.firstUp(studyPropertyKey); + const setter = "set" + upKey; + this[setter](newValue); + } + delete studyDiffs[studyPropertyKey]; + } + }); + }, + + // json PATCHES + updateStudyFromPatches: function(studyPatches) { + const studyPropertyKeys = this.self().getProperties(); + studyPatches.forEach(patch => { + const op = patch.op; + const path = patch.path; + const value = patch.value; + switch (op) { + case "replace": + const studyProperty = path.substring(1); // remove the leading "/" + if (studyPropertyKeys.includes(studyProperty)) { + if (path === "/lastChangeDate") { + this.setLastChangeDate(new Date(value)); + } else { + const setter = "set" + qx.lang.String.firstUp(studyProperty); + if (this[setter]) { + this[setter](value); + } else { + console.warn(`Property "${studyProperty}" does not have a setter in osparc.data.model.Study`); + } + } + } + break; + default: + console.warn(`Unhandled patch operation "${op}" for path "${path}" with value "${value}"`); + } + }); + }, } }); 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 038c88ed8af..c3a13bdd0fb 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 @@ -36,10 +36,9 @@ qx.Class.define("osparc.data.model.StudyUI", { annotations: {}, }); - if ("annotations" in studyDataUI) { + if (studyDataUI["annotations"]) { Object.entries(studyDataUI["annotations"]).forEach(([annotationId, annotationData]) => { - const annotation = new osparc.workbench.Annotation(annotationData, annotationId); - this.addAnnotation(annotation); + this.addAnnotation(annotationData, annotationId); }); } @@ -96,6 +95,8 @@ qx.Class.define("osparc.data.model.StudyUI", { events: { "projectDocumentChanged": "qx.event.type.Data", + "annotationAdded": "qx.event.type.Data", + "annotationRemoved": "qx.event.type.Data", }, statics: { @@ -121,7 +122,8 @@ qx.Class.define("osparc.data.model.StudyUI", { } }, - addAnnotation: function(annotation) { + addAnnotation: function(annotationData, annotationId) { + const annotation = new osparc.workbench.Annotation(annotationData, annotationId); this.getAnnotations()[annotation.getId()] = annotation; this.fireDataEvent("projectDocumentChanged", { "op": "add", @@ -137,6 +139,7 @@ qx.Class.define("osparc.data.model.StudyUI", { "osparc-resource": "study-ui", }); }, this); + return annotation; }, removeAnnotation: function(annotationId) { @@ -156,6 +159,265 @@ qx.Class.define("osparc.data.model.StudyUI", { this.getSlideshow().removeNode(nodeId); }, + // unused in favor of updateUiFromPatches + updateUiFromDiff: function(uiDiff) { + if (uiDiff["workbench"]) { + const currentStudy = osparc.store.Store.getInstance().getCurrentStudy(); + if (currentStudy) { + Object.keys(uiDiff["workbench"]).forEach(nodeId => { + const node = currentStudy.getWorkbench().getNode(nodeId); + if ("position" in uiDiff["workbench"][nodeId]) { + const positionDiff = uiDiff["workbench"][nodeId]["position"]; + this.__updateNodePositionFromDiff(node, positionDiff); + } + if ("marker" in uiDiff["workbench"][nodeId]) { + const markerDiff = uiDiff["workbench"][nodeId]["marker"]; + this.__updateNodeMarkerFromDiff(node, markerDiff); + } + }); + } + } + if (uiDiff["annotations"]) { + const annotationsDiff = uiDiff["annotations"]; + this.__updateAnnotationsFromDiff(annotationsDiff); + } + }, + + __updateNodePositionFromDiff: function(node, positionDiff) { + if (node) { + const newPos = node.getPosition(); + if ("x" in positionDiff) { + newPos.x = positionDiff["x"][1]; + } + if ("y" in positionDiff) { + newPos.y = positionDiff["y"][1]; + } + node.setPosition(newPos); + } + }, + + __updateNodeMarkerFromDiff: function(node, markerDiff) { + if (node) { + if (markerDiff instanceof Array) { + if (markerDiff.length === 2 && markerDiff[1] === null) { + // it was removed + node.setMarker(null); + } else if (markerDiff.length === 1) { + // it was added + node.addMarker(markerDiff[0]); + } + } else if ("color" in markerDiff && markerDiff["color"] instanceof Array) { + // it was updated + const newColor = markerDiff["color"][1]; + node.getMarker().setColor(newColor); + } + } + }, + + __updateAnnotationAttributesFromDiff: function(annotation, attributesDiff) { + if (annotation) { + const newPos = annotation.getPosition(); + if ("x" in attributesDiff) { + newPos.x = attributesDiff["x"][1]; + } + if ("y" in attributesDiff) { + newPos.y = attributesDiff["y"][1]; + } + annotation.setPosition(newPos.x, newPos.y); + + if ("fontSize" in attributesDiff) { + annotation.setFontSize(attributesDiff["fontSize"][1]); + } + if ("text" in attributesDiff) { + annotation.setText(attributesDiff["text"][1]); + } + } + }, + + __updateAnnotationsFromDiff: function(annotationsDiff) { + // check if annotation data is an object or an array + const annotations = this.getAnnotations(); + if (annotationsDiff instanceof Array) { + // from or to empty annotations + if (annotationsDiff.length === 2) { + if (annotationsDiff[0] === null) { + // first annotation(s) was added + const annotationsData = annotationsDiff[1]; + Object.entries(annotationsData).forEach(([annotationId, annotationData]) => { + const annotation = this.addAnnotation(annotationData, annotationId); + this.fireDataEvent("annotationAdded", annotation); + }); + } else if (annotationsDiff[1] === null) { + // all annotations were removed + const removedAnnotationsData = annotationsDiff[0]; + Object.keys(removedAnnotationsData).forEach(annotationId => { + this.removeAnnotation(annotationId); + this.fireDataEvent("annotationRemoved", annotationId); + }); + } + } + } else if (annotationsDiff instanceof Object) { + Object.entries(annotationsDiff).forEach(([annotationId, annotationDiff]) => { + if (annotationDiff instanceof Array) { + if (annotationDiff.length === 1) { + // it was added + const annotation = this.addAnnotation(annotationDiff[0], annotationId); + this.fireDataEvent("annotationAdded", annotation); + } else if (annotationDiff.length === 3 && annotationDiff[1] === 0) { + // it was removed + this.removeAnnotation(annotationId); + this.fireDataEvent("annotationRemoved", annotationId); + } + } else if (annotationDiff instanceof Object) { + // it was updated + if (annotationId in annotations) { + const annotation = annotations[annotationId]; + if ("attributes" in annotationDiff) { + this.__updateAnnotationAttributesFromDiff(annotation, annotationDiff["attributes"]); + } + if ("color" in annotationDiff) { + annotation.setColor(annotationDiff["color"][1]); + } + } else { + console.warn(`Annotation with id ${annotationId} not found`); + } + } + }); + } + }, + + updateUiFromPatches: function(uiPatches) { + uiPatches.forEach(patch => { + const path = patch.path; + if (path.startsWith("/ui/workbench/")) { + const nodeId = path.split("/")[3]; + const currentStudy = osparc.store.Store.getInstance().getCurrentStudy(); + if (currentStudy) { + const node = currentStudy.getWorkbench().getNode(nodeId); + if (path.includes("/position")) { + this.__updateNodePositionFromPatch(node, patch); + } + if (path.includes("/marker")) { + this.__updateNodeMarkerFromPatch(node, patch); + } + } + } else if (path.startsWith("/ui/annotations")) { + this.__updateAnnotationFromPatch(patch); + } + }); + }, + + __updateNodePositionFromPatch: function(node, patch) { + if (node) { + const op = patch.op; + const path = patch.path; + const value = patch.value; + if (op === "replace") { + const newPos = node.getPosition(); + if (path.includes("/position/x")) { + newPos.x = value; + } + if (path.includes("/position/y")) { + newPos.y = value; + } + node.setPosition(newPos); + } + } + }, + + __updateNodeMarkerFromPatch: function(node, patch) { + if (node) { + const op = patch.op; + const path = patch.path; + const value = patch.value; + if (op === "delete" || value === null) { + // it was removed + node.setMarker(null); + } else if (op === "add") { + // it was added + node.addMarker(value); + } else if (op === "replace" && path.includes("/color")) { + // it was updated + if (node.getMarker()) { + node.getMarker().setColor(value); + } + } + } + }, + + __updateAnnotationFromPatch: function(patch) { + const op = patch.op; + const path = patch.path; + const value = patch.value; + let annotationId = path.split("/")[3]; + switch (op) { + case "add": { + const annotation = this.addAnnotation(value, annotationId); + this.fireDataEvent("annotationAdded", annotation); + break; + } + case "remove": + this.removeAnnotation(annotationId); + this.fireDataEvent("annotationRemoved", annotationId); + break; + case "replace": + if (annotationId && annotationId in this.getAnnotations()) { + const annotation = this.getAnnotations()[annotationId]; + if (annotation) { + if (path.includes("/color")) { + annotation.setColor(value); + } else if (path.includes("/attributes")) { + this.__updateAnnotationAttributesFromPatch(annotation, path, value); + } + } + } else { + // the first (add) or last (remove) annotation will fall here + if (value && Object.keys(value).length) { + // first added + annotationId = Object.keys(value)[0]; + const annotationData = Object.values(value)[0]; + const annotation = this.addAnnotation(annotationData, annotationId); + this.fireDataEvent("annotationAdded", annotation); + } else { + // last removed + const currentIds = Object.keys(this.getAnnotations()); + if (currentIds.length === 1) { + annotationId = currentIds[0]; + this.removeAnnotation(annotationId); + this.fireDataEvent("annotationRemoved", annotationId); + } + } + } + break; + } + }, + + __updateAnnotationAttributesFromPatch: function(annotation, path, value) { + if (annotation) { + const attribute = path.split("/")[5]; + switch (attribute) { + case "x": { + const newPos = annotation.getPosition(); + newPos.x = value; + annotation.setPosition(newPos.x, newPos.y); + break; + } + case "y": { + const newPos = annotation.getPosition(); + newPos.y = value; + annotation.setPosition(newPos.x, newPos.y); + break; + } + case "fontSize": + annotation.setFontSize(value); + break; + case "text": + annotation.setText(value); + break; + } + } + }, + listenToChanges: function() { const propertyKeys = Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.StudyUI)); this.self().ListenChangesProps.forEach(key => { @@ -186,6 +448,7 @@ qx.Class.define("osparc.data.model.StudyUI", { jsonObject["slideshow"] = this.getSlideshow().serialize(); jsonObject["currentNodeId"] = this.getCurrentNodeId() || ""; jsonObject["mode"] = this.getMode(); + jsonObject["annotations"] = null; const annotations = this.getAnnotations(); if (Object.keys(annotations).length) { jsonObject["annotations"] = {}; 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 ce7b1a9e841..eed47e42c1c 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", + "nodeRemoved": "qx.event.type.Data", "reloadModel": "qx.event.type.Event", "retrieveInputs": "qx.event.type.Data", "fileRequested": "qx.event.type.Data", @@ -283,6 +284,10 @@ qx.Class.define("osparc.data.model.Workbench", { 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; }, @@ -316,9 +321,6 @@ qx.Class.define("osparc.data.model.Workbench", { this.fireEvent("restartAutoSaveTimer"); const node = this.__createNode(this.getStudy(), metadata, nodeId); - this.__initNodeSignals(node); - this.__addNode(node); - node.populateNodeData(); this.__giveUniqueNameToNode(node, node.getLabel()); node.checkState(); @@ -539,11 +541,11 @@ qx.Class.define("osparc.data.model.Workbench", { removeNode: async function(nodeId) { if (!osparc.data.Permissions.getInstance().canDo("study.node.delete", true)) { - return false; + return; } if (this.getStudy().isPipelineRunning()) { osparc.FlashMessenger.logAs(this.self().CANT_DELETE_NODE, "ERROR"); - return false; + return; } this.fireEvent("restartAutoSaveTimer"); @@ -552,26 +554,33 @@ qx.Class.define("osparc.data.model.Workbench", { // remove the node in the backend first const removed = await node.removeNode(); if (removed) { - this.fireEvent("restartAutoSaveTimer"); + this.__nodeRemoved(nodeId); + } + } + }, - delete this.__nodes[nodeId]; + __nodeRemoved: function(nodeId) { + this.fireEvent("restartAutoSaveTimer"); - // remove first the connected edges - const connectedEdges = this.getConnectedEdges(nodeId); - connectedEdges.forEach(connectedEdgeId => { - this.removeEdge(connectedEdgeId); - }); + delete this.__nodes[nodeId]; - // remove it from ui model - if (this.getStudy()) { - this.getStudy().getUi().removeNode(nodeId); - } + // remove first the connected edges + const connectedEdgeIds = this.getConnectedEdges(nodeId); + connectedEdgeIds.forEach(connectedEdgeId => { + this.removeEdge(connectedEdgeId); + }); - this.fireEvent("pipelineChanged"); - return true; - } + // remove it from ui model + if (this.getStudy()) { + this.getStudy().getUi().removeNode(nodeId); } - return false; + + this.fireEvent("pipelineChanged"); + + this.fireDataEvent("nodeRemoved", { + nodeId, + connectedEdgeIds, + }); }, addServiceBetween: async function(service, leftNodeId, rightNodeId) { @@ -710,9 +719,7 @@ qx.Class.define("osparc.data.model.Workbench", { for (let i=0; i { const node = this.getNode(nodeId); @@ -827,6 +834,104 @@ qx.Class.define("osparc.data.model.Workbench", { } }) return Promise.all(promises); - } + }, + + updateWorkbenchFromPatches: function(workbenchPatches) { + // group the patches by nodeId + const nodesAdded = []; + const nodesRemoved = []; + const workbenchPatchesByNode = {}; + workbenchPatches.forEach(workbenchPatch => { + const nodeId = workbenchPatch.path.split("/")[2]; + + const pathParts = workbenchPatch.path.split("/"); + if (pathParts.length === 3) { + if (workbenchPatch.op === "add") { + // node was added + nodesAdded.push(nodeId); + } else if (workbenchPatch.op === "remove") { + // node was removed + nodesRemoved.push(nodeId); + } + } + + if (!(nodeId in workbenchPatchesByNode)) { + workbenchPatchesByNode[nodeId] = []; + } + workbenchPatchesByNode[nodeId].push(workbenchPatch); + }); + + // first, remove nodes + 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); + } else { + // third, update nodes + this.__updateNodesFromPatches(workbenchPatchesByNode); + } + }, + + __removeNodesFromPatches: function(nodesRemoved, workbenchPatchesByNode) { + nodesRemoved.forEach(nodeId => { + const node = this.getNode(nodeId); + + // if the user is in that node, restore the node to the workbench + if (this.getStudy().getUi().getCurrentNodeId() === nodeId) { + this.getStudy().getUi().setMode("pipeline"); + this.getStudy().getUi().setCurrentNodeId(null); + } + if (node) { + node.nodeRemoved(nodeId); + } + this.__nodeRemoved(nodeId); + delete workbenchPatchesByNode[nodeId]; + }); + }, + + __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 => { + const addNodePatch = workbenchPatchesByNode[nodeId].find(workbenchPatch => { + const pathParts = workbenchPatch.path.split("/"); + return pathParts.length === 3 && workbenchPatch.op === "add"; + }); + const nodeData = addNodePatch.value; + // delete the node "add" from the workbenchPatchesByNode + const index = workbenchPatchesByNode[nodeId].indexOf(addNodePatch); + if (index > -1) { + workbenchPatchesByNode[nodeId].splice(index, 1); + } + // this is an async operation with an await + return this.createNode(nodeData["key"], nodeData["version"]); + }); + 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) { + Object.keys(workbenchPatchesByNode).forEach(nodeId => { + const node = this.getNode(nodeId); + if (node === null) { + console.warn(`Node with id ${nodeId} not found, skipping patch application.`); + return; + } + const nodePatches = workbenchPatchesByNode[nodeId]; + node.updateNodeFromPatch(nodePatches); + }); + }, } }); 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 177fef9a788..db2902c0539 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js +++ b/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js @@ -109,6 +109,45 @@ qx.Class.define("osparc.desktop.StudyEditor", { 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."), + + curateBackendProjectDocument: function(projectDocument) { + // ignore the ``state`` property, it has its own channel + [ + "state", + ].forEach(prop => { + delete projectDocument[prop]; + }); + // in order to pair it the with frontend's node serialization + // remove null entries + // remove state entries + Object.keys(projectDocument["workbench"]).forEach(nodeId => { + const node = projectDocument["workbench"][nodeId]; + Object.keys(node).forEach(nodeProp => { + if (nodeProp === "state") { + delete node[nodeProp]; + } + if (node[nodeProp] === null) { + delete node[nodeProp]; + } + }); + }); + delete projectDocument["ui"]["icon"]; + delete projectDocument["ui"]["templateType"]; + }, + + curateFrontendProjectDocument: function(myStudy) { + // the updatedStudy model doesn't contain the following properties + [ + "accessRights", + "creationDate", + "folderId", + "prjOwner", + "tags", + "trashedBy", + ].forEach(prop => { + delete myStudy[prop]; + }); + } }, members: { @@ -119,11 +158,13 @@ qx.Class.define("osparc.desktop.StudyEditor", { __autoSaveTimer: null, __savingTimer: null, __studyEditorIdlingTracker: null, - __studyDataInBackend: null, + __lastSyncedProjectDocument: null, + __lastSyncedProjectVersion: null, __updatingStudy: null, __updateThrottled: null, __nodesSlidesTree: null, __throttledPatchPending: null, + __blockUpdates: null, setStudyData: function(studyData) { if (this.__settingStudy) { @@ -153,7 +194,7 @@ qx.Class.define("osparc.desktop.StudyEditor", { study.openStudy() .then(studyData => { - this.__setStudyDataInBackend(studyData); + this.__setLastSyncedProjectDocument(studyData); this.__workbenchView.setStudy(study); this.__slideshowView.setStudy(study); @@ -256,15 +297,19 @@ qx.Class.define("osparc.desktop.StudyEditor", { workbench.addListener("updateStudyDocument", () => this.updateStudyDocument()); workbench.addListener("restartAutoSaveTimer", () => this.__restartAutoSaveTimer()); } + + if (osparc.utils.DisabledPlugins.isRTCEnabled()) { + this.__listenToProjectDocument(); + } }, - __setStudyDataInBackend: function(studyData) { - this.__studyDataInBackend = osparc.data.model.Study.deepCloneStudyObject(studyData, true); + __setLastSyncedProjectDocument: function(studyData) { + this.__lastSyncedProjectDocument = osparc.data.model.Study.deepCloneStudyObject(studyData, true); - // remove the runHash, this.__studyDataInBackend is only used for diff comparison and the frontend doesn't keep it - Object.keys(this.__studyDataInBackend["workbench"]).forEach(nodeId => { - if ("runHash" in this.__studyDataInBackend["workbench"][nodeId]) { - delete this.__studyDataInBackend["workbench"][nodeId]["runHash"]; + // remove the runHash, this.__lastSyncedProjectDocument is only used for diff comparison and the frontend doesn't keep it + Object.keys(this.__lastSyncedProjectDocument["workbench"]).forEach(nodeId => { + if ("runHash" in this.__lastSyncedProjectDocument["workbench"][nodeId]) { + delete this.__lastSyncedProjectDocument["workbench"][nodeId]["runHash"]; } }); }, @@ -281,6 +326,65 @@ qx.Class.define("osparc.desktop.StudyEditor", { this.__listenToStatePorts(); }, + __listenToProjectDocument: function() { + const socket = osparc.wrapper.WebSocket.getInstance(); + + if (!socket.slotExists("projectDocument:updated")) { + socket.on("projectDocument:updated", data => { + 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); + 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); + } + }, + __listenToLogger: function() { const socket = osparc.wrapper.WebSocket.getInstance(); @@ -877,7 +981,7 @@ qx.Class.define("osparc.desktop.StudyEditor", { sourceStudy, delta: {}, } - const delta = osparc.wrapper.JsonDiffPatch.getInstance().diff(this.__studyDataInBackend, sourceStudy); + const delta = osparc.wrapper.JsonDiffPatch.getInstance().diff(this.__lastSyncedProjectDocument, sourceStudy); if (delta) { // lastChangeDate and creationDate should not be taken into account as data change delete delta["creationDate"]; @@ -909,10 +1013,11 @@ 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); + projectDocumentChanged: function(patchData) { + patchData["userGroupId"] = osparc.auth.Data.getInstance().getGroupId(); + // avoid echo loop + if (this.__blockUpdates) { + return; } this.getStudy().setSavePending(true); @@ -937,8 +1042,8 @@ qx.Class.define("osparc.desktop.StudyEditor", { this.getStudy().setSavePending(true); this.__updatingStudy++; const studyDiffs = this.__getStudyDiffs(); - return this.getStudy().patchStudyDelayed(studyDiffs.delta, studyDiffs.sourceStudy) - .then(studyData => this.__setStudyDataInBackend(studyData)) + return this.getStudy().patchStudyDiffs(studyDiffs.delta, studyDiffs.sourceStudy) + .then(studyData => this.__setLastSyncedProjectDocument(studyData)) .catch(error => { if ("status" in error && error.status === 409) { console.log("Flash message blocked"); // Workaround for osparc-issues #1189 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 0d9e6183018..a57e7231aae 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js @@ -246,6 +246,10 @@ qx.Class.define("osparc.desktop.WorkbenchView", { this.__connectEvents(); study.getWorkbench().addListener("pipelineChanged", () => this.__evalSlidesButtons()); + study.getWorkbench().addListener("nodeRemoved", e => { + const {nodeId, connectedEdgeIds} = e.getData(); + this.nodeRemoved(nodeId, connectedEdgeIds); + }); study.getUi().getSlideshow().addListener("changeSlideshow", () => this.__evalSlidesButtons()); study.getUi().addListener("changeMode", () => this.__evalSlidesButtons()); this.__evalSlidesButtons(); @@ -1169,23 +1173,23 @@ qx.Class.define("osparc.desktop.WorkbenchView", { } }, - __doRemoveNode: async function(nodeId) { + __doRemoveNode: function(nodeId) { const workbench = this.getStudy().getWorkbench(); - const connectedEdges = workbench.getConnectedEdges(nodeId); - const removed = await workbench.removeNode(nodeId); - if (removed) { - // remove first the connected edges - for (let i = 0; i < connectedEdges.length; i++) { - const edgeId = connectedEdges[i]; - this.__workbenchUI.clearEdge(edgeId); - } - this.__workbenchUI.clearNode(nodeId); - } + workbench.removeNode(nodeId); if ([this.__currentNodeId, null].includes(this.__nodesTree.getCurrentNodeId())) { this.nodeSelected(this.getStudy().getUuid()); } }, + 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/io/rest/Resource.js b/services/static-webserver/client/source/class/osparc/io/rest/Resource.js index 6a68dc551a4..ec5225e084c 100644 --- a/services/static-webserver/client/source/class/osparc/io/rest/Resource.js +++ b/services/static-webserver/client/source/class/osparc/io/rest/Resource.js @@ -34,6 +34,9 @@ qx.Class.define("osparc.io.rest.Resource", { }, { key: "X-Simcore-Products-Name", value: qx.core.Environment.get("product.name") + }, { + key: "X-Client-Session-Id", + value: osparc.utils.Utils.getClientSessionID() }]; if (this.AUTHENTICATION !== undefined && this.AUTHENTICATION !== null) { 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 a2ee4daab00..86b9b713f18 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 @@ -165,7 +165,7 @@ qx.Class.define("osparc.node.slideshow.BaseNodeView", { flex: 1 }); - const outputsBtn = this._outputsBtn = new qx.ui.form.ToggleButton().set({ + const outputsBtn = this.__outputsBtn = new qx.ui.form.ToggleButton().set({ width: 110, label: this.tr("Outputs"), icon: "@FontAwesome5Solid/sign-out-alt/14", @@ -198,7 +198,7 @@ qx.Class.define("osparc.node.slideshow.BaseNodeView", { }); mainView.bind("backgroundColor", outputsLayout, "backgroundColor"); mainView.bind("backgroundColor", outputsLayout.getChildControl("frame"), "backgroundColor"); - this._outputsBtn.bind("value", outputsLayout, "visibility", { + this.__outputsBtn.bind("value", outputsLayout, "visibility", { converter: value => value ? "visible" : "excluded" }); hBox.add(outputsLayout); @@ -266,7 +266,7 @@ qx.Class.define("osparc.node.slideshow.BaseNodeView", { }, getOutputsButton: function() { - return this._outputsBtn; + return this.__outputsBtn; }, getMainView: function() { @@ -415,7 +415,7 @@ qx.Class.define("osparc.node.slideshow.BaseNodeView", { node.getStatus().addListener("changeProgress", () => updateProgress(), this); } - node.bind("outputs", this._outputsBtn, "label", { + node.bind("outputs", this.__outputsBtn, "label", { converter: outputsData => { let outputCounter = 0; Object.keys(outputsData).forEach(outKey => { @@ -427,7 +427,17 @@ qx.Class.define("osparc.node.slideshow.BaseNodeView", { return this.tr("Outputs") + ` (${outputCounter})`; } }); - this._outputsBtn.addListener("changeLabel", () => osparc.utils.Utils.makeButtonBlink(this._outputsBtn, 2)); + this.__outputsBtn.addListener("changeLabel", () => { + // new output received + // make button blink + osparc.utils.Utils.makeButtonBlink(this.__outputsBtn, 2); + // and show Flash Message + const outputs = this.getNode().getOutputs(); + if (outputs && Object.keys(outputs).length > 0) { + const flashMsg = this.tr("New Outputs received"); + osparc.FlashMessenger.getInstance().logAs(flashMsg, "INFO"); + } + }); this._addLogger(); } 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 a0f26f40645..1d74811ad0c 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 @@ -98,7 +98,7 @@ qx.Class.define("osparc.node.slideshow.NodeView", { this._outputsLayout.add(outputsForm); } - this._outputsBtn.set({ + this.getOutputsButton().set({ value: false, enabled: this.getNode().hasOutputs() > 0 }); diff --git a/services/static-webserver/client/source/class/osparc/ui/basic/AvatarGroup.js b/services/static-webserver/client/source/class/osparc/ui/basic/AvatarGroup.js index acac5918031..e2033e97fa7 100644 --- a/services/static-webserver/client/source/class/osparc/ui/basic/AvatarGroup.js +++ b/services/static-webserver/client/source/class/osparc/ui/basic/AvatarGroup.js @@ -53,7 +53,10 @@ qx.Class.define("osparc.ui.basic.AvatarGroup", { __onGlobalPointerMove: null, setUserGroupIds: function(userGroupIds) { - if (JSON.stringify(userGroupIds) === JSON.stringify(this.__userGroupIds)) { + if ( + userGroupIds.length && + JSON.stringify(userGroupIds) === JSON.stringify(this.__userGroupIds) + ) { return; } this.__userGroupIds = userGroupIds || []; 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 03898c10a63..9f68010a56f 100644 --- a/services/static-webserver/client/source/class/osparc/utils/Utils.js +++ b/services/static-webserver/client/source/class/osparc/utils/Utils.js @@ -156,7 +156,11 @@ qx.Class.define("osparc.utils.Utils", { source = imgSrc; } }) - .finally(() => image.setSource(source)); + .finally(() => { + if (image.getContentElement() && imgSrc) { // check if the image is still there + image.setSource(source); + } + }); }, addWhiteSpaces: function(integer) { 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 476a3583322..d887ff0c6b1 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/NodeUI.js @@ -341,6 +341,10 @@ qx.Class.define("osparc.workbench.NodeUI", { }, __applyNode: function(node) { + node.addListener("changePosition", e => { + this.moveNodeTo(e.getData()); + }); + if (node.isDynamic()) { const startButton = new qx.ui.menu.Button().set({ label: this.tr("Start"), 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 ad7bd682a3e..60fae2ad5c1 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js @@ -663,6 +663,16 @@ 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 => { + const data = e.getData(); + const { nodeId1, nodeId2 } = data; + this._createEdgeBetweenNodes(nodeId1, nodeId2, false); + }); + node.addListener("removeEdge", e => { + const data = e.getData(); + const { nodeId1, nodeId2 } = data; + this.__removeEdgeBetweenNodes(nodeId1, nodeId2); + }); nodeUI.populateNodeLayout(this.__svgLayer); nodeUI.addListener("renameNode", e => this.__openNodeRenamer(e.getData()), this); nodeUI.addListener("markerClicked", e => this.__openMarkerEditor(e.getData()), this); @@ -828,38 +838,62 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { const edgeUI = new osparc.workbench.EdgeUI(edge, edgeRepresentation); this.__edgesUI.push(edgeUI); - const hint = edgeUI.getHint(); - const that = this; + this.__decorateEdgeUI(edgeUI); + } + }, + + __decorateEdgeUI: function(edgeUI) { + const hint = edgeUI.getHint(); + const edgeRepresentation = edgeUI.getRepresentation(); + const that = this; + [ + edgeRepresentation.widerCurve.node, + edgeRepresentation.node + ].forEach(svgEl => { + svgEl.addEventListener("click", e => { + // this is needed to get out of the context of svg + that.__setSelectedItem(edgeUI.getEdgeId()); // eslint-disable-line no-underscore-dangle + e.stopPropagation(); + }, this); + + const topOffset = 20; [ - edgeRepresentation.widerCurve.node, - edgeRepresentation.node - ].forEach(svgEl => { - svgEl.addEventListener("click", e => { - // this is needed to get out of the context of svg - that.__setSelectedItem(edgeUI.getEdgeId()); // eslint-disable-line no-underscore-dangle - e.stopPropagation(); + "mouseover", + "mousemove" + ].forEach(ev => { + svgEl.addEventListener(ev, e => { + const leftOffset = -(parseInt(hint.getHintBounds().width/2)); + const properties = { + top: e.clientY + topOffset, + left: e.clientX + leftOffset + }; + hint.setLayoutProperties(properties); + if (hint.getText()) { + hint.show(); + } }, this); - - const topOffset = 20; - [ - "mouseover", - "mousemove" - ].forEach(ev => { - svgEl.addEventListener(ev, e => { - const leftOffset = -(parseInt(hint.getHintBounds().width/2)); - const properties = { - top: e.clientY + topOffset, - left: e.clientX + leftOffset - }; - hint.setLayoutProperties(properties); - if (hint.getText()) { - hint.show(); - } - }, this); - }); }); - edgeUI.getRepresentation().widerCurve.node.addEventListener("mouseout", () => hint.exclude(), this); - this.__svgLayer.addListener("mouseout", () => hint.exclude(), this); + }); + edgeRepresentation.widerCurve.node.addEventListener("mouseout", () => hint.exclude(), this); + this.__svgLayer.addListener("mouseout", () => hint.exclude(), this); + }, + + __getEdgeUIBetweenNodes: function(node1Id, node2Id) { + const foundEdgeUI = this.__edgesUI.find(edgeUi => { + const edgeObj = edgeUi.getEdge(); + const inputNode = edgeObj.getInputNode(); + const outputNode = edgeObj.getOutputNode(); + if (inputNode.getNodeId() === node1Id && outputNode.getNodeId() === node2Id) { + return true; + } + }); + return foundEdgeUI; + }, + + __removeEdgeBetweenNodes: function(node1Id, node2Id) { + const edgeUI = this.__getEdgeUIBetweenNodes(node1Id, node2Id); + if (edgeUI) { + this.__removeEdge(edgeUI); } }, @@ -1070,7 +1104,9 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { clearNode(nodeId) { const nodeUI = this.getNodeUI(nodeId); - this.__clearNodeUI(nodeUI); + if (nodeUI) { + this.__clearNodeUI(nodeUI); + } }, clearEdge: function(edgeId) { @@ -1207,6 +1243,14 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { Object.values(annotations).forEach(annotation => { this.__renderAnnotation(annotation); }); + studyUI.addListener("annotationAdded", e => { + const annotation = e.getData(); + this.__renderAnnotation(annotation); + }, this); + studyUI.addListener("annotationRemoved", e => { + const annotationId = e.getData(); + this.__removeAnnotation(annotationId); + }, this); }, __setSelectedItem: function(newID) { @@ -2018,9 +2062,8 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { this.__toolHint.setValue(null); }, - __addAnnotation: function(annotationData, id) { - const annotation = new osparc.workbench.Annotation(annotationData, id); - this.getStudy().getUi().addAnnotation(annotation); + __addAnnotation: function(annotationData) { + const annotation = this.getStudy().getUi().addAnnotation(annotationData); this.__renderAnnotation(annotation); diff --git a/services/static-webserver/client/source/class/osparc/wrapper/JsonDiffPatch.js b/services/static-webserver/client/source/class/osparc/wrapper/JsonDiffPatch.js index 92e998ccb3f..5c17cff1c5a 100644 --- a/services/static-webserver/client/source/class/osparc/wrapper/JsonDiffPatch.js +++ b/services/static-webserver/client/source/class/osparc/wrapper/JsonDiffPatch.js @@ -33,7 +33,7 @@ qx.Class.define("osparc.wrapper.JsonDiffPatch", { statics: { NAME: "jsondiffpatch", - VERSION: "0.3.11", + VERSION: "0.7.3", URL: "https://github.com/benjamine/jsondiffpatch" }, @@ -51,11 +51,12 @@ qx.Class.define("osparc.wrapper.JsonDiffPatch", { members: { __diffPatcher: null, + __deltaToPatch: null, init: function() { // initialize the script loading - let jsondiffpatchPath = "jsondiffpatch/jsondiffpatch.min.js"; - let dynLoader = new qx.util.DynamicScriptLoader([ + const jsondiffpatchPath = "jsondiffpatch/jsondiffpatch.min.js"; // own build required for the formatters to work + const dynLoader = new qx.util.DynamicScriptLoader([ jsondiffpatchPath ]); @@ -64,6 +65,9 @@ qx.Class.define("osparc.wrapper.JsonDiffPatch", { this.__diffPatcher = jsondiffpatch.create(); + const JsonPatchFormatter = jsondiffpatch.formatters.jsonpatch; + this.__deltaToPatch = new JsonPatchFormatter(); + this.setLibReady(true); }, this); @@ -75,20 +79,20 @@ qx.Class.define("osparc.wrapper.JsonDiffPatch", { dynLoader.start(); }, + // https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md diff: function(obj1, obj2) { - // https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md let delta = this.__diffPatcher.diff(obj1, obj2); return delta; }, - patch: function(obj, delta) { - this.__diffPatcher.patch(obj, delta); - return obj; + // format to JSON PATCH (RFC 6902) + // https://github.com/benjamine/jsondiffpatch/blob/master/docs/formatters.md + deltaToJsonPatches: function(delta) { + if (this.__deltaToPatch) { + const patches = this.__deltaToPatch.format(delta); + return patches; + } + return []; }, - - // deep clone - clone: function(obj) { - return this.__diffPatcher.clone(obj); - } } }); diff --git a/services/static-webserver/client/source/resource/jsondiffpatch/jsondiffpatch.min.js b/services/static-webserver/client/source/resource/jsondiffpatch/jsondiffpatch.min.js index c7297e6fff8..e54a0615272 100644 --- a/services/static-webserver/client/source/resource/jsondiffpatch/jsondiffpatch.min.js +++ b/services/static-webserver/client/source/resource/jsondiffpatch/jsondiffpatch.min.js @@ -1,36 +1,3 @@ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.jsondiffpatch=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;oa;a++)for(var n=e[a],l=0;i>l;l++){var s=t[l];if(a!==l&&n===s)return!0}}function matchItems(e,t,r,i,a){var n=e[r],l=t[i];if(n===l)return!0;if("object"!=typeof n||"object"!=typeof l)return!1;var s=a.objectHash;if(!s)return a.matchByPosition&&r===i;var o,f;return"number"==typeof r?(a.hashCache1=a.hashCache1||[],o=a.hashCache1[r],"undefined"==typeof o&&(a.hashCache1[r]=o=s(n,r))):o=s(n),"undefined"==typeof o?!1:("number"==typeof i?(a.hashCache2=a.hashCache2||[],f=a.hashCache2[i],"undefined"==typeof f&&(a.hashCache2[i]=f=s(l,i))):f=s(l),"undefined"==typeof f?!1:o===f)}var DiffContext=require("../contexts/diff").DiffContext,PatchContext=require("../contexts/patch").PatchContext,ReverseContext=require("../contexts/reverse").ReverseContext,lcs=require("./lcs"),ARRAY_MOVE=3,isArray="function"==typeof Array.isArray?Array.isArray:function(e){return e instanceof Array},arrayIndexOf="function"==typeof Array.prototype.indexOf?function(e,t){return e.indexOf(t)}:function(e,t){for(var r=e.length,i=0;r>i;i++)if(e[i]===t)return i;return-1},diffFilter=function(e){if(e.leftIsArray){var t,r,i,a,n={objectHash:e.options&&e.options.objectHash,matchByPosition:e.options&&e.options.matchByPosition},l=0,s=0,o=e.left,f=e.right,c=o.length,h=f.length;for(c>0&&h>0&&!n.objectHash&&"boolean"!=typeof n.matchByPosition&&(n.matchByPosition=!arraysHaveMatchByRef(o,f,c,h));c>l&&h>l&&matchItems(o,f,l,l,n);)t=l,a=new DiffContext(e.left[t],e.right[t]),e.push(a,t),l++;for(;c>s+l&&h>s+l&&matchItems(o,f,c-1-s,h-1-s,n);)r=c-1-s,i=h-1-s,a=new DiffContext(e.left[r],e.right[i]),e.push(a,i),s++;var u;if(l+s===c){if(c===h)return void e.setResult(void 0).exit();for(u=u||{_t:"a"},t=l;h-s>t;t++)u[t]=[f[t]];return void e.setResult(u).exit()}if(l+s===h){for(u=u||{_t:"a"},t=l;c-s>t;t++)u["_"+t]=[o[t],0,0];return void e.setResult(u).exit()}delete n.hashCache1,delete n.hashCache2;var d=o.slice(l,c-s),v=f.slice(l,h-s),p=lcs.get(d,v,matchItems,n),y=[];for(u=u||{_t:"a"},t=l;c-s>t;t++)arrayIndexOf(p.indices1,t-l)<0&&(u["_"+t]=[o[t],0,0],y.push(t));var x=!0;e.options&&e.options.arrays&&e.options.arrays.detectMove===!1&&(x=!1);var m=!1;e.options&&e.options.arrays&&e.options.arrays.includeValueOnMove&&(m=!0);var C=y.length;for(t=l;h-s>t;t++){var R=arrayIndexOf(p.indices2,t-l);if(0>R){var A=!1;if(x&&C>0)for(var _=0;C>_;_++)if(r=y[_],matchItems(d,v,r-l,t-l,n)){u["_"+r].splice(1,2,t,ARRAY_MOVE),m||(u["_"+r][0]=""),i=t,a=new DiffContext(e.left[r],e.right[i]),e.push(a,i),y.splice(_,1),A=!0;break}A||(u[t]=[f[t]])}else r=p.indices1[R]+l,i=p.indices2[R]+l,a=new DiffContext(e.left[r],e.right[i]),e.push(a,i)}e.setResult(u).exit()}};diffFilter.filterName="arrays";var compare={numerically:function(e,t){return e-t},numericallyBy:function(e){return function(t,r){return t[e]-r[e]}}},patchFilter=function(e){if(e.nested&&"a"===e.delta._t){var t,r,i=e.delta,a=e.left,n=[],l=[],s=[];for(t in i)if("_t"!==t)if("_"===t[0]){if(0!==i[t][2]&&i[t][2]!==ARRAY_MOVE)throw new Error("only removal or move can be applied at original array indices, invalid diff type: "+i[t][2]);n.push(parseInt(t.slice(1),10))}else 1===i[t].length?l.push({index:parseInt(t,10),value:i[t][0]}):s.push({index:parseInt(t,10),delta:i[t]});for(n=n.sort(compare.numerically),t=n.length-1;t>=0;t--){r=n[t];var o=i["_"+r],f=a.splice(r,1)[0];o[2]===ARRAY_MOVE&&l.push({index:o[1],value:f})}l=l.sort(compare.numericallyBy("index"));var c=l.length;for(t=0;c>t;t++){var h=l[t];a.splice(h.index,0,h.value)}var u,d=s.length;if(d>0)for(t=0;d>t;t++){var v=s[t];u=new PatchContext(e.left[v.index],v.delta),e.push(u,v.index)}return e.children?void e.exit():void e.setResult(e.left).exit()}};patchFilter.filterName="arrays";var collectChildrenPatchFilter=function(e){if(e&&e.children&&"a"===e.delta._t){for(var t,r=e.children.length,i=0;r>i;i++)t=e.children[i],e.left[t.childName]=t.result;e.setResult(e.left).exit()}};collectChildrenPatchFilter.filterName="arraysCollectChildren";var reverseFilter=function(e){if(!e.nested)return void(e.delta[2]===ARRAY_MOVE&&(e.newName="_"+e.delta[1],e.setResult([e.delta[0],parseInt(e.childName.substr(1),10),ARRAY_MOVE]).exit()));if("a"===e.delta._t){var t,r;for(t in e.delta)"_t"!==t&&(r=new ReverseContext(e.delta[t]),e.push(r,t));e.exit()}};reverseFilter.filterName="arrays";var reverseArrayDeltaIndex=function(e,t,r){if("string"==typeof t&&"_"===t[0])return parseInt(t.substr(1),10);if(isArray(r)&&0===r[2])return"_"+t;var i=+t;for(var a in e){var n=e[a];if(isArray(n))if(n[2]===ARRAY_MOVE){var l=parseInt(a.substr(1),10),s=n[1];if(s===+t)return l;i>=l&&s>i?i++:l>=i&&i>s&&i--}else if(0===n[2]){var o=parseInt(a.substr(1),10);i>=o&&i++}else 1===n.length&&i>=a&&i--}return i},collectChildrenReverseFilter=function(e){if(e&&e.children&&"a"===e.delta._t){for(var t,r=e.children.length,i={_t:"a"},a=0;r>a;a++){t=e.children[a];var n=t.newName;"undefined"==typeof n&&(n=reverseArrayDeltaIndex(e.delta,t.childName,t.result)),i[n]!==t.result&&(i[n]=t.result)}e.setResult(i).exit()}};collectChildrenReverseFilter.filterName="arraysCollectChildren",exports.diffFilter=diffFilter,exports.patchFilter=patchFilter,exports.collectChildrenPatchFilter=collectChildrenPatchFilter,exports.reverseFilter=reverseFilter,exports.collectChildrenReverseFilter=collectChildrenReverseFilter; -},{"../contexts/diff":4,"../contexts/patch":5,"../contexts/reverse":6,"./lcs":12}],11:[function(require,module,exports){ -var diffFilter=function(t){t.left instanceof Date?(t.right instanceof Date?t.left.getTime()!==t.right.getTime()?t.setResult([t.left,t.right]):t.setResult(void 0):t.setResult([t.left,t.right]),t.exit()):t.right instanceof Date&&t.setResult([t.left,t.right]).exit()};diffFilter.filterName="dates",exports.diffFilter=diffFilter; -},{}],12:[function(require,module,exports){ -var defaultMatch=function(t,e,n,r){return t[n]===e[r]},lengthMatrix=function(t,e,n,r){var c,a,i=t.length,u=e.length,f=[i+1];for(c=0;i+1>c;c++)for(f[c]=[u+1],a=0;u+1>a;a++)f[c][a]=0;for(f.match=n,c=1;i+1>c;c++)for(a=1;u+1>a;a++)n(t,e,c-1,a-1,r)?f[c][a]=f[c-1][a-1]+1:f[c][a]=Math.max(f[c-1][a],f[c][a-1]);return f},backtrack=function(t,e,n,r,c,a){if(0===r||0===c)return{sequence:[],indices1:[],indices2:[]};if(t.match(e,n,r-1,c-1,a)){var i=backtrack(t,e,n,r-1,c-1,a);return i.sequence.push(e[r-1]),i.indices1.push(r-1),i.indices2.push(c-1),i}return t[r][c-1]>t[r-1][c]?backtrack(t,e,n,r,c-1,a):backtrack(t,e,n,r-1,c,a)},get=function(t,e,n,r){r=r||{};var c=lengthMatrix(t,e,n||defaultMatch,r),a=backtrack(c,t,e,t.length,e.length,r);return"string"==typeof t&&"string"==typeof e&&(a.sequence=a.sequence.join("")),a};exports.get=get; -},{}],13:[function(require,module,exports){ -var DiffContext=require("../contexts/diff").DiffContext,PatchContext=require("../contexts/patch").PatchContext,ReverseContext=require("../contexts/reverse").ReverseContext,collectChildrenDiffFilter=function(e){if(e&&e.children){for(var t,l=e.children.length,r=e.result,i=0;l>i;i++)t=e.children[i],"undefined"!=typeof t.result&&(r=r||{},r[t.childName]=t.result);r&&e.leftIsArray&&(r._t="a"),e.setResult(r).exit()}};collectChildrenDiffFilter.filterName="collectChildren";var objectsDiffFilter=function(e){if(!e.leftIsArray&&"object"===e.leftType){var t,l,r=e.options.propertyFilter;for(t in e.left)Object.prototype.hasOwnProperty.call(e.left,t)&&(r&&!r(t,e)||(l=new DiffContext(e.left[t],e.right[t]),e.push(l,t)));for(t in e.right)Object.prototype.hasOwnProperty.call(e.right,t)&&(r&&!r(t,e)||"undefined"==typeof e.left[t]&&(l=new DiffContext(void 0,e.right[t]),e.push(l,t)));return e.children&&0!==e.children.length?void e.exit():void e.setResult(void 0).exit()}};objectsDiffFilter.filterName="objects";var patchFilter=function(e){if(e.nested&&!e.delta._t){var t,l;for(t in e.delta)l=new PatchContext(e.left[t],e.delta[t]),e.push(l,t);e.exit()}};patchFilter.filterName="objects";var collectChildrenPatchFilter=function(e){if(e&&e.children&&!e.delta._t){for(var t,l=e.children.length,r=0;l>r;r++)t=e.children[r],Object.prototype.hasOwnProperty.call(e.left,t.childName)&&void 0===t.result?delete e.left[t.childName]:e.left[t.childName]!==t.result&&(e.left[t.childName]=t.result);e.setResult(e.left).exit()}};collectChildrenPatchFilter.filterName="collectChildren";var reverseFilter=function(e){if(e.nested&&!e.delta._t){var t,l;for(t in e.delta)l=new ReverseContext(e.delta[t]),e.push(l,t);e.exit()}};reverseFilter.filterName="objects";var collectChildrenReverseFilter=function(e){if(e&&e.children&&!e.delta._t){for(var t,l=e.children.length,r={},i=0;l>i;i++)t=e.children[i],r[t.childName]!==t.result&&(r[t.childName]=t.result);e.setResult(r).exit()}};collectChildrenReverseFilter.filterName="collectChildren",exports.collectChildrenDiffFilter=collectChildrenDiffFilter,exports.objectsDiffFilter=objectsDiffFilter,exports.patchFilter=patchFilter,exports.collectChildrenPatchFilter=collectChildrenPatchFilter,exports.reverseFilter=reverseFilter,exports.collectChildrenReverseFilter=collectChildrenReverseFilter; -},{"../contexts/diff":4,"../contexts/patch":5,"../contexts/reverse":6}],14:[function(require,module,exports){ -var TEXT_DIFF=2,DEFAULT_MIN_LENGTH=60,cachedDiffPatch=null,getDiffMatchPatch=function(t){if(!cachedDiffPatch){var e;if("undefined"!=typeof diff_match_patch)e="function"==typeof diff_match_patch?new diff_match_patch:new diff_match_patch.diff_match_patch;else if("function"==typeof require)try{var i="diff_match_patch_uncompressed",f=require("../../public/external/"+i);e=new f.diff_match_patch}catch(r){e=null}if(!e){if(!t)return null;var a=new Error("text diff_match_patch library not found");throw a.diff_match_patch_not_found=!0,a}cachedDiffPatch={diff:function(t,i){return e.patch_toText(e.patch_make(t,i))},patch:function(t,i){for(var f=e.patch_apply(e.patch_fromText(i),t),r=0;re;e++){r=f[e];var o=r.slice(0,1);"@"===o?(h=d.exec(r),c=e,l=null,n=null,f[c]="@@ -"+h[3]+","+h[4]+" +"+h[1]+","+h[2]+" @@"):"+"===o?(l=e,f[e]="-"+f[e].slice(1),"+"===f[e-1].slice(0,1)&&(a=f[e],f[e]=f[e-1],f[e-1]=a)):"-"===o&&(n=e,f[e]="+"+f[e].slice(1))}return f.join("\n")},reverseFilter=function(t){t.nested||t.delta[2]===TEXT_DIFF&&t.setResult([textDeltaReverse(t.delta[0]),0,TEXT_DIFF]).exit()};reverseFilter.filterName="texts",exports.diffFilter=diffFilter,exports.patchFilter=patchFilter,exports.reverseFilter=reverseFilter; -},{}],15:[function(require,module,exports){ -var isArray="function"==typeof Array.isArray?Array.isArray:function(e){return e instanceof Array},diffFilter=function(e){if(e.left===e.right)return void e.setResult(void 0).exit();if("undefined"==typeof e.left){if("function"==typeof e.right)throw new Error("functions are not supported");return void e.setResult([e.right]).exit()}if("undefined"==typeof e.right)return void e.setResult([e.left,0,0]).exit();if("function"==typeof e.left||"function"==typeof e.right)throw new Error("functions are not supported");if(e.leftType=null===e.left?"null":typeof e.left,e.rightType=null===e.right?"null":typeof e.right,e.leftType!==e.rightType)return void e.setResult([e.left,e.right]).exit();if("boolean"===e.leftType||"number"===e.leftType)return void e.setResult([e.left,e.right]).exit();if("object"===e.leftType&&(e.leftIsArray=isArray(e.left)),"object"===e.rightType&&(e.rightIsArray=isArray(e.right)),e.leftIsArray!==e.rightIsArray)return void e.setResult([e.left,e.right]).exit();if(e.left instanceof RegExp){if(!(e.right instanceof RegExp))return void e.setResult([e.left,e.right]).exit();e.setResult([e.left.toString(),e.right.toString()]).exit()}};diffFilter.filterName="trivial";var patchFilter=function(e){if("undefined"==typeof e.delta)return void e.setResult(e.left).exit();if(e.nested=!isArray(e.delta),!e.nested){if(1===e.delta.length)return void e.setResult(e.delta[0]).exit();if(2===e.delta.length){if(e.left instanceof RegExp){var t=/^\/(.*)\/([gimyu]+)$/.exec(e.delta[1]);if(t)return void e.setResult(new RegExp(t[1],t[2])).exit()}return void e.setResult(e.delta[1]).exit()}return 3===e.delta.length&&0===e.delta[2]?void e.setResult(void 0).exit():void 0}};patchFilter.filterName="trivial";var reverseFilter=function(e){return"undefined"==typeof e.delta?void e.setResult(e.delta).exit():(e.nested=!isArray(e.delta),e.nested?void 0:1===e.delta.length?void e.setResult([e.delta[0],0,0]).exit():2===e.delta.length?void e.setResult([e.delta[1],e.delta[0]]).exit():3===e.delta.length&&0===e.delta[2]?void e.setResult([e.delta[0]]).exit():void 0)};reverseFilter.filterName="trivial",exports.diffFilter=diffFilter,exports.patchFilter=patchFilter,exports.reverseFilter=reverseFilter; -},{}],16:[function(require,module,exports){ -var Pipe=function(t){this.name=t,this.filters=[]};Pipe.prototype.process=function(t){if(!this.processor)throw new Error("add this pipe to a processor before using it");for(var e=this.debug,r=this.filters.length,i=t,s=0;r>s;s++){var o=this.filters[s];if(e&&this.log("filter: "+o.filterName),o(i),"object"==typeof i&&i.exiting){i.exiting=!1;break}}!i.next&&this.resultCheck&&this.resultCheck(i)},Pipe.prototype.log=function(t){console.log("[jsondiffpatch] "+this.name+" pipe, "+t)},Pipe.prototype.append=function(){return this.filters.push.apply(this.filters,arguments),this},Pipe.prototype.prepend=function(){return this.filters.unshift.apply(this.filters,arguments),this},Pipe.prototype.indexOf=function(t){if(!t)throw new Error("a filter name is required");for(var e=0;e{var De=Object.defineProperty;var Ne=(i,e)=>{for(var r in e)De(i,r,{get:e[r],enumerable:!0})};var le={};Ne(le,{DiffPatcher:()=>C,clone:()=>Xe,create:()=>Se,dateReviver:()=>P,diff:()=>We,patch:()=>Be,reverse:()=>qe,unpatch:()=>Ve});function P(i,e){var r,n,t,s,o,d;if(typeof e!="string")return e;let a=/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d*))?(Z|([+-])(\d{2}):(\d{2}))$/.exec(e);return a?new Date(Date.UTC(Number.parseInt((r=a[1])!==null&&r!==void 0?r:"0",10),Number.parseInt((n=a[2])!==null&&n!==void 0?n:"0",10)-1,Number.parseInt((t=a[3])!==null&&t!==void 0?t:"0",10),Number.parseInt((s=a[4])!==null&&s!==void 0?s:"0",10),Number.parseInt((o=a[5])!==null&&o!==void 0?o:"0",10),Number.parseInt((d=a[6])!==null&&d!==void 0?d:"0",10),(a[7]?Number.parseInt(a[7]):0)||0)):e}function Ee(i){var e;let r=/^\/(.*)\/([gimyu]*)$/.exec(i.toString());if(!r)throw new Error("Invalid RegExp");return new RegExp((e=r[1])!==null&&e!==void 0?e:"",r[2])}function D(i){if(typeof i!="object")return i;if(i===null)return null;if(Array.isArray(i))return i.map(D);if(i instanceof Date)return new Date(i.getTime());if(i instanceof RegExp)return Ee(i);let e={};for(let r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=D(i[r]));return e}function me(i,e){if(i.length===0)throw new Error(e||"Expected a non-empty array")}function ye(i){return i.length>0}function ve(i){return i.length>=2}var ge=i=>i[i.length-1];var b=class{setResult(e){return this.result=e,this.hasResult=!0,this}exit(){return this.exiting=!0,this}push(e,r){return e.parent=this,typeof r<"u"&&(e.childName=r),e.root=this.root||this,e.options=e.options||this.options,this.children?(me(this.children),ge(this.children).next=e,this.children.push(e)):(this.children=[e],this.nextAfterChildren=this.next||null,this.next=e),e.next=this,this}};var M=class extends b{constructor(e,r){super(),this.left=e,this.right=r,this.pipe="diff"}prepareDeltaResult(e){var r,n,t,s;if(typeof e=="object"&&(!((r=this.options)===null||r===void 0)&&r.omitRemovedValues&&Array.isArray(e)&&e.length>1&&(e.length===2||e[2]===0||e[2]===3)&&(e[0]=0),!((n=this.options)===null||n===void 0)&&n.cloneDiffValues)){let o=typeof((t=this.options)===null||t===void 0?void 0:t.cloneDiffValues)=="function"?(s=this.options)===null||s===void 0?void 0:s.cloneDiffValues:D;typeof e[0]=="object"&&(e[0]=o(e[0])),typeof e[1]=="object"&&(e[1]=o(e[1]))}return e}setResult(e){return this.prepareDeltaResult(e),super.setResult(e)}},R=M;var k=class extends b{constructor(e,r){super(),this.left=e,this.delta=r,this.pipe="patch"}},A=k;var L=class extends b{constructor(e){super(),this.delta=e,this.pipe="reverse"}},x=L;var H=class{constructor(e){this.name=e,this.filters=[]}process(e){if(!this.processor)throw new Error("add this pipe to a processor before using it");let r=this.debug,n=this.filters.length,t=e;for(let s=0;se.filterName)}after(e,...r){let n=this.indexOf(e);return this.filters.splice(n+1,0,...r),this}before(e,...r){let n=this.indexOf(e);return this.filters.splice(n,0,...r),this}replace(e,...r){let n=this.indexOf(e);return this.filters.splice(n,1,...r),this}remove(e){let r=this.indexOf(e);return this.filters.splice(r,1),this}clear(){return this.filters.length=0,this}shouldHaveResult(e){return e===!1?(this.resultCheck=null,this):this.resultCheck?this:(this.resultCheck=r=>{if(!r.hasResult){console.log(r);let n=new Error(`${this.name} failed`);throw n.noResult=!0,n}},this)}},j=H;var S=class{constructor(e){this.selfOptions=e||{},this.pipes={}}options(e){return e&&(this.selfOptions=e),this.selfOptions}pipe(e,r){let n=r;if(typeof e=="string"){if(typeof n>"u")return this.pipes[e];this.pipes[e]=n}if(e&&e.name){if(n=e,n.processor===this)return n;this.pipes[n.name]=n}if(!n)throw new Error(`pipe is not defined: ${e}`);return n.processor=this,n}process(e,r){let n=e;n.options=this.options();let t=r||e.pipe||"default",s;for(;t;)typeof n.nextAfterChildren<"u"&&(n.next=n.nextAfterChildren,n.nextAfterChildren=null),typeof t=="string"&&(t=this.pipe(t)),t.process(n),s=t,t=null,n&&n.next&&(n=n.next,t=n.pipe||s);return n.hasResult?n.result:void 0}},we=S;var Ae=(i,e,r,n)=>i[r]===e[n],xe=(i,e,r,n)=>{var t,s,o;let d=i.length,a=e.length,u,h,c=new Array(d+1);for(u=0;u{let t=e.length,s=r.length,o={sequence:[],indices1:[],indices2:[]};for(;t!==0&&s!==0;){if(i.match===void 0)throw new Error("LCS matrix match function is undefined");if(i.match(e,r,t-1,s-1,n))o.sequence.unshift(e[t-1]),o.indices1.unshift(t-1),o.indices2.unshift(s-1),--t,--s;else{let a=i[t];if(a===void 0)throw new Error("LCS matrix row is undefined");let u=a[s-1];if(u===void 0)throw new Error("LCS matrix value is undefined");let h=i[t-1];if(h===void 0)throw new Error("LCS matrix row is undefined");let c=h[s];if(c===void 0)throw new Error("LCS matrix value is undefined");u>c?--s:--t}}return o},Te=(i,e,r,n)=>{let t=n||{},s=xe(i,e,r||Ae,t);return Ie(s,i,e,t)},Re={get:Te};var N=3;function $e(i,e,r,n){for(let t=0;t"u"&&(t.hashCache1[r]=a=d(s,r)),typeof a>"u")return!1;t.hashCache2=t.hashCache2||[];let u=t.hashCache2[n];return typeof u>"u"&&(t.hashCache2[n]=u=d(o,n)),typeof u>"u"?!1:a===u}var W=function(e){var r,n,t,s,o;if(!e.leftIsArray)return;let d={objectHash:(r=e.options)===null||r===void 0?void 0:r.objectHash,matchByPosition:(n=e.options)===null||n===void 0?void 0:n.matchByPosition},a=0,u=0,h,c,f,l=e.left,p=e.right,g=l.length,w=p.length,F;for(g>0&&w>0&&!d.objectHash&&typeof d.matchByPosition!="boolean"&&(d.matchByPosition=!$e(l,p,g,w));a0)for(let T=0;Te[i]-r[i]}},B=function(e){var r;if(!e.nested)return;let n=e.delta;if(n._t!=="a")return;let t,s,o=n,d=e.left,a=[],u=[],h=[];for(t in o)if(t!=="_t")if(t[0]==="_"){let l=t;if(o[l]!==void 0&&(o[l][2]===0||o[l][2]===N))a.push(Number.parseInt(t.slice(1),10));else throw new Error(`only removal or move can be applied at original array indices, invalid diff type: ${(r=o[l])===null||r===void 0?void 0:r[2]}`)}else{let l=t;o[l].length===1?u.push({index:Number.parseInt(l,10),value:o[l][0]}):h.push({index:Number.parseInt(l,10),delta:o[l]})}for(a=a.sort(be.numerically),t=a.length-1;t>=0;t--){if(s=a[t],s===void 0)continue;let l=o[`_${s}`],p=d.splice(s,1)[0];l?.[2]===N&&u.push({index:l[1],value:p})}u=u.sort(be.numericallyBy("index"));let c=u.length;for(t=0;t0)for(t=0;t{if(typeof e=="string"&&e[0]==="_")return Number.parseInt(e.substring(1),10);if(Array.isArray(r)&&r[2]===0)return`_${e}`;let n=+e;for(let t in i){let s=i[t];if(Array.isArray(s))if(s[2]===N){let o=Number.parseInt(t.substring(1),10),d=s[1];if(d===+e)return o;o<=n&&d>n?n++:o>=n&&d{if(!i||!i.children)return;let e=i.delta;if(e._t!=="a")return;let r=e,n=i.children.length,t={_t:"a"};for(let s=0;s"u"){if(o.childName===void 0)throw new Error("child.childName is undefined");d=je(r,o.childName,o.result)}t[d]!==o.result&&(t[d]=o.result)}i.setResult(t).exit()};X.filterName="arraysCollectChildren";var U=function(e){e.left instanceof Date?(e.right instanceof Date?e.left.getTime()!==e.right.getTime()?e.setResult([e.left,e.right]):e.setResult(void 0):e.setResult([e.left,e.right]),e.exit()):e.right instanceof Date&&e.setResult([e.left,e.right]).exit()};U.filterName="dates";var G=i=>{if(!i||!i.children)return;let e=i.children.length,r=i.result;for(let n=0;n"u")){if(r=r||{},t.childName===void 0)throw new Error("diff child.childName is undefined");r[t.childName]=t.result}}r&&i.leftIsArray&&(r._t="a"),i.setResult(r).exit()};G.filterName="collectChildren";var J=i=>{var e;if(i.leftIsArray||i.leftType!=="object")return;let r=i.left,n=i.right,t=(e=i.options)===null||e===void 0?void 0:e.propertyFilter;for(let s in r){if(!Object.prototype.hasOwnProperty.call(r,s)||t&&!t(s,i))continue;let o=new R(r[s],n[s]);i.push(o,s)}for(let s in n)if(Object.prototype.hasOwnProperty.call(n,s)&&!(t&&!t(s,i))&&typeof r[s]>"u"){let o=new R(void 0,n[s]);i.push(o,s)}if(!i.children||i.children.length===0){i.setResult(void 0).exit();return}i.exit()};J.filterName="objects";var Y=function(e){if(!e.nested)return;let r=e.delta;if(r._t)return;let n=r;for(let t in n){let s=new A(e.left[t],n[t]);e.push(s,t)}e.exit()};Y.filterName="objects";var Z=function(e){if(!e||!e.children||e.delta._t)return;let n=e.left,t=e.children.length;for(let s=0;s{if(!i||!i.children||i.delta._t)return;let r=i.children.length,n={};for(let t=0;tn.patch_toText(n.patch_make(t,s)),patch:(t,s)=>{let o=n.patch_apply(n.patch_fromText(s),t);for(let d of o[1])if(!d){let a=new Error("text patch failed");throw a.textPatchFailed=!0,a}return o[0]}}}return Q}var ee=function(e){var r,n;if(e.leftType!=="string")return;let t=e.left,s=e.right,o=((n=(r=e.options)===null||r===void 0?void 0:r.textDiff)===null||n===void 0?void 0:n.minLength)||60;if(t.length{var e,r,n;let t=/^@@ +-(\d+),(\d+) +\+(\d+),(\d+) +@@$/,s=i.split(` +`);for(let o=0;o"u"){if(typeof e.right=="function")throw new Error("functions are not supported");e.setResult([e.right]).exit();return}if(typeof e.right>"u"){e.setResult([e.left,0,0]).exit();return}if(typeof e.left=="function"||typeof e.right=="function")throw new Error("functions are not supported");if(e.leftType=e.left===null?"null":typeof e.left,e.rightType=e.right===null?"null":typeof e.right,e.leftType!==e.rightType){e.setResult([e.left,e.right]).exit();return}if(e.leftType==="boolean"||e.leftType==="number"){e.setResult([e.left,e.right]).exit();return}if(e.leftType==="object"&&(e.leftIsArray=Array.isArray(e.left)),e.rightType==="object"&&(e.rightIsArray=Array.isArray(e.right)),e.leftIsArray!==e.rightIsArray){e.setResult([e.left,e.right]).exit();return}e.left instanceof RegExp&&(e.right instanceof RegExp?e.setResult([e.left.toString(),e.right.toString()]).exit():e.setResult([e.left,e.right]).exit())};ie.filterName="trivial";var ne=function(e){if(typeof e.delta>"u"){e.setResult(e.left).exit();return}if(e.nested=!Array.isArray(e.delta),e.nested)return;let r=e.delta;if(r.length===1){e.setResult(r[0]).exit();return}if(r.length===2){if(e.left instanceof RegExp){let n=/^\/(.*)\/([gimyu]+)$/.exec(r[1]);if(n?.[1]){e.setResult(new RegExp(n[1],n[2])).exit();return}}e.setResult(r[1]).exit();return}r.length===3&&r[2]===0&&e.setResult(void 0).exit()};ne.filterName="trivial";var se=function(e){if(typeof e.delta>"u"){e.setResult(e.delta).exit();return}if(e.nested=!Array.isArray(e.delta),e.nested)return;let r=e.delta;if(r.length===1){e.setResult([r[0],0,0]).exit();return}if(r.length===2){e.setResult([r[1],r[0]]).exit();return}r.length===3&&r[2]===0&&e.setResult([r[0]]).exit()};se.filterName="trivial";var oe=class{constructor(e){this.processor=new we(e),this.processor.pipe(new j("diff").append(G,ie,U,ee,J,W).shouldHaveResult()),this.processor.pipe(new j("patch").append(Z,V,ne,te,Y,B).shouldHaveResult()),this.processor.pipe(new j("reverse").append(K,X,se,re,z,q).shouldHaveResult())}options(e){return this.processor.options(e)}diff(e,r){return this.processor.process(new R(e,r))}patch(e,r){return this.processor.process(new A(e,r))}reverse(e){return this.processor.process(new x(e))}unpatch(e,r){return this.patch(e,this.reverse(r))}clone(e){return D(e)}},C=oe;function Se(i){return new C(i)}var y;function We(i,e){return y||(y=new C),y.diff(i,e)}function Be(i,e){return y||(y=new C),y.patch(i,e)}function Ve(i,e){return y||(y=new C),y.unpatch(i,e)}function qe(i){return y||(y=new C),y.reverse(i)}function Xe(i){return y||(y=new C),y.clone(i)}var _e=i=>{let e=[],r=[...i],n=0;for(;r.length>0;){let{next:t,extra:s}=Ue(r);if(t.from!==t.to){e.push({from:t.from,to:t.to});for(let o of r){if(t.from===o.from)throw new Error("trying to move the same item twice");t.from100)throw new Error("failed to apply all array moves");r.push(s)}}return e},Ue=i=>{if(!ye(i))throw new Error("no more moves to make");if(!ve(i))return{next:i.shift()};let e=i[0],r=-1,n=i[0],t=-1;for(let f=0;fn.to)&&(n=l,t=f))}let s=i[0],o=-1,d=i[0],a=-1;for(let f=0;fd.from)&&(d=l,a=f))}if(o<0||e.tod.from||n.to>n.from&&n.to===d.from){let f=i.splice(t,1)[0];if(!f)throw new Error("failed to get next move");return{next:f}}let u=i.splice(o,1)[0];if(!u)throw new Error("failed to get next move");let h=i.reduce((f,l)=>f+((l.to0;){let t=n.pop();if(t===void 0||!t.delta)break;if(Array.isArray(t.delta)){if(t.delta.length===1&&r.push({op:E.add,path:t.path,value:t.delta[0]}),t.delta.length===2&&r.push({op:E.replace,path:t.path,value:t.delta[1]}),t.delta[2]===0&&r.push({op:E.remove,path:t.path}),t.delta[2]===2)throw new Error("JSONPatch (RFC 6902) doesn't support text diffs, disable textDiff option")}else if(t.delta._t==="a"){let s=t.delta,o=[],d=[],a=[],u=[];for(let c of Object.keys(s))if(c!=="_t")if(c.substring(0,1)==="_"){let f=Number.parseInt(c.substring(1)),l=s[c];if(!l)continue;Array.isArray(l)?l.length===3&&(l[2]===3?d.push({from:f,to:l[1]}):l[2]===0&&o.push(f)):u.push({to:f,delta:l})}else{let f=s[c],l=Number.parseInt(c);if(f){if(!Array.isArray(f))u.push({to:l,delta:f});else if(f.length===1)a.push({to:l,value:f[0]});else if(f.length===2)u.push({to:l,delta:f});else if(f.length===3&&f[2]===3)throw new Error("JSONPatch (RFC 6902) doesn't support text diffs, disable textDiff option")}}a.sort((c,f)=>c.to-f.to),o.sort((c,f)=>f-c);for(let c of o)if(r.push({op:E.remove,path:`${t.path}/${c}`}),d.length>0)for(let f of d)c0){let c=[...a].reverse();for(let l of c)for(let p of d)l.to0&&n.push(...h.reverse())}else for(let s of Object.keys(t.delta).reverse()){let o=t.delta[s];n.push({path:`${t.path}/${Ge(s)}`,delta:o})}}return r}},Ce=fe;var Ge=i=>typeof i!="string"?i.toString():i.indexOf("/")===-1&&i.indexOf("~")===-1?i:i.replace(/~/g,"~0").replace(/\//g,"~1");window.jsondiffpatch=le;window.jsondiffpatch.formatters={jsonpatch:Ce};})();