Add undo/redo.

Some bugs in undoing function argument changes.
This commit is contained in:
Neil Fraser
2016-03-14 23:14:05 -07:00
parent 8fb1178ed8
commit 72bb08a4ec
6 changed files with 82 additions and 48 deletions

View File

@@ -123,18 +123,7 @@ Blockly.Blocks['lists_create_with'] = {
this.updateShape_();
// Reconnect any child blocks.
for (var i = 0; i < this.itemCount_; i++) {
var connectionChild = connections[i];
if (connectionChild) {
var parent = connectionChild.targetBlock();
var connectionParent = this.getInput('ADD' + i).connection;
if (connectionParent.targetConnection != connectionChild &&
(!parent || parent == this)) {
if (connectionParent.targetConnection) {
connectionParent.disconnect();
}
connectionParent.connect(connectionChild);
}
}
Blockly.Mutator.reconnect(connections[i], this, 'ADD' + i);
}
},
/**

View File

@@ -152,16 +152,10 @@ Blockly.Blocks['controls_if'] = {
this.updateShape_();
// Reconnect any child blocks.
for (var i = 1; i <= this.elseifCount_; i++) {
if (valueConnections[i]) {
this.getInput('IF' + i).connection.connect(valueConnections[i]);
}
if (statementConnections[i]) {
this.getInput('DO' + i).connection.connect(statementConnections[i]);
}
}
if (elseStatementConnection) {
this.getInput('ELSE').connection.connect(elseStatementConnection);
Blockly.Mutator.reconnect(valueConnections[i], this, 'IF' + i);
Blockly.Mutator.reconnect(statementConnections[i], this, 'DO' + i);
}
Blockly.Mutator.reconnect(elseStatementConnection, this, 'ELSE');
},
/**
* Store pointers to any connected child blocks.

View File

@@ -141,9 +141,7 @@ Blockly.Blocks['text_join'] = {
this.updateShape_();
// Reconnect any child blocks.
for (var i = 0; i < this.itemCount_; i++) {
if (connections[i]) {
this.getInput('ADD' + i).connection.connect(connections[i]);
}
Blockly.Mutator.reconnect(connections[i], this, 'ADD' + i);
}
},
/**
@@ -168,29 +166,27 @@ Blockly.Blocks['text_join'] = {
* @this Blockly.Block
*/
updateShape_: function() {
// Delete everything.
if (this.getInput('EMPTY')) {
if (this.itemCount_ && this.getInput('EMPTY')) {
this.removeInput('EMPTY');
} else {
var i = 0;
while (this.getInput('ADD' + i)) {
this.removeInput('ADD' + i);
i++;
}
}
// Rebuild block.
if (this.itemCount_ == 0) {
} else if (!this.itemCount_ && !this.getInput('EMPTY')) {
this.appendDummyInput('EMPTY')
.appendField(this.newQuote_(true))
.appendField(this.newQuote_(false));
} else {
for (var i = 0; i < this.itemCount_; i++) {
}
// Add new inputs.
for (var i = 0; i < this.itemCount_; i++) {
if (!this.getInput('ADD' + i)) {
var input = this.appendValueInput('ADD' + i);
if (i == 0) {
input.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH);
}
}
}
// Remove deleted inputs.
while (this.getInput('ADD' + i)) {
this.removeInput('ADD' + i);
i++;
}
},
newQuote_: Blockly.Blocks['text'].newQuote_
};

View File

