Skip to content

Commit

Permalink
fix: create and delete events, and the trashcan (google#5425)
Browse files Browse the repository at this point in the history
* fix: create and delete events with JSON serialization

* fix: trashcan with JSON serialization

* fix: build

* fix: tests

* fix: PR comments

* fix: types

* fix: tests
  • Loading branch information
BeksOmega authored and alschmiedt committed Sep 20, 2021
1 parent 17f9f4f commit 84514ef
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 201 deletions.
21 changes: 12 additions & 9 deletions core/events/events_block_create.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ const Block = goog.requireType('Blockly.Block');
const BlockBase = goog.require('Blockly.Events.BlockBase');
const Events = goog.require('Blockly.Events');
const Xml = goog.require('Blockly.Xml');
const blocks = goog.require('Blockly.serialization.blocks');
const object = goog.require('Blockly.utils.object');
const registry = goog.require('Blockly.registry');
const xml = goog.require('Blockly.utils.xml');


/**
Expand All @@ -40,12 +40,15 @@ const BlockCreate = function(opt_block) {
this.recordUndo = false;
}

if (opt_block.workspace.rendered) {
this.xml = Xml.blockToDomWithXY(opt_block);
} else {
this.xml = Xml.blockToDom(opt_block);
}
this.xml = Xml.blockToDomWithXY(opt_block);
this.ids = Events.getDescendantIds(opt_block);

/**
* JSON representation of the block that was just created.
* @type {!blocks.State}
*/
this.json = /** @type {!blocks.State} */ (blocks.save(
opt_block, {addCoordinates: true}));
};
object.inherits(BlockCreate, BlockBase);

