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
5 changes: 4 additions & 1 deletion core/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,12 @@ Blockly.Connection.prototype.onFailedConnect = function(_otherConnection) {
/**
* Connect this connection to another connection.
* @param {!Blockly.Connection} otherConnection Connection to connect to.
* @return {boolean} Whether the the blocks are now connected or not.
*/
Blockly.Connection.prototype.connect = function(otherConnection) {
if (this.targetConnection == otherConnection) {
// Already connected together. NOP.
return;
return true;
}

var checker = this.getConnectionChecker();
Expand All @@ -308,6 +309,8 @@ Blockly.Connection.prototype.connect = function(otherConnection) {
Blockly.Events.setGroup(false);
}
}

return this.isConnected();
};

/**
Expand Down
66 changes: 56 additions & 10 deletions core/serialization/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const Block = goog.requireType('Blockly.Block');
// eslint-disable-next-line no-unused-vars
const Connection = goog.requireType('Blockly.Connection');
const Events = goog.require('Blockly.Events');
const {MissingBlockType, MissingConnection, BadConnectionCheck} =
goog.require('Blockly.serialization.exceptions');
// eslint-disable-next-line no-unused-vars
const Workspace = goog.requireType('Blockly.Workspace');
const Xml = goog.require('Blockly.Xml');
Expand Down Expand Up @@ -311,16 +313,15 @@ exports.load = load;
* @return {!Block} The block that was just loaded.
*/
const loadInternal = function(state, workspace, parentConnection = undefined) {
if (!state['type']) {
throw new MissingBlockType(state);
}

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));
}
tryToConnectParent(parentConnection, block, state);
// loadIcons(block, state);
loadFields(block, state);
loadInputBlocks(block, state);
Expand Down Expand Up @@ -383,6 +384,49 @@ const loadExtraState = function(block, state) {
block.loadExtraState(state['extraState']);
};

/**
* Attempts to connect the block to the parent connection, if it exists.
* @param {(!Connection|undefined)} parentConnection The parent connnection to
* try to connect the block to.
* @param {!Block} child The block to try to conecnt to the parent.
* @param {!State} state The state which defines the given block
*/
const tryToConnectParent = function(parentConnection, child, state) {
if (!parentConnection) {
return;
}

let connected = false;
let childConnection;
if (parentConnection.type == inputTypes.VALUE) {
childConnection = child.outputConnection;
if (!childConnection) {
throw new MissingConnection('output', child, state);
}
connected = parentConnection.connect(childConnection);
} else { // Statement type.
childConnection = child.previousConnection;
if (!childConnection) {
throw new MissingConnection('previous', child, state);
}
connected = parentConnection.connect(childConnection);
}

if (!connected) {
const checker = child.workspace.connectionChecker;
throw new BadConnectionCheck(
checker.getErrorMessage(
checker.canConnectWithReason(
childConnection, parentConnection, false),
childConnection,
parentConnection),
parentConnection.type == inputTypes.VALUE ?
'output connection' : 'previous connection',
child,
state);
}
};

/**
* Applies any field information available on the state object to the block.
* @param {!Block} block The block to set the field state of.
Expand Down Expand Up @@ -420,9 +464,10 @@ const loadInputBlocks = function(block, state) {
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]);
if (!input || !input.connection) {
throw new MissingConnection(inputName, block, state);
}
loadConnection(input.connection, state['inputs'][inputName]);
}
};

Expand All @@ -436,9 +481,10 @@ const loadNextBlocks = function(block, state) {
if (!state['next']) {
return;
}
if (block.nextConnection) {
loadConnection(block.nextConnection, state['next']);
if (!block.nextConnection) {
throw new MissingConnection('next', block, state);
}
loadConnection(block.nextConnection, state['next']);
};

/**
Expand Down
128 changes: 128 additions & 0 deletions core/serialization/exceptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@

/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @fileoverview Contains custom errors thrown by the serialization system.
*/
'use strict';

goog.module('Blockly.serialization.exceptions');
goog.module.declareLegacyNamespace();


// eslint-disable-next-line no-unused-vars
const {State} = goog.requireType('Blockly.serialization.blocks');

class DeserializationError extends Error { }
exports.DeserializationError = DeserializationError;

/**
* Represents an error where the serialized state is expected to provide a
* block type, but it is not provided.
*/
class MissingBlockType extends DeserializationError {
/**
* @param {!State} state The state object which is missing the block type.
*/
constructor(state) {
super(`Expected to find a 'type' property, defining the block type`);

/**
* The state object containing the bad name.
* @type {!State}
*/
this.state = state;
}
}
exports.MissingBlockType = MissingBlockType;

/**
* Represents an error where deserialization encountered a block that did
* not have a connection that was defined in the serialized state.
*/
class MissingConnection extends DeserializationError {
/**
* @param {string} connection The name of the connection that is missing. E.g.
* 'IF0', or 'next'.
* @param {!Blockly.Block} block The block missing the connection.
* @param {!State} state The state object containing the bad connection.
*/
constructor(connection, block, state) {
super(`The block ${block.toDevString()} is missing a(n) ${connection}
connection`);

/**
* The block missing the connection.
* @type {!Blockly.Block}
*/
this.block = block;

/**
* The state object containing the bad name.
* @type {!State}
*/
this.state = state;
}
}
exports.MissingConnection = MissingConnection;

/**
* Represents an error where deserialization tried to connect two connections
* that were not compatible.
*/
class BadConnectionCheck extends DeserializationError {
/**
* @param {string} reason The reason the connections were not compatible.
* @param {string} childConnection The name of the incompatible child
* connection. E.g. 'output' or 'previous'.
* @param {!Blockly.Block} childBlock The child block that could not connect
* to its parent.
* @param {!State} childState The state object representing the child block.
*/
constructor(reason, childConnection, childBlock, childState) {
super(`The block ${childBlock.toDevString()} could not connect its
${childConnection} to its parent, because: ${reason}`);

/**
* The block that could not connect to its parent.
* @type {!Blockly.Block}
*/
this.childBlock = childBlock;

/**
* The state object representing the block that could not connect to its
* parent.
* @type {!State}
*/
this.childState = childState;
}
}
exports.BadConnectionCheck = BadConnectionCheck;

/**
* Represents an error where deserialization encountered a real block as it
* was deserializing children of a shadow.
* This is an error because it is an invariant of Blockly that shadow blocks
* do not have real children.
*/
class RealChildOfShadow extends DeserializationError {
/**
* @param {!State} state The state object representing the real block.
*/
constructor(state) {
super(`Encountered a real block which is defined as a child of a shadow
block. It is an invariant of Blockly that shadow blocks only have shadow
children`);

/**
* The state object representing the real block.
* @type {!State}
*/
this.state = state;
}
}
exports.RealChildOfShadow = RealChildOfShadow;
7 changes: 4 additions & 3 deletions tests/deps.js

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

Loading