Skip to content

Commit

Permalink
feat: add serialization of child blocks (google#5120)
Browse files Browse the repository at this point in the history
* Add tests for serializing connected blocks

* Add serialization of child blocks

* Add tests for not serializing children

* Add options for not serializing children

* Fix types

* Change addNextBlocks to default to true

* Cleanup

* Fix types
  • Loading branch information
BeksOmega authored and alschmiedt committed Sep 20, 2021
1 parent 8faa360 commit 56d3cb6
Show file tree
Hide file tree
Showing 3 changed files with 404 additions and 13 deletions.
125 changes: 113 additions & 12 deletions core/serialization/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,25 @@ goog.module.declareLegacyNamespace();

// eslint-disable-next-line no-unused-vars
const Block = goog.requireType('Blockly.Block');
// eslint-disable-next-line no-unused-vars
const Connection = goog.requireType('Blockly.Connection');
const Xml = goog.require('Blockly.Xml');
const inputTypes = goog.require('Blockly.inputTypes');


// TODO: Remove this once lint is fixed.
/* eslint-disable no-use-before-define */

/**
* Represents the state of a connection.
* @typedef {{
* shadow: (!State|undefined),
* block: (!State|undefined)
* }}
*/
var ConnectionState;
exports.ConnectionState = ConnectionState;

/**
* Represents the state of a given block.
* @typedef {{
Expand All @@ -35,7 +49,9 @@ const Block = goog.requireType('Blockly.Block');
* inline: (boolean|undefined),
* data: (string|undefined),
* extra-state: *,
* fields: (Object<string, *>|undefined),
* fields: (!Object<string, *>|undefined),
* inputs: (!Object<string, !ConnectionState>|undefined),
* next: (!ConnectionState|undefined)
* }}
*/
var State;
Expand All @@ -44,14 +60,27 @@ exports.State = State;
/**
* Returns the state of the given block as a plain JavaScript object.
* @param {!Block} block The block to serialize.
* @param {{addCoordinates: (boolean|undefined)}=} param1
* addCoordinates: If true the coordinates of the block are added to the
* @param {{addCoordinates: (boolean|undefined), addInputBlocks:
* (boolean|undefined), addNextBlocks: (boolean|undefined)}=} param1
* addCoordinates: If true, the coordinates of the block are added to the
* serialized state. False by default.
* addinputBlocks: If true, children of the block which are connected to
* inputs will be serialized. True by default.
* addNextBlocks: If true, children of the block which are connected to the
* block's next connection (if it exists) will be serialized.
* True by default.
* @return {?State} The serialized state of the
* block, or null if the block could not be serialied (eg it was an
* insertion marker).
*/
const save = function(block, {addCoordinates = false} = {}) {
const save = function(
block,
{
addCoordinates = false,
addInputBlocks = true,
addNextBlocks = true
} = {}
) {
if (block.isInsertionMarker()) {
return null;
}
Expand All @@ -62,11 +91,17 @@ const save = function(block, {addCoordinates = false} = {}) {
};

if (addCoordinates) {
addCoords(block, state);
saveCoords(block, state);
}
saveAttributes(block, state);
saveExtraState(block, state);
saveFields(block, state);
if (addInputBlocks) {
saveInputBlocks(block, state);
}
if (addNextBlocks) {
saveNextBlocks(block, state);
}
addAttributes(block, state);
addExtraState(block, state);
addFields(block, state);

return state;
};
Expand All @@ -78,7 +113,7 @@ exports.save = save;
* @param {!Block} block The block to base the attributes on.
* @param {!State} state The state object to append to.
*/
const addAttributes = function(block, state) {
const saveAttributes = function(block, state) {
if (block.isCollapsed()) {
state['collapsed'] = true;
}
Expand Down Expand Up @@ -111,7 +146,7 @@ const addAttributes = function(block, state) {
* @param {!Block} block The block to base the coordinates on
* @param {!State} state The state object to append to
*/
const addCoords = function(block, state) {
const saveCoords = function(block, state) {
const workspace = block.workspace;
const xy = block.getRelativeToSurfaceXY();
state['x'] = Math.round(workspace.RTL ? workspace.getWidth() - xy.x : xy.x);
Expand All @@ -123,7 +158,7 @@ const addCoords = function(block, state) {
* @param {!Block} block The block to serialize the extra state of.
* @param {!State} state The state object to append to.
*/
const addExtraState = function(block, state) {
const saveExtraState = function(block, state) {
if (block.saveExtraState) {
const extraState = block.saveExtraState();
if (extraState !== null) {
Expand All @@ -137,7 +172,7 @@ const addExtraState = function(block, state) {
* @param {!Block} block The block to serialize the field state of.
* @param {!State} state The state object to append to.
*/
const addFields = function(block, state) {
const saveFields = function(block, state) {
let hasFieldState = false;
let fields = Object.create(null);
for (let i = 0; i < block.inputList.length; i++) {
Expand All @@ -154,3 +189,69 @@ const addFields = function(block, state) {
state['fields'] = fields;
}
};

/**
* Adds the state of all of the child blocks of the given block (which are
* connected to inputs) to the given state object.
* @param {!Block} block The block to serialize the input blocks of.
* @param {!State} state The state object to append to.
*/
const saveInputBlocks = function(block, state) {
const inputs = Object.create(null);
for (let i = 0; i < block.inputList.length; i++) {
const input = block.inputList[i];
if (input.type === inputTypes.DUMMY) {
continue;
}
const connectionState =
saveConnection(/** @type {!Connection} */ (input.connection));
if (connectionState) {
inputs[input.name] = connectionState;
}
}

if (Object.keys(inputs).length) {
state['inputs'] = inputs;
}
};

/**
* Adds the state of all of the next blocks of the given block to the given
* state object.
* @param {!Block} block The block to serialize the next blocks of.
* @param {!State} state The state object to append to.
*/
const saveNextBlocks = function(block, state) {
if (!block.nextConnection) {
return;
}
const connectionState = saveConnection(block.nextConnection);
if (connectionState) {
state['next'] = connectionState;
}
};

/**
* Returns the state of the given connection (ie the state of any connected
* shadow or real blocks).
* @param {!Connection} connection The connection to serialize the connected
* blocks of.
* @return {?ConnectionState} An object containing the state of any connected
* shadow block, or any connected real block.
*/
const saveConnection = function(connection) {
const shadow = connection.getShadowDom();
const child = connection.targetBlock();
if (!shadow && !child) {
return null;
}
var state = Object.create(null);
if (shadow) {
state['shadow'] = Xml.domToText(shadow)
.replace('xmlns="https://developers.google.com/blockly/xml"', '');
}
if (child) {
state['block'] = save(child);
}
return state;
};
2 changes: 1 addition & 1 deletion tests/deps.js

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

Loading

0 comments on commit 56d3cb6

Please sign in to comment.