diff --git a/core/blockly.js b/core/blockly.js
index 85d311e6d..365255099 100644
--- a/core/blockly.js
+++ b/core/blockly.js
@@ -222,7 +222,8 @@ Blockly.onMouseMove_ = function(e) {
* @private
*/
Blockly.onKeyDown_ = function(e) {
- if (Blockly.isTargetInput_(e)) {
+ if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) {
+ // No key actions on readonly workspaces.
// When focused on an HTML text input widget, don't trap any keys.
return;
}
diff --git a/core/events.js b/core/events.js
index 8aafdfdb1..f890d85cb 100644
--- a/core/events.js
+++ b/core/events.js
@@ -239,6 +239,37 @@ Blockly.Events.getDescendantIds_ = function(block) {
return ids;
};
+/**
+ * Decode the JSON into an event.
+ * @param {!Object} json JSON representation.
+ * @param {!Blockly.Workspace} workspace Target workspace for event.
+ */
+Blockly.Events.fromJson = function(json, workspace) {
+ var event;
+ switch (json.type) {
+ case Blockly.Events.CREATE:
+ event = new Blockly.Events.Create(null);
+ break;
+ case Blockly.Events.DELETE:
+ event = new Blockly.Events.Delete(null);
+ break;
+ case Blockly.Events.CHANGE:
+ event = new Blockly.Events.Change(null);
+ break;
+ case Blockly.Events.MOVE:
+ event = new Blockly.Events.Move(null);
+ break;
+ case Blockly.Events.UI:
+ event = new Blockly.Events.Ui(null);
+ break;
+ default:
+ throw 'Unknown event type.'
+ }
+ event.fromJson(json);
+ event.workspaceId = workspace.id;
+ return event;
+};
+
/**
* Abstract class for an event.
* @param {Blockly.Block} block The block.
@@ -260,8 +291,7 @@ Blockly.Events.Abstract = function(block) {
Blockly.Events.Abstract.prototype.toJson = function() {
var json = {
'type': this.type,
- 'blockId': this.blockId,
- 'workspaceId': this.workspaceId
+ 'blockId': this.blockId
};
if (this.group) {
json['group'] = this.group;
@@ -269,6 +299,15 @@ Blockly.Events.Abstract.prototype.toJson = function() {
return json;
};
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Abstract.prototype.fromJson = function(json) {
+ this.blockId = json['blockId'];
+ this.group = json['group'];
+};
+
/**
* Does this event record any change of state?
* @return {boolean} True if null, false if something changed.
@@ -287,11 +326,14 @@ Blockly.Events.Abstract.prototype.run = function(forward) {
/**
* Class for a block creation event.
- * @param {!Blockly.Block} block The created block.
+ * @param {Blockly.Block} block The created block. Null for a blank event.
* @extends {Blockly.Events.Abstract}
* @constructor
*/
Blockly.Events.Create = function(block) {
+ if (!block) {
+ return; // Blank event to be populated by fromJson.
+ }
Blockly.Events.Create.superClass_.constructor.call(this, block);
this.xml = Blockly.Xml.blockToDomWithXY(block);
this.ids = Blockly.Events.getDescendantIds_(block);
@@ -315,13 +357,23 @@ Blockly.Events.Create.prototype.toJson = function() {
return json;
};
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Create.prototype.fromJson = function(json) {
+ Blockly.Events.Create.superClass_.fromJson.call(this, json);
+ this.xml = Blockly.Xml.textToDom('' + json['xml'] + '').firstChild;
+ this.ids = json['ids'];
+};
+
/**
* Run a creation event.
* @param {boolean} forward True if run forward, false if run backward (undo).
*/
Blockly.Events.Create.prototype.run = function(forward) {
+ var workspace = Blockly.Workspace.getById(this.workspaceId);
if (forward) {
- var workspace = Blockly.Workspace.getById(this.workspaceId);
var xml = goog.dom.createDom('xml');
xml.appendChild(this.xml);
Blockly.Xml.domToWorkspace(xml, workspace);
@@ -340,11 +392,14 @@ Blockly.Events.Create.prototype.run = function(forward) {
/**
* Class for a block deletion event.
- * @param {!Blockly.Block} block The deleted block.
+ * @param {Blockly.Block} block The deleted block. Null for a blank event.
* @extends {Blockly.Events.Abstract}
* @constructor
*/
Blockly.Events.Delete = function(block) {
+ if (!block) {
+ return; // Blank event to be populated by fromJson.
+ }
if (block.getParent()) {
throw 'Connected blocks cannot be deleted.';
}
@@ -370,6 +425,15 @@ Blockly.Events.Delete.prototype.toJson = function() {
return json;
};
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Delete.prototype.fromJson = function(json) {
+ Blockly.Events.Delete.superClass_.fromJson.call(this, json);
+ this.ids = json['ids'];
+};
+
/**
* Run a deletion event.
* @param {boolean} forward True if run forward, false if run backward (undo).
@@ -395,7 +459,7 @@ Blockly.Events.Delete.prototype.run = function(forward) {
/**
* Class for a block change event.
- * @param {!Blockly.Block} block The changed block.
+ * @param {Blockly.Block} block The changed block. Null for a blank event.
* @param {string} element One of 'field', 'comment', 'disabled', etc.
* @param {?string} name Name of input or field affected, or null.
* @param {string} oldValue Previous value of element.
@@ -404,6 +468,9 @@ Blockly.Events.Delete.prototype.run = function(forward) {
* @constructor
*/
Blockly.Events.Change = function(block, element, name, oldValue, newValue) {
+ if (!block) {
+ return; // Blank event to be populated by fromJson.
+ }
Blockly.Events.Change.superClass_.constructor.call(this, block);
this.element = element;
this.name = name;
@@ -432,6 +499,17 @@ Blockly.Events.Change.prototype.toJson = function() {
return json;
};
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Change.prototype.fromJson = function(json) {
+ Blockly.Events.Change.superClass_.fromJson.call(this, json);
+ this.element = json['element'];
+ this.name = json['name'];
+ this.newValue = json['newValue'];
+};
+
/**
* Does this event record any change of state?
* @return {boolean} True if something changed.
@@ -498,11 +576,14 @@ Blockly.Events.Change.prototype.run = function(forward) {
/**
* Class for a block move event. Created before the move.
- * @param {!Blockly.Block} block The moved block.
+ * @param {Blockly.Block} block The moved block. Null for a blank event.
* @extends {Blockly.Events.Abstract}
* @constructor
*/
Blockly.Events.Move = function(block) {
+ if (!block) {
+ return; // Blank event to be populated by fromJson.
+ }
Blockly.Events.Move.superClass_.constructor.call(this, block);
var location = this.currentLocation_();
this.oldParentId = location.parentId;
@@ -536,6 +617,21 @@ Blockly.Events.Move.prototype.toJson = function() {
return json;
};
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Move.prototype.fromJson = function(json) {
+ Blockly.Events.Move.superClass_.fromJson.call(this, json);
+ this.newParentId = json['newParentId'];
+ this.newInputName = json['newInputName'];
+ if (json['newCoordinate']) {
+ var xy = json['newCoordinate'].split(',');
+ this.newCoordinate =
+ new goog.math.Coordinate(parseFloat(xy[0]), parseFloat(xy[1]));
+ }
+};
+
/**
* Record the block's new location. Called after the move.
*/
@@ -662,3 +758,13 @@ Blockly.Events.Ui.prototype.toJson = function() {
}
return json;
};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Ui.prototype.fromJson = function(json) {
+ Blockly.Events.Ui.superClass_.fromJson.call(this, json);
+ this.element = json['element'];
+ this.newValue = json['newValue'];
+};
diff --git a/demos/index.html b/demos/index.html
index 3fe46a780..f9f9833e7 100644
--- a/demos/index.html
+++ b/demos/index.html
@@ -160,6 +160,18 @@
+
+
+
+
+
+ |
+
+
+ Two Blockly instances connected as master-slave.
+ |
+
+
|
diff --git a/demos/mirror/icon.png b/demos/mirror/icon.png
new file mode 100644
index 000000000..45e2a9a29
Binary files /dev/null and b/demos/mirror/icon.png differ
diff --git a/demos/mirror/index.html b/demos/mirror/index.html
new file mode 100644
index 000000000..8721d7354
--- /dev/null
+++ b/demos/mirror/index.html
@@ -0,0 +1,76 @@
+
+
+
+
+ Blockly Demo: Mirrored Blockly
+
+
+
+
+
+
+
+
+ This is a simple demo of a master Blockly that controls a slave Blockly.
+ Open the JavaScript console to see the event passing.
+
+ → More info on events…
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
|