mirror of
https://github.com/google/blockly.git
synced 2026-01-08 09:30:06 +01:00
* Google changed from an Inc to an LLC. This happened back in 2017 but we didn’t notice. Officially we should update files from Inc to LLC when they are changed as part of regular edits, but this is a nightmare to remember for the next decade. * Remove project description/titles from licenses This is no longer part of Google’s header requirements. Our existing descriptions were useless (“Visual Blocks Editor”) or grossly obselete (“Visual Blocks Language”). * License no longer requires URL. * Fix license regexps.
754 lines
24 KiB
JavaScript
754 lines
24 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2011 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview Components for creating connections between blocks.
|
|
* @author fraser@google.com (Neil Fraser)
|
|
*/
|
|
'use strict';
|
|
|
|
goog.provide('Blockly.Connection');
|
|
|
|
goog.require('Blockly.Events');
|
|
goog.require('Blockly.Events.BlockMove');
|
|
goog.require('Blockly.Xml');
|
|
|
|
|
|
/**
|
|
* Class for a connection between blocks.
|
|
* @param {!Blockly.Block} source The block establishing this connection.
|
|
* @param {number} type The type of the connection.
|
|
* @constructor
|
|
*/
|
|
Blockly.Connection = function(source, type) {
|
|
/**
|
|
* @type {!Blockly.Block}
|
|
* @protected
|
|
*/
|
|
this.sourceBlock_ = source;
|
|
/** @type {number} */
|
|
this.type = type;
|
|
};
|
|
|
|
/**
|
|
* Constants for checking whether two connections are compatible.
|
|
*/
|
|
Blockly.Connection.CAN_CONNECT = 0;
|
|
Blockly.Connection.REASON_SELF_CONNECTION = 1;
|
|
Blockly.Connection.REASON_WRONG_TYPE = 2;
|
|
Blockly.Connection.REASON_TARGET_NULL = 3;
|
|
Blockly.Connection.REASON_CHECKS_FAILED = 4;
|
|
Blockly.Connection.REASON_DIFFERENT_WORKSPACES = 5;
|
|
Blockly.Connection.REASON_SHADOW_PARENT = 6;
|
|
|
|
/**
|
|
* Connection this connection connects to. Null if not connected.
|
|
* @type {Blockly.Connection}
|
|
*/
|
|
Blockly.Connection.prototype.targetConnection = null;
|
|
|
|
/**
|
|
* Has this connection been disposed of?
|
|
* @type {boolean}
|
|
* @package
|
|
*/
|
|
Blockly.Connection.prototype.disposed = false;
|
|
|
|
/**
|
|
* List of compatible value types. Null if all types are compatible.
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
Blockly.Connection.prototype.check_ = null;
|
|
|
|
/**
|
|
* DOM representation of a shadow block, or null if none.
|
|
* @type {Element}
|
|
* @private
|
|
*/
|
|
Blockly.Connection.prototype.shadowDom_ = null;
|
|
|
|
/**
|
|
* Horizontal location of this connection.
|
|
* @type {number}
|
|
* @protected
|
|
*/
|
|
Blockly.Connection.prototype.x_ = 0;
|
|
|
|
/**
|
|
* Vertical location of this connection.
|
|
* @type {number}
|
|
* @protected
|
|
*/
|
|
Blockly.Connection.prototype.y_ = 0;
|
|
|
|
/**
|
|
* Connect two connections together. This is the connection on the superior
|
|
* block.
|
|
* @param {!Blockly.Connection} childConnection Connection on inferior block.
|
|
* @protected
|
|
*/
|
|
Blockly.Connection.prototype.connect_ = function(childConnection) {
|
|
var parentConnection = this;
|
|
var parentBlock = parentConnection.getSourceBlock();
|
|
var childBlock = childConnection.getSourceBlock();
|
|
// Disconnect any existing parent on the child connection.
|
|
if (childConnection.isConnected()) {
|
|
childConnection.disconnect();
|
|
}
|
|
if (parentConnection.isConnected()) {
|
|
// Other connection is already connected to something.
|
|
// Disconnect it and reattach it or bump it as needed.
|
|
var orphanBlock = parentConnection.targetBlock();
|
|
var shadowDom = parentConnection.getShadowDom();
|
|
// Temporarily set the shadow DOM to null so it does not respawn.
|
|
parentConnection.setShadowDom(null);
|
|
// Displaced shadow blocks dissolve rather than reattaching or bumping.
|
|
if (orphanBlock.isShadow()) {
|
|
// Save the shadow block so that field values are preserved.
|
|
shadowDom = Blockly.Xml.blockToDom(orphanBlock);
|
|
orphanBlock.dispose();
|
|
orphanBlock = null;
|
|
} else if (parentConnection.type == Blockly.INPUT_VALUE) {
|
|
// Value connections.
|
|
// If female block is already connected, disconnect and bump the male.
|
|
if (!orphanBlock.outputConnection) {
|
|
throw Error('Orphan block does not have an output connection.');
|
|
}
|
|
// Attempt to reattach the orphan at the end of the newly inserted
|
|
// block. Since this block may be a row, walk down to the end
|
|
// or to the first (and only) shadow block.
|
|
var connection = Blockly.Connection.lastConnectionInRow_(
|
|
childBlock, orphanBlock);
|
|
if (connection) {
|
|
orphanBlock.outputConnection.connect(connection);
|
|
orphanBlock = null;
|
|
}
|
|
} else if (parentConnection.type == Blockly.NEXT_STATEMENT) {
|
|
// Statement connections.
|
|
// Statement blocks may be inserted into the middle of a stack.
|
|
// Split the stack.
|
|
if (!orphanBlock.previousConnection) {
|
|
throw Error('Orphan block does not have a previous connection.');
|
|
}
|
|
// Attempt to reattach the orphan at the bottom of the newly inserted
|
|
// block. Since this block may be a stack, walk down to the end.
|
|
var newBlock = childBlock;
|
|
while (newBlock.nextConnection) {
|
|
var nextBlock = newBlock.getNextBlock();
|
|
if (nextBlock && !nextBlock.isShadow()) {
|
|
newBlock = nextBlock;
|
|
} else {
|
|
if (orphanBlock.previousConnection.checkType_(
|
|
newBlock.nextConnection)) {
|
|
newBlock.nextConnection.connect(orphanBlock.previousConnection);
|
|
orphanBlock = null;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (orphanBlock) {
|
|
// Unable to reattach orphan.
|
|
parentConnection.disconnect();
|
|
if (Blockly.Events.recordUndo) {
|
|
// Bump it off to the side after a moment.
|
|
var group = Blockly.Events.getGroup();
|
|
setTimeout(function() {
|
|
// Verify orphan hasn't been deleted or reconnected (user on meth).
|
|
if (orphanBlock.workspace && !orphanBlock.getParent()) {
|
|
Blockly.Events.setGroup(group);
|
|
if (orphanBlock.outputConnection) {
|
|
orphanBlock.outputConnection.onFailedConnect(parentConnection);
|
|
} else if (orphanBlock.previousConnection) {
|
|
orphanBlock.previousConnection.onFailedConnect(parentConnection);
|
|
}
|
|
Blockly.Events.setGroup(false);
|
|
}
|
|
}, Blockly.BUMP_DELAY);
|
|
}
|
|
}
|
|
// Restore the shadow DOM.
|
|
parentConnection.setShadowDom(shadowDom);
|
|
}
|
|
|
|
var event;
|
|
if (Blockly.Events.isEnabled()) {
|
|
event = new Blockly.Events.BlockMove(childBlock);
|
|
}
|
|
// Establish the connections.
|
|
Blockly.Connection.connectReciprocally_(parentConnection, childConnection);
|
|
// Demote the inferior block so that one is a child of the superior one.
|
|
childBlock.setParent(parentBlock);
|
|
if (event) {
|
|
event.recordNew();
|
|
Blockly.Events.fire(event);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Dispose of this connection. Deal with connected blocks and remove this
|
|
* connection from the database.
|
|
* @package
|
|
*/
|
|
Blockly.Connection.prototype.dispose = function() {
|
|
|
|
// isConnected returns true for shadows and non-shadows.
|
|
if (this.isConnected()) {
|
|
this.setShadowDom(null);
|
|
var targetBlock = this.targetBlock();
|
|
if (targetBlock.isShadow()) {
|
|
// Destroy the attached shadow block & its children.
|
|
targetBlock.dispose();
|
|
} else {
|
|
// Disconnect the attached normal block.
|
|
targetBlock.unplug();
|
|
}
|
|
}
|
|
|
|
this.disposed = true;
|
|
};
|
|
|
|
/**
|
|
* Get the source block for this connection.
|
|
* @return {Blockly.Block} The source block, or null if there is none.
|
|
*/
|
|
Blockly.Connection.prototype.getSourceBlock = function() {
|
|
return this.sourceBlock_;
|
|
};
|
|
|
|
/**
|
|
* Does the connection belong to a superior block (higher in the source stack)?
|
|
* @return {boolean} True if connection faces down or right.
|
|
*/
|
|
Blockly.Connection.prototype.isSuperior = function() {
|
|
return this.type == Blockly.INPUT_VALUE ||
|
|
this.type == Blockly.NEXT_STATEMENT;
|
|
};
|
|
|
|
/**
|
|
* Is the connection connected?
|
|
* @return {boolean} True if connection is connected to another connection.
|
|
*/
|
|
Blockly.Connection.prototype.isConnected = function() {
|
|
return !!this.targetConnection;
|
|
};
|
|
|
|
/**
|
|
* Checks whether the current connection can connect with the target
|
|
* connection.
|
|
* @param {Blockly.Connection} target Connection to check compatibility with.
|
|
* @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
|
|
* an error code otherwise.
|
|
* @private
|
|
*/
|
|
Blockly.Connection.prototype.canConnectWithReason_ = function(target) {
|
|
if (!target) {
|
|
return Blockly.Connection.REASON_TARGET_NULL;
|
|
}
|
|
if (this.isSuperior()) {
|
|
var blockA = this.sourceBlock_;
|
|
var blockB = target.getSourceBlock();
|
|
} else {
|
|
var blockB = this.sourceBlock_;
|
|
var blockA = target.getSourceBlock();
|
|
}
|
|
if (blockA && blockA == blockB) {
|
|
return Blockly.Connection.REASON_SELF_CONNECTION;
|
|
} else if (target.type != Blockly.OPPOSITE_TYPE[this.type]) {
|
|
return Blockly.Connection.REASON_WRONG_TYPE;
|
|
} else if (blockA && blockB && blockA.workspace !== blockB.workspace) {
|
|
return Blockly.Connection.REASON_DIFFERENT_WORKSPACES;
|
|
} else if (!this.checkType_(target)) {
|
|
return Blockly.Connection.REASON_CHECKS_FAILED;
|
|
} else if (blockA.isShadow() && !blockB.isShadow()) {
|
|
return Blockly.Connection.REASON_SHADOW_PARENT;
|
|
}
|
|
return Blockly.Connection.CAN_CONNECT;
|
|
};
|
|
|
|
/**
|
|
* Checks whether the current connection and target connection are compatible
|
|
* and throws an exception if they are not.
|
|
* @param {Blockly.Connection} target The connection to check compatibility
|
|
* with.
|
|
* @private
|
|
*/
|
|
Blockly.Connection.prototype.checkConnection_ = function(target) {
|
|
switch (this.canConnectWithReason_(target)) {
|
|
case Blockly.Connection.CAN_CONNECT:
|
|
break;
|
|
case Blockly.Connection.REASON_SELF_CONNECTION:
|
|
throw Error('Attempted to connect a block to itself.');
|
|
case Blockly.Connection.REASON_DIFFERENT_WORKSPACES:
|
|
// Usually this means one block has been deleted.
|
|
throw Error('Blocks not on same workspace.');
|
|
case Blockly.Connection.REASON_WRONG_TYPE:
|
|
throw Error('Attempt to connect incompatible types.');
|
|
case Blockly.Connection.REASON_TARGET_NULL:
|
|
throw Error('Target connection is null.');
|
|
case Blockly.Connection.REASON_CHECKS_FAILED:
|
|
var msg = 'Connection checks failed. ';
|
|
msg += this + ' expected ' + this.check_ + ', found ' + target.check_;
|
|
throw Error(msg);
|
|
case Blockly.Connection.REASON_SHADOW_PARENT:
|
|
throw Error('Connecting non-shadow to shadow block.');
|
|
default:
|
|
throw Error('Unknown connection failure: this should never happen!');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check if the two connections can be dragged to connect to each other.
|
|
* This is used by the connection database when searching for the closest
|
|
* connection.
|
|
* @param {!Blockly.Connection} candidate A nearby connection to check, which
|
|
* must be a previous connection.
|
|
* @return {boolean} True if the connection is allowed, false otherwise.
|
|
* @private
|
|
*/
|
|
Blockly.Connection.prototype.canConnectToPrevious_ = function(candidate) {
|
|
if (this.targetConnection) {
|
|
// This connection is already occupied.
|
|
// A next connection will never disconnect itself mid-drag.
|
|
return false;
|
|
}
|
|
|
|
// Don't let blocks try to connect to themselves or ones they nest.
|
|
if (Blockly.draggingConnections_.indexOf(candidate) != -1) {
|
|
return false;
|
|
}
|
|
|
|
if (!candidate.targetConnection) {
|
|
return true;
|
|
}
|
|
|
|
var targetBlock = candidate.targetBlock();
|
|
// If it is connected to a real block, game over.
|
|
if (!targetBlock.isInsertionMarker()) {
|
|
return false;
|
|
}
|
|
// If it's connected to an insertion marker but that insertion marker
|
|
// is the first block in a stack, it's still fine. If that insertion
|
|
// marker is in the middle of a stack, it won't work.
|
|
return !targetBlock.getPreviousBlock();
|
|
};
|
|
|
|
/**
|
|
* Check if the two connections can be dragged to connect to each other.
|
|
* @param {!Blockly.Connection} candidate A nearby connection to check.
|
|
* @return {boolean} True if the connection is allowed, false otherwise.
|
|
*/
|
|
Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
|
|
// Don't consider insertion markers.
|
|
if (candidate.sourceBlock_.isInsertionMarker()) {
|
|
return false;
|
|
}
|
|
// Type checking.
|
|
var canConnect = this.canConnectWithReason_(candidate);
|
|
if (canConnect != Blockly.Connection.CAN_CONNECT) {
|
|
return false;
|
|
}
|
|
|
|
switch (candidate.type) {
|
|
case Blockly.PREVIOUS_STATEMENT:
|
|
return this.canConnectToPrevious_(candidate);
|
|
case Blockly.OUTPUT_VALUE: {
|
|
// Don't offer to connect an already connected left (male) value plug to
|
|
// an available right (female) value plug.
|
|
if ((candidate.isConnected() &&
|
|
!candidate.targetBlock().isInsertionMarker()) ||
|
|
this.isConnected()) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case Blockly.INPUT_VALUE: {
|
|
// Offering to connect the left (male) of a value block to an already
|
|
// connected value pair is ok, we'll splice it in.
|
|
// However, don't offer to splice into an immovable block.
|
|
if (candidate.isConnected() &&
|
|
!candidate.targetBlock().isMovable() &&
|
|
!candidate.targetBlock().isShadow()) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case Blockly.NEXT_STATEMENT: {
|
|
// Don't let a block with no next connection bump other blocks out of the
|
|
// stack. But covering up a shadow block or stack of shadow blocks is
|
|
// fine. Similarly, replacing a terminal statement with another terminal
|
|
// statement is allowed.
|
|
if (candidate.isConnected() &&
|
|
!this.sourceBlock_.nextConnection &&
|
|
!candidate.targetBlock().isShadow() &&
|
|
candidate.targetBlock().nextConnection) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
throw Error('Unknown connection type in isConnectionAllowed');
|
|
}
|
|
|
|
// Don't let blocks try to connect to themselves or ones they nest.
|
|
if (Blockly.draggingConnections_.indexOf(candidate) != -1) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Behavior after a connection attempt fails.
|
|
* @param {Blockly.Connection} _otherConnection Connection that this connection
|
|
* failed to connect to.
|
|
* @package
|
|
*/
|
|
Blockly.Connection.prototype.onFailedConnect = function(_otherConnection) {
|
|
// NOP
|
|
};
|
|
|
|
/**
|
|
* Connect this connection to another connection.
|
|
* @param {!Blockly.Connection} otherConnection Connection to connect to.
|
|
*/
|
|
Blockly.Connection.prototype.connect = function(otherConnection) {
|
|
if (this.targetConnection == otherConnection) {
|
|
// Already connected together. NOP.
|
|
return;
|
|
}
|
|
this.checkConnection_(otherConnection);
|
|
var eventGroup = Blockly.Events.getGroup();
|
|
if (!eventGroup) {
|
|
Blockly.Events.setGroup(true);
|
|
}
|
|
// Determine which block is superior (higher in the source stack).
|
|
if (this.isSuperior()) {
|
|
// Superior block.
|
|
this.connect_(otherConnection);
|
|
} else {
|
|
// Inferior block.
|
|
otherConnection.connect_(this);
|
|
}
|
|
if (!eventGroup) {
|
|
Blockly.Events.setGroup(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update two connections to target each other.
|
|
* @param {Blockly.Connection} first The first connection to update.
|
|
* @param {Blockly.Connection} second The second connection to update.
|
|
* @private
|
|
*/
|
|
Blockly.Connection.connectReciprocally_ = function(first, second) {
|
|
if (!first || !second) {
|
|
throw Error('Cannot connect null connections.');
|
|
}
|
|
first.targetConnection = second;
|
|
second.targetConnection = first;
|
|
};
|
|
|
|
/**
|
|
* Does the given block have one and only one connection point that will accept
|
|
* an orphaned block?
|
|
* @param {!Blockly.Block} block The superior block.
|
|
* @param {!Blockly.Block} orphanBlock The inferior block.
|
|
* @return {Blockly.Connection} The suitable connection point on 'block',
|
|
* or null.
|
|
* @private
|
|
*/
|
|
Blockly.Connection.singleConnection_ = function(block, orphanBlock) {
|
|
var connection = false;
|
|
for (var i = 0; i < block.inputList.length; i++) {
|
|
var thisConnection = block.inputList[i].connection;
|
|
if (thisConnection && thisConnection.type == Blockly.INPUT_VALUE &&
|
|
orphanBlock.outputConnection.checkType_(thisConnection)) {
|
|
if (connection) {
|
|
return null; // More than one connection.
|
|
}
|
|
connection = thisConnection;
|
|
}
|
|
}
|
|
return connection;
|
|
};
|
|
|
|
/**
|
|
* Walks down a row a blocks, at each stage checking if there are any
|
|
* connections that will accept the orphaned block. If at any point there
|
|
* are zero or multiple eligible connections, returns null. Otherwise
|
|
* returns the only input on the last block in the chain.
|
|
* Terminates early for shadow blocks.
|
|
* @param {!Blockly.Block} startBlock The block on which to start the search.
|
|
* @param {!Blockly.Block} orphanBlock The block that is looking for a home.
|
|
* @return {Blockly.Connection} The suitable connection point on the chain
|
|
* of blocks, or null.
|
|
* @private
|
|
*/
|
|
Blockly.Connection.lastConnectionInRow_ = function(startBlock, orphanBlock) {
|
|
var newBlock = startBlock;
|
|
var connection;
|
|
while (connection = Blockly.Connection.singleConnection_(
|
|
/** @type {!Blockly.Block} */ (newBlock), orphanBlock)) {
|
|
// '=' is intentional in line above.
|
|
newBlock = connection.targetBlock();
|
|
if (!newBlock || newBlock.isShadow()) {
|
|
return connection;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Disconnect this connection.
|
|
*/
|
|
Blockly.Connection.prototype.disconnect = function() {
|
|
var otherConnection = this.targetConnection;
|
|
if (!otherConnection) {
|
|
throw Error('Source connection not connected.');
|
|
}
|
|
if (otherConnection.targetConnection != this) {
|
|
throw Error('Target connection not connected to source connection.');
|
|
}
|
|
var parentBlock, childBlock, parentConnection;
|
|
if (this.isSuperior()) {
|
|
// Superior block.
|
|
parentBlock = this.sourceBlock_;
|
|
childBlock = otherConnection.getSourceBlock();
|
|
parentConnection = this;
|
|
} else {
|
|
// Inferior block.
|
|
parentBlock = otherConnection.getSourceBlock();
|
|
childBlock = this.sourceBlock_;
|
|
parentConnection = otherConnection;
|
|
}
|
|
|
|
var eventGroup = Blockly.Events.getGroup();
|
|
if (!eventGroup) {
|
|
Blockly.Events.setGroup(true);
|
|
}
|
|
this.disconnectInternal_(parentBlock, childBlock);
|
|
parentConnection.respawnShadow_();
|
|
if (!eventGroup) {
|
|
Blockly.Events.setGroup(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Disconnect two blocks that are connected by this connection.
|
|
* @param {!Blockly.Block} parentBlock The superior block.
|
|
* @param {!Blockly.Block} childBlock The inferior block.
|
|
* @protected
|
|
*/
|
|
Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock,
|
|
childBlock) {
|
|
var event;
|
|
if (Blockly.Events.isEnabled()) {
|
|
event = new Blockly.Events.BlockMove(childBlock);
|
|
}
|
|
var otherConnection = this.targetConnection;
|
|
otherConnection.targetConnection = null;
|
|
this.targetConnection = null;
|
|
childBlock.setParent(null);
|
|
if (event) {
|
|
event.recordNew();
|
|
Blockly.Events.fire(event);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Respawn the shadow block if there was one connected to the this connection.
|
|
* @protected
|
|
*/
|
|
Blockly.Connection.prototype.respawnShadow_ = function() {
|
|
var parentBlock = this.getSourceBlock();
|
|
var shadow = this.getShadowDom();
|
|
if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) {
|
|
var blockShadow =
|
|
Blockly.Xml.domToBlock(shadow, parentBlock.workspace);
|
|
if (blockShadow.outputConnection) {
|
|
this.connect(blockShadow.outputConnection);
|
|
} else if (blockShadow.previousConnection) {
|
|
this.connect(blockShadow.previousConnection);
|
|
} else {
|
|
throw Error('Child block does not have output or previous statement.');
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns the block that this connection connects to.
|
|
* @return {Blockly.Block} The connected block or null if none is connected.
|
|
*/
|
|
Blockly.Connection.prototype.targetBlock = function() {
|
|
if (this.isConnected()) {
|
|
return this.targetConnection.getSourceBlock();
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Is this connection compatible with another connection with respect to the
|
|
* value type system. E.g. square_root("Hello") is not compatible.
|
|
* @param {!Blockly.Connection} otherConnection Connection to compare against.
|
|
* @return {boolean} True if the connections share a type.
|
|
* @protected
|
|
*/
|
|
Blockly.Connection.prototype.checkType_ = function(otherConnection) {
|
|
if (!this.check_ || !otherConnection.check_) {
|
|
// One or both sides are promiscuous enough that anything will fit.
|
|
return true;
|
|
}
|
|
// Find any intersection in the check lists.
|
|
for (var i = 0; i < this.check_.length; i++) {
|
|
if (otherConnection.check_.indexOf(this.check_[i]) != -1) {
|
|
return true;
|
|
}
|
|
}
|
|
// No intersection.
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Function to be called when this connection's compatible types have changed.
|
|
* @private
|
|
*/
|
|
Blockly.Connection.prototype.onCheckChanged_ = function() {
|
|
// The new value type may not be compatible with the existing connection.
|
|
if (this.isConnected() && !this.checkType_(this.targetConnection)) {
|
|
var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
|
|
child.unplug();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Change a connection's compatibility.
|
|
* @param {string|!Array<string>} check Compatible value type or list of value
|
|
* types. Null if all types are compatible.
|
|
* @return {!Blockly.Connection} The connection being modified
|
|
* (to allow chaining).
|
|
*/
|
|
Blockly.Connection.prototype.setCheck = function(check) {
|
|
if (check) {
|
|
// Ensure that check is in an array.
|
|
if (!Array.isArray(check)) {
|
|
check = [check];
|
|
}
|
|
this.check_ = check;
|
|
this.onCheckChanged_();
|
|
} else {
|
|
this.check_ = null;
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Get a connection's compatibility.
|
|
* @return {Array} List of compatible value types.
|
|
* Null if all types are compatible.
|
|
* @public
|
|
*/
|
|
Blockly.Connection.prototype.getCheck = function() {
|
|
return this.check_;
|
|
};
|
|
|
|
/**
|
|
* Change a connection's shadow block.
|
|
* @param {Element} shadow DOM representation of a block or null.
|
|
*/
|
|
Blockly.Connection.prototype.setShadowDom = function(shadow) {
|
|
this.shadowDom_ = shadow;
|
|
};
|
|
|
|
/**
|
|
* Return a connection's shadow block.
|
|
* @return {Element} Shadow DOM representation of a block or null.
|
|
*/
|
|
Blockly.Connection.prototype.getShadowDom = function() {
|
|
return this.shadowDom_;
|
|
};
|
|
|
|
/**
|
|
* Find all nearby compatible connections to this connection.
|
|
* Type checking does not apply, since this function is used for bumping.
|
|
*
|
|
* Headless configurations (the default) do not have neighboring connection,
|
|
* and always return an empty list (the default).
|
|
* {@link Blockly.RenderedConnection} overrides this behavior with a list
|
|
* computed from the rendered positioning.
|
|
* @param {number} _maxLimit The maximum radius to another connection.
|
|
* @return {!Array.<!Blockly.Connection>} List of connections.
|
|
* @private
|
|
*/
|
|
Blockly.Connection.prototype.neighbours_ = function(_maxLimit) {
|
|
return [];
|
|
};
|
|
|
|
/**
|
|
* Get the parent input of a connection.
|
|
* @return {Blockly.Input} The input that the connection belongs to or null if
|
|
* no parent exists.
|
|
* @package
|
|
*/
|
|
Blockly.Connection.prototype.getParentInput = function() {
|
|
var parentInput = null;
|
|
var block = this.sourceBlock_;
|
|
var inputs = block.inputList;
|
|
for (var idx = 0; idx < block.inputList.length; idx++) {
|
|
if (inputs[idx].connection === this) {
|
|
parentInput = inputs[idx];
|
|
break;
|
|
}
|
|
}
|
|
return parentInput;
|
|
};
|
|
|
|
/**
|
|
* This method returns a string describing this Connection in developer terms
|
|
* (English only). Intended to on be used in console logs and errors.
|
|
* @return {string} The description.
|
|
*/
|
|
Blockly.Connection.prototype.toString = function() {
|
|
var msg;
|
|
var block = this.sourceBlock_;
|
|
if (!block) {
|
|
return 'Orphan Connection';
|
|
} else if (block.outputConnection == this) {
|
|
msg = 'Output Connection of ';
|
|
} else if (block.previousConnection == this) {
|
|
msg = 'Previous Connection of ';
|
|
} else if (block.nextConnection == this) {
|
|
msg = 'Next Connection of ';
|
|
} else {
|
|
var parentInput = null;
|
|
for (var i = 0, input; input = block.inputList[i]; i++) {
|
|
if (input.connection == this) {
|
|
parentInput = input;
|
|
break;
|
|
}
|
|
}
|
|
if (parentInput) {
|
|
msg = 'Input "' + parentInput.name + '" connection on ';
|
|
} else {
|
|
console.warn('Connection not actually connected to sourceBlock_');
|
|
return 'Orphan Connection';
|
|
}
|
|
}
|
|
return msg + block.toDevString();
|
|
};
|