Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 160 additions & 3 deletions core/serialization/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
13 changes: 7 additions & 6 deletions tests/mocha/serializer_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1260,7 +1260,7 @@ Serializer.Connections.OverwrittenShadow.Row = new SerializerTestCase('Row',
'</shadow>' +
'</value>' +
'</shadow>' +
'<block type="logic_boolean" id="id3*****************">' +
'<block type="logic_boolean" id="id4*****************">' +
'<field name="BOOL">TRUE</field>' +
'</block>' +
'</value>' +
Expand All @@ -1276,18 +1276,18 @@ Serializer.Connections.OverwrittenShadow.Nested = new SerializerTestCase(
'<shadow type="text_print" id="id3*****************"></shadow>' +
'</statement>' +
'</shadow>' +
'<block type="text_print" id="id3*****************"></block>' +
'<block type="text_print" id="id4*****************"></block>' +
'</statement>' +
'</block>' +
'</xml>');
Serializer.Connections.OverwrittenShadow.Stack = new SerializerTestCase('Stack',
'<xml xmlns="https://developers.google.com/blockly/xml">' +
'<block type="text_print" id="id******************" x="42" y="42">' +
'<next>' +
'<block type="text_print" id="id3*****************"></block>' +
'<shadow type="text_print" id="id2*****************">' +
'<block type="text_print" id="id2*****************"></block>' +
'<shadow type="text_print" id="id3*****************">' +
'<next>' +
'<shadow type="text_print" id="id3*****************"></shadow>' +
'<shadow type="text_print" id="id4*****************"></shadow>' +
'</next>' +
'</shadow>' +
'</next>' +
Expand Down Expand Up @@ -1768,5 +1768,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);