Expand All @@ -63,6 +66,7 @@ BlockCreate.prototype.toJson = function() {
const json = BlockCreate.superClass_.toJson.call(this);
json['xml'] = Xml.domToText(this.xml);
json['ids'] = this.ids;
json['json'] = this.json;
if (!this.recordUndo) {
json['recordUndo'] = this.recordUndo;
}
Expand All @@ -77,6 +81,7 @@ BlockCreate.prototype.fromJson = function(json) {
BlockCreate.superClass_.fromJson.call(this, json);
this.xml = Xml.textToDom(json['xml']);
this.ids = json['ids'];
this.json = /** @type {!blocks.State} */ (json['json']);
if (json['recordUndo'] !== undefined) {
this.recordUndo = json['recordUndo'];
}
Expand All @@ -89,9 +94,7 @@ BlockCreate.prototype.fromJson = function(json) {
BlockCreate.prototype.run = function(forward) {
const workspace = this.getEventWorkspace_();
if (forward) {
const xmlEl = xml.createElement('xml');
xmlEl.appendChild(this.xml);
Xml.domToWorkspace(xmlEl, workspace);
blocks.load(this.json, workspace);
} else {
for (let i = 0; i < this.ids.length; i++) {
const id = this.ids[i];
Expand Down
30 changes: 21 additions & 9 deletions core/events/events_block_delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ const Block = goog.requireType('Blockly.Block');
const BlockBase = goog.require('Blockly.Events.BlockBase');
const Events = goog.require('Blockly.Events');
const Xml = goog.require('Blockly.Xml');
const blocks = goog.require('Blockly.serialization.blocks');
const object = goog.require('Blockly.utils.object');
const registry = goog.require('Blockly.registry');
const xml = goog.require('Blockly.utils.xml');


/**
Expand All @@ -43,12 +43,21 @@ const BlockDelete = function(opt_block) {
this.recordUndo = false;
}

if (opt_block.workspace.rendered) {
this.oldXml = Xml.blockToDomWithXY(opt_block);
} else {
this.oldXml = Xml.blockToDom(opt_block);
}
this.oldXml = Xml.blockToDomWithXY(opt_block);
this.ids = Events.getDescendantIds(opt_block);

/**
* Was the block that was just deleted a shadow?
* @type {boolean}
*/
this.wasShadow = opt_block.isShadow();

/**
* JSON representation of the block that was just deleted.
* @type {!blocks.State}
*/
this.oldJson = /** @type {!blocks.State} */ (blocks.save(
opt_block, {addCoordinates: true}));
};
object.inherits(BlockDelete, BlockBase);

Expand All @@ -66,6 +75,8 @@ BlockDelete.prototype.toJson = function() {
const json = BlockDelete.superClass_.toJson.call(this);
json['oldXml'] = Xml.domToText(this.oldXml);
json['ids'] = this.ids;
json['wasShadow'] = this.wasShadow;
json['oldJson'] = this.oldJson;
if (!this.recordUndo) {
json['recordUndo'] = this.recordUndo;
}
Expand All @@ -80,6 +91,9 @@ BlockDelete.prototype.fromJson = function(json) {
BlockDelete.superClass_.fromJson.call(this, json);
this.oldXml = Xml.textToDom(json['oldXml']);
this.ids = json['ids'];
this.wasShadow =
json['wasShadow'] || this.oldXml.tagName.toLowerCase() == 'shadow';
this.oldJson = /** @type {!blocks.State} */ (json['oldJson']);
if (json['recordUndo'] !== undefined) {
this.recordUndo = json['recordUndo'];
}
Expand All @@ -103,9 +117,7 @@ BlockDelete.prototype.run = function(forward) {
}
}
} else {
const xmlEl = xml.createElement('xml');
xmlEl.appendChild(this.oldXml);
Xml.domToWorkspace(xmlEl, workspace);
blocks.load(this.oldJson, workspace);
}
};

Expand Down
100 changes: 50 additions & 50 deletions core/trashcan.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const Size = goog.require('Blockly.utils.Size');
const Svg = goog.require('Blockly.utils.Svg');
/* eslint-disable-next-line no-unused-vars */
const WorkspaceSvg = goog.requireType('Blockly.WorkspaceSvg');
const Xml = goog.require('Blockly.Xml');
/* eslint-disable-next-line no-unused-vars */
const blocks = goog.requireType('Blockly.serialization.blocks');
const browserEvents = goog.require('Blockly.browserEvents');
const dom = goog.require('Blockly.utils.dom');
const internalConstants = goog.require('Blockly.internalConstants');
Expand Down Expand Up @@ -73,7 +74,7 @@ const Trashcan = function(workspace) {
this.id = 'trashcan';

/**
* A list of XML (stored as strings) representing blocks in the trashcan.
* A list of JSON (stored as strings) representing blocks in the trashcan.
* @type {!Array<string>}
* @private
*/
Expand Down Expand Up @@ -393,8 +394,10 @@ Trashcan.prototype.openFlyout = function() {
if (this.contentsIsOpen()) {
return;
}
const xml = this.contents_.map(Xml.textToDom);
this.flyout.show(xml);
const contents = this.contents_.map(function(string) {
return JSON.parse(string);
});
this.flyout.show(contents);
this.fireUiEvent_(true);
};

Expand Down Expand Up @@ -669,14 +672,12 @@ Trashcan.prototype.onDelete_ = function(event) {
if (this.workspace_.options.maxTrashcanContents <= 0) {
return;
}
// Must check that the tagName exists since oldXml can be a DocumentFragment.
if (event.type == Events.BLOCK_DELETE && event.oldXml.tagName &&
event.oldXml.tagName.toLowerCase() != 'shadow') {
const cleanedXML = this.cleanBlockXML_(event.oldXml);
if (this.contents_.indexOf(cleanedXML) != -1) {
if (event.type == Events.BLOCK_DELETE && !event.wasShadow) {
const cleanedJson = this.cleanBlockJson_(event.oldJson);
if (this.contents_.indexOf(cleanedJson) != -1) {
return;
}
this.contents_.unshift(cleanedXML);
this.contents_.unshift(cleanedJson);
while (this.contents_.length >
this.workspace_.options.maxTrashcanContents) {
this.contents_.pop();
Expand All @@ -687,52 +688,51 @@ Trashcan.prototype.onDelete_ = function(event) {
};

/**
* Converts XML representing a block into text that can be stored in the
* content array.
* @param {!Element} xml An XML tree defining the block and any
* connected child blocks.
* @return {string} Text representing the XML tree, cleaned of all unnecessary
* attributes.
* Converts JSON representing a block into text that can be stored in the
* content array.
* @param {!blocks.State} json A JSON representation of
* a block's state.
* @return {string} Text representing the JSON, cleaned of all unnecessary
* attributes.
* @private
*/
Trashcan.prototype.cleanBlockXML_ = function(xml) {
const xmlBlock = xml.cloneNode(true);
let node = xmlBlock;
while (node) {
// Things like text inside tags are still treated as nodes, but they
// don't have attributes (or the removeAttribute function) so we can
// skip removing attributes from them.
if (node.removeAttribute) {
node.removeAttribute('x');
node.removeAttribute('y');
node.removeAttribute('id');
node.removeAttribute('disabled');
if (node.nodeName == 'comment') { // Future proof just in case.
node.removeAttribute('h');
node.removeAttribute('w');
node.removeAttribute('pinned');
}
Trashcan.prototype.cleanBlockJson_ = function(json) {
// Create a deep copy.
json = /** @type {!blocks.State} */(JSON.parse(JSON.stringify(json)));

function cleanRec(json) {
if (!json) {
return;
}

// Try to go down the tree
let nextNode = node.firstChild || node.nextSibling;
// If we can't go down, try to go back up the tree.
if (!nextNode) {
nextNode = node.parentNode;
while (nextNode) {
// We are valid again!
if (nextNode.nextSibling) {
nextNode = nextNode.nextSibling;
break;
}
// Try going up again. If parentNode is null that means we have
// reached the top, and we will break out of both loops.
nextNode = nextNode.parentNode;
}
delete json['id'];
delete json['x'];
delete json['y'];
delete json['enabled'];

if (json['icons'] && json['icons']['comment']) {
const comment = json['icons']['comment'];
delete comment['height'];
delete comment['width'];
delete comment['pinned'];
}

const inputs = json['inputs'];
for (var name in inputs) {
const input = inputs[name];
cleanRec(input['block']);
cleanRec(input['shadow']);
}
if (json['next']) {
const next = json['next'];
cleanRec(next['block']);
cleanRec(next['shadow']);
}
node = nextNode;
}
return Xml.domToText(xmlBlock);

cleanRec(json);
json['kind'] = 'BLOCK';
return JSON.stringify(json);
};

exports = Trashcan;
6 changes: 3 additions & 3 deletions tests/deps.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions tests/deps.mocha.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 84514ef

Please sign in to comment.