diff --git a/README.md b/README.md index fd626a49a..474dbf8ac 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,5 @@ blocks together to build programs. All code is free and open source. **The project page is https://developers.google.com/blockly/** ![](https://developers.google.com/blockly/images/sample.png) + +Blockly has an active [developer forum](https://groups.google.com/forum/#!forum/blockly). Please drop by and say hello. Show us your prototypes early; collectively we have a lot of experience and can offer hints which will save you time. diff --git a/accessible/audio.service.js b/accessible/audio.service.js index 2f3f00f0a..1225bb6bd 100644 --- a/accessible/audio.service.js +++ b/accessible/audio.service.js @@ -41,6 +41,13 @@ blocklyApp.AudioService = ng.core.Class({ } this.cachedAudioFiles_ = {}; + // Store callback references here so that they can be removed if a new + // call to this.play_() comes in. + this.onEndedCallbacks_ = { + 'connect': [], + 'delete': [], + 'oops': [] + }; } ], play_: function(audioId, onEndedCallback) { @@ -48,11 +55,18 @@ blocklyApp.AudioService = ng.core.Class({ if (!this.cachedAudioFiles_.hasOwnProperty(audioId)) { this.cachedAudioFiles_[audioId] = new Audio(this.AUDIO_PATHS_[audioId]); } + if (onEndedCallback) { + this.onEndedCallbacks_[audioId].push(onEndedCallback); this.cachedAudioFiles_[audioId].addEventListener( 'ended', onEndedCallback); } else { - this.cachedAudioFiles_[audioId].removeEventListener('ended'); + var that = this; + this.onEndedCallbacks_[audioId].forEach(function(callback) { + that.cachedAudioFiles_[audioId].removeEventListener( + 'ended', callback); + }); + this.onEndedCallbacks_[audioId].length = 0; } this.cachedAudioFiles_[audioId].play(); diff --git a/accessible/block-connection.service.js b/accessible/block-connection.service.js index 37145663a..930ef6f15 100644 --- a/accessible/block-connection.service.js +++ b/accessible/block-connection.service.js @@ -34,18 +34,20 @@ blocklyApp.BlockConnectionService = ng.core.Class({ // link is stored here. this.markedConnection_ = null; }], - findCompatibleConnection_: function(block) { + findCompatibleConnection_: function(block, targetConnection) { // Locates and returns a connection on the given block that is compatible - // with the marked connection, if one exists. Returns null if no such + // with the target connection, if one exists. Returns null if no such // connection exists. - // Note: this currently ignores input connections on the given block, since - // one doesn't usually mark an output connection and attach a block to it. - if (!this.markedConnection_ || - !this.markedConnection_.getSourceBlock().workspace) { + // Note: the targetConnection is assumed to be the markedConnection_, or + // possibly its counterpart (in the case where the marked connection is + // currently attached to another connection). This method therefore ignores + // input connections on the given block, since one doesn't usually mark an + // output connection and attach a block to it. + if (!targetConnection || !targetConnection.getSourceBlock().workspace) { return null; } - var desiredType = Blockly.OPPOSITE_TYPE[this.markedConnection_.type]; + var desiredType = Blockly.OPPOSITE_TYPE[targetConnection.type]; var potentialConnection = ( desiredType == Blockly.OUTPUT_VALUE ? block.outputConnection : desiredType == Blockly.PREVIOUS_STATEMENT ? block.previousConnection : @@ -53,7 +55,7 @@ blocklyApp.BlockConnectionService = ng.core.Class({ null); if (potentialConnection && - potentialConnection.checkType_(this.markedConnection_)) { + potentialConnection.checkType_(targetConnection)) { return potentialConnection; } else { return null; @@ -67,7 +69,8 @@ blocklyApp.BlockConnectionService = ng.core.Class({ this.markedConnection_.getSourceBlock() : null; }, canBeAttachedToMarkedConnection: function(block) { - return Boolean(this.findCompatibleConnection_(block)); + return Boolean( + this.findCompatibleConnection_(block, this.markedConnection_)); }, canBeMovedToMarkedConnection: function(block) { if (!this.markedConnection_) { @@ -94,9 +97,24 @@ blocklyApp.BlockConnectionService = ng.core.Class({ var xml = Blockly.Xml.blockToDom(block); var reconstitutedBlock = Blockly.Xml.domToBlock(blocklyApp.workspace, xml); - var connection = this.findCompatibleConnection_(reconstitutedBlock); + var targetConnection = null; + if (this.markedConnection_.targetBlock() && + this.markedConnection_.type == Blockly.PREVIOUS_STATEMENT) { + // Is the marked connection a 'previous' connection that is already + // connected? If so, find the block that's currently connected to it, and + // use that block's 'next' connection as the new marked connection. + // Otherwise, splicing does not happen correctly, and inserting a block + // in the middle of a group of two linked blocks will split the group. + targetConnection = this.markedConnection_.targetConnection; + } else { + targetConnection = this.markedConnection_; + } + + var connection = this.findCompatibleConnection_( + reconstitutedBlock, targetConnection); if (connection) { - this.markedConnection_.connect(connection); + targetConnection.connect(connection); + this.markedConnection_ = null; this.audioService.playConnectSound(); return reconstitutedBlock.id; diff --git a/accessible/block-options-modal.component.js b/accessible/block-options-modal.component.js index d68a04b70..8f0d532f2 100644 --- a/accessible/block-options-modal.component.js +++ b/accessible/block-options-modal.component.js @@ -36,10 +36,10 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({

{{'BLOCK_OPTIONS'|translate}}

-
@@ -110,6 +110,10 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({ evt.preventDefault(); evt.stopPropagation(); + if (that.activeActionButtonIndex == -1) { + return; + } + var button = document.getElementById( that.getOptionId(that.activeActionButtonIndex)); if (that.activeActionButtonIndex < @@ -150,7 +154,7 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({ }, // Returns the ID for the corresponding option button. getOptionId: function(index) { - return 'modal-option-' + index; + return 'block-options-modal-option-' + index; }, // Returns the ID for the "cancel" option button. getCancelOptionId: function() { diff --git a/accessible/block-options-modal.service.js b/accessible/block-options-modal.service.js index 82e425c05..c7b068d42 100644 --- a/accessible/block-options-modal.service.js +++ b/accessible/block-options-modal.service.js @@ -26,6 +26,9 @@ blocklyApp.BlockOptionsModalService = ng.core.Class({ constructor: [function() { this.actionButtonsInfo = []; + // The aim of the pre-show hook is to populate the modal component with the + // information it needs to display the modal (e.g., which action buttons to + // display). this.preShowHook = function() { throw Error( 'A pre-show hook must be defined for the block options modal ' + @@ -35,8 +38,9 @@ blocklyApp.BlockOptionsModalService = ng.core.Class({ this.onDismissCallback = null; }], registerPreShowHook: function(preShowHook) { + var that = this; this.preShowHook = function() { - preShowHook(this.actionButtonsInfo, this.onDismissCallback); + preShowHook(that.actionButtonsInfo, that.onDismissCallback); }; }, isModalShown: function() { diff --git a/accessible/field-segment.component.js b/accessible/field-segment.component.js index 74222ac4d..d14e52303 100644 --- a/accessible/field-segment.component.js +++ b/accessible/field-segment.component.js @@ -34,28 +34,28 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({