diff --git a/accessible/app.component.js b/accessible/app.component.js index b57ec4aa9..6d37bb9e7 100644 --- a/accessible/app.component.js +++ b/accessible/app.component.js @@ -56,8 +56,8 @@ blocklyApp.AppComponent = ng.core.Component({ // https://www.sitepoint.com/angular-2-components-providers-classes-factories-values/ providers: [ blocklyApp.AudioService, + blocklyApp.BlockConnectionService, blocklyApp.BlockOptionsModalService, - blocklyApp.ClipboardService, blocklyApp.KeyboardInputService, blocklyApp.NotificationsService, blocklyApp.ToolboxModalService, diff --git a/accessible/block-connection.service.js b/accessible/block-connection.service.js new file mode 100644 index 000000000..37145663a --- /dev/null +++ b/accessible/block-connection.service.js @@ -0,0 +1,111 @@ +/** + * AccessibleBlockly + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * 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 Angular2 Service for handling the mechanics of how blocks + * get connected to each other. + * @author sll@google.com (Sean Lip) + */ + +blocklyApp.BlockConnectionService = ng.core.Class({ + constructor: [ + blocklyApp.NotificationsService, blocklyApp.AudioService, + function(_notificationsService, _audioService) { + this.notificationsService = _notificationsService; + this.audioService = _audioService; + + // When a user "adds a link" to a block, the connection representing this + // link is stored here. + this.markedConnection_ = null; + }], + findCompatibleConnection_: function(block) { + // Locates and returns a connection on the given block that is compatible + // with the marked 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) { + return null; + } + + var desiredType = Blockly.OPPOSITE_TYPE[this.markedConnection_.type]; + var potentialConnection = ( + desiredType == Blockly.OUTPUT_VALUE ? block.outputConnection : + desiredType == Blockly.PREVIOUS_STATEMENT ? block.previousConnection : + desiredType == Blockly.NEXT_STATEMENT ? block.nextConnection : + null); + + if (potentialConnection && + potentialConnection.checkType_(this.markedConnection_)) { + return potentialConnection; + } else { + return null; + } + }, + isAnyConnectionMarked: function() { + return Boolean(this.markedConnection_); + }, + getMarkedConnectionSourceBlock: function() { + return this.markedConnection_ ? + this.markedConnection_.getSourceBlock() : null; + }, + canBeAttachedToMarkedConnection: function(block) { + return Boolean(this.findCompatibleConnection_(block)); + }, + canBeMovedToMarkedConnection: function(block) { + if (!this.markedConnection_) { + return false; + } + + // It should not be possible to move any ancestor of the block containing + // the marked connection to the marked connection. + var ancestorBlock = this.getMarkedConnectionSourceBlock(); + while (ancestorBlock) { + if (ancestorBlock.id == block.id) { + return false; + } + ancestorBlock = ancestorBlock.getParent(); + } + + return this.canBeAttachedToMarkedConnection(block); + }, + markConnection: function(connection) { + this.markedConnection_ = connection; + this.notificationsService.speak(Blockly.Msg.ADDED_LINK_MSG); + }, + attachToMarkedConnection: function(block) { + var xml = Blockly.Xml.blockToDom(block); + var reconstitutedBlock = Blockly.Xml.domToBlock(blocklyApp.workspace, xml); + + var connection = this.findCompatibleConnection_(reconstitutedBlock); + if (connection) { + this.markedConnection_.connect(connection); + this.markedConnection_ = null; + this.audioService.playConnectSound(); + return reconstitutedBlock.id; + } else { + // We throw an error here, because we expect any UI controls that would + // result in a non-connection to be disabled or hidden. + throw Error( + 'Unable to connect block to marked connection. This should not ' + + 'happen.'); + } + } +}); diff --git a/accessible/clipboard.service.js b/accessible/clipboard.service.js deleted file mode 100644 index 61510b94f..000000000 --- a/accessible/clipboard.service.js +++ /dev/null @@ -1,123 +0,0 @@ -/** - * AccessibleBlockly - * - * Copyright 2016 Google Inc. - * https://developers.google.com/blockly/ - * - * 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 Angular2 Service that handles the clipboard and marked spots. - * @author madeeha@google.com (Madeeha Ghori) - */ - -blocklyApp.ClipboardService = ng.core.Class({ - constructor: [ - blocklyApp.NotificationsService, blocklyApp.UtilsService, - blocklyApp.AudioService, - function(_notificationsService, _utilsService, _audioService) { - this.markedConnection_ = null; - this.notificationsService = _notificationsService; - this.utilsService = _utilsService; - this.audioService = _audioService; - }], - areConnectionsCompatible_: function(blockConnection, connection) { - // Check that both connections exist, that it's the right kind of - // connection, and that the types match. - return Boolean( - connection && blockConnection && - Blockly.OPPOSITE_TYPE[blockConnection.type] == connection.type && - connection.checkType_(blockConnection)); - }, - getMarkedConnectionBlock: function() { - if (!this.markedConnection_) { - return null; - } else { - return this.markedConnection_.getSourceBlock(); - } - }, - isAnyConnectionMarked: function() { - return Boolean(this.markedConnection_); - }, - isMovableToMarkedConnection: function(block) { - // It should not be possible to move any ancestor of the block containing - // the marked spot to the marked spot. - if (!this.markedConnection_) { - return false; - } - - var markedSpotAncestorBlock = this.getMarkedConnectionBlock(); - while (markedSpotAncestorBlock) { - if (markedSpotAncestorBlock.id == block.id) { - return false; - } - markedSpotAncestorBlock = markedSpotAncestorBlock.getParent(); - } - - return this.canBeAttachedToMarkedConnection(block); - }, - canBeAttachedToMarkedConnection: function(block) { - if (!this.markedConnection_ || - !this.markedConnection_.getSourceBlock().workspace) { - return false; - } - - var potentialConnections = [ - block.outputConnection, - block.previousConnection, - block.nextConnection - ]; - - var that = this; - return potentialConnections.some(function(connection) { - return that.areConnectionsCompatible_( - connection, that.markedConnection_); - }); - }, - markConnection: function(connection) { - this.markedConnection_ = connection; - this.notificationsService.speak(Blockly.Msg.ADDED_LINK_MSG); - }, - pasteToMarkedConnection: function(block) { - var xml = Blockly.Xml.blockToDom(block); - var reconstitutedBlock = Blockly.Xml.domToBlock( - blocklyApp.workspace, xml); - - var potentialConnections = [ - reconstitutedBlock.outputConnection, - reconstitutedBlock.previousConnection, - reconstitutedBlock.nextConnection - ]; - - var connectionSuccessful = false; - for (var i = 0; i < potentialConnections.length; i++) { - if (this.areConnectionsCompatible_( - this.markedConnection_, potentialConnections[i])) { - this.markedConnection_.connect(potentialConnections[i]); - this.audioService.playConnectSound(); - connectionSuccessful = true; - break; - } - } - - if (!connectionSuccessful) { - console.error('ERROR: Could not connect block to marked spot.'); - return; - } - - this.markedConnection_ = null; - - return reconstitutedBlock.id; - } -}); diff --git a/accessible/messages.js b/accessible/messages.js index a9db4a423..f43908a9e 100644 --- a/accessible/messages.js +++ b/accessible/messages.js @@ -37,7 +37,7 @@ Blockly.Msg.DELETE = 'Delete block'; Blockly.Msg.MARK_SPOT_BEFORE = 'Add link before'; Blockly.Msg.MARK_SPOT_AFTER = 'Add link after'; Blockly.Msg.MARK_THIS_SPOT = 'Add link inside'; -Blockly.Msg.MOVE_TO_MARKED_SPOT = 'Attach to existing link'; +Blockly.Msg.MOVE_TO_MARKED_SPOT = 'Move to existing link'; Blockly.Msg.PASTE_AFTER = 'Paste after'; Blockly.Msg.PASTE_BEFORE = 'Paste before'; Blockly.Msg.PASTE_INSIDE = 'Paste inside'; diff --git a/accessible/sidebar.component.js b/accessible/sidebar.component.js index 09ef1a159..d614c193e 100644 --- a/accessible/sidebar.component.js +++ b/accessible/sidebar.component.js @@ -61,12 +61,13 @@ blocklyApp.SidebarComponent = ng.core.Component({ }) .Class({ constructor: [ - blocklyApp.NotificationsService, blocklyApp.TreeService, - blocklyApp.UtilsService, blocklyApp.ToolboxModalService, - blocklyApp.ClipboardService, + blocklyApp.BlockConnectionService, + blocklyApp.ToolboxModalService, + blocklyApp.TreeService, + blocklyApp.UtilsService, function( - _notificationsService, _treeService, _utilsService, - _toolboxModalService, _clipboardService) { + blockConnectionService, toolboxModalService, treeService, + utilsService) { // ACCESSIBLE_GLOBALS is a global variable defined by the containing // page. It should contain a key, customSidebarButtons, describing // additional buttons that should be displayed after the default ones. @@ -75,18 +76,18 @@ blocklyApp.SidebarComponent = ng.core.Component({ ACCESSIBLE_GLOBALS && ACCESSIBLE_GLOBALS.customSidebarButtons ? ACCESSIBLE_GLOBALS.customSidebarButtons : []; this.workspace = blocklyApp.workspace; - this.notificationsService = _notificationsService; - this.treeService = _treeService; - this.utilsService = _utilsService; - this.toolboxModalService = _toolboxModalService; - this.clipboardService = _clipboardService; + + this.blockConnectionService = blockConnectionService; + this.toolboxModalService = toolboxModalService; + this.treeService = treeService; + this.utilsService = utilsService; this.ID_FOR_ATTACH_TO_LINK_BUTTON = 'blocklyAttachToLinkBtn'; this.ID_FOR_CREATE_NEW_GROUP_BUTTON = 'blocklyCreateNewGroupBtn'; } ], isAnyConnectionMarked: function() { - return this.clipboardService.isAnyConnectionMarked(); + return this.blockConnectionService.isAnyConnectionMarked(); }, handleButtonClick: function(buttonConfig) { buttonConfig.action(); diff --git a/accessible/toolbox-modal.service.js b/accessible/toolbox-modal.service.js index 126c01ef7..538932f72 100644 --- a/accessible/toolbox-modal.service.js +++ b/accessible/toolbox-modal.service.js @@ -25,15 +25,17 @@ blocklyApp.ToolboxModalService = ng.core.Class({ constructor: [ - blocklyApp.TreeService, blocklyApp.UtilsService, - blocklyApp.NotificationsService, blocklyApp.ClipboardService, + blocklyApp.BlockConnectionService, + blocklyApp.NotificationsService, + blocklyApp.TreeService, + blocklyApp.UtilsService, function( - _treeService, _utilsService, - _notificationsService, _clipboardService) { - this.treeService = _treeService; - this.utilsService = _utilsService; - this.notificationsService = _notificationsService; - this.clipboardService = _clipboardService; + blockConnectionService, notificationsService, treeService, + utilsService) { + this.blockConnectionService = blockConnectionService; + this.notificationsService = notificationsService; + this.treeService = treeService; + this.utilsService = utilsService; this.modalIsShown = false; @@ -109,7 +111,8 @@ blocklyApp.ToolboxModalService = ng.core.Class({ var selectedToolboxCategories = []; this.allToolboxCategories.forEach(function(toolboxCategory) { var selectedBlocks = toolboxCategory.blocks.filter(function(block) { - return that.clipboardService.canBeAttachedToMarkedConnection(block); + return that.blockConnectionService.canBeAttachedToMarkedConnection( + block); }); if (selectedBlocks.length > 0) { @@ -125,9 +128,10 @@ blocklyApp.ToolboxModalService = ng.core.Class({ // Clean up the active desc for the destination tree. var oldDestinationTreeId = that.treeService.getTreeIdForBlock( - that.clipboardService.getMarkedConnectionBlock().id); + that.blockConnectionService.getMarkedConnectionSourceBlock().id); that.treeService.clearActiveDesc(oldDestinationTreeId); - var newBlockId = that.clipboardService.pasteToMarkedConnection(block); + var newBlockId = that.blockConnectionService.attachToMarkedConnection( + block); // Invoke a digest cycle, so that the DOM settles. setTimeout(function() { diff --git a/accessible/tree.service.js b/accessible/tree.service.js index d374d7e32..de6cf26f9 100644 --- a/accessible/tree.service.js +++ b/accessible/tree.service.js @@ -26,19 +26,22 @@ blocklyApp.TreeService = ng.core.Class({ constructor: [ - blocklyApp.NotificationsService, blocklyApp.UtilsService, - blocklyApp.ClipboardService, blocklyApp.BlockOptionsModalService, blocklyApp.AudioService, + blocklyApp.BlockConnectionService, + blocklyApp.BlockOptionsModalService, + blocklyApp.NotificationsService, + blocklyApp.UtilsService, function( - _notificationsService, _utilsService, _clipboardService, - _blockOptionsModalService, _audioService) { + audioService, blockConnectionService, blockOptionsModalService, + notificationsService, utilsService) { + this.audioService = audioService; + this.blockConnectionService = blockConnectionService; + this.blockOptionsModalService = blockOptionsModalService; + this.notificationsService = notificationsService; + this.utilsService = utilsService; + // Stores active descendant ids for each tree in the page. this.activeDescendantIds_ = {}; - this.notificationsService = _notificationsService; - this.utilsService = _utilsService; - this.clipboardService = _clipboardService; - this.blockOptionsModalService = _blockOptionsModalService; - this.audioService = _audioService; } ], // Returns a list of all top-level workspace tree nodes on the page. @@ -273,7 +276,7 @@ blocklyApp.TreeService = ng.core.Class({ if (block.previousConnection) { actionButtonsInfo.push({ action: function() { - that.clipboardService.markConnection(block.previousConnection); + that.blockConnectionService.markConnection(block.previousConnection); that.focusOnBlock(block.id); }, translationIdForText: 'MARK_SPOT_BEFORE' @@ -283,23 +286,23 @@ blocklyApp.TreeService = ng.core.Class({ if (block.nextConnection) { actionButtonsInfo.push({ action: function() { - that.clipboardService.markConnection(block.nextConnection); + that.blockConnectionService.markConnection(block.nextConnection); that.focusOnBlock(block.id); }, translationIdForText: 'MARK_SPOT_AFTER' }); } - if (this.clipboardService.isMovableToMarkedConnection(block)) { + if (this.blockConnectionService.canBeMovedToMarkedConnection(block)) { actionButtonsInfo.push({ action: function() { var blockDescription = that.utilsService.getBlockDescription( block); var oldDestinationTreeId = that.getTreeIdForBlock( - that.clipboardService.getMarkedConnectionBlock().id); + that.blockConnectionService.getMarkedConnectionSourceBlock().id); that.clearActiveDesc(oldDestinationTreeId); - var newBlockId = that.clipboardService.pasteToMarkedConnection( + var newBlockId = that.blockConnectionService.attachToMarkedConnection( block); that.removeBlockAndSetFocus(block, blockRootNode, function() { diff --git a/accessible/workspace-tree.component.js b/accessible/workspace-tree.component.js index 13ae7c6a7..1963ceb73 100644 --- a/accessible/workspace-tree.component.js +++ b/accessible/workspace-tree.component.js @@ -78,15 +78,17 @@ blocklyApp.WorkspaceTreeComponent = ng.core.Component({ }) .Class({ constructor: [ - blocklyApp.ClipboardService, blocklyApp.TreeService, - blocklyApp.UtilsService, blocklyApp.AudioService, - function( - _clipboardService, _treeService, _utilsService, _audioService) { - this.clipboardService = _clipboardService; - this.treeService = _treeService; - this.utilsService = _utilsService; - this.audioService = _audioService; - }], + blocklyApp.AudioService, + blocklyApp.BlockConnectionService, + blocklyApp.TreeService, + blocklyApp.UtilsService, + function(audioService, blockConnectionService, treeService, utilsService) { + this.audioService = audioService; + this.blockConnectionService = blockConnectionService; + this.treeService = treeService; + this.utilsService = utilsService; + } + ], ngOnInit: function() { var SUPPORTED_FIELDS = [ Blockly.FieldTextInput, Blockly.FieldDropdown, @@ -137,7 +139,7 @@ blocklyApp.WorkspaceTreeComponent = ng.core.Component({ baseIdKey: 'markSpot', translationIdForText: 'MARK_THIS_SPOT', action: function(connection) { - that.clipboardService.markConnection(connection); + that.blockConnectionService.markConnection(connection); }, isDisabled: function() { return false; diff --git a/demos/accessible/index.html b/demos/accessible/index.html index 9c36da551..fde57ba8c 100644 --- a/demos/accessible/index.html +++ b/demos/accessible/index.html @@ -20,10 +20,10 @@ + + - -