diff --git a/core/serialization/blocks.js b/core/serialization/blocks.js
index 86a1bc33334..d509936bc4c 100644
--- a/core/serialization/blocks.js
+++ b/core/serialization/blocks.js
@@ -252,7 +252,7 @@ const saveConnection = function(connection) {
state['shadow'] = Xml.domToText(shadow)
.replace('xmlns="https://developers.google.com/blockly/xml"', '');
}
- if (child) {
+ if (child && !child.isShadow()) {
state['block'] = save(child);
}
return state;
@@ -263,8 +263,165 @@ const saveConnection = function(connection) {
* @param {!State} state The state of a block to deserialize into the workspace.
* @param {!Workspace} workspace The workspace to add the block to.
*/
-// eslint-disable-next-line no-unused-vars
const load = function(state, workspace) {
- // Temporarily NOP while connecting things together.
+ loadInternal(state, workspace);
+};
+
+/**
+ * Loads the block represented by the given state into the given workspace.
+ * This is defined internally so that the extra optional parameter doesn't
+ * clutter our external API.
+ * @param {!State} state The state of a block to deserialize into the workspace.
+ * @param {!Workspace} workspace The workspace to add the block to.
+ * @param {!Connection} parentConnection The optional parent connection to
+ * attach the block to.
+ */
+const loadInternal = function(state, workspace, parentConnection = undefined) {
+ const block = workspace.newBlock(state['type'], state['id']);
+ loadCoords(block, state);
+ loadAttributes(block, state);
+ loadExtraState(block, state);
+ if (parentConnection &&
+ (block.outputConnection || block.previousConnection)) {
+ parentConnection.connect(
+ /** @type {!Connection} */
+ (block.outputConnection || block.previousConnection));
+ }
+ // loadIcons(block, state);
+ loadFields(block, state);
+ loadInputBlocks(block, state);
+ loadNextBlocks(block, state);
+};
+
+/**
+ * Applies any coordinate information available on the state object to the
+ * block.
+ * @param {!Block} block The block to set the position of.
+ * @param {!State} state The state object to reference.
+ */
+const loadCoords = function(block, state) {
+ const x = state['x'] === undefined ? 10 : state['x'];
+ const y = state['y'] === undefined ? 10 : state['y'];
+ block.moveBy(x, y);
+};
+
+/**
+ * Applies any attribute information available on the state object to the block.
+ * @param {!Block} block The block to set the attributes of.
+ * @param {!State} state The state object to reference.
+ */
+const loadAttributes = function(block, state) {
+ if (state['collapsed']) {
+ block.setCollapsed(true);
+ }
+ if (state['enabled'] === false) {
+ block.setEnabled(false);
+ }
+ if (state['editable'] === false) {
+ block.setEditable(false);
+ }
+ if (state['deletable'] === false) {
+ block.setDeletable(false);
+ }
+ if (state['movable'] === false) {
+ block.setMovable(false);
+ }
+ if (state['inline'] !== undefined) {
+ block.setInputsInline(state['inline']);
+ }
+ if (state['data'] !== undefined) {
+ block.data = state['data'];
+ }
+};
+
+/**
+ * Applies any extra state information available on the state object to the
+ * block.
+ * @param {!Block} block The block to set the extra state of.
+ * @param {!State} state The state object to reference.
+ */
+const loadExtraState = function(block, state) {
+ if (!state['extraState']) {
+ return;
+ }
+ block.loadExtraState(state['extraState']);
+};
+
+/**
+ * Applies any field information available on the state object to the block.
+ * @param {!Block} block The block to set the field state of.
+ * @param {!State} state The state object to reference.
+ */
+const loadFields = function(block, state) {
+ if (!state['fields']) {
+ return;
+ }
+ const keys = Object.keys(state['fields']);
+ for (let i = 0; i < keys.length; i++) {
+ const fieldName = keys[i];
+ const fieldState = state['fields'][fieldName];
+ const field = block.getField(fieldName);
+ if (!field) {
+ console.warn(
+ `Ignoring non-existant field ${fieldName} in block ${block.type}`);
+ continue;
+ }
+ field.loadState(fieldState);
+ }
+};
+
+/**
+ * Creates any child blocks (attached to inputs) defined by the given state
+ * and attaches them to the given block.
+ * @param {!Block} block The block to attach input blocks to.
+ * @param {!State} state The state object to reference.
+ */
+const loadInputBlocks = function(block, state) {
+ if (!state['inputs']) {
+ return;
+ }
+ const keys = Object.keys(state['inputs']);
+ for (let i = 0; i < keys.length; i++) {
+ const inputName = keys[i];
+ const input = block.getInput(inputName);
+ if (input && input.connection) {
+ loadConnection(input.connection, state['inputs'][inputName]);
+ }
+ }
+};
+
+/**
+ * Creates any next blocks defined by the given state and attaches them to the
+ * given block.
+ * @param {!Block} block The block to attach next blocks to.
+ * @param {!State} state The state object to reference.
+ */
+const loadNextBlocks = function(block, state) {
+ if (!state['next']) {
+ return;
+ }
+ if (block.nextConnection) {
+ loadConnection(block.nextConnection, state['next']);
+ }
+};
+
+/**
+ * Applies the state defined by connectionState to the given connection, ie
+ * assigns shadows and attaches child blocks.
+ * @param {!Connection} connection The connection to serialize the
+ * connected blocks of.
+ * @param {!ConnectionState} connectionState The object containing the state of
+ * any connected shadow block, or any connected real block.
+ */
+const loadConnection = function(connection, connectionState) {
+ if (connectionState['shadow']) {
+ connection.setShadowDom(Blockly.Xml.textToDom(connectionState['shadow']));
+ }
+ if (connectionState['block']) {
+ loadInternal(
+ connectionState['block'],
+ connection.getSourceBlock().workspace,
+ connection);
+ }
};
exports.load = load;
diff --git a/tests/mocha/serializer_test.js b/tests/mocha/serializer_test.js
index 1f8f3654505..3da2ff50045 100644
--- a/tests/mocha/serializer_test.js
+++ b/tests/mocha/serializer_test.js
@@ -1264,7 +1264,7 @@ Serializer.Connections.OverwrittenShadow.Row = new SerializerTestCase('Row',
'' +
'' +
'' +
- '' +
+ '' +
'TRUE' +
'' +
'' +
@@ -1280,7 +1280,7 @@ Serializer.Connections.OverwrittenShadow.Nested = new SerializerTestCase(
'' +
'' +
'' +
- '' +
+ '' +
'' +
'' +
'');
@@ -1288,10 +1288,10 @@ Serializer.Connections.OverwrittenShadow.Stack = new SerializerTestCase('Stack',
'' +
'' +
'' +
- '' +
- '' +
+ '' +
+ '' +
'' +
- '' +
+ '' +
'' +
'' +
'' +
@@ -1772,5 +1772,6 @@ var runSerializerTestSuite = (serializer, deserializer, testSuite) => {
};
runSerializerTestSuite(null, null, Serializer);
-Serializer.skip = true;
+Serializer.Icons.skip = true;
+Serializer.Mutations.skip = true;
runSerializerTestSuite(state => state, state => state, Serializer);