undoManager between users

I am trying to send the undo/redo from one user to another user via node.js for real-time editing purposes. This way I can make it so changes that are undone/redone by one user are undone/redone by another user. Due to the recursiveness of the objects, I can’t pass the entire transaction to undo/redo, so I loop through the changes and pass those instead. However, I have two problems:

  1. If user 1 makes changes to two or more nodes (i.e. deletes the nodes) and undoes/redoes the changes, the passed changes to user 2 will only undo/redo one of the changes (i.e. deletions) instead of the two or more.
  2. If user 1 undoes/redoes a moved node, the moved node is not affected for user 2.

Every other interaction works flawlessly. Essentially, this is the code I have so far when the model is changed:

if (fileDiagram.undoManager.isUndoingRedoing) {
     var changes = [];
     if (e.oldValue == 'Undo' && fileDiagram.undoManager.transactionToUndo !== null) {
         fileDiagram.undoManager.transactionToUndo.changes.each(function(change) { changes.push(change.change); });
         socket.emit('change', {origin: 'diagram-undo', transaction: changes});
     } else if (e.oldValue == 'Redo' && fileDiagram.undoManager.transactionToRedo !== null) {
         fileDiagram.undoManager.transactionToRedo.changes.each(function(change) { changes.push(change.change); });
         socket.emit('change', {origin: 'diagram-redo', transaction: changes});

Then for user 2, I have the following which basically checks for an undo or redo event and then sets the transaction to undo/redo and then undoes/redoes the transaction:

if (data.origin == 'diagram-undo') {
    fileDiagram.undoManager.transactionToUndo = data.transaction;
} else if (data.origin == 'diagram-redo') {
    fileDiagram.undoManager.transactionToRedo = data.transaction;

Thanks in advance for any help you can offer to my two list items above.

You’ll need to debug why you are only sending two objects in the changes Array, covering only one node deletion and one node re-insertion. I don’t understand your design. There is supposed to be one of your change objects for each Part insertion and for each Part removal, yes? Why do you need to send anything? Depending on your design, you just need to pass a number for how many times to “undo” or to “redo”.

The reason why I need to send something is because many changes could have been made before user 2 joins the document. If I just send a number then it will only be able to undo the changes since they joined the document. I want to be able to undo changes that occurred before joining the document.

Maybe a picture will help with my explanation:

In the image above, if the right user deletes fields test6 and test4, both test6 and test4 are removed from the left user. Then, if the right user undoes their last action, only test6 is undone on the left user. The same occurs if deleting PERSON and RENTAL and undoing the changes, only one comes back.

Therefore, my first question is how do I determine the number of “transactions” that were undone in a single undo and then grab those transactions, so I can pass those transactions to the second user?

My second question is do you have any idea how I could pass an undone transaction for nodes that are moved (i.e. the PERSON node)?

Let me know if this clarifies things.

An undo (i.e.UndoManager.undo call) only undoes one Transaction. (Or none if it couldn’t undo at all.)

You can look at the Transaction.changes to see all of the ChangedEvents that were undone or redone. Or committed, the first time. Note how there are also ChangedEvent.undo and ChangedEvent.redo methods.

When a Node is moved there is a ChangedEvent describing the property change, including not only the moved node but also the old and new values for the named property. But please note that there are changes not only for the Node.location, but also the Node.position. And if there is a TwoWay Binding for either of those properties, there will be a changed event for that property change on the node data object in the model.

Implementing a demo of cooperative simultaneous editing is on our list of things to do some day.

Okay, so after a lot of testing, it appears to be undoing the last transaction completed on the second user’s diagram. For example, as I pass the deletes, they are passed one by one so they are undone one by one.

To fix this, I will need to add the same transaction that happened on the first user’s diagram to the second user’s diagram. Do you know if there is a way to programmatically add an existing transaction to the undoManager history that originates from the first user? For example, I want to make user 2’s transactionToUndo the same as user 1’s transaction to undo then undo it.

UPDATE: Oh and I am glad you are planning on implementing simultaneous editing in the future! I am just so close to having it done myself. This is the last step.

Yes, just conduct a transaction. However, I don’t know about copying someone else’s Transaction instances. And what would you want to do if the user has already done an undo?

In previous work I found it more natural for an undo to only undo the changes that the user made, not all changes globally. But your design is plausible. It might be easier to broadcast the new state to everyone, rather than trying to figure out incremental changes that are appropriate for each user.

I think I found a solution to broadcast undoing multiple edits as long as the second user was present during those edits. What I think I am going to do now is when the undo has exhausted the second user’s undo stack, is to just broadcast the new state as you mentioned. The problem with broadcasting the new state is that it eliminates the user’s ability to undo/redo their own edits.