diff --git a/accessible/clipboard.service.js b/accessible/clipboard.service.js
index 035939104..43c93b278 100644
--- a/accessible/clipboard.service.js
+++ b/accessible/clipboard.service.js
@@ -45,7 +45,7 @@ blocklyApp.ClipboardService = ng.core
this.areConnectionsCompatible_(connection, superiorConnection) ||
this.areConnectionsCompatible_(connection, nextConnection));
},
- canBeMovedToMarkedConnection: function(block) {
+ isMovableToMarkedConnection: function(block) {
// It should not be possible to move a block to one of its own
// connections.
if (this.markedConnection_ &&
diff --git a/accessible/tree.service.js b/accessible/tree.service.js
index 2387de9ae..e464bc788 100644
--- a/accessible/tree.service.js
+++ b/accessible/tree.service.js
@@ -186,11 +186,17 @@ blocklyApp.TreeService = ng.core
var activeDesc = document.getElementById(this.getActiveDescId(treeId));
if (!activeDesc) {
console.error('ERROR: no active descendant for current tree.');
- // TODO(sll): This just gives the focus somewhere to go in the event
- // of an error, but we need to generalize this to other trees (both
- // within and outside the workspace).
- this.setActiveDesc(
- blocklyApp.workspace.topBlocks_[0].id + 'blockSummary', treeId);
+
+ // TODO(sll): Generalize this to other trees (outside the workspace).
+ var workspaceTreeNodes = this.getWorkspaceTreeNodes_();
+ for (var i = 0; i < workspaceTreeNodes.length; i++) {
+ if (workspaceTreeNodes[i].id == treeId) {
+ // Set the active desc to the first child in this tree.
+ this.setActiveDesc(
+ this.getFirstChild(workspaceTreeNodes[i]).id, treeId);
+ break;
+ }
+ }
return;
}
diff --git a/accessible/workspace-tree.component.js b/accessible/workspace-tree.component.js
index 98ef4d775..5e5a094ac 100644
--- a/accessible/workspace-tree.component.js
+++ b/accessible/workspace-tree.component.js
@@ -28,69 +28,29 @@ blocklyApp.WorkspaceTreeComponent = ng.core
.Component({
selector: 'blockly-workspace-tree',
template: `
-
+ [attr.aria-level]="level" aria-selected="false">
{{block.toString()}}
-
+
+
+ [attr.aria-level]="level+1" aria-selected="false">
{{'BLOCK_ACTION_LIST'|translate}}
-
-
- {{'CUT_BLOCK'|translate}}
-
-
- {{'COPY_BLOCK'|translate}}
-
-
-
- {{'PASTE_BELOW'|translate}}
+
+
+
+ {{buttonInfo.translationIdForText|translate}}
-
-
- {{'PASTE_ABOVE'|translate}}
-
-
-
-
- {{'MARK_SPOT_BELOW'|translate}}
-
-
-
- {{'MARK_SPOT_ABOVE'|translate}}
-
-
- {{'MOVE_TO_MARKED_SPOT'|translate}}
-
-
- {{'DELETE'|translate}}
-
+
{{utilsService.getInputTypeLabel(inputBlock.connection)}} {{utilsService.getBlockTypeLabel(inputBlock)}} needed:
-
+
@@ -135,70 +95,15 @@ blocklyApp.WorkspaceTreeComponent = ng.core
})
.Class({
constructor: [
- blocklyApp.ClipboardService, blocklyApp.TreeService, blocklyApp.UtilsService,
+ blocklyApp.ClipboardService, blocklyApp.TreeService,
+ blocklyApp.UtilsService,
function(_clipboardService, _treeService, _utilsService) {
this.infoBlocks = Object.create(null);
this.clipboardService = _clipboardService;
this.treeService = _treeService;
this.utilsService = _utilsService;
}],
- getElementsNeedingIds_: function() {
- var elementsNeedingIds = ['blockSummary', 'listItem', 'label',
- 'cutListItem', 'cutButton', 'copyListItem', 'copyButton',
- 'pasteBelow', 'pasteBelowButton', 'pasteAbove', 'pasteAboveButton',
- 'markBelow', 'markBelowButton', 'markAbove', 'markAboveButton',
- 'sendToSelectedListItem', 'sendToSelectedButton', 'delete',
- 'deleteButton'];
-
- for (var i = 0; i < this.block.inputList.length; i++) {
- var inputBlock = this.block.inputList[i];
- if (inputBlock.connection && !inputBlock.connection.targetBlock()) {
- elementsNeedingIds = elementsNeedingIds.concat(
- ['inputList' + i, 'inputMenuLabel' + i, 'markSpot' + i,
- 'markSpotButton' + i, 'paste' + i, 'pasteButton' + i]);
- }
- }
-
- return elementsNeedingIds;
- },
- ngOnChanges: function() {
- // The ids needs to be generated on every change (as opposed to only on
- // init), so that they will update if, e.g., a new workspace-tree
- // component is added to the middle of an array.
- var elementsNeedingIds = this.getElementsNeedingIds_();
-
- this.idMap = {}
- this.idMap['parentList'] = this.block.id + 'parent';
- for (var i = 0; i < elementsNeedingIds.length; i++) {
- this.idMap[elementsNeedingIds[i]] =
- this.block.id + elementsNeedingIds[i];
- }
- },
- ngAfterViewInit: function() {
- // If this is a top-level tree in the workspace, set its id and active
- // descendant.
- if (this.tree && this.isTopLevel && !this.tree.id) {
- this.tree.id = this.utilsService.generateUniqueId();
- }
-
- if (this.tree && this.isTopLevel &&
- !this.treeService.getActiveDescId(this.tree.id)) {
- this.treeService.setActiveDesc(this.idMap['parentList'], this.tree.id);
- }
- },
- canBeMovedToMarkedConnection: function(block) {
- return this.clipboardService.canBeMovedToMarkedConnection(block);
- },
- hasPreviousConnection: function(block) {
- return Boolean(block.previousConnection);
- },
- hasNextConnection: function(block) {
- return Boolean(block.nextConnection);
- },
- isCompatibleWithClipboard: function(connection) {
- return this.clipboardService.isCompatibleWithClipboard(connection);
- },
- isIsolatedTopLevelBlock: function(block) {
+ isIsolatedTopLevelBlock_: function(block) {
// Returns whether the given block is at the top level, and has no
// siblings.
return Boolean(
@@ -208,62 +113,164 @@ blocklyApp.WorkspaceTreeComponent = ng.core
return topBlock.id == block.id;
}));
},
- generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) {
- return this.utilsService.generateAriaLabelledByAttr(
- mainLabel, secondLabel, isDisabled);
- },
- pasteAbove: function(block) {
- var that = this;
- this.treeService.runWhilePreservingFocus(function() {
- that.clipboardService.pasteFromClipboard(block.previousConnection);
- }, this.tree.id);
- },
- pasteBelow: function(block) {
- var that = this;
- this.treeService.runWhilePreservingFocus(function() {
- that.clipboardService.pasteFromClipboard(block.nextConnection);
- }, this.tree.id);
- },
- getRootNode: function() {
- // Gets the root HTML node for this component.
- return document.getElementById(this.idMap['parentList']);
- },
removeBlockAndSetFocus_: function(block, deleteBlockFunc) {
// This method runs the given function and then does one of two things:
// - If the block is an isolated top-level block, it shifts the tree
// focus.
// - Otherwise, it sets the correct new active desc for the current tree.
- if (this.isIsolatedTopLevelBlock(block)) {
+ if (this.isIsolatedTopLevelBlock_(block)) {
var nextNodeToFocusOn =
this.treeService.getNodeToFocusOnWhenTreeIsDeleted(this.tree.id);
deleteBlockFunc();
nextNodeToFocusOn.focus();
} else {
+ var blockRootNode = document.getElementById(this.idMap['blockRoot']);
var nextActiveDesc =
this.treeService.getNextActiveDescWhenBlockIsDeleted(
- this.getRootNode());
+ blockRootNode);
this.treeService.runWhilePreservingFocus(
deleteBlockFunc, this.tree.id, nextActiveDesc.id);
}
},
- cutToClipboard: function(block) {
+ cutBlock_: function() {
var that = this;
- this.removeBlockAndSetFocus_(block, function() {
- that.clipboardService.cut(block);
+ this.removeBlockAndSetFocus_(this.block, function() {
+ that.clipboardService.cut(that.block);
});
},
- deleteBlock: function(block) {
- this.removeBlockAndSetFocus_(block, function() {
- block.dispose(true);
+ deleteBlock_: function() {
+ var that = this;
+ this.removeBlockAndSetFocus_(this.block, function() {
+ that.block.dispose(true);
});
},
- sendToMarkedSpot: function(block) {
- this.clipboardService.pasteToMarkedConnection(block, false);
+ pasteToConnection_: function(connection) {
+ var that = this;
+ this.treeService.runWhilePreservingFocus(function() {
+ that.clipboardService.pasteFromClipboard(connection);
+ }, this.tree.id);
+ },
+ sendToMarkedSpot_: function() {
+ this.clipboardService.pasteToMarkedConnection(this.block, false);
- this.removeBlockAndSetFocus_(block, function() {
- block.dispose(true);
+ var that = this;
+ this.removeBlockAndSetFocus_(this.block, function() {
+ that.block.dispose(true);
});
- alert('Block moved to marked spot: ' + block.toString());
+ alert('Block moved to marked spot: ' + this.block.toString());
+ },
+ ngOnInit: function() {
+ var that = this;
+
+ // Generate a list of action buttons.
+ this.actionButtonsInfo = [{
+ baseIdKey: 'cut',
+ translationIdForText: 'CUT_BLOCK',
+ action: that.cutBlock_.bind(that),
+ isDisabled: function() {
+ return false;
+ }
+ }, {
+ baseIdKey: 'copy',
+ translationIdForText: 'COPY_BLOCK',
+ action: that.clipboardService.copy.bind(
+ that.clipboardService, that.block, true),
+ isDisabled: function() {
+ return false;
+ }
+ }, {
+ baseIdKey: 'pasteBelow',
+ translationIdForText: 'PASTE_BELOW',
+ action: that.pasteToConnection_.bind(that, that.block.nextConnection),
+ isDisabled: function() {
+ return Boolean(
+ !that.block.nextConnection ||
+ !that.isCompatibleWithClipboard(that.block.nextConnection));
+ }
+ }, {
+ baseIdKey: 'pasteAbove',
+ translationIdForText: 'PASTE_ABOVE',
+ action: that.pasteToConnection_.bind(
+ that, that.block.previousConnection),
+ isDisabled: function() {
+ return Boolean(
+ !that.block.previousConnection ||
+ !that.isCompatibleWithClipboard(that.block.previousConnection));
+ }
+ }, {
+ baseIdKey: 'markBelow',
+ translationIdForText: 'MARK_SPOT_BELOW',
+ action: that.clipboardService.markConnection.bind(
+ that.clipboardService, that.block.nextConnection),
+ isDisabled: function() {
+ return !that.block.nextConnection;
+ }
+ }, {
+ baseIdKey: 'markAbove',
+ translationIdForText: 'MARK_SPOT_ABOVE',
+ action: that.clipboardService.markConnection.bind(
+ that.clipboardService, that.block.previousConnection),
+ isDisabled: function() {
+ return !that.block.previousConnection;
+ }
+ }, {
+ baseIdKey: 'sendToMarkedSpot',
+ translationIdForText: 'MOVE_TO_MARKED_SPOT',
+ action: that.sendToMarkedSpot_.bind(that),
+ isDisabled: function() {
+ return !that.clipboardService.isMovableToMarkedConnection(
+ that.block);
+ }
+ }, {
+ baseIdKey: 'delete',
+ translationIdForText: 'DELETE',
+ action: that.deleteBlock_.bind(that),
+ isDisabled: function() {
+ return false;
+ }
+ }];
+
+ // Make a list of all the id keys.
+ this.idKeys = ['blockRoot', 'blockSummary', 'listItem', 'label'];
+ this.actionButtonsInfo.forEach(function(buttonInfo) {
+ that.idKeys.push(buttonInfo.baseIdKey, buttonInfo.baseIdKey + 'Button');
+ });
+ for (var i = 0; i < this.block.inputList.length; i++) {
+ var inputBlock = this.block.inputList[i];
+ if (inputBlock.connection && !inputBlock.connection.targetBlock()) {
+ that.idKeys.push(
+ 'inputList' + i, 'inputMenuLabel' + i, 'markSpot' + i,
+ 'markSpotButton' + i, 'paste' + i, 'pasteButton' + i);
+ }
+ }
+ },
+ ngDoCheck: function() {
+ // Generate a unique id for each id key. This needs to be done every time
+ // changes happen, but after the first ng-init, in order to force the
+ // element ids to change in cases where, e.g., a block is inserted in the
+ // middle of a sequence of blocks.
+ this.idMap = {};
+ for (var i = 0; i < this.idKeys.length; i++) {
+ this.idMap[this.idKeys[i]] = this.block.id + this.idKeys[i];
+ }
+ },
+ ngAfterViewInit: function() {
+ // If this is a top-level tree in the workspace, set its id and active
+ // descendant.
+ if (this.tree && this.isTopLevel && !this.tree.id) {
+ this.tree.id = this.utilsService.generateUniqueId();
+ }
+ if (this.tree && this.isTopLevel &&
+ !this.treeService.getActiveDescId(this.tree.id)) {
+ this.treeService.setActiveDesc(this.idMap['blockRoot'], this.tree.id);
+ }
+ },
+ generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) {
+ return this.utilsService.generateAriaLabelledByAttr(
+ mainLabel, secondLabel, isDisabled);
+ },
+ isCompatibleWithClipboard: function(connection) {
+ return this.clipboardService.isCompatibleWithClipboard(connection);
}
});