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 @@
+
+
-
-