Browse Source

Fix more bugs

Joeri Exelmans 4 years ago
parent
commit
3172f33baf

+ 11 - 1
lib/versioning/run_server.js

@@ -41,7 +41,16 @@ async function startServer() {
     // You pass two more arguments for config and middleware
     // More details here: https://github.com/vercel/serve-handler#options
     console.log(request.method, request.url)
-    return handler(request, response);
+    return handler(request, response, {
+      "headers": [
+        {
+          "source": "**/*.js",
+          "headers": [{
+            "key": "Cache-Control",
+            "value": "max-age=0",
+          }],
+        }],
+      });
   });
 
   const wsServer = new WebSocketServer({
@@ -229,6 +238,7 @@ async function startServer() {
           return new LeftHandler();
         }
         else if (req.type === "new_edit") {
+          // await asyncSleep(3000);
           const { reqId, op: {id, detail} } = req;
           await opsDB.writeJSON(id, detail);
           await sessionDB.append(this.session.id, id); // Creates file if it doesn't exist yet

+ 110 - 95
src/main/webapp/plugins/cdf/versioning.browser.js

@@ -1343,7 +1343,7 @@ Draw.loadPlugin(async function(ui) {
         const buttonShare = menu.addItem("Share this diagram",
           null, // image
           () => {
-            const ops = history.getOpsSequence();
+            const ops = historyWrapper.history.getOpsSequence();
             const serialized = ops.map(op => op.serialize());
             controller.addInput("new_share", "in", [serialized], controller.wallclockToSimtime());
           }, // callback
@@ -1463,10 +1463,6 @@ Draw.loadPlugin(async function(ui) {
 
   const uiState = new UIState();
 
-  const context = new Context(async id => {
-    throw new Error("Fetch is forbidden. ", id)
-  });
-
   const codec = new mxCodec();
   const xmlSerializer = new XMLSerializer();
   const xmlParser = new DOMParser();
@@ -1520,12 +1516,8 @@ Draw.loadPlugin(async function(ui) {
           model.setStyle(cell, style);
         },
         parent: () => {
-          const {parentId} = value;
-          if (parentId === null) {
-            // Creates a mxChildChange object
-            model.remove(cell);
-          }
-          else {
+          if (value) {
+            const {parentId} = value;
             let parent = getCell(parentId);
             if (parent === undefined) {
               console.log("creating non-existing parent");
@@ -1534,6 +1526,9 @@ Draw.loadPlugin(async function(ui) {
             }
             // Creates a mxChildChange object
             model.add(parent, cell, null);
+          } else {
+            // Creates a mxChildChange object
+            model.remove(cell);
           }
         },
         value: () => {
@@ -1607,39 +1602,80 @@ Draw.loadPlugin(async function(ui) {
     return a.id > b.id;
   };
 
-  let history, mergePromise;
-  function resetHistory() {
-    history = new History(context, setState, resolve);
-    mergePromise = Promise.resolve();
-  }
-  resetHistory();
+  // Fired when a local change happens
+  model.addListener(mxEvent.NOTIFY, function(sender, event) {
+    if (historyWrapper.transactionDepth === 0) {
+      console.log("NOTIFY:", event.properties.edit.changes);
+
+      const delta = {};
+      for (const change of event.properties.edit.changes) {
+        changeToDelta(change, delta);
+      }
+      if (Object.keys(delta).length > 0) {
+        const op = historyWrapper.history.new(delta, 
+          false // do NOT update mxGraphModel with the change (the change was already executed)
+        );
+        const serializedOp = op.serialize();
+        console.log("NEW LOCAL OP:", serializedOp);
+        controller.addInput("new_edit", "in", [serializedOp], controller.wallclockToSimtime());
+      }
+    }
+  });
 
-  function queuedMerge(serializedOp) {
-    mergePromise = mergePromise.then(() => {
-      return history.context.receiveOperation(serializedOp).then(op => {
-        try {
-          listenForEdits++;
+  class HistoryWrapper {
+    constructor(graph, context) {
+      this.graph = graph;
+      this.context = context;
+      this.transactionDepth = 0;
+    }
 
-          model.beginUpdate();
-          history.autoMerge(op);
-          model.endUpdate();
-        }
-        catch (e) {
-          e.op = op;
-          throw e;
-        }
-        finally {
-          listenForEdits--;
-        }
-        console.log("Merged ", op.id);
+    newEmptyDiagram() {
+      console.log("newEmptyDiagram")
+      this.history = new History(this.context, setState, resolve);
+      this.graph.model.clear(); // the resetting of the diagram will be recorded into the (blank) history
+      this.mergePromise = Promise.resolve();
+    }
+
+    // returns Promise
+    loadExistingDiagram(ops) {
+      console.log("loadExistingDiagram")
+      this.graph.setEnabled(false);
+      this.transactionDepth++; // prevent us from re-recording any changes
+      this.graph.model.clear();
+      this.history = new History(this.context, setState, resolve);
+      this.mergePromise = Promise.resolve();
+      for (const op of ops) {
+        this.queuedMerge(op);
+      }
+      return this.mergePromise.then(() => {
+        this.graph.setEnabled(true);
+        this.transactionDepth--;
       });
-    });
-  }
-  function leaveOnError() {
-    mergePromise.catch(err => {
-      console.error("Unexpected error merging operation", err.op.id, err);
-      controller.addInput("leave", "in", [], controller.wallclockToSimtime());
-    });
+    }
+
+    // returns Promise
+    queuedMerge(serializedOp) {
+      this.mergePromise = this.mergePromise.then(() => {
+        return this.context.receiveOperation(serializedOp).then(op => {
+          console.log("Merging ", op.id);
+          try {
+            this.transactionDepth++;
+
+            this.graph.model.beginUpdate();
+            this.history.autoMerge(op);
+            this.graph.model.endUpdate();
+          }
+          catch (e) {
+            e.op = op;
+            throw e;
+          }
+          finally {
+            this.transactionDepth--;
+          }
+        });
+      });
+      return this.mergePromise;
+    }
   }
 
   function getMyName() {
@@ -1651,23 +1687,17 @@ Draw.loadPlugin(async function(ui) {
   const selectionCounter = new Map();
   const userSelection = new Map();
 
+  // const handleMergeError = err => {
+  //   console.error("Unexpected error merging:", err);
+  //   controller.addInput("leave", "in", [], controller.wallclockToSimtime());
+  // }
+
   controller.addMyOwnOutputListener({
     'add': event => {
       // console.log("output event:", event);
       if (event.name === "ack_join") {
         const [ops] = event.parameters;
-        // console.log("Outevent ack_join", ops.length, "ops..");
-        try {
-          listenForEdits++;
-          model.clear();
-          resetHistory();
-          for (const op of ops) {
-            queuedMerge(op);
-          }
-          leaveOnError();
-        } finally {
-          listenForEdits--;
-        }
+        historyWrapper.loadExistingDiagram(ops);
       }
       else if (event.name === "ack_new_share") {
         // nothing needs to happen
@@ -1677,8 +1707,7 @@ Draw.loadPlugin(async function(ui) {
         const [sessionId, msg] = event.parameters;
         if (msg.type === "pub_edit") {
           const {op} = msg;
-          queuedMerge(op);
-          leaveOnError();
+          historyWrapper.queuedMerge(op);
         }
         else if (msg.type === "update_cursor") {
           const {userId, name, x, y} = msg;
@@ -1837,9 +1866,7 @@ Draw.loadPlugin(async function(ui) {
             parentId: change.parent.id,
             isVertex: change.parent.isVertex(),
             isEdge: change.parent.isEdge(),
-          } : {
-            parentId: null,
-          };
+          } : null;
         } else {
           // no previous parent -> cell was created:
           console.log("no previous parent");
@@ -1880,49 +1907,37 @@ Draw.loadPlugin(async function(ui) {
     }
   }
 
-  // Fired when a local change happens
-  let listenForEdits = 0;
-  model.addListener(mxEvent.NOTIFY, function(sender, event) {
-    if (listenForEdits === 0) {
-      console.log("NOTIFY:", event.properties.edit.changes);
-
-      const delta = {};
-      for (const change of event.properties.edit.changes) {
-        changeToDelta(change, delta);
-      }
-      if (Object.keys(delta).length > 0) {
-        const op = history.new(delta, 
-          false // do NOT update mxGraphModel with the change (the change was already executed)
-        );
-        const serializedOp = op.serialize();
-        console.log("OP:", serializedOp);
-        controller.addInput("new_edit", "in", [serializedOp], controller.wallclockToSimtime());
-      }
-    }
-  });
 
   // UI stuff
   uiState.install(ui);
 
-  document.addEventListener('keydown', e => {
-  //   console.log(e);
-    if (e.code === 'KeyC') {
-      console.log("KeyC pressed: Clear model and reset history");
-      resetHistory();
-      model.clear();
-    }
 
-    if (e.code === 'KeyH') {
-      const seq = history.getOpsSequence();
-      console.log(seq.map(op => op.serialize()));
-    }
+  const context = new Context(async id => {
+    throw new Error("Fetch is forbidden. ", id)
+  });
+  const historyWrapper = new HistoryWrapper(graph, context);
+  historyWrapper.newEmptyDiagram();
 
-  //   if (e.code === 'KeyU') {
-  //     const u = window.prompt("Choose a user name:", myName);
-  //     if (u) {
-  //       myName = u;
-  //     }
-  //   }
+
+  // For debugging
+  document.addEventListener('keydown', e => {
+  //   console.log(e);
+    // if (e.code === 'KeyC') {
+    //   console.log("KeyC pressed: Clear model and reset history");
+    //   historyWrapper.reset();
+    // }
+
+    // if (e.code === 'KeyH') {
+    //   const seq = historyWrapper.history.getOpsSequence();
+    //   console.log(seq.map(op => op.serialize()));
+    // }
+
+    // if (e.code === 'KeyU') {
+    //   const u = window.prompt("Choose a user name:", myName);
+    //   if (u) {
+    //     myName = u;
+    //   }
+    // }
   });
 });
 },{"../../../../../lib/versioning/DragHandler.js":16,"../../../../../lib/versioning/GhostOverlays.js":17,"../../../../../lib/versioning/History.js":18,"../../../../../lib/versioning/UserColors.js":19}]},{},[20]);

+ 110 - 95
src/main/webapp/plugins/cdf/versioning.js

@@ -61,7 +61,7 @@ Draw.loadPlugin(async function(ui) {
         const buttonShare = menu.addItem("Share this diagram",
           null, // image
           () => {
-            const ops = history.getOpsSequence();
+            const ops = historyWrapper.history.getOpsSequence();
             const serialized = ops.map(op => op.serialize());
             controller.addInput("new_share", "in", [serialized], controller.wallclockToSimtime());
           }, // callback
@@ -181,10 +181,6 @@ Draw.loadPlugin(async function(ui) {
 
   const uiState = new UIState();
 
-  const context = new Context(async id => {
-    throw new Error("Fetch is forbidden. ", id)
-  });
-
   const codec = new mxCodec();
   const xmlSerializer = new XMLSerializer();
   const xmlParser = new DOMParser();
@@ -238,12 +234,8 @@ Draw.loadPlugin(async function(ui) {
           model.setStyle(cell, style);
         },
         parent: () => {
-          const {parentId} = value;
-          if (parentId === null) {
-            // Creates a mxChildChange object
-            model.remove(cell);
-          }
-          else {
+          if (value) {
+            const {parentId} = value;
             let parent = getCell(parentId);
             if (parent === undefined) {
               console.log("creating non-existing parent");
@@ -252,6 +244,9 @@ Draw.loadPlugin(async function(ui) {
             }
             // Creates a mxChildChange object
             model.add(parent, cell, null);
+          } else {
+            // Creates a mxChildChange object
+            model.remove(cell);
           }
         },
         value: () => {
@@ -325,39 +320,80 @@ Draw.loadPlugin(async function(ui) {
     return a.id > b.id;
   };
 
-  let history, mergePromise;
-  function resetHistory() {
-    history = new History(context, setState, resolve);
-    mergePromise = Promise.resolve();
-  }
-  resetHistory();
+  // Fired when a local change happens
+  model.addListener(mxEvent.NOTIFY, function(sender, event) {
+    if (historyWrapper.transactionDepth === 0) {
+      console.log("NOTIFY:", event.properties.edit.changes);
+
+      const delta = {};
+      for (const change of event.properties.edit.changes) {
+        changeToDelta(change, delta);
+      }
+      if (Object.keys(delta).length > 0) {
+        const op = historyWrapper.history.new(delta, 
+          false // do NOT update mxGraphModel with the change (the change was already executed)
+        );
+        const serializedOp = op.serialize();
+        console.log("NEW LOCAL OP:", serializedOp);
+        controller.addInput("new_edit", "in", [serializedOp], controller.wallclockToSimtime());
+      }
+    }
+  });
 
-  function queuedMerge(serializedOp) {
-    mergePromise = mergePromise.then(() => {
-      return history.context.receiveOperation(serializedOp).then(op => {
-        try {
-          listenForEdits++;
+  class HistoryWrapper {
+    constructor(graph, context) {
+      this.graph = graph;
+      this.context = context;
+      this.transactionDepth = 0;
+    }
 
-          model.beginUpdate();
-          history.autoMerge(op);
-          model.endUpdate();
-        }
-        catch (e) {
-          e.op = op;
-          throw e;
-        }
-        finally {
-          listenForEdits--;
-        }
-        console.log("Merged ", op.id);
+    newEmptyDiagram() {
+      console.log("newEmptyDiagram")
+      this.history = new History(this.context, setState, resolve);
+      this.graph.model.clear(); // the resetting of the diagram will be recorded into the (blank) history
+      this.mergePromise = Promise.resolve();
+    }
+
+    // returns Promise
+    loadExistingDiagram(ops) {
+      console.log("loadExistingDiagram")
+      this.graph.setEnabled(false);
+      this.transactionDepth++; // prevent us from re-recording any changes
+      this.graph.model.clear();
+      this.history = new History(this.context, setState, resolve);
+      this.mergePromise = Promise.resolve();
+      for (const op of ops) {
+        this.queuedMerge(op);
+      }
+      return this.mergePromise.then(() => {
+        this.graph.setEnabled(true);
+        this.transactionDepth--;
       });
-    });
-  }
-  function leaveOnError() {
-    mergePromise.catch(err => {
-      console.error("Unexpected error merging operation", err.op.id, err);
-      controller.addInput("leave", "in", [], controller.wallclockToSimtime());
-    });
+    }
+
+    // returns Promise
+    queuedMerge(serializedOp) {
+      this.mergePromise = this.mergePromise.then(() => {
+        return this.context.receiveOperation(serializedOp).then(op => {
+          console.log("Merging ", op.id);
+          try {
+            this.transactionDepth++;
+
+            this.graph.model.beginUpdate();
+            this.history.autoMerge(op);
+            this.graph.model.endUpdate();
+          }
+          catch (e) {
+            e.op = op;
+            throw e;
+          }
+          finally {
+            this.transactionDepth--;
+          }
+        });
+      });
+      return this.mergePromise;
+    }
   }
 
   function getMyName() {
@@ -369,23 +405,17 @@ Draw.loadPlugin(async function(ui) {
   const selectionCounter = new Map();
   const userSelection = new Map();
 
+  // const handleMergeError = err => {
+  //   console.error("Unexpected error merging:", err);
+  //   controller.addInput("leave", "in", [], controller.wallclockToSimtime());
+  // }
+
   controller.addMyOwnOutputListener({
     'add': event => {
       // console.log("output event:", event);
       if (event.name === "ack_join") {
         const [ops] = event.parameters;
-        // console.log("Outevent ack_join", ops.length, "ops..");
-        try {
-          listenForEdits++;
-          model.clear();
-          resetHistory();
-          for (const op of ops) {
-            queuedMerge(op);
-          }
-          leaveOnError();
-        } finally {
-          listenForEdits--;
-        }
+        historyWrapper.loadExistingDiagram(ops);
       }
       else if (event.name === "ack_new_share") {
         // nothing needs to happen
@@ -395,8 +425,7 @@ Draw.loadPlugin(async function(ui) {
         const [sessionId, msg] = event.parameters;
         if (msg.type === "pub_edit") {
           const {op} = msg;
-          queuedMerge(op);
-          leaveOnError();
+          historyWrapper.queuedMerge(op);
         }
         else if (msg.type === "update_cursor") {
           const {userId, name, x, y} = msg;
@@ -555,9 +584,7 @@ Draw.loadPlugin(async function(ui) {
             parentId: change.parent.id,
             isVertex: change.parent.isVertex(),
             isEdge: change.parent.isEdge(),
-          } : {
-            parentId: null,
-          };
+          } : null;
         } else {
           // no previous parent -> cell was created:
           console.log("no previous parent");
@@ -598,48 +625,36 @@ Draw.loadPlugin(async function(ui) {
     }
   }
 
-  // Fired when a local change happens
-  let listenForEdits = 0;
-  model.addListener(mxEvent.NOTIFY, function(sender, event) {
-    if (listenForEdits === 0) {
-      console.log("NOTIFY:", event.properties.edit.changes);
-
-      const delta = {};
-      for (const change of event.properties.edit.changes) {
-        changeToDelta(change, delta);
-      }
-      if (Object.keys(delta).length > 0) {
-        const op = history.new(delta, 
-          false // do NOT update mxGraphModel with the change (the change was already executed)
-        );
-        const serializedOp = op.serialize();
-        console.log("OP:", serializedOp);
-        controller.addInput("new_edit", "in", [serializedOp], controller.wallclockToSimtime());
-      }
-    }
-  });
 
   // UI stuff
   uiState.install(ui);
 
-  document.addEventListener('keydown', e => {
-  //   console.log(e);
-    if (e.code === 'KeyC') {
-      console.log("KeyC pressed: Clear model and reset history");
-      resetHistory();
-      model.clear();
-    }
 
-    if (e.code === 'KeyH') {
-      const seq = history.getOpsSequence();
-      console.log(seq.map(op => op.serialize()));
-    }
+  const context = new Context(async id => {
+    throw new Error("Fetch is forbidden. ", id)
+  });
+  const historyWrapper = new HistoryWrapper(graph, context);
+  historyWrapper.newEmptyDiagram();
+
 
-  //   if (e.code === 'KeyU') {
-  //     const u = window.prompt("Choose a user name:", myName);
-  //     if (u) {
-  //       myName = u;
-  //     }
-  //   }
+  // For debugging
+  document.addEventListener('keydown', e => {
+  //   console.log(e);
+    // if (e.code === 'KeyC') {
+    //   console.log("KeyC pressed: Clear model and reset history");
+    //   historyWrapper.reset();
+    // }
+
+    // if (e.code === 'KeyH') {
+    //   const seq = historyWrapper.history.getOpsSequence();
+    //   console.log(seq.map(op => op.serialize()));
+    // }
+
+    // if (e.code === 'KeyU') {
+    //   const u = window.prompt("Choose a user name:", myName);
+    //   if (u) {
+    //     myName = u;
+    //   }
+    // }
   });
 });