@@ -97,7 +97,7 @@ Blockly.Events.fire = function(event) {
* @private
*/
Blockly.Events.fireNow_ = function() {
var queue = Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_);
var queue = Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_, true);
Blockly.Events.FIRE_QUEUE_.length = 0;
for (var i = 0, event; event = queue[i]; i++) {
var workspace = Blockly.Workspace.getById(event.workspaceId);
@@ -110,10 +110,15 @@ Blockly.Events.fireNow_ = function() {
/**
* Filter the queued events and merge duplicates.
* @param {!Array.<!Blockly.Events.Abstract>} queueIn Array of events.
* @param {boolean} forward True if forward (redo), false if backward (undo).
* @return {!Array.<!Blockly.Events.Abstract>} Array of filtered events.
*/
Blockly.Events.filter = function(queueIn) {
Blockly.Events.filter = function(queueIn, forward) {
var queue = goog.array.clone(queueIn);
if (!forward) {
// Undo is merged in reverse order.
queue.reverse();
}
// Merge duplicates. O(n^2), but n should be very small.
for (var i = 0, event1; event1 = queue[i]; i++) {
for (var j = i + 1, event2; event2 = queue[j]; j++) {
@@ -144,6 +149,10 @@ Blockly.Events.filter = function(queueIn) {
queue.splice(i, 1);
}
}
if (!forward) {
// Restore undo order.
queue.reverse();
}
// Move mutation events to the top of the queue.
// Intentionally skip first event.
for (var i = 1, event; event = queue[i]; i++) {
@@ -251,6 +260,8 @@ Blockly.Events.Create.prototype.run = function(forward) {
var block = Blockly.Block.getById(this.blockId);
if (block) {
block.dispose(false, true);
} else {
console.warn("Can't delete non-existant block: " + this.blockId);
}
}
};
@@ -285,6 +296,8 @@ Blockly.Events.Delete.prototype.run = function(forward) {
var block = Blockly.Block.getById(this.blockId);
if (block) {
block.dispose(false, true);
} else {
console.warn("Can't delete non-existant block: " + this.blockId);
}
} else {
var workspace = Blockly.Workspace.getById(this.workspaceId);
@@ -334,6 +347,7 @@ Blockly.Events.Change.prototype.isNull = function() {
Blockly.Events.Change.prototype.run = function(forward) {
var block = Blockly.Block.getById(this.blockId);
if (!block) {
console.warn("Can't change non-existant block: " + this.blockId);
return;
}
var value = forward ? this.newValue : this.oldValue;
@@ -375,6 +389,8 @@ Blockly.Events.Change.prototype.run = function(forward) {
Blockly.Events.fire(new Blockly.Events.Change(
block, 'mutation', null, oldMutation, value));
break;
default:
console.warn("Unknown change type: " + this.element);
}
};
@@ -448,6 +464,7 @@ Blockly.Events.Move.prototype.isNull = function() {
Blockly.Events.Move.prototype.run = function(forward) {
var block = Blockly.Block.getById(this.blockId);
if (!block) {
console.warn("Can't move non-existant block: " + this.blockId);
return;
}
var parentId = forward ? this.newParentId : this.oldParentId;
@@ -457,6 +474,7 @@ Blockly.Events.Move.prototype.run = function(forward) {
if (parentId) {
parentBlock = Blockly.Block.getById(parentId);
if (!parentBlock) {
console.warn("Can't connect to non-existant block: " + parentId);
return;
}
}
@@ -473,14 +491,14 @@ Blockly.Events.Move.prototype.run = function(forward) {
var input = parentBlock.getInput(inputName);
if (input) {
parentConnection = input.connection;
} else {
console.warn("Can't connect to non-existant input: " + inputName);
}
} else if (blockConnection.type == Blockly.PREVIOUS_STATEMENT) {
parentConnection = parentBlock.nextConnection;
}
if (parentConnection) {
blockConnection.connect(parentConnection);
} else {
console.warn("Can't connect to non-existant input: " + inputName);
}
}
};

View File

@@ -340,3 +340,35 @@ Blockly.Mutator.prototype.dispose = function() {
this.block_.mutator = null;
Blockly.Icon.prototype.dispose.call(this);
};
/**
* Reconnect an block to a mutated input.
* @param {Blockly.Connection} connectionChild Connection on child block.
* @param {!Blockly.Block} block Parent block.
* @param {string} inputName Name of input on parent block.
*/
Blockly.Mutator.reconnect = function(connectionChild, block, inputName) {
if (!connectionChild) {
return;
}
var connectionParent = block.getInput(inputName).connection;
var currentParent = connectionChild.targetBlock();
if ((!currentParent || currentParent == block) &&
connectionParent.targetConnection != connectionChild) {
if (connectionParent.targetConnection) {
// There's already something connected here. Get rid of it.
connectionParent.disconnect();
}
connectionParent.connect(connectionChild);
}
};
// Export symbols that would otherwise be renamed by Closure compiler.
if (!goog.global['Blockly']) {
goog.global['Blockly'] = {};
}
if (!goog.global['Blockly']['Mutator']) {
goog.global['Blockly']['Mutator'] = {};
}
goog.global['Blockly']['Mutator']['reconnect'] = Blockly.Mutator.reconnect;

View File

@@ -209,22 +209,27 @@ Blockly.Workspace.prototype.remainingCapacity = function() {
* @param {boolean} redo False if undo, true if redo.
*/
Blockly.Workspace.prototype.undo = function(redo) {
var sourceStack = redo ? this.redoStack_ : this.undoStack_;
var event = sourceStack.pop();
var inputStack = redo ? this.redoStack_ : this.undoStack_;
var outputStack = redo ? this.undoStack_ : this.redoStack_;
var event = inputStack.pop();
if (!event) {
return;
}
var events = [event];
// Do another undo/redo if the next one is of the same group.
while (sourceStack.length && event.group &&
event.group == sourceStack[sourceStack.length - 1].group) {
events.push(sourceStack.pop());
while (inputStack.length && event.group &&
event.group == inputStack[inputStack.length - 1].group) {
events.push(inputStack.pop());
}
events = Blockly.Events.filter(events);
// Push these popped events on the opposite stack.
for (var i = 0, event; event = events[i]; i++) {
outputStack.push(event);
}
events = Blockly.Events.filter(events, redo);
Blockly.Events.recordUndo = false;
for (var i = 0, event; event = events[i]; i++) {
console.log(event);
event.run(redo);
(redo ? this.undoStack_ : this.redoStack_).push(event);
}
Blockly.Events.recordUndo = true;
};