From 6c13b5c81bbb42ff6abd142b2a88e79bb4c8bc60 Mon Sep 17 00:00:00 2001 From: Sean Lip Date: Mon, 13 Jun 2016 17:58:13 -0700 Subject: [PATCH 01/23] Change the TreeService to a singleton. --- accessible/app.component.js | 13 +++- accessible/toolbox.component.js | 3 +- accessible/tree.service.js | 85 +++++++++++++++++--------- accessible/workspace-tree.component.js | 66 +++++++++++--------- accessible/workspace.component.js | 25 ++++---- 5 files changed, 118 insertions(+), 74 deletions(-) diff --git a/accessible/app.component.js b/accessible/app.component.js index 2a8b75218..f81481fc4 100644 --- a/accessible/app.component.js +++ b/accessible/app.component.js @@ -55,11 +55,18 @@ blocklyApp.AppView = ng.core `, directives: [blocklyApp.ToolboxComponent, blocklyApp.WorkspaceComponent], pipes: [blocklyApp.TranslatePipe], - // The clipboard and utils services are declared here, so that all + // The clipboard, tree and utils services are declared here, so that all // components in the application use the same instance of the service. // https://www.sitepoint.com/angular-2-components-providers-classes-factories-values/ - providers: [blocklyApp.ClipboardService, blocklyApp.UtilsService] + providers: [ + blocklyApp.ClipboardService, blocklyApp.TreeService, + blocklyApp.UtilsService] }) .Class({ - constructor: function() {} + constructor: [blocklyApp.TreeService, function(_treeService) { + this.treeService = _treeService; + }], + ngAfterViewInit: function() { + this.treeService.initTreeRegistry(); + } }); diff --git a/accessible/toolbox.component.js b/accessible/toolbox.component.js index f6e8009d1..5240812f4 100644 --- a/accessible/toolbox.component.js +++ b/accessible/toolbox.component.js @@ -66,8 +66,7 @@ blocklyApp.ToolboxComponent = ng.core `, - directives: [blocklyApp.ToolboxTreeComponent], - providers: [blocklyApp.TreeService], + directives: [blocklyApp.ToolboxTreeComponent] }) .Class({ constructor: [ diff --git a/accessible/tree.service.js b/accessible/tree.service.js index 27f5aebfe..3e54e054f 100644 --- a/accessible/tree.service.js +++ b/accessible/tree.service.js @@ -18,8 +18,8 @@ */ /** - * @fileoverview Angular2 Service that handles all tree keyboard navigation. - * A separate TreeService is constructed for each tree in the application. + * @fileoverview Angular2 Service that handles tree keyboard navigation. + * This is a singleton service for the entire application. * * @author madeeha@google.com (Madeeha Ghori) */ @@ -27,8 +27,8 @@ blocklyApp.TreeService = ng.core .Class({ constructor: function() { - blocklyApp.debug && console.log('making a new tree service'); - this.trees = document.getElementsByClassName('blocklyTree'); + // A list of all trees in the application. + this.treeRegistry = []; // Keeping track of the last key pressed. If the user presses // enter (to edit a text input or press a button), the keyboard // focus shifts to that element. In the next keystroke, if the user @@ -36,6 +36,57 @@ blocklyApp.TreeService = ng.core // to shift focus back to the tree as a whole. this.previousKey_ = null; }, + initTreeRegistry: function() { + this.treeRegistry = []; + this.treeRegistry.push(document.getElementById('blockly-toolbox-tree')); + // TODO(sll): Extend this to handle injected toolbar buttons. + this.treeRegistry.push(document.getElementById('clear-workspace')); + + // Focus on the toolbox tree. + this.treeRegistry[0].focus(); + }, + isTreeInRegistry: function(treeId) { + return this.treeRegistry.some(function(tree) { + return tree.id == treeId; + }); + }, + addTreeToRegistry: function(tree) { + this.treeRegistry.push(tree); + }, + deleteTreeFromRegistry: function(treeId) { + // Shift focus to the next tree (if it exists), otherwise shift focus + // to the previous tree. + var movedToNextTree = this.goToNextTree(treeId); + if (!movedToNextTree) { + this.goToPreviousTree(treeId); + } + + // Delete the tree from the tree registry. + for (var i = 0; i < this.treeRegistry.length; i++) { + if (this.treeRegistry[i].id == treeId) { + this.treeRegistry.splice(i, 1); + break; + } + } + }, + goToNextTree: function(treeId) { + for (var i = 0; i < this.treeRegistry.length - 1; i++) { + if (this.treeRegistry[i].id == treeId) { + this.treeRegistry[i + 1].focus(); + return true; + } + } + return false; + }, + goToPreviousTree: function(treeId) { + for (var i = this.treeRegistry.length - 1; i > 0; i--) { + if (this.treeRegistry[i].id == treeId) { + this.treeRegistry[i - 1].focus(); + return true; + } + } + return false; + }, // Make a given node the active descendant of a given tree. setActiveDesc: function(node, tree, keepFocus) { blocklyApp.debug && console.log('setting activeDesc for tree ' + tree.id); @@ -62,7 +113,8 @@ blocklyApp.TreeService = ng.core return document.getElementById(activeDescendantId); }, onWorkspaceToolbarKeypress: function(e, treeId) { - blocklyApp.debug && console.log(e.keyCode + 'inside TreeService onWorkspaceToolbarKeypress'); + blocklyApp.debug && console.log( + e.keyCode + 'inside TreeService onWorkspaceToolbarKeypress'); switch (e.keyCode) { case 9: // 16,9: shift, tab @@ -79,29 +131,6 @@ blocklyApp.TreeService = ng.core break; } }, - goToNextTree: function(treeId, e) { - for (var i = 0; i < this.trees.length; i++) { - if (this.trees[i].id == treeId) { - if (i + 1 < this.trees.length) { - this.trees[i + 1].focus(); - } - break; - } - } - }, - goToPreviousTree: function(treeId, e) { - if (treeId == this.trees[0].id) { - return; - } - for (var i = (this.trees.length - 1); i >= 0; i--) { - if (this.trees[i].id == treeId) { - if (i - 1 < this.trees.length) { - this.trees[i - 1].focus(); - } - break; - } - } - }, onKeypress: function(e, tree) { var treeId = tree.id; var node = this.getActiveDesc(treeId); diff --git a/accessible/workspace-tree.component.js b/accessible/workspace-tree.component.js index 25cccc9b1..a58b6e35e 100644 --- a/accessible/workspace-tree.component.js +++ b/accessible/workspace-tree.component.js @@ -41,7 +41,7 @@ blocklyApp.WorkspaceTreeComponent = ng.core
  • - +
  • -
  • + [block]="inputBlock.connection.targetBlock()" [level]="level">
  • `, directives: [blocklyApp.FieldComponent, ng.core.forwardRef(function() { return blocklyApp.WorkspaceTreeComponent; })], - inputs: ['block', 'isTopBlock', 'topBlockIndex', 'level', 'parentId', 'tree'], - pipes: [blocklyApp.TranslatePipe], - providers: [blocklyApp.TreeService], + // The 'tree' input is only passed down at the top level. + inputs: ['block', 'level', 'tree'], + pipes: [blocklyApp.TranslatePipe] }) .Class({ constructor: [ @@ -160,16 +158,15 @@ blocklyApp.WorkspaceTreeComponent = ng.core } } this.idMap = this.utilsService.generateIds(elementsNeedingIds); - this.idMap['parentList'] = - this.isTopBlock ? this.parentId + '-node0' : - this.utilsService.generateUniqueId(); + this.idMap['parentList'] = this.utilsService.generateUniqueId(); }, ngAfterViewInit: function() { - // If this is a top-level tree in the workspace, set its active - // descendant after the ids have been computed. + // If this is a top-level tree in the workspace, add it to the tree + // registry and set its active descendant. if (this.tree && - this.tree.getAttribute('aria-activedescendant') == - this.idMap['parentList']) { + (!this.tree.id || !this.treeService.isTreeInRegistry(this.tree.id))) { + this.tree.id = this.utilsService.generateUniqueId(); + this.treeService.addTreeToRegistry(this.tree); this.treeService.setActiveDesc( document.getElementById(this.idMap['parentList']), this.tree); @@ -179,16 +176,24 @@ blocklyApp.WorkspaceTreeComponent = ng.core return this.clipboardService.isClipboardCompatibleWithConnection( connection); }, - deleteBlock: function(block) { - // If this is the top block, shift focus to the previous tree. - var topBlocks = blocklyApp.workspace.topBlocks_; - for (var i = 0; i < topBlocks.length; i++) { - if (topBlocks[i].id == block.id) { - this.treeService.goToPreviousTree(this.parentId); - break; - } + isTopLevelBlock: function(block) { + return blocklyApp.workspace.topBlocks_.some(function(topBlock) { + return topBlock.id == block.id; + }); + }, + cutToClipboard: function(block) { + if (this.isTopLevelBlock(block)) { + this.treeService.deleteTreeFromRegistry(this.tree.id); } - // If this is not the top block, change the active descendant of the tree. + + this.clipboardService.cut(block); + }, + deleteBlock: function(block, cutToClipboard) { + if (this.isTopLevelBlock(block)) { + this.treeService.deleteTreeFromRegistry(this.tree.id); + } + + // TODO(sll): Change the active descendant of the tree. block.dispose(true); }, generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) { @@ -201,11 +206,14 @@ blocklyApp.WorkspaceTreeComponent = ng.core hasNextConnection: function(block) { return Boolean(block.nextConnection); }, - sendToSelected: function(block) { - if (this.clipboardService) { - this.clipboardService.pasteToMarkedConnection(block, false); - block.dispose(true); - alert('Block moved to marked spot: ' + block.toString()); + sendToMarkedSpot: function(block) { + this.clipboardService.pasteToMarkedConnection(block, false); + + if (this.isTopLevelBlock(block)) { + this.treeService.deleteTreeFromRegistry(this.tree.id); } + block.dispose(true); + + alert('Block moved to marked spot: ' + block.toString()); } }); diff --git a/accessible/workspace.component.js b/accessible/workspace.component.js index c00cf7e5f..bbe308283 100644 --- a/accessible/workspace.component.js +++ b/accessible/workspace.component.js @@ -37,27 +37,24 @@ blocklyApp.WorkspaceComponent = ng.core {{buttonConfig.text}} -
    -
      - +
    `, directives: [blocklyApp.WorkspaceTreeComponent], - pipes: [blocklyApp.TranslatePipe], - providers: [blocklyApp.TreeService] + pipes: [blocklyApp.TranslatePipe] }) .Class({ constructor: [blocklyApp.TreeService, function(_treeService) { @@ -72,14 +69,18 @@ blocklyApp.WorkspaceComponent = ng.core this.workspace = blocklyApp.workspace; this.treeService = _treeService; }], + clearWorkspace: function() { + this.workspace.clear(); + this.treeService.initTreeRegistry(); + }, onWorkspaceToolbarKeypress: function(event) { - var activeElementId = document.activeElement.id; - this.treeService.onWorkspaceToolbarKeypress(event, activeElementId); + this.treeService.onWorkspaceToolbarKeypress( + event, document.activeElement.id); }, onKeypress: function(event, tree){ this.treeService.onKeypress(event, tree); }, isWorkspaceEmpty: function() { - return !blocklyApp.workspace.topBlocks_.length; + return !this.workspace.topBlocks_.length; } }); From 02b2c219ebab921b3c725f1e8ac20ebc80e29202 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Tue, 14 Jun 2016 17:36:50 -0700 Subject: [PATCH 02/23] Prevent selected block from ending up underneath a bumped block. --- core/rendered_connection.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/rendered_connection.js b/core/rendered_connection.js index c77c53b3a..ef7130dc6 100644 --- a/core/rendered_connection.js +++ b/core/rendered_connection.js @@ -85,7 +85,8 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) reverse = true; } // Raise it to the top for extra visibility. - rootBlock.getSvgRoot().parentNode.appendChild(rootBlock.getSvgRoot()); + var selected = Blockly.selected == rootBlock; + selected || rootBlock.select(); var dx = (staticConnection.x_ + Blockly.SNAP_RADIUS) - this.x_; var dy = (staticConnection.y_ + Blockly.SNAP_RADIUS) - this.y_; if (reverse) { @@ -96,6 +97,7 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) dx = -dx; } rootBlock.moveBy(dx, dy); + selected || rootBlock.unselect(); }; /** From 04ebbb3b765096d397b0549a83427ca8a963adb5 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Tue, 14 Jun 2016 18:42:49 -0700 Subject: [PATCH 03/23] Fix undo on fields with validators with side effects. --- core/events.js | 4 ++++ core/field.js | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/core/events.js b/core/events.js index 9b9974478..67e210b55 100644 --- a/core/events.js +++ b/core/events.js @@ -550,6 +550,10 @@ Blockly.Events.Change.prototype.run = function(forward) { case 'field': var field = block.getField(this.name); if (field) { + // Run the validator for any side-effects it may have. + // The validator's opinion on validity is ignored. + var validator = field.getValidator(); + validator && validator.call(field, value); field.setValue(value); } else { console.warn("Can't set non-existant field: " + this.name); diff --git a/core/field.js b/core/field.js index 305ecd6d6..4eea423ec 100644 --- a/core/field.js +++ b/core/field.js @@ -232,6 +232,14 @@ Blockly.Field.prototype.setValidator = function(handler) { this.validator_ = handler; }; +/** + * Gets the validation function for editable fields. + * @return {Function} Validation function, or null. + */ +Blockly.Field.prototype.getValidator = function() { + return this.validator_; +}; + /** * Gets the group element for this editable field. * Used for measuring the size and for positioning. From 36130cbc396660066c97e8788f0f20e073aa91f1 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Wed, 15 Jun 2016 01:30:04 -0700 Subject: [PATCH 04/23] Don't fire change event on fields that haven't been named yet. --- core/field.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/field.js b/core/field.js index 4eea423ec..0ea102fff 100644 --- a/core/field.js +++ b/core/field.js @@ -156,10 +156,6 @@ Blockly.Field.prototype.init = function() { Blockly.bindEvent_(this.fieldGroup_, 'mouseup', this, this.onMouseUp_); // Force a render. this.updateTextNode_(); - if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Change( - this.sourceBlock_, 'field', this.name, '', this.getValue())); - } }; /** From 019082b795cc63cc9efa8d96e9be663607de0c30 Mon Sep 17 00:00:00 2001 From: Sean Lip Date: Thu, 16 Jun 2016 14:52:38 -0700 Subject: [PATCH 05/23] Fix tree focus issues. --- accessible/app.component.js | 7 +-- accessible/tree.service.js | 80 +++++++++++++++----------- accessible/workspace-tree.component.js | 38 +++++++----- accessible/workspace.component.js | 12 ++-- 4 files changed, 78 insertions(+), 59 deletions(-) diff --git a/accessible/app.component.js b/accessible/app.component.js index f81481fc4..5de03e575 100644 --- a/accessible/app.component.js +++ b/accessible/app.component.js @@ -63,10 +63,5 @@ blocklyApp.AppView = ng.core blocklyApp.UtilsService] }) .Class({ - constructor: [blocklyApp.TreeService, function(_treeService) { - this.treeService = _treeService; - }], - ngAfterViewInit: function() { - this.treeService.initTreeRegistry(); - } + constructor: [function() {}] }); diff --git a/accessible/tree.service.js b/accessible/tree.service.js index 3e54e054f..0b98687af 100644 --- a/accessible/tree.service.js +++ b/accessible/tree.service.js @@ -27,8 +27,6 @@ blocklyApp.TreeService = ng.core .Class({ constructor: function() { - // A list of all trees in the application. - this.treeRegistry = []; // Keeping track of the last key pressed. If the user presses // enter (to edit a text input or press a button), the keyboard // focus shifts to that element. In the next keystroke, if the user @@ -36,52 +34,66 @@ blocklyApp.TreeService = ng.core // to shift focus back to the tree as a whole. this.previousKey_ = null; }, - initTreeRegistry: function() { - this.treeRegistry = []; - this.treeRegistry.push(document.getElementById('blockly-toolbox-tree')); - // TODO(sll): Extend this to handle injected toolbar buttons. - this.treeRegistry.push(document.getElementById('clear-workspace')); - - // Focus on the toolbox tree. - this.treeRegistry[0].focus(); + getToolboxTreeNode_: function() { + return document.getElementById('blockly-toolbox-tree'); }, - isTreeInRegistry: function(treeId) { - return this.treeRegistry.some(function(tree) { + getWorkspaceTreeNodes_: function() { + // Returns a list of all the top-level workspace tree nodes on the page. + return Array.from(document.querySelectorAll('ol.blocklyWorkspaceTree')); + }, + getAllTreeNodes_: function() { + // Returns a list of all top-level tree nodes on the page. + var trees = []; + + trees.push(document.getElementById('blockly-toolbox-tree')); + // TODO(sll): Extend this to handle injected toolbar buttons. + if (blocklyApp.workspace.topBlocks_.length > 0) { + trees.push(document.getElementById('clear-workspace')); + } + + trees = trees.concat(this.getWorkspaceTreeNodes_()); + return trees; + }, + focusOnToolbox: function() { + this.getToolboxTreeNode_().focus(); + }, + isTopLevelWorkspaceTree: function(treeId) { + return this.getWorkspaceTreeNodes_().some(function(tree) { return tree.id == treeId; }); }, - addTreeToRegistry: function(tree) { - this.treeRegistry.push(tree); - }, - deleteTreeFromRegistry: function(treeId) { - // Shift focus to the next tree (if it exists), otherwise shift focus - // to the previous tree. - var movedToNextTree = this.goToNextTree(treeId); - if (!movedToNextTree) { - this.goToPreviousTree(treeId); - } - - // Delete the tree from the tree registry. - for (var i = 0; i < this.treeRegistry.length; i++) { - if (this.treeRegistry[i].id == treeId) { - this.treeRegistry.splice(i, 1); - break; + getNodeToFocusOnWhenTreeIsDeleted: function(deletedTreeId) { + // This returns the node to focus on after the deletion happens. + // We shift focus to the next tree (if it exists), otherwise we shift + // focus to the previous tree. + var workspaceTrees = this.getWorkspaceTreeNodes_(); + for (var i = 0; i < workspaceTrees.length; i++) { + if (workspaceTrees[i].id == deletedTreeId) { + if (i + 1 < workspaceTrees.length) { + return workspaceTrees[i + 1]; + } else if (i > 0) { + return workspaceTrees[i - 1]; + } } } + + return this.getToolboxTreeNode_(); }, goToNextTree: function(treeId) { - for (var i = 0; i < this.treeRegistry.length - 1; i++) { - if (this.treeRegistry[i].id == treeId) { - this.treeRegistry[i + 1].focus(); + var trees = this.getAllTreeNodes_(); + for (var i = 0; i < trees.length - 1; i++) { + if (trees[i].id == treeId) { + trees[i + 1].focus(); return true; } } return false; }, goToPreviousTree: function(treeId) { - for (var i = this.treeRegistry.length - 1; i > 0; i--) { - if (this.treeRegistry[i].id == treeId) { - this.treeRegistry[i - 1].focus(); + var trees = this.getAllTreeNodes_(); + for (var i = trees.length - 1; i > 0; i--) { + if (trees[i].id == treeId) { + trees[i - 1].focus(); return true; } } diff --git a/accessible/workspace-tree.component.js b/accessible/workspace-tree.component.js index a58b6e35e..4793f74b6 100644 --- a/accessible/workspace-tree.component.js +++ b/accessible/workspace-tree.component.js @@ -161,12 +161,12 @@ blocklyApp.WorkspaceTreeComponent = ng.core this.idMap['parentList'] = this.utilsService.generateUniqueId(); }, ngAfterViewInit: function() { - // If this is a top-level tree in the workspace, add it to the tree - // registry and set its active descendant. + // If this is a top-level tree in the workspace, set its active + // descendant. if (this.tree && - (!this.tree.id || !this.treeService.isTreeInRegistry(this.tree.id))) { + (!this.tree.id || + !this.treeService.isTopLevelWorkspaceTree(this.tree.id))) { this.tree.id = this.utilsService.generateUniqueId(); - this.treeService.addTreeToRegistry(this.tree); this.treeService.setActiveDesc( document.getElementById(this.idMap['parentList']), this.tree); @@ -183,18 +183,25 @@ blocklyApp.WorkspaceTreeComponent = ng.core }, cutToClipboard: function(block) { if (this.isTopLevelBlock(block)) { - this.treeService.deleteTreeFromRegistry(this.tree.id); + nextNodeToFocusOn = this.treeService.getNodeToFocusOnWhenTreeIsDeleted( + this.tree.id); + this.clipboardService.cut(block); + nextNodeToFocusOn.focus(); + } else { + // TODO(sll): Change the active descendant of the tree. + this.clipboardService.cut(block); } - - this.clipboardService.cut(block); }, deleteBlock: function(block, cutToClipboard) { if (this.isTopLevelBlock(block)) { - this.treeService.deleteTreeFromRegistry(this.tree.id); + nextNodeToFocusOn = this.treeService.getNodeToFocusOnWhenTreeIsDeleted( + this.tree.id); + block.dispose(true); + nextNodeToFocusOn.focus(); + } else { + // TODO(sll): Change the active descendant of the tree. + block.dispose(true); } - - // TODO(sll): Change the active descendant of the tree. - block.dispose(true); }, generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) { return this.utilsService.generateAriaLabelledByAttr( @@ -210,9 +217,14 @@ blocklyApp.WorkspaceTreeComponent = ng.core this.clipboardService.pasteToMarkedConnection(block, false); if (this.isTopLevelBlock(block)) { - this.treeService.deleteTreeFromRegistry(this.tree.id); + nextNodeToFocusOn = this.treeService.getNodeToFocusOnWhenTreeIsDeleted( + this.tree.id); + block.dispose(true); + nextNodeToFocusOn.focus(); + } else { + // TODO(sll): Change the active descendant of the tree. + block.dispose(true); } - block.dispose(true); alert('Block moved to marked spot: ' + block.toString()); } diff --git a/accessible/workspace.component.js b/accessible/workspace.component.js index bbe308283..527ea2787 100644 --- a/accessible/workspace.component.js +++ b/accessible/workspace.component.js @@ -45,7 +45,7 @@ blocklyApp.WorkspaceComponent = ng.core
      @@ -71,14 +71,14 @@ blocklyApp.WorkspaceComponent = ng.core }], clearWorkspace: function() { this.workspace.clear(); - this.treeService.initTreeRegistry(); + this.treeService.focusOnToolbox(); }, - onWorkspaceToolbarKeypress: function(event) { + onWorkspaceToolbarKeypress: function(e) { this.treeService.onWorkspaceToolbarKeypress( - event, document.activeElement.id); + e, document.activeElement.id); }, - onKeypress: function(event, tree){ - this.treeService.onKeypress(event, tree); + onKeypress: function(e, tree) { + this.treeService.onKeypress(e, tree); }, isWorkspaceEmpty: function() { return !this.workspace.topBlocks_.length; From 850281501f8b84fb4629579c22e4b28369c7c3ef Mon Sep 17 00:00:00 2001 From: Sean Lip Date: Thu, 16 Jun 2016 17:29:21 -0700 Subject: [PATCH 06/23] Fix remaining focus issues on block deletion. --- accessible/tree.service.js | 49 ++++++++++++-------------- accessible/workspace-tree.component.js | 2 +- accessible/workspace.component.js | 7 ++-- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/accessible/tree.service.js b/accessible/tree.service.js index 0b98687af..a7be7aa9e 100644 --- a/accessible/tree.service.js +++ b/accessible/tree.service.js @@ -37,25 +37,20 @@ blocklyApp.TreeService = ng.core getToolboxTreeNode_: function() { return document.getElementById('blockly-toolbox-tree'); }, + getWorkspaceToolbarButtonNodes_: function() { + return Array.from(document.querySelectorAll( + 'button.blocklyWorkspaceToolbarButton')); + }, + // Returns a list of all top-level workspace tree nodes on the page. getWorkspaceTreeNodes_: function() { - // Returns a list of all the top-level workspace tree nodes on the page. return Array.from(document.querySelectorAll('ol.blocklyWorkspaceTree')); }, + // Returns a list of all top-level tree nodes on the page. getAllTreeNodes_: function() { - // Returns a list of all top-level tree nodes on the page. - var trees = []; - - trees.push(document.getElementById('blockly-toolbox-tree')); - // TODO(sll): Extend this to handle injected toolbar buttons. - if (blocklyApp.workspace.topBlocks_.length > 0) { - trees.push(document.getElementById('clear-workspace')); - } - - trees = trees.concat(this.getWorkspaceTreeNodes_()); - return trees; - }, - focusOnToolbox: function() { - this.getToolboxTreeNode_().focus(); + var treeNodes = [this.getToolboxTreeNode_()]; + treeNodes = treeNodes.concat(this.getWorkspaceToolbarButtonNodes_()); + treeNodes = treeNodes.concat(this.getWorkspaceTreeNodes_()); + return treeNodes; }, isTopLevelWorkspaceTree: function(treeId) { return this.getWorkspaceTreeNodes_().some(function(tree) { @@ -66,20 +61,20 @@ blocklyApp.TreeService = ng.core // This returns the node to focus on after the deletion happens. // We shift focus to the next tree (if it exists), otherwise we shift // focus to the previous tree. - var workspaceTrees = this.getWorkspaceTreeNodes_(); - for (var i = 0; i < workspaceTrees.length; i++) { - if (workspaceTrees[i].id == deletedTreeId) { - if (i + 1 < workspaceTrees.length) { - return workspaceTrees[i + 1]; + var trees = this.getAllTreeNodes_(); + for (var i = 0; i < trees.length; i++) { + if (trees[i].id == deletedTreeId) { + if (i + 1 < trees.length) { + return trees[i + 1]; } else if (i > 0) { - return workspaceTrees[i - 1]; + return trees[i - 1]; } } } return this.getToolboxTreeNode_(); }, - goToNextTree: function(treeId) { + focusOnNextTree_: function(treeId) { var trees = this.getAllTreeNodes_(); for (var i = 0; i < trees.length - 1; i++) { if (trees[i].id == treeId) { @@ -89,7 +84,7 @@ blocklyApp.TreeService = ng.core } return false; }, - goToPreviousTree: function(treeId) { + focusOnPreviousTree_: function(treeId) { var trees = this.getAllTreeNodes_(); for (var i = trees.length - 1; i > 0; i--) { if (trees[i].id == treeId) { @@ -133,10 +128,10 @@ blocklyApp.TreeService = ng.core if (e.shiftKey) { blocklyApp.debug && console.log('shifttabbing'); // If the previous key is shift, we're shift-tabbing mode. - this.goToPreviousTree(treeId); + this.focusOnPreviousTree_(treeId); } else { // If previous key isn't shift, we're tabbing. - this.goToNextTree(treeId); + this.focusOnNextTree_(treeId); } e.preventDefault(); e.stopPropagation(); @@ -157,11 +152,11 @@ blocklyApp.TreeService = ng.core if (e.shiftKey) { blocklyApp.debug && console.log('shifttabbing'); // If the previous key is shift, we're shift-tabbing. - this.goToPreviousTree(treeId); + this.focusOnPreviousTree_(treeId); } else { // If previous key isn't shift, we're tabbing // we want to go to the run code button. - this.goToNextTree(treeId); + this.focusOnNextTree_(treeId); } // Setting the previous key variable in each case because // we only want to save the previous navigation keystroke, diff --git a/accessible/workspace-tree.component.js b/accessible/workspace-tree.component.js index 4793f74b6..36a98e872 100644 --- a/accessible/workspace-tree.component.js +++ b/accessible/workspace-tree.component.js @@ -165,7 +165,7 @@ blocklyApp.WorkspaceTreeComponent = ng.core // descendant. if (this.tree && (!this.tree.id || - !this.treeService.isTopLevelWorkspaceTree(this.tree.id))) { + this.treeService.isTopLevelWorkspaceTree(this.tree.id))) { this.tree.id = this.utilsService.generateUniqueId(); this.treeService.setActiveDesc( document.getElementById(this.idMap['parentList']), diff --git a/accessible/workspace.component.js b/accessible/workspace.component.js index 527ea2787..81b3de5cc 100644 --- a/accessible/workspace.component.js +++ b/accessible/workspace.component.js @@ -33,12 +33,14 @@ blocklyApp.WorkspaceComponent = ng.core
      -
      @@ -71,7 +73,6 @@ blocklyApp.WorkspaceComponent = ng.core }], clearWorkspace: function() { this.workspace.clear(); - this.treeService.focusOnToolbox(); }, onWorkspaceToolbarKeypress: function(e) { this.treeService.onWorkspaceToolbarKeypress( From 8f601345bb004b99dcfeb782744de680bd064c6b Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 17 Jun 2016 12:39:18 -0700 Subject: [PATCH 07/23] cache delete areas instead of recalculating them onMouseDown --- core/block_svg.js | 1 - core/flyout.js | 4 ++++ core/toolbox.js | 4 ++++ core/trashcan.js | 4 ++++ core/workspace_svg.js | 3 +++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/block_svg.js b/core/block_svg.js index 8fc64f3a8..c1f2b8df6 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -535,7 +535,6 @@ Blockly.BlockSvg.prototype.onMouseDown_ = function(e) { Blockly.terminateDrag_(); this.select(); Blockly.hideChaff(); - this.workspace.recordDeleteAreas(); if (Blockly.isRightButton(e)) { // Right-click. this.showContextMenu_(e); diff --git a/core/flyout.js b/core/flyout.js index 66d5c2c6e..85aaee983 100644 --- a/core/flyout.js +++ b/core/flyout.js @@ -952,6 +952,10 @@ Blockly.Flyout.prototype.filterForCapacity_ = function() { * @return {goog.math.Rect} Rectangle in which to delete. */ Blockly.Flyout.prototype.getClientRect = function() { + if (!this.svgGroup_) { + return null; + } + var flyoutRect = this.svgGroup_.getBoundingClientRect(); // BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout // area are still deleted. Must be larger than the largest screen size, diff --git a/core/toolbox.js b/core/toolbox.js index d9ca56dcc..b5b27f869 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -390,6 +390,10 @@ Blockly.Toolbox.prototype.clearSelection = function() { * @return {goog.math.Rect} Rectangle in which to delete. */ Blockly.Toolbox.prototype.getClientRect = function() { + if (!this.HtmlDiv) { + return null; + } + // BIG_NUM is offscreen padding so that blocks dragged beyond the toolbox // area are still deleted. Must be smaller than Infinity, but larger than // the largest screen size. diff --git a/core/trashcan.js b/core/trashcan.js index 0a2954308..28baa0f20 100644 --- a/core/trashcan.js +++ b/core/trashcan.js @@ -265,6 +265,10 @@ Blockly.Trashcan.prototype.position = function() { * @return {goog.math.Rect} Rectangle in which to delete. */ Blockly.Trashcan.prototype.getClientRect = function() { + if (!this.svgGroup_) { + return null; + } + var trashRect = this.svgGroup_.getBoundingClientRect(); var left = trashRect.left + this.SPRITE_LEFT_ - this.MARGIN_HOTSPOT_; var top = trashRect.top + this.SPRITE_TOP_ - this.MARGIN_HOTSPOT_; diff --git a/core/workspace_svg.js b/core/workspace_svg.js index de636a7a2..95150df19 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -211,6 +211,7 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { this.addFlyout_(); } this.updateGridPattern_(); + this.recordDeleteAreas(); return this.svgGroup_; }; @@ -355,6 +356,8 @@ Blockly.WorkspaceSvg.prototype.resize = function() { if (this.scrollbar) { this.scrollbar.resize(); } + + this.recordDeleteAreas(); }; /** From c4cad3c6e4d4e3fac0e68a7c49e5f820152794ed Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 17 Jun 2016 14:26:04 -0700 Subject: [PATCH 08/23] Cache screen CTM for performance improvement. --- core/scrollbar.js | 3 ++- core/utils.js | 5 ++--- core/workspace_svg.js | 33 ++++++++++++++++++++++++++++++--- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/core/scrollbar.js b/core/scrollbar.js index ee080b0bc..6381b928f 100644 --- a/core/scrollbar.js +++ b/core/scrollbar.js @@ -606,7 +606,8 @@ Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) { e.stopPropagation(); return; } - var mouseXY = Blockly.mouseToSvg(e, this.workspace_.getParentSvg()); + var mouseXY = Blockly.mouseToSvg(e, this.workspace_.getParentSvg(), + this.workspace_.getInverseScreenCTM()); var mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y; var handleXY = Blockly.getSvgXY_(this.svgHandle_, this.workspace_); diff --git a/core/utils.js b/core/utils.js index 9b9e04d60..19ccc4cc8 100644 --- a/core/utils.js +++ b/core/utils.js @@ -308,14 +308,13 @@ Blockly.isRightButton = function(e) { * The origin (0,0) is the top-left corner of the Blockly svg. * @param {!Event} e Mouse event. * @param {!Element} svg SVG element. + * @param {SVGMatrix} matrix Inverted screen CTM to use. * @return {!Object} Object with .x and .y properties. */ -Blockly.mouseToSvg = function(e, svg) { +Blockly.mouseToSvg = function(e, svg, matrix) { var svgPoint = svg.createSVGPoint(); svgPoint.x = e.clientX; svgPoint.y = e.clientY; - var matrix = svg.getScreenCTM(); - matrix = matrix.inverse(); return svgPoint.matrixTransform(matrix); }; diff --git a/core/workspace_svg.js b/core/workspace_svg.js index de636a7a2..4816349d2 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -144,6 +144,28 @@ Blockly.WorkspaceSvg.prototype.scrollbar = null; */ Blockly.WorkspaceSvg.prototype.lastSound_ = null; +/** + * Inverted screen CTM, for use in mouseToSvg. + * @type {SVGMatrix} + * @private + */ +Blockly.WorkspaceSvg.prototype.inverseScreenCTM_ = null; + +/** + * Getter for the inverted screen CTM. + * @return {SVGMatrix} The matrix to use in mouseToSvg + */ +Blockly.WorkspaceSvg.prototype.getInverseScreenCTM = function() { + return this.inverseScreenCTM_; +}; + +/** + * Update the inverted screen CTM. + */ +Blockly.WorkspaceSvg.prototype.updateInverseScreenCTM = function() { + this.inverseScreenCTM_ = this.getParentSvg().getScreenCTM().inverse(); +}; + /** * Save resize handler data so we can delete it later in dispose. * @param {!Array.} handler Data that can be passed to unbindEvent_. @@ -330,6 +352,7 @@ Blockly.WorkspaceSvg.prototype.resizeContents = function() { // based on contents. this.scrollbar.resize(); } + this.updateInverseScreenCTM(); }; /** @@ -355,6 +378,7 @@ Blockly.WorkspaceSvg.prototype.resize = function() { if (this.scrollbar) { this.scrollbar.resize(); } + this.updateInverseScreenCTM(); }; /** @@ -661,7 +685,8 @@ Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) { */ Blockly.WorkspaceSvg.prototype.startDrag = function(e, xy) { // Record the starting offset between the bubble's location and the mouse. - var point = Blockly.mouseToSvg(e, this.getParentSvg()); + var point = Blockly.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); // Fix scale of mouse event. point.x /= this.scale; point.y /= this.scale; @@ -674,7 +699,8 @@ Blockly.WorkspaceSvg.prototype.startDrag = function(e, xy) { * @return {!goog.math.Coordinate} New location of object. */ Blockly.WorkspaceSvg.prototype.moveDrag = function(e) { - var point = Blockly.mouseToSvg(e, this.getParentSvg()); + var point = Blockly.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); // Fix scale of mouse event. point.x /= this.scale; point.y /= this.scale; @@ -690,7 +716,8 @@ Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) { // TODO: Remove terminateDrag and compensate for coordinate skew during zoom. Blockly.terminateDrag_(); var delta = e.deltaY > 0 ? -1 : 1; - var position = Blockly.mouseToSvg(e, this.getParentSvg()); + var position = Blockly.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); this.zoom(position.x, position.y, delta); e.preventDefault(); }; From 7a1db20765171e90a0736ccf544a305db67f9729 Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Fri, 17 Jun 2016 14:34:28 -0700 Subject: [PATCH 09/23] Allow terminal blocks to replace other terminal blocks (#433) * Allow terminal blocks to replace other terminal blocks * Updated test to allow replacing terminal blocks --- core/connection.js | 8 ++++++-- tests/jsunit/connection_test.js | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/core/connection.js b/core/connection.js index 65d7fd9cf..3b8c82af8 100644 --- a/core/connection.js +++ b/core/connection.js @@ -370,10 +370,14 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) { } // Don't let a block with no next connection bump other blocks out of the - // stack. + // 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 (this.type == Blockly.PREVIOUS_STATEMENT && candidate.isConnected() && - !this.sourceBlock_.nextConnection) { + !this.sourceBlock_.nextConnection && + !candidate.targetBlock().isShadow() && + candidate.targetBlock().nextConnection) { return false; } diff --git a/tests/jsunit/connection_test.js b/tests/jsunit/connection_test.js index bb0729599..0d10e63ea 100644 --- a/tests/jsunit/connection_test.js +++ b/tests/jsunit/connection_test.js @@ -309,7 +309,8 @@ function test_isConnectionAllowed_NoNext() { three.sourceBlock_.previousConnection = three; Blockly.Connection.connectReciprocally_(one, three); - assertFalse(two.isConnectionAllowed(one)); + // A terminal block is allowed to replace another terminal block. + assertTrue(two.isConnectionAllowed(one)); } function testCheckConnection_Okay() { From 3ca593273add30aa19d0aabc829cff406d52d01d Mon Sep 17 00:00:00 2001 From: Sean Lip Date: Fri, 17 Jun 2016 17:42:51 -0700 Subject: [PATCH 10/23] Refactor how activeDescendant is set. Introduce helper functions to ensure that calls like pasteAbove() preserve the focus. --- accessible/toolbox-tree.component.js | 15 +----- accessible/toolbox.component.js | 31 ++++++------ accessible/tree.service.js | 65 ++++++++++++++++---------- accessible/workspace-tree.component.js | 63 +++++++++++++++++-------- accessible/workspace.component.js | 6 ++- 5 files changed, 106 insertions(+), 74 deletions(-) diff --git a/accessible/toolbox-tree.component.js b/accessible/toolbox-tree.component.js index 364cf586e..cc701fc35 100644 --- a/accessible/toolbox-tree.component.js +++ b/accessible/toolbox-tree.component.js @@ -103,7 +103,7 @@ blocklyApp.ToolboxTreeComponent = ng.core return blocklyApp.ToolboxTreeComponent; })], inputs: [ - 'block', 'displayBlockMenu', 'level', 'index', 'tree', 'noCategories'], + 'block', 'displayBlockMenu', 'level', 'index', 'tree', 'noCategories', 'isTopLevel'], pipes: [blocklyApp.TranslatePipe] }) .Class({ @@ -128,23 +128,12 @@ blocklyApp.ToolboxTreeComponent = ng.core elementsNeedingIds.push('listItem' + i, 'listItem' + i + 'Label') } this.idMap = this.utilsService.generateIds(elementsNeedingIds); - if (this.index == 0 && this.noCategories) { + if (this.isTopLevel) { this.idMap['parentList'] = 'blockly-toolbox-tree-node0'; } else { this.idMap['parentList'] = this.utilsService.generateUniqueId(); } }, - ngAfterViewInit: function() { - // If this is a top-level tree in the toolbox, set its active - // descendant after the ids have been computed. - if (this.index == 0 && - this.tree.getAttribute('aria-activedescendant') == - 'blockly-toolbox-tree-node0') { - this.treeService.setActiveDesc( - document.getElementById(this.idMap['parentList']), - this.tree); - } - }, generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) { return this.utilsService.generateAriaLabelledByAttr( mainLabel, secondLabel, isDisabled); diff --git a/accessible/toolbox.component.js b/accessible/toolbox.component.js index 5240812f4..589a73712 100644 --- a/accessible/toolbox.component.js +++ b/accessible/toolbox.component.js @@ -39,17 +39,17 @@ blocklyApp.ToolboxComponent = ng.core [id]="idMap['Parent' + i]" role="treeitem" [ngClass]="{blocklyHasChildren: true, blocklyActiveDescendant: tree.getAttribute('aria-activedescendant') == idMap['Parent' + i]}" *ngFor="#category of toolboxCategories; #i=index" - aria-level="1" aria-selected=false> + aria-level="1" aria-selected=false + [attr.aria-label]="category.attributes.name.value">
      - {{labelCategory(name, i, tree)}}
        + [tree]="tree">
      @@ -59,9 +59,9 @@ blocklyApp.ToolboxComponent = ng.core + [noCategories]="true" + [isTopLevel]="true">
    @@ -94,23 +94,22 @@ blocklyApp.ToolboxComponent = ng.core elementsNeedingIds.push('Parent' + i, 'Label' + i); } this.idMap = this.utilsService.generateIds(elementsNeedingIds); - this.idMap['Parent0'] = 'blockly-toolbox-tree-node0'; + for (var i = 0; i < this.toolboxCategories.length; i++) { + this.idMap['Parent' + i] = 'blockly-toolbox-tree-node' + i; + } } else { // Create a single category is created with all the top-level blocks. this.xmlHasCategories = false; this.toolboxCategories = [Array.from(xmlToolboxElt.children)]; } }, - labelCategory: function(label, i, tree) { - var parent = label.parentNode; - while (parent && parent.tagName != 'LI') { - parent = parent.parentNode; - } - parent.setAttribute('aria-label', label.innerText); - parent.id = 'blockly-toolbox-tree-node' + i; - if (i == 0 && tree.getAttribute('aria-activedescendant') == - 'blockly-toolbox-tree-node0') { - this.treeService.setActiveDesc(parent, tree); + ngAfterViewInit: function() { + // If this is a top-level tree in the toolbox, set its active + // descendant after the ids have been computed. + if (this.xmlHasCategories) { + this.treeService.setActiveDesc( + document.getElementById('blockly-toolbox-tree-node0'), + document.getElementById('blockly-toolbox-tree')); } }, getToolboxWorkspace: function(categoryNode) { diff --git a/accessible/tree.service.js b/accessible/tree.service.js index a7be7aa9e..c7ad06b55 100644 --- a/accessible/tree.service.js +++ b/accessible/tree.service.js @@ -33,6 +33,8 @@ blocklyApp.TreeService = ng.core // navigates away from the element using the arrow keys, we want // to shift focus back to the tree as a whole. this.previousKey_ = null; + // Stores active descendant ids for each tree in the page. + this.activeDescendantIds_ = {}; }, getToolboxTreeNode_: function() { return document.getElementById('blockly-toolbox-tree'); @@ -94,30 +96,43 @@ blocklyApp.TreeService = ng.core } return false; }, - // Make a given node the active descendant of a given tree. - setActiveDesc: function(node, tree, keepFocus) { - blocklyApp.debug && console.log('setting activeDesc for tree ' + tree.id); - - var activeDesc = this.getActiveDesc(tree.id); + getActiveDescId: function(treeId) { + return this.activeDescendantIds_[treeId] || ''; + }, + unmarkActiveDesc_: function(activeDescId) { + var activeDesc = document.getElementById(activeDescId); if (activeDesc) { activeDesc.classList.remove('blocklyActiveDescendant'); activeDesc.setAttribute('aria-selected', 'false'); } - - node.classList.add('blocklyActiveDescendant'); - node.setAttribute('aria-selected', 'true'); - tree.setAttribute('aria-activedescendant', node.id); - - // Make sure keyboard focus is on the entire tree in the case where the - // focus was previously on a button or input element. - if (keepFocus) { - tree.focus(); - } }, - getActiveDesc: function(treeId) { - var activeDescendantId = document.getElementById( - treeId).getAttribute('aria-activedescendant'); - return document.getElementById(activeDescendantId); + markActiveDesc_: function(activeDescId) { + var newActiveDesc = document.getElementById(activeDescId); + newActiveDesc.classList.add('blocklyActiveDescendant'); + newActiveDesc.setAttribute('aria-selected', 'true'); + }, + // Runs the given function while preserving the focus and active descendant + // for the given tree. + runWhilePreservingFocus: function(func, treeId) { + var activeDescId = this.getActiveDescId(treeId); + this.unmarkActiveDesc_(activeDescId); + func(); + + // The timeout is needed in order to give the DOM time to stabilize + // before setting the new active descendant, especially in cases like + // pasteAbove(). + var that = this; + setTimeout(function() { + that.markActiveDesc_(activeDescId); + that.activeDescendantIds_[treeId] = activeDescId; + document.getElementById(treeId).focus(); + }, 0); + }, + // Make a given node the active descendant of a given tree. + setActiveDesc: function(newActiveDesc, tree) { + this.unmarkActiveDesc_(this.getActiveDescId(tree.id)); + this.markActiveDesc_(newActiveDesc.id); + this.activeDescendantIds_[tree.id] = newActiveDesc.id; }, onWorkspaceToolbarKeypress: function(e, treeId) { blocklyApp.debug && console.log( @@ -140,7 +155,7 @@ blocklyApp.TreeService = ng.core }, onKeypress: function(e, tree) { var treeId = tree.id; - var node = this.getActiveDesc(treeId); + var node = document.getElementById(this.getActiveDescId(treeId)); var keepFocus = this.previousKey_ == 13; if (!node) { blocklyApp.debug && console.log('KeyHandler: no active descendant'); @@ -179,7 +194,7 @@ blocklyApp.TreeService = ng.core if (!nextNode || nextNode.className == 'treeview') { return; } - this.setActiveDesc(nextNode, tree, keepFocus); + this.setActiveDesc(nextNode, tree); this.previousKey_ = e.keyCode; e.preventDefault(); e.stopPropagation(); @@ -189,7 +204,7 @@ blocklyApp.TreeService = ng.core blocklyApp.debug && console.log('node passed in: ' + node.id); var prevSibling = this.getPreviousSibling(node); if (prevSibling && prevSibling.tagName != 'H1') { - this.setActiveDesc(prevSibling, tree, keepFocus); + this.setActiveDesc(prevSibling, tree); } else { blocklyApp.debug && console.log('no previous sibling'); } @@ -201,7 +216,7 @@ blocklyApp.TreeService = ng.core blocklyApp.debug && console.log('in right arrow section'); var firstChild = this.getFirstChild(node); if (firstChild) { - this.setActiveDesc(firstChild, tree, keepFocus); + this.setActiveDesc(firstChild, tree); } else { blocklyApp.debug && console.log('no valid child'); } @@ -215,7 +230,7 @@ blocklyApp.TreeService = ng.core blocklyApp.debug && console.log('preventing propogation'); var nextSibling = this.getNextSibling(node); if (nextSibling) { - this.setActiveDesc(nextSibling, tree, keepFocus); + this.setActiveDesc(nextSibling, tree); } else { blocklyApp.debug && console.log('no next sibling'); } @@ -226,7 +241,7 @@ blocklyApp.TreeService = ng.core case 13: // If I've pressed enter, I want to interact with a child. blocklyApp.debug && console.log('enter is pressed'); - var activeDesc = this.getActiveDesc(treeId); + var activeDesc = node; if (activeDesc) { var children = activeDesc.children; var child = children[0]; diff --git a/accessible/workspace-tree.component.js b/accessible/workspace-tree.component.js index 36a98e872..2fe99002c 100644 --- a/accessible/workspace-tree.component.js +++ b/accessible/workspace-tree.component.js @@ -51,7 +51,7 @@ blocklyApp.WorkspaceTreeComponent = ng.core
  • - @@ -59,7 +59,7 @@ blocklyApp.WorkspaceTreeComponent = ng.core
  • - @@ -94,7 +94,8 @@ blocklyApp.WorkspaceTreeComponent = ng.core
    + [block]="inputBlock.connection.targetBlock()" [level]="level" + [tree]="tree">
  • + [level]="level" [tree]="tree"> `, directives: [blocklyApp.FieldComponent, ng.core.forwardRef(function() { return blocklyApp.WorkspaceTreeComponent; })], - // The 'tree' input is only passed down at the top level. - inputs: ['block', 'level', 'tree'], + inputs: ['block', 'level', 'tree', 'isTopLevel'], pipes: [blocklyApp.TranslatePipe] }) .Class({ @@ -142,13 +142,14 @@ blocklyApp.WorkspaceTreeComponent = ng.core this.treeService = _treeService; this.utilsService = _utilsService; }], - ngOnInit: function() { + 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()) { @@ -157,21 +158,39 @@ blocklyApp.WorkspaceTreeComponent = ng.core 'markSpotButton' + i, 'paste' + i, 'pasteButton' + i]); } } - this.idMap = this.utilsService.generateIds(elementsNeedingIds); + + return elementsNeedingIds; + }, + ngOnInit: function() { + var elementsNeedingIds = this.getElementsNeedingIds_(); + + this.idMap = {} this.idMap['parentList'] = this.utilsService.generateUniqueId(); + 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 active + // If this is a top-level tree in the workspace, set its id and active // descendant. - if (this.tree && - (!this.tree.id || - this.treeService.isTopLevelWorkspaceTree(this.tree.id))) { + 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( document.getElementById(this.idMap['parentList']), this.tree); } }, + hasPreviousConnection: function(block) { + return Boolean(block.previousConnection); + }, + hasNextConnection: function(block) { + return Boolean(block.nextConnection); + }, isCompatibleWithClipboard: function(connection) { return this.clipboardService.isClipboardCompatibleWithConnection( connection); @@ -181,6 +200,18 @@ blocklyApp.WorkspaceTreeComponent = ng.core return topBlock.id == block.id; }); }, + 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); + }, cutToClipboard: function(block) { if (this.isTopLevelBlock(block)) { nextNodeToFocusOn = this.treeService.getNodeToFocusOnWhenTreeIsDeleted( @@ -192,7 +223,7 @@ blocklyApp.WorkspaceTreeComponent = ng.core this.clipboardService.cut(block); } }, - deleteBlock: function(block, cutToClipboard) { + deleteBlock: function(block) { if (this.isTopLevelBlock(block)) { nextNodeToFocusOn = this.treeService.getNodeToFocusOnWhenTreeIsDeleted( this.tree.id); @@ -207,12 +238,6 @@ blocklyApp.WorkspaceTreeComponent = ng.core return this.utilsService.generateAriaLabelledByAttr( mainLabel, secondLabel, isDisabled); }, - hasPreviousConnection: function(block) { - return Boolean(block.previousConnection); - }, - hasNextConnection: function(block) { - return Boolean(block.nextConnection); - }, sendToMarkedSpot: function(block) { this.clipboardService.pasteToMarkedConnection(block, false); diff --git a/accessible/workspace.component.js b/accessible/workspace.component.js index 81b3de5cc..38e50fae5 100644 --- a/accessible/workspace.component.js +++ b/accessible/workspace.component.js @@ -48,9 +48,10 @@ blocklyApp.WorkspaceComponent = ng.core
      - +
    @@ -74,6 +75,9 @@ blocklyApp.WorkspaceComponent = ng.core clearWorkspace: function() { this.workspace.clear(); }, + getActiveDescId: function(tree) { + return this.treeService.getActiveDescId(tree.id); + }, onWorkspaceToolbarKeypress: function(e) { this.treeService.onWorkspaceToolbarKeypress( e, document.activeElement.id); From 6502ea502653c784cf81687a93405d78b082f86d Mon Sep 17 00:00:00 2001 From: Sean Lip Date: Mon, 20 Jun 2016 15:05:39 -0700 Subject: [PATCH 11/23] Remove unnecessary logging. --- accessible/clipboard.service.js | 6 ------ accessible/field.component.js | 1 - accessible/tree.service.js | 14 -------------- accessible/utils.service.js | 4 +--- 4 files changed, 1 insertion(+), 24 deletions(-) diff --git a/accessible/clipboard.service.js b/accessible/clipboard.service.js index dee70dfc0..de9b382a3 100644 --- a/accessible/clipboard.service.js +++ b/accessible/clipboard.service.js @@ -25,7 +25,6 @@ blocklyApp.ClipboardService = ng.core .Class({ constructor: function() { - blocklyApp.debug && console.log('Clipboard service constructed'); this.clipboardBlockXml_ = null; this.clipboardBlockSuperiorConnection_ = null; this.clipboardBlockNextConnection_ = null; @@ -35,7 +34,6 @@ blocklyApp.ClipboardService = ng.core var blockSummary = block.toString(); this.copy(block, false); block.dispose(true); - blocklyApp.debug && console.log('cut'); alert(Blockly.Msg.CUT_BLOCK_MSG + blockSummary); }, copy: function(block, announce) { @@ -43,7 +41,6 @@ blocklyApp.ClipboardService = ng.core this.clipboardBlockSuperiorConnection_ = block.outputConnection || block.previousConnection; this.clipboardBlockNextConnection_ = block.nextConnection; - blocklyApp.debug && console.log('copy'); if (announce) { alert(Blockly.Msg.COPIED_BLOCK_MSG + block.toString()); } @@ -61,7 +58,6 @@ blocklyApp.ClipboardService = ng.core default: connection.connect(reconstitutedBlock.outputConnection); } - blocklyApp.debug && console.log('paste'); alert( Blockly.Msg.PASTED_BLOCK_FROM_CLIPBOARD_MSG + reconstitutedBlock.toString()); @@ -73,7 +69,6 @@ blocklyApp.ClipboardService = ng.core this.markedConnection_.connect( reconstitutedBlock.outputConnection || reconstitutedBlock.previousConnection); - blocklyApp.debug && console.log('paste to marked connection'); if (announce) { alert( Blockly.Msg.PASTED_BLOCK_TO_MARKED_SPOT_MSG + @@ -82,7 +77,6 @@ blocklyApp.ClipboardService = ng.core }, markConnection: function(connection) { this.markedConnection_ = connection; - blocklyApp.debug && console.log('mark connection'); alert(Blockly.Msg.MARKED_SPOT_MSG); }, isCompatibleWithConnection_: function(blockConnection, connection) { diff --git a/accessible/field.component.js b/accessible/field.component.js index 6f5f06284..bb6b4cf07 100644 --- a/accessible/field.component.js +++ b/accessible/field.component.js @@ -128,7 +128,6 @@ blocklyApp.FieldComponent = ng.core return; } if (this.field instanceof Blockly.FieldVariable) { - blocklyApp.debug && console.log(); Blockly.FieldVariable.dropdownChange.call(this.field, text); } else { this.field.setValue(text); diff --git a/accessible/tree.service.js b/accessible/tree.service.js index c7ad06b55..84ac80541 100644 --- a/accessible/tree.service.js +++ b/accessible/tree.service.js @@ -135,13 +135,10 @@ blocklyApp.TreeService = ng.core this.activeDescendantIds_[tree.id] = newActiveDesc.id; }, onWorkspaceToolbarKeypress: function(e, treeId) { - blocklyApp.debug && console.log( - e.keyCode + 'inside TreeService onWorkspaceToolbarKeypress'); switch (e.keyCode) { case 9: // 16,9: shift, tab if (e.shiftKey) { - blocklyApp.debug && console.log('shifttabbing'); // If the previous key is shift, we're shift-tabbing mode. this.focusOnPreviousTree_(treeId); } else { @@ -160,12 +157,10 @@ blocklyApp.TreeService = ng.core if (!node) { blocklyApp.debug && console.log('KeyHandler: no active descendant'); } - blocklyApp.debug && console.log(e.keyCode + ': inside TreeService'); switch (e.keyCode) { case 9: // 16,9: shift, tab if (e.shiftKey) { - blocklyApp.debug && console.log('shifttabbing'); // If the previous key is shift, we're shift-tabbing. this.focusOnPreviousTree_(treeId); } else { @@ -182,7 +177,6 @@ blocklyApp.TreeService = ng.core break; case 37: // Left-facing arrow: go out a level, if possible. If not, do nothing. - blocklyApp.debug && console.log('in left arrow section'); var nextNode = node.parentNode; if (node.tagName == 'BUTTON' || node.tagName == 'INPUT') { nextNode = nextNode.parentNode; @@ -201,7 +195,6 @@ blocklyApp.TreeService = ng.core break; case 38: // Up-facing arrow: go up a level, if possible. If not, do nothing. - blocklyApp.debug && console.log('node passed in: ' + node.id); var prevSibling = this.getPreviousSibling(node); if (prevSibling && prevSibling.tagName != 'H1') { this.setActiveDesc(prevSibling, tree); @@ -213,7 +206,6 @@ blocklyApp.TreeService = ng.core e.stopPropagation(); break; case 39: - blocklyApp.debug && console.log('in right arrow section'); var firstChild = this.getFirstChild(node); if (firstChild) { this.setActiveDesc(firstChild, tree); @@ -227,7 +219,6 @@ blocklyApp.TreeService = ng.core case 40: // Down-facing arrow: go down a level, if possible. // If not, do nothing. - blocklyApp.debug && console.log('preventing propogation'); var nextSibling = this.getNextSibling(node); if (nextSibling) { this.setActiveDesc(nextSibling, tree); @@ -240,7 +231,6 @@ blocklyApp.TreeService = ng.core break; case 13: // If I've pressed enter, I want to interact with a child. - blocklyApp.debug && console.log('enter is pressed'); var activeDesc = node; if (activeDesc) { var children = activeDesc.children; @@ -319,15 +309,12 @@ blocklyApp.TreeService = ng.core } else { var parent = element.parentNode; while (parent) { - blocklyApp.debug && console.log('looping'); if (parent.tagName == 'OL') { break; } if (parent.previousElementSibling) { - blocklyApp.debug && console.log('parent has a sibling!'); var node = parent.previousElementSibling; if (node.tagName == 'LI') { - blocklyApp.debug && console.log('return the sibling of the parent!'); return node; } else { // Find the last list element child of the sibling of the parent. @@ -342,7 +329,6 @@ blocklyApp.TreeService = ng.core }, getLastChild: function(element) { if (!element) { - blocklyApp.debug && console.log('no element'); return element; } else { var childList = element.children; diff --git a/accessible/utils.service.js b/accessible/utils.service.js index 7a1e0d273..5afd89698 100644 --- a/accessible/utils.service.js +++ b/accessible/utils.service.js @@ -29,9 +29,7 @@ var blocklyApp = {}; blocklyApp.UtilsService = ng.core .Class({ - constructor: function() { - blocklyApp.debug && console.log('Utils service constructed'); - }, + constructor: function() {}, generateUniqueId: function() { return 'blockly-' + Blockly.genUid(); }, From 4b319d461df5c1d7490bfacbf0d967035c949002 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Mon, 20 Jun 2016 17:34:36 -0700 Subject: [PATCH 12/23] Reduce unneeded parentheses in JS and Python. --- core/generator.js | 42 ++++++++++++---- generators/dart/lists.js | 8 +--- generators/javascript.js | 87 +++++++++++++++++++++------------- generators/javascript/lists.js | 8 +--- generators/lua/lists.js | 6 +-- generators/php/lists.js | 6 +-- generators/python.js | 10 ++++ generators/python/lists.js | 8 +--- 8 files changed, 107 insertions(+), 68 deletions(-) diff --git a/core/generator.js b/core/generator.js index 3c6a5c7e1..8ee110f86 100644 --- a/core/generator.js +++ b/core/generator.js @@ -77,6 +77,12 @@ Blockly.Generator.prototype.INDENT = ' '; */ Blockly.Generator.prototype.COMMENT_WRAP = 60; +/** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array.>} + */ +Blockly.Generator.prototype.ORDER_OVERRIDES = []; + /** * Generate code for all blocks in the workspace to the specified language. * @param {Blockly.Workspace} workspace Workspace to generate code from. @@ -198,13 +204,13 @@ Blockly.Generator.prototype.blockToCode = function(block) { * Generate code representing the specified value input. * @param {!Blockly.Block} block The block containing the input. * @param {string} name The name of the input. - * @param {number} order The maximum binding strength (minimum order value) + * @param {number} outerOrder The maximum binding strength (minimum order value) * of any operators adjacent to "block". * @return {string} Generated code or '' if no blocks are connected or the * specified input does not exist. */ -Blockly.Generator.prototype.valueToCode = function(block, name, order) { - if (isNaN(order)) { +Blockly.Generator.prototype.valueToCode = function(block, name, outerOrder) { + if (isNaN(outerOrder)) { goog.asserts.fail('Expecting valid order from block "%s".', block.type); } var targetBlock = block.getInputTargetBlock(name); @@ -226,8 +232,17 @@ Blockly.Generator.prototype.valueToCode = function(block, name, order) { goog.asserts.fail('Expecting valid order from value block "%s".', targetBlock.type); } - if (code && order <= innerOrder) { - if (order == innerOrder && (order == 0 || order == 99)) { + if (!code) { + return ''; + } + + // Add parentheses if needed. + var parensNeeded = false; + var outerOrderClass = Math.floor(outerOrder); + var innerOrderClass = Math.floor(innerOrder); + if (outerOrderClass <= innerOrderClass) { + if (outerOrderClass == innerOrderClass && + (outerOrderClass == 0 || outerOrderClass == 99)) { // Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs. // 0 is the atomic order, 99 is the none order. No parentheses needed. // In all known languages multiple such code blocks are not order @@ -236,11 +251,22 @@ Blockly.Generator.prototype.valueToCode = function(block, name, order) { // The operators outside this code are stonger than the operators // inside this code. To prevent the code from being pulled apart, // wrap the code in parentheses. - // Technically, this should be handled on a language-by-language basis. - // However all known (sane) languages use parentheses for grouping. - code = '(' + code + ')'; + parensNeeded = true; + // Check for special exceptions. + for (var i = 0; i < this.ORDER_OVERRIDES.length; i++) { + if (this.ORDER_OVERRIDES[i][0] == outerOrder && + this.ORDER_OVERRIDES[i][1] == innerOrder) { + parensNeeded = false; + break; + } + } } } + if (parensNeeded) { + // Technically, this should be handled on a language-by-language basis. + // However all known (sane) languages use parentheses for grouping. + code = '(' + code + ')'; + } return code; }; diff --git a/generators/dart/lists.js b/generators/dart/lists.js index c357ddd0a..24fcd2243 100644 --- a/generators/dart/lists.js +++ b/generators/dart/lists.js @@ -90,12 +90,8 @@ Blockly.Dart['lists_getIndex'] = function(block) { var where = block.getFieldValue('WHERE') || 'FROM_START'; var at = Blockly.Dart.valueToCode(block, 'AT', Blockly.Dart.ORDER_UNARY_PREFIX) || '1'; - // Special case to avoid wrapping function calls in unneeded parenthesis. - // func()[0] is prefered over (func())[0] - var valueBlock = this.getInputTargetBlock('VALUE'); - var order = (valueBlock && valueBlock.type == 'procedures_callreturn') ? - Blockly.Dart.ORDER_NONE : Blockly.Dart.ORDER_UNARY_POSTFIX; - var list = Blockly.Dart.valueToCode(block, 'VALUE', order) || '[]'; + var list = Blockly.Dart.valueToCode(block, 'VALUE', + Blockly.Dart.ORDER_UNARY_POSTFIX) || '[]'; if (where == 'FIRST') { if (mode == 'GET') { diff --git a/generators/javascript.js b/generators/javascript.js index f228a045d..1254c88e8 100644 --- a/generators/javascript.js +++ b/generators/javascript.js @@ -70,38 +70,61 @@ Blockly.JavaScript.addReservedWords( * Order of operation ENUMs. * https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence */ -Blockly.JavaScript.ORDER_ATOMIC = 0; // 0 "" ... -Blockly.JavaScript.ORDER_MEMBER = 1; // . [] -Blockly.JavaScript.ORDER_NEW = 1; // new -Blockly.JavaScript.ORDER_FUNCTION_CALL = 2; // () -Blockly.JavaScript.ORDER_INCREMENT = 3; // ++ -Blockly.JavaScript.ORDER_DECREMENT = 3; // -- -Blockly.JavaScript.ORDER_LOGICAL_NOT = 4; // ! -Blockly.JavaScript.ORDER_BITWISE_NOT = 4; // ~ -Blockly.JavaScript.ORDER_UNARY_PLUS = 4; // + -Blockly.JavaScript.ORDER_UNARY_NEGATION = 4; // - -Blockly.JavaScript.ORDER_TYPEOF = 4; // typeof -Blockly.JavaScript.ORDER_VOID = 4; // void -Blockly.JavaScript.ORDER_DELETE = 4; // delete -Blockly.JavaScript.ORDER_MULTIPLICATION = 5; // * -Blockly.JavaScript.ORDER_DIVISION = 5; // / -Blockly.JavaScript.ORDER_MODULUS = 5; // % -Blockly.JavaScript.ORDER_ADDITION = 6; // + -Blockly.JavaScript.ORDER_SUBTRACTION = 6; // - -Blockly.JavaScript.ORDER_BITWISE_SHIFT = 7; // << >> >>> -Blockly.JavaScript.ORDER_RELATIONAL = 8; // < <= > >= -Blockly.JavaScript.ORDER_IN = 8; // in -Blockly.JavaScript.ORDER_INSTANCEOF = 8; // instanceof -Blockly.JavaScript.ORDER_EQUALITY = 9; // == != === !== -Blockly.JavaScript.ORDER_BITWISE_AND = 10; // & -Blockly.JavaScript.ORDER_BITWISE_XOR = 11; // ^ -Blockly.JavaScript.ORDER_BITWISE_OR = 12; // | -Blockly.JavaScript.ORDER_LOGICAL_AND = 13; // && -Blockly.JavaScript.ORDER_LOGICAL_OR = 14; // || -Blockly.JavaScript.ORDER_CONDITIONAL = 15; // ?: -Blockly.JavaScript.ORDER_ASSIGNMENT = 16; // = += -= *= /= %= <<= >>= ... -Blockly.JavaScript.ORDER_COMMA = 17; // , -Blockly.JavaScript.ORDER_NONE = 99; // (...) +Blockly.JavaScript.ORDER_ATOMIC = 0; // 0 "" ... +Blockly.JavaScript.ORDER_MEMBER = 1.1; // . [] +Blockly.JavaScript.ORDER_NEW = 1.2; // new +Blockly.JavaScript.ORDER_FUNCTION_CALL = 2; // () +Blockly.JavaScript.ORDER_INCREMENT = 3; // ++ +Blockly.JavaScript.ORDER_DECREMENT = 3; // -- +Blockly.JavaScript.ORDER_LOGICAL_NOT = 4.1; // ! +Blockly.JavaScript.ORDER_BITWISE_NOT = 4.2; // ~ +Blockly.JavaScript.ORDER_UNARY_PLUS = 4.3; // + +Blockly.JavaScript.ORDER_UNARY_NEGATION = 4.4; // - +Blockly.JavaScript.ORDER_TYPEOF = 4.5; // typeof +Blockly.JavaScript.ORDER_VOID = 4.6; // void +Blockly.JavaScript.ORDER_DELETE = 4.7; // delete +Blockly.JavaScript.ORDER_MULTIPLICATION = 5.1; // * +Blockly.JavaScript.ORDER_DIVISION = 5.2; // / +Blockly.JavaScript.ORDER_MODULUS = 5.3; // % +Blockly.JavaScript.ORDER_ADDITION = 6.1; // + +Blockly.JavaScript.ORDER_SUBTRACTION = 6.2; // - +Blockly.JavaScript.ORDER_BITWISE_SHIFT = 7; // << >> >>> +Blockly.JavaScript.ORDER_RELATIONAL = 8; // < <= > >= +Blockly.JavaScript.ORDER_IN = 8; // in +Blockly.JavaScript.ORDER_INSTANCEOF = 8; // instanceof +Blockly.JavaScript.ORDER_EQUALITY = 9; // == != === !== +Blockly.JavaScript.ORDER_BITWISE_AND = 10; // & +Blockly.JavaScript.ORDER_BITWISE_XOR = 11; // ^ +Blockly.JavaScript.ORDER_BITWISE_OR = 12; // | +Blockly.JavaScript.ORDER_LOGICAL_AND = 13; // && +Blockly.JavaScript.ORDER_LOGICAL_OR = 14; // || +Blockly.JavaScript.ORDER_CONDITIONAL = 15; // ?: +Blockly.JavaScript.ORDER_ASSIGNMENT = 16; // = += -= *= /= %= <<= >>= ... +Blockly.JavaScript.ORDER_COMMA = 17; // , +Blockly.JavaScript.ORDER_NONE = 99; // (...) + +/** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array.>} + */ +Blockly.JavaScript.ORDER_OVERRIDES = [ + // (foo()).bar() -> foo().bar() + // (foo())[0] -> foo()[0] + [Blockly.JavaScript.ORDER_FUNCTION_CALL, Blockly.JavaScript.ORDER_MEMBER], + // (foo[0])[1] -> foo[0][1] + // (foo.bar).baz -> foo.bar.baz + [Blockly.JavaScript.ORDER_MEMBER, Blockly.JavaScript.ORDER_MEMBER], + // !(!foo) -> !!foo + [Blockly.JavaScript.ORDER_LOGICAL_NOT, Blockly.JavaScript.ORDER_LOGICAL_NOT], + // a * (b * c) -> a * b * c + [Blockly.JavaScript.ORDER_MULTIPLICATION, Blockly.JavaScript.ORDER_MULTIPLICATION], + // a + (b + c) -> a + b + c + [Blockly.JavaScript.ORDER_ADDITION, Blockly.JavaScript.ORDER_ADDITION], + // a && (b && c) -> a && b && c + [Blockly.JavaScript.ORDER_LOGICAL_AND, Blockly.JavaScript.ORDER_LOGICAL_AND], + // a || (b || c) -> a || b || c + [Blockly.JavaScript.ORDER_LOGICAL_OR, Blockly.JavaScript.ORDER_LOGICAL_OR] +]; /** * Allow for switching between one and zero based indexing, one based by diff --git a/generators/javascript/lists.js b/generators/javascript/lists.js index b05b6d380..4bb6aac20 100644 --- a/generators/javascript/lists.js +++ b/generators/javascript/lists.js @@ -98,12 +98,8 @@ Blockly.JavaScript['lists_getIndex'] = function(block) { var where = block.getFieldValue('WHERE') || 'FROM_START'; var at = Blockly.JavaScript.valueToCode(block, 'AT', Blockly.JavaScript.ORDER_UNARY_NEGATION) || '1'; - // Special case to avoid wrapping function calls in unneeded parenthesis. - // func()[0] is prefered over (func())[0] - var valueBlock = this.getInputTargetBlock('VALUE'); - var order = (valueBlock && valueBlock.type == 'procedures_callreturn') ? - Blockly.JavaScript.ORDER_NONE : Blockly.JavaScript.ORDER_MEMBER; - var list = Blockly.JavaScript.valueToCode(block, 'VALUE', order) || '[]'; + var list = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_MEMBER) || '[]'; if (where == 'FIRST') { if (mode == 'GET') { diff --git a/generators/lua/lists.js b/generators/lua/lists.js index a2afebd05..8c89a1bd3 100644 --- a/generators/lua/lists.js +++ b/generators/lua/lists.js @@ -160,11 +160,7 @@ Blockly.Lua['lists_getIndex'] = function(block) { var at = Blockly.Lua.valueToCode(block, 'AT', Blockly.Lua.ORDER_ADDITIVE) || '1'; if (mode == 'GET') { - // Special case to avoid wrapping function calls in unneeded parenthesis. - // func()[0] is prefered over (func())[0] - var valueBlock = this.getInputTargetBlock('VALUE'); - var order = (valueBlock && valueBlock.type == 'procedures_callreturn') ? - Blockly.Lua.ORDER_NONE : Blockly.Lua.ORDER_HIGH; + var order = Blockly.Lua.ORDER_HIGH; } else { // List will be an argument in a function call. var order = Blockly.Lua.ORDER_NONE; diff --git a/generators/php/lists.js b/generators/php/lists.js index 2176f5f1b..a406aa6d2 100644 --- a/generators/php/lists.js +++ b/generators/php/lists.js @@ -133,11 +133,7 @@ Blockly.PHP['lists_getIndex'] = function(block) { var at = Blockly.PHP.valueToCode(block, 'AT', Blockly.PHP.ORDER_UNARY_NEGATION) || '1'; if (mode == 'GET') { - // Special case to avoid wrapping function calls in unneeded parenthesis. - // func()[0] is prefered over (func())[0] - var valueBlock = this.getInputTargetBlock('VALUE'); - var order = (valueBlock && valueBlock.type == 'procedures_callreturn') ? - Blockly.PHP.ORDER_NONE : Blockly.PHP.ORDER_FUNCTION_CALL; + var order = Blockly.PHP.ORDER_FUNCTION_CALL; } else { // List will be an argument in a function call. var order = Blockly.PHP.ORDER_COMMA; diff --git a/generators/python.js b/generators/python.js index 0e89973dd..18fb26a3e 100644 --- a/generators/python.js +++ b/generators/python.js @@ -79,6 +79,16 @@ Blockly.Python.ORDER_CONDITIONAL = 15; // if else Blockly.Python.ORDER_LAMBDA = 16; // lambda Blockly.Python.ORDER_NONE = 99; // (...) +/** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array.>} + */ +Blockly.Python.ORDER_OVERRIDES = [ + // (foo()).bar() -> foo().bar() + // (foo())[0] -> foo()[0] + [Blockly.Python.ORDER_FUNCTION_CALL, Blockly.Python.ORDER_MEMBER] +]; + /** * Initialise the database of variable names. * @param {!Blockly.Workspace} workspace Workspace to generate code from. diff --git a/generators/python/lists.js b/generators/python/lists.js index afc5d7708..e43e31d5e 100644 --- a/generators/python/lists.js +++ b/generators/python/lists.js @@ -105,12 +105,8 @@ Blockly.Python['lists_getIndex'] = function(block) { var where = block.getFieldValue('WHERE') || 'FROM_START'; var at = Blockly.Python.valueToCode(block, 'AT', Blockly.Python.ORDER_UNARY_SIGN) || '1'; - // Special case to avoid wrapping function calls in unneeded parenthesis. - // func()[0] is prefered over (func())[0] - var valueBlock = this.getInputTargetBlock('VALUE'); - var order = (valueBlock && valueBlock.type == 'procedures_callreturn') ? - Blockly.Python.ORDER_NONE : Blockly.Python.ORDER_MEMBER; - var list = Blockly.Python.valueToCode(block, 'VALUE', order) || '[]'; + var list = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_MEMBER) || '[]'; if (where == 'FIRST') { if (mode == 'GET') { From 732e9b065912e45462f806ce8c6e9ae2c7ebb502 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Tue, 21 Jun 2016 04:10:32 -0700 Subject: [PATCH 13/23] Start using field_number. --- blocks/loops.js | 6 +- blocks/math.js | 3 +- core/block.js | 5 +- core/field_angle.js | 25 +++---- core/field_number.js | 123 +++++++++++++++++++++++++++++++++-- core/field_textinput.js | 2 + demos/blockfactory/blocks.js | 6 +- 7 files changed, 145 insertions(+), 25 deletions(-) diff --git a/blocks/loops.js b/blocks/loops.js index d9d8cbfcd..b9bae1e80 100644 --- a/blocks/loops.js +++ b/blocks/loops.js @@ -73,7 +73,9 @@ Blockly.Blocks['controls_repeat'] = { { "type": "field_number", "name": "TIMES", - "text": "10" + "value": 10, + "min": 0, + "precision": 1 } ], "previousStatement": null, @@ -84,8 +86,6 @@ Blockly.Blocks['controls_repeat'] = { }); this.appendStatementInput('DO') .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); - this.getField('TIMES').setValidator( - Blockly.FieldTextInput.nonnegativeIntegerValidator); } }; diff --git a/blocks/math.js b/blocks/math.js index 311d9875b..475fd6ce2 100644 --- a/blocks/math.js +++ b/blocks/math.js @@ -43,8 +43,7 @@ Blockly.Blocks['math_number'] = { this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL); this.setColour(Blockly.Blocks.math.HUE); this.appendDummyInput() - .appendField(new Blockly.FieldNumber('0', - Blockly.FieldTextInput.numberValidator), 'NUM'); + .appendField(new Blockly.FieldNumber('0'), 'NUM'); this.setOutput(true, 'Number'); // Assign 'this' to a variable for use in the tooltip closure below. var thisBlock = this; diff --git a/core/block.js b/core/block.js index bf6041416..36a703171 100644 --- a/core/block.js +++ b/core/block.js @@ -1111,7 +1111,10 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { element['width'], element['height'], element['alt']); break; case 'field_number': - field = new Blockly.FieldNumber(element['text']); + field = new Blockly.FieldNumber(element['value']); + field.setPrecision(element['precision']); + field.setMin(element['min']); + field.setMax(element['max']); break; case 'field_date': if (Blockly.FieldDate) { diff --git a/core/field_angle.js b/core/field_angle.js index b5ca196ce..35534a426 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -305,16 +305,19 @@ Blockly.FieldAngle.prototype.updateGraph_ = function() { * @return {?string} A string representing a valid angle, or null if invalid. */ Blockly.FieldAngle.angleValidator = function(text) { - var n = Blockly.FieldTextInput.numberValidator(text); - if (n !== null) { - n = n % 360; - if (n < 0) { - n += 360; - } - if (n > Blockly.FieldAngle.WRAP) { - n -= 360; - } - n = String(n); + if (text === null) { + return null; } - return n; + var n = parseFloat(text || 0); + if (isNaN(n)) { + return null; + } + n = n % 360; + if (n < 0) { + n += 360; + } + if (n > Blockly.FieldAngle.WRAP) { + n -= 360; + } + return String(n); }; diff --git a/core/field_number.js b/core/field_number.js index eb92291bf..0ce38bf1e 100644 --- a/core/field_number.js +++ b/core/field_number.js @@ -27,10 +27,11 @@ goog.provide('Blockly.FieldNumber'); goog.require('Blockly.FieldTextInput'); +goog.require('goog.math'); /** * Class for an editable number field. - * @param {string} text The initial content of the field. + * @param {string} value The initial content of the field. * @param {Function=} opt_validator An optional function that is called * to validate any constraints on what the user entered. Takes the new * text as an argument and returns either the accepted text, a replacement @@ -38,8 +39,122 @@ goog.require('Blockly.FieldTextInput'); * @extends {Blockly.FieldTextInput} * @constructor */ -Blockly.FieldNumber = function(text, opt_validator) { - Blockly.FieldNumber.superClass_.constructor.call(this, text, - opt_validator); +Blockly.FieldNumber = function(value, opt_validator) { + Blockly.FieldNumber.superClass_.constructor.call(this, value, opt_validator); }; goog.inherits(Blockly.FieldNumber, Blockly.FieldTextInput); + +/** + * Steps between allowed numbers. + * @private + * @type {number} + */ +Blockly.FieldNumber.prototype.precision_ = 0; + +/** + * Minimum allowed value. + * @private + * @type {number} + */ +Blockly.FieldNumber.prototype.min_ = -Infinity; + +/** + * Maximum allowed value. + * @private + * @type {number} + */ +Blockly.FieldNumber.prototype.max_ = Infinity; + +/** + * Setting precision (usually a power of 10) enforces a minimum step between + * values. That is, the user's value will rounded to the closest multiple of + * precision. The least significant digit place is inferred from the precision. + * Integers values can be enforces by choosing an integer precision. + * @param {number|string|undefined} precision Precision for value. + */ +Blockly.FieldNumber.prototype.setPrecision = function(precision) { + precision = parseFloat(precision); + if (!isNaN(precision)) { + this.precision_ = precision; + } +}; + +/** + * Set a maximum limit on this field's value. + * @param {number|string|undefined} max Maximum value. + */ +Blockly.FieldNumber.prototype.setMin = function(min) { + min = parseFloat(min); + if (!isNaN(min)) { + this.min_ = min; + } +}; + +/** + * Set a maximum limit on this field's value. + * @param {number|string|undefined} max Minimum value. + */ +Blockly.FieldNumber.prototype.setMax = function(max) { + max = parseFloat(max); + if (!isNaN(max)) { + this.max_ = max; + } +}; + +/** + * Sets a new change handler for number field. + * @param {Function} handler New change handler, or null. + */ +Blockly.FieldNumber.prototype.setValidator = function(handler) { + var wrappedHandler; + if (handler) { + // Wrap the user's change handler together with the angle validator. + wrappedHandler = function(value) { + var v1 = handler.call(this, value); + if (v1 === null) { + var v2 = v1; + } else { + if (v1 === undefined) { + v1 = value; + } + var v2 = Blockly.FieldNumber.numberValidator.call(this, v1); + if (v2 === undefined) { + v2 = v1; + } + } + return v2 === value ? undefined : v2; + }; + } else { + wrappedHandler = Blockly.FieldNumber.numberValidator; + } + Blockly.FieldNumber.superClass_.setValidator.call(this, wrappedHandler); +}; + +/** + * Ensure that only a number in the correct range may be entered. + * @param {string} text The user's text. + * @return {?string} A string representing a valid number, or null if invalid. + */ +Blockly.FieldNumber.numberValidator = function(text) { + if (text === null) { + return null; + } + text = String(text); + // TODO: Handle cases like 'ten', '1.203,14', etc. + // 'O' is sometimes mistaken for '0' by inexperienced users. + text = text.replace(/O/ig, '0'); + // Strip out thousands separators. + text = text.replace(/,/g, ''); + var n = parseFloat(text || 0); + if (isNaN(n)) { + // Invalid number. + return null; + } + // Round to nearest multiple of precision. + if (this.precision_ && Number.isFinite(n)) { + n = Math.round(n / this.precision_) * this.precision_; + } + // Get the value in range. + n = goog.math.clamp(n, this.min_, this.max_); + return String(n); +}; diff --git a/core/field_textinput.js b/core/field_textinput.js index 994db7263..6ef6bf1d2 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -296,6 +296,8 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() { * @return {?string} A string representing a valid number, or null if invalid. */ Blockly.FieldTextInput.numberValidator = function(text) { + console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' + + 'Use Blockly.FieldNumber instead.'); if (text === null) { return null; } diff --git a/demos/blockfactory/blocks.js b/demos/blockfactory/blocks.js index ee7cc285a..f2d8dd290 100644 --- a/demos/blockfactory/blocks.js +++ b/demos/blockfactory/blocks.js @@ -470,11 +470,9 @@ Blockly.Blocks['field_image'] = { .appendField(new Blockly.FieldTextInput(src), 'SRC'); this.appendDummyInput() .appendField('width') - .appendField(new Blockly.FieldTextInput('15', - Blockly.FieldTextInput.numberValidator), 'WIDTH') + .appendField(new Blockly.FieldNumber('15'), 'WIDTH') .appendField('height') - .appendField(new Blockly.FieldTextInput('15', - Blockly.FieldTextInput.numberValidator), 'HEIGHT') + .appendField(new Blockly.FieldNumber('15'), 'HEIGHT') .appendField('alt text') .appendField(new Blockly.FieldTextInput('*'), 'ALT'); this.setPreviousStatement(true, 'Field'); From 93125fd1e6fd825a676c3f788cc94e0067ac7957 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Tue, 21 Jun 2016 04:31:45 -0700 Subject: [PATCH 14/23] Make it easy to disable unconnected blocks. --- core/events.js | 31 +++++++++++++++++++++++++++++++ demos/blockfactory/factory.js | 1 + demos/graph/index.html | 2 ++ demos/plane/plane.js | 1 + 4 files changed, 35 insertions(+) diff --git a/core/events.js b/core/events.js index 67e210b55..f1971478d 100644 --- a/core/events.js +++ b/core/events.js @@ -784,3 +784,34 @@ Blockly.Events.Ui.prototype.fromJson = function(json) { this.element = json['element']; this.newValue = json['newValue']; }; + +/** + * Enable/disable a block depending on whether it is properly connected. + * Use this on applications where all blocks should be connected to a top block. + * Recommend setting the 'disable' option to 'false' in the config so that + * users don't try to reenable disabled orphan blocks. + * @param {!Blockly.Events.Abstract} event Custom data for event. + */ +Blockly.Events.disableOrphans = function(event) { + if (event.type == Blockly.Events.MOVE || + event.type == Blockly.Events.CREATE) { + Blockly.Events.disable(); + var workspace = Blockly.Workspace.getById(event.workspaceId); + var block = workspace.getBlockById(event.blockId); + if (block) { + if (block.getParent() && !block.getParent().disabled) { + do { + block.setDisabled(false); + block = block.getNextBlock(); + } while (block); + } else if ((block.outputConnection || block.previousConnection) && + Blockly.dragMode_ == Blockly.DRAG_NONE) { + do { + block.setDisabled(true); + block = block.getNextBlock(); + } while (block); + } + } + Blockly.Events.enable(); + } +}; diff --git a/demos/blockfactory/factory.js b/demos/blockfactory/factory.js index 2d229c316..3e1cf16a0 100644 --- a/demos/blockfactory/factory.js +++ b/demos/blockfactory/factory.js @@ -790,6 +790,7 @@ function init() { } mainWorkspace.clearUndo(); + mainWorkspace.addChangeListener(Blockly.Events.disableOrphans); mainWorkspace.addChangeListener(updateLanguage); document.getElementById('direction') .addEventListener('change', updatePreview); diff --git a/demos/graph/index.html b/demos/graph/index.html index c3ce000cc..4732070d9 100644 --- a/demos/graph/index.html +++ b/demos/graph/index.html @@ -337,6 +337,7 @@ Graph.resize = function() { Graph.init = function() { Graph.workspace = Blockly.inject('blocklyDiv', {collapse: false, + disable: false, media: '../../media/', toolbox: document.getElementById('toolbox')}); Blockly.Xml.domToWorkspace(document.getElementById('startBlocks'), @@ -345,6 +346,7 @@ Graph.init = function() { // When Blockly changes, update the graph. Graph.workspace.addChangeListener(Graph.drawVisualization); + Graph.workspace.addChangeListener(Blockly.Events.disableOrphans); Graph.resize(); }; diff --git a/demos/plane/plane.js b/demos/plane/plane.js index 25e350fb6..99e5c175c 100644 --- a/demos/plane/plane.js +++ b/demos/plane/plane.js @@ -276,6 +276,7 @@ Plane.init = function() { Plane.loadBlocks(defaultXml); Plane.workspace.addChangeListener(Plane.recalculate); + Plane.workspace.addChangeListener(Blockly.Events.disableOrphans); // Initialize the slider. var svg = document.getElementById('plane'); From 26bbe74ba10d3a1e93045ab3038809fa3d8352fd Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Tue, 21 Jun 2016 04:33:35 -0700 Subject: [PATCH 15/23] Routine recompile. --- blockly_compressed.js | 57 +++++++++++++++++++++------------------- blockly_uncompressed.js | 8 +++--- blocks_compressed.js | 4 +-- dart_compressed.js | 10 +++---- javascript_compressed.js | 16 ++++++----- lua_compressed.js | 6 ++--- msg/js/be-tarask.js | 2 +- msg/js/cs.js | 8 +++--- msg/js/zh-hans.js | 2 +- php_compressed.js | 18 ++++++------- python_compressed.js | 10 +++---- 11 files changed, 74 insertions(+), 67 deletions(-) diff --git a/blockly_compressed.js b/blockly_compressed.js index 4bb4256ff..8fa47ec9c 100644 --- a/blockly_compressed.js +++ b/blockly_compressed.js @@ -912,8 +912,8 @@ Blockly.Connection.prototype.canConnectWithReason_=function(a){if(!a)return Bloc Blockly.Connection.CAN_CONNECT:Blockly.Connection.REASON_CHECKS_FAILED}; Blockly.Connection.prototype.checkConnection_=function(a){switch(this.canConnectWithReason_(a)){case Blockly.Connection.CAN_CONNECT:break;case Blockly.Connection.REASON_SELF_CONNECTION:throw"Attempted to connect a block to itself.";case Blockly.Connection.REASON_DIFFERENT_WORKSPACES:throw"Blocks not on same workspace.";case Blockly.Connection.REASON_WRONG_TYPE:throw"Attempt to connect incompatible types.";case Blockly.Connection.REASON_TARGET_NULL:throw"Target connection is null.";case Blockly.Connection.REASON_CHECKS_FAILED:throw"Connection checks failed."; case Blockly.Connection.REASON_SHADOW_PARENT:throw"Connecting non-shadow to shadow block.";default:throw"Unknown connection failure: this should never happen!";}}; -Blockly.Connection.prototype.isConnectionAllowed=function(a){if(this.canConnectWithReason_(a)!=Blockly.Connection.CAN_CONNECT)return!1;if(a.type==Blockly.OUTPUT_VALUE||a.type==Blockly.PREVIOUS_STATEMENT)if(a.isConnected()||this.isConnected())return!1;return a.type==Blockly.INPUT_VALUE&&a.isConnected()&&!a.targetBlock().isMovable()&&!a.targetBlock().isShadow()||this.type==Blockly.PREVIOUS_STATEMENT&&a.isConnected()&&!this.sourceBlock_.nextConnection||-1!=Blockly.draggingConnections_.indexOf(a)?!1: -!0};Blockly.Connection.prototype.connect=function(a){this.targetConnection!=a&&(this.checkConnection_(a),this.isSuperior()?this.connect_(a):a.connect_(this))};Blockly.Connection.connectReciprocally_=function(a,b){goog.asserts.assert(a&&b,"Cannot connect null connections.");a.targetConnection=b;b.targetConnection=a}; +Blockly.Connection.prototype.isConnectionAllowed=function(a){if(this.canConnectWithReason_(a)!=Blockly.Connection.CAN_CONNECT)return!1;if(a.type==Blockly.OUTPUT_VALUE||a.type==Blockly.PREVIOUS_STATEMENT)if(a.isConnected()||this.isConnected())return!1;return a.type==Blockly.INPUT_VALUE&&a.isConnected()&&!a.targetBlock().isMovable()&&!a.targetBlock().isShadow()||this.type==Blockly.PREVIOUS_STATEMENT&&a.isConnected()&&!this.sourceBlock_.nextConnection&&!a.targetBlock().isShadow()&&a.targetBlock().nextConnection|| +-1!=Blockly.draggingConnections_.indexOf(a)?!1:!0};Blockly.Connection.prototype.connect=function(a){this.targetConnection!=a&&(this.checkConnection_(a),this.isSuperior()?this.connect_(a):a.connect_(this))};Blockly.Connection.connectReciprocally_=function(a,b){goog.asserts.assert(a&&b,"Cannot connect null connections.");a.targetConnection=b;b.targetConnection=a}; Blockly.Connection.singleConnection_=function(a,b){for(var c=!1,d=0;dthis.lidOpen_&&(this.lidTask_=goog.Timer.callOnce(this.animateLid_,20,this))}; Blockly.Trashcan.prototype.close=function(){this.setOpen_(!1)};Blockly.Trashcan.prototype.click=function(){var a=this.workspace_.startScrollX-this.workspace_.scrollX,b=this.workspace_.startScrollY-this.workspace_.scrollY;Math.sqrt(a*a+b*b)>Blockly.DRAG_RADIUS||console.log("TODO: Inspect trash.")};Blockly.Xml={};Blockly.Xml.workspaceToDom=function(a){var b=goog.dom.createDom("xml");a=a.getTopBlocks(!0);for(var c=0,d;d=a[c];c++)b.appendChild(Blockly.Xml.blockToDomWithXY(d));return b};Blockly.Xml.blockToDomWithXY=function(a){var b;a.workspace.RTL&&(b=a.workspace.getWidth());var c=Blockly.Xml.blockToDom(a),d=a.getRelativeToSurfaceXY();c.setAttribute("x",Math.round(a.workspace.RTL?b-d.x:d.x));c.setAttribute("y",Math.round(d.y));return c}; Blockly.Xml.blockToDom=function(a){var b=goog.dom.createDom(a.isShadow()?"shadow":"block");b.setAttribute("type",a.type);b.setAttribute("id",a.id);if(a.mutationToDom){var c=a.mutationToDom();c&&(c.hasChildNodes()||c.hasAttributes())&&b.appendChild(c)}for(var c=0,d;d=a.inputList[c];c++)for(var e=0,f;f=d.fieldRow[e];e++)if(f.name&&f.EDITABLE){var g=goog.dom.createDom("field",null,f.getValue());g.setAttribute("name",f.name);b.appendChild(g)}if(c=a.getCommentText())c=goog.dom.createDom("comment",null, @@ -1019,12 +1018,12 @@ Blockly.WorkspaceSvg=function(a){Blockly.WorkspaceSvg.superClass_.constructor.ca Blockly.WorkspaceSvg.prototype.scrollY=0;Blockly.WorkspaceSvg.prototype.startScrollX=0;Blockly.WorkspaceSvg.prototype.startScrollY=0;Blockly.WorkspaceSvg.prototype.dragDeltaXY_=null;Blockly.WorkspaceSvg.prototype.scale=1;Blockly.WorkspaceSvg.prototype.trashcan=null;Blockly.WorkspaceSvg.prototype.scrollbar=null;Blockly.WorkspaceSvg.prototype.lastSound_=null;Blockly.WorkspaceSvg.prototype.setResizeHandlerWrapper=function(a){this.resizeHandlerWrapper_=a}; Blockly.WorkspaceSvg.prototype.createDom=function(a){this.svgGroup_=Blockly.createSvgElement("g",{"class":"blocklyWorkspace"},null);a&&(this.svgBackground_=Blockly.createSvgElement("rect",{height:"100%",width:"100%","class":a},this.svgGroup_),"blocklyMainBackground"==a&&(this.svgBackground_.style.fill="url(#"+this.options.gridPattern.id+")"));this.svgBlockCanvas_=Blockly.createSvgElement("g",{"class":"blocklyBlockCanvas"},this.svgGroup_,this);this.svgBubbleCanvas_=Blockly.createSvgElement("g",{"class":"blocklyBubbleCanvas"}, this.svgGroup_,this);a=Blockly.Scrollbar.scrollbarThickness;this.options.hasTrashcan&&(a=this.addTrashcan_(a));this.options.zoomOptions&&this.options.zoomOptions.controls&&(a=this.addZoomControls_(a));Blockly.bindEvent_(this.svgGroup_,"mousedown",this,this.onMouseDown_);var b=this;Blockly.bindEvent_(this.svgGroup_,"touchstart",null,function(a){Blockly.longStart_(a,b)});this.options.zoomOptions&&this.options.zoomOptions.wheel&&Blockly.bindEvent_(this.svgGroup_,"wheel",this,this.onMouseWheel_);this.options.hasCategories? -this.toolbox_=new Blockly.Toolbox(this):this.options.languageTree&&this.addFlyout_();this.updateGridPattern_();return this.svgGroup_}; +this.toolbox_=new Blockly.Toolbox(this):this.options.languageTree&&this.addFlyout_();this.updateGridPattern_();this.recordDeleteAreas();return this.svgGroup_}; Blockly.WorkspaceSvg.prototype.dispose=function(){this.rendered=!1;Blockly.WorkspaceSvg.superClass_.dispose.call(this);this.svgGroup_&&(goog.dom.removeNode(this.svgGroup_),this.svgGroup_=null);this.svgBubbleCanvas_=this.svgBlockCanvas_=null;this.toolbox_&&(this.toolbox_.dispose(),this.toolbox_=null);this.flyout_&&(this.flyout_.dispose(),this.flyout_=null);this.trashcan&&(this.trashcan.dispose(),this.trashcan=null);this.scrollbar&&(this.scrollbar.dispose(),this.scrollbar=null);this.zoomControls_&& (this.zoomControls_.dispose(),this.zoomControls_=null);this.options.parentWorkspace||goog.dom.removeNode(this.getParentSvg());this.resizeHandlerWrapper_&&(Blockly.unbindEvent_(this.resizeHandlerWrapper_),this.resizeHandlerWrapper_=null)};Blockly.WorkspaceSvg.prototype.newBlock=function(a,b){return new Blockly.BlockSvg(this,a,b)}; Blockly.WorkspaceSvg.prototype.addTrashcan_=function(a){this.trashcan=new Blockly.Trashcan(this);var b=this.trashcan.createDom();this.svgGroup_.insertBefore(b,this.svgBlockCanvas_);return this.trashcan.init(a)};Blockly.WorkspaceSvg.prototype.addZoomControls_=function(a){this.zoomControls_=new Blockly.ZoomControls(this);var b=this.zoomControls_.createDom();this.svgGroup_.appendChild(b);return this.zoomControls_.init(a)}; Blockly.WorkspaceSvg.prototype.addFlyout_=function(){this.flyout_=new Blockly.Flyout({disabledPatternId:this.options.disabledPatternId,parentWorkspace:this,RTL:this.RTL,horizontalLayout:this.horizontalLayout,toolboxPosition:this.options.toolboxPosition});this.flyout_.autoClose=!1;var a=this.flyout_.createDom();this.svgGroup_.insertBefore(a,this.svgBlockCanvas_)};Blockly.WorkspaceSvg.prototype.resizeContents=function(){this.scrollbar&&this.scrollbar.resize()}; -Blockly.WorkspaceSvg.prototype.resize=function(){this.toolbox_&&this.toolbox_.position();this.flyout_&&this.flyout_.position();this.trashcan&&this.trashcan.position();this.zoomControls_&&this.zoomControls_.position();this.scrollbar&&this.scrollbar.resize()};Blockly.WorkspaceSvg.prototype.getCanvas=function(){return this.svgBlockCanvas_};Blockly.WorkspaceSvg.prototype.getBubbleCanvas=function(){return this.svgBubbleCanvas_}; +Blockly.WorkspaceSvg.prototype.resize=function(){this.toolbox_&&this.toolbox_.position();this.flyout_&&this.flyout_.position();this.trashcan&&this.trashcan.position();this.zoomControls_&&this.zoomControls_.position();this.scrollbar&&this.scrollbar.resize();this.recordDeleteAreas()};Blockly.WorkspaceSvg.prototype.getCanvas=function(){return this.svgBlockCanvas_};Blockly.WorkspaceSvg.prototype.getBubbleCanvas=function(){return this.svgBubbleCanvas_}; Blockly.WorkspaceSvg.prototype.getParentSvg=function(){if(this.cachedParentSvg_)return this.cachedParentSvg_;for(var a=this.svgGroup_;a;){if("svg"==a.tagName)return this.cachedParentSvg_=a;a=a.parentNode}return null};Blockly.WorkspaceSvg.prototype.translate=function(a,b){var c="translate("+a+","+b+") scale("+this.scale+")";this.svgBlockCanvas_.setAttribute("transform",c);this.svgBubbleCanvas_.setAttribute("transform",c)}; Blockly.WorkspaceSvg.prototype.getWidth=function(){var a=this.getMetrics();return a?a.viewWidth/this.scale:0};Blockly.WorkspaceSvg.prototype.setVisible=function(a){this.getParentSvg().style.display=a?"block":"none";this.toolbox_&&(this.toolbox_.HtmlDiv.style.display=a?"block":"none");a?(this.render(),this.toolbox_&&this.toolbox_.position()):Blockly.hideChaff(!0)};Blockly.WorkspaceSvg.prototype.render=function(){for(var a=this.getAllBlocks(),b=a.length-1;0<=b;b--)a[b].render(!1)}; Blockly.WorkspaceSvg.prototype.traceOn=function(a){this.traceOn_=a;this.traceWrapper_&&(Blockly.unbindEvent_(this.traceWrapper_),this.traceWrapper_=null);a&&(this.traceWrapper_=Blockly.bindEvent_(this.svgBlockCanvas_,"blocklySelectChange",this,function(){this.traceOn_=!1}))}; @@ -1101,7 +1100,7 @@ void 0!==a.nextStatement&&this.setNextStatement(!0,a.nextStatement);void 0!==a.t Blockly.Block.prototype.interpolate_=function(a,b,c){var d=Blockly.tokenizeInterpolation(a),e=[],f=0;a=[];for(var g=0;g=b.height&&(k-=f.height);c?f.width>=a.clientX&&(g+=f.width):a.clientX+f.width>=b.width&&(g-=f.width);Blockly.WidgetDiv.position(g,k,b,e,c);d.setAllowAutoFocus(!0);setTimeout(function(){h.focus()},1);Blockly.ContextMenu.currentBlock=null}else Blockly.ContextMenu.hide()}; Blockly.ContextMenu.hide=function(){Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);Blockly.ContextMenu.currentBlock=null}; Blockly.ContextMenu.callbackFactory=function(a,b){return function(){Blockly.Events.disable();var c=Blockly.Xml.domToBlock(b,a.workspace),d=a.getRelativeToSurfaceXY();d.x=a.RTL?d.x-Blockly.SNAP_RADIUS:d.x+Blockly.SNAP_RADIUS;d.y+=2*Blockly.SNAP_RADIUS;c.moveBy(d.x,d.y);Blockly.Events.enable();Blockly.Events.isEnabled()&&!c.isShadow()&&Blockly.Events.fire(new Blockly.Events.Create(c));c.select()}};Blockly.RenderedConnection=function(a,b){Blockly.RenderedConnection.superClass_.constructor.call(this,a,b);this.offsetInBlock_=new goog.math.Coordinate(0,0)};goog.inherits(Blockly.RenderedConnection,Blockly.Connection);Blockly.RenderedConnection.prototype.distanceFrom=function(a){var b=this.x_-a.x_;a=this.y_-a.y_;return Math.sqrt(b*b+a*a)}; -Blockly.RenderedConnection.prototype.bumpAwayFrom_=function(a){if(Blockly.dragMode_==Blockly.DRAG_NONE){var b=this.sourceBlock_.getRootBlock();if(!b.isInFlyout){var c=!1;if(!b.isMovable()){b=a.getSourceBlock().getRootBlock();if(!b.isMovable())return;a=this;c=!0}b.getSvgRoot().parentNode.appendChild(b.getSvgRoot());var d=a.x_+Blockly.SNAP_RADIUS-this.x_;a=a.y_+Blockly.SNAP_RADIUS-this.y_;c&&(a=-a);b.RTL&&(d=-d);b.moveBy(d,a)}}}; +Blockly.RenderedConnection.prototype.bumpAwayFrom_=function(a){if(Blockly.dragMode_==Blockly.DRAG_NONE){var b=this.sourceBlock_.getRootBlock();if(!b.isInFlyout){var c=!1;if(!b.isMovable()){b=a.getSourceBlock().getRootBlock();if(!b.isMovable())return;a=this;c=!0}var d=Blockly.selected==b;d||b.select();var e=a.x_+Blockly.SNAP_RADIUS-this.x_;a=a.y_+Blockly.SNAP_RADIUS-this.y_;c&&(a=-a);b.RTL&&(e=-e);b.moveBy(e,a);d||b.unselect()}}}; Blockly.RenderedConnection.prototype.moveTo=function(a,b){this.inDB_&&this.db_.removeConnection_(this);this.x_=a;this.y_=b;this.hidden_||this.db_.addConnection(this)};Blockly.RenderedConnection.prototype.moveBy=function(a,b){this.moveTo(this.x_+a,this.y_+b)};Blockly.RenderedConnection.prototype.moveToOffset=function(a){this.moveTo(a.x+this.offsetInBlock_.x,a.y+this.offsetInBlock_.y)}; Blockly.RenderedConnection.prototype.setOffsetInBlock=function(a,b){this.offsetInBlock_.x=a;this.offsetInBlock_.y=b};Blockly.RenderedConnection.prototype.tighten_=function(){var a=this.targetConnection.x_-this.x_,b=this.targetConnection.y_-this.y_;if(0!=a||0!=b){var c=this.targetBlock(),d=c.getSvgRoot();if(!d)throw"block is not rendered.";d=Blockly.getRelativeXY_(d);c.getSvgRoot().setAttribute("transform","translate("+(d.x-a)+","+(d.y-b)+")");c.moveConnections_(-a,-b)}}; Blockly.RenderedConnection.prototype.closest=function(a,b,c){return this.dbOpposite_.searchForClosest(this,a,b,c)}; @@ -1139,10 +1138,10 @@ Blockly.BlockSvg.prototype.getHeightWidth=function(){var a=this.height,b=this.wi Blockly.BlockSvg.prototype.getBoundingRectangle=function(){var a=this.getRelativeToSurfaceXY(this),b=this.outputConnection?Blockly.BlockSvg.TAB_WIDTH:0,c=this.getHeightWidth(),d;this.RTL?(d=new goog.math.Coordinate(a.x-(c.width-b),a.y),a=new goog.math.Coordinate(a.x+b,a.y+c.height)):(d=new goog.math.Coordinate(a.x-b,a.y),a=new goog.math.Coordinate(a.x+c.width-b,a.y+c.height));return{topLeft:d,bottomRight:a}}; Blockly.BlockSvg.prototype.setCollapsed=function(a){if(this.collapsed_!=a){for(var b=[],c=0,d;d=this.inputList[c];c++)b.push.apply(b,d.setVisible(!a));if(a){d=this.getIcons();for(c=0;cthis.workspace.remainingCapacity()&&(d.enabled=!1);c.push(d);this.isEditable()&&!this.collapsed_&&this.workspace.options.comments&&(d={enabled:!goog.userAgent.IE},this.comment?(d.text=Blockly.Msg.REMOVE_COMMENT, d.callback=function(){b.setCommentText(null)}):(d.text=Blockly.Msg.ADD_COMMENT,d.callback=function(){b.setCommentText("")}),c.push(d));if(!this.collapsed_)for(d=1;dthis.workspace.scale)){var a=Blockly.getSvgXY_(this.svgGroup_,this.workspace);this.outputConnection?(a.x+=(this.RTL?3:-3)*this.workspace.scale,a.y+=13*this.workspace.scale):this.previousConnection&&(a.x+=(this.RTL?-23:23)*this.workspace.scale,a.y+=3*this.workspace.scale);a=Blockly.createSvgElement("circle",{cx:a.x,cy:a.y,r:0,fill:"none",stroke:"#888","stroke-width":10},this.workspace.getParentSvg()); @@ -1223,15 +1222,16 @@ Blockly.Events.Delete=function(a){if(a){if(a.getParent())throw"Connected blocks Blockly.Events.Delete.prototype.fromJson=function(a){Blockly.Events.Delete.superClass_.fromJson.call(this,a);this.ids=a.ids};Blockly.Events.Delete.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId);if(a){a=0;for(var c;c=this.ids[a];a++){var d=b.getBlockById(c);d?d.dispose(!1,!1):c==this.blockId&&console.warn("Can't delete non-existant block: "+c)}}else a=goog.dom.createDom("xml"),a.appendChild(this.oldXml),Blockly.Xml.domToWorkspace(a,b)}; Blockly.Events.Change=function(a,b,c,d,e){a&&(Blockly.Events.Change.superClass_.constructor.call(this,a),this.element=b,this.name=c,this.oldValue=d,this.newValue=e)};goog.inherits(Blockly.Events.Change,Blockly.Events.Abstract);Blockly.Events.Change.prototype.type=Blockly.Events.CHANGE;Blockly.Events.Change.prototype.toJson=function(){var a=Blockly.Events.Change.superClass_.toJson.call(this);a.element=this.element;this.name&&(a.name=this.name);a.newValue=this.newValue;return a}; Blockly.Events.Change.prototype.fromJson=function(a){Blockly.Events.Change.superClass_.fromJson.call(this,a);this.element=a.element;this.name=a.name;this.newValue=a.newValue};Blockly.Events.Change.prototype.isNull=function(){return this.oldValue==this.newValue}; -Blockly.Events.Change.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId).getBlockById(this.blockId);if(b)switch(b.mutator&&b.mutator.setVisible(!1),a=a?this.newValue:this.oldValue,this.element){case "field":(b=b.getField(this.name))?b.setValue(a):console.warn("Can't set non-existant field: "+this.name);break;case "comment":b.setCommentText(a||null);break;case "collapsed":b.setCollapsed(a);break;case "disabled":b.setDisabled(a);break;case "inline":b.setInputsInline(a);break; -case "mutation":var c="";b.mutationToDom&&(c=(c=b.mutationToDom())&&Blockly.Xml.domToText(c));if(b.domToMutation){a=a||"";var d=Blockly.Xml.textToDom(""+a+"");b.domToMutation(d.firstChild)}Blockly.Events.fire(new Blockly.Events.Change(b,"mutation",null,c,a));break;default:console.warn("Unknown change type: "+this.element)}else console.warn("Can't change non-existant block: "+this.blockId)}; +Blockly.Events.Change.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId).getBlockById(this.blockId);if(b)switch(b.mutator&&b.mutator.setVisible(!1),a=a?this.newValue:this.oldValue,this.element){case "field":if(b=b.getField(this.name)){var c=b.getValidator();c&&c.call(b,a);b.setValue(a)}else console.warn("Can't set non-existant field: "+this.name);break;case "comment":b.setCommentText(a||null);break;case "collapsed":b.setCollapsed(a);break;case "disabled":b.setDisabled(a); +break;case "inline":b.setInputsInline(a);break;case "mutation":c="";b.mutationToDom&&(c=(c=b.mutationToDom())&&Blockly.Xml.domToText(c));if(b.domToMutation){a=a||"";var d=Blockly.Xml.textToDom(""+a+"");b.domToMutation(d.firstChild)}Blockly.Events.fire(new Blockly.Events.Change(b,"mutation",null,c,a));break;default:console.warn("Unknown change type: "+this.element)}else console.warn("Can't change non-existant block: "+this.blockId)}; Blockly.Events.Move=function(a){a&&(Blockly.Events.Move.superClass_.constructor.call(this,a),a=this.currentLocation_(),this.oldParentId=a.parentId,this.oldInputName=a.inputName,this.oldCoordinate=a.coordinate)};goog.inherits(Blockly.Events.Move,Blockly.Events.Abstract);Blockly.Events.Move.prototype.type=Blockly.Events.MOVE; Blockly.Events.Move.prototype.toJson=function(){var a=Blockly.Events.Move.superClass_.toJson.call(this);this.newParentId&&(a.newParentId=this.newParentId);this.newInputName&&(a.newInputName=this.newInputName);this.newCoordinate&&(a.newCoordinate=Math.round(this.newCoordinate.x)+","+Math.round(this.newCoordinate.y));return a}; Blockly.Events.Move.prototype.fromJson=function(a){Blockly.Events.Move.superClass_.fromJson.call(this,a);this.newParentId=a.newParentId;this.newInputName=a.newInputName;a.newCoordinate&&(a=a.newCoordinate.split(","),this.newCoordinate=new goog.math.Coordinate(parseFloat(a[0]),parseFloat(a[1])))};Blockly.Events.Move.prototype.recordNew=function(){var a=this.currentLocation_();this.newParentId=a.parentId;this.newInputName=a.inputName;this.newCoordinate=a.coordinate}; Blockly.Events.Move.prototype.currentLocation_=function(){var a=Blockly.Workspace.getById(this.workspaceId).getBlockById(this.blockId),b={},c=a.getParent();if(c){if(b.parentId=c.id,a=c.getInputWithBlock(a))b.inputName=a.name}else b.coordinate=a.getRelativeToSurfaceXY();return b};Blockly.Events.Move.prototype.isNull=function(){return this.oldParentId==this.newParentId&&this.oldInputName==this.newInputName&&goog.math.Coordinate.equals(this.oldCoordinate,this.newCoordinate)}; Blockly.Events.Move.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId),c=b.getBlockById(this.blockId);if(c){var d=a?this.newParentId:this.oldParentId,e=a?this.newInputName:this.oldInputName;a=a?this.newCoordinate:this.oldCoordinate;var f=null;if(d&&(f=b.getBlockById(d),!f)){console.warn("Can't connect to non-existant block: "+d);return}c.getParent()&&c.unplug();if(a)e=c.getRelativeToSurfaceXY(),c.moveBy(a.x-e.x,a.y-e.y);else{var c=c.outputConnection||c.previousConnection, g;if(e){if(b=f.getInput(e))g=b.connection}else c.type==Blockly.PREVIOUS_STATEMENT&&(g=f.nextConnection);g?c.connect(g):console.warn("Can't connect to non-existant input: "+e)}}else console.warn("Can't move non-existant block: "+this.blockId)};Blockly.Events.Ui=function(a,b,c,d){Blockly.Events.Ui.superClass_.constructor.call(this,a);this.element=b;this.oldValue=c;this.newValue=d;this.recordUndo=!1};goog.inherits(Blockly.Events.Ui,Blockly.Events.Abstract);Blockly.Events.Ui.prototype.type=Blockly.Events.UI; -Blockly.Events.Ui.prototype.toJson=function(){var a=Blockly.Events.Ui.superClass_.toJson.call(this);a.element=this.element;void 0!==this.newValue&&(a.newValue=this.newValue);return a};Blockly.Events.Ui.prototype.fromJson=function(a){Blockly.Events.Ui.superClass_.fromJson.call(this,a);this.element=a.element;this.newValue=a.newValue};Blockly.Msg={};goog.getMsgOrig=goog.getMsg;goog.getMsg=function(a,b){var c=goog.getMsg.blocklyMsgMap[a];c&&(a=Blockly.Msg[c]);return goog.getMsgOrig(a,b)};goog.getMsg.blocklyMsgMap={Today:"TODAY"};Blockly.FieldTextInput=function(a,b){Blockly.FieldTextInput.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldTextInput,Blockly.Field);Blockly.FieldTextInput.FONTSIZE=11;Blockly.FieldTextInput.prototype.CURSOR="text";Blockly.FieldTextInput.prototype.spellcheck_=!0;Blockly.FieldTextInput.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldTextInput.superClass_.dispose.call(this)}; +Blockly.Events.Ui.prototype.toJson=function(){var a=Blockly.Events.Ui.superClass_.toJson.call(this);a.element=this.element;void 0!==this.newValue&&(a.newValue=this.newValue);return a};Blockly.Events.Ui.prototype.fromJson=function(a){Blockly.Events.Ui.superClass_.fromJson.call(this,a);this.element=a.element;this.newValue=a.newValue}; +Blockly.Events.disableOrphans=function(a){if(a.type==Blockly.Events.MOVE||a.type==Blockly.Events.CREATE){Blockly.Events.disable();if(a=Blockly.Workspace.getById(a.workspaceId).getBlockById(a.blockId))if(a.getParent()&&!a.getParent().disabled){do a.setDisabled(!1),a=a.getNextBlock();while(a)}else if((a.outputConnection||a.previousConnection)&&Blockly.dragMode_==Blockly.DRAG_NONE){do a.setDisabled(!0),a=a.getNextBlock();while(a)}Blockly.Events.enable()}};Blockly.Msg={};goog.getMsgOrig=goog.getMsg;goog.getMsg=function(a,b){var c=goog.getMsg.blocklyMsgMap[a];c&&(a=Blockly.Msg[c]);return goog.getMsgOrig(a,b)};goog.getMsg.blocklyMsgMap={Today:"TODAY"};Blockly.FieldTextInput=function(a,b){Blockly.FieldTextInput.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldTextInput,Blockly.Field);Blockly.FieldTextInput.FONTSIZE=11;Blockly.FieldTextInput.prototype.CURSOR="text";Blockly.FieldTextInput.prototype.spellcheck_=!0;Blockly.FieldTextInput.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldTextInput.superClass_.dispose.call(this)}; Blockly.FieldTextInput.prototype.setValue=function(a){if(null!==a){if(this.sourceBlock_&&this.validator_){var b=this.validator_(a);null!==b&&void 0!==b&&(a=b)}Blockly.Field.prototype.setValue.call(this,a)}};Blockly.FieldTextInput.prototype.setSpellcheck=function(a){this.spellcheck_=a}; Blockly.FieldTextInput.prototype.showEditor_=function(a){this.workspace_=this.sourceBlock_.workspace;a=a||!1;if(!a&&(goog.userAgent.MOBILE||goog.userAgent.ANDROID||goog.userAgent.IPAD)){a=window.prompt(Blockly.Msg.CHANGE_VALUE_TITLE,this.text_);if(this.sourceBlock_&&this.validator_){var b=this.validator_(a);void 0!==b&&(a=b)}this.setValue(a)}else{Blockly.WidgetDiv.show(this,this.sourceBlock_.RTL,this.widgetDispose_());var b=Blockly.WidgetDiv.DIV,c=goog.dom.createDom("input","blocklyHtmlInput");c.setAttribute("spellcheck", this.spellcheck_);var d=Blockly.FieldTextInput.FONTSIZE*this.workspace_.scale+"pt";b.style.fontSize=d;c.style.fontSize=d;Blockly.FieldTextInput.htmlInput_=c;b.appendChild(c);c.value=c.defaultValue=this.text_;c.oldValue_=null;this.validate_();this.resizeEditor_();a||(c.focus(),c.select());c.onKeyDownWrapper_=Blockly.bindEvent_(c,"keydown",this,this.onHtmlInputKeyDown_);c.onKeyUpWrapper_=Blockly.bindEvent_(c,"keyup",this,this.onHtmlInputChange_);c.onKeyPressWrapper_=Blockly.bindEvent_(c,"keypress", @@ -1240,7 +1240,8 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_=function(a){a=Blockly.FieldT Blockly.FieldTextInput.prototype.validate_=function(){var a=!0;goog.asserts.assertObject(Blockly.FieldTextInput.htmlInput_);var b=Blockly.FieldTextInput.htmlInput_;this.sourceBlock_&&this.validator_&&(a=this.validator_(b.value));null===a?Blockly.addClass_(b,"blocklyInvalidInput"):Blockly.removeClass_(b,"blocklyInvalidInput")}; Blockly.FieldTextInput.prototype.resizeEditor_=function(){var a=Blockly.WidgetDiv.DIV,b=this.fieldGroup_.getBBox();a.style.width=b.width*this.workspace_.scale+"px";a.style.height=b.height*this.workspace_.scale+"px";b=this.getAbsoluteXY_();if(this.sourceBlock_.RTL){var c=this.getScaledBBox_();b.x+=c.width;b.x-=a.offsetWidth}b.y+=1;goog.userAgent.GECKO&&Blockly.WidgetDiv.DIV.style.top&&(--b.x,--b.y);goog.userAgent.WEBKIT&&(b.y-=3);a.style.left=b.x+"px";a.style.top=b.y+"px"}; Blockly.FieldTextInput.prototype.widgetDispose_=function(){var a=this;return function(){var b=Blockly.FieldTextInput.htmlInput_,c=b.value;if(a.sourceBlock_&&a.validator_){var d=a.validator_(c);null===d?c=b.defaultValue:void 0!==d&&(c=d)}a.setValue(c);a.sourceBlock_.rendered&&a.sourceBlock_.render();Blockly.unbindEvent_(b.onKeyDownWrapper_);Blockly.unbindEvent_(b.onKeyUpWrapper_);Blockly.unbindEvent_(b.onKeyPressWrapper_);a.workspace_.removeChangeListener(b.onWorkspaceChangeWrapper_);Blockly.FieldTextInput.htmlInput_= -null;b=Blockly.WidgetDiv.DIV.style;b.width="auto";b.height="auto";b.fontSize=""}};Blockly.FieldTextInput.numberValidator=function(a){if(null===a)return null;a=String(a);a=a.replace(/O/ig,"0");a=a.replace(/,/g,"");a=parseFloat(a||0);return isNaN(a)?null:String(a)};Blockly.FieldTextInput.nonnegativeIntegerValidator=function(a){(a=Blockly.FieldTextInput.numberValidator(a))&&(a=String(Math.max(0,Math.floor(a))));return a};Blockly.FieldAngle=function(a,b){this.symbol_=Blockly.createSvgElement("tspan",{},null);this.symbol_.appendChild(document.createTextNode("\u00b0"));Blockly.FieldAngle.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldAngle,Blockly.FieldTextInput); +null;b=Blockly.WidgetDiv.DIV.style;b.width="auto";b.height="auto";b.fontSize=""}};Blockly.FieldTextInput.numberValidator=function(a){console.warn("Blockly.FieldTextInput.numberValidator is deprecated. Use Blockly.FieldNumber instead.");if(null===a)return null;a=String(a);a=a.replace(/O/ig,"0");a=a.replace(/,/g,"");a=parseFloat(a||0);return isNaN(a)?null:String(a)}; +Blockly.FieldTextInput.nonnegativeIntegerValidator=function(a){(a=Blockly.FieldTextInput.numberValidator(a))&&(a=String(Math.max(0,Math.floor(a))));return a};Blockly.FieldAngle=function(a,b){this.symbol_=Blockly.createSvgElement("tspan",{},null);this.symbol_.appendChild(document.createTextNode("\u00b0"));Blockly.FieldAngle.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldAngle,Blockly.FieldTextInput); Blockly.FieldAngle.prototype.setValidator=function(a){Blockly.FieldAngle.superClass_.setValidator.call(this,a?function(b){var c=a.call(this,b);if(null===c)var d=c;else void 0===c&&(c=b),d=Blockly.FieldAngle.angleValidator.call(this,c),void 0===d&&(d=c);return d===b?void 0:d}:Blockly.FieldAngle.angleValidator)};Blockly.FieldAngle.ROUND=15;Blockly.FieldAngle.HALF=50;Blockly.FieldAngle.CLOCKWISE=!1;Blockly.FieldAngle.OFFSET=0;Blockly.FieldAngle.WRAP=360; Blockly.FieldAngle.RADIUS=Blockly.FieldAngle.HALF-1;Blockly.FieldAngle.prototype.dispose_=function(){var a=this;return function(){Blockly.FieldAngle.superClass_.dispose_.call(a)();a.gauge_=null;a.clickWrapper_&&Blockly.unbindEvent_(a.clickWrapper_);a.moveWrapper1_&&Blockly.unbindEvent_(a.moveWrapper1_);a.moveWrapper2_&&Blockly.unbindEvent_(a.moveWrapper2_)}}; Blockly.FieldAngle.prototype.showEditor_=function(){Blockly.FieldAngle.superClass_.showEditor_.call(this,goog.userAgent.MOBILE||goog.userAgent.ANDROID||goog.userAgent.IPAD);var a=Blockly.WidgetDiv.DIV;if(a.firstChild){var a=Blockly.createSvgElement("svg",{xmlns:"http://www.w3.org/2000/svg","xmlns:html":"http://www.w3.org/1999/xhtml","xmlns:xlink":"http://www.w3.org/1999/xlink",version:"1.1",height:2*Blockly.FieldAngle.HALF+"px",width:2*Blockly.FieldAngle.HALF+"px"},a),b=Blockly.createSvgElement("circle", @@ -1250,7 +1251,7 @@ Blockly.FieldAngle.prototype.onMouseMove=function(a){var b=this.gauge_.ownerSVGE b,this.setValue(b),this.validate_(),this.resizeEditor_())};Blockly.FieldAngle.prototype.setText=function(a){Blockly.FieldAngle.superClass_.setText.call(this,a);this.textElement_&&(this.updateGraph_(),this.sourceBlock_.RTL?this.textElement_.insertBefore(this.symbol_,this.textElement_.firstChild):this.textElement_.appendChild(this.symbol_),this.size_.width=0)}; Blockly.FieldAngle.prototype.updateGraph_=function(){if(this.gauge_){var a=Number(this.getText())+Blockly.FieldAngle.OFFSET,b=goog.math.toRadians(a),a=["M ",Blockly.FieldAngle.HALF,",",Blockly.FieldAngle.HALF],c=Blockly.FieldAngle.HALF,d=Blockly.FieldAngle.HALF;if(!isNaN(b)){var e=goog.math.toRadians(Blockly.FieldAngle.OFFSET),f=Math.cos(e)*Blockly.FieldAngle.RADIUS,g=Math.sin(e)*-Blockly.FieldAngle.RADIUS;Blockly.FieldAngle.CLOCKWISE&&(b=2*e-b);c+=Math.cos(b)*Blockly.FieldAngle.RADIUS;d-=Math.sin(b)* Blockly.FieldAngle.RADIUS;b=Math.abs(Math.floor((b-e)/Math.PI)%2);Blockly.FieldAngle.CLOCKWISE&&(b=1-b);a.push(" l ",f,",",g," A ",Blockly.FieldAngle.RADIUS,",",Blockly.FieldAngle.RADIUS," 0 ",b," ",Number(Blockly.FieldAngle.CLOCKWISE)," ",c,",",d," z")}this.gauge_.setAttribute("d",a.join(""));this.line_.setAttribute("x2",c);this.line_.setAttribute("y2",d)}}; -Blockly.FieldAngle.angleValidator=function(a){a=Blockly.FieldTextInput.numberValidator(a);null!==a&&(a%=360,0>a&&(a+=360),a>Blockly.FieldAngle.WRAP&&(a-=360),a=String(a));return a};Blockly.FieldCheckbox=function(a,b){Blockly.FieldCheckbox.superClass_.constructor.call(this,"",b);this.setValue(a)};goog.inherits(Blockly.FieldCheckbox,Blockly.Field);Blockly.FieldCheckbox.CHECK_CHAR="\u2713";Blockly.FieldCheckbox.prototype.CURSOR="default"; +Blockly.FieldAngle.angleValidator=function(a){if(null===a)return null;a=parseFloat(a||0);if(isNaN(a))return null;a%=360;0>a&&(a+=360);a>Blockly.FieldAngle.WRAP&&(a-=360);return String(a)};Blockly.FieldCheckbox=function(a,b){Blockly.FieldCheckbox.superClass_.constructor.call(this,"",b);this.setValue(a)};goog.inherits(Blockly.FieldCheckbox,Blockly.Field);Blockly.FieldCheckbox.CHECK_CHAR="\u2713";Blockly.FieldCheckbox.prototype.CURSOR="default"; Blockly.FieldCheckbox.prototype.init=function(){if(!this.fieldGroup_){Blockly.FieldCheckbox.superClass_.init.call(this);this.checkElement_=Blockly.createSvgElement("text",{"class":"blocklyText blocklyCheckbox",x:-3,y:14},this.fieldGroup_);var a=document.createTextNode(Blockly.FieldCheckbox.CHECK_CHAR);this.checkElement_.appendChild(a);this.checkElement_.style.display=this.state_?"block":"none"}};Blockly.FieldCheckbox.prototype.getValue=function(){return String(this.state_).toUpperCase()}; Blockly.FieldCheckbox.prototype.setValue=function(a){a="TRUE"==a;this.state_!==a&&(this.sourceBlock_&&Blockly.Events.isEnabled()&&Blockly.Events.fire(new Blockly.Events.Change(this.sourceBlock_,"field",this.name,this.state_,a)),this.state_=a,this.checkElement_&&(this.checkElement_.style.display=a?"block":"none"))};Blockly.FieldCheckbox.prototype.showEditor_=function(){var a=!this.state_;if(this.sourceBlock_&&this.validator_){var b=this.validator_(a);void 0!==b&&(a=b)}null!==a&&this.setValue(String(a).toUpperCase())};Blockly.FieldColour=function(a,b){Blockly.FieldColour.superClass_.constructor.call(this,a,b);this.setText(Blockly.Field.NBSP+Blockly.Field.NBSP+Blockly.Field.NBSP)};goog.inherits(Blockly.FieldColour,Blockly.Field);Blockly.FieldColour.prototype.colours_=null;Blockly.FieldColour.prototype.columns_=0;Blockly.FieldColour.prototype.init=function(){Blockly.FieldColour.superClass_.init.call(this);this.borderRect_.style.fillOpacity=1;this.setValue(this.getValue())};Blockly.FieldColour.prototype.CURSOR="default"; Blockly.FieldColour.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldColour.superClass_.dispose.call(this)};Blockly.FieldColour.prototype.getValue=function(){return this.colour_};Blockly.FieldColour.prototype.setValue=function(a){this.sourceBlock_&&Blockly.Events.isEnabled()&&this.colour_!=a&&Blockly.Events.fire(new Blockly.Events.Change(this.sourceBlock_,"field",this.name,this.colour_,a));this.colour_=a;this.borderRect_&&(this.borderRect_.style.fill=a)}; @@ -1270,7 +1271,9 @@ Blockly.FieldDropdown.prototype.setText=function(a){this.sourceBlock_&&this.arro Blockly.FieldDropdown.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldDropdown.superClass_.dispose.call(this)};Blockly.FieldImage=function(a,b,c,d){this.sourceBlock_=null;this.height_=Number(c);this.width_=Number(b);this.size_=new goog.math.Size(this.width_,this.height_+2*Blockly.BlockSvg.INLINE_PADDING_Y);this.text_=d||"";this.setValue(a)};goog.inherits(Blockly.FieldImage,Blockly.Field);Blockly.FieldImage.prototype.rectElement_=null;Blockly.FieldImage.prototype.EDITABLE=!1; Blockly.FieldImage.prototype.init=function(){if(!this.fieldGroup_){this.fieldGroup_=Blockly.createSvgElement("g",{},null);this.visible_||(this.fieldGroup_.style.display="none");this.imageElement_=Blockly.createSvgElement("image",{height:this.height_+"px",width:this.width_+"px"},this.fieldGroup_);this.setValue(this.src_);goog.userAgent.GECKO&&(this.rectElement_=Blockly.createSvgElement("rect",{height:this.height_+"px",width:this.width_+"px","fill-opacity":0},this.fieldGroup_));this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); var a=this.rectElement_||this.imageElement_;a.tooltip=this.sourceBlock_;Blockly.Tooltip.bindMouseEvents(a)}};Blockly.FieldImage.prototype.dispose=function(){goog.dom.removeNode(this.fieldGroup_);this.rectElement_=this.imageElement_=this.fieldGroup_=null};Blockly.FieldImage.prototype.setTooltip=function(a){(this.rectElement_||this.imageElement_).tooltip=a};Blockly.FieldImage.prototype.getValue=function(){return this.src_}; -Blockly.FieldImage.prototype.setValue=function(a){null!==a&&(this.src_=a,this.imageElement_&&this.imageElement_.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",goog.isString(a)?a:""))};Blockly.FieldImage.prototype.setText=function(a){null!==a&&(this.text_=a)};Blockly.FieldImage.prototype.render_=function(){};Blockly.FieldNumber=function(a,b){Blockly.FieldNumber.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldNumber,Blockly.FieldTextInput);Blockly.Variables={};Blockly.Variables.NAME_TYPE="VARIABLE";Blockly.Variables.allVariables=function(a){var b;if(a.getDescendants)b=a.getDescendants();else if(a.getAllBlocks)b=a.getAllBlocks();else throw"Not Block or Workspace: "+a;a=Object.create(null);for(var c=0;ca)}}; -Blockly.Flyout.prototype.getClientRect=function(){var a=this.svgGroup_.getBoundingClientRect(),b=a.left,c=a.top,d=a.width,a=a.height;return this.toolboxPosition_==Blockly.TOOLBOX_AT_TOP?new goog.math.Rect(-1E9,c-1E9,2E9,1E9+a):this.toolboxPosition_==Blockly.TOOLBOX_AT_BOTTOM?new goog.math.Rect(-1E9,c,2E9,1E9+a):this.toolboxPosition_==Blockly.TOOLBOX_AT_LEFT?new goog.math.Rect(b-1E9,-1E9,1E9+d,2E9):new goog.math.Rect(b,-1E9,1E9+d,2E9)}; +Blockly.Flyout.prototype.getClientRect=function(){if(!this.svgGroup_)return null;var a=this.svgGroup_.getBoundingClientRect(),b=a.left,c=a.top,d=a.width,a=a.height;return this.toolboxPosition_==Blockly.TOOLBOX_AT_TOP?new goog.math.Rect(-1E9,c-1E9,2E9,1E9+a):this.toolboxPosition_==Blockly.TOOLBOX_AT_BOTTOM?new goog.math.Rect(-1E9,c,2E9,1E9+a):this.toolboxPosition_==Blockly.TOOLBOX_AT_LEFT?new goog.math.Rect(b-1E9,-1E9,1E9+d,2E9):new goog.math.Rect(b,-1E9,1E9+d,2E9)}; Blockly.Flyout.terminateDrag_=function(){Blockly.Flyout.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_),Blockly.Flyout.onMouseUpWrapper_=null);Blockly.Flyout.onMouseMoveBlockWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveBlockWrapper_),Blockly.Flyout.onMouseMoveBlockWrapper_=null);Blockly.Flyout.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveWrapper_),Blockly.Flyout.onMouseMoveWrapper_=null);Blockly.Flyout.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_), Blockly.Flyout.onMouseUpWrapper_=null);Blockly.Flyout.startDownEvent_=null;Blockly.Flyout.startBlock_=null;Blockly.Flyout.startFlyout_=null}; Blockly.Flyout.prototype.reflowHorizontal=function(a){this.workspace_.scale=this.targetWorkspace_.scale;for(var b=0,c=0,d;d=a[c];c++)b=Math.max(b,d.getHeightWidth().height);b+=1.5*this.MARGIN;b*=this.workspace_.scale;b+=Blockly.Scrollbar.scrollbarThickness;if(this.height_!=b){for(c=0;d=a[c];c++){var e=d.getHeightWidth();if(d.flyoutRect_){d.flyoutRect_.setAttribute("width",e.width);d.flyoutRect_.setAttribute("height",e.height);var f=d.outputConnection?Blockly.BlockSvg.TAB_WIDTH:0,g=d.getRelativeToSurfaceXY(); @@ -1346,7 +1349,7 @@ Blockly.Toolbox.prototype.position=function(){var a=this.HtmlDiv;if(a){var b=thi Blockly.Toolbox.prototype.populate_=function(a){function b(a,g,h){for(var k=null,m=0,p;p=a.childNodes[m];m++)if(p.tagName)switch(p.tagName.toUpperCase()){case "CATEGORY":k=c.createNode(p.getAttribute("name"));k.blocks=[];g.add(k);var l=p.getAttribute("custom");l?k.blocks=l:b(p,k,h);l=p.getAttribute("colour");goog.isString(l)?(l.match(/^#[0-9a-fA-F]{6}$/)?k.hexColour=l:k.hexColour=Blockly.hueToRgb(l),e=!0):k.hexColour="";"true"==p.getAttribute("expanded")?(k.blocks.length&&c.setSelectedItem(k),k.setExpanded(!0)): k.setExpanded(!1);k=p;break;case "SEP":k&&("CATEGORY"==k.tagName.toUpperCase()?g.add(new Blockly.Toolbox.TreeSeparator(d.treeSeparatorConfig_)):(p=parseFloat(p.getAttribute("gap")),isNaN(p)||(l=parseFloat(k.getAttribute("gap")),p=isNaN(l)?p:l+p,k.setAttribute("gap",p))));break;case "BLOCK":case "SHADOW":g.blocks.push(p),k=p}}var c=this.tree_,d=this;c.removeChildren();c.blocks=[];var e=!1;b(a,this.tree_,this.workspace_.options.pathToMedia);this.hasColours_=e;if(c.blocks.length)throw"Toolbox cannot have both blocks and categories in the root level."; Blockly.resizeSvgContents(this.workspace_)};Blockly.Toolbox.prototype.addColour_=function(a){a=(a||this.tree_).getChildren();for(var b=0,c;c=a[b];b++){var d=c.getRowElement();if(d){var e=this.hasColours_?"8px solid "+(c.hexColour||"#ddd"):"none";this.workspace_.RTL?d.style.borderRight=e:d.style.borderLeft=e}this.addColour_(c)}};Blockly.Toolbox.prototype.clearSelection=function(){this.tree_.setSelectedItem(null)}; -Blockly.Toolbox.prototype.getClientRect=function(){var a=this.HtmlDiv.getBoundingClientRect(),b=a.left,c=a.top,d=a.width,a=a.height;return this.toolboxPosition==Blockly.TOOLBOX_AT_LEFT?new goog.math.Rect(-1E7,-1E7,1E7+b+d,2E7):this.toolboxPosition==Blockly.TOOLBOX_AT_RIGHT?new goog.math.Rect(b,-1E7,1E7+d,2E7):this.toolboxPosition==Blockly.TOOLBOX_AT_TOP?new goog.math.Rect(-1E7,-1E7,2E7,1E7+c+a):new goog.math.Rect(0,c,2E7,1E7+d)}; +Blockly.Toolbox.prototype.getClientRect=function(){if(!this.HtmlDiv)return null;var a=this.HtmlDiv.getBoundingClientRect(),b=a.left,c=a.top,d=a.width,a=a.height;return this.toolboxPosition==Blockly.TOOLBOX_AT_LEFT?new goog.math.Rect(-1E7,-1E7,1E7+b+d,2E7):this.toolboxPosition==Blockly.TOOLBOX_AT_RIGHT?new goog.math.Rect(b,-1E7,1E7+d,2E7):this.toolboxPosition==Blockly.TOOLBOX_AT_TOP?new goog.math.Rect(-1E7,-1E7,2E7,1E7+c+a):new goog.math.Rect(0,c,2E7,1E7+d)}; Blockly.Toolbox.TreeControl=function(a,b){this.toolbox_=a;goog.ui.tree.TreeControl.call(this,goog.html.SafeHtml.EMPTY,b)};goog.inherits(Blockly.Toolbox.TreeControl,goog.ui.tree.TreeControl);Blockly.Toolbox.TreeControl.prototype.enterDocument=function(){Blockly.Toolbox.TreeControl.superClass_.enterDocument.call(this);if(goog.events.BrowserFeature.TOUCH_ENABLED){var a=this.getElement();Blockly.bindEvent_(a,goog.events.EventType.TOUCHSTART,this,this.handleTouchEvent_)}}; Blockly.Toolbox.TreeControl.prototype.handleTouchEvent_=function(a){a.preventDefault();var b=this.getNodeFromEvent_(a);b&&a.type===goog.events.EventType.TOUCHSTART&&setTimeout(function(){b.onMouseDown(a)},1)};Blockly.Toolbox.TreeControl.prototype.createNode=function(a){return new Blockly.Toolbox.TreeNode(this.toolbox_,a?goog.html.SafeHtml.htmlEscape(a):goog.html.SafeHtml.EMPTY,this.getConfig(),this.getDomHelper())}; Blockly.Toolbox.TreeControl.prototype.setSelectedItem=function(a){var b=this.toolbox_;if(a!=this.selectedItem_&&a!=b.tree_){b.lastCategory_&&(b.lastCategory_.getRowElement().style.backgroundColor="");if(a){var c=a.hexColour||"#57e";a.getRowElement().style.backgroundColor=c;b.addColour_(a)}c=this.getSelectedItem();goog.ui.tree.TreeControl.prototype.setSelectedItem.call(this,a);a&&a.blocks&&a.blocks.length?(b.flyout_.show(a.blocks),b.lastCategory_!=a&&b.flyout_.scrollToStart()):b.flyout_.hide();c!= diff --git a/blockly_uncompressed.js b/blockly_uncompressed.js index fb8afa9e7..623ac3dc5 100644 --- a/blockly_uncompressed.js +++ b/blockly_uncompressed.js @@ -59,7 +59,7 @@ goog.addDependency("../../../" + dir + "/core/field_date.js", ['Blockly.FieldDat goog.addDependency("../../../" + dir + "/core/field_dropdown.js", ['Blockly.FieldDropdown'], ['Blockly.Field', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.userAgent']); goog.addDependency("../../../" + dir + "/core/field_image.js", ['Blockly.FieldImage'], ['Blockly.Field', 'goog.dom', 'goog.math.Size', 'goog.userAgent']); goog.addDependency("../../../" + dir + "/core/field_label.js", ['Blockly.FieldLabel'], ['Blockly.Field', 'Blockly.Tooltip', 'goog.dom', 'goog.math.Size']); -goog.addDependency("../../../" + dir + "/core/field_number.js", ['Blockly.FieldNumber'], ['Blockly.FieldTextInput']); +goog.addDependency("../../../" + dir + "/core/field_number.js", ['Blockly.FieldNumber'], ['Blockly.FieldTextInput', 'goog.math']); goog.addDependency("../../../" + dir + "/core/field_textinput.js", ['Blockly.FieldTextInput'], ['Blockly.Field', 'Blockly.Msg', 'goog.asserts', 'goog.dom', 'goog.userAgent']); goog.addDependency("../../../" + dir + "/core/field_variable.js", ['Blockly.FieldVariable'], ['Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.Variables', 'goog.string']); goog.addDependency("../../../" + dir + "/core/flyout.js", ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.Comment', 'Blockly.Events', 'Blockly.WorkspaceSvg', 'goog.dom', 'goog.events', 'goog.math.Rect', 'goog.userAgent']); @@ -277,7 +277,7 @@ goog.addDependency("dom/controlrange_test.js", ['goog.dom.ControlRangeTest'], [' goog.addDependency("dom/dataset.js", ['goog.dom.dataset'], ['goog.string', 'goog.userAgent.product']); goog.addDependency("dom/dataset_test.js", ['goog.dom.datasetTest'], ['goog.dom', 'goog.dom.dataset', 'goog.testing.jsunit']); goog.addDependency("dom/dom.js", ['goog.dom', 'goog.dom.Appendable', 'goog.dom.DomHelper'], ['goog.array', 'goog.asserts', 'goog.dom.BrowserFeature', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.html.uncheckedconversions', 'goog.math.Coordinate', 'goog.math.Size', 'goog.object', 'goog.string', 'goog.string.Unicode', 'goog.userAgent']); -goog.addDependency("dom/dom_test.js", ['goog.dom.dom_test'], ['goog.array', 'goog.dom', 'goog.dom.BrowserFeature', 'goog.dom.DomHelper', 'goog.dom.InputType', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.functions', 'goog.html.testing', 'goog.object', 'goog.string.Const', 'goog.string.Unicode', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("dom/dom_test.js", ['goog.dom.dom_test'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.BrowserFeature', 'goog.dom.DomHelper', 'goog.dom.InputType', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.TypedTagName', 'goog.functions', 'goog.html.testing', 'goog.object', 'goog.string.Const', 'goog.string.Unicode', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); goog.addDependency("dom/fontsizemonitor.js", ['goog.dom.FontSizeMonitor', 'goog.dom.FontSizeMonitor.EventType'], ['goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.userAgent']); goog.addDependency("dom/fontsizemonitor_test.js", ['goog.dom.FontSizeMonitorTest'], ['goog.dom', 'goog.dom.FontSizeMonitor', 'goog.dom.TagName', 'goog.events', 'goog.events.Event', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']); goog.addDependency("dom/forms.js", ['goog.dom.forms'], ['goog.dom.InputType', 'goog.dom.TagName', 'goog.structs.Map', 'goog.window']); @@ -318,6 +318,8 @@ goog.addDependency("dom/textrange.js", ['goog.dom.TextRange'], ['goog.array', 'g goog.addDependency("dom/textrange_test.js", ['goog.dom.TextRangeTest'], ['goog.dom', 'goog.dom.ControlRange', 'goog.dom.Range', 'goog.dom.TextRange', 'goog.math.Coordinate', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); goog.addDependency("dom/textrangeiterator.js", ['goog.dom.TextRangeIterator'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.RangeIterator', 'goog.dom.TagName', 'goog.iter.StopIteration']); goog.addDependency("dom/textrangeiterator_test.js", ['goog.dom.TextRangeIteratorTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.TextRangeIterator', 'goog.iter.StopIteration', 'goog.testing.dom', 'goog.testing.jsunit']); +goog.addDependency("dom/typedtagname.js", ['goog.dom.TypedTagName'], []); +goog.addDependency("dom/typedtagname_test.js", ['goog.dom.TypedTagNameTest'], ['goog.dom.TypedTagName', 'goog.testing.jsunit']); goog.addDependency("dom/vendor.js", ['goog.dom.vendor'], ['goog.string', 'goog.userAgent']); goog.addDependency("dom/vendor_test.js", ['goog.dom.vendorTest'], ['goog.array', 'goog.dom.vendor', 'goog.labs.userAgent.util', 'goog.testing.MockUserAgent', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgentTestUtil']); goog.addDependency("dom/viewportsizemonitor.js", ['goog.dom.ViewportSizeMonitor'], ['goog.dom', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.math.Size']); @@ -855,7 +857,7 @@ goog.addDependency("module/moduleloadcallback.js", ['goog.module.ModuleLoadCallb goog.addDependency("module/moduleloadcallback_test.js", ['goog.module.ModuleLoadCallbackTest'], ['goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.functions', 'goog.module.ModuleLoadCallback', 'goog.testing.jsunit', 'goog.testing.recordFunction']); goog.addDependency("module/moduleloader.js", ['goog.module.ModuleLoader'], ['goog.Timer', 'goog.array', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventId', 'goog.events.EventTarget', 'goog.labs.userAgent.browser', 'goog.log', 'goog.module.AbstractModuleLoader', 'goog.net.BulkLoader', 'goog.net.EventType', 'goog.net.jsloader', 'goog.userAgent', 'goog.userAgent.product']); goog.addDependency("module/moduleloader_test.js", ['goog.module.ModuleLoaderTest'], ['goog.Promise', 'goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.functions', 'goog.module.ModuleLoader', 'goog.module.ModuleManager', 'goog.net.BulkLoader', 'goog.net.XmlHttp', 'goog.object', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.events.EventObserver', 'goog.testing.jsunit', 'goog.userAgent']); -goog.addDependency("module/modulemanager.js", ['goog.module.ModuleManager', 'goog.module.ModuleManager.CallbackType', 'goog.module.ModuleManager.FailureType'], ['goog.Disposable', 'goog.array', 'goog.asserts', 'goog.async.Deferred', 'goog.debug.Trace', 'goog.dispose', 'goog.log', 'goog.module', 'goog.module.ModuleInfo', 'goog.module.ModuleLoadCallback', 'goog.object']); +goog.addDependency("module/modulemanager.js", ['goog.module.ModuleManager', 'goog.module.ModuleManager.CallbackType', 'goog.module.ModuleManager.FailureType'], ['goog.Disposable', 'goog.array', 'goog.asserts', 'goog.async.Deferred', 'goog.debug.Trace', 'goog.dispose', 'goog.log', 'goog.module', 'goog.module.AbstractModuleLoader', 'goog.module.ModuleInfo', 'goog.module.ModuleLoadCallback', 'goog.object']); goog.addDependency("module/modulemanager_test.js", ['goog.module.ModuleManagerTest'], ['goog.array', 'goog.functions', 'goog.module.BaseModule', 'goog.module.ModuleManager', 'goog.testing', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']); goog.addDependency("module/testdata/modA_1.js", ['goog.module.testdata.modA_1'], []); goog.addDependency("module/testdata/modA_2.js", ['goog.module.testdata.modA_2'], ['goog.module.ModuleManager']); diff --git a/blocks_compressed.js b/blocks_compressed.js index f68898e1c..042a84ad0 100644 --- a/blocks_compressed.js +++ b/blocks_compressed.js @@ -55,7 +55,7 @@ Blockly.Blocks.logic_boolean={init:function(){this.jsonInit({message0:"%1",args0 Blockly.Blocks.logic_null={init:function(){this.jsonInit({message0:Blockly.Msg.LOGIC_NULL,output:null,colour:Blockly.Blocks.logic.HUE,tooltip:Blockly.Msg.LOGIC_NULL_TOOLTIP,helpUrl:Blockly.Msg.LOGIC_NULL_HELPURL})}}; Blockly.Blocks.logic_ternary={init:function(){this.setHelpUrl(Blockly.Msg.LOGIC_TERNARY_HELPURL);this.setColour(Blockly.Blocks.logic.HUE);this.appendValueInput("IF").setCheck("Boolean").appendField(Blockly.Msg.LOGIC_TERNARY_CONDITION);this.appendValueInput("THEN").appendField(Blockly.Msg.LOGIC_TERNARY_IF_TRUE);this.appendValueInput("ELSE").appendField(Blockly.Msg.LOGIC_TERNARY_IF_FALSE);this.setOutput(!0);this.setTooltip(Blockly.Msg.LOGIC_TERNARY_TOOLTIP);this.prevParentConnection_=null},onchange:function(a){var b= this.getInputTargetBlock("THEN"),c=this.getInputTargetBlock("ELSE"),d=this.outputConnection.targetConnection;if((b||c)&&d)for(var e=0;2>e;e++){var f=1==e?b:c;f&&!f.outputConnection.checkType_(d)&&(Blockly.Events.setGroup(a.group),d===this.prevParentConnection_?(this.unplug(),d.getSourceBlock().bumpNeighbours_()):(f.unplug(),f.bumpNeighbours_()),Blockly.Events.setGroup(!1))}this.prevParentConnection_=d}};Blockly.Blocks.loops={};Blockly.Blocks.loops.HUE=120;Blockly.Blocks.controls_repeat_ext={init:function(){this.jsonInit({message0:Blockly.Msg.CONTROLS_REPEAT_TITLE,args0:[{type:"input_value",name:"TIMES",check:"Number"}],previousStatement:null,nextStatement:null,colour:Blockly.Blocks.loops.HUE,tooltip:Blockly.Msg.CONTROLS_REPEAT_TOOLTIP,helpUrl:Blockly.Msg.CONTROLS_REPEAT_HELPURL});this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO)}}; -Blockly.Blocks.controls_repeat={init:function(){this.jsonInit({message0:Blockly.Msg.CONTROLS_REPEAT_TITLE,args0:[{type:"field_number",name:"TIMES",text:"10"}],previousStatement:null,nextStatement:null,colour:Blockly.Blocks.loops.HUE,tooltip:Blockly.Msg.CONTROLS_REPEAT_TOOLTIP,helpUrl:Blockly.Msg.CONTROLS_REPEAT_HELPURL});this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);this.getField("TIMES").setValidator(Blockly.FieldTextInput.nonnegativeIntegerValidator)}}; +Blockly.Blocks.controls_repeat={init:function(){this.jsonInit({message0:Blockly.Msg.CONTROLS_REPEAT_TITLE,args0:[{type:"field_number",name:"TIMES",value:10,min:0,precision:1}],previousStatement:null,nextStatement:null,colour:Blockly.Blocks.loops.HUE,tooltip:Blockly.Msg.CONTROLS_REPEAT_TOOLTIP,helpUrl:Blockly.Msg.CONTROLS_REPEAT_HELPURL});this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO)}}; Blockly.Blocks.controls_whileUntil={init:function(){var a=[[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE,"WHILE"],[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL,"UNTIL"]];this.setHelpUrl(Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL);this.setColour(Blockly.Blocks.loops.HUE);this.appendValueInput("BOOL").setCheck("Boolean").appendField(new Blockly.FieldDropdown(a),"MODE");this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO);this.setPreviousStatement(!0);this.setNextStatement(!0); var b=this;this.setTooltip(function(){var a=b.getFieldValue("MODE");return{WHILE:Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE,UNTIL:Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL}[a]})}}; Blockly.Blocks.controls_for={init:function(){this.jsonInit({message0:Blockly.Msg.CONTROLS_FOR_TITLE,args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"FROM",check:"Number",align:"RIGHT"},{type:"input_value",name:"TO",check:"Number",align:"RIGHT"},{type:"input_value",name:"BY",check:"Number",align:"RIGHT"}],inputsInline:!0,previousStatement:null,nextStatement:null,colour:Blockly.Blocks.loops.HUE,helpUrl:Blockly.Msg.CONTROLS_FOR_HELPURL});this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_FOR_INPUT_DO); @@ -63,7 +63,7 @@ var a=this;this.setTooltip(function(){return Blockly.Msg.CONTROLS_FOR_TOOLTIP.re Blockly.Blocks.controls_forEach={init:function(){this.jsonInit({message0:Blockly.Msg.CONTROLS_FOREACH_TITLE,args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"LIST",check:"Array"}],previousStatement:null,nextStatement:null,colour:Blockly.Blocks.loops.HUE,helpUrl:Blockly.Msg.CONTROLS_FOREACH_HELPURL});this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_DO);var a=this;this.setTooltip(function(){return Blockly.Msg.CONTROLS_FOREACH_TOOLTIP.replace("%1", a.getFieldValue("VAR"))})},customContextMenu:Blockly.Blocks.controls_for.customContextMenu}; Blockly.Blocks.controls_flow_statements={init:function(){var a=[[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK,"BREAK"],[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE,"CONTINUE"]];this.setHelpUrl(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL);this.setColour(Blockly.Blocks.loops.HUE);this.appendDummyInput().appendField(new Blockly.FieldDropdown(a),"FLOW");this.setPreviousStatement(!0);var b=this;this.setTooltip(function(){var a=b.getFieldValue("FLOW");return{BREAK:Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK, -CONTINUE:Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}[a]})},onchange:function(a){a=!1;var b=this;do{if(-1!=this.LOOP_TYPES.indexOf(b.type)){a=!0;break}b=b.getSurroundParent()}while(b);a?this.setWarningText(null):this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING)},LOOP_TYPES:["controls_repeat","controls_repeat_ext","controls_forEach","controls_for","controls_whileUntil"]};Blockly.Blocks.math={};Blockly.Blocks.math.HUE=230;Blockly.Blocks.math_number={init:function(){this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL);this.setColour(Blockly.Blocks.math.HUE);this.appendDummyInput().appendField(new Blockly.FieldNumber("0",Blockly.FieldTextInput.numberValidator),"NUM");this.setOutput(!0,"Number");var a=this;this.setTooltip(function(){var b=a.getParent();return b&&b.getInputsInline()&&b.tooltip||Blockly.Msg.MATH_NUMBER_TOOLTIP})}}; +CONTINUE:Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}[a]})},onchange:function(a){a=!1;var b=this;do{if(-1!=this.LOOP_TYPES.indexOf(b.type)){a=!0;break}b=b.getSurroundParent()}while(b);a?this.setWarningText(null):this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING)},LOOP_TYPES:["controls_repeat","controls_repeat_ext","controls_forEach","controls_for","controls_whileUntil"]};Blockly.Blocks.math={};Blockly.Blocks.math.HUE=230;Blockly.Blocks.math_number={init:function(){this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL);this.setColour(Blockly.Blocks.math.HUE);this.appendDummyInput().appendField(new Blockly.FieldNumber("0"),"NUM");this.setOutput(!0,"Number");var a=this;this.setTooltip(function(){var b=a.getParent();return b&&b.getInputsInline()&&b.tooltip||Blockly.Msg.MATH_NUMBER_TOOLTIP})}}; Blockly.Blocks.math_arithmetic={init:function(){this.jsonInit({message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Number"},{type:"field_dropdown",name:"OP",options:[[Blockly.Msg.MATH_ADDITION_SYMBOL,"ADD"],[Blockly.Msg.MATH_SUBTRACTION_SYMBOL,"MINUS"],[Blockly.Msg.MATH_MULTIPLICATION_SYMBOL,"MULTIPLY"],[Blockly.Msg.MATH_DIVISION_SYMBOL,"DIVIDE"],[Blockly.Msg.MATH_POWER_SYMBOL,"POWER"]]},{type:"input_value",name:"B",check:"Number"}],inputsInline:!0,output:"Number",colour:Blockly.Blocks.math.HUE, helpUrl:Blockly.Msg.MATH_ARITHMETIC_HELPURL});var a=this;this.setTooltip(function(){var b=a.getFieldValue("OP");return{ADD:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_ADD,MINUS:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MINUS,MULTIPLY:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MULTIPLY,DIVIDE:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_DIVIDE,POWER:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_POWER}[b]})}}; Blockly.Blocks.math_single={init:function(){this.jsonInit({message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[[Blockly.Msg.MATH_SINGLE_OP_ROOT,"ROOT"],[Blockly.Msg.MATH_SINGLE_OP_ABSOLUTE,"ABS"],["-","NEG"],["ln","LN"],["log10","LOG10"],["e^","EXP"],["10^","POW10"]]},{type:"input_value",name:"NUM",check:"Number"}],output:"Number",colour:Blockly.Blocks.math.HUE,helpUrl:Blockly.Msg.MATH_SINGLE_HELPURL});var a=this;this.setTooltip(function(){var b=a.getFieldValue("OP");return{ROOT:Blockly.Msg.MATH_SINGLE_TOOLTIP_ROOT, diff --git a/dart_compressed.js b/dart_compressed.js index edc056c6d..963bbc380 100644 --- a/dart_compressed.js +++ b/dart_compressed.js @@ -19,11 +19,11 @@ Blockly.Dart.colour_blend=function(a){var b=Blockly.Dart.valueToCode(a,"COLOUR1" " num bn = (b1 * (1 - ratio) + b2 * ratio).round();"," String bs = bn.toInt().toRadixString(16);"," rs = '0$rs';"," rs = rs.substring(rs.length - 2);"," gs = '0$gs';"," gs = gs.substring(gs.length - 2);"," bs = '0$bs';"," bs = bs.substring(bs.length - 2);"," return '#$rs$gs$bs';","}"])+"("+b+", "+c+", "+a+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};Blockly.Dart.lists={};Blockly.Dart.addReservedWords("Math");Blockly.Dart.lists_create_empty=function(a){return["[]",Blockly.Dart.ORDER_ATOMIC]};Blockly.Dart.lists_create_with=function(a){for(var b=Array(a.itemCount_),c=0;c Date: Tue, 21 Jun 2016 15:56:00 -0700 Subject: [PATCH 16/23] Check if matrix is null in mouseToSvg --- core/utils.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/utils.js b/core/utils.js index 19ccc4cc8..89644fff9 100644 --- a/core/utils.js +++ b/core/utils.js @@ -315,6 +315,10 @@ Blockly.mouseToSvg = function(e, svg, matrix) { var svgPoint = svg.createSVGPoint(); svgPoint.x = e.clientX; svgPoint.y = e.clientY; + + if (!matrix) { + matrix = svg.getScreenCTM().inverse(); + } return svgPoint.matrixTransform(matrix); }; From 98617d8ddc15dd1fd6ee7a8c58d810a83e2b03fd Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Wed, 22 Jun 2016 12:56:56 -0700 Subject: [PATCH 17/23] Move tokenizeIntepolation into Blockly.utils namespace. --- core/block.js | 2 +- core/blockly.js | 3 ++- core/utils.js | 2 +- tests/jsunit/blockly_test.js | 14 +++++++------- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/core/block.js b/core/block.js index 36a703171..584a9fa56 100644 --- a/core/block.js +++ b/core/block.js @@ -1021,7 +1021,7 @@ Blockly.Block.prototype.jsonInit = function(json) { * @private */ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { - var tokens = Blockly.tokenizeInterpolation(message); + var tokens = Blockly.utils.tokenizeInterpolation(message); // Interpolate the arguments. Build a list of elements. var indexDup = []; var indexCount = 0; diff --git a/core/blockly.js b/core/blockly.js index 4fd5de0ca..c51191b08 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -549,7 +549,8 @@ Blockly.addChangeListener = function(func) { /** * Returns the main workspace. Returns the last used main workspace (based on - * focus). + * focus). Try not to use this function, particularly if there are multiple + * Blockly instances on a page. * @return {!Blockly.Workspace} The main workspace. */ Blockly.getMainWorkspace = function() { diff --git a/core/utils.js b/core/utils.js index 89644fff9..fe13572ac 100644 --- a/core/utils.js +++ b/core/utils.js @@ -423,7 +423,7 @@ Blockly.isNumber = function(str) { * @param {string} message Text containing interpolation tokens. * @return {!Array.} Array of strings and numbers. */ -Blockly.tokenizeInterpolation = function(message) { +Blockly.utils.tokenizeInterpolation = function(message) { var tokens = []; var chars = message.split(''); chars.push(''); // End marker. diff --git a/tests/jsunit/blockly_test.js b/tests/jsunit/blockly_test.js index 88f0cebaa..0278b8442 100644 --- a/tests/jsunit/blockly_test.js +++ b/tests/jsunit/blockly_test.js @@ -123,18 +123,18 @@ function test_commonWordSuffix() { } function test_tokenizeInterpolation() { - var tokens = Blockly.tokenizeInterpolation(''); + var tokens = Blockly.utils.tokenizeInterpolation(''); assertArrayEquals('Null interpolation', [], tokens); - tokens = Blockly.tokenizeInterpolation('Hello'); + tokens = Blockly.utils.tokenizeInterpolation('Hello'); assertArrayEquals('No interpolation', ['Hello'], tokens); - tokens = Blockly.tokenizeInterpolation('Hello%World'); + tokens = Blockly.utils.tokenizeInterpolation('Hello%World'); assertArrayEquals('Unescaped %.', ['Hello%World'], tokens); - tokens = Blockly.tokenizeInterpolation('Hello%%World'); + tokens = Blockly.utils.tokenizeInterpolation('Hello%%World'); assertArrayEquals('Escaped %.', ['Hello%World'], tokens); - tokens = Blockly.tokenizeInterpolation('Hello %1 World'); + tokens = Blockly.utils.tokenizeInterpolation('Hello %1 World'); assertArrayEquals('Interpolation.', ['Hello ', 1, ' World'], tokens); - tokens = Blockly.tokenizeInterpolation('%123Hello%456World%789'); + tokens = Blockly.utils.tokenizeInterpolation('%123Hello%456World%789'); assertArrayEquals('Interpolations.', [123, 'Hello', 456, 'World', 789], tokens); - tokens = Blockly.tokenizeInterpolation('%%%x%%0%00%01%'); + tokens = Blockly.utils.tokenizeInterpolation('%%%x%%0%00%01%'); assertArrayEquals('Torture interpolations.', ['%%x%0', 0, 1, '%'], tokens); } From 685288836f96f46ed85b7cce08518ec3ff409506 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Wed, 22 Jun 2016 12:58:02 -0700 Subject: [PATCH 18/23] Use simpler message interpolation in Code demo. --- demos/code/code.js | 23 +++++++---------------- demos/code/index.html | 30 +++++++++++++++--------------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/demos/code/code.js b/demos/code/code.js index a55633754..2dfebbbfb 100644 --- a/demos/code/code.js +++ b/demos/code/code.js @@ -382,7 +382,12 @@ Code.init = function() { }; window.addEventListener('resize', onresize, false); - var toolbox = document.getElementById('toolbox'); + // Interpolate translated messages into toolbox. + var toolboxText = document.getElementById('toolbox').outerHTML; + toolboxText = toolboxText.replace(/{(\w+)}/g, + function(m, p1) {return MSG[p1]}); + var toolboxXml = Blockly.Xml.textToDom(toolboxText); + Code.workspace = Blockly.inject('content_blocks', {grid: {spacing: 25, @@ -391,7 +396,7 @@ Code.init = function() { snap: true}, media: '../../media/', rtl: rtl, - toolbox: toolbox, + toolbox: toolboxXml, zoom: {controls: true, wheel: true} @@ -481,20 +486,6 @@ Code.initLanguage = function() { document.getElementById('linkButton').title = MSG['linkTooltip']; document.getElementById('runButton').title = MSG['runTooltip']; document.getElementById('trashButton').title = MSG['trashTooltip']; - - var categories = ['catLogic', 'catLoops', 'catMath', 'catText', 'catLists', - 'catColour', 'catVariables', 'catFunctions']; - for (var i = 0, cat; cat = categories[i]; i++) { - document.getElementById(cat).setAttribute('name', MSG[cat]); - } - var textVars = document.getElementsByClassName('textVar'); - for (var i = 0, textVar; textVar = textVars[i]; i++) { - textVar.textContent = MSG['textVariable']; - } - var listVars = document.getElementsByClassName('listVar'); - for (var i = 0, listVar; listVar = listVars[i]; i++) { - listVar.textContent = MSG['listVariable']; - } }; /** diff --git a/demos/code/index.html b/demos/code/index.html index 4e12249dd..54a59acda 100644 --- a/demos/code/index.html +++ b/demos/code/index.html @@ -74,7 +74,7 @@ From 91b10cae2fccc341640b5bcc7bdb09dbb9979483 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Wed, 22 Jun 2016 13:00:29 -0700 Subject: [PATCH 19/23] Create console stub for IE 9. --- core/blockly.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/blockly.js b/core/blockly.js index c51191b08..cda45992e 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -557,6 +557,14 @@ Blockly.getMainWorkspace = function() { return Blockly.mainWorkspace; }; +// IE9 does not have a console. Create a stub to stop errors. +if (!goog.global['console']) { + goog.global['console'] = { + 'log': function() {}, + 'warn': function() {} + }; +} + // Export symbols that would otherwise be renamed by Closure compiler. if (!goog.global['Blockly']) { goog.global['Blockly'] = {}; From 425513b7297da4f194a8595d1f0a4169afb79769 Mon Sep 17 00:00:00 2001 From: Andrew n marshall Date: Wed, 22 Jun 2016 13:11:19 -0700 Subject: [PATCH 20/23] Don't output blockId if not set (e.g., toolbox category event). (#443) --- core/events.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/events.js b/core/events.js index f1971478d..f78222d03 100644 --- a/core/events.js +++ b/core/events.js @@ -303,8 +303,10 @@ Blockly.Events.Abstract = function(block) { Blockly.Events.Abstract.prototype.toJson = function() { var json = { 'type': this.type, - 'blockId': this.blockId }; + if (this.blockId) { + json['blockId'] = this.blockId; + } if (this.group) { json['group'] = this.group; } From 0be0cc89c7f686ed6377248f7e1caa76578669c3 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Wed, 22 Jun 2016 15:07:07 -0700 Subject: [PATCH 21/23] Second version of FieldNumber API. --- core/block.js | 6 ++-- core/field_number.js | 67 ++++++++++-------------------------- demos/blockfactory/blocks.js | 4 +-- 3 files changed, 22 insertions(+), 55 deletions(-) diff --git a/core/block.js b/core/block.js index 584a9fa56..92b533be8 100644 --- a/core/block.js +++ b/core/block.js @@ -1111,10 +1111,8 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { element['width'], element['height'], element['alt']); break; case 'field_number': - field = new Blockly.FieldNumber(element['value']); - field.setPrecision(element['precision']); - field.setMin(element['min']); - field.setMax(element['max']); + field = new Blockly.FieldNumber(element['value'], + element['min'], element['max'], element['precision']); break; case 'field_date': if (Blockly.FieldDate) { diff --git a/core/field_number.js b/core/field_number.js index 0ce38bf1e..000022998 100644 --- a/core/field_number.js +++ b/core/field_number.js @@ -32,6 +32,9 @@ goog.require('goog.math'); /** * Class for an editable number field. * @param {string} value The initial content of the field. + * @param {number|string|undefined} opt_min Minimum value. + * @param {number|string|undefined} opt_max Maximum value. + * @param {number|string|undefined} opt_precision Precision for value. * @param {Function=} opt_validator An optional function that is called * to validate any constraints on what the user entered. Takes the new * text as an argument and returns either the accepted text, a replacement @@ -39,66 +42,32 @@ goog.require('goog.math'); * @extends {Blockly.FieldTextInput} * @constructor */ -Blockly.FieldNumber = function(value, opt_validator) { +Blockly.FieldNumber = + function(value, opt_min, opt_max, opt_precision, opt_validator) { Blockly.FieldNumber.superClass_.constructor.call(this, value, opt_validator); + this.setConstraints(opt_min, opt_max, opt_precision); }; goog.inherits(Blockly.FieldNumber, Blockly.FieldTextInput); /** - * Steps between allowed numbers. - * @private - * @type {number} - */ -Blockly.FieldNumber.prototype.precision_ = 0; - -/** - * Minimum allowed value. - * @private - * @type {number} - */ -Blockly.FieldNumber.prototype.min_ = -Infinity; - -/** - * Maximum allowed value. - * @private - * @type {number} - */ -Blockly.FieldNumber.prototype.max_ = Infinity; - -/** + * Set the maximum, minimum and precision constraints on this field. + * Any of these properties may be undefiend or NaN to be disabled. * Setting precision (usually a power of 10) enforces a minimum step between * values. That is, the user's value will rounded to the closest multiple of * precision. The least significant digit place is inferred from the precision. * Integers values can be enforces by choosing an integer precision. + * @param {number|string|undefined} min Minimum value. + * @param {number|string|undefined} max Maximum value. * @param {number|string|undefined} precision Precision for value. */ -Blockly.FieldNumber.prototype.setPrecision = function(precision) { +Blockly.FieldNumber.prototype.setConstraints = function(min, max, precision) { precision = parseFloat(precision); - if (!isNaN(precision)) { - this.precision_ = precision; - } -}; - -/** - * Set a maximum limit on this field's value. - * @param {number|string|undefined} max Maximum value. - */ -Blockly.FieldNumber.prototype.setMin = function(min) { + this.precision_ = isNaN(precision) ? 0 : precision; min = parseFloat(min); - if (!isNaN(min)) { - this.min_ = min; - } -}; - -/** - * Set a maximum limit on this field's value. - * @param {number|string|undefined} max Minimum value. - */ -Blockly.FieldNumber.prototype.setMax = function(max) { + this.min_ = isNaN(min) ? -Infinity : min; max = parseFloat(max); - if (!isNaN(max)) { - this.max_ = max; - } + this.max_ = isNaN(max) ? Infinity : max; + this.setValue(this.numberValidator(this.getValue)); }; /** @@ -117,7 +86,7 @@ Blockly.FieldNumber.prototype.setValidator = function(handler) { if (v1 === undefined) { v1 = value; } - var v2 = Blockly.FieldNumber.numberValidator.call(this, v1); + var v2 = this.numberValidator(v1); if (v2 === undefined) { v2 = v1; } @@ -125,7 +94,7 @@ Blockly.FieldNumber.prototype.setValidator = function(handler) { return v2 === value ? undefined : v2; }; } else { - wrappedHandler = Blockly.FieldNumber.numberValidator; + wrappedHandler = this.numberValidator; } Blockly.FieldNumber.superClass_.setValidator.call(this, wrappedHandler); }; @@ -135,7 +104,7 @@ Blockly.FieldNumber.prototype.setValidator = function(handler) { * @param {string} text The user's text. * @return {?string} A string representing a valid number, or null if invalid. */ -Blockly.FieldNumber.numberValidator = function(text) { +Blockly.FieldNumber.prototype.numberValidator = function(text) { if (text === null) { return null; } diff --git a/demos/blockfactory/blocks.js b/demos/blockfactory/blocks.js index f2d8dd290..5008e6a20 100644 --- a/demos/blockfactory/blocks.js +++ b/demos/blockfactory/blocks.js @@ -470,9 +470,9 @@ Blockly.Blocks['field_image'] = { .appendField(new Blockly.FieldTextInput(src), 'SRC'); this.appendDummyInput() .appendField('width') - .appendField(new Blockly.FieldNumber('15'), 'WIDTH') + .appendField(new Blockly.FieldNumber('15', 0, NaN, 1), 'WIDTH') .appendField('height') - .appendField(new Blockly.FieldNumber('15'), 'HEIGHT') + .appendField(new Blockly.FieldNumber('15', 0, NaN, 1), 'HEIGHT') .appendField('alt text') .appendField(new Blockly.FieldTextInput('*'), 'ALT'); this.setPreviousStatement(true, 'Field'); From 16fef9f2e2a09a36df72e01333a1258bee13e44f Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Mon, 27 Jun 2016 15:52:35 -0700 Subject: [PATCH 22/23] Reduce more unneeded parentheses in JS and Python. --- generators/javascript.js | 32 ++++++++++++++++++++------------ generators/python.js | 25 +++++++++++++++++++++---- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/generators/javascript.js b/generators/javascript.js index 1254c88e8..b9e44929c 100644 --- a/generators/javascript.js +++ b/generators/javascript.js @@ -71,23 +71,23 @@ Blockly.JavaScript.addReservedWords( * https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence */ Blockly.JavaScript.ORDER_ATOMIC = 0; // 0 "" ... -Blockly.JavaScript.ORDER_MEMBER = 1.1; // . [] -Blockly.JavaScript.ORDER_NEW = 1.2; // new +Blockly.JavaScript.ORDER_NEW = 1.1; // new +Blockly.JavaScript.ORDER_MEMBER = 1.2; // . [] Blockly.JavaScript.ORDER_FUNCTION_CALL = 2; // () Blockly.JavaScript.ORDER_INCREMENT = 3; // ++ Blockly.JavaScript.ORDER_DECREMENT = 3; // -- -Blockly.JavaScript.ORDER_LOGICAL_NOT = 4.1; // ! -Blockly.JavaScript.ORDER_BITWISE_NOT = 4.2; // ~ -Blockly.JavaScript.ORDER_UNARY_PLUS = 4.3; // + -Blockly.JavaScript.ORDER_UNARY_NEGATION = 4.4; // - +Blockly.JavaScript.ORDER_BITWISE_NOT = 4.1; // ~ +Blockly.JavaScript.ORDER_UNARY_PLUS = 4.2; // + +Blockly.JavaScript.ORDER_UNARY_NEGATION = 4.3; // - +Blockly.JavaScript.ORDER_LOGICAL_NOT = 4.4; // ! Blockly.JavaScript.ORDER_TYPEOF = 4.5; // typeof Blockly.JavaScript.ORDER_VOID = 4.6; // void Blockly.JavaScript.ORDER_DELETE = 4.7; // delete -Blockly.JavaScript.ORDER_MULTIPLICATION = 5.1; // * -Blockly.JavaScript.ORDER_DIVISION = 5.2; // / +Blockly.JavaScript.ORDER_DIVISION = 5.1; // / +Blockly.JavaScript.ORDER_MULTIPLICATION = 5.2; // * Blockly.JavaScript.ORDER_MODULUS = 5.3; // % -Blockly.JavaScript.ORDER_ADDITION = 6.1; // + -Blockly.JavaScript.ORDER_SUBTRACTION = 6.2; // - +Blockly.JavaScript.ORDER_SUBTRACTION = 6.1; // - +Blockly.JavaScript.ORDER_ADDITION = 6.2; // + Blockly.JavaScript.ORDER_BITWISE_SHIFT = 7; // << >> >>> Blockly.JavaScript.ORDER_RELATIONAL = 8; // < <= > >= Blockly.JavaScript.ORDER_IN = 8; // in @@ -108,12 +108,20 @@ Blockly.JavaScript.ORDER_NONE = 99; // (...) * @type {!Array.>} */ Blockly.JavaScript.ORDER_OVERRIDES = [ - // (foo()).bar() -> foo().bar() + // (foo()).bar -> foo().bar // (foo())[0] -> foo()[0] [Blockly.JavaScript.ORDER_FUNCTION_CALL, Blockly.JavaScript.ORDER_MEMBER], - // (foo[0])[1] -> foo[0][1] + // (foo())() -> foo()() + [Blockly.JavaScript.ORDER_FUNCTION_CALL, Blockly.JavaScript.ORDER_FUNCTION_CALL], // (foo.bar).baz -> foo.bar.baz + // (foo.bar)[0] -> foo.bar[0] + // (foo[0]).bar -> foo[0].bar + // (foo[0])[1] -> foo[0][1] [Blockly.JavaScript.ORDER_MEMBER, Blockly.JavaScript.ORDER_MEMBER], + // (foo.bar)() -> foo.bar() + // (foo[0])() -> foo[0]() + [Blockly.JavaScript.ORDER_MEMBER, Blockly.JavaScript.ORDER_FUNCTION_CALL] + // !(!foo) -> !!foo [Blockly.JavaScript.ORDER_LOGICAL_NOT, Blockly.JavaScript.ORDER_LOGICAL_NOT], // a * (b * c) -> a * b * c diff --git a/generators/python.js b/generators/python.js index 18fb26a3e..020e30107 100644 --- a/generators/python.js +++ b/generators/python.js @@ -59,8 +59,8 @@ Blockly.Python.addReservedWords( Blockly.Python.ORDER_ATOMIC = 0; // 0 "" ... Blockly.Python.ORDER_COLLECTION = 1; // tuples, lists, dictionaries Blockly.Python.ORDER_STRING_CONVERSION = 1; // `expression...` -Blockly.Python.ORDER_MEMBER = 2; // . [] -Blockly.Python.ORDER_FUNCTION_CALL = 2; // () +Blockly.Python.ORDER_MEMBER = 2.1; // . [] +Blockly.Python.ORDER_FUNCTION_CALL = 2.2; // () Blockly.Python.ORDER_EXPONENTIATION = 3; // ** Blockly.Python.ORDER_UNARY_SIGN = 4; // + - Blockly.Python.ORDER_BITWISE_NOT = 4; // ~ @@ -84,9 +84,26 @@ Blockly.Python.ORDER_NONE = 99; // (...) * @type {!Array.>} */ Blockly.Python.ORDER_OVERRIDES = [ - // (foo()).bar() -> foo().bar() + // (foo()).bar -> foo().bar // (foo())[0] -> foo()[0] - [Blockly.Python.ORDER_FUNCTION_CALL, Blockly.Python.ORDER_MEMBER] + [Blockly.Python.ORDER_FUNCTION_CALL, Blockly.Python.ORDER_MEMBER], + // (foo())() -> foo()() + [Blockly.Python.ORDER_FUNCTION_CALL, Blockly.Python.ORDER_FUNCTION_CALL], + // (foo.bar).baz -> foo.bar.baz + // (foo.bar)[0] -> foo.bar[0] + // (foo[0]).bar -> foo[0].bar + // (foo[0])[1] -> foo[0][1] + [Blockly.Python.ORDER_MEMBER, Blockly.Python.ORDER_MEMBER], + // (foo.bar)() -> foo.bar() + // (foo[0])() -> foo[0]() + [Blockly.Python.ORDER_MEMBER, Blockly.Python.ORDER_FUNCTION_CALL] + + // not (not foo) -> not not foo + [Blockly.Python.ORDER_LOGICAL_NOT, Blockly.Python.ORDER_LOGICAL_NOT], + // a and (b and c) -> a and b and c + [Blockly.Python.ORDER_LOGICAL_AND, Blockly.Python.ORDER_LOGICAL_AND], + // a or (b or c) -> a or b or c + [Blockly.Python.ORDER_LOGICAL_OR, Blockly.Python.ORDER_LOGICAL_OR] ]; /** From aca074891d2c6f9009f9176f7ede96e4bf457b0d Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Mon, 27 Jun 2016 17:27:08 -0700 Subject: [PATCH 23/23] Fix some problems with flyout rendering (#461) --- core/flyout.js | 68 +++++++++++++++++------------------------------ core/scrollbar.js | 2 +- 2 files changed, 25 insertions(+), 45 deletions(-) diff --git a/core/flyout.js b/core/flyout.js index 85aaee983..e8aa2f872 100644 --- a/core/flyout.js +++ b/core/flyout.js @@ -286,8 +286,8 @@ Blockly.Flyout.prototype.getMetrics_ = function() { contentWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale, viewTop: -this.workspace_.scrollY, viewLeft: -this.workspace_.scrollX, - contentTop: 0, // TODO: #349 - contentLeft: 0, // TODO: #349 + contentTop: optionBox.y, + contentLeft: optionBox.x, absoluteTop: absoluteTop, absoluteLeft: absoluteLeft }; @@ -426,9 +426,8 @@ Blockly.Flyout.prototype.setBackgroundPathVertical_ = function(width, height) { * rounded corners. * @private */ -Blockly.Flyout.prototype.setBackgroundPathHorizontal_ = - function(width, height) { - /* eslint-disable indent */ +Blockly.Flyout.prototype.setBackgroundPathHorizontal_ = function(width, + height) { var atTop = this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP; // Start at top left. var path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)]; @@ -461,7 +460,7 @@ Blockly.Flyout.prototype.setBackgroundPathHorizontal_ = path.push('z'); } this.svgBackground_.setAttribute('d', path.join(' ')); -}; /* eslint-enable indent */ +}; /** * Scroll the flyout to the top. @@ -588,7 +587,6 @@ Blockly.Flyout.prototype.show = function(xmlList) { } this.reflow(); - this.offsetHorizontalRtlBlocks(this.workspace_.getTopBlocks(false)); this.filterForCapacity_(); // Correctly position the flyout's scrollbar when it opens. @@ -605,9 +603,12 @@ Blockly.Flyout.prototype.show = function(xmlList) { * @private */ Blockly.Flyout.prototype.layoutBlocks_ = function(blocks, gaps) { - var margin = this.MARGIN * this.workspace_.scale; + var margin = this.MARGIN; var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH; var cursorY = margin; + if (this.horizontalLayout_ && this.RTL) { + blocks = blocks.reverse(); + } for (var i = 0, block; block = blocks[i]; i++) { var allBlocks = block.getDescendants(); for (var j = 0, child; child = allBlocks[j]; j++) { @@ -623,8 +624,13 @@ Blockly.Flyout.prototype.layoutBlocks_ = function(blocks, gaps) { if (this.horizontalLayout_) { cursorX += tab; } - block.moveBy((this.horizontalLayout_ && this.RTL) ? -cursorX : cursorX, - cursorY); + + if (this.horizontalLayout_ && this.RTL) { + block.moveBy(cursorX + blockHW.width - tab, cursorY); + } else { + block.moveBy(cursorX, cursorY); + } + if (this.horizontalLayout_) { cursorX += (blockHW.width + gaps[i] - tab); } else { @@ -1074,10 +1080,9 @@ Blockly.Flyout.prototype.reflowVertical = function(blocks) { if (this.RTL) { // With the flyoutWidth known, right-align the blocks. var oldX = block.getRelativeToSurfaceXY().x; - var dx = flyoutWidth - this.MARGIN; - dx /= this.workspace_.scale; - dx -= Blockly.BlockSvg.TAB_WIDTH; - block.moveBy(dx - oldX, 0); + var newX = flyoutWidth / this.workspace_.scale - this.MARGIN; + newX -= Blockly.BlockSvg.TAB_WIDTH; + block.moveBy(newX - oldX, 0); } if (block.flyoutRect_) { block.flyoutRect_.setAttribute('width', blockHW.width); @@ -1109,41 +1114,16 @@ Blockly.Flyout.prototype.reflowVertical = function(blocks) { * Reflow blocks and their buttons. */ Blockly.Flyout.prototype.reflow = function() { + if (this.reflowWrapper_) { + this.workspace_.removeChangeListener(this.reflowWrapper_); + } var blocks = this.workspace_.getTopBlocks(false); if (this.horizontalLayout_) { this.reflowHorizontal(blocks); } else { this.reflowVertical(blocks); } -}; - -/** - * In the horizontal RTL case all of the blocks will be laid out to the left of - * the origin, but we won't know how big the workspace is until the layout pass - * is done. - * Now that it's done, shunt all the blocks to be right of the origin. - * @param {!Array} blocks The blocks to reposition. - */ -Blockly.Flyout.prototype.offsetHorizontalRtlBlocks = function(blocks) { - if (this.horizontalLayout_ && this.RTL) { - // We don't know this workspace's view width yet. - this.position(); - try { - var optionBox = this.workspace_.getCanvas().getBBox(); - } catch (e) { - // Firefox has trouble with hidden elements (Bug 528969). - optionBox = {height: 0, y: 0, width: 0, x: 0}; - } - - var offset = Math.max(-optionBox.x + this.MARGIN, - this.width_ / this.workspace_.scale); - - for (var i = 0, block; block = blocks[i]; i++) { - block.moveBy(offset, 0); - if (block.flyoutRect_) { - block.flyoutRect_.setAttribute('x', - offset + Number(block.flyoutRect_.getAttribute('x'))); - } - } + if (this.reflowWrapper_) { + this.workspace_.addChangeListener(this.reflowWrapper_); } }; diff --git a/core/scrollbar.js b/core/scrollbar.js index 6381b928f..da2dd942d 100644 --- a/core/scrollbar.js +++ b/core/scrollbar.js @@ -694,7 +694,7 @@ Blockly.Scrollbar.prototype.onMouseUpHandle_ = function() { * @private */ Blockly.Scrollbar.prototype.constrainHandle_ = function(value) { - if (value <= 0 || isNaN(value)) { + if (value <= 0 || isNaN(value) || this.scrollViewSize_ < this.handleLength_) { value = 0; } else { value = Math.min(value, this.scrollViewSize_ - this.handleLength